From ca8beaed148a1fc65d181aedeb91b23c262886e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20R=2E=20Bald=C3=A9?= Date: Wed, 20 Nov 2024 01:10:30 +0000 Subject: [PATCH 001/340] Add and test events in `pallet-conviction-voting` (#6544) # Description https://github.com/paritytech/polkadot-sdk/pull/4613 introduced events for `pallet_conviction_voting::{vote, remove_vote, remove_other_vote}`. However: 1. it did not include `unlock` 2. the pallet's unit tests were missing an update ## Integration N/A ## Review Notes This is as https://github.com/paritytech/polkadot-sdk/pull/6261 was, so it is a trivial change. --- prdoc/pr_6544.prdoc | 14 +++ substrate/frame/conviction-voting/src/lib.rs | 7 +- .../frame/conviction-voting/src/tests.rs | 85 ++++++++++++++++--- .../frame/conviction-voting/src/types.rs | 9 +- 4 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 prdoc/pr_6544.prdoc diff --git a/prdoc/pr_6544.prdoc b/prdoc/pr_6544.prdoc new file mode 100644 index 000000000000..f2bc9627697d --- /dev/null +++ b/prdoc/pr_6544.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add and test events to conviction voting pallet + +doc: + - audience: Runtime Dev + description: | + Add event for the unlocking of an expired conviction vote's funds, and test recently added + voting events. + +crates: + - name: pallet-conviction-voting + bump: major diff --git a/substrate/frame/conviction-voting/src/lib.rs b/substrate/frame/conviction-voting/src/lib.rs index 85da1aed3c27..31bd6b85ec86 100644 --- a/substrate/frame/conviction-voting/src/lib.rs +++ b/substrate/frame/conviction-voting/src/lib.rs @@ -171,10 +171,12 @@ pub mod pallet { Delegated(T::AccountId, T::AccountId), /// An \[account\] has cancelled a previous delegation operation. Undelegated(T::AccountId), - /// An account that has voted + /// An account has voted Voted { who: T::AccountId, vote: AccountVote> }, - /// A vote that been removed + /// A vote has been removed VoteRemoved { who: T::AccountId, vote: AccountVote> }, + /// The lockup period of a conviction vote expired, and the funds have been unlocked. + VoteUnlocked { who: T::AccountId, class: ClassOf }, } #[pallet::error] @@ -315,6 +317,7 @@ pub mod pallet { ensure_signed(origin)?; let target = T::Lookup::lookup(target)?; Self::update_lock(&class, &target); + Self::deposit_event(Event::VoteUnlocked { who: target, class }); Ok(()) } diff --git a/substrate/frame/conviction-voting/src/tests.rs b/substrate/frame/conviction-voting/src/tests.rs index 37cdd7a5b338..dd9ee33ee183 100644 --- a/substrate/frame/conviction-voting/src/tests.rs +++ b/substrate/frame/conviction-voting/src/tests.rs @@ -238,27 +238,52 @@ fn basic_stuff() { fn basic_voting_works() { new_test_ext().execute_with(|| { assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(2, 5))); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::Voted { + who: 1, + vote: aye(2, 5), + })); assert_eq!(tally(3), Tally::from_parts(10, 0, 2)); assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, nay(2, 5))); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::Voted { + who: 1, + vote: nay(2, 5), + })); assert_eq!(tally(3), Tally::from_parts(0, 10, 0)); assert_eq!(Balances::usable_balance(1), 8); assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(5, 1))); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::Voted { + who: 1, + vote: aye(5, 1), + })); assert_eq!(tally(3), Tally::from_parts(5, 0, 5)); assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, nay(5, 1))); assert_eq!(tally(3), Tally::from_parts(0, 5, 0)); assert_eq!(Balances::usable_balance(1), 5); assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, aye(10, 0))); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::Voted { + who: 1, + vote: aye(10, 0), + })); assert_eq!(tally(3), Tally::from_parts(1, 0, 10)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, nay(10, 0))); assert_eq!(tally(3), Tally::from_parts(0, 1, 0)); assert_eq!(Balances::usable_balance(1), 0); assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), None, 3)); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::VoteRemoved { + who: 1, + vote: nay(10, 0), + })); assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), class(3), 1)); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::VoteUnlocked { + who: 1, + class: class(3), + })); assert_eq!(Balances::usable_balance(1), 10); }); } @@ -267,15 +292,32 @@ fn basic_voting_works() { fn split_voting_works() { new_test_ext().execute_with(|| { assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, split(10, 0))); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::Voted { + who: 1, + vote: split(10, 0), + })); assert_eq!(tally(3), Tally::from_parts(1, 0, 10)); + assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, split(5, 5))); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::Voted { + who: 1, + vote: split(5, 5), + })); assert_eq!(tally(3), Tally::from_parts(0, 0, 5)); assert_eq!(Balances::usable_balance(1), 0); assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), None, 3)); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::VoteRemoved { + who: 1, + vote: split(5, 5), + })); assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), class(3), 1)); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::VoteUnlocked { + who: 1, + class: class(3), + })); assert_eq!(Balances::usable_balance(1), 10); }); } @@ -284,25 +326,48 @@ fn split_voting_works() { fn abstain_voting_works() { new_test_ext().execute_with(|| { assert_ok!(Voting::vote(RuntimeOrigin::signed(1), 3, split_abstain(0, 0, 10))); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::Voted { + who: 1, + vote: split_abstain(0, 0, 10), + })); assert_eq!(tally(3), Tally::from_parts(0, 0, 10)); - assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 3, split_abstain(0, 0, 20))); - assert_eq!(tally(3), Tally::from_parts(0, 0, 30)); - assert_ok!(Voting::vote(RuntimeOrigin::signed(2), 3, split_abstain(10, 0, 10))); - assert_eq!(tally(3), Tally::from_parts(1, 0, 30)); + + assert_ok!(Voting::vote(RuntimeOrigin::signed(6), 3, split_abstain(10, 0, 20))); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::Voted { + who: 6, + vote: split_abstain(10, 0, 20), + })); + assert_eq!(tally(3), Tally::from_parts(1, 0, 40)); + + assert_ok!(Voting::vote(RuntimeOrigin::signed(6), 3, split_abstain(0, 0, 40))); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::Voted { + who: 6, + vote: split_abstain(0, 0, 40), + })); + + assert_eq!(tally(3), Tally::from_parts(0, 0, 50)); assert_eq!(Balances::usable_balance(1), 0); - assert_eq!(Balances::usable_balance(2), 0); + assert_eq!(Balances::usable_balance(6), 20); assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(1), None, 3)); - assert_eq!(tally(3), Tally::from_parts(1, 0, 20)); - - assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(2), None, 3)); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::VoteRemoved { + who: 1, + vote: split_abstain(0, 0, 10), + })); + assert_eq!(tally(3), Tally::from_parts(0, 0, 40)); + + assert_ok!(Voting::remove_vote(RuntimeOrigin::signed(6), Some(class(3)), 3)); + System::assert_last_event(tests::RuntimeEvent::Voting(Event::VoteRemoved { + who: 6, + vote: split_abstain(0, 0, 40), + })); assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); assert_ok!(Voting::unlock(RuntimeOrigin::signed(1), class(3), 1)); assert_eq!(Balances::usable_balance(1), 10); - assert_ok!(Voting::unlock(RuntimeOrigin::signed(2), class(3), 2)); - assert_eq!(Balances::usable_balance(2), 20); + assert_ok!(Voting::unlock(RuntimeOrigin::signed(6), class(3), 6)); + assert_eq!(Balances::usable_balance(6), 60); }); } diff --git a/substrate/frame/conviction-voting/src/types.rs b/substrate/frame/conviction-voting/src/types.rs index d6bbb678a14b..aa7dd578fbad 100644 --- a/substrate/frame/conviction-voting/src/types.rs +++ b/substrate/frame/conviction-voting/src/types.rs @@ -117,14 +117,9 @@ impl< pub fn from_parts( ayes_with_conviction: Votes, nays_with_conviction: Votes, - ayes: Votes, + support: Votes, ) -> Self { - Self { - ayes: ayes_with_conviction, - nays: nays_with_conviction, - support: ayes, - dummy: PhantomData, - } + Self { ayes: ayes_with_conviction, nays: nays_with_conviction, support, dummy: PhantomData } } /// Add an account's vote into the tally. From 65a92ba5d444c7278d038c8181086bd9f006f68d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 20 Nov 2024 11:19:28 +0000 Subject: [PATCH 002/340] Increase default trie cache size to 1GiB (#6546) The default trie cache size before was set to `64MiB`, which is quite low to achieve real speed ups. `1GiB` should be a reasonable number as the requirements for validators/collators/full nodes are much higher when it comes to minimum memory requirements. Also the cache will not use `1GiB` from the start and fills over time. The setting can be changed by setting `--trie-cache-size BYTE_SIZE`. --------- Co-authored-by: GitHub Action --- prdoc/pr_6546.prdoc | 13 +++++++++++++ substrate/client/cli/src/params/import_params.rs | 10 +--------- 2 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 prdoc/pr_6546.prdoc diff --git a/prdoc/pr_6546.prdoc b/prdoc/pr_6546.prdoc new file mode 100644 index 000000000000..353578a7f58f --- /dev/null +++ b/prdoc/pr_6546.prdoc @@ -0,0 +1,13 @@ +title: Increase default trie cache size to 1GiB +doc: +- audience: Node Operator + description: "The default trie cache size before was set to `64MiB`, which is quite\ + \ low to achieve real speed ups. `1GiB` should be a reasonable number as the requirements\ + \ for validators/collators/full nodes are much higher when it comes to minimum\ + \ memory requirements. Also the cache will not use `1GiB` from the start and fills\ + \ over time. The setting can be changed by setting `--trie-cache-size BYTE_SIZE`.\ + The CLI option `--state-cache-size` is also removed, which was not having any effect anymore.\r\ + \n" +crates: +- name: sc-cli + bump: patch diff --git a/substrate/client/cli/src/params/import_params.rs b/substrate/client/cli/src/params/import_params.rs index add7cb4f8505..e4b8b9644feb 100644 --- a/substrate/client/cli/src/params/import_params.rs +++ b/substrate/client/cli/src/params/import_params.rs @@ -78,21 +78,13 @@ pub struct ImportParams { /// Specify the state cache size. /// /// Providing `0` will disable the cache. - #[arg(long, value_name = "Bytes", default_value_t = 67108864)] + #[arg(long, value_name = "Bytes", default_value_t = 1024 * 1024 * 1024)] pub trie_cache_size: usize, - - /// DEPRECATED: switch to `--trie-cache-size`. - #[arg(long)] - state_cache_size: Option, } impl ImportParams { /// Specify the trie cache maximum size. pub fn trie_cache_maximum_size(&self) -> Option { - if self.state_cache_size.is_some() { - eprintln!("`--state-cache-size` was deprecated. Please switch to `--trie-cache-size`."); - } - if self.trie_cache_size == 0 { None } else { From bd0d0cde53272833deaaa0bcaddf95fba290ea77 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 20 Nov 2024 13:23:37 +0100 Subject: [PATCH 003/340] Bridges testing improvements (#6536) This PR includes: - Refactored integrity tests to support standalone deployment of `pallet-bridge-messages`. - Refactored the `open_and_close_bridge_works` test case to support multiple scenarios, such as: 1. A local chain opening a bridge. 2. Sibling parachains opening a bridge. 3. The relay chain opening a bridge. - Previously, we added instance support for `pallet-bridge-relayer` but overlooked updating the `DeliveryConfirmationPaymentsAdapter`. --------- Co-authored-by: GitHub Action --- bridges/bin/runtime-common/src/integrity.rs | 95 +++++++++++---- bridges/bin/runtime-common/src/mock.rs | 1 + bridges/modules/relayers/src/lib.rs | 5 +- bridges/modules/relayers/src/mock.rs | 15 +-- .../modules/relayers/src/payment_adapter.rs | 24 ++-- .../src/bridge_to_bulletin_config.rs | 1 - .../src/bridge_to_westend_config.rs | 3 +- .../bridge-hub-rococo/tests/tests.rs | 30 +++-- .../src/bridge_to_rococo_config.rs | 3 +- .../bridge-hub-westend/tests/tests.rs | 15 ++- .../test-utils/src/test_cases/helpers.rs | 112 +++++++++++------- .../test-utils/src/test_cases/mod.rs | 28 +++-- .../parachains/runtimes/test-utils/src/lib.rs | 22 ++-- prdoc/pr_6536.prdoc | 24 ++++ 14 files changed, 251 insertions(+), 127 deletions(-) create mode 100644 prdoc/pr_6536.prdoc diff --git a/bridges/bin/runtime-common/src/integrity.rs b/bridges/bin/runtime-common/src/integrity.rs index 2ff6c4c9165a..535f1a26e5e8 100644 --- a/bridges/bin/runtime-common/src/integrity.rs +++ b/bridges/bin/runtime-common/src/integrity.rs @@ -89,13 +89,11 @@ macro_rules! assert_bridge_messages_pallet_types( /// Macro that combines four other macro calls - `assert_chain_types`, `assert_bridge_types`, /// and `assert_bridge_messages_pallet_types`. It may be used -/// at the chain that is implementing complete standard messages bridge (i.e. with bridge GRANDPA -/// and messages pallets deployed). +/// at the chain that is implementing standard messages bridge with messages pallets deployed. #[macro_export] macro_rules! assert_complete_bridge_types( ( runtime: $r:path, - with_bridged_chain_grandpa_instance: $gi:path, with_bridged_chain_messages_instance: $mi:path, this_chain: $this:path, bridged_chain: $bridged:path, @@ -186,34 +184,55 @@ where ); } -/// Parameters for asserting bridge pallet names. +/// Parameters for asserting bridge GRANDPA pallet names. #[derive(Debug)] -pub struct AssertBridgePalletNames<'a> { +struct AssertBridgeGrandpaPalletNames<'a> { /// Name of the GRANDPA pallet, deployed at this chain and used to bridge with the bridged /// chain. pub with_bridged_chain_grandpa_pallet_name: &'a str, - /// Name of the messages pallet, deployed at this chain and used to bridge with the bridged - /// chain. - pub with_bridged_chain_messages_pallet_name: &'a str, } /// Tests that bridge pallet names used in `construct_runtime!()` macro call are matching constants /// from chain primitives crates. -fn assert_bridge_pallet_names(params: AssertBridgePalletNames) +fn assert_bridge_grandpa_pallet_names(params: AssertBridgeGrandpaPalletNames) where - R: pallet_bridge_grandpa::Config + pallet_bridge_messages::Config, + R: pallet_bridge_grandpa::Config, GI: 'static, - MI: 'static, { // check that the bridge GRANDPA pallet has required name assert_eq!( - pallet_bridge_grandpa::PalletOwner::::storage_value_final_key().to_vec(), + pallet_bridge_grandpa::PalletOwner::::storage_value_final_key().to_vec(), + bp_runtime::storage_value_key( + params.with_bridged_chain_grandpa_pallet_name, + "PalletOwner", + ) + .0, + ); + assert_eq!( + pallet_bridge_grandpa::PalletOperatingMode::::storage_value_final_key().to_vec(), bp_runtime::storage_value_key( params.with_bridged_chain_grandpa_pallet_name, - "PalletOwner", - ).0, + "PalletOperatingMode", + ) + .0, ); +} +/// Parameters for asserting bridge messages pallet names. +#[derive(Debug)] +struct AssertBridgeMessagesPalletNames<'a> { + /// Name of the messages pallet, deployed at this chain and used to bridge with the bridged + /// chain. + pub with_bridged_chain_messages_pallet_name: &'a str, +} + +/// Tests that bridge pallet names used in `construct_runtime!()` macro call are matching constants +/// from chain primitives crates. +fn assert_bridge_messages_pallet_names(params: AssertBridgeMessagesPalletNames) +where + R: pallet_bridge_messages::Config, + MI: 'static, +{ // check that the bridge messages pallet has required name assert_eq!( pallet_bridge_messages::PalletOwner::::storage_value_final_key().to_vec(), @@ -223,6 +242,14 @@ where ) .0, ); + assert_eq!( + pallet_bridge_messages::PalletOperatingMode::::storage_value_final_key().to_vec(), + bp_runtime::storage_value_key( + params.with_bridged_chain_messages_pallet_name, + "PalletOperatingMode", + ) + .0, + ); } /// Parameters for asserting complete standard messages bridge. @@ -246,9 +273,11 @@ pub fn assert_complete_with_relay_chain_bridge_constants( assert_chain_constants::(params.this_chain_constants); assert_bridge_grandpa_pallet_constants::(); assert_bridge_messages_pallet_constants::(); - assert_bridge_pallet_names::(AssertBridgePalletNames { + assert_bridge_grandpa_pallet_names::(AssertBridgeGrandpaPalletNames { with_bridged_chain_grandpa_pallet_name: >::BridgedChain::WITH_CHAIN_GRANDPA_PALLET_NAME, + }); + assert_bridge_messages_pallet_names::(AssertBridgeMessagesPalletNames { with_bridged_chain_messages_pallet_name: >::BridgedChain::WITH_CHAIN_MESSAGES_PALLET_NAME, }); @@ -256,21 +285,43 @@ pub fn assert_complete_with_relay_chain_bridge_constants( /// All bridge-related constants tests for the complete standard parachain messages bridge /// (i.e. with bridge GRANDPA, parachains and messages pallets deployed). -pub fn assert_complete_with_parachain_bridge_constants( +pub fn assert_complete_with_parachain_bridge_constants( params: AssertCompleteBridgeConstants, ) where R: frame_system::Config - + pallet_bridge_grandpa::Config + + pallet_bridge_parachains::Config + pallet_bridge_messages::Config, - GI: 'static, + >::BridgedRelayChain: ChainWithGrandpa, + PI: 'static, + MI: 'static, +{ + assert_chain_constants::(params.this_chain_constants); + assert_bridge_grandpa_pallet_constants::(); + assert_bridge_messages_pallet_constants::(); + assert_bridge_grandpa_pallet_names::( + AssertBridgeGrandpaPalletNames { + with_bridged_chain_grandpa_pallet_name: + <>::BridgedRelayChain>::WITH_CHAIN_GRANDPA_PALLET_NAME, + }, + ); + assert_bridge_messages_pallet_names::(AssertBridgeMessagesPalletNames { + with_bridged_chain_messages_pallet_name: + >::BridgedChain::WITH_CHAIN_MESSAGES_PALLET_NAME, + }); +} + +/// All bridge-related constants tests for the standalone messages bridge deployment (only with +/// messages pallets deployed). +pub fn assert_standalone_messages_bridge_constants(params: AssertCompleteBridgeConstants) +where + R: frame_system::Config + pallet_bridge_messages::Config, MI: 'static, - RelayChain: ChainWithGrandpa, { assert_chain_constants::(params.this_chain_constants); - assert_bridge_grandpa_pallet_constants::(); assert_bridge_messages_pallet_constants::(); - assert_bridge_pallet_names::(AssertBridgePalletNames { - with_bridged_chain_grandpa_pallet_name: RelayChain::WITH_CHAIN_GRANDPA_PALLET_NAME, + assert_bridge_messages_pallet_names::(AssertBridgeMessagesPalletNames { with_bridged_chain_messages_pallet_name: >::BridgedChain::WITH_CHAIN_MESSAGES_PALLET_NAME, }); diff --git a/bridges/bin/runtime-common/src/mock.rs b/bridges/bin/runtime-common/src/mock.rs index 6cf04b452da7..88037d9deff5 100644 --- a/bridges/bin/runtime-common/src/mock.rs +++ b/bridges/bin/runtime-common/src/mock.rs @@ -196,6 +196,7 @@ impl pallet_bridge_messages::Config for TestRuntime { type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< TestRuntime, (), + (), ConstU64<100_000>, >; type OnMessagesDelivered = (); diff --git a/bridges/modules/relayers/src/lib.rs b/bridges/modules/relayers/src/lib.rs index f06c2e16ac24..d1c71b6d3051 100644 --- a/bridges/modules/relayers/src/lib.rs +++ b/bridges/modules/relayers/src/lib.rs @@ -22,8 +22,9 @@ use bp_relayers::{ ExplicitOrAccountParams, PaymentProcedure, Registration, RelayerRewardsKeyProvider, - RewardsAccountParams, StakeAndSlash, + StakeAndSlash, }; +pub use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::StorageDoubleMapKeyProvider; use frame_support::fail; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; @@ -31,7 +32,7 @@ use sp_runtime::{traits::CheckedSub, Saturating}; use sp_std::marker::PhantomData; pub use pallet::*; -pub use payment_adapter::DeliveryConfirmationPaymentsAdapter; +pub use payment_adapter::{DeliveryConfirmationPaymentsAdapter, PayRewardFromAccount}; pub use stake_adapter::StakeAndSlashNamed; pub use weights::WeightInfo; pub use weights_ext::WeightInfoExt; diff --git a/bridges/modules/relayers/src/mock.rs b/bridges/modules/relayers/src/mock.rs index d186e968e648..7dc213249379 100644 --- a/bridges/modules/relayers/src/mock.rs +++ b/bridges/modules/relayers/src/mock.rs @@ -171,14 +171,14 @@ pub type TestStakeAndSlash = pallet_bridge_relayers::StakeAndSlashNamed< frame_support::construct_runtime! { pub enum TestRuntime { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system, Utility: pallet_utility, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, - BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event}, - BridgeGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage, Event}, - BridgeParachains: pallet_bridge_parachains::{Pallet, Call, Storage, Event}, - BridgeMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event, Config}, + Balances: pallet_balances, + TransactionPayment: pallet_transaction_payment, + BridgeRelayers: pallet_bridge_relayers, + BridgeGrandpa: pallet_bridge_grandpa, + BridgeParachains: pallet_bridge_parachains, + BridgeMessages: pallet_bridge_messages, } } @@ -267,6 +267,7 @@ impl pallet_bridge_messages::Config for TestRuntime { type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< TestRuntime, (), + (), ConstU64<100_000>, >; type OnMessagesDelivered = (); diff --git a/bridges/modules/relayers/src/payment_adapter.rs b/bridges/modules/relayers/src/payment_adapter.rs index 5383cba5ecbd..5af0d8f9dfbf 100644 --- a/bridges/modules/relayers/src/payment_adapter.rs +++ b/bridges/modules/relayers/src/payment_adapter.rs @@ -22,6 +22,7 @@ use bp_messages::{ source_chain::{DeliveryConfirmationPayments, RelayersRewards}, MessageNonce, }; +pub use bp_relayers::PayRewardFromAccount; use bp_relayers::{RewardsAccountOwner, RewardsAccountParams}; use bp_runtime::Chain; use frame_support::{sp_runtime::SaturatedConversion, traits::Get}; @@ -31,15 +32,16 @@ use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeIn /// Adapter that allows relayers pallet to be used as a delivery+dispatch payment mechanism /// for the messages pallet. -pub struct DeliveryConfirmationPaymentsAdapter( - PhantomData<(T, MI, DeliveryReward)>, +pub struct DeliveryConfirmationPaymentsAdapter( + PhantomData<(T, MI, RI, DeliveryReward)>, ); -impl DeliveryConfirmationPayments> - for DeliveryConfirmationPaymentsAdapter +impl DeliveryConfirmationPayments> + for DeliveryConfirmationPaymentsAdapter where - T: Config + pallet_bridge_messages::Config::LaneId>, + T: Config + pallet_bridge_messages::Config>::LaneId>, MI: 'static, + RI: 'static, DeliveryReward: Get, { type Error = &'static str; @@ -54,7 +56,7 @@ where bp_messages::calc_relayers_rewards::(messages_relayers, received_range); let rewarded_relayers = relayers_rewards.len(); - register_relayers_rewards::( + register_relayers_rewards::( confirmation_relayer, relayers_rewards, RewardsAccountParams::new( @@ -70,7 +72,7 @@ where } // Update rewards to given relayers, optionally rewarding confirmation relayer. -fn register_relayers_rewards( +fn register_relayers_rewards, I: 'static>( confirmation_relayer: &T::AccountId, relayers_rewards: RelayersRewards, lane_id: RewardsAccountParams, @@ -84,7 +86,7 @@ fn register_relayers_rewards( let relayer_reward = T::Reward::saturated_from(messages).saturating_mul(delivery_fee); if relayer != *confirmation_relayer { - Pallet::::register_relayer_reward(lane_id, &relayer, relayer_reward); + Pallet::::register_relayer_reward(lane_id, &relayer, relayer_reward); } else { confirmation_relayer_reward = confirmation_relayer_reward.saturating_add(relayer_reward); @@ -92,7 +94,7 @@ fn register_relayers_rewards( } // finally - pay reward to confirmation relayer - Pallet::::register_relayer_reward( + Pallet::::register_relayer_reward( lane_id, confirmation_relayer, confirmation_relayer_reward, @@ -115,7 +117,7 @@ mod tests { #[test] fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() { run_test(|| { - register_relayers_rewards::( + register_relayers_rewards::( &RELAYER_2, relayers_rewards(), test_reward_account_param(), @@ -136,7 +138,7 @@ mod tests { #[test] fn confirmation_relayer_is_not_rewarded_if_it_has_not_delivered_any_messages() { run_test(|| { - register_relayers_rewards::( + register_relayers_rewards::( &RELAYER_3, relayers_rewards(), test_reward_account_param(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs index 7e0385692375..b284fa9e7af7 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -201,7 +201,6 @@ mod tests { fn ensure_bridge_integrity() { assert_complete_bridge_types!( runtime: Runtime, - with_bridged_chain_grandpa_instance: BridgeGrandpaRococoBulletinInstance, with_bridged_chain_messages_instance: WithRococoBulletinMessagesInstance, this_chain: bp_bridge_hub_rococo::BridgeHubRococo, bridged_chain: bp_polkadot_bulletin::PolkadotBulletin, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index 0eab3c74a7e2..2710d033d64b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -121,6 +121,7 @@ impl pallet_bridge_messages::Config for Ru type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< Runtime, WithBridgeHubWestendMessagesInstance, + RelayersForLegacyLaneIdsMessagesInstance, DeliveryRewardInBalance, >; @@ -256,7 +257,6 @@ mod tests { fn ensure_bridge_integrity() { assert_complete_bridge_types!( runtime: Runtime, - with_bridged_chain_grandpa_instance: BridgeGrandpaWestendInstance, with_bridged_chain_messages_instance: WithBridgeHubWestendMessagesInstance, this_chain: bp_bridge_hub_rococo::BridgeHubRococo, bridged_chain: bp_bridge_hub_westend::BridgeHubWestend, @@ -266,7 +266,6 @@ mod tests { Runtime, BridgeGrandpaWestendInstance, WithBridgeHubWestendMessagesInstance, - bp_westend::Westend, >(AssertCompleteBridgeConstants { this_chain_constants: AssertChainConstants { block_length: bp_bridge_hub_rococo::BlockLength::get(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index 2e7dd98e9dce..6ca858e961d3 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -324,11 +324,12 @@ mod bridge_hub_westend_tests { >( SiblingParachainLocation::get(), BridgedUniversalLocation::get(), - |locations, fee| { + false, + |locations, _fee| { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverBridgeHubWestendInstance - >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + >(locations, LegacyLaneId([0, 0, 0, 1])) } ).1 }, @@ -388,11 +389,12 @@ mod bridge_hub_westend_tests { >( SiblingParachainLocation::get(), BridgedUniversalLocation::get(), - |locations, fee| { + false, + |locations, _fee| { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverBridgeHubWestendInstance, - >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + >(locations, LegacyLaneId([0, 0, 0, 1])) }, ) .1 @@ -422,11 +424,12 @@ mod bridge_hub_westend_tests { >( SiblingParachainLocation::get(), BridgedUniversalLocation::get(), - |locations, fee| { + false, + |locations, _fee| { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverBridgeHubWestendInstance, - >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + >(locations, LegacyLaneId([0, 0, 0, 1])) }, ) .1 @@ -591,11 +594,12 @@ mod bridge_hub_bulletin_tests { >( SiblingPeopleParachainLocation::get(), BridgedBulletinLocation::get(), - |locations, fee| { + false, + |locations, _fee| { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverPolkadotBulletinInstance - >(locations, fee, HashedLaneId::try_new(1, 2).unwrap()) + >(locations, HashedLaneId::try_new(1, 2).unwrap()) } ).1 }, @@ -654,11 +658,12 @@ mod bridge_hub_bulletin_tests { >( SiblingPeopleParachainLocation::get(), BridgedBulletinLocation::get(), - |locations, fee| { + false, + |locations, _fee| { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverPolkadotBulletinInstance, - >(locations, fee, HashedLaneId::try_new(1, 2).unwrap()) + >(locations, HashedLaneId::try_new(1, 2).unwrap()) }, ) .1 @@ -687,11 +692,12 @@ mod bridge_hub_bulletin_tests { >( SiblingPeopleParachainLocation::get(), BridgedBulletinLocation::get(), - |locations, fee| { + false, + |locations, _fee| { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverPolkadotBulletinInstance, - >(locations, fee, HashedLaneId::try_new(1, 2).unwrap()) + >(locations, HashedLaneId::try_new(1, 2).unwrap()) }, ) .1 diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index 62c93da7c831..cd3465513144 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -152,6 +152,7 @@ impl pallet_bridge_messages::Config for Run type DeliveryConfirmationPayments = pallet_bridge_relayers::DeliveryConfirmationPaymentsAdapter< Runtime, WithBridgeHubRococoMessagesInstance, + RelayersForLegacyLaneIdsMessagesInstance, DeliveryRewardInBalance, >; @@ -284,7 +285,6 @@ mod tests { fn ensure_bridge_integrity() { assert_complete_bridge_types!( runtime: Runtime, - with_bridged_chain_grandpa_instance: BridgeGrandpaRococoInstance, with_bridged_chain_messages_instance: WithBridgeHubRococoMessagesInstance, this_chain: bp_bridge_hub_westend::BridgeHubWestend, bridged_chain: bp_bridge_hub_rococo::BridgeHubRococo, @@ -294,7 +294,6 @@ mod tests { Runtime, BridgeGrandpaRococoInstance, WithBridgeHubRococoMessagesInstance, - bp_rococo::Rococo, >(AssertCompleteBridgeConstants { this_chain_constants: AssertChainConstants { block_length: bp_bridge_hub_westend::BlockLength::get(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index 69301b34fe6b..84025c4cefeb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -246,10 +246,11 @@ fn handle_export_message_from_system_parachain_add_to_outbound_queue_works() { >( SiblingParachainLocation::get(), BridgedUniversalLocation::get(), - |locations, fee| { + false, + |locations, _fee| { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverBridgeHubRococoInstance - >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + >(locations, LegacyLaneId([0, 0, 0, 1])) } ).1 }, @@ -307,11 +308,12 @@ fn relayed_incoming_message_works() { >( SiblingParachainLocation::get(), BridgedUniversalLocation::get(), - |locations, fee| { + false, + |locations, _fee| { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverBridgeHubRococoInstance, - >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + >(locations, LegacyLaneId([0, 0, 0, 1])) }, ) .1 @@ -341,11 +343,12 @@ fn free_relay_extrinsic_works() { >( SiblingParachainLocation::get(), BridgedUniversalLocation::get(), - |locations, fee| { + false, + |locations, _fee| { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverBridgeHubRococoInstance, - >(locations, fee, LegacyLaneId([0, 0, 0, 1])) + >(locations, LegacyLaneId([0, 0, 0, 1])) }, ) .1 diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs index aac60bba0b53..03ddc4313b45 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs @@ -29,7 +29,7 @@ use core::marker::PhantomData; use frame_support::{ assert_ok, dispatch::GetDispatchInfo, - traits::{fungible::Mutate, OnFinalize, OnInitialize, PalletInfoAccess}, + traits::{fungible::Mutate, Contains, OnFinalize, OnInitialize, PalletInfoAccess}, }; use frame_system::pallet_prelude::BlockNumberFor; use pallet_bridge_grandpa::{BridgedBlockHash, BridgedHeader}; @@ -395,7 +395,7 @@ pub fn ensure_opened_bridge< XcmOverBridgePalletInstance, LocationToAccountId, TokenLocation> -(source: Location, destination: InteriorLocation, bridge_opener: impl Fn(BridgeLocations, Asset)) -> (BridgeLocations, pallet_xcm_bridge_hub::LaneIdOf) +(source: Location, destination: InteriorLocation, is_paid_xcm_execution: bool, bridge_opener: impl Fn(BridgeLocations, Option)) -> (BridgeLocations, pallet_xcm_bridge_hub::LaneIdOf) where Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig, XcmOverBridgePalletInstance: 'static, @@ -416,24 +416,37 @@ TokenLocation: Get{ ) .is_none()); - // required balance: ED + fee + BridgeDeposit - let bridge_deposit = - >::BridgeDeposit::get( - ); - // random high enough value for `BuyExecution` fees - let buy_execution_fee_amount = 5_000_000_000_000_u128; - let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into(); - let balance_needed = ::ExistentialDeposit::get() + - buy_execution_fee_amount.into() + - bridge_deposit.into(); - // SA of source location needs to have some required balance - let source_account_id = LocationToAccountId::convert_location(&source).expect("valid location"); - let _ = >::mint_into(&source_account_id, balance_needed) - .expect("mint_into passes"); + if !>::AllowWithoutBridgeDeposit::contains(&source) { + // required balance: ED + fee + BridgeDeposit + let bridge_deposit = + >::BridgeDeposit::get( + ); + let balance_needed = ::ExistentialDeposit::get() + bridge_deposit.into(); + + let source_account_id = LocationToAccountId::convert_location(&source).expect("valid location"); + let _ = >::mint_into(&source_account_id, balance_needed) + .expect("mint_into passes"); + }; + + let maybe_paid_execution = if is_paid_xcm_execution { + // random high enough value for `BuyExecution` fees + let buy_execution_fee_amount = 5_000_000_000_000_u128; + let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into(); + + let balance_needed = ::ExistentialDeposit::get() + + buy_execution_fee_amount.into(); + let source_account_id = + LocationToAccountId::convert_location(&source).expect("valid location"); + let _ = >::mint_into(&source_account_id, balance_needed) + .expect("mint_into passes"); + Some(buy_execution_fee) + } else { + None + }; // call the bridge opener - bridge_opener(*locations.clone(), buy_execution_fee); + bridge_opener(*locations.clone(), maybe_paid_execution); // check opened bridge let bridge = pallet_xcm_bridge_hub::Bridges::::get( @@ -452,8 +465,9 @@ TokenLocation: Get{ /// Utility for opening bridge with dedicated `pallet_xcm_bridge_hub`'s extrinsic. pub fn open_bridge_with_extrinsic( - locations: BridgeLocations, - buy_execution_fee: Asset, + (origin, origin_kind): (Location, OriginKind), + bridge_destination_universal_location: InteriorLocation, + maybe_paid_execution: Option, ) where Runtime: frame_system::Config + pallet_xcm_bridge_hub::Config @@ -469,15 +483,15 @@ pub fn open_bridge_with_extrinsic( XcmOverBridgePalletInstance, >::open_bridge { bridge_destination_universal_location: Box::new( - locations.bridge_destination_universal_location().clone().into(), + bridge_destination_universal_location.clone().into(), ), }); // execute XCM as source origin would do with `Transact -> Origin::Xcm` - assert_ok!(RuntimeHelper::::execute_as_origin_xcm( - locations.bridge_origin_relative_location().clone(), + assert_ok!(RuntimeHelper::::execute_as_origin( + (origin, origin_kind), open_bridge_call, - buy_execution_fee + maybe_paid_execution ) .ensure_complete()); } @@ -486,7 +500,6 @@ pub fn open_bridge_with_extrinsic( /// purposes). pub fn open_bridge_with_storage( locations: BridgeLocations, - _buy_execution_fee: Asset, lane_id: pallet_xcm_bridge_hub::LaneIdOf, ) where Runtime: pallet_xcm_bridge_hub::Config, @@ -503,8 +516,12 @@ pub fn open_bridge_with_storage( } /// Helper function to close the bridge/lane for `source` and `destination`. -pub fn close_bridge(source: Location, destination: InteriorLocation) -where +pub fn close_bridge( + expected_source: Location, + bridge_destination_universal_location: InteriorLocation, + (origin, origin_kind): (Location, OriginKind), + is_paid_xcm_execution: bool +) where Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig, XcmOverBridgePalletInstance: 'static, ::RuntimeCall: GetDispatchInfo + From>, @@ -515,8 +532,8 @@ TokenLocation: Get{ // construct expected bridge configuration let locations = pallet_xcm_bridge_hub::Pallet::::bridge_locations( - source.clone().into(), - destination.clone().into(), + expected_source.clone().into(), + bridge_destination_universal_location.clone().into(), ) .expect("valid bridge locations"); assert!(pallet_xcm_bridge_hub::Bridges::::get( @@ -525,35 +542,38 @@ TokenLocation: Get{ .is_some()); // required balance: ED + fee + BridgeDeposit - let bridge_deposit = - >::BridgeDeposit::get( - ); - // random high enough value for `BuyExecution` fees - let buy_execution_fee_amount = 2_500_000_000_000_u128; - let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into(); - let balance_needed = ::ExistentialDeposit::get() + - buy_execution_fee_amount.into() + - bridge_deposit.into(); - - // SA of source location needs to have some required balance - let source_account_id = LocationToAccountId::convert_location(&source).expect("valid location"); - let _ = >::mint_into(&source_account_id, balance_needed) - .expect("mint_into passes"); + let maybe_paid_execution = if is_paid_xcm_execution { + // random high enough value for `BuyExecution` fees + let buy_execution_fee_amount = 2_500_000_000_000_u128; + let buy_execution_fee = (TokenLocation::get(), buy_execution_fee_amount).into(); + + let balance_needed = ::ExistentialDeposit::get() + + buy_execution_fee_amount.into(); + let source_account_id = + LocationToAccountId::convert_location(&expected_source).expect("valid location"); + let _ = >::mint_into(&source_account_id, balance_needed) + .expect("mint_into passes"); + Some(buy_execution_fee) + } else { + None + }; // close bridge with `Transact` call let close_bridge_call = RuntimeCallOf::::from(BridgeXcmOverBridgeCall::< Runtime, XcmOverBridgePalletInstance, >::close_bridge { - bridge_destination_universal_location: Box::new(destination.into()), + bridge_destination_universal_location: Box::new( + bridge_destination_universal_location.into(), + ), may_prune_messages: 16, }); // execute XCM as source origin would do with `Transact -> Origin::Xcm` - assert_ok!(RuntimeHelper::::execute_as_origin_xcm( - source.clone(), + assert_ok!(RuntimeHelper::::execute_as_origin( + (origin, origin_kind), close_bridge_call, - buy_execution_fee + maybe_paid_execution ) .ensure_complete()); diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs index ad6db0b83e80..f96d0bf405b9 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/mod.rs @@ -654,8 +654,10 @@ where pub fn open_and_close_bridge_works( collator_session_key: CollatorSessionKeys, runtime_para_id: u32, - source: Location, + expected_source: Location, destination: InteriorLocation, + origin_with_origin_kind: (Location, OriginKind), + is_paid_xcm_execution: bool, ) where Runtime: BasicParachainRuntime + BridgeXcmOverBridgeConfig, XcmOverBridgePalletInstance: 'static, @@ -669,7 +671,7 @@ pub fn open_and_close_bridge_works(collator_session_key, runtime_para_id, vec![], || { // construct expected bridge configuration let locations = pallet_xcm_bridge_hub::Pallet::::bridge_locations( - source.clone().into(), + expected_source.clone().into(), destination.clone().into(), ).expect("valid bridge locations"); let expected_lane_id = @@ -704,7 +706,7 @@ pub fn open_and_close_bridge_works( - source.clone(), + expected_source.clone(), destination.clone(), - open_bridge_with_extrinsic:: + is_paid_xcm_execution, + |locations, maybe_paid_execution| open_bridge_with_extrinsic::< + Runtime, + XcmOverBridgePalletInstance, + >( + origin_with_origin_kind.clone(), + locations.bridge_destination_universal_location().clone(), + maybe_paid_execution + ) ) .0 .bridge_id(), @@ -727,7 +737,7 @@ pub fn open_and_close_bridge_works(source.clone(), destination); + >(expected_source, destination, origin_with_origin_kind, is_paid_xcm_execution); // check bridge/lane DOES not exist assert_eq!( diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index 05ecf6ca8e81..3f2e721d13f6 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -460,18 +460,26 @@ impl< ) } - pub fn execute_as_origin_xcm( - origin: Location, + pub fn execute_as_origin( + (origin, origin_kind): (Location, OriginKind), call: Call, - buy_execution_fee: Asset, + maybe_buy_execution_fee: Option, ) -> Outcome { + let mut instructions = if let Some(buy_execution_fee) = maybe_buy_execution_fee { + vec![ + WithdrawAsset(buy_execution_fee.clone().into()), + BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, + ] + } else { + vec![UnpaidExecution { check_origin: None, weight_limit: Unlimited }] + }; + // prepare `Transact` xcm - let xcm = Xcm(vec![ - WithdrawAsset(buy_execution_fee.clone().into()), - BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, - Transact { origin_kind: OriginKind::Xcm, call: call.encode().into() }, + instructions.extend(vec![ + Transact { origin_kind, call: call.encode().into() }, ExpectTransactStatus(MaybeErrorCode::Success), ]); + let xcm = Xcm(instructions); // execute xcm as parent origin let mut hash = xcm.using_encoded(sp_io::hashing::blake2_256); diff --git a/prdoc/pr_6536.prdoc b/prdoc/pr_6536.prdoc new file mode 100644 index 000000000000..676b5c131f17 --- /dev/null +++ b/prdoc/pr_6536.prdoc @@ -0,0 +1,24 @@ +title: Bridges testing improvements +doc: +- audience: Runtime Dev + description: |- + This PR includes: + - Refactored integrity tests to support standalone deployment of `pallet-bridge-messages`. + - Refactored the `open_and_close_bridge_works` test case to support multiple scenarios, such as: + 1. A local chain opening a bridge. + 2. Sibling parachains opening a bridge. + 3. The relay chain opening a bridge. + - Previously, we added instance support for `pallet-bridge-relayer` but overlooked updating the `DeliveryConfirmationPaymentsAdapter`. +crates: +- name: bridge-runtime-common + bump: patch +- name: pallet-bridge-relayers + bump: patch +- name: bridge-hub-rococo-runtime + bump: patch +- name: bridge-hub-westend-runtime + bump: patch +- name: bridge-hub-test-utils + bump: major +- name: parachains-runtimes-test-utils + bump: major From 07808bd5e5f72b6779bdb8bce52b9f37c63e300a Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 20 Nov 2024 16:31:39 +0200 Subject: [PATCH 004/340] inbound queue v2 --- Cargo.toml | 5 + .../pallets/inbound-queue-v2/Cargo.toml | 94 ++++ .../pallets/inbound-queue-v2/README.md | 3 + .../inbound-queue-v2/fixtures/Cargo.toml | 34 ++ .../inbound-queue-v2/fixtures/src/lib.rs | 7 + .../fixtures/src/register_token.rs | 97 ++++ .../fixtures/src/send_token.rs | 95 ++++ .../fixtures/src/send_token_to_penpal.rs | 95 ++++ .../inbound-queue-v2/runtime-api/Cargo.toml | 30 + .../inbound-queue-v2/runtime-api/README.md | 3 + .../inbound-queue-v2/runtime-api/src/lib.rs | 15 + .../pallets/inbound-queue-v2/src/api.rs | 16 + .../inbound-queue-v2/src/benchmarking.rs | 38 ++ .../pallets/inbound-queue-v2/src/envelope.rs | 47 ++ .../pallets/inbound-queue-v2/src/lib.rs | 240 ++++++++ .../pallets/inbound-queue-v2/src/mock.rs | 264 +++++++++ .../pallets/inbound-queue-v2/src/test.rs | 308 +++++++++++ .../pallets/inbound-queue-v2/src/weights.rs | 31 ++ .../pallets/inbound-queue/src/lib.rs | 2 +- .../pallets/inbound-queue/src/mock.rs | 2 +- .../snowbridge/primitives/router/Cargo.toml | 6 + .../primitives/router/src/inbound/mod.rs | 500 +---------------- .../primitives/router/src/inbound/v1.rs | 520 ++++++++++++++++++ .../primitives/router/src/inbound/v2.rs | 247 +++++++++ .../src/bridge_to_ethereum_config.rs | 21 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 2 + 26 files changed, 2247 insertions(+), 475 deletions(-) create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/README.md create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs create mode 100644 bridges/snowbridge/primitives/router/src/inbound/v1.rs create mode 100644 bridges/snowbridge/primitives/router/src/inbound/v2.rs diff --git a/Cargo.toml b/Cargo.toml index 533ea4c9e878..3af053cc74e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,9 @@ members = [ "bridges/snowbridge/pallets/ethereum-client/fixtures", "bridges/snowbridge/pallets/inbound-queue", "bridges/snowbridge/pallets/inbound-queue/fixtures", + "bridges/snowbridge/pallets/inbound-queue-v2", + "bridges/snowbridge/pallets/inbound-queue-v2/fixtures", + "bridges/snowbridge/pallets/inbound-queue-v2/runtime-api", "bridges/snowbridge/pallets/outbound-queue", "bridges/snowbridge/pallets/outbound-queue/merkle-tree", "bridges/snowbridge/pallets/outbound-queue/runtime-api", @@ -1228,6 +1231,8 @@ snowbridge-pallet-ethereum-client = { path = "bridges/snowbridge/pallets/ethereu snowbridge-pallet-ethereum-client-fixtures = { path = "bridges/snowbridge/pallets/ethereum-client/fixtures", default-features = false } snowbridge-pallet-inbound-queue = { path = "bridges/snowbridge/pallets/inbound-queue", default-features = false } snowbridge-pallet-inbound-queue-fixtures = { path = "bridges/snowbridge/pallets/inbound-queue/fixtures", default-features = false } +snowbridge-pallet-inbound-queue-fixtures-v2 = { path = "bridges/snowbridge/pallets/inbound-queue-v2/fixtures", default-features = false } +snowbridge-pallet-inbound-queue-v2 = { path = "bridges/snowbridge/pallets/inbound-queue-v2", default-features = false } snowbridge-pallet-outbound-queue = { path = "bridges/snowbridge/pallets/outbound-queue", default-features = false } snowbridge-pallet-system = { path = "bridges/snowbridge/pallets/system", default-features = false } snowbridge-router-primitives = { path = "bridges/snowbridge/primitives/router", default-features = false } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml new file mode 100644 index 000000000000..fcbb41743f45 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml @@ -0,0 +1,94 @@ +[package] +name = "snowbridge-pallet-inbound-queue-v2" +description = "Snowbridge Inbound Queue Pallet V2" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { optional = true, workspace = true, default-features = true } +codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } +hex-literal = { optional = true, workspace = true, default-features = true } +log = { workspace = true } +alloy-primitives = { features = ["rlp"], workspace = true } +alloy-sol-types = { workspace = true } + +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-balances = { workspace = true } +sp-core = { workspace = true } +sp-std = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } + +xcm = { workspace = true } +xcm-executor = { workspace = true } + +snowbridge-core = { workspace = true } +snowbridge-router-primitives = { workspace = true } +snowbridge-beacon-primitives = { workspace = true } +snowbridge-pallet-inbound-queue-fixtures-v2 = { optional = true, workspace = true } + +[dev-dependencies] +frame-benchmarking = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +snowbridge-pallet-ethereum-client = { workspace = true, default-features = true } +hex-literal = { workspace = true, default-features = true } +hex = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "alloy-primitives/std", + "alloy-sol-types/std", + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", + "scale-info/std", + "serde", + "snowbridge-beacon-primitives/std", + "snowbridge-core/std", + "snowbridge-pallet-inbound-queue-fixtures-v2?/std", + "snowbridge-router-primitives/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-executor/std", + "xcm/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal", + "pallet-balances/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "snowbridge-pallet-ethereum-client/runtime-benchmarks", + "snowbridge-pallet-inbound-queue-fixtures-v2/runtime-benchmarks", + "snowbridge-router-primitives/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "snowbridge-pallet-ethereum-client/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/README.md b/bridges/snowbridge/pallets/inbound-queue-v2/README.md new file mode 100644 index 000000000000..cc2f7c636e68 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/README.md @@ -0,0 +1,3 @@ +# Ethereum Inbound Queue + +Reads messages from Ethereum and sends it to intended destination on Polkadot, using XCM. diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml new file mode 100644 index 000000000000..05a4a473a28a --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "snowbridge-pallet-inbound-queue-fixtures-v2" +description = "Snowbridge Inbound Queue Test Fixtures V2" +version = "0.10.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +hex-literal = { workspace = true, default-features = true } +sp-core = { workspace = true } +sp-std = { workspace = true } +snowbridge-core = { workspace = true } +snowbridge-beacon-primitives = { workspace = true } + +[features] +default = ["std"] +std = [ + "snowbridge-beacon-primitives/std", + "snowbridge-core/std", + "sp-core/std", + "sp-std/std", +] +runtime-benchmarks = [ + "snowbridge-core/runtime-benchmarks", +] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs new file mode 100644 index 000000000000..00adcdfa186a --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/lib.rs @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod register_token; +pub mod send_token; +pub mod send_token_to_penpal; diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs new file mode 100644 index 000000000000..5ab12490d040 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, +}; +use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; +use sp_core::U256; +use sp_std::vec; + +pub fn make_register_token_message() -> InboundQueueFixture { + InboundQueueFixture { + message: Message { + event_log: Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(), + }, + proof: Proof { + receipt_proof: (vec![ + hex!("dccdfceea05036f7b61dcdabadc937945d31e68a8d3dfd4dc85684457988c284").to_vec(), + hex!("4a98e45a319168b0fc6005ce6b744ee9bf54338e2c0784b976a8578d241ced0f").to_vec(), + ], vec![ + hex!("f851a09c01dd6d2d8de951c45af23d3ad00829ce021c04d6c8acbe1612d456ee320d4980808080808080a04a98e45a319168b0fc6005ce6b744ee9bf54338e2c0784b976a8578d241ced0f8080808080808080").to_vec(), + hex!("f9028c30b9028802f90284018301d205b9010000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000000000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000040004000000000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000200000000000010f90179f85894eda338e4dc46038493b885327842fd3e301cab39e1a0f78bb28d4b1d7da699e5c0bc2be29c2b04b5aab6aacf6298fe5304f9db9c6d7ea000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7df9011c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a05f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0b8a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e00a736aa00000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").to_vec(), + ]), + execution_proof: ExecutionProof { + header: BeaconHeader { + slot: 393, + proposer_index: 4, + parent_root: hex!("6545b47a614a1dd4cad042a0cdbbf5be347e8ffcdc02c6c64540d5153acebeef").into(), + state_root: hex!("b62ac34a8cb82497be9542fe2114410c9f6021855b766015406101a1f3d86434").into(), + body_root: hex!("04005fe231e11a5b7b1580cb73b177ae8b338bedd745497e6bb7122126a806db").into(), + }, + ancestry_proof: Some(AncestryProof { + header_branch: vec![ + hex!("6545b47a614a1dd4cad042a0cdbbf5be347e8ffcdc02c6c64540d5153acebeef").into(), + hex!("fa84cc88ca53a72181599ff4eb07d8b444bce023fe2347c3b4f51004c43439d3").into(), + hex!("cadc8ae211c6f2221c9138e829249adf902419c78eb4727a150baa4d9a02cc9d").into(), + hex!("33a89962df08a35c52bd7e1d887cd71fa7803e68787d05c714036f6edf75947c").into(), + hex!("2c9760fce5c2829ef3f25595a703c21eb22d0186ce223295556ed5da663a82cf").into(), + hex!("e1aa87654db79c8a0ecd6c89726bb662fcb1684badaef5cd5256f479e3c622e1").into(), + hex!("aa70d5f314e4a1fbb9c362f3db79b21bf68b328887248651fbd29fc501d0ca97").into(), + hex!("160b6c235b3a1ed4ef5f80b03ee1c76f7bf3f591c92fca9d8663e9221b9f9f0f").into(), + hex!("f68d7dcd6a07a18e9de7b5d2aa1980eb962e11d7dcb584c96e81a7635c8d2535").into(), + hex!("1d5f912dfd6697110dd1ecb5cb8e77952eef57d85deb373572572df62bb157fc").into(), + hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b").into(), + hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220").into(), + hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f").into(), + ], + finalized_block_root: hex!("751414cd97c0624f922b3e80285e9f776b08fa22fd5f87391f2ed7ef571a8d46").into(), + }), + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: hex!("8092290aa21b7751576440f77edd02a94058429ce50e63a92d620951fb25eda2").into(), + fee_recipient: hex!("0000000000000000000000000000000000000000").into(), + state_root: hex!("96a83e9ddf745346fafcb0b03d57314623df669ed543c110662b21302a0fae8b").into(), + receipts_root: hex!("dccdfceea05036f7b61dcdabadc937945d31e68a8d3dfd4dc85684457988c284").into(), + logs_bloom: hex!("00000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000080000000400000000000000000000004000000000080000000000000000000000000000000000010100000000000000000000000000000000020000000000000000000000000000000000080000000000000000000000000000040004000000000000002002002000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000080000000000000000000000000000000000100000000000000000200000200000010").into(), + prev_randao: hex!("62e309d4f5119d1f5c783abc20fc1a549efbab546d8d0b25ff1cfd58be524e67").into(), + block_number: 393, + gas_limit: 54492273, + gas_used: 199644, + timestamp: 1710552813, + extra_data: hex!("d983010d0b846765746888676f312e32312e368664617277696e").into(), + base_fee_per_gas: U256::from(7u64), + block_hash: hex!("6a9810efb9581d30c1a5c9074f27c68ea779a8c1ae31c213241df16225f4e131").into(), + transactions_root: hex!("2cfa6ed7327e8807c7973516c5c32a68ef2459e586e8067e113d081c3bd8c07d").into(), + withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![ + hex!("a6833fa629f3286b6916c6e50b8bf089fc9126bee6f64d0413b4e59c1265834d").into(), + hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), + hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), + hex!("d3af7c05c516726be7505239e0b9c7cb53d24abce6b91cdb3b3995f0164a75da").into(), + ], + } + }, + }, + finalized_header: BeaconHeader { + slot: 864, + proposer_index: 4, + parent_root: hex!("614e7672f991ac268cd841055973f55e1e42228831a211adef207bb7329be614").into(), + state_root: hex!("5fa8dfca3d760e4242ab46d529144627aa85348a19173b6e081172c701197a4a").into(), + body_root: hex!("0f34c083b1803666bb1ac5e73fa71582731a2cf37d279ff0a3b0cad5a2ff371e").into(), + }, + block_roots_root: hex!("b9aab9c388c4e4fcd899b71f62c498fc73406e38e8eb14aa440e9affa06f2a10").into(), + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs new file mode 100644 index 000000000000..52da807efd31 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, +}; +use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; +use sp_core::U256; +use sp_std::vec; + +pub fn make_send_token_message() -> InboundQueueFixture { + InboundQueueFixture { + message: Message { + event_log: Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("c8eaf22f2cb07bac4679df0a660e7115ed87fcfd4e32ac269f6540265bbbd26f").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000").into(), + }, + proof: Proof { + receipt_proof: (vec![ + hex!("f9d844c5b79638609ba385b910fec3b5d891c9d7b189f135f0432f33473de915").to_vec(), + ], vec![ + hex!("f90451822080b9044b02f90447018301bcb6b9010000800000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000010000000000000000000000008000000200000000000000001000008000000000000000000000000000000008000080000000000200000000000000000000000000100000000000000000011000000000000020200000000000000000000000000003000000040080008000000000000000000040044000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200800000000000f9033cf89b9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000057a2d4ff0c3866d96556884bf09fecdd7ccd530ca00000000000000000000000000000000000000000000000000de0b6b3a7640000f9015d94eda338e4dc46038493b885327842fd3e301cab39f884a024c5d2de620c6e25186ae16f6919eba93b6e2c1a33857cc419d9f3a00d6967e9a000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7da000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000000000000000000000000000000000000000003e8b8c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000208eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48f9013c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a0c8eaf22f2cb07bac4679df0a660e7115ed87fcfd4e32ac269f6540265bbbd26fb8c000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000").to_vec(), + ]), + execution_proof: ExecutionProof { + header: BeaconHeader { + slot: 2321, + proposer_index: 5, + parent_root: hex!("2add14727840d3a5ea061e14baa47030bb81380a65999200d119e73b86411d20").into(), + state_root: hex!("d962981467920bb2b7efa4a7a1baf64745582c3250857f49a957c5dae9a0da39").into(), + body_root: hex!("18e3f7f51a350f371ad35d166f2683b42af51d1836b295e4093be08acb0dcb7a").into(), + }, + ancestry_proof: Some(AncestryProof { + header_branch: vec![ + hex!("2add14727840d3a5ea061e14baa47030bb81380a65999200d119e73b86411d20").into(), + hex!("48b2e2f5256906a564e5058698f70e3406765fefd6a2edc064bb5fb88aa2ed0a").into(), + hex!("e5ed7c704e845418219b2fda42cd2f3438ffbe4c4b320935ae49439c6189f7a7").into(), + hex!("4a7ce24526b3f571548ad69679e4e260653a1b3b911a344e7f988f25a5c917a7").into(), + hex!("46fc859727ab0d0e8c344011f7d7a4426ccb537bb51363397e56cc7153f56391").into(), + hex!("f496b6f85a7c6c28a9048f2153550a7c5bcb4b23844ed3b87f6baa646124d8a3").into(), + hex!("7318644e474beb46e595a1875acc7444b937f5208065241911d2a71ac50c2de3").into(), + hex!("5cf48519e518ac64286aef5391319782dd38831d5dcc960578a6b9746d5f8cee").into(), + hex!("efb3e50fa39ca9fe7f76adbfa36fa8451ec2fd5d07b22aaf822137c04cf95a76").into(), + hex!("2206cd50750355ffaef4a67634c21168f2b564c58ffd04f33b0dc7af7dab3291").into(), + hex!("1a4014f6c4fcce9949fba74cb0f9e88df086706f9e05560cc9f0926f8c90e373").into(), + hex!("2df7cc0bcf3060be4132c63da7599c2600d9bbadf37ab001f15629bc2255698e").into(), + hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f").into(), + ], + finalized_block_root: hex!("f869dd1c9598043008a3ac2a5d91b3d6c7b0bb3295b3843bc84c083d70b0e604").into(), + }), + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: hex!("5d7859883dde1eba6c98b20eac18426134b25da2a89e5e360f3343b15e0e0a31").into(), + fee_recipient: hex!("0000000000000000000000000000000000000000").into(), + state_root: hex!("f8fbebed4c84d46231bd293bb9fbc9340d5c28c284d99fdaddb77238b8960ae2").into(), + receipts_root: hex!("f9d844c5b79638609ba385b910fec3b5d891c9d7b189f135f0432f33473de915").into(), + logs_bloom: hex!("00800000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000010000000000000000000000008000000200000000000000001000008000000000000000000000000000000008000080000000000200000000000000000000000000100000000000000000011000000000000020200000000000000000000000000003000000040080008000000000000000000040044000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200800000000000").into(), + prev_randao: hex!("15533eeb366c6386bea5aeb8f425871928348c092209e4377f2418a6dedd7fd0").into(), + block_number: 2321, + gas_limit: 30000000, + gas_used: 113846, + timestamp: 1710554741, + extra_data: hex!("d983010d0b846765746888676f312e32312e368664617277696e").into(), + base_fee_per_gas: U256::from(7u64), + block_hash: hex!("585a07122a30339b03b6481eae67c2d3de2b6b64f9f426230986519bf0f1bdfe").into(), + transactions_root: hex!("09cd60ee2207d804397c81f7b7e1e5d3307712b136e5376623a80317a4bdcd7a").into(), + withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![ + hex!("9d419471a9a4719b40e7607781fbe32d9a7766b79805505c78c0c58133496ba2").into(), + hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), + hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), + hex!("bee375b8f1bbe4cd0e783c78026c1829ae72741c2dead5cab05d6834c5e5df65").into(), + ], + } + }, + }, + finalized_header: BeaconHeader { + slot: 4032, + proposer_index: 5, + parent_root: hex!("180aaaec59d38c3860e8af203f01f41c9bc41665f4d17916567c80f6cd23e8a2").into(), + state_root: hex!("3341790429ed3bf894cafa3004351d0b99e08baf6c38eb2a54d58e69fd2d19c6").into(), + body_root: hex!("a221e0c695ac7b7d04ce39b28b954d8a682ecd57961d81b44783527c6295f455").into(), + }, + block_roots_root: hex!("5744385ef06f82e67606f49aa29cd162f2e837a68fb7bd82f1fc6155d9f8640f").into(), + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs new file mode 100644 index 000000000000..4b4e78b63513 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, +}; +use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; +use sp_core::U256; +use sp_std::vec; + +pub fn make_send_token_to_penpal_message() -> InboundQueueFixture { + InboundQueueFixture { + message: Message { + event_log: Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("be323bced46a1a49c8da2ab62ad5e974fd50f1dabaeed70b23ca5bcf14bfe4aa").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000007300a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d01d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000000000000000000000000000").into(), + }, + proof: Proof { + receipt_proof: (vec![ + hex!("106f1eaeac04e469da0020ad5c8a72af66323638bd3f561a3c8236063202c120").to_vec(), + ], vec![ + hex!("f90471822080b9046b02f904670183017d9cb9010000800000000000008000000000000000000000000000004000000000000000000400000000004000000000001000000010000000000000000000001008000000000000000000000001000008000040000000000000000000000000008000080000000000200000000000000000000000000100000000000000000010000000000000020000000000000000000000000000003000000000080018000000000000000000040004000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200820000000000f9035cf89b9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df863a0ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000057a2d4ff0c3866d96556884bf09fecdd7ccd530ca00000000000000000000000000000000000000000000000000de0b6b3a7640000f9015d94eda338e4dc46038493b885327842fd3e301cab39f884a024c5d2de620c6e25186ae16f6919eba93b6e2c1a33857cc419d9f3a00d6967e9a000000000000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7da000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000000000000000000000000000000000000000007d0b8c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000201cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07cf9015c94eda338e4dc46038493b885327842fd3e301cab39f863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a0be323bced46a1a49c8da2ab62ad5e974fd50f1dabaeed70b23ca5bcf14bfe4aab8e000000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000007300a736aa00000000000187d1f7fdfee7f651fabc8bfcb6e086c278b77a7d01d00700001cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c00286bee000000000000000000000000000064a7b3b6e00d000000000000000000e40b5402000000000000000000000000000000000000000000000000").to_vec(), + ]), + execution_proof: ExecutionProof { + header: BeaconHeader { + slot: 4235, + proposer_index: 4, + parent_root: hex!("1b31e6264c19bcad120e434e0aede892e7d7c8ed80ab505cb593d9a4a16bc566").into(), + state_root: hex!("725f51771a0ecf72c647a283ab814ca088f998eb8c203181496b0b8e01f624fa").into(), + body_root: hex!("6f1c326d192e7e97e21e27b16fd7f000b8fa09b435ff028849927e382302b0ce").into(), + }, + ancestry_proof: Some(AncestryProof { + header_branch: vec![ + hex!("1b31e6264c19bcad120e434e0aede892e7d7c8ed80ab505cb593d9a4a16bc566").into(), + hex!("335eb186c077fa7053ec96dcc5d34502c997713d2d5bc4eb74842118d8cd5a64").into(), + hex!("326607faf2a7dfc9cfc4b6895f8f3d92a659552deb2c8fd1e892ec00c86c734c").into(), + hex!("4e20002125d7b6504df7c774f3f48e018e1e6762d03489149670a8335bba1425").into(), + hex!("e76af5cd61aade5aec8282b6f1df9046efa756b0466bba5e49032410f7739a1b").into(), + hex!("ee4dcd9527712116380cddafd120484a3bedf867225bbb86850b84decf6da730").into(), + hex!("e4687a07421d3150439a2cd2f09f3b468145d75b359a2e5fa88dfbec51725b15").into(), + hex!("38eaa78978e95759aa9b6f8504a8dbe36151f20ae41907e6a1ea165700ceefcd").into(), + hex!("1c1b071ec6f13e15c47d07d1bfbcc9135d6a6c819e68e7e6078a2007418c1a23").into(), + hex!("0b3ad7ad193c691c8c4ba1606ad2a90482cd1d033c7db58cfe739d0e20431e9e").into(), + hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b").into(), + hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220").into(), + hex!("b2ffec5f2c14640305dd941330f09216c53b99d198e93735a400a6d3a4de191f").into(), + ], + finalized_block_root: hex!("08be7a59e947f08cd95c4ef470758730bf9e3b0db0824cb663ea541c39b0e65c").into(), + }), + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: hex!("5d1186ae041f58785edb2f01248e95832f2e5e5d6c4eb8f7ff2f58980bfc2de9").into(), + fee_recipient: hex!("0000000000000000000000000000000000000000").into(), + state_root: hex!("2a66114d20e93082c8e9b47c8d401a937013487d757c9c2f3123cf43dc1f656d").into(), + receipts_root: hex!("106f1eaeac04e469da0020ad5c8a72af66323638bd3f561a3c8236063202c120").into(), + logs_bloom: hex!("00800000000000008000000000000000000000000000004000000000000000000400000000004000000000001000000010000000000000000000001008000000000000000000000001000008000040000000000000000000000000008000080000000000200000000000000000000000000100000000000000000010000000000000020000000000000000000000000000003000000000080018000000000000000000040004000021000000002000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000200820000000000").into(), + prev_randao: hex!("92e063c7e369b74149fdd1d7132ed2f635a19b9d8bff57637b8ee4736576426e").into(), + block_number: 4235, + gas_limit: 30000000, + gas_used: 97692, + timestamp: 1710556655, + extra_data: hex!("d983010d0b846765746888676f312e32312e368664617277696e").into(), + base_fee_per_gas: U256::from(7u64), + block_hash: hex!("ce24fe3047aa20a8f222cd1d04567c12b39455400d681141962c2130e690953f").into(), + transactions_root: hex!("0c8388731de94771777c60d452077065354d90d6e5088db61fc6a134684195cc").into(), + withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![ + hex!("99d397fa180078e66cd3a3b77bcb07553052f4e21d447167f3a406f663b14e6a").into(), + hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), + hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), + hex!("53ddf17147819c1abb918178b0230d965d1bc2c0d389f45e91e54cb1d2d468aa").into(), + ], + } + }, + }, + finalized_header: BeaconHeader { + slot: 4672, + proposer_index: 4, + parent_root: hex!("951233bf9f4bddfb2fa8f54e3bd0c7883779ef850e13e076baae3130dd7732db").into(), + state_root: hex!("4d303003b8cb097cbcc14b0f551ee70dac42de2c1cc2f4acfca7058ca9713291").into(), + body_root: hex!("664d13952b6f369bf4cf3af74d067ec33616eb57ed3a8a403fd5bae4fbf737dd").into(), + }, + block_roots_root: hex!("af71048297c070e6539cf3b9b90ae07d86d363454606bc239734629e6b49b983").into(), + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml new file mode 100644 index 000000000000..9b03370ec891 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "snowbridge-inbound-queue-v2-runtime-api" +description = "Snowbridge Inbound Queue V2 Runtime API" +version = "0.2.0" +authors = ["Snowfork "] +edition.workspace = true +repository.workspace = true +license = "Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +sp-api = { workspace = true } +snowbridge-core = { workspace = true } +snowbridge-router-primitives = { workspace = true } +xcm = { workspace = true } + +[features] +default = ["std"] +std = [ + "snowbridge-core/std", + "snowbridge-router-primitives/std", + "sp-api/std", + "xcm/std", +] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md new file mode 100644 index 000000000000..89b6b0e157c5 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/README.md @@ -0,0 +1,3 @@ +# Ethereum Inbound Queue V2 Runtime API + +Provides an API to dry-run inbound messages to get the XCM (and its execution cost) that will be executed on AssetHub. diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs new file mode 100644 index 000000000000..03720b7ca3d2 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +#![cfg_attr(not(feature = "std"), no_std)] + +use snowbridge_core::inbound::Proof; +use snowbridge_router_primitives::inbound::v2::Message; +use xcm::latest::Xcm; + +sp_api::decl_runtime_apis! { + pub trait InboundQueueApiV2 + { + /// Dry runs the provided message on AH to provide the XCM payload and execution cost. + fn dry_run(message: Message, proof: Proof) -> (Xcm<()>, u128); + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs new file mode 100644 index 000000000000..47207df7383c --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Implements the dry-run API. + +use crate::{Config, Error}; +use snowbridge_core::inbound::Proof; +use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; +use xcm::latest::Xcm; + +pub fn dry_run(message: Message, _proof: Proof) -> Result, Error> + where + T: Config, +{ + let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + Ok(xcm) +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs new file mode 100644 index 000000000000..b6d2a9739f3d --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use crate::Pallet as InboundQueue; +use frame_benchmarking::v2::*; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use snowbridge_pallet_inbound_queue_fixtures_v2::register_token::make_register_token_message; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn submit() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); + + let create_message = make_register_token_message(); + + T::Helper::initialize_storage( + create_message.finalized_header, + create_message.block_roots_root, + ); + + #[block] + { + assert_ok!(InboundQueue::::submit( + RawOrigin::Signed(caller.clone()).into(), + create_message.message, + )); + } + + Ok(()) + } + + impl_benchmark_test_suite!(InboundQueue, crate::mock::new_tester(), crate::mock::Test); +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs new file mode 100644 index 000000000000..41353954e5b2 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use snowbridge_core::inbound::Log; + +use sp_core::{RuntimeDebug, H160}; +use sp_std::prelude::*; + +use alloy_primitives::B256; +use alloy_sol_types::{sol, SolEvent}; + +sol! { + event OutboundMessageAccepted(uint64 indexed nonce, uint128 fee, bytes payload); +} + +/// An inbound message that has had its outer envelope decoded. +#[derive(Clone, RuntimeDebug)] +pub struct Envelope { + /// The address of the outbound queue on Ethereum that emitted this message as an event log + pub gateway: H160, + /// A nonce for enforcing replay protection and ordering. + pub nonce: u64, + /// Total fee paid in Ether on Ethereum, should cover all the cost + pub fee: u128, + /// The inner payload generated from the source application. + pub payload: Vec, +} + +#[derive(Copy, Clone, RuntimeDebug)] +pub struct EnvelopeDecodeError; + +impl TryFrom<&Log> for Envelope { + type Error = EnvelopeDecodeError; + + fn try_from(log: &Log) -> Result { + let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); + + let event = OutboundMessageAccepted::decode_log(topics, &log.data, true) + .map_err(|_| EnvelopeDecodeError)?; + + Ok(Self { + gateway: log.address, + nonce: event.nonce, + fee: event.fee, + payload: event.payload, + }) + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs new file mode 100644 index 000000000000..91c0acaa97a1 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -0,0 +1,240 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Inbound Queue +//! +//! # Overview +//! +//! Receives messages emitted by the Gateway contract on Ethereum, whereupon they are verified, +//! translated to XCM, and finally sent to their final destination parachain. +//! +//! The message relayers are rewarded using native currency from the sovereign account of the +//! destination parachain. +//! +//! # Extrinsics +//! +//! ## Governance +//! +//! * [`Call::set_operating_mode`]: Set the operating mode of the pallet. Can be used to disable +//! processing of inbound messages. +//! +//! ## Message Submission +//! +//! * [`Call::submit`]: Submit a message for verification and dispatch the final destination +//! parachain. +#![cfg_attr(not(feature = "std"), no_std)] +pub mod api; +mod envelope; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +pub mod weights; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod test; + +use codec::{Decode, DecodeAll, Encode}; +use envelope::Envelope; +use frame_support::PalletError; +use frame_system::ensure_signed; +use scale_info::TypeInfo; +use sp_core::H160; +use sp_std::vec; +use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}; + +use snowbridge_core::{ + inbound::{Message, VerificationError, Verifier}, + BasicOperatingMode, +}; +use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; +pub use weights::WeightInfo; + +#[cfg(feature = "runtime-benchmarks")] +use snowbridge_beacon_primitives::BeaconHeader; + +use snowbridge_router_primitives::inbound::v2::ConvertMessageError; + +pub use pallet::*; + +pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256); + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The verifier for inbound messages from Ethereum + type Verifier: Verifier; + + /// XCM message sender + type XcmSender: SendXcm; + /// Address of the Gateway contract + #[pallet::constant] + type GatewayAddress: Get; + type WeightInfo: WeightInfo; + /// AssetHub parachain ID + type AssetHubParaId: Get; + type MessageConverter: ConvertMessage; + #[cfg(feature = "runtime-benchmarks")] + type Helper: BenchmarkHelper; + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A message was received from Ethereum + MessageReceived { + /// The message nonce + nonce: u64, + /// ID of the XCM message which was forwarded to the final destination parachain + message_id: [u8; 32], + }, + /// Set OperatingMode + OperatingModeChanged { mode: BasicOperatingMode }, + } + + #[pallet::error] + pub enum Error { + /// Message came from an invalid outbound channel on the Ethereum side. + InvalidGateway, + /// Message has an invalid envelope. + InvalidEnvelope, + /// Message has an unexpected nonce. + InvalidNonce, + /// Message has an invalid payload. + InvalidPayload, + /// Message channel is invalid + InvalidChannel, + /// The max nonce for the type has been reached + MaxNonceReached, + /// Cannot convert location + InvalidAccountConversion, + /// Pallet is halted + Halted, + /// Message verification error, + Verification(VerificationError), + /// XCMP send failure + Send(SendError), + /// Message conversion error + ConvertMessage(ConvertMessageError), + } + + #[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo, PalletError)] + pub enum SendError { + NotApplicable, + NotRoutable, + Transport, + DestinationUnsupported, + ExceedsMaxMessageSize, + MissingArgument, + Fees, + } + + impl From for Error { + fn from(e: XcmpSendError) -> Self { + match e { + XcmpSendError::NotApplicable => Error::::Send(SendError::NotApplicable), + XcmpSendError::Unroutable => Error::::Send(SendError::NotRoutable), + XcmpSendError::Transport(_) => Error::::Send(SendError::Transport), + XcmpSendError::DestinationUnsupported => + Error::::Send(SendError::DestinationUnsupported), + XcmpSendError::ExceedsMaxMessageSize => + Error::::Send(SendError::ExceedsMaxMessageSize), + XcmpSendError::MissingArgument => Error::::Send(SendError::MissingArgument), + XcmpSendError::Fees => Error::::Send(SendError::Fees), + } + } + } + + /// The nonce of the message been processed or not + #[pallet::storage] + pub type Nonce = StorageMap<_, Identity, u64, bool, ValueQuery>; + + /// The current operating mode of the pallet. + #[pallet::storage] + #[pallet::getter(fn operating_mode)] + pub type OperatingMode = StorageValue<_, BasicOperatingMode, ValueQuery>; + + #[pallet::call] + impl Pallet { + /// Submit an inbound message originating from the Gateway contract on Ethereum + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::submit())] + pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { + let _who = ensure_signed(origin)?; + ensure!(!Self::operating_mode().is_halted(), Error::::Halted); + + // submit message to verifier for verification + T::Verifier::verify(&message.event_log, &message.proof) + .map_err(|e| Error::::Verification(e))?; + + // Decode event log into an Envelope + let envelope = + Envelope::try_from(&message.event_log).map_err(|_| Error::::InvalidEnvelope)?; + + // Verify that the message was submitted from the known Gateway contract + ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); + + // Verify the message has not been processed + ensure!(!Nonce::::contains_key(envelope.nonce), Error::::InvalidNonce); + + // Decode payload into `MessageV2` + let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) + .map_err(|_| Error::::InvalidPayload)?; + + let xcm = + T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + + // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: + // T::RewardLeger::deposit(who, envelope.fee.into())?; + // a. The submit extrinsic cost on BH + // b. The delivery cost to AH + // c. The execution cost on AH + // d. The execution cost on destination chain(if any) + // e. The reward + + // Attempt to forward XCM to AH + let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]); + let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; + Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); + + // Set nonce flag to true + Nonce::::insert(envelope.nonce, ()); + + Ok(()) + } + + /// Halt or resume all pallet operations. May only be called by root. + #[pallet::call_index(1)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + mode: BasicOperatingMode, + ) -> DispatchResult { + ensure_root(origin)?; + OperatingMode::::set(mode); + Self::deposit_event(Event::OperatingModeChanged { mode }); + Ok(()) + } + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs new file mode 100644 index 000000000000..f36535d88c3a --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use crate::{self as inbound_queue}; +use frame_support::{derive_impl, parameter_types, traits::ConstU32}; +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, +}; +use snowbridge_core::{ + inbound::{Log, Proof, VerificationError}, + TokenId, +}; +use snowbridge_router_primitives::inbound::v2::MessageToXcm; +use sp_core::H160; +use sp_runtime::{ + traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, + BuildStorage, MultiSignature, +}; +use sp_std::{convert::From, default::Default}; +use xcm::{latest::SendXcm, prelude::*}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + EthereumBeaconClient: snowbridge_pallet_ethereum_client::{Pallet, Call, Storage, Event}, + InboundQueue: inbound_queue::{Pallet, Call, Storage, Event}, + } +); + +pub type Signature = MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +type Balance = u128; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type AccountData = pallet_balances::AccountData; + type Block = Block; +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + +parameter_types! { + pub const ChainForkVersions: ForkVersions = ForkVersions{ + genesis: Fork { + version: [0, 0, 0, 1], // 0x00000001 + epoch: 0, + }, + altair: Fork { + version: [1, 0, 0, 1], // 0x01000001 + epoch: 0, + }, + bellatrix: Fork { + version: [2, 0, 0, 1], // 0x02000001 + epoch: 0, + }, + capella: Fork { + version: [3, 0, 0, 1], // 0x03000001 + epoch: 0, + }, + deneb: Fork { + version: [4, 0, 0, 1], // 0x04000001 + epoch: 4294967295, + } + }; +} + +impl snowbridge_pallet_ethereum_client::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ForkVersions = ChainForkVersions; + type FreeHeadersInterval = ConstU32<32>; + type WeightInfo = (); +} + +// Mock verifier +pub struct MockVerifier; + +impl Verifier for MockVerifier { + fn verify(_: &Log, _: &Proof) -> Result<(), VerificationError> { + Ok(()) + } +} + +const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper for Test { + // not implemented since the MockVerifier is used for tests + fn initialize_storage(_: BeaconHeader, _: H256) {} +} + +// Mock XCM sender that always succeeds +pub struct MockXcmSender; + +impl SendXcm for MockXcmSender { + type Ticket = Xcm<()>; + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + if let Some(location) = dest { + match location.unpack() { + (_, [Parachain(1001)]) => return Err(XcmpSendError::NotApplicable), + _ => Ok((xcm.clone().unwrap(), Assets::default())), + } + } else { + Ok((xcm.clone().unwrap(), Assets::default())) + } + } + + fn deliver(xcm: Self::Ticket) -> core::result::Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) + } +} + +pub struct MockTokenIdConvert; +impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::parent()) + } + fn convert_back(_loc: &Location) -> Option { + None + } +} + +parameter_types! { + pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; + pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); + pub const InboundQueuePalletInstance: u8 = 80; + pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); +} + +impl inbound_queue::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Verifier = MockVerifier; + type XcmSender = MockXcmSender; + type WeightInfo = (); + type GatewayAddress = GatewayAddress; + type AssetHubParaId = ConstU32<1000>; + type MessageConverter = + MessageToXcm; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Test; +} + +pub fn last_events(n: usize) -> Vec { + frame_system::Pallet::::events() + .into_iter() + .rev() + .take(n) + .rev() + .map(|e| e.event) + .collect() +} + +pub fn expect_events(e: Vec) { + assert_eq!(last_events(e.len()), e); +} + +pub fn setup() { + System::set_block_number(1); +} + +pub fn new_tester() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext: sp_io::TestExternalities = storage.into(); + ext.execute_with(setup); + ext +} + +// Generated from smoketests: +// cd smoketests +// ./make-bindings +// cargo test --test register_token -- --nocapture +pub fn mock_event_log() -> Log { + Log { + // gateway address + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // channel id + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + // message id + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + // Nonce + Payload + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(), + } +} + +pub fn mock_event_log_invalid_channel() -> Log { + Log { + address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // invalid channel id + hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), + } +} + +pub fn mock_event_log_invalid_gateway() -> Log { + Log { + // gateway address + address: H160::zero(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + // channel id + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + // message id + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + // Nonce + Payload + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), + } +} + +pub fn mock_execution_proof() -> ExecutionProof { + ExecutionProof { + header: BeaconHeader::default(), + ancestry_proof: None, + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: Default::default(), + fee_recipient: Default::default(), + state_root: Default::default(), + receipts_root: Default::default(), + logs_bloom: vec![], + prev_randao: Default::default(), + block_number: 0, + gas_limit: 0, + gas_used: 0, + timestamp: 0, + extra_data: vec![], + base_fee_per_gas: Default::default(), + block_hash: Default::default(), + transactions_root: Default::default(), + withdrawals_root: Default::default(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![], + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs new file mode 100644 index 000000000000..148a01b3efe1 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use super::*; + +use frame_support::{assert_noop, assert_ok}; +use hex_literal::hex; +use snowbridge_core::inbound::Proof; +use sp_keyring::AccountKeyring as Keyring; +use sp_runtime::DispatchError; + +use crate::{mock::*, Error, Event as InboundQueueEvent}; +use codec::DecodeLimit; +use snowbridge_router_primitives::inbound::v2::InboundAsset; +use sp_core::H256; +use xcm::{ + opaque::latest::{ + prelude::{ClearOrigin, ReceiveTeleportedAsset}, + Asset, + }, + prelude::*, + VersionedXcm, MAX_XCM_DECODE_DEPTH, +}; + +#[test] +fn test_submit_happy_path() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + + let origin = RuntimeOrigin::signed(relayer.clone()); + + // Submit message + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + expect_events(vec![InboundQueueEvent::MessageReceived { + nonce: 1, + message_id: [ + 183, 243, 1, 130, 170, 254, 104, 45, 116, 181, 146, 237, 14, 139, 138, 89, 43, 166, + 182, 24, 163, 222, 112, 238, 215, 83, 21, 160, 24, 88, 112, 9, + ], + } + .into()]); + }); +} + +#[test] +fn test_submit_xcm_invalid_channel() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Submit message + let message = Message { + event_log: mock_event_log_invalid_channel(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + assert_noop!( + InboundQueue::submit(origin.clone(), message.clone()), + Error::::InvalidChannel, + ); + }); +} + +#[test] +fn test_submit_with_invalid_gateway() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Submit message + let message = Message { + event_log: mock_event_log_invalid_gateway(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + assert_noop!( + InboundQueue::submit(origin.clone(), message.clone()), + Error::::InvalidGateway + ); + }); +} + +#[test] +fn test_submit_with_invalid_nonce() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Submit message + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + + // Submit the same again + assert_noop!( + InboundQueue::submit(origin.clone(), message.clone()), + Error::::InvalidNonce + ); + }); +} + +#[test] +fn test_set_operating_mode() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + + assert_ok!(InboundQueue::set_operating_mode( + RuntimeOrigin::root(), + snowbridge_core::BasicOperatingMode::Halted + )); + + assert_noop!(InboundQueue::submit(origin, message), Error::::Halted); + }); +} + +#[test] +fn test_set_operating_mode_root_only() { + new_tester().execute_with(|| { + assert_noop!( + InboundQueue::set_operating_mode( + RuntimeOrigin::signed(Keyring::Bob.into()), + snowbridge_core::BasicOperatingMode::Halted + ), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn test_send_native_erc20_token_payload() { + new_tester().execute_with(|| { + // To generate test data: forge test --match-test testSendEther -vvvv + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf04005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000b2d3595bf00600000000000000000000").to_vec(); + let message = MessageV2::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + + let inbound_message = message.unwrap(); + + let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let expected_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); + let expected_value = 500000000000000000u128; + let expected_xcm: Vec = vec![]; + let expected_claimer: Option> = None; + + assert_eq!(expected_origin, inbound_message.origin); + assert_eq!(1, inbound_message.assets.len()); + if let InboundAsset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { + assert_eq!(expected_token_id, *token_id); + assert_eq!(expected_value, *value); + } else { + panic!("Expected NativeTokenERC20 asset"); + } + assert_eq!(expected_xcm, inbound_message.xcm); + assert_eq!(expected_claimer, inbound_message.claimer); + }); +} + +#[test] +fn test_send_foreign_erc20_token_payload() { + new_tester().execute_with(|| { + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let message = MessageV2::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + + let inbound_message = message.unwrap(); + + let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let expected_token_id: H256 = hex!("97874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f40").into(); + let expected_value = 500000000000000000u128; + let expected_xcm: Vec = vec![]; + let expected_claimer: Option> = None; + + assert_eq!(expected_origin, inbound_message.origin); + assert_eq!(1, inbound_message.assets.len()); + if let InboundAsset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { + assert_eq!(expected_token_id, *token_id); + assert_eq!(expected_value, *value); + } else { + panic!("Expected ForeignTokenERC20 asset"); + } + assert_eq!(expected_xcm, inbound_message.xcm); + assert_eq!(expected_claimer, inbound_message.claimer); + }); +} + +#[test] +fn test_register_token_inbound_message_with_xcm_and_claimer() { + new_tester().execute_with(|| { + let payload = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a904005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000300508020401000002286bee0a015029e3b139f4393adda86303fcdaa35f60bb7092bf").to_vec(); + let message = MessageV2::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + + let inbound_message = message.unwrap(); + + let expected_origin: H160 = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a9").into(); + let expected_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); + let expected_value = 0u128; + let expected_xcm: Vec = hex!("0508020401000002286bee0a").to_vec(); + let expected_claimer: Option> = Some(hex!("29E3b139f4393aDda86303fcdAa35F60Bb7092bF").to_vec()); + + assert_eq!(expected_origin, inbound_message.origin); + assert_eq!(1, inbound_message.assets.len()); + if let InboundAsset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { + assert_eq!(expected_token_id, *token_id); + assert_eq!(expected_value, *value); + } else { + panic!("Expected NativeTokenERC20 asset"); + } + assert_eq!(expected_xcm, inbound_message.xcm); + assert_eq!(expected_claimer, inbound_message.claimer); + + // decode xcm + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut inbound_message.xcm.as_ref(), + ); + + assert_ok!(versioned_xcm.clone()); + + // Check if decoding was successful + let decoded_instructions = match versioned_xcm.unwrap() { + VersionedXcm::V5(decoded) => decoded, + _ => { + panic!("unexpected xcm version found") + } + }; + + let mut decoded_instructions = decoded_instructions.into_iter(); + let decoded_first = decoded_instructions.next().take(); + assert!(decoded_first.is_some()); + let decoded_second = decoded_instructions.next().take(); + assert!(decoded_second.is_some()); + assert_eq!(ClearOrigin, decoded_second.unwrap(), "Second instruction (ClearOrigin) does not match."); + }); +} + +#[test] +fn encode_xcm() { + new_tester().execute_with(|| { + let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); + + let instructions: Xcm<()> = + vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); + + let versioned_xcm_message = VersionedXcm::V5(instructions.clone()); + + let xcm_bytes = VersionedXcm::encode(&versioned_xcm_message); + let hex_string = hex::encode(xcm_bytes.clone()); + + println!("xcm hex: {}", hex_string); + + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut xcm_bytes.as_ref(), + ); + + assert_ok!(versioned_xcm.clone()); + + // Check if decoding was successful + let decoded_instructions = match versioned_xcm.unwrap() { + VersionedXcm::V5(decoded) => decoded, + _ => { + panic!("unexpected xcm version found") + }, + }; + + let mut original_instructions = instructions.into_iter(); + let mut decoded_instructions = decoded_instructions.into_iter(); + + let original_first = original_instructions.next().take(); + let decoded_first = decoded_instructions.next().take(); + assert_eq!( + original_first, decoded_first, + "First instruction (ReceiveTeleportedAsset) does not match." + ); + + let original_second = original_instructions.next().take(); + let decoded_second = decoded_instructions.next().take(); + assert_eq!( + original_second, decoded_second, + "Second instruction (ClearOrigin) does not match." + ); + }); +} diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs new file mode 100644 index 000000000000..c2c665f40d9e --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/weights.rs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Autogenerated weights for `snowbridge_inbound_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-07-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `macbook pro 14 m2`, CPU: `m2-arm64` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for ethereum_beacon_client. +pub trait WeightInfo { + fn submit() -> Weight; +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn submit() -> Weight { + Weight::from_parts(70_000_000, 0) + .saturating_add(Weight::from_parts(0, 3601)) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } +} diff --git a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs index 423b92b9fae0..5814886fe355 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/lib.rs @@ -61,7 +61,7 @@ use snowbridge_core::{ sibling_sovereign_account, BasicOperatingMode, Channel, ChannelId, ParaId, PricingParameters, StaticLookup, }; -use snowbridge_router_primitives::inbound::{ +use snowbridge_router_primitives::inbound::v1::{ ConvertMessage, ConvertMessageError, VersionedMessage, }; use sp_runtime::{traits::Saturating, SaturatedConversion, TokenError}; diff --git a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs index 675d4b691593..82862616466d 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs @@ -12,7 +12,7 @@ use snowbridge_core::{ inbound::{Log, Proof, VerificationError}, meth, Channel, ChannelId, PricingParameters, Rewards, StaticLookup, TokenId, }; -use snowbridge_router_primitives::inbound::MessageToXcm; +use snowbridge_router_primitives::inbound::v1::MessageToXcm; use sp_core::{H160, H256}; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index ee8d481cec12..aa4b3177c00b 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -17,6 +17,7 @@ scale-info = { features = ["derive"], workspace = true } log = { workspace = true } frame-support = { workspace = true } +frame-system = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } @@ -24,6 +25,7 @@ sp-std = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } +xcm-builder = { workspace = true } snowbridge-core = { workspace = true } @@ -36,6 +38,7 @@ default = ["std"] std = [ "codec/std", "frame-support/std", + "frame-system/std", "log/std", "scale-info/std", "snowbridge-core/std", @@ -43,12 +46,15 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "xcm-builder/std", "xcm-executor/std", "xcm/std", ] runtime-benchmarks = [ "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", ] diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index e03560f66e24..1e43bd7544cf 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -1,479 +1,37 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork -//! Converts messages from Ethereum to XCM messages - -#[cfg(test)] -mod tests; - -use codec::{Decode, Encode}; -use core::marker::PhantomData; -use frame_support::{traits::tokens::Balance as BalanceT, PalletError}; -use scale_info::TypeInfo; -use snowbridge_core::TokenId; -use sp_core::{Get, RuntimeDebug, H160, H256}; -use sp_io::hashing::blake2_256; -use sp_runtime::{traits::MaybeEquivalence, MultiAddress}; -use sp_std::prelude::*; -use xcm::prelude::{Junction::AccountKey20, *}; +// SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. + +pub mod v1; +pub mod v2; +use codec::Encode; +use sp_core::blake2_256; +use sp_std::marker::PhantomData; +use xcm::prelude::{AccountKey20, Ethereum, GlobalConsensus, Location}; use xcm_executor::traits::ConvertLocation; -const MINIMUM_DEPOSIT: u128 = 1; - -/// Messages from Ethereum are versioned. This is because in future, -/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. -/// Instead having BridgeHub transcode the messages into XCM. -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum VersionedMessage { - V1(MessageV1), -} - -/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are -/// self-contained, in that they can be transcoded using only information in the message. -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub struct MessageV1 { - /// EIP-155 chain id of the origin Ethereum network - pub chain_id: u64, - /// The command originating from the Gateway contract - pub command: Command, -} - -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Command { - /// Register a wrapped token on the AssetHub `ForeignAssets` pallet - RegisterToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Ethereum token to AssetHub or another parachain - SendToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Polkadot token back to the original parachain - SendNativeToken { - /// The Id of the token - token_id: TokenId, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, -} - -/// Destination for bridged tokens -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum Destination { - /// The funds will be deposited into account `id` on AssetHub - AccountId32 { id: [u8; 32] }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId32 { - para_id: u32, - id: [u8; 32], - /// XCM execution fee on final destination - fee: u128, - }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId20 { - para_id: u32, - id: [u8; 20], - /// XCM execution fee on final destination - fee: u128, - }, -} - -pub struct MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, -> where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - Balance: BalanceT, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, +pub struct GlobalConsensusEthereumConvertsFor(PhantomData); +impl ConvertLocation for GlobalConsensusEthereumConvertsFor + where + AccountId: From<[u8; 32]> + Clone, { - _phantom: PhantomData<( - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - )>, -} - -/// Reason why a message conversion failed. -#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] -pub enum ConvertMessageError { - /// The message version is not supported for conversion. - UnsupportedVersion, - InvalidDestination, - InvalidToken, - /// The fee asset is not supported for conversion. - UnsupportedFeeAsset, - CannotReanchor, -} - -/// convert the inbound message to xcm which will be forwarded to the destination chain -pub trait ConvertMessage { - type Balance: BalanceT + From; - type AccountId; - /// Converts a versioned message into an XCM message and an optional topicID - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; + fn convert_location(location: &Location) -> Option { + match location.unpack() { + (2, [GlobalConsensus(Ethereum { chain_id })]) => + Some(Self::from_chain_id(chain_id).into()), + (2, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) => + Some(Self::from_chain_id_with_key(chain_id, *key).into()), + _ => None, + } + } +} +impl GlobalConsensusEthereumConvertsFor { + pub fn from_chain_id(chain_id: &u64) -> [u8; 32] { + (b"ethereum-chain", chain_id).using_encoded(blake2_256) + } + pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] { + (b"ethereum-chain", chain_id, key).using_encoded(blake2_256) + } } pub type CallIndex = [u8; 2]; - -impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > ConvertMessage - for MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > -where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - type Balance = Balance; - type AccountId = AccountId; - - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { - use Command::*; - use VersionedMessage::*; - match message { - V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => - Ok(Self::convert_register_token(message_id, chain_id, token, fee)), - V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => - Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), - V1(MessageV1 { - chain_id, - command: SendNativeToken { token_id, destination, amount, fee }, - }) => Self::convert_send_native_token( - message_id, - chain_id, - token_id, - destination, - amount, - fee, - ), - } - } -} - -impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > - MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - > -where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, -{ - fn convert_register_token( - message_id: H256, - chain_id: u64, - token: H160, - fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let xcm_fee: Asset = (Location::parent(), fee).into(); - let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); - - let total_amount = fee + CreateAssetDeposit::get(); - let total: Asset = (Location::parent(), total_amount).into(); - - let bridge_location = Location::new(2, GlobalConsensus(network)); - - let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - let asset_id = Self::convert_token_address(network, token); - let create_call_index: [u8; 2] = CreateAssetCall::get(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let xcm: Xcm<()> = vec![ - // Teleport required fees. - ReceiveTeleportedAsset(total.into()), - // Pay for execution. - BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, - // Fund the snowbridge sovereign with the required deposit for creation. - DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, - // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be - // deposited to snowbridge sovereign, instead of being trapped, regardless of - // `Transact` success or not. - SetAppendix(Xcm(vec![ - RefundSurplus, - DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, - ])), - // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - // Change origin to the bridge. - UniversalOrigin(GlobalConsensus(network)), - // Call create_asset on foreign assets pallet. - Transact { - origin_kind: OriginKind::Xcm, - call: ( - create_call_index, - asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner), - MINIMUM_DEPOSIT, - ) - .encode() - .into(), - }, - // Forward message id to Asset Hub - SetTopic(message_id.into()), - // Once the program ends here, appendix program will run, which will deposit any - // leftover fee to snowbridge sovereign. - ] - .into(); - - (xcm, total_amount.into()) - } - - fn convert_send_token( - message_id: H256, - chain_id: u64, - token: H160, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - let asset: Asset = (Self::convert_token_address(network, token), amount).into(); - - let (dest_para_id, beneficiary, dest_para_fee) = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - (None, Location::new(0, [AccountId32 { network: None, id }]), 0), - // Final destination is a 32-byte account on a sibling of AssetHub - Destination::ForeignAccountId32 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountId32 { network: None, id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - // Final destination is a 20-byte account on a sibling of AssetHub - Destination::ForeignAccountId20 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountKey20 { network: None, key: id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - }; - - let total_fees = asset_hub_fee.saturating_add(dest_para_fee); - let total_fee_asset: Asset = (Location::parent(), total_fees).into(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let mut instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - ReserveAssetDeposited(asset.clone().into()), - ClearOrigin, - ]; - - match dest_para_id { - Some(dest_para_id) => { - let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); - let bridge_location = Location::new(2, GlobalConsensus(network)); - - instructions.extend(vec![ - // After program finishes deposit any leftover assets to the snowbridge - // sovereign. - SetAppendix(Xcm(vec![DepositAsset { - assets: Wild(AllCounted(2)), - beneficiary: bridge_location, - }])), - // Perform a deposit reserve to send to destination chain. - DepositReserveAsset { - assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), - dest: Location::new(1, [Parachain(dest_para_id)]), - xcm: vec![ - // Buy execution on target. - BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, - // Deposit assets to beneficiary. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - // Forward message id to destination parachain. - SetTopic(message_id.into()), - ] - .into(), - }, - ]); - }, - None => { - instructions.extend(vec![ - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - ]); - }, - } - - // Forward message id to Asset Hub. - instructions.push(SetTopic(message_id.into())); - - // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since - // they are teleported within `instructions`). - (instructions.into(), total_fees.into()) - } - - // Convert ERC20 token address to a location that can be understood by Assets Hub. - fn convert_token_address(network: NetworkId, token: H160) -> Location { - Location::new( - 2, - [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], - ) - } - - /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign - /// account of the Gateway contract and either deposits those assets into a recipient account or - /// forwards the assets to another parachain. - fn convert_send_native_token( - message_id: H256, - chain_id: u64, - token_id: TokenId, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let beneficiary = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - Ok(Location::new(0, [AccountId32 { network: None, id }])), - // Forwarding to a destination parachain is not allowed for PNA and is validated on the - // Ethereum side. https://github.com/Snowfork/snowbridge/blob/e87ddb2215b513455c844463a25323bb9c01ff36/contracts/src/Assets.sol#L216-L224 - _ => Err(ConvertMessageError::InvalidDestination), - }?; - - let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let asset_loc = - ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; - - let mut reanchored_asset_loc = asset_loc.clone(); - reanchored_asset_loc - .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) - .map_err(|_| ConvertMessageError::CannotReanchor)?; - - let asset: Asset = (reanchored_asset_loc, amount).into(); - - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.clone().into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - WithdrawAsset(asset.clone().into()), - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - SetTopic(message_id.into()), - ]; - - // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also - // teleport fees) - Ok((instructions.into(), asset_hub_fee.into())) - } -} - -pub struct EthereumLocationsConverterFor(PhantomData); -impl ConvertLocation for EthereumLocationsConverterFor -where - AccountId: From<[u8; 32]> + Clone, -{ - fn convert_location(location: &Location) -> Option { - match location.unpack() { - (2, [GlobalConsensus(Ethereum { chain_id })]) => - Some(Self::from_chain_id(chain_id).into()), - (2, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) => - Some(Self::from_chain_id_with_key(chain_id, *key).into()), - _ => None, - } - } -} - -impl EthereumLocationsConverterFor { - pub fn from_chain_id(chain_id: &u64) -> [u8; 32] { - (b"ethereum-chain", chain_id).using_encoded(blake2_256) - } - pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] { - (b"ethereum-chain", chain_id, key).using_encoded(blake2_256) - } -} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs new file mode 100644 index 000000000000..d413674970b2 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/v1.rs @@ -0,0 +1,520 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts messages from Ethereum to XCM messages + +use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use frame_support::{traits::tokens::Balance as BalanceT, PalletError}; +use scale_info::TypeInfo; +use snowbridge_core::TokenId; +use sp_core::{Get, RuntimeDebug, H160, H256}; +use sp_runtime::{traits::MaybeEquivalence, MultiAddress}; +use sp_std::prelude::*; +use xcm::prelude::{Junction::AccountKey20, *}; + +const MINIMUM_DEPOSIT: u128 = 1; + +/// Messages from Ethereum are versioned. This is because in future, +/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. +/// Instead having BridgeHub transcode the messages into XCM. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum VersionedMessage { + V1(MessageV1), +} + +/// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are +/// self-contained, in that they can be transcoded using only information in the message. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub struct MessageV1 { + /// EIP-155 chain id of the origin Ethereum network + pub chain_id: u64, + /// The command originating from the Gateway contract + pub command: Command, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum Command { + /// Register a wrapped token on the AssetHub `ForeignAssets` pallet + RegisterToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// XCM execution fee on AssetHub + fee: u128, + }, + /// Send Ethereum token to AssetHub or another parachain + SendToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, + /// Send Polkadot token back to the original parachain + SendNativeToken { + /// The Id of the token + token_id: TokenId, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, +} + +/// Destination for bridged tokens +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum Destination { + /// The funds will be deposited into account `id` on AssetHub + AccountId32 { id: [u8; 32] }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId32 { + para_id: u32, + id: [u8; 32], + /// XCM execution fee on final destination + fee: u128, + }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId20 { + para_id: u32, + id: [u8; 20], + /// XCM execution fee on final destination + fee: u128, + }, +} + +pub struct MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, +> where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + Balance: BalanceT, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + _phantom: PhantomData<( + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + )>, +} + +/// Reason why a message conversion failed. +#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] +pub enum ConvertMessageError { + /// The message version is not supported for conversion. + UnsupportedVersion, + InvalidDestination, + InvalidToken, + /// The fee asset is not supported for conversion. + UnsupportedFeeAsset, + CannotReanchor, +} + +/// convert the inbound message to xcm which will be forwarded to the destination chain +pub trait ConvertMessage { + type Balance: BalanceT + From; + type AccountId; + /// Converts a versioned message into an XCM message and an optional topicID + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; +} + +impl< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, +> ConvertMessage +for MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, +> + where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + type Balance = Balance; + type AccountId = AccountId; + + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { + use Command::*; + use VersionedMessage::*; + match message { + V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => + Ok(Self::convert_register_token(message_id, chain_id, token, fee)), + V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => + Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), + V1(MessageV1 { + chain_id, + command: SendNativeToken { token_id, destination, amount, fee }, + }) => Self::convert_send_native_token( + message_id, + chain_id, + token_id, + destination, + amount, + fee, + ), + } + } +} + +impl< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, +> +MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, +> + where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, +{ + fn convert_register_token( + message_id: H256, + chain_id: u64, + token: H160, + fee: u128, + ) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let xcm_fee: Asset = (Location::parent(), fee).into(); + let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); + + let total_amount = fee + CreateAssetDeposit::get(); + let total: Asset = (Location::parent(), total_amount).into(); + + let bridge_location = Location::new(2, GlobalConsensus(network)); + + let owner = GlobalConsensusEthereumConvertsFor::<[u8; 32]>::from_chain_id(&chain_id); + let asset_id = Self::convert_token_address(network, token); + let create_call_index: [u8; 2] = CreateAssetCall::get(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let xcm: Xcm<()> = vec![ + // Teleport required fees. + ReceiveTeleportedAsset(total.into()), + // Pay for execution. + BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, + // Fund the snowbridge sovereign with the required deposit for creation. + DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, + // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be + // deposited to snowbridge sovereign, instead of being trapped, regardless of + // `Transact` success or not. + SetAppendix(Xcm(vec![ + RefundSurplus, + DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, + ])), + // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + // Change origin to the bridge. + UniversalOrigin(GlobalConsensus(network)), + // Call create_asset on foreign assets pallet. + Transact { + origin_kind: OriginKind::Xcm, + call: ( + create_call_index, + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner), + MINIMUM_DEPOSIT, + ) + .encode() + .into(), + }, + // Forward message id to Asset Hub + SetTopic(message_id.into()), + // Once the program ends here, appendix program will run, which will deposit any + // leftover fee to snowbridge sovereign. + ] + .into(); + + (xcm, total_amount.into()) + } + + fn convert_send_token( + message_id: H256, + chain_id: u64, + token: H160, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + let asset: Asset = (Self::convert_token_address(network, token), amount).into(); + + let (dest_para_id, beneficiary, dest_para_fee) = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => + (None, Location::new(0, [AccountId32 { network: None, id }]), 0), + // Final destination is a 32-byte account on a sibling of AssetHub + Destination::ForeignAccountId32 { para_id, id, fee } => ( + Some(para_id), + Location::new(0, [AccountId32 { network: None, id }]), + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + // Final destination is a 20-byte account on a sibling of AssetHub + Destination::ForeignAccountId20 { para_id, id, fee } => ( + Some(para_id), + Location::new(0, [AccountKey20 { network: None, key: id }]), + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + }; + + let total_fees = asset_hub_fee.saturating_add(dest_para_fee); + let total_fee_asset: Asset = (Location::parent(), total_fees).into(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let mut instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + ReserveAssetDeposited(asset.clone().into()), + ClearOrigin, + ]; + + match dest_para_id { + Some(dest_para_id) => { + let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); + let bridge_location = Location::new(2, GlobalConsensus(network)); + + instructions.extend(vec![ + // After program finishes deposit any leftover assets to the snowbridge + // sovereign. + SetAppendix(Xcm(vec![DepositAsset { + assets: Wild(AllCounted(2)), + beneficiary: bridge_location, + }])), + // Perform a deposit reserve to send to destination chain. + DepositReserveAsset { + assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), + dest: Location::new(1, [Parachain(dest_para_id)]), + xcm: vec![ + // Buy execution on target. + BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, + // Deposit assets to beneficiary. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + // Forward message id to destination parachain. + SetTopic(message_id.into()), + ] + .into(), + }, + ]); + }, + None => { + instructions.extend(vec![ + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + ]); + }, + } + + // Forward message id to Asset Hub. + instructions.push(SetTopic(message_id.into())); + + // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since + // they are teleported within `instructions`). + (instructions.into(), total_fees.into()) + } + + // Convert ERC20 token address to a location that can be understood by Assets Hub. + fn convert_token_address(network: NetworkId, token: H160) -> Location { + Location::new( + 2, + [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], + ) + } + + /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign + /// account of the Gateway contract and either deposits those assets into a recipient account or + /// forwards the assets to another parachain. + fn convert_send_native_token( + message_id: H256, + chain_id: u64, + token_id: TokenId, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let beneficiary = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => + Ok(Location::new(0, [AccountId32 { network: None, id }])), + _ => Err(ConvertMessageError::InvalidDestination), + }?; + + let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let asset_loc = + ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; + + let mut reanchored_asset_loc = asset_loc.clone(); + reanchored_asset_loc + .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) + .map_err(|_| ConvertMessageError::CannotReanchor)?; + + let asset: Asset = (reanchored_asset_loc, amount).into(); + + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.clone().into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + WithdrawAsset(asset.clone().into()), + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + SetTopic(message_id.into()), + ]; + + // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also + // teleport fees) + Ok((instructions.into(), asset_hub_fee.into())) + } +} + +#[cfg(test)] +mod tests { + use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; + use frame_support::{assert_ok, parameter_types}; + use hex_literal::hex; + use xcm::prelude::*; + use xcm_executor::traits::ConvertLocation; + + const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; + + parameter_types! { + pub EthereumNetwork: NetworkId = NETWORK; + + pub const CreateAssetCall: CallIndex = [1, 1]; + pub const CreateAssetExecutionFee: u128 = 123; + pub const CreateAssetDeposit: u128 = 891; + pub const SendTokenExecutionFee: u128 = 592; + } + + #[test] + fn test_contract_location_with_network_converts_successfully() { + let expected_account: [u8; 32] = + hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); + let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); + + let account = + GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location) + .unwrap(); + + assert_eq!(account, expected_account); + } + + #[test] + fn test_contract_location_with_incorrect_location_fails_convert() { + let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); + + assert_eq!( + GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location), + None, + ); + } + + #[test] + fn test_reanchor_all_assets() { + let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); + let ethereum = Location::new(2, ethereum_context.clone()); + let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + let global_ah = Location::new(1, ah_context.clone()); + let assets = vec![ + // DOT + Location::new(1, []), + // GLMR (Some Polkadot parachain currency) + Location::new(1, [Parachain(2004)]), + // AH asset + Location::new(0, [PalletInstance(50), GeneralIndex(42)]), + // KSM + Location::new(2, [GlobalConsensus(Kusama)]), + // KAR (Some Kusama parachain currency) + Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), + ]; + for asset in assets.iter() { + // reanchor logic in pallet_xcm on AH + let mut reanchored_asset = asset.clone(); + assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); + // reanchor back to original location in context of Ethereum + let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); + assert_ok!( + reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context) + ); + assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); + } + } +} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs new file mode 100644 index 000000000000..69cacdb995fa --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +//! Converts messages from Ethereum to XCM messages + +use codec::{Decode, DecodeLimit, Encode}; +use core::marker::PhantomData; +use frame_support::PalletError; +use scale_info::TypeInfo; +use snowbridge_core::TokenId; +use sp_core::{Get, RuntimeDebug, H160, H256}; +use sp_runtime::traits::MaybeEquivalence; +use sp_std::prelude::*; +use xcm::{ + prelude::{Junction::AccountKey20, *}, + MAX_XCM_DECODE_DEPTH, +}; + +const LOG_TARGET: &str = "snowbridge-router-primitives"; + +/// Messages from Ethereum are versioned. This is because in future, +/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. +/// Instead having BridgeHub transcode the messages into XCM. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum VersionedMessage { + V2(Message), +} + +/// The ethereum side sends messages which are transcoded into XCM on BH. These messages are +/// self-contained, in that they can be transcoded using only information in the message. +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Message { + /// The origin address + pub origin: H160, + /// The assets + pub assets: Vec, + // The command originating from the Gateway contract + pub xcm: Vec, + // The claimer in the case that funds get trapped. + pub claimer: Option>, +} + +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum Asset { + NativeTokenERC20 { + /// The native token ID + token_id: H160, + /// The monetary value of the asset + value: u128, + }, + ForeignTokenERC20 { + /// The foreign token ID + token_id: H256, + /// The monetary value of the asset + value: u128, + }, +} + +/// Reason why a message conversion failed. +#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] +pub enum ConvertMessageError { + /// The XCM provided with the message could not be decoded into XCM. + InvalidXCM, + /// The XCM provided with the message could not be decoded into versioned XCM. + InvalidVersionedXCM, + /// Invalid claimer MultiAddress provided in payload. + InvalidClaimer, + /// Invalid foreign ERC20 token ID + InvalidAsset, +} + +pub trait ConvertMessage { + fn convert(message: Message) -> Result, ConvertMessageError>; +} + +pub struct MessageToXcm + where + EthereumNetwork: Get, + InboundQueuePalletInstance: Get, + ConvertAssetId: MaybeEquivalence, +{ + _phantom: PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId)>, +} + +impl ConvertMessage +for MessageToXcm + where + EthereumNetwork: Get, + InboundQueuePalletInstance: Get, + ConvertAssetId: MaybeEquivalence, +{ + fn convert(message: Message) -> Result, ConvertMessageError> { + let mut message_xcm: Xcm<()> = Xcm::new(); + if message.xcm.len() > 0 { + // Decode xcm + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut message.xcm.as_ref(), + ) + .map_err(|_| ConvertMessageError::InvalidVersionedXCM)?; + message_xcm = versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; + } + + log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); + + let network = EthereumNetwork::get(); + + let origin_location = Location::new(2, GlobalConsensus(network)) + .push_interior(AccountKey20 { key: message.origin.into(), network: None }) + .map_err(|_| ConvertMessageError::InvalidXCM)?; + + let network = EthereumNetwork::get(); + + let fee_asset = Location::new(1, Here); + let fee_value = 1_000_000_000u128; // TODO get from command + let fee: xcm::prelude::Asset = (fee_asset, fee_value).into(); + let mut instructions = vec![ + ReceiveTeleportedAsset(fee.clone().into()), + BuyExecution { fees: fee, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), + UniversalOrigin(GlobalConsensus(network)), + ]; + + for asset in &message.assets { + match asset { + Asset::NativeTokenERC20 { token_id, value } => { + let token_location: Location = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: (*token_id).into() }, + ], + ); + instructions.push(ReserveAssetDeposited((token_location, *value).into())); + }, + Asset::ForeignTokenERC20 { token_id, value } => { + let asset_id = ConvertAssetId::convert(&token_id) + .ok_or(ConvertMessageError::InvalidAsset)?; + instructions.push(WithdrawAsset((asset_id, *value).into())); + }, + } + } + + if let Some(claimer) = message.claimer { + let claimer = Junction::decode(&mut claimer.as_ref()) + .map_err(|_| ConvertMessageError::InvalidClaimer)?; + let claimer_location: Location = Location::new(0, [claimer.into()]); + instructions.push(SetAssetClaimer { location: claimer_location }); + } + + // Set the alias origin to the original sender on Ethereum. Important to be before the + // arbitrary XCM that is appended to the message on the next line. + instructions.push(AliasOrigin(origin_location.into())); + + // Add the XCM sent in the message to the end of the xcm instruction + instructions.extend(message_xcm.0); + + Ok(instructions.into()) + } +} + +#[cfg(test)] +mod tests { + use crate::inbound::{ + v2::{ConvertMessage, Message, MessageToXcm}, + CallIndex, GlobalConsensusEthereumConvertsFor, + }; + use codec::Decode; + use frame_support::{assert_ok, parameter_types}; + use hex_literal::hex; + use sp_runtime::traits::ConstU8; + use xcm::prelude::*; + use xcm_executor::traits::ConvertLocation; + + const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; + + parameter_types! { + pub EthereumNetwork: NetworkId = NETWORK; + + pub const CreateAssetCall: CallIndex = [1, 1]; + pub const CreateAssetExecutionFee: u128 = 123; + pub const CreateAssetDeposit: u128 = 891; + pub const SendTokenExecutionFee: u128 = 592; + } + + #[test] + fn test_contract_location_with_network_converts_successfully() { + let expected_account: [u8; 32] = + hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); + let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); + + let account = + GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location) + .unwrap(); + + assert_eq!(account, expected_account); + } + + #[test] + fn test_contract_location_with_incorrect_location_fails_convert() { + let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); + + assert_eq!( + GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location), + None, + ); + } + + #[test] + fn test_reanchor_all_assets() { + let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); + let ethereum = Location::new(2, ethereum_context.clone()); + let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + let global_ah = Location::new(1, ah_context.clone()); + let assets = vec![ + // DOT + Location::new(1, []), + // GLMR (Some Polkadot parachain currency) + Location::new(1, [Parachain(2004)]), + // AH asset + Location::new(0, [PalletInstance(50), GeneralIndex(42)]), + // KSM + Location::new(2, [GlobalConsensus(Kusama)]), + // KAR (Some Kusama parachain currency) + Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), + ]; + for asset in assets.iter() { + // reanchor logic in pallet_xcm on AH + let mut reanchored_asset = asset.clone(); + assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); + // reanchor back to original location in context of Ethereum + let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); + assert_ok!( + reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context) + ); + assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); + } + } + + #[test] + fn test_convert_message() { + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let message = Message::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + let result = MessageToXcm::>::convert(message.unwrap()); + assert_ok!(result); + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 94921fd8af9a..def9b1af6207 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -25,7 +25,9 @@ use crate::{ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; -use snowbridge_router_primitives::{inbound::MessageToXcm, outbound::EthereumBlobExporter}; +use snowbridge_router_primitives::{ + outbound::{v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2}, +}; use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, @@ -84,7 +86,7 @@ impl snowbridge_pallet_inbound_queue::Config for Runtime { type GatewayAddress = EthereumGatewayAddress; #[cfg(feature = "runtime-benchmarks")] type Helper = Runtime; - type MessageConverter = MessageToXcm< + type MessageConverter = snowbridge_router_primitives::inbound::v1::MessageToXcm< CreateAssetCall, CreateAssetDeposit, ConstU8, @@ -102,6 +104,21 @@ impl snowbridge_pallet_inbound_queue::Config for Runtime { type AssetTransactor = ::AssetTransactor; } +impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Verifier = snowbridge_pallet_ethereum_client::Pallet; + #[cfg(not(feature = "runtime-benchmarks"))] + type XcmSender = XcmRouter; + #[cfg(feature = "runtime-benchmarks")] + type XcmSender = DoNothingRouter; + type GatewayAddress = EthereumGatewayAddress; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Runtime; + type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; + type AssetHubParaId = ConstU32<1000>; + type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm, EthereumSystem>; +} + impl snowbridge_pallet_outbound_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Hashing = Keccak256; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 065400016791..8f8f1c93bd6d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -563,6 +563,7 @@ construct_runtime!( EthereumOutboundQueue: snowbridge_pallet_outbound_queue = 81, EthereumBeaconClient: snowbridge_pallet_ethereum_client = 82, EthereumSystem: snowbridge_pallet_system = 83, + EthereumInboundQueueV2: snowbridge_pallet_inbound_queue_v2 = 84, // Message Queue. Importantly, is registered last so that messages are processed after // the `on_initialize` hooks of bridging pallets. @@ -621,6 +622,7 @@ mod benches { [snowbridge_pallet_outbound_queue, EthereumOutboundQueue] [snowbridge_pallet_system, EthereumSystem] [snowbridge_pallet_ethereum_client, EthereumBeaconClient] + [snowbridge_pallet_inbound_queue_v2, EthereumInboundQueueV2] ); } From 70b6c7b08a0bc27c6155bca66461af97b829d208 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 21 Nov 2024 00:04:46 +0800 Subject: [PATCH 005/340] Migrate pallet-scheduler benchmark to v2 (#6292) Part of: - #6202. --------- Signed-off-by: Xavier Lau Co-authored-by: Giuseppe Re Co-authored-by: Guillaume Thiolliere --- substrate/frame/scheduler/src/benchmarking.rs | 306 ++++++++++++------ 1 file changed, 202 insertions(+), 104 deletions(-) diff --git a/substrate/frame/scheduler/src/benchmarking.rs b/substrate/frame/scheduler/src/benchmarking.rs index d0a14fc73d64..ff40e8ef8abf 100644 --- a/substrate/frame/scheduler/src/benchmarking.rs +++ b/substrate/frame/scheduler/src/benchmarking.rs @@ -17,25 +17,23 @@ //! Scheduler pallet benchmarking. -use super::*; use alloc::vec; -use frame_benchmarking::v1::{account, benchmarks, BenchmarkError}; +use frame_benchmarking::v2::*; use frame_support::{ ensure, traits::{schedule::Priority, BoundedInline}, weights::WeightMeter, }; -use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; +use frame_system::{EventRecord, RawOrigin}; -use crate::Pallet as Scheduler; -use frame_system::{Call as SystemCall, EventRecord}; +use crate::*; -const SEED: u32 = 0; +type SystemCall = frame_system::Call; +type SystemOrigin = ::RuntimeOrigin; +const SEED: u32 = 0; const BLOCK_NUMBER: u32 = 2; -type SystemOrigin = ::RuntimeOrigin; - fn assert_last_event(generic_event: ::RuntimeEvent) { let events = frame_system::Pallet::::events(); let system_event: ::RuntimeEvent = generic_event.into(); @@ -61,7 +59,7 @@ fn fill_schedule( let call = make_call::(None); let period = Some(((i + 100).into(), 100)); let name = u32_to_name(i); - Scheduler::::do_schedule_named(name, t, period, 0, origin.clone(), call)?; + Pallet::::do_schedule_named(name, t, period, 0, origin.clone(), call)?; } ensure!(Agenda::::get(when).len() == n as usize, "didn't fill schedule"); Ok(()) @@ -134,107 +132,160 @@ fn make_origin(signed: bool) -> ::PalletsOrigin { } } -benchmarks! { +#[benchmarks] +mod benchmarks { + use super::*; + // `service_agendas` when no work is done. - service_agendas_base { - let now = BlockNumberFor::::from(BLOCK_NUMBER); + #[benchmark] + fn service_agendas_base() { + let now = BLOCK_NUMBER.into(); IncompleteSince::::put(now - One::one()); - }: { - Scheduler::::service_agendas(&mut WeightMeter::new(), now, 0); - } verify { + + #[block] + { + Pallet::::service_agendas(&mut WeightMeter::new(), now, 0); + } + assert_eq!(IncompleteSince::::get(), Some(now - One::one())); } // `service_agenda` when no work is done. - service_agenda_base { + #[benchmark] + fn service_agenda_base( + s: Linear<0, { T::MaxScheduledPerBlock::get() }>, + ) -> Result<(), BenchmarkError> { let now = BLOCK_NUMBER.into(); - let s in 0 .. T::MaxScheduledPerBlock::get(); fill_schedule::(now, s)?; let mut executed = 0; - }: { - Scheduler::::service_agenda(&mut WeightMeter::new(), &mut executed, now, now, 0); - } verify { + + #[block] + { + Pallet::::service_agenda(&mut WeightMeter::new(), &mut executed, now, now, 0); + } + assert_eq!(executed, 0); + + Ok(()) } // `service_task` when the task is a non-periodic, non-named, non-fetched call which is not // dispatched (e.g. due to being overweight). - service_task_base { + #[benchmark] + fn service_task_base() { let now = BLOCK_NUMBER.into(); let task = make_task::(false, false, false, None, 0); // prevent any tasks from actually being executed as we only want the surrounding weight. let mut counter = WeightMeter::with_limit(Weight::zero()); - }: { - let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); - } verify { - //assert_eq!(result, Ok(())); + let _result; + + #[block] + { + _result = Pallet::::service_task(&mut counter, now, now, 0, true, task); + } + + // assert!(_result.is_ok()); } // `service_task` when the task is a non-periodic, non-named, fetched call (with a known // preimage length) and which is not dispatched (e.g. due to being overweight). - #[pov_mode = MaxEncodedLen { + #[benchmark(pov_mode = MaxEncodedLen { // Use measured PoV size for the Preimages since we pass in a length witness. Preimage::PreimageFor: Measured - }] - service_task_fetched { - let s in (BoundedInline::bound() as u32) .. (T::Preimages::MAX_LENGTH as u32); + })] + fn service_task_fetched( + s: Linear<{ BoundedInline::bound() as u32 }, { T::Preimages::MAX_LENGTH as u32 }>, + ) { let now = BLOCK_NUMBER.into(); let task = make_task::(false, false, false, Some(s), 0); // prevent any tasks from actually being executed as we only want the surrounding weight. let mut counter = WeightMeter::with_limit(Weight::zero()); - }: { - let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); - } verify { + let _result; + + #[block] + { + _result = Pallet::::service_task(&mut counter, now, now, 0, true, task); + } + + // assert!(result.is_ok()); } // `service_task` when the task is a non-periodic, named, non-fetched call which is not // dispatched (e.g. due to being overweight). - service_task_named { + #[benchmark] + fn service_task_named() { let now = BLOCK_NUMBER.into(); let task = make_task::(false, true, false, None, 0); // prevent any tasks from actually being executed as we only want the surrounding weight. let mut counter = WeightMeter::with_limit(Weight::zero()); - }: { - let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); - } verify { + let _result; + + #[block] + { + _result = Pallet::::service_task(&mut counter, now, now, 0, true, task); + } + + // assert!(result.is_ok()); } // `service_task` when the task is a periodic, non-named, non-fetched call which is not // dispatched (e.g. due to being overweight). - service_task_periodic { + #[benchmark] + fn service_task_periodic() { let now = BLOCK_NUMBER.into(); let task = make_task::(true, false, false, None, 0); // prevent any tasks from actually being executed as we only want the surrounding weight. let mut counter = WeightMeter::with_limit(Weight::zero()); - }: { - let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); - } verify { + let _result; + + #[block] + { + _result = Pallet::::service_task(&mut counter, now, now, 0, true, task); + } + + // assert!(result.is_ok()); } // `execute_dispatch` when the origin is `Signed`, not counting the dispatchable's weight. - execute_dispatch_signed { + #[benchmark] + fn execute_dispatch_signed() -> Result<(), BenchmarkError> { let mut counter = WeightMeter::new(); let origin = make_origin::(true); - let call = T::Preimages::realize(&make_call::(None)).unwrap().0; - }: { - assert!(Scheduler::::execute_dispatch(&mut counter, origin, call).is_ok()); - } - verify { + let call = T::Preimages::realize(&make_call::(None))?.0; + let result; + + #[block] + { + result = Pallet::::execute_dispatch(&mut counter, origin, call); + } + + assert!(result.is_ok()); + + Ok(()) } // `execute_dispatch` when the origin is not `Signed`, not counting the dispatchable's weight. - execute_dispatch_unsigned { + #[benchmark] + fn execute_dispatch_unsigned() -> Result<(), BenchmarkError> { let mut counter = WeightMeter::new(); let origin = make_origin::(false); - let call = T::Preimages::realize(&make_call::(None)).unwrap().0; - }: { - assert!(Scheduler::::execute_dispatch(&mut counter, origin, call).is_ok()); - } - verify { + let call = T::Preimages::realize(&make_call::(None))?.0; + let result; + + #[block] + { + result = Pallet::::execute_dispatch(&mut counter, origin, call); + } + + assert!(result.is_ok()); + + Ok(()) } - schedule { - let s in 0 .. (T::MaxScheduledPerBlock::get() - 1); + #[benchmark] + fn schedule( + s: Linear<0, { T::MaxScheduledPerBlock::get() - 1 }>, + ) -> Result<(), BenchmarkError> { let when = BLOCK_NUMBER.into(); let periodic = Some((BlockNumberFor::::one(), 100)); let priority = 0; @@ -242,24 +293,27 @@ benchmarks! { let call = Box::new(SystemCall::set_storage { items: vec![] }.into()); fill_schedule::(when, s)?; - }: _(RawOrigin::Root, when, periodic, priority, call) - verify { - ensure!( - Agenda::::get(when).len() == (s + 1) as usize, - "didn't add to schedule" - ); + + #[extrinsic_call] + _(RawOrigin::Root, when, periodic, priority, call); + + ensure!(Agenda::::get(when).len() == s as usize + 1, "didn't add to schedule"); + + Ok(()) } - cancel { - let s in 1 .. T::MaxScheduledPerBlock::get(); + #[benchmark] + fn cancel(s: Linear<1, { T::MaxScheduledPerBlock::get() }>) -> Result<(), BenchmarkError> { let when = BLOCK_NUMBER.into(); fill_schedule::(when, s)?; assert_eq!(Agenda::::get(when).len(), s as usize); let schedule_origin = T::ScheduleOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - }: _>(schedule_origin, when, 0) - verify { + + #[extrinsic_call] + _(schedule_origin as SystemOrigin, when, 0); + ensure!( s == 1 || Lookup::::get(u32_to_name(0)).is_none(), "didn't remove from lookup if more than 1 task scheduled for `when`" @@ -273,10 +327,14 @@ benchmarks! { s > 1 || Agenda::::get(when).len() == 0, "remove from schedule if only 1 task scheduled for `when`" ); + + Ok(()) } - schedule_named { - let s in 0 .. (T::MaxScheduledPerBlock::get() - 1); + #[benchmark] + fn schedule_named( + s: Linear<0, { T::MaxScheduledPerBlock::get() - 1 }>, + ) -> Result<(), BenchmarkError> { let id = u32_to_name(s); let when = BLOCK_NUMBER.into(); let periodic = Some((BlockNumberFor::::one(), 100)); @@ -285,21 +343,26 @@ benchmarks! { let call = Box::new(SystemCall::set_storage { items: vec![] }.into()); fill_schedule::(when, s)?; - }: _(RawOrigin::Root, id, when, periodic, priority, call) - verify { - ensure!( - Agenda::::get(when).len() == (s + 1) as usize, - "didn't add to schedule" - ); + + #[extrinsic_call] + _(RawOrigin::Root, id, when, periodic, priority, call); + + ensure!(Agenda::::get(when).len() == s as usize + 1, "didn't add to schedule"); + + Ok(()) } - cancel_named { - let s in 1 .. T::MaxScheduledPerBlock::get(); + #[benchmark] + fn cancel_named( + s: Linear<1, { T::MaxScheduledPerBlock::get() }>, + ) -> Result<(), BenchmarkError> { let when = BLOCK_NUMBER.into(); fill_schedule::(when, s)?; - }: _(RawOrigin::Root, u32_to_name(0)) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, u32_to_name(0)); + ensure!( s == 1 || Lookup::::get(u32_to_name(0)).is_none(), "didn't remove from lookup if more than 1 task scheduled for `when`" @@ -313,33 +376,49 @@ benchmarks! { s > 1 || Agenda::::get(when).len() == 0, "remove from schedule if only 1 task scheduled for `when`" ); + + Ok(()) } - schedule_retry { - let s in 1 .. T::MaxScheduledPerBlock::get(); + #[benchmark] + fn schedule_retry( + s: Linear<1, { T::MaxScheduledPerBlock::get() }>, + ) -> Result<(), BenchmarkError> { let when = BLOCK_NUMBER.into(); fill_schedule::(when, s)?; let name = u32_to_name(s - 1); let address = Lookup::::get(name).unwrap(); - let period: BlockNumberFor = 1u32.into(); - let root: ::PalletsOrigin = frame_system::RawOrigin::Root.into(); + let period: BlockNumberFor = 1_u32.into(); let retry_config = RetryConfig { total_retries: 10, remaining: 10, period }; Retries::::insert(address, retry_config); let (mut when, index) = address; let task = Agenda::::get(when)[index as usize].clone().unwrap(); let mut weight_counter = WeightMeter::with_limit(T::MaximumWeight::get()); - }: { - Scheduler::::schedule_retry(&mut weight_counter, when, when, index, &task, retry_config); - } verify { + + #[block] + { + Pallet::::schedule_retry( + &mut weight_counter, + when, + when, + index, + &task, + retry_config, + ); + } + when = when + BlockNumberFor::::one(); assert_eq!( Retries::::get((when, 0)), Some(RetryConfig { total_retries: 10, remaining: 9, period }) ); + + Ok(()) } - set_retry { + #[benchmark] + fn set_retry() -> Result<(), BenchmarkError> { let s = T::MaxScheduledPerBlock::get(); let when = BLOCK_NUMBER.into(); @@ -348,8 +427,10 @@ benchmarks! { let address = Lookup::::get(name).unwrap(); let (when, index) = address; let period = BlockNumberFor::::one(); - }: _(RawOrigin::Root, (when, index), 10, period) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, (when, index), 10, period); + assert_eq!( Retries::::get((when, index)), Some(RetryConfig { total_retries: 10, remaining: 10, period }) @@ -357,9 +438,12 @@ benchmarks! { assert_last_event::( Event::RetrySet { task: address, id: None, period, retries: 10 }.into(), ); + + Ok(()) } - set_retry_named { + #[benchmark] + fn set_retry_named() -> Result<(), BenchmarkError> { let s = T::MaxScheduledPerBlock::get(); let when = BLOCK_NUMBER.into(); @@ -368,8 +452,10 @@ benchmarks! { let address = Lookup::::get(name).unwrap(); let (when, index) = address; let period = BlockNumberFor::::one(); - }: _(RawOrigin::Root, name, 10, period) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, name, 10, period); + assert_eq!( Retries::::get((when, index)), Some(RetryConfig { total_retries: 10, remaining: 10, period }) @@ -377,9 +463,12 @@ benchmarks! { assert_last_event::( Event::RetrySet { task: address, id: Some(name), period, retries: 10 }.into(), ); + + Ok(()) } - cancel_retry { + #[benchmark] + fn cancel_retry() -> Result<(), BenchmarkError> { let s = T::MaxScheduledPerBlock::get(); let when = BLOCK_NUMBER.into(); @@ -388,16 +477,19 @@ benchmarks! { let address = Lookup::::get(name).unwrap(); let (when, index) = address; let period = BlockNumberFor::::one(); - assert!(Scheduler::::set_retry(RawOrigin::Root.into(), (when, index), 10, period).is_ok()); - }: _(RawOrigin::Root, (when, index)) - verify { + assert!(Pallet::::set_retry(RawOrigin::Root.into(), (when, index), 10, period).is_ok()); + + #[extrinsic_call] + _(RawOrigin::Root, (when, index)); + assert!(!Retries::::contains_key((when, index))); - assert_last_event::( - Event::RetryCancelled { task: address, id: None }.into(), - ); + assert_last_event::(Event::RetryCancelled { task: address, id: None }.into()); + + Ok(()) } - cancel_retry_named { + #[benchmark] + fn cancel_retry_named() -> Result<(), BenchmarkError> { let s = T::MaxScheduledPerBlock::get(); let when = BLOCK_NUMBER.into(); @@ -406,14 +498,20 @@ benchmarks! { let address = Lookup::::get(name).unwrap(); let (when, index) = address; let period = BlockNumberFor::::one(); - assert!(Scheduler::::set_retry_named(RawOrigin::Root.into(), name, 10, period).is_ok()); - }: _(RawOrigin::Root, name) - verify { + assert!(Pallet::::set_retry_named(RawOrigin::Root.into(), name, 10, period).is_ok()); + + #[extrinsic_call] + _(RawOrigin::Root, name); + assert!(!Retries::::contains_key((when, index))); - assert_last_event::( - Event::RetryCancelled { task: address, id: Some(name) }.into(), - ); + assert_last_event::(Event::RetryCancelled { task: address, id: Some(name) }.into()); + + Ok(()) } - impl_benchmark_test_suite!(Scheduler, crate::mock::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite! { + Pallet, + mock::new_test_ext(), + mock::Test + } } From a8722784fb36e13c811605bd5631d78643273e24 Mon Sep 17 00:00:00 2001 From: gupnik Date: Thu, 21 Nov 2024 09:53:55 +0530 Subject: [PATCH 006/340] Removes constraint in `BlockNumberProvider` from treasury (#6522) https://github.com/paritytech/polkadot-sdk/pull/3970 updated the treasury pallet to support relay chain block number provider. However, it added a constraint to the BlockNumberProvider to have the same block number type as frame_system: ```rust type BlockNumberProvider: BlockNumberProvider>; ``` This PR removes that constraint as suggested by @gui1117 --- prdoc/pr_6522.prdoc | 18 +++++++++ substrate/frame/bounties/src/benchmarking.rs | 6 +-- substrate/frame/bounties/src/lib.rs | 19 +++++---- .../frame/child-bounties/src/benchmarking.rs | 2 +- substrate/frame/child-bounties/src/lib.rs | 8 +++- substrate/frame/treasury/src/benchmarking.rs | 2 +- substrate/frame/treasury/src/lib.rs | 40 ++++++++++--------- .../primitives/runtime/src/traits/mod.rs | 3 +- 8 files changed, 64 insertions(+), 34 deletions(-) create mode 100644 prdoc/pr_6522.prdoc diff --git a/prdoc/pr_6522.prdoc b/prdoc/pr_6522.prdoc new file mode 100644 index 000000000000..bd59e9cb08dc --- /dev/null +++ b/prdoc/pr_6522.prdoc @@ -0,0 +1,18 @@ +title: Removes constraint in BlockNumberProvider from treasury + +doc: +- audience: Runtime Dev + description: |- + https://github.com/paritytech/polkadot-sdk/pull/3970 updated the treasury pallet to support + relay chain block number provider. However, it added a constraint to the `BlockNumberProvider` + trait to have the same block number type as `frame_system`: + + ```rust + type BlockNumberProvider: BlockNumberProvider>; + ``` + + This PR removes that constraint and allows the treasury pallet to use any block number type. + +crates: +- name: pallet-treasury + bump: major \ No newline at end of file diff --git a/substrate/frame/bounties/src/benchmarking.rs b/substrate/frame/bounties/src/benchmarking.rs index 8ad85d5420ed..1e931958898d 100644 --- a/substrate/frame/bounties/src/benchmarking.rs +++ b/substrate/frame/bounties/src/benchmarking.rs @@ -25,7 +25,7 @@ use alloc::{vec, vec::Vec}; use frame_benchmarking::v1::{ account, benchmarks_instance_pallet, whitelisted_caller, BenchmarkError, }; -use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; +use frame_system::{pallet_prelude::BlockNumberFor as SystemBlockNumberFor, RawOrigin}; use sp_runtime::traits::{BlockNumberProvider, Bounded}; use crate::Pallet as Bounties; @@ -33,7 +33,7 @@ use pallet_treasury::Pallet as Treasury; const SEED: u32 = 0; -fn set_block_number, I: 'static>(n: BlockNumberFor) { +fn set_block_number, I: 'static>(n: BlockNumberFor) { >::BlockNumberProvider::set_block_number(n); } @@ -132,7 +132,7 @@ benchmarks_instance_pallet! { Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; let bounty_id = BountyCount::::get() - 1; let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - Treasury::::on_initialize(BlockNumberFor::::zero()); + Treasury::::on_initialize(SystemBlockNumberFor::::zero()); }: _(approve_origin, bounty_id, curator_lookup, fee) verify { assert_last_event::( diff --git a/substrate/frame/bounties/src/lib.rs b/substrate/frame/bounties/src/lib.rs index 3ed408a19120..729c76b5cc75 100644 --- a/substrate/frame/bounties/src/lib.rs +++ b/substrate/frame/bounties/src/lib.rs @@ -105,7 +105,9 @@ use sp_runtime::{ use frame_support::{dispatch::DispatchResultWithPostInfo, traits::EnsureOrigin}; use frame_support::pallet_prelude::*; -use frame_system::pallet_prelude::*; +use frame_system::pallet_prelude::{ + ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor, +}; use scale_info::TypeInfo; pub use weights::WeightInfo; @@ -120,6 +122,9 @@ pub type BountyIndex = u32; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +type BlockNumberFor = + <>::BlockNumberProvider as BlockNumberProvider>::BlockNumber; + /// A bounty proposal. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct Bounty { @@ -213,11 +218,11 @@ pub mod pallet { /// The delay period for which a bounty beneficiary need to wait before claim the payout. #[pallet::constant] - type BountyDepositPayoutDelay: Get>; + type BountyDepositPayoutDelay: Get>; /// Bounty duration in blocks. #[pallet::constant] - type BountyUpdatePeriod: Get>; + type BountyUpdatePeriod: Get>; /// The curator deposit is calculated as a percentage of the curator fee. /// @@ -326,7 +331,7 @@ pub mod pallet { _, Twox64Concat, BountyIndex, - Bounty, BlockNumberFor>, + Bounty, BlockNumberFor>, >; /// The description of each bounty. @@ -876,9 +881,9 @@ pub mod pallet { } #[pallet::hooks] - impl, I: 'static> Hooks> for Pallet { + impl, I: 'static> Hooks> for Pallet { #[cfg(feature = "try-runtime")] - fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + fn try_state(_n: SystemBlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { Self::do_try_state() } } @@ -928,7 +933,7 @@ impl, I: 'static> Pallet { /// Get the block number used in the treasury pallet. /// /// It may be configured to use the relay chain block number on a parachain. - pub fn treasury_block_number() -> BlockNumberFor { + pub fn treasury_block_number() -> BlockNumberFor { >::BlockNumberProvider::current_block_number() } diff --git a/substrate/frame/child-bounties/src/benchmarking.rs b/substrate/frame/child-bounties/src/benchmarking.rs index 4b2d62cd920e..2864f3ab5048 100644 --- a/substrate/frame/child-bounties/src/benchmarking.rs +++ b/substrate/frame/child-bounties/src/benchmarking.rs @@ -22,7 +22,7 @@ use alloc::vec; use frame_benchmarking::{v2::*, BenchmarkError}; use frame_support::ensure; -use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; +use frame_system::RawOrigin; use pallet_bounties::Pallet as Bounties; use pallet_treasury::Pallet as Treasury; use sp_runtime::traits::BlockNumberProvider; diff --git a/substrate/frame/child-bounties/src/lib.rs b/substrate/frame/child-bounties/src/lib.rs index ea1d9547d465..9fca26510989 100644 --- a/substrate/frame/child-bounties/src/lib.rs +++ b/substrate/frame/child-bounties/src/lib.rs @@ -79,7 +79,9 @@ use sp_runtime::{ }; use frame_support::pallet_prelude::*; -use frame_system::pallet_prelude::*; +use frame_system::pallet_prelude::{ + ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor, +}; use pallet_bounties::BountyStatus; use scale_info::TypeInfo; pub use weights::WeightInfo; @@ -90,6 +92,8 @@ type BalanceOf = pallet_treasury::BalanceOf; type BountiesError = pallet_bounties::Error; type BountyIndex = pallet_bounties::BountyIndex; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +type BlockNumberFor = + <::BlockNumberProvider as BlockNumberProvider>::BlockNumber; /// A child bounty proposal. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] @@ -810,7 +814,7 @@ pub mod pallet { } #[pallet::hooks] - impl Hooks> for Pallet { + impl Hooks> for Pallet { fn integrity_test() { let parent_bounty_id: BountyIndex = 1; let child_bounty_id: BountyIndex = 2; diff --git a/substrate/frame/treasury/src/benchmarking.rs b/substrate/frame/treasury/src/benchmarking.rs index a03ee149db9b..a11723a27b2c 100644 --- a/substrate/frame/treasury/src/benchmarking.rs +++ b/substrate/frame/treasury/src/benchmarking.rs @@ -198,7 +198,7 @@ mod benchmarks { None, ); - let valid_from = frame_system::Pallet::::block_number(); + let valid_from = T::BlockNumberProvider::current_block_number(); let expire_at = valid_from.saturating_add(T::PayoutPeriod::get()); assert_last_event::( Event::AssetSpendApproved { diff --git a/substrate/frame/treasury/src/lib.rs b/substrate/frame/treasury/src/lib.rs index faacda1c0783..281012ffb4c9 100644 --- a/substrate/frame/treasury/src/lib.rs +++ b/substrate/frame/treasury/src/lib.rs @@ -106,7 +106,7 @@ use frame_support::{ weights::Weight, BoundedVec, PalletId, }; -use frame_system::pallet_prelude::BlockNumberFor; +use frame_system::pallet_prelude::BlockNumberFor as SystemBlockNumberFor; pub use pallet::*; pub use weights::WeightInfo; @@ -122,6 +122,8 @@ pub type NegativeImbalanceOf = <>::Currency as Currenc >>::NegativeImbalance; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; type BeneficiaryLookupOf = <>::BeneficiaryLookup as StaticLookup>::Source; +pub type BlockNumberFor = + <>::BlockNumberProvider as BlockNumberProvider>::BlockNumber; /// A trait to allow the Treasury Pallet to spend it's funds for other purposes. /// There is an expectation that the implementer of this trait will correctly manage @@ -202,7 +204,7 @@ pub mod pallet { pallet_prelude::*, traits::tokens::{ConversionFromAssetBalance, PaymentStatus}, }; - use frame_system::pallet_prelude::*; + use frame_system::pallet_prelude::{ensure_signed, OriginFor}; #[pallet::pallet] pub struct Pallet(PhantomData<(T, I)>); @@ -221,7 +223,7 @@ pub mod pallet { /// Period between successive spends. #[pallet::constant] - type SpendPeriod: Get>; + type SpendPeriod: Get>; /// Percentage of spare funds (if any) that are burnt per spend period. #[pallet::constant] @@ -277,14 +279,14 @@ pub mod pallet { /// The period during which an approved treasury spend has to be claimed. #[pallet::constant] - type PayoutPeriod: Get>; + type PayoutPeriod: Get>; /// Helper type for benchmarks. #[cfg(feature = "runtime-benchmarks")] type BenchmarkHelper: ArgumentsFactory; /// Provider for the block number. Normally this is the `frame_system` pallet. - type BlockNumberProvider: BlockNumberProvider>; + type BlockNumberProvider: BlockNumberProvider; } /// DEPRECATED: associated with `spend_local` call and will be removed in May 2025. @@ -335,7 +337,7 @@ pub mod pallet { T::AssetKind, AssetBalanceOf, T::Beneficiary, - BlockNumberFor, + BlockNumberFor, ::Id, >, OptionQuery, @@ -343,7 +345,7 @@ pub mod pallet { /// The blocknumber for the last triggered spend period. #[pallet::storage] - pub(crate) type LastSpendPeriod = StorageValue<_, BlockNumberFor, OptionQuery>; + pub(crate) type LastSpendPeriod = StorageValue<_, BlockNumberFor, OptionQuery>; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] @@ -391,8 +393,8 @@ pub mod pallet { asset_kind: T::AssetKind, amount: AssetBalanceOf, beneficiary: T::Beneficiary, - valid_from: BlockNumberFor, - expire_at: BlockNumberFor, + valid_from: BlockNumberFor, + expire_at: BlockNumberFor, }, /// An approved spend was voided. AssetSpendVoided { index: SpendIndex }, @@ -434,10 +436,10 @@ pub mod pallet { } #[pallet::hooks] - impl, I: 'static> Hooks> for Pallet { + impl, I: 'static> Hooks> for Pallet { /// ## Complexity /// - `O(A)` where `A` is the number of approvals - fn on_initialize(_do_not_use_local_block_number: BlockNumberFor) -> Weight { + fn on_initialize(_do_not_use_local_block_number: SystemBlockNumberFor) -> Weight { let block_number = T::BlockNumberProvider::current_block_number(); let pot = Self::pot(); let deactivated = Deactivated::::get(); @@ -458,7 +460,7 @@ pub mod pallet { // empty. .unwrap_or_else(|| Self::update_last_spend_period()); let blocks_since_last_spend_period = block_number.saturating_sub(last_spend_period); - let safe_spend_period = T::SpendPeriod::get().max(BlockNumberFor::::one()); + let safe_spend_period = T::SpendPeriod::get().max(BlockNumberFor::::one()); // Safe because of `max(1)` above. let (spend_periods_passed, extra_blocks) = ( @@ -466,7 +468,7 @@ pub mod pallet { blocks_since_last_spend_period % safe_spend_period, ); let new_last_spend_period = block_number.saturating_sub(extra_blocks); - if spend_periods_passed > BlockNumberFor::::zero() { + if spend_periods_passed > BlockNumberFor::::zero() { Self::spend_funds(spend_periods_passed, new_last_spend_period) } else { Weight::zero() @@ -474,7 +476,7 @@ pub mod pallet { } #[cfg(feature = "try-runtime")] - fn try_state(_: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + fn try_state(_: SystemBlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { Self::do_try_state()?; Ok(()) } @@ -638,7 +640,7 @@ pub mod pallet { asset_kind: Box, #[pallet::compact] amount: AssetBalanceOf, beneficiary: Box>, - valid_from: Option>, + valid_from: Option>, ) -> DispatchResult { let max_amount = T::SpendOrigin::ensure_origin(origin)?; let beneficiary = T::BeneficiaryLookup::lookup(*beneficiary)?; @@ -844,9 +846,9 @@ impl, I: 'static> Pallet { // Backfill the `LastSpendPeriod` storage, assuming that no configuration has changed // since introducing this code. Used specifically for a migration-less switch to populate // `LastSpendPeriod`. - fn update_last_spend_period() -> BlockNumberFor { + fn update_last_spend_period() -> BlockNumberFor { let block_number = T::BlockNumberProvider::current_block_number(); - let spend_period = T::SpendPeriod::get().max(BlockNumberFor::::one()); + let spend_period = T::SpendPeriod::get().max(BlockNumberFor::::one()); let time_since_last_spend = block_number % spend_period; // If it happens that this logic runs directly on a spend period block, we need to backdate // to the last spend period so a spend still occurs this block. @@ -889,8 +891,8 @@ impl, I: 'static> Pallet { /// Spend some money! returns number of approvals before spend. pub fn spend_funds( - spend_periods_passed: BlockNumberFor, - new_last_spend_period: BlockNumberFor, + spend_periods_passed: BlockNumberFor, + new_last_spend_period: BlockNumberFor, ) -> Weight { LastSpendPeriod::::put(new_last_spend_period); let mut total_weight = Weight::zero(); diff --git a/substrate/primitives/runtime/src/traits/mod.rs b/substrate/primitives/runtime/src/traits/mod.rs index 01bdcca86b6f..02bc7adc8ba5 100644 --- a/substrate/primitives/runtime/src/traits/mod.rs +++ b/substrate/primitives/runtime/src/traits/mod.rs @@ -2349,7 +2349,8 @@ pub trait BlockNumberProvider { + TypeInfo + Debug + MaxEncodedLen - + Copy; + + Copy + + EncodeLike; /// Returns the current block number. /// From 6f238eb925a3d637b30483ab69bbff168922c683 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 21 Nov 2024 08:58:41 +0200 Subject: [PATCH 007/340] adds sparse bitmap --- Cargo.lock | 53 ++++++ .../pallets/inbound-queue-v2/src/lib.rs | 2 +- bridges/snowbridge/primitives/core/src/lib.rs | 1 + .../primitives/core/src/sparse_bitmap.rs | 157 ++++++++++++++++++ 4 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 bridges/snowbridge/primitives/core/src/sparse_bitmap.rs diff --git a/Cargo.lock b/Cargo.lock index 330c2563d976..6d58f682ae76 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24785,6 +24785,16 @@ dependencies = [ "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "snowbridge-inbound-queue-v2-runtime-api" +version = "0.2.0" +dependencies = [ + "snowbridge-core 0.2.0", + "snowbridge-router-primitives 0.9.0", + "sp-api 26.0.0", + "staging-xcm 7.0.0", +] + [[package]] name = "snowbridge-milagro-bls" version = "1.5.4" @@ -25010,6 +25020,47 @@ dependencies = [ "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "snowbridge-pallet-inbound-queue-fixtures-v2" +version = "0.10.0" +dependencies = [ + "hex-literal", + "snowbridge-beacon-primitives 0.2.0", + "snowbridge-core 0.2.0", + "sp-core 28.0.0", + "sp-std 14.0.0", +] + +[[package]] +name = "snowbridge-pallet-inbound-queue-v2" +version = "0.2.0" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "hex", + "hex-literal", + "log", + "pallet-balances 28.0.0", + "parity-scale-codec", + "scale-info", + "serde", + "snowbridge-beacon-primitives 0.2.0", + "snowbridge-core 0.2.0", + "snowbridge-pallet-ethereum-client 0.2.0", + "snowbridge-pallet-inbound-queue-fixtures-v2", + "snowbridge-router-primitives 0.9.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-keyring 31.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0", + "staging-xcm 7.0.0", + "staging-xcm-executor 7.0.0", +] + [[package]] name = "snowbridge-pallet-outbound-queue" version = "0.2.0" @@ -25108,6 +25159,7 @@ name = "snowbridge-router-primitives" version = "0.9.0" dependencies = [ "frame-support 28.0.0", + "frame-system 28.0.0", "hex-literal", "log", "parity-scale-codec", @@ -25118,6 +25170,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", "staging-xcm-executor 7.0.0", ] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 91c0acaa97a1..16d312809ede 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -168,7 +168,7 @@ pub mod pallet { /// The nonce of the message been processed or not #[pallet::storage] - pub type Nonce = StorageMap<_, Identity, u64, bool, ValueQuery>; + pub type Nonce = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; /// The current operating mode of the pallet. #[pallet::storage] diff --git a/bridges/snowbridge/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs index 7ad129a52542..d88e387d7c24 100644 --- a/bridges/snowbridge/primitives/core/src/lib.rs +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -14,6 +14,7 @@ pub mod operating_mode; pub mod outbound; pub mod pricing; pub mod ringbuffer; +pub mod sparse_bitmap; pub use location::{AgentId, AgentIdOf, TokenId, TokenIdOf}; pub use polkadot_parachain_primitives::primitives::{ diff --git a/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs b/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs new file mode 100644 index 000000000000..894f159ef64e --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs @@ -0,0 +1,157 @@ +use frame_support::storage::StorageMap; +use sp_std::marker::PhantomData; + +pub trait SparseBitmap +where + BitMap: StorageMap, +{ + fn get(index: u128) -> bool; + fn set(index: u128); +} + +pub struct SparseBitmapImpl(PhantomData); + +impl SparseBitmap for SparseBitmapImpl +where + BitMap: StorageMap, +{ + fn get(index: u128) -> bool { + // Calculate bucket and mask + let bucket = index >> 7; // Divide by 2^7 (128 bits) + let mask = 1u128 << (index & 127); // Mask for the bit in the bucket + + // Retrieve bucket and check bit + let bucket_value = BitMap::get(bucket); + bucket_value & mask != 0 + } + + fn set(index: u128) { + // Calculate bucket and mask + let bucket = index >> 7; // Divide by 2^7 (128 bits) + let mask = 1u128 << (index & 127); // Mask for the bit in the bucket + + // Mutate the storage to set the bit + BitMap::mutate(bucket, |value| { + *value |= mask; // Set the bit in the bucket + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::{ + storage::{generator::StorageMap as StorageMapHelper, storage_prefix}, + Twox64Concat, + }; + use sp_io::TestExternalities; + pub struct MockStorageMap; + + impl StorageMapHelper for MockStorageMap { + type Query = u128; + type Hasher = Twox64Concat; + fn pallet_prefix() -> &'static [u8] { + b"MyModule" + } + + fn storage_prefix() -> &'static [u8] { + b"MyStorageMap" + } + + fn prefix_hash() -> [u8; 32] { + storage_prefix(Self::pallet_prefix(), Self::storage_prefix()) + } + + fn from_optional_value_to_query(v: Option) -> Self::Query { + v.unwrap_or_default() + } + + fn from_query_to_optional_value(v: Self::Query) -> Option { + Some(v) + } + } + + type TestSparseBitmap = SparseBitmapImpl; + + #[test] + fn test_sparse_bitmap_set_and_get() { + TestExternalities::default().execute_with(|| { + let index = 300; + let bucket = index >> 7; + let mask = 1u128 << (index & 127); + + // Test initial state + assert_eq!(MockStorageMap::get(bucket), 0); + assert!(!TestSparseBitmap::get(index)); + + // Set the bit + TestSparseBitmap::set(index); + + // Test after setting + assert_eq!(MockStorageMap::get(bucket), mask); + assert!(TestSparseBitmap::get(index)); + }); + } + + #[test] + fn test_sparse_bitmap_multiple_sets() { + TestExternalities::default().execute_with(|| { + let index1 = 300; + let index2 = 305; // Same bucket, different bit + let bucket = index1 >> 7; + + let mask1 = 1u128 << (index1 & 127); + let mask2 = 1u128 << (index2 & 127); + + // Test initial state + assert_eq!(MockStorageMap::get(bucket), 0); + assert!(!TestSparseBitmap::get(index1)); + assert!(!TestSparseBitmap::get(index2)); + + // Set the first bit + TestSparseBitmap::set(index1); + + // Test after first set + assert_eq!(MockStorageMap::get(bucket), mask1); + assert!(TestSparseBitmap::get(index1)); + assert!(!TestSparseBitmap::get(index2)); + + // Set the second bit + TestSparseBitmap::set(index2); + + // Test after second set + assert_eq!(MockStorageMap::get(bucket), mask1 | mask2); // Bucket should contain both masks + assert!(TestSparseBitmap::get(index1)); + assert!(TestSparseBitmap::get(index2)); + }) + } + + #[test] + fn test_sparse_bitmap_different_buckets() { + TestExternalities::default().execute_with(|| { + let index1 = 300; // Bucket 1 + let index2 = 300 + (1 << 7); // Bucket 2 (128 bits apart) + + let bucket1 = index1 >> 7; + let bucket2 = index2 >> 7; + + let mask1 = 1u128 << (index1 & 127); + let mask2 = 1u128 << (index2 & 127); + + // Test initial state + assert_eq!(MockStorageMap::get(bucket1), 0); + assert_eq!(MockStorageMap::get(bucket2), 0); + + // Set bits in different buckets + TestSparseBitmap::set(index1); + TestSparseBitmap::set(index2); + + // Test after setting + assert_eq!(MockStorageMap::get(bucket1), mask1); // Bucket 1 should contain mask1 + assert_eq!(MockStorageMap::get(bucket2), mask2); // Bucket 2 should contain mask2 + + assert!(TestSparseBitmap::get(index1)); + assert!(TestSparseBitmap::get(index2)); + }) + } +} From b0115cdcec5b48282416889cfa5320138044d1fa Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 21 Nov 2024 09:19:45 +0200 Subject: [PATCH 008/340] use sparse bitmap --- .../pallets/inbound-queue-v2/src/api.rs | 8 +- .../inbound-queue-v2/src/benchmarking.rs | 32 +- .../pallets/inbound-queue-v2/src/envelope.rs | 40 +- .../pallets/inbound-queue-v2/src/lib.rs | 355 +++++++++--------- .../pallets/inbound-queue-v2/src/mock.rs | 190 +++++----- .../pallets/inbound-queue-v2/src/test.rs | 260 ++++++------- .../pallets/inbound-queue-v2/src/types.rs | 5 + .../primitives/core/src/sparse_bitmap.rs | 3 + 8 files changed, 452 insertions(+), 441 deletions(-) create mode 100644 bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index 47207df7383c..a285a7c5af42 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -8,9 +8,9 @@ use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; use xcm::latest::Xcm; pub fn dry_run(message: Message, _proof: Proof) -> Result, Error> - where - T: Config, +where + T: Config, { - let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; - Ok(xcm) + let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + Ok(xcm) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs index b6d2a9739f3d..4c5df07b27ac 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/benchmarking.rs @@ -10,29 +10,29 @@ use snowbridge_pallet_inbound_queue_fixtures_v2::register_token::make_register_t #[benchmarks] mod benchmarks { - use super::*; + use super::*; - #[benchmark] - fn submit() -> Result<(), BenchmarkError> { - let caller: T::AccountId = whitelisted_caller(); + #[benchmark] + fn submit() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); - let create_message = make_register_token_message(); + let create_message = make_register_token_message(); - T::Helper::initialize_storage( - create_message.finalized_header, - create_message.block_roots_root, - ); + T::Helper::initialize_storage( + create_message.finalized_header, + create_message.block_roots_root, + ); - #[block] - { - assert_ok!(InboundQueue::::submit( + #[block] + { + assert_ok!(InboundQueue::::submit( RawOrigin::Signed(caller.clone()).into(), create_message.message, )); - } + } - Ok(()) - } + Ok(()) + } - impl_benchmark_test_suite!(InboundQueue, crate::mock::new_tester(), crate::mock::Test); + impl_benchmark_test_suite!(InboundQueue, crate::mock::new_tester(), crate::mock::Test); } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs index 41353954e5b2..8c9b137c64ba 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs @@ -15,33 +15,33 @@ sol! { /// An inbound message that has had its outer envelope decoded. #[derive(Clone, RuntimeDebug)] pub struct Envelope { - /// The address of the outbound queue on Ethereum that emitted this message as an event log - pub gateway: H160, - /// A nonce for enforcing replay protection and ordering. - pub nonce: u64, - /// Total fee paid in Ether on Ethereum, should cover all the cost - pub fee: u128, - /// The inner payload generated from the source application. - pub payload: Vec, + /// The address of the outbound queue on Ethereum that emitted this message as an event log + pub gateway: H160, + /// A nonce for enforcing replay protection and ordering. + pub nonce: u64, + /// Total fee paid in Ether on Ethereum, should cover all the cost + pub fee: u128, + /// The inner payload generated from the source application. + pub payload: Vec, } #[derive(Copy, Clone, RuntimeDebug)] pub struct EnvelopeDecodeError; impl TryFrom<&Log> for Envelope { - type Error = EnvelopeDecodeError; + type Error = EnvelopeDecodeError; - fn try_from(log: &Log) -> Result { - let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); + fn try_from(log: &Log) -> Result { + let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); - let event = OutboundMessageAccepted::decode_log(topics, &log.data, true) - .map_err(|_| EnvelopeDecodeError)?; + let event = OutboundMessageAccepted::decode_log(topics, &log.data, true) + .map_err(|_| EnvelopeDecodeError)?; - Ok(Self { - gateway: log.address, - nonce: event.nonce, - fee: event.fee, - payload: event.payload, - }) - } + Ok(Self { + gateway: log.address, + nonce: event.nonce, + fee: event.fee, + payload: event.payload, + }) + } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 16d312809ede..fddfc4e6df56 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -27,6 +27,7 @@ mod envelope; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +mod types; pub mod weights; @@ -43,11 +44,12 @@ use frame_system::ensure_signed; use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; +use types::Nonce; use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}; use snowbridge_core::{ - inbound::{Message, VerificationError, Verifier}, - BasicOperatingMode, + inbound::{Message, VerificationError, Verifier}, + BasicOperatingMode, }; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; pub use weights::WeightInfo; @@ -55,6 +57,7 @@ pub use weights::WeightInfo; #[cfg(feature = "runtime-benchmarks")] use snowbridge_beacon_primitives::BeaconHeader; +use snowbridge_core::sparse_bitmap::SparseBitmap; use snowbridge_router_primitives::inbound::v2::ConvertMessageError; pub use pallet::*; @@ -63,178 +66,178 @@ pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; #[frame_support::pallet] pub mod pallet { - use super::*; - - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - - #[pallet::pallet] - pub struct Pallet(_); - - #[cfg(feature = "runtime-benchmarks")] - pub trait BenchmarkHelper { - fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256); - } - - #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// The verifier for inbound messages from Ethereum - type Verifier: Verifier; - - /// XCM message sender - type XcmSender: SendXcm; - /// Address of the Gateway contract - #[pallet::constant] - type GatewayAddress: Get; - type WeightInfo: WeightInfo; - /// AssetHub parachain ID - type AssetHubParaId: Get; - type MessageConverter: ConvertMessage; - #[cfg(feature = "runtime-benchmarks")] - type Helper: BenchmarkHelper; - } - - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A message was received from Ethereum - MessageReceived { - /// The message nonce - nonce: u64, - /// ID of the XCM message which was forwarded to the final destination parachain - message_id: [u8; 32], - }, - /// Set OperatingMode - OperatingModeChanged { mode: BasicOperatingMode }, - } - - #[pallet::error] - pub enum Error { - /// Message came from an invalid outbound channel on the Ethereum side. - InvalidGateway, - /// Message has an invalid envelope. - InvalidEnvelope, - /// Message has an unexpected nonce. - InvalidNonce, - /// Message has an invalid payload. - InvalidPayload, - /// Message channel is invalid - InvalidChannel, - /// The max nonce for the type has been reached - MaxNonceReached, - /// Cannot convert location - InvalidAccountConversion, - /// Pallet is halted - Halted, - /// Message verification error, - Verification(VerificationError), - /// XCMP send failure - Send(SendError), - /// Message conversion error - ConvertMessage(ConvertMessageError), - } - - #[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo, PalletError)] - pub enum SendError { - NotApplicable, - NotRoutable, - Transport, - DestinationUnsupported, - ExceedsMaxMessageSize, - MissingArgument, - Fees, - } - - impl From for Error { - fn from(e: XcmpSendError) -> Self { - match e { - XcmpSendError::NotApplicable => Error::::Send(SendError::NotApplicable), - XcmpSendError::Unroutable => Error::::Send(SendError::NotRoutable), - XcmpSendError::Transport(_) => Error::::Send(SendError::Transport), - XcmpSendError::DestinationUnsupported => - Error::::Send(SendError::DestinationUnsupported), - XcmpSendError::ExceedsMaxMessageSize => - Error::::Send(SendError::ExceedsMaxMessageSize), - XcmpSendError::MissingArgument => Error::::Send(SendError::MissingArgument), - XcmpSendError::Fees => Error::::Send(SendError::Fees), - } - } - } - - /// The nonce of the message been processed or not - #[pallet::storage] - pub type Nonce = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; - - /// The current operating mode of the pallet. - #[pallet::storage] - #[pallet::getter(fn operating_mode)] - pub type OperatingMode = StorageValue<_, BasicOperatingMode, ValueQuery>; - - #[pallet::call] - impl Pallet { - /// Submit an inbound message originating from the Gateway contract on Ethereum - #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::submit())] - pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { - let _who = ensure_signed(origin)?; - ensure!(!Self::operating_mode().is_halted(), Error::::Halted); - - // submit message to verifier for verification - T::Verifier::verify(&message.event_log, &message.proof) - .map_err(|e| Error::::Verification(e))?; - - // Decode event log into an Envelope - let envelope = - Envelope::try_from(&message.event_log).map_err(|_| Error::::InvalidEnvelope)?; - - // Verify that the message was submitted from the known Gateway contract - ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); - - // Verify the message has not been processed - ensure!(!Nonce::::contains_key(envelope.nonce), Error::::InvalidNonce); - - // Decode payload into `MessageV2` - let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) - .map_err(|_| Error::::InvalidPayload)?; - - let xcm = - T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; - - // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: - // T::RewardLeger::deposit(who, envelope.fee.into())?; - // a. The submit extrinsic cost on BH - // b. The delivery cost to AH - // c. The execution cost on AH - // d. The execution cost on destination chain(if any) - // e. The reward - - // Attempt to forward XCM to AH - let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]); - let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; - Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); - - // Set nonce flag to true - Nonce::::insert(envelope.nonce, ()); - - Ok(()) - } - - /// Halt or resume all pallet operations. May only be called by root. - #[pallet::call_index(1)] - #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] - pub fn set_operating_mode( - origin: OriginFor, - mode: BasicOperatingMode, - ) -> DispatchResult { - ensure_root(origin)?; - OperatingMode::::set(mode); - Self::deposit_event(Event::OperatingModeChanged { mode }); - Ok(()) - } - } + use super::*; + + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256); + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The verifier for inbound messages from Ethereum + type Verifier: Verifier; + + /// XCM message sender + type XcmSender: SendXcm; + /// Address of the Gateway contract + #[pallet::constant] + type GatewayAddress: Get; + type WeightInfo: WeightInfo; + /// AssetHub parachain ID + type AssetHubParaId: Get; + type MessageConverter: ConvertMessage; + #[cfg(feature = "runtime-benchmarks")] + type Helper: BenchmarkHelper; + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A message was received from Ethereum + MessageReceived { + /// The message nonce + nonce: u64, + /// ID of the XCM message which was forwarded to the final destination parachain + message_id: [u8; 32], + }, + /// Set OperatingMode + OperatingModeChanged { mode: BasicOperatingMode }, + } + + #[pallet::error] + pub enum Error { + /// Message came from an invalid outbound channel on the Ethereum side. + InvalidGateway, + /// Message has an invalid envelope. + InvalidEnvelope, + /// Message has an unexpected nonce. + InvalidNonce, + /// Message has an invalid payload. + InvalidPayload, + /// Message channel is invalid + InvalidChannel, + /// The max nonce for the type has been reached + MaxNonceReached, + /// Cannot convert location + InvalidAccountConversion, + /// Pallet is halted + Halted, + /// Message verification error, + Verification(VerificationError), + /// XCMP send failure + Send(SendError), + /// Message conversion error + ConvertMessage(ConvertMessageError), + } + + #[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo, PalletError)] + pub enum SendError { + NotApplicable, + NotRoutable, + Transport, + DestinationUnsupported, + ExceedsMaxMessageSize, + MissingArgument, + Fees, + } + + impl From for Error { + fn from(e: XcmpSendError) -> Self { + match e { + XcmpSendError::NotApplicable => Error::::Send(SendError::NotApplicable), + XcmpSendError::Unroutable => Error::::Send(SendError::NotRoutable), + XcmpSendError::Transport(_) => Error::::Send(SendError::Transport), + XcmpSendError::DestinationUnsupported => + Error::::Send(SendError::DestinationUnsupported), + XcmpSendError::ExceedsMaxMessageSize => + Error::::Send(SendError::ExceedsMaxMessageSize), + XcmpSendError::MissingArgument => Error::::Send(SendError::MissingArgument), + XcmpSendError::Fees => Error::::Send(SendError::Fees), + } + } + } + + /// The nonce of the message been processed or not + #[pallet::storage] + pub type NoncesBitmap = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; + + /// The current operating mode of the pallet. + #[pallet::storage] + #[pallet::getter(fn operating_mode)] + pub type OperatingMode = StorageValue<_, BasicOperatingMode, ValueQuery>; + + #[pallet::call] + impl Pallet { + /// Submit an inbound message originating from the Gateway contract on Ethereum + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::submit())] + pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { + let _who = ensure_signed(origin)?; + ensure!(!Self::operating_mode().is_halted(), Error::::Halted); + + // submit message to verifier for verification + T::Verifier::verify(&message.event_log, &message.proof) + .map_err(|e| Error::::Verification(e))?; + + // Decode event log into an Envelope + let envelope = + Envelope::try_from(&message.event_log).map_err(|_| Error::::InvalidEnvelope)?; + + // Verify that the message was submitted from the known Gateway contract + ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); + + // Verify the message has not been processed + ensure!(!>::get(envelope.nonce.into()), Error::::InvalidNonce); + + // Decode payload into `MessageV2` + let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) + .map_err(|_| Error::::InvalidPayload)?; + + let xcm = + T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + + // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: + // T::RewardLeger::deposit(who, envelope.fee.into())?; + // a. The submit extrinsic cost on BH + // b. The delivery cost to AH + // c. The execution cost on AH + // d. The execution cost on destination chain(if any) + // e. The reward + + // Attempt to forward XCM to AH + let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]); + let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; + Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); + + // Set nonce flag to true + >::set(envelope.nonce.into()); + + Ok(()) + } + + /// Halt or resume all pallet operations. May only be called by root. + #[pallet::call_index(1)] + #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))] + pub fn set_operating_mode( + origin: OriginFor, + mode: BasicOperatingMode, + ) -> DispatchResult { + ensure_root(origin)?; + OperatingMode::::set(mode); + Self::deposit_event(Event::OperatingModeChanged { mode }); + Ok(()) + } + } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index f36535d88c3a..63768340c193 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -6,17 +6,17 @@ use crate::{self as inbound_queue}; use frame_support::{derive_impl, parameter_types, traits::ConstU32}; use hex_literal::hex; use snowbridge_beacon_primitives::{ - types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, + types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, }; use snowbridge_core::{ - inbound::{Log, Proof, VerificationError}, - TokenId, + inbound::{Log, Proof, VerificationError}, + TokenId, }; use snowbridge_router_primitives::inbound::v2::MessageToXcm; use sp_core::H160; use sp_runtime::{ - traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, - BuildStorage, MultiSignature, + traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, + BuildStorage, MultiSignature, }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; @@ -40,10 +40,10 @@ type Balance = u128; #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { - type AccountId = AccountId; - type Lookup = IdentityLookup; - type AccountData = pallet_balances::AccountData; - type Block = Block; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type AccountData = pallet_balances::AccountData; + type Block = Block; } parameter_types! { @@ -52,9 +52,9 @@ parameter_types! { #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Test { - type Balance = Balance; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; } parameter_types! { @@ -83,63 +83,63 @@ parameter_types! { } impl snowbridge_pallet_ethereum_client::Config for Test { - type RuntimeEvent = RuntimeEvent; - type ForkVersions = ChainForkVersions; - type FreeHeadersInterval = ConstU32<32>; - type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type ForkVersions = ChainForkVersions; + type FreeHeadersInterval = ConstU32<32>; + type WeightInfo = (); } // Mock verifier pub struct MockVerifier; impl Verifier for MockVerifier { - fn verify(_: &Log, _: &Proof) -> Result<(), VerificationError> { - Ok(()) - } + fn verify(_: &Log, _: &Proof) -> Result<(), VerificationError> { + Ok(()) + } } const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; #[cfg(feature = "runtime-benchmarks")] impl BenchmarkHelper for Test { - // not implemented since the MockVerifier is used for tests - fn initialize_storage(_: BeaconHeader, _: H256) {} + // not implemented since the MockVerifier is used for tests + fn initialize_storage(_: BeaconHeader, _: H256) {} } // Mock XCM sender that always succeeds pub struct MockXcmSender; impl SendXcm for MockXcmSender { - type Ticket = Xcm<()>; + type Ticket = Xcm<()>; - fn validate( - dest: &mut Option, - xcm: &mut Option>, - ) -> SendResult { - if let Some(location) = dest { - match location.unpack() { - (_, [Parachain(1001)]) => return Err(XcmpSendError::NotApplicable), - _ => Ok((xcm.clone().unwrap(), Assets::default())), - } - } else { - Ok((xcm.clone().unwrap(), Assets::default())) - } - } + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + if let Some(location) = dest { + match location.unpack() { + (_, [Parachain(1001)]) => return Err(XcmpSendError::NotApplicable), + _ => Ok((xcm.clone().unwrap(), Assets::default())), + } + } else { + Ok((xcm.clone().unwrap(), Assets::default())) + } + } - fn deliver(xcm: Self::Ticket) -> core::result::Result { - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); - Ok(hash) - } + fn deliver(xcm: Self::Ticket) -> core::result::Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) + } } pub struct MockTokenIdConvert; impl MaybeEquivalence for MockTokenIdConvert { - fn convert(_id: &TokenId) -> Option { - Some(Location::parent()) - } - fn convert_back(_loc: &Location) -> Option { - None - } + fn convert(_id: &TokenId) -> Option { + Some(Location::parent()) + } + fn convert_back(_loc: &Location) -> Option { + None + } } parameter_types! { @@ -150,41 +150,41 @@ parameter_types! { } impl inbound_queue::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Verifier = MockVerifier; - type XcmSender = MockXcmSender; - type WeightInfo = (); - type GatewayAddress = GatewayAddress; - type AssetHubParaId = ConstU32<1000>; - type MessageConverter = - MessageToXcm; - #[cfg(feature = "runtime-benchmarks")] - type Helper = Test; + type RuntimeEvent = RuntimeEvent; + type Verifier = MockVerifier; + type XcmSender = MockXcmSender; + type WeightInfo = (); + type GatewayAddress = GatewayAddress; + type AssetHubParaId = ConstU32<1000>; + type MessageConverter = + MessageToXcm; + #[cfg(feature = "runtime-benchmarks")] + type Helper = Test; } pub fn last_events(n: usize) -> Vec { - frame_system::Pallet::::events() - .into_iter() - .rev() - .take(n) - .rev() - .map(|e| e.event) - .collect() + frame_system::Pallet::::events() + .into_iter() + .rev() + .take(n) + .rev() + .map(|e| e.event) + .collect() } pub fn expect_events(e: Vec) { - assert_eq!(last_events(e.len()), e); + assert_eq!(last_events(e.len()), e); } pub fn setup() { - System::set_block_number(1); + System::set_block_number(1); } pub fn new_tester() -> sp_io::TestExternalities { - let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let mut ext: sp_io::TestExternalities = storage.into(); - ext.execute_with(setup); - ext + let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext: sp_io::TestExternalities = storage.into(); + ext.execute_with(setup); + ext } // Generated from smoketests: @@ -192,7 +192,7 @@ pub fn new_tester() -> sp_io::TestExternalities { // ./make-bindings // cargo test --test register_token -- --nocapture pub fn mock_event_log() -> Log { - Log { + Log { // gateway address address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), topics: vec![ @@ -208,7 +208,7 @@ pub fn mock_event_log() -> Log { } pub fn mock_event_log_invalid_channel() -> Log { - Log { + Log { address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), topics: vec![ hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), @@ -221,7 +221,7 @@ pub fn mock_event_log_invalid_channel() -> Log { } pub fn mock_event_log_invalid_gateway() -> Log { - Log { + Log { // gateway address address: H160::zero(), topics: vec![ @@ -237,28 +237,28 @@ pub fn mock_event_log_invalid_gateway() -> Log { } pub fn mock_execution_proof() -> ExecutionProof { - ExecutionProof { - header: BeaconHeader::default(), - ancestry_proof: None, - execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { - parent_hash: Default::default(), - fee_recipient: Default::default(), - state_root: Default::default(), - receipts_root: Default::default(), - logs_bloom: vec![], - prev_randao: Default::default(), - block_number: 0, - gas_limit: 0, - gas_used: 0, - timestamp: 0, - extra_data: vec![], - base_fee_per_gas: Default::default(), - block_hash: Default::default(), - transactions_root: Default::default(), - withdrawals_root: Default::default(), - blob_gas_used: 0, - excess_blob_gas: 0, - }), - execution_branch: vec![], - } + ExecutionProof { + header: BeaconHeader::default(), + ancestry_proof: None, + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: Default::default(), + fee_recipient: Default::default(), + state_root: Default::default(), + receipts_root: Default::default(), + logs_bloom: vec![], + prev_randao: Default::default(), + block_number: 0, + gas_limit: 0, + gas_used: 0, + timestamp: 0, + extra_data: vec![], + base_fee_per_gas: Default::default(), + block_hash: Default::default(), + transactions_root: Default::default(), + withdrawals_root: Default::default(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![], + } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 148a01b3efe1..d2720f2dcf05 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -13,146 +13,146 @@ use codec::DecodeLimit; use snowbridge_router_primitives::inbound::v2::InboundAsset; use sp_core::H256; use xcm::{ - opaque::latest::{ - prelude::{ClearOrigin, ReceiveTeleportedAsset}, - Asset, - }, - prelude::*, - VersionedXcm, MAX_XCM_DECODE_DEPTH, + opaque::latest::{ + prelude::{ClearOrigin, ReceiveTeleportedAsset}, + Asset, + }, + prelude::*, + VersionedXcm, MAX_XCM_DECODE_DEPTH, }; #[test] fn test_submit_happy_path() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - - let origin = RuntimeOrigin::signed(relayer.clone()); - - // Submit message - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - expect_events(vec![InboundQueueEvent::MessageReceived { - nonce: 1, - message_id: [ - 183, 243, 1, 130, 170, 254, 104, 45, 116, 181, 146, 237, 14, 139, 138, 89, 43, 166, - 182, 24, 163, 222, 112, 238, 215, 83, 21, 160, 24, 88, 112, 9, - ], - } - .into()]); - }); + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + + let origin = RuntimeOrigin::signed(relayer.clone()); + + // Submit message + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + expect_events(vec![InboundQueueEvent::MessageReceived { + nonce: 1, + message_id: [ + 183, 243, 1, 130, 170, 254, 104, 45, 116, 181, 146, 237, 14, 139, 138, 89, 43, 166, + 182, 24, 163, 222, 112, 238, 215, 83, 21, 160, 24, 88, 112, 9, + ], + } + .into()]); + }); } #[test] fn test_submit_xcm_invalid_channel() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Submit message - let message = Message { - event_log: mock_event_log_invalid_channel(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_noop!( + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Submit message + let message = Message { + event_log: mock_event_log_invalid_channel(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + assert_noop!( InboundQueue::submit(origin.clone(), message.clone()), Error::::InvalidChannel, ); - }); + }); } #[test] fn test_submit_with_invalid_gateway() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Submit message - let message = Message { - event_log: mock_event_log_invalid_gateway(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_noop!( + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Submit message + let message = Message { + event_log: mock_event_log_invalid_gateway(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + assert_noop!( InboundQueue::submit(origin.clone(), message.clone()), Error::::InvalidGateway ); - }); + }); } #[test] fn test_submit_with_invalid_nonce() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Submit message - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - - // Submit the same again - assert_noop!( + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + + // Submit message + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + + // Submit the same again + assert_noop!( InboundQueue::submit(origin.clone(), message.clone()), Error::::InvalidNonce ); - }); + }); } #[test] fn test_set_operating_mode() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - - assert_ok!(InboundQueue::set_operating_mode( + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + let origin = RuntimeOrigin::signed(relayer); + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + + assert_ok!(InboundQueue::set_operating_mode( RuntimeOrigin::root(), snowbridge_core::BasicOperatingMode::Halted )); - assert_noop!(InboundQueue::submit(origin, message), Error::::Halted); - }); + assert_noop!(InboundQueue::submit(origin, message), Error::::Halted); + }); } #[test] fn test_set_operating_mode_root_only() { - new_tester().execute_with(|| { - assert_noop!( + new_tester().execute_with(|| { + assert_noop!( InboundQueue::set_operating_mode( RuntimeOrigin::signed(Keyring::Bob.into()), snowbridge_core::BasicOperatingMode::Halted ), DispatchError::BadOrigin ); - }); + }); } #[test] fn test_send_native_erc20_token_payload() { - new_tester().execute_with(|| { + new_tester().execute_with(|| { // To generate test data: forge test --match-test testSendEther -vvvv let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf04005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000b2d3595bf00600000000000000000000").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); @@ -181,7 +181,7 @@ fn test_send_native_erc20_token_payload() { #[test] fn test_send_foreign_erc20_token_payload() { - new_tester().execute_with(|| { + new_tester().execute_with(|| { let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); assert_ok!(message.clone()); @@ -209,7 +209,7 @@ fn test_send_foreign_erc20_token_payload() { #[test] fn test_register_token_inbound_message_with_xcm_and_claimer() { - new_tester().execute_with(|| { + new_tester().execute_with(|| { let payload = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a904005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000300508020401000002286bee0a015029e3b139f4393adda86303fcdaa35f60bb7092bf").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); assert_ok!(message.clone()); @@ -260,49 +260,49 @@ fn test_register_token_inbound_message_with_xcm_and_claimer() { #[test] fn encode_xcm() { - new_tester().execute_with(|| { - let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); + new_tester().execute_with(|| { + let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); - let instructions: Xcm<()> = - vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); + let instructions: Xcm<()> = + vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); - let versioned_xcm_message = VersionedXcm::V5(instructions.clone()); + let versioned_xcm_message = VersionedXcm::V5(instructions.clone()); - let xcm_bytes = VersionedXcm::encode(&versioned_xcm_message); - let hex_string = hex::encode(xcm_bytes.clone()); + let xcm_bytes = VersionedXcm::encode(&versioned_xcm_message); + let hex_string = hex::encode(xcm_bytes.clone()); - println!("xcm hex: {}", hex_string); + println!("xcm hex: {}", hex_string); - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut xcm_bytes.as_ref(), - ); + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut xcm_bytes.as_ref(), + ); - assert_ok!(versioned_xcm.clone()); + assert_ok!(versioned_xcm.clone()); - // Check if decoding was successful - let decoded_instructions = match versioned_xcm.unwrap() { - VersionedXcm::V5(decoded) => decoded, - _ => { - panic!("unexpected xcm version found") - }, - }; + // Check if decoding was successful + let decoded_instructions = match versioned_xcm.unwrap() { + VersionedXcm::V5(decoded) => decoded, + _ => { + panic!("unexpected xcm version found") + }, + }; - let mut original_instructions = instructions.into_iter(); - let mut decoded_instructions = decoded_instructions.into_iter(); + let mut original_instructions = instructions.into_iter(); + let mut decoded_instructions = decoded_instructions.into_iter(); - let original_first = original_instructions.next().take(); - let decoded_first = decoded_instructions.next().take(); - assert_eq!( - original_first, decoded_first, - "First instruction (ReceiveTeleportedAsset) does not match." - ); + let original_first = original_instructions.next().take(); + let decoded_first = decoded_instructions.next().take(); + assert_eq!( + original_first, decoded_first, + "First instruction (ReceiveTeleportedAsset) does not match." + ); - let original_second = original_instructions.next().take(); - let decoded_second = decoded_instructions.next().take(); - assert_eq!( - original_second, decoded_second, - "Second instruction (ClearOrigin) does not match." - ); - }); + let original_second = original_instructions.next().take(); + let decoded_second = decoded_instructions.next().take(); + assert_eq!( + original_second, decoded_second, + "Second instruction (ClearOrigin) does not match." + ); + }); } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs new file mode 100644 index 000000000000..eecf07b1f830 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use snowbridge_core::sparse_bitmap::SparseBitmapImpl; + +pub type Nonce = SparseBitmapImpl>; diff --git a/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs b/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs index 894f159ef64e..810c4747c382 100644 --- a/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs +++ b/bridges/snowbridge/primitives/core/src/sparse_bitmap.rs @@ -1,6 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork use frame_support::storage::StorageMap; use sp_std::marker::PhantomData; +/// Sparse bitmap implementation. pub trait SparseBitmap where BitMap: StorageMap, From c27def22aeffcb3c9042fdf57b85747f0fc101e9 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 21 Nov 2024 10:13:24 +0200 Subject: [PATCH 009/340] use payfees --- bridges/snowbridge/primitives/router/src/inbound/v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 69cacdb995fa..b8f1ab8ac782 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -115,7 +115,7 @@ for MessageToXcm let fee: xcm::prelude::Asset = (fee_asset, fee_value).into(); let mut instructions = vec![ ReceiveTeleportedAsset(fee.clone().into()), - BuyExecution { fees: fee, weight_limit: Unlimited }, + PayFees { asset: fee }, DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), UniversalOrigin(GlobalConsensus(network)), ]; From b290f27c9bdbc76ca0f21272e4567c346da23f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 21 Nov 2024 10:13:23 +0100 Subject: [PATCH 010/340] revive: Bump connect timeout to fix flaky tests (#6567) The eth RPC tests fail sometimes because they run into a connect timeout because the node takes a long time to start. This bumps the connect timeout from 30 to 120 seconds. Locally they take around 40s for me. As a drive by I also remove a apparently duplicated nextest config. --------- Co-authored-by: ordian --- .config/nextest.toml | 1 - substrate/.config/nextest.toml | 124 ------------------------ substrate/frame/revive/rpc/src/tests.rs | 4 +- 3 files changed, 2 insertions(+), 127 deletions(-) delete mode 100644 substrate/.config/nextest.toml diff --git a/.config/nextest.toml b/.config/nextest.toml index 912bf2514a77..b4bdec4aea92 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -21,7 +21,6 @@ retries = 5 # The number of threads to run tests with. Supported values are either an integer or # the string "num-cpus". Can be overridden through the `--test-threads` option. # test-threads = "num-cpus" - test-threads = 20 # The number of threads required for each test. This is generally used in overrides to diff --git a/substrate/.config/nextest.toml b/substrate/.config/nextest.toml deleted file mode 100644 index eb0ed09cad92..000000000000 --- a/substrate/.config/nextest.toml +++ /dev/null @@ -1,124 +0,0 @@ -# This is the default config used by nextest. It is embedded in the binary at -# build time. It may be used as a template for .config/nextest.toml. - -[store] -# The directory under the workspace root at which nextest-related files are -# written. Profile-specific storage is currently written to dir/. -dir = "target/nextest" - -# This section defines the default nextest profile. Custom profiles are layered -# on top of the default profile. -[profile.default] -# "retries" defines the number of times a test should be retried. If set to a -# non-zero value, tests that succeed on a subsequent attempt will be marked as -# non-flaky. Can be overridden through the `--retries` option. -# Examples -# * retries = 3 -# * retries = { backoff = "fixed", count = 2, delay = "1s" } -# * retries = { backoff = "exponential", count = 10, delay = "1s", jitter = true, max-delay = "10s" } -retries = 5 - -# The number of threads to run tests with. Supported values are either an integer or -# the string "num-cpus". Can be overridden through the `--test-threads` option. -test-threads = "num-cpus" - -# The number of threads required for each test. This is generally used in overrides to -# mark certain tests as heavier than others. However, it can also be set as a global parameter. -threads-required = 1 - -# Show these test statuses in the output. -# -# The possible values this can take are: -# * none: no output -# * fail: show failed (including exec-failed) tests -# * retry: show flaky and retried tests -# * slow: show slow tests -# * pass: show passed tests -# * skip: show skipped tests (most useful for CI) -# * all: all of the above -# -# Each value includes all the values above it; for example, "slow" includes -# failed and retried tests. -# -# Can be overridden through the `--status-level` flag. -status-level = "pass" - -# Similar to status-level, show these test statuses at the end of the run. -final-status-level = "flaky" - -# "failure-output" defines when standard output and standard error for failing tests are produced. -# Accepted values are -# * "immediate": output failures as soon as they happen -# * "final": output failures at the end of the test run -# * "immediate-final": output failures as soon as they happen and at the end of -# the test run; combination of "immediate" and "final" -# * "never": don't output failures at all -# -# For large test suites and CI it is generally useful to use "immediate-final". -# -# Can be overridden through the `--failure-output` option. -failure-output = "immediate" - -# "success-output" controls production of standard output and standard error on success. This should -# generally be set to "never". -success-output = "never" - -# Cancel the test run on the first failure. For CI runs, consider setting this -# to false. -fail-fast = true - -# Treat a test that takes longer than the configured 'period' as slow, and print a message. -# See for more information. -# -# Optional: specify the parameter 'terminate-after' with a non-zero integer, -# which will cause slow tests to be terminated after the specified number of -# periods have passed. -# Example: slow-timeout = { period = "60s", terminate-after = 2 } -slow-timeout = { period = "60s" } - -# Treat a test as leaky if after the process is shut down, standard output and standard error -# aren't closed within this duration. -# -# This usually happens in case of a test that creates a child process and lets it inherit those -# handles, but doesn't clean the child process up (especially when it fails). -# -# See for more information. -leak-timeout = "100ms" - -[profile.default.junit] -# Output a JUnit report into the given file inside 'store.dir/'. -# If unspecified, JUnit is not written out. - -path = "junit.xml" - -# The name of the top-level "report" element in JUnit report. If aggregating -# reports across different test runs, it may be useful to provide separate names -# for each report. -report-name = "substrate" - -# Whether standard output and standard error for passing tests should be stored in the JUnit report. -# Output is stored in the and elements of the element. -store-success-output = false - -# Whether standard output and standard error for failing tests should be stored in the JUnit report. -# Output is stored in the and elements of the element. -# -# Note that if a description can be extracted from the output, it is always stored in the -# element. -store-failure-output = true - -# This profile is activated if MIRI_SYSROOT is set. -[profile.default-miri] -# Miri tests take up a lot of memory, so only run 1 test at a time by default. -test-threads = 1 - -# Mutual exclusion of tests with `cargo build` invocation as a lock to avoid multiple -# simultaneous invocations clobbering each other. -[test-groups] -serial-integration = { max-threads = 1 } - -# Running UI tests sequentially -# More info can be found here: https://github.com/paritytech/ci_cd/issues/754 -[[profile.default.overrides]] -filter = 'test(/(^ui$|_ui|ui_)/)' -test-group = 'serial-integration' diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index eb23bd7583a0..7734c8c57209 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -32,9 +32,9 @@ use static_init::dynamic; use std::thread; use substrate_cli_test_utils::*; -/// Create a websocket client with a 30s timeout. +/// Create a websocket client with a 120s timeout. async fn ws_client_with_retry(url: &str) -> WsClient { - let timeout = tokio::time::Duration::from_secs(30); + let timeout = tokio::time::Duration::from_secs(120); tokio::time::timeout(timeout, async { loop { if let Ok(client) = WsClientBuilder::default().build(url).await { From 7c9e34b576ad91aba2575ddaaddca0ad1aecae83 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:58:44 +0200 Subject: [PATCH 011/340] network-gossip: Ensure sync event is processed on unknown peer roles (#6553) The `GossipEngine::poll_next` implementation polls both the `notification_service` and the `sync_event_stream`. If both polls produce valid data to be processed (`Poll::Ready(Some(..))`), then the sync event is ignored when we receive `NotificationEvent::NotificationStreamOpened` and the role cannot be deduced. This PR ensures both events are processed gracefully. While at it, I have added a warning to the sync engine related to `notification_service` producing `Poll::Ready(None)`. This effectively ensures that `SyncEvents` propagate to the network potentially fixing any state mismatch. For more context: https://github.com/paritytech/polkadot-sdk/issues/6507 cc @paritytech/sdk-node --------- Signed-off-by: Alexandru Vasile --- prdoc/pr_6553.prdoc | 13 ++++++++++++ substrate/client/network-gossip/src/bridge.rs | 20 +++++++++---------- substrate/client/network/sync/src/engine.rs | 9 ++++++++- 3 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 prdoc/pr_6553.prdoc diff --git a/prdoc/pr_6553.prdoc b/prdoc/pr_6553.prdoc new file mode 100644 index 000000000000..8692eba3a9f5 --- /dev/null +++ b/prdoc/pr_6553.prdoc @@ -0,0 +1,13 @@ +title: Ensure sync event is processed on unknown peer roles + +doc: + - audience: Node Dev + description: | + The GossipEngine::poll_next implementation polls both the notification_service and the sync_event_stream. + This PR ensures both events are processed gracefully. + +crates: + - name: sc-network-gossip + bump: patch + - name: sc-network-sync + bump: patch diff --git a/substrate/client/network-gossip/src/bridge.rs b/substrate/client/network-gossip/src/bridge.rs index a4bd922a76d5..2daf1e49ee4b 100644 --- a/substrate/client/network-gossip/src/bridge.rs +++ b/substrate/client/network-gossip/src/bridge.rs @@ -220,18 +220,16 @@ impl Future for GossipEngine { }, NotificationEvent::NotificationStreamOpened { peer, handshake, .. - } => { - let Some(role) = this.network.peer_role(peer, handshake) else { + } => + if let Some(role) = this.network.peer_role(peer, handshake) { + this.state_machine.new_peer( + &mut this.notification_service, + peer, + role, + ); + } else { log::debug!(target: "gossip", "role for {peer} couldn't be determined"); - continue - }; - - this.state_machine.new_peer( - &mut this.notification_service, - peer, - role, - ); - }, + }, NotificationEvent::NotificationStreamClosed { peer } => { this.state_machine .peer_disconnected(&mut this.notification_service, peer); diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index cc2089d1974c..349c41ee1f4a 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -545,7 +545,14 @@ where self.process_service_command(command), notification_event = self.notification_service.next_event() => match notification_event { Some(event) => self.process_notification_event(event), - None => return, + None => { + error!( + target: LOG_TARGET, + "Terminating `SyncingEngine` because `NotificationService` has terminated.", + ); + + return; + } }, response_event = self.pending_responses.select_next_some() => self.process_response_event(response_event), From 56d97c3ad8c86e602bc7ac368751210517c4309f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 21 Nov 2024 10:14:36 +0000 Subject: [PATCH 012/340] slot-based-collator: Move spawning of the futures (#6561) Move spawning of the slot-based collator into the `run` function. Also the tasks are being spawned as blocking task and not just as normal tasks. --------- Co-authored-by: GitHub Action --- .../aura/src/collators/slot_based/mod.rs | 82 ++++++++++++------- .../polkadot-omni-node/lib/src/nodes/aura.rs | 23 ++---- cumulus/test/service/src/lib.rs | 14 +--- prdoc/pr_6561.prdoc | 11 +++ 4 files changed, 73 insertions(+), 57 deletions(-) create mode 100644 prdoc/pr_6561.prdoc diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs index 7453d3c89d08..18e63681d578 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs @@ -28,6 +28,7 @@ //! during the relay chain block. After the block is built, the block builder task sends it to //! the collation task which compresses it and submits it to the collation-generation subsystem. +use self::{block_builder_task::run_block_builder, collation_task::run_collation_task}; use codec::Codec; use consensus_common::ParachainCandidate; use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterface; @@ -36,32 +37,28 @@ use cumulus_client_consensus_proposer::ProposerInterface; use cumulus_primitives_aura::AuraUnincludedSegmentApi; use cumulus_primitives_core::GetCoreSelectorApi; use cumulus_relay_chain_interface::RelayChainInterface; +use futures::FutureExt; use polkadot_primitives::{ CollatorPair, CoreIndex, Hash as RelayHash, Id as ParaId, ValidationCodeHash, }; - use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf, UsageProvider}; use sc_consensus::BlockImport; use sc_utils::mpsc::tracing_unbounded; - use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; use sp_consensus_aura::AuraApi; -use sp_core::crypto::Pair; +use sp_core::{crypto::Pair, traits::SpawnNamed}; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Member}; - use std::{sync::Arc, time::Duration}; -use self::{block_builder_task::run_block_builder, collation_task::run_collation_task}; - mod block_builder_task; mod collation_task; /// Parameters for [`run`]. -pub struct Params { +pub struct Params { /// Inherent data providers. Only non-consensus inherent data should be provided, i.e. /// the timestamp, slot, and paras inherents should be omitted, as they are set by this /// collator. @@ -93,13 +90,30 @@ pub struct Params { /// Drift slots by a fixed duration. This can be used to create more preferrable authoring /// timings. pub slot_drift: Duration, + /// Spawner for spawning futures. + pub spawner: Spawner, } /// Run aura-based block building and collation task. -pub fn run( - params: Params, -) -> (impl futures::Future, impl futures::Future) -where +pub fn run( + Params { + create_inherent_data_providers, + block_import, + para_client, + para_backend, + relay_client, + code_hash_provider, + keystore, + collator_key, + para_id, + proposer, + collator_service, + authoring_duration, + reinitialize, + slot_drift, + spawner, + }: Params, +) where Block: BlockT, Client: ProvideRuntimeApi + BlockOf @@ -123,39 +137,49 @@ where P: Pair + 'static, P::Public: AppPublic + Member + Codec, P::Signature: TryFrom> + Member + Codec, + Spawner: SpawnNamed, { let (tx, rx) = tracing_unbounded("mpsc_builder_to_collator", 100); let collator_task_params = collation_task::Params { - relay_client: params.relay_client.clone(), - collator_key: params.collator_key, - para_id: params.para_id, - reinitialize: params.reinitialize, - collator_service: params.collator_service.clone(), + relay_client: relay_client.clone(), + collator_key, + para_id, + reinitialize, + collator_service: collator_service.clone(), collator_receiver: rx, }; let collation_task_fut = run_collation_task::(collator_task_params); let block_builder_params = block_builder_task::BuilderTaskParams { - create_inherent_data_providers: params.create_inherent_data_providers, - block_import: params.block_import, - para_client: params.para_client, - para_backend: params.para_backend, - relay_client: params.relay_client, - code_hash_provider: params.code_hash_provider, - keystore: params.keystore, - para_id: params.para_id, - proposer: params.proposer, - collator_service: params.collator_service, - authoring_duration: params.authoring_duration, + create_inherent_data_providers, + block_import, + para_client, + para_backend, + relay_client, + code_hash_provider, + keystore, + para_id, + proposer, + collator_service, + authoring_duration, collator_sender: tx, - slot_drift: params.slot_drift, + slot_drift, }; let block_builder_fut = run_block_builder::(block_builder_params); - (collation_task_fut, block_builder_fut) + spawner.spawn_blocking( + "slot-based-block-builder", + Some("slot-based-collator"), + block_builder_fut.boxed(), + ); + spawner.spawn_blocking( + "slot-based-collation", + Some("slot-based-collator"), + collation_task_fut.boxed(), + ); } /// Message to be sent from the block builder to the collation task. diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs index ec5d0a439ec4..0b2c230f695d 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs @@ -54,6 +54,7 @@ use sc_service::{Configuration, Error, TaskManager}; use sc_telemetry::TelemetryHandle; use sc_transaction_pool::TransactionPoolHandle; use sp_api::ProvideRuntimeApi; +use sp_core::traits::SpawnNamed; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; use sp_runtime::{ @@ -242,7 +243,7 @@ where AuraId: AuraIdT + Sync, { #[docify::export_content] - fn launch_slot_based_collator( + fn launch_slot_based_collator( params: SlotBasedParams< ParachainBlockImport, CIDP, @@ -252,28 +253,17 @@ where CHP, Proposer, CS, + Spawner, >, - task_manager: &TaskManager, ) where CIDP: CreateInherentDataProviders + 'static, CIDP::InherentDataProviders: Send, CHP: cumulus_client_consensus_common::ValidationCodeHashProvider + Send + 'static, Proposer: ProposerInterface + Send + Sync + 'static, CS: CollatorServiceInterface + Send + Sync + Clone + 'static, + Spawner: SpawnNamed, { - let (collation_future, block_builder_future) = - slot_based::run::::Pair, _, _, _, _, _, _, _, _>(params); - - task_manager.spawn_essential_handle().spawn( - "collation-task", - Some("parachain-block-authoring"), - collation_future, - ); - task_manager.spawn_essential_handle().spawn( - "block-builder-task", - Some("parachain-block-authoring"), - block_builder_future, - ); + slot_based::run::::Pair, _, _, _, _, _, _, _, _, _>(params); } } @@ -335,11 +325,12 @@ where authoring_duration: Duration::from_millis(2000), reinitialize: false, slot_drift: Duration::from_secs(1), + spawner: task_manager.spawn_handle(), }; // We have a separate function only to be able to use `docify::export` on this piece of // code. - Self::launch_slot_based_collator(params, task_manager); + Self::launch_slot_based_collator(params); Ok(()) } diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 9234442d399c..f01da9becef1 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -497,20 +497,10 @@ where authoring_duration: Duration::from_millis(2000), reinitialize: false, slot_drift: Duration::from_secs(1), + spawner: task_manager.spawn_handle(), }; - let (collation_future, block_builder_future) = - slot_based::run::(params); - task_manager.spawn_essential_handle().spawn( - "collation-task", - None, - collation_future, - ); - task_manager.spawn_essential_handle().spawn( - "block-builder-task", - None, - block_builder_future, - ); + slot_based::run::(params); } else { tracing::info!(target: LOG_TARGET, "Starting block authoring with lookahead collator."); let params = AuraParams { diff --git a/prdoc/pr_6561.prdoc b/prdoc/pr_6561.prdoc new file mode 100644 index 000000000000..714521925a6b --- /dev/null +++ b/prdoc/pr_6561.prdoc @@ -0,0 +1,11 @@ +title: 'slot-based-collator: Move spawning of the futures' +doc: +- audience: Node Dev + description: "Move spawning of the slot-based collator into the `run` function.\ + \ Also the tasks are being spawned as blocking task and not just as normal tasks.\r\ + \n" +crates: +- name: cumulus-client-consensus-aura + bump: major +- name: polkadot-omni-node-lib + bump: major From 1f7765b63530e362d6b1b4a225b47daddda03637 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:39:08 +0200 Subject: [PATCH 013/340] github/workflows: add ARM macos build binaries job (#6427) # Description This PR adds the required changes to release `polkadot`, `polkadot-parachain` and `polkadot-omni-node` binaries built on Apple Sillicon macos. ## Integration This addresses requests from the community for such binaries: #802, and they should be part of the Github release page. ## Review Notes Test on paritytech-stg solely focused on macos binaries: https://github.com/paritytech-stg/polkadot-sdk/actions/runs/11824692766/job/32946793308, except the steps related to `pgpkms` (which need AWS credentials, missing from paritytech-stg). The binary names don't have a `darwin-arm` identifier, and conflict with the existing x86_64-linux binaries. I haven't tested building everything on `paritytech-stg` because the x86_64-linux builds run on `unbutu-latest-m` which isn't enabled on `pairtytech-stg` (and I haven't asked CI team to enable one), so testing how to go around naming conflicts should be covered next. ### TODO - [x] Test the workflow start to end (especially the last bits related to uploading the binaries on S3 and ensuring the previous binaries and the new ones coexist harmoniously on S3/action artifacts storage without naming conflicts) @EgorPopelyaev - [x] Publish the arm binaries on the Github release page - to clarify what's needed @iulianbarbu . Current practice is to manually publish the binaries built via `release-build-rc.yml` workflow, taken from S3. Would be great to have the binaries there in the first place before working on automating this, but I would also do it in a follow up PR. ### Follow ups - [ ] unify the binaries building under `release-30_publish_release_draft.yml` maybe? - [ ] automate binary artifacts upload to S3 in `release-30_publish_release_draft.yml` --------- Signed-off-by: Iulian Barbu Co-authored-by: EgorPopelyaev --- .../scripts/release/build-linux-release.sh | 4 +- .../scripts/release/build-macos-release.sh | 37 +++ .github/scripts/release/release_lib.sh | 38 ++-- .../release-30_publish_release_draft.yml | 4 +- .github/workflows/release-build-rc.yml | 91 ++++++++ .../workflows/release-reusable-rc-buid.yml | 212 +++++++++++++++++- .../workflows/release-reusable-s3-upload.yml | 17 +- 7 files changed, 376 insertions(+), 27 deletions(-) create mode 100755 .github/scripts/release/build-macos-release.sh diff --git a/.github/scripts/release/build-linux-release.sh b/.github/scripts/release/build-linux-release.sh index a6bd658d292a..874c9b44788b 100755 --- a/.github/scripts/release/build-linux-release.sh +++ b/.github/scripts/release/build-linux-release.sh @@ -3,6 +3,8 @@ # This is used to build our binaries: # - polkadot # - polkadot-parachain +# - polkadot-omni-node +# # set -e BIN=$1 @@ -21,7 +23,7 @@ time cargo build --profile $PROFILE --locked --verbose --bin $BIN --package $PAC echo "Artifact target: $ARTIFACTS" cp ./target/$PROFILE/$BIN "$ARTIFACTS" -pushd "$ARTIFACTS" > /dev/nul +pushd "$ARTIFACTS" > /dev/null sha256sum "$BIN" | tee "$BIN.sha256" EXTRATAG="$($ARTIFACTS/$BIN --version | diff --git a/.github/scripts/release/build-macos-release.sh b/.github/scripts/release/build-macos-release.sh new file mode 100755 index 000000000000..ba6dcc65d650 --- /dev/null +++ b/.github/scripts/release/build-macos-release.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +# This is used to build our binaries: +# - polkadot +# - polkadot-parachain +# - polkadot-omni-node +# set -e + +BIN=$1 +PACKAGE=${2:-$BIN} + +PROFILE=${PROFILE:-production} +# parity-macos runner needs a path where it can +# write, so make it relative to github workspace. +ARTIFACTS=$GITHUB_WORKSPACE/artifacts/$BIN +VERSION=$(git tag -l --contains HEAD | grep -E "^v.*") + +echo "Artifacts will be copied into $ARTIFACTS" +mkdir -p "$ARTIFACTS" + +git log --pretty=oneline -n 1 +time cargo build --profile $PROFILE --locked --verbose --bin $BIN --package $PACKAGE + +echo "Artifact target: $ARTIFACTS" + +cp ./target/$PROFILE/$BIN "$ARTIFACTS" +pushd "$ARTIFACTS" > /dev/null +sha256sum "$BIN" | tee "$BIN.sha256" + +EXTRATAG="$($ARTIFACTS/$BIN --version | + sed -n -r 's/^'$BIN' ([0-9.]+.*-[0-9a-f]{7,13})-.*$/\1/p')" + +EXTRATAG="${VERSION}-${EXTRATAG}-$(cut -c 1-8 $ARTIFACTS/$BIN.sha256)" + +echo "$BIN version = ${VERSION} (EXTRATAG = ${EXTRATAG})" +echo -n ${VERSION} > "$ARTIFACTS/VERSION" +echo -n ${EXTRATAG} > "$ARTIFACTS/EXTRATAG" diff --git a/.github/scripts/release/release_lib.sh b/.github/scripts/release/release_lib.sh index f5032073b617..8b9254ec3f29 100644 --- a/.github/scripts/release/release_lib.sh +++ b/.github/scripts/release/release_lib.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Set the new version by replacing the value of the constant given as patetrn +# Set the new version by replacing the value of the constant given as pattern # in the file. # # input: pattern, version, file @@ -119,21 +119,23 @@ set_polkadot_parachain_binary_version() { upload_s3_release() { - alias aws='podman run --rm -it docker.io/paritytech/awscli -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_BUCKET aws' - - product=$1 - version=$2 - - echo "Working on product: $product " - echo "Working on version: $version " - - echo "Current content, should be empty on new uploads:" - aws s3 ls "s3://releases.parity.io/polkadot/${version}/" --recursive --human-readable --summarize || true - echo "Content to be uploaded:" - artifacts="artifacts/$product/" - ls "$artifacts" - aws s3 sync --acl public-read "$artifacts" "s3://releases.parity.io/polkadot/${version}/" - echo "Uploaded files:" - aws s3 ls "s3://releases.parity.io/polkadot/${version}/" --recursive --human-readable --summarize - echo "✅ The release should be at https://releases.parity.io/polkadot/${version}" + alias aws='podman run --rm -it docker.io/paritytech/awscli -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_BUCKET aws' + + product=$1 + version=$2 + target=$3 + + echo "Working on product: $product " + echo "Working on version: $version " + echo "Working on platform: $target " + + echo "Current content, should be empty on new uploads:" + aws s3 ls "s3://releases.parity.io/${product}/${version}/${target}" --recursive --human-readable --summarize || true + echo "Content to be uploaded:" + artifacts="artifacts/$product/" + ls "$artifacts" + aws s3 sync --acl public-read "$artifacts" "s3://releases.parity.io/${product}/${version}/${target}" + echo "Uploaded files:" + aws s3 ls "s3://releases.parity.io/${product}/${version}/${target}" --recursive --human-readable --summarize + echo "✅ The release should be at https://releases.parity.io/${product}/${version}/${target}" } diff --git a/.github/workflows/release-30_publish_release_draft.yml b/.github/workflows/release-30_publish_release_draft.yml index 376f5fbce909..4364b4f80457 100644 --- a/.github/workflows/release-30_publish_release_draft.yml +++ b/.github/workflows/release-30_publish_release_draft.yml @@ -34,7 +34,7 @@ jobs: strategy: matrix: # Tuples of [package, binary-name] - binary: [ [frame-omni-bencher, frame-omni-bencher], [staging-chain-spec-builder, chain-spec-builder], [polkadot-omni-node, polkadot-omni-node] ] + binary: [ [frame-omni-bencher, frame-omni-bencher], [staging-chain-spec-builder, chain-spec-builder] ] steps: - name: Checkout sources uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 @@ -161,7 +161,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - binary: [frame-omni-bencher, chain-spec-builder, polkadot-omni-node] + binary: [frame-omni-bencher, chain-spec-builder] steps: - name: Download artifacts diff --git a/.github/workflows/release-build-rc.yml b/.github/workflows/release-build-rc.yml index 94bacf320898..a43c2b282a8d 100644 --- a/.github/workflows/release-build-rc.yml +++ b/.github/workflows/release-build-rc.yml @@ -10,6 +10,7 @@ on: options: - polkadot - polkadot-parachain + - polkadot-omni-node - all release_tag: @@ -47,6 +48,7 @@ jobs: binary: '["polkadot", "polkadot-prepare-worker", "polkadot-execute-worker"]' package: polkadot release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: x86_64-unknown-linux-gnu secrets: PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} @@ -68,6 +70,95 @@ jobs: binary: '["polkadot-parachain"]' package: "polkadot-parachain-bin" release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: x86_64-unknown-linux-gnu + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + + build-polkadot-omni-node-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'polkadot-omni-node' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["polkadot-omni-node"]' + package: "polkadot-omni-node" + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: x86_64-unknown-linux-gnu + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + + build-polkadot-macos-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'polkadot' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["polkadot", "polkadot-prepare-worker", "polkadot-execute-worker"]' + package: polkadot + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: aarch64-apple-darwin + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + + build-polkadot-parachain-macos-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["polkadot-parachain"]' + package: "polkadot-parachain-bin" + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: aarch64-apple-darwin + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + + build-polkadot-omni-node-macos-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'polkadot-omni-node' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["polkadot-omni-node"]' + package: "polkadot-omni-node" + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: aarch64-apple-darwin secrets: PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} diff --git a/.github/workflows/release-reusable-rc-buid.yml b/.github/workflows/release-reusable-rc-buid.yml index d925839fb84a..7e31a4744b59 100644 --- a/.github/workflows/release-reusable-rc-buid.yml +++ b/.github/workflows/release-reusable-rc-buid.yml @@ -10,7 +10,7 @@ on: type: string package: - description: Package to be built, for now is either polkadot or polkadot-parachain-bin + description: Package to be built, for now can be polkadot, polkadot-parachain-bin, or polkadot-omni-node required: true type: string @@ -19,6 +19,11 @@ on: required: true type: string + target: + description: Target triple for which the artifacts are being built (e.g. x86_64-unknown-linux-gnu) + required: true + type: string + secrets: PGP_KMS_KEY: required: true @@ -57,6 +62,7 @@ jobs: run: cat .github/env >> $GITHUB_OUTPUT build-rc: + if: ${{ inputs.target == 'x86_64-unknown-linux-gnu' }} needs: [set-image] runs-on: ubuntu-latest-m environment: release @@ -130,8 +136,124 @@ jobs: name: ${{ matrix.binaries }} path: /artifacts/${{ matrix.binaries }} + build-macos-rc: + if: ${{ inputs.target == 'aarch64-apple-darwin' }} + runs-on: parity-macos + environment: release + strategy: + matrix: + binaries: ${{ fromJSON(inputs.binary) }} + env: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + SKIP_WASM_BUILD: 1 + steps: + - name: Checkout sources + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + with: + ref: ${{ inputs.release_tag }} + fetch-depth: 0 + + - name: Set rust version from env file + run: | + RUST_VERSION=$(cat .github/env | sed -E 's/.*ci-unified:([^-]+)-([^-]+).*/\2/') + echo $RUST_VERSION + echo "RUST_VERSION=${RUST_VERSION}" >> $GITHUB_ENV + - name: Set workspace environment variable + # relevant for artifacts upload, which can not interpolate Github Action variable syntax when + # used within valid paths. We can not use root-based paths either, since it is set as read-only + # on the `parity-macos` runner. + run: echo "ARTIFACTS_PATH=${GITHUB_WORKSPACE}/artifacts/${{ matrix.binaries }}" >> $GITHUB_ENV + + - name: Set up Homebrew + uses: Homebrew/actions/setup-homebrew@1ccc07ccd54b6048295516a3eb89b192c35057dc # master from 12.09.2024 + - name: Set homebrew binaries location on path + run: echo "/opt/homebrew/bin" >> $GITHUB_PATH + + - name: Install rust ${{ env.RUST_VERSION }} + uses: actions-rust-lang/setup-rust-toolchain@11df97af8e8102fd60b60a77dfbf58d40cd843b8 # v1.10.1 + with: + cache: false + toolchain: ${{ env.RUST_VERSION }} + target: wasm32-unknown-unknown + components: cargo, clippy, rust-docs, rust-src, rustfmt, rustc, rust-std + + - name: cargo info + run: | + echo "######## rustup show ########" + rustup show + echo "######## cargo --version ########" + cargo --version + + - name: Install protobuf + run: brew install protobuf + - name: Install gpg + run: | + brew install gnupg + # Setup for being able to resolve: keyserver.ubuntu.com. + # See: https://github.com/actions/runner-images/issues/9777 + mkdir -p ~/.gnupg/ + touch ~/.gnupg/dirmngr.conf + echo "standard-resolver" > ~/.gnupg/dirmngr.conf + - name: Install sha256sum + run: | + brew install coreutils + + - name: Install pgpkkms + run: | + # Install pgpkms that is used to sign built artifacts + python3 -m pip install "pgpkms @ git+https://github.com/paritytech-release/pgpkms.git@5a8f82fbb607ea102d8c178e761659de54c7af69" --break-system-packages + + - name: Import gpg keys + shell: bash + run: | + . ./.github/scripts/common/lib.sh + + import_gpg_keys + + - name: Build binary + run: | + git config --global --add safe.directory "${GITHUB_WORKSPACE}" #avoid "detected dubious ownership" error + ./.github/scripts/release/build-macos-release.sh ${{ matrix.binaries }} ${{ inputs.package }} + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + with: + subject-path: ${{ env.ARTIFACTS_PATH }}/${{ matrix.binaries }} + + - name: Sign artifacts + working-directory: ${{ env.ARTIFACTS_PATH }} + run: | + python3 -m pgpkms sign --input ${{matrix.binaries }} -o ${{ matrix.binaries }}.asc + + - name: Check sha256 ${{ matrix.binaries }} + working-directory: ${{ env.ARTIFACTS_PATH }} + shell: bash + run: | + . "${GITHUB_WORKSPACE}"/.github/scripts/common/lib.sh + + echo "Checking binary ${{ matrix.binaries }}" + check_sha256 ${{ matrix.binaries }} + + - name: Check GPG ${{ matrix.binaries }} + working-directory: ${{ env.ARTIFACTS_PATH }} + shell: bash + run: | + . "${GITHUB_WORKSPACE}"/.github/scripts/common/lib.sh + + check_gpg ${{ matrix.binaries }} + + - name: Upload ${{ matrix.binaries }} artifacts + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: ${{ matrix.binaries }}_${{ inputs.target }} + path: ${{ env.ARTIFACTS_PATH }} + build-polkadot-deb-package: - if: ${{ inputs.package == 'polkadot' }} + if: ${{ inputs.package == 'polkadot' && inputs.target == 'x86_64-unknown-linux-gnu' }} needs: [build-rc] runs-on: ubuntu-latest @@ -168,12 +290,13 @@ jobs: overwrite: true upload-polkadot-artifacts-to-s3: - if: ${{ inputs.package == 'polkadot' }} + if: ${{ inputs.package == 'polkadot' && inputs.target == 'x86_64-unknown-linux-gnu' }} needs: [build-polkadot-deb-package] uses: ./.github/workflows/release-reusable-s3-upload.yml with: package: ${{ inputs.package }} release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} secrets: AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} @@ -181,12 +304,93 @@ jobs: upload-polkadot-parachain-artifacts-to-s3: - if: ${{ inputs.package == 'polkadot-parachain-bin' }} + if: ${{ inputs.package == 'polkadot-parachain-bin' && inputs.target == 'x86_64-unknown-linux-gnu' }} needs: [build-rc] uses: ./.github/workflows/release-reusable-s3-upload.yml with: package: polkadot-parachain release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-polkadot-omni-node-artifacts-to-s3: + if: ${{ inputs.package == 'polkadot-omni-node' && inputs.target == 'x86_64-unknown-linux-gnu' }} + needs: [build-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: ${{ inputs.package }} + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-polkadot-macos-artifacts-to-s3: + if: ${{ inputs.package == 'polkadot' && inputs.target == 'aarch64-apple-darwin' }} + # TODO: add and use a `build-polkadot-homebrew-package` which packs all `polkadot` binaries: + # `polkadot`, `polkadot-prepare-worker` and `polkadot-execute-worker`. + needs: [build-macos-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: ${{ inputs.package }} + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-polkadot-prepare-worker-macos-artifacts-to-s3: + if: ${{ inputs.package == 'polkadot' && inputs.target == 'aarch64-apple-darwin' }} + needs: [build-macos-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: polkadot-prepare-worker + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-polkadot-execute-worker-macos-artifacts-to-s3: + if: ${{ inputs.package == 'polkadot' && inputs.target == 'aarch64-apple-darwin' }} + needs: [build-macos-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: polkadot-execute-worker + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-polkadot-omni-node-macos-artifacts-to-s3: + if: ${{ inputs.package == 'polkadot-omni-node' && inputs.target == 'aarch64-apple-darwin' }} + needs: [build-macos-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: ${{ inputs.package }} + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-polkadot-parachain-macos-artifacts-to-s3: + if: ${{ inputs.package == 'polkadot-parachain-bin' && inputs.target == 'aarch64-apple-darwin' }} + needs: [build-macos-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: polkadot-parachain + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} secrets: AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} diff --git a/.github/workflows/release-reusable-s3-upload.yml b/.github/workflows/release-reusable-s3-upload.yml index 6776b78da8e6..f85466bc8c07 100644 --- a/.github/workflows/release-reusable-s3-upload.yml +++ b/.github/workflows/release-reusable-s3-upload.yml @@ -13,6 +13,11 @@ on: required: true type: string + target: + description: Target triple for which the artifacts are being uploaded (e.g aarch64-apple-darwin) + required: true + type: string + secrets: AWS_DEFAULT_REGION: required: true @@ -34,12 +39,20 @@ jobs: - name: Checkout uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - - name: Download artifacts + - name: Download amd64 artifacts + if: ${{ inputs.target == 'x86_64-unknown-linux-gnu' }} uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: name: ${{ inputs.package }} path: artifacts/${{ inputs.package }} + - name: Download arm artifacts + if: ${{ inputs.target == 'aarch64-apple-darwin' }} + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: ${{ inputs.package }}_aarch64-apple-darwin + path: artifacts/${{ inputs.package }} + - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 with: @@ -50,4 +63,4 @@ jobs: - name: Upload ${{ inputs.package }} artifacts to s3 run: | . ./.github/scripts/release/release_lib.sh - upload_s3_release ${{ inputs.package }} ${{ inputs.release_tag }} + upload_s3_release ${{ inputs.package }} ${{ inputs.release_tag }} ${{ inputs.target }} From bf20a9ee18f7215210bbbabf79e955c8c35b3360 Mon Sep 17 00:00:00 2001 From: Ankan <10196091+Ank4n@users.noreply.github.com> Date: Thu, 21 Nov 2024 21:04:47 +0700 Subject: [PATCH 014/340] [Fix|NominationPools] Only allow apply slash to be executed if the slash amount is atleast ED (#6540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change prevents `pools::apply_slash` from being executed when the pending slash amount of the member is lower than the ED. The issue came to light with the failing [benchmark test](https://github.com/polkadot-fellows/runtimes/actions/runs/11879471717/job/33101445269?pr=490#step:11:765) in Kusama. The problem arises from the inexact conversion between points and balance. Specifically, when points are converted to balance and then back to points, rounding can introduce a small discrepancy between the input and the resulting value. This issue surfaced in Kusama due to its ED being different from Westend and Polkadot (1 UNIT/300), making the rounding issue noticeable. This fix is also significant because applying a slash is feeless and permissionless. Allowing super small slash amounts to be applied without a fee is undesirable. With this change, such small slashes will still be applied but only when member funds are withdrawn. --------- Co-authored-by: Dónal Murray --- prdoc/pr_6540.prdoc | 16 ++++++++ .../nomination-pools/runtime-api/src/lib.rs | 3 ++ substrate/frame/nomination-pools/src/lib.rs | 23 +++++++++--- .../test-delegate-stake/Cargo.toml | 2 +- .../test-delegate-stake/src/lib.rs | 37 +++++++++++++++++-- 5 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 prdoc/pr_6540.prdoc diff --git a/prdoc/pr_6540.prdoc b/prdoc/pr_6540.prdoc new file mode 100644 index 000000000000..5e0305205521 --- /dev/null +++ b/prdoc/pr_6540.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Only allow apply slash to be executed if the slash amount is atleast ED + +doc: + - audience: Runtime User + description: | + This change prevents `pools::apply_slash` from being executed when the pending slash amount of the member is lower + than the ED. With this change, such small slashes will still be applied but only when member funds are withdrawn. + +crates: +- name: pallet-nomination-pools-runtime-api + bump: patch +- name: pallet-nomination-pools + bump: major diff --git a/substrate/frame/nomination-pools/runtime-api/src/lib.rs b/substrate/frame/nomination-pools/runtime-api/src/lib.rs index 4138dd22d898..644ee07fd634 100644 --- a/substrate/frame/nomination-pools/runtime-api/src/lib.rs +++ b/substrate/frame/nomination-pools/runtime-api/src/lib.rs @@ -43,6 +43,9 @@ sp_api::decl_runtime_apis! { fn pool_pending_slash(pool_id: PoolId) -> Balance; /// Returns the pending slash for a given pool member. + /// + /// If pending slash of the member exceeds `ExistentialDeposit`, it can be reported on + /// chain. fn member_pending_slash(member: AccountId) -> Balance; /// Returns true if the pool with `pool_id` needs migration. diff --git a/substrate/frame/nomination-pools/src/lib.rs b/substrate/frame/nomination-pools/src/lib.rs index 201b0af1d608..dc82bf3a37c6 100644 --- a/substrate/frame/nomination-pools/src/lib.rs +++ b/substrate/frame/nomination-pools/src/lib.rs @@ -1944,6 +1944,8 @@ pub mod pallet { NothingToAdjust, /// No slash pending that can be applied to the member. NothingToSlash, + /// The slash amount is too low to be applied. + SlashTooLow, /// The pool or member delegation has already migrated to delegate stake. AlreadyMigrated, /// The pool or member delegation has not migrated yet to delegate stake. @@ -2300,7 +2302,7 @@ pub mod pallet { let slash_weight = // apply slash if any before withdraw. - match Self::do_apply_slash(&member_account, None) { + match Self::do_apply_slash(&member_account, None, false) { Ok(_) => T::WeightInfo::apply_slash(), Err(e) => { let no_pending_slash: DispatchResult = Err(Error::::NothingToSlash.into()); @@ -2974,8 +2976,10 @@ pub mod pallet { /// Fails unless [`crate::pallet::Config::StakeAdapter`] is of strategy type: /// [`adapter::StakeStrategyType::Delegate`]. /// - /// This call can be dispatched permissionlessly (i.e. by any account). If the member has - /// slash to be applied, caller may be rewarded with the part of the slash. + /// The pending slash amount of the member must be equal or more than `ExistentialDeposit`. + /// This call can be dispatched permissionlessly (i.e. by any account). If the execution + /// is successful, fee is refunded and caller may be rewarded with a part of the slash + /// based on the [`crate::pallet::Config::StakeAdapter`] configuration. #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::apply_slash())] pub fn apply_slash( @@ -2989,7 +2993,7 @@ pub mod pallet { let who = ensure_signed(origin)?; let member_account = T::Lookup::lookup(member_account)?; - Self::do_apply_slash(&member_account, Some(who))?; + Self::do_apply_slash(&member_account, Some(who), true)?; // If successful, refund the fees. Ok(Pays::No.into()) @@ -3574,15 +3578,21 @@ impl Pallet { fn do_apply_slash( member_account: &T::AccountId, reporter: Option, + enforce_min_slash: bool, ) -> DispatchResult { let member = PoolMembers::::get(member_account).ok_or(Error::::PoolMemberNotFound)?; let pending_slash = Self::member_pending_slash(Member::from(member_account.clone()), member.clone())?; - // if nothing to slash, return error. + // ensure there is something to slash. ensure!(!pending_slash.is_zero(), Error::::NothingToSlash); + if enforce_min_slash { + // ensure slashed amount is at least the minimum balance. + ensure!(pending_slash >= T::Currency::minimum_balance(), Error::::SlashTooLow); + } + T::StakeAdapter::member_slash( Member::from(member_account.clone()), Pool::from(Pallet::::generate_bonded_account(member.pool_id)), @@ -3946,6 +3956,9 @@ impl Pallet { /// Returns the unapplied slash of a member. /// /// Pending slash is only applicable with [`adapter::DelegateStake`] strategy. + /// + /// If pending slash of the member exceeds `ExistentialDeposit`, it can be reported on + /// chain via [`Call::apply_slash`]. pub fn api_member_pending_slash(who: T::AccountId) -> BalanceOf { PoolMembers::::get(who.clone()) .map(|pool_member| { diff --git a/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml index 7940caaff775..70e1591409b8 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml +++ b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml @@ -26,7 +26,7 @@ sp-staking = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } +frame-support = { features = ["experimental"], workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs index 40025cdbb3cd..cc6335959ab7 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs @@ -20,7 +20,7 @@ mod mock; use frame_support::{ - assert_noop, assert_ok, + assert_noop, assert_ok, hypothetically, traits::{fungible::InspectHold, Currency}, }; use mock::*; @@ -537,10 +537,10 @@ fn pool_slash_proportional() { // a typical example where 3 pool members unbond in era 99, 100, and 101, and a slash that // happened in era 100 should only affect the latter two. new_test_ext().execute_with(|| { - ExistentialDeposit::set(1); + ExistentialDeposit::set(2); BondingDuration::set(28); - assert_eq!(Balances::minimum_balance(), 1); - assert_eq!(CurrentEra::::get(), None); + assert_eq!(Balances::minimum_balance(), 2); + assert_eq!(Staking::current_era(), None); // create the pool, we know this has id 1. assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); @@ -670,6 +670,34 @@ fn pool_slash_proportional() { // no pending slash yet. assert_eq!(Pools::api_pool_pending_slash(1), 0); + // and therefore applying slash fails + assert_noop!( + Pools::apply_slash(RuntimeOrigin::signed(10), 21), + PoolsError::::NothingToSlash + ); + + hypothetically!({ + // a very small amount is slashed + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 3, + &mut Default::default(), + &mut Default::default(), + 100, + ); + + // ensure correct amount is pending to be slashed + assert_eq!(Pools::api_pool_pending_slash(1), 3); + + // 21 has pending slash lower than ED (2) + assert_eq!(Pools::api_member_pending_slash(21), 1); + + // slash fails as minimum pending slash amount not met. + assert_noop!( + Pools::apply_slash(RuntimeOrigin::signed(10), 21), + PoolsError::::SlashTooLow + ); + }); pallet_staking::slashing::do_slash::( &POOL1_BONDED, @@ -909,6 +937,7 @@ fn pool_slash_non_proportional_bonded_pool_and_chunks() { ); }); } + #[test] fn pool_migration_e2e() { new_test_ext().execute_with(|| { From 6d59c3b1417cd07ae8d502236e8a98c3498f76b1 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:19:14 +0200 Subject: [PATCH 015/340] parachain-template-node: add properties for dev chain-spec (#6560) # Description Reused as before the `properties` variable when defining a development chain spec for parachain-template-node. ## Integration N/A ## Review Notes One line change, pretty self explanatory (it got lost within the history of changes over the parachain-template-node/chain_spec.rs file). To be honest, not really sure how useful it is, but I had the choice of removing the `properties` var or reuse it as before, and I went with the latter. Signed-off-by: Iulian Barbu --- templates/parachain/node/src/chain_spec.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/parachain/node/src/chain_spec.rs b/templates/parachain/node/src/chain_spec.rs index 55a099dd022b..7ae3c4900e42 100644 --- a/templates/parachain/node/src/chain_spec.rs +++ b/templates/parachain/node/src/chain_spec.rs @@ -45,6 +45,7 @@ pub fn development_chain_spec() -> ChainSpec { .with_id("dev") .with_chain_type(ChainType::Development) .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) + .with_properties(properties) .build() } From d8ce550256f08c42d1ba6630990ed7e2e9ce0352 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Thu, 21 Nov 2024 21:44:42 +0100 Subject: [PATCH 016/340] [pallet-revive] Support all eth tx types (#6461) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for all eth tx types Note that js libs will continue to use the Legacy type since we don't include base_fee_per_gas yet in the block. We can think about setting these values after we revisit how we encode the gas into weight & deposit_limit in a follow up PR --------- Co-authored-by: Alexander Theißen Co-authored-by: GitHub Action --- prdoc/pr_6461.prdoc | 12 + substrate/frame/revive/rpc/src/client.rs | 24 +- substrate/frame/revive/rpc/src/example.rs | 9 +- substrate/frame/revive/rpc/src/lib.rs | 57 +-- substrate/frame/revive/src/evm/api.rs | 2 - substrate/frame/revive/src/evm/api/account.rs | 28 +- .../frame/revive/src/evm/api/rlp_codec.rs | 471 ++++++++++++++++-- .../frame/revive/src/evm/api/rpc_types.rs | 140 +++++- .../frame/revive/src/evm/api/rpc_types_gen.rs | 18 +- .../frame/revive/src/evm/api/signature.rs | 190 +++++-- substrate/frame/revive/src/evm/api/type_id.rs | 22 +- substrate/frame/revive/src/evm/runtime.rs | 39 +- substrate/frame/revive/src/lib.rs | 2 +- 13 files changed, 824 insertions(+), 190 deletions(-) create mode 100644 prdoc/pr_6461.prdoc diff --git a/prdoc/pr_6461.prdoc b/prdoc/pr_6461.prdoc new file mode 100644 index 000000000000..1b3d1e8b0364 --- /dev/null +++ b/prdoc/pr_6461.prdoc @@ -0,0 +1,12 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json +title: '[pallet-revive] add support for all eth tx types' +doc: +- audience: Runtime Dev + description: Add support for 1559, 4844, and 2930 transaction types +crates: +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor + diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 1ca1a6d37c53..d37f1d760065 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -17,13 +17,12 @@ //! The client connects to the source substrate chain //! and is used by the rpc server to query and send transactions to the substrate chain. use crate::{ - rlp, runtime::GAS_PRICE, subxt_client::{ revive::{calls::types::EthTransact, events::ContractEmitted}, runtime_types::pallet_revive::storage::ContractInfo, }, - TransactionLegacySigned, LOG_TARGET, + LOG_TARGET, }; use futures::{stream, StreamExt}; use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned}; @@ -269,25 +268,26 @@ impl ClientInner { let extrinsics = extrinsics.iter().flat_map(|ext| { let call = ext.as_extrinsic::().ok()??; let transaction_hash = H256(keccak_256(&call.payload)); - let tx = rlp::decode::(&call.payload).ok()?; - let from = tx.recover_eth_address().ok()?; - let contract_address = if tx.transaction_legacy_unsigned.to.is_none() { - Some(create1(&from, tx.transaction_legacy_unsigned.nonce.try_into().ok()?)) + let signed_tx = TransactionSigned::decode(&call.payload).ok()?; + let from = signed_tx.recover_eth_address().ok()?; + let tx_info = GenericTransaction::from_signed(signed_tx.clone(), Some(from)); + let contract_address = if tx_info.to.is_none() { + Some(create1(&from, tx_info.nonce.unwrap_or_default().try_into().ok()?)) } else { None }; - Some((from, tx, transaction_hash, contract_address, ext)) + Some((from, signed_tx, tx_info, transaction_hash, contract_address, ext)) }); // Map each extrinsic to a receipt stream::iter(extrinsics) - .map(|(from, tx, transaction_hash, contract_address, ext)| async move { + .map(|(from, signed_tx, tx_info, transaction_hash, contract_address, ext)| async move { let events = ext.events().await?; let tx_fees = events.find_first::()?.ok_or(ClientError::TxFeeNotFound)?; - let gas_price = tx.transaction_legacy_unsigned.gas_price; + let gas_price = tx_info.gas_price.unwrap_or_default(); let gas_used = (tx_fees.tip.saturating_add(tx_fees.actual_fee)) .checked_div(gas_price.as_u128()) .unwrap_or_default(); @@ -324,16 +324,16 @@ impl ClientInner { contract_address, from, logs, - tx.transaction_legacy_unsigned.to, + tx_info.to, gas_price, gas_used.into(), success, transaction_hash, transaction_index.into(), - tx.transaction_legacy_unsigned.r#type.as_byte() + tx_info.r#type.unwrap_or_default() ); - Ok::<_, ClientError>((receipt.transaction_hash, (tx.into(), receipt))) + Ok::<_, ClientError>((receipt.transaction_hash, (signed_tx, receipt))) }) .buffer_unordered(10) .collect::>>() diff --git a/substrate/frame/revive/rpc/src/example.rs b/substrate/frame/revive/rpc/src/example.rs index 20f00465b146..3b9a33296ef4 100644 --- a/substrate/frame/revive/rpc/src/example.rs +++ b/substrate/frame/revive/rpc/src/example.rs @@ -20,8 +20,7 @@ use crate::{EthRpcClient, ReceiptInfo}; use anyhow::Context; use pallet_revive::evm::{ - rlp::*, Account, BlockTag, Bytes, GenericTransaction, TransactionLegacyUnsigned, H160, H256, - U256, + Account, BlockTag, Bytes, GenericTransaction, TransactionLegacyUnsigned, H160, H256, U256, }; /// Wait for a transaction receipt. @@ -169,11 +168,11 @@ impl TransactionBuilder { mutate(&mut unsigned_tx); - let tx = signer.sign_transaction(unsigned_tx.clone()); - let bytes = tx.rlp_bytes().to_vec(); + let tx = signer.sign_transaction(unsigned_tx.into()); + let bytes = tx.signed_payload(); let hash = client - .send_raw_transaction(bytes.clone().into()) + .send_raw_transaction(bytes.into()) .await .with_context(|| "transaction failed")?; diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 8d9d6fab829e..6a324e63a857 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -137,7 +137,7 @@ impl EthRpcServer for EthRpcServerImpl { async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult { let hash = H256(keccak_256(&transaction.0)); - let tx = rlp::decode::(&transaction.0).map_err(|err| { + let tx = TransactionSigned::decode(&transaction.0).map_err(|err| { log::debug!(target: LOG_TARGET, "Failed to decode transaction: {err:?}"); EthRpcError::from(err) })?; @@ -147,21 +147,10 @@ impl EthRpcServer for EthRpcServerImpl { EthRpcError::InvalidSignature })?; + let tx = GenericTransaction::from_signed(tx, Some(eth_addr)); + // Dry run the transaction to get the weight limit and storage deposit limit - let TransactionLegacyUnsigned { to, input, value, .. } = tx.transaction_legacy_unsigned; - let dry_run = self - .client - .dry_run( - &GenericTransaction { - from: Some(eth_addr), - input: Some(input.clone()), - to, - value: Some(value), - ..Default::default() - }, - BlockTag::Latest.into(), - ) - .await?; + let dry_run = self.client.dry_run(&tx, BlockTag::Latest.into()).await?; let EthContractResult { gas_required, storage_deposit, .. } = dry_run; let call = subxt_client::tx().revive().eth_transact( @@ -174,11 +163,10 @@ impl EthRpcServer for EthRpcServerImpl { Ok(hash) } - async fn send_transaction(&self, transaction: GenericTransaction) -> RpcResult { + async fn send_transaction(&self, mut transaction: GenericTransaction) -> RpcResult { log::debug!(target: LOG_TARGET, "{transaction:#?}"); - let GenericTransaction { from, gas, gas_price, input, to, value, r#type, .. } = transaction; - let Some(from) = from else { + let Some(from) = transaction.from else { log::debug!(target: LOG_TARGET, "Transaction must have a sender"); return Err(EthRpcError::InvalidTransaction.into()); }; @@ -189,27 +177,26 @@ impl EthRpcServer for EthRpcServerImpl { .find(|account| account.address() == from) .ok_or(EthRpcError::AccountNotFound(from))?; - let gas_price = gas_price.unwrap_or_else(|| U256::from(GAS_PRICE)); - let chain_id = Some(self.client.chain_id().into()); - let input = input.unwrap_or_default(); - let value = value.unwrap_or_default(); - let r#type = r#type.unwrap_or_default(); + if transaction.gas.is_none() { + transaction.gas = Some(self.estimate_gas(transaction.clone(), None).await?); + } - let Some(gas) = gas else { - log::debug!(target: LOG_TARGET, "Transaction must have a gas limit"); - return Err(EthRpcError::InvalidTransaction.into()); - }; + if transaction.gas_price.is_none() { + transaction.gas_price = Some(self.gas_price().await?); + } - let r#type = Type0::try_from_byte(r#type.clone()) - .map_err(|_| EthRpcError::TransactionTypeNotSupported(r#type))?; + if transaction.nonce.is_none() { + transaction.nonce = + Some(self.get_transaction_count(from, BlockTag::Latest.into()).await?); + } - let nonce = self.get_transaction_count(from, BlockTag::Latest.into()).await?; + if transaction.chain_id.is_none() { + transaction.chain_id = Some(self.chain_id().await?); + } - let tx = - TransactionLegacyUnsigned { chain_id, gas, gas_price, input, nonce, to, value, r#type }; - let tx = account.sign_transaction(tx); - let rlp_bytes = rlp::encode(&tx).to_vec(); - self.send_raw_transaction(Bytes(rlp_bytes)).await + let tx = transaction.try_into_unsigned().map_err(|_| EthRpcError::InvalidTransaction)?; + let payload = account.sign_transaction(tx).signed_payload(); + self.send_raw_transaction(Bytes(payload)).await } async fn get_block_by_hash( diff --git a/substrate/frame/revive/src/evm/api.rs b/substrate/frame/revive/src/evm/api.rs index 8185a2c8f6f8..fe18c8735bed 100644 --- a/substrate/frame/revive/src/evm/api.rs +++ b/substrate/frame/revive/src/evm/api.rs @@ -25,9 +25,7 @@ pub use rlp; mod type_id; pub use type_id::*; -#[cfg(feature = "std")] mod rpc_types; - mod rpc_types_gen; pub use rpc_types_gen::*; diff --git a/substrate/frame/revive/src/evm/api/account.rs b/substrate/frame/revive/src/evm/api/account.rs index 8365ebf83cae..ba1c68ea0cf7 100644 --- a/substrate/frame/revive/src/evm/api/account.rs +++ b/substrate/frame/revive/src/evm/api/account.rs @@ -16,10 +16,9 @@ // limitations under the License. //! Utilities for working with Ethereum accounts. use crate::{ - evm::{TransactionLegacySigned, TransactionLegacyUnsigned}, + evm::{TransactionSigned, TransactionUnsigned}, H160, }; -use rlp::Encodable; use sp_runtime::AccountId32; /// A simple account that can sign transactions @@ -38,6 +37,11 @@ impl From for Account { } impl Account { + /// Create a new account from a secret + pub fn from_secret_key(secret_key: [u8; 32]) -> Self { + subxt_signer::eth::Keypair::from_secret_key(secret_key).unwrap().into() + } + /// Get the [`H160`] address of the account. pub fn address(&self) -> H160 { H160::from_slice(&self.0.public_key().to_account_id().as_ref()) @@ -52,9 +56,21 @@ impl Account { } /// Sign a transaction. - pub fn sign_transaction(&self, tx: TransactionLegacyUnsigned) -> TransactionLegacySigned { - let rlp_encoded = tx.rlp_bytes(); - let signature = self.0.sign(&rlp_encoded); - TransactionLegacySigned::from(tx, signature.as_ref()) + pub fn sign_transaction(&self, tx: TransactionUnsigned) -> TransactionSigned { + let payload = tx.unsigned_payload(); + let signature = self.0.sign(&payload).0; + tx.with_signature(signature) } } + +#[test] +fn from_secret_key_works() { + let account = Account::from_secret_key(hex_literal::hex!( + "a872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f" + )); + + assert_eq!( + account.address(), + H160::from(hex_literal::hex!("75e480db528101a381ce68544611c169ad7eb342")) + ) +} diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs index e5f24c28a482..3442ed73acca 100644 --- a/substrate/frame/revive/src/evm/api/rlp_codec.rs +++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs @@ -21,6 +21,73 @@ use super::*; use alloc::vec::Vec; use rlp::{Decodable, Encodable}; +impl TransactionUnsigned { + /// Return the bytes to be signed by the private key. + pub fn unsigned_payload(&self) -> Vec { + use TransactionUnsigned::*; + let mut s = rlp::RlpStream::new(); + match self { + Transaction2930Unsigned(ref tx) => { + s.append(&tx.r#type.value()); + s.append(tx); + }, + Transaction1559Unsigned(ref tx) => { + s.append(&tx.r#type.value()); + s.append(tx); + }, + Transaction4844Unsigned(ref tx) => { + s.append(&tx.r#type.value()); + s.append(tx); + }, + TransactionLegacyUnsigned(ref tx) => { + s.append(tx); + }, + } + + s.out().to_vec() + } +} + +impl TransactionSigned { + /// Encode the Ethereum transaction into bytes. + pub fn signed_payload(&self) -> Vec { + use TransactionSigned::*; + let mut s = rlp::RlpStream::new(); + match self { + Transaction2930Signed(ref tx) => { + s.append(&tx.transaction_2930_unsigned.r#type.value()); + s.append(tx); + }, + Transaction1559Signed(ref tx) => { + s.append(&tx.transaction_1559_unsigned.r#type.value()); + s.append(tx); + }, + Transaction4844Signed(ref tx) => { + s.append(&tx.transaction_4844_unsigned.r#type.value()); + s.append(tx); + }, + TransactionLegacySigned(ref tx) => { + s.append(tx); + }, + } + + s.out().to_vec() + } + + /// Decode the Ethereum transaction from bytes. + pub fn decode(data: &[u8]) -> Result { + if data.len() < 1 { + return Err(rlp::DecoderError::RlpIsTooShort); + } + match data[0] { + TYPE_EIP2930 => rlp::decode::(&data[1..]).map(Into::into), + TYPE_EIP1559 => rlp::decode::(&data[1..]).map(Into::into), + TYPE_EIP4844 => rlp::decode::(&data[1..]).map(Into::into), + _ => rlp::decode::(data).map(Into::into), + } + } +} + impl TransactionLegacyUnsigned { /// Get the rlp encoded bytes of a signed transaction with a dummy 65 bytes signature. pub fn dummy_signed_payload(&self) -> Vec { @@ -47,8 +114,8 @@ impl Encodable for TransactionLegacyUnsigned { s.append(&self.value); s.append(&self.input.0); s.append(&chain_id); - s.append(&0_u8); - s.append(&0_u8); + s.append(&0u8); + s.append(&0u8); } else { s.begin_list(6); s.append(&self.nonce); @@ -64,7 +131,6 @@ impl Encodable for TransactionLegacyUnsigned { } } -/// See impl Decodable for TransactionLegacyUnsigned { fn decode(rlp: &rlp::Rlp) -> Result { Ok(TransactionLegacyUnsigned { @@ -95,16 +161,18 @@ impl Decodable for TransactionLegacyUnsigned { impl Encodable for TransactionLegacySigned { fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_legacy_unsigned; + s.begin_list(9); - s.append(&self.transaction_legacy_unsigned.nonce); - s.append(&self.transaction_legacy_unsigned.gas_price); - s.append(&self.transaction_legacy_unsigned.gas); - match self.transaction_legacy_unsigned.to { + s.append(&tx.nonce); + s.append(&tx.gas_price); + s.append(&tx.gas); + match tx.to { Some(ref to) => s.append(to), None => s.append_empty_data(), }; - s.append(&self.transaction_legacy_unsigned.value); - s.append(&self.transaction_legacy_unsigned.input.0); + s.append(&tx.value); + s.append(&tx.input.0); s.append(&self.v); s.append(&self.r); @@ -112,6 +180,232 @@ impl Encodable for TransactionLegacySigned { } } +impl Encodable for AccessListEntry { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(2); + s.append(&self.address); + s.append_list(&self.storage_keys); + } +} + +impl Decodable for AccessListEntry { + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(AccessListEntry { address: rlp.val_at(0)?, storage_keys: rlp.list_at(1)? }) + } +} + +/// See +impl Encodable for Transaction1559Unsigned { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(9); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas); + match self.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&self.value); + s.append(&self.input.0); + s.append_list(&self.access_list); + } +} + +/// See +impl Encodable for Transaction1559Signed { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_1559_unsigned; + s.begin_list(12); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.max_priority_fee_per_gas); + s.append(&tx.max_fee_per_gas); + s.append(&tx.gas); + match tx.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&tx.value); + s.append(&tx.input.0); + s.append_list(&tx.access_list); + + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } +} + +impl Decodable for Transaction1559Signed { + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Transaction1559Signed { + transaction_1559_unsigned: { + Transaction1559Unsigned { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + max_priority_fee_per_gas: rlp.val_at(2)?, + max_fee_per_gas: rlp.val_at(3)?, + gas: rlp.val_at(4)?, + to: { + let to = rlp.at(5)?; + if to.is_empty() { + None + } else { + Some(to.as_val()?) + } + }, + value: rlp.val_at(6)?, + input: Bytes(rlp.val_at(7)?), + access_list: rlp.list_at(8)?, + ..Default::default() + } + }, + y_parity: rlp.val_at(9)?, + r: rlp.val_at(10)?, + s: rlp.val_at(11)?, + ..Default::default() + }) + } +} + +//See https://eips.ethereum.org/EIPS/eip-2930 +impl Encodable for Transaction2930Unsigned { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(8); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.gas_price); + s.append(&self.gas); + match self.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&self.value); + s.append(&self.input.0); + s.append_list(&self.access_list); + } +} + +//See https://eips.ethereum.org/EIPS/eip-2930 +impl Encodable for Transaction2930Signed { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_2930_unsigned; + s.begin_list(11); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.gas_price); + s.append(&tx.gas); + match tx.to { + Some(ref to) => s.append(to), + None => s.append_empty_data(), + }; + s.append(&tx.value); + s.append(&tx.input.0); + s.append_list(&tx.access_list); + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } +} + +impl Decodable for Transaction2930Signed { + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Transaction2930Signed { + transaction_2930_unsigned: { + Transaction2930Unsigned { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + gas_price: rlp.val_at(2)?, + gas: rlp.val_at(3)?, + to: { + let to = rlp.at(4)?; + if to.is_empty() { + None + } else { + Some(to.as_val()?) + } + }, + value: rlp.val_at(5)?, + input: Bytes(rlp.val_at(6)?), + access_list: rlp.list_at(7)?, + ..Default::default() + } + }, + y_parity: rlp.val_at(8)?, + r: rlp.val_at(9)?, + s: rlp.val_at(10)?, + ..Default::default() + }) + } +} + +//See https://eips.ethereum.org/EIPS/eip-4844 +impl Encodable for Transaction4844Unsigned { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + s.begin_list(11); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas); + s.append(&self.to); + s.append(&self.value); + s.append(&self.input.0); + s.append_list(&self.access_list); + s.append(&self.max_fee_per_blob_gas); + s.append_list(&self.blob_versioned_hashes); + } +} + +//See https://eips.ethereum.org/EIPS/eip-4844 +impl Encodable for Transaction4844Signed { + fn rlp_append(&self, s: &mut rlp::RlpStream) { + let tx = &self.transaction_4844_unsigned; + s.begin_list(14); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.max_priority_fee_per_gas); + s.append(&tx.max_fee_per_gas); + s.append(&tx.gas); + s.append(&tx.to); + s.append(&tx.value); + s.append(&tx.input.0); + s.append_list(&tx.access_list); + s.append(&tx.max_fee_per_blob_gas); + s.append_list(&tx.blob_versioned_hashes); + s.append(&self.y_parity); + s.append(&self.r); + s.append(&self.s); + } +} + +impl Decodable for Transaction4844Signed { + fn decode(rlp: &rlp::Rlp) -> Result { + Ok(Transaction4844Signed { + transaction_4844_unsigned: { + Transaction4844Unsigned { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + max_priority_fee_per_gas: rlp.val_at(2)?, + max_fee_per_gas: rlp.val_at(3)?, + gas: rlp.val_at(4)?, + to: rlp.val_at(5)?, + value: rlp.val_at(6)?, + input: Bytes(rlp.val_at(7)?), + access_list: rlp.list_at(8)?, + max_fee_per_blob_gas: rlp.val_at(9)?, + blob_versioned_hashes: rlp.list_at(10)?, + ..Default::default() + } + }, + y_parity: rlp.val_at(11)?, + r: rlp.val_at(12)?, + s: rlp.val_at(13)?, + }) + } +} + /// See impl Decodable for TransactionLegacySigned { fn decode(rlp: &rlp::Rlp) -> Result { @@ -142,7 +436,7 @@ impl Decodable for TransactionLegacySigned { value: rlp.val_at(4)?, input: Bytes(rlp.val_at(5)?), chain_id: extract_chain_id(v).map(|v| v.into()), - r#type: Type0 {}, + r#type: TypeLegacy {}, } }, v, @@ -157,26 +451,118 @@ mod test { use super::*; #[test] - fn encode_decode_legacy_transaction_works() { - let tx = TransactionLegacyUnsigned { - chain_id: Some(596.into()), - gas: U256::from(21000), - nonce: U256::from(1), - gas_price: U256::from("0x640000006a"), - to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()), - value: U256::from(123123), - input: Bytes(vec![]), - r#type: Type0, - }; + fn encode_decode_tx_works() { + let txs = [ + // Legacy + ( + "f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808025a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8", + r#" + { + "chainId": "0x1", + "gas": "0x1e241", + "gasPrice": "0x0", + "input": "0x", + "nonce": "0x0", + "to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "type": "0x0", + "value": "0x0", + "r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0", + "s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8", + "v": "0x25" + } + "# + ), + // type 1: EIP2930 + ( + "01f89b0180808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8", + r#" + { + "accessList": [ + { + "address": "0x0000000000000000000000000000000000000001", + "storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"] + } + ], + "chainId": "0x1", + "gas": "0x1e241", + "gasPrice": "0x0", + "input": "0x", + "nonce": "0x0", + "to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "type": "0x1", + "value": "0x0", + "r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0", + "s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8", + "yParity": "0x0" + } + "# + ), + // type 2: EIP1559 + ( + "02f89c018080018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8", + r#" + { + "accessList": [ + { + "address": "0x0000000000000000000000000000000000000001", + "storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"] + } + ], + "chainId": "0x1", + "gas": "0x1e241", + "gasPrice": "0x0", + "input": "0x", + "maxFeePerGas": "0x1", + "maxPriorityFeePerGas": "0x0", + "nonce": "0x0", + "to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "type": "0x2", + "value": "0x0", + "r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0", + "s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8", + "yParity": "0x0" - let rlp_bytes = rlp::encode(&tx); - let decoded = rlp::decode::(&rlp_bytes).unwrap(); - assert_eq!(&tx, &decoded); + } + "# + ), + // type 3: EIP4844 + ( - let tx = Account::default().sign_transaction(tx); - let rlp_bytes = rlp::encode(&tx); - let decoded = rlp::decode::(&rlp_bytes).unwrap(); - assert_eq!(&tx, &decoded); + "03f8bf018002018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8", + r#" + { + "accessList": [ + { + "address": "0x0000000000000000000000000000000000000001", + "storageKeys": ["0x0000000000000000000000000000000000000000000000000000000000000000"] + } + ], + "blobVersionedHashes": ["0x0000000000000000000000000000000000000000000000000000000000000000"], + "chainId": "0x1", + "gas": "0x1e241", + "input": "0x", + "maxFeePerBlobGas": "0x0", + "maxFeePerGas": "0x1", + "maxPriorityFeePerGas": "0x2", + "nonce": "0x0", + "to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", + "type": "0x3", + "value": "0x0", + "r": "0xfe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0", + "s": "0x6de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8", + "yParity": "0x0" + } + "# + ) + ]; + + for (tx, json) in txs { + let raw_tx = hex::decode(tx).unwrap(); + let tx = TransactionSigned::decode(&raw_tx).unwrap(); + assert_eq!(tx.signed_payload(), raw_tx); + let expected_tx = serde_json::from_str(json).unwrap(); + assert_eq!(tx, expected_tx); + } } #[test] @@ -189,31 +575,12 @@ mod test { to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()), value: U256::from(123123), input: Bytes(vec![]), - r#type: Type0, + r#type: TypeLegacy, }; - let signed_tx = Account::default().sign_transaction(tx.clone()); - let rlp_bytes = rlp::encode(&signed_tx); - assert_eq!(tx.dummy_signed_payload().len(), rlp_bytes.len()); - } - - #[test] - fn recover_address_works() { - let account = Account::default(); - - let unsigned_tx = TransactionLegacyUnsigned { - value: 200_000_000_000_000_000_000u128.into(), - gas_price: 100_000_000_200u64.into(), - gas: 100_107u32.into(), - nonce: 3.into(), - to: Some(Account::from(subxt_signer::eth::dev::baltathar()).address()), - chain_id: Some(596.into()), - ..Default::default() - }; - - let tx = account.sign_transaction(unsigned_tx.clone()); - let recovered_address = tx.recover_eth_address().unwrap(); - - assert_eq!(account.address(), recovered_address); + let dummy_signed_payload = tx.dummy_signed_payload(); + let tx: TransactionUnsigned = tx.into(); + let payload = Account::default().sign_transaction(tx).signed_payload(); + assert_eq!(dummy_signed_payload.len(), payload.len()); } } diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index dd1a2642724d..1cf8d984b68b 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -16,7 +16,8 @@ // limitations under the License. //! Utility impl for the RPC types. use super::*; -use sp_core::U256; +use alloc::vec::Vec; +use sp_core::{H160, U256}; impl TransactionInfo { /// Create a new [`TransactionInfo`] from a receipt and a signed transaction. @@ -138,3 +139,140 @@ fn logs_bloom_works() { .unwrap(); assert_eq!(receipt.logs_bloom, ReceiptInfo::logs_bloom(&receipt.logs)); } + +impl GenericTransaction { + /// Create a new [`GenericTransaction`] from a signed transaction. + pub fn from_signed(tx: TransactionSigned, from: Option) -> Self { + use TransactionSigned::*; + match tx { + TransactionLegacySigned(tx) => { + let tx = tx.transaction_legacy_unsigned; + GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: tx.chain_id, + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some(tx.gas_price), + ..Default::default() + } + }, + Transaction4844Signed(tx) => { + let tx = tx.transaction_4844_unsigned; + GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: Some(tx.to), + gas: Some(tx.gas), + gas_price: Some(tx.max_fee_per_blob_gas), + access_list: Some(tx.access_list), + blob_versioned_hashes: Some(tx.blob_versioned_hashes), + max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), + max_fee_per_gas: Some(tx.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), + ..Default::default() + } + }, + Transaction1559Signed(tx) => { + let tx = tx.transaction_1559_unsigned; + GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some(tx.gas_price), + access_list: Some(tx.access_list), + max_fee_per_gas: Some(tx.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), + ..Default::default() + } + }, + Transaction2930Signed(tx) => { + let tx = tx.transaction_2930_unsigned; + GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some(tx.gas_price), + access_list: Some(tx.access_list), + ..Default::default() + } + }, + } + } + + /// Convert to a [`TransactionUnsigned`]. + pub fn try_into_unsigned(self) -> Result { + match self.r#type.unwrap_or_default().0 { + TYPE_LEGACY => Ok(TransactionLegacyUnsigned { + r#type: TypeLegacy {}, + chain_id: self.chain_id, + input: self.input.unwrap_or_default(), + nonce: self.nonce.unwrap_or_default(), + value: self.value.unwrap_or_default(), + to: self.to, + gas: self.gas.unwrap_or_default(), + gas_price: self.gas_price.unwrap_or_default(), + } + .into()), + TYPE_EIP1559 => Ok(Transaction1559Unsigned { + r#type: TypeEip1559 {}, + chain_id: self.chain_id.unwrap_or_default(), + input: self.input.unwrap_or_default(), + nonce: self.nonce.unwrap_or_default(), + value: self.value.unwrap_or_default(), + to: self.to, + gas: self.gas.unwrap_or_default(), + gas_price: self.gas_price.unwrap_or_default(), + access_list: self.access_list.unwrap_or_default(), + max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(), + } + .into()), + TYPE_EIP2930 => Ok(Transaction2930Unsigned { + r#type: TypeEip2930 {}, + chain_id: self.chain_id.unwrap_or_default(), + input: self.input.unwrap_or_default(), + nonce: self.nonce.unwrap_or_default(), + value: self.value.unwrap_or_default(), + to: self.to, + gas: self.gas.unwrap_or_default(), + gas_price: self.gas_price.unwrap_or_default(), + access_list: self.access_list.unwrap_or_default(), + } + .into()), + TYPE_EIP4844 => Ok(Transaction4844Unsigned { + r#type: TypeEip4844 {}, + chain_id: self.chain_id.unwrap_or_default(), + input: self.input.unwrap_or_default(), + nonce: self.nonce.unwrap_or_default(), + value: self.value.unwrap_or_default(), + to: self.to.unwrap_or_default(), + gas: self.gas.unwrap_or_default(), + max_fee_per_gas: self.max_fee_per_gas.unwrap_or_default(), + max_fee_per_blob_gas: self.max_fee_per_blob_gas.unwrap_or_default(), + max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(), + access_list: self.access_list.unwrap_or_default(), + blob_versioned_hashes: self.blob_versioned_hashes.unwrap_or_default(), + } + .into()), + _ => Err(()), + } + } +} diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs index 48045a6acc86..5037ec05d881 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -17,7 +17,7 @@ //! Generated JSON-RPC types. #![allow(missing_docs)] -use super::{byte::*, Type0, Type1, Type2, Type3}; +use super::{byte::*, TypeEip1559, TypeEip2930, TypeEip4844, TypeLegacy}; use alloc::vec::Vec; use codec::{Decode, Encode}; use derive_more::{From, TryInto}; @@ -455,7 +455,7 @@ pub struct Transaction1559Unsigned { /// to address pub to: Option
, /// type - pub r#type: Type2, + pub r#type: TypeEip1559, /// value pub value: U256, } @@ -486,7 +486,7 @@ pub struct Transaction2930Unsigned { /// to address pub to: Option
, /// type - pub r#type: Type1, + pub r#type: TypeEip2930, /// value pub value: U256, } @@ -530,7 +530,7 @@ pub struct Transaction4844Unsigned { /// to address pub to: Address, /// type - pub r#type: Type3, + pub r#type: TypeEip4844, /// value pub value: U256, } @@ -557,7 +557,7 @@ pub struct TransactionLegacyUnsigned { /// to address pub to: Option
, /// type - pub r#type: Type0, + pub r#type: TypeLegacy, /// value pub value: U256, } @@ -622,8 +622,8 @@ pub struct Transaction1559Signed { pub v: Option, /// yParity /// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature. - #[serde(rename = "yParity", skip_serializing_if = "Option::is_none")] - pub y_parity: Option, + #[serde(rename = "yParity")] + pub y_parity: U256, } /// Signed 2930 Transaction @@ -661,8 +661,8 @@ pub struct Transaction4844Signed { pub s: U256, /// yParity /// The parity (0 for even, 1 for odd) of the y-value of the secp256k1 signature. - #[serde(rename = "yParity", skip_serializing_if = "Option::is_none")] - pub y_parity: Option, + #[serde(rename = "yParity")] + pub y_parity: U256, } /// Signed Legacy Transaction diff --git a/substrate/frame/revive/src/evm/api/signature.rs b/substrate/frame/revive/src/evm/api/signature.rs index 957d50c8e324..9f39b92b461e 100644 --- a/substrate/frame/revive/src/evm/api/signature.rs +++ b/substrate/frame/revive/src/evm/api/signature.rs @@ -15,49 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. //! Ethereum signature utilities -use super::{TransactionLegacySigned, TransactionLegacyUnsigned}; -use rlp::Encodable; +use super::*; use sp_core::{H160, U256}; use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; -impl TransactionLegacyUnsigned { - /// Recover the Ethereum address, from an RLP encoded transaction and a 65 bytes signature. - pub fn recover_eth_address(rlp_encoded: &[u8], signature: &[u8; 65]) -> Result { - let hash = keccak_256(rlp_encoded); - let mut addr = H160::default(); - let pk = secp256k1_ecdsa_recover(&signature, &hash).map_err(|_| ())?; - addr.assign_from_slice(&keccak_256(&pk[..])[12..]); - - Ok(addr) - } -} - impl TransactionLegacySigned { - /// Create a signed transaction from an [`TransactionLegacyUnsigned`] and a signature. - pub fn from( - transaction_legacy_unsigned: TransactionLegacyUnsigned, - signature: &[u8; 65], - ) -> TransactionLegacySigned { - let r = U256::from_big_endian(&signature[..32]); - let s = U256::from_big_endian(&signature[32..64]); - let recovery_id = signature[64] as u32; - let v = transaction_legacy_unsigned - .chain_id - .map(|chain_id| chain_id * 2 + 35 + recovery_id) - .unwrap_or_else(|| U256::from(27) + recovery_id); - - TransactionLegacySigned { transaction_legacy_unsigned, r, s, v } - } - - /// Get the raw 65 bytes signature from the signed transaction. - pub fn raw_signature(&self) -> Result<[u8; 65], ()> { - let mut s = [0u8; 65]; - self.r.write_as_big_endian(s[0..32].as_mut()); - self.s.write_as_big_endian(s[32..64].as_mut()); - s[64] = self.extract_recovery_id().ok_or(())?; - Ok(s) - } - /// Get the recovery ID from the signed transaction. /// See https://eips.ethereum.org/EIPS/eip-155 fn extract_recovery_id(&self) -> Option { @@ -71,10 +33,154 @@ impl TransactionLegacySigned { self.v.try_into().ok() } } +} + +impl TransactionUnsigned { + /// Extract the unsigned transaction from a signed transaction. + pub fn from_signed(tx: TransactionSigned) -> Self { + match tx { + TransactionSigned::TransactionLegacySigned(signed) => + Self::TransactionLegacyUnsigned(signed.transaction_legacy_unsigned), + TransactionSigned::Transaction4844Signed(signed) => + Self::Transaction4844Unsigned(signed.transaction_4844_unsigned), + TransactionSigned::Transaction1559Signed(signed) => + Self::Transaction1559Unsigned(signed.transaction_1559_unsigned), + TransactionSigned::Transaction2930Signed(signed) => + Self::Transaction2930Unsigned(signed.transaction_2930_unsigned), + } + } + + /// Create a signed transaction from an [`TransactionUnsigned`] and a signature. + pub fn with_signature(self, signature: [u8; 65]) -> TransactionSigned { + let r = U256::from_big_endian(&signature[..32]); + let s = U256::from_big_endian(&signature[32..64]); + let recovery_id = signature[64]; + + match self { + TransactionUnsigned::Transaction2930Unsigned(transaction_2930_unsigned) => + Transaction2930Signed { + transaction_2930_unsigned, + r, + s, + v: None, + y_parity: U256::from(recovery_id), + } + .into(), + TransactionUnsigned::Transaction1559Unsigned(transaction_1559_unsigned) => + Transaction1559Signed { + transaction_1559_unsigned, + r, + s, + v: None, + y_parity: U256::from(recovery_id), + } + .into(), + + TransactionUnsigned::Transaction4844Unsigned(transaction_4844_unsigned) => + Transaction4844Signed { + transaction_4844_unsigned, + r, + s, + y_parity: U256::from(recovery_id), + } + .into(), + + TransactionUnsigned::TransactionLegacyUnsigned(transaction_legacy_unsigned) => { + let v = transaction_legacy_unsigned + .chain_id + .map(|chain_id| { + chain_id + .saturating_mul(U256::from(2)) + .saturating_add(U256::from(35u32 + recovery_id as u32)) + }) + .unwrap_or_else(|| U256::from(27u32 + recovery_id as u32)); + + TransactionLegacySigned { transaction_legacy_unsigned, r, s, v }.into() + }, + } + } +} + +impl TransactionSigned { + /// Get the raw 65 bytes signature from the signed transaction. + pub fn raw_signature(&self) -> Result<[u8; 65], ()> { + use TransactionSigned::*; + let (r, s, v) = match self { + TransactionLegacySigned(tx) => (tx.r, tx.s, tx.extract_recovery_id().ok_or(())?), + Transaction4844Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?), + Transaction1559Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?), + Transaction2930Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?), + }; + let mut sig = [0u8; 65]; + r.write_as_big_endian(sig[0..32].as_mut()); + s.write_as_big_endian(sig[32..64].as_mut()); + sig[64] = v; + Ok(sig) + } - /// Recover the Ethereum address from the signed transaction. + /// Recover the Ethereum address, from a signed transaction. pub fn recover_eth_address(&self) -> Result { - let rlp_encoded = self.transaction_legacy_unsigned.rlp_bytes(); - TransactionLegacyUnsigned::recover_eth_address(&rlp_encoded, &self.raw_signature()?) + use TransactionSigned::*; + + let mut s = rlp::RlpStream::new(); + match self { + TransactionLegacySigned(tx) => { + let tx = &tx.transaction_legacy_unsigned; + s.append(tx); + }, + Transaction4844Signed(tx) => { + let tx = &tx.transaction_4844_unsigned; + s.append(&tx.r#type.value()); + s.append(tx); + }, + Transaction1559Signed(tx) => { + let tx = &tx.transaction_1559_unsigned; + s.append(&tx.r#type.value()); + s.append(tx); + }, + Transaction2930Signed(tx) => { + let tx = &tx.transaction_2930_unsigned; + s.append(&tx.r#type.value()); + s.append(tx); + }, + } + let bytes = s.out().to_vec(); + let signature = self.raw_signature()?; + + let hash = keccak_256(&bytes); + let mut addr = H160::default(); + let pk = secp256k1_ecdsa_recover(&signature, &hash).map_err(|_| ())?; + addr.assign_from_slice(&keccak_256(&pk[..])[12..]); + Ok(addr) + } +} + +#[test] +fn sign_and_recover_work() { + use crate::evm::TransactionUnsigned; + let txs = [ + // Legacy + "f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808026a07b2e762a17a71a46b422e60890a04512cf0d907ccf6b78b5bd6e6977efdc2bf5a01ea673d50bbe7c2236acb498ceb8346a8607c941f0b8cbcde7cf439aa9369f1f", + //// type 1: EIP2930 + "01f89b0180808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0c45a61b3d1d00169c649e7326e02857b850efb96e587db4b9aad29afc80d0752a070ae1eb47ab4097dbed2f19172ae286492621b46ac737ee6c32fb18a00c94c9c", + // type 2: EIP1559 + "02f89c018080018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a055d72bbc3047d4b9d3e4b8099f187143202407746118204cc2e0cb0c85a68baea04f6ef08a1418c70450f53398d9f0f2d78d9e9d6b8a80cba886b67132c4a744f2", + // type 3: EIP4844 + "03f8bf018002018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080e1a0000000000000000000000000000000000000000000000000000000000000000001a0672b8bac466e2cf1be3148c030988d40d582763ecebbc07700dfc93bb070d8a4a07c635887005b11cb58964c04669ac2857fa633aa66f662685dadfd8bcacb0f21", + ]; + let account = Account::from_secret_key(hex_literal::hex!( + "a872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f" + )); + + for tx in txs { + let raw_tx = hex::decode(tx).unwrap(); + let tx = TransactionSigned::decode(&raw_tx).unwrap(); + + let address = tx.recover_eth_address(); + assert_eq!(address.unwrap(), account.address()); + + let unsigned = TransactionUnsigned::from_signed(tx.clone()); + let signed = account.sign_transaction(unsigned); + assert_eq!(tx, signed); } } diff --git a/substrate/frame/revive/src/evm/api/type_id.rs b/substrate/frame/revive/src/evm/api/type_id.rs index 7434ca6e9b7f..c6e018a379b3 100644 --- a/substrate/frame/revive/src/evm/api/type_id.rs +++ b/substrate/frame/revive/src/evm/api/type_id.rs @@ -17,6 +17,7 @@ //! Ethereum Typed Transaction types use super::Byte; use codec::{Decode, Encode}; +use paste::paste; use rlp::Decodable; use scale_info::TypeInfo; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -29,8 +30,14 @@ macro_rules! transaction_type { #[derive(Clone, Default, Debug, Eq, PartialEq)] pub struct $name; + // upper case const name + paste! { + #[doc = concat!("Transaction value for type identifier: ", $value)] + pub const [<$name:snake:upper>]: u8 = $value; + } + impl $name { - /// Get the value of the type + /// Convert to u8 pub fn value(&self) -> u8 { $value } @@ -107,7 +114,12 @@ macro_rules! transaction_type { }; } -transaction_type!(Type0, 0); -transaction_type!(Type1, 1); -transaction_type!(Type2, 2); -transaction_type!(Type3, 3); +transaction_type!(TypeLegacy, 0); +transaction_type!(TypeEip2930, 1); +transaction_type!(TypeEip1559, 2); +transaction_type!(TypeEip4844, 3); + +#[test] +fn transaction_type() { + assert_eq!(TYPE_EIP2930, 1u8); +} diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 21294fdf6baa..40c210304ca2 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -16,7 +16,7 @@ // limitations under the License. //! Runtime types for integrating `pallet-revive` with the EVM. use crate::{ - evm::api::{TransactionLegacySigned, TransactionLegacyUnsigned}, + evm::api::{GenericTransaction, TransactionSigned}, AccountIdOf, AddressMapper, BalanceOf, MomentOf, Weight, LOG_TARGET, }; use codec::{Decode, Encode}; @@ -293,7 +293,7 @@ pub trait EthExtra { CallOf: From>, ::Hash: frame_support::traits::IsType, { - let tx = rlp::decode::(&payload).map_err(|err| { + let tx = TransactionSigned::decode(&payload).map_err(|err| { log::debug!(target: LOG_TARGET, "Failed to decode transaction: {err:?}"); InvalidTransaction::Call })?; @@ -305,33 +305,33 @@ pub trait EthExtra { let signer = ::AddressMapper::to_fallback_account_id(&signer); - let TransactionLegacyUnsigned { nonce, chain_id, to, value, input, gas, gas_price, .. } = - tx.transaction_legacy_unsigned; + let GenericTransaction { nonce, chain_id, to, value, input, gas, gas_price, .. } = + GenericTransaction::from_signed(tx, None); if chain_id.unwrap_or_default() != ::ChainId::get().into() { log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}"); return Err(InvalidTransaction::Call); } - let value = crate::Pallet::::convert_evm_to_native(value).map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}"); - InvalidTransaction::Call - })?; + let value = crate::Pallet::::convert_evm_to_native(value.unwrap_or_default()) + .map_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}"); + InvalidTransaction::Call + })?; + let data = input.unwrap_or_default().0; let call = if let Some(dest) = to { crate::Call::call:: { dest, value, gas_limit, storage_deposit_limit, - data: input.0, + data, } } else { - let blob = match polkavm::ProgramBlob::blob_length(&input.0) { - Some(blob_len) => blob_len - .try_into() - .ok() - .and_then(|blob_len| (input.0.split_at_checked(blob_len))), + let blob = match polkavm::ProgramBlob::blob_length(&data) { + Some(blob_len) => + blob_len.try_into().ok().and_then(|blob_len| (data.split_at_checked(blob_len))), _ => None, }; @@ -350,18 +350,18 @@ pub trait EthExtra { } }; - let nonce = nonce.try_into().map_err(|_| InvalidTransaction::Call)?; + let nonce = nonce.unwrap_or_default().try_into().map_err(|_| InvalidTransaction::Call)?; // Fees calculated with the fixed `GAS_PRICE` // When we dry-run the transaction, we set the gas to `Fee / GAS_PRICE` let eth_fee_no_tip = U256::from(GAS_PRICE) - .saturating_mul(gas) + .saturating_mul(gas.unwrap_or_default()) .try_into() .map_err(|_| InvalidTransaction::Call)?; // Fees with the actual gas_price from the transaction. - let eth_fee: BalanceOf = U256::from(gas_price) - .saturating_mul(gas) + let eth_fee: BalanceOf = U256::from(gas_price.unwrap_or_default()) + .saturating_mul(gas.unwrap_or_default()) .try_into() .map_err(|_| InvalidTransaction::Call)?; @@ -414,7 +414,6 @@ mod test { }; use frame_support::{error::LookupError, traits::fungible::Mutate}; use pallet_revive_fixtures::compile_module; - use rlp::Encodable; use sp_runtime::{ traits::{Checkable, DispatchTransaction}, MultiAddress, MultiSignature, @@ -523,7 +522,7 @@ mod test { 100_000_000_000_000, ); - let payload = account.sign_transaction(tx).rlp_bytes().to_vec(); + let payload = account.sign_transaction(tx.into()).signed_payload(); let call = RuntimeCall::Contracts(crate::Call::eth_transact { payload, gas_limit, diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index caecf07c4071..b55854e2eec5 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -768,7 +768,7 @@ pub mod pallet { /// /// # Parameters /// - /// * `payload`: The RLP-encoded [`crate::evm::TransactionLegacySigned`]. + /// * `payload`: The encoded [`crate::evm::TransactionSigned`]. /// * `gas_limit`: The gas limit enforced during contract execution. /// * `storage_deposit_limit`: The maximum balance that can be charged to the caller for /// storage usage. From 7c5224cb01710d0c14c87bf3463cc79e49b3e7b5 Mon Sep 17 00:00:00 2001 From: gupnik Date: Fri, 22 Nov 2024 10:16:45 +0530 Subject: [PATCH 017/340] Adds `BlockNumberProvider` in multisig, proxy and nft pallets (#5723) Step in https://github.com/paritytech/polkadot-sdk/issues/3268 This PR adds the ability for these pallets to specify their source of the block number. This is useful when these pallets are migrated from the relay chain to a parachain and vice versa. This change is backwards compatible: 1. If the `BlockNumberProvider` continues to use the system pallet's block number 2. When a pallet deployed on the relay chain is moved to a parachain, but still uses the relay chain's block number However, we would need migrations if the deployed pallets are upgraded on an existing parachain, and the `BlockNumberProvider` uses the relay chain block number. --------- Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- .../assets/asset-hub-rococo/src/lib.rs | 3 ++ .../assets/asset-hub-westend/src/lib.rs | 3 ++ .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 1 + .../bridge-hubs/bridge-hub-westend/src/lib.rs | 1 + .../collectives-westend/src/lib.rs | 2 ++ .../contracts/contracts-rococo/src/lib.rs | 1 + .../coretime/coretime-rococo/src/lib.rs | 2 ++ .../coretime/coretime-westend/src/lib.rs | 2 ++ .../runtimes/people/people-rococo/src/lib.rs | 2 ++ .../runtimes/people/people-westend/src/lib.rs | 2 ++ polkadot/runtime/rococo/src/lib.rs | 2 ++ polkadot/runtime/westend/src/lib.rs | 2 ++ prdoc/pr_5723.prdoc | 24 +++++++++++++++ substrate/bin/node/runtime/src/lib.rs | 3 ++ substrate/frame/contracts/src/tests.rs | 1 + substrate/frame/multisig/src/lib.rs | 10 +++++-- substrate/frame/multisig/src/tests.rs | 1 + .../frame/nft-fractionalization/src/mock.rs | 1 + substrate/frame/nfts/src/benchmarking.rs | 22 +++++++------- .../frame/nfts/src/features/approvals.rs | 6 ++-- .../frame/nfts/src/features/atomic_swap.rs | 8 ++--- .../frame/nfts/src/features/attributes.rs | 2 +- .../nfts/src/features/create_delete_item.rs | 2 +- substrate/frame/nfts/src/features/settings.rs | 6 +--- substrate/frame/nfts/src/lib.rs | 29 ++++++++++--------- substrate/frame/nfts/src/mock.rs | 1 + substrate/frame/nfts/src/types.rs | 12 ++++---- substrate/frame/proxy/src/benchmarking.rs | 6 ++-- substrate/frame/proxy/src/lib.rs | 12 ++++++-- substrate/frame/proxy/src/tests.rs | 1 + substrate/frame/revive/src/tests.rs | 1 + substrate/frame/safe-mode/src/mock.rs | 1 + substrate/frame/src/lib.rs | 4 +-- substrate/frame/tx-pause/src/mock.rs | 1 + 34 files changed, 125 insertions(+), 52 deletions(-) create mode 100644 prdoc/pr_5723.prdoc diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 2f9d83bd9d0b..bc48c2d805fd 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -467,6 +467,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = MaxSignatories; type WeightInfo = weights::pallet_multisig::WeightInfo; + type BlockNumberProvider = frame_system::Pallet; } impl pallet_utility::Config for Runtime { @@ -652,6 +653,7 @@ impl pallet_proxy::Config for Runtime { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = frame_system::Pallet; } parameter_types! { @@ -918,6 +920,7 @@ impl pallet_nfts::Config for Runtime { type WeightInfo = weights::pallet_nfts::WeightInfo; #[cfg(feature = "runtime-benchmarks")] type Helper = (); + type BlockNumberProvider = frame_system::Pallet; } /// XCM router instance to BridgeHub with bridging capabilities for `Westend` global diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 2206aea78ec2..cafea3b6ff8b 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -466,6 +466,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = MaxSignatories; type WeightInfo = weights::pallet_multisig::WeightInfo; + type BlockNumberProvider = frame_system::Pallet; } impl pallet_utility::Config for Runtime { @@ -651,6 +652,7 @@ impl pallet_proxy::Config for Runtime { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = frame_system::Pallet; } parameter_types! { @@ -912,6 +914,7 @@ impl pallet_nfts::Config for Runtime { type WeightInfo = weights::pallet_nfts::WeightInfo; #[cfg(feature = "runtime-benchmarks")] type Helper = (); + type BlockNumberProvider = frame_system::Pallet; } /// XCM router instance to BridgeHub with bridging capabilities for `Rococo` global diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index ff7af475f5e2..3f3316d0be49 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -537,6 +537,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = ConstU32<100>; type WeightInfo = weights::pallet_multisig::WeightInfo; + type BlockNumberProvider = frame_system::Pallet; } impl pallet_utility::Config for Runtime { diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 065400016791..65e7d291dc37 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -513,6 +513,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = ConstU32<100>; type WeightInfo = weights::pallet_multisig::WeightInfo; + type BlockNumberProvider = frame_system::Pallet; } impl pallet_utility::Config for Runtime { diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index c3e105a84fb6..0ee3a4068718 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -258,6 +258,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = ConstU32<100>; type WeightInfo = weights::pallet_multisig::WeightInfo; + type BlockNumberProvider = frame_system::Pallet; } impl pallet_utility::Config for Runtime { @@ -382,6 +383,7 @@ impl pallet_proxy::Config for Runtime { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = frame_system::Pallet; } parameter_types! { diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index f661a8bdccfe..2951662a979b 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -268,6 +268,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = ConstU32<100>; type WeightInfo = pallet_multisig::weights::SubstrateWeight; + type BlockNumberProvider = frame_system::Pallet; } impl pallet_utility::Config for Runtime { diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index 31700c2e25ff..3f3126b749d8 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -445,6 +445,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = ConstU32<100>; type WeightInfo = weights::pallet_multisig::WeightInfo; + type BlockNumberProvider = frame_system::Pallet; } /// The type used to represent the kinds of proxying allowed. @@ -577,6 +578,7 @@ impl pallet_proxy::Config for Runtime { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = frame_system::Pallet; } impl pallet_utility::Config for Runtime { diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 1f0f54884fa8..098a17cc9984 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -446,6 +446,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = ConstU32<100>; type WeightInfo = weights::pallet_multisig::WeightInfo; + type BlockNumberProvider = frame_system::Pallet; } /// The type used to represent the kinds of proxying allowed. @@ -578,6 +579,7 @@ impl pallet_proxy::Config for Runtime { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = frame_system::Pallet; } impl pallet_utility::Config for Runtime { diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 25356a84806d..7921030f2bb8 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -407,6 +407,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = ConstU32<100>; type WeightInfo = weights::pallet_multisig::WeightInfo; + type BlockNumberProvider = frame_system::Pallet; } /// The type used to represent the kinds of proxying allowed. @@ -520,6 +521,7 @@ impl pallet_proxy::Config for Runtime { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = frame_system::Pallet; } impl pallet_utility::Config for Runtime { diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 1c5183636c49..19a64ab8d6e8 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -406,6 +406,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = ConstU32<100>; type WeightInfo = weights::pallet_multisig::WeightInfo; + type BlockNumberProvider = frame_system::Pallet; } /// The type used to represent the kinds of proxying allowed. @@ -519,6 +520,7 @@ impl pallet_proxy::Config for Runtime { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = frame_system::Pallet; } impl pallet_utility::Config for Runtime { diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 96a97faa4750..5da9da86f02e 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -767,6 +767,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = MaxSignatories; type WeightInfo = weights::pallet_multisig::WeightInfo; + type BlockNumberProvider = frame_system::Pallet; } parameter_types! { @@ -971,6 +972,7 @@ impl pallet_proxy::Config for Runtime { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = frame_system::Pallet; } impl parachains_origin::Config for Runtime {} diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 7a5562cc98c1..9f0b701f20be 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1004,6 +1004,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = MaxSignatories; type WeightInfo = weights::pallet_multisig::WeightInfo; + type BlockNumberProvider = frame_system::Pallet; } parameter_types! { @@ -1204,6 +1205,7 @@ impl pallet_proxy::Config for Runtime { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = frame_system::Pallet; } impl parachains_origin::Config for Runtime {} diff --git a/prdoc/pr_5723.prdoc b/prdoc/pr_5723.prdoc new file mode 100644 index 000000000000..ded5f9cebd1d --- /dev/null +++ b/prdoc/pr_5723.prdoc @@ -0,0 +1,24 @@ +title: Adds `BlockNumberProvider` in multisig, proxy and nft pallets + +doc: + - audience: Runtime Dev + description: | + This PR adds the ability for these pallets to specify their source of the block number. + This is useful when these pallets are migrated from the relay chain to a parachain and + vice versa. + + This change is backwards compatible: + 1. If the `BlockNumberProvider` continues to use the system pallet's block number + 2. When a pallet deployed on the relay chain is moved to a parachain, but still uses the + relay chain's block number + + However, we would need migrations if the deployed pallets are upgraded on an existing parachain, + and the `BlockNumberProvider` uses the relay chain block number. + +crates: + - name: pallet-multisig + bump: major + - name: pallet-proxy + bump: major + - name: pallet-nfts + bump: major diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index e68e04840776..bff263548087 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -392,6 +392,7 @@ impl pallet_multisig::Config for Runtime { type DepositFactor = DepositFactor; type MaxSignatories = ConstU32<100>; type WeightInfo = pallet_multisig::weights::SubstrateWeight; + type BlockNumberProvider = frame_system::Pallet; } parameter_types! { @@ -479,6 +480,7 @@ impl pallet_proxy::Config for Runtime { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = AnnouncementDepositBase; type AnnouncementDepositFactor = AnnouncementDepositFactor; + type BlockNumberProvider = frame_system::Pallet; } parameter_types! { @@ -2048,6 +2050,7 @@ impl pallet_nfts::Config for Runtime { type Helper = (); type CreateOrigin = AsEnsureOriginWithArg>; type Locker = (); + type BlockNumberProvider = frame_system::Pallet; } impl pallet_transaction_storage::Config for Runtime { diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index c3b6e3273f34..b01d0aa4fa48 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -399,6 +399,7 @@ impl pallet_proxy::Config for Test { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = ConstU64<1>; type AnnouncementDepositFactor = ConstU64<1>; + type BlockNumberProvider = frame_system::Pallet; } impl pallet_dummy::Config for Test {} diff --git a/substrate/frame/multisig/src/lib.rs b/substrate/frame/multisig/src/lib.rs index 4a30b5c119b9..869b4adc2adc 100644 --- a/substrate/frame/multisig/src/lib.rs +++ b/substrate/frame/multisig/src/lib.rs @@ -77,6 +77,9 @@ macro_rules! log { type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +pub type BlockNumberFor = + <::BlockNumberProvider as BlockNumberProvider>::BlockNumber; + /// A global extrinsic index, formed as the extrinsic index within a block, together with that /// block's height. This allows a transaction in which a multisig operation of a particular /// composite was created to be uniquely identified. @@ -153,6 +156,9 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: weights::WeightInfo; + + /// Provider for the block number. Normally this is the `frame_system` pallet. + type BlockNumberProvider: BlockNumberProvider; } /// The in-code storage version. @@ -235,7 +241,7 @@ pub mod pallet { } #[pallet::hooks] - impl Hooks> for Pallet {} + impl Hooks> for Pallet {} #[pallet::call] impl Pallet { @@ -626,7 +632,7 @@ impl Pallet { /// The current `Timepoint`. pub fn timepoint() -> Timepoint> { Timepoint { - height: >::block_number(), + height: T::BlockNumberProvider::current_block_number(), index: >::extrinsic_index().unwrap_or_default(), } } diff --git a/substrate/frame/multisig/src/tests.rs b/substrate/frame/multisig/src/tests.rs index c5a98845270c..4065ce73f905 100644 --- a/substrate/frame/multisig/src/tests.rs +++ b/substrate/frame/multisig/src/tests.rs @@ -66,6 +66,7 @@ impl Config for Test { type DepositFactor = ConstU64<1>; type MaxSignatories = ConstU32<3>; type WeightInfo = (); + type BlockNumberProvider = frame_system::Pallet; } use pallet_balances::Call as BalancesCall; diff --git a/substrate/frame/nft-fractionalization/src/mock.rs b/substrate/frame/nft-fractionalization/src/mock.rs index 50b41b5fc64e..762c1776e30f 100644 --- a/substrate/frame/nft-fractionalization/src/mock.rs +++ b/substrate/frame/nft-fractionalization/src/mock.rs @@ -115,6 +115,7 @@ impl pallet_nfts::Config for Test { type OffchainSignature = Signature; type OffchainPublic = AccountPublic; type WeightInfo = (); + type BlockNumberProvider = frame_system::Pallet; pallet_nfts::runtime_benchmarks_enabled! { type Helper = (); } diff --git a/substrate/frame/nfts/src/benchmarking.rs b/substrate/frame/nfts/src/benchmarking.rs index bc81096b459d..81828be5fa09 100644 --- a/substrate/frame/nfts/src/benchmarking.rs +++ b/substrate/frame/nfts/src/benchmarking.rs @@ -29,7 +29,7 @@ use frame_support::{ traits::{EnsureOrigin, Get, UnfilteredDispatchable}, BoundedVec, }; -use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin as SystemOrigin}; +use frame_system::RawOrigin as SystemOrigin; use sp_runtime::traits::{Bounded, One}; use crate::Pallet as Nfts; @@ -577,7 +577,7 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); - let deadline = BlockNumberFor::::max_value(); + let deadline = BlockNumberFor::::max_value(); }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline)) verify { assert_last_event::(Event::TransferApproved { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into()); @@ -589,7 +589,7 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); - let deadline = BlockNumberFor::::max_value(); + let deadline = BlockNumberFor::::max_value(); Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) verify { @@ -602,7 +602,7 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); - let deadline = BlockNumberFor::::max_value(); + let deadline = BlockNumberFor::::max_value(); Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; }: _(SystemOrigin::Signed(caller.clone()), collection, item) verify { @@ -712,10 +712,10 @@ benchmarks_instance_pallet! { let price_direction = PriceDirection::Receive; let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; let duration = T::MaxDeadlineDuration::get(); - frame_system::Pallet::::set_block_number(One::one()); + T::BlockNumberProvider::set_block_number(One::one()); }: _(SystemOrigin::Signed(caller.clone()), collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration) verify { - let current_block = frame_system::Pallet::::block_number(); + let current_block = T::BlockNumberProvider::current_block_number(); assert_last_event::(Event::SwapCreated { offered_collection: collection, offered_item: item1, @@ -735,7 +735,7 @@ benchmarks_instance_pallet! { let duration = T::MaxDeadlineDuration::get(); let price_direction = PriceDirection::Receive; let price_with_direction = PriceWithDirection { amount: price, direction: price_direction }; - frame_system::Pallet::::set_block_number(One::one()); + T::BlockNumberProvider::set_block_number(One::one()); Nfts::::create_swap(origin, collection, item1, collection, Some(item2), Some(price_with_direction.clone()), duration)?; }: _(SystemOrigin::Signed(caller.clone()), collection, item1) verify { @@ -761,7 +761,7 @@ benchmarks_instance_pallet! { let target_lookup = T::Lookup::unlookup(target.clone()); T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); let origin = SystemOrigin::Signed(caller.clone()); - frame_system::Pallet::::set_block_number(One::one()); + T::BlockNumberProvider::set_block_number(One::one()); Nfts::::transfer(origin.clone().into(), collection, item2, target_lookup)?; Nfts::::create_swap( origin.clone().into(), @@ -774,7 +774,7 @@ benchmarks_instance_pallet! { )?; }: _(SystemOrigin::Signed(target.clone()), collection, item2, collection, item1, Some(price_with_direction.clone())) verify { - let current_block = frame_system::Pallet::::block_number(); + let current_block = T::BlockNumberProvider::current_block_number(); assert_last_event::(Event::SwapClaimed { sent_collection: collection, sent_item: item2, @@ -822,7 +822,7 @@ benchmarks_instance_pallet! { let target: T::AccountId = account("target", 0, SEED); T::Currency::make_free_balance_be(&target, DepositBalanceOf::::max_value()); - frame_system::Pallet::::set_block_number(One::one()); + T::BlockNumberProvider::set_block_number(One::one()); }: _(SystemOrigin::Signed(target.clone()), Box::new(mint_data), signature.into(), caller) verify { let metadata: BoundedVec<_, _> = metadata.try_into().unwrap(); @@ -865,7 +865,7 @@ benchmarks_instance_pallet! { let message = Encode::encode(&pre_signed_data); let signature = T::Helper::sign(&signer_public, &message); - frame_system::Pallet::::set_block_number(One::one()); + T::BlockNumberProvider::set_block_number(One::one()); }: _(SystemOrigin::Signed(item_owner.clone()), pre_signed_data, signature.into(), signer.clone()) verify { assert_last_event::( diff --git a/substrate/frame/nfts/src/features/approvals.rs b/substrate/frame/nfts/src/features/approvals.rs index 053fa67163b9..4738f69f83c4 100644 --- a/substrate/frame/nfts/src/features/approvals.rs +++ b/substrate/frame/nfts/src/features/approvals.rs @@ -46,7 +46,7 @@ impl, I: 'static> Pallet { collection: T::CollectionId, item: T::ItemId, delegate: T::AccountId, - maybe_deadline: Option>, + maybe_deadline: Option>, ) -> DispatchResult { ensure!( Self::is_pallet_feature_enabled(PalletFeature::Approvals), @@ -65,7 +65,7 @@ impl, I: 'static> Pallet { ensure!(check_origin == details.owner, Error::::NoPermission); } - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); let deadline = maybe_deadline.map(|d| d.saturating_add(now)); details @@ -111,7 +111,7 @@ impl, I: 'static> Pallet { let maybe_deadline = details.approvals.get(&delegate).ok_or(Error::::NotDelegate)?; let is_past_deadline = if let Some(deadline) = maybe_deadline { - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); now > *deadline } else { false diff --git a/substrate/frame/nfts/src/features/atomic_swap.rs b/substrate/frame/nfts/src/features/atomic_swap.rs index 830283b73c2a..03ebd35b81b2 100644 --- a/substrate/frame/nfts/src/features/atomic_swap.rs +++ b/substrate/frame/nfts/src/features/atomic_swap.rs @@ -53,7 +53,7 @@ impl, I: 'static> Pallet { desired_collection_id: T::CollectionId, maybe_desired_item_id: Option, maybe_price: Option>>, - duration: frame_system::pallet_prelude::BlockNumberFor, + duration: BlockNumberFor, ) -> DispatchResult { ensure!( Self::is_pallet_feature_enabled(PalletFeature::Swaps), @@ -76,7 +76,7 @@ impl, I: 'static> Pallet { ), }; - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); let deadline = duration.saturating_add(now); PendingSwapOf::::insert( @@ -119,7 +119,7 @@ impl, I: 'static> Pallet { let swap = PendingSwapOf::::get(&offered_collection_id, &offered_item_id) .ok_or(Error::::UnknownSwap)?; - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); if swap.deadline > now { let item = Item::::get(&offered_collection_id, &offered_item_id) .ok_or(Error::::UnknownItem)?; @@ -187,7 +187,7 @@ impl, I: 'static> Pallet { ensure!(desired_item == send_item_id, Error::::UnknownSwap); } - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); ensure!(now <= swap.deadline, Error::::DeadlineExpired); if let Some(ref price) = swap.price { diff --git a/substrate/frame/nfts/src/features/attributes.rs b/substrate/frame/nfts/src/features/attributes.rs index 28f7bd2c58ce..2cd09f7d2193 100644 --- a/substrate/frame/nfts/src/features/attributes.rs +++ b/substrate/frame/nfts/src/features/attributes.rs @@ -225,7 +225,7 @@ impl, I: 'static> Pallet { Error::::MaxAttributesLimitReached ); - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); ensure!(deadline >= now, Error::::DeadlineExpired); let item_details = diff --git a/substrate/frame/nfts/src/features/create_delete_item.rs b/substrate/frame/nfts/src/features/create_delete_item.rs index 37f64ae1b1b9..57366127f142 100644 --- a/substrate/frame/nfts/src/features/create_delete_item.rs +++ b/substrate/frame/nfts/src/features/create_delete_item.rs @@ -145,7 +145,7 @@ impl, I: 'static> Pallet { ensure!(account == mint_to, Error::::WrongOrigin); } - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); ensure!(deadline >= now, Error::::DeadlineExpired); ensure!( diff --git a/substrate/frame/nfts/src/features/settings.rs b/substrate/frame/nfts/src/features/settings.rs index d4f7533ffa4e..48719ae2c20e 100644 --- a/substrate/frame/nfts/src/features/settings.rs +++ b/substrate/frame/nfts/src/features/settings.rs @@ -96,11 +96,7 @@ impl, I: 'static> Pallet { pub(crate) fn do_update_mint_settings( maybe_check_origin: Option, collection: T::CollectionId, - mint_settings: MintSettings< - BalanceOf, - frame_system::pallet_prelude::BlockNumberFor, - T::CollectionId, - >, + mint_settings: MintSettings, BlockNumberFor, T::CollectionId>, ) -> DispatchResult { if let Some(check_origin) = &maybe_check_origin { ensure!( diff --git a/substrate/frame/nfts/src/lib.rs b/substrate/frame/nfts/src/lib.rs index 4e5493a3c755..346ad162c503 100644 --- a/substrate/frame/nfts/src/lib.rs +++ b/substrate/frame/nfts/src/lib.rs @@ -58,7 +58,7 @@ use frame_support::traits::{ }; use frame_system::Config as SystemConfig; use sp_runtime::{ - traits::{IdentifyAccount, Saturating, StaticLookup, Verify, Zero}, + traits::{BlockNumberProvider, IdentifyAccount, Saturating, StaticLookup, Verify, Zero}, RuntimeDebug, }; @@ -76,7 +76,7 @@ type AccountIdLookupOf = <::Lookup as StaticLookup>::Sourc pub mod pallet { use super::*; use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; - use frame_system::pallet_prelude::*; + use frame_system::{ensure_signed, pallet_prelude::OriginFor}; /// The in-code storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); @@ -210,7 +210,7 @@ pub mod pallet { /// The max duration in blocks for deadlines. #[pallet::constant] - type MaxDeadlineDuration: Get>; + type MaxDeadlineDuration: Get>; /// The max number of attributes a user could set per call. #[pallet::constant] @@ -242,6 +242,9 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + /// Provider for the block number. Normally this is the `frame_system` pallet. + type BlockNumberProvider: BlockNumberProvider; } /// Details of a collection. @@ -388,7 +391,7 @@ pub mod pallet { T::CollectionId, T::ItemId, PriceWithDirection>, - BlockNumberFor, + BlockNumberFor, >, OptionQuery, >; @@ -459,7 +462,7 @@ pub mod pallet { item: T::ItemId, owner: T::AccountId, delegate: T::AccountId, - deadline: Option>, + deadline: Option>, }, /// An approval for a `delegate` account to transfer the `item` of an item /// `collection` was cancelled by its `owner`. @@ -554,7 +557,7 @@ pub mod pallet { desired_collection: T::CollectionId, desired_item: Option, price: Option>>, - deadline: BlockNumberFor, + deadline: BlockNumberFor, }, /// The swap was cancelled. SwapCancelled { @@ -563,7 +566,7 @@ pub mod pallet { desired_collection: T::CollectionId, desired_item: Option, price: Option>>, - deadline: BlockNumberFor, + deadline: BlockNumberFor, }, /// The swap has been claimed. SwapClaimed { @@ -574,7 +577,7 @@ pub mod pallet { received_item: T::ItemId, received_item_owner: T::AccountId, price: Option>>, - deadline: BlockNumberFor, + deadline: BlockNumberFor, }, /// New attributes have been set for an `item` of the `collection`. PreSignedAttributesSet { @@ -857,7 +860,7 @@ pub mod pallet { item_config, |collection_details, collection_config| { let mint_settings = collection_config.mint_settings; - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); if let Some(start_block) = mint_settings.start_block { ensure!(start_block <= now, Error::::MintNotStarted); @@ -1029,7 +1032,7 @@ pub mod pallet { let deadline = details.approvals.get(&origin).ok_or(Error::::NoPermission)?; if let Some(d) = deadline { - let block_number = frame_system::Pallet::::block_number(); + let block_number = T::BlockNumberProvider::current_block_number(); ensure!(block_number <= *d, Error::::ApprovalExpired); } } @@ -1290,7 +1293,7 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, delegate: AccountIdLookupOf, - maybe_deadline: Option>, + maybe_deadline: Option>, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) @@ -1713,7 +1716,7 @@ pub mod pallet { pub fn update_mint_settings( origin: OriginFor, collection: T::CollectionId, - mint_settings: MintSettings, BlockNumberFor, T::CollectionId>, + mint_settings: MintSettings, BlockNumberFor, T::CollectionId>, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) @@ -1809,7 +1812,7 @@ pub mod pallet { desired_collection: T::CollectionId, maybe_desired_item: Option, maybe_price: Option>>, - duration: BlockNumberFor, + duration: BlockNumberFor, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::do_create_swap( diff --git a/substrate/frame/nfts/src/mock.rs b/substrate/frame/nfts/src/mock.rs index 5b589f591ca3..291c3c081334 100644 --- a/substrate/frame/nfts/src/mock.rs +++ b/substrate/frame/nfts/src/mock.rs @@ -92,6 +92,7 @@ impl Config for Test { type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Helper = (); + type BlockNumberProvider = frame_system::Pallet; } pub(crate) fn new_test_ext() -> sp_io::TestExternalities { diff --git a/substrate/frame/nfts/src/types.rs b/substrate/frame/nfts/src/types.rs index d67fb404ea79..3ab85993473a 100644 --- a/substrate/frame/nfts/src/types.rs +++ b/substrate/frame/nfts/src/types.rs @@ -27,9 +27,11 @@ use frame_support::{ traits::Get, BoundedBTreeMap, BoundedBTreeSet, }; -use frame_system::pallet_prelude::BlockNumberFor; use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; +pub type BlockNumberFor = + <>::BlockNumberProvider as BlockNumberProvider>::BlockNumber; + /// A type alias for handling balance deposits. pub type DepositBalanceOf = <>::Currency as Currency<::AccountId>>::Balance; @@ -39,7 +41,7 @@ pub type CollectionDetailsFor = /// A type alias for keeping track of approvals used by a single item. pub type ApprovalsOf = BoundedBTreeMap< ::AccountId, - Option>, + Option>, >::ApprovalsLimit, >; /// A type alias for keeping track of approvals for an item's attributes. @@ -70,13 +72,13 @@ pub type ItemTipOf = ItemTip< >; /// A type alias for the settings configuration of a collection. pub type CollectionConfigFor = - CollectionConfig, BlockNumberFor, >::CollectionId>; + CollectionConfig, BlockNumberFor, >::CollectionId>; /// A type alias for the pre-signed minting configuration for a specified collection. pub type PreSignedMintOf = PreSignedMint< >::CollectionId, >::ItemId, ::AccountId, - BlockNumberFor, + BlockNumberFor, BalanceOf, >; /// A type alias for the pre-signed minting configuration on the attribute level of an item. @@ -84,7 +86,7 @@ pub type PreSignedAttributesOf = PreSignedAttributes< >::CollectionId, >::ItemId, ::AccountId, - BlockNumberFor, + BlockNumberFor, >; /// Information about a collection. diff --git a/substrate/frame/proxy/src/benchmarking.rs b/substrate/frame/proxy/src/benchmarking.rs index eebb506bf374..b72f53af8e72 100644 --- a/substrate/frame/proxy/src/benchmarking.rs +++ b/substrate/frame/proxy/src/benchmarking.rs @@ -22,7 +22,9 @@ use super::*; use crate::Pallet as Proxy; use alloc::{boxed::Box, vec}; -use frame::benchmarking::prelude::*; +use frame::benchmarking::prelude::{ + account, benchmarks, impl_test_function, whitelisted_caller, BenchmarkError, RawOrigin, +}; const SEED: u32 = 0; @@ -317,7 +319,7 @@ mod benchmarks { BlockNumberFor::::zero(), 0, )?; - let height = frame_system::Pallet::::block_number(); + let height = T::BlockNumberProvider::current_block_number(); let ext_index = frame_system::Pallet::::extrinsic_index().unwrap_or(0); let pure_account = Pallet::::pure_account(&caller, &T::ProxyType::default(), 0, None); diff --git a/substrate/frame/proxy/src/lib.rs b/substrate/frame/proxy/src/lib.rs index cc8aeedcc5f9..cc21db7469b2 100644 --- a/substrate/frame/proxy/src/lib.rs +++ b/substrate/frame/proxy/src/lib.rs @@ -47,6 +47,9 @@ type CallHashOf = <::CallHasher as Hash>::Output; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +pub type BlockNumberFor = + <::BlockNumberProvider as BlockNumberProvider>::BlockNumber; + type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; /// The parameters under which a particular account has a proxy relationship with some other @@ -163,6 +166,9 @@ pub mod pallet { /// into a pre-existing storage value. #[pallet::constant] type AnnouncementDepositFactor: Get>; + + /// Provider for the block number. Normally this is the `frame_system` pallet. + type BlockNumberProvider: BlockNumberProvider; } #[pallet::call] @@ -379,7 +385,7 @@ pub mod pallet { let announcement = Announcement { real: real.clone(), call_hash, - height: frame_system::Pallet::::block_number(), + height: T::BlockNumberProvider::current_block_number(), }; Announcements::::try_mutate(&who, |(ref mut pending, ref mut deposit)| { @@ -490,7 +496,7 @@ pub mod pallet { let def = Self::find_proxy(&real, &delegate, force_proxy_type)?; let call_hash = T::CallHasher::hash_of(&call); - let now = frame_system::Pallet::::block_number(); + let now = T::BlockNumberProvider::current_block_number(); Self::edit_announcements(&delegate, |ann| { ann.real != real || ann.call_hash != call_hash || @@ -626,7 +632,7 @@ impl Pallet { ) -> T::AccountId { let (height, ext_index) = maybe_when.unwrap_or_else(|| { ( - frame_system::Pallet::::block_number(), + T::BlockNumberProvider::current_block_number(), frame_system::Pallet::::extrinsic_index().unwrap_or_default(), ) }); diff --git a/substrate/frame/proxy/src/tests.rs b/substrate/frame/proxy/src/tests.rs index 5baf9bb9e838..afc668188e6c 100644 --- a/substrate/frame/proxy/src/tests.rs +++ b/substrate/frame/proxy/src/tests.rs @@ -119,6 +119,7 @@ impl Config for Test { type MaxPending = ConstU32<2>; type AnnouncementDepositBase = ConstU64<1>; type AnnouncementDepositFactor = ConstU64<1>; + type BlockNumberProvider = frame_system::Pallet; } use super::{Call as ProxyCall, Event as ProxyEvent}; diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 177b8dff706b..34afe8aabfe6 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -416,6 +416,7 @@ impl pallet_proxy::Config for Test { type CallHasher = BlakeTwo256; type AnnouncementDepositBase = ConstU64<1>; type AnnouncementDepositFactor = ConstU64<1>; + type BlockNumberProvider = frame_system::Pallet; } parameter_types! { diff --git a/substrate/frame/safe-mode/src/mock.rs b/substrate/frame/safe-mode/src/mock.rs index ec1ad8249514..aaf3456272fa 100644 --- a/substrate/frame/safe-mode/src/mock.rs +++ b/substrate/frame/safe-mode/src/mock.rs @@ -138,6 +138,7 @@ impl pallet_proxy::Config for Test { type MaxPending = ConstU32<2>; type AnnouncementDepositBase = ConstU64<1>; type AnnouncementDepositFactor = ConstU64<1>; + type BlockNumberProvider = frame_system::Pallet; } /// The calls that can always bypass safe-mode. diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 0ca36ca8545a..03d815e349df 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -219,8 +219,8 @@ pub mod prelude { /// Runtime traits #[doc(no_inline)] pub use sp_runtime::traits::{ - Bounded, DispatchInfoOf, Dispatchable, SaturatedConversion, Saturating, StaticLookup, - TrailingZeroInput, + BlockNumberProvider, Bounded, DispatchInfoOf, Dispatchable, SaturatedConversion, + Saturating, StaticLookup, TrailingZeroInput, }; /// Other error/result types for runtime diff --git a/substrate/frame/tx-pause/src/mock.rs b/substrate/frame/tx-pause/src/mock.rs index 84ce45e83528..fd9b3b552ccd 100644 --- a/substrate/frame/tx-pause/src/mock.rs +++ b/substrate/frame/tx-pause/src/mock.rs @@ -105,6 +105,7 @@ impl pallet_proxy::Config for Test { type MaxPending = ConstU32<2>; type AnnouncementDepositBase = ConstU64<1>; type AnnouncementDepositFactor = ConstU64<1>; + type BlockNumberProvider = frame_system::Pallet; } parameter_types! { From 08ec8cdbfdbdd29c7921a8a141187e04354a449e Mon Sep 17 00:00:00 2001 From: eskimor Date: Fri, 22 Nov 2024 14:19:40 +0100 Subject: [PATCH 018/340] Only mess with coretime if we are registering an actual parachain. (#6554) Co-authored-by: Robert Co-authored-by: ordian --- polkadot/runtime/common/src/paras_sudo_wrapper.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/polkadot/runtime/common/src/paras_sudo_wrapper.rs b/polkadot/runtime/common/src/paras_sudo_wrapper.rs index af93c70b4783..a93c209e9279 100644 --- a/polkadot/runtime/common/src/paras_sudo_wrapper.rs +++ b/polkadot/runtime/common/src/paras_sudo_wrapper.rs @@ -24,7 +24,7 @@ pub use pallet::*; use polkadot_primitives::Id as ParaId; use polkadot_runtime_parachains::{ configuration, dmp, hrmp, - paras::{self, AssignCoretime, ParaGenesisArgs}, + paras::{self, AssignCoretime, ParaGenesisArgs, ParaKind}, ParaLifecycle, }; @@ -80,10 +80,15 @@ pub mod pallet { genesis: ParaGenesisArgs, ) -> DispatchResult { ensure_root(origin)?; + + let assign_coretime = genesis.para_kind == ParaKind::Parachain; + polkadot_runtime_parachains::schedule_para_initialize::(id, genesis) .map_err(|_| Error::::ParaAlreadyExists)?; - T::AssignCoretime::assign_coretime(id)?; + if assign_coretime { + T::AssignCoretime::assign_coretime(id)?; + } Ok(()) } From 1e3b8e1639c1cf784eabf0a9afcab1f3987e0ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sun, 24 Nov 2024 08:36:16 +0000 Subject: [PATCH 019/340] Notify telemetry only every second about the tx pool status (#6605) Before this was done for every imported transaction. When a lot of transactions got imported, the import notification channel was filled. The underlying problem was that the `status` call is read locking the `validated_pool` which will be write locked by the internal submitting logic. Thus, the submitting and status reading was interferring which each other. --------- Co-authored-by: GitHub Action --- Cargo.lock | 1 + cumulus/client/service/Cargo.toml | 1 + prdoc/pr_6605.prdoc | 10 +++++ substrate/client/service/src/builder.rs | 58 +++++++++++++++++-------- 4 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 prdoc/pr_6605.prdoc diff --git a/Cargo.lock b/Cargo.lock index 330c2563d976..c79adee6f38e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4680,6 +4680,7 @@ dependencies = [ "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "futures", + "futures-timer", "polkadot-primitives 7.0.0", "sc-client-api", "sc-consensus", diff --git a/cumulus/client/service/Cargo.toml b/cumulus/client/service/Cargo.toml index 8e9e41ca89dc..0a77b465d96a 100644 --- a/cumulus/client/service/Cargo.toml +++ b/cumulus/client/service/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] futures = { workspace = true } +futures-timer = { workspace = true } # Substrate sc-client-api = { workspace = true, default-features = true } diff --git a/prdoc/pr_6605.prdoc b/prdoc/pr_6605.prdoc new file mode 100644 index 000000000000..2adb1d8aee35 --- /dev/null +++ b/prdoc/pr_6605.prdoc @@ -0,0 +1,10 @@ +title: Notify telemetry only every second about the tx pool status +doc: +- audience: Node Operator + description: |- + Before this was done for every imported transaction. When a lot of transactions got imported, the import notification channel was filled. The underlying problem was that the `status` call is read locking the `validated_pool` which will be write locked by the internal submitting logic. Thus, the submitting and status reading was interferring which each other. +crates: +- name: cumulus-client-service + bump: patch +- name: sc-service + bump: patch diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index 68ac94539df8..ac9371a8941b 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -25,7 +25,7 @@ use crate::{ start_rpc_servers, BuildGenesisBlock, GenesisBlockBuilder, RpcHandlers, SpawnTaskHandle, TaskManager, TransactionPoolAdapter, }; -use futures::{future::ready, FutureExt, StreamExt}; +use futures::{select, FutureExt, StreamExt}; use jsonrpsee::RpcModule; use log::info; use prometheus_endpoint::Registry; @@ -90,7 +90,11 @@ use sp_consensus::block_validation::{ use sp_core::traits::{CodeExecutor, SpawnNamed}; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, BlockIdTo, NumberFor, Zero}; -use std::{str::FromStr, sync::Arc, time::SystemTime}; +use std::{ + str::FromStr, + sync::Arc, + time::{Duration, SystemTime}, +}; /// Full client type. pub type TFullClient = @@ -577,22 +581,42 @@ pub async fn propagate_transaction_notifications( Block: BlockT, ExPool: MaintainedTransactionPool::Hash>, { + const TELEMETRY_INTERVAL: Duration = Duration::from_secs(1); + // transaction notifications - transaction_pool - .import_notification_stream() - .for_each(move |hash| { - tx_handler_controller.propagate_transaction(hash); - let status = transaction_pool.status(); - telemetry!( - telemetry; - SUBSTRATE_INFO; - "txpool.import"; - "ready" => status.ready, - "future" => status.future, - ); - ready(()) - }) - .await; + let mut notifications = transaction_pool.import_notification_stream().fuse(); + let mut timer = futures_timer::Delay::new(TELEMETRY_INTERVAL).fuse(); + let mut tx_imported = false; + + loop { + select! { + notification = notifications.next() => { + let Some(hash) = notification else { return }; + + tx_handler_controller.propagate_transaction(hash); + + tx_imported = true; + }, + _ = timer => { + timer = futures_timer::Delay::new(TELEMETRY_INTERVAL).fuse(); + + if !tx_imported { + continue; + } + + tx_imported = false; + let status = transaction_pool.status(); + + telemetry!( + telemetry; + SUBSTRATE_INFO; + "txpool.import"; + "ready" => status.ready, + "future" => status.future, + ); + } + } + } } /// Initialize telemetry with provided configuration and return telemetry handle From 6da7d36e060c6e5fd5a20395470db6910037a640 Mon Sep 17 00:00:00 2001 From: gupnik Date: Mon, 25 Nov 2024 10:56:21 +0530 Subject: [PATCH 020/340] Fixes cfg attributes in runtime macro (#6410) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes https://github.com/paritytech/polkadot-sdk/issues/6209 This PR adds the support for cfg attributes in the runtime macro. --------- Co-authored-by: Bastian Köcher --- .../procedural/src/runtime/parse/pallet.rs | 15 ++++- substrate/frame/support/test/tests/pallet.rs | 59 +++++++++++++++---- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/substrate/frame/support/procedural/src/runtime/parse/pallet.rs b/substrate/frame/support/procedural/src/runtime/parse/pallet.rs index 52f57cd2cd8b..1397b7266a18 100644 --- a/substrate/frame/support/procedural/src/runtime/parse/pallet.rs +++ b/substrate/frame/support/procedural/src/runtime/parse/pallet.rs @@ -21,7 +21,7 @@ use crate::{ }; use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; -use syn::{punctuated::Punctuated, token, Error}; +use syn::{punctuated::Punctuated, spanned::Spanned, token, Error}; impl Pallet { pub fn try_from( @@ -78,7 +78,18 @@ impl Pallet { }) .collect(); - let cfg_pattern = vec![]; + let cfg_pattern = item + .attrs + .iter() + .filter(|attr| attr.path().segments.first().map_or(false, |s| s.ident == "cfg")) + .map(|attr| { + attr.parse_args_with(|input: syn::parse::ParseStream| { + let input = input.parse::()?; + cfg_expr::Expression::parse(&input.to_string()) + .map_err(|e| syn::Error::new(attr.span(), e.to_string())) + }) + }) + .collect::>>()?; let docs = get_doc_literals(&item.attrs); diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index b0b83f772499..de7f7eb4bc97 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -799,20 +799,43 @@ where } } -frame_support::construct_runtime!( - pub struct Runtime { - // Exclude part `Storage` in order not to check its metadata in tests. - System: frame_system exclude_parts { Pallet, Storage }, - Example: pallet, - Example2: pallet2 exclude_parts { Call }, - #[cfg(feature = "frame-feature-testing")] - Example3: pallet3, - Example4: pallet4 use_parts { Call }, +#[frame_support::runtime] +mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask + )] + pub struct Runtime; - #[cfg(feature = "frame-feature-testing-2")] - Example5: pallet5, - } -); + #[runtime::pallet_index(0)] + pub type System = frame_system + Call + Event; + + #[runtime::pallet_index(1)] + pub type Example = pallet; + + #[runtime::pallet_index(2)] + #[runtime::disable_call] + pub type Example2 = pallet2; + + #[cfg(feature = "frame-feature-testing")] + #[runtime::pallet_index(3)] + pub type Example3 = pallet3; + + #[runtime::pallet_index(4)] + pub type Example4 = pallet4; + + #[cfg(feature = "frame-feature-testing-2")] + #[runtime::pallet_index(5)] + pub type Example5 = pallet5; +} // Test that the part `RuntimeCall` is excluded from Example2 and included in Example4. fn _ensure_call_is_correctly_excluded_and_included(call: RuntimeCall) { @@ -1847,6 +1870,16 @@ fn metadata() { error: None, docs: vec![" Test that the supertrait check works when we pass some parameter to the `frame_system::Config`."], }, + PalletMetadata { + index: 4, + name: "Example4", + storage: None, + calls: Some(meta_type::>().into()), + event: None, + constants: vec![], + error: None, + docs: vec![], + }, #[cfg(feature = "frame-feature-testing-2")] PalletMetadata { index: 5, From 93b451604a4452915faddea5650e66d113802e57 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Mon, 25 Nov 2024 07:30:16 +0200 Subject: [PATCH 021/340] Update bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs Co-authored-by: Vincent Geddes <117534+vgeddes@users.noreply.github.com> --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index fddfc4e6df56..c9374651c563 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -199,7 +199,7 @@ pub mod pallet { ensure!(T::GatewayAddress::get() == envelope.gateway, Error::::InvalidGateway); // Verify the message has not been processed - ensure!(!>::get(envelope.nonce.into()), Error::::InvalidNonce); + ensure!(!Nonce::::get(envelope.nonce.into()), Error::::InvalidNonce); // Decode payload into `MessageV2` let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) From 3a131345eb4325a16e95ec7de44d40acf1f53023 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Mon, 25 Nov 2024 07:36:47 +0200 Subject: [PATCH 022/340] Update bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs Co-authored-by: Vincent Geddes <117534+vgeddes@users.noreply.github.com> --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index c9374651c563..4faebedd45b8 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -222,7 +222,7 @@ pub mod pallet { Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); // Set nonce flag to true - >::set(envelope.nonce.into()); + Nonce::::set(envelope.nonce.into()); Ok(()) } From 7e234629e09c8882dc14fa4d5a3bf6368f1f5a03 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 07:37:23 +0200 Subject: [PATCH 023/340] rename NonceBitmap --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 2 +- bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 4faebedd45b8..1a85454218b9 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -171,7 +171,7 @@ pub mod pallet { /// The nonce of the message been processed or not #[pallet::storage] - pub type NoncesBitmap = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; + pub type NonceBitmap = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; /// The current operating mode of the pallet. #[pallet::storage] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs index eecf07b1f830..150f6028b129 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/types.rs @@ -2,4 +2,4 @@ // SPDX-FileCopyrightText: 2023 Snowfork use snowbridge_core::sparse_bitmap::SparseBitmapImpl; -pub type Nonce = SparseBitmapImpl>; +pub type Nonce = SparseBitmapImpl>; From 68f5a8e586de211f94eeeee0a023a61bfc083fb6 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 08:09:52 +0200 Subject: [PATCH 024/340] config T::XcmPrologueFee: Balance --- .../snowbridge/pallets/inbound-queue-v2/src/lib.rs | 13 +++++++++++-- .../snowbridge/pallets/inbound-queue-v2/src/mock.rs | 10 ++++++---- .../snowbridge/pallets/inbound-queue-v2/src/test.rs | 11 +++++------ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 1a85454218b9..cc95bb602a56 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -39,13 +39,19 @@ mod test; use codec::{Decode, DecodeAll, Encode}; use envelope::Envelope; -use frame_support::PalletError; use frame_system::ensure_signed; use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; use types::Nonce; use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}; +use frame_support::{ + traits::{ + fungible::{Inspect, Mutate}, + }, + PalletError, +}; +use frame_system::pallet_prelude::*; use snowbridge_core::{ inbound::{Message, VerificationError, Verifier}, @@ -64,12 +70,13 @@ pub use pallet::*; pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; +type BalanceOf = +<::Token as Inspect<::AccountId>>::Balance; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; #[pallet::pallet] pub struct Pallet(_); @@ -95,6 +102,8 @@ pub mod pallet { /// AssetHub parachain ID type AssetHubParaId: Get; type MessageConverter: ConvertMessage; + type XcmPrologueFee: Get>; + type Token: Mutate + Inspect; #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 63768340c193..8d39dc0931f4 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -2,8 +2,8 @@ // SPDX-FileCopyrightText: 2023 Snowfork use super::*; -use crate::{self as inbound_queue}; -use frame_support::{derive_impl, parameter_types, traits::ConstU32}; +use crate::{self as inbound_queue_v2}; +use frame_support::{derive_impl, parameter_types, traits::{ConstU32, ConstU128}}; use hex_literal::hex; use snowbridge_beacon_primitives::{ types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, @@ -29,7 +29,7 @@ frame_support::construct_runtime!( System: frame_system::{Pallet, Call, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, EthereumBeaconClient: snowbridge_pallet_ethereum_client::{Pallet, Call, Storage, Event}, - InboundQueue: inbound_queue::{Pallet, Call, Storage, Event}, + InboundQueue: inbound_queue_v2::{Pallet, Call, Storage, Event}, } ); @@ -149,7 +149,7 @@ parameter_types! { pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); } -impl inbound_queue::Config for Test { +impl inbound_queue_v2::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; type XcmSender = MockXcmSender; @@ -158,6 +158,8 @@ impl inbound_queue::Config for Test { type AssetHubParaId = ConstU32<1000>; type MessageConverter = MessageToXcm; + type Token = Balances; + type XcmPrologueFee = ConstU128<1_000_000_000>; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index d2720f2dcf05..9554a702cf07 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -10,12 +10,11 @@ use sp_runtime::DispatchError; use crate::{mock::*, Error, Event as InboundQueueEvent}; use codec::DecodeLimit; -use snowbridge_router_primitives::inbound::v2::InboundAsset; +use snowbridge_router_primitives::inbound::v2::Asset; use sp_core::H256; use xcm::{ opaque::latest::{ prelude::{ClearOrigin, ReceiveTeleportedAsset}, - Asset, }, prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH, @@ -168,7 +167,7 @@ fn test_send_native_erc20_token_payload() { assert_eq!(expected_origin, inbound_message.origin); assert_eq!(1, inbound_message.assets.len()); - if let InboundAsset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { + if let Asset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { assert_eq!(expected_token_id, *token_id); assert_eq!(expected_value, *value); } else { @@ -196,7 +195,7 @@ fn test_send_foreign_erc20_token_payload() { assert_eq!(expected_origin, inbound_message.origin); assert_eq!(1, inbound_message.assets.len()); - if let InboundAsset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { + if let Asset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { assert_eq!(expected_token_id, *token_id); assert_eq!(expected_value, *value); } else { @@ -224,7 +223,7 @@ fn test_register_token_inbound_message_with_xcm_and_claimer() { assert_eq!(expected_origin, inbound_message.origin); assert_eq!(1, inbound_message.assets.len()); - if let InboundAsset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { + if let Asset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { assert_eq!(expected_token_id, *token_id); assert_eq!(expected_value, *value); } else { @@ -261,7 +260,7 @@ fn test_register_token_inbound_message_with_xcm_and_claimer() { #[test] fn encode_xcm() { new_tester().execute_with(|| { - let total_fee_asset: Asset = (Location::parent(), 1_000_000_000).into(); + let total_fee_asset: xcm::opaque::latest::Asset = (Location::parent(), 1_000_000_000).into(); let instructions: Xcm<()> = vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); From 02e808118589ab3df03a11baf5f51db336ca9ec1 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 09:13:28 +0200 Subject: [PATCH 025/340] burn fees --- Cargo.lock | 1 + .../pallets/inbound-queue-v2/src/lib.rs | 37 +++++++++++----- .../pallets/inbound-queue-v2/src/mock.rs | 39 ++++++++++++++++- .../pallets/inbound-queue-v2/src/test.rs | 7 ++-- bridges/snowbridge/primitives/core/Cargo.toml | 5 ++- .../snowbridge/primitives/core/src/fees.rs | 42 +++++++++++++++++++ bridges/snowbridge/primitives/core/src/lib.rs | 1 + 7 files changed, 116 insertions(+), 16 deletions(-) create mode 100644 bridges/snowbridge/primitives/core/src/fees.rs diff --git a/Cargo.lock b/Cargo.lock index 6d58f682ae76..a371125e7842 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24703,6 +24703,7 @@ dependencies = [ "frame-system 28.0.0", "hex", "hex-literal", + "log", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "scale-info", diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index cc95bb602a56..22ec58fe9996 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -39,26 +39,25 @@ mod test; use codec::{Decode, DecodeAll, Encode}; use envelope::Envelope; -use frame_system::ensure_signed; +use frame_support::{ + traits::fungible::{Inspect, Mutate}, + PalletError, +}; +use frame_system::{ensure_signed, pallet_prelude::*}; use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; use types::Nonce; use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}; -use frame_support::{ - traits::{ - fungible::{Inspect, Mutate}, - }, - PalletError, -}; -use frame_system::pallet_prelude::*; use snowbridge_core::{ + fees::burn_fees, inbound::{Message, VerificationError, Verifier}, BasicOperatingMode, }; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; pub use weights::WeightInfo; +use xcm_executor::traits::TransactAsset; #[cfg(feature = "runtime-benchmarks")] use snowbridge_beacon_primitives::BeaconHeader; @@ -70,8 +69,9 @@ pub use pallet::*; pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; +pub type AccountIdOf = ::AccountId; type BalanceOf = -<::Token as Inspect<::AccountId>>::Balance; + <::Token as Inspect<::AccountId>>::Balance; #[frame_support::pallet] pub mod pallet { use super::*; @@ -104,6 +104,7 @@ pub mod pallet { type MessageConverter: ConvertMessage; type XcmPrologueFee: Get>; type Token: Mutate + Inspect; + type AssetTransactor: TransactAsset; #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; } @@ -129,6 +130,8 @@ pub mod pallet { pub enum Error { /// Message came from an invalid outbound channel on the Ethereum side. InvalidGateway, + /// Account could not be converted to bytes + InvalidAccount, /// Message has an invalid envelope. InvalidEnvelope, /// Message has an unexpected nonce. @@ -193,7 +196,7 @@ pub mod pallet { #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::submit())] pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { - let _who = ensure_signed(origin)?; + let who = ensure_signed(origin)?; ensure!(!Self::operating_mode().is_halted(), Error::::Halted); // submit message to verifier for verification @@ -217,6 +220,12 @@ pub mod pallet { let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + // Burn the required fees for the static XCM message part + burn_fees::>( + Self::account_to_location(who)?, + T::XcmPrologueFee::get(), + )?; + // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: // T::RewardLeger::deposit(who, envelope.fee.into())?; // a. The submit extrinsic cost on BH @@ -249,4 +258,12 @@ pub mod pallet { Ok(()) } } + + impl Pallet { + pub fn account_to_location(account: AccountIdOf) -> Result> { + let account_bytes: [u8; 32] = + account.encode().try_into().map_err(|_| Error::::InvalidAccount)?; + Ok(Location::new(0, [AccountId32 { network: None, id: account_bytes }])) + } + } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 8d39dc0931f4..d406d5825f7b 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -3,7 +3,10 @@ use super::*; use crate::{self as inbound_queue_v2}; -use frame_support::{derive_impl, parameter_types, traits::{ConstU32, ConstU128}}; +use frame_support::{ + derive_impl, parameter_types, + traits::{ConstU128, ConstU32}, +}; use hex_literal::hex; use snowbridge_beacon_primitives::{ types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, @@ -20,6 +23,7 @@ use sp_runtime::{ }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; +use xcm_executor::{traits::TransactAsset, AssetsInHolding}; type Block = frame_system::mocking::MockBlock; @@ -160,10 +164,43 @@ impl inbound_queue_v2::Config for Test { MessageToXcm; type Token = Balances; type XcmPrologueFee = ConstU128<1_000_000_000>; + type AssetTransactor = SuccessfulTransactor; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; } +pub struct SuccessfulTransactor; +impl TransactAsset for SuccessfulTransactor { + fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { + Ok(()) + } + + fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { + Ok(()) + } + + fn withdraw_asset( + _what: &Asset, + _who: &Location, + _context: Option<&XcmContext>, + ) -> Result { + Ok(AssetsInHolding::default()) + } + + fn internal_transfer_asset( + _what: &Asset, + _from: &Location, + _to: &Location, + _context: &XcmContext, + ) -> Result { + Ok(AssetsInHolding::default()) + } +} + pub fn last_events(n: usize) -> Vec { frame_system::Pallet::::events() .into_iter() diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 9554a702cf07..0b9e49ca43b3 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -13,9 +13,7 @@ use codec::DecodeLimit; use snowbridge_router_primitives::inbound::v2::Asset; use sp_core::H256; use xcm::{ - opaque::latest::{ - prelude::{ClearOrigin, ReceiveTeleportedAsset}, - }, + opaque::latest::prelude::{ClearOrigin, ReceiveTeleportedAsset}, prelude::*, VersionedXcm, MAX_XCM_DECODE_DEPTH, }; @@ -260,7 +258,8 @@ fn test_register_token_inbound_message_with_xcm_and_claimer() { #[test] fn encode_xcm() { new_tester().execute_with(|| { - let total_fee_asset: xcm::opaque::latest::Asset = (Location::parent(), 1_000_000_000).into(); + let total_fee_asset: xcm::opaque::latest::Asset = + (Location::parent(), 1_000_000_000).into(); let instructions: Xcm<()> = vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index fa37c795b2d1..4f5935a9fa43 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -16,6 +16,7 @@ serde = { optional = true, features = ["alloc", "derive"], workspace = true } codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } hex-literal = { workspace = true, default-features = true } +log = { workspace = true } polkadot-parachain-primitives = { workspace = true } xcm = { workspace = true } @@ -33,9 +34,10 @@ snowbridge-beacon-primitives = { workspace = true } ethabi = { workspace = true } +xcm-executor = { workspace = true } + [dev-dependencies] hex = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } [features] default = ["std"] @@ -54,6 +56,7 @@ std = [ "sp-runtime/std", "sp-std/std", "xcm-builder/std", + "xcm-executor/std", "xcm/std", ] serde = ["dep:serde", "scale-info/serde"] diff --git a/bridges/snowbridge/primitives/core/src/fees.rs b/bridges/snowbridge/primitives/core/src/fees.rs new file mode 100644 index 000000000000..a9ae0407fbfe --- /dev/null +++ b/bridges/snowbridge/primitives/core/src/fees.rs @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use log; +use sp_runtime::{DispatchResult, SaturatedConversion, Saturating, TokenError}; +use xcm::opaque::latest::{Location, XcmContext}; +use xcm_executor::traits::TransactAsset; +const LOG_TARGET: &str = "xcm_fees"; + +/// Burns the fees embedded in the XCM for teleports. +pub fn burn_fees(dest: Location, fee: Balance) -> DispatchResult +where + AssetTransactor: TransactAsset, + Balance: Saturating + TryInto + Copy, +{ + let dummy_context = XcmContext { origin: None, message_id: Default::default(), topic: None }; + let fees = (Location::parent(), fee.saturated_into::()).into(); + + // Check if the asset can be checked out + AssetTransactor::can_check_out(&dest, &fees, &dummy_context).map_err(|error| { + log::error!( + target: LOG_TARGET, + "XCM asset check out failed with error {:?}", + error + ); + TokenError::FundsUnavailable + })?; + + // Check out the asset + AssetTransactor::check_out(&dest, &fees, &dummy_context); + + // Withdraw the asset and handle potential errors + AssetTransactor::withdraw_asset(&fees, &dest, None).map_err(|error| { + log::error!( + target: LOG_TARGET, + "XCM asset withdraw failed with error {:?}", + error + ); + TokenError::FundsUnavailable + })?; + + Ok(()) +} diff --git a/bridges/snowbridge/primitives/core/src/lib.rs b/bridges/snowbridge/primitives/core/src/lib.rs index d88e387d7c24..558ac43d0d31 100644 --- a/bridges/snowbridge/primitives/core/src/lib.rs +++ b/bridges/snowbridge/primitives/core/src/lib.rs @@ -8,6 +8,7 @@ #[cfg(test)] mod tests; +pub mod fees; pub mod inbound; pub mod location; pub mod operating_mode; From ee0bc2703795abc6b56d10039fa35ae60fa24af3 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 10:23:04 +0200 Subject: [PATCH 026/340] fix westend config --- Cargo.lock | 1 + .../primitives/router/src/inbound/mod.rs | 42 +++++------ .../primitives/router/src/inbound/v1.rs | 10 +-- .../src/tests/snowbridge.rs | 5 +- .../src/bridge_to_ethereum_config.rs | 2 +- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 1 + .../src/bridge_to_ethereum_config.rs | 9 +-- .../bridge-hub-westend/src/weights/mod.rs | 1 + .../snowbridge_pallet_inbound_queue_v2.rs | 69 +++++++++++++++++++ 9 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs diff --git a/Cargo.lock b/Cargo.lock index a371125e7842..5db995eae135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2826,6 +2826,7 @@ dependencies = [ "snowbridge-outbound-queue-runtime-api 0.2.0", "snowbridge-pallet-ethereum-client 0.2.0", "snowbridge-pallet-inbound-queue 0.2.0", + "snowbridge-pallet-inbound-queue-v2", "snowbridge-pallet-outbound-queue 0.2.0", "snowbridge-pallet-system 0.2.0", "snowbridge-router-primitives 0.9.0", diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 1e43bd7544cf..5bf5258f3c4c 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -10,28 +10,30 @@ use sp_std::marker::PhantomData; use xcm::prelude::{AccountKey20, Ethereum, GlobalConsensus, Location}; use xcm_executor::traits::ConvertLocation; -pub struct GlobalConsensusEthereumConvertsFor(PhantomData); -impl ConvertLocation for GlobalConsensusEthereumConvertsFor - where - AccountId: From<[u8; 32]> + Clone, +pub struct EthereumLocationsConverterFor(PhantomData); +impl ConvertLocation for EthereumLocationsConverterFor +where + AccountId: From<[u8; 32]> + Clone, { - fn convert_location(location: &Location) -> Option { - match location.unpack() { - (2, [GlobalConsensus(Ethereum { chain_id })]) => - Some(Self::from_chain_id(chain_id).into()), - (2, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) => - Some(Self::from_chain_id_with_key(chain_id, *key).into()), - _ => None, - } - } + fn convert_location(location: &Location) -> Option { + match location.unpack() { + (2, [GlobalConsensus(Ethereum { chain_id })]) => + Some(Self::from_chain_id(chain_id).into()), + (2, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) => + Some(Self::from_chain_id_with_key(chain_id, *key).into()), + _ => None, + } + } } -impl GlobalConsensusEthereumConvertsFor { - pub fn from_chain_id(chain_id: &u64) -> [u8; 32] { - (b"ethereum-chain", chain_id).using_encoded(blake2_256) - } - pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] { - (b"ethereum-chain", chain_id, key).using_encoded(blake2_256) - } + +impl EthereumLocationsConverterFor { + pub fn from_chain_id(chain_id: &u64) -> [u8; 32] { + (b"ethereum-chain", chain_id).using_encoded(blake2_256) + } + pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] { + (b"ethereum-chain", chain_id, key).using_encoded(blake2_256) + } } + pub type CallIndex = [u8; 2]; diff --git a/bridges/snowbridge/primitives/router/src/inbound/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs index d413674970b2..9e2191d651a0 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v1.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v1.rs @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Converts messages from Ethereum to XCM messages -use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; +use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; use codec::{Decode, Encode}; use core::marker::PhantomData; use frame_support::{traits::tokens::Balance as BalanceT, PalletError}; @@ -247,7 +247,7 @@ MessageToXcm< let bridge_location = Location::new(2, GlobalConsensus(network)); - let owner = GlobalConsensusEthereumConvertsFor::<[u8; 32]>::from_chain_id(&chain_id); + let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); let asset_id = Self::convert_token_address(network, token); let create_call_index: [u8; 2] = CreateAssetCall::get(); let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); @@ -409,6 +409,8 @@ MessageToXcm< // Final destination is a 32-byte account on AssetHub Destination::AccountId32 { id } => Ok(Location::new(0, [AccountId32 { network: None, id }])), + // Forwarding to a destination parachain is not allowed for PNA and is validated on the + // Ethereum side. https://github.com/Snowfork/snowbridge/blob/e87ddb2215b513455c844463a25323bb9c01ff36/contracts/src/Assets.sol#L216-L224 _ => Err(ConvertMessageError::InvalidDestination), }?; @@ -471,7 +473,7 @@ mod tests { let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); let account = - GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location) + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location) .unwrap(); assert_eq!(account, expected_account); @@ -482,7 +484,7 @@ mod tests { let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); assert_eq!( - GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location), + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location), None, ); } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index ffa60a4f52e7..1f6fbf3d930f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -21,8 +21,9 @@ use frame_support::pallet_prelude::TypeInfo; use hex_literal::hex; use rococo_westend_system_emulated_network::asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner; use snowbridge_core::{outbound::OperatingMode, AssetMetadata, TokenIdOf}; -use snowbridge_router_primitives::inbound::{ - Command, Destination, EthereumLocationsConverterFor, MessageV1, VersionedMessage, +use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; +use snowbridge_router_primitives::inbound::v1::{ + Command, Destination, MessageV1, VersionedMessage, }; use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs index be7005b5379a..16fcaeab5bad 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_ethereum_config.rs @@ -24,7 +24,7 @@ use crate::{ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; -use snowbridge_router_primitives::{inbound::MessageToXcm, outbound::EthereumBlobExporter}; +use snowbridge_router_primitives::{inbound::v1::MessageToXcm, outbound::EthereumBlobExporter}; use sp_core::H160; use testnet_parachains_constants::rococo::{ currency::*, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 637e7c710640..5169a51c3702 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -110,6 +110,7 @@ snowbridge-system-runtime-api = { workspace = true } snowbridge-core = { workspace = true } snowbridge-pallet-ethereum-client = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } +snowbridge-pallet-inbound-queue-v2 = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-router-primitives = { workspace = true } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index def9b1af6207..5d783aac34cc 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -25,9 +25,7 @@ use crate::{ use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; -use snowbridge_router_primitives::{ - outbound::{v1::EthereumBlobExporter, v2::EthereumBlobExporter as EthereumBlobExporterV2}, -}; +use snowbridge_router_primitives::outbound::EthereumBlobExporter; use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, @@ -41,7 +39,7 @@ use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ - traits::{ConstU32, ConstU8, Keccak256}, + traits::{ConstU32, ConstU8, ConstU128, Keccak256}, FixedU128, }; use xcm::prelude::{GlobalConsensus, InteriorLocation, Location, Parachain}; @@ -116,6 +114,9 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type Helper = Runtime; type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; type AssetHubParaId = ConstU32<1000>; + type Token = Balances; + type XcmPrologueFee = ConstU128<1_000_000_000>; + type AssetTransactor = ::AssetTransactor; type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm, EthereumSystem>; } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs index c1c5c337aca8..ee8ad5f31794 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs @@ -47,6 +47,7 @@ pub mod xcm; pub mod snowbridge_pallet_ethereum_client; pub mod snowbridge_pallet_inbound_queue; +pub mod snowbridge_pallet_inbound_queue_v2; pub mod snowbridge_pallet_outbound_queue; pub mod snowbridge_pallet_system; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs new file mode 100644 index 000000000000..8cfa14981b3a --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue_v2.rs @@ -0,0 +1,69 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `snowbridge_pallet_inbound_queue` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-09-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `macbook pro 14 m2`, CPU: `m2-arm64` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/polkadot-parachain +// benchmark +// pallet +// --chain=bridge-hub-rococo-dev +// --pallet=snowbridge_inbound_queue +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --steps +// 50 +// --repeat +// 20 +// --output +// ./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_inbound_queue.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `snowbridge_pallet_inbound_queue_v2`. +pub struct WeightInfo(PhantomData); +impl snowbridge_pallet_inbound_queue_v2::WeightInfo for WeightInfo { + /// Storage: EthereumInboundQueue PalletOperatingMode (r:1 w:0) + /// Proof: EthereumInboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: EthereumBeaconClient ExecutionHeaders (r:1 w:0) + /// Proof: EthereumBeaconClient ExecutionHeaders (max_values: None, max_size: Some(136), added: 2611, mode: MaxEncodedLen) + /// Storage: EthereumInboundQueue Nonce (r:1 w:1) + /// Proof: EthereumInboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn submit() -> Weight { + // Proof Size summary in bytes: + // Measured: `800` + // Estimated: `7200` + // Minimum execution time: 200_000_000 picoseconds. + Weight::from_parts(200_000_000, 0) + .saturating_add(Weight::from_parts(0, 7200)) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(6)) + } +} From 430927224cc1fa2d09e648c2421047aec19ef5ca Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 10:40:32 +0200 Subject: [PATCH 027/340] use sendcontroller --- Cargo.lock | 1 + .../pallets/inbound-queue-v2/Cargo.toml | 2 ++ .../pallets/inbound-queue-v2/src/lib.rs | 24 ++++++++++++------- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5db995eae135..ef3f6cc673ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25060,6 +25060,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm 7.0.0", + "staging-xcm-builder 7.0.0", "staging-xcm-executor 7.0.0", ] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml index fcbb41743f45..ecebc677e997 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml @@ -34,6 +34,7 @@ sp-runtime = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } +xcm-builder = { workspace = true } snowbridge-core = { workspace = true } snowbridge-router-primitives = { workspace = true } @@ -69,6 +70,7 @@ std = [ "sp-runtime/std", "sp-std/std", "xcm-executor/std", + "xcm-builder/std", "xcm/std", ] runtime-benchmarks = [ diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 22ec58fe9996..f7937e26d0d9 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -48,23 +48,25 @@ use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; use types::Nonce; -use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm}; +use xcm::prelude::{Junction::*, Location, SendError as XcmpSendError}; use snowbridge_core::{ fees::burn_fees, inbound::{Message, VerificationError, Verifier}, + sparse_bitmap::SparseBitmap, BasicOperatingMode, }; -use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message as MessageV2}; +use snowbridge_router_primitives::inbound::v2::{ + ConvertMessage, ConvertMessageError, Message as MessageV2, +}; pub use weights::WeightInfo; +use xcm::{VersionedLocation, VersionedXcm}; +use xcm_builder::SendController; use xcm_executor::traits::TransactAsset; #[cfg(feature = "runtime-benchmarks")] use snowbridge_beacon_primitives::BeaconHeader; -use snowbridge_core::sparse_bitmap::SparseBitmap; -use snowbridge_router_primitives::inbound::v2::ConvertMessageError; - pub use pallet::*; pub const LOG_TARGET: &str = "snowbridge-inbound-queue:v2"; @@ -94,7 +96,7 @@ pub mod pallet { type Verifier: Verifier; /// XCM message sender - type XcmSender: SendXcm; + type XcmSender: SendController<::RuntimeOrigin>; /// Address of the Gateway contract #[pallet::constant] type GatewayAddress: Get; @@ -196,7 +198,7 @@ pub mod pallet { #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::submit())] pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { - let who = ensure_signed(origin)?; + let who = ensure_signed(origin.clone())?; ensure!(!Self::operating_mode().is_halted(), Error::::Halted); // submit message to verifier for verification @@ -235,8 +237,12 @@ pub mod pallet { // e. The reward // Attempt to forward XCM to AH - let dest = Location::new(1, [Parachain(T::AssetHubParaId::get())]); - let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; + let versioned_dest = Box::new(VersionedLocation::V5(Location::new( + 1, + [Parachain(T::AssetHubParaId::get())], + ))); + let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); + let message_id = T::XcmSender::send(origin, versioned_dest, versioned_xcm)?; // TODO origin should be this parachain, maybe Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); // Set nonce flag to true From 75e79fad682bd86451464bc95117f6e22b86dfd9 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:02:37 +0100 Subject: [PATCH 028/340] ci: fix node-bench-regression-guard for master (#6589) Closes https://github.com/paritytech/ci_cd/issues/1067 --- .github/workflows/tests-misc.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/tests-misc.yml b/.github/workflows/tests-misc.yml index cca32650b106..decd88f2e84c 100644 --- a/.github/workflows/tests-misc.yml +++ b/.github/workflows/tests-misc.yml @@ -165,12 +165,14 @@ jobs: - name: Download artifact (master run) uses: actions/download-artifact@v4.1.8 + continue-on-error: true with: name: cargo-check-benches-master-${{ github.sha }} path: ./artifacts/master - name: Download artifact (current run) uses: actions/download-artifact@v4.1.8 + continue-on-error: true with: name: cargo-check-benches-current-${{ github.sha }} path: ./artifacts/current @@ -183,6 +185,12 @@ jobs: exit 0 fi + # fail if no artifacts + if [ ! -d ./artifacts/master ] || [ ! -d ./artifacts/current ]; then + echo "No artifacts found" + exit 1 + fi + docker run --rm \ -v $PWD/artifacts/master:/artifacts/master \ -v $PWD/artifacts/current:/artifacts/current \ From de00eb6b5acee49a9b86498266fa9eebd7ef3822 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 12:13:55 +0200 Subject: [PATCH 029/340] runtime config for sendcontroller --- .../pallets/inbound-queue-v2/src/lib.rs | 5 ++- .../pallets/inbound-queue-v2/src/mock.rs | 42 +++++++++---------- .../src/tests/snowbridge.rs | 6 +-- .../src/bridge_to_ethereum_config.rs | 14 ++++--- 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index f7937e26d0d9..12d67d3115dd 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -22,6 +22,8 @@ //! * [`Call::submit`]: Submit a message for verification and dispatch the final destination //! parachain. #![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; pub mod api; mod envelope; @@ -48,6 +50,7 @@ use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; use types::Nonce; +use alloc::boxed::Box; use xcm::prelude::{Junction::*, Location, SendError as XcmpSendError}; use snowbridge_core::{ @@ -242,7 +245,7 @@ pub mod pallet { [Parachain(T::AssetHubParaId::get())], ))); let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); - let message_id = T::XcmSender::send(origin, versioned_dest, versioned_xcm)?; // TODO origin should be this parachain, maybe + let message_id = T::XcmSender::send(origin, versioned_dest, versioned_xcm)?; Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); // Set nonce flag to true diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index d406d5825f7b..e724b2f4e842 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -22,8 +22,11 @@ use sp_runtime::{ BuildStorage, MultiSignature, }; use sp_std::{convert::From, default::Default}; -use xcm::{latest::SendXcm, prelude::*}; +use xcm::prelude::*; use xcm_executor::{traits::TransactAsset, AssetsInHolding}; +use xcm_builder::SendControllerWeightInfo; +use sp_runtime::DispatchError; +use sp_core::H256; type Block = frame_system::mocking::MockBlock; @@ -110,29 +113,26 @@ impl BenchmarkHelper for Test { fn initialize_storage(_: BeaconHeader, _: H256) {} } -// Mock XCM sender that always succeeds -pub struct MockXcmSender; -impl SendXcm for MockXcmSender { - type Ticket = Xcm<()>; - - fn validate( - dest: &mut Option, - xcm: &mut Option>, - ) -> SendResult { - if let Some(location) = dest { - match location.unpack() { - (_, [Parachain(1001)]) => return Err(XcmpSendError::NotApplicable), - _ => Ok((xcm.clone().unwrap(), Assets::default())), - } - } else { - Ok((xcm.clone().unwrap(), Assets::default())) - } +pub struct MockXcmSenderWeights; + +impl SendControllerWeightInfo for MockXcmSenderWeights { + fn send() -> Weight { + return Weight::default(); } +} + +// Mock XCM sender that always succeeds +pub struct MockXcmSender; - fn deliver(xcm: Self::Ticket) -> core::result::Result { - let hash = xcm.using_encoded(sp_io::hashing::blake2_256); - Ok(hash) +impl SendController for MockXcmSender { + type WeightInfo = MockXcmSenderWeights; + fn send( + _origin: mock::RuntimeOrigin, + _dest: Box, + _message: Box>, + ) -> Result { + Ok(H256::random().into()) } } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index 1f6fbf3d930f..3055043dd79c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -21,9 +21,9 @@ use frame_support::pallet_prelude::TypeInfo; use hex_literal::hex; use rococo_westend_system_emulated_network::asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner; use snowbridge_core::{outbound::OperatingMode, AssetMetadata, TokenIdOf}; -use snowbridge_router_primitives::inbound::EthereumLocationsConverterFor; -use snowbridge_router_primitives::inbound::v1::{ - Command, Destination, MessageV1, VersionedMessage, +use snowbridge_router_primitives::inbound::{ + v1::{Command, Destination, MessageV1, VersionedMessage}, + EthereumLocationsConverterFor, }; use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 5d783aac34cc..e9c4f3ad288b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -14,14 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -#[cfg(not(feature = "runtime-benchmarks"))] -use crate::XcmRouter; use crate::{ xcm_config, xcm_config::{TreasuryAccount, UniversalLocation}, Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumSystem, MessageQueue, Runtime, RuntimeEvent, TransactionByteFee, }; +#[cfg(not(feature = "runtime-benchmarks"))] +use crate::{PolkadotXcm, XcmRouter}; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; @@ -39,7 +39,7 @@ use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ - traits::{ConstU32, ConstU8, ConstU128, Keccak256}, + traits::{ConstU128, ConstU32, ConstU8, Keccak256}, FixedU128, }; use xcm::prelude::{GlobalConsensus, InteriorLocation, Location, Parachain}; @@ -106,7 +106,7 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Verifier = snowbridge_pallet_ethereum_client::Pallet; #[cfg(not(feature = "runtime-benchmarks"))] - type XcmSender = XcmRouter; + type XcmSender = PolkadotXcm; #[cfg(feature = "runtime-benchmarks")] type XcmSender = DoNothingRouter; type GatewayAddress = EthereumGatewayAddress; @@ -117,7 +117,11 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type Token = Balances; type XcmPrologueFee = ConstU128<1_000_000_000>; type AssetTransactor = ::AssetTransactor; - type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm, EthereumSystem>; + type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm< + EthereumNetwork, + ConstU8, + EthereumSystem, + >; } impl snowbridge_pallet_outbound_queue::Config for Runtime { From e709c9f3db017309d88d240cb32d62c2df6f0a52 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 25 Nov 2024 11:59:48 +0100 Subject: [PATCH 030/340] Error logging for send xcm to pallet-xcm (#6579) --- polkadot/xcm/pallet-xcm/src/lib.rs | 16 +++++++++++++--- .../xcm/xcm-builder/src/process_xcm_message.rs | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs index 5e0512c6a9fd..6360298b21c3 100644 --- a/polkadot/xcm/pallet-xcm/src/lib.rs +++ b/polkadot/xcm/pallet-xcm/src/lib.rs @@ -363,7 +363,10 @@ pub mod pallet { let message: Xcm<()> = (*message).try_into().map_err(|()| Error::::BadVersion)?; let message_id = Self::send_xcm(interior, dest.clone(), message.clone()) - .map_err(Error::::from)?; + .map_err(|error| { + tracing::error!(target: "xcm::pallet_xcm::send", ?error, ?dest, ?message, "XCM send failed with error"); + Error::::from(error) + })?; let e = Event::Sent { origin: origin_location, destination: dest, message, message_id }; Self::deposit_event(e); Ok(message_id) @@ -1800,7 +1803,10 @@ impl Pallet { if let Some(remote_xcm) = remote_xcm { let (ticket, price) = validate_send::(dest.clone(), remote_xcm.clone()) - .map_err(Error::::from)?; + .map_err(|error| { + tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM validate_send failed with error"); + Error::::from(error) + })?; if origin != Here.into_location() { Self::charge_fees(origin.clone(), price.clone()).map_err(|error| { tracing::error!( @@ -1810,7 +1816,11 @@ impl Pallet { Error::::FeesNotMet })?; } - let message_id = T::XcmRouter::deliver(ticket).map_err(Error::::from)?; + let message_id = T::XcmRouter::deliver(ticket) + .map_err(|error| { + tracing::error!(target: "xcm::pallet_xcm::execute_xcm_transfer", ?error, ?dest, ?remote_xcm, "XCM deliver failed with error"); + Error::::from(error) + })?; let e = Event::Sent { origin, destination: dest, message: remote_xcm, message_id }; Self::deposit_event(e); diff --git a/polkadot/xcm/xcm-builder/src/process_xcm_message.rs b/polkadot/xcm/xcm-builder/src/process_xcm_message.rs index 8dafbf66adf0..67c05c116e9d 100644 --- a/polkadot/xcm/xcm-builder/src/process_xcm_message.rs +++ b/polkadot/xcm/xcm-builder/src/process_xcm_message.rs @@ -58,7 +58,7 @@ impl< let message = Xcm::::try_from(versioned_message).map_err(|_| { log::trace!( target: LOG_TARGET, - "Failed to convert `VersionedXcm` into `XcmV3`.", + "Failed to convert `VersionedXcm` into `xcm::prelude::Xcm`!", ); ProcessMessageError::Unsupported From 3653b7f5ec02cf511bc66f41d151c067fe3da92c Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 25 Nov 2024 13:15:50 +0200 Subject: [PATCH 031/340] wip --- .../pallets/inbound-queue-v2/src/lib.rs | 39 +++++------- .../primitives/router/src/inbound/v2.rs | 4 ++ .../bridge-hub-westend/src/tests/mod.rs | 1 + .../src/tests/snowbridge_v2.rs | 62 +++++++++++++++++++ 4 files changed, 83 insertions(+), 23 deletions(-) create mode 100644 cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 12d67d3115dd..0ca2ee10be7d 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -51,7 +51,7 @@ use sp_core::H160; use sp_std::vec; use types::Nonce; use alloc::boxed::Box; -use xcm::prelude::{Junction::*, Location, SendError as XcmpSendError}; +use xcm::prelude::{Junction::*, Location, *}; use snowbridge_core::{ fees::burn_fees, @@ -170,22 +170,6 @@ pub mod pallet { Fees, } - impl From for Error { - fn from(e: XcmpSendError) -> Self { - match e { - XcmpSendError::NotApplicable => Error::::Send(SendError::NotApplicable), - XcmpSendError::Unroutable => Error::::Send(SendError::NotRoutable), - XcmpSendError::Transport(_) => Error::::Send(SendError::Transport), - XcmpSendError::DestinationUnsupported => - Error::::Send(SendError::DestinationUnsupported), - XcmpSendError::ExceedsMaxMessageSize => - Error::::Send(SendError::ExceedsMaxMessageSize), - XcmpSendError::MissingArgument => Error::::Send(SendError::MissingArgument), - XcmpSendError::Fees => Error::::Send(SendError::Fees), - } - } - } - /// The nonce of the message been processed or not #[pallet::storage] pub type NonceBitmap = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; @@ -240,12 +224,8 @@ pub mod pallet { // e. The reward // Attempt to forward XCM to AH - let versioned_dest = Box::new(VersionedLocation::V5(Location::new( - 1, - [Parachain(T::AssetHubParaId::get())], - ))); - let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); - let message_id = T::XcmSender::send(origin, versioned_dest, versioned_xcm)?; + + let message_id = Self::send_xcm(origin, xcm, T::AssetHubParaId::get())?; Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); // Set nonce flag to true @@ -274,5 +254,18 @@ pub mod pallet { account.encode().try_into().map_err(|_| Error::::InvalidAccount)?; Ok(Location::new(0, [AccountId32 { network: None, id: account_bytes }])) } + + pub fn send_xcm(origin: OriginFor, xcm: Xcm<()>, dest_para_id: u32) -> Result { + let versioned_dest = Box::new(VersionedLocation::V5(Location::new( + 1, + [Parachain(dest_para_id)], + ))); + let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); + Ok(T::XcmSender::send(origin, versioned_dest, versioned_xcm)?) + } + + pub fn do_convert(message: MessageV2) -> Result, Error> { + Ok(T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?) + } } } diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index b8f1ab8ac782..133021a5f7a5 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -140,6 +140,8 @@ for MessageToXcm } } + log::debug!(target: LOG_TARGET,"extracted assets"); + if let Some(claimer) = message.claimer { let claimer = Junction::decode(&mut claimer.as_ref()) .map_err(|_| ConvertMessageError::InvalidClaimer)?; @@ -147,6 +149,8 @@ for MessageToXcm instructions.push(SetAssetClaimer { location: claimer_location }); } + log::debug!(target: LOG_TARGET,"extracted claimer"); + // Set the alias origin to the original sender on Ethereum. Important to be before the // arbitrary XCM that is appended to the message on the next line. instructions.push(AliasOrigin(origin_location.into())); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 6c1cdb98e8b2..cd826e3bfb29 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -20,6 +20,7 @@ mod claim_assets; mod register_bridged_assets; mod send_xcm; mod snowbridge; +mod snowbridge_v2; mod teleport; mod transact; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs new file mode 100644 index 000000000000..2c51c25a26f8 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -0,0 +1,62 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::imports::*; +use hex_literal::hex; +use bridge_hub_westend_runtime::EthereumInboundQueueV2; +use snowbridge_router_primitives::inbound::v2::Message; +use bridge_hub_westend_runtime::RuntimeOrigin; +use sp_core::H160; +use snowbridge_router_primitives::inbound::v2::Asset::NativeTokenERC20; + +/// Calculates the XCM prologue fee for sending an XCM to AH. +const INITIAL_FUND: u128 = 5_000_000_000_000; +#[test] +fn xcm_prologue_fee() { + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + let relayer = BridgeHubWestendSender::get(); + let claimer = AssetHubWestendReceiver::get(); + BridgeHubWestend::fund_accounts(vec![ + (relayer.clone(), INITIAL_FUND), + ]); + + let token_id_1 = H160::random(); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let claimer = AccountId32{network: None, id: claimer.into()}; + let claimer_bytes = claimer.encode(); + + let message = Message{ + origin: H160::random(), + assets: vec![ + NativeTokenERC20 { + token_id: token_id_1, + value: 1_000_000_000, + } + ], + xcm: hex!().to_vec(), + claimer: Some(claimer_bytes) + }; + let xcm = EthereumInboundQueueV2::do_convert(message).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(RuntimeOrigin::root(relayer.clone()), xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); +} From c422d8bbae8ba327597582203f05d30c26ef1392 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:12:22 +0100 Subject: [PATCH 032/340] ci: improve workflow-stopper ux (#6632) PR addresses https://github.com/paritytech/polkadot-sdk/pull/6265#issuecomment-2497506857 cc https://github.com/paritytech/ci_cd/issues/1084 --- .github/workflows/build-misc.yml | 4 ++-- .github/workflows/check-frame-omni-bencher.yml | 4 ++-- .github/workflows/checks-quick.yml | 2 +- .github/workflows/checks.yml | 6 +++--- .github/workflows/docs.yml | 4 ++-- .github/workflows/tests-linux-stable.yml | 8 ++++---- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/build-misc.yml b/.github/workflows/build-misc.yml index a9b433a94b64..c4a7281b9ebc 100644 --- a/.github/workflows/build-misc.yml +++ b/.github/workflows/build-misc.yml @@ -44,7 +44,7 @@ jobs: forklift cargo check -p rococo-runtime forklift cargo check -p polkadot-test-runtime - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -73,7 +73,7 @@ jobs: cd ./substrate/bin/utils/subkey forklift cargo build --locked --release - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} diff --git a/.github/workflows/check-frame-omni-bencher.yml b/.github/workflows/check-frame-omni-bencher.yml index b47c9d49feaf..bc0ff82b6774 100644 --- a/.github/workflows/check-frame-omni-bencher.yml +++ b/.github/workflows/check-frame-omni-bencher.yml @@ -41,7 +41,7 @@ jobs: forklift cargo build --locked --quiet --release -p asset-hub-westend-runtime --features runtime-benchmarks forklift cargo run --locked --release -p frame-omni-bencher --quiet -- v1 benchmark pallet --runtime target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.compact.compressed.wasm --all --steps 2 --repeat 1 --quiet - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -99,7 +99,7 @@ jobs: echo "Running command: $cmd" eval "$cmd" - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} diff --git a/.github/workflows/checks-quick.yml b/.github/workflows/checks-quick.yml index 4fcaf80c83fc..c733a2517cb8 100644 --- a/.github/workflows/checks-quick.yml +++ b/.github/workflows/checks-quick.yml @@ -30,7 +30,7 @@ jobs: id: required run: cargo +nightly fmt --all -- --check - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c240504fa1e7..02428711811f 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -36,7 +36,7 @@ jobs: cargo clippy --all-targets --locked --workspace --quiet cargo clippy --all-targets --all-features --locked --workspace --quiet - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -62,7 +62,7 @@ jobs: # experimental code may rely on try-runtime and vice-versa forklift cargo check --locked --all --features try-runtime,experimental --quiet - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -91,7 +91,7 @@ jobs: ./check-features-variants.sh cd - - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cc84e7f9ad3b..b7c70c9e6d66 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -29,7 +29,7 @@ jobs: env: RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -69,7 +69,7 @@ jobs: retention-days: 1 if-no-files-found: error - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} diff --git a/.github/workflows/tests-linux-stable.yml b/.github/workflows/tests-linux-stable.yml index b9d0605b2495..3f8dc4fe1240 100644 --- a/.github/workflows/tests-linux-stable.yml +++ b/.github/workflows/tests-linux-stable.yml @@ -37,7 +37,7 @@ jobs: id: required run: WASM_BUILD_NO_COLOR=1 forklift cargo test -p staging-node-cli --release --locked -- --ignored - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -63,7 +63,7 @@ jobs: id: required run: forklift cargo nextest run --workspace --features runtime-benchmarks benchmark --locked --cargo-profile testnet --cargo-quiet - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -113,7 +113,7 @@ jobs: if: ${{ matrix.partition == '1/3' }} run: forklift cargo nextest run -p sp-api-test --features enable-staging-api --cargo-quiet - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} @@ -155,7 +155,7 @@ jobs: --filter-expr " !test(/all_security_features_work/) - test(/nonexistent_cache_dir/)" \ --partition count:${{ matrix.partition }} \ - name: Stop all workflows if failed - if: ${{ failure() && steps.required.conclusion == 'failure' }} + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper with: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} From 41b6915ecb4b5691cdeeb585e26d46c4897ae151 Mon Sep 17 00:00:00 2001 From: jpserrat <35823283+jpserrat@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:54:04 -0300 Subject: [PATCH 033/340] remove ReportCollator message (#6628) Closes #6415 # Description Remove unused message `ReportCollator` and test related to this message on the collator protocol validator side. cc: @tdimitrov --------- Co-authored-by: Tsvetomir Dimitrov Co-authored-by: command-bot <> --- .../src/collator_side/mod.rs | 2 +- .../src/validator_side/mod.rs | 3 - .../src/validator_side/tests/mod.rs | 60 ------------------- polkadot/node/subsystem-types/src/messages.rs | 15 ++--- .../src/node/collators/collator-protocol.md | 6 -- .../src/node/subsystems-and-jobs.md | 7 +-- .../src/types/overseer-protocol.md | 3 - prdoc/pr_6628.prdoc | 12 ++++ 8 files changed, 20 insertions(+), 88 deletions(-) create mode 100644 prdoc/pr_6628.prdoc diff --git a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs index 504b0d716043..d77480272cb4 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -899,7 +899,7 @@ async fn process_msg( ); } }, - msg @ (ReportCollator(..) | Invalid(..) | Seconded(..)) => { + msg @ (Invalid(..) | Seconded(..)) => { gum::warn!( target: LOG_TARGET, "{:?} message is not expected on the collator side of the protocol", diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 86358f503d04..36ec959c3406 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -1462,9 +1462,6 @@ async fn process_msg( "DistributeCollation message is not expected on the validator side of the protocol", ); }, - ReportCollator(id) => { - report_collator(&mut state.reputation, ctx.sender(), &state.peer_data, id).await; - }, NetworkBridgeUpdate(event) => { if let Err(e) = handle_network_msg(ctx, state, keystore, event).await { gum::warn!( diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index 7bc61dd4ebec..f2f23c188a66 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -638,66 +638,6 @@ fn act_on_advertisement_v2() { }); } -// Test that other subsystems may modify collators' reputations. -#[test] -fn collator_reporting_works() { - let test_state = TestState::default(); - - test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { - let TestHarness { mut virtual_overseer, .. } = test_harness; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - - let peer_b = PeerId::random(); - let peer_c = PeerId::random(); - - connect_and_declare_collator( - &mut virtual_overseer, - peer_b, - test_state.collators[0].clone(), - test_state.chain_ids[0], - CollationVersion::V1, - ) - .await; - - connect_and_declare_collator( - &mut virtual_overseer, - peer_c, - test_state.collators[1].clone(), - test_state.chain_ids[0], - CollationVersion::V1, - ) - .await; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::ReportCollator(test_state.collators[0].public()), - ) - .await; - - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer, rep)), - ) => { - assert_eq!(peer, peer_b); - assert_eq!(rep.value, COST_REPORT_BAD.cost_or_benefit()); - } - ); - - virtual_overseer - }); -} - // Test that we verify the signatures on `Declare` and `AdvertiseCollation` messages. #[test] fn collator_authentication_verification_works() { diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index 28a3a1ab82ab..b541f9519219 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -48,12 +48,12 @@ use polkadot_primitives::{ CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, - CandidateIndex, CollatorId, CoreIndex, DisputeState, ExecutorParams, GroupIndex, - GroupRotationInfo, Hash, HeadData, Header as BlockHeader, Id as ParaId, InboundDownwardMessage, - InboundHrmpMessage, MultiDisputeStatementSet, NodeFeatures, OccupiedCoreAssumption, - PersistedValidationData, PvfCheckStatement, PvfExecKind as RuntimePvfExecKind, SessionIndex, - SessionInfo, SignedAvailabilityBitfield, SignedAvailabilityBitfields, ValidationCode, - ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, + CandidateIndex, CoreIndex, DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, + HeadData, Header as BlockHeader, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, + MultiDisputeStatementSet, NodeFeatures, OccupiedCoreAssumption, PersistedValidationData, + PvfCheckStatement, PvfExecKind as RuntimePvfExecKind, SessionIndex, SessionInfo, + SignedAvailabilityBitfield, SignedAvailabilityBitfields, ValidationCode, ValidationCodeHash, + ValidatorId, ValidatorIndex, ValidatorSignature, }; use polkadot_statement_table::v2::Misbehavior; use std::{ @@ -250,9 +250,6 @@ pub enum CollatorProtocolMessage { /// The core index where the candidate should be backed. core_index: CoreIndex, }, - /// Report a collator as having provided an invalid collation. This should lead to disconnect - /// and blacklist of the collator. - ReportCollator(CollatorId), /// Get a network bridge update. #[from] NetworkBridgeUpdate(NetworkBridgeEvent), diff --git a/polkadot/roadmap/implementers-guide/src/node/collators/collator-protocol.md b/polkadot/roadmap/implementers-guide/src/node/collators/collator-protocol.md index 432d9ab69bab..586a4169b5bc 100644 --- a/polkadot/roadmap/implementers-guide/src/node/collators/collator-protocol.md +++ b/polkadot/roadmap/implementers-guide/src/node/collators/collator-protocol.md @@ -151,12 +151,6 @@ time per relay parent. This reduces the bandwidth requirements and as we can sec the others are probably not required anyway. If the request times out, we need to note the collator as being unreliable and reduce its priority relative to other collators. -As a validator, once the collation has been fetched some other subsystem will inspect and do deeper validation of the -collation. The subsystem will report to this subsystem with a [`CollatorProtocolMessage`][CPM]`::ReportCollator`. In -that case, if we are connected directly to the collator, we apply a cost to the `PeerId` associated with the collator -and potentially disconnect or blacklist it. If the collation is seconded, we notify the collator and apply a benefit to -the `PeerId` associated with the collator. - ### Interaction with [Candidate Backing][CB] As collators advertise the availability, a validator will simply second the first valid parablock candidate per relay diff --git a/polkadot/roadmap/implementers-guide/src/node/subsystems-and-jobs.md b/polkadot/roadmap/implementers-guide/src/node/subsystems-and-jobs.md index a3ca7347eb63..a96f3fa3d4a0 100644 --- a/polkadot/roadmap/implementers-guide/src/node/subsystems-and-jobs.md +++ b/polkadot/roadmap/implementers-guide/src/node/subsystems-and-jobs.md @@ -129,7 +129,6 @@ digraph { cand_sel -> coll_prot [arrowhead = "diamond", label = "FetchCollation"] cand_sel -> cand_back [arrowhead = "onormal", label = "Second"] - cand_sel -> coll_prot [arrowhead = "onormal", label = "ReportCollator"] cand_val -> runt_api [arrowhead = "diamond", label = "Request::PersistedValidationData"] cand_val -> runt_api [arrowhead = "diamond", label = "Request::ValidationCode"] @@ -231,7 +230,7 @@ sequenceDiagram VS ->> CandidateSelection: Collation - Note over CandidateSelection: Lots of other machinery in play here,
but there are only three outcomes from the
perspective of the `CollatorProtocol`: + Note over CandidateSelection: Lots of other machinery in play here,
but there are only two outcomes from the
perspective of the `CollatorProtocol`: alt happy path CandidateSelection -->> VS: FetchCollation @@ -242,10 +241,6 @@ sequenceDiagram NB ->> VS: Collation Deactivate VS - else collation invalid or unexpected - CandidateSelection ->> VS: ReportCollator - VS ->> NB: ReportPeer - else CandidateSelection already selected a different candidate Note over CandidateSelection: silently drop end diff --git a/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md b/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md index 85415e42a11c..cb862440727b 100644 --- a/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md +++ b/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md @@ -436,9 +436,6 @@ enum CollatorProtocolMessage { DistributeCollation(CandidateReceipt, PoV, Option>), /// Fetch a collation under the given relay-parent for the given ParaId. FetchCollation(Hash, ParaId, ResponseChannel<(CandidateReceipt, PoV)>), - /// Report a collator as having provided an invalid collation. This should lead to disconnect - /// and blacklist of the collator. - ReportCollator(CollatorId), /// Note a collator as having provided a good collation. NoteGoodCollation(CollatorId, SignedFullStatement), /// Notify a collator that its collation was seconded. diff --git a/prdoc/pr_6628.prdoc b/prdoc/pr_6628.prdoc new file mode 100644 index 000000000000..7ea0c4968385 --- /dev/null +++ b/prdoc/pr_6628.prdoc @@ -0,0 +1,12 @@ +title: "Remove ReportCollator message" + +doc: + - audience: Node Dev + description: | + Remove unused message ReportCollator and test related to this message on the collator protocol validator side. + +crates: + - name: polkadot-node-subsystem-types + bump: patch + - name: polkadot-collator-protocol + bump: major \ No newline at end of file From 6d5f8141ad42d7a528a09715c492974550709b92 Mon Sep 17 00:00:00 2001 From: Tarek Mohamed Abdalla Date: Mon, 25 Nov 2024 19:33:32 +0200 Subject: [PATCH 034/340] rpc server: fix subscription id_provider being reset to default one. (#6588) # Description The PR ensures that the id_provider variable is cloned instead of taken, which can help prevent issues related id provider being reset to the default. In [a test in moonbeam](https://github.com/moonbeam-foundation/moonbeam/blob/c6d07d703dfcdd94cc311fa83b553071b7d433ff/test/suites/dev/moonbase/test-subscription/test-subscription.ts#L20-L31) we found that the id_provider is being reset somehow and changed to the default one. Changing .take() to .clone() would fix the issue. # Checklist * [x] My PR includes a detailed description as outlined in the "Description" and its two subsections above. * [ ] My PR follows the [labeling requirements]( https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process ) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. * [ ] I have made corresponding changes to the documentation (if applicable) * [ ] I have added tests that prove my fix is effective or that my feature works (if applicable) --------- Co-authored-by: Niklas Adolfsson --- prdoc/pr_6588.prdoc | 14 ++++++++++++++ substrate/client/rpc-servers/src/lib.rs | 7 +++---- 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_6588.prdoc diff --git a/prdoc/pr_6588.prdoc b/prdoc/pr_6588.prdoc new file mode 100644 index 000000000000..bf44b2ed3784 --- /dev/null +++ b/prdoc/pr_6588.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "rpc server: fix subscription id_provider being reset to default one" + +doc: + - audience: Node Dev + description: | + The modification ensures that the id_provider variable is cloned instead of taken, which can help prevent issues related id provider being reset to the default. + + +crates: + - name: sc-rpc-server + bump: patch \ No newline at end of file diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index 31e4042d81f2..ff21e2da768e 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -144,7 +144,7 @@ where local_addrs.push(local_addr); let cfg = cfg.clone(); - let mut id_provider2 = id_provider.clone(); + let id_provider2 = id_provider.clone(); tokio_handle.spawn(async move { loop { @@ -197,10 +197,9 @@ where .set_http_middleware(http_middleware) .set_message_buffer_capacity(max_buffer_capacity_per_connection) .set_batch_request_config(batch_config) - .custom_tokio_runtime(cfg.tokio_handle.clone()) - .set_id_provider(RandomStringIdProvider::new(16)); + .custom_tokio_runtime(cfg.tokio_handle.clone()); - if let Some(provider) = id_provider2.take() { + if let Some(provider) = id_provider2.clone() { builder = builder.set_id_provider(provider); } else { builder = builder.set_id_provider(RandomStringIdProvider::new(16)); From 990bf4761644e0d2280d677004b5f164560ed69d Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 26 Nov 2024 06:34:13 +0200 Subject: [PATCH 035/340] tests --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 2 +- bridges/snowbridge/primitives/router/src/inbound/v2.rs | 5 ++--- .../bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs | 2 +- .../bridge-hubs/bridge-hub-westend/src/xcm_config.rs | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 0ca2ee10be7d..c50f66c81303 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -265,7 +265,7 @@ pub mod pallet { } pub fn do_convert(message: MessageV2) -> Result, Error> { - Ok(T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?) + Ok(T::MessageConverter::convert(message, T::XcmPrologueFee::get().into()).map_err(|e| Error::::ConvertMessage(e))?) } } } diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 133021a5f7a5..cc34ba104a9a 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -88,7 +88,7 @@ for MessageToXcm InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, { - fn convert(message: Message) -> Result, ConvertMessageError> { + fn convert(message: Message, xcm_prologue_fee: u128) -> Result, ConvertMessageError> { let mut message_xcm: Xcm<()> = Xcm::new(); if message.xcm.len() > 0 { // Decode xcm @@ -111,8 +111,7 @@ for MessageToXcm let network = EthereumNetwork::get(); let fee_asset = Location::new(1, Here); - let fee_value = 1_000_000_000u128; // TODO get from command - let fee: xcm::prelude::Asset = (fee_asset, fee_value).into(); + let fee: xcm::prelude::Asset = (fee_asset, xcm_prologue_fee).into(); let mut instructions = vec![ ReceiveTeleportedAsset(fee.clone().into()), PayFees { asset: fee }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 2c51c25a26f8..e54e5934f94e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -52,7 +52,7 @@ fn xcm_prologue_fee() { claimer: Some(claimer_bytes) }; let xcm = EthereumInboundQueueV2::do_convert(message).unwrap(); - let _ = EthereumInboundQueueV2::send_xcm(RuntimeOrigin::root(relayer.clone()), xcm, AssetHubWestend::para_id().into()).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(RuntimeOrigin::signed(relayer.clone()), xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( BridgeHubWestend, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs index befb63ef9709..38d9bec0c0f8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs @@ -250,7 +250,7 @@ impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type XcmRouter = XcmRouter; // We want to disallow users sending (arbitrary) XCMs from this chain. - type SendXcmOrigin = EnsureXcmOrigin; + type SendXcmOrigin = EnsureXcmOrigin; // We support local origins dispatching XCM executions. type ExecuteXcmOrigin = EnsureXcmOrigin; type XcmExecuteFilter = Everything; From 9f7829c8fbfdc984f4f81fa50755d92ef491bca6 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 26 Nov 2024 09:51:30 +0200 Subject: [PATCH 036/340] xcm fee --- .../pallets/inbound-queue-v2/src/api.rs | 7 +- .../pallets/inbound-queue-v2/src/lib.rs | 31 +- .../pallets/inbound-queue-v2/src/mock.rs | 21 +- .../primitives/router/src/inbound/mod.rs | 1 - .../primitives/router/src/inbound/v1.rs | 878 +++++++++--------- .../primitives/router/src/inbound/v2.rs | 379 ++++---- .../src/tests/snowbridge_v2.rs | 99 +- .../src/bridge_to_ethereum_config.rs | 8 +- 8 files changed, 756 insertions(+), 668 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index a285a7c5af42..532a1b453366 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -2,15 +2,18 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Implements the dry-run API. -use crate::{Config, Error}; +use crate::{Config, Error, Junction::AccountId32, Location}; use snowbridge_core::inbound::Proof; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; +use sp_core::H256; use xcm::latest::Xcm; pub fn dry_run(message: Message, _proof: Proof) -> Result, Error> where T: Config, { - let xcm = T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + let dummy_origin = Location::new(0, AccountId32 { id: H256::zero().into(), network: None }); + let xcm = T::MessageConverter::convert(message, dummy_origin) + .map_err(|e| Error::::ConvertMessage(e))?; Ok(xcm) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index c50f66c81303..12b0f417576f 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -39,6 +39,7 @@ mod mock; #[cfg(test)] mod test; +use alloc::boxed::Box; use codec::{Decode, DecodeAll, Encode}; use envelope::Envelope; use frame_support::{ @@ -50,7 +51,6 @@ use scale_info::TypeInfo; use sp_core::H160; use sp_std::vec; use types::Nonce; -use alloc::boxed::Box; use xcm::prelude::{Junction::*, Location, *}; use snowbridge_core::{ @@ -141,6 +141,8 @@ pub mod pallet { InvalidEnvelope, /// Message has an unexpected nonce. InvalidNonce, + /// Fee provided is invalid. + InvalidFee, /// Message has an invalid payload. InvalidPayload, /// Message channel is invalid @@ -206,12 +208,13 @@ pub mod pallet { let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) .map_err(|_| Error::::InvalidPayload)?; - let xcm = - T::MessageConverter::convert(message).map_err(|e| Error::::ConvertMessage(e))?; + let origin_account_location = Self::account_to_location(who)?; + + let xcm = Self::do_convert(message, origin_account_location.clone())?; // Burn the required fees for the static XCM message part burn_fees::>( - Self::account_to_location(who)?, + origin_account_location, T::XcmPrologueFee::get(), )?; @@ -255,17 +258,23 @@ pub mod pallet { Ok(Location::new(0, [AccountId32 { network: None, id: account_bytes }])) } - pub fn send_xcm(origin: OriginFor, xcm: Xcm<()>, dest_para_id: u32) -> Result { - let versioned_dest = Box::new(VersionedLocation::V5(Location::new( - 1, - [Parachain(dest_para_id)], - ))); + pub fn send_xcm( + origin: OriginFor, + xcm: Xcm<()>, + dest_para_id: u32, + ) -> Result { + let versioned_dest = + Box::new(VersionedLocation::V5(Location::new(1, [Parachain(dest_para_id)]))); let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); Ok(T::XcmSender::send(origin, versioned_dest, versioned_xcm)?) } - pub fn do_convert(message: MessageV2) -> Result, Error> { - Ok(T::MessageConverter::convert(message, T::XcmPrologueFee::get().into()).map_err(|e| Error::::ConvertMessage(e))?) + pub fn do_convert( + message: MessageV2, + origin_account_location: Location, + ) -> Result, Error> { + Ok(T::MessageConverter::convert(message, origin_account_location) + .map_err(|e| Error::::ConvertMessage(e))?) } } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index e724b2f4e842..105863f5772f 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -16,17 +16,15 @@ use snowbridge_core::{ TokenId, }; use snowbridge_router_primitives::inbound::v2::MessageToXcm; -use sp_core::H160; +use sp_core::{H160, H256}; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, - BuildStorage, MultiSignature, + BuildStorage, DispatchError, MultiSignature, }; use sp_std::{convert::From, default::Default}; use xcm::prelude::*; -use xcm_executor::{traits::TransactAsset, AssetsInHolding}; use xcm_builder::SendControllerWeightInfo; -use sp_runtime::DispatchError; -use sp_core::H256; +use xcm_executor::{traits::TransactAsset, AssetsInHolding}; type Block = frame_system::mocking::MockBlock; @@ -113,7 +111,6 @@ impl BenchmarkHelper for Test { fn initialize_storage(_: BeaconHeader, _: H256) {} } - pub struct MockXcmSenderWeights; impl SendControllerWeightInfo for MockXcmSenderWeights { @@ -153,6 +150,8 @@ parameter_types! { pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); } +const XCM_PROLOGUE_FEE: u128 = 1_000_000_000_000; + impl inbound_queue_v2::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; @@ -160,10 +159,14 @@ impl inbound_queue_v2::Config for Test { type WeightInfo = (); type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; - type MessageConverter = - MessageToXcm; + type MessageConverter = MessageToXcm< + EthereumNetwork, + InboundQueuePalletInstance, + MockTokenIdConvert, + ConstU128, + >; type Token = Balances; - type XcmPrologueFee = ConstU128<1_000_000_000>; + type XcmPrologueFee = ConstU128; type AssetTransactor = SuccessfulTransactor; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 5bf5258f3c4c..69fa554df265 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -35,5 +35,4 @@ impl EthereumLocationsConverterFor { } } - pub type CallIndex = [u8; 2]; diff --git a/bridges/snowbridge/primitives/router/src/inbound/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs index 9e2191d651a0..2d3cf4e55b86 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v1.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v1.rs @@ -20,444 +20,444 @@ const MINIMUM_DEPOSIT: u128 = 1; /// Instead having BridgeHub transcode the messages into XCM. #[derive(Clone, Encode, Decode, RuntimeDebug)] pub enum VersionedMessage { - V1(MessageV1), + V1(MessageV1), } /// For V1, the ethereum side sends messages which are transcoded into XCM. These messages are /// self-contained, in that they can be transcoded using only information in the message. #[derive(Clone, Encode, Decode, RuntimeDebug)] pub struct MessageV1 { - /// EIP-155 chain id of the origin Ethereum network - pub chain_id: u64, - /// The command originating from the Gateway contract - pub command: Command, + /// EIP-155 chain id of the origin Ethereum network + pub chain_id: u64, + /// The command originating from the Gateway contract + pub command: Command, } #[derive(Clone, Encode, Decode, RuntimeDebug)] pub enum Command { - /// Register a wrapped token on the AssetHub `ForeignAssets` pallet - RegisterToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Ethereum token to AssetHub or another parachain - SendToken { - /// The address of the ERC20 token to be bridged over to AssetHub - token: H160, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, - /// Send Polkadot token back to the original parachain - SendNativeToken { - /// The Id of the token - token_id: TokenId, - /// The destination for the transfer - destination: Destination, - /// Amount to transfer - amount: u128, - /// XCM execution fee on AssetHub - fee: u128, - }, + /// Register a wrapped token on the AssetHub `ForeignAssets` pallet + RegisterToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// XCM execution fee on AssetHub + fee: u128, + }, + /// Send Ethereum token to AssetHub or another parachain + SendToken { + /// The address of the ERC20 token to be bridged over to AssetHub + token: H160, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, + /// Send Polkadot token back to the original parachain + SendNativeToken { + /// The Id of the token + token_id: TokenId, + /// The destination for the transfer + destination: Destination, + /// Amount to transfer + amount: u128, + /// XCM execution fee on AssetHub + fee: u128, + }, } /// Destination for bridged tokens #[derive(Clone, Encode, Decode, RuntimeDebug)] pub enum Destination { - /// The funds will be deposited into account `id` on AssetHub - AccountId32 { id: [u8; 32] }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId32 { - para_id: u32, - id: [u8; 32], - /// XCM execution fee on final destination - fee: u128, - }, - /// The funds will deposited into the sovereign account of destination parachain `para_id` on - /// AssetHub, Account `id` on the destination parachain will receive the funds via a - /// reserve-backed transfer. See - ForeignAccountId20 { - para_id: u32, - id: [u8; 20], - /// XCM execution fee on final destination - fee: u128, - }, + /// The funds will be deposited into account `id` on AssetHub + AccountId32 { id: [u8; 32] }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId32 { + para_id: u32, + id: [u8; 32], + /// XCM execution fee on final destination + fee: u128, + }, + /// The funds will deposited into the sovereign account of destination parachain `para_id` on + /// AssetHub, Account `id` on the destination parachain will receive the funds via a + /// reserve-backed transfer. See + ForeignAccountId20 { + para_id: u32, + id: [u8; 20], + /// XCM execution fee on final destination + fee: u128, + }, } pub struct MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, > where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - Balance: BalanceT, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, + CreateAssetCall: Get, + CreateAssetDeposit: Get, + Balance: BalanceT, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, { - _phantom: PhantomData<( - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, - )>, + _phantom: PhantomData<( + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + )>, } /// Reason why a message conversion failed. #[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] pub enum ConvertMessageError { - /// The message version is not supported for conversion. - UnsupportedVersion, - InvalidDestination, - InvalidToken, - /// The fee asset is not supported for conversion. - UnsupportedFeeAsset, - CannotReanchor, + /// The message version is not supported for conversion. + UnsupportedVersion, + InvalidDestination, + InvalidToken, + /// The fee asset is not supported for conversion. + UnsupportedFeeAsset, + CannotReanchor, } /// convert the inbound message to xcm which will be forwarded to the destination chain pub trait ConvertMessage { - type Balance: BalanceT + From; - type AccountId; - /// Converts a versioned message into an XCM message and an optional topicID - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; + type Balance: BalanceT + From; + type AccountId; + /// Converts a versioned message into an XCM message and an optional topicID + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError>; } impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, -> ConvertMessage -for MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, -> - where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > ConvertMessage + for MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > +where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, { - type Balance = Balance; - type AccountId = AccountId; - - fn convert( - message_id: H256, - message: VersionedMessage, - ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { - use Command::*; - use VersionedMessage::*; - match message { - V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => - Ok(Self::convert_register_token(message_id, chain_id, token, fee)), - V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => - Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), - V1(MessageV1 { - chain_id, - command: SendNativeToken { token_id, destination, amount, fee }, - }) => Self::convert_send_native_token( - message_id, - chain_id, - token_id, - destination, - amount, - fee, - ), - } - } + type Balance = Balance; + type AccountId = AccountId; + + fn convert( + message_id: H256, + message: VersionedMessage, + ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> { + use Command::*; + use VersionedMessage::*; + match message { + V1(MessageV1 { chain_id, command: RegisterToken { token, fee } }) => + Ok(Self::convert_register_token(message_id, chain_id, token, fee)), + V1(MessageV1 { chain_id, command: SendToken { token, destination, amount, fee } }) => + Ok(Self::convert_send_token(message_id, chain_id, token, destination, amount, fee)), + V1(MessageV1 { + chain_id, + command: SendNativeToken { token_id, destination, amount, fee }, + }) => Self::convert_send_native_token( + message_id, + chain_id, + token_id, + destination, + amount, + fee, + ), + } + } } impl< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, -> -MessageToXcm< - CreateAssetCall, - CreateAssetDeposit, - InboundQueuePalletInstance, - AccountId, - Balance, - ConvertAssetId, - EthereumUniversalLocation, - GlobalAssetHubLocation, -> - where - CreateAssetCall: Get, - CreateAssetDeposit: Get, - InboundQueuePalletInstance: Get, - Balance: BalanceT + From, - AccountId: Into<[u8; 32]>, - ConvertAssetId: MaybeEquivalence, - EthereumUniversalLocation: Get, - GlobalAssetHubLocation: Get, + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > + MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + ConvertAssetId, + EthereumUniversalLocation, + GlobalAssetHubLocation, + > +where + CreateAssetCall: Get, + CreateAssetDeposit: Get, + InboundQueuePalletInstance: Get, + Balance: BalanceT + From, + AccountId: Into<[u8; 32]>, + ConvertAssetId: MaybeEquivalence, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, { - fn convert_register_token( - message_id: H256, - chain_id: u64, - token: H160, - fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let xcm_fee: Asset = (Location::parent(), fee).into(); - let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); - - let total_amount = fee + CreateAssetDeposit::get(); - let total: Asset = (Location::parent(), total_amount).into(); - - let bridge_location = Location::new(2, GlobalConsensus(network)); - - let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - let asset_id = Self::convert_token_address(network, token); - let create_call_index: [u8; 2] = CreateAssetCall::get(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let xcm: Xcm<()> = vec![ - // Teleport required fees. - ReceiveTeleportedAsset(total.into()), - // Pay for execution. - BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, - // Fund the snowbridge sovereign with the required deposit for creation. - DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, - // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be - // deposited to snowbridge sovereign, instead of being trapped, regardless of - // `Transact` success or not. - SetAppendix(Xcm(vec![ - RefundSurplus, - DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, - ])), - // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - // Change origin to the bridge. - UniversalOrigin(GlobalConsensus(network)), - // Call create_asset on foreign assets pallet. - Transact { - origin_kind: OriginKind::Xcm, - call: ( - create_call_index, - asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner), - MINIMUM_DEPOSIT, - ) - .encode() - .into(), - }, - // Forward message id to Asset Hub - SetTopic(message_id.into()), - // Once the program ends here, appendix program will run, which will deposit any - // leftover fee to snowbridge sovereign. - ] - .into(); - - (xcm, total_amount.into()) - } - - fn convert_send_token( - message_id: H256, - chain_id: u64, - token: H160, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> (Xcm<()>, Balance) { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - let asset: Asset = (Self::convert_token_address(network, token), amount).into(); - - let (dest_para_id, beneficiary, dest_para_fee) = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - (None, Location::new(0, [AccountId32 { network: None, id }]), 0), - // Final destination is a 32-byte account on a sibling of AssetHub - Destination::ForeignAccountId32 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountId32 { network: None, id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - // Final destination is a 20-byte account on a sibling of AssetHub - Destination::ForeignAccountId20 { para_id, id, fee } => ( - Some(para_id), - Location::new(0, [AccountKey20 { network: None, key: id }]), - // Total fee needs to cover execution on AssetHub and Sibling - fee, - ), - }; - - let total_fees = asset_hub_fee.saturating_add(dest_para_fee); - let total_fee_asset: Asset = (Location::parent(), total_fees).into(); - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let mut instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - ReserveAssetDeposited(asset.clone().into()), - ClearOrigin, - ]; - - match dest_para_id { - Some(dest_para_id) => { - let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); - let bridge_location = Location::new(2, GlobalConsensus(network)); - - instructions.extend(vec![ - // After program finishes deposit any leftover assets to the snowbridge - // sovereign. - SetAppendix(Xcm(vec![DepositAsset { - assets: Wild(AllCounted(2)), - beneficiary: bridge_location, - }])), - // Perform a deposit reserve to send to destination chain. - DepositReserveAsset { - assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), - dest: Location::new(1, [Parachain(dest_para_id)]), - xcm: vec![ - // Buy execution on target. - BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, - // Deposit assets to beneficiary. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - // Forward message id to destination parachain. - SetTopic(message_id.into()), - ] - .into(), - }, - ]); - }, - None => { - instructions.extend(vec![ - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - ]); - }, - } - - // Forward message id to Asset Hub. - instructions.push(SetTopic(message_id.into())); - - // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since - // they are teleported within `instructions`). - (instructions.into(), total_fees.into()) - } - - // Convert ERC20 token address to a location that can be understood by Assets Hub. - fn convert_token_address(network: NetworkId, token: H160) -> Location { - Location::new( - 2, - [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], - ) - } - - /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign - /// account of the Gateway contract and either deposits those assets into a recipient account or - /// forwards the assets to another parachain. - fn convert_send_native_token( - message_id: H256, - chain_id: u64, - token_id: TokenId, - destination: Destination, - amount: u128, - asset_hub_fee: u128, - ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { - let network = Ethereum { chain_id }; - let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let beneficiary = match destination { - // Final destination is a 32-byte account on AssetHub - Destination::AccountId32 { id } => - Ok(Location::new(0, [AccountId32 { network: None, id }])), - // Forwarding to a destination parachain is not allowed for PNA and is validated on the - // Ethereum side. https://github.com/Snowfork/snowbridge/blob/e87ddb2215b513455c844463a25323bb9c01ff36/contracts/src/Assets.sol#L216-L224 - _ => Err(ConvertMessageError::InvalidDestination), - }?; - - let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); - - let asset_loc = - ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; - - let mut reanchored_asset_loc = asset_loc.clone(); - reanchored_asset_loc - .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) - .map_err(|_| ConvertMessageError::CannotReanchor)?; - - let asset: Asset = (reanchored_asset_loc, amount).into(); - - let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); - - let instructions = vec![ - ReceiveTeleportedAsset(total_fee_asset.clone().into()), - BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, - DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), - UniversalOrigin(GlobalConsensus(network)), - WithdrawAsset(asset.clone().into()), - // Deposit both asset and fees to beneficiary so the fees will not get - // trapped. Another benefit is when fees left more than ED on AssetHub could be - // used to create the beneficiary account in case it does not exist. - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - SetTopic(message_id.into()), - ]; - - // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also - // teleport fees) - Ok((instructions.into(), asset_hub_fee.into())) - } + fn convert_register_token( + message_id: H256, + chain_id: u64, + token: H160, + fee: u128, + ) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let xcm_fee: Asset = (Location::parent(), fee).into(); + let deposit: Asset = (Location::parent(), CreateAssetDeposit::get()).into(); + + let total_amount = fee + CreateAssetDeposit::get(); + let total: Asset = (Location::parent(), total_amount).into(); + + let bridge_location = Location::new(2, GlobalConsensus(network)); + + let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + let asset_id = Self::convert_token_address(network, token); + let create_call_index: [u8; 2] = CreateAssetCall::get(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let xcm: Xcm<()> = vec![ + // Teleport required fees. + ReceiveTeleportedAsset(total.into()), + // Pay for execution. + BuyExecution { fees: xcm_fee, weight_limit: Unlimited }, + // Fund the snowbridge sovereign with the required deposit for creation. + DepositAsset { assets: Definite(deposit.into()), beneficiary: bridge_location.clone() }, + // This `SetAppendix` ensures that `xcm_fee` not spent by `Transact` will be + // deposited to snowbridge sovereign, instead of being trapped, regardless of + // `Transact` success or not. + SetAppendix(Xcm(vec![ + RefundSurplus, + DepositAsset { assets: AllCounted(1).into(), beneficiary: bridge_location }, + ])), + // Only our inbound-queue pallet is allowed to invoke `UniversalOrigin`. + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + // Change origin to the bridge. + UniversalOrigin(GlobalConsensus(network)), + // Call create_asset on foreign assets pallet. + Transact { + origin_kind: OriginKind::Xcm, + call: ( + create_call_index, + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner), + MINIMUM_DEPOSIT, + ) + .encode() + .into(), + }, + // Forward message id to Asset Hub + SetTopic(message_id.into()), + // Once the program ends here, appendix program will run, which will deposit any + // leftover fee to snowbridge sovereign. + ] + .into(); + + (xcm, total_amount.into()) + } + + fn convert_send_token( + message_id: H256, + chain_id: u64, + token: H160, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> (Xcm<()>, Balance) { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + let asset: Asset = (Self::convert_token_address(network, token), amount).into(); + + let (dest_para_id, beneficiary, dest_para_fee) = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => + (None, Location::new(0, [AccountId32 { network: None, id }]), 0), + // Final destination is a 32-byte account on a sibling of AssetHub + Destination::ForeignAccountId32 { para_id, id, fee } => ( + Some(para_id), + Location::new(0, [AccountId32 { network: None, id }]), + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + // Final destination is a 20-byte account on a sibling of AssetHub + Destination::ForeignAccountId20 { para_id, id, fee } => ( + Some(para_id), + Location::new(0, [AccountKey20 { network: None, key: id }]), + // Total fee needs to cover execution on AssetHub and Sibling + fee, + ), + }; + + let total_fees = asset_hub_fee.saturating_add(dest_para_fee); + let total_fee_asset: Asset = (Location::parent(), total_fees).into(); + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let mut instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + ReserveAssetDeposited(asset.clone().into()), + ClearOrigin, + ]; + + match dest_para_id { + Some(dest_para_id) => { + let dest_para_fee_asset: Asset = (Location::parent(), dest_para_fee).into(); + let bridge_location = Location::new(2, GlobalConsensus(network)); + + instructions.extend(vec![ + // After program finishes deposit any leftover assets to the snowbridge + // sovereign. + SetAppendix(Xcm(vec![DepositAsset { + assets: Wild(AllCounted(2)), + beneficiary: bridge_location, + }])), + // Perform a deposit reserve to send to destination chain. + DepositReserveAsset { + assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), + dest: Location::new(1, [Parachain(dest_para_id)]), + xcm: vec![ + // Buy execution on target. + BuyExecution { fees: dest_para_fee_asset, weight_limit: Unlimited }, + // Deposit assets to beneficiary. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + // Forward message id to destination parachain. + SetTopic(message_id.into()), + ] + .into(), + }, + ]); + }, + None => { + instructions.extend(vec![ + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + ]); + }, + } + + // Forward message id to Asset Hub. + instructions.push(SetTopic(message_id.into())); + + // The `instructions` to forward to AssetHub, and the `total_fees` to locally burn (since + // they are teleported within `instructions`). + (instructions.into(), total_fees.into()) + } + + // Convert ERC20 token address to a location that can be understood by Assets Hub. + fn convert_token_address(network: NetworkId, token: H160) -> Location { + Location::new( + 2, + [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], + ) + } + + /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign + /// account of the Gateway contract and either deposits those assets into a recipient account or + /// forwards the assets to another parachain. + fn convert_send_native_token( + message_id: H256, + chain_id: u64, + token_id: TokenId, + destination: Destination, + amount: u128, + asset_hub_fee: u128, + ) -> Result<(Xcm<()>, Balance), ConvertMessageError> { + let network = Ethereum { chain_id }; + let asset_hub_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let beneficiary = match destination { + // Final destination is a 32-byte account on AssetHub + Destination::AccountId32 { id } => + Ok(Location::new(0, [AccountId32 { network: None, id }])), + // Forwarding to a destination parachain is not allowed for PNA and is validated on the + // Ethereum side. https://github.com/Snowfork/snowbridge/blob/e87ddb2215b513455c844463a25323bb9c01ff36/contracts/src/Assets.sol#L216-L224 + _ => Err(ConvertMessageError::InvalidDestination), + }?; + + let total_fee_asset: Asset = (Location::parent(), asset_hub_fee).into(); + + let asset_loc = + ConvertAssetId::convert(&token_id).ok_or(ConvertMessageError::InvalidToken)?; + + let mut reanchored_asset_loc = asset_loc.clone(); + reanchored_asset_loc + .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) + .map_err(|_| ConvertMessageError::CannotReanchor)?; + + let asset: Asset = (reanchored_asset_loc, amount).into(); + + let inbound_queue_pallet_index = InboundQueuePalletInstance::get(); + + let instructions = vec![ + ReceiveTeleportedAsset(total_fee_asset.clone().into()), + BuyExecution { fees: asset_hub_fee_asset, weight_limit: Unlimited }, + DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()), + UniversalOrigin(GlobalConsensus(network)), + WithdrawAsset(asset.clone().into()), + // Deposit both asset and fees to beneficiary so the fees will not get + // trapped. Another benefit is when fees left more than ED on AssetHub could be + // used to create the beneficiary account in case it does not exist. + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + SetTopic(message_id.into()), + ]; + + // `total_fees` to burn on this chain when sending `instructions` to run on AH (which also + // teleport fees) + Ok((instructions.into(), asset_hub_fee.into())) + } } #[cfg(test)] mod tests { - use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; - use frame_support::{assert_ok, parameter_types}; - use hex_literal::hex; - use xcm::prelude::*; - use xcm_executor::traits::ConvertLocation; + use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; + use frame_support::{assert_ok, parameter_types}; + use hex_literal::hex; + use xcm::prelude::*; + use xcm_executor::traits::ConvertLocation; - const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; + const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; - parameter_types! { + parameter_types! { pub EthereumNetwork: NetworkId = NETWORK; pub const CreateAssetCall: CallIndex = [1, 1]; @@ -466,57 +466,57 @@ mod tests { pub const SendTokenExecutionFee: u128 = 592; } - #[test] - fn test_contract_location_with_network_converts_successfully() { - let expected_account: [u8; 32] = - hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); - let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); + #[test] + fn test_contract_location_with_network_converts_successfully() { + let expected_account: [u8; 32] = + hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); + let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); - let account = - EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location) - .unwrap(); + let account = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location) + .unwrap(); - assert_eq!(account, expected_account); - } + assert_eq!(account, expected_account); + } - #[test] - fn test_contract_location_with_incorrect_location_fails_convert() { - let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); + #[test] + fn test_contract_location_with_incorrect_location_fails_convert() { + let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); - assert_eq!( + assert_eq!( EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location), None, ); - } - - #[test] - fn test_reanchor_all_assets() { - let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); - let ethereum = Location::new(2, ethereum_context.clone()); - let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); - let global_ah = Location::new(1, ah_context.clone()); - let assets = vec![ - // DOT - Location::new(1, []), - // GLMR (Some Polkadot parachain currency) - Location::new(1, [Parachain(2004)]), - // AH asset - Location::new(0, [PalletInstance(50), GeneralIndex(42)]), - // KSM - Location::new(2, [GlobalConsensus(Kusama)]), - // KAR (Some Kusama parachain currency) - Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), - ]; - for asset in assets.iter() { - // reanchor logic in pallet_xcm on AH - let mut reanchored_asset = asset.clone(); - assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); - // reanchor back to original location in context of Ethereum - let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); - assert_ok!( + } + + #[test] + fn test_reanchor_all_assets() { + let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); + let ethereum = Location::new(2, ethereum_context.clone()); + let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + let global_ah = Location::new(1, ah_context.clone()); + let assets = vec![ + // DOT + Location::new(1, []), + // GLMR (Some Polkadot parachain currency) + Location::new(1, [Parachain(2004)]), + // AH asset + Location::new(0, [PalletInstance(50), GeneralIndex(42)]), + // KSM + Location::new(2, [GlobalConsensus(Kusama)]), + // KAR (Some Kusama parachain currency) + Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), + ]; + for asset in assets.iter() { + // reanchor logic in pallet_xcm on AH + let mut reanchored_asset = asset.clone(); + assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); + // reanchor back to original location in context of Ethereum + let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); + assert_ok!( reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context) ); - assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); - } - } + assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); + } + } } diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index cc34ba104a9a..63b997a1f454 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -11,8 +11,8 @@ use sp_core::{Get, RuntimeDebug, H160, H256}; use sp_runtime::traits::MaybeEquivalence; use sp_std::prelude::*; use xcm::{ - prelude::{Junction::AccountKey20, *}, - MAX_XCM_DECODE_DEPTH, + prelude::{Junction::AccountKey20, *}, + MAX_XCM_DECODE_DEPTH, }; const LOG_TARGET: &str = "snowbridge-router-primitives"; @@ -22,161 +22,176 @@ const LOG_TARGET: &str = "snowbridge-router-primitives"; /// Instead having BridgeHub transcode the messages into XCM. #[derive(Clone, Encode, Decode, RuntimeDebug)] pub enum VersionedMessage { - V2(Message), + V2(Message), } /// The ethereum side sends messages which are transcoded into XCM on BH. These messages are /// self-contained, in that they can be transcoded using only information in the message. #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct Message { - /// The origin address - pub origin: H160, - /// The assets - pub assets: Vec, - // The command originating from the Gateway contract - pub xcm: Vec, - // The claimer in the case that funds get trapped. - pub claimer: Option>, + /// The origin address + pub origin: H160, + /// The assets + pub assets: Vec, + // The command originating from the Gateway contract + pub xcm: Vec, + // The claimer in the case that funds get trapped. + pub claimer: Option>, } #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum Asset { - NativeTokenERC20 { - /// The native token ID - token_id: H160, - /// The monetary value of the asset - value: u128, - }, - ForeignTokenERC20 { - /// The foreign token ID - token_id: H256, - /// The monetary value of the asset - value: u128, - }, + NativeTokenERC20 { + /// The native token ID + token_id: H160, + /// The monetary value of the asset + value: u128, + }, + ForeignTokenERC20 { + /// The foreign token ID + token_id: H256, + /// The monetary value of the asset + value: u128, + }, } /// Reason why a message conversion failed. #[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] pub enum ConvertMessageError { - /// The XCM provided with the message could not be decoded into XCM. - InvalidXCM, - /// The XCM provided with the message could not be decoded into versioned XCM. - InvalidVersionedXCM, - /// Invalid claimer MultiAddress provided in payload. - InvalidClaimer, - /// Invalid foreign ERC20 token ID - InvalidAsset, + /// The XCM provided with the message could not be decoded into XCM. + InvalidXCM, + /// The XCM provided with the message could not be decoded into versioned XCM. + InvalidVersionedXCM, + /// Invalid claimer MultiAddress provided in payload. + InvalidClaimer, + /// Invalid foreign ERC20 token ID + InvalidAsset, } pub trait ConvertMessage { - fn convert(message: Message) -> Result, ConvertMessageError>; + fn convert(message: Message, origin_account: Location) -> Result, ConvertMessageError>; } -pub struct MessageToXcm - where - EthereumNetwork: Get, - InboundQueuePalletInstance: Get, - ConvertAssetId: MaybeEquivalence, +pub struct MessageToXcm +where + EthereumNetwork: Get, + InboundQueuePalletInstance: Get, + ConvertAssetId: MaybeEquivalence, + XcmPrologueFee: Get, { - _phantom: PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId)>, + _phantom: + PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, XcmPrologueFee)>, } -impl ConvertMessage -for MessageToXcm - where - EthereumNetwork: Get, - InboundQueuePalletInstance: Get, - ConvertAssetId: MaybeEquivalence, +impl ConvertMessage + for MessageToXcm +where + EthereumNetwork: Get, + InboundQueuePalletInstance: Get, + ConvertAssetId: MaybeEquivalence, + XcmPrologueFee: Get, { - fn convert(message: Message, xcm_prologue_fee: u128) -> Result, ConvertMessageError> { - let mut message_xcm: Xcm<()> = Xcm::new(); - if message.xcm.len() > 0 { - // Decode xcm - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut message.xcm.as_ref(), - ) - .map_err(|_| ConvertMessageError::InvalidVersionedXCM)?; - message_xcm = versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; - } - - log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); - - let network = EthereumNetwork::get(); - - let origin_location = Location::new(2, GlobalConsensus(network)) - .push_interior(AccountKey20 { key: message.origin.into(), network: None }) - .map_err(|_| ConvertMessageError::InvalidXCM)?; - - let network = EthereumNetwork::get(); - - let fee_asset = Location::new(1, Here); - let fee: xcm::prelude::Asset = (fee_asset, xcm_prologue_fee).into(); - let mut instructions = vec![ - ReceiveTeleportedAsset(fee.clone().into()), - PayFees { asset: fee }, - DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), - UniversalOrigin(GlobalConsensus(network)), - ]; - - for asset in &message.assets { - match asset { - Asset::NativeTokenERC20 { token_id, value } => { - let token_location: Location = Location::new( - 2, - [ - GlobalConsensus(EthereumNetwork::get()), - AccountKey20 { network: None, key: (*token_id).into() }, - ], - ); - instructions.push(ReserveAssetDeposited((token_location, *value).into())); - }, - Asset::ForeignTokenERC20 { token_id, value } => { - let asset_id = ConvertAssetId::convert(&token_id) - .ok_or(ConvertMessageError::InvalidAsset)?; - instructions.push(WithdrawAsset((asset_id, *value).into())); - }, - } - } - - log::debug!(target: LOG_TARGET,"extracted assets"); - - if let Some(claimer) = message.claimer { - let claimer = Junction::decode(&mut claimer.as_ref()) - .map_err(|_| ConvertMessageError::InvalidClaimer)?; - let claimer_location: Location = Location::new(0, [claimer.into()]); - instructions.push(SetAssetClaimer { location: claimer_location }); - } - - log::debug!(target: LOG_TARGET,"extracted claimer"); - - // Set the alias origin to the original sender on Ethereum. Important to be before the - // arbitrary XCM that is appended to the message on the next line. - instructions.push(AliasOrigin(origin_location.into())); - - // Add the XCM sent in the message to the end of the xcm instruction - instructions.extend(message_xcm.0); - - Ok(instructions.into()) - } + fn convert( + message: Message, + origin_account_location: Location, + ) -> Result, ConvertMessageError> { + let mut message_xcm: Xcm<()> = Xcm::new(); + if message.xcm.len() > 0 { + // Decode xcm + let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + MAX_XCM_DECODE_DEPTH, + &mut message.xcm.as_ref(), + ) + .map_err(|_| ConvertMessageError::InvalidVersionedXCM)?; + message_xcm = versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; + } + + log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); + + let network = EthereumNetwork::get(); + + let origin_location = Location::new( + 2, + [GlobalConsensus(network), AccountKey20 { key: message.origin.into(), network: None }], + ); + + let network = EthereumNetwork::get(); + + let fee_asset = Location::new(1, Here); + let fee: xcm::prelude::Asset = (fee_asset.clone(), XcmPrologueFee::get()).into(); + let mut instructions = vec![ + ReceiveTeleportedAsset(fee.clone().into()), + PayFees { asset: fee }, + DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), + UniversalOrigin(GlobalConsensus(network)), + ]; + + for asset in &message.assets { + match asset { + Asset::NativeTokenERC20 { token_id, value } => { + let token_location: Location = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: (*token_id).into() }, + ], + ); + instructions.push(ReserveAssetDeposited((token_location, *value).into())); + }, + Asset::ForeignTokenERC20 { token_id, value } => { + let asset_id = ConvertAssetId::convert(&token_id) + .ok_or(ConvertMessageError::InvalidAsset)?; + instructions.push(WithdrawAsset((asset_id, *value).into())); + }, + } + } + + if let Some(claimer) = message.claimer { + let claimer = Junction::decode(&mut claimer.as_ref()) + .map_err(|_| ConvertMessageError::InvalidClaimer)?; + let claimer_location: Location = Location::new(0, [claimer.into()]); + instructions.push(SetAssetClaimer { location: claimer_location }); + } + + // Set the alias origin to the original sender on Ethereum. Important to be before the + // arbitrary XCM that is appended to the message on the next line. + instructions.push(AliasOrigin(origin_location.into())); + + // Add the XCM sent in the message to the end of the xcm instruction + instructions.extend(message_xcm.0); + + let appendix = vec![ + RefundSurplus, + // Refund excess fees to the relayer + // TODO maybe refund all fees to the relayer instead of just DOT? + DepositAsset { + assets: Wild(AllOf { id: AssetId(fee_asset.into()), fun: WildFungible }), + beneficiary: origin_account_location, + }, + ]; + + instructions.extend(appendix); + + Ok(instructions.into()) + } } #[cfg(test)] mod tests { - use crate::inbound::{ - v2::{ConvertMessage, Message, MessageToXcm}, - CallIndex, GlobalConsensusEthereumConvertsFor, - }; - use codec::Decode; - use frame_support::{assert_ok, parameter_types}; - use hex_literal::hex; - use sp_runtime::traits::ConstU8; - use xcm::prelude::*; - use xcm_executor::traits::ConvertLocation; - - const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; - - parameter_types! { + use crate::inbound::{ + v2::{ConvertMessage, Message, MessageToXcm}, + CallIndex, GlobalConsensusEthereumConvertsFor, + }; + use codec::Decode; + use frame_support::{assert_ok, parameter_types}; + use hex_literal::hex; + use sp_runtime::traits::ConstU8; + use xcm::prelude::*; + use xcm_executor::traits::ConvertLocation; + + const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; + + parameter_types! { pub EthereumNetwork: NetworkId = NETWORK; pub const CreateAssetCall: CallIndex = [1, 1]; @@ -185,66 +200,66 @@ mod tests { pub const SendTokenExecutionFee: u128 = 592; } - #[test] - fn test_contract_location_with_network_converts_successfully() { - let expected_account: [u8; 32] = - hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); - let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); + #[test] + fn test_contract_location_with_network_converts_successfully() { + let expected_account: [u8; 32] = + hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); + let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); - let account = - GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location) - .unwrap(); + let account = + GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location) + .unwrap(); - assert_eq!(account, expected_account); - } + assert_eq!(account, expected_account); + } - #[test] - fn test_contract_location_with_incorrect_location_fails_convert() { - let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); + #[test] + fn test_contract_location_with_incorrect_location_fails_convert() { + let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); - assert_eq!( + assert_eq!( GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location), None, ); - } - - #[test] - fn test_reanchor_all_assets() { - let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); - let ethereum = Location::new(2, ethereum_context.clone()); - let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); - let global_ah = Location::new(1, ah_context.clone()); - let assets = vec![ - // DOT - Location::new(1, []), - // GLMR (Some Polkadot parachain currency) - Location::new(1, [Parachain(2004)]), - // AH asset - Location::new(0, [PalletInstance(50), GeneralIndex(42)]), - // KSM - Location::new(2, [GlobalConsensus(Kusama)]), - // KAR (Some Kusama parachain currency) - Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), - ]; - for asset in assets.iter() { - // reanchor logic in pallet_xcm on AH - let mut reanchored_asset = asset.clone(); - assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); - // reanchor back to original location in context of Ethereum - let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); - assert_ok!( + } + + #[test] + fn test_reanchor_all_assets() { + let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); + let ethereum = Location::new(2, ethereum_context.clone()); + let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + let global_ah = Location::new(1, ah_context.clone()); + let assets = vec![ + // DOT + Location::new(1, []), + // GLMR (Some Polkadot parachain currency) + Location::new(1, [Parachain(2004)]), + // AH asset + Location::new(0, [PalletInstance(50), GeneralIndex(42)]), + // KSM + Location::new(2, [GlobalConsensus(Kusama)]), + // KAR (Some Kusama parachain currency) + Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), + ]; + for asset in assets.iter() { + // reanchor logic in pallet_xcm on AH + let mut reanchored_asset = asset.clone(); + assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); + // reanchor back to original location in context of Ethereum + let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); + assert_ok!( reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context) ); - assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); - } - } - - #[test] - fn test_convert_message() { - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); - let message = Message::decode(&mut payload.as_ref()); - assert_ok!(message.clone()); - let result = MessageToXcm::>::convert(message.unwrap()); - assert_ok!(result); - } + assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); + } + } + + #[test] + fn test_convert_message() { + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let message = Message::decode(&mut payload.as_ref()); + assert_ok!(message.clone()); + let result = MessageToXcm::>::convert(message.unwrap()); + assert_ok!(result); + } } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index e54e5934f94e..8e5cbf8ff4d6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -13,50 +13,103 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::imports::*; -use hex_literal::hex; -use bridge_hub_westend_runtime::EthereumInboundQueueV2; -use snowbridge_router_primitives::inbound::v2::Message; -use bridge_hub_westend_runtime::RuntimeOrigin; +use bridge_hub_westend_runtime::{EthereumInboundQueueV2, RuntimeOrigin}; +use frame_support::weights::WeightToFee; +use snowbridge_router_primitives::inbound::v2::{Asset::NativeTokenERC20, Message}; use sp_core::H160; -use snowbridge_router_primitives::inbound::v2::Asset::NativeTokenERC20; +use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; +use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; #[test] fn xcm_prologue_fee() { BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + let relayer = BridgeHubWestendSender::get(); - let claimer = AssetHubWestendReceiver::get(); - BridgeHubWestend::fund_accounts(vec![ - (relayer.clone(), INITIAL_FUND), - ]); + let receiver = AssetHubWestendReceiver::get(); + BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); + + let mut token_ids = Vec::new(); + for _ in 0..8 { + token_ids.push(H160::random()); + } + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + for token_id in token_ids.iter() { + let token_id = *token_id; + + let asset_location = Location::new( + 2, + [ + GlobalConsensus(ethereum_network_v5), + AccountKey20 { network: None, key: token_id.into() }, + ], + ); - let token_id_1 = H160::random(); + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + asset_location.clone(), + asset_hub_sovereign.clone().into(), + false, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + asset_location.clone().try_into().unwrap(), + )); + } + }); + + let native_tokens: Vec = token_ids + .iter() + .map(|token_id| NativeTokenERC20 { token_id: *token_id, value: 3_000_000_000 }) + .collect(); BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let claimer = AccountId32{network: None, id: claimer.into()}; + let claimer = AccountId32 { network: None, id: receiver.clone().into() }; let claimer_bytes = claimer.encode(); + let origin = H160::random(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - let message = Message{ - origin: H160::random(), - assets: vec![ - NativeTokenERC20 { - token_id: token_id_1, - value: 1_000_000_000, - } - ], - xcm: hex!().to_vec(), - claimer: Some(claimer_bytes) + let message_xcm_instructions = + vec![DepositAsset { assets: Wild(AllCounted(8)), beneficiary: receiver.into() }]; + let message_xcm: Xcm<()> = message_xcm_instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(message_xcm); + + let message = Message { + origin, + assets: native_tokens, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), }; - let xcm = EthereumInboundQueueV2::do_convert(message).unwrap(); - let _ = EthereumInboundQueueV2::send_xcm(RuntimeOrigin::signed(relayer.clone()), xcm, AssetHubWestend::para_id().into()).unwrap(); + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm( + RuntimeOrigin::signed(relayer.clone()), + xcm, + AssetHubWestend::para_id().into(), + ) + .unwrap(); assert_expected_events!( BridgeHubWestend, vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] ); }); + + let execution_fee = WeightCalculator::weight_to_fee(&Weight::from_parts(1410450000, 33826)); + let buffered_fee = execution_fee * 2; + println!("buffered execution fee for prologue for 8 assets: {}", buffered_fee); } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index e9c4f3ad288b..624127823518 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -72,6 +72,11 @@ parameter_types! { pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(RelayNetwork::get()),Parachain(westend_runtime_constants::system_parachain::ASSET_HUB_ID)]); pub EthereumUniversalLocation: InteriorLocation = [GlobalConsensus(EthereumNetwork::get())].into(); } + +/// The XCM execution fee on AH for the static part of the XCM message (not the user provided parts). +/// Calculated with integration test snowbridge_v2::xcm_prologue_fee +const XCM_PROLOGUE_FEE: u128 = 67_652_000_000; + impl snowbridge_pallet_inbound_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Verifier = snowbridge_pallet_ethereum_client::Pallet; @@ -115,12 +120,13 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; type AssetHubParaId = ConstU32<1000>; type Token = Balances; - type XcmPrologueFee = ConstU128<1_000_000_000>; + type XcmPrologueFee = ConstU128; type AssetTransactor = ::AssetTransactor; type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm< EthereumNetwork, ConstU8, EthereumSystem, + ConstU128, >; } From 11839dcff2e2348dc4fe392280ffb90de4f726bd Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 26 Nov 2024 09:57:22 +0200 Subject: [PATCH 037/340] adds comments --- .../snowbridge/pallets/inbound-queue-v2/src/lib.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 12b0f417576f..f4c6d99276bc 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -94,21 +94,23 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// The verifier for inbound messages from Ethereum + /// The verifier for inbound messages from Ethereum. type Verifier: Verifier; - - /// XCM message sender + /// XCM message sender. type XcmSender: SendController<::RuntimeOrigin>; - /// Address of the Gateway contract + /// Address of the Gateway contract. #[pallet::constant] type GatewayAddress: Get; type WeightInfo: WeightInfo; - /// AssetHub parachain ID + /// AssetHub parachain ID. type AssetHubParaId: Get; + /// Convert a command from Ethereum to an XCM message. type MessageConverter: ConvertMessage; + /// The AH XCM execution fee for the static part of the XCM message. type XcmPrologueFee: Get>; + /// Used to burn fees from the origin account (the relayer), which will be teleported to AH. type Token: Mutate + Inspect; + /// Used to burn fees. type AssetTransactor: TransactAsset; #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; From 7f64e66b3b01ff5e391e42a8159f601cbbeb46d8 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Tue, 26 Nov 2024 09:25:18 +0100 Subject: [PATCH 038/340] bump zombienet-sdk version `v0.2.16` (#6633) Fix #6575. cc: @iulianbarbu Co-authored-by: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c79adee6f38e..338d5025b662 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31910,9 +31910,9 @@ dependencies = [ [[package]] name = "zombienet-configuration" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d7a8cc4f8e8bb3f40757b62d3b054da5c95f43321c775eb321edc89d431583e" +checksum = "8ad4fc5b0f1aa54de6bf2d6771c449b41cad47e1cf30559af0a71452686b47ab" dependencies = [ "anyhow", "lazy_static", @@ -31930,9 +31930,9 @@ dependencies = [ [[package]] name = "zombienet-orchestrator" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d32fa87851f41443a78971bd7110274f9a66d139ac834de159adc08f90cf8e3" +checksum = "e4a7dd25842ded75c7f4dc4f38f05fef567bd0b37fd3057c223d4ee34d8fa817" dependencies = [ "anyhow", "async-trait", @@ -31963,9 +31963,9 @@ dependencies = [ [[package]] name = "zombienet-prom-metrics-parser" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9acb9c94bc7c2c83f8eb8e26ed403f757af1632f22b89394d8876412ede990ca" +checksum = "a63e0c6024dd19b0f8b28afa94f78c211e5c163350ecda4a48084532d74d7cfe" dependencies = [ "pest", "pest_derive", @@ -31974,9 +31974,9 @@ dependencies = [ [[package]] name = "zombienet-provider" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc8f3f71d4d974fc4a2262fa9293c2eedc423540378bd7c1dc1b66cc95d1d1af" +checksum = "8d87c29390a342d0f4f62b6796861fb82e0e56c49929a272b689e8dbf24eaab9" dependencies = [ "anyhow", "async-trait", @@ -32005,9 +32005,9 @@ dependencies = [ [[package]] name = "zombienet-sdk" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dbfddce7a6100cdc930b93301f1b6381e6577ecc013d6802258ea6902a2bebd" +checksum = "829e5111182caf00ba57cd63656cf0bde6ce6add7f6a9747d15821c202a3f27e" dependencies = [ "async-trait", "futures", @@ -32022,9 +32022,9 @@ dependencies = [ [[package]] name = "zombienet-support" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20567c52b4fd46b600cda254dedb6a6dc30cabf512de91e4f6f78f0f7f4644b" +checksum = "99568384a1d9645458ab9de377b3517cb543a1ece5aba905aeb58d269139df4e" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 533ea4c9e878..57e049885da5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1387,7 +1387,7 @@ xcm-procedural = { path = "polkadot/xcm/procedural", default-features = false } xcm-runtime-apis = { path = "polkadot/xcm/xcm-runtime-apis", default-features = false } xcm-simulator = { path = "polkadot/xcm/xcm-simulator", default-features = false } zeroize = { version = "1.7.0", default-features = false } -zombienet-sdk = { version = "0.2.15" } +zombienet-sdk = { version = "0.2.16" } zstd = { version = "0.12.4", default-features = false } [profile.release] From 86a917f59d04ea3b399b34edcb8effe76b6415e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 26 Nov 2024 08:49:46 +0000 Subject: [PATCH 039/340] Bump rustls from 0.23.14 to 0.23.18 (#6641) Bumps [rustls](https://github.com/rustls/rustls) from 0.23.14 to 0.23.18.
Commits
  • 33af2c3 Prepare 0.23.18
  • ffe646d Add reproducer for bug 2227
  • 69b6f74 Record and restore the processed cursor in first_handshake_message
  • 4ef3532 Upgrade to mio 1
  • 092a164 Manage dependencies via the workspace
  • a01bd6b rustls-bench: fix warnings with no features
  • 7d74de2 tests: linearize new test code helper
  • 499d797 fix: do not send session_ticket(35) extension for TLS 1.3
  • faca289 chore(deps): lock file maintenance
  • d12f423 fix(deps): update rust crate asn1 to 0.20
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=rustls&package-manager=cargo&previous-version=0.23.14&new-version=0.23.18)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/paritytech/polkadot-sdk/network/alerts).
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 28 ++++++++++++++-------------- Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 338d5025b662..c2d2eb3e9644 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8530,7 +8530,7 @@ dependencies = [ "hyper 1.3.1", "hyper-util", "log", - "rustls 0.23.14", + "rustls 0.23.18", "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", @@ -9145,7 +9145,7 @@ dependencies = [ "http 1.1.0", "jsonrpsee-core", "pin-project", - "rustls 0.23.14", + "rustls 0.23.18", "rustls-pki-types", "rustls-platform-verifier", "soketto 0.8.0", @@ -9198,7 +9198,7 @@ dependencies = [ "hyper-util", "jsonrpsee-core", "jsonrpsee-types", - "rustls 0.23.14", + "rustls 0.23.18", "rustls-platform-verifier", "serde", "serde_json", @@ -20609,7 +20609,7 @@ dependencies = [ "quinn-proto 0.11.8", "quinn-udp 0.5.4", "rustc-hash 2.0.0", - "rustls 0.23.14", + "rustls 0.23.18", "socket2 0.5.7", "thiserror", "tokio", @@ -20643,7 +20643,7 @@ dependencies = [ "rand", "ring 0.17.7", "rustc-hash 2.0.0", - "rustls 0.23.14", + "rustls 0.23.18", "slab", "thiserror", "tinyvec", @@ -21130,7 +21130,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn 0.11.5", - "rustls 0.23.14", + "rustls 0.23.18", "rustls-pemfile 2.0.0", "rustls-pki-types", "serde", @@ -21734,9 +21734,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.14" +version = "0.23.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" dependencies = [ "log", "once_cell", @@ -21806,9 +21806,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-platform-verifier" @@ -21821,7 +21821,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.14", + "rustls 0.23.18", "rustls-native-certs 0.7.0", "rustls-platform-verifier-android", "rustls-webpki 0.102.8", @@ -23128,7 +23128,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.3", "rand", - "rustls 0.23.14", + "rustls 0.23.18", "sc-block-builder", "sc-client-api", "sc-client-db", @@ -29529,7 +29529,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.14", + "rustls 0.23.18", "rustls-pki-types", "tokio", ] @@ -30240,7 +30240,7 @@ dependencies = [ "flate2", "log", "once_cell", - "rustls 0.23.14", + "rustls 0.23.18", "rustls-pki-types", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 57e049885da5..53f95406e79a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1136,7 +1136,7 @@ rstest = { version = "0.18.2" } rustc-hash = { version = "1.1.0" } rustc-hex = { version = "2.1.0", default-features = false } rustix = { version = "0.36.7", default-features = false } -rustls = { version = "0.23.14", default-features = false, features = ["logging", "ring", "std", "tls12"] } +rustls = { version = "0.23.18", default-features = false, features = ["logging", "ring", "std", "tls12"] } rustversion = { version = "1.0.17" } rusty-fork = { version = "0.3.0", default-features = false } safe-mix = { version = "1.0", default-features = false } From 8216235b480630c5a69636b172f5146711c88c9a Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:58:48 +0200 Subject: [PATCH 040/340] ci/check-semver: Fix semver failed step (#6535) The semver-check is failing with the following [error](https://github.com/paritytech/polkadot-sdk/actions/runs/11908981132/job/33185572284): ```bash error[E0658]: use of unstable library feature 'error_in_core' --> /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/frame-decode-0.5.0/src/decoding/extrinsic_decoder.rs:56:6 | 56 | impl core::error::Error for ExtrinsicDecodeError {} | ^^^^^^^^^^^^^^^^^^ | = note: see issue #103765 for more information = help: add `#![feature(error_in_core)]` to the crate attributes to enable = note: this compiler was built on 2024-05-31; consider upgrading it if it is out of date ``` This is related to the toolchain nightly version 1.80. In rust, 1.81 the `core::error::Error` is stable. After updating the rust-toolchain, parity-publish crate must be updated as well. The `cargo-semver-checks` dependency of `parity-publish` crate is updated from 0.34 to 0.38. This update enables rustdoc v36 that fixes the following [issue](https://github.com/paritytech/polkadot-sdk/actions/runs/11912689841/job/33196936011): ```bash validating prdocs... checking file changes... checking semver changes... (1/18) building frame-support-HEAD... (2/18) building frame-support-28.0.0... Error: rustdoc format v36 for file /__w/polkadot-sdk/polkadot-sdk/target/doc/frame_support.new is not supported ``` This PR is pending on a release of parity-publish to version 0.9.0 (fixes already on origin/master) --------- Signed-off-by: Alexandru Vasile --- .github/workflows/check-semver.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index 78602410cdf6..8d77b6a31b75 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -11,7 +11,7 @@ concurrency: cancel-in-progress: true env: - TOOLCHAIN: nightly-2024-06-01 + TOOLCHAIN: nightly-2024-10-19 jobs: preflight: @@ -74,7 +74,7 @@ jobs: - name: install parity-publish # Set the target dir to cache the build. - run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.8.0 --locked -q + run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.1 --locked -q - name: check semver run: | From 3c003872178b6ce535e9f26ce52e324f36075ffd Mon Sep 17 00:00:00 2001 From: Egor_P Date: Tue, 26 Nov 2024 14:46:51 +0100 Subject: [PATCH 041/340] [Release|CI/CD] Github pipeline to publish polkadot deb package (#6640) This pipeline should replace a manual action done on the `cleamroom` server to publish the `polkadot` deb package to our apt repo with the pipeline triggered from the new paritytech-release org. Right now, this is done manually by running the [add-packages.sh](https://github.com/paritytech/cleanroom/blob/master/ansible/roles/parity-repos/files/add-packages.sh) script on the `cleanroom` machine. What is done under the hood: - Pipeline downloads `polakdot` deb package from S3, that was prebuilt in the [Build release rc pipeline](https://github.com/paritytech/polkadot-sdk/blob/master/.github/workflows/release-build-rc.yml) - Prepares and syncs local apt repository - Adds and signs deb package to it using `reprepro` - Uploads new deb package to the distributed repo Closes: https://github.com/paritytech/release-engineering/issues/239 --- .github/scripts/common/lib.sh | 33 +++- .github/scripts/release/distributions | 39 +++++ .../release-40_publish-deb-package.yml | 152 ++++++++++++++++++ 3 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 .github/scripts/release/distributions create mode 100644 .github/workflows/release-40_publish-deb-package.yml diff --git a/.github/scripts/common/lib.sh b/.github/scripts/common/lib.sh index e3dd6224f29b..6b8f70a26d7e 100755 --- a/.github/scripts/common/lib.sh +++ b/.github/scripts/common/lib.sh @@ -237,15 +237,44 @@ fetch_release_artifacts() { popd > /dev/null } -# Fetch the release artifacts like binary and signatures from S3. Assumes the ENV are set: +# Fetch deb package from S3. Assumes the ENV are set: # - RELEASE_ID # - GITHUB_TOKEN # - REPO in the form paritytech/polkadot -fetch_release_artifacts_from_s3() { +fetch_debian_package_from_s3() { BINARY=$1 echo "Version : $VERSION" echo "Repo : $REPO" echo "Binary : $BINARY" + echo "Tag : $RELEASE_TAG" + OUTPUT_DIR=${OUTPUT_DIR:-"./release-artifacts/${BINARY}"} + echo "OUTPUT_DIR : $OUTPUT_DIR" + + URL_BASE=$(get_s3_url_base $BINARY) + echo "URL_BASE=$URL_BASE" + + URL=$URL_BASE/$RELEASE_TAG/x86_64-unknown-linux-gnu/${BINARY}_${VERSION}_amd64.deb + + mkdir -p "$OUTPUT_DIR" + pushd "$OUTPUT_DIR" > /dev/null + + echo "Fetching deb package..." + + echo "Fetching %s" "$URL" + curl --progress-bar -LO "$URL" || echo "Missing $URL" + + pwd + ls -al --color + popd > /dev/null + +} + +# Fetch the release artifacts like binary and signatures from S3. Assumes the ENV are set: +# - RELEASE_ID +# - GITHUB_TOKEN +# - REPO in the form paritytech/polkadot +fetch_release_artifacts_from_s3() { + BINARY=$1 OUTPUT_DIR=${OUTPUT_DIR:-"./release-artifacts/${BINARY}"} echo "OUTPUT_DIR : $OUTPUT_DIR" diff --git a/.github/scripts/release/distributions b/.github/scripts/release/distributions new file mode 100644 index 000000000000..a430ec76c6ba --- /dev/null +++ b/.github/scripts/release/distributions @@ -0,0 +1,39 @@ +Origin: Parity +Label: Parity +Codename: release +Architectures: amd64 +Components: main +Description: Apt repository for software made by Parity Technologies Ltd. +SignWith: 90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE + +Origin: Parity +Label: Parity Staging +Codename: staging +Architectures: amd64 +Components: main +Description: Staging distribution for Parity Technologies Ltd. packages +SignWith: 90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE + +Origin: Parity +Label: Parity stable2407 +Codename: stable2407 +Architectures: amd64 +Components: main +Description: Apt repository for software made by Parity Technologies Ltd. +SignWith: 90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE + +Origin: Parity +Label: Parity stable2409 +Codename: stable2409 +Architectures: amd64 +Components: main +Description: Apt repository for software made by Parity Technologies Ltd. +SignWith: 90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE + +Origin: Parity +Label: Parity stable2412 +Codename: stable2412 +Architectures: amd64 +Components: main +Description: Apt repository for software made by Parity Technologies Ltd. +SignWith: 90BD75EBBB8E95CB3DA6078F94A4029AB4B35DAE diff --git a/.github/workflows/release-40_publish-deb-package.yml b/.github/workflows/release-40_publish-deb-package.yml new file mode 100644 index 000000000000..3c5411ab16f0 --- /dev/null +++ b/.github/workflows/release-40_publish-deb-package.yml @@ -0,0 +1,152 @@ +name: Release - Publish polakdot deb package + +on: + workflow_dispatch: + inputs: + tag: + description: Current final release tag in the format polakdot-stableYYMM or polkadot-stable-YYMM-X + default: polkadot-stable2412 + required: true + type: string + + distribution: + description: Distribution where to publish deb package (release, staging, stable2407, etc) + default: staging + required: true + type: string + +jobs: + check-synchronization: + uses: paritytech-release/sync-workflows/.github/workflows/check-syncronization.yml@main + + validate-inputs: + needs: [check-synchronization] + if: ${{ needs.check-synchronization.outputs.checks_passed }} == 'true' + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.validate_inputs.outputs.release_tag }} + + steps: + - name: Checkout sources + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Validate inputs + id: validate_inputs + run: | + . ./.github/scripts/common/lib.sh + + RELEASE_TAG=$(validate_stable_tag ${{ inputs.tag }}) + echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT + + + fetch-artifacts-from-s3: + runs-on: ubuntu-latest + needs: [validate-inputs] + env: + REPO: ${{ github.repository }} + RELEASE_TAG: ${{ needs.validate-inputs.outputs.release_tag }} + outputs: + VERSION: ${{ steps.fetch_artifacts_from_s3.outputs.VERSION }} + + steps: + - name: Checkout sources + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Fetch rc artifacts or release artifacts from s3 based on version + id: fetch_artifacts_from_s3 + run: | + . ./.github/scripts/common/lib.sh + + VERSION="$(get_polkadot_node_version_from_code)" + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT + + fetch_debian_package_from_s3 polkadot + + - name: Upload artifacts + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: release-artifacts + path: release-artifacts/polkadot/*.deb + + publish-deb-package: + runs-on: ubuntu-latest + needs: [fetch-artifacts-from-s3] + environment: release + env: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_DEB_PATH: "s3://releases-package-repos/deb" + LOCAL_DEB_REPO_PATH: ${{ github.workspace }}/deb + VERSION: ${{ needs.fetch-artifacts-from-s3.outputs.VERSION }} + + steps: + - name: Install pgpkkms + run: | + # Install pgpkms that is used to sign built artifacts + python3 -m pip install "pgpkms @ git+https://github.com/paritytech-release/pgpkms.git@1f8555426662ac93a3849480a35449f683b1c89f" + echo "PGPKMS_REPREPRO_PATH=$(which pgpkms-reprepro)" >> $GITHUB_ENV + + - name: Install awscli + run: | + python3 -m pip install awscli + which aws + + - name: Checkout sources + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 + + - name: Import gpg keys + shell: bash + run: | + . ./.github/scripts/common/lib.sh + + import_gpg_keys + + - name: Download artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: release-artifacts + path: release-artifacts + + - name: Setup local deb repo + run: | + sudo apt-get install -y reprepro + which reprepro + + sed -i "s|^SignWith:.*|SignWith: ! ${PGPKMS_REPREPRO_PATH}|" ${{ github.workspace }}/.github/scripts/release/distributions + + mkdir -p ${{ github.workspace }}/deb/conf + cp ${{ github.workspace }}/.github/scripts/release/distributions ${{ github.workspace }}/deb/conf/distributions + cat ${{ github.workspace }}/deb/conf/distributions + + - name: Sync local deb repo + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + run: | + # Download the current state of the deb repo + aws s3 sync "$AWS_DEB_PATH/db" "$LOCAL_DEB_REPO_PATH/db" + aws s3 sync "$AWS_DEB_PATH/pool" "$LOCAL_DEB_REPO_PATH/pool" + aws s3 sync "$AWS_DEB_PATH/dists" "$LOCAL_DEB_REPO_PATH/dists" + + - name: Add deb package to local repo + env: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + run: | + # Add the new deb to the repo + reprepro -b "$LOCAL_DEB_REPO_PATH" includedeb "${{ inputs.distribution }}" "release-artifacts/polkadot_${VERSION}_amd64.deb" + + - name: Upload updated deb repo + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + run: | + # Upload the updated repo - dists and pool should be publicly readable + aws s3 sync "$LOCAL_DEB_REPO_PATH/pool" "$AWS_DEB_PATH/pool" --acl public-read + aws s3 sync "$LOCAL_DEB_REPO_PATH/dists" "$AWS_DEB_PATH/dists" --acl public-read + aws s3 sync "$LOCAL_DEB_REPO_PATH/db" "$AWS_DEB_PATH/db" + aws s3 sync "$LOCAL_DEB_REPO_PATH/conf" "$AWS_DEB_PATH/conf" + + # Invalidate caches to make sure latest files are served + aws cloudfront create-invalidation --distribution-id E36FKEYWDXAZYJ --paths '/deb/*' From 1c0b610078611dfebc082be52e77109ea4517e48 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 26 Nov 2024 15:52:23 +0100 Subject: [PATCH 042/340] xcm: fix local/remote exports when inner routers return `NotApplicable` (#6645) This PR addresses two small fixes: 1. Fixed a typo ("as as") found on the way. 2. Resolved a bug in the `local/remote exporters` used for bridging. Previously, they consumed `dest` and `msg` without returning them when inner routers/exporters failed with `NotApplicable`. This PR ensures compliance with the [`SendXcm`](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/xcm/src/v5/traits.rs#L449-L450) and [`ExportXcm`](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/xcm/xcm-executor/src/traits/export.rs#L44-L45) traits. --------- Co-authored-by: GitHub Action --- polkadot/grafana/README.md | 2 +- polkadot/grafana/parachains/status.json | 2 +- polkadot/xcm/src/v3/traits.rs | 4 +- polkadot/xcm/src/v4/traits.rs | 4 +- polkadot/xcm/src/v5/traits.rs | 4 +- .../xcm/xcm-builder/src/universal_exports.rs | 265 +++++++++++++++--- .../xcm/xcm-executor/src/traits/export.rs | 4 +- prdoc/pr_6645.prdoc | 14 + .../frame/examples/default-config/src/lib.rs | 4 +- 9 files changed, 248 insertions(+), 55 deletions(-) create mode 100644 prdoc/pr_6645.prdoc diff --git a/polkadot/grafana/README.md b/polkadot/grafana/README.md index e909fdd29a75..0ecb0b70515b 100644 --- a/polkadot/grafana/README.md +++ b/polkadot/grafana/README.md @@ -90,4 +90,4 @@ and issue statement or initiate dispute. - **Assignment delay tranches**. Approval voting is designed such that validators assigned to check a specific candidate are split up into equal delay tranches (0.5 seconds each). All validators checks are ordered by the delay tranche index. Early tranches of validators have the opportunity to check the candidate first before later tranches -that act as as backups in case of no shows. +that act as backups in case of no shows. diff --git a/polkadot/grafana/parachains/status.json b/polkadot/grafana/parachains/status.json index 5942cbdf4479..22250967848d 100644 --- a/polkadot/grafana/parachains/status.json +++ b/polkadot/grafana/parachains/status.json @@ -1405,7 +1405,7 @@ "type": "prometheus", "uid": "$data_source" }, - "description": "Approval voting requires that validators which are assigned to check a specific \ncandidate are split up into delay tranches (0.5s each). Then, all validators checks are ordered by the delay \ntranche index. Early tranches of validators will check the candidate first and later tranches act as as backups in case of no shows.", + "description": "Approval voting requires that validators which are assigned to check a specific \ncandidate are split up into delay tranches (0.5s each). Then, all validators checks are ordered by the delay \ntranche index. Early tranches of validators will check the candidate first and later tranches act as backups in case of no shows.", "gridPos": { "h": 9, "w": 18, diff --git a/polkadot/xcm/src/v3/traits.rs b/polkadot/xcm/src/v3/traits.rs index 1c8620708922..cbf85b454cc6 100644 --- a/polkadot/xcm/src/v3/traits.rs +++ b/polkadot/xcm/src/v3/traits.rs @@ -547,13 +547,13 @@ impl SendXcm for Tuple { } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. pub fn validate_send(dest: MultiLocation, msg: Xcm<()>) -> SendResult { T::validate(&mut Some(dest), &mut Some(msg)) } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. /// /// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message /// could not be sent. diff --git a/polkadot/xcm/src/v4/traits.rs b/polkadot/xcm/src/v4/traits.rs index f32b26fb163d..178093d27177 100644 --- a/polkadot/xcm/src/v4/traits.rs +++ b/polkadot/xcm/src/v4/traits.rs @@ -289,13 +289,13 @@ impl SendXcm for Tuple { } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. pub fn validate_send(dest: Location, msg: Xcm<()>) -> SendResult { T::validate(&mut Some(dest), &mut Some(msg)) } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. /// /// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message /// could not be sent. diff --git a/polkadot/xcm/src/v5/traits.rs b/polkadot/xcm/src/v5/traits.rs index 1f5041ca8d84..dd067b774fcd 100644 --- a/polkadot/xcm/src/v5/traits.rs +++ b/polkadot/xcm/src/v5/traits.rs @@ -502,13 +502,13 @@ impl SendXcm for Tuple { } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. pub fn validate_send(dest: Location, msg: Xcm<()>) -> SendResult { T::validate(&mut Some(dest), &mut Some(msg)) } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. /// /// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message /// could not be sent. diff --git a/polkadot/xcm/xcm-builder/src/universal_exports.rs b/polkadot/xcm/xcm-builder/src/universal_exports.rs index 5c754f01ec0a..aae8438c78d2 100644 --- a/polkadot/xcm/xcm-builder/src/universal_exports.rs +++ b/polkadot/xcm/xcm-builder/src/universal_exports.rs @@ -68,20 +68,28 @@ impl> SendXcm fn validate( dest: &mut Option, - xcm: &mut Option>, + msg: &mut Option>, ) -> SendResult { - let d = dest.take().ok_or(MissingArgument)?; + // This `clone` ensures that `dest` is not consumed in any case. + let d = dest.clone().take().ok_or(MissingArgument)?; let universal_source = UniversalLocation::get(); - let devolved = match ensure_is_remote(universal_source.clone(), d) { - Ok(x) => x, - Err(d) => { - *dest = Some(d); - return Err(NotApplicable) - }, - }; - let (network, destination) = devolved; - let xcm = xcm.take().ok_or(SendError::MissingArgument)?; - validate_export::(network, 0, universal_source, destination, xcm) + let devolved = ensure_is_remote(universal_source.clone(), d).map_err(|_| NotApplicable)?; + let (remote_network, remote_location) = devolved; + let xcm = msg.take().ok_or(MissingArgument)?; + + validate_export::( + remote_network, + 0, + universal_source, + remote_location, + xcm.clone(), + ) + .inspect_err(|err| { + if let NotApplicable = err { + // We need to make sure that msg is not consumed in case of `NotApplicable`. + *msg = Some(xcm); + } + }) } fn deliver(ticket: Exporter::Ticket) -> Result { @@ -95,7 +103,7 @@ pub trait ExporterFor { /// /// The payment is specified from the local context, not the bridge chain. This is the /// total amount to withdraw in to Holding and should cover both payment for the execution on - /// the bridge chain as well as payment for the use of the `ExportMessage` instruction. + /// the bridge chain and payment for the use of the `ExportMessage` instruction. fn exporter_for( network: &NetworkId, remote_location: &InteriorLocation, @@ -205,7 +213,8 @@ impl, msg: &mut Option>, ) -> SendResult { - let d = dest.clone().ok_or(MissingArgument)?; + // This `clone` ensures that `dest` is not consumed in any case. + let d = dest.clone().take().ok_or(MissingArgument)?; let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?; let (remote_network, remote_location) = devolved; let xcm = msg.take().ok_or(MissingArgument)?; @@ -216,7 +225,7 @@ impl(bridge, message) + validate_send::(bridge, message).inspect_err(|err| { + if let NotApplicable = err { + // We need to make sure that msg is not consumed in case of `NotApplicable`. + *msg = Some(xcm); + } + }) } fn deliver(validation: Self::Ticket) -> Result { @@ -272,9 +290,9 @@ impl, msg: &mut Option>, ) -> SendResult { - let d = dest.as_ref().ok_or(MissingArgument)?; - let devolved = - ensure_is_remote(UniversalLocation::get(), d.clone()).map_err(|_| NotApplicable)?; + // This `clone` ensures that `dest` is not consumed in any case. + let d = dest.clone().take().ok_or(MissingArgument)?; + let devolved = ensure_is_remote(UniversalLocation::get(), d).map_err(|_| NotApplicable)?; let (remote_network, remote_location) = devolved; let xcm = msg.take().ok_or(MissingArgument)?; @@ -284,7 +302,7 @@ impl(bridge, message)?; + let (v, mut cost) = validate_send::(bridge, message).inspect_err(|err| { + if let NotApplicable = err { + // We need to make sure that msg is not consumed in case of `NotApplicable`. + *msg = Some(xcm); + } + })?; if let Some(bridge_payment) = maybe_payment { cost.push(bridge_payment); } @@ -476,10 +502,10 @@ impl< let Location { parents, interior: mut junctions } = BridgedNetwork::get(); match junctions.take_first() { Some(GlobalConsensus(network)) => (network, parents), - _ => return Err(SendError::NotApplicable), + _ => return Err(NotApplicable), } }; - ensure!(&network == &bridged_network, SendError::NotApplicable); + ensure!(&network == &bridged_network, NotApplicable); // We don't/can't use the `channel` for this adapter. let dest = destination.take().ok_or(SendError::MissingArgument)?; @@ -496,7 +522,7 @@ impl< }, Err((dest, _)) => { *destination = Some(dest); - return Err(SendError::NotApplicable) + return Err(NotApplicable) }, }; @@ -540,6 +566,10 @@ impl< #[cfg(test)] mod tests { use super::*; + use frame_support::{ + assert_err, assert_ok, + traits::{Contains, Equals}, + }; #[test] fn ensure_is_remote_works() { @@ -564,21 +594,48 @@ mod tests { assert_eq!(x, Err((Parent, Polkadot, Parachain(1000)).into())); } - pub struct OkSender; - impl SendXcm for OkSender { + pub struct OkFor(PhantomData); + impl> SendXcm for OkFor { type Ticket = (); fn validate( - _destination: &mut Option, + destination: &mut Option, _message: &mut Option>, ) -> SendResult { - Ok(((), Assets::new())) + if let Some(d) = destination.as_ref() { + if Filter::contains(&d) { + return Ok(((), Assets::new())) + } + } + Err(NotApplicable) } fn deliver(_ticket: Self::Ticket) -> Result { Ok([0; 32]) } } + impl> ExportXcm for OkFor { + type Ticket = (); + + fn validate( + network: NetworkId, + _: u32, + _: &mut Option, + destination: &mut Option, + _: &mut Option>, + ) -> SendResult { + if let Some(d) = destination.as_ref() { + if Filter::contains(&(network, d.clone())) { + return Ok(((), Assets::new())) + } + } + Err(NotApplicable) + } + + fn deliver(_ticket: Self::Ticket) -> Result { + Ok([1; 32]) + } + } /// Generic test case asserting that dest and msg is not consumed by `validate` implementation /// of `SendXcm` in case of expected result. @@ -598,46 +655,168 @@ mod tests { } #[test] - fn remote_exporters_does_not_consume_dest_or_msg_on_not_applicable() { + fn local_exporters_works() { frame_support::parameter_types! { pub Local: NetworkId = ByGenesis([0; 32]); pub UniversalLocation: InteriorLocation = [GlobalConsensus(Local::get()), Parachain(1234)].into(); pub DifferentRemote: NetworkId = ByGenesis([22; 32]); - // no routers - pub BridgeTable: Vec = vec![]; + pub RemoteDestination: Junction = Parachain(9657); + pub RoutableBridgeFilter: (NetworkId, InteriorLocation) = (DifferentRemote::get(), RemoteDestination::get().into()); } + type RoutableBridgeExporter = OkFor>; + type NotApplicableBridgeExporter = OkFor<()>; + assert_ok!(validate_export::( + DifferentRemote::get(), + 0, + UniversalLocation::get(), + RemoteDestination::get().into(), + Xcm::default() + )); + assert_err!( + validate_export::( + DifferentRemote::get(), + 0, + UniversalLocation::get(), + RemoteDestination::get().into(), + Xcm::default() + ), + NotApplicable + ); - // check with local destination (should be remote) + // 1. check with local destination (should be remote) let local_dest: Location = (Parent, Parachain(5678)).into(); assert!(ensure_is_remote(UniversalLocation::get(), local_dest.clone()).is_err()); + // UnpaidLocalExporter ensure_validate_does_not_consume_dest_or_msg::< - UnpaidRemoteExporter, OkSender, UniversalLocation>, + UnpaidLocalExporter, >(local_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); + // 2. check with not applicable from the inner router (using `NotApplicableBridgeSender`) + let remote_dest: Location = + (Parent, Parent, DifferentRemote::get(), RemoteDestination::get()).into(); + assert!(ensure_is_remote(UniversalLocation::get(), remote_dest.clone()).is_ok()); + + // UnpaidLocalExporter + ensure_validate_does_not_consume_dest_or_msg::< + UnpaidLocalExporter, + >(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); + + // 3. Ok - deliver + // UnpaidRemoteExporter + assert_ok!(send_xcm::>( + remote_dest, + Xcm::default() + )); + } + + #[test] + fn remote_exporters_works() { + frame_support::parameter_types! { + pub Local: NetworkId = ByGenesis([0; 32]); + pub UniversalLocation: InteriorLocation = [GlobalConsensus(Local::get()), Parachain(1234)].into(); + pub DifferentRemote: NetworkId = ByGenesis([22; 32]); + pub RoutableBridge: Location = Location::new(1, Parachain(9657)); + // not routable + pub NotApplicableBridgeTable: Vec = vec![]; + // routable + pub RoutableBridgeTable: Vec = vec![ + NetworkExportTableItem::new( + DifferentRemote::get(), + None, + RoutableBridge::get(), + None + ) + ]; + } + type RoutableBridgeSender = OkFor>; + type NotApplicableBridgeSender = OkFor<()>; + assert_ok!(validate_send::(RoutableBridge::get(), Xcm::default())); + assert_err!( + validate_send::(RoutableBridge::get(), Xcm::default()), + NotApplicable + ); + + // 1. check with local destination (should be remote) + let local_dest: Location = (Parent, Parachain(5678)).into(); + assert!(ensure_is_remote(UniversalLocation::get(), local_dest.clone()).is_err()); + + // UnpaidRemoteExporter + ensure_validate_does_not_consume_dest_or_msg::< + UnpaidRemoteExporter< + NetworkExportTable, + RoutableBridgeSender, + UniversalLocation, + >, + >(local_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); + // SovereignPaidRemoteExporter ensure_validate_does_not_consume_dest_or_msg::< SovereignPaidRemoteExporter< - NetworkExportTable, - OkSender, + NetworkExportTable, + RoutableBridgeSender, UniversalLocation, >, >(local_dest, |result| assert_eq!(Err(NotApplicable), result)); - // check with not applicable destination + // 2. check with not applicable destination (`NotApplicableBridgeTable`) let remote_dest: Location = (Parent, Parent, DifferentRemote::get()).into(); assert!(ensure_is_remote(UniversalLocation::get(), remote_dest.clone()).is_ok()); + // UnpaidRemoteExporter ensure_validate_does_not_consume_dest_or_msg::< - UnpaidRemoteExporter, OkSender, UniversalLocation>, + UnpaidRemoteExporter< + NetworkExportTable, + RoutableBridgeSender, + UniversalLocation, + >, >(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); - + // SovereignPaidRemoteExporter ensure_validate_does_not_consume_dest_or_msg::< SovereignPaidRemoteExporter< - NetworkExportTable, - OkSender, + NetworkExportTable, + RoutableBridgeSender, UniversalLocation, >, >(remote_dest, |result| assert_eq!(Err(NotApplicable), result)); + + // 3. check with not applicable from the inner router (using `NotApplicableBridgeSender`) + let remote_dest: Location = (Parent, Parent, DifferentRemote::get()).into(); + assert!(ensure_is_remote(UniversalLocation::get(), remote_dest.clone()).is_ok()); + + // UnpaidRemoteExporter + ensure_validate_does_not_consume_dest_or_msg::< + UnpaidRemoteExporter< + NetworkExportTable, + NotApplicableBridgeSender, + UniversalLocation, + >, + >(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); + // SovereignPaidRemoteExporter + ensure_validate_does_not_consume_dest_or_msg::< + SovereignPaidRemoteExporter< + NetworkExportTable, + NotApplicableBridgeSender, + UniversalLocation, + >, + >(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); + + // 4. Ok - deliver + // UnpaidRemoteExporter + assert_ok!(send_xcm::< + UnpaidRemoteExporter< + NetworkExportTable, + RoutableBridgeSender, + UniversalLocation, + >, + >(remote_dest.clone(), Xcm::default())); + // SovereignPaidRemoteExporter + assert_ok!(send_xcm::< + SovereignPaidRemoteExporter< + NetworkExportTable, + RoutableBridgeSender, + UniversalLocation, + >, + >(remote_dest, Xcm::default())); } #[test] diff --git a/polkadot/xcm/xcm-executor/src/traits/export.rs b/polkadot/xcm/xcm-executor/src/traits/export.rs index b356e0da7df7..3e9275edab37 100644 --- a/polkadot/xcm/xcm-executor/src/traits/export.rs +++ b/polkadot/xcm/xcm-executor/src/traits/export.rs @@ -108,7 +108,7 @@ impl ExportXcm for Tuple { } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. pub fn validate_export( network: NetworkId, channel: u32, @@ -120,7 +120,7 @@ pub fn validate_export( } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps -/// both in `Some` before passing them as as mutable references into `T::send_xcm`. +/// both in `Some` before passing them as mutable references into `T::send_xcm`. /// /// Returns either `Ok` with the price of the delivery, or `Err` with the reason why the message /// could not be sent. diff --git a/prdoc/pr_6645.prdoc b/prdoc/pr_6645.prdoc new file mode 100644 index 000000000000..f033cadc0b6e --- /dev/null +++ b/prdoc/pr_6645.prdoc @@ -0,0 +1,14 @@ +title: 'xcm: fix local/remote exports when inner routers return `NotApplicable`' +doc: +- audience: Runtime Dev + description: |- + Resolved a bug in the `local/remote exporters` used for bridging. Previously, they consumed `dest` and `msg` without returning them when inner routers/exporters failed with `NotApplicable`. This PR ensures compliance with the [`SendXcm`](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/xcm/src/v5/traits.rs#L449-L450) and [`ExportXcm`](https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/xcm/xcm-executor/src/traits/export.rs#L44-L45) traits. +crates: +- name: staging-xcm-builder + bump: patch +- name: polkadot + bump: none +- name: staging-xcm + bump: none +- name: staging-xcm-executor + bump: none diff --git a/substrate/frame/examples/default-config/src/lib.rs b/substrate/frame/examples/default-config/src/lib.rs index ccdcd4968598..f690bffe0998 100644 --- a/substrate/frame/examples/default-config/src/lib.rs +++ b/substrate/frame/examples/default-config/src/lib.rs @@ -62,10 +62,10 @@ pub mod pallet { type OverwrittenDefaultValue: Get; /// An input parameter that relies on `::AccountId`. This can - /// too have a default, as long as as it is present in `frame_system::DefaultConfig`. + /// too have a default, as long as it is present in `frame_system::DefaultConfig`. type CanDeriveDefaultFromSystem: Get; - /// We might chose to declare as one that doesn't have a default, for whatever semantical + /// We might choose to declare as one that doesn't have a default, for whatever semantical /// reason. #[pallet::no_default] type HasNoDefault: Get; From f520adb0aacd51247ed548c7db9bef874c2cca9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dino=20Pa=C4=8Dandi?= <3002868+Dinonard@users.noreply.github.com> Date: Tue, 26 Nov 2024 15:56:02 +0100 Subject: [PATCH 043/340] Zero refund check for FungibleAdapter (#6506) `FungibleAdapter` will now check if the _refund amount_ is zero before calling deposit & emitting an event. Fixes https://github.com/paritytech/polkadot-sdk/issues/6469. --------- Co-authored-by: GitHub Action --- prdoc/pr_6506.prdoc | 10 +++++ .../frame/transaction-payment/src/payment.rs | 17 +++++---- .../frame/transaction-payment/src/tests.rs | 37 +++++++++++++++++++ 3 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 prdoc/pr_6506.prdoc diff --git a/prdoc/pr_6506.prdoc b/prdoc/pr_6506.prdoc new file mode 100644 index 000000000000..7c6164a9959a --- /dev/null +++ b/prdoc/pr_6506.prdoc @@ -0,0 +1,10 @@ +title: Zero refund check for FungibleAdapter +doc: +- audience: Runtime User + description: |- + `FungibleAdapter` will now check if the _refund amount_ is zero before calling deposit & emitting an event. + + Fixes https://github.com/paritytech/polkadot-sdk/issues/6469. +crates: +- name: pallet-transaction-payment + bump: patch diff --git a/substrate/frame/transaction-payment/src/payment.rs b/substrate/frame/transaction-payment/src/payment.rs index 4b39cd3fe53b..b8a047fee3e6 100644 --- a/substrate/frame/transaction-payment/src/payment.rs +++ b/substrate/frame/transaction-payment/src/payment.rs @@ -155,14 +155,15 @@ where if let Some(paid) = already_withdrawn { // Calculate how much refund we should return let refund_amount = paid.peek().saturating_sub(corrected_fee); - // refund to the the account that paid the fees if it exists. otherwise, don't refind - // anything. - let refund_imbalance = if F::total_balance(who) > F::Balance::zero() { - F::deposit(who, refund_amount, Precision::BestEffort) - .unwrap_or_else(|_| Debt::::zero()) - } else { - Debt::::zero() - }; + // Refund to the the account that paid the fees if it exists & refund is non-zero. + // Otherwise, don't refund anything. + let refund_imbalance = + if refund_amount > Zero::zero() && F::total_balance(who) > F::Balance::zero() { + F::deposit(who, refund_amount, Precision::BestEffort) + .unwrap_or_else(|_| Debt::::zero()) + } else { + Debt::::zero() + }; // merge the imbalance caused by paying the fees and refunding parts of it again. let adjusted_paid: Credit = paid .offset(refund_imbalance) diff --git a/substrate/frame/transaction-payment/src/tests.rs b/substrate/frame/transaction-payment/src/tests.rs index 572c1d4961dd..bde1bf64728e 100644 --- a/substrate/frame/transaction-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/src/tests.rs @@ -877,3 +877,40 @@ fn no_fee_and_no_weight_for_other_origins() { assert_eq!(post_info.actual_weight, Some(info.call_weight)); }) } + +#[test] +fn fungible_adapter_no_zero_refund_action() { + type FungibleAdapterT = payment::FungibleAdapter; + + ExtBuilder::default().balance_factor(10).build().execute_with(|| { + System::set_block_number(10); + + let dummy_acc = 1; + let (actual_fee, no_tip) = (10, 0); + let already_paid = >::withdraw_fee( + &dummy_acc, + CALL, + &CALL.get_dispatch_info(), + actual_fee, + no_tip, + ).expect("Account must have enough funds."); + + // Correction action with no expected side effect. + assert!(>::correct_and_deposit_fee( + &dummy_acc, + &CALL.get_dispatch_info(), + &default_post_info(), + actual_fee, + no_tip, + already_paid, + ).is_ok()); + + // Ensure no zero amount deposit event is emitted. + let events = System::events(); + assert!(!events + .iter() + .any(|record| matches!(record.event, RuntimeEvent::Balances(pallet_balances::Event::Deposit { amount, .. }) if amount.is_zero())), + "No zero amount deposit amount event should be emitted.", + ); + }); +} From fc315ac5979e9bf1cc56b58ab6f364b6b2689635 Mon Sep 17 00:00:00 2001 From: Giuseppe Re Date: Tue, 26 Nov 2024 17:26:19 +0100 Subject: [PATCH 044/340] Hide nonce implementation details in metadata (#6562) See https://github.com/polkadot-fellows/runtimes/issues/248 : using `TypeWithDefault` having derived `TypeInfo` for `Nonce` causes a breaking change in metadata for nonce type because it's no longer `u64`. Adding a default implementation of `TypeInfo` for `TypeWithDefault` to restore the original type info in metadata. --------- Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- prdoc/pr_6562.prdoc | 14 +++++++++++ .../runtime/src/type_with_default.rs | 23 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_6562.prdoc diff --git a/prdoc/pr_6562.prdoc b/prdoc/pr_6562.prdoc new file mode 100644 index 000000000000..250b656aefb5 --- /dev/null +++ b/prdoc/pr_6562.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Hide nonce implementation details in metadata + +doc: + - audience: Runtime Dev + description: | + Use custom implementation of TypeInfo for TypeWithDefault to show inner value's type info. + This should bring back nonce to u64 in metadata. + +crates: +- name: sp-runtime + bump: minor \ No newline at end of file diff --git a/substrate/primitives/runtime/src/type_with_default.rs b/substrate/primitives/runtime/src/type_with_default.rs index 5790e3ab6bf6..b0eca22e5c1a 100644 --- a/substrate/primitives/runtime/src/type_with_default.rs +++ b/substrate/primitives/runtime/src/type_with_default.rs @@ -31,7 +31,7 @@ use num_traits::{ CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, Num, NumCast, PrimInt, Saturating, ToPrimitive, }; -use scale_info::TypeInfo; +use scale_info::{StaticTypeInfo, TypeInfo}; use sp_core::Get; #[cfg(feature = "serde")] @@ -40,7 +40,8 @@ use serde::{Deserialize, Serialize}; /// A type that wraps another type and provides a default value. /// /// Passes through arithmetical and many other operations to the inner value. -#[derive(Encode, Decode, TypeInfo, Debug, MaxEncodedLen)] +/// Type information for metadata is the same as the inner value's type. +#[derive(Encode, Decode, Debug, MaxEncodedLen)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct TypeWithDefault>(T, PhantomData); @@ -50,6 +51,17 @@ impl> TypeWithDefault { } } +// Hides implementation details from the outside (for metadata type information). +// +// The type info showed in metadata is the one of the inner value's type. +impl + 'static> TypeInfo for TypeWithDefault { + type Identity = Self; + + fn type_info() -> scale_info::Type { + T::type_info() + } +} + impl> Clone for TypeWithDefault { fn clone(&self) -> Self { Self(self.0.clone(), PhantomData) @@ -511,6 +523,7 @@ impl> CompactAs for TypeWithDefault { #[cfg(test)] mod tests { use super::TypeWithDefault; + use scale_info::TypeInfo; use sp_arithmetic::traits::{AtLeast16Bit, AtLeast32Bit, AtLeast8Bit}; use sp_core::Get; @@ -565,5 +578,11 @@ mod tests { } type U128WithDefault = TypeWithDefault; impl WrapAtLeast32Bit for U128WithDefault {} + + assert_eq!(U8WithDefault::type_info(), ::type_info()); + assert_eq!(U16WithDefault::type_info(), ::type_info()); + assert_eq!(U32WithDefault::type_info(), ::type_info()); + assert_eq!(U64WithDefault::type_info(), ::type_info()); + assert_eq!(U128WithDefault::type_info(), ::type_info()); } } From 139691b17c66aa074d2b4ae935158d4296068f72 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 26 Nov 2024 16:24:43 -0300 Subject: [PATCH 045/340] Fix `XcmPaymentApi::query_weight_to_asset_fee` version conversion (#6459) The `query_weight_to_asset_fee` function was trying to convert versions by using `try_as`, this function [doesn't convert from a versioned to a concrete type](https://github.com/paritytech/polkadot-sdk/blob/0156ca8f959d5cf3787c18113ce48acaaf1a8345/polkadot/xcm/src/lib.rs#L131). This would cause all calls with a lower version to fail. The correct function to use is the good old [try_into](https://github.com/paritytech/polkadot-sdk/blob/0156ca8f959d5cf3787c18113ce48acaaf1a8345/polkadot/xcm/src/lib.rs#L184). Now those calls work :) --------- Co-authored-by: command-bot <> Co-authored-by: Branislav Kontur Co-authored-by: GitHub Action --- Cargo.lock | 13 +++ .../assets/asset-hub-rococo/Cargo.toml | 1 + .../assets/asset-hub-rococo/src/lib.rs | 28 ++--- .../assets/asset-hub-rococo/tests/tests.rs | 24 +++- .../assets/asset-hub-westend/Cargo.toml | 1 + .../assets/asset-hub-westend/src/lib.rs | 29 ++--- .../assets/asset-hub-westend/tests/tests.rs | 18 ++- .../runtimes/assets/common/src/lib.rs | 79 +++++++++---- .../runtimes/assets/test-utils/Cargo.toml | 4 + .../assets/test-utils/src/test_cases.rs | 110 +++++++++++++++++- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 1 + .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 3 +- .../bridge-hub-rococo/tests/tests.rs | 16 ++- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 1 + .../bridge-hubs/bridge-hub-westend/src/lib.rs | 3 +- .../bridge-hub-westend/tests/tests.rs | 16 ++- .../collectives-westend/Cargo.toml | 1 + .../collectives-westend/src/lib.rs | 3 +- .../collectives-westend/tests/tests.rs | 14 ++- .../coretime/coretime-rococo/Cargo.toml | 4 + .../coretime/coretime-rococo/src/lib.rs | 3 +- .../coretime/coretime-rococo/tests/tests.rs | 14 ++- .../coretime/coretime-westend/Cargo.toml | 3 + .../coretime/coretime-westend/src/lib.rs | 3 +- .../coretime/coretime-westend/tests/tests.rs | 14 ++- .../runtimes/people/people-rococo/Cargo.toml | 3 + .../runtimes/people/people-rococo/src/lib.rs | 3 +- .../people/people-rococo/tests/tests.rs | 14 ++- .../runtimes/people/people-westend/Cargo.toml | 3 + .../runtimes/people/people-westend/src/lib.rs | 3 +- .../people/people-westend/tests/tests.rs | 14 ++- .../parachains/runtimes/test-utils/Cargo.toml | 4 + .../runtimes/test-utils/src/test_cases.rs | 67 ++++++++++- .../xcm-runtime-apis/tests/fee_estimation.rs | 23 ++++ polkadot/xcm/xcm-runtime-apis/tests/mock.rs | 3 +- prdoc/pr_6459.prdoc | 22 ++++ 36 files changed, 483 insertions(+), 82 deletions(-) create mode 100644 prdoc/pr_6459.prdoc diff --git a/Cargo.lock b/Cargo.lock index c2d2eb3e9644..58b8b222cce4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -899,6 +899,7 @@ dependencies = [ "pallet-xcm-benchmarks 7.0.0", "pallet-xcm-bridge-hub-router 0.5.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -1036,6 +1037,7 @@ dependencies = [ "pallet-xcm-benchmarks 7.0.0", "pallet-xcm-bridge-hub-router 0.5.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -1077,6 +1079,7 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "hex-literal", + "pallet-asset-conversion 10.0.0", "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-collator-selection 9.0.0", @@ -1094,6 +1097,7 @@ dependencies = [ "staging-xcm-builder 7.0.0", "staging-xcm-executor 7.0.0", "substrate-wasm-builder 17.0.0", + "xcm-runtime-apis 0.1.0", ] [[package]] @@ -2578,6 +2582,7 @@ dependencies = [ "pallet-xcm-benchmarks 7.0.0", "pallet-xcm-bridge-hub 0.2.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -2815,6 +2820,7 @@ dependencies = [ "pallet-xcm-benchmarks 7.0.0", "pallet-xcm-bridge-hub 0.2.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -3550,6 +3556,7 @@ dependencies = [ "pallet-utility 28.0.0", "pallet-xcm 7.0.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -3991,6 +3998,7 @@ dependencies = [ "pallet-xcm 7.0.0", "pallet-xcm-benchmarks 7.0.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -4090,6 +4098,7 @@ dependencies = [ "pallet-xcm 7.0.0", "pallet-xcm-benchmarks 7.0.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -16165,6 +16174,7 @@ dependencies = [ "pallet-session 28.0.0", "pallet-timestamp 27.0.0", "pallet-xcm 7.0.0", + "parachains-common 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "sp-consensus-aura 0.32.0", @@ -16176,6 +16186,7 @@ dependencies = [ "staging-xcm 7.0.0", "staging-xcm-executor 7.0.0", "substrate-wasm-builder 17.0.0", + "xcm-runtime-apis 0.1.0", ] [[package]] @@ -16574,6 +16585,7 @@ dependencies = [ "pallet-xcm 7.0.0", "pallet-xcm-benchmarks 7.0.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", @@ -16675,6 +16687,7 @@ dependencies = [ "pallet-xcm 7.0.0", "pallet-xcm-benchmarks 7.0.0", "parachains-common 7.0.0", + "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-runtime-common 7.0.0", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index 42adaba7a27c..bfe8ed869758 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -99,6 +99,7 @@ snowbridge-router-primitives = { workspace = true } [dev-dependencies] asset-test-utils = { workspace = true, default-features = true } +parachains-runtimes-test-utils = { workspace = true, default-features = true } [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index bc48c2d805fd..b6f3ccd3901b 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -1415,37 +1415,31 @@ impl_runtime_apis! { // We accept the native token to pay fees. let mut acceptable_assets = vec![AssetId(native_token.clone())]; // We also accept all assets in a pool with the native token. - let assets_in_pool_with_native = assets_common::get_assets_in_pool_with::< - Runtime, - xcm::v5::Location - >(&native_token).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?.into_iter(); - acceptable_assets.extend(assets_in_pool_with_native); + acceptable_assets.extend( + assets_common::PoolAdapter::::get_assets_in_pool_with(native_token) + .map_err(|()| XcmPaymentApiError::VersionedConversionFailed)? + ); PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets) } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { let native_asset = xcm_config::TokenLocation::get(); let fee_in_native = WeightToFee::weight_to_fee(&weight); - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == native_asset => { // for native token Ok(fee_in_native) }, Ok(asset_id) => { - let assets_in_pool_with_this_asset: Vec<_> = assets_common::get_assets_in_pool_with::< - Runtime, - xcm::v5::Location - >(&asset_id.0).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?; - if assets_in_pool_with_this_asset - .into_iter() - .map(|asset_id| asset_id.0) - .any(|location| location == native_asset) { - pallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens( - asset_id.clone().0, + // Try to get current price of `asset_id` in `native_asset`. + if let Ok(Some(swapped_in_native)) = assets_common::PoolAdapter::::quote_price_tokens_for_exact_tokens( + asset_id.0.clone(), native_asset, fee_in_native, true, // We include the fee. - ).ok_or(XcmPaymentApiError::AssetNotFound) + ) { + Ok(swapped_in_native) } else { log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!"); Err(XcmPaymentApiError::AssetNotFound) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs index 5da8b45417a3..d056405adff8 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -24,10 +24,10 @@ use asset_hub_rococo_runtime::{ ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, LocationToAccountId, StakingPot, TokenLocation, TrustBackedAssetsPalletLocation, XcmConfig, }, - AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, CollatorSelection, - ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, - MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, - SessionKeys, TrustBackedAssetsInstance, XcmpQueue, + AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, Block, + CollatorSelection, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, + MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, + RuntimeEvent, RuntimeOrigin, SessionKeys, TrustBackedAssetsInstance, XcmpQueue, }; use asset_test_utils::{ test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, @@ -1471,3 +1471,19 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); + asset_test_utils::test_cases::xcm_payment_api_with_pools_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index d5eaa43ab834..a3eaebb59153 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -101,6 +101,7 @@ snowbridge-router-primitives = { workspace = true } [dev-dependencies] asset-test-utils = { workspace = true, default-features = true } +parachains-runtimes-test-utils = { workspace = true, default-features = true } [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index cafea3b6ff8b..f20b6b1fece0 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1528,38 +1528,31 @@ impl_runtime_apis! { // We accept the native token to pay fees. let mut acceptable_assets = vec![AssetId(native_token.clone())]; // We also accept all assets in a pool with the native token. - let assets_in_pool_with_native = assets_common::get_assets_in_pool_with::< - Runtime, - xcm::v5::Location - >(&native_token).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?.into_iter(); - acceptable_assets.extend(assets_in_pool_with_native); + acceptable_assets.extend( + assets_common::PoolAdapter::::get_assets_in_pool_with(native_token) + .map_err(|()| XcmPaymentApiError::VersionedConversionFailed)? + ); PolkadotXcm::query_acceptable_payment_assets(xcm_version, acceptable_assets) } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { let native_asset = xcm_config::WestendLocation::get(); let fee_in_native = WeightToFee::weight_to_fee(&weight); - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == native_asset => { // for native asset Ok(fee_in_native) }, Ok(asset_id) => { - // We recognize assets in a pool with the native one. - let assets_in_pool_with_this_asset: Vec<_> = assets_common::get_assets_in_pool_with::< - Runtime, - xcm::v5::Location - >(&asset_id.0).map_err(|()| XcmPaymentApiError::VersionedConversionFailed)?; - if assets_in_pool_with_this_asset - .into_iter() - .map(|asset_id| asset_id.0) - .any(|location| location == native_asset) { - pallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens( - asset_id.clone().0, + // Try to get current price of `asset_id` in `native_asset`. + if let Ok(Some(swapped_in_native)) = assets_common::PoolAdapter::::quote_price_tokens_for_exact_tokens( + asset_id.0.clone(), native_asset, fee_in_native, true, // We include the fee. - ).ok_or(XcmPaymentApiError::AssetNotFound) + ) { + Ok(swapped_in_native) } else { log::trace!(target: "xcm::xcm_runtime_apis", "query_weight_to_asset_fee - unhandled asset_id: {asset_id:?}!"); Err(XcmPaymentApiError::AssetNotFound) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 5d0f843554a1..109a5dd2c029 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -24,7 +24,7 @@ use asset_hub_westend_runtime::{ ForeignAssetFeeAsExistentialDepositMultiplierFeeCharger, LocationToAccountId, StakingPot, TrustBackedAssetsPalletLocation, WestendLocation, XcmConfig, }, - AllPalletsWithoutSystem, Assets, Balances, ExistentialDeposit, ForeignAssets, + AllPalletsWithoutSystem, Assets, Balances, Block, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, TrustBackedAssetsInstance, XcmpQueue, @@ -1446,3 +1446,19 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); + asset_test_utils::test_cases::xcm_payment_api_with_pools_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 1d2d45b42c5d..25c2df6b68d1 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -28,7 +28,7 @@ extern crate alloc; use crate::matching::{LocalLocationPattern, ParentLocation}; use alloc::vec::Vec; use codec::{Decode, EncodeLike}; -use core::cmp::PartialEq; +use core::{cmp::PartialEq, marker::PhantomData}; use frame_support::traits::{Equals, EverythingBut}; use parachains_common::{AssetIdForTrustBackedAssets, CollectionId, ItemId}; use sp_runtime::traits::TryConvertInto; @@ -137,24 +137,62 @@ pub type PoolAssetsConvertedConcreteId = TryConvertInto, >; -/// Returns an iterator of all assets in a pool with `asset`. -/// -/// Should only be used in runtime APIs since it iterates over the whole -/// `pallet_asset_conversion::Pools` map. -/// -/// It takes in any version of an XCM Location but always returns the latest one. -/// This is to allow some margin of migrating the pools when updating the XCM version. -/// -/// An error of type `()` is returned if the version conversion fails for XCM locations. -/// This error should be mapped by the caller to a more descriptive one. -pub fn get_assets_in_pool_with< - Runtime: pallet_asset_conversion::Config, - L: TryInto + Clone + Decode + EncodeLike + PartialEq, ->( - asset: &L, -) -> Result, ()> { - pallet_asset_conversion::Pools::::iter_keys() - .filter_map(|(asset_1, asset_2)| { +/// Adapter implementation for accessing pools (`pallet_asset_conversion`) that uses `AssetKind` as +/// a `xcm::v*` which could be different from the `xcm::latest`. +pub struct PoolAdapter(PhantomData); +impl< + Runtime: pallet_asset_conversion::Config, + L: TryFrom + TryInto + Clone + Decode + EncodeLike + PartialEq, + > PoolAdapter +{ + /// Returns a vector of all assets in a pool with `asset`. + /// + /// Should only be used in runtime APIs since it iterates over the whole + /// `pallet_asset_conversion::Pools` map. + /// + /// It takes in any version of an XCM Location but always returns the latest one. + /// This is to allow some margin of migrating the pools when updating the XCM version. + /// + /// An error of type `()` is returned if the version conversion fails for XCM locations. + /// This error should be mapped by the caller to a more descriptive one. + pub fn get_assets_in_pool_with(asset: Location) -> Result, ()> { + // convert latest to the `L` version. + let asset: L = asset.try_into().map_err(|_| ())?; + Self::iter_assets_in_pool_with(&asset) + .map(|location| { + // convert `L` to the latest `AssetId` + location.try_into().map_err(|_| ()).map(AssetId) + }) + .collect::, _>>() + } + + /// Provides a current prices. Wrapper over + /// `pallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens`. + /// + /// An error of type `()` is returned if the version conversion fails for XCM locations. + /// This error should be mapped by the caller to a more descriptive one. + pub fn quote_price_tokens_for_exact_tokens( + asset_1: Location, + asset_2: Location, + amount: Runtime::Balance, + include_fees: bool, + ) -> Result, ()> { + // Convert latest to the `L` version. + let asset_1: L = asset_1.try_into().map_err(|_| ())?; + let asset_2: L = asset_2.try_into().map_err(|_| ())?; + + // Quote swap price. + Ok(pallet_asset_conversion::Pallet::::quote_price_tokens_for_exact_tokens( + asset_1, + asset_2, + amount, + include_fees, + )) + } + + /// Helper function for filtering pool. + pub fn iter_assets_in_pool_with(asset: &L) -> impl Iterator + '_ { + pallet_asset_conversion::Pools::::iter_keys().filter_map(|(asset_1, asset_2)| { if asset_1 == *asset { Some(asset_2) } else if asset_2 == *asset { @@ -163,8 +201,7 @@ pub fn get_assets_in_pool_with< None } }) - .map(|location| location.try_into().map_err(|_| ()).map(AssetId)) - .collect::, _>>() + } } #[cfg(test)] diff --git a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml index 529d6460fc4e..f6b3c13e8102 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml @@ -16,6 +16,7 @@ codec = { features = ["derive", "max-encoded-len"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-assets = { workspace = true } +pallet-asset-conversion = { workspace = true } pallet-balances = { workspace = true } pallet-timestamp = { workspace = true } pallet-session = { workspace = true } @@ -36,6 +37,7 @@ xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } pallet-xcm = { workspace = true } +xcm-runtime-apis = { workspace = true } # Bridges pallet-xcm-bridge-hub-router = { workspace = true } @@ -55,6 +57,7 @@ std = [ "cumulus-primitives-core/std", "frame-support/std", "frame-system/std", + "pallet-asset-conversion/std", "pallet-assets/std", "pallet-balances/std", "pallet-collator-selection/std", @@ -69,5 +72,6 @@ std = [ "sp-runtime/std", "xcm-builder/std", "xcm-executor/std", + "xcm-runtime-apis/std", "xcm/std", ] diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs index 8dc720e27753..aeacc1a5471e 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -34,11 +34,14 @@ use parachains_runtimes_test_utils::{ CollatorSessionKeys, ExtBuilder, SlotDurations, ValidatorIdOf, XcmReceivedFrom, }; use sp_runtime::{ - traits::{MaybeEquivalence, StaticLookup, Zero}, + traits::{Block as BlockT, MaybeEquivalence, StaticLookup, Zero}, DispatchError, Saturating, }; use xcm::{latest::prelude::*, VersionedAssets}; use xcm_executor::{traits::ConvertLocation, XcmExecutor}; +use xcm_runtime_apis::fees::{ + runtime_decl_for_xcm_payment_api::XcmPaymentApiV1, Error as XcmPaymentApiError, +}; type RuntimeHelper = parachains_runtimes_test_utils::RuntimeHelper; @@ -1584,3 +1587,108 @@ pub fn reserve_transfer_native_asset_to_non_teleport_para_works< ); }) } + +pub fn xcm_payment_api_with_pools_works() +where + Runtime: XcmPaymentApiV1 + + frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config + + pallet_assets::Config< + pallet_assets::Instance1, + AssetId = u32, + Balance = ::Balance, + > + pallet_asset_conversion::Config< + AssetKind = xcm::v5::Location, + Balance = ::Balance, + >, + ValidatorIdOf: From>, + RuntimeOrigin: OriginTrait::AccountId>, + <::Lookup as StaticLookup>::Source: + From<::AccountId>, + Block: BlockT, +{ + use xcm::prelude::*; + + ExtBuilder::::default().build().execute_with(|| { + let test_account = AccountId::from([0u8; 32]); + let transfer_amount = 100u128; + let xcm_to_weigh = Xcm::::builder_unsafe() + .withdraw_asset((Here, transfer_amount)) + .buy_execution((Here, transfer_amount), Unlimited) + .deposit_asset(AllCounted(1), [1u8; 32]) + .build(); + let versioned_xcm_to_weigh = VersionedXcm::from(xcm_to_weigh.clone().into()); + + let xcm_weight = Runtime::query_xcm_weight(versioned_xcm_to_weigh); + assert!(xcm_weight.is_ok()); + let native_token: Location = Parent.into(); + let native_token_versioned = VersionedAssetId::from(AssetId(native_token.clone())); + let execution_fees = + Runtime::query_weight_to_asset_fee(xcm_weight.unwrap(), native_token_versioned); + assert!(execution_fees.is_ok()); + + // We need some balance to create an asset. + assert_ok!( + pallet_balances::Pallet::::mint_into(&test_account, 3_000_000_000_000,) + ); + + // Now we try to use an asset that's not in a pool. + let asset_id = 1984u32; // USDT. + let asset_not_in_pool: Location = + (PalletInstance(50), GeneralIndex(asset_id.into())).into(); + assert_ok!(pallet_assets::Pallet::::create( + RuntimeOrigin::signed(test_account.clone()), + asset_id.into(), + test_account.clone().into(), + 1000 + )); + let execution_fees = Runtime::query_weight_to_asset_fee( + xcm_weight.unwrap(), + asset_not_in_pool.clone().into(), + ); + assert_eq!(execution_fees, Err(XcmPaymentApiError::AssetNotFound)); + + // We add it to a pool with native. + assert_ok!(pallet_asset_conversion::Pallet::::create_pool( + RuntimeOrigin::signed(test_account.clone()), + native_token.clone().try_into().unwrap(), + asset_not_in_pool.clone().try_into().unwrap() + )); + let execution_fees = Runtime::query_weight_to_asset_fee( + xcm_weight.unwrap(), + asset_not_in_pool.clone().into(), + ); + // Still not enough because it doesn't have any liquidity. + assert_eq!(execution_fees, Err(XcmPaymentApiError::AssetNotFound)); + + // We mint some of the asset... + assert_ok!(pallet_assets::Pallet::::mint( + RuntimeOrigin::signed(test_account.clone()), + asset_id.into(), + test_account.clone().into(), + 3_000_000_000_000, + )); + // ...so we can add liquidity to the pool. + assert_ok!(pallet_asset_conversion::Pallet::::add_liquidity( + RuntimeOrigin::signed(test_account.clone()), + native_token.try_into().unwrap(), + asset_not_in_pool.clone().try_into().unwrap(), + 1_000_000_000_000, + 2_000_000_000_000, + 0, + 0, + test_account + )); + let execution_fees = + Runtime::query_weight_to_asset_fee(xcm_weight.unwrap(), asset_not_in_pool.into()); + // Now it works! + assert_ok!(execution_fees); + }); +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 4af8a9f43850..3eb06e3a18c1 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -122,6 +122,7 @@ bridge-hub-test-utils = { workspace = true, default-features = true } bridge-runtime-common = { features = ["integrity-test"], workspace = true, default-features = true } pallet-bridge-relayers = { features = ["integrity-test"], workspace = true } snowbridge-runtime-test-common = { workspace = true, default-features = true } +parachains-runtimes-test-utils = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 3f3316d0be49..598afeddb984 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -847,7 +847,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::TokenLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index 6ca858e961d3..29f9615bff6a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -20,9 +20,9 @@ use bp_polkadot_core::Signature; use bridge_hub_rococo_runtime::{ bridge_common_config, bridge_to_bulletin_config, bridge_to_westend_config, xcm_config::{RelayNetwork, TokenLocation, XcmConfig}, - AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, ExistentialDeposit, - ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, - TransactionPayment, TxExtension, UncheckedExtrinsic, + AllPalletsWithoutSystem, Block, BridgeRejectObsoleteHeadersAndMessages, Executive, + ExistentialDeposit, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, + RuntimeOrigin, SessionKeys, TransactionPayment, TxExtension, UncheckedExtrinsic, }; use bridge_hub_test_utils::SlotDurations; use codec::{Decode, Encode}; @@ -838,3 +838,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 637e7c710640..871bf44ec5b2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -121,6 +121,7 @@ bridge-hub-test-utils = { workspace = true, default-features = true } bridge-runtime-common = { features = ["integrity-test"], workspace = true, default-features = true } pallet-bridge-relayers = { features = ["integrity-test"], workspace = true } snowbridge-runtime-test-common = { workspace = true, default-features = true } +parachains-runtimes-test-utils = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 65e7d291dc37..ae3dbfa06cba 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -779,7 +779,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::WestendLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index 84025c4cefeb..d7e70ed769b1 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -27,9 +27,9 @@ use bridge_hub_westend_runtime::{ bridge_common_config, bridge_to_rococo_config, bridge_to_rococo_config::RococoGlobalConsensusNetwork, xcm_config::{LocationToAccountId, RelayNetwork, WestendLocation, XcmConfig}, - AllPalletsWithoutSystem, BridgeRejectObsoleteHeadersAndMessages, Executive, ExistentialDeposit, - ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, - TransactionPayment, TxExtension, UncheckedExtrinsic, + AllPalletsWithoutSystem, Block, BridgeRejectObsoleteHeadersAndMessages, Executive, + ExistentialDeposit, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, + RuntimeOrigin, SessionKeys, TransactionPayment, TxExtension, UncheckedExtrinsic, }; use bridge_to_rococo_config::{ BridgeGrandpaRococoInstance, BridgeHubRococoLocation, BridgeParachainRococoInstance, @@ -525,3 +525,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index e03fc934ceaf..810abcf572d4 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -94,6 +94,7 @@ substrate-wasm-builder = { optional = true, workspace = true, default-features = [dev-dependencies] sp-io = { features = ["std"], workspace = true, default-features = true } +parachains-runtimes-test-utils = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 0ee3a4068718..f4c62f212e8c 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -963,7 +963,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::WndLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs index 7add10559d84..c9191eba49f6 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/tests/tests.rs @@ -16,7 +16,9 @@ #![cfg(test)] -use collectives_westend_runtime::xcm_config::LocationToAccountId; +use collectives_westend_runtime::{ + xcm_config::LocationToAccountId, Block, Runtime, RuntimeCall, RuntimeOrigin, +}; use parachains_common::AccountId; use sp_core::crypto::Ss58Codec; use xcm::latest::prelude::*; @@ -132,3 +134,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index a38b7400cfa3..02807827cf92 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -80,6 +80,9 @@ parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true } +[dev-dependencies] +parachains-runtimes-test-utils = { workspace = true } + [features] default = ["std"] std = [ @@ -120,6 +123,7 @@ std = [ "pallet-xcm/std", "parachain-info/std", "parachains-common/std", + "parachains-runtimes-test-utils/std", "polkadot-parachain-primitives/std", "polkadot-runtime-common/std", "rococo-runtime-constants/std", diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index 3f3126b749d8..ae3ad93a9e85 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -835,7 +835,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::RocRelayLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs index 2cabce567b6e..89a593ab0f57 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/tests/tests.rs @@ -16,7 +16,9 @@ #![cfg(test)] -use coretime_rococo_runtime::xcm_config::LocationToAccountId; +use coretime_rococo_runtime::{ + xcm_config::LocationToAccountId, Block, Runtime, RuntimeCall, RuntimeOrigin, +}; use parachains_common::AccountId; use sp_core::crypto::Ss58Codec; use xcm::latest::prelude::*; @@ -132,3 +134,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index 149fa5d0b045..34353d312b1f 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -80,6 +80,9 @@ parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["westend"], workspace = true } +[dev-dependencies] +parachains-runtimes-test-utils = { workspace = true, default-features = true } + [features] default = ["std"] std = [ diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 098a17cc9984..39ea39f25a8b 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -827,7 +827,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::TokenRelayLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs index e391d71a9ab7..976ce23d6e87 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/tests/tests.rs @@ -16,7 +16,9 @@ #![cfg(test)] -use coretime_westend_runtime::xcm_config::LocationToAccountId; +use coretime_westend_runtime::{ + xcm_config::LocationToAccountId, Block, Runtime, RuntimeCall, RuntimeOrigin, +}; use parachains_common::AccountId; use sp_core::crypto::Ss58Codec; use xcm::latest::prelude::*; @@ -132,3 +134,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index 34458c2352fb..a55143b62071 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -77,6 +77,9 @@ parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true } +[dev-dependencies] +parachains-runtimes-test-utils = { workspace = true, default-features = true } + [features] default = ["std"] std = [ diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index 7921030f2bb8..dc5f2ac0997c 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -783,7 +783,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::RelayLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs b/cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs index 3627d9c40ec2..00fe7781822a 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/tests/tests.rs @@ -17,7 +17,9 @@ #![cfg(test)] use parachains_common::AccountId; -use people_rococo_runtime::xcm_config::LocationToAccountId; +use people_rococo_runtime::{ + xcm_config::LocationToAccountId, Block, Runtime, RuntimeCall, RuntimeOrigin, +}; use sp_core::crypto::Ss58Codec; use xcm::latest::prelude::*; use xcm_runtime_apis::conversions::LocationToAccountHelper; @@ -132,3 +134,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index 6840b97d8c3f..4d66332e96dd 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -77,6 +77,9 @@ parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["westend"], workspace = true } +[dev-dependencies] +parachains-runtimes-test-utils = { workspace = true, default-features = true } + [features] default = ["std"] std = [ diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 19a64ab8d6e8..1b9a3b60a2c4 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -781,7 +781,8 @@ impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::RelayLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/cumulus/parachains/runtimes/people/people-westend/tests/tests.rs b/cumulus/parachains/runtimes/people/people-westend/tests/tests.rs index fa9331952b4b..5cefec44b1cd 100644 --- a/cumulus/parachains/runtimes/people/people-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/people/people-westend/tests/tests.rs @@ -17,7 +17,9 @@ #![cfg(test)] use parachains_common::AccountId; -use people_westend_runtime::xcm_config::LocationToAccountId; +use people_westend_runtime::{ + xcm_config::LocationToAccountId, Block, Runtime, RuntimeCall, RuntimeOrigin, +}; use sp_core::crypto::Ss58Codec; use xcm::latest::prelude::*; use xcm_runtime_apis::conversions::LocationToAccountHelper; @@ -132,3 +134,13 @@ fn location_conversion_works() { assert_eq!(got, expected, "{}", tc.description); } } + +#[test] +fn xcm_payment_api_works() { + parachains_runtimes_test_utils::test_cases::xcm_payment_api_with_native_token_works::< + Runtime, + RuntimeCall, + RuntimeOrigin, + Block, + >(); +} diff --git a/cumulus/parachains/runtimes/test-utils/Cargo.toml b/cumulus/parachains/runtimes/test-utils/Cargo.toml index 01d7fcc2b5c8..e9d666617ee2 100644 --- a/cumulus/parachains/runtimes/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/test-utils/Cargo.toml @@ -29,6 +29,7 @@ cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } +parachains-common = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-parachain-inherent = { workspace = true } cumulus-test-relay-sproof-builder = { workspace = true } @@ -37,6 +38,7 @@ cumulus-test-relay-sproof-builder = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } pallet-xcm = { workspace = true } +xcm-runtime-apis = { workspace = true } polkadot-parachain-primitives = { workspace = true } [dev-dependencies] @@ -62,11 +64,13 @@ std = [ "pallet-timestamp/std", "pallet-xcm/std", "parachain-info/std", + "parachains-common/std", "polkadot-parachain-primitives/std", "sp-consensus-aura/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "xcm-executor/std", + "xcm-runtime-apis/std", "xcm/std", ] diff --git a/cumulus/parachains/runtimes/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/test-utils/src/test_cases.rs index a66163154cf6..6bdf3ef09d1b 100644 --- a/cumulus/parachains/runtimes/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/test-utils/src/test_cases.rs @@ -18,7 +18,15 @@ use crate::{AccountIdOf, CollatorSessionKeys, ExtBuilder, ValidatorIdOf}; use codec::Encode; -use frame_support::{assert_ok, traits::Get}; +use frame_support::{ + assert_ok, + traits::{Get, OriginTrait}, +}; +use parachains_common::AccountId; +use sp_runtime::traits::{Block as BlockT, StaticLookup}; +use xcm_runtime_apis::fees::{ + runtime_decl_for_xcm_payment_api::XcmPaymentApiV1, Error as XcmPaymentApiError, +}; type RuntimeHelper = crate::RuntimeHelper; @@ -128,3 +136,60 @@ pub fn set_storage_keys_by_governance_works( assert_storage(); }); } + +pub fn xcm_payment_api_with_native_token_works() +where + Runtime: XcmPaymentApiV1 + + frame_system::Config + + pallet_balances::Config + + pallet_session::Config + + pallet_xcm::Config + + parachain_info::Config + + pallet_collator_selection::Config + + cumulus_pallet_parachain_system::Config + + cumulus_pallet_xcmp_queue::Config + + pallet_timestamp::Config, + ValidatorIdOf: From>, + RuntimeOrigin: OriginTrait::AccountId>, + <::Lookup as StaticLookup>::Source: + From<::AccountId>, + Block: BlockT, +{ + use xcm::prelude::*; + ExtBuilder::::default().build().execute_with(|| { + let transfer_amount = 100u128; + let xcm_to_weigh = Xcm::::builder_unsafe() + .withdraw_asset((Here, transfer_amount)) + .buy_execution((Here, transfer_amount), Unlimited) + .deposit_asset(AllCounted(1), [1u8; 32]) + .build(); + let versioned_xcm_to_weigh = VersionedXcm::from(xcm_to_weigh.clone().into()); + + // We first try calling it with a lower XCM version. + let lower_version_xcm_to_weigh = + versioned_xcm_to_weigh.clone().into_version(XCM_VERSION - 1).unwrap(); + let xcm_weight = Runtime::query_xcm_weight(lower_version_xcm_to_weigh); + assert!(xcm_weight.is_ok()); + let native_token: Location = Parent.into(); + let native_token_versioned = VersionedAssetId::from(AssetId(native_token)); + let lower_version_native_token = + native_token_versioned.clone().into_version(XCM_VERSION - 1).unwrap(); + let execution_fees = + Runtime::query_weight_to_asset_fee(xcm_weight.unwrap(), lower_version_native_token); + assert!(execution_fees.is_ok()); + + // Now we call it with the latest version. + let xcm_weight = Runtime::query_xcm_weight(versioned_xcm_to_weigh); + assert!(xcm_weight.is_ok()); + let execution_fees = + Runtime::query_weight_to_asset_fee(xcm_weight.unwrap(), native_token_versioned); + assert!(execution_fees.is_ok()); + + // If we call it with anything other than the native token it will error. + let non_existent_token: Location = Here.into(); + let non_existent_token_versioned = VersionedAssetId::from(AssetId(non_existent_token)); + let execution_fees = + Runtime::query_weight_to_asset_fee(xcm_weight.unwrap(), non_existent_token_versioned); + assert_eq!(execution_fees, Err(XcmPaymentApiError::AssetNotFound)); + }); +} diff --git a/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs b/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs index 2d14b4e571c6..c3046b134d1f 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/fee_estimation.rs @@ -353,3 +353,26 @@ fn dry_run_xcm() { ); }); } + +#[test] +fn calling_payment_api_with_a_lower_version_works() { + let transfer_amount = 100u128; + let xcm_to_weigh = Xcm::::builder_unsafe() + .withdraw_asset((Here, transfer_amount)) + .buy_execution((Here, transfer_amount), Unlimited) + .deposit_asset(AllCounted(1), [1u8; 32]) + .build(); + let versioned_xcm_to_weigh = VersionedXcm::from(xcm_to_weigh.clone().into()); + let lower_version_xcm_to_weigh = versioned_xcm_to_weigh.into_version(XCM_VERSION - 1).unwrap(); + let client = TestClient; + let runtime_api = client.runtime_api(); + let xcm_weight = + runtime_api.query_xcm_weight(H256::zero(), lower_version_xcm_to_weigh).unwrap(); + assert!(xcm_weight.is_ok()); + let native_token = VersionedAssetId::from(AssetId(Here.into())); + let lower_version_native_token = native_token.into_version(XCM_VERSION - 1).unwrap(); + let execution_fees = runtime_api + .query_weight_to_asset_fee(H256::zero(), xcm_weight.unwrap(), lower_version_native_token) + .unwrap(); + assert!(execution_fees.is_ok()); +} diff --git a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs index f0a5be908f69..fb5d1ae7c0e5 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs @@ -453,7 +453,8 @@ sp_api::mock_impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == HereLocation::get() => { Ok(WeightToFee::weight_to_fee(&weight)) }, diff --git a/prdoc/pr_6459.prdoc b/prdoc/pr_6459.prdoc new file mode 100644 index 000000000000..592ba4c6b29d --- /dev/null +++ b/prdoc/pr_6459.prdoc @@ -0,0 +1,22 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix version conversion in XcmPaymentApi::query_weight_to_asset_fee. + +doc: + - audience: Runtime Dev + description: | + The `query_weight_to_asset_fee` function of the `XcmPaymentApi` was trying + to convert versions in the wrong way. + This resulted in all calls made with lower versions failing. + The version conversion is now done correctly and these same calls will now succeed. + +crates: + - name: asset-hub-westend-runtime + bump: patch + - name: asset-hub-rococo-runtime + bump: patch + - name: xcm-runtime-apis + bump: patch + - name: assets-common + bump: patch From 89746964578648edd2e9eebea83ad39b065d4d01 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 27 Nov 2024 09:26:34 +0200 Subject: [PATCH 046/340] revert sendcontroller --- .../pallets/inbound-queue-v2/src/lib.rs | 45 +++++++++++-------- .../pallets/inbound-queue-v2/src/mock.rs | 42 ++++++++--------- .../src/tests/snowbridge_v2.rs | 9 +--- .../src/bridge_to_ethereum_config.rs | 4 +- .../bridge-hub-westend/src/xcm_config.rs | 2 +- 5 files changed, 53 insertions(+), 49 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index f4c6d99276bc..53cc9cc4f8ef 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -39,7 +39,6 @@ mod mock; #[cfg(test)] mod test; -use alloc::boxed::Box; use codec::{Decode, DecodeAll, Encode}; use envelope::Envelope; use frame_support::{ @@ -48,11 +47,6 @@ use frame_support::{ }; use frame_system::{ensure_signed, pallet_prelude::*}; use scale_info::TypeInfo; -use sp_core::H160; -use sp_std::vec; -use types::Nonce; -use xcm::prelude::{Junction::*, Location, *}; - use snowbridge_core::{ fees::burn_fees, inbound::{Message, VerificationError, Verifier}, @@ -62,9 +56,11 @@ use snowbridge_core::{ use snowbridge_router_primitives::inbound::v2::{ ConvertMessage, ConvertMessageError, Message as MessageV2, }; +use sp_core::H160; +use sp_std::vec; +use types::Nonce; pub use weights::WeightInfo; -use xcm::{VersionedLocation, VersionedXcm}; -use xcm_builder::SendController; +use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, *}; use xcm_executor::traits::TransactAsset; #[cfg(feature = "runtime-benchmarks")] @@ -97,7 +93,7 @@ pub mod pallet { /// The verifier for inbound messages from Ethereum. type Verifier: Verifier; /// XCM message sender. - type XcmSender: SendController<::RuntimeOrigin>; + type XcmSender: SendXcm; /// Address of the Gateway contract. #[pallet::constant] type GatewayAddress: Get; @@ -174,6 +170,22 @@ pub mod pallet { Fees, } + impl From for Error { + fn from(e: XcmpSendError) -> Self { + match e { + XcmpSendError::NotApplicable => Error::::Send(SendError::NotApplicable), + XcmpSendError::Unroutable => Error::::Send(SendError::NotRoutable), + XcmpSendError::Transport(_) => Error::::Send(SendError::Transport), + XcmpSendError::DestinationUnsupported => + Error::::Send(SendError::DestinationUnsupported), + XcmpSendError::ExceedsMaxMessageSize => + Error::::Send(SendError::ExceedsMaxMessageSize), + XcmpSendError::MissingArgument => Error::::Send(SendError::MissingArgument), + XcmpSendError::Fees => Error::::Send(SendError::Fees), + } + } + } + /// The nonce of the message been processed or not #[pallet::storage] pub type NonceBitmap = StorageMap<_, Twox64Concat, u128, u128, ValueQuery>; @@ -230,7 +242,7 @@ pub mod pallet { // Attempt to forward XCM to AH - let message_id = Self::send_xcm(origin, xcm, T::AssetHubParaId::get())?; + let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); // Set nonce flag to true @@ -260,15 +272,10 @@ pub mod pallet { Ok(Location::new(0, [AccountId32 { network: None, id: account_bytes }])) } - pub fn send_xcm( - origin: OriginFor, - xcm: Xcm<()>, - dest_para_id: u32, - ) -> Result { - let versioned_dest = - Box::new(VersionedLocation::V5(Location::new(1, [Parachain(dest_para_id)]))); - let versioned_xcm = Box::new(VersionedXcm::V5(xcm)); - Ok(T::XcmSender::send(origin, versioned_dest, versioned_xcm)?) + pub fn send_xcm(xcm: Xcm<()>, dest_para_id: u32) -> Result> { + let dest = Location::new(1, [Parachain(dest_para_id)]); + let (message_id, _) = send_xcm::(dest, xcm).map_err(Error::::from)?; + Ok(message_id) } pub fn do_convert( diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 105863f5772f..e04ff64a37a4 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -16,14 +16,13 @@ use snowbridge_core::{ TokenId, }; use snowbridge_router_primitives::inbound::v2::MessageToXcm; -use sp_core::{H160, H256}; +use sp_core::H160; use sp_runtime::{ traits::{IdentifyAccount, IdentityLookup, MaybeEquivalence, Verify}, - BuildStorage, DispatchError, MultiSignature, + BuildStorage, MultiSignature, }; use sp_std::{convert::From, default::Default}; -use xcm::prelude::*; -use xcm_builder::SendControllerWeightInfo; +use xcm::{latest::SendXcm, prelude::*}; use xcm_executor::{traits::TransactAsset, AssetsInHolding}; type Block = frame_system::mocking::MockBlock; @@ -111,25 +110,28 @@ impl BenchmarkHelper for Test { fn initialize_storage(_: BeaconHeader, _: H256) {} } -pub struct MockXcmSenderWeights; - -impl SendControllerWeightInfo for MockXcmSenderWeights { - fn send() -> Weight { - return Weight::default(); - } -} - // Mock XCM sender that always succeeds pub struct MockXcmSender; +impl SendXcm for MockXcmSender { + type Ticket = Xcm<()>; + + fn validate( + dest: &mut Option, + xcm: &mut Option>, + ) -> SendResult { + if let Some(location) = dest { + match location.unpack() { + (_, [Parachain(1001)]) => return Err(XcmpSendError::NotApplicable), + _ => Ok((xcm.clone().unwrap(), Assets::default())), + } + } else { + Ok((xcm.clone().unwrap(), Assets::default())) + } + } -impl SendController for MockXcmSender { - type WeightInfo = MockXcmSenderWeights; - fn send( - _origin: mock::RuntimeOrigin, - _dest: Box, - _message: Box>, - ) -> Result { - Ok(H256::random().into()) + fn deliver(xcm: Self::Ticket) -> core::result::Result { + let hash = xcm.using_encoded(sp_io::hashing::blake2_256); + Ok(hash) } } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 8e5cbf8ff4d6..59f0b9b107ff 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::imports::*; -use bridge_hub_westend_runtime::{EthereumInboundQueueV2, RuntimeOrigin}; +use bridge_hub_westend_runtime::EthereumInboundQueueV2; use frame_support::weights::WeightToFee; use snowbridge_router_primitives::inbound::v2::{Asset::NativeTokenERC20, Message}; use sp_core::H160; @@ -96,12 +96,7 @@ fn xcm_prologue_fee() { claimer: Some(claimer_bytes), }; let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); - let _ = EthereumInboundQueueV2::send_xcm( - RuntimeOrigin::signed(relayer.clone()), - xcm, - AssetHubWestend::para_id().into(), - ) - .unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( BridgeHubWestend, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 624127823518..a9e3e48e4f50 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -21,7 +21,7 @@ use crate::{ RuntimeEvent, TransactionByteFee, }; #[cfg(not(feature = "runtime-benchmarks"))] -use crate::{PolkadotXcm, XcmRouter}; +use crate::XcmRouter; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; @@ -111,7 +111,7 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Verifier = snowbridge_pallet_ethereum_client::Pallet; #[cfg(not(feature = "runtime-benchmarks"))] - type XcmSender = PolkadotXcm; + type XcmSender = XcmRouter; #[cfg(feature = "runtime-benchmarks")] type XcmSender = DoNothingRouter; type GatewayAddress = EthereumGatewayAddress; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs index 38d9bec0c0f8..befb63ef9709 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs @@ -250,7 +250,7 @@ impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; type XcmRouter = XcmRouter; // We want to disallow users sending (arbitrary) XCMs from this chain. - type SendXcmOrigin = EnsureXcmOrigin; + type SendXcmOrigin = EnsureXcmOrigin; // We support local origins dispatching XCM executions. type ExecuteXcmOrigin = EnsureXcmOrigin; type XcmExecuteFilter = Everything; From 445c1c80d438d5b937c347b04e4daa66ad25d878 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 27 Nov 2024 10:28:26 +0100 Subject: [PATCH 047/340] Add stable2412 to target_branches for command-backport.yml (#6666) The backport bot opens PR for `A4-needs-backport` only for stable2407 stable2409, but we have already stable2412. The question is, when should we append a new `stable*` branch here? Should it be done when a new `stable*` branch is created? Can we automate this process somehow? --- .github/workflows/command-backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/command-backport.yml b/.github/workflows/command-backport.yml index 8f23bcd75f01..eecf0ac72d2c 100644 --- a/.github/workflows/command-backport.yml +++ b/.github/workflows/command-backport.yml @@ -40,7 +40,7 @@ jobs: uses: korthout/backport-action@v3 id: backport with: - target_branches: stable2407 stable2409 + target_branches: stable2407 stable2409 stable2412 merge_commits: skip github_token: ${{ steps.generate_token.outputs.token }} pull_description: | From 75d3c064dd781fbef03cb5c24adcde299028b9ce Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 27 Nov 2024 11:44:21 +0200 Subject: [PATCH 048/340] adds test for xcm register command --- Cargo.lock | 1 + .../bridges/bridge-hub-westend/Cargo.toml | 1 + .../src/tests/snowbridge_v2.rs | 60 +++++++++++++++++++ 3 files changed, 62 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ef3f6cc673ff..f7352d583e40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2731,6 +2731,7 @@ dependencies = [ "cumulus-pallet-xcmp-queue 0.7.0", "emulated-integration-tests-common", "frame-support 28.0.0", + "hex", "hex-literal", "log", "pallet-asset-conversion 10.0.0", diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index b87f25ac0f01..fc3cbc835b04 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -12,6 +12,7 @@ workspace = true [dependencies] hex-literal = { workspace = true, default-features = true } +hex = { workspace = true, default-features = true } codec = { workspace = true } log = { workspace = true } scale-info = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 59f0b9b107ff..d194e1948bba 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -17,6 +17,11 @@ use bridge_hub_westend_runtime::EthereumInboundQueueV2; use frame_support::weights::WeightToFee; use snowbridge_router_primitives::inbound::v2::{Asset::NativeTokenERC20, Message}; use sp_core::H160; +use sp_core::H256; +use codec::Encode; +use sp_runtime::MultiAddress; +use bridge_hub_westend_runtime::bridge_to_ethereum_config::CreateAssetCall; +use bridge_hub_westend_runtime::bridge_to_ethereum_config::CreateAssetDeposit; use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. @@ -108,3 +113,58 @@ fn xcm_prologue_fee() { let buffered_fee = execution_fee * 2; println!("buffered execution fee for prologue for 8 assets: {}", buffered_fee); } + +#[test] +fn register_token_xcm() { + BridgeHubWestend::execute_with(|| { + let weth_contract = H160::zero(); // weth contract placeholder + let token: H160 = H160::zero(); // token id placeholder + let owner: H256 = H256::zero(); // bridge placeholder + let dot_to_eth_rate: u128 = 2_500_000_000_000_000; + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let weth_asset = Location::new( + 2, + [ + GlobalConsensus(ethereum_network_v5), + AccountKey20 { network: None, key: weth_contract.into() }, + ], + ); + let dot_asset = Location::new(1, Here); + + let weth_asset_creation: u128 = (CreateAssetDeposit::get() * dot_to_eth_rate) / 10_u128.pow(18); + let weth_fee: xcm::prelude::Asset = (weth_asset, weth_asset_creation).into(); + let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + + println!("weth_asset_creation: {:?}", weth_asset_creation); + + let asset_id = Location::new( + 2, + [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], + ); + + let register_token_xcm = + vec![ + ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, + PayFees { asset: dot_fee }, + Transact { + origin_kind: OriginKind::Xcm, + call: ( + CreateAssetCall::get(), + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner.into()), + 1, + ) + .encode() + .into(), + }, + ]; + let message_xcm: Xcm<()> = register_token_xcm.into(); + let versioned_message_xcm = VersionedXcm::V5(message_xcm); + + let xcm_bytes = versioned_message_xcm.encode(); + let hex_string = hex::encode(xcm_bytes); + println!("register token hex: {:x?}", hex_string); + }); +} + From c666d235fde1832dbb7047a2e8e32196fe241985 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 27 Nov 2024 12:33:56 +0200 Subject: [PATCH 049/340] update test --- .../src/tests/snowbridge_v2.rs | 23 +++++-------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index d194e1948bba..b54cf0d3a753 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -117,27 +117,17 @@ fn xcm_prologue_fee() { #[test] fn register_token_xcm() { BridgeHubWestend::execute_with(|| { - let weth_contract = H160::zero(); // weth contract placeholder - let token: H160 = H160::zero(); // token id placeholder - let owner: H256 = H256::zero(); // bridge placeholder - let dot_to_eth_rate: u128 = 2_500_000_000_000_000; + //let token: H160 = H160::zero(); // token id placeholder + let token: H160 = H160::random(); // token id placeholder + //let owner: H256 = H256::zero(); // bridge owner placeholder + let owner: H256 = H256::random(); // bridge owner placeholder + println!("token: {:x?}", token); + println!("owner: {:x?}", owner); let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let weth_asset = Location::new( - 2, - [ - GlobalConsensus(ethereum_network_v5), - AccountKey20 { network: None, key: weth_contract.into() }, - ], - ); let dot_asset = Location::new(1, Here); - - let weth_asset_creation: u128 = (CreateAssetDeposit::get() * dot_to_eth_rate) / 10_u128.pow(18); - let weth_fee: xcm::prelude::Asset = (weth_asset, weth_asset_creation).into(); let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); - println!("weth_asset_creation: {:?}", weth_asset_creation); - let asset_id = Location::new( 2, [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], @@ -145,7 +135,6 @@ fn register_token_xcm() { let register_token_xcm = vec![ - ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, PayFees { asset: dot_fee }, Transact { origin_kind: OriginKind::Xcm, From 2a0b2680b8df9c18d2543f82629917f759827e1c Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 27 Nov 2024 13:11:01 +0200 Subject: [PATCH 050/340] litep2p/req-resp: Always provide main protocol name in responses (#6603) Request responses are initialized with a main protocol name, and optional protocol names as a fallback. Running litep2p in kusama as a validator has surfaced a `debug_asserts` coming from the sync component: https://github.com/paritytech/polkadot-sdk/blob/3906c578c96d97a8a099a4bdac4685acbe375a7c/substrate/client/network/sync/src/strategy/chain_sync.rs#L640-L646 The issue is that we initiate a request-response over the main protocol name `/genesis/sync/2` but receive a response over the legacy procotol `ksm/sync/2`. This behavior is correct because litep2p propagates to the higher levels the protocol that responded. In contrast, libp2p provides the main protocol name regardless of negotiating a legacy protocol. Because of this, higher level components assume that only the main protocol name will respond. To not break this assumption, this PR alings litep2p shim layer with the libp2p behavior. Closes: https://github.com/paritytech/polkadot-sdk/issues/6581 --------- Signed-off-by: Alexandru Vasile Co-authored-by: Dmitry Markin --- prdoc/pr_6603.prdoc | 16 ++++++++++++++++ .../src/litep2p/shim/request_response/mod.rs | 7 ++----- 2 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 prdoc/pr_6603.prdoc diff --git a/prdoc/pr_6603.prdoc b/prdoc/pr_6603.prdoc new file mode 100644 index 000000000000..20c5e7294dfa --- /dev/null +++ b/prdoc/pr_6603.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Always provide main protocol name in litep2p responses + +doc: + - audience: [ Node Dev, Node Operator ] + description: | + This PR aligns litep2p behavior with libp2p. Previously, litep2p network backend + would provide the actual negotiated request-response protocol that produced a + response message. After this PR, only the main protocol name is reported to other + subsystems. + +crates: + - name: sc-network + bump: patch diff --git a/substrate/client/network/src/litep2p/shim/request_response/mod.rs b/substrate/client/network/src/litep2p/shim/request_response/mod.rs index bfd7a60ef9fe..146f2e4add97 100644 --- a/substrate/client/network/src/litep2p/shim/request_response/mod.rs +++ b/substrate/client/network/src/litep2p/shim/request_response/mod.rs @@ -320,7 +320,7 @@ impl RequestResponseProtocol { &mut self, peer: litep2p::PeerId, request_id: RequestId, - fallback: Option, + _fallback: Option, response: Vec, ) { match self.pending_inbound_responses.remove(&request_id) { @@ -337,10 +337,7 @@ impl RequestResponseProtocol { response.len(), ); - let _ = tx.send(Ok(( - response, - fallback.map_or_else(|| self.protocol.clone(), Into::into), - ))); + let _ = tx.send(Ok((response, self.protocol.clone()))); self.metrics.register_outbound_request_success(started.elapsed()); }, } From 5b1b34db0c03057c7962eb9682b4d581c324ae26 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:10:36 +0200 Subject: [PATCH 051/340] v16: Expose the unstable metadata v16 (#5732) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR exposes the *unstable* metadata V16. The metadata is exposed under the unstable u32::MAX number. Developers can start experimenting with the new features of the metadata v16. *Please note that this metadata is under development and expect breaking changes until stabilization.* The `ExtrinsicMetadata` trait receives a breaking change. Its associated type `VERSION` is rename to `VERSIONS` and now supports a constant static list of metadata versions. The versions implemented for `UncheckedExtrinsic` are v4 (legacy version) and v5 (new version). For metadata collection, it is assumed that all `TransactionExtensions` are under version 0. Builds on top of: https://github.com/paritytech/polkadot-sdk/pull/5274 Closes: https://github.com/paritytech/polkadot-sdk/issues/5980 Closes: https://github.com/paritytech/polkadot-sdk/issues/5347 Closes: https://github.com/paritytech/polkadot-sdk/issues/5285 cc @paritytech/subxt-team --------- Signed-off-by: Alexandru Vasile Co-authored-by: Niklas Adolfsson Co-authored-by: Bastian Köcher Co-authored-by: James Wilson Co-authored-by: GitHub Action --- Cargo.lock | 28 ++- Cargo.toml | 4 +- prdoc/pr_5732.prdoc | 29 +++ .../frame/metadata-hash-extension/Cargo.toml | 2 +- substrate/frame/revive/src/evm/runtime.rs | 8 +- substrate/frame/support/Cargo.toml | 1 + .../src/construct_runtime/expand/metadata.rs | 2 +- substrate/frame/support/test/Cargo.toml | 2 +- substrate/frame/support/test/tests/pallet.rs | 8 +- substrate/primitives/metadata-ir/Cargo.toml | 2 +- substrate/primitives/metadata-ir/src/lib.rs | 25 ++- substrate/primitives/metadata-ir/src/types.rs | 6 +- .../primitives/metadata-ir/src/unstable.rs | 211 ++++++++++++++++++ substrate/primitives/metadata-ir/src/v14.rs | 5 +- substrate/primitives/metadata-ir/src/v15.rs | 2 +- .../src/generic/unchecked_extrinsic.rs | 3 +- .../primitives/runtime/src/traits/mod.rs | 6 +- .../src/overhead/remark_builder.rs | 8 +- .../src/overhead/runtime_utilities.rs | 15 +- substrate/utils/wasm-builder/Cargo.toml | 2 +- 20 files changed, 329 insertions(+), 40 deletions(-) create mode 100644 prdoc/pr_5732.prdoc create mode 100644 substrate/primitives/metadata-ir/src/unstable.rs diff --git a/Cargo.lock b/Cargo.lock index 58b8b222cce4..2c938ec17bd0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7224,6 +7224,18 @@ dependencies = [ "serde", ] +[[package]] +name = "frame-metadata" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daaf440c68eb2c3d88e5760fe8c7af3f9fee9181fab6c2f2c4e7cc48dcc40bb8" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] + [[package]] name = "frame-metadata-hash-extension" version = "0.1.0" @@ -7231,7 +7243,7 @@ dependencies = [ "array-bytes", "const-hex", "docify", - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "frame-support 28.0.0", "frame-system 28.0.0", "log", @@ -7316,7 +7328,7 @@ dependencies = [ "bitflags 1.3.2", "docify", "environmental", - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "frame-support-procedural 23.0.0", "frame-system 28.0.0", "impl-trait-for-tuples", @@ -7494,7 +7506,7 @@ version = "3.0.0" dependencies = [ "frame-benchmarking 28.0.0", "frame-executive 28.0.0", - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "frame-support 28.0.0", "frame-support-test-pallet", "frame-system 28.0.0", @@ -10520,13 +10532,13 @@ dependencies = [ [[package]] name = "merkleized-metadata" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f313fcff1d2a4bcaa2deeaa00bf7530d77d5f7bd0467a117dde2e29a75a7a17a" +checksum = "943f6d92804ed0100803d51fa9b21fd9432b5d122ba4c713dc26fe6d2f619cf6" dependencies = [ "array-bytes", "blake3", - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "parity-scale-codec", "scale-decode 0.13.1", "scale-info", @@ -26664,7 +26676,7 @@ dependencies = [ name = "sp-metadata-ir" version = "0.6.0" dependencies = [ - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "parity-scale-codec", "scale-info", ] @@ -28601,7 +28613,7 @@ dependencies = [ "cargo_metadata", "console", "filetime", - "frame-metadata 16.0.0", + "frame-metadata 18.0.0", "jobserver", "merkleized-metadata", "parity-scale-codec", diff --git a/Cargo.toml b/Cargo.toml index 53f95406e79a..b1a52712e736 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -779,7 +779,7 @@ frame-benchmarking-pallet-pov = { default-features = false, path = "substrate/fr frame-election-provider-solution-type = { path = "substrate/frame/election-provider-support/solution-type", default-features = false } frame-election-provider-support = { path = "substrate/frame/election-provider-support", default-features = false } frame-executive = { path = "substrate/frame/executive", default-features = false } -frame-metadata = { version = "16.0.0", default-features = false } +frame-metadata = { version = "18.0.0", default-features = false } frame-metadata-hash-extension = { path = "substrate/frame/metadata-hash-extension", default-features = false } frame-support = { path = "substrate/frame/support", default-features = false } frame-support-procedural = { path = "substrate/frame/support/procedural", default-features = false } @@ -854,7 +854,7 @@ macro_magic = { version = "0.5.1" } maplit = { version = "1.0.2" } memmap2 = { version = "0.9.3" } memory-db = { version = "0.32.0", default-features = false } -merkleized-metadata = { version = "0.1.0" } +merkleized-metadata = { version = "0.1.2" } merlin = { version = "3.0", default-features = false } messages-relay = { path = "bridges/relays/messages" } metered = { version = "0.6.1", default-features = false, package = "prioritized-metered-channel" } diff --git a/prdoc/pr_5732.prdoc b/prdoc/pr_5732.prdoc new file mode 100644 index 000000000000..6f3f9b8a1668 --- /dev/null +++ b/prdoc/pr_5732.prdoc @@ -0,0 +1,29 @@ +title: Expose the unstable metadata v16 +doc: +- audience: Node Dev + description: | + This PR exposes the *unstable* metadata V16. The metadata is exposed under the unstable u32::MAX number. + Developers can start experimenting with the new features of the metadata v16. *Please note that this metadata is under development and expect breaking changes until stabilization.* + The `ExtrinsicMetadata` trait receives a breaking change. Its associated type `VERSION` is rename to `VERSIONS` and now supports a constant static list of metadata versions. + The versions implemented for `UncheckedExtrinsic` are v4 (legacy version) and v5 (new version). + For metadata collection, it is assumed that all `TransactionExtensions` are under version 0. + +crates: + - name: sp-metadata-ir + bump: major + - name: frame-support-procedural + bump: patch + - name: frame-support + bump: minor + - name: frame-support-test + bump: major + - name: frame-metadata-hash-extension + bump: patch + - name: substrate-wasm-builder + bump: minor + - name: pallet-revive + bump: minor + - name: sp-runtime + bump: major + - name: frame-benchmarking-cli + bump: patch diff --git a/substrate/frame/metadata-hash-extension/Cargo.toml b/substrate/frame/metadata-hash-extension/Cargo.toml index bca2c3ffb198..8f4ba922984c 100644 --- a/substrate/frame/metadata-hash-extension/Cargo.toml +++ b/substrate/frame/metadata-hash-extension/Cargo.toml @@ -25,7 +25,7 @@ substrate-test-runtime-client = { workspace = true } sp-api = { workspace = true, default-features = true } sp-transaction-pool = { workspace = true, default-features = true } merkleized-metadata = { workspace = true } -frame-metadata = { features = ["current"], workspace = true, default-features = true } +frame-metadata = { features = ["current", "unstable"], workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } [features] diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 40c210304ca2..b5dc9a36065b 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -92,8 +92,12 @@ impl ExtrinsicLike impl ExtrinsicMetadata for UncheckedExtrinsic { - const VERSION: u8 = - generic::UncheckedExtrinsic::, Signature, E::Extension>::VERSION; + const VERSIONS: &'static [u8] = generic::UncheckedExtrinsic::< + Address, + CallOf, + Signature, + E::Extension, + >::VERSIONS; type TransactionExtensions = E::Extension; } diff --git a/substrate/frame/support/Cargo.toml b/substrate/frame/support/Cargo.toml index d7da034b3492..d48c80510581 100644 --- a/substrate/frame/support/Cargo.toml +++ b/substrate/frame/support/Cargo.toml @@ -28,6 +28,7 @@ scale-info = { features = [ ], workspace = true } frame-metadata = { features = [ "current", + "unstable", ], workspace = true } sp-api = { features = [ "frame-metadata", diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs index c12fc20bc8b8..4590a3a7f490 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs @@ -117,7 +117,7 @@ pub fn expand_runtime_metadata( pallets: #scrate::__private::vec![ #(#pallets),* ], extrinsic: #scrate::__private::metadata_ir::ExtrinsicMetadataIR { ty, - version: <#extrinsic as #scrate::sp_runtime::traits::ExtrinsicMetadata>::VERSION, + versions: <#extrinsic as #scrate::sp_runtime::traits::ExtrinsicMetadata>::VERSIONS.into_iter().map(|ref_version| *ref_version).collect(), address_ty, call_ty, signature_ty, diff --git a/substrate/frame/support/test/Cargo.toml b/substrate/frame/support/test/Cargo.toml index 2187ee22b395..17ee3130b741 100644 --- a/substrate/frame/support/test/Cargo.toml +++ b/substrate/frame/support/test/Cargo.toml @@ -19,7 +19,7 @@ static_assertions = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true } codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } -frame-metadata = { features = ["current"], workspace = true } +frame-metadata = { features = ["current", "unstable"], workspace = true } sp-api = { workspace = true } sp-arithmetic = { workspace = true } sp-io = { workspace = true } diff --git a/substrate/frame/support/test/tests/pallet.rs b/substrate/frame/support/test/tests/pallet.rs index de7f7eb4bc97..9df1f461bba2 100644 --- a/substrate/frame/support/test/tests/pallet.rs +++ b/substrate/frame/support/test/tests/pallet.rs @@ -53,6 +53,9 @@ parameter_types! { /// Latest stable metadata version used for testing. const LATEST_METADATA_VERSION: u32 = 15; +/// Unstable metadata version. +const UNSTABLE_METADATA_VERSION: u32 = u32::MAX; + pub struct SomeType1; impl From for u64 { fn from(_t: SomeType1) -> Self { @@ -1977,7 +1980,10 @@ fn metadata_at_version() { #[test] fn metadata_versions() { - assert_eq!(vec![14, LATEST_METADATA_VERSION], Runtime::metadata_versions()); + assert_eq!( + vec![14, LATEST_METADATA_VERSION, UNSTABLE_METADATA_VERSION], + Runtime::metadata_versions() + ); } #[test] diff --git a/substrate/primitives/metadata-ir/Cargo.toml b/substrate/primitives/metadata-ir/Cargo.toml index d7786347dd02..046441104b88 100644 --- a/substrate/primitives/metadata-ir/Cargo.toml +++ b/substrate/primitives/metadata-ir/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame-metadata = { features = ["current"], workspace = true } +frame-metadata = { features = ["current", "unstable"], workspace = true } scale-info = { features = ["derive"], workspace = true } [features] diff --git a/substrate/primitives/metadata-ir/src/lib.rs b/substrate/primitives/metadata-ir/src/lib.rs index 4bd13b935afd..bf234432a1a6 100644 --- a/substrate/primitives/metadata-ir/src/lib.rs +++ b/substrate/primitives/metadata-ir/src/lib.rs @@ -30,6 +30,7 @@ mod types; use frame_metadata::RuntimeMetadataPrefixed; pub use types::*; +mod unstable; mod v14; mod v15; @@ -39,23 +40,33 @@ const V14: u32 = 14; /// Metadata V15. const V15: u32 = 15; +/// Unstable metadata V16. +const UNSTABLE_V16: u32 = u32::MAX; + /// Transform the IR to the specified version. /// /// Use [`supported_versions`] to find supported versions. pub fn into_version(metadata: MetadataIR, version: u32) -> Option { // Note: Unstable metadata version is `u32::MAX` until stabilized. match version { - // Latest stable version. + // Version V14. This needs to be around until the + // deprecation of the `Metadata_metadata` runtime call in favor of + // `Metadata_metadata_at_version. V14 => Some(into_v14(metadata)), - // Unstable metadata. + + // Version V15 - latest stable. V15 => Some(into_latest(metadata)), + + // Unstable metadata under `u32::MAX`. + UNSTABLE_V16 => Some(into_unstable(metadata)), + _ => None, } } /// Returns the supported metadata versions. pub fn supported_versions() -> alloc::vec::Vec { - alloc::vec![V14, V15] + alloc::vec![V14, V15, UNSTABLE_V16] } /// Transform the IR to the latest stable metadata version. @@ -70,6 +81,12 @@ pub fn into_v14(metadata: MetadataIR) -> RuntimeMetadataPrefixed { latest.into() } +/// Transform the IR to unstable metadata version 16. +pub fn into_unstable(metadata: MetadataIR) -> RuntimeMetadataPrefixed { + let latest: frame_metadata::v16::RuntimeMetadataV16 = metadata.into(); + latest.into() +} + #[cfg(test)] mod test { use super::*; @@ -81,7 +98,7 @@ mod test { pallets: vec![], extrinsic: ExtrinsicMetadataIR { ty: meta_type::<()>(), - version: 0, + versions: vec![0], address_ty: meta_type::<()>(), call_ty: meta_type::<()>(), signature_ty: meta_type::<()>(), diff --git a/substrate/primitives/metadata-ir/src/types.rs b/substrate/primitives/metadata-ir/src/types.rs index 199b692fbd8c..af217ffe16ee 100644 --- a/substrate/primitives/metadata-ir/src/types.rs +++ b/substrate/primitives/metadata-ir/src/types.rs @@ -170,8 +170,8 @@ pub struct ExtrinsicMetadataIR { /// /// Note: Field used for metadata V14 only. pub ty: T::Type, - /// Extrinsic version. - pub version: u8, + /// Extrinsic versions. + pub versions: Vec, /// The type of the address that signs the extrinsic pub address_ty: T::Type, /// The type of the outermost Call enum. @@ -191,7 +191,7 @@ impl IntoPortable for ExtrinsicMetadataIR { fn into_portable(self, registry: &mut Registry) -> Self::Output { ExtrinsicMetadataIR { ty: registry.register_type(&self.ty), - version: self.version, + versions: self.versions, address_ty: registry.register_type(&self.address_ty), call_ty: registry.register_type(&self.call_ty), signature_ty: registry.register_type(&self.signature_ty), diff --git a/substrate/primitives/metadata-ir/src/unstable.rs b/substrate/primitives/metadata-ir/src/unstable.rs new file mode 100644 index 000000000000..d46ce3ec6a7d --- /dev/null +++ b/substrate/primitives/metadata-ir/src/unstable.rs @@ -0,0 +1,211 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Convert the IR to V16 metadata. + +use crate::{ + DeprecationInfoIR, DeprecationStatusIR, OuterEnumsIR, PalletAssociatedTypeMetadataIR, + PalletCallMetadataIR, PalletConstantMetadataIR, PalletErrorMetadataIR, PalletEventMetadataIR, + PalletStorageMetadataIR, StorageEntryMetadataIR, +}; + +use super::types::{ + ExtrinsicMetadataIR, MetadataIR, PalletMetadataIR, RuntimeApiMetadataIR, + RuntimeApiMethodMetadataIR, RuntimeApiMethodParamMetadataIR, TransactionExtensionMetadataIR, +}; + +use frame_metadata::v16::{ + CustomMetadata, DeprecationInfo, DeprecationStatus, ExtrinsicMetadata, OuterEnums, + PalletAssociatedTypeMetadata, PalletCallMetadata, PalletConstantMetadata, PalletErrorMetadata, + PalletEventMetadata, PalletMetadata, PalletStorageMetadata, RuntimeApiMetadata, + RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, RuntimeMetadataV16, + StorageEntryMetadata, TransactionExtensionMetadata, +}; + +impl From for RuntimeMetadataV16 { + fn from(ir: MetadataIR) -> Self { + RuntimeMetadataV16::new( + ir.pallets.into_iter().map(Into::into).collect(), + ir.extrinsic.into(), + ir.apis.into_iter().map(Into::into).collect(), + ir.outer_enums.into(), + // Substrate does not collect yet the custom metadata fields. + // This allows us to extend the V16 easily. + CustomMetadata { map: Default::default() }, + ) + } +} + +impl From for RuntimeApiMetadata { + fn from(ir: RuntimeApiMetadataIR) -> Self { + RuntimeApiMetadata { + name: ir.name, + methods: ir.methods.into_iter().map(Into::into).collect(), + docs: ir.docs, + deprecation_info: ir.deprecation_info.into(), + } + } +} + +impl From for RuntimeApiMethodMetadata { + fn from(ir: RuntimeApiMethodMetadataIR) -> Self { + RuntimeApiMethodMetadata { + name: ir.name, + inputs: ir.inputs.into_iter().map(Into::into).collect(), + output: ir.output, + docs: ir.docs, + deprecation_info: ir.deprecation_info.into(), + } + } +} + +impl From for RuntimeApiMethodParamMetadata { + fn from(ir: RuntimeApiMethodParamMetadataIR) -> Self { + RuntimeApiMethodParamMetadata { name: ir.name, ty: ir.ty } + } +} + +impl From for PalletMetadata { + fn from(ir: PalletMetadataIR) -> Self { + PalletMetadata { + name: ir.name, + storage: ir.storage.map(Into::into), + calls: ir.calls.map(Into::into), + event: ir.event.map(Into::into), + constants: ir.constants.into_iter().map(Into::into).collect(), + error: ir.error.map(Into::into), + index: ir.index, + docs: ir.docs, + associated_types: ir.associated_types.into_iter().map(Into::into).collect(), + deprecation_info: ir.deprecation_info.into(), + } + } +} + +impl From for PalletStorageMetadata { + fn from(ir: PalletStorageMetadataIR) -> Self { + PalletStorageMetadata { + prefix: ir.prefix, + entries: ir.entries.into_iter().map(Into::into).collect(), + } + } +} + +impl From for StorageEntryMetadata { + fn from(ir: StorageEntryMetadataIR) -> Self { + StorageEntryMetadata { + name: ir.name, + modifier: ir.modifier.into(), + ty: ir.ty.into(), + default: ir.default, + docs: ir.docs, + deprecation_info: ir.deprecation_info.into(), + } + } +} + +impl From for PalletAssociatedTypeMetadata { + fn from(ir: PalletAssociatedTypeMetadataIR) -> Self { + PalletAssociatedTypeMetadata { name: ir.name, ty: ir.ty, docs: ir.docs } + } +} + +impl From for PalletErrorMetadata { + fn from(ir: PalletErrorMetadataIR) -> Self { + PalletErrorMetadata { ty: ir.ty, deprecation_info: ir.deprecation_info.into() } + } +} + +impl From for PalletEventMetadata { + fn from(ir: PalletEventMetadataIR) -> Self { + PalletEventMetadata { ty: ir.ty, deprecation_info: ir.deprecation_info.into() } + } +} + +impl From for PalletCallMetadata { + fn from(ir: PalletCallMetadataIR) -> Self { + PalletCallMetadata { ty: ir.ty, deprecation_info: ir.deprecation_info.into() } + } +} + +impl From for PalletConstantMetadata { + fn from(ir: PalletConstantMetadataIR) -> Self { + PalletConstantMetadata { + name: ir.name, + ty: ir.ty, + value: ir.value, + docs: ir.docs, + deprecation_info: ir.deprecation_info.into(), + } + } +} + +impl From for TransactionExtensionMetadata { + fn from(ir: TransactionExtensionMetadataIR) -> Self { + TransactionExtensionMetadata { identifier: ir.identifier, ty: ir.ty, implicit: ir.implicit } + } +} + +impl From for ExtrinsicMetadata { + fn from(ir: ExtrinsicMetadataIR) -> Self { + // Assume version 0 for all extensions. + let indexes = (0..ir.extensions.len()).map(|index| index as u32).collect(); + let transaction_extensions_by_version = [(0, indexes)].iter().cloned().collect(); + + ExtrinsicMetadata { + versions: ir.versions, + address_ty: ir.address_ty, + signature_ty: ir.signature_ty, + transaction_extensions_by_version, + transaction_extensions: ir.extensions.into_iter().map(Into::into).collect(), + } + } +} + +impl From for OuterEnums { + fn from(ir: OuterEnumsIR) -> Self { + OuterEnums { + call_enum_ty: ir.call_enum_ty, + event_enum_ty: ir.event_enum_ty, + error_enum_ty: ir.error_enum_ty, + } + } +} + +impl From for DeprecationStatus { + fn from(ir: DeprecationStatusIR) -> Self { + match ir { + DeprecationStatusIR::NotDeprecated => DeprecationStatus::NotDeprecated, + DeprecationStatusIR::DeprecatedWithoutNote => DeprecationStatus::DeprecatedWithoutNote, + DeprecationStatusIR::Deprecated { since, note } => + DeprecationStatus::Deprecated { since, note }, + } + } +} + +impl From for DeprecationInfo { + fn from(ir: DeprecationInfoIR) -> Self { + match ir { + DeprecationInfoIR::NotDeprecated => DeprecationInfo::NotDeprecated, + DeprecationInfoIR::ItemDeprecated(status) => + DeprecationInfo::ItemDeprecated(status.into()), + DeprecationInfoIR::VariantsDeprecated(btree) => DeprecationInfo::VariantsDeprecated( + btree.into_iter().map(|(key, value)| (key.0, value.into())).collect(), + ), + } + } +} diff --git a/substrate/primitives/metadata-ir/src/v14.rs b/substrate/primitives/metadata-ir/src/v14.rs index 70e84532add9..f3cb5973f5bd 100644 --- a/substrate/primitives/metadata-ir/src/v14.rs +++ b/substrate/primitives/metadata-ir/src/v14.rs @@ -149,9 +149,12 @@ impl From for SignedExtensionMetadata { impl From for ExtrinsicMetadata { fn from(ir: ExtrinsicMetadataIR) -> Self { + let lowest_supported_version = + ir.versions.iter().min().expect("Metadata V14 supports one version; qed"); + ExtrinsicMetadata { ty: ir.ty, - version: ir.version, + version: *lowest_supported_version, signed_extensions: ir.extensions.into_iter().map(Into::into).collect(), } } diff --git a/substrate/primitives/metadata-ir/src/v15.rs b/substrate/primitives/metadata-ir/src/v15.rs index 4b3b6106d27f..ed315a31e6dc 100644 --- a/substrate/primitives/metadata-ir/src/v15.rs +++ b/substrate/primitives/metadata-ir/src/v15.rs @@ -100,7 +100,7 @@ impl From for SignedExtensionMetadata { impl From for ExtrinsicMetadata { fn from(ir: ExtrinsicMetadataIR) -> Self { ExtrinsicMetadata { - version: ir.version, + version: *ir.versions.iter().min().expect("Metadata V15 supports only one version"), address_ty: ir.address_ty, call_ty: ir.call_ty, signature_ty: ir.signature_ty, diff --git a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs index 91ba37451909..d8510a60a789 100644 --- a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -389,8 +389,7 @@ where impl> ExtrinsicMetadata for UncheckedExtrinsic { - // TODO: Expose both version 4 and version 5 in metadata v16. - const VERSION: u8 = LEGACY_EXTRINSIC_FORMAT_VERSION; + const VERSIONS: &'static [u8] = &[LEGACY_EXTRINSIC_FORMAT_VERSION, EXTRINSIC_FORMAT_VERSION]; type TransactionExtensions = Extension; } diff --git a/substrate/primitives/runtime/src/traits/mod.rs b/substrate/primitives/runtime/src/traits/mod.rs index 02bc7adc8ba5..cfcc3e5a354d 100644 --- a/substrate/primitives/runtime/src/traits/mod.rs +++ b/substrate/primitives/runtime/src/traits/mod.rs @@ -1410,10 +1410,10 @@ impl SignaturePayload for () { /// Implementor is an [`Extrinsic`] and provides metadata about this extrinsic. pub trait ExtrinsicMetadata { - /// The format version of the `Extrinsic`. + /// The format versions of the `Extrinsic`. /// - /// By format is meant the encoded representation of the `Extrinsic`. - const VERSION: u8; + /// By format we mean the encoded representation of the `Extrinsic`. + const VERSIONS: &'static [u8]; /// Transaction extensions attached to this `Extrinsic`. type TransactionExtensions; diff --git a/substrate/utils/frame/benchmarking-cli/src/overhead/remark_builder.rs b/substrate/utils/frame/benchmarking-cli/src/overhead/remark_builder.rs index a1d5f282d9f8..3a2d8776d1e1 100644 --- a/substrate/utils/frame/benchmarking-cli/src/overhead/remark_builder.rs +++ b/substrate/utils/frame/benchmarking-cli/src/overhead/remark_builder.rs @@ -54,13 +54,15 @@ impl> DynamicRemarkBuilder { log::debug!("Found metadata API version {}.", metadata_api_version); let opaque_metadata = if metadata_api_version > 1 { - let Ok(mut supported_metadata_versions) = api.metadata_versions(genesis) else { + let Ok(supported_metadata_versions) = api.metadata_versions(genesis) else { return Err("Unable to fetch metadata versions".to_string().into()); }; let latest = supported_metadata_versions - .pop() - .ok_or("No metadata version supported".to_string())?; + .into_iter() + .filter(|v| *v != u32::MAX) + .max() + .ok_or("No stable metadata versions supported".to_string())?; api.metadata_at_version(genesis, latest) .map_err(|e| format!("Unable to fetch metadata: {:?}", e))? diff --git a/substrate/utils/frame/benchmarking-cli/src/overhead/runtime_utilities.rs b/substrate/utils/frame/benchmarking-cli/src/overhead/runtime_utilities.rs index c498da38afb0..3081197dc033 100644 --- a/substrate/utils/frame/benchmarking-cli/src/overhead/runtime_utilities.rs +++ b/substrate/utils/frame/benchmarking-cli/src/overhead/runtime_utilities.rs @@ -35,14 +35,19 @@ pub fn fetch_latest_metadata_from_code_blob( let opaque_metadata: OpaqueMetadata = match version_result { Ok(supported_versions) => { - let latest_version = Vec::::decode(&mut supported_versions.as_slice()) - .map_err(|e| format!("Unable to decode version list: {e}"))? - .pop() - .ok_or("No metadata versions supported".to_string())?; + let supported_versions = Vec::::decode(&mut supported_versions.as_slice()) + .map_err(|e| format!("Unable to decode version list: {e}"))?; + + let latest_stable = supported_versions + .into_iter() + .filter(|v| *v != u32::MAX) + .max() + .ok_or("No stable metadata versions supported".to_string())?; let encoded = runtime_caller - .call("Metadata_metadata_at_version", latest_version) + .call("Metadata_metadata_at_version", latest_stable) .map_err(|_| "Unable to fetch metadata from blob".to_string())?; + Option::::decode(&mut encoded.as_slice())? .ok_or_else(|| "Metadata not found".to_string())? }, diff --git a/substrate/utils/wasm-builder/Cargo.toml b/substrate/utils/wasm-builder/Cargo.toml index 8f0e8a23e54a..fb15e8619a38 100644 --- a/substrate/utils/wasm-builder/Cargo.toml +++ b/substrate/utils/wasm-builder/Cargo.toml @@ -35,7 +35,7 @@ sc-executor = { optional = true, workspace = true, default-features = true } sp-core = { optional = true, workspace = true, default-features = true } sp-io = { optional = true, workspace = true, default-features = true } sp-version = { optional = true, workspace = true, default-features = true } -frame-metadata = { features = ["current"], optional = true, workspace = true, default-features = true } +frame-metadata = { features = ["current", "unstable"], optional = true, workspace = true, default-features = true } codec = { optional = true, workspace = true, default-features = true } array-bytes = { optional = true, workspace = true, default-features = true } sp-tracing = { optional = true, workspace = true, default-features = true } From afd065fa7267494246a9a8d767dfd030e2681fce Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:12:39 +0200 Subject: [PATCH 052/340] rpc-v2: Implement `archive_unstable_storageDiff` (#5997) This PR implements the `archive_unstable_storageDiff`. The implementation follows the rpc-v2 spec from: - https://github.com/paritytech/json-rpc-interface-spec/pull/159. - builds on top of https://github.com/paritytech/json-rpc-interface-spec/pull/161 cc @paritytech/subxt-team --------- Signed-off-by: Alexandru Vasile Co-authored-by: James Wilson --- Cargo.lock | 7 +- prdoc/pr_5997.prdoc | 18 + substrate/client/rpc-spec-v2/Cargo.toml | 1 + .../client/rpc-spec-v2/src/archive/api.rs | 22 +- .../client/rpc-spec-v2/src/archive/archive.rs | 89 +- .../src/archive/archive_storage.rs | 829 +++++++++++++++++- .../client/rpc-spec-v2/src/archive/tests.rs | 277 +++++- .../client/rpc-spec-v2/src/common/events.rs | 208 ++++- .../client/rpc-spec-v2/src/common/storage.rs | 15 + substrate/client/service/src/builder.rs | 1 + 10 files changed, 1449 insertions(+), 18 deletions(-) create mode 100644 prdoc/pr_5997.prdoc diff --git a/Cargo.lock b/Cargo.lock index 2c938ec17bd0..12e642bc9d06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20959,7 +20959,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.8", "regex-syntax 0.8.5", ] @@ -20980,9 +20980,9 @@ checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -23277,6 +23277,7 @@ dependencies = [ "futures", "futures-util", "hex", + "itertools 0.11.0", "jsonrpsee", "log", "parity-scale-codec", diff --git a/prdoc/pr_5997.prdoc b/prdoc/pr_5997.prdoc new file mode 100644 index 000000000000..6bac36a44586 --- /dev/null +++ b/prdoc/pr_5997.prdoc @@ -0,0 +1,18 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Implement archive_unstable_storageDiff method + +doc: + - audience: Node Dev + description: | + This PR implements the `archive_unstable_storageDiff` rpc-v2 method. + Developers can use this method to fetch the storage differences + between two blocks. This is useful for oracles and archive nodes. + For more details see: https://github.com/paritytech/json-rpc-interface-spec/blob/main/src/api/archive_unstable_storageDiff.md. + +crates: + - name: sc-rpc-spec-v2 + bump: major + - name: sc-service + bump: patch diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index daa805912fb9..b304bc905925 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -42,6 +42,7 @@ log = { workspace = true, default-features = true } futures-util = { workspace = true } rand = { workspace = true, default-features = true } schnellru = { workspace = true } +itertools = { workspace = true } [dev-dependencies] async-trait = { workspace = true } diff --git a/substrate/client/rpc-spec-v2/src/archive/api.rs b/substrate/client/rpc-spec-v2/src/archive/api.rs index b19738304000..dcfeaecb147b 100644 --- a/substrate/client/rpc-spec-v2/src/archive/api.rs +++ b/substrate/client/rpc-spec-v2/src/archive/api.rs @@ -19,7 +19,10 @@ //! API trait of the archive methods. use crate::{ - common::events::{ArchiveStorageResult, PaginatedStorageQuery}, + common::events::{ + ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageResult, + PaginatedStorageQuery, + }, MethodResult, }; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; @@ -104,4 +107,21 @@ pub trait ArchiveApi { items: Vec>, child_trie: Option, ) -> RpcResult; + + /// Returns the storage difference between two blocks. + /// + /// # Unstable + /// + /// This method is unstable and can change in minor or patch releases. + #[subscription( + name = "archive_unstable_storageDiff" => "archive_unstable_storageDiffEvent", + unsubscribe = "archive_unstable_storageDiff_stopStorageDiff", + item = ArchiveStorageDiffEvent, + )] + fn archive_unstable_storage_diff( + &self, + hash: Hash, + items: Vec>, + previous_hash: Option, + ); } diff --git a/substrate/client/rpc-spec-v2/src/archive/archive.rs b/substrate/client/rpc-spec-v2/src/archive/archive.rs index dd6c566a76ed..55054d91d85d 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive.rs @@ -19,17 +19,29 @@ //! API implementation for `archive`. use crate::{ - archive::{error::Error as ArchiveError, ArchiveApiServer}, - common::events::{ArchiveStorageResult, PaginatedStorageQuery}, - hex_string, MethodResult, + archive::{ + archive_storage::{ArchiveStorage, ArchiveStorageDiff}, + error::Error as ArchiveError, + ArchiveApiServer, + }, + common::events::{ + ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageResult, + PaginatedStorageQuery, + }, + hex_string, MethodResult, SubscriptionTaskExecutor, }; use codec::Encode; -use jsonrpsee::core::{async_trait, RpcResult}; +use futures::FutureExt; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + PendingSubscriptionSink, +}; use sc_client_api::{ Backend, BlockBackend, BlockchainEvents, CallExecutor, ChildInfo, ExecutorProvider, StorageKey, StorageProvider, }; +use sc_rpc::utils::Subscription; use sp_api::{CallApiAt, CallContext}; use sp_blockchain::{ Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata, @@ -41,7 +53,9 @@ use sp_runtime::{ }; use std::{collections::HashSet, marker::PhantomData, sync::Arc}; -use super::archive_storage::ArchiveStorage; +use tokio::sync::mpsc; + +pub(crate) const LOG_TARGET: &str = "rpc-spec-v2::archive"; /// The configuration of [`Archive`]. pub struct ArchiveConfig { @@ -64,6 +78,12 @@ const MAX_DESCENDANT_RESPONSES: usize = 5; /// `MAX_DESCENDANT_RESPONSES`. const MAX_QUERIED_ITEMS: usize = 8; +/// The buffer capacity for each storage query. +/// +/// This is small because the underlying JSON-RPC server has +/// its down buffer capacity per connection as well. +const STORAGE_QUERY_BUF: usize = 16; + impl Default for ArchiveConfig { fn default() -> Self { Self { @@ -79,6 +99,8 @@ pub struct Archive, Block: BlockT, Client> { client: Arc, /// Backend of the chain. backend: Arc, + /// Executor to spawn subscriptions. + executor: SubscriptionTaskExecutor, /// The hexadecimal encoded hash of the genesis block. genesis_hash: String, /// The maximum number of items the `archive_storage` can return for a descendant query before @@ -96,12 +118,14 @@ impl, Block: BlockT, Client> Archive { client: Arc, backend: Arc, genesis_hash: GenesisHash, + executor: SubscriptionTaskExecutor, config: ArchiveConfig, ) -> Self { let genesis_hash = hex_string(&genesis_hash.as_ref()); Self { client, backend, + executor, genesis_hash, storage_max_descendant_responses: config.max_descendant_responses, storage_max_queried_items: config.max_queried_items, @@ -278,4 +302,59 @@ where Ok(storage_client.handle_query(hash, items, child_trie)) } + + fn archive_unstable_storage_diff( + &self, + pending: PendingSubscriptionSink, + hash: Block::Hash, + items: Vec>, + previous_hash: Option, + ) { + let storage_client = ArchiveStorageDiff::new(self.client.clone()); + let client = self.client.clone(); + + log::trace!(target: LOG_TARGET, "Storage diff subscription started"); + + let fut = async move { + let Ok(mut sink) = pending.accept().await.map(Subscription::from) else { return }; + + let previous_hash = if let Some(previous_hash) = previous_hash { + previous_hash + } else { + let Ok(Some(current_header)) = client.header(hash) else { + let message = format!("Block header is not present: {hash}"); + let _ = sink.send(&ArchiveStorageDiffEvent::err(message)).await; + return + }; + *current_header.parent_hash() + }; + + let (tx, mut rx) = tokio::sync::mpsc::channel(STORAGE_QUERY_BUF); + let storage_fut = + storage_client.handle_trie_queries(hash, items, previous_hash, tx.clone()); + + // We don't care about the return value of this join: + // - process_events might encounter an error (if the client disconnected) + // - storage_fut might encounter an error while processing a trie queries and + // the error is propagated via the sink. + let _ = futures::future::join(storage_fut, process_events(&mut rx, &mut sink)).await; + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + } +} + +/// Sends all the events to the sink. +async fn process_events(rx: &mut mpsc::Receiver, sink: &mut Subscription) { + while let Some(event) = rx.recv().await { + if event.is_done() { + log::debug!(target: LOG_TARGET, "Finished processing partial trie query"); + } else if event.is_err() { + log::debug!(target: LOG_TARGET, "Error encountered while processing partial trie query"); + } + + if sink.send(&event).await.is_err() { + return + } + } } diff --git a/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs b/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs index 26e7c299de41..5a3920882f00 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs @@ -18,15 +18,28 @@ //! Implementation of the `archive_storage` method. -use std::sync::Arc; +use std::{ + collections::{hash_map::Entry, HashMap}, + sync::Arc, +}; +use itertools::Itertools; use sc_client_api::{Backend, ChildInfo, StorageKey, StorageProvider}; use sp_runtime::traits::Block as BlockT; -use crate::common::{ - events::{ArchiveStorageResult, PaginatedStorageQuery, StorageQueryType}, - storage::{IterQueryType, QueryIter, Storage}, +use super::error::Error as ArchiveError; +use crate::{ + archive::archive::LOG_TARGET, + common::{ + events::{ + ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageDiffOperationType, + ArchiveStorageDiffResult, ArchiveStorageDiffType, ArchiveStorageResult, + PaginatedStorageQuery, StorageQueryType, StorageResult, + }, + storage::{IterQueryType, QueryIter, Storage}, + }, }; +use tokio::sync::mpsc; /// Generates the events of the `archive_storage` method. pub struct ArchiveStorage { @@ -127,3 +140,811 @@ where ArchiveStorageResult::ok(storage_results, discarded_items) } } + +/// Parse hex-encoded string parameter as raw bytes. +/// +/// If the parsing fails, returns an error propagated to the RPC method. +pub fn parse_hex_param(param: String) -> Result, ArchiveError> { + // Methods can accept empty parameters. + if param.is_empty() { + return Ok(Default::default()) + } + + array_bytes::hex2bytes(¶m).map_err(|_| ArchiveError::InvalidParam(param)) +} + +#[derive(Debug, PartialEq, Clone)] +pub struct DiffDetails { + key: StorageKey, + return_type: ArchiveStorageDiffType, + child_trie_key: Option, + child_trie_key_string: Option, +} + +/// The type of storage query. +#[derive(Debug, PartialEq, Clone, Copy)] +enum FetchStorageType { + /// Only fetch the value. + Value, + /// Only fetch the hash. + Hash, + /// Fetch both the value and the hash. + Both, +} + +/// The return value of the `fetch_storage` method. +#[derive(Debug, PartialEq, Clone)] +enum FetchedStorage { + /// Storage value under a key. + Value(StorageResult), + /// Storage hash under a key. + Hash(StorageResult), + /// Both storage value and hash under a key. + Both { value: StorageResult, hash: StorageResult }, +} + +pub struct ArchiveStorageDiff { + client: Storage, +} + +impl ArchiveStorageDiff { + pub fn new(client: Arc) -> Self { + Self { client: Storage::new(client) } + } +} + +impl ArchiveStorageDiff +where + Block: BlockT + 'static, + BE: Backend + 'static, + Client: StorageProvider + Send + Sync + 'static, +{ + /// Fetch the storage from the given key. + fn fetch_storage( + &self, + hash: Block::Hash, + key: StorageKey, + maybe_child_trie: Option, + ty: FetchStorageType, + ) -> Result, String> { + match ty { + FetchStorageType::Value => { + let result = self.client.query_value(hash, &key, maybe_child_trie.as_ref())?; + + Ok(result.map(FetchedStorage::Value)) + }, + + FetchStorageType::Hash => { + let result = self.client.query_hash(hash, &key, maybe_child_trie.as_ref())?; + + Ok(result.map(FetchedStorage::Hash)) + }, + + FetchStorageType::Both => { + let Some(value) = self.client.query_value(hash, &key, maybe_child_trie.as_ref())? + else { + return Ok(None); + }; + + let Some(hash) = self.client.query_hash(hash, &key, maybe_child_trie.as_ref())? + else { + return Ok(None); + }; + + Ok(Some(FetchedStorage::Both { value, hash })) + }, + } + } + + /// Check if the key belongs to the provided query items. + /// + /// A key belongs to the query items when: + /// - the provided key is a prefix of the key in the query items. + /// - the query items are empty. + /// + /// Returns an optional `FetchStorageType` based on the query items. + /// If the key does not belong to the query items, returns `None`. + fn belongs_to_query(key: &StorageKey, items: &[DiffDetails]) -> Option { + // User has requested all keys, by default this fallbacks to fetching the value. + if items.is_empty() { + return Some(FetchStorageType::Value) + } + + let mut value = false; + let mut hash = false; + + for item in items { + if key.as_ref().starts_with(&item.key.as_ref()) { + match item.return_type { + ArchiveStorageDiffType::Value => value = true, + ArchiveStorageDiffType::Hash => hash = true, + } + } + } + + match (value, hash) { + (true, true) => Some(FetchStorageType::Both), + (true, false) => Some(FetchStorageType::Value), + (false, true) => Some(FetchStorageType::Hash), + (false, false) => None, + } + } + + /// Send the provided result to the `tx` sender. + /// + /// Returns `false` if the sender has been closed. + fn send_result( + tx: &mpsc::Sender, + result: FetchedStorage, + operation_type: ArchiveStorageDiffOperationType, + child_trie_key: Option, + ) -> bool { + let items = match result { + FetchedStorage::Value(storage_result) | FetchedStorage::Hash(storage_result) => + vec![storage_result], + FetchedStorage::Both { value, hash } => vec![value, hash], + }; + + for item in items { + let res = ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: item.key, + result: item.result, + operation_type, + child_trie_key: child_trie_key.clone(), + }); + if tx.blocking_send(res).is_err() { + return false + } + } + + true + } + + fn handle_trie_queries_inner( + &self, + hash: Block::Hash, + previous_hash: Block::Hash, + items: Vec, + tx: &mpsc::Sender, + ) -> Result<(), String> { + // Parse the child trie key as `ChildInfo` and `String`. + let maybe_child_trie = items.first().and_then(|item| item.child_trie_key.clone()); + let maybe_child_trie_str = + items.first().and_then(|item| item.child_trie_key_string.clone()); + + // Iterator over the current block and previous block + // at the same time to compare the keys. This approach effectively + // leverages backpressure to avoid memory consumption. + let keys_iter = self.client.raw_keys_iter(hash, maybe_child_trie.clone())?; + let previous_keys_iter = + self.client.raw_keys_iter(previous_hash, maybe_child_trie.clone())?; + + let mut diff_iter = lexicographic_diff(keys_iter, previous_keys_iter); + + while let Some(item) = diff_iter.next() { + let (operation_type, key) = match item { + Diff::Added(key) => (ArchiveStorageDiffOperationType::Added, key), + Diff::Deleted(key) => (ArchiveStorageDiffOperationType::Deleted, key), + Diff::Equal(key) => (ArchiveStorageDiffOperationType::Modified, key), + }; + + let Some(fetch_type) = Self::belongs_to_query(&key, &items) else { + // The key does not belong the the query items. + continue; + }; + + let maybe_result = match operation_type { + ArchiveStorageDiffOperationType::Added => + self.fetch_storage(hash, key.clone(), maybe_child_trie.clone(), fetch_type)?, + ArchiveStorageDiffOperationType::Deleted => self.fetch_storage( + previous_hash, + key.clone(), + maybe_child_trie.clone(), + fetch_type, + )?, + ArchiveStorageDiffOperationType::Modified => { + let Some(storage_result) = self.fetch_storage( + hash, + key.clone(), + maybe_child_trie.clone(), + fetch_type, + )? + else { + continue + }; + + let Some(previous_storage_result) = self.fetch_storage( + previous_hash, + key.clone(), + maybe_child_trie.clone(), + fetch_type, + )? + else { + continue + }; + + // For modified records we need to check the actual storage values. + if storage_result == previous_storage_result { + continue + } + + Some(storage_result) + }, + }; + + if let Some(storage_result) = maybe_result { + if !Self::send_result( + &tx, + storage_result, + operation_type, + maybe_child_trie_str.clone(), + ) { + return Ok(()) + } + } + } + + Ok(()) + } + + /// This method will iterate over the keys of the main trie or a child trie and fetch the + /// given keys. The fetched keys will be sent to the provided `tx` sender to leverage + /// the backpressure mechanism. + pub async fn handle_trie_queries( + &self, + hash: Block::Hash, + items: Vec>, + previous_hash: Block::Hash, + tx: mpsc::Sender, + ) -> Result<(), tokio::task::JoinError> { + let this = ArchiveStorageDiff { client: self.client.clone() }; + + tokio::task::spawn_blocking(move || { + // Deduplicate the items. + let mut trie_items = match deduplicate_storage_diff_items(items) { + Ok(items) => items, + Err(error) => { + let _ = tx.blocking_send(ArchiveStorageDiffEvent::err(error.to_string())); + return + }, + }; + // Default to using the main storage trie if no items are provided. + if trie_items.is_empty() { + trie_items.push(Vec::new()); + } + log::trace!(target: LOG_TARGET, "Storage diff deduplicated items: {:?}", trie_items); + + for items in trie_items { + log::trace!( + target: LOG_TARGET, + "handle_trie_queries: hash={:?}, previous_hash={:?}, items={:?}", + hash, + previous_hash, + items + ); + + let result = this.handle_trie_queries_inner(hash, previous_hash, items, &tx); + + if let Err(error) = result { + log::trace!( + target: LOG_TARGET, + "handle_trie_queries: sending error={:?}", + error, + ); + + let _ = tx.blocking_send(ArchiveStorageDiffEvent::err(error)); + + return + } else { + log::trace!( + target: LOG_TARGET, + "handle_trie_queries: sending storage diff done", + ); + } + } + + let _ = tx.blocking_send(ArchiveStorageDiffEvent::StorageDiffDone); + }) + .await?; + + Ok(()) + } +} + +/// The result of the `lexicographic_diff` method. +#[derive(Debug, PartialEq)] +enum Diff { + Added(T), + Deleted(T), + Equal(T), +} + +/// Compare two iterators lexicographically and return the differences. +fn lexicographic_diff( + mut left: LeftIter, + mut right: RightIter, +) -> impl Iterator> +where + T: Ord, + LeftIter: Iterator, + RightIter: Iterator, +{ + let mut a = left.next(); + let mut b = right.next(); + + core::iter::from_fn(move || match (a.take(), b.take()) { + (Some(a_value), Some(b_value)) => + if a_value < b_value { + b = Some(b_value); + a = left.next(); + + Some(Diff::Added(a_value)) + } else if a_value > b_value { + a = Some(a_value); + b = right.next(); + + Some(Diff::Deleted(b_value)) + } else { + a = left.next(); + b = right.next(); + + Some(Diff::Equal(a_value)) + }, + (Some(a_value), None) => { + a = left.next(); + Some(Diff::Added(a_value)) + }, + (None, Some(b_value)) => { + b = right.next(); + Some(Diff::Deleted(b_value)) + }, + (None, None) => None, + }) +} + +/// Deduplicate the provided items and return a list of `DiffDetails`. +/// +/// Each list corresponds to a single child trie or the main trie. +fn deduplicate_storage_diff_items( + items: Vec>, +) -> Result>, ArchiveError> { + let mut deduplicated: HashMap, Vec> = HashMap::new(); + + for diff_item in items { + // Ensure the provided hex keys are valid before deduplication. + let key = StorageKey(parse_hex_param(diff_item.key)?); + let child_trie_key_string = diff_item.child_trie_key.clone(); + let child_trie_key = diff_item + .child_trie_key + .map(|child_trie_key| parse_hex_param(child_trie_key)) + .transpose()? + .map(ChildInfo::new_default_from_vec); + + let diff_item = DiffDetails { + key, + return_type: diff_item.return_type, + child_trie_key: child_trie_key.clone(), + child_trie_key_string, + }; + + match deduplicated.entry(child_trie_key.clone()) { + Entry::Occupied(mut entry) => { + let mut should_insert = true; + + for existing in entry.get() { + // This points to a different return type. + if existing.return_type != diff_item.return_type { + continue + } + // Keys and return types are identical. + if existing.key == diff_item.key { + should_insert = false; + break + } + + // The following two conditions ensure that we keep the shortest key. + + // The current key is a longer prefix of the existing key. + if diff_item.key.as_ref().starts_with(&existing.key.as_ref()) { + should_insert = false; + break + } + + // The existing key is a longer prefix of the current key. + // We need to keep the current key and remove the existing one. + if existing.key.as_ref().starts_with(&diff_item.key.as_ref()) { + let to_remove = existing.clone(); + entry.get_mut().retain(|item| item != &to_remove); + break; + } + } + + if should_insert { + entry.get_mut().push(diff_item); + } + }, + Entry::Vacant(entry) => { + entry.insert(vec![diff_item]); + }, + } + } + + Ok(deduplicated + .into_iter() + .sorted_by_key(|(child_trie_key, _)| child_trie_key.clone()) + .map(|(_, values)| values) + .collect()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dedup_empty() { + let items = vec![]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert!(result.is_empty()); + } + + #[test] + fn dedup_single() { + let items = vec![ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 1); + + let expected = DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }; + assert_eq!(result[0][0], expected); + } + + #[test] + fn dedup_with_different_keys() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x02".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 2); + + let expected = vec![ + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }, + DiffDetails { + key: StorageKey(vec![2]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }, + ]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_with_same_keys() { + // Identical keys. + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 1); + + let expected = vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_with_same_prefix() { + // Identical keys. + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x01ff".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 1); + + let expected = vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_with_different_return_types() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: None, + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 2); + + let expected = vec![ + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }, + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: None, + child_trie_key_string: None, + }, + ]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_with_different_child_tries() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x01".into()), + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x02".into()), + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 2); + assert_eq!(result[0].len(), 1); + assert_eq!(result[1].len(), 1); + + let expected = vec![ + vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])), + child_trie_key_string: Some("0x01".into()), + }], + vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![2])), + child_trie_key_string: Some("0x02".into()), + }], + ]; + assert_eq!(result, expected); + } + + #[test] + fn dedup_with_same_child_tries() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x01".into()), + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x01".into()), + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 1); + + let expected = vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])), + child_trie_key_string: Some("0x01".into()), + }]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_with_shorter_key_reverse_order() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x01ff".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ]; + let result = deduplicate_storage_diff_items(items).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].len(), 1); + + let expected = vec![DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }]; + assert_eq!(result[0], expected); + } + + #[test] + fn dedup_multiple_child_tries() { + let items = vec![ + ArchiveStorageDiffItem { + key: "0x02".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x01".into()), + }, + ArchiveStorageDiffItem { + key: "0x02".into(), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: Some("0x01".into()), + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x02".into()), + }, + ArchiveStorageDiffItem { + key: "0x01".into(), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: Some("0x02".into()), + }, + ArchiveStorageDiffItem { + key: "0x01ff".into(), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x02".into()), + }, + ]; + + let result = deduplicate_storage_diff_items(items).unwrap(); + + let expected = vec![ + vec![DiffDetails { + key: StorageKey(vec![2]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + child_trie_key_string: None, + }], + vec![ + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])), + child_trie_key_string: Some("0x01".into()), + }, + DiffDetails { + key: StorageKey(vec![2]), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![1])), + child_trie_key_string: Some("0x01".into()), + }, + ], + vec![ + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![2])), + child_trie_key_string: Some("0x02".into()), + }, + DiffDetails { + key: StorageKey(vec![1]), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: Some(ChildInfo::new_default_from_vec(vec![2])), + child_trie_key_string: Some("0x02".into()), + }, + ], + ]; + + assert_eq!(result, expected); + } + + #[test] + fn test_lexicographic_diff() { + let left = vec![1, 2, 3, 4, 5]; + let right = vec![2, 3, 4, 5, 6]; + + let diff = lexicographic_diff(left.into_iter(), right.into_iter()).collect::>(); + let expected = vec![ + Diff::Added(1), + Diff::Equal(2), + Diff::Equal(3), + Diff::Equal(4), + Diff::Equal(5), + Diff::Deleted(6), + ]; + assert_eq!(diff, expected); + } + + #[test] + fn test_lexicographic_diff_one_side_empty() { + let left = vec![]; + let right = vec![1, 2, 3, 4, 5, 6]; + + let diff = lexicographic_diff(left.into_iter(), right.into_iter()).collect::>(); + let expected = vec![ + Diff::Deleted(1), + Diff::Deleted(2), + Diff::Deleted(3), + Diff::Deleted(4), + Diff::Deleted(5), + Diff::Deleted(6), + ]; + assert_eq!(diff, expected); + + let left = vec![1, 2, 3, 4, 5, 6]; + let right = vec![]; + + let diff = lexicographic_diff(left.into_iter(), right.into_iter()).collect::>(); + let expected = vec![ + Diff::Added(1), + Diff::Added(2), + Diff::Added(3), + Diff::Added(4), + Diff::Added(5), + Diff::Added(6), + ]; + assert_eq!(diff, expected); + } +} diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index 078016f5b3e2..994c5d28bd61 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -18,8 +18,9 @@ use crate::{ common::events::{ - ArchiveStorageMethodOk, ArchiveStorageResult, PaginatedStorageQuery, StorageQueryType, - StorageResultType, + ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageDiffOperationType, + ArchiveStorageDiffResult, ArchiveStorageDiffType, ArchiveStorageMethodOk, + ArchiveStorageResult, PaginatedStorageQuery, StorageQueryType, StorageResultType, }, hex_string, MethodResult, }; @@ -32,10 +33,13 @@ use super::{ use assert_matches::assert_matches; use codec::{Decode, Encode}; use jsonrpsee::{ - core::EmptyServerParams as EmptyParams, rpc_params, MethodsError as Error, RpcModule, + core::{server::Subscription as RpcSubscription, EmptyServerParams as EmptyParams}, + rpc_params, MethodsError as Error, RpcModule, }; + use sc_block_builder::BlockBuilderBuilder; use sc_client_api::ChildInfo; +use sc_rpc::testing::TokioTestExecutor; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; use sp_core::{Blake2Hasher, Hasher}; @@ -78,6 +82,7 @@ fn setup_api( client.clone(), backend, CHAIN_GENESIS, + Arc::new(TokioTestExecutor::default()), ArchiveConfig { max_descendant_responses, max_queried_items }, ) .into_rpc(); @@ -85,6 +90,15 @@ fn setup_api( (client, api) } +async fn get_next_event(sub: &mut RpcSubscription) -> T { + let (event, _sub_id) = tokio::time::timeout(std::time::Duration::from_secs(60), sub.next()) + .await + .unwrap() + .unwrap() + .unwrap(); + event +} + #[tokio::test] async fn archive_genesis() { let (_client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); @@ -838,3 +852,260 @@ async fn archive_storage_discarded_items() { _ => panic!("Unexpected result"), }; } + +#[tokio::test] +async fn archive_storage_diff_main_trie() { + let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap(); + builder.push_storage_change(b":A".to_vec(), Some(b"B".to_vec())).unwrap(); + builder.push_storage_change(b":AA".to_vec(), Some(b"BB".to_vec())).unwrap(); + let prev_block = builder.build().unwrap().block; + let prev_hash = format!("{:?}", prev_block.header.hash()); + client.import(BlockOrigin::Own, prev_block.clone()).await.unwrap(); + + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(prev_block.hash()) + .with_parent_block_number(1) + .build() + .unwrap(); + builder.push_storage_change(b":A".to_vec(), Some(b"11".to_vec())).unwrap(); + builder.push_storage_change(b":AA".to_vec(), Some(b"22".to_vec())).unwrap(); + builder.push_storage_change(b":AAA".to_vec(), Some(b"222".to_vec())).unwrap(); + let block = builder.build().unwrap().block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Search for items in the main trie: + // - values of keys under ":A" + // - hashes of keys under ":AA" + let items = vec![ + ArchiveStorageDiffItem:: { + key: hex_string(b":A"), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }, + ArchiveStorageDiffItem:: { + key: hex_string(b":AA"), + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: None, + }, + ]; + let mut sub = api + .subscribe_unbounded( + "archive_unstable_storageDiff", + rpc_params![&block_hash, items.clone(), &prev_hash], + ) + .await + .unwrap(); + + let event = get_next_event::(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":A"), + result: StorageResultType::Value(hex_string(b"11")), + operation_type: ArchiveStorageDiffOperationType::Modified, + child_trie_key: None, + }), + event, + ); + + let event = get_next_event::(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":AA"), + result: StorageResultType::Value(hex_string(b"22")), + operation_type: ArchiveStorageDiffOperationType::Modified, + child_trie_key: None, + }), + event, + ); + + let event = get_next_event::(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":AA"), + result: StorageResultType::Hash(format!("{:?}", Blake2Hasher::hash(b"22"))), + operation_type: ArchiveStorageDiffOperationType::Modified, + child_trie_key: None, + }), + event, + ); + + // Added key. + let event = get_next_event::(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":AAA"), + result: StorageResultType::Value(hex_string(b"222")), + operation_type: ArchiveStorageDiffOperationType::Added, + child_trie_key: None, + }), + event, + ); + + let event = get_next_event::(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":AAA"), + result: StorageResultType::Hash(format!("{:?}", Blake2Hasher::hash(b"222"))), + operation_type: ArchiveStorageDiffOperationType::Added, + child_trie_key: None, + }), + event, + ); + + let event = get_next_event::(&mut sub).await; + assert_eq!(ArchiveStorageDiffEvent::StorageDiffDone, event); +} + +#[tokio::test] +async fn archive_storage_diff_no_changes() { + let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + + // Build 2 identical blocks. + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap(); + builder.push_storage_change(b":A".to_vec(), Some(b"B".to_vec())).unwrap(); + builder.push_storage_change(b":AA".to_vec(), Some(b"BB".to_vec())).unwrap(); + builder.push_storage_change(b":B".to_vec(), Some(b"CC".to_vec())).unwrap(); + builder.push_storage_change(b":BA".to_vec(), Some(b"CC".to_vec())).unwrap(); + let prev_block = builder.build().unwrap().block; + let prev_hash = format!("{:?}", prev_block.header.hash()); + client.import(BlockOrigin::Own, prev_block.clone()).await.unwrap(); + + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(prev_block.hash()) + .with_parent_block_number(1) + .build() + .unwrap(); + builder.push_storage_change(b":A".to_vec(), Some(b"B".to_vec())).unwrap(); + builder.push_storage_change(b":AA".to_vec(), Some(b"BB".to_vec())).unwrap(); + let block = builder.build().unwrap().block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Search for items in the main trie with keys prefixed with ":A". + let items = vec![ArchiveStorageDiffItem:: { + key: hex_string(b":A"), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }]; + let mut sub = api + .subscribe_unbounded( + "archive_unstable_storageDiff", + rpc_params![&block_hash, items.clone(), &prev_hash], + ) + .await + .unwrap(); + + let event = get_next_event::(&mut sub).await; + assert_eq!(ArchiveStorageDiffEvent::StorageDiffDone, event); +} + +#[tokio::test] +async fn archive_storage_diff_deleted_changes() { + let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + + // Blocks are imported as forks. + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap(); + builder.push_storage_change(b":A".to_vec(), Some(b"B".to_vec())).unwrap(); + builder.push_storage_change(b":AA".to_vec(), Some(b"BB".to_vec())).unwrap(); + builder.push_storage_change(b":B".to_vec(), Some(b"CC".to_vec())).unwrap(); + builder.push_storage_change(b":BA".to_vec(), Some(b"CC".to_vec())).unwrap(); + let prev_block = builder.build().unwrap().block; + let prev_hash = format!("{:?}", prev_block.header.hash()); + client.import(BlockOrigin::Own, prev_block.clone()).await.unwrap(); + + let mut builder = BlockBuilderBuilder::new(&*client) + .on_parent_block(client.chain_info().genesis_hash) + .with_parent_block_number(0) + .build() + .unwrap(); + builder + .push_transfer(Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 41, + nonce: 0, + }) + .unwrap(); + builder.push_storage_change(b":A".to_vec(), Some(b"B".to_vec())).unwrap(); + let block = builder.build().unwrap().block; + let block_hash = format!("{:?}", block.header.hash()); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Search for items in the main trie with keys prefixed with ":A". + let items = vec![ArchiveStorageDiffItem:: { + key: hex_string(b":A"), + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }]; + + let mut sub = api + .subscribe_unbounded( + "archive_unstable_storageDiff", + rpc_params![&block_hash, items.clone(), &prev_hash], + ) + .await + .unwrap(); + + let event = get_next_event::(&mut sub).await; + assert_eq!( + ArchiveStorageDiffEvent::StorageDiff(ArchiveStorageDiffResult { + key: hex_string(b":AA"), + result: StorageResultType::Value(hex_string(b"BB")), + operation_type: ArchiveStorageDiffOperationType::Deleted, + child_trie_key: None, + }), + event, + ); + + let event = get_next_event::(&mut sub).await; + assert_eq!(ArchiveStorageDiffEvent::StorageDiffDone, event); +} + +#[tokio::test] +async fn archive_storage_diff_invalid_params() { + let invalid_hash = hex_string(&INVALID_HASH); + let (_, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + + // Invalid shape for parameters. + let items: Vec> = Vec::new(); + let err = api + .subscribe_unbounded( + "archive_unstable_storageDiff", + rpc_params!["123", items.clone(), &invalid_hash], + ) + .await + .unwrap_err(); + assert_matches!(err, + Error::JsonRpc(ref err) if err.code() == crate::chain_head::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message() == "Invalid params" + ); + + // The shape is right, but the block hash is invalid. + let items: Vec> = Vec::new(); + let mut sub = api + .subscribe_unbounded( + "archive_unstable_storageDiff", + rpc_params![&invalid_hash, items.clone(), &invalid_hash], + ) + .await + .unwrap(); + + let event = get_next_event::(&mut sub).await; + assert_matches!(event, + ArchiveStorageDiffEvent::StorageDiffError(ref err) if err.error.contains("Header was not found") + ); +} diff --git a/substrate/client/rpc-spec-v2/src/common/events.rs b/substrate/client/rpc-spec-v2/src/common/events.rs index b1627d74c844..198a60bf4cac 100644 --- a/substrate/client/rpc-spec-v2/src/common/events.rs +++ b/substrate/client/rpc-spec-v2/src/common/events.rs @@ -81,7 +81,7 @@ pub struct StorageResult { } /// The type of the storage query. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum StorageResultType { /// Fetch the value of the provided key. @@ -136,17 +136,221 @@ pub struct ArchiveStorageMethodOk { } /// The error of a storage call. -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ArchiveStorageMethodErr { /// Reported error. pub error: String, } +/// The type of the archive storage difference query. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ArchiveStorageDiffType { + /// The result is provided as value of the key. + Value, + /// The result the hash of the value of the key. + Hash, +} + +/// The storage item to query. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ArchiveStorageDiffItem { + /// The provided key. + pub key: Key, + /// The type of the storage query. + pub return_type: ArchiveStorageDiffType, + /// The child trie key if provided. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub child_trie_key: Option, +} + +/// The result of a storage difference call. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ArchiveStorageDiffMethodResult { + /// Reported results. + pub result: Vec, +} + +/// The result of a storage difference call operation type. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum ArchiveStorageDiffOperationType { + /// The key is added. + Added, + /// The key is modified. + Modified, + /// The key is removed. + Deleted, +} + +/// The result of an individual storage difference key. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ArchiveStorageDiffResult { + /// The hex-encoded key of the result. + pub key: String, + /// The result of the query. + #[serde(flatten)] + pub result: StorageResultType, + /// The operation type. + #[serde(rename = "type")] + pub operation_type: ArchiveStorageDiffOperationType, + /// The child trie key if provided. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub child_trie_key: Option, +} + +/// The event generated by the `archive_storageDiff` method. +/// +/// The `archive_storageDiff` can generate the following events: +/// - `storageDiff` event - generated when a `ArchiveStorageDiffResult` is produced. +/// - `storageDiffError` event - generated when an error is produced. +/// - `storageDiffDone` event - generated when the `archive_storageDiff` method completed. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(tag = "event")] +pub enum ArchiveStorageDiffEvent { + /// The `storageDiff` event. + StorageDiff(ArchiveStorageDiffResult), + /// The `storageDiffError` event. + StorageDiffError(ArchiveStorageMethodErr), + /// The `storageDiffDone` event. + StorageDiffDone, +} + +impl ArchiveStorageDiffEvent { + /// Create a new `ArchiveStorageDiffEvent::StorageDiffError` event. + pub fn err(error: String) -> Self { + Self::StorageDiffError(ArchiveStorageMethodErr { error }) + } + + /// Checks if the event is a `StorageDiffDone` event. + pub fn is_done(&self) -> bool { + matches!(self, Self::StorageDiffDone) + } + + /// Checks if the event is a `StorageDiffError` event. + pub fn is_err(&self) -> bool { + matches!(self, Self::StorageDiffError(_)) + } +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn archive_diff_input() { + // Item with Value. + let item = ArchiveStorageDiffItem { + key: "0x1", + return_type: ArchiveStorageDiffType::Value, + child_trie_key: None, + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","returnType":"value"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffItem<&str> = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + + // Item with Hash. + let item = ArchiveStorageDiffItem { + key: "0x1", + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: None, + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","returnType":"hash"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffItem<&str> = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + + // Item with Value and child trie key. + let item = ArchiveStorageDiffItem { + key: "0x1", + return_type: ArchiveStorageDiffType::Value, + child_trie_key: Some("0x2"), + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","returnType":"value","childTrieKey":"0x2"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffItem<&str> = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + + // Item with Hash and child trie key. + let item = ArchiveStorageDiffItem { + key: "0x1", + return_type: ArchiveStorageDiffType::Hash, + child_trie_key: Some("0x2"), + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","returnType":"hash","childTrieKey":"0x2"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffItem<&str> = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + } + + #[test] + fn archive_diff_output() { + // Item with Value. + let item = ArchiveStorageDiffResult { + key: "0x1".into(), + result: StorageResultType::Value("res".into()), + operation_type: ArchiveStorageDiffOperationType::Added, + child_trie_key: None, + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","value":"res","type":"added"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffResult = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + + // Item with Hash. + let item = ArchiveStorageDiffResult { + key: "0x1".into(), + result: StorageResultType::Hash("res".into()), + operation_type: ArchiveStorageDiffOperationType::Modified, + child_trie_key: None, + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","hash":"res","type":"modified"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffResult = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + + // Item with Hash, child trie key and removed. + let item = ArchiveStorageDiffResult { + key: "0x1".into(), + result: StorageResultType::Hash("res".into()), + operation_type: ArchiveStorageDiffOperationType::Deleted, + child_trie_key: Some("0x2".into()), + }; + // Encode + let ser = serde_json::to_string(&item).unwrap(); + let exp = r#"{"key":"0x1","hash":"res","type":"deleted","childTrieKey":"0x2"}"#; + assert_eq!(ser, exp); + // Decode + let dec: ArchiveStorageDiffResult = serde_json::from_str(exp).unwrap(); + assert_eq!(dec, item); + } + #[test] fn storage_result() { // Item with Value. diff --git a/substrate/client/rpc-spec-v2/src/common/storage.rs b/substrate/client/rpc-spec-v2/src/common/storage.rs index 2e24a8da8ca8..673e20b2bc78 100644 --- a/substrate/client/rpc-spec-v2/src/common/storage.rs +++ b/substrate/client/rpc-spec-v2/src/common/storage.rs @@ -248,4 +248,19 @@ where }); Ok((ret, maybe_next_query)) } + + /// Raw iterator over the keys. + pub fn raw_keys_iter( + &self, + hash: Block::Hash, + child_key: Option, + ) -> Result, String> { + let keys_iter = if let Some(child_key) = child_key { + self.client.child_storage_keys(hash, child_key, None, None) + } else { + self.client.storage_keys(hash, None, None) + }; + + keys_iter.map_err(|err| err.to_string()) + } } diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index ac9371a8941b..027a444012af 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -755,6 +755,7 @@ where client.clone(), backend.clone(), genesis_hash, + task_executor.clone(), // Defaults to sensible limits for the `Archive`. sc_rpc_spec_v2::archive::ArchiveConfig::default(), ) From 2ef2723126584dfcd6d2a9272282ee78375dbcd3 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Wed, 27 Nov 2024 21:40:23 +0100 Subject: [PATCH 053/340] chain-spec-guide-runtime: path to wasm blob fixed (#6673) In `chain-spec-guide-runtime` crate's tests, there was assumption that release version of wasm blob exists. This PR uses `chain_spec_guide_runtime::runtime::WASM_BINARY_PATH` const to use correct path to runtime blob. --------- Co-authored-by: GitHub Action --- .../tests/chain_spec_builder_tests.rs | 14 ++++++++------ prdoc/pr_6673.prdoc | 7 +++++++ 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 prdoc/pr_6673.prdoc diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs index c2fe5a6727e6..df400b68f79d 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs @@ -1,8 +1,10 @@ use serde_json::{json, Value}; use std::{process::Command, str}; -const WASM_FILE_PATH: &str = - "../../../../../target/release/wbuild/chain-spec-guide-runtime/chain_spec_guide_runtime.wasm"; +fn wasm_file_path() -> &'static str { + chain_spec_guide_runtime::runtime::WASM_BINARY_PATH + .expect("chain_spec_guide_runtime wasm should exist. qed") +} const CHAIN_SPEC_BUILDER_PATH: &str = "../../../../../target/release/chain-spec-builder"; @@ -26,7 +28,7 @@ fn list_presets() { let output = Command::new(get_chain_spec_builder_path()) .arg("list-presets") .arg("-r") - .arg(WASM_FILE_PATH) + .arg(wasm_file_path()) .output() .expect("Failed to execute command"); @@ -50,7 +52,7 @@ fn get_preset() { let output = Command::new(get_chain_spec_builder_path()) .arg("display-preset") .arg("-r") - .arg(WASM_FILE_PATH) + .arg(wasm_file_path()) .arg("-p") .arg("preset_2") .output() @@ -83,7 +85,7 @@ fn generate_chain_spec() { .arg("/dev/stdout") .arg("create") .arg("-r") - .arg(WASM_FILE_PATH) + .arg(wasm_file_path()) .arg("named-preset") .arg("preset_2") .output() @@ -140,7 +142,7 @@ fn generate_para_chain_spec() { .arg("-p") .arg("1000") .arg("-r") - .arg(WASM_FILE_PATH) + .arg(wasm_file_path()) .arg("named-preset") .arg("preset_2") .output() diff --git a/prdoc/pr_6673.prdoc b/prdoc/pr_6673.prdoc new file mode 100644 index 000000000000..d2ca3c61ff39 --- /dev/null +++ b/prdoc/pr_6673.prdoc @@ -0,0 +1,7 @@ +title: 'chain-spec-guide-runtime: path to wasm blob fixed' +doc: +- audience: Runtime Dev + description: In `chain-spec-guide-runtime` crate's tests, there was assumption that + release version of wasm blob exists. This PR uses `chain_spec_guide_runtime::runtime::WASM_BINARY_PATH` + const to use correct path to runtime blob. +crates: [] From 42d688a28a31e76e58043955d3fbef2c5cb4a677 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 10:28:09 +0200 Subject: [PATCH 054/340] runtime api --- Cargo.lock | 3 + Cargo.toml | 1 + .../inbound-queue-v2/runtime-api/Cargo.toml | 12 ++- .../inbound-queue-v2/runtime-api/src/lib.rs | 7 +- .../pallets/inbound-queue-v2/src/api.rs | 22 ++++-- .../pallets/inbound-queue-v2/src/lib.rs | 10 ++- .../pallets/inbound-queue-v2/src/mock.rs | 2 + .../src/tests/snowbridge_v2.rs | 78 +++++++++++-------- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 3 +- .../src/bridge_to_ethereum_config.rs | 10 ++- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 8 ++ 11 files changed, 104 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7352d583e40..5b76010033b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2824,6 +2824,7 @@ dependencies = [ "serde_json", "snowbridge-beacon-primitives 0.2.0", "snowbridge-core 0.2.0", + "snowbridge-inbound-queue-v2-runtime-api", "snowbridge-outbound-queue-runtime-api 0.2.0", "snowbridge-pallet-ethereum-client 0.2.0", "snowbridge-pallet-inbound-queue 0.2.0", @@ -24792,9 +24793,11 @@ dependencies = [ name = "snowbridge-inbound-queue-v2-runtime-api" version = "0.2.0" dependencies = [ + "frame-support 28.0.0", "snowbridge-core 0.2.0", "snowbridge-router-primitives 0.9.0", "sp-api 26.0.0", + "sp-runtime 31.0.1", "staging-xcm 7.0.0", ] diff --git a/Cargo.toml b/Cargo.toml index 3af053cc74e1..270e4e268d5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1233,6 +1233,7 @@ snowbridge-pallet-inbound-queue = { path = "bridges/snowbridge/pallets/inbound-q snowbridge-pallet-inbound-queue-fixtures = { path = "bridges/snowbridge/pallets/inbound-queue/fixtures", default-features = false } snowbridge-pallet-inbound-queue-fixtures-v2 = { path = "bridges/snowbridge/pallets/inbound-queue-v2/fixtures", default-features = false } snowbridge-pallet-inbound-queue-v2 = { path = "bridges/snowbridge/pallets/inbound-queue-v2", default-features = false } +snowbridge-inbound-queue-v2-runtime-api = { path = "bridges/snowbridge/pallets/inbound-queue-v2/runtime-api", default-features = false } snowbridge-pallet-outbound-queue = { path = "bridges/snowbridge/pallets/outbound-queue", default-features = false } snowbridge-pallet-system = { path = "bridges/snowbridge/pallets/system", default-features = false } snowbridge-router-primitives = { path = "bridges/snowbridge/primitives/router", default-features = false } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml index 9b03370ec891..c9c38a44dd54 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml @@ -15,16 +15,20 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-api = { workspace = true } -snowbridge-core = { workspace = true } -snowbridge-router-primitives = { workspace = true } -xcm = { workspace = true } +frame-support = { workspace = true, default-features = false } +sp-api = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +snowbridge-core = { workspace = true, default-features = false } +snowbridge-router-primitives = { workspace = true, default-features = false } +xcm = { workspace = true, default-features = false } [features] default = ["std"] std = [ + "frame-support/std", "snowbridge-core/std", "snowbridge-router-primitives/std", + "sp-runtime/std", "sp-api/std", "xcm/std", ] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs index 03720b7ca3d2..d899f7477b45 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs @@ -2,14 +2,15 @@ // SPDX-FileCopyrightText: 2023 Snowfork #![cfg_attr(not(feature = "std"), no_std)] -use snowbridge_core::inbound::Proof; +use frame_support::traits::tokens::Balance as BalanceT; use snowbridge_router_primitives::inbound::v2::Message; use xcm::latest::Xcm; +use sp_runtime::DispatchError; sp_api::decl_runtime_apis! { - pub trait InboundQueueApiV2 + pub trait InboundQueueApiV2 where Balance: BalanceT { /// Dry runs the provided message on AH to provide the XCM payload and execution cost. - fn dry_run(message: Message, proof: Proof) -> (Xcm<()>, u128); + fn dry_run(message: Message) -> Result<(Xcm<()>, Balance), DispatchError>; } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index 532a1b453366..241d1372f7ea 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -2,18 +2,30 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Implements the dry-run API. -use crate::{Config, Error, Junction::AccountId32, Location}; -use snowbridge_core::inbound::Proof; +use crate::{weights::WeightInfo, Config, Error, Junction::AccountId32, Location}; +use frame_support::weights::WeightToFee; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; -use sp_core::H256; +use sp_core::{Get, H256}; +use sp_runtime::{DispatchError, Saturating}; use xcm::latest::Xcm; -pub fn dry_run(message: Message, _proof: Proof) -> Result, Error> +pub fn dry_run(message: Message) -> Result<(Xcm<()>, T::Balance), DispatchError> where T: Config, { + // Convert message to XCM let dummy_origin = Location::new(0, AccountId32 { id: H256::zero().into(), network: None }); let xcm = T::MessageConverter::convert(message, dummy_origin) .map_err(|e| Error::::ConvertMessage(e))?; - Ok(xcm) + + // Calculate fee. Consists of the cost of the "submit" extrinsic as well as the XCM execution + // prologue fee (static XCM part of the message that is execution on AH). + let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit()); + let xcm_prologue_fee = T::XcmPrologueFee::get(); + let fee: u128 = weight_fee + .saturating_add(xcm_prologue_fee) + .try_into() + .map_err(|_| Error::::InvalidFee)?; + + Ok((xcm, fee.into())) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 53cc9cc4f8ef..6da0ddcdb673 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -42,7 +42,11 @@ mod test; use codec::{Decode, DecodeAll, Encode}; use envelope::Envelope; use frame_support::{ - traits::fungible::{Inspect, Mutate}, + traits::{ + fungible::{Inspect, Mutate}, + tokens::Balance, + }, + weights::WeightToFee, PalletError, }; use frame_system::{ensure_signed, pallet_prelude::*}; @@ -98,6 +102,8 @@ pub mod pallet { #[pallet::constant] type GatewayAddress: Get; type WeightInfo: WeightInfo; + /// Convert a weight value into deductible balance type. + type WeightToFee: WeightToFee>; /// AssetHub parachain ID. type AssetHubParaId: Get; /// Convert a command from Ethereum to an XCM message. @@ -106,6 +112,8 @@ pub mod pallet { type XcmPrologueFee: Get>; /// Used to burn fees from the origin account (the relayer), which will be teleported to AH. type Token: Mutate + Inspect; + /// Used for the dry run API implementation. + type Balance: Balance + From; /// Used to burn fees. type AssetTransactor: TransactAsset; #[cfg(feature = "runtime-benchmarks")] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index e04ff64a37a4..4c895f85bc8c 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -159,6 +159,7 @@ impl inbound_queue_v2::Config for Test { type Verifier = MockVerifier; type XcmSender = MockXcmSender; type WeightInfo = (); + type WeightToFee = IdentityFee; type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; type MessageConverter = MessageToXcm< @@ -168,6 +169,7 @@ impl inbound_queue_v2::Config for Test { ConstU128, >; type Token = Balances; + type Balance = u128; type XcmPrologueFee = ConstU128; type AssetTransactor = SuccessfulTransactor; #[cfg(feature = "runtime-benchmarks")] diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index b54cf0d3a753..a103195c8fb8 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -13,15 +13,19 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::imports::*; -use bridge_hub_westend_runtime::EthereumInboundQueueV2; +use bridge_hub_westend_runtime::{ + bridge_to_ethereum_config::{CreateAssetCall, CreateAssetDeposit}, + EthereumInboundQueueV2, +}; +use codec::Encode; use frame_support::weights::WeightToFee; -use snowbridge_router_primitives::inbound::v2::{Asset::NativeTokenERC20, Message}; +use hex_literal::hex; +use snowbridge_router_primitives::inbound::{ + v2::{Asset::NativeTokenERC20, Message}, + EthereumLocationsConverterFor, +}; use sp_core::H160; -use sp_core::H256; -use codec::Encode; use sp_runtime::MultiAddress; -use bridge_hub_westend_runtime::bridge_to_ethereum_config::CreateAssetCall; -use bridge_hub_westend_runtime::bridge_to_ethereum_config::CreateAssetDeposit; use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. @@ -117,12 +121,8 @@ fn xcm_prologue_fee() { #[test] fn register_token_xcm() { BridgeHubWestend::execute_with(|| { - //let token: H160 = H160::zero(); // token id placeholder - let token: H160 = H160::random(); // token id placeholder - //let owner: H256 = H256::zero(); // bridge owner placeholder - let owner: H256 = H256::random(); // bridge owner placeholder + let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // token id placeholder println!("token: {:x?}", token); - println!("owner: {:x?}", owner); let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); let dot_asset = Location::new(1, Here); @@ -130,30 +130,40 @@ fn register_token_xcm() { let asset_id = Location::new( 2, - [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], - ); - - let register_token_xcm = - vec![ - PayFees { asset: dot_fee }, - Transact { - origin_kind: OriginKind::Xcm, - call: ( - CreateAssetCall::get(), - asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner.into()), - 1, - ) - .encode() - .into(), - }, - ]; - let message_xcm: Xcm<()> = register_token_xcm.into(); - let versioned_message_xcm = VersionedXcm::V5(message_xcm); + [ + GlobalConsensus(ethereum_network_v5), + AccountKey20 { network: None, key: token.into() }, + ], + ); - let xcm_bytes = versioned_message_xcm.encode(); - let hex_string = hex::encode(xcm_bytes); - println!("register token hex: {:x?}", hex_string); + println!( + "register token mainnet: {:x?}", + get_xcm_hex(1u64, asset_id.clone(), dot_fee.clone()) + ); + println!("register token sepolia: {:x?}", get_xcm_hex(11155111u64, asset_id, dot_fee)); }); } +fn get_xcm_hex(chain_id: u64, asset_id: Location, dot_fee: xcm::prelude::Asset) -> String { + let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + + let register_token_xcm = vec![ + PayFees { asset: dot_fee }, + Transact { + origin_kind: OriginKind::Xcm, + call: ( + CreateAssetCall::get(), + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner.into()), + 1, + ) + .encode() + .into(), + }, + ]; + let message_xcm: Xcm<()> = register_token_xcm.into(); + let versioned_message_xcm = VersionedXcm::V5(message_xcm); + + let xcm_bytes = versioned_message_xcm.encode(); + hex::encode(xcm_bytes) +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 5169a51c3702..29c21c875d13 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -111,12 +111,12 @@ snowbridge-core = { workspace = true } snowbridge-pallet-ethereum-client = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-inbound-queue-v2 = { workspace = true } +snowbridge-inbound-queue-v2-runtime-api = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } - [dev-dependencies] bridge-hub-test-utils = { workspace = true, default-features = true } bridge-runtime-common = { features = ["integrity-test"], workspace = true, default-features = true } @@ -187,6 +187,7 @@ std = [ "snowbridge-beacon-primitives/std", "snowbridge-core/std", "snowbridge-outbound-queue-runtime-api/std", + "snowbridge-inbound-queue-v2-runtime-api/std", "snowbridge-pallet-ethereum-client/std", "snowbridge-pallet-inbound-queue/std", "snowbridge-pallet-outbound-queue/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index a9e3e48e4f50..e1d3cc42364a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -14,14 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . +#[cfg(not(feature = "runtime-benchmarks"))] +use crate::XcmRouter; use crate::{ xcm_config, xcm_config::{TreasuryAccount, UniversalLocation}, Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumSystem, MessageQueue, Runtime, RuntimeEvent, TransactionByteFee, }; -#[cfg(not(feature = "runtime-benchmarks"))] -use crate::XcmRouter; use parachains_common::{AccountId, Balance}; use snowbridge_beacon_primitives::{Fork, ForkVersions}; use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards}; @@ -73,8 +73,8 @@ parameter_types! { pub EthereumUniversalLocation: InteriorLocation = [GlobalConsensus(EthereumNetwork::get())].into(); } -/// The XCM execution fee on AH for the static part of the XCM message (not the user provided parts). -/// Calculated with integration test snowbridge_v2::xcm_prologue_fee +/// The XCM execution fee on AH for the static part of the XCM message (not the user provided +/// parts). Calculated with integration test snowbridge_v2::xcm_prologue_fee const XCM_PROLOGUE_FEE: u128 = 67_652_000_000; impl snowbridge_pallet_inbound_queue::Config for Runtime { @@ -118,8 +118,10 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type Helper = Runtime; type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue_v2::WeightInfo; + type WeightToFee = WeightToFee; type AssetHubParaId = ConstU32<1000>; type Token = Balances; + type Balance = Balance; type XcmPrologueFee = ConstU128; type AssetTransactor = ::AssetTransactor; type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm< diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 8f8f1c93bd6d..fe43ba51c7f2 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -51,6 +51,8 @@ use sp_runtime::{ ApplyExtrinsicResult, }; +use snowbridge_router_primitives::inbound::v2::Message; +use sp_runtime::DispatchError; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; @@ -901,6 +903,12 @@ impl_runtime_apis! { } } + impl snowbridge_inbound_queue_v2_runtime_api::InboundQueueApiV2 for Runtime { + fn dry_run(message: Message) -> Result<(Xcm<()>, Balance), DispatchError> { + snowbridge_pallet_inbound_queue_v2::api::dry_run::(message) + } + } + impl snowbridge_system_runtime_api::ControlApi for Runtime { fn agent_id(location: VersionedLocation) -> Option { snowbridge_pallet_system::api::agent_id::(location) From 92e341963f9ae18a0acbd60e2b9f1e1068c894a7 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 10:29:34 +0200 Subject: [PATCH 055/340] adds comment --- .../bridge-hub-westend/src/bridge_to_ethereum_config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index e1d3cc42364a..4d236b801384 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -74,7 +74,7 @@ parameter_types! { } /// The XCM execution fee on AH for the static part of the XCM message (not the user provided -/// parts). Calculated with integration test snowbridge_v2::xcm_prologue_fee +/// xcm). Teleported from BH. Calculated with integration test snowbridge_v2::xcm_prologue_fee. const XCM_PROLOGUE_FEE: u128 = 67_652_000_000; impl snowbridge_pallet_inbound_queue::Config for Runtime { From 51c3e95a05c528b3869ad267aeb5c356551e38db Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Thu, 28 Nov 2024 11:16:06 +0200 Subject: [PATCH 056/340] chore: Update litep2p to v0.8.2 (#6677) This includes a critical fix for debug release versions of litep2p (which are running in Kusama as validators). While at it, have stopped the oncall pain of alerts around `incoming_connections_total`. We can rethink the metric expose of litep2p in Q1. ## [0.8.2] - 2024-11-27 This release ensures that the provided peer identity is verified at the crypto/noise protocol level, enhancing security and preventing potential misuses. The release also includes a fix that caused `TransportService` component to panic on debug builds. ### Fixed - req-resp: Fix panic on connection closed for substream open failure ([#291](https://github.com/paritytech/litep2p/pull/291)) - crypto/noise: Verify crypto/noise signature payload ([#278](https://github.com/paritytech/litep2p/pull/278)) ### Changed - transport_service/logs: Provide less details for trace logs ([#292](https://github.com/paritytech/litep2p/pull/292)) ## Testing Done This has been extensively tested in Kusama on all validators, that are now running litep2p. Deployed PR: https://github.com/paritytech/polkadot-sdk/pull/6638 ### Litep2p Dashboards ![Screenshot 2024-11-26 at 19 19 41](https://github.com/user-attachments/assets/e00b2b2b-7e64-4d96-ab26-165e2b8d0dc9) ### Libp2p vs Litep2p CPU usage After deploying litep2p we have reduced CPU usage from around 300-400% to 200%, this is a significant boost in performance, freeing resources for other subcomponents to function more optimally. ![image(1)](https://github.com/user-attachments/assets/fa793df5-4d58-4601-963d-246e56dd2a26) cc @paritytech/sdk-node --------- Signed-off-by: Alexandru Vasile Co-authored-by: GitHub Action --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- prdoc/pr_6677.prdoc | 11 +++++++++++ substrate/client/network/src/litep2p/mod.rs | 11 ++++++++++- 4 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_6677.prdoc diff --git a/Cargo.lock b/Cargo.lock index 12e642bc9d06..84477cd05416 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10216,9 +10216,9 @@ dependencies = [ [[package]] name = "litep2p" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b67484b8ac41e1cfdf012f65fa81e88c2ef5f8a7d6dec0e2678c2d06dc04530" +checksum = "569e7dbec8a0d4b08d30f4942cd579cfe8db5d3f83f8604abe61697c38d17e73" dependencies = [ "async-trait", "bs58", diff --git a/Cargo.toml b/Cargo.toml index b1a52712e736..964964908a9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -848,7 +848,7 @@ linked-hash-map = { version = "0.5.4" } linked_hash_set = { version = "0.1.4" } linregress = { version = "0.5.1" } lite-json = { version = "0.2.0", default-features = false } -litep2p = { version = "0.8.1", features = ["websocket"] } +litep2p = { version = "0.8.2", features = ["websocket"] } log = { version = "0.4.22", default-features = false } macro_magic = { version = "0.5.1" } maplit = { version = "1.0.2" } diff --git a/prdoc/pr_6677.prdoc b/prdoc/pr_6677.prdoc new file mode 100644 index 000000000000..c6766889e68d --- /dev/null +++ b/prdoc/pr_6677.prdoc @@ -0,0 +1,11 @@ +title: 'chore: Update litep2p to v0.8.2' +doc: +- audience: Node Dev + description: |- + This includes a critical fix for debug release versions of litep2p (which are running in Kusama as validators). + + While at it, have stopped the oncall pain of alerts around `incoming_connections_total`. We can rethink the metric expose of litep2p in Q1. + +crates: +- name: sc-network + bump: minor diff --git a/substrate/client/network/src/litep2p/mod.rs b/substrate/client/network/src/litep2p/mod.rs index 10cf9f4da36d..6d3575fc2b6b 100644 --- a/substrate/client/network/src/litep2p/mod.rs +++ b/substrate/client/network/src/litep2p/mod.rs @@ -986,7 +986,15 @@ impl NetworkBackend for Litep2pNetworkBac let direction = match endpoint { Endpoint::Dialer { .. } => "out", - Endpoint::Listener { .. } => "in", + Endpoint::Listener { .. } => { + // Increment incoming connections counter. + // + // Note: For litep2p these are represented by established negotiated connections, + // while for libp2p (legacy) these represent not-yet-negotiated connections. + metrics.incoming_connections_total.inc(); + + "in" + }, }; metrics.connections_opened_total.with_label_values(&[direction]).inc(); @@ -1058,6 +1066,7 @@ impl NetworkBackend for Litep2pNetworkBac NegotiationError::ParseError(_) => "parse-error", NegotiationError::IoError(_) => "io-error", NegotiationError::WebSocket(_) => "webscoket-error", + NegotiationError::BadSignature => "bad-signature", } }; From 37ecd53dfcd643aadf6e34803ec15bd36616b3cf Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 11:23:33 +0200 Subject: [PATCH 057/340] update test --- .../src/tests/snowbridge_v2.rs | 52 +++++++++++-------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index a103195c8fb8..73a3250566b3 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -121,33 +121,43 @@ fn xcm_prologue_fee() { #[test] fn register_token_xcm() { BridgeHubWestend::execute_with(|| { - let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // token id placeholder - println!("token: {:x?}", token); - - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let dot_asset = Location::new(1, Here); - let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); - - let asset_id = Location::new( - 2, - [ - GlobalConsensus(ethereum_network_v5), - AccountKey20 { network: None, key: token.into() }, - ], - ); - - println!( - "register token mainnet: {:x?}", - get_xcm_hex(1u64, asset_id.clone(), dot_fee.clone()) - ); - println!("register token sepolia: {:x?}", get_xcm_hex(11155111u64, asset_id, dot_fee)); + println!("register token mainnet: {:x?}", get_xcm_hex(1u64)); + println!("===============================",); + println!("register token sepolia: {:x?}", get_xcm_hex(11155111u64)); }); } -fn get_xcm_hex(chain_id: u64, asset_id: Location, dot_fee: xcm::prelude::Asset) -> String { +fn get_xcm_hex(chain_id: u64) -> String { let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + let weth_token_id: H160 = hex!("be68fc2d8249eb60bfcf0e71d5a0d2f2e292c4ed").into(); // TODO insert token id + let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // token id placeholder + let weth_amount = 300_000_000_000_000u128; + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let dot_asset = Location::new(1, Here); + let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + + println!("register token id: {:x?}", token); + println!("weth token id: {:x?}", weth_token_id); + println!("weth_amount: {:x?}", hex::encode(weth_amount.encode())); + println!("dot asset: {:x?}", hex::encode(dot_fee.encode())); + + let weth_asset = Location::new( + 2, + [ + GlobalConsensus(ethereum_network_v5), + AccountKey20 { network: None, key: weth_token_id.into() }, + ], + ); + let weth_fee: xcm::prelude::Asset = (weth_asset, weth_amount).into(); // TODO replace Weth fee acmount + + let asset_id = Location::new( + 2, + [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], + ); let register_token_xcm = vec![ + ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, PayFees { asset: dot_fee }, Transact { origin_kind: OriginKind::Xcm, From 9ec8009c5a8fdf89499fcd2a40df0292d3950efa Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 28 Nov 2024 12:41:09 +0100 Subject: [PATCH 058/340] Multiple instances for pallet-bridge-relayers fix (#6684) Previously, we added multi-instance pallet support for `pallet-bridge-relayers`, but missed fixing it in this one place. --------- Co-authored-by: GitHub Action Co-authored-by: Adrian Catangiu --- bridges/modules/relayers/src/extension/mod.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bridges/modules/relayers/src/extension/mod.rs b/bridges/modules/relayers/src/extension/mod.rs index 34d280d26d6e..d562ed9bcd0e 100644 --- a/bridges/modules/relayers/src/extension/mod.rs +++ b/bridges/modules/relayers/src/extension/mod.rs @@ -129,7 +129,7 @@ pub struct BridgeRelayersTransactionExtension( impl BridgeRelayersTransactionExtension where Self: 'static + Send + Sync, - R: RelayersConfig + R: RelayersConfig + BridgeMessagesConfig + TransactionPaymentConfig, C: ExtensionConfig, @@ -250,7 +250,7 @@ where // let's also replace the weight of slashing relayer with the weight of rewarding relayer if call_info.is_receive_messages_proof_call() { post_info_weight = post_info_weight.saturating_sub( - ::WeightInfo::extra_weight_of_successful_receive_messages_proof_call(), + >::WeightInfo::extra_weight_of_successful_receive_messages_proof_call(), ); } @@ -278,7 +278,7 @@ impl TransactionExtension for BridgeRelayersTransactionExtension where Self: 'static + Send + Sync, - R: RelayersConfig + R: RelayersConfig + BridgeMessagesConfig + TransactionPaymentConfig, C: ExtensionConfig, @@ -326,7 +326,9 @@ where }; // we only boost priority if relayer has staked required balance - if !RelayersPallet::::is_registration_active(&data.relayer) { + if !RelayersPallet::::is_registration_active( + &data.relayer, + ) { return Ok((Default::default(), Some(data), origin)) } @@ -382,7 +384,11 @@ where match call_result { RelayerAccountAction::None => (), RelayerAccountAction::Reward(relayer, reward_account, reward) => { - RelayersPallet::::register_relayer_reward(reward_account, &relayer, reward); + RelayersPallet::::register_relayer_reward( + reward_account, + &relayer, + reward, + ); log::trace!( target: LOG_TARGET, @@ -394,7 +400,7 @@ where ); }, RelayerAccountAction::Slash(relayer, slash_account) => - RelayersPallet::::slash_and_deregister( + RelayersPallet::::slash_and_deregister( &relayer, ExplicitOrAccountParams::Params(slash_account), ), From 23369acd34411cfae924718a2834df1a7ea8bc05 Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Thu, 28 Nov 2024 15:27:49 +0100 Subject: [PATCH 059/340] Migrating pallet-xcm-benchmarks to V2 (#6618) # Description Migrated pallet-xcm-benchmarks to benchmaking syntax V2 This is part of #6202 --------- Co-authored-by: Giuseppe Re --- .../src/generic/benchmarking.rs | 652 +++++++++++------- 1 file changed, 394 insertions(+), 258 deletions(-) diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs index 87bf27e4ff18..f4836b7cdde1 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs @@ -13,12 +13,13 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +#![cfg(feature = "runtime-benchmarks")] use super::*; use crate::{account_and_location, new_executor, EnsureDelivery, XcmCallOf}; use alloc::{vec, vec::Vec}; use codec::Encode; -use frame_benchmarking::{benchmarks, BenchmarkError}; +use frame_benchmarking::v2::*; use frame_support::traits::fungible::Inspect; use xcm::{ latest::{prelude::*, MaxDispatchErrorLen, MaybeErrorCode, Weight, MAX_ITEMS_IN_ASSETS}, @@ -29,16 +30,21 @@ use xcm_executor::{ ExecutorError, FeesMode, }; -benchmarks! { - report_holding { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn report_holding() -> Result<(), BenchmarkError> { let (sender_account, sender_location) = account_and_location::(1); let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &sender_location, - &destination, - FeeReason::Report, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &sender_location, + &destination, + FeeReason::Report, + ); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); // generate holding and add possible required fees @@ -64,21 +70,33 @@ benchmarks! { query_id: Default::default(), max_weight: Weight::MAX, }, - // Worst case is looking through all holdings for every asset explicitly - respecting the limit `MAX_ITEMS_IN_ASSETS`. - assets: Definite(holding.into_inner().into_iter().take(MAX_ITEMS_IN_ASSETS).collect::>().into()), + // Worst case is looking through all holdings for every asset explicitly - respecting + // the limit `MAX_ITEMS_IN_ASSETS`. + assets: Definite( + holding + .into_inner() + .into_iter() + .take(MAX_ITEMS_IN_ASSETS) + .collect::>() + .into(), + ), }; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); + + Ok(()) } // This benchmark does not use any additional orders or instructions. This should be managed // by the `deep` and `shallow` implementation. - buy_execution { + #[benchmark] + fn buy_execution() -> Result<(), BenchmarkError> { let holding = T::worst_case_holding(0).into(); let mut executor = new_executor::(Default::default()); @@ -92,13 +110,16 @@ benchmarks! { }; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } + Ok(()) } - pay_fees { + #[benchmark] + fn pay_fees() -> Result<(), BenchmarkError> { let holding = T::worst_case_holding(0).into(); let mut executor = new_executor::(Default::default()); @@ -111,40 +132,53 @@ benchmarks! { let instruction = Instruction::>::PayFees { asset: fee_asset }; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify {} + #[block] + { + executor.bench_process(xcm)?; + } + Ok(()) + } - set_asset_claimer { + #[benchmark] + fn set_asset_claimer() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let (_, sender_location) = account_and_location::(1); - let instruction = Instruction::SetAssetClaimer{ location:sender_location.clone() }; + let instruction = Instruction::SetAssetClaimer { location: sender_location.clone() }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.asset_claimer(), Some(sender_location.clone())); + + Ok(()) } - query_response { + #[benchmark] + fn query_response() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let (query_id, response) = T::worst_case_response(); let max_weight = Weight::MAX; let querier: Option = Some(Here.into()); let instruction = Instruction::QueryResponse { query_id, response, max_weight, querier }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + + #[block] + { + executor.bench_process(xcm)?; + } // The assert above is enough to show this XCM succeeded + + Ok(()) } // We don't care about the call itself, since that is accounted for in the weight parameter // and included in the final weight calculation. So this is just the overhead of submitting // a noop call. - transact { + #[benchmark] + fn transact() -> Result<(), BenchmarkError> { let (origin, noop_call) = T::transact_origin_and_runtime_call()?; let mut executor = new_executor::(origin); let double_encoded_noop_call: DoubleEncoded<_> = noop_call.encode().into(); @@ -154,119 +188,145 @@ benchmarks! { call: double_encoded_noop_call, }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // TODO Make the assertion configurable? + + Ok(()) } - refund_surplus { + #[benchmark] + fn refund_surplus() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let holding_assets = T::worst_case_holding(1); // We can already buy execution since we'll load the holding register manually let asset_for_fees = T::fee_asset().unwrap(); - let previous_xcm = Xcm(vec![BuyExecution { fees: asset_for_fees, weight_limit: Limited(Weight::from_parts(1337, 1337)) }]); + let previous_xcm = Xcm(vec![BuyExecution { + fees: asset_for_fees, + weight_limit: Limited(Weight::from_parts(1337, 1337)), + }]); executor.set_holding(holding_assets.into()); executor.set_total_surplus(Weight::from_parts(1337, 1337)); executor.set_total_refunded(Weight::zero()); - executor.bench_process(previous_xcm).expect("Holding has been loaded, so we can buy execution here"); + executor + .bench_process(previous_xcm) + .expect("Holding has been loaded, so we can buy execution here"); let instruction = Instruction::>::RefundSurplus; let xcm = Xcm(vec![instruction]); - } : { - let result = executor.bench_process(xcm)?; - } verify { + #[block] + { + let _result = executor.bench_process(xcm)?; + } assert_eq!(executor.total_surplus(), &Weight::from_parts(1337, 1337)); assert_eq!(executor.total_refunded(), &Weight::from_parts(1337, 1337)); + + Ok(()) } - set_error_handler { + #[benchmark] + fn set_error_handler() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let instruction = Instruction::>::SetErrorHandler(Xcm(vec![])); let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.error_handler(), &Xcm(vec![])); + + Ok(()) } - set_appendix { + #[benchmark] + fn set_appendix() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let appendix = Xcm(vec![]); let instruction = Instruction::>::SetAppendix(appendix); let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.appendix(), &Xcm(vec![])); + Ok(()) } - clear_error { + #[benchmark] + fn clear_error() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); executor.set_error(Some((5u32, XcmError::Overflow))); let instruction = Instruction::>::ClearError; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { - assert!(executor.error().is_none()) + #[block] + { + executor.bench_process(xcm)?; + } + assert!(executor.error().is_none()); + Ok(()) } - descend_origin { + #[benchmark] + fn descend_origin() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let who = Junctions::from([OnlyChild, OnlyChild]); let instruction = Instruction::DescendOrigin(who.clone()); let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { - assert_eq!( - executor.origin(), - &Some(Location { - parents: 0, - interior: who, - }), - ); + #[block] + { + executor.bench_process(xcm)?; + } + assert_eq!(executor.origin(), &Some(Location { parents: 0, interior: who }),); + + Ok(()) } - execute_with_origin { + #[benchmark] + fn execute_with_origin() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let who: Junctions = Junctions::from([AccountId32 { id: [0u8; 32], network: None }]); - let instruction = Instruction::ExecuteWithOrigin { descendant_origin: Some(who.clone()), xcm: Xcm(vec![]) }; + let instruction = Instruction::ExecuteWithOrigin { + descendant_origin: Some(who.clone()), + xcm: Xcm(vec![]), + }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { - assert_eq!( - executor.origin(), - &Some(Location { - parents: 0, - interior: Here, - }), - ); + #[block] + { + executor.bench_process(xcm)?; + } + assert_eq!(executor.origin(), &Some(Location { parents: 0, interior: Here }),); + + Ok(()) } - clear_origin { + #[benchmark] + fn clear_origin() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let instruction = Instruction::ClearOrigin; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.origin(), &None); + Ok(()) } - report_error { + #[benchmark] + fn report_error() -> Result<(), BenchmarkError> { let (sender_account, sender_location) = account_and_location::(1); let query_id = Default::default(); let max_weight = Default::default(); let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &sender_location, - &destination, - FeeReason::Report, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &sender_location, + &destination, + FeeReason::Report, + ); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); let mut executor = new_executor::(sender_location); @@ -278,18 +338,21 @@ benchmarks! { } executor.set_error(Some((0u32, XcmError::Unimplemented))); - let instruction = Instruction::ReportError(QueryResponseInfo { - query_id, destination, max_weight - }); + let instruction = + Instruction::ReportError(QueryResponseInfo { query_id, destination, max_weight }); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); + + Ok(()) } - claim_asset { + #[benchmark] + fn claim_asset() -> Result<(), BenchmarkError> { use xcm_executor::traits::DropAssets; let (origin, ticket, assets) = T::claimable_asset()?; @@ -298,11 +361,7 @@ benchmarks! { ::AssetTrap::drop_assets( &origin, assets.clone().into(), - &XcmContext { - origin: Some(origin.clone()), - message_id: [0; 32], - topic: None, - }, + &XcmContext { origin: Some(origin.clone()), message_id: [0; 32], topic: None }, ); // Assets should be in the trap now. @@ -310,28 +369,32 @@ benchmarks! { let mut executor = new_executor::(origin); let instruction = Instruction::ClaimAsset { assets: assets.clone(), ticket }; let xcm = Xcm(vec![instruction]); - } :{ - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert!(executor.holding().ensure_contains(&assets).is_ok()); + Ok(()) } - trap { + #[benchmark] + fn trap() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let instruction = Instruction::Trap(10); let xcm = Xcm(vec![instruction]); // In order to access result in the verification below, it needs to be defined here. - let mut _result = Ok(()); - } : { - _result = executor.bench_process(xcm); - } verify { - assert!(matches!(_result, Err(ExecutorError { - xcm_error: XcmError::Trap(10), - .. - }))); + let result; + #[block] + { + result = executor.bench_process(xcm); + } + assert!(matches!(result, Err(ExecutorError { xcm_error: XcmError::Trap(10), .. }))); + + Ok(()) } - subscribe_version { + #[benchmark] + fn subscribe_version() -> Result<(), BenchmarkError> { use xcm_executor::traits::VersionChangeNotifier; let origin = T::subscribe_origin()?; let query_id = Default::default(); @@ -339,13 +402,18 @@ benchmarks! { let mut executor = new_executor::(origin.clone()); let instruction = Instruction::SubscribeVersion { query_id, max_response_weight }; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { - assert!(::SubscriptionService::is_subscribed(&origin)); + #[block] + { + executor.bench_process(xcm)?; + } + assert!(::SubscriptionService::is_subscribed( + &origin + )); + Ok(()) } - unsubscribe_version { + #[benchmark] + fn unsubscribe_version() -> Result<(), BenchmarkError> { use xcm_executor::traits::VersionChangeNotifier; // First we need to subscribe to notifications. let (origin, _) = T::transact_origin_and_runtime_call()?; @@ -355,24 +423,28 @@ benchmarks! { &origin, query_id, max_response_weight, - &XcmContext { - origin: Some(origin.clone()), - message_id: [0; 32], - topic: None, - }, - ).map_err(|_| "Could not start subscription")?; - assert!(::SubscriptionService::is_subscribed(&origin)); + &XcmContext { origin: Some(origin.clone()), message_id: [0; 32], topic: None }, + ) + .map_err(|_| "Could not start subscription")?; + assert!(::SubscriptionService::is_subscribed( + &origin + )); let mut executor = new_executor::(origin.clone()); let instruction = Instruction::UnsubscribeVersion; let xcm = Xcm(vec![instruction]); - } : { - executor.bench_process(xcm)?; - } verify { - assert!(!::SubscriptionService::is_subscribed(&origin)); + #[block] + { + executor.bench_process(xcm)?; + } + assert!(!::SubscriptionService::is_subscribed( + &origin + )); + Ok(()) } - burn_asset { + #[benchmark] + fn burn_asset() -> Result<(), BenchmarkError> { let holding = T::worst_case_holding(0); let assets = holding.clone(); @@ -381,13 +453,16 @@ benchmarks! { let instruction = Instruction::BurnAsset(assets.into()); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert!(executor.holding().is_empty()); + Ok(()) } - expect_asset { + #[benchmark] + fn expect_asset() -> Result<(), BenchmarkError> { let holding = T::worst_case_holding(0); let assets = holding.clone(); @@ -396,71 +471,86 @@ benchmarks! { let instruction = Instruction::ExpectAsset(assets.into()); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // `execute` completing successfully is as good as we can check. + + Ok(()) } - expect_origin { + #[benchmark] + fn expect_origin() -> Result<(), BenchmarkError> { let expected_origin = Parent.into(); let mut executor = new_executor::(Default::default()); let instruction = Instruction::ExpectOrigin(Some(expected_origin)); let xcm = Xcm(vec![instruction]); let mut _result = Ok(()); - }: { - _result = executor.bench_process(xcm); - } verify { - assert!(matches!(_result, Err(ExecutorError { - xcm_error: XcmError::ExpectationFalse, - .. - }))); + #[block] + { + _result = executor.bench_process(xcm); + } + assert!(matches!( + _result, + Err(ExecutorError { xcm_error: XcmError::ExpectationFalse, .. }) + )); + + Ok(()) } - expect_error { + #[benchmark] + fn expect_error() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); executor.set_error(Some((3u32, XcmError::Overflow))); let instruction = Instruction::ExpectError(None); let xcm = Xcm(vec![instruction]); let mut _result = Ok(()); - }: { - _result = executor.bench_process(xcm); - } verify { - assert!(matches!(_result, Err(ExecutorError { - xcm_error: XcmError::ExpectationFalse, - .. - }))); + #[block] + { + _result = executor.bench_process(xcm); + } + assert!(matches!( + _result, + Err(ExecutorError { xcm_error: XcmError::ExpectationFalse, .. }) + )); + + Ok(()) } - expect_transact_status { + #[benchmark] + fn expect_transact_status() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); - let worst_error = || -> MaybeErrorCode { - vec![0; MaxDispatchErrorLen::get() as usize].into() - }; + let worst_error = + || -> MaybeErrorCode { vec![0; MaxDispatchErrorLen::get() as usize].into() }; executor.set_transact_status(worst_error()); let instruction = Instruction::ExpectTransactStatus(worst_error()); let xcm = Xcm(vec![instruction]); let mut _result = Ok(()); - }: { - _result = executor.bench_process(xcm); - } verify { + #[block] + { + _result = executor.bench_process(xcm); + } assert!(matches!(_result, Ok(..))); + Ok(()) } - query_pallet { + #[benchmark] + fn query_pallet() -> Result<(), BenchmarkError> { let (sender_account, sender_location) = account_and_location::(1); let query_id = Default::default(); let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; let max_weight = Default::default(); - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &sender_location, - &destination, - FeeReason::QueryPallet, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &sender_location, + &destination, + FeeReason::QueryPallet, + ); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); let mut executor = new_executor::(sender_location); if let Some(expected_fees_mode) = expected_fees_mode { @@ -476,15 +566,19 @@ benchmarks! { response_info: QueryResponseInfo { destination, query_id, max_weight }, }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + + Ok(()) } - expect_pallet { + #[benchmark] + fn expect_pallet() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let valid_pallet = T::valid_pallet(); let instruction = Instruction::ExpectPallet { @@ -495,23 +589,27 @@ benchmarks! { min_crate_minor: valid_pallet.crate_version.minor.into(), }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // the execution succeeding is all we need to verify this xcm was successful + Ok(()) } - report_transact_status { + #[benchmark] + fn report_transact_status() -> Result<(), BenchmarkError> { let (sender_account, sender_location) = account_and_location::(1); let query_id = Default::default(); let destination = T::valid_destination().map_err(|_| BenchmarkError::Skip)?; let max_weight = Default::default(); - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &sender_location, - &destination, - FeeReason::Report, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &sender_location, + &destination, + FeeReason::Report, + ); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); let mut executor = new_executor::(sender_location); @@ -529,84 +627,102 @@ benchmarks! { max_weight, }); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + Ok(()) } - clear_transact_status { + #[benchmark] + fn clear_transact_status() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); executor.set_transact_status(b"MyError".to_vec().into()); let instruction = Instruction::ClearTransactStatus; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.transact_status(), &MaybeErrorCode::Success); + Ok(()) } - set_topic { + #[benchmark] + fn set_topic() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let instruction = Instruction::SetTopic([1; 32]); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.topic(), &Some([1; 32])); + Ok(()) } - clear_topic { + #[benchmark] + fn clear_topic() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); executor.set_topic(Some([2; 32])); let instruction = Instruction::ClearTopic; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.topic(), &None); + Ok(()) } - exchange_asset { + #[benchmark] + fn exchange_asset() -> Result<(), BenchmarkError> { let (give, want) = T::worst_case_asset_exchange().map_err(|_| BenchmarkError::Skip)?; let assets = give.clone(); let mut executor = new_executor::(Default::default()); executor.set_holding(give.into()); - let instruction = Instruction::ExchangeAsset { - give: assets.into(), - want: want.clone(), - maximal: true, - }; + let instruction = + Instruction::ExchangeAsset { give: assets.into(), want: want.clone(), maximal: true }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.holding(), &want.into()); + Ok(()) } - universal_origin { + #[benchmark] + fn universal_origin() -> Result<(), BenchmarkError> { let (origin, alias) = T::universal_alias().map_err(|_| BenchmarkError::Skip)?; let mut executor = new_executor::(origin); let instruction = Instruction::UniversalOrigin(alias); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } use frame_support::traits::Get; let universal_location = ::UniversalLocation::get(); - assert_eq!(executor.origin(), &Some(Junctions::from([alias]).relative_to(&universal_location))); + assert_eq!( + executor.origin(), + &Some(Junctions::from([alias]).relative_to(&universal_location)) + ); + + Ok(()) } - export_message { - let x in 1 .. 1000; + #[benchmark] + fn export_message(x: Linear<1, 1000>) -> Result<(), BenchmarkError> { // The `inner_xcm` influences `ExportMessage` total weight based on // `inner_xcm.encoded_size()`, so for this benchmark use smallest encoded instruction // to approximate weight per "unit" of encoded size; then actual weight can be estimated @@ -616,11 +732,12 @@ benchmarks! { // Get `origin`, `network` and `destination` from configured runtime. let (origin, network, destination) = T::export_message_origin_and_destination()?; - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &origin, - &destination.clone().into(), - FeeReason::Export { network, destination: destination.clone() }, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &origin, + &destination.clone().into(), + FeeReason::Export { network, destination: destination.clone() }, + ); let sender_account = T::AccountIdConverter::convert_location(&origin).unwrap(); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); @@ -631,37 +748,39 @@ benchmarks! { if let Some(expected_assets_in_holding) = expected_assets_in_holding { executor.set_holding(expected_assets_in_holding.into()); } - let xcm = Xcm(vec![ExportMessage { - network, destination: destination.clone(), xcm: inner_xcm, - }]); - }: { - executor.bench_process(xcm)?; - } verify { + let xcm = + Xcm(vec![ExportMessage { network, destination: destination.clone(), xcm: inner_xcm }]); + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + Ok(()) } - set_fees_mode { + #[benchmark] + fn set_fees_mode() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); executor.set_fees_mode(FeesMode { jit_withdraw: false }); let instruction = Instruction::SetFeesMode { jit_withdraw: true }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.fees_mode(), &FeesMode { jit_withdraw: true }); + Ok(()) } - lock_asset { + #[benchmark] + fn lock_asset() -> Result<(), BenchmarkError> { let (unlocker, owner, asset) = T::unlockable_asset()?; - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &owner, - &unlocker, - FeeReason::LockAsset, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery(&owner, &unlocker, FeeReason::LockAsset); let sender_account = T::AccountIdConverter::convert_location(&owner).unwrap(); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); @@ -681,15 +800,18 @@ benchmarks! { let instruction = Instruction::LockAsset { asset, unlocker }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + Ok(()) } - unlock_asset { + #[benchmark] + fn unlock_asset() -> Result<(), BenchmarkError> { use xcm_executor::traits::{AssetLock, Enact}; let (unlocker, owner, asset) = T::unlockable_asset()?; @@ -709,13 +831,15 @@ benchmarks! { // ... then unlock them with the UnlockAsset instruction. let instruction = Instruction::UnlockAsset { asset, target: owner }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { - + #[block] + { + executor.bench_process(xcm)?; + } + Ok(()) } - note_unlockable { + #[benchmark] + fn note_unlockable() -> Result<(), BenchmarkError> { use xcm_executor::traits::{AssetLock, Enact}; let (unlocker, owner, asset) = T::unlockable_asset()?; @@ -735,13 +859,15 @@ benchmarks! { // ... then note them as unlockable with the NoteUnlockable instruction. let instruction = Instruction::NoteUnlockable { asset, owner }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { - + #[block] + { + executor.bench_process(xcm)?; + } + Ok(()) } - request_unlock { + #[benchmark] + fn request_unlock() -> Result<(), BenchmarkError> { use xcm_executor::traits::{AssetLock, Enact}; let (locker, owner, asset) = T::unlockable_asset()?; @@ -756,11 +882,12 @@ benchmarks! { .enact() .map_err(|_| BenchmarkError::Skip)?; - let (expected_fees_mode, expected_assets_in_holding) = T::DeliveryHelper::ensure_successful_delivery( - &owner, - &locker, - FeeReason::RequestUnlock, - ); + let (expected_fees_mode, expected_assets_in_holding) = + T::DeliveryHelper::ensure_successful_delivery( + &owner, + &locker, + FeeReason::RequestUnlock, + ); let sender_account = T::AccountIdConverter::convert_location(&owner).unwrap(); let sender_account_balance_before = T::TransactAsset::balance(&sender_account); @@ -774,15 +901,18 @@ benchmarks! { } let instruction = Instruction::RequestUnlock { asset, locker }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } // Check we charged the delivery fees assert!(T::TransactAsset::balance(&sender_account) <= sender_account_balance_before); // TODO: Potentially add new trait to XcmSender to detect a queued outgoing message. #4426 + Ok(()) } - unpaid_execution { + #[benchmark] + fn unpaid_execution() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); executor.set_origin(Some(Here.into())); @@ -792,21 +922,27 @@ benchmarks! { }; let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; + #[block] + { + executor.bench_process(xcm)?; + } + Ok(()) } - alias_origin { + #[benchmark] + fn alias_origin() -> Result<(), BenchmarkError> { let (origin, target) = T::alias_origin().map_err(|_| BenchmarkError::Skip)?; let mut executor = new_executor::(origin); let instruction = Instruction::AliasOrigin(target.clone()); let xcm = Xcm(vec![instruction]); - }: { - executor.bench_process(xcm)?; - } verify { + #[block] + { + executor.bench_process(xcm)?; + } assert_eq!(executor.origin(), &Some(target)); + Ok(()) } impl_benchmark_test_suite!( From fdb264d0df6fdbed32f001ba43c3282a01dd3d65 Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Thu, 28 Nov 2024 17:18:30 +0100 Subject: [PATCH 060/340] Migrating pallet-state-trie-migration to benchmarking V2 (#6617) # Description Migrated pallet-state-trie-migration benchmarking to the new benchmarking syntax v2. This is part of #6202 Co-authored-by: Shawn Tabrizi Co-authored-by: Giuseppe Re --- .../frame/state-trie-migration/src/lib.rs | 176 +++++++++++------- 1 file changed, 108 insertions(+), 68 deletions(-) diff --git a/substrate/frame/state-trie-migration/src/lib.rs b/substrate/frame/state-trie-migration/src/lib.rs index 3fe5abb81031..61323b70b33d 100644 --- a/substrate/frame/state-trie-migration/src/lib.rs +++ b/substrate/frame/state-trie-migration/src/lib.rs @@ -249,13 +249,13 @@ pub mod pallet { if limits.item.is_zero() || limits.size.is_zero() { // handle this minor edge case, else we would call `migrate_tick` at least once. log!(warn, "limits are zero. stopping"); - return Ok(()) + return Ok(()); } while !self.exhausted(limits) && !self.finished() { if let Err(e) = self.migrate_tick() { log!(error, "migrate_until_exhaustion failed: {:?}", e); - return Err(e) + return Err(e); } } @@ -332,7 +332,7 @@ pub mod pallet { _ => { // defensive: there must be an ongoing top migration. frame_support::defensive!("cannot migrate child key."); - return Ok(()) + return Ok(()); }, }; @@ -374,7 +374,7 @@ pub mod pallet { Progress::Complete => { // defensive: there must be an ongoing top migration. frame_support::defensive!("cannot migrate top key."); - return Ok(()) + return Ok(()); }, }; @@ -669,7 +669,7 @@ pub mod pallet { // ensure that the migration witness data was correct. if real_size_upper < task.dyn_size { Self::slash(who, deposit)?; - return Ok(().into()) + return Ok(().into()); } Self::deposit_event(Event::::Migrated { @@ -957,6 +957,7 @@ pub mod pallet { mod benchmarks { use super::{pallet::Pallet as StateTrieMigration, *}; use alloc::vec; + use frame_benchmarking::v2::*; use frame_support::traits::fungible::{Inspect, Mutate}; // The size of the key seemingly makes no difference in the read/write time, so we make it @@ -970,8 +971,12 @@ mod benchmarks { stash } - frame_benchmarking::benchmarks! { - continue_migrate { + #[benchmarks] + mod inner_benchmarks { + use super::*; + + #[benchmark] + fn continue_migrate() -> Result<(), BenchmarkError> { // note that this benchmark should migrate nothing, as we only want the overhead weight // of the bookkeeping, and the migration cost itself is noted via the `dynamic_weight` // function. @@ -980,116 +985,151 @@ mod benchmarks { let stash = set_balance_for_deposit::(&caller, null.item); // Allow signed migrations. SignedMigrationMaxLimits::::put(MigrationLimits { size: 1024, item: 5 }); - }: _(frame_system::RawOrigin::Signed(caller.clone()), null, 0, StateTrieMigration::::migration_process()) - verify { + + #[extrinsic_call] + _( + frame_system::RawOrigin::Signed(caller.clone()), + null, + 0, + StateTrieMigration::::migration_process(), + ); + assert_eq!(StateTrieMigration::::migration_process(), Default::default()); - assert_eq!(T::Currency::balance(&caller), stash) + assert_eq!(T::Currency::balance(&caller), stash); + + Ok(()) } - continue_migrate_wrong_witness { + #[benchmark] + fn continue_migrate_wrong_witness() -> Result<(), BenchmarkError> { let null = MigrationLimits::default(); let caller = frame_benchmarking::whitelisted_caller(); - let bad_witness = MigrationTask { progress_top: Progress::LastKey(vec![1u8].try_into().unwrap()), ..Default::default() }; - }: { - assert!( - StateTrieMigration::::continue_migrate( + let bad_witness = MigrationTask { + progress_top: Progress::LastKey(vec![1u8].try_into().unwrap()), + ..Default::default() + }; + #[block] + { + assert!(StateTrieMigration::::continue_migrate( frame_system::RawOrigin::Signed(caller).into(), null, 0, bad_witness, ) - .is_err() - ) - } - verify { - assert_eq!(StateTrieMigration::::migration_process(), Default::default()) + .is_err()); + } + + assert_eq!(StateTrieMigration::::migration_process(), Default::default()); + + Ok(()) } - migrate_custom_top_success { + #[benchmark] + fn migrate_custom_top_success() -> Result<(), BenchmarkError> { let null = MigrationLimits::default(); let caller: T::AccountId = frame_benchmarking::whitelisted_caller(); let stash = set_balance_for_deposit::(&caller, null.item); - }: migrate_custom_top(frame_system::RawOrigin::Signed(caller.clone()), Default::default(), 0) - verify { + #[extrinsic_call] + migrate_custom_top( + frame_system::RawOrigin::Signed(caller.clone()), + Default::default(), + 0, + ); + assert_eq!(StateTrieMigration::::migration_process(), Default::default()); - assert_eq!(T::Currency::balance(&caller), stash) + assert_eq!(T::Currency::balance(&caller), stash); + Ok(()) } - migrate_custom_top_fail { + #[benchmark] + fn migrate_custom_top_fail() -> Result<(), BenchmarkError> { let null = MigrationLimits::default(); let caller: T::AccountId = frame_benchmarking::whitelisted_caller(); let stash = set_balance_for_deposit::(&caller, null.item); // for tests, we need to make sure there is _something_ in storage that is being // migrated. - sp_io::storage::set(b"foo", vec![1u8;33].as_ref()); - }: { - assert!( - StateTrieMigration::::migrate_custom_top( + sp_io::storage::set(b"foo", vec![1u8; 33].as_ref()); + #[block] + { + assert!(StateTrieMigration::::migrate_custom_top( frame_system::RawOrigin::Signed(caller.clone()).into(), vec![b"foo".to_vec()], 1, - ).is_ok() - ); + ) + .is_ok()); + + frame_system::Pallet::::assert_last_event( + ::RuntimeEvent::from(crate::Event::Slashed { + who: caller.clone(), + amount: StateTrieMigration::::calculate_deposit_for(1u32), + }) + .into(), + ); + } - frame_system::Pallet::::assert_last_event( - ::RuntimeEvent::from(crate::Event::Slashed { - who: caller.clone(), - amount: StateTrieMigration::::calculate_deposit_for(1u32), - }).into(), - ); - } - verify { assert_eq!(StateTrieMigration::::migration_process(), Default::default()); // must have gotten slashed - assert!(T::Currency::balance(&caller) < stash) + assert!(T::Currency::balance(&caller) < stash); + + Ok(()) } - migrate_custom_child_success { + #[benchmark] + fn migrate_custom_child_success() -> Result<(), BenchmarkError> { let caller: T::AccountId = frame_benchmarking::whitelisted_caller(); let stash = set_balance_for_deposit::(&caller, 0); - }: migrate_custom_child( - frame_system::RawOrigin::Signed(caller.clone()), - StateTrieMigration::::childify(Default::default()), - Default::default(), - 0 - ) - verify { + + #[extrinsic_call] + migrate_custom_child( + frame_system::RawOrigin::Signed(caller.clone()), + StateTrieMigration::::childify(Default::default()), + Default::default(), + 0, + ); + assert_eq!(StateTrieMigration::::migration_process(), Default::default()); assert_eq!(T::Currency::balance(&caller), stash); + + Ok(()) } - migrate_custom_child_fail { + #[benchmark] + fn migrate_custom_child_fail() -> Result<(), BenchmarkError> { let caller: T::AccountId = frame_benchmarking::whitelisted_caller(); let stash = set_balance_for_deposit::(&caller, 1); // for tests, we need to make sure there is _something_ in storage that is being // migrated. - sp_io::default_child_storage::set(b"top", b"foo", vec![1u8;33].as_ref()); - }: { - assert!( - StateTrieMigration::::migrate_custom_child( + sp_io::default_child_storage::set(b"top", b"foo", vec![1u8; 33].as_ref()); + + #[block] + { + assert!(StateTrieMigration::::migrate_custom_child( frame_system::RawOrigin::Signed(caller.clone()).into(), StateTrieMigration::::childify("top"), vec![b"foo".to_vec()], 1, - ).is_ok() - ) - } - verify { + ) + .is_ok()); + } assert_eq!(StateTrieMigration::::migration_process(), Default::default()); // must have gotten slashed - assert!(T::Currency::balance(&caller) < stash) + assert!(T::Currency::balance(&caller) < stash); + Ok(()) } - process_top_key { - let v in 1 .. (4 * 1024 * 1024); - + #[benchmark] + fn process_top_key(v: Linear<1, { 4 * 1024 * 1024 }>) -> Result<(), BenchmarkError> { let value = alloc::vec![1u8; v as usize]; sp_io::storage::set(KEY, &value); - }: { - let data = sp_io::storage::get(KEY).unwrap(); - sp_io::storage::set(KEY, &data); - let _next = sp_io::storage::next_key(KEY); - assert_eq!(data, value); + #[block] + { + let data = sp_io::storage::get(KEY).unwrap(); + sp_io::storage::set(KEY, &data); + let _next = sp_io::storage::next_key(KEY); + assert_eq!(data, value); + } + + Ok(()) } impl_benchmark_test_suite!( @@ -1741,7 +1781,7 @@ pub(crate) mod remote_tests { let ((finished, weight), proof) = ext.execute_and_prove(|| { let weight = run_to_block::(now + One::one()).1; if StateTrieMigration::::migration_process().finished() { - return (true, weight) + return (true, weight); } duration += One::one(); now += One::one(); @@ -1768,7 +1808,7 @@ pub(crate) mod remote_tests { ext.commit_all().unwrap(); if finished { - break + break; } } From 69a660be5bb9e67e4371b90a3c0be54ef60f0ad1 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 19:35:45 +0200 Subject: [PATCH 061/340] update tests --- .../pallets/inbound-queue-v2/src/mock.rs | 1 + .../primitives/router/src/inbound/mod.rs | 74 +++++++++++++ .../primitives/router/src/inbound/v1.rs | 2 +- .../primitives/router/src/inbound/v2.rs | 103 +++++------------- 4 files changed, 105 insertions(+), 75 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 4c895f85bc8c..b069c352ae8a 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -6,6 +6,7 @@ use crate::{self as inbound_queue_v2}; use frame_support::{ derive_impl, parameter_types, traits::{ConstU128, ConstU32}, + weights::IdentityFee, }; use hex_literal::hex; use snowbridge_beacon_primitives::{ diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 69fa554df265..b494bc5b0e64 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -36,3 +36,77 @@ impl EthereumLocationsConverterFor { } pub type CallIndex = [u8; 2]; + +#[cfg(test)] +mod tests { + use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; + use frame_support::{assert_ok, parameter_types}; + use hex_literal::hex; + use xcm::prelude::*; + use xcm_executor::traits::ConvertLocation; + + const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; + + parameter_types! { + pub EthereumNetwork: NetworkId = NETWORK; + + pub const CreateAssetCall: CallIndex = [1, 1]; + pub const CreateAssetExecutionFee: u128 = 123; + pub const CreateAssetDeposit: u128 = 891; + pub const SendTokenExecutionFee: u128 = 592; + } + + #[test] + fn test_contract_location_with_network_converts_successfully() { + let expected_account: [u8; 32] = + hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); + let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); + + let account = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location) + .unwrap(); + + assert_eq!(account, expected_account); + } + + #[test] + fn test_contract_location_with_incorrect_location_fails_convert() { + let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); + + assert_eq!( + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(&contract_location), + None, + ); + } + + #[test] + fn test_reanchor_all_assets() { + let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); + let ethereum = Location::new(2, ethereum_context.clone()); + let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); + let global_ah = Location::new(1, ah_context.clone()); + let assets = vec![ + // DOT + Location::new(1, []), + // GLMR (Some Polkadot parachain currency) + Location::new(1, [Parachain(2004)]), + // AH asset + Location::new(0, [PalletInstance(50), GeneralIndex(42)]), + // KSM + Location::new(2, [GlobalConsensus(Kusama)]), + // KAR (Some Kusama parachain currency) + Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), + ]; + for asset in assets.iter() { + // reanchor logic in pallet_xcm on AH + let mut reanchored_asset = asset.clone(); + assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); + // reanchor back to original location in context of Ethereum + let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); + assert_ok!( + reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context) + ); + assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); + } + } +} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs index 2d3cf4e55b86..42a04d0dc6c9 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v1.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v1.rs @@ -449,7 +449,7 @@ where #[cfg(test)] mod tests { - use crate::inbound::{CallIndex, GlobalConsensusEthereumConvertsFor}; + use crate::inbound::{CallIndex, EthereumLocationsConverterFor}; use frame_support::{assert_ok, parameter_types}; use hex_literal::hex; use xcm::prelude::*; diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 63b997a1f454..aa4cd4d951b9 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -17,14 +17,6 @@ use xcm::{ const LOG_TARGET: &str = "snowbridge-router-primitives"; -/// Messages from Ethereum are versioned. This is because in future, -/// we may want to evolve the protocol so that the ethereum side sends XCM messages directly. -/// Instead having BridgeHub transcode the messages into XCM. -#[derive(Clone, Encode, Decode, RuntimeDebug)] -pub enum VersionedMessage { - V2(Message), -} - /// The ethereum side sends messages which are transcoded into XCM on BH. These messages are /// self-contained, in that they can be transcoded using only information in the message. #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] @@ -33,12 +25,15 @@ pub struct Message { pub origin: H160, /// The assets pub assets: Vec, - // The command originating from the Gateway contract + /// The command originating from the Gateway contract pub xcm: Vec, - // The claimer in the case that funds get trapped. + /// The claimer in the case that funds get trapped. pub claimer: Option>, } +/// An asset that will be transacted on AH. The asset will be reserved/withdrawn and placed into +/// the holding register. The user needs to provide additional xcm to deposit the asset +/// in a beneficiary account. #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum Asset { NativeTokenERC20 { @@ -178,88 +173,48 @@ where #[cfg(test)] mod tests { - use crate::inbound::{ - v2::{ConvertMessage, Message, MessageToXcm}, - CallIndex, GlobalConsensusEthereumConvertsFor, - }; + use crate::inbound::v2::{ConvertMessage, Message, MessageToXcm}; use codec::Decode; use frame_support::{assert_ok, parameter_types}; use hex_literal::hex; - use sp_runtime::traits::ConstU8; + use sp_core::H256; + use sp_runtime::traits::{ConstU128, ConstU8}; use xcm::prelude::*; - use xcm_executor::traits::ConvertLocation; + + use snowbridge_core::TokenId; + use sp_runtime::traits::MaybeEquivalence; const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; parameter_types! { pub EthereumNetwork: NetworkId = NETWORK; - - pub const CreateAssetCall: CallIndex = [1, 1]; - pub const CreateAssetExecutionFee: u128 = 123; - pub const CreateAssetDeposit: u128 = 891; - pub const SendTokenExecutionFee: u128 = 592; - } - - #[test] - fn test_contract_location_with_network_converts_successfully() { - let expected_account: [u8; 32] = - hex!("ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d"); - let contract_location = Location::new(2, [GlobalConsensus(NETWORK)]); - - let account = - GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location) - .unwrap(); - - assert_eq!(account, expected_account); } - #[test] - fn test_contract_location_with_incorrect_location_fails_convert() { - let contract_location = Location::new(2, [GlobalConsensus(Polkadot), Parachain(1000)]); - - assert_eq!( - GlobalConsensusEthereumConvertsFor::<[u8; 32]>::convert_location(&contract_location), - None, - ); - } - - #[test] - fn test_reanchor_all_assets() { - let ethereum_context: InteriorLocation = [GlobalConsensus(Ethereum { chain_id: 1 })].into(); - let ethereum = Location::new(2, ethereum_context.clone()); - let ah_context: InteriorLocation = [GlobalConsensus(Polkadot), Parachain(1000)].into(); - let global_ah = Location::new(1, ah_context.clone()); - let assets = vec![ - // DOT - Location::new(1, []), - // GLMR (Some Polkadot parachain currency) - Location::new(1, [Parachain(2004)]), - // AH asset - Location::new(0, [PalletInstance(50), GeneralIndex(42)]), - // KSM - Location::new(2, [GlobalConsensus(Kusama)]), - // KAR (Some Kusama parachain currency) - Location::new(2, [GlobalConsensus(Kusama), Parachain(2000)]), - ]; - for asset in assets.iter() { - // reanchor logic in pallet_xcm on AH - let mut reanchored_asset = asset.clone(); - assert_ok!(reanchored_asset.reanchor(ðereum, &ah_context)); - // reanchor back to original location in context of Ethereum - let mut reanchored_asset_with_ethereum_context = reanchored_asset.clone(); - assert_ok!( - reanchored_asset_with_ethereum_context.reanchor(&global_ah, ðereum_context) - ); - assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); + pub struct MockTokenIdConvert; + impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::parent()) + } + fn convert_back(_loc: &Location) -> Option { + None } } #[test] - fn test_convert_message() { + fn convert_message() { let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let origin_account = + Location::new(0, AccountId32 { id: H256::random().into(), network: None }); + let message = Message::decode(&mut payload.as_ref()); assert_ok!(message.clone()); - let result = MessageToXcm::>::convert(message.unwrap()); + + let result = MessageToXcm::< + EthereumNetwork, + ConstU8<80>, + MockTokenIdConvert, + ConstU128<1_000_000_000_000>, + >::convert(message.unwrap(), origin_account); assert_ok!(result); } } From 71929f67f5e0e3691c12c690699abf08591f2e3a Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 19:49:31 +0200 Subject: [PATCH 062/340] prdoc --- prdoc/pr_6697.prdoc | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 prdoc/pr_6697.prdoc diff --git a/prdoc/pr_6697.prdoc b/prdoc/pr_6697.prdoc new file mode 100644 index 000000000000..cf2c5cbe845d --- /dev/null +++ b/prdoc/pr_6697.prdoc @@ -0,0 +1,22 @@ +title: 'Snowbridge Unordered Message Delivery - Inbound Queue' +doc: +- audience: Node Dev + description: |- + New pallets for unordered message delivery for Snowbridge, specifically the Inbound Queue part. No breaking changes + are made in this PR, only new functionality added. + +crates: +- name: snowbridge-pallet-inbound-queue-v2 + bump: minor +- name: snowbridge-inbound-queue-v2-runtime-api + bump: minor +- name: snowbridge-pallet-inbound-queue-fixtures-v2 + bump: minor +- name: snowbridge-core + bump: major +- name: snowbridge-router-primitives + bump: major +- name: bridge-hub-westend-integration-tests + bump: major +- name: bridge-hub-westend-runtime + bump: major From 6416b280a7d0032ba3c265e4506504c6d6536637 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Thu, 28 Nov 2024 19:01:41 +0100 Subject: [PATCH 063/340] [pallet-revive] bugfix decoding 64bit args in the decoder (#6695) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The argument index of the next argument is dictated by the size of the current one. --------- Signed-off-by: xermicus Co-authored-by: GitHub Action Co-authored-by: Alexander Theißen --- prdoc/pr_6695.prdoc | 8 ++++++++ substrate/frame/revive/proc-macro/src/lib.rs | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_6695.prdoc diff --git a/prdoc/pr_6695.prdoc b/prdoc/pr_6695.prdoc new file mode 100644 index 000000000000..7a950e8546cd --- /dev/null +++ b/prdoc/pr_6695.prdoc @@ -0,0 +1,8 @@ +title: '[pallet-revive] bugfix decoding 64bit args in the decoder' +doc: +- audience: Runtime Dev + description: The argument index of the next argument is dictated by the size of + the current one. +crates: +- name: pallet-revive-proc-macro + bump: patch diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 012b4bfab9a9..7232c6342824 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -342,7 +342,8 @@ where const ALLOWED_REGISTERS: u32 = 6; let mut registers_used = 0; let mut bindings = vec![]; - for (idx, (name, ty)) in param_names.clone().zip(param_types.clone()).enumerate() { + let mut idx = 0; + for (name, ty) in param_names.clone().zip(param_types.clone()) { let syn::Type::Path(path) = &**ty else { panic!("Type needs to be path"); }; @@ -380,6 +381,7 @@ where } }; bindings.push(binding); + idx += size; } quote! { #( #bindings )* From 57359607a89e776b09c5c03a0875bb017450b688 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 20:22:26 +0200 Subject: [PATCH 064/340] adds register token test --- .../src/tests/snowbridge_v2.rs | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 73a3250566b3..53cdbe7d88c0 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -31,6 +31,100 @@ use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; + +#[test] +fn register_token_v2() { + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + + let relayer = BridgeHubWestendSender::get(); + let receiver = AssetHubWestendReceiver::get(); + BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + let chain_id = 11155111u64; + let claimer = AccountId32 { network: None, id: receiver.clone().into() }; + let claimer_bytes = claimer.encode(); + let origin = H160::random(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); + + let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + let weth_token_id: H160 = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14").into(); + let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); + let weth_amount = 300_000_000_000_000u128; + + let assets = vec![NativeTokenERC20 { token_id: weth_token_id, value: weth_amount }]; + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let dot_asset = Location::new(1, Here); + let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + + let weth_asset = Location::new( + 2, + [ + GlobalConsensus(ethereum_network_v5), + AccountKey20 { network: None, key: weth_token_id.into() }, + ], + ); + let weth_fee: xcm::prelude::Asset = (weth_asset, weth_amount).into(); + + let asset_id = Location::new( + 2, + [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], + ); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let register_token_instructions = vec![ + ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, + PayFees { asset: dot_fee }, + Transact { + origin_kind: OriginKind::Xcm, + call: ( + CreateAssetCall::get(), + asset_id, + MultiAddress::<[u8; 32], ()>::Id(owner.into()), + 1, + ) + .encode() + .into(), + }, + ]; + let xcm: Xcm<()> = register_token_instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + + let message = Message { + origin, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); +} + #[test] fn xcm_prologue_fee() { BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); From a935ff28d99c1e0b2ad253d689dfaeecfd021c25 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 28 Nov 2024 21:50:50 +0200 Subject: [PATCH 065/340] enable exchange asset on AH --- .../assets/asset-hub-westend/src/weights/xcm/mod.rs | 2 +- .../src/weights/xcm/pallet_xcm_benchmarks_fungible.rs | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs index 35ff2dc367c0..dc8ed7d667b7 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs @@ -119,7 +119,7 @@ impl XcmWeightInfo for AssetHubWestendXcmWeight { assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) } fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { - Weight::MAX + XcmFungibleWeight::::exchange_asset() } fn initiate_reserve_withdraw( assets: &AssetFilter, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 97e59c24dd89..52c941f69cd2 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -220,4 +220,14 @@ impl WeightInfo { .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(4)) } + + pub fn exchange_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `159` + // Estimated: `6196` + // Minimum execution time: 87_253_000 picoseconds. + Weight::from_parts(88_932_000, 6196) + .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().writes(4)) + } } From 373d63a7948cf59002236a8f9431ff9040c0ecf5 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Fri, 29 Nov 2024 10:14:47 +0200 Subject: [PATCH 066/340] fix transact --- .../bridge-hub-westend/src/tests/snowbridge_v2.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 53cdbe7d88c0..9883db10ddd1 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -36,11 +36,6 @@ use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; fn register_token_v2() { BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); - let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( - 1, - [Parachain(AssetHubWestend::para_id().into())], - )); - let relayer = BridgeHubWestendSender::get(); let receiver = AssetHubWestendReceiver::get(); BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); @@ -57,13 +52,12 @@ fn register_token_v2() { let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); let weth_token_id: H160 = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14").into(); let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); - let weth_amount = 300_000_000_000_000u128; + let weth_amount = 9_000_000_000_000_000_000_000u128; let assets = vec![NativeTokenERC20 { token_id: weth_token_id, value: weth_amount }]; let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); let dot_asset = Location::new(1, Here); - let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); let weth_asset = Location::new( 2, @@ -81,17 +75,15 @@ fn register_token_v2() { BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let register_token_instructions = vec![ - ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, - PayFees { asset: dot_fee }, + PayFees { asset: weth_fee.into() }, Transact { origin_kind: OriginKind::Xcm, call: ( CreateAssetCall::get(), asset_id, MultiAddress::<[u8; 32], ()>::Id(owner.into()), - 1, + 1u128, ) .encode() .into(), From 72fb8bd3cd4a5051bb855415b360657d7ce247fb Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:33:46 +0000 Subject: [PATCH 067/340] Expose types from `sc-service` (#5855) # Description At moonbeam we have worked on a `lazy-loading` feature which is a client mode that forks a live parachain and fetches its state on-demand, we have been able to do this by duplicating some code from `sc_service::client`. The objective of this PR is to simplify the implementation by making public some types in polkadot-sdk. - Modules: - `sc_service::client` **I do not see a point to only expose this type when `test-helpers` feature is enabled** ## Integration Not applicable, the PR just makes some types public. ## Review Notes The changes included in this PR give more flexibility for client developers by exposing important types. --- prdoc/pr_5855.prdoc | 15 +++++++ substrate/bin/node/testing/Cargo.toml | 2 +- substrate/client/network/test/Cargo.toml | 2 +- substrate/client/rpc-spec-v2/Cargo.toml | 2 +- .../src/chain_head/subscription/inner.rs | 6 +-- .../rpc-spec-v2/src/chain_head/tests.rs | 6 +-- substrate/client/service/Cargo.toml | 2 - substrate/client/service/src/client/client.rs | 40 +------------------ substrate/client/service/src/client/mod.rs | 3 +- substrate/client/service/src/lib.rs | 5 +-- substrate/client/service/test/Cargo.toml | 2 +- .../client/service/test/src/client/mod.rs | 6 +-- substrate/test-utils/client/Cargo.toml | 4 +- substrate/test-utils/runtime/Cargo.toml | 2 +- 14 files changed, 34 insertions(+), 63 deletions(-) create mode 100644 prdoc/pr_5855.prdoc diff --git a/prdoc/pr_5855.prdoc b/prdoc/pr_5855.prdoc new file mode 100644 index 000000000000..7735cfee9f37 --- /dev/null +++ b/prdoc/pr_5855.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Remove feature `test-helpers` from sc-service + +doc: + - audience: Node Dev + description: | + Removes feature `test-helpers` from sc-service. + +crates: + - name: sc-service + bump: major + - name: sc-rpc-spec-v2 + bump: major diff --git a/substrate/bin/node/testing/Cargo.toml b/substrate/bin/node/testing/Cargo.toml index 16112386ad7c..1972c03a368b 100644 --- a/substrate/bin/node/testing/Cargo.toml +++ b/substrate/bin/node/testing/Cargo.toml @@ -37,7 +37,7 @@ sc-client-api = { workspace = true, default-features = true } sc-client-db = { features = ["rocksdb"], workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } -sc-service = { features = ["rocksdb", "test-helpers"], workspace = true, default-features = true } +sc-service = { features = ["rocksdb"], workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } diff --git a/substrate/client/network/test/Cargo.toml b/substrate/client/network/test/Cargo.toml index ebece1762f29..6340d1dfb2f4 100644 --- a/substrate/client/network/test/Cargo.toml +++ b/substrate/client/network/test/Cargo.toml @@ -33,7 +33,7 @@ sc-network-types = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } sc-network-light = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sc-service = { features = ["test-helpers"], workspace = true } +sc-service = { workspace = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index b304bc905925..70f68436767f 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -56,7 +56,7 @@ sp-consensus = { workspace = true, default-features = true } sp-externalities = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } -sc-service = { features = ["test-helpers"], workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true, features = ["test-helpers"] } assert_matches = { workspace = true } pretty_assertions = { workspace = true } diff --git a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs index 95a7c7fe1832..3e1bd23776d3 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/subscription/inner.rs @@ -784,7 +784,7 @@ mod tests { use super::*; use jsonrpsee::ConnectionId; use sc_block_builder::BlockBuilderBuilder; - use sc_service::client::new_in_mem; + use sc_service::client::new_with_backend; use sp_consensus::BlockOrigin; use sp_core::{testing::TaskExecutor, H256}; use substrate_test_runtime_client::{ @@ -811,13 +811,13 @@ mod tests { ) .unwrap(); let client = Arc::new( - new_in_mem::<_, Block, _, RuntimeApi>( + new_with_backend::<_, _, Block, _, RuntimeApi>( backend.clone(), executor, genesis_block_builder, + Box::new(TaskExecutor::new()), None, None, - Box::new(TaskExecutor::new()), client_config, ) .unwrap(), diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs index c505566d887d..21e8365622a1 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -34,7 +34,7 @@ use jsonrpsee::{ use sc_block_builder::BlockBuilderBuilder; use sc_client_api::ChildInfo; use sc_rpc::testing::TokioTestExecutor; -use sc_service::client::new_in_mem; +use sc_service::client::new_with_backend; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; use sp_core::{ @@ -2547,13 +2547,13 @@ async fn pin_block_references() { .unwrap(); let client = Arc::new( - new_in_mem::<_, Block, _, RuntimeApi>( + new_with_backend::<_, _, Block, _, RuntimeApi>( backend.clone(), executor, genesis_block_builder, + Box::new(TokioTestExecutor::default()), None, None, - Box::new(TokioTestExecutor::default()), client_config, ) .unwrap(), diff --git a/substrate/client/service/Cargo.toml b/substrate/client/service/Cargo.toml index f2fc65ef2439..3981395d9768 100644 --- a/substrate/client/service/Cargo.toml +++ b/substrate/client/service/Cargo.toml @@ -20,8 +20,6 @@ default = ["rocksdb"] # The RocksDB feature activates the RocksDB database backend. If it is not activated, and you pass # a path to a database, an error will be produced at runtime. rocksdb = ["sc-client-db/rocksdb"] -# exposes the client type -test-helpers = [] runtime-benchmarks = [ "sc-client-db/runtime-benchmarks", "sp-runtime/runtime-benchmarks", diff --git a/substrate/client/service/src/client/client.rs b/substrate/client/service/src/client/client.rs index ce5b92551bf2..eddbb9260c05 100644 --- a/substrate/client/service/src/client/client.rs +++ b/substrate/client/service/src/client/client.rs @@ -85,10 +85,8 @@ use std::{ sync::Arc, }; -#[cfg(feature = "test-helpers")] -use { - super::call_executor::LocalCallExecutor, sc_client_api::in_mem, sp_core::traits::CodeExecutor, -}; +use super::call_executor::LocalCallExecutor; +use sp_core::traits::CodeExecutor; type NotificationSinks = Mutex>>; @@ -152,39 +150,6 @@ enum PrepareStorageChangesResult { Discard(ImportResult), Import(Option>), } - -/// Create an instance of in-memory client. -#[cfg(feature = "test-helpers")] -pub fn new_in_mem( - backend: Arc>, - executor: E, - genesis_block_builder: G, - prometheus_registry: Option, - telemetry: Option, - spawn_handle: Box, - config: ClientConfig, -) -> sp_blockchain::Result< - Client, LocalCallExecutor, E>, Block, RA>, -> -where - E: CodeExecutor + sc_executor::RuntimeVersionOf, - Block: BlockT, - G: BuildGenesisBlock< - Block, - BlockImportOperation = as backend::Backend>::BlockImportOperation, - >, -{ - new_with_backend( - backend, - executor, - genesis_block_builder, - spawn_handle, - prometheus_registry, - telemetry, - config, - ) -} - /// Client configuration items. #[derive(Debug, Clone)] pub struct ClientConfig { @@ -218,7 +183,6 @@ impl Default for ClientConfig { /// Create a client with the explicitly provided backend. /// This is useful for testing backend implementations. -#[cfg(feature = "test-helpers")] pub fn new_with_backend( backend: Arc, executor: E, diff --git a/substrate/client/service/src/client/mod.rs b/substrate/client/service/src/client/mod.rs index ec77a92f162f..3020b3d296f4 100644 --- a/substrate/client/service/src/client/mod.rs +++ b/substrate/client/service/src/client/mod.rs @@ -56,5 +56,4 @@ pub use call_executor::LocalCallExecutor; pub use client::{Client, ClientConfig}; pub(crate) use code_provider::CodeProvider; -#[cfg(feature = "test-helpers")] -pub use self::client::{new_in_mem, new_with_backend}; +pub use self::client::new_with_backend; diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 9c01d7288a81..b5a38d875e3b 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -23,14 +23,11 @@ #![recursion_limit = "1024"] pub mod chain_ops; +pub mod client; pub mod config; pub mod error; mod builder; -#[cfg(feature = "test-helpers")] -pub mod client; -#[cfg(not(feature = "test-helpers"))] -mod client; mod metrics; mod task_manager; diff --git a/substrate/client/service/test/Cargo.toml b/substrate/client/service/test/Cargo.toml index 0edfc5b19314..632b98104f6b 100644 --- a/substrate/client/service/test/Cargo.toml +++ b/substrate/client/service/test/Cargo.toml @@ -31,7 +31,7 @@ sc-consensus = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sc-service = { features = ["test-helpers"], workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } diff --git a/substrate/client/service/test/src/client/mod.rs b/substrate/client/service/test/src/client/mod.rs index 55bbfcdd8594..ead90c4c65d8 100644 --- a/substrate/client/service/test/src/client/mod.rs +++ b/substrate/client/service/test/src/client/mod.rs @@ -29,7 +29,7 @@ use sc_consensus::{ BlockCheckParams, BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, }; use sc_executor::WasmExecutor; -use sc_service::client::{new_in_mem, Client, LocalCallExecutor}; +use sc_service::client::{new_with_backend, Client, LocalCallExecutor}; use sp_api::ProvideRuntimeApi; use sp_consensus::{BlockOrigin, Error as ConsensusError, SelectChain}; use sp_core::{testing::TaskExecutor, traits::CallContext, H256}; @@ -2087,13 +2087,13 @@ fn cleans_up_closed_notification_sinks_on_block_import() { // NOTE: we need to build the client here instead of using the client // provided by test_runtime_client otherwise we can't access the private // `import_notification_sinks` and `finality_notification_sinks` fields. - let mut client = new_in_mem::<_, Block, _, RuntimeApi>( + let mut client = new_with_backend::<_, _, Block, _, RuntimeApi>( backend, executor, genesis_block_builder, + Box::new(TaskExecutor::new()), None, None, - Box::new(TaskExecutor::new()), client_config, ) .unwrap(); diff --git a/substrate/test-utils/client/Cargo.toml b/substrate/test-utils/client/Cargo.toml index ebd1eab5980d..a67c91fc5f79 100644 --- a/substrate/test-utils/client/Cargo.toml +++ b/substrate/test-utils/client/Cargo.toml @@ -29,9 +29,7 @@ sc-client-db = { features = [ sc-consensus = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-offchain = { workspace = true, default-features = true } -sc-service = { features = [ - "test-helpers", -], workspace = true } +sc-service = { workspace = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } diff --git a/substrate/test-utils/runtime/Cargo.toml b/substrate/test-utils/runtime/Cargo.toml index 1c82c73072bc..96a888052876 100644 --- a/substrate/test-utils/runtime/Cargo.toml +++ b/substrate/test-utils/runtime/Cargo.toml @@ -45,7 +45,7 @@ sp-consensus-grandpa = { features = ["serde"], workspace = true } sp-trie = { workspace = true } sp-transaction-pool = { workspace = true } trie-db = { workspace = true } -sc-service = { features = ["test-helpers"], optional = true, workspace = true } +sc-service = { optional = true, workspace = true } sp-state-machine = { workspace = true } sp-externalities = { workspace = true } From 1dd21bcc1406e0f07f70e604f9cef4dc2115c989 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Fri, 29 Nov 2024 12:00:52 +0100 Subject: [PATCH 068/340] ci: update nightly in ci-unified to 2024-11-19 (#6691) cc https://github.com/paritytech/ci_cd/issues/1088 --- .github/env | 2 +- .gitlab-ci.yml | 2 +- docs/contributor/container.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/env b/.github/env index bb61e1f4cd99..730c37f1db80 100644 --- a/.github/env +++ b/.github/env @@ -1 +1 @@ -IMAGE="docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-09-11-v202409111034" +IMAGE="docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-11-19-v202411281558" diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f508404f1efa..42a7e87bda43 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,7 @@ workflow: variables: # CI_IMAGE: !reference [ .ci-unified, variables, CI_IMAGE ] - CI_IMAGE: "docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-09-11-v202409111034" + CI_IMAGE: "docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-11-19-v202411281558" # BUILDAH_IMAGE is defined in group variables BUILDAH_COMMAND: "buildah --storage-driver overlay2" RELENG_SCRIPTS_BRANCH: "master" diff --git a/docs/contributor/container.md b/docs/contributor/container.md index ec51b8b9d7cc..e387f568d7b5 100644 --- a/docs/contributor/container.md +++ b/docs/contributor/container.md @@ -24,7 +24,7 @@ The command below allows building a Linux binary without having to even install docker run --rm -it \ -w /polkadot-sdk \ -v $(pwd):/polkadot-sdk \ - docker.io/paritytech/ci-unified:bullseye-1.77.0-2024-04-10-v20240408 \ + docker.io/paritytech/ci-unified:bullseye-1.81.0-2024-11-19-v202411281558 \ cargo build --release --locked -p polkadot-parachain-bin --bin polkadot-parachain sudo chown -R $(id -u):$(id -g) target/ ``` From b3ab312724ee8c3a0c7f3d9b5ea6c98513b5c951 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Fri, 29 Nov 2024 20:02:59 +0800 Subject: [PATCH 069/340] Migrate pallet-preimage to benchmark v2 (#6277) Part of: - #6202. --------- Co-authored-by: Giuseppe Re Co-authored-by: command-bot <> --- substrate/frame/preimage/src/benchmarking.rs | 296 ++++++++++--------- substrate/frame/preimage/src/weights.rs | 150 +++++----- 2 files changed, 231 insertions(+), 215 deletions(-) diff --git a/substrate/frame/preimage/src/benchmarking.rs b/substrate/frame/preimage/src/benchmarking.rs index 3d0c5b900579..ea635bf3ef77 100644 --- a/substrate/frame/preimage/src/benchmarking.rs +++ b/substrate/frame/preimage/src/benchmarking.rs @@ -17,14 +17,13 @@ //! Preimage pallet benchmarking. -use super::*; use alloc::vec; -use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller, BenchmarkError}; +use frame_benchmarking::v2::*; use frame_support::assert_ok; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; -use crate::Pallet as Preimage; +use crate::*; fn funded_account() -> T::AccountId { let caller: T::AccountId = whitelisted_caller(); @@ -43,206 +42,225 @@ fn sized_preimage_and_hash(size: u32) -> (Vec, T::Hash) { (preimage, hash) } -benchmarks! { +fn insert_old_unrequested(s: u32) -> ::Hash { + let acc = account("old", s, 0); + T::Currency::make_free_balance_be(&acc, BalanceOf::::max_value() / 2u32.into()); + + // The preimage size does not matter here as it is not touched. + let preimage = s.to_le_bytes(); + let hash = ::Hashing::hash(&preimage[..]); + + #[allow(deprecated)] + StatusFor::::insert( + &hash, + OldRequestStatus::Unrequested { deposit: (acc, 123u32.into()), len: preimage.len() as u32 }, + ); + hash +} + +#[benchmarks] +mod benchmarks { + use super::*; + // Expensive note - will reserve. - note_preimage { - let s in 0 .. MAX_SIZE; + #[benchmark] + fn note_preimage(s: Linear<0, MAX_SIZE>) { let caller = funded_account::(); let (preimage, hash) = sized_preimage_and_hash::(s); - }: _(RawOrigin::Signed(caller), preimage) - verify { - assert!(Preimage::::have_preimage(&hash)); + + #[extrinsic_call] + _(RawOrigin::Signed(caller), preimage); + + assert!(Pallet::::have_preimage(&hash)); } + // Cheap note - will not reserve since it was requested. - note_requested_preimage { - let s in 0 .. MAX_SIZE; + #[benchmark] + fn note_requested_preimage(s: Linear<0, MAX_SIZE>) { let caller = funded_account::(); let (preimage, hash) = sized_preimage_and_hash::(s); - assert_ok!(Preimage::::request_preimage( + assert_ok!(Pallet::::request_preimage( T::ManagerOrigin::try_successful_origin() .expect("ManagerOrigin has no successful origin required for the benchmark"), hash, )); - }: note_preimage(RawOrigin::Signed(caller), preimage) - verify { - assert!(Preimage::::have_preimage(&hash)); + + #[extrinsic_call] + note_preimage(RawOrigin::Signed(caller), preimage); + + assert!(Pallet::::have_preimage(&hash)); } + // Cheap note - will not reserve since it's the manager. - note_no_deposit_preimage { - let s in 0 .. MAX_SIZE; + #[benchmark] + fn note_no_deposit_preimage(s: Linear<0, MAX_SIZE>) { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (preimage, hash) = sized_preimage_and_hash::(s); - assert_ok!(Preimage::::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - }: note_preimage( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - preimage - ) verify { - assert!(Preimage::::have_preimage(&hash)); + assert_ok!(Pallet::::request_preimage(o.clone(), hash,)); + + #[extrinsic_call] + note_preimage(o as T::RuntimeOrigin, preimage); + + assert!(Pallet::::have_preimage(&hash)); } // Expensive unnote - will unreserve. - unnote_preimage { + #[benchmark] + fn unnote_preimage() { let caller = funded_account::(); let (preimage, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::note_preimage(RawOrigin::Signed(caller.clone()).into(), preimage)); - }: _(RawOrigin::Signed(caller), hash) - verify { - assert!(!Preimage::::have_preimage(&hash)); + assert_ok!(Pallet::::note_preimage(RawOrigin::Signed(caller.clone()).into(), preimage)); + + #[extrinsic_call] + _(RawOrigin::Signed(caller), hash); + + assert!(!Pallet::::have_preimage(&hash)); } + // Cheap unnote - will not unreserve since there's no deposit held. - unnote_no_deposit_preimage { + #[benchmark] + fn unnote_no_deposit_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (preimage, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::note_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - preimage, - )); - }: unnote_preimage( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { - assert!(!Preimage::::have_preimage(&hash)); + assert_ok!(Pallet::::note_preimage(o.clone(), preimage,)); + + #[extrinsic_call] + unnote_preimage(o as T::RuntimeOrigin, hash); + + assert!(!Pallet::::have_preimage(&hash)); } // Expensive request - will unreserve the noter's deposit. - request_preimage { + #[benchmark] + fn request_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (preimage, hash) = preimage_and_hash::(); let noter = funded_account::(); - assert_ok!(Preimage::::note_preimage(RawOrigin::Signed(noter.clone()).into(), preimage)); - }: _( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { - let ticket = TicketOf::::new(¬er, Footprint { count: 1, size: MAX_SIZE as u64 }).unwrap(); - let s = RequestStatus::Requested { maybe_ticket: Some((noter, ticket)), count: 1, maybe_len: Some(MAX_SIZE) }; + assert_ok!(Pallet::::note_preimage(RawOrigin::Signed(noter.clone()).into(), preimage)); + + #[extrinsic_call] + _(o as T::RuntimeOrigin, hash); + + let ticket = + TicketOf::::new(¬er, Footprint { count: 1, size: MAX_SIZE as u64 }).unwrap(); + let s = RequestStatus::Requested { + maybe_ticket: Some((noter, ticket)), + count: 1, + maybe_len: Some(MAX_SIZE), + }; assert_eq!(RequestStatusFor::::get(&hash), Some(s)); } + // Cheap request - would unreserve the deposit but none was held. - request_no_deposit_preimage { + #[benchmark] + fn request_no_deposit_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (preimage, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::note_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - preimage, - )); - }: request_preimage( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { - let s = RequestStatus::Requested { maybe_ticket: None, count: 2, maybe_len: Some(MAX_SIZE) }; + assert_ok!(Pallet::::note_preimage(o.clone(), preimage,)); + + #[extrinsic_call] + request_preimage(o as T::RuntimeOrigin, hash); + + let s = + RequestStatus::Requested { maybe_ticket: None, count: 2, maybe_len: Some(MAX_SIZE) }; assert_eq!(RequestStatusFor::::get(&hash), Some(s)); } + // Cheap request - the preimage is not yet noted, so deposit to unreserve. - request_unnoted_preimage { + #[benchmark] + fn request_unnoted_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (_, hash) = preimage_and_hash::(); - }: request_preimage( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { + + #[extrinsic_call] + request_preimage(o as T::RuntimeOrigin, hash); + let s = RequestStatus::Requested { maybe_ticket: None, count: 1, maybe_len: None }; assert_eq!(RequestStatusFor::::get(&hash), Some(s)); } + // Cheap request - the preimage is already requested, so just a counter bump. - request_requested_preimage { + #[benchmark] + fn request_requested_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (_, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - }: request_preimage( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { + assert_ok!(Pallet::::request_preimage(o.clone(), hash,)); + + #[extrinsic_call] + request_preimage(o as T::RuntimeOrigin, hash); + let s = RequestStatus::Requested { maybe_ticket: None, count: 2, maybe_len: None }; assert_eq!(RequestStatusFor::::get(&hash), Some(s)); } // Expensive unrequest - last reference and it's noted, so will destroy the preimage. - unrequest_preimage { + #[benchmark] + fn unrequest_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (preimage, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - assert_ok!(Preimage::::note_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - preimage, - )); - }: _( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { + assert_ok!(Pallet::::request_preimage(o.clone(), hash,)); + assert_ok!(Pallet::::note_preimage(o.clone(), preimage)); + + #[extrinsic_call] + _(o as T::RuntimeOrigin, hash); + assert_eq!(RequestStatusFor::::get(&hash), None); } + // Cheap unrequest - last reference, but it's not noted. - unrequest_unnoted_preimage { + #[benchmark] + fn unrequest_unnoted_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (_, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - }: unrequest_preimage( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { + assert_ok!(Pallet::::request_preimage(o.clone(), hash,)); + + #[extrinsic_call] + unrequest_preimage(o as T::RuntimeOrigin, hash); + assert_eq!(RequestStatusFor::::get(&hash), None); } + // Cheap unrequest - not the last reference. - unrequest_multi_referenced_preimage { + #[benchmark] + fn unrequest_multi_referenced_preimage() { + let o = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); let (_, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - assert_ok!(Preimage::::request_preimage( - T::ManagerOrigin::try_successful_origin() - .expect("ManagerOrigin has no successful origin required for the benchmark"), - hash, - )); - }: unrequest_preimage( - T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, - hash - ) verify { + assert_ok!(Pallet::::request_preimage(o.clone(), hash,)); + assert_ok!(Pallet::::request_preimage(o.clone(), hash,)); + + #[extrinsic_call] + unrequest_preimage(o as T::RuntimeOrigin, hash); + let s = RequestStatus::Requested { maybe_ticket: None, count: 1, maybe_len: None }; assert_eq!(RequestStatusFor::::get(&hash), Some(s)); } - ensure_updated { - let n in 1..MAX_HASH_UPGRADE_BULK_COUNT; - + #[benchmark] + fn ensure_updated(n: Linear<1, MAX_HASH_UPGRADE_BULK_COUNT>) { let caller = funded_account::(); let hashes = (0..n).map(|i| insert_old_unrequested::(i)).collect::>(); - }: _(RawOrigin::Signed(caller), hashes) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(caller), hashes); + assert_eq!(RequestStatusFor::::iter_keys().count(), n as usize); #[allow(deprecated)] let c = StatusFor::::iter_keys().count(); assert_eq!(c, 0); } - impl_benchmark_test_suite!(Preimage, crate::mock::new_test_ext(), crate::mock::Test); -} - -fn insert_old_unrequested(s: u32) -> ::Hash { - let acc = account("old", s, 0); - T::Currency::make_free_balance_be(&acc, BalanceOf::::max_value() / 2u32.into()); - - // The preimage size does not matter here as it is not touched. - let preimage = s.to_le_bytes(); - let hash = ::Hashing::hash(&preimage[..]); - - #[allow(deprecated)] - StatusFor::::insert( - &hash, - OldRequestStatus::Unrequested { deposit: (acc, 123u32.into()), len: preimage.len() as u32 }, - ); - hash + impl_benchmark_test_suite! { + Pallet, + mock::new_test_ext(), + mock::Test + } } diff --git a/substrate/frame/preimage/src/weights.rs b/substrate/frame/preimage/src/weights.rs index edb2eed9c75a..a3aec7e7546e 100644 --- a/substrate/frame/preimage/src/weights.rs +++ b/substrate/frame/preimage/src/weights.rs @@ -18,27 +18,25 @@ //! Autogenerated weights for `pallet_preimage` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-11-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-11-28, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// target/production/substrate-node // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_preimage -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/preimage/src/weights.rs +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_preimage +// --chain=dev // --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/preimage/src/weights.rs // --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -84,10 +82,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `7` // Estimated: `6012` - // Minimum execution time: 51_981_000 picoseconds. - Weight::from_parts(52_228_000, 6012) - // Standard Error: 6 - .saturating_add(Weight::from_parts(2_392, 0).saturating_mul(s.into())) + // Minimum execution time: 51_305_000 picoseconds. + Weight::from_parts(51_670_000, 6012) + // Standard Error: 5 + .saturating_add(Weight::from_parts(2_337, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -102,10 +100,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 15_835_000 picoseconds. - Weight::from_parts(16_429_000, 3556) - // Standard Error: 8 - .saturating_add(Weight::from_parts(2_647, 0).saturating_mul(s.into())) + // Minimum execution time: 16_204_000 picoseconds. + Weight::from_parts(16_613_000, 3556) + // Standard Error: 6 + .saturating_add(Weight::from_parts(2_503, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -120,10 +118,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 15_263_000 picoseconds. - Weight::from_parts(15_578_000, 3556) - // Standard Error: 7 - .saturating_add(Weight::from_parts(2_598, 0).saturating_mul(s.into())) + // Minimum execution time: 15_118_000 picoseconds. + Weight::from_parts(15_412_000, 3556) + // Standard Error: 6 + .saturating_add(Weight::from_parts(2_411, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -139,8 +137,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `206` // Estimated: `3820` - // Minimum execution time: 64_189_000 picoseconds. - Weight::from_parts(70_371_000, 3820) + // Minimum execution time: 57_218_000 picoseconds. + Weight::from_parts(61_242_000, 3820) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -154,8 +152,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 27_582_000 picoseconds. - Weight::from_parts(31_256_000, 3556) + // Minimum execution time: 25_140_000 picoseconds. + Weight::from_parts(27_682_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -167,8 +165,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `150` // Estimated: `3556` - // Minimum execution time: 27_667_000 picoseconds. - Weight::from_parts(32_088_000, 3556) + // Minimum execution time: 25_296_000 picoseconds. + Weight::from_parts(27_413_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -180,8 +178,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 16_065_000 picoseconds. - Weight::from_parts(20_550_000, 3556) + // Minimum execution time: 15_011_000 picoseconds. + Weight::from_parts(16_524_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -193,8 +191,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `4` // Estimated: `3556` - // Minimum execution time: 13_638_000 picoseconds. - Weight::from_parts(16_979_000, 3556) + // Minimum execution time: 14_649_000 picoseconds. + Weight::from_parts(15_439_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -206,8 +204,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 11_383_000 picoseconds. - Weight::from_parts(12_154_000, 3556) + // Minimum execution time: 10_914_000 picoseconds. + Weight::from_parts(11_137_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -221,8 +219,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 22_832_000 picoseconds. - Weight::from_parts(30_716_000, 3556) + // Minimum execution time: 22_512_000 picoseconds. + Weight::from_parts(24_376_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -234,8 +232,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 10_685_000 picoseconds. - Weight::from_parts(12_129_000, 3556) + // Minimum execution time: 10_571_000 picoseconds. + Weight::from_parts(10_855_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -247,8 +245,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 10_394_000 picoseconds. - Weight::from_parts(10_951_000, 3556) + // Minimum execution time: 10_312_000 picoseconds. + Weight::from_parts(10_653_000, 3556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -267,10 +265,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0 + n * (227 ±0)` // Estimated: `6012 + n * (2830 ±0)` - // Minimum execution time: 62_203_000 picoseconds. - Weight::from_parts(63_735_000, 6012) - // Standard Error: 59_589 - .saturating_add(Weight::from_parts(59_482_352, 0).saturating_mul(n.into())) + // Minimum execution time: 61_990_000 picoseconds. + Weight::from_parts(62_751_000, 6012) + // Standard Error: 44_079 + .saturating_add(Weight::from_parts(57_343_378, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes((4_u64).saturating_mul(n.into()))) @@ -295,10 +293,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `7` // Estimated: `6012` - // Minimum execution time: 51_981_000 picoseconds. - Weight::from_parts(52_228_000, 6012) - // Standard Error: 6 - .saturating_add(Weight::from_parts(2_392, 0).saturating_mul(s.into())) + // Minimum execution time: 51_305_000 picoseconds. + Weight::from_parts(51_670_000, 6012) + // Standard Error: 5 + .saturating_add(Weight::from_parts(2_337, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -313,10 +311,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 15_835_000 picoseconds. - Weight::from_parts(16_429_000, 3556) - // Standard Error: 8 - .saturating_add(Weight::from_parts(2_647, 0).saturating_mul(s.into())) + // Minimum execution time: 16_204_000 picoseconds. + Weight::from_parts(16_613_000, 3556) + // Standard Error: 6 + .saturating_add(Weight::from_parts(2_503, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -331,10 +329,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 15_263_000 picoseconds. - Weight::from_parts(15_578_000, 3556) - // Standard Error: 7 - .saturating_add(Weight::from_parts(2_598, 0).saturating_mul(s.into())) + // Minimum execution time: 15_118_000 picoseconds. + Weight::from_parts(15_412_000, 3556) + // Standard Error: 6 + .saturating_add(Weight::from_parts(2_411, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -350,8 +348,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `206` // Estimated: `3820` - // Minimum execution time: 64_189_000 picoseconds. - Weight::from_parts(70_371_000, 3820) + // Minimum execution time: 57_218_000 picoseconds. + Weight::from_parts(61_242_000, 3820) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -365,8 +363,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 27_582_000 picoseconds. - Weight::from_parts(31_256_000, 3556) + // Minimum execution time: 25_140_000 picoseconds. + Weight::from_parts(27_682_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -378,8 +376,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `150` // Estimated: `3556` - // Minimum execution time: 27_667_000 picoseconds. - Weight::from_parts(32_088_000, 3556) + // Minimum execution time: 25_296_000 picoseconds. + Weight::from_parts(27_413_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -391,8 +389,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 16_065_000 picoseconds. - Weight::from_parts(20_550_000, 3556) + // Minimum execution time: 15_011_000 picoseconds. + Weight::from_parts(16_524_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -404,8 +402,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `4` // Estimated: `3556` - // Minimum execution time: 13_638_000 picoseconds. - Weight::from_parts(16_979_000, 3556) + // Minimum execution time: 14_649_000 picoseconds. + Weight::from_parts(15_439_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -417,8 +415,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 11_383_000 picoseconds. - Weight::from_parts(12_154_000, 3556) + // Minimum execution time: 10_914_000 picoseconds. + Weight::from_parts(11_137_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -432,8 +430,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3556` - // Minimum execution time: 22_832_000 picoseconds. - Weight::from_parts(30_716_000, 3556) + // Minimum execution time: 22_512_000 picoseconds. + Weight::from_parts(24_376_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -445,8 +443,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 10_685_000 picoseconds. - Weight::from_parts(12_129_000, 3556) + // Minimum execution time: 10_571_000 picoseconds. + Weight::from_parts(10_855_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -458,8 +456,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3556` - // Minimum execution time: 10_394_000 picoseconds. - Weight::from_parts(10_951_000, 3556) + // Minimum execution time: 10_312_000 picoseconds. + Weight::from_parts(10_653_000, 3556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -478,10 +476,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0 + n * (227 ±0)` // Estimated: `6012 + n * (2830 ±0)` - // Minimum execution time: 62_203_000 picoseconds. - Weight::from_parts(63_735_000, 6012) - // Standard Error: 59_589 - .saturating_add(Weight::from_parts(59_482_352, 0).saturating_mul(n.into())) + // Minimum execution time: 61_990_000 picoseconds. + Weight::from_parts(62_751_000, 6012) + // Standard Error: 44_079 + .saturating_add(Weight::from_parts(57_343_378, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes((4_u64).saturating_mul(n.into()))) From a61ceef724dd163dbbb993d84d1e5abfefe378a8 Mon Sep 17 00:00:00 2001 From: ron Date: Fri, 29 Nov 2024 20:15:14 +0800 Subject: [PATCH 070/340] Fix for register token --- .../primitives/router/src/inbound/v2.rs | 2 +- .../src/tests/snowbridge_v2.rs | 66 +++++++++++++++---- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index aa4cd4d951b9..59ab2ea9ee0a 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -150,7 +150,7 @@ where // Set the alias origin to the original sender on Ethereum. Important to be before the // arbitrary XCM that is appended to the message on the next line. - instructions.push(AliasOrigin(origin_location.into())); + // instructions.push(AliasOrigin(origin_location.into())); // Add the XCM sent in the message to the end of the xcm instruction instructions.extend(message_xcm.0); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 9883db10ddd1..899e89f32888 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -18,7 +18,7 @@ use bridge_hub_westend_runtime::{ EthereumInboundQueueV2, }; use codec::Encode; -use frame_support::weights::WeightToFee; +use frame_support::{traits::fungibles::Mutate, weights::WeightToFee}; use hex_literal::hex; use snowbridge_router_primitives::inbound::{ v2::{Asset::NativeTokenERC20, Message}, @@ -31,6 +31,50 @@ use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; +const WETH: [u8; 20] = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14"); +const WETH_FEE: u128 = 1_000_000_000_000; + +pub fn weth_location() -> Location { + Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get().into()), + AccountKey20 { network: None, key: WETH.into() }, + ], + ) +} + +pub fn register_weth() { + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_location().try_into().unwrap(), + assethub_sovereign.clone().into(), + true, + 1000, //ED will be used as exchange rate by default when used to PayFees with + )); + + assert!(::ForeignAssets::asset_exists( + weth_location().try_into().unwrap(), + )); + + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &AssetHubWestendReceiver::get(), + 1000000, + )); + + assert_ok!(::ForeignAssets::mint_into( + weth_location().try_into().unwrap(), + &AssetHubWestendSender::get(), + 1000000, + )); + }); +} #[test] fn register_token_v2() { @@ -40,7 +84,7 @@ fn register_token_v2() { let receiver = AssetHubWestendReceiver::get(); BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + register_weth(); let chain_id = 11155111u64; let claimer = AccountId32 { network: None, id: receiver.clone().into() }; @@ -50,23 +94,16 @@ fn register_token_v2() { Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - let weth_token_id: H160 = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14").into(); + AssetHubWestend::fund_accounts(vec![(owner.into(), INITIAL_FUND)]); + let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); - let weth_amount = 9_000_000_000_000_000_000_000u128; + let weth_amount = 9_000_000_000_000u128; - let assets = vec![NativeTokenERC20 { token_id: weth_token_id, value: weth_amount }]; + let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_amount }]; let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let dot_asset = Location::new(1, Here); - let weth_asset = Location::new( - 2, - [ - GlobalConsensus(ethereum_network_v5), - AccountKey20 { network: None, key: weth_token_id.into() }, - ], - ); - let weth_fee: xcm::prelude::Asset = (weth_asset, weth_amount).into(); + let weth_fee: xcm::prelude::Asset = (weth_location(), WETH_FEE).into(); let asset_id = Location::new( 2, @@ -88,6 +125,7 @@ fn register_token_v2() { .encode() .into(), }, + ExpectTransactStatus(MaybeErrorCode::Success), ]; let xcm: Xcm<()> = register_token_instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); From 447902eff4a574e66894ad60cb41999b05bf5e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Fri, 29 Nov 2024 13:46:31 +0100 Subject: [PATCH 071/340] pallet_revive: Switch to 64bit RISC-V (#6565) This PR updates pallet_revive to the newest PolkaVM version and adapts the test fixtures and syscall interface to work under 64bit. Please note that after this PR no 32bit contracts can be deployed (they will be rejected at deploy time). Pre-deployed 32bit contracts are now considered defunct since we changes how parameters are passed for functions with more than 6 arguments. ## Fixtures The fixtures are now built for the 64bit target. I also removed the temporary directory mechanism that triggered a full rebuild every time. It also makes it easier to find the compiled fixtures since they are now always in `target/pallet-revive-fixtures`. ## Syscall interface ### Passing pointer Registers and pointers are now 64bit wide. This allows us to pass u64 arguments in a single register. Before we needed two registers to pass them. This means that just as before we need one register per pointer we pass. We keep pointers as `u32` argument by truncating the register. This is done since the memory space of PolkaVM is 32bit. ### Functions with more than 6 arguments We only have 6 registers to pass arguments. This is why we pass a pointer to a struct when we need more than 6. Before this PR we expected a packed struct and interpreted it as SCALE encoded tuple. However, this was buggy because the `MaxEncodedLen` returned something that was larger than the packed size of the structure. This wasn't a problem before. But now the memory space changed in a way that things were placed at the edges of the memory space and those extra bytes lead to an out of bound access. This is why this PR drops SCALE and expects the arguments to be passed as a pointer to a `C` aligned struct. This avoids unaligned accesses. However, revive needs to adapt its codegen to properly align the structure fields. ## TODO - [ ] Add multi block migration that wipes all existing contracts as we made breaking changes to the syscall interface --------- Co-authored-by: GitHub Action --- .github/workflows/checks-quick.yml | 1 - Cargo.lock | 72 +++++++------- prdoc/pr_6565.prdoc | 35 +++++++ substrate/frame/revive/Cargo.toml | 2 +- substrate/frame/revive/fixtures/Cargo.toml | 4 +- substrate/frame/revive/fixtures/build.rs | 96 +++++++++++++------ .../build/{Cargo.toml => _Cargo.toml} | 5 +- .../fixtures/build/_rust-toolchain.toml | 4 + .../riscv32emac-unknown-none-polkavm.json | 26 ----- substrate/frame/revive/fixtures/src/lib.rs | 13 +-- substrate/frame/revive/proc-macro/src/lib.rs | 91 ++++++++++-------- substrate/frame/revive/rpc/src/tests.rs | 6 ++ substrate/frame/revive/src/chain_extension.rs | 12 +-- substrate/frame/revive/src/limits.rs | 21 +++- substrate/frame/revive/src/wasm/mod.rs | 20 +++- substrate/frame/revive/src/wasm/runtime.rs | 33 ++----- substrate/frame/revive/uapi/Cargo.toml | 6 +- substrate/frame/revive/uapi/src/host.rs | 4 +- .../uapi/src/host/{riscv32.rs => riscv64.rs} | 86 ++++++++--------- substrate/frame/revive/uapi/src/lib.rs | 6 ++ 20 files changed, 309 insertions(+), 234 deletions(-) create mode 100644 prdoc/pr_6565.prdoc rename substrate/frame/revive/fixtures/build/{Cargo.toml => _Cargo.toml} (80%) create mode 100644 substrate/frame/revive/fixtures/build/_rust-toolchain.toml delete mode 100644 substrate/frame/revive/fixtures/riscv32emac-unknown-none-polkavm.json rename substrate/frame/revive/uapi/src/host/{riscv32.rs => riscv64.rs} (93%) diff --git a/.github/workflows/checks-quick.yml b/.github/workflows/checks-quick.yml index c733a2517cb8..4c26b85a6303 100644 --- a/.github/workflows/checks-quick.yml +++ b/.github/workflows/checks-quick.yml @@ -97,7 +97,6 @@ jobs: --exclude "substrate/frame/contracts/fixtures/build" "substrate/frame/contracts/fixtures/contracts/common" - "substrate/frame/revive/fixtures/build" "substrate/frame/revive/fixtures/contracts/common" - name: deny git deps run: python3 .github/scripts/deny-git-deps.py . diff --git a/Cargo.lock b/Cargo.lock index 84477cd05416..e1abeea49283 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5975,6 +5975,15 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-sys" version = "0.4.1" @@ -14646,7 +14655,7 @@ dependencies = [ "pallet-utility 28.0.0", "parity-scale-codec", "paste", - "polkavm 0.13.0", + "polkavm 0.17.0", "pretty_assertions", "rlp 0.6.1", "scale-info", @@ -14742,12 +14751,10 @@ dependencies = [ "anyhow", "frame-system 28.0.0", "log", - "parity-wasm", - "polkavm-linker 0.14.0", + "polkavm-linker 0.17.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", - "tempfile", "toml 0.8.12", ] @@ -14864,7 +14871,7 @@ dependencies = [ "bitflags 1.3.2", "parity-scale-codec", "paste", - "polkavm-derive 0.14.0", + "polkavm-derive 0.17.0", "scale-info", ] @@ -19699,15 +19706,15 @@ dependencies = [ [[package]] name = "polkavm" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e79a14b15ed38cb5b9a1e38d02e933f19e3d180ae5b325fed606c5e5b9177e" +checksum = "84979be196ba2855f73616413e7b1d18258128aa396b3dc23f520a00a807720e" dependencies = [ "libc", "log", - "polkavm-assembler 0.13.0", - "polkavm-common 0.13.0", - "polkavm-linux-raw 0.13.0", + "polkavm-assembler 0.17.0", + "polkavm-common 0.17.0", + "polkavm-linux-raw 0.17.0", ] [[package]] @@ -19730,9 +19737,9 @@ dependencies = [ [[package]] name = "polkavm-assembler" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e8da55465000feb0a61bbf556ed03024db58f3420eca37721fc726b3b2136bf" +checksum = "0ba7b434ff630b0f73a1560e8baea807246ca22098abe49f97821e0e2d2accc4" dependencies = [ "log", ] @@ -19764,20 +19771,14 @@ dependencies = [ [[package]] name = "polkavm-common" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084b4339aae7dfdaaa5aa7d634110afd95970e0737b6fb2a0cb10db8b56b753c" +checksum = "8f0dbafef4ab6ceecb4982ac3b550df430ef4f9fdbf07c108b7d4f91a0682fce" dependencies = [ "log", - "polkavm-assembler 0.13.0", + "polkavm-assembler 0.17.0", ] -[[package]] -name = "polkavm-common" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711952a783e9c5ad407cdacb1ed147f36d37c5d43417c1091d86456d2999417b" - [[package]] name = "polkavm-derive" version = "0.8.0" @@ -19807,11 +19808,11 @@ dependencies = [ [[package]] name = "polkavm-derive" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4832a0aebf6cefc988bb7b2d74ea8c86c983164672e2fc96300f356a1babfc1" +checksum = "c0c3dbb6c8c7bd3e5f5b05aa7fc9355acf14df7ce5d392911e77d01090a38d0d" dependencies = [ - "polkavm-derive-impl-macro 0.14.0", + "polkavm-derive-impl-macro 0.17.0", ] [[package]] @@ -19852,11 +19853,11 @@ dependencies = [ [[package]] name = "polkavm-derive-impl" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e339fc7c11310fe5adf711d9342278ac44a75c9784947937cce12bd4f30842f2" +checksum = "42565aed4adbc4034612d0b17dea8db3681fb1bd1aed040d6edc5455a9f478a1" dependencies = [ - "polkavm-common 0.14.0", + "polkavm-common 0.17.0", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.87", @@ -19894,11 +19895,11 @@ dependencies = [ [[package]] name = "polkavm-derive-impl-macro" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b569754b15060d03000c09e3bf11509d527f60b75d79b4c30c3625b5071d9702" +checksum = "86d9838e95241b0bce4fe269cdd4af96464160505840ed5a8ac8536119ba19e2" dependencies = [ - "polkavm-derive-impl 0.14.0", + "polkavm-derive-impl 0.17.0", "syn 2.0.87", ] @@ -19934,15 +19935,16 @@ dependencies = [ [[package]] name = "polkavm-linker" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0959ac3b0f4fd5caf5c245c637705f19493efe83dba31a83bbba928b93b0116a" +checksum = "d359dc721d2cc9b555ebb3558c305112ddc5bdac09d26f95f2f7b49c1f2db7e9" dependencies = [ + "dirs", "gimli 0.31.1", "hashbrown 0.14.5", "log", "object 0.36.1", - "polkavm-common 0.14.0", + "polkavm-common 0.17.0", "regalloc2 0.9.3", "rustc-demangle", ] @@ -19961,9 +19963,9 @@ checksum = "26e45fa59c7e1bb12ef5289080601e9ec9b31435f6e32800a5c90c132453d126" [[package]] name = "polkavm-linux-raw" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686c4dd9c9c16cc22565b51bdbb269792318d0fd2e6b966b5f6c788534cad0e9" +checksum = "e64c3d93a58ffbc3099d1227f0da9675a025a9ea6c917038f266920c1de1e568" [[package]] name = "polling" diff --git a/prdoc/pr_6565.prdoc b/prdoc/pr_6565.prdoc new file mode 100644 index 000000000000..f9a75a16a6a7 --- /dev/null +++ b/prdoc/pr_6565.prdoc @@ -0,0 +1,35 @@ +title: 'pallet_revive: Switch to 64bit RISC-V' +doc: +- audience: Runtime Dev + description: |- + This PR updates pallet_revive to the newest PolkaVM version and adapts the test fixtures and syscall interface to work under 64bit. + + Please note that after this PR no 32bit contracts can be deployed (they will be rejected at deploy time). Pre-deployed 32bit contracts are now considered defunct since we changes how parameters are passed for functions with more than 6 arguments. + + ## Fixtures + + The fixtures are now built for the 64bit target. I also removed the temporary directory mechanism that triggered a full rebuild every time. It also makes it easier to find the compiled fixtures since they are now always in `target/pallet-revive-fixtures`. + + ## Syscall interface + + ### Passing pointer + + Registers and pointers are now 64bit wide. This allows us to pass u64 arguments in a single register. Before we needed two registers to pass them. This means that just as before we need one register per pointer we pass. We keep pointers as `u32` argument by truncating the register. This is done since the memory space of PolkaVM is 32bit. + + ### Functions with more than 6 arguments + + We only have 6 registers to pass arguments. This is why we pass a pointer to a struct when we need more than 6. Before this PR we expected a packed struct and interpreted it as SCALE encoded tuple. However, this was buggy because the `MaxEncodedLen` returned something that was larger than the packed size of the structure. This wasn't a problem before. But now the memory space changed in a way that things were placed at the edges of the memory space and those extra bytes lead to an out of bound access. + + This is why this PR drops SCALE and expects the arguments to be passed as a pointer to a `C` aligned struct. This avoids unaligned accesses. However, revive needs to adapt its codegen to properly align the structure fields. + + ## TODO + - [ ] Add multi block migration that wipes all existing contracts as we made breaking changes to the syscall interface +crates: +- name: pallet-revive + bump: major +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive-proc-macro + bump: major +- name: pallet-revive-uapi + bump: major diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 81fbbc8cf38e..677ef0e1367f 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] environmental = { workspace = true } paste = { workspace = true } -polkavm = { version = "0.13.0", default-features = false } +polkavm = { version = "0.17.0", default-features = false } bitflags = { workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } scale-info = { features = ["derive"], workspace = true } diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index 7a5452853d65..798ed8c75a5a 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -18,10 +18,8 @@ anyhow = { workspace = true, default-features = true, optional = true } log = { workspace = true } [build-dependencies] -parity-wasm = { workspace = true } -tempfile = { workspace = true } toml = { workspace = true } -polkavm-linker = { version = "0.14.0" } +polkavm-linker = { version = "0.17.0" } anyhow = { workspace = true, default-features = true } [features] diff --git a/substrate/frame/revive/fixtures/build.rs b/substrate/frame/revive/fixtures/build.rs index 3472e0846efd..46cd5760ca4e 100644 --- a/substrate/frame/revive/fixtures/build.rs +++ b/substrate/frame/revive/fixtures/build.rs @@ -20,7 +20,8 @@ use anyhow::Result; use anyhow::{bail, Context}; use std::{ - cfg, env, fs, + env, fs, + io::Write, path::{Path, PathBuf}, process::Command, }; @@ -82,7 +83,7 @@ fn create_cargo_toml<'a>( entries: impl Iterator, output_dir: &Path, ) -> Result<()> { - let mut cargo_toml: toml::Value = toml::from_str(include_str!("./build/Cargo.toml"))?; + let mut cargo_toml: toml::Value = toml::from_str(include_str!("./build/_Cargo.toml"))?; let mut set_dep = |name, path| -> Result<()> { cargo_toml["dependencies"][name]["path"] = toml::Value::String( fixtures_dir.join(path).canonicalize()?.to_str().unwrap().to_string(), @@ -108,21 +109,24 @@ fn create_cargo_toml<'a>( let cargo_toml = toml::to_string_pretty(&cargo_toml)?; fs::write(output_dir.join("Cargo.toml"), cargo_toml.clone()) .with_context(|| format!("Failed to write {cargo_toml:?}"))?; + fs::copy( + fixtures_dir.join("build/_rust-toolchain.toml"), + output_dir.join("rust-toolchain.toml"), + ) + .context("Failed to write toolchain file")?; Ok(()) } -fn invoke_build(target: &Path, current_dir: &Path) -> Result<()> { +fn invoke_build(current_dir: &Path) -> Result<()> { let encoded_rustflags = ["-Dwarnings"].join("\x1f"); - let mut build_command = Command::new(env::var("CARGO")?); + let mut build_command = Command::new("cargo"); build_command .current_dir(current_dir) .env_clear() .env("PATH", env::var("PATH").unwrap_or_default()) .env("CARGO_ENCODED_RUSTFLAGS", encoded_rustflags) - .env("RUSTC_BOOTSTRAP", "1") .env("RUSTUP_HOME", env::var("RUSTUP_HOME").unwrap_or_default()) - .env("RUSTUP_TOOLCHAIN", env::var("RUSTUP_TOOLCHAIN").unwrap_or_default()) .args([ "build", "--release", @@ -130,7 +134,7 @@ fn invoke_build(target: &Path, current_dir: &Path) -> Result<()> { "-Zbuild-std-features=panic_immediate_abort", ]) .arg("--target") - .arg(target); + .arg(polkavm_linker::target_json_64_path().unwrap()); if let Ok(toolchain) = env::var(OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR) { build_command.env("RUSTUP_TOOLCHAIN", &toolchain); @@ -168,7 +172,7 @@ fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec) -> Result for entry in entries { post_process( &build_dir - .join("target/riscv32emac-unknown-none-polkavm/release") + .join("target/riscv64emac-unknown-none-polkavm/release") .join(entry.name()), &out_dir.join(entry.out_filename()), )?; @@ -177,11 +181,61 @@ fn write_output(build_dir: &Path, out_dir: &Path, entries: Vec) -> Result Ok(()) } +/// Create a directory in the `target` as output directory +fn create_out_dir() -> Result { + let temp_dir: PathBuf = env::var("OUT_DIR")?.into(); + + // this is set in case the user has overriden the target directory + let out_dir = if let Ok(path) = env::var("CARGO_TARGET_DIR") { + path.into() + } else { + // otherwise just traverse up from the out dir + let mut out_dir: PathBuf = temp_dir.clone(); + loop { + if !out_dir.pop() { + bail!("Cannot find project root.") + } + if out_dir.join("Cargo.lock").exists() { + break; + } + } + out_dir.join("target") + } + .join("pallet-revive-fixtures"); + + // clean up some leftover symlink from previous versions of this script + if out_dir.exists() && !out_dir.is_dir() { + fs::remove_file(&out_dir)?; + } + fs::create_dir_all(&out_dir).context("Failed to create output directory")?; + + // write the location of the out dir so it can be found later + let mut file = fs::File::create(temp_dir.join("fixture_location.rs")) + .context("Failed to create fixture_location.rs")?; + write!( + file, + r#" + #[allow(dead_code)] + const FIXTURE_DIR: &str = "{0}"; + macro_rules! fixture {{ + ($name: literal) => {{ + include_bytes!(concat!("{0}", "/", $name, ".polkavm")) + }}; + }} + "#, + out_dir.display() + ) + .context("Failed to write to fixture_location.rs")?; + + Ok(out_dir) +} + pub fn main() -> Result<()> { let fixtures_dir: PathBuf = env::var("CARGO_MANIFEST_DIR")?.into(); let contracts_dir = fixtures_dir.join("contracts"); - let out_dir: PathBuf = env::var("OUT_DIR")?.into(); - let target = fixtures_dir.join("riscv32emac-unknown-none-polkavm.json"); + let out_dir = create_out_dir().context("Cannot determine output directory")?; + let build_dir = out_dir.join("build"); + fs::create_dir_all(&build_dir).context("Failed to create build directory")?; println!("cargo::rerun-if-env-changed={OVERRIDE_RUSTUP_TOOLCHAIN_ENV_VAR}"); println!("cargo::rerun-if-env-changed={OVERRIDE_STRIP_ENV_VAR}"); @@ -199,25 +253,9 @@ pub fn main() -> Result<()> { return Ok(()) } - let tmp_dir = tempfile::tempdir()?; - let tmp_dir_path = tmp_dir.path(); - - create_cargo_toml(&fixtures_dir, entries.iter(), tmp_dir.path())?; - invoke_build(&target, tmp_dir_path)?; - - write_output(tmp_dir_path, &out_dir, entries)?; - - #[cfg(unix)] - if let Ok(symlink_dir) = env::var("CARGO_WORKSPACE_ROOT_DIR") { - let symlink_dir: PathBuf = symlink_dir.into(); - let symlink_dir: PathBuf = symlink_dir.join("target").join("pallet-revive-fixtures"); - if symlink_dir.is_symlink() { - fs::remove_file(&symlink_dir) - .with_context(|| format!("Failed to remove_file {symlink_dir:?}"))?; - } - std::os::unix::fs::symlink(&out_dir, &symlink_dir) - .with_context(|| format!("Failed to symlink {out_dir:?} -> {symlink_dir:?}"))?; - } + create_cargo_toml(&fixtures_dir, entries.iter(), &build_dir)?; + invoke_build(&build_dir)?; + write_output(&build_dir, &out_dir, entries)?; Ok(()) } diff --git a/substrate/frame/revive/fixtures/build/Cargo.toml b/substrate/frame/revive/fixtures/build/_Cargo.toml similarity index 80% rename from substrate/frame/revive/fixtures/build/Cargo.toml rename to substrate/frame/revive/fixtures/build/_Cargo.toml index 5d0e256e2e73..beaabd83403e 100644 --- a/substrate/frame/revive/fixtures/build/Cargo.toml +++ b/substrate/frame/revive/fixtures/build/_Cargo.toml @@ -4,6 +4,9 @@ publish = false version = "1.0.0" edition = "2021" +# Make sure this is not included into the workspace +[workspace] + # Binary targets are injected dynamically by the build script. [[bin]] @@ -11,7 +14,7 @@ edition = "2021" [dependencies] uapi = { package = 'pallet-revive-uapi', path = "", default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } -polkavm-derive = { version = "0.14.0" } +polkavm-derive = { version = "0.17.0" } [profile.release] opt-level = 3 diff --git a/substrate/frame/revive/fixtures/build/_rust-toolchain.toml b/substrate/frame/revive/fixtures/build/_rust-toolchain.toml new file mode 100644 index 000000000000..4c757c708d58 --- /dev/null +++ b/substrate/frame/revive/fixtures/build/_rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly-2024-11-19" +components = ["rust-src"] +profile = "minimal" diff --git a/substrate/frame/revive/fixtures/riscv32emac-unknown-none-polkavm.json b/substrate/frame/revive/fixtures/riscv32emac-unknown-none-polkavm.json deleted file mode 100644 index bbd54cdefbac..000000000000 --- a/substrate/frame/revive/fixtures/riscv32emac-unknown-none-polkavm.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "arch": "riscv32", - "cpu": "generic-rv32", - "crt-objects-fallback": "false", - "data-layout": "e-m:e-p:32:32-i64:64-n32-S32", - "eh-frame-header": false, - "emit-debug-gdb-scripts": false, - "features": "+e,+m,+a,+c,+lui-addi-fusion,+fast-unaligned-access,+xtheadcondmov", - "linker": "rust-lld", - "linker-flavor": "ld.lld", - "llvm-abiname": "ilp32e", - "llvm-target": "riscv32", - "max-atomic-width": 32, - "panic-strategy": "abort", - "relocation-model": "pie", - "target-pointer-width": "32", - "singlethread": true, - "pre-link-args": { - "ld": [ - "--emit-relocs", - "--unique", - "--relocatable" - ] - }, - "env": "polkavm" -} diff --git a/substrate/frame/revive/fixtures/src/lib.rs b/substrate/frame/revive/fixtures/src/lib.rs index cc84daec9b59..24f6ee547dc7 100644 --- a/substrate/frame/revive/fixtures/src/lib.rs +++ b/substrate/frame/revive/fixtures/src/lib.rs @@ -19,10 +19,13 @@ extern crate alloc; +// generated file that tells us where to find the fixtures +include!(concat!(env!("OUT_DIR"), "/fixture_location.rs")); + /// Load a given wasm module and returns a wasm binary contents along with it's hash. #[cfg(feature = "std")] pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec, sp_core::H256)> { - let out_dir: std::path::PathBuf = env!("OUT_DIR").into(); + let out_dir: std::path::PathBuf = FIXTURE_DIR.into(); let fixture_path = out_dir.join(format!("{fixture_name}.polkavm")); log::debug!("Loading fixture from {fixture_path:?}"); let binary = std::fs::read(fixture_path)?; @@ -36,12 +39,6 @@ pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec, sp_core::H /// available in no-std environments (runtime benchmarks). pub mod bench { use alloc::vec::Vec; - - macro_rules! fixture { - ($name: literal) => { - include_bytes!(concat!(env!("OUT_DIR"), "/", $name, ".polkavm")) - }; - } pub const DUMMY: &[u8] = fixture!("dummy"); pub const NOOP: &[u8] = fixture!("noop"); pub const INSTR: &[u8] = fixture!("instr_benchmark"); @@ -61,7 +58,7 @@ pub mod bench { mod test { #[test] fn out_dir_should_have_compiled_mocks() { - let out_dir: std::path::PathBuf = env!("OUT_DIR").into(); + let out_dir: std::path::PathBuf = crate::FIXTURE_DIR.into(); assert!(out_dir.join("dummy.polkavm").exists()); } } diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 7232c6342824..6814add128d9 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -79,6 +79,7 @@ use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, F /// - `Result<(), TrapReason>`, /// - `Result`, /// - `Result`. +/// - `Result`. /// /// The macro expands to `pub struct Env` declaration, with the following traits implementations: /// - `pallet_revive::wasm::Environment> where E: Ext` @@ -127,6 +128,7 @@ struct HostFn { enum HostFnReturn { Unit, U32, + U64, ReturnCode, } @@ -134,8 +136,7 @@ impl HostFnReturn { fn map_output(&self) -> TokenStream2 { match self { Self::Unit => quote! { |_| None }, - Self::U32 => quote! { |ret_val| Some(ret_val) }, - Self::ReturnCode => quote! { |ret_code| Some(ret_code.into()) }, + _ => quote! { |ret_val| Some(ret_val.into()) }, } } @@ -143,6 +144,7 @@ impl HostFnReturn { match self { Self::Unit => syn::ReturnType::Default, Self::U32 => parse_quote! { -> u32 }, + Self::U64 => parse_quote! { -> u64 }, Self::ReturnCode => parse_quote! { -> ReturnErrorCode }, } } @@ -243,7 +245,8 @@ impl HostFn { let msg = r#"Should return one of the following: - Result<(), TrapReason>, - Result, - - Result"#; + - Result, + - Result"#; let ret_ty = match item.clone().sig.output { syn::ReturnType::Type(_, ty) => Ok(ty.clone()), _ => Err(err(span, &msg)), @@ -305,6 +308,7 @@ impl HostFn { let returns = match ok_ty_str.as_str() { "()" => Ok(HostFnReturn::Unit), "u32" => Ok(HostFnReturn::U32), + "u64" => Ok(HostFnReturn::U64), "ReturnErrorCode" => Ok(HostFnReturn::ReturnCode), _ => Err(err(arg1.span(), &msg)), }?; @@ -339,50 +343,61 @@ where P: Iterator> + Clone, I: Iterator> + Clone, { - const ALLOWED_REGISTERS: u32 = 6; - let mut registers_used = 0; - let mut bindings = vec![]; - let mut idx = 0; - for (name, ty) in param_names.clone().zip(param_types.clone()) { + const ALLOWED_REGISTERS: usize = 6; + + // all of them take one register but we truncate them before passing into the function + // it is important to not allow any type which has illegal bit patterns like 'bool' + if !param_types.clone().all(|ty| { let syn::Type::Path(path) = &**ty else { panic!("Type needs to be path"); }; let Some(ident) = path.path.get_ident() else { panic!("Type needs to be ident"); }; - let size = if ident == "i8" || - ident == "i16" || - ident == "i32" || - ident == "u8" || - ident == "u16" || - ident == "u32" - { - 1 - } else if ident == "i64" || ident == "u64" { - 2 - } else { - panic!("Pass by value only supports primitives"); - }; - registers_used += size; - if registers_used > ALLOWED_REGISTERS { - return quote! { - let (#( #param_names, )*): (#( #param_types, )*) = memory.read_as(__a0__)?; - } - } - let this_reg = quote::format_ident!("__a{}__", idx); - let next_reg = quote::format_ident!("__a{}__", idx + 1); - let binding = if size == 1 { + matches!(ident.to_string().as_ref(), "u8" | "u16" | "u32" | "u64") + }) { + panic!("Only primitive unsigned integers are allowed as arguments to syscalls"); + } + + // too many arguments: pass as pointer to a struct in memory + if param_names.clone().count() > ALLOWED_REGISTERS { + let fields = param_names.clone().zip(param_types.clone()).map(|(name, ty)| { quote! { - let #name = #this_reg as #ty; + #name: #ty, } - } else { - quote! { - let #name = (#this_reg as #ty) | ((#next_reg as #ty) << 32); + }); + return quote! { + #[derive(Default)] + #[repr(C)] + struct Args { + #(#fields)* } - }; - bindings.push(binding); - idx += size; + let Args { #(#param_names,)* } = { + let len = ::core::mem::size_of::(); + let mut args = Args::default(); + let ptr = &mut args as *mut Args as *mut u8; + // Safety + // 1. The struct is initialized at all times. + // 2. We only allow primitive integers (no bools) as arguments so every bit pattern is safe. + // 3. The reference doesn't outlive the args field. + // 4. There is only the single reference to the args field. + // 5. The length of the generated slice is the same as the struct. + let reference = unsafe { + ::core::slice::from_raw_parts_mut(ptr, len) + }; + memory.read_into_buf(__a0__ as _, reference)?; + args + }; + } } + + // otherwise: one argument per register + let bindings = param_names.zip(param_types).enumerate().map(|(idx, (name, ty))| { + let reg = quote::format_ident!("__a{}__", idx); + quote! { + let #name = #reg as #ty; + } + }); quote! { #( #bindings )* } @@ -409,7 +424,7 @@ fn expand_env(def: &EnvDef) -> TokenStream2 { memory: &mut M, __syscall_symbol__: &[u8], __available_api_version__: ApiVersion, - ) -> Result, TrapReason> + ) -> Result, TrapReason> { #impls } diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 7734c8c57209..920318b26f71 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -218,6 +218,8 @@ async fn deploy_and_call() -> anyhow::Result<()> { Ok(()) } +/// TODO: enable ( https://github.com/paritytech/contract-issues/issues/12 ) +#[ignore] #[tokio::test] async fn revert_call() -> anyhow::Result<()> { let _lock = SHARED_RESOURCES.write(); @@ -240,6 +242,8 @@ async fn revert_call() -> anyhow::Result<()> { Ok(()) } +/// TODO: enable ( https://github.com/paritytech/contract-issues/issues/12 ) +#[ignore] #[tokio::test] async fn event_logs() -> anyhow::Result<()> { let _lock = SHARED_RESOURCES.write(); @@ -279,6 +283,8 @@ async fn invalid_transaction() -> anyhow::Result<()> { Ok(()) } +/// TODO: enable ( https://github.com/paritytech/contract-issues/issues/12 ) +#[ignore] #[tokio::test] async fn native_evm_ratio_works() -> anyhow::Result<()> { let _lock = SHARED_RESOURCES.write(); diff --git a/substrate/frame/revive/src/chain_extension.rs b/substrate/frame/revive/src/chain_extension.rs index ccea12945054..5b3e886a5628 100644 --- a/substrate/frame/revive/src/chain_extension.rs +++ b/substrate/frame/revive/src/chain_extension.rs @@ -75,7 +75,7 @@ use crate::{ Error, }; use alloc::vec::Vec; -use codec::{Decode, MaxEncodedLen}; +use codec::Decode; use frame_support::weights::Weight; use sp_runtime::DispatchError; @@ -304,16 +304,6 @@ impl<'a, 'b, E: Ext, M: ?Sized + Memory> Environment<'a, 'b, E, M> { Ok(()) } - /// Reads and decodes a type with a size fixed at compile time from contract memory. - /// - /// This function is secure and recommended for all input types of fixed size - /// as long as the cost of reading the memory is included in the overall already charged - /// weight of the chain extension. This should usually be the case when fixed input types - /// are used. - pub fn read_as(&mut self) -> Result { - self.memory.read_as(self.input_ptr) - } - /// Reads and decodes a type with a dynamic size from contract memory. /// /// Make sure to include `len` in your weight calculations. diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 64e66382b9ab..5ce96f59c14d 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -129,23 +129,36 @@ pub mod code { Error::::CodeRejected })?; + if !program.is_64_bit() { + log::debug!(target: LOG_TARGET, "32bit programs are not supported."); + Err(Error::::CodeRejected)?; + } + // This scans the whole program but we only do it once on code deployment. // It is safe to do unchecked math in u32 because the size of the program // was already checked above. - use polkavm::program::ISA32_V1_NoSbrk as ISA; + use polkavm::program::ISA64_V1 as ISA; let mut num_instructions: u32 = 0; let mut max_basic_block_size: u32 = 0; let mut basic_block_size: u32 = 0; for inst in program.instructions(ISA) { + use polkavm::program::Instruction; num_instructions += 1; basic_block_size += 1; if inst.kind.opcode().starts_new_basic_block() { max_basic_block_size = max_basic_block_size.max(basic_block_size); basic_block_size = 0; } - if matches!(inst.kind, polkavm::program::Instruction::invalid) { - log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset); - return Err(>::InvalidInstruction.into()) + match inst.kind { + Instruction::invalid => { + log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset); + return Err(>::InvalidInstruction.into()) + }, + Instruction::sbrk(_, _) => { + log::debug!(target: LOG_TARGET, "sbrk instruction is not allowed. offset {}", inst.offset); + return Err(>::InvalidInstruction.into()) + }, + _ => (), } } diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index f10c4f5fddf8..d87ec7112286 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -293,8 +293,15 @@ impl WasmBlob { ) -> Result, ExecError> { let mut config = polkavm::Config::default(); config.set_backend(Some(polkavm::BackendKind::Interpreter)); - let engine = - polkavm::Engine::new(&config).expect("interpreter is available on all plattforms; qed"); + config.set_cache_enabled(false); + #[cfg(feature = "std")] + if std::env::var_os("REVIVE_USE_COMPILER").is_some() { + config.set_backend(Some(polkavm::BackendKind::Compiler)); + } + let engine = polkavm::Engine::new(&config).expect( + "on-chain (no_std) use of interpreter is hard coded. + interpreter is available on all plattforms; qed", + ); let mut module_config = polkavm::ModuleConfig::new(); module_config.set_page_size(limits::PAGE_SIZE); @@ -306,6 +313,15 @@ impl WasmBlob { Error::::CodeRejected })?; + // This is checked at deploy time but we also want to reject pre-existing + // 32bit programs. + // TODO: Remove when we reset the test net. + // https://github.com/paritytech/contract-issues/issues/11 + if !module.is_64_bit() { + log::debug!(target: LOG_TARGET, "32bit programs are not supported."); + Err(Error::::CodeRejected)?; + } + let entry_program_counter = module .exports() .find(|export| export.symbol().as_bytes() == entry_point.identifier().as_bytes()) diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 3e2c83db1ebd..7ea518081e23 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -27,7 +27,7 @@ use crate::{ Config, Error, LOG_TARGET, SENTINEL, }; use alloc::{boxed::Box, vec, vec::Vec}; -use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; +use codec::{Decode, DecodeLimit, Encode}; use core::{fmt, marker::PhantomData, mem}; use frame_support::{ dispatch::DispatchInfo, ensure, pallet_prelude::DispatchResultWithPostInfo, parameter_types, @@ -126,34 +126,13 @@ pub trait Memory { /// /// # Note /// - /// There must be an extra benchmark for determining the influence of `len` with - /// regard to the overall weight. + /// Make sure to charge a proportional amount of weight if `len` is not fixed. fn read_as_unbounded(&self, ptr: u32, len: u32) -> Result { let buf = self.read(ptr, len)?; let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut buf.as_ref()) .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; Ok(decoded) } - - /// Reads and decodes a type with a size fixed at compile time from contract memory. - /// - /// # Only use on fixed size types - /// - /// Don't use this for types where the encoded size is not fixed but merely bounded. Otherwise - /// this implementation will out of bound access the buffer declared by the guest. Some examples - /// of those bounded but not fixed types: Enums with data, `BoundedVec` or any compact encoded - /// integer. - /// - /// # Note - /// - /// The weight of reading a fixed value is included in the overall weight of any - /// contract callable function. - fn read_as(&self, ptr: u32) -> Result { - let buf = self.read(ptr, D::max_encoded_len() as u32)?; - let decoded = D::decode_with_depth_limit(MAX_DECODE_NESTING, &mut buf.as_ref()) - .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; - Ok(decoded) - } } /// Allows syscalls access to the PolkaVM instance they are executing in. @@ -164,8 +143,8 @@ pub trait Memory { pub trait PolkaVmInstance: Memory { fn gas(&self) -> polkavm::Gas; fn set_gas(&mut self, gas: polkavm::Gas); - fn read_input_regs(&self) -> (u32, u32, u32, u32, u32, u32); - fn write_output(&mut self, output: u32); + fn read_input_regs(&self) -> (u64, u64, u64, u64, u64, u64); + fn write_output(&mut self, output: u64); } // Memory implementation used in benchmarking where guest memory is mapped into the host. @@ -214,7 +193,7 @@ impl PolkaVmInstance for polkavm::RawInstance { self.set_gas(gas) } - fn read_input_regs(&self) -> (u32, u32, u32, u32, u32, u32) { + fn read_input_regs(&self) -> (u64, u64, u64, u64, u64, u64) { ( self.reg(polkavm::Reg::A0), self.reg(polkavm::Reg::A1), @@ -225,7 +204,7 @@ impl PolkaVmInstance for polkavm::RawInstance { ) } - fn write_output(&mut self, output: u32) { + fn write_output(&mut self, output: u64) { self.set_reg(polkavm::Reg::A0, output); } } diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml index 0c7461a35d69..b55391dd5d6c 100644 --- a/substrate/frame/revive/uapi/Cargo.toml +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -20,11 +20,11 @@ codec = { features = [ "max-encoded-len", ], optional = true, workspace = true } -[target.'cfg(target_arch = "riscv32")'.dependencies] -polkavm-derive = { version = "0.14.0" } +[target.'cfg(target_arch = "riscv64")'.dependencies] +polkavm-derive = { version = "0.17.0" } [package.metadata.docs.rs] -default-target = ["wasm32-unknown-unknown"] +default-target = ["riscv64imac-unknown-none-elf"] [features] default = ["scale"] diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 6b3a8b07f040..d3fd4ac8d03e 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -14,8 +14,8 @@ use crate::{CallFlags, Result, ReturnFlags, StorageFlags}; use paste::paste; -#[cfg(target_arch = "riscv32")] -mod riscv32; +#[cfg(target_arch = "riscv64")] +mod riscv64; macro_rules! hash_fn { ( $name:ident, $bytes:literal ) => { diff --git a/substrate/frame/revive/uapi/src/host/riscv32.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs similarity index 93% rename from substrate/frame/revive/uapi/src/host/riscv32.rs rename to substrate/frame/revive/uapi/src/host/riscv64.rs index e8b27057ed18..3cba14db6a04 100644 --- a/substrate/frame/revive/uapi/src/host/riscv32.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -26,10 +26,10 @@ mod sys { mod abi {} impl abi::FromHost for ReturnCode { - type Regs = (u32,); + type Regs = (u64,); fn from_host((a0,): Self::Regs) -> Self { - ReturnCode(a0) + ReturnCode(a0 as _) } } @@ -207,33 +207,33 @@ impl HostFn for HostFnImpl { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); let salt_ptr = ptr_or_sentinel(&salt); - #[repr(packed)] + #[repr(C)] #[allow(dead_code)] struct Args { - code_hash: *const u8, + code_hash: u32, ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: *const u8, - value: *const u8, - input: *const u8, + deposit_limit: u32, + value: u32, + input: u32, input_len: u32, - address: *const u8, - output: *mut u8, - output_len: *mut u32, - salt: *const u8, + address: u32, + output: u32, + output_len: u32, + salt: u32, } let args = Args { - code_hash: code_hash.as_ptr(), + code_hash: code_hash.as_ptr() as _, ref_time_limit, proof_size_limit, - deposit_limit: deposit_limit_ptr, - value: value.as_ptr(), - input: input.as_ptr(), + deposit_limit: deposit_limit_ptr as _, + value: value.as_ptr() as _, + input: input.as_ptr() as _, input_len: input.len() as _, - address, - output: output_ptr, - output_len: &mut output_len as *mut _, - salt: salt_ptr, + address: address as _, + output: output_ptr as _, + output_len: &mut output_len as *mut _ as _, + salt: salt_ptr as _, }; let ret_code = { unsafe { sys::instantiate(&args as *const Args as *const _) } }; @@ -257,31 +257,31 @@ impl HostFn for HostFnImpl { ) -> Result { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); - #[repr(packed)] + #[repr(C)] #[allow(dead_code)] struct Args { flags: u32, - callee: *const u8, + callee: u32, ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: *const u8, - value: *const u8, - input: *const u8, + deposit_limit: u32, + value: u32, + input: u32, input_len: u32, - output: *mut u8, - output_len: *mut u32, + output: u32, + output_len: u32, } let args = Args { flags: flags.bits(), - callee: callee.as_ptr(), + callee: callee.as_ptr() as _, ref_time_limit, proof_size_limit, - deposit_limit: deposit_limit_ptr, - value: value.as_ptr(), - input: input.as_ptr(), + deposit_limit: deposit_limit_ptr as _, + value: value.as_ptr() as _, + input: input.as_ptr() as _, input_len: input.len() as _, - output: output_ptr, - output_len: &mut output_len as *mut _, + output: output_ptr as _, + output_len: &mut output_len as *mut _ as _, }; let ret_code = { unsafe { sys::call(&args as *const Args as *const _) } }; @@ -308,29 +308,29 @@ impl HostFn for HostFnImpl { ) -> Result { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); - #[repr(packed)] + #[repr(C)] #[allow(dead_code)] struct Args { flags: u32, - address: *const u8, + address: u32, ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: *const u8, - input: *const u8, + deposit_limit: u32, + input: u32, input_len: u32, - output: *mut u8, - output_len: *mut u32, + output: u32, + output_len: u32, } let args = Args { flags: flags.bits(), - address: address.as_ptr(), + address: address.as_ptr() as _, ref_time_limit, proof_size_limit, - deposit_limit: deposit_limit_ptr, - input: input.as_ptr(), + deposit_limit: deposit_limit_ptr as _, + input: input.as_ptr() as _, input_len: input.len() as _, - output: output_ptr, - output_len: &mut output_len as *mut _, + output: output_ptr as _, + output_len: &mut output_len as *mut _ as _, }; let ret_code = { unsafe { sys::delegate_call(&args as *const Args as *const _) } }; diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs index e660ce36ef75..91c2543bb719 100644 --- a/substrate/frame/revive/uapi/src/lib.rs +++ b/substrate/frame/revive/uapi/src/lib.rs @@ -65,6 +65,12 @@ impl From for u32 { } } +impl From for u64 { + fn from(error: ReturnErrorCode) -> Self { + u32::from(error).into() + } +} + define_error_codes! { /// The called function trapped and has its state changes reverted. /// In this case no output buffer is returned. From 1e89a311471eba937a9552d7d1f55af1661feb08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 29 Nov 2024 14:09:49 +0100 Subject: [PATCH 072/340] Fix runtime api impl detection by construct runtime (#6665) Construct runtime uses autoref-based specialization to fetch the metadata about the implemented runtime apis. This is done to not fail to compile when there are no runtime apis implemented. However, there was an issue with detecting runtime apis when they were implemented in a different file. The problem is solved by moving the trait implemented by `impl_runtime_apis!` to the metadata ir crate. Closes: https://github.com/paritytech/polkadot-sdk/issues/6659 --------- Co-authored-by: GitHub Action --- Cargo.lock | 1 + prdoc/pr_6665.prdoc | 15 ++++++ .../src/construct_runtime/expand/metadata.rs | 2 + .../procedural/src/construct_runtime/mod.rs | 3 +- .../support/test/tests/runtime_metadata.rs | 49 ++++++++++--------- .../api/proc-macro/src/runtime_metadata.rs | 6 +-- substrate/primitives/api/test/Cargo.toml | 3 +- .../api/test/tests/decl_and_impl.rs | 2 + substrate/primitives/metadata-ir/src/lib.rs | 10 ++++ 9 files changed, 62 insertions(+), 29 deletions(-) create mode 100644 prdoc/pr_6665.prdoc diff --git a/Cargo.lock b/Cargo.lock index e1abeea49283..5e4e9c267b08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25548,6 +25548,7 @@ dependencies = [ "sp-api 26.0.0", "sp-consensus", "sp-core 28.0.0", + "sp-metadata-ir 0.6.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-tracing 16.0.0", diff --git a/prdoc/pr_6665.prdoc b/prdoc/pr_6665.prdoc new file mode 100644 index 000000000000..b5aaf8a3b184 --- /dev/null +++ b/prdoc/pr_6665.prdoc @@ -0,0 +1,15 @@ +title: Fix runtime api impl detection by construct runtime +doc: +- audience: Runtime Dev + description: |- + Construct runtime uses autoref-based specialization to fetch the metadata about the implemented runtime apis. This is done to not fail to compile when there are no runtime apis implemented. However, there was an issue with detecting runtime apis when they were implemented in a different file. The problem is solved by moving the trait implemented by `impl_runtime_apis!` to the metadata ir crate. + + + Closes: https://github.com/paritytech/polkadot-sdk/issues/6659 +crates: +- name: frame-support-procedural + bump: patch +- name: sp-api-proc-macro + bump: patch +- name: sp-metadata-ir + bump: patch diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs index 4590a3a7f490..0b3bd5168865 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs @@ -113,6 +113,8 @@ pub fn expand_runtime_metadata( <#extrinsic as #scrate::traits::SignedTransactionBuilder>::Extension >(); + use #scrate::__private::metadata_ir::InternalImplRuntimeApis; + #scrate::__private::metadata_ir::MetadataIR { pallets: #scrate::__private::vec![ #(#pallets),* ], extrinsic: #scrate::__private::metadata_ir::ExtrinsicMetadataIR { diff --git a/substrate/frame/support/procedural/src/construct_runtime/mod.rs b/substrate/frame/support/procedural/src/construct_runtime/mod.rs index 17042c248780..087faf37252d 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/mod.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/mod.rs @@ -466,7 +466,6 @@ fn construct_runtime_final_expansion( // Therefore, the `Deref` trait will resolve the `runtime_metadata` from `impl_runtime_apis!` // when both macros are called; and will resolve an empty `runtime_metadata` when only the `construct_runtime!` // is called. - #[doc(hidden)] trait InternalConstructRuntime { #[inline(always)] @@ -477,6 +476,8 @@ fn construct_runtime_final_expansion( #[doc(hidden)] impl InternalConstructRuntime for &#name {} + use #scrate::__private::metadata_ir::InternalImplRuntimeApis; + #outer_event #outer_error diff --git a/substrate/frame/support/test/tests/runtime_metadata.rs b/substrate/frame/support/test/tests/runtime_metadata.rs index 7523a415d458..a098643abb91 100644 --- a/substrate/frame/support/test/tests/runtime_metadata.rs +++ b/substrate/frame/support/test/tests/runtime_metadata.rs @@ -80,34 +80,39 @@ sp_api::decl_runtime_apis! { } } -sp_api::impl_runtime_apis! { - impl self::Api for Runtime { - fn test(_data: u64) { - unimplemented!() - } +// Module to emulate having the implementation in a different file. +mod apis { + use super::{Block, BlockT, Runtime}; - fn something_with_block(_: Block) -> Block { - unimplemented!() - } + sp_api::impl_runtime_apis! { + impl crate::Api for Runtime { + fn test(_data: u64) { + unimplemented!() + } - fn function_with_two_args(_: u64, _: Block) { - unimplemented!() - } + fn something_with_block(_: Block) -> Block { + unimplemented!() + } - fn same_name() {} + fn function_with_two_args(_: u64, _: Block) { + unimplemented!() + } - fn wild_card(_: u32) {} - } + fn same_name() {} - impl sp_api::Core for Runtime { - fn version() -> sp_version::RuntimeVersion { - unimplemented!() - } - fn execute_block(_: Block) { - unimplemented!() + fn wild_card(_: u32) {} } - fn initialize_block(_: &::Header) -> sp_runtime::ExtrinsicInclusionMode { - unimplemented!() + + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &::Header) -> sp_runtime::ExtrinsicInclusionMode { + unimplemented!() + } } } } diff --git a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs index 6be396339259..1706f8ca6fbb 100644 --- a/substrate/primitives/api/proc-macro/src/runtime_metadata.rs +++ b/substrate/primitives/api/proc-macro/src/runtime_metadata.rs @@ -298,18 +298,14 @@ pub fn generate_impl_runtime_metadata(impls: &[ItemImpl]) -> Result #crate_::vec::Vec<#crate_::metadata_ir::RuntimeApiMetadataIR> { #crate_::vec![ #( #metadata, )* ] } } - #[doc(hidden)] - impl InternalImplRuntimeApis for #runtime_name {} } )) } diff --git a/substrate/primitives/api/test/Cargo.toml b/substrate/primitives/api/test/Cargo.toml index 1d21f23eb804..27f6dafa24bf 100644 --- a/substrate/primitives/api/test/Cargo.toml +++ b/substrate/primitives/api/test/Cargo.toml @@ -21,6 +21,7 @@ sp-version = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-metadata-ir = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } codec = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } @@ -40,5 +41,5 @@ name = "bench" harness = false [features] -"enable-staging-api" = [] +enable-staging-api = [] disable-ui-tests = [] diff --git a/substrate/primitives/api/test/tests/decl_and_impl.rs b/substrate/primitives/api/test/tests/decl_and_impl.rs index 890cf6eccdbc..2e5a078cb382 100644 --- a/substrate/primitives/api/test/tests/decl_and_impl.rs +++ b/substrate/primitives/api/test/tests/decl_and_impl.rs @@ -309,6 +309,8 @@ fn mock_runtime_api_works_with_advanced() { #[test] fn runtime_api_metadata_matches_version_implemented() { + use sp_metadata_ir::InternalImplRuntimeApis; + let rt = Runtime {}; let runtime_metadata = rt.runtime_metadata(); diff --git a/substrate/primitives/metadata-ir/src/lib.rs b/substrate/primitives/metadata-ir/src/lib.rs index bf234432a1a6..dc01f7eaadb3 100644 --- a/substrate/primitives/metadata-ir/src/lib.rs +++ b/substrate/primitives/metadata-ir/src/lib.rs @@ -87,6 +87,16 @@ pub fn into_unstable(metadata: MetadataIR) -> RuntimeMetadataPrefixed { latest.into() } +/// INTERNAL USE ONLY +/// +/// Special trait that is used together with `InternalConstructRuntime` by `construct_runtime!` to +/// fetch the runtime api metadata without exploding when there is no runtime api implementation +/// available. +#[doc(hidden)] +pub trait InternalImplRuntimeApis { + fn runtime_metadata(&self) -> alloc::vec::Vec; +} + #[cfg(test)] mod test { use super::*; From 4e7c968ae97c66812df989117ad251cba3864632 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:49:45 +0200 Subject: [PATCH 073/340] archive: Refactor `archive_storage` method into subscription (#6483) This PR adapts the `archive_storage` implementation from a method to a subscription. This keeps the archive APIs uniform and consistent. Builds on: https://github.com/paritytech/polkadot-sdk/pull/5997 cc @paritytech/subxt-team --------- Signed-off-by: Alexandru Vasile Co-authored-by: James Wilson --- .../client/rpc-spec-v2/src/archive/api.rs | 13 +- .../client/rpc-spec-v2/src/archive/archive.rs | 202 +++---- .../src/archive/archive_storage.rs | 105 +--- .../client/rpc-spec-v2/src/archive/mod.rs | 2 +- .../client/rpc-spec-v2/src/archive/tests.rs | 500 +++++++----------- .../rpc-spec-v2/src/chain_head/event.rs | 3 +- .../client/rpc-spec-v2/src/common/events.rs | 59 ++- .../client/rpc-spec-v2/src/common/storage.rs | 151 ++++-- substrate/client/service/src/builder.rs | 2 - 9 files changed, 458 insertions(+), 579 deletions(-) diff --git a/substrate/client/rpc-spec-v2/src/archive/api.rs b/substrate/client/rpc-spec-v2/src/archive/api.rs index dcfeaecb147b..a205d0502c93 100644 --- a/substrate/client/rpc-spec-v2/src/archive/api.rs +++ b/substrate/client/rpc-spec-v2/src/archive/api.rs @@ -20,8 +20,7 @@ use crate::{ common::events::{ - ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageResult, - PaginatedStorageQuery, + ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageEvent, StorageQuery, }, MethodResult, }; @@ -100,13 +99,17 @@ pub trait ArchiveApi { /// # Unstable /// /// This method is unstable and subject to change in the future. - #[method(name = "archive_unstable_storage", blocking)] + #[subscription( + name = "archive_unstable_storage" => "archive_unstable_storageEvent", + unsubscribe = "archive_unstable_stopStorage", + item = ArchiveStorageEvent, + )] fn archive_unstable_storage( &self, hash: Hash, - items: Vec>, + items: Vec>, child_trie: Option, - ) -> RpcResult; + ); /// Returns the storage difference between two blocks. /// diff --git a/substrate/client/rpc-spec-v2/src/archive/archive.rs b/substrate/client/rpc-spec-v2/src/archive/archive.rs index 55054d91d85d..62e44a016241 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive.rs @@ -20,13 +20,13 @@ use crate::{ archive::{ - archive_storage::{ArchiveStorage, ArchiveStorageDiff}, - error::Error as ArchiveError, - ArchiveApiServer, + archive_storage::ArchiveStorageDiff, error::Error as ArchiveError, ArchiveApiServer, }, - common::events::{ - ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageResult, - PaginatedStorageQuery, + common::{ + events::{ + ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageEvent, StorageQuery, + }, + storage::{QueryResult, StorageSubscriptionClient}, }, hex_string, MethodResult, SubscriptionTaskExecutor, }; @@ -57,42 +57,12 @@ use tokio::sync::mpsc; pub(crate) const LOG_TARGET: &str = "rpc-spec-v2::archive"; -/// The configuration of [`Archive`]. -pub struct ArchiveConfig { - /// The maximum number of items the `archive_storage` can return for a descendant query before - /// pagination is required. - pub max_descendant_responses: usize, - /// The maximum number of queried items allowed for the `archive_storage` at a time. - pub max_queried_items: usize, -} - -/// The maximum number of items the `archive_storage` can return for a descendant query before -/// pagination is required. -/// -/// Note: this is identical to the `chainHead` value. -const MAX_DESCENDANT_RESPONSES: usize = 5; - -/// The maximum number of queried items allowed for the `archive_storage` at a time. -/// -/// Note: A queried item can also be a descendant query which can return up to -/// `MAX_DESCENDANT_RESPONSES`. -const MAX_QUERIED_ITEMS: usize = 8; - /// The buffer capacity for each storage query. /// /// This is small because the underlying JSON-RPC server has /// its down buffer capacity per connection as well. const STORAGE_QUERY_BUF: usize = 16; -impl Default for ArchiveConfig { - fn default() -> Self { - Self { - max_descendant_responses: MAX_DESCENDANT_RESPONSES, - max_queried_items: MAX_QUERIED_ITEMS, - } - } -} - /// An API for archive RPC calls. pub struct Archive, Block: BlockT, Client> { /// Substrate client. @@ -103,11 +73,6 @@ pub struct Archive, Block: BlockT, Client> { executor: SubscriptionTaskExecutor, /// The hexadecimal encoded hash of the genesis block. genesis_hash: String, - /// The maximum number of items the `archive_storage` can return for a descendant query before - /// pagination is required. - storage_max_descendant_responses: usize, - /// The maximum number of queried items allowed for the `archive_storage` at a time. - storage_max_queried_items: usize, /// Phantom member to pin the block type. _phantom: PhantomData, } @@ -119,18 +84,9 @@ impl, Block: BlockT, Client> Archive { backend: Arc, genesis_hash: GenesisHash, executor: SubscriptionTaskExecutor, - config: ArchiveConfig, ) -> Self { let genesis_hash = hex_string(&genesis_hash.as_ref()); - Self { - client, - backend, - executor, - genesis_hash, - storage_max_descendant_responses: config.max_descendant_responses, - storage_max_queried_items: config.max_queried_items, - _phantom: PhantomData, - } + Self { client, backend, executor, genesis_hash, _phantom: PhantomData } } } @@ -260,47 +216,53 @@ where fn archive_unstable_storage( &self, + pending: PendingSubscriptionSink, hash: Block::Hash, - items: Vec>, + items: Vec>, child_trie: Option, - ) -> RpcResult { - let items = items - .into_iter() - .map(|query| { - let key = StorageKey(parse_hex_param(query.key)?); - let pagination_start_key = query - .pagination_start_key - .map(|key| parse_hex_param(key).map(|key| StorageKey(key))) - .transpose()?; - - // Paginated start key is only supported - if pagination_start_key.is_some() && !query.query_type.is_descendant_query() { - return Err(ArchiveError::InvalidParam( - "Pagination start key is only supported for descendants queries" - .to_string(), - )) - } + ) { + let mut storage_client = + StorageSubscriptionClient::::new(self.client.clone()); + + let fut = async move { + let Ok(mut sink) = pending.accept().await.map(Subscription::from) else { return }; - Ok(PaginatedStorageQuery { - key, - query_type: query.query_type, - pagination_start_key, + let items = match items + .into_iter() + .map(|query| { + let key = StorageKey(parse_hex_param(query.key)?); + Ok(StorageQuery { key, query_type: query.query_type }) }) - }) - .collect::, ArchiveError>>()?; + .collect::, ArchiveError>>() + { + Ok(items) => items, + Err(error) => { + let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())); + return + }, + }; - let child_trie = child_trie - .map(|child_trie| parse_hex_param(child_trie)) - .transpose()? - .map(ChildInfo::new_default_from_vec); + let child_trie = child_trie.map(|child_trie| parse_hex_param(child_trie)).transpose(); + let child_trie = match child_trie { + Ok(child_trie) => child_trie.map(ChildInfo::new_default_from_vec), + Err(error) => { + let _ = sink.send(&ArchiveStorageEvent::err(error.to_string())); + return + }, + }; - let storage_client = ArchiveStorage::new( - self.client.clone(), - self.storage_max_descendant_responses, - self.storage_max_queried_items, - ); + let (tx, mut rx) = tokio::sync::mpsc::channel(STORAGE_QUERY_BUF); + let storage_fut = storage_client.generate_events(hash, items, child_trie, tx); - Ok(storage_client.handle_query(hash, items, child_trie)) + // We don't care about the return value of this join: + // - process_events might encounter an error (if the client disconnected) + // - storage_fut might encounter an error while processing a trie queries and + // the error is propagated via the sink. + let _ = futures::future::join(storage_fut, process_storage_events(&mut rx, &mut sink)) + .await; + }; + + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } fn archive_unstable_storage_diff( @@ -337,24 +299,74 @@ where // - process_events might encounter an error (if the client disconnected) // - storage_fut might encounter an error while processing a trie queries and // the error is propagated via the sink. - let _ = futures::future::join(storage_fut, process_events(&mut rx, &mut sink)).await; + let _ = + futures::future::join(storage_fut, process_storage_diff_events(&mut rx, &mut sink)) + .await; }; self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } } -/// Sends all the events to the sink. -async fn process_events(rx: &mut mpsc::Receiver, sink: &mut Subscription) { - while let Some(event) = rx.recv().await { - if event.is_done() { - log::debug!(target: LOG_TARGET, "Finished processing partial trie query"); - } else if event.is_err() { - log::debug!(target: LOG_TARGET, "Error encountered while processing partial trie query"); +/// Sends all the events of the storage_diff method to the sink. +async fn process_storage_diff_events( + rx: &mut mpsc::Receiver, + sink: &mut Subscription, +) { + loop { + tokio::select! { + _ = sink.closed() => { + return + }, + + maybe_event = rx.recv() => { + let Some(event) = maybe_event else { + break; + }; + + if event.is_done() { + log::debug!(target: LOG_TARGET, "Finished processing partial trie query"); + } else if event.is_err() { + log::debug!(target: LOG_TARGET, "Error encountered while processing partial trie query"); + } + + if sink.send(&event).await.is_err() { + return + } + } } + } +} + +/// Sends all the events of the storage method to the sink. +async fn process_storage_events(rx: &mut mpsc::Receiver, sink: &mut Subscription) { + loop { + tokio::select! { + _ = sink.closed() => { + break + } + + maybe_storage = rx.recv() => { + let Some(event) = maybe_storage else { + break; + }; + + match event { + Ok(None) => continue, + + Ok(Some(event)) => + if sink.send(&ArchiveStorageEvent::result(event)).await.is_err() { + return + }, - if sink.send(&event).await.is_err() { - return + Err(error) => { + let _ = sink.send(&ArchiveStorageEvent::err(error)).await; + return + } + } + } } } + + let _ = sink.send(&ArchiveStorageEvent::StorageDone).await; } diff --git a/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs b/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs index 5a3920882f00..390db765a48f 100644 --- a/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs +++ b/substrate/client/rpc-spec-v2/src/archive/archive_storage.rs @@ -33,114 +33,13 @@ use crate::{ common::{ events::{ ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageDiffOperationType, - ArchiveStorageDiffResult, ArchiveStorageDiffType, ArchiveStorageResult, - PaginatedStorageQuery, StorageQueryType, StorageResult, + ArchiveStorageDiffResult, ArchiveStorageDiffType, StorageResult, }, - storage::{IterQueryType, QueryIter, Storage}, + storage::Storage, }, }; use tokio::sync::mpsc; -/// Generates the events of the `archive_storage` method. -pub struct ArchiveStorage { - /// Storage client. - client: Storage, - /// The maximum number of responses the API can return for a descendant query at a time. - storage_max_descendant_responses: usize, - /// The maximum number of queried items allowed for the `archive_storage` at a time. - storage_max_queried_items: usize, -} - -impl ArchiveStorage { - /// Constructs a new [`ArchiveStorage`]. - pub fn new( - client: Arc, - storage_max_descendant_responses: usize, - storage_max_queried_items: usize, - ) -> Self { - Self { - client: Storage::new(client), - storage_max_descendant_responses, - storage_max_queried_items, - } - } -} - -impl ArchiveStorage -where - Block: BlockT + 'static, - BE: Backend + 'static, - Client: StorageProvider + 'static, -{ - /// Generate the response of the `archive_storage` method. - pub fn handle_query( - &self, - hash: Block::Hash, - mut items: Vec>, - child_key: Option, - ) -> ArchiveStorageResult { - let discarded_items = items.len().saturating_sub(self.storage_max_queried_items); - items.truncate(self.storage_max_queried_items); - - let mut storage_results = Vec::with_capacity(items.len()); - for item in items { - match item.query_type { - StorageQueryType::Value => { - match self.client.query_value(hash, &item.key, child_key.as_ref()) { - Ok(Some(value)) => storage_results.push(value), - Ok(None) => continue, - Err(error) => return ArchiveStorageResult::err(error), - } - }, - StorageQueryType::Hash => - match self.client.query_hash(hash, &item.key, child_key.as_ref()) { - Ok(Some(value)) => storage_results.push(value), - Ok(None) => continue, - Err(error) => return ArchiveStorageResult::err(error), - }, - StorageQueryType::ClosestDescendantMerkleValue => - match self.client.query_merkle_value(hash, &item.key, child_key.as_ref()) { - Ok(Some(value)) => storage_results.push(value), - Ok(None) => continue, - Err(error) => return ArchiveStorageResult::err(error), - }, - StorageQueryType::DescendantsValues => { - match self.client.query_iter_pagination( - QueryIter { - query_key: item.key, - ty: IterQueryType::Value, - pagination_start_key: item.pagination_start_key, - }, - hash, - child_key.as_ref(), - self.storage_max_descendant_responses, - ) { - Ok((results, _)) => storage_results.extend(results), - Err(error) => return ArchiveStorageResult::err(error), - } - }, - StorageQueryType::DescendantsHashes => { - match self.client.query_iter_pagination( - QueryIter { - query_key: item.key, - ty: IterQueryType::Hash, - pagination_start_key: item.pagination_start_key, - }, - hash, - child_key.as_ref(), - self.storage_max_descendant_responses, - ) { - Ok((results, _)) => storage_results.extend(results), - Err(error) => return ArchiveStorageResult::err(error), - } - }, - }; - } - - ArchiveStorageResult::ok(storage_results, discarded_items) - } -} - /// Parse hex-encoded string parameter as raw bytes. /// /// If the parsing fails, returns an error propagated to the RPC method. diff --git a/substrate/client/rpc-spec-v2/src/archive/mod.rs b/substrate/client/rpc-spec-v2/src/archive/mod.rs index 5f020c203eab..14fa104c113a 100644 --- a/substrate/client/rpc-spec-v2/src/archive/mod.rs +++ b/substrate/client/rpc-spec-v2/src/archive/mod.rs @@ -32,4 +32,4 @@ pub mod archive; pub mod error; pub use api::ArchiveApiServer; -pub use archive::{Archive, ArchiveConfig}; +pub use archive::Archive; diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index 994c5d28bd61..cddaafde6659 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -19,16 +19,13 @@ use crate::{ common::events::{ ArchiveStorageDiffEvent, ArchiveStorageDiffItem, ArchiveStorageDiffOperationType, - ArchiveStorageDiffResult, ArchiveStorageDiffType, ArchiveStorageMethodOk, - ArchiveStorageResult, PaginatedStorageQuery, StorageQueryType, StorageResultType, + ArchiveStorageDiffResult, ArchiveStorageDiffType, ArchiveStorageEvent, StorageQuery, + StorageQueryType, StorageResult, StorageResultType, }, hex_string, MethodResult, }; -use super::{ - archive::{Archive, ArchiveConfig}, - *, -}; +use super::{archive::Archive, *}; use assert_matches::assert_matches; use codec::{Decode, Encode}; @@ -55,8 +52,6 @@ use substrate_test_runtime_client::{ const CHAIN_GENESIS: [u8; 32] = [0; 32]; const INVALID_HASH: [u8; 32] = [1; 32]; -const MAX_PAGINATION_LIMIT: usize = 5; -const MAX_QUERIED_LIMIT: usize = 5; const KEY: &[u8] = b":mock"; const VALUE: &[u8] = b"hello world"; const CHILD_STORAGE_KEY: &[u8] = b"child"; @@ -65,10 +60,7 @@ const CHILD_VALUE: &[u8] = b"child value"; type Header = substrate_test_runtime_client::runtime::Header; type Block = substrate_test_runtime_client::runtime::Block; -fn setup_api( - max_descendant_responses: usize, - max_queried_items: usize, -) -> (Arc>, RpcModule>>) { +fn setup_api() -> (Arc>, RpcModule>>) { let child_info = ChildInfo::new_default(CHILD_STORAGE_KEY); let builder = TestClientBuilder::new().add_extra_child_storage( &child_info, @@ -83,7 +75,6 @@ fn setup_api( backend, CHAIN_GENESIS, Arc::new(TokioTestExecutor::default()), - ArchiveConfig { max_descendant_responses, max_queried_items }, ) .into_rpc(); @@ -101,7 +92,7 @@ async fn get_next_event(sub: &mut RpcSubscriptio #[tokio::test] async fn archive_genesis() { - let (_client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (_client, api) = setup_api(); let genesis: String = api.call("archive_unstable_genesisHash", EmptyParams::new()).await.unwrap(); @@ -110,7 +101,7 @@ async fn archive_genesis() { #[tokio::test] async fn archive_body() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); // Invalid block hash. let invalid_hash = hex_string(&INVALID_HASH); @@ -144,7 +135,7 @@ async fn archive_body() { #[tokio::test] async fn archive_header() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); // Invalid block hash. let invalid_hash = hex_string(&INVALID_HASH); @@ -178,7 +169,7 @@ async fn archive_header() { #[tokio::test] async fn archive_finalized_height() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); let client_height: u32 = client.info().finalized_number.saturated_into(); @@ -190,7 +181,7 @@ async fn archive_finalized_height() { #[tokio::test] async fn archive_hash_by_height() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); // Genesis height. let hashes: Vec = api.call("archive_unstable_hashByHeight", [0]).await.unwrap(); @@ -296,7 +287,7 @@ async fn archive_hash_by_height() { #[tokio::test] async fn archive_call() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); let invalid_hash = hex_string(&INVALID_HASH); // Invalid parameter (non-hex). @@ -355,7 +346,7 @@ async fn archive_call() { #[tokio::test] async fn archive_storage_hashes_values() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); let block = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().genesis_hash) @@ -369,42 +360,23 @@ async fn archive_storage_hashes_values() { let block_hash = format!("{:?}", block.header.hash()); let key = hex_string(&KEY); - let items: Vec> = vec![ - PaginatedStorageQuery { - key: key.clone(), - query_type: StorageQueryType::DescendantsHashes, - pagination_start_key: None, - }, - PaginatedStorageQuery { - key: key.clone(), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: None, - }, - PaginatedStorageQuery { - key: key.clone(), - query_type: StorageQueryType::Hash, - pagination_start_key: None, - }, - PaginatedStorageQuery { - key: key.clone(), - query_type: StorageQueryType::Value, - pagination_start_key: None, - }, + let items: Vec> = vec![ + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::Hash }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::Value }, ]; - let result: ArchiveStorageResult = api - .call("archive_unstable_storage", rpc_params![&block_hash, items.clone()]) + let mut sub = api + .subscribe_unbounded("archive_unstable_storage", rpc_params![&block_hash, items.clone()]) .await .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - // Key has not been imported yet. - assert_eq!(result.len(), 0); - assert_eq!(discarded_items, 0); - }, - _ => panic!("Unexpected result"), - }; + // Key has not been imported yet. + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageDone, + ); // Import a block with the given key value pair. let mut builder = BlockBuilderBuilder::new(&*client) @@ -420,32 +392,103 @@ async fn archive_storage_hashes_values() { let expected_hash = format!("{:?}", Blake2Hasher::hash(&VALUE)); let expected_value = hex_string(&VALUE); - let result: ArchiveStorageResult = api - .call("archive_unstable_storage", rpc_params![&block_hash, items]) + let mut sub = api + .subscribe_unbounded("archive_unstable_storage", rpc_params![&block_hash, items]) .await .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 4); - assert_eq!(discarded_items, 0); - - assert_eq!(result[0].key, key); - assert_eq!(result[0].result, StorageResultType::Hash(expected_hash.clone())); - assert_eq!(result[1].key, key); - assert_eq!(result[1].result, StorageResultType::Value(expected_value.clone())); - assert_eq!(result[2].key, key); - assert_eq!(result[2].result, StorageResultType::Hash(expected_hash)); - assert_eq!(result[3].key, key); - assert_eq!(result[3].result, StorageResultType::Value(expected_value)); - }, - _ => panic!("Unexpected result"), - }; + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Hash(expected_hash.clone()), + child_trie_key: None, + }), + ); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Value(expected_value.clone()), + child_trie_key: None, + }), + ); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Hash(expected_hash), + child_trie_key: None, + }), + ); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Value(expected_value), + child_trie_key: None, + }), + ); + + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageDone + ); +} + +#[tokio::test] +async fn archive_storage_hashes_values_child_trie() { + let (client, api) = setup_api(); + + // Get child storage values set in `setup_api`. + let child_info = hex_string(&CHILD_STORAGE_KEY); + let key = hex_string(&KEY); + let genesis_hash = format!("{:?}", client.genesis_hash()); + let expected_hash = format!("{:?}", Blake2Hasher::hash(&CHILD_VALUE)); + let expected_value = hex_string(&CHILD_VALUE); + + let items: Vec> = vec![ + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsHashes }, + StorageQuery { key: key.clone(), query_type: StorageQueryType::DescendantsValues }, + ]; + let mut sub = api + .subscribe_unbounded( + "archive_unstable_storage", + rpc_params![&genesis_hash, items, &child_info], + ) + .await + .unwrap(); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Hash(expected_hash.clone()), + child_trie_key: Some(child_info.clone()), + }) + ); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: key.clone(), + result: StorageResultType::Value(expected_value.clone()), + child_trie_key: Some(child_info.clone()), + }) + ); + + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageDone, + ); } #[tokio::test] async fn archive_storage_closest_merkle_value() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); /// The core of this test. /// @@ -457,55 +500,47 @@ async fn archive_storage_closest_merkle_value() { api: &RpcModule>>, block_hash: String, ) -> HashMap { - let result: ArchiveStorageResult = api - .call( + let mut sub = api + .subscribe_unbounded( "archive_unstable_storage", rpc_params![ &block_hash, vec![ - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAAA"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAAB"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, // Key with descendant. - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":A"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AA"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, // Keys below this comment do not produce a result. // Key that exceed the keyspace of the trie. - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAAAX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAABX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, // Key that are not part of the trie. - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, - PaginatedStorageQuery { + StorageQuery { key: hex_string(b":AAAX"), query_type: StorageQueryType::ClosestDescendantMerkleValue, - pagination_start_key: None, }, ] ], @@ -513,19 +548,21 @@ async fn archive_storage_closest_merkle_value() { .await .unwrap(); - let merkle_values: HashMap<_, _> = match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, .. }) => result - .into_iter() - .map(|res| { - let value = match res.result { + let mut merkle_values = HashMap::new(); + loop { + let event = get_next_event::(&mut sub).await; + match event { + ArchiveStorageEvent::Storage(result) => { + let str_result = match result.result { StorageResultType::ClosestDescendantMerkleValue(value) => value, - _ => panic!("Unexpected StorageResultType"), + _ => panic!("Unexpected result type"), }; - (res.key, value) - }) - .collect(), - _ => panic!("Unexpected result"), - }; + merkle_values.insert(result.key, str_result); + }, + ArchiveStorageEvent::StorageError(err) => panic!("Unexpected error {err:?}"), + ArchiveStorageEvent::StorageDone => break, + } + } // Response for AAAA, AAAB, A and AA. assert_eq!(merkle_values.len(), 4); @@ -604,9 +641,9 @@ async fn archive_storage_closest_merkle_value() { } #[tokio::test] -async fn archive_storage_paginate_iterations() { +async fn archive_storage_iterations() { // 1 iteration allowed before pagination kicks in. - let (client, api) = setup_api(1, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); // Import a new block with storage changes. let mut builder = BlockBuilderBuilder::new(&*client) @@ -625,237 +662,94 @@ async fn archive_storage_paginate_iterations() { // Calling with an invalid hash. let invalid_hash = hex_string(&INVALID_HASH); - let result: ArchiveStorageResult = api - .call( + let mut sub = api + .subscribe_unbounded( "archive_unstable_storage", rpc_params![ &invalid_hash, - vec![PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: None, - }] - ], - ) - .await - .unwrap(); - match result { - ArchiveStorageResult::Err(_) => (), - _ => panic!("Unexpected result"), - }; - - // Valid call with storage at the key. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: None, - }] - ], - ) - .await - .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 0); - - assert_eq!(result[0].key, hex_string(b":m")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"a"))); - }, - _ => panic!("Unexpected result"), - }; - - // Continue with pagination. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: Some(hex_string(b":m")), - }] - ], - ) - .await - .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 0); - - assert_eq!(result[0].key, hex_string(b":mo")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"ab"))); - }, - _ => panic!("Unexpected result"), - }; - - // Continue with pagination. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![PaginatedStorageQuery { + vec![StorageQuery { key: hex_string(b":m"), query_type: StorageQueryType::DescendantsValues, - pagination_start_key: Some(hex_string(b":mo")), }] ], ) .await .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 0); - - assert_eq!(result[0].key, hex_string(b":moD")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"abcmoD"))); - }, - _ => panic!("Unexpected result"), - }; - // Continue with pagination. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: Some(hex_string(b":moD")), - }] - ], - ) - .await - .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 0); - - assert_eq!(result[0].key, hex_string(b":moc")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"abc"))); - }, - _ => panic!("Unexpected result"), - }; + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageError(_) + ); - // Continue with pagination. - let result: ArchiveStorageResult = api - .call( + // Valid call with storage at the key. + let mut sub = api + .subscribe_unbounded( "archive_unstable_storage", rpc_params![ &block_hash, - vec![PaginatedStorageQuery { + vec![StorageQuery { key: hex_string(b":m"), query_type: StorageQueryType::DescendantsValues, - pagination_start_key: Some(hex_string(b":moc")), }] ], ) .await .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 0); - assert_eq!(result[0].key, hex_string(b":mock")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"abcd"))); - }, - _ => panic!("Unexpected result"), - }; + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":m"), + result: StorageResultType::Value(hex_string(b"a")), + child_trie_key: None, + }) + ); - // Continue with pagination until no keys are returned. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::DescendantsValues, - pagination_start_key: Some(hex_string(b":mock")), - }] - ], - ) - .await - .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 0); - assert_eq!(discarded_items, 0); - }, - _ => panic!("Unexpected result"), - }; -} + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":mo"), + result: StorageResultType::Value(hex_string(b"ab")), + child_trie_key: None, + }) + ); -#[tokio::test] -async fn archive_storage_discarded_items() { - // One query at a time - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, 1); + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":moD"), + result: StorageResultType::Value(hex_string(b"abcmoD")), + child_trie_key: None, + }) + ); - // Import a new block with storage changes. - let mut builder = BlockBuilderBuilder::new(&*client) - .on_parent_block(client.chain_info().genesis_hash) - .with_parent_block_number(0) - .build() - .unwrap(); - builder.push_storage_change(b":m".to_vec(), Some(b"a".to_vec())).unwrap(); - let block = builder.build().unwrap().block; - let block_hash = format!("{:?}", block.header.hash()); - client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":moc"), + result: StorageResultType::Value(hex_string(b"abc")), + child_trie_key: None, + }) + ); - // Valid call with storage at the key. - let result: ArchiveStorageResult = api - .call( - "archive_unstable_storage", - rpc_params![ - &block_hash, - vec![ - PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::Value, - pagination_start_key: None, - }, - PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::Hash, - pagination_start_key: None, - }, - PaginatedStorageQuery { - key: hex_string(b":m"), - query_type: StorageQueryType::Hash, - pagination_start_key: None, - } - ] - ], - ) - .await - .unwrap(); - match result { - ArchiveStorageResult::Ok(ArchiveStorageMethodOk { result, discarded_items }) => { - assert_eq!(result.len(), 1); - assert_eq!(discarded_items, 2); + assert_eq!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::Storage(StorageResult { + key: hex_string(b":mock"), + result: StorageResultType::Value(hex_string(b"abcd")), + child_trie_key: None, + }) + ); - assert_eq!(result[0].key, hex_string(b":m")); - assert_eq!(result[0].result, StorageResultType::Value(hex_string(b"a"))); - }, - _ => panic!("Unexpected result"), - }; + assert_matches!( + get_next_event::(&mut sub).await, + ArchiveStorageEvent::StorageDone + ); } #[tokio::test] async fn archive_storage_diff_main_trie() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); let mut builder = BlockBuilderBuilder::new(&*client) .on_parent_block(client.chain_info().genesis_hash) @@ -965,7 +859,7 @@ async fn archive_storage_diff_main_trie() { #[tokio::test] async fn archive_storage_diff_no_changes() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); // Build 2 identical blocks. let mut builder = BlockBuilderBuilder::new(&*client) @@ -1012,7 +906,7 @@ async fn archive_storage_diff_no_changes() { #[tokio::test] async fn archive_storage_diff_deleted_changes() { - let (client, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (client, api) = setup_api(); // Blocks are imported as forks. let mut builder = BlockBuilderBuilder::new(&*client) @@ -1079,7 +973,7 @@ async fn archive_storage_diff_deleted_changes() { #[tokio::test] async fn archive_storage_diff_invalid_params() { let invalid_hash = hex_string(&INVALID_HASH); - let (_, api) = setup_api(MAX_PAGINATION_LIMIT, MAX_QUERIED_LIMIT); + let (_, api) = setup_api(); // Invalid shape for parameters. let items: Vec> = Vec::new(); diff --git a/substrate/client/rpc-spec-v2/src/chain_head/event.rs b/substrate/client/rpc-spec-v2/src/chain_head/event.rs index bd9863060910..de74145a3f08 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/event.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/event.rs @@ -235,7 +235,7 @@ pub struct OperationCallDone { pub output: String, } -/// The response of the `chainHead_call` method. +/// The response of the `chainHead_storage` method. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OperationStorageItems { @@ -536,6 +536,7 @@ mod tests { items: vec![StorageResult { key: "0x1".into(), result: StorageResultType::Value("0x123".to_string()), + child_trie_key: None, }], }); diff --git a/substrate/client/rpc-spec-v2/src/common/events.rs b/substrate/client/rpc-spec-v2/src/common/events.rs index 198a60bf4cac..44f722c0c61b 100644 --- a/substrate/client/rpc-spec-v2/src/common/events.rs +++ b/substrate/client/rpc-spec-v2/src/common/events.rs @@ -78,6 +78,10 @@ pub struct StorageResult { /// The result of the query. #[serde(flatten)] pub result: StorageResultType, + /// The child trie key if provided. + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub child_trie_key: Option, } /// The type of the storage query. @@ -105,23 +109,41 @@ pub struct StorageResultErr { /// The result of a storage call. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(untagged)] -pub enum ArchiveStorageResult { +#[serde(rename_all = "camelCase")] +#[serde(tag = "event")] +pub enum ArchiveStorageEvent { /// Query generated a result. - Ok(ArchiveStorageMethodOk), + Storage(StorageResult), /// Query encountered an error. - Err(ArchiveStorageMethodErr), + StorageError(ArchiveStorageMethodErr), + /// Operation storage is done. + StorageDone, } -impl ArchiveStorageResult { - /// Create a new `ArchiveStorageResult::Ok` result. - pub fn ok(result: Vec, discarded_items: usize) -> Self { - Self::Ok(ArchiveStorageMethodOk { result, discarded_items }) +impl ArchiveStorageEvent { + /// Create a new `ArchiveStorageEvent::StorageErr` event. + pub fn err(error: String) -> Self { + Self::StorageError(ArchiveStorageMethodErr { error }) } - /// Create a new `ArchiveStorageResult::Err` result. - pub fn err(error: String) -> Self { - Self::Err(ArchiveStorageMethodErr { error }) + /// Create a new `ArchiveStorageEvent::StorageResult` event. + pub fn result(result: StorageResult) -> Self { + Self::Storage(result) + } + + /// Checks if the event is a `StorageDone` event. + pub fn is_done(&self) -> bool { + matches!(self, Self::StorageDone) + } + + /// Checks if the event is a `StorageErr` event. + pub fn is_err(&self) -> bool { + matches!(self, Self::StorageError(_)) + } + + /// Checks if the event is a `StorageResult` event. + pub fn is_result(&self) -> bool { + matches!(self, Self::Storage(_)) } } @@ -354,8 +376,11 @@ mod tests { #[test] fn storage_result() { // Item with Value. - let item = - StorageResult { key: "0x1".into(), result: StorageResultType::Value("res".into()) }; + let item = StorageResult { + key: "0x1".into(), + result: StorageResultType::Value("res".into()), + child_trie_key: None, + }; // Encode let ser = serde_json::to_string(&item).unwrap(); let exp = r#"{"key":"0x1","value":"res"}"#; @@ -365,8 +390,11 @@ mod tests { assert_eq!(dec, item); // Item with Hash. - let item = - StorageResult { key: "0x1".into(), result: StorageResultType::Hash("res".into()) }; + let item = StorageResult { + key: "0x1".into(), + result: StorageResultType::Hash("res".into()), + child_trie_key: None, + }; // Encode let ser = serde_json::to_string(&item).unwrap(); let exp = r#"{"key":"0x1","hash":"res"}"#; @@ -379,6 +407,7 @@ mod tests { let item = StorageResult { key: "0x1".into(), result: StorageResultType::ClosestDescendantMerkleValue("res".into()), + child_trie_key: None, }; // Encode let ser = serde_json::to_string(&item).unwrap(); diff --git a/substrate/client/rpc-spec-v2/src/common/storage.rs b/substrate/client/rpc-spec-v2/src/common/storage.rs index 673e20b2bc78..a1e34d51530e 100644 --- a/substrate/client/rpc-spec-v2/src/common/storage.rs +++ b/substrate/client/rpc-spec-v2/src/common/storage.rs @@ -24,7 +24,7 @@ use sc_client_api::{Backend, ChildInfo, StorageKey, StorageProvider}; use sp_runtime::traits::Block as BlockT; use tokio::sync::mpsc; -use super::events::{StorageResult, StorageResultType}; +use super::events::{StorageQuery, StorageQueryType, StorageResult, StorageResultType}; use crate::hex_string; /// Call into the storage of blocks. @@ -70,9 +70,6 @@ pub enum IterQueryType { /// The result of making a query call. pub type QueryResult = Result, String>; -/// The result of iterating over keys. -pub type QueryIterResult = Result<(Vec, Option), String>; - impl Storage where Block: BlockT + 'static, @@ -97,6 +94,7 @@ where QueryResult::Ok(opt.map(|storage_data| StorageResult { key: hex_string(&key.0), result: StorageResultType::Value(hex_string(&storage_data.0)), + child_trie_key: child_key.map(|c| hex_string(&c.storage_key())), })) }) .unwrap_or_else(|error| QueryResult::Err(error.to_string())) @@ -120,6 +118,7 @@ where QueryResult::Ok(opt.map(|storage_data| StorageResult { key: hex_string(&key.0), result: StorageResultType::Hash(hex_string(&storage_data.as_ref())), + child_trie_key: child_key.map(|c| hex_string(&c.storage_key())), })) }) .unwrap_or_else(|error| QueryResult::Err(error.to_string())) @@ -149,6 +148,7 @@ where StorageResult { key: hex_string(&key.0), result: StorageResultType::ClosestDescendantMerkleValue(result), + child_trie_key: child_key.map(|c| hex_string(&c.storage_key())), } })) }) @@ -199,56 +199,6 @@ where } } - /// Iterate over at most the provided number of keys. - /// - /// Returns the storage result with a potential next key to resume iteration. - pub fn query_iter_pagination( - &self, - query: QueryIter, - hash: Block::Hash, - child_key: Option<&ChildInfo>, - count: usize, - ) -> QueryIterResult { - let QueryIter { ty, query_key, pagination_start_key } = query; - - let mut keys_iter = if let Some(child_key) = child_key { - self.client.child_storage_keys( - hash, - child_key.to_owned(), - Some(&query_key), - pagination_start_key.as_ref(), - ) - } else { - self.client.storage_keys(hash, Some(&query_key), pagination_start_key.as_ref()) - } - .map_err(|err| err.to_string())?; - - let mut ret = Vec::with_capacity(count); - let mut next_pagination_key = None; - for _ in 0..count { - let Some(key) = keys_iter.next() else { break }; - - next_pagination_key = Some(key.clone()); - - let result = match ty { - IterQueryType::Value => self.query_value(hash, &key, child_key), - IterQueryType::Hash => self.query_hash(hash, &key, child_key), - }?; - - if let Some(value) = result { - ret.push(value); - } - } - - // Save the next key if any to continue the iteration. - let maybe_next_query = keys_iter.next().map(|_| QueryIter { - ty, - query_key, - pagination_start_key: next_pagination_key, - }); - Ok((ret, maybe_next_query)) - } - /// Raw iterator over the keys. pub fn raw_keys_iter( &self, @@ -264,3 +214,96 @@ where keys_iter.map_err(|err| err.to_string()) } } + +/// Generates storage events for `chainHead_storage` and `archive_storage` subscriptions. +pub struct StorageSubscriptionClient { + /// Storage client. + client: Storage, + _phandom: PhantomData<(BE, Block)>, +} + +impl Clone for StorageSubscriptionClient { + fn clone(&self) -> Self { + Self { client: self.client.clone(), _phandom: PhantomData } + } +} + +impl StorageSubscriptionClient { + /// Constructs a new [`StorageSubscriptionClient`]. + pub fn new(client: Arc) -> Self { + Self { client: Storage::new(client), _phandom: PhantomData } + } +} + +impl StorageSubscriptionClient +where + Block: BlockT + 'static, + BE: Backend + 'static, + Client: StorageProvider + Send + Sync + 'static, +{ + /// Generate storage events to the provided sender. + pub async fn generate_events( + &mut self, + hash: Block::Hash, + items: Vec>, + child_key: Option, + tx: mpsc::Sender, + ) -> Result<(), tokio::task::JoinError> { + let this = self.clone(); + + tokio::task::spawn_blocking(move || { + for item in items { + match item.query_type { + StorageQueryType::Value => { + let rp = this.client.query_value(hash, &item.key, child_key.as_ref()); + if tx.blocking_send(rp).is_err() { + break; + } + }, + StorageQueryType::Hash => { + let rp = this.client.query_hash(hash, &item.key, child_key.as_ref()); + if tx.blocking_send(rp).is_err() { + break; + } + }, + StorageQueryType::ClosestDescendantMerkleValue => { + let rp = + this.client.query_merkle_value(hash, &item.key, child_key.as_ref()); + if tx.blocking_send(rp).is_err() { + break; + } + }, + StorageQueryType::DescendantsValues => { + let query = QueryIter { + query_key: item.key, + ty: IterQueryType::Value, + pagination_start_key: None, + }; + this.client.query_iter_pagination_with_producer( + query, + hash, + child_key.as_ref(), + &tx, + ) + }, + StorageQueryType::DescendantsHashes => { + let query = QueryIter { + query_key: item.key, + ty: IterQueryType::Hash, + pagination_start_key: None, + }; + this.client.query_iter_pagination_with_producer( + query, + hash, + child_key.as_ref(), + &tx, + ) + }, + } + } + }) + .await?; + + Ok(()) + } +} diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index 027a444012af..a47a05c0a190 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -756,8 +756,6 @@ where backend.clone(), genesis_hash, task_executor.clone(), - // Defaults to sensible limits for the `Archive`. - sc_rpc_spec_v2::archive::ArchiveConfig::default(), ) .into_rpc(); rpc_api.merge(archive_v2).map_err(|e| Error::Application(e.into()))?; From 1d519a1054d2edb8fc0b868eba6318fb3d448b33 Mon Sep 17 00:00:00 2001 From: Pavlo Khrystenko <45178695+pkhry@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:24:58 +0100 Subject: [PATCH 074/340] Update scale-info to 2.11.6 (#6681) # Description Updates scale-info to from 2.11.5 2.11.6, so that generated code is annotated with `allow(deprecated)` Pre-requisite for https://github.com/paritytech/polkadot-sdk/pull/6312 --- Cargo.lock | 8 +- Cargo.toml | 2 +- prdoc/pr_6681.prdoc | 406 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 411 insertions(+), 5 deletions(-) create mode 100644 prdoc/pr_6681.prdoc diff --git a/Cargo.lock b/Cargo.lock index 5e4e9c267b08..1fe2d766f16a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23715,9 +23715,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.5" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa7ffc1c0ef49b0452c6e2986abf2b07743320641ffd5fc63d552458e3b779b" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "bitvec", "cfg-if", @@ -23729,9 +23729,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.11.5" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46385cc24172cf615450267463f937c10072516359b3ff1cb24228a4a08bf951" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", diff --git a/Cargo.toml b/Cargo.toml index 964964908a9b..ecc385504181 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1197,7 +1197,7 @@ sc-tracing-proc-macro = { path = "substrate/client/tracing/proc-macro", default- sc-transaction-pool = { path = "substrate/client/transaction-pool", default-features = false } sc-transaction-pool-api = { path = "substrate/client/transaction-pool/api", default-features = false } sc-utils = { path = "substrate/client/utils", default-features = false } -scale-info = { version = "2.11.1", default-features = false } +scale-info = { version = "2.11.6", default-features = false } schemars = { version = "0.8.13", default-features = false } schnellru = { version = "0.2.3" } schnorrkel = { version = "0.11.4", default-features = false } diff --git a/prdoc/pr_6681.prdoc b/prdoc/pr_6681.prdoc new file mode 100644 index 000000000000..93a967d4a66c --- /dev/null +++ b/prdoc/pr_6681.prdoc @@ -0,0 +1,406 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: update scale-info to 2.11.6 + +doc: + - audience: Runtime Dev + description: | + Updates scale-info to 2.11.1 from 2.11.5. + Updated version of scale-info annotates generated code with `allow(deprecated)` + +crates: + - name: bridge-runtime-common + bump: none + - name: bp-header-chain + bump: none + - name: bp-runtime + bump: none + - name: frame-support + bump: none + - name: sp-core + bump: none + - name: sp-trie + bump: none + - name: sp-runtime + bump: none + - name: sp-application-crypto + bump: none + - name: sp-arithmetic + bump: none + - name: sp-weights + bump: none + - name: sp-api + bump: none + - name: sp-metadata-ir + bump: none + - name: sp-version + bump: none + - name: sp-inherents + bump: none + - name: frame-executive + bump: none + - name: frame-system + bump: none + - name: pallet-balances + bump: none + - name: frame-benchmarking + bump: none + - name: pallet-migrations + bump: none + - name: cumulus-pallet-parachain-system + bump: none + - name: cumulus-primitives-core + bump: none + - name: polkadot-core-primitives + bump: none + - name: polkadot-parachain-primitives + bump: none + - name: polkadot-primitives + bump: none + - name: sp-authority-discovery + bump: none + - name: sp-consensus-slots + bump: none + - name: sp-staking + bump: none + - name: staging-xcm + bump: none + - name: cumulus-primitives-parachain-inherent + bump: none + - name: pallet-message-queue + bump: none + - name: polkadot-runtime-common + bump: none + - name: frame-election-provider-support + bump: none + - name: sp-npos-elections + bump: none + - name: sp-consensus-grandpa + bump: none + - name: polkadot-primitives + bump: none + - name: sp-authority-discovery + bump: none + - name: sp-consensus-grandpa + bump: none + - name: sp-genesis-builder + bump: none + - name: sp-consensus-babe + bump: none + - name: sp-mixnet + bump: none + - name: sc-rpc-api + bump: none + - name: sp-session + bump: none + - name: sp-statement-store + bump: none + - name: sp-transaction-storage-proof + bump: none + - name: pallet-asset-rate + bump: none + - name: pallet-authorship + bump: none + - name: pallet-babe + bump: none + - name: pallet-session + bump: none + - name: pallet-timestamp + bump: none + - name: pallet-offences + bump: none + - name: pallet-staking + bump: none + - name: pallet-bags-list + bump: none + - name: pallet-broker + bump: none + - name: pallet-election-provider-multi-phase + bump: none + - name: pallet-fast-unstake + bump: none + - name: pallet-identity + bump: none + - name: pallet-transaction-payment + bump: none + - name: pallet-treasury + bump: none + - name: pallet-utility + bump: none + - name: pallet-collective + bump: none + - name: pallet-root-testing + bump: none + - name: pallet-vesting + bump: none + - name: polkadot-runtime-parachains + bump: none + - name: pallet-authority-discovery + bump: none + - name: pallet-mmr + bump: none + - name: sp-mmr-primitives + bump: none + - name: staging-xcm-executor + bump: none + - name: staging-xcm-builder + bump: none + - name: pallet-asset-conversion + bump: none + - name: pallet-assets + bump: none + - name: pallet-salary + bump: none + - name: pallet-ranked-collective + bump: none + - name: pallet-xcm + bump: none + - name: xcm-runtime-apis + bump: none + - name: pallet-grandpa + bump: none + - name: pallet-indices + bump: none + - name: pallet-sudo + bump: none + - name: sp-consensus-beefy + bump: none + - name: cumulus-primitives-storage-weight-reclaim + bump: none + - name: cumulus-pallet-aura-ext + bump: none + - name: pallet-aura + bump: none + - name: sp-consensus-aura + bump: none + - name: pallet-collator-selection + bump: none + - name: pallet-glutton + bump: none + - name: staging-parachain-info + bump: none + - name: westend-runtime + bump: none + - name: frame-metadata-hash-extension + bump: none + - name: frame-system-benchmarking + bump: none + - name: pallet-beefy + bump: none + - name: pallet-beefy-mmr + bump: none + - name: pallet-conviction-voting + bump: none + - name: pallet-scheduler + bump: none + - name: pallet-preimage + bump: none + - name: pallet-delegated-staking + bump: none + - name: pallet-nomination-pools + bump: none + - name: pallet-democracy + bump: none + - name: pallet-elections-phragmen + bump: none + - name: pallet-membership + bump: none + - name: pallet-multisig + bump: none + - name: polkadot-sdk-frame + bump: none + - name: pallet-dev-mode + bump: none + - name: pallet-verify-signature + bump: none + - name: pallet-nomination-pools-benchmarking + bump: none + - name: pallet-offences-benchmarking + bump: none + - name: pallet-im-online + bump: none + - name: pallet-parameters + bump: none + - name: pallet-proxy + bump: none + - name: pallet-recovery + bump: none + - name: pallet-referenda + bump: none + - name: pallet-society + bump: none + - name: pallet-state-trie-migration + bump: none + - name: pallet-whitelist + bump: none + - name: pallet-xcm-benchmarks + bump: none + - name: rococo-runtime + bump: none + - name: pallet-bounties + bump: none + - name: pallet-child-bounties + bump: none + - name: pallet-nis + bump: none + - name: pallet-tips + bump: none + - name: parachains-common + bump: none + - name: pallet-asset-tx-payment + bump: none + - name: cumulus-pallet-xcmp-queue + bump: none + - name: bp-xcm-bridge-hub-router + bump: none + - name: pallet-xcm-bridge-hub-router + bump: none + - name: assets-common + bump: none + - name: bp-messages + bump: none + - name: bp-parachains + bump: none + - name: bp-polkadot-core + bump: none + - name: bp-relayers + bump: none + - name: bp-xcm-bridge-hub + bump: none + - name: bridge-hub-common + bump: none + - name: snowbridge-core + bump: none + - name: snowbridge-beacon-primitives + bump: none + - name: snowbridge-ethereum + bump: none + - name: pallet-bridge-grandpa + bump: none + - name: pallet-bridge-messages + bump: none + - name: pallet-bridge-parachains + bump: none + - name: pallet-bridge-relayers + bump: none + - name: pallet-xcm-bridge-hub + bump: none + - name: cumulus-pallet-dmp-queue + bump: none + - name: cumulus-pallet-solo-to-para + bump: none + - name: cumulus-pallet-xcm + bump: none + - name: cumulus-ping + bump: none + - name: frame-benchmarking-pallet-pov + bump: none + - name: pallet-alliance + bump: none + - name: pallet-asset-conversion-ops + bump: none + - name: pallet-asset-conversion-tx-payment + bump: none + - name: pallet-assets-freezer + bump: none + - name: pallet-atomic-swap + bump: none + - name: pallet-collective-content + bump: none + - name: pallet-contracts + bump: none + - name: pallet-contracts-uapi + bump: none + - name: pallet-insecure-randomness-collective-flip + bump: none + - name: pallet-contracts-mock-network + bump: none + - name: xcm-simulator + bump: none + - name: pallet-core-fellowship + bump: none + - name: pallet-lottery + bump: none + - name: pallet-mixnet + bump: none + - name: pallet-nft-fractionalization + bump: none + - name: pallet-nfts + bump: none + - name: pallet-node-authorization + bump: none + - name: pallet-paged-list + bump: none + - name: pallet-remark + bump: none + - name: pallet-revive + bump: none + - name: pallet-revive-uapi + bump: none + - name: pallet-revive-eth-rpc + bump: none + - name: pallet-skip-feeless-payment + bump: none + - name: pallet-revive-mock-network + bump: none + - name: pallet-root-offences + bump: none + - name: pallet-safe-mode + bump: none + - name: pallet-scored-pool + bump: none + - name: pallet-statement + bump: none + - name: pallet-transaction-storage + bump: none + - name: pallet-tx-pause + bump: none + - name: pallet-uniques + bump: none + - name: snowbridge-outbound-queue-merkle-tree + bump: none + - name: snowbridge-pallet-ethereum-client + bump: none + - name: snowbridge-pallet-inbound-queue + bump: none + - name: snowbridge-router-primitives + bump: none + - name: snowbridge-pallet-outbound-queue + bump: none + - name: snowbridge-pallet-system + bump: none + - name: bp-asset-hub-rococo + bump: none + - name: bp-asset-hub-westend + bump: none + - name: bp-polkadot-bulletin + bump: none + - name: asset-hub-rococo-runtime + bump: none + - name: asset-hub-westend-runtime + bump: none + - name: bridge-hub-rococo-runtime + bump: none + - name: bridge-hub-westend-runtime + bump: none + - name: collectives-westend-runtime + bump: none + - name: coretime-rococo-runtime + bump: none + - name: coretime-westend-runtime + bump: none + - name: people-rococo-runtime + bump: none + - name: people-westend-runtime + bump: none + - name: penpal-runtime + bump: none + - name: contracts-rococo-runtime + bump: none + - name: glutton-westend-runtime + bump: none + - name: rococo-parachain-runtime + bump: none + - name: xcm-simulator-example + bump: none \ No newline at end of file From 5ad8780b653350050c6a854205de20c439aa7b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20R=2E=20Bald=C3=A9?= Date: Fri, 29 Nov 2024 19:35:06 +0000 Subject: [PATCH 075/340] People chain integration tests (#6377) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Made as a follow-up of https://github.com/polkadot-fellows/runtimes/pull/499 ## Integration N/A ## Review Notes N/A --------- Co-authored-by: Dónal Murray --- Cargo.lock | 1 + .../tests/people/people-westend/Cargo.toml | 1 + .../people-westend/src/tests/governance.rs | 503 ++++++++++++++++++ .../people/people-westend/src/tests/mod.rs | 1 + 4 files changed, 506 insertions(+) create mode 100644 cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs diff --git a/Cargo.lock b/Cargo.lock index 1fe2d766f16a..a945d148e051 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16662,6 +16662,7 @@ dependencies = [ "sp-runtime 31.0.1", "staging-xcm 7.0.0", "staging-xcm-executor 7.0.0", + "westend-runtime", "westend-runtime-constants 7.0.0", "westend-system-emulated-network", ] diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml index aa6eebc5458f..53acd038cdf5 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml @@ -21,6 +21,7 @@ sp-runtime = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true, default-features = true } westend-runtime-constants = { workspace = true, default-features = true } +westend-runtime = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs new file mode 100644 index 000000000000..3dadcdd94870 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs @@ -0,0 +1,503 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::imports::*; +use frame_support::traits::ProcessMessageError; + +use codec::Encode; +use frame_support::sp_runtime::traits::Dispatchable; +use parachains_common::AccountId; +use people_westend_runtime::people::IdentityInfo; +use westend_runtime::governance::pallet_custom_origins::Origin::GeneralAdmin as GeneralAdminOrigin; +use westend_system_emulated_network::people_westend_emulated_chain::people_westend_runtime; + +use pallet_identity::Data; + +use emulated_integration_tests_common::accounts::{ALICE, BOB}; + +#[test] +fn relay_commands_add_registrar() { + let (origin_kind, origin) = (OriginKind::Superuser, ::RuntimeOrigin::root()); + + let registrar: AccountId = [1; 32].into(); + Westend::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + type RuntimeEvent = ::RuntimeEvent; + type PeopleCall = ::RuntimeCall; + type PeopleRuntime = ::Runtime; + + let add_registrar_call = + PeopleCall::Identity(pallet_identity::Call::::add_registrar { + account: registrar.into(), + }); + + let xcm_message = RuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { origin_kind, call: add_registrar_call.encode().into() } + ]))), + }); + + assert_ok!(xcm_message.dispatch(origin)); + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + PeopleWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::Identity(pallet_identity::Event::RegistrarAdded { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {}, + ] + ); + }); +} + +#[test] +fn relay_commands_add_registrar_wrong_origin() { + let people_westend_alice = PeopleWestend::account_id_of(ALICE); + + let origins = vec![ + ( + OriginKind::SovereignAccount, + ::RuntimeOrigin::signed(people_westend_alice), + ), + (OriginKind::Xcm, GeneralAdminOrigin.into()), + ]; + + let mut signed_origin = true; + + for (origin_kind, origin) in origins { + let registrar: AccountId = [1; 32].into(); + Westend::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + type RuntimeEvent = ::RuntimeEvent; + type PeopleCall = ::RuntimeCall; + type PeopleRuntime = ::Runtime; + + let add_registrar_call = + PeopleCall::Identity(pallet_identity::Call::::add_registrar { + account: registrar.into(), + }); + + let xcm_message = RuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { origin_kind, call: add_registrar_call.encode().into() } + ]))), + }); + + assert_ok!(xcm_message.dispatch(origin)); + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + PeopleWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + if signed_origin { + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::ProcessingFailed { error: ProcessMessageError::Unsupported, .. }) => {}, + ] + ); + } else { + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {}, + ] + ); + } + }); + + signed_origin = false; + } +} + +#[test] +fn relay_commands_kill_identity() { + // To kill an identity, first one must be set + PeopleWestend::execute_with(|| { + type PeopleRuntime = ::Runtime; + type PeopleRuntimeEvent = ::RuntimeEvent; + + let people_westend_alice = + ::RuntimeOrigin::signed(PeopleWestend::account_id_of(ALICE)); + + let identity_info = IdentityInfo { + email: Data::Raw(b"test@test.io".to_vec().try_into().unwrap()), + ..Default::default() + }; + let identity: Box<::IdentityInformation> = + Box::new(identity_info); + + assert_ok!(::Identity::set_identity( + people_westend_alice, + identity + )); + + assert_expected_events!( + PeopleWestend, + vec![ + PeopleRuntimeEvent::Identity(pallet_identity::Event::IdentitySet { .. }) => {}, + ] + ); + }); + + let (origin_kind, origin) = (OriginKind::Superuser, ::RuntimeOrigin::root()); + + Westend::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + type PeopleCall = ::RuntimeCall; + type RuntimeEvent = ::RuntimeEvent; + type PeopleRuntime = ::Runtime; + + let kill_identity_call = + PeopleCall::Identity(pallet_identity::Call::::kill_identity { + target: people_westend_runtime::MultiAddress::Id(PeopleWestend::account_id_of( + ALICE, + )), + }); + + let xcm_message = RuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { origin_kind, call: kill_identity_call.encode().into() } + ]))), + }); + + assert_ok!(xcm_message.dispatch(origin)); + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + PeopleWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::Identity(pallet_identity::Event::IdentityKilled { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {}, + ] + ); + }); +} + +#[test] +fn relay_commands_kill_identity_wrong_origin() { + let people_westend_alice = PeopleWestend::account_id_of(BOB); + + let origins = vec![ + ( + OriginKind::SovereignAccount, + ::RuntimeOrigin::signed(people_westend_alice), + ), + (OriginKind::Xcm, GeneralAdminOrigin.into()), + ]; + + for (origin_kind, origin) in origins { + Westend::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + type PeopleCall = ::RuntimeCall; + type RuntimeEvent = ::RuntimeEvent; + type PeopleRuntime = ::Runtime; + + let kill_identity_call = + PeopleCall::Identity(pallet_identity::Call::::kill_identity { + target: people_westend_runtime::MultiAddress::Id(PeopleWestend::account_id_of( + ALICE, + )), + }); + + let xcm_message = RuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { origin_kind, call: kill_identity_call.encode().into() } + ]))), + }); + + assert_ok!(xcm_message.dispatch(origin)); + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + PeopleWestend::execute_with(|| { + assert_expected_events!(PeopleWestend, vec![]); + }); + } +} + +#[test] +fn relay_commands_add_remove_username_authority() { + let people_westend_alice = PeopleWestend::account_id_of(ALICE); + let people_westend_bob = PeopleWestend::account_id_of(BOB); + + let (origin_kind, origin, usr) = + (OriginKind::Superuser, ::RuntimeOrigin::root(), "rootusername"); + + // First, add a username authority. + Westend::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + type RuntimeEvent = ::RuntimeEvent; + type PeopleCall = ::RuntimeCall; + type PeopleRuntime = ::Runtime; + + let add_username_authority = + PeopleCall::Identity(pallet_identity::Call::::add_username_authority { + authority: people_westend_runtime::MultiAddress::Id(people_westend_alice.clone()), + suffix: b"suffix1".into(), + allocation: 10, + }); + + let add_authority_xcm_msg = RuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { origin_kind, call: add_username_authority.encode().into() } + ]))), + }); + + assert_ok!(add_authority_xcm_msg.dispatch(origin.clone())); + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + // Check events system-parachain-side + PeopleWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::Identity(pallet_identity::Event::AuthorityAdded { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {}, + ] + ); + }); + + // Now, use the previously added username authority to concede a username to an account. + PeopleWestend::execute_with(|| { + type PeopleRuntimeEvent = ::RuntimeEvent; + let full_username = [usr.to_owned(), ".suffix1".to_owned()].concat().into_bytes(); + + assert_ok!(::Identity::set_username_for( + ::RuntimeOrigin::signed(people_westend_alice.clone()), + people_westend_runtime::MultiAddress::Id(people_westend_bob.clone()), + full_username, + None, + true + )); + + assert_expected_events!( + PeopleWestend, + vec![ + PeopleRuntimeEvent::Identity(pallet_identity::Event::UsernameQueued { .. }) => {}, + ] + ); + }); + + // Accept the given username + PeopleWestend::execute_with(|| { + type PeopleRuntimeEvent = ::RuntimeEvent; + let full_username = [usr.to_owned(), ".suffix1".to_owned()].concat().into_bytes(); + + assert_ok!(::Identity::accept_username( + ::RuntimeOrigin::signed(people_westend_bob.clone()), + full_username.try_into().unwrap(), + )); + + assert_expected_events!( + PeopleWestend, + vec![ + PeopleRuntimeEvent::Identity(pallet_identity::Event::UsernameSet { .. }) => {}, + ] + ); + }); + + // Now, remove the username authority with another priviledged XCM call. + Westend::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + type RuntimeEvent = ::RuntimeEvent; + type PeopleCall = ::RuntimeCall; + type PeopleRuntime = ::Runtime; + + let remove_username_authority = PeopleCall::Identity(pallet_identity::Call::< + PeopleRuntime, + >::remove_username_authority { + authority: people_westend_runtime::MultiAddress::Id(people_westend_alice.clone()), + suffix: b"suffix1".into(), + }); + + let remove_authority_xcm_msg = RuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { origin_kind, call: remove_username_authority.encode().into() } + ]))), + }); + + assert_ok!(remove_authority_xcm_msg.dispatch(origin)); + + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + // Final event check. + PeopleWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + PeopleWestend, + vec![ + RuntimeEvent::Identity(pallet_identity::Event::AuthorityRemoved { .. }) => {}, + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {}, + ] + ); + }); +} + +#[test] +fn relay_commands_add_remove_username_authority_wrong_origin() { + let people_westend_alice = PeopleWestend::account_id_of(ALICE); + + let origins = vec![ + ( + OriginKind::SovereignAccount, + ::RuntimeOrigin::signed(people_westend_alice.clone()), + ), + (OriginKind::Xcm, GeneralAdminOrigin.into()), + ]; + + for (origin_kind, origin) in origins { + Westend::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + type RuntimeEvent = ::RuntimeEvent; + type PeopleCall = ::RuntimeCall; + type PeopleRuntime = ::Runtime; + + let add_username_authority = PeopleCall::Identity(pallet_identity::Call::< + PeopleRuntime, + >::add_username_authority { + authority: people_westend_runtime::MultiAddress::Id(people_westend_alice.clone()), + suffix: b"suffix1".into(), + allocation: 10, + }); + + let add_authority_xcm_msg = RuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { origin_kind, call: add_username_authority.encode().into() } + ]))), + }); + + assert_ok!(add_authority_xcm_msg.dispatch(origin.clone())); + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + // Check events system-parachain-side + PeopleWestend::execute_with(|| { + assert_expected_events!(PeopleWestend, vec![]); + }); + + Westend::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeCall = ::RuntimeCall; + type RuntimeEvent = ::RuntimeEvent; + type PeopleCall = ::RuntimeCall; + type PeopleRuntime = ::Runtime; + + let remove_username_authority = PeopleCall::Identity(pallet_identity::Call::< + PeopleRuntime, + >::remove_username_authority { + authority: people_westend_runtime::MultiAddress::Id(people_westend_alice.clone()), + suffix: b"suffix1".into(), + }); + + let remove_authority_xcm_msg = + RuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), + message: bx!(VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::SovereignAccount, + call: remove_username_authority.encode().into(), + } + ]))), + }); + + assert_ok!(remove_authority_xcm_msg.dispatch(origin)); + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + PeopleWestend::execute_with(|| { + assert_expected_events!(PeopleWestend, vec![]); + }); + } +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs index 08749b295dc2..b9ad9e3db467 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/mod.rs @@ -14,4 +14,5 @@ // limitations under the License. mod claim_assets; +mod governance; mod teleport; From 8eac4e887c827ea0bac8915901c305a05457a8d9 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Fri, 29 Nov 2024 23:28:34 +0200 Subject: [PATCH 076/340] network/libp2p-backend: Suppress warning adding already reserved node as reserved (#6703) Fixes https://github.com/paritytech/polkadot-sdk/issues/6598. --------- Co-authored-by: GitHub Action --- prdoc/pr_6703.prdoc | 7 +++++++ substrate/client/network/src/protocol_controller.rs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_6703.prdoc diff --git a/prdoc/pr_6703.prdoc b/prdoc/pr_6703.prdoc new file mode 100644 index 000000000000..2dd0962a3eea --- /dev/null +++ b/prdoc/pr_6703.prdoc @@ -0,0 +1,7 @@ +title: 'network/libp2p-backend: Suppress warning adding already reserved node as reserved' +doc: +- audience: Node Dev + description: Fixes https://github.com/paritytech/polkadot-sdk/issues/6598. +crates: +- name: sc-network + bump: patch diff --git a/substrate/client/network/src/protocol_controller.rs b/substrate/client/network/src/protocol_controller.rs index af7adb50907f..11f5321294d0 100644 --- a/substrate/client/network/src/protocol_controller.rs +++ b/substrate/client/network/src/protocol_controller.rs @@ -464,7 +464,7 @@ impl ProtocolController { /// maintain connections with such peers. fn on_add_reserved_peer(&mut self, peer_id: PeerId) { if self.reserved_nodes.contains_key(&peer_id) { - warn!( + debug!( target: LOG_TARGET, "Trying to add an already reserved node {peer_id} as reserved on {:?}.", self.set_id, From 5e0bcb0ee9788b7bb16ccfbda4fdc153b24c6386 Mon Sep 17 00:00:00 2001 From: eskimor Date: Sat, 30 Nov 2024 00:31:27 +0100 Subject: [PATCH 077/340] Let's be a bit less strict here. (#6662) This might actually happen in non malicious cases. Co-authored-by: eskimor --- polkadot/node/network/collator-protocol/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/polkadot/node/network/collator-protocol/src/error.rs b/polkadot/node/network/collator-protocol/src/error.rs index ae7f9a8c1fbc..598cdcf43900 100644 --- a/polkadot/node/network/collator-protocol/src/error.rs +++ b/polkadot/node/network/collator-protocol/src/error.rs @@ -122,7 +122,7 @@ impl SecondingError { PersistedValidationDataMismatch | CandidateHashMismatch | RelayParentMismatch | - Duplicate | ParentHeadDataMismatch | + ParentHeadDataMismatch | InvalidCoreIndex(_, _) | InvalidSessionIndex(_, _) | InvalidReceiptVersion(_) From d1fafa85fa1254af143b8e9b0ebf5d2731f8d91a Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Sun, 1 Dec 2024 17:30:09 +0100 Subject: [PATCH 078/340] [pallet-revive] eth-prc fix geth diff (#6608) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a bunch of differential tests to ensure that responses from eth-rpc matches the one from `geth` - These [tests](https://github.com/paritytech/polkadot-sdk/blob/pg/fix-geth-diff/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts) are not run in CI for now but can be run locally with ```bash cd revive/rpc/examples/js bun test ``` * EVM RPC server will not fail gas_estimation if no gas is specified, I updated pallet-revive to add an extra `skip_transfer` boolean check to replicate this behavior in our pallet * `eth_transact` and `bare_eth_transact` api have been updated to use `GenericTransaction` directly as this is what is used by `eth_estimateGas` and `eth_call` ## TODO - [ ] Add tests the new `skip_transfer` flag --------- Co-authored-by: GitHub Action Co-authored-by: Alexander Theißen --- Cargo.lock | 1 + .../assets/asset-hub-westend/src/lib.rs | 30 +- prdoc/pr_6608.prdoc | 14 + substrate/bin/node/runtime/src/lib.rs | 25 +- substrate/frame/revive/Cargo.toml | 1 + .../frame/revive/mock-network/src/tests.rs | 4 +- substrate/frame/revive/rpc/Cargo.toml | 2 +- .../revive/rpc/examples/js/abi/errorTester.ts | 106 ++++++ .../revive/rpc/examples/js/abi/event.json | 34 -- .../frame/revive/rpc/examples/js/abi/event.ts | 34 ++ .../revive/rpc/examples/js/abi/piggyBank.json | 65 ---- .../piggyBank.ts} | 19 +- .../revive/rpc/examples/js/abi/revert.json | 14 - .../frame/revive/rpc/examples/js/bun.lockb | Bin 45391 -> 33662 bytes .../rpc/examples/js/contracts/.solhint.json | 3 + .../rpc/examples/js/contracts/ErrorTester.sol | 51 +++ .../rpc/examples/js/contracts/PiggyBank.sol | 8 +- .../frame/revive/rpc/examples/js/package.json | 41 ++- .../rpc/examples/js/pvm/errorTester.polkavm | Bin 0 -> 12890 bytes .../revive/rpc/examples/js/src/balance.ts | 8 + .../rpc/examples/js/src/build-contracts.ts | 27 +- .../frame/revive/rpc/examples/js/src/event.ts | 40 +- .../rpc/examples/js/src/geth-diff-setup.ts | 162 ++++++++ .../rpc/examples/js/src/geth-diff.test.ts | 245 +++++++++++++ .../frame/revive/rpc/examples/js/src/lib.ts | 126 ++++--- .../revive/rpc/examples/js/src/piggy-bank.ts | 81 +++- .../revive/rpc/examples/js/src/revert.ts | 10 - .../revive/rpc/examples/js/src/transfer.ts | 15 +- .../js/types/ethers-contracts/Event.ts | 117 ------ .../js/types/ethers-contracts/PiggyBank.ts | 96 ----- .../js/types/ethers-contracts/Revert.ts | 78 ---- .../js/types/ethers-contracts/common.ts | 100 ----- .../factories/Event__factory.ts | 51 --- .../factories/Revert__factory.ts | 31 -- .../types/ethers-contracts/factories/index.ts | 6 - .../js/types/ethers-contracts/index.ts | 10 - .../frame/revive/rpc/revive_chain.metadata | Bin 658056 -> 659977 bytes substrate/frame/revive/rpc/src/client.rs | 125 ++++--- substrate/frame/revive/rpc/src/lib.rs | 44 +-- .../frame/revive/rpc/src/rpc_methods_gen.rs | 1 + .../frame/revive/rpc/src/subxt_client.rs | 12 +- substrate/frame/revive/rpc/src/tests.rs | 3 +- .../frame/revive/src/benchmarking/mod.rs | 2 +- .../frame/revive/src/evm/api/rlp_codec.rs | 18 +- .../frame/revive/src/evm/api/rpc_types.rs | 148 ++++---- .../frame/revive/src/evm/api/rpc_types_gen.rs | 24 +- substrate/frame/revive/src/evm/runtime.rs | 345 ++++++++++-------- substrate/frame/revive/src/exec.rs | 73 +++- substrate/frame/revive/src/lib.rs | 215 +++++++---- substrate/frame/revive/src/primitives.rs | 45 ++- substrate/frame/revive/src/storage/meter.rs | 52 ++- .../frame/revive/src/test_utils/builder.rs | 11 +- substrate/frame/revive/src/tests.rs | 12 +- .../frame/revive/src/tests/test_debug.rs | 5 +- substrate/frame/revive/src/wasm/mod.rs | 11 +- 55 files changed, 1553 insertions(+), 1248 deletions(-) create mode 100644 prdoc/pr_6608.prdoc create mode 100644 substrate/frame/revive/rpc/examples/js/abi/errorTester.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/event.json create mode 100644 substrate/frame/revive/rpc/examples/js/abi/event.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/piggyBank.json rename substrate/frame/revive/rpc/examples/js/{types/ethers-contracts/factories/PiggyBank__factory.ts => abi/piggyBank.ts} (62%) delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/revert.json create mode 100644 substrate/frame/revive/rpc/examples/js/contracts/.solhint.json create mode 100644 substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol create mode 100644 substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm create mode 100644 substrate/frame/revive/rpc/examples/js/src/balance.ts create mode 100644 substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts create mode 100644 substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/src/revert.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts diff --git a/Cargo.lock b/Cargo.lock index a945d148e051..bc2ebb2a057d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14633,6 +14633,7 @@ dependencies = [ "assert_matches", "bitflags 1.3.2", "derive_more 0.99.17", + "env_logger 0.11.3", "environmental", "ethereum-types 0.15.1", "frame-benchmarking 28.0.0", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index f20b6b1fece0..98d647d868db 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -124,7 +124,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_016_006, + spec_version: 1_016_008, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, @@ -2081,18 +2081,10 @@ impl_runtime_apis! { let account = ::AddressMapper::to_account_id(&address); System::account_nonce(account) } - fn eth_transact( - from: H160, - dest: Option, - value: U256, - input: Vec, - gas_limit: Option, - storage_deposit_limit: Option, - ) -> pallet_revive::EthContractResult + + fn eth_transact(tx: pallet_revive::evm::GenericTransaction) -> Result, pallet_revive::EthTransactError> { - use pallet_revive::AddressMapper; - let blockweights = ::BlockWeights::get(); - let origin = ::AddressMapper::to_account_id(&from); + let blockweights: BlockWeights = ::BlockWeights::get(); let encoded_size = |pallet_call| { let call = RuntimeCall::Revive(pallet_call); @@ -2101,15 +2093,9 @@ impl_runtime_apis! { }; Revive::bare_eth_transact( - origin, - dest, - value, - input, - gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + tx, + blockweights.max_block, encoded_size, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -2127,7 +2113,7 @@ impl_runtime_apis! { dest, value, gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, pallet_revive::DebugInfo::UnsafeDebug, pallet_revive::CollectEvents::UnsafeCollect, @@ -2149,7 +2135,7 @@ impl_runtime_apis! { RuntimeOrigin::signed(origin), value, gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), code, data, salt, diff --git a/prdoc/pr_6608.prdoc b/prdoc/pr_6608.prdoc new file mode 100644 index 000000000000..b9cd7008de47 --- /dev/null +++ b/prdoc/pr_6608.prdoc @@ -0,0 +1,14 @@ +title: '[pallet-revive] eth-prc fix geth diff' +doc: +- audience: Runtime Dev + description: |- + * Add a bunch of differential tests to ensure that responses from eth-rpc matches the one from `geth` + * EVM RPC server will not fail gas_estimation if no gas is specified, I updated pallet-revive to add an extra `skip_transfer` boolean check to replicate this behavior in our pallet + * `eth_transact` and `bare_eth_transact` api have been updated to use `GenericTransaction` directly as this is what is used by `eth_estimateGas` and `eth_call` +crates: +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor +- name: asset-hub-westend-runtime + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index bff263548087..faffcd23fbcf 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -3218,18 +3218,9 @@ impl_runtime_apis! { System::account_nonce(account) } - fn eth_transact( - from: H160, - dest: Option, - value: U256, - input: Vec, - gas_limit: Option, - storage_deposit_limit: Option, - ) -> pallet_revive::EthContractResult + fn eth_transact(tx: pallet_revive::evm::GenericTransaction) -> Result, pallet_revive::EthTransactError> { - use pallet_revive::AddressMapper; let blockweights: BlockWeights = ::BlockWeights::get(); - let origin = ::AddressMapper::to_account_id(&from); let encoded_size = |pallet_call| { let call = RuntimeCall::Revive(pallet_call); @@ -3238,15 +3229,9 @@ impl_runtime_apis! { }; Revive::bare_eth_transact( - origin, - dest, - value, - input, - gas_limit.unwrap_or(blockweights.max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + tx, + blockweights.max_block, encoded_size, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -3263,7 +3248,7 @@ impl_runtime_apis! { dest, value, gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, pallet_revive::DebugInfo::UnsafeDebug, pallet_revive::CollectEvents::UnsafeCollect, @@ -3284,7 +3269,7 @@ impl_runtime_apis! { RuntimeOrigin::signed(origin), value, gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), - storage_deposit_limit.unwrap_or(u128::MAX), + pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), code, data, salt, diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 677ef0e1367f..098a66df8dee 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -65,6 +65,7 @@ pallet-revive-fixtures = { workspace = true, default-features = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } hex-literal = { workspace = true } +env_logger = { workspace = true } # Polkadot SDK Dependencies pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/mock-network/src/tests.rs b/substrate/frame/revive/mock-network/src/tests.rs index bd05726a1a45..34f797c2b530 100644 --- a/substrate/frame/revive/mock-network/src/tests.rs +++ b/substrate/frame/revive/mock-network/src/tests.rs @@ -24,7 +24,7 @@ use frame_support::traits::{fungibles::Mutate, Currency}; use frame_system::RawOrigin; use pallet_revive::{ test_utils::{self, builder::*}, - Code, + Code, DepositLimit, }; use pallet_revive_fixtures::compile_module; use pallet_revive_uapi::ReturnErrorCode; @@ -52,7 +52,7 @@ fn instantiate_test_contract(name: &str) -> Contract { RawOrigin::Signed(ALICE).into(), Code::Upload(wasm), ) - .storage_deposit_limit(1_000_000_000_000) + .storage_deposit_limit(DepositLimit::Balance(1_000_000_000_000)) .build_and_unwrap_contract() }); diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 9f89b74c668f..fe9cc82dd4d9 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -67,13 +67,13 @@ hex = { workspace = true } hex-literal = { workspace = true, optional = true } scale-info = { workspace = true } secp256k1 = { workspace = true, optional = true, features = ["recovery"] } -env_logger = { workspace = true } ethabi = { version = "18.0.0" } [features] example = ["hex-literal", "rlp", "secp256k1", "subxt-signer"] [dev-dependencies] +env_logger = { workspace = true } static_init = { workspace = true } hex-literal = { workspace = true } pallet-revive-fixtures = { workspace = true } diff --git a/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts b/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts new file mode 100644 index 000000000000..93daf34e02b6 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts @@ -0,0 +1,106 @@ +export const abi = [ + { + inputs: [ + { + internalType: 'string', + name: 'message', + type: 'string', + }, + ], + name: 'CustomError', + type: 'error', + }, + { + inputs: [ + { + internalType: 'bool', + name: 'newState', + type: 'bool', + }, + ], + name: 'setState', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'state', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'triggerAssertError', + outputs: [], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'triggerCustomError', + outputs: [], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'triggerDivisionByZero', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'triggerOutOfBoundsError', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'triggerRequireError', + outputs: [], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'triggerRevertError', + outputs: [], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'valueMatch', + outputs: [], + stateMutability: 'payable', + type: 'function', + }, +] as const diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.json b/substrate/frame/revive/rpc/examples/js/abi/event.json deleted file mode 100644 index d36089fbc84e..000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/event.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "string", - "name": "message", - "type": "string" - } - ], - "name": "ExampleEvent", - "type": "event" - }, - { - "inputs": [], - "name": "triggerEvent", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.ts b/substrate/frame/revive/rpc/examples/js/abi/event.ts new file mode 100644 index 000000000000..c389e2daf1da --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/event.ts @@ -0,0 +1,34 @@ +export const abi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'sender', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + { + indexed: false, + internalType: 'string', + name: 'message', + type: 'string', + }, + ], + name: 'ExampleEvent', + type: 'event', + }, + { + inputs: [], + name: 'triggerEvent', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, +] as const diff --git a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json deleted file mode 100644 index 2c2cfd5f7533..000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "deposit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [], - "name": "getDeposit", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "withdrawAmount", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [ - { - "internalType": "uint256", - "name": "remainingBal", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts similarity index 62% rename from substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts rename to substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts index 0efea80ed2dc..3d44cd998ad1 100644 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/PiggyBank__factory.ts +++ b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts @@ -1,11 +1,4 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ - -import { Contract, Interface, type ContractRunner } from 'ethers' -import type { PiggyBank, PiggyBankInterface } from '../PiggyBank' - -const _abi = [ +export const abi = [ { inputs: [], stateMutability: 'nonpayable', @@ -70,13 +63,3 @@ const _abi = [ type: 'function', }, ] as const - -export class PiggyBank__factory { - static readonly abi = _abi - static createInterface(): PiggyBankInterface { - return new Interface(_abi) as PiggyBankInterface - } - static connect(address: string, runner?: ContractRunner | null): PiggyBank { - return new Contract(address, _abi, runner) as unknown as PiggyBank - } -} diff --git a/substrate/frame/revive/rpc/examples/js/abi/revert.json b/substrate/frame/revive/rpc/examples/js/abi/revert.json deleted file mode 100644 index be2945fcc0a5..000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/revert.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "doRevert", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/substrate/frame/revive/rpc/examples/js/bun.lockb b/substrate/frame/revive/rpc/examples/js/bun.lockb index 700dca51da2ad3f843e890258b59c16fd4df6457..0ff3d54157db21a636e30076634343a24c812dca 100755 GIT binary patch delta 9083 zcmeG?X;@UpvgZr~3@FH^4u~MAfFQ##AUh)niZIIJ!ib8>GK5h!83aV*0Ad7{1S~O% zpc2<;Hkah8i3@5pafwT!*EL=f;~HENm*9U39E zSNC*xaq0&_$6JCr$)KX9gr5`ge;?ae+b?_Z>T92NPgpE(-Sy$9H=3Pp4H{e(a=(({ zQgXf0SvzmMuD~#vs@!~{i`A-J!lein3{yTskEiJI7{uMNzJz6%Ziq+d%W3KAqS*y1 zMy<&&(O?~yA8RuV9yC`$A=O^+2i ztSt{fq9+QtBDO2(bcOo8ucp2h;h>P|0 zV-feqvM*v+#5RbX5&w#v5I%?4NsGj1`ie%x)R7i(AH;=-Y2r~o&crkgNS8l%(9P!` zr%sL^(LC3GH_>Wj?&=*KjV(s|e(lw|es;}r@y@5BfP?!TFPlEE{BZBowl{~b*)Z?+ z#gWlTM~i#?J;ZbHyc6;jvyM&vt?%Yi^KmI#bC!AuH%)7G5~O@~Ol2yJeRJ2_VaIp0 zx3s@EO`iRZ`ISJ6+)|%SHEk-lG>;hghp~r8M`YyDss0=9^l>#>leE_Vr}LJTq#}{oS6B{34hwAeKrN`>VG$@;W(?1<@-|imVWknU4uOJxCJ@qFF32%~GDIs)U=O0>Ch#1Qi7AAL z<$^d(Lh=bhhJ`R!eXQ2XZDMfb+ z<4UY$pjjD~{5d5m>;eV5%^;+Y+;|Tz=XeP36KX8NO>h)Pjq9Y=c2YlgQbFkRahzNw zQX@I)SSMwJJD9F+Vkh-pCv~@zlJ(%_$~vioNR80xg5IK|;ybBjozx|yqB%JyYo1E) zq&9X^cXgCg7;6I|E^=cPI_D$^cL_BWYvW%her4Di!v+gb8tHRC86e zNTqSqeWX%3N`d?58yvMADHTWc7BkFLjwaesEOnd#X5Enyi462a z#sOEF7vS2nK8UsC6SOjjLtS`j(a#tH+S$;C46YQ~ySYS|6rv8O9?iie!nk;7>*eaP zVNMQrOsO|0wxFKAe&X;+2uXN;MtA3!J8_y0QM zkAi1`1Hs?qN6D|ZFK9Y&>!i~c)!*3kxjVq;$&QrN z@rs?ygCA0#{cwZ3We+^jm`q`2PIC_j7o z*4XKn1CFjs+~zdSwsg&+I@fL!8w#&UZJY}Vd%SgfQ{auW|M+C8&0UZ5yPH(ILi2mt z;c8)-jvgI|X`|+o@VtC|MDjl)9qzaNPy?zv9+#_)Q|49{REC$| zYjCqqsQL%n^GHLf`-rG|Z|S!mw7r=b*Y@B>?{aukBG$$yFC1R*ebuV^A1#51#eX+WIbz=Ftq1UGV@cgNL$=#-( zjYo73Ef{{ctmbj))rHNEqsKS8+MWV$AF=jPWy`y*3#-o#-7vbrcZ>6k%-bLD_&Qqj zFyfQQJ%cjc&$jgRQSDUGiO}_VXo)C;!&lvx|1Ldif-L z82e>n>NlOEoZ?j-N_v|)2?edLBMdA|gCIRwu%EB_;SYg<+wl2~1cUY@flYwXVVN9_;ZElk?b=DE@R=$QAfbkPpac%4bO?9ss)?xklJ z?EUo@|2@AsIBuwnYxm2@J6@Z9`EPz3?=7kOE%)@zvJCs$zo|FG@2+XM;gy{GJo3ec zXKR;xGikqzGoV(AwoAU2^5}T(7m{l~mIZCKl+~ps)btLMwmvzSd2xGLqjR37x2oy# z(Q^;Bw$zxfxHkHz#rAjP=T82(ckRzlPc#P&?V=qXHanAWdcyBs(YYJN;rohW8+J6S zwMlJXZ|jla8h0UMLRMYT*MU8D%@8Esw)eZg_{hBLfz2yt%(<8PbWT~xjX533Z`VW} zf^c6kv_&gM;B7X}Q7^z@;!puk|?84HyNX2M65b#O3ct(`Jh2@P!CqnnlX?T7$ zvaacp-${#jHO%E8zl`MvqXi{bA(@B~%AnzzZx5gxDA*1cg|@hcOD)9ol1*tOq2GRI-EMeat<9 zja9NV2lk_stUt`eduVK zRv|t_s?U;{*q_Iji7T(7^4%l%@RskuPkAiKOj|Q{x zN;V$SFi(Kp;}gvIO)(KJdMmX_{L~>9dS?2L6!)AksBE~`>W{`8$r{t^%`2In8$Ucd zcl5ofTRva%sYmc*N$QXHUnIQSx+#0_r5EvU*{jC7Ikit;{aJnA=to^#e@xeQ^Y$ek z6CU)^f{>o%7;jrgt}=^3J6?=fySEHrl{#=HIWc z|I#5jf0bwdpB_w3*x#k$v0WS9_VuA#->Ih!opq$tD^hTzE@97@ZQl(IT%^iQTVmIE zrD*-ZfYTbSd}~p*OOo_Uw*HVt{>}=uzma^oammbyUX3gsYIu8$>)Nne;*!u?Ek(o3 zw^6dJ4 zMbE6eW$U{rJicqY?;RRiF>P$vW>e*tH{$QyD)SYdaXtR!-6d|?Cb`>b=TOVgi&@v&D ztDKxH5?#Pw6udK14EwPeLo&0mzM_5OKJL;#N z&~^4ORV0$&xdiX5<*33qmK6ePb&;RVutdI)3Zy$A}m+Mt0!zBK`UyP@D7}wMRCl0n@#rS%)BprBUN{1I1$hT~ z33*Ep0vR{}VF&_w26>1Kfha3`7MGGjsDq@M-slmO&P{2Bhmo_|9{j-Leoz-g|dmXAO1 z&%MZ$No2kf|3E&Hz`xrHSSj{L=HX)v{L8L@4U+fnGYn(}lLvPLW)Jzeqnj+KX_3TV97J3_N_I=tS|pL6&Dz4nckou}vV0UrK2T|gXP z3A*X)Dj}$>ayVlU*UmdDem2MboS$-zk0o^^|$9pl++EzQ(L!kR%`QwaRv7VeH z!z@J!vRt<2qnFQnY*pK_Q_wmRTtQ99!JVc2^bosoDOVnw^8LuV`hu1V6`<4U{e%|D ztG<97&KYQVyy^?c;U?mB`yl5}?8CRlhsDzQD1?Be`KE=>N6z>-1*e=Z@m@6@Z#mL| z503HS3ho;jP2LeB$g30!P8mUUrA%dE@oJMF#ntMo!neYX>GmUIXVHS z*Axci^Tm#@Pq{CyWm=WvO`veTr(l{1tezhd@IRRsxhYsyiS7Ti3uvvn7r@7j_&AFl z8-_bQZs6q!h`AZsHB0kV&^Z~p0`3<$vCJe9{IEdg$VdA$H6uHAZWu6EXIWac*F_+G zOB}!l(Z2tFfAdz^-e4iFP;3CVXFklP-W|5t-z4{@fn#S5^YBwXFxUE1{bt+VG0zMF zeCRG<)QQhpmhLPwaN^A2#9LxVKGyf%`}3yX7Y#gX5GXKTx=ZGR5e5N1AbELy)(GF_ms$;+o9588Q0&M@ zFw=YeZq~!u?x{h*vKySnw)v3fS5M2G=dK?Q1_3@Oy1I{a&$o9nCK@=3Zs1fc4&cM6 z7QLhItp2F@s6l`av~C@tPAwej-elm^c7vR1u_GUuJ=0><_pryE1cN}K4Xmw}Ir8zx z&+U5Iq`tGVQpoyCWZXJ!w1NBBIUlf0YP^3xV(pm@g8&!PL`O0yI-8mE&94Fj=T{pD zs}Vc$LC~Owa}q4A7M?K(_}W52jW~dhn*KIrW?cBIWpWLaJ%8d z@O&bcESS4!7#vt6b(o9CELzD`K|@ZkrIsBJ^&&Sb z!F;isZ3O-2fyO6&Rvt%W6sim0QJtd?oh5v{IpnQBL`aI>D2vc!%qmp*q^k@3WL@ZB zIJwHfqC}ORHmfK*&$~c2Bmk_J_v!yi4L0{m*;`7xG*o=(0!htcyueq~xn9J#Em@R5gwYry(!Qy zzZ3=XOL26#V^SmBze}ic)kQ_BLcfT#%)+#+tl2)QJdJl=eqnA}j=GH0ha=6SoL>>e zW?m_K4_Ugb|BlL)<5>81Spb}G{GfZfI?snc62i7M&QP_)&WMhKX)9fzc~u{{y($Wx zHTT&urD-&~BW(2?8#uimALh@E{S?4O|C9$s(jW_4`TvvD%UhyyJQ*qP) delta 15944 zcmc(G2VB$1vv?8`AyT9lr6W~BuhN?!qF8_kNC^-i5Nc>P0-gmGMO_hy-@E&N-dmWR-I?9lnc4C!nQz}*TFVpK5-ao9I@cc* zPpLoS7!^}Itw%0A{M`#~XU}Nfum`0IS*E{kYi^(rsc>r%>ZLVc$ul(Twb1lmzm7xrKIK+=5n&Lr&1_cnTcF1 zFIm76a*OhEIk!N4Y{5myu;4U7R%SAf&rRYcRh_jT+TdXu!K=7iE!44J z){F5x!0J#3+bF7NCUuzbm5Qjv6TsNwOipGNFNs3o3KH^p{3ObDXs!YD)qru&Sz^o= z;|MYK0jvgm7GS)lw8WSp#&3X$>1|?sPK1Red&PumFO372$Ch%X<4B2qN8v*MB zE)~;b0UJTx9k3x_9l+4Hqz|NG{0K0RN{)%^n*rm2762XwcqU*x2TQL z$YV23`u&Z@C6*(EbGQjUuZokunT%lEkF}Y&Ys7&?KQj}U7QXr$>7dJE6Nf zJF>Z(vF<^a*||kC6f|fzir5pbkBxbI?y=IE4h_wR*HhFsTORLxa^?ETb$2eC&M5eD zw!yb@brEN9%*^XU3@2!fUwjBYjo?4s3P?eM!x+0~CUQ2l^Rt&!HET2(i-FNZ^dl{wb$!;5Z2^BqJ z1Tmdp8;yjil|ec3&a`?8>d^9(e+fiiAW8$KAY0iHNPm!wyek}aEBdxm1KAVU6!d10 zGcyEg?ogv6Tc$H}CDcIA5(e5S@65aiHGin}i)Jd)$<}X}&hjBpv%`EChz>nOYSsZ2 zs|nhINjnFL)cgWef01Mu)eFvcY@igWG_w#WSSu;CQ^8sOIMlFFSO&A-Eec=}))iAl zhUv>QWq=qAL{M45nb`ofAgF;kvd+w2sCh$8G|VGKrVRKQOOOKhIWzO1hOMO&T2#pp zY-|u1Bje1x4{WTmG~o<=8Ij7O#xP6F$w0v>i^f_BHLNlXJ5ug8)JRp4jkG167^W^9 z)(+5wikA8lQ>8Q!D1-)J5uCylHPFNqJAERds6 zB32)%CZiWqP?xkD!v-!C4!9K~2}(AICGEys4kg@PnvnApYTjs@jGK}X9AsWVhnc6~ zB_bNRN|h-C4BTPBm1}?^a=h&jXZedzvjjfwB%Wv36un|`mHu_E5n0H2(o~dC8lV6r zR12s?33UKEse~-#J!uSOlm^IK8Px)su8cZlJmt^8RU-gY1YIcody%H0f;!|qnF6>Z zi5$37)|pnPg3=T`X`fY4Eg&~l)B$LYDzX>?L#U#(A)bs=>J-X2lrhAOp#qoB5TX>< zU#jabed;gyX~L=}+AV@o5K+3(U((ScY25zOw*JxwqQp{jrw=8Y&FL>)g3>6FDsVj# zl_L8~8~aPopyW@qGuI|d8U3Z5{iP3Ni58@T($qW|*>D+*Mj2{uj0;eTMBCKe>G~u| z2&G8`^D2~Lh?2Ddg~B6BtD(dpN}r)5;zk%!C@BQ(2$Z-)$lov9BgAz{ zj1^#s`Bq{+#<;z;7{h@>BpVoCT=*--UV)Q~NRk)}at6Qyxf0wTW6XCG^D)N#+ySr$ z;LsqFmw0@FfE#!NVC(~clO)DRBpiwae>7%7`k!FjA1+&j;uCRaNJ6rJF&=mlrb3d$ zm>xz@MVJBQNB}qoDO@ofV{BQfxc(yydz*APiG^h0{|JNs$tn0xFy0AE#TNd5!FbSr zDhTU=7b%8+x_yOsTm6guORSlHv3*5O|L6M`w1+gXp9kvy+4dDr`j7VUf3$x^m#rS`B|NnLSf*SvH`~BN~6iVNaK6FYy)%(mB z#e}Bp_uJn#7uI)=bTM~)|C`|(*S>23d^6psr=#?w-*FB;8S+b4^h)>2c)P{5+ntXe zoV0$1=95&nkCRNFK5tmdeTaDD?Z(Afbj*yE zyO!MgOSwkq2e@=lv7LbNRf9})y&aLq+@0$u+?&g*tjsef$EuYwKml6plV$xT7o)T>|Ygc3M6-BRlmcHtU7b zsP>VK>tbU3f9_y6zug_GZ5Cwg`jOon6%*Ea-RGlAvA}ZE)|P<0JMWg8ubgsXERW@1 zog|?h-dtp&Hxx$({odE*zin5P@xJ)SuS^fjY}W9c@V4sfwJnzA2THb0J9MFQ<94l# zh-rS?rl_}aJGpiyZ1=D0=mF|SRXkuhVhz_6E~yzyG3n7gU~Z(a)#JHTaEkA^uIjvFFVE#(%kf>a zVn@>|-N|2M&VB24d$eZ9vtM6C@NG|oO3inhw^rfE@}<8tNN9&IK4hX7d>UVJ<&sIo zVd?Zz-v#TlVq(V|I2tn^H`z2Z*Tx@Ns~i4cQv9hGT6Lj24g~%hvtKy6>qw5%^2QIJ z;^#S?9@Jxq+CV$S4XTb6*Kf8}{J|T!sg`wM<}5+p?;Dk~8!pMW)~R&ePTw<=yEnvd z<4;j5OibLz)(sBe<@|B0UB_mKG-HcP%u@;N;GwEN(H-}POm|NcKG0rw>!xMXjXv`| z6Piob?|W}Jm(%RB;a#`1>HLenug+*4vhq;(SuL|{-&tmA%G3>Ix+P&pH)rm0Y(k5S zHHCJ!n}m0G!*AJWER0~cT;Ar;r7>A+;ft^8HM!KgR@-W(sBy+mj%jkD%nR21joDnL zs@(D6Q039FIl2C-PgX5EAfX++UhPlxBYRTobps|acC6SoZPH0$?VveECi}MXUn}fR zX0Barv9E2k-=LR^BDG|al%{@GiWs(dN$HC9-N*cj{YL-pSo=0j9eJ2&3Vj_DkKH)C zf04UQS;@vExA8<|aEY=8@}C5T+ux5Aa(Vn%x<5YZ?6R>6Hymv7w(~DRHGGe{qdXf`6u$q zGv+rRdYV>z)K+=L;wzuN?b_ir{ek)!l^XZ@nQA%#tMs?Ij=$tO%UdhCc-e*<(L>RCQ%zw{%GGH_AtyL@Y#jrO^gYbXYZT|ciyCfO z{;*W%X!MwSoj21net(&)L|TouLYZ{ueG>dZHRZg9?!ediV@=Z2- z=OQNh2HK4qvr}pgZRFQXx$TR%bB?D)+pD{%HM~w(CnYxD4EtoG-xDsi{jg_`(vt4JE1ACs z?HM=S(qr&)htA23t0lC97X$r?Uc02^h}O(q_ZI7ZTCl75%=+UKn)>o?ZW@|he`R@3 zpS9eSPhA%GXm7QeOCB~obogrj;_jNogO0~2Sm;kln%Ud=IuONJXbM%#nW66;EgWv< z-sP2)=+89TvuATAH|TN6`48Q}=wPDpOX;5bzRD|}xz((&ug*RdLcJzuB|oyXIYrG` z`E!Vegm&x7gS(D>cn*!ln4b@%uvMCum3BpPM}-XPVRf=$Pd+ ztqpmbt{<@NEqOWT+B5a_`|dHkEz4q)mMbeG`4O7J0418|*)I!v-ej#A%(=kOvEZrR zc~#L@;2PFy-em8!#x#x*@X#^2a-@9ri_@}?mMW|||83li$M5tXo(^PI8=FgLr`N9= zoG5v(n(oW4Y}?l0xG&J~sn%v^{qppMlfIN%e_5Nebf#;;iJ#9reAHv76|uob7`SoF zh1t~$Ts>u6kt0eA#%@39X&H8UsLqt|MWv zzkOxZmXr5-^nd;|N!UfDR~Ys@(^I=Wy0RuVTzXoSOkVvl7Sds93aJsV+k6YA-|XmI zc$H`Ey*BXa^htN$t~_1i@T#)#uA!0nh9klgV0JX70i7GC zHTu^sO&jD@TD4uioX@-IcH`@a=%UYW%7>s!pq=T#X%AErXzq>{zKAm!{9A~y>N#UYD|J~Pi|%iFJ0zI{0_nm*KvW^7=< zoHNJOOF}UD_6zGxM_*3!ioFsd_D8#y4}O2ewLn*YdWTD0%2ticOSZ!H%gePc$__U> z8+m)j>~)&U?C+lrp2a9S&mN)Pm_M)5VDN@x5`xLcZ;{}(vTsM+>&mUw&uGXT>L@;T zeEY~#J!Y$R%9cfZ@PGBtDO$Luc3t;`E78Sj>n^;B3>5YnNkn$rpxL zI6Z#JU73~s>$KKo$rl!X^0vQXIymYa6#Jr~Zs~ z%*H!6&n?u|xV(bVs=A-s_+B+ZK}PoOZ-2CXjUUIUqWMknY^}e(OhPXC<^#*72ke^K z=Q=0*ny)b1aq7>v&^qOFU#Htg#ti8xxZlg}U2olK;iobNUl+T^-8EmL!&)-klV84h$)!Wv=ROgt{>lhg z+oB@q);U=ozV2Z|($g*gHv1IA6(_XcSAwV@dU$%#?8Hn^V8M`Z&$FnQsjQM zq_$K-uDyg@A^l~f;J}^>Ng3zv?6?%BZ(J0VczJv3Y3kclV~=xlda@5$S4Ye<+41Uy z+S$$zx4h~EU3xi`&cO9X&3mT@v?WcFyiiH=FKFHtsoCJAK8DQj_7I=%v03AJkNI_8vB3o^tx=7rx9idqLJO@e&$4 zqDo^n)d{_U+!>8FVN+dDHRP`7Gvp&tjVYVzhNxz2sym8=+ygPq*+|n#9%Y!1p?aYM z=4`4rQnFxEeGm_FUvvy|KcqE+P4!1PkO!dl;q1s!{p!P1YCQgBd5;l;$LEC?e=NQ0 zsXNyA*0g%sq9=EZwceV>g}KcO2`!!2^6)CR?a}t-ucy|$3hIdn&>QruLRV|bt(T1w z`Uj$OFh&qEuw+w5qp6SwqgKerAafR*8iGn8AB*llJ`UMhv8kbGKIG%kW5_2US8FzP zB3cUhB-9Rh81k`UQ^QduCZin4Q_wlcQ;~r)o618| zAx}fCkf$Sa7dDlTN+HiccOcJ1wytby7Mc%vHhK(s4sspIrskrhkPA>d*pH*TTmDMJ;0t)wgB*Q@XVUxKe>IGP46@i)T^r0*@GWdRPwMEtf9A8|N}MF?0O zJ@%fod%T}F6~%iN;pXyW6ptzh8$7_qlK5W!40#YkkfFm_bj4eaCZ~cPcqgbALI?;J zi-#cNSPrNHD}t2oPN-w)C&O@syT>77*$&j*E&-zyiAF!l;P)e?!SMwk6{kW}7VeJU z>h=90N|=}l0LI`@7b4+0eo2K@!M`G5UXHkq>EwOCKup7Lr}f3~TRHI0Q7iD@>N2}yM-N{jGR!@pug0Yn2#0l>dm5SOvh*eL7(Y&bRwI|Vxs z-@;)BVk5E9*pb+Y*qOKub}$A!GkAuu^N1fQ@f6|da|0L&fGu?ez+T6*gJ&QBz#jlF zSswsz051SM-Qqp@hcA>dAA3m(0DF@PfW5;77z;2O052J`&mV{tsUZvj${2uPfFyuK zfCK;zKs-PkKrBEEz!ZRJfGB`SfXM)CfCzwafG~hb0C;gu02mJt3NQ|!0057k50D2S z0LaCL4;5V{NQla5FNFlZ+fA zMB74BNAem$#+Cuc7DU5F5rPTWax$C_ICfTcSQ;7sN(S5!970h9edM8!I3!QV02bC1 zY`_VeSwe*ELDZfi)zbr;2l9TvvA2S56f$m=43q+6#7@{-DphoB@D_cVx&i86*iDY$_bM5WFlTqL;~dNkSaqZZeLU44fo7 zxnjM^0Aw|E7 zivf-!xL(0k5iXu3WaQ~UCo)u=j7_CcVHD!P_li{rd#};o_G8~-@fCUcdhL_f#`_M&46I@!!5Os)&9YF-eLMME}!y+c5 z&Z~7k25&ezD;PL1j1^py3lxw|tR_vUfP!P~XcY>mI5vcKUIAT<)zl_~&dE4hVowk* zTCcb)PE(rBGt*Nh?tr`Ul0|ku`ZycwsVNG|pcU5cO zKm+pAgF$&$llYh4(FQnuI%r0crZ)Mlg3Igap$dxgS_c}q>!JpbO@7?a8(UfIuFe$TQg&6(`#b ztpW<(N3&fVFH(yX`fL`8n(S{t=aWsG;KIulBK7DTyb@ zvrgpmFj%Dt5S2eHbf7)Uy1&fA``*qfFJ&4bPhd^nPUZ<%iCGyLoXjK^pO?vHadJ}x zK;sGo+{`>4hc7_<(nP24DApiXvIPs@Y}#Yl3A{`eDOjuyddF8~!D$PB(4Y{$N8++F zIJxObS%sN|_TQ7ly|_P8OEm6Zv<2z-E)=Byh4mdFU%*M>3i5J!nJKK~tlSJv9(I?( zcN7@nyE@3WK|5y{ppASPFTy{B6lnHW(JXw!OSplPo6DKXO5h}>=W!DFTo#`*H7lPO zZKNat+atke!67M{qQb1)Bz#Rx6C`oD+4w1ho0)`_)sd(}Z;2WUZb7l4)&qT^!9azD zueyYja=A(QiClb-m;_TYh$SRxUB^{H$D9`+cC*tRps7gS=^k7G#kHcX^m+BcUmXEU zKkZ*UeIlKbd9bf@x!HV9B3E>4VU-BN_=zYhRoC=83P*qk{4)t=&>lNLPf`-JldO_X z`rB0i4+dDq57rr8AHY%ietp3GJzh2c&xvyutVn&kq_)-%+;Dazxf!Nv(c=9wv4hD{>09wEs0Xth_VxOlw zx!`vTz{Lp8Rk)4E(ldCOJUFisQv z63Z4Fq$42+8cEcMT?12M)juZi`^zxZ_}&>CoC`||j;3tv(k$W}@&6MII{t}5EMi+? zwvZ1zE%g zmK8A(D&(UZ!4-WVi^lLwl!=dIfhc;YA%!X6=OUnD1T@qz!_a|zK?G#7NL&gD`%L14Vpt(@@^lg|(UF+%`;z^BPEp+34&I!KGWW&@8bLJMcnd9!<2S{M~+ z^Xrjgnl`e`7^=oe%H<>{PqpS|3Rs!=O3LR=Q6GH?R}1NGm`fYEUM-rbccR7Mj#SN|7sAwr`7 diff --git a/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json b/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json new file mode 100644 index 000000000000..ce2220e0b756 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/contracts/.solhint.json @@ -0,0 +1,3 @@ +{ + "extends": "solhint:recommended" +} diff --git a/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol b/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol new file mode 100644 index 000000000000..f1fdd219624a --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract ErrorTester { + bool public state; + + // Payable function that can be used to test insufficient funds errors + function valueMatch(uint256 value) public payable { + require(msg.value == value , "msg.value does not match value"); + } + + function setState(bool newState) public { + state = newState; + } + + // Trigger a require statement failure with a custom error message + function triggerRequireError() public pure { + require(false, "This is a require error"); + } + + // Trigger an assert statement failure + function triggerAssertError() public pure { + assert(false); + } + + // Trigger a revert statement with a custom error message + function triggerRevertError() public pure { + revert("This is a revert error"); + } + + // Trigger a division by zero error + function triggerDivisionByZero() public pure returns (uint256) { + uint256 a = 1; + uint256 b = 0; + return a / b; + } + + // Trigger an out-of-bounds array access + function triggerOutOfBoundsError() public pure returns (uint256) { + uint256[] memory arr = new uint256[](1); + return arr[2]; + } + + // Trigger a custom error + error CustomError(string message); + + function triggerCustomError() public pure { + revert CustomError("This is a custom error"); + } +} + diff --git a/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol b/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol index 1906c4658889..0c8a4d26f4dc 100644 --- a/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol +++ b/substrate/frame/revive/rpc/examples/js/contracts/PiggyBank.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; contract PiggyBank { - uint private balance; + uint256 private balance; address public owner; constructor() { @@ -11,16 +11,16 @@ contract PiggyBank { balance = 0; } - function deposit() public payable returns (uint) { + function deposit() public payable returns (uint256) { balance += msg.value; return balance; } - function getDeposit() public view returns (uint) { + function getDeposit() public view returns (uint256) { return balance; } - function withdraw(uint withdrawAmount) public returns (uint remainingBal) { + function withdraw(uint256 withdrawAmount) public returns (uint256 remainingBal) { require(msg.sender == owner); balance -= withdrawAmount; (bool success, ) = payable(msg.sender).call{value: withdrawAmount}(""); diff --git a/substrate/frame/revive/rpc/examples/js/package.json b/substrate/frame/revive/rpc/examples/js/package.json index 3ae1f0fbd799..6d8d00fd4214 100644 --- a/substrate/frame/revive/rpc/examples/js/package.json +++ b/substrate/frame/revive/rpc/examples/js/package.json @@ -1,22 +1,23 @@ { - "name": "demo", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "generate-types": "typechain --target=ethers-v6 'abi/*.json'" - }, - "dependencies": { - "@typechain/ethers-v6": "^0.5.1", - "ethers": "^6.13.4", - "solc": "^0.8.28", - "typechain": "^8.3.2" - }, - "devDependencies": { - "typescript": "^5.5.3", - "vite": "^5.4.8" - } + "name": "demo", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "ethers": "^6.13.4", + "solc": "^0.8.28", + "viem": "^2.21.47", + "@parity/revive": "^0.0.5" + }, + "devDependencies": { + "prettier": "^3.3.3", + "@types/bun": "^1.1.13", + "typescript": "^5.5.3", + "vite": "^5.4.8" + } } diff --git a/substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..aebe24c4c0f597fb3d0171a9f527bc86d2d77b93 GIT binary patch literal 12890 zcmds73v?URnVvg$tb0cq$+9w*M$$m!Fl3xG(7>8vsJkmtQ>0*Wyw+^0a=c3tVr(ZN zq_$(rk3(r<;z!cfh?F=Xv?&QmoP;gqWE%o$PulL1@Yu8MS^5GBp}b$G910X@+U$2n zvh!##ER8g&lh0EKoX=-b0T3k{l?_RH5y>QvG-J3m&n^r8luG{my2mVRky}eo5E!`#k zm$Zb8$@j~@mEV#LT2^v)NvUTEyPj|4>(raQ8+^BETm2oShlBIVE)D&2s4H}H=)0kZ zLeGcjl&Mp`QZ9wh46lro8STa+h8F#7v^lya`b5khzbKxKf7iVHj62T=oO$rfOQ+_h z4o$sh>N8Vm;>wD*Dzek|PgBlHoOR(@4QKt)+JE*1(|>A5DsQWNu9D0+Z^ptI-7^Mf z+&1II8T~V#oVoc^cYLa$JJ1tUSgH3C#VbkV)590>AubP04Rsy)?1kv?Dr}16<&q@N z$mwB*?}nVhf|AS!066|@iX@jEp+S?D8#JuT<%v+)kx(a5n8H0l`L;0QlI$mvBHo}4 z#ynC+BGc(Cg-)lH;$ewLLOe|IFvLSwXqW{R$~?>yR8&vUbGa;+9i+h;T3$`VRdRU} zR2sqxODa6!3G&YzV=OEy9JHp>#gdH8zF|6DApUCNBP}Lb6(p<4kWPk8vQ{JO6f$6tL9X5F z)gDl_F;yF2+F4vYH?UH6J=R~Cn)lW}GuBH;vq3uQ$%-WD&62(p>2XL~9cix(RPxZE zbW94&Ftkw~nt`9{My^gXw7iNA+F}N(ix?NV7#9^W{?5>DR<$7-*jWURx!|!Pc$XpO zG$aQO7r_s>;0KD}hYd07AuVuE5xn08?=OPyGqnAJd_d))jpz&bL`TRc!1r-gGPJvV z=m=cSL-z^rTmyUvU0wv&8roi;U<_t(m}402e3dLQ#lNGxlrJYO;9O&1s5thpm}+Q1 zyxhgrL8HJ!?1&H`t?^Hc)p)rk$&C9HHd~?dXc;L01OEWvR54t$$#Wbcr_Phh3X&H* z>rbUjkzA2b7*SM33IQmWZTT||HWFH%B{b|1xqJbED2?xy{_4@fTM$04kU$z~^m-tZ zUJcUeRUo!r31aK@LSnr_cI~w7bJ9NhLVL`)P}P#m=~bzb^>WCteaK$q9Fj?-G2W$2 z?IrdAHR_xJ9}U-Q_cN!>r+uE$n4@8N1I+36DNr{^=LeEsS9qE4r-~v&uP|4M26DyC z>@_s(Yq#JiWU0%5NNNKrjit^_zkBm-KRS<`o4)hKTa>R*mi||KZ}G77aQqhQc3zr} z8vmHNLGf4A5PwBAS{2%Cv{`7CXck(6$dLpQY7v@Eh>;*9T17~#hLHFyLQIR0a5W*3 zN^)E|nweHPp&Y9MB$6dfqoeJsV^4g=;TF%$_oCtO;3ihr%rd=P-f;|4VC7hRC(-*k&#A!Aba8Di zi+2&dUFD0fQEF0gIn7WHNw%5+v&6!0^YgL^MUhOAL z^QjOBC+98o-pvZDkG=H{W7?mNuU=@%zH8qxy1TL1Y4W%CER(TVf5m3WUoncd9&H3| z9oisTKiUnF9JxVq#bQJv#tjmQu9rw`vqa+SBx3eUBs?mS$e{EdV(}kfSC(wi$quqx zBV!8L!(G-kg0;Mx%l*g9_1s#oW(HLG&@sM&TabX9ObsV~i0HkHx5?(+%(|OqIw{#5 zG}md`A(rW;^1frdonqJH`-wiH@@~!A?8$U_$PR;ynVQcRzl-S1$G`8fzVEXb)4Lhp z$3M5$ZgtkG_H6s1%t4>FM(C$~8HqN=HzuZTB+g|jw#?pU+Zl^#Gri6ZRlAxAa|ccC zRZG>M!&nIeX5RWXW8eGZNvvh*KSB-9QA*SMSL}YL3@Y_;s@s6~lc8?su-p9wo}4-G z2gYuD_e7p3QljJqc=E)viI0~X-&8*-+*n;&;KsD>w;21zJ126Z%vH9JQz&+E63&ul z(xQ=8g)HH6?@2efb-V_5a}%9vp>neH-l!wAavHL3;u18MGrX zx?Gwa>g`t8z%v|(3W4Sj^;sS}(k~W33>!d>?D@@XpAbp&48YCAC?BJnZ5sU9I zw0$hJyBLcj4?rNkgCUL$4De8wz(ByggN23##x6JD-YL8mjbsDOMT`~^goj!L#voU( zF|?Kfh7&kc#5n9?94=xYop3!LjnoHN5rexJyoiC6;fBH(b%D!@7%3McRm4En&|ko) z4P02nsCF@`ix^0GRu?dm0lSD%>0(qCF_1Q`@@So&4{A3eh*=H9tcYLDW=3KNUE@jf z9zlc{wdN>CNN0}0(^nIuE+mSORzMK-#_NUoizD_m9TTaPK+2jsIf*p_pn;=Edj=R| zrJnKJV%ANsF}CCPB$6U+^`t#XIOf$cnW4b~ArBG;;hwu>cQ!&PkH z60f$&b4t2&^6nu~G#Wojsy8GBQpMyU?Ph{$K_1Bic4a*hMP>3V)H_)k+wc$)O>GaS z!K|i;6c9m_NH1Fp>EJhCr?Dh8>u9)^>h(145F_=z>7fT{e?zk;c<$$Z$5`fdyn>9t zZK1pZ=gq^Mx~$gpLB1LTNJAWdtA?ZR0Pa4oU+E;FfiiqX0O$QG_8)? z@OD~enwGgngRUVQxBAeZ<+fsVcrw#G{A$UX%}6MTV3P*r$O0NtJi*zHL1K$3GN9-J z5^N#yWyGjAhnY1j)95Tr4tWSQsxvv67d|lU=xaPyAIDSQ%k@6QfR4UGq$ng_1@}zivvs&(+lQxaGsrVBYxDy?Vpi)9`5?J4 z5yZ&CZHs*>RyAIFO$e_sOV4>4T0Ft?55CISFJ41-O^nKh)``@+YA5=C_}WR9t6Uij3p|H@H2h>oV(3pq0`UOK=OB^*WMokgp zVgo@FEC!8c1KmZ89=8b6BQSPx8cC{s6G%>ACJ*frgl37p&@6#;KPNXRG*qYB29jUb zz#lignt{wGq!q}KVNF%ta!&!UNsJ^!-PQX# z?INM{kSDtf9?4%}BKI-SbhIFvhQ`qpGzo3;(8pi#D}+Fgqx}@^5wyc-52O7%+CdaC zWkW*g=a?KroD@HZs^vTqLQFK}1;j~+k79_K;)t0{#7u6SbW{=rEv!RZ>w7;ZLL&zx z8oo!O=3a@$?viM9k3^^JlxS$1MB`f|8I=*2_7+L;B|ut1d{Zb2)~mI*q?{SJ}zkm3Pw0rfklt**28*$XnwlJ5m!ynC_1S{V~lSqkzQ@eXjwWsd!Jp`9i}x&xxgLFF_goVbdQr{9)Z6 z4*J8IKg|7M#qXJy>isfw1TT1k&Vw%cZ(j6=6JSv2w67@K5<}1bmW%tn7mxc9%XO0l zJ&bl(ew9ld0-9sQSwUEC+1KmCp_xYGq6eBRyB|8122w{1ChKlh<6=c4&f ziydlx?gYyzV*39Z%lZ1VlUdHi_x@JQ?LVJA#jbsL%ee|e!8T7=&TFzRg{7xbhHPA_);3`}+nP#tzwZIj`$u;7AVNEUb{E=S zw4G>s(6*s%aU-{4^n@S|!4$$LUl_4mF@RcfsXOEBGw^bM)fme2%=^bsurer?c!J9g z9uaK&@26pt?3_pwJ3TnRJe>o!2;o??-6pj_$Gtd*APp9mH%Q5>8QIPoCHdWGZKeVaJ?S4WC#SfqQe=${; zJvEuB>M3>SHtngunyLEbk&{hT-<`h^t3QbDzfzF5(xotAs;;kd30x%zoM@^}4Dvdg z+ByoBs!SeSNOR&EN20Yo;b%@uYrDc1W6qugjw{`v^dqinEG9QMPDlC{8>*@$XR4c~_f zlS;$0NF>QOc&tt>vr)_3haywGTVw)GoqJ5oGWQ{2c6g3jt9>NY$nTab8}W2Us+)p_uiLy2a8_F#yWC8%3c{%gK|5e~8|ii*L8MZ?_cRE)<0k zy^WU?cDmr5MKH>isDTc!056smy`m%J6<`#dQ4ZcBs-%Ir#nJ|9D7XYNw+Kenahs@+ zAWNcSjdW+0?r`Y#dJ>*R_teqbQ*qjmOZW&?`4j=cj_ zOM7!>gQjg_{;8DqAJ_bG?vE=t%C}qM_7S9B;))0Aj1Z71j2{3S%VP^;eRpIlfxdSuFe}(V9Y!pmqT;p^64FI#IuhW^oCE44GuuAZvB9E=Vzv z9)tGkv@b}8G&0P|T7?W!%%g|XUWMkyIk!&w*{=odryqyEBOPRgLOMC-D{gY+f}~H! zeGVb3qKFkhSqf(iGmHBPS-pMy6k@!#k17~2E-qrAw!gK2k)4po2VIN@ix@a#K-H{= zPI`=y;}gQZ$$fiM@ofk@viNR!LYkMj;3Y*cq#0HA9?!(-1;iDz^A^DcaqT&t=+wFO zvy(48?PGSYtvS7#z0{uREcI$De9i%{z1FUAaA7Q-wfDNW2dKC`uvf)VhO+PM0U|?ehW_LYG|_p z+ew>I8mG-1+E1H>^`y-~SaaH}L#HV2*hL!Mi(`p~4W7h$Dd7%u{-cVDOuwN;*mpPQ zPuwDvxS)ub+G)=X{QQKZKQSu({1<{V&;Q!R7m5G*r+uxE_$M|=cewcbetq15ScGaag_>OD6ge)6Yc{AAR4WrdZm{3X-idV zCTL9P1ueI@xZkNvZ1!KDqHzNe35uJZ=lDbZ`%HBlFXUDTg zPY=Ir)&Mhbjm2w=eMP|z@g)I|P+a|60IgZEBsq>X3{&Hb<&-9lY{&L-4 z9`u)M{&Maw$EMGYcO7&!0`ebp6+t6W#TkWaaAytkmz1&xP`;flH;Jl>7<#*8W9Yro z#Lz37FmM9bcrp0Gj;mWh;JS{)wII^eo>&e-O^Lo2*sz4=^u(E@7`#H@-B z;PRF?Gw3A|gYUrYGPr7!@58~6Cv%@CvkS7BK-_P20-=)c!1+;0W>*P`SXh*`5}mVn z51zD*cEgr(iW^>?L1AuJ8nnkG!6YfK!F@TotagfoEXR}BDeZj(5s4?UOWOU2bVt9X z&b0J~)Y<9$aPT|fIlQ8Ogs1cCQSlSM`z-)At+Ysjk2Tj@EWyrrg7`F^YLyFyLp zNBXUFY7T$}kRA$cnv-`vwK&yUTWhJ&Ibxu>XQy{sGX#b^Zm`0#D$=2#_>|%EwZXx` zRNlJDMFG~tU-`6cRh)CcCc4CrhVw6@Jpo#t!$ z$ukvOpLZWZazIqjPnU~d?oJBc;*$uuTuuo_xSyLg70|I%3?SC)O*dTO&P)vSUZRO% z@h1cn61Q#r`usN0Beot5l)vi2#XqUMQb2CVZv$t!vPyh`4=EW#+-O+^VkdE0Ol4dF z1Oc%n7^LAQF|QEVihuR|NELSI_!M2?KV<_1+}#GOEIrcDFgGvuqoLSNn3|wc)Q5B) zR4z#bt#KCNdsV9TESDJGObNyoR*tb}s5j~OKc7(3i8NN&zYQxsMT$WYOoKT?}1i8fRf-*mjwgy>*{?hrx+#BHgDpx~@{`_lZX)SN_+EAl1o P|DsnIq4bp_3F&_TlmMih literal 0 HcmV?d00001 diff --git a/substrate/frame/revive/rpc/examples/js/src/balance.ts b/substrate/frame/revive/rpc/examples/js/src/balance.ts new file mode 100644 index 000000000000..1261dcab7812 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/balance.ts @@ -0,0 +1,8 @@ +import { walletClient } from './lib.ts' + +const recipient = '0x8D97689C9818892B700e27F316cc3E41e17fBeb9' +try { + console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`) +} catch (err) { + console.error(err) +} diff --git a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts index c6b7700d1ccf..b25b5a7f2199 100644 --- a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts +++ b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts @@ -1,11 +1,23 @@ import { compile } from '@parity/revive' +import { format } from 'prettier' +import { parseArgs } from 'node:util' import solc from 'solc' import { readFileSync, writeFileSync } from 'fs' import { join } from 'path' type CompileInput = Parameters[0] -type CompileOutput = Awaited> -type Abi = CompileOutput['contracts'][string][string]['abi'] + +const { + values: { filter }, +} = parseArgs({ + args: process.argv.slice(2), + options: { + filter: { + type: 'string', + short: 'f', + }, + }, +}) function evmCompile(sources: CompileInput) { const input = { @@ -27,9 +39,9 @@ console.log('Compiling contracts...') const input = [ { file: 'Event.sol', contract: 'EventExample', keypath: 'event' }, - { file: 'Revert.sol', contract: 'RevertExample', keypath: 'revert' }, { file: 'PiggyBank.sol', contract: 'PiggyBank', keypath: 'piggyBank' }, -] + { file: 'ErrorTester.sol', contract: 'ErrorTester', keypath: 'errorTester' }, +].filter(({ keypath }) => !filter || keypath.includes(filter)) for (const { keypath, contract, file } of input) { const input = { @@ -41,7 +53,12 @@ for (const { keypath, contract, file } of input) { const out = JSON.parse(evmCompile(input)) const entry = out.contracts[file][contract] writeFileSync(join('evm', `${keypath}.bin`), Buffer.from(entry.evm.bytecode.object, 'hex')) - writeFileSync(join('abi', `${keypath}.json`), JSON.stringify(entry.abi, null, 2)) + writeFileSync( + join('abi', `${keypath}.ts`), + await format(`export const abi = ${JSON.stringify(entry.abi, null, 2)} as const`, { + parser: 'typescript', + }) + ) } { diff --git a/substrate/frame/revive/rpc/examples/js/src/event.ts b/substrate/frame/revive/rpc/examples/js/src/event.ts index 94cc2560272e..2e672a9772ff 100644 --- a/substrate/frame/revive/rpc/examples/js/src/event.ts +++ b/substrate/frame/revive/rpc/examples/js/src/event.ts @@ -1,15 +1,29 @@ //! Run with bun run script-event.ts -import { call, getContract, deploy } from './lib.ts' - -try { - const { abi, bytecode } = getContract('event') - const contract = await deploy(bytecode, abi) - const receipt = await call('triggerEvent', await contract.getAddress(), abi) - if (receipt) { - for (const log of receipt.logs) { - console.log('Event log:', JSON.stringify(log, null, 2)) - } - } -} catch (err) { - console.error(err) + +import { abi } from '../abi/event.ts' +import { assert, getByteCode, walletClient } from './lib.ts' + +const deployHash = await walletClient.deployContract({ + abi, + bytecode: getByteCode('event'), +}) +const deployReceipt = await walletClient.waitForTransactionReceipt({ hash: deployHash }) +const contractAddress = deployReceipt.contractAddress +console.log('Contract deployed:', contractAddress) +assert(contractAddress, 'Contract address should be set') + +const { request } = await walletClient.simulateContract({ + account: walletClient.account, + address: contractAddress, + abi, + functionName: 'triggerEvent', +}) + +const hash = await walletClient.writeContract(request) +const receipt = await walletClient.waitForTransactionReceipt({ hash }) +console.log(`Receipt: ${receipt.status}`) +console.log(`Logs receipt: ${receipt.status}`) + +for (const log of receipt.logs) { + console.log('Event log:', log) } diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts new file mode 100644 index 000000000000..92b20473d165 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts @@ -0,0 +1,162 @@ +import { spawn, spawnSync, Subprocess } from 'bun' +import { join, resolve } from 'path' +import { readFileSync } from 'fs' +import { createWalletClient, defineChain, Hex, http, publicActions } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' + +export function getByteCode(name: string, evm: boolean): Hex { + const bytecode = evm ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`) + return `0x${Buffer.from(bytecode).toString('hex')}` +} + +export type JsonRpcError = { + code: number + message: string + data: Hex +} + +export function killProcessOnPort(port: number) { + // Check which process is using the specified port + const result = spawnSync(['lsof', '-ti', `:${port}`]) + const output = result.stdout.toString().trim() + + if (output) { + console.log(`Port ${port} is in use. Killing process...`) + const pids = output.split('\n') + + // Kill each process using the port + for (const pid of pids) { + spawnSync(['kill', '-9', pid]) + console.log(`Killed process with PID: ${pid}`) + } + } +} + +export let jsonRpcErrors: JsonRpcError[] = [] +export async function createEnv(name: 'geth' | 'kitchensink') { + const gethPort = process.env.GETH_PORT || '8546' + const kitchensinkPort = process.env.KITCHENSINK_PORT || '8545' + const url = `http://localhost:${name == 'geth' ? gethPort : kitchensinkPort}` + const chain = defineChain({ + id: name == 'geth' ? 1337 : 420420420, + name, + nativeCurrency: { + name: 'Westie', + symbol: 'WST', + decimals: 18, + }, + rpcUrls: { + default: { + http: [url], + }, + }, + testnet: true, + }) + + const transport = http(url, { + onFetchResponse: async (response) => { + const raw = await response.clone().json() + if (raw.error) { + jsonRpcErrors.push(raw.error as JsonRpcError) + } + }, + }) + + const wallet = createWalletClient({ + transport, + chain, + }) + + const [account] = await wallet.getAddresses() + const serverWallet = createWalletClient({ + account, + transport, + chain, + }).extend(publicActions) + + const accountWallet = createWalletClient({ + account: privateKeyToAccount( + '0xa872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f' + ), + transport, + chain, + }).extend(publicActions) + + return { serverWallet, accountWallet, evm: name == 'geth' } +} + +// wait for http request to return 200 +export function waitForHealth(url: string) { + return new Promise((resolve, reject) => { + const start = Date.now() + const interval = setInterval(() => { + fetch(url) + .then((res) => { + if (res.status === 200) { + clearInterval(interval) + resolve() + } + }) + .catch(() => { + const elapsed = Date.now() - start + if (elapsed > 30_000) { + clearInterval(interval) + reject(new Error('hit timeout')) + } + }) + }, 1000) + }) +} + +export const procs: Subprocess[] = [] +const polkadotSdkPath = resolve(__dirname, '../../../../../../..') +if (!process.env.USE_LIVE_SERVERS) { + procs.push( + // Run geth on port 8546 + // + (() => { + killProcessOnPort(8546) + return spawn( + 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( + ' ' + ), + { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } + ) + })(), + //Run the substate node + (() => { + killProcessOnPort(9944) + return spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: polkadotSdkPath, + } + ) + })(), + // Run eth-rpc on 8545 + await (async () => { + killProcessOnPort(8545) + const proc = spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: polkadotSdkPath, + } + ) + await waitForHealth('http://localhost:8545/health').catch() + return proc + })() + ) +} diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts new file mode 100644 index 000000000000..468e7860bb9a --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -0,0 +1,245 @@ +import { jsonRpcErrors, procs, createEnv, getByteCode } from './geth-diff-setup.ts' +import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test' +import { encodeFunctionData, Hex, parseEther } from 'viem' +import { abi } from '../abi/errorTester' + +afterEach(() => { + jsonRpcErrors.length = 0 +}) + +afterAll(async () => { + procs.forEach((proc) => proc.kill()) +}) + +const envs = await Promise.all([createEnv('geth'), createEnv('kitchensink')]) + +for (const env of envs) { + describe(env.serverWallet.chain.name, () => { + let errorTesterAddr: Hex = '0x' + beforeAll(async () => { + const hash = await env.serverWallet.deployContract({ + abi, + bytecode: getByteCode('errorTester', env.evm), + }) + const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) + if (!deployReceipt.contractAddress) throw new Error('Contract address should be set') + errorTesterAddr = deployReceipt.contractAddress + }) + + test('triggerAssertError', async () => { + expect.assertions(3) + try { + await env.accountWallet.readContract({ + address: errorTesterAddr, + abi, + functionName: 'triggerAssertError', + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x4e487b710000000000000000000000000000000000000000000000000000000000000001' + ) + expect(lastJsonRpcError?.message).toBe('execution reverted: assert(false)') + } + }) + + test('triggerRevertError', async () => { + expect.assertions(3) + try { + await env.accountWallet.readContract({ + address: errorTesterAddr, + abi, + functionName: 'triggerRevertError', + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.message).toBe('execution reverted: This is a revert error') + expect(lastJsonRpcError?.data).toBe( + '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001654686973206973206120726576657274206572726f7200000000000000000000' + ) + } + }) + + test('triggerDivisionByZero', async () => { + expect.assertions(3) + try { + await env.accountWallet.readContract({ + address: errorTesterAddr, + abi, + functionName: 'triggerDivisionByZero', + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x4e487b710000000000000000000000000000000000000000000000000000000000000012' + ) + expect(lastJsonRpcError?.message).toBe( + 'execution reverted: division or modulo by zero' + ) + } + }) + + test('triggerOutOfBoundsError', async () => { + expect.assertions(3) + try { + await env.accountWallet.readContract({ + address: errorTesterAddr, + abi, + functionName: 'triggerOutOfBoundsError', + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x4e487b710000000000000000000000000000000000000000000000000000000000000032' + ) + expect(lastJsonRpcError?.message).toBe( + 'execution reverted: out-of-bounds access of an array or bytesN' + ) + } + }) + + test('triggerCustomError', async () => { + expect.assertions(3) + try { + await env.accountWallet.readContract({ + address: errorTesterAddr, + abi, + functionName: 'triggerCustomError', + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.data).toBe( + '0x8d6ea8be0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001654686973206973206120637573746f6d206572726f7200000000000000000000' + ) + expect(lastJsonRpcError?.message).toBe('execution reverted') + } + }) + + test('eth_call (not enough funds)', async () => { + expect.assertions(3) + try { + await env.accountWallet.simulateContract({ + address: errorTesterAddr, + abi, + functionName: 'valueMatch', + value: parseEther('10'), + args: [parseEther('10')], + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_call transfer (not enough funds)', async () => { + expect.assertions(3) + try { + await env.accountWallet.sendTransaction({ + to: '0x75E480dB528101a381Ce68544611C169Ad7EB342', + value: parseEther('10'), + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_estimate (not enough funds)', async () => { + expect.assertions(3) + try { + await env.accountWallet.estimateContractGas({ + address: errorTesterAddr, + abi, + functionName: 'valueMatch', + value: parseEther('10'), + args: [parseEther('10')], + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_estimate (revert)', async () => { + expect.assertions(3) + try { + await env.serverWallet.estimateContractGas({ + address: errorTesterAddr, + abi, + functionName: 'valueMatch', + value: parseEther('11'), + args: [parseEther('10')], + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(3) + expect(lastJsonRpcError?.message).toBe( + 'execution reverted: msg.value does not match value' + ) + expect(lastJsonRpcError?.data).toBe( + '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e6d73672e76616c756520646f6573206e6f74206d617463682076616c75650000' + ) + } + }) + + test('eth_get_balance (no account)', async () => { + const balance = await env.serverWallet.getBalance({ + address: '0x0000000000000000000000000000000000000123', + }) + expect(balance).toBe(0n) + }) + + test('eth_estimate (not enough funds to cover gas specified)', async () => { + expect.assertions(4) + try { + let balance = await env.serverWallet.getBalance(env.accountWallet.account) + expect(balance).toBe(0n) + + await env.accountWallet.estimateContractGas({ + address: errorTesterAddr, + abi, + functionName: 'setState', + args: [true], + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_estimate (no gas specified)', async () => { + let balance = await env.serverWallet.getBalance(env.accountWallet.account) + expect(balance).toBe(0n) + + const data = encodeFunctionData({ + abi, + functionName: 'setState', + args: [true], + }) + + await env.accountWallet.request({ + method: 'eth_estimateGas', + params: [ + { + data, + from: env.accountWallet.account.address, + to: errorTesterAddr, + }, + ], + }) + }) + }) +} diff --git a/substrate/frame/revive/rpc/examples/js/src/lib.ts b/substrate/frame/revive/rpc/examples/js/src/lib.ts index 975d8faf15b3..e1f0e780d95b 100644 --- a/substrate/frame/revive/rpc/examples/js/src/lib.ts +++ b/substrate/frame/revive/rpc/examples/js/src/lib.ts @@ -1,22 +1,11 @@ -import { - Contract, - ContractFactory, - JsonRpcProvider, - TransactionReceipt, - TransactionResponse, - Wallet, -} from 'ethers' import { readFileSync } from 'node:fs' -import type { compile } from '@parity/revive' import { spawn } from 'node:child_process' import { parseArgs } from 'node:util' -import { BaseContract } from 'ethers' - -type CompileOutput = Awaited> -type Abi = CompileOutput['contracts'][string][string]['abi'] +import { createWalletClient, defineChain, Hex, http, parseEther, publicActions } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' const { - values: { geth, westend, ['private-key']: privateKey }, + values: { geth, proxy, westend, endowment, ['private-key']: privateKey }, } = parseArgs({ args: process.argv.slice(2), options: { @@ -24,6 +13,13 @@ const { type: 'string', short: 'k', }, + endowment: { + type: 'string', + short: 'e', + }, + proxy: { + type: 'boolean', + }, geth: { type: 'boolean', }, @@ -42,7 +38,7 @@ if (geth) { '--http.api', 'web3,eth,debug,personal,net', '--http.port', - '8546', + process.env.GETH_PORT ?? '8546', '--dev', '--verbosity', '0', @@ -55,56 +51,78 @@ if (geth) { await new Promise((resolve) => setTimeout(resolve, 500)) } -export const provider = new JsonRpcProvider( - westend +const rpcUrl = proxy + ? 'http://localhost:8080' + : westend ? 'https://westend-asset-hub-eth-rpc.polkadot.io' : geth ? 'http://localhost:8546' : 'http://localhost:8545' -) -export const signer = privateKey ? new Wallet(privateKey, provider) : await provider.getSigner() -console.log(`Signer address: ${await signer.getAddress()}, Nonce: ${await signer.getNonce()}`) +export const chain = defineChain({ + id: geth ? 1337 : 420420420, + name: 'Asset Hub Westend', + network: 'asset-hub', + nativeCurrency: { + name: 'Westie', + symbol: 'WST', + decimals: 18, + }, + rpcUrls: { + default: { + http: [rpcUrl], + }, + }, + testnet: true, +}) + +const wallet = createWalletClient({ + transport: http(), + chain, +}) +const [account] = await wallet.getAddresses() +export const serverWalletClient = createWalletClient({ + account, + transport: http(), + chain, +}) + +export const walletClient = await (async () => { + if (privateKey) { + const account = privateKeyToAccount(`0x${privateKey}`) + console.log(`Wallet address ${account.address}`) + + const wallet = createWalletClient({ + account, + transport: http(), + chain, + }) + + if (endowment) { + await serverWalletClient.sendTransaction({ + to: account.address, + value: parseEther(endowment), + }) + console.log(`Endowed address ${account.address} with: ${endowment}`) + } + + return wallet.extend(publicActions) + } else { + return serverWalletClient.extend(publicActions) + } +})() /** * Get one of the pre-built contracts * @param name - the contract name */ -export function getContract(name: string): { abi: Abi; bytecode: string } { +export function getByteCode(name: string): Hex { const bytecode = geth ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`) - const abi = JSON.parse(readFileSync(`abi/${name}.json`, 'utf8')) as Abi - return { abi, bytecode: Buffer.from(bytecode).toString('hex') } + return `0x${Buffer.from(bytecode).toString('hex')}` } -/** - * Deploy a contract - * @returns the contract address - **/ -export async function deploy(bytecode: string, abi: Abi, args: any[] = []): Promise { - console.log('Deploying contract with', args) - const contractFactory = new ContractFactory(abi, bytecode, signer) - - const contract = await contractFactory.deploy(args) - await contract.waitForDeployment() - const address = await contract.getAddress() - console.log(`Contract deployed: ${address}`) - - return contract -} - -/** - * Call a contract - **/ -export async function call( - method: string, - address: string, - abi: Abi, - args: any[] = [], - opts: { value?: bigint } = {} -): Promise { - console.log(`Calling ${method} at ${address} with`, args, opts) - const contract = new Contract(address, abi, signer) - const tx = (await contract[method](...args, opts)) as TransactionResponse - console.log('Call transaction hash:', tx.hash) - return tx.wait() +export function assert(condition: any, message: string): asserts condition { + if (!condition) { + throw new Error(message) + } } diff --git a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts index 7a8edbde3662..0040b0c78dc4 100644 --- a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts +++ b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts @@ -1,24 +1,69 @@ -import { provider, call, getContract, deploy } from './lib.ts' -import { parseEther } from 'ethers' -import { PiggyBank } from '../types/ethers-contracts/PiggyBank' +import { assert, getByteCode, walletClient } from './lib.ts' +import { abi } from '../abi/piggyBank.ts' +import { parseEther } from 'viem' -try { - const { abi, bytecode } = getContract('piggyBank') - const contract = (await deploy(bytecode, abi)) as PiggyBank - const address = await contract.getAddress() +const hash = await walletClient.deployContract({ + abi, + bytecode: getByteCode('piggyBank'), +}) +const deployReceipt = await walletClient.waitForTransactionReceipt({ hash }) +const contractAddress = deployReceipt.contractAddress +console.log('Contract deployed:', contractAddress) +assert(contractAddress, 'Contract address should be set') - let receipt = await call('deposit', address, abi, [], { - value: parseEther('10.0'), +// Deposit 10 WST +{ + const result = await walletClient.estimateContractGas({ + account: walletClient.account, + address: contractAddress, + abi, + functionName: 'deposit', + value: parseEther('10'), }) - console.log('Deposit receipt:', receipt?.status) - console.log(`Contract balance: ${await provider.getBalance(address)}`) - console.log('deposit: ', await contract.getDeposit()) + console.log(`Gas estimate: ${result}`) - receipt = await call('withdraw', address, abi, [parseEther('5.0')]) - console.log('Withdraw receipt:', receipt?.status) - console.log(`Contract balance: ${await provider.getBalance(address)}`) - console.log('deposit: ', await contract.getDeposit()) -} catch (err) { - console.error(err) + const { request } = await walletClient.simulateContract({ + account: walletClient.account, + address: contractAddress, + abi, + functionName: 'deposit', + value: parseEther('10'), + }) + + request.nonce = 0 + const hash = await walletClient.writeContract(request) + + const receipt = await walletClient.waitForTransactionReceipt({ hash }) + console.log(`Deposit receipt: ${receipt.status}`) + if (process.env.STOP) { + process.exit(0) + } +} + +// Withdraw 5 WST +{ + const { request } = await walletClient.simulateContract({ + account: walletClient.account, + address: contractAddress, + abi, + functionName: 'withdraw', + args: [parseEther('5')], + }) + + const hash = await walletClient.writeContract(request) + const receipt = await walletClient.waitForTransactionReceipt({ hash }) + console.log(`Withdraw receipt: ${receipt.status}`) + + // Check remaining balance + const balance = await walletClient.readContract({ + address: contractAddress, + abi, + functionName: 'getDeposit', + }) + + console.log(`Get deposit: ${balance}`) + console.log( + `Get contract balance: ${await walletClient.getBalance({ address: contractAddress })}` + ) } diff --git a/substrate/frame/revive/rpc/examples/js/src/revert.ts b/substrate/frame/revive/rpc/examples/js/src/revert.ts deleted file mode 100644 index ea1bf4eceeb9..000000000000 --- a/substrate/frame/revive/rpc/examples/js/src/revert.ts +++ /dev/null @@ -1,10 +0,0 @@ -//! Run with bun run script-revert.ts -import { call, getContract, deploy } from './lib.ts' - -try { - const { abi, bytecode } = getContract('revert') - const contract = await deploy(bytecode, abi) - await call('doRevert', await contract.getAddress(), abi) -} catch (err) { - console.error(err) -} diff --git a/substrate/frame/revive/rpc/examples/js/src/transfer.ts b/substrate/frame/revive/rpc/examples/js/src/transfer.ts index ae2dd50f2af8..aef9a487b0c0 100644 --- a/substrate/frame/revive/rpc/examples/js/src/transfer.ts +++ b/substrate/frame/revive/rpc/examples/js/src/transfer.ts @@ -1,17 +1,18 @@ -import { parseEther } from 'ethers' -import { provider, signer } from './lib.ts' +import { parseEther } from 'viem' +import { walletClient } from './lib.ts' const recipient = '0x75E480dB528101a381Ce68544611C169Ad7EB342' try { - console.log(`Signer balance: ${await provider.getBalance(signer.address)}`) - console.log(`Recipient balance: ${await provider.getBalance(recipient)}`) - await signer.sendTransaction({ + console.log(`Signer balance: ${await walletClient.getBalance(walletClient.account)}`) + console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`) + + await walletClient.sendTransaction({ to: recipient, value: parseEther('1.0'), }) console.log(`Sent: ${parseEther('1.0')}`) - console.log(`Signer balance: ${await provider.getBalance(signer.address)}`) - console.log(`Recipient balance: ${await provider.getBalance(recipient)}`) + console.log(`Signer balance: ${await walletClient.getBalance(walletClient.account)}`) + console.log(`Recipient balance: ${await walletClient.getBalance({ address: recipient })}`) } catch (err) { console.error(err) } diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts deleted file mode 100644 index d65f953969f0..000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Event.ts +++ /dev/null @@ -1,117 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - BaseContract, - BigNumberish, - BytesLike, - FunctionFragment, - Result, - Interface, - EventFragment, - AddressLike, - ContractRunner, - ContractMethod, - Listener, -} from 'ethers' -import type { - TypedContractEvent, - TypedDeferredTopicFilter, - TypedEventLog, - TypedLogDescription, - TypedListener, - TypedContractMethod, -} from './common' - -export interface EventInterface extends Interface { - getFunction(nameOrSignature: 'triggerEvent'): FunctionFragment - - getEvent(nameOrSignatureOrTopic: 'ExampleEvent'): EventFragment - - encodeFunctionData(functionFragment: 'triggerEvent', values?: undefined): string - - decodeFunctionResult(functionFragment: 'triggerEvent', data: BytesLike): Result -} - -export namespace ExampleEventEvent { - export type InputTuple = [sender: AddressLike, value: BigNumberish, message: string] - export type OutputTuple = [sender: string, value: bigint, message: string] - export interface OutputObject { - sender: string - value: bigint - message: string - } - export type Event = TypedContractEvent - export type Filter = TypedDeferredTopicFilter - export type Log = TypedEventLog - export type LogDescription = TypedLogDescription -} - -export interface Event extends BaseContract { - connect(runner?: ContractRunner | null): Event - waitForDeployment(): Promise - - interface: EventInterface - - queryFilter( - event: TCEvent, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise>> - queryFilter( - filter: TypedDeferredTopicFilter, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise>> - - on( - event: TCEvent, - listener: TypedListener - ): Promise - on( - filter: TypedDeferredTopicFilter, - listener: TypedListener - ): Promise - - once( - event: TCEvent, - listener: TypedListener - ): Promise - once( - filter: TypedDeferredTopicFilter, - listener: TypedListener - ): Promise - - listeners( - event: TCEvent - ): Promise>> - listeners(eventName?: string): Promise> - removeAllListeners(event?: TCEvent): Promise - - triggerEvent: TypedContractMethod<[], [void], 'nonpayable'> - - getFunction(key: string | FunctionFragment): T - - getFunction(nameOrSignature: 'triggerEvent'): TypedContractMethod<[], [void], 'nonpayable'> - - getEvent( - key: 'ExampleEvent' - ): TypedContractEvent< - ExampleEventEvent.InputTuple, - ExampleEventEvent.OutputTuple, - ExampleEventEvent.OutputObject - > - - filters: { - 'ExampleEvent(address,uint256,string)': TypedContractEvent< - ExampleEventEvent.InputTuple, - ExampleEventEvent.OutputTuple, - ExampleEventEvent.OutputObject - > - ExampleEvent: TypedContractEvent< - ExampleEventEvent.InputTuple, - ExampleEventEvent.OutputTuple, - ExampleEventEvent.OutputObject - > - } -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts deleted file mode 100644 index ca137fcc8b30..000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/PiggyBank.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - BaseContract, - BigNumberish, - BytesLike, - FunctionFragment, - Result, - Interface, - ContractRunner, - ContractMethod, - Listener, -} from 'ethers' -import type { - TypedContractEvent, - TypedDeferredTopicFilter, - TypedEventLog, - TypedListener, - TypedContractMethod, -} from './common' - -export interface PiggyBankInterface extends Interface { - getFunction(nameOrSignature: 'deposit' | 'getDeposit' | 'owner' | 'withdraw'): FunctionFragment - - encodeFunctionData(functionFragment: 'deposit', values?: undefined): string - encodeFunctionData(functionFragment: 'getDeposit', values?: undefined): string - encodeFunctionData(functionFragment: 'owner', values?: undefined): string - encodeFunctionData(functionFragment: 'withdraw', values: [BigNumberish]): string - - decodeFunctionResult(functionFragment: 'deposit', data: BytesLike): Result - decodeFunctionResult(functionFragment: 'getDeposit', data: BytesLike): Result - decodeFunctionResult(functionFragment: 'owner', data: BytesLike): Result - decodeFunctionResult(functionFragment: 'withdraw', data: BytesLike): Result -} - -export interface PiggyBank extends BaseContract { - connect(runner?: ContractRunner | null): PiggyBank - waitForDeployment(): Promise - - interface: PiggyBankInterface - - queryFilter( - event: TCEvent, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise>> - queryFilter( - filter: TypedDeferredTopicFilter, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise>> - - on( - event: TCEvent, - listener: TypedListener - ): Promise - on( - filter: TypedDeferredTopicFilter, - listener: TypedListener - ): Promise - - once( - event: TCEvent, - listener: TypedListener - ): Promise - once( - filter: TypedDeferredTopicFilter, - listener: TypedListener - ): Promise - - listeners( - event: TCEvent - ): Promise>> - listeners(eventName?: string): Promise> - removeAllListeners(event?: TCEvent): Promise - - deposit: TypedContractMethod<[], [bigint], 'payable'> - - getDeposit: TypedContractMethod<[], [bigint], 'view'> - - owner: TypedContractMethod<[], [string], 'view'> - - withdraw: TypedContractMethod<[withdrawAmount: BigNumberish], [bigint], 'nonpayable'> - - getFunction(key: string | FunctionFragment): T - - getFunction(nameOrSignature: 'deposit'): TypedContractMethod<[], [bigint], 'payable'> - getFunction(nameOrSignature: 'getDeposit'): TypedContractMethod<[], [bigint], 'view'> - getFunction(nameOrSignature: 'owner'): TypedContractMethod<[], [string], 'view'> - getFunction( - nameOrSignature: 'withdraw' - ): TypedContractMethod<[withdrawAmount: BigNumberish], [bigint], 'nonpayable'> - - filters: {} -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts deleted file mode 100644 index ad6e23b38a65..000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/Revert.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - BaseContract, - BytesLike, - FunctionFragment, - Result, - Interface, - ContractRunner, - ContractMethod, - Listener, -} from 'ethers' -import type { - TypedContractEvent, - TypedDeferredTopicFilter, - TypedEventLog, - TypedListener, - TypedContractMethod, -} from './common' - -export interface RevertInterface extends Interface { - getFunction(nameOrSignature: 'doRevert'): FunctionFragment - - encodeFunctionData(functionFragment: 'doRevert', values?: undefined): string - - decodeFunctionResult(functionFragment: 'doRevert', data: BytesLike): Result -} - -export interface Revert extends BaseContract { - connect(runner?: ContractRunner | null): Revert - waitForDeployment(): Promise - - interface: RevertInterface - - queryFilter( - event: TCEvent, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise>> - queryFilter( - filter: TypedDeferredTopicFilter, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise>> - - on( - event: TCEvent, - listener: TypedListener - ): Promise - on( - filter: TypedDeferredTopicFilter, - listener: TypedListener - ): Promise - - once( - event: TCEvent, - listener: TypedListener - ): Promise - once( - filter: TypedDeferredTopicFilter, - listener: TypedListener - ): Promise - - listeners( - event: TCEvent - ): Promise>> - listeners(eventName?: string): Promise> - removeAllListeners(event?: TCEvent): Promise - - doRevert: TypedContractMethod<[], [void], 'nonpayable'> - - getFunction(key: string | FunctionFragment): T - - getFunction(nameOrSignature: 'doRevert'): TypedContractMethod<[], [void], 'nonpayable'> - - filters: {} -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts deleted file mode 100644 index 247b9468ece2..000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/common.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - FunctionFragment, - Typed, - EventFragment, - ContractTransaction, - ContractTransactionResponse, - DeferredTopicFilter, - EventLog, - TransactionRequest, - LogDescription, -} from 'ethers' - -export interface TypedDeferredTopicFilter<_TCEvent extends TypedContractEvent> - extends DeferredTopicFilter {} - -export interface TypedContractEvent< - InputTuple extends Array = any, - OutputTuple extends Array = any, - OutputObject = any, -> { - ( - ...args: Partial - ): TypedDeferredTopicFilter> - name: string - fragment: EventFragment - getFragment(...args: Partial): EventFragment -} - -type __TypechainAOutputTuple = T extends TypedContractEvent ? W : never -type __TypechainOutputObject = - T extends TypedContractEvent ? V : never - -export interface TypedEventLog extends Omit { - args: __TypechainAOutputTuple & __TypechainOutputObject -} - -export interface TypedLogDescription - extends Omit { - args: __TypechainAOutputTuple & __TypechainOutputObject -} - -export type TypedListener = ( - ...listenerArg: [...__TypechainAOutputTuple, TypedEventLog, ...undefined[]] -) => void - -export type MinEthersFactory = { - deploy(...a: ARGS[]): Promise -} - -export type GetContractTypeFromFactory = F extends MinEthersFactory ? C : never -export type GetARGsTypeFromFactory = - F extends MinEthersFactory ? Parameters : never - -export type StateMutability = 'nonpayable' | 'payable' | 'view' - -export type BaseOverrides = Omit -export type NonPayableOverrides = Omit -export type PayableOverrides = Omit -export type ViewOverrides = Omit -export type Overrides = S extends 'nonpayable' - ? NonPayableOverrides - : S extends 'payable' - ? PayableOverrides - : ViewOverrides - -export type PostfixOverrides, S extends StateMutability> = - | A - | [...A, Overrides] -export type ContractMethodArgs, S extends StateMutability> = PostfixOverrides< - { [I in keyof A]-?: A[I] | Typed }, - S -> - -export type DefaultReturnType = R extends Array ? R[0] : R - -// export interface ContractMethod = Array, R = any, D extends R | ContractTransactionResponse = R | ContractTransactionResponse> { -export interface TypedContractMethod< - A extends Array = Array, - R = any, - S extends StateMutability = 'payable', -> { - ( - ...args: ContractMethodArgs - ): S extends 'view' ? Promise> : Promise - - name: string - - fragment: FunctionFragment - - getFragment(...args: ContractMethodArgs): FunctionFragment - - populateTransaction(...args: ContractMethodArgs): Promise - staticCall(...args: ContractMethodArgs): Promise> - send(...args: ContractMethodArgs): Promise - estimateGas(...args: ContractMethodArgs): Promise - staticCallResult(...args: ContractMethodArgs): Promise -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts deleted file mode 100644 index 2e16b18a7ed8..000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Event__factory.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ - -import { Contract, Interface, type ContractRunner } from 'ethers' -import type { Event, EventInterface } from '../Event' - -const _abi = [ - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'sender', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - { - indexed: false, - internalType: 'string', - name: 'message', - type: 'string', - }, - ], - name: 'ExampleEvent', - type: 'event', - }, - { - inputs: [], - name: 'triggerEvent', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const - -export class Event__factory { - static readonly abi = _abi - static createInterface(): EventInterface { - return new Interface(_abi) as EventInterface - } - static connect(address: string, runner?: ContractRunner | null): Event { - return new Contract(address, _abi, runner) as unknown as Event - } -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts deleted file mode 100644 index ece1c6b5426e..000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/Revert__factory.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ - -import { Contract, Interface, type ContractRunner } from 'ethers' -import type { Revert, RevertInterface } from '../Revert' - -const _abi = [ - { - inputs: [], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [], - name: 'doRevert', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const - -export class Revert__factory { - static readonly abi = _abi - static createInterface(): RevertInterface { - return new Interface(_abi) as RevertInterface - } - static connect(address: string, runner?: ContractRunner | null): Revert { - return new Contract(address, _abi, runner) as unknown as Revert - } -} diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts deleted file mode 100644 index 67370dba411c..000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/factories/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -export { Event__factory } from './Event__factory' -export { PiggyBank__factory } from './PiggyBank__factory' -export { Revert__factory } from './Revert__factory' diff --git a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts b/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts deleted file mode 100644 index 3e324e80dcb1..000000000000 --- a/substrate/frame/revive/rpc/examples/js/types/ethers-contracts/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -export type { Event } from './Event' -export type { PiggyBank } from './PiggyBank' -export type { Revert } from './Revert' -export * as factories from './factories' -export { Event__factory } from './factories/Event__factory' -export { PiggyBank__factory } from './factories/PiggyBank__factory' -export { Revert__factory } from './factories/Revert__factory' diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index 3560b3b90407acce7f602ce91ac089843be8dea8..64b1f2014dd06815fcea6a87bc96306eb00eda8b 100644 GIT binary patch delta 13838 zcmbt*4OmrG*6`Wq-h1}h_a6lX0llcGC?F^(D41xZSR|-ezKM8+i{6BL;r^&hkul{Y zjWikIip-KJGG}s(96MPtMrM;GCY6m;_%o$xrD~dt*uWbXI?LQ8 z&VbiW$n z&*w@|q3Uby7`ZDetvteL0ZAYGR)C2W!Y z!^31Gd>LnW{EkAhV1>(3;qsKYJbGN*rq!&&YPdIAn_sUv{j%3WNjj`E;CfLe37 zDud24hZj4%J{JNwJ&vW>Yux^T%M)-r%Wia4c>V6c(!_iMdCLu#>fd70#+Mud`(76bGLv zR6K2nQQ&DqWH<>`cD-tdB*wffkHcA9jFX10*9;3Ci?bZ7ya8A5^;Yg(s$!zs?o}aU&D(C+KB+0}~pUd5piJ$RBX7L|sLBOG{mzVpqmo zt)1^$g`)?m5L$SXMCG}B%iLvUg3!p|_--;(x!WxbXVF`bn%Kky{2^7LT_-fFPup3V zupM6YN#n#F44euXto-VeZYE+2t8coLG;uf6&*zl)FcQAdRh+S4njq}vm}YybC}1~| z#rIZ~SQ`r$+EhF!dWj4q`=Mr)6anU-G@TrP+@R#fM(sKrl%5D{XC!Tbv#hKtV}`TL ziBm$_?fiQ&>EO*k_-U1N3poPWtEF4TlMD)T=uq(#gX$bQ0*Z^;tAoLhSJ7_i`sYVKL~|J zMKUS-s-)Wu;w5dqo_spcd|3q+x*7hUs8hN_Bt|%}R@$lTyifW{gyD}#Nx&hE7Ne?$ z^+Jg9!hO;ik%eK07-1A5py>}%6^Z6e6ij|VS}MjE>&MR@skA;I4b_uah&ios^0V!VQ8uUzA2d#zRslNmue8lDb8b33E3}wIoZW zQ$P38K#~nZACbZN%)QlNBil131+h@;IUh2VNj zdYm|4YLr3VIOTQNSNA(v)x@Pe@_V{kSxbRA~cO!@(y|E-!4B29p|id$Tkw zYMqfR$np3CzMyJ!x4GQQR|Nd(LaouDhdm)ZPuB70nWPr%Tcl)C2Q#)v@nj=-w@9N& z1Hcw(n!eFUK7_~dGZ>$g>|_f>J}D)VCYbXi!qmasr?3%V*i+It5)La!sUADj5&IMNJvVIyug!h2K12w+cBlXCDWDK>=cfNx)r%3=PC(o`{>z{4*} zgAM6K*ki1Z@J7Ps36#O7Ez)Yzs)-df86VbWyk>=Me8qI=g#GZ>7bSn_0aa#;NSe^D zNzX2HaLiNty>OBbgRiI5 zp766q;jBhhweWN5^R$aSBrf+NfqmgxeAVk0kV{4)+i&%HTqqj*d~dl+FI+;3Xs?%g zIP>k6R2 zt~3lAJ{8T$xZ%ZjRe&GfmC~#(6N$RX=fsU=&&S*1$#{;$hkHc zgev&zuryYzHq{r_Mkq-g(f|>Smk*J-u15lOz0k0)*TSjoua#P)i(09LmJg)Mtd5Ve z&LnK)uLI$&52bvu!Bl_j!AK?Lh_slHM%eg~^dp*c?Z2ctH0LK|8UtxZrIGktcoc2A z8P*+@Qip8Ek>U$HK1_66nAMcXBs}0QUg-+#fDeyK(?|;p?3Ct^?XaX%T0C(Fm!&Lh zHwi6$_}$T;Uk-}=?$&g%M@jxzdQ3F6ss!3h7&28^gzfK<#erV3*bhUxq>Td)@Hy@` z3GFIQ2jX;SI7fPLI(y+90sS%Q9%CoRIbssJOpx?}7^cOn=Z{HaMB_=740_#DaO{Ni z9BG3GPD*3QS>=V3(h@y62e#7~S1!UoPD^p*qH^XmCIsY?%D#BnR3G#B5ctbJ$*eqe zMtV;q-QfL7`a&e;`e{!NQaoQv-Gmr`T#%xa_2(rA8NkdWKFjZS1?C5wfgmm%#=j1N z!{10r#KNB|BSXw2B}+}ku0WA+pUdg@dcvUYf^-83(QrPyfZjVydmeXD+H8uz^^C~~ zF8OHaxG0sOm8!%Sd@GID$C`yuXtL4hVIF6>D;CF?fyo{wneziaOr-=8?yo9e<}Hhb zec!5<-6qjwnEN0dK;o3l@1%tiN$6>lfq!d=qVk|r^EJ~>#o@(Hose#Z{FilMyXVqF zqL^uh7g|XuOw>^xXcl-SaY2rnhTm zk!CJexojhLp|aSz1gP9oH)phL%XqM50w)qGb#O@BJCFo_*J1|vnm zjsFW>Jv>Cv4sud^<`NG-Wk!f3l9Go>xhrs!3$4|M0s9mODG<+^>s#I%q)dyXLrmx+ zLPpbJux1o}3_S!sPvagUZw!5(T!s;G)H$@evBe~zPD zL}F30#?p~G(O{|H`dv7zO`v5~X5nJ9h#{h2v6${~|!+;NUbm0#o)2)95HMM}vl> z8|W+q+j;{XJvQIcJJJ?d`sF_bC?H%g1uAX?{QCyFXyT$iNQM283N<9&pu?*sf>YI7 z-1Quoi)+Ab5%7n=jilTnl=m=tVmduNveH6AX9j(%Tmp$NcYBKb!HNp6FL13}sO$k3 zXV5pM2Q9>LlY5P;Bxh2JKpbm|aIcJU=QrI|*Xzd3Y|v64^GHkt$3;7+vJkVIKUN`c z2D4CYseky_NToGMcj?gMS!U4}&FfUQg+?OOTA<@C{SY`ci_XWrLi%i)joxbQY&t=# z>-p5q`!>SB96B5Q+~OQ`rA=^m4jqY|d|{7i=vml}y%u(ZI)Xh_5*qPI#Mdo7NM+w#T19Y6!ui!U7Ht-# zPCRSr@1@UL`g&=5&t@|L^B>`y1uDlIBH*(^IvjHyX$jgzH{7}eC-z+tj`sKxIul*m zu_e?YUdAltUs%&pMjE0ZsfY&g!?Q&+7Jc2pBDCplmDW%gxRm}uBvzU0u%eKm`)J}=?GCIca`~JVxZp&EvL57l%jgVZfwroi zNzP?f&Fh6&`_D7P+JBzRifcH`iWBAki4j%-f7~GPL$uWlP03iEyZ0A8RrjRi(QR{! zDJ3yV-ksE?pAkQw7X%l0y|;K*3o-iVcm=IbjSvfV0k79l=3R{~FAq8^D$3lhlEjRx zGR)gbs$ig(j)$*3G>MFWXfIueg~x!GF4e{K8TB617D4Aib z)c&xZ=ES?Kz5Te`+Ru->ky+%~eHGhQOLO(*S6>6Q^lse7U9818R;f-$AGDgak(8m7R^|eI0-YaM zRoH=^nYH(t*#Qk~_G7ewwey)BunHZ@vyagmjhMrI^=EpQ*lC5B6ls+5(9?7bAzi8h zVB$6!0;XpAI60*}+f41e&Zay|)5ux%J7!=h+vqa%T(Aut*x#Z3De1DZ>p40Pgs%YkcMjvz0nqY;<*suFxbtcddxHUbm};2V)9 zMqc7F8+qB$Zegaaw{fN0 z`WaU`qM>nRA>2HdKUfx!G1tBy4+ydmR-U_3FWisB1)Q-Db3qL_8xjxG;qcCTbQGp; z7vH19BXezJc7bQ52lp?%6^4{^6a>~2bcooFfQ23OR?P64J5Uw{@NNekn_OV)8vttb z80pe*T8nTv^*^!DCg2YPK#e{PGqO?d(|H5)Z6qnjv%=-WBb*zZ0aS=f<7>zJbW)PL z&xFcty=PR;>GqvcxrUnh0eu1Xeu$H!AJXKcU>_n?oJjQ*u~hXUQjO0z)hc-XLmC6M zAJR#rnm;eWRBPA~bd2jD=Ln6BtL>wWn*Q3T(FoN69HAd$X65^c9*S$|gVop{t5L&h z1oyva2Ifu&kJ6#Ap$naG6Kw6Gi*aw*-9DtOVqRsS(hI-~xOszX1ql-ok>9P^W9l=Q{QynRVxSvHh zogVR$@I&=Boe+nN{65?Rn>47K01alwXU8y)317}8Ht6S9+il8#tQ z?7iJbckl7K`;BL?ho)4hoAe@|Yn6MI%j{ZJZgy64Ift-epC2wCKlp>MAM1v-=e;e~f{Q z9t_Fc51eDLvG#FpyX7TvLR{%0+lCTcpn8 zy0kNTfvqjNF33_2g2Q|H3F}po*98src<~6cKbLaAd%X!`d2<9;RGcQGwJ2p(fLy8= zyo3EI2@IErHd9{BvDo`XA`6y5(TVa#46zF#|F>15>V2)I_-qXPc#{k zlq8TOKi=%Rt`q8~+ z*4f|Zb)pI2%wp^SC+Vosv3(>F(O(kC2*N}0DLSt3e=rr5A3NuVvm+BZh&e!VA1h3? zqi0G->S`2EMVeP)Ub;$ONVS9gI2{t5ksVlZC1~e(O1=81o)c}{RolVxZ<-n#<-&a) zE*zYkog0Hd?08+q*;@?h8nw04dr`xr!Rr%H(>y-+m6{Nhi>8C>&>Jr=u}R$^V>Ah%4x$v_<`uwn(G7h@(ERxUdhnvp={~1Luui z4i6VxeGuIJ5!@Pr+b%;3FV8=HijKvN-632gx^k^^ARc|-jPb4b+{cv5A#^ zq>zH*3|?6i`~bKo4GL=1gK*+BeQj-3ADpD#I7$6*s`~)faD`nRp$5tRW(eM;m)F_r z?7}*H6l?85tzEtmuZWbF`?$9%cf&WQsR0~kFzVHDhC*S{8O(^|;O;XtD{*6=@z(bq zZhgPe8sMulG-qHV`l9`4KSSJdG3MjP+Q*fzWShSp5c!<>|2DJROgV{I&B~bS;4M z=jlY8%J6S!0*;dR4KlO`s=vW+A+Ylsge-*PyeS9U1#GI}h6}jL8lZwVjnKq9_dv%5 zMDK!Md2<#LF5I3VBBG&p||52zCOGrA|3^(_vU4}ouSHCMt@ zylH@UzQvu`S@;3LG5s0;9i58Bb}0J}`Lw{+?`W1j+CrX#*OO5QzkY|M$OIVnJ?=F# z;g#>{WaRMM?~$h(7=DRP5_enbo2=2W`Vx&oHXpr2CyQ;CtMUT#54bz+g6JRUWPP-i zJOR^hmm;9@2c((|8-Jh+u{KhlWRHed&rx&zBKsh$h16Htqu{0=@jg$XwSI>^9ParM z!GrMRk2D2q4Ili7ldlH-Pgv@!ff+yHTL<`mq7!h-{OnIS?N0dmCpra>9!6iL8(7X8 zTfZn3OoC(1_6{=e*PegFod+TWVtUyN+Y-sZQtjWf#3VfSqbk!-RCdtEf+M{WT95TXC$MnJ&C8kiW;Jfnpm9k?{?|W&E;KT_I zH=Z-DftDrm$RVFP)a^LG9jV?etKdnSH|Q^`f^U|{L)QxV#DPK9%}W>jK2*HJ6lD|0OU%~i0aj@2vN#VaBRUaG^f{CuzCSb?QFKff!c z-nPTrb9h}%y-4O5hL`yQj^*BfdOhtLt5NGe};}e#ARdO4;4=CL$V)a3LQpHR2vQMr20FxCe#}#yb!p#M&`F6y*2U@H2Xs}a;T__Hr~%ik5Q!i9(klZLwP@Zp;hv^MY>@h>ki)@_o^A`CAf|fA#DY*$Pq3bDmA6i1wR(UuUg5KOJ zpQU(->2J@;r%?e7+vQU9JHKp~XObPt^uNffMX7}oKMa*_IaKL*UjCV&$+rJZoA|G{5?6Ml)We)Be*lU`S0>U+!clI zmQU)tjd&ZUd$+t0FXAW#FUj{2JiY5wMo@PMjd*{dUDBSUH{20ZK=gS|< z>GpQxZbTJGw1LzS0p&O36I5v{q<68+3Qmz-# z<2HXLuSAiBd@fJLg`NAkJQH z`YsT?Xg8SQx$kkxco_4&e5)?P91WkIlP~LHur8bb3k_4wotGamU`V+4dwG=je}Nt; zD!2V4dvxgQ-}+hR&iVAuayhQ<1;5A{23*w}2@4QiruhYKHt~?6V_DbUp3$+Fq%1Rv z^m*tAkt6F9f1&VuJ&VL8|D>Kx;WhP61H*Gp<-a6`J^=Mf@oRzdHDw#aQLpzn*espP zyp>ZQ0~Z`DgSa6fhLs}9qcQ9v*XAEavl7(iSEHE&ZJ;udHK0=Icvc0GaqL+`H6I_Q zCb3lbIgW*4YzP_4(y=&{l*~p^vnYzS<~mrC%u-O(JI1o7br=it6WK6Sv_?!pV&l?7F*IMJcRYy=K^-H#VGxnb2H{CP5*?>IU~car2=&Q0%_DFqnSDgMl;#O+v;ohi z+9$JQRQ#`#*>v=5)6Z%_@>~j7GyGq^3N$OR^;*ed`{1x5L& zVkT{7HPyyAkVHwLcth3ZwG{+C2#cVU#uDnpp8bq=iZYo6y?tz+8)Ilq> zl(KtpB7GhD&2F{|W&ehoJ%nB=|4#NhUAv_N4&BL4(Kd{`vWvjnKcR;?ypoklM>vmf z^Nuhjzl=SN<#XjyIXl1=v)9kIp(!p8u)FY3PWe253dRGumsYW5I$UzaRqRF`O6Vk_Fxz9_n$EW-z3~ zclR-m84rh$(L%y+oj@?hnm7{em-GKHnpq_1w;l4_r!;6L;VND^zrt0#P^(XM zTkUCT--0zgJ;Y{DQhRs_%ol>ta~2ewEEbSN!R#Xzi&G_8Qxz zPq*neKu9Ya1pj`GjU$~i5BDO~RlJmMYh zfTI|*PCWF*^b*CX{yj#NR`pZZd+bR)uIlGLU^)?3)$>Q#TKz_w{!OU-h>d`GAE6O$ zgz}Hr!x)4A^AStZ=^8cuy&`E-nm%P42nMLcW9$W7g~yMv?Y2FdfYR)KXIUVv6<+>~ z&4H80S(bRf1|6TXyJ71ICXo(h+X)_bIwAVs>?GPhN48(;ja-8<7_o1Y^(bP4J=qQg zpR?bgzdQUnyI;(=at- z$}bT=Upf6HE8t!t;~e{gs*nBW*qvf{xjh3*Wkvi-8sC_Ey``utnyPXL=6V}{V$B#5CtqfhFcx|)vpX@t>AcL|vaYj-szmGT z8||UrL)I^Bi1N?>hbC$2^+n121+5dq;1Atwh8}n8FT=h6VbQSYKWqd_xcWcrHqxvd o`43yCM>D@eXKW&Sly05zBHr^*zA+dv9<(WQnQ;=)?Y9g64VE!@asU7T delta 11763 zcmb_?eO#1P_V{z3nYs75^NxTF0yd+f;47#osHiAtn5g)=Zjp>I>Ll+BDw!FT-^9W+ z!jq;c-_|uMw^-L6*#I5pw%{xJ2F z7|2~~^4tZkB~A~d9wWUEY)+SpV2MW;avOjCLPwf+Z=Uepk^a_-wGwer+}@8kjB zk+sRN+{r>j-3q84EG9mqi4tW^lxFT-6<#hJp?$1B(ZP+m+7XafqoH!-JneH-!6bjQ zSo_(hBotm5tlObkK;}bVqb?Rc9-`}I&vs=mbrslGI19r_VV1LSh24{DU+S_u^YXIX zuFRoe7^({k^sI2%^PB}9d+sv3Qdein>xb&btO*JvgYD^g&f=_GXJ-01`+OXpJ9Ex3 z_~1`E2c79!?#w8bzyFgi#73;LwpJHHESU*8c4tOLZc&a0u{j~~UERXq^aT5=T#rlX z@E`%M9D9K)J9m{UvwUl=Lr(seuJsN1$(3|658KKGL9E~c{sgTg)N6$8YHb&f6?VZl z9y)|n%7!AELBzcr9Cx%)qpG-e9!?hab7EWQ%9t>Jq99Z$Kt^kfC}1~Y#CElc=ODKU z2RV?maFvt!t=17V3m|*U%Z_QC=Q4JqR4dbUHZ> z?qWJiJjtQt0oGSM!=ds4)*tQ^(}#c8*-u9V;kcL3f%QH@8@do$uTo|GR%lSj#MVjg zn!VQ2$%t58bLCn(#lKOV7$cq+n!F&mi`2C=uzcbqE}vUVojTGClS^o#p+y}RTDi~) z&zI1L#5N8sCG;(^otKkYq43cnW|U9NWB(FKhZlnARc3D#bo$@T;2eiUlU^{vq*59R zO&e(%@l)!LX#({|NGPR!;JjXB@;9Z_t|h_SHq%5gOb-wD5&J;;W?D`haBDN&M#ACQ z=V>O1gd5M(7%@_>$Sx30B+~(qzJ&&e!}R479}JeC-$E~nBpM3KX{i{a&WeKJFQPEw zV9Ja1I*Etq3L1*VtO`1xB*3c`bTCPhKdGQ|H6&SfY^VK+n4$tsA*oQhgYF;;z`Tow zz@(SyZL%1~?4&8;;!XlG*+4kFlNtik^dxr5`~sIN#hEwZfuZ(=s77gUekTnIav~E1 z<)7%%3;09L9BmWa3Rahx?OQ7!ZoGmV`eGLiB1Ld*7rl=ZgYgyG2V3gM)K}=6*oF0} zWF3s&O`}N(EZa?ok_}M4n+_tS@Q>YeqP9#=zJPae7>4hmRm;?w%K_ecW#bWJD)V` ziDaFXo8v-WSm)(tyRVh}f={Hu(Gyoo4IM zlf*d%xvSinu7U|uoLSDC3>WH3hj(AeHFUO-7!>y-SUdEBPX1F3O=5(@mBTcZm|#L3 zjf^xIy3*xm=s}ksVnDk5U~?TEM*`t^9gQKuaJP?>#yai^TV6Hjh?- zA`L!};kM@G1BXqnzUR?x1)3%L!;Ea*dSO*RHiG-Sqy#kEC|MYg66##^6Sxn7Aw>y zk~BC~kGK{?pHFD4d9i_nPAhP`axx3iD&_P~Xu6iTpzbpo7wGP?CAUJoPA6o8<#Re% z%s0TKYB5Nj`#E|Wv`K|$@x?x#m2`o_zAiemuBU9)A!3xxIyiZfw(}BYnPLN)4xxUp(8^RKu1aOUxZhBI{e?bq z-q&=UXsA}P95mnwsO~e5Z<^*Bg94n9KIsj~;Eg(3}x`dc&@ zi_5q0a0@mnhuahqCOg~cLP{LoQVdu7qhPYgq`*kyJu8jGMZ_~2VVImIvTsE(+PLkQ zCIA-cSSg8tCLP;jj8o{4tno%6-iM`@GO2e$7c2=1mKq`?86oOJO%N1+LJe{UWtBus z_QLf3Z!RLmI~JH~^tz7)T~MaN5Y86y1q$X=qp%pBeYB$WWvdw6Z$`rV5 z#Vr>r^&R$g>O|`?ClN~2H5!Ob*r3*IZwjTx^5^S<;qOmUqr9XSi_nT?MkraX?F;*Q zv)9F~YQrC%aBEF{DvYRv5{aFfi^OFpr@`FZEIb4%e3SMBv1oC-ukl?F>ld`kI3OV} z51qU-tB_P01tST_U7h2Ctrux)ixLx(1xZfz?Pw@iBglZqD*}-gu zc+l6l63l`^YK%kYc?#S)%V)Z?-5voq-AP>yg!W-0#Tp}2XKMo>vk%MaQ>!4fT8}l7 z1|rm{*w2uoM&YOrWk(!Gs2ajTv3P$7+a-J4&@_s@r8k*S=ebGnGlAm@3zXj) z&5nv_`z5i=A2yCfl@0dR2gkD6QkXJ3N^mG$Q5fs~HI^;GLY*8Q$7W&N>xpA;;SoAu z92<_sym73JM9azJSt3CjcP1fLdm^K-ZvtD3r)&Rs_5?<^=O?lVEcQ%fgWy&?v;D3s zZ6XWq)-|bHYY;?DLYTNN=+76iInXu%&!W#KvF9<2Zd%2nJ6HJUWTwZ1`-91BCP{&S z1oX!7U0~-Yu<4Nrrmm-Bys5_l6pyUH11Jf0Ca^(x{@>f0ZVLP3sFZH-sXgISz3@tb z4sRV17O1j{_;0>zvBLH=lYl>*yq!2rg0stl+Ntcj{w@;RM<{1d{{q|*}*&mwW9>mX0Rj-30|GS zMq*1HxjlnT#;d{D2iW}>Mm+HVyANINo(I?f>{PUOP^nLQ%erW9sfynkLxnP|iP+q# zU<-lRnQR)uV|^*Eqb?w(u%~{9d6yS+r4RGoE|@D-%-hii?^Q5kv&xHk_Xk=Ahn}Ps zdHyW6N<;R`AEmMiLaG&9iQ+-i@9va%?^ei%!wc~ktnIQwt%5g|3U$!9kWC^-z0+nb zV*Yq(P(Z@m)N8NxU7+d}C^yBZwE?a3hOYD(F<~x=H_J1$<57D951o2c6(MVaTPcT1Z1*l znvPC@qD(ebBxc_w8+Sdc!zWUmx#zZZ=I*DD6;GcI6Dl(|BO{c5qRA}aPq9cG@H4{` z^}2rW#%#*TmVJdT#z>&-$zm>TuL%iR7*k~yr?{6ZThBzFxHg}K!;E|u1q1T&5ZsfG z9;ZKime10~Koyf6<`*zo3^v2_6WB6{TE!eF;LzDQ%X9H4(+XiwS@{3W9g1PuV>@53IA!%k7T{%KdooW7&_vK8&PK+ z&!EL8!?b7ER3vB9Gb~a}_K|bx8Rjph_!?@GBvnS30itGsS%Mos>jL4?P0WoKr*Aj0 zDThjA)Kg5H||?5RL0T>^Bqm0o;FFR>iMUR9!26mFFl!51&#(YRl! zk3j#Hx*a`(TJ*vEBF@zrgoECR`?s^5VvTPrwZ5&?b=gX-cix|Oum$=$Wh=F2;iws| zo2X3=dzlT~VCSo`D;l!5VeW*~1vYb9-${oY{n{Gi_s+ApM8+2Vi}bE~<{+-~kL z>nz-2vIJU$7Bi3&$P{xeixOLXjm5QWh}h<9?5<^l#CBh!sh0H8TN>C5jM2_Eu*ikcmaaxK%+kYX zh9MXlO$^SP>nbeD@<^CzdRm)A;&84sd!<%*8j%agiUO?==ban}=Rafp;IYrt2 zha}*>7v!wW!E46+0%uO4Q@y7snB!%ftR+fh_ZeoHr7tjGNP-4|?dbh4$p z|1Qy@1CPaRrQmYvKQYxJ;1B(Gi8dA^s?%Svx&BEO5|x~@!d2kP@gzDuD5=F>x|W<| zW1`Z!ZOCcqx+AB8xBHfyUeL=Y+1uzO`h1BIX3Z%)m)uP`_oaE5&nS=tS%tS3~N z7pe?GzG4&5b3A^Q^@R^EV6agEXD_ga&=I_Kp2b@#EJQn3AU4-3WK-c@Xli6(!z;Qm zXS>3j6+UTi@3O@e>JGOnxMMNXBcEjsEY6%|;}N^M;MYwoIcje=TB~}}TIHoxDa1L# zelO^UUn838E_5I4M)$%0M)$#Pbk}sHyQT--HD25^&f$$^I~1Q|!-m&(gRkofU)KY^ z&I_N|j4oUHh9w3Z?FQXUk!!~x{Tmh_9rtb!bHj=hc6`I;VQ%>PH!P%2gN4{1EE+R< zynS?^#f*kVdZv z8ewA-i-msYSr{}nvHOQ#>PFa^uG>1(V_VH$ggF;jJREL96e0vQ$0sqmTkAd_{-=oI#gt%5|cbDrsn2&3eZvb{7$GZ8BJ{3 zx#W@s^Qy*WE|-mT_Jipn8w`2hGh;mFy%bkIcaHTLc$L0$ zg^i31Mr`-8^WdJWI{B8Y8a;lpFP?Qyi?2XPhEvn$Unoo>*(OdQ;}&H`X} z3wv#Sd^fpJkE|_wUwGy0lWVHPtF4V^vJC3Mp18u`q8*c6Py% zyaG)Bz}@Ri2OnKWQ%F^)8biWh&<`wO_=0XrZS13RX zhgHH0KVmF%iI-2t^zfMa4ykuplD6JV-c&-% z5IA@jslOzDc9%WJ?^~K;%MuKNeMVIS@xSZ)n=<{GV1XE-ZtsJ2T(avs+mHVO`?xer zgKhasE*%`DJ$<3_IeAOG)KBBBzB5_!>#wUPS|L>aI77-KNLpEeG|Pf^V$UmZt#aoU z6=oH$fohl3SI$``jn|Vh`Sm<0h>)%Fzw@O}2p+Fn3#B=zMHdRC$MNKHdZdMOG44sOfU1YekNM|K< z06tV=a$3n^3(XvMZIIT9EgVAsNBRjr3y|xdlC}}W2wWSaF=B^z0=}7S&EY@$#vKI4 zY(y$_@a9ITKfaB8x>0(VaCtrQXSO>>hYl@;RPuGCchxr(W^UO zK*%uo@&zdj)wk^hDGqPXBg&<vu~{Xo1c>(h$59m+g`67gO~xw1!WR{VSzlEmh;GI5{u9x99Yn$(Z{MGk*WYG(KfZ+Tn#4w--ZZD|==a$2=C6+bVj ztd>@bWUn0jH{2pB_3(G3kxDk}9cdI674JwhQ6v}Mkv#ah$Rb(Vgx`r=kfkaoRE-?NdVpMb zQ97wb7m@tEG*ENWa7HfpUMd&SbJ45PN^_I4{u=ufu56!6m06nv5@%6fB>~E5Upr!HvqqbtRQghmNZLa zGWtP$o7AoeG|oX)4v(K^lG^N@===XQQ4U z)bI&9)bE=T_n>|o-$wL9p+e6S{?pJI_&X@ie;W7{RPwt9K2FICFAwLLDAJiDxE&23 zdjzjQ$*vf~i{Y^-{%2jj5%~%`$MP6>D4GYNJAW{i-v?Wxc^|xUZH?svnNbwQb;c6- zS1ccmGB=OpuWB5n#^Hi>)-q2aQU6bfxc8o>a<L{n?5^DP3mVsZP&VMH81MH*x}jt|n2lQ4V|k4A+^pTsAludAHI4a!Y; z|789L^e;4l$6_%lfv@U&$rvTr=HcfU1;xZyO1927;b!c9%pb$| z-3`ecPbG)^!y=CEBofXn;SZ2zIXs;&(cni+8#3@qvls}=*%JrnoT1c{JufJ!l_c@V6KF)5IyqRq!|+nnK|YzQ>{XCT)~`bc%b59p870 zT*HUXbQUakEmZ0w@wrtV{W1!fWXo+kc?iXHrr)c4unxsD;WfS#EoAp=d<90FQWa`> z8N^oc*{JIqtB@@f=Izi@#W&$WzUFm43lH)SUgwP{y1j4k2T^q7O}-MPll>+?gwh$f zpMQspH2sB}2_7DyZ}BJb@Ywwp59N6K{pTSbuB7`m??E?y5{A_9xp<^JUW3}$C?Bao zW{XW`*!5>=ll;Ozcp1fOul?WLvA)?n;QpyATp26zL#IEw3UcS+hcd2$7W2l*YVW)} zSH?nDffC5Knys;F-~2Vco;I^JO6{5M%5fFC3){`3y)#nq#mt%I_7r!RM@MvG&v5B1 z8;7c`MK1U96&`NcI0p{@n+NnZSvDrB11b3ZY$oQG?#HiB1L4ZQaT8$>{~nrcB&>Rm zAHbk`@DZMfPocR-_L6T(wxIW-50gEk?XN|)W zu{0|;W91Tzu~v*ulb0RiQ$>r*GN2GY0&!(yrsgPf%ZEPXuZx-@OR;>TiHC^r#0m5P z>*U|=@Et^+)4;#eirSB%`U@Vo?G*3LeBaB;$4>EAw5Vsx&vK23f?nCk*K0Rev_~N7 z9PbZ)=g<;1z|eDi3v}G!(`C&!{EDb4^DdJsl7n*kxBNMR2SUpw{x<5yE8p=d%YN@Z zVyzgBd19+!^;JFt_FU!(7)4yT%%37hTJTLM%~uD1lZbMlHjvC#K2A0T#E;M7(AC_0JMYiI{tK6Q;hP9%rL znB~f``oTljd2nYdP+oVPZxoSe+R87XU0-WO$Uu4dO};=xVb$K^|D&>US{r{vv_)Gd ztaKGGQ9g=LuA8~J%aF|$dpsn!@uBjqpU@{zwI@yO0r7}t07PBjlHBqO#}H1Hg1<() z&LSlN>EHnsk^0Rfrs9x(42iG!LH`IE_8*DB!v1}z3PU+eXixbWi!eJdd)a-K<#UV4N4 MsTJ4Mlv;)V0}5B>2><{9 diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index d37f1d760065..901c15e9756b 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -32,7 +32,7 @@ use pallet_revive::{ Block, BlockNumberOrTag, BlockNumberOrTagOrHash, Bytes256, GenericTransaction, Log, ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256, }, - EthContractResult, + EthTransactError, EthTransactInfo, }; use sp_core::keccak_256; use sp_weights::Weight; @@ -116,18 +116,42 @@ fn unwrap_call_err(err: &subxt::error::RpcError) -> Option { /// Extract the revert message from a revert("msg") solidity statement. fn extract_revert_message(exec_data: &[u8]) -> Option { - let function_selector = exec_data.get(0..4)?; - - // keccak256("Error(string)") - let expected_selector = [0x08, 0xC3, 0x79, 0xA0]; - if function_selector != expected_selector { - return None; - } + let error_selector = exec_data.get(0..4)?; + + match error_selector { + // assert(false) + [0x4E, 0x48, 0x7B, 0x71] => { + let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?; + + // See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require + let msg = match panic_code { + 0x00 => "generic panic", + 0x01 => "assert(false)", + 0x11 => "arithmetic underflow or overflow", + 0x12 => "division or modulo by zero", + 0x21 => "enum overflow", + 0x22 => "invalid encoded storage byte array accessed", + 0x31 => "out-of-bounds array access; popping on an empty array", + 0x32 => "out-of-bounds access of an array or bytesN", + 0x41 => "out of memory", + 0x51 => "uninitialized function", + code => return Some(format!("execution reverted: unknown panic code: {code:#x}")), + }; - let decoded = ethabi::decode(&[ethabi::ParamType::String], &exec_data[4..]).ok()?; - match decoded.first()? { - ethabi::Token::String(msg) => Some(msg.to_string()), - _ => None, + Some(format!("execution reverted: {msg}")) + }, + // revert(string) + [0x08, 0xC3, 0x79, 0xA0] => { + let decoded = ethabi::decode(&[ethabi::ParamType::String], &exec_data[4..]).ok()?; + if let Some(ethabi::Token::String(msg)) = decoded.first() { + return Some(format!("execution reverted: {msg}")) + } + Some("execution reverted".to_string()) + }, + _ => { + log::debug!(target: LOG_TARGET, "Unknown revert function selector: {error_selector:?}"); + Some("execution reverted".to_string()) + }, } } @@ -146,42 +170,46 @@ pub enum ClientError { /// A [`codec::Error`] wrapper error. #[error(transparent)] CodecError(#[from] codec::Error), - /// The dry run failed. - #[error("Dry run failed: {0}")] - DryRunFailed(String), /// Contract reverted - #[error("Execution reverted: {}", extract_revert_message(.0).unwrap_or_default())] - Reverted(Vec), + #[error("contract reverted")] + Reverted(EthTransactError), /// A decimal conversion failed. - #[error("Conversion failed")] + #[error("conversion failed")] ConversionFailed, /// The block hash was not found. - #[error("Hash not found")] + #[error("hash not found")] BlockNotFound, /// The transaction fee could not be found - #[error("TransactionFeePaid event not found")] + #[error("transactionFeePaid event not found")] TxFeeNotFound, /// The cache is empty. - #[error("Cache is empty")] + #[error("cache is empty")] CacheEmpty, } -// TODO convert error code to https://eips.ethereum.org/EIPS/eip-1474#error-codes +const REVERT_CODE: i32 = 3; impl From for ErrorObjectOwned { fn from(err: ClientError) -> Self { - let msg = err.to_string(); match err { ClientError::SubxtError(subxt::Error::Rpc(err)) | ClientError::RpcError(err) => { if let Some(err) = unwrap_call_err(&err) { return err; } - ErrorObjectOwned::owned::>(CALL_EXECUTION_FAILED_CODE, msg, None) + ErrorObjectOwned::owned::>( + CALL_EXECUTION_FAILED_CODE, + err.to_string(), + None, + ) }, - ClientError::Reverted(data) => { + ClientError::Reverted(EthTransactError::Data(data)) => { + let msg = extract_revert_message(&data).unwrap_or_default(); let data = format!("0x{}", hex::encode(data)); - ErrorObjectOwned::owned::(CALL_EXECUTION_FAILED_CODE, msg, Some(data)) + ErrorObjectOwned::owned::(REVERT_CODE, msg, Some(data)) }, - _ => ErrorObjectOwned::owned::(CALL_EXECUTION_FAILED_CODE, msg, None), + ClientError::Reverted(EthTransactError::Message(msg)) => + ErrorObjectOwned::owned::(CALL_EXECUTION_FAILED_CODE, msg, None), + _ => + ErrorObjectOwned::owned::(CALL_EXECUTION_FAILED_CODE, err.to_string(), None), } } } @@ -634,54 +662,25 @@ impl Client { Ok(result) } - /// Dry run a transaction and returns the [`EthContractResult`] for the transaction. + /// Dry run a transaction and returns the [`EthTransactInfo`] for the transaction. pub async fn dry_run( &self, - tx: &GenericTransaction, + tx: GenericTransaction, block: BlockNumberOrTagOrHash, - ) -> Result>, ClientError> { + ) -> Result, ClientError> { let runtime_api = self.runtime_api(&block).await?; + let payload = subxt_client::apis().revive_api().eth_transact(tx.into()); - // TODO: remove once subxt is updated - let value = subxt::utils::Static(tx.value.unwrap_or_default()); - let from = tx.from.map(|v| v.0.into()); - let to = tx.to.map(|v| v.0.into()); - - let payload = subxt_client::apis().revive_api().eth_transact( - from.unwrap_or_default(), - to, - value, - tx.input.clone().unwrap_or_default().0, - None, - None, - ); - - let EthContractResult { fee, gas_required, storage_deposit, result } = - runtime_api.call(payload).await?.0; + let result = runtime_api.call(payload).await?; match result { Err(err) => { log::debug!(target: LOG_TARGET, "Dry run failed {err:?}"); - Err(ClientError::DryRunFailed(format!("{err:?}"))) + Err(ClientError::Reverted(err.0)) }, - Ok(result) if result.did_revert() => { - log::debug!(target: LOG_TARGET, "Dry run reverted"); - Err(ClientError::Reverted(result.0.data)) - }, - Ok(result) => - Ok(EthContractResult { fee, gas_required, storage_deposit, result: result.0.data }), + Ok(result) => Ok(result.0), } } - /// Dry run a transaction and returns the gas estimate for the transaction. - pub async fn estimate_gas( - &self, - tx: &GenericTransaction, - block: BlockNumberOrTagOrHash, - ) -> Result { - let dry_run = self.dry_run(tx, block).await?; - Ok(U256::from(dry_run.fee / GAS_PRICE as u128) + GAS_PRICE) - } - /// Get the nonce of the given address. pub async fn nonce( &self, diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 6a324e63a857..ccd8bb043e90 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -23,7 +23,7 @@ use jsonrpsee::{ core::{async_trait, RpcResult}, types::{ErrorCode, ErrorObjectOwned}, }; -use pallet_revive::{evm::*, EthContractResult}; +use pallet_revive::evm::*; use sp_core::{keccak_256, H160, H256, U256}; use thiserror::Error; @@ -128,10 +128,22 @@ impl EthRpcServer for EthRpcServerImpl { async fn estimate_gas( &self, transaction: GenericTransaction, - _block: Option, + block: Option, ) -> RpcResult { - let result = self.client.estimate_gas(&transaction, BlockTag::Latest.into()).await?; - Ok(result) + let dry_run = self.client.dry_run(transaction, block.unwrap_or_default().into()).await?; + Ok(dry_run.eth_gas) + } + + async fn call( + &self, + transaction: GenericTransaction, + block: Option, + ) -> RpcResult { + let dry_run = self + .client + .dry_run(transaction, block.unwrap_or_else(|| BlockTag::Latest.into())) + .await?; + Ok(dry_run.data.into()) } async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult { @@ -150,15 +162,17 @@ impl EthRpcServer for EthRpcServerImpl { let tx = GenericTransaction::from_signed(tx, Some(eth_addr)); // Dry run the transaction to get the weight limit and storage deposit limit - let dry_run = self.client.dry_run(&tx, BlockTag::Latest.into()).await?; + let dry_run = self.client.dry_run(tx, BlockTag::Latest.into()).await?; - let EthContractResult { gas_required, storage_deposit, .. } = dry_run; let call = subxt_client::tx().revive().eth_transact( transaction.0, - gas_required.into(), - storage_deposit, + dry_run.gas_required.into(), + dry_run.storage_deposit, ); - self.client.submit(call).await?; + self.client.submit(call).await.map_err(|err| { + log::debug!(target: LOG_TARGET, "submit call failed: {err:?}"); + err + })?; log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}"); Ok(hash) } @@ -234,18 +248,6 @@ impl EthRpcServer for EthRpcServerImpl { Ok(self.accounts.iter().map(|account| account.address()).collect()) } - async fn call( - &self, - transaction: GenericTransaction, - block: Option, - ) -> RpcResult { - let dry_run = self - .client - .dry_run(&transaction, block.unwrap_or_else(|| BlockTag::Latest.into())) - .await?; - Ok(dry_run.result.into()) - } - async fn get_block_by_number( &self, block: BlockNumberOrTag, diff --git a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs index 339080368969..ad34dbfdfb49 100644 --- a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs +++ b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs @@ -14,6 +14,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + //! Generated JSON-RPC methods. #![allow(missing_docs)] diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index a232b231bc7c..1e1c395028a4 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -27,8 +27,16 @@ use subxt::config::{signed_extensions, Config, PolkadotConfig}; with = "::subxt::utils::Static<::sp_core::U256>" ), substitute_type( - path = "pallet_revive::primitives::EthContractResult", - with = "::subxt::utils::Static<::pallet_revive::EthContractResult>" + path = "pallet_revive::evm::api::rpc_types_gen::GenericTransaction", + with = "::subxt::utils::Static<::pallet_revive::evm::GenericTransaction>" + ), + substitute_type( + path = "pallet_revive::primitives::EthTransactInfo", + with = "::subxt::utils::Static<::pallet_revive::EthTransactInfo>" + ), + substitute_type( + path = "pallet_revive::primitives::EthTransactError", + with = "::subxt::utils::Static<::pallet_revive::EthTransactError>" ), substitute_type( path = "pallet_revive::primitives::ExecReturnValue", diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 920318b26f71..7f2d4e683c31 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -238,7 +238,8 @@ async fn revert_call() -> anyhow::Result<()> { .unwrap_err(); let call_err = unwrap_call_err!(err.source().unwrap()); - assert_eq!(call_err.message(), "Execution reverted: revert message"); + assert_eq!(call_err.message(), "execution reverted: revert message"); + assert_eq!(call_err.code(), 3); Ok(()) } diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 9c4d817a07de..b73815bfb9ea 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -103,7 +103,7 @@ where origin, 0u32.into(), Weight::MAX, - default_deposit_limit::(), + DepositLimit::Balance(default_deposit_limit::()), Code::Upload(module.code), data, salt, diff --git a/substrate/frame/revive/src/evm/api/rlp_codec.rs b/substrate/frame/revive/src/evm/api/rlp_codec.rs index 3442ed73acca..9b61cd042ec5 100644 --- a/substrate/frame/revive/src/evm/api/rlp_codec.rs +++ b/substrate/frame/revive/src/evm/api/rlp_codec.rs @@ -88,14 +88,14 @@ impl TransactionSigned { } } -impl TransactionLegacyUnsigned { - /// Get the rlp encoded bytes of a signed transaction with a dummy 65 bytes signature. +impl TransactionUnsigned { + /// Get a signed transaction payload with a dummy 65 bytes signature. pub fn dummy_signed_payload(&self) -> Vec { - let mut s = rlp::RlpStream::new(); - s.append(self); const DUMMY_SIGNATURE: [u8; 65] = [0u8; 65]; - s.append_raw(&DUMMY_SIGNATURE.as_ref(), 1); - s.out().to_vec() + self.unsigned_payload() + .into_iter() + .chain(DUMMY_SIGNATURE.iter().copied()) + .collect::>() } } @@ -567,7 +567,7 @@ mod test { #[test] fn dummy_signed_payload_works() { - let tx = TransactionLegacyUnsigned { + let tx: TransactionUnsigned = TransactionLegacyUnsigned { chain_id: Some(596.into()), gas: U256::from(21000), nonce: U256::from(1), @@ -576,10 +576,10 @@ mod test { value: U256::from(123123), input: Bytes(vec![]), r#type: TypeLegacy, - }; + } + .into(); let dummy_signed_payload = tx.dummy_signed_payload(); - let tx: TransactionUnsigned = tx.into(); let payload = Account::default().sign_transaction(tx).signed_payload(); assert_eq!(dummy_signed_payload.len(), payload.len()); } diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index 1cf8d984b68b..ed046cb4da44 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -19,6 +19,27 @@ use super::*; use alloc::vec::Vec; use sp_core::{H160, U256}; +impl From for BlockNumberOrTagOrHash { + fn from(b: BlockNumberOrTag) -> Self { + match b { + BlockNumberOrTag::U256(n) => BlockNumberOrTagOrHash::U256(n), + BlockNumberOrTag::BlockTag(t) => BlockNumberOrTagOrHash::BlockTag(t), + } + } +} + +impl From for TransactionUnsigned { + fn from(tx: TransactionSigned) -> Self { + use TransactionSigned::*; + match tx { + Transaction4844Signed(tx) => tx.transaction_4844_unsigned.into(), + Transaction1559Signed(tx) => tx.transaction_1559_unsigned.into(), + Transaction2930Signed(tx) => tx.transaction_2930_unsigned.into(), + TransactionLegacySigned(tx) => tx.transaction_legacy_unsigned.into(), + } + } +} + impl TransactionInfo { /// Create a new [`TransactionInfo`] from a receipt and a signed transaction. pub fn new(receipt: ReceiptInfo, transaction_signed: TransactionSigned) -> Self { @@ -143,76 +164,69 @@ fn logs_bloom_works() { impl GenericTransaction { /// Create a new [`GenericTransaction`] from a signed transaction. pub fn from_signed(tx: TransactionSigned, from: Option) -> Self { - use TransactionSigned::*; + Self::from_unsigned(tx.into(), from) + } + + /// Create a new [`GenericTransaction`] from a unsigned transaction. + pub fn from_unsigned(tx: TransactionUnsigned, from: Option) -> Self { + use TransactionUnsigned::*; match tx { - TransactionLegacySigned(tx) => { - let tx = tx.transaction_legacy_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: tx.chain_id, - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: tx.to, - gas: Some(tx.gas), - gas_price: Some(tx.gas_price), - ..Default::default() - } + TransactionLegacyUnsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: tx.chain_id, + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some(tx.gas_price), + ..Default::default() }, - Transaction4844Signed(tx) => { - let tx = tx.transaction_4844_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: Some(tx.chain_id), - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: Some(tx.to), - gas: Some(tx.gas), - gas_price: Some(tx.max_fee_per_blob_gas), - access_list: Some(tx.access_list), - blob_versioned_hashes: Some(tx.blob_versioned_hashes), - max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), - max_fee_per_gas: Some(tx.max_fee_per_gas), - max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), - ..Default::default() - } + Transaction4844Unsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: Some(tx.to), + gas: Some(tx.gas), + gas_price: Some(tx.max_fee_per_blob_gas), + access_list: Some(tx.access_list), + blob_versioned_hashes: tx.blob_versioned_hashes, + max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), + max_fee_per_gas: Some(tx.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), + ..Default::default() }, - Transaction1559Signed(tx) => { - let tx = tx.transaction_1559_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: Some(tx.chain_id), - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: tx.to, - gas: Some(tx.gas), - gas_price: Some(tx.gas_price), - access_list: Some(tx.access_list), - max_fee_per_gas: Some(tx.max_fee_per_gas), - max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), - ..Default::default() - } + Transaction1559Unsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some(tx.gas_price), + access_list: Some(tx.access_list), + max_fee_per_gas: Some(tx.max_fee_per_gas), + max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), + ..Default::default() }, - Transaction2930Signed(tx) => { - let tx = tx.transaction_2930_unsigned; - GenericTransaction { - from, - r#type: Some(tx.r#type.as_byte()), - chain_id: Some(tx.chain_id), - input: Some(tx.input), - nonce: Some(tx.nonce), - value: Some(tx.value), - to: tx.to, - gas: Some(tx.gas), - gas_price: Some(tx.gas_price), - access_list: Some(tx.access_list), - ..Default::default() - } + Transaction2930Unsigned(tx) => GenericTransaction { + from, + r#type: Some(tx.r#type.as_byte()), + chain_id: Some(tx.chain_id), + input: Some(tx.input), + nonce: Some(tx.nonce), + value: Some(tx.value), + to: tx.to, + gas: Some(tx.gas), + gas_price: Some(tx.gas_price), + access_list: Some(tx.access_list), + ..Default::default() }, } } @@ -269,7 +283,7 @@ impl GenericTransaction { max_fee_per_blob_gas: self.max_fee_per_blob_gas.unwrap_or_default(), max_priority_fee_per_gas: self.max_priority_fee_per_gas.unwrap_or_default(), access_list: self.access_list.unwrap_or_default(), - blob_versioned_hashes: self.blob_versioned_hashes.unwrap_or_default(), + blob_versioned_hashes: self.blob_versioned_hashes, } .into()), _ => Err(()), diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs index 5037ec05d881..1d65fdefdde6 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -94,8 +94,8 @@ pub struct Block { /// Uncles pub uncles: Vec, /// Withdrawals - #[serde(skip_serializing_if = "Option::is_none")] - pub withdrawals: Option>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub withdrawals: Vec, /// Withdrawals root #[serde(rename = "withdrawalsRoot", skip_serializing_if = "Option::is_none")] pub withdrawals_root: Option, @@ -114,7 +114,7 @@ pub enum BlockNumberOrTag { } impl Default for BlockNumberOrTag { fn default() -> Self { - BlockNumberOrTag::U256(Default::default()) + BlockNumberOrTag::BlockTag(Default::default()) } } @@ -133,7 +133,7 @@ pub enum BlockNumberOrTagOrHash { } impl Default for BlockNumberOrTagOrHash { fn default() -> Self { - BlockNumberOrTagOrHash::U256(Default::default()) + BlockNumberOrTagOrHash::BlockTag(Default::default()) } } @@ -148,12 +148,12 @@ pub struct GenericTransaction { pub access_list: Option, /// blobVersionedHashes /// List of versioned blob hashes associated with the transaction's EIP-4844 data blobs. - #[serde(rename = "blobVersionedHashes", skip_serializing_if = "Option::is_none")] - pub blob_versioned_hashes: Option>, + #[serde(rename = "blobVersionedHashes", default, skip_serializing_if = "Vec::is_empty")] + pub blob_versioned_hashes: Vec, /// blobs /// Raw blob data. - #[serde(skip_serializing_if = "Option::is_none")] - pub blobs: Option>, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub blobs: Vec, /// chainId /// Chain ID that this transaction is valid on. #[serde(rename = "chainId", skip_serializing_if = "Option::is_none")] @@ -319,7 +319,7 @@ pub enum TransactionUnsigned { } impl Default for TransactionUnsigned { fn default() -> Self { - TransactionUnsigned::Transaction4844Unsigned(Default::default()) + TransactionUnsigned::TransactionLegacyUnsigned(Default::default()) } } @@ -341,13 +341,13 @@ pub type AccessList = Vec; )] pub enum BlockTag { #[serde(rename = "earliest")] - #[default] Earliest, #[serde(rename = "finalized")] Finalized, #[serde(rename = "safe")] Safe, #[serde(rename = "latest")] + #[default] Latest, #[serde(rename = "pending")] Pending, @@ -392,7 +392,7 @@ pub struct Log { #[serde(skip_serializing_if = "Option::is_none")] pub removed: Option, /// topics - #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub topics: Vec, /// transaction hash #[serde(rename = "transactionHash")] @@ -574,7 +574,7 @@ pub enum TransactionSigned { } impl Default for TransactionSigned { fn default() -> Self { - TransactionSigned::Transaction4844Signed(Default::default()) + TransactionSigned::TransactionLegacySigned(Default::default()) } } diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index b5dc9a36065b..24b75de83569 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -455,236 +455,265 @@ mod test { /// A builder for creating an unchecked extrinsic, and test that the check function works. #[derive(Clone)] struct UncheckedExtrinsicBuilder { - tx: TransactionLegacyUnsigned, + tx: GenericTransaction, gas_limit: Weight, storage_deposit_limit: BalanceOf, + before_validate: Option>, } impl UncheckedExtrinsicBuilder { /// Create a new builder with default values. fn new() -> Self { Self { - tx: TransactionLegacyUnsigned { + tx: GenericTransaction { + from: Some(Account::default().address()), chain_id: Some(::ChainId::get().into()), - gas_price: U256::from(GAS_PRICE), + gas_price: Some(U256::from(GAS_PRICE)), ..Default::default() }, gas_limit: Weight::zero(), storage_deposit_limit: 0, + before_validate: None, } } fn estimate_gas(&mut self) { - let dry_run = crate::Pallet::::bare_eth_transact( - Account::default().substrate_account(), - self.tx.to, - self.tx.value.try_into().unwrap(), - self.tx.input.clone().0, - Weight::MAX, - u64::MAX, - |call| { + let dry_run = + crate::Pallet::::bare_eth_transact(self.tx.clone(), Weight::MAX, |call| { let call = RuntimeCall::Contracts(call); let uxt: Ex = sp_runtime::generic::UncheckedExtrinsic::new_bare(call).into(); uxt.encoded_size() as u32 + }); + + match dry_run { + Ok(dry_run) => { + log::debug!(target: LOG_TARGET, "Estimated gas: {:?}", dry_run.eth_gas); + self.tx.gas = Some(dry_run.eth_gas); + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Failed to estimate gas: {:?}", err); }, - crate::DebugInfo::Skip, - crate::CollectEvents::Skip, - ); - self.tx.gas = ((dry_run.fee + GAS_PRICE as u64) / (GAS_PRICE as u64)).into(); + } } /// Create a new builder with a call to the given address. fn call_with(dest: H160) -> Self { let mut builder = Self::new(); builder.tx.to = Some(dest); - builder.estimate_gas(); + ExtBuilder::default().build().execute_with(|| builder.estimate_gas()); builder } /// Create a new builder with an instantiate call. fn instantiate_with(code: Vec, data: Vec) -> Self { let mut builder = Self::new(); - builder.tx.input = Bytes(code.into_iter().chain(data.into_iter()).collect()); - builder.estimate_gas(); + builder.tx.input = Some(Bytes(code.into_iter().chain(data.into_iter()).collect())); + ExtBuilder::default().build().execute_with(|| builder.estimate_gas()); builder } /// Update the transaction with the given function. - fn update(mut self, f: impl FnOnce(&mut TransactionLegacyUnsigned) -> ()) -> Self { + fn update(mut self, f: impl FnOnce(&mut GenericTransaction) -> ()) -> Self { f(&mut self.tx); self } + /// Set before_validate function. + fn before_validate(mut self, f: impl Fn() + Send + Sync + 'static) -> Self { + self.before_validate = Some(std::sync::Arc::new(f)); + self + } /// Call `check` on the unchecked extrinsic, and `pre_dispatch` on the signed extension. fn check(&self) -> Result<(RuntimeCall, SignedExtra), TransactionValidityError> { - let UncheckedExtrinsicBuilder { tx, gas_limit, storage_deposit_limit } = self.clone(); - - // Fund the account. - let account = Account::default(); - let _ = ::Currency::set_balance( - &account.substrate_account(), - 100_000_000_000_000, - ); - - let payload = account.sign_transaction(tx.into()).signed_payload(); - let call = RuntimeCall::Contracts(crate::Call::eth_transact { - payload, - gas_limit, - storage_deposit_limit, - }); - - let encoded_len = call.encoded_size(); - let uxt: Ex = generic::UncheckedExtrinsic::new_bare(call).into(); - let result: CheckedExtrinsic<_, _, _> = uxt.check(&TestContext {})?; - let (account_id, extra): (AccountId32, SignedExtra) = match result.format { - ExtrinsicFormat::Signed(signer, extra) => (signer, extra), - _ => unreachable!(), - }; - - extra.clone().validate_and_prepare( - RuntimeOrigin::signed(account_id), - &result.function, - &result.function.get_dispatch_info(), - encoded_len, - 0, - )?; + ExtBuilder::default().build().execute_with(|| { + let UncheckedExtrinsicBuilder { + tx, + gas_limit, + storage_deposit_limit, + before_validate, + } = self.clone(); + + // Fund the account. + let account = Account::default(); + let _ = ::Currency::set_balance( + &account.substrate_account(), + 100_000_000_000_000, + ); + + let payload = + account.sign_transaction(tx.try_into_unsigned().unwrap()).signed_payload(); + let call = RuntimeCall::Contracts(crate::Call::eth_transact { + payload, + gas_limit, + storage_deposit_limit, + }); + + let encoded_len = call.encoded_size(); + let uxt: Ex = generic::UncheckedExtrinsic::new_bare(call).into(); + let result: CheckedExtrinsic<_, _, _> = uxt.check(&TestContext {})?; + let (account_id, extra): (AccountId32, SignedExtra) = match result.format { + ExtrinsicFormat::Signed(signer, extra) => (signer, extra), + _ => unreachable!(), + }; - Ok((result.function, extra)) + before_validate.map(|f| f()); + extra.clone().validate_and_prepare( + RuntimeOrigin::signed(account_id), + &result.function, + &result.function.get_dispatch_info(), + encoded_len, + 0, + )?; + + Ok((result.function, extra)) + }) } } #[test] fn check_eth_transact_call_works() { - ExtBuilder::default().build().execute_with(|| { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); - assert_eq!( - builder.check().unwrap().0, - crate::Call::call:: { - dest: builder.tx.to.unwrap(), - value: builder.tx.value.as_u64(), - gas_limit: builder.gas_limit, - storage_deposit_limit: builder.storage_deposit_limit, - data: builder.tx.input.0 - } - .into() - ); - }); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); + assert_eq!( + builder.check().unwrap().0, + crate::Call::call:: { + dest: builder.tx.to.unwrap(), + value: builder.tx.value.unwrap_or_default().as_u64(), + gas_limit: builder.gas_limit, + storage_deposit_limit: builder.storage_deposit_limit, + data: builder.tx.input.unwrap_or_default().0 + } + .into() + ); } #[test] fn check_eth_transact_instantiate_works() { - ExtBuilder::default().build().execute_with(|| { - let (code, _) = compile_module("dummy").unwrap(); - let data = vec![]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); - - assert_eq!( - builder.check().unwrap().0, - crate::Call::instantiate_with_code:: { - value: builder.tx.value.as_u64(), - gas_limit: builder.gas_limit, - storage_deposit_limit: builder.storage_deposit_limit, - code, - data, - salt: None - } - .into() - ); - }); + let (code, _) = compile_module("dummy").unwrap(); + let data = vec![]; + let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); + + assert_eq!( + builder.check().unwrap().0, + crate::Call::instantiate_with_code:: { + value: builder.tx.value.unwrap_or_default().as_u64(), + gas_limit: builder.gas_limit, + storage_deposit_limit: builder.storage_deposit_limit, + code, + data, + salt: None + } + .into() + ); } #[test] fn check_eth_transact_nonce_works() { - ExtBuilder::default().build().execute_with(|| { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) - .update(|tx| tx.nonce = 1u32.into()); - - assert_eq!( - builder.check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Future)) - ); - - >::inc_account_nonce(Account::default().substrate_account()); - - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); - assert_eq!( - builder.check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)) - ); - }); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) + .update(|tx| tx.nonce = Some(1u32.into())); + + assert_eq!( + builder.check(), + Err(TransactionValidityError::Invalid(InvalidTransaction::Future)) + ); + + let builder = + UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).before_validate(|| { + >::inc_account_nonce(Account::default().substrate_account()); + }); + + assert_eq!( + builder.check(), + Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)) + ); } #[test] fn check_eth_transact_chain_id_works() { - ExtBuilder::default().build().execute_with(|| { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) - .update(|tx| tx.chain_id = Some(42.into())); - - assert_eq!( - builder.check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) - ); - }); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) + .update(|tx| tx.chain_id = Some(42.into())); + + assert_eq!( + builder.check(), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + ); } #[test] fn check_instantiate_data() { - ExtBuilder::default().build().execute_with(|| { - let code = b"invalid code".to_vec(); - let data = vec![1]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); - - // Fail because the tx input fail to get the blob length - assert_eq!( - builder.clone().update(|tx| tx.input = Bytes(vec![1, 2, 3])).check(), - Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) - ); - }); + let code = b"invalid code".to_vec(); + let data = vec![1]; + let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); + + // Fail because the tx input fail to get the blob length + assert_eq!( + builder.clone().update(|tx| tx.input = Some(Bytes(vec![1, 2, 3]))).check(), + Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) + ); } #[test] fn check_transaction_fees() { - ExtBuilder::default().build().execute_with(|| { - let scenarios: [(_, Box, _); 5] = [ - ("Eth fees too low", Box::new(|tx| tx.gas_price /= 2), InvalidTransaction::Payment), - ("Gas fees too high", Box::new(|tx| tx.gas *= 2), InvalidTransaction::Call), - ("Gas fees too low", Box::new(|tx| tx.gas *= 2), InvalidTransaction::Call), - ( - "Diff > 10%", - Box::new(|tx| tx.gas = tx.gas * 111 / 100), - InvalidTransaction::Call, - ), - ( - "Diff < 10%", - Box::new(|tx| { - tx.gas_price *= 2; - tx.gas = tx.gas * 89 / 100 - }), - InvalidTransaction::Call, - ), - ]; - - for (msg, update_tx, err) in scenarios { - let builder = - UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).update(update_tx); - - assert_eq!(builder.check(), Err(TransactionValidityError::Invalid(err)), "{}", msg); - } - }); + let scenarios: [(_, Box, _); 5] = [ + ( + "Eth fees too low", + Box::new(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() / 2); + }), + InvalidTransaction::Payment, + ), + ( + "Gas fees too high", + Box::new(|tx| { + tx.gas = Some(tx.gas.unwrap() * 2); + }), + InvalidTransaction::Call, + ), + ( + "Gas fees too low", + Box::new(|tx| { + tx.gas = Some(tx.gas.unwrap() * 2); + }), + InvalidTransaction::Call, + ), + ( + "Diff > 10%", + Box::new(|tx| { + tx.gas = Some(tx.gas.unwrap() * 111 / 100); + }), + InvalidTransaction::Call, + ), + ( + "Diff < 10%", + Box::new(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() * 2); + tx.gas = Some(tx.gas.unwrap() * 89 / 100); + }), + InvalidTransaction::Call, + ), + ]; + + for (msg, update_tx, err) in scenarios { + let builder = + UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).update(update_tx); + + assert_eq!(builder.check(), Err(TransactionValidityError::Invalid(err)), "{}", msg); + } } #[test] fn check_transaction_tip() { - ExtBuilder::default().build().execute_with(|| { - let (code, _) = compile_module("dummy").unwrap(); - let data = vec![]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) - .update(|tx| tx.gas_price = tx.gas_price * 103 / 100); - - let tx = &builder.tx; - let expected_tip = tx.gas_price * tx.gas - U256::from(GAS_PRICE) * tx.gas; - let (_, extra) = builder.check().unwrap(); - assert_eq!(U256::from(extra.1.tip()), expected_tip); - }); + let (code, _) = compile_module("dummy").unwrap(); + let data = vec![]; + let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) + .update(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100); + log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price); + }); + + let tx = &builder.tx; + let expected_tip = + tx.gas_price.unwrap() * tx.gas.unwrap() - U256::from(GAS_PRICE) * tx.gas.unwrap(); + let (_, extra) = builder.check().unwrap(); + assert_eq!(U256::from(extra.1.tip()), expected_tip); } } diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 49c08166483e..b23d7e4e60ef 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -562,6 +562,9 @@ pub struct Stack<'a, T: Config, E> { debug_message: Option<&'a mut DebugBuffer>, /// Transient storage used to store data, which is kept for the duration of a transaction. transient_storage: TransientStorage, + /// Whether or not actual transfer of funds should be performed. + /// This is set to `true` exclusively when we simulate a call through eth_transact. + skip_transfer: bool, /// No executable is held by the struct but influences its behaviour. _phantom: PhantomData, } @@ -777,6 +780,7 @@ where storage_meter: &'a mut storage::meter::Meter, value: U256, input_data: Vec, + skip_transfer: bool, debug_message: Option<&'a mut DebugBuffer>, ) -> ExecResult { let dest = T::AddressMapper::to_account_id(&dest); @@ -786,6 +790,7 @@ where gas_meter, storage_meter, value, + skip_transfer, debug_message, )? { stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) @@ -812,6 +817,7 @@ where value: U256, input_data: Vec, salt: Option<&[u8; 32]>, + skip_transfer: bool, debug_message: Option<&'a mut DebugBuffer>, ) -> Result<(H160, ExecReturnValue), ExecError> { let (mut stack, executable) = Self::new( @@ -825,6 +831,7 @@ where gas_meter, storage_meter, value, + skip_transfer, debug_message, )? .expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE); @@ -853,6 +860,7 @@ where gas_meter, storage_meter, value.into(), + false, debug_message, ) .unwrap() @@ -869,6 +877,7 @@ where gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, value: U256, + skip_transfer: bool, debug_message: Option<&'a mut DebugBuffer>, ) -> Result, ExecError> { origin.ensure_mapped()?; @@ -896,6 +905,7 @@ where frames: Default::default(), debug_message, transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), + skip_transfer, _phantom: Default::default(), }; @@ -1073,6 +1083,7 @@ where &frame.account_id, frame.contract_info.get(&frame.account_id), executable.code_info(), + self.skip_transfer, )?; // Needs to be incremented before calling into the code so that it is visible // in case of recursion. @@ -2101,6 +2112,7 @@ mod tests { &mut storage_meter, value.into(), vec![], + false, None, ), Ok(_) @@ -2193,6 +2205,7 @@ mod tests { &mut storage_meter, value.into(), vec![], + false, None, ) .unwrap(); @@ -2233,6 +2246,7 @@ mod tests { &mut storage_meter, value.into(), vec![], + false, None, )); @@ -2269,6 +2283,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ), ExecError { @@ -2286,6 +2301,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -2314,6 +2330,7 @@ mod tests { &mut storage_meter, 55u64.into(), vec![], + false, None, ) .unwrap(); @@ -2363,6 +2380,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); @@ -2392,6 +2410,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); @@ -2421,6 +2440,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![1, 2, 3, 4], + false, None, ); assert_matches!(result, Ok(_)); @@ -2457,6 +2477,7 @@ mod tests { min_balance.into(), vec![1, 2, 3, 4], Some(&[0; 32]), + false, None, ); assert_matches!(result, Ok(_)); @@ -2511,6 +2532,7 @@ mod tests { &mut storage_meter, value.into(), vec![], + false, None, ); @@ -2575,6 +2597,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); @@ -2640,6 +2663,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); @@ -2672,6 +2696,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); assert_matches!(result, Ok(_)); @@ -2709,6 +2734,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -2735,6 +2761,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -2779,6 +2806,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -2805,6 +2833,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -2831,6 +2860,7 @@ mod tests { &mut storage_meter, 1u64.into(), vec![0], + false, None, ); assert_matches!(result, Err(_)); @@ -2875,6 +2905,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -2920,6 +2951,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); @@ -2946,6 +2978,7 @@ mod tests { U256::zero(), // <- zero value vec![], Some(&[0; 32]), + false, None, ), Err(_) @@ -2981,6 +3014,7 @@ mod tests { min_balance.into(), vec![], Some(&[0 ;32]), + false, None, ), Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address @@ -3032,10 +3066,10 @@ mod tests { executable, &mut gas_meter, &mut storage_meter, - min_balance.into(), vec![], Some(&[0; 32]), + false, None, ), Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address @@ -3100,6 +3134,7 @@ mod tests { &mut storage_meter, (min_balance * 10).into(), vec![], + false, None, ), Ok(_) @@ -3180,6 +3215,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ), Ok(_) @@ -3223,6 +3259,7 @@ mod tests { 100u64.into(), vec![], Some(&[0; 32]), + false, None, ), Err(Error::::TerminatedInConstructor.into()) @@ -3287,6 +3324,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -3349,6 +3387,7 @@ mod tests { 10u64.into(), vec![], Some(&[0; 32]), + false, None, ); assert_matches!(result, Ok(_)); @@ -3395,6 +3434,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap(); @@ -3426,6 +3466,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, Some(&mut debug_buffer), ) .unwrap(); @@ -3459,6 +3500,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, Some(&mut debug_buffer), ); assert!(result.is_err()); @@ -3492,6 +3534,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, Some(&mut debug_buf_after), ) .unwrap(); @@ -3525,6 +3568,7 @@ mod tests { &mut storage_meter, U256::zero(), CHARLIE_ADDR.as_bytes().to_vec(), + false, None, )); @@ -3537,6 +3581,7 @@ mod tests { &mut storage_meter, U256::zero(), BOB_ADDR.as_bytes().to_vec(), + false, None, ) .map_err(|e| e.error), @@ -3587,6 +3632,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ) .map_err(|e| e.error), @@ -3621,6 +3667,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap(); @@ -3705,6 +3752,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap(); @@ -3831,6 +3879,7 @@ mod tests { (min_balance * 100).into(), vec![], Some(&[0; 32]), + false, None, ) .ok(); @@ -3844,6 +3893,7 @@ mod tests { (min_balance * 100).into(), vec![], Some(&[0; 32]), + false, None, )); assert_eq!(System::account_nonce(&ALICE), 1); @@ -3856,6 +3906,7 @@ mod tests { (min_balance * 200).into(), vec![], Some(&[0; 32]), + false, None, )); assert_eq!(System::account_nonce(&ALICE), 2); @@ -3868,6 +3919,7 @@ mod tests { (min_balance * 200).into(), vec![], Some(&[0; 32]), + false, None, )); assert_eq!(System::account_nonce(&ALICE), 3); @@ -3936,6 +3988,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4047,6 +4100,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4086,6 +4140,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4125,6 +4180,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4178,6 +4234,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4234,6 +4291,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4309,6 +4367,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4379,6 +4438,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -4417,6 +4477,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, )); }); @@ -4479,6 +4540,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -4512,6 +4574,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); assert_matches!(result, Ok(_)); @@ -4595,6 +4658,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap() @@ -4663,6 +4727,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ); assert_matches!(result, Ok(_)); @@ -4734,6 +4799,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ); assert_matches!(result, Ok(_)); @@ -4785,6 +4851,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap() @@ -4854,6 +4921,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap() @@ -4900,6 +4968,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap() @@ -4944,6 +5013,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![], + false, None, ) .unwrap() @@ -4999,6 +5069,7 @@ mod tests { &mut storage_meter, U256::zero(), vec![0], + false, None, ), Ok(_) diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index b55854e2eec5..1dee1da03bc4 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -41,13 +41,13 @@ pub mod test_utils; pub mod weights; use crate::{ - evm::{runtime::GAS_PRICE, TransactionLegacyUnsigned}, + evm::{runtime::GAS_PRICE, GenericTransaction}, exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, wasm::{CodeInfo, RuntimeCosts, WasmBlob}, }; -use alloc::boxed::Box; +use alloc::{boxed::Box, format, vec}; use codec::{Codec, Decode, Encode}; use environmental::*; use frame_support::{ @@ -74,7 +74,7 @@ use pallet_transaction_payment::OnChargeTransaction; use scale_info::TypeInfo; use sp_core::{H160, H256, U256}; use sp_runtime::{ - traits::{BadOrigin, Convert, Dispatchable, Saturating, Zero}, + traits::{BadOrigin, Bounded, Convert, Dispatchable, Saturating, Zero}, DispatchError, }; @@ -823,7 +823,7 @@ pub mod pallet { dest, value, gas_limit, - storage_deposit_limit, + DepositLimit::Balance(storage_deposit_limit), data, DebugInfo::Skip, CollectEvents::Skip, @@ -859,7 +859,7 @@ pub mod pallet { origin, value, gas_limit, - storage_deposit_limit, + DepositLimit::Balance(storage_deposit_limit), Code::Existing(code_hash), data, salt, @@ -925,7 +925,7 @@ pub mod pallet { origin, value, gas_limit, - storage_deposit_limit, + DepositLimit::Balance(storage_deposit_limit), Code::Upload(code), data, salt, @@ -1083,7 +1083,7 @@ fn dispatch_result( impl Pallet where - BalanceOf: Into + TryFrom, + BalanceOf: Into + TryFrom + Bounded, MomentOf: Into, T::Hash: frame_support::traits::IsType, { @@ -1098,7 +1098,7 @@ where dest: H160, value: BalanceOf, gas_limit: Weight, - storage_deposit_limit: BalanceOf, + storage_deposit_limit: DepositLimit>, data: Vec, debug: DebugInfo, collect_events: CollectEvents, @@ -1112,7 +1112,10 @@ where }; let try_call = || { let origin = Origin::from_runtime_origin(origin)?; - let mut storage_meter = StorageMeter::new(&origin, storage_deposit_limit, value)?; + let mut storage_meter = match storage_deposit_limit { + DepositLimit::Balance(limit) => StorageMeter::new(&origin, limit, value)?, + DepositLimit::Unchecked => StorageMeter::new_unchecked(BalanceOf::::max_value()), + }; let result = ExecStack::>::run_call( origin.clone(), dest, @@ -1120,9 +1123,14 @@ where &mut storage_meter, Self::convert_native_to_evm(value), data, + storage_deposit_limit.is_unchecked(), debug_message.as_mut(), )?; - storage_deposit = storage_meter.try_into_deposit(&origin)?; + storage_deposit = storage_meter + .try_into_deposit(&origin, storage_deposit_limit.is_unchecked()) + .inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to transfer deposit: {err:?}"); + })?; Ok(result) }; let result = Self::run_guarded(try_call); @@ -1151,7 +1159,7 @@ where origin: OriginFor, value: BalanceOf, gas_limit: Weight, - mut storage_deposit_limit: BalanceOf, + storage_deposit_limit: DepositLimit>, code: Code, data: Vec, salt: Option<[u8; 32]>, @@ -1162,13 +1170,24 @@ where let mut storage_deposit = Default::default(); let mut debug_message = if debug == DebugInfo::UnsafeDebug { Some(DebugBuffer::default()) } else { None }; + + let unchecked_deposit_limit = storage_deposit_limit.is_unchecked(); + let mut storage_deposit_limit = match storage_deposit_limit { + DepositLimit::Balance(limit) => limit, + DepositLimit::Unchecked => BalanceOf::::max_value(), + }; + let try_instantiate = || { let instantiate_account = T::InstantiateOrigin::ensure_origin(origin.clone())?; let (executable, upload_deposit) = match code { Code::Upload(code) => { let upload_account = T::UploadOrigin::ensure_origin(origin)?; - let (executable, upload_deposit) = - Self::try_upload_code(upload_account, code, storage_deposit_limit)?; + let (executable, upload_deposit) = Self::try_upload_code( + upload_account, + code, + storage_deposit_limit, + unchecked_deposit_limit, + )?; storage_deposit_limit.saturating_reduce(upload_deposit); (executable, upload_deposit) }, @@ -1176,8 +1195,12 @@ where (WasmBlob::from_storage(code_hash, &mut gas_meter)?, Default::default()), }; let instantiate_origin = Origin::from_account_id(instantiate_account.clone()); - let mut storage_meter = - StorageMeter::new(&instantiate_origin, storage_deposit_limit, value)?; + let mut storage_meter = if unchecked_deposit_limit { + StorageMeter::new_unchecked(storage_deposit_limit) + } else { + StorageMeter::new(&instantiate_origin, storage_deposit_limit, value)? + }; + let result = ExecStack::>::run_instantiate( instantiate_account, executable, @@ -1186,10 +1209,11 @@ where Self::convert_native_to_evm(value), data, salt.as_ref(), + unchecked_deposit_limit, debug_message.as_mut(), ); storage_deposit = storage_meter - .try_into_deposit(&instantiate_origin)? + .try_into_deposit(&instantiate_origin, unchecked_deposit_limit)? .saturating_add(&StorageDeposit::Charge(upload_deposit)); result }; @@ -1215,28 +1239,15 @@ where /// /// # Parameters /// - /// - `origin`: The origin of the call. - /// - `dest`: The destination address of the call. - /// - `value`: The EVM value to transfer. - /// - `input`: The input data. + /// - `tx`: The Ethereum transaction to simulate. /// - `gas_limit`: The gas limit enforced during contract execution. - /// - `storage_deposit_limit`: The maximum balance that can be charged to the caller for storage - /// usage. /// - `utx_encoded_size`: A function that takes a call and returns the encoded size of the /// unchecked extrinsic. - /// - `debug`: Debugging configuration. - /// - `collect_events`: Event collection configuration. pub fn bare_eth_transact( - origin: T::AccountId, - dest: Option, - value: U256, - input: Vec, + mut tx: GenericTransaction, gas_limit: Weight, - storage_deposit_limit: BalanceOf, utx_encoded_size: impl Fn(Call) -> u32, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> EthContractResult> + ) -> Result>, EthTransactError> where T: pallet_transaction_payment::Config, ::RuntimeCall: @@ -1247,26 +1258,58 @@ where T::Nonce: Into, T::Hash: frame_support::traits::IsType, { - log::debug!(target: LOG_TARGET, "bare_eth_transact: dest: {dest:?} value: {value:?} - gas_limit: {gas_limit:?} storage_deposit_limit: {storage_deposit_limit:?}"); + log::debug!(target: LOG_TARGET, "bare_eth_transact: tx: {tx:?} gas_limit: {gas_limit:?}"); + + let from = tx.from.unwrap_or_default(); + let origin = T::AddressMapper::to_account_id(&from); - // Get the nonce to encode in the tx. - let nonce: T::Nonce = >::account_nonce(&origin); + let storage_deposit_limit = if tx.gas.is_some() { + DepositLimit::Balance(BalanceOf::::max_value()) + } else { + DepositLimit::Unchecked + }; + + // TODO remove once we have revisited how we encode the gas limit. + if tx.nonce.is_none() { + tx.nonce = Some(>::account_nonce(&origin).into()); + } + if tx.gas_price.is_none() { + tx.gas_price = Some(GAS_PRICE.into()); + } + if tx.chain_id.is_none() { + tx.chain_id = Some(T::ChainId::get().into()); + } // Convert the value to the native balance type. - let native_value = match Self::convert_evm_to_native(value) { + let evm_value = tx.value.unwrap_or_default(); + let native_value = match Self::convert_evm_to_native(evm_value) { Ok(v) => v, - Err(err) => - return EthContractResult { - gas_required: Default::default(), - storage_deposit: Default::default(), - fee: Default::default(), - result: Err(err.into()), - }, + Err(_) => return Err(EthTransactError::Message("Failed to convert value".into())), + }; + + let input = tx.input.clone().unwrap_or_default().0; + let debug = DebugInfo::Skip; + let collect_events = CollectEvents::Skip; + + let extract_error = |err| { + if err == Error::::TransferFailed.into() || + err == Error::::StorageDepositNotEnoughFunds.into() || + err == Error::::StorageDepositLimitExhausted.into() + { + let balance = Self::evm_balance(&from); + return Err(EthTransactError::Message( + format!("insufficient funds for gas * price + value: address {from:?} have {balance} (supplied gas {})", + tx.gas.unwrap_or_default())) + ); + } + + return Err(EthTransactError::Message(format!( + "Failed to instantiate contract: {err:?}" + ))); }; // Dry run the call - let (mut result, dispatch_info) = match dest { + let (mut result, dispatch_info) = match tx.to { // A contract call. Some(dest) => { // Dry run the call. @@ -1281,11 +1324,24 @@ where collect_events, ); - let result = EthContractResult { + let data = match result.result { + Ok(return_value) => { + if return_value.did_revert() { + return Err(EthTransactError::Data(return_value.data)); + } + return_value.data + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Failed to execute call: {err:?}"); + return extract_error(err) + }, + }; + + let result = EthTransactInfo { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(), - result: result.result, - fee: Default::default(), + data, + eth_gas: Default::default(), }; // Get the dispatch info of the call. let dispatch_call: ::RuntimeCall = crate::Call::::call { @@ -1326,11 +1382,24 @@ where collect_events, ); - let result = EthContractResult { + let returned_data = match result.result { + Ok(return_value) => { + if return_value.result.did_revert() { + return Err(EthTransactError::Data(return_value.result.data)); + } + return_value.result.data + }, + Err(err) => { + log::debug!(target: LOG_TARGET, "Failed to instantiate: {err:?}"); + return extract_error(err) + }, + }; + + let result = EthTransactInfo { gas_required: result.gas_required, storage_deposit: result.storage_deposit.charge_or_zero(), - result: result.result.map(|v| v.result), - fee: Default::default(), + data: returned_data, + eth_gas: Default::default(), }; // Get the dispatch info of the call. @@ -1348,23 +1417,18 @@ where }, }; - let mut tx = TransactionLegacyUnsigned { - value, - input: input.into(), - nonce: nonce.into(), - chain_id: Some(T::ChainId::get().into()), - gas_price: GAS_PRICE.into(), - to: dest, - ..Default::default() - }; - // The transaction fees depend on the extrinsic's length, which in turn is influenced by // the encoded length of the gas limit specified in the transaction (tx.gas). // We iteratively compute the fee by adjusting tx.gas until the fee stabilizes. // with a maximum of 3 iterations to avoid an infinite loop. for _ in 0..3 { + let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { + log::debug!(target: LOG_TARGET, "Failed to convert to unsigned"); + return Err(EthTransactError::Message("Invalid transaction".into())); + }; + let eth_dispatch_call = crate::Call::::eth_transact { - payload: tx.dummy_signed_payload(), + payload: unsigned_tx.dummy_signed_payload(), gas_limit: result.gas_required, storage_deposit_limit: result.storage_deposit, }; @@ -1375,17 +1439,18 @@ where 0u32.into(), ) .into(); + let eth_gas: U256 = (fee / GAS_PRICE.into()).into(); - if fee == result.fee { - log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} fee: {fee:?}"); + if eth_gas == result.eth_gas { + log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); break; } - result.fee = fee; - tx.gas = (fee / GAS_PRICE.into()).into(); - log::debug!(target: LOG_TARGET, "Adjusting Eth gas to: {:?}", tx.gas); + result.eth_gas = eth_gas; + tx.gas = Some(eth_gas.into()); + log::debug!(target: LOG_TARGET, "Adjusting Eth gas to: {eth_gas:?}"); } - result + Ok(result) } /// Get the balance with EVM decimals of the given `address`. @@ -1403,7 +1468,7 @@ where storage_deposit_limit: BalanceOf, ) -> CodeUploadResult> { let origin = T::UploadOrigin::ensure_origin(origin)?; - let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit)?; + let (module, deposit) = Self::try_upload_code(origin, code, storage_deposit_limit, false)?; Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) } @@ -1421,9 +1486,10 @@ where origin: T::AccountId, code: Vec, storage_deposit_limit: BalanceOf, + skip_transfer: bool, ) -> Result<(WasmBlob, BalanceOf), DispatchError> { let mut module = WasmBlob::from_code(code, origin)?; - let deposit = module.store_code()?; + let deposit = module.store_code(skip_transfer)?; ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); Ok((module, deposit)) } @@ -1527,14 +1593,7 @@ sp_api::decl_runtime_apis! { /// Perform an Ethereum call. /// /// See [`crate::Pallet::bare_eth_transact`] - fn eth_transact( - origin: H160, - dest: Option, - value: U256, - input: Vec, - gas_limit: Option, - storage_deposit_limit: Option, - ) -> EthContractResult; + fn eth_transact(tx: GenericTransaction) -> Result, EthTransactError>; /// Upload new code without instantiating a contract from it. /// diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 024b1f3448e1..a7127f812b4b 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -17,8 +17,8 @@ //! A crate that hosts a common definitions that are relevant for the pallet-revive. -use crate::H160; -use alloc::vec::Vec; +use crate::{H160, U256}; +use alloc::{string::String, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::weights::Weight; use pallet_revive_uapi::ReturnFlags; @@ -28,6 +28,30 @@ use sp_runtime::{ DispatchError, RuntimeDebug, }; +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum DepositLimit { + /// Allows bypassing all balance transfer checks. + Unchecked, + + /// Specifies a maximum allowable balance for a deposit. + Balance(Balance), +} + +impl DepositLimit { + pub fn is_unchecked(&self) -> bool { + match self { + Self::Unchecked => true, + _ => false, + } + } +} + +impl From for DepositLimit { + fn from(value: T) -> Self { + Self::Balance(value) + } +} + /// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and /// `ContractsApi::instantiate`. /// @@ -84,15 +108,22 @@ pub struct ContractResult { /// The result of the execution of a `eth_transact` call. #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct EthContractResult> { - /// The fee charged for the execution. - pub fee: Balance, +pub struct EthTransactInfo { /// The amount of gas that was necessary to execute the transaction. pub gas_required: Weight, /// Storage deposit charged. pub storage_deposit: Balance, - /// The execution result. - pub result: R, + /// The weight and deposit equivalent in EVM Gas. + pub eth_gas: U256, + /// The execution return value. + pub data: Vec, +} + +/// Error type of a `eth_transact` call. +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum EthTransactError { + Data(Vec), + Message(String), } /// Result type of a `bare_code_upload` call. diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index 712010bc8257..6eddf048be98 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -373,24 +373,36 @@ where } } + /// Create new storage meter without checking the limit. + pub fn new_unchecked(limit: BalanceOf) -> Self { + return Self { limit, ..Default::default() } + } + /// The total amount of deposit that should change hands as result of the execution /// that this meter was passed into. This will also perform all the charges accumulated /// in the whole contract stack. /// /// This drops the root meter in order to make sure it is only called when the whole /// execution did finish. - pub fn try_into_deposit(self, origin: &Origin) -> Result, DispatchError> { - // Only refund or charge deposit if the origin is not root. - let origin = match origin { - Origin::Root => return Ok(Deposit::Charge(Zero::zero())), - Origin::Signed(o) => o, - }; - for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) { - E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; - } - for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) { - E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; + pub fn try_into_deposit( + self, + origin: &Origin, + skip_transfer: bool, + ) -> Result, DispatchError> { + if !skip_transfer { + // Only refund or charge deposit if the origin is not root. + let origin = match origin { + Origin::Root => return Ok(Deposit::Charge(Zero::zero())), + Origin::Signed(o) => o, + }; + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) { + E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; + } + for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) { + E::charge(origin, &charge.contract, &charge.amount, &charge.state)?; + } } + Ok(self.total_deposit) } } @@ -425,13 +437,18 @@ impl> RawMeter { contract: &T::AccountId, contract_info: &mut ContractInfo, code_info: &CodeInfo, + skip_transfer: bool, ) -> Result<(), DispatchError> { debug_assert!(matches!(self.contract_state(), ContractState::Alive)); // We need to make sure that the contract's account exists. let ed = Pallet::::min_balance(); self.total_deposit = Deposit::Charge(ed); - T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?; + if skip_transfer { + T::Currency::set_balance(contract, ed); + } else { + T::Currency::transfer(origin, contract, ed, Preservation::Preserve)?; + } // A consumer is added at account creation and removed it on termination, otherwise the // runtime could remove the account. As long as a contract exists its account must exist. @@ -479,6 +496,7 @@ impl> RawMeter { } if let Deposit::Charge(amount) = total_deposit { if amount > self.limit { + log::debug!( target: LOG_TARGET, "Storage deposit limit exhausted: {:?} > {:?}", amount, self.limit); return Err(>::StorageDepositLimitExhausted.into()) } } @@ -811,7 +829,10 @@ mod tests { nested0.enforce_limit(Some(&mut nested0_info)).unwrap(); meter.absorb(nested0, &BOB, Some(&mut nested0_info)); - assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit); + assert_eq!( + meter.try_into_deposit(&test_case.origin, false).unwrap(), + test_case.deposit + ); assert_eq!(nested0_info.extra_deposit(), 112); assert_eq!(nested1_info.extra_deposit(), 110); @@ -882,7 +903,10 @@ mod tests { nested0.absorb(nested1, &CHARLIE, None); meter.absorb(nested0, &BOB, None); - assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit); + assert_eq!( + meter.try_into_deposit(&test_case.origin, false).unwrap(), + test_case.deposit + ); assert_eq!(TestExtTestValue::get(), test_case.expected) } } diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index e64f58894432..8ba5e7384070 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -18,7 +18,8 @@ use super::{deposit_limit, GAS_LIMIT}; use crate::{ address::AddressMapper, AccountIdOf, BalanceOf, Code, CollectEvents, Config, ContractResult, - DebugInfo, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, + DebugInfo, DepositLimit, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor, + Pallet, Weight, }; use frame_support::pallet_prelude::DispatchResultWithPostInfo; use paste::paste; @@ -133,7 +134,7 @@ builder!( origin: OriginFor, value: BalanceOf, gas_limit: Weight, - storage_deposit_limit: BalanceOf, + storage_deposit_limit: DepositLimit>, code: Code, data: Vec, salt: Option<[u8; 32]>, @@ -159,7 +160,7 @@ builder!( origin, value: 0u32.into(), gas_limit: GAS_LIMIT, - storage_deposit_limit: deposit_limit::(), + storage_deposit_limit: DepositLimit::Balance(deposit_limit::()), code, data: vec![], salt: Some([0; 32]), @@ -198,7 +199,7 @@ builder!( dest: H160, value: BalanceOf, gas_limit: Weight, - storage_deposit_limit: BalanceOf, + storage_deposit_limit: DepositLimit>, data: Vec, debug: DebugInfo, collect_events: CollectEvents, @@ -216,7 +217,7 @@ builder!( dest, value: 0u32.into(), gas_limit: GAS_LIMIT, - storage_deposit_limit: deposit_limit::(), + storage_deposit_limit: DepositLimit::Balance(deposit_limit::()), data: vec![], debug: DebugInfo::UnsafeDebug, collect_events: CollectEvents::Skip, diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 34afe8aabfe6..1df300f031a7 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -1249,7 +1249,7 @@ fn transfer_expendable_cannot_kill_account() { test_utils::contract_info_storage_deposit(&addr) ); - // Some ot the total balance is held, so it can't be transferred. + // Some or the total balance is held, so it can't be transferred. assert_err!( <::Currency as Mutate>::transfer( &account, @@ -2290,7 +2290,7 @@ fn gas_estimation_for_subcalls() { // Make the same call using the estimated gas. Should succeed. let result = builder::bare_call(addr_caller) .gas_limit(result_orig.gas_required) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); assert_ok!(&result.result); @@ -2298,7 +2298,7 @@ fn gas_estimation_for_subcalls() { // Check that it fails with too little ref_time let result = builder::bare_call(addr_caller) .gas_limit(result_orig.gas_required.sub_ref_time(1)) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); assert_err!(result.result, error); @@ -2306,7 +2306,7 @@ fn gas_estimation_for_subcalls() { // Check that it fails with too little proof_size let result = builder::bare_call(addr_caller) .gas_limit(result_orig.gas_required.sub_proof_size(1)) - .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero()) + .storage_deposit_limit(result_orig.storage_deposit.charge_or_zero().into()) .data(input.clone()) .build(); assert_err!(result.result, error); @@ -3592,7 +3592,7 @@ fn deposit_limit_in_nested_instantiate() { // Set enough deposit limit for the child instantiate. This should succeed. let result = builder::bare_call(addr_caller) .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 4 + 2) + .storage_deposit_limit((callee_info_len + 2 + ED + 4 + 2).into()) .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 3 + 2)).encode()) .build(); @@ -3879,7 +3879,7 @@ fn locking_delegate_dependency_works() { // Locking a dependency with a storage limit too low should fail. assert_err!( builder::bare_call(addr_caller) - .storage_deposit_limit(dependency_deposit - 1) + .storage_deposit_limit((dependency_deposit - 1).into()) .data((1u32, hash2addr(&callee_hashes[0]), callee_hashes[0]).encode()) .build() .result, diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs index 7c4fbba71f65..c9e19e52ace1 100644 --- a/substrate/frame/revive/src/tests/test_debug.rs +++ b/substrate/frame/revive/src/tests/test_debug.rs @@ -21,6 +21,7 @@ use crate::{ debug::{CallInterceptor, CallSpan, ExecResult, ExportedFunction, Tracing}, primitives::ExecReturnValue, test_utils::*, + DepositLimit, }; use frame_support::traits::Currency; use pretty_assertions::assert_eq; @@ -114,7 +115,7 @@ fn debugging_works() { RuntimeOrigin::signed(ALICE), 0, GAS_LIMIT, - deposit_limit::(), + DepositLimit::Balance(deposit_limit::()), Code::Upload(wasm), vec![], Some([0u8; 32]), @@ -198,7 +199,7 @@ fn call_interception_works() { RuntimeOrigin::signed(ALICE), 0, GAS_LIMIT, - deposit_limit::(), + deposit_limit::().into(), Code::Upload(wasm), vec![], // some salt to ensure that the address of this contract is unique among all tests diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index d87ec7112286..54fb02c866e1 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -183,7 +183,7 @@ where } /// Puts the module blob into storage, and returns the deposit collected for the storage. - pub fn store_code(&mut self) -> Result, Error> { + pub fn store_code(&mut self, skip_transfer: bool) -> Result, Error> { let code_hash = *self.code_hash(); >::mutate(code_hash, |stored_code_info| { match stored_code_info { @@ -195,15 +195,16 @@ where // the `owner` is always the origin of the current transaction. None => { let deposit = self.code_info.deposit; - T::Currency::hold( + + if !skip_transfer { + T::Currency::hold( &HoldReason::CodeUploadDepositReserve.into(), &self.code_info.owner, deposit, - ) - .map_err(|err| { - log::debug!(target: LOG_TARGET, "failed to store code for owner: {:?}: {err:?}", self.code_info.owner); + ) .map_err(|err| { log::debug!(target: LOG_TARGET, "failed to store code for owner: {:?}: {err:?}", self.code_info.owner); >::StorageDepositNotEnoughFunds })?; + } self.code_info.refcount = 0; >::insert(code_hash, &self.code); From c0921339f9d486981b3681760ee83ba9237f2eaa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 07:20:56 +0000 Subject: [PATCH 079/340] Bump the ci_dependencies group across 1 directory with 3 updates (#6516) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps the ci_dependencies group with 3 updates in the / directory: [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action), [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) and [codecov/codecov-action](https://github.com/codecov/codecov-action). Updates `lycheeverse/lychee-action` from 2.0.2 to 2.1.0
Commits

Updates `actions/attest-build-provenance` from 1.4.3 to 1.4.4
Release notes

Sourced from actions/attest-build-provenance's releases.

v1.4.4

What's Changed

Full Changelog: https://github.com/actions/attest-build-provenance/compare/v1.4.3...v1.4.4

Commits
  • ef24412 bump predicate from 1.1.3 to 1.1.4 (#310)
  • 36fa7d0 bump @​actions/attest from 1.4.2 to 1.5.0 (#309)
  • 390c0bb Bump @​types/node from 22.8.1 to 22.8.7 in the npm-development group (#305)
  • 21da615 Bump the npm-development group with 3 updates (#299)
  • 0704961 Bump actions/publish-immutable-action in the actions-minor group (#298)
  • d01b070 Bump the npm-development group with 3 updates (#278)
  • b1d65e4 Add workflow file for publishing releases to immutable action package (#277)
  • 3a27694 Bump @​actions/core from 1.10.1 to 1.11.1 (#275)
  • dff1ae6 prevent e2e workflows on forks (#272)
  • e5892d0 Bump the npm-development group with 3 updates (#263)
  • Additional commits viewable in compare view

Updates `codecov/codecov-action` from 4 to 5
Release notes

Sourced from codecov/codecov-action's releases.

v5.0.0

v5 Release

v5 of the Codecov GitHub Action will use the Codecov Wrapper to encapsulate the CLI. This will help ensure that the Action gets updates quicker.

Migration Guide

The v5 release also coincides with the opt-out feature for tokens for public repositories. In the Global Upload Token section of the settings page of an organization in codecov.io, you can set the ability for Codecov to receive a coverage reports from any source. This will allow contributors or other members of a repository to upload without needing access to the Codecov token. For more details see how to upload without a token.

[!WARNING]
The following arguments have been changed

  • file (this has been deprecated in favor of files)
  • plugin (this has been deprecated in favor of plugins)

The following arguments have been added:

  • binary
  • gcov_args
  • gcov_executable
  • gcov_ignore
  • gcov_include
  • report_type
  • skip_validation
  • swift_project

You can see their usage in the action.yml file.

What's Changed

... (truncated)

Changelog

Sourced from codecov/codecov-action's changelog.

4.0.0-beta.2

Fixes

  • #1085 not adding -n if empty to do-upload command

4.0.0-beta.1

v4 represents a move from the universal uploader to the Codecov CLI. Although this will unlock new features for our users, the CLI is not yet at feature parity with the universal uploader.

Breaking Changes

  • No current support for aarch64 and alpine architectures.
  • Tokenless uploading is unsuported
  • Various arguments to the Action have been removed

3.1.4

Fixes

  • #967 Fix typo in README.md
  • #971 fix: add back in working dir
  • #969 fix: CLI option names for uploader

Dependencies

  • #970 build(deps-dev): bump @​types/node from 18.15.12 to 18.16.3
  • #979 build(deps-dev): bump @​types/node from 20.1.0 to 20.1.2
  • #981 build(deps-dev): bump @​types/node from 20.1.2 to 20.1.4

3.1.3

Fixes

  • #960 fix: allow for aarch64 build

Dependencies

  • #957 build(deps-dev): bump jest-junit from 15.0.0 to 16.0.0
  • #958 build(deps): bump openpgp from 5.7.0 to 5.8.0
  • #959 build(deps-dev): bump @​types/node from 18.15.10 to 18.15.12

3.1.2

Fixes

  • #718 Update README.md
  • #851 Remove unsupported path_to_write_report argument
  • #898 codeql-analysis.yml
  • #901 Update README to contain correct information - inputs and negate feature
  • #955 fix: add in all the extra arguments for uploader

Dependencies

  • #819 build(deps): bump openpgp from 5.4.0 to 5.5.0
  • #835 build(deps): bump node-fetch from 3.2.4 to 3.2.10
  • #840 build(deps): bump ossf/scorecard-action from 1.1.1 to 2.0.4
  • #841 build(deps): bump @​actions/core from 1.9.1 to 1.10.0
  • #843 build(deps): bump @​actions/github from 5.0.3 to 5.1.1
  • #869 build(deps): bump node-fetch from 3.2.10 to 3.3.0
  • #872 build(deps-dev): bump jest-junit from 13.2.0 to 15.0.0
  • #879 build(deps): bump decode-uri-component from 0.2.0 to 0.2.2

... (truncated)

Commits

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore ` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore ` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore ` will remove the ignore condition of the specified dependency and ignore conditions
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-links.yml | 2 +- .github/workflows/release-reusable-rc-buid.yml | 6 +++--- .github/workflows/tests-linux-stable-coverage.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml index dd9d3eaf824f..cea6b9a8636a 100644 --- a/.github/workflows/check-links.yml +++ b/.github/workflows/check-links.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.0 (22. Sep 2023) - name: Lychee link checker - uses: lycheeverse/lychee-action@7cd0af4c74a61395d455af97419279d86aafaede # for v1.9.1 (10. Jan 2024) + uses: lycheeverse/lychee-action@f81112d0d2814ded911bd23e3beaa9dda9093915 # for v1.9.1 (10. Jan 2024) with: args: >- --config .config/lychee.toml diff --git a/.github/workflows/release-reusable-rc-buid.yml b/.github/workflows/release-reusable-rc-buid.yml index 7e31a4744b59..f5240878cba2 100644 --- a/.github/workflows/release-reusable-rc-buid.yml +++ b/.github/workflows/release-reusable-rc-buid.yml @@ -104,7 +104,7 @@ jobs: ./.github/scripts/release/build-linux-release.sh ${{ matrix.binaries }} ${{ inputs.package }} - name: Generate artifact attestation - uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-path: /artifacts/${{ matrix.binaries }}/${{ matrix.binaries }} @@ -220,7 +220,7 @@ jobs: ./.github/scripts/release/build-macos-release.sh ${{ matrix.binaries }} ${{ inputs.package }} - name: Generate artifact attestation - uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-path: ${{ env.ARTIFACTS_PATH }}/${{ matrix.binaries }} @@ -278,7 +278,7 @@ jobs: . "${GITHUB_WORKSPACE}"/.github/scripts/release/build-deb.sh ${{ inputs.package }} ${VERSION} - name: Generate artifact attestation - uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4 with: subject-path: target/production/*.deb diff --git a/.github/workflows/tests-linux-stable-coverage.yml b/.github/workflows/tests-linux-stable-coverage.yml index c5af6bcae77f..61e01cda4428 100644 --- a/.github/workflows/tests-linux-stable-coverage.yml +++ b/.github/workflows/tests-linux-stable-coverage.yml @@ -102,7 +102,7 @@ jobs: merge-multiple: true - run: ls -al reports/ - name: Upload to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} verbose: true From 7e64403ac977dd2acb8e7baac3e245c93c32355a Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 2 Dec 2024 13:46:15 +0200 Subject: [PATCH 080/340] add pool --- .../src/tests/snowbridge_v2.rs | 78 +++++++++++++++++-- 1 file changed, 73 insertions(+), 5 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 899e89f32888..db86166743ad 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -44,6 +44,10 @@ pub fn weth_location() -> Location { ) } +pub fn dot_location() -> Location { + Location::new(1, Here) +} + pub fn register_weth() { let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); @@ -76,16 +80,72 @@ pub fn register_weth() { }); } +pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { + let wnd: v5::Location = v5::Parent.into(); + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let owner = BridgeHubWestend::sovereign_account_id_of(assethub_location); + + AssetHubWestend::fund_accounts(vec![ + (owner.clone(), 3_000_000_000_000), + ]); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::mint( + signed_owner.clone(), + asset.clone().into(), + owner.clone().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + signed_owner.clone(), + Box::new(wnd.clone()), + Box::new(asset.clone()), + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + signed_owner.clone(), + Box::new(wnd), + Box::new(asset), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner.into() + )); + /* + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + );*/ + }); +} + #[test] fn register_token_v2() { - BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); - let relayer = BridgeHubWestendSender::get(); let receiver = AssetHubWestendReceiver::get(); BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); register_weth(); + set_up_weth_pool_with_wnd_on_ah_westend(weth_location()); + let chain_id = 11155111u64; let claimer = AccountId32 { network: None, id: receiver.clone().into() }; let claimer_bytes = claimer.encode(); @@ -93,8 +153,7 @@ fn register_token_v2() { let relayer_location = Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - AssetHubWestend::fund_accounts(vec![(owner.into(), INITIAL_FUND)]); + let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); let weth_amount = 9_000_000_000_000u128; @@ -110,16 +169,25 @@ fn register_token_v2() { [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], ); + let dot_asset = Location::new(1, Here); + let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; let register_token_instructions = vec![ + // Exchange weth for dot to pay the asset creation deposit + ExchangeAsset { give: weth_fee.clone().into(), want: dot_fee.clone().into(), maximal: false }, + // Deposit the dot deposit into the bridge sovereign account (where the asset creation fee + // will be deducted from) + DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, + // Pay for the transact execution PayFees { asset: weth_fee.into() }, Transact { origin_kind: OriginKind::Xcm, call: ( CreateAssetCall::get(), asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner.into()), + MultiAddress::<[u8; 32], ()>::Id(bridge_owner.into()), 1u128, ) .encode() From d77a1ee566a6a322dda831da725efac0b4781087 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 2 Dec 2024 14:09:25 +0200 Subject: [PATCH 081/340] fix pool owner --- .../src/tests/snowbridge_v2.rs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index db86166743ad..0f2e0f118ad0 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -83,7 +83,8 @@ pub fn register_weth() { pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { let wnd: v5::Location = v5::Parent.into(); let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); - let owner = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let owner = AssetHubWestendSender::get(); + let bh_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); AssetHubWestend::fund_accounts(vec![ (owner.clone(), 3_000_000_000_000), @@ -93,11 +94,19 @@ pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { type RuntimeEvent = ::RuntimeEvent; let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + let signed_bh_sovereign = ::RuntimeOrigin::signed(bh_sovereign.clone()); type RuntimeOrigin = ::RuntimeOrigin; assert_ok!(::ForeignAssets::mint( - signed_owner.clone(), + signed_bh_sovereign.clone(), + asset.clone().into(), + bh_sovereign.clone().into(), + 3_500_000_000_000, + )); + + assert_ok!(::ForeignAssets::transfer( + signed_bh_sovereign.clone(), asset.clone().into(), owner.clone().into(), 3_000_000_000_000, @@ -126,13 +135,13 @@ pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { 1, owner.into() )); - /* + assert_expected_events!( AssetHubWestend, vec![ RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, ] - );*/ + ); }); } @@ -163,6 +172,7 @@ fn register_token_v2() { let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); let weth_fee: xcm::prelude::Asset = (weth_location(), WETH_FEE).into(); + let weth_transact_fee: xcm::prelude::Asset = (weth_location(), WETH_FEE / 2).into(); let asset_id = Location::new( 2, @@ -181,7 +191,7 @@ fn register_token_v2() { // will be deducted from) DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, // Pay for the transact execution - PayFees { asset: weth_fee.into() }, + //PayFees { asset: weth_transact_fee.into() }, Transact { origin_kind: OriginKind::Xcm, call: ( From 0845044454c005b577eab7afaea18583bd7e3dd3 Mon Sep 17 00:00:00 2001 From: clangenb <37865735+clangenb@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:07:21 +0100 Subject: [PATCH 082/340] migrate pallet-session-benchmarking to bench V2 syntax (#6294) Migrates pallet-session-benchmarking to bench V2 syntax. Part of: * #6202 --------- Co-authored-by: Shawn Tabrizi Co-authored-by: Giuseppe Re --- .../frame/session/benchmarking/src/inner.rs | 68 ++++++++++++------- .../frame/session/benchmarking/src/mock.rs | 6 +- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/substrate/frame/session/benchmarking/src/inner.rs b/substrate/frame/session/benchmarking/src/inner.rs index 9ba47b34ed7a..9789b6bb593d 100644 --- a/substrate/frame/session/benchmarking/src/inner.rs +++ b/substrate/frame/session/benchmarking/src/inner.rs @@ -22,7 +22,7 @@ use alloc::{vec, vec::Vec}; use sp_runtime::traits::{One, StaticLookup, TrailingZeroInput}; use codec::Decode; -use frame_benchmarking::v1::benchmarks; +use frame_benchmarking::v2::*; use frame_support::traits::{Get, KeyOwnerProofSystem, OnInitialize}; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_session::{historical::Pallet as Historical, Pallet as Session, *}; @@ -45,8 +45,12 @@ impl OnInitialize> for Pallet { } } -benchmarks! { - set_keys { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn set_keys() -> Result<(), BenchmarkError> { let n = MaxNominationsOf::::get(); let (v_stash, _) = create_validator_with_nominators::( n, @@ -58,13 +62,19 @@ benchmarks! { let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap(); - let proof: Vec = vec![0,1,2,3]; + let proof: Vec = vec![0, 1, 2, 3]; // Whitelist controller account from further DB operations. let v_controller_key = frame_system::Account::::hashed_key_for(&v_controller); frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into()); - }: _(RawOrigin::Signed(v_controller), keys, proof) - purge_keys { + #[extrinsic_call] + _(RawOrigin::Signed(v_controller), keys, proof); + + Ok(()) + } + + #[benchmark] + fn purge_keys() -> Result<(), BenchmarkError> { let n = MaxNominationsOf::::get(); let (v_stash, _) = create_validator_with_nominators::( n, @@ -75,30 +85,33 @@ benchmarks! { )?; let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap(); - let proof: Vec = vec![0,1,2,3]; + let proof: Vec = vec![0, 1, 2, 3]; Session::::set_keys(RawOrigin::Signed(v_controller.clone()).into(), keys, proof)?; // Whitelist controller account from further DB operations. let v_controller_key = frame_system::Account::::hashed_key_for(&v_controller); frame_benchmarking::benchmarking::add_to_whitelist(v_controller_key.into()); - }: _(RawOrigin::Signed(v_controller)) - #[extra] - check_membership_proof_current_session { - let n in 2 .. MAX_VALIDATORS as u32; + #[extrinsic_call] + _(RawOrigin::Signed(v_controller)); + Ok(()) + } + + #[benchmark(extra)] + fn check_membership_proof_current_session(n: Linear<2, MAX_VALIDATORS>) { let (key, key_owner_proof1) = check_membership_proof_setup::(n); let key_owner_proof2 = key_owner_proof1.clone(); - }: { - Historical::::check_proof(key, key_owner_proof1); - } - verify { + + #[block] + { + Historical::::check_proof(key, key_owner_proof1); + } + assert!(Historical::::check_proof(key, key_owner_proof2).is_some()); } - #[extra] - check_membership_proof_historical_session { - let n in 2 .. MAX_VALIDATORS as u32; - + #[benchmark(extra)] + fn check_membership_proof_historical_session(n: Linear<2, MAX_VALIDATORS>) { let (key, key_owner_proof1) = check_membership_proof_setup::(n); // skip to the next session so that the session is historical @@ -106,14 +119,21 @@ benchmarks! { Session::::rotate_session(); let key_owner_proof2 = key_owner_proof1.clone(); - }: { - Historical::::check_proof(key, key_owner_proof1); - } - verify { + + #[block] + { + Historical::::check_proof(key, key_owner_proof1); + } + assert!(Historical::::check_proof(key, key_owner_proof2).is_some()); } - impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test, extra = false); + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(), + crate::mock::Test, + extra = false + ); } /// Sets up the benchmark for checking a membership proof. It creates the given diff --git a/substrate/frame/session/benchmarking/src/mock.rs b/substrate/frame/session/benchmarking/src/mock.rs index 2aec58cceded..346cd04c0fa9 100644 --- a/substrate/frame/session/benchmarking/src/mock.rs +++ b/substrate/frame/session/benchmarking/src/mock.rs @@ -27,7 +27,7 @@ use frame_support::{ derive_impl, parameter_types, traits::{ConstU32, ConstU64}, }; -use sp_runtime::{traits::IdentityLookup, BuildStorage}; +use sp_runtime::{traits::IdentityLookup, BuildStorage, KeyTypeId}; type AccountId = u64; type Nonce = u32; @@ -42,6 +42,7 @@ frame_support::construct_runtime!( Balances: pallet_balances, Staking: pallet_staking, Session: pallet_session, + Historical: pallet_session::historical } ); @@ -79,7 +80,8 @@ sp_runtime::impl_opaque_keys! { pub struct TestSessionHandler; impl pallet_session::SessionHandler for TestSessionHandler { - const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[]; + // corresponds to the opaque key id above + const KEY_TYPE_IDS: &'static [KeyTypeId] = &[KeyTypeId([100u8, 117u8, 109u8, 121u8])]; fn on_genesis_session(_validators: &[(AccountId, Ks)]) {} From 3d8da815ecd12b8f04daf87d6ffba5ec4a181806 Mon Sep 17 00:00:00 2001 From: clangenb <37865735+clangenb@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:36:52 +0100 Subject: [PATCH 083/340] migrate pallet-offences-benchmarking to benchmark v2 syntax (#6300) Migrates pallet-offences-benchmarking to benchmark v2 syntax. Part of: * #6202 --------- Co-authored-by: Giuseppe Re --- .../frame/offences/benchmarking/src/inner.rs | 107 +++++++++++------- .../frame/offences/benchmarking/src/mock.rs | 5 +- 2 files changed, 66 insertions(+), 46 deletions(-) diff --git a/substrate/frame/offences/benchmarking/src/inner.rs b/substrate/frame/offences/benchmarking/src/inner.rs index 573114de0742..75f3e9931e34 100644 --- a/substrate/frame/offences/benchmarking/src/inner.rs +++ b/substrate/frame/offences/benchmarking/src/inner.rs @@ -19,7 +19,7 @@ use alloc::{vec, vec::Vec}; -use frame_benchmarking::v1::{account, benchmarks}; +use frame_benchmarking::v2::*; use frame_support::traits::Get; use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin}; @@ -144,7 +144,7 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' fn make_offenders( num_offenders: u32, num_nominators: u32, -) -> Result<(Vec>, Vec>), &'static str> { +) -> Result>, &'static str> { Staking::::new_session(0); let mut offenders = vec![]; @@ -167,21 +167,50 @@ fn make_offenders( .expect("failed to convert validator id to full identification") }) .collect::>>(); - Ok((id_tuples, offenders)) + Ok(id_tuples) } -benchmarks! { - where_clause { - where +#[cfg(test)] +fn assert_all_slashes_applied(offender_count: usize) +where + T: Config, + ::RuntimeEvent: TryInto>, + ::RuntimeEvent: TryInto>, + ::RuntimeEvent: TryInto, + ::RuntimeEvent: TryInto>, +{ + // make sure that all slashes have been applied + // (n nominators + one validator) * (slashed + unlocked) + deposit to reporter + + // reporter account endowed + some funds rescinded from issuance. + assert_eq!( + System::::read_events_for_pallet::>().len(), + 2 * (offender_count + 1) + 3 + ); + // (n nominators + one validator) * slashed + Slash Reported + assert_eq!( + System::::read_events_for_pallet::>().len(), + 1 * (offender_count + 1) + 1 + ); + // offence + assert_eq!(System::::read_events_for_pallet::().len(), 1); + // reporter new account + assert_eq!(System::::read_events_for_pallet::>().len(), 1); +} + +#[benchmarks( + where ::RuntimeEvent: TryInto>, ::RuntimeEvent: TryInto>, ::RuntimeEvent: TryInto, ::RuntimeEvent: TryInto>, - } - - report_offence_grandpa { - let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::::get()); - +)] +mod benchmarks { + use super::*; + + #[benchmark] + pub fn report_offence_grandpa( + n: Linear<0, { MAX_NOMINATORS.min(MaxNominationsOf::::get()) }>, + ) -> Result<(), BenchmarkError> { // for grandpa equivocation reports the number of reporters // and offenders is always 1 let reporters = vec![account("reporter", 1, SEED)]; @@ -189,7 +218,7 @@ benchmarks! { // make sure reporters actually get rewarded Staking::::set_slash_reward_fraction(Perbill::one()); - let (mut offenders, raw_offenders) = make_offenders::(1, n)?; + let mut offenders = make_offenders::(1, n)?; let validator_set_count = Session::::validators().len() as u32; let offence = GrandpaEquivocationOffence { @@ -199,28 +228,24 @@ benchmarks! { offender: T::convert(offenders.pop().unwrap()), }; assert_eq!(System::::event_count(), 0); - }: { - let _ = Offences::::report_offence(reporters, offence); - } - verify { + + #[block] + { + let _ = Offences::::report_offence(reporters, offence); + } + #[cfg(test)] { - // make sure that all slashes have been applied - // (n nominators + one validator) * (slashed + unlocked) + deposit to reporter + reporter - // account endowed + some funds rescinded from issuance. - assert_eq!(System::::read_events_for_pallet::>().len(), 2 * (n + 1) as usize + 3); - // (n nominators + one validator) * slashed + Slash Reported - assert_eq!(System::::read_events_for_pallet::>().len(), 1 * (n + 1) as usize + 1); - // offence - assert_eq!(System::::read_events_for_pallet::().len(), 1); - // reporter new account - assert_eq!(System::::read_events_for_pallet::>().len(), 1); + assert_all_slashes_applied::(n as usize); } - } - report_offence_babe { - let n in 0 .. MAX_NOMINATORS.min(MaxNominationsOf::::get()); + Ok(()) + } + #[benchmark] + fn report_offence_babe( + n: Linear<0, { MAX_NOMINATORS.min(MaxNominationsOf::::get()) }>, + ) -> Result<(), BenchmarkError> { // for babe equivocation reports the number of reporters // and offenders is always 1 let reporters = vec![account("reporter", 1, SEED)]; @@ -228,7 +253,7 @@ benchmarks! { // make sure reporters actually get rewarded Staking::::set_slash_reward_fraction(Perbill::one()); - let (mut offenders, raw_offenders) = make_offenders::(1, n)?; + let mut offenders = make_offenders::(1, n)?; let validator_set_count = Session::::validators().len() as u32; let offence = BabeEquivocationOffence { @@ -238,23 +263,17 @@ benchmarks! { offender: T::convert(offenders.pop().unwrap()), }; assert_eq!(System::::event_count(), 0); - }: { - let _ = Offences::::report_offence(reporters, offence); - } - verify { + + #[block] + { + let _ = Offences::::report_offence(reporters, offence); + } #[cfg(test)] { - // make sure that all slashes have been applied - // (n nominators + one validator) * (slashed + unlocked) + deposit to reporter + reporter - // account endowed + some funds rescinded from issuance. - assert_eq!(System::::read_events_for_pallet::>().len(), 2 * (n + 1) as usize + 3); - // (n nominators + one validator) * slashed + Slash Reported - assert_eq!(System::::read_events_for_pallet::>().len(), 1 * (n + 1) as usize + 1); - // offence - assert_eq!(System::::read_events_for_pallet::().len(), 1); - // reporter new account - assert_eq!(System::::read_events_for_pallet::>().len(), 1); + assert_all_slashes_applied::(n as usize); } + + Ok(()) } impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/substrate/frame/offences/benchmarking/src/mock.rs b/substrate/frame/offences/benchmarking/src/mock.rs index efaec49a65b3..c5c178aa4443 100644 --- a/substrate/frame/offences/benchmarking/src/mock.rs +++ b/substrate/frame/offences/benchmarking/src/mock.rs @@ -29,7 +29,7 @@ use frame_system as system; use pallet_session::historical as pallet_session_historical; use sp_runtime::{ testing::{Header, UintAuthorityId}, - BuildStorage, Perbill, + BuildStorage, KeyTypeId, Perbill, }; type AccountId = u64; @@ -66,7 +66,8 @@ sp_runtime::impl_opaque_keys! { pub struct TestSessionHandler; impl pallet_session::SessionHandler for TestSessionHandler { - const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[]; + // corresponds to the opaque key id above + const KEY_TYPE_IDS: &'static [KeyTypeId] = &[KeyTypeId([100u8, 117u8, 109u8, 121u8])]; fn on_genesis_session(_validators: &[(AccountId, Ks)]) {} From f40cfd4f6db05793cfc59ab51b127dace97dd10e Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 2 Dec 2024 21:43:56 +0200 Subject: [PATCH 084/340] use weth for execution fees on AH --- .../pallets/inbound-queue-v2/src/api.rs | 6 +- .../pallets/inbound-queue-v2/src/lib.rs | 12 --- .../pallets/inbound-queue-v2/src/mock.rs | 10 +- .../pallets/inbound-queue-v2/src/test.rs | 31 ++---- .../primitives/router/src/inbound/v2.rs | 27 +++--- .../src/tests/snowbridge_v2.rs | 96 +------------------ .../src/bridge_to_ethereum_config.rs | 9 +- 7 files changed, 34 insertions(+), 157 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index 241d1372f7ea..f54f4a3a0de0 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -5,8 +5,8 @@ use crate::{weights::WeightInfo, Config, Error, Junction::AccountId32, Location}; use frame_support::weights::WeightToFee; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; -use sp_core::{Get, H256}; -use sp_runtime::{DispatchError, Saturating}; +use sp_core::H256; +use sp_runtime::{DispatchError}; use xcm::latest::Xcm; pub fn dry_run(message: Message) -> Result<(Xcm<()>, T::Balance), DispatchError> @@ -21,9 +21,7 @@ where // Calculate fee. Consists of the cost of the "submit" extrinsic as well as the XCM execution // prologue fee (static XCM part of the message that is execution on AH). let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit()); - let xcm_prologue_fee = T::XcmPrologueFee::get(); let fee: u128 = weight_fee - .saturating_add(xcm_prologue_fee) .try_into() .map_err(|_| Error::::InvalidFee)?; diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 6da0ddcdb673..00273ff107e0 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -52,7 +52,6 @@ use frame_support::{ use frame_system::{ensure_signed, pallet_prelude::*}; use scale_info::TypeInfo; use snowbridge_core::{ - fees::burn_fees, inbound::{Message, VerificationError, Verifier}, sparse_bitmap::SparseBitmap, BasicOperatingMode, @@ -65,7 +64,6 @@ use sp_std::vec; use types::Nonce; pub use weights::WeightInfo; use xcm::prelude::{send_xcm, Junction::*, Location, SendError as XcmpSendError, SendXcm, *}; -use xcm_executor::traits::TransactAsset; #[cfg(feature = "runtime-benchmarks")] use snowbridge_beacon_primitives::BeaconHeader; @@ -108,14 +106,10 @@ pub mod pallet { type AssetHubParaId: Get; /// Convert a command from Ethereum to an XCM message. type MessageConverter: ConvertMessage; - /// The AH XCM execution fee for the static part of the XCM message. - type XcmPrologueFee: Get>; /// Used to burn fees from the origin account (the relayer), which will be teleported to AH. type Token: Mutate + Inspect; /// Used for the dry run API implementation. type Balance: Balance + From; - /// Used to burn fees. - type AssetTransactor: TransactAsset; #[cfg(feature = "runtime-benchmarks")] type Helper: BenchmarkHelper; } @@ -234,12 +228,6 @@ pub mod pallet { let xcm = Self::do_convert(message, origin_account_location.clone())?; - // Burn the required fees for the static XCM message part - burn_fees::>( - origin_account_location, - T::XcmPrologueFee::get(), - )?; - // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: // T::RewardLeger::deposit(who, envelope.fee.into())?; // a. The submit extrinsic cost on BH diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index b069c352ae8a..69eaa473fff7 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -5,7 +5,7 @@ use super::*; use crate::{self as inbound_queue_v2}; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU128, ConstU32}, + traits::ConstU32, weights::IdentityFee, }; use hex_literal::hex; @@ -104,6 +104,7 @@ impl Verifier for MockVerifier { } const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; +const WETH_ADDRESS: [u8; 20] = hex!["fff9976782d46cc05630d1f6ebab18b2324d6b14"]; #[cfg(feature = "runtime-benchmarks")] impl BenchmarkHelper for Test { @@ -149,12 +150,11 @@ impl MaybeEquivalence for MockTokenIdConvert { parameter_types! { pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); + pub const WethAddress: H160 = H160(WETH_ADDRESS); pub const InboundQueuePalletInstance: u8 = 80; pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); } -const XCM_PROLOGUE_FEE: u128 = 1_000_000_000_000; - impl inbound_queue_v2::Config for Test { type RuntimeEvent = RuntimeEvent; type Verifier = MockVerifier; @@ -167,12 +167,10 @@ impl inbound_queue_v2::Config for Test { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - ConstU128, + WethAddress, >; type Token = Balances; type Balance = u128; - type XcmPrologueFee = ConstU128; - type AssetTransactor = SuccessfulTransactor; #[cfg(feature = "runtime-benchmarks")] type Helper = Test; } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 0b9e49ca43b3..cdaf95e4b267 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -46,27 +46,6 @@ fn test_submit_happy_path() { }); } -#[test] -fn test_submit_xcm_invalid_channel() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Submit message - let message = Message { - event_log: mock_event_log_invalid_channel(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_noop!( - InboundQueue::submit(origin.clone(), message.clone()), - Error::::InvalidChannel, - ); - }); -} - #[test] fn test_submit_with_invalid_gateway() { new_tester().execute_with(|| { @@ -151,7 +130,7 @@ fn test_set_operating_mode_root_only() { fn test_send_native_erc20_token_payload() { new_tester().execute_with(|| { // To generate test data: forge test --match-test testSendEther -vvvv - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf04005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000b2d3595bf00600000000000000000000").to_vec(); + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf0030ef7dba020000000000000000000004005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000b2d3595bf00600000000000000000000").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); assert_ok!(message.clone()); @@ -179,12 +158,13 @@ fn test_send_native_erc20_token_payload() { #[test] fn test_send_foreign_erc20_token_payload() { new_tester().execute_with(|| { - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf0030ef7dba0200000000000000000000040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); assert_ok!(message.clone()); - let inbound_message = message.unwrap(); + let inbound_message = message.unwrap(); + let expected_fee = 3_000_000_000_000u128; let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); let expected_token_id: H256 = hex!("97874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f40").into(); let expected_value = 500000000000000000u128; @@ -192,6 +172,7 @@ fn test_send_foreign_erc20_token_payload() { let expected_claimer: Option> = None; assert_eq!(expected_origin, inbound_message.origin); + assert_eq!(expected_fee, inbound_message.fee); assert_eq!(1, inbound_message.assets.len()); if let Asset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { assert_eq!(expected_token_id, *token_id); @@ -207,7 +188,7 @@ fn test_send_foreign_erc20_token_payload() { #[test] fn test_register_token_inbound_message_with_xcm_and_claimer() { new_tester().execute_with(|| { - let payload = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a904005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000300508020401000002286bee0a015029e3b139f4393adda86303fcdaa35f60bb7092bf").to_vec(); + let payload = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a90030ef7dba020000000000000000000004005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000300508020401000002286bee0a015029e3b139f4393adda86303fcdaa35f60bb7092bf").to_vec(); let message = MessageV2::decode(&mut payload.as_ref()); assert_ok!(message.clone()); diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 59ab2ea9ee0a..c9c8edb28921 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -11,7 +11,7 @@ use sp_core::{Get, RuntimeDebug, H160, H256}; use sp_runtime::traits::MaybeEquivalence; use sp_std::prelude::*; use xcm::{ - prelude::{Junction::AccountKey20, *}, + prelude::{Asset as XcmAsset, Junction::AccountKey20, *}, MAX_XCM_DECODE_DEPTH, }; @@ -23,6 +23,8 @@ const LOG_TARGET: &str = "snowbridge-router-primitives"; pub struct Message { /// The origin address pub origin: H160, + /// Fee in weth to cover the xcm execution on AH. + pub fee: u128, /// The assets pub assets: Vec, /// The command originating from the Gateway contract @@ -67,24 +69,24 @@ pub trait ConvertMessage { fn convert(message: Message, origin_account: Location) -> Result, ConvertMessageError>; } -pub struct MessageToXcm +pub struct MessageToXcm where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, - XcmPrologueFee: Get, + WethAddress: Get, { _phantom: - PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, XcmPrologueFee)>, + PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, WethAddress)>, } -impl ConvertMessage - for MessageToXcm +impl ConvertMessage + for MessageToXcm where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, - XcmPrologueFee: Get, + WethAddress: Get, { fn convert( message: Message, @@ -112,13 +114,14 @@ where let network = EthereumNetwork::get(); - let fee_asset = Location::new(1, Here); - let fee: xcm::prelude::Asset = (fee_asset.clone(), XcmPrologueFee::get()).into(); + // use weth as asset + let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get()), AccountKey20 { network: None, key: WethAddress::get().into() } ]); + let fee: XcmAsset = (fee_asset.clone(), message.fee).into(); let mut instructions = vec![ - ReceiveTeleportedAsset(fee.clone().into()), - PayFees { asset: fee }, DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), UniversalOrigin(GlobalConsensus(network)), + ReserveAssetDeposited(fee.clone().into()), + PayFees { asset: fee }, ]; for asset in &message.assets { @@ -150,6 +153,7 @@ where // Set the alias origin to the original sender on Ethereum. Important to be before the // arbitrary XCM that is appended to the message on the next line. + // TODO allow address from Ethereum to create foreign assets on AH // instructions.push(AliasOrigin(origin_location.into())); // Add the XCM sent in the message to the end of the xcm instruction @@ -158,7 +162,6 @@ where let appendix = vec![ RefundSurplus, // Refund excess fees to the relayer - // TODO maybe refund all fees to the relayer instead of just DOT? DepositAsset { assets: Wild(AllOf { id: AssetId(fee_asset.into()), fun: WildFungible }), beneficiary: origin_account_location, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 0f2e0f118ad0..18b48b3dbc7e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -32,7 +32,6 @@ use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; const INITIAL_FUND: u128 = 5_000_000_000_000; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; const WETH: [u8; 20] = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14"); -const WETH_FEE: u128 = 1_000_000_000_000; pub fn weth_location() -> Location { Location::new( @@ -147,6 +146,7 @@ pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { #[test] fn register_token_v2() { + // Whole register token fee is 374_851_000_000 let relayer = BridgeHubWestendSender::get(); let receiver = AssetHubWestendReceiver::get(); BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); @@ -171,8 +171,9 @@ fn register_token_v2() { let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let weth_fee: xcm::prelude::Asset = (weth_location(), WETH_FEE).into(); - let weth_transact_fee: xcm::prelude::Asset = (weth_location(), WETH_FEE / 2).into(); + let weth_fee_value: u128 = 1_000_000_000_000; + let weth_fee: xcm::prelude::Asset = (weth_location(), weth_fee_value).into(); + let weth_transact_fee: xcm::prelude::Asset = (weth_location(), weth_fee_value).into(); let asset_id = Location::new( 2, @@ -191,7 +192,6 @@ fn register_token_v2() { // will be deducted from) DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, // Pay for the transact execution - //PayFees { asset: weth_transact_fee.into() }, Transact { origin_kind: OriginKind::Xcm, call: ( @@ -210,6 +210,7 @@ fn register_token_v2() { let message = Message { origin, + fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), @@ -233,93 +234,6 @@ fn register_token_v2() { }); } -#[test] -fn xcm_prologue_fee() { - BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); - - let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( - 1, - [Parachain(AssetHubWestend::para_id().into())], - )); - - let relayer = BridgeHubWestendSender::get(); - let receiver = AssetHubWestendReceiver::get(); - BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); - - let mut token_ids = Vec::new(); - for _ in 0..8 { - token_ids.push(H160::random()); - } - - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - - AssetHubWestend::execute_with(|| { - type RuntimeOrigin = ::RuntimeOrigin; - - for token_id in token_ids.iter() { - let token_id = *token_id; - - let asset_location = Location::new( - 2, - [ - GlobalConsensus(ethereum_network_v5), - AccountKey20 { network: None, key: token_id.into() }, - ], - ); - - assert_ok!(::ForeignAssets::force_create( - RuntimeOrigin::root(), - asset_location.clone(), - asset_hub_sovereign.clone().into(), - false, - 1, - )); - - assert!(::ForeignAssets::asset_exists( - asset_location.clone().try_into().unwrap(), - )); - } - }); - - let native_tokens: Vec = token_ids - .iter() - .map(|token_id| NativeTokenERC20 { token_id: *token_id, value: 3_000_000_000 }) - .collect(); - - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - let claimer = AccountId32 { network: None, id: receiver.clone().into() }; - let claimer_bytes = claimer.encode(); - let origin = H160::random(); - let relayer_location = - Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - - let message_xcm_instructions = - vec![DepositAsset { assets: Wild(AllCounted(8)), beneficiary: receiver.into() }]; - let message_xcm: Xcm<()> = message_xcm_instructions.into(); - let versioned_message_xcm = VersionedXcm::V5(message_xcm); - - let message = Message { - origin, - assets: native_tokens, - xcm: versioned_message_xcm.encode(), - claimer: Some(claimer_bytes), - }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); - let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); - - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] - ); - }); - - let execution_fee = WeightCalculator::weight_to_fee(&Weight::from_parts(1410450000, 33826)); - let buffered_fee = execution_fee * 2; - println!("buffered execution fee for prologue for 8 assets: {}", buffered_fee); -} - #[test] fn register_token_xcm() { BridgeHubWestend::execute_with(|| { diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 4d236b801384..e998909fd2da 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -71,12 +71,9 @@ parameter_types! { }; pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(RelayNetwork::get()),Parachain(westend_runtime_constants::system_parachain::ASSET_HUB_ID)]); pub EthereumUniversalLocation: InteriorLocation = [GlobalConsensus(EthereumNetwork::get())].into(); + pub WethAddress: H160 = H160(hex_literal::hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14")); } -/// The XCM execution fee on AH for the static part of the XCM message (not the user provided -/// xcm). Teleported from BH. Calculated with integration test snowbridge_v2::xcm_prologue_fee. -const XCM_PROLOGUE_FEE: u128 = 67_652_000_000; - impl snowbridge_pallet_inbound_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Verifier = snowbridge_pallet_ethereum_client::Pallet; @@ -122,13 +119,11 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type AssetHubParaId = ConstU32<1000>; type Token = Balances; type Balance = Balance; - type XcmPrologueFee = ConstU128; - type AssetTransactor = ::AssetTransactor; type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm< EthereumNetwork, ConstU8, EthereumSystem, - ConstU128, + WethAddress, >; } From 9b8117b0b76dffdba3641286d7b8dee7e205ab86 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 2 Dec 2024 21:54:33 +0200 Subject: [PATCH 085/340] cleanup test --- .../src/tests/snowbridge_v2.rs | 23 ++++++------------- .../src/bridge_to_ethereum_config.rs | 2 +- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 18b48b3dbc7e..aef3afb2350c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -18,7 +18,7 @@ use bridge_hub_westend_runtime::{ EthereumInboundQueueV2, }; use codec::Encode; -use frame_support::{traits::fungibles::Mutate, weights::WeightToFee}; +use frame_support::{traits::fungibles::Mutate}; use hex_literal::hex; use snowbridge_router_primitives::inbound::{ v2::{Asset::NativeTokenERC20, Message}, @@ -26,7 +26,6 @@ use snowbridge_router_primitives::inbound::{ }; use sp_core::H160; use sp_runtime::MultiAddress; -use testnet_parachains_constants::westend::fee::WeightToFee as WeightCalculator; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; @@ -43,10 +42,6 @@ pub fn weth_location() -> Location { ) } -pub fn dot_location() -> Location { - Location::new(1, Here) -} - pub fn register_weth() { let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); @@ -95,8 +90,6 @@ pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { let signed_owner = ::RuntimeOrigin::signed(owner.clone()); let signed_bh_sovereign = ::RuntimeOrigin::signed(bh_sovereign.clone()); - type RuntimeOrigin = ::RuntimeOrigin; - assert_ok!(::ForeignAssets::mint( signed_bh_sovereign.clone(), asset.clone().into(), @@ -165,15 +158,13 @@ fn register_token_v2() { let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); - let weth_amount = 9_000_000_000_000u128; - - let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_amount }]; let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let weth_fee_value: u128 = 1_000_000_000_000; - let weth_fee: xcm::prelude::Asset = (weth_location(), weth_fee_value).into(); - let weth_transact_fee: xcm::prelude::Asset = (weth_location(), weth_fee_value).into(); + // Used to pay the asset creation deposit. + let weth_asset_value = 9_000_000_000_000u128; + let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_asset_value }]; + let asset_deposit_weth: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); let asset_id = Location::new( 2, @@ -187,11 +178,11 @@ fn register_token_v2() { type RuntimeEvent = ::RuntimeEvent; let register_token_instructions = vec![ // Exchange weth for dot to pay the asset creation deposit - ExchangeAsset { give: weth_fee.clone().into(), want: dot_fee.clone().into(), maximal: false }, + ExchangeAsset { give: asset_deposit_weth.clone().into(), want: dot_fee.clone().into(), maximal: false }, // Deposit the dot deposit into the bridge sovereign account (where the asset creation fee // will be deducted from) DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, - // Pay for the transact execution + // Call to create the asset. Transact { origin_kind: OriginKind::Xcm, call: ( diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index e998909fd2da..0bd1a736eb3d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -39,7 +39,7 @@ use benchmark_helpers::DoNothingRouter; use frame_support::{parameter_types, weights::ConstantMultiplier}; use pallet_xcm::EnsureXcm; use sp_runtime::{ - traits::{ConstU128, ConstU32, ConstU8, Keccak256}, + traits::{ConstU32, ConstU8, Keccak256}, FixedU128, }; use xcm::prelude::{GlobalConsensus, InteriorLocation, Location, Parachain}; From c2804dc4d642d0d4c1113a8946a0e7a6be722d6a Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 3 Dec 2024 12:35:27 +0200 Subject: [PATCH 086/340] fix aliasorigin --- .../pallets/inbound-queue-v2/src/api.rs | 6 +- .../pallets/inbound-queue-v2/src/mock.rs | 14 +--- .../primitives/router/src/inbound/v2.rs | 68 ++++++++++++++----- .../src/tests/snowbridge_v2.rs | 26 ++++--- .../src/bridge_to_ethereum_config.rs | 1 + 5 files changed, 72 insertions(+), 43 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index f54f4a3a0de0..beb96b1cb50d 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -6,7 +6,7 @@ use crate::{weights::WeightInfo, Config, Error, Junction::AccountId32, Location} use frame_support::weights::WeightToFee; use snowbridge_router_primitives::inbound::v2::{ConvertMessage, Message}; use sp_core::H256; -use sp_runtime::{DispatchError}; +use sp_runtime::DispatchError; use xcm::latest::Xcm; pub fn dry_run(message: Message) -> Result<(Xcm<()>, T::Balance), DispatchError> @@ -21,9 +21,7 @@ where // Calculate fee. Consists of the cost of the "submit" extrinsic as well as the XCM execution // prologue fee (static XCM part of the message that is execution on AH). let weight_fee = T::WeightToFee::weight_to_fee(&T::WeightInfo::submit()); - let fee: u128 = weight_fee - .try_into() - .map_err(|_| Error::::InvalidFee)?; + let fee: u128 = weight_fee.try_into().map_err(|_| Error::::InvalidFee)?; Ok((xcm, fee.into())) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 69eaa473fff7..28db523bd7a7 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -3,11 +3,7 @@ use super::*; use crate::{self as inbound_queue_v2}; -use frame_support::{ - derive_impl, parameter_types, - traits::ConstU32, - weights::IdentityFee, -}; +use frame_support::{derive_impl, parameter_types, traits::ConstU32, weights::IdentityFee}; use hex_literal::hex; use snowbridge_beacon_primitives::{ types::deneb, BeaconHeader, ExecutionProof, Fork, ForkVersions, VersionedExecutionPayloadHeader, @@ -163,12 +159,8 @@ impl inbound_queue_v2::Config for Test { type WeightToFee = IdentityFee; type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; - type MessageConverter = MessageToXcm< - EthereumNetwork, - InboundQueuePalletInstance, - MockTokenIdConvert, - WethAddress, - >; + type MessageConverter = + MessageToXcm; type Token = Balances; type Balance = u128; #[cfg(feature = "runtime-benchmarks")] diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index c9c8edb28921..b50184413ac5 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -63,30 +63,56 @@ pub enum ConvertMessageError { InvalidClaimer, /// Invalid foreign ERC20 token ID InvalidAsset, + /// The origin could not be added to the interior location of the origin location. + InvalidOrigin, } pub trait ConvertMessage { fn convert(message: Message, origin_account: Location) -> Result, ConvertMessageError>; } -pub struct MessageToXcm -where +pub struct MessageToXcm< + EthereumNetwork, + InboundQueuePalletInstance, + ConvertAssetId, + WethAddress, + GatewayProxyAddress, +> where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, WethAddress: Get, + GatewayProxyAddress: Get, { - _phantom: - PhantomData<(EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, WethAddress)>, + _phantom: PhantomData<( + EthereumNetwork, + InboundQueuePalletInstance, + ConvertAssetId, + WethAddress, + GatewayProxyAddress, + )>, } -impl ConvertMessage - for MessageToXcm +impl< + EthereumNetwork, + InboundQueuePalletInstance, + ConvertAssetId, + WethAddress, + GatewayProxyAddress, + > ConvertMessage + for MessageToXcm< + EthereumNetwork, + InboundQueuePalletInstance, + ConvertAssetId, + WethAddress, + GatewayProxyAddress, + > where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, WethAddress: Get, + GatewayProxyAddress: Get, { fn convert( message: Message, @@ -107,15 +133,14 @@ where let network = EthereumNetwork::get(); - let origin_location = Location::new( + // use weth as asset + let fee_asset = Location::new( 2, - [GlobalConsensus(network), AccountKey20 { key: message.origin.into(), network: None }], + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: WethAddress::get().into() }, + ], ); - - let network = EthereumNetwork::get(); - - // use weth as asset - let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get()), AccountKey20 { network: None, key: WethAddress::get().into() } ]); let fee: XcmAsset = (fee_asset.clone(), message.fee).into(); let mut instructions = vec![ DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), @@ -151,10 +176,19 @@ where instructions.push(SetAssetClaimer { location: claimer_location }); } - // Set the alias origin to the original sender on Ethereum. Important to be before the - // arbitrary XCM that is appended to the message on the next line. - // TODO allow address from Ethereum to create foreign assets on AH - // instructions.push(AliasOrigin(origin_location.into())); + let mut origin_location = Location::new(2, [GlobalConsensus(network)]); + if message.origin == GatewayProxyAddress::get() { + // If the message origin is the gateway proxy contract, set the origin to + // Ethereum, for consistency with v1. + instructions.push(AliasOrigin(origin_location)); + } else { + // Set the alias origin to the original sender on Ethereum. Important to be before the + // arbitrary XCM that is appended to the message on the next line. + origin_location + .push_interior(AccountKey20 { key: message.origin.into(), network: None }) + .map_err(|_| ConvertMessageError::InvalidOrigin)?; + instructions.push(AliasOrigin(origin_location.into())); + } // Add the XCM sent in the message to the end of the xcm instruction instructions.extend(message_xcm.0); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index aef3afb2350c..bd714fac1d0a 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -14,11 +14,11 @@ // limitations under the License. use crate::imports::*; use bridge_hub_westend_runtime::{ - bridge_to_ethereum_config::{CreateAssetCall, CreateAssetDeposit}, + bridge_to_ethereum_config::{CreateAssetCall, CreateAssetDeposit, EthereumGatewayAddress}, EthereumInboundQueueV2, }; use codec::Encode; -use frame_support::{traits::fungibles::Mutate}; +use frame_support::traits::fungibles::Mutate; use hex_literal::hex; use snowbridge_router_primitives::inbound::{ v2::{Asset::NativeTokenERC20, Message}, @@ -80,15 +80,14 @@ pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { let owner = AssetHubWestendSender::get(); let bh_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); - AssetHubWestend::fund_accounts(vec![ - (owner.clone(), 3_000_000_000_000), - ]); + AssetHubWestend::fund_accounts(vec![(owner.clone(), 3_000_000_000_000)]); AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; let signed_owner = ::RuntimeOrigin::signed(owner.clone()); - let signed_bh_sovereign = ::RuntimeOrigin::signed(bh_sovereign.clone()); + let signed_bh_sovereign = + ::RuntimeOrigin::signed(bh_sovereign.clone()); assert_ok!(::ForeignAssets::mint( signed_bh_sovereign.clone(), @@ -151,13 +150,13 @@ fn register_token_v2() { let chain_id = 11155111u64; let claimer = AccountId32 { network: None, id: receiver.clone().into() }; let claimer_bytes = claimer.encode(); - let origin = H160::random(); + let relayer_location = Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); + let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // a is the token let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); @@ -178,9 +177,13 @@ fn register_token_v2() { type RuntimeEvent = ::RuntimeEvent; let register_token_instructions = vec![ // Exchange weth for dot to pay the asset creation deposit - ExchangeAsset { give: asset_deposit_weth.clone().into(), want: dot_fee.clone().into(), maximal: false }, - // Deposit the dot deposit into the bridge sovereign account (where the asset creation fee - // will be deducted from) + ExchangeAsset { + give: asset_deposit_weth.clone().into(), + want: dot_fee.clone().into(), + maximal: false, + }, + // Deposit the dot deposit into the bridge sovereign account (where the asset creation + // fee will be deducted from) DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, // Call to create the asset. Transact { @@ -198,6 +201,7 @@ fn register_token_v2() { ]; let xcm: Xcm<()> = register_token_instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); let message = Message { origin, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 0bd1a736eb3d..469a0ebdb2fb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -124,6 +124,7 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { ConstU8, EthereumSystem, WethAddress, + EthereumGatewayAddress, >; } From 8f1606e9f9bd6269a4c2631a161dcc73e969a302 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Tue, 3 Dec 2024 12:55:50 +0200 Subject: [PATCH 087/340] Rococo People <> Bulletin bridge fixes (#6708) --- .../chains/chain-polkadot-bulletin/src/lib.rs | 2 +- bridges/relays/utils/src/initialize.rs | 7 ++-- .../src/bridge_to_bulletin_config.rs | 41 ++++--------------- .../src/genesis_config_presets.rs | 10 +++++ .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 1 - .../bridge-hub-rococo/tests/tests.rs | 12 +++--- 6 files changed, 28 insertions(+), 45 deletions(-) diff --git a/bridges/chains/chain-polkadot-bulletin/src/lib.rs b/bridges/chains/chain-polkadot-bulletin/src/lib.rs index c5c18beb2cad..070bc7b0ba3d 100644 --- a/bridges/chains/chain-polkadot-bulletin/src/lib.rs +++ b/bridges/chains/chain-polkadot-bulletin/src/lib.rs @@ -225,4 +225,4 @@ impl ChainWithMessages for PolkadotBulletin { } decl_bridge_finality_runtime_apis!(polkadot_bulletin, grandpa); -decl_bridge_messages_runtime_apis!(polkadot_bulletin, bp_messages::HashedLaneId); +decl_bridge_messages_runtime_apis!(polkadot_bulletin, bp_messages::LegacyLaneId); diff --git a/bridges/relays/utils/src/initialize.rs b/bridges/relays/utils/src/initialize.rs index 564ed1f0e5cc..deb9b9d059d5 100644 --- a/bridges/relays/utils/src/initialize.rs +++ b/bridges/relays/utils/src/initialize.rs @@ -52,9 +52,10 @@ pub fn initialize_logger(with_timestamp: bool) { format, ); - let env_filter = EnvFilter::from_default_env() - .add_directive(Level::WARN.into()) - .add_directive("bridge=info".parse().expect("static filter string is valid")); + let env_filter = EnvFilter::builder() + .with_default_directive(Level::WARN.into()) + .with_default_directive("bridge=info".parse().expect("static filter string is valid")) + .from_env_lossy(); let builder = SubscriberBuilder::default().with_env_filter(env_filter); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs index b284fa9e7af7..1e733503f43b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -22,14 +22,13 @@ use crate::{ bridge_common_config::RelayersForPermissionlessLanesInstance, weights, xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeRococoBulletinGrandpa, - BridgeRococoBulletinMessages, PolkadotXcm, Runtime, RuntimeEvent, RuntimeHoldReason, - XcmOverRococoBulletin, XcmRouter, + BridgeRococoBulletinMessages, Runtime, RuntimeEvent, RuntimeHoldReason, XcmOverRococoBulletin, + XcmRouter, }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, - target_chain::FromBridgedChainMessagesProof, HashedLaneId, + target_chain::FromBridgedChainMessagesProof, LegacyLaneId, }; -use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; use frame_support::{ parameter_types, @@ -46,6 +45,7 @@ use testnet_parachains_constants::rococo::currency::UNITS as ROC; use xcm::{ latest::prelude::*, prelude::{InteriorLocation, NetworkId}, + AlwaysV5, }; use xcm_builder::{BridgeBlobDispatcher, ParentIsPreset, SiblingParachainConvertsVia}; @@ -120,7 +120,7 @@ impl pallet_bridge_messages::Config for Runt type OutboundPayload = XcmAsPlainPayload; type InboundPayload = XcmAsPlainPayload; - type LaneId = HashedLaneId; + type LaneId = LegacyLaneId; type DeliveryPayments = (); type DeliveryConfirmationPayments = (); @@ -139,8 +139,7 @@ impl pallet_xcm_bridge_hub::Config for Runtime type BridgeMessagesPalletInstance = WithRococoBulletinMessagesInstance; type MessageExportPrice = (); - type DestinationVersion = - XcmVersionOfDestAndRemoteBridge; + type DestinationVersion = AlwaysV5; type ForceOrigin = EnsureRoot; // We don't want to allow creating bridges for this instance. @@ -253,7 +252,7 @@ where let universal_source = [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), Parachain(sibling_para_id)].into(); let universal_destination = - [GlobalConsensus(RococoBulletinGlobalConsensusNetwork::get()), Parachain(2075)].into(); + [GlobalConsensus(RococoBulletinGlobalConsensusNetwork::get())].into(); let bridge_id = BridgeId::new(&universal_source, &universal_destination); // insert only bridge metadata, because the benchmarks create lanes @@ -279,29 +278,3 @@ where universal_source } - -/// Contains the migration for the PeopleRococo<>RococoBulletin bridge. -pub mod migration { - use super::*; - use frame_support::traits::ConstBool; - - parameter_types! { - pub BulletinRococoLocation: InteriorLocation = [GlobalConsensus(RococoBulletinGlobalConsensusNetwork::get())].into(); - pub RococoPeopleToRococoBulletinMessagesLane: HashedLaneId = pallet_xcm_bridge_hub::Pallet::< Runtime, XcmOverPolkadotBulletinInstance >::bridge_locations( - PeopleRococoLocation::get(), - BulletinRococoLocation::get() - ) - .unwrap() - .calculate_lane_id(xcm::latest::VERSION).expect("Valid locations"); - } - - /// Ensure that the existing lanes for the People<>Bulletin bridge are correctly configured. - pub type StaticToDynamicLanes = pallet_xcm_bridge_hub::migration::OpenBridgeForLane< - Runtime, - XcmOverPolkadotBulletinInstance, - RococoPeopleToRococoBulletinMessagesLane, - ConstBool, - PeopleRococoLocation, - BulletinRococoLocation, - >; -} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs index 98e2450ee832..55fd499c2f54 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/genesis_config_presets.rs @@ -61,10 +61,20 @@ fn bridge_hub_rococo_genesis( .collect(), }, polkadot_xcm: PolkadotXcmConfig { safe_xcm_version: Some(SAFE_XCM_VERSION) }, + bridge_polkadot_bulletin_grandpa: BridgePolkadotBulletinGrandpaConfig { + owner: bridges_pallet_owner.clone(), + }, bridge_westend_grandpa: BridgeWestendGrandpaConfig { owner: bridges_pallet_owner.clone() }, bridge_westend_messages: BridgeWestendMessagesConfig { owner: bridges_pallet_owner.clone(), }, + xcm_over_polkadot_bulletin: XcmOverPolkadotBulletinConfig { + opened_bridges: vec![( + Location::new(1, [Parachain(1004)]), + Junctions::from([GlobalConsensus(NetworkId::PolkadotBulletin).into()]), + Some(bp_messages::LegacyLaneId([0, 0, 0, 0])), + )], + }, xcm_over_bridge_hub_westend: XcmOverBridgeHubWestendConfig { opened_bridges }, ethereum_system: EthereumSystemConfig { para_id: id, asset_hub_para_id }, }) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 598afeddb984..d87ff9b43fef 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -169,7 +169,6 @@ pub type Migrations = ( bridge_to_westend_config::WithBridgeHubWestendMessagesInstance, >, bridge_to_westend_config::migration::StaticToDynamicLanes, - bridge_to_bulletin_config::migration::StaticToDynamicLanes, frame_support::migrations::RemoveStorage< BridgeWestendMessagesPalletName, OutboundLanesCongestedSignalsKey, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index 29f9615bff6a..44e69c31a560 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -501,10 +501,10 @@ mod bridge_hub_westend_tests { mod bridge_hub_bulletin_tests { use super::*; - use bp_messages::{HashedLaneId, LaneIdType}; + use bp_messages::LegacyLaneId; use bridge_common_config::BridgeGrandpaRococoBulletinInstance; use bridge_hub_rococo_runtime::{ - bridge_common_config::RelayersForPermissionlessLanesInstance, + bridge_common_config::RelayersForLegacyLaneIdsMessagesInstance, xcm_config::LocationToAccountId, }; use bridge_hub_test_utils::test_cases::from_grandpa_chain; @@ -528,7 +528,7 @@ mod bridge_hub_bulletin_tests { AllPalletsWithoutSystem, BridgeGrandpaRococoBulletinInstance, WithRococoBulletinMessagesInstance, - RelayersForPermissionlessLanesInstance, + RelayersForLegacyLaneIdsMessagesInstance, >; #[test] @@ -599,7 +599,7 @@ mod bridge_hub_bulletin_tests { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverPolkadotBulletinInstance - >(locations, HashedLaneId::try_new(1, 2).unwrap()) + >(locations, LegacyLaneId([0, 0, 0, 0])) } ).1 }, @@ -663,7 +663,7 @@ mod bridge_hub_bulletin_tests { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverPolkadotBulletinInstance, - >(locations, HashedLaneId::try_new(1, 2).unwrap()) + >(locations, LegacyLaneId([0, 0, 0, 0])) }, ) .1 @@ -697,7 +697,7 @@ mod bridge_hub_bulletin_tests { bridge_hub_test_utils::open_bridge_with_storage::< Runtime, XcmOverPolkadotBulletinInstance, - >(locations, HashedLaneId::try_new(1, 2).unwrap()) + >(locations, LegacyLaneId([0, 0, 0, 0])) }, ) .1 From 592bb3205be7569cf2d705b31a272340038bbed7 Mon Sep 17 00:00:00 2001 From: Egor_P Date: Tue, 3 Dec 2024 13:06:43 +0100 Subject: [PATCH 088/340] [Release/CICD] Re-worked Create Release Draft flow (#6734) This PR contains following changes in release pipelines: - re-built Create Release Draft workflow - binaries builds are moved completely to the `Release - Build node release candidate` flow - added upload of all the release artefacts to the S3 - adjusted `Release - Publish Docker Image` workflow, so that it will match now the new release flow. --- .github/scripts/common/lib.sh | 45 +++- .github/scripts/release/release_lib.sh | 22 ++ ...le.yml => release-10_branchoff-stable.yml} | 0 ...ation.yml => release-11_rc-automation.yml} | 0 ...e-build-rc.yml => release-20_build-rc.yml} | 96 +++++++- .../release-30_publish_release_draft.yml | 206 +++++++++++------- .../workflows/release-50_publish-docker.yml | 97 +++------ .../workflows/release-reusable-rc-buid.yml | 53 ++++- .github/workflows/release-srtool.yml | 18 +- 9 files changed, 373 insertions(+), 164 deletions(-) rename .github/workflows/{release-branchoff-stable.yml => release-10_branchoff-stable.yml} (100%) rename .github/workflows/{release-10_rc-automation.yml => release-11_rc-automation.yml} (100%) rename .github/workflows/{release-build-rc.yml => release-20_build-rc.yml} (62%) diff --git a/.github/scripts/common/lib.sh b/.github/scripts/common/lib.sh index 6b8f70a26d7e..41dc0ba06dd2 100755 --- a/.github/scripts/common/lib.sh +++ b/.github/scripts/common/lib.sh @@ -270,20 +270,19 @@ fetch_debian_package_from_s3() { } # Fetch the release artifacts like binary and signatures from S3. Assumes the ENV are set: -# - RELEASE_ID -# - GITHUB_TOKEN -# - REPO in the form paritytech/polkadot +# inputs: binary (polkadot), target(aarch64-apple-darwin) fetch_release_artifacts_from_s3() { BINARY=$1 - OUTPUT_DIR=${OUTPUT_DIR:-"./release-artifacts/${BINARY}"} + TARGET=$2 + OUTPUT_DIR=${OUTPUT_DIR:-"./release-artifacts/${TARGET}/${BINARY}"} echo "OUTPUT_DIR : $OUTPUT_DIR" URL_BASE=$(get_s3_url_base $BINARY) echo "URL_BASE=$URL_BASE" - URL_BINARY=$URL_BASE/$VERSION/$BINARY - URL_SHA=$URL_BASE/$VERSION/$BINARY.sha256 - URL_ASC=$URL_BASE/$VERSION/$BINARY.asc + URL_BINARY=$URL_BASE/$VERSION/$TARGET/$BINARY + URL_SHA=$URL_BASE/$VERSION/$TARGET/$BINARY.sha256 + URL_ASC=$URL_BASE/$VERSION/$TARGET/$BINARY.asc # Fetch artifacts mkdir -p "$OUTPUT_DIR" @@ -306,15 +305,26 @@ fetch_release_artifacts_from_s3() { function get_s3_url_base() { name=$1 case $name in - polkadot | polkadot-execute-worker | polkadot-prepare-worker | staking-miner) + polkadot | polkadot-execute-worker | polkadot-prepare-worker ) printf "https://releases.parity.io/polkadot" ;; - polkadot-parachain) - printf "https://releases.parity.io/cumulus" + polkadot-parachain) + printf "https://releases.parity.io/polkadot-parachain" + ;; + + polkadot-omni-node) + printf "https://releases.parity.io/polkadot-omni-node" + ;; + + chain-spec-builder) + printf "https://releases.parity.io/chain-spec-builder" ;; - *) + frame-omni-bencher) + printf "https://releases.parity.io/frame-omni-bencher" + ;; + *) printf "UNSUPPORTED BINARY $name" exit 1 ;; @@ -497,3 +507,16 @@ validate_stable_tag() { exit 1 fi } + +# Prepare docker stable tag form the polkadot stable tag +# input: tag (polkaodot-stableYYMM(-X) or polkadot-stableYYMM(-X)-rcX) +# output: stableYYMM(-X) or stableYYMM(-X)-rcX +prepare_docker_stable_tag() { + tag="$1" + if [[ "$tag" =~ stable[0-9]{4}(-[0-9]+)?(-rc[0-9]+)? ]]; then + echo "${BASH_REMATCH[0]}" + else + echo "Tag is invalid: $tag" + exit 1 + fi +} diff --git a/.github/scripts/release/release_lib.sh b/.github/scripts/release/release_lib.sh index 8b9254ec3f29..43227180cb7c 100644 --- a/.github/scripts/release/release_lib.sh +++ b/.github/scripts/release/release_lib.sh @@ -139,3 +139,25 @@ upload_s3_release() { aws s3 ls "s3://releases.parity.io/${product}/${version}/${target}" --recursive --human-readable --summarize echo "✅ The release should be at https://releases.parity.io/${product}/${version}/${target}" } + +# Upload runtimes artifacts to s3 release bucket +# +# input: version (stable release tage.g. polkadot-stable2412 or polkadot-stable2412-rc1) +# output: none +upload_s3_runtimes_release_artifacts() { + alias aws='podman run --rm -it docker.io/paritytech/awscli -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_BUCKET aws' + + version=$1 + + echo "Working on version: $version " + + echo "Current content, should be empty on new uploads:" + aws s3 ls "s3://releases.parity.io/polkadot/runtimes/${version}/" --recursive --human-readable --summarize || true + echo "Content to be uploaded:" + artifacts="artifacts/runtimes/" + ls "$artifacts" + aws s3 sync --acl public-read "$artifacts" "s3://releases.parity.io/polkadot/runtimes/${version}/" + echo "Uploaded files:" + aws s3 ls "s3://releases.parity.io/polkadot/runtimes/${version}/" --recursive --human-readable --summarize + echo "✅ The release should be at https://releases.parity.io/polkadot/runtimes/${version}" +} diff --git a/.github/workflows/release-branchoff-stable.yml b/.github/workflows/release-10_branchoff-stable.yml similarity index 100% rename from .github/workflows/release-branchoff-stable.yml rename to .github/workflows/release-10_branchoff-stable.yml diff --git a/.github/workflows/release-10_rc-automation.yml b/.github/workflows/release-11_rc-automation.yml similarity index 100% rename from .github/workflows/release-10_rc-automation.yml rename to .github/workflows/release-11_rc-automation.yml diff --git a/.github/workflows/release-build-rc.yml b/.github/workflows/release-20_build-rc.yml similarity index 62% rename from .github/workflows/release-build-rc.yml rename to .github/workflows/release-20_build-rc.yml index a43c2b282a8d..d4c7055c37c5 100644 --- a/.github/workflows/release-build-rc.yml +++ b/.github/workflows/release-20_build-rc.yml @@ -11,10 +11,12 @@ on: - polkadot - polkadot-parachain - polkadot-omni-node + - frame-omni-bencher + - chain-spec-builder - all release_tag: - description: Tag matching the actual release candidate with the format stableYYMM-rcX or stableYYMM + description: Tag matching the actual release candidate with the format polkadot-stableYYMM(-X)-rcX or polkadot-stableYYMM(-X) type: string jobs: @@ -106,6 +108,50 @@ jobs: attestations: write contents: read + build-frame-omni-bencher-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'frame-omni-bencher' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["frame-omni-bencher"]' + package: "frame-omni-bencher" + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: x86_64-unknown-linux-gnu + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + + build-chain-spec-builder-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'chain-spec-builder' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["chain-spec-builder"]' + package: staging-chain-spec-builder + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: x86_64-unknown-linux-gnu + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + build-polkadot-macos-binary: needs: [validate-inputs] if: ${{ inputs.binary == 'polkadot' || inputs.binary == 'all' }} @@ -134,7 +180,7 @@ jobs: uses: "./.github/workflows/release-reusable-rc-buid.yml" with: binary: '["polkadot-parachain"]' - package: "polkadot-parachain-bin" + package: polkadot-parachain-bin release_tag: ${{ needs.validate-inputs.outputs.release_tag }} target: aarch64-apple-darwin secrets: @@ -156,7 +202,51 @@ jobs: uses: "./.github/workflows/release-reusable-rc-buid.yml" with: binary: '["polkadot-omni-node"]' - package: "polkadot-omni-node" + package: polkadot-omni-node + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: aarch64-apple-darwin + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + + build-frame-omni-bencher-macos-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'frame-omni-bencher' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["frame-omni-bencher"]' + package: frame-omni-bencher + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: aarch64-apple-darwin + secrets: + PGP_KMS_KEY: ${{ secrets.PGP_KMS_KEY }} + PGP_KMS_HASH: ${{ secrets.PGP_KMS_HASH }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + permissions: + id-token: write + attestations: write + contents: read + + build-chain-spec-builder-macos-binary: + needs: [validate-inputs] + if: ${{ inputs.binary == 'chain-spec-builder' || inputs.binary == 'all' }} + uses: "./.github/workflows/release-reusable-rc-buid.yml" + with: + binary: '["chain-spec-builder"]' + package: staging-chain-spec-builder release_tag: ${{ needs.validate-inputs.outputs.release_tag }} target: aarch64-apple-darwin secrets: diff --git a/.github/workflows/release-30_publish_release_draft.yml b/.github/workflows/release-30_publish_release_draft.yml index 4364b4f80457..78ceea91f100 100644 --- a/.github/workflows/release-30_publish_release_draft.yml +++ b/.github/workflows/release-30_publish_release_draft.yml @@ -1,19 +1,46 @@ name: Release - Publish draft -on: - push: - tags: - # Catches v1.2.3 and v1.2.3-rc1 - - v[0-9]+.[0-9]+.[0-9]+* - # - polkadot-stable[0-9]+* Activate when the release process from release org is setteled +# This workflow runs in paritytech-release and creates full release draft with: +# - release notes +# - info about the runtimes +# - attached artifacts: +# - runtimes +# - binaries +# - signatures +on: workflow_dispatch: inputs: - version: - description: Current release/rc version + release_tag: + description: Tag matching the actual release candidate with the format polkadot-stableYYMM(-X)-rcX or polkadot-stableYYMM(-X) + required: true + type: string jobs: + check-synchronization: + uses: paritytech-release/sync-workflows/.github/workflows/check-syncronization.yml@main + + validate-inputs: + needs: [ check-synchronization ] + if: ${{ needs.check-synchronization.outputs.checks_passed }} == 'true' + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.validate_inputs.outputs.release_tag }} + + steps: + - name: Checkout sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Validate inputs + id: validate_inputs + run: | + . ./.github/scripts/common/lib.sh + + RELEASE_TAG=$(validate_stable_tag ${{ inputs.release_tag }}) + echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT + get-rust-versions: + needs: [ validate-inputs ] runs-on: ubuntu-latest outputs: rustc-stable: ${{ steps.get-rust-versions.outputs.stable }} @@ -24,47 +51,28 @@ jobs: echo "stable=$RUST_STABLE_VERSION" >> $GITHUB_OUTPUT build-runtimes: + needs: [ validate-inputs ] uses: "./.github/workflows/release-srtool.yml" with: excluded_runtimes: "asset-hub-rococo bridge-hub-rococo contracts-rococo coretime-rococo people-rococo rococo rococo-parachain substrate-test bp cumulus-test kitchensink minimal-template parachain-template penpal polkadot-test seedling shell frame-try sp solochain-template polkadot-sdk-docs-first" build_opts: "--features on-chain-release-build" - - build-binaries: - runs-on: ubuntu-latest - strategy: - matrix: - # Tuples of [package, binary-name] - binary: [ [frame-omni-bencher, frame-omni-bencher], [staging-chain-spec-builder, chain-spec-builder] ] - steps: - - name: Checkout sources - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 - - - name: Install protobuf-compiler - run: | - sudo apt update - sudo apt install -y protobuf-compiler - - - name: Build ${{ matrix.binary[1] }} binary - run: | - cargo build --locked --profile=production -p ${{ matrix.binary[0] }} --bin ${{ matrix.binary[1] }} - target/production/${{ matrix.binary[1] }} --version - - - name: Upload ${{ matrix.binary[1] }} binary - uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 - with: - name: ${{ matrix.binary[1] }} - path: target/production/${{ matrix.binary[1] }} - + profile: production + permissions: + id-token: write + attestations: write + contents: read publish-release-draft: runs-on: ubuntu-latest - needs: [ get-rust-versions, build-runtimes ] + environment: release + needs: [ validate-inputs, get-rust-versions, build-runtimes ] outputs: release_url: ${{ steps.create-release.outputs.html_url }} asset_upload_url: ${{ steps.create-release.outputs.upload_url }} + steps: - name: Checkout - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Download artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 @@ -87,20 +95,21 @@ jobs: GLUTTON_WESTEND_DIGEST: ${{ github.workspace}}/glutton-westend-runtime/glutton-westend-srtool-digest.json PEOPLE_WESTEND_DIGEST: ${{ github.workspace}}/people-westend-runtime/people-westend-srtool-digest.json WESTEND_DIGEST: ${{ github.workspace}}/westend-runtime/westend-srtool-digest.json + RELEASE_TAG: ${{ needs.validate-inputs.outputs.release_tag }} shell: bash run: | . ./.github/scripts/common/lib.sh export REF1=$(get_latest_release_tag) - if [[ -z "${{ inputs.version }}" ]]; then + if [[ -z "$RELEASE_TAG" ]]; then export REF2="${{ github.ref_name }}" echo "REF2: ${REF2}" else - export REF2="${{ inputs.version }}" + export REF2="$RELEASE_TAG" echo "REF2: ${REF2}" fi echo "REL_TAG=$REF2" >> $GITHUB_ENV - export VERSION=$(echo "$REF2" | sed -E 's/.*(stable[0-9]+).*$/\1/') + export VERSION=$(echo "$REF2" | sed -E 's/.*(stable[0-9]{4}(-[0-9]+)?).*$/\1/') ./scripts/release/build-changelogs.sh @@ -112,19 +121,29 @@ jobs: scripts/release/context.json **/*-srtool-digest.json + - name: Generate content write token for the release automation + id: generate_write_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.POLKADOT_SDK_RELEASE_RW_APP_ID }} + private-key: ${{ secrets.POLKADOT_SDK_RELEASE_RW_APP_KEY }} + owner: paritytech + repositories: polkadot-sdk + - name: Create draft release id: create-release - uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e # v1.1.4 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ env.REL_TAG }} - release_name: Polkadot ${{ env.REL_TAG }} - body_path: ${{ github.workspace}}/scripts/release/RELEASE_DRAFT.md - draft: true + GITHUB_TOKEN: ${{ steps.generate_write_token.outputs.token }} + run: | + gh release create ${{ env.REL_TAG }} \ + --repo paritytech/polkadot-sdk \ + --draft \ + --title "Polkadot ${{ env.REL_TAG }}" \ + --notes-file ${{ github.workspace}}/scripts/release/RELEASE_DRAFT.md publish-runtimes: - needs: [ build-runtimes, publish-release-draft ] + needs: [ validate-inputs, build-runtimes, publish-release-draft ] + environment: release continue-on-error: true runs-on: ubuntu-latest strategy: @@ -132,7 +151,7 @@ jobs: steps: - name: Checkout sources - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.0.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Download artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 @@ -144,44 +163,83 @@ jobs: >>$GITHUB_ENV echo ASSET=$(find ${{ matrix.chain }}-runtime -name '*.compact.compressed.wasm') >>$GITHUB_ENV echo SPEC=$(<${JSON} jq -r .runtimes.compact.subwasm.core_version.specVersion) + - name: Generate content write token for the release automation + id: generate_write_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ vars.POLKADOT_SDK_RELEASE_RW_APP_ID }} + private-key: ${{ secrets.POLKADOT_SDK_RELEASE_RW_APP_KEY }} + owner: paritytech + repositories: polkadot-sdk + - name: Upload compressed ${{ matrix.chain }} v${{ env.SPEC }} wasm - if: ${{ matrix.chain != 'rococo-parachain' }} - uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 #v1.0.2 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.publish-release-draft.outputs.asset_upload_url }} - asset_path: ${{ env.ASSET }} - asset_name: ${{ matrix.chain }}_runtime-v${{ env.SPEC }}.compact.compressed.wasm - asset_content_type: application/wasm + GITHUB_TOKEN: ${{ steps.generate_write_token.outputs.token }} + run: | + gh release upload ${{ needs.validate-inputs.outputs.release_tag }} \ + --repo paritytech/polkadot-sdk \ + '${{ env.ASSET }}#${{ matrix.chain }}_runtime-v${{ env.SPEC }}.compact.compressed.wasm' - publish-binaries: - needs: [ publish-release-draft, build-binaries ] + publish-release-artifacts: + needs: [ validate-inputs, publish-release-draft ] + environment: release continue-on-error: true runs-on: ubuntu-latest strategy: matrix: - binary: [frame-omni-bencher, chain-spec-builder] + binary: [ polkadot, polkadot-execute-worker, polkadot-prepare-worker, polkadot-parachain, polkadot-omni-node, frame-omni-bencher, chain-spec-builder ] + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] steps: - - name: Download artifacts - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + - name: Checkout sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Fetch binaries from s3 based on version + run: | + . ./.github/scripts/common/lib.sh + + VERSION="${{ needs.validate-inputs.outputs.release_tag }}" + fetch_release_artifacts_from_s3 ${{ matrix.binary }} ${{ matrix.target }} + + - name: Rename aarch64-apple-darwin binaries + if: ${{ matrix.target == 'aarch64-apple-darwin' }} + working-directory: ${{ github.workspace}}/release-artifacts/${{ matrix.target }}/${{ matrix.binary }} + run: | + mv ${{ matrix.binary }} ${{ matrix.binary }}-aarch64-apple-darwin + mv ${{ matrix.binary }}.asc ${{ matrix.binary }}-aarch64-apple-darwin.asc + mv ${{ matrix.binary }}.sha256 ${{ matrix.binary }}-aarch64-apple-darwin.sha256 + + - name: Generate content write token for the release automation + id: generate_write_token + uses: actions/create-github-app-token@v1 with: - name: ${{ matrix.binary }} + app-id: ${{ vars.POLKADOT_SDK_RELEASE_RW_APP_ID }} + private-key: ${{ secrets.POLKADOT_SDK_RELEASE_RW_APP_KEY }} + owner: paritytech + repositories: polkadot-sdk - - name: Upload ${{ matrix.binary }} binary - uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5 #v1.0.2 + - name: Upload ${{ matrix.binary }} binary to release draft env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.publish-release-draft.outputs.asset_upload_url }} - asset_path: ${{ github.workspace}}/${{ matrix.binary }} - asset_name: ${{ matrix.binary }} - asset_content_type: application/octet-stream + GITHUB_TOKEN: ${{ steps.generate_write_token.outputs.token }} + working-directory: ${{ github.workspace}}/release-artifacts/${{ matrix.target }}/${{ matrix.binary }} + run: | + if [[ ${{ matrix.target }} == "aarch64-apple-darwin" ]]; then + gh release upload ${{ needs.validate-inputs.outputs.release_tag }} \ + --repo paritytech/polkadot-sdk \ + ${{ matrix.binary }}-aarch64-apple-darwin \ + ${{ matrix.binary }}-aarch64-apple-darwin.asc \ + ${{ matrix.binary }}-aarch64-apple-darwin.sha256 + else + gh release upload ${{ needs.validate-inputs.outputs.release_tag }} \ + --repo paritytech/polkadot-sdk \ + ${{ matrix.binary }} \ + ${{ matrix.binary }}.asc \ + ${{ matrix.binary }}.sha256 + fi post_to_matrix: runs-on: ubuntu-latest - needs: publish-release-draft + needs: [ validate-inputs, publish-release-draft ] environment: release strategy: matrix: @@ -197,5 +255,5 @@ jobs: access_token: ${{ secrets.RELEASENOTES_MATRIX_V2_ACCESS_TOKEN }} server: m.parity.io message: | - **New version of polkadot tagged**: ${{ github.ref_name }}
- Draft release created: ${{ needs.publish-release-draft.outputs.release_url }} + **New version of polkadot tagged**: ${{ needs.validate-inputs.outputs.release_tag }}
+ And release draft is release created in [polkadot-sdk repo](https://github.com/paritytech/polkadot-sdk/releases) diff --git a/.github/workflows/release-50_publish-docker.yml b/.github/workflows/release-50_publish-docker.yml index 627e53bacd88..5c3c3a6e854d 100644 --- a/.github/workflows/release-50_publish-docker.yml +++ b/.github/workflows/release-50_publish-docker.yml @@ -4,10 +4,6 @@ name: Release - Publish Docker Image # It builds and published releases and rc candidates. on: - #TODO: activate automated run later - # release: - # types: - # - published workflow_dispatch: inputs: image_type: @@ -30,16 +26,6 @@ on: - polkadot-parachain - chain-spec-builder - release_id: - description: | - Release ID. - You can find it using the command: - curl -s \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" https://api.github.com/repos/$OWNER/$REPO/releases | \ - jq '.[] | { name: .name, id: .id }' - required: true - type: number - registry: description: Container registry required: true @@ -55,7 +41,7 @@ on: default: parity version: - description: version to build/release + description: Version of the polkadot node release in format v1.16.0 or v1.16.0-rc1 default: v0.9.18 required: true @@ -78,11 +64,15 @@ env: IMAGE_TYPE: ${{ inputs.image_type }} jobs: + check-synchronization: + uses: paritytech-release/sync-workflows/.github/workflows/check-syncronization.yml@main + validate-inputs: + needs: [check-synchronization] + if: ${{ needs.check-synchronization.outputs.checks_passed }} == 'true' runs-on: ubuntu-latest outputs: version: ${{ steps.validate_inputs.outputs.VERSION }} - release_id: ${{ steps.validate_inputs.outputs.RELEASE_ID }} stable_tag: ${{ steps.validate_inputs.outputs.stable_tag }} steps: @@ -97,11 +87,6 @@ jobs: VERSION=$(filter_version_from_input "${{ inputs.version }}") echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT - RELEASE_ID=$(check_release_id "${{ inputs.release_id }}") - echo "RELEASE_ID=${RELEASE_ID}" >> $GITHUB_OUTPUT - - echo "Release ID: $RELEASE_ID" - STABLE_TAG=$(validate_stable_tag ${{ inputs.stable_tag }}) echo "stable_tag=${STABLE_TAG}" >> $GITHUB_OUTPUT @@ -114,50 +99,26 @@ jobs: - name: Checkout sources uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - #TODO: this step will be needed when automated triggering will work - #this step runs only if the workflow is triggered automatically when new release is published - # if: ${{ env.EVENT_NAME == 'release' && env.EVENT_ACTION != '' && env.EVENT_ACTION == 'published' }} - # run: | - # mkdir -p release-artifacts && cd release-artifacts - - # for f in $BINARY $BINARY.asc $BINARY.sha256; do - # URL="https://github.com/${{ github.event.repository.full_name }}/releases/download/${{ github.event.release.tag_name }}/$f" - # echo " - Fetching $f from $URL" - # wget "$URL" -O "$f" - # done - # chmod a+x $BINARY - # ls -al - - name: Fetch rc artifacts or release artifacts from s3 based on version - #this step runs only if the workflow is triggered manually - if: ${{ env.EVENT_NAME == 'workflow_dispatch' && inputs.binary != 'polkadot-omni-node' && inputs.binary != 'chain-spec-builder'}} + # if: ${{ env.EVENT_NAME == 'workflow_dispatch' && inputs.binary != 'polkadot-omni-node' && inputs.binary != 'chain-spec-builder'}} run: | . ./.github/scripts/common/lib.sh - VERSION="${{ needs.validate-inputs.outputs.VERSION }}" + VERSION="${{ needs.validate-inputs.outputs.stable_tag }}" if [[ ${{ inputs.binary }} == 'polkadot' ]]; then bins=(polkadot polkadot-prepare-worker polkadot-execute-worker) for bin in "${bins[@]}"; do - fetch_release_artifacts_from_s3 $bin + fetch_release_artifacts_from_s3 $bin x86_64-unknown-linux-gnu done else - fetch_release_artifacts_from_s3 $BINARY + fetch_release_artifacts_from_s3 $BINARY x86_64-unknown-linux-gnu fi - - name: Fetch polkadot-omni-node/chain-spec-builder rc artifacts or release artifacts based on release id - #this step runs only if the workflow is triggered manually and only for chain-spec-builder - if: ${{ env.EVENT_NAME == 'workflow_dispatch' && (inputs.binary == 'polkadot-omni-node' || inputs.binary == 'chain-spec-builder') }} - run: | - . ./.github/scripts/common/lib.sh - - RELEASE_ID="${{ needs.validate-inputs.outputs.RELEASE_ID }}" - fetch_release_artifacts - - name: Upload artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: release-artifacts - path: release-artifacts/${{ env.BINARY }}/**/* + path: release-artifacts/x86_64-unknown-linux-gnu/${{ env.BINARY }}/**/* build-container: # this job will be triggered for the polkadot-parachain rc and release or polkadot rc image build if: ${{ inputs.binary == 'polkadot-omni-node' || inputs.binary == 'polkadot-parachain' || inputs.binary == 'chain-spec-builder' || inputs.image_type == 'rc' }} @@ -173,7 +134,7 @@ jobs: uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - name: Check sha256 ${{ env.BINARY }} - if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'polkadot' }} + # if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'polkadot' }} working-directory: release-artifacts run: | . ../.github/scripts/common/lib.sh @@ -182,7 +143,7 @@ jobs: check_sha256 $BINARY && echo "OK" || echo "ERR" - name: Check GPG ${{ env.BINARY }} - if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'polkadot' }} + # if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'polkadot' }} working-directory: release-artifacts run: | . ../.github/scripts/common/lib.sh @@ -190,35 +151,29 @@ jobs: check_gpg $BINARY - name: Fetch rc commit and tag + working-directory: release-artifacts if: ${{ env.IMAGE_TYPE == 'rc' }} id: fetch_rc_refs + shell: bash run: | - . ./.github/scripts/common/lib.sh - - echo "release=${{ needs.validate-inputs.outputs.stable_tag }}" >> $GITHUB_OUTPUT + . ../.github/scripts/common/lib.sh commit=$(git rev-parse --short HEAD) && \ echo "commit=${commit}" >> $GITHUB_OUTPUT - - echo "tag=${{ needs.validate-inputs.outputs.version }}" >> $GITHUB_OUTPUT + echo "release=$(echo ${{ needs.validate-inputs.outputs.version }})" >> $GITHUB_OUTPUT + echo "tag=$(prepare_docker_stable_tag ${{ needs.validate-inputs.outputs.stable_tag }})" >> $GITHUB_OUTPUT - name: Fetch release tags working-directory: release-artifacts if: ${{ env.IMAGE_TYPE == 'release'}} id: fetch_release_refs + shell: bash run: | - chmod a+rx $BINARY - - if [[ $BINARY != 'chain-spec-builder' ]]; then - VERSION=$(./$BINARY --version | awk '{ print $2 }' ) - release=$( echo $VERSION | cut -f1 -d- ) - else - release=$(echo ${{ needs.validate-inputs.outputs.VERSION }} | sed 's/^v//') - fi + . ../.github/scripts/common/lib.sh echo "tag=latest" >> $GITHUB_OUTPUT - echo "release=${release}" >> $GITHUB_OUTPUT - echo "stable=${{ needs.validate-inputs.outputs.stable_tag }}" >> $GITHUB_OUTPUT + echo "release=$(echo ${{ needs.validate-inputs.outputs.version }})" >> $GITHUB_OUTPUT + echo "stable=$(prepare_docker_stable_tag ${{ needs.validate-inputs.outputs.stable_tag }})" >> $GITHUB_OUTPUT - name: Build Injected Container image for polkadot rc if: ${{ env.BINARY == 'polkadot' }} @@ -342,8 +297,10 @@ jobs: - name: Fetch values id: fetch-data run: | + . ./.github/scripts/common/lib.sh date=$(date -u '+%Y-%m-%dT%H:%M:%SZ') echo "date=$date" >> $GITHUB_OUTPUT + echo "stable=$(prepare_docker_stable_tag ${{ needs.validate-inputs.outputs.stable_tag }})" >> $GITHUB_OUTPUT - name: Build and push id: docker_build @@ -354,9 +311,9 @@ jobs: # TODO: The owner should be used below but buildx does not resolve the VARs # TODO: It would be good to get rid of this GHA that we don't really need. tags: | - parity/polkadot:${{ needs.validate-inputs.outputs.stable_tag }} - parity/polkadot:latest - parity/polkadot:${{ needs.fetch-latest-debian-package-version.outputs.polkadot_container_tag }} + egorpop/polkadot:${{ steps.fetch-data.outputs.stable }} + egorpop/polkadot:latest + egorpop/polkadot:${{ needs.fetch-latest-debian-package-version.outputs.polkadot_container_tag }} build-args: | VCS_REF=${{ github.ref }} POLKADOT_VERSION=${{ needs.fetch-latest-debian-package-version.outputs.polkadot_apt_version }} diff --git a/.github/workflows/release-reusable-rc-buid.yml b/.github/workflows/release-reusable-rc-buid.yml index f5240878cba2..dc1b4553eb9b 100644 --- a/.github/workflows/release-reusable-rc-buid.yml +++ b/.github/workflows/release-reusable-rc-buid.yml @@ -302,7 +302,6 @@ jobs: AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} - upload-polkadot-parachain-artifacts-to-s3: if: ${{ inputs.package == 'polkadot-parachain-bin' && inputs.target == 'x86_64-unknown-linux-gnu' }} needs: [build-rc] @@ -329,6 +328,32 @@ jobs: AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + upload-frame-omni-bencher-artifacts-to-s3: + if: ${{ inputs.package == 'frame-omni-bencher' && inputs.target == 'x86_64-unknown-linux-gnu' }} + needs: [build-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: ${{ inputs.package }} + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-chain-spec-builder-artifacts-to-s3: + if: ${{ inputs.package == 'staging-chain-spec-builder' && inputs.target == 'x86_64-unknown-linux-gnu' }} + needs: [build-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: chain-spec-builder + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + upload-polkadot-macos-artifacts-to-s3: if: ${{ inputs.package == 'polkadot' && inputs.target == 'aarch64-apple-darwin' }} # TODO: add and use a `build-polkadot-homebrew-package` which packs all `polkadot` binaries: @@ -395,3 +420,29 @@ jobs: AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-frame-omni-bencher-macos-artifacts-to-s3: + if: ${{ inputs.package == 'frame-omni-bencher' && inputs.target == 'aarch64-apple-darwin' }} + needs: [build-macos-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: ${{ inputs.package }} + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + upload-chain-spec-builder-macos-artifacts-to-s3: + if: ${{ inputs.package == 'staging-chain-spec-builder' && inputs.target == 'aarch64-apple-darwin' }} + needs: [build-macos-rc] + uses: ./.github/workflows/release-reusable-s3-upload.yml + with: + package: chain-spec-builder + release_tag: ${{ inputs.release_tag }} + target: ${{ inputs.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/release-srtool.yml b/.github/workflows/release-srtool.yml index 9a29b46d2fc3..fc10496d481b 100644 --- a/.github/workflows/release-srtool.yml +++ b/.github/workflows/release-srtool.yml @@ -1,7 +1,7 @@ name: Srtool build env: - SUBWASM_VERSION: 0.20.0 + SUBWASM_VERSION: 0.21.0 TOML_CLI_VERSION: 0.2.4 on: @@ -11,14 +11,16 @@ on: type: string build_opts: type: string + profile: + type: string outputs: published_runtimes: value: ${{ jobs.find-runtimes.outputs.runtime }} - schedule: - - cron: "00 02 * * 1" # 2AM weekly on monday - - workflow_dispatch: +permissions: + id-token: write + attestations: write + contents: read jobs: find-runtimes: @@ -75,6 +77,7 @@ jobs: with: chain: ${{ matrix.chain }} runtime_dir: ${{ matrix.runtime_dir }} + profile: ${{ inputs.profile }} - name: Summary run: | @@ -83,6 +86,11 @@ jobs: echo "Compact Runtime: ${{ steps.srtool_build.outputs.wasm }}" echo "Compressed Runtime: ${{ steps.srtool_build.outputs.wasm_compressed }}" + - name: Generate artifact attestation + uses: actions/attest-build-provenance@1c608d11d69870c2092266b3f9a6f3abbf17002c # v1.4.3 + with: + subject-path: ${{ steps.srtool_build.outputs.wasm }} + # We now get extra information thanks to subwasm - name: Install subwasm run: | From 76a292b23bf6f35156fd3dd832e9c4ec31b24b2c Mon Sep 17 00:00:00 2001 From: Lulu Date: Tue, 3 Dec 2024 13:22:45 +0100 Subject: [PATCH 089/340] Update parity-publish (#6549) --- .github/workflows/check-semver.yml | 4 +- .github/workflows/publish-check-crates.yml | 2 +- .github/workflows/publish-claim-crates.yml | 2 +- .../snowbridge/runtime/test-common/Cargo.toml | 2 + cumulus/client/cli/Cargo.toml | 2 + cumulus/client/collator/Cargo.toml | 2 + cumulus/client/consensus/aura/Cargo.toml | 2 + cumulus/client/consensus/common/Cargo.toml | 2 + cumulus/client/consensus/proposer/Cargo.toml | 2 + .../client/consensus/relay-chain/Cargo.toml | 2 + cumulus/client/network/Cargo.toml | 2 + cumulus/client/parachain-inherent/Cargo.toml | 2 + cumulus/client/pov-recovery/Cargo.toml | 2 + .../Cargo.toml | 2 + .../client/relay-chain-interface/Cargo.toml | 2 + .../relay-chain-minimal-node/Cargo.toml | 2 + .../relay-chain-rpc-interface/Cargo.toml | 2 + cumulus/client/service/Cargo.toml | 2 + cumulus/pallets/aura-ext/Cargo.toml | 2 + cumulus/pallets/parachain-system/Cargo.toml | 2 + .../parachain-system/proc-macro/Cargo.toml | 2 + cumulus/pallets/solo-to-para/Cargo.toml | 2 + cumulus/pallets/xcm/Cargo.toml | 2 + cumulus/pallets/xcmp-queue/Cargo.toml | 2 + cumulus/parachains/common/Cargo.toml | 2 + .../emulated/common/Cargo.toml | 2 + .../pallets/collective-content/Cargo.toml | 2 + .../pallets/parachain-info/Cargo.toml | 2 + cumulus/parachains/pallets/ping/Cargo.toml | 2 + .../assets/asset-hub-rococo/Cargo.toml | 2 + .../assets/asset-hub-westend/Cargo.toml | 2 + .../runtimes/assets/common/Cargo.toml | 2 + .../runtimes/assets/test-utils/Cargo.toml | 2 + .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 + .../bridge-hubs/bridge-hub-westend/Cargo.toml | 2 + .../runtimes/bridge-hubs/common/Cargo.toml | 2 + .../bridge-hubs/test-utils/Cargo.toml | 2 + .../collectives-westend/Cargo.toml | 2 + .../parachains/runtimes/constants/Cargo.toml | 2 + .../contracts/contracts-rococo/Cargo.toml | 2 + .../coretime/coretime-rococo/Cargo.toml | 2 + .../coretime/coretime-westend/Cargo.toml | 2 + .../glutton/glutton-westend/Cargo.toml | 2 + .../runtimes/people/people-rococo/Cargo.toml | 2 + .../runtimes/people/people-westend/Cargo.toml | 2 + .../parachains/runtimes/test-utils/Cargo.toml | 2 + .../testing/rococo-parachain/Cargo.toml | 2 + cumulus/polkadot-omni-node/Cargo.toml | 2 + cumulus/polkadot-omni-node/lib/Cargo.toml | 2 + cumulus/polkadot-parachain/Cargo.toml | 2 + cumulus/primitives/aura/Cargo.toml | 2 + cumulus/primitives/core/Cargo.toml | 2 + .../primitives/parachain-inherent/Cargo.toml | 2 + .../proof-size-hostfunction/Cargo.toml | 2 + .../storage-weight-reclaim/Cargo.toml | 2 + cumulus/primitives/timestamp/Cargo.toml | 2 + cumulus/primitives/utility/Cargo.toml | 2 + cumulus/test/relay-sproof-builder/Cargo.toml | 2 + cumulus/xcm/xcm-emulator/Cargo.toml | 2 + polkadot/Cargo.toml | 2 + polkadot/cli/Cargo.toml | 2 + polkadot/core-primitives/Cargo.toml | 2 + polkadot/erasure-coding/Cargo.toml | 2 + polkadot/node/collation-generation/Cargo.toml | 2 + .../core/approval-voting-parallel/Cargo.toml | 2 + polkadot/node/core/approval-voting/Cargo.toml | 2 + polkadot/node/core/av-store/Cargo.toml | 2 + polkadot/node/core/backing/Cargo.toml | 2 + .../node/core/bitfield-signing/Cargo.toml | 2 + .../node/core/candidate-validation/Cargo.toml | 2 + polkadot/node/core/chain-api/Cargo.toml | 2 + polkadot/node/core/chain-selection/Cargo.toml | 2 + .../node/core/dispute-coordinator/Cargo.toml | 2 + .../node/core/parachains-inherent/Cargo.toml | 2 + .../core/prospective-parachains/Cargo.toml | 2 + polkadot/node/core/provisioner/Cargo.toml | 2 + polkadot/node/core/pvf-checker/Cargo.toml | 2 + polkadot/node/core/pvf/Cargo.toml | 2 + polkadot/node/core/pvf/common/Cargo.toml | 2 + .../node/core/pvf/execute-worker/Cargo.toml | 2 + .../node/core/pvf/prepare-worker/Cargo.toml | 2 + polkadot/node/core/runtime-api/Cargo.toml | 2 + polkadot/node/gum/Cargo.toml | 2 + polkadot/node/gum/proc-macro/Cargo.toml | 2 + polkadot/node/metrics/Cargo.toml | 2 + .../network/approval-distribution/Cargo.toml | 2 + .../availability-distribution/Cargo.toml | 2 + .../network/availability-recovery/Cargo.toml | 2 + .../network/bitfield-distribution/Cargo.toml | 2 + polkadot/node/network/bridge/Cargo.toml | 2 + .../node/network/collator-protocol/Cargo.toml | 2 + .../network/dispute-distribution/Cargo.toml | 2 + .../node/network/gossip-support/Cargo.toml | 2 + polkadot/node/network/protocol/Cargo.toml | 2 + .../network/statement-distribution/Cargo.toml | 2 + polkadot/node/overseer/Cargo.toml | 2 + polkadot/node/primitives/Cargo.toml | 2 + polkadot/node/service/Cargo.toml | 2 + polkadot/node/subsystem-types/Cargo.toml | 2 + polkadot/node/subsystem-util/Cargo.toml | 2 + polkadot/node/subsystem/Cargo.toml | 2 + polkadot/node/tracking-allocator/Cargo.toml | 2 + polkadot/parachain/Cargo.toml | 2 + polkadot/primitives/Cargo.toml | 2 + polkadot/rpc/Cargo.toml | 2 + polkadot/runtime/common/Cargo.toml | 2 + .../common/slot_range_helper/Cargo.toml | 2 + polkadot/runtime/metrics/Cargo.toml | 2 + polkadot/runtime/parachains/Cargo.toml | 2 + polkadot/runtime/rococo/Cargo.toml | 2 + polkadot/runtime/rococo/constants/Cargo.toml | 2 + polkadot/runtime/westend/Cargo.toml | 2 + polkadot/runtime/westend/constants/Cargo.toml | 2 + polkadot/statement-table/Cargo.toml | 2 + polkadot/utils/generate-bags/Cargo.toml | 2 + polkadot/xcm/Cargo.toml | 2 + polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml | 2 + polkadot/xcm/pallet-xcm/Cargo.toml | 2 + polkadot/xcm/procedural/Cargo.toml | 2 + polkadot/xcm/xcm-builder/Cargo.toml | 2 + polkadot/xcm/xcm-executor/Cargo.toml | 2 + polkadot/xcm/xcm-simulator/Cargo.toml | 2 + polkadot/xcm/xcm-simulator/example/Cargo.toml | 2 + prdoc/pr_6549.prdoc | 247 ++++++++++++++++++ scripts/generate-umbrella.py | 2 + substrate/frame/revive/fixtures/Cargo.toml | 2 + umbrella/Cargo.toml | 6 + 127 files changed, 501 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_6549.prdoc diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index 8d77b6a31b75..e9bedd16e6d1 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -11,7 +11,7 @@ concurrency: cancel-in-progress: true env: - TOOLCHAIN: nightly-2024-10-19 + TOOLCHAIN: nightly-2024-11-19 jobs: preflight: @@ -74,7 +74,7 @@ jobs: - name: install parity-publish # Set the target dir to cache the build. - run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.1 --locked -q + run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.2 --locked -q - name: check semver run: | diff --git a/.github/workflows/publish-check-crates.yml b/.github/workflows/publish-check-crates.yml index 3fad3b641474..1e5a8054e2c7 100644 --- a/.github/workflows/publish-check-crates.yml +++ b/.github/workflows/publish-check-crates.yml @@ -24,7 +24,7 @@ jobs: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.8.0 --locked -q + run: cargo install parity-publish@0.10.2 --locked -q - name: parity-publish check run: parity-publish --color always check --allow-unpublished diff --git a/.github/workflows/publish-claim-crates.yml b/.github/workflows/publish-claim-crates.yml index 37bf06bb82d8..845b57a61b96 100644 --- a/.github/workflows/publish-claim-crates.yml +++ b/.github/workflows/publish-claim-crates.yml @@ -18,7 +18,7 @@ jobs: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.8.0 --locked -q + run: cargo install parity-publish@0.10.2 --locked -q - name: parity-publish claim env: diff --git a/bridges/snowbridge/runtime/test-common/Cargo.toml b/bridges/snowbridge/runtime/test-common/Cargo.toml index 6f8e586bf5ff..9f47f158ed4a 100644 --- a/bridges/snowbridge/runtime/test-common/Cargo.toml +++ b/bridges/snowbridge/runtime/test-common/Cargo.toml @@ -6,6 +6,8 @@ authors = ["Snowfork "] edition.workspace = true license = "Apache-2.0" categories = ["cryptography::cryptocurrencies"] +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/cli/Cargo.toml b/cumulus/client/cli/Cargo.toml index 9b6f6b73960b..198f9428f1dd 100644 --- a/cumulus/client/cli/Cargo.toml +++ b/cumulus/client/cli/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Parachain node CLI utilities." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/collator/Cargo.toml b/cumulus/client/collator/Cargo.toml index 6ebde0c2c653..83a3f2661e7a 100644 --- a/cumulus/client/collator/Cargo.toml +++ b/cumulus/client/collator/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Common node-side functionality and glue code to collate parachain blocks." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 0bb2de6bb9b8..6e0c124591cb 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/consensus/common/Cargo.toml b/cumulus/client/consensus/common/Cargo.toml index 4bc2f1d1e600..0f532a2101c4 100644 --- a/cumulus/client/consensus/common/Cargo.toml +++ b/cumulus/client/consensus/common/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/consensus/proposer/Cargo.toml b/cumulus/client/consensus/proposer/Cargo.toml index bb760ae03f4d..e391481bc445 100644 --- a/cumulus/client/consensus/proposer/Cargo.toml +++ b/cumulus/client/consensus/proposer/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/consensus/relay-chain/Cargo.toml b/cumulus/client/consensus/relay-chain/Cargo.toml index f3ee6fc2f7d2..7f0f4333c961 100644 --- a/cumulus/client/consensus/relay-chain/Cargo.toml +++ b/cumulus/client/consensus/relay-chain/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" authors.workspace = true edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/network/Cargo.toml b/cumulus/client/network/Cargo.toml index bc67678eedeb..b78df8d73eae 100644 --- a/cumulus/client/network/Cargo.toml +++ b/cumulus/client/network/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true description = "Cumulus-specific networking protocol" edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/parachain-inherent/Cargo.toml b/cumulus/client/parachain-inherent/Cargo.toml index 0d82cf648743..4f53e2bc1bc2 100644 --- a/cumulus/client/parachain-inherent/Cargo.toml +++ b/cumulus/client/parachain-inherent/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [dependencies] async-trait = { workspace = true } diff --git a/cumulus/client/pov-recovery/Cargo.toml b/cumulus/client/pov-recovery/Cargo.toml index 3127dd26fcaa..762837e0bb11 100644 --- a/cumulus/client/pov-recovery/Cargo.toml +++ b/cumulus/client/pov-recovery/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true description = "Parachain PoV recovery" edition.workspace = true license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml index 6f1b74191be7..9e6e8da929bb 100644 --- a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml +++ b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" edition.workspace = true description = "Implementation of the RelayChainInterface trait for Polkadot full-nodes." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/relay-chain-interface/Cargo.toml b/cumulus/client/relay-chain-interface/Cargo.toml index a496fab050dd..2b9e72bbeca6 100644 --- a/cumulus/client/relay-chain-interface/Cargo.toml +++ b/cumulus/client/relay-chain-interface/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" edition.workspace = true description = "Common interface for different relay chain datasources." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/relay-chain-minimal-node/Cargo.toml b/cumulus/client/relay-chain-minimal-node/Cargo.toml index 95ecadc8bd06..0fad188bb1ab 100644 --- a/cumulus/client/relay-chain-minimal-node/Cargo.toml +++ b/cumulus/client/relay-chain-minimal-node/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" edition.workspace = true description = "Minimal node implementation to be used in tandem with RPC or light-client mode." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml index fb4cb4ceed4e..162f5ad0e9e8 100644 --- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml +++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml @@ -5,6 +5,8 @@ version = "0.7.0" edition.workspace = true description = "Implementation of the RelayChainInterface trait that connects to a remote RPC-node." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/client/service/Cargo.toml b/cumulus/client/service/Cargo.toml index 0a77b465d96a..193283648f19 100644 --- a/cumulus/client/service/Cargo.toml +++ b/cumulus/client/service/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Common functions used to assemble the components of a parachain node." license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/pallets/aura-ext/Cargo.toml b/cumulus/pallets/aura-ext/Cargo.toml index c08148928b7c..fcda79f1d5c1 100644 --- a/cumulus/pallets/aura-ext/Cargo.toml +++ b/cumulus/pallets/aura-ext/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "AURA consensus extension pallet for parachains" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 3cb0394c4b95..05498a474e42 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Base pallet for cumulus-based parachains" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml index da6f0fd03efb..629818f9c4cc 100644 --- a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml +++ b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Proc macros provided by the parachain-system pallet" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/pallets/solo-to-para/Cargo.toml b/cumulus/pallets/solo-to-para/Cargo.toml index 5fd1939e93a0..2088361bf11a 100644 --- a/cumulus/pallets/solo-to-para/Cargo.toml +++ b/cumulus/pallets/solo-to-para/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Adds functionality to migrate from a Solo to a Parachain" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/pallets/xcm/Cargo.toml b/cumulus/pallets/xcm/Cargo.toml index 35d7a083b061..ff9be866d48f 100644 --- a/cumulus/pallets/xcm/Cargo.toml +++ b/cumulus/pallets/xcm/Cargo.toml @@ -5,6 +5,8 @@ name = "cumulus-pallet-xcm" version = "0.7.0" license = "Apache-2.0" description = "Pallet for stuff specific to parachains' usage of XCM" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/pallets/xcmp-queue/Cargo.toml b/cumulus/pallets/xcmp-queue/Cargo.toml index 9c7470eda6da..af70a3169d8e 100644 --- a/cumulus/pallets/xcmp-queue/Cargo.toml +++ b/cumulus/pallets/xcmp-queue/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Pallet to queue outbound and inbound XCMP messages." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/common/Cargo.toml b/cumulus/parachains/common/Cargo.toml index 6d436bdf799a..641693a6a01b 100644 --- a/cumulus/parachains/common/Cargo.toml +++ b/cumulus/parachains/common/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Logic which is common to all parachain runtimes" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml index 23edaf6bfe65..8282d12d317f 100644 --- a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Common resources for integration testing with xcm-emulator" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/pallets/collective-content/Cargo.toml b/cumulus/parachains/pallets/collective-content/Cargo.toml index c52021f67e36..09301bd738f3 100644 --- a/cumulus/parachains/pallets/collective-content/Cargo.toml +++ b/cumulus/parachains/pallets/collective-content/Cargo.toml @@ -5,6 +5,8 @@ authors = ["Parity Technologies "] edition.workspace = true description = "Managed content" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/pallets/parachain-info/Cargo.toml b/cumulus/parachains/pallets/parachain-info/Cargo.toml index e0bed23c4f8c..604441c65f29 100644 --- a/cumulus/parachains/pallets/parachain-info/Cargo.toml +++ b/cumulus/parachains/pallets/parachain-info/Cargo.toml @@ -5,6 +5,8 @@ name = "staging-parachain-info" version = "0.7.0" license = "Apache-2.0" description = "Pallet to store the parachain ID" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/pallets/ping/Cargo.toml b/cumulus/parachains/pallets/ping/Cargo.toml index 51fc384a4f14..ceb38f39fd80 100644 --- a/cumulus/parachains/pallets/ping/Cargo.toml +++ b/cumulus/parachains/pallets/ping/Cargo.toml @@ -5,6 +5,8 @@ name = "cumulus-ping" version = "0.7.0" license = "Apache-2.0" description = "Ping Pallet for Cumulus XCM/UMP testing." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index bfe8ed869758..949640dd4be6 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Rococo variant of Asset Hub parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index a3eaebb59153..8e47146a06c3 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Westend variant of Asset Hub parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index fb66f0de2322..fa9efbca7a39 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Assets common utilities" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml index f6b3c13e8102..393d06f95b15 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Test utils for Asset Hub runtimes." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 3eb06e3a18c1..a7710783a1e0 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Rococo's BridgeHub parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 871bf44ec5b2..91900c830ba6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Westend's BridgeHub parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml index 9cb24a2b2820..76a89bcb2e72 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Bridge hub common utilities" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [dependencies] codec = { features = ["derive"], workspace = true } diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml index 915b3090092f..16fef951f328 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Utils for BridgeHub testing" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 810abcf572d4..dc4b73db69e3 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Westend Collectives Parachain Runtime" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/constants/Cargo.toml b/cumulus/parachains/runtimes/constants/Cargo.toml index d54f1e7db6c1..01b023e0fb89 100644 --- a/cumulus/parachains/runtimes/constants/Cargo.toml +++ b/cumulus/parachains/runtimes/constants/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Common constants for Testnet Parachains runtimes" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index c98ca7ba3e74..1aeff5eb2e48 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -5,6 +5,8 @@ description = "Parachain testnet runtime for FRAME Contracts pallet." authors.workspace = true edition.workspace = true license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index 02807827cf92..ab621134b252 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Rococo's Coretime parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index 34353d312b1f..44dfbf93c30e 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Westend's Coretime parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml index 09b4ef679d24..9bbdb8d2ee08 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Glutton parachain runtime." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index a55143b62071..893133bf3c1a 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Rococo's People parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index 4d66332e96dd..66b324b51af4 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Westend's People parachain runtime" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } diff --git a/cumulus/parachains/runtimes/test-utils/Cargo.toml b/cumulus/parachains/runtimes/test-utils/Cargo.toml index e9d666617ee2..17c81ae4921a 100644 --- a/cumulus/parachains/runtimes/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/test-utils/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Utils for Runtimes testing" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml index b0581c8d43ff..4713f4398eaa 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Simple runtime used by the rococo parachain(s)" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/polkadot-omni-node/Cargo.toml b/cumulus/polkadot-omni-node/Cargo.toml index a736e1ef80c5..8b46bc882868 100644 --- a/cumulus/polkadot-omni-node/Cargo.toml +++ b/cumulus/polkadot-omni-node/Cargo.toml @@ -6,6 +6,8 @@ edition.workspace = true build = "build.rs" description = "Generic binary that can run a parachain node with u32 block number and Aura consensus" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index a690229f1695..cca4ac3b2b69 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Helper library that can be used to build a parachain node" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 5520126d0742..f5ce040bb530 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -6,6 +6,8 @@ edition.workspace = true build = "build.rs" description = "Runs a polkadot parachain node" license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/primitives/aura/Cargo.toml b/cumulus/primitives/aura/Cargo.toml index 185b2d40833f..715ce3e1a03e 100644 --- a/cumulus/primitives/aura/Cargo.toml +++ b/cumulus/primitives/aura/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Core primitives for Aura in Cumulus" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/primitives/core/Cargo.toml b/cumulus/primitives/core/Cargo.toml index 533d368d3b00..b5bfe4fbc889 100644 --- a/cumulus/primitives/core/Cargo.toml +++ b/cumulus/primitives/core/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Cumulus related core primitive types and traits" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/primitives/parachain-inherent/Cargo.toml b/cumulus/primitives/parachain-inherent/Cargo.toml index a4271d3fd9cc..2ff990b8d514 100644 --- a/cumulus/primitives/parachain-inherent/Cargo.toml +++ b/cumulus/primitives/parachain-inherent/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Inherent that needs to be present in every parachain block. Contains messages and a relay chain storage-proof." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/primitives/proof-size-hostfunction/Cargo.toml b/cumulus/primitives/proof-size-hostfunction/Cargo.toml index e61c865d05fb..6e8168091892 100644 --- a/cumulus/primitives/proof-size-hostfunction/Cargo.toml +++ b/cumulus/primitives/proof-size-hostfunction/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Hostfunction exposing storage proof size to the runtime." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/primitives/storage-weight-reclaim/Cargo.toml b/cumulus/primitives/storage-weight-reclaim/Cargo.toml index e1ae6743335a..3c358bc25edb 100644 --- a/cumulus/primitives/storage-weight-reclaim/Cargo.toml +++ b/cumulus/primitives/storage-weight-reclaim/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Utilities to reclaim storage weight." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/primitives/timestamp/Cargo.toml b/cumulus/primitives/timestamp/Cargo.toml index cb328e2f2cc6..70cb3e607b98 100644 --- a/cumulus/primitives/timestamp/Cargo.toml +++ b/cumulus/primitives/timestamp/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true description = "Provides timestamp related functionality for parachains." license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/primitives/utility/Cargo.toml b/cumulus/primitives/utility/Cargo.toml index 2ca8b82001d5..1444571edbe0 100644 --- a/cumulus/primitives/utility/Cargo.toml +++ b/cumulus/primitives/utility/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Helper datatypes for Cumulus" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/test/relay-sproof-builder/Cargo.toml b/cumulus/test/relay-sproof-builder/Cargo.toml index e266b5807081..c1efa141a45d 100644 --- a/cumulus/test/relay-sproof-builder/Cargo.toml +++ b/cumulus/test/relay-sproof-builder/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license = "Apache-2.0" description = "Mocked relay state proof builder for testing Cumulus." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index 8598481fae76..d0c637d64d01 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -5,6 +5,8 @@ version = "0.5.0" authors.workspace = true edition.workspace = true license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/Cargo.toml b/polkadot/Cargo.toml index 3a939464868f..101caac0e313 100644 --- a/polkadot/Cargo.toml +++ b/polkadot/Cargo.toml @@ -20,6 +20,8 @@ authors.workspace = true edition.workspace = true version = "6.0.0" default-run = "polkadot" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index da37f6062c57..3eff525b7b1e 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/core-primitives/Cargo.toml b/polkadot/core-primitives/Cargo.toml index 42ca27953738..33869f216f78 100644 --- a/polkadot/core-primitives/Cargo.toml +++ b/polkadot/core-primitives/Cargo.toml @@ -5,6 +5,8 @@ description = "Core Polkadot types used by Relay Chains and parachains." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/erasure-coding/Cargo.toml b/polkadot/erasure-coding/Cargo.toml index 969742c5bb0a..528b955c4db3 100644 --- a/polkadot/erasure-coding/Cargo.toml +++ b/polkadot/erasure-coding/Cargo.toml @@ -5,6 +5,8 @@ description = "Erasure coding used for Polkadot's availability system" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/collation-generation/Cargo.toml b/polkadot/node/collation-generation/Cargo.toml index 777458673f5b..c1716e2e6eb8 100644 --- a/polkadot/node/collation-generation/Cargo.toml +++ b/polkadot/node/collation-generation/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Collator-side subsystem that handles incoming candidate submissions from the parachain." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/approval-voting-parallel/Cargo.toml b/polkadot/node/core/approval-voting-parallel/Cargo.toml index 3a98cce80e92..995687fb4c11 100644 --- a/polkadot/node/core/approval-voting-parallel/Cargo.toml +++ b/polkadot/node/core/approval-voting-parallel/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Approval Voting Subsystem running approval work in parallel" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index f9754d2babc9..80f5dcb7f318 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Approval Voting Subsystem of the Polkadot node" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/av-store/Cargo.toml b/polkadot/node/core/av-store/Cargo.toml index 1d14e4cfba37..9f6864269cef 100644 --- a/polkadot/node/core/av-store/Cargo.toml +++ b/polkadot/node/core/av-store/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/backing/Cargo.toml b/polkadot/node/core/backing/Cargo.toml index cd1acf9daa93..a81fe9486c63 100644 --- a/polkadot/node/core/backing/Cargo.toml +++ b/polkadot/node/core/backing/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "The Candidate Backing Subsystem. Tracks parachain candidates that can be backed, as well as the issuance of statements about candidates." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/bitfield-signing/Cargo.toml b/polkadot/node/core/bitfield-signing/Cargo.toml index 126a18a14166..f00ba5712661 100644 --- a/polkadot/node/core/bitfield-signing/Cargo.toml +++ b/polkadot/node/core/bitfield-signing/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Bitfield signing subsystem for the Polkadot node" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/candidate-validation/Cargo.toml b/polkadot/node/core/candidate-validation/Cargo.toml index 87855dbce415..fea16b1c7604 100644 --- a/polkadot/node/core/candidate-validation/Cargo.toml +++ b/polkadot/node/core/candidate-validation/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/chain-api/Cargo.toml b/polkadot/node/core/chain-api/Cargo.toml index a8e911e0c5c9..0f443868dada 100644 --- a/polkadot/node/core/chain-api/Cargo.toml +++ b/polkadot/node/core/chain-api/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "The Chain API subsystem provides access to chain related utility functions like block number to hash conversions." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/chain-selection/Cargo.toml b/polkadot/node/core/chain-selection/Cargo.toml index 755d5cadeaaf..d2cc425a4816 100644 --- a/polkadot/node/core/chain-selection/Cargo.toml +++ b/polkadot/node/core/chain-selection/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/dispute-coordinator/Cargo.toml b/polkadot/node/core/dispute-coordinator/Cargo.toml index 344b66af1933..11b4ac645c23 100644 --- a/polkadot/node/core/dispute-coordinator/Cargo.toml +++ b/polkadot/node/core/dispute-coordinator/Cargo.toml @@ -5,6 +5,8 @@ description = "The node-side components that participate in disputes" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/parachains-inherent/Cargo.toml b/polkadot/node/core/parachains-inherent/Cargo.toml index 1e4953f40d0b..b1cd5e971b00 100644 --- a/polkadot/node/core/parachains-inherent/Cargo.toml +++ b/polkadot/node/core/parachains-inherent/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Parachains inherent data provider for Polkadot node" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/prospective-parachains/Cargo.toml b/polkadot/node/core/prospective-parachains/Cargo.toml index 5629e4ef7fbe..ced6c30c64b6 100644 --- a/polkadot/node/core/prospective-parachains/Cargo.toml +++ b/polkadot/node/core/prospective-parachains/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "The Prospective Parachains subsystem. Tracks and handles prospective parachain fragments." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/provisioner/Cargo.toml b/polkadot/node/core/provisioner/Cargo.toml index 64a598b420f7..26dca1adbc79 100644 --- a/polkadot/node/core/provisioner/Cargo.toml +++ b/polkadot/node/core/provisioner/Cargo.toml @@ -5,6 +5,8 @@ description = "Responsible for assembling a relay chain block from a set of avai authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/pvf-checker/Cargo.toml b/polkadot/node/core/pvf-checker/Cargo.toml index 73ef17a2843a..cb7e3eadcf0a 100644 --- a/polkadot/node/core/pvf-checker/Cargo.toml +++ b/polkadot/node/core/pvf-checker/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/pvf/Cargo.toml b/polkadot/node/core/pvf/Cargo.toml index 37d5878ea597..1b2a16ae8b55 100644 --- a/polkadot/node/core/pvf/Cargo.toml +++ b/polkadot/node/core/pvf/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/pvf/common/Cargo.toml b/polkadot/node/core/pvf/common/Cargo.toml index 903c8dd1af29..d058d582fc26 100644 --- a/polkadot/node/core/pvf/common/Cargo.toml +++ b/polkadot/node/core/pvf/common/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/pvf/execute-worker/Cargo.toml b/polkadot/node/core/pvf/execute-worker/Cargo.toml index 6ad340d25612..8327cf8058cd 100644 --- a/polkadot/node/core/pvf/execute-worker/Cargo.toml +++ b/polkadot/node/core/pvf/execute-worker/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/pvf/prepare-worker/Cargo.toml b/polkadot/node/core/pvf/prepare-worker/Cargo.toml index 56235bd82192..9dc800a8ef56 100644 --- a/polkadot/node/core/pvf/prepare-worker/Cargo.toml +++ b/polkadot/node/core/pvf/prepare-worker/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/core/runtime-api/Cargo.toml b/polkadot/node/core/runtime-api/Cargo.toml index 834e4b300b9e..15cbf4665d06 100644 --- a/polkadot/node/core/runtime-api/Cargo.toml +++ b/polkadot/node/core/runtime-api/Cargo.toml @@ -5,6 +5,8 @@ description = "Wrapper around the parachain-related runtime APIs" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/gum/Cargo.toml b/polkadot/node/gum/Cargo.toml index 9b2df435a06a..84875ea121b6 100644 --- a/polkadot/node/gum/Cargo.toml +++ b/polkadot/node/gum/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Stick logs together with the TraceID as provided by tempo" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/gum/proc-macro/Cargo.toml b/polkadot/node/gum/proc-macro/Cargo.toml index da6364977cae..b4a3401b15e4 100644 --- a/polkadot/node/gum/proc-macro/Cargo.toml +++ b/polkadot/node/gum/proc-macro/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Generate an overseer including builder pattern and message wrapper from a single annotated struct definition." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/metrics/Cargo.toml b/polkadot/node/metrics/Cargo.toml index 41b08b66e9b4..05344993a75e 100644 --- a/polkadot/node/metrics/Cargo.toml +++ b/polkadot/node/metrics/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/network/approval-distribution/Cargo.toml b/polkadot/node/network/approval-distribution/Cargo.toml index 8d674a733470..abf345552f89 100644 --- a/polkadot/node/network/approval-distribution/Cargo.toml +++ b/polkadot/node/network/approval-distribution/Cargo.toml @@ -5,6 +5,8 @@ description = "Polkadot Approval Distribution subsystem for the distribution of authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/network/availability-distribution/Cargo.toml b/polkadot/node/network/availability-distribution/Cargo.toml index 8c5574f244e4..e87103d99f72 100644 --- a/polkadot/node/network/availability-distribution/Cargo.toml +++ b/polkadot/node/network/availability-distribution/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/network/availability-recovery/Cargo.toml b/polkadot/node/network/availability-recovery/Cargo.toml index 41f09b1f7044..be4323e74f02 100644 --- a/polkadot/node/network/availability-recovery/Cargo.toml +++ b/polkadot/node/network/availability-recovery/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/network/bitfield-distribution/Cargo.toml b/polkadot/node/network/bitfield-distribution/Cargo.toml index 6d007255c574..2ff30489b6c1 100644 --- a/polkadot/node/network/bitfield-distribution/Cargo.toml +++ b/polkadot/node/network/bitfield-distribution/Cargo.toml @@ -5,6 +5,8 @@ description = "Polkadot Bitfiled Distribution subsystem, which gossips signed av authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/network/bridge/Cargo.toml b/polkadot/node/network/bridge/Cargo.toml index b4b5743853cd..c4b46c1dc001 100644 --- a/polkadot/node/network/bridge/Cargo.toml +++ b/polkadot/node/network/bridge/Cargo.toml @@ -5,6 +5,8 @@ description = "The Network Bridge Subsystem — protocol multiplexer for Polkado authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/network/collator-protocol/Cargo.toml b/polkadot/node/network/collator-protocol/Cargo.toml index 304cb23bb6aa..a51d24c70807 100644 --- a/polkadot/node/network/collator-protocol/Cargo.toml +++ b/polkadot/node/network/collator-protocol/Cargo.toml @@ -5,6 +5,8 @@ description = "Polkadot Collator Protocol subsystem. Allows collators and valida authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/network/dispute-distribution/Cargo.toml b/polkadot/node/network/dispute-distribution/Cargo.toml index b4dcafe09eb6..4f2f9ccadf8b 100644 --- a/polkadot/node/network/dispute-distribution/Cargo.toml +++ b/polkadot/node/network/dispute-distribution/Cargo.toml @@ -5,6 +5,8 @@ description = "Polkadot Dispute Distribution subsystem, which ensures all concer authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/network/gossip-support/Cargo.toml b/polkadot/node/network/gossip-support/Cargo.toml index c8c19e5de070..7d17ea45eab9 100644 --- a/polkadot/node/network/gossip-support/Cargo.toml +++ b/polkadot/node/network/gossip-support/Cargo.toml @@ -5,6 +5,8 @@ description = "Polkadot Gossip Support subsystem. Responsible for keeping track authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/network/protocol/Cargo.toml b/polkadot/node/network/protocol/Cargo.toml index 3d51d3c0a565..0bcf224332bc 100644 --- a/polkadot/node/network/protocol/Cargo.toml +++ b/polkadot/node/network/protocol/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Primitives types for the Node-side" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/network/statement-distribution/Cargo.toml b/polkadot/node/network/statement-distribution/Cargo.toml index de07937ffb0a..d737c7bf8968 100644 --- a/polkadot/node/network/statement-distribution/Cargo.toml +++ b/polkadot/node/network/statement-distribution/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/overseer/Cargo.toml b/polkadot/node/overseer/Cargo.toml index 2253a5ae0c66..62634c1da090 100644 --- a/polkadot/node/overseer/Cargo.toml +++ b/polkadot/node/overseer/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "System overseer of the Polkadot node" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/primitives/Cargo.toml b/polkadot/node/primitives/Cargo.toml index 7185205f905b..50ee3a80ddb8 100644 --- a/polkadot/node/primitives/Cargo.toml +++ b/polkadot/node/primitives/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index 6e8eade21a43..7f58a56d5d16 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -6,6 +6,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Utils to tie different Polkadot components together and allow instantiation of a node." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/subsystem-types/Cargo.toml b/polkadot/node/subsystem-types/Cargo.toml index b5686ec96be1..44bb7036d63d 100644 --- a/polkadot/node/subsystem-types/Cargo.toml +++ b/polkadot/node/subsystem-types/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml index d12daa572055..9c21fede1c47 100644 --- a/polkadot/node/subsystem-util/Cargo.toml +++ b/polkadot/node/subsystem-util/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/subsystem/Cargo.toml b/polkadot/node/subsystem/Cargo.toml index ce4bceec7336..4f30d3ce9c09 100644 --- a/polkadot/node/subsystem/Cargo.toml +++ b/polkadot/node/subsystem/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/node/tracking-allocator/Cargo.toml b/polkadot/node/tracking-allocator/Cargo.toml index d98377e53759..0fbf526ccb8b 100644 --- a/polkadot/node/tracking-allocator/Cargo.toml +++ b/polkadot/node/tracking-allocator/Cargo.toml @@ -5,6 +5,8 @@ version = "2.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/parachain/Cargo.toml b/polkadot/parachain/Cargo.toml index 9d0518fd46ad..ea6c4423dc19 100644 --- a/polkadot/parachain/Cargo.toml +++ b/polkadot/parachain/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true version = "6.0.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml index dd269caa2d60..150aaf153fa7 100644 --- a/polkadot/primitives/Cargo.toml +++ b/polkadot/primitives/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Shared primitives used by Polkadot runtime" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/rpc/Cargo.toml b/polkadot/rpc/Cargo.toml index d01528d4dee0..48980dde4bbc 100644 --- a/polkadot/rpc/Cargo.toml +++ b/polkadot/rpc/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Polkadot specific RPC functionality." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index 01b56b31cf20..1646db54455a 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -5,6 +5,8 @@ description = "Pallets and constants used in Relay Chain networks." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/runtime/common/slot_range_helper/Cargo.toml b/polkadot/runtime/common/slot_range_helper/Cargo.toml index 02810b75283f..3f110bdd76c6 100644 --- a/polkadot/runtime/common/slot_range_helper/Cargo.toml +++ b/polkadot/runtime/common/slot_range_helper/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Helper crate for generating slot ranges for the Polkadot runtime." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/runtime/metrics/Cargo.toml b/polkadot/runtime/metrics/Cargo.toml index 3709e1eb697e..0415e4754009 100644 --- a/polkadot/runtime/metrics/Cargo.toml +++ b/polkadot/runtime/metrics/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Runtime metric interface for the Polkadot node" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index a3eec3f9d961..b01778eeb424 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -5,6 +5,8 @@ description = "Relay Chain runtime code responsible for Parachains." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 3b11c977edf3..764c53abbfcb 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -6,6 +6,8 @@ description = "Rococo testnet Relay Chain runtime." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/runtime/rococo/constants/Cargo.toml b/polkadot/runtime/rococo/constants/Cargo.toml index 1d0adac44af4..921bc8f5fe92 100644 --- a/polkadot/runtime/rococo/constants/Cargo.toml +++ b/polkadot/runtime/rococo/constants/Cargo.toml @@ -5,6 +5,8 @@ description = "Constants used throughout the Rococo network." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [package.metadata.polkadot-sdk] exclude-from-umbrella = true diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index f94301baab09..584f5855b7a4 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -6,6 +6,8 @@ description = "Westend testnet Relay Chain runtime." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/runtime/westend/constants/Cargo.toml b/polkadot/runtime/westend/constants/Cargo.toml index 27d5b19b8e77..a50e2f9cc639 100644 --- a/polkadot/runtime/westend/constants/Cargo.toml +++ b/polkadot/runtime/westend/constants/Cargo.toml @@ -5,6 +5,8 @@ description = "Constants used throughout the Westend network." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [package.metadata.polkadot-sdk] exclude-from-umbrella = true diff --git a/polkadot/statement-table/Cargo.toml b/polkadot/statement-table/Cargo.toml index 53ea0b74463b..d9519dafe12d 100644 --- a/polkadot/statement-table/Cargo.toml +++ b/polkadot/statement-table/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Stores messages other authorities issue about candidates in Polkadot." +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/utils/generate-bags/Cargo.toml b/polkadot/utils/generate-bags/Cargo.toml index 16205b0f51f5..3006d8325ef9 100644 --- a/polkadot/utils/generate-bags/Cargo.toml +++ b/polkadot/utils/generate-bags/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "CLI to generate voter bags for Polkadot runtimes" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/xcm/Cargo.toml b/polkadot/xcm/Cargo.toml index 86c7067ad6fa..113e72c27ae1 100644 --- a/polkadot/xcm/Cargo.toml +++ b/polkadot/xcm/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml index b07bdfdca3d1..fe2b78163223 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml +++ b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml @@ -5,6 +5,8 @@ edition.workspace = true license.workspace = true version = "7.0.0" description = "Benchmarks for the XCM pallet" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index 4d44d75e34dd..e8cdd3b4931b 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -5,6 +5,8 @@ description = "A pallet for handling XCM programs." authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/xcm/procedural/Cargo.toml b/polkadot/xcm/procedural/Cargo.toml index 83b35d19cf7e..3167766158ff 100644 --- a/polkadot/xcm/procedural/Cargo.toml +++ b/polkadot/xcm/procedural/Cargo.toml @@ -6,6 +6,8 @@ edition.workspace = true license.workspace = true version = "7.0.0" publish = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index eaa115740f3e..2819a0b0a555 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true version = "7.0.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/xcm/xcm-executor/Cargo.toml b/polkadot/xcm/xcm-executor/Cargo.toml index cc966f91fe4d..20ca40de5faa 100644 --- a/polkadot/xcm/xcm-executor/Cargo.toml +++ b/polkadot/xcm/xcm-executor/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true version = "7.0.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/xcm/xcm-simulator/Cargo.toml b/polkadot/xcm/xcm-simulator/Cargo.toml index c7caa49393ed..47900e226d48 100644 --- a/polkadot/xcm/xcm-simulator/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/Cargo.toml @@ -5,6 +5,8 @@ version = "7.0.0" authors.workspace = true edition.workspace = true license.workspace = true +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/polkadot/xcm/xcm-simulator/example/Cargo.toml b/polkadot/xcm/xcm-simulator/example/Cargo.toml index e0aff9b7782a..6fbe9243944a 100644 --- a/polkadot/xcm/xcm-simulator/example/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/example/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true version = "7.0.0" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/prdoc/pr_6549.prdoc b/prdoc/pr_6549.prdoc new file mode 100644 index 000000000000..61a64c724185 --- /dev/null +++ b/prdoc/pr_6549.prdoc @@ -0,0 +1,247 @@ +doc: [] + +crates: + - name: polkadot-sdk + bump: none + - name: asset-test-utils + bump: none + - name: cumulus-pallet-parachain-system + bump: none + - name: cumulus-pallet-parachain-system-proc-macro + bump: none + - name: cumulus-primitives-core + bump: none + - name: polkadot-core-primitives + bump: none + - name: polkadot-parachain-primitives + bump: none + - name: polkadot-primitives + bump: none + - name: staging-xcm + bump: none + - name: xcm-procedural + bump: none + - name: cumulus-primitives-parachain-inherent + bump: none + - name: cumulus-primitives-proof-size-hostfunction + bump: none + - name: polkadot-runtime-common + bump: none + - name: polkadot-runtime-parachains + bump: none + - name: polkadot-runtime-metrics + bump: none + - name: staging-xcm-executor + bump: none + - name: slot-range-helper + bump: none + - name: staging-xcm-builder + bump: none + - name: pallet-xcm + bump: none + - name: cumulus-primitives-storage-weight-reclaim + bump: none + - name: cumulus-pallet-aura-ext + bump: none + - name: cumulus-primitives-aura + bump: none + - name: staging-parachain-info + bump: none + - name: cumulus-test-relay-sproof-builder + bump: none + - name: cumulus-client-cli + bump: none + - name: cumulus-client-collator + bump: none + - name: cumulus-client-consensus-common + bump: none + - name: cumulus-client-pov-recovery + bump: none + - name: cumulus-relay-chain-interface + bump: none + - name: polkadot-overseer + bump: none + - name: tracing-gum + bump: none + - name: tracing-gum-proc-macro + bump: none + - name: polkadot-node-metrics + bump: none + - name: polkadot-node-primitives + bump: none + - name: polkadot-erasure-coding + bump: none + - name: polkadot-node-subsystem + bump: none + - name: polkadot-node-subsystem-types + bump: none + - name: polkadot-node-network-protocol + bump: none + - name: polkadot-statement-table + bump: none + - name: polkadot-rpc + bump: none + - name: polkadot-service + bump: none + - name: cumulus-client-parachain-inherent + bump: none + - name: westend-runtime + bump: none + - name: pallet-xcm-benchmarks + bump: none + - name: westend-runtime-constants + bump: none + - name: polkadot-approval-distribution + bump: none + - name: polkadot-node-subsystem-util + bump: none + - name: polkadot-availability-bitfield-distribution + bump: none + - name: polkadot-availability-distribution + bump: none + - name: polkadot-availability-recovery + bump: none + - name: polkadot-node-core-approval-voting + bump: none + - name: polkadot-node-core-approval-voting-parallel + bump: none + - name: polkadot-node-core-av-store + bump: none + - name: polkadot-node-core-chain-api + bump: none + - name: polkadot-statement-distribution + bump: none + - name: polkadot-collator-protocol + bump: none + - name: polkadot-dispute-distribution + bump: none + - name: polkadot-gossip-support + bump: none + - name: polkadot-network-bridge + bump: none + - name: polkadot-node-collation-generation + bump: none + - name: polkadot-node-core-backing + bump: none + - name: polkadot-node-core-bitfield-signing + bump: none + - name: polkadot-node-core-candidate-validation + bump: none + - name: polkadot-node-core-pvf + bump: none + - name: polkadot-node-core-pvf-common + bump: none + - name: polkadot-node-core-pvf-execute-worker + bump: none + - name: polkadot-node-core-pvf-prepare-worker + bump: none + - name: staging-tracking-allocator + bump: none + - name: rococo-runtime + bump: none + - name: rococo-runtime-constants + bump: none + - name: polkadot-node-core-chain-selection + bump: none + - name: polkadot-node-core-dispute-coordinator + bump: none + - name: polkadot-node-core-parachains-inherent + bump: none + - name: polkadot-node-core-prospective-parachains + bump: none + - name: polkadot-node-core-provisioner + bump: none + - name: polkadot-node-core-pvf-checker + bump: none + - name: polkadot-node-core-runtime-api + bump: none + - name: cumulus-client-network + bump: none + - name: cumulus-relay-chain-inprocess-interface + bump: none + - name: polkadot-cli + bump: none + - name: cumulus-client-consensus-aura + bump: none + - name: cumulus-client-consensus-proposer + bump: none + - name: cumulus-client-consensus-relay-chain + bump: none + - name: cumulus-client-service + bump: none + - name: cumulus-relay-chain-minimal-node + bump: none + - name: cumulus-relay-chain-rpc-interface + bump: none + - name: parachains-common + bump: none + - name: cumulus-primitives-utility + bump: none + - name: cumulus-pallet-xcmp-queue + bump: none + - name: parachains-runtimes-test-utils + bump: none + - name: assets-common + bump: none + - name: bridge-hub-common + bump: none + - name: bridge-hub-test-utils + bump: none + - name: cumulus-pallet-solo-to-para + bump: none + - name: cumulus-pallet-xcm + bump: none + - name: cumulus-ping + bump: none + - name: cumulus-primitives-timestamp + bump: none + - name: emulated-integration-tests-common + bump: none + - name: xcm-emulator + bump: none + - name: pallet-collective-content + bump: none + - name: xcm-simulator + bump: none + - name: pallet-revive-fixtures + bump: none + - name: polkadot-omni-node-lib + bump: none + - name: snowbridge-runtime-test-common + bump: none + - name: testnet-parachains-constants + bump: none + - name: asset-hub-rococo-runtime + bump: none + - name: asset-hub-westend-runtime + bump: none + - name: bridge-hub-rococo-runtime + bump: none + - name: bridge-hub-westend-runtime + bump: none + - name: collectives-westend-runtime + bump: none + - name: coretime-rococo-runtime + bump: none + - name: coretime-westend-runtime + bump: none + - name: people-rococo-runtime + bump: none + - name: people-westend-runtime + bump: none + - name: contracts-rococo-runtime + bump: none + - name: glutton-westend-runtime + bump: none + - name: rococo-parachain-runtime + bump: none + - name: polkadot-omni-node + bump: none + - name: polkadot-parachain-bin + bump: none + - name: polkadot + bump: none + - name: polkadot-voter-bags + bump: none + - name: xcm-simulator-example + bump: none diff --git a/scripts/generate-umbrella.py b/scripts/generate-umbrella.py index 8326909c3449..ae3873180553 100644 --- a/scripts/generate-umbrella.py +++ b/scripts/generate-umbrella.py @@ -120,6 +120,8 @@ def main(path, version): "edition": { "workspace": True }, "authors": { "workspace": True }, "description": "Polkadot SDK umbrella crate.", + "homepage": { "workspace": True }, + "repository": { "workspace": True }, "license": "Apache-2.0", "metadata": { "docs": { "rs": { "features": ["runtime-full", "node"], diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index 798ed8c75a5a..9fd434db6179 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -5,6 +5,8 @@ authors.workspace = true edition.workspace = true license.workspace = true description = "Fixtures for testing and benchmarking" +homepage.workspace = true +repository.workspace = true [lints] workspace = true diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 7f50658c4e16..9affcffd2ade 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -617,6 +617,12 @@ workspace = true [package.authors] workspace = true +[package.homepage] +workspace = true + +[package.repository] +workspace = true + [dependencies.assets-common] path = "../cumulus/parachains/runtimes/assets/common" default-features = false From c56a98b991e2cdce7419813886a74d5280b66d2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Tue, 3 Dec 2024 13:44:52 +0100 Subject: [PATCH 090/340] pallet-revive-fixtures: Try not to re-create fixture dir (#6735) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On some systems trying to re-create the output directory will lead to an error. Fixes https://github.com/paritytech/subxt/issues/1876 --------- Co-authored-by: Bastian Köcher --- substrate/frame/revive/fixtures/build.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/fixtures/build.rs b/substrate/frame/revive/fixtures/build.rs index 46cd5760ca4e..eca547bc6ddd 100644 --- a/substrate/frame/revive/fixtures/build.rs +++ b/substrate/frame/revive/fixtures/build.rs @@ -204,10 +204,15 @@ fn create_out_dir() -> Result { .join("pallet-revive-fixtures"); // clean up some leftover symlink from previous versions of this script - if out_dir.exists() && !out_dir.is_dir() { + let mut out_exists = out_dir.exists(); + if out_exists && !out_dir.is_dir() { fs::remove_file(&out_dir)?; + out_exists = false; + } + + if !out_exists { + fs::create_dir(&out_dir).context("Failed to create output directory")?; } - fs::create_dir_all(&out_dir).context("Failed to create output directory")?; // write the location of the out dir so it can be found later let mut file = fs::File::create(temp_dir.join("fixture_location.rs")) From d1d92ab76004ce349a97fc5d325eaf9a4a7101b7 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Tue, 3 Dec 2024 13:45:35 +0100 Subject: [PATCH 091/340] Bump Westend AH (#6583) Bump Asset-Hub westend spec version --------- Co-authored-by: GitHub Action --- .../runtimes/assets/asset-hub-westend/src/lib.rs | 2 +- prdoc/pr_6583.prdoc | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_6583.prdoc diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 98d647d868db..21368e9c2b4b 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -124,7 +124,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_016_008, + spec_version: 1_017_002, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, diff --git a/prdoc/pr_6583.prdoc b/prdoc/pr_6583.prdoc new file mode 100644 index 000000000000..0e67ed33e27c --- /dev/null +++ b/prdoc/pr_6583.prdoc @@ -0,0 +1,7 @@ +title: Bump Westend AH +doc: +- audience: Runtime Dev + description: Bump Asset-Hub westend spec version +crates: +- name: asset-hub-westend-runtime + bump: minor From 896c81440c1dd169bd2f5e65aba46eca228609f8 Mon Sep 17 00:00:00 2001 From: Lulu Date: Tue, 3 Dec 2024 14:18:05 +0100 Subject: [PATCH 092/340] Add publish-check-compile workflow (#6556) Add publish-check-compile workflow This Applies staged prdocs then configures crate deps to pull from crates.io for our already published crates and local paths for things to be published. Then runs cargo check on the result. This results in a build state consitent with that of publish time and should catch compile errors that we would of otherwise ran into mid pubish. This acts as a supplement to the check-semver job. check-semver works on a high level and judges what changes are incorrect and why. This job just runs the change, sees if it compiles, and if not spits out a compile error. --- .github/workflows/publish-check-compile.yml | 48 +++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/publish-check-compile.yml diff --git a/.github/workflows/publish-check-compile.yml b/.github/workflows/publish-check-compile.yml new file mode 100644 index 000000000000..83cd3ff8fa90 --- /dev/null +++ b/.github/workflows/publish-check-compile.yml @@ -0,0 +1,48 @@ +name: Check publish build + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + merge_group: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + preflight: + uses: ./.github/workflows/reusable-preflight.yml + + check-publish: + timeout-minutes: 90 + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + steps: + - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 + + - name: Rust Cache + uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 + with: + cache-on-failure: true + + - name: install parity-publish + run: cargo install parity-publish@0.10.2 --locked -q + + - name: parity-publish update plan + run: parity-publish --color always plan --skip-check --prdoc prdoc/ + + - name: parity-publish apply plan + run: parity-publish --color always apply --registry + + - name: parity-publish check compile + run: | + packages="$(parity-publish apply --print)" + + if [ -n "$packages" ]; then + cargo --color always check $(printf -- '-p %s ' $packages) + fi From 41a5d8ec5f3d3d0ff82899be66113b223395ade5 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:02:03 +0100 Subject: [PATCH 093/340] `fatxpool`: handling limits and priorities improvements (#6405) This PR provides a number of improvements around handling limits and priorities in the fork-aware transaction pool. #### Notes to reviewers. #### Following are the notable changes: 1. #### [Better support](https://github.com/paritytech/polkadot-sdk/pull/6405/commits/414ec3ccad154c9a2aab0586bfa2d2c884fd140f) for `Usurped` transactions When any view reports an `Usurped` transaction (replaced by other with higher priority) it is removed from all the views (also inactive). Removal is implemented by simply submitting usurper transaction to all the views. It is also ensured that usurped tx will not sneak into the `view_store` in newly created view (this is why `ViewStore::pending_txs_replacements` was added). 1. #### [`TimedTransactionSource`](https://github.com/paritytech/polkadot-sdk/pull/6405/commits/f10590f3bde69b31250761a5b10802fb139ab2b2) introduced: Every view now has an information when the transaction entered the pool. Enforce limits (now only for future txs) uses this timestamp to find worst transactions. Having common timestamp ensures coherent assessment of the transaction's importance across different views. This also could later be used to select which ready transaction shall be dropped. 1. #### `DroppedWatcher`: [improved logic](https://github.com/paritytech/polkadot-sdk/pull/6405/commits/560db28c987dd1e634119788ebc8318967df206b) for future transactions For future transaction - if the last referencing view is removed, the transaction will be dropped from the pool. This prevents future unincluded and un-promoted transactions from staying in the pool for long time. #### And some minor changes: 1. [simplified](https://github.com/paritytech/polkadot-sdk/pull/6405/commits/2d0bbf83e2df2b4c641ef84c1188907c4bfad3c6) the flow in `update_view_with_mempool` (code duplication + minor bug fix). 2. `graph::BasePool`: [handling priorities](https://github.com/paritytech/polkadot-sdk/pull/6405/commits/c9f2d39355853d034fdbc6ea31e4e0e5bf34cb6a) for future transaction improved (previously transaction with lower prio was reported as failed), 3. `graph::listener`: dedicated `limit_enforced`/`usurped`/`dropped` [calls added](https://github.com/paritytech/polkadot-sdk/pull/6405/commits/7b58a68cccfcf372321ea41826fbe9d4222829cf), 4. flaky test [fixed](https://github.com/paritytech/polkadot-sdk/pull/6405/commits/e0a7bc6c048245943796839b166505e2aecdbd7d) 5. new tests added, related to: #5809 --------- Co-authored-by: GitHub Action Co-authored-by: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> --- prdoc/pr_6405.prdoc | 9 + .../client/transaction-pool/benches/basics.rs | 4 +- .../src/fork_aware_txpool/dropped_watcher.rs | 291 +++++++++++++----- .../fork_aware_txpool/fork_aware_txpool.rs | 199 +++++++----- .../import_notification_sink.rs | 19 +- .../fork_aware_txpool/multi_view_listener.rs | 38 ++- .../fork_aware_txpool/revalidation_worker.rs | 9 +- .../src/fork_aware_txpool/tx_mem_pool.rs | 88 ++++-- .../src/fork_aware_txpool/view.rs | 31 +- .../src/fork_aware_txpool/view_store.rs | 262 ++++++++++++++-- .../transaction-pool/src/graph/base_pool.rs | 159 +++++++++- .../transaction-pool/src/graph/listener.rs | 47 ++- .../client/transaction-pool/src/graph/pool.rs | 30 +- .../transaction-pool/src/graph/ready.rs | 5 +- .../transaction-pool/src/graph/rotator.rs | 5 +- .../src/graph/validated_pool.rs | 27 +- .../transaction-pool/src/graph/watcher.rs | 6 + substrate/client/transaction-pool/src/lib.rs | 5 +- .../src/single_state_txpool/revalidation.rs | 25 +- .../single_state_txpool.rs | 46 ++- .../client/transaction-pool/tests/fatp.rs | 14 +- .../transaction-pool/tests/fatp_common/mod.rs | 14 + .../transaction-pool/tests/fatp_limits.rs | 189 ++++++++++++ .../transaction-pool/tests/fatp_prios.rs | 249 +++++++++++++++ .../client/transaction-pool/tests/pool.rs | 28 +- 25 files changed, 1420 insertions(+), 379 deletions(-) create mode 100644 prdoc/pr_6405.prdoc create mode 100644 substrate/client/transaction-pool/tests/fatp_prios.rs diff --git a/prdoc/pr_6405.prdoc b/prdoc/pr_6405.prdoc new file mode 100644 index 000000000000..9e4e0b3c6c20 --- /dev/null +++ b/prdoc/pr_6405.prdoc @@ -0,0 +1,9 @@ +title: '`fatxpool`: handling limits and priorities improvements' +doc: +- audience: Node Dev + description: |- + This PR provides a number of improvements and fixes around handling limits and priorities in the fork-aware transaction pool. + +crates: +- name: sc-transaction-pool + bump: major diff --git a/substrate/client/transaction-pool/benches/basics.rs b/substrate/client/transaction-pool/benches/basics.rs index 0d8c1cbba9b4..5e40b0fb72d6 100644 --- a/substrate/client/transaction-pool/benches/basics.rs +++ b/substrate/client/transaction-pool/benches/basics.rs @@ -152,7 +152,7 @@ fn uxt(transfer: TransferData) -> Extrinsic { } fn bench_configured(pool: Pool, number: u64, api: Arc) { - let source = TransactionSource::External; + let source = TimedTransactionSource::new_external(false); let mut futures = Vec::new(); let mut tags = Vec::new(); let at = HashAndNumber { @@ -171,7 +171,7 @@ fn bench_configured(pool: Pool, number: u64, api: Arc) { tags.push(to_tag(nonce, AccountId::from_h256(H256::from_low_u64_be(1)))); - futures.push(pool.submit_one(&at, source, xt)); + futures.push(pool.submit_one(&at, source.clone(), xt)); } let res = block_on(futures::future::join_all(futures.into_iter())); diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs index ecae21395c91..7679e3b169d2 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs @@ -24,7 +24,7 @@ use crate::{ common::log_xt::log_xt_trace, fork_aware_txpool::stream_map_util::next_event, - graph::{BlockHash, ChainApi, ExtrinsicHash}, + graph::{self, BlockHash, ExtrinsicHash}, LOG_TARGET, }; use futures::stream::StreamExt; @@ -33,12 +33,44 @@ use sc_transaction_pool_api::TransactionStatus; use sc_utils::mpsc; use sp_runtime::traits::Block as BlockT; use std::{ - collections::{hash_map::Entry, HashMap, HashSet}, + collections::{ + hash_map::{Entry, OccupiedEntry}, + HashMap, HashSet, + }, fmt::{self, Debug, Formatter}, pin::Pin, }; use tokio_stream::StreamMap; +/// Represents a transaction that was removed from the transaction pool, including the reason of its +/// removal. +#[derive(Debug, PartialEq)] +pub struct DroppedTransaction { + /// Hash of the dropped extrinsic. + pub tx_hash: Hash, + /// Reason of the transaction being dropped. + pub reason: DroppedReason, +} + +impl DroppedTransaction { + fn new_usurped(tx_hash: Hash, by: Hash) -> Self { + Self { reason: DroppedReason::Usurped(by), tx_hash } + } + + fn new_enforced_by_limts(tx_hash: Hash) -> Self { + Self { reason: DroppedReason::LimitsEnforced, tx_hash } + } +} + +/// Provides reason of why transactions was dropped. +#[derive(Debug, PartialEq)] +pub enum DroppedReason { + /// Transaction was replaced by other transaction (e.g. because of higher priority). + Usurped(Hash), + /// Transaction was dropped because of internal pool limits being enforced. + LimitsEnforced, +} + /// Dropped-logic related event from the single view. pub type ViewStreamEvent = crate::graph::DroppedByLimitsEvent, BlockHash>; @@ -47,7 +79,8 @@ type ViewStream = Pin> + Se /// Stream of extrinsic hashes that were dropped by the views and have no references by existing /// views. -pub(crate) type StreamOfDropped = Pin> + Send>>; +pub(crate) type StreamOfDropped = + Pin>> + Send>>; /// A type alias for a sender used as the controller of the [`MultiViewDropWatcherContext`]. /// Used to send control commands from the [`MultiViewDroppedWatcherController`] to @@ -59,24 +92,24 @@ type Controller = mpsc::TracingUnboundedSender; type CommandReceiver = mpsc::TracingUnboundedReceiver; /// Commands to control the instance of dropped transactions stream [`StreamOfDropped`]. -enum Command +enum Command where - C: ChainApi, + ChainApi: graph::ChainApi, { /// Adds a new stream of dropped-related events originating in a view with a specific block /// hash - AddView(BlockHash, ViewStream), + AddView(BlockHash, ViewStream), /// Removes an existing view's stream associated with a specific block hash. - RemoveView(BlockHash), - /// Removes internal states for given extrinsic hashes. + RemoveView(BlockHash), + /// Removes referencing views for given extrinsic hashes. /// /// Intended to ba called on finalization. - RemoveFinalizedTxs(Vec>), + RemoveFinalizedTxs(Vec>), } -impl Debug for Command +impl Debug for Command where - C: ChainApi, + ChainApi: graph::ChainApi, { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { match self { @@ -92,30 +125,114 @@ where /// /// This struct maintains a mapping of active views and their corresponding streams, as well as the /// state of each transaction with respect to these views. -struct MultiViewDropWatcherContext +struct MultiViewDropWatcherContext where - C: ChainApi, + ChainApi: graph::ChainApi, { /// A map that associates the views identified by corresponding block hashes with their streams /// of dropped-related events. This map is used to keep track of active views and their event /// streams. - stream_map: StreamMap, ViewStream>, + stream_map: StreamMap, ViewStream>, /// A receiver for commands to control the state of the stream, allowing the addition and /// removal of views. This is used to dynamically update which views are being tracked. - command_receiver: CommandReceiver>, - + command_receiver: CommandReceiver>, /// For each transaction hash we keep the set of hashes representing the views that see this - /// transaction as ready or future. + /// transaction as ready or in_block. + /// + /// Even if all views referencing a ready transactions are removed, we still want to keep + /// transaction, there can be a fork which sees the transaction as ready. /// /// Once transaction is dropped, dropping view is removed from the set. - transaction_states: HashMap, HashSet>>, + ready_transaction_views: HashMap, HashSet>>, + /// For each transaction hash we keep the set of hashes representing the views that see this + /// transaction as future. + /// + /// Once all views referencing a future transactions are removed, the future can be dropped. + /// + /// Once transaction is dropped, dropping view is removed from the set. + future_transaction_views: HashMap, HashSet>>, + + /// Transactions that need to be notified as dropped. + pending_dropped_transactions: Vec>, } impl MultiViewDropWatcherContext where - C: ChainApi + 'static, - <::Block as BlockT>::Hash: Unpin, + C: graph::ChainApi + 'static, + <::Block as BlockT>::Hash: Unpin, { + /// Provides the ready or future `HashSet` containing views referencing given transaction. + fn transaction_views( + &mut self, + tx_hash: ExtrinsicHash, + ) -> Option, HashSet>>> { + if let Entry::Occupied(views_keeping_tx_valid) = self.ready_transaction_views.entry(tx_hash) + { + return Some(views_keeping_tx_valid) + } + if let Entry::Occupied(views_keeping_tx_valid) = + self.future_transaction_views.entry(tx_hash) + { + return Some(views_keeping_tx_valid) + } + None + } + + /// Processes the command and updates internal state accordingly. + fn handle_command(&mut self, cmd: Command) { + match cmd { + Command::AddView(key, stream) => { + trace!( + target: LOG_TARGET, + "dropped_watcher: Command::AddView {key:?} views:{:?}", + self.stream_map.keys().collect::>() + ); + self.stream_map.insert(key, stream); + }, + Command::RemoveView(key) => { + trace!( + target: LOG_TARGET, + "dropped_watcher: Command::RemoveView {key:?} views:{:?}", + self.stream_map.keys().collect::>() + ); + self.stream_map.remove(&key); + self.ready_transaction_views.iter_mut().for_each(|(tx_hash, views)| { + trace!( + target: LOG_TARGET, + "[{:?}] dropped_watcher: Command::RemoveView ready views: {:?}", + tx_hash, + views + ); + views.remove(&key); + }); + + self.future_transaction_views.iter_mut().for_each(|(tx_hash, views)| { + trace!( + target: LOG_TARGET, + "[{:?}] dropped_watcher: Command::RemoveView future views: {:?}", + tx_hash, + views + ); + views.remove(&key); + if views.is_empty() { + self.pending_dropped_transactions.push(*tx_hash); + } + }); + }, + Command::RemoveFinalizedTxs(xts) => { + log_xt_trace!( + target: LOG_TARGET, + xts.clone(), + "[{:?}] dropped_watcher: finalized xt removed" + ); + xts.iter().for_each(|xt| { + self.ready_transaction_views.remove(xt); + self.future_transaction_views.remove(xt); + }); + }, + } + } + /// Processes a `ViewStreamEvent` from a specific view and updates the internal state /// accordingly. /// @@ -125,41 +242,69 @@ where &mut self, block_hash: BlockHash, event: ViewStreamEvent, - ) -> Option> { + ) -> Option>> { trace!( target: LOG_TARGET, - "dropped_watcher: handle_event: event:{:?} views:{:?}, ", - event, + "dropped_watcher: handle_event: event:{event:?} from:{block_hash:?} future_views:{:?} ready_views:{:?} stream_map views:{:?}, ", + self.future_transaction_views.get(&event.0), + self.ready_transaction_views.get(&event.0), self.stream_map.keys().collect::>(), ); let (tx_hash, status) = event; match status { - TransactionStatus::Ready | TransactionStatus::Future => { - self.transaction_states.entry(tx_hash).or_default().insert(block_hash); + TransactionStatus::Future => { + self.future_transaction_views.entry(tx_hash).or_default().insert(block_hash); + }, + TransactionStatus::Ready | TransactionStatus::InBlock(..) => { + // note: if future transaction was once seens as the ready we may want to treat it + // as ready transactions. Unreferenced future transactions are more likely to be + // removed when the last referencing view is removed then ready transactions. + // Transcaction seen as ready is likely quite close to be included in some + // future fork. + if let Some(mut views) = self.future_transaction_views.remove(&tx_hash) { + views.insert(block_hash); + self.ready_transaction_views.insert(tx_hash, views); + } else { + self.ready_transaction_views.entry(tx_hash).or_default().insert(block_hash); + } }, - TransactionStatus::Dropped | TransactionStatus::Usurped(_) => { - if let Entry::Occupied(mut views_keeping_tx_valid) = - self.transaction_states.entry(tx_hash) - { + TransactionStatus::Dropped => { + if let Some(mut views_keeping_tx_valid) = self.transaction_views(tx_hash) { views_keeping_tx_valid.get_mut().remove(&block_hash); - if views_keeping_tx_valid.get().is_empty() || - views_keeping_tx_valid - .get() - .iter() - .all(|h| !self.stream_map.contains_key(h)) - { - return Some(tx_hash) + if views_keeping_tx_valid.get().is_empty() { + return Some(DroppedTransaction::new_enforced_by_limts(tx_hash)) } } else { debug!("[{:?}] dropped_watcher: removing (non-tracked) tx", tx_hash); - return Some(tx_hash) + return Some(DroppedTransaction::new_enforced_by_limts(tx_hash)) } }, + TransactionStatus::Usurped(by) => + return Some(DroppedTransaction::new_usurped(tx_hash, by)), _ => {}, }; None } + /// Gets pending dropped transactions if any. + fn get_pending_dropped_transaction(&mut self) -> Option>> { + while let Some(tx_hash) = self.pending_dropped_transactions.pop() { + // never drop transaction that was seen as ready. It may not have a referencing + // view now, but such fork can appear. + if self.ready_transaction_views.get(&tx_hash).is_some() { + continue + } + + if let Some(views) = self.future_transaction_views.get(&tx_hash) { + if views.is_empty() { + self.future_transaction_views.remove(&tx_hash); + return Some(DroppedTransaction::new_enforced_by_limts(tx_hash)) + } + } + } + None + } + /// Creates a new `StreamOfDropped` and its associated event stream controller. /// /// This method initializes the internal structures and unfolds the stream of dropped @@ -176,42 +321,29 @@ where let ctx = Self { stream_map: StreamMap::new(), command_receiver, - transaction_states: Default::default(), + ready_transaction_views: Default::default(), + future_transaction_views: Default::default(), + pending_dropped_transactions: Default::default(), }; let stream_map = futures::stream::unfold(ctx, |mut ctx| async move { loop { + if let Some(dropped) = ctx.get_pending_dropped_transaction() { + debug!("dropped_watcher: sending out (pending): {dropped:?}"); + return Some((dropped, ctx)); + } tokio::select! { biased; - cmd = ctx.command_receiver.next() => { - match cmd? { - Command::AddView(key,stream) => { - trace!(target: LOG_TARGET,"dropped_watcher: Command::AddView {key:?} views:{:?}",ctx.stream_map.keys().collect::>()); - ctx.stream_map.insert(key,stream); - }, - Command::RemoveView(key) => { - trace!(target: LOG_TARGET,"dropped_watcher: Command::RemoveView {key:?} views:{:?}",ctx.stream_map.keys().collect::>()); - ctx.stream_map.remove(&key); - ctx.transaction_states.iter_mut().for_each(|(_,state)| { - state.remove(&key); - }); - }, - Command::RemoveFinalizedTxs(xts) => { - log_xt_trace!(target: LOG_TARGET, xts.clone(), "[{:?}] dropped_watcher: finalized xt removed"); - xts.iter().for_each(|xt| { - ctx.transaction_states.remove(xt); - }); - - }, - } - }, - Some(event) = next_event(&mut ctx.stream_map) => { if let Some(dropped) = ctx.handle_event(event.0, event.1) { debug!("dropped_watcher: sending out: {dropped:?}"); return Some((dropped, ctx)); } + }, + cmd = ctx.command_receiver.next() => { + ctx.handle_command(cmd?); } + } } }) @@ -225,30 +357,30 @@ where /// /// This struct provides methods to add and remove streams associated with views to and from the /// stream. -pub struct MultiViewDroppedWatcherController { +pub struct MultiViewDroppedWatcherController { /// A controller allowing to update the state of the associated [`StreamOfDropped`]. - controller: Controller>, + controller: Controller>, } -impl Clone for MultiViewDroppedWatcherController { +impl Clone for MultiViewDroppedWatcherController { fn clone(&self) -> Self { Self { controller: self.controller.clone() } } } -impl MultiViewDroppedWatcherController +impl MultiViewDroppedWatcherController where - C: ChainApi + 'static, - <::Block as BlockT>::Hash: Unpin, + ChainApi: graph::ChainApi + 'static, + <::Block as BlockT>::Hash: Unpin, { /// Creates new [`StreamOfDropped`] and its controller. - pub fn new() -> (MultiViewDroppedWatcherController, StreamOfDropped) { - let (stream_map, ctrl) = MultiViewDropWatcherContext::::event_stream(); + pub fn new() -> (MultiViewDroppedWatcherController, StreamOfDropped) { + let (stream_map, ctrl) = MultiViewDropWatcherContext::::event_stream(); (Self { controller: ctrl }, stream_map.boxed()) } /// Notifies the [`StreamOfDropped`] that new view was created. - pub fn add_view(&self, key: BlockHash, view: ViewStream) { + pub fn add_view(&self, key: BlockHash, view: ViewStream) { let _ = self.controller.unbounded_send(Command::AddView(key, view)).map_err(|e| { trace!(target: LOG_TARGET, "dropped_watcher: add_view {key:?} send message failed: {e}"); }); @@ -256,14 +388,17 @@ where /// Notifies the [`StreamOfDropped`] that the view was destroyed and shall be removed the /// stream map. - pub fn remove_view(&self, key: BlockHash) { + pub fn remove_view(&self, key: BlockHash) { let _ = self.controller.unbounded_send(Command::RemoveView(key)).map_err(|e| { trace!(target: LOG_TARGET, "dropped_watcher: remove_view {key:?} send message failed: {e}"); }); } /// Removes status info for finalized transactions. - pub fn remove_finalized_txs(&self, xts: impl IntoIterator> + Clone) { + pub fn remove_finalized_txs( + &self, + xts: impl IntoIterator> + Clone, + ) { let _ = self .controller .unbounded_send(Command::RemoveFinalizedTxs(xts.into_iter().collect())) @@ -298,7 +433,7 @@ mod dropped_watcher_tests { watcher.add_view(block_hash, view_stream); let handle = tokio::spawn(async move { output_stream.take(1).collect::>().await }); - assert_eq!(handle.await.unwrap(), vec![tx_hash]); + assert_eq!(handle.await.unwrap(), vec![DroppedTransaction::new_enforced_by_limts(tx_hash)]); } #[tokio::test] @@ -348,7 +483,10 @@ mod dropped_watcher_tests { watcher.add_view(block_hash0, view_stream0); watcher.add_view(block_hash1, view_stream1); let handle = tokio::spawn(async move { output_stream.take(1).collect::>().await }); - assert_eq!(handle.await.unwrap(), vec![tx_hash1]); + assert_eq!( + handle.await.unwrap(), + vec![DroppedTransaction::new_enforced_by_limts(tx_hash1)] + ); } #[tokio::test] @@ -373,10 +511,11 @@ mod dropped_watcher_tests { watcher.add_view(block_hash0, view_stream0); assert!(output_stream.next().now_or_never().is_none()); + watcher.remove_view(block_hash0); watcher.add_view(block_hash1, view_stream1); let handle = tokio::spawn(async move { output_stream.take(1).collect::>().await }); - assert_eq!(handle.await.unwrap(), vec![tx_hash]); + assert_eq!(handle.await.unwrap(), vec![DroppedTransaction::new_enforced_by_limts(tx_hash)]); } #[tokio::test] @@ -419,6 +558,6 @@ mod dropped_watcher_tests { let block_hash2 = H256::repeat_byte(0x03); watcher.add_view(block_hash2, view_stream2); let handle = tokio::spawn(async move { output_stream.take(1).collect::>().await }); - assert_eq!(handle.await.unwrap(), vec![tx_hash]); + assert_eq!(handle.await.unwrap(), vec![DroppedTransaction::new_enforced_by_limts(tx_hash)]); } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs index 065d0cb3a274..4ec87f1fefa4 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs @@ -23,7 +23,7 @@ use super::{ import_notification_sink::MultiViewImportNotificationSink, metrics::MetricsLink as PrometheusMetrics, multi_view_listener::MultiViewListener, - tx_mem_pool::{TxInMemPool, TxMemPool, TXMEMPOOL_TRANSACTION_LIMIT_MULTIPLIER}, + tx_mem_pool::{InsertionInfo, TxInMemPool, TxMemPool, TXMEMPOOL_TRANSACTION_LIMIT_MULTIPLIER}, view::View, view_store::ViewStore, }; @@ -31,8 +31,12 @@ use crate::{ api::FullChainApi, common::log_xt::log_xt_trace, enactment_state::{EnactmentAction, EnactmentState}, - fork_aware_txpool::revalidation_worker, - graph::{self, base_pool::Transaction, ExtrinsicFor, ExtrinsicHash, IsValidator, Options}, + fork_aware_txpool::{dropped_watcher::DroppedReason, revalidation_worker}, + graph::{ + self, + base_pool::{TimedTransactionSource, Transaction}, + ExtrinsicFor, ExtrinsicHash, IsValidator, Options, + }, ReadyIteratorFor, LOG_TARGET, }; use async_trait::async_trait; @@ -197,9 +201,14 @@ where let (dropped_stream_controller, dropped_stream) = MultiViewDroppedWatcherController::::new(); + + let view_store = + Arc::new(ViewStore::new(pool_api.clone(), listener, dropped_stream_controller)); + let dropped_monitor_task = Self::dropped_monitor_task( dropped_stream, mempool.clone(), + view_store.clone(), import_notification_sink.clone(), ); @@ -216,8 +225,8 @@ where ( Self { mempool, - api: pool_api.clone(), - view_store: Arc::new(ViewStore::new(pool_api, listener, dropped_stream_controller)), + api: pool_api, + view_store, ready_poll: Arc::from(Mutex::from(ReadyPoll::new())), enactment_state: Arc::new(Mutex::new(EnactmentState::new( best_block_hash, @@ -233,14 +242,17 @@ where ) } - /// Monitors the stream of dropped transactions and removes them from the mempool. + /// Monitors the stream of dropped transactions and removes them from the mempool and + /// view_store. /// /// This asynchronous task continuously listens for dropped transaction notifications provided /// within `dropped_stream` and ensures that these transactions are removed from the `mempool` - /// and `import_notification_sink` instances. + /// and `import_notification_sink` instances. For Usurped events, the transaction is also + /// removed from the view_store. async fn dropped_monitor_task( mut dropped_stream: StreamOfDropped, mempool: Arc>, + view_store: Arc>, import_notification_sink: MultiViewImportNotificationSink< Block::Hash, ExtrinsicHash, @@ -251,9 +263,33 @@ where log::debug!(target: LOG_TARGET, "fatp::dropped_monitor_task: terminated..."); break; }; - log::trace!(target: LOG_TARGET, "[{:?}] fatp::dropped notification, removing", dropped); - mempool.remove_dropped_transactions(&[dropped]).await; - import_notification_sink.clean_notified_items(&[dropped]); + let dropped_tx_hash = dropped.tx_hash; + log::trace!(target: LOG_TARGET, "[{:?}] fatp::dropped notification {:?}, removing", dropped_tx_hash,dropped.reason); + match dropped.reason { + DroppedReason::Usurped(new_tx_hash) => { + if let Some(new_tx) = mempool.get_by_hash(new_tx_hash) { + view_store + .replace_transaction( + new_tx.source(), + new_tx.tx(), + dropped_tx_hash, + new_tx.is_watched(), + ) + .await; + } else { + log::trace!( + target:LOG_TARGET, + "error: dropped_monitor_task: no entry in mempool for new transaction {:?}", + new_tx_hash, + ); + } + }, + DroppedReason::LimitsEnforced => {}, + }; + + mempool.remove_dropped_transaction(&dropped_tx_hash).await; + view_store.listener.transaction_dropped(dropped); + import_notification_sink.clean_notified_items(&[dropped_tx_hash]); } } @@ -288,9 +324,13 @@ where let (dropped_stream_controller, dropped_stream) = MultiViewDroppedWatcherController::::new(); + + let view_store = + Arc::new(ViewStore::new(pool_api.clone(), listener, dropped_stream_controller)); let dropped_monitor_task = Self::dropped_monitor_task( dropped_stream, mempool.clone(), + view_store.clone(), import_notification_sink.clone(), ); @@ -306,8 +346,8 @@ where Self { mempool, - api: pool_api.clone(), - view_store: Arc::new(ViewStore::new(pool_api, listener, dropped_stream_controller)), + api: pool_api, + view_store, ready_poll: Arc::from(Mutex::from(ReadyPoll::new())), enactment_state: Arc::new(Mutex::new(EnactmentState::new( best_block_hash, @@ -366,6 +406,16 @@ where self.mempool.unwatched_and_watched_count() } + /// Returns a set of future transactions for given block hash. + /// + /// Intended for logging / tests. + pub fn futures_at( + &self, + at: Block::Hash, + ) -> Option, ExtrinsicFor>>> { + self.view_store.futures_at(at) + } + /// Returns a best-effort set of ready transactions for a given block, without executing full /// maintain process. /// @@ -600,31 +650,33 @@ where let mempool_results = self.mempool.extend_unwatched(source, &xts); if view_store.is_empty() { - return Ok(mempool_results) + return Ok(mempool_results.into_iter().map(|r| r.map(|r| r.hash)).collect::>()) } let to_be_submitted = mempool_results .iter() .zip(xts) - .filter_map(|(result, xt)| result.as_ref().ok().map(|_| xt)) + .filter_map(|(result, xt)| { + result.as_ref().ok().map(|insertion| (insertion.source.clone(), xt)) + }) .collect::>(); self.metrics .report(|metrics| metrics.submitted_transactions.inc_by(to_be_submitted.len() as _)); let mempool = self.mempool.clone(); - let results_map = view_store.submit(source, to_be_submitted.into_iter()).await; + let results_map = view_store.submit(to_be_submitted.into_iter()).await; let mut submission_results = reduce_multiview_result(results_map).into_iter(); Ok(mempool_results .into_iter() .map(|result| { - result.and_then(|xt_hash| { + result.and_then(|insertion| { submission_results .next() .expect("The number of Ok results in mempool is exactly the same as the size of to-views-submission result. qed.") .inspect_err(|_| - mempool.remove(xt_hash) + mempool.remove(insertion.hash) ) }) }) @@ -660,19 +712,18 @@ where ) -> Result>>, Self::Error> { log::trace!(target: LOG_TARGET, "[{:?}] fatp::submit_and_watch views:{}", self.tx_hash(&xt), self.active_views_count()); let xt = Arc::from(xt); - let xt_hash = match self.mempool.push_watched(source, xt.clone()) { - Ok(xt_hash) => xt_hash, - Err(e) => return Err(e), - }; + let InsertionInfo { hash: xt_hash, source: timed_source } = + match self.mempool.push_watched(source, xt.clone()) { + Ok(result) => result, + Err(e) => return Err(e), + }; self.metrics.report(|metrics| metrics.submitted_transactions.inc()); - let view_store = self.view_store.clone(); - let mempool = self.mempool.clone(); - view_store - .submit_and_watch(at, source, xt) + self.view_store + .submit_and_watch(at, timed_source, xt) .await - .inspect_err(|_| mempool.remove(xt_hash)) + .inspect_err(|_| self.mempool.remove(xt_hash)) } /// Intended to remove transactions identified by the given hashes, and any dependent @@ -801,12 +852,12 @@ where ) -> Result { log::debug!(target: LOG_TARGET, "fatp::submit_local views:{}", self.active_views_count()); let xt = Arc::from(xt); - let result = self + let InsertionInfo { hash: xt_hash, .. } = self .mempool .extend_unwatched(TransactionSource::Local, &[xt.clone()]) .remove(0)?; - self.view_store.submit_local(xt).or_else(|_| Ok(result)) + self.view_store.submit_local(xt).or_else(|_| Ok(xt_hash)) } } @@ -914,6 +965,9 @@ where let start = Instant::now(); let watched_xts = self.register_listeners(&mut view).await; let duration = start.elapsed(); + // sync the transactions statuses and referencing views in all the listeners with newly + // cloned view. + view.pool.validated_pool().retrigger_notifications(); log::debug!(target: LOG_TARGET, "register_listeners: at {at:?} took {duration:?}"); // 2. Handle transactions from the tree route. Pruning transactions from the view first @@ -1041,58 +1095,35 @@ where self.active_views_count() ); let included_xts = self.extrinsics_included_since_finalized(view.at.hash).await; - let xts = self.mempool.clone_unwatched(); - - let mut all_submitted_count = 0; - if !xts.is_empty() { - let unwatched_count = xts.len(); - let mut buckets = HashMap::>>::default(); - xts.into_iter() - .filter(|(hash, _)| !view.pool.validated_pool().pool.read().is_imported(hash)) - .filter(|(hash, _)| !included_xts.contains(&hash)) - .map(|(_, tx)| (tx.source(), tx.tx())) - .for_each(|(source, tx)| buckets.entry(source).or_default().push(tx)); - - for (source, xts) in buckets { - all_submitted_count += xts.len(); - let _ = view.submit_many(source, xts).await; - } - log::debug!(target: LOG_TARGET, "update_view_with_mempool: at {:?} unwatched {}/{}", view.at.hash, all_submitted_count, unwatched_count); - } - - let watched_submitted_count = watched_xts.len(); - let mut buckets = HashMap::< - TransactionSource, - Vec<(ExtrinsicHash, ExtrinsicFor)>, - >::default(); - watched_xts + let (hashes, xts_filtered): (Vec<_>, Vec<_>) = watched_xts .into_iter() + .chain(self.mempool.clone_unwatched().into_iter()) + .filter(|(hash, _)| !view.is_imported(hash)) .filter(|(hash, _)| !included_xts.contains(&hash)) - .map(|(tx_hash, tx)| (tx.source(), tx_hash, tx.tx())) - .for_each(|(source, tx_hash, tx)| { - buckets.entry(source).or_default().push((tx_hash, tx)) - }); + .map(|(tx_hash, tx)| (tx_hash, (tx.source(), tx.tx()))) + .unzip(); - let mut watched_results = Vec::default(); - for (source, watched_xts) in buckets { - let hashes = watched_xts.iter().map(|i| i.0).collect::>(); - let results = view - .submit_many(source, watched_xts.into_iter().map(|i| i.1)) - .await - .into_iter() - .zip(hashes) - .map(|(result, tx_hash)| result.or_else(|_| Err(tx_hash))) - .collect::>(); - watched_results.extend(results); - } + let watched_results = view + .submit_many(xts_filtered) + .await + .into_iter() + .zip(hashes) + .map(|(result, tx_hash)| result.or_else(|_| Err(tx_hash))) + .collect::>(); + + let submitted_count = watched_results.len(); - log::debug!(target: LOG_TARGET, "update_view_with_mempool: at {:?} watched {}/{}", view.at.hash, watched_submitted_count, self.mempool_len().1); + log::debug!( + target: LOG_TARGET, + "update_view_with_mempool: at {:?} submitted {}/{}", + view.at.hash, + submitted_count, + self.mempool.len() + ); - all_submitted_count += watched_submitted_count; - let _ = all_submitted_count - .try_into() - .map(|v| self.metrics.report(|metrics| metrics.submitted_from_mempool_txs.inc_by(v))); + self.metrics + .report(|metrics| metrics.submitted_from_mempool_txs.inc_by(submitted_count as _)); // if there are no views yet, and a single newly created view is reporting error, just send // out the invalid event, and remove transaction. @@ -1176,7 +1207,14 @@ where }) .map(|(tx_hash, tx)| { //find arc if tx is known - self.mempool.get_by_hash(tx_hash).unwrap_or_else(|| Arc::from(tx)) + self.mempool + .get_by_hash(tx_hash) + .map(|tx| (tx.source(), tx.tx())) + .unwrap_or_else(|| { + // These transactions are coming from retracted blocks, we + // should simply consider them external. + (TimedTransactionSource::new_external(true), Arc::from(tx)) + }) }), ); @@ -1185,16 +1223,7 @@ where }); } - let _ = view - .pool - .resubmit_at( - &hash_and_number, - // These transactions are coming from retracted blocks, we should - // simply consider them external. - TransactionSource::External, - resubmit_transactions, - ) - .await; + let _ = view.pool.resubmit_at(&hash_and_number, resubmit_transactions).await; } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs index 7fbdcade63b8..f9a41673bb8f 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/import_notification_sink.rs @@ -326,6 +326,7 @@ mod tests { let j0 = tokio::spawn(runnable); let stream = ctrl.event_stream(); + let stream2 = ctrl.event_stream(); let mut v1 = View::new(vec![(10, 1), (10, 2), (10, 3)]); let mut v2 = View::new(vec![(20, 1), (20, 2), (20, 6)]); @@ -342,20 +343,16 @@ mod tests { ctrl.add_view(1000, o1); ctrl.add_view(2000, o2); - let j4 = { - let ctrl = ctrl.clone(); - tokio::spawn(async move { - tokio::time::sleep(Duration::from_millis(70)).await; - ctrl.clean_notified_items(&vec![1, 3]); - ctrl.add_view(3000, o3.boxed()); - }) - }; + let out = stream.take(4).collect::>().await; + assert_eq!(out, vec![1, 2, 3, 6]); - let out = stream.take(6).collect::>().await; + ctrl.clean_notified_items(&vec![1, 3]); + ctrl.add_view(3000, o3.boxed()); + let out = stream2.take(6).collect::>().await; assert_eq!(out, vec![1, 2, 3, 6, 1, 3]); - drop(ctrl); - futures::future::join_all(vec![j0, j1, j2, j3, j4]).await; + drop(ctrl); + futures::future::join_all(vec![j0, j1, j2, j3]).await; } #[tokio::test] diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs index 8d0e69db2e9a..a00234a99808 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/multi_view_listener.rs @@ -36,6 +36,8 @@ use std::{ }; use tokio_stream::StreamMap; +use super::dropped_watcher::{DroppedReason, DroppedTransaction}; + /// A side channel allowing to control the external stream instance (one per transaction) with /// [`ControllerCommand`]. /// @@ -79,7 +81,7 @@ enum ControllerCommand { /// Notifies that a transaction was dropped from the pool. /// /// If all preconditions are met, an external dropped event will be sent out. - TransactionDropped, + TransactionDropped(DroppedReason>), } impl std::fmt::Debug for ControllerCommand @@ -99,8 +101,8 @@ where ControllerCommand::TransactionBroadcasted(_) => { write!(f, "ListenerAction::TransactionBroadcasted(...)") }, - ControllerCommand::TransactionDropped => { - write!(f, "ListenerAction::TransactionDropped") + ControllerCommand::TransactionDropped(r) => { + write!(f, "ListenerAction::TransactionDropped {r:?}") }, } } @@ -268,6 +270,7 @@ where /// stream map. fn remove_view(&mut self, block_hash: BlockHash) { self.status_stream_map.remove(&block_hash); + self.views_keeping_tx_valid.remove(&block_hash); trace!(target: LOG_TARGET, "[{:?}] RemoveView view: {:?} views:{:?}", self.tx_hash, block_hash, self.status_stream_map.keys().collect::>()); } } @@ -282,6 +285,11 @@ where Self { controllers: Default::default() } } + /// Returns `true` if the listener contains a stream controller for the specified hash. + pub fn contains_tx(&self, tx_hash: &ExtrinsicHash) -> bool { + self.controllers.read().contains_key(tx_hash) + } + /// Creates an external aggregated stream of events for given transaction. /// /// This method initializes an `ExternalWatcherContext` for the provided transaction hash, sets @@ -346,11 +354,16 @@ where log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Broadcasted", ctx.tx_hash); return Some((TransactionStatus::Broadcast(peers), ctx)) }, - ControllerCommand::TransactionDropped => { + ControllerCommand::TransactionDropped(DroppedReason::LimitsEnforced) => { log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Dropped", ctx.tx_hash); ctx.terminate = true; return Some((TransactionStatus::Dropped, ctx)) }, + ControllerCommand::TransactionDropped(DroppedReason::Usurped(by)) => { + log::trace!(target: LOG_TARGET, "[{:?}] mvl sending out: Usurped({:?})", ctx.tx_hash, by); + ctx.terminate = true; + return Some((TransactionStatus::Usurped(by), ctx)) + }, } }, }; @@ -445,16 +458,15 @@ where /// /// This method sends a `TransactionDropped` command to the controller of each requested /// transaction prompting and external `Broadcasted` event. - pub(crate) fn transactions_dropped(&self, dropped: &[ExtrinsicHash]) { + pub(crate) fn transaction_dropped(&self, dropped: DroppedTransaction>) { let mut controllers = self.controllers.write(); - debug!(target: LOG_TARGET, "mvl::transactions_dropped: {:?}", dropped); - for tx_hash in dropped { - if let Some(tx) = controllers.remove(&tx_hash) { - debug!(target: LOG_TARGET, "[{:?}] transaction_dropped", tx_hash); - if let Err(e) = tx.unbounded_send(ControllerCommand::TransactionDropped) { - trace!(target: LOG_TARGET, "[{:?}] transactions_dropped: send message failed: {:?}", tx_hash, e); - }; - } + debug!(target: LOG_TARGET, "mvl::transaction_dropped: {:?}", dropped); + if let Some(tx) = controllers.remove(&dropped.tx_hash) { + let DroppedTransaction { tx_hash, reason } = dropped; + debug!(target: LOG_TARGET, "[{:?}] transaction_dropped", tx_hash); + if let Err(e) = tx.unbounded_send(ControllerCommand::TransactionDropped(reason)) { + trace!(target: LOG_TARGET, "[{:?}] transaction_dropped: send message failed: {:?}", tx_hash, e); + }; } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs index 9464ab3f5766..eb898c35a134 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs @@ -186,9 +186,9 @@ mod tests { use crate::{ common::tests::{uxt, TestApi}, fork_aware_txpool::view::FinishRevalidationLocalChannels, + TimedTransactionSource, }; use futures::executor::block_on; - use sc_transaction_pool_api::TransactionSource; use substrate_test_runtime::{AccountId, Transfer, H256}; use substrate_test_runtime_client::AccountKeyring::Alice; #[test] @@ -212,9 +212,10 @@ mod tests { nonce: 0, }); - let _ = block_on( - view.submit_many(TransactionSource::External, std::iter::once(uxt.clone().into())), - ); + let _ = block_on(view.submit_many(std::iter::once(( + TimedTransactionSource::new_external(false), + uxt.clone().into(), + )))); assert_eq!(api.validation_requests().len(), 1); let (finish_revalidation_request_tx, finish_revalidation_request_rx) = diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index 86c07008c3f3..7b824d4653c2 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -30,7 +30,7 @@ use super::{metrics::MetricsLink as PrometheusMetrics, multi_view_listener::Mult use crate::{ common::log_xt::log_xt_trace, graph, - graph::{tracked_map::Size, ExtrinsicFor, ExtrinsicHash}, + graph::{base_pool::TimedTransactionSource, tracked_map::Size, ExtrinsicFor, ExtrinsicHash}, LOG_TARGET, }; use futures::FutureExt; @@ -74,7 +74,7 @@ where /// Size of the extrinsics actual body. bytes: usize, /// Transaction source. - source: TransactionSource, + source: TimedTransactionSource, /// When the transaction was revalidated, used to periodically revalidate the mem pool buffer. validated_at: AtomicU64, //todo: we need to add future / ready status at finalized block. @@ -95,18 +95,30 @@ where /// Shall the progress of transaction be watched. /// /// Was transaction sent with `submit_and_watch`. - fn is_watched(&self) -> bool { + pub(crate) fn is_watched(&self) -> bool { self.watched } /// Creates a new instance of wrapper for unwatched transaction. fn new_unwatched(source: TransactionSource, tx: ExtrinsicFor, bytes: usize) -> Self { - Self { watched: false, tx, source, validated_at: AtomicU64::new(0), bytes } + Self { + watched: false, + tx, + source: TimedTransactionSource::from_transaction_source(source, true), + validated_at: AtomicU64::new(0), + bytes, + } } /// Creates a new instance of wrapper for watched transaction. fn new_watched(source: TransactionSource, tx: ExtrinsicFor, bytes: usize) -> Self { - Self { watched: true, tx, source, validated_at: AtomicU64::new(0), bytes } + Self { + watched: true, + tx, + source: TimedTransactionSource::from_transaction_source(source, true), + validated_at: AtomicU64::new(0), + bytes, + } } /// Provides a clone of actual transaction body. @@ -117,8 +129,8 @@ where } /// Returns the source of the transaction. - pub(crate) fn source(&self) -> TransactionSource { - self.source + pub(crate) fn source(&self) -> TimedTransactionSource { + self.source.clone() } } @@ -174,6 +186,19 @@ where max_transactions_total_bytes: usize, } +/// Helper structure to encapsulate a result of [`TxMemPool::try_insert`]. +#[derive(Debug)] +pub(super) struct InsertionInfo { + pub(super) hash: Hash, + pub(super) source: TimedTransactionSource, +} + +impl InsertionInfo { + fn new(hash: Hash, source: TimedTransactionSource) -> Self { + Self { hash, source } + } +} + impl TxMemPool where Block: BlockT, @@ -220,8 +245,8 @@ where pub(super) fn get_by_hash( &self, hash: ExtrinsicHash, - ) -> Option> { - self.transactions.read().get(&hash).map(|t| t.tx()) + ) -> Option>> { + self.transactions.read().get(&hash).map(Clone::clone) } /// Returns a tuple with the count of unwatched and watched transactions in the memory pool. @@ -231,6 +256,11 @@ where (transactions.len() - watched_count, watched_count) } + /// Returns a total number of transactions kept within mempool. + pub fn len(&self) -> usize { + self.transactions.read().len() + } + /// Returns the number of bytes used by all extrinsics in the the pool. #[cfg(test)] pub fn bytes(&self) -> usize { @@ -249,7 +279,7 @@ where &self, hash: ExtrinsicHash, tx: TxInMemPool, - ) -> Result, ChainApi::Error> { + ) -> Result>, ChainApi::Error> { let bytes = self.transactions.bytes(); let mut transactions = self.transactions.write(); let result = match ( @@ -257,14 +287,15 @@ where transactions.contains_key(&hash), ) { (true, false) => { + let source = tx.source(); transactions.insert(hash, Arc::from(tx)); - Ok(hash) + Ok(InsertionInfo::new(hash, source)) }, (_, true) => Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash)).into()), (false, _) => Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped.into()), }; - log::trace!(target: LOG_TARGET, "[{:?}] mempool::try_insert: {:?}", hash, result); + log::trace!(target: LOG_TARGET, "[{:?}] mempool::try_insert: {:?}", hash, result.as_ref().map(|r| r.hash)); result } @@ -277,7 +308,7 @@ where &self, source: TransactionSource, xts: &[ExtrinsicFor], - ) -> Vec, ChainApi::Error>> { + ) -> Vec>, ChainApi::Error>> { let result = xts .iter() .map(|xt| { @@ -294,25 +325,18 @@ where &self, source: TransactionSource, xt: ExtrinsicFor, - ) -> Result, ChainApi::Error> { + ) -> Result>, ChainApi::Error> { let (hash, length) = self.api.hash_and_length(&xt); self.try_insert(hash, TxInMemPool::new_watched(source, xt.clone(), length)) } - /// Removes transactions from the memory pool which are specified by the given list of hashes - /// and send the `Dropped` event to the listeners of these transactions. - pub(super) async fn remove_dropped_transactions( + /// Removes transaction from the memory pool which are specified by the given list of hashes. + pub(super) async fn remove_dropped_transaction( &self, - to_be_removed: &[ExtrinsicHash], - ) { - log::debug!(target: LOG_TARGET, "remove_dropped_transactions count:{:?}", to_be_removed.len()); - log_xt_trace!(target: LOG_TARGET, to_be_removed, "[{:?}] mempool::remove_dropped_transactions"); - let mut transactions = self.transactions.write(); - to_be_removed.iter().for_each(|t| { - transactions.remove(t); - }); - - self.listener.transactions_dropped(to_be_removed); + dropped: &ExtrinsicHash, + ) -> Option>> { + log::debug!(target: LOG_TARGET, "[{:?}] mempool::remove_dropped_transaction", dropped); + self.transactions.write().remove(dropped) } /// Clones and returns a `HashMap` of references to all unwatched transactions in the memory @@ -369,13 +393,13 @@ where }; let validations_futures = input.into_iter().map(|(xt_hash, xt)| { - self.api.validate_transaction(finalized_block.hash, xt.source, xt.tx()).map( - move |validation_result| { + self.api + .validate_transaction(finalized_block.hash, xt.source.clone().into(), xt.tx()) + .map(move |validation_result| { xt.validated_at .store(finalized_block.number.into().as_u64(), atomic::Ordering::Relaxed); (xt_hash, validation_result) - }, - ) + }) }); let validation_results = futures::future::join_all(validations_futures).await; let input_len = validation_results.len(); @@ -403,7 +427,7 @@ where log::debug!( target: LOG_TARGET, - "mempool::revalidate: at {finalized_block:?} count:{input_len}/{count} purged:{} took {duration:?}", invalid_hashes.len(), + "mempool::revalidate: at {finalized_block:?} count:{input_len}/{count} invalid_hashes:{} took {duration:?}", invalid_hashes.len(), ); invalid_hashes diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs index 99095d88cb0a..3cbb8fa4871d 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -27,13 +27,13 @@ use super::metrics::MetricsLink as PrometheusMetrics; use crate::{ common::log_xt::log_xt_trace, graph::{ - self, watcher::Watcher, ExtrinsicFor, ExtrinsicHash, IsValidator, ValidatedTransaction, - ValidatedTransactionFor, + self, base_pool::TimedTransactionSource, watcher::Watcher, ExtrinsicFor, ExtrinsicHash, + IsValidator, ValidatedTransaction, ValidatedTransactionFor, }, LOG_TARGET, }; use parking_lot::Mutex; -use sc_transaction_pool_api::{error::Error as TxPoolError, PoolStatus, TransactionSource}; +use sc_transaction_pool_api::{error::Error as TxPoolError, PoolStatus}; use sp_blockchain::HashAndNumber; use sp_runtime::{ generic::BlockId, traits::Block as BlockT, transaction_validity::TransactionValidityError, @@ -157,22 +157,21 @@ where /// Imports many unvalidated extrinsics into the view. pub(super) async fn submit_many( &self, - source: TransactionSource, - xts: impl IntoIterator>, + xts: impl IntoIterator)>, ) -> Vec, ChainApi::Error>> { if log::log_enabled!(target: LOG_TARGET, log::Level::Trace) { let xts = xts.into_iter().collect::>(); - log_xt_trace!(target: LOG_TARGET, xts.iter().map(|xt| self.pool.validated_pool().api().hash_and_length(xt).0), "[{:?}] view::submit_many at:{}", self.at.hash); - self.pool.submit_at(&self.at, source, xts).await + log_xt_trace!(target: LOG_TARGET, xts.iter().map(|(_,xt)| self.pool.validated_pool().api().hash_and_length(xt).0), "[{:?}] view::submit_many at:{}", self.at.hash); + self.pool.submit_at(&self.at, xts).await } else { - self.pool.submit_at(&self.at, source, xts).await + self.pool.submit_at(&self.at, xts).await } } /// Import a single extrinsic and starts to watch its progress in the view. pub(super) async fn submit_and_watch( &self, - source: TransactionSource, + source: TimedTransactionSource, xt: ExtrinsicFor, ) -> Result, ExtrinsicHash>, ChainApi::Error> { log::trace!(target: LOG_TARGET, "[{:?}] view::submit_and_watch at:{}", self.pool.validated_pool().api().hash_and_length(&xt).0, self.at.hash); @@ -193,7 +192,7 @@ where .api() .validate_transaction_blocking( self.at.hash, - TransactionSource::Local, + sc_transaction_pool_api::TransactionSource::Local, Arc::from(xt.clone()), )? .map_err(|e| { @@ -214,7 +213,7 @@ where let validated = ValidatedTransaction::valid_at( block_number.saturated_into::(), hash, - TransactionSource::Local, + TimedTransactionSource::new_local(true), Arc::from(xt), length, validity, @@ -285,7 +284,7 @@ where } _ = async { if let Some(tx) = batch_iter.next() { - let validation_result = (api.validate_transaction(self.at.hash, tx.source, tx.data.clone()).await, tx.hash, tx); + let validation_result = (api.validate_transaction(self.at.hash, tx.source.clone().into(), tx.data.clone()).await, tx.hash, tx); validation_results.push(validation_result); } else { self.revalidation_worker_channels.lock().as_mut().map(|ch| ch.remove_sender()); @@ -324,7 +323,7 @@ where ValidatedTransaction::valid_at( self.at.number.saturated_into::(), tx_hash, - tx.source, + tx.source.clone(), tx.data.clone(), api.hash_and_length(&tx.data).1, validity, @@ -455,4 +454,10 @@ where ); } } + + /// Returns true if the transaction with given hash is already imported into the view. + pub(super) fn is_imported(&self, tx_hash: &ExtrinsicHash) -> bool { + const IGNORE_BANNED: bool = false; + self.pool.validated_pool().check_is_known(tx_hash, IGNORE_BANNED).is_err() + } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs index f23dcedd5bfd..a06c051f0a7e 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs @@ -24,17 +24,51 @@ use super::{ }; use crate::{ fork_aware_txpool::dropped_watcher::MultiViewDroppedWatcherController, - graph, - graph::{base_pool::Transaction, ExtrinsicFor, ExtrinsicHash, TransactionFor}, + graph::{ + self, + base_pool::{TimedTransactionSource, Transaction}, + ExtrinsicFor, ExtrinsicHash, TransactionFor, + }, ReadyIteratorFor, LOG_TARGET, }; use futures::prelude::*; use itertools::Itertools; use parking_lot::RwLock; -use sc_transaction_pool_api::{error::Error as PoolError, PoolStatus, TransactionSource}; +use sc_transaction_pool_api::{error::Error as PoolError, PoolStatus}; use sp_blockchain::TreeRoute; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; -use std::{collections::HashMap, sync::Arc, time::Instant}; +use std::{ + collections::{hash_map::Entry, HashMap}, + sync::Arc, + time::Instant, +}; + +/// Helper struct to keep the context for transaction replacements. +#[derive(Clone)] +struct PendingTxReplacement +where + ChainApi: graph::ChainApi, +{ + /// Indicates if the new transaction was already submitted to all the views in the view_store. + /// If true, it can be removed after inserting any new view. + processed: bool, + /// New transaction replacing the old one. + xt: ExtrinsicFor, + /// Source of the transaction. + source: TimedTransactionSource, + /// Inidicates if transaction is watched. + watched: bool, +} + +impl PendingTxReplacement +where + ChainApi: graph::ChainApi, +{ + /// Creates new unprocessed instance of pending transaction replacement. + fn new(xt: ExtrinsicFor, source: TimedTransactionSource, watched: bool) -> Self { + Self { processed: false, xt, source, watched } + } +} /// The helper structure encapsulates all the views. pub(super) struct ViewStore @@ -62,6 +96,13 @@ where pub(super) most_recent_view: RwLock>, /// The controller of multi view dropped stream. pub(super) dropped_stream_controller: MultiViewDroppedWatcherController, + /// The map used to synchronize replacement of transactions between maintain and dropped + /// notifcication threads. It is meant to assure that replaced transaction is also removed from + /// newly built views in maintain process. + /// + /// The map's key is hash of replaced extrinsic. + pending_txs_replacements: + RwLock, PendingTxReplacement>>, } impl ViewStore @@ -83,14 +124,14 @@ where listener, most_recent_view: RwLock::from(None), dropped_stream_controller, + pending_txs_replacements: Default::default(), } } /// Imports a bunch of unverified extrinsics to every active view. pub(super) async fn submit( &self, - source: TransactionSource, - xts: impl IntoIterator> + Clone, + xts: impl IntoIterator)> + Clone, ) -> HashMap, ChainApi::Error>>> { let submit_futures = { let active_views = self.active_views.read(); @@ -99,7 +140,7 @@ where .map(|(_, view)| { let view = view.clone(); let xts = xts.clone(); - async move { (view.at.hash, view.submit_many(source, xts).await) } + async move { (view.at.hash, view.submit_many(xts).await) } }) .collect::>() }; @@ -145,7 +186,7 @@ where pub(super) async fn submit_and_watch( &self, _at: Block::Hash, - source: TransactionSource, + source: TimedTransactionSource, xt: ExtrinsicFor, ) -> Result, ChainApi::Error> { let tx_hash = self.api.hash_and_length(&xt).0; @@ -159,6 +200,7 @@ where .map(|(_, view)| { let view = view.clone(); let xt = xt.clone(); + let source = source.clone(); async move { match view.submit_and_watch(source, xt).await { Ok(watcher) => { @@ -261,12 +303,20 @@ where ) -> Vec, ExtrinsicFor>> { self.most_recent_view .read() - .map(|at| self.get_view_at(at, true)) + .map(|at| self.futures_at(at)) .flatten() - .map(|(v, _)| v.pool.validated_pool().pool.read().futures().cloned().collect()) .unwrap_or_default() } + /// Returns a list of future transactions in the view at given block hash. + pub(super) fn futures_at( + &self, + at: Block::Hash, + ) -> Option, ExtrinsicFor>>> { + self.get_view_at(at, true) + .map(|(v, _)| v.pool.validated_pool().pool.read().futures().cloned().collect()) + } + /// Collects all the transactions included in the blocks on the provided `tree_route` and /// triggers finalization event for them. /// @@ -329,12 +379,16 @@ where /// - moved to the inactive views set (`inactive_views`), /// - removed from the multi view listeners. /// - /// The `most_recent_view` is update with the reference to the newly inserted view. + /// The `most_recent_view` is updated with the reference to the newly inserted view. + /// + /// If there are any pending tx replacments, they are applied to the new view. pub(super) async fn insert_new_view( &self, view: Arc>, tree_route: &TreeRoute, ) { + self.apply_pending_tx_replacements(view.clone()).await; + //note: most_recent_view must be synced with changes in in/active_views. { let mut most_recent_view_lock = self.most_recent_view.write(); @@ -386,8 +440,10 @@ where let mut removed_views = vec![]; { - self.active_views - .read() + let active_views = self.active_views.read(); + let inactive_views = self.inactive_views.read(); + + active_views .iter() .filter(|(hash, v)| !match finalized_number { Err(_) | Ok(None) => **hash == finalized_hash, @@ -396,11 +452,8 @@ where }) .map(|(_, v)| removed_views.push(v.at.hash)) .for_each(drop); - } - { - self.inactive_views - .read() + inactive_views .iter() .filter(|(_, v)| !match finalized_number { Err(_) | Ok(None) => false, @@ -438,30 +491,48 @@ where let finalized_xts = self.finalize_route(finalized_hash, tree_route).await; let finalized_number = self.api.block_id_to_number(&BlockId::Hash(finalized_hash)); + let mut dropped_views = vec![]; //clean up older then finalized { let mut active_views = self.active_views.write(); - active_views.retain(|hash, v| match finalized_number { - Err(_) | Ok(None) => *hash == finalized_hash, - Ok(Some(n)) if v.at.number == n => *hash == finalized_hash, - Ok(Some(n)) => v.at.number > n, + let mut inactive_views = self.inactive_views.write(); + active_views.retain(|hash, v| { + let retain = match finalized_number { + Err(_) | Ok(None) => *hash == finalized_hash, + Ok(Some(n)) if v.at.number == n => *hash == finalized_hash, + Ok(Some(n)) => v.at.number > n, + }; + if !retain { + dropped_views.push(*hash); + } + retain }); - } - { - let mut inactive_views = self.inactive_views.write(); - inactive_views.retain(|_, v| match finalized_number { - Err(_) | Ok(None) => false, - Ok(Some(n)) => v.at.number >= n, + inactive_views.retain(|hash, v| { + let retain = match finalized_number { + Err(_) | Ok(None) => false, + Ok(Some(n)) => v.at.number >= n, + }; + if !retain { + dropped_views.push(*hash); + } + retain }); log::trace!(target:LOG_TARGET,"handle_finalized: inactive_views: {:?}", inactive_views.keys()); } - self.listener.remove_view(finalized_hash); + log::trace!(target:LOG_TARGET,"handle_finalized: dropped_views: {:?}", dropped_views); + self.listener.remove_stale_controllers(); self.dropped_stream_controller.remove_finalized_txs(finalized_xts.clone()); + self.listener.remove_view(finalized_hash); + for view in dropped_views { + self.listener.remove_view(view); + self.dropped_stream_controller.remove_view(view); + } + finalized_xts } @@ -484,4 +555,139 @@ where futures::future::join_all(finish_revalidation_futures).await; log::trace!(target:LOG_TARGET,"finish_background_revalidations took {:?}", start.elapsed()); } + + /// Replaces an existing transaction in the view_store with a new one. + /// + /// Attempts to replace a transaction identified by `replaced` with a new transaction `xt`. + /// + /// Before submitting a transaction to the views, the new *unprocessed* transaction replacement + /// record will be inserted into a pending replacement map. Once the submission to all the views + /// is accomplished, the record is marked as *processed*. + /// + /// This map is later applied in `insert_new_view` method executed from different thread. + /// + /// If the transaction is already being replaced, it will simply return without making + /// changes. + pub(super) async fn replace_transaction( + &self, + source: TimedTransactionSource, + xt: ExtrinsicFor, + replaced: ExtrinsicHash, + watched: bool, + ) { + if let Entry::Vacant(entry) = self.pending_txs_replacements.write().entry(replaced) { + entry.insert(PendingTxReplacement::new(xt.clone(), source.clone(), watched)); + } else { + return + }; + + let xt_hash = self.api.hash_and_length(&xt).0; + log::trace!(target:LOG_TARGET,"[{replaced:?}] replace_transaction wtih {xt_hash:?}, w:{watched}"); + + self.replace_transaction_in_views(source, xt, xt_hash, replaced, watched).await; + + if let Some(replacement) = self.pending_txs_replacements.write().get_mut(&replaced) { + replacement.processed = true; + } + } + + /// Applies pending transaction replacements to the specified view. + /// + /// After application, all already processed replacements are removed. + async fn apply_pending_tx_replacements(&self, view: Arc>) { + let mut futures = vec![]; + for replacement in self.pending_txs_replacements.read().values() { + let xt_hash = self.api.hash_and_length(&replacement.xt).0; + futures.push(self.replace_transaction_in_view( + view.clone(), + replacement.source.clone(), + replacement.xt.clone(), + xt_hash, + replacement.watched, + )); + } + let _results = futures::future::join_all(futures).await; + self.pending_txs_replacements.write().retain(|_, r| r.processed); + } + + /// Submits `xt` to the given view. + /// + /// For watched transaction stream is added to the listener. + async fn replace_transaction_in_view( + &self, + view: Arc>, + source: TimedTransactionSource, + xt: ExtrinsicFor, + xt_hash: ExtrinsicHash, + watched: bool, + ) { + if watched { + match view.submit_and_watch(source, xt).await { + Ok(watcher) => { + self.listener.add_view_watcher_for_tx( + xt_hash, + view.at.hash, + watcher.into_stream().boxed(), + ); + }, + Err(e) => { + log::trace!( + target:LOG_TARGET, + "[{:?}] replace_transaction: submit_and_watch to {} failed {}", + xt_hash, view.at.hash, e + ); + }, + } + } else { + if let Some(Err(e)) = view.submit_many(std::iter::once((source, xt))).await.pop() { + log::trace!( + target:LOG_TARGET, + "[{:?}] replace_transaction: submit to {} failed {}", + xt_hash, view.at.hash, e + ); + } + } + } + + /// Sends `xt` to every view (both active and inactive) containing `replaced` extrinsics. + /// + /// It is assumed that transaction is already known by the pool. Intended to ba called when `xt` + /// is replacing `replaced` extrinsic. + async fn replace_transaction_in_views( + &self, + source: TimedTransactionSource, + xt: ExtrinsicFor, + xt_hash: ExtrinsicHash, + replaced: ExtrinsicHash, + watched: bool, + ) { + if watched && !self.listener.contains_tx(&xt_hash) { + log::trace!( + target:LOG_TARGET, + "error: replace_transaction_in_views: no listener for watched transaction {:?}", + xt_hash, + ); + return; + } + + let submit_futures = { + let active_views = self.active_views.read(); + let inactive_views = self.inactive_views.read(); + active_views + .iter() + .chain(inactive_views.iter()) + .filter(|(_, view)| view.is_imported(&replaced)) + .map(|(_, view)| { + self.replace_transaction_in_view( + view.clone(), + source.clone(), + xt.clone(), + xt_hash, + watched, + ) + }) + .collect::>() + }; + let _results = futures::future::join_all(submit_futures).await; + } } diff --git a/substrate/client/transaction-pool/src/graph/base_pool.rs b/substrate/client/transaction-pool/src/graph/base_pool.rs index e4c3a6c425a9..04eaa998f42e 100644 --- a/substrate/client/transaction-pool/src/graph/base_pool.rs +++ b/substrate/client/transaction-pool/src/graph/base_pool.rs @@ -20,7 +20,7 @@ //! //! For a more full-featured pool, have a look at the `pool` module. -use std::{cmp::Ordering, collections::HashSet, fmt, hash, sync::Arc}; +use std::{cmp::Ordering, collections::HashSet, fmt, hash, sync::Arc, time::Instant}; use crate::LOG_TARGET; use log::{trace, warn}; @@ -30,8 +30,8 @@ use sp_core::hexdisplay::HexDisplay; use sp_runtime::{ traits::Member, transaction_validity::{ - TransactionLongevity as Longevity, TransactionPriority as Priority, - TransactionSource as Source, TransactionTag as Tag, + TransactionLongevity as Longevity, TransactionPriority as Priority, TransactionSource, + TransactionTag as Tag, }, }; @@ -83,6 +83,44 @@ pub struct PruneStatus { pub pruned: Vec>>, } +/// A transaction source that includes a timestamp indicating when the transaction was submitted. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TimedTransactionSource { + /// The original source of the transaction. + pub source: TransactionSource, + + /// The time at which the transaction was submitted. + pub timestamp: Option, +} + +impl From for TransactionSource { + fn from(value: TimedTransactionSource) -> Self { + value.source + } +} + +impl TimedTransactionSource { + /// Creates a new instance with an internal `TransactionSource::InBlock` source and an optional + /// timestamp. + pub fn new_in_block(with_timestamp: bool) -> Self { + Self { source: TransactionSource::InBlock, timestamp: with_timestamp.then(Instant::now) } + } + /// Creates a new instance with an internal `TransactionSource::External` source and an optional + /// timestamp. + pub fn new_external(with_timestamp: bool) -> Self { + Self { source: TransactionSource::External, timestamp: with_timestamp.then(Instant::now) } + } + /// Creates a new instance with an internal `TransactionSource::Local` source and an optional + /// timestamp. + pub fn new_local(with_timestamp: bool) -> Self { + Self { source: TransactionSource::Local, timestamp: with_timestamp.then(Instant::now) } + } + /// Creates a new instance with an given source and an optional timestamp. + pub fn from_transaction_source(source: TransactionSource, with_timestamp: bool) -> Self { + Self { source, timestamp: with_timestamp.then(Instant::now) } + } +} + /// Immutable transaction #[derive(PartialEq, Eq, Clone)] pub struct Transaction { @@ -102,8 +140,8 @@ pub struct Transaction { pub provides: Vec, /// Should that transaction be propagated. pub propagate: bool, - /// Source of that transaction. - pub source: Source, + /// Timed source of that transaction. + pub source: TimedTransactionSource, } impl AsRef for Transaction { @@ -157,7 +195,7 @@ impl Transaction { bytes: self.bytes, hash: self.hash.clone(), priority: self.priority, - source: self.source, + source: self.source.clone(), valid_till: self.valid_till, requires: self.requires.clone(), provides: self.provides.clone(), @@ -322,22 +360,36 @@ impl BasePool { if !first { - promoted.push(current_hash); + promoted.push(current_hash.clone()); } + // If there were conflicting future transactions promoted, removed them from + // promoted set. + promoted.retain(|hash| replaced.iter().all(|tx| *hash != tx.hash)); // The transactions were removed from the ready pool. We might attempt to // re-import them. removed.append(&mut replaced); }, + Err(e @ error::Error::TooLowPriority { .. }) => + if first { + trace!(target: LOG_TARGET, "[{:?}] Error importing {first}: {:?}", current_tx.hash, e); + return Err(e) + } else { + trace!(target: LOG_TARGET, "[{:?}] Error importing {first}: {:?}", current_tx.hash, e); + removed.push(current_tx); + promoted.retain(|hash| *hash != current_hash); + }, // transaction failed to be imported. Err(e) => if first { - trace!(target: LOG_TARGET, "[{:?}] Error importing: {:?}", current_hash, e); + trace!(target: LOG_TARGET, "[{:?}] Error importing {first}: {:?}", current_tx.hash, e); return Err(e) } else { - failed.push(current_hash); + trace!(target: LOG_TARGET, "[{:?}] Error importing {first}: {:?}", current_tx.hash, e); + failed.push(current_tx.hash.clone()); }, } first = false; @@ -434,8 +486,24 @@ impl BasePool Some(current.clone()), - Some(ref tx) if tx.imported_at > current.imported_at => Some(current.clone()), - other => other, + Some(worst) => Some( + match (worst.transaction.source.timestamp, current.transaction.source.timestamp) + { + (Some(worst_timestamp), Some(current_timestamp)) => { + if worst_timestamp > current_timestamp { + current.clone() + } else { + worst + } + }, + _ => + if worst.imported_at > current.imported_at { + current.clone() + } else { + worst + }, + }, + ), }); if let Some(worst) = worst { @@ -562,7 +630,7 @@ mod tests { requires: vec![], provides: vec![], propagate: true, - source: Source::External, + source: TimedTransactionSource::new_external(false), } } @@ -760,6 +828,58 @@ mod tests { ); } + #[test] + fn should_remove_conflicting_future() { + let mut pool = pool(); + pool.import(Transaction { + data: vec![3u8].into(), + hash: 3, + requires: vec![vec![1]], + priority: 50u64, + provides: vec![vec![3]], + ..default_tx().clone() + }) + .unwrap(); + assert_eq!(pool.ready().count(), 0); + assert_eq!(pool.ready.len(), 0); + + let tx2 = Transaction { + data: vec![2u8].into(), + hash: 2, + requires: vec![vec![1]], + provides: vec![vec![3]], + ..default_tx().clone() + }; + pool.import(tx2.clone()).unwrap(); + assert_eq!(pool.future.len(), 2); + + let res = pool + .import(Transaction { + data: vec![1u8].into(), + hash: 1, + provides: vec![vec![1]], + ..default_tx().clone() + }) + .unwrap(); + + assert_eq!( + res, + Imported::Ready { + hash: 1, + promoted: vec![3], + failed: vec![], + removed: vec![tx2.into()] + } + ); + + let mut it = pool.ready().into_iter().map(|tx| tx.data[0]); + assert_eq!(it.next(), Some(1)); + assert_eq!(it.next(), Some(3)); + assert_eq!(it.next(), None); + + assert_eq!(pool.future.len(), 0); + } + #[test] fn should_handle_a_cycle() { // given @@ -783,14 +903,14 @@ mod tests { assert_eq!(pool.ready.len(), 0); // when - pool.import(Transaction { + let tx2 = Transaction { data: vec![2u8].into(), hash: 2, requires: vec![vec![2]], provides: vec![vec![0]], ..default_tx().clone() - }) - .unwrap(); + }; + pool.import(tx2.clone()).unwrap(); // then { @@ -817,7 +937,12 @@ mod tests { assert_eq!(it.next(), None); assert_eq!( res, - Imported::Ready { hash: 4, promoted: vec![1, 3], failed: vec![2], removed: vec![] } + Imported::Ready { + hash: 4, + promoted: vec![1, 3], + failed: vec![], + removed: vec![tx2.into()] + } ); assert_eq!(pool.future.len(), 0); } @@ -1024,7 +1149,7 @@ mod tests { ), "Transaction { \ hash: 4, priority: 1000, valid_till: 64, bytes: 1, propagate: true, \ -source: TransactionSource::External, requires: [03, 02], provides: [04], data: [4]}" +source: TimedTransactionSource { source: TransactionSource::External, timestamp: None }, requires: [03, 02], provides: [04], data: [4]}" .to_owned() ); } diff --git a/substrate/client/transaction-pool/src/graph/listener.rs b/substrate/client/transaction-pool/src/graph/listener.rs index a5593920eec4..41daf5491f70 100644 --- a/substrate/client/transaction-pool/src/graph/listener.rs +++ b/substrate/client/transaction-pool/src/graph/listener.rs @@ -36,6 +36,7 @@ pub type DroppedByLimitsStream = TracingUnboundedReceiver { + /// Map containing per-transaction sinks for emitting transaction status events. watchers: HashMap>>, finality_watchers: LinkedHashMap, Vec>, @@ -119,32 +120,44 @@ impl Listener, limits_enforced: bool) { + /// Transaction was dropped from the pool because of enforcing the limit. + pub fn limit_enforced(&mut self, tx: &H) { + trace!(target: LOG_TARGET, "[{:?}] Dropped (limit enforced)", tx); + self.fire(tx, |watcher| watcher.limit_enforced()); + + if let Some(ref sink) = self.dropped_by_limits_sink { + if let Err(e) = sink.unbounded_send((tx.clone(), TransactionStatus::Dropped)) { + trace!(target: LOG_TARGET, "[{:?}] dropped_sink: send message failed: {:?}", tx, e); + } + } + } + + /// Transaction was replaced with other extrinsic. + pub fn usurped(&mut self, tx: &H, by: &H) { trace!(target: LOG_TARGET, "[{:?}] Dropped (replaced with {:?})", tx, by); - self.fire(tx, |watcher| match by { - Some(t) => watcher.usurped(t.clone()), - None => watcher.dropped(), - }); - - //note: LimitEnforced could be introduced as new status to get rid of this flag. - if limits_enforced { - if let Some(ref sink) = self.dropped_by_limits_sink { - if let Err(e) = sink.unbounded_send((tx.clone(), TransactionStatus::Dropped)) { - trace!(target: LOG_TARGET, "[{:?}] dropped_sink/future: send message failed: {:?}", tx, e); - } + self.fire(tx, |watcher| watcher.usurped(by.clone())); + + if let Some(ref sink) = self.dropped_by_limits_sink { + if let Err(e) = + sink.unbounded_send((tx.clone(), TransactionStatus::Usurped(by.clone()))) + { + trace!(target: LOG_TARGET, "[{:?}] dropped_sink: send message failed: {:?}", tx, e); } } } + /// Transaction was dropped from the pool because of the failure during the resubmission of + /// revalidate transactions or failure during pruning tags. + pub fn dropped(&mut self, tx: &H) { + trace!(target: LOG_TARGET, "[{:?}] Dropped", tx); + self.fire(tx, |watcher| watcher.dropped()); + } + /// Transaction was removed as invalid. pub fn invalid(&mut self, tx: &H) { trace!(target: LOG_TARGET, "[{:?}] Extrinsic invalid", tx); diff --git a/substrate/client/transaction-pool/src/graph/pool.rs b/substrate/client/transaction-pool/src/graph/pool.rs index 2dd8de352c6b..23b71ce437b3 100644 --- a/substrate/client/transaction-pool/src/graph/pool.rs +++ b/substrate/client/transaction-pool/src/graph/pool.rs @@ -181,10 +181,8 @@ impl Pool { pub async fn submit_at( &self, at: &HashAndNumber, - source: TransactionSource, - xts: impl IntoIterator>, + xts: impl IntoIterator)>, ) -> Vec, B::Error>> { - let xts = xts.into_iter().map(|xt| (source, xt)); let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::Yes).await; self.validated_pool.submit(validated_transactions.into_values()) } @@ -195,10 +193,8 @@ impl Pool { pub async fn resubmit_at( &self, at: &HashAndNumber, - source: TransactionSource, - xts: impl IntoIterator>, + xts: impl IntoIterator)>, ) -> Vec, B::Error>> { - let xts = xts.into_iter().map(|xt| (source, xt)); let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::No).await; self.validated_pool.submit(validated_transactions.into_values()) } @@ -207,10 +203,10 @@ impl Pool { pub async fn submit_one( &self, at: &HashAndNumber, - source: TransactionSource, + source: base::TimedTransactionSource, xt: ExtrinsicFor, ) -> Result, B::Error> { - let res = self.submit_at(at, source, std::iter::once(xt)).await.pop(); + let res = self.submit_at(at, std::iter::once((source, xt))).await.pop(); res.expect("One extrinsic passed; one result returned; qed") } @@ -218,7 +214,7 @@ impl Pool { pub async fn submit_and_watch( &self, at: &HashAndNumber, - source: TransactionSource, + source: base::TimedTransactionSource, xt: ExtrinsicFor, ) -> Result, ExtrinsicHash>, B::Error> { let (_, tx) = self @@ -368,7 +364,7 @@ impl Pool { // Try to re-validate pruned transactions since some of them might be still valid. // note that `known_imported_hashes` will be rejected here due to temporary ban. let pruned_transactions = - prune_status.pruned.into_iter().map(|tx| (tx.source, tx.data.clone())); + prune_status.pruned.into_iter().map(|tx| (tx.source.clone(), tx.data.clone())); let reverified_transactions = self.verify(at, pruned_transactions, CheckBannedBeforeVerify::Yes).await; @@ -396,7 +392,7 @@ impl Pool { async fn verify( &self, at: &HashAndNumber, - xts: impl IntoIterator)>, + xts: impl IntoIterator)>, check: CheckBannedBeforeVerify, ) -> IndexMap, ValidatedTransactionFor> { let HashAndNumber { number, hash } = *at; @@ -417,7 +413,7 @@ impl Pool { &self, block_hash: ::Hash, block_number: NumberFor, - source: TransactionSource, + source: base::TimedTransactionSource, xt: ExtrinsicFor, check: CheckBannedBeforeVerify, ) -> (ExtrinsicHash, ValidatedTransactionFor) { @@ -431,7 +427,7 @@ impl Pool { let validation_result = self .validated_pool .api() - .validate_transaction(block_hash, source, xt.clone()) + .validate_transaction(block_hash, source.clone().into(), xt.clone()) .await; let status = match validation_result { @@ -488,6 +484,7 @@ mod tests { use super::{super::base_pool::Limit, *}; use crate::common::tests::{pool, uxt, TestApi, INVALID_NONCE}; use assert_matches::assert_matches; + use base::TimedTransactionSource; use codec::Encode; use futures::executor::block_on; use parking_lot::Mutex; @@ -497,7 +494,8 @@ mod tests { use substrate_test_runtime::{AccountId, ExtrinsicBuilder, Transfer, H256}; use substrate_test_runtime_client::AccountKeyring::{Alice, Bob}; - const SOURCE: TransactionSource = TransactionSource::External; + const SOURCE: TimedTransactionSource = + TimedTransactionSource { source: TransactionSource::External, timestamp: None }; #[test] fn should_validate_and_import_transaction() { @@ -545,8 +543,8 @@ mod tests { let initial_hashes = txs.iter().map(|t| api.hash_and_length(t).0).collect::>(); // when - let txs = txs.into_iter().map(|x| Arc::from(x)).collect::>(); - let hashes = block_on(pool.submit_at(&api.expect_hash_and_number(0), SOURCE, txs)); + let txs = txs.into_iter().map(|x| (SOURCE, Arc::from(x))).collect::>(); + let hashes = block_on(pool.submit_at(&api.expect_hash_and_number(0), txs)); log::debug!("--> {hashes:#?}"); // then diff --git a/substrate/client/transaction-pool/src/graph/ready.rs b/substrate/client/transaction-pool/src/graph/ready.rs index 860bcff0bace..9061d0e25581 100644 --- a/substrate/client/transaction-pool/src/graph/ready.rs +++ b/substrate/client/transaction-pool/src/graph/ready.rs @@ -589,7 +589,6 @@ fn remove_item(vec: &mut Vec, item: &T) { #[cfg(test)] mod tests { use super::*; - use sp_runtime::transaction_validity::TransactionSource as Source; fn tx(id: u8) -> Transaction> { Transaction { @@ -601,7 +600,7 @@ mod tests { requires: vec![vec![1], vec![2]], provides: vec![vec![3], vec![4]], propagate: true, - source: Source::External, + source: crate::TimedTransactionSource::new_external(false), } } @@ -711,7 +710,7 @@ mod tests { requires: vec![tx1.provides[0].clone()], provides: vec![], propagate: true, - source: Source::External, + source: crate::TimedTransactionSource::new_external(false), }; // when diff --git a/substrate/client/transaction-pool/src/graph/rotator.rs b/substrate/client/transaction-pool/src/graph/rotator.rs index 61a26fb4138c..9a2e269b5eed 100644 --- a/substrate/client/transaction-pool/src/graph/rotator.rs +++ b/substrate/client/transaction-pool/src/graph/rotator.rs @@ -106,7 +106,6 @@ impl PoolRotator { #[cfg(test)] mod tests { use super::*; - use sp_runtime::transaction_validity::TransactionSource; type Hash = u64; type Ex = (); @@ -126,7 +125,7 @@ mod tests { requires: vec![], provides: vec![], propagate: true, - source: TransactionSource::External, + source: crate::TimedTransactionSource::new_external(false), }; (hash, tx) @@ -192,7 +191,7 @@ mod tests { requires: vec![], provides: vec![], propagate: true, - source: TransactionSource::External, + source: crate::TimedTransactionSource::new_external(false), } } diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index d7f55198a40a..14df63d9673e 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -30,7 +30,7 @@ use serde::Serialize; use sp_blockchain::HashAndNumber; use sp_runtime::{ traits::{self, SaturatedConversion}, - transaction_validity::{TransactionSource, TransactionTag as Tag, ValidTransaction}, + transaction_validity::{TransactionTag as Tag, ValidTransaction}, }; use std::time::Instant; @@ -62,7 +62,7 @@ impl ValidatedTransaction { pub fn valid_at( at: u64, hash: Hash, - source: TransactionSource, + source: base::TimedTransactionSource, data: Ex, bytes: usize, validity: ValidTransaction, @@ -280,7 +280,7 @@ impl ValidatedPool { // run notifications let mut listener = self.listener.write(); for h in &removed { - listener.dropped(h, None, true); + listener.limit_enforced(h); } removed @@ -453,7 +453,7 @@ impl ValidatedPool { match final_status { Status::Future => listener.future(&hash), Status::Ready => listener.ready(&hash, None), - Status::Dropped => listener.dropped(&hash, None, false), + Status::Dropped => listener.dropped(&hash), Status::Failed => listener.invalid(&hash), } } @@ -492,7 +492,7 @@ impl ValidatedPool { fire_events(&mut *listener, promoted); } for f in &status.failed { - listener.dropped(f, None, false); + listener.dropped(f); } } @@ -671,6 +671,21 @@ impl ValidatedPool { ) -> super::listener::DroppedByLimitsStream, BlockHash> { self.listener.write().create_dropped_by_limits_stream() } + + /// Resends ready and future events for all the ready and future transactions that are already + /// in the pool. + /// + /// Intended to be called after cloning the instance of `ValidatedPool`. + pub fn retrigger_notifications(&self) { + let pool = self.pool.read(); + let mut listener = self.listener.write(); + pool.ready().for_each(|r| { + listener.ready(&r.hash, None); + }); + pool.futures().for_each(|f| { + listener.future(&f.hash); + }); + } } fn fire_events(listener: &mut Listener, imported: &base::Imported) @@ -682,7 +697,7 @@ where base::Imported::Ready { ref promoted, ref failed, ref removed, ref hash } => { listener.ready(hash, None); failed.iter().for_each(|f| listener.invalid(f)); - removed.iter().for_each(|r| listener.dropped(&r.hash, Some(hash), false)); + removed.iter().for_each(|r| listener.usurped(&r.hash, hash)); promoted.iter().for_each(|p| listener.ready(p, None)); }, base::Imported::Future { ref hash } => listener.future(hash), diff --git a/substrate/client/transaction-pool/src/graph/watcher.rs b/substrate/client/transaction-pool/src/graph/watcher.rs index fb7cf99d4dc6..2fd31e772fd8 100644 --- a/substrate/client/transaction-pool/src/graph/watcher.rs +++ b/substrate/client/transaction-pool/src/graph/watcher.rs @@ -113,6 +113,12 @@ impl Sender { } /// Transaction has been dropped from the pool because of the limit. + pub fn limit_enforced(&mut self) { + self.send(TransactionStatus::Dropped); + self.is_finalized = true; + } + + /// Transaction has been dropped from the pool. pub fn dropped(&mut self) { self.send(TransactionStatus::Dropped); self.is_finalized = true; diff --git a/substrate/client/transaction-pool/src/lib.rs b/substrate/client/transaction-pool/src/lib.rs index 3d3d596c291f..366d91a973d2 100644 --- a/substrate/client/transaction-pool/src/lib.rs +++ b/substrate/client/transaction-pool/src/lib.rs @@ -36,7 +36,10 @@ pub use api::FullChainApi; pub use builder::{Builder, TransactionPoolHandle, TransactionPoolOptions, TransactionPoolType}; pub use common::notification_future; pub use fork_aware_txpool::{ForkAwareTxPool, ForkAwareTxPoolTask}; -pub use graph::{base_pool::Limit as PoolLimit, ChainApi, Options, Pool}; +pub use graph::{ + base_pool::{Limit as PoolLimit, TimedTransactionSource}, + ChainApi, Options, Pool, +}; use single_state_txpool::prune_known_txs_for_block; pub use single_state_txpool::{BasicPool, RevalidationType}; pub use transaction_pool_wrapper::TransactionPoolWrapper; diff --git a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs index 5ef726c9f7d3..74031b1e1c72 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs @@ -88,7 +88,7 @@ async fn batch_revalidate( let validation_results = futures::future::join_all(batch.into_iter().filter_map(|ext_hash| { pool.validated_pool().ready_by_hash(&ext_hash).map(|ext| { - api.validate_transaction(at, ext.source, ext.data.clone()) + api.validate_transaction(at, ext.source.clone().into(), ext.data.clone()) .map(move |validation_result| (validation_result, ext_hash, ext)) }) })) @@ -121,7 +121,7 @@ async fn batch_revalidate( ValidatedTransaction::valid_at( block_number.saturated_into::(), ext_hash, - ext.source, + ext.source.clone(), ext.data.clone(), api.hash_and_length(&ext.data).1, validity, @@ -375,9 +375,9 @@ mod tests { use crate::{ common::tests::{uxt, TestApi}, graph::Pool, + TimedTransactionSource, }; use futures::executor::block_on; - use sc_transaction_pool_api::TransactionSource; use substrate_test_runtime::{AccountId, Transfer, H256}; use substrate_test_runtime_client::AccountKeyring::{Alice, Bob}; @@ -398,7 +398,7 @@ mod tests { let uxt_hash = block_on(pool.submit_one( &han_of_block0, - TransactionSource::External, + TimedTransactionSource::new_external(false), uxt.clone().into(), )) .expect("Should be valid"); @@ -433,14 +433,15 @@ mod tests { let han_of_block0 = api.expect_hash_and_number(0); let unknown_block = H256::repeat_byte(0x13); - let uxt_hashes = block_on(pool.submit_at( - &han_of_block0, - TransactionSource::External, - vec![uxt0.into(), uxt1.into()], - )) - .into_iter() - .map(|r| r.expect("Should be valid")) - .collect::>(); + let source = TimedTransactionSource::new_external(false); + let uxt_hashes = + block_on(pool.submit_at( + &han_of_block0, + vec![(source.clone(), uxt0.into()), (source, uxt1.into())], + )) + .into_iter() + .map(|r| r.expect("Should be valid")) + .collect::>(); assert_eq!(api.validation_requests().len(), 2); assert_eq!(pool.validated_pool().status().ready, 2); diff --git a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs index b29630b563bb..e7504012ca67 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs @@ -29,7 +29,7 @@ use crate::{ error, log_xt::log_xt_trace, }, - graph::{self, ExtrinsicHash, IsValidator}, + graph::{self, base_pool::TimedTransactionSource, ExtrinsicHash, IsValidator}, ReadyIteratorFor, LOG_TARGET, }; use async_trait::async_trait; @@ -254,14 +254,19 @@ where xts: Vec>, ) -> Result, Self::Error>>, Self::Error> { let pool = self.pool.clone(); - let xts = xts.into_iter().map(Arc::from).collect::>(); + let xts = xts + .into_iter() + .map(|xt| { + (TimedTransactionSource::from_transaction_source(source, false), Arc::from(xt)) + }) + .collect::>(); self.metrics .report(|metrics| metrics.submitted_transactions.inc_by(xts.len() as u64)); let number = self.api.resolve_block_number(at); let at = HashAndNumber { hash: at, number: number? }; - Ok(pool.submit_at(&at, source, xts).await) + Ok(pool.submit_at(&at, xts).await) } async fn submit_one( @@ -277,7 +282,8 @@ where let number = self.api.resolve_block_number(at); let at = HashAndNumber { hash: at, number: number? }; - pool.submit_one(&at, source, xt).await + pool.submit_one(&at, TimedTransactionSource::from_transaction_source(source, false), xt) + .await } async fn submit_and_watch( @@ -294,7 +300,13 @@ where let number = self.api.resolve_block_number(at); let at = HashAndNumber { hash: at, number: number? }; - let watcher = pool.submit_and_watch(&at, source, xt).await?; + let watcher = pool + .submit_and_watch( + &at, + TimedTransactionSource::from_transaction_source(source, false), + xt, + ) + .await?; Ok(watcher.into_stream().boxed()) } @@ -458,7 +470,7 @@ where let validated = ValidatedTransaction::valid_at( block_number.saturated_into::(), hash, - TransactionSource::Local, + TimedTransactionSource::new_local(false), Arc::from(xt), bytes, validity, @@ -662,8 +674,8 @@ where resubmit_transactions.extend( //todo: arctx - we need to get ref from somewhere - block_transactions.into_iter().map(Arc::from).filter(|tx| { - let tx_hash = pool.hash_of(tx); + block_transactions.into_iter().map(Arc::from).filter_map(|tx| { + let tx_hash = pool.hash_of(&tx); let contains = pruned_log.contains(&tx_hash); // need to count all transactions, not just filtered, here @@ -676,8 +688,15 @@ where tx_hash, hash, ); + Some(( + // These transactions are coming from retracted blocks, we should + // simply consider them external. + TimedTransactionSource::new_external(false), + tx, + )) + } else { + None } - !contains }), ); @@ -686,14 +705,7 @@ where }); } - pool.resubmit_at( - &hash_and_number, - // These transactions are coming from retracted blocks, we should - // simply consider them external. - TransactionSource::External, - resubmit_transactions, - ) - .await; + pool.resubmit_at(&hash_and_number, resubmit_transactions).await; } let extra_pool = pool.clone(); diff --git a/substrate/client/transaction-pool/tests/fatp.rs b/substrate/client/transaction-pool/tests/fatp.rs index 9f343a9bd029..c51ca6e17663 100644 --- a/substrate/client/transaction-pool/tests/fatp.rs +++ b/substrate/client/transaction-pool/tests/fatp.rs @@ -2267,19 +2267,13 @@ fn fatp_avoid_stuck_transaction() { assert_pool_status!(header06.hash(), &pool, 0, 0); - // Import enough blocks to make xt4i revalidated - let mut prev_header = header03; - // wait 10 blocks for revalidation - for n in 7..=11 { - let header = api.push_block(n, vec![], true); - let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); - block_on(pool.maintain(event)); - prev_header = header; - } + let header07 = api.push_block(7, vec![], true); + let event = finalized_block_event(&pool, header03.hash(), header07.hash()); + block_on(pool.maintain(event)); let xt4i_events = futures::executor::block_on_stream(xt4i_watcher).collect::>(); log::debug!("xt4i_events: {:#?}", xt4i_events); - assert_eq!(xt4i_events, vec![TransactionStatus::Future, TransactionStatus::Invalid]); + assert_eq!(xt4i_events, vec![TransactionStatus::Future, TransactionStatus::Dropped]); assert_eq!(pool.mempool_len(), (0, 0)); } diff --git a/substrate/client/transaction-pool/tests/fatp_common/mod.rs b/substrate/client/transaction-pool/tests/fatp_common/mod.rs index 15f2b7f79c14..aecd83360f1e 100644 --- a/substrate/client/transaction-pool/tests/fatp_common/mod.rs +++ b/substrate/client/transaction-pool/tests/fatp_common/mod.rs @@ -201,6 +201,20 @@ macro_rules! assert_ready_iterator { }}; } +#[macro_export] +macro_rules! assert_future_iterator { + ($hash:expr, $pool:expr, [$( $xt:expr ),*]) => {{ + let futures = $pool.futures_at($hash).unwrap(); + let expected = vec![ $($pool.api().hash_and_length(&$xt).0),*]; + log::debug!(target:LOG_TARGET, "expected: {:#?}", futures); + log::debug!(target:LOG_TARGET, "output: {:#?}", expected); + assert_eq!(expected.len(), futures.len()); + let hsf = futures.iter().map(|a| a.hash).collect::>(); + let hse = expected.into_iter().collect::>(); + assert_eq!(hse,hsf); + }}; +} + pub const SOURCE: TransactionSource = TransactionSource::External; #[cfg(test)] diff --git a/substrate/client/transaction-pool/tests/fatp_limits.rs b/substrate/client/transaction-pool/tests/fatp_limits.rs index 03792fd89dfa..afd8183957a8 100644 --- a/substrate/client/transaction-pool/tests/fatp_limits.rs +++ b/substrate/client/transaction-pool/tests/fatp_limits.rs @@ -641,3 +641,192 @@ fn fatp_limits_future_size_works() { assert_pool_status!(header01.hash(), &pool, 0, 3); assert_eq!(pool.mempool_len().0, 3); } + +#[test] +fn fatp_limits_watcher_ready_transactions_are_not_droped_when_view_is_dropped() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(6).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + api.set_nonce(api.genesis_hash(), Eve.into(), 600); + api.set_nonce(api.genesis_hash(), Ferdie.into(), 700); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + + let xt3 = uxt(Dave, 500); + let xt4 = uxt(Eve, 600); + let xt5 = uxt(Ferdie, 700); + + let _xt0_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let _xt1_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 2); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let _xt2_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let _xt3_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 4); + + let header03 = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash()))); + + let _xt4_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + let _xt5_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).unwrap(); + + assert_pool_status!(header03.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 6); + + let header04 = + api.push_block_with_parent(header03.hash(), vec![xt4.clone(), xt5.clone()], true); + api.set_nonce(header04.hash(), Alice.into(), 201); + api.set_nonce(header04.hash(), Bob.into(), 301); + api.set_nonce(header04.hash(), Charlie.into(), 401); + api.set_nonce(header04.hash(), Dave.into(), 501); + api.set_nonce(header04.hash(), Eve.into(), 601); + api.set_nonce(header04.hash(), Ferdie.into(), 701); + block_on(pool.maintain(new_best_block_event(&pool, Some(header03.hash()), header04.hash()))); + + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1]); + assert_ready_iterator!(header02.hash(), pool, [xt2, xt3]); + assert_ready_iterator!(header03.hash(), pool, [xt4, xt5]); + assert_ready_iterator!(header04.hash(), pool, []); + + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header01.hash()))); + assert!(!pool.status_all().contains_key(&header01.hash())); + + block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash()))); + assert!(!pool.status_all().contains_key(&header02.hash())); + + //view 01 was dropped + assert!(pool.ready_at(header01.hash()).now_or_never().is_none()); + assert_eq!(pool.mempool_len().1, 6); + + block_on(pool.maintain(finalized_block_event(&pool, header02.hash(), header03.hash()))); + + //no revalidation has happened yet, all txs are kept + assert_eq!(pool.mempool_len().1, 6); + + //view 03 is still there + assert!(!pool.status_all().contains_key(&header03.hash())); + + //view 02 was dropped + assert!(pool.ready_at(header02.hash()).now_or_never().is_none()); + + let mut prev_header = header03; + for n in 5..=11 { + let header = api.push_block(n, vec![], true); + let event = finalized_block_event(&pool, prev_header.hash(), header.hash()); + block_on(pool.maintain(event)); + prev_header = header; + } + + //now revalidation has happened, all txs are dropped + assert_eq!(pool.mempool_len().1, 0); +} + +#[test] +fn fatp_limits_watcher_future_transactions_are_droped_when_view_is_dropped() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(6).with_future_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + api.set_nonce(api.genesis_hash(), Eve.into(), 600); + api.set_nonce(api.genesis_hash(), Ferdie.into(), 700); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 201); + let xt1 = uxt(Bob, 301); + let xt2 = uxt(Charlie, 401); + + let xt3 = uxt(Dave, 501); + let xt4 = uxt(Eve, 601); + let xt5 = uxt(Ferdie, 701); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 0, 2); + assert_eq!(pool.mempool_len().1, 2); + assert_future_iterator!(header01.hash(), pool, [xt0, xt1]); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header02.hash(), &pool, 0, 2); + assert_eq!(pool.mempool_len().1, 4); + assert_future_iterator!(header02.hash(), pool, [xt2, xt3]); + + let header03 = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash()))); + + let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + let xt5_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).unwrap(); + + assert_pool_status!(header03.hash(), &pool, 0, 2); + assert_eq!(pool.mempool_len().1, 6); + assert_future_iterator!(header03.hash(), pool, [xt4, xt5]); + + let header04 = api.push_block_with_parent(header03.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header03.hash()), header04.hash()))); + + assert_pool_status!(header04.hash(), &pool, 0, 2); + assert_eq!(pool.futures().len(), 2); + assert_future_iterator!(header04.hash(), pool, [xt4, xt5]); + + block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header04.hash()))); + assert_eq!(pool.active_views_count(), 1); + assert_eq!(pool.inactive_views_count(), 0); + //todo: can we do better? We don't have API to check if event was processed internally. + let mut counter = 0; + while pool.mempool_len().1 != 2 { + sleep(std::time::Duration::from_millis(1)); + counter = counter + 1; + if counter > 20 { + assert!(false, "timeout {}", pool.mempool_len().1); + } + } + assert_eq!(pool.mempool_len().1, 2); + assert_pool_status!(header04.hash(), &pool, 0, 2); + assert_eq!(pool.futures().len(), 2); + + let to_be_checked = vec![xt0_watcher, xt1_watcher, xt2_watcher, xt3_watcher]; + for x in to_be_checked { + let x_status = futures::executor::block_on_stream(x).take(2).collect::>(); + assert_eq!(x_status, vec![TransactionStatus::Future, TransactionStatus::Dropped]); + } + + let to_be_checked = vec![xt4_watcher, xt5_watcher]; + for x in to_be_checked { + let x_status = futures::executor::block_on_stream(x).take(1).collect::>(); + assert_eq!(x_status, vec![TransactionStatus::Future]); + } +} diff --git a/substrate/client/transaction-pool/tests/fatp_prios.rs b/substrate/client/transaction-pool/tests/fatp_prios.rs new file mode 100644 index 000000000000..41bc374b38f4 --- /dev/null +++ b/substrate/client/transaction-pool/tests/fatp_prios.rs @@ -0,0 +1,249 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Tests of priorities for fork-aware transaction pool. + +pub mod fatp_common; + +use fatp_common::{new_best_block_event, TestPoolBuilder, LOG_TARGET, SOURCE}; +use futures::{executor::block_on, FutureExt}; +use sc_transaction_pool::ChainApi; +use sc_transaction_pool_api::{MaintainedTransactionPool, TransactionPool, TransactionStatus}; +use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_transaction_pool::uxt; + +#[test] +fn fatp_prio_ready_higher_evicts_lower() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 200); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 3); + + let result0 = block_on(pool.submit_one(header01.hash(), SOURCE, xt0.clone())); + let result1 = block_on(pool.submit_one(header01.hash(), SOURCE, xt1.clone())); + + log::info!("r0 => {:?}", result0); + log::info!("r1 => {:?}", result1); + log::info!("len: {:?}", pool.mempool_len()); + log::info!("len: {:?}", pool.status_all()[&header01.hash()]); + assert_ready_iterator!(header01.hash(), pool, [xt1]); + assert_pool_status!(header01.hash(), &pool, 1, 0); +} + +#[test] +fn fatp_prio_watcher_ready_higher_evicts_lower() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 200); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 3); + + let xt0_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap(); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::>(); + assert_eq!( + xt0_status, + vec![TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt1).0)] + ); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::>(); + assert_eq!(xt1_status, vec![TransactionStatus::Ready]); + + log::info!("len: {:?}", pool.mempool_len()); + log::info!("len: {:?}", pool.status_all()[&header01.hash()]); + assert_ready_iterator!(header01.hash(), pool, [xt1]); + assert_pool_status!(header01.hash(), &pool, 1, 0); +} + +#[test] +fn fatp_prio_watcher_future_higher_evicts_lower() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(3).build(); + + let header01 = api.push_block(1, vec![], true); + + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 201); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 200); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 3); + + let xt0_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt2.clone())).unwrap(); + + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::>(); + + assert_eq!( + xt0_status, + vec![TransactionStatus::Future, TransactionStatus::Usurped(api.hash_and_length(&xt2).0)] + ); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(2).collect::>(); + assert_eq!(xt1_status, vec![TransactionStatus::Future, TransactionStatus::Ready]); + let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::>(); + assert_eq!(xt2_status, vec![TransactionStatus::Ready]); + + assert_eq!(pool.mempool_len().1, 2); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt1]); + assert_pool_status!(header01.hash(), &pool, 2, 0); +} + +#[test] +fn fatp_prio_watcher_ready_lower_prio_gets_dropped_from_all_views() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build(); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 200); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 3); + + let xt0_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap(); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let header03a = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header03a.hash()))); + + let header03b = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header03a.hash()), header03b.hash()))); + + assert_pool_status!(header03a.hash(), &pool, 1, 0); + assert_ready_iterator!(header03a.hash(), pool, [xt0]); + assert_pool_status!(header03b.hash(), &pool, 1, 0); + assert_ready_iterator!(header03b.hash(), pool, [xt0]); + assert_ready_iterator!(header01.hash(), pool, [xt0]); + assert_ready_iterator!(header02.hash(), pool, [xt0]); + + let xt1_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap(); + + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::>(); + assert_eq!(xt1_status, vec![TransactionStatus::Ready]); + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::>(); + assert_eq!( + xt0_status, + vec![TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt1).0)] + ); + assert_ready_iterator!(header03a.hash(), pool, [xt1]); + assert_ready_iterator!(header03b.hash(), pool, [xt1]); + assert_ready_iterator!(header01.hash(), pool, [xt1]); + assert_ready_iterator!(header02.hash(), pool, [xt1]); +} + +#[test] +fn fatp_prio_watcher_future_lower_prio_gets_dropped_from_all_views() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build(); + + let header01 = api.push_block(1, vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash()))); + + let xt0 = uxt(Alice, 201); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 200); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 3); + + let xt0_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap(); + + let xt1_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap(); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let header03a = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header03a.hash()))); + + let header03b = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header03a.hash()), header03b.hash()))); + + assert_pool_status!(header03a.hash(), &pool, 0, 2); + assert_future_iterator!(header03a.hash(), pool, [xt0, xt1]); + assert_pool_status!(header03b.hash(), &pool, 0, 2); + assert_future_iterator!(header03b.hash(), pool, [xt0, xt1]); + assert_future_iterator!(header01.hash(), pool, [xt0, xt1]); + assert_future_iterator!(header02.hash(), pool, [xt0, xt1]); + + let xt2_watcher = + block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt2.clone())).unwrap(); + + let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::>(); + assert_eq!(xt2_status, vec![TransactionStatus::Ready]); + let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::>(); + assert_eq!(xt1_status, vec![TransactionStatus::Future]); + let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::>(); + assert_eq!( + xt0_status, + vec![TransactionStatus::Future, TransactionStatus::Usurped(api.hash_and_length(&xt2).0)] + ); + assert_future_iterator!(header03a.hash(), pool, []); + assert_future_iterator!(header03b.hash(), pool, []); + assert_future_iterator!(header01.hash(), pool, []); + assert_future_iterator!(header02.hash(), pool, []); + + assert_ready_iterator!(header03a.hash(), pool, [xt2, xt1]); + assert_ready_iterator!(header03b.hash(), pool, [xt2, xt1]); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt1]); + assert_ready_iterator!(header02.hash(), pool, [xt2, xt1]); +} diff --git a/substrate/client/transaction-pool/tests/pool.rs b/substrate/client/transaction-pool/tests/pool.rs index ed0fd7d4e655..e556ba9875f1 100644 --- a/substrate/client/transaction-pool/tests/pool.rs +++ b/substrate/client/transaction-pool/tests/pool.rs @@ -80,12 +80,14 @@ fn create_basic_pool(test_api: TestApi) -> BasicPool { create_basic_pool_with_genesis(Arc::from(test_api)).0 } +const TSOURCE: TimedTransactionSource = + TimedTransactionSource { source: TransactionSource::External, timestamp: None }; const SOURCE: TransactionSource = TransactionSource::External; #[test] fn submission_should_work() { let (pool, api) = pool(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 209).into())) .unwrap(); let pending: Vec<_> = pool @@ -99,9 +101,9 @@ fn submission_should_work() { #[test] fn multiple_submission_should_work() { let (pool, api) = pool(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 209).into())) .unwrap(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 210).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 210).into())) .unwrap(); let pending: Vec<_> = pool @@ -116,7 +118,7 @@ fn multiple_submission_should_work() { fn early_nonce_should_be_culled() { sp_tracing::try_init_simple(); let (pool, api) = pool(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 208).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 208).into())) .unwrap(); log::debug!("-> {:?}", pool.validated_pool().status()); @@ -132,7 +134,7 @@ fn early_nonce_should_be_culled() { fn late_nonce_should_be_queued() { let (pool, api) = pool(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 210).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 210).into())) .unwrap(); let pending: Vec<_> = pool .validated_pool() @@ -141,7 +143,7 @@ fn late_nonce_should_be_queued() { .collect(); assert_eq!(pending, Vec::::new()); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 209).into())) .unwrap(); let pending: Vec<_> = pool .validated_pool() @@ -155,9 +157,9 @@ fn late_nonce_should_be_queued() { fn prune_tags_should_work() { let (pool, api) = pool(); let hash209 = - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 209).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 209).into())) .unwrap(); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt(Alice, 210).into())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 210).into())) .unwrap(); let pending: Vec<_> = pool @@ -183,9 +185,9 @@ fn should_ban_invalid_transactions() { let (pool, api) = pool(); let uxt = Arc::from(uxt(Alice, 209)); let hash = - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.clone())).unwrap(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())).unwrap(); pool.validated_pool().remove_invalid(&[hash]); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())).unwrap_err(); // when let pending: Vec<_> = pool @@ -196,7 +198,7 @@ fn should_ban_invalid_transactions() { assert_eq!(pending, Vec::::new()); // then - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())).unwrap_err(); } #[test] @@ -224,7 +226,7 @@ fn should_correctly_prune_transactions_providing_more_than_one_tag() { })); let pool = Pool::new(Default::default(), true.into(), api.clone()); let xt0 = Arc::from(uxt(Alice, 209)); - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt0.clone())) + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, xt0.clone())) .expect("1. Imported"); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(api.validation_requests().len(), 1); @@ -242,7 +244,7 @@ fn should_correctly_prune_transactions_providing_more_than_one_tag() { api.increment_nonce(Alice.into()); api.push_block(2, Vec::new(), true); let xt1 = uxt(Alice, 211); - block_on(pool.submit_one(&api.expect_hash_and_number(2), SOURCE, xt1.clone().into())) + block_on(pool.submit_one(&api.expect_hash_and_number(2), TSOURCE, xt1.clone().into())) .expect("2. Imported"); assert_eq!(api.validation_requests().len(), 3); assert_eq!(pool.validated_pool().status().ready, 1); From 02750d68fcf10a98f774c847596625e751b35b03 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 4 Dec 2024 10:31:14 +0200 Subject: [PATCH 094/340] remove unnecessary alias origin --- .../primitives/router/src/inbound/v2.rs | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index b50184413ac5..f730d0adedeb 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -63,8 +63,6 @@ pub enum ConvertMessageError { InvalidClaimer, /// Invalid foreign ERC20 token ID InvalidAsset, - /// The origin could not be added to the interior location of the origin location. - InvalidOrigin, } pub trait ConvertMessage { @@ -176,17 +174,17 @@ where instructions.push(SetAssetClaimer { location: claimer_location }); } - let mut origin_location = Location::new(2, [GlobalConsensus(network)]); - if message.origin == GatewayProxyAddress::get() { - // If the message origin is the gateway proxy contract, set the origin to - // Ethereum, for consistency with v1. - instructions.push(AliasOrigin(origin_location)); - } else { - // Set the alias origin to the original sender on Ethereum. Important to be before the - // arbitrary XCM that is appended to the message on the next line. - origin_location - .push_interior(AccountKey20 { key: message.origin.into(), network: None }) - .map_err(|_| ConvertMessageError::InvalidOrigin)?; + // If the message origin is not the gateway proxy contract, set the origin to + // the original sender on Ethereum. Important to be before the arbitrary XCM that is + // appended to the message on the next line. + if message.origin != GatewayProxyAddress::get() { + let origin_location = Location::new( + 2, + [ + GlobalConsensus(network), + AccountKey20 { key: message.origin.into(), network: None }, + ], + ); instructions.push(AliasOrigin(origin_location.into())); } From a2ffae303e8b6c52cff720fb2f3e2019d65d91b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 4 Dec 2024 10:06:57 +0100 Subject: [PATCH 095/340] umbrella: Remove `pallet-revive-fixtures` (#6743) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No need to have them in the umbrella crate also by having them in the umbrella crate they are bleeding into the normal build. --------- Co-authored-by: GitHub Action Co-authored-by: Alexander Theißen --- Cargo.lock | 1 - prdoc/pr_6743.prdoc | 10 ++++++++++ substrate/frame/revive/fixtures/Cargo.toml | 3 +++ umbrella/Cargo.toml | 8 +------- umbrella/src/lib.rs | 4 ---- 5 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 prdoc/pr_6743.prdoc diff --git a/Cargo.lock b/Cargo.lock index bc2ebb2a057d..863822f4ffd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18577,7 +18577,6 @@ dependencies = [ "pallet-remark 28.0.0", "pallet-revive 0.1.0", "pallet-revive-eth-rpc", - "pallet-revive-fixtures 0.1.0", "pallet-revive-mock-network 0.1.0", "pallet-revive-proc-macro 0.1.0", "pallet-revive-uapi 0.1.0", diff --git a/prdoc/pr_6743.prdoc b/prdoc/pr_6743.prdoc new file mode 100644 index 000000000000..4c35ff46ca67 --- /dev/null +++ b/prdoc/pr_6743.prdoc @@ -0,0 +1,10 @@ +title: 'umbrella: Remove `pallet-revive-fixtures`' +doc: +- audience: Runtime Dev + description: |- + No need to have them in the umbrella crate also by having them in the umbrella crate they are bleeding into the normal build. +crates: +- name: pallet-revive-fixtures + bump: major +- name: polkadot-sdk + bump: major diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index 9fd434db6179..88921cca08ec 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -8,6 +8,9 @@ description = "Fixtures for testing and benchmarking" homepage.workspace = true repository.workspace = true +[package.metadata.polkadot-sdk] +exclude-from-umbrella = true + [lints] workspace = true diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 9affcffd2ade..8ed9c3dcb02c 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -120,7 +120,6 @@ std = [ "pallet-recovery?/std", "pallet-referenda?/std", "pallet-remark?/std", - "pallet-revive-fixtures?/std", "pallet-revive-mock-network?/std", "pallet-revive?/std", "pallet-root-offences?/std", @@ -541,7 +540,7 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] -runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-fixtures", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", @@ -1193,11 +1192,6 @@ path = "../substrate/frame/revive" default-features = false optional = true -[dependencies.pallet-revive-fixtures] -path = "../substrate/frame/revive/fixtures" -default-features = false -optional = true - [dependencies.pallet-revive-proc-macro] path = "../substrate/frame/revive/proc-macro" default-features = false diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 2216864fad0f..3712fb3343cf 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -584,10 +584,6 @@ pub use pallet_revive; #[cfg(feature = "pallet-revive-eth-rpc")] pub use pallet_revive_eth_rpc; -/// Fixtures for testing and benchmarking. -#[cfg(feature = "pallet-revive-fixtures")] -pub use pallet_revive_fixtures; - /// A mock network for testing pallet-revive. #[cfg(feature = "pallet-revive-mock-network")] pub use pallet_revive_mock_network; From 377bc3f830a63e277dd94fed490670d1be6d840f Mon Sep 17 00:00:00 2001 From: Egor_P Date: Wed, 4 Dec 2024 11:06:55 +0100 Subject: [PATCH 096/340] [Release|CI/CD] Add pipeline to promote release candidate from rcX to final in S3 (#6748) This PR adds the pipeline, that moves release candidate artefacts from `polkadot-stableYYMM-rcX` bucket to the one that is going to be the final `polkadot-stableYYMM` (bucket name matches the tag name). So that it could be used for publishing later without a need to re-build it again. --- .github/scripts/common/lib.sh | 2 +- .github/scripts/release/release_lib.sh | 44 +++++- .../release-31_promote-rc-to-final.yml | 125 ++++++++++++++++++ .../release-reusable-promote-to-final.yml | 83 ++++++++++++ .../workflows/release-reusable-rc-buid.yml | 4 +- .../workflows/release-reusable-s3-upload.yml | 14 +- 6 files changed, 253 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/release-31_promote-rc-to-final.yml create mode 100644 .github/workflows/release-reusable-promote-to-final.yml diff --git a/.github/scripts/common/lib.sh b/.github/scripts/common/lib.sh index 41dc0ba06dd2..00f8c089831e 100755 --- a/.github/scripts/common/lib.sh +++ b/.github/scripts/common/lib.sh @@ -297,7 +297,7 @@ fetch_release_artifacts_from_s3() { pwd ls -al --color popd > /dev/null - + unset OUTPUT_DIR } # Pass the name of the binary as input, it will diff --git a/.github/scripts/release/release_lib.sh b/.github/scripts/release/release_lib.sh index 43227180cb7c..984709f2ea03 100644 --- a/.github/scripts/release/release_lib.sh +++ b/.github/scripts/release/release_lib.sh @@ -129,15 +129,17 @@ upload_s3_release() { echo "Working on version: $version " echo "Working on platform: $target " + URL_BASE=$(get_s3_url_base $product) + echo "Current content, should be empty on new uploads:" - aws s3 ls "s3://releases.parity.io/${product}/${version}/${target}" --recursive --human-readable --summarize || true + aws s3 ls "s3://${URL_BASE}/${version}/${target}" --recursive --human-readable --summarize || true echo "Content to be uploaded:" - artifacts="artifacts/$product/" + artifacts="release-artifacts/$target/$product/" ls "$artifacts" - aws s3 sync --acl public-read "$artifacts" "s3://releases.parity.io/${product}/${version}/${target}" + aws s3 sync --acl public-read "$artifacts" "s3://${URL_BASE}/${version}/${target}" echo "Uploaded files:" - aws s3 ls "s3://releases.parity.io/${product}/${version}/${target}" --recursive --human-readable --summarize - echo "✅ The release should be at https://releases.parity.io/${product}/${version}/${target}" + aws s3 ls "s3://${URL_BASE}/${version}/${target}" --recursive --human-readable --summarize + echo "✅ The release should be at https://${URL_BASE}/${version}/${target}" } # Upload runtimes artifacts to s3 release bucket @@ -161,3 +163,35 @@ upload_s3_runtimes_release_artifacts() { aws s3 ls "s3://releases.parity.io/polkadot/runtimes/${version}/" --recursive --human-readable --summarize echo "✅ The release should be at https://releases.parity.io/polkadot/runtimes/${version}" } + + +# Pass the name of the binary as input, it will +# return the s3 base url +function get_s3_url_base() { + name=$1 + case $name in + polkadot | polkadot-execute-worker | polkadot-prepare-worker ) + printf "releases.parity.io/polkadot" + ;; + + polkadot-parachain) + printf "releases.parity.io/polkadot-parachain" + ;; + + polkadot-omni-node) + printf "releases.parity.io/polkadot-omni-node" + ;; + + chain-spec-builder) + printf "releases.parity.io/chain-spec-builder" + ;; + + frame-omni-bencher) + printf "releases.parity.io/frame-omni-bencher" + ;; + *) + printf "UNSUPPORTED BINARY $name" + exit 1 + ;; + esac +} diff --git a/.github/workflows/release-31_promote-rc-to-final.yml b/.github/workflows/release-31_promote-rc-to-final.yml new file mode 100644 index 000000000000..6aa9d4bddd1d --- /dev/null +++ b/.github/workflows/release-31_promote-rc-to-final.yml @@ -0,0 +1,125 @@ +name: Release - Promote RC to final candidate on S3 + +on: + workflow_dispatch: + inputs: + binary: + description: Binary to be build for the release + default: all + type: choice + options: + - polkadot + - polkadot-parachain + - polkadot-omni-node + - frame-omni-bencher + - chain-spec-builder + - all + release_tag: + description: Tag matching the actual release candidate with the format polkadot-stableYYMM(-X)-rcX + type: string + + +jobs: + + check-synchronization: + uses: paritytech-release/sync-workflows/.github/workflows/check-syncronization.yml@main + + validate-inputs: + needs: [ check-synchronization ] + if: ${{ needs.check-synchronization.outputs.checks_passed }} == 'true' + runs-on: ubuntu-latest + outputs: + release_tag: ${{ steps.validate_inputs.outputs.release_tag }} + final_tag: ${{ steps.validate_inputs.outputs.final_tag }} + + steps: + - name: Checkout sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Validate inputs + id: validate_inputs + run: | + . ./.github/scripts/common/lib.sh + + RELEASE_TAG=$(validate_stable_tag ${{ inputs.release_tag }}) + echo "release_tag=${RELEASE_TAG}" >> $GITHUB_OUTPUT + + promote-polkadot-rc-to-final: + if: ${{ inputs.binary == 'polkadot' || inputs.binary == 'all' }} + needs: [ validate-inputs ] + uses: ./.github/workflows/release-reusable-promote-to-final.yml + strategy: + matrix: + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] + with: + package: polkadot + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: ${{ matrix.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + promote-polkadot-parachain-rc-to-final: + if: ${{ inputs.binary == 'polkadot-parachain' || inputs.binary == 'all' }} + needs: [ validate-inputs ] + uses: ./.github/workflows/release-reusable-promote-to-final.yml + strategy: + matrix: + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] + with: + package: polkadot-parachain + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: ${{ matrix.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + promote-polkadot-omni-node-rc-to-final: + if: ${{ inputs.binary == 'polkadot-omni-node' || inputs.binary == 'all' }} + needs: [ validate-inputs ] + uses: ./.github/workflows/release-reusable-promote-to-final.yml + strategy: + matrix: + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] + with: + package: polkadot-omni-node + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: ${{ matrix.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + promote-frame-omni-bencher-rc-to-final: + if: ${{ inputs.binary == 'frame-omni-bencher' || inputs.binary == 'all' }} + needs: [ validate-inputs ] + uses: ./.github/workflows/release-reusable-promote-to-final.yml + strategy: + matrix: + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] + with: + package: frame-omni-bencher + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: ${{ matrix.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + + promote-chain-spec-builder-rc-to-final: + if: ${{ inputs.binary == 'chain-spec-builder' || inputs.binary == 'all' }} + needs: [ validate-inputs ] + uses: ./.github/workflows/release-reusable-promote-to-final.yml + strategy: + matrix: + target: [ x86_64-unknown-linux-gnu, aarch64-apple-darwin ] + with: + package: chain-spec-builder + release_tag: ${{ needs.validate-inputs.outputs.release_tag }} + target: ${{ matrix.target }} + secrets: + AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + AWS_RELEASE_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_RELEASE_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/release-reusable-promote-to-final.yml b/.github/workflows/release-reusable-promote-to-final.yml new file mode 100644 index 000000000000..ed4a80a01e82 --- /dev/null +++ b/.github/workflows/release-reusable-promote-to-final.yml @@ -0,0 +1,83 @@ +name: Promote rc to final + +on: + workflow_call: + inputs: + package: + description: Package to be promoted + required: true + type: string + + release_tag: + description: Tag matching the actual release candidate with the format polkadot-stableYYMM(-X)-rcX taht will be changed to final in form of polkadot-stableYYMM(-X) + required: true + type: string + + target: + description: Target triple for which the artifacts are being uploaded (e.g aarch64-apple-darwin) + required: true + type: string + + secrets: + AWS_DEFAULT_REGION: + required: true + AWS_RELEASE_ACCESS_KEY_ID: + required: true + AWS_RELEASE_SECRET_ACCESS_KEY: + required: true + +jobs: + + promote-release-artifacts: + environment: release + runs-on: ubuntu-latest + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_RELEASE_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_RELEASE_SECRET_ACCESS_KEY }} + AWS_REGION: ${{ secrets.AWS_DEFAULT_REGION }} + + steps: + - name: Checkout sources + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Prepare final tag + id: prepare_final_tag + shell: bash + run: | + tag="$(echo ${{ inputs.release_tag }} | sed 's/-rc[0-9]*$//')" + echo $tag + echo "FINAL_TAG=${tag}" >> $GITHUB_OUTPUT + + - name: Fetch binaries from s3 based on version + run: | + . ./.github/scripts/common/lib.sh + + VERSION="${{ inputs.release_tag }}" + if [[ ${{ inputs.package }} == 'polkadot' ]]; then + packages=(polkadot polkadot-prepare-worker polkadot-execute-worker) + for package in "${packages[@]}"; do + fetch_release_artifacts_from_s3 $package ${{ inputs.target }} + done + else + fetch_release_artifacts_from_s3 ${{ inputs.package }} ${{ inputs.target }} + fi + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 + with: + aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Upload ${{ inputs.package }} ${{ inputs.target }} artifacts to s3 + run: | + . ./.github/scripts/release/release_lib.sh + + if [[ ${{ inputs.package }} == 'polkadot' ]]; then + packages=(polkadot polkadot-prepare-worker polkadot-execute-worker) + for package in "${packages[@]}"; do + upload_s3_release $package ${{ steps.prepare_final_tag.outputs.final_tag }} ${{ inputs.target }} + done + else + upload_s3_release ${{ inputs.package }} ${{ steps.prepare_final_tag.outputs.final_tag }} ${{ inputs.target }} + fi diff --git a/.github/workflows/release-reusable-rc-buid.yml b/.github/workflows/release-reusable-rc-buid.yml index dc1b4553eb9b..0222b2aa91e2 100644 --- a/.github/workflows/release-reusable-rc-buid.yml +++ b/.github/workflows/release-reusable-rc-buid.yml @@ -133,7 +133,7 @@ jobs: - name: Upload ${{ matrix.binaries }} artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: - name: ${{ matrix.binaries }} + name: ${{ matrix.binaries }}_${{ inputs.target }} path: /artifacts/${{ matrix.binaries }} build-macos-rc: @@ -285,7 +285,7 @@ jobs: - name: Upload ${{inputs.package }} artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: - name: ${{ inputs.package }} + name: ${{ inputs.package }}_${{ inputs.target }} path: target/production overwrite: true diff --git a/.github/workflows/release-reusable-s3-upload.yml b/.github/workflows/release-reusable-s3-upload.yml index f85466bc8c07..48c7e53c6c8f 100644 --- a/.github/workflows/release-reusable-s3-upload.yml +++ b/.github/workflows/release-reusable-s3-upload.yml @@ -9,7 +9,7 @@ on: type: string release_tag: - description: Tag matching the actual release candidate with the format stableYYMM-rcX or stableYYMM-rcX + description: Tag matching the actual release candidate with the format polkadot-stableYYMM(-X)-rcX or polkadot-stableYYMM-rcX required: true type: string @@ -40,18 +40,10 @@ jobs: uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Download amd64 artifacts - if: ${{ inputs.target == 'x86_64-unknown-linux-gnu' }} uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: - name: ${{ inputs.package }} - path: artifacts/${{ inputs.package }} - - - name: Download arm artifacts - if: ${{ inputs.target == 'aarch64-apple-darwin' }} - uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 - with: - name: ${{ inputs.package }}_aarch64-apple-darwin - path: artifacts/${{ inputs.package }} + name: ${{ inputs.package }}_${{ inputs.target }} + path: release-artifacts/${{ inputs.target }}/${{ inputs.package }} - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@e3dd6a429d7300a6a4c196c26e071d42e0343502 # v4.0.2 From 34632ed68272ce2c250cb085c5ab9e53f0a2ced6 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Wed, 4 Dec 2024 13:29:13 +0100 Subject: [PATCH 097/340] Disable flaky tests reported in #6574/#6644 (#6749) Reference issues #6574 #6644 --- .gitlab/pipeline/zombienet/polkadot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 3dab49a118e5..ac4bdac7ad15 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -179,7 +179,7 @@ zombienet-polkadot-elastic-scaling-0001-basic-3cores-6s-blocks: --local-dir="${LOCAL_DIR}/elastic_scaling" --test="0001-basic-3cores-6s-blocks.zndsl" -zombienet-polkadot-elastic-scaling-0002-elastic-scaling-doesnt-break-parachains: +.zombienet-polkadot-elastic-scaling-0002-elastic-scaling-doesnt-break-parachains: extends: - .zombienet-polkadot-common before_script: @@ -233,7 +233,7 @@ zombienet-polkadot-functional-0015-coretime-shared-core: --local-dir="${LOCAL_DIR}/functional" --test="0016-approval-voting-parallel.zndsl" -zombienet-polkadot-functional-0017-sync-backing: +.zombienet-polkadot-functional-0017-sync-backing: extends: - .zombienet-polkadot-common script: From 5ca726750da563c46449f9aa915296e6c6967e61 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:47:47 +0100 Subject: [PATCH 098/340] chore: Update litep2p to v0.8.3 (#6742) ## [0.8.3] - 2024-12-03 This release includes two fixes for small memory leaks on edge-cases in the notification and request-response protocols. ### Fixed - req-resp: Fix memory leak of pending substreams ([#297](https://github.com/paritytech/litep2p/pull/297)) - notification: Fix memory leak of pending substreams ([#296](https://github.com/paritytech/litep2p/pull/296)) cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- prdoc/pr_6742.prdoc | 11 +++++++++++ substrate/client/network/src/litep2p/mod.rs | 2 +- 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_6742.prdoc diff --git a/Cargo.lock b/Cargo.lock index 863822f4ffd5..eee12dc5bc40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10225,9 +10225,9 @@ dependencies = [ [[package]] name = "litep2p" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "569e7dbec8a0d4b08d30f4942cd579cfe8db5d3f83f8604abe61697c38d17e73" +checksum = "14e490b5a6d486711fd0284bd30e607a287343f2935a59a9192bd7109e85f443" dependencies = [ "async-trait", "bs58", diff --git a/Cargo.toml b/Cargo.toml index ecc385504181..49fdc198fe33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -848,7 +848,7 @@ linked-hash-map = { version = "0.5.4" } linked_hash_set = { version = "0.1.4" } linregress = { version = "0.5.1" } lite-json = { version = "0.2.0", default-features = false } -litep2p = { version = "0.8.2", features = ["websocket"] } +litep2p = { version = "0.8.3", features = ["websocket"] } log = { version = "0.4.22", default-features = false } macro_magic = { version = "0.5.1" } maplit = { version = "1.0.2" } diff --git a/prdoc/pr_6742.prdoc b/prdoc/pr_6742.prdoc new file mode 100644 index 000000000000..92c3755a3c28 --- /dev/null +++ b/prdoc/pr_6742.prdoc @@ -0,0 +1,11 @@ +title: Update litep2p backend to v0.8.3 +doc: +- audience: Node Dev + description: |- + This release includes two fixes for small memory leaks on edge-cases in the notification and request-response protocols. + While at it, have downgraded a log message from litep2p. + +crates: +- name: sc-network + bump: patch + diff --git a/substrate/client/network/src/litep2p/mod.rs b/substrate/client/network/src/litep2p/mod.rs index 6d3575fc2b6b..b6d64b34d64a 100644 --- a/substrate/client/network/src/litep2p/mod.rs +++ b/substrate/client/network/src/litep2p/mod.rs @@ -753,7 +753,7 @@ impl NetworkBackend for Litep2pNetworkBac } if self.litep2p.add_known_address(peer.into(), iter::once(address.clone())) == 0usize { - log::warn!( + log::debug!( target: LOG_TARGET, "couldn't add known address ({address}) for {peer:?}, unsupported transport" ); From 2779043b0f667b75062cdc085e8052190b78cb20 Mon Sep 17 00:00:00 2001 From: Egor_P Date: Wed, 4 Dec 2024 17:43:51 +0100 Subject: [PATCH 099/340] [CI/CD] Fix permissions issue in the backport to stable flow (#6754) This PR has changes to the `command-backport.yml`: - swapped action that creates backports PRs from master to the stable branches and added another app with more permissions --- .github/workflows/command-backport.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/command-backport.yml b/.github/workflows/command-backport.yml index eecf0ac72d2c..53dcea2f1d6d 100644 --- a/.github/workflows/command-backport.yml +++ b/.github/workflows/command-backport.yml @@ -29,12 +29,13 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Generate token - id: generate_token - uses: tibdex/github-app-token@v2.1.0 + - name: Generate content write token for the release automation + id: generate_write_token + uses: actions/create-github-app-token@v1 with: - app_id: ${{ secrets.CMD_BOT_APP_ID }} - private_key: ${{ secrets.CMD_BOT_APP_KEY }} + app-id: ${{ vars.RELEASE_AUTOMATION_APP_ID }} + private-key: ${{ secrets.RELEASE_AUTOMATION_APP_PRIVATE_KEY }} + owner: paritytech - name: Create backport pull requests uses: korthout/backport-action@v3 @@ -42,7 +43,7 @@ jobs: with: target_branches: stable2407 stable2409 stable2412 merge_commits: skip - github_token: ${{ steps.generate_token.outputs.token }} + github_token: ${{ steps.generate_write_token.outputs.token }} pull_description: | Backport #${pull_number} into `${target_branch}` from ${pull_author}. @@ -86,7 +87,7 @@ jobs: const reviewer = '${{ github.event.pull_request.user.login }}'; for (const pullNumber of pullNumbers) { - await github.pulls.createReviewRequest({ + await github.pulls.requestReviewers({ owner: context.repo.owner, repo: context.repo.repo, pull_number: parseInt(pullNumber), From 82117ad53fc68e8097183e759926b62265ffff0a Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Wed, 4 Dec 2024 18:55:33 +0100 Subject: [PATCH 100/340] wasm-builder: Use riscv32emac-unknown-none-polkavm.json target (#6419) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Closes #6335. ## Integration N/A ## Review Notes `RuntimeTarget` is converted to return path to the custom target JSON file --------- Signed-off-by: Jarkko Sakkinen Co-authored-by: Alexander Theißen Co-authored-by: Koute --- Cargo.lock | 12 +- Cargo.toml | 4 +- prdoc/pr_6419.prdoc | 12 ++ substrate/utils/wasm-builder/src/builder.rs | 3 +- substrate/utils/wasm-builder/src/lib.rs | 105 +++++++----------- .../utils/wasm-builder/src/prerequisites.rs | 7 +- .../utils/wasm-builder/src/wasm_project.rs | 28 +++-- 7 files changed, 87 insertions(+), 84 deletions(-) create mode 100644 prdoc/pr_6419.prdoc diff --git a/Cargo.lock b/Cargo.lock index eee12dc5bc40..dad578ba0c1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14752,7 +14752,7 @@ dependencies = [ "anyhow", "frame-system 28.0.0", "log", - "polkavm-linker 0.17.0", + "polkavm-linker 0.17.1", "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", @@ -19936,9 +19936,9 @@ dependencies = [ [[package]] name = "polkavm-linker" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d359dc721d2cc9b555ebb3558c305112ddc5bdac09d26f95f2f7b49c1f2db7e9" +checksum = "0422ead3030d5cde69e2206dbc7d65da872b121876507cd5363f6c6e6aa45157" dependencies = [ "dirs", "gimli 0.31.1", @@ -26495,7 +26495,7 @@ dependencies = [ "libsecp256k1", "log", "parity-scale-codec", - "polkavm-derive 0.9.1", + "polkavm-derive 0.17.0", "rustversion", "secp256k1 0.28.2", "sp-core 28.0.0", @@ -26979,7 +26979,7 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive 0.9.1", + "polkavm-derive 0.17.0", "primitive-types 0.13.1", "rustversion", "sp-core 28.0.0", @@ -28623,7 +28623,7 @@ dependencies = [ "merkleized-metadata", "parity-scale-codec", "parity-wasm", - "polkavm-linker 0.9.2", + "polkavm-linker 0.17.1", "sc-executor 0.32.0", "shlex", "sp-core 28.0.0", diff --git a/Cargo.toml b/Cargo.toml index 49fdc198fe33..383fc46c4e76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1090,8 +1090,8 @@ polkadot-test-client = { path = "polkadot/node/test/client" } polkadot-test-runtime = { path = "polkadot/runtime/test-runtime" } polkadot-test-service = { path = "polkadot/node/test/service" } polkavm = { version = "0.9.3", default-features = false } -polkavm-derive = "0.9.1" -polkavm-linker = "0.9.2" +polkavm-derive = "0.17.0" +polkavm-linker = "0.17.1" portpicker = { version = "0.1.1" } pretty_assertions = { version = "1.3.0" } primitive-types = { version = "0.13.1", default-features = false, features = [ diff --git a/prdoc/pr_6419.prdoc b/prdoc/pr_6419.prdoc new file mode 100644 index 000000000000..6cc155d64b91 --- /dev/null +++ b/prdoc/pr_6419.prdoc @@ -0,0 +1,12 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Use the custom target riscv32emac-unknown-none-polkavm +doc: + - audience: Runtime Dev + description: | + Closes: https://github.com/paritytech/polkadot-sdk/issues/6335 + +crates: +- name: substrate-wasm-builder + bump: patch diff --git a/substrate/utils/wasm-builder/src/builder.rs b/substrate/utils/wasm-builder/src/builder.rs index a40aafe1d812..5bdc743eac31 100644 --- a/substrate/utils/wasm-builder/src/builder.rs +++ b/substrate/utils/wasm-builder/src/builder.rs @@ -235,7 +235,8 @@ impl WasmBuilder { /// Build the WASM binary. pub fn build(mut self) { - let target = crate::runtime_target(); + let target = RuntimeTarget::new(); + if target == RuntimeTarget::Wasm { if self.export_heap_base { self.rust_flags.push("-Clink-arg=--export=__heap_base".into()); diff --git a/substrate/utils/wasm-builder/src/lib.rs b/substrate/utils/wasm-builder/src/lib.rs index 420ecd63e1dc..ce90f492e08f 100644 --- a/substrate/utils/wasm-builder/src/lib.rs +++ b/substrate/utils/wasm-builder/src/lib.rs @@ -112,7 +112,6 @@ //! wasm32-unknown-unknown --toolchain nightly-2020-02-20`. use std::{ - collections::BTreeSet, env, fs, io::BufRead, path::{Path, PathBuf}, @@ -254,26 +253,22 @@ struct CargoCommand { program: String, args: Vec, version: Option, - target_list: Option>, } impl CargoCommand { fn new(program: &str) -> Self { let version = Self::extract_version(program, &[]); - let target_list = Self::extract_target_list(program, &[]); - CargoCommand { program: program.into(), args: Vec::new(), version, target_list } + CargoCommand { program: program.into(), args: Vec::new(), version } } fn new_with_args(program: &str, args: &[&str]) -> Self { let version = Self::extract_version(program, args); - let target_list = Self::extract_target_list(program, args); CargoCommand { program: program.into(), args: args.iter().map(ToString::to_string).collect(), version, - target_list, } } @@ -294,23 +289,6 @@ impl CargoCommand { Version::extract(&version) } - fn extract_target_list(program: &str, args: &[&str]) -> Option> { - // This is technically an unstable option, but we don't care because we only need this - // to build RISC-V runtimes, and those currently require a specific nightly toolchain - // anyway, so it's totally fine for this to fail in other cases. - let list = Command::new(program) - .args(args) - .args(&["rustc", "-Z", "unstable-options", "--print", "target-list"]) - // Make sure if we're called from within a `build.rs` the host toolchain won't override - // a rustup toolchain we've picked. - .env_remove("RUSTC") - .output() - .ok() - .and_then(|o| String::from_utf8(o.stdout).ok())?; - - Some(list.trim().split("\n").map(ToString::to_string).collect()) - } - /// Returns the version of this cargo command or `None` if it failed to extract the version. fn version(&self) -> Option { self.version @@ -326,19 +304,10 @@ impl CargoCommand { fn supports_substrate_runtime_env(&self, target: RuntimeTarget) -> bool { match target { RuntimeTarget::Wasm => self.supports_substrate_runtime_env_wasm(), - RuntimeTarget::Riscv => self.supports_substrate_runtime_env_riscv(), + RuntimeTarget::Riscv => true, } } - /// Check if the supplied cargo command supports our RISC-V runtime environment. - fn supports_substrate_runtime_env_riscv(&self) -> bool { - let Some(target_list) = self.target_list.as_ref() else { return false }; - // This is our custom target which currently doesn't exist on any upstream toolchain, - // so if it exists it's guaranteed to be our custom toolchain and have have everything - // we need, so any further version checks are unnecessary at this point. - target_list.contains("riscv32ema-unknown-none-elf") - } - /// Check if the supplied cargo command supports our Substrate wasm environment. /// /// This means that either the cargo version is at minimum 1.68.0 or this is a nightly cargo. @@ -409,13 +378,6 @@ fn get_bool_environment_variable(name: &str) -> Option { } } -/// Returns whether we need to also compile the standard library when compiling the runtime. -fn build_std_required() -> bool { - let default = runtime_target() == RuntimeTarget::Wasm; - - crate::get_bool_environment_variable(crate::WASM_BUILD_STD).unwrap_or(default) -} - #[derive(Copy, Clone, PartialEq, Eq)] enum RuntimeTarget { Wasm, @@ -423,36 +385,55 @@ enum RuntimeTarget { } impl RuntimeTarget { - fn rustc_target(self) -> &'static str { + /// Creates a new instance. + fn new() -> Self { + let Some(value) = env::var_os(RUNTIME_TARGET) else { + return Self::Wasm; + }; + + if value == "wasm" { + Self::Wasm + } else if value == "riscv" { + Self::Riscv + } else { + build_helper::warning!( + "RUNTIME_TARGET environment variable must be set to either \"wasm\" or \"riscv\"" + ); + std::process::exit(1); + } + } + + /// Figures out the target parameter value for rustc. + fn rustc_target(self) -> String { match self { - RuntimeTarget::Wasm => "wasm32-unknown-unknown", - RuntimeTarget::Riscv => "riscv32ema-unknown-none-elf", + RuntimeTarget::Wasm => "wasm32-unknown-unknown".to_string(), + RuntimeTarget::Riscv => { + let path = polkavm_linker::target_json_32_path().expect("riscv not found"); + path.into_os_string().into_string().unwrap() + }, } } - fn build_subdirectory(self) -> &'static str { - // Keep the build directories separate so that when switching between - // the targets we won't trigger unnecessary rebuilds. + /// Figures out the target directory name used by cargo. + fn rustc_target_dir(self) -> &'static str { match self { - RuntimeTarget::Wasm => "wbuild", - RuntimeTarget::Riscv => "rbuild", + RuntimeTarget::Wasm => "wasm32-unknown-unknown", + RuntimeTarget::Riscv => "riscv32emac-unknown-none-polkavm", } } -} -fn runtime_target() -> RuntimeTarget { - let Some(value) = env::var_os(RUNTIME_TARGET) else { - return RuntimeTarget::Wasm; - }; + /// Figures out the build-std argument. + fn rustc_target_build_std(self) -> Option<&'static str> { + if !crate::get_bool_environment_variable(crate::WASM_BUILD_STD).unwrap_or(true) { + return None; + } - if value == "wasm" { - RuntimeTarget::Wasm - } else if value == "riscv" { - RuntimeTarget::Riscv - } else { - build_helper::warning!( - "the '{RUNTIME_TARGET}' environment variable has an invalid value; it must be either 'wasm' or 'riscv'" - ); - std::process::exit(1); + // This is a nightly-only flag. + let arg = match self { + RuntimeTarget::Wasm => "build-std", + RuntimeTarget::Riscv => "build-std=core,alloc", + }; + + Some(arg) } } diff --git a/substrate/utils/wasm-builder/src/prerequisites.rs b/substrate/utils/wasm-builder/src/prerequisites.rs index 4de6b87f618d..9abfd1725237 100644 --- a/substrate/utils/wasm-builder/src/prerequisites.rs +++ b/substrate/utils/wasm-builder/src/prerequisites.rs @@ -196,11 +196,14 @@ fn check_wasm_toolchain_installed( error, colorize_aux_message(&"-".repeat(60)), )) - } + }; } let version = dummy_crate.get_rustc_version(); - if crate::build_std_required() { + + let target = RuntimeTarget::new(); + assert!(target == RuntimeTarget::Wasm); + if target.rustc_target_build_std().is_some() { if let Some(sysroot) = dummy_crate.get_sysroot() { let src_path = Path::new(sysroot.trim()).join("lib").join("rustlib").join("src").join("rust"); diff --git a/substrate/utils/wasm-builder/src/wasm_project.rs b/substrate/utils/wasm-builder/src/wasm_project.rs index 26edd2ea1f22..6530e4c22fb9 100644 --- a/substrate/utils/wasm-builder/src/wasm_project.rs +++ b/substrate/utils/wasm-builder/src/wasm_project.rs @@ -109,6 +109,15 @@ fn crate_metadata(cargo_manifest: &Path) -> Metadata { crate_metadata } +/// Keep the build directories separate so that when switching between the +/// targets we won't trigger unnecessary rebuilds. +fn build_subdirectory(target: RuntimeTarget) -> &'static str { + match target { + RuntimeTarget::Wasm => "wbuild", + RuntimeTarget::Riscv => "rbuild", + } +} + /// Creates the WASM project, compiles the WASM binary and compacts the WASM binary. /// /// # Returns @@ -125,7 +134,7 @@ pub(crate) fn create_and_compile( #[cfg(feature = "metadata-hash")] enable_metadata_hash: Option, ) -> (Option, WasmBinaryBloaty) { let runtime_workspace_root = get_wasm_workspace_root(); - let runtime_workspace = runtime_workspace_root.join(target.build_subdirectory()); + let runtime_workspace = runtime_workspace_root.join(build_subdirectory(target)); let crate_metadata = crate_metadata(orig_project_cargo_toml); @@ -770,7 +779,7 @@ impl BuildConfiguration { .collect::>() .iter() .rev() - .take_while(|c| c.as_os_str() != target.build_subdirectory()) + .take_while(|c| c.as_os_str() != build_subdirectory(target)) .last() .expect("We put the runtime project within a `target/.../[rw]build` path; qed") .as_os_str() @@ -841,9 +850,7 @@ fn build_bloaty_blob( "-C target-cpu=mvp -C target-feature=-sign-ext -C link-arg=--export-table ", ); }, - RuntimeTarget::Riscv => { - rustflags.push_str("-C target-feature=+lui-addi-fusion -C relocation-model=pie -C link-arg=--emit-relocs -C link-arg=--unique "); - }, + RuntimeTarget::Riscv => (), } rustflags.push_str(default_rustflags); @@ -907,10 +914,9 @@ fn build_bloaty_blob( // // So here we force the compiler to also compile the standard library crates for us // to make sure that they also only use the MVP features. - if crate::build_std_required() { - // Unfortunately this is still a nightly-only flag, but FWIW it is pretty widely used - // so it's unlikely to break without a replacement. - build_cmd.arg("-Z").arg("build-std"); + if let Some(arg) = target.rustc_target_build_std() { + build_cmd.arg("-Z").arg(arg); + if !cargo_cmd.supports_nightly_features() { build_cmd.env("RUSTC_BOOTSTRAP", "1"); } @@ -934,7 +940,7 @@ fn build_bloaty_blob( let blob_name = get_blob_name(target, &manifest_path); let target_directory = project .join("target") - .join(target.rustc_target()) + .join(target.rustc_target_dir()) .join(blob_build_profile.directory()); match target { RuntimeTarget::Riscv => { @@ -968,7 +974,7 @@ fn build_bloaty_blob( }, }; - std::fs::write(&polkavm_path, program.as_bytes()) + std::fs::write(&polkavm_path, program) .expect("writing the blob to a file always works"); } From 40e85eae7b373f34060548ab7ba9a7c68fdd85fa Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 09:11:55 +0200 Subject: [PATCH 101/340] separate inbound pallet index IDs for v1 and v2 --- .../pallets/inbound-queue-v2/src/mock.rs | 2 +- .../assets/asset-hub-westend/src/xcm_config.rs | 16 ++++++++++++---- .../src/bridge_to_ethereum_config.rs | 6 +++--- .../parachains/runtimes/constants/src/westend.rs | 3 ++- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 28db523bd7a7..c981c99bf3aa 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -147,7 +147,7 @@ parameter_types! { pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); pub const WethAddress: H160 = H160(WETH_ADDRESS); - pub const InboundQueuePalletInstance: u8 = 80; + pub const InboundQueuePalletInstance: u8 = 84; pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index b4e938f1f8b5..8045a4c9255f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -649,7 +649,7 @@ pub mod bridging { use assets_common::matching::FromNetwork; use sp_std::collections::btree_set::BTreeSet; use testnet_parachains_constants::westend::snowbridge::{ - EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX, + EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX_V1, INBOUND_QUEUE_PALLET_INDEX_V2 }; parameter_types! { @@ -659,11 +659,18 @@ pub mod bridging { /// Polkadot uses 10 decimals, Kusama,Rococo,Westend 12 decimals. pub const DefaultBridgeHubEthereumBaseFee: Balance = 2_750_872_500_000; pub storage BridgeHubEthereumBaseFee: Balance = DefaultBridgeHubEthereumBaseFee::get(); - pub SiblingBridgeHubWithEthereumInboundQueueInstance: Location = Location::new( + pub SiblingBridgeHubWithEthereumInboundQueueV1Instance: Location = Location::new( 1, [ Parachain(SiblingBridgeHubParaId::get()), - PalletInstance(INBOUND_QUEUE_PALLET_INDEX) + PalletInstance(INBOUND_QUEUE_PALLET_INDEX_V1) + ] + ); + pub SiblingBridgeHubWithEthereumInboundQueueV2Instance: Location = Location::new( + 1, + [ + Parachain(SiblingBridgeHubParaId::get()), + PalletInstance(INBOUND_QUEUE_PALLET_INDEX_V2) ] ); @@ -684,7 +691,8 @@ pub mod bridging { /// Universal aliases pub UniversalAliases: BTreeSet<(Location, Junction)> = BTreeSet::from_iter( sp_std::vec![ - (SiblingBridgeHubWithEthereumInboundQueueInstance::get(), GlobalConsensus(EthereumNetwork::get().into())), + (SiblingBridgeHubWithEthereumInboundQueueV1Instance::get(), GlobalConsensus(EthereumNetwork::get().into())), + (SiblingBridgeHubWithEthereumInboundQueueV2Instance::get(), GlobalConsensus(EthereumNetwork::get().into())), ] ); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 469a0ebdb2fb..b46291f09e9e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -30,7 +30,7 @@ use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, fee::WeightToFee, - snowbridge::{EthereumLocation, EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX}, + snowbridge::{EthereumLocation, EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX_V1, INBOUND_QUEUE_PALLET_INDEX_V2}, }; use crate::xcm_config::RelayNetwork; @@ -89,7 +89,7 @@ impl snowbridge_pallet_inbound_queue::Config for Runtime { type MessageConverter = snowbridge_router_primitives::inbound::v1::MessageToXcm< CreateAssetCall, CreateAssetDeposit, - ConstU8, + ConstU8, AccountId, Balance, EthereumSystem, @@ -121,7 +121,7 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { type Balance = Balance; type MessageConverter = snowbridge_router_primitives::inbound::v2::MessageToXcm< EthereumNetwork, - ConstU8, + ConstU8, EthereumSystem, WethAddress, EthereumGatewayAddress, diff --git a/cumulus/parachains/runtimes/constants/src/westend.rs b/cumulus/parachains/runtimes/constants/src/westend.rs index 8c4c0c594359..b880e229bc1f 100644 --- a/cumulus/parachains/runtimes/constants/src/westend.rs +++ b/cumulus/parachains/runtimes/constants/src/westend.rs @@ -174,7 +174,8 @@ pub mod snowbridge { use xcm::prelude::{Location, NetworkId}; /// The pallet index of the Ethereum inbound queue pallet in the bridge hub runtime. - pub const INBOUND_QUEUE_PALLET_INDEX: u8 = 80; + pub const INBOUND_QUEUE_PALLET_INDEX_V1: u8 = 80; + pub const INBOUND_QUEUE_PALLET_INDEX_V2: u8 = 84; parameter_types! { /// Network and location for the Ethereum chain. On Westend, the Ethereum chain bridged From d70ac6c3f2a87eb739214278dfd87accac15aa55 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 09:19:49 +0200 Subject: [PATCH 102/340] fix reentrancy bug --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 00273ff107e0..ffe21dfe4fd5 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -238,12 +238,12 @@ pub mod pallet { // Attempt to forward XCM to AH - let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; - Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); - // Set nonce flag to true Nonce::::set(envelope.nonce.into()); + let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; + Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); + Ok(()) } From bc45bbd9e86591919c0fc65e27c19e2d640af65b Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 09:36:21 +0200 Subject: [PATCH 103/340] send all assets in a single instruction --- .../primitives/router/src/inbound/v2.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index f730d0adedeb..8177c66052a5 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -146,10 +146,13 @@ where ReserveAssetDeposited(fee.clone().into()), PayFees { asset: fee }, ]; + let mut reserve_assets = vec![]; + let mut withdraw_assets = vec![]; for asset in &message.assets { match asset { Asset::NativeTokenERC20 { token_id, value } => { + let token_location: Location = Location::new( 2, [ @@ -157,16 +160,25 @@ where AccountKey20 { network: None, key: (*token_id).into() }, ], ); - instructions.push(ReserveAssetDeposited((token_location, *value).into())); + let asset: XcmAsset = (token_location, *value).into(); + reserve_assets.push(asset); }, Asset::ForeignTokenERC20 { token_id, value } => { let asset_id = ConvertAssetId::convert(&token_id) .ok_or(ConvertMessageError::InvalidAsset)?; - instructions.push(WithdrawAsset((asset_id, *value).into())); + let asset: XcmAsset = (asset_id, *value).into(); + withdraw_assets.push(asset); }, } } + if reserve_assets.len() > 0 { + instructions.push(ReserveAssetDeposited(reserve_assets.into())); + } + if withdraw_assets.len() > 0 { + instructions.push(WithdrawAsset(withdraw_assets.into())); + } + if let Some(claimer) = message.claimer { let claimer = Junction::decode(&mut claimer.as_ref()) .map_err(|_| ConvertMessageError::InvalidClaimer)?; From 9ffb3e1613c1b3067b3b02812604cb5cf89bd24b Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 09:43:37 +0200 Subject: [PATCH 104/340] use descend origin instead of alias origin --- bridges/snowbridge/primitives/router/src/inbound/v2.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 8177c66052a5..2872c13c9b9d 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -190,14 +190,7 @@ where // the original sender on Ethereum. Important to be before the arbitrary XCM that is // appended to the message on the next line. if message.origin != GatewayProxyAddress::get() { - let origin_location = Location::new( - 2, - [ - GlobalConsensus(network), - AccountKey20 { key: message.origin.into(), network: None }, - ], - ); - instructions.push(AliasOrigin(origin_location.into())); + instructions.push(DescendOrigin(AccountKey20 { key: message.origin.into(), network: None}.into())); } // Add the XCM sent in the message to the end of the xcm instruction From 85d355e1d8d5d2e4d0f8d4c5a2d580c92611593a Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 09:56:37 +0200 Subject: [PATCH 105/340] use claimer for refund surplus, otherwise relayer --- .../snowbridge/primitives/router/src/inbound/v2.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 2872c13c9b9d..d03a6c1c4eba 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -152,7 +152,6 @@ where for asset in &message.assets { match asset { Asset::NativeTokenERC20 { token_id, value } => { - let token_location: Location = Location::new( 2, [ @@ -179,10 +178,13 @@ where instructions.push(WithdrawAsset(withdraw_assets.into())); } + let mut refund_surplus_to = origin_account_location; + if let Some(claimer) = message.claimer { let claimer = Junction::decode(&mut claimer.as_ref()) .map_err(|_| ConvertMessageError::InvalidClaimer)?; let claimer_location: Location = Location::new(0, [claimer.into()]); + refund_surplus_to = claimer_location.clone(); instructions.push(SetAssetClaimer { location: claimer_location }); } @@ -190,7 +192,9 @@ where // the original sender on Ethereum. Important to be before the arbitrary XCM that is // appended to the message on the next line. if message.origin != GatewayProxyAddress::get() { - instructions.push(DescendOrigin(AccountKey20 { key: message.origin.into(), network: None}.into())); + instructions.push(DescendOrigin( + AccountKey20 { key: message.origin.into(), network: None }.into(), + )); } // Add the XCM sent in the message to the end of the xcm instruction @@ -198,10 +202,10 @@ where let appendix = vec![ RefundSurplus, - // Refund excess fees to the relayer + // Refund excess fees to the claimer, if present, otherwise the relayer DepositAsset { assets: Wild(AllOf { id: AssetId(fee_asset.into()), fun: WildFungible }), - beneficiary: origin_account_location, + beneficiary: refund_surplus_to, }, ]; From 654d60c3a373cb5268f50e6cd9274580e12dce87 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:42:32 +0100 Subject: [PATCH 106/340] ci: skip check-semver in master and merge queue (#6762) tbd --- .github/workflows/check-semver.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index e9bedd16e6d1..11b386da21e9 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -78,6 +78,11 @@ jobs: - name: check semver run: | + if [ -z "$PR" ]; then + echo "Skipping master/merge queue" + exit 0 + fi + export CARGO_TARGET_DIR=target export RUSTFLAGS='-A warnings -A missing_docs' export SKIP_WASM_BUILD=1 From f4a196ab1473856c9c5992239fcc2f14c2c42914 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Thu, 5 Dec 2024 09:54:31 +0100 Subject: [PATCH 107/340] Optimize initialization of networking protocol benchmarks (#6636) # Description These changes should enhance the quality of benchmark results by excluding worker initialization time from the measurements and reducing the overall duration of the benchmarks. ### Integration It should not affect any downstream projects. ### Review Notes - Workers initialize once per benchmark to avoid side effects. - The listen address is assigned when a worker starts. - Benchmarks are divided into two groups by size to create better charts for comparison. --------- Co-authored-by: GitHub Action --- prdoc/pr_6636.prdoc | 9 + .../network/benches/notifications_protocol.rs | 386 ++++++++---------- .../benches/request_response_protocol.rs | 318 ++++++++------- 3 files changed, 353 insertions(+), 360 deletions(-) create mode 100644 prdoc/pr_6636.prdoc diff --git a/prdoc/pr_6636.prdoc b/prdoc/pr_6636.prdoc new file mode 100644 index 000000000000..1db5fd54d971 --- /dev/null +++ b/prdoc/pr_6636.prdoc @@ -0,0 +1,9 @@ +title: Optimize initialization of networking protocol benchmarks +doc: +- audience: Node Dev + description: |- + These changes should enhance the quality of benchmark results by excluding worker initialization time from the measurements and reducing the overall duration of the benchmarks. + +crates: +- name: sc-network + validate: false diff --git a/substrate/client/network/benches/notifications_protocol.rs b/substrate/client/network/benches/notifications_protocol.rs index c1e18c7b7f47..40a810d616b5 100644 --- a/substrate/client/network/benches/notifications_protocol.rs +++ b/substrate/client/network/benches/notifications_protocol.rs @@ -25,55 +25,42 @@ use sc_network::{ FullNetworkConfiguration, MultiaddrWithPeerId, NetworkConfiguration, NonReservedPeerMode, NotificationHandshake, Params, ProtocolId, Role, SetConfig, }, - service::traits::NotificationEvent, + service::traits::{NetworkService, NotificationEvent}, Litep2pNetworkBackend, NetworkBackend, NetworkWorker, NotificationMetrics, NotificationService, - Roles, + PeerId, Roles, }; use sc_network_common::{sync::message::BlockAnnouncesHandshake, ExHashT}; -use sc_network_types::build_multiaddr; use sp_core::H256; use sp_runtime::traits::{Block as BlockT, Zero}; -use std::{ - net::{IpAddr, Ipv4Addr, TcpListener}, - str::FromStr, -}; +use std::{sync::Arc, time::Duration}; use substrate_test_runtime_client::runtime; +use tokio::{sync::Mutex, task::JoinHandle}; -const MAX_SIZE: u64 = 2u64.pow(30); -const SAMPLE_SIZE: usize = 50; -const NOTIFICATIONS: usize = 50; -const EXPONENTS: &[(u32, &'static str)] = &[ - (6, "64B"), - (9, "512B"), - (12, "4KB"), - (15, "64KB"), - (18, "256KB"), - (21, "2MB"), - (24, "16MB"), - (27, "128MB"), +const SMALL_PAYLOAD: &[(u32, usize, &'static str)] = &[ + // (Exponent of size, number of notifications, label) + (6, 100, "64B"), + (9, 100, "512B"), + (12, 100, "4KB"), + (15, 100, "64KB"), ]; - -// TODO: It's be better to bind system-provided port when initializing the worker -fn get_listen_address() -> sc_network::Multiaddr { - let ip = Ipv4Addr::from_str("127.0.0.1").unwrap(); - let listener = TcpListener::bind((IpAddr::V4(ip), 0)).unwrap(); // Bind to a random port - let local_addr = listener.local_addr().unwrap(); - let port = local_addr.port(); - - build_multiaddr!(Ip4(ip), Tcp(port)) -} +const LARGE_PAYLOAD: &[(u32, usize, &'static str)] = &[ + // (Exponent of size, number of notifications, label) + (18, 10, "256KB"), + (21, 10, "2MB"), + (24, 10, "16MB"), + (27, 10, "128MB"), +]; +const MAX_SIZE: u64 = 2u64.pow(30); fn create_network_worker( - listen_addr: sc_network::Multiaddr, -) -> (N, Box) +) -> (N, Arc, Arc>>) where B: BlockT + 'static, H: ExHashT, N: NetworkBackend, { let role = Role::Full; - let mut net_conf = NetworkConfiguration::new_local(); - net_conf.listen_addresses = vec![listen_addr]; + let net_conf = NetworkConfiguration::new_local(); let network_config = FullNetworkConfiguration::::new(&net_conf, None); let genesis_hash = runtime::Hash::zero(); let (block_announce_config, notification_service) = N::notification_config( @@ -110,96 +97,122 @@ where notification_metrics: NotificationMetrics::new(None), }) .unwrap(); + let network_service = worker.network_service(); + let notification_service = Arc::new(Mutex::new(notification_service)); - (worker, notification_service) + (worker, network_service, notification_service) } -async fn run_serially(size: usize, limit: usize) +struct BenchSetup { + notification_service1: Arc>>, + notification_service2: Arc>>, + peer_id2: PeerId, + handle1: JoinHandle<()>, + handle2: JoinHandle<()>, +} + +impl Drop for BenchSetup { + fn drop(&mut self) { + self.handle1.abort(); + self.handle2.abort(); + } +} + +fn setup_workers(rt: &tokio::runtime::Runtime) -> Arc where B: BlockT + 'static, H: ExHashT, N: NetworkBackend, { - let listen_address1 = get_listen_address(); - let listen_address2 = get_listen_address(); - let (worker1, mut notification_service1) = create_network_worker::(listen_address1); - let (worker2, mut notification_service2) = - create_network_worker::(listen_address2.clone()); - let peer_id2: sc_network::PeerId = worker2.network_service().local_peer_id().into(); + let _guard = rt.enter(); - worker1 - .network_service() - .add_reserved_peer(MultiaddrWithPeerId { multiaddr: listen_address2, peer_id: peer_id2 }) - .unwrap(); + let (worker1, network_service1, notification_service1) = create_network_worker::(); + let (worker2, network_service2, notification_service2) = create_network_worker::(); + let peer_id2: sc_network::PeerId = network_service2.local_peer_id().into(); + let handle1 = tokio::spawn(worker1.run()); + let handle2 = tokio::spawn(worker2.run()); - let network1_run = worker1.run(); - let network2_run = worker2.run(); - let (tx, rx) = async_channel::bounded(10); + let ready = tokio::spawn({ + let notification_service1 = Arc::clone(¬ification_service1); + let notification_service2 = Arc::clone(¬ification_service2); - let network1 = tokio::spawn(async move { - let mut sent_counter = 0; - tokio::pin!(network1_run); - loop { - tokio::select! { - _ = &mut network1_run => {}, - event = notification_service1.next_event() => { - match event { - Some(NotificationEvent::NotificationStreamOpened { .. }) => { - sent_counter += 1; - notification_service1 - .send_async_notification(&peer_id2, vec![0; size]) - .await - .unwrap(); - }, - Some(NotificationEvent::NotificationStreamClosed { .. }) => { - if sent_counter >= limit { - break; - } - panic!("Unexpected stream closure {:?}", event); - } - event => panic!("Unexpected event {:?}", event), - }; - }, - message = rx.recv() => { - match message { - Ok(Some(_)) => { - sent_counter += 1; - notification_service1 - .send_async_notification(&peer_id2, vec![0; size]) - .await - .unwrap(); - }, - Ok(None) => break, - Err(err) => panic!("Unexpected error {:?}", err), + async move { + let listen_address2 = { + while network_service2.listen_addresses().is_empty() { + tokio::time::sleep(Duration::from_millis(10)).await; + } + network_service2.listen_addresses()[0].clone() + }; + network_service1 + .add_reserved_peer(MultiaddrWithPeerId { + multiaddr: listen_address2, + peer_id: peer_id2, + }) + .unwrap(); - } + let mut notification_service1 = notification_service1.lock().await; + let mut notification_service2 = notification_service2.lock().await; + loop { + tokio::select! { + Some(event) = notification_service1.next_event() => { + if let NotificationEvent::NotificationStreamOpened { .. } = event { + break; + } + }, + Some(event) = notification_service2.next_event() => { + if let NotificationEvent::ValidateInboundSubstream { result_tx, .. } = event { + result_tx.send(sc_network::service::traits::ValidationResult::Accept).unwrap(); + } + }, } } } }); - let network2 = tokio::spawn(async move { - let mut received_counter = 0; - tokio::pin!(network2_run); - loop { - tokio::select! { - _ = &mut network2_run => {}, - event = notification_service2.next_event() => { - match event { - Some(NotificationEvent::ValidateInboundSubstream { result_tx, .. }) => { - result_tx.send(sc_network::service::traits::ValidationResult::Accept).unwrap(); - }, - Some(NotificationEvent::NotificationStreamOpened { .. }) => {}, - Some(NotificationEvent::NotificationReceived { .. }) => { - received_counter += 1; - if received_counter >= limit { - let _ = tx.send(None).await; - break - } - let _ = tx.send(Some(())).await; - }, - event => panic!("Unexpected event {:?}", event), - }; - }, + + tokio::task::block_in_place(|| { + let _ = tokio::runtime::Handle::current().block_on(ready); + }); + + Arc::new(BenchSetup { + notification_service1, + notification_service2, + peer_id2, + handle1, + handle2, + }) +} + +async fn run_serially(setup: Arc, size: usize, limit: usize) { + let (tx, rx) = async_channel::bounded(1); + let _ = tx.send(Some(())).await; + let network1 = tokio::spawn({ + let notification_service1 = Arc::clone(&setup.notification_service1); + let peer_id2 = setup.peer_id2; + async move { + let mut notification_service1 = notification_service1.lock().await; + while let Ok(message) = rx.recv().await { + let Some(_) = message else { break }; + notification_service1 + .send_async_notification(&peer_id2, vec![0; size]) + .await + .unwrap(); + } + } + }); + let network2 = tokio::spawn({ + let notification_service2 = Arc::clone(&setup.notification_service2); + async move { + let mut notification_service2 = notification_service2.lock().await; + let mut received_counter = 0; + while let Some(event) = notification_service2.next_event().await { + if let NotificationEvent::NotificationReceived { .. } = event { + received_counter += 1; + if received_counter >= limit { + let _ = tx.send(None).await; + break; + } + let _ = tx.send(Some(())).await; + } } } }); @@ -207,77 +220,34 @@ where let _ = tokio::join!(network1, network2); } -async fn run_with_backpressure(size: usize, limit: usize) -where - B: BlockT + 'static, - H: ExHashT, - N: NetworkBackend, -{ - let listen_address1 = get_listen_address(); - let listen_address2 = get_listen_address(); - let (worker1, mut notification_service1) = create_network_worker::(listen_address1); - let (worker2, mut notification_service2) = - create_network_worker::(listen_address2.clone()); - let peer_id2: sc_network::PeerId = worker2.network_service().local_peer_id().into(); - - worker1 - .network_service() - .add_reserved_peer(MultiaddrWithPeerId { multiaddr: listen_address2, peer_id: peer_id2 }) - .unwrap(); - - let network1_run = worker1.run(); - let network2_run = worker2.run(); - - let network1 = tokio::spawn(async move { - let mut sent_counter = 0; - tokio::pin!(network1_run); - loop { - tokio::select! { - _ = &mut network1_run => {}, - event = notification_service1.next_event() => { - match event { - Some(NotificationEvent::NotificationStreamOpened { .. }) => { - while sent_counter < limit { - sent_counter += 1; - notification_service1 - .send_async_notification(&peer_id2, vec![0; size]) - .await - .unwrap(); - } - }, - Some(NotificationEvent::NotificationStreamClosed { .. }) => { - if sent_counter != limit { panic!("Stream closed unexpectedly") } - break - }, - event => panic!("Unexpected event {:?}", event), - }; - }, +async fn run_with_backpressure(setup: Arc, size: usize, limit: usize) { + let (tx, rx) = async_channel::bounded(1); + let network1 = tokio::spawn({ + let setup = Arc::clone(&setup); + async move { + let mut notification_service1 = setup.notification_service1.lock().await; + for _ in 0..limit { + notification_service1 + .send_async_notification(&setup.peer_id2, vec![0; size]) + .await + .unwrap(); } + let _ = rx.recv().await; } }); - let network2 = tokio::spawn(async move { - let mut received_counter = 0; - tokio::pin!(network2_run); - loop { - tokio::select! { - _ = &mut network2_run => {}, - event = notification_service2.next_event() => { - match event { - Some(NotificationEvent::ValidateInboundSubstream { result_tx, .. }) => { - result_tx.send(sc_network::service::traits::ValidationResult::Accept).unwrap(); - }, - Some(NotificationEvent::NotificationStreamOpened { .. }) => {}, - Some(NotificationEvent::NotificationStreamClosed { .. }) => { - if received_counter != limit { panic!("Stream closed unexpectedly") } - break - }, - Some(NotificationEvent::NotificationReceived { .. }) => { - received_counter += 1; - if received_counter >= limit { break } - }, - event => panic!("Unexpected event {:?}", event), - }; - }, + let network2 = tokio::spawn({ + let setup = Arc::clone(&setup); + async move { + let mut notification_service2 = setup.notification_service2.lock().await; + let mut received_counter = 0; + while let Some(event) = notification_service2.next_event().await { + if let NotificationEvent::NotificationReceived { .. } = event { + received_counter += 1; + if received_counter >= limit { + let _ = tx.send(()).await; + break; + } + } } } }); @@ -285,64 +255,64 @@ where let _ = tokio::join!(network1, network2); } -fn run_benchmark(c: &mut Criterion) { +fn run_benchmark(c: &mut Criterion, payload: &[(u32, usize, &'static str)], group: &str) { let rt = tokio::runtime::Runtime::new().unwrap(); let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); - let mut group = c.benchmark_group("notifications_benchmark"); + let mut group = c.benchmark_group(group); group.plot_config(plot_config); - for &(exponent, label) in EXPONENTS.iter() { + let libp2p_setup = setup_workers::>(&rt); + for &(exponent, limit, label) in payload.iter() { let size = 2usize.pow(exponent); - group.throughput(Throughput::Bytes(NOTIFICATIONS as u64 * size as u64)); - + group.throughput(Throughput::Bytes(limit as u64 * size as u64)); group.bench_with_input( BenchmarkId::new("libp2p/serially", label), - &(size, NOTIFICATIONS), + &(size, limit), |b, &(size, limit)| { - b.to_async(&rt).iter(|| { - run_serially::>(size, limit) - }); + b.to_async(&rt).iter(|| run_serially(Arc::clone(&libp2p_setup), size, limit)); }, ); group.bench_with_input( - BenchmarkId::new("litep2p/serially", label), - &(size, NOTIFICATIONS), + BenchmarkId::new("libp2p/with_backpressure", label), + &(size, limit), |b, &(size, limit)| { - b.to_async(&rt).iter(|| { - run_serially::( - size, limit, - ) - }); + b.to_async(&rt) + .iter(|| run_with_backpressure(Arc::clone(&libp2p_setup), size, limit)); }, ); + } + drop(libp2p_setup); + + let litep2p_setup = setup_workers::(&rt); + for &(exponent, limit, label) in payload.iter() { + let size = 2usize.pow(exponent); + group.throughput(Throughput::Bytes(limit as u64 * size as u64)); group.bench_with_input( - BenchmarkId::new("libp2p/with_backpressure", label), - &(size, NOTIFICATIONS), + BenchmarkId::new("litep2p/serially", label), + &(size, limit), |b, &(size, limit)| { - b.to_async(&rt).iter(|| { - run_with_backpressure::>( - size, limit, - ) - }); + b.to_async(&rt).iter(|| run_serially(Arc::clone(&litep2p_setup), size, limit)); }, ); group.bench_with_input( BenchmarkId::new("litep2p/with_backpressure", label), - &(size, NOTIFICATIONS), + &(size, limit), |b, &(size, limit)| { - b.to_async(&rt).iter(|| { - run_with_backpressure::( - size, limit, - ) - }); + b.to_async(&rt) + .iter(|| run_with_backpressure(Arc::clone(&litep2p_setup), size, limit)); }, ); } + drop(litep2p_setup); } -criterion_group! { - name = benches; - config = Criterion::default().sample_size(SAMPLE_SIZE); - targets = run_benchmark +fn run_benchmark_with_small_payload(c: &mut Criterion) { + run_benchmark(c, SMALL_PAYLOAD, "notifications_protocol/small_payload"); } + +fn run_benchmark_with_large_payload(c: &mut Criterion) { + run_benchmark(c, LARGE_PAYLOAD, "notifications_protocol/large_payload"); +} + +criterion_group!(benches, run_benchmark_with_small_payload, run_benchmark_with_large_payload); criterion_main!(benches); diff --git a/substrate/client/network/benches/request_response_protocol.rs b/substrate/client/network/benches/request_response_protocol.rs index b428d0d75ac5..85381112b753 100644 --- a/substrate/client/network/benches/request_response_protocol.rs +++ b/substrate/client/network/benches/request_response_protocol.rs @@ -25,46 +25,39 @@ use sc_network::{ FullNetworkConfiguration, IncomingRequest, NetworkConfiguration, NonReservedPeerMode, NotificationHandshake, OutgoingResponse, Params, ProtocolId, Role, SetConfig, }, + service::traits::NetworkService, IfDisconnected, Litep2pNetworkBackend, NetworkBackend, NetworkRequest, NetworkWorker, - NotificationMetrics, NotificationService, Roles, + NotificationMetrics, NotificationService, PeerId, Roles, }; use sc_network_common::{sync::message::BlockAnnouncesHandshake, ExHashT}; -use sc_network_types::build_multiaddr; use sp_core::H256; use sp_runtime::traits::{Block as BlockT, Zero}; -use std::{ - net::{IpAddr, Ipv4Addr, TcpListener}, - str::FromStr, - time::Duration, -}; +use std::{sync::Arc, time::Duration}; use substrate_test_runtime_client::runtime; +use tokio::{sync::Mutex, task::JoinHandle}; const MAX_SIZE: u64 = 2u64.pow(30); -const SAMPLE_SIZE: usize = 50; -const REQUESTS: usize = 50; -const EXPONENTS: &[(u32, &'static str)] = &[ - (6, "64B"), - (9, "512B"), - (12, "4KB"), - (15, "64KB"), - (18, "256KB"), - (21, "2MB"), - (24, "16MB"), - (27, "128MB"), +const SMALL_PAYLOAD: &[(u32, usize, &'static str)] = &[ + // (Exponent of size, number of requests, label) + (6, 100, "64B"), + (9, 100, "512B"), + (12, 100, "4KB"), + (15, 100, "64KB"), +]; +const LARGE_PAYLOAD: &[(u32, usize, &'static str)] = &[ + // (Exponent of size, number of requests, label) + (18, 10, "256KB"), + (21, 10, "2MB"), + (24, 10, "16MB"), + (27, 10, "128MB"), ]; -fn get_listen_address() -> sc_network::Multiaddr { - let ip = Ipv4Addr::from_str("127.0.0.1").unwrap(); - let listener = TcpListener::bind((IpAddr::V4(ip), 0)).unwrap(); // Bind to a random port - let local_addr = listener.local_addr().unwrap(); - let port = local_addr.port(); - - build_multiaddr!(Ip4(ip), Tcp(port)) -} - -pub fn create_network_worker( - listen_addr: sc_network::Multiaddr, -) -> (N, async_channel::Receiver, Box) +pub fn create_network_worker() -> ( + N, + Arc, + async_channel::Receiver, + Arc>>, +) where B: BlockT + 'static, H: ExHashT, @@ -80,8 +73,7 @@ where Some(tx), ); let role = Role::Full; - let mut net_conf = NetworkConfiguration::new_local(); - net_conf.listen_addresses = vec![listen_addr]; + let net_conf = NetworkConfiguration::new_local(); let mut network_config = FullNetworkConfiguration::new(&net_conf, None); network_config.add_request_response_protocol(request_response_config); let genesis_hash = runtime::Hash::zero(); @@ -119,71 +111,115 @@ where notification_metrics: NotificationMetrics::new(None), }) .unwrap(); + let notification_service = Arc::new(Mutex::new(notification_service)); + let network_service = worker.network_service(); - (worker, rx, notification_service) + (worker, network_service, rx, notification_service) } -async fn run_serially(size: usize, limit: usize) +struct BenchSetup { + #[allow(dead_code)] + notification_service1: Arc>>, + #[allow(dead_code)] + notification_service2: Arc>>, + network_service1: Arc, + peer_id2: PeerId, + handle1: JoinHandle<()>, + handle2: JoinHandle<()>, + #[allow(dead_code)] + rx1: async_channel::Receiver, + rx2: async_channel::Receiver, +} + +impl Drop for BenchSetup { + fn drop(&mut self) { + self.handle1.abort(); + self.handle2.abort(); + } +} + +fn setup_workers(rt: &tokio::runtime::Runtime) -> Arc where B: BlockT + 'static, H: ExHashT, N: NetworkBackend, { - let listen_address1 = get_listen_address(); - let listen_address2 = get_listen_address(); - let (worker1, _rx1, _notification_service1) = create_network_worker::(listen_address1); - let service1 = worker1.network_service().clone(); - let (worker2, rx2, _notification_service2) = - create_network_worker::(listen_address2.clone()); + let _guard = rt.enter(); + + let (worker1, network_service1, rx1, notification_service1) = + create_network_worker::(); + let (worker2, network_service2, rx2, notification_service2) = + create_network_worker::(); let peer_id2 = worker2.network_service().local_peer_id(); + let handle1 = tokio::spawn(worker1.run()); + let handle2 = tokio::spawn(worker2.run()); - worker1.network_service().add_known_address(peer_id2, listen_address2.into()); + let ready = tokio::spawn({ + let network_service1 = Arc::clone(&network_service1); - let network1_run = worker1.run(); - let network2_run = worker2.run(); - let (break_tx, break_rx) = async_channel::bounded(10); - let requests = async move { - let mut sent_counter = 0; - while sent_counter < limit { - let _ = service1 - .request( - peer_id2.into(), - "/request-response/1".into(), - vec![0; 2], - None, - IfDisconnected::TryConnect, - ) - .await - .unwrap(); - sent_counter += 1; + async move { + let listen_address2 = { + while network_service2.listen_addresses().is_empty() { + tokio::time::sleep(Duration::from_millis(10)).await; + } + network_service2.listen_addresses()[0].clone() + }; + network_service1.add_known_address(peer_id2, listen_address2.into()); } - let _ = break_tx.send(()).await; - }; + }); - let network1 = tokio::spawn(async move { - tokio::pin!(requests); - tokio::pin!(network1_run); - loop { - tokio::select! { - _ = &mut network1_run => {}, - _ = &mut requests => break, + tokio::task::block_in_place(|| { + let _ = tokio::runtime::Handle::current().block_on(ready); + }); + + Arc::new(BenchSetup { + notification_service1, + notification_service2, + network_service1, + peer_id2, + handle1, + handle2, + rx1, + rx2, + }) +} + +async fn run_serially(setup: Arc, size: usize, limit: usize) { + let (break_tx, break_rx) = async_channel::bounded(1); + let network1 = tokio::spawn({ + let network_service1 = Arc::clone(&setup.network_service1); + let peer_id2 = setup.peer_id2; + async move { + for _ in 0..limit { + let _ = network_service1 + .request( + peer_id2.into(), + "/request-response/1".into(), + vec![0; 2], + None, + IfDisconnected::TryConnect, + ) + .await + .unwrap(); } + let _ = break_tx.send(()).await; } }); - let network2 = tokio::spawn(async move { - tokio::pin!(network2_run); - loop { - tokio::select! { - _ = &mut network2_run => {}, - res = rx2.recv() => { - let IncomingRequest { pending_response, .. } = res.unwrap(); - pending_response.send(OutgoingResponse { - result: Ok(vec![0; size]), - reputation_changes: vec![], - sent_feedback: None, - }).unwrap(); - }, - _ = break_rx.recv() => break, + let network2 = tokio::spawn({ + let rx2 = setup.rx2.clone(); + async move { + loop { + tokio::select! { + res = rx2.recv() => { + let IncomingRequest { pending_response, .. } = res.unwrap(); + pending_response.send(OutgoingResponse { + result: Ok(vec![0; size]), + reputation_changes: vec![], + sent_feedback: None, + }).unwrap(); + }, + _ = break_rx.recv() => break, + } } } }); @@ -194,29 +230,12 @@ where // The libp2p request-response implementation does not provide any backpressure feedback. // So this benchmark is useless until we implement it for litep2p. #[allow(dead_code)] -async fn run_with_backpressure(size: usize, limit: usize) -where - B: BlockT + 'static, - H: ExHashT, - N: NetworkBackend, -{ - let listen_address1 = get_listen_address(); - let listen_address2 = get_listen_address(); - let (worker1, _rx1, _notification_service1) = create_network_worker::(listen_address1); - let service1 = worker1.network_service().clone(); - let (worker2, rx2, _notification_service2) = - create_network_worker::(listen_address2.clone()); - let peer_id2 = worker2.network_service().local_peer_id(); - - worker1.network_service().add_known_address(peer_id2, listen_address2.into()); - - let network1_run = worker1.run(); - let network2_run = worker2.run(); - let (break_tx, break_rx) = async_channel::bounded(10); +async fn run_with_backpressure(setup: Arc, size: usize, limit: usize) { + let (break_tx, break_rx) = async_channel::bounded(1); let requests = futures::future::join_all((0..limit).into_iter().map(|_| { let (tx, rx) = futures::channel::oneshot::channel(); - service1.start_request( - peer_id2.into(), + setup.network_service1.start_request( + setup.peer_id2.into(), "/request-response/1".into(), vec![0; 8], None, @@ -227,77 +246,72 @@ where })); let network1 = tokio::spawn(async move { - tokio::pin!(requests); - tokio::pin!(network1_run); - loop { - tokio::select! { - _ = &mut network1_run => {}, - responses = &mut requests => { - for res in responses { - res.unwrap().unwrap(); - } - let _ = break_tx.send(()).await; - break; - }, - } + let responses = requests.await; + for res in responses { + res.unwrap().unwrap(); } + let _ = break_tx.send(()).await; }); let network2 = tokio::spawn(async move { - tokio::pin!(network2_run); - loop { - tokio::select! { - _ = &mut network2_run => {}, - res = rx2.recv() => { - let IncomingRequest { pending_response, .. } = res.unwrap(); - pending_response.send(OutgoingResponse { - result: Ok(vec![0; size]), - reputation_changes: vec![], - sent_feedback: None, - }).unwrap(); - }, - _ = break_rx.recv() => break, - } + for _ in 0..limit { + let IncomingRequest { pending_response, .. } = setup.rx2.recv().await.unwrap(); + pending_response + .send(OutgoingResponse { + result: Ok(vec![0; size]), + reputation_changes: vec![], + sent_feedback: None, + }) + .unwrap(); } + break_rx.recv().await }); let _ = tokio::join!(network1, network2); } -fn run_benchmark(c: &mut Criterion) { +fn run_benchmark(c: &mut Criterion, payload: &[(u32, usize, &'static str)], group: &str) { let rt = tokio::runtime::Runtime::new().unwrap(); let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); - let mut group = c.benchmark_group("request_response_benchmark"); + let mut group = c.benchmark_group(group); group.plot_config(plot_config); - for &(exponent, label) in EXPONENTS.iter() { + let libp2p_setup = setup_workers::>(&rt); + for &(exponent, limit, label) in payload.iter() { let size = 2usize.pow(exponent); - group.throughput(Throughput::Bytes(REQUESTS as u64 * size as u64)); + group.throughput(Throughput::Bytes(limit as u64 * size as u64)); group.bench_with_input( BenchmarkId::new("libp2p/serially", label), - &(size, REQUESTS), - |b, &(size, limit)| { - b.to_async(&rt).iter(|| { - run_serially::>(size, limit) - }); - }, - ); - group.bench_with_input( - BenchmarkId::new("litep2p/serially", label), - &(size, REQUESTS), + &(size, limit), |b, &(size, limit)| { - b.to_async(&rt).iter(|| { - run_serially::( - size, limit, - ) - }); + b.to_async(&rt).iter(|| run_serially(Arc::clone(&libp2p_setup), size, limit)); }, ); } + drop(libp2p_setup); + + // TODO: NetworkRequest::request should be implemented for Litep2pNetworkService + let litep2p_setup = setup_workers::(&rt); + // for &(exponent, limit, label) in payload.iter() { + // let size = 2usize.pow(exponent); + // group.throughput(Throughput::Bytes(limit as u64 * size as u64)); + // group.bench_with_input( + // BenchmarkId::new("litep2p/serially", label), + // &(size, limit), + // |b, &(size, limit)| { + // b.to_async(&rt).iter(|| run_serially(Arc::clone(&litep2p_setup), size, limit)); + // }, + // ); + // } + drop(litep2p_setup); } -criterion_group! { - name = benches; - config = Criterion::default().sample_size(SAMPLE_SIZE); - targets = run_benchmark +fn run_benchmark_with_small_payload(c: &mut Criterion) { + run_benchmark(c, SMALL_PAYLOAD, "request_response_benchmark/small_payload"); } + +fn run_benchmark_with_large_payload(c: &mut Criterion) { + run_benchmark(c, LARGE_PAYLOAD, "request_response_benchmark/large_payload"); +} + +criterion_group!(benches, run_benchmark_with_small_payload, run_benchmark_with_large_payload); criterion_main!(benches); From df1375ea8dabda5ba0b79f268b4883c65f59bf2f Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:55:13 +0100 Subject: [PATCH 108/340] chainHead: Always report discarded items for storage operations (#6760) This PR ensures that substrate always reports discarded items as zero. This is needed to align with the rpc-v2 spec Closes: https://github.com/paritytech/polkadot-sdk/issues/6683 cc @paritytech/subxt-team --------- Signed-off-by: Alexandru Vasile Co-authored-by: GitHub Action --- prdoc/pr_6760.prdoc | 9 +++++++++ .../client/rpc-spec-v2/src/chain_head/chain_head.rs | 10 ++++++---- substrate/client/rpc-spec-v2/src/chain_head/tests.rs | 7 +++++-- 3 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 prdoc/pr_6760.prdoc diff --git a/prdoc/pr_6760.prdoc b/prdoc/pr_6760.prdoc new file mode 100644 index 000000000000..8224b72fb0a4 --- /dev/null +++ b/prdoc/pr_6760.prdoc @@ -0,0 +1,9 @@ +title: 'chainHead: Always report discarded items for storage operations' +doc: +- audience: [Node Dev, Node Operator] + description: |- + This PR ensures that substrate always reports discarded items as zero. + This is needed to align with the rpc-v2 spec +crates: +- name: sc-rpc-spec-v2 + bump: patch diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs index 61eb47d1b9ab..b949fb25402b 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -318,7 +318,7 @@ where }), }; - let (rp, rp_fut) = method_started_response(operation_id); + let (rp, rp_fut) = method_started_response(operation_id, None); let fut = async move { // Wait for the server to send out the response and if it produces an error no event // should be generated. @@ -432,7 +432,8 @@ where let mut storage_client = ChainHeadStorage::::new(self.client.clone()); - let (rp, rp_fut) = method_started_response(block_guard.operation().operation_id()); + // Storage items are never discarded. + let (rp, rp_fut) = method_started_response(block_guard.operation().operation_id(), Some(0)); let fut = async move { // Wait for the server to send out the response and if it produces an error no event @@ -507,7 +508,7 @@ where let operation_id = block_guard.operation().operation_id(); let client = self.client.clone(); - let (rp, rp_fut) = method_started_response(operation_id.clone()); + let (rp, rp_fut) = method_started_response(operation_id.clone(), None); let fut = async move { // Wait for the server to send out the response and if it produces an error no event // should be generated. @@ -630,8 +631,9 @@ where fn method_started_response( operation_id: String, + discarded_items: Option, ) -> (ResponsePayload<'static, MethodResponse>, MethodResponseFuture) { - let rp = MethodResponse::Started(MethodResponseStarted { operation_id, discarded_items: None }); + let rp = MethodResponse::Started(MethodResponseStarted { operation_id, discarded_items }); ResponsePayload::success(rp).notify_on_completion() } diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs index 21e8365622a1..43b52175bd6d 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -2871,7 +2871,7 @@ async fn ensure_operation_limits_works() { let operation_id = match response { MethodResponse::Started(started) => { // Check discarded items. - assert!(started.discarded_items.is_none()); + assert_eq!(started.discarded_items, Some(0)); started.operation_id }, MethodResponse::LimitReached => panic!("Expected started response"), @@ -3228,7 +3228,10 @@ async fn storage_closest_merkle_value() { .await .unwrap(); let operation_id = match response { - MethodResponse::Started(started) => started.operation_id, + MethodResponse::Started(started) => { + assert_eq!(started.discarded_items, Some(0)); + started.operation_id + }, MethodResponse::LimitReached => panic!("Expected started response"), }; From 44766de645c741fd03d159fca7f17cd6e99c33e3 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:12:32 +0100 Subject: [PATCH 109/340] bump zombienet and re-enable test (#6090) Re-enable zombienet test for `solochain`. Thx! --- .gitlab/pipeline/zombienet/parachain-template.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet/parachain-template.yml b/.gitlab/pipeline/zombienet/parachain-template.yml index 896ba7913be7..d5c1b6558b39 100644 --- a/.gitlab/pipeline/zombienet/parachain-template.yml +++ b/.gitlab/pipeline/zombienet/parachain-template.yml @@ -43,4 +43,4 @@ zombienet-parachain-template-smoke: - ls -ltr $(pwd)/artifacts - cargo test -p template-zombienet-tests --features zombienet --tests minimal_template_block_production_test - cargo test -p template-zombienet-tests --features zombienet --tests parachain_template_block_production_test - # - cargo test -p template-zombienet-tests --features zombienet --tests solochain_template_block_production_test + - cargo test -p template-zombienet-tests --features zombienet --tests solochain_template_block_production_test From 0d3d450273af6ddb9a6733683726628a8d1d3c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 5 Dec 2024 16:50:45 +0100 Subject: [PATCH 110/340] pallet-revive: Adjust error handling of sub calls (#6741) We were trapping the host context in case a sub call was exhausting the storage deposit limit set for this sub call. This prevents the caller from handling this error. In this PR we added a new error code that is returned when either gas or storage deposit limit is exhausted by the sub call. We also remove the longer used `NotCallable` error. No longer used because this is no longer an error: It will just be a balance transfer. We also make `set_code_hash` infallible to be consistent with other host functions which just trap on any error condition. --------- Co-authored-by: GitHub Action --- prdoc/pr_6741.prdoc | 16 ++++ .../fixtures/contracts/caller_contract.rs | 8 +- .../contracts/create_storage_and_call.rs | 8 +- .../create_storage_and_instantiate.rs | 8 +- .../contracts/delegate_call_deposit_limit.rs | 6 +- .../fixtures/contracts/set_code_hash.rs | 2 +- substrate/frame/revive/src/exec.rs | 16 ++-- substrate/frame/revive/src/tests.rs | 75 ++++++++----------- substrate/frame/revive/src/wasm/runtime.rs | 46 ++++-------- substrate/frame/revive/uapi/src/host.rs | 12 +-- .../frame/revive/uapi/src/host/riscv64.rs | 7 +- substrate/frame/revive/uapi/src/lib.rs | 18 ++--- 12 files changed, 109 insertions(+), 113 deletions(-) create mode 100644 prdoc/pr_6741.prdoc diff --git a/prdoc/pr_6741.prdoc b/prdoc/pr_6741.prdoc new file mode 100644 index 000000000000..d4b795038bcd --- /dev/null +++ b/prdoc/pr_6741.prdoc @@ -0,0 +1,16 @@ +title: 'pallet-revive: Adjust error handling of sub calls' +doc: +- audience: Runtime Dev + description: |- + We were trapping the host context in case a sub call was exhausting the storage deposit limit set for this sub call. This prevents the caller from handling this error. In this PR we added a new error code that is returned when either gas or storage deposit limit is exhausted by the sub call. + + We also remove the longer used `NotCallable` error. No longer used because this is no longer an error: It will just be a balance transfer. + + We also make `set_code_hash` infallible to be consistent with other host functions which just trap on any error condition. +crates: +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major +- name: pallet-revive-fixtures + bump: major diff --git a/substrate/frame/revive/fixtures/contracts/caller_contract.rs b/substrate/frame/revive/fixtures/contracts/caller_contract.rs index f9a30b87df47..edad43fae251 100644 --- a/substrate/frame/revive/fixtures/contracts/caller_contract.rs +++ b/substrate/frame/revive/fixtures/contracts/caller_contract.rs @@ -65,7 +65,7 @@ pub extern "C" fn call() { None, Some(&salt), ); - assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + assert!(matches!(res, Err(ReturnErrorCode::OutOfResources))); // Fail to deploy the contract due to insufficient proof_size weight. let res = api::instantiate( @@ -79,7 +79,7 @@ pub extern "C" fn call() { None, Some(&salt), ); - assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + assert!(matches!(res, Err(ReturnErrorCode::OutOfResources))); // Deploy the contract successfully. let mut callee = [0u8; 20]; @@ -121,7 +121,7 @@ pub extern "C" fn call() { &input, None, ); - assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + assert!(matches!(res, Err(ReturnErrorCode::OutOfResources))); // Fail to call the contract due to insufficient proof_size weight. let res = api::call( @@ -134,7 +134,7 @@ pub extern "C" fn call() { &input, None, ); - assert!(matches!(res, Err(ReturnErrorCode::CalleeTrapped))); + assert!(matches!(res, Err(ReturnErrorCode::OutOfResources))); // Call the contract successfully. let mut output = [0u8; 4]; diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs index 4fa2db0c8c1c..a12c36af856a 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs @@ -40,7 +40,7 @@ pub extern "C" fn call() { api::set_storage(StorageFlags::empty(), buffer, &[1u8; 4]); // Call the callee - api::call( + let ret = api::call( uapi::CallFlags::empty(), callee, 0u64, // How much ref_time weight to devote for the execution. 0 = all. @@ -49,8 +49,10 @@ pub extern "C" fn call() { &[0u8; 32], // Value transferred to the contract. input, None, - ) - .unwrap(); + ); + if let Err(code) = ret { + api::return_value(uapi::ReturnFlags::REVERT, &(code as u32).to_le_bytes()); + }; // create 8 byte of storage after calling // item of 12 bytes because we override 4 bytes diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs index 463706457a15..ecc0fc79e6fd 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs @@ -39,7 +39,7 @@ pub extern "C" fn call() { let salt = [0u8; 32]; let mut address = [0u8; 20]; - api::instantiate( + let ret = api::instantiate( code_hash, 0u64, // How much ref_time weight to devote for the execution. 0 = all. 0u64, // How much proof_size weight to devote for the execution. 0 = all. @@ -49,8 +49,10 @@ pub extern "C" fn call() { Some(&mut address), None, Some(&salt), - ) - .unwrap(); + ); + if let Err(code) = ret { + api::return_value(uapi::ReturnFlags::REVERT, &(code as u32).to_le_bytes()); + }; // Return the deployed contract address. api::return_value(uapi::ReturnFlags::empty(), &address); diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs index 55203d534c9b..0f157f5a18ac 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs @@ -34,7 +34,11 @@ pub extern "C" fn call() { ); let input = [0u8; 0]; - api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, Some(&u256_bytes(deposit_limit)), &input, None).unwrap(); + let ret = api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, Some(&u256_bytes(deposit_limit)), &input, None); + + if let Err(code) = ret { + api::return_value(uapi::ReturnFlags::REVERT, &(code as u32).to_le_bytes()); + }; let mut key = [0u8; 32]; key[0] = 1u8; diff --git a/substrate/frame/revive/fixtures/contracts/set_code_hash.rs b/substrate/frame/revive/fixtures/contracts/set_code_hash.rs index 75995d7bb8a2..7292c6fd10ae 100644 --- a/substrate/frame/revive/fixtures/contracts/set_code_hash.rs +++ b/substrate/frame/revive/fixtures/contracts/set_code_hash.rs @@ -29,7 +29,7 @@ pub extern "C" fn deploy() {} #[polkavm_derive::polkavm_export] pub extern "C" fn call() { input!(addr: &[u8; 32],); - api::set_code_hash(addr).unwrap(); + api::set_code_hash(addr); // we return 1 after setting new code_hash // next `call` will NOT return this value, because contract code has been changed diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index b23d7e4e60ef..b6f0e3ae1a81 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1068,7 +1068,7 @@ where self.transient_storage.start_transaction(); - let do_transaction = || { + let do_transaction = || -> ExecResult { let caller = self.caller(); let frame = top_frame_mut!(self); @@ -1107,11 +1107,8 @@ where let call_span = T::Debug::new_call_span(&contract_address, entry_point, &input_data); let output = T::Debug::intercept_call(&contract_address, entry_point, &input_data) - .unwrap_or_else(|| { - executable - .execute(self, entry_point, input_data) - .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee }) - })?; + .unwrap_or_else(|| executable.execute(self, entry_point, input_data)) + .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?; call_span.after_call(&output); @@ -1136,7 +1133,10 @@ where // If a special limit was set for the sub-call, we enforce it here. // The sub-call will be rolled back in case the limit is exhausted. let contract = frame.contract_info.as_contract(); - frame.nested_storage.enforce_subcall_limit(contract)?; + frame + .nested_storage + .enforce_subcall_limit(contract) + .map_err(|e| ExecError { error: e, origin: ErrorOrigin::Callee })?; let account_id = T::AddressMapper::to_address(&frame.account_id); match (entry_point, delegated_code_hash) { @@ -3412,7 +3412,7 @@ mod tests { true, false ), - >::TransferFailed + >::TransferFailed, ); exec_success() }); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 1df300f031a7..a612e7760acb 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -38,8 +38,8 @@ use crate::{ wasm::Memory, weights::WeightInfo, AccountId32Mapper, BalanceOf, Code, CodeInfoOf, CollectEvents, Config, ContractInfo, - ContractInfoOf, DebugInfo, DeletionQueueCounter, Error, HoldReason, Origin, Pallet, - PristineCode, H160, + ContractInfoOf, DebugInfo, DeletionQueueCounter, DepositLimit, Error, HoldReason, Origin, + Pallet, PristineCode, H160, }; use crate::test_utils::builder::Contract; @@ -1211,14 +1211,11 @@ fn delegate_call_with_deposit_limit() { // Delegate call will write 1 storage and deposit of 2 (1 item) + 32 (bytes) is required. // Fails, not enough deposit - assert_err!( - builder::bare_call(caller_addr) - .value(1337) - .data((callee_addr, 33u64).encode()) - .build() - .result, - Error::::StorageDepositLimitExhausted, - ); + let ret = builder::bare_call(caller_addr) + .value(1337) + .data((callee_addr, 33u64).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); assert_ok!(builder::call(caller_addr) .value(1337) @@ -1678,8 +1675,8 @@ fn instantiate_return_code() { // Contract has enough balance but the passed code hash is invalid ::Currency::set_balance(&contract.account_id, min_balance + 10_000); - let result = builder::bare_call(contract.addr).data(vec![0; 33]).build_and_unwrap_result(); - assert_return_code!(result, RuntimeReturnCode::CodeNotFound); + let result = builder::bare_call(contract.addr).data(vec![0; 33]).build(); + assert_err!(result.result, >::CodeNotFound); // Contract has enough balance but callee reverts because "1" is passed. let result = builder::bare_call(contract.addr) @@ -3463,13 +3460,11 @@ fn deposit_limit_in_nested_calls() { // nested call. This should fail as callee adds up 2 bytes to the storage, meaning // that the nested call should have a deposit limit of at least 2 Balance. The // sub-call should be rolled back, which is covered by the next test case. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .storage_deposit_limit(16) - .data((102u32, &addr_callee, U256::from(1u64)).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); + let ret = builder::bare_call(addr_caller) + .storage_deposit_limit(DepositLimit::Balance(16)) + .data((102u32, &addr_callee, U256::from(1u64)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // Refund in the callee contract but not enough to cover the 14 Balance required by the // caller. Note that if previous sub-call wouldn't roll back, this call would pass @@ -3485,13 +3480,11 @@ fn deposit_limit_in_nested_calls() { let _ = ::Currency::set_balance(&ALICE, 511); // Require more than the sender's balance. - // We don't set a special limit for the nested call. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .data((512u32, &addr_callee, U256::from(1u64)).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); + // Limit the sub call to little balance so it should fail in there + let ret = builder::bare_call(addr_caller) + .data((512u32, &addr_callee, U256::from(1u64)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // Same as above but allow for the additional deposit of 1 Balance in parent. // We set the special deposit limit of 1 Balance for the nested call, which isn't @@ -3563,14 +3556,12 @@ fn deposit_limit_in_nested_instantiate() { // Now we set enough limit in parent call, but an insufficient limit for child // instantiate. This should fail during the charging for the instantiation in // `RawMeter::charge_instantiate()` - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 2) - .data((0u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 1)).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(callee_info_len + 2 + ED + 2)) + .data((0u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 1)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on the instantiation should be rolled back. assert_eq!(::Currency::free_balance(&BOB), 1_000_000); @@ -3578,14 +3569,12 @@ fn deposit_limit_in_nested_instantiate() { // item of 1 byte to be covered by the limit, which implies 3 more Balance. // Now we set enough limit for the parent call, but insufficient limit for child // instantiate. This should fail right after the constructor execution. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 3) // enough parent limit - .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 2)).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(callee_info_len + 2 + ED + 3)) // enough parent limit + .data((1u32, &code_hash_callee, U256::from(callee_info_len + 2 + ED + 2)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on the instantiation should be rolled back. assert_eq!(::Currency::free_balance(&BOB), 1_000_000); @@ -3890,7 +3879,7 @@ fn locking_delegate_dependency_works() { assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE_FALLBACK), callee_hashes[0])); // Calling should fail since the delegated contract is not on chain anymore. - assert_err!(call(&addr_caller, &noop_input).result, Error::::ContractTrapped); + assert_err!(call(&addr_caller, &noop_input).result, Error::::CodeNotFound); // Add the dependency back. Contracts::upload_code( diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 7ea518081e23..8fb7e5c27470 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -745,29 +745,24 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { Ok(()) } - /// Fallible conversion of `DispatchError` to `ReturnErrorCode`. - fn err_into_return_code(from: DispatchError) -> Result { - use ReturnErrorCode::*; - - let transfer_failed = Error::::TransferFailed.into(); - let no_code = Error::::CodeNotFound.into(); - let not_found = Error::::ContractNotFound.into(); - - match from { - x if x == transfer_failed => Ok(TransferFailed), - x if x == no_code => Ok(CodeNotFound), - x if x == not_found => Ok(NotCallable), - err => Err(err), - } - } - /// Fallible conversion of a `ExecError` to `ReturnErrorCode`. + /// + /// This is used when converting the error returned from a subcall in order to decide + /// whether to trap the caller or allow handling of the error. fn exec_error_into_return_code(from: ExecError) -> Result { use crate::exec::ErrorOrigin::Callee; + use ReturnErrorCode::*; + let transfer_failed = Error::::TransferFailed.into(); + let out_of_gas = Error::::OutOfGas.into(); + let out_of_deposit = Error::::StorageDepositLimitExhausted.into(); + + // errors in the callee do not trap the caller match (from.error, from.origin) { - (_, Callee) => Ok(ReturnErrorCode::CalleeTrapped), - (err, _) => Self::err_into_return_code(err), + (err, _) if err == transfer_failed => Ok(TransferFailed), + (err, Callee) if err == out_of_gas || err == out_of_deposit => Ok(OutOfResources), + (_, Callee) => Ok(CalleeTrapped), + (err, _) => Err(err), } } @@ -1971,20 +1966,11 @@ pub mod env { /// Disabled until the internal implementation takes care of collecting /// the immutable data of the new code hash. #[mutating] - fn set_code_hash( - &mut self, - memory: &mut M, - code_hash_ptr: u32, - ) -> Result { + fn set_code_hash(&mut self, memory: &mut M, code_hash_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::SetCodeHash)?; let code_hash: H256 = memory.read_h256(code_hash_ptr)?; - match self.ext.set_code_hash(code_hash) { - Err(err) => { - let code = Self::err_into_return_code(err)?; - Ok(code) - }, - Ok(()) => Ok(ReturnErrorCode::Success), - } + self.ext.set_code_hash(code_hash)?; + Ok(()) } /// Calculates Ethereum address from the ECDSA compressed public key and stores diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index d3fd4ac8d03e..400a12879363 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -138,7 +138,7 @@ pub trait HostFn: private::Sealed { /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] - /// - [NotCallable][`crate::ReturnErrorCode::NotCallable] + /// - [OutOfResources][`crate::ReturnErrorCode::OutOfResources] fn call( flags: CallFlags, callee: &[u8; 20], @@ -341,7 +341,7 @@ pub trait HostFn: private::Sealed { /// /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] - /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + /// - [OutOfResources][`crate::ReturnErrorCode::OutOfResources] fn delegate_call( flags: CallFlags, address: &[u8; 20], @@ -468,7 +468,7 @@ pub trait HostFn: private::Sealed { /// - [CalleeReverted][`crate::ReturnErrorCode::CalleeReverted]: Output buffer is returned. /// - [CalleeTrapped][`crate::ReturnErrorCode::CalleeTrapped] /// - [TransferFailed][`crate::ReturnErrorCode::TransferFailed] - /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] + /// - [OutOfResources][`crate::ReturnErrorCode::OutOfResources] fn instantiate( code_hash: &[u8; 32], ref_time_limit: u64, @@ -566,10 +566,10 @@ pub trait HostFn: private::Sealed { /// - `code_hash`: The hash of the new code. Should be decodable as an `T::Hash`. Traps /// otherwise. /// - /// # Errors + /// # Panics /// - /// - [CodeNotFound][`crate::ReturnErrorCode::CodeNotFound] - fn set_code_hash(code_hash: &[u8; 32]) -> Result; + /// Panics if there is no code on-chain with the specified hash. + fn set_code_hash(code_hash: &[u8; 32]); /// Set the value at the given key in the contract storage. /// diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 3cba14db6a04..b76320718a69 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -115,7 +115,7 @@ mod sys { message_len: u32, message_ptr: *const u8, ) -> ReturnCode; - pub fn set_code_hash(code_hash_ptr: *const u8) -> ReturnCode; + pub fn set_code_hash(code_hash_ptr: *const u8); pub fn ecdsa_to_eth_address(key_ptr: *const u8, out_ptr: *mut u8) -> ReturnCode; pub fn instantiation_nonce() -> u64; pub fn lock_delegate_dependency(code_hash_ptr: *const u8); @@ -531,9 +531,8 @@ impl HostFn for HostFnImpl { ret_val.into_bool() } - fn set_code_hash(code_hash: &[u8; 32]) -> Result { - let ret_val = unsafe { sys::set_code_hash(code_hash.as_ptr()) }; - ret_val.into() + fn set_code_hash(code_hash: &[u8; 32]) { + unsafe { sys::set_code_hash(code_hash.as_ptr()) } } fn code_hash(address: &[u8; 20], output: &mut [u8; 32]) { diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs index 91c2543bb719..14a5e3f28889 100644 --- a/substrate/frame/revive/uapi/src/lib.rs +++ b/substrate/frame/revive/uapi/src/lib.rs @@ -85,23 +85,21 @@ define_error_codes! { /// Transfer failed for other not further specified reason. Most probably /// reserved or locked balance of the sender that was preventing the transfer. TransferFailed = 4, - /// No code could be found at the supplied code hash. - CodeNotFound = 5, - /// The account that was called is no contract. - NotCallable = 6, /// The call to `debug_message` had no effect because debug message /// recording was disabled. - LoggingDisabled = 7, + LoggingDisabled = 5, /// The call dispatched by `call_runtime` was executed but returned an error. - CallRuntimeFailed = 8, + CallRuntimeFailed = 6, /// ECDSA public key recovery failed. Most probably wrong recovery id or signature. - EcdsaRecoveryFailed = 9, + EcdsaRecoveryFailed = 7, /// sr25519 signature verification failed. - Sr25519VerifyFailed = 10, + Sr25519VerifyFailed = 8, /// The `xcm_execute` call failed. - XcmExecutionFailed = 11, + XcmExecutionFailed = 9, /// The `xcm_send` call failed. - XcmSendFailed = 12, + XcmSendFailed = 10, + /// The subcall ran out of weight or storage deposit. + OutOfResources = 11, } /// The raw return code returned by the host side. From 4f43b72f2c479656fdee8bc10341d221a4a1e4b9 Mon Sep 17 00:00:00 2001 From: Egor_P Date: Thu, 5 Dec 2024 16:53:31 +0100 Subject: [PATCH 111/340] [CI/CD] Add release environment to the backport job (#6765) Fix adds release environment to the backport job, so that token could be properly generated --- .github/workflows/command-backport.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/command-backport.yml b/.github/workflows/command-backport.yml index 53dcea2f1d6d..db006e9bd907 100644 --- a/.github/workflows/command-backport.yml +++ b/.github/workflows/command-backport.yml @@ -16,6 +16,7 @@ jobs: backport: name: Backport pull request runs-on: ubuntu-latest + environment: release # The 'github.event.pull_request.merged' ensures that it got into master: if: > From f31c70aad4f90168824871c3c094008f3120b333 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 5 Dec 2024 17:27:39 +0100 Subject: [PATCH 112/340] Added fallback_max_weight to Transact for sending messages to V4 chains (#6643) Closes: https://github.com/paritytech/polkadot-sdk/issues/6585 Removing the `require_weight_at_most` parameter in V5 Transact had only one problem. Converting a message from V5 to V4 to send to chains that didn't upgrade yet. The conversion would not know what weight to give to the Transact, since V4 and below require it. To fix this, I added back the weight in the form of an `Option` called `fallback_max_weight`. This can be set to `None` if you don't intend to deal with a chain that hasn't upgraded yet. If you set it to `Some(_)`, the behaviour is the same. The plan is to totally remove this in V6 since there will be a good conversion path from V6 to V5. --------- Co-authored-by: GitHub Action Co-authored-by: Adrian Catangiu --- .../pallets/inbound-queue/src/test.rs | 4 +- .../primitives/router/src/inbound/mod.rs | 1 + .../emulated/common/src/xcm_helpers.rs | 4 +- .../asset-hub-westend/src/tests/transact.rs | 2 +- .../bridge-hub-rococo/src/tests/snowbridge.rs | 18 ++++- .../bridge-hub-westend/src/tests/transact.rs | 2 +- .../src/tests/fellowship.rs | 1 + .../people-westend/src/tests/governance.rs | 43 +++++++++-- cumulus/parachains/pallets/ping/src/lib.rs | 2 + .../asset-hub-rococo/src/weights/xcm/mod.rs | 6 +- .../asset-hub-westend/src/weights/xcm/mod.rs | 6 +- .../assets/test-utils/src/test_cases.rs | 14 +++- .../bridge-hub-rococo/src/weights/xcm/mod.rs | 6 +- .../bridge-hub-westend/src/weights/xcm/mod.rs | 6 +- .../coretime/coretime-rococo/src/coretime.rs | 4 + .../coretime-rococo/src/weights/xcm/mod.rs | 6 +- .../coretime/coretime-westend/src/coretime.rs | 16 ++++ .../coretime-westend/src/weights/xcm/mod.rs | 6 +- .../people-rococo/src/weights/xcm/mod.rs | 6 +- .../people-westend/src/weights/xcm/mod.rs | 6 +- .../parachains/runtimes/test-utils/src/lib.rs | 8 +- .../runtime/parachains/src/coretime/mod.rs | 6 ++ polkadot/runtime/parachains/src/mock.rs | 2 + polkadot/runtime/rococo/src/impls.rs | 14 +++- polkadot/runtime/rococo/src/lib.rs | 2 + .../runtime/rococo/src/weights/xcm/mod.rs | 6 +- polkadot/runtime/test-runtime/src/lib.rs | 2 + polkadot/runtime/westend/src/impls.rs | 14 +++- polkadot/runtime/westend/src/lib.rs | 2 + .../runtime/westend/src/weights/xcm/mod.rs | 6 +- .../src/generic/benchmarking.rs | 1 + polkadot/xcm/src/v4/mod.rs | 16 +++- polkadot/xcm/src/v5/mod.rs | 76 +++++++++++++++++-- polkadot/xcm/src/v5/traits.rs | 1 + .../xcm/xcm-builder/src/tests/transacting.rs | 11 +++ .../xcm-executor/integration-tests/src/lib.rs | 6 +- polkadot/xcm/xcm-executor/src/lib.rs | 3 +- .../xcm/xcm-simulator/example/src/tests.rs | 4 + prdoc/pr_6643.prdoc | 47 ++++++++++++ 39 files changed, 341 insertions(+), 45 deletions(-) create mode 100644 prdoc/pr_6643.prdoc diff --git a/bridges/snowbridge/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs index 76d0b98e9eb4..5386b845f2ec 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/test.rs @@ -40,8 +40,8 @@ fn test_submit_happy_path() { .into(), nonce: 1, message_id: [ - 11, 25, 133, 51, 23, 68, 111, 211, 132, 94, 254, 17, 194, 252, 198, 233, 10, 193, - 156, 93, 72, 140, 65, 69, 79, 155, 154, 28, 141, 166, 171, 255, + 97, 161, 116, 204, 182, 115, 192, 144, 130, 243, 240, 193, 122, 154, 108, 91, 247, + 41, 226, 237, 202, 158, 238, 239, 210, 8, 147, 131, 84, 146, 171, 176, ], fee_burned: 110000000000, } diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index e03560f66e24..54a578b988a4 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -279,6 +279,7 @@ where // Call create_asset on foreign assets pallet. Transact { origin_kind: OriginKind::Xcm, + fallback_max_weight: None, call: ( create_call_index, asset_id, diff --git a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs index 9125c976525e..380f4983ad98 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/xcm_helpers.rs @@ -31,7 +31,7 @@ pub fn xcm_transact_paid_execution( VersionedXcm::from(Xcm(vec![ WithdrawAsset(fees.clone().into()), BuyExecution { fees, weight_limit }, - Transact { origin_kind, call }, + Transact { origin_kind, call, fallback_max_weight: None }, RefundSurplus, DepositAsset { assets: All.into(), @@ -53,7 +53,7 @@ pub fn xcm_transact_unpaid_execution( VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit, check_origin }, - Transact { origin_kind, call }, + Transact { origin_kind, call, fallback_max_weight: None }, ])) } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index 3c53cfb261be..592c2845255c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -43,7 +43,7 @@ fn transfer_and_transact_in_same_xcm( // xcm to be executed at dest let xcm_on_dest = Xcm(vec![ - Transact { origin_kind: OriginKind::Xcm, call }, + Transact { origin_kind: OriginKind::Xcm, call, fallback_max_weight: None }, ExpectTransactStatus(MaybeErrorCode::Success), // since this is the last hop, we don't need to further use any assets previously // reserved for fees (there are no further hops to cover transport fees for); we diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index d59553574c26..c72d5045ddc0 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -84,7 +84,11 @@ fn create_agent() { let remote_xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, DescendOrigin(Parachain(origin_para).into()), - Transact { origin_kind: OriginKind::Xcm, call: create_agent_call.encode().into() }, + Transact { + origin_kind: OriginKind::Xcm, + call: create_agent_call.encode().into(), + fallback_max_weight: None, + }, ])); // Rococo Global Consensus @@ -138,7 +142,11 @@ fn create_channel() { let create_agent_xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, DescendOrigin(Parachain(origin_para).into()), - Transact { origin_kind: OriginKind::Xcm, call: create_agent_call.encode().into() }, + Transact { + origin_kind: OriginKind::Xcm, + call: create_agent_call.encode().into(), + fallback_max_weight: None, + }, ])); let create_channel_call = @@ -147,7 +155,11 @@ fn create_channel() { let create_channel_xcm = VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, DescendOrigin(Parachain(origin_para).into()), - Transact { origin_kind: OriginKind::Xcm, call: create_channel_call.encode().into() }, + Transact { + origin_kind: OriginKind::Xcm, + call: create_channel_call.encode().into(), + fallback_max_weight: None, + }, ])); // Rococo Global Consensus diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs index db42704dae61..7831c8d66357 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs @@ -49,7 +49,7 @@ fn transfer_and_transact_in_same_xcm( // xcm to be executed at dest let xcm_on_dest = Xcm(vec![ - Transact { origin_kind: OriginKind::Xcm, call }, + Transact { origin_kind: OriginKind::Xcm, call, fallback_max_weight: None }, ExpectTransactStatus(MaybeErrorCode::Success), // since this is the last hop, we don't need to further use any assets previously // reserved for fees (there are no further hops to cover transport fees for); we diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship.rs b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship.rs index 80b82e0c446f..802fed1e681d 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship.rs @@ -41,6 +41,7 @@ fn fellows_whitelist_call() { ) .encode() .into(), + fallback_max_weight: None } ]))), }); diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs index 3dadcdd94870..1ba787aaec52 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs @@ -48,7 +48,11 @@ fn relay_commands_add_registrar() { dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), message: bx!(VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { origin_kind, call: add_registrar_call.encode().into() } + Transact { + origin_kind, + call: add_registrar_call.encode().into(), + fallback_max_weight: None + } ]))), }); @@ -107,7 +111,11 @@ fn relay_commands_add_registrar_wrong_origin() { dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), message: bx!(VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { origin_kind, call: add_registrar_call.encode().into() } + Transact { + origin_kind, + call: add_registrar_call.encode().into(), + fallback_max_weight: None + } ]))), }); @@ -194,7 +202,11 @@ fn relay_commands_kill_identity() { dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), message: bx!(VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { origin_kind, call: kill_identity_call.encode().into() } + Transact { + origin_kind, + call: kill_identity_call.encode().into(), + fallback_max_weight: None + } ]))), }); @@ -252,7 +264,11 @@ fn relay_commands_kill_identity_wrong_origin() { dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), message: bx!(VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { origin_kind, call: kill_identity_call.encode().into() } + Transact { + origin_kind, + call: kill_identity_call.encode().into(), + fallback_max_weight: None + } ]))), }); @@ -298,7 +314,11 @@ fn relay_commands_add_remove_username_authority() { dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), message: bx!(VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { origin_kind, call: add_username_authority.encode().into() } + Transact { + origin_kind, + call: add_username_authority.encode().into(), + fallback_max_weight: None + } ]))), }); @@ -383,7 +403,11 @@ fn relay_commands_add_remove_username_authority() { dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), message: bx!(VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { origin_kind, call: remove_username_authority.encode().into() } + Transact { + origin_kind, + call: remove_username_authority.encode().into(), + fallback_max_weight: None + } ]))), }); @@ -443,7 +467,11 @@ fn relay_commands_add_remove_username_authority_wrong_origin() { dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), message: bx!(VersionedXcm::from(Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { origin_kind, call: add_username_authority.encode().into() } + Transact { + origin_kind, + call: add_username_authority.encode().into(), + fallback_max_weight: None + } ]))), }); @@ -483,6 +511,7 @@ fn relay_commands_add_remove_username_authority_wrong_origin() { Transact { origin_kind: OriginKind::SovereignAccount, call: remove_username_authority.encode().into(), + fallback_max_weight: None, } ]))), }); diff --git a/cumulus/parachains/pallets/ping/src/lib.rs b/cumulus/parachains/pallets/ping/src/lib.rs index 2cf32c891fc0..b6423a81db3c 100644 --- a/cumulus/parachains/pallets/ping/src/lib.rs +++ b/cumulus/parachains/pallets/ping/src/lib.rs @@ -114,6 +114,7 @@ pub mod pallet { }) .encode() .into(), + fallback_max_weight: None, }]), ) { Ok((hash, cost)) => { @@ -214,6 +215,7 @@ pub mod pallet { }) .encode() .into(), + fallback_max_weight: None, }]), ) { Ok((hash, cost)) => diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs index 025c39bcee07..74f564037400 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs @@ -84,7 +84,11 @@ impl XcmWeightInfo for AssetHubRococoXcmWeight { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option, + _call: &DoubleEncoded, + ) -> Weight { XcmGeneric::::transact() } fn hrmp_new_channel_open_request( diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs index 35ff2dc367c0..ff99f1242b22 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs @@ -83,7 +83,11 @@ impl XcmWeightInfo for AssetHubWestendXcmWeight { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option, + _call: &DoubleEncoded, + ) -> Weight { XcmGeneric::::transact() } fn hrmp_new_channel_open_request( diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs index aeacc1a5471e..b1577e0ca7f6 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases.rs @@ -1205,14 +1205,20 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor let xcm = Xcm(vec![ WithdrawAsset(buy_execution_fee.clone().into()), BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, - Transact { origin_kind: OriginKind::Xcm, call: foreign_asset_create.into() }, + Transact { + origin_kind: OriginKind::Xcm, + call: foreign_asset_create.into(), + fallback_max_weight: None, + }, Transact { origin_kind: OriginKind::SovereignAccount, call: foreign_asset_set_metadata.into(), + fallback_max_weight: None, }, Transact { origin_kind: OriginKind::SovereignAccount, call: foreign_asset_set_team.into(), + fallback_max_weight: None, }, ExpectTransactStatus(MaybeErrorCode::Success), ]); @@ -1318,7 +1324,11 @@ pub fn create_and_manage_foreign_assets_for_local_consensus_parachain_assets_wor let xcm = Xcm(vec![ WithdrawAsset(buy_execution_fee.clone().into()), BuyExecution { fees: buy_execution_fee.clone(), weight_limit: Unlimited }, - Transact { origin_kind: OriginKind::Xcm, call: foreign_asset_create.into() }, + Transact { + origin_kind: OriginKind::Xcm, + call: foreign_asset_create.into(), + fallback_max_weight: None, + }, ExpectTransactStatus(MaybeErrorCode::from(DispatchError::BadOrigin.encode())), ]); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs index 288aac38563c..e5c6f493d6dc 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs @@ -84,7 +84,11 @@ impl XcmWeightInfo for BridgeHubRococoXcmWeight { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option, + _call: &DoubleEncoded, + ) -> Weight { XcmGeneric::::transact() } fn hrmp_new_channel_open_request( diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs index fa1304d11c6f..939b1c7a287b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs @@ -85,7 +85,11 @@ impl XcmWeightInfo for BridgeHubWestendXcmWeight { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option, + _call: &DoubleEncoded, + ) -> Weight { XcmGeneric::::transact() } fn hrmp_new_channel_open_request( diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs index d76ac443a147..35c3dd8836a8 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs @@ -135,6 +135,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: request_core_count_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); @@ -164,6 +165,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: request_revenue_info_at_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); @@ -192,6 +194,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: credit_account_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); @@ -256,6 +259,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: assign_core_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs index f69736e31451..2c4a97601c64 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs @@ -84,7 +84,11 @@ impl XcmWeightInfo for CoretimeRococoXcmWeight { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option, + _call: &DoubleEncoded, + ) -> Weight { XcmGeneric::::transact() } fn hrmp_new_channel_open_request( diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs index f0c03849750a..985e64fb76f9 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs @@ -127,6 +127,12 @@ impl CoretimeInterface for CoretimeAllocator { use crate::coretime::CoretimeProviderCalls::RequestCoreCount; let request_core_count_call = RelayRuntimePallets::Coretime(RequestCoreCount(count)); + // Weight for `request_core_count` from westend benchmarks: + // `ref_time` = 7889000 + (3 * 25000000) + (1 * 100000000) = 182889000 + // `proof_size` = 1636 + // Add 5% to each component and round to 2 significant figures. + let call_weight = Weight::from_parts(190_000_000, 1700); + let message = Xcm(vec![ Instruction::UnpaidExecution { weight_limit: WeightLimit::Unlimited, @@ -135,6 +141,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: request_core_count_call.encode().into(), + fallback_max_weight: Some(call_weight), }, ]); @@ -164,6 +171,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: request_revenue_info_at_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); @@ -192,6 +200,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: credit_account_call.encode().into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000_000, 200_000)), }, ]); @@ -216,6 +225,12 @@ impl CoretimeInterface for CoretimeAllocator { ) { use crate::coretime::CoretimeProviderCalls::AssignCore; + // Weight for `assign_core` from westend benchmarks: + // `ref_time` = 10177115 + (1 * 25000000) + (2 * 100000000) + (57600 * 13932) = 937660315 + // `proof_size` = 3612 + // Add 5% to each component and round to 2 significant figures. + let call_weight = Weight::from_parts(980_000_000, 3800); + // The relay chain currently only allows `assign_core` to be called with a complete mask // and only ever with increasing `begin`. The assignments must be truncated to avoid // dropping that core's assignment completely. @@ -256,6 +271,7 @@ impl CoretimeInterface for CoretimeAllocator { Instruction::Transact { origin_kind: OriginKind::Native, call: assign_core_call.encode().into(), + fallback_max_weight: Some(call_weight), }, ]); diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs index 1640baa38c99..906088a1df86 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs @@ -83,7 +83,11 @@ impl XcmWeightInfo for CoretimeWestendXcmWeight { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option, + _call: &DoubleEncoded, + ) -> Weight { XcmGeneric::::transact() } fn hrmp_new_channel_open_request( diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs index 631cc7b7f0b0..47008a2943e5 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs @@ -83,7 +83,11 @@ impl XcmWeightInfo for PeopleRococoXcmWeight { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option, + _call: &DoubleEncoded, + ) -> Weight { XcmGeneric::::transact() } fn hrmp_new_channel_open_request( diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs index 4b51a3ba411b..27fd499ebba7 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs @@ -83,7 +83,11 @@ impl XcmWeightInfo for PeopleWestendXcmWeight { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) } - fn transact(_origin_type: &OriginKind, _call: &DoubleEncoded) -> Weight { + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option, + _call: &DoubleEncoded, + ) -> Weight { XcmGeneric::::transact() } fn hrmp_new_channel_open_request( diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index 3f2e721d13f6..5c33809ba67b 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -445,7 +445,11 @@ impl< // prepare xcm as governance will do let xcm = Xcm(vec![ UnpaidExecution { weight_limit: Unlimited, check_origin: None }, - Transact { origin_kind: OriginKind::Superuser, call: call.into() }, + Transact { + origin_kind: OriginKind::Superuser, + call: call.into(), + fallback_max_weight: None, + }, ExpectTransactStatus(MaybeErrorCode::Success), ]); @@ -476,7 +480,7 @@ impl< // prepare `Transact` xcm instructions.extend(vec![ - Transact { origin_kind, call: call.encode().into() }, + Transact { origin_kind, call: call.encode().into(), fallback_max_weight: None }, ExpectTransactStatus(MaybeErrorCode::Success), ]); let xcm = Xcm(instructions); diff --git a/polkadot/runtime/parachains/src/coretime/mod.rs b/polkadot/runtime/parachains/src/coretime/mod.rs index 966b7997a277..5656e92b90be 100644 --- a/polkadot/runtime/parachains/src/coretime/mod.rs +++ b/polkadot/runtime/parachains/src/coretime/mod.rs @@ -136,6 +136,11 @@ pub mod pallet { type AssetTransactor: TransactAsset; /// AccountId to Location converter type AccountToLocation: for<'a> TryConvert<&'a Self::AccountId, Location>; + + /// Maximum weight for any XCM transact call that should be executed on the coretime chain. + /// + /// Basically should be `max_weight(set_leases, reserve, notify_core_count)`. + type MaxXcmTransactWeight: Get; } #[pallet::event] @@ -333,6 +338,7 @@ impl OnNewSession> for Pallet { fn mk_coretime_call(call: crate::coretime::CoretimeCalls) -> Instruction<()> { Instruction::Transact { origin_kind: OriginKind::Superuser, + fallback_max_weight: Some(T::MaxXcmTransactWeight::get()), call: BrokerRuntimePallets::Broker(call).encode().into(), } } diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs index d701e1f9bd80..ee1990a7b618 100644 --- a/polkadot/runtime/parachains/src/mock.rs +++ b/polkadot/runtime/parachains/src/mock.rs @@ -421,6 +421,7 @@ impl assigner_coretime::Config for Test {} parameter_types! { pub const BrokerId: u32 = 10u32; + pub MaxXcmTransactWeight: Weight = Weight::from_parts(10_000_000, 10_000); } pub struct BrokerPot; @@ -437,6 +438,7 @@ impl coretime::Config for Test { type BrokerId = BrokerId; type WeightInfo = crate::coretime::TestWeightInfo; type SendXcm = DummyXcmSender; + type MaxXcmTransactWeight = MaxXcmTransactWeight; type BrokerPotLocation = BrokerPot; type AssetTransactor = (); type AccountToLocation = (); diff --git a/polkadot/runtime/rococo/src/impls.rs b/polkadot/runtime/rococo/src/impls.rs index ab796edc54b1..7d7e9fa9f06c 100644 --- a/polkadot/runtime/rococo/src/impls.rs +++ b/polkadot/runtime/rococo/src/impls.rs @@ -21,7 +21,7 @@ use core::marker::PhantomData; use frame_support::pallet_prelude::DispatchResult; use frame_system::RawOrigin; use polkadot_primitives::Balance; -use polkadot_runtime_common::identity_migrator::OnReapIdentity; +use polkadot_runtime_common::identity_migrator::{OnReapIdentity, WeightInfo}; use rococo_runtime_constants::currency::*; use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm}; use xcm_executor::traits::TransactAsset; @@ -88,7 +88,10 @@ where AccountId: Into<[u8; 32]> + Clone + Encode, { fn on_reap_identity(who: &AccountId, fields: u32, subs: u32) -> DispatchResult { - use crate::impls::IdentityMigratorCalls::PokeDeposit; + use crate::{ + impls::IdentityMigratorCalls::PokeDeposit, + weights::polkadot_runtime_common_identity_migrator::WeightInfo as MigratorWeights, + }; let total_to_send = Self::calculate_remote_deposit(fields, subs); @@ -141,6 +144,7 @@ where .into(); let poke = PeopleRuntimePallets::::IdentityMigrator(PokeDeposit(who.clone())); + let remote_weight_limit = MigratorWeights::::poke_deposit().saturating_mul(2); // Actual program to execute on People Chain. let program: Xcm<()> = Xcm(vec![ @@ -157,7 +161,11 @@ where .into(), }, // Poke the deposit to reserve the appropriate amount on the parachain. - Transact { origin_kind: OriginKind::Superuser, call: poke.encode().into() }, + Transact { + origin_kind: OriginKind::Superuser, + fallback_max_weight: Some(remote_weight_limit), + call: poke.encode().into(), + }, ]); // send diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 5da9da86f02e..c832ace91c07 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1100,6 +1100,7 @@ impl parachains_scheduler::Config for Runtime { parameter_types! { pub const BrokerId: u32 = BROKER_ID; pub const BrokerPalletId: PalletId = PalletId(*b"py/broke"); + pub MaxXcmTransactWeight: Weight = Weight::from_parts(200_000_000, 20_000); } pub struct BrokerPot; @@ -1123,6 +1124,7 @@ impl coretime::Config for Runtime { xcm_config::ThisNetwork, ::AccountId, >; + type MaxXcmTransactWeight = MaxXcmTransactWeight; } parameter_types! { diff --git a/polkadot/runtime/rococo/src/weights/xcm/mod.rs b/polkadot/runtime/rococo/src/weights/xcm/mod.rs index a28b46800874..16f51a778917 100644 --- a/polkadot/runtime/rococo/src/weights/xcm/mod.rs +++ b/polkadot/runtime/rococo/src/weights/xcm/mod.rs @@ -111,7 +111,11 @@ impl XcmWeightInfo for RococoXcmWeight { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmBalancesWeight::::transfer_reserve_asset()) } - fn transact(_origin_kind: &OriginKind, _call: &DoubleEncoded) -> Weight { + fn transact( + _origin_kind: &OriginKind, + _fallback_max_weight: &Option, + _call: &DoubleEncoded, + ) -> Weight { XcmGeneric::::transact() } fn hrmp_new_channel_open_request( diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 69ce187dce40..d4031f7ac57a 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -584,6 +584,7 @@ impl parachains_paras::Config for Runtime { parameter_types! { pub const BrokerId: u32 = 10u32; + pub MaxXcmTransactWeight: Weight = Weight::from_parts(10_000_000, 10_000); } pub struct BrokerPot; @@ -657,6 +658,7 @@ impl coretime::Config for Runtime { type BrokerId = BrokerId; type WeightInfo = crate::coretime::TestWeightInfo; type SendXcm = DummyXcmSender; + type MaxXcmTransactWeight = MaxXcmTransactWeight; type BrokerPotLocation = BrokerPot; type AssetTransactor = (); type AccountToLocation = (); diff --git a/polkadot/runtime/westend/src/impls.rs b/polkadot/runtime/westend/src/impls.rs index d7281dad56d4..8cb597cbaa95 100644 --- a/polkadot/runtime/westend/src/impls.rs +++ b/polkadot/runtime/westend/src/impls.rs @@ -21,7 +21,7 @@ use core::marker::PhantomData; use frame_support::pallet_prelude::DispatchResult; use frame_system::RawOrigin; use polkadot_primitives::Balance; -use polkadot_runtime_common::identity_migrator::OnReapIdentity; +use polkadot_runtime_common::identity_migrator::{OnReapIdentity, WeightInfo}; use westend_runtime_constants::currency::*; use xcm::{latest::prelude::*, VersionedLocation, VersionedXcm}; use xcm_executor::traits::TransactAsset; @@ -88,7 +88,10 @@ where AccountId: Into<[u8; 32]> + Clone + Encode, { fn on_reap_identity(who: &AccountId, fields: u32, subs: u32) -> DispatchResult { - use crate::impls::IdentityMigratorCalls::PokeDeposit; + use crate::{ + impls::IdentityMigratorCalls::PokeDeposit, + weights::polkadot_runtime_common_identity_migrator::WeightInfo as MigratorWeights, + }; let total_to_send = Self::calculate_remote_deposit(fields, subs); @@ -141,6 +144,7 @@ where .into(); let poke = PeopleRuntimePallets::::IdentityMigrator(PokeDeposit(who.clone())); + let remote_weight_limit = MigratorWeights::::poke_deposit().saturating_mul(2); // Actual program to execute on People Chain. let program: Xcm<()> = Xcm(vec![ @@ -157,7 +161,11 @@ where .into(), }, // Poke the deposit to reserve the appropriate amount on the parachain. - Transact { origin_kind: OriginKind::Superuser, call: poke.encode().into() }, + Transact { + origin_kind: OriginKind::Superuser, + call: poke.encode().into(), + fallback_max_weight: Some(remote_weight_limit), + }, ]); // send diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 9f0b701f20be..f25ed33012a2 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1326,6 +1326,7 @@ impl parachains_scheduler::Config for Runtime { parameter_types! { pub const BrokerId: u32 = BROKER_ID; pub const BrokerPalletId: PalletId = PalletId(*b"py/broke"); + pub MaxXcmTransactWeight: Weight = Weight::from_parts(200_000_000, 20_000); } pub struct BrokerPot; @@ -1349,6 +1350,7 @@ impl coretime::Config for Runtime { xcm_config::ThisNetwork, ::AccountId, >; + type MaxXcmTransactWeight = MaxXcmTransactWeight; } parameter_types! { diff --git a/polkadot/runtime/westend/src/weights/xcm/mod.rs b/polkadot/runtime/westend/src/weights/xcm/mod.rs index 5be9bad824da..60265445334d 100644 --- a/polkadot/runtime/westend/src/weights/xcm/mod.rs +++ b/polkadot/runtime/westend/src/weights/xcm/mod.rs @@ -114,7 +114,11 @@ impl XcmWeightInfo for WestendXcmWeight { fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { assets.weigh_assets(XcmBalancesWeight::::transfer_reserve_asset()) } - fn transact(_origin_kind: &OriginKind, _call: &DoubleEncoded) -> Weight { + fn transact( + _origin_kind: &OriginKind, + _fallback_max_weight: &Option, + _call: &DoubleEncoded, + ) -> Weight { XcmGeneric::::transact() } fn hrmp_new_channel_open_request( diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs index f4836b7cdde1..285322891c63 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs @@ -186,6 +186,7 @@ mod benchmarks { let instruction = Instruction::Transact { origin_kind: OriginKind::SovereignAccount, call: double_encoded_noop_call, + fallback_max_weight: None, }; let xcm = Xcm(vec![instruction]); #[block] diff --git a/polkadot/xcm/src/v4/mod.rs b/polkadot/xcm/src/v4/mod.rs index 9baf58eacfb0..3ae94b6ede88 100644 --- a/polkadot/xcm/src/v4/mod.rs +++ b/polkadot/xcm/src/v4/mod.rs @@ -1314,8 +1314,20 @@ impl TryFrom> for Instructi HrmpChannelAccepted { recipient } => Self::HrmpChannelAccepted { recipient }, HrmpChannelClosing { initiator, sender, recipient } => Self::HrmpChannelClosing { initiator, sender, recipient }, - Transact { origin_kind, mut call } => { - let require_weight_at_most = call.take_decoded()?.get_dispatch_info().call_weight; + Transact { origin_kind, mut call, fallback_max_weight } => { + // We first try to decode the call, if we can't, we use the fallback weight, + // if there's no fallback, we just return `Weight::MAX`. + let require_weight_at_most = match call.take_decoded() { + Ok(decoded) => decoded.get_dispatch_info().call_weight, + Err(error) => { + log::error!( + target: "xcm::versions::v5Tov4", + "Couldn't decode call in Transact: {:?}, using fallback weight.", + error, + ); + fallback_max_weight.unwrap_or(Weight::MAX) + }, + }; Self::Transact { origin_kind, require_weight_at_most, call: call.into() } }, ReportError(response_info) => Self::ReportError(QueryResponseInfo { diff --git a/polkadot/xcm/src/v5/mod.rs b/polkadot/xcm/src/v5/mod.rs index 830b23cc44b7..193b82b6c223 100644 --- a/polkadot/xcm/src/v5/mod.rs +++ b/polkadot/xcm/src/v5/mod.rs @@ -493,13 +493,21 @@ pub enum Instruction { /// /// - `origin_kind`: The means of expressing the message origin as a dispatch origin. /// - `call`: The encoded transaction to be applied. + /// - `fallback_max_weight`: Used for compatibility with previous versions. Corresponds to the + /// `require_weight_at_most` parameter in previous versions. If you don't care about + /// compatibility you can just put `None`. WARNING: If you do, your XCM might not work with + /// older versions. Make sure to dry-run and validate. /// /// Safety: No concerns. /// /// Kind: *Command*. /// /// Errors: - Transact { origin_kind: OriginKind, call: DoubleEncoded }, + Transact { + origin_kind: OriginKind, + fallback_max_weight: Option, + call: DoubleEncoded, + }, /// A message to notify about a new incoming HRMP channel. This message is meant to be sent by /// the relay-chain to a para. @@ -1159,7 +1167,8 @@ impl Instruction { HrmpChannelAccepted { recipient } => HrmpChannelAccepted { recipient }, HrmpChannelClosing { initiator, sender, recipient } => HrmpChannelClosing { initiator, sender, recipient }, - Transact { origin_kind, call } => Transact { origin_kind, call: call.into() }, + Transact { origin_kind, call, fallback_max_weight } => + Transact { origin_kind, call: call.into(), fallback_max_weight }, ReportError(response_info) => ReportError(response_info), DepositAsset { assets, beneficiary } => DepositAsset { assets, beneficiary }, DepositReserveAsset { assets, dest, xcm } => DepositReserveAsset { assets, dest, xcm }, @@ -1227,7 +1236,8 @@ impl> GetWeight for Instruction { TransferAsset { assets, beneficiary } => W::transfer_asset(assets, beneficiary), TransferReserveAsset { assets, dest, xcm } => W::transfer_reserve_asset(&assets, dest, xcm), - Transact { origin_kind, call } => W::transact(origin_kind, call), + Transact { origin_kind, fallback_max_weight, call } => + W::transact(origin_kind, fallback_max_weight, call), HrmpNewChannelOpenRequest { sender, max_message_size, max_capacity } => W::hrmp_new_channel_open_request(sender, max_message_size, max_capacity), HrmpChannelAccepted { recipient } => W::hrmp_channel_accepted(recipient), @@ -1343,8 +1353,11 @@ impl TryFrom> for Instruction { HrmpChannelAccepted { recipient } => Self::HrmpChannelAccepted { recipient }, HrmpChannelClosing { initiator, sender, recipient } => Self::HrmpChannelClosing { initiator, sender, recipient }, - Transact { origin_kind, require_weight_at_most: _, call } => - Self::Transact { origin_kind, call: call.into() }, + Transact { origin_kind, require_weight_at_most, call } => Self::Transact { + origin_kind, + call: call.into(), + fallback_max_weight: Some(require_weight_at_most), + }, ReportError(response_info) => Self::ReportError(QueryResponseInfo { query_id: response_info.query_id, destination: response_info.destination.try_into().map_err(|_| ())?, @@ -1577,6 +1590,59 @@ mod tests { assert_eq!(new_xcm, xcm); } + #[test] + fn transact_roundtrip_works() { + // We can convert as long as there's a fallback. + let xcm = Xcm::<()>(vec![ + WithdrawAsset((Here, 1u128).into()), + Transact { + origin_kind: OriginKind::SovereignAccount, + call: vec![200, 200, 200].into(), + fallback_max_weight: Some(Weight::from_parts(1_000_000, 1_024)), + }, + ]); + let old_xcm = OldXcm::<()>(vec![ + OldInstruction::WithdrawAsset((OldHere, 1u128).into()), + OldInstruction::Transact { + origin_kind: OriginKind::SovereignAccount, + call: vec![200, 200, 200].into(), + require_weight_at_most: Weight::from_parts(1_000_000, 1_024), + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + assert_eq!(new_xcm, xcm); + + // If we have no fallback the resulting message won't know the weight. + let xcm_without_fallback = Xcm::<()>(vec![ + WithdrawAsset((Here, 1u128).into()), + Transact { + origin_kind: OriginKind::SovereignAccount, + call: vec![200, 200, 200].into(), + fallback_max_weight: None, + }, + ]); + let old_xcm = OldXcm::<()>(vec![ + OldInstruction::WithdrawAsset((OldHere, 1u128).into()), + OldInstruction::Transact { + origin_kind: OriginKind::SovereignAccount, + call: vec![200, 200, 200].into(), + require_weight_at_most: Weight::MAX, + }, + ]); + assert_eq!(old_xcm, OldXcm::<()>::try_from(xcm_without_fallback.clone()).unwrap()); + let new_xcm: Xcm<()> = old_xcm.try_into().unwrap(); + let xcm_with_max_weight_fallback = Xcm::<()>(vec![ + WithdrawAsset((Here, 1u128).into()), + Transact { + origin_kind: OriginKind::SovereignAccount, + call: vec![200, 200, 200].into(), + fallback_max_weight: Some(Weight::MAX), + }, + ]); + assert_eq!(new_xcm, xcm_with_max_weight_fallback); + } + #[test] fn decoding_respects_limit() { let max_xcm = Xcm::<()>(vec![ClearOrigin; MAX_INSTRUCTIONS_TO_DECODE as usize]); diff --git a/polkadot/xcm/src/v5/traits.rs b/polkadot/xcm/src/v5/traits.rs index dd067b774fcd..71b67e97d5fe 100644 --- a/polkadot/xcm/src/v5/traits.rs +++ b/polkadot/xcm/src/v5/traits.rs @@ -428,6 +428,7 @@ pub type SendResult = result::Result<(T, Assets), SendError>; /// let message = Xcm(vec![Instruction::Transact { /// origin_kind: OriginKind::Superuser, /// call: call.into(), +/// fallback_max_weight: None, /// }]); /// let message_hash = message.using_encoded(sp_io::hashing::blake2_256); /// diff --git a/polkadot/xcm/xcm-builder/src/tests/transacting.rs b/polkadot/xcm/xcm-builder/src/tests/transacting.rs index 8963e7147fdc..ba932beaeb3d 100644 --- a/polkadot/xcm/xcm-builder/src/tests/transacting.rs +++ b/polkadot/xcm/xcm-builder/src/tests/transacting.rs @@ -23,6 +23,7 @@ fn transacting_should_work() { let message = Xcm::(vec![Transact { origin_kind: OriginKind::Native, call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }]); let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(60, 60); @@ -43,6 +44,7 @@ fn transacting_should_respect_max_weight_requirement() { let message = Xcm::(vec![Transact { origin_kind: OriginKind::Native, call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }]); let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(60, 60); @@ -65,6 +67,7 @@ fn transacting_should_refund_weight() { call: TestCall::Any(Weight::from_parts(50, 50), Some(Weight::from_parts(30, 30))) .encode() .into(), + fallback_max_weight: None, }]); let mut hash = fake_message_hash(&message); let weight_limit = Weight::from_parts(60, 60); @@ -96,6 +99,7 @@ fn paid_transacting_should_refund_payment_for_unused_weight() { call: TestCall::Any(Weight::from_parts(50, 50), Some(Weight::from_parts(10, 10))) .encode() .into(), + fallback_max_weight: None, }, RefundSurplus, DepositAsset { assets: AllCounted(1).into(), beneficiary: one }, @@ -124,6 +128,7 @@ fn report_successful_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ReportTransactStatus(QueryResponseInfo { destination: Parent.into(), @@ -159,6 +164,7 @@ fn report_failed_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ReportTransactStatus(QueryResponseInfo { destination: Parent.into(), @@ -194,6 +200,7 @@ fn expect_successful_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ExpectTransactStatus(MaybeErrorCode::Success), ]); @@ -212,6 +219,7 @@ fn expect_successful_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ExpectTransactStatus(MaybeErrorCode::Success), ]); @@ -238,6 +246,7 @@ fn expect_failed_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ExpectTransactStatus(vec![2].into()), ]); @@ -256,6 +265,7 @@ fn expect_failed_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::Any(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ExpectTransactStatus(vec![2].into()), ]); @@ -282,6 +292,7 @@ fn clear_transact_status_should_work() { Transact { origin_kind: OriginKind::Native, call: TestCall::OnlyRoot(Weight::from_parts(50, 50), None).encode().into(), + fallback_max_weight: None, }, ClearTransactStatus, ReportTransactStatus(QueryResponseInfo { diff --git a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs index 9b918fd7eeed..699a081e4f22 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs @@ -79,7 +79,11 @@ fn transact_recursion_limit_works() { Xcm(vec![ WithdrawAsset((Here, 1_000).into()), BuyExecution { fees: (Here, 1).into(), weight_limit: Unlimited }, - Transact { origin_kind: OriginKind::Native, call: call.encode().into() }, + Transact { + origin_kind: OriginKind::Native, + call: call.encode().into(), + fallback_max_weight: None, + }, ]) }; let mut call: Option = None; diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 4e051f24050c..11fd4e04761f 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -939,7 +939,8 @@ impl XcmExecutor { Ok(()) }) }, - Transact { origin_kind, mut call } => { + // `fallback_max_weight` is not used in the executor, it's only for conversions. + Transact { origin_kind, mut call, .. } => { // We assume that the Relay-chain is allowed to use transact on this parachain. let origin = self.cloned_origin().ok_or_else(|| { tracing::trace!( diff --git a/polkadot/xcm/xcm-simulator/example/src/tests.rs b/polkadot/xcm/xcm-simulator/example/src/tests.rs index bbac44ed8a1f..f971812f4f4d 100644 --- a/polkadot/xcm/xcm-simulator/example/src/tests.rs +++ b/polkadot/xcm/xcm-simulator/example/src/tests.rs @@ -47,6 +47,7 @@ fn dmp() { Xcm(vec![Transact { origin_kind: OriginKind::SovereignAccount, call: remark.encode().into(), + fallback_max_weight: None, }]), )); }); @@ -74,6 +75,7 @@ fn ump() { Xcm(vec![Transact { origin_kind: OriginKind::SovereignAccount, call: remark.encode().into(), + fallback_max_weight: None, }]), )); }); @@ -101,6 +103,7 @@ fn xcmp() { Xcm(vec![Transact { origin_kind: OriginKind::SovereignAccount, call: remark.encode().into(), + fallback_max_weight: None, }]), )); }); @@ -388,6 +391,7 @@ fn reserve_asset_class_create_and_reserve_transfer() { ) .encode() .into(), + fallback_max_weight: None, }]); // Send creation. assert_ok!(RelayChainPalletXcm::send_xcm(Here, Parachain(1), message)); diff --git a/prdoc/pr_6643.prdoc b/prdoc/pr_6643.prdoc new file mode 100644 index 000000000000..c111f6356519 --- /dev/null +++ b/prdoc/pr_6643.prdoc @@ -0,0 +1,47 @@ +title: Added fallback_max_weight to Transact for sending messages to V4 chains +doc: +- audience: Runtime Dev + description: |- + Removing the `require_weight_at_most` parameter in V5 Transact introduced a problem when converting a message from V5 to V4 to send to chains that didn't upgrade yet. + The local chain doesn't know how to decode calls for remote chains so it can't automatically populate `require_weight_at_most` required by V4 Transact. + To fix this, XCM v5 Transact now also takes a `fallback_max_weight: Option` parameter. + This can be set to `None` if the instruction is not meant to be sent to chains running XCM versions lower than V5. + If set to `Some(weight)`, a subsequent conversion to V4 will result in `Transact { require_weight_at_most: weight, .. }`. + The plan is to remove this workaround in V6 since there will be a good conversion path from V6 to V5. +crates: +- name: snowbridge-router-primitives + bump: major +- name: emulated-integration-tests-common + bump: major +- name: asset-hub-rococo-runtime + bump: major +- name: asset-hub-westend-runtime + bump: major +- name: asset-test-utils + bump: major +- name: bridge-hub-rococo-runtime + bump: major +- name: bridge-hub-westend-runtime + bump: major +- name: coretime-rococo-runtime + bump: major +- name: coretime-westend-runtime + bump: major +- name: people-rococo-runtime + bump: major +- name: people-westend-runtime + bump: major +- name: parachains-runtimes-test-utils + bump: major +- name: polkadot-runtime-parachains + bump: major +- name: rococo-runtime + bump: major +- name: westend-runtime + bump: major +- name: staging-xcm + bump: major +- name: staging-xcm-builder + bump: major +- name: staging-xcm-executor + bump: major From f1263e4a2e400f5acafc708ab7c71fb92bf63cd3 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 21:14:16 +0200 Subject: [PATCH 113/340] more tests --- .../src/tests/snowbridge_v2.rs | 506 ++++++++++++------ 1 file changed, 356 insertions(+), 150 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index bd714fac1d0a..fc2f5533cf97 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -18,7 +18,6 @@ use bridge_hub_westend_runtime::{ EthereumInboundQueueV2, }; use codec::Encode; -use frame_support::traits::fungibles::Mutate; use hex_literal::hex; use snowbridge_router_primitives::inbound::{ v2::{Asset::NativeTokenERC20, Message}, @@ -26,171 +25,333 @@ use snowbridge_router_primitives::inbound::{ }; use sp_core::H160; use sp_runtime::MultiAddress; +use sp_core::H256; +use asset_hub_westend_runtime::ForeignAssets; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; const WETH: [u8; 20] = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14"); +/// An ERC-20 token to be registered and sent. +const TOKEN_ID: [u8; 20] = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); +const CHAIN_ID: u64 = 11155111u64; pub fn weth_location() -> Location { + erc20_token_location(WETH.into()) +} + +pub fn erc20_token_location(token_id: H160) -> Location { Location::new( 2, [ GlobalConsensus(EthereumNetwork::get().into()), - AccountKey20 { network: None, key: WETH.into() }, + AccountKey20 { network: None, key: token_id.into() }, ], ) } -pub fn register_weth() { - let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); - let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); - AssetHubWestend::execute_with(|| { - type RuntimeOrigin = ::RuntimeOrigin; +#[test] +fn register_token_v2() { + let relayer = BridgeHubWestendSender::get(); + let receiver = AssetHubWestendReceiver::get(); + BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); - assert_ok!(::ForeignAssets::force_create( - RuntimeOrigin::root(), - weth_location().try_into().unwrap(), - assethub_sovereign.clone().into(), - true, - 1000, //ED will be used as exchange rate by default when used to PayFees with - )); + register_foreign_asset(weth_location()); - assert!(::ForeignAssets::asset_exists( - weth_location().try_into().unwrap(), - )); + set_up_weth_and_dot_pool(weth_location()); - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &AssetHubWestendReceiver::get(), - 1000000, - )); + let claimer = AccountId32 { network: None, id: receiver.clone().into() }; + let claimer_bytes = claimer.encode(); - assert_ok!(::ForeignAssets::mint_into( - weth_location().try_into().unwrap(), - &AssetHubWestendSender::get(), - 1000000, - )); - }); -} + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); -pub(crate) fn set_up_weth_pool_with_wnd_on_ah_westend(asset: v5::Location) { - let wnd: v5::Location = v5::Parent.into(); - let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); - let owner = AssetHubWestendSender::get(); - let bh_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&CHAIN_ID); - AssetHubWestend::fund_accounts(vec![(owner.clone(), 3_000_000_000_000)]); + let token: H160 = TOKEN_ID.into(); + let asset_id = erc20_token_location(token.into()); + + let dot_asset = Location::new(1, Here); + let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + + // Used to pay the asset creation deposit. + let weth_asset_value = 9_000_000_000_000u128; + let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_asset_value }]; + let asset_deposit: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![ + // Exchange weth for dot to pay the asset creation deposit + ExchangeAsset { + give: asset_deposit.clone().into(), + want: dot_fee.clone().into(), + maximal: false, + }, + // Deposit the dot deposit into the bridge sovereign account (where the asset creation + // fee will be deducted from) + DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, + // Call to create the asset. + Transact { + origin_kind: OriginKind::Xcm, + call: ( + CreateAssetCall::get(), + asset_id, + MultiAddress::<[u8; 32], ()>::Id(bridge_owner.into()), + 1u128, + ) + .encode() + .into(), + }, + ExpectTransactStatus(MaybeErrorCode::Success), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let signed_owner = ::RuntimeOrigin::signed(owner.clone()); - let signed_bh_sovereign = - ::RuntimeOrigin::signed(bh_sovereign.clone()); + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); +} - assert_ok!(::ForeignAssets::mint( - signed_bh_sovereign.clone(), - asset.clone().into(), - bh_sovereign.clone().into(), - 3_500_000_000_000, - )); +#[test] +fn send_token_v2() { + let relayer = BridgeHubWestendSender::get(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - assert_ok!(::ForeignAssets::transfer( - signed_bh_sovereign.clone(), - asset.clone().into(), - owner.clone().into(), - 3_000_000_000_000, - )); + let token: H160 = TOKEN_ID.into(); + let token_location = erc20_token_location(token); - assert_ok!(::AssetConversion::create_pool( - signed_owner.clone(), - Box::new(wnd.clone()), - Box::new(asset.clone()), - )); + let beneficiary_acc_id: H256 = H256::random(); + let beneficiary_acc_bytes: [u8; 32] = beneficiary_acc_id.into(); + let beneficiary = + Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.into() }); + + let claimer_acc_id = H256::random(); + let claimer_acc_id_bytes: [u8; 32] = claimer_acc_id.into(); + let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; + let claimer_bytes = claimer.encode(); + + register_foreign_asset(weth_location()); + register_foreign_asset(token_location.clone()); + + let token_transfer_value = 2_000_000_000_000u128; + + let assets = vec![ + // to pay fees + NativeTokenERC20 { token_id: WETH.into(), value: 1_500_000_000_000u128 }, + // the token being transferred + NativeTokenERC20 { token_id: token.into(), value: token_transfer_value } + ]; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![ + DepositAsset { assets: Wild(AllOf { + id: AssetId(token_location.clone()), + fun: WildFungibility::Fungible, + }), beneficiary }, + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, - ] + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] ); + }); - assert_ok!(::AssetConversion::add_liquidity( - signed_owner.clone(), - Box::new(wnd), - Box::new(asset), - 1_000_000_000_000, - 2_000_000_000_000, - 1, - 1, - owner.into() - )); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the token was received and issued as a foreign asset on AssetHub assert_expected_events!( AssetHubWestend, - vec![ - RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, - ] + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Beneficiary received the token transfer value + assert_eq!( + ForeignAssets::balance(token_location, AccountId::from(beneficiary_acc_bytes)), + token_transfer_value + ); + + // Claimer received weth refund for fees paid + assert!( + ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 ); }); } #[test] -fn register_token_v2() { - // Whole register token fee is 374_851_000_000 +fn send_weth_v2() { let relayer = BridgeHubWestendSender::get(); - let receiver = AssetHubWestendReceiver::get(); - BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); - - register_weth(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - set_up_weth_pool_with_wnd_on_ah_westend(weth_location()); + let beneficiary_acc_id: H256 = H256::random(); + let beneficiary_acc_bytes: [u8; 32] = beneficiary_acc_id.into(); + let beneficiary = + Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.into() }); - let chain_id = 11155111u64; - let claimer = AccountId32 { network: None, id: receiver.clone().into() }; + let claimer_acc_id = H256::random(); + let claimer_acc_id_bytes: [u8; 32] = claimer_acc_id.into(); + let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; let claimer_bytes = claimer.encode(); + register_foreign_asset(weth_location()); + + let token_transfer_value = 2_000_000_000_000u128; + + let assets = vec![ + // to pay fees + NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, + // the token being transferred + ]; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![ + DepositAsset { assets: Wild(AllOf { + id: AssetId(weth_location().clone()), + fun: WildFungibility::Fungible, + }), beneficiary }, + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Beneficiary received the token transfer value + assert_eq!( + ForeignAssets::balance(weth_location(), AccountId::from(beneficiary_acc_bytes)), + token_transfer_value + ); + + // Claimer received weth refund for fees paid + assert!( + ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 + ); + }); +} + +#[test] +fn register_and_send_tokens_v2() { + let relayer = BridgeHubWestendSender::get(); let relayer_location = Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); - let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); + let token: H160 = TOKEN_ID.into(); + let token_location = erc20_token_location(token); - let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // a is the token + let bridge_owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&CHAIN_ID); - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let beneficiary_acc_id: H256 = H256::random(); + let beneficiary_acc_bytes: [u8; 32] = beneficiary_acc_id.into(); + let beneficiary = + Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.clone().into() }); - // Used to pay the asset creation deposit. - let weth_asset_value = 9_000_000_000_000u128; - let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_asset_value }]; - let asset_deposit_weth: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); + AssetHubWestend::fund_accounts(vec![(sp_runtime::AccountId32::from(beneficiary_acc_bytes), 3_000_000_000_000)]); - let asset_id = Location::new( - 2, - [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], - ); + let claimer_acc_id = H256::random(); + let claimer_acc_id_bytes: [u8; 32] = claimer_acc_id.into(); + let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; + let claimer_bytes = claimer.encode(); + + register_foreign_asset(weth_location()); + + set_up_weth_and_dot_pool(weth_location()); + + let token_transfer_value = 2_000_000_000_000u128; + let weth_transfer_value = 2_500_000_000_000u128; let dot_asset = Location::new(1, Here); let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + // Used to pay the asset creation deposit. + let weth_asset_value = 9_000_000_000_000u128; + let asset_deposit: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); + + let assets = vec![ + // to pay fees and transfer assets + NativeTokenERC20 { token_id: WETH.into(), value: 2_800_000_000_000u128 }, + // the token being transferred + NativeTokenERC20 { token_id: token.into(), value: token_transfer_value } + ]; + BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let register_token_instructions = vec![ - // Exchange weth for dot to pay the asset creation deposit + let instructions = vec![ ExchangeAsset { - give: asset_deposit_weth.clone().into(), + give: asset_deposit.clone().into(), want: dot_fee.clone().into(), maximal: false, }, - // Deposit the dot deposit into the bridge sovereign account (where the asset creation - // fee will be deducted from) DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, - // Call to create the asset. Transact { origin_kind: OriginKind::Xcm, call: ( CreateAssetCall::get(), - asset_id, + token_location.clone(), MultiAddress::<[u8; 32], ()>::Id(bridge_owner.into()), 1u128, ) @@ -198,8 +359,12 @@ fn register_token_v2() { .into(), }, ExpectTransactStatus(MaybeErrorCode::Success), + DepositAsset { assets: Wild(AllOf { + id: AssetId(token_location.clone()), + fun: WildFungibility::Fungible, + }), beneficiary: beneficiary.clone() }, ]; - let xcm: Xcm<()> = register_token_instructions.into(); + let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); let origin = EthereumGatewayAddress::get(); @@ -210,6 +375,7 @@ fn register_token_v2() { xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), }; + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); @@ -222,69 +388,109 @@ fn register_token_v2() { AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; + // The token was created assert_expected_events!( AssetHubWestend, vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] ); + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Beneficiary received the token transfer value + assert_eq!( + ForeignAssets::balance(token_location, AccountId::from(beneficiary_acc_bytes)), + token_transfer_value + ); + + // Claimer received weth refund for fees paid + assert!( + ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 + ); }); } -#[test] -fn register_token_xcm() { - BridgeHubWestend::execute_with(|| { - println!("register token mainnet: {:x?}", get_xcm_hex(1u64)); - println!("===============================",); - println!("register token sepolia: {:x?}", get_xcm_hex(11155111u64)); +pub fn register_foreign_asset(token_location: Location) { + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + token_location.clone().try_into().unwrap(), + assethub_sovereign.clone().into(), + true, + 1000, + )); + + assert!(::ForeignAssets::asset_exists( + token_location.clone().try_into().unwrap(), + )); }); } -fn get_xcm_hex(chain_id: u64) -> String { - let owner = EthereumLocationsConverterFor::<[u8; 32]>::from_chain_id(&chain_id); - let weth_token_id: H160 = hex!("be68fc2d8249eb60bfcf0e71d5a0d2f2e292c4ed").into(); // TODO insert token id - let token: H160 = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").into(); // token id placeholder - let weth_amount = 300_000_000_000_000u128; +pub(crate) fn set_up_weth_and_dot_pool(asset: v5::Location) { + let wnd: v5::Location = v5::Parent.into(); + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let owner = AssetHubWestendSender::get(); + let bh_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - let dot_asset = Location::new(1, Here); - let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); + AssetHubWestend::fund_accounts(vec![(owner.clone(), 3_000_000_000_000)]); - println!("register token id: {:x?}", token); - println!("weth token id: {:x?}", weth_token_id); - println!("weth_amount: {:x?}", hex::encode(weth_amount.encode())); - println!("dot asset: {:x?}", hex::encode(dot_fee.encode())); + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; - let weth_asset = Location::new( - 2, - [ - GlobalConsensus(ethereum_network_v5), - AccountKey20 { network: None, key: weth_token_id.into() }, - ], - ); - let weth_fee: xcm::prelude::Asset = (weth_asset, weth_amount).into(); // TODO replace Weth fee acmount + let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + let signed_bh_sovereign = + ::RuntimeOrigin::signed(bh_sovereign.clone()); - let asset_id = Location::new( - 2, - [GlobalConsensus(ethereum_network_v5), AccountKey20 { network: None, key: token.into() }], - ); - - let register_token_xcm = vec![ - ExchangeAsset { give: weth_fee.into(), want: dot_fee.clone().into(), maximal: false }, - PayFees { asset: dot_fee }, - Transact { - origin_kind: OriginKind::Xcm, - call: ( - CreateAssetCall::get(), - asset_id, - MultiAddress::<[u8; 32], ()>::Id(owner.into()), - 1, - ) - .encode() - .into(), - }, - ]; - let message_xcm: Xcm<()> = register_token_xcm.into(); - let versioned_message_xcm = VersionedXcm::V5(message_xcm); + assert_ok!(::ForeignAssets::mint( + signed_bh_sovereign.clone(), + asset.clone().into(), + bh_sovereign.clone().into(), + 3_500_000_000_000, + )); + + assert_ok!(::ForeignAssets::transfer( + signed_bh_sovereign.clone(), + asset.clone().into(), + owner.clone().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + signed_owner.clone(), + Box::new(wnd.clone()), + Box::new(asset.clone()), + )); - let xcm_bytes = versioned_message_xcm.encode(); - hex::encode(xcm_bytes) + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + signed_owner.clone(), + Box::new(wnd), + Box::new(asset), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner.into() + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); } From 6af760bb5451ad17572ae70120be7b943f60f19b Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 5 Dec 2024 21:14:37 +0200 Subject: [PATCH 114/340] allow invalid xcm --- .../snowbridge/primitives/router/src/inbound/v2.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index d03a6c1c4eba..15c58e1315e7 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -118,13 +118,16 @@ where ) -> Result, ConvertMessageError> { let mut message_xcm: Xcm<()> = Xcm::new(); if message.xcm.len() > 0 { - // Decode xcm - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( + // Allow xcm decode failure so that assets can be trapped on AH instead of this + // message failing but funds are already locked on Ethereum. + if let Ok(versioned_xcm) = VersionedXcm::<()>::decode_with_depth_limit( MAX_XCM_DECODE_DEPTH, &mut message.xcm.as_ref(), - ) - .map_err(|_| ConvertMessageError::InvalidVersionedXCM)?; - message_xcm = versioned_xcm.try_into().map_err(|_| ConvertMessageError::InvalidXCM)?; + ) { + if let Ok(decoded_xcm) = versioned_xcm.try_into() { + message_xcm = decoded_xcm; + } + } } log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); From f7838db506f48e48671f867f23d8c12858c5b67c Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Thu, 5 Dec 2024 22:27:24 +0100 Subject: [PATCH 115/340] `basic-authorship`: debug level is now less spammy (#6768) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `debug` level in `sc-basic-authorship` is now less spammy. Previously it was outputing logs per individual transactions. It was quite hard to follow the logs (and also generates unneeded traffic in grafana). Now `debug` level only show some internal details, without spamming output with per-transaction logs. They were moved to `trace` level. I also added the `EndProposingReason` to the summary INFO message. This allows us to know what was the block limit (which is very useful for debugging). Example: ``` 🎁 Prepared block for proposing at 64 (1186 ms) hash: 0x4b5386c13c507d0dbab319ac054cc1bcfa08311e184452221ad07f12ecc6091c; parent_hash: 0x157c…ca5e; end: HitBlockWeightLimit; extrinsics_count: 7032; ``` --------- Co-authored-by: GitHub Action --- prdoc/pr_6768.prdoc | 14 ++++++++++++++ .../basic-authorship/src/basic_authorship.rs | 12 +++++++----- substrate/client/proposer-metrics/src/lib.rs | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 prdoc/pr_6768.prdoc diff --git a/prdoc/pr_6768.prdoc b/prdoc/pr_6768.prdoc new file mode 100644 index 000000000000..3e194078df26 --- /dev/null +++ b/prdoc/pr_6768.prdoc @@ -0,0 +1,14 @@ +title: '`basic-authorship`: debug level is now less spammy' +doc: +- audience: Node Dev + description: |- + The `debug` level in `sc-basic-authorship` is now less spammy. Previously it was outputing logs per individual transactions. It made quite hard to follow the logs (and also generates unneeded traffic in grafana). + + Now debug level only show some internal details, without spamming output with per-transaction logs. They were moved to `trace` level. + + I also added the `EndProposingReason` to the summary INFO message. This allows us to know what was the block limit (which is very useful for debugging). +crates: +- name: sc-basic-authorship + bump: major +- name: sc-proposer-metrics + bump: major diff --git a/substrate/client/basic-authorship/src/basic_authorship.rs b/substrate/client/basic-authorship/src/basic_authorship.rs index 79e6fddae99f..848ac724c6b8 100644 --- a/substrate/client/basic-authorship/src/basic_authorship.rs +++ b/substrate/client/basic-authorship/src/basic_authorship.rs @@ -483,7 +483,7 @@ where match sc_block_builder::BlockBuilder::push(block_builder, pending_tx_data) { Ok(()) => { transaction_pushed = true; - debug!(target: LOG_TARGET, "[{:?}] Pushed to the block.", pending_tx_hash); + trace!(target: LOG_TARGET, "[{:?}] Pushed to the block.", pending_tx_hash); }, Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { pending_iterator.report_invalid(&pending_tx); @@ -565,20 +565,22 @@ where if log::log_enabled!(log::Level::Info) { info!( - "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; extrinsics_count: {}", + "🎁 Prepared block for proposing at {} ({} ms) hash: {:?}; parent_hash: {}; end: {:?}; extrinsics_count: {}", block.header().number(), block_took.as_millis(), ::Hash::from(block.header().hash()), block.header().parent_hash(), + end_reason, extrinsics.len() ) - } else if log::log_enabled!(log::Level::Debug) { - debug!( - "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; {extrinsics_summary}", + } else if log::log_enabled!(log::Level::Trace) { + trace!( + "🎁 Prepared block for proposing at {} ({} ms) hash: {:?}; parent_hash: {}; end: {:?}; {extrinsics_summary}", block.header().number(), block_took.as_millis(), ::Hash::from(block.header().hash()), block.header().parent_hash(), + end_reason ); } diff --git a/substrate/client/proposer-metrics/src/lib.rs b/substrate/client/proposer-metrics/src/lib.rs index 2856300cf802..a62278988f12 100644 --- a/substrate/client/proposer-metrics/src/lib.rs +++ b/substrate/client/proposer-metrics/src/lib.rs @@ -44,7 +44,7 @@ impl MetricsLink { } /// The reason why proposing a block ended. -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum EndProposingReason { NoMoreTransactions, HitDeadline, From b3687603853b007f1aeedc590ff52b0e1634eac6 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Fri, 6 Dec 2024 10:50:44 +0200 Subject: [PATCH 116/340] allow invalid claimer --- .../primitives/router/src/inbound/v2.rs | 18 +- .../src/tests/snowbridge_v2.rs | 204 +++++++++++++++--- 2 files changed, 184 insertions(+), 38 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 15c58e1315e7..b154983aec59 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -55,12 +55,6 @@ pub enum Asset { /// Reason why a message conversion failed. #[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] pub enum ConvertMessageError { - /// The XCM provided with the message could not be decoded into XCM. - InvalidXCM, - /// The XCM provided with the message could not be decoded into versioned XCM. - InvalidVersionedXCM, - /// Invalid claimer MultiAddress provided in payload. - InvalidClaimer, /// Invalid foreign ERC20 token ID InvalidAsset, } @@ -184,11 +178,13 @@ where let mut refund_surplus_to = origin_account_location; if let Some(claimer) = message.claimer { - let claimer = Junction::decode(&mut claimer.as_ref()) - .map_err(|_| ConvertMessageError::InvalidClaimer)?; - let claimer_location: Location = Location::new(0, [claimer.into()]); - refund_surplus_to = claimer_location.clone(); - instructions.push(SetAssetClaimer { location: claimer_location }); + // If the claimer can be decoded, add it to the message. If the claimer decoding fails, + // do not add it to the message, because it will cause the xcm to fail. + if let Ok(claimer) = Junction::decode(&mut claimer.as_ref()) { + let claimer_location: Location = Location::new(0, [claimer.into()]); + refund_surplus_to = claimer_location.clone(); + instructions.push(SetAssetClaimer { location: claimer_location }); + } } // If the message origin is not the gateway proxy contract, set the origin to diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index fc2f5533cf97..66a34504a2cf 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -13,6 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::imports::*; +use asset_hub_westend_runtime::ForeignAssets; use bridge_hub_westend_runtime::{ bridge_to_ethereum_config::{CreateAssetCall, CreateAssetDeposit, EthereumGatewayAddress}, EthereumInboundQueueV2, @@ -23,10 +24,8 @@ use snowbridge_router_primitives::inbound::{ v2::{Asset::NativeTokenERC20, Message}, EthereumLocationsConverterFor, }; -use sp_core::H160; +use sp_core::{H160, H256}; use sp_runtime::MultiAddress; -use sp_core::H256; -use asset_hub_westend_runtime::ForeignAssets; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; @@ -163,17 +162,18 @@ fn send_token_v2() { // to pay fees NativeTokenERC20 { token_id: WETH.into(), value: 1_500_000_000_000u128 }, // the token being transferred - NativeTokenERC20 { token_id: token.into(), value: token_transfer_value } + NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, ]; BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let instructions = vec![ - DepositAsset { assets: Wild(AllOf { + let instructions = vec![DepositAsset { + assets: Wild(AllOf { id: AssetId(token_location.clone()), fun: WildFungibility::Fungible, - }), beneficiary }, - ]; + }), + beneficiary, + }]; let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); let origin = EthereumGatewayAddress::get(); @@ -211,9 +211,7 @@ fn send_token_v2() { ); // Claimer received weth refund for fees paid - assert!( - ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 - ); + assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); }); } @@ -245,12 +243,13 @@ fn send_weth_v2() { BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let instructions = vec![ - DepositAsset { assets: Wild(AllOf { + let instructions = vec![DepositAsset { + assets: Wild(AllOf { id: AssetId(weth_location().clone()), fun: WildFungibility::Fungible, - }), beneficiary }, - ]; + }), + beneficiary, + }]; let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); let origin = EthereumGatewayAddress::get(); @@ -288,14 +287,12 @@ fn send_weth_v2() { ); // Claimer received weth refund for fees paid - assert!( - ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 - ); + assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); }); } #[test] -fn register_and_send_tokens_v2() { +fn register_and_send_multiple_tokens_v2() { let relayer = BridgeHubWestendSender::get(); let relayer_location = Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); @@ -310,7 +307,11 @@ fn register_and_send_tokens_v2() { let beneficiary = Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.clone().into() }); - AssetHubWestend::fund_accounts(vec![(sp_runtime::AccountId32::from(beneficiary_acc_bytes), 3_000_000_000_000)]); + // To satisfy ED + AssetHubWestend::fund_accounts(vec![( + sp_runtime::AccountId32::from(beneficiary_acc_bytes), + 3_000_000_000_000, + )]); let claimer_acc_id = H256::random(); let claimer_acc_id_bytes: [u8; 32] = claimer_acc_id.into(); @@ -335,7 +336,7 @@ fn register_and_send_tokens_v2() { // to pay fees and transfer assets NativeTokenERC20 { token_id: WETH.into(), value: 2_800_000_000_000u128 }, // the token being transferred - NativeTokenERC20 { token_id: token.into(), value: token_transfer_value } + NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, ]; BridgeHubWestend::execute_with(|| { @@ -347,6 +348,7 @@ fn register_and_send_tokens_v2() { maximal: false, }, DepositAsset { assets: dot_fee.into(), beneficiary: bridge_owner.into() }, + // register new token Transact { origin_kind: OriginKind::Xcm, call: ( @@ -359,10 +361,22 @@ fn register_and_send_tokens_v2() { .into(), }, ExpectTransactStatus(MaybeErrorCode::Success), - DepositAsset { assets: Wild(AllOf { - id: AssetId(token_location.clone()), - fun: WildFungibility::Fungible, - }), beneficiary: beneficiary.clone() }, + // deposit new token to beneficiary + DepositAsset { + assets: Wild(AllOf { + id: AssetId(token_location.clone()), + fun: WildFungibility::Fungible, + }), + beneficiary: beneficiary.clone(), + }, + // deposit weth to beneficiary + DepositAsset { + assets: Wild(AllOf { + id: AssetId(weth_location()), + fun: WildFungibility::Fungible, + }), + beneficiary: beneficiary.clone(), + }, ]; let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); @@ -406,13 +420,149 @@ fn register_and_send_tokens_v2() { token_transfer_value ); - // Claimer received weth refund for fees paid + // Beneficiary received the weth transfer value assert!( - ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0 + ForeignAssets::balance(weth_location(), AccountId::from(beneficiary_acc_bytes)) > + weth_transfer_value + ); + + // Claimer received weth refund for fees paid + assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); + }); +} + +#[test] +fn invalid_xcm_traps_funds_on_ah() { + let relayer = BridgeHubWestendSender::get(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); + + let token: H160 = TOKEN_ID.into(); + let claimer = AccountId32 { network: None, id: H256::random().into() }; + let claimer_bytes = claimer.encode(); + let beneficiary_acc_bytes: [u8; 32] = H256::random().into(); + + AssetHubWestend::fund_accounts(vec![( + sp_runtime::AccountId32::from(beneficiary_acc_bytes), + 3_000_000_000_000, + )]); + + register_foreign_asset(weth_location()); + + set_up_weth_and_dot_pool(weth_location()); + + let assets = vec![ + // to pay fees and transfer assets + NativeTokenERC20 { token_id: WETH.into(), value: 2_800_000_000_000u128 }, + // the token being transferred + NativeTokenERC20 { token_id: token.into(), value: 2_000_000_000_000u128 }, + ]; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // invalid xcm + let instructions = hex!("02806c072d50e2c7cd6821d1f084cbb4"); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: instructions.to_vec(), + claimer: Some(claimer_bytes), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Assets are trapped + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::PolkadotXcm(pallet_xcm::Event::AssetsTrapped { .. }) => {},] ); }); } +#[test] +fn invalid_claimer_does_not_fail_the_message() { + let relayer = BridgeHubWestendSender::get(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); + + let beneficiary_acc: [u8; 32] = H256::random().into(); + let beneficiary = Location::new(0, AccountId32 { network: None, id: beneficiary_acc.into() }); + + register_foreign_asset(weth_location()); + + let token_transfer_value = 2_000_000_000_000u128; + + let assets = vec![ + // to pay fees + NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, + // the token being transferred + ]; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![DepositAsset { + assets: Wild(AllOf { + id: AssetId(weth_location().clone()), + fun: WildFungibility::Fungible, + }), + beneficiary, + }]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + // Set an invalid claimer + claimer: Some(hex!("2b7ce7bc7e87e4d6619da21487c7a53f").to_vec()), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + // Message still processes successfully + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Beneficiary received the token transfer value + assert_eq!( + ForeignAssets::balance(weth_location(), AccountId::from(beneficiary_acc)), + token_transfer_value + ); + + // Relayer (instead of claimer) received weth refund for fees paid + assert!(ForeignAssets::balance(weth_location(), AccountId::from(relayer)) > 0); + }); +} + pub fn register_foreign_asset(token_location: Location) { let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); From 9939927a434e70aa68924c65e4d8b3526147293d Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Fri, 6 Dec 2024 10:53:22 +0200 Subject: [PATCH 117/340] move claimer before assets --- .../primitives/router/src/inbound/v2.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index b154983aec59..e0cc59f08769 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -146,6 +146,16 @@ where let mut reserve_assets = vec![]; let mut withdraw_assets = vec![]; + if let Some(claimer) = message.claimer { + // If the claimer can be decoded, add it to the message. If the claimer decoding fails, + // do not add it to the message, because it will cause the xcm to fail. + if let Ok(claimer) = Junction::decode(&mut claimer.as_ref()) { + let claimer_location: Location = Location::new(0, [claimer.into()]); + refund_surplus_to = claimer_location.clone(); + instructions.push(SetAssetClaimer { location: claimer_location }); + } + } + for asset in &message.assets { match asset { Asset::NativeTokenERC20 { token_id, value } => { @@ -177,16 +187,6 @@ where let mut refund_surplus_to = origin_account_location; - if let Some(claimer) = message.claimer { - // If the claimer can be decoded, add it to the message. If the claimer decoding fails, - // do not add it to the message, because it will cause the xcm to fail. - if let Ok(claimer) = Junction::decode(&mut claimer.as_ref()) { - let claimer_location: Location = Location::new(0, [claimer.into()]); - refund_surplus_to = claimer_location.clone(); - instructions.push(SetAssetClaimer { location: claimer_location }); - } - } - // If the message origin is not the gateway proxy contract, set the origin to // the original sender on Ethereum. Important to be before the arbitrary XCM that is // appended to the message on the next line. From 8f11a76b6301c733aaa935a702fbe030645a98b8 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Fri, 6 Dec 2024 11:38:01 +0200 Subject: [PATCH 118/340] apply weight at most change --- bridges/snowbridge/primitives/router/src/inbound/v1.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs index 42a04d0dc6c9..b78c9ca78b43 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v1.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v1.rs @@ -273,6 +273,7 @@ where // Call create_asset on foreign assets pallet. Transact { origin_kind: OriginKind::Xcm, + fallback_max_weight: None, call: ( create_call_index, asset_id, From 526a440203d1ff96640c6163291e57e10c8e4f9b Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:34:08 +0100 Subject: [PATCH 119/340] Update weights for westend pallet balances (#6777) Co-authored-by: command-bot <> --- .../westend/src/weights/pallet_balances.rs | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/polkadot/runtime/westend/src/weights/pallet_balances.rs b/polkadot/runtime/westend/src/weights/pallet_balances.rs index 5e91f31920ca..b303a1a21766 100644 --- a/polkadot/runtime/westend/src/weights/pallet_balances.rs +++ b/polkadot/runtime/westend/src/weights/pallet_balances.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_balances` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-05-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-unxyhko3-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -54,8 +54,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 43_248_000 picoseconds. - Weight::from_parts(43_872_000, 0) + // Minimum execution time: 50_394_000 picoseconds. + Weight::from_parts(51_666_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -66,8 +66,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 33_990_000 picoseconds. - Weight::from_parts(34_693_000, 0) + // Minimum execution time: 40_307_000 picoseconds. + Weight::from_parts(41_722_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -78,8 +78,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 12_681_000 picoseconds. - Weight::from_parts(13_183_000, 0) + // Minimum execution time: 19_110_000 picoseconds. + Weight::from_parts(19_623_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -90,8 +90,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 17_474_000 picoseconds. - Weight::from_parts(18_063_000, 0) + // Minimum execution time: 26_837_000 picoseconds. + Weight::from_parts(27_672_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -102,8 +102,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 45_699_000 picoseconds. - Weight::from_parts(46_099_000, 0) + // Minimum execution time: 53_032_000 picoseconds. + Weight::from_parts(54_040_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -114,8 +114,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 42_453_000 picoseconds. - Weight::from_parts(43_133_000, 0) + // Minimum execution time: 49_429_000 picoseconds. + Weight::from_parts(50_020_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -126,8 +126,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 15_066_000 picoseconds. - Weight::from_parts(15_605_000, 0) + // Minimum execution time: 22_114_000 picoseconds. + Weight::from_parts(22_526_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -139,11 +139,11 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 14_180_000 picoseconds. - Weight::from_parts(14_598_000, 0) + // Minimum execution time: 17_550_000 picoseconds. + Weight::from_parts(17_860_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 13_221 - .saturating_add(Weight::from_parts(13_422_901, 0).saturating_mul(u.into())) + // Standard Error: 11_891 + .saturating_add(Weight::from_parts(15_027_705, 0).saturating_mul(u.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(u.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(u.into()))) .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) @@ -152,24 +152,24 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_130_000 picoseconds. - Weight::from_parts(5_257_000, 0) + // Minimum execution time: 6_605_000 picoseconds. + Weight::from_parts(6_922_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn burn_allow_death() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 27_328_000 picoseconds. - Weight::from_parts(27_785_000, 0) + // Minimum execution time: 31_182_000 picoseconds. + Weight::from_parts(32_104_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn burn_keep_alive() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 17_797_000 picoseconds. - Weight::from_parts(18_103_000, 0) + // Minimum execution time: 21_105_000 picoseconds. + Weight::from_parts(21_533_000, 0) .saturating_add(Weight::from_parts(0, 0)) } } From 906fa9e51306635245a22e03160d1c761fae6cc3 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Mon, 9 Dec 2024 00:02:38 +0100 Subject: [PATCH 120/340] XCM V5 - SetHints instruction (#6566) Last feature we wanted for V5, changing `SetAssetClaimer` to be just one of many possible "hints" that you can specify at the beginning of your program to change its behaviour. This makes it easier to add new hints in the future and have barriers accept them. --------- Co-authored-by: GitHub Action --- Cargo.lock | 1 + .../pallets/inbound-queue/src/test.rs | 4 +- .../src/tests/set_asset_claimer.rs | 4 +- .../asset-hub-rococo/src/weights/xcm/mod.rs | 13 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 2 +- .../asset-hub-westend/src/weights/xcm/mod.rs | 13 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 2 +- .../bridge-hub-rococo/src/weights/xcm/mod.rs | 13 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 2 +- .../bridge-hub-westend/src/weights/xcm/mod.rs | 13 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 2 +- .../coretime-rococo/src/weights/xcm/mod.rs | 13 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 2 +- .../coretime-westend/src/weights/xcm/mod.rs | 13 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 2 +- .../people-rococo/src/weights/xcm/mod.rs | 13 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 2 +- .../people-westend/src/weights/xcm/mod.rs | 13 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 2 +- .../runtime/rococo/src/weights/xcm/mod.rs | 13 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 2 +- .../runtime/westend/src/weights/xcm/mod.rs | 15 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 2 +- .../src/generic/benchmarking.rs | 8 +- polkadot/xcm/procedural/Cargo.toml | 2 + .../xcm/procedural/src/builder_pattern.rs | 394 ++++++++++-------- .../enum_variants.rs} | 36 +- polkadot/xcm/procedural/src/lib.rs | 9 + .../xcm/procedural/tests/builder_pattern.rs | 59 +++ ...ution_named_fields.rs => enum_variants.rs} | 23 +- .../loads_holding_no_operands.stderr | 6 - .../unexpected_attribute.stderr | 5 + .../unpaid_execution_named_fields.stderr | 5 - polkadot/xcm/src/v4/mod.rs | 2 +- polkadot/xcm/src/v5/mod.rs | 34 +- polkadot/xcm/xcm-builder/src/barriers.rs | 3 +- .../xcm/xcm-builder/src/tests/barriers.rs | 20 + polkadot/xcm/xcm-executor/src/lib.rs | 10 +- .../src/tests/set_asset_claimer.rs | 6 +- prdoc/pr_6566.prdoc | 45 ++ 40 files changed, 559 insertions(+), 269 deletions(-) rename polkadot/xcm/procedural/{tests/ui/builder_pattern/loads_holding_no_operands.rs => src/enum_variants.rs} (51%) rename polkadot/xcm/procedural/tests/{ui/builder_pattern/unpaid_execution_named_fields.rs => enum_variants.rs} (70%) delete mode 100644 polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.stderr create mode 100644 polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.stderr delete mode 100644 polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.stderr create mode 100644 prdoc/pr_6566.prdoc diff --git a/Cargo.lock b/Cargo.lock index dad578ba0c1b..cee1e2ce7411 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31675,6 +31675,7 @@ name = "xcm-procedural" version = "7.0.0" dependencies = [ "Inflector", + "frame-support 28.0.0", "proc-macro2 1.0.86", "quote 1.0.37", "staging-xcm 7.0.0", diff --git a/bridges/snowbridge/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs index 5386b845f2ec..1e0bd8acc925 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/test.rs @@ -40,8 +40,8 @@ fn test_submit_happy_path() { .into(), nonce: 1, message_id: [ - 97, 161, 116, 204, 182, 115, 192, 144, 130, 243, 240, 193, 122, 154, 108, 91, 247, - 41, 226, 237, 202, 158, 238, 239, 210, 8, 147, 131, 84, 146, 171, 176, + 86, 101, 80, 125, 84, 10, 227, 145, 230, 209, 152, 38, 206, 251, 206, 208, 244, + 221, 22, 215, 1, 252, 79, 181, 99, 207, 166, 220, 98, 3, 81, 7, ], fee_burned: 110000000000, } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/set_asset_claimer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/set_asset_claimer.rs index 544b05360521..bc00106b47c1 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/set_asset_claimer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/set_asset_claimer.rs @@ -44,7 +44,7 @@ fn test_set_asset_claimer_within_a_chain() { type RuntimeCall = ::RuntimeCall; let asset_trap_xcm = Xcm::::builder_unsafe() - .set_asset_claimer(bob_location.clone()) + .set_hints(vec![AssetClaimer { location: bob_location.clone() }]) .withdraw_asset(assets.clone()) .clear_origin() .build(); @@ -116,7 +116,7 @@ fn test_set_asset_claimer_between_the_chains() { let assets: Assets = (Parent, trap_amount).into(); type RuntimeCall = ::RuntimeCall; let trap_xcm = Xcm::::builder_unsafe() - .set_asset_claimer(alice_bh_sibling.clone()) + .set_hints(vec![AssetClaimer { location: alice_bh_sibling.clone() }]) .withdraw_asset(assets.clone()) .clear_origin() .build(); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs index 74f564037400..ccf473484cad 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/mod.rs @@ -22,6 +22,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -176,8 +177,16 @@ impl XcmWeightInfo for AssetHubRococoXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::::set_asset_claimer() + fn set_hints(hints: &BoundedVec) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::::asset_claimer()); + }, + } + } + weight } fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index b69c136b29d9..d48debef94c8 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -87,7 +87,7 @@ impl WeightInfo { // Minimum execution time: 5_803_000 picoseconds. Weight::from_parts(5_983_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs index ff99f1242b22..a0e9705ff01d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/mod.rs @@ -21,6 +21,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -176,8 +177,16 @@ impl XcmWeightInfo for AssetHubWestendXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::::set_asset_claimer() + fn set_hints(hints: &BoundedVec) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::::asset_claimer()); + }, + } + } + weight } fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 528694123115..0ec2741c0490 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -87,7 +87,7 @@ impl WeightInfo { // Minimum execution time: 5_580_000 picoseconds. Weight::from_parts(5_950_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs index e5c6f493d6dc..efc2798999bf 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/mod.rs @@ -22,6 +22,7 @@ use codec::Encode; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -257,8 +258,16 @@ impl XcmWeightInfo for BridgeHubRococoXcmWeight { fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::::set_asset_claimer() + fn set_hints(hints: &BoundedVec) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::::asset_claimer()); + }, + } + } + weight } fn execute_with_origin(_: &Option, _: &Xcm) -> Weight { XcmGeneric::::execute_with_origin() diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index bac73e0e0567..daf22190a42b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -373,7 +373,7 @@ impl WeightInfo { // Minimum execution time: 1_085_000 picoseconds. Weight::from_parts(1_161_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs index 939b1c7a287b..15a1dae09d9b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/mod.rs @@ -23,6 +23,7 @@ use codec::Encode; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -178,8 +179,16 @@ impl XcmWeightInfo for BridgeHubWestendXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::::set_asset_claimer() + fn set_hints(hints: &BoundedVec) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::::asset_claimer()); + }, + } + } + weight } fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 6434f6206fbe..03cbaa866ad8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -373,7 +373,7 @@ impl WeightInfo { // Minimum execution time: 995_000 picoseconds. Weight::from_parts(1_060_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs index 2c4a97601c64..dc21e2ea117f 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/mod.rs @@ -22,6 +22,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -255,8 +256,16 @@ impl XcmWeightInfo for CoretimeRococoXcmWeight { fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::::set_asset_claimer() + fn set_hints(hints: &BoundedVec) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::::asset_claimer()); + }, + } + } + weight } fn execute_with_origin(_: &Option, _: &Xcm) -> Weight { XcmGeneric::::execute_with_origin() diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index d207c09ffcd8..cdcba6134bf8 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -331,7 +331,7 @@ impl WeightInfo { // Minimum execution time: 650_000 picoseconds. Weight::from_parts(673_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs index 906088a1df86..29466b3718c1 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs @@ -21,6 +21,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -176,8 +177,16 @@ impl XcmWeightInfo for CoretimeWestendXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::::set_asset_claimer() + fn set_hints(hints: &BoundedVec) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::::asset_claimer()); + }, + } + } + weight } fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index fb6e4631736d..6c6d3cf8c525 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -331,7 +331,7 @@ impl WeightInfo { // Minimum execution time: 624_000 picoseconds. Weight::from_parts(659_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs index 47008a2943e5..d55198f60a00 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/mod.rs @@ -21,6 +21,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -254,8 +255,16 @@ impl XcmWeightInfo for PeopleRococoXcmWeight { fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::::set_asset_claimer() + fn set_hints(hints: &BoundedVec) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::::asset_claimer()); + }, + } + } + weight } fn execute_with_origin(_: &Option, _: &Xcm) -> Weight { XcmGeneric::::execute_with_origin() diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 6aac6119e7ec..caa916507348 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -331,7 +331,7 @@ impl WeightInfo { // Minimum execution time: 685_000 picoseconds. Weight::from_parts(757_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs index 27fd499ebba7..915a499cb77c 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs @@ -21,6 +21,7 @@ use alloc::vec::Vec; use frame_support::weights::Weight; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::{ latest::{prelude::*, AssetTransferFilter}, DoubleEncoded, @@ -254,8 +255,16 @@ impl XcmWeightInfo for PeopleWestendXcmWeight { fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::::set_asset_claimer() + fn set_hints(hints: &BoundedVec) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::::asset_claimer()); + }, + } + } + weight } fn execute_with_origin(_: &Option, _: &Xcm) -> Weight { XcmGeneric::::execute_with_origin() diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 36400f2c1e66..ad2cde22a075 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -331,7 +331,7 @@ impl WeightInfo { // Minimum execution time: 598_000 picoseconds. Weight::from_parts(655_000, 0) } - pub fn set_asset_claimer() -> Weight { + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/polkadot/runtime/rococo/src/weights/xcm/mod.rs b/polkadot/runtime/rococo/src/weights/xcm/mod.rs index 16f51a778917..eb27e5c5a897 100644 --- a/polkadot/runtime/rococo/src/weights/xcm/mod.rs +++ b/polkadot/runtime/rococo/src/weights/xcm/mod.rs @@ -24,6 +24,7 @@ use xcm::{latest::prelude::*, DoubleEncoded}; use pallet_xcm_benchmarks_fungible::WeightInfo as XcmBalancesWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::latest::AssetTransferFilter; /// Types of asset supported by the Rococo runtime. @@ -290,8 +291,16 @@ impl XcmWeightInfo for RococoXcmWeight { fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() } - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::::set_asset_claimer() + fn set_hints(hints: &BoundedVec) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::::asset_claimer()); + }, + } + } + weight } fn execute_with_origin(_: &Option, _: &Xcm) -> Weight { XcmGeneric::::execute_with_origin() diff --git a/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index e5915a7986bf..2dc8880c8326 100644 --- a/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/polkadot/runtime/rococo/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -82,7 +82,7 @@ impl WeightInfo { // Minimum execution time: 2_899_000 picoseconds. Weight::from_parts(3_090_000, 0) } - pub(crate) fn set_asset_claimer() -> Weight { + pub(crate) fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/polkadot/runtime/westend/src/weights/xcm/mod.rs b/polkadot/runtime/westend/src/weights/xcm/mod.rs index 60265445334d..d2691c998d99 100644 --- a/polkadot/runtime/westend/src/weights/xcm/mod.rs +++ b/polkadot/runtime/westend/src/weights/xcm/mod.rs @@ -27,6 +27,7 @@ use xcm::{ use pallet_xcm_benchmarks_fungible::WeightInfo as XcmBalancesWeight; use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; use xcm::latest::AssetTransferFilter; /// Types of asset supported by the westend runtime. @@ -208,11 +209,17 @@ impl XcmWeightInfo for WestendXcmWeight { fn clear_error() -> Weight { XcmGeneric::::clear_error() } - - fn set_asset_claimer(_location: &Location) -> Weight { - XcmGeneric::::set_asset_claimer() + fn set_hints(hints: &BoundedVec) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::::asset_claimer()); + }, + } + } + weight } - fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { XcmGeneric::::claim_asset() } diff --git a/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 076744a59753..dfc02fd20bc3 100644 --- a/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -82,7 +82,7 @@ impl WeightInfo { // Minimum execution time: 3_096_000 picoseconds. Weight::from_parts(3_313_000, 0) } - pub(crate) fn set_asset_claimer() -> Weight { + pub(crate) fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs index 285322891c63..431c7a5f1371 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs @@ -20,7 +20,7 @@ use crate::{account_and_location, new_executor, EnsureDelivery, XcmCallOf}; use alloc::{vec, vec::Vec}; use codec::Encode; use frame_benchmarking::v2::*; -use frame_support::traits::fungible::Inspect; +use frame_support::{traits::fungible::Inspect, BoundedVec}; use xcm::{ latest::{prelude::*, MaxDispatchErrorLen, MaybeErrorCode, Weight, MAX_ITEMS_IN_ASSETS}, DoubleEncoded, @@ -144,7 +144,11 @@ mod benchmarks { let mut executor = new_executor::(Default::default()); let (_, sender_location) = account_and_location::(1); - let instruction = Instruction::SetAssetClaimer { location: sender_location.clone() }; + let instruction = Instruction::SetHints { + hints: BoundedVec::::truncate_from(vec![AssetClaimer { + location: sender_location.clone(), + }]), + }; let xcm = Xcm(vec![instruction]); #[block] diff --git a/polkadot/xcm/procedural/Cargo.toml b/polkadot/xcm/procedural/Cargo.toml index 3167766158ff..88ed3c94ddf4 100644 --- a/polkadot/xcm/procedural/Cargo.toml +++ b/polkadot/xcm/procedural/Cargo.toml @@ -26,3 +26,5 @@ trybuild = { features = ["diff"], workspace = true } # NOTE: we have to explicitly specify `std` because of trybuild # https://github.com/paritytech/polkadot-sdk/pull/5167 xcm = { workspace = true, default-features = true, features = ["std"] } +# For testing macros. +frame-support = { workspace = true } diff --git a/polkadot/xcm/procedural/src/builder_pattern.rs b/polkadot/xcm/procedural/src/builder_pattern.rs index b65290332af9..34b89f13422c 100644 --- a/polkadot/xcm/procedural/src/builder_pattern.rs +++ b/polkadot/xcm/procedural/src/builder_pattern.rs @@ -20,8 +20,8 @@ use inflector::Inflector; use proc_macro2::TokenStream as TokenStream2; use quote::{format_ident, quote}; use syn::{ - Data, DataEnum, DeriveInput, Error, Expr, ExprLit, Fields, Ident, Lit, Meta, MetaNameValue, - Result, Variant, + Data, DataEnum, DeriveInput, Error, Expr, ExprLit, Fields, GenericArgument, Ident, Lit, Meta, + MetaNameValue, PathArguments, Result, Type, TypePath, Variant, }; pub fn derive(input: DeriveInput) -> Result { @@ -29,7 +29,7 @@ pub fn derive(input: DeriveInput) -> Result { Data::Enum(data_enum) => data_enum, _ => return Err(Error::new_spanned(&input, "Expected the `Instruction` enum")), }; - let builder_raw_impl = generate_builder_raw_impl(&input.ident, data_enum); + let builder_raw_impl = generate_builder_raw_impl(&input.ident, data_enum)?; let builder_impl = generate_builder_impl(&input.ident, data_enum)?; let builder_unpaid_impl = generate_builder_unpaid_impl(&input.ident, data_enum)?; let output = quote! { @@ -83,54 +83,12 @@ pub fn derive(input: DeriveInput) -> Result { Ok(output) } -fn generate_builder_raw_impl(name: &Ident, data_enum: &DataEnum) -> TokenStream2 { - let methods = data_enum.variants.iter().map(|variant| { - let variant_name = &variant.ident; - let method_name_string = &variant_name.to_string().to_snake_case(); - let method_name = syn::Ident::new(method_name_string, variant_name.span()); - let docs = get_doc_comments(variant); - let method = match &variant.fields { - Fields::Unit => { - quote! { - pub fn #method_name(mut self) -> Self { - self.instructions.push(#name::::#variant_name); - self - } - } - }, - Fields::Unnamed(fields) => { - let arg_names: Vec<_> = fields - .unnamed - .iter() - .enumerate() - .map(|(index, _)| format_ident!("arg{}", index)) - .collect(); - let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect(); - quote! { - pub fn #method_name(mut self, #(#arg_names: impl Into<#arg_types>),*) -> Self { - #(let #arg_names = #arg_names.into();)* - self.instructions.push(#name::::#variant_name(#(#arg_names),*)); - self - } - } - }, - Fields::Named(fields) => { - let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect(); - let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect(); - quote! { - pub fn #method_name(mut self, #(#arg_names: impl Into<#arg_types>),*) -> Self { - #(let #arg_names = #arg_names.into();)* - self.instructions.push(#name::::#variant_name { #(#arg_names),* }); - self - } - } - }, - }; - quote! { - #(#docs)* - #method - } - }); +fn generate_builder_raw_impl(name: &Ident, data_enum: &DataEnum) -> Result { + let methods = data_enum + .variants + .iter() + .map(|variant| convert_variant_to_method(name, variant, None)) + .collect::>>()?; let output = quote! { impl XcmBuilder { #(#methods)* @@ -140,7 +98,7 @@ fn generate_builder_raw_impl(name: &Ident, data_enum: &DataEnum) -> TokenStream2 } } }; - output + Ok(output) } fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result { @@ -165,11 +123,17 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result>>()?; @@ -178,57 +142,14 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result { - let arg_names: Vec<_> = fields - .unnamed - .iter() - .enumerate() - .map(|(index, _)| format_ident!("arg{}", index)) - .collect(); - let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect(); - quote! { - #(#docs)* - pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder { - let mut new_instructions = self.instructions; - #(let #arg_names = #arg_names.into();)* - new_instructions.push(#name::::#variant_name(#(#arg_names),*)); - XcmBuilder { - instructions: new_instructions, - state: core::marker::PhantomData, - } - } - } - }, - Fields::Named(fields) => { - let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect(); - let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect(); - quote! { - #(#docs)* - pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder { - let mut new_instructions = self.instructions; - #(let #arg_names = #arg_names.into();)* - new_instructions.push(#name::::#variant_name { #(#arg_names),* }); - XcmBuilder { - instructions: new_instructions, - state: core::marker::PhantomData, - } - } - } - }, - _ => - return Err(Error::new_spanned( - variant, - "Instructions that load the holding register should take operands", - )), - }; + let method = convert_variant_to_method( + name, + variant, + Some(quote! { XcmBuilder }), + )?; Ok(method) }) - .collect::, _>>()?; + .collect::>>()?; let first_impl = quote! { impl XcmBuilder { @@ -240,27 +161,12 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result = data_enum .variants .iter() - .filter(|variant| variant.ident == "ClearOrigin") + .filter(|variant| variant.ident == "ClearOrigin" || variant.ident == "SetHints") .map(|variant| { - let variant_name = &variant.ident; - let method_name_string = &variant_name.to_string().to_snake_case(); - let method_name = syn::Ident::new(method_name_string, variant_name.span()); - let docs = get_doc_comments(variant); - let method = match &variant.fields { - Fields::Unit => { - quote! { - #(#docs)* - pub fn #method_name(mut self) -> XcmBuilder { - self.instructions.push(#name::::#variant_name); - self - } - } - }, - _ => return Err(Error::new_spanned(variant, "ClearOrigin should have no fields")), - }; + let method = convert_variant_to_method(name, variant, None)?; Ok(method) }) - .collect::, _>>()?; + .collect::>>()?; // Then we require fees to be paid let pay_fees_variants = data_enum @@ -295,36 +201,12 @@ fn generate_builder_impl(name: &Ident, data_enum: &DataEnum) -> Result { - let arg_names: Vec<_> = - fields.named.iter().map(|field| &field.ident).collect(); - let arg_types: Vec<_> = - fields.named.iter().map(|field| &field.ty).collect(); - quote! { - #(#docs)* - pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder { - let mut new_instructions = self.instructions; - #(let #arg_names = #arg_names.into();)* - new_instructions.push(#name::::#variant_name { #(#arg_names),* }); - XcmBuilder { - instructions: new_instructions, - state: core::marker::PhantomData, - } - } - } - }, - _ => - return Err(Error::new_spanned( - variant, - "Both BuyExecution and PayFees have named fields", - )), - }; - Ok(fields) + let method = convert_variant_to_method( + name, + variant, + Some(quote! { XcmBuilder }), + )?; + Ok(method) }) .collect::>>()?; @@ -349,35 +231,156 @@ fn generate_builder_unpaid_impl(name: &Ident, data_enum: &DataEnum) -> Result fields, - _ => - return Err(Error::new_spanned( - unpaid_execution_variant, - "UnpaidExecution should have named fields", - )), - }; - let arg_names: Vec<_> = fields.named.iter().map(|field| &field.ident).collect(); - let arg_types: Vec<_> = fields.named.iter().map(|field| &field.ty).collect(); + let method = convert_variant_to_method( + name, + &unpaid_execution_variant, + Some(quote! { XcmBuilder }), + )?; Ok(quote! { impl XcmBuilder { - #(#docs)* - pub fn #unpaid_execution_method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> XcmBuilder { - let mut new_instructions = self.instructions; - #(let #arg_names = #arg_names.into();)* - new_instructions.push(#name::::#unpaid_execution_ident { #(#arg_names),* }); - XcmBuilder { - instructions: new_instructions, - state: core::marker::PhantomData, + #method + } + }) +} + +// Have to call with `XcmBuilder` in allowed_after_load_holding_methods. +fn convert_variant_to_method( + name: &Ident, + variant: &Variant, + maybe_return_type: Option, +) -> Result { + let variant_name = &variant.ident; + let method_name_string = &variant_name.to_string().to_snake_case(); + let method_name = syn::Ident::new(method_name_string, variant_name.span()); + let docs = get_doc_comments(variant); + let method = match &variant.fields { + Fields::Unit => + if let Some(return_type) = maybe_return_type { + quote! { + pub fn #method_name(self) -> #return_type { + let mut new_instructions = self.instructions; + new_instructions.push(#name::::#variant_name); + XcmBuilder { + instructions: new_instructions, + state: core::marker::PhantomData, + } + } + } + } else { + quote! { + pub fn #method_name(mut self) -> Self { + self.instructions.push(#name::::#variant_name); + self + } + } + }, + Fields::Unnamed(fields) => { + let arg_names: Vec<_> = fields + .unnamed + .iter() + .enumerate() + .map(|(index, _)| format_ident!("arg{}", index)) + .collect(); + let arg_types: Vec<_> = fields.unnamed.iter().map(|field| &field.ty).collect(); + if let Some(return_type) = maybe_return_type { + quote! { + pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),*) -> #return_type { + let mut new_instructions = self.instructions; + #(let #arg_names = #arg_names.into();)* + new_instructions.push(#name::::#variant_name(#(#arg_names),*)); + XcmBuilder { + instructions: new_instructions, + state: core::marker::PhantomData, + } + } + } + } else { + quote! { + pub fn #method_name(mut self, #(#arg_names: impl Into<#arg_types>),*) -> Self { + #(let #arg_names = #arg_names.into();)* + self.instructions.push(#name::::#variant_name(#(#arg_names),*)); + self + } } } - } + }, + Fields::Named(fields) => { + let normal_fields: Vec<_> = fields + .named + .iter() + .filter(|field| { + if let Type::Path(TypePath { path, .. }) = &field.ty { + for segment in &path.segments { + if segment.ident == format_ident!("BoundedVec") { + return false; + } + } + true + } else { + true + } + }) + .collect(); + let bounded_fields: Vec<_> = fields + .named + .iter() + .filter(|field| { + if let Type::Path(TypePath { path, .. }) = &field.ty { + for segment in &path.segments { + if segment.ident == format_ident!("BoundedVec") { + return true; + } + } + false + } else { + false + } + }) + .collect(); + let arg_names: Vec<_> = normal_fields.iter().map(|field| &field.ident).collect(); + let arg_types: Vec<_> = normal_fields.iter().map(|field| &field.ty).collect(); + let bounded_names: Vec<_> = bounded_fields.iter().map(|field| &field.ident).collect(); + let bounded_types = bounded_fields + .iter() + .map(|field| extract_generic_argument(&field.ty, 0, "BoundedVec's inner type")) + .collect::>>()?; + let bounded_sizes = bounded_fields + .iter() + .map(|field| extract_generic_argument(&field.ty, 1, "BoundedVec's size")) + .collect::>>()?; + let comma_in_the_middle = if normal_fields.is_empty() { + quote! {} + } else { + quote! {,} + }; + if let Some(return_type) = maybe_return_type { + quote! { + pub fn #method_name(self, #(#arg_names: impl Into<#arg_types>),* #comma_in_the_middle #(#bounded_names: Vec<#bounded_types>),*) -> #return_type { + let mut new_instructions = self.instructions; + #(let #arg_names = #arg_names.into();)* + #(let #bounded_names = BoundedVec::<#bounded_types, #bounded_sizes>::truncate_from(#bounded_names);)* + new_instructions.push(#name::::#variant_name { #(#arg_names),* #comma_in_the_middle #(#bounded_names),* }); + XcmBuilder { + instructions: new_instructions, + state: core::marker::PhantomData, + } + } + } + } else { + quote! { + pub fn #method_name(mut self, #(#arg_names: impl Into<#arg_types>),* #comma_in_the_middle #(#bounded_names: Vec<#bounded_types>),*) -> Self { + #(let #arg_names = #arg_names.into();)* + #(let #bounded_names = BoundedVec::<#bounded_types, #bounded_sizes>::truncate_from(#bounded_names);)* + self.instructions.push(#name::::#variant_name { #(#arg_names),* #comma_in_the_middle #(#bounded_names),* }); + self + } + } + } + }, + }; + Ok(quote! { + #(#docs)* + #method }) } @@ -395,3 +398,40 @@ fn get_doc_comments(variant: &Variant) -> Vec { .map(|doc| syn::parse_str::(&format!("/// {}", doc)).unwrap()) .collect() } + +fn extract_generic_argument<'a>( + field_ty: &'a Type, + index: usize, + expected_msg: &str, +) -> Result<&'a Ident> { + if let Type::Path(type_path) = field_ty { + if let Some(segment) = type_path.path.segments.last() { + if let PathArguments::AngleBracketed(angle_brackets) = &segment.arguments { + let args: Vec<_> = angle_brackets.args.iter().collect(); + if let Some(GenericArgument::Type(Type::Path(TypePath { path, .. }))) = + args.get(index) + { + return path.get_ident().ok_or_else(|| { + Error::new_spanned( + path, + format!("Expected an identifier for {}", expected_msg), + ) + }); + } + return Err(Error::new_spanned( + angle_brackets, + format!("Expected a generic argument at index {} for {}", index, expected_msg), + )); + } + return Err(Error::new_spanned( + &segment.arguments, + format!("Expected angle-bracketed arguments for {}", expected_msg), + )); + } + return Err(Error::new_spanned( + &type_path.path, + format!("Expected at least one path segment for {}", expected_msg), + )); + } + Err(Error::new_spanned(field_ty, format!("Expected a path type for {}", expected_msg))) +} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.rs b/polkadot/xcm/procedural/src/enum_variants.rs similarity index 51% rename from polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.rs rename to polkadot/xcm/procedural/src/enum_variants.rs index 070f0be6bacc..f9f2d9e15675 100644 --- a/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.rs +++ b/polkadot/xcm/procedural/src/enum_variants.rs @@ -14,19 +14,25 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Test error when an instruction that loads the holding register doesn't take operands. - -use xcm_procedural::Builder; - -struct Xcm(pub Vec>); - -#[derive(Builder)] -enum Instruction { - #[builder(loads_holding)] - WithdrawAsset, - BuyExecution { fees: u128 }, - UnpaidExecution { weight_limit: (u32, u32) }, - Transact { call: Call }, +//! Simple derive macro for getting the number of variants in an enum. + +use proc_macro2::TokenStream as TokenStream2; +use quote::{format_ident, quote}; +use syn::{Data, DeriveInput, Error, Result}; + +pub fn derive(input: DeriveInput) -> Result { + let data_enum = match &input.data { + Data::Enum(data_enum) => data_enum, + _ => return Err(Error::new_spanned(&input, "Expected an enum.")), + }; + let ident = format_ident!("{}NumVariants", input.ident); + let number_of_variants: usize = data_enum.variants.iter().count(); + Ok(quote! { + pub struct #ident; + impl ::frame_support::traits::Get for #ident { + fn get() -> u32 { + #number_of_variants as u32 + } + } + }) } - -fn main() {} diff --git a/polkadot/xcm/procedural/src/lib.rs b/polkadot/xcm/procedural/src/lib.rs index 9971fdceb69a..0dd270286f69 100644 --- a/polkadot/xcm/procedural/src/lib.rs +++ b/polkadot/xcm/procedural/src/lib.rs @@ -20,6 +20,7 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; mod builder_pattern; +mod enum_variants; mod v3; mod v4; mod v5; @@ -86,3 +87,11 @@ pub fn derive_builder(input: TokenStream) -> TokenStream { .unwrap_or_else(syn::Error::into_compile_error) .into() } + +#[proc_macro_derive(NumVariants)] +pub fn derive_num_variants(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + enum_variants::derive(input) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} diff --git a/polkadot/xcm/procedural/tests/builder_pattern.rs b/polkadot/xcm/procedural/tests/builder_pattern.rs index 4202309bf3f7..3915621916d4 100644 --- a/polkadot/xcm/procedural/tests/builder_pattern.rs +++ b/polkadot/xcm/procedural/tests/builder_pattern.rs @@ -17,6 +17,7 @@ //! Test the methods generated by the Builder derive macro. //! Tests directly on the actual Xcm struct and Instruction enum. +use frame_support::BoundedVec; use xcm::latest::prelude::*; #[test] @@ -100,3 +101,61 @@ fn default_builder_allows_clear_origin_before_buy_execution() { ]) ); } + +#[test] +fn bounded_vecs_use_vecs_and_truncate_them() { + let claimer = Location::parent(); + // We can use a vec instead of a bounded vec for specifying hints. + let xcm: Xcm<()> = Xcm::builder_unsafe() + .set_hints(vec![AssetClaimer { location: claimer.clone() }]) + .build(); + assert_eq!( + xcm, + Xcm(vec![SetHints { + hints: BoundedVec::::truncate_from(vec![AssetClaimer { + location: Location::parent() + },]), + },]) + ); + + // If we include more than the limit they'll get truncated. + let xcm: Xcm<()> = Xcm::builder_unsafe() + .set_hints(vec![ + AssetClaimer { location: claimer.clone() }, + AssetClaimer { location: Location::here() }, + ]) + .build(); + assert_eq!( + xcm, + Xcm(vec![SetHints { + hints: BoundedVec::::truncate_from(vec![AssetClaimer { + location: Location::parent() + },]), + },]) + ); + + let xcm: Xcm<()> = Xcm::builder() + .withdraw_asset((Here, 100u128)) + .set_hints(vec![AssetClaimer { location: claimer }]) + .clear_origin() + .pay_fees((Here, 10u128)) + .deposit_asset(All, [0u8; 32]) + .build(); + assert_eq!( + xcm, + Xcm(vec![ + WithdrawAsset(Asset { id: AssetId(Location::here()), fun: Fungible(100) }.into()), + SetHints { + hints: BoundedVec::::truncate_from(vec![AssetClaimer { + location: Location::parent() + }]) + }, + ClearOrigin, + PayFees { asset: Asset { id: AssetId(Location::here()), fun: Fungible(10) } }, + DepositAsset { + assets: All.into(), + beneficiary: AccountId32 { id: [0u8; 32], network: None }.into() + }, + ]) + ); +} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.rs b/polkadot/xcm/procedural/tests/enum_variants.rs similarity index 70% rename from polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.rs rename to polkadot/xcm/procedural/tests/enum_variants.rs index bb98d603fd91..4a5362c1579a 100644 --- a/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.rs +++ b/polkadot/xcm/procedural/tests/enum_variants.rs @@ -14,17 +14,20 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Test error when the `BuyExecution` instruction doesn't take named fields. +//! Test the struct generated by the `NumVariants` derive macro. -use xcm_procedural::Builder; +use frame_support::traits::Get; +use xcm_procedural::NumVariants; -struct Xcm(pub Vec>); - -#[derive(Builder)] -enum Instruction { - BuyExecution { fees: u128 }, - UnpaidExecution(u32, u32), - Transact { call: Call }, +#[allow(dead_code)] +#[derive(NumVariants)] +enum SomeEnum { + Variant1, + Variant2, + Variant3, } -fn main() {} +#[test] +fn num_variants_works() { + assert_eq!(SomeEnumNumVariants::get(), 3); +} diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.stderr deleted file mode 100644 index 0358a35ad3dd..000000000000 --- a/polkadot/xcm/procedural/tests/ui/builder_pattern/loads_holding_no_operands.stderr +++ /dev/null @@ -1,6 +0,0 @@ -error: Instructions that load the holding register should take operands - --> tests/ui/builder_pattern/loads_holding_no_operands.rs:25:5 - | -25 | / #[builder(loads_holding)] -26 | | WithdrawAsset, - | |_________________^ diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.stderr new file mode 100644 index 000000000000..c4d711e0d455 --- /dev/null +++ b/polkadot/xcm/procedural/tests/ui/builder_pattern/unexpected_attribute.stderr @@ -0,0 +1,5 @@ +error: Expected `builder(loads_holding)` or `builder(pays_fees)` + --> tests/ui/builder_pattern/unexpected_attribute.rs:25:5 + | +25 | #[builder(funds_holding)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.stderr b/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.stderr deleted file mode 100644 index 0a3c0a40a33b..000000000000 --- a/polkadot/xcm/procedural/tests/ui/builder_pattern/unpaid_execution_named_fields.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: UnpaidExecution should have named fields - --> tests/ui/builder_pattern/unpaid_execution_named_fields.rs:26:5 - | -26 | UnpaidExecution(u32, u32), - | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/polkadot/xcm/src/v4/mod.rs b/polkadot/xcm/src/v4/mod.rs index 3ae94b6ede88..bbf5ca049228 100644 --- a/polkadot/xcm/src/v4/mod.rs +++ b/polkadot/xcm/src/v4/mod.rs @@ -1435,7 +1435,7 @@ impl TryFrom> for Instructi }, InitiateTransfer { .. } | PayFees { .. } | - SetAssetClaimer { .. } | + SetHints { .. } | ExecuteWithOrigin { .. } => { log::debug!(target: "xcm::versions::v5tov4", "`{new_instruction:?}` not supported by v4"); return Err(()); diff --git a/polkadot/xcm/src/v5/mod.rs b/polkadot/xcm/src/v5/mod.rs index 193b82b6c223..21845d07529e 100644 --- a/polkadot/xcm/src/v5/mod.rs +++ b/polkadot/xcm/src/v5/mod.rs @@ -196,6 +196,8 @@ pub mod prelude { AssetInstance::{self, *}, Assets, BodyId, BodyPart, Error as XcmError, ExecuteXcm, Fungibility::{self, *}, + Hint::{self, *}, + HintNumVariants, Instruction::*, InteriorLocation, Junction::{self, *}, @@ -747,15 +749,6 @@ pub enum Instruction { /// Errors: None. ClearError, - /// Set asset claimer for all the trapped assets during the execution. - /// - /// - `location`: The claimer of any assets potentially trapped during the execution of current - /// XCM. It can be an arbitrary location, not necessarily the caller or origin. - /// - /// Kind: *Command* - /// - /// Errors: None. - SetAssetClaimer { location: Location }, /// Create some assets which are being held on behalf of the origin. /// /// - `assets`: The assets which are to be claimed. This must match exactly with the assets @@ -1136,6 +1129,25 @@ pub enum Instruction { /// Errors: /// - `BadOrigin` ExecuteWithOrigin { descendant_origin: Option, xcm: Xcm }, + + /// Set hints for XCM execution. + /// + /// These hints change the behaviour of the XCM program they are present in. + /// + /// Parameters: + /// + /// - `hints`: A bounded vector of `ExecutionHint`, specifying the different hints that will + /// be activated. + SetHints { hints: BoundedVec }, +} + +#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Eq, Clone, xcm_procedural::NumVariants)] +pub enum Hint { + /// Set asset claimer for all the trapped assets during the execution. + /// + /// - `location`: The claimer of any assets potentially trapped during the execution of current + /// XCM. It can be an arbitrary location, not necessarily the caller or origin. + AssetClaimer { location: Location }, } impl Xcm { @@ -1184,7 +1196,7 @@ impl Instruction { SetErrorHandler(xcm) => SetErrorHandler(xcm.into()), SetAppendix(xcm) => SetAppendix(xcm.into()), ClearError => ClearError, - SetAssetClaimer { location } => SetAssetClaimer { location }, + SetHints { hints } => SetHints { hints }, ClaimAsset { assets, ticket } => ClaimAsset { assets, ticket }, Trap(code) => Trap(code), SubscribeVersion { query_id, max_response_weight } => @@ -1259,7 +1271,7 @@ impl> GetWeight for Instruction { SetErrorHandler(xcm) => W::set_error_handler(xcm), SetAppendix(xcm) => W::set_appendix(xcm), ClearError => W::clear_error(), - SetAssetClaimer { location } => W::set_asset_claimer(location), + SetHints { hints } => W::set_hints(hints), ClaimAsset { assets, ticket } => W::claim_asset(assets, ticket), Trap(code) => W::trap(code), SubscribeVersion { query_id, max_response_weight } => diff --git a/polkadot/xcm/xcm-builder/src/barriers.rs b/polkadot/xcm/xcm-builder/src/barriers.rs index 56a8493ef0ab..adba9a3ef79f 100644 --- a/polkadot/xcm/xcm-builder/src/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/barriers.rs @@ -95,7 +95,8 @@ impl> ShouldExecute for AllowTopLevelPaidExecutionFrom })? .skip_inst_while(|inst| { matches!(inst, ClearOrigin | AliasOrigin(..)) || - matches!(inst, DescendOrigin(child) if child != &Here) + matches!(inst, DescendOrigin(child) if child != &Here) || + matches!(inst, SetHints { .. }) })? .match_next_inst(|inst| match inst { BuyExecution { weight_limit: Limited(ref mut weight), .. } diff --git a/polkadot/xcm/xcm-builder/src/tests/barriers.rs b/polkadot/xcm/xcm-builder/src/tests/barriers.rs index cd2b6db66efc..d8805274d3a5 100644 --- a/polkadot/xcm/xcm-builder/src/tests/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/tests/barriers.rs @@ -333,6 +333,26 @@ fn allow_paid_should_deprivilege_origin() { assert_eq!(r, Err(ProcessMessageError::Overweight(Weight::from_parts(30, 30)))); } +#[test] +fn allow_paid_should_allow_hints() { + AllowPaidFrom::set(vec![Parent.into()]); + let fees = (Parent, 1).into(); + + let mut paying_message_with_hints = Xcm::<()>(vec![ + ReserveAssetDeposited((Parent, 100).into()), + SetHints { hints: vec![AssetClaimer { location: Location::here() }].try_into().unwrap() }, + BuyExecution { fees, weight_limit: Limited(Weight::from_parts(30, 30)) }, + DepositAsset { assets: AllCounted(1).into(), beneficiary: Here.into() }, + ]); + let r = AllowTopLevelPaidExecutionFrom::>::should_execute( + &Parent.into(), + paying_message_with_hints.inner_mut(), + Weight::from_parts(30, 30), + &mut props(Weight::zero()), + ); + assert_eq!(r, Ok(())); +} + #[test] fn suspension_should_work() { TestSuspender::set_suspended(true); diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 11fd4e04761f..64a32f488420 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -1371,8 +1371,14 @@ impl XcmExecutor { self.error = None; Ok(()) }, - SetAssetClaimer { location } => { - self.asset_claimer = Some(location); + SetHints { hints } => { + for hint in hints.into_iter() { + match hint { + AssetClaimer { location } => { + self.asset_claimer = Some(location) + }, + } + } Ok(()) }, ClaimAsset { assets, ticket } => { diff --git a/polkadot/xcm/xcm-executor/src/tests/set_asset_claimer.rs b/polkadot/xcm/xcm-executor/src/tests/set_asset_claimer.rs index bc504b8db2a2..cc97e2b3a16e 100644 --- a/polkadot/xcm/xcm-executor/src/tests/set_asset_claimer.rs +++ b/polkadot/xcm/xcm-executor/src/tests/set_asset_claimer.rs @@ -38,7 +38,7 @@ fn set_asset_claimer() { // if withdrawing fails we're not missing any corner case. .withdraw_asset((Here, 100u128)) .clear_origin() - .set_asset_claimer(bob.clone()) + .set_hints(vec![AssetClaimer { location: bob.clone() }]) .pay_fees((Here, 10u128)) // 10% destined for fees, not more. .build(); @@ -93,7 +93,7 @@ fn trap_then_set_asset_claimer() { .withdraw_asset((Here, 100u128)) .clear_origin() .trap(0u64) - .set_asset_claimer(bob) + .set_hints(vec![AssetClaimer { location: bob }]) .pay_fees((Here, 10u128)) // 10% destined for fees, not more. .build(); @@ -121,7 +121,7 @@ fn set_asset_claimer_then_trap() { // if withdrawing fails we're not missing any corner case. .withdraw_asset((Here, 100u128)) .clear_origin() - .set_asset_claimer(bob.clone()) + .set_hints(vec![AssetClaimer { location: bob.clone() }]) .trap(0u64) .pay_fees((Here, 10u128)) // 10% destined for fees, not more. .build(); diff --git a/prdoc/pr_6566.prdoc b/prdoc/pr_6566.prdoc new file mode 100644 index 000000000000..bbd48b799538 --- /dev/null +++ b/prdoc/pr_6566.prdoc @@ -0,0 +1,45 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: XCMv5 - SetHints instruction + +doc: + - audience: Runtime Dev + description: | + Implementation of fellowship RFC 107. + The new SetHints instruction is a repackaging of SetAssetClaimer that also allows future + "hints" which alter the default behaviour of the executor. + The AllowTopLevelPaidExecutionFrom barrier allows this instruction between WithdrawAsset and + BuyExecution/PayFees to configure things before the actual meat of the program. + +crates: + - name: asset-hub-rococo-runtime + bump: major + - name: asset-hub-westend-runtime + bump: major + - name: bridge-hub-rococo-runtime + bump: major + - name: bridge-hub-westend-runtime + bump: major + - name: coretime-rococo-runtime + bump: major + - name: coretime-westend-runtime + bump: major + - name: people-rococo-runtime + bump: major + - name: people-westend-runtime + bump: major + - name: rococo-runtime + bump: major + - name: westend-runtime + bump: major + - name: pallet-xcm-benchmarks + bump: major + - name: xcm-procedural + bump: minor + - name: staging-xcm + bump: major + - name: staging-xcm-builder + bump: major + - name: staging-xcm-executor + bump: major From 2484821a7f89d369a99fb3938c76f574f69696ba Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 9 Dec 2024 12:44:25 +0200 Subject: [PATCH 121/340] penpal test --- Cargo.lock | 1 + .../primitives/router/src/inbound/v2.rs | 4 +- .../bridge-hub-rococo/src/tests/snowbridge.rs | 5 +- .../bridges/bridge-hub-westend/Cargo.toml | 1 + .../src/tests/snowbridge.rs | 117 ++++++ .../src/tests/snowbridge_v2.rs | 344 ++++++++++++++++++ .../bridge-hubs/bridge-hub-westend/src/lib.rs | 19 +- 7 files changed, 487 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdc690ad36b5..f482bea693a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2748,6 +2748,7 @@ dependencies = [ "pallet-xcm-bridge-hub 0.2.0", "parachains-common 7.0.0", "parity-scale-codec", + "penpal-emulated-chain", "rococo-westend-system-emulated-network", "scale-info", "snowbridge-core 0.2.0", diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index e0cc59f08769..0757869d3045 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -146,6 +146,8 @@ where let mut reserve_assets = vec![]; let mut withdraw_assets = vec![]; + let mut refund_surplus_to = origin_account_location; + if let Some(claimer) = message.claimer { // If the claimer can be decoded, add it to the message. If the claimer decoding fails, // do not add it to the message, because it will cause the xcm to fail. @@ -185,8 +187,6 @@ where instructions.push(WithdrawAsset(withdraw_assets.into())); } - let mut refund_surplus_to = origin_account_location; - // If the message origin is not the gateway proxy contract, set the origin to // the original sender on Ethereum. Important to be before the arbitrary XCM that is // appended to the message on the next line. diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index c72d5045ddc0..8331138f777b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -25,7 +25,10 @@ use snowbridge_pallet_inbound_queue_fixtures::{ }; use snowbridge_pallet_system; use snowbridge_router_primitives::inbound::{ - Command, Destination, EthereumLocationsConverterFor, MessageV1, VersionedMessage, + EthereumLocationsConverterFor +}; +use snowbridge_router_primitives::inbound::v1::{ + Command, Destination, MessageV1, VersionedMessage, }; use sp_core::H256; use sp_runtime::{DispatchError::Token, TokenError::FundsUnavailable}; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index fc3cbc835b04..ec518175fc61 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -44,6 +44,7 @@ rococo-westend-system-emulated-network = { workspace = true } testnet-parachains-constants = { features = ["rococo", "westend"], workspace = true, default-features = true } asset-hub-westend-runtime = { workspace = true } bridge-hub-westend-runtime = { workspace = true } +penpal-emulated-chain = { workspace = true } # Snowbridge snowbridge-core = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index 3055043dd79c..be9323775e84 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -28,6 +28,8 @@ use snowbridge_router_primitives::inbound::{ use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm_executor::traits::ConvertLocation; +use penpal_emulated_chain::PARA_ID_B; +use penpal_emulated_chain::penpal_runtime; const INITIAL_FUND: u128 = 5_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; @@ -441,6 +443,121 @@ fn transfer_relay_token() { }); } +/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is +/// still located on AssetHub. +#[test] +fn send_token_from_ethereum_to_penpal() { + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + // Fund PenPal receiver (covering ED) + PenpalB::fund_accounts(vec![(PenpalBReceiver::get(), INITIAL_FUND)]); + + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), + )], + )); + }); + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + // The Weth asset location, identified by the contract address on Ethereum + let weth_asset_location: Location = + (Parent, Parent, ethereum_network_v5, AccountKey20 { network: None, key: WETH }).into(); + + let origin_location = (Parent, Parent, ethereum_network_v5).into(); + + // Fund ethereum sovereign on AssetHub + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Create asset on the Penpal parachain. + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::force_create( + ::RuntimeOrigin::root(), + weth_asset_location.clone(), + asset_hub_sovereign.clone().into(), + false, + 1000, + )); + + assert!(::ForeignAssets::asset_exists(weth_asset_location.clone())); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + asset_hub_sovereign.into(), + false, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone().try_into().unwrap(), + )); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::ForeignAccountId32 { + para_id: PARA_ID_B, + id: PenpalBReceiver::get().into(), + fee: 100_000_000_000u128, + }, + amount: TOKEN_AMOUNT, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + // Check that the send token message was sent using xcm + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) =>{},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on PenPal + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + #[test] fn transfer_ah_token() { let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 66a34504a2cf..24779ae3da80 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -26,6 +26,8 @@ use snowbridge_router_primitives::inbound::{ }; use sp_core::{H160, H256}; use sp_runtime::MultiAddress; +use emulated_integration_tests_common::RESERVABLE_ASSET_ID; +use penpal_emulated_chain::PARA_ID_B; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; @@ -93,6 +95,7 @@ fn register_token_v2() { // Call to create the asset. Transact { origin_kind: OriginKind::Xcm, + fallback_max_weight: None, call: ( CreateAssetCall::get(), asset_id, @@ -351,6 +354,7 @@ fn register_and_send_multiple_tokens_v2() { // register new token Transact { origin_kind: OriginKind::Xcm, + fallback_max_weight: None, call: ( CreateAssetCall::get(), token_location.clone(), @@ -431,6 +435,283 @@ fn register_and_send_multiple_tokens_v2() { }); } +#[test] +fn send_token_to_penpal_v2() { + let relayer = BridgeHubWestendSender::get(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); + + let token: H160 = TOKEN_ID.into(); + let token_location = erc20_token_location(token); + + let beneficiary_acc_id: H256 = H256::random(); + let beneficiary_acc_bytes: [u8; 32] = beneficiary_acc_id.into(); + let beneficiary = + Location::new(0, AccountId32 { network: None, id: beneficiary_acc_id.into() }); + + let claimer_acc_id = H256::random(); + let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; + let claimer_bytes = claimer.encode(); + + // To pay fees on Penpal. + let weth_fee_penpal: xcm::prelude::Asset = (weth_location(), 3_000_000_000_000u128).into(); + + register_foreign_asset(weth_location()); + register_foreign_asset(token_location.clone()); + + // To satisfy ED + PenpalB::fund_accounts(vec![( + sp_runtime::AccountId32::from(beneficiary_acc_bytes), + 3_000_000_000_000, + )]); + + let penpal_location = BridgeHubWestend::sibling_location_of(PenpalB::para_id()); + let penpal_sovereign = BridgeHubWestend::sovereign_account_id_of(penpal_location); + PenpalB::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + // Register token on Penpal + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + token_location.clone().try_into().unwrap(), + penpal_sovereign.clone().into(), + true, + 1000, + )); + + assert!(::ForeignAssets::asset_exists( + token_location.clone().try_into().unwrap(), + )); + + // Register weth on Penpal + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_location().try_into().unwrap(), + penpal_sovereign.clone().into(), + true, + 1000, + )); + + assert!(::ForeignAssets::asset_exists( + weth_location().try_into().unwrap(), + )); + + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), + )], + )); + }); + + set_up_weth_and_dot_pool(weth_location()); + + set_up_weth_and_dot_pool_on_penpal(weth_location()); + + let token_transfer_value = 2_000_000_000_000u128; + + let assets = vec![ + // to pay fees + NativeTokenERC20 { token_id: WETH.into(), value: 5_000_000_000_000u128 }, + // the token being transferred + NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, + ]; + + let token_asset: xcm::prelude::Asset = (token_location.clone(), token_transfer_value).into(); + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![ + // Send message to Penpal + DepositReserveAsset { + // Send the token plus some weth for execution fees + assets: Definite(vec![weth_fee_penpal.clone(), token_asset].into()), + // Penpal + dest: Location::new(1, [Parachain(PARA_ID_B)]), + xcm: vec![ + // Pay fees on Penpal. + PayFees { asset: weth_fee_penpal }, + // Deposit assets to beneficiary. + DepositAsset { assets: Wild(AllOf { + id: AssetId(token_location.clone()), + fun: WildFungibility::Fungible, + }), + beneficiary: beneficiary.clone(), }, + SetTopic(H256::random().into()), + ] + .into(), + }, + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_000_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on PenpalB + assert_expected_events!( + PenpalB, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] + ); + + // Beneficiary received the token transfer value + assert_eq!( + ForeignAssets::balance(token_location, AccountId::from(beneficiary_acc_bytes)), + token_transfer_value + ); + }); +} + +/* +#[test] +fn send_foreign_erc20_token_back_to_polkadot() { + let claimer = AccountId32 { network: None, id: H256::random().into() }; + let claimer_bytes = claimer.encode(); + + let asset_id: Location = + [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())].into(); + + let asset_id_in_bh: Location = Location::new( + 1, + [ + Parachain(AssetHubWestend::para_id().into()), + PalletInstance(ASSETS_PALLET_ID), + GeneralIndex(RESERVABLE_ASSET_ID.into()), + ], + ); + + let asset_id_after_reanchored = Location::new( + 1, + [ + GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), + Parachain(AssetHubWestend::para_id().into()), + ], + ) + .appended_with(asset_id.clone().interior) + .unwrap(); + + // Register token + BridgeHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::EthereumSystem::register_token( + RuntimeOrigin::root(), + Box::new(VersionedLocation::from(asset_id_in_bh.clone())), + AssetMetadata { + name: "ah_asset".as_bytes().to_vec().try_into().unwrap(), + symbol: "ah_asset".as_bytes().to_vec().try_into().unwrap(), + decimals: 12, + }, + )); + }); + + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::<[u8; 32]>::convert_location(ðereum_destination) + .unwrap() + .into(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Mint some token into Snowbridge sovereign to mimic locked assets + AssetHubWestend::mint_asset( + ::RuntimeOrigin::signed(ethereum_sovereign), + RESERVABLE_ASSET_ID, + AssetHubWestendSender::get(), + TOKEN_AMOUNT, + ); + + let token_id = TokenIdOf::convert_location(&asset_id_after_reanchored).unwrap(); + let asset: Asset = (asset_id_after_reanchored, amount).into(); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + let instructions = vec![ + WithdrawAsset(asset.clone().into()), + DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, + SetTopic(message_id.into()), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_message_xcm = VersionedXcm::V5(xcm); + let origin = EthereumGatewayAddress::get(); + + let message = Message { + origin, + fee: 1_500_000_000_000u128, + assets, + xcm: versioned_message_xcm.encode(), + claimer: Some(claimer_bytes), + }; + + let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::Assets(pallet_assets::Event::Burned{..}) => {},] + ); + + let events = AssetHubWestend::events(); + + // Check that the native token burnt from some reserved account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Assets(pallet_assets::Event::Burned { owner, .. }) + if *owner == ethereum_sovereign.clone(), + )), + "token burnt from Ethereum sovereign account." + ); + + // Check that the token was minted to beneficiary + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Assets(pallet_assets::Event::Issued { owner, .. }) + if *owner == AssetHubWestendReceiver::get() + )), + "Token minted to beneficiary." + ); + }); +}*/ + #[test] fn invalid_xcm_traps_funds_on_ah() { let relayer = BridgeHubWestendSender::get(); @@ -644,3 +925,66 @@ pub(crate) fn set_up_weth_and_dot_pool(asset: v5::Location) { ); }); } + + +pub(crate) fn set_up_weth_and_dot_pool_on_penpal(asset: v5::Location) { + let wnd: v5::Location = v5::Parent.into(); + let penpal_location = BridgeHubWestend::sibling_location_of(PenpalB::para_id()); + let owner = PenpalBSender::get(); + let bh_sovereign = BridgeHubWestend::sovereign_account_id_of(penpal_location); + + PenpalB::fund_accounts(vec![(owner.clone(), 3_000_000_000_000)]); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let signed_owner = ::RuntimeOrigin::signed(owner.clone()); + let signed_bh_sovereign = + ::RuntimeOrigin::signed(bh_sovereign.clone()); + + assert_ok!(::ForeignAssets::mint( + signed_bh_sovereign.clone(), + asset.clone().into(), + bh_sovereign.clone().into(), + 3_500_000_000_000, + )); + + assert_ok!(::ForeignAssets::transfer( + signed_bh_sovereign.clone(), + asset.clone().into(), + owner.clone().into(), + 3_000_000_000_000, + )); + + assert_ok!(::AssetConversion::create_pool( + signed_owner.clone(), + Box::new(wnd.clone()), + Box::new(asset.clone()), + )); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {}, + ] + ); + + assert_ok!(::AssetConversion::add_liquidity( + signed_owner.clone(), + Box::new(wnd), + Box::new(asset), + 1_000_000_000_000, + 2_000_000_000_000, + 1, + 1, + owner.into() + )); + + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded {..}) => {}, + ] + ); + }); +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 44d6fb6d72f1..b653e3990137 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -50,7 +50,7 @@ use sp_runtime::{ transaction_validity::{TransactionSource, TransactionValidity}, ApplyExtrinsicResult, }; - +use frame_support::traits::Contains; use snowbridge_router_primitives::inbound::v2::Message; use sp_runtime::DispatchError; #[cfg(feature = "std")] @@ -267,6 +267,22 @@ parameter_types! { } // Configure FRAME pallets to include in runtime. +pub struct BaseFilter; +impl Contains for BaseFilter { + fn contains(call: &RuntimeCall) -> bool { + // Disallow these Snowbridge system calls. + if matches!( + call, + RuntimeCall::EthereumSystem(snowbridge_pallet_system::Call::create_agent { .. }) + ) || matches!( + call, + RuntimeCall::EthereumSystem(snowbridge_pallet_system::Call::create_channel { .. }) + ) { + return false + } + return true + } +} #[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] impl frame_system::Config for Runtime { @@ -299,6 +315,7 @@ impl frame_system::Config for Runtime { /// The action to take on a Runtime Upgrade type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; type MaxConsumers = frame_support::traits::ConstU32<16>; + type BaseCallFilter = BaseFilter; } impl pallet_timestamp::Config for Runtime { From 373c878045ae6f5fac5955c0e1bb00d998994588 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 9 Dec 2024 12:59:24 +0200 Subject: [PATCH 122/340] pna progress --- .../src/tests/snowbridge.rs | 231 +++++++++--------- .../src/tests/snowbridge_v2.rs | 29 ++- 2 files changed, 139 insertions(+), 121 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index be9323775e84..a55aa9f9c353 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -289,6 +289,122 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { }); } + +/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is +/// still located on AssetHub. +#[test] +fn send_token_from_ethereum_to_penpal() { + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + // Fund PenPal receiver (covering ED) + PenpalB::fund_accounts(vec![(PenpalBReceiver::get(), INITIAL_FUND)]); + + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), + )], + )); + }); + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + // The Weth asset location, identified by the contract address on Ethereum + let weth_asset_location: Location = + (Parent, Parent, ethereum_network_v5, AccountKey20 { network: None, key: WETH }).into(); + + let origin_location = (Parent, Parent, ethereum_network_v5).into(); + + // Fund ethereum sovereign on AssetHub + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Create asset on the Penpal parachain. + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::force_create( + ::RuntimeOrigin::root(), + weth_asset_location.clone(), + asset_hub_sovereign.clone().into(), + false, + 1000, + )); + + assert!(::ForeignAssets::asset_exists(weth_asset_location.clone())); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + asset_hub_sovereign.into(), + false, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone().try_into().unwrap(), + )); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::ForeignAccountId32 { + para_id: PARA_ID_B, + id: PenpalBReceiver::get().into(), + fee: 100_000_000_000u128, + }, + amount: TOKEN_AMOUNT, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + // Check that the send token message was sent using xcm + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) =>{},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on PenPal + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + #[test] fn transfer_relay_token() { let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( @@ -443,121 +559,6 @@ fn transfer_relay_token() { }); } -/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is -/// still located on AssetHub. -#[test] -fn send_token_from_ethereum_to_penpal() { - let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( - 1, - [Parachain(AssetHubWestend::para_id().into())], - )); - // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer - BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); - // Fund PenPal receiver (covering ED) - PenpalB::fund_accounts(vec![(PenpalBReceiver::get(), INITIAL_FUND)]); - - PenpalB::execute_with(|| { - assert_ok!(::System::set_storage( - ::RuntimeOrigin::root(), - vec![( - PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), - Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), - )], - )); - }); - - let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); - - // The Weth asset location, identified by the contract address on Ethereum - let weth_asset_location: Location = - (Parent, Parent, ethereum_network_v5, AccountKey20 { network: None, key: WETH }).into(); - - let origin_location = (Parent, Parent, ethereum_network_v5).into(); - - // Fund ethereum sovereign on AssetHub - let ethereum_sovereign: AccountId = - EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); - AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); - - // Create asset on the Penpal parachain. - PenpalB::execute_with(|| { - assert_ok!(::ForeignAssets::force_create( - ::RuntimeOrigin::root(), - weth_asset_location.clone(), - asset_hub_sovereign.clone().into(), - false, - 1000, - )); - - assert!(::ForeignAssets::asset_exists(weth_asset_location.clone())); - }); - - AssetHubWestend::execute_with(|| { - type RuntimeOrigin = ::RuntimeOrigin; - - assert_ok!(::ForeignAssets::force_create( - RuntimeOrigin::root(), - weth_asset_location.clone().try_into().unwrap(), - asset_hub_sovereign.into(), - false, - 1, - )); - - assert!(::ForeignAssets::asset_exists( - weth_asset_location.clone().try_into().unwrap(), - )); - }); - - BridgeHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - - let message = VersionedMessage::V1(MessageV1 { - chain_id: CHAIN_ID, - command: Command::SendToken { - token: WETH.into(), - destination: Destination::ForeignAccountId32 { - para_id: PARA_ID_B, - id: PenpalBReceiver::get().into(), - fee: 100_000_000_000u128, - }, - amount: TOKEN_AMOUNT, - fee: XCM_FEE, - }, - }); - let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); - let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); - - // Check that the send token message was sent using xcm - assert_expected_events!( - BridgeHubWestend, - vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) =>{},] - ); - }); - - AssetHubWestend::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - // Check that the assets were issued on AssetHub - assert_expected_events!( - AssetHubWestend, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, - RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, - ] - ); - }); - - PenpalB::execute_with(|| { - type RuntimeEvent = ::RuntimeEvent; - // Check that the assets were issued on PenPal - assert_expected_events!( - PenpalB, - vec![ - RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, - ] - ); - }); -} - #[test] fn transfer_ah_token() { let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of( diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 24779ae3da80..5ee51cda9295 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -28,6 +28,11 @@ use sp_core::{H160, H256}; use sp_runtime::MultiAddress; use emulated_integration_tests_common::RESERVABLE_ASSET_ID; use penpal_emulated_chain::PARA_ID_B; +use snowbridge_core::AssetMetadata; +use xcm_executor::traits::ConvertLocation; +use snowbridge_core::TokenIdOf; +use snowbridge_router_primitives::inbound::v2::Asset::ForeignTokenERC20; +const TOKEN_AMOUNT: u128 = 100_000_000_000; /// Calculates the XCM prologue fee for sending an XCM to AH. const INITIAL_FUND: u128 = 5_000_000_000_000; @@ -592,11 +597,16 @@ fn send_token_to_penpal_v2() { }); } -/* #[test] fn send_foreign_erc20_token_back_to_polkadot() { + let relayer = BridgeHubWestendSender::get(); + let relayer_location = + Location::new(0, AccountId32 { network: None, id: relayer.clone().into() }); + let claimer = AccountId32 { network: None, id: H256::random().into() }; let claimer_bytes = claimer.encode(); + let beneficiary = + Location::new(0, AccountId32 { network: None, id: AssetHubWestendReceiver::get().into() }); let asset_id: Location = [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())].into(); @@ -620,6 +630,8 @@ fn send_foreign_erc20_token_back_to_polkadot() { .appended_with(asset_id.clone().interior) .unwrap(); + let ethereum_destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); + // Register token BridgeHubWestend::execute_with(|| { type RuntimeOrigin = ::RuntimeOrigin; @@ -641,23 +653,28 @@ fn send_foreign_erc20_token_back_to_polkadot() { .into(); AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); - // Mint some token into Snowbridge sovereign to mimic locked assets AssetHubWestend::mint_asset( - ::RuntimeOrigin::signed(ethereum_sovereign), + ::RuntimeOrigin::signed(AssetHubWestendAssetOwner::get()), RESERVABLE_ASSET_ID, AssetHubWestendSender::get(), TOKEN_AMOUNT, ); let token_id = TokenIdOf::convert_location(&asset_id_after_reanchored).unwrap(); - let asset: Asset = (asset_id_after_reanchored, amount).into(); + let asset: Asset = (asset_id_after_reanchored, TOKEN_AMOUNT).into(); + + let assets = vec![ + // to pay fees + NativeTokenERC20 { token_id: WETH.into(), value: 2_000_000_000_000u128 }, + // the token being transferred + ForeignTokenERC20 { token_id: token_id.into(), value: TOKEN_AMOUNT }, + ]; BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; let instructions = vec![ WithdrawAsset(asset.clone().into()), DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - SetTopic(message_id.into()), ]; let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); @@ -710,7 +727,7 @@ fn send_foreign_erc20_token_back_to_polkadot() { "Token minted to beneficiary." ); }); -}*/ +} #[test] fn invalid_xcm_traps_funds_on_ah() { From b2e1e592008f2d8f89ff988c6d6975156aa03567 Mon Sep 17 00:00:00 2001 From: Egor_P Date: Mon, 9 Dec 2024 13:44:07 +0100 Subject: [PATCH 123/340] [CI/CD]Revert the token changes in backport flow (#6794) Set back the token for the cmd_bot in the backport flow so that it work again, till the new set up will be figured out with the sec team --- .github/workflows/command-backport.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/command-backport.yml b/.github/workflows/command-backport.yml index db006e9bd907..8a017a434525 100644 --- a/.github/workflows/command-backport.yml +++ b/.github/workflows/command-backport.yml @@ -16,7 +16,6 @@ jobs: backport: name: Backport pull request runs-on: ubuntu-latest - environment: release # The 'github.event.pull_request.merged' ensures that it got into master: if: > @@ -30,13 +29,12 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Generate content write token for the release automation - id: generate_write_token - uses: actions/create-github-app-token@v1 + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@v2.1.0 with: - app-id: ${{ vars.RELEASE_AUTOMATION_APP_ID }} - private-key: ${{ secrets.RELEASE_AUTOMATION_APP_PRIVATE_KEY }} - owner: paritytech + app_id: ${{ secrets.CMD_BOT_APP_ID }} + private_key: ${{ secrets.CMD_BOT_APP_KEY }} - name: Create backport pull requests uses: korthout/backport-action@v3 @@ -44,7 +42,7 @@ jobs: with: target_branches: stable2407 stable2409 stable2412 merge_commits: skip - github_token: ${{ steps.generate_write_token.outputs.token }} + github_token: ${{ steps.generate_token.outputs.token }} pull_description: | Backport #${pull_number} into `${target_branch}` from ${pull_author}. From 81b979ae2a23842e8ed5a879e73b1deae577dc47 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:49:35 +0000 Subject: [PATCH 124/340] Mak cmd swap omnibench (#6769) - change bench to default to old CLI - fix profile to production --------- Co-authored-by: GitHub Action Co-authored-by: command-bot <> --- .github/scripts/cmd/cmd.py | 195 +++++++++++++++++- .github/scripts/cmd/test_cmd.py | 38 ++-- .github/workflows/cmd.yml | 24 ++- .github/workflows/runtimes-matrix.json | 33 ++- .../westend/src/weights/pallet_balances.rs | 68 +++--- 5 files changed, 289 insertions(+), 69 deletions(-) diff --git a/.github/scripts/cmd/cmd.py b/.github/scripts/cmd/cmd.py index 9da05cac17b9..2c017b7d0c3e 100755 --- a/.github/scripts/cmd/cmd.py +++ b/.github/scripts/cmd/cmd.py @@ -58,7 +58,7 @@ def setup_logging(): %(prog)s --runtime westend rococo --pallet pallet_balances pallet_multisig --quiet --clean ''' -parser_bench = subparsers.add_parser('bench', help='Runs benchmarks', epilog=bench_example, formatter_class=argparse.RawDescriptionHelpFormatter) +parser_bench = subparsers.add_parser('bench', help='Runs benchmarks (old CLI)', epilog=bench_example, formatter_class=argparse.RawDescriptionHelpFormatter) for arg, config in common_args.items(): parser_bench.add_argument(arg, **config) @@ -67,6 +67,35 @@ def setup_logging(): parser_bench.add_argument('--pallet', help='Pallet(s) space separated', nargs='*', default=[]) parser_bench.add_argument('--fail-fast', help='Fail fast on first failed benchmark', action='store_true') + +""" +BENCH OMNI +""" + +bench_example = '''**Examples**: + Runs all benchmarks + %(prog)s + + Runs benchmarks for pallet_balances and pallet_multisig for all runtimes which have these pallets. **--quiet** makes it to output nothing to PR but reactions + %(prog)s --pallet pallet_balances pallet_xcm_benchmarks::generic --quiet + + Runs bench for all pallets for westend runtime and fails fast on first failed benchmark + %(prog)s --runtime westend --fail-fast + + Does not output anything and cleans up the previous bot's & author command triggering comments in PR + %(prog)s --runtime westend rococo --pallet pallet_balances pallet_multisig --quiet --clean +''' + +parser_bench_old = subparsers.add_parser('bench-omni', help='Runs benchmarks (frame omni bencher)', epilog=bench_example, formatter_class=argparse.RawDescriptionHelpFormatter) + +for arg, config in common_args.items(): + parser_bench_old.add_argument(arg, **config) + +parser_bench_old.add_argument('--runtime', help='Runtime(s) space separated', choices=runtimeNames, nargs='*', default=runtimeNames) +parser_bench_old.add_argument('--pallet', help='Pallet(s) space separated', nargs='*', default=[]) +parser_bench_old.add_argument('--fail-fast', help='Fail fast on first failed benchmark', action='store_true') + + """ FMT """ @@ -98,12 +127,12 @@ def main(): print(f'args: {args}') - if args.command == 'bench': + if args.command == 'bench-omni': runtime_pallets_map = {} failed_benchmarks = {} successful_benchmarks = {} - profile = "release" + profile = "production" print(f'Provided runtimes: {args.runtime}') # convert to mapped dict @@ -113,11 +142,22 @@ def main(): # loop over remaining runtimes to collect available pallets for runtime in runtimesMatrix.values(): - os.system(f"forklift cargo build -p {runtime['package']} --profile {profile} --features={runtime['bench_features']}") + build_command = f"forklift cargo build -p {runtime['package']} --profile {profile} --features={runtime['bench_features']}" + print(f'-- building "{runtime["name"]}" with `{build_command}`') + os.system(build_command) print(f'-- listing pallets for benchmark for {runtime["name"]}') wasm_file = f"target/{profile}/wbuild/{runtime['package']}/{runtime['package'].replace('-', '_')}.wasm" - output = os.popen( - f"frame-omni-bencher v1 benchmark pallet --no-csv-header --no-storage-info --no-min-squares --no-median-slopes --all --list --runtime={wasm_file} {runtime['bench_flags']}").read() + list_command = f"frame-omni-bencher v1 benchmark pallet " \ + f"--no-csv-header " \ + f"--no-storage-info " \ + f"--no-min-squares " \ + f"--no-median-slopes " \ + f"--all " \ + f"--list " \ + f"--runtime={wasm_file} " \ + f"{runtime['bench_flags']}" + print(f'-- running: {list_command}') + output = os.popen(list_command).read() raw_pallets = output.strip().split('\n') all_pallets = set() @@ -230,6 +270,149 @@ def main(): print_and_log('✅ Successful benchmarks of runtimes/pallets:') for runtime, pallets in successful_benchmarks.items(): print_and_log(f'-- {runtime}: {pallets}') + + if args.command == 'bench': + runtime_pallets_map = {} + failed_benchmarks = {} + successful_benchmarks = {} + + profile = "production" + + print(f'Provided runtimes: {args.runtime}') + # convert to mapped dict + runtimesMatrix = list(filter(lambda x: x['name'] in args.runtime, runtimesMatrix)) + runtimesMatrix = {x['name']: x for x in runtimesMatrix} + print(f'Filtered out runtimes: {runtimesMatrix}') + + # loop over remaining runtimes to collect available pallets + for runtime in runtimesMatrix.values(): + build_command = f"forklift cargo build -p {runtime['old_package']} --profile {profile} --features={runtime['bench_features']} --locked" + print(f'-- building {runtime["name"]} with `{build_command}`') + os.system(build_command) + + chain = runtime['name'] if runtime['name'] == 'dev' else f"{runtime['name']}-dev" + + machine_test = f"target/{profile}/{runtime['old_bin']} benchmark machine --chain={chain}" + print(f"Running machine test for `{machine_test}`") + os.system(machine_test) + + print(f'-- listing pallets for benchmark for {chain}') + list_command = f"target/{profile}/{runtime['old_bin']} " \ + f"benchmark pallet " \ + f"--no-csv-header " \ + f"--no-storage-info " \ + f"--no-min-squares " \ + f"--no-median-slopes " \ + f"--all " \ + f"--list " \ + f"--chain={chain}" + print(f'-- running: {list_command}') + output = os.popen(list_command).read() + raw_pallets = output.strip().split('\n') + + all_pallets = set() + for pallet in raw_pallets: + if pallet: + all_pallets.add(pallet.split(',')[0].strip()) + + pallets = list(all_pallets) + print(f'Pallets in {runtime["name"]}: {pallets}') + runtime_pallets_map[runtime['name']] = pallets + + print(f'\n') + + # filter out only the specified pallets from collected runtimes/pallets + if args.pallet: + print(f'Pallets: {args.pallet}') + new_pallets_map = {} + # keep only specified pallets if they exist in the runtime + for runtime in runtime_pallets_map: + if set(args.pallet).issubset(set(runtime_pallets_map[runtime])): + new_pallets_map[runtime] = args.pallet + + runtime_pallets_map = new_pallets_map + + print(f'Filtered out runtimes & pallets: {runtime_pallets_map}\n') + + if not runtime_pallets_map: + if args.pallet and not args.runtime: + print(f"No pallets {args.pallet} found in any runtime") + elif args.runtime and not args.pallet: + print(f"{args.runtime} runtime does not have any pallets") + elif args.runtime and args.pallet: + print(f"No pallets {args.pallet} found in {args.runtime}") + else: + print('No runtimes found') + sys.exit(1) + + for runtime in runtime_pallets_map: + for pallet in runtime_pallets_map[runtime]: + config = runtimesMatrix[runtime] + header_path = os.path.abspath(config['header']) + template = None + + chain = config['name'] if runtime == 'dev' else f"{config['name']}-dev" + + print(f'-- config: {config}') + if runtime == 'dev': + # to support sub-modules (https://github.com/paritytech/command-bot/issues/275) + search_manifest_path = f"cargo metadata --locked --format-version 1 --no-deps | jq -r '.packages[] | select(.name == \"{pallet.replace('_', '-')}\") | .manifest_path'" + print(f'-- running: {search_manifest_path}') + manifest_path = os.popen(search_manifest_path).read() + if not manifest_path: + print(f'-- pallet {pallet} not found in dev runtime') + if args.fail_fast: + print_and_log(f'Error: {pallet} not found in dev runtime') + sys.exit(1) + package_dir = os.path.dirname(manifest_path) + print(f'-- package_dir: {package_dir}') + print(f'-- manifest_path: {manifest_path}') + output_path = os.path.join(package_dir, "src", "weights.rs") + template = config['template'] + else: + default_path = f"./{config['path']}/src/weights" + xcm_path = f"./{config['path']}/src/weights/xcm" + output_path = default_path + if pallet.startswith("pallet_xcm_benchmarks"): + template = config['template'] + output_path = xcm_path + + print(f'-- benchmarking {pallet} in {runtime} into {output_path}') + cmd = f"target/{profile}/{config['old_bin']} benchmark pallet " \ + f"--extrinsic=* " \ + f"--chain={chain} " \ + f"--pallet={pallet} " \ + f"--header={header_path} " \ + f"--output={output_path} " \ + f"--wasm-execution=compiled " \ + f"--steps=50 " \ + f"--repeat=20 " \ + f"--heap-pages=4096 " \ + f"{f'--template={template} ' if template else ''}" \ + f"--no-storage-info --no-min-squares --no-median-slopes " + print(f'-- Running: {cmd} \n') + status = os.system(cmd) + + if status != 0 and args.fail_fast: + print_and_log(f'❌ Failed to benchmark {pallet} in {runtime}') + sys.exit(1) + + # Otherwise collect failed benchmarks and print them at the end + # push failed pallets to failed_benchmarks + if status != 0: + failed_benchmarks[f'{runtime}'] = failed_benchmarks.get(f'{runtime}', []) + [pallet] + else: + successful_benchmarks[f'{runtime}'] = successful_benchmarks.get(f'{runtime}', []) + [pallet] + + if failed_benchmarks: + print_and_log('❌ Failed benchmarks of runtimes/pallets:') + for runtime, pallets in failed_benchmarks.items(): + print_and_log(f'-- {runtime}: {pallets}') + + if successful_benchmarks: + print_and_log('✅ Successful benchmarks of runtimes/pallets:') + for runtime, pallets in successful_benchmarks.items(): + print_and_log(f'-- {runtime}: {pallets}') elif args.command == 'fmt': command = f"cargo +nightly fmt" diff --git a/.github/scripts/cmd/test_cmd.py b/.github/scripts/cmd/test_cmd.py index 7b29fbfe90d8..68998b989909 100644 --- a/.github/scripts/cmd/test_cmd.py +++ b/.github/scripts/cmd/test_cmd.py @@ -47,7 +47,7 @@ def get_mock_bench_output(runtime, pallets, output_path, header, bench_flags, template = None): return f"frame-omni-bencher v1 benchmark pallet --extrinsic=* " \ - f"--runtime=target/release/wbuild/{runtime}-runtime/{runtime.replace('-', '_')}_runtime.wasm " \ + f"--runtime=target/production/wbuild/{runtime}-runtime/{runtime.replace('-', '_')}_runtime.wasm " \ f"--pallet={pallets} --header={header} " \ f"--output={output_path} " \ f"--wasm-execution=compiled " \ @@ -93,7 +93,7 @@ def tearDown(self): def test_bench_command_normal_execution_all_runtimes(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=list(map(lambda x: x['name'], mock_runtimes_matrix)), pallet=['pallet_balances'], fail_fast=True, @@ -117,10 +117,10 @@ def test_bench_command_normal_execution_all_runtimes(self): expected_calls = [ # Build calls - call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks"), - call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), - call("forklift cargo build -p rococo-runtime --profile release --features=runtime-benchmarks"), - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p kitchensink-runtime --profile production --features=runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile production --features=runtime-benchmarks"), + call("forklift cargo build -p rococo-runtime --profile production --features=runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile production --features=runtime-benchmarks"), call(get_mock_bench_output( runtime='kitchensink', @@ -150,7 +150,7 @@ def test_bench_command_normal_execution_all_runtimes(self): def test_bench_command_normal_execution(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['westend'], pallet=['pallet_balances', 'pallet_staking'], fail_fast=True, @@ -170,7 +170,7 @@ def test_bench_command_normal_execution(self): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile production --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output( @@ -193,7 +193,7 @@ def test_bench_command_normal_execution(self): def test_bench_command_normal_execution_xcm(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['westend'], pallet=['pallet_xcm_benchmarks::generic'], fail_fast=True, @@ -213,7 +213,7 @@ def test_bench_command_normal_execution_xcm(self): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile production --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output( @@ -229,7 +229,7 @@ def test_bench_command_normal_execution_xcm(self): def test_bench_command_two_runtimes_two_pallets(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['westend', 'rococo'], pallet=['pallet_balances', 'pallet_staking'], fail_fast=True, @@ -250,8 +250,8 @@ def test_bench_command_two_runtimes_two_pallets(self): expected_calls = [ # Build calls - call("forklift cargo build -p westend-runtime --profile release --features=runtime-benchmarks"), - call("forklift cargo build -p rococo-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p westend-runtime --profile production --features=runtime-benchmarks"), + call("forklift cargo build -p rococo-runtime --profile production --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output( runtime='westend', @@ -287,7 +287,7 @@ def test_bench_command_two_runtimes_two_pallets(self): def test_bench_command_one_dev_runtime(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['dev'], pallet=['pallet_balances'], fail_fast=True, @@ -309,7 +309,7 @@ def test_bench_command_one_dev_runtime(self): expected_calls = [ # Build calls - call("forklift cargo build -p kitchensink-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p kitchensink-runtime --profile production --features=runtime-benchmarks"), # Westend runtime calls call(get_mock_bench_output( runtime='kitchensink', @@ -324,7 +324,7 @@ def test_bench_command_one_dev_runtime(self): def test_bench_command_one_cumulus_runtime(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['asset-hub-westend'], pallet=['pallet_assets'], fail_fast=True, @@ -344,7 +344,7 @@ def test_bench_command_one_cumulus_runtime(self): expected_calls = [ # Build calls - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile production --features=runtime-benchmarks"), # Asset-hub-westend runtime calls call(get_mock_bench_output( runtime='asset-hub-westend', @@ -359,7 +359,7 @@ def test_bench_command_one_cumulus_runtime(self): def test_bench_command_one_cumulus_runtime_xcm(self): self.mock_parse_args.return_value = (argparse.Namespace( - command='bench', + command='bench-omni', runtime=['asset-hub-westend'], pallet=['pallet_xcm_benchmarks::generic', 'pallet_assets'], fail_fast=True, @@ -379,7 +379,7 @@ def test_bench_command_one_cumulus_runtime_xcm(self): expected_calls = [ # Build calls - call("forklift cargo build -p asset-hub-westend-runtime --profile release --features=runtime-benchmarks"), + call("forklift cargo build -p asset-hub-westend-runtime --profile production --features=runtime-benchmarks"), # Asset-hub-westend runtime calls call(get_mock_bench_output( runtime='asset-hub-westend', diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 525ab0c0fc23..6cd1adec1d8b 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -227,7 +227,8 @@ jobs: cat .github/env >> $GITHUB_OUTPUT if [ -n "$IMAGE_OVERRIDE" ]; then - echo "IMAGE=$IMAGE_OVERRIDE" >> $GITHUB_OUTPUT + IMAGE=$IMAGE_OVERRIDE + echo "IMAGE=$IMAGE" >> $GITHUB_OUTPUT fi if [[ $BODY == "/cmd bench"* ]]; then @@ -237,6 +238,10 @@ jobs: else echo "RUNNER=ubuntu-latest" >> $GITHUB_OUTPUT fi + - name: Print outputs + run: | + echo "RUNNER=${{ steps.set-image.outputs.RUNNER }}" + echo "IMAGE=${{ steps.set-image.outputs.IMAGE }}" # Get PR branch name, because the issue_comment event does not contain the PR branch name get-pr-branch: @@ -283,10 +288,16 @@ jobs: env: JOB_NAME: "cmd" runs-on: ${{ needs.set-image.outputs.RUNNER }} - timeout-minutes: 4320 # 72 hours -> 3 days; as it could take a long time to run all the runtimes/pallets container: image: ${{ needs.set-image.outputs.IMAGE }} + timeout-minutes: 1440 # 24 hours per runtime steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ needs.get-pr-branch.outputs.repo }} + ref: ${{ needs.get-pr-branch.outputs.pr-branch }} + - name: Get command uses: actions-ecosystem/action-regex-match@v2 id: get-pr-comment @@ -340,13 +351,7 @@ jobs: repo: context.repo.repo, body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has started 🚀 [See logs here](${job_url})` }) - - - name: Checkout - uses: actions/checkout@v4 - with: - repository: ${{ needs.get-pr-branch.outputs.repo }} - ref: ${{ needs.get-pr-branch.outputs.pr-branch }} - + - name: Install dependencies for bench if: startsWith(steps.get-pr-comment.outputs.group2, 'bench') run: | @@ -364,6 +369,7 @@ jobs: # Fixes "detected dubious ownership" error in the ci git config --global --add safe.directory '*' git remote -v + cat /proc/cpuinfo python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt python3 .github/scripts/cmd/cmd.py $CMD $PR_ARG git status diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index f991db55b86d..104e73521331 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -8,6 +8,8 @@ "bench_features": "runtime-benchmarks", "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic,pallet_nomination_pools,pallet_remark,pallet_transaction_storage", "uri": null, + "old_package": "staging-node-cli", + "old_bin": "substrate-node", "is_relay": false }, { @@ -19,6 +21,8 @@ "bench_flags": "", "bench_features": "runtime-benchmarks", "uri": "wss://try-runtime-westend.polkadot.io:443", + "old_package": "polkadot", + "old_bin": "polkadot", "is_relay": true }, { @@ -27,9 +31,11 @@ "path": "polkadot/runtime/rococo", "header": "polkadot/file_header.txt", "template": "polkadot/xcm/pallet-xcm-benchmarks/template.hbs", - "uri": "wss://try-runtime-rococo.polkadot.io:443", "bench_features": "runtime-benchmarks", "bench_flags": "", + "uri": "wss://try-runtime-rococo.polkadot.io:443", + "old_package": "polkadot", + "old_bin": "polkadot", "is_relay": true }, { @@ -41,6 +47,8 @@ "bench_features": "runtime-benchmarks", "bench_flags": "", "uri": "wss://westend-asset-hub-rpc.polkadot.io:443", + "old_package": "polkadot-parachain-bin", + "old_bin": "polkadot-parachain", "is_relay": false }, { @@ -52,6 +60,8 @@ "bench_features": "runtime-benchmarks", "bench_flags": "", "uri": "wss://rococo-asset-hub-rpc.polkadot.io:443", + "old_package": "polkadot-parachain-bin", + "old_bin": "polkadot-parachain", "is_relay": false }, { @@ -63,6 +73,8 @@ "bench_features": "runtime-benchmarks", "bench_flags": "", "uri": "wss://rococo-bridge-hub-rpc.polkadot.io:443", + "old_package": "polkadot-parachain-bin", + "old_bin": "polkadot-parachain", "is_relay": false }, { @@ -74,6 +86,8 @@ "bench_features": "runtime-benchmarks", "bench_flags": "", "uri": "wss://westend-bridge-hub-rpc.polkadot.io:443", + "old_package": "polkadot-parachain-bin", + "old_bin": "polkadot-parachain", "is_relay": false }, { @@ -84,7 +98,10 @@ "template": "cumulus/templates/xcm-bench-template.hbs", "bench_features": "runtime-benchmarks", "bench_flags": "", - "uri": "wss://westend-collectives-rpc.polkadot.io:443" + "uri": "wss://westend-collectives-rpc.polkadot.io:443", + "old_package": "polkadot-parachain-bin", + "old_bin": "polkadot-parachain", + "is_relay": false }, { "name": "contracts-rococo", @@ -95,6 +112,8 @@ "bench_features": "runtime-benchmarks", "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm", "uri": "wss://rococo-contracts-rpc.polkadot.io:443", + "old_package": "polkadot-parachain-bin", + "old_bin": "polkadot-parachain", "is_relay": false }, { @@ -106,6 +125,8 @@ "bench_features": "runtime-benchmarks", "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic", "uri": "wss://rococo-coretime-rpc.polkadot.io:443", + "old_package": "polkadot-parachain-bin", + "old_bin": "polkadot-parachain", "is_relay": false }, { @@ -117,6 +138,8 @@ "bench_features": "runtime-benchmarks", "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic", "uri": "wss://westend-coretime-rpc.polkadot.io:443", + "old_package": "polkadot-parachain-bin", + "old_bin": "polkadot-parachain", "is_relay": false }, { @@ -128,6 +151,8 @@ "bench_features": "runtime-benchmarks", "bench_flags": "--genesis-builder-policy=none", "uri": null, + "old_package": "polkadot-parachain-bin", + "old_bin": "polkadot-parachain", "is_relay": false }, { @@ -139,6 +164,8 @@ "bench_features": "runtime-benchmarks", "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic", "uri": "wss://rococo-people-rpc.polkadot.io:443", + "old_package": "polkadot-parachain-bin", + "old_bin": "polkadot-parachain", "is_relay": false }, { @@ -150,6 +177,8 @@ "bench_features": "runtime-benchmarks", "bench_flags": "--genesis-builder-policy=none --exclude-pallets=pallet_xcm,pallet_xcm_benchmarks::fungible,pallet_xcm_benchmarks::generic", "uri": "wss://westend-people-rpc.polkadot.io:443", + "old_package": "polkadot-parachain-bin", + "old_bin": "polkadot-parachain", "is_relay": false } ] diff --git a/polkadot/runtime/westend/src/weights/pallet_balances.rs b/polkadot/runtime/westend/src/weights/pallet_balances.rs index b303a1a21766..deaf8840462b 100644 --- a/polkadot/runtime/westend/src/weights/pallet_balances.rs +++ b/polkadot/runtime/westend/src/weights/pallet_balances.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_balances` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-12-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `95c137a642c3`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=westend-dev +// --pallet=pallet_balances +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_balances -// --chain=westend-dev -// --header=./polkadot/file_header.txt -// --output=./polkadot/runtime/westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,8 +56,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 50_394_000 picoseconds. - Weight::from_parts(51_666_000, 0) + // Minimum execution time: 51_474_000 picoseconds. + Weight::from_parts(52_840_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -66,8 +68,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 40_307_000 picoseconds. - Weight::from_parts(41_722_000, 0) + // Minimum execution time: 39_875_000 picoseconds. + Weight::from_parts(41_408_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -78,8 +80,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 19_110_000 picoseconds. - Weight::from_parts(19_623_000, 0) + // Minimum execution time: 19_614_000 picoseconds. + Weight::from_parts(20_194_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -90,8 +92,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 26_837_000 picoseconds. - Weight::from_parts(27_672_000, 0) + // Minimum execution time: 27_430_000 picoseconds. + Weight::from_parts(28_151_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -102,8 +104,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `6196` - // Minimum execution time: 53_032_000 picoseconds. - Weight::from_parts(54_040_000, 0) + // Minimum execution time: 54_131_000 picoseconds. + Weight::from_parts(54_810_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -114,8 +116,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3593` - // Minimum execution time: 49_429_000 picoseconds. - Weight::from_parts(50_020_000, 0) + // Minimum execution time: 48_692_000 picoseconds. + Weight::from_parts(51_416_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -126,8 +128,8 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `174` // Estimated: `3593` - // Minimum execution time: 22_114_000 picoseconds. - Weight::from_parts(22_526_000, 0) + // Minimum execution time: 22_604_000 picoseconds. + Weight::from_parts(23_336_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -139,11 +141,11 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0 + u * (136 ±0)` // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 17_550_000 picoseconds. - Weight::from_parts(17_860_000, 0) + // Minimum execution time: 18_118_000 picoseconds. + Weight::from_parts(18_352_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 11_891 - .saturating_add(Weight::from_parts(15_027_705, 0).saturating_mul(u.into())) + // Standard Error: 14_688 + .saturating_add(Weight::from_parts(15_412_440, 0).saturating_mul(u.into())) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(u.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(u.into()))) .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) @@ -152,24 +154,24 @@ impl pallet_balances::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_605_000 picoseconds. - Weight::from_parts(6_922_000, 0) + // Minimum execution time: 6_779_000 picoseconds. + Weight::from_parts(7_246_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn burn_allow_death() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 31_182_000 picoseconds. - Weight::from_parts(32_104_000, 0) + // Minimum execution time: 30_935_000 picoseconds. + Weight::from_parts(32_251_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn burn_keep_alive() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 21_105_000 picoseconds. - Weight::from_parts(21_533_000, 0) + // Minimum execution time: 21_002_000 picoseconds. + Weight::from_parts(21_760_000, 0) .saturating_add(Weight::from_parts(0, 0)) } } From da953454aa4b381c5b44ee6a32ff1c43e744390c Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Mon, 9 Dec 2024 14:00:19 +0100 Subject: [PATCH 125/340] Fix `Possible bug: Vote import failed` after aggression is enabled (#6690) After finality started lagging on kusama around 025-11-25 15:55:40 validators started seeing ocassionally this log, when importing votes covering more than one assignment. ``` Possible bug: Vote import failed ``` That happens because the assumption that assignments from the same validator would have the same required routing doesn't hold after you enabled aggression, so you might end up receiving the first assignment then you modify the routing for it in `enable_aggression` then your receive the second assignment and the vote covering both assignments, so the rouing for the first and second assingment wouldn't match and we would fail to import the vote. From the logs I've seen, I don't think this is the reason the network didn't fully recover until the failsafe kicked it, because the votes had been already imported in approval-voting before this error. --------- Signed-off-by: Alexandru Gheorghe --- .../network/approval-distribution/src/lib.rs | 19 ++---- .../network/protocol/src/grid_topology.rs | 60 +++++++++++++++++++ prdoc/pr_6690.prdoc | 17 ++++++ 3 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 prdoc/pr_6690.prdoc diff --git a/polkadot/node/network/approval-distribution/src/lib.rs b/polkadot/node/network/approval-distribution/src/lib.rs index 876cc59b9c28..d6bbb0dca83c 100644 --- a/polkadot/node/network/approval-distribution/src/lib.rs +++ b/polkadot/node/network/approval-distribution/src/lib.rs @@ -163,8 +163,6 @@ enum ApprovalEntryError { InvalidCandidateIndex, DuplicateApproval, UnknownAssignment, - #[allow(dead_code)] - AssignmentsFollowedDifferentPaths(RequiredRouting, RequiredRouting), } impl ApprovalEntry { @@ -568,7 +566,7 @@ impl BlockEntry { &mut self, approval: IndirectSignedApprovalVoteV2, ) -> Result<(RequiredRouting, HashSet), ApprovalEntryError> { - let mut required_routing = None; + let mut required_routing: Option = None; let mut peers_randomly_routed_to = HashSet::new(); if self.candidates.len() < approval.candidate_indices.len() as usize { @@ -595,16 +593,11 @@ impl BlockEntry { peers_randomly_routed_to .extend(approval_entry.routing_info().peers_randomly_routed.iter()); - if let Some(required_routing) = required_routing { - if required_routing != approval_entry.routing_info().required_routing { - // This shouldn't happen since the required routing is computed based on the - // validator_index, so two assignments from the same validators will have - // the same required routing. - return Err(ApprovalEntryError::AssignmentsFollowedDifferentPaths( - required_routing, - approval_entry.routing_info().required_routing, - )) - } + if let Some(current_required_routing) = required_routing { + required_routing = Some( + current_required_routing + .combine(approval_entry.routing_info().required_routing), + ); } else { required_routing = Some(approval_entry.routing_info().required_routing) } diff --git a/polkadot/node/network/protocol/src/grid_topology.rs b/polkadot/node/network/protocol/src/grid_topology.rs index 4dd7d29fc25c..f4c1a07ba3c2 100644 --- a/polkadot/node/network/protocol/src/grid_topology.rs +++ b/polkadot/node/network/protocol/src/grid_topology.rs @@ -575,6 +575,22 @@ impl RequiredRouting { _ => false, } } + + /// Combine two required routing sets into one that would cover both routing modes. + pub fn combine(self, other: Self) -> Self { + match (self, other) { + (RequiredRouting::All, _) | (_, RequiredRouting::All) => RequiredRouting::All, + (RequiredRouting::GridXY, _) | (_, RequiredRouting::GridXY) => RequiredRouting::GridXY, + (RequiredRouting::GridX, RequiredRouting::GridY) | + (RequiredRouting::GridY, RequiredRouting::GridX) => RequiredRouting::GridXY, + (RequiredRouting::GridX, RequiredRouting::GridX) => RequiredRouting::GridX, + (RequiredRouting::GridY, RequiredRouting::GridY) => RequiredRouting::GridY, + (RequiredRouting::None, RequiredRouting::PendingTopology) | + (RequiredRouting::PendingTopology, RequiredRouting::None) => RequiredRouting::PendingTopology, + (RequiredRouting::None, _) | (RequiredRouting::PendingTopology, _) => other, + (_, RequiredRouting::None) | (_, RequiredRouting::PendingTopology) => self, + } + } } #[cfg(test)] @@ -587,6 +603,50 @@ mod tests { rand_chacha::ChaCha12Rng::seed_from_u64(12345) } + #[test] + fn test_required_routing_combine() { + assert_eq!(RequiredRouting::All.combine(RequiredRouting::None), RequiredRouting::All); + assert_eq!(RequiredRouting::All.combine(RequiredRouting::GridXY), RequiredRouting::All); + assert_eq!(RequiredRouting::GridXY.combine(RequiredRouting::All), RequiredRouting::All); + assert_eq!(RequiredRouting::None.combine(RequiredRouting::All), RequiredRouting::All); + assert_eq!(RequiredRouting::None.combine(RequiredRouting::None), RequiredRouting::None); + assert_eq!( + RequiredRouting::PendingTopology.combine(RequiredRouting::GridX), + RequiredRouting::GridX + ); + + assert_eq!( + RequiredRouting::GridX.combine(RequiredRouting::PendingTopology), + RequiredRouting::GridX + ); + assert_eq!(RequiredRouting::GridX.combine(RequiredRouting::GridY), RequiredRouting::GridXY); + assert_eq!(RequiredRouting::GridY.combine(RequiredRouting::GridX), RequiredRouting::GridXY); + assert_eq!( + RequiredRouting::GridXY.combine(RequiredRouting::GridXY), + RequiredRouting::GridXY + ); + assert_eq!(RequiredRouting::GridX.combine(RequiredRouting::GridX), RequiredRouting::GridX); + assert_eq!(RequiredRouting::GridY.combine(RequiredRouting::GridY), RequiredRouting::GridY); + + assert_eq!(RequiredRouting::None.combine(RequiredRouting::GridY), RequiredRouting::GridY); + assert_eq!(RequiredRouting::None.combine(RequiredRouting::GridX), RequiredRouting::GridX); + assert_eq!(RequiredRouting::None.combine(RequiredRouting::GridXY), RequiredRouting::GridXY); + + assert_eq!(RequiredRouting::GridY.combine(RequiredRouting::None), RequiredRouting::GridY); + assert_eq!(RequiredRouting::GridX.combine(RequiredRouting::None), RequiredRouting::GridX); + assert_eq!(RequiredRouting::GridXY.combine(RequiredRouting::None), RequiredRouting::GridXY); + + assert_eq!( + RequiredRouting::PendingTopology.combine(RequiredRouting::None), + RequiredRouting::PendingTopology + ); + + assert_eq!( + RequiredRouting::None.combine(RequiredRouting::PendingTopology), + RequiredRouting::PendingTopology + ); + } + #[test] fn test_random_routing_sample() { // This test is fragile as it relies on a specific ChaCha12Rng diff --git a/prdoc/pr_6690.prdoc b/prdoc/pr_6690.prdoc new file mode 100644 index 000000000000..0e4a2437ef96 --- /dev/null +++ b/prdoc/pr_6690.prdoc @@ -0,0 +1,17 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix Possible bug, Vote import failed after aggression is enabled + +doc: + - audience: Node Dev + description: | + Fix the appearance of Possible bug: Vote import failed after aggression is enabled, the log itself is + harmless because approval gets imported anyway and aggression is able to distribute it, nevertheless + is something that can be easily be fixed by picking the highest required routing possible. + +crates: + - name: polkadot-node-network-protocol + bump: minor + - name: polkadot-approval-distribution + bump: minor From 4198dc95b20e7a157fa0d1b8ef4a2e01a223af1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Mon, 9 Dec 2024 15:57:00 +0100 Subject: [PATCH 126/340] pallet-revive: Remove unused dependencies (#6796) The dependency on `pallet_balances` doesn't seem to be necessary. At least everything compiles for me without it. Removed this dependency and a few others that seem to be left overs. --------- Co-authored-by: GitHub Action --- Cargo.lock | 4 ---- prdoc/pr_6796.prdoc | 9 +++++++++ substrate/frame/revive/Cargo.toml | 19 ++++--------------- .../revive/src/benchmarking/call_builder.rs | 4 ++-- .../frame/revive/src/benchmarking/mod.rs | 8 +++----- 5 files changed, 18 insertions(+), 26 deletions(-) create mode 100644 prdoc/pr_6796.prdoc diff --git a/Cargo.lock b/Cargo.lock index cee1e2ce7411..aa91d9284a10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14633,7 +14633,6 @@ dependencies = [ "assert_matches", "bitflags 1.3.2", "derive_more 0.99.17", - "env_logger 0.11.3", "environmental", "ethereum-types 0.15.1", "frame-benchmarking 28.0.0", @@ -14642,11 +14641,8 @@ dependencies = [ "hex", "hex-literal", "impl-trait-for-tuples", - "jsonrpsee", "log", - "pallet-assets 29.1.0", "pallet-balances 28.0.0", - "pallet-message-queue 31.0.0", "pallet-proxy 28.0.0", "pallet-revive-fixtures 0.1.0", "pallet-revive-proc-macro 0.1.0", diff --git a/prdoc/pr_6796.prdoc b/prdoc/pr_6796.prdoc new file mode 100644 index 000000000000..aeb305847bf8 --- /dev/null +++ b/prdoc/pr_6796.prdoc @@ -0,0 +1,9 @@ +title: 'pallet-revive: Remove unused dependencies' +doc: +- audience: Runtime Dev + description: The dependency on `pallet_balances` doesn't seem to be necessary. At + least everything compiles for me without it. Removed this dependency and a few + others that seem to be left overs. +crates: +- name: pallet-revive + bump: major diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 098a66df8dee..2e069bacf737 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -32,17 +32,15 @@ impl-trait-for-tuples = { workspace = true } rlp = { workspace = true } derive_more = { workspace = true } hex = { workspace = true } -jsonrpsee = { workspace = true, features = ["full"], optional = true } ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] } # Polkadot SDK Dependencies frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -pallet-balances = { optional = true, workspace = true } -pallet-revive-fixtures = { workspace = true, default-features = false, optional = true } -pallet-revive-uapi = { workspace = true, default-features = true } -pallet-revive-proc-macro = { workspace = true, default-features = true } +pallet-revive-fixtures = { workspace = true, optional = true } +pallet-revive-uapi = { workspace = true, features = ["scale"] } +pallet-revive-proc-macro = { workspace = true } pallet-transaction-payment = { workspace = true } sp-api = { workspace = true } sp-arithmetic = { workspace = true } @@ -61,19 +59,16 @@ subxt-signer = { workspace = true, optional = true, features = [ array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } pretty_assertions = { workspace = true } -pallet-revive-fixtures = { workspace = true, default-features = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } hex-literal = { workspace = true } -env_logger = { workspace = true } # Polkadot SDK Dependencies pallet-balances = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } -pallet-message-queue = { workspace = true, default-features = true } pallet-utility = { workspace = true, default-features = true } -pallet-assets = { workspace = true, default-features = true } pallet-proxy = { workspace = true, default-features = true } +pallet-revive-fixtures = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } xcm-builder = { workspace = true, default-features = true } @@ -88,9 +83,7 @@ std = [ "frame-support/std", "frame-system/std", "hex/std", - "jsonrpsee", "log/std", - "pallet-balances?/std", "pallet-proxy/std", "pallet-revive-fixtures?/std", "pallet-timestamp/std", @@ -118,9 +111,7 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", - "pallet-message-queue/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-revive-fixtures", "pallet-timestamp/runtime-benchmarks", @@ -132,9 +123,7 @@ runtime-benchmarks = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-assets/try-runtime", "pallet-balances/try-runtime", - "pallet-message-queue/try-runtime", "pallet-proxy/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", diff --git a/substrate/frame/revive/src/benchmarking/call_builder.rs b/substrate/frame/revive/src/benchmarking/call_builder.rs index c666383abb2f..8d3157541168 100644 --- a/substrate/frame/revive/src/benchmarking/call_builder.rs +++ b/substrate/frame/revive/src/benchmarking/call_builder.rs @@ -45,7 +45,7 @@ pub struct CallSetup { impl Default for CallSetup where - T: Config + pallet_balances::Config, + T: Config, BalanceOf: Into + TryFrom, MomentOf: Into, T::Hash: frame_support::traits::IsType, @@ -57,7 +57,7 @@ where impl CallSetup where - T: Config + pallet_balances::Config, + T: Config, BalanceOf: Into + TryFrom, MomentOf: Into, T::Hash: frame_support::traits::IsType, diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index b73815bfb9ea..ebdea8675824 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -34,11 +34,10 @@ use frame_benchmarking::v2::*; use frame_support::{ self, assert_ok, storage::child, - traits::{fungible::InspectHold, Currency}, + traits::fungible::InspectHold, weights::{Weight, WeightMeter}, }; use frame_system::RawOrigin; -use pallet_balances; use pallet_revive_uapi::{CallFlags, ReturnErrorCode, StorageFlags}; use sp_runtime::traits::{Bounded, Hash}; @@ -68,7 +67,7 @@ struct Contract { impl Contract where - T: Config + pallet_balances::Config, + T: Config, BalanceOf: Into + TryFrom, MomentOf: Into, T::Hash: frame_support::traits::IsType, @@ -220,11 +219,10 @@ fn default_deposit_limit() -> BalanceOf { #[benchmarks( where BalanceOf: Into + TryFrom, - T: Config + pallet_balances::Config, + T: Config, MomentOf: Into, ::RuntimeEvent: From>, ::RuntimeCall: From>, - as Currency>::Balance: From>, ::Hash: frame_support::traits::IsType, )] mod benchmarks { From 30d0cf4242758c5558e9fd83c6c2c507692f2d52 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 9 Dec 2024 19:35:09 +0200 Subject: [PATCH 127/340] finish integration tests --- .../primitives/router/src/inbound/v2.rs | 24 +++++++-- .../src/tests/snowbridge.rs | 10 ++-- .../src/tests/snowbridge_v2.rs | 50 +++++++++---------- .../src/bridge_to_ethereum_config.rs | 2 + 4 files changed, 53 insertions(+), 33 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 0757869d3045..23b51eb4a5f7 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -55,8 +55,10 @@ pub enum Asset { /// Reason why a message conversion failed. #[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] pub enum ConvertMessageError { - /// Invalid foreign ERC20 token ID + /// Invalid foreign ERC-20 token ID InvalidAsset, + /// Cannot reachor a foreign ERC-20 asset location. + CannotReanchor, } pub trait ConvertMessage { @@ -69,12 +71,16 @@ pub struct MessageToXcm< ConvertAssetId, WethAddress, GatewayProxyAddress, + EthereumUniversalLocation, + GlobalAssetHubLocation, > where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, WethAddress: Get, GatewayProxyAddress: Get, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, { _phantom: PhantomData<( EthereumNetwork, @@ -82,6 +88,8 @@ pub struct MessageToXcm< ConvertAssetId, WethAddress, GatewayProxyAddress, + EthereumUniversalLocation, + GlobalAssetHubLocation, )>, } @@ -91,6 +99,8 @@ impl< ConvertAssetId, WethAddress, GatewayProxyAddress, + EthereumUniversalLocation, + GlobalAssetHubLocation, > ConvertMessage for MessageToXcm< EthereumNetwork, @@ -98,6 +108,8 @@ impl< ConvertAssetId, WethAddress, GatewayProxyAddress, + EthereumUniversalLocation, + GlobalAssetHubLocation, > where EthereumNetwork: Get, @@ -105,6 +117,8 @@ where ConvertAssetId: MaybeEquivalence, WethAddress: Get, GatewayProxyAddress: Get, + EthereumUniversalLocation: Get, + GlobalAssetHubLocation: Get, { fn convert( message: Message, @@ -172,9 +186,13 @@ where reserve_assets.push(asset); }, Asset::ForeignTokenERC20 { token_id, value } => { - let asset_id = ConvertAssetId::convert(&token_id) + let asset_loc = ConvertAssetId::convert(&token_id) .ok_or(ConvertMessageError::InvalidAsset)?; - let asset: XcmAsset = (asset_id, *value).into(); + let mut reanchored_asset_loc = asset_loc.clone(); + reanchored_asset_loc + .reanchor(&GlobalAssetHubLocation::get(), &EthereumUniversalLocation::get()) + .map_err(|_| ConvertMessageError::CannotReanchor)?; + let asset: XcmAsset = (reanchored_asset_loc, *value).into(); withdraw_assets.push(asset); }, } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index a55aa9f9c353..48eeb07a7c6a 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -19,6 +19,7 @@ use codec::{Decode, Encode}; use emulated_integration_tests_common::RESERVABLE_ASSET_ID; use frame_support::pallet_prelude::TypeInfo; use hex_literal::hex; +use penpal_emulated_chain::PARA_ID_B; use rococo_westend_system_emulated_network::asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner; use snowbridge_core::{outbound::OperatingMode, AssetMetadata, TokenIdOf}; use snowbridge_router_primitives::inbound::{ @@ -28,8 +29,6 @@ use snowbridge_router_primitives::inbound::{ use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm_executor::traits::ConvertLocation; -use penpal_emulated_chain::PARA_ID_B; -use penpal_emulated_chain::penpal_runtime; const INITIAL_FUND: u128 = 5_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; @@ -38,7 +37,7 @@ const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EB const XCM_FEE: u128 = 100_000_000_000; const TOKEN_AMOUNT: u128 = 100_000_000_000; -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeIfmtnfo)] pub enum ControlCall { #[codec(index = 3)] CreateAgent, @@ -289,7 +288,6 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { }); } - /// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is /// still located on AssetHub. #[test] @@ -336,7 +334,9 @@ fn send_token_from_ethereum_to_penpal() { 1000, )); - assert!(::ForeignAssets::asset_exists(weth_asset_location.clone())); + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone() + )); }); AssetHubWestend::execute_with(|| { diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 5ee51cda9295..6bc41f212b6e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -19,19 +19,20 @@ use bridge_hub_westend_runtime::{ EthereumInboundQueueV2, }; use codec::Encode; +use emulated_integration_tests_common::RESERVABLE_ASSET_ID; use hex_literal::hex; +use penpal_emulated_chain::PARA_ID_B; +use snowbridge_core::{AssetMetadata, TokenIdOf}; use snowbridge_router_primitives::inbound::{ - v2::{Asset::NativeTokenERC20, Message}, + v2::{ + Asset::{ForeignTokenERC20, NativeTokenERC20}, + Message, + }, EthereumLocationsConverterFor, }; use sp_core::{H160, H256}; use sp_runtime::MultiAddress; -use emulated_integration_tests_common::RESERVABLE_ASSET_ID; -use penpal_emulated_chain::PARA_ID_B; -use snowbridge_core::AssetMetadata; use xcm_executor::traits::ConvertLocation; -use snowbridge_core::TokenIdOf; -use snowbridge_router_primitives::inbound::v2::Asset::ForeignTokenERC20; const TOKEN_AMOUNT: u128 = 100_000_000_000; /// Calculates the XCM prologue fee for sending an XCM to AH. @@ -537,14 +538,16 @@ fn send_token_to_penpal_v2() { // Pay fees on Penpal. PayFees { asset: weth_fee_penpal }, // Deposit assets to beneficiary. - DepositAsset { assets: Wild(AllOf { - id: AssetId(token_location.clone()), - fun: WildFungibility::Fungible, - }), - beneficiary: beneficiary.clone(), }, + DepositAsset { + assets: Wild(AllOf { + id: AssetId(token_location.clone()), + fun: WildFungibility::Fungible, + }), + beneficiary: beneficiary.clone(), + }, SetTopic(H256::random().into()), ] - .into(), + .into(), }, ]; let xcm: Xcm<()> = instructions.into(); @@ -611,6 +614,8 @@ fn send_foreign_erc20_token_back_to_polkadot() { let asset_id: Location = [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())].into(); + register_foreign_asset(weth_location()); + let asset_id_in_bh: Location = Location::new( 1, [ @@ -627,8 +632,8 @@ fn send_foreign_erc20_token_back_to_polkadot() { Parachain(AssetHubWestend::para_id().into()), ], ) - .appended_with(asset_id.clone().interior) - .unwrap(); + .appended_with(asset_id.clone().interior) + .unwrap(); let ethereum_destination = Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]); @@ -653,36 +658,33 @@ fn send_foreign_erc20_token_back_to_polkadot() { .into(); AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + // Mint the asset into the bridge sovereign account, to mimic locked funds AssetHubWestend::mint_asset( ::RuntimeOrigin::signed(AssetHubWestendAssetOwner::get()), RESERVABLE_ASSET_ID, - AssetHubWestendSender::get(), + ethereum_sovereign.clone(), TOKEN_AMOUNT, ); let token_id = TokenIdOf::convert_location(&asset_id_after_reanchored).unwrap(); - let asset: Asset = (asset_id_after_reanchored, TOKEN_AMOUNT).into(); let assets = vec![ // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: 2_000_000_000_000u128 }, + NativeTokenERC20 { token_id: WETH.into(), value: 3_000_000_000_000u128 }, // the token being transferred ForeignTokenERC20 { token_id: token_id.into(), value: TOKEN_AMOUNT }, ]; BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - let instructions = vec![ - WithdrawAsset(asset.clone().into()), - DepositAsset { assets: Wild(AllCounted(2)), beneficiary }, - ]; + let instructions = vec![DepositAsset { assets: Wild(AllCounted(2)), beneficiary }]; let xcm: Xcm<()> = instructions.into(); let versioned_message_xcm = VersionedXcm::V5(xcm); let origin = EthereumGatewayAddress::get(); let message = Message { origin, - fee: 1_500_000_000_000u128, + fee: 3_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), @@ -943,7 +945,6 @@ pub(crate) fn set_up_weth_and_dot_pool(asset: v5::Location) { }); } - pub(crate) fn set_up_weth_and_dot_pool_on_penpal(asset: v5::Location) { let wnd: v5::Location = v5::Parent.into(); let penpal_location = BridgeHubWestend::sibling_location_of(PenpalB::para_id()); @@ -956,8 +957,7 @@ pub(crate) fn set_up_weth_and_dot_pool_on_penpal(asset: v5::Location) { type RuntimeEvent = ::RuntimeEvent; let signed_owner = ::RuntimeOrigin::signed(owner.clone()); - let signed_bh_sovereign = - ::RuntimeOrigin::signed(bh_sovereign.clone()); + let signed_bh_sovereign = ::RuntimeOrigin::signed(bh_sovereign.clone()); assert_ok!(::ForeignAssets::mint( signed_bh_sovereign.clone(), diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index b46291f09e9e..b05fa357fb98 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -125,6 +125,8 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { EthereumSystem, WethAddress, EthereumGatewayAddress, + EthereumUniversalLocation, + AssetHubFromEthereum, >; } From e79fd2bb9926be7d94be0c002676fca57b6116bf Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Mon, 9 Dec 2024 21:58:10 +0200 Subject: [PATCH 128/340] xcm-executor: take transport fee from transferred assets if necessary (#4834) # Description Sending XCM messages to other chains requires paying a "transport fee". This can be paid either: - from `origin` local account if `jit_withdraw = true`, - taken from Holding register otherwise. This currently works for following hops/scenarios: 1. On destination no transport fee needed (only sending costs, not receiving), 2. Local/originating chain: just set JIT=true and fee will be paid from signed account, 3. Intermediary hops - only if intermediary is acting as reserve between two untrusted chains (aka only for `DepositReserveAsset` instruction) - this was fixed in https://github.com/paritytech/polkadot-sdk/pull/3142 But now we're seeing more complex asset transfers that are mixing reserve transfers with teleports depending on the involved chains. # Example E.g. transferring DOT between Relay and parachain, but through AH (using AH instead of the Relay chain as parachain's DOT reserve). In the `Parachain --1--> AssetHub --2--> Relay` scenario, DOT has to be reserve-withdrawn in leg `1`, then teleported in leg `2`. On the intermediary hop (AssetHub), `InitiateTeleport` fails to send onward message because of missing transport fees. We also can't rely on `jit_withdraw` because the original origin is lost on the way, and even if it weren't we can't rely on the user having funded accounts on each hop along the way. # Solution/Changes - Charge the transport fee in the executor from the transferred assets (if available), - Only charge from transferred assets if JIT_WITHDRAW was not set, - Only charge from transferred assets if unless using XCMv5 `PayFees` where we do not have this problem. # Testing Added regression tests in emulated transfers. Fixes https://github.com/paritytech/polkadot-sdk/issues/4832 Fixes https://github.com/paritytech/polkadot-sdk/issues/6637 --------- Signed-off-by: Adrian Catangiu Co-authored-by: Francisco Aguirre --- .../primitives/router/src/inbound/mod.rs | 4 +- .../src/tests/reserve_transfer.rs | 6 +- .../tests/assets/asset-hub-westend/src/lib.rs | 1 + .../src/tests/hybrid_transfers.rs | 141 +++++++++++++++++- .../src/tests/reserve_transfer.rs | 6 +- .../asset-hub-westend/src/tests/transact.rs | 2 +- .../src/tests/asset_transfers.rs | 2 +- .../src/tests/register_bridged_assets.rs | 2 +- .../bridge-hub-rococo/src/tests/send_xcm.rs | 2 +- .../src/tests/asset_transfers.rs | 4 +- .../src/tests/register_bridged_assets.rs | 2 +- .../bridge-hub-westend/src/tests/send_xcm.rs | 2 +- .../bridge-hub-westend/src/tests/transact.rs | 2 +- polkadot/xcm/xcm-executor/src/lib.rs | 83 ++++++++--- prdoc/pr_4834.prdoc | 15 ++ 15 files changed, 230 insertions(+), 44 deletions(-) create mode 100644 prdoc/pr_4834.prdoc diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index 54a578b988a4..a7bd0dfc1234 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -358,7 +358,9 @@ where }])), // Perform a deposit reserve to send to destination chain. DepositReserveAsset { - assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), + // Send over assets and unspent fees, XCM delivery fee will be charged from + // here. + assets: Wild(AllCounted(2)), dest: Location::new(1, [Parachain(dest_para_id)]), xcm: vec![ // Buy execution on target. diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index 698ef2c9e792..d642e877f002 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -115,7 +115,7 @@ pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { assert_expected_events!( AssetHubRococo, vec![ - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {}, ] ); @@ -274,7 +274,7 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) { t.args.dest.clone() ), }, - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm( pallet_xcm::Event::FeesPaid { .. } ) => {}, @@ -305,7 +305,7 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { owner: *owner == t.sender.account_id, balance: *balance == t.args.amount, }, - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm( pallet_xcm::Event::FeesPaid { .. } ) => {}, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index 3cca99fbfe5c..36630e2d2221 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -106,6 +106,7 @@ mod imports { pub type ParaToParaThroughRelayTest = Test; pub type ParaToParaThroughAHTest = Test; pub type RelayToParaThroughAHTest = Test; + pub type PenpalToRelayThroughAHTest = Test; } #[cfg(test)] diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs index a0fc82fba6ef..0686bd71d085 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs @@ -658,13 +658,13 @@ fn bidirectional_teleport_foreign_asset_between_para_and_asset_hub_using_explici } // =============================================================== -// ===== Transfer - Native Asset - Relay->AssetHub->Parachain ==== +// ====== Transfer - Native Asset - Relay->AssetHub->Penpal ====== // =============================================================== -/// Transfers of native asset Relay to Parachain (using AssetHub reserve). Parachains want to avoid +/// Transfers of native asset Relay to Penpal (using AssetHub reserve). Parachains want to avoid /// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their /// Sovereign Account on Asset Hub. #[test] -fn transfer_native_asset_from_relay_to_para_through_asset_hub() { +fn transfer_native_asset_from_relay_to_penpal_through_asset_hub() { // Init values for Relay let destination = Westend::child_location_of(PenpalA::para_id()); let sender = WestendSender::get(); @@ -820,6 +820,137 @@ fn transfer_native_asset_from_relay_to_para_through_asset_hub() { assert!(receiver_assets_after < receiver_assets_before + amount_to_send); } +// =============================================================== +// ===== Transfer - Native Asset - Penpal->AssetHub->Relay ======= +// =============================================================== +/// Transfers of native asset Penpal to Relay (using AssetHub reserve). Parachains want to avoid +/// managing SAs on all system chains, thus want all their DOT-in-reserve to be held in their +/// Sovereign Account on Asset Hub. +#[test] +fn transfer_native_asset_from_penpal_to_relay_through_asset_hub() { + // Init values for Penpal + let destination = RelayLocation::get(); + let sender = PenpalASender::get(); + let amount_to_send: Balance = WESTEND_ED * 100; + + // Init values for Penpal + let relay_native_asset_location = RelayLocation::get(); + let receiver = WestendReceiver::get(); + + // Init Test + let test_args = TestContext { + sender: sender.clone(), + receiver: receiver.clone(), + args: TestArgs::new_para( + destination.clone(), + receiver.clone(), + amount_to_send, + (Parent, amount_to_send).into(), + None, + 0, + ), + }; + let mut test = PenpalToRelayThroughAHTest::new(test_args); + + let sov_penpal_on_ah = AssetHubWestend::sovereign_account_id_of( + AssetHubWestend::sibling_location_of(PenpalA::para_id()), + ); + // fund Penpal's sender account + PenpalA::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + relay_native_asset_location.clone(), + sender.clone(), + amount_to_send * 2, + ); + // fund Penpal's SA on AssetHub with the assets held in reserve + AssetHubWestend::fund_accounts(vec![(sov_penpal_on_ah.clone().into(), amount_to_send * 2)]); + + // prefund Relay checking account so we accept teleport "back" from AssetHub + let check_account = + Westend::execute_with(|| ::XcmPallet::check_account()); + Westend::fund_accounts(vec![(check_account, amount_to_send)]); + + // Query initial balances + let sender_balance_before = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &sender) + }); + let sov_penpal_on_ah_before = AssetHubWestend::execute_with(|| { + ::Balances::free_balance(sov_penpal_on_ah.clone()) + }); + let receiver_balance_before = Westend::execute_with(|| { + ::Balances::free_balance(receiver.clone()) + }); + + fn transfer_assets_dispatchable(t: PenpalToRelayThroughAHTest) -> DispatchResult { + let fee_idx = t.args.fee_asset_item as usize; + let fee: Asset = t.args.assets.inner().get(fee_idx).cloned().unwrap(); + let asset_hub_location = PenpalA::sibling_location_of(AssetHubWestend::para_id()); + let context = PenpalUniversalLocation::get(); + + // reanchor fees to the view of destination (Westend Relay) + let mut remote_fees = fee.clone().reanchored(&t.args.dest, &context).unwrap(); + if let Fungible(ref mut amount) = remote_fees.fun { + // we already spent some fees along the way, just use half of what we started with + *amount = *amount / 2; + } + let xcm_on_final_dest = Xcm::<()>(vec![ + BuyExecution { fees: remote_fees, weight_limit: t.args.weight_limit.clone() }, + DepositAsset { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + beneficiary: t.args.beneficiary, + }, + ]); + + // reanchor final dest (Westend Relay) to the view of hop (Asset Hub) + let mut dest = t.args.dest.clone(); + dest.reanchor(&asset_hub_location, &context).unwrap(); + // on Asset Hub + let xcm_on_hop = Xcm::<()>(vec![InitiateTeleport { + assets: Wild(AllCounted(t.args.assets.len() as u32)), + dest, + xcm: xcm_on_final_dest, + }]); + + // First leg is a reserve-withdraw, from there a teleport to final dest + ::PolkadotXcm::transfer_assets_using_type_and_then( + t.signed_origin, + bx!(asset_hub_location.into()), + bx!(t.args.assets.into()), + bx!(TransferType::DestinationReserve), + bx!(fee.id.into()), + bx!(TransferType::DestinationReserve), + bx!(VersionedXcm::from(xcm_on_hop)), + t.args.weight_limit, + ) + } + test.set_dispatchable::(transfer_assets_dispatchable); + test.assert(); + + // Query final balances + let sender_balance_after = PenpalA::execute_with(|| { + type ForeignAssets = ::ForeignAssets; + >::balance(relay_native_asset_location.clone(), &sender) + }); + let sov_penpal_on_ah_after = AssetHubWestend::execute_with(|| { + ::Balances::free_balance(sov_penpal_on_ah.clone()) + }); + let receiver_balance_after = Westend::execute_with(|| { + ::Balances::free_balance(receiver.clone()) + }); + + // Sender's asset balance is reduced by amount sent plus delivery fees + assert!(sender_balance_after < sender_balance_before - amount_to_send); + // SA on AH balance is decreased by `amount_to_send` + assert_eq!(sov_penpal_on_ah_after, sov_penpal_on_ah_before - amount_to_send); + // Receiver's balance is increased + assert!(receiver_balance_after > receiver_balance_before); + // Receiver's balance increased by `amount_to_send - delivery_fees - bought_execution`; + // `delivery_fees` might be paid from transfer or JIT, also `bought_execution` is unknown but + // should be non-zero + assert!(receiver_balance_after < receiver_balance_before + amount_to_send); +} + // ============================================================================================== // ==== Bidirectional Transfer - Native + Teleportable Foreign Assets - Parachain<->AssetHub ==== // ============================================================================================== @@ -839,7 +970,7 @@ fn bidirectional_transfer_multiple_assets_between_penpal_and_asset_hub() { // xcm to be executed at dest let xcm_on_dest = Xcm(vec![ // since this is the last hop, we don't need to further use any assets previously - // reserved for fees (there are no further hops to cover transport fees for); we + // reserved for fees (there are no further hops to cover delivery fees for); we // RefundSurplus to get back any unspent fees RefundSurplus, DepositAsset { assets: Wild(All), beneficiary: t.args.beneficiary }, @@ -875,7 +1006,7 @@ fn bidirectional_transfer_multiple_assets_between_penpal_and_asset_hub() { // xcm to be executed at dest let xcm_on_dest = Xcm(vec![ // since this is the last hop, we don't need to further use any assets previously - // reserved for fees (there are no further hops to cover transport fees for); we + // reserved for fees (there are no further hops to cover delivery fees for); we // RefundSurplus to get back any unspent fees RefundSurplus, DepositAsset { assets: Wild(All), beneficiary: t.args.beneficiary }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 558eab13e5c7..707e8adc8a56 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -115,7 +115,7 @@ pub fn system_para_to_para_sender_assertions(t: SystemParaToParaTest) { assert_expected_events!( AssetHubWestend, vec![ - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm(pallet_xcm::Event::FeesPaid { .. }) => {}, ] ); @@ -274,7 +274,7 @@ fn system_para_to_para_assets_sender_assertions(t: SystemParaToParaTest) { t.args.dest.clone() ), }, - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm( pallet_xcm::Event::FeesPaid { .. } ) => {}, @@ -305,7 +305,7 @@ fn para_to_system_para_assets_sender_assertions(t: ParaToSystemParaTest) { owner: *owner == t.sender.account_id, balance: *balance == t.args.amount, }, - // Transport fees are paid + // Delivery fees are paid RuntimeEvent::PolkadotXcm( pallet_xcm::Event::FeesPaid { .. } ) => {}, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs index 592c2845255c..7e881a332a53 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/transact.rs @@ -46,7 +46,7 @@ fn transfer_and_transact_in_same_xcm( Transact { origin_kind: OriginKind::Xcm, call, fallback_max_weight: None }, ExpectTransactStatus(MaybeErrorCode::Success), // since this is the last hop, we don't need to further use any assets previously - // reserved for fees (there are no further hops to cover transport fees for); we + // reserved for fees (there are no further hops to cover delivery fees for); we // RefundSurplus to get back any unspent fees RefundSurplus, DepositAsset { assets: Wild(All), beneficiary }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index 33ab1e70b97b..a2a61660afff 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -16,7 +16,7 @@ use crate::tests::*; fn send_assets_over_bridge(send_fn: F) { - // fund the AHR's SA on BHR for paying bridge transport fees + // fund the AHR's SA on BHR for paying bridge delivery fees BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); // set XCM versions diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs index 1ae3a1b15805..70e7a7a3ddd3 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/register_bridged_assets.rs @@ -58,7 +58,7 @@ fn register_rococo_asset_on_wah_from_rah() { let destination = asset_hub_westend_location(); - // fund the RAH's SA on RBH for paying bridge transport fees + // fund the RAH's SA on RBH for paying bridge delivery fees BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); // set XCM versions diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs index 931a3128f826..116ec4dc0e55 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs @@ -65,7 +65,7 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { let native_token = Location::parent(); let amount = ASSET_HUB_ROCOCO_ED * 1_000; - // fund the AHR's SA on BHR for paying bridge transport fees + // fund the AHR's SA on BHR for paying bridge delivery fees BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), 10_000_000_000_000u128); // fund sender AssetHubRococo::fund_accounts(vec![(AssetHubRococoSender::get().into(), amount * 10)]); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index ab09517339db..cc90c10b54bc 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -17,7 +17,7 @@ use crate::{create_pool_with_native_on, tests::*}; use xcm::latest::AssetTransferFilter; fn send_assets_over_bridge(send_fn: F) { - // fund the AHW's SA on BHW for paying bridge transport fees + // fund the AHW's SA on BHW for paying bridge delivery fees BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); // set XCM versions @@ -592,7 +592,7 @@ fn do_send_pens_and_wnds_from_penpal_westend_via_ahw_to_asset_hub_rococo( // XCM to be executed at dest (Rococo Asset Hub) let xcm_on_dest = Xcm(vec![ // since this is the last hop, we don't need to further use any assets previously - // reserved for fees (there are no further hops to cover transport fees for); we + // reserved for fees (there are no further hops to cover delivery fees for); we // RefundSurplus to get back any unspent fees RefundSurplus, // deposit everything to final beneficiary diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs index 424f1e55956b..952fc35e6703 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/register_bridged_assets.rs @@ -82,7 +82,7 @@ fn register_asset_on_rah_from_wah(bridged_asset_at_rah: Location) { let destination = asset_hub_rococo_location(); - // fund the WAH's SA on WBH for paying bridge transport fees + // fund the WAH's SA on WBH for paying bridge delivery fees BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); // set XCM versions diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs index 787d7dc842cb..acce60b4fa76 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs @@ -65,7 +65,7 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { let native_token = Location::parent(); let amount = ASSET_HUB_WESTEND_ED * 1_000; - // fund the AHR's SA on BHR for paying bridge transport fees + // fund the AHR's SA on BHR for paying bridge delivery fees BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), 10_000_000_000_000u128); // fund sender AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get().into(), amount * 10)]); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs index 7831c8d66357..f6a3c53c4bf5 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/transact.rs @@ -52,7 +52,7 @@ fn transfer_and_transact_in_same_xcm( Transact { origin_kind: OriginKind::Xcm, call, fallback_max_weight: None }, ExpectTransactStatus(MaybeErrorCode::Success), // since this is the last hop, we don't need to further use any assets previously - // reserved for fees (there are no further hops to cover transport fees for); we + // reserved for fees (there are no further hops to cover delivery fees for); we // RefundSurplus to get back any unspent fees RefundSurplus, DepositAsset { assets: Wild(All), beneficiary }, diff --git a/polkadot/xcm/xcm-executor/src/lib.rs b/polkadot/xcm/xcm-executor/src/lib.rs index 64a32f488420..d0f18aea1ab3 100644 --- a/polkadot/xcm/xcm-executor/src/lib.rs +++ b/polkadot/xcm/xcm-executor/src/lib.rs @@ -1087,18 +1087,19 @@ impl XcmExecutor { DepositReserveAsset { assets, dest, xcm } => { let old_holding = self.holding.clone(); let result = Config::TransactionalProcessor::process(|| { - let maybe_delivery_fee_from_holding = if self.fees.is_empty() { - self.get_delivery_fee_from_holding(&assets, &dest, &xcm)? + let mut assets = self.holding.saturating_take(assets); + // When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from + // transferred assets. + let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw { + // Deduct and return the part of `assets` that shall be used for delivery fees. + self.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::DepositReserveAsset, &xcm)? } else { None }; - let mut message = Vec::with_capacity(xcm.len() + 2); - // now take assets to deposit (after having taken delivery fees) - let deposited = self.holding.saturating_take(assets); - tracing::trace!(target: "xcm::DepositReserveAsset", ?deposited, "Assets except delivery fee"); + tracing::trace!(target: "xcm::DepositReserveAsset", ?assets, "Assets except delivery fee"); Self::do_reserve_deposit_assets( - deposited, + assets, &dest, &mut message, Some(&self.context), @@ -1107,7 +1108,7 @@ impl XcmExecutor { message.push(ClearOrigin); // append custom instructions message.extend(xcm.0.into_iter()); - if let Some(delivery_fee) = maybe_delivery_fee_from_holding { + if let Some(delivery_fee) = maybe_delivery_fee_from_assets { // Put back delivery_fee in holding register to be charged by XcmSender. self.holding.subsume_assets(delivery_fee); } @@ -1122,7 +1123,15 @@ impl XcmExecutor { InitiateReserveWithdraw { assets, reserve, xcm } => { let old_holding = self.holding.clone(); let result = Config::TransactionalProcessor::process(|| { - let assets = self.holding.saturating_take(assets); + let mut assets = self.holding.saturating_take(assets); + // When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from + // transferred assets. + let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw { + // Deduct and return the part of `assets` that shall be used for delivery fees. + self.take_delivery_fee_from_assets(&mut assets, &reserve, FeeReason::InitiateReserveWithdraw, &xcm)? + } else { + None + }; let mut message = Vec::with_capacity(xcm.len() + 2); Self::do_reserve_withdraw_assets( assets, @@ -1134,6 +1143,10 @@ impl XcmExecutor { message.push(ClearOrigin); // append custom instructions message.extend(xcm.0.into_iter()); + if let Some(delivery_fee) = maybe_delivery_fee_from_assets { + // Put back delivery_fee in holding register to be charged by XcmSender. + self.holding.subsume_assets(delivery_fee); + } self.send(reserve, Xcm(message), FeeReason::InitiateReserveWithdraw)?; Ok(()) }); @@ -1145,13 +1158,25 @@ impl XcmExecutor { InitiateTeleport { assets, dest, xcm } => { let old_holding = self.holding.clone(); let result = Config::TransactionalProcessor::process(|| { - let assets = self.holding.saturating_take(assets); + let mut assets = self.holding.saturating_take(assets); + // When not using `PayFees`, nor `JIT_WITHDRAW`, delivery fees are paid from + // transferred assets. + let maybe_delivery_fee_from_assets = if self.fees.is_empty() && !self.fees_mode.jit_withdraw { + // Deduct and return the part of `assets` that shall be used for delivery fees. + self.take_delivery_fee_from_assets(&mut assets, &dest, FeeReason::InitiateTeleport, &xcm)? + } else { + None + }; let mut message = Vec::with_capacity(xcm.len() + 2); Self::do_teleport_assets(assets, &dest, &mut message, &self.context)?; // clear origin for subsequent custom instructions message.push(ClearOrigin); // append custom instructions message.extend(xcm.0.into_iter()); + if let Some(delivery_fee) = maybe_delivery_fee_from_assets { + // Put back delivery_fee in holding register to be charged by XcmSender. + self.holding.subsume_assets(delivery_fee); + } self.send(dest.clone(), Xcm(message), FeeReason::InitiateTeleport)?; Ok(()) }); @@ -1714,36 +1739,48 @@ impl XcmExecutor { Ok(()) } - /// Gets the necessary delivery fee to send a reserve transfer message to `destination` from - /// holding. + /// Take from transferred `assets` the delivery fee required to send an onward transfer message + /// to `destination`. /// /// Will be removed once the transition from `BuyExecution` to `PayFees` is complete. - fn get_delivery_fee_from_holding( - &mut self, - assets: &AssetFilter, + fn take_delivery_fee_from_assets( + &self, + assets: &mut AssetsInHolding, destination: &Location, + reason: FeeReason, xcm: &Xcm<()>, ) -> Result, XcmError> { - // we need to do this take/put cycle to solve wildcards and get exact assets to - // be weighed - let to_weigh = self.holding.saturating_take(assets.clone()); - self.holding.subsume_assets(to_weigh.clone()); + let to_weigh = assets.clone(); let to_weigh_reanchored = Self::reanchored(to_weigh, &destination, None); - let mut message_to_weigh = vec![ReserveAssetDeposited(to_weigh_reanchored), ClearOrigin]; + let remote_instruction = match reason { + FeeReason::DepositReserveAsset => ReserveAssetDeposited(to_weigh_reanchored), + FeeReason::InitiateReserveWithdraw => WithdrawAsset(to_weigh_reanchored), + FeeReason::InitiateTeleport => ReceiveTeleportedAsset(to_weigh_reanchored), + _ => { + tracing::debug!( + target: "xcm::take_delivery_fee_from_assets", + "Unexpected delivery fee reason", + ); + return Err(XcmError::NotHoldingFees); + }, + }; + let mut message_to_weigh = Vec::with_capacity(xcm.len() + 2); + message_to_weigh.push(remote_instruction); + message_to_weigh.push(ClearOrigin); message_to_weigh.extend(xcm.0.clone().into_iter()); let (_, fee) = validate_send::(destination.clone(), Xcm(message_to_weigh))?; let maybe_delivery_fee = fee.get(0).map(|asset_needed_for_fees| { tracing::trace!( - target: "xcm::fees::DepositReserveAsset", + target: "xcm::fees::take_delivery_fee_from_assets", "Asset provided to pay for fees {:?}, asset required for delivery fees: {:?}", self.asset_used_in_buy_execution, asset_needed_for_fees, ); let asset_to_pay_for_fees = self.calculate_asset_for_delivery_fees(asset_needed_for_fees.clone()); // set aside fee to be charged by XcmSender - let delivery_fee = self.holding.saturating_take(asset_to_pay_for_fees.into()); - tracing::trace!(target: "xcm::fees::DepositReserveAsset", ?delivery_fee); + let delivery_fee = assets.saturating_take(asset_to_pay_for_fees.into()); + tracing::trace!(target: "xcm::fees::take_delivery_fee_from_assets", ?delivery_fee); delivery_fee }); Ok(maybe_delivery_fee) diff --git a/prdoc/pr_4834.prdoc b/prdoc/pr_4834.prdoc new file mode 100644 index 000000000000..b7c8b15cb073 --- /dev/null +++ b/prdoc/pr_4834.prdoc @@ -0,0 +1,15 @@ +title: "xcm-executor: take delivery fee from transferred assets if necessary" + +doc: + - audience: Runtime Dev + description: | + In asset transfers, as a last resort, XCM delivery fees are taken from + transferred assets rather than failing the transfer. + +crates: + - name: staging-xcm-executor + bump: patch + - name: snowbridge-router-primitives + bump: patch + - name: snowbridge-pallet-inbound-queue + bump: patch From 4fc9248629fe8cd913bd05025c7690f99d7203d7 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Tue, 10 Dec 2024 01:32:21 -0300 Subject: [PATCH 129/340] Add fallback_max_weight to snowbridge Transact (#6792) We removed the `require_weight_at_most` field and later changed it to `fallback_max_weight`. This was to have a fallback when sending a message to v4 chains, which happens in the small time window when chains are upgrading. We originally put no fallback for a message in snowbridge's inbound queue but we should have one. This PR adds it. --------- Co-authored-by: GitHub Action --- bridges/snowbridge/pallets/inbound-queue/src/test.rs | 4 ++-- .../snowbridge/primitives/router/src/inbound/mod.rs | 2 +- prdoc/pr_6792.prdoc | 11 +++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_6792.prdoc diff --git a/bridges/snowbridge/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs index 1e0bd8acc925..beb9f7614574 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/test.rs @@ -40,8 +40,8 @@ fn test_submit_happy_path() { .into(), nonce: 1, message_id: [ - 86, 101, 80, 125, 84, 10, 227, 145, 230, 209, 152, 38, 206, 251, 206, 208, 244, - 221, 22, 215, 1, 252, 79, 181, 99, 207, 166, 220, 98, 3, 81, 7, + 118, 166, 139, 182, 84, 52, 165, 189, 54, 14, 178, 73, 2, 228, 192, 97, 153, 201, + 4, 75, 151, 15, 82, 6, 164, 187, 162, 133, 26, 183, 186, 126, ], fee_burned: 110000000000, } diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index a7bd0dfc1234..bc5d401cd4f7 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -279,7 +279,7 @@ where // Call create_asset on foreign assets pallet. Transact { origin_kind: OriginKind::Xcm, - fallback_max_weight: None, + fallback_max_weight: Some(Weight::from_parts(400_000_000, 8_000)), call: ( create_call_index, asset_id, diff --git a/prdoc/pr_6792.prdoc b/prdoc/pr_6792.prdoc new file mode 100644 index 000000000000..80982a34b3e8 --- /dev/null +++ b/prdoc/pr_6792.prdoc @@ -0,0 +1,11 @@ +title: Add fallback_max_weight to snowbridge Transact +doc: +- audience: Runtime Dev + description: |- + We removed the `require_weight_at_most` field and later changed it to `fallback_max_weight`. + This was to have a fallback when sending a message to v4 chains, which happens in the small time window when chains are upgrading. + We originally put no fallback for a message in snowbridge's inbound queue but we should have one. + This PR adds it. +crates: +- name: snowbridge-router-primitives + bump: patch From 3fb99c0b48b59fd49e14b853e278989d9ca52483 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 10 Dec 2024 05:44:45 +0100 Subject: [PATCH 130/340] Add fallback_weight to the log (#6782) Co-authored-by: Francisco Aguirre --- polkadot/xcm/src/v4/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/polkadot/xcm/src/v4/mod.rs b/polkadot/xcm/src/v4/mod.rs index bbf5ca049228..a0ce551b7608 100644 --- a/polkadot/xcm/src/v4/mod.rs +++ b/polkadot/xcm/src/v4/mod.rs @@ -1320,12 +1320,14 @@ impl TryFrom> for Instructi let require_weight_at_most = match call.take_decoded() { Ok(decoded) => decoded.get_dispatch_info().call_weight, Err(error) => { - log::error!( + let fallback_weight = fallback_max_weight.unwrap_or(Weight::MAX); + log::debug!( target: "xcm::versions::v5Tov4", - "Couldn't decode call in Transact: {:?}, using fallback weight.", + "Couldn't decode call in Transact: {:?}, using fallback weight: {:?}", error, + fallback_weight, ); - fallback_max_weight.unwrap_or(Weight::MAX) + fallback_weight }, }; Self::Transact { origin_kind, require_weight_at_most, call: call.into() } From 8f4b99cffabbe9edaa9c29f451e45cfdb4674adc Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 10 Dec 2024 06:37:57 +0100 Subject: [PATCH 131/340] Bridges - revert-back congestion mechanism (#6781) Closes: https://github.com/paritytech/polkadot-sdk/issues/5551 ## Description With [permissionless lanes PR#4949](https://github.com/paritytech/polkadot-sdk/pull/4949), the congestion mechanism based on sending `Transact(report_bridge_status(is_congested))` from `pallet-xcm-bridge-hub` to `pallet-xcm-bridge-hub-router` was replaced with a congestion mechanism that relied on monitoring XCMP queues. However, this approach could cause issues, such as suspending the entire XCMP queue instead of isolating the affected bridge. This PR reverts back to using `report_bridge_status` as before. ## TODO - [x] benchmarks - [x] prdoc ## Follow-up https://github.com/paritytech/polkadot-sdk/pull/6231 --------- Co-authored-by: GitHub Action Co-authored-by: command-bot <> Co-authored-by: Adrian Catangiu --- Cargo.lock | 7 + .../chains/chain-asset-hub-rococo/Cargo.toml | 6 + .../chains/chain-asset-hub-rococo/src/lib.rs | 25 ++ .../chains/chain-asset-hub-westend/Cargo.toml | 6 + .../chains/chain-asset-hub-westend/src/lib.rs | 25 ++ .../xcm-bridge-hub-router/src/benchmarking.rs | 27 +- .../modules/xcm-bridge-hub-router/src/lib.rs | 230 ++++++++++++------ .../modules/xcm-bridge-hub-router/src/mock.rs | 1 + .../xcm-bridge-hub-router/src/weights.rs | 27 ++ bridges/modules/xcm-bridge-hub/Cargo.toml | 2 + .../modules/xcm-bridge-hub/src/exporter.rs | 115 +++++++-- bridges/modules/xcm-bridge-hub/src/lib.rs | 4 +- bridges/modules/xcm-bridge-hub/src/mock.rs | 150 ++++++++++-- bridges/primitives/xcm-bridge-hub/src/lib.rs | 5 + .../assets/asset-hub-rococo/src/lib.rs | 7 +- .../weights/pallet_xcm_bridge_hub_router.rs | 30 ++- .../assets/asset-hub-rococo/tests/tests.rs | 55 ++++- .../assets/asset-hub-westend/src/lib.rs | 9 +- .../weights/pallet_xcm_bridge_hub_router.rs | 32 ++- .../assets/asset-hub-westend/tests/tests.rs | 52 +++- .../test-utils/src/test_cases_over_bridge.rs | 5 +- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 + .../src/bridge_to_westend_config.rs | 43 +++- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 2 + .../src/bridge_to_rococo_config.rs | 43 +++- prdoc/pr_6781.prdoc | 28 +++ 26 files changed, 781 insertions(+), 157 deletions(-) create mode 100644 prdoc/pr_6781.prdoc diff --git a/Cargo.lock b/Cargo.lock index aa91d9284a10..007cd1f05ba7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1938,6 +1938,8 @@ dependencies = [ "frame-support 28.0.0", "parity-scale-codec", "scale-info", + "sp-core 28.0.0", + "staging-xcm 7.0.0", ] [[package]] @@ -1948,6 +1950,8 @@ dependencies = [ "frame-support 28.0.0", "parity-scale-codec", "scale-info", + "sp-core 28.0.0", + "staging-xcm 7.0.0", ] [[package]] @@ -2541,6 +2545,7 @@ dependencies = [ "bp-rococo", "bp-runtime 0.7.0", "bp-westend", + "bp-xcm-bridge-hub-router 0.6.0", "bridge-hub-common 0.1.0", "bridge-hub-test-utils 0.7.0", "bridge-runtime-common 0.7.0", @@ -2779,6 +2784,7 @@ dependencies = [ "bp-rococo", "bp-runtime 0.7.0", "bp-westend", + "bp-xcm-bridge-hub-router 0.6.0", "bridge-hub-common 0.1.0", "bridge-hub-test-utils 0.7.0", "bridge-runtime-common 0.7.0", @@ -15982,6 +15988,7 @@ dependencies = [ "bp-messages 0.7.0", "bp-runtime 0.7.0", "bp-xcm-bridge-hub 0.2.0", + "bp-xcm-bridge-hub-router 0.6.0", "frame-support 28.0.0", "frame-system 28.0.0", "log", diff --git a/bridges/chains/chain-asset-hub-rococo/Cargo.toml b/bridges/chains/chain-asset-hub-rococo/Cargo.toml index 363a869048aa..4eb93ab52bc9 100644 --- a/bridges/chains/chain-asset-hub-rococo/Cargo.toml +++ b/bridges/chains/chain-asset-hub-rococo/Cargo.toml @@ -19,10 +19,14 @@ scale-info = { features = ["derive"], workspace = true } # Substrate Dependencies frame-support = { workspace = true } +sp-core = { workspace = true } # Bridge Dependencies bp-xcm-bridge-hub-router = { workspace = true } +# Polkadot dependencies +xcm = { workspace = true } + [features] default = ["std"] std = [ @@ -30,4 +34,6 @@ std = [ "codec/std", "frame-support/std", "scale-info/std", + "sp-core/std", + "xcm/std", ] diff --git a/bridges/chains/chain-asset-hub-rococo/src/lib.rs b/bridges/chains/chain-asset-hub-rococo/src/lib.rs index de2e9ae856d1..4ff7b391acd0 100644 --- a/bridges/chains/chain-asset-hub-rococo/src/lib.rs +++ b/bridges/chains/chain-asset-hub-rococo/src/lib.rs @@ -18,10 +18,13 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + use codec::{Decode, Encode}; use scale_info::TypeInfo; pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; +use xcm::latest::prelude::*; /// `AssetHubRococo` Runtime `Call` enum. /// @@ -44,5 +47,27 @@ frame_support::parameter_types! { pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); } +/// Builds an (un)congestion XCM program with the `report_bridge_status` call for +/// `ToWestendXcmRouter`. +pub fn build_congestion_message( + bridge_id: sp_core::H256, + is_congested: bool, +) -> alloc::vec::Vec> { + alloc::vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + fallback_max_weight: Some(XcmBridgeHubRouterTransactCallMaxWeight::get()), + call: Call::ToWestendXcmRouter(XcmBridgeHubRouterCall::report_bridge_status { + bridge_id, + is_congested, + }) + .encode() + .into(), + }, + ExpectTransactStatus(MaybeErrorCode::Success), + ] +} + /// Identifier of AssetHubRococo in the Rococo relay chain. pub const ASSET_HUB_ROCOCO_PARACHAIN_ID: u32 = 1000; diff --git a/bridges/chains/chain-asset-hub-westend/Cargo.toml b/bridges/chains/chain-asset-hub-westend/Cargo.toml index 430d9b6116cf..22071399f4d1 100644 --- a/bridges/chains/chain-asset-hub-westend/Cargo.toml +++ b/bridges/chains/chain-asset-hub-westend/Cargo.toml @@ -19,10 +19,14 @@ scale-info = { features = ["derive"], workspace = true } # Substrate Dependencies frame-support = { workspace = true } +sp-core = { workspace = true } # Bridge Dependencies bp-xcm-bridge-hub-router = { workspace = true } +# Polkadot dependencies +xcm = { workspace = true } + [features] default = ["std"] std = [ @@ -30,4 +34,6 @@ std = [ "codec/std", "frame-support/std", "scale-info/std", + "sp-core/std", + "xcm/std", ] diff --git a/bridges/chains/chain-asset-hub-westend/src/lib.rs b/bridges/chains/chain-asset-hub-westend/src/lib.rs index 9de1c8809894..9d245e08f7cc 100644 --- a/bridges/chains/chain-asset-hub-westend/src/lib.rs +++ b/bridges/chains/chain-asset-hub-westend/src/lib.rs @@ -18,10 +18,13 @@ #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + use codec::{Decode, Encode}; use scale_info::TypeInfo; pub use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; +use xcm::latest::prelude::*; /// `AssetHubWestend` Runtime `Call` enum. /// @@ -44,5 +47,27 @@ frame_support::parameter_types! { pub const XcmBridgeHubRouterTransactCallMaxWeight: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(200_000_000, 6144); } +/// Builds an (un)congestion XCM program with the `report_bridge_status` call for +/// `ToRococoXcmRouter`. +pub fn build_congestion_message( + bridge_id: sp_core::H256, + is_congested: bool, +) -> alloc::vec::Vec> { + alloc::vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + fallback_max_weight: Some(XcmBridgeHubRouterTransactCallMaxWeight::get()), + call: Call::ToRococoXcmRouter(XcmBridgeHubRouterCall::report_bridge_status { + bridge_id, + is_congested, + }) + .encode() + .into(), + }, + ExpectTransactStatus(MaybeErrorCode::Success), + ] +} + /// Identifier of AssetHubWestend in the Westend relay chain. pub const ASSET_HUB_WESTEND_PARACHAIN_ID: u32 = 1000; diff --git a/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs b/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs index 3c4a10f82e7d..ff06a1e3c8c5 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/benchmarking.rs @@ -18,9 +18,9 @@ #![cfg(feature = "runtime-benchmarks")] -use crate::{DeliveryFeeFactor, MINIMAL_DELIVERY_FEE_FACTOR}; +use crate::{Bridge, BridgeState, Call, MINIMAL_DELIVERY_FEE_FACTOR}; use frame_benchmarking::{benchmarks_instance_pallet, BenchmarkError}; -use frame_support::traits::{Get, Hooks}; +use frame_support::traits::{EnsureOrigin, Get, Hooks, UnfilteredDispatchable}; use sp_runtime::traits::Zero; use xcm::prelude::*; @@ -45,16 +45,35 @@ pub trait Config: crate::Config { benchmarks_instance_pallet! { on_initialize_when_non_congested { - DeliveryFeeFactor::::put(MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR); + Bridge::::put(BridgeState { + is_congested: false, + delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR, + }); }: { crate::Pallet::::on_initialize(Zero::zero()) } on_initialize_when_congested { - DeliveryFeeFactor::::put(MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR); + Bridge::::put(BridgeState { + is_congested: false, + delivery_fee_factor: MINIMAL_DELIVERY_FEE_FACTOR + MINIMAL_DELIVERY_FEE_FACTOR, + }); let _ = T::ensure_bridged_target_destination()?; T::make_congested(); }: { crate::Pallet::::on_initialize(Zero::zero()) } + + report_bridge_status { + Bridge::::put(BridgeState::default()); + + let origin: T::RuntimeOrigin = T::BridgeHubOrigin::try_successful_origin().expect("expected valid BridgeHubOrigin"); + let bridge_id = Default::default(); + let is_congested = true; + + let call = Call::::report_bridge_status { bridge_id, is_congested }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(Bridge::::get().is_congested); + } } diff --git a/bridges/modules/xcm-bridge-hub-router/src/lib.rs b/bridges/modules/xcm-bridge-hub-router/src/lib.rs index fe8f5a2efdfb..7361696faba7 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/lib.rs @@ -30,9 +30,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; +pub use bp_xcm_bridge_hub_router::{BridgeState, XcmChannelStatusProvider}; use codec::Encode; use frame_support::traits::Get; +use sp_core::H256; use sp_runtime::{FixedPointNumber, FixedU128, Saturating}; use sp_std::vec::Vec; use xcm::prelude::*; @@ -98,6 +99,8 @@ pub mod pallet { /// Checks the XCM version for the destination. type DestinationVersion: GetVersion; + /// Origin of the sibling bridge hub that is allowed to report bridge status. + type BridgeHubOrigin: EnsureOrigin; /// Actual message sender (`HRMP` or `DMP`) to the sibling bridge hub location. type ToBridgeHubSender: SendXcm; /// Local XCM channel manager. @@ -120,95 +123,112 @@ pub mod pallet { return T::WeightInfo::on_initialize_when_congested() } + // if bridge has reported congestion, we don't change anything + let mut bridge = Self::bridge(); + if bridge.is_congested { + return T::WeightInfo::on_initialize_when_congested() + } + // if we can't decrease the delivery fee factor anymore, we don't change anything - let mut delivery_fee_factor = Self::delivery_fee_factor(); - if delivery_fee_factor == MINIMAL_DELIVERY_FEE_FACTOR { + if bridge.delivery_fee_factor == MINIMAL_DELIVERY_FEE_FACTOR { return T::WeightInfo::on_initialize_when_congested() } - let previous_factor = delivery_fee_factor; - delivery_fee_factor = - MINIMAL_DELIVERY_FEE_FACTOR.max(delivery_fee_factor / EXPONENTIAL_FEE_BASE); + let previous_factor = bridge.delivery_fee_factor; + bridge.delivery_fee_factor = + MINIMAL_DELIVERY_FEE_FACTOR.max(bridge.delivery_fee_factor / EXPONENTIAL_FEE_BASE); + log::info!( target: LOG_TARGET, "Bridge channel is uncongested. Decreased fee factor from {} to {}", previous_factor, - delivery_fee_factor, + bridge.delivery_fee_factor, ); Self::deposit_event(Event::DeliveryFeeFactorDecreased { - new_value: delivery_fee_factor, + new_value: bridge.delivery_fee_factor, }); - DeliveryFeeFactor::::put(delivery_fee_factor); + Bridge::::put(bridge); T::WeightInfo::on_initialize_when_non_congested() } } - /// Initialization value for the delivery fee factor. - #[pallet::type_value] - pub fn InitialFactor() -> FixedU128 { - MINIMAL_DELIVERY_FEE_FACTOR + #[pallet::call] + impl, I: 'static> Pallet { + /// Notification about congested bridge queue. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::report_bridge_status())] + pub fn report_bridge_status( + origin: OriginFor, + // this argument is not currently used, but to ease future migration, we'll keep it + // here + bridge_id: H256, + is_congested: bool, + ) -> DispatchResult { + let _ = T::BridgeHubOrigin::ensure_origin(origin)?; + + log::info!( + target: LOG_TARGET, + "Received bridge status from {:?}: congested = {}", + bridge_id, + is_congested, + ); + + Bridge::::mutate(|bridge| { + bridge.is_congested = is_congested; + }); + Ok(()) + } } - /// The number to multiply the base delivery fee by. + /// Bridge that we are using. /// - /// This factor is shared by all bridges, served by this pallet. For example, if this - /// chain (`Config::UniversalLocation`) opens two bridges ( - /// `X2(GlobalConsensus(Config::BridgedNetworkId::get()), Parachain(1000))` and - /// `X2(GlobalConsensus(Config::BridgedNetworkId::get()), Parachain(2000))`), then they - /// both will be sharing the same fee factor. This is because both bridges are sharing - /// the same local XCM channel with the child/sibling bridge hub, which we are using - /// to detect congestion: - /// - /// ```nocompile - /// ThisChain --- Local XCM channel --> Sibling Bridge Hub ------ - /// | | - /// | | - /// | | - /// Lane1 Lane2 - /// | | - /// | | - /// | | - /// \ / | - /// Parachain1 <-- Local XCM channel --- Remote Bridge Hub <------ - /// | - /// | - /// Parachain1 <-- Local XCM channel --------- - /// ``` - /// - /// If at least one of other channels is congested, the local XCM channel with sibling - /// bridge hub eventually becomes congested too. And we have no means to detect - which - /// bridge exactly causes the congestion. So the best solution here is not to make - /// any differences between all bridges, started by this chain. + /// **bridges-v1** assumptions: all outbound messages through this router are using single lane + /// and to single remote consensus. If there is some other remote consensus that uses the same + /// bridge hub, the separate pallet instance shall be used, In `v2` we'll have all required + /// primitives (lane-id aka bridge-id, derived from XCM locations) to support multiple bridges + /// by the same pallet instance. #[pallet::storage] - #[pallet::getter(fn delivery_fee_factor)] - pub type DeliveryFeeFactor, I: 'static = ()> = - StorageValue<_, FixedU128, ValueQuery, InitialFactor>; + #[pallet::getter(fn bridge)] + pub type Bridge, I: 'static = ()> = StorageValue<_, BridgeState, ValueQuery>; impl, I: 'static> Pallet { /// Called when new message is sent (queued to local outbound XCM queue) over the bridge. pub(crate) fn on_message_sent_to_bridge(message_size: u32) { - // if outbound channel is not congested, do nothing - if !T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get()) { - return - } + log::trace!( + target: LOG_TARGET, + "on_message_sent_to_bridge - message_size: {message_size:?}", + ); + let _ = Bridge::::try_mutate(|bridge| { + let is_channel_with_bridge_hub_congested = + T::LocalXcmChannelManager::is_congested(&T::SiblingBridgeHubLocation::get()); + let is_bridge_congested = bridge.is_congested; + + // if outbound queue is not congested AND bridge has not reported congestion, do + // nothing + if !is_channel_with_bridge_hub_congested && !is_bridge_congested { + return Err(()) + } + + // ok - we need to increase the fee factor, let's do that + let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024)) + .saturating_mul(MESSAGE_SIZE_FEE_BASE); + let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor); + let previous_factor = bridge.delivery_fee_factor; + bridge.delivery_fee_factor = + bridge.delivery_fee_factor.saturating_mul(total_factor); - // ok - we need to increase the fee factor, let's do that - let message_size_factor = FixedU128::from_u32(message_size.saturating_div(1024)) - .saturating_mul(MESSAGE_SIZE_FEE_BASE); - let total_factor = EXPONENTIAL_FEE_BASE.saturating_add(message_size_factor); - DeliveryFeeFactor::::mutate(|f| { - let previous_factor = *f; - *f = f.saturating_mul(total_factor); log::info!( target: LOG_TARGET, "Bridge channel is congested. Increased fee factor from {} to {}", previous_factor, - f, + bridge.delivery_fee_factor, ); - Self::deposit_event(Event::DeliveryFeeFactorIncreased { new_value: *f }); - *f + Self::deposit_event(Event::DeliveryFeeFactorIncreased { + new_value: bridge.delivery_fee_factor, + }); + Ok(()) }); } } @@ -310,9 +330,9 @@ impl, I: 'static> ExporterFor for Pallet { let message_size = message.encoded_size(); let message_fee = (message_size as u128).saturating_mul(T::ByteFee::get()); let fee_sum = base_fee.saturating_add(message_fee); - - let fee_factor = Self::delivery_fee_factor(); + let fee_factor = Self::bridge().delivery_fee_factor; let fee = fee_factor.saturating_mul_int(fee_sum); + let fee = if fee > 0 { Some((T::FeeAsset::get(), fee).into()) } else { None }; log::info!( @@ -427,24 +447,47 @@ mod tests { use frame_system::{EventRecord, Phase}; use sp_runtime::traits::One; + fn congested_bridge(delivery_fee_factor: FixedU128) -> BridgeState { + BridgeState { is_congested: true, delivery_fee_factor } + } + + fn uncongested_bridge(delivery_fee_factor: FixedU128) -> BridgeState { + BridgeState { is_congested: false, delivery_fee_factor } + } + #[test] fn initial_fee_factor_is_one() { run_test(|| { - assert_eq!(DeliveryFeeFactor::::get(), MINIMAL_DELIVERY_FEE_FACTOR); + assert_eq!( + Bridge::::get(), + uncongested_bridge(MINIMAL_DELIVERY_FEE_FACTOR), + ); }) } #[test] fn fee_factor_is_not_decreased_from_on_initialize_when_xcm_channel_is_congested() { run_test(|| { - DeliveryFeeFactor::::put(FixedU128::from_rational(125, 100)); + Bridge::::put(uncongested_bridge(FixedU128::from_rational(125, 100))); TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get()); // it should not decrease, because queue is congested - let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + let old_delivery = XcmBridgeHubRouter::bridge(); XcmBridgeHubRouter::on_initialize(One::one()); - assert_eq!(XcmBridgeHubRouter::delivery_fee_factor(), old_delivery_fee_factor); + assert_eq!(XcmBridgeHubRouter::bridge(), old_delivery); + assert_eq!(System::events(), vec![]); + }) + } + + #[test] + fn fee_factor_is_not_decreased_from_on_initialize_when_bridge_has_reported_congestion() { + run_test(|| { + Bridge::::put(congested_bridge(FixedU128::from_rational(125, 100))); + // it should not decrease, because bridge congested + let old_bridge = XcmBridgeHubRouter::bridge(); + XcmBridgeHubRouter::on_initialize(One::one()); + assert_eq!(XcmBridgeHubRouter::bridge(), old_bridge); assert_eq!(System::events(), vec![]); }) } @@ -453,16 +496,19 @@ mod tests { fn fee_factor_is_decreased_from_on_initialize_when_xcm_channel_is_uncongested() { run_test(|| { let initial_fee_factor = FixedU128::from_rational(125, 100); - DeliveryFeeFactor::::put(initial_fee_factor); + Bridge::::put(uncongested_bridge(initial_fee_factor)); - // it shold eventually decreased to one - while XcmBridgeHubRouter::delivery_fee_factor() > MINIMAL_DELIVERY_FEE_FACTOR { + // it should eventually decrease to one + while XcmBridgeHubRouter::bridge().delivery_fee_factor > MINIMAL_DELIVERY_FEE_FACTOR { XcmBridgeHubRouter::on_initialize(One::one()); } - // verify that it doesn't decreases anymore + // verify that it doesn't decrease anymore XcmBridgeHubRouter::on_initialize(One::one()); - assert_eq!(XcmBridgeHubRouter::delivery_fee_factor(), MINIMAL_DELIVERY_FEE_FACTOR); + assert_eq!( + XcmBridgeHubRouter::bridge(), + uncongested_bridge(MINIMAL_DELIVERY_FEE_FACTOR) + ); // check emitted event let first_system_event = System::events().first().cloned(); @@ -582,7 +628,7 @@ mod tests { // but when factor is larger than one, it increases the fee, so it becomes: // `(BASE_FEE + BYTE_FEE * msg_size) * F + HRMP_FEE` let factor = FixedU128::from_rational(125, 100); - DeliveryFeeFactor::::put(factor); + Bridge::::put(uncongested_bridge(factor)); let expected_fee = (FixedU128::saturating_from_integer(BASE_FEE + BYTE_FEE * (msg_size as u128)) * factor) @@ -598,7 +644,7 @@ mod tests { #[test] fn sent_message_doesnt_increase_factor_if_queue_is_uncongested() { run_test(|| { - let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + let old_bridge = XcmBridgeHubRouter::bridge(); assert_eq!( send_xcm::( Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]), @@ -609,7 +655,7 @@ mod tests { ); assert!(TestToBridgeHubSender::is_message_sent()); - assert_eq!(old_delivery_fee_factor, XcmBridgeHubRouter::delivery_fee_factor()); + assert_eq!(old_bridge, XcmBridgeHubRouter::bridge()); assert_eq!(System::events(), vec![]); }); @@ -620,7 +666,39 @@ mod tests { run_test(|| { TestLocalXcmChannelManager::make_congested(&SiblingBridgeHubLocation::get()); - let old_delivery_fee_factor = XcmBridgeHubRouter::delivery_fee_factor(); + let old_bridge = XcmBridgeHubRouter::bridge(); + assert_ok!(send_xcm::( + Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]), + vec![ClearOrigin].into(), + ) + .map(drop)); + + assert!(TestToBridgeHubSender::is_message_sent()); + assert!( + old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor + ); + + // check emitted event + let first_system_event = System::events().first().cloned(); + assert!(matches!( + first_system_event, + Some(EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::XcmBridgeHubRouter( + Event::DeliveryFeeFactorIncreased { .. } + ), + .. + }) + )); + }); + } + + #[test] + fn sent_message_increases_factor_if_bridge_has_reported_congestion() { + run_test(|| { + Bridge::::put(congested_bridge(MINIMAL_DELIVERY_FEE_FACTOR)); + + let old_bridge = XcmBridgeHubRouter::bridge(); assert_ok!(send_xcm::( Location::new(2, [GlobalConsensus(BridgedNetworkId::get()), Parachain(1000)]), vec![ClearOrigin].into(), @@ -628,7 +706,9 @@ mod tests { .map(drop)); assert!(TestToBridgeHubSender::is_message_sent()); - assert!(old_delivery_fee_factor < XcmBridgeHubRouter::delivery_fee_factor()); + assert!( + old_bridge.delivery_fee_factor < XcmBridgeHubRouter::bridge().delivery_fee_factor + ); // check emitted event let first_system_event = System::events().first().cloned(); diff --git a/bridges/modules/xcm-bridge-hub-router/src/mock.rs b/bridges/modules/xcm-bridge-hub-router/src/mock.rs index 095572883920..ac642e108c2a 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/mock.rs @@ -80,6 +80,7 @@ impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { type DestinationVersion = LatestOrNoneForLocationVersionChecker>; + type BridgeHubOrigin = frame_system::EnsureRoot; type ToBridgeHubSender = TestToBridgeHubSender; type LocalXcmChannelManager = TestLocalXcmChannelManager; diff --git a/bridges/modules/xcm-bridge-hub-router/src/weights.rs b/bridges/modules/xcm-bridge-hub-router/src/weights.rs index d9a0426fecaf..8f5012c9de26 100644 --- a/bridges/modules/xcm-bridge-hub-router/src/weights.rs +++ b/bridges/modules/xcm-bridge-hub-router/src/weights.rs @@ -52,6 +52,7 @@ use sp_std::marker::PhantomData; pub trait WeightInfo { fn on_initialize_when_non_congested() -> Weight; fn on_initialize_when_congested() -> Weight; + fn report_bridge_status() -> Weight; } /// Weights for `pallet_xcm_bridge_hub_router` that are generated using one of the Bridge testnets. @@ -85,6 +86,19 @@ impl WeightInfo for BridgeWeight { // Minimum execution time: 4_239 nanoseconds. Weight::from_parts(4_383_000, 3547).saturating_add(T::DbWeight::get().reads(1_u64)) } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `53` + // Estimated: `1502` + // Minimum execution time: 10_427 nanoseconds. + Weight::from_parts(10_682_000, 1502) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } } // For backwards compatibility and tests @@ -120,4 +134,17 @@ impl WeightInfo for () { // Minimum execution time: 4_239 nanoseconds. Weight::from_parts(4_383_000, 3547).saturating_add(RocksDbWeight::get().reads(1_u64)) } + /// Storage: `XcmBridgeHubRouter::Bridge` (r:1 w:1) + /// + /// Proof: `XcmBridgeHubRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: + /// 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `53` + // Estimated: `1502` + // Minimum execution time: 10_427 nanoseconds. + Weight::from_parts(10_682_000, 1502) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } diff --git a/bridges/modules/xcm-bridge-hub/Cargo.toml b/bridges/modules/xcm-bridge-hub/Cargo.toml index fe58b910a94e..251dcfb45bcb 100644 --- a/bridges/modules/xcm-bridge-hub/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub/Cargo.toml @@ -39,6 +39,7 @@ sp-io = { workspace = true } bp-runtime = { workspace = true } bp-header-chain = { workspace = true } pallet-xcm-bridge-hub-router = { workspace = true } +bp-xcm-bridge-hub-router = { workspace = true } polkadot-parachain-primitives = { workspace = true } [features] @@ -47,6 +48,7 @@ std = [ "bp-header-chain/std", "bp-messages/std", "bp-runtime/std", + "bp-xcm-bridge-hub-router/std", "bp-xcm-bridge-hub/std", "codec/std", "frame-support/std", diff --git a/bridges/modules/xcm-bridge-hub/src/exporter.rs b/bridges/modules/xcm-bridge-hub/src/exporter.rs index 5afb9f36bc94..93b6093b42af 100644 --- a/bridges/modules/xcm-bridge-hub/src/exporter.rs +++ b/bridges/modules/xcm-bridge-hub/src/exporter.rs @@ -364,7 +364,7 @@ mod tests { use bp_runtime::RangeInclusiveExt; use bp_xcm_bridge_hub::{Bridge, BridgeLocations, BridgeState}; - use frame_support::assert_ok; + use frame_support::{assert_ok, traits::EnsureOrigin}; use pallet_bridge_messages::InboundLaneStorage; use xcm_builder::{NetworkExportTable, UnpaidRemoteExporter}; use xcm_executor::traits::{export_xcm, ConvertLocation}; @@ -381,9 +381,8 @@ mod tests { BridgedUniversalDestination::get() } - fn open_lane() -> (BridgeLocations, TestLaneIdType) { + fn open_lane(origin: RuntimeOrigin) -> (BridgeLocations, TestLaneIdType) { // open expected outbound lane - let origin = OpenBridgeOrigin::sibling_parachain_origin(); let with = bridged_asset_hub_universal_location(); let locations = XcmOverBridge::bridge_locations_from_origin(origin, Box::new(with.into())).unwrap(); @@ -439,7 +438,7 @@ mod tests { } fn open_lane_and_send_regular_message() -> (BridgeId, TestLaneIdType) { - let (locations, lane_id) = open_lane(); + let (locations, lane_id) = open_lane(OpenBridgeOrigin::sibling_parachain_origin()); // now let's try to enqueue message using our `ExportXcm` implementation export_xcm::( @@ -473,7 +472,7 @@ mod tests { fn exporter_does_not_suspend_the_bridge_if_outbound_bridge_queue_is_not_congested() { run_test(|| { let (bridge_id, _) = open_lane_and_send_regular_message(); - assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); + assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -490,7 +489,7 @@ mod tests { } open_lane_and_send_regular_message(); - assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); + assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); }); } @@ -502,11 +501,11 @@ mod tests { open_lane_and_send_regular_message(); } - assert!(!TestLocalXcmChannelManager::is_bridge_suspened()); + assert!(!TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); open_lane_and_send_regular_message(); - assert!(TestLocalXcmChannelManager::is_bridge_suspened()); + assert!(TestLocalXcmChannelManager::is_bridge_suspended(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended); }); } @@ -523,7 +522,7 @@ mod tests { OUTBOUND_LANE_UNCONGESTED_THRESHOLD + 1, ); - assert!(!TestLocalXcmChannelManager::is_bridge_resumed()); + assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Suspended); }); } @@ -537,7 +536,7 @@ mod tests { OUTBOUND_LANE_UNCONGESTED_THRESHOLD, ); - assert!(!TestLocalXcmChannelManager::is_bridge_resumed()); + assert!(!TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -554,7 +553,7 @@ mod tests { OUTBOUND_LANE_UNCONGESTED_THRESHOLD, ); - assert!(TestLocalXcmChannelManager::is_bridge_resumed()); + assert!(TestLocalXcmChannelManager::is_bridge_resumed(&bridge_id)); assert_eq!(XcmOverBridge::bridge(&bridge_id).unwrap().state, BridgeState::Opened); }); } @@ -648,7 +647,10 @@ mod tests { let dest = Location::new(2, BridgedUniversalDestination::get()); // open bridge - let (_, expected_lane_id) = open_lane(); + let origin = OpenBridgeOrigin::sibling_parachain_origin(); + let origin_as_location = + OpenBridgeOriginOf::::try_origin(origin.clone()).unwrap(); + let (_, expected_lane_id) = open_lane(origin); // check before - no messages assert_eq!( @@ -662,18 +664,24 @@ mod tests { ); // send `ExportMessage(message)` by `UnpaidRemoteExporter`. - TestExportXcmWithXcmOverBridge::set_origin_for_execute(SiblingLocation::get()); + ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location); assert_ok!(send_xcm::< UnpaidRemoteExporter< NetworkExportTable, - TestExportXcmWithXcmOverBridge, + ExecuteXcmOverSendXcm, UniversalLocation, >, >(dest.clone(), Xcm::<()>::default())); + // we need to set `UniversalLocation` for `sibling_parachain_origin` for + // `XcmOverBridgeWrappedWithExportMessageRouterInstance`. + ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get())); // send `ExportMessage(message)` by `pallet_xcm_bridge_hub_router`. - TestExportXcmWithXcmOverBridge::set_origin_for_execute(SiblingLocation::get()); - assert_ok!(send_xcm::(dest.clone(), Xcm::<()>::default())); + ExecuteXcmOverSendXcm::set_origin_for_execute(SiblingLocation::get()); + assert_ok!(send_xcm::( + dest.clone(), + Xcm::<()>::default() + )); // check after - a message ready to be relayed assert_eq!( @@ -765,7 +773,7 @@ mod tests { ); // ok - let _ = open_lane(); + let _ = open_lane(OpenBridgeOrigin::sibling_parachain_origin()); let mut dest_wrapper = Some(bridged_relative_destination()); assert_ok!(XcmOverBridge::validate( BridgedRelayNetwork::get(), @@ -780,4 +788,77 @@ mod tests { assert_eq!(None, dest_wrapper); }); } + + #[test] + fn congestion_with_pallet_xcm_bridge_hub_router_works() { + run_test(|| { + // valid routable destination + let dest = Location::new(2, BridgedUniversalDestination::get()); + + fn router_bridge_state() -> pallet_xcm_bridge_hub_router::BridgeState { + pallet_xcm_bridge_hub_router::Bridge::< + TestRuntime, + XcmOverBridgeWrappedWithExportMessageRouterInstance, + >::get() + } + + // open two bridges + let origin = OpenBridgeOrigin::sibling_parachain_origin(); + let origin_as_location = + OpenBridgeOriginOf::::try_origin(origin.clone()).unwrap(); + let (bridge_1, expected_lane_id_1) = open_lane(origin); + + // we need to set `UniversalLocation` for `sibling_parachain_origin` for + // `XcmOverBridgeWrappedWithExportMessageRouterInstance`. + ExportMessageOriginUniversalLocation::set(Some(SiblingUniversalLocation::get())); + + // check before + // bridges are opened + assert_eq!( + XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, + BridgeState::Opened + ); + + // the router is uncongested + assert!(!router_bridge_state().is_congested); + assert!(!TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id())); + assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id())); + + // make bridges congested with sending too much messages + for _ in 1..(OUTBOUND_LANE_CONGESTED_THRESHOLD + 2) { + // send `ExportMessage(message)` by `pallet_xcm_bridge_hub_router`. + ExecuteXcmOverSendXcm::set_origin_for_execute(origin_as_location.clone()); + assert_ok!(send_xcm::( + dest.clone(), + Xcm::<()>::default() + )); + } + + // checks after + // bridges are suspended + assert_eq!( + XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, + BridgeState::Suspended, + ); + // the router is congested + assert!(router_bridge_state().is_congested); + assert!(TestLocalXcmChannelManager::is_bridge_suspended(bridge_1.bridge_id())); + assert!(!TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id())); + + // make bridges uncongested to trigger resume signal + XcmOverBridge::on_bridge_messages_delivered( + expected_lane_id_1, + OUTBOUND_LANE_UNCONGESTED_THRESHOLD, + ); + + // bridge is again opened + assert_eq!( + XcmOverBridge::bridge(bridge_1.bridge_id()).unwrap().state, + BridgeState::Opened + ); + // the router is uncongested + assert!(!router_bridge_state().is_congested); + assert!(TestLocalXcmChannelManager::is_bridge_resumed(bridge_1.bridge_id())); + }) + } } diff --git a/bridges/modules/xcm-bridge-hub/src/lib.rs b/bridges/modules/xcm-bridge-hub/src/lib.rs index 1b2536598a20..682db811efa7 100644 --- a/bridges/modules/xcm-bridge-hub/src/lib.rs +++ b/bridges/modules/xcm-bridge-hub/src/lib.rs @@ -145,8 +145,8 @@ use bp_messages::{LaneState, MessageNonce}; use bp_runtime::{AccountIdOf, BalanceOf, RangeInclusiveExt}; -pub use bp_xcm_bridge_hub::{Bridge, BridgeId, BridgeState}; -use bp_xcm_bridge_hub::{BridgeLocations, BridgeLocationsError, LocalXcmChannelManager}; +pub use bp_xcm_bridge_hub::{Bridge, BridgeId, BridgeState, LocalXcmChannelManager}; +use bp_xcm_bridge_hub::{BridgeLocations, BridgeLocationsError}; use frame_support::{traits::fungible::MutateHold, DefaultNoBound}; use frame_system::Config as SystemConfig; use pallet_bridge_messages::{Config as BridgeMessagesConfig, LanesManagerError}; diff --git a/bridges/modules/xcm-bridge-hub/src/mock.rs b/bridges/modules/xcm-bridge-hub/src/mock.rs index 9f06b99ef6d5..d186507dab17 100644 --- a/bridges/modules/xcm-bridge-hub/src/mock.rs +++ b/bridges/modules/xcm-bridge-hub/src/mock.rs @@ -24,10 +24,10 @@ use bp_messages::{ }; use bp_runtime::{messages::MessageDispatchResult, Chain, ChainId, HashOf}; use bp_xcm_bridge_hub::{BridgeId, LocalXcmChannelManager}; -use codec::Encode; +use codec::{Decode, Encode}; use frame_support::{ assert_ok, derive_impl, parameter_types, - traits::{EnsureOrigin, Equals, Everything, OriginTrait}, + traits::{EnsureOrigin, Equals, Everything, Get, OriginTrait}, weights::RuntimeDbWeight, }; use polkadot_parachain_primitives::primitives::Sibling; @@ -44,7 +44,7 @@ use xcm_builder::{ InspectMessageQueues, NetworkExportTable, NetworkExportTableItem, ParentIsPreset, SiblingParachainConvertsVia, }; -use xcm_executor::XcmExecutor; +use xcm_executor::{traits::ConvertOrigin, XcmExecutor}; pub type AccountId = AccountId32; pub type Balance = u64; @@ -63,7 +63,7 @@ frame_support::construct_runtime! { Balances: pallet_balances::{Pallet, Event}, Messages: pallet_bridge_messages::{Pallet, Call, Event}, XcmOverBridge: pallet_xcm_bridge_hub::{Pallet, Call, HoldReason, Event}, - XcmOverBridgeRouter: pallet_xcm_bridge_hub_router, + XcmOverBridgeWrappedWithExportMessageRouter: pallet_xcm_bridge_hub_router = 57, } } @@ -208,17 +208,27 @@ impl pallet_xcm_bridge_hub::Config for TestRuntime { type BlobDispatcher = TestBlobDispatcher; } -impl pallet_xcm_bridge_hub_router::Config<()> for TestRuntime { +/// A router instance simulates a scenario where the router is deployed on a different chain than +/// the `MessageExporter`. This means that the router sends an `ExportMessage`. +pub type XcmOverBridgeWrappedWithExportMessageRouterInstance = (); +impl pallet_xcm_bridge_hub_router::Config + for TestRuntime +{ type RuntimeEvent = RuntimeEvent; type WeightInfo = (); - type UniversalLocation = UniversalLocation; + type UniversalLocation = ExportMessageOriginUniversalLocation; type SiblingBridgeHubLocation = BridgeHubLocation; type BridgedNetworkId = BridgedRelayNetwork; type Bridges = NetworkExportTable; type DestinationVersion = AlwaysLatest; - type ToBridgeHubSender = TestExportXcmWithXcmOverBridge; + // We convert to root `here` location with `BridgeHubLocationXcmOriginAsRoot` + type BridgeHubOrigin = frame_system::EnsureRoot; + // **Note**: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which + // calls the `ExportXcm` implementation of `pallet_xcm_bridge_hub` as the + // `MessageExporter`. + type ToBridgeHubSender = ExecuteXcmOverSendXcm; type LocalXcmChannelManager = TestLocalXcmChannelManager; type ByteFee = ConstU128<0>; @@ -230,7 +240,7 @@ impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; type XcmSender = (); type AssetTransactor = (); - type OriginConverter = (); + type OriginConverter = BridgeHubLocationXcmOriginAsRoot; type IsReserve = (); type IsTeleporter = (); type UniversalLocation = UniversalLocation; @@ -270,8 +280,8 @@ thread_local! { /// /// Note: The crucial part is that `ExportMessage` is processed by `XcmExecutor`, which calls the /// `ExportXcm` implementation of `pallet_xcm_bridge_hub` as `MessageExporter`. -pub struct TestExportXcmWithXcmOverBridge; -impl SendXcm for TestExportXcmWithXcmOverBridge { +pub struct ExecuteXcmOverSendXcm; +impl SendXcm for ExecuteXcmOverSendXcm { type Ticket = Xcm<()>; fn validate( @@ -298,7 +308,7 @@ impl SendXcm for TestExportXcmWithXcmOverBridge { Ok(hash) } } -impl InspectMessageQueues for TestExportXcmWithXcmOverBridge { +impl InspectMessageQueues for ExecuteXcmOverSendXcm { fn clear_messages() { todo!() } @@ -307,12 +317,51 @@ impl InspectMessageQueues for TestExportXcmWithXcmOverBridge { todo!() } } -impl TestExportXcmWithXcmOverBridge { +impl ExecuteXcmOverSendXcm { pub fn set_origin_for_execute(origin: Location) { EXECUTE_XCM_ORIGIN.with(|o| *o.borrow_mut() = Some(origin)); } } +/// A dynamic way to set different universal location for the origin which sends `ExportMessage`. +pub struct ExportMessageOriginUniversalLocation; +impl ExportMessageOriginUniversalLocation { + pub(crate) fn set(universal_location: Option) { + EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| *o.borrow_mut() = universal_location); + } +} +impl Get for ExportMessageOriginUniversalLocation { + fn get() -> InteriorLocation { + EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION.with(|o| { + o.borrow() + .clone() + .expect("`EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION` is not set!") + }) + } +} +thread_local! { + pub static EXPORT_MESSAGE_ORIGIN_UNIVERSAL_LOCATION: RefCell> = RefCell::new(None); +} + +pub struct BridgeHubLocationXcmOriginAsRoot( + sp_std::marker::PhantomData, +); +impl ConvertOrigin + for BridgeHubLocationXcmOriginAsRoot +{ + fn convert_origin( + origin: impl Into, + kind: OriginKind, + ) -> Result { + let origin = origin.into(); + if kind == OriginKind::Xcm && origin.eq(&BridgeHubLocation::get()) { + Ok(RuntimeOrigin::root()) + } else { + Err(origin) + } + } +} + /// Type for specifying how a `Location` can be converted into an `AccountId`. This is used /// when determining ownership of accounts for asset transacting and when attempting to use XCM /// `Transact` in order to determine the dispatch Origin. @@ -396,6 +445,9 @@ impl EnsureOrigin for OpenBridgeOrigin { } } +pub(crate) type OpenBridgeOriginOf = + >::OpenBridgeOrigin; + pub struct TestLocalXcmChannelManager; impl TestLocalXcmChannelManager { @@ -403,30 +455,82 @@ impl TestLocalXcmChannelManager { frame_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Congested", &true); } - pub fn is_bridge_suspened() -> bool { - frame_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Suspended") + fn suspended_key(bridge: &BridgeId) -> Vec { + [b"TestLocalXcmChannelManager.Suspended", bridge.encode().as_slice()].concat() + } + fn resumed_key(bridge: &BridgeId) -> Vec { + [b"TestLocalXcmChannelManager.Resumed", bridge.encode().as_slice()].concat() + } + + pub fn is_bridge_suspended(bridge: &BridgeId) -> bool { + frame_support::storage::unhashed::get_or_default(&Self::suspended_key(bridge)) } - pub fn is_bridge_resumed() -> bool { - frame_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Resumed") + pub fn is_bridge_resumed(bridge: &BridgeId) -> bool { + frame_support::storage::unhashed::get_or_default(&Self::resumed_key(bridge)) + } + + fn build_congestion_message(bridge: &BridgeId, is_congested: bool) -> Vec> { + use bp_xcm_bridge_hub_router::XcmBridgeHubRouterCall; + #[allow(clippy::large_enum_variant)] + #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, scale_info::TypeInfo)] + enum Call { + #[codec(index = 57)] + XcmOverBridgeWrappedWithExportMessageRouter(XcmBridgeHubRouterCall), + } + + sp_std::vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::Xcm, + fallback_max_weight: None, + call: Call::XcmOverBridgeWrappedWithExportMessageRouter( + XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: bridge.inner(), + is_congested, + } + ) + .encode() + .into(), + }, + ExpectTransactStatus(MaybeErrorCode::Success), + ] + } + + fn report_bridge_status( + local_origin: &Location, + bridge: &BridgeId, + is_congested: bool, + key: Vec, + ) -> Result<(), SendError> { + // send as BridgeHub would send to sibling chain + ExecuteXcmOverSendXcm::set_origin_for_execute(BridgeHubLocation::get()); + let result = send_xcm::( + local_origin.clone(), + Self::build_congestion_message(&bridge, is_congested).into(), + ); + + if result.is_ok() { + frame_support::storage::unhashed::put(&key, &true); + } + + result.map(|_| ()) } } impl LocalXcmChannelManager for TestLocalXcmChannelManager { - type Error = (); + type Error = SendError; fn is_congested(_with: &Location) -> bool { frame_support::storage::unhashed::get_or_default(b"TestLocalXcmChannelManager.Congested") } - fn suspend_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> { - frame_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Suspended", &true); - Ok(()) + fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + Self::report_bridge_status(local_origin, &bridge, true, Self::suspended_key(&bridge)) } - fn resume_bridge(_local_origin: &Location, _bridge: BridgeId) -> Result<(), Self::Error> { - frame_support::storage::unhashed::put(b"TestLocalXcmChannelManager.Resumed", &true); - Ok(()) + fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + Self::report_bridge_status(local_origin, &bridge, false, Self::resumed_key(&bridge)) } } diff --git a/bridges/primitives/xcm-bridge-hub/src/lib.rs b/bridges/primitives/xcm-bridge-hub/src/lib.rs index 63beb1bc3041..471cf402c34f 100644 --- a/bridges/primitives/xcm-bridge-hub/src/lib.rs +++ b/bridges/primitives/xcm-bridge-hub/src/lib.rs @@ -87,6 +87,11 @@ impl BridgeId { .into(), ) } + + /// Access the inner representation. + pub fn inner(&self) -> H256 { + self.0 + } } impl core::fmt::Debug for BridgeId { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index b6f3ccd3901b..7e1fb247ad3c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -62,7 +62,8 @@ use frame_support::{ ord_parameter_types, parameter_types, traits::{ fungible, fungibles, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, ConstBool, - ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, InstanceFilter, TransformOrigin, + ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, InstanceFilter, + TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, BoundedVec, PalletId, @@ -936,6 +937,10 @@ impl pallet_xcm_bridge_hub_router::Config for Runtim type Bridges = xcm_config::bridging::NetworkExportTable; type DestinationVersion = PolkadotXcm; + type BridgeHubOrigin = frame_support::traits::EitherOfDiverse< + EnsureRoot, + EnsureXcm>, + >; type ToBridgeHubSender = XcmpQueue; type LocalXcmChannelManager = cumulus_pallet_xcmp_queue::bridging::InAndOutXcmpChannelStatusProvider; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs index 00ecf239428f..9a75428ada8b 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm_bridge_hub_router.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm_bridge_hub_router` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-acd6uxux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -52,14 +52,14 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: Some(4002), added: 4497, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) - /// Storage: `ToWestendXcmRouter::DeliveryFeeFactor` (r:1 w:1) - /// Proof: `ToWestendXcmRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `ToWestendXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToWestendXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) fn on_initialize_when_non_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `153` + // Measured: `154` // Estimated: `5487` - // Minimum execution time: 12_993_000 picoseconds. - Weight::from_parts(13_428_000, 0) + // Minimum execution time: 13_884_000 picoseconds. + Weight::from_parts(14_312_000, 0) .saturating_add(Weight::from_parts(0, 5487)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -72,9 +72,21 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh // Proof Size summary in bytes: // Measured: `144` // Estimated: `5487` - // Minimum execution time: 6_305_000 picoseconds. - Weight::from_parts(6_536_000, 0) + // Minimum execution time: 6_909_000 picoseconds. + Weight::from_parts(7_115_000, 0) .saturating_add(Weight::from_parts(0, 5487)) .saturating_add(T::DbWeight::get().reads(2)) } + /// Storage: `ToWestendXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToWestendXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `150` + // Estimated: `1502` + // Minimum execution time: 12_394_000 picoseconds. + Weight::from_parts(12_883_000, 0) + .saturating_add(Weight::from_parts(0, 1502)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs index d056405adff8..144934ecd4ab 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/tests/tests.rs @@ -27,7 +27,8 @@ use asset_hub_rococo_runtime::{ AllPalletsWithoutSystem, AssetConversion, AssetDeposit, Assets, Balances, Block, CollatorSelection, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, Runtime, RuntimeCall, - RuntimeEvent, RuntimeOrigin, SessionKeys, TrustBackedAssetsInstance, XcmpQueue, + RuntimeEvent, RuntimeOrigin, SessionKeys, ToWestendXcmRouterInstance, + TrustBackedAssetsInstance, XcmpQueue, }; use asset_test_utils::{ test_cases_over_bridge::TestBridgingConfig, CollatorSessionKey, CollatorSessionKeys, @@ -1242,6 +1243,58 @@ mod asset_hub_rococo_tests { ) } + #[test] + fn report_bridge_status_from_xcm_bridge_router_for_westend_works() { + asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + LocationToAccountId, + ToWestendXcmRouterInstance, + >( + collator_session_keys(), + bridging_to_asset_hub_westend, + || bp_asset_hub_rococo::build_congestion_message(Default::default(), true).into(), + || bp_asset_hub_rococo::build_congestion_message(Default::default(), false).into(), + ) + } + + #[test] + fn test_report_bridge_status_call_compatibility() { + // if this test fails, make sure `bp_asset_hub_rococo` has valid encoding + assert_eq!( + RuntimeCall::ToWestendXcmRouter( + pallet_xcm_bridge_hub_router::Call::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode(), + bp_asset_hub_rococo::Call::ToWestendXcmRouter( + bp_asset_hub_rococo::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode() + ); + } + + #[test] + fn check_sane_weight_report_bridge_status_for_westend() { + use pallet_xcm_bridge_hub_router::WeightInfo; + let actual = >::WeightInfo::report_bridge_status(); + let max_weight = bp_asset_hub_rococo::XcmBridgeHubRouterTransactCallMaxWeight::get(); + assert!( + actual.all_lte(max_weight), + "max_weight: {:?} should be adjusted to actual {:?}", + max_weight, + actual + ); + } + #[test] fn reserve_transfer_native_asset_to_non_teleport_para_works() { asset_test_utils::test_cases::reserve_transfer_native_asset_to_non_teleport_para_works::< diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 21368e9c2b4b..ffd54ce4c8ac 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -46,8 +46,8 @@ use frame_support::{ traits::{ fungible, fungibles, tokens::{imbalance::ResolveAssetTo, nonfungibles_v2::Inspect}, - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, InstanceFilter, - Nothing, TransformOrigin, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Equals, + InstanceFilter, Nothing, TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, BoundedVec, PalletId, @@ -59,6 +59,7 @@ use frame_system::{ use pallet_asset_conversion_tx_payment::SwapAssetAdapter; use pallet_nfts::{DestroyWitness, PalletFeatures}; use pallet_revive::{evm::runtime::EthExtra, AddressMapper}; +use pallet_xcm::EnsureXcm; use parachains_common::{ impls::DealWithFees, message_queue::*, AccountId, AssetIdForTrustBackedAssets, AuraId, Balance, BlockNumber, CollectionId, Hash, Header, ItemId, Nonce, Signature, AVERAGE_ON_INITIALIZE_RATIO, @@ -930,6 +931,10 @@ impl pallet_xcm_bridge_hub_router::Config for Runtime type Bridges = xcm_config::bridging::NetworkExportTable; type DestinationVersion = PolkadotXcm; + type BridgeHubOrigin = frame_support::traits::EitherOfDiverse< + EnsureRoot, + EnsureXcm>, + >; type ToBridgeHubSender = XcmpQueue; type LocalXcmChannelManager = cumulus_pallet_xcmp_queue::bridging::InAndOutXcmpChannelStatusProvider; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs index c0898012e9f3..78aa839deacd 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm_bridge_hub_router.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm_bridge_hub_router` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-15, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-acd6uxux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -52,14 +52,14 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh /// Proof: `XcmpQueue::InboundXcmpSuspended` (`max_values`: Some(1), `max_size`: Some(4002), added: 4497, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:0) /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) - /// Storage: `ToRococoXcmRouter::DeliveryFeeFactor` (r:1 w:1) - /// Proof: `ToRococoXcmRouter::DeliveryFeeFactor` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `ToRococoXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToRococoXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) fn on_initialize_when_non_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `225` + // Measured: `259` // Estimated: `5487` - // Minimum execution time: 13_483_000 picoseconds. - Weight::from_parts(13_862_000, 0) + // Minimum execution time: 14_643_000 picoseconds. + Weight::from_parts(14_992_000, 0) .saturating_add(Weight::from_parts(0, 5487)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -70,11 +70,23 @@ impl pallet_xcm_bridge_hub_router::WeightInfo for Weigh /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) fn on_initialize_when_congested() -> Weight { // Proof Size summary in bytes: - // Measured: `111` + // Measured: `144` // Estimated: `5487` - // Minimum execution time: 5_078_000 picoseconds. - Weight::from_parts(5_233_000, 0) + // Minimum execution time: 5_367_000 picoseconds. + Weight::from_parts(5_604_000, 0) .saturating_add(Weight::from_parts(0, 5487)) .saturating_add(T::DbWeight::get().reads(2)) } + /// Storage: `ToRococoXcmRouter::Bridge` (r:1 w:1) + /// Proof: `ToRococoXcmRouter::Bridge` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + fn report_bridge_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `150` + // Estimated: `1502` + // Minimum execution time: 12_562_000 picoseconds. + Weight::from_parts(12_991_000, 0) + .saturating_add(Weight::from_parts(0, 1502)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs index 109a5dd2c029..24b6d83ffae4 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/tests/tests.rs @@ -27,7 +27,7 @@ use asset_hub_westend_runtime::{ AllPalletsWithoutSystem, Assets, Balances, Block, ExistentialDeposit, ForeignAssets, ForeignAssetsInstance, MetadataDepositBase, MetadataDepositPerByte, ParachainSystem, PolkadotXcm, Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, SessionKeys, - TrustBackedAssetsInstance, XcmpQueue, + ToRococoXcmRouterInstance, TrustBackedAssetsInstance, XcmpQueue, }; pub use asset_hub_westend_runtime::{AssetConversion, AssetDeposit, CollatorSelection, System}; use asset_test_utils::{ @@ -1250,6 +1250,56 @@ fn receive_reserve_asset_deposited_roc_from_asset_hub_rococo_fees_paid_by_suffic ) } +#[test] +fn report_bridge_status_from_xcm_bridge_router_for_rococo_works() { + asset_test_utils::test_cases_over_bridge::report_bridge_status_from_xcm_bridge_router_works::< + Runtime, + AllPalletsWithoutSystem, + XcmConfig, + LocationToAccountId, + ToRococoXcmRouterInstance, + >( + collator_session_keys(), + bridging_to_asset_hub_rococo, + || bp_asset_hub_westend::build_congestion_message(Default::default(), true).into(), + || bp_asset_hub_westend::build_congestion_message(Default::default(), false).into(), + ) +} + +#[test] +fn test_report_bridge_status_call_compatibility() { + // if this test fails, make sure `bp_asset_hub_rococo` has valid encoding + assert_eq!( + RuntimeCall::ToRococoXcmRouter(pallet_xcm_bridge_hub_router::Call::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + }) + .encode(), + bp_asset_hub_westend::Call::ToRococoXcmRouter( + bp_asset_hub_westend::XcmBridgeHubRouterCall::report_bridge_status { + bridge_id: Default::default(), + is_congested: true, + } + ) + .encode() + ) +} + +#[test] +fn check_sane_weight_report_bridge_status() { + use pallet_xcm_bridge_hub_router::WeightInfo; + let actual = >::WeightInfo::report_bridge_status(); + let max_weight = bp_asset_hub_westend::XcmBridgeHubRouterTransactCallMaxWeight::get(); + assert!( + actual.all_lte(max_weight), + "max_weight: {:?} should be adjusted to actual {:?}", + max_weight, + actual + ); +} + #[test] fn change_xcm_bridge_hub_router_byte_fee_by_governance_works() { asset_test_utils::test_cases::change_storage_constant_by_governance_works::< diff --git a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs index 4f144e24aa30..9b05f2d46dfb 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs +++ b/cumulus/parachains/runtimes/assets/test-utils/src/test_cases_over_bridge.rs @@ -551,10 +551,7 @@ pub fn report_bridge_status_from_xcm_bridge_router_works< Weight::zero(), ); assert_ok!(outcome.ensure_complete()); - assert_eq!( - is_congested, - <>::LocalXcmChannelManager as pallet_xcm_bridge_hub_router::XcmChannelStatusProvider>::is_congested(&local_bridge_hub_location) - ); + assert_eq!(is_congested, pallet_xcm_bridge_hub_router::Pallet::::bridge().is_congested); }; report_bridge_status(true); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index a7710783a1e0..c0d6db5ad500 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -98,6 +98,7 @@ bp-relayers = { workspace = true } bp-runtime = { workspace = true } bp-rococo = { workspace = true } bp-westend = { workspace = true } +bp-xcm-bridge-hub-router = { workspace = true } pallet-bridge-grandpa = { workspace = true } pallet-bridge-messages = { workspace = true } pallet-bridge-parachains = { workspace = true } @@ -143,6 +144,7 @@ std = [ "bp-rococo/std", "bp-runtime/std", "bp-westend/std", + "bp-xcm-bridge-hub-router/std", "bridge-hub-common/std", "bridge-runtime-common/std", "codec/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index 2710d033d64b..a14101eb454b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -24,14 +24,14 @@ use crate::{ weights, xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeWestendMessages, PolkadotXcm, Runtime, RuntimeEvent, - RuntimeHoldReason, XcmOverBridgeHubWestend, XcmRouter, + RuntimeHoldReason, XcmOverBridgeHubWestend, XcmRouter, XcmpQueue, }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, target_chain::FromBridgedChainMessagesProof, LegacyLaneId, }; use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; -use pallet_xcm_bridge_hub::XcmAsPlainPayload; +use pallet_xcm_bridge_hub::{BridgeId, XcmAsPlainPayload}; use frame_support::{parameter_types, traits::PalletInfoAccess}; use frame_system::{EnsureNever, EnsureRoot}; @@ -157,11 +157,46 @@ impl pallet_xcm_bridge_hub::Config for Runtime type AllowWithoutBridgeDeposit = RelayOrOtherSystemParachains; - // TODO:(bridges-v2) - add `LocalXcmChannelManager` impl - https://github.com/paritytech/parity-bridges-common/issues/3047 - type LocalXcmChannelManager = (); + type LocalXcmChannelManager = CongestionManager; type BlobDispatcher = FromWestendMessageBlobDispatcher; } +/// Implementation of `bp_xcm_bridge_hub::LocalXcmChannelManager` for congestion management. +pub struct CongestionManager; +impl pallet_xcm_bridge_hub::LocalXcmChannelManager for CongestionManager { + type Error = SendError; + + fn is_congested(with: &Location) -> bool { + // This is used to check the inbound bridge queue/messages to determine if they can be + // dispatched and sent to the sibling parachain. Therefore, checking outbound `XcmpQueue` + // is sufficient here. + use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; + cumulus_pallet_xcmp_queue::bridging::OutXcmpChannelStatusProvider::::is_congested( + with, + ) + } + + fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + // This bridge is intended for AH<>AH communication with a hard-coded/static lane, + // so `local_origin` is expected to represent only the local AH. + send_xcm::( + local_origin.clone(), + bp_asset_hub_rococo::build_congestion_message(bridge.inner(), true).into(), + ) + .map(|_| ()) + } + + fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + // This bridge is intended for AH<>AH communication with a hard-coded/static lane, + // so `local_origin` is expected to represent only the local AH. + send_xcm::( + local_origin.clone(), + bp_asset_hub_rococo::build_congestion_message(bridge.inner(), false).into(), + ) + .map(|_| ()) + } +} + #[cfg(feature = "runtime-benchmarks")] pub(crate) fn open_bridge_for_benchmarks( with: pallet_xcm_bridge_hub::LaneIdOf, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 91900c830ba6..f429a28a2e52 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -97,6 +97,7 @@ bp-relayers = { workspace = true } bp-runtime = { workspace = true } bp-rococo = { workspace = true } bp-westend = { workspace = true } +bp-xcm-bridge-hub-router = { workspace = true } pallet-bridge-grandpa = { workspace = true } pallet-bridge-messages = { workspace = true } pallet-bridge-parachains = { workspace = true } @@ -140,6 +141,7 @@ std = [ "bp-rococo/std", "bp-runtime/std", "bp-westend/std", + "bp-xcm-bridge-hub-router/std", "bridge-hub-common/std", "bridge-runtime-common/std", "codec/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index cd3465513144..24e5482b7b09 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -21,7 +21,7 @@ use crate::{ weights, xcm_config::UniversalLocation, AccountId, Balance, Balances, BridgeRococoMessages, PolkadotXcm, Runtime, RuntimeEvent, - RuntimeHoldReason, XcmOverBridgeHubRococo, XcmRouter, + RuntimeHoldReason, XcmOverBridgeHubRococo, XcmRouter, XcmpQueue, }; use bp_messages::{ source_chain::FromBridgedChainMessagesDeliveryProof, @@ -29,7 +29,7 @@ use bp_messages::{ }; use bp_parachains::SingleParaStoredHeaderDataBuilder; use bridge_hub_common::xcm_version::XcmVersionOfDestAndRemoteBridge; -use pallet_xcm_bridge_hub::XcmAsPlainPayload; +use pallet_xcm_bridge_hub::{BridgeId, XcmAsPlainPayload}; use frame_support::{ parameter_types, @@ -186,11 +186,46 @@ impl pallet_xcm_bridge_hub::Config for Runtime { type AllowWithoutBridgeDeposit = RelayOrOtherSystemParachains; - // TODO:(bridges-v2) - add `LocalXcmChannelManager` impl - https://github.com/paritytech/parity-bridges-common/issues/3047 - type LocalXcmChannelManager = (); + type LocalXcmChannelManager = CongestionManager; type BlobDispatcher = FromRococoMessageBlobDispatcher; } +/// Implementation of `bp_xcm_bridge_hub::LocalXcmChannelManager` for congestion management. +pub struct CongestionManager; +impl pallet_xcm_bridge_hub::LocalXcmChannelManager for CongestionManager { + type Error = SendError; + + fn is_congested(with: &Location) -> bool { + // This is used to check the inbound bridge queue/messages to determine if they can be + // dispatched and sent to the sibling parachain. Therefore, checking outbound `XcmpQueue` + // is sufficient here. + use bp_xcm_bridge_hub_router::XcmChannelStatusProvider; + cumulus_pallet_xcmp_queue::bridging::OutXcmpChannelStatusProvider::::is_congested( + with, + ) + } + + fn suspend_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + // This bridge is intended for AH<>AH communication with a hard-coded/static lane, + // so `local_origin` is expected to represent only the local AH. + send_xcm::( + local_origin.clone(), + bp_asset_hub_westend::build_congestion_message(bridge.inner(), true).into(), + ) + .map(|_| ()) + } + + fn resume_bridge(local_origin: &Location, bridge: BridgeId) -> Result<(), Self::Error> { + // This bridge is intended for AH<>AH communication with a hard-coded/static lane, + // so `local_origin` is expected to represent only the local AH. + send_xcm::( + local_origin.clone(), + bp_asset_hub_westend::build_congestion_message(bridge.inner(), false).into(), + ) + .map(|_| ()) + } +} + #[cfg(feature = "runtime-benchmarks")] pub(crate) fn open_bridge_for_benchmarks( with: pallet_xcm_bridge_hub::LaneIdOf, diff --git a/prdoc/pr_6781.prdoc b/prdoc/pr_6781.prdoc new file mode 100644 index 000000000000..8090be420341 --- /dev/null +++ b/prdoc/pr_6781.prdoc @@ -0,0 +1,28 @@ +title: Bridges - revert-back congestion mechanism + +doc: +- audience: Runtime Dev + description: |- + With [permissionless lanes PR#4949](https://github.com/paritytech/polkadot-sdk/pull/4949), the congestion mechanism based on sending `Transact(report_bridge_status(is_congested))` from `pallet-xcm-bridge-hub` to `pallet-xcm-bridge-hub-router` was replaced with a congestion mechanism that relied on monitoring XCMP queues. However, this approach could cause issues, such as suspending the entire XCMP queue instead of isolating the affected bridge. This PR reverts back to using `report_bridge_status` as before. + +crates: +- name: pallet-xcm-bridge-hub-router + bump: patch +- name: pallet-xcm-bridge-hub + bump: patch +- name: bp-xcm-bridge-hub + bump: patch +- name: bp-asset-hub-rococo + bump: patch +- name: bp-asset-hub-westend + bump: patch +- name: asset-hub-rococo-runtime + bump: patch +- name: asset-hub-westend-runtime + bump: patch +- name: asset-test-utils + bump: patch +- name: bridge-hub-rococo-runtime + bump: patch +- name: bridge-hub-westend-runtime + bump: patch From 311ea43886a875f350726af52ce4a8b0210ba55f Mon Sep 17 00:00:00 2001 From: Joseph Zhao <65984904+programskillforverification@users.noreply.github.com> Date: Tue, 10 Dec 2024 17:30:07 +0800 Subject: [PATCH 132/340] Remove AccountKeyring everywhere (#5899) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Close: #5858 --------- Co-authored-by: Bastian Köcher --- Cargo.lock | 32 ++--- .../pallets/inbound-queue/src/test.rs | 2 +- .../snowbridge/runtime/test-common/src/lib.rs | 6 +- .../bridge-hub-rococo/tests/snowbridge.rs | 6 +- .../bridge-hub-rococo/tests/tests.rs | 6 +- .../bridge-hub-westend/tests/snowbridge.rs | 6 +- .../bridge-hub-westend/tests/tests.rs | 6 +- .../src/test_cases/from_grandpa_chain.rs | 8 +- .../src/test_cases/from_parachain.rs | 8 +- .../test-utils/src/test_cases/helpers.rs | 8 +- cumulus/test/client/src/lib.rs | 6 +- .../packages/guides/first-runtime/src/lib.rs | 4 +- .../chain_spec_runtime/src/presets.rs | 6 +- polkadot/node/metrics/src/tests.rs | 2 +- polkadot/node/test/service/src/lib.rs | 4 +- .../adder/collator/tests/integration.rs | 2 +- .../undying/collator/tests/integration.rs | 2 +- polkadot/runtime/rococo/src/tests.rs | 2 +- polkadot/runtime/westend/src/tests.rs | 2 +- prdoc/pr_5899.prdoc | 52 +++++++++ substrate/bin/node/cli/src/service.rs | 6 +- .../basic-authorship/src/basic_authorship.rs | 10 +- substrate/client/basic-authorship/src/lib.rs | 2 +- substrate/client/chain-spec/src/chain_spec.rs | 14 +-- .../client/consensus/manual-seal/src/lib.rs | 2 +- substrate/client/network/test/src/lib.rs | 6 +- .../client/rpc-spec-v2/src/archive/tests.rs | 18 +-- .../rpc-spec-v2/src/chain_head/tests.rs | 46 ++++---- .../tests/transaction_broadcast_tests.rs | 2 +- .../transaction/tests/transaction_tests.rs | 2 +- substrate/client/rpc/src/author/tests.rs | 20 ++-- substrate/client/rpc/src/state/tests.rs | 12 +- substrate/client/service/src/lib.rs | 4 +- .../client/service/test/src/client/mod.rs | 110 +++++++++--------- .../fork_aware_txpool/revalidation_worker.rs | 2 +- .../src/fork_aware_txpool/tx_mem_pool.rs | 2 +- .../client/transaction-pool/src/graph/pool.rs | 2 +- .../src/single_state_txpool/revalidation.rs | 2 +- .../client/transaction-pool/tests/fatp.rs | 2 +- .../transaction-pool/tests/fatp_common/mod.rs | 2 +- .../transaction-pool/tests/fatp_limits.rs | 2 +- .../transaction-pool/tests/fatp_prios.rs | 2 +- .../client/transaction-pool/tests/pool.rs | 2 +- substrate/docs/Upgrading-2.0-to-3.0.md | 4 +- .../authorization-tx-extension/src/tests.rs | 16 +-- substrate/frame/src/lib.rs | 2 +- substrate/frame/support/Cargo.toml | 4 +- .../api/test/tests/runtime_calls.rs | 4 +- substrate/primitives/keyring/src/lib.rs | 9 -- substrate/test-utils/client/src/lib.rs | 4 +- .../test-utils/runtime/client/src/lib.rs | 2 +- .../runtime/client/src/trait_tests.rs | 38 +++--- substrate/test-utils/runtime/src/extrinsic.rs | 8 +- .../test-utils/runtime/src/genesismap.rs | 10 +- substrate/test-utils/runtime/src/lib.rs | 32 ++--- .../runtime/transaction-pool/src/lib.rs | 6 +- substrate/utils/frame/rpc/system/src/lib.rs | 16 +-- templates/minimal/runtime/src/lib.rs | 6 +- .../runtime/src/genesis_config_presets.rs | 18 +-- 59 files changed, 330 insertions(+), 291 deletions(-) create mode 100644 prdoc/pr_5899.prdoc diff --git a/Cargo.lock b/Cargo.lock index 007cd1f05ba7..8aa03954467d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1295,7 +1295,7 @@ dependencies = [ "futures-lite 2.3.0", "parking", "polling 3.4.0", - "rustix 0.38.25", + "rustix 0.38.21", "slab", "tracing", "windows-sys 0.52.0", @@ -1377,7 +1377,7 @@ dependencies = [ "cfg-if", "event-listener 5.3.1", "futures-lite 2.3.0", - "rustix 0.38.25", + "rustix 0.38.21", "tracing", ] @@ -1393,7 +1393,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.25", + "rustix 0.38.21", "signal-hook-registry", "slab", "windows-sys 0.52.0", @@ -7714,7 +7714,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ - "rustix 0.38.25", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -8972,7 +8972,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.9", - "rustix 0.38.25", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -10195,9 +10195,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lioness" @@ -19996,7 +19996,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", - "rustix 0.38.25", + "rustix 0.38.21", "tracing", "windows-sys 0.52.0", ] @@ -20315,7 +20315,7 @@ dependencies = [ "hex", "lazy_static", "procfs-core", - "rustix 0.38.25", + "rustix 0.38.21", ] [[package]] @@ -21715,14 +21715,14 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.11", + "linux-raw-sys 0.4.10", "windows-sys 0.48.0", ] @@ -26239,7 +26239,7 @@ dependencies = [ [[package]] name = "sp-crypto-ec-utils" version = "0.4.1" -source = "git+https://github.com/paritytech/polkadot-sdk#838a534da874cf6071fba1df07643c6c5b033ae0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ "ark-bls12-377", "ark-bls12-377-ext", @@ -27062,7 +27062,7 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" version = "11.0.0" -source = "git+https://github.com/paritytech/polkadot-sdk#838a534da874cf6071fba1df07643c6c5b033ae0" +source = "git+https://github.com/paritytech/polkadot-sdk#82912acb33a9030c0ef3bf590a34fca09b72dc5f" dependencies = [ "Inflector", "proc-macro-crate 1.3.1", @@ -29110,7 +29110,7 @@ dependencies = [ "cfg-if", "fastrand 2.1.0", "redox_syscall 0.4.1", - "rustix 0.38.25", + "rustix 0.38.21", "windows-sys 0.48.0", ] @@ -29140,7 +29140,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.25", + "rustix 0.38.21", "windows-sys 0.48.0", ] diff --git a/bridges/snowbridge/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs index beb9f7614574..053a341b54a0 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/test.rs @@ -5,7 +5,7 @@ use super::*; use frame_support::{assert_noop, assert_ok}; use hex_literal::hex; use snowbridge_core::{inbound::Proof, ChannelId}; -use sp_keyring::AccountKeyring as Keyring; +use sp_keyring::Sr25519Keyring as Keyring; use sp_runtime::DispatchError; use sp_std::convert::From; diff --git a/bridges/snowbridge/runtime/test-common/src/lib.rs b/bridges/snowbridge/runtime/test-common/src/lib.rs index dca5062ab310..5441dd822cac 100644 --- a/bridges/snowbridge/runtime/test-common/src/lib.rs +++ b/bridges/snowbridge/runtime/test-common/src/lib.rs @@ -13,7 +13,7 @@ use parachains_runtimes_test_utils::{ use snowbridge_core::{ChannelId, ParaId}; use snowbridge_pallet_ethereum_client_fixtures::*; use sp_core::{Get, H160, U256}; -use sp_keyring::AccountKeyring::*; +use sp_keyring::Sr25519Keyring::*; use sp_runtime::{traits::Header, AccountId32, DigestItem, SaturatedConversion, Saturating}; use xcm::latest::prelude::*; use xcm_executor::XcmExecutor; @@ -431,7 +431,7 @@ pub fn ethereum_extrinsic( collator_session_key: CollatorSessionKeys, runtime_para_id: u32, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, ::RuntimeCall, ) -> sp_runtime::DispatchOutcome, ) where @@ -567,7 +567,7 @@ pub fn ethereum_to_polkadot_message_extrinsics_work( collator_session_key: CollatorSessionKeys, runtime_para_id: u32, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, ::RuntimeCall, ) -> sp_runtime::DispatchOutcome, ) where diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs index 8be2993c68f4..d5baa1c71dfd 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs @@ -29,7 +29,7 @@ use frame_support::parameter_types; use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_pallet_ethereum_client::WeightInfo; use sp_core::H160; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, @@ -166,7 +166,7 @@ pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works() { } fn construct_extrinsic( - sender: sp_keyring::AccountKeyring, + sender: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); @@ -192,7 +192,7 @@ fn construct_extrinsic( } fn construct_and_apply_extrinsic( - origin: sp_keyring::AccountKeyring, + origin: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> sp_runtime::DispatchOutcome { let xt = construct_extrinsic(origin, call); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index 44e69c31a560..8d74b221a609 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -31,7 +31,7 @@ use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_core::ChannelId; use sp_consensus_aura::SlotDuration; use sp_core::{crypto::Ss58Codec, H160}; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, Perbill, @@ -45,7 +45,7 @@ parameter_types! { } fn construct_extrinsic( - sender: sp_keyring::AccountKeyring, + sender: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); @@ -72,7 +72,7 @@ fn construct_extrinsic( } fn construct_and_apply_extrinsic( - relayer_at_target: sp_keyring::AccountKeyring, + relayer_at_target: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> sp_runtime::DispatchOutcome { let xt = construct_extrinsic(relayer_at_target, call); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs index 1a1ce2a28ea3..d71400fa71b6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs @@ -30,7 +30,7 @@ use frame_support::parameter_types; use parachains_common::{AccountId, AuraId, Balance}; use snowbridge_pallet_ethereum_client::WeightInfo; use sp_core::H160; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, @@ -167,7 +167,7 @@ pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works() { } fn construct_extrinsic( - sender: sp_keyring::AccountKeyring, + sender: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); @@ -193,7 +193,7 @@ fn construct_extrinsic( } fn construct_and_apply_extrinsic( - origin: sp_keyring::AccountKeyring, + origin: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> sp_runtime::DispatchOutcome { let xt = construct_extrinsic(origin, call); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index d7e70ed769b1..9d32f28f4fc6 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -40,7 +40,7 @@ use frame_support::{dispatch::GetDispatchInfo, parameter_types, traits::ConstU8} use parachains_common::{AccountId, AuraId, Balance}; use sp_consensus_aura::SlotDuration; use sp_core::crypto::Ss58Codec; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use sp_runtime::{ generic::{Era, SignedPayload}, AccountId32, Perbill, @@ -77,7 +77,7 @@ parameter_types! { } fn construct_extrinsic( - sender: sp_keyring::AccountKeyring, + sender: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> UncheckedExtrinsic { let account_id = AccountId32::from(sender.public()); @@ -104,7 +104,7 @@ fn construct_extrinsic( } fn construct_and_apply_extrinsic( - relayer_at_target: sp_keyring::AccountKeyring, + relayer_at_target: sp_keyring::Sr25519Keyring, call: RuntimeCall, ) -> sp_runtime::DispatchOutcome { let xt = construct_extrinsic(relayer_at_target, call); diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs index 320f3030b60a..358c184c815d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_grandpa_chain.rs @@ -34,7 +34,7 @@ use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; use sp_core::Get; -use sp_keyring::AccountKeyring::*; +use sp_keyring::Sr25519Keyring::*; use sp_runtime::{traits::Header as HeaderT, AccountId32}; use xcm::latest::prelude::*; @@ -103,7 +103,7 @@ pub fn relayed_incoming_message_works( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, RuntimeCallOf, ) -> sp_runtime::DispatchOutcome, expect_rewards: bool, @@ -210,7 +210,7 @@ pub fn free_relay_extrinsic_works( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, RuntimeCallOf, ) -> sp_runtime::DispatchOutcome, expect_rewards: bool, @@ -344,7 +344,7 @@ pub fn complex_relay_extrinsic_works( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, RuntimeCallOf, ) -> sp_runtime::DispatchOutcome, ) where diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs index 1da901e0bcdf..d8fff55b4b50 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/from_parachain.rs @@ -36,7 +36,7 @@ use parachains_runtimes_test_utils::{ AccountIdOf, BasicParachainRuntime, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; use sp_core::Get; -use sp_keyring::AccountKeyring::*; +use sp_keyring::Sr25519Keyring::*; use sp_runtime::{traits::Header as HeaderT, AccountId32}; use xcm::latest::prelude::*; @@ -112,7 +112,7 @@ pub fn relayed_incoming_message_works( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, ::RuntimeCall, ) -> sp_runtime::DispatchOutcome, expect_rewards: bool, @@ -246,7 +246,7 @@ pub fn free_relay_extrinsic_works( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, ::RuntimeCall, ) -> sp_runtime::DispatchOutcome, expect_rewards: bool, @@ -414,7 +414,7 @@ pub fn complex_relay_extrinsic_works( local_relay_chain_id: NetworkId, prepare_configuration: impl Fn() -> LaneIdOf, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, ::RuntimeCall, ) -> sp_runtime::DispatchOutcome, ) where diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs index 03ddc4313b45..a99bda5bfdf4 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/src/test_cases/helpers.rs @@ -39,7 +39,7 @@ use parachains_runtimes_test_utils::{ mock_open_hrmp_channel, AccountIdOf, CollatorSessionKeys, RuntimeCallOf, SlotDurations, }; use sp_core::Get; -use sp_keyring::AccountKeyring::*; +use sp_keyring::Sr25519Keyring::*; use sp_runtime::{traits::TrailingZeroInput, AccountId32}; use xcm::latest::prelude::*; use xcm_executor::traits::ConvertLocation; @@ -264,7 +264,7 @@ pub fn relayed_incoming_message_works( sibling_parachain_id: u32, local_relay_chain_id: NetworkId, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, RuntimeCallOf, ) -> sp_runtime::DispatchOutcome, prepare_message_proof_import: impl FnOnce( @@ -374,9 +374,9 @@ pub fn relayed_incoming_message_works( /// Execute every call and verify its outcome. fn execute_and_verify_calls( - submitter: sp_keyring::AccountKeyring, + submitter: sp_keyring::Sr25519Keyring, construct_and_apply_extrinsic: fn( - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, RuntimeCallOf, ) -> sp_runtime::DispatchOutcome, calls_and_verifiers: CallsAndVerifiers, diff --git a/cumulus/test/client/src/lib.rs b/cumulus/test/client/src/lib.rs index 863a8fa93f6f..26cf02b3dea9 100644 --- a/cumulus/test/client/src/lib.rs +++ b/cumulus/test/client/src/lib.rs @@ -167,7 +167,7 @@ pub fn generate_extrinsic_with_pair( /// Generate an extrinsic from the provided function call, origin and [`Client`]. pub fn generate_extrinsic( client: &Client, - origin: sp_keyring::AccountKeyring, + origin: sp_keyring::Sr25519Keyring, function: impl Into, ) -> UncheckedExtrinsic { generate_extrinsic_with_pair(client, origin.into(), function, None) @@ -176,8 +176,8 @@ pub fn generate_extrinsic( /// Transfer some token from one account to another using a provided test [`Client`]. pub fn transfer( client: &Client, - origin: sp_keyring::AccountKeyring, - dest: sp_keyring::AccountKeyring, + origin: sp_keyring::Sr25519Keyring, + dest: sp_keyring::Sr25519Keyring, value: Balance, ) -> UncheckedExtrinsic { let function = RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { diff --git a/docs/sdk/packages/guides/first-runtime/src/lib.rs b/docs/sdk/packages/guides/first-runtime/src/lib.rs index 61ca550c8750..2ab060c8c43f 100644 --- a/docs/sdk/packages/guides/first-runtime/src/lib.rs +++ b/docs/sdk/packages/guides/first-runtime/src/lib.rs @@ -139,11 +139,11 @@ pub mod genesis_config_presets { let endowment = >::get().max(1) * 1000; build_struct_json_patch!(RuntimeGenesisConfig { balances: BalancesConfig { - balances: AccountKeyring::iter() + balances: Sr25519Keyring::iter() .map(|a| (a.to_account_id(), endowment)) .collect::>(), }, - sudo: SudoConfig { key: Some(AccountKeyring::Alice.to_account_id()) }, + sudo: SudoConfig { key: Some(Sr25519Keyring::Alice.to_account_id()) }, }) } diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/src/presets.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/src/presets.rs index 5918f2b8ccd5..5432d37e907d 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/src/presets.rs +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/src/presets.rs @@ -25,7 +25,7 @@ use alloc::vec; use frame_support::build_struct_json_patch; use serde_json::{json, to_string, Value}; use sp_application_crypto::Ss58Codec; -use sp_keyring::AccountKeyring; +use sp_keyring::Sr25519Keyring; /// A demo preset with strings only. pub const PRESET_1: &str = "preset_1"; @@ -70,7 +70,7 @@ fn preset_2() -> Value { some_integer: 200, some_enum: FooEnum::Data2(SomeFooData2 { values: vec![0x0c, 0x10] }) }, - bar: BarConfig { initial_account: Some(AccountKeyring::Ferdie.public().into()) }, + bar: BarConfig { initial_account: Some(Sr25519Keyring::Ferdie.public().into()) }, }) } @@ -80,7 +80,7 @@ fn preset_2() -> Value { fn preset_3() -> Value { json!({ "bar": { - "initialAccount": AccountKeyring::Alice.public().to_ss58check(), + "initialAccount": Sr25519Keyring::Alice.public().to_ss58check(), }, "foo": { "someEnum": FooEnum::Data1( diff --git a/polkadot/node/metrics/src/tests.rs b/polkadot/node/metrics/src/tests.rs index 4760138058eb..43dce0ec2ffe 100644 --- a/polkadot/node/metrics/src/tests.rs +++ b/polkadot/node/metrics/src/tests.rs @@ -21,7 +21,7 @@ use hyper::Uri; use hyper_util::{client::legacy::Client, rt::TokioExecutor}; use polkadot_primitives::metric_definitions::PARACHAIN_INHERENT_DATA_BITFIELDS_PROCESSED; use polkadot_test_service::{node_config, run_validator_node, test_prometheus_config}; -use sp_keyring::AccountKeyring::*; +use sp_keyring::Sr25519Keyring::*; use std::collections::HashMap; const DEFAULT_PROMETHEUS_PORT: u16 = 9616; diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index 6e09bb9e4310..f34bb62a7cf0 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -451,8 +451,8 @@ pub fn construct_extrinsic( /// Construct a transfer extrinsic. pub fn construct_transfer_extrinsic( client: &Client, - origin: sp_keyring::AccountKeyring, - dest: sp_keyring::AccountKeyring, + origin: sp_keyring::Sr25519Keyring, + dest: sp_keyring::Sr25519Keyring, value: Balance, ) -> UncheckedExtrinsic { let function = diff --git a/polkadot/parachain/test-parachains/adder/collator/tests/integration.rs b/polkadot/parachain/test-parachains/adder/collator/tests/integration.rs index 85abf8bf36b9..5d728517c4bb 100644 --- a/polkadot/parachain/test-parachains/adder/collator/tests/integration.rs +++ b/polkadot/parachain/test-parachains/adder/collator/tests/integration.rs @@ -23,7 +23,7 @@ #[tokio::test(flavor = "multi_thread")] async fn collating_using_adder_collator() { use polkadot_primitives::Id as ParaId; - use sp_keyring::AccountKeyring::*; + use sp_keyring::Sr25519Keyring::*; let mut builder = sc_cli::LoggerBuilder::new(""); builder.with_colors(false); diff --git a/polkadot/parachain/test-parachains/undying/collator/tests/integration.rs b/polkadot/parachain/test-parachains/undying/collator/tests/integration.rs index 8be535b9bb4c..b8e32b13bc9c 100644 --- a/polkadot/parachain/test-parachains/undying/collator/tests/integration.rs +++ b/polkadot/parachain/test-parachains/undying/collator/tests/integration.rs @@ -22,7 +22,7 @@ #[tokio::test(flavor = "multi_thread")] async fn collating_using_undying_collator() { use polkadot_primitives::Id as ParaId; - use sp_keyring::AccountKeyring::*; + use sp_keyring::Sr25519Keyring::*; let mut builder = sc_cli::LoggerBuilder::new(""); builder.with_colors(false); diff --git a/polkadot/runtime/rococo/src/tests.rs b/polkadot/runtime/rococo/src/tests.rs index 01eaad87e342..0b46caec5a35 100644 --- a/polkadot/runtime/rococo/src/tests.rs +++ b/polkadot/runtime/rococo/src/tests.rs @@ -22,7 +22,7 @@ use std::collections::HashSet; use crate::xcm_config::LocationConverter; use frame_support::traits::WhitelistedStorageKeys; use sp_core::{crypto::Ss58Codec, hexdisplay::HexDisplay}; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use xcm_runtime_apis::conversions::LocationToAccountHelper; #[test] diff --git a/polkadot/runtime/westend/src/tests.rs b/polkadot/runtime/westend/src/tests.rs index 02fd6b61496b..fcdaf7ff2de6 100644 --- a/polkadot/runtime/westend/src/tests.rs +++ b/polkadot/runtime/westend/src/tests.rs @@ -23,7 +23,7 @@ use approx::assert_relative_eq; use frame_support::traits::WhitelistedStorageKeys; use pallet_staking::EraPayout; use sp_core::{crypto::Ss58Codec, hexdisplay::HexDisplay}; -use sp_keyring::AccountKeyring::Alice; +use sp_keyring::Sr25519Keyring::Alice; use xcm_runtime_apis::conversions::LocationToAccountHelper; const MILLISECONDS_PER_HOUR: u64 = 60 * 60 * 1000; diff --git a/prdoc/pr_5899.prdoc b/prdoc/pr_5899.prdoc new file mode 100644 index 000000000000..fef810dd5f20 --- /dev/null +++ b/prdoc/pr_5899.prdoc @@ -0,0 +1,52 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Remove usage of AccountKeyring" + +doc: + - audience: Runtime Dev + description: | + Compared with AccountKeyring, Sr25519Keyring and Ed25519Keyring are more intuitive. + When both Sr25519Keyring and Ed25519Keyring are required, using AccountKeyring bring confusion. + There are two AccountKeyring definitions, it becomes more complex if export two AccountKeyring from frame. + +crates: + - name: frame-support + bump: patch + - name: sp-keyring + bump: major + - name: sc-service + bump: patch + - name: sc-chain-spec + bump: patch + - name: sc-rpc + bump: patch + - name: sc-transaction-pool + bump: patch + - name: sc-rpc-spec-v2 + bump: patch + - name: polkadot-node-metrics + bump: patch + - name: substrate-frame-rpc-system + bump: patch + - name: westend-runtime + bump: patch + - name: polkadot-sdk-frame + bump: patch + - name: rococo-runtime + bump: patch + - name: sc-basic-authorship + bump: patch + - name: bridge-hub-test-utils + bump: patch + - name: sc-consensus-manual-seal + bump: patch + - name: snowbridge-pallet-inbound-queue + bump: patch + - name: snowbridge-runtime-test-common + bump: patch + - name: bridge-hub-rococo-runtime + bump: patch + - name: bridge-hub-westend-runtime + bump: patch + diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index 5cde85ae5790..5f6806c235f6 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -871,7 +871,7 @@ mod tests { use sp_consensus::{BlockOrigin, Environment, Proposer}; use sp_core::crypto::Pair; use sp_inherents::InherentDataProvider; - use sp_keyring::AccountKeyring; + use sp_keyring::Sr25519Keyring; use sp_keystore::KeystorePtr; use sp_runtime::{ generic::{self, Digest, Era, SignedPayload}, @@ -906,8 +906,8 @@ mod tests { let mut slot = 1u64; // For the extrinsics factory - let bob = Arc::new(AccountKeyring::Bob.pair()); - let charlie = Arc::new(AccountKeyring::Charlie.pair()); + let bob = Arc::new(Sr25519Keyring::Bob.pair()); + let charlie = Arc::new(Sr25519Keyring::Charlie.pair()); let mut index = 0; sc_service_test::sync( diff --git a/substrate/client/basic-authorship/src/basic_authorship.rs b/substrate/client/basic-authorship/src/basic_authorship.rs index 848ac724c6b8..2096af1c25bb 100644 --- a/substrate/client/basic-authorship/src/basic_authorship.rs +++ b/substrate/client/basic-authorship/src/basic_authorship.rs @@ -910,8 +910,8 @@ mod tests { let extrinsics_num = 5; let extrinsics = std::iter::once( Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 100, nonce: 0, } @@ -1016,7 +1016,7 @@ mod tests { }; let huge = |who| { ExtrinsicBuilder::new_fill_block(Perbill::from_parts(HUGE)) - .signer(AccountKeyring::numeric(who)) + .signer(Sr25519Keyring::numeric(who)) .build() }; @@ -1082,13 +1082,13 @@ mod tests { let tiny = |who| { ExtrinsicBuilder::new_fill_block(Perbill::from_parts(TINY)) - .signer(AccountKeyring::numeric(who)) + .signer(Sr25519Keyring::numeric(who)) .nonce(1) .build() }; let huge = |who| { ExtrinsicBuilder::new_fill_block(Perbill::from_parts(HUGE)) - .signer(AccountKeyring::numeric(who)) + .signer(Sr25519Keyring::numeric(who)) .build() }; diff --git a/substrate/client/basic-authorship/src/lib.rs b/substrate/client/basic-authorship/src/lib.rs index adea7a3571dd..13c75fd08c3c 100644 --- a/substrate/client/basic-authorship/src/lib.rs +++ b/substrate/client/basic-authorship/src/lib.rs @@ -26,7 +26,7 @@ //! # use sp_runtime::generic::BlockId; //! # use std::{sync::Arc, time::Duration}; //! # use substrate_test_runtime_client::{ -//! # runtime::Transfer, AccountKeyring, +//! # runtime::Transfer, Sr25519Keyring, //! # DefaultTestClientBuilderExt, TestClientBuilderExt, //! # }; //! # use sc_transaction_pool::{BasicPool, FullChainApi}; diff --git a/substrate/client/chain-spec/src/chain_spec.rs b/substrate/client/chain-spec/src/chain_spec.rs index aa3c1ba3e6f1..fa161f1202ab 100644 --- a/substrate/client/chain-spec/src/chain_spec.rs +++ b/substrate/client/chain-spec/src/chain_spec.rs @@ -782,7 +782,7 @@ mod tests { use serde_json::{from_str, json, Value}; use sp_application_crypto::Ss58Codec; use sp_core::storage::well_known_keys; - use sp_keyring::AccountKeyring; + use sp_keyring::Sr25519Keyring; type TestSpec = ChainSpec; @@ -924,8 +924,8 @@ mod tests { }, "substrateTest": { "authorities": [ - AccountKeyring::Ferdie.public().to_ss58check(), - AccountKeyring::Alice.public().to_ss58check() + Sr25519Keyring::Ferdie.public().to_ss58check(), + Sr25519Keyring::Alice.public().to_ss58check() ], } })) @@ -980,8 +980,8 @@ mod tests { }, "substrateTest": { "authorities": [ - AccountKeyring::Ferdie.public().to_ss58check(), - AccountKeyring::Alice.public().to_ss58check() + Sr25519Keyring::Ferdie.public().to_ss58check(), + Sr25519Keyring::Alice.public().to_ss58check() ], } })) @@ -1083,8 +1083,8 @@ mod tests { "invalid_pallet": {}, "substrateTest": { "authorities": [ - AccountKeyring::Ferdie.public().to_ss58check(), - AccountKeyring::Alice.public().to_ss58check() + Sr25519Keyring::Ferdie.public().to_ss58check(), + Sr25519Keyring::Alice.public().to_ss58check() ], } })) diff --git a/substrate/client/consensus/manual-seal/src/lib.rs b/substrate/client/consensus/manual-seal/src/lib.rs index 39f8f8609d8d..af9bcc8d56d6 100644 --- a/substrate/client/consensus/manual-seal/src/lib.rs +++ b/substrate/client/consensus/manual-seal/src/lib.rs @@ -353,7 +353,7 @@ mod tests { use sp_inherents::InherentData; use sp_runtime::generic::{Digest, DigestItem}; use substrate_test_runtime_client::{ - AccountKeyring::*, DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt, + DefaultTestClientBuilderExt, Sr25519Keyring::*, TestClientBuilder, TestClientBuilderExt, }; use substrate_test_runtime_transaction_pool::{uxt, TestApi}; diff --git a/substrate/client/network/test/src/lib.rs b/substrate/client/network/test/src/lib.rs index 825481314c67..3cdf211e07f6 100644 --- a/substrate/client/network/test/src/lib.rs +++ b/substrate/client/network/test/src/lib.rs @@ -91,7 +91,7 @@ use sp_runtime::{ traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero}, Justification, Justifications, }; -use substrate_test_runtime_client::AccountKeyring; +use substrate_test_runtime_client::Sr25519Keyring; pub use substrate_test_runtime_client::{ runtime::{Block, ExtrinsicBuilder, Hash, Header, Transfer}, TestClient, TestClientBuilder, TestClientBuilderExt, @@ -475,8 +475,8 @@ where BlockOrigin::File, |mut builder| { let transfer = Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Alice.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Alice.into(), amount: 1, nonce, }; diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs index cddaafde6659..48cbbaa4934a 100644 --- a/substrate/client/rpc-spec-v2/src/archive/tests.rs +++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs @@ -117,8 +117,8 @@ async fn archive_body() { builder .push_transfer(runtime::Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42, nonce: 0, }) @@ -151,8 +151,8 @@ async fn archive_header() { builder .push_transfer(runtime::Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42, nonce: 0, }) @@ -249,8 +249,8 @@ async fn archive_hash_by_height() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -330,7 +330,7 @@ async fn archive_call() { client.import(BlockOrigin::Own, block_1.clone()).await.unwrap(); // Valid call. - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); // Hex encoded scale encoded bytes representing the call parameters. let call_parameters = hex_string(&alice_id.encode()); let result: MethodResult = api @@ -929,8 +929,8 @@ async fn archive_storage_diff_deleted_changes() { .unwrap(); builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs index 43b52175bd6d..3ec5e805ecd5 100644 --- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs @@ -506,8 +506,8 @@ async fn get_body() { .unwrap(); builder .push_transfer(runtime::Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42, nonce: 0, }) @@ -580,7 +580,7 @@ async fn call_runtime() { ); // Valid call. - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); // Hex encoded scale encoded bytes representing the call parameters. let call_parameters = hex_string(&alice_id.encode()); let response: MethodResponse = api @@ -670,7 +670,7 @@ async fn call_runtime_without_flag() { ); // Valid runtime call on a subscription started with `with_runtime` false. - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); let call_parameters = hex_string(&alice_id.encode()); let err = api .call::<_, serde_json::Value>( @@ -1256,7 +1256,7 @@ async fn unique_operation_ids() { assert!(op_ids.insert(operation_id)); // Valid `chainHead_v1_call` call. - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); let call_parameters = hex_string(&alice_id.encode()); let response: MethodResponse = api .call( @@ -1423,8 +1423,8 @@ async fn follow_generates_initial_blocks() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2046,8 +2046,8 @@ async fn follow_prune_best_block() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2217,8 +2217,8 @@ async fn follow_forks_pruned_block() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2233,8 +2233,8 @@ async fn follow_forks_pruned_block() { .unwrap(); block_builder .push_transfer(Transfer { - from: AccountKeyring::Bob.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Bob.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2379,8 +2379,8 @@ async fn follow_report_multiple_pruned_block() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2397,8 +2397,8 @@ async fn follow_report_multiple_pruned_block() { block_builder .push_transfer(Transfer { - from: AccountKeyring::Bob.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Bob.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -2883,7 +2883,7 @@ async fn ensure_operation_limits_works() { ); // The storage is finished and capacity must be released. - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); // Hex encoded scale encoded bytes representing the call parameters. let call_parameters = hex_string(&alice_id.encode()); let response: MethodResponse = api @@ -3537,7 +3537,7 @@ async fn chain_head_single_connection_context() { .unwrap(); assert_matches!(response, MethodResponse::LimitReached); - let alice_id = AccountKeyring::Alice.to_account_id(); + let alice_id = Sr25519Keyring::Alice.to_account_id(); // Hex encoded scale encoded bytes representing the call parameters. let call_parameters = hex_string(&alice_id.encode()); let response: MethodResponse = ChainHeadApiClient::::chain_head_unstable_call( @@ -3663,8 +3663,8 @@ async fn follow_unique_pruned_blocks() { let block_6_hash = import_block(client.clone(), block_2_f_hash, 2).await.hash(); // Import block 2 as best on the fork. let mut tx_alice_ferdie = Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }; @@ -3846,8 +3846,8 @@ async fn follow_report_best_block_of_a_known_block() { // imported block_builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs index efb3bd94ddbf..c2f11878e8fc 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_broadcast_tests.rs @@ -23,7 +23,7 @@ use jsonrpsee::{rpc_params, MethodsError as Error}; use sc_transaction_pool::{Options, PoolLimit}; use sc_transaction_pool_api::{ChainEvent, MaintainedTransactionPool, TransactionPool}; use std::sync::Arc; -use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; const MAX_TX_PER_CONNECTION: usize = 4; diff --git a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs index 53c5b8ce3895..879d51eaf5f3 100644 --- a/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs +++ b/substrate/client/rpc-spec-v2/src/transaction/tests/transaction_tests.rs @@ -26,7 +26,7 @@ use jsonrpsee::rpc_params; use sc_transaction_pool_api::{ChainEvent, MaintainedTransactionPool}; use sp_core::H256; use std::{sync::Arc, vec}; -use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; // Test helpers. diff --git a/substrate/client/rpc/src/author/tests.rs b/substrate/client/rpc/src/author/tests.rs index ab0b8bdab699..b1c899667624 100644 --- a/substrate/client/rpc/src/author/tests.rs +++ b/substrate/client/rpc/src/author/tests.rs @@ -39,15 +39,15 @@ use std::sync::Arc; use substrate_test_runtime_client::{ self, runtime::{Block, Extrinsic, ExtrinsicBuilder, SessionKeys, Transfer}, - AccountKeyring, Backend, Client, DefaultTestClientBuilderExt, TestClientBuilderExt, + Backend, Client, DefaultTestClientBuilderExt, Sr25519Keyring, TestClientBuilderExt, }; -fn uxt(sender: AccountKeyring, nonce: u64) -> Extrinsic { +fn uxt(sender: Sr25519Keyring, nonce: u64) -> Extrinsic { let tx = Transfer { amount: Default::default(), nonce, from: sender.into(), - to: AccountKeyring::Bob.into(), + to: Sr25519Keyring::Bob.into(), }; ExtrinsicBuilder::new_transfer(tx).build() } @@ -99,7 +99,7 @@ impl TestSetup { async fn author_submit_transaction_should_not_cause_error() { let api = TestSetup::into_rpc(); - let xt: Bytes = uxt(AccountKeyring::Alice, 1).encode().into(); + let xt: Bytes = uxt(Sr25519Keyring::Alice, 1).encode().into(); let extrinsic_hash: H256 = blake2_256(&xt).into(); let response: H256 = api.call("author_submitExtrinsic", [xt.clone()]).await.unwrap(); @@ -116,7 +116,7 @@ async fn author_should_watch_extrinsic() { let api = TestSetup::into_rpc(); let xt = to_hex( &ExtrinsicBuilder::new_call_with_priority(0) - .signer(AccountKeyring::Alice.into()) + .signer(Sr25519Keyring::Alice.into()) .build() .encode(), true, @@ -135,7 +135,7 @@ async fn author_should_watch_extrinsic() { // Replace the extrinsic and observe the subscription is notified. let (xt_replacement, xt_hash) = { let tx = ExtrinsicBuilder::new_call_with_priority(1) - .signer(AccountKeyring::Alice.into()) + .signer(Sr25519Keyring::Alice.into()) .build() .encode(); let hash = blake2_256(&tx); @@ -172,7 +172,7 @@ async fn author_should_return_watch_validation_error() { async fn author_should_return_pending_extrinsics() { let api = TestSetup::into_rpc(); - let xt_bytes: Bytes = uxt(AccountKeyring::Alice, 0).encode().into(); + let xt_bytes: Bytes = uxt(Sr25519Keyring::Alice, 0).encode().into(); api.call::<_, H256>("author_submitExtrinsic", [to_hex(&xt_bytes, true)]) .await .unwrap(); @@ -190,14 +190,14 @@ async fn author_should_remove_extrinsics() { // Submit three extrinsics, then remove two of them (will cause the third to be removed as well, // having a higher nonce) - let xt1_bytes = uxt(AccountKeyring::Alice, 0).encode(); + let xt1_bytes = uxt(Sr25519Keyring::Alice, 0).encode(); let xt1 = to_hex(&xt1_bytes, true); let xt1_hash: H256 = api.call("author_submitExtrinsic", [xt1]).await.unwrap(); - let xt2 = to_hex(&uxt(AccountKeyring::Alice, 1).encode(), true); + let xt2 = to_hex(&uxt(Sr25519Keyring::Alice, 1).encode(), true); let xt2_hash: H256 = api.call("author_submitExtrinsic", [xt2]).await.unwrap(); - let xt3 = to_hex(&uxt(AccountKeyring::Bob, 0).encode(), true); + let xt3 = to_hex(&uxt(Sr25519Keyring::Bob, 0).encode(), true); let xt3_hash: H256 = api.call("author_submitExtrinsic", [xt3]).await.unwrap(); assert_eq!(setup.pool.status().ready, 3); diff --git a/substrate/client/rpc/src/state/tests.rs b/substrate/client/rpc/src/state/tests.rs index 6b711f2425e9..c02f0d0b759b 100644 --- a/substrate/client/rpc/src/state/tests.rs +++ b/substrate/client/rpc/src/state/tests.rs @@ -228,8 +228,8 @@ async fn should_notify_about_storage_changes() { .unwrap(); builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42, nonce: 0, }) @@ -255,11 +255,11 @@ async fn should_send_initial_storage_changes_and_notifications() { let alice_balance_key = [ sp_crypto_hashing::twox_128(b"System"), sp_crypto_hashing::twox_128(b"Account"), - sp_crypto_hashing::blake2_128(&AccountKeyring::Alice.public()), + sp_crypto_hashing::blake2_128(&Sr25519Keyring::Alice.public()), ] .concat() .iter() - .chain(AccountKeyring::Alice.public().0.iter()) + .chain(Sr25519Keyring::Alice.public().0.iter()) .cloned() .collect::>(); @@ -281,8 +281,8 @@ async fn should_send_initial_storage_changes_and_notifications() { .unwrap(); builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42, nonce: 0, }) diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index b5a38d875e3b..2a3144a33e1a 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -596,8 +596,8 @@ mod tests { let transaction = Transfer { amount: 5, nonce: 0, - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), } .into_unchecked_extrinsic(); block_on(pool.submit_one(best.hash(), source, transaction.clone())).unwrap(); diff --git a/substrate/client/service/test/src/client/mod.rs b/substrate/client/service/test/src/client/mod.rs index ead90c4c65d8..ef5de93d64ca 100644 --- a/substrate/client/service/test/src/client/mod.rs +++ b/substrate/client/service/test/src/client/mod.rs @@ -48,8 +48,8 @@ use substrate_test_runtime_client::{ genesismap::{insert_genesis_block, GenesisStorageBuilder}, Block, BlockNumber, Digest, Hash, Header, RuntimeApi, Transfer, }, - AccountKeyring, BlockBuilderExt, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, - Sr25519Keyring, TestClientBuilder, TestClientBuilderExt, + BlockBuilderExt, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, Sr25519Keyring, + TestClientBuilder, TestClientBuilderExt, }; mod db; @@ -126,8 +126,8 @@ fn block1(genesis_hash: Hash, backend: &InMemoryBackend) -> Vec 1, genesis_hash, vec![Transfer { - from: AccountKeyring::One.into(), - to: AccountKeyring::Two.into(), + from: Sr25519Keyring::One.into(), + to: Sr25519Keyring::Two.into(), amount: 69 * DOLLARS, nonce: 0, }], @@ -158,7 +158,7 @@ fn finality_notification_check( fn construct_genesis_should_work_with_native() { let mut storage = GenesisStorageBuilder::new( vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], - vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], + vec![Sr25519Keyring::One.into(), Sr25519Keyring::Two.into()], 1000 * DOLLARS, ) .build(); @@ -189,7 +189,7 @@ fn construct_genesis_should_work_with_native() { fn construct_genesis_should_work_with_wasm() { let mut storage = GenesisStorageBuilder::new( vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], - vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], + vec![Sr25519Keyring::One.into(), Sr25519Keyring::Two.into()], 1000 * DOLLARS, ) .build(); @@ -223,14 +223,14 @@ fn client_initializes_from_genesis_ok() { assert_eq!( client .runtime_api() - .balance_of(client.chain_info().best_hash, AccountKeyring::Alice.into()) + .balance_of(client.chain_info().best_hash, Sr25519Keyring::Alice.into()) .unwrap(), 1000 * DOLLARS ); assert_eq!( client .runtime_api() - .balance_of(client.chain_info().best_hash, AccountKeyring::Ferdie.into()) + .balance_of(client.chain_info().best_hash, Sr25519Keyring::Ferdie.into()) .unwrap(), 0 * DOLLARS ); @@ -266,8 +266,8 @@ fn block_builder_works_with_transactions() { builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42 * DOLLARS, nonce: 0, }) @@ -301,14 +301,14 @@ fn block_builder_works_with_transactions() { assert_eq!( client .runtime_api() - .balance_of(client.chain_info().best_hash, AccountKeyring::Alice.into()) + .balance_of(client.chain_info().best_hash, Sr25519Keyring::Alice.into()) .unwrap(), 958 * DOLLARS ); assert_eq!( client .runtime_api() - .balance_of(client.chain_info().best_hash, AccountKeyring::Ferdie.into()) + .balance_of(client.chain_info().best_hash, Sr25519Keyring::Ferdie.into()) .unwrap(), 42 * DOLLARS ); @@ -325,8 +325,8 @@ fn block_builder_does_not_include_invalid() { builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 42 * DOLLARS, nonce: 0, }) @@ -334,8 +334,8 @@ fn block_builder_does_not_include_invalid() { assert!(builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 30 * DOLLARS, nonce: 0, }) @@ -491,8 +491,8 @@ fn uncles_with_multiple_forks() { // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41 * DOLLARS, nonce: 0, }) @@ -531,8 +531,8 @@ fn uncles_with_multiple_forks() { // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 1, }) @@ -549,8 +549,8 @@ fn uncles_with_multiple_forks() { // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -691,8 +691,8 @@ fn finality_target_on_longest_chain_with_multiple_forks() { // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41 * DOLLARS, nonce: 0, }) @@ -732,8 +732,8 @@ fn finality_target_on_longest_chain_with_multiple_forks() { // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 1, }) @@ -751,8 +751,8 @@ fn finality_target_on_longest_chain_with_multiple_forks() { // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -982,8 +982,8 @@ fn finality_target_with_best_not_on_longest_chain() { // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41 * DOLLARS, nonce: 0, }) @@ -1134,8 +1134,8 @@ fn importing_diverged_finalized_block_should_trigger_reorg() { .unwrap(); // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -1195,8 +1195,8 @@ fn finalizing_diverged_block_should_trigger_reorg() { .unwrap(); // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -1303,8 +1303,8 @@ fn finality_notifications_content() { .unwrap(); // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 0, }) @@ -1329,8 +1329,8 @@ fn finality_notifications_content() { .unwrap(); // needed to make sure B1 gets a different hash from A1 c1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 2 * DOLLARS, nonce: 0, }) @@ -1346,8 +1346,8 @@ fn finality_notifications_content() { // needed to make sure D3 gets a different hash from A3 d3.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 2 * DOLLARS, nonce: 0, }) @@ -1415,7 +1415,7 @@ fn state_reverted_on_reorg() { let current_balance = |client: &substrate_test_runtime_client::TestClient| { client .runtime_api() - .balance_of(client.chain_info().best_hash, AccountKeyring::Alice.into()) + .balance_of(client.chain_info().best_hash, Sr25519Keyring::Alice.into()) .unwrap() }; @@ -1428,8 +1428,8 @@ fn state_reverted_on_reorg() { .build() .unwrap(); a1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 10 * DOLLARS, nonce: 0, }) @@ -1443,8 +1443,8 @@ fn state_reverted_on_reorg() { .build() .unwrap(); b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 50 * DOLLARS, nonce: 0, }) @@ -1460,8 +1460,8 @@ fn state_reverted_on_reorg() { .build() .unwrap(); a2.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Charlie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Charlie.into(), amount: 10 * DOLLARS, nonce: 1, }) @@ -1530,8 +1530,8 @@ fn doesnt_import_blocks_that_revert_finality() { // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -1580,8 +1580,8 @@ fn doesnt_import_blocks_that_revert_finality() { // needed to make sure C1 gets a different hash from A1 and B1 c1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 2 * DOLLARS, nonce: 0, }) @@ -1788,8 +1788,8 @@ fn returns_status_for_pruned_blocks() { // b1 is created, but not imported b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) @@ -2191,8 +2191,8 @@ fn reorg_triggers_a_notification_even_for_sources_that_should_not_trigger_notifi // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1 * DOLLARS, nonce: 0, }) diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs index eb898c35a134..e1c65a08a70b 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/revalidation_worker.rs @@ -190,7 +190,7 @@ mod tests { }; use futures::executor::block_on; use substrate_test_runtime::{AccountId, Transfer, H256}; - use substrate_test_runtime_client::AccountKeyring::Alice; + use substrate_test_runtime_client::Sr25519Keyring::Alice; #[test] fn revalidation_queue_works() { let api = Arc::new(TestApi::default()); diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index 7b824d4653c2..989ae4425dc4 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -469,7 +469,7 @@ mod tx_mem_pool_tests { use super::*; use crate::{common::tests::TestApi, graph::ChainApi}; use substrate_test_runtime::{AccountId, Extrinsic, ExtrinsicBuilder, Transfer, H256}; - use substrate_test_runtime_client::AccountKeyring::*; + use substrate_test_runtime_client::Sr25519Keyring::*; fn uxt(nonce: u64) -> Extrinsic { crate::common::tests::uxt(Transfer { from: Alice.into(), diff --git a/substrate/client/transaction-pool/src/graph/pool.rs b/substrate/client/transaction-pool/src/graph/pool.rs index 23b71ce437b3..ff9cc1541af4 100644 --- a/substrate/client/transaction-pool/src/graph/pool.rs +++ b/substrate/client/transaction-pool/src/graph/pool.rs @@ -492,7 +492,7 @@ mod tests { use sp_runtime::transaction_validity::TransactionSource; use std::{collections::HashMap, time::Instant}; use substrate_test_runtime::{AccountId, ExtrinsicBuilder, Transfer, H256}; - use substrate_test_runtime_client::AccountKeyring::{Alice, Bob}; + use substrate_test_runtime_client::Sr25519Keyring::{Alice, Bob}; const SOURCE: TimedTransactionSource = TimedTransactionSource { source: TransactionSource::External, timestamp: None }; diff --git a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs index 74031b1e1c72..f22fa2ddabde 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs @@ -379,7 +379,7 @@ mod tests { }; use futures::executor::block_on; use substrate_test_runtime::{AccountId, Transfer, H256}; - use substrate_test_runtime_client::AccountKeyring::{Alice, Bob}; + use substrate_test_runtime_client::Sr25519Keyring::{Alice, Bob}; #[test] fn revalidation_queue_works() { diff --git a/substrate/client/transaction-pool/tests/fatp.rs b/substrate/client/transaction-pool/tests/fatp.rs index c51ca6e17663..8bf08122995c 100644 --- a/substrate/client/transaction-pool/tests/fatp.rs +++ b/substrate/client/transaction-pool/tests/fatp.rs @@ -30,7 +30,7 @@ use sc_transaction_pool_api::{ }; use sp_runtime::transaction_validity::InvalidTransaction; use std::{sync::Arc, time::Duration}; -use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; pub mod fatp_common; diff --git a/substrate/client/transaction-pool/tests/fatp_common/mod.rs b/substrate/client/transaction-pool/tests/fatp_common/mod.rs index aecd83360f1e..aaffebc0db0a 100644 --- a/substrate/client/transaction-pool/tests/fatp_common/mod.rs +++ b/substrate/client/transaction-pool/tests/fatp_common/mod.rs @@ -24,7 +24,7 @@ use sp_runtime::transaction_validity::TransactionSource; use std::sync::Arc; use substrate_test_runtime_client::{ runtime::{Block, Hash, Header}, - AccountKeyring::*, + Sr25519Keyring::*, }; use substrate_test_runtime_transaction_pool::{uxt, TestApi}; pub const LOG_TARGET: &str = "txpool"; diff --git a/substrate/client/transaction-pool/tests/fatp_limits.rs b/substrate/client/transaction-pool/tests/fatp_limits.rs index afd8183957a8..fb02b21ebc2b 100644 --- a/substrate/client/transaction-pool/tests/fatp_limits.rs +++ b/substrate/client/transaction-pool/tests/fatp_limits.rs @@ -29,7 +29,7 @@ use sc_transaction_pool_api::{ error::Error as TxPoolError, MaintainedTransactionPool, TransactionPool, TransactionStatus, }; use std::thread::sleep; -use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; #[test] diff --git a/substrate/client/transaction-pool/tests/fatp_prios.rs b/substrate/client/transaction-pool/tests/fatp_prios.rs index 41bc374b38f4..4ed9b4503861 100644 --- a/substrate/client/transaction-pool/tests/fatp_prios.rs +++ b/substrate/client/transaction-pool/tests/fatp_prios.rs @@ -24,7 +24,7 @@ use fatp_common::{new_best_block_event, TestPoolBuilder, LOG_TARGET, SOURCE}; use futures::{executor::block_on, FutureExt}; use sc_transaction_pool::ChainApi; use sc_transaction_pool_api::{MaintainedTransactionPool, TransactionPool, TransactionStatus}; -use substrate_test_runtime_client::AccountKeyring::*; +use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; #[test] diff --git a/substrate/client/transaction-pool/tests/pool.rs b/substrate/client/transaction-pool/tests/pool.rs index e556ba9875f1..20997606c607 100644 --- a/substrate/client/transaction-pool/tests/pool.rs +++ b/substrate/client/transaction-pool/tests/pool.rs @@ -40,8 +40,8 @@ use sp_runtime::{ use std::{collections::BTreeSet, pin::Pin, sync::Arc}; use substrate_test_runtime_client::{ runtime::{Block, Extrinsic, ExtrinsicBuilder, Hash, Header, Nonce, Transfer, TransferData}, - AccountKeyring::*, ClientBlockImportExt, + Sr25519Keyring::*, }; use substrate_test_runtime_transaction_pool::{uxt, TestApi}; diff --git a/substrate/docs/Upgrading-2.0-to-3.0.md b/substrate/docs/Upgrading-2.0-to-3.0.md index 1be41a34ef34..f6fc5cf4b079 100644 --- a/substrate/docs/Upgrading-2.0-to-3.0.md +++ b/substrate/docs/Upgrading-2.0-to-3.0.md @@ -1003,7 +1003,7 @@ modified your chain you should probably try to apply these patches: }; use sp_timestamp; - use sp_finality_tracker; - use sp_keyring::AccountKeyring; + use sp_keyring::Sr25519Keyring; use sc_service_test::TestNetNode; use crate::service::{new_full_base, new_light_base, NewFullBase}; - use sp_runtime::traits::IdentifyAccount; @@ -1034,7 +1034,7 @@ modified your chain you should probably try to apply these patches: + let mut slot = 1u64; // For the extrinsics factory - let bob = Arc::new(AccountKeyring::Bob.pair()); + let bob = Arc::new(Sr25519Keyring::Bob.pair()); @@ -528,14 +539,13 @@ mod tests { Ok((node, (inherent_data_providers, setup_handles.unwrap()))) }, diff --git a/substrate/frame/examples/authorization-tx-extension/src/tests.rs b/substrate/frame/examples/authorization-tx-extension/src/tests.rs index 8ca35c099556..5579e7a98416 100644 --- a/substrate/frame/examples/authorization-tx-extension/src/tests.rs +++ b/substrate/frame/examples/authorization-tx-extension/src/tests.rs @@ -24,7 +24,7 @@ use frame_support::{ pallet_prelude::{InvalidTransaction, TransactionValidityError}, }; use pallet_verify_signature::VerifySignature; -use sp_keyring::AccountKeyring; +use sp_keyring::Sr25519Keyring; use sp_runtime::{ generic::ExtensionVersion, traits::{Applyable, Checkable, IdentityLookup, TransactionExtension}, @@ -36,7 +36,7 @@ use crate::{extensions::AuthorizeCoownership, mock::*, pallet_assets}; #[test] fn create_asset_works() { new_test_ext().execute_with(|| { - let alice_keyring = AccountKeyring::Alice; + let alice_keyring = Sr25519Keyring::Alice; let alice_account = AccountId::from(alice_keyring.public()); // Simple call to create asset with Id `42`. let create_asset_call = @@ -99,9 +99,9 @@ fn create_asset_works() { #[test] fn create_coowned_asset_works() { new_test_ext().execute_with(|| { - let alice_keyring = AccountKeyring::Alice; - let bob_keyring = AccountKeyring::Bob; - let charlie_keyring = AccountKeyring::Charlie; + let alice_keyring = Sr25519Keyring::Alice; + let bob_keyring = Sr25519Keyring::Bob; + let charlie_keyring = Sr25519Keyring::Charlie; let alice_account = AccountId::from(alice_keyring.public()); let bob_account = AccountId::from(bob_keyring.public()); let charlie_account = AccountId::from(charlie_keyring.public()); @@ -189,9 +189,9 @@ fn create_coowned_asset_works() { #[test] fn inner_authorization_works() { new_test_ext().execute_with(|| { - let alice_keyring = AccountKeyring::Alice; - let bob_keyring = AccountKeyring::Bob; - let charlie_keyring = AccountKeyring::Charlie; + let alice_keyring = Sr25519Keyring::Alice; + let bob_keyring = Sr25519Keyring::Bob; + let charlie_keyring = Sr25519Keyring::Charlie; let charlie_account = AccountId::from(charlie_keyring.public()); // Simple call to create asset with Id `42`. let create_asset_call = diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 03d815e349df..8031ddf96e6a 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -391,7 +391,7 @@ pub mod runtime { LOCAL_TESTNET_RUNTIME_PRESET, }; pub use sp_inherents::{CheckInherentsResult, InherentData}; - pub use sp_keyring::AccountKeyring; + pub use sp_keyring::Sr25519Keyring; pub use sp_runtime::{ApplyExtrinsicResult, ExtrinsicInclusionMode}; } diff --git a/substrate/frame/support/Cargo.toml b/substrate/frame/support/Cargo.toml index d48c80510581..4348bd275605 100644 --- a/substrate/frame/support/Cargo.toml +++ b/substrate/frame/support/Cargo.toml @@ -35,9 +35,7 @@ sp-api = { features = [ ], workspace = true } sp-std = { workspace = true } sp-io = { workspace = true } -sp-runtime = { features = [ - "serde", -], workspace = true } +sp-runtime = { features = ["serde"], workspace = true } sp-tracing = { workspace = true } sp-core = { workspace = true } sp-arithmetic = { workspace = true } diff --git a/substrate/primitives/api/test/tests/runtime_calls.rs b/substrate/primitives/api/test/tests/runtime_calls.rs index 5a524d1c7f4d..0470b8b72aa0 100644 --- a/substrate/primitives/api/test/tests/runtime_calls.rs +++ b/substrate/primitives/api/test/tests/runtime_calls.rs @@ -99,8 +99,8 @@ fn record_proof_works() { let transaction = Transfer { amount: 1000, nonce: 0, - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), } .into_unchecked_extrinsic(); diff --git a/substrate/primitives/keyring/src/lib.rs b/substrate/primitives/keyring/src/lib.rs index 008c01b150f0..36e77dabd601 100644 --- a/substrate/primitives/keyring/src/lib.rs +++ b/substrate/primitives/keyring/src/lib.rs @@ -32,20 +32,11 @@ pub mod ed25519; #[cfg(feature = "bandersnatch-experimental")] pub mod bandersnatch; -/// Convenience export: Sr25519's Keyring is exposed as `AccountKeyring`, since it tends to be -/// used for accounts (although it may also be used by authorities). -pub use sr25519::Keyring as AccountKeyring; - #[cfg(feature = "bandersnatch-experimental")] pub use bandersnatch::Keyring as BandersnatchKeyring; pub use ed25519::Keyring as Ed25519Keyring; pub use sr25519::Keyring as Sr25519Keyring; -pub mod test { - /// The keyring for use with accounts when using the test runtime. - pub use super::ed25519::Keyring as AccountKeyring; -} - #[derive(Debug)] /// Represents an error that occurs when parsing a string into a `KeyRing`. pub struct ParseKeyringError; diff --git a/substrate/test-utils/client/src/lib.rs b/substrate/test-utils/client/src/lib.rs index c07640653d56..5a4e6c911694 100644 --- a/substrate/test-utils/client/src/lib.rs +++ b/substrate/test-utils/client/src/lib.rs @@ -27,9 +27,7 @@ pub use sc_client_db::{self, Backend, BlocksPruning}; pub use sc_executor::{self, WasmExecutionMethod, WasmExecutor}; pub use sc_service::{client, RpcHandlers}; pub use sp_consensus; -pub use sp_keyring::{ - ed25519::Keyring as Ed25519Keyring, sr25519::Keyring as Sr25519Keyring, AccountKeyring, -}; +pub use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; pub use sp_keystore::{Keystore, KeystorePtr}; pub use sp_runtime::{Storage, StorageChild}; diff --git a/substrate/test-utils/runtime/client/src/lib.rs b/substrate/test-utils/runtime/client/src/lib.rs index 435f3f5ebacb..a5a37660660c 100644 --- a/substrate/test-utils/runtime/client/src/lib.rs +++ b/substrate/test-utils/runtime/client/src/lib.rs @@ -45,7 +45,7 @@ pub mod prelude { Backend, ExecutorDispatch, TestClient, TestClientBuilder, WasmExecutionMethod, }; // Keyring - pub use super::{AccountKeyring, Sr25519Keyring}; + pub use super::Sr25519Keyring; } /// Test client database backend. diff --git a/substrate/test-utils/runtime/client/src/trait_tests.rs b/substrate/test-utils/runtime/client/src/trait_tests.rs index c3a5f173d14e..815e05163281 100644 --- a/substrate/test-utils/runtime/client/src/trait_tests.rs +++ b/substrate/test-utils/runtime/client/src/trait_tests.rs @@ -23,7 +23,7 @@ use std::sync::Arc; use crate::{ - AccountKeyring, BlockBuilderExt, ClientBlockImportExt, TestClientBuilder, TestClientBuilderExt, + BlockBuilderExt, ClientBlockImportExt, Sr25519Keyring, TestClientBuilder, TestClientBuilderExt, }; use futures::executor::block_on; use sc_block_builder::BlockBuilderBuilder; @@ -132,8 +132,8 @@ where // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -179,8 +179,8 @@ where // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 1, }) @@ -199,8 +199,8 @@ where // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 0, }) @@ -295,8 +295,8 @@ where // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -338,8 +338,8 @@ where // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 1, }) @@ -357,8 +357,8 @@ where // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 0, }) @@ -464,8 +464,8 @@ where // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 41, nonce: 0, }) @@ -507,8 +507,8 @@ where // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 1, }) @@ -526,8 +526,8 @@ where // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Ferdie.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Ferdie.into(), amount: 1, nonce: 0, }) diff --git a/substrate/test-utils/runtime/src/extrinsic.rs b/substrate/test-utils/runtime/src/extrinsic.rs index 4c884d4174ff..491086bef497 100644 --- a/substrate/test-utils/runtime/src/extrinsic.rs +++ b/substrate/test-utils/runtime/src/extrinsic.rs @@ -25,7 +25,7 @@ use codec::Encode; use frame_metadata_hash_extension::CheckMetadataHash; use frame_system::{CheckNonce, CheckWeight}; use sp_core::crypto::Pair as TraitPair; -use sp_keyring::AccountKeyring; +use sp_keyring::Sr25519Keyring; use sp_runtime::{ generic::Preamble, traits::TransactionExtension, transaction_validity::TransactionPriority, Perbill, @@ -54,8 +54,8 @@ impl Transfer { impl Default for TransferData { fn default() -> Self { Self { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 0, nonce: 0, } @@ -93,7 +93,7 @@ impl ExtrinsicBuilder { pub fn new(function: impl Into) -> Self { Self { function: function.into(), - signer: Some(AccountKeyring::Alice.pair()), + signer: Some(Sr25519Keyring::Alice.pair()), nonce: None, metadata_hash: None, } diff --git a/substrate/test-utils/runtime/src/genesismap.rs b/substrate/test-utils/runtime/src/genesismap.rs index 9e972886b377..5c0c146d45a5 100644 --- a/substrate/test-utils/runtime/src/genesismap.rs +++ b/substrate/test-utils/runtime/src/genesismap.rs @@ -27,7 +27,7 @@ use sp_core::{ storage::{well_known_keys, StateVersion, Storage}, Pair, }; -use sp_keyring::{AccountKeyring, Sr25519Keyring}; +use sp_keyring::Sr25519Keyring; use sp_runtime::{ traits::{Block as BlockT, Hash as HashT, Header as HeaderT}, BuildStorage, @@ -60,11 +60,11 @@ impl Default for GenesisStorageBuilder { ], (0..16_usize) .into_iter() - .map(|i| AccountKeyring::numeric(i).public()) + .map(|i| Sr25519Keyring::numeric(i).public()) .chain(vec![ - AccountKeyring::Alice.into(), - AccountKeyring::Bob.into(), - AccountKeyring::Charlie.into(), + Sr25519Keyring::Alice.into(), + Sr25519Keyring::Bob.into(), + Sr25519Keyring::Charlie.into(), ]) .collect(), 1000 * currency::DOLLARS, diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index 461d583b5cc6..666776865316 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -47,7 +47,7 @@ use frame_system::{ }; use scale_info::TypeInfo; use sp_application_crypto::Ss58Codec; -use sp_keyring::AccountKeyring; +use sp_keyring::Sr25519Keyring; use sp_application_crypto::{ecdsa, ed25519, sr25519, RuntimeAppPublic}; use sp_core::{OpaqueMetadata, RuntimeDebug}; @@ -731,8 +731,8 @@ impl_runtime_apis! { let patch = match name.as_ref() { "staging" => { let endowed_accounts: Vec = vec![ - AccountKeyring::Bob.public().into(), - AccountKeyring::Charlie.public().into(), + Sr25519Keyring::Bob.public().into(), + Sr25519Keyring::Charlie.public().into(), ]; json!({ @@ -741,8 +741,8 @@ impl_runtime_apis! { }, "substrateTest": { "authorities": [ - AccountKeyring::Alice.public().to_ss58check(), - AccountKeyring::Ferdie.public().to_ss58check() + Sr25519Keyring::Alice.public().to_ss58check(), + Sr25519Keyring::Ferdie.public().to_ss58check() ], } }) @@ -911,11 +911,11 @@ pub mod storage_key_generator { let balances_map_keys = (0..16_usize) .into_iter() - .map(|i| AccountKeyring::numeric(i).public().to_vec()) + .map(|i| Sr25519Keyring::numeric(i).public().to_vec()) .chain(vec![ - AccountKeyring::Alice.public().to_vec(), - AccountKeyring::Bob.public().to_vec(), - AccountKeyring::Charlie.public().to_vec(), + Sr25519Keyring::Alice.public().to_vec(), + Sr25519Keyring::Bob.public().to_vec(), + Sr25519Keyring::Charlie.public().to_vec(), ]) .map(|pubkey| { sp_crypto_hashing::blake2_128(&pubkey) @@ -1133,8 +1133,8 @@ mod tests { pub fn new_test_ext() -> sp_io::TestExternalities { genesismap::GenesisStorageBuilder::new( - vec![AccountKeyring::One.public().into(), AccountKeyring::Two.public().into()], - vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], + vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], + vec![Sr25519Keyring::One.into(), Sr25519Keyring::Two.into()], 1000 * currency::DOLLARS, ) .build() @@ -1202,7 +1202,7 @@ mod tests { fn check_substrate_check_signed_extension_works() { sp_tracing::try_init_simple(); new_test_ext().execute_with(|| { - let x = AccountKeyring::Alice.into(); + let x = Sr25519Keyring::Alice.into(); let info = DispatchInfo::default(); let len = 0_usize; assert_eq!( @@ -1472,8 +1472,8 @@ mod tests { }, "substrateTest": { "authorities": [ - AccountKeyring::Ferdie.public().to_ss58check(), - AccountKeyring::Alice.public().to_ss58check() + Sr25519Keyring::Ferdie.public().to_ss58check(), + Sr25519Keyring::Alice.public().to_ss58check() ], } }); @@ -1502,8 +1502,8 @@ mod tests { let authority_key_vec = Vec::::decode(&mut &value[..]).unwrap(); assert_eq!(authority_key_vec.len(), 2); - assert_eq!(authority_key_vec[0], AccountKeyring::Ferdie.public()); - assert_eq!(authority_key_vec[1], AccountKeyring::Alice.public()); + assert_eq!(authority_key_vec[0], Sr25519Keyring::Ferdie.public()); + assert_eq!(authority_key_vec[1], Sr25519Keyring::Alice.public()); //Babe|Authorities let value: Vec = get_from_storage( diff --git a/substrate/test-utils/runtime/transaction-pool/src/lib.rs b/substrate/test-utils/runtime/transaction-pool/src/lib.rs index 6a4f38f63e82..93e5855eefc6 100644 --- a/substrate/test-utils/runtime/transaction-pool/src/lib.rs +++ b/substrate/test-utils/runtime/transaction-pool/src/lib.rs @@ -43,7 +43,7 @@ use substrate_test_runtime_client::{ AccountId, Block, BlockNumber, Extrinsic, ExtrinsicBuilder, Hash, Header, Nonce, Transfer, TransferData, }, - AccountKeyring::{self, *}, + Sr25519Keyring::{self, *}, }; /// Error type used by [`TestApi`]. @@ -338,7 +338,7 @@ trait TagFrom { impl TagFrom for AccountId { fn tag_from(&self) -> u8 { - let f = AccountKeyring::iter().enumerate().find(|k| AccountId::from(k.1) == *self); + let f = Sr25519Keyring::iter().enumerate().find(|k| AccountId::from(k.1) == *self); u8::try_from(f.unwrap().0).unwrap() } } @@ -534,7 +534,7 @@ impl sp_blockchain::HeaderMetadata for TestApi { /// Generate transfer extrinsic with a given nonce. /// /// Part of the test api. -pub fn uxt(who: AccountKeyring, nonce: Nonce) -> Extrinsic { +pub fn uxt(who: Sr25519Keyring, nonce: Nonce) -> Extrinsic { let dummy = codec::Decode::decode(&mut TrailingZeroInput::zeroes()).unwrap(); let transfer = Transfer { from: who.into(), to: dummy, nonce, amount: 1 }; ExtrinsicBuilder::new_transfer(transfer).build() diff --git a/substrate/utils/frame/rpc/system/src/lib.rs b/substrate/utils/frame/rpc/system/src/lib.rs index 824c871a3562..e1b3994c03dd 100644 --- a/substrate/utils/frame/rpc/system/src/lib.rs +++ b/substrate/utils/frame/rpc/system/src/lib.rs @@ -224,7 +224,7 @@ mod tests { transaction_validity::{InvalidTransaction, TransactionValidityError}, ApplyExtrinsicResult, }; - use substrate_test_runtime_client::{runtime::Transfer, AccountKeyring}; + use substrate_test_runtime_client::{runtime::Transfer, Sr25519Keyring}; fn deny_unsafe() -> Extensions { let mut ext = Extensions::new(); @@ -256,8 +256,8 @@ mod tests { let source = sp_runtime::transaction_validity::TransactionSource::External; let new_transaction = |nonce: u64| { let t = Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 5, nonce, }; @@ -273,7 +273,7 @@ mod tests { let accounts = System::new(client, pool); // when - let nonce = accounts.nonce(AccountKeyring::Alice.into()).await; + let nonce = accounts.nonce(Sr25519Keyring::Alice.into()).await; // then assert_eq!(nonce.unwrap(), 2); @@ -321,8 +321,8 @@ mod tests { let accounts = System::new(client, pool); let tx = Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 5, nonce: 0, } @@ -357,8 +357,8 @@ mod tests { let accounts = System::new(client, pool); let tx = Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), + from: Sr25519Keyring::Alice.into(), + to: Sr25519Keyring::Bob.into(), amount: 5, nonce: 100, } diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 7b8449f2abe4..72eded5bfd13 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -41,7 +41,7 @@ pub mod genesis_config_presets { use super::*; use crate::{ interface::{Balance, MinimumBalance}, - sp_keyring::AccountKeyring, + sp_keyring::Sr25519Keyring, BalancesConfig, RuntimeGenesisConfig, SudoConfig, }; @@ -53,11 +53,11 @@ pub mod genesis_config_presets { let endowment = >::get().max(1) * 1000; frame_support::build_struct_json_patch!(RuntimeGenesisConfig { balances: BalancesConfig { - balances: AccountKeyring::iter() + balances: Sr25519Keyring::iter() .map(|a| (a.to_account_id(), endowment)) .collect::>(), }, - sudo: SudoConfig { key: Some(AccountKeyring::Alice.to_account_id()) }, + sudo: SudoConfig { key: Some(Sr25519Keyring::Alice.to_account_id()) }, }) } diff --git a/templates/solochain/runtime/src/genesis_config_presets.rs b/templates/solochain/runtime/src/genesis_config_presets.rs index 049f4593451b..6af8dc9cd18a 100644 --- a/templates/solochain/runtime/src/genesis_config_presets.rs +++ b/templates/solochain/runtime/src/genesis_config_presets.rs @@ -22,7 +22,7 @@ use serde_json::Value; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_genesis_builder::{self, PresetId}; -use sp_keyring::AccountKeyring; +use sp_keyring::Sr25519Keyring; // Returns the genesis config presets populated with given parameters. fn testnet_genesis( @@ -56,12 +56,12 @@ pub fn development_config_genesis() -> Value { sp_keyring::Ed25519Keyring::Alice.public().into(), )], vec![ - AccountKeyring::Alice.to_account_id(), - AccountKeyring::Bob.to_account_id(), - AccountKeyring::AliceStash.to_account_id(), - AccountKeyring::BobStash.to_account_id(), + Sr25519Keyring::Alice.to_account_id(), + Sr25519Keyring::Bob.to_account_id(), + Sr25519Keyring::AliceStash.to_account_id(), + Sr25519Keyring::BobStash.to_account_id(), ], - sp_keyring::AccountKeyring::Alice.to_account_id(), + sp_keyring::Sr25519Keyring::Alice.to_account_id(), ) } @@ -78,11 +78,11 @@ pub fn local_config_genesis() -> Value { sp_keyring::Ed25519Keyring::Bob.public().into(), ), ], - AccountKeyring::iter() - .filter(|v| v != &AccountKeyring::One && v != &AccountKeyring::Two) + Sr25519Keyring::iter() + .filter(|v| v != &Sr25519Keyring::One && v != &Sr25519Keyring::Two) .map(|v| v.to_account_id()) .collect::>(), - AccountKeyring::Alice.to_account_id(), + Sr25519Keyring::Alice.to_account_id(), ) } From a35eee11d3bb6c48a54855e1bdb0b0504eeea81c Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 10 Dec 2024 11:48:33 +0200 Subject: [PATCH 133/340] adds tests, changes message format --- .../pallets/inbound-queue-v2/src/api.rs | 2 +- .../pallets/inbound-queue-v2/src/lib.rs | 6 +- .../pallets/inbound-queue-v2/src/mock.rs | 5 +- .../pallets/inbound-queue-v2/src/test.rs | 159 --------- .../primitives/router/src/inbound/v2.rs | 319 ++++++++++++++++-- .../src/tests/snowbridge.rs | 2 +- .../src/tests/snowbridge_v2.rs | 48 ++- 7 files changed, 334 insertions(+), 207 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs index beb96b1cb50d..8efc6eb2a280 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/api.rs @@ -15,7 +15,7 @@ where { // Convert message to XCM let dummy_origin = Location::new(0, AccountId32 { id: H256::zero().into(), network: None }); - let xcm = T::MessageConverter::convert(message, dummy_origin) + let (xcm, _) = T::MessageConverter::convert(message, dummy_origin) .map_err(|e| Error::::ConvertMessage(e))?; // Calculate fee. Consists of the cost of the "submit" extrinsic as well as the XCM execution diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index ffe21dfe4fd5..43c24a44b23d 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -226,10 +226,10 @@ pub mod pallet { let origin_account_location = Self::account_to_location(who)?; - let xcm = Self::do_convert(message, origin_account_location.clone())?; + let (xcm, _relayer_reward) = Self::do_convert(message, origin_account_location.clone())?; // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: - // T::RewardLeger::deposit(who, envelope.fee.into())?; + // T::RewardLeger::deposit(who, relayer_reward.into())?; // a. The submit extrinsic cost on BH // b. The delivery cost to AH // c. The execution cost on AH @@ -277,7 +277,7 @@ pub mod pallet { pub fn do_convert( message: MessageV2, origin_account_location: Location, - ) -> Result, Error> { + ) -> Result<(Xcm<()>, u128), Error> { Ok(T::MessageConverter::convert(message, origin_account_location) .map_err(|e| Error::::ConvertMessage(e))?) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index c981c99bf3aa..e96797fec96d 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -149,6 +149,9 @@ parameter_types! { pub const WethAddress: H160 = H160(WETH_ADDRESS); pub const InboundQueuePalletInstance: u8 = 84; pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1002)].into(); + pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),Parachain(1000)]); } impl inbound_queue_v2::Config for Test { @@ -160,7 +163,7 @@ impl inbound_queue_v2::Config for Test { type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; type MessageConverter = - MessageToXcm; + MessageToXcm; type Token = Balances; type Balance = u128; #[cfg(feature = "runtime-benchmarks")] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index cdaf95e4b267..84b6a51e6c7f 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -126,162 +126,3 @@ fn test_set_operating_mode_root_only() { }); } -#[test] -fn test_send_native_erc20_token_payload() { - new_tester().execute_with(|| { - // To generate test data: forge test --match-test testSendEther -vvvv - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf0030ef7dba020000000000000000000004005615deb798bb3e4dfa0139dfa1b3d433cc23b72f0000b2d3595bf00600000000000000000000").to_vec(); - let message = MessageV2::decode(&mut payload.as_ref()); - assert_ok!(message.clone()); - - let inbound_message = message.unwrap(); - - let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); - let expected_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); - let expected_value = 500000000000000000u128; - let expected_xcm: Vec = vec![]; - let expected_claimer: Option> = None; - - assert_eq!(expected_origin, inbound_message.origin); - assert_eq!(1, inbound_message.assets.len()); - if let Asset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { - assert_eq!(expected_token_id, *token_id); - assert_eq!(expected_value, *value); - } else { - panic!("Expected NativeTokenERC20 asset"); - } - assert_eq!(expected_xcm, inbound_message.xcm); - assert_eq!(expected_claimer, inbound_message.claimer); - }); -} - -#[test] -fn test_send_foreign_erc20_token_payload() { - new_tester().execute_with(|| { - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf0030ef7dba0200000000000000000000040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); - let message = MessageV2::decode(&mut payload.as_ref()); - assert_ok!(message.clone()); - - let inbound_message = message.unwrap(); - - let expected_fee = 3_000_000_000_000u128; - let expected_origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); - let expected_token_id: H256 = hex!("97874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f40").into(); - let expected_value = 500000000000000000u128; - let expected_xcm: Vec = vec![]; - let expected_claimer: Option> = None; - - assert_eq!(expected_origin, inbound_message.origin); - assert_eq!(expected_fee, inbound_message.fee); - assert_eq!(1, inbound_message.assets.len()); - if let Asset::ForeignTokenERC20 { token_id, value } = &inbound_message.assets[0] { - assert_eq!(expected_token_id, *token_id); - assert_eq!(expected_value, *value); - } else { - panic!("Expected ForeignTokenERC20 asset"); - } - assert_eq!(expected_xcm, inbound_message.xcm); - assert_eq!(expected_claimer, inbound_message.claimer); - }); -} - -#[test] -fn test_register_token_inbound_message_with_xcm_and_claimer() { - new_tester().execute_with(|| { - let payload = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a90030ef7dba020000000000000000000004005615deb798bb3e4dfa0139dfa1b3d433cc23b72f00000000000000000000000000000000300508020401000002286bee0a015029e3b139f4393adda86303fcdaa35f60bb7092bf").to_vec(); - let message = MessageV2::decode(&mut payload.as_ref()); - assert_ok!(message.clone()); - - let inbound_message = message.unwrap(); - - let expected_origin: H160 = hex!("5991a2df15a8f6a256d3ec51e99254cd3fb576a9").into(); - let expected_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); - let expected_value = 0u128; - let expected_xcm: Vec = hex!("0508020401000002286bee0a").to_vec(); - let expected_claimer: Option> = Some(hex!("29E3b139f4393aDda86303fcdAa35F60Bb7092bF").to_vec()); - - assert_eq!(expected_origin, inbound_message.origin); - assert_eq!(1, inbound_message.assets.len()); - if let Asset::NativeTokenERC20 { token_id, value } = &inbound_message.assets[0] { - assert_eq!(expected_token_id, *token_id); - assert_eq!(expected_value, *value); - } else { - panic!("Expected NativeTokenERC20 asset"); - } - assert_eq!(expected_xcm, inbound_message.xcm); - assert_eq!(expected_claimer, inbound_message.claimer); - - // decode xcm - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut inbound_message.xcm.as_ref(), - ); - - assert_ok!(versioned_xcm.clone()); - - // Check if decoding was successful - let decoded_instructions = match versioned_xcm.unwrap() { - VersionedXcm::V5(decoded) => decoded, - _ => { - panic!("unexpected xcm version found") - } - }; - - let mut decoded_instructions = decoded_instructions.into_iter(); - let decoded_first = decoded_instructions.next().take(); - assert!(decoded_first.is_some()); - let decoded_second = decoded_instructions.next().take(); - assert!(decoded_second.is_some()); - assert_eq!(ClearOrigin, decoded_second.unwrap(), "Second instruction (ClearOrigin) does not match."); - }); -} - -#[test] -fn encode_xcm() { - new_tester().execute_with(|| { - let total_fee_asset: xcm::opaque::latest::Asset = - (Location::parent(), 1_000_000_000).into(); - - let instructions: Xcm<()> = - vec![ReceiveTeleportedAsset(total_fee_asset.into()), ClearOrigin].into(); - - let versioned_xcm_message = VersionedXcm::V5(instructions.clone()); - - let xcm_bytes = VersionedXcm::encode(&versioned_xcm_message); - let hex_string = hex::encode(xcm_bytes.clone()); - - println!("xcm hex: {}", hex_string); - - let versioned_xcm = VersionedXcm::<()>::decode_with_depth_limit( - MAX_XCM_DECODE_DEPTH, - &mut xcm_bytes.as_ref(), - ); - - assert_ok!(versioned_xcm.clone()); - - // Check if decoding was successful - let decoded_instructions = match versioned_xcm.unwrap() { - VersionedXcm::V5(decoded) => decoded, - _ => { - panic!("unexpected xcm version found") - }, - }; - - let mut original_instructions = instructions.into_iter(); - let mut decoded_instructions = decoded_instructions.into_iter(); - - let original_first = original_instructions.next().take(); - let decoded_first = decoded_instructions.next().take(); - assert_eq!( - original_first, decoded_first, - "First instruction (ReceiveTeleportedAsset) does not match." - ); - - let original_second = original_instructions.next().take(); - let decoded_second = decoded_instructions.next().take(); - assert_eq!( - original_second, decoded_second, - "Second instruction (ClearOrigin) does not match." - ); - }); -} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 23b51eb4a5f7..45d6276b4764 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -23,14 +23,18 @@ const LOG_TARGET: &str = "snowbridge-router-primitives"; pub struct Message { /// The origin address pub origin: H160, - /// Fee in weth to cover the xcm execution on AH. - pub fee: u128, /// The assets pub assets: Vec, /// The command originating from the Gateway contract pub xcm: Vec, /// The claimer in the case that funds get trapped. pub claimer: Option>, + /// The full value of the assets. + pub value: u128, + /// Fee in eth to cover the xcm execution on AH. + pub execution_fee: u128, + /// Relayer reward in eth. Needs to cover all costs of sending a message. + pub relayer_fee: u128, } /// An asset that will be transacted on AH. The asset will be reserved/withdrawn and placed into @@ -53,7 +57,7 @@ pub enum Asset { } /// Reason why a message conversion failed. -#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug)] +#[derive(Copy, Clone, TypeInfo, PalletError, Encode, Decode, RuntimeDebug, PartialEq)] pub enum ConvertMessageError { /// Invalid foreign ERC-20 token ID InvalidAsset, @@ -62,7 +66,10 @@ pub enum ConvertMessageError { } pub trait ConvertMessage { - fn convert(message: Message, origin_account: Location) -> Result, ConvertMessageError>; + fn convert( + message: Message, + origin_account: Location, + ) -> Result<(Xcm<()>, u128), ConvertMessageError>; } pub struct MessageToXcm< @@ -123,7 +130,7 @@ where fn convert( message: Message, origin_account_location: Location, - ) -> Result, ConvertMessageError> { + ) -> Result<(Xcm<()>, u128), ConvertMessageError> { let mut message_xcm: Xcm<()> = Xcm::new(); if message.xcm.len() > 0 { // Allow xcm decode failure so that assets can be trapped on AH instead of this @@ -150,7 +157,7 @@ where AccountKey20 { network: None, key: WethAddress::get().into() }, ], ); - let fee: XcmAsset = (fee_asset.clone(), message.fee).into(); + let fee: XcmAsset = (fee_asset.clone(), message.execution_fee).into(); let mut instructions = vec![ DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), UniversalOrigin(GlobalConsensus(network)), @@ -228,27 +235,35 @@ where instructions.extend(appendix); - Ok(instructions.into()) + Ok((instructions.into(), message.relayer_fee)) } } #[cfg(test)] mod tests { - use crate::inbound::v2::{ConvertMessage, Message, MessageToXcm}; - use codec::Decode; - use frame_support::{assert_ok, parameter_types}; + use crate::inbound::v2::{ + Asset::{ForeignTokenERC20, NativeTokenERC20}, + ConvertMessage, ConvertMessageError, Message, MessageToXcm, + }; + use codec::Encode; + use frame_support::{assert_err, assert_ok, parameter_types}; use hex_literal::hex; - use sp_core::H256; - use sp_runtime::traits::{ConstU128, ConstU8}; - use xcm::prelude::*; - use snowbridge_core::TokenId; + use sp_core::{H160, H256}; use sp_runtime::traits::MaybeEquivalence; - - const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; + use xcm::{opaque::latest::WESTEND_GENESIS_HASH, prelude::*}; + const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; + const WETH_ADDRESS: [u8; 20] = hex!["fff9976782d46cc05630d1f6ebab18b2324d6b14"]; parameter_types! { - pub EthereumNetwork: NetworkId = NETWORK; + pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; + pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); + pub const WethAddress: H160 = H160(WETH_ADDRESS); + pub const InboundQueuePalletInstance: u8 = 84; + pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1002)].into(); + pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),Parachain(1000)]); } pub struct MockTokenIdConvert; @@ -261,21 +276,273 @@ mod tests { } } + pub struct MockFailedTokenConvert; + impl MaybeEquivalence for MockFailedTokenConvert { + fn convert(_id: &TokenId) -> Option { + None + } + fn convert_back(_loc: &Location) -> Option { + None + } + } + #[test] - fn convert_message() { - let payload = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf040197874824853fb4ad04794ccfd1cc8d2a7463839cfcbc6a315a1045c60ab85f400000b2d3595bf00600000000000000000000").to_vec(); + fn test_successful_message() { let origin_account = - Location::new(0, AccountId32 { id: H256::random().into(), network: None }); + Location::new(0, [AccountId32 { network: None, id: H256::random().into() }]); + let origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let native_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); + let foreign_token_id: H256 = + hex!("37a6c666da38711a963d938eafdd09314fd3f95a96a3baffb55f26560f4ecdd8").into(); + let beneficiary = + hex!("908783d8cd24c9e02cee1d26ab9c46d458621ad0150b626c536a40b9df3f09c6").into(); + let message_id: H256 = + hex!("8b69c7e376e28114618e829a7ec768dbda28357d359ba417a3bd79b11215059d").into(); + let token_value = 3_000_000_000_000u128; + let assets = vec![ + NativeTokenERC20 { token_id: native_token_id, value: token_value }, + ForeignTokenERC20 { token_id: foreign_token_id, value: token_value }, + ]; + let instructions = vec![ + DepositAsset { assets: Wild(AllCounted(1).into()), beneficiary }, + SetTopic(message_id.into()), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_xcm = VersionedXcm::V5(xcm); + let claimer_account = AccountId32 { network: None, id: H256::random().into() }; + let claimer: Option> = Some(claimer_account.clone().encode()); + let value = 6_000_000_000_000u128; + let execution_fee = 1_000_000_000_000u128; + let relayer_fee = 5_000_000_000_000u128; - let message = Message::decode(&mut payload.as_ref()); - assert_ok!(message.clone()); + let message = Message { + origin: origin.clone(), + assets, + xcm: versioned_xcm.encode(), + claimer, + value, + execution_fee, + relayer_fee, + }; let result = MessageToXcm::< EthereumNetwork, - ConstU8<80>, + InboundQueuePalletInstance, MockTokenIdConvert, - ConstU128<1_000_000_000_000>, - >::convert(message.unwrap(), origin_account); - assert_ok!(result); + WethAddress, + GatewayAddress, + UniversalLocation, + AssetHubFromEthereum, + >::convert(message, origin_account); + + assert_ok!(result.clone()); + + let (xcm, _) = result.unwrap(); + + let mut instructions = xcm.into_iter(); + + let mut asset_claimer_found = false; + let mut commands_found = 0; + while let Some(instruction) = instructions.next() { + if let SetAssetClaimer { ref location } = instruction { + assert_eq!(Location::new(0, [claimer_account]), location.clone()); + asset_claimer_found = true; + } + if let DescendOrigin(ref location) = instruction { + commands_found = commands_found + 1; + if commands_found == 2 { + let junctions: Junctions = + AccountKey20 { key: origin.into(), network: None }.into(); + assert_eq!(junctions, location.clone()); + } + } + } + // SetAssetClaimer must be in the message. + assert!(asset_claimer_found); + // The first DescendOrigin to descend into the InboundV2 pallet index and the DescendOrigin + // into the message.origin + assert!(commands_found == 2); + } + + #[test] + fn test_invalid_foreign_erc20() { + let origin_account = + Location::new(0, [AccountId32 { network: None, id: H256::random().into() }]); + let origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let token_id: H256 = + hex!("37a6c666da38711a963d938eafdd09314fd3f95a96a3baffb55f26560f4ecdd8").into(); + let beneficiary = + hex!("908783d8cd24c9e02cee1d26ab9c46d458621ad0150b626c536a40b9df3f09c6").into(); + let message_id: H256 = + hex!("8b69c7e376e28114618e829a7ec768dbda28357d359ba417a3bd79b11215059d").into(); + let token_value = 3_000_000_000_000u128; + let assets = vec![ForeignTokenERC20 { token_id, value: token_value }]; + let instructions = vec![ + DepositAsset { assets: Wild(AllCounted(1).into()), beneficiary }, + SetTopic(message_id.into()), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_xcm = VersionedXcm::V5(xcm); + let claimer_account = AccountId32 { network: None, id: H256::random().into() }; + let claimer: Option> = Some(claimer_account.clone().encode()); + let value = 6_000_000_000_000u128; + let execution_fee = 1_000_000_000_000u128; + let relayer_fee = 5_000_000_000_000u128; + + let message = Message { + origin, + assets, + xcm: versioned_xcm.encode(), + claimer, + value, + execution_fee, + relayer_fee, + }; + + let result = MessageToXcm::< + EthereumNetwork, + InboundQueuePalletInstance, + MockFailedTokenConvert, + WethAddress, + GatewayAddress, + UniversalLocation, + AssetHubFromEthereum, + >::convert(message, origin_account); + + assert_err!(result.clone(), ConvertMessageError::InvalidAsset); + } + + #[test] + fn test_invalid_claimer() { + let origin_account = + Location::new(0, [AccountId32 { network: None, id: H256::random().into() }]); + let origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let token_id: H256 = + hex!("37a6c666da38711a963d938eafdd09314fd3f95a96a3baffb55f26560f4ecdd8").into(); + let beneficiary = + hex!("908783d8cd24c9e02cee1d26ab9c46d458621ad0150b626c536a40b9df3f09c6").into(); + let message_id: H256 = + hex!("8b69c7e376e28114618e829a7ec768dbda28357d359ba417a3bd79b11215059d").into(); + let token_value = 3_000_000_000_000u128; + let assets = vec![ForeignTokenERC20 { token_id, value: token_value }]; + let instructions = vec![ + DepositAsset { assets: Wild(AllCounted(1).into()), beneficiary }, + SetTopic(message_id.into()), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_xcm = VersionedXcm::V5(xcm); + // Invalid claimer location, cannot be decoded into a Junction + let claimer: Option> = + Some(hex!("43581a7d43757158624921ab0e9e112a1d7da93cbe64782d563e8e1144a06c3c").to_vec()); + let value = 6_000_000_000_000u128; + let execution_fee = 1_000_000_000_000u128; + let relayer_fee = 5_000_000_000_000u128; + + let message = Message { + origin, + assets, + xcm: versioned_xcm.encode(), + claimer, + value, + execution_fee, + relayer_fee, + }; + + let result = MessageToXcm::< + EthereumNetwork, + InboundQueuePalletInstance, + MockTokenIdConvert, + WethAddress, + GatewayAddress, + UniversalLocation, + AssetHubFromEthereum, + >::convert(message, origin_account.clone()); + + // Invalid claimer does not break the message conversion + assert_ok!(result.clone()); + + let (xcm, _) = result.unwrap(); + + let mut result_instructions = xcm.clone().into_iter(); + + let mut found = false; + while let Some(instruction) = result_instructions.next() { + if let SetAssetClaimer { .. } = instruction { + found = true; + break; + } + } + // SetAssetClaimer should not be in the message. + assert!(!found); + + // Find the last two instructions to check the appendix is correct. + let mut second_last = None; + let mut last = None; + + for instruction in xcm.into_iter() { + second_last = last; + last = Some(instruction); + } + + // Check if both instructions are found + assert!(last.is_some()); + assert!(second_last.is_some()); + + let fee_asset = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: WethAddress::get().into() }, + ], + ); + assert_eq!( + last, + Some(DepositAsset { + assets: Wild(AllOf { id: AssetId(fee_asset), fun: WildFungibility::Fungible }), + // beneficiary is the relayer + beneficiary: origin_account + }) + ); + } + + #[test] + fn test_invalid_xcm() { + let origin_account = + Location::new(0, [AccountId32 { network: None, id: H256::random().into() }]); + let origin: H160 = hex!("29e3b139f4393adda86303fcdaa35f60bb7092bf").into(); + let token_id: H256 = + hex!("37a6c666da38711a963d938eafdd09314fd3f95a96a3baffb55f26560f4ecdd8").into(); + let token_value = 3_000_000_000_000u128; + let assets = vec![ForeignTokenERC20 { token_id, value: token_value }]; + // invalid xcm + let versioned_xcm = hex!("8b69c7e376e28114618e829a7ec7").to_vec(); + let claimer_account = AccountId32 { network: None, id: H256::random().into() }; + let claimer: Option> = Some(claimer_account.clone().encode()); + let value = 6_000_000_000_000u128; + let execution_fee = 1_000_000_000_000u128; + let relayer_fee = 5_000_000_000_000u128; + + let message = Message { + origin, + assets, + xcm: versioned_xcm, + claimer: Some(claimer.encode()), + value, + execution_fee, + relayer_fee, + }; + + let result = MessageToXcm::< + EthereumNetwork, + InboundQueuePalletInstance, + MockTokenIdConvert, + WethAddress, + GatewayAddress, + UniversalLocation, + AssetHubFromEthereum, + >::convert(message, origin_account.clone()); + + // Invalid xcm does not break the message, allowing funds to be trapped on AH. + assert_ok!(result.clone()); } } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index 48eeb07a7c6a..c5926913bd70 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -37,7 +37,7 @@ const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EB const XCM_FEE: u128 = 100_000_000_000; const TOKEN_AMOUNT: u128 = 100_000_000_000; -#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeIfmtnfo)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum ControlCall { #[codec(index = 3)] CreateAgent, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 6bc41f212b6e..101af63adfca 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -119,12 +119,14 @@ fn register_token_v2() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 3_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -189,13 +191,15 @@ fn send_token_v2() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 3_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -265,13 +269,15 @@ fn send_weth_v2() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 3_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -394,13 +400,15 @@ fn register_and_send_multiple_tokens_v2() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 3_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -556,13 +564,15 @@ fn send_token_to_penpal_v2() { let message = Message { origin, - fee: 1_000_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 3_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -684,13 +694,15 @@ fn send_foreign_erc20_token_back_to_polkadot() { let message = Message { origin, - fee: 3_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), + value: 1_500_000_000_000u128, + execution_fee: 3_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -766,13 +778,15 @@ fn invalid_xcm_traps_funds_on_ah() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: instructions.to_vec(), claimer: Some(claimer_bytes), + value: 1_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( @@ -826,14 +840,16 @@ fn invalid_claimer_does_not_fail_the_message() { let message = Message { origin, - fee: 1_500_000_000_000u128, assets, xcm: versioned_message_xcm.encode(), // Set an invalid claimer claimer: Some(hex!("2b7ce7bc7e87e4d6619da21487c7a53f").to_vec()), + value: 1_500_000_000_000u128, + execution_fee: 1_500_000_000_000u128, + relayer_fee: 1_500_000_000_000u128, }; - let xcm = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); + let (xcm, _) = EthereumInboundQueueV2::do_convert(message, relayer_location).unwrap(); let _ = EthereumInboundQueueV2::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); assert_expected_events!( From 6208dfdfd28caed20b38ee3c669cf7ea2cb02a9f Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 10 Dec 2024 11:49:05 +0200 Subject: [PATCH 134/340] fmt --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 3 ++- .../snowbridge/pallets/inbound-queue-v2/src/mock.rs | 10 ++++++++-- .../snowbridge/pallets/inbound-queue-v2/src/test.rs | 1 - 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 43c24a44b23d..7320bf9188a6 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -226,7 +226,8 @@ pub mod pallet { let origin_account_location = Self::account_to_location(who)?; - let (xcm, _relayer_reward) = Self::do_convert(message, origin_account_location.clone())?; + let (xcm, _relayer_reward) = + Self::do_convert(message, origin_account_location.clone())?; // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: // T::RewardLeger::deposit(who, relayer_reward.into())?; diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index e96797fec96d..bdd2f3ea9bd0 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -162,8 +162,14 @@ impl inbound_queue_v2::Config for Test { type WeightToFee = IdentityFee; type GatewayAddress = GatewayAddress; type AssetHubParaId = ConstU32<1000>; - type MessageConverter = - MessageToXcm; + type MessageConverter = MessageToXcm< + EthereumNetwork, + InboundQueuePalletInstance, + MockTokenIdConvert, + WethAddress, + UniversalLocation, + AssetHubFromEthereum, + >; type Token = Balances; type Balance = u128; #[cfg(feature = "runtime-benchmarks")] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 84b6a51e6c7f..a272cbf525fa 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -125,4 +125,3 @@ fn test_set_operating_mode_root_only() { ); }); } - From d0624e2b8c97f06125325375805385cf7fc36085 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 10 Dec 2024 12:46:30 +0200 Subject: [PATCH 135/340] fmt --- .../primitives/router/src/inbound/v2.rs | 131 +++++++++++++++++- 1 file changed, 126 insertions(+), 5 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 45d6276b4764..28452f3d99b1 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -243,7 +243,7 @@ where mod tests { use crate::inbound::v2::{ Asset::{ForeignTokenERC20, NativeTokenERC20}, - ConvertMessage, ConvertMessageError, Message, MessageToXcm, + ConvertMessage, ConvertMessageError, Message, MessageToXcm, XcmAsset, }; use codec::Encode; use frame_support::{assert_err, assert_ok, parameter_types}; @@ -342,26 +342,147 @@ mod tests { let mut instructions = xcm.into_iter(); let mut asset_claimer_found = false; - let mut commands_found = 0; + let mut pay_fees_found = false; + let mut descend_origin_found = 0; + let mut reserve_deposited_found = 0; + let mut withdraw_assets_found = 0; while let Some(instruction) = instructions.next() { if let SetAssetClaimer { ref location } = instruction { assert_eq!(Location::new(0, [claimer_account]), location.clone()); asset_claimer_found = true; } if let DescendOrigin(ref location) = instruction { - commands_found = commands_found + 1; - if commands_found == 2 { + descend_origin_found = descend_origin_found + 1; + // The second DescendOrigin should be the message.origin (sender) + if descend_origin_found == 2 { let junctions: Junctions = AccountKey20 { key: origin.into(), network: None }.into(); assert_eq!(junctions, location.clone()); } } + if let PayFees { ref asset } = instruction { + let fee_asset = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: WethAddress::get().into() }, + ], + ); + assert_eq!(asset.id, AssetId(fee_asset)); + assert_eq!(asset.fun, Fungible(execution_fee)); + pay_fees_found = true; + } + if let ReserveAssetDeposited(ref reserve_assets) = instruction { + reserve_deposited_found = reserve_deposited_found + 1; + if reserve_deposited_found == 1 { + let fee_asset = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: WethAddress::get().into() }, + ], + ); + let fee: XcmAsset = (fee_asset, execution_fee).into(); + let fee_assets: Assets = fee.into(); + assert_eq!(fee_assets, reserve_assets.clone()); + } + if reserve_deposited_found == 2 { + let token_asset = Location::new( + 2, + [ + GlobalConsensus(EthereumNetwork::get()), + AccountKey20 { network: None, key: native_token_id.into() }, + ], + ); + let token: XcmAsset = (token_asset, token_value).into(); + let token_assets: Assets = token.into(); + assert_eq!(token_assets, reserve_assets.clone()); + } + } + if let WithdrawAsset(ref withdraw_assets) = instruction { + withdraw_assets_found = withdraw_assets_found + 1; + let token_asset = Location::new(2, Here); + let token: XcmAsset = (token_asset, token_value).into(); + let token_assets: Assets = token.into(); + assert_eq!(token_assets, withdraw_assets.clone()); + } } // SetAssetClaimer must be in the message. assert!(asset_claimer_found); + // PayFees must be in the message. + assert!(pay_fees_found); // The first DescendOrigin to descend into the InboundV2 pallet index and the DescendOrigin // into the message.origin - assert!(commands_found == 2); + assert!(descend_origin_found == 2); + // Expecting two ReserveAssetDeposited instructions, one for the fee and one for the token + // being transferred. + assert!(reserve_deposited_found == 2); + // Expecting one WithdrawAsset for the foreign ERC-20 + assert!(withdraw_assets_found == 1); + } + + #[test] + fn test_message_with_gateway_origin_does_not_descend_origin_into_sender() { + let origin_account = + Location::new(0, [AccountId32 { network: None, id: H256::random().into() }]); + let origin: H160 = GatewayAddress::get(); + let native_token_id: H160 = hex!("5615deb798bb3e4dfa0139dfa1b3d433cc23b72f").into(); + let foreign_token_id: H256 = + hex!("37a6c666da38711a963d938eafdd09314fd3f95a96a3baffb55f26560f4ecdd8").into(); + let beneficiary = + hex!("908783d8cd24c9e02cee1d26ab9c46d458621ad0150b626c536a40b9df3f09c6").into(); + let message_id: H256 = + hex!("8b69c7e376e28114618e829a7ec768dbda28357d359ba417a3bd79b11215059d").into(); + let token_value = 3_000_000_000_000u128; + let assets = vec![ + NativeTokenERC20 { token_id: native_token_id, value: token_value }, + ForeignTokenERC20 { token_id: foreign_token_id, value: token_value }, + ]; + let instructions = vec![ + DepositAsset { assets: Wild(AllCounted(1).into()), beneficiary }, + SetTopic(message_id.into()), + ]; + let xcm: Xcm<()> = instructions.into(); + let versioned_xcm = VersionedXcm::V5(xcm); + let claimer_account = AccountId32 { network: None, id: H256::random().into() }; + let claimer: Option> = Some(claimer_account.clone().encode()); + let value = 6_000_000_000_000u128; + let execution_fee = 1_000_000_000_000u128; + let relayer_fee = 5_000_000_000_000u128; + + let message = Message { + origin: origin.clone(), + assets, + xcm: versioned_xcm.encode(), + claimer, + value, + execution_fee, + relayer_fee, + }; + + let result = MessageToXcm::< + EthereumNetwork, + InboundQueuePalletInstance, + MockTokenIdConvert, + WethAddress, + GatewayAddress, + UniversalLocation, + AssetHubFromEthereum, + >::convert(message, origin_account); + + assert_ok!(result.clone()); + + let (xcm, _) = result.unwrap(); + + let mut instructions = xcm.into_iter(); + let mut commands_found = 0; + while let Some(instruction) = instructions.next() { + if let DescendOrigin(ref _location) = instruction { + commands_found = commands_found + 1; + } + } + // There should only be 1 DescendOrigin in the message. + assert!(commands_found == 1); } #[test] From c808a0097cd83c141b8ff6362876ee689d5fdabf Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Tue, 10 Dec 2024 10:47:15 +0000 Subject: [PATCH 136/340] Let cmd bot to trigger ci on commit (#6813) Fixes: https://github.com/paritytech/ci_cd/issues/1079 Improvements: - switch to github native token creation action - refresh branch in CI from HEAD, to prevent failure - add APP token when pushing, to allow CI to be retriggering by bot --- .github/workflows/cmd.yml | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 6cd1adec1d8b..b6a50ea0d15e 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -19,10 +19,10 @@ jobs: steps: - name: Generate token id: generate_token - uses: tibdex/github-app-token@v2.1.0 + uses: actions/create-github-app-token@v1 with: - app_id: ${{ secrets.CMD_BOT_APP_ID }} - private_key: ${{ secrets.CMD_BOT_APP_KEY }} + app-id: ${{ secrets.CMD_BOT_APP_ID }} + private-key: ${{ secrets.CMD_BOT_APP_KEY }} - name: Check if user is a member of the organization id: is-member @@ -292,9 +292,17 @@ jobs: image: ${{ needs.set-image.outputs.IMAGE }} timeout-minutes: 1440 # 24 hours per runtime steps: + - name: Generate token + uses: actions/create-github-app-token@v1 + id: generate_token + with: + app-id: ${{ secrets.CMD_BOT_APP_ID }} + private-key: ${{ secrets.CMD_BOT_APP_KEY }} + - name: Checkout uses: actions/checkout@v4 with: + token: ${{ steps.generate_token.outputs.token }} repository: ${{ needs.get-pr-branch.outputs.repo }} ref: ${{ needs.get-pr-branch.outputs.pr-branch }} @@ -395,16 +403,30 @@ jobs: - name: Commit changes run: | if [ -n "$(git status --porcelain)" ]; then - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" + git config --global user.name command-bot + git config --global user.email "<>" + git config --global pull.rebase false + + # Push the results to the target branch + git remote add \ + github \ + "https://token:${{ steps.generate_token.outputs.token }}@github.com/${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}.git" || : + + push_changes() { + git push github "HEAD:${{ needs.get-pr-branch.outputs.pr-branch }}" + } git add . git restore --staged Cargo.lock # ignore changes in Cargo.lock git commit -m "Update from ${{ github.actor }} running command '${{ steps.get-pr-comment.outputs.group2 }}'" || true - git pull --rebase origin ${{ needs.get-pr-branch.outputs.pr-branch }} - - git push origin ${{ needs.get-pr-branch.outputs.pr-branch }} + # Attempt to push changes + if ! push_changes; then + echo "Push failed, trying to rebase..." + git pull --rebase github "${{ needs.get-pr-branch.outputs.pr-branch }}" + # After successful rebase, try pushing again + push_changes + fi else echo "Nothing to commit"; fi From 19bc578e60d13a17f42e1ee7a960b1671985b9e4 Mon Sep 17 00:00:00 2001 From: Kazunobu Ndong <33208377+ndkazu@users.noreply.github.com> Date: Tue, 10 Dec 2024 20:26:25 +0900 Subject: [PATCH 137/340] polkadot-sdk-docs: Use command_macro! (#6624) # Description **Understood assignment:** Initial assignment description is in #6194. In order to Simplify the display of commands and ensure they are tested for chain spec builder's `polkadot-sdk` reference docs, find every occurrence of `#[docify::export]` where `process:Command` is used, and replace the use of `process:Command` by `run_cmd!` from the `cmd_lib crate`. --------- Co-authored-by: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> --- Cargo.lock | 2 + docs/sdk/Cargo.toml | 1 + docs/sdk/src/guides/your_first_node.rs | 24 +- .../src/reference_docs/chain_spec_genesis.rs | 8 +- .../chain_spec_runtime/Cargo.toml | 1 + .../tests/chain_spec_builder_tests.rs | 290 +++++++++--------- prdoc/pr_6624.prdoc | 11 + 7 files changed, 171 insertions(+), 166 deletions(-) create mode 100644 prdoc/pr_6624.prdoc diff --git a/Cargo.lock b/Cargo.lock index 8aa03954467d..989430fdfe29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3171,6 +3171,7 @@ dependencies = [ name = "chain-spec-guide-runtime" version = "0.0.0" dependencies = [ + "cmd_lib", "docify", "frame-support 28.0.0", "pallet-balances 28.0.0", @@ -19054,6 +19055,7 @@ version = "0.0.1" dependencies = [ "assert_cmd", "chain-spec-guide-runtime", + "cmd_lib", "cumulus-client-service", "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index 0c39367eeed3..0e4a5c001382 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -138,4 +138,5 @@ first-pallet = { workspace = true, default-features = true } [dev-dependencies] assert_cmd = "2.0.14" +cmd_lib = { workspace = true } rand = "0.8" diff --git a/docs/sdk/src/guides/your_first_node.rs b/docs/sdk/src/guides/your_first_node.rs index da37c11c206f..3c782e4793ba 100644 --- a/docs/sdk/src/guides/your_first_node.rs +++ b/docs/sdk/src/guides/your_first_node.rs @@ -103,6 +103,7 @@ #[cfg(test)] mod tests { use assert_cmd::Command; + use cmd_lib::*; use rand::Rng; use sc_chain_spec::{DEV_RUNTIME_PRESET, LOCAL_TESTNET_RUNTIME_PRESET}; use sp_genesis_builder::PresetId; @@ -173,13 +174,10 @@ mod tests { println!("Building polkadot-sdk-docs-first-runtime..."); #[docify::export_content] fn build_runtime() { - Command::new("cargo") - .arg("build") - .arg("--release") - .arg("-p") - .arg(FIRST_RUNTIME) - .assert() - .success(); + run_cmd!( + cargo build --release -p $FIRST_RUNTIME + ) + .expect("Failed to run command"); } build_runtime() } @@ -274,14 +272,10 @@ mod tests { let chain_spec_builder = find_release_binary(&CHAIN_SPEC_BUILDER).unwrap(); let runtime_path = find_wasm(PARA_RUNTIME).unwrap(); let output = "/tmp/demo-chain-spec.json"; - Command::new(chain_spec_builder) - .args(["-c", output]) - .arg("create") - .args(["--para-id", "1000", "--relay-chain", "dontcare"]) - .args(["-r", runtime_path.to_str().unwrap()]) - .args(["named-preset", "development"]) - .assert() - .success(); + let runtime_str = runtime_path.to_str().unwrap(); + run_cmd!( + $chain_spec_builder -c $output create --para-id 1000 --relay-chain dontcare -r $runtime_str named-preset development + ).expect("Failed to run command"); std::fs::remove_file(output).unwrap(); } build_para_chain_spec_works(); diff --git a/docs/sdk/src/reference_docs/chain_spec_genesis.rs b/docs/sdk/src/reference_docs/chain_spec_genesis.rs index b7a0a648d0cf..d5cc482711ad 100644 --- a/docs/sdk/src/reference_docs/chain_spec_genesis.rs +++ b/docs/sdk/src/reference_docs/chain_spec_genesis.rs @@ -174,13 +174,13 @@ //! ``` //! Here are some examples in the form of rust tests: //! ## Listing available preset names: -#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", list_presets)] +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_list_presets)] //! ## Displaying preset with given name -#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", get_preset)] +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_get_preset)] //! ## Building a solo chain-spec (the default) using given preset -#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", generate_chain_spec)] +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_generate_chain_spec)] //! ## Building a parachain chain-spec using given preset -#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", generate_para_chain_spec)] +#![doc = docify::embed!("./src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs", cmd_generate_para_chain_spec)] //! //! [`RuntimeGenesisConfig`]: //! chain_spec_guide_runtime::runtime::RuntimeGenesisConfig diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml b/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml index 07c0342f5fbe..307e2daa38c6 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml @@ -42,6 +42,7 @@ substrate-wasm-builder = { optional = true, workspace = true, default-features = [dev-dependencies] chain-spec-builder = { workspace = true, default-features = true } +cmd_lib = { workspace = true } sc-chain-spec = { workspace = true, default-features = true } [features] diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs b/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs index df400b68f79d..b773af24de80 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/tests/chain_spec_builder_tests.rs @@ -1,5 +1,6 @@ +use cmd_lib::*; use serde_json::{json, Value}; -use std::{process::Command, str}; +use std::str; fn wasm_file_path() -> &'static str { chain_spec_guide_runtime::runtime::WASM_BINARY_PATH @@ -8,189 +9,184 @@ fn wasm_file_path() -> &'static str { const CHAIN_SPEC_BUILDER_PATH: &str = "../../../../../target/release/chain-spec-builder"; +macro_rules! bash( + ( chain-spec-builder $($a:tt)* ) => {{ + let path = get_chain_spec_builder_path(); + spawn_with_output!( + $path $($a)* + ) + .expect("a process running. qed") + .wait_with_output() + .expect("to get output. qed.") + + }} +); + fn get_chain_spec_builder_path() -> &'static str { - // dev-dependencies do not build binary. So let's do the naive work-around here: - let _ = std::process::Command::new("cargo") - .arg("build") - .arg("--release") - .arg("-p") - .arg("staging-chain-spec-builder") - .arg("--bin") - .arg("chain-spec-builder") - .status() - .expect("Failed to execute command"); + run_cmd!( + cargo build --release -p staging-chain-spec-builder --bin chain-spec-builder + ) + .expect("Failed to execute command"); CHAIN_SPEC_BUILDER_PATH } +#[docify::export_content] +fn cmd_list_presets(runtime_path: &str) -> String { + bash!( + chain-spec-builder list-presets -r $runtime_path + ) +} + #[test] -#[docify::export] fn list_presets() { - let output = Command::new(get_chain_spec_builder_path()) - .arg("list-presets") - .arg("-r") - .arg(wasm_file_path()) - .output() - .expect("Failed to execute command"); - - let output: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); + let output: serde_json::Value = + serde_json::from_slice(cmd_list_presets(wasm_file_path()).as_bytes()).unwrap(); + assert_eq!( + output, + json!({ + "presets":[ + "preset_1", + "preset_2", + "preset_3", + "preset_4", + "preset_invalid" + ] + }), + "Output did not match expected" + ); +} - let expected_output = json!({ - "presets":[ - "preset_1", - "preset_2", - "preset_3", - "preset_4", - "preset_invalid" - ] - }); - assert_eq!(output, expected_output, "Output did not match expected"); +#[docify::export_content] +fn cmd_get_preset(runtime_path: &str) -> String { + bash!( + chain-spec-builder display-preset -r $runtime_path -p preset_2 + ) } #[test] -#[docify::export] fn get_preset() { - let output = Command::new(get_chain_spec_builder_path()) - .arg("display-preset") - .arg("-r") - .arg(wasm_file_path()) - .arg("-p") - .arg("preset_2") - .output() - .expect("Failed to execute command"); - - let output: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); - - //note: copy of chain_spec_guide_runtime::preset_2 - let expected_output = json!({ - "bar": { - "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", - }, - "foo": { - "someEnum": { - "Data2": { - "values": "0x0c10" - } + let output: serde_json::Value = + serde_json::from_slice(cmd_get_preset(wasm_file_path()).as_bytes()).unwrap(); + assert_eq!( + output, + json!({ + "bar": { + "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", }, - "someInteger": 200 - }, - }); - assert_eq!(output, expected_output, "Output did not match expected"); + "foo": { + "someEnum": { + "Data2": { + "values": "0x0c10" + } + }, + "someInteger": 200 + }, + }), + "Output did not match expected" + ); +} + +#[docify::export_content] +fn cmd_generate_chain_spec(runtime_path: &str) -> String { + bash!( + chain-spec-builder -c /dev/stdout create -r $runtime_path named-preset preset_2 + ) } #[test] -#[docify::export] fn generate_chain_spec() { - let output = Command::new(get_chain_spec_builder_path()) - .arg("-c") - .arg("/dev/stdout") - .arg("create") - .arg("-r") - .arg(wasm_file_path()) - .arg("named-preset") - .arg("preset_2") - .output() - .expect("Failed to execute command"); - - let mut output: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); - - //remove code field for better readability + let mut output: serde_json::Value = + serde_json::from_slice(cmd_generate_chain_spec(wasm_file_path()).as_bytes()).unwrap(); if let Some(code) = output["genesis"]["runtimeGenesis"].as_object_mut().unwrap().get_mut("code") { *code = Value::String("0x123".to_string()); } - - let expected_output = json!({ - "name": "Custom", - "id": "custom", - "chainType": "Live", - "bootNodes": [], - "telemetryEndpoints": null, - "protocolId": null, - "properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" }, - "codeSubstitutes": {}, - "genesis": { - "runtimeGenesis": { - "code": "0x123", - "patch": { - "bar": { - "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL" - }, - "foo": { - "someEnum": { - "Data2": { - "values": "0x0c10" + assert_eq!( + output, + json!({ + "name": "Custom", + "id": "custom", + "chainType": "Live", + "bootNodes": [], + "telemetryEndpoints": null, + "protocolId": null, + "properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" }, + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "code": "0x123", + "patch": { + "bar": { + "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL" + }, + "foo": { + "someEnum": { + "Data2": { + "values": "0x0c10" + } + }, + "someInteger": 200 } - }, - "someInteger": 200 + } } } - } - } - }); - assert_eq!(output, expected_output, "Output did not match expected"); + }), + "Output did not match expected" + ); +} + +#[docify::export_content] +fn cmd_generate_para_chain_spec(runtime_path: &str) -> String { + bash!( + chain-spec-builder -c /dev/stdout create -c polkadot -p 1000 -r $runtime_path named-preset preset_2 + ) } #[test] -#[docify::export] fn generate_para_chain_spec() { - let output = Command::new(get_chain_spec_builder_path()) - .arg("-c") - .arg("/dev/stdout") - .arg("create") - .arg("-c") - .arg("polkadot") - .arg("-p") - .arg("1000") - .arg("-r") - .arg(wasm_file_path()) - .arg("named-preset") - .arg("preset_2") - .output() - .expect("Failed to execute command"); - - let mut output: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap(); - - //remove code field for better readability + let mut output: serde_json::Value = + serde_json::from_slice(cmd_generate_para_chain_spec(wasm_file_path()).as_bytes()).unwrap(); if let Some(code) = output["genesis"]["runtimeGenesis"].as_object_mut().unwrap().get_mut("code") { *code = Value::String("0x123".to_string()); } - - let expected_output = json!({ - "name": "Custom", - "id": "custom", - "chainType": "Live", - "bootNodes": [], - "telemetryEndpoints": null, - "protocolId": null, - "relay_chain": "polkadot", - "para_id": 1000, - "properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" }, - "codeSubstitutes": {}, - "genesis": { - "runtimeGenesis": { - "code": "0x123", - "patch": { - "bar": { - "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL" - }, - "foo": { - "someEnum": { - "Data2": { - "values": "0x0c10" - } + assert_eq!( + output, + json!({ + "name": "Custom", + "id": "custom", + "chainType": "Live", + "bootNodes": [], + "telemetryEndpoints": null, + "protocolId": null, + "relay_chain": "polkadot", + "para_id": 1000, + "properties": { "tokenDecimals": 12, "tokenSymbol": "UNIT" }, + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "code": "0x123", + "patch": { + "bar": { + "initialAccount": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL" }, - "someInteger": 200 + "foo": { + "someEnum": { + "Data2": { + "values": "0x0c10" + } + }, + "someInteger": 200 + } } } - } - } - }); - assert_eq!(output, expected_output, "Output did not match expected"); + }}), + "Output did not match expected" + ); } #[test] -#[docify::export] +#[docify::export_content] fn preset_4_json() { assert_eq!( chain_spec_guide_runtime::presets::preset_4(), diff --git a/prdoc/pr_6624.prdoc b/prdoc/pr_6624.prdoc new file mode 100644 index 000000000000..4db55a46e8df --- /dev/null +++ b/prdoc/pr_6624.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Use `cmd_lib` instead of `std::process::Command` when using `#[docify::export]` + +doc: + - audience: Runtime Dev + description: | + Simplified the display of commands and ensured they are tested for chain spec builder's `polkadot-sdk` reference docs. + +crates: [] \ No newline at end of file From c66fcb1d8b0de015f497d8f3dc7fe11c3368dc26 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 10 Dec 2024 13:57:10 +0200 Subject: [PATCH 138/340] change fee asset from weth to eth --- .../pallets/inbound-queue-v2/src/mock.rs | 65 +----------- .../pallets/inbound-queue-v2/src/test.rs | 63 +----------- .../primitives/router/src/inbound/v2.rs | 51 ++-------- .../src/tests/snowbridge_v2.rs | 99 +++++++++---------- .../src/bridge_to_ethereum_config.rs | 1 - 5 files changed, 57 insertions(+), 222 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index bdd2f3ea9bd0..974142553aaf 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -20,7 +20,7 @@ use sp_runtime::{ }; use sp_std::{convert::From, default::Default}; use xcm::{latest::SendXcm, prelude::*}; -use xcm_executor::{traits::TransactAsset, AssetsInHolding}; +use xcm::opaque::latest::WESTEND_GENESIS_HASH; type Block = frame_system::mocking::MockBlock; @@ -100,7 +100,6 @@ impl Verifier for MockVerifier { } const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; -const WETH_ADDRESS: [u8; 20] = hex!["fff9976782d46cc05630d1f6ebab18b2324d6b14"]; #[cfg(feature = "runtime-benchmarks")] impl BenchmarkHelper for Test { @@ -146,7 +145,6 @@ impl MaybeEquivalence for MockTokenIdConvert { parameter_types! { pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); - pub const WethAddress: H160 = H160(WETH_ADDRESS); pub const InboundQueuePalletInstance: u8 = 84; pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); pub UniversalLocation: InteriorLocation = @@ -166,7 +164,7 @@ impl inbound_queue_v2::Config for Test { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - WethAddress, + GatewayAddress, UniversalLocation, AssetHubFromEthereum, >; @@ -176,52 +174,6 @@ impl inbound_queue_v2::Config for Test { type Helper = Test; } -pub struct SuccessfulTransactor; -impl TransactAsset for SuccessfulTransactor { - fn can_check_in(_origin: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Ok(()) - } - - fn can_check_out(_dest: &Location, _what: &Asset, _context: &XcmContext) -> XcmResult { - Ok(()) - } - - fn deposit_asset(_what: &Asset, _who: &Location, _context: Option<&XcmContext>) -> XcmResult { - Ok(()) - } - - fn withdraw_asset( - _what: &Asset, - _who: &Location, - _context: Option<&XcmContext>, - ) -> Result { - Ok(AssetsInHolding::default()) - } - - fn internal_transfer_asset( - _what: &Asset, - _from: &Location, - _to: &Location, - _context: &XcmContext, - ) -> Result { - Ok(AssetsInHolding::default()) - } -} - -pub fn last_events(n: usize) -> Vec { - frame_system::Pallet::::events() - .into_iter() - .rev() - .take(n) - .rev() - .map(|e| e.event) - .collect() -} - -pub fn expect_events(e: Vec) { - assert_eq!(last_events(e.len()), e); -} - pub fn setup() { System::set_block_number(1); } @@ -253,19 +205,6 @@ pub fn mock_event_log() -> Log { } } -pub fn mock_event_log_invalid_channel() -> Log { - Log { - address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), - topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - // invalid channel id - hex!("0000000000000000000000000000000000000000000000000000000000000000").into(), - hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), - ], - data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), - } -} - pub fn mock_event_log_invalid_gateway() -> Log { Log { // gateway address diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index a272cbf525fa..db321576917e 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -3,48 +3,11 @@ use super::*; use frame_support::{assert_noop, assert_ok}; -use hex_literal::hex; use snowbridge_core::inbound::Proof; use sp_keyring::AccountKeyring as Keyring; use sp_runtime::DispatchError; -use crate::{mock::*, Error, Event as InboundQueueEvent}; -use codec::DecodeLimit; -use snowbridge_router_primitives::inbound::v2::Asset; -use sp_core::H256; -use xcm::{ - opaque::latest::prelude::{ClearOrigin, ReceiveTeleportedAsset}, - prelude::*, - VersionedXcm, MAX_XCM_DECODE_DEPTH, -}; - -#[test] -fn test_submit_happy_path() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - - let origin = RuntimeOrigin::signed(relayer.clone()); - - // Submit message - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - expect_events(vec![InboundQueueEvent::MessageReceived { - nonce: 1, - message_id: [ - 183, 243, 1, 130, 170, 254, 104, 45, 116, 181, 146, 237, 14, 139, 138, 89, 43, 166, - 182, 24, 163, 222, 112, 238, 215, 83, 21, 160, 24, 88, 112, 9, - ], - } - .into()]); - }); -} +use crate::{mock::*, Error}; #[test] fn test_submit_with_invalid_gateway() { @@ -67,30 +30,6 @@ fn test_submit_with_invalid_gateway() { }); } -#[test] -fn test_submit_with_invalid_nonce() { - new_tester().execute_with(|| { - let relayer: AccountId = Keyring::Bob.into(); - let origin = RuntimeOrigin::signed(relayer); - - // Submit message - let message = Message { - event_log: mock_event_log(), - proof: Proof { - receipt_proof: Default::default(), - execution_proof: mock_execution_proof(), - }, - }; - assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - - // Submit the same again - assert_noop!( - InboundQueue::submit(origin.clone(), message.clone()), - Error::::InvalidNonce - ); - }); -} - #[test] fn test_set_operating_mode() { new_tester().execute_with(|| { diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 28452f3d99b1..2defe5166bcf 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -76,7 +76,6 @@ pub struct MessageToXcm< EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, - WethAddress, GatewayProxyAddress, EthereumUniversalLocation, GlobalAssetHubLocation, @@ -84,7 +83,6 @@ pub struct MessageToXcm< EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, - WethAddress: Get, GatewayProxyAddress: Get, EthereumUniversalLocation: Get, GlobalAssetHubLocation: Get, @@ -93,7 +91,6 @@ pub struct MessageToXcm< EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, - WethAddress, GatewayProxyAddress, EthereumUniversalLocation, GlobalAssetHubLocation, @@ -104,7 +101,6 @@ impl< EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, - WethAddress, GatewayProxyAddress, EthereumUniversalLocation, GlobalAssetHubLocation, @@ -113,7 +109,6 @@ impl< EthereumNetwork, InboundQueuePalletInstance, ConvertAssetId, - WethAddress, GatewayProxyAddress, EthereumUniversalLocation, GlobalAssetHubLocation, @@ -122,7 +117,6 @@ where EthereumNetwork: Get, InboundQueuePalletInstance: Get, ConvertAssetId: MaybeEquivalence, - WethAddress: Get, GatewayProxyAddress: Get, EthereumUniversalLocation: Get, GlobalAssetHubLocation: Get, @@ -149,19 +143,14 @@ where let network = EthereumNetwork::get(); - // use weth as asset - let fee_asset = Location::new( - 2, - [ - GlobalConsensus(EthereumNetwork::get()), - AccountKey20 { network: None, key: WethAddress::get().into() }, - ], - ); + // use eth as asset + let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); let fee: XcmAsset = (fee_asset.clone(), message.execution_fee).into(); + let eth: XcmAsset = (fee_asset.clone(), message.execution_fee.saturating_add(message.value)).into(); let mut instructions = vec![ DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), UniversalOrigin(GlobalConsensus(network)), - ReserveAssetDeposited(fee.clone().into()), + ReserveAssetDeposited(eth.into()), PayFees { asset: fee }, ]; let mut reserve_assets = vec![]; @@ -253,12 +242,9 @@ mod tests { use sp_runtime::traits::MaybeEquivalence; use xcm::{opaque::latest::WESTEND_GENESIS_HASH, prelude::*}; const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; - const WETH_ADDRESS: [u8; 20] = hex!["fff9976782d46cc05630d1f6ebab18b2324d6b14"]; - parameter_types! { pub const EthereumNetwork: xcm::v5::NetworkId = xcm::v5::NetworkId::Ethereum { chain_id: 11155111 }; pub const GatewayAddress: H160 = H160(GATEWAY_ADDRESS); - pub const WethAddress: H160 = H160(WETH_ADDRESS); pub const InboundQueuePalletInstance: u8 = 84; pub AssetHubLocation: InteriorLocation = Parachain(1000).into(); pub UniversalLocation: InteriorLocation = @@ -329,7 +315,6 @@ mod tests { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - WethAddress, GatewayAddress, UniversalLocation, AssetHubFromEthereum, @@ -361,13 +346,7 @@ mod tests { } } if let PayFees { ref asset } = instruction { - let fee_asset = Location::new( - 2, - [ - GlobalConsensus(EthereumNetwork::get()), - AccountKey20 { network: None, key: WethAddress::get().into() }, - ], - ); + let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); assert_eq!(asset.id, AssetId(fee_asset)); assert_eq!(asset.fun, Fungible(execution_fee)); pay_fees_found = true; @@ -375,13 +354,7 @@ mod tests { if let ReserveAssetDeposited(ref reserve_assets) = instruction { reserve_deposited_found = reserve_deposited_found + 1; if reserve_deposited_found == 1 { - let fee_asset = Location::new( - 2, - [ - GlobalConsensus(EthereumNetwork::get()), - AccountKey20 { network: None, key: WethAddress::get().into() }, - ], - ); + let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); let fee: XcmAsset = (fee_asset, execution_fee).into(); let fee_assets: Assets = fee.into(); assert_eq!(fee_assets, reserve_assets.clone()); @@ -464,7 +437,6 @@ mod tests { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - WethAddress, GatewayAddress, UniversalLocation, AssetHubFromEthereum, @@ -524,7 +496,6 @@ mod tests { EthereumNetwork, InboundQueuePalletInstance, MockFailedTokenConvert, - WethAddress, GatewayAddress, UniversalLocation, AssetHubFromEthereum, @@ -573,7 +544,6 @@ mod tests { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - WethAddress, GatewayAddress, UniversalLocation, AssetHubFromEthereum, @@ -609,13 +579,7 @@ mod tests { assert!(last.is_some()); assert!(second_last.is_some()); - let fee_asset = Location::new( - 2, - [ - GlobalConsensus(EthereumNetwork::get()), - AccountKey20 { network: None, key: WethAddress::get().into() }, - ], - ); + let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); assert_eq!( last, Some(DepositAsset { @@ -657,7 +621,6 @@ mod tests { EthereumNetwork, InboundQueuePalletInstance, MockTokenIdConvert, - WethAddress, GatewayAddress, UniversalLocation, AssetHubFromEthereum, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 101af63adfca..6fb1f0354bce 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -43,6 +43,10 @@ const WETH: [u8; 20] = hex!("fff9976782d46cc05630d1f6ebab18b2324d6b14"); const TOKEN_ID: [u8; 20] = hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"); const CHAIN_ID: u64 = 11155111u64; +pub fn eth_location() -> Location { + Location::new(2, [GlobalConsensus(EthereumNetwork::get().into())]) +} + pub fn weth_location() -> Location { erc20_token_location(WETH.into()) } @@ -63,9 +67,9 @@ fn register_token_v2() { let receiver = AssetHubWestendReceiver::get(); BridgeHubWestend::fund_accounts(vec![(relayer.clone(), INITIAL_FUND)]); - register_foreign_asset(weth_location()); + register_foreign_asset(eth_location()); - set_up_weth_and_dot_pool(weth_location()); + set_up_eth_and_dot_pool(eth_location()); let claimer = AccountId32 { network: None, id: receiver.clone().into() }; let claimer_bytes = claimer.encode(); @@ -81,15 +85,13 @@ fn register_token_v2() { let dot_asset = Location::new(1, Here); let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); - // Used to pay the asset creation deposit. - let weth_asset_value = 9_000_000_000_000u128; - let assets = vec![NativeTokenERC20 { token_id: WETH.into(), value: weth_asset_value }]; - let asset_deposit: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); + let eth_asset_value = 9_000_000_000_000u128; + let asset_deposit: xcm::prelude::Asset = (eth_location(), eth_asset_value).into(); BridgeHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; let instructions = vec![ - // Exchange weth for dot to pay the asset creation deposit + // Exchange eth for dot to pay the asset creation deposit ExchangeAsset { give: asset_deposit.clone().into(), want: dot_fee.clone().into(), @@ -119,10 +121,11 @@ fn register_token_v2() { let message = Message { origin, - assets, + assets: vec![], xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), - value: 3_500_000_000_000u128, + // Used to pay the asset creation deposit. + value: 9_000_000_000_000u128, execution_fee: 1_500_000_000_000u128, relayer_fee: 1_500_000_000_000u128, }; @@ -164,14 +167,12 @@ fn send_token_v2() { let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; let claimer_bytes = claimer.encode(); - register_foreign_asset(weth_location()); + register_foreign_asset(eth_location()); register_foreign_asset(token_location.clone()); let token_transfer_value = 2_000_000_000_000u128; let assets = vec![ - // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: 1_500_000_000_000u128 }, // the token being transferred NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, ]; @@ -194,7 +195,7 @@ fn send_token_v2() { assets, xcm: versioned_message_xcm.encode(), claimer: Some(claimer_bytes), - value: 3_500_000_000_000u128, + value: 1_500_000_000_000u128, execution_fee: 1_500_000_000_000u128, relayer_fee: 1_500_000_000_000u128, }; @@ -223,8 +224,8 @@ fn send_token_v2() { token_transfer_value ); - // Claimer received weth refund for fees paid - assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); + // Claimer received eth refund for fees paid + assert!(ForeignAssets::balance(eth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); }); } @@ -244,14 +245,14 @@ fn send_weth_v2() { let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; let claimer_bytes = claimer.encode(); + register_foreign_asset(eth_location()); register_foreign_asset(weth_location()); let token_transfer_value = 2_000_000_000_000u128; let assets = vec![ - // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, // the token being transferred + NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, ]; BridgeHubWestend::execute_with(|| { @@ -289,7 +290,7 @@ fn send_weth_v2() { AssetHubWestend::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; - // Check that the token was received and issued as a foreign asset on AssetHub + // Check that the weth was received and issued as a foreign asset on AssetHub assert_expected_events!( AssetHubWestend, vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},] @@ -301,8 +302,8 @@ fn send_weth_v2() { token_transfer_value ); - // Claimer received weth refund for fees paid - assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); + // Claimer received eth refund for fees paid + assert!(ForeignAssets::balance(eth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); }); } @@ -333,9 +334,10 @@ fn register_and_send_multiple_tokens_v2() { let claimer = AccountId32 { network: None, id: claimer_acc_id.into() }; let claimer_bytes = claimer.encode(); + register_foreign_asset(eth_location()); register_foreign_asset(weth_location()); - set_up_weth_and_dot_pool(weth_location()); + set_up_eth_and_dot_pool(eth_location()); let token_transfer_value = 2_000_000_000_000u128; let weth_transfer_value = 2_500_000_000_000u128; @@ -344,13 +346,11 @@ fn register_and_send_multiple_tokens_v2() { let dot_fee: xcm::prelude::Asset = (dot_asset, CreateAssetDeposit::get()).into(); // Used to pay the asset creation deposit. - let weth_asset_value = 9_000_000_000_000u128; - let asset_deposit: xcm::prelude::Asset = (weth_location(), weth_asset_value).into(); + let eth_asset_value = 9_000_000_000_000u128; + let asset_deposit: xcm::prelude::Asset = (eth_location(), eth_asset_value).into(); let assets = vec![ - // to pay fees and transfer assets NativeTokenERC20 { token_id: WETH.into(), value: 2_800_000_000_000u128 }, - // the token being transferred NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, ]; @@ -444,8 +444,8 @@ fn register_and_send_multiple_tokens_v2() { weth_transfer_value ); - // Claimer received weth refund for fees paid - assert!(ForeignAssets::balance(weth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); + // Claimer received eth refund for fees paid + assert!(ForeignAssets::balance(eth_location(), AccountId::from(claimer_acc_id_bytes)) > 0); }); } @@ -468,9 +468,9 @@ fn send_token_to_penpal_v2() { let claimer_bytes = claimer.encode(); // To pay fees on Penpal. - let weth_fee_penpal: xcm::prelude::Asset = (weth_location(), 3_000_000_000_000u128).into(); + let eth_fee_penpal: xcm::prelude::Asset = (eth_location(), 3_000_000_000_000u128).into(); - register_foreign_asset(weth_location()); + register_foreign_asset(eth_location()); register_foreign_asset(token_location.clone()); // To satisfy ED @@ -497,17 +497,17 @@ fn send_token_to_penpal_v2() { token_location.clone().try_into().unwrap(), )); - // Register weth on Penpal + // Register eth on Penpal assert_ok!(::ForeignAssets::force_create( RuntimeOrigin::root(), - weth_location().try_into().unwrap(), + eth_location().try_into().unwrap(), penpal_sovereign.clone().into(), true, 1000, )); assert!(::ForeignAssets::asset_exists( - weth_location().try_into().unwrap(), + eth_location().try_into().unwrap(), )); assert_ok!(::System::set_storage( @@ -519,15 +519,12 @@ fn send_token_to_penpal_v2() { )); }); - set_up_weth_and_dot_pool(weth_location()); - - set_up_weth_and_dot_pool_on_penpal(weth_location()); + set_up_eth_and_dot_pool(eth_location()); + set_up_eth_and_dot_pool_on_penpal(eth_location()); let token_transfer_value = 2_000_000_000_000u128; let assets = vec![ - // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: 5_000_000_000_000u128 }, // the token being transferred NativeTokenERC20 { token_id: token.into(), value: token_transfer_value }, ]; @@ -538,13 +535,13 @@ fn send_token_to_penpal_v2() { let instructions = vec![ // Send message to Penpal DepositReserveAsset { - // Send the token plus some weth for execution fees - assets: Definite(vec![weth_fee_penpal.clone(), token_asset].into()), + // Send the token plus some eth for execution fees + assets: Definite(vec![eth_fee_penpal.clone(), token_asset].into()), // Penpal dest: Location::new(1, [Parachain(PARA_ID_B)]), xcm: vec![ // Pay fees on Penpal. - PayFees { asset: weth_fee_penpal }, + PayFees { asset: eth_fee_penpal }, // Deposit assets to beneficiary. DepositAsset { assets: Wild(AllOf { @@ -624,7 +621,7 @@ fn send_foreign_erc20_token_back_to_polkadot() { let asset_id: Location = [PalletInstance(ASSETS_PALLET_ID), GeneralIndex(RESERVABLE_ASSET_ID.into())].into(); - register_foreign_asset(weth_location()); + register_foreign_asset(eth_location()); let asset_id_in_bh: Location = Location::new( 1, @@ -679,8 +676,6 @@ fn send_foreign_erc20_token_back_to_polkadot() { let token_id = TokenIdOf::convert_location(&asset_id_after_reanchored).unwrap(); let assets = vec![ - // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: 3_000_000_000_000u128 }, // the token being transferred ForeignTokenERC20 { token_id: token_id.into(), value: TOKEN_AMOUNT }, ]; @@ -759,12 +754,12 @@ fn invalid_xcm_traps_funds_on_ah() { 3_000_000_000_000, )]); - register_foreign_asset(weth_location()); + register_foreign_asset(eth_location()); - set_up_weth_and_dot_pool(weth_location()); + set_up_eth_and_dot_pool(eth_location()); let assets = vec![ - // to pay fees and transfer assets + // to transfer assets NativeTokenERC20 { token_id: WETH.into(), value: 2_800_000_000_000u128 }, // the token being transferred NativeTokenERC20 { token_id: token.into(), value: 2_000_000_000_000u128 }, @@ -815,14 +810,14 @@ fn invalid_claimer_does_not_fail_the_message() { let beneficiary_acc: [u8; 32] = H256::random().into(); let beneficiary = Location::new(0, AccountId32 { network: None, id: beneficiary_acc.into() }); + register_foreign_asset(eth_location()); register_foreign_asset(weth_location()); let token_transfer_value = 2_000_000_000_000u128; let assets = vec![ - // to pay fees - NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, // the token being transferred + NativeTokenERC20 { token_id: WETH.into(), value: token_transfer_value }, ]; BridgeHubWestend::execute_with(|| { @@ -874,8 +869,8 @@ fn invalid_claimer_does_not_fail_the_message() { token_transfer_value ); - // Relayer (instead of claimer) received weth refund for fees paid - assert!(ForeignAssets::balance(weth_location(), AccountId::from(relayer)) > 0); + // Relayer (instead of claimer) received eth refund for fees paid + assert!(ForeignAssets::balance(eth_location(), AccountId::from(relayer)) > 0); }); } @@ -899,7 +894,7 @@ pub fn register_foreign_asset(token_location: Location) { }); } -pub(crate) fn set_up_weth_and_dot_pool(asset: v5::Location) { +pub(crate) fn set_up_eth_and_dot_pool(asset: v5::Location) { let wnd: v5::Location = v5::Parent.into(); let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); let owner = AssetHubWestendSender::get(); @@ -961,7 +956,7 @@ pub(crate) fn set_up_weth_and_dot_pool(asset: v5::Location) { }); } -pub(crate) fn set_up_weth_and_dot_pool_on_penpal(asset: v5::Location) { +pub(crate) fn set_up_eth_and_dot_pool_on_penpal(asset: v5::Location) { let wnd: v5::Location = v5::Parent.into(); let penpal_location = BridgeHubWestend::sibling_location_of(PenpalB::para_id()); let owner = PenpalBSender::get(); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index b05fa357fb98..2de53d503118 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -123,7 +123,6 @@ impl snowbridge_pallet_inbound_queue_v2::Config for Runtime { EthereumNetwork, ConstU8, EthereumSystem, - WethAddress, EthereumGatewayAddress, EthereumUniversalLocation, AssetHubFromEthereum, From 65a4e5ee06b11844d536730379d4e1cab337beb4 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:40:28 +0200 Subject: [PATCH 139/340] Fix order of resending messages after restart (#6729) The way we build the messages we need to send to approval-distribution can result in a situation where is we have multiple assignments covered by a coalesced approval, the messages are sent in this order: ASSIGNMENT1, APPROVAL, ASSIGNMENT2, because we iterate over each candidate and add to the queue of messages both the assignment and the approval for that candidate, and when the approval reaches the approval-distribution subsystem it won't be imported and gossiped because one of the assignment for it is not known. So in a network where a lot of nodes are restarting at the same time we could end up in a situation where a set of the nodes correctly received the assignments and approvals before the restart and approve their blocks and don't trigger their assignments. The other set of nodes should receive the assignments and approvals after the restart, but because the approvals never get broacasted anymore because of this bug, the only way they could approve is if other nodes start broadcasting their assignments. I think this bug contribute to the reason the network did not recovered on `25-11-25 15:55:40` after the restarts. Tested this scenario with a `zombienet` where `nodes` are finalising blocks because of aggression and all nodes are restarted at once and confirmed the network lags and doesn't recover before and it does after the fix --------- Signed-off-by: Alexandru Gheorghe --- polkadot/node/core/approval-voting/src/lib.rs | 12 +- .../node/core/approval-voting/src/tests.rs | 314 ++++++++++++++++++ prdoc/pr_6729.prdoc | 15 + 3 files changed, 338 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_6729.prdoc diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 2176cc7675be..7cea22d1a6a7 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -1582,8 +1582,9 @@ async fn handle_actions< session_info_provider, ) .await?; - - approval_voting_sender.send_messages(messages.into_iter()).await; + for message in messages.into_iter() { + approval_voting_sender.send_unbounded_message(message); + } let next_actions: Vec = next_actions.into_iter().map(|v| v.clone()).chain(actions_iter).collect(); @@ -1668,6 +1669,7 @@ async fn distribution_messages_for_activation, +) -> (ChainBuilder, SessionInfo) { + let validators = vec![ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Eve, + ]; + let session_info = SessionInfo { + validator_groups: IndexedVec::>::from(vec![ + vec![ValidatorIndex(0), ValidatorIndex(1)], + vec![ValidatorIndex(2)], + vec![ValidatorIndex(3), ValidatorIndex(4)], + ]), + ..session_info(&validators) + }; + + let candidates = Some( + candidate_receipt + .iter() + .enumerate() + .map(|(i, receipt)| (receipt.clone(), CoreIndex(i as u32), GroupIndex(i as u32))) + .collect(), + ); + let mut chain_builder = ChainBuilder::new(); + + chain_builder + .major_syncing(sync_oracle_handle.is_major_syncing.clone()) + .add_block( + block_hash1, + ChainBuilder::GENESIS_HASH, + 1, + BlockConfig { + slot, + candidates: candidates.clone(), + session_info: Some(session_info.clone()), + end_syncing: true, + }, + ); + (chain_builder, session_info) +} + +async fn setup_overseer_with_blocks_with_two_assignments_triggered( + virtual_overseer: &mut VirtualOverseer, + store: TestStore, + clock: &Arc, + sync_oracle_handle: TestSyncOracleHandle, +) { + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + let candidate_hash = candidate_receipt.hash(); + + let mut candidate_commitments2 = CandidateCommitments::default(); + candidate_commitments2.processed_downward_messages = 3; + let mut candidate_receipt2 = dummy_candidate_receipt_v2(block_hash); + candidate_receipt2.commitments_hash = candidate_commitments2.hash(); + let candidate_hash2 = candidate_receipt2.hash(); + + let slot = Slot::from(1); + let (chain_builder, _session_info) = build_chain_with_block_with_two_candidates( + block_hash, + slot, + sync_oracle_handle, + vec![candidate_receipt, candidate_receipt2], + ) + .await; + chain_builder.build(virtual_overseer).await; + + assert!(!clock.inner.lock().current_wakeup_is(1)); + clock.inner.lock().wakeup_all(1); + + assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot))); + clock.inner.lock().wakeup_all(slot_to_tick(slot)); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + clock.inner.lock().wakeup_all(slot_to_tick(slot + 2)); + + assert_eq!(clock.inner.lock().wakeups.len(), 0); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(our_assignment.triggered()); + + let candidate_entry = store.load_candidate_entry(&candidate_hash2).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(our_assignment.triggered()); +} + // Tests that for candidates that we did not approve yet, for which we triggered the assignment and // the approval work we restart the work to approve it. #[test] @@ -4920,6 +5028,212 @@ fn subsystem_sends_pending_approvals_on_approval_restart() { }); } +// Test that after restart approvals are sent after all assignments have been distributed. +#[test] +fn subsystem_sends_assignment_approval_in_correct_order_on_approval_restart() { + let assignment_criteria = Box::new(MockAssignmentCriteria( + || { + let mut assignments = HashMap::new(); + + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: vec![CoreIndex(0), CoreIndex(2)].try_into().unwrap(), + }), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + let _ = assignments.insert( + CoreIndex(1), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFDelay { + core_index: CoreIndex(1), + }), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + assignments + }, + |_| Ok(0), + )); + let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build(); + let store = config.backend(); + let store_clone = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + + setup_overseer_with_blocks_with_two_assignments_triggered( + &mut virtual_overseer, + store, + &clock, + sync_oracle_handle, + ) + .await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _ + )) => { + } + ); + + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive { + exec_kind, + response_sender, + .. + }) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + .unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive { + exec_kind, + response_sender, + .. + }) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + .unwrap(); + } + ); + + // Configure a big coalesce number, so that the signature is cached instead of being sent to + // approval-distribution. + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 2, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 2, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); + + let config = HarnessConfigBuilder::default().backend(store_clone).major_syncing(true).build(); + // On restart we should first distribute all assignments covering a coalesced approval. + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + + let mut candidate_commitments2 = CandidateCommitments::default(); + candidate_commitments2.processed_downward_messages = 3; + let mut candidate_receipt2 = dummy_candidate_receipt_v2(block_hash); + candidate_receipt2.commitments_hash = candidate_commitments2.hash(); + + let slot = Slot::from(1); + + clock.inner.lock().set_tick(slot_to_tick(slot + 2)); + let (chain_builder, _session_info) = build_chain_with_block_with_two_candidates( + block_hash, + slot, + sync_oracle_handle, + vec![candidate_receipt.into(), candidate_receipt2.into()], + ) + .await; + chain_builder.build(&mut virtual_overseer).await; + + futures_timer::Delay::new(Duration::from_millis(2000)).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks( + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(approval)) => { + assert_eq!(approval.candidate_indices.count_ones(), 2); + } + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); +} + // Test we correctly update the timer when we mark the beginning of gathering assignments. #[test] fn test_gathering_assignments_statements() { diff --git a/prdoc/pr_6729.prdoc b/prdoc/pr_6729.prdoc new file mode 100644 index 000000000000..9eaa67363c9a --- /dev/null +++ b/prdoc/pr_6729.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix order of resending messages after restart + +doc: + - audience: Node Dev + description: | + At restart when dealing with a coalesced approval we might end up in a situation where we sent to + approval-distribution the approval before all assignments covering it, in that case, the approval + is ignored and never distribute, which will lead to no-shows. + +crates: + - name: polkadot-node-core-approval-voting + bump: minor From ddece3ce4ca91dc906772fcadfa1f886912dc98b Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 10 Dec 2024 15:14:54 +0200 Subject: [PATCH 140/340] adds abi decoding --- Cargo.lock | 1 + .../pallets/inbound-queue-v2/src/mock.rs | 3 +- .../snowbridge/primitives/router/Cargo.toml | 2 + .../primitives/router/src/inbound/mod.rs | 1 + .../primitives/router/src/inbound/payload.rs | 91 +++++++++++++++++++ .../primitives/router/src/inbound/v2.rs | 5 +- 6 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 bridges/snowbridge/primitives/router/src/inbound/payload.rs diff --git a/Cargo.lock b/Cargo.lock index f482bea693a1..3a5a84a672bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25196,6 +25196,7 @@ dependencies = [ name = "snowbridge-router-primitives" version = "0.9.0" dependencies = [ + "alloy-sol-types", "frame-support 28.0.0", "frame-system 28.0.0", "hex-literal", diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index 974142553aaf..e603ab8e270a 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -19,8 +19,7 @@ use sp_runtime::{ BuildStorage, MultiSignature, }; use sp_std::{convert::From, default::Default}; -use xcm::{latest::SendXcm, prelude::*}; -use xcm::opaque::latest::WESTEND_GENESIS_HASH; +use xcm::{latest::SendXcm, opaque::latest::WESTEND_GENESIS_HASH, prelude::*}; type Block = frame_system::mocking::MockBlock; diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index aa4b3177c00b..77c8f3c1e9e2 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -15,6 +15,7 @@ workspace = true codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } log = { workspace = true } +alloy-sol-types = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } @@ -36,6 +37,7 @@ hex-literal = { workspace = true, default-features = true } [features] default = ["std"] std = [ + "alloy-sol-types/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index b494bc5b0e64..fc0a32163b49 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2023 Snowfork // SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. +pub mod payload; pub mod v1; pub mod v2; use codec::Encode; diff --git a/bridges/snowbridge/primitives/router/src/inbound/payload.rs b/bridges/snowbridge/primitives/router/src/inbound/payload.rs new file mode 100644 index 000000000000..3caa5641427f --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/payload.rs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +use crate::inbound::v2::{ + Asset::{ForeignTokenERC20, NativeTokenERC20}, + Message, +}; +use alloy_sol_types::{sol, SolType}; +use sp_core::{RuntimeDebug, H160, H256}; + +sol! { + struct AsNativeTokenERC20 { + address token_id; + uint128 value; + } +} + +sol! { + struct AsForeignTokenERC20 { + bytes32 token_id; + uint128 value; + } +} + +sol! { + struct EthereumAsset { + uint8 kind; + bytes data; + } +} + +sol! { + struct Payload { + address origin; + EthereumAsset[] assets; + bytes xcm; + bytes claimer; + uint128 value; + uint128 executionFee; + uint128 relayerFee; + } +} + +#[derive(Copy, Clone, RuntimeDebug)] +pub struct PayloadDecodeError; +impl TryFrom<&[u8]> for Message { + type Error = PayloadDecodeError; + + fn try_from(encoded_payload: &[u8]) -> Result { + let decoded_payload = + Payload::abi_decode(&encoded_payload, true).map_err(|_| PayloadDecodeError)?; + + let mut substrate_assets = vec![]; + + for asset in decoded_payload.assets { + match asset.kind { + 0 => { + let native_data = AsNativeTokenERC20::abi_decode(&asset.data, true) + .map_err(|_| PayloadDecodeError)?; + substrate_assets.push(NativeTokenERC20 { + token_id: H160::from(native_data.token_id.as_ref()), + value: native_data.value, + }); + }, + 1 => { + let foreign_data = AsForeignTokenERC20::abi_decode(&asset.data, true) + .map_err(|_| PayloadDecodeError)?; + substrate_assets.push(ForeignTokenERC20 { + token_id: H256::from(foreign_data.token_id.as_ref()), + value: foreign_data.value, + }); + }, + _ => return Err(PayloadDecodeError), + } + } + + let mut claimer = None; + if decoded_payload.claimer.len() > 0 { + claimer = Some(decoded_payload.claimer); + } + + Ok(Self { + origin: H160::from(decoded_payload.origin.as_ref()), + assets: substrate_assets, + xcm: decoded_payload.xcm, + claimer, + value: decoded_payload.value, + execution_fee: decoded_payload.executionFee, + relayer_fee: decoded_payload.relayerFee, + }) + } +} diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 2defe5166bcf..eb5cdcece065 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -146,7 +146,8 @@ where // use eth as asset let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); let fee: XcmAsset = (fee_asset.clone(), message.execution_fee).into(); - let eth: XcmAsset = (fee_asset.clone(), message.execution_fee.saturating_add(message.value)).into(); + let eth: XcmAsset = + (fee_asset.clone(), message.execution_fee.saturating_add(message.value)).into(); let mut instructions = vec![ DescendOrigin(PalletInstance(InboundQueuePalletInstance::get()).into()), UniversalOrigin(GlobalConsensus(network)), @@ -355,7 +356,7 @@ mod tests { reserve_deposited_found = reserve_deposited_found + 1; if reserve_deposited_found == 1 { let fee_asset = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]); - let fee: XcmAsset = (fee_asset, execution_fee).into(); + let fee: XcmAsset = (fee_asset, execution_fee + value).into(); let fee_assets: Assets = fee.into(); assert_eq!(fee_assets, reserve_assets.clone()); } From fe4846f54838c23de9fadf6ac92b0739e82c25b1 Mon Sep 17 00:00:00 2001 From: Ron Date: Wed, 11 Dec 2024 00:17:15 +0800 Subject: [PATCH 141/340] XCMv5: Fix for compatibility with V4 (#6503) ## Description Our smoke tests transfer `WETH` from Sepolia to Westend-AssetHub breaks, try to reregister `WETH` on AH but fails as following: https://bridgehub-westend.subscan.io/xcm_message/westend-4796d6b3600aca32ef63b9953acf6a456cfd2fbe https://assethub-westend.subscan.io/extrinsic/9731267-0?event=9731267-2 The reason is that the transact call encoded on BH to register the asset https://github.com/paritytech/polkadot-sdk/blob/a77940bac783108fcae783c553528c8d5328e5b2/bridges/snowbridge/primitives/router/src/inbound/mod.rs#L282-L289 ``` 0x3500020209079edaa8020300fff9976782d46cc05630d1f6ebab18b2324d6b1400ce796ae65569a670d0c1cc1ac12515a3ce21b5fbf729d63d7b289baad070139d01000000000000000000000000000000 ``` the `asset_id` which is the xcm location can't be decoded on AH in V5 Issue initial post in https://matrix.to/#/!qUtSTcfMJzBdPmpFKa:parity.io/$RNMAxIIOKGtBAqkgwiFuQf4eNaYpmOK-Pfw4d6vv1aU?via=parity.io&via=matrix.org&via=web3.foundation --------- Co-authored-by: Adrian Catangiu Co-authored-by: Francisco Aguirre --- .../pallets/inbound-queue/src/mock.rs | 14 ----------- .../pallets/inbound-queue/src/test.rs | 23 +++++++++---------- polkadot/xcm/src/v5/junction.rs | 4 ++++ prdoc/pr_6503.prdoc | 10 ++++++++ 4 files changed, 25 insertions(+), 26 deletions(-) create mode 100644 prdoc/pr_6503.prdoc diff --git a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs index 675d4b691593..eed0656e9ca7 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/mock.rs @@ -248,20 +248,6 @@ impl inbound_queue::Config for Test { type AssetTransactor = SuccessfulTransactor; } -pub fn last_events(n: usize) -> Vec { - frame_system::Pallet::::events() - .into_iter() - .rev() - .take(n) - .rev() - .map(|e| e.event) - .collect() -} - -pub fn expect_events(e: Vec) { - assert_eq!(last_events(e.len()), e); -} - pub fn setup() { System::set_block_number(1); Balances::mint_into( diff --git a/bridges/snowbridge/pallets/inbound-queue/src/test.rs b/bridges/snowbridge/pallets/inbound-queue/src/test.rs index 053a341b54a0..aa99d63b4bf9 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/test.rs @@ -9,7 +9,7 @@ use sp_keyring::Sr25519Keyring as Keyring; use sp_runtime::DispatchError; use sp_std::convert::From; -use crate::{Error, Event as InboundQueueEvent}; +use crate::Error; use crate::mock::*; @@ -35,17 +35,16 @@ fn test_submit_happy_path() { assert_eq!(Balances::balance(&channel_sovereign), initial_fund); assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); - expect_events(vec![InboundQueueEvent::MessageReceived { - channel_id: hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539") - .into(), - nonce: 1, - message_id: [ - 118, 166, 139, 182, 84, 52, 165, 189, 54, 14, 178, 73, 2, 228, 192, 97, 153, 201, - 4, 75, 151, 15, 82, 6, 164, 187, 162, 133, 26, 183, 186, 126, - ], - fee_burned: 110000000000, - } - .into()]); + + let events = frame_system::Pallet::::events(); + assert!( + events.iter().any(|event| matches!( + event.event, + RuntimeEvent::InboundQueue(Event::MessageReceived { nonce, ..}) + if nonce == 1 + )), + "no event emit." + ); let delivery_cost = InboundQueue::calculate_delivery_cost(message.encode().len() as u32); assert!( diff --git a/polkadot/xcm/src/v5/junction.rs b/polkadot/xcm/src/v5/junction.rs index 952b61cd9ffe..d86a762fcf44 100644 --- a/polkadot/xcm/src/v5/junction.rs +++ b/polkadot/xcm/src/v5/junction.rs @@ -143,16 +143,20 @@ pub enum NetworkId { /// The Kusama canary-net Relay-chain. Kusama, /// An Ethereum network specified by its chain ID. + #[codec(index = 7)] Ethereum { /// The EIP-155 chain ID. #[codec(compact)] chain_id: u64, }, /// The Bitcoin network, including hard-forks supported by Bitcoin Core development team. + #[codec(index = 8)] BitcoinCore, /// The Bitcoin network, including hard-forks supported by Bitcoin Cash developers. + #[codec(index = 9)] BitcoinCash, /// The Polkadot Bulletin chain. + #[codec(index = 10)] PolkadotBulletin, } diff --git a/prdoc/pr_6503.prdoc b/prdoc/pr_6503.prdoc new file mode 100644 index 000000000000..dc296a93f0eb --- /dev/null +++ b/prdoc/pr_6503.prdoc @@ -0,0 +1,10 @@ +title: "xcm: minor fix for compatibility with V4" + +doc: + - audience: ["Runtime Dev", "Runtime User"] + description: | + Following the removal of `Rococo`, `Westend` and `Wococo` from `NetworkId`, fixed `xcm::v5::NetworkId` encoding/decoding to be compatible with `xcm::v4::NetworkId` + +crates: +- name: staging-xcm + bump: patch From 48c28d4c8b396307fbc9130ad491cb7b15f99c4b Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Wed, 11 Dec 2024 00:33:13 +0200 Subject: [PATCH 142/340] omni-node: --dev sets manual seal and allows --chain to be set (#6646) # Description This PR changes a few things: * `--dev` flag will not conflict with `--chain` anymore, but if `--chain` is not given will set `--chain=dev`. * `--dev-block-time` is optional and it defaults to 3000ms if not set after setting `--dev`. * to start OmniNode with manual seal it is enough to pass just `--dev`. * `--dev-block-time` can still be used to start a node with manual seal, but it will not set it up as `--dev` does (it will not set a bunch of flags which are enabled by default when `--dev` is set: e.g. `--tmp`, `--alice` and `--force-authoring`. Closes: #6537 ## Integration Relevant for node/runtime developers that use OmniNode lib, including `polkadot-omni-node` binary, although the recommended way for runtime development is to use `chopsticks`. ## Review Notes * Decided to focus only on OmniNode & templates docs in relation to it, and leave the `parachain-template-node` as is (meaning `--dev` isn't usable and testing a runtime with the `parachain-template-node` still needs a relay chain here). I am doing this because I think we want either way to phase out `parachain-template-node` and adding manual seal support for it is wasted effort. We might add support though if the demand is for `parachain-template-node`. * Decided to not infer the block time based on AURA config yet because there is still the option of setting a specific block time by using `--dev-block-time`. Also, would want first to align & merge on runtime metadata checks we added in Omni Node here: https://github.com/paritytech/polkadot-sdk/pull/6450 before starting to infer AURA config slot duration via the same way. - [x] update the docs to mention `--dev` now. - [x] mention about chopsticks in the context of runtime development --------- Signed-off-by: Iulian Barbu Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> --- cumulus/polkadot-omni-node/README.md | 4 +- cumulus/polkadot-omni-node/lib/src/cli.rs | 11 +++-- cumulus/polkadot-omni-node/lib/src/command.rs | 15 ++++++- prdoc/pr_6646.prdoc | 19 +++++++++ .../client/cli/src/params/shared_params.rs | 18 ++++----- templates/minimal/README.md | 9 ++--- templates/minimal/zombienet-omni-node.toml | 2 +- templates/parachain/README.md | 40 ++++++++++++++++--- 8 files changed, 90 insertions(+), 28 deletions(-) create mode 100644 prdoc/pr_6646.prdoc diff --git a/cumulus/polkadot-omni-node/README.md b/cumulus/polkadot-omni-node/README.md index d87b3b63c407..015019961c9f 100644 --- a/cumulus/polkadot-omni-node/README.md +++ b/cumulus/polkadot-omni-node/README.md @@ -49,10 +49,10 @@ chain-spec-builder create --relay-chain --para-id -r +polkadot-omni-node --dev --chain ``` ## Useful links diff --git a/cumulus/polkadot-omni-node/lib/src/cli.rs b/cumulus/polkadot-omni-node/lib/src/cli.rs index dc59c185d909..9c4e2561592d 100644 --- a/cumulus/polkadot-omni-node/lib/src/cli.rs +++ b/cumulus/polkadot-omni-node/lib/src/cli.rs @@ -126,9 +126,14 @@ pub struct Cli { /// Start a dev node that produces a block each `dev_block_time` ms. /// - /// This is a dev option, and it won't result in starting or connecting to a parachain network. - /// The resulting node will work on its own, running the wasm blob and artificially producing - /// a block each `dev_block_time` ms, as if it was part of a parachain. + /// This is a dev option. It enables a manual sealing, meaning blocks are produced manually + /// rather than being part of an actual network consensus process. Using the option won't + /// result in starting or connecting to a parachain network. The resulting node will work on + /// its own, running the wasm blob and artificially producing a block each `dev_block_time` ms, + /// as if it was part of a parachain. + /// + /// The `--dev` flag sets the `dev_block_time` to a default value of 3000ms unless explicitly + /// provided. #[arg(long)] pub dev_block_time: Option, diff --git a/cumulus/polkadot-omni-node/lib/src/command.rs b/cumulus/polkadot-omni-node/lib/src/command.rs index cf283819966f..fe7f7cac0971 100644 --- a/cumulus/polkadot-omni-node/lib/src/command.rs +++ b/cumulus/polkadot-omni-node/lib/src/command.rs @@ -34,11 +34,13 @@ use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunc use cumulus_primitives_core::ParaId; use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use log::info; -use sc_cli::{Result, SubstrateCli}; +use sc_cli::{CliConfiguration, Result, SubstrateCli}; use sp_runtime::traits::AccountIdConversion; #[cfg(feature = "runtime-benchmarks")] use sp_runtime::traits::HashingFor; +const DEFAULT_DEV_BLOCK_TIME_MS: u64 = 3000; + /// Structure that can be used in order to provide customizers for different functionalities of the /// node binary that is being built using this library. pub struct RunConfig { @@ -230,10 +232,19 @@ pub fn run(cmd_config: RunConfig) -> Result<() .ok_or("Could not find parachain extension in chain-spec.")?, ); + if cli.run.base.is_dev()? { + // Set default dev block time to 3000ms if not set. + // TODO: take block time from AURA config if set. + let dev_block_time = cli.dev_block_time.unwrap_or(DEFAULT_DEV_BLOCK_TIME_MS); + return node_spec + .start_manual_seal_node(config, para_id, dev_block_time) + .map_err(Into::into); + } + if let Some(dev_block_time) = cli.dev_block_time { return node_spec .start_manual_seal_node(config, para_id, dev_block_time) - .map_err(Into::into) + .map_err(Into::into); } // If Statemint (Statemine, Westmint, Rockmine) DB exists and we're using the diff --git a/prdoc/pr_6646.prdoc b/prdoc/pr_6646.prdoc new file mode 100644 index 000000000000..4dcda8d41bda --- /dev/null +++ b/prdoc/pr_6646.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: OmniNode --dev flag starts node with manual seal + +doc: + - audience: [ Runtime Dev, Node Dev ] + description: | + `polkadot-omni-node` lib supports `--dev` flag now by allowing also to pass over a chain spec, + and starts the node with manual seal. It will seal the node at each `dev_block_time` milliseconds, + which can be set via `--dev-block-time`, and if not set will default to `3000ms`. + +crates: + - name: sc-cli + bump: patch + - name: polkadot-omni-node-lib + bump: patch + - name: polkadot-omni-node + bump: patch diff --git a/substrate/client/cli/src/params/shared_params.rs b/substrate/client/cli/src/params/shared_params.rs index 465372fba17d..e0c52deb44ca 100644 --- a/substrate/client/cli/src/params/shared_params.rs +++ b/substrate/client/cli/src/params/shared_params.rs @@ -33,10 +33,12 @@ pub struct SharedParams { /// Specify the development chain. /// - /// This flag sets `--chain=dev`, `--force-authoring`, `--rpc-cors=all`, - /// `--alice`, and `--tmp` flags, unless explicitly overridden. - /// It also disables local peer discovery (see --no-mdns and --discover-local) - #[arg(long, conflicts_with_all = &["chain"])] + /// This flag sets `--chain=dev`, `--force-authoring`, `--rpc-cors=all`, `--alice`, and `--tmp` + /// flags, unless explicitly overridden. It also disables local peer discovery (see `--no-mdns` + /// and `--discover-local`). With this flag some nodes might start with manual seal, producing + /// blocks at certain events (e.g. `polkadot-omni-node`, which produces blocks at certain + /// intervals dictated by `--dev-block-time`). + #[arg(long)] pub dev: bool, /// Specify custom base path. @@ -109,12 +111,8 @@ impl SharedParams { pub fn chain_id(&self, is_dev: bool) -> String { match self.chain { Some(ref chain) => chain.clone(), - None => - if is_dev { - "dev".into() - } else { - "".into() - }, + None if is_dev => "dev".into(), + _ => "".into(), } } diff --git a/templates/minimal/README.md b/templates/minimal/README.md index cf43d71d8849..22f396c243ef 100644 --- a/templates/minimal/README.md +++ b/templates/minimal/README.md @@ -105,12 +105,11 @@ Omni Node, nonetheless. #### Run Omni Node -Start Omni Node with manual seal (3 seconds block times), minimal template runtime based -chain spec. We'll use `--tmp` flag to start the node with its configurations stored in a -temporary directory, which will be deleted at the end of the process. +Start Omni Node in development mode (sets up block production and finalization based on manual seal, +sealing a new block every 3 seconds), with a minimal template runtime chain spec. ```sh -polkadot-omni-node --chain --dev-block-time 3000 --tmp +polkadot-omni-node --chain --dev ``` ### Minimal Template Node @@ -160,7 +159,7 @@ Then make the changes in the network specification like so: # ... chain = "dev" chain_spec_path = "" -default_args = ["--dev-block-time 3000"] +default_args = ["--dev"] # .. ``` diff --git a/templates/minimal/zombienet-omni-node.toml b/templates/minimal/zombienet-omni-node.toml index 33b0fceba68c..acd5b121c674 100644 --- a/templates/minimal/zombienet-omni-node.toml +++ b/templates/minimal/zombienet-omni-node.toml @@ -2,7 +2,7 @@ default_command = "polkadot-omni-node" chain = "dev" chain_spec_path = "" -default_args = ["--dev-block-time 3000"] +default_args = ["--dev"] [[relaychain.nodes]] name = "alice" diff --git a/templates/parachain/README.md b/templates/parachain/README.md index 65a6979041f2..c1e333df9e9e 100644 --- a/templates/parachain/README.md +++ b/templates/parachain/README.md @@ -27,6 +27,7 @@ - [Connect with the Polkadot-JS Apps Front-End](#connect-with-the-polkadot-js-apps-front-end) - [Takeaways](#takeaways) +- [Runtime development](#runtime-development) - [Contributing](#contributing) - [Getting Help](#getting-help) @@ -107,13 +108,11 @@ with the relay chain ID where this instantiation of parachain-template will conn #### Run Omni Node -Start Omni Node with the generated chain spec. We'll start it development mode (without a relay chain config), -with a temporary directory for configuration (given `--tmp`), and block production set to create a block with -every second. +Start Omni Node with the generated chain spec. We'll start it in development mode (without a relay chain config), producing +and finalizing blocks based on manual seal, configured below to seal a block with each second. ```bash -polkadot-omni-node --chain --tmp --dev-block-time 1000 - +polkadot-omni-node --chain --dev --dev-block-time 1000 ``` However, such a setup is not close to what would run in production, and for that we need to setup a local @@ -197,6 +196,37 @@ Development parachains: - 💰 Are preconfigured with a genesis state that includes several prefunded development accounts. - 🧑‍⚖️ Development accounts are used as validators, collators, and `sudo` accounts. +## Runtime development + +We recommend using [`chopsticks`](https://github.com/AcalaNetwork/chopsticks) when the focus is more on the runtime +development and `OmniNode` is enough as is. + +### Install chopsticks + +To use `chopsticks`, please install the latest version according to the installation [guide](https://github.com/AcalaNetwork/chopsticks?tab=readme-ov-file#install). + +### Build a raw chain spec + +Build the `parachain-template-runtime` as mentioned before in this guide and use `chain-spec-builder` +again but this time by passing `--raw-storage` flag: + +```sh +chain-spec-builder create --raw-storage --relay-chain "rococo-local" --para-id 1000 --runtime \ + target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development +``` + +### Start `chopsticks` with the chain spec + +```sh +npx @acala-network/chopsticks@latest --chain-spec +``` + +### Alternatives + +`OmniNode` can be still used for runtime development if using the `--dev` flag, while `parachain-template-node` doesn't +support it at this moment. It can still be used to test a runtime in a full setup where it is started alongside a +relay chain network (see [Parachain Template node](#parachain-template-node) setup). + ## Contributing - 🔄 This template is automatically updated after releases in the main [Polkadot SDK monorepo](https://github.com/paritytech/polkadot-sdk). From 99be9b1e8079315cb8fe2ce68497841f6049cde1 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Tue, 10 Dec 2024 20:26:30 -0500 Subject: [PATCH 143/340] [pallet-revive] eth-rpc add missing tests (#6728) Add tests for #6608 fix https://github.com/paritytech/contract-issues/issues/12 --------- Co-authored-by: command-bot <> --- prdoc/pr_6728.prdoc | 12 + substrate/frame/revive/rpc/Cargo.toml | 2 +- .../rpc/examples/js/abi/ErrorTester.json | 106 +++++++++ .../revive/rpc/examples/js/abi/ErrorTester.ts | 106 +++++++++ .../rpc/examples/js/abi/EventExample.json | 34 +++ .../rpc/examples/js/abi/EventExample.ts | 34 +++ .../revive/rpc/examples/js/abi/Flipper.json | 35 +++ .../revive/rpc/examples/js/abi/Flipper.ts | 35 +++ .../rpc/examples/js/abi/FlipperCaller.json | 46 ++++ .../rpc/examples/js/abi/FlipperCaller.ts | 46 ++++ .../revive/rpc/examples/js/abi/PiggyBank.json | 65 ++++++ .../rpc/examples/js/abi/RevertExample.ts | 14 ++ .../revive/rpc/examples/js/abi/errorTester.ts | 212 +++++++++--------- .../frame/revive/rpc/examples/js/abi/event.ts | 34 --- .../revive/rpc/examples/js/abi/piggyBank.ts | 130 +++++------ .../frame/revive/rpc/examples/js/bun.lockb | Bin 33662 -> 40649 bytes .../rpc/examples/js/contracts/Flipper.sol | 35 +++ .../rpc/examples/js/contracts/Revert.sol | 11 - .../frame/revive/rpc/examples/js/evm/.gitkeep | 0 .../rpc/examples/js/pvm/ErrorTester.polkavm | Bin 0 -> 8919 bytes .../rpc/examples/js/pvm/EventExample.polkavm | Bin 0 -> 4104 bytes .../rpc/examples/js/pvm/Flipper.polkavm | Bin 0 -> 1842 bytes .../rpc/examples/js/pvm/FlipperCaller.polkavm | Bin 0 -> 5803 bytes .../rpc/examples/js/pvm/PiggyBank.polkavm | Bin 0 -> 6470 bytes .../rpc/examples/js/pvm/errorTester.polkavm | Bin 12890 -> 0 bytes .../revive/rpc/examples/js/pvm/event.polkavm | Bin 5186 -> 0 bytes .../rpc/examples/js/pvm/piggyBank.polkavm | Bin 12334 -> 0 bytes .../revive/rpc/examples/js/pvm/revert.polkavm | Bin 2490 -> 0 bytes .../rpc/examples/js/src/build-contracts.ts | 79 ++++--- .../rpc/examples/js/src/geth-diff-setup.ts | 53 +++-- .../rpc/examples/js/src/geth-diff.test.ts | 106 +++++++-- substrate/frame/revive/rpc/src/tests.rs | 18 +- substrate/frame/revive/src/tests.rs | 102 ++++++++- 33 files changed, 1019 insertions(+), 296 deletions(-) create mode 100644 prdoc/pr_6728.prdoc create mode 100644 substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json create mode 100644 substrate/frame/revive/rpc/examples/js/abi/ErrorTester.ts create mode 100644 substrate/frame/revive/rpc/examples/js/abi/EventExample.json create mode 100644 substrate/frame/revive/rpc/examples/js/abi/EventExample.ts create mode 100644 substrate/frame/revive/rpc/examples/js/abi/Flipper.json create mode 100644 substrate/frame/revive/rpc/examples/js/abi/Flipper.ts create mode 100644 substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.json create mode 100644 substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.ts create mode 100644 substrate/frame/revive/rpc/examples/js/abi/PiggyBank.json create mode 100644 substrate/frame/revive/rpc/examples/js/abi/RevertExample.ts delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/event.ts create mode 100644 substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol delete mode 100644 substrate/frame/revive/rpc/examples/js/contracts/Revert.sol create mode 100644 substrate/frame/revive/rpc/examples/js/evm/.gitkeep create mode 100644 substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm create mode 100644 substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm create mode 100644 substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm create mode 100644 substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm create mode 100644 substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm delete mode 100644 substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm delete mode 100644 substrate/frame/revive/rpc/examples/js/pvm/event.polkavm delete mode 100644 substrate/frame/revive/rpc/examples/js/pvm/piggyBank.polkavm delete mode 100644 substrate/frame/revive/rpc/examples/js/pvm/revert.polkavm diff --git a/prdoc/pr_6728.prdoc b/prdoc/pr_6728.prdoc new file mode 100644 index 000000000000..68f61190d947 --- /dev/null +++ b/prdoc/pr_6728.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] eth-rpc add missing tests' +doc: +- audience: Runtime Dev + description: |- + Add tests for #6608 + + fix https://github.com/paritytech/contract-issues/issues/12 +crates: +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index fe9cc82dd4d9..674abdd5b73e 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -76,6 +76,6 @@ example = ["hex-literal", "rlp", "secp256k1", "subxt-signer"] env_logger = { workspace = true } static_init = { workspace = true } hex-literal = { workspace = true } -pallet-revive-fixtures = { workspace = true } +pallet-revive-fixtures = { workspace = true, default-features = true } substrate-cli-test-utils = { workspace = true } subxt-signer = { workspace = true, features = ["unstable-eth"] } diff --git a/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json b/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json new file mode 100644 index 000000000000..2d8dccc771e8 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json @@ -0,0 +1,106 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "CustomError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "newState", + "type": "bool" + } + ], + "name": "setState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "state", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "triggerAssertError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerCustomError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerDivisionByZero", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerOutOfBoundsError", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerRequireError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "triggerRevertError", + "outputs": [], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "valueMatch", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.ts b/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.ts new file mode 100644 index 000000000000..f3776e498fd5 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.ts @@ -0,0 +1,106 @@ +export const ErrorTesterAbi = [ + { + inputs: [ + { + internalType: "string", + name: "message", + type: "string", + }, + ], + name: "CustomError", + type: "error", + }, + { + inputs: [ + { + internalType: "bool", + name: "newState", + type: "bool", + }, + ], + name: "setState", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "state", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "triggerAssertError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerCustomError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerDivisionByZero", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerOutOfBoundsError", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerRequireError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerRevertError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "valueMatch", + outputs: [], + stateMutability: "payable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/EventExample.json b/substrate/frame/revive/rpc/examples/js/abi/EventExample.json new file mode 100644 index 000000000000..a64c920c4068 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/EventExample.json @@ -0,0 +1,34 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "message", + "type": "string" + } + ], + "name": "ExampleEvent", + "type": "event" + }, + { + "inputs": [], + "name": "triggerEvent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/EventExample.ts b/substrate/frame/revive/rpc/examples/js/abi/EventExample.ts new file mode 100644 index 000000000000..efb0d741b48f --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/EventExample.ts @@ -0,0 +1,34 @@ +export const EventExampleAbi = [ + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + { + indexed: false, + internalType: "string", + name: "message", + type: "string", + }, + ], + name: "ExampleEvent", + type: "event", + }, + { + inputs: [], + name: "triggerEvent", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/Flipper.json b/substrate/frame/revive/rpc/examples/js/abi/Flipper.json new file mode 100644 index 000000000000..4c1b163d2943 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/Flipper.json @@ -0,0 +1,35 @@ +[ + { + "inputs": [], + "name": "flip", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getValue", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "value", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/Flipper.ts b/substrate/frame/revive/rpc/examples/js/abi/Flipper.ts new file mode 100644 index 000000000000..d7428beb6aa9 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/Flipper.ts @@ -0,0 +1,35 @@ +export const FlipperAbi = [ + { + inputs: [], + name: "flip", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "getValue", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "value", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.json b/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.json new file mode 100644 index 000000000000..c4ed4228f47d --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.json @@ -0,0 +1,46 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_flipperAddress", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "callFlip", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "callGetValue", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "flipperAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.ts b/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.ts new file mode 100644 index 000000000000..2d695886d960 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/FlipperCaller.ts @@ -0,0 +1,46 @@ +export const FlipperCallerAbi = [ + { + inputs: [ + { + internalType: "address", + name: "_flipperAddress", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "callFlip", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "callGetValue", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "flipperAddress", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/PiggyBank.json b/substrate/frame/revive/rpc/examples/js/abi/PiggyBank.json new file mode 100644 index 000000000000..e6655889e21a --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/PiggyBank.json @@ -0,0 +1,65 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "withdrawAmount", + "type": "uint256" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "remainingBal", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/substrate/frame/revive/rpc/examples/js/abi/RevertExample.ts b/substrate/frame/revive/rpc/examples/js/abi/RevertExample.ts new file mode 100644 index 000000000000..ab483b1811c4 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/abi/RevertExample.ts @@ -0,0 +1,14 @@ +export const RevertExampleAbi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "doRevert", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts b/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts index 93daf34e02b6..f3776e498fd5 100644 --- a/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts +++ b/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts @@ -1,106 +1,106 @@ -export const abi = [ - { - inputs: [ - { - internalType: 'string', - name: 'message', - type: 'string', - }, - ], - name: 'CustomError', - type: 'error', - }, - { - inputs: [ - { - internalType: 'bool', - name: 'newState', - type: 'bool', - }, - ], - name: 'setState', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [], - name: 'state', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'triggerAssertError', - outputs: [], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [], - name: 'triggerCustomError', - outputs: [], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [], - name: 'triggerDivisionByZero', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [], - name: 'triggerOutOfBoundsError', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [], - name: 'triggerRequireError', - outputs: [], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [], - name: 'triggerRevertError', - outputs: [], - stateMutability: 'pure', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - ], - name: 'valueMatch', - outputs: [], - stateMutability: 'payable', - type: 'function', - }, -] as const +export const ErrorTesterAbi = [ + { + inputs: [ + { + internalType: "string", + name: "message", + type: "string", + }, + ], + name: "CustomError", + type: "error", + }, + { + inputs: [ + { + internalType: "bool", + name: "newState", + type: "bool", + }, + ], + name: "setState", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "state", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "triggerAssertError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerCustomError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerDivisionByZero", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerOutOfBoundsError", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerRequireError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [], + name: "triggerRevertError", + outputs: [], + stateMutability: "pure", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "valueMatch", + outputs: [], + stateMutability: "payable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/abi/event.ts b/substrate/frame/revive/rpc/examples/js/abi/event.ts deleted file mode 100644 index c389e2daf1da..000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/event.ts +++ /dev/null @@ -1,34 +0,0 @@ -export const abi = [ - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'address', - name: 'sender', - type: 'address', - }, - { - indexed: false, - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - { - indexed: false, - internalType: 'string', - name: 'message', - type: 'string', - }, - ], - name: 'ExampleEvent', - type: 'event', - }, - { - inputs: [], - name: 'triggerEvent', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const diff --git a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts index 3d44cd998ad1..a6b8c1b0be56 100644 --- a/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts +++ b/substrate/frame/revive/rpc/examples/js/abi/piggyBank.ts @@ -1,65 +1,65 @@ -export const abi = [ - { - inputs: [], - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - inputs: [], - name: 'deposit', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'payable', - type: 'function', - }, - { - inputs: [], - name: 'getDeposit', - outputs: [ - { - internalType: 'uint256', - name: '', - type: 'uint256', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'owner', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'uint256', - name: 'withdrawAmount', - type: 'uint256', - }, - ], - name: 'withdraw', - outputs: [ - { - internalType: 'uint256', - name: 'remainingBal', - type: 'uint256', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, -] as const +export const PiggyBankAbi = [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "deposit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "getDeposit", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "withdrawAmount", + type: "uint256", + }, + ], + name: "withdraw", + outputs: [ + { + internalType: "uint256", + name: "remainingBal", + type: "uint256", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, +] as const; diff --git a/substrate/frame/revive/rpc/examples/js/bun.lockb b/substrate/frame/revive/rpc/examples/js/bun.lockb index 0ff3d54157db21a636e30076634343a24c812dca..46994bb147547bfb9b960b7630d1a6d274ee75dd 100755 GIT binary patch delta 9716 zcmeHNX;f6#vOcGo(FRl$8k&F@5rl4_nFSjVQ4tkUpo0j=&|ou9qA1dU;uI6b(nRCa zL}Q|um`F&BXq*xpaEdtGIN{Ul5J6FiLt>2js!sQz+p8;Uy|wQB_1D_vybRQ1%JPedF|LLTV@CPP{BmUp!*m&FjO3I`N~fp!FzuMO!t%4oN$(JH3*->S zI;riAH7yyMXe#}yN7mQ?DI1t-%K1hzwCR!za)Dunpyht3m#->N<*Q1|ka789%%`4A zM13lsZLYER5IOeLzd8&{QLes%q(Ac0n5GJcFl~!GmpsDf6-Qw%Yp;HUgF)lbiG(Z^ zq!kpZG8sl$nm$97m&xqFPTOGqC&+31mS}mImZxZW6mn|W4>@+XdZ3mIwfw22CjX|E zw`sV#dXF}vLCZftPJNuGIr(V$@9a9UP#yu;4KAw~=o~?u2}?Ha`pbAWV-$?u>jea!2IGT7Dachw8T? zr~GZmsi76f?UC0ZrxC`{Q&mHb8kJ;8Xv+Ea;-mcRA+nL)9eMQ>$YY`u8p*`zh~o>Sg)`EGcSSz*Yb z&e)H3H+dN@Vofincuo4oz4<-ZX;j2IgPUaUb~&g_LX8gN z)(nDe#=!zc0G%dsqj21oGUzf1W=lb38p`g32GdZ1i2-z)%Gt@_W){k>0u|Dy(16q% zI*`r)H}g=o0aQpkpaH2PbReA$ZoNX;jiBlkD!63?oxS9&4BUE$vZskZq)VYe7%DuBn>YlApXP^wMq_tyGB!1{#H|;Ox#r+)7GPG1DPK&P zK({EsY&)jHFl9_-!Uvf0z!Y{7JJ)EgsXq*7cmn1MIGsh93VcQ9YJaodm?_m}8gLaq zu`n~mOgUyU&}|$b^uR?#R*X2`ynDIBRgsw~W|E2kip>LzUScX7BzD2Vv3LSdM>LLj zn{bX8Ex_Dy&}A0fryZqGEH@-a5$15sXb5%INQ{TdBkN3C2bg_?sUWN| z0J=>Bgl8~CLxP7NV~uq=`vONHbPDBWb8yL14{-!~1qhmikYgbiuokctS%d|2B74sQ zhFZ!6$1NboQf_nt4~hwpZy9W4--kRTDZ58%>XBadNYQu=q;n{Z()faBnnp_Nkv8{8T_}y@^t|zG)JQXWq~;#!IWGw& z+CWZ!xzQ{pHq8zoJRNIbGMrJGRFc<>x01y>^u53*p|j)Jw0&?%A&eszSc zz2pK%Jd*6>f)ppnLAKEewj#Ui1f9qn2EtJLVQO6c41tb2oD6Pd;c#*faaxTyY+OE` zjNC&U-=_@rmdnRJaC+!1mqu?mn?sP(qt9`-(yrn3JsD;Ml`|-C&;N##A%BDbBE^ns zs*X#Ydx(?U!w}@=a0G1{T>RWaoXV+f${&TG=kGY_k4BKau?X6L;}G;9S91o(Ytqb{ zI2pjBfP0AJW@M&lk{l<)$=dXrIE{Ikw)}O@_5be_G&NtZ_;1SBw*NOCYF5_*Z4dt4 zT>bAbNK>>BL36tpLFpw3dWch+o;Pe6HT{U<9^$0(K{ZqX(}xP-1g0E7WEund)Pf&HY*&oX@ZOZdtLa>_&51WN)wi`(Tf|t-5(z^wrxoV{Gzv$Ho*k&it@zl=+Yz({@`|0v{@nM+X@=^dODcZ}8&xR%#UQ@U)y^(; zd&tZ>sdK#P&HI6i##t;ld13hctP?SDw^=Cguu(rf+7-QWP=C8R^Z716WEUK$n{Pa8 z_~W&=26R^ykH{3&%A`hiGQYWuyFx17G}9 zWzuMFA}$#-HS4eKt;UwY;2^P8|C6a)w=HM;h0@x#79uIQJd-&@V4yYEF# zt$7%oH!@=WefL8N#-G)#ulFnZ+^WLbwQu7bJ@1C-d*^Yj+v(P_w6>dr?MH+^I1;}$ z^`~ba`~3Vxf_&%Wz9uJH_x+G(6_=XOnfw6yl={aW9+DM@b6?p+9MN!uCnST?tz?`n&>j$>ynhTZ!0aO#nDM+V(5 zDNHuB2)})7(#7F!3svbS9V>Q;_3XOQuMhq)EIrYgxM#^ zezVoPXRh!3r`YWMf#H`7ZwIA3p0nv>@t@ypepIGfX7`owyYo@Af3FnOmFgKD${R&) zxHa_S2L}5ii|_c1$<6ufd|~0Gptx06pS6ydocNcYI-||}mhXwJ7w&94Ir^*kR;ypX zsY;5fcieFQ-HxDBC(U*xuZq+&?5=Nk@9BNNFW7Xb<#=6JXz}+QqwmP%cYI3j4s3X* z^^1bqou{r^Pyh2_P9LMvWjB6Q?aO)Q(iS2+vVK$g(7g}A#O%orWLK@-mLB?s(|$VD zy=~Bey5ac&Q5LJN%cKXs61o3Fc{k-dv%SNXUd%M89hv^~k%KDs{TUm(YlGwFnu_Ls zxi5I8@$;oF)t@(d=s7IWxBE!E;hUE7%Ex(rtN*xvbY<)}AFo#Lk$=BDLg9LP!R0lX zJC}4V-!Ev4tXfhM6!^$zLQ%imD7icH&$oYFS5clm@4o(;pdZ5gW4Pe*XWf3AUH2aS zYh+#SlmQpQDppPG-kB1y>13C&bm8vw6P^_xEMI!Uqu{;k!vqtbD@{KwPV)SH$$gh- z^-rD?YS!J+^O*lJK*kM&WuAE@ODtCJY708}n1;TY3S&;(WTSj{Fz_RSGQc;el}Qfzt*T~LQKM!r-hDx-2E!QF{kduhjFJpR$no7 z?&BoVGwiKzI6%5QJ0oLvzUNPI^$uO_Py7lx1y$F&UU(hN*}W^LU;UYm+K!5Asq+JNXJ7TKLtAhR7hjsfS&@! zga~17fm6#FaKNzjUPG7Jn*uyIgl5b(s$qj(nRnMQm|8CG14Ts zhIA?f4O6hmP>(bPI*_J9c(8(<25XR}K{wKLhze1#8PMpeU^8I@%1U^OGz(&fE7)vk zKsvpNm4D5$xzHGz3)AFB@Y=yp$X|;V;tygWTnn?ITr2)^RO*?Lr;_HBHibvnvT!P_ zx4>EmSHjWT6C=V{G#^5Sz$3!cC>%d~EYbAjrfG6R7M72+ z260$#qhIjXffZ$=^2nyZ$Wc~YRh|rD%@17(tH`!P}B)wy_ikOB-L!=`z z@S1Os!cSd{atWdoL0{5Ih^dJ2h~Wr1A{0S~?Ar*ux#2$X!;z0hj6p;pMj#>(BN35^ zafq=9nogPpT&|2If<`$Ef!AbovFu5=%yidIH}>4;9$lZ{Jry_fi;kjUvM>wWr}UxbX~9(=HFnz*?N#&0luhxL;-3%0-_i3I z+gRcw#XkmOr}+8MCpivTHGeBFU{k!LUQ+C$vTVAtq!gEJ(!kuhfTX)v;*a*EzR)~n zojv)E3%h#8(W!qpY?);)V7;Y&QksNmP@E)U^Wfbikv%PKT>91iKkgG>GWoMsr?mj~ zCHdIrY0J0mN$r{&`SEAihPTw0%v6AI>fhC`fbgjzwhod|>vbzt-co;!6^&NI)JS}( z-Jd#;{RH|Xi`cE;p6r7!jMQY2-!$}rdQx@!!kE~@Zn&20W6#IcHaI*PyMBM|SkAIcimU1@c&3Qhc8E&xVY^^%%6Qfl zj-`m~`N-Zf^UDFk{8^qfUf!I^Ja~!aZyHY@DQy;e@JV%NT_80zSg(&F))0P96^;L! zdd8aQ8XpWnA7wIW5Dp$6;N#;oOw7kuOBauM@$F}C zSL%BGNC-t~w*GwF^2(Kc>%Nff2{L4H{_%XkR{_2(`eGZZ=8!GE#`(8&B||J>FIMG4 zp$Bf(Z|P?<`gffIA1w7BcYMc&72nR*l_Xn2?{r%+A6?zpP?dAfbnt1N!aPd|OSkps z!?C7)WodO@qz7;;$ z^l`+<7G23^TX=z0`Plc08gXJ(`D0g|0v{cZyRvV?!bdfBx)L)Hj8oc*`FOg+>=^cD z<+*B|LZ}F;mA3wT0De))+9BY41J$e+S>o? zexvgR&jUQm41vuO;eDTdmdLzF`3xc&~?} zS;K^3MQJ6fvI@@X49J3pYz%NJK8Oou_{Lg{d$~(TFYog;tz#6gFH(zC_h8O9sksNrczlX zQO+t;7G$Cmsv^#NNk(CQzOtZ!W-sKkq!_Q*#|JcO|TDdzb| z13AC-oWL@@6b`OGG*%7_@9CLtkWljtxe|Iy`cTipiVBMIJrUTOf-I=17AL)ugZ24B zi43cF>nc$NRkl=FP%bG^7UiX7D7D6vWity)a-|t*k}Or8Qlloxn4YHMCfrLu1I_Cv zvA6U)lG-RLDJ(0L1cIHIQ9`9%s$S zlVoud=%AOzYI-RNo<-1@J;;k|;q_kk^g(|ps3$Zhy>ctCJhuYV0kFNm8BWz0Lt?Ga g0NJ1PJ8)B0wM^ zXob>@#zU=10YS$OSRd7(60HiN z!e0|++@3bvDamPBwJK97>4@E3K|vED0msp;QEHzs;XwVG6$naBR>Ne1stv9zrY|S zsa@)L7s3Qo90lG4+zX5awkYLAzbD4GVlhgaVs3Txb->uqRg3brl2_qE;xx^jR z1O5sPf{!4R&Lx~w4BkmSI+wN|`z)2-4@%ekO;DuXsZGQlvs1J_T~tty-X$KULA^`! zm4+gNonjsp2fM_b)C2wr4T8s$X{1Z6qhj!1QxEuiGzcC_rco|YLdD>Fsb`c+e_ks| zDVV|tIvYMq6B|fFqnx6OOa_IrdaM(L(oArowbOf$EIs_I8+N| zX^Brd=#wt_q!hS+fvndFDMyxG^GRViHQurkpY(uFy6BVaqgA~vKItT+Y_Baii(YAw zPulB~h9J$A^(KU>(kh?y8=rJhl{B-%DAMfIR$#X-r)YD!_Bfme8Q2P1Djz*d`x0^v+8>&(8DXZ9Sf^&bnR)>`%rpczX{N}yw07J| z5)&T{%v{1Y<+Cp*M(kt0JmhjrK~9Xg>Tq_whodOrE|VsMIDH#=teZjH zuSJqp0c7DvBX^h#V#8<2y?hr1l}?Ij$3~RqDYcAjevXp=A;ii^E0y}60NdrReu9qv z<9=}CN%hJE>T$=)$zSka&MV`~7V*kAfOx^0K%8#|abn~=r=N0Xw*QE&DI0+;`lIn% zJ9FIsx2D&Rd<6ctw&ql?VgG(>_6<%6(9t)!ZklQ}(EBsp!bAyH550?Am(?xIG>F_T zn}KHA+#;HG+dPzFH_)fZ#ZaobBjqd zfZUhJC8xW^6xxyQ5mRXpJb`Q(9x;t}gHNZk;4>(7wnyAd-QX6w06vp4ogQJO9&j67 z0k>1G%Oeu$5O@-O1)fZWb3DR9N5E&1$n=O5Dh5xbC&1H4e~U+?Q#p7B^@Go*&@2xg zHmMHCxkq=KLTgZl7(7jL35a!p-c0oMe#R5n%cLJJ)Y=dT)9g@+AZ6&-vB zsn6`k^r%|a*HO?WxCvz})@d!+tE0K2zPh{sK?jSBRHuujy1b^o6-%i=~i&He>{UYO_7ShS&=(yROIDAR=k^g;dCqATREAIRYuv^j#N+@C>?|c1phHh zGeK664P*x;f|5X!Nx#7y&7H%C^0R?0WQ*7WzSvjNFE?bh^S^2?XaUF#S_sMmEdu3( z*cXMM#US|ypadz?Mb zE5Ngw0h$h)21)?&Z8ZhN9*m>t>Z}RuKlYpIL$)%7oraXOmOiMSHkLniUJFck-fi9S znNF11Echi-dQCF@ye8759vbyyMYC3V2Giyyahs$eCW_CZ^+wti0 zD?i;f@BW80!e((;*zAoIT4xfibfh*>l+pXp-Kgj~PQDoRUeMQ_=-FmTVr8%HMw(t{ zGOM5D@1#wevHcC_eHyWyErh4aXeD}-wZtZ(x_NvP*2Rf?=pQKGNB1|H$WkAf%v%8) zu%r346<W;JFoxH{yKoo}u>D$mAh!TNqgZ#Dd| z|Li{hg$E_FsfrgD7P{F+p{&RK?Hqm#;J>iz0aX{hsBnPHtf-Gt=yWfHhmUK;iYIdqoWazwK_4z>AC&!u_Ur9M*H}b03c_PboWZYzTsBwtK-8t8fK6F!? z>@<0zYNTTNnFkvNHa@vRuCZINaWCtsZBs;)8Ud*6&p4bESbZ))VCpstcDgZ`ezz&Y zp@s}jeB6CFeEj^s`xVrnf@9$uPanGH=oWv?f?&FirfL-8fg{^D4C^MH_A97yid_MZ ztX~8B7aXBcjy6#w$Suzsf#eebTS+a4>bWMmUsENXJQOc>d%InmK+2HCXcA z&b8ULdxuW=Yt9AJn`o*=S5}R?rXL++{KBuG#$PD?Qv8Ol-d+Bhi6bd$b3~LHusLA1 zKKrK&t4sU}?vYfmIl`evcnss`UO4br%gcTRH3szC?8?#x*OYz!ny!&_0DY@Lqk%(X zqFXgi5Jsmr+oROj$aBVVVWoHVbOwkdi(NjPyBhvys|xb9a_ z!!3bLr`J@yc~#@Dxf(__XsX6%QZ8@J4;gdkfL}q4;Ox2Un|;s3hhFs86ou38w?ss# zA)j-ZlXRbF&3(zQus578qi;1vbkB~c;_WS;PxULPQKTgwym;u&&vr!mYrY64=hg^^ z8h47`k}p1PJJaD;@I|KnGGLo=?e%N^nj9njVrxW{94W#@vh~usKkf?qWFlXCHcKL| zZ-k1DZ;eR){&~a6v|BAUyCvlZ&TO*9?yw}`lpESi!5wRH;E|56#T#M{{<*2H%^KN; ze;Gcdx2Gh(AzDMOUsklvb<(qKX>?_qxi5O#5iLElZ*||<&QqfAv)z9PqvpG!s9;}U JU)=t~;-4>p@d^L{ diff --git a/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol b/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol new file mode 100644 index 000000000000..51aaafcae428 --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/contracts/Flipper.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Flipper - Stores and toggles a boolean value +contract Flipper { + bool public value; + + function flip() external { + value = !value; + } + + function getValue() external view returns (bool) { + return value; + } +} + +// FlipperCaller - Interacts with the Flipper contract +contract FlipperCaller { + // Address of the Flipper contract + address public flipperAddress; + + // Constructor to initialize Flipper's address + constructor(address _flipperAddress) { + flipperAddress = _flipperAddress; + } + + function callFlip() external { + Flipper(flipperAddress).flip(); + } + + function callGetValue() external view returns (bool) { + return Flipper(flipperAddress).getValue(); + } +} + diff --git a/substrate/frame/revive/rpc/examples/js/contracts/Revert.sol b/substrate/frame/revive/rpc/examples/js/contracts/Revert.sol deleted file mode 100644 index 53f1f8994256..000000000000 --- a/substrate/frame/revive/rpc/examples/js/contracts/Revert.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -contract RevertExample { - constructor() { - } - - function doRevert() public { - revert("revert message"); - } -} diff --git a/substrate/frame/revive/rpc/examples/js/evm/.gitkeep b/substrate/frame/revive/rpc/examples/js/evm/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..ffdbbe2f9c4b52b7a37a159bc88e2bdac706c561 GIT binary patch literal 8919 zcmd5>4RjmTm7W=m^hTPI^(0I4^Q0t-I>8RxA8(ffCP}-FQK^)rIYw4PcBWoCA&Mm> z1d|_INjNlz0+EySY-|E?0;V|>636KeyZynCv<+>yq=oL%o^6*Nwyh7eXG7W2v|F;F z{CK|^Sx!O<1$uI}K7JbM&7C**eed4y-FsihvX61xf226~k0;z?ZQ^mRO)W((Lt(|*?)8O@o7S(}fLo;-ZrHSO#mcpX?o}%` zu5fy$h5J{m-Bjq_xPHaD4XX?5*B4fK!@}r`;`J-mt{pw?TUF>=d*f!`{Z}s*?&B)l zIQK>FU%6I(BY!9VMgB4VIiB;RJdNT?(eJ&*`=al?te_r5VAR&KPUL(IG zHz`@=-<9_Sp9}74_?u84{Stj&_zU5$hvmrkA{!c6^kDSI(Zw-cU8$~9f2w^`7mVi^ zAAe8$+W1ZJPsZ> zQZbf{geN2YyhudJM}=KIgmOZV=R{`+k1!zUOZo8i+DcEW=vnD$aXy+wPY+3o9^xZD zD*AoYr}4$`af`NEu@*~hF2>q0W=<7JnLS9xQg7HU>-HgPkLmVd$sSimB;^oMwv!OK zx&EfW_g^Pui(RJnp^QDY+&cQ#SbX_xk$%bsT&A0Fn4sNur4L>-OVgbl;kNNm2R3ouiLg zah?CGdw;~eKl}OT?-nOT(dVpV_6zv&f}dn}#P2pAYY1fx{o2k;q^`MV_^!F9&`zRF z@`5(WJIkP*;%)s5ZyP6hn@#fPEX3~`x*fylmK@Ff(6>7P^^03yHMf$&X1REDDshiu z-sA6l++RF2RoLt|HXeSgzRn*9sA+Evm{aBdY<{L=v({u&wRG!?oNSZ4<5SZ_Ff_?cYN)>J7<44 zPm1$jHRsEP38nbtRAQIU+!g3t6DTg6Dm)r6AC=>+eBX9bc$j>s(sU$Wkzra*C*u2( zbN2E16aM^GKk>&fxkZ3fdar})W%vk~0C&LBC_lR0(jPaO6;m~JP8{9LHp3;W$GgnTAwGo5#hyUjk_VUI6| zaqk-4H+oR{E>XTJDHW*<`ex^!HB}~O=UZ#pn^&H@sDr{yd|1FOBq~eHZju2<6g)M&baG-M_U<1Cw zmVmiMwgF*bt6bEQypPTvQfNvLE35#(t&kej%PE_ zxb0jA`GIWrf-b`pkR%oxSJy#W>$L=J*9Ha(})OQ1&_&`AFR&x4n4Mm?qJArl_Z368f zv@x^;XuHuy(5h(Lc#aqJ%l;i7^ue`M23?u6+xc0{UN2jOvtwbGp1eV?SaudlzvG$>>u5S2{?wm zcgC>q3J9xnFlQe%e{zP9I|4SAPAb-UnPgu9+ij3S*+=@0yli`%wK{_-okBZ_23`In znavCQk+u4h9QGT3eOi~aL9t)^XQoy6*#6O-@Yr$X5u%jq9@{%Jv15KK4nuUj^^ohW za9<|DDw}`%zl7Z9w>9``1TNC#XT}_6%x1=l8Z%U8#F!CfMk6yK z%m_1sG9$!{24)1Ap)f;cMt~U@L}CVEhL0IuW{Ax2e4S&4Foq9)T~pCcoZ*_D=sY3! zORr2#O*y-a4Ry>hNm;8n*4K0}e^AOlAu<1_xsdd_?Kt`W3w0hnS1LN@?a^anQr*9`!66Og?g(Bhu70^s86L%+8KeDU3b z??TD@62iWgr|7%!tMB{XjbH8cVcJe?@WJPQPsrWAw}=fcdmIuQeQsuu9Vz?(+Q*{;1C-d(m%#>+9V9PWj>`@4K1TH@tPeXr<8PYu*}P zwAo{sb9VR<@WnBI{Lt}b+n4PPk$WY8;iC(F_CJK|^u7fYi;h8}gjc|C7kf{?{?1u& zj`zr#x@Y zhV3cn|8JhN-V5Klcm(sd+3-Wq=zKOzZ~iSIm$-_aWkXQ(#Z&fevEd%!d^WuLho=bn zBs$*)8-o5~pu8xqE*%y`B}s2~HW`p&2)zv;yWl2KzKL#i_8RmC*3+Av-DU;7+1YMf zLZml48%~W6ew`=M zt%%m+#&J&gbsTYAEd(P=W%W3c{#pc3Yevy#dIqz5fZ-nJf z$9RkX403xPK?LhWpi8fNF+{%X=jv#7d=v`(eg@397x#VpO?WQ%hhGzN=r5Y){8MjE zbNPi(|K#gu-SRJk=9WRD^J#9`@P8BXx2~dRY3~0&===O@=hNKs7hWJ_1f6e#=D>2` zvh#yJQBKmWPArXZuK{<1;5MayhU2!@IBx4%j@vrJao|V3$Vgt4RyjKPsMQT!l-GED z5~h#DZj}=}B@q5Q2@FHtVjvAbS)=Y3pZEae|MOKRdnq=xnN57UIEMnGrJcYy z_)c<~WP5)7JR$pEvC(N8CphH8wvK1m1g9V&tAR4&RyLio=uHrG&4kw-c~o0QB#~QL z+?>p9@xJgp#8=~o=sS5rB~!i z*wUBFl?ZApa;4GId*n*g(sy%mC1&Y;az(ZDDt>5|z6}@2(kF27Tlxqt0ZSjlMYi+< zxF{A(L#_lZ{UKZ$Ed2y7Aqz$&S1cX&fFcf3V7Ve#Fj~3dvEb}-MYMFv%N4H$vzIGA z$I0aZ;uJsYzFxfQzX&;XW@Zzt*K|;ef76KKu`|Me>%pkTmpyp?p);k`NW)vPG^hfW zs?;`;rG2^+Kshee0t2&+4*W!=4Tg$Z+qA9Le4rxO`f^7`tDxj(8%0;9XGX?0`e0zT zwwqHnqfArfV9s`Y8#iszFitnrv5gVVm4lZ#a$_~Q183!I_7M6o(o>v*0%ZIIZc#gN zoSQan7^!QRHha1zccvzHvL-iqR?d!}LLWw=ys}kx_z~u9s~kUV;xN{uNf^BoUcrD6 zVU>@x(lw-fq=l}*_h!1rTRxJcYsB&qov!hek5IZsC?ApN8lINR`&(%_RNmi0%MIoI z&9odW?@!XQQr@rAvRvLzX*obkQhA?5OaAg;D=lf|!4_Il%Y)6d6e|xVX(?JB)M=@) zJV%4(qx_8%!xcg71ouXcsu6OCQOP9K&a}NOep8W;#KUX5C zCz0NMFsLVjx*pV{L7fJ5C8$e5y`JT-|1zXkoaQl}yHz=P{>%@N+lZgV@EHr|H%>s; zf5$JJ^BniUN~lhs=~P^q0~TFw#X2lClly9Jr)9J$B}q9leX3-!mT4j!0evS!(xrm% z70f4hofU8O(Jdc<5*jM{IPkHw=E8MOUFjmmL#UQFjpa&Bt2;-0{nE^i6mK;rBs<>L zbj(WV`D2o0I7Ez!WBzWpAhwCOV#3GWE$2EPy?M)XgsgL zoW&T687!u=n8so%i^W(h%3_Ty7Gbe4i%}K}u~-9(1zAjCF`2~zEaqo1iNy$u`B)4i ziY(?~F@eSSe)5eOMs0OB%{G+ZT{@-tI|%cJ$WM@92>or2v3T9I{{ivy1h$Ux8AfB`ywjPnJN2xWT+jO}d>#)^KG2GM0A%5~Dw{dHpg{}FLw{k0=2!})pH;XS)|cXVck%eP;% zR zd#W@%^Umxry}y}HN9H+C&rO}9O_m#~rXQ%9?P;^FAfECkBSRTS(itH=^Vfdfs~(_3 zW~N=d)6C|o!&jeotER<{+p6Z`NGg*~Uj|SCsQZRtJoa&nn_7~F+Gka)cI@F&gTr`P z4gj9*kF=So@ch&u4G*5vD^$~F&XL5JKxeR;Nks-T=^eFIs!hek%>~XM&gQb&;cBir z<}Tm#K=bED+~pdcUeyQMuW7Hr2jw#9+9>X4x_JpVly=@U{{o<@(IL#_TxmX?ip+N; zF^fS=5|07pT>$xq)oP|a9m%E!^B-{rZiX40yBg7X+cMEX{HO2F1j0kWClwib01v{3 z&qDG?zCSPydvHKW)tAi`8Zsk;>C{lhjC{oX95kWSym{IC&2#n5;@+?$<_Krq4?JgP zYp>jM!JDhOY$P=-W~&lzMN`99FX_BpdGNCArOteF?YEt4O4+-(VT^EXu6k~-KZytD V%})b=$2aO0=DrCNDJ{O0`#(LIA`$=q literal 0 HcmV?d00001 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..4b013b35852e668fd21ef7ce1dbd61936d7433bd GIT binary patch literal 4104 zcmcInZA=`;8Q$4jZgy|?VDI?Y@qpnV>ww)lAyyKTltyPtqXRbx1F_JI=wa~Xb{A|D z8^fKkDmNkJaIj*iC4&vEWhF9T)I@13gH2l6KSWNXT2-ayN2OS5ROLroS#ds`)-`?S zFi9h|{ZsXHJF_$M%scPLGw;kc?|nv+R8z|S5SD(`sjNs*U!?2dx!1;uR&O-=zV^uL zFB}iw+8O-GpX&;6;Y&N)Mz$XMNQwK!6j9{EL*RJ;p2y(n-q+ikIJ`g6cj$=QA5RP< z4)(hT`;PV>N$l&5A5QciJ$&#;qCf5)IB>LoUsrD;zJFi;zJYzcM-%b>!}|^%c`kAI zaAJQ>NIuo12wA6|^@Fh9Luvmj?OWti-+fOCpgi;tnnb@tw^5#4EHH z`p)`Z^Ih}3?|V$SpzO`Lle61z;1PUL{Zrtxz;695y~Fs>(1MQ#pAB{dThh75OaSdGIG#8 z8S&;L?ohqDu^C)v|>*dJSk)2p;N~E=xV)nMqio$%<5`FcJ>k3=bd$E2`g= zx36{LYDo?vNf9dgx_r%|h$+5K994YSkNu{i`AxrtoX`q0Ygl15 zE2?q|qad`U2qY+K#BZvQ zmghSx(qWk$mfC^k!KR!L_Xxja@>#;?R6cJ^s>UU3jNm+cd}F1+0a)o6_e_2%#%J64 zT$Il@&kW6s%uLSAi3wLUV^s5|BF3WXW+Kp&r|y;qVKu*LJOXo{3aB-bjMbomI~B0L zNeS^J5w6J%!*Cf-n%p63N(FyuO}@~&+sse@k%tutIY0=cXo?huAt!W!neD8wnH9yH z!cOqWhL#1ZS-2wqH-9L8@NirOU&l4^768}BbqGS`Sn-rnHig>8w93WGg|@_h=Pb%Mdw`+l`QS#%>BsNSxodE zGZzsnd;_tfS>zP1psx`2!3RM$)06-ND2{=qaTN#~*MPdrOQCoGcnYi<16EyPJ)Ma4 zwj*O%V@U@U53Vq$5A7XcZVc@`i}6)ZZr#|b8-K^Z8s6(5XEkcCs(gPRN(}4v?m%lM zz}OPx}G>e#E@|XyAUstsuV1r z%^D1>0pIw|>Z_k){8xWAR3h*@RZ7a9pro{#lFgKqP*N;{L^e@ED6uFhqNI?L0!s2J z2~!fH#H1vTl3YrHlo*uglmsZzC;>^B5mozBGj^|RYa{qY86l`pITvRg{WmxE00>a)Cy9|pq5Up0JSt~snh~lern}V zOQDvJS~9f|h0P7&5j21Fd$9DIIRp5Y8+3DkZX=jO47f006kcoK6|@(xohT2#n8TCR zNKX^yHtXq4%&pebl(|toT@04$=@JNwp56@GS5KERw^L7-F}Gb$o6J2e>FE%2`}A~} zxhJ5J&)k%rE@18~GzyuUfvSkP7of73I|-G_+#67V6W@R;z}yw6bmlHXWia(F9-h?WTx$i;6Tmm`*MQ%YuX08M+A9L?OOJQJ+p3Y&e3YA|3N*}}wjFLx0qFK)X zk9U8D@mU3Ibq7`MGz0m#6Lm8tcWc}!BJ#cHiMbgS_9!%A^SVolCDpuYE}G^~2{z8jGnZ%1&17btFy=7pse;TDL2&PZxWnNg zGO#h5jj>2e2V}XjQ|xqk60#mZ#rCQx`vl`wS>$415Oh453mtXP0Vi0hlQ}A~Q$Qj( z#UQ1_`DKr)t-vY9&hndr5Dd)wxrJxaXk6tjc?<;T2ZB9M#Jj6zcEYj29WpcOO@bvd>?EFy%nXXdKdm{| z3fz=io{z)#haA^cC=65FRR~;DjuAFwvg0aC!6jv@`(%WrtJ$%t4lBY^HOMvPK}D6t zN!x^+J<)p{u0tHILtI>kLMsTqP{hr>ZZoK|hl5%jgme(IgQy*9Q*7vYxM5`h^>;tQ zxLN>$Bm$1x6(l7=LV~0qNX#HHfHA`(rd%Ew#_bRiEU|^IAd?0JM*(wTXt;O0#0oU%&=NMtUenWt>0spSB%=@_46>#ZaAZc#$(xuqBUWk z3`gxo`=ni0FIwUs4vF9Bc+A#94K0P^_PE_xpUHeN4v>pgsQzNCVxnTy42^EggZ{>0 zH4+}r)Yrp1{3{yl(fSGy6ozU~wj7UD+#iH-d%}jn^+Mn#L?N&Z;qiZwx-LwVQpKJ# z6SIx=6M(9ys~^t51o&rS^%>B{4vl6ls}Gk~gtf?hbF#BfRD{Y~S|9{3f!DS*OoYE( WU$2eYyTak`gV?&KTN=vCrGEkjk_2@C literal 0 HcmV?d00001 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..7fb299e464dd75177e916f6631f6fa7dd7cf3ea9 GIT binary patch literal 1842 zcmb7EO>7%Q7@b{@cXk~&-gV;H%_KBV*!)TrQ2`OepNS0hQZ))DplDYs$7zf=A#FqA z#7PTMN?YPqJ+xMf5>>8}s#W5)hf;(ZS|NoKy&xe@91zN(2P6)lo~lq{zI75PJ@7N~ z8?U~Z`R1GV-b~Ls`w-eqP`PmgeHFp0D1rwBH&6kP0kwc#fKEUCA8{AC8{BtXl_&1G>sj;c#ckd} z?=kOL?@jM*Z+yD)u#8E-_bT=w#65ND`P7`q&(;UCYcD(p0Ust+$R{Giw@p=3I3*vS z!X%9N)GQ4A^;Lx&WzCc|GbEc-bVA{vvKE}=Fu|fA^M!sQB7O&lu)D&;XL+rgQ}(L^ zo=)r;@N~FcEB5r0F!m6E2r`xgSx`AgSvBQOGt^d@k^4zM;vIDjPGqLF`1`i1*{W=-qD}a0w^y--HET(>mNly= zT37vZqJN3_XGws3u)UM-#|=UbTf?%o6tkB5tYXBv+EX}Em@Ui|itd72lK-SMJRkP2 zh(ozBL;~VoJ`1}Evi}mSec30HzS zWKB+Q1PDf)1Nv}S!YBwsj)F?2`~aDuUy(_FLQV*AK+ATi)lWmd4|&Z06De2#niC=r zo{-!xAMl^303}*Z*Usbmd9L@g)UwiYy?4d$zwPV2E|n=H(x+U)Rj-hJFB>L%ioe|Z zW|<><-j06~4g~hRcK`A#=QuN{*$s2ZZa4$T0cH@d&LEd&^$fDKIb_x3ki}-uBSf#d zdEJVjgoNal9LhQTZ!391aZ8!ig>nw_}U1TN|~?9?;5 zlhG#_tz)#7QO0NuqZ*?sqm zY1On^%ss-k;XAD~*w7jh!Rr5!{as|stUGW279i~$(ETq`l%3%^WYZmw$}xks^iUU2 z?IfuBn2L`X%T`8hWy)3@Tj_VxfTG|lzP5ieiv_;3G%#BY7>~`NU>249Y4Ui1ChIe6 zdQ2^5s*z1+lwDEb5(%%>rYpiftP|2)`aiCCu)$Bp(}LW%2upw7uvASK5;Xi-Uo9&X3f5ymP-VFBUlyZwk*~wfk k7YjD#Uynu`8}oX;H`r*zp^RnOc(!d{X!p7HHXWls01Lvaxc~qF literal 0 HcmV?d00001 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..f112ca275df4ba33a26faa9c69efdd8d5292a98e GIT binary patch literal 5803 zcmcIoeQ+Dcb-z0tuy@!4u?G@|${s?2bntScMawfB%Uba4N! zM-Dt>?cZaR92j5yy}RInS48!kSK>IKz0jZ`_)Zl>6yXGG5?D-I zi@g0JPhLo^Z2rdE^_;D)nuMA`Vrr5dj0Duxz!)zgQSuPsi3}o~;Nv;b(d81x1={OU zcZY^uT_S9{)48;Xt_+HZF62QTBFY}(X?EM{r5h&c;>}aOYL~53*ywSt))B>c(Ya}TrFA*|imZH> zGwoRGv9#>8qy78?gnkU~eKZ2evcpHi zK1zMG!AFUYDn2UtD2y~5UG!BLJI~vrBH~5a==G?ef@S+WME^Ft`BOF=!>uxIlW@Bi zci`N(v+djr$t_Uuka7~?HXm+Ra7P{9=Fb(Ku;dmyJ;@wVoV+EFuA#q9)l1n{={kB-6ZLEO=Rw`q8L z=wodyn)X0>+{|VMx4y>>ZhZ^nXCSK}S3zC}SpvBPavo$6WDevsP?lh?azg$RCybot zMERzOdWGHDf8dyXmCKDsAO&IHfEsY};8w~HZY3ZR2*!1HLpZD0ZBebDezw}Qdzd(aAPD3@B7y(*{n zxO;x-VZ`sO)e=wSoYb?`3^3y~FynDxMquLLX@?nWHg8p7&Wu|W{wH1(J-}cU zIvbLmJ1-F25C*nueX40jY@XrMPsy!|tuLk*6-d$ai*kiUk$i&(?&DnL@saTey7!eI zTs{3A5#9UhA$I*5@4xqnzk2VXRo)B?3`Pg|!Dt^y0wl%@p&0K_ZK#iDbbx1#37+XO z-f`eM+5SkZkZOf-hh(7Hib?ik9~h4z>oB%2-VC2oj8k&|W!XM=6Y7w07&XoFqtg=5 zMg%=04n`r=Q3!PuLLKEm3Lv8(Hb@2pQY;Fo7KJp6t~zN%LG%nSG_8J^MxjyO3?*2| zhOa)RWuS^xt4-U*_Yk^AWcmO*_un%?x!|C&JOi6?2(bo6zZ4WrEhdZsQ>3yU(s2Y7 z2x2PBv;k|(!)T)XB%^&mxQ#%#Bu8}(>zY^BWL-nL=Fv5`u8F$l(ltTXc-@nq?IQW+ z{A?%5Z_dxQk^H9oY=q>)`B_TxP5D_u@_K$oBDqFlll)W{$%XP$og}B_r`kv^n4gM} zTtj|}l3aa$ijZ6&UzCWgk`XDt*hOrAezB9-b@|0MV*BzJBVc1f5=egIm|hZo zeN3Gcb5gCEW*cJ~1EQ`_16z-&X7enRapGEwY3IMagU~*q7Sq^z+5T$z$?{BjL3tS| zE0R*EMD;QPW~r2~&j}D!v$4$#cbZLIrXCT_IT0T7kUOVAT|{7^0V^x%v{|Q{b-GEX zVVySVRM%;vPN_~qI@NR<)MvbB?sj5?=Q@>8@bn4ToqEoC>uTEv1f*zz(k51h> z6?N)*K&L`Vr~GrA3A&&Lg<|FLTpa|oWf8eiwk&=Sl5 zGOZxMK|&P;q68V~A)ox|FA%c$N~!`ifR8-nQ_Jw|OT0_V_yA7S4qXB*BKW$Bu`zTL zK2T@iDdnNRg6}Y(o0>Gj`=SOfniRUWP{s^x2mEt4S>Tf3(wx(I&%bu9H8Wi+HR9R=rQc!>TH=-M3Sne#x% z%hS23h*S5A5tAld^gM*6&kW6RrdCC&09nw>LjYa}v|a_YE&*CE-9jruDS%Ct^gjFj z|3s+wCM5i?Vr1+VM)Ea`WIx16(@h5;O`SD#m?4PL?;i$bJSd~UfUL30dJZ_QS=Tn} z+9q8K>sphp>AKdaYe0n|UDI?esA~G>U7PgYsvu}-!h0{Gh-~c2hfMV9RO9A zzR!ZA6z~ZkzxRj#j?jZ^EKKob881nA*^5_h*VHcg@Unte>hP71X=)1szRcq#ye!}q z*KJzOB{yCc@rnmuK_6`+omR8p!j~KI62;3pUb$Vfy0i%|hw;j0eC1=B)q)3KR`C+S z%K^Mne>)q3c)0oSZ^9|LikH6U z-a=^e2mhOxz4g8@4t_U7ogR{U5T^gr`;efFb^#*-K>|NIlr)K23l3^RozOb`e?X?z zWDvU^=0b6O%$F-;u9_rb2C1o$)BzS~vtMd9Uc%OQJ;wKy{)>urpJLpH0uQX3 z^^(=^GWt2|kFkZt@E&aJard8hTi?LeOK#&Ok8uTCX9R1%U~R+JUwc|dTECngQ4~vP z{bG7M_T&95BpC-#cmf*}QvU_Xcov19$Hw#C{tMplAIi|A!SUGd{i4|bOVy(Hm8)+; zz+(XqPz)d&;aBu_wVlQ%V1mHFt0qiHUtsMA*m+nBks{_ zPNF_C0$;p`jPAb1{rQ9;cNh(+#FjhepD~gh-4prf#BoVgPn1Y>g2cK@rS#Kh4K)x5 zlqM3T^p<4FSpVxtP8rDn{1fkKuRe}D@6v&iVMMnidy=W>^T!RjbXItJ;iaJ^YYULA;cLb)4cfaZO#tCrk*^Xo>UMi7|{)kQ_V1(Kp_o~sB zmZQZYQ6D{1I_pgqjpUXs$ur%?1A$a2od6TpO@kP2Fml6vswd^Fk_L-9eKoZtlMT6HA;+ literal 0 HcmV?d00001 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm new file mode 100644 index 0000000000000000000000000000000000000000..4423c605f746aa424937c84a10dfb9a4f7b6f8e5 GIT binary patch literal 6470 zcmbVQe{dUBe&1cM_pPLr^(1S5=t+rXvxyxjSI-Ss487(=&~Z|@bA>EGv#+Y11jVbh z3A!ZMvYne|xPaxr-6d)uPCy;gCd5u|rcB!-4)L^Uk12-GncNIFL;tE<%9P=bYuYPU z0}1K(t!;3e9>bl+-(BzS`|*9>_xXIj-`&gu-{QD?BAol<5%>2A@dB3+_dn-lcSJDu zj|xV>ZU(y(?CW3;f^7%ev2AdWS#fuNac}RQy^rkP)?X~`DnGh+cX98dyLa_Iv~BOU z-W`vWM^<0&DUBBQ6t@lbF5TP%T}ux?{OI0o+Xsto4|ZpHu%T^(j~09P?%uX*&(7lR z-NlC*gTm8)EV4o<+}pcz^2ql`pP*S!wg{@PDo8v8KOhy43WuraJ;}1-9Y=tOs`m|0eW) z=;KhZIopiFFNBNg!)kV*@}qboKPHx2cP}d%-O*O3mcY-Elsx7cqq6K8sbGH$`TgpeF$@cpXWr@qd^!J zjP8cu4t;w=m)Nkqp_3gt#D+c;7aNd-BrJL)EF}ax$X&DWB4??WIZJz&vsI3VfwP*3 z5~3WJuzdG5gk(XKVfgJ0ee95DhYTkSfwS&!zh&b-WOIV8FTk>_ek1$sf^HOaT+rnL z5{A1yLFyRv6sFS#ossEjWl~m7A!Qr|&=fE9$Gdsj-@)6Rv=pxomqz3Ds#A?C)3Rk7 z&TzawS(=C|N0HSp!)?nhY@Fo^8(##Q2Akse!p2GX=MY#GY(H1f$_1@b(CmWN$MJ$a z#R<-SP8jGDJ=a9kE$rw&!j*>O)Sl#M2?j1~d<*(r0-FcB!11&sTcs2&8CEG_*?Fr! zBiI*UkOUjR7l$$AwNCt|KCGRypydl%rl6$?8f?p&$yRp5`=hG}jZz2GQz<%~r!xt9 zI#VC3kJl&bGc2fQJ<5c~ITlxD`f_?`E%TZ~~J3Y(#)bHww~z zgF&dtg2yeiA5+_qD>C59(=7#;;Dt9|4Iq)@Z2&SadPFV+kL=)us}}AOEVWaxv<|^m z69PbI#nrGE#=DF}?~T9#V2KUA2$0#!kl8D+OAo-aeXRYfxiO{v+2}^GWhb9|R#Nu( za&yv&c{WC!*5>_rN8vQeW~-MOB}kFUOf4|M;Zh7 zR)2o%$J-l43Ffd!rO>*_7g{fay$iO$3;F`j(5YYMsj25wb-+Y`K+hsRgZn@T5-f4<2B z%bzU>xX=n*Xaz2`0?_p-&Rx)p95rS+8ky!Onc`M0=ntjOTaaSEaV_?oWIpHV>0h;= z(|mcFE$9y_?Z5&T7!_eK1m5ksH^dGRCR=5?2^LY_ z0}xybx$9W~0D>^if=vVX0C+$FAv(1Z#I+HCNLl(@Fm}q)FTp8c>GQm$U04S01-IXl zN&>%N<9FzgU375oT{xwvwg7o;DZOa_zKGE44VK1Sh2dnNUxiL5V2TDE^Ym1nM*6JC z6i;VTG-_MXX&w}hM$1<8MZSKlJ`71hIqI!fm4jZuxN|zrWN%ugk-Qa|1*c1PY&>-NhvNm5RXJuu^Q$NOH1_3+g zDM8L)*0APsKJWH^{V>2Uxokhg&JCPhe-adj1+K4t43`X|`H1EvnulnJ zXcEyHi6#=QfoKBJcp^E+yRg&Z9Ph-=dgpitcGfw^%yTOI&QK!JP88%<;K9eV~uvcJZua4*6g^iXE~Em#~&GpZN@m z-6C7jvU!WIXWZ9vmXDP@z=)O^K6i(b8&?8~!$Jh5b4Ta@Af#P`tu1IqLBj=2E=b*} zv2TO8Kobey9DoKNeB#&QpdCTK7V~R{Ukm#+?AH{(Ci^vLXR@Jw=bc|7bnz3xcIzrr z(V359TXg0PY&SUb7~6vLhKvV!7f84L<9{La!z%y}4}0g3f^6Ze zVud6Vfoj#QT3z9{B7N)y$tXvw`~#JM{0`z9CdQ_do&{M<6^m5+JIT+69v^Bb2D z`m2vWO+7?wA=-MPts`2DXi=gOqD6=XI;s;*BbrLIFwvTc79v`ZXqadLqBRlCPc&tf z*2-(-c8UYR9RbDtF-IgB*kM;hv%;#x;wVU+w`bT3JSmQP%~24KJm`e2@8=|(*xW#k zDn~=`0@=JDD(piX5GhvinH42lrs1-Ax2NZv!m6)PpE(MHTKqw#;uSDaekHDMo9`Gq+85Id{IscysJ^zub6i~N7;FofJW+i7Ok3)m~ zA5kM?kGGiPPyv9^S}ltU_N)(AQJ+VxGQ{sXtRcU7xLAv`4-MFDu1%6gsw0 zJd27G9?R&oB5`v9N_{0Z=dq$4<{avoM8#2`MH1#2+0s(QC#3ds?M_e0Yel=10gn|k z+ArpgLxuVSpm8epg2#Npn>#L3lAzJ9*vnohG;)(rXP2+8_uz+qrCf@}I+WL#8naquX1P|e&L#r#4?g7eP8T#?5>6e3 zVZZYs)hjSA$oh4q3L8CBKaa-@XRZtHYjNf}@xJxWTnFB_&Y6qjeKBXw!26=k9LD>I zb6&<{5j={SZj9+nH^wxk8)GWdjj=G(jj?8?8)G4+8>2xyA~W3>4KUppZDP7H>Sww! zsxaLc^)cNT^)lTU@!$%RjS<9TV?<)IG1ACnV?<=KG19Bb|`MAVTf0!7He82IVTAfMt3XKmoOX`APv|+P6=cduZKf=ox6}+^Df?3PNh^|2O0sfh z`OAex*rqRiys-3mKwd8hKY|_3J_vA=aQ3f%j*t&3HTbMx=^e!f5z$-XL|;$zbwrO5 zJxX*!^a#-nqU%K0h^`VnO!Q`=hlm~|IwpF6=uJfT6I~&?kLX^adx$O*9mbM~-bi$j z=nX^{hz>PiXZv)v)V{N4QW}y!4SKZ8oR!&*%mMs!bFvj-^|Teixo2d80&$ik+pZpA=2SBeoijOuDwIx8*^+a7q)EH5tL?uLx5Y-^6PE?JkDpA8kZ6<1ns6nD) zqC)#7qWX!d5Y2m5GXoDiIY%6p7kERDr1c5c<0nU>IE{<3IxVm79QnaPA32 z8UyH`K&ylyrrKhoTdh9O3+|nTyB$!g{&q_H5J+tiw0020wgi>!HYja(;v&=A-B91c zqsE<3;qHMFcNa)5Jmz;3DF-~Y$NQLCkwo#JtGHD!(7t%kYaWF7%0(9CxOLV#CtELL zYu2DRPt^?7Qg*Pf89p>aip#(E9>7b&*1x^SojhQAZZ^Z&)P{8JAb#GGTg@%WrHKK=_=kLw;srBN{13vycH)CiBvLq8$39Gx75sZ&1`dj)v6dgbf{XjlgS#i zXAjaFHl*44U^QDiVg`e(UA6jVHB~bYXS3Ores5N*ZtEJ{ht_w6m{-Bz;e*wb844cG zg(s4swotll!mLf7Fw<=}Cm&C>C6gj%t)9FmX--t#U%V}QIAzujq-wcr&D^@hOmEuo zd@`B5sjZe|-qgT_R+#j$YU=B4hnJgWz4z6!Tj42mS!#6M@o^&<=D zTST}6gWLl%C6hR~X;WKUa3aZK%6`dLa%O14Ox8}+*s80wBiUzGjo^;9$(`~*$5(y= zp$TtF*2Hwx9rf^uG=%1U)+iNfTjkgR*}afq-8%ibxT@7~y3H{UpSbP*`OMZoJ;7$^*pN)Eb|UAA iL&xw2*p1Ids&UZtz8Xy9It5z;Xi2(P5aCj5EBAlUKU0_h literal 0 HcmV?d00001 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/errorTester.polkavm deleted file mode 100644 index aebe24c4c0f597fb3d0171a9f527bc86d2d77b93..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12890 zcmds73v?URnVvg$tb0cq$+9w*M$$m!Fl3xG(7>8vsJkmtQ>0*Wyw+^0a=c3tVr(ZN zq_$(rk3(r<;z!cfh?F=Xv?&QmoP;gqWE%o$PulL1@Yu8MS^5GBp}b$G910X@+U$2n zvh!##ER8g&lh0EKoX=-b0T3k{l?_RH5y>QvG-J3m&n^r8luG{my2mVRky}eo5E!`#k zm$Zb8$@j~@mEV#LT2^v)NvUTEyPj|4>(raQ8+^BETm2oShlBIVE)D&2s4H}H=)0kZ zLeGcjl&Mp`QZ9wh46lro8STa+h8F#7v^lya`b5khzbKxKf7iVHj62T=oO$rfOQ+_h z4o$sh>N8Vm;>wD*Dzek|PgBlHoOR(@4QKt)+JE*1(|>A5DsQWNu9D0+Z^ptI-7^Mf z+&1II8T~V#oVoc^cYLa$JJ1tUSgH3C#VbkV)590>AubP04Rsy)?1kv?Dr}16<&q@N z$mwB*?}nVhf|AS!066|@iX@jEp+S?D8#JuT<%v+)kx(a5n8H0l`L;0QlI$mvBHo}4 z#ynC+BGc(Cg-)lH;$ewLLOe|IFvLSwXqW{R$~?>yR8&vUbGa;+9i+h;T3$`VRdRU} zR2sqxODa6!3G&YzV=OEy9JHp>#gdH8zF|6DApUCNBP}Lb6(p<4kWPk8vQ{JO6f$6tL9X5F z)gDl_F;yF2+F4vYH?UH6J=R~Cn)lW}GuBH;vq3uQ$%-WD&62(p>2XL~9cix(RPxZE zbW94&Ftkw~nt`9{My^gXw7iNA+F}N(ix?NV7#9^W{?5>DR<$7-*jWURx!|!Pc$XpO zG$aQO7r_s>;0KD}hYd07AuVuE5xn08?=OPyGqnAJd_d))jpz&bL`TRc!1r-gGPJvV z=m=cSL-z^rTmyUvU0wv&8roi;U<_t(m}402e3dLQ#lNGxlrJYO;9O&1s5thpm}+Q1 zyxhgrL8HJ!?1&H`t?^Hc)p)rk$&C9HHd~?dXc;L01OEWvR54t$$#Wbcr_Phh3X&H* z>rbUjkzA2b7*SM33IQmWZTT||HWFH%B{b|1xqJbED2?xy{_4@fTM$04kU$z~^m-tZ zUJcUeRUo!r31aK@LSnr_cI~w7bJ9NhLVL`)P}P#m=~bzb^>WCteaK$q9Fj?-G2W$2 z?IrdAHR_xJ9}U-Q_cN!>r+uE$n4@8N1I+36DNr{^=LeEsS9qE4r-~v&uP|4M26DyC z>@_s(Yq#JiWU0%5NNNKrjit^_zkBm-KRS<`o4)hKTa>R*mi||KZ}G77aQqhQc3zr} z8vmHNLGf4A5PwBAS{2%Cv{`7CXck(6$dLpQY7v@Eh>;*9T17~#hLHFyLQIR0a5W*3 zN^)E|nweHPp&Y9MB$6dfqoeJsV^4g=;TF%$_oCtO;3ihr%rd=P-f;|4VC7hRC(-*k&#A!Aba8Di zi+2&dUFD0fQEF0gIn7WHNw%5+v&6!0^YgL^MUhOAL z^QjOBC+98o-pvZDkG=H{W7?mNuU=@%zH8qxy1TL1Y4W%CER(TVf5m3WUoncd9&H3| z9oisTKiUnF9JxVq#bQJv#tjmQu9rw`vqa+SBx3eUBs?mS$e{EdV(}kfSC(wi$quqx zBV!8L!(G-kg0;Mx%l*g9_1s#oW(HLG&@sM&TabX9ObsV~i0HkHx5?(+%(|OqIw{#5 zG}md`A(rW;^1frdonqJH`-wiH@@~!A?8$U_$PR;ynVQcRzl-S1$G`8fzVEXb)4Lhp z$3M5$ZgtkG_H6s1%t4>FM(C$~8HqN=HzuZTB+g|jw#?pU+Zl^#Gri6ZRlAxAa|ccC zRZG>M!&nIeX5RWXW8eGZNvvh*KSB-9QA*SMSL}YL3@Y_;s@s6~lc8?su-p9wo}4-G z2gYuD_e7p3QljJqc=E)viI0~X-&8*-+*n;&;KsD>w;21zJ126Z%vH9JQz&+E63&ul z(xQ=8g)HH6?@2efb-V_5a}%9vp>neH-l!wAavHL3;u18MGrX zx?Gwa>g`t8z%v|(3W4Sj^;sS}(k~W33>!d>?D@@XpAbp&48YCAC?BJnZ5sU9I zw0$hJyBLcj4?rNkgCUL$4De8wz(ByggN23##x6JD-YL8mjbsDOMT`~^goj!L#voU( zF|?Kfh7&kc#5n9?94=xYop3!LjnoHN5rexJyoiC6;fBH(b%D!@7%3McRm4En&|ko) z4P02nsCF@`ix^0GRu?dm0lSD%>0(qCF_1Q`@@So&4{A3eh*=H9tcYLDW=3KNUE@jf z9zlc{wdN>CNN0}0(^nIuE+mSORzMK-#_NUoizD_m9TTaPK+2jsIf*p_pn;=Edj=R| zrJnKJV%ANsF}CCPB$6U+^`t#XIOf$cnW4b~ArBG;;hwu>cQ!&PkH z60f$&b4t2&^6nu~G#Wojsy8GBQpMyU?Ph{$K_1Bic4a*hMP>3V)H_)k+wc$)O>GaS z!K|i;6c9m_NH1Fp>EJhCr?Dh8>u9)^>h(145F_=z>7fT{e?zk;c<$$Z$5`fdyn>9t zZK1pZ=gq^Mx~$gpLB1LTNJAWdtA?ZR0Pa4oU+E;FfiiqX0O$QG_8)? z@OD~enwGgngRUVQxBAeZ<+fsVcrw#G{A$UX%}6MTV3P*r$O0NtJi*zHL1K$3GN9-J z5^N#yWyGjAhnY1j)95Tr4tWSQsxvv67d|lU=xaPyAIDSQ%k@6QfR4UGq$ng_1@}zivvs&(+lQxaGsrVBYxDy?Vpi)9`5?J4 z5yZ&CZHs*>RyAIFO$e_sOV4>4T0Ft?55CISFJ41-O^nKh)``@+YA5=C_}WR9t6Uij3p|H@H2h>oV(3pq0`UOK=OB^*WMokgp zVgo@FEC!8c1KmZ89=8b6BQSPx8cC{s6G%>ACJ*frgl37p&@6#;KPNXRG*qYB29jUb zz#lignt{wGq!q}KVNF%ta!&!UNsJ^!-PQX# z?INM{kSDtf9?4%}BKI-SbhIFvhQ`qpGzo3;(8pi#D}+Fgqx}@^5wyc-52O7%+CdaC zWkW*g=a?KroD@HZs^vTqLQFK}1;j~+k79_K;)t0{#7u6SbW{=rEv!RZ>w7;ZLL&zx z8oo!O=3a@$?viM9k3^^JlxS$1MB`f|8I=*2_7+L;B|ut1d{Zb2)~mI*q?{SJ}zkm3Pw0rfklt**28*$XnwlJ5m!ynC_1S{V~lSqkzQ@eXjwWsd!Jp`9i}x&xxgLFF_goVbdQr{9)Z6 z4*J8IKg|7M#qXJy>isfw1TT1k&Vw%cZ(j6=6JSv2w67@K5<}1bmW%tn7mxc9%XO0l zJ&bl(ew9ld0-9sQSwUEC+1KmCp_xYGq6eBRyB|8122w{1ChKlh<6=c4&f ziydlx?gYyzV*39Z%lZ1VlUdHi_x@JQ?LVJA#jbsL%ee|e!8T7=&TFzRg{7xbhHPA_);3`}+nP#tzwZIj`$u;7AVNEUb{E=S zw4G>s(6*s%aU-{4^n@S|!4$$LUl_4mF@RcfsXOEBGw^bM)fme2%=^bsurer?c!J9g z9uaK&@26pt?3_pwJ3TnRJe>o!2;o??-6pj_$Gtd*APp9mH%Q5>8QIPoCHdWGZKeVaJ?S4WC#SfqQe=${; zJvEuB>M3>SHtngunyLEbk&{hT-<`h^t3QbDzfzF5(xotAs;;kd30x%zoM@^}4Dvdg z+ByoBs!SeSNOR&EN20Yo;b%@uYrDc1W6qugjw{`v^dqinEG9QMPDlC{8>*@$XR4c~_f zlS;$0NF>QOc&tt>vr)_3haywGTVw)GoqJ5oGWQ{2c6g3jt9>NY$nTab8}W2Us+)p_uiLy2a8_F#yWC8%3c{%gK|5e~8|ii*L8MZ?_cRE)<0k zy^WU?cDmr5MKH>isDTc!056smy`m%J6<`#dQ4ZcBs-%Ir#nJ|9D7XYNw+Kenahs@+ zAWNcSjdW+0?r`Y#dJ>*R_teqbQ*qjmOZW&?`4j=cj_ zOM7!>gQjg_{;8DqAJ_bG?vE=t%C}qM_7S9B;))0Aj1Z71j2{3S%VP^;eRpIlfxdSuFe}(V9Y!pmqT;p^64FI#IuhW^oCE44GuuAZvB9E=Vzv z9)tGkv@b}8G&0P|T7?W!%%g|XUWMkyIk!&w*{=odryqyEBOPRgLOMC-D{gY+f}~H! zeGVb3qKFkhSqf(iGmHBPS-pMy6k@!#k17~2E-qrAw!gK2k)4po2VIN@ix@a#K-H{= zPI`=y;}gQZ$$fiM@ofk@viNR!LYkMj;3Y*cq#0HA9?!(-1;iDz^A^DcaqT&t=+wFO zvy(48?PGSYtvS7#z0{uREcI$De9i%{z1FUAaA7Q-wfDNW2dKC`uvf)VhO+PM0U|?ehW_LYG|_p z+ew>I8mG-1+E1H>^`y-~SaaH}L#HV2*hL!Mi(`p~4W7h$Dd7%u{-cVDOuwN;*mpPQ zPuwDvxS)ub+G)=X{QQKZKQSu({1<{V&;Q!R7m5G*r+uxE_$M|=cewcbetq15ScGaag_>OD6ge)6Yc{AAR4WrdZm{3X-idV zCTL9P1ueI@xZkNvZ1!KDqHzNe35uJZ=lDbZ`%HBlFXUDTg zPY=Ir)&Mhbjm2w=eMP|z@g)I|P+a|60IgZEBsq>X3{&Hb<&-9lY{&L-4 z9`u)M{&Maw$EMGYcO7&!0`ebp6+t6W#TkWaaAytkmz1&xP`;flH;Jl>7<#*8W9Yro z#Lz37FmM9bcrp0Gj;mWh;JS{)wII^eo>&e-O^Lo2*sz4=^u(E@7`#H@-B z;PRF?Gw3A|gYUrYGPr7!@58~6Cv%@CvkS7BK-_P20-=)c!1+;0W>*P`SXh*`5}mVn z51zD*cEgr(iW^>?L1AuJ8nnkG!6YfK!F@TotagfoEXR}BDeZj(5s4?UOWOU2bVt9X z&b0J~)Y<9$aPT|fIlQ8Ogs1cCQSlSM`z-)At+Ysjk2Tj@EWyrrg7`F^YLyFyLp zNBXUFY7T$}kRA$cnv-`vwK&yUTWhJ&Ibxu>XQy{sGX#b^Zm`0#D$=2#_>|%EwZXx` zRNlJDMFG~tU-`6cRh)CcCc4CrhVw6@Jpo#t!$ z$ukvOpLZWZazIqjPnU~d?oJBc;*$uuTuuo_xSyLg70|I%3?SC)O*dTO&P)vSUZRO% z@h1cn61Q#r`usN0Beot5l)vi2#XqUMQb2CVZv$t!vPyh`4=EW#+-O+^VkdE0Ol4dF z1Oc%n7^LAQF|QEVihuR|NELSI_!M2?KV<_1+}#GOEIrcDFgGvuqoLSNn3|wc)Q5B) zR4z#bt#KCNdsV9TESDJGObNyoR*tb}s5j~OKc7(3i8NN&zYQxsMT$WYOoKT?}1i8fRf-*mjwgy>*{?hrx+#BHgDpx~@{`_lZX)SN_+EAl1o P|DsnIq4bp_3F&_TlmMih diff --git a/substrate/frame/revive/rpc/examples/js/pvm/event.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/event.polkavm deleted file mode 100644 index 859c1cdf5d3e97c7df8cbad6a0dc0cbd5f8d6388..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5186 zcma)AZ*UvOb-#Ce!0sLPzykylXA-qY=R%MQ9W$03(GJ@|Sd0zOtcC?J3a4QsN&<^N zLL{1m#UqL7C=Oi^q&gL0wxkq)@VN4b)OeD{)|iy!nKGTph?epplZo>wlPEKtrkP1= zJ58G=bOk7M>qHQXD^ea^f#W$Hzz0E}y*Er3hP#``u`)aB9l^(v^03v3E=2 z;Dc_s1sVyHKPF!wMZ#TSSGVg~*J0PYuAjMnDF{5kI# z@2|YyYiKvF8$UJn`1bog=Sxg^rwxTRXrEU!NrK1bpJWv#m-qT7Z-4F}c4!LSBWX;M z3N_E2H4!w>^?s{t{c(0;?YNW$5@$=i7hKS^f-F`x8?>&w@M$^2=a+ zE%DC7um95Z1XcInqx)g1hUDpOd2ZXsRy>~T^2^)of+_|z@DKJ#vhOxALZs0oO#!*F z)$hCQKMe|1nA?#5aJz{zN!CG9gi9o09+G_!Y$sg`X(t_`u|(4NecZ%Cc{h7`>#ihcq3K8zVSPqDi3 zH7uGT(WFE(D4GFDCRwwc#3We`-o`B@nNZ?6Js8r1rXCFFK|>F6J;?N+qSHezA7n%D zf&pKJN&x0Vu)yK00%a5C7{8|RcT~Qn@-pS0V4_{74fHQ@18S~mPoZlw02vdCgD@6> znIR~}U^)fk127Twb};{(v@Ll%O}@hXo%pJrXX+y+ud3KVvLSCzUE@hdWHt_#n(-KRZQqg}C3ueege97e`Hg#UnNbJ=kJshCi-CJ|sR@SOYr= zS=_l{Z>4T~UbyNni@}pjoil5Dh$kWt#Q*0*N_#cNC7CjhLc0}mNbx~U==~82_X=~F z5VIL#PCZ0?HRu`t^kD;4BnJ`-1w~aPKMG~vE5sNkjYFg5Nt3C05Xj*^Xb!bp zR`y5oMgP8woF1`LS-w=!oLX` z7^lc#kJ6`W2if%N-%xtuqmK{r5K$!4byIR1<_#n|fs*ETQRkVvbV9Bz)CqkXb!wsb z@HbGa?IUOj!5ahcW)!Za;A#xsj=)=kurb67TwoOu`;^K%9r)h?QFY#QLRH16Ulr%q zz`p-m*!aCb4w?!}yyZ4pyK(wge+#CG8ziu*NRK#({E#5gi)C2e-`tJac1I(!O^mlC z(zqd!rYn-%xGCL1e;q&gHKjk*6a~Sg3Hs9*GMlC$|7kK3p2m^zybH`P;@i96#PUKt zSTWu<#9~O+elS-^4`v$e!91nPt3dLFmdo2DS8O@InVMI3sc)Z&`{~~&9JCGTbj&0%m&h%Yo;- z(yJ7X6fHkB;n64K#G$Y;Z=$x93hW8oWc!VX_c zu50{lnzS{@3?-+qEwM1u%@9@{9^Eu`GoYJ>ZgSnkUWH_%I&09Lp>ntteIS_;xAAEU zeRu+=EI|f=AqL+yaJ-Plv8P%mPm$~aoyA5A1vzs@Is}<>63GN0a~2<%_~!5tg3K&F zaCv9&VeP=P`yc<4((k&_Js2N^i3k*jU@Qiu6wD03RP^4lRS&CGg#4IyrCuFeaE|Xn z{rJv1wS}OJ>~8EKfKd5P098C*RyUwJVpSMG_+kBU4yzBz8>@@sjyPTy*U*W@Nl#sT z&&o&Jo0;CMAQ^-!E$g@UmH-(cRS{ty zwqYa{R+3D?aL141L*d7hJu-R~UV#(C;q~MOrBloIqccS96RUGp7 z3Pw`P@}`K2YytAI)I7Q|0QrdXG6?xzd}*LFXgUgZh-716nm}t zO6K82H(6ki?jf@rj`b2O#YZCKOaO94QZXSHBb5-P|MK8kSm1T=K6u54%VEqI zLQL+1*Z!JVxep-s)x`3Bfam|=zgQF6SO_b_s-8*+p78$uyMM!NW>O42<% zUaPQwDC5!HW$cm&F3fjpg32+U;O5JdBbY?+^xVc%OCM1Bnv7%w;q1tRL$9jVt6Fje@!lmMQo(t; zp%4>^wBlj6JZw#|mQ?mN5}M%FgvXkt$s3-~Eaf+7a@vEDAh@hWsEpfc)>Y5hxT;-+EocPqxLZE$5{o3RyJEuQ*p7 z%+0bm1x!W_Gzm_4jk%4;{`V=3VpJh(jNOy)k?nEi^73A58QCey@J@6d{4=*(uDv2I z5$82soLPgmEy)`rBwKPDeLsGW()T4fxQRDd=lXiK>Be_4$d5g?_8MMezwtuJk5}1# z@~O|@b@u0d0ptPp{z3igc&#mWp8ADO8(%z_f^2};y?BWtb`RcjiG2XFt;FtztVQfD zJ=mcKEj@_Tf*fTa>m365Pb1ra^(bIH3ZfqMZQ_5m;7S#|tw;OOsW0L>e>bJ!3UziU zHuD@-yQu9po_-j0A9HkDk)J+)&mjuKgyuG4zxek%Yhw%V0eHddcYWnOA@48Y1Kh?y z@jdCaSxen#1ro7O#^ZmM=zMf-Dc!&Clkuw7*7xYF(Z||+RjX6=$>Lp&_gAeaI^$I< z?vKw|@#?IFZ`E^_8jm^eeg3M|rpBx1W>HuZbz0Ve7p=9!Tj#!qedpRNG_;zk&Q>p? z0uvL=G7!oyIYTTlh4|e0TQl1Jc2@Hx;<3J3=Q+!&SXNs+(N^tGzQ{ZOdTwrM7Ofb< z5!9**q4O@@+TwkFbV_(t?~C^@&c=4E<20!4`1^l-7goeN#QJtst+wjLi%ZsP_>0T& z>cyD<5fn0OZZT&0YCZFFa|y9S PI4Am)*gBU=#F+HI@tB`4 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/piggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/piggyBank.polkavm deleted file mode 100644 index 1a45c15d53f68431b7e5c1dff297c768e1160e97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12334 zcmd5?eQ+G*dEa;6)$Y4@yQkH8wYzt>!8)<{B;zIyby5LND$GGnMtD@+K{TlnWFw=K zEQlh@(n%IGE^&}#I~^lCST=6zfNV=xZJc1hUzu@$scq~(NQX&DhcZfm0KsYaYU3eJ z-QT-+@>d<7GYOsE+_N9=`|SJqyubJNJi85d-zkt!&lmh3QG_4QmyQUv(wgl7o+3f` zML8r2nG3lIavS8!kY$j2mv(k?S>D~Yyyu>kJzwu$+P1v?fv#0O-OGDcbw6-V>(ZX3 z_uTvSuKo+}SGErEuqib$M6k*Zbw~FZ+Tx`HyYF9$~NWvT%^TDTyW5l-y8~DOs$1 zO_^ZNv%~63>Q@7Ofky&+0&i)i{zd&qWxp&l!$-r5XU#SbmVeRuroG9&Gjd&YL3AV* za+iGS@TcDWRP>q~uh}>|QW1?O<9n}-T=)K*%EVtK)+Dwhb|wxaq^j>$ovzwh{SVcR z{hL8Mt{4owN7XiuZtw9Re&hHksM?dZ0t z+lFpyy3KT3(v>A@8OgfjA%lFAjF@DdOV(>-gG7cLGR(At0qwY|O{&_EqFu}Qaf?!_ zJWa*6iY0_Bs~{coN!BCnRb<7y&=dftX8a-p>fjiD9 zN*f_<4q4eidTL2m6B(!{{V6h-A$`fv9!8c)_VvNggrn_e_S~R)$Tu#S&IF@bHCr=bt;;Q$cR!6AV_1 z$4S;i?WEn!n{BCbKks;7GkQTfBpZeF^ETMKRc_tri2oGU5C_M$p);a>2Jxmg>9{hadQ`mk{qCn;XND;E-)V{C>XE5^tsgc z_19mzjwq?WKC$!L4~eCz;Zs|mUnNp=kcM0I_!*+dPeVR}yazc2c^mQuWSRhK2?x{g zdxSXCghW0fBzlIB*eOEXHwdv#6Jozj&O>UOqE(%P)I$`#CEYGr>5v|i3>YLw1~t+v zkv=A_KjW=u?BSqz=&ZX!^;QJaV?i-@mhD%)e!&}Iv0h?!$}G#=W3qRQrCXQ_b7@o3 ztC_g_ER4X*DX|G+u4OEzLOsS1M+=f(`J`lv#4#R;)v(6-EE= z3vI>WwsLUq@0^JTFgSNU82xk;p|f%`;{!O215V?B(>UNX4mgbiPUC>n_zpp|cL@IY z>=lRubdBs2Nc6BkVp9Tf#|2{T7l=I}{GRc7S|s5?Qel!Ra=k{XB{C0N`WcvtHxyu# zOk8``-ORkrYI>b2zIc{xV;+WLA`m-D%+(4T2)G9o?;uV0QgXfFZqTqnrTeM)>{+&w zqVKT-#N4Q|e$Csiru$Sf$03!j7F1%ZiTRAewg_IA;yn{Ie<-tF`3ox&N@j(UxFK;O z{ajFMQ8QbDi7$~zPi$+&?5!m8B{lI-Vt4v+1&cj1q-s>rI(dbe)oQ6)U6LQ&=vRNE zDBnMGesq_}lHiuSdW~$jKf7jTL_bN3dl2UM|HIK+G`&AO{5+}~Gv7onyVu{3dp--J=o5qeYH>cr|^z+OG z>b@9APcjl46c0d|+dC$lxIN}}m5l_vZEAX0)lRV3Fji~MTn~d%J^a@*a}6dRYhp#d zft3^gsVE$>EQ~AcsE=gLw!9@o{RgtDF_1pq{a5aOEXp z8F37eI?W_%itJ&TMw|FN?pC~qRklSVF-;@}6WuB1O)2RSh1v}yoMoa2NMrjX5|KoL z(mau}TPZQS*k*}1Jq%mEQ;!AC#wBbV?q)pKL(LQrfJVA9{Q>qk^Qu&~iF!<=;jH_V zLTA-_Pa(Ip@p@`!GW|j7BwdDGUPuRm>>29a%ShA}iRFlFgXle{q=yya?UrbG(CcO--0WQ&Oy9w1 zq{(d$YA2M;01P=hyAL}=>+EefX3|^mNT;9D1ppydPo`bT1QhpLRXd<+F9bk-u~%ls zK)vt4N`Xr-_nHqCrRy}YT6D|REl0OZ-7<7b(=7(ZtIJDLtG@xG^_Azn|AC_1dRn(C z;5d|B_5q1`5!@^&Tc{^P617v3j7?p%j|gx~!Y^PiIfi}a zB=#8)>ex#{Xpq^R8mEUZ2JAMCdnRC2M-}dO*n{v)>^3Fum)I+OHh0b|v2&auBG>mx z>~=r*oJV5!z$4+gzCU7TeZDq44K5ET`oMnF^2ujJ)i;)Ietz8X$M_!X`h9bD%HqNo%7Xv<{Ta?cvyMv z3{MQ`6sa$&lp~RbqDnnjB}J9GNTjK#QYVQt7gcJ;(k!afN+Q{!N?Fbf;YogizogU8mCM0&g87MwbV|9-W`w#)y^kmH{OP-RA>z zR?>T3qp_Ow9Qm3GN*eYcRBeg%5F!PM#L-mh^p!r+jO}}_oQEJ{V`K;Rb`Qp&Zna*hlXF8nmaHhi1#hPEX$9;rkk9l(V5}BE}FA0vjrZ1mF(TeuYzWYQIdV^%5>?seKZs zB(;y>l%n>$8^AeZgJkXwoE-~{do(BMq; z>P&v&9ltD9TWt+b5~VnQ}*A9}k~`AJF1OTWr{O^dp5FPHrI zSLXp3tg>+>7zVKym|Nh>%s2bFFK0eMy;}q3q(30sE0UaYwr9Y`Nk_dlq>;8}?)zYCIj)Ab70mK6zH6wD zIldWV^T}(#v}yD(QB=9)tM7*T3q}s2k3A??Fkw)AzP-tfFjCpLefmw_ug|=jH_+5i z{M(9mNu-6fD|);Fvs6nM;G4in#7zoP4{3m8AWh_~=)kjvS4-^{B(xG5$-*_Sz&mI6~P2WUqz4saP9)cEe`a@eqTuN?}4X-rOHzJ$D3ktrgFhX zg-yiA@-yg5GqF$O1W5k~CqVjpeh2MSIDy5yjT08}8#sZ*Oye{-V>^on-{50>;vIqk z>P8$tNd_`xHKO^oq_=_Od;+uICoq`g>Z>3y9LopaAx;Y!zA6^eBpda=d7U@n_IE&d zuZZXU;4iI3!8}_8%!$=m6w14bLU~V7D9;sz^1T>FmO= zMq|`hZ_lgq*v2iFeX0=K$h`Y=-j|!+I=>C^=*En_-hI-~j=kmEYXezXOM04od(H6I zP`PAi7YpA9IN|*FGTSGO3|>O5*KzE@TPRR#PQZoGG3GP!&~Y9e2p{L!AkZMc$5m}a z3+*ptAMmpe6tWS901l1>@V+Vkbe*4FSIEXYr_IO?hH47gH~QH(7P9f)X%n*LP*owj z+Rv^oWFrzW%2ytrB`b?vp2mR8t=b+x(ZC$$G>!ZI9EislUx6RLdG#x>SvHzhP4iKI z;Y~iQk!H39^LUw=rA|s`?D_s3;+W)(fvxng0mNYCbe{~4%i03o0MM|DC4$~+EnTUl z@6!N|Y*6zCk>1J@lGh`rE1}%U+IU2t#}K>mo=LRV2sA9o##eTt)U^VQq8V!hVhkfH zv;h$!xUq`2PNb1~;xvP4P^T%KgL@0J&A96oZ#{gDoVSs4hIB4Stc`-ZksC8{HVf(Q zAX;JeN?xywkbyZCaK{vHOilNyW(VlfnBeVG%p6?f!MlgZtnGN=(P{2t=9tD`x$MHg zX3ZyL^HJG+SoQ`Pj^;jQ4k8+uGrPcHx=1YJbDjop9xoB(%qOA1(;{w*c^F=HlL@~G z8yj{_XOJ(lcK=QN7Dd6BH@q=JhvderY&5_7ns5Ja;Kd(tQjP5}{ycPp2yp0bo>>ZC z`?0TS@aM&W+1>z3Z(=#YTjReR!F=?qVJzWDCj8_y0t&=zA$K&9`eu@9Ah&0(ybNdv z@87s%8fy1VvAtl5@bJ)hSn3s5iFn0T!lPCV;`YD9TO77A;01!|t(d(GgS&cinym}4 zc^VCy)UEL5Y3#)S4`4VRjk?}CA-!E-D}pQtE{Q-0^H!+rM*rPdr|Q-O>8yEPjhKd) zTUSf1D%bZ#(|g5>nThWn!=EMfNfo@C(-$Fhyue3Z!g0G5mY@iKdMhP%yMl;~*9N<408yfm`RSyJcNsLA z#8RS018dO|k0p+zPY8*d69ehh__!mO*^4+$EsfM@n}XC{2<8oc0%2;5TVi`+eR>1p z|a-rh)CoLKoigYSjgB+=ghU?~Z9(KDKH?>|#In=;Ma&kw5~@l(OQQ11PAlGsq8U%M|S}pR9C$KnchtKX}cLr8*{} zvxR7F43SzFu~kHE`GyNT$Ls=Yuy%%gNPqA(9wg*}mA(p4ZVv0M=Fj-N-iy*&`0Fb8 zvep`Sm}~GJZixhhUqhw2={kIgSCOdZv;B1XKnZcZ#Bo0ByV8@g zHl?P&BWus8fLcCVx8l$UM}I0Slv)kRAr_ zaM*5qECN>JvS-1q8j++?afS$mNVI(BgrzO%S-G&Aar1j-ug zQoIw7^NvAj)Whgps6AoCcV3)=L5eTx_!gTiuN=R7c;hzSn5ytd(t}*^Ge#yJ!3b93 zVJ`VU3Qt~Gdf|FxEi~0p<8N@kXze&}%gisyhnoKl4Dd`b=gO-KLod!dfZs!@p)7sQbxA-|(=^M}^!zd(` ztSrAE?-p;_ZAiqB@|N>|oSP#rYSH=LptkW#OcYAI!+4@`IFnk`!7uX#%p5D~E|{d5 zCZKx134Wz<-Ye8yS2^O1^3H`(2l zKJ0vK_!Q@B%`aGfjfpEHR);I|h1P}Zc+w-|ymJ@)u>`-3)mKQnwD5~mefx=H%4eG5 zmowhTPjl6B}VbOo#UH%;%%dgJig-aH|qizZi7kwou9^W JpgkTJ{s(-6RoDOk diff --git a/substrate/frame/revive/rpc/examples/js/pvm/revert.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/revert.polkavm deleted file mode 100644 index 7505c402f21b7f8a16914f1f067f94eebb9b3c96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2490 zcmbtVZ)_V$72h{A-I-nQI_r&NZ|v2@343u80z@Ucrn-P!%c6FYMv=2Z5h`i}ah+b% zQfxO#{|H5pyV&VTRK7#a9Uy_!JDTv};6Ua2=fj~Bkm?XfAU+`7ha&L_z9At6HEnof zn}!3OiiG8TJ3GHOzj-tBdvB&be@uZpn$q|ISAIUk-c?d8!(_(+d@Ou=@Hu_%{Dn%n zFuQP~RH$4iohuhACr+O|d!aJ>rG>(Yx!KC>;_SkO!ih?0_FVZ?p;Ri&9SD&sVPBqI zSg301T;cq}S1zixqft_|i^`Vrmhv0rG~6V=Ab%o%C++ly?3e7f?62%I>L1jD{G0qf z|A7C6Ya%4_7lW59#@qBGOji{6)cRpj6J)K+UV8BSA?(l@Z&!4oD5QTm9O3xRFEef_ zM64mO-)dJ#=mE7{+TqYhm~0DZ~VY$q_W~OokxZO9vV4rG3(P z(j=sDrE#PQGaBI*qg>^x#WdAY#|a6&ORZ7bF+wB5q$7os{rN`yhj5NM9Q_@UGlT`?vcY0|?Atd?$7fg$8{;OuxHKUFh4VzKRj2dQC zm{Df(Ng9HA7rt)6H=q`Vbr-H0@G^rn2dcujqZ{`%V^cHMxbZO|?RDP9zlO_ihNge# zG3S>7iVjq!;Cu=$&%l)|T=Jnb0p(1vPuNvuPYL!r#)h!_v8lZ#v>wN(YuG{O-QY;e z<8Z^{aLePeU!umxNYzntO;RBK2c@@e${vx)2KCBEa|%qmv{A zO)!{3uuIYvU9l`saoQw=n!>ZZM{TIQh9npVrZL4vkc3vKb%k~;(@2GMtSBg>f1OnL zZ@Y~Vs5q)i_0j04&QOZF6s4}AWOad()fH5$ZlG?H*OTl1bv^lhlKby-VZTdZpf{Is5YJNcadwrcz&;NNNpW<~CczoyH$P>}zVWE*`N^GiJy)LsMpG!VG22P{s_UNHitIYfpibf=(Y|$00reZU!PV5Y6sO zx5$mYC(`|~0?`pDxll&MhT)P0%W%bjMFvZPT;27q3h`2a+}(4}Xx^E?^hSUz?}>Lb z@1o+>M0^p#3#ynG?zZY}i|LcXz0QpypS~=}5BBhkyk#E03E@>CmNoRBUJ}L_k1s*E ztceZb;lIqoC5|QL6Z}Z?BqLvV$oZMa`zJXGOmPl8LsiR()22`CIrKUDAs^I1GXvci zVjq7D)+mrEpebN85XgezgFXQw6MR$Hr=*B6flh1on^K3TgoX+9f>bc=9QhBWu1k-t zhEi|JQ>4!D|C41kJ(z7JVXl#cImwST0MqDI%u)8V0`SolL^WOt7|#pihD;%PaFwU$PQRO^}Te{lZCU9g8x<6=NJb@ATC??L-W z@(_f*UsSEc z5XN<67owx0$g!H^Es*%{iVn_Dy!t^`6`*qvVhR6s+?qa!jrf~#k09n@!{lvZ=bK(&&S#&^)z_`>2fA0SbavItjipx=*-qE%Uhdc!ntI0W$<@7XEmvRl zyj[0] @@ -37,37 +37,60 @@ function evmCompile(sources: CompileInput) { console.log('Compiling contracts...') -const input = [ - { file: 'Event.sol', contract: 'EventExample', keypath: 'event' }, - { file: 'PiggyBank.sol', contract: 'PiggyBank', keypath: 'piggyBank' }, - { file: 'ErrorTester.sol', contract: 'ErrorTester', keypath: 'errorTester' }, -].filter(({ keypath }) => !filter || keypath.includes(filter)) +const rootDir = join(__dirname, '..') +const contractsDir = join(rootDir, 'contracts') +const abiDir = join(rootDir, 'abi') +const pvmDir = join(rootDir, 'pvm') +const evmDir = join(rootDir, 'evm') -for (const { keypath, contract, file } of input) { +const input = readdirSync(contractsDir) + .filter((f) => f.endsWith('.sol')) + .filter((f) => !filter || f.includes(filter)) + +for (const file of input) { + console.log(`🔨 Compiling ${file}...`) + const name = basename(file, '.sol') const input = { - [file]: { content: readFileSync(join('contracts', file), 'utf8') }, + [name]: { content: readFileSync(join(contractsDir, file), 'utf8') }, } - { - console.log(`Compile with solc ${file}`) - const out = JSON.parse(evmCompile(input)) - const entry = out.contracts[file][contract] - writeFileSync(join('evm', `${keypath}.bin`), Buffer.from(entry.evm.bytecode.object, 'hex')) - writeFileSync( - join('abi', `${keypath}.ts`), - await format(`export const abi = ${JSON.stringify(entry.abi, null, 2)} as const`, { - parser: 'typescript', - }) - ) + console.log('Compiling with revive...') + const reviveOut = await compile(input) + + for (const contracts of Object.values(reviveOut.contracts)) { + for (const [name, contract] of Object.entries(contracts)) { + console.log(`📜 Add PVM contract ${name}`) + const abi = contract.abi + const abiName = `${name}Abi` + writeFileSync( + join(abiDir, `${name}.json`), + JSON.stringify(abi, null, 2) + ) + + writeFileSync( + join(abiDir, `${name}.ts`), + await format(`export const ${abiName} = ${JSON.stringify(abi, null, 2)} as const`, { + parser: 'typescript', + }) + ) + + writeFileSync( + join(pvmDir, `${name}.polkavm`), + Buffer.from(contract.evm.bytecode.object, 'hex') + ) + } } - { - console.log(`Compile with revive ${file}`) - const out = await compile(input) - const entry = out.contracts[file][contract] - writeFileSync( - join('pvm', `${keypath}.polkavm`), - Buffer.from(entry.evm.bytecode.object, 'hex') - ) + console.log(`Compile with solc ${file}`) + const evmOut = JSON.parse(evmCompile(input)) as typeof reviveOut + + for (const contracts of Object.values(evmOut.contracts)) { + for (const [name, contract] of Object.entries(contracts)) { + console.log(`📜 Add EVM contract ${name}`) + writeFileSync( + join(evmDir, `${name}.bin`), + Buffer.from(contract.evm.bytecode.object, 'hex') + ) + } } } diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts index 92b20473d165..3db2453f2475 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts @@ -1,5 +1,5 @@ import { spawn, spawnSync, Subprocess } from 'bun' -import { join, resolve } from 'path' +import { resolve } from 'path' import { readFileSync } from 'fs' import { createWalletClient, defineChain, Hex, http, publicActions } from 'viem' import { privateKeyToAccount } from 'viem/accounts' @@ -89,21 +89,34 @@ export async function createEnv(name: 'geth' | 'kitchensink') { export function waitForHealth(url: string) { return new Promise((resolve, reject) => { const start = Date.now() - const interval = setInterval(() => { - fetch(url) - .then((res) => { - if (res.status === 200) { - clearInterval(interval) - resolve() - } - }) - .catch(() => { - const elapsed = Date.now() - start - if (elapsed > 30_000) { - clearInterval(interval) - reject(new Error('hit timeout')) - } + const interval = setInterval(async () => { + try { + const res = await fetch(url, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify({ + jsonrpc: '2.0', + method: 'eth_syncing', + params: [], + id: 1, + }), }) + + if (res.status !== 200) { + return + } + + clearInterval(interval) + resolve() + } catch (_err) { + const elapsed = Date.now() - start + if (elapsed > 30_000) { + clearInterval(interval) + reject(new Error('hit timeout')) + } + } }, 1000) }) } @@ -113,15 +126,17 @@ const polkadotSdkPath = resolve(__dirname, '../../../../../../..') if (!process.env.USE_LIVE_SERVERS) { procs.push( // Run geth on port 8546 - // - (() => { + await (async () => { killProcessOnPort(8546) - return spawn( + const proc = spawn( 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( ' ' ), { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } ) + + await waitForHealth('http://localhost:8546').catch() + return proc })(), //Run the substate node (() => { @@ -155,7 +170,7 @@ if (!process.env.USE_LIVE_SERVERS) { cwd: polkadotSdkPath, } ) - await waitForHealth('http://localhost:8545/health').catch() + await waitForHealth('http://localhost:8545').catch() return proc })() ) diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index 468e7860bb9a..37ebbc9ea3b3 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -1,7 +1,9 @@ import { jsonRpcErrors, procs, createEnv, getByteCode } from './geth-diff-setup.ts' import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test' import { encodeFunctionData, Hex, parseEther } from 'viem' -import { abi } from '../abi/errorTester' +import { ErrorTesterAbi } from '../abi/ErrorTester' +import { FlipperCallerAbi } from '../abi/FlipperCaller' +import { FlipperAbi } from '../abi/Flipper' afterEach(() => { jsonRpcErrors.length = 0 @@ -16,14 +18,42 @@ const envs = await Promise.all([createEnv('geth'), createEnv('kitchensink')]) for (const env of envs) { describe(env.serverWallet.chain.name, () => { let errorTesterAddr: Hex = '0x' + let flipperAddr: Hex = '0x' + let flipperCallerAddr: Hex = '0x' beforeAll(async () => { - const hash = await env.serverWallet.deployContract({ - abi, - bytecode: getByteCode('errorTester', env.evm), - }) - const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) - if (!deployReceipt.contractAddress) throw new Error('Contract address should be set') - errorTesterAddr = deployReceipt.contractAddress + { + const hash = await env.serverWallet.deployContract({ + abi: ErrorTesterAbi, + bytecode: getByteCode('errorTester', env.evm), + }) + const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) + if (!deployReceipt.contractAddress) + throw new Error('Contract address should be set') + errorTesterAddr = deployReceipt.contractAddress + } + + { + const hash = await env.serverWallet.deployContract({ + abi: FlipperAbi, + bytecode: getByteCode('flipper', env.evm), + }) + const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) + if (!deployReceipt.contractAddress) + throw new Error('Contract address should be set') + flipperAddr = deployReceipt.contractAddress + } + + { + const hash = await env.serverWallet.deployContract({ + abi: FlipperCallerAbi, + args: [flipperAddr], + bytecode: getByteCode('flipperCaller', env.evm), + }) + const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) + if (!deployReceipt.contractAddress) + throw new Error('Contract address should be set') + flipperCallerAddr = deployReceipt.contractAddress + } }) test('triggerAssertError', async () => { @@ -31,7 +61,7 @@ for (const env of envs) { try { await env.accountWallet.readContract({ address: errorTesterAddr, - abi, + abi: ErrorTesterAbi, functionName: 'triggerAssertError', }) } catch (err) { @@ -49,7 +79,7 @@ for (const env of envs) { try { await env.accountWallet.readContract({ address: errorTesterAddr, - abi, + abi: ErrorTesterAbi, functionName: 'triggerRevertError', }) } catch (err) { @@ -67,7 +97,7 @@ for (const env of envs) { try { await env.accountWallet.readContract({ address: errorTesterAddr, - abi, + abi: ErrorTesterAbi, functionName: 'triggerDivisionByZero', }) } catch (err) { @@ -87,7 +117,7 @@ for (const env of envs) { try { await env.accountWallet.readContract({ address: errorTesterAddr, - abi, + abi: ErrorTesterAbi, functionName: 'triggerOutOfBoundsError', }) } catch (err) { @@ -107,7 +137,7 @@ for (const env of envs) { try { await env.accountWallet.readContract({ address: errorTesterAddr, - abi, + abi: ErrorTesterAbi, functionName: 'triggerCustomError', }) } catch (err) { @@ -125,7 +155,7 @@ for (const env of envs) { try { await env.accountWallet.simulateContract({ address: errorTesterAddr, - abi, + abi: ErrorTesterAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], @@ -158,7 +188,25 @@ for (const env of envs) { try { await env.accountWallet.estimateContractGas({ address: errorTesterAddr, - abi, + abi: ErrorTesterAbi, + functionName: 'valueMatch', + value: parseEther('10'), + args: [parseEther('10')], + }) + } catch (err) { + const lastJsonRpcError = jsonRpcErrors.pop() + expect(lastJsonRpcError?.code).toBe(-32000) + expect(lastJsonRpcError?.message).toInclude('insufficient funds') + expect(lastJsonRpcError?.data).toBeUndefined() + } + }) + + test('eth_estimate call caller (not enough funds)', async () => { + expect.assertions(3) + try { + await env.accountWallet.estimateContractGas({ + address: errorTesterAddr, + abi: ErrorTesterAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], @@ -176,7 +224,7 @@ for (const env of envs) { try { await env.serverWallet.estimateContractGas({ address: errorTesterAddr, - abi, + abi: ErrorTesterAbi, functionName: 'valueMatch', value: parseEther('11'), args: [parseEther('10')], @@ -208,7 +256,7 @@ for (const env of envs) { await env.accountWallet.estimateContractGas({ address: errorTesterAddr, - abi, + abi: ErrorTesterAbi, functionName: 'setState', args: [true], }) @@ -225,7 +273,7 @@ for (const env of envs) { expect(balance).toBe(0n) const data = encodeFunctionData({ - abi, + abi: ErrorTesterAbi, functionName: 'setState', args: [true], }) @@ -241,5 +289,27 @@ for (const env of envs) { ], }) }) + + test.only('eth_estimate (no gas specified) child_call', async () => { + let balance = await env.serverWallet.getBalance(env.accountWallet.account) + expect(balance).toBe(0n) + + const data = encodeFunctionData({ + abi: FlipperCallerAbi, + functionName: 'callFlip', + }) + + await env.accountWallet.request({ + method: 'eth_estimateGas', + params: [ + { + data, + from: env.accountWallet.account.address, + to: flipperCallerAddr, + gas: `0x${Number(1000000).toString(16)}`, + }, + ], + }) + }) }) } diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 7f2d4e683c31..43b600c33d78 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -218,38 +218,34 @@ async fn deploy_and_call() -> anyhow::Result<()> { Ok(()) } -/// TODO: enable ( https://github.com/paritytech/contract-issues/issues/12 ) -#[ignore] #[tokio::test] async fn revert_call() -> anyhow::Result<()> { let _lock = SHARED_RESOURCES.write(); let client = SharedResources::client().await; - let (bytecode, contract) = get_contract("revert")?; + let (bytecode, contract) = get_contract("ErrorTester")?; let receipt = TransactionBuilder::default() - .input(contract.constructor.clone().unwrap().encode_input(bytecode, &[]).unwrap()) + .input(bytecode) .send_and_wait_for_receipt(&client) .await?; let err = TransactionBuilder::default() .to(receipt.contract_address.unwrap()) - .input(contract.function("doRevert")?.encode_input(&[])?.to_vec()) + .input(contract.function("triggerRequireError")?.encode_input(&[])?.to_vec()) .send(&client) .await .unwrap_err(); let call_err = unwrap_call_err!(err.source().unwrap()); - assert_eq!(call_err.message(), "execution reverted: revert message"); + assert_eq!(call_err.message(), "execution reverted: This is a require error"); assert_eq!(call_err.code(), 3); Ok(()) } -/// TODO: enable ( https://github.com/paritytech/contract-issues/issues/12 ) -#[ignore] #[tokio::test] async fn event_logs() -> anyhow::Result<()> { let _lock = SHARED_RESOURCES.write(); let client = SharedResources::client().await; - let (bytecode, contract) = get_contract("event")?; + let (bytecode, contract) = get_contract("EventExample")?; let receipt = TransactionBuilder::default() .input(bytecode) .send_and_wait_for_receipt(&client) @@ -284,13 +280,11 @@ async fn invalid_transaction() -> anyhow::Result<()> { Ok(()) } -/// TODO: enable ( https://github.com/paritytech/contract-issues/issues/12 ) -#[ignore] #[tokio::test] async fn native_evm_ratio_works() -> anyhow::Result<()> { let _lock = SHARED_RESOURCES.write(); let client = SharedResources::client().await; - let (bytecode, contract) = get_contract("piggyBank")?; + let (bytecode, contract) = get_contract("PiggyBank")?; let contract_address = TransactionBuilder::default() .input(bytecode) .send_and_wait_for_receipt(&client) diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index a612e7760acb..58d4721b4e53 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -29,6 +29,7 @@ use crate::{ ChainExtension, Environment, Ext, RegisteredChainExtension, Result as ExtensionResult, RetVal, ReturnFlags, }, + evm::GenericTransaction, exec::Key, limits, primitives::CodeUploadReturnValue, @@ -38,8 +39,8 @@ use crate::{ wasm::Memory, weights::WeightInfo, AccountId32Mapper, BalanceOf, Code, CodeInfoOf, CollectEvents, Config, ContractInfo, - ContractInfoOf, DebugInfo, DeletionQueueCounter, DepositLimit, Error, HoldReason, Origin, - Pallet, PristineCode, H160, + ContractInfoOf, DebugInfo, DeletionQueueCounter, DepositLimit, Error, EthTransactError, + HoldReason, Origin, Pallet, PristineCode, H160, }; use crate::test_utils::builder::Contract; @@ -4655,3 +4656,100 @@ fn mapped_address_works() { assert_eq!(::Currency::total_balance(&EVE), 1_100); }); } + +#[test] +fn skip_transfer_works() { + let (code_caller, _) = compile_module("call").unwrap(); + let (code, _) = compile_module("set_empty_storage").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ::Currency::set_balance(&ALICE, 1_000_000); + ::Currency::set_balance(&BOB, 0); + + // fails to instantiate when gas is specified. + assert_err!( + Pallet::::bare_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + input: Some(code.clone().into()), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_| 0u32 + ), + EthTransactError::Message(format!( + "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" + )) + ); + + // works when no gas is specified. + assert_ok!(Pallet::::bare_eth_transact( + GenericTransaction { + from: Some(ALICE_ADDR), + input: Some(code.clone().into()), + ..Default::default() + }, + Weight::MAX, + |_| 0u32 + )); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let Contract { addr: caller_addr, .. } = + builder::bare_instantiate(Code::Upload(code_caller)).build_and_unwrap_contract(); + + // fails to call when gas is specified. + assert_err!( + Pallet::::bare_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(addr), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_| 0u32 + ), + EthTransactError::Message(format!( + "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" + )) + ); + + // fails when calling from a contract when gas is specified. + assert_err!( + Pallet::::bare_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(caller_addr), + input: Some((0u32, &addr).encode().into()), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_| 0u32 + ), + EthTransactError::Message(format!("insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)")) + ); + + // works when no gas is specified. + assert_ok!(Pallet::::bare_eth_transact( + GenericTransaction { from: Some(BOB_ADDR), to: Some(addr), ..Default::default() }, + Weight::MAX, + |_| 0u32 + )); + + // works when calling from a contract when no gas is specified. + assert_ok!(Pallet::::bare_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(caller_addr), + input: Some((0u32, &addr).encode().into()), + ..Default::default() + }, + Weight::MAX, + |_| 0u32 + )); + }); +} From 9dcdf8133cbae2184caffd9070d18c1217ac5194 Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Wed, 11 Dec 2024 02:52:29 +0100 Subject: [PATCH 144/340] Migration of polkadot-runtime-common auctions benchmarking to v2 (#6613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Migrated polkadot-runtime-common auctions benchmarking to the new benchmarking syntax v2. This is part of #6202 --------- Co-authored-by: Giuseppe Re Co-authored-by: Bastian Köcher --- polkadot/runtime/common/src/auctions.rs | 120 ++++++++++++++++-------- 1 file changed, 83 insertions(+), 37 deletions(-) diff --git a/polkadot/runtime/common/src/auctions.rs b/polkadot/runtime/common/src/auctions.rs index 78f20d918bab..3ac1ba2dc4e0 100644 --- a/polkadot/runtime/common/src/auctions.rs +++ b/polkadot/runtime/common/src/auctions.rs @@ -339,10 +339,10 @@ impl Auctioneer> for Pallet { let sample_length = T::SampleLength::get().max(One::one()); let sample = after_early_end / sample_length; let sub_sample = after_early_end % sample_length; - return AuctionStatus::EndingPeriod(sample, sub_sample) + return AuctionStatus::EndingPeriod(sample, sub_sample); } else { // This is safe because of the comparison operator above - return AuctionStatus::VrfDelay(after_early_end - ending_period) + return AuctionStatus::VrfDelay(after_early_end - ending_period); } } @@ -559,7 +559,7 @@ impl Pallet { #[allow(deprecated)] Winning::::remove_all(None); AuctionInfo::::kill(); - return Some((res, lease_period_index)) + return Some((res, lease_period_index)); } } } @@ -765,11 +765,11 @@ mod tests { let (current_lease_period, _) = Self::lease_period_index(now).ok_or(LeaseError::NoLeasePeriod)?; if period_begin < current_lease_period { - return Err(LeaseError::AlreadyEnded) + return Err(LeaseError::AlreadyEnded); } for period in period_begin..(period_begin + period_count) { if leases.contains_key(&(para, period)) { - return Err(LeaseError::AlreadyLeased) + return Err(LeaseError::AlreadyLeased); } leases.insert((para, period), LeaseData { leaser: *leaser, amount }); } @@ -1718,7 +1718,7 @@ mod benchmarking { use polkadot_runtime_parachains::paras; use sp_runtime::{traits::Bounded, SaturatedConversion}; - use frame_benchmarking::{account, benchmarks, whitelisted_caller, BenchmarkError}; + use frame_benchmarking::v2::*; fn assert_last_event(generic_event: ::RuntimeEvent) { let events = frame_system::Pallet::::events(); @@ -1772,25 +1772,37 @@ mod benchmarking { } } - benchmarks! { - where_clause { where T: pallet_babe::Config + paras::Config } + #[benchmarks( + where T: pallet_babe::Config + paras::Config, + )] + mod benchmarks { + use super::*; - new_auction { + #[benchmark] + fn new_auction() -> Result<(), BenchmarkError> { let duration = BlockNumberFor::::max_value(); let lease_period_index = LeasePeriodOf::::max_value(); - let origin = - T::InitiateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - }: _(origin, duration, lease_period_index) - verify { - assert_last_event::(Event::::AuctionStarted { - auction_index: AuctionCounter::::get(), - lease_period: LeasePeriodOf::::max_value(), - ending: BlockNumberFor::::max_value(), - }.into()); + let origin = T::InitiateOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, duration, lease_period_index); + + assert_last_event::( + Event::::AuctionStarted { + auction_index: AuctionCounter::::get(), + lease_period: LeasePeriodOf::::max_value(), + ending: BlockNumberFor::::max_value(), + } + .into(), + ); + + Ok(()) } // Worst case scenario a new bid comes in which kicks out an existing bid for the same slot. - bid { + #[benchmark] + fn bid() -> Result<(), BenchmarkError> { // If there is an offset, we need to be on that block to be able to do lease things. let (_, offset) = T::Leaser::lease_period_length(); frame_system::Pallet::::set_block_number(offset + One::one()); @@ -1810,8 +1822,18 @@ mod benchmarking { CurrencyOf::::make_free_balance_be(&owner, BalanceOf::::max_value()); let worst_head_data = T::Registrar::worst_head_data(); let worst_validation_code = T::Registrar::worst_validation_code(); - T::Registrar::register(owner.clone(), para, worst_head_data.clone(), worst_validation_code.clone())?; - T::Registrar::register(owner, new_para, worst_head_data, worst_validation_code.clone())?; + T::Registrar::register( + owner.clone(), + para, + worst_head_data.clone(), + worst_validation_code.clone(), + )?; + T::Registrar::register( + owner, + new_para, + worst_head_data, + worst_validation_code.clone(), + )?; assert_ok!(paras::Pallet::::add_trusted_validation_code( frame_system::Origin::::Root.into(), worst_validation_code, @@ -1839,15 +1861,28 @@ mod benchmarking { CurrencyOf::::make_free_balance_be(&caller, BalanceOf::::max_value()); let bigger_amount = CurrencyOf::::minimum_balance().saturating_mul(10u32.into()); assert_eq!(CurrencyOf::::reserved_balance(&first_bidder), first_amount); - }: _(RawOrigin::Signed(caller.clone()), new_para, auction_index, first_slot, last_slot, bigger_amount) - verify { - // Confirms that we unreserved funds from a previous bidder, which is worst case scenario. + + #[extrinsic_call] + _( + RawOrigin::Signed(caller.clone()), + new_para, + auction_index, + first_slot, + last_slot, + bigger_amount, + ); + + // Confirms that we unreserved funds from a previous bidder, which is worst case + // scenario. assert_eq!(CurrencyOf::::reserved_balance(&caller), bigger_amount); + + Ok(()) } - // Worst case: 10 bidders taking all wining spots, and we need to calculate the winner for auction end. - // Entire winner map should be full and removed at the end of the benchmark. - on_initialize { + // Worst case: 10 bidders taking all wining spots, and we need to calculate the winner for + // auction end. Entire winner map should be full and removed at the end of the benchmark. + #[benchmark] + fn on_initialize() -> Result<(), BenchmarkError> { // If there is an offset, we need to be on that block to be able to do lease things. let (lease_length, offset) = T::Leaser::lease_period_length(); frame_system::Pallet::::set_block_number(offset + One::one()); @@ -1868,7 +1903,7 @@ mod benchmarking { let winning_data = Winning::::get(BlockNumberFor::::from(0u32)).unwrap(); // Make winning map full - for i in 0u32 .. (T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { + for i in 0u32..(T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { Winning::::insert(BlockNumberFor::::from(i), winning_data.clone()); } @@ -1882,20 +1917,29 @@ mod benchmarking { let authorities = pallet_babe::Pallet::::authorities(); // Check for non empty authority set since it otherwise emits a No-OP warning. if !authorities.is_empty() { - pallet_babe::Pallet::::enact_epoch_change(authorities.clone(), authorities, None); + pallet_babe::Pallet::::enact_epoch_change( + authorities.clone(), + authorities, + None, + ); } } - }: { - Auctions::::on_initialize(duration + now + T::EndingPeriod::get()); - } verify { + #[block] + { + Auctions::::on_initialize(duration + now + T::EndingPeriod::get()); + } + let auction_index = AuctionCounter::::get(); assert_last_event::(Event::::AuctionClosed { auction_index }.into()); assert!(Winning::::iter().count().is_zero()); + + Ok(()) } // Worst case: 10 bidders taking all wining spots, and winning data is full. - cancel_auction { + #[benchmark] + fn cancel_auction() -> Result<(), BenchmarkError> { // If there is an offset, we need to be on that block to be able to do lease things. let (lease_length, offset) = T::Leaser::lease_period_length(); frame_system::Pallet::::set_block_number(offset + One::one()); @@ -1903,7 +1947,6 @@ mod benchmarking { // Create a new auction let duration: BlockNumberFor = lease_length / 2u32.into(); let lease_period_index = LeasePeriodOf::::zero(); - let now = frame_system::Pallet::::block_number(); let origin = T::InitiateOrigin::try_successful_origin() .expect("InitiateOrigin has no successful origin required for the benchmark"); Auctions::::new_auction(origin, duration, lease_period_index)?; @@ -1916,13 +1959,16 @@ mod benchmarking { } // Make winning map full - for i in 0u32 .. (T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { + for i in 0u32..(T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { Winning::::insert(BlockNumberFor::::from(i), winning_data.clone()); } assert!(AuctionInfo::::get().is_some()); - }: _(RawOrigin::Root) - verify { + + #[extrinsic_call] + _(RawOrigin::Root); + assert!(AuctionInfo::::get().is_none()); + Ok(()) } impl_benchmark_test_suite!( From 85dd228d9a7d3d736e0dab7c0b084e3fc2c1b003 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:21:05 +0200 Subject: [PATCH 145/340] Make approval-distribution aggression a bit more robust and less spammy (#6696) After finality started lagging on kusama around `2025-11-25 15:55:40` nodes started being overloaded with messages and some restarted with ``` Subsystem approval-distribution-subsystem appears unresponsive when sending a message of type polkadot_node_subsystem_types::messages::ApprovalDistributionMessage. origin=polkadot_service::relay_chain_selection::SelectRelayChainInner, sp_runtime::OpaqueExtrinsic>>, polkadot_overseer::Handle> ``` I think this happened because our aggression in the current form is way too spammy and create problems in situation where we already constructed blocks with a load of candidates to check which what happened around `#25933682` before and after. However aggression, does help in the nightmare scenario where the network is segmented and sparsely connected, so I tend to think we shouldn't completely remove it. The current configuration is: ``` l1_threshold: Some(16), l2_threshold: Some(28), resend_unfinalized_period: Some(8), ``` The way aggression works right now : 1. After L1 is triggered all nodes send all messages they created to all the other nodes and all messages they would have they already send according to the topology. 2. Because of resend_unfinalized_period for each block all messages at step 1) are sent every 8 blocks, so for example let's say we have blocks 1 to 24 unfinalized, then at block 25, all messages for block 1, 9 will be resent, and consequently at block 26, all messages for block 2, 10 will be resent, this becomes worse as more blocks are created if backing backpressure did not kick in yet. In total this logic makes that each node receive 3 * total_number_of messages_per_block 3. L2 aggression is way too spammy, when L2 aggression is enabled all nodes sends all messages of a block on GridXY, that means that all messages are received and sent by node at least 2*sqrt(num_validators), so on kusama would be 66 * NUM_MESSAGES_AT_FIRST_UNFINALIZED_BLOCK, so even with a reasonable number of messages like 10K, which you can have if you escalated because of no shows, you end-up sending and receiving ~660k messages at once, I think that's what makes the approval-distribution to appear unresponsive on some nodes. 4. Duplicate messages are received by the nodes which turn, mark the node as banned, which may create more no-shows. ## Proposed improvements: 1. Make L2 trigger way later 28 blocks, instead of 64, this should literally the last resort, until then we should try to let the approval-voting escalation mechanism to do its things and cover the no-shows. 2. On L1 aggression don't send messages for blocks too far from the first_unfinalized there is no point in sending the messages for block 20, if block 1 is still unfinalized. 3. On L1 aggression, send messages then back-off for 3 * resend_unfinalized_period to give time for everyone to clear up their queues. 4. If aggression is enabled accept duplicate messages from validators and don't punish them by reducting their reputation which, which may create no-shows. --------- Signed-off-by: Alexandru Gheorghe Co-authored-by: Andrei Sandu <54316454+sandreim@users.noreply.github.com> --- .../network/approval-distribution/src/lib.rs | 143 ++++++++++++++---- .../approval-distribution/src/tests.rs | 137 ++++++++++++++++- prdoc/pr_6696.prdoc | 15 ++ 3 files changed, 261 insertions(+), 34 deletions(-) create mode 100644 prdoc/pr_6696.prdoc diff --git a/polkadot/node/network/approval-distribution/src/lib.rs b/polkadot/node/network/approval-distribution/src/lib.rs index d6bbb0dca83c..cefb1d744992 100644 --- a/polkadot/node/network/approval-distribution/src/lib.rs +++ b/polkadot/node/network/approval-distribution/src/lib.rs @@ -316,7 +316,7 @@ impl Default for AggressionConfig { fn default() -> Self { AggressionConfig { l1_threshold: Some(16), - l2_threshold: Some(28), + l2_threshold: Some(64), resend_unfinalized_period: Some(8), } } @@ -512,6 +512,8 @@ struct BlockEntry { vrf_story: RelayVRFStory, /// The block slot. slot: Slot, + /// Backing off from re-sending messages to peers. + last_resent_at_block_number: Option, } impl BlockEntry { @@ -878,6 +880,7 @@ impl State { candidates_metadata: meta.candidates, vrf_story: meta.vrf_story, slot: meta.slot, + last_resent_at_block_number: None, }); self.topologies.inc_session_refs(meta.session); @@ -1317,6 +1320,33 @@ impl State { self.enable_aggression(network_sender, Resend::No, metrics).await; } + // When finality is lagging as a last resort nodes start sending the messages they have + // multiples times. This means it is safe to accept duplicate messages without punishing the + // peer and reduce the reputation and can end up banning the Peer, which in turn will create + // more no-shows. + fn accept_duplicates_from_validators( + blocks_by_number: &BTreeMap>, + topologies: &SessionGridTopologies, + aggression_config: &AggressionConfig, + entry: &BlockEntry, + peer: PeerId, + ) -> bool { + let topology = topologies.get_topology(entry.session); + let min_age = blocks_by_number.iter().next().map(|(num, _)| num); + let max_age = blocks_by_number.iter().rev().next().map(|(num, _)| num); + + // Return if we don't have at least 1 block. + let (min_age, max_age) = match (min_age, max_age) { + (Some(min), Some(max)) => (*min, *max), + _ => return false, + }; + + let age = max_age.saturating_sub(min_age); + + aggression_config.should_trigger_aggression(age) && + topology.map(|topology| topology.is_validator(&peer)).unwrap_or(false) + } + async fn import_and_circulate_assignment( &mut self, approval_voting_sender: &mut A, @@ -1381,20 +1411,29 @@ impl State { if peer_knowledge.contains(&message_subject, message_kind) { // wasn't included before if !peer_knowledge.received.insert(message_subject.clone(), message_kind) { - gum::debug!( - target: LOG_TARGET, - ?peer_id, - ?message_subject, - "Duplicate assignment", - ); - - modify_reputation( - &mut self.reputation, - network_sender, + if !Self::accept_duplicates_from_validators( + &self.blocks_by_number, + &self.topologies, + &self.aggression_config, + entry, peer_id, - COST_DUPLICATE_MESSAGE, - ) - .await; + ) { + gum::debug!( + target: LOG_TARGET, + ?peer_id, + ?message_subject, + "Duplicate assignment", + ); + + modify_reputation( + &mut self.reputation, + network_sender, + peer_id, + COST_DUPLICATE_MESSAGE, + ) + .await; + } + metrics.on_assignment_duplicate(); } else { gum::trace!( @@ -1710,6 +1749,9 @@ impl State { assignments_knowledge_key: &Vec<(MessageSubject, MessageKind)>, approval_knowledge_key: &(MessageSubject, MessageKind), entry: &mut BlockEntry, + blocks_by_number: &BTreeMap>, + topologies: &SessionGridTopologies, + aggression_config: &AggressionConfig, reputation: &mut ReputationAggregator, peer_id: PeerId, metrics: &Metrics, @@ -1738,20 +1780,27 @@ impl State { .received .insert(approval_knowledge_key.0.clone(), approval_knowledge_key.1) { - gum::trace!( - target: LOG_TARGET, - ?peer_id, - ?approval_knowledge_key, - "Duplicate approval", - ); - - modify_reputation( - reputation, - network_sender, + if !Self::accept_duplicates_from_validators( + blocks_by_number, + topologies, + aggression_config, + entry, peer_id, - COST_DUPLICATE_MESSAGE, - ) - .await; + ) { + gum::trace!( + target: LOG_TARGET, + ?peer_id, + ?approval_knowledge_key, + "Duplicate approval", + ); + modify_reputation( + reputation, + network_sender, + peer_id, + COST_DUPLICATE_MESSAGE, + ) + .await; + } metrics.on_approval_duplicate(); } return false @@ -1843,6 +1892,9 @@ impl State { &assignments_knowledge_keys, &approval_knwowledge_key, entry, + &self.blocks_by_number, + &self.topologies, + &self.aggression_config, &mut self.reputation, peer_id, metrics, @@ -2253,18 +2305,43 @@ impl State { &self.topologies, |block_entry| { let block_age = max_age - block_entry.number; + // We want to resend only for blocks of min_age, there is no point in + // resending for blocks newer than that, because we are just going to create load + // and not gain anything. + let diff_from_min_age = block_entry.number - min_age; + + // We want to back-off on resending for blocks that have been resent recently, to + // give time for nodes to process all the extra messages, if we still have not + // finalized we are going to resend again after unfinalized_period * 2 since the + // last resend. + let blocks_since_last_sent = block_entry + .last_resent_at_block_number + .map(|last_resent_at_block_number| max_age - last_resent_at_block_number); + + let can_resend_at_this_age = blocks_since_last_sent + .zip(config.resend_unfinalized_period) + .map(|(blocks_since_last_sent, unfinalized_period)| { + blocks_since_last_sent >= unfinalized_period * 2 + }) + .unwrap_or(true); if resend == Resend::Yes && - config - .resend_unfinalized_period - .as_ref() - .map_or(false, |p| block_age > 0 && block_age % p == 0) - { + config.resend_unfinalized_period.as_ref().map_or(false, |p| { + block_age > 0 && + block_age % p == 0 && diff_from_min_age == 0 && + can_resend_at_this_age + }) { // Retry sending to all peers. for (_, knowledge) in block_entry.known_by.iter_mut() { knowledge.sent = Knowledge::default(); } - + block_entry.last_resent_at_block_number = Some(max_age); + gum::debug!( + target: LOG_TARGET, + block_number = ?block_entry.number, + ?max_age, + "Aggression enabled with resend for block", + ); true } else { false diff --git a/polkadot/node/network/approval-distribution/src/tests.rs b/polkadot/node/network/approval-distribution/src/tests.rs index 063e71f2f528..323b2cb08fec 100644 --- a/polkadot/node/network/approval-distribution/src/tests.rs +++ b/polkadot/node/network/approval-distribution/src/tests.rs @@ -1030,6 +1030,141 @@ fn peer_sending_us_the_same_we_just_sent_them_is_ok() { ); } +#[test] +fn peer_sending_us_duplicates_while_aggression_enabled_is_ok() { + let parent_hash = Hash::repeat_byte(0xFF); + let hash = Hash::repeat_byte(0xAA); + + let peers = make_peers_and_authority_ids(8); + let peer_a = peers.first().unwrap().0; + + let _ = test_harness( + Arc::new(MockAssignmentCriteria { tranche: Ok(0) }), + Arc::new(SystemClock {}), + state_without_reputation_delay(), + |mut virtual_overseer| async move { + let overseer = &mut virtual_overseer; + let peer = &peer_a; + setup_peer_with_view(overseer, peer, view![], ValidationVersion::V3).await; + + let peers_with_optional_peer_id = peers + .iter() + .map(|(peer_id, authority)| (Some(*peer_id), authority.clone())) + .collect_vec(); + // Setup a topology where peer_a is neighbor to current node. + setup_gossip_topology( + overseer, + make_gossip_topology(1, &peers_with_optional_peer_id, &[0], &[2], 1), + ) + .await; + + // new block `hash` with 1 candidates + let meta = BlockApprovalMeta { + hash, + parent_hash, + number: 1, + candidates: vec![Default::default(); 1], + slot: 1.into(), + session: 1, + vrf_story: RelayVRFStory(Default::default()), + }; + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); + overseer_send(overseer, msg).await; + + // import an assignment related to `hash` locally + let validator_index = ValidatorIndex(0); + let candidate_indices: CandidateBitfield = + vec![0 as CandidateIndex].try_into().unwrap(); + let candidate_bitfields = vec![CoreIndex(0)].try_into().unwrap(); + let cert = fake_assignment_cert_v2(hash, validator_index, candidate_bitfields); + overseer_send( + overseer, + ApprovalDistributionMessage::DistributeAssignment( + cert.clone().into(), + candidate_indices.clone(), + ), + ) + .await; + + // update peer view to include the hash + overseer_send( + overseer, + ApprovalDistributionMessage::NetworkBridgeUpdate( + NetworkBridgeEvent::PeerViewChange(*peer, view![hash]), + ), + ) + .await; + + // we should send them the assignment + assert_matches!( + overseer_recv(overseer).await, + AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage( + peers, + Versioned::V3(protocol_v3::ValidationProtocol::ApprovalDistribution( + protocol_v3::ApprovalDistributionMessage::Assignments(assignments) + )) + )) => { + assert_eq!(peers.len(), 1); + assert_eq!(assignments.len(), 1); + } + ); + + // but if someone else is sending it the same assignment + // the peer could send us it as well + let assignments = vec![(cert, candidate_indices)]; + let msg = protocol_v3::ApprovalDistributionMessage::Assignments(assignments); + send_message_from_peer_v3(overseer, peer, msg.clone()).await; + + assert!( + overseer.recv().timeout(TIMEOUT).await.is_none(), + "we should not punish the peer" + ); + + // send the assignments again + send_message_from_peer_v3(overseer, peer, msg.clone()).await; + + // now we should + expect_reputation_change(overseer, peer, COST_DUPLICATE_MESSAGE).await; + + // Peers will be continously punished for sending duplicates until approval-distribution + // aggression kicks, at which point they aren't anymore. + let mut parent_hash = hash; + for level in 0..16 { + // As long as the lag is bellow l1 aggression, punish peers for duplicates. + send_message_from_peer_v3(overseer, peer, msg.clone()).await; + expect_reputation_change(overseer, peer, COST_DUPLICATE_MESSAGE).await; + + let number = 1 + level + 1; // first block had number 1 + let hash = BlakeTwo256::hash_of(&(parent_hash, number)); + let meta = BlockApprovalMeta { + hash, + parent_hash, + number, + candidates: vec![], + slot: (level as u64).into(), + session: 1, + vrf_story: RelayVRFStory(Default::default()), + }; + + let msg = ApprovalDistributionMessage::ApprovalCheckingLagUpdate(level + 1); + overseer_send(overseer, msg).await; + + let msg = ApprovalDistributionMessage::NewBlocks(vec![meta]); + overseer_send(overseer, msg).await; + + parent_hash = hash; + } + + // send the assignments again, we should not punish the peer because aggression is + // enabled. + send_message_from_peer_v3(overseer, peer, msg).await; + + assert!(overseer.recv().timeout(TIMEOUT).await.is_none(), "no message should be sent"); + virtual_overseer + }, + ); +} + #[test] fn import_approval_happy_path_v1_v2_peers() { let peers = make_peers_and_authority_ids(15); @@ -3892,7 +4027,7 @@ fn resends_messages_periodically() { // Add blocks until resend is done. { let mut parent_hash = hash; - for level in 0..2 { + for level in 0..4 { number = number + 1; let hash = BlakeTwo256::hash_of(&(parent_hash, number)); let meta = BlockApprovalMeta { diff --git a/prdoc/pr_6696.prdoc b/prdoc/pr_6696.prdoc new file mode 100644 index 000000000000..c5c73f831886 --- /dev/null +++ b/prdoc/pr_6696.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Make approval-distribution aggression a bit more robust and less spammy + +doc: + - audience: Node Dev + description: | + The problem with the current implementation of approval-distribution aggression is that is too spammy, + and can overload the nodes, so make it less spammy by moving back the moment we trigger L2 aggression + and make resend enable only for the latest unfinalized block. + +crates: + - name: polkadot-approval-distribution + bump: minor From da2dd9b7737cb7c0dc9dc3dc74b384c719ea3306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 11 Dec 2024 12:34:12 +0100 Subject: [PATCH 146/340] snowbridge: Update alloy-core (#6808) I am planning to use `alloy_core` to implement precompile support in `pallet_revive`. I noticed that it is already used by snowbridge. In order to unify the dependencies I did the following: 1. Switch to the `alloy-core` umbrella crate so that we have less individual dependencies to update. 2. Bump the latest version and fixup the resulting compile errors. --- Cargo.lock | 247 ++++++++++++++++-- Cargo.toml | 3 +- .../pallets/inbound-queue/Cargo.toml | 6 +- .../pallets/inbound-queue/src/envelope.rs | 7 +- 4 files changed, 227 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 989430fdfe29..9b023a38cb2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,48 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "alloy-core" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c618bd382f0bc2ac26a7e4bfae01c9b015ca8f21b37ca40059ae35a7e62b3dc6" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-primitives 0.8.15", + "alloy-rlp", + "alloy-sol-types 0.8.15", +] + +[[package]] +name = "alloy-dyn-abi" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41056bde53ae10ffbbf11618efbe1e0290859e5eab0fe9ef82ebdb62f12a866f" +dependencies = [ + "alloy-json-abi", + "alloy-primitives 0.8.15", + "alloy-sol-type-parser", + "alloy-sol-types 0.8.15", + "const-hex", + "itoa", + "serde", + "serde_json", + "winnow 0.6.18", +] + +[[package]] +name = "alloy-json-abi" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c357da577dfb56998d01f574d81ad7a1958d248740a7981b205d69d65a7da404" +dependencies = [ + "alloy-primitives 0.8.15", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + [[package]] name = "alloy-primitives" version = "0.4.2" @@ -145,6 +187,34 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "alloy-primitives" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6259a506ab13e1d658796c31e6e39d2e2ee89243bcc505ddc613b35732e0a430" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more 1.0.0", + "foldhash", + "hashbrown 0.15.2", + "hex-literal", + "indexmap 2.7.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand", + "ruint", + "rustc-hash 2.0.0", + "serde", + "sha3 0.10.8", + "tiny-keccak", +] + [[package]] name = "alloy-rlp" version = "0.3.3" @@ -169,18 +239,88 @@ dependencies = [ "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.87", - "syn-solidity", + "syn-solidity 0.4.2", "tiny-keccak", ] +[[package]] +name = "alloy-sol-macro" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9d64f851d95619233f74b310f12bcf16e0cbc27ee3762b6115c14a84809280a" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf7ed1574b699f48bf17caab4e6e54c6d12bc3c006ab33d58b1e227c1c3559f" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck 0.5.0", + "indexmap 2.7.0", + "proc-macro-error2", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", + "syn-solidity 0.8.15", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c02997ccef5f34f9c099277d4145f183b422938ed5322dc57a089fe9b9ad9ee" +dependencies = [ + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", + "syn-solidity 0.8.15", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce13ff37285b0870d0a0746992a4ae48efaf34b766ae4c2640fa15e5305f8e73" +dependencies = [ + "serde", + "winnow 0.6.18", +] + [[package]] name = "alloy-sol-types" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98d7107bed88e8f09f0ddcc3335622d87bfb6821f3e0c7473329fb1cfad5e015" dependencies = [ - "alloy-primitives", - "alloy-sol-macro", + "alloy-primitives 0.4.2", + "alloy-sol-macro 0.4.2", + "const-hex", + "serde", +] + +[[package]] +name = "alloy-sol-types" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1174cafd6c6d810711b4e00383037bdb458efc4fe3dbafafa16567e0320c54d8" +dependencies = [ + "alloy-json-abi", + "alloy-primitives 0.8.15", + "alloy-sol-macro 0.8.15", "const-hex", "serde", ] @@ -3001,6 +3141,9 @@ name = "bytes" version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +dependencies = [ + "serde", +] [[package]] name = "bzip2-sys" @@ -3759,9 +3902,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.10.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5104de16b218eddf8e34ffe2f86f74bfa4e61e95a1b89732fccf6325efd0557" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -6688,7 +6831,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb42427514b063d97ce21d5199f36c0c307d981434a6be32582bc79fe5bd2303" dependencies = [ "expander", - "indexmap 2.2.3", + "indexmap 2.7.0", "proc-macro-crate 3.1.0", "proc-macro2 1.0.86", "quote 1.0.37", @@ -6879,6 +7022,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -8137,7 +8286,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.9", - "indexmap 2.2.3", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -8156,7 +8305,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.2.3", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -8227,6 +8376,16 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", + "serde", +] + [[package]] name = "hashlink" version = "0.8.4" @@ -8277,6 +8436,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-conservative" @@ -8854,12 +9016,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.2", + "serde", ] [[package]] @@ -9359,6 +9522,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "keccak-hash" version = "0.11.0" @@ -10245,7 +10418,7 @@ dependencies = [ "futures-timer", "hex-literal", "hickory-resolver", - "indexmap 2.2.3", + "indexmap 2.7.0", "libc", "mockall 0.13.0", "multiaddr 0.17.1", @@ -11594,7 +11767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7b1d40dd8f367db3c65bec8d3dd47d4a604ee8874480738f93191bddab4e0e0" dependencies = [ "expander", - "indexmap 2.2.3", + "indexmap 2.7.0", "itertools 0.11.0", "petgraph", "proc-macro-crate 3.1.0", @@ -16797,7 +16970,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.2.3", + "indexmap 2.7.0", ] [[package]] @@ -17126,7 +17299,7 @@ dependencies = [ "fatality", "futures", "futures-timer", - "indexmap 2.2.3", + "indexmap 2.7.0", "parity-scale-codec", "polkadot-erasure-coding", "polkadot-node-network-protocol", @@ -19373,7 +19546,7 @@ dependencies = [ "fatality", "futures", "futures-timer", - "indexmap 2.2.3", + "indexmap 2.7.0", "parity-scale-codec", "polkadot-node-network-protocol", "polkadot-node-primitives", @@ -20742,6 +20915,7 @@ dependencies = [ "libc", "rand_chacha", "rand_core 0.6.4", + "serde", ] [[package]] @@ -23581,7 +23755,7 @@ dependencies = [ "criterion", "futures", "futures-timer", - "indexmap 2.2.3", + "indexmap 2.7.0", "itertools 0.11.0", "linked-hash-map", "log", @@ -24155,7 +24329,7 @@ version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.7.0", "itoa", "memchr", "ryu", @@ -24189,7 +24363,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.7.0", "itoa", "ryu", "serde", @@ -24276,6 +24450,16 @@ dependencies = [ "keccak", ] +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -24968,8 +25152,7 @@ dependencies = [ name = "snowbridge-pallet-inbound-queue" version = "0.2.0" dependencies = [ - "alloy-primitives", - "alloy-sol-types", + "alloy-core", "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-system 28.0.0", @@ -24999,8 +25182,8 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e6a9d00e60e3744e6b6f0c21fea6694b9c6401ac40e41340a96e561dcf1935" dependencies = [ - "alloy-primitives", - "alloy-sol-types", + "alloy-primitives 0.4.2", + "alloy-sol-types 0.4.2", "frame-benchmarking 38.0.0", "frame-support 38.0.0", "frame-system 38.0.0", @@ -29015,6 +29198,18 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "syn-solidity" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219389c1ebe89f8333df8bdfb871f6631c552ff399c23cac02480b6088aad8f0" +dependencies = [ + "paste", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", +] + [[package]] name = "sync_wrapper" version = "1.0.1" @@ -29672,7 +29867,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -29685,7 +29880,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.7.0", "toml_datetime", "winnow 0.5.15", ] @@ -29696,7 +29891,7 @@ version = "0.22.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", diff --git a/Cargo.toml b/Cargo.toml index 383fc46c4e76..e76af28ecc31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -594,8 +594,7 @@ zero-prefixed-literal = { level = "allow", priority = 2 } # 00_1000_0 Inflector = { version = "0.11.4" } aes-gcm = { version = "0.10" } ahash = { version = "0.8.2" } -alloy-primitives = { version = "0.4.2", default-features = false } -alloy-sol-types = { version = "0.4.2", default-features = false } +alloy-core = { version = "0.8.15", default-features = false } always-assert = { version = "0.1" } anyhow = { version = "1.0.81", default-features = false } approx = { version = "0.5.1" } diff --git a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml index 1b08bb39b434..3ab633bfcd79 100644 --- a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml @@ -20,8 +20,7 @@ codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } hex-literal = { optional = true, workspace = true, default-features = true } log = { workspace = true } -alloy-primitives = { features = ["rlp"], workspace = true } -alloy-sol-types = { workspace = true } +alloy-core = { workspace = true, features = ["sol-types"] } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } @@ -49,8 +48,7 @@ hex-literal = { workspace = true, default-features = true } [features] default = ["std"] std = [ - "alloy-primitives/std", - "alloy-sol-types/std", + "alloy-core/std", "codec/std", "frame-benchmarking/std", "frame-support/std", diff --git a/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs index 31a8992442d8..d213c8aad648 100644 --- a/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs +++ b/bridges/snowbridge/pallets/inbound-queue/src/envelope.rs @@ -5,8 +5,7 @@ use snowbridge_core::{inbound::Log, ChannelId}; use sp_core::{RuntimeDebug, H160, H256}; use sp_std::prelude::*; -use alloy_primitives::B256; -use alloy_sol_types::{sol, SolEvent}; +use alloy_core::{primitives::B256, sol, sol_types::SolEvent}; sol! { event OutboundMessageAccepted(bytes32 indexed channel_id, uint64 nonce, bytes32 indexed message_id, bytes payload); @@ -36,7 +35,7 @@ impl TryFrom<&Log> for Envelope { fn try_from(log: &Log) -> Result { let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); - let event = OutboundMessageAccepted::decode_log(topics, &log.data, true) + let event = OutboundMessageAccepted::decode_raw_log(topics, &log.data, true) .map_err(|_| EnvelopeDecodeError)?; Ok(Self { @@ -44,7 +43,7 @@ impl TryFrom<&Log> for Envelope { channel_id: ChannelId::from(event.channel_id.as_ref()), nonce: event.nonce, message_id: H256::from(event.message_id.as_ref()), - payload: event.payload, + payload: event.payload.into(), }) } } From 48c6574b1a32893e6a2113622d009cefda0a5f21 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Wed, 11 Dec 2024 11:39:41 -0300 Subject: [PATCH 147/340] Add aliasers to westend chains (#6814) `InitiateTransfer`, the new instruction introduced in XCMv5, allows preserving the origin after a cross-chain transfer via the usage of the `AliasOrigin` instruction. The receiving chain needs to be configured to allow such this instruction to have its intended effect and not just throw an error. In this PR, I add the alias rules specified in the [RFC for origin preservation](https://github.com/polkadot-fellows/RFCs/blob/main/text/0122-alias-origin-on-asset-transfers.md) to westend chains so we can test these scenarios in the testnet. The new scenarios include: - Sending a cross-chain transfer from one system chain to another and doing a Transact on the same message (1 hop) - Sending a reserve asset transfer from one chain to another going through asset hub and doing Transact on the same message (2 hops) The updated chains are: - Relay: added `AliasChildLocation` - Collectives: added `AliasChildLocation` and `AliasOriginRootUsingFilter` - People: added `AliasChildLocation` and `AliasOriginRootUsingFilter` - Coretime: added `AliasChildLocation` and `AliasOriginRootUsingFilter` AssetHub already has `AliasChildLocation` and doesn't need the other config item. BridgeHub is not intended to be used by end users so I didn't add any config item. Only added `AliasChildOrigin` to the relay since we intend for it to be used less. --------- Co-authored-by: GitHub Action Co-authored-by: command-bot <> --- .../collectives-westend/src/xcm_config.rs | 9 +- .../coretime/coretime-westend/src/lib.rs | 4 +- .../coretime-westend/src/weights/xcm/mod.rs | 3 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 169 ++++++++-------- .../coretime-westend/src/xcm_config.rs | 10 +- .../runtimes/people/people-westend/src/lib.rs | 4 +- .../people-westend/src/weights/xcm/mod.rs | 3 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 185 ++++++++++-------- .../people/people-westend/src/xcm_config.rs | 10 +- polkadot/runtime/westend/src/lib.rs | 5 +- .../runtime/westend/src/weights/xcm/mod.rs | 3 +- .../xcm/pallet_xcm_benchmarks_generic.rs | 153 ++++++++------- polkadot/runtime/westend/src/xcm_config.rs | 22 ++- .../src/generic/benchmarking.rs | 2 +- prdoc/pr_6814.prdoc | 32 +++ 15 files changed, 353 insertions(+), 261 deletions(-) create mode 100644 prdoc/pr_6814.prdoc diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs index 56ef2e8ba02f..9eb9b85a3918 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -35,7 +35,8 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice; use westend_runtime_constants::xcm as xcm_constants; use xcm::latest::{prelude::*, WESTEND_GENESIS_HASH}; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AccountId32Aliases, AliasChildLocation, AliasOriginRootUsingFilter, + AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, @@ -191,6 +192,10 @@ pub type WaivedLocations = ( /// - DOT with the parent Relay Chain and sibling parachains. pub type TrustedTeleporters = ConcreteAssetFromSystem; +/// We allow locations to alias into their own child locations, as well as +/// AssetHub to alias into anything. +pub type Aliasers = (AliasChildLocation, AliasOriginRootUsingFilter); + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -227,7 +232,7 @@ impl xcm_executor::Config for XcmConfig { type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; - type Aliasers = Nothing; + type Aliasers = Aliasers; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 39ea39f25a8b..67f7ad7afc6b 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -1126,7 +1126,9 @@ impl_runtime_apis! { } fn alias_origin() -> Result<(Location, Location), BenchmarkError> { - Err(BenchmarkError::Skip) + let origin = Location::new(1, [Parachain(1000)]); + let target = Location::new(1, [Parachain(1000), AccountId32 { id: [128u8; 32], network: None }]); + Ok((origin, target)) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs index 29466b3718c1..2f7529481543 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/mod.rs @@ -261,8 +261,7 @@ impl XcmWeightInfo for CoretimeWestendXcmWeight { XcmGeneric::::clear_topic() } fn alias_origin(_: &Location) -> Weight { - // XCM Executor does not currently support alias origin operations - Weight::MAX + XcmGeneric::::alias_origin() } fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index 6c6d3cf8c525..2d10ac16ea26 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,26 +17,28 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-svzsllib-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `9340d096ec0f`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("coretime-westend-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=coretime-westend-dev +// --pallet=pallet_xcm_benchmarks::generic +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm_benchmarks::generic -// --chain=coretime-westend-dev -// --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/xcm/ +// --template=cumulus/templates/xcm-bench-template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,8 +66,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 29_463_000 picoseconds. - Weight::from_parts(30_178_000, 3571) + // Minimum execution time: 30_717_000 picoseconds. + Weight::from_parts(31_651_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -73,15 +75,26 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 568_000 picoseconds. - Weight::from_parts(608_000, 0) + // Minimum execution time: 618_000 picoseconds. + Weight::from_parts(659_000, 0) } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) pub fn pay_fees() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 3_504_000 picoseconds. + Weight::from_parts(3_757_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_530_000 picoseconds. - Weight::from_parts(1_585_000, 0) + // Minimum execution time: 643_000 picoseconds. + Weight::from_parts(702_000, 0) } // Storage: `PolkadotXcm::Queries` (r:1 w:0) // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -89,58 +102,65 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `3497` - // Minimum execution time: 7_400_000 picoseconds. - Weight::from_parts(7_572_000, 3497) + // Minimum execution time: 7_799_000 picoseconds. + Weight::from_parts(8_037_000, 3497) .saturating_add(T::DbWeight::get().reads(1)) } pub fn transact() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_951_000 picoseconds. - Weight::from_parts(7_173_000, 0) + // Minimum execution time: 6_910_000 picoseconds. + Weight::from_parts(7_086_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_245_000 picoseconds. - Weight::from_parts(1_342_000, 0) + // Minimum execution time: 1_257_000 picoseconds. + Weight::from_parts(1_384_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 613_000 picoseconds. - Weight::from_parts(657_000, 0) + // Minimum execution time: 634_000 picoseconds. + Weight::from_parts(687_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 613_000 picoseconds. - Weight::from_parts(656_000, 0) + // Minimum execution time: 604_000 picoseconds. + Weight::from_parts(672_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 570_000 picoseconds. - Weight::from_parts(608_000, 0) + // Minimum execution time: 593_000 picoseconds. + Weight::from_parts(643_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 557_000 picoseconds. - Weight::from_parts(607_000, 0) + // Minimum execution time: 630_000 picoseconds. + Weight::from_parts(694_000, 0) + } + pub fn execute_with_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 706_000 picoseconds. + Weight::from_parts(764_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 557_000 picoseconds. - Weight::from_parts(578_000, 0) + // Minimum execution time: 606_000 picoseconds. + Weight::from_parts(705_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -158,8 +178,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 26_179_000 picoseconds. - Weight::from_parts(27_089_000, 3571) + // Minimum execution time: 27_188_000 picoseconds. + Weight::from_parts(27_847_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -169,8 +189,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 10_724_000 picoseconds. - Weight::from_parts(10_896_000, 3555) + // Minimum execution time: 11_170_000 picoseconds. + Weight::from_parts(11_416_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -178,8 +198,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 567_000 picoseconds. - Weight::from_parts(623_000, 0) + // Minimum execution time: 590_000 picoseconds. + Weight::from_parts(653_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -197,8 +217,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 24_367_000 picoseconds. - Weight::from_parts(25_072_000, 3539) + // Minimum execution time: 25_196_000 picoseconds. + Weight::from_parts(25_641_000, 3539) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -208,44 +228,44 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_554_000 picoseconds. - Weight::from_parts(2_757_000, 0) + // Minimum execution time: 2_686_000 picoseconds. + Weight::from_parts(2_827_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } pub fn burn_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 922_000 picoseconds. - Weight::from_parts(992_000, 0) + // Minimum execution time: 989_000 picoseconds. + Weight::from_parts(1_051_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 688_000 picoseconds. - Weight::from_parts(723_000, 0) + // Minimum execution time: 713_000 picoseconds. + Weight::from_parts(766_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 607_000 picoseconds. - Weight::from_parts(647_000, 0) + // Minimum execution time: 626_000 picoseconds. + Weight::from_parts(657_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 591_000 picoseconds. - Weight::from_parts(620_000, 0) + // Minimum execution time: 595_000 picoseconds. + Weight::from_parts(639_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 735_000 picoseconds. - Weight::from_parts(802_000, 0) + // Minimum execution time: 755_000 picoseconds. + Weight::from_parts(820_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -263,8 +283,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 29_923_000 picoseconds. - Weight::from_parts(30_770_000, 3571) + // Minimum execution time: 31_409_000 picoseconds. + Weight::from_parts(32_098_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -272,8 +292,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_884_000 picoseconds. - Weight::from_parts(3_088_000, 0) + // Minimum execution time: 3_258_000 picoseconds. + Weight::from_parts(3_448_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -291,8 +311,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 26_632_000 picoseconds. - Weight::from_parts(27_228_000, 3571) + // Minimum execution time: 27_200_000 picoseconds. + Weight::from_parts(28_299_000, 3571) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -300,49 +320,42 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 599_000 picoseconds. - Weight::from_parts(655_000, 0) + // Minimum execution time: 659_000 picoseconds. + Weight::from_parts(699_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 587_000 picoseconds. - Weight::from_parts(628_000, 0) + // Minimum execution time: 595_000 picoseconds. + Weight::from_parts(647_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 572_000 picoseconds. - Weight::from_parts(631_000, 0) + // Minimum execution time: 583_000 picoseconds. + Weight::from_parts(617_000, 0) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 570_000 picoseconds. - Weight::from_parts(615_000, 0) + // Minimum execution time: 595_000 picoseconds. + Weight::from_parts(633_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 624_000 picoseconds. - Weight::from_parts(659_000, 0) - } - pub fn asset_claimer() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 707_000 picoseconds. - Weight::from_parts(749_000, 0) + // Minimum execution time: 610_000 picoseconds. + Weight::from_parts(670_000, 0) } - pub fn execute_with_origin() -> Weight { + pub fn alias_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 713_000 picoseconds. - Weight::from_parts(776_000, 0) + // Minimum execution time: 630_000 picoseconds. + Weight::from_parts(700_000, 0) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs index 9f38975efae6..8a4879a1506e 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/xcm_config.rs @@ -39,7 +39,8 @@ use polkadot_runtime_common::xcm_sender::ExponentialPrice; use sp_runtime::traits::AccountIdConversion; use xcm::latest::{prelude::*, WESTEND_GENESIS_HASH}; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AccountId32Aliases, AliasChildLocation, AliasOriginRootUsingFilter, + AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, @@ -54,6 +55,7 @@ use xcm_executor::XcmExecutor; parameter_types! { pub const RootLocation: Location = Location::here(); pub const TokenRelayLocation: Location = Location::parent(); + pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]); pub const RelayNetwork: Option = Some(NetworkId::ByGenesis(WESTEND_GENESIS_HASH)); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorLocation = @@ -191,6 +193,10 @@ pub type WaivedLocations = ( Equals, ); +/// We allow locations to alias into their own child locations, as well as +/// AssetHub to alias into anything. +pub type Aliasers = (AliasChildLocation, AliasOriginRootUsingFilter); + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -232,7 +238,7 @@ impl xcm_executor::Config for XcmConfig { type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; - type Aliasers = Nothing; + type Aliasers = Aliasers; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 1b9a3b60a2c4..3265062a0441 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -1046,7 +1046,9 @@ impl_runtime_apis! { } fn alias_origin() -> Result<(Location, Location), BenchmarkError> { - Err(BenchmarkError::Skip) + let origin = Location::new(1, [Parachain(1000)]); + let target = Location::new(1, [Parachain(1000), AccountId32 { id: [128u8; 32], network: None }]); + Ok((origin, target)) } } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs index 915a499cb77c..466da1eadd55 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/mod.rs @@ -249,8 +249,7 @@ impl XcmWeightInfo for PeopleWestendXcmWeight { XcmGeneric::::clear_topic() } fn alias_origin(_: &Location) -> Weight { - // XCM Executor does not currently support alias origin operations - Weight::MAX + XcmGeneric::::alias_origin() } fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index ad2cde22a075..3fa51a816b69 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,26 +17,28 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-svzsllib-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `9340d096ec0f`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("people-westend-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=people-westend-dev +// --pallet=pallet_xcm_benchmarks::generic +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights/xcm // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm_benchmarks::generic -// --chain=people-westend-dev -// --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights/xcm/ +// --template=cumulus/templates/xcm-bench-template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,10 +64,10 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_holding() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 29_015_000 picoseconds. - Weight::from_parts(30_359_000, 3535) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 31_309_000 picoseconds. + Weight::from_parts(31_924_000, 3572) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -73,15 +75,26 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 572_000 picoseconds. - Weight::from_parts(637_000, 0) + // Minimum execution time: 635_000 picoseconds. + Weight::from_parts(677_000, 0) } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) pub fn pay_fees() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 3_457_000 picoseconds. + Weight::from_parts(3_656_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_550_000 picoseconds. - Weight::from_parts(1_604_000, 0) + // Minimum execution time: 644_000 picoseconds. + Weight::from_parts(695_000, 0) } // Storage: `PolkadotXcm::Queries` (r:1 w:0) // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -89,58 +102,65 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `3497` - // Minimum execution time: 7_354_000 picoseconds. - Weight::from_parts(7_808_000, 3497) + // Minimum execution time: 7_701_000 picoseconds. + Weight::from_parts(8_120_000, 3497) .saturating_add(T::DbWeight::get().reads(1)) } pub fn transact() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_716_000 picoseconds. - Weight::from_parts(7_067_000, 0) + // Minimum execution time: 6_945_000 picoseconds. + Weight::from_parts(7_187_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_280_000 picoseconds. - Weight::from_parts(1_355_000, 0) + // Minimum execution time: 1_352_000 picoseconds. + Weight::from_parts(1_428_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 587_000 picoseconds. - Weight::from_parts(645_000, 0) + // Minimum execution time: 603_000 picoseconds. + Weight::from_parts(648_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 629_000 picoseconds. - Weight::from_parts(662_000, 0) + // Minimum execution time: 621_000 picoseconds. + Weight::from_parts(661_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 590_000 picoseconds. - Weight::from_parts(639_000, 0) + // Minimum execution time: 591_000 picoseconds. + Weight::from_parts(655_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 651_000 picoseconds. - Weight::from_parts(688_000, 0) + // Minimum execution time: 666_000 picoseconds. + Weight::from_parts(736_000, 0) + } + pub fn execute_with_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 694_000 picoseconds. + Weight::from_parts(759_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 601_000 picoseconds. - Weight::from_parts(630_000, 0) + // Minimum execution time: 632_000 picoseconds. + Weight::from_parts(664_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -156,10 +176,10 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_error() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 25_650_000 picoseconds. - Weight::from_parts(26_440_000, 3535) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 26_932_000 picoseconds. + Weight::from_parts(27_882_000, 3572) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -169,8 +189,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 10_492_000 picoseconds. - Weight::from_parts(10_875_000, 3555) + // Minimum execution time: 11_316_000 picoseconds. + Weight::from_parts(11_608_000, 3555) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -178,8 +198,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 597_000 picoseconds. - Weight::from_parts(647_000, 0) + // Minimum execution time: 564_000 picoseconds. + Weight::from_parts(614_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -197,8 +217,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 23_732_000 picoseconds. - Weight::from_parts(24_290_000, 3503) + // Minimum execution time: 24_373_000 picoseconds. + Weight::from_parts(25_068_000, 3503) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -208,44 +228,44 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_446_000 picoseconds. - Weight::from_parts(2_613_000, 0) + // Minimum execution time: 2_582_000 picoseconds. + Weight::from_parts(2_714_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } pub fn burn_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 960_000 picoseconds. - Weight::from_parts(1_045_000, 0) + // Minimum execution time: 952_000 picoseconds. + Weight::from_parts(1_059_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 703_000 picoseconds. - Weight::from_parts(739_000, 0) + // Minimum execution time: 684_000 picoseconds. + Weight::from_parts(734_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 616_000 picoseconds. - Weight::from_parts(651_000, 0) + // Minimum execution time: 600_000 picoseconds. + Weight::from_parts(650_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 621_000 picoseconds. - Weight::from_parts(660_000, 0) + // Minimum execution time: 599_000 picoseconds. + Weight::from_parts(628_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 794_000 picoseconds. - Weight::from_parts(831_000, 0) + // Minimum execution time: 769_000 picoseconds. + Weight::from_parts(816_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -261,10 +281,10 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn query_pallet() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 29_527_000 picoseconds. - Weight::from_parts(30_614_000, 3535) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 31_815_000 picoseconds. + Weight::from_parts(32_738_000, 3572) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -272,8 +292,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_189_000 picoseconds. - Weight::from_parts(3_296_000, 0) + // Minimum execution time: 3_462_000 picoseconds. + Weight::from_parts(3_563_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -289,10 +309,10 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_transact_status() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 25_965_000 picoseconds. - Weight::from_parts(26_468_000, 3535) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 27_752_000 picoseconds. + Weight::from_parts(28_455_000, 3572) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -300,49 +320,42 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 618_000 picoseconds. - Weight::from_parts(659_000, 0) + // Minimum execution time: 605_000 picoseconds. + Weight::from_parts(687_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 593_000 picoseconds. - Weight::from_parts(618_000, 0) + // Minimum execution time: 610_000 picoseconds. + Weight::from_parts(646_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 603_000 picoseconds. - Weight::from_parts(634_000, 0) + // Minimum execution time: 579_000 picoseconds. + Weight::from_parts(636_000, 0) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 568_000 picoseconds. - Weight::from_parts(629_000, 0) + // Minimum execution time: 583_000 picoseconds. + Weight::from_parts(626_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 598_000 picoseconds. - Weight::from_parts(655_000, 0) - } - pub fn asset_claimer() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 707_000 picoseconds. - Weight::from_parts(749_000, 0) + // Minimum execution time: 616_000 picoseconds. + Weight::from_parts(679_000, 0) } - pub fn execute_with_origin() -> Weight { + pub fn alias_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 713_000 picoseconds. - Weight::from_parts(776_000, 0) + // Minimum execution time: 626_000 picoseconds. + Weight::from_parts(687_000, 0) } } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs index 25256495ef91..7eaa43c05b20 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/xcm_config.rs @@ -36,7 +36,8 @@ use polkadot_parachain_primitives::primitives::Sibling; use sp_runtime::traits::AccountIdConversion; use xcm::latest::{prelude::*, WESTEND_GENESIS_HASH}; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, + AccountId32Aliases, AliasChildLocation, AliasOriginRootUsingFilter, + AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, DescribeTerminus, EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, @@ -51,6 +52,7 @@ use xcm_executor::XcmExecutor; parameter_types! { pub const RootLocation: Location = Location::here(); pub const RelayLocation: Location = Location::parent(); + pub AssetHubLocation: Location = Location::new(1, [Parachain(1000)]); pub const RelayNetwork: Option = Some(NetworkId::ByGenesis(WESTEND_GENESIS_HASH)); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorLocation = @@ -195,6 +197,10 @@ pub type WaivedLocations = ( LocalPlurality, ); +/// We allow locations to alias into their own child locations, as well as +/// AssetHub to alias into anything. +pub type Aliasers = (AliasChildLocation, AliasOriginRootUsingFilter); + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -236,7 +242,7 @@ impl xcm_executor::Config for XcmConfig { type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; - type Aliasers = Nothing; + type Aliasers = Aliasers; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index f25ed33012a2..c540b3773286 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -2801,8 +2801,9 @@ sp_api::impl_runtime_apis! { } fn alias_origin() -> Result<(Location, Location), BenchmarkError> { - // The XCM executor of Westend doesn't have a configured `Aliasers` - Err(BenchmarkError::Skip) + let origin = Location::new(0, [Parachain(1000)]); + let target = Location::new(0, [Parachain(1000), AccountId32 { id: [128u8; 32], network: None }]); + Ok((origin, target)) } } diff --git a/polkadot/runtime/westend/src/weights/xcm/mod.rs b/polkadot/runtime/westend/src/weights/xcm/mod.rs index d2691c998d99..a5fb82a66837 100644 --- a/polkadot/runtime/westend/src/weights/xcm/mod.rs +++ b/polkadot/runtime/westend/src/weights/xcm/mod.rs @@ -299,8 +299,7 @@ impl XcmWeightInfo for WestendXcmWeight { XcmGeneric::::clear_topic() } fn alias_origin(_: &Location) -> Weight { - // XCM Executor does not currently support alias origin operations - Weight::MAX + XcmGeneric::::alias_origin() } fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { XcmGeneric::::unpaid_execution() diff --git a/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index dfc02fd20bc3..4e10e72356ab 100644 --- a/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/polkadot/runtime/westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,26 +17,28 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-11-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-vcatxqpx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `aa8403b52523`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=westend-dev +// --pallet=pallet_xcm_benchmarks::generic +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/xcm // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm_benchmarks::generic -// --chain=westend-dev -// --header=./polkadot/file_header.txt -// --template=./polkadot/xcm/pallet-xcm-benchmarks/template.hbs -// --output=./polkadot/runtime/westend/src/weights/xcm/ +// --template=polkadot/xcm/pallet-xcm-benchmarks/template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -63,8 +65,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `351` // Estimated: `6196` - // Minimum execution time: 69_051_000 picoseconds. - Weight::from_parts(71_282_000, 6196) + // Minimum execution time: 74_868_000 picoseconds. + Weight::from_parts(77_531_000, 6196) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -72,22 +74,22 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(695_000, 0) + // Minimum execution time: 688_000 picoseconds. + Weight::from_parts(733_000, 0) } pub(crate) fn pay_fees() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_096_000 picoseconds. - Weight::from_parts(3_313_000, 0) + // Minimum execution time: 3_491_000 picoseconds. + Weight::from_parts(3_667_000, 0) } pub(crate) fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 661_000 picoseconds. - Weight::from_parts(707_000, 0) + // Minimum execution time: 757_000 picoseconds. + Weight::from_parts(804_000, 0) } /// Storage: `XcmPallet::Queries` (r:1 w:0) /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -95,65 +97,65 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `3465` - // Minimum execution time: 6_054_000 picoseconds. - Weight::from_parts(6_151_000, 3465) + // Minimum execution time: 6_322_000 picoseconds. + Weight::from_parts(6_565_000, 3465) .saturating_add(T::DbWeight::get().reads(1)) } pub(crate) fn transact() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_462_000 picoseconds. - Weight::from_parts(7_750_000, 0) + // Minimum execution time: 7_841_000 picoseconds. + Weight::from_parts(8_240_000, 0) } pub(crate) fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_378_000 picoseconds. - Weight::from_parts(1_454_000, 0) + // Minimum execution time: 1_327_000 picoseconds. + Weight::from_parts(1_460_000, 0) } pub(crate) fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(744_000, 0) + // Minimum execution time: 680_000 picoseconds. + Weight::from_parts(752_000, 0) } pub(crate) fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 713_000 picoseconds. - Weight::from_parts(755_000, 0) + // Minimum execution time: 712_000 picoseconds. + Weight::from_parts(764_000, 0) } pub(crate) fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 632_000 picoseconds. - Weight::from_parts(703_000, 0) + // Minimum execution time: 663_000 picoseconds. + Weight::from_parts(712_000, 0) } pub(crate) fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 712_000 picoseconds. - Weight::from_parts(771_000, 0) + // Minimum execution time: 756_000 picoseconds. + Weight::from_parts(801_000, 0) } pub(crate) fn execute_with_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 740_000 picoseconds. - Weight::from_parts(826_000, 0) + // Minimum execution time: 773_000 picoseconds. + Weight::from_parts(822_000, 0) } pub(crate) fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 653_000 picoseconds. - Weight::from_parts(707_000, 0) + // Minimum execution time: 669_000 picoseconds. + Weight::from_parts(750_000, 0) } /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -169,8 +171,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `351` // Estimated: `6196` - // Minimum execution time: 66_765_000 picoseconds. - Weight::from_parts(69_016_000, 6196) + // Minimum execution time: 73_173_000 picoseconds. + Weight::from_parts(75_569_000, 6196) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -180,8 +182,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `23` // Estimated: `3488` - // Minimum execution time: 9_545_000 picoseconds. - Weight::from_parts(9_853_000, 3488) + // Minimum execution time: 9_851_000 picoseconds. + Weight::from_parts(10_087_000, 3488) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -189,8 +191,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 676_000 picoseconds. - Weight::from_parts(723_000, 0) + // Minimum execution time: 673_000 picoseconds. + Weight::from_parts(744_000, 0) } /// Storage: `XcmPallet::VersionNotifyTargets` (r:1 w:1) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -206,8 +208,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `147` // Estimated: `3612` - // Minimum execution time: 31_324_000 picoseconds. - Weight::from_parts(32_023_000, 3612) + // Minimum execution time: 35_714_000 picoseconds. + Weight::from_parts(36_987_000, 3612) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -217,44 +219,44 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_058_000 picoseconds. - Weight::from_parts(3_199_000, 0) + // Minimum execution time: 3_128_000 picoseconds. + Weight::from_parts(3_364_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } pub(crate) fn burn_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 994_000 picoseconds. - Weight::from_parts(1_115_000, 0) + // Minimum execution time: 1_070_000 picoseconds. + Weight::from_parts(1_188_000, 0) } pub(crate) fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 763_000 picoseconds. - Weight::from_parts(824_000, 0) + // Minimum execution time: 764_000 picoseconds. + Weight::from_parts(863_000, 0) } pub(crate) fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 665_000 picoseconds. - Weight::from_parts(712_000, 0) + // Minimum execution time: 675_000 picoseconds. + Weight::from_parts(755_000, 0) } pub(crate) fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 627_000 picoseconds. - Weight::from_parts(695_000, 0) + // Minimum execution time: 666_000 picoseconds. + Weight::from_parts(745_000, 0) } pub(crate) fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 839_000 picoseconds. - Weight::from_parts(889_000, 0) + // Minimum execution time: 838_000 picoseconds. + Weight::from_parts(918_000, 0) } /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -270,8 +272,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `351` // Estimated: `6196` - // Minimum execution time: 75_853_000 picoseconds. - Weight::from_parts(77_515_000, 6196) + // Minimum execution time: 82_721_000 picoseconds. + Weight::from_parts(85_411_000, 6196) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -279,8 +281,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_183_000 picoseconds. - Weight::from_parts(8_378_000, 0) + // Minimum execution time: 8_138_000 picoseconds. + Weight::from_parts(8_344_000, 0) } /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -296,8 +298,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `351` // Estimated: `6196` - // Minimum execution time: 66_576_000 picoseconds. - Weight::from_parts(69_465_000, 6196) + // Minimum execution time: 73_617_000 picoseconds. + Weight::from_parts(76_999_000, 6196) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -305,35 +307,42 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 739_000 picoseconds. - Weight::from_parts(773_000, 0) + // Minimum execution time: 714_000 picoseconds. + Weight::from_parts(806_000, 0) } pub(crate) fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 648_000 picoseconds. - Weight::from_parts(693_000, 0) + // Minimum execution time: 676_000 picoseconds. + Weight::from_parts(720_000, 0) } pub(crate) fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 654_000 picoseconds. - Weight::from_parts(700_000, 0) + // Minimum execution time: 666_000 picoseconds. + Weight::from_parts(731_000, 0) } pub(crate) fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 646_000 picoseconds. - Weight::from_parts(702_000, 0) + // Minimum execution time: 662_000 picoseconds. + Weight::from_parts(696_000, 0) } pub(crate) fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 665_000 picoseconds. - Weight::from_parts(714_000, 0) + // Minimum execution time: 693_000 picoseconds. + Weight::from_parts(760_000, 0) + } + pub(crate) fn alias_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 705_000 picoseconds. + Weight::from_parts(746_000, 0) } } diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index f8bb2676de3f..3f6a7304c8a9 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -38,13 +38,14 @@ use westend_runtime_constants::{ }; use xcm::latest::{prelude::*, WESTEND_GENESIS_HASH}; use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses, - AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, ChildParachainAsNative, - ChildParachainConvertsVia, DescribeAllTerminal, DescribeFamily, FrameTransactionalProcessor, - FungibleAdapter, HashedDescription, IsChildSystemParachain, IsConcrete, MintLocation, - OriginToPluralityVoice, SendXcmFeeToAccount, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + AccountId32Aliases, AliasChildLocation, AllowExplicitUnpaidExecutionFrom, + AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, + ChildParachainAsNative, ChildParachainConvertsVia, DescribeAllTerminal, DescribeFamily, + FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsChildSystemParachain, + IsConcrete, MintLocation, OriginToPluralityVoice, SendXcmFeeToAccount, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -183,6 +184,11 @@ pub type Barrier = TrailingSetTopicAsId<( /// We only waive fees for system functions, which these locations represent. pub type WaivedLocations = (SystemParachains, Equals, LocalPlurality); +/// We let locations alias into child locations of their own. +/// This is a very simple aliasing rule, mimicking the behaviour of +/// the `DescendOrigin` instruction. +pub type Aliasers = AliasChildLocation; + pub struct XcmConfig; impl xcm_executor::Config for XcmConfig { type RuntimeCall = RuntimeCall; @@ -216,7 +222,7 @@ impl xcm_executor::Config for XcmConfig { type UniversalAliases = Nothing; type CallDispatcher = RuntimeCall; type SafeCallFilter = Everything; - type Aliasers = Nothing; + type Aliasers = Aliasers; type TransactionalProcessor = FrameTransactionalProcessor; type HrmpNewChannelOpenRequestHandler = (); type HrmpChannelAcceptedHandler = (); diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs index 431c7a5f1371..84d4cba1dbe1 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs @@ -140,7 +140,7 @@ mod benchmarks { } #[benchmark] - fn set_asset_claimer() -> Result<(), BenchmarkError> { + fn asset_claimer() -> Result<(), BenchmarkError> { let mut executor = new_executor::(Default::default()); let (_, sender_location) = account_and_location::(1); diff --git a/prdoc/pr_6814.prdoc b/prdoc/pr_6814.prdoc new file mode 100644 index 000000000000..4edbf2f8ed28 --- /dev/null +++ b/prdoc/pr_6814.prdoc @@ -0,0 +1,32 @@ +title: Add aliasers to westend chains +doc: +- audience: Runtime Dev + description: |- + `InitiateTransfer`, the new instruction introduced in XCMv5, allows preserving the origin after a cross-chain transfer via the usage of the `AliasOrigin` instruction. The receiving chain needs to be configured to allow such this instruction to have its intended effect and not just throw an error. + + In this PR, I add the alias rules specified in the [RFC for origin preservation](https://github.com/polkadot-fellows/RFCs/blob/main/text/0122-alias-origin-on-asset-transfers.md) to westend chains so we can test these scenarios in the testnet. + + The new scenarios include: + - Sending a cross-chain transfer from one system chain to another and doing a Transact on the same message (1 hop) + - Sending a reserve asset transfer from one chain to another going through asset hub and doing Transact on the same message (2 hops) + + The updated chains are: + - Relay: added `AliasChildLocation` + - Collectives: added `AliasChildLocation` and `AliasOriginRootUsingFilter` + - People: added `AliasChildLocation` and `AliasOriginRootUsingFilter` + - Coretime: added `AliasChildLocation` and `AliasOriginRootUsingFilter` + + AssetHub already has `AliasChildLocation` and doesn't need the other config item. + BridgeHub is not intended to be used by end users so I didn't add any config item. + Only added `AliasChildOrigin` to the relay since we intend for it to be used less. +crates: +- name: westend-runtime + bump: patch +- name: collectives-westend-runtime + bump: patch +- name: people-westend-runtime + bump: patch +- name: coretime-westend-runtime + bump: patch +- name: pallet-xcm-benchmarks + bump: patch From f0b5c3e6c9f2dbef3df55a9ae19e9d4532fd6754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 11 Dec 2024 23:02:23 +0100 Subject: [PATCH 148/340] pallet-revive: Statically verify imports on code deployment (#6759) Previously, we failed at runtime if an unknown or unstable host function was called. This requires us to keep track of when a host function was added and when a code was deployed. We used the `api_version` to track at which API version each code was deployed. This made sure that when a new host function was added that old code won't have access to it. This is necessary as otherwise the behavior of a contract that made calls to this previously non existent host function would change from "trap" to "do something". In this PR we remove the API version. Instead, we statically verify on upload that no non-existent host function is ever used in the code. This will allow us to add new host function later without needing to keep track when they were added. This simplifies the code and also gives an immediate feedback if unknown host functions are used. --------- Co-authored-by: GitHub Action --- prdoc/pr_6759.prdoc | 16 +++ substrate/frame/revive/README.md | 2 +- .../fixtures/contracts/unknown_syscall.rs | 44 ++++++++ .../fixtures/contracts/unstable_interface.rs | 44 ++++++++ substrate/frame/revive/proc-macro/src/lib.rs | 72 ++++++------ .../revive/src/benchmarking/call_builder.rs | 10 +- substrate/frame/revive/src/lib.rs | 21 ---- substrate/frame/revive/src/limits.rs | 25 ++++- substrate/frame/revive/src/tests.rs | 32 ++++++ substrate/frame/revive/src/wasm/mod.rs | 42 ++----- substrate/frame/revive/src/wasm/runtime.rs | 105 +++++++++--------- 11 files changed, 261 insertions(+), 152 deletions(-) create mode 100644 prdoc/pr_6759.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/unknown_syscall.rs create mode 100644 substrate/frame/revive/fixtures/contracts/unstable_interface.rs diff --git a/prdoc/pr_6759.prdoc b/prdoc/pr_6759.prdoc new file mode 100644 index 000000000000..3dff12d740d4 --- /dev/null +++ b/prdoc/pr_6759.prdoc @@ -0,0 +1,16 @@ +title: 'pallet-revive: Statically verify imports on code deployment' +doc: +- audience: Runtime Dev + description: |- + Previously, we failed at runtime if an unknown or unstable host function was called. This requires us to keep track of when a host function was added and when a code was deployed. We used the `api_version` to track at which API version each code was deployed. This made sure that when a new host function was added that old code won't have access to it. This is necessary as otherwise the behavior of a contract that made calls to this previously non existent host function would change from "trap" to "do something". + + In this PR we remove the API version. Instead, we statically verify on upload that no non-existent host function is ever used in the code. This will allow us to add new host function later without needing to keep track when they were added. + + This simplifies the code and also gives an immediate feedback if unknown host functions are used. +crates: +- name: pallet-revive-proc-macro + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-fixtures + bump: major diff --git a/substrate/frame/revive/README.md b/substrate/frame/revive/README.md index 5352e636c252..575920dfaac7 100644 --- a/substrate/frame/revive/README.md +++ b/substrate/frame/revive/README.md @@ -92,7 +92,7 @@ Driven by the desire to have an iterative approach in developing new contract in concept of an unstable interface. Akin to the rust nightly compiler it allows us to add new interfaces but mark them as unstable so that contract languages can experiment with them and give feedback before we stabilize those. -In order to access interfaces which don't have a stable `#[api_version(x)]` in [`runtime.rs`](src/wasm/runtime.rs) +In order to access interfaces which don't have a stable `#[stable]` in [`runtime.rs`](src/wasm/runtime.rs) one need to set `pallet_revive::Config::UnsafeUnstableInterface` to `ConstU32`. **It should be obvious that any production runtime should never be compiled with this feature: In addition to be subject to change or removal those interfaces might not have proper weights associated with them and are therefore diff --git a/substrate/frame/revive/fixtures/contracts/unknown_syscall.rs b/substrate/frame/revive/fixtures/contracts/unknown_syscall.rs new file mode 100644 index 000000000000..93ea86754f55 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/unknown_syscall.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![no_main] + +extern crate common; + +#[polkavm_derive::polkavm_import] +extern "C" { + pub fn __this_syscall_does_not_exist__(); +} + +// Export that is never called. We can put code here that should be in the binary +// but is never supposed to be run. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call_never() { + // make sure it is not optimized away + unsafe { + __this_syscall_does_not_exist__(); + } +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/fixtures/contracts/unstable_interface.rs b/substrate/frame/revive/fixtures/contracts/unstable_interface.rs new file mode 100644 index 000000000000..d73ae041dc06 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/unstable_interface.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![no_main] + +extern crate common; + +#[polkavm_derive::polkavm_import] +extern "C" { + pub fn set_code_hash(); +} + +// Export that is never called. We can put code here that should be in the binary +// but is never supposed to be run. +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call_never() { + // make sure it is not optimized away + unsafe { + set_code_hash(); + } +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() {} diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index 6814add128d9..ed1798e5b689 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -119,7 +119,7 @@ struct EnvDef { /// Parsed host function definition. struct HostFn { item: syn::ItemFn, - api_version: Option, + is_stable: bool, name: String, returns: HostFnReturn, cfg: Option, @@ -183,22 +183,21 @@ impl HostFn { }; // process attributes - let msg = "Only #[api_version()], #[cfg] and #[mutating] attributes are allowed."; + let msg = "Only #[stable], #[cfg] and #[mutating] attributes are allowed."; let span = item.span(); let mut attrs = item.attrs.clone(); attrs.retain(|a| !a.path().is_ident("doc")); - let mut api_version = None; + let mut is_stable = false; let mut mutating = false; let mut cfg = None; while let Some(attr) = attrs.pop() { let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string(); match ident.as_str() { - "api_version" => { - if api_version.is_some() { - return Err(err(span, "#[api_version] can only be specified once")) + "stable" => { + if is_stable { + return Err(err(span, "#[stable] can only be specified once")) } - api_version = - Some(attr.parse_args::().and_then(|lit| lit.base10_parse())?); + is_stable = true; }, "mutating" => { if mutating { @@ -313,7 +312,7 @@ impl HostFn { _ => Err(err(arg1.span(), &msg)), }?; - Ok(Self { item, api_version, name, returns, cfg }) + Ok(Self { item, is_stable, name, returns, cfg }) }, _ => Err(err(span, &msg)), } @@ -411,19 +410,23 @@ fn expand_env(def: &EnvDef) -> TokenStream2 { let impls = expand_functions(def); let bench_impls = expand_bench_functions(def); let docs = expand_func_doc(def); - let highest_api_version = - def.host_funcs.iter().filter_map(|f| f.api_version).max().unwrap_or_default(); + let stable_syscalls = expand_func_list(def, false); + let all_syscalls = expand_func_list(def, true); quote! { - #[cfg(test)] - pub const HIGHEST_API_VERSION: u16 = #highest_api_version; + pub fn list_syscalls(include_unstable: bool) -> &'static [&'static [u8]] { + if include_unstable { + #all_syscalls + } else { + #stable_syscalls + } + } impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { fn handle_ecall( &mut self, memory: &mut M, __syscall_symbol__: &[u8], - __available_api_version__: ApiVersion, ) -> Result, TrapReason> { #impls @@ -474,10 +477,6 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { let body = &f.item.block; let map_output = f.returns.map_output(); let output = &f.item.sig.output; - let api_version = match f.api_version { - Some(version) => quote! { Some(#version) }, - None => quote! { None }, - }; // wrapped host function body call with host function traces // see https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/contracts#host-function-tracing @@ -513,7 +512,7 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { quote! { #cfg - #syscall_symbol if __is_available__(#api_version) => { + #syscall_symbol => { // closure is needed so that "?" can infere the correct type (|| #output { #arg_decoder @@ -534,18 +533,6 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { // This is the overhead to call an empty syscall that always needs to be charged. self.charge_gas(crate::wasm::RuntimeCosts::HostFn).map_err(TrapReason::from)?; - // Not all APIs are available depending on configuration or when the code was deployed. - // This closure will be used by syscall specific code to perform this check. - let __is_available__ = |syscall_version: Option| { - match __available_api_version__ { - ApiVersion::UnsafeNewest => true, - ApiVersion::Versioned(max_available_version) => - syscall_version - .map(|required_version| max_available_version >= required_version) - .unwrap_or(false), - } - }; - // They will be mapped to variable names by the syscall specific code. let (__a0__, __a1__, __a2__, __a3__, __a4__, __a5__) = memory.read_input_regs(); @@ -607,10 +594,8 @@ fn expand_func_doc(def: &EnvDef) -> TokenStream2 { }); quote! { #( #docs )* } }; - let availability = if let Some(version) = func.api_version { - let info = format!( - "\n# Required API version\nThis API was added in version **{version}**.", - ); + let availability = if func.is_stable { + let info = "\n# Stable API\nThis API is stable and will never change."; quote! { #[doc = #info] } } else { let info = @@ -632,3 +617,20 @@ fn expand_func_doc(def: &EnvDef) -> TokenStream2 { #( #docs )* } } + +fn expand_func_list(def: &EnvDef, include_unstable: bool) -> TokenStream2 { + let docs = def.host_funcs.iter().filter(|f| include_unstable || f.is_stable).map(|f| { + let name = Literal::byte_string(f.name.as_bytes()); + quote! { + #name.as_slice() + } + }); + let len = docs.clone().count(); + + quote! { + { + static FUNCS: [&[u8]; #len] = [#(#docs),*]; + FUNCS.as_slice() + } + } +} diff --git a/substrate/frame/revive/src/benchmarking/call_builder.rs b/substrate/frame/revive/src/benchmarking/call_builder.rs index 8d3157541168..1177d47aadc3 100644 --- a/substrate/frame/revive/src/benchmarking/call_builder.rs +++ b/substrate/frame/revive/src/benchmarking/call_builder.rs @@ -21,7 +21,7 @@ use crate::{ exec::{ExportedFunction, Ext, Key, Stack}, storage::meter::Meter, transient_storage::MeterEntry, - wasm::{ApiVersion, PreparedCall, Runtime}, + wasm::{PreparedCall, Runtime}, BalanceOf, Config, DebugBuffer, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight, }; use alloc::{vec, vec::Vec}; @@ -164,13 +164,7 @@ where module: WasmBlob, input: Vec, ) -> PreparedCall<'a, StackExt<'a, T>> { - module - .prepare_call( - Runtime::new(ext, input), - ExportedFunction::Call, - ApiVersion::UnsafeNewest, - ) - .unwrap() + module.prepare_call(Runtime::new(ext, input), ExportedFunction::Call).unwrap() } /// Add transient_storage diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 1dee1da03bc4..b9a39e7ce4d3 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -115,19 +115,6 @@ const SENTINEL: u32 = u32::MAX; /// Example: `RUST_LOG=runtime::revive=debug my_code --dev` const LOG_TARGET: &str = "runtime::revive"; -/// This version determines which syscalls are available to contracts. -/// -/// Needs to be bumped every time a versioned syscall is added. -const API_VERSION: u16 = 0; - -#[test] -fn api_version_up_to_date() { - assert!( - API_VERSION == crate::wasm::HIGHEST_API_VERSION, - "A new versioned API has been added. The `API_VERSION` needs to be bumped." - ); -} - #[frame_support::pallet] pub mod pallet { use super::*; @@ -623,14 +610,6 @@ pub mod pallet { #[pallet::storage] pub(crate) type AddressSuffix = StorageMap<_, Identity, H160, [u8; 12]>; - #[pallet::extra_constants] - impl Pallet { - #[pallet::constant_name(ApiVersion)] - fn api_version() -> u16 { - API_VERSION - } - } - #[pallet::hooks] impl Hooks> for Pallet { fn on_idle(_block: BlockNumberFor, limit: Weight) -> Weight { diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 5ce96f59c14d..2e112baae301 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -116,7 +116,10 @@ pub mod code { const BASIC_BLOCK_SIZE: u32 = 1000; /// Make sure that the various program parts are within the defined limits. - pub fn enforce(blob: Vec) -> Result { + pub fn enforce( + blob: Vec, + available_syscalls: &[&[u8]], + ) -> Result { fn round_page(n: u32) -> u64 { // performing the rounding in u64 in order to prevent overflow u64::from(n).next_multiple_of(PAGE_SIZE.into()) @@ -134,6 +137,26 @@ pub mod code { Err(Error::::CodeRejected)?; } + // Need to check that no non-existent syscalls are used. This allows us to add + // new syscalls later without affecting already deployed code. + for (idx, import) in program.imports().iter().enumerate() { + // We are being defensive in case an attacker is able to somehow include + // a lot of imports. This is important because we search the array of host + // functions for every import. + if idx == available_syscalls.len() { + log::debug!(target: LOG_TARGET, "Program contains too many imports."); + Err(Error::::CodeRejected)?; + } + let Some(import) = import else { + log::debug!(target: LOG_TARGET, "Program contains malformed import."); + return Err(Error::::CodeRejected.into()); + }; + if !available_syscalls.contains(&import.as_bytes()) { + log::debug!(target: LOG_TARGET, "Program references unknown syscall: {}", import); + Err(Error::::CodeRejected)?; + } + } + // This scans the whole program but we only do it once on code deployment. // It is safe to do unchecked math in u32 because the size of the program // was already checked above. diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 58d4721b4e53..a000de1491fa 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4753,3 +4753,35 @@ fn skip_transfer_works() { )); }); } + +#[test] +fn unknown_syscall_rejected() { + let (code, _) = compile_module("unknown_syscall").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ::Currency::set_balance(&ALICE, 1_000_000); + + assert_err!( + builder::bare_instantiate(Code::Upload(code)).build().result, + >::CodeRejected, + ) + }); +} + +#[test] +fn unstable_interface_rejected() { + let (code, _) = compile_module("unstable_interface").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + ::Currency::set_balance(&ALICE, 1_000_000); + + Test::set_unstable_interface(false); + assert_err!( + builder::bare_instantiate(Code::Upload(code.clone())).build().result, + >::CodeRejected, + ); + + Test::set_unstable_interface(true); + assert_ok!(builder::bare_instantiate(Code::Upload(code)).build().result); + }); +} diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index 54fb02c866e1..e963895dafae 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -23,13 +23,10 @@ mod runtime; #[cfg(doc)] pub use crate::wasm::runtime::SyscallDoc; -#[cfg(test)] -pub use runtime::HIGHEST_API_VERSION; - #[cfg(feature = "runtime-benchmarks")] pub use crate::wasm::runtime::{ReturnData, TrapReason}; -pub use crate::wasm::runtime::{ApiVersion, Memory, Runtime, RuntimeCosts}; +pub use crate::wasm::runtime::{Memory, Runtime, RuntimeCosts}; use crate::{ address::AddressMapper, @@ -39,7 +36,7 @@ use crate::{ storage::meter::Diff, weights::WeightInfo, AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, Event, ExecError, - HoldReason, Pallet, PristineCode, Weight, API_VERSION, LOG_TARGET, + HoldReason, Pallet, PristineCode, Weight, LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode, MaxEncodedLen}; @@ -87,11 +84,6 @@ pub struct CodeInfo { refcount: u64, /// Length of the code in bytes. code_len: u32, - /// The API version that this contract operates under. - /// - /// This determines which host functions are available to the contract. This - /// prevents that new host functions become available to already deployed contracts. - api_version: u16, /// The behaviour version that this contract operates under. /// /// Whenever any observeable change (with the exception of weights) are made we need @@ -99,7 +91,7 @@ pub struct CodeInfo { /// exposing the old behaviour depending on the set behaviour version of the contract. /// /// As of right now this is a reserved field that is always set to 0. - behaviour_version: u16, + behaviour_version: u32, } impl ExportedFunction { @@ -130,9 +122,10 @@ where { /// We only check for size and nothing else when the code is uploaded. pub fn from_code(code: Vec, owner: AccountIdOf) -> Result { - // We do size checks when new code is deployed. This allows us to increase + // We do validation only when new code is deployed. This allows us to increase // the limits later without affecting already deployed code. - let code = limits::code::enforce::(code)?; + let available_syscalls = runtime::list_syscalls(T::UnsafeUnstableInterface::get()); + let code = limits::code::enforce::(code, available_syscalls)?; let code_len = code.len() as u32; let bytes_added = code_len.saturating_add(>::max_encoded_len() as u32); @@ -144,7 +137,6 @@ where deposit, refcount: 0, code_len, - api_version: API_VERSION, behaviour_version: Default::default(), }; let code_hash = H256(sp_io::hashing::keccak_256(&code)); @@ -230,7 +222,6 @@ impl CodeInfo { deposit: Default::default(), refcount: 0, code_len: 0, - api_version: API_VERSION, behaviour_version: Default::default(), } } @@ -260,7 +251,6 @@ pub struct PreparedCall<'a, E: Ext> { module: polkavm::Module, instance: polkavm::RawInstance, runtime: Runtime<'a, E, polkavm::RawInstance>, - api_version: ApiVersion, } impl<'a, E: Ext> PreparedCall<'a, E> @@ -271,12 +261,9 @@ where pub fn call(mut self) -> ExecResult { let exec_result = loop { let interrupt = self.instance.run(); - if let Some(exec_result) = self.runtime.handle_interrupt( - interrupt, - &self.module, - &mut self.instance, - self.api_version, - ) { + if let Some(exec_result) = + self.runtime.handle_interrupt(interrupt, &self.module, &mut self.instance) + { break exec_result } }; @@ -290,7 +277,6 @@ impl WasmBlob { self, mut runtime: Runtime, entry_point: ExportedFunction, - api_version: ApiVersion, ) -> Result, ExecError> { let mut config = polkavm::Config::default(); config.set_backend(Some(polkavm::BackendKind::Interpreter)); @@ -344,7 +330,7 @@ impl WasmBlob { instance.set_gas(gas_limit_polkavm); instance.prepare_call_untyped(entry_program_counter, &[]); - Ok(PreparedCall { module, instance, runtime, api_version }) + Ok(PreparedCall { module, instance, runtime }) } } @@ -365,13 +351,7 @@ where function: ExportedFunction, input_data: Vec, ) -> ExecResult { - let api_version = if ::UnsafeUnstableInterface::get() { - ApiVersion::UnsafeNewest - } else { - ApiVersion::Versioned(self.code_info.api_version) - }; - let prepared_call = - self.prepare_call(Runtime::new(ext, input_data), function, api_version)?; + let prepared_call = self.prepare_call(Runtime::new(ext, input_data), function)?; prepared_call.call() } diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 8fb7e5c27470..8d54b7fd0ddf 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -44,14 +44,6 @@ type CallOf = ::RuntimeCall; /// The maximum nesting depth a contract can use when encoding types. const MAX_DECODE_NESTING: u32 = 256; -#[derive(Clone, Copy)] -pub enum ApiVersion { - /// Expose all APIs even unversioned ones. Only used for testing and benchmarking. - UnsafeNewest, - /// Only expose API's up to and including the specified version. - Versioned(u16), -} - /// Abstraction over the memory access within syscalls. /// /// The reason for this abstraction is that we run syscalls on the host machine when @@ -551,7 +543,6 @@ impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { interrupt: Result, module: &polkavm::Module, instance: &mut M, - api_version: ApiVersion, ) -> Option { use polkavm::InterruptKind::*; @@ -571,7 +562,7 @@ impl<'a, E: Ext, M: PolkaVmInstance> Runtime<'a, E, M> { let Some(syscall_symbol) = module.imports().get(idx) else { return Some(Err(>::InvalidSyscall.into())); }; - match self.handle_ecall(instance, syscall_symbol.as_bytes(), api_version) { + match self.handle_ecall(instance, syscall_symbol.as_bytes()) { Ok(None) => None, Ok(Some(return_value)) => { instance.write_output(return_value); @@ -1127,14 +1118,18 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { #[define_env] pub mod env { /// Noop function used to benchmark the time it takes to execute an empty function. + /// + /// Marked as stable because it needs to be called from benchmarks even when the benchmarked + /// parachain has unstable functions disabled. #[cfg(feature = "runtime-benchmarks")] + #[stable] fn noop(&mut self, memory: &mut M) -> Result<(), TrapReason> { Ok(()) } /// Set the value at the given key in the contract storage. /// See [`pallet_revive_uapi::HostFn::set_storage_v2`] - #[api_version(0)] + #[stable] #[mutating] fn set_storage( &mut self, @@ -1150,7 +1145,7 @@ pub mod env { /// Clear the value at the given key in the contract storage. /// See [`pallet_revive_uapi::HostFn::clear_storage`] - #[api_version(0)] + #[stable] #[mutating] fn clear_storage( &mut self, @@ -1164,7 +1159,7 @@ pub mod env { /// Retrieve the value under the given key from storage. /// See [`pallet_revive_uapi::HostFn::get_storage`] - #[api_version(0)] + #[stable] fn get_storage( &mut self, memory: &mut M, @@ -1179,7 +1174,7 @@ pub mod env { /// Checks whether there is a value stored under the given key. /// See [`pallet_revive_uapi::HostFn::contains_storage`] - #[api_version(0)] + #[stable] fn contains_storage( &mut self, memory: &mut M, @@ -1192,7 +1187,7 @@ pub mod env { /// Retrieve and remove the value under the given key from storage. /// See [`pallet_revive_uapi::HostFn::take_storage`] - #[api_version(0)] + #[stable] #[mutating] fn take_storage( &mut self, @@ -1208,7 +1203,7 @@ pub mod env { /// Make a call to another contract. /// See [`pallet_revive_uapi::HostFn::call`]. - #[api_version(0)] + #[stable] fn call( &mut self, memory: &mut M, @@ -1239,7 +1234,7 @@ pub mod env { /// Execute code in the context (storage, caller, value) of the current contract. /// See [`pallet_revive_uapi::HostFn::delegate_call`]. - #[api_version(0)] + #[stable] fn delegate_call( &mut self, memory: &mut M, @@ -1269,7 +1264,7 @@ pub mod env { /// Instantiate a contract with the specified code hash. /// See [`pallet_revive_uapi::HostFn::instantiate`]. - #[api_version(0)] + #[stable] #[mutating] fn instantiate( &mut self, @@ -1303,7 +1298,7 @@ pub mod env { /// Remove the calling account and transfer remaining **free** balance. /// See [`pallet_revive_uapi::HostFn::terminate`]. - #[api_version(0)] + #[stable] #[mutating] fn terminate(&mut self, memory: &mut M, beneficiary_ptr: u32) -> Result<(), TrapReason> { self.terminate(memory, beneficiary_ptr) @@ -1311,7 +1306,7 @@ pub mod env { /// Stores the input passed by the caller into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::input`]. - #[api_version(0)] + #[stable] fn input(&mut self, memory: &mut M, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { if let Some(input) = self.input_data.take() { self.write_sandbox_output(memory, out_ptr, out_len_ptr, &input, false, |len| { @@ -1326,7 +1321,7 @@ pub mod env { /// Cease contract execution and save a data buffer as a result of the execution. /// See [`pallet_revive_uapi::HostFn::return_value`]. - #[api_version(0)] + #[stable] fn seal_return( &mut self, memory: &mut M, @@ -1340,7 +1335,7 @@ pub mod env { /// Stores the address of the caller into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::caller`]. - #[api_version(0)] + #[stable] fn caller(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Caller)?; let caller = ::AddressMapper::to_address(self.ext.caller().account_id()?); @@ -1355,7 +1350,7 @@ pub mod env { /// Stores the address of the call stack origin into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::origin`]. - #[api_version(0)] + #[stable] fn origin(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Origin)?; let origin = ::AddressMapper::to_address(self.ext.origin().account_id()?); @@ -1370,7 +1365,7 @@ pub mod env { /// Checks whether a specified address belongs to a contract. /// See [`pallet_revive_uapi::HostFn::is_contract`]. - #[api_version(0)] + #[stable] fn is_contract(&mut self, memory: &mut M, account_ptr: u32) -> Result { self.charge_gas(RuntimeCosts::IsContract)?; let address = memory.read_h160(account_ptr)?; @@ -1379,7 +1374,7 @@ pub mod env { /// Retrieve the code hash for a specified contract address. /// See [`pallet_revive_uapi::HostFn::code_hash`]. - #[api_version(0)] + #[stable] fn code_hash(&mut self, memory: &mut M, addr_ptr: u32, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::CodeHash)?; let address = memory.read_h160(addr_ptr)?; @@ -1394,7 +1389,7 @@ pub mod env { /// Retrieve the code size for a given contract address. /// See [`pallet_revive_uapi::HostFn::code_size`]. - #[api_version(0)] + #[stable] fn code_size(&mut self, memory: &mut M, addr_ptr: u32, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::CodeSize)?; let address = memory.read_h160(addr_ptr)?; @@ -1409,7 +1404,7 @@ pub mod env { /// Retrieve the code hash of the currently executing contract. /// See [`pallet_revive_uapi::HostFn::own_code_hash`]. - #[api_version(0)] + #[stable] fn own_code_hash(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::OwnCodeHash)?; let code_hash = *self.ext.own_code_hash(); @@ -1424,7 +1419,7 @@ pub mod env { /// Checks whether the caller of the current contract is the origin of the whole call stack. /// See [`pallet_revive_uapi::HostFn::caller_is_origin`]. - #[api_version(0)] + #[stable] fn caller_is_origin(&mut self, _memory: &mut M) -> Result { self.charge_gas(RuntimeCosts::CallerIsOrigin)?; Ok(self.ext.caller_is_origin() as u32) @@ -1432,7 +1427,7 @@ pub mod env { /// Checks whether the caller of the current contract is root. /// See [`pallet_revive_uapi::HostFn::caller_is_root`]. - #[api_version(0)] + #[stable] fn caller_is_root(&mut self, _memory: &mut M) -> Result { self.charge_gas(RuntimeCosts::CallerIsRoot)?; Ok(self.ext.caller_is_root() as u32) @@ -1440,7 +1435,7 @@ pub mod env { /// Stores the address of the current contract into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::address`]. - #[api_version(0)] + #[stable] fn address(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Address)?; let address = self.ext.address(); @@ -1455,7 +1450,7 @@ pub mod env { /// Stores the price for the specified amount of weight into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::weight_to_fee`]. - #[api_version(0)] + #[stable] fn weight_to_fee( &mut self, memory: &mut M, @@ -1476,7 +1471,7 @@ pub mod env { /// Stores the amount of weight left into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::weight_left`]. - #[api_version(0)] + #[stable] fn weight_left( &mut self, memory: &mut M, @@ -1497,7 +1492,7 @@ pub mod env { /// Stores the immutable data into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::get_immutable_data`]. - #[api_version(0)] + #[stable] fn get_immutable_data( &mut self, memory: &mut M, @@ -1513,7 +1508,7 @@ pub mod env { /// Attaches the supplied immutable data to the currently executing contract. /// See [`pallet_revive_uapi::HostFn::set_immutable_data`]. - #[api_version(0)] + #[stable] fn set_immutable_data(&mut self, memory: &mut M, ptr: u32, len: u32) -> Result<(), TrapReason> { if len > limits::IMMUTABLE_BYTES { return Err(Error::::OutOfBounds.into()); @@ -1527,7 +1522,7 @@ pub mod env { /// Stores the *free* balance of the current account into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::balance`]. - #[api_version(0)] + #[stable] fn balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Balance)?; Ok(self.write_fixed_sandbox_output( @@ -1541,7 +1536,7 @@ pub mod env { /// Stores the *free* balance of the supplied address into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::balance`]. - #[api_version(0)] + #[stable] fn balance_of( &mut self, memory: &mut M, @@ -1561,7 +1556,7 @@ pub mod env { /// Returns the chain ID. /// See [`pallet_revive_uapi::HostFn::chain_id`]. - #[api_version(0)] + #[stable] fn chain_id(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { Ok(self.write_fixed_sandbox_output( memory, @@ -1574,7 +1569,7 @@ pub mod env { /// Stores the value transferred along with this call/instantiate into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::value_transferred`]. - #[api_version(0)] + #[stable] fn value_transferred(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::ValueTransferred)?; Ok(self.write_fixed_sandbox_output( @@ -1588,7 +1583,7 @@ pub mod env { /// Load the latest block timestamp into the supplied buffer /// See [`pallet_revive_uapi::HostFn::now`]. - #[api_version(0)] + #[stable] fn now(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::Now)?; Ok(self.write_fixed_sandbox_output( @@ -1602,7 +1597,7 @@ pub mod env { /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::minimum_balance`]. - #[api_version(0)] + #[stable] fn minimum_balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::MinimumBalance)?; Ok(self.write_fixed_sandbox_output( @@ -1616,7 +1611,7 @@ pub mod env { /// Deposit a contract event with the data buffer and optional list of topics. /// See [pallet_revive_uapi::HostFn::deposit_event] - #[api_version(0)] + #[stable] #[mutating] fn deposit_event( &mut self, @@ -1656,7 +1651,7 @@ pub mod env { /// Stores the current block number of the current contract into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::block_number`]. - #[api_version(0)] + #[stable] fn block_number(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::BlockNumber)?; Ok(self.write_fixed_sandbox_output( @@ -1670,7 +1665,7 @@ pub mod env { /// Stores the block hash at given block height into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::block_hash`]. - #[api_version(0)] + #[stable] fn block_hash( &mut self, memory: &mut M, @@ -1691,7 +1686,7 @@ pub mod env { /// Computes the SHA2 256-bit hash on the given input buffer. /// See [`pallet_revive_uapi::HostFn::hash_sha2_256`]. - #[api_version(0)] + #[stable] fn hash_sha2_256( &mut self, memory: &mut M, @@ -1707,7 +1702,7 @@ pub mod env { /// Computes the KECCAK 256-bit hash on the given input buffer. /// See [`pallet_revive_uapi::HostFn::hash_keccak_256`]. - #[api_version(0)] + #[stable] fn hash_keccak_256( &mut self, memory: &mut M, @@ -1723,7 +1718,7 @@ pub mod env { /// Computes the BLAKE2 256-bit hash on the given input buffer. /// See [`pallet_revive_uapi::HostFn::hash_blake2_256`]. - #[api_version(0)] + #[stable] fn hash_blake2_256( &mut self, memory: &mut M, @@ -1739,7 +1734,7 @@ pub mod env { /// Computes the BLAKE2 128-bit hash on the given input buffer. /// See [`pallet_revive_uapi::HostFn::hash_blake2_128`]. - #[api_version(0)] + #[stable] fn hash_blake2_128( &mut self, memory: &mut M, @@ -1785,7 +1780,7 @@ pub mod env { /// Emit a custom debug message. /// See [`pallet_revive_uapi::HostFn::debug_message`]. - #[api_version(0)] + #[stable] fn debug_message( &mut self, memory: &mut M, @@ -1903,7 +1898,7 @@ pub mod env { /// Recovers the ECDSA public key from the given message hash and signature. /// See [`pallet_revive_uapi::HostFn::ecdsa_recover`]. - #[api_version(0)] + #[stable] fn ecdsa_recover( &mut self, memory: &mut M, @@ -1934,7 +1929,7 @@ pub mod env { /// Verify a sr25519 signature /// See [`pallet_revive_uapi::HostFn::sr25519_verify`]. - #[api_version(0)] + #[stable] fn sr25519_verify( &mut self, memory: &mut M, @@ -1975,7 +1970,7 @@ pub mod env { /// Calculates Ethereum address from the ECDSA compressed public key and stores /// See [`pallet_revive_uapi::HostFn::ecdsa_to_eth_address`]. - #[api_version(0)] + #[stable] fn ecdsa_to_eth_address( &mut self, memory: &mut M, @@ -1997,7 +1992,7 @@ pub mod env { /// Adds a new delegate dependency to the contract. /// See [`pallet_revive_uapi::HostFn::lock_delegate_dependency`]. - #[api_version(0)] + #[stable] #[mutating] fn lock_delegate_dependency( &mut self, @@ -2012,7 +2007,7 @@ pub mod env { /// Removes the delegate dependency from the contract. /// see [`pallet_revive_uapi::HostFn::unlock_delegate_dependency`]. - #[api_version(0)] + #[stable] #[mutating] fn unlock_delegate_dependency( &mut self, @@ -2027,7 +2022,7 @@ pub mod env { /// Stores the length of the data returned by the last call into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::return_data_size`]. - #[api_version(0)] + #[stable] fn return_data_size(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { Ok(self.write_fixed_sandbox_output( memory, @@ -2040,7 +2035,7 @@ pub mod env { /// Stores data returned by the last call, starting from `offset`, into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::return_data`]. - #[api_version(0)] + #[stable] fn return_data_copy( &mut self, memory: &mut M, From 61518e0f808723beef9f4f63d1da9cc604c77530 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Thu, 12 Dec 2024 11:29:35 +0200 Subject: [PATCH 149/340] Update parity-publish to 0.10.3 (#6854) # Description Upgrades parity-publish with fixes for `Error: no dep` and panic triggered when running `plan` and there is a new unpublished member crate introduced in the workspace. ## Integration N/A ## Review Notes Context: https://github.com/paritytech/polkadot-sdk/pull/6450#issuecomment-2537108971 Signed-off-by: Iulian Barbu --- .github/workflows/check-semver.yml | 2 +- .github/workflows/publish-check-compile.yml | 2 +- .github/workflows/publish-check-crates.yml | 2 +- .github/workflows/publish-claim-crates.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index 11b386da21e9..16028c8de770 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -74,7 +74,7 @@ jobs: - name: install parity-publish # Set the target dir to cache the build. - run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.2 --locked -q + run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.3 --locked -q - name: check semver run: | diff --git a/.github/workflows/publish-check-compile.yml b/.github/workflows/publish-check-compile.yml index 83cd3ff8fa90..ada8635e314e 100644 --- a/.github/workflows/publish-check-compile.yml +++ b/.github/workflows/publish-check-compile.yml @@ -31,7 +31,7 @@ jobs: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.10.2 --locked -q + run: cargo install parity-publish@0.10.3 --locked -q - name: parity-publish update plan run: parity-publish --color always plan --skip-check --prdoc prdoc/ diff --git a/.github/workflows/publish-check-crates.yml b/.github/workflows/publish-check-crates.yml index 1e5a8054e2c7..3150cb9dd405 100644 --- a/.github/workflows/publish-check-crates.yml +++ b/.github/workflows/publish-check-crates.yml @@ -24,7 +24,7 @@ jobs: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.10.2 --locked -q + run: cargo install parity-publish@0.10.3 --locked -q - name: parity-publish check run: parity-publish --color always check --allow-unpublished diff --git a/.github/workflows/publish-claim-crates.yml b/.github/workflows/publish-claim-crates.yml index 845b57a61b96..a6efc8a5599e 100644 --- a/.github/workflows/publish-claim-crates.yml +++ b/.github/workflows/publish-claim-crates.yml @@ -18,7 +18,7 @@ jobs: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.10.2 --locked -q + run: cargo install parity-publish@0.10.3 --locked -q - name: parity-publish claim env: From 389e221dcf65fc744d206f5293227959a228ba91 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Thu, 12 Dec 2024 12:56:20 +0100 Subject: [PATCH 150/340] [pallet-revive] implement the call data load API (#6835) This PR implements the call data load API akin to [how it works on ethereum](https://www.evm.codes/?fork=cancun#35). There will also be a second PR to adjust the input function to resemble the call data copy opcode on EVM. --------- Signed-off-by: Cyrill Leutwiler Co-authored-by: command-bot <> --- prdoc/pr_6835.prdoc | 12 + .../fixtures/contracts/call_data_load.rs | 44 + .../frame/revive/src/benchmarking/mod.rs | 15 + substrate/frame/revive/src/tests.rs | 42 + substrate/frame/revive/src/wasm/runtime.rs | 34 + substrate/frame/revive/src/weights.rs | 881 +++++++++--------- substrate/frame/revive/uapi/src/host.rs | 15 + .../frame/revive/uapi/src/host/riscv64.rs | 5 + 8 files changed, 617 insertions(+), 431 deletions(-) create mode 100644 prdoc/pr_6835.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/call_data_load.rs diff --git a/prdoc/pr_6835.prdoc b/prdoc/pr_6835.prdoc new file mode 100644 index 000000000000..73d1a81e761c --- /dev/null +++ b/prdoc/pr_6835.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] implement the call data load API' +doc: +- audience: Runtime Dev + description: |- + This PR implements the call data load API akin to [how it works on ethereum](https://www.evm.codes/?fork=cancun#35). +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/call_data_load.rs b/substrate/frame/revive/fixtures/contracts/call_data_load.rs new file mode 100644 index 000000000000..d3df9433f5d1 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_data_load.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This uses the call data load API to first the first input byte. +//! This single input byte is used as the offset for a second call +//! to the call data load API. +//! The output of the second API call is returned. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut buf = [0; 32]; + api::call_data_load(&mut buf, 0); + + let offset = buf[31] as u32; + let mut buf = [0; 32]; + api::call_data_load(&mut buf, offset); + + api::return_value(ReturnFlags::empty(), &buf); +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index ebdea8675824..94fac13d78e8 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -838,6 +838,21 @@ mod benchmarks { assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().get_weight_price(weight)); } + #[benchmark(pov_mode = Measured)] + fn seal_call_data_load() { + let mut setup = CallSetup::::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![42u8; 32]); + let mut memory = memory!(vec![0u8; 32],); + let result; + #[block] + { + result = runtime.bench_call_data_load(memory.as_mut_slice(), 0, 0); + } + assert_ok!(result); + assert_eq!(&memory[..], &vec![42u8; 32]); + } + #[benchmark(pov_mode = Measured)] fn seal_input(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) { let mut setup = CallSetup::::default(); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index a000de1491fa..b3cd591e4d0c 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4407,6 +4407,48 @@ fn chain_id_works() { }); } +#[test] +fn call_data_load_api_works() { + let (code, _) = compile_module("call_data_load").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It reads a byte for the offset and then returns + // what call data load returned using this byte as the offset. + let input = (3u8, U256::max_value(), U256::max_value()).encode(); + let received = builder::bare_call(addr).data(input).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::max_value()); + + // Edge case + let input = (2u8, U256::from(255).to_big_endian()).encode(); + let received = builder::bare_call(addr).data(input).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::from(65280)); + + // Edge case + let received = builder::bare_call(addr).data(vec![1]).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::zero()); + + // OOB case + let input = (42u8).encode(); + let received = builder::bare_call(addr).data(input).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::zero()); + + // No calldata should return the zero value + let received = builder::bare_call(addr).build().result.unwrap(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::zero()); + }); +} + #[test] fn return_data_api_works() { let (code_return_data_api, _) = compile_module("return_data_api").unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 8d54b7fd0ddf..0d03771224b5 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -267,6 +267,8 @@ pub enum RuntimeCosts { CopyFromContract(u32), /// Weight charged for copying data to the sandbox. CopyToContract(u32), + /// Weight of calling `seal_call_data_load``. + CallDataLoad, /// Weight of calling `seal_caller`. Caller, /// Weight of calling `seal_origin`. @@ -429,6 +431,7 @@ impl Token for RuntimeCosts { HostFn => cost_args!(noop_host_fn, 1), CopyToContract(len) => T::WeightInfo::seal_input(len), CopyFromContract(len) => T::WeightInfo::seal_return(len), + CallDataLoad => T::WeightInfo::seal_call_data_load(), Caller => T::WeightInfo::seal_caller(), Origin => T::WeightInfo::seal_origin(), IsContract => T::WeightInfo::seal_is_contract(), @@ -1319,6 +1322,37 @@ pub mod env { } } + /// Stores the U256 value at given call input `offset` into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::call_data_load`]. + #[stable] + fn call_data_load( + &mut self, + memory: &mut M, + out_ptr: u32, + offset: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::CallDataLoad)?; + + let Some(input) = self.input_data.as_ref() else { + return Err(Error::::InputForwarded.into()); + }; + + let mut data = [0; 32]; + let start = offset as usize; + let data = if start >= input.len() { + data // Any index is valid to request; OOB offsets return zero. + } else { + let end = start.saturating_add(32).min(input.len()); + data[..end - start].copy_from_slice(&input[start..end]); + data.reverse(); + data // Solidity expects right-padded data + }; + + self.write_fixed_sandbox_output(memory, out_ptr, &data, false, already_charged)?; + + Ok(()) + } + /// Cease contract execution and save a data buffer as a result of the execution. /// See [`pallet_revive_uapi::HostFn::return_value`]. #[stable] diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 96654432a5cc..e9178287f8f9 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -18,28 +18,28 @@ //! Autogenerated weights for `pallet_revive` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-11-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `9fd11f1b2ec3`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// target/production/substrate-node // benchmark // pallet +// --extrinsic=* // --chain=dev +// --pallet=pallet_revive +// --header=/__w/polkadot-sdk/polkadot-sdk/substrate/HEADER-APACHE2 +// --output=/__w/polkadot-sdk/polkadot-sdk/substrate/frame/revive/src/weights.rs +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --pallet=pallet_revive +// --heap-pages=4096 +// --template=substrate/.maintain/frame-weight-template.hbs // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./substrate/frame/revive/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -84,6 +84,7 @@ pub trait WeightInfo { fn seal_block_hash() -> Weight; fn seal_now() -> Weight; fn seal_weight_to_fee() -> Weight; + fn seal_call_data_load() -> Weight; fn seal_input(n: u32, ) -> Weight; fn seal_return(n: u32, ) -> Weight; fn seal_terminate(n: u32, ) -> Weight; @@ -133,8 +134,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_818_000 picoseconds. - Weight::from_parts(3_058_000, 1594) + // Minimum execution time: 2_874_000 picoseconds. + Weight::from_parts(3_131_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -144,10 +145,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 15_916_000 picoseconds. - Weight::from_parts(16_132_000, 415) - // Standard Error: 1_482 - .saturating_add(Weight::from_parts(1_185_583, 0).saturating_mul(k.into())) + // Minimum execution time: 16_079_000 picoseconds. + Weight::from_parts(5_747_743, 415) + // Standard Error: 1_130 + .saturating_add(Weight::from_parts(1_181_775, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -169,10 +170,10 @@ impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 88_115_000 picoseconds. - Weight::from_parts(92_075_651, 7442) + // Measured: `1536` + // Estimated: `7476` + // Minimum execution time: 94_513_000 picoseconds. + Weight::from_parts(99_111_938, 7476) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -194,14 +195,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn instantiate_with_code(c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `403` - // Estimated: `6326` - // Minimum execution time: 188_274_000 picoseconds. - Weight::from_parts(157_773_869, 6326) - // Standard Error: 11 - .saturating_add(Weight::from_parts(16, 0).saturating_mul(c.into())) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_464, 0).saturating_mul(i.into())) + // Measured: `416` + // Estimated: `6345` + // Minimum execution time: 195_917_000 picoseconds. + Weight::from_parts(175_835_928, 6345) + // Standard Error: 10 + .saturating_add(Weight::from_parts(10, 0).saturating_mul(c.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(4_554, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -223,11 +224,11 @@ impl WeightInfo for SubstrateWeight { fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `1296` - // Estimated: `4739` - // Minimum execution time: 158_616_000 picoseconds. - Weight::from_parts(134_329_076, 4739) - // Standard Error: 15 - .saturating_add(Weight::from_parts(4_358, 0).saturating_mul(i.into())) + // Estimated: `4753` + // Minimum execution time: 162_583_000 picoseconds. + Weight::from_parts(143_621_658, 4753) + // Standard Error: 16 + .saturating_add(Weight::from_parts(4_499, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -245,10 +246,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 134_935_000 picoseconds. - Weight::from_parts(141_040_000, 7442) + // Measured: `1536` + // Estimated: `7476` + // Minimum execution time: 145_642_000 picoseconds. + Weight::from_parts(152_866_000, 7476) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -263,8 +264,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 51_026_000 picoseconds. - Weight::from_parts(53_309_143, 3574) + // Minimum execution time: 51_664_000 picoseconds. + Weight::from_parts(53_863_257, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -278,8 +279,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 44_338_000 picoseconds. - Weight::from_parts(45_398_000, 3750) + // Minimum execution time: 44_879_000 picoseconds. + Weight::from_parts(46_401_000, 3750) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -291,8 +292,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 26_420_000 picoseconds. - Weight::from_parts(27_141_000, 6469) + // Minimum execution time: 27_833_000 picoseconds. + Weight::from_parts(29_013_000, 6469) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -304,8 +305,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 39_735_000 picoseconds. - Weight::from_parts(41_260_000, 3574) + // Minimum execution time: 40_611_000 picoseconds. + Weight::from_parts(41_336_000, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -317,8 +318,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 32_059_000 picoseconds. - Weight::from_parts(32_776_000, 3521) + // Minimum execution time: 32_576_000 picoseconds. + Weight::from_parts(33_300_000, 3521) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -330,8 +331,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_553_000 picoseconds. - Weight::from_parts(14_121_000, 3610) + // Minimum execution time: 13_978_000 picoseconds. + Weight::from_parts(14_573_000, 3610) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -339,24 +340,24 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_392_000 picoseconds. - Weight::from_parts(7_692_248, 0) - // Standard Error: 105 - .saturating_add(Weight::from_parts(180_036, 0).saturating_mul(r.into())) + // Minimum execution time: 6_877_000 picoseconds. + Weight::from_parts(8_471_206, 0) + // Standard Error: 226 + .saturating_add(Weight::from_parts(165_314, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 287_000 picoseconds. - Weight::from_parts(317_000, 0) + // Minimum execution time: 290_000 picoseconds. + Weight::from_parts(345_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 235_000 picoseconds. - Weight::from_parts(288_000, 0) + // Minimum execution time: 243_000 picoseconds. + Weight::from_parts(303_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -364,8 +365,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_101_000 picoseconds. - Weight::from_parts(10_420_000, 3771) + // Minimum execution time: 10_441_000 picoseconds. + Weight::from_parts(10_812_000, 3771) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -374,16 +375,16 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_422_000 picoseconds. - Weight::from_parts(11_829_000, 3868) + // Minimum execution time: 11_403_000 picoseconds. + Weight::from_parts(11_913_000, 3868) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 247_000 picoseconds. - Weight::from_parts(282_000, 0) + // Minimum execution time: 259_000 picoseconds. + Weight::from_parts(306_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -393,44 +394,44 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_856_000 picoseconds. - Weight::from_parts(15_528_000, 3938) + // Minimum execution time: 14_887_000 picoseconds. + Weight::from_parts(15_625_000, 3938) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 303_000 picoseconds. - Weight::from_parts(361_000, 0) + // Minimum execution time: 315_000 picoseconds. + Weight::from_parts(389_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 253_000 picoseconds. - Weight::from_parts(287_000, 0) + // Minimum execution time: 294_000 picoseconds. + Weight::from_parts(322_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 231_000 picoseconds. - Weight::from_parts(263_000, 0) + // Minimum execution time: 239_000 picoseconds. + Weight::from_parts(299_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 628_000 picoseconds. - Weight::from_parts(697_000, 0) + Weight::from_parts(703_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `103` // Estimated: `0` - // Minimum execution time: 4_531_000 picoseconds. - Weight::from_parts(4_726_000, 0) + // Minimum execution time: 4_816_000 picoseconds. + Weight::from_parts(5_078_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -440,8 +441,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 8_787_000 picoseconds. - Weight::from_parts(9_175_000, 3729) + // Minimum execution time: 8_965_000 picoseconds. + Weight::from_parts(9_533_000, 3729) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -451,10 +452,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_760_000 picoseconds. - Weight::from_parts(6_591_336, 3703) + // Minimum execution time: 6_174_000 picoseconds. + Weight::from_parts(6_755_842, 3703) // Standard Error: 4 - .saturating_add(Weight::from_parts(628, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(699, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -465,32 +466,32 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_971_000 picoseconds. - Weight::from_parts(2_206_252, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(529, 0).saturating_mul(n.into())) + // Minimum execution time: 1_977_000 picoseconds. + Weight::from_parts(2_175_653, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(633, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 246_000 picoseconds. - Weight::from_parts(279_000, 0) + // Minimum execution time: 259_000 picoseconds. + Weight::from_parts(298_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 223_000 picoseconds. - Weight::from_parts(274_000, 0) + // Minimum execution time: 277_000 picoseconds. + Weight::from_parts(330_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 213_000 picoseconds. - Weight::from_parts(270_000, 0) + // Minimum execution time: 260_000 picoseconds. + Weight::from_parts(295_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -498,43 +499,50 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_502_000 picoseconds. - Weight::from_parts(3_777_000, 3495) + // Minimum execution time: 3_607_000 picoseconds. + Weight::from_parts(3_760_000, 3495) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 232_000 picoseconds. - Weight::from_parts(277_000, 0) + // Minimum execution time: 271_000 picoseconds. + Weight::from_parts(299_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_293_000 picoseconds. - Weight::from_parts(1_426_000, 0) + // Minimum execution time: 1_320_000 picoseconds. + Weight::from_parts(1_406_000, 0) + } + fn seal_call_data_load() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 250_000 picoseconds. + Weight::from_parts(285_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_input(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 449_000 picoseconds. - Weight::from_parts(446_268, 0) + // Minimum execution time: 411_000 picoseconds. + Weight::from_parts(514_738, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(612_733, 0) + // Minimum execution time: 282_000 picoseconds. + Weight::from_parts(463_520, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(295, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -550,11 +558,11 @@ impl WeightInfo for SubstrateWeight { fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `324 + n * (88 ±0)` - // Estimated: `3789 + n * (2563 ±0)` - // Minimum execution time: 21_822_000 picoseconds. - Weight::from_parts(22_468_601, 3789) - // Standard Error: 7_303 - .saturating_add(Weight::from_parts(4_138_073, 0).saturating_mul(n.into())) + // Estimated: `3791 + n * (2563 ±0)` + // Minimum execution time: 22_960_000 picoseconds. + Weight::from_parts(23_432_764, 3791) + // Standard Error: 12_030 + .saturating_add(Weight::from_parts(4_292_055, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -567,22 +575,22 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_127_000 picoseconds. - Weight::from_parts(4_043_097, 0) - // Standard Error: 3_136 - .saturating_add(Weight::from_parts(209_603, 0).saturating_mul(t.into())) - // Standard Error: 28 - .saturating_add(Weight::from_parts(988, 0).saturating_mul(n.into())) + // Minimum execution time: 4_346_000 picoseconds. + Weight::from_parts(4_208_327, 0) + // Standard Error: 2_509 + .saturating_add(Weight::from_parts(194_145, 0).saturating_mul(t.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(1_084, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 276_000 picoseconds. - Weight::from_parts(1_111_301, 0) + // Minimum execution time: 360_000 picoseconds. + Weight::from_parts(374_000, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(706, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(816, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -590,8 +598,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 7_869_000 picoseconds. - Weight::from_parts(8_190_000, 744) + // Minimum execution time: 8_066_000 picoseconds. + Weight::from_parts(8_425_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -600,8 +608,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 42_793_000 picoseconds. - Weight::from_parts(43_861_000, 10754) + // Minimum execution time: 43_707_000 picoseconds. + Weight::from_parts(44_613_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -610,8 +618,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 8_753_000 picoseconds. - Weight::from_parts(9_235_000, 744) + // Minimum execution time: 9_101_000 picoseconds. + Weight::from_parts(9_425_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -621,8 +629,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 44_446_000 picoseconds. - Weight::from_parts(45_586_000, 10754) + // Minimum execution time: 45_990_000 picoseconds. + Weight::from_parts(46_945_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -634,12 +642,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_214_000 picoseconds. - Weight::from_parts(9_888_060, 247) - // Standard Error: 41 - .saturating_add(Weight::from_parts(151, 0).saturating_mul(n.into())) - // Standard Error: 41 - .saturating_add(Weight::from_parts(315, 0).saturating_mul(o.into())) + // Minimum execution time: 9_229_000 picoseconds. + Weight::from_parts(10_039_961, 247) + // Standard Error: 39 + .saturating_add(Weight::from_parts(359, 0).saturating_mul(n.into())) + // Standard Error: 39 + .saturating_add(Weight::from_parts(424, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -651,10 +659,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_647_000 picoseconds. - Weight::from_parts(9_553_009, 247) - // Standard Error: 48 - .saturating_add(Weight::from_parts(651, 0).saturating_mul(n.into())) + // Minimum execution time: 9_038_000 picoseconds. + Weight::from_parts(9_855_448, 247) + // Standard Error: 55 + .saturating_add(Weight::from_parts(544, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -666,10 +674,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_457_000 picoseconds. - Weight::from_parts(9_199_745, 247) - // Standard Error: 59 - .saturating_add(Weight::from_parts(1_562, 0).saturating_mul(n.into())) + // Minimum execution time: 8_533_000 picoseconds. + Weight::from_parts(9_485_405, 247) + // Standard Error: 60 + .saturating_add(Weight::from_parts(1_436, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -680,10 +688,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_025_000 picoseconds. - Weight::from_parts(8_700_911, 247) - // Standard Error: 49 - .saturating_add(Weight::from_parts(635, 0).saturating_mul(n.into())) + // Minimum execution time: 8_300_000 picoseconds. + Weight::from_parts(8_914_778, 247) + // Standard Error: 46 + .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -694,10 +702,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_346_000 picoseconds. - Weight::from_parts(10_297_284, 247) - // Standard Error: 62 - .saturating_add(Weight::from_parts(1_396, 0).saturating_mul(n.into())) + // Minimum execution time: 9_384_000 picoseconds. + Weight::from_parts(10_500_656, 247) + // Standard Error: 64 + .saturating_add(Weight::from_parts(1_400, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -706,36 +714,36 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_428_000 picoseconds. - Weight::from_parts(1_517_000, 0) + // Minimum execution time: 1_478_000 picoseconds. + Weight::from_parts(1_625_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_868_000 picoseconds. - Weight::from_parts(1_942_000, 0) + // Minimum execution time: 1_842_000 picoseconds. + Weight::from_parts(1_969_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_403_000 picoseconds. - Weight::from_parts(1_539_000, 0) + // Minimum execution time: 1_437_000 picoseconds. + Weight::from_parts(1_557_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_676_000 picoseconds. - Weight::from_parts(1_760_000, 0) + // Minimum execution time: 1_600_000 picoseconds. + Weight::from_parts(1_679_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_119_000 picoseconds. - Weight::from_parts(1_205_000, 0) + // Minimum execution time: 1_114_000 picoseconds. + Weight::from_parts(1_191_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -743,50 +751,50 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_146_000 picoseconds. - Weight::from_parts(2_315_339, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(327, 0).saturating_mul(n.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(366, 0).saturating_mul(o.into())) + // Minimum execution time: 2_326_000 picoseconds. + Weight::from_parts(2_451_799, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(391, 0).saturating_mul(n.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(361, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_950_000 picoseconds. - Weight::from_parts(2_271_073, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(373, 0).saturating_mul(n.into())) + // Minimum execution time: 1_951_000 picoseconds. + Weight::from_parts(2_353_245, 0) + // Standard Error: 16 + .saturating_add(Weight::from_parts(369, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_839_000 picoseconds. - Weight::from_parts(2_049_659, 0) + // Minimum execution time: 1_822_000 picoseconds. + Weight::from_parts(2_059_181, 0) // Standard Error: 14 - .saturating_add(Weight::from_parts(291, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(398, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_716_000 picoseconds. - Weight::from_parts(1_893_932, 0) + // Minimum execution time: 1_697_000 picoseconds. + Weight::from_parts(1_905_887, 0) // Standard Error: 12 - .saturating_add(Weight::from_parts(172, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(215, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_448_000 picoseconds. - Weight::from_parts(2_676_764, 0) + // Minimum execution time: 2_533_000 picoseconds. + Weight::from_parts(2_759_660, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -802,30 +810,32 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1294 + t * (242 ±0)` + // Measured: `1294 + t * (243 ±0)` // Estimated: `4759 + t * (2501 ±0)` - // Minimum execution time: 39_786_000 picoseconds. - Weight::from_parts(41_175_457, 4759) - // Standard Error: 45_251 - .saturating_add(Weight::from_parts(2_375_617, 0).saturating_mul(t.into())) + // Minimum execution time: 43_295_000 picoseconds. + Weight::from_parts(44_592_141, 4759) + // Standard Error: 60_598 + .saturating_add(Weight::from_parts(1_458_798, 0).saturating_mul(t.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(3, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 2501).saturating_mul(t.into())) } + /// Storage: `Revive::ContractInfoOf` (r:1 w:0) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn seal_delegate_call() -> Weight { // Proof Size summary in bytes: - // Measured: `1064` - // Estimated: `4529` - // Minimum execution time: 29_762_000 picoseconds. - Weight::from_parts(31_345_000, 4529) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Measured: `1237` + // Estimated: `4702` + // Minimum execution time: 37_787_000 picoseconds. + Weight::from_parts(38_510_000, 4702) + .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -838,12 +848,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1310` - // Estimated: `4748` - // Minimum execution time: 117_791_000 picoseconds. - Weight::from_parts(105_413_907, 4748) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_038, 0).saturating_mul(i.into())) + // Measured: `1273` + // Estimated: `4736` + // Minimum execution time: 121_346_000 picoseconds. + Weight::from_parts(115_747_843, 4736) + // Standard Error: 10 + .saturating_add(Weight::from_parts(4_189, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -852,64 +862,64 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 638_000 picoseconds. - Weight::from_parts(4_703_710, 0) + // Minimum execution time: 696_000 picoseconds. + Weight::from_parts(3_319_775, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_349, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_500, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_085_000 picoseconds. - Weight::from_parts(3_630_716, 0) + // Minimum execution time: 1_070_000 picoseconds. + Weight::from_parts(4_463_019, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_567, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_689, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 643_000 picoseconds. - Weight::from_parts(3_733_026, 0) + // Minimum execution time: 617_000 picoseconds. + Weight::from_parts(3_175_243, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_492, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_617, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 653_000 picoseconds. - Weight::from_parts(4_627_285, 0) + // Minimum execution time: 616_000 picoseconds. + Weight::from_parts(3_420_409, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_478, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_623, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 45_786_000 picoseconds. - Weight::from_parts(36_383_470, 0) - // Standard Error: 10 - .saturating_add(Weight::from_parts(5_396, 0).saturating_mul(n.into())) + // Minimum execution time: 45_562_000 picoseconds. + Weight::from_parts(34_462_046, 0) + // Standard Error: 11 + .saturating_add(Weight::from_parts(5_259, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 48_140_000 picoseconds. - Weight::from_parts(49_720_000, 0) + // Minimum execution time: 49_472_000 picoseconds. + Weight::from_parts(50_517_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_565_000 picoseconds. - Weight::from_parts(12_704_000, 0) + // Minimum execution time: 12_716_000 picoseconds. + Weight::from_parts(12_812_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -917,8 +927,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 17_208_000 picoseconds. - Weight::from_parts(18_307_000, 3765) + // Minimum execution time: 17_891_000 picoseconds. + Weight::from_parts(18_833_000, 3765) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -928,8 +938,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 13_686_000 picoseconds. - Weight::from_parts(14_186_000, 3803) + // Minimum execution time: 14_523_000 picoseconds. + Weight::from_parts(14_812_000, 3803) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -939,8 +949,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 12_381_000 picoseconds. - Weight::from_parts(13_208_000, 3561) + // Minimum execution time: 13_114_000 picoseconds. + Weight::from_parts(13_567_000, 3561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -949,10 +959,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_118_000 picoseconds. - Weight::from_parts(9_813_514, 0) - // Standard Error: 40 - .saturating_add(Weight::from_parts(71_154, 0).saturating_mul(r.into())) + // Minimum execution time: 8_717_000 picoseconds. + Weight::from_parts(9_983_815, 0) + // Standard Error: 115 + .saturating_add(Weight::from_parts(72_253, 0).saturating_mul(r.into())) } } @@ -964,8 +974,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_818_000 picoseconds. - Weight::from_parts(3_058_000, 1594) + // Minimum execution time: 2_874_000 picoseconds. + Weight::from_parts(3_131_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -975,10 +985,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 15_916_000 picoseconds. - Weight::from_parts(16_132_000, 415) - // Standard Error: 1_482 - .saturating_add(Weight::from_parts(1_185_583, 0).saturating_mul(k.into())) + // Minimum execution time: 16_079_000 picoseconds. + Weight::from_parts(5_747_743, 415) + // Standard Error: 1_130 + .saturating_add(Weight::from_parts(1_181_775, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1000,10 +1010,10 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 88_115_000 picoseconds. - Weight::from_parts(92_075_651, 7442) + // Measured: `1536` + // Estimated: `7476` + // Minimum execution time: 94_513_000 picoseconds. + Weight::from_parts(99_111_938, 7476) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1025,14 +1035,14 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn instantiate_with_code(c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `403` - // Estimated: `6326` - // Minimum execution time: 188_274_000 picoseconds. - Weight::from_parts(157_773_869, 6326) - // Standard Error: 11 - .saturating_add(Weight::from_parts(16, 0).saturating_mul(c.into())) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_464, 0).saturating_mul(i.into())) + // Measured: `416` + // Estimated: `6345` + // Minimum execution time: 195_917_000 picoseconds. + Weight::from_parts(175_835_928, 6345) + // Standard Error: 10 + .saturating_add(Weight::from_parts(10, 0).saturating_mul(c.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(4_554, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1054,11 +1064,11 @@ impl WeightInfo for () { fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `1296` - // Estimated: `4739` - // Minimum execution time: 158_616_000 picoseconds. - Weight::from_parts(134_329_076, 4739) - // Standard Error: 15 - .saturating_add(Weight::from_parts(4_358, 0).saturating_mul(i.into())) + // Estimated: `4753` + // Minimum execution time: 162_583_000 picoseconds. + Weight::from_parts(143_621_658, 4753) + // Standard Error: 16 + .saturating_add(Weight::from_parts(4_499, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1076,10 +1086,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 134_935_000 picoseconds. - Weight::from_parts(141_040_000, 7442) + // Measured: `1536` + // Estimated: `7476` + // Minimum execution time: 145_642_000 picoseconds. + Weight::from_parts(152_866_000, 7476) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1094,8 +1104,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 51_026_000 picoseconds. - Weight::from_parts(53_309_143, 3574) + // Minimum execution time: 51_664_000 picoseconds. + Weight::from_parts(53_863_257, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1109,8 +1119,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 44_338_000 picoseconds. - Weight::from_parts(45_398_000, 3750) + // Minimum execution time: 44_879_000 picoseconds. + Weight::from_parts(46_401_000, 3750) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1122,8 +1132,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 26_420_000 picoseconds. - Weight::from_parts(27_141_000, 6469) + // Minimum execution time: 27_833_000 picoseconds. + Weight::from_parts(29_013_000, 6469) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1135,8 +1145,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 39_735_000 picoseconds. - Weight::from_parts(41_260_000, 3574) + // Minimum execution time: 40_611_000 picoseconds. + Weight::from_parts(41_336_000, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1148,8 +1158,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 32_059_000 picoseconds. - Weight::from_parts(32_776_000, 3521) + // Minimum execution time: 32_576_000 picoseconds. + Weight::from_parts(33_300_000, 3521) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1161,8 +1171,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_553_000 picoseconds. - Weight::from_parts(14_121_000, 3610) + // Minimum execution time: 13_978_000 picoseconds. + Weight::from_parts(14_573_000, 3610) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1170,24 +1180,24 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_392_000 picoseconds. - Weight::from_parts(7_692_248, 0) - // Standard Error: 105 - .saturating_add(Weight::from_parts(180_036, 0).saturating_mul(r.into())) + // Minimum execution time: 6_877_000 picoseconds. + Weight::from_parts(8_471_206, 0) + // Standard Error: 226 + .saturating_add(Weight::from_parts(165_314, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 287_000 picoseconds. - Weight::from_parts(317_000, 0) + // Minimum execution time: 290_000 picoseconds. + Weight::from_parts(345_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 235_000 picoseconds. - Weight::from_parts(288_000, 0) + // Minimum execution time: 243_000 picoseconds. + Weight::from_parts(303_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1195,8 +1205,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_101_000 picoseconds. - Weight::from_parts(10_420_000, 3771) + // Minimum execution time: 10_441_000 picoseconds. + Weight::from_parts(10_812_000, 3771) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -1205,16 +1215,16 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_422_000 picoseconds. - Weight::from_parts(11_829_000, 3868) + // Minimum execution time: 11_403_000 picoseconds. + Weight::from_parts(11_913_000, 3868) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 247_000 picoseconds. - Weight::from_parts(282_000, 0) + // Minimum execution time: 259_000 picoseconds. + Weight::from_parts(306_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1224,44 +1234,44 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_856_000 picoseconds. - Weight::from_parts(15_528_000, 3938) + // Minimum execution time: 14_887_000 picoseconds. + Weight::from_parts(15_625_000, 3938) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 303_000 picoseconds. - Weight::from_parts(361_000, 0) + // Minimum execution time: 315_000 picoseconds. + Weight::from_parts(389_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 253_000 picoseconds. - Weight::from_parts(287_000, 0) + // Minimum execution time: 294_000 picoseconds. + Weight::from_parts(322_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 231_000 picoseconds. - Weight::from_parts(263_000, 0) + // Minimum execution time: 239_000 picoseconds. + Weight::from_parts(299_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` // Minimum execution time: 628_000 picoseconds. - Weight::from_parts(697_000, 0) + Weight::from_parts(703_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `103` // Estimated: `0` - // Minimum execution time: 4_531_000 picoseconds. - Weight::from_parts(4_726_000, 0) + // Minimum execution time: 4_816_000 picoseconds. + Weight::from_parts(5_078_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1271,8 +1281,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 8_787_000 picoseconds. - Weight::from_parts(9_175_000, 3729) + // Minimum execution time: 8_965_000 picoseconds. + Weight::from_parts(9_533_000, 3729) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -1282,10 +1292,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_760_000 picoseconds. - Weight::from_parts(6_591_336, 3703) + // Minimum execution time: 6_174_000 picoseconds. + Weight::from_parts(6_755_842, 3703) // Standard Error: 4 - .saturating_add(Weight::from_parts(628, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(699, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1296,32 +1306,32 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_971_000 picoseconds. - Weight::from_parts(2_206_252, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(529, 0).saturating_mul(n.into())) + // Minimum execution time: 1_977_000 picoseconds. + Weight::from_parts(2_175_653, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(633, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 246_000 picoseconds. - Weight::from_parts(279_000, 0) + // Minimum execution time: 259_000 picoseconds. + Weight::from_parts(298_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 223_000 picoseconds. - Weight::from_parts(274_000, 0) + // Minimum execution time: 277_000 picoseconds. + Weight::from_parts(330_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 213_000 picoseconds. - Weight::from_parts(270_000, 0) + // Minimum execution time: 260_000 picoseconds. + Weight::from_parts(295_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -1329,43 +1339,50 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_502_000 picoseconds. - Weight::from_parts(3_777_000, 3495) + // Minimum execution time: 3_607_000 picoseconds. + Weight::from_parts(3_760_000, 3495) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 232_000 picoseconds. - Weight::from_parts(277_000, 0) + // Minimum execution time: 271_000 picoseconds. + Weight::from_parts(299_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_293_000 picoseconds. - Weight::from_parts(1_426_000, 0) + // Minimum execution time: 1_320_000 picoseconds. + Weight::from_parts(1_406_000, 0) + } + fn seal_call_data_load() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 250_000 picoseconds. + Weight::from_parts(285_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_input(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 449_000 picoseconds. - Weight::from_parts(446_268, 0) + // Minimum execution time: 411_000 picoseconds. + Weight::from_parts(514_738, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(612_733, 0) + // Minimum execution time: 282_000 picoseconds. + Weight::from_parts(463_520, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(295, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1381,11 +1398,11 @@ impl WeightInfo for () { fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `324 + n * (88 ±0)` - // Estimated: `3789 + n * (2563 ±0)` - // Minimum execution time: 21_822_000 picoseconds. - Weight::from_parts(22_468_601, 3789) - // Standard Error: 7_303 - .saturating_add(Weight::from_parts(4_138_073, 0).saturating_mul(n.into())) + // Estimated: `3791 + n * (2563 ±0)` + // Minimum execution time: 22_960_000 picoseconds. + Weight::from_parts(23_432_764, 3791) + // Standard Error: 12_030 + .saturating_add(Weight::from_parts(4_292_055, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -1398,22 +1415,22 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_127_000 picoseconds. - Weight::from_parts(4_043_097, 0) - // Standard Error: 3_136 - .saturating_add(Weight::from_parts(209_603, 0).saturating_mul(t.into())) - // Standard Error: 28 - .saturating_add(Weight::from_parts(988, 0).saturating_mul(n.into())) + // Minimum execution time: 4_346_000 picoseconds. + Weight::from_parts(4_208_327, 0) + // Standard Error: 2_509 + .saturating_add(Weight::from_parts(194_145, 0).saturating_mul(t.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(1_084, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 276_000 picoseconds. - Weight::from_parts(1_111_301, 0) + // Minimum execution time: 360_000 picoseconds. + Weight::from_parts(374_000, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(706, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(816, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -1421,8 +1438,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 7_869_000 picoseconds. - Weight::from_parts(8_190_000, 744) + // Minimum execution time: 8_066_000 picoseconds. + Weight::from_parts(8_425_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1431,8 +1448,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 42_793_000 picoseconds. - Weight::from_parts(43_861_000, 10754) + // Minimum execution time: 43_707_000 picoseconds. + Weight::from_parts(44_613_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1441,8 +1458,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 8_753_000 picoseconds. - Weight::from_parts(9_235_000, 744) + // Minimum execution time: 9_101_000 picoseconds. + Weight::from_parts(9_425_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1452,8 +1469,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 44_446_000 picoseconds. - Weight::from_parts(45_586_000, 10754) + // Minimum execution time: 45_990_000 picoseconds. + Weight::from_parts(46_945_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1465,12 +1482,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_214_000 picoseconds. - Weight::from_parts(9_888_060, 247) - // Standard Error: 41 - .saturating_add(Weight::from_parts(151, 0).saturating_mul(n.into())) - // Standard Error: 41 - .saturating_add(Weight::from_parts(315, 0).saturating_mul(o.into())) + // Minimum execution time: 9_229_000 picoseconds. + Weight::from_parts(10_039_961, 247) + // Standard Error: 39 + .saturating_add(Weight::from_parts(359, 0).saturating_mul(n.into())) + // Standard Error: 39 + .saturating_add(Weight::from_parts(424, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -1482,10 +1499,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_647_000 picoseconds. - Weight::from_parts(9_553_009, 247) - // Standard Error: 48 - .saturating_add(Weight::from_parts(651, 0).saturating_mul(n.into())) + // Minimum execution time: 9_038_000 picoseconds. + Weight::from_parts(9_855_448, 247) + // Standard Error: 55 + .saturating_add(Weight::from_parts(544, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1497,10 +1514,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_457_000 picoseconds. - Weight::from_parts(9_199_745, 247) - // Standard Error: 59 - .saturating_add(Weight::from_parts(1_562, 0).saturating_mul(n.into())) + // Minimum execution time: 8_533_000 picoseconds. + Weight::from_parts(9_485_405, 247) + // Standard Error: 60 + .saturating_add(Weight::from_parts(1_436, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1511,10 +1528,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_025_000 picoseconds. - Weight::from_parts(8_700_911, 247) - // Standard Error: 49 - .saturating_add(Weight::from_parts(635, 0).saturating_mul(n.into())) + // Minimum execution time: 8_300_000 picoseconds. + Weight::from_parts(8_914_778, 247) + // Standard Error: 46 + .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1525,10 +1542,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_346_000 picoseconds. - Weight::from_parts(10_297_284, 247) - // Standard Error: 62 - .saturating_add(Weight::from_parts(1_396, 0).saturating_mul(n.into())) + // Minimum execution time: 9_384_000 picoseconds. + Weight::from_parts(10_500_656, 247) + // Standard Error: 64 + .saturating_add(Weight::from_parts(1_400, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1537,36 +1554,36 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_428_000 picoseconds. - Weight::from_parts(1_517_000, 0) + // Minimum execution time: 1_478_000 picoseconds. + Weight::from_parts(1_625_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_868_000 picoseconds. - Weight::from_parts(1_942_000, 0) + // Minimum execution time: 1_842_000 picoseconds. + Weight::from_parts(1_969_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_403_000 picoseconds. - Weight::from_parts(1_539_000, 0) + // Minimum execution time: 1_437_000 picoseconds. + Weight::from_parts(1_557_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_676_000 picoseconds. - Weight::from_parts(1_760_000, 0) + // Minimum execution time: 1_600_000 picoseconds. + Weight::from_parts(1_679_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_119_000 picoseconds. - Weight::from_parts(1_205_000, 0) + // Minimum execution time: 1_114_000 picoseconds. + Weight::from_parts(1_191_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -1574,50 +1591,50 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_146_000 picoseconds. - Weight::from_parts(2_315_339, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(327, 0).saturating_mul(n.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(366, 0).saturating_mul(o.into())) + // Minimum execution time: 2_326_000 picoseconds. + Weight::from_parts(2_451_799, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(391, 0).saturating_mul(n.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(361, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_950_000 picoseconds. - Weight::from_parts(2_271_073, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(373, 0).saturating_mul(n.into())) + // Minimum execution time: 1_951_000 picoseconds. + Weight::from_parts(2_353_245, 0) + // Standard Error: 16 + .saturating_add(Weight::from_parts(369, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_839_000 picoseconds. - Weight::from_parts(2_049_659, 0) + // Minimum execution time: 1_822_000 picoseconds. + Weight::from_parts(2_059_181, 0) // Standard Error: 14 - .saturating_add(Weight::from_parts(291, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(398, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_716_000 picoseconds. - Weight::from_parts(1_893_932, 0) + // Minimum execution time: 1_697_000 picoseconds. + Weight::from_parts(1_905_887, 0) // Standard Error: 12 - .saturating_add(Weight::from_parts(172, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(215, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_448_000 picoseconds. - Weight::from_parts(2_676_764, 0) + // Minimum execution time: 2_533_000 picoseconds. + Weight::from_parts(2_759_660, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1633,30 +1650,32 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1294 + t * (242 ±0)` + // Measured: `1294 + t * (243 ±0)` // Estimated: `4759 + t * (2501 ±0)` - // Minimum execution time: 39_786_000 picoseconds. - Weight::from_parts(41_175_457, 4759) - // Standard Error: 45_251 - .saturating_add(Weight::from_parts(2_375_617, 0).saturating_mul(t.into())) + // Minimum execution time: 43_295_000 picoseconds. + Weight::from_parts(44_592_141, 4759) + // Standard Error: 60_598 + .saturating_add(Weight::from_parts(1_458_798, 0).saturating_mul(t.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(3, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 2501).saturating_mul(t.into())) } + /// Storage: `Revive::ContractInfoOf` (r:1 w:0) + /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) /// Storage: `Revive::CodeInfoOf` (r:1 w:0) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) /// Storage: `Revive::PristineCode` (r:1 w:0) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) fn seal_delegate_call() -> Weight { // Proof Size summary in bytes: - // Measured: `1064` - // Estimated: `4529` - // Minimum execution time: 29_762_000 picoseconds. - Weight::from_parts(31_345_000, 4529) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + // Measured: `1237` + // Estimated: `4702` + // Minimum execution time: 37_787_000 picoseconds. + Weight::from_parts(38_510_000, 4702) + .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1669,12 +1688,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1310` - // Estimated: `4748` - // Minimum execution time: 117_791_000 picoseconds. - Weight::from_parts(105_413_907, 4748) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_038, 0).saturating_mul(i.into())) + // Measured: `1273` + // Estimated: `4736` + // Minimum execution time: 121_346_000 picoseconds. + Weight::from_parts(115_747_843, 4736) + // Standard Error: 10 + .saturating_add(Weight::from_parts(4_189, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1683,64 +1702,64 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 638_000 picoseconds. - Weight::from_parts(4_703_710, 0) + // Minimum execution time: 696_000 picoseconds. + Weight::from_parts(3_319_775, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_349, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_500, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_085_000 picoseconds. - Weight::from_parts(3_630_716, 0) + // Minimum execution time: 1_070_000 picoseconds. + Weight::from_parts(4_463_019, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_567, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_689, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 643_000 picoseconds. - Weight::from_parts(3_733_026, 0) + // Minimum execution time: 617_000 picoseconds. + Weight::from_parts(3_175_243, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_492, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_617, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 653_000 picoseconds. - Weight::from_parts(4_627_285, 0) + // Minimum execution time: 616_000 picoseconds. + Weight::from_parts(3_420_409, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_478, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_623, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 45_786_000 picoseconds. - Weight::from_parts(36_383_470, 0) - // Standard Error: 10 - .saturating_add(Weight::from_parts(5_396, 0).saturating_mul(n.into())) + // Minimum execution time: 45_562_000 picoseconds. + Weight::from_parts(34_462_046, 0) + // Standard Error: 11 + .saturating_add(Weight::from_parts(5_259, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 48_140_000 picoseconds. - Weight::from_parts(49_720_000, 0) + // Minimum execution time: 49_472_000 picoseconds. + Weight::from_parts(50_517_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_565_000 picoseconds. - Weight::from_parts(12_704_000, 0) + // Minimum execution time: 12_716_000 picoseconds. + Weight::from_parts(12_812_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1748,8 +1767,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 17_208_000 picoseconds. - Weight::from_parts(18_307_000, 3765) + // Minimum execution time: 17_891_000 picoseconds. + Weight::from_parts(18_833_000, 3765) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1759,8 +1778,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 13_686_000 picoseconds. - Weight::from_parts(14_186_000, 3803) + // Minimum execution time: 14_523_000 picoseconds. + Weight::from_parts(14_812_000, 3803) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1770,8 +1789,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 12_381_000 picoseconds. - Weight::from_parts(13_208_000, 3561) + // Minimum execution time: 13_114_000 picoseconds. + Weight::from_parts(13_567_000, 3561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1780,9 +1799,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_118_000 picoseconds. - Weight::from_parts(9_813_514, 0) - // Standard Error: 40 - .saturating_add(Weight::from_parts(71_154, 0).saturating_mul(r.into())) + // Minimum execution time: 8_717_000 picoseconds. + Weight::from_parts(9_983_815, 0) + // Standard Error: 115 + .saturating_add(Weight::from_parts(72_253, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 400a12879363..c6b9ef9d4fa2 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -436,6 +436,21 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the input data. fn input(output: &mut &mut [u8]); + /// Stores the U256 value at given `offset` from the input passed by the caller + /// into the supplied buffer. + /// + /// # Note + /// - If `offset` is out of bounds, a value of zero will be returned. + /// - If `offset` is in bounds but there is not enough call data, the available data + /// is right-padded in order to fill a whole U256 value. + /// - The data written to `output` is a little endian U256 integer value. + /// + /// # Parameters + /// + /// - `output`: A reference to the fixed output data buffer to write the value. + /// - `offset`: The offset (index) into the call data. + fn call_data_load(output: &mut [u8; 32], offset: u32); + /// Instantiate a contract with the specified code hash. /// /// This function creates an account and executes the constructor defined in the code specified diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index b76320718a69..a208fef7055a 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -63,6 +63,7 @@ mod sys { pub fn instantiate(ptr: *const u8) -> ReturnCode; pub fn terminate(beneficiary_ptr: *const u8); pub fn input(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn call_data_load(out_ptr: *mut u8, offset: u32); pub fn seal_return(flags: u32, data_ptr: *const u8, data_len: u32); pub fn caller(out_ptr: *mut u8); pub fn origin(out_ptr: *mut u8); @@ -449,6 +450,10 @@ impl HostFn for HostFnImpl { extract_from_slice(output, output_len as usize); } + fn call_data_load(out_ptr: &mut [u8; 32], offset: u32) { + unsafe { sys::call_data_load(out_ptr.as_mut_ptr(), offset) }; + } + fn return_value(flags: ReturnFlags, return_value: &[u8]) -> ! { unsafe { sys::seal_return(flags.bits(), return_value.as_ptr(), return_value.len() as u32) } panic!("seal_return does not return"); From 7cc5cdd0d98ae3466dc33b339197c169cf241fc0 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Thu, 12 Dec 2024 15:18:22 +0200 Subject: [PATCH 151/340] omni-node: add metadata checks for runtime/parachain compatibility (#6450) # Description Get runtime's metadata, parse it and verify pallets list for a pallet named `ParachainSystem` (for now), and block number to be the same for both node and runtime. Ideally we'll add other pallets checks too, at least a small set of pallets we think right away as mandatory for parachain compatibility. Closes: #5565 ## Integration Runtime devs must be made aware that to be fully compatible with Omni Node, certain naming conventions should be respected when defining pallets (e.g we verify parachain-system pallet existence by searching for a pallet with `name` `ParachainSystem` in runtime's metadata). Not finding such a pallet will not influence the functionality yet, but by doing these checks we could provide useful feedback for runtimes that are clearly not implementing what's required for full parachain compatibility with Omni Node. ## Review Notes - [x] parachain system check - [x] check frame_system's metadata to ensure the block number in there is the same as the one in the node side - [x] add tests for the previous checking logic - [x] update omni node polkadot-sdk docs to make these conventions visible. - [ ] add more pallets checks? --------- Signed-off-by: Iulian Barbu Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> --- Cargo.lock | 29 ++ Cargo.toml | 3 + cumulus/polkadot-omni-node/lib/Cargo.toml | 16 +- .../lib/src/common/runtime.rs | 147 +++++++++- .../polkadot-omni-node/lib/src/common/spec.rs | 272 +++++++++--------- docs/sdk/src/reference_docs/omni_node.rs | 16 ++ prdoc/pr_6450.prdoc | 21 ++ substrate/client/runtime-utilities/Cargo.toml | 36 +++ .../client/runtime-utilities/src/error.rs | 35 +++ .../runtime-utilities/src/lib.rs} | 88 +++--- .../utils/frame/benchmarking-cli/Cargo.toml | 1 + .../utils/frame/benchmarking-cli/src/lib.rs | 1 - .../benchmarking-cli/src/overhead/command.rs | 28 +- .../benchmarking-cli/src/overhead/mod.rs | 1 - umbrella/Cargo.toml | 7 +- umbrella/src/lib.rs | 4 + 16 files changed, 510 insertions(+), 195 deletions(-) create mode 100644 prdoc/pr_6450.prdoc create mode 100644 substrate/client/runtime-utilities/Cargo.toml create mode 100644 substrate/client/runtime-utilities/src/error.rs rename substrate/{utils/frame/benchmarking-cli/src/overhead/runtime_utilities.rs => client/runtime-utilities/src/lib.rs} (60%) diff --git a/Cargo.lock b/Cargo.lock index 9b023a38cb2a..afd7507e7ab9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7172,6 +7172,7 @@ dependencies = [ "sc-client-db", "sc-executor 0.32.0", "sc-executor-common 0.29.0", + "sc-runtime-utilities", "sc-service", "sc-sysinfo", "serde", @@ -18095,6 +18096,7 @@ dependencies = [ "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", "cumulus-relay-chain-interface", + "cumulus-test-runtime", "docify", "frame-benchmarking 28.0.0", "frame-benchmarking-cli", @@ -18123,29 +18125,36 @@ dependencies = [ "sc-executor 0.32.0", "sc-network", "sc-rpc", + "sc-runtime-utilities", "sc-service", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", + "scale-info", "serde", "serde_json", "sp-api 26.0.0", "sp-block-builder 26.0.0", "sp-consensus-aura 0.32.0", "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", "sp-genesis-builder 0.8.0", "sp-inherents 26.0.0", + "sp-io 30.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-session 27.0.0", + "sp-storage 19.0.0", "sp-timestamp 26.0.0", "sp-transaction-pool 26.0.0", "sp-version 29.0.0", + "sp-wasm-interface 20.0.0", "sp-weights 27.0.0", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-state-trie-migration-rpc", + "subxt-metadata", "tokio", "wait-timeout", ] @@ -18883,6 +18892,7 @@ dependencies = [ "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", + "sc-runtime-utilities", "sc-service", "sc-state-db", "sc-statement-store", @@ -23505,6 +23515,25 @@ dependencies = [ "substrate-wasm-builder 17.0.0", ] +[[package]] +name = "sc-runtime-utilities" +version = "0.1.0" +dependencies = [ + "cumulus-primitives-proof-size-hostfunction 0.2.0", + "cumulus-test-runtime", + "parity-scale-codec", + "sc-executor 0.32.0", + "sc-executor-common 0.29.0", + "sp-core 28.0.0", + "sp-crypto-hashing 0.1.0", + "sp-io 30.0.0", + "sp-state-machine 0.35.0", + "sp-version 29.0.0", + "sp-wasm-interface 20.0.0", + "subxt", + "thiserror", +] + [[package]] name = "sc-service" version = "0.35.0" diff --git a/Cargo.toml b/Cargo.toml index e76af28ecc31..089ba3cef208 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -293,6 +293,7 @@ members = [ "substrate/client/rpc-api", "substrate/client/rpc-servers", "substrate/client/rpc-spec-v2", + "substrate/client/runtime-utilities", "substrate/client/service", "substrate/client/service/test", "substrate/client/state-db", @@ -1183,6 +1184,7 @@ sc-rpc-api = { path = "substrate/client/rpc-api", default-features = false } sc-rpc-server = { path = "substrate/client/rpc-servers", default-features = false } sc-rpc-spec-v2 = { path = "substrate/client/rpc-spec-v2", default-features = false } sc-runtime-test = { path = "substrate/client/executor/runtime-test" } +sc-runtime-utilities = { path = "substrate/client/runtime-utilities", default-features = true } sc-service = { path = "substrate/client/service", default-features = false } sc-service-test = { path = "substrate/client/service/test" } sc-state-db = { path = "substrate/client/state-db", default-features = false } @@ -1316,6 +1318,7 @@ substrate-test-runtime-transaction-pool = { path = "substrate/test-utils/runtime substrate-test-utils = { path = "substrate/test-utils" } substrate-wasm-builder = { path = "substrate/utils/wasm-builder", default-features = false } subxt = { version = "0.38", default-features = false } +subxt-metadata = { version = "0.38.0", default-features = false } subxt-signer = { version = "0.38" } syn = { version = "2.0.87" } sysinfo = { version = "0.30" } diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index cca4ac3b2b69..4d003a69456e 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -28,10 +28,13 @@ docify = { workspace = true } # Local jsonrpsee = { features = ["server"], workspace = true } parachains-common = { workspace = true, default-features = true } +scale-info = { workspace = true } +subxt-metadata = { workspace = true, default-features = true } # Substrate frame-benchmarking = { optional = true, workspace = true, default-features = true } frame-benchmarking-cli = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true } sp-runtime = { workspace = true } sp-core = { workspace = true, default-features = true } sp-session = { workspace = true, default-features = true } @@ -57,12 +60,16 @@ sc-rpc = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } sp-weights = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } +sc-runtime-utilities = { workspace = true, default-features = true } +sp-storage = { workspace = true, default-features = true } frame-system-rpc-runtime-api = { workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-consensus-aura = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-wasm-interface = { workspace = true, default-features = true } sc-consensus-manual-seal = { workspace = true, default-features = true } sc-sysinfo = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } @@ -93,15 +100,12 @@ assert_cmd = { workspace = true } nix = { features = ["signal"], workspace = true } tokio = { version = "1.32.0", features = ["macros", "parking_lot", "time"] } wait-timeout = { workspace = true } +cumulus-test-runtime = { workspace = true } [features] default = [] -rococo-native = [ - "polkadot-cli/rococo-native", -] -westend-native = [ - "polkadot-cli/westend-native", -] +rococo-native = ["polkadot-cli/rococo-native"] +westend-native = ["polkadot-cli/westend-native"] runtime-benchmarks = [ "cumulus-primitives-core/runtime-benchmarks", "frame-benchmarking-cli/runtime-benchmarks", diff --git a/cumulus/polkadot-omni-node/lib/src/common/runtime.rs b/cumulus/polkadot-omni-node/lib/src/common/runtime.rs index 509d13b9d7a2..2a95f41495a6 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/runtime.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/runtime.rs @@ -16,7 +16,19 @@ //! Runtime parameters. +use codec::Decode; +use cumulus_client_service::ParachainHostFunctions; use sc_chain_spec::ChainSpec; +use sc_executor::WasmExecutor; +use sc_runtime_utilities::fetch_latest_metadata_from_code_blob; +use scale_info::{form::PortableForm, TypeDef, TypeDefPrimitive}; +use std::fmt::Display; +use subxt_metadata::{Metadata, StorageEntryType}; + +/// Expected parachain system pallet runtime type name. +pub const DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME: &str = "ParachainSystem"; +/// Expected frame system pallet runtime type name. +pub const DEFAULT_FRAME_SYSTEM_PALLET_NAME: &str = "System"; /// The Aura ID used by the Aura consensus #[derive(PartialEq)] @@ -35,7 +47,7 @@ pub enum Consensus { } /// The choice of block number for the parachain omni-node. -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] pub enum BlockNumber { /// u32 U32, @@ -43,6 +55,34 @@ pub enum BlockNumber { U64, } +impl Display for BlockNumber { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BlockNumber::U32 => write!(f, "u32"), + BlockNumber::U64 => write!(f, "u64"), + } + } +} + +impl Into for BlockNumber { + fn into(self) -> TypeDefPrimitive { + match self { + BlockNumber::U32 => TypeDefPrimitive::U32, + BlockNumber::U64 => TypeDefPrimitive::U64, + } + } +} + +impl BlockNumber { + fn from_type_def(type_def: &TypeDef) -> Option { + match type_def { + TypeDef::Primitive(TypeDefPrimitive::U32) => Some(BlockNumber::U32), + TypeDef::Primitive(TypeDefPrimitive::U64) => Some(BlockNumber::U64), + _ => None, + } + } +} + /// Helper enum listing the supported Runtime types #[derive(PartialEq)] pub enum Runtime { @@ -62,7 +102,108 @@ pub trait RuntimeResolver { pub struct DefaultRuntimeResolver; impl RuntimeResolver for DefaultRuntimeResolver { - fn runtime(&self, _chain_spec: &dyn ChainSpec) -> sc_cli::Result { - Ok(Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519))) + fn runtime(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result { + let metadata_inspector = MetadataInspector::new(chain_spec)?; + let block_number = match metadata_inspector.block_number() { + Some(inner) => inner, + None => { + log::warn!( + r#"⚠️ There isn't a runtime type named `System`, corresponding to the `frame-system` + pallet (https://docs.rs/frame-system/latest/frame_system/). Please check Omni Node docs for runtime conventions: + https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html#runtime-conventions. + Note: We'll assume a block number size of `u32`."# + ); + BlockNumber::U32 + }, + }; + + if !metadata_inspector.pallet_exists(DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME) { + log::warn!( + r#"⚠️ The parachain system pallet (https://docs.rs/crate/cumulus-pallet-parachain-system/latest) is + missing from the runtime’s metadata. Please check Omni Node docs for runtime conventions: + https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html#runtime-conventions."# + ); + } + + Ok(Runtime::Omni(block_number, Consensus::Aura(AuraConsensusId::Sr25519))) + } +} + +struct MetadataInspector(Metadata); + +impl MetadataInspector { + fn new(chain_spec: &dyn ChainSpec) -> Result { + MetadataInspector::fetch_metadata(chain_spec).map(MetadataInspector) + } + + fn pallet_exists(&self, name: &str) -> bool { + self.0.pallet_by_name(name).is_some() + } + + fn block_number(&self) -> Option { + let pallet_metadata = self.0.pallet_by_name(DEFAULT_FRAME_SYSTEM_PALLET_NAME); + pallet_metadata + .and_then(|inner| inner.storage()) + .and_then(|inner| inner.entry_by_name("Number")) + .and_then(|number_ty| match number_ty.entry_type() { + StorageEntryType::Plain(ty_id) => Some(ty_id), + _ => None, + }) + .and_then(|ty_id| self.0.types().resolve(*ty_id)) + .and_then(|portable_type| BlockNumber::from_type_def(&portable_type.type_def)) + } + + fn fetch_metadata(chain_spec: &dyn ChainSpec) -> Result { + let mut storage = chain_spec.build_storage()?; + let code_bytes = storage + .top + .remove(sp_storage::well_known_keys::CODE) + .ok_or("chain spec genesis does not contain code")?; + let opaque_metadata = fetch_latest_metadata_from_code_blob( + &WasmExecutor::::builder() + .with_allow_missing_host_functions(true) + .build(), + sp_runtime::Cow::Borrowed(code_bytes.as_slice()), + ) + .map_err(|err| err.to_string())?; + + Metadata::decode(&mut (*opaque_metadata).as_slice()).map_err(Into::into) + } +} + +#[cfg(test)] +mod tests { + use crate::runtime::{ + BlockNumber, MetadataInspector, DEFAULT_FRAME_SYSTEM_PALLET_NAME, + DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME, + }; + use codec::Decode; + use cumulus_client_service::ParachainHostFunctions; + use sc_executor::WasmExecutor; + use sc_runtime_utilities::fetch_latest_metadata_from_code_blob; + + fn cumulus_test_runtime_metadata() -> subxt_metadata::Metadata { + let opaque_metadata = fetch_latest_metadata_from_code_blob( + &WasmExecutor::::builder() + .with_allow_missing_host_functions(true) + .build(), + sp_runtime::Cow::Borrowed(cumulus_test_runtime::WASM_BINARY.unwrap()), + ) + .unwrap(); + + subxt_metadata::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap() + } + + #[test] + fn test_pallet_exists() { + let metadata_inspector = MetadataInspector(cumulus_test_runtime_metadata()); + assert!(metadata_inspector.pallet_exists(DEFAULT_PARACHAIN_SYSTEM_PALLET_NAME)); + assert!(metadata_inspector.pallet_exists(DEFAULT_FRAME_SYSTEM_PALLET_NAME)); + } + + #[test] + fn test_runtime_block_number() { + let metadata_inspector = MetadataInspector(cumulus_test_runtime_metadata()); + assert_eq!(metadata_inspector.block_number().unwrap(), BlockNumber::U32); } } diff --git a/cumulus/polkadot-omni-node/lib/src/common/spec.rs b/cumulus/polkadot-omni-node/lib/src/common/spec.rs index 259f89049c92..38f0e7d72881 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/spec.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/spec.rs @@ -208,151 +208,151 @@ pub(crate) trait NodeSpec: BaseNodeSpec { where Net: NetworkBackend, { - Box::pin( - async move { - let parachain_config = prepare_node_config(parachain_config); - - let params = Self::new_partial(¶chain_config)?; - let (block_import, mut telemetry, telemetry_worker_handle) = params.other; - - let client = params.client.clone(); - let backend = params.backend.clone(); - - let mut task_manager = params.task_manager; - let (relay_chain_interface, collator_key) = build_relay_chain_interface( - polkadot_config, - ¶chain_config, - telemetry_worker_handle, - &mut task_manager, - collator_options.clone(), - hwbench.clone(), - ) - .await - .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?; - - let validator = parachain_config.role.is_authority(); - let prometheus_registry = parachain_config.prometheus_registry().cloned(); - let transaction_pool = params.transaction_pool.clone(); - let import_queue_service = params.import_queue.service(); - let net_config = FullNetworkConfiguration::<_, _, Net>::new( - ¶chain_config.network, - prometheus_registry.clone(), - ); - - let (network, system_rpc_tx, tx_handler_controller, sync_service) = - build_network(BuildNetworkParams { - parachain_config: ¶chain_config, - net_config, - client: client.clone(), - transaction_pool: transaction_pool.clone(), - para_id, - spawn_handle: task_manager.spawn_handle(), - relay_chain_interface: relay_chain_interface.clone(), - import_queue: params.import_queue, - sybil_resistance_level: Self::SYBIL_RESISTANCE, - }) - .await?; - - let rpc_builder = { - let client = client.clone(); - let transaction_pool = transaction_pool.clone(); - let backend_for_rpc = backend.clone(); - - Box::new(move |_| { - Self::BuildRpcExtensions::build_rpc_extensions( - client.clone(), - backend_for_rpc.clone(), - transaction_pool.clone(), - ) - }) - }; - - sc_service::spawn_tasks(sc_service::SpawnTasksParams { - rpc_builder, + let fut = async move { + let parachain_config = prepare_node_config(parachain_config); + + let params = Self::new_partial(¶chain_config)?; + let (block_import, mut telemetry, telemetry_worker_handle) = params.other; + let client = params.client.clone(); + let backend = params.backend.clone(); + let mut task_manager = params.task_manager; + let (relay_chain_interface, collator_key) = build_relay_chain_interface( + polkadot_config, + ¶chain_config, + telemetry_worker_handle, + &mut task_manager, + collator_options.clone(), + hwbench.clone(), + ) + .await + .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?; + + let validator = parachain_config.role.is_authority(); + let prometheus_registry = parachain_config.prometheus_registry().cloned(); + let transaction_pool = params.transaction_pool.clone(); + let import_queue_service = params.import_queue.service(); + let net_config = FullNetworkConfiguration::<_, _, Net>::new( + ¶chain_config.network, + prometheus_registry.clone(), + ); + + let (network, system_rpc_tx, tx_handler_controller, sync_service) = + build_network(BuildNetworkParams { + parachain_config: ¶chain_config, + net_config, client: client.clone(), transaction_pool: transaction_pool.clone(), - task_manager: &mut task_manager, - config: parachain_config, - keystore: params.keystore_container.keystore(), - backend: backend.clone(), - network: network.clone(), - sync_service: sync_service.clone(), - system_rpc_tx, - tx_handler_controller, - telemetry: telemetry.as_mut(), - })?; - - if let Some(hwbench) = hwbench { - sc_sysinfo::print_hwbench(&hwbench); - if validator { - warn_if_slow_hardware(&hwbench); - } - - if let Some(ref mut telemetry) = telemetry { - let telemetry_handle = telemetry.handle(); - task_manager.spawn_handle().spawn( - "telemetry_hwbench", - None, - sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), - ); - } - } - - let announce_block = { - let sync_service = sync_service.clone(); - Arc::new(move |hash, data| sync_service.announce_block(hash, data)) - }; - - let relay_chain_slot_duration = Duration::from_secs(6); - - let overseer_handle = relay_chain_interface - .overseer_handle() - .map_err(|e| sc_service::Error::Application(Box::new(e)))?; - - start_relay_chain_tasks(StartRelayChainTasksParams { - client: client.clone(), - announce_block: announce_block.clone(), para_id, + spawn_handle: task_manager.spawn_handle(), relay_chain_interface: relay_chain_interface.clone(), - task_manager: &mut task_manager, - da_recovery_profile: if validator { - DARecoveryProfile::Collator - } else { - DARecoveryProfile::FullNode - }, - import_queue: import_queue_service, - relay_chain_slot_duration, - recovery_handle: Box::new(overseer_handle.clone()), - sync_service, - })?; - - if validator { - Self::StartConsensus::start_consensus( + import_queue: params.import_queue, + sybil_resistance_level: Self::SYBIL_RESISTANCE, + }) + .await?; + + let rpc_builder = { + let client = client.clone(); + let transaction_pool = transaction_pool.clone(); + let backend_for_rpc = backend.clone(); + + Box::new(move |_| { + Self::BuildRpcExtensions::build_rpc_extensions( client.clone(), - block_import, - prometheus_registry.as_ref(), - telemetry.as_ref().map(|t| t.handle()), - &task_manager, - relay_chain_interface.clone(), - transaction_pool, - params.keystore_container.keystore(), - relay_chain_slot_duration, - para_id, - collator_key.expect("Command line arguments do not allow this. qed"), - overseer_handle, - announce_block, - backend.clone(), - node_extra_args, - )?; + backend_for_rpc.clone(), + transaction_pool.clone(), + ) + }) + }; + + sc_service::spawn_tasks(sc_service::SpawnTasksParams { + rpc_builder, + client: client.clone(), + transaction_pool: transaction_pool.clone(), + task_manager: &mut task_manager, + config: parachain_config, + keystore: params.keystore_container.keystore(), + backend: backend.clone(), + network: network.clone(), + sync_service: sync_service.clone(), + system_rpc_tx, + tx_handler_controller, + telemetry: telemetry.as_mut(), + })?; + + if let Some(hwbench) = hwbench { + sc_sysinfo::print_hwbench(&hwbench); + if validator { + warn_if_slow_hardware(&hwbench); } - Ok(task_manager) + if let Some(ref mut telemetry) = telemetry { + let telemetry_handle = telemetry.handle(); + task_manager.spawn_handle().spawn( + "telemetry_hwbench", + None, + sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), + ); + } } - .instrument(sc_tracing::tracing::info_span!( + + let announce_block = { + let sync_service = sync_service.clone(); + Arc::new(move |hash, data| sync_service.announce_block(hash, data)) + }; + + let relay_chain_slot_duration = Duration::from_secs(6); + + let overseer_handle = relay_chain_interface + .overseer_handle() + .map_err(|e| sc_service::Error::Application(Box::new(e)))?; + + start_relay_chain_tasks(StartRelayChainTasksParams { + client: client.clone(), + announce_block: announce_block.clone(), + para_id, + relay_chain_interface: relay_chain_interface.clone(), + task_manager: &mut task_manager, + da_recovery_profile: if validator { + DARecoveryProfile::Collator + } else { + DARecoveryProfile::FullNode + }, + import_queue: import_queue_service, + relay_chain_slot_duration, + recovery_handle: Box::new(overseer_handle.clone()), + sync_service, + })?; + + if validator { + Self::StartConsensus::start_consensus( + client.clone(), + block_import, + prometheus_registry.as_ref(), + telemetry.as_ref().map(|t| t.handle()), + &task_manager, + relay_chain_interface.clone(), + transaction_pool, + params.keystore_container.keystore(), + relay_chain_slot_duration, + para_id, + collator_key.expect("Command line arguments do not allow this. qed"), + overseer_handle, + announce_block, + backend.clone(), + node_extra_args, + )?; + } + + Ok(task_manager) + }; + + Box::pin(Instrument::instrument( + fut, + sc_tracing::tracing::info_span!( sc_tracing::logging::PREFIX_LOG_SPAN, - name = "Parachain", - )), - ) + name = "Parachain" + ), + )) } } diff --git a/docs/sdk/src/reference_docs/omni_node.rs b/docs/sdk/src/reference_docs/omni_node.rs index 44d63704a458..150755fb29a2 100644 --- a/docs/sdk/src/reference_docs/omni_node.rs +++ b/docs/sdk/src/reference_docs/omni_node.rs @@ -177,9 +177,25 @@ //! [This](https://github.com/paritytech/polkadot-sdk/issues/5565) future improvement to OmniNode //! aims to make such checks automatic. //! +//! ### Runtime conventions +//! +//! The Omni Node needs to make some assumptions about the runtime. During startup, the node fetches +//! the runtime metadata and asserts that the runtime represents a compatible parachain. +//! The checks are best effort and will generate warning level logs in the Omni Node log file on +//! failure. +//! +//! The list of checks may evolve in the future and for now only few rules are implemented: +//! * runtimes must define a type for [`cumulus-pallet-parachain-system`], which is recommended to +//! be named as `ParachainSystem`. +//! * runtimes must define a type for [`frame-system`] pallet, which is recommended to be named as +//! `System`. The configured [`block number`] here will be used by Omni Node to configure AURA +//! accordingly. //! //! [`templates`]: crate::polkadot_sdk::templates //! [`parachain-template`]: https://github.com/paritytech/polkadot-sdk-parachain-template //! [`--dev-block-time`]: polkadot_omni_node_lib::cli::Cli::dev_block_time //! [`polkadot-omni-node`]: https://crates.io/crates/polkadot-omni-node //! [`chain-spec-builder`]: https://crates.io/crates/staging-chain-spec-builder +//! [`cumulus-pallet-parachain-system`]: https://docs.rs/cumulus-pallet-parachain-system/latest/cumulus_pallet_parachain_system/ +//! [`frame-system`]: https://docs.rs/frame-system/latest/frame_system/ +//! [`block number`]: https://docs.rs/frame-system/latest/frame_system/pallet/storage_types/struct.Number.html diff --git a/prdoc/pr_6450.prdoc b/prdoc/pr_6450.prdoc new file mode 100644 index 000000000000..a9e927e45106 --- /dev/null +++ b/prdoc/pr_6450.prdoc @@ -0,0 +1,21 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add omni-node checks for runtime parachain compatibility + +doc: + - audience: [ Node Dev, Runtime Dev ] + description: | + OmniNode parses runtime metadata and checks against the existence of `cumulus-pallet-parachain-system` + and `frame-system`, by filtering pallets by names: `ParachainSystem` and `System`. It also checks the + `frame-system` pallet storage `Number` type, and then uses it to configure AURA if `u32` or `u64`. + +crates: + - name: polkadot-omni-node-lib + bump: minor + - name: polkadot-sdk + bump: minor + - name: sc-runtime-utilities + bump: patch + - name: frame-benchmarking-cli + bump: major diff --git a/substrate/client/runtime-utilities/Cargo.toml b/substrate/client/runtime-utilities/Cargo.toml new file mode 100644 index 000000000000..5e026a5eff1a --- /dev/null +++ b/substrate/client/runtime-utilities/Cargo.toml @@ -0,0 +1,36 @@ +[package] +description = "Substrate client utilities for frame runtime functions calls." +name = "sc-runtime-utilities" +version = "0.1.0" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true +documentation = "https://docs.rs/sc-metadata" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true, default-features = true } + +sc-executor = { workspace = true, default-features = true } +sc-executor-common = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true, default-features = true } +sp-wasm-interface = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } + + +thiserror = { workspace = true } + +[dev-dependencies] +cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } +cumulus-test-runtime = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +subxt = { workspace = true, features = ["native"] } diff --git a/substrate/client/runtime-utilities/src/error.rs b/substrate/client/runtime-utilities/src/error.rs new file mode 100644 index 000000000000..a0f1e45a5e57 --- /dev/null +++ b/substrate/client/runtime-utilities/src/error.rs @@ -0,0 +1,35 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +//! Errors types of runtime utilities. + +/// Generic result for the runtime utilities. +pub type Result = std::result::Result; + +/// Error type for the runtime utilities. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("Scale codec error: {0}")] + ScaleCodec(#[from] codec::Error), + #[error("Opaque metadata not found")] + OpaqueMetadataNotFound, + #[error("Stable metadata version not found")] + StableMetadataVersionNotFound, + #[error("WASM executor error: {0}")] + Executor(#[from] sc_executor_common::error::Error), +} diff --git a/substrate/utils/frame/benchmarking-cli/src/overhead/runtime_utilities.rs b/substrate/client/runtime-utilities/src/lib.rs similarity index 60% rename from substrate/utils/frame/benchmarking-cli/src/overhead/runtime_utilities.rs rename to substrate/client/runtime-utilities/src/lib.rs index 3081197dc033..1ae3e2f1105a 100644 --- a/substrate/utils/frame/benchmarking-cli/src/overhead/runtime_utilities.rs +++ b/substrate/client/runtime-utilities/src/lib.rs @@ -1,21 +1,29 @@ // This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Substrate client runtime utilities. +//! +//! Provides convenient APIs to ease calling functions contained by a FRAME +//! runtime WASM blob. +#![warn(missing_docs)] use codec::{Decode, Encode}; +use error::{Error, Result}; use sc_executor::WasmExecutor; use sp_core::{ traits::{CallContext, CodeExecutor, FetchRuntimeCode, RuntimeCode}, @@ -25,41 +33,35 @@ use sp_state_machine::BasicExternalities; use sp_wasm_interface::HostFunctions; use std::borrow::Cow; +pub mod error; + /// Fetches the latest metadata from the given runtime blob. pub fn fetch_latest_metadata_from_code_blob( executor: &WasmExecutor, code_bytes: Cow<[u8]>, -) -> sc_cli::Result { +) -> Result { let runtime_caller = RuntimeCaller::new(executor, code_bytes); let version_result = runtime_caller.call("Metadata_metadata_versions", ()); - let opaque_metadata: OpaqueMetadata = match version_result { + match version_result { Ok(supported_versions) => { - let supported_versions = Vec::::decode(&mut supported_versions.as_slice()) - .map_err(|e| format!("Unable to decode version list: {e}"))?; - + let supported_versions = Vec::::decode(&mut supported_versions.as_slice())?; let latest_stable = supported_versions .into_iter() .filter(|v| *v != u32::MAX) .max() - .ok_or("No stable metadata versions supported".to_string())?; + .ok_or(Error::StableMetadataVersionNotFound)?; - let encoded = runtime_caller - .call("Metadata_metadata_at_version", latest_stable) - .map_err(|_| "Unable to fetch metadata from blob".to_string())?; + let encoded = runtime_caller.call("Metadata_metadata_at_version", latest_stable)?; Option::::decode(&mut encoded.as_slice())? - .ok_or_else(|| "Metadata not found".to_string())? + .ok_or(Error::OpaqueMetadataNotFound) }, Err(_) => { - let encoded = runtime_caller - .call("Metadata_metadata", ()) - .map_err(|_| "Unable to fetch metadata from blob".to_string())?; - Decode::decode(&mut encoded.as_slice())? + let encoded = runtime_caller.call("Metadata_metadata", ())?; + Decode::decode(&mut encoded.as_slice()).map_err(Into::into) }, - }; - - Ok(subxt::Metadata::decode(&mut (*opaque_metadata).as_slice())?) + } } struct BasicCodeFetcher<'a> { @@ -74,11 +76,11 @@ impl<'a> FetchRuntimeCode for BasicCodeFetcher<'a> { } impl<'a> BasicCodeFetcher<'a> { - pub fn new(code: Cow<'a, [u8]>) -> Self { + fn new(code: Cow<'a, [u8]>) -> Self { Self { hash: sp_crypto_hashing::blake2_256(&code).to_vec(), code } } - pub fn runtime_code(&'a self) -> RuntimeCode<'a> { + fn runtime_code(&'a self) -> RuntimeCode<'a> { RuntimeCode { code_fetcher: self as &'a dyn FetchRuntimeCode, heap_pages: None, @@ -88,17 +90,20 @@ impl<'a> BasicCodeFetcher<'a> { } /// Simple utility that is used to call into the runtime. -struct RuntimeCaller<'a, 'b, HF: HostFunctions> { +pub struct RuntimeCaller<'a, 'b, HF: HostFunctions> { executor: &'b WasmExecutor, code_fetcher: BasicCodeFetcher<'a>, } impl<'a, 'b, HF: HostFunctions> RuntimeCaller<'a, 'b, HF> { + /// Instantiate a new runtime caller. pub fn new(executor: &'b WasmExecutor, code_bytes: Cow<'a, [u8]>) -> Self { Self { executor, code_fetcher: BasicCodeFetcher::new(code_bytes) } } - fn call(&self, method: &str, data: impl Encode) -> sc_executor_common::error::Result> { + /// Calls a runtime function represented by a `method` name and `parity-scale-codec` + /// encodable arguments that will be passed to it. + pub fn call(&self, method: &str, data: impl Encode) -> Result> { let mut ext = BasicExternalities::default(); self.executor .call( @@ -109,24 +114,33 @@ impl<'a, 'b, HF: HostFunctions> RuntimeCaller<'a, 'b, HF> { CallContext::Offchain, ) .0 + .map_err(Into::into) } } #[cfg(test)] mod tests { - use crate::overhead::command::ParachainHostFunctions; use codec::Decode; use sc_executor::WasmExecutor; use sp_version::RuntimeVersion; + type ParachainHostFunctions = ( + cumulus_primitives_proof_size_hostfunction::storage_proof_size::HostFunctions, + sp_io::SubstrateHostFunctions, + ); + #[test] fn test_fetch_latest_metadata_from_blob_fetches_metadata() { let executor: WasmExecutor = WasmExecutor::builder().build(); let code_bytes = cumulus_test_runtime::WASM_BINARY .expect("To run this test, build the wasm binary of cumulus-test-runtime") .to_vec(); - let metadata = - super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap(); + let metadata = subxt::Metadata::decode( + &mut (*super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()) + .unwrap()) + .as_slice(), + ) + .unwrap(); assert!(metadata.pallet_by_name("ParachainInfo").is_some()); } diff --git a/substrate/utils/frame/benchmarking-cli/Cargo.toml b/substrate/utils/frame/benchmarking-cli/Cargo.toml index 8a4a06b1b40a..6d86346ee180 100644 --- a/substrate/utils/frame/benchmarking-cli/Cargo.toml +++ b/substrate/utils/frame/benchmarking-cli/Cargo.toml @@ -44,6 +44,7 @@ sc-executor = { workspace = true, default-features = true } sc-executor-common = { workspace = true } sc-service = { workspace = true } sc-sysinfo = { workspace = true, default-features = true } +sc-runtime-utilities = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } diff --git a/substrate/utils/frame/benchmarking-cli/src/lib.rs b/substrate/utils/frame/benchmarking-cli/src/lib.rs index 1e8642e54d70..e1c3c5fe3706 100644 --- a/substrate/utils/frame/benchmarking-cli/src/lib.rs +++ b/substrate/utils/frame/benchmarking-cli/src/lib.rs @@ -30,7 +30,6 @@ pub use extrinsic::{ExtrinsicBuilder, ExtrinsicCmd, ExtrinsicFactory}; pub use machine::{MachineCmd, SUBSTRATE_REFERENCE_HARDWARE}; pub use overhead::{ remark_builder::{DynamicRemarkBuilder, SubstrateRemarkBuilder}, - runtime_utilities::fetch_latest_metadata_from_code_blob, OpaqueBlock, OverheadCmd, }; pub use pallet::PalletCmd; diff --git a/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs b/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs index 8102f14b4f4b..8df8ee5464f7 100644 --- a/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs @@ -18,7 +18,6 @@ //! Contains the [`OverheadCmd`] as entry point for the CLI to execute //! the *overhead* benchmarks. -use super::runtime_utilities::*; use crate::{ extrinsic::{ bench::{Benchmark, BenchmarkParams as ExtrinsicBenchmarkParams}, @@ -37,7 +36,7 @@ use crate::{ }, }; use clap::{error::ErrorKind, Args, CommandFactory, Parser}; -use codec::Encode; +use codec::{Decode, Encode}; use cumulus_client_parachain_inherent::MockValidationDataInherentDataProvider; use fake_runtime_api::RuntimeApi as FakeRuntimeApi; use frame_support::Deserialize; @@ -50,6 +49,7 @@ use sc_cli::{CliConfiguration, Database, ImportParams, Result, SharedParams}; use sc_client_api::{execution_extensions::ExecutionExtensions, UsageProvider}; use sc_client_db::{BlocksPruning, DatabaseSettings}; use sc_executor::WasmExecutor; +use sc_runtime_utilities::fetch_latest_metadata_from_code_blob; use sc_service::{new_client, new_db_backend, BasePath, ClientConfig, TFullClient, TaskManager}; use serde::Serialize; use serde_json::{json, Value}; @@ -317,7 +317,7 @@ impl OverheadCmd { Some(self.params.genesis_builder_preset.clone()), ), self.params.para_id, - )) + )); }; Err("Neither a runtime nor a chain-spec were specified".to_string().into()) @@ -335,7 +335,7 @@ impl OverheadCmd { ErrorKind::MissingRequiredArgument, "Provide either a runtime via `--runtime` or a chain spec via `--chain`" .to_string(), - )) + )); } match self.params.genesis_builder { @@ -344,7 +344,7 @@ impl OverheadCmd { return Err(( ErrorKind::MissingRequiredArgument, "Provide a chain spec via `--chain`.".to_string(), - )) + )); }, _ => {}, }; @@ -400,8 +400,12 @@ impl OverheadCmd { .with_allow_missing_host_functions(true) .build(); - let metadata = - fetch_latest_metadata_from_code_blob(&executor, state_handler.get_code_bytes()?)?; + let opaque_metadata = + fetch_latest_metadata_from_code_blob(&executor, state_handler.get_code_bytes()?) + .map_err(|_| { + <&str as Into>::into("Unable to fetch latest stable metadata") + })?; + let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice())?; // At this point we know what kind of chain we are dealing with. let chain_type = identify_chain(&metadata, para_id); @@ -682,6 +686,7 @@ mod tests { OverheadCmd, }; use clap::Parser; + use codec::Decode; use sc_executor::WasmExecutor; #[test] @@ -690,8 +695,9 @@ mod tests { let code_bytes = westend_runtime::WASM_BINARY .expect("To run this test, build the wasm binary of westend-runtime") .to_vec(); - let metadata = + let opaque_metadata = super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap(); + let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap(); let chain_type = identify_chain(&metadata, None); assert_eq!(chain_type, ChainType::Relaychain); assert_eq!(chain_type.requires_proof_recording(), false); @@ -703,8 +709,9 @@ mod tests { let code_bytes = cumulus_test_runtime::WASM_BINARY .expect("To run this test, build the wasm binary of cumulus-test-runtime") .to_vec(); - let metadata = + let opaque_metadata = super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap(); + let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap(); let chain_type = identify_chain(&metadata, Some(100)); assert_eq!(chain_type, ChainType::Parachain(100)); assert!(chain_type.requires_proof_recording()); @@ -717,8 +724,9 @@ mod tests { let code_bytes = substrate_test_runtime::WASM_BINARY .expect("To run this test, build the wasm binary of substrate-test-runtime") .to_vec(); - let metadata = + let opaque_metadata = super::fetch_latest_metadata_from_code_blob(&executor, code_bytes.into()).unwrap(); + let metadata = subxt::Metadata::decode(&mut (*opaque_metadata).as_slice()).unwrap(); let chain_type = identify_chain(&metadata, None); assert_eq!(chain_type, ChainType::Unknown); assert_eq!(chain_type.requires_proof_recording(), false); diff --git a/substrate/utils/frame/benchmarking-cli/src/overhead/mod.rs b/substrate/utils/frame/benchmarking-cli/src/overhead/mod.rs index 89c23d1fb6c1..de524d9ebc18 100644 --- a/substrate/utils/frame/benchmarking-cli/src/overhead/mod.rs +++ b/substrate/utils/frame/benchmarking-cli/src/overhead/mod.rs @@ -20,6 +20,5 @@ pub mod template; mod fake_runtime_api; pub mod remark_builder; -pub mod runtime_utilities; pub use command::{OpaqueBlock, OverheadCmd}; diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 8ed9c3dcb02c..d5ef707d2b8f 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -604,7 +604,7 @@ runtime = [ "sp-wasm-interface", "sp-weights", ] -node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-eth-rpc", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-omni-node-lib", "polkadot-overseer", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] +node = ["asset-test-utils", "bridge-hub-test-utils", "cumulus-client-cli", "cumulus-client-collator", "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", "cumulus-client-consensus-relay-chain", "cumulus-client-network", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", "cumulus-relay-chain-rpc-interface", "cumulus-test-relay-sproof-builder", "emulated-integration-tests-common", "fork-tree", "frame-benchmarking-cli", "frame-remote-externalities", "frame-support-procedural-tools", "generate-bags", "mmr-gadget", "mmr-rpc", "pallet-contracts-mock-network", "pallet-revive-eth-rpc", "pallet-revive-mock-network", "pallet-transaction-payment-rpc", "parachains-runtimes-test-utils", "polkadot-approval-distribution", "polkadot-availability-bitfield-distribution", "polkadot-availability-distribution", "polkadot-availability-recovery", "polkadot-cli", "polkadot-collator-protocol", "polkadot-dispute-distribution", "polkadot-erasure-coding", "polkadot-gossip-support", "polkadot-network-bridge", "polkadot-node-collation-generation", "polkadot-node-core-approval-voting", "polkadot-node-core-approval-voting-parallel", "polkadot-node-core-av-store", "polkadot-node-core-backing", "polkadot-node-core-bitfield-signing", "polkadot-node-core-candidate-validation", "polkadot-node-core-chain-api", "polkadot-node-core-chain-selection", "polkadot-node-core-dispute-coordinator", "polkadot-node-core-parachains-inherent", "polkadot-node-core-prospective-parachains", "polkadot-node-core-provisioner", "polkadot-node-core-pvf", "polkadot-node-core-pvf-checker", "polkadot-node-core-pvf-common", "polkadot-node-core-pvf-execute-worker", "polkadot-node-core-pvf-prepare-worker", "polkadot-node-core-runtime-api", "polkadot-node-metrics", "polkadot-node-network-protocol", "polkadot-node-primitives", "polkadot-node-subsystem", "polkadot-node-subsystem-types", "polkadot-node-subsystem-util", "polkadot-omni-node-lib", "polkadot-overseer", "polkadot-rpc", "polkadot-service", "polkadot-statement-distribution", "polkadot-statement-table", "sc-allocator", "sc-authority-discovery", "sc-basic-authorship", "sc-block-builder", "sc-chain-spec", "sc-cli", "sc-client-api", "sc-client-db", "sc-consensus", "sc-consensus-aura", "sc-consensus-babe", "sc-consensus-babe-rpc", "sc-consensus-beefy", "sc-consensus-beefy-rpc", "sc-consensus-epochs", "sc-consensus-grandpa", "sc-consensus-grandpa-rpc", "sc-consensus-manual-seal", "sc-consensus-pow", "sc-consensus-slots", "sc-executor", "sc-executor-common", "sc-executor-polkavm", "sc-executor-wasmtime", "sc-informant", "sc-keystore", "sc-mixnet", "sc-network", "sc-network-common", "sc-network-gossip", "sc-network-light", "sc-network-statement", "sc-network-sync", "sc-network-transactions", "sc-network-types", "sc-offchain", "sc-proposer-metrics", "sc-rpc", "sc-rpc-api", "sc-rpc-server", "sc-rpc-spec-v2", "sc-runtime-utilities", "sc-service", "sc-state-db", "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "snowbridge-runtime-test-common", "sp-blockchain", "sp-consensus", "sp-core-hashing", "sp-core-hashing-proc-macro", "sp-database", "sp-maybe-compressed-blob", "sp-panic-handler", "sp-rpc", "staging-chain-spec-builder", "staging-node-inspect", "staging-tracking-allocator", "std", "subkey", "substrate-build-script-utils", "substrate-frame-rpc-support", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", "substrate-rpc-client", "substrate-state-trie-migration-rpc", "substrate-wasm-builder", "tracing-gum", "xcm-emulator", "xcm-simulator"] tuples-96 = [ "frame-support-procedural?/tuples-96", "frame-support?/tuples-96", @@ -2327,6 +2327,11 @@ path = "../substrate/client/rpc-spec-v2" default-features = false optional = true +[dependencies.sc-runtime-utilities] +path = "../substrate/client/runtime-utilities" +default-features = false +optional = true + [dependencies.sc-service] path = "../substrate/client/service" default-features = false diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 3712fb3343cf..7b3c869588f0 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -1119,6 +1119,10 @@ pub use sc_rpc_server; #[cfg(feature = "sc-rpc-spec-v2")] pub use sc_rpc_spec_v2; +/// Substrate client utilities for frame runtime functions calls. +#[cfg(feature = "sc-runtime-utilities")] +pub use sc_runtime_utilities; + /// Substrate service. Starts a thread that spins up the network, client, and extrinsic pool. /// Manages communication between them. #[cfg(feature = "sc-service")] From 50e5dd27dc905fbe71778b00f62aad3490e47de5 Mon Sep 17 00:00:00 2001 From: Kazunobu Ndong <33208377+ndkazu@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:04:35 +0900 Subject: [PATCH 152/340] Remove collation-generation subsystem from validator nodes (#6832) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Issue #6476 Collation-generation is not needed for validators node, and should be removed. ## Implementation Use a `DummySubsystem` for `collation_generation` --------- Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> Co-authored-by: Dmitry Markin Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> --- polkadot/node/service/src/overseer.rs | 9 +++++---- prdoc/pr_6832.prdoc | 13 +++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_6832.prdoc diff --git a/polkadot/node/service/src/overseer.rs b/polkadot/node/service/src/overseer.rs index 279b6ff80704..e4ea6efeaac2 100644 --- a/polkadot/node/service/src/overseer.rs +++ b/polkadot/node/service/src/overseer.rs @@ -210,7 +210,7 @@ pub fn validator_overseer_builder( AuthorityDiscoveryService, >, ChainApiSubsystem, - CollationGenerationSubsystem, + DummySubsystem, CollatorProtocolSubsystem, ApprovalDistributionSubsystem, ApprovalVotingSubsystem, @@ -237,6 +237,7 @@ where let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?; let approval_voting_parallel_metrics: ApprovalVotingParallelMetrics = Metrics::register(registry)?; + let builder = Overseer::builder() .network_bridge_tx(NetworkBridgeTxSubsystem::new( network_service.clone(), @@ -295,7 +296,7 @@ where )) .pvf_checker(PvfCheckerSubsystem::new(keystore.clone(), Metrics::register(registry)?)) .chain_api(ChainApiSubsystem::new(runtime_client.clone(), Metrics::register(registry)?)) - .collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?)) + .collation_generation(DummySubsystem) .collator_protocol({ let side = match is_parachain_node { IsParachainNode::Collator(_) | IsParachainNode::FullNode => @@ -434,7 +435,7 @@ pub fn validator_with_parallel_overseer_builder( AuthorityDiscoveryService, >, ChainApiSubsystem, - CollationGenerationSubsystem, + DummySubsystem, CollatorProtocolSubsystem, DummySubsystem, DummySubsystem, @@ -519,7 +520,7 @@ where )) .pvf_checker(PvfCheckerSubsystem::new(keystore.clone(), Metrics::register(registry)?)) .chain_api(ChainApiSubsystem::new(runtime_client.clone(), Metrics::register(registry)?)) - .collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?)) + .collation_generation(DummySubsystem) .collator_protocol({ let side = match is_parachain_node { IsParachainNode::Collator(_) | IsParachainNode::FullNode => diff --git a/prdoc/pr_6832.prdoc b/prdoc/pr_6832.prdoc new file mode 100644 index 000000000000..bd0abbfba853 --- /dev/null +++ b/prdoc/pr_6832.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Remove collation-generation subsystem from validator nodes" + +doc: + - audience: Node Dev + description: | + Collation-generation is only needed for Collators, and therefore not needed for validators + +crates: + - name: polkadot-service + bump: patch \ No newline at end of file From f8e5a8a131907df3ba2b33781ceab5c5dab68c67 Mon Sep 17 00:00:00 2001 From: davidk-pt Date: Thu, 12 Dec 2024 16:25:54 +0200 Subject: [PATCH 153/340] pallet-revive: disable host functions not in revive recompiler (#6844) Resolves https://github.com/paritytech/polkadot-sdk/issues/6720 List of used host functions in PolkaVM recompiler is here https://github.com/paritytech/revive/blob/main/crates/runtime-api/src/polkavm_imports.c#L65 --------- Co-authored-by: DavidK --- prdoc/pr_6844.prdoc | 8 ++++++++ substrate/frame/revive/src/wasm/runtime.rs | 19 ------------------- 2 files changed, 8 insertions(+), 19 deletions(-) create mode 100644 prdoc/pr_6844.prdoc diff --git a/prdoc/pr_6844.prdoc b/prdoc/pr_6844.prdoc new file mode 100644 index 000000000000..32901bf04df9 --- /dev/null +++ b/prdoc/pr_6844.prdoc @@ -0,0 +1,8 @@ +title: 'pallet-revive: disable host functions unused in solidity PolkaVM compiler' +doc: +- audience: Runtime Dev + description: Disables host functions in contracts that are not enabled + in solidity PolkaVM compiler to reduce surface of possible attack vectors. +crates: +- name: pallet-revive + bump: major diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 0d03771224b5..d8b856b0b766 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -1148,7 +1148,6 @@ pub mod env { /// Clear the value at the given key in the contract storage. /// See [`pallet_revive_uapi::HostFn::clear_storage`] - #[stable] #[mutating] fn clear_storage( &mut self, @@ -1177,7 +1176,6 @@ pub mod env { /// Checks whether there is a value stored under the given key. /// See [`pallet_revive_uapi::HostFn::contains_storage`] - #[stable] fn contains_storage( &mut self, memory: &mut M, @@ -1190,7 +1188,6 @@ pub mod env { /// Retrieve and remove the value under the given key from storage. /// See [`pallet_revive_uapi::HostFn::take_storage`] - #[stable] #[mutating] fn take_storage( &mut self, @@ -1301,7 +1298,6 @@ pub mod env { /// Remove the calling account and transfer remaining **free** balance. /// See [`pallet_revive_uapi::HostFn::terminate`]. - #[stable] #[mutating] fn terminate(&mut self, memory: &mut M, beneficiary_ptr: u32) -> Result<(), TrapReason> { self.terminate(memory, beneficiary_ptr) @@ -1399,7 +1395,6 @@ pub mod env { /// Checks whether a specified address belongs to a contract. /// See [`pallet_revive_uapi::HostFn::is_contract`]. - #[stable] fn is_contract(&mut self, memory: &mut M, account_ptr: u32) -> Result { self.charge_gas(RuntimeCosts::IsContract)?; let address = memory.read_h160(account_ptr)?; @@ -1438,7 +1433,6 @@ pub mod env { /// Retrieve the code hash of the currently executing contract. /// See [`pallet_revive_uapi::HostFn::own_code_hash`]. - #[stable] fn own_code_hash(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::OwnCodeHash)?; let code_hash = *self.ext.own_code_hash(); @@ -1453,7 +1447,6 @@ pub mod env { /// Checks whether the caller of the current contract is the origin of the whole call stack. /// See [`pallet_revive_uapi::HostFn::caller_is_origin`]. - #[stable] fn caller_is_origin(&mut self, _memory: &mut M) -> Result { self.charge_gas(RuntimeCosts::CallerIsOrigin)?; Ok(self.ext.caller_is_origin() as u32) @@ -1461,7 +1454,6 @@ pub mod env { /// Checks whether the caller of the current contract is root. /// See [`pallet_revive_uapi::HostFn::caller_is_root`]. - #[stable] fn caller_is_root(&mut self, _memory: &mut M) -> Result { self.charge_gas(RuntimeCosts::CallerIsRoot)?; Ok(self.ext.caller_is_root() as u32) @@ -1505,7 +1497,6 @@ pub mod env { /// Stores the amount of weight left into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::weight_left`]. - #[stable] fn weight_left( &mut self, memory: &mut M, @@ -1631,7 +1622,6 @@ pub mod env { /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::minimum_balance`]. - #[stable] fn minimum_balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { self.charge_gas(RuntimeCosts::MinimumBalance)?; Ok(self.write_fixed_sandbox_output( @@ -1720,7 +1710,6 @@ pub mod env { /// Computes the SHA2 256-bit hash on the given input buffer. /// See [`pallet_revive_uapi::HostFn::hash_sha2_256`]. - #[stable] fn hash_sha2_256( &mut self, memory: &mut M, @@ -1752,7 +1741,6 @@ pub mod env { /// Computes the BLAKE2 256-bit hash on the given input buffer. /// See [`pallet_revive_uapi::HostFn::hash_blake2_256`]. - #[stable] fn hash_blake2_256( &mut self, memory: &mut M, @@ -1768,7 +1756,6 @@ pub mod env { /// Computes the BLAKE2 128-bit hash on the given input buffer. /// See [`pallet_revive_uapi::HostFn::hash_blake2_128`]. - #[stable] fn hash_blake2_128( &mut self, memory: &mut M, @@ -1814,7 +1801,6 @@ pub mod env { /// Emit a custom debug message. /// See [`pallet_revive_uapi::HostFn::debug_message`]. - #[stable] fn debug_message( &mut self, memory: &mut M, @@ -1932,7 +1918,6 @@ pub mod env { /// Recovers the ECDSA public key from the given message hash and signature. /// See [`pallet_revive_uapi::HostFn::ecdsa_recover`]. - #[stable] fn ecdsa_recover( &mut self, memory: &mut M, @@ -1963,7 +1948,6 @@ pub mod env { /// Verify a sr25519 signature /// See [`pallet_revive_uapi::HostFn::sr25519_verify`]. - #[stable] fn sr25519_verify( &mut self, memory: &mut M, @@ -2004,7 +1988,6 @@ pub mod env { /// Calculates Ethereum address from the ECDSA compressed public key and stores /// See [`pallet_revive_uapi::HostFn::ecdsa_to_eth_address`]. - #[stable] fn ecdsa_to_eth_address( &mut self, memory: &mut M, @@ -2026,7 +2009,6 @@ pub mod env { /// Adds a new delegate dependency to the contract. /// See [`pallet_revive_uapi::HostFn::lock_delegate_dependency`]. - #[stable] #[mutating] fn lock_delegate_dependency( &mut self, @@ -2041,7 +2023,6 @@ pub mod env { /// Removes the delegate dependency from the contract. /// see [`pallet_revive_uapi::HostFn::unlock_delegate_dependency`]. - #[stable] #[mutating] fn unlock_delegate_dependency( &mut self, From 5788ae8609e1e6947c588a5745d22d8777e47f4e Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Thu, 12 Dec 2024 17:47:30 +0200 Subject: [PATCH 154/340] chore: Update litep2p to version 0.8.4 (#6860) ## [0.8.4] - 2024-12-12 This release aims to make the MDNS component more robust by fixing a bug that caused the MDNS service to fail to register opened substreams. Additionally, the release includes several improvements to the `identify` protocol, replacing `FuturesUnordered` with `FuturesStream` for better performance. ### Fixed - mdns/fix: Failed to register opened substream ([#301](https://github.com/paritytech/litep2p/pull/301)) ### Changed - identify: Replace FuturesUnordered with FuturesStream ([#302](https://github.com/paritytech/litep2p/pull/302)) - chore: Update hickory-resolver to version 0.24.2 ([#304](https://github.com/paritytech/litep2p/pull/304)) - ci: Ensure cargo-machete is working with rust version from CI ([#303](https://github.com/paritytech/litep2p/pull/303)) cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- prdoc/pr_6860.prdoc | 10 ++++++++++ 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 prdoc/pr_6860.prdoc diff --git a/Cargo.lock b/Cargo.lock index afd7507e7ab9..9eb9d3736a60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8488,9 +8488,9 @@ dependencies = [ [[package]] name = "hickory-resolver" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28757f23aa75c98f254cf0405e6d8c25b831b32921b050a66692427679b1f243" +checksum = "0a2e2aba9c389ce5267d31cf1e4dace82390ae276b0b364ea55630b1fa1b44b4" dependencies = [ "cfg-if", "futures-util", @@ -10406,9 +10406,9 @@ dependencies = [ [[package]] name = "litep2p" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e490b5a6d486711fd0284bd30e607a287343f2935a59a9192bd7109e85f443" +checksum = "2b0fef34af8847e816003bf7fdeac5ea50b9a7a88441ac927a6166b5e812ab79" dependencies = [ "async-trait", "bs58", diff --git a/Cargo.toml b/Cargo.toml index 089ba3cef208..98ab6551c802 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -848,7 +848,7 @@ linked-hash-map = { version = "0.5.4" } linked_hash_set = { version = "0.1.4" } linregress = { version = "0.5.1" } lite-json = { version = "0.2.0", default-features = false } -litep2p = { version = "0.8.3", features = ["websocket"] } +litep2p = { version = "0.8.4", features = ["websocket"] } log = { version = "0.4.22", default-features = false } macro_magic = { version = "0.5.1" } maplit = { version = "1.0.2" } diff --git a/prdoc/pr_6860.prdoc b/prdoc/pr_6860.prdoc new file mode 100644 index 000000000000..76b460ce52dd --- /dev/null +++ b/prdoc/pr_6860.prdoc @@ -0,0 +1,10 @@ +title: Update litep2p network backend to v0.8.4 + +doc: + - audience: [ Node Dev, Node Operator ] + description: | + This PR updates the Litep2p network backend to version 0.8.4 + +crates: + - name: sc-network + bump: patch From c10e25aaa8b8afd8665b53f0a0b02e4ea44caa77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 12 Dec 2024 17:50:23 +0100 Subject: [PATCH 155/340] dmp: Check that the para exist before delivering a message (#6604) Co-authored-by: GitHub Action Co-authored-by: Branislav Kontur Co-authored-by: command-bot <> --- Cargo.lock | 4 +- bridges/bin/runtime-common/Cargo.toml | 1 + .../modules/xcm-bridge-hub-router/Cargo.toml | 1 + bridges/modules/xcm-bridge-hub/Cargo.toml | 1 + .../pallets/inbound-queue/Cargo.toml | 1 + bridges/snowbridge/pallets/system/Cargo.toml | 1 + bridges/snowbridge/primitives/core/Cargo.toml | 1 + .../snowbridge/primitives/router/Cargo.toml | 1 + .../runtime/runtime-common/Cargo.toml | 1 + .../snowbridge/runtime/test-common/Cargo.toml | 1 + cumulus/pallets/dmp-queue/Cargo.toml | 1 + cumulus/pallets/parachain-system/Cargo.toml | 5 +- cumulus/pallets/parachain-system/src/lib.rs | 2 +- cumulus/pallets/xcmp-queue/Cargo.toml | 1 + cumulus/parachains/common/Cargo.toml | 1 + .../emulated/common/src/impls.rs | 2 + .../emulated/common/src/macros.rs | 3 + .../src/tests/hybrid_transfers.rs | 4 + .../src/tests/reserve_transfer.rs | 13 +++ .../asset-hub-rococo/src/tests/treasury.rs | 4 + .../src/tests/hybrid_transfers.rs | 4 + .../src/tests/reserve_transfer.rs | 13 +++ .../asset-hub-westend/src/tests/treasury.rs | 3 + .../bridge-hub-rococo/src/tests/send_xcm.rs | 4 + .../bridge-hub-westend/src/tests/send_xcm.rs | 4 + .../src/tests/fellowship_treasury.rs | 3 + .../src/tests/coretime_interface.rs | 5 + .../src/tests/coretime_interface.rs | 5 + .../people-westend/src/tests/governance.rs | 20 +++- .../assets/asset-hub-rococo/Cargo.toml | 1 + .../assets/asset-hub-westend/Cargo.toml | 1 + .../runtimes/assets/common/Cargo.toml | 1 + .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 1 + .../bridge-hubs/bridge-hub-westend/Cargo.toml | 1 + .../runtimes/bridge-hubs/common/Cargo.toml | 1 + .../collectives-westend/Cargo.toml | 1 + .../contracts/contracts-rococo/Cargo.toml | 1 + .../coretime/coretime-rococo/Cargo.toml | 1 + .../coretime/coretime-westend/Cargo.toml | 1 + .../glutton/glutton-westend/Cargo.toml | 1 + .../runtimes/people/people-rococo/Cargo.toml | 1 + .../runtimes/people/people-westend/Cargo.toml | 1 + .../runtimes/testing/penpal/Cargo.toml | 1 + .../testing/rococo-parachain/Cargo.toml | 1 + cumulus/polkadot-parachain/Cargo.toml | 1 + cumulus/primitives/core/Cargo.toml | 1 + cumulus/primitives/utility/Cargo.toml | 1 + polkadot/node/service/Cargo.toml | 1 + polkadot/parachain/src/primitives.rs | 5 + polkadot/runtime/common/Cargo.toml | 1 + .../runtime/common/src/identity_migrator.rs | 16 +++ .../runtime/common/src/paras_sudo_wrapper.rs | 3 + polkadot/runtime/common/src/xcm_sender.rs | 29 +++-- polkadot/runtime/parachains/Cargo.toml | 1 + .../parachains/src/coretime/benchmarking.rs | 2 + polkadot/runtime/parachains/src/dmp.rs | 37 ++++-- polkadot/runtime/parachains/src/dmp/tests.rs | 44 ++++++++ polkadot/runtime/parachains/src/lib.rs | 16 +++ polkadot/runtime/rococo/Cargo.toml | 1 + polkadot/runtime/rococo/src/impls.rs | 5 + polkadot/runtime/rococo/src/lib.rs | 6 +- polkadot/runtime/test-runtime/Cargo.toml | 1 + polkadot/runtime/westend/Cargo.toml | 1 + polkadot/runtime/westend/src/impls.rs | 5 + polkadot/runtime/westend/src/lib.rs | 6 +- polkadot/xcm/Cargo.toml | 4 + polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml | 1 + .../src/fungible/benchmarking.rs | 35 +++++- .../src/generic/benchmarking.rs | 6 + polkadot/xcm/pallet-xcm/Cargo.toml | 1 + polkadot/xcm/pallet-xcm/src/benchmarking.rs | 32 +++++- polkadot/xcm/src/v5/traits.rs | 11 ++ polkadot/xcm/xcm-builder/Cargo.toml | 1 + polkadot/xcm/xcm-builder/src/pay.rs | 11 +- polkadot/xcm/xcm-builder/src/routing.rs | 15 +++ polkadot/xcm/xcm-builder/src/tests/pay/pay.rs | 2 +- .../xcm/xcm-builder/src/universal_exports.rs | 16 +++ polkadot/xcm/xcm-executor/Cargo.toml | 1 + .../xcm-executor/integration-tests/Cargo.toml | 5 +- .../xcm-executor/integration-tests/src/lib.rs | 25 ++++- polkadot/xcm/xcm-runtime-apis/Cargo.toml | 1 + polkadot/xcm/xcm-simulator/example/Cargo.toml | 1 + polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml | 1 + prdoc/pr_6604.prdoc | 106 ++++++++++++++++++ substrate/frame/contracts/Cargo.toml | 1 + .../frame/contracts/mock-network/Cargo.toml | 1 + substrate/frame/revive/Cargo.toml | 1 + .../frame/revive/mock-network/Cargo.toml | 1 + umbrella/Cargo.toml | 1 + 89 files changed, 537 insertions(+), 51 deletions(-) create mode 100644 prdoc/pr_6604.prdoc diff --git a/Cargo.lock b/Cargo.lock index 9eb9d3736a60..f2379d4ee6de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4955,7 +4955,6 @@ dependencies = [ "pallet-message-queue 31.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", - "polkadot-runtime-common 7.0.0", "polkadot-runtime-parachains 7.0.0", "rand", "sc-client-api", @@ -31882,10 +31881,13 @@ name = "xcm-executor-integration-tests" version = "1.0.0" dependencies = [ "frame-support 28.0.0", + "frame-system 28.0.0", "futures", + "pallet-sudo 28.0.0", "pallet-transaction-payment 28.0.0", "pallet-xcm 7.0.0", "parity-scale-codec", + "polkadot-runtime-parachains 7.0.0", "polkadot-test-client", "polkadot-test-runtime", "polkadot-test-service", diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index 37b56140c289..49cd086fd3eb 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -99,6 +99,7 @@ runtime-benchmarks = [ "pallet-utility/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-trie", + "xcm/runtime-benchmarks", ] integrity-test = ["static_assertions"] test-helpers = ["bp-runtime/test-helpers", "sp-trie"] diff --git a/bridges/modules/xcm-bridge-hub-router/Cargo.toml b/bridges/modules/xcm-bridge-hub-router/Cargo.toml index 55824f6a7fe7..b0286938f36d 100644 --- a/bridges/modules/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub-router/Cargo.toml @@ -56,6 +56,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/bridges/modules/xcm-bridge-hub/Cargo.toml b/bridges/modules/xcm-bridge-hub/Cargo.toml index 251dcfb45bcb..ef49b3396b5c 100644 --- a/bridges/modules/xcm-bridge-hub/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub/Cargo.toml @@ -77,6 +77,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml index 3ab633bfcd79..c0789940a9e8 100644 --- a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml @@ -81,6 +81,7 @@ runtime-benchmarks = [ "snowbridge-router-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/bridges/snowbridge/pallets/system/Cargo.toml b/bridges/snowbridge/pallets/system/Cargo.toml index f1e749afb997..d8e124d73d14 100644 --- a/bridges/snowbridge/pallets/system/Cargo.toml +++ b/bridges/snowbridge/pallets/system/Cargo.toml @@ -71,6 +71,7 @@ runtime-benchmarks = [ "snowbridge-pallet-outbound-queue/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index fa37c795b2d1..af002a5a965c 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -64,4 +64,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index ee8d481cec12..1f7f489c6b18 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -51,4 +51,5 @@ runtime-benchmarks = [ "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/bridges/snowbridge/runtime/runtime-common/Cargo.toml b/bridges/snowbridge/runtime/runtime-common/Cargo.toml index d47cb3cb7101..514a4c186696 100644 --- a/bridges/snowbridge/runtime/runtime-common/Cargo.toml +++ b/bridges/snowbridge/runtime/runtime-common/Cargo.toml @@ -43,4 +43,5 @@ runtime-benchmarks = [ "snowbridge-core/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/bridges/snowbridge/runtime/test-common/Cargo.toml b/bridges/snowbridge/runtime/test-common/Cargo.toml index 9f47f158ed4a..cc1ed1288ca0 100644 --- a/bridges/snowbridge/runtime/test-common/Cargo.toml +++ b/bridges/snowbridge/runtime/test-common/Cargo.toml @@ -92,5 +92,6 @@ runtime-benchmarks = [ "snowbridge-pallet-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] fast-runtime = [] diff --git a/cumulus/pallets/dmp-queue/Cargo.toml b/cumulus/pallets/dmp-queue/Cargo.toml index 936526290d93..ae85a108fe72 100644 --- a/cumulus/pallets/dmp-queue/Cargo.toml +++ b/cumulus/pallets/dmp-queue/Cargo.toml @@ -56,6 +56,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 05498a474e42..c911f8531da2 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -38,7 +38,6 @@ sp-version = { workspace = true } # Polkadot polkadot-parachain-primitives = { features = ["wasm-api"], workspace = true } polkadot-runtime-parachains = { workspace = true } -polkadot-runtime-common = { optional = true, workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } @@ -84,7 +83,6 @@ std = [ "log/std", "pallet-message-queue/std", "polkadot-parachain-primitives/std", - "polkadot-runtime-common/std", "polkadot-runtime-parachains/std", "scale-info/std", "sp-core/std", @@ -109,17 +107,16 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", - "polkadot-runtime-common/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-message-queue/try-runtime", - "polkadot-runtime-common?/try-runtime", "polkadot-runtime-parachains/try-runtime", "sp-runtime/try-runtime", ] diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 39fc8321a072..0fa759357f65 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -1636,7 +1636,7 @@ impl InspectMessageQueues for Pallet { } #[cfg(feature = "runtime-benchmarks")] -impl polkadot_runtime_common::xcm_sender::EnsureForParachain for Pallet { +impl polkadot_runtime_parachains::EnsureForParachain for Pallet { fn ensure(para_id: ParaId) { if let ChannelStatus::Closed = Self::get_channel_status(para_id) { Self::open_outbound_hrmp_channel_for_benchmarks_or_tests(para_id) diff --git a/cumulus/pallets/xcmp-queue/Cargo.toml b/cumulus/pallets/xcmp-queue/Cargo.toml index af70a3169d8e..432be3027e05 100644 --- a/cumulus/pallets/xcmp-queue/Cargo.toml +++ b/cumulus/pallets/xcmp-queue/Cargo.toml @@ -87,6 +87,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-parachain-system/try-runtime", diff --git a/cumulus/parachains/common/Cargo.toml b/cumulus/parachains/common/Cargo.toml index 641693a6a01b..ae4d7fc1d115 100644 --- a/cumulus/parachains/common/Cargo.toml +++ b/cumulus/parachains/common/Cargo.toml @@ -92,4 +92,5 @@ runtime-benchmarks = [ "polkadot-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs index c0d42cf2758e..9dad323aa19c 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/impls.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/impls.rs @@ -370,6 +370,8 @@ macro_rules! impl_send_transact_helpers_for_relay_chain { let destination: $crate::impls::Location = ::child_location_of(recipient); let xcm = $crate::impls::xcm_transact_unpaid_execution(call, $crate::impls::OriginKind::Superuser); + $crate::impls::dmp::Pallet::<::Runtime>::make_parachain_reachable(recipient); + // Send XCM `Transact` $crate::impls::assert_ok!(]>::XcmPallet::send( root_origin, diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index b776cafb2545..cd2b41e5198f 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -23,6 +23,7 @@ pub use pallet_message_queue; pub use pallet_xcm; // Polkadot +pub use polkadot_runtime_parachains::dmp::Pallet as Dmp; pub use xcm::{ prelude::{ AccountId32, All, Asset, AssetId, BuyExecution, DepositAsset, ExpectTransactStatus, @@ -156,6 +157,8 @@ macro_rules! test_relay_is_trusted_teleporter { // Send XCM message from Relay <$sender_relay>::execute_with(|| { + $crate::macros::Dmp::<<$sender_relay as $crate::macros::Chain>::Runtime>::make_parachain_reachable(<$receiver_para>::para_id()); + assert_ok!(<$sender_relay as [<$sender_relay Pallet>]>::XcmPallet::limited_teleport_assets( origin.clone(), bx!(para_destination.clone().into()), diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs index baec7d20f415..fb95c361f089 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/hybrid_transfers.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use rococo_system_emulated_network::rococo_emulated_chain::rococo_runtime::Dmp; + use super::reserve_transfer::*; use crate::{ imports::*, @@ -777,6 +779,8 @@ fn transfer_native_asset_from_relay_to_para_through_asset_hub() { xcm: xcm_on_final_dest, }]); + Dmp::make_parachain_reachable(AssetHubRococo::para_id()); + // First leg is a teleport, from there a local-reserve-transfer to final dest ::XcmPallet::transfer_assets_using_type_and_then( t.signed_origin, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs index d642e877f002..407a581afeb9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reserve_transfer.rs @@ -14,6 +14,7 @@ // limitations under the License. use crate::imports::*; +use rococo_system_emulated_network::rococo_emulated_chain::rococo_runtime::Dmp; use sp_core::{crypto::get_public_from_string_or_panic, sr25519}; fn relay_to_para_sender_assertions(t: RelayToParaTest) { @@ -487,6 +488,11 @@ pub fn para_to_para_through_hop_receiver_assertions(t: Test DispatchResult { + let Junction::Parachain(para_id) = *t.args.dest.chain_location().last().unwrap() else { + unimplemented!("Destination is not a parachain?") + }; + + Dmp::make_parachain_reachable(para_id); ::XcmPallet::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -546,6 +552,13 @@ fn para_to_system_para_reserve_transfer_assets(t: ParaToSystemParaTest) -> Dispa fn para_to_para_through_relay_limited_reserve_transfer_assets( t: ParaToParaThroughRelayTest, ) -> DispatchResult { + let Junction::Parachain(para_id) = *t.args.dest.chain_location().last().unwrap() else { + unimplemented!("Destination is not a parachain?") + }; + + Rococo::ext_wrapper(|| { + Dmp::make_parachain_reachable(para_id); + }); ::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs index 69111d38bcac..8648c8ce9311 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/treasury.rs @@ -29,6 +29,7 @@ use frame_support::{ use parachains_common::AccountId; use polkadot_runtime_common::impls::VersionedLocatableAsset; use rococo_runtime_constants::currency::GRAND; +use rococo_system_emulated_network::rococo_emulated_chain::rococo_runtime::Dmp; use xcm_executor::traits::ConvertLocation; // Fund Treasury account on Asset Hub from Treasury account on Relay Chain with ROCs. @@ -64,6 +65,7 @@ fn spend_roc_on_asset_hub() { treasury_balance * 2, )); + Dmp::make_parachain_reachable(1000); let native_asset = Location::here(); let asset_hub_location: Location = [Parachain(1000)].into(); let treasury_location: Location = (Parent, PalletInstance(18)).into(); @@ -199,6 +201,8 @@ fn create_and_claim_treasury_spend_in_usdt() { // create a conversion rate from `asset_kind` to the native currency. assert_ok!(AssetRate::create(root.clone(), Box::new(asset_kind.clone()), 2.into())); + Dmp::make_parachain_reachable(1000); + // create and approve a treasury spend. assert_ok!(Treasury::spend( root, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs index 0686bd71d085..91ebdda16828 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/hybrid_transfers.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; + use super::reserve_transfer::*; use crate::{ imports::*, @@ -778,6 +780,8 @@ fn transfer_native_asset_from_relay_to_penpal_through_asset_hub() { xcm: xcm_on_final_dest, }]); + Dmp::make_parachain_reachable(AssetHubWestend::para_id()); + // First leg is a teleport, from there a local-reserve-transfer to final dest ::XcmPallet::transfer_assets_using_type_and_then( t.signed_origin, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs index 707e8adc8a56..dc36fed42932 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reserve_transfer.rs @@ -15,6 +15,7 @@ use crate::{create_pool_with_wnd_on, foreign_balance_on, imports::*}; use sp_core::{crypto::get_public_from_string_or_panic, sr25519}; +use westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; fn relay_to_para_sender_assertions(t: RelayToParaTest) { type RuntimeEvent = ::RuntimeEvent; @@ -487,6 +488,11 @@ pub fn para_to_para_through_hop_receiver_assertions(t: Test DispatchResult { + let Junction::Parachain(para_id) = *t.args.dest.chain_location().last().unwrap() else { + unimplemented!("Destination is not a parachain?") + }; + + Dmp::make_parachain_reachable(para_id); ::XcmPallet::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), @@ -533,6 +539,13 @@ fn para_to_system_para_reserve_transfer_assets(t: ParaToSystemParaTest) -> Dispa fn para_to_para_through_relay_limited_reserve_transfer_assets( t: ParaToParaThroughRelayTest, ) -> DispatchResult { + let Junction::Parachain(para_id) = *t.args.dest.chain_location().last().unwrap() else { + unimplemented!("Destination is not a parachain?") + }; + + Westend::ext_wrapper(|| { + Dmp::make_parachain_reachable(para_id); + }); ::PolkadotXcm::limited_reserve_transfer_assets( t.signed_origin, bx!(t.args.dest.into()), diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs index c303e6411d33..3b53557fc05c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/treasury.rs @@ -20,6 +20,7 @@ use emulated_integration_tests_common::{ }; use frame_support::traits::fungibles::{Inspect, Mutate}; use polkadot_runtime_common::impls::VersionedLocatableAsset; +use westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; use xcm_executor::traits::ConvertLocation; #[test] @@ -58,6 +59,8 @@ fn create_and_claim_treasury_spend() { // create a conversion rate from `asset_kind` to the native currency. assert_ok!(AssetRate::create(root.clone(), Box::new(asset_kind.clone()), 2.into())); + Dmp::make_parachain_reachable(1000); + // create and approve a treasury spend. assert_ok!(Treasury::spend( root, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs index 116ec4dc0e55..cfcb581238e6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use rococo_system_emulated_network::rococo_emulated_chain::rococo_runtime::Dmp; + use crate::tests::*; #[test] @@ -38,6 +40,8 @@ fn send_xcm_from_rococo_relay_to_westend_asset_hub_should_fail_on_not_applicable // Rococo Global Consensus // Send XCM message from Relay Chain to Bridge Hub source Parachain Rococo::execute_with(|| { + Dmp::make_parachain_reachable(BridgeHubRococo::para_id()); + assert_ok!(::XcmPallet::send( sudo_origin, bx!(destination), diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs index acce60b4fa76..60f8af2242f9 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs @@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use rococo_westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; + use crate::tests::*; #[test] @@ -38,6 +40,8 @@ fn send_xcm_from_westend_relay_to_rococo_asset_hub_should_fail_on_not_applicable // Westend Global Consensus // Send XCM message from Relay Chain to Bridge Hub source Parachain Westend::execute_with(|| { + Dmp::make_parachain_reachable(BridgeHubWestend::para_id()); + assert_ok!(::XcmPallet::send( sudo_origin, bx!(destination), diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_treasury.rs b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_treasury.rs index 8418e3da3bba..ed7c9bafc607 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_treasury.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/src/tests/fellowship_treasury.rs @@ -20,6 +20,7 @@ use frame_support::{ }; use polkadot_runtime_common::impls::VersionedLocatableAsset; use westend_runtime_constants::currency::UNITS; +use westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; use xcm_executor::traits::ConvertLocation; // Fund Fellowship Treasury from Westend Treasury and spend from Fellowship Treasury. @@ -57,6 +58,8 @@ fn fellowship_treasury_spend() { treasury_balance * 2, )); + Dmp::make_parachain_reachable(1000); + let native_asset = Location::here(); let asset_hub_location: Location = [Parachain(1000)].into(); let treasury_location: Location = (Parent, PalletInstance(37)).into(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs index 9915b1753ef6..554025e1ecfe 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/src/tests/coretime_interface.rs @@ -17,6 +17,7 @@ use crate::imports::*; use frame_support::traits::OnInitialize; use pallet_broker::{ConfigRecord, Configuration, CoreAssignment, CoreMask, ScheduleItem}; use rococo_runtime_constants::system_parachain::coretime::TIMESLICE_PERIOD; +use rococo_system_emulated_network::rococo_emulated_chain::rococo_runtime::Dmp; use sp_runtime::Perbill; #[test] @@ -34,6 +35,10 @@ fn transact_hardcoded_weights_are_sane() { type CoretimeEvent = ::RuntimeEvent; type RelayEvent = ::RuntimeEvent; + Rococo::execute_with(|| { + Dmp::make_parachain_reachable(CoretimeRococo::para_id()); + }); + // Reserve a workload, configure broker and start sales. CoretimeRococo::execute_with(|| { // Hooks don't run in emulated tests - workaround as we need `on_initialize` to tick things diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs index 00530f80b958..900994b1afc1 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/src/tests/coretime_interface.rs @@ -18,6 +18,7 @@ use frame_support::traits::OnInitialize; use pallet_broker::{ConfigRecord, Configuration, CoreAssignment, CoreMask, ScheduleItem}; use sp_runtime::Perbill; use westend_runtime_constants::system_parachain::coretime::TIMESLICE_PERIOD; +use westend_system_emulated_network::westend_emulated_chain::westend_runtime::Dmp; #[test] fn transact_hardcoded_weights_are_sane() { @@ -34,6 +35,10 @@ fn transact_hardcoded_weights_are_sane() { type CoretimeEvent = ::RuntimeEvent; type RelayEvent = ::RuntimeEvent; + Westend::execute_with(|| { + Dmp::make_parachain_reachable(CoretimeWestend::para_id()); + }); + // Reserve a workload, configure broker and start sales. CoretimeWestend::execute_with(|| { // Hooks don't run in emulated tests - workaround as we need `on_initialize` to tick things diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs index 1ba787aaec52..ea438f80552e 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs @@ -20,7 +20,9 @@ use codec::Encode; use frame_support::sp_runtime::traits::Dispatchable; use parachains_common::AccountId; use people_westend_runtime::people::IdentityInfo; -use westend_runtime::governance::pallet_custom_origins::Origin::GeneralAdmin as GeneralAdminOrigin; +use westend_runtime::{ + governance::pallet_custom_origins::Origin::GeneralAdmin as GeneralAdminOrigin, Dmp, +}; use westend_system_emulated_network::people_westend_emulated_chain::people_westend_runtime; use pallet_identity::Data; @@ -39,6 +41,8 @@ fn relay_commands_add_registrar() { type PeopleCall = ::RuntimeCall; type PeopleRuntime = ::Runtime; + Dmp::make_parachain_reachable(1004); + let add_registrar_call = PeopleCall::Identity(pallet_identity::Call::::add_registrar { account: registrar.into(), @@ -102,6 +106,8 @@ fn relay_commands_add_registrar_wrong_origin() { type PeopleCall = ::RuntimeCall; type PeopleRuntime = ::Runtime; + Dmp::make_parachain_reachable(1004); + let add_registrar_call = PeopleCall::Identity(pallet_identity::Call::::add_registrar { account: registrar.into(), @@ -191,6 +197,8 @@ fn relay_commands_kill_identity() { type RuntimeEvent = ::RuntimeEvent; type PeopleRuntime = ::Runtime; + Dmp::make_parachain_reachable(1004); + let kill_identity_call = PeopleCall::Identity(pallet_identity::Call::::kill_identity { target: people_westend_runtime::MultiAddress::Id(PeopleWestend::account_id_of( @@ -253,6 +261,8 @@ fn relay_commands_kill_identity_wrong_origin() { type RuntimeEvent = ::RuntimeEvent; type PeopleRuntime = ::Runtime; + Dmp::make_parachain_reachable(1004); + let kill_identity_call = PeopleCall::Identity(pallet_identity::Call::::kill_identity { target: people_westend_runtime::MultiAddress::Id(PeopleWestend::account_id_of( @@ -303,6 +313,8 @@ fn relay_commands_add_remove_username_authority() { type PeopleCall = ::RuntimeCall; type PeopleRuntime = ::Runtime; + Dmp::make_parachain_reachable(1004); + let add_username_authority = PeopleCall::Identity(pallet_identity::Call::::add_username_authority { authority: people_westend_runtime::MultiAddress::Id(people_westend_alice.clone()), @@ -392,6 +404,8 @@ fn relay_commands_add_remove_username_authority() { type PeopleCall = ::RuntimeCall; type PeopleRuntime = ::Runtime; + Dmp::make_parachain_reachable(1004); + let remove_username_authority = PeopleCall::Identity(pallet_identity::Call::< PeopleRuntime, >::remove_username_authority { @@ -455,6 +469,8 @@ fn relay_commands_add_remove_username_authority_wrong_origin() { type PeopleCall = ::RuntimeCall; type PeopleRuntime = ::Runtime; + Dmp::make_parachain_reachable(1004); + let add_username_authority = PeopleCall::Identity(pallet_identity::Call::< PeopleRuntime, >::add_username_authority { @@ -503,6 +519,8 @@ fn relay_commands_add_remove_username_authority_wrong_origin() { suffix: b"suffix1".into(), }); + Dmp::make_parachain_reachable(1004); + let remove_authority_xcm_msg = RuntimeCall::XcmPallet(pallet_xcm::Call::::send { dest: bx!(VersionedLocation::from(Location::new(0, [Parachain(1004)]))), diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index 949640dd4be6..81ebc7e09494 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -146,6 +146,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 8e47146a06c3..7dd2a4ab4b51 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -150,6 +150,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index fa9efbca7a39..552afa4daa68 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -66,4 +66,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index c0d6db5ad500..ff50223ef575 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -266,6 +266,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index f429a28a2e52..efdd0abbb8ee 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -263,6 +263,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml index 76a89bcb2e72..9eacb27639a3 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml @@ -41,4 +41,5 @@ runtime-benchmarks = [ "pallet-message-queue/runtime-benchmarks", "snowbridge-core/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index dc4b73db69e3..2e35fe761c04 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -138,6 +138,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index 1aeff5eb2e48..260c748819ae 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -173,6 +173,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index ab621134b252..aa692c3c7e74 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -180,6 +180,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index 44dfbf93c30e..226e1c817bb8 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -177,6 +177,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml index 9bbdb8d2ee08..f2922b710e24 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml @@ -77,6 +77,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] std = [ "codec/std", diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index 893133bf3c1a..4984f6314f87 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -176,6 +176,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index 66b324b51af4..7822df585a58 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -176,6 +176,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index 3a6b9d42f211..3bd1e5c6f436 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -175,6 +175,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml index 4713f4398eaa..035d0ac94be6 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml @@ -136,6 +136,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] # A feature that should be enabled when the runtime should be built for on-chain diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index f5ce040bb530..3bfb79610448 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -78,6 +78,7 @@ runtime-benchmarks = [ "people-rococo-runtime/runtime-benchmarks", "people-westend-runtime/runtime-benchmarks", "rococo-parachain-runtime/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "polkadot-omni-node-lib/try-runtime", diff --git a/cumulus/primitives/core/Cargo.toml b/cumulus/primitives/core/Cargo.toml index b5bfe4fbc889..307860897aec 100644 --- a/cumulus/primitives/core/Cargo.toml +++ b/cumulus/primitives/core/Cargo.toml @@ -43,4 +43,5 @@ runtime-benchmarks = [ "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/cumulus/primitives/utility/Cargo.toml b/cumulus/primitives/utility/Cargo.toml index 1444571edbe0..f26e34a29509 100644 --- a/cumulus/primitives/utility/Cargo.toml +++ b/cumulus/primitives/utility/Cargo.toml @@ -52,4 +52,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index 7f58a56d5d16..c1e06dd830b5 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -210,6 +210,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "westend-runtime?/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-system/try-runtime", diff --git a/polkadot/parachain/src/primitives.rs b/polkadot/parachain/src/primitives.rs index c5757928c3fc..1f2f9e2e9cdc 100644 --- a/polkadot/parachain/src/primitives.rs +++ b/polkadot/parachain/src/primitives.rs @@ -57,6 +57,8 @@ impl HeadData { } } +impl codec::EncodeLike for alloc::vec::Vec {} + /// Parachain validation code. #[derive( PartialEq, @@ -154,6 +156,9 @@ pub struct BlockData(#[cfg_attr(feature = "std", serde(with = "bytes"))] pub Vec #[cfg_attr(feature = "std", derive(derive_more::Display))] pub struct Id(u32); +impl codec::EncodeLike for Id {} +impl codec::EncodeLike for u32 {} + impl TypeId for Id { const TYPE_ID: [u8; 4] = *b"para"; } diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index 1646db54455a..4b307b56bcbe 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -142,6 +142,7 @@ runtime-benchmarks = [ "sp-staking/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-election-provider-support/try-runtime", diff --git a/polkadot/runtime/common/src/identity_migrator.rs b/polkadot/runtime/common/src/identity_migrator.rs index 126c886280e6..e3835b692526 100644 --- a/polkadot/runtime/common/src/identity_migrator.rs +++ b/polkadot/runtime/common/src/identity_migrator.rs @@ -160,12 +160,22 @@ pub trait OnReapIdentity { /// - `bytes`: The byte size of `IdentityInfo`. /// - `subs`: The number of sub-accounts they had. fn on_reap_identity(who: &AccountId, bytes: u32, subs: u32) -> DispatchResult; + + /// Ensure that identity reaping will be succesful in benchmarking. + /// + /// Should setup the state in a way that the same call ot `[Self::on_reap_identity]` will be + /// successful. + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_identity_reaping(who: &AccountId, bytes: u32, subs: u32); } impl OnReapIdentity for () { fn on_reap_identity(_who: &AccountId, _bytes: u32, _subs: u32) -> DispatchResult { Ok(()) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_identity_reaping(_: &AccountId, _: u32, _: u32) {} } #[cfg(feature = "runtime-benchmarks")] @@ -219,6 +229,12 @@ mod benchmarks { } Identity::::set_subs(target_origin.clone(), subs.clone())?; + T::ReapIdentityHandler::ensure_successful_identity_reaping( + &target, + info.encoded_size() as u32, + subs.len() as u32, + ); + // add registrars and provide judgements let registrar_origin = T::RegistrarOrigin::try_successful_origin() .expect("RegistrarOrigin has no successful origin required for the benchmark"); diff --git a/polkadot/runtime/common/src/paras_sudo_wrapper.rs b/polkadot/runtime/common/src/paras_sudo_wrapper.rs index a93c209e9279..bd5984b3b63e 100644 --- a/polkadot/runtime/common/src/paras_sudo_wrapper.rs +++ b/polkadot/runtime/common/src/paras_sudo_wrapper.rs @@ -48,6 +48,8 @@ pub mod pallet { /// A DMP message couldn't be sent because it exceeds the maximum size allowed for a /// downward message. ExceedsMaxMessageSize, + /// A DMP message couldn't be sent because the destination is unreachable. + Unroutable, /// Could not schedule para cleanup. CouldntCleanup, /// Not a parathread (on-demand parachain). @@ -157,6 +159,7 @@ pub mod pallet { { dmp::QueueDownwardMessageError::ExceedsMaxMessageSize => Error::::ExceedsMaxMessageSize.into(), + dmp::QueueDownwardMessageError::Unroutable => Error::::Unroutable.into(), }) } diff --git a/polkadot/runtime/common/src/xcm_sender.rs b/polkadot/runtime/common/src/xcm_sender.rs index 7ff7f69faf14..32ea4fdd2f27 100644 --- a/polkadot/runtime/common/src/xcm_sender.rs +++ b/polkadot/runtime/common/src/xcm_sender.rs @@ -138,6 +138,13 @@ where .map(|()| hash) .map_err(|_| SendError::Transport(&"Error placing into DMP queue")) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option) { + if let Some((0, [Parachain(id)])) = location.as_ref().map(|l| l.unpack()) { + dmp::Pallet::::make_parachain_reachable(*id); + } + } } impl InspectMessageQueues for ChildParachainRouter { @@ -190,7 +197,7 @@ impl< ExistentialDeposit: Get>, PriceForDelivery: PriceForMessageDelivery, Parachain: Get, - ToParachainHelper: EnsureForParachain, + ToParachainHelper: polkadot_runtime_parachains::EnsureForParachain, > xcm_builder::EnsureDelivery for ToParachainDeliveryHelper< XcmConfig, @@ -219,6 +226,9 @@ impl< return (None, None) } + // allow more initialization for target parachain + ToParachainHelper::ensure(Parachain::get()); + let mut fees_mode = None; if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) { // if not waived, we need to set up accounts for paying and receiving fees @@ -238,9 +248,6 @@ impl< XcmConfig::AssetTransactor::deposit_asset(&fee, &origin_ref, None).unwrap(); } - // allow more initialization for target parachain - ToParachainHelper::ensure(Parachain::get()); - // expected worst case - direct withdraw fees_mode = Some(FeesMode { jit_withdraw: true }); } @@ -248,18 +255,6 @@ impl< } } -/// Ensure more initialization for `ParaId`. (e.g. open HRMP channels, ...) -#[cfg(feature = "runtime-benchmarks")] -pub trait EnsureForParachain { - fn ensure(para_id: ParaId); -} -#[cfg(feature = "runtime-benchmarks")] -impl EnsureForParachain for () { - fn ensure(_: ParaId) { - // doing nothing - } -} - #[cfg(test)] mod tests { use super::*; @@ -349,6 +344,8 @@ mod tests { c.max_downward_message_size = u32::MAX; }); + dmp::Pallet::::make_parachain_reachable(5555); + // Check that the good message is validated: assert_ok!(::validate( &mut Some(dest.into()), diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index b01778eeb424..b583e9c6cc50 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -140,6 +140,7 @@ runtime-benchmarks = [ "sp-std", "static_assertions", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support-test/try-runtime", diff --git a/polkadot/runtime/parachains/src/coretime/benchmarking.rs b/polkadot/runtime/parachains/src/coretime/benchmarking.rs index 6d593f1954ff..49e3d8a88c01 100644 --- a/polkadot/runtime/parachains/src/coretime/benchmarking.rs +++ b/polkadot/runtime/parachains/src/coretime/benchmarking.rs @@ -43,6 +43,8 @@ mod benchmarks { .unwrap(); on_demand::Revenue::::put(rev); + crate::paras::Heads::::insert(ParaId::from(T::BrokerId::get()), vec![1, 2, 3]); + ::Currency::make_free_balance_be( &>::account_id(), minimum_balance * (mhr * (mhr + 1)).into(), diff --git a/polkadot/runtime/parachains/src/dmp.rs b/polkadot/runtime/parachains/src/dmp.rs index 03580e11b8e9..3c9cf8004186 100644 --- a/polkadot/runtime/parachains/src/dmp.rs +++ b/polkadot/runtime/parachains/src/dmp.rs @@ -44,7 +44,7 @@ use crate::{ configuration::{self, HostConfiguration}, - initializer, FeeTracker, + initializer, paras, FeeTracker, }; use alloc::vec::Vec; use core::fmt; @@ -72,12 +72,15 @@ const MESSAGE_SIZE_FEE_BASE: FixedU128 = FixedU128::from_rational(1, 1000); // 0 pub enum QueueDownwardMessageError { /// The message being sent exceeds the configured max message size. ExceedsMaxMessageSize, + /// The destination is unknown. + Unroutable, } impl From for SendError { fn from(err: QueueDownwardMessageError) -> Self { match err { QueueDownwardMessageError::ExceedsMaxMessageSize => SendError::ExceedsMaxMessageSize, + QueueDownwardMessageError::Unroutable => SendError::Unroutable, } } } @@ -116,7 +119,7 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + configuration::Config {} + pub trait Config: frame_system::Config + configuration::Config + paras::Config {} /// The downward messages addressed for a certain para. #[pallet::storage] @@ -200,6 +203,11 @@ impl Pallet { return Err(QueueDownwardMessageError::ExceedsMaxMessageSize) } + // If the head exists, we assume the parachain is legit and exists. + if !paras::Heads::::contains_key(para) { + return Err(QueueDownwardMessageError::Unroutable) + } + Ok(()) } @@ -217,14 +225,7 @@ impl Pallet { msg: DownwardMessage, ) -> Result<(), QueueDownwardMessageError> { let serialized_len = msg.len() as u32; - if serialized_len > config.max_downward_message_size { - return Err(QueueDownwardMessageError::ExceedsMaxMessageSize) - } - - // Hard limit on Queue size - if Self::dmq_length(para) > Self::dmq_max_length(config.max_downward_message_size) { - return Err(QueueDownwardMessageError::ExceedsMaxMessageSize) - } + Self::can_queue_downward_message(config, ¶, &msg)?; let inbound = InboundDownwardMessage { msg, sent_at: frame_system::Pallet::::block_number() }; @@ -336,6 +337,15 @@ impl Pallet { ) -> Vec>> { DownwardMessageQueues::::get(&recipient) } + + /// Make the parachain reachable for downward messages. + /// + /// Only useable in benchmarks or tests. + #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] + pub fn make_parachain_reachable(para: impl Into) { + let para = para.into(); + crate::paras::Heads::::insert(para, para.encode()); + } } impl FeeTracker for Pallet { @@ -359,3 +369,10 @@ impl FeeTracker for Pallet { }) } } + +#[cfg(feature = "runtime-benchmarks")] +impl crate::EnsureForParachain for Pallet { + fn ensure(para: ParaId) { + Self::make_parachain_reachable(para); + } +} diff --git a/polkadot/runtime/parachains/src/dmp/tests.rs b/polkadot/runtime/parachains/src/dmp/tests.rs index de1515958125..617c9488bd2a 100644 --- a/polkadot/runtime/parachains/src/dmp/tests.rs +++ b/polkadot/runtime/parachains/src/dmp/tests.rs @@ -61,6 +61,12 @@ fn queue_downward_message( Dmp::queue_downward_message(&configuration::ActiveConfig::::get(), para_id, msg) } +fn register_paras(paras: &[ParaId]) { + paras.iter().for_each(|p| { + Dmp::make_parachain_reachable(*p); + }); +} + #[test] fn clean_dmp_works() { let a = ParaId::from(1312); @@ -68,6 +74,8 @@ fn clean_dmp_works() { let c = ParaId::from(123); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a, b, c]); + // enqueue downward messages to A, B and C. queue_downward_message(a, vec![1, 2, 3]).unwrap(); queue_downward_message(b, vec![4, 5, 6]).unwrap(); @@ -89,6 +97,8 @@ fn dmq_length_and_head_updated_properly() { let b = ParaId::from(228); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a, b]); + assert_eq!(Dmp::dmq_length(a), 0); assert_eq!(Dmp::dmq_length(b), 0); @@ -101,11 +111,30 @@ fn dmq_length_and_head_updated_properly() { }); } +#[test] +fn dmq_fail_if_para_does_not_exist() { + let a = ParaId::from(1312); + + new_test_ext(default_genesis_config()).execute_with(|| { + assert_eq!(Dmp::dmq_length(a), 0); + + assert!(matches!( + queue_downward_message(a, vec![1, 2, 3]), + Err(QueueDownwardMessageError::Unroutable) + )); + + assert_eq!(Dmp::dmq_length(a), 0); + assert!(Dmp::dmq_mqc_head(a).is_zero()); + }); +} + #[test] fn dmp_mqc_head_fixture() { let a = ParaId::from(2000); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a]); + run_to_block(2, None); assert!(Dmp::dmq_mqc_head(a).is_zero()); queue_downward_message(a, vec![1, 2, 3]).unwrap(); @@ -125,6 +154,8 @@ fn check_processed_downward_messages() { let a = ParaId::from(1312); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a]); + let block_number = System::block_number(); // processed_downward_messages=0 is allowed when the DMQ is empty. @@ -150,6 +181,8 @@ fn check_processed_downward_messages_advancement_rule() { let a = ParaId::from(1312); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a]); + let block_number = System::block_number(); run_to_block(block_number + 1, None); @@ -170,6 +203,8 @@ fn dmq_pruning() { let a = ParaId::from(1312); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a]); + assert_eq!(Dmp::dmq_length(a), 0); queue_downward_message(a, vec![1, 2, 3]).unwrap(); @@ -194,6 +229,8 @@ fn queue_downward_message_critical() { genesis.configuration.config.max_downward_message_size = 7; new_test_ext(genesis).execute_with(|| { + register_paras(&[a]); + let smol = [0; 3].to_vec(); let big = [0; 8].to_vec(); @@ -215,6 +252,8 @@ fn verify_dmq_mqc_head_is_externally_accessible() { let a = ParaId::from(2020); new_test_ext(default_genesis_config()).execute_with(|| { + register_paras(&[a]); + let head = sp_io::storage::get(&well_known_keys::dmq_mqc_head(a)); assert_eq!(head, None); @@ -235,9 +274,12 @@ fn verify_dmq_mqc_head_is_externally_accessible() { #[test] fn verify_fee_increase_and_decrease() { let a = ParaId::from(123); + let mut genesis = default_genesis_config(); genesis.configuration.config.max_downward_message_size = 16777216; new_test_ext(genesis).execute_with(|| { + register_paras(&[a]); + let initial = InitialFactor::get(); assert_eq!(DeliveryFeeFactor::::get(a), initial); @@ -287,6 +329,8 @@ fn verify_fee_factor_reaches_high_value() { let mut genesis = default_genesis_config(); genesis.configuration.config.max_downward_message_size = 51200; new_test_ext(genesis).execute_with(|| { + register_paras(&[a]); + let max_messages = Dmp::dmq_max_length(ActiveConfig::::get().max_downward_message_size); let mut total_fee_factor = FixedU128::from_float(1.0); diff --git a/polkadot/runtime/parachains/src/lib.rs b/polkadot/runtime/parachains/src/lib.rs index 828c0b9bcef2..b1ff5419470e 100644 --- a/polkadot/runtime/parachains/src/lib.rs +++ b/polkadot/runtime/parachains/src/lib.rs @@ -114,3 +114,19 @@ pub fn schedule_code_upgrade( pub fn set_current_head(id: ParaId, new_head: HeadData) { paras::Pallet::::set_current_head(id, new_head) } + +/// Ensure more initialization for `ParaId` when benchmarking. (e.g. open HRMP channels, ...) +#[cfg(feature = "runtime-benchmarks")] +pub trait EnsureForParachain { + fn ensure(para_id: ParaId); +} + +#[cfg(feature = "runtime-benchmarks")] +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl EnsureForParachain for Tuple { + fn ensure(para: ParaId) { + for_tuples!( #( + Tuple::ensure(para); + )* ); + } +} diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 764c53abbfcb..1fd32c5d0c32 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -277,6 +277,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-executive/try-runtime", diff --git a/polkadot/runtime/rococo/src/impls.rs b/polkadot/runtime/rococo/src/impls.rs index 7d7e9fa9f06c..a5cb2eddfa0d 100644 --- a/polkadot/runtime/rococo/src/impls.rs +++ b/polkadot/runtime/rococo/src/impls.rs @@ -176,4 +176,9 @@ where )?; Ok(()) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_identity_reaping(_: &AccountId, _: u32, _: u32) { + crate::Dmp::make_parachain_reachable(1004); + } } diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index c832ace91c07..3304f89fc0cc 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -2471,14 +2471,14 @@ sp_api::impl_runtime_apis! { ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, AssetHubParaId, - (), + Dmp, >, polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< XcmConfig, ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, RandomParaId, - (), + Dmp, > ); @@ -2537,7 +2537,7 @@ sp_api::impl_runtime_apis! { ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, AssetHubParaId, - (), + Dmp, >; fn valid_destination() -> Result { Ok(AssetHub::get()) diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index 90a0285cd17b..8b33bf9cebc6 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -154,4 +154,5 @@ runtime-benchmarks = [ "sp-staking/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 584f5855b7a4..13e39b5aa317 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -298,6 +298,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-election-provider-support/try-runtime", diff --git a/polkadot/runtime/westend/src/impls.rs b/polkadot/runtime/westend/src/impls.rs index 8cb597cbaa95..0e0d345a0ed4 100644 --- a/polkadot/runtime/westend/src/impls.rs +++ b/polkadot/runtime/westend/src/impls.rs @@ -176,4 +176,9 @@ where )?; Ok(()) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_identity_reaping(_: &AccountId, _: u32, _: u32) { + crate::Dmp::make_parachain_reachable(1004); + } } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index c540b3773286..f9ef74fee29c 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -2639,14 +2639,14 @@ sp_api::impl_runtime_apis! { ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, AssetHubParaId, - (), + Dmp, >, polkadot_runtime_common::xcm_sender::ToParachainDeliveryHelper< xcm_config::XcmConfig, ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, RandomParaId, - (), + Dmp, > ); @@ -2712,7 +2712,7 @@ sp_api::impl_runtime_apis! { ExistentialDepositAsset, xcm_config::PriceForChildParachainDelivery, AssetHubParaId, - (), + Dmp, >; fn valid_destination() -> Result { Ok(AssetHub::get()) diff --git a/polkadot/xcm/Cargo.toml b/polkadot/xcm/Cargo.toml index 113e72c27ae1..7ac12dc1e377 100644 --- a/polkadot/xcm/Cargo.toml +++ b/polkadot/xcm/Cargo.toml @@ -51,3 +51,7 @@ json-schema = [ "dep:schemars", "sp-weights/json-schema", ] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] diff --git a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml index fe2b78163223..d4131cc53ee6 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml +++ b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml @@ -64,4 +64,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs index 303ff9493f71..4428076aa077 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/fungible/benchmarking.rs @@ -231,6 +231,13 @@ benchmarks_instance_pallet! { let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap(); assert!(T::TransactAsset::balance(&dest_account).is_zero()); + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &dest_location, + FeeReason::ChargeFees, + ); + let mut executor = new_executor::(Default::default()); executor.set_holding(holding.into()); let instruction = Instruction::>::DepositAsset { @@ -257,6 +264,13 @@ benchmarks_instance_pallet! { let dest_account = T::AccountIdConverter::convert_location(&dest_location).unwrap(); assert!(T::TransactAsset::balance(&dest_account).is_zero()); + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &dest_location, + FeeReason::ChargeFees, + ); + let mut executor = new_executor::(Default::default()); executor.set_holding(holding.into()); let instruction = Instruction::>::DepositReserveAsset { @@ -281,12 +295,20 @@ benchmarks_instance_pallet! { // Checked account starts at zero assert!(T::CheckedAccount::get().map_or(true, |(c, _)| T::TransactAsset::balance(&c).is_zero())); + let dest_location = T::valid_destination()?; + + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &dest_location, + FeeReason::ChargeFees, + ); let mut executor = new_executor::(Default::default()); executor.set_holding(holding.into()); let instruction = Instruction::>::InitiateTeleport { assets: asset.into(), - dest: T::valid_destination()?, + dest: dest_location, xcm: Xcm::new(), }; let xcm = Xcm(vec![instruction]); @@ -303,6 +325,15 @@ benchmarks_instance_pallet! { let (sender_account, sender_location) = account_and_location::(1); let asset = T::get_asset(); let mut holding = T::worst_case_holding(1); + let dest_location = T::valid_destination()?; + + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + &sender_location, + &dest_location, + FeeReason::ChargeFees, + ); + let sender_account_balance_before = T::TransactAsset::balance(&sender_account); // Add our asset to the holding. @@ -311,7 +342,7 @@ benchmarks_instance_pallet! { let mut executor = new_executor::(sender_location); executor.set_holding(holding.into()); let instruction = Instruction::>::InitiateTransfer { - destination: T::valid_destination()?, + destination: dest_location, // ReserveDeposit is the most expensive filter. remote_fees: Some(AssetTransferFilter::ReserveDeposit(asset.clone().into())), // It's more expensive if we reanchor the origin. diff --git a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs index 84d4cba1dbe1..1c62bb5886d8 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm-benchmarks/src/generic/benchmarking.rs @@ -407,6 +407,9 @@ mod benchmarks { let mut executor = new_executor::(origin.clone()); let instruction = Instruction::SubscribeVersion { query_id, max_response_weight }; let xcm = Xcm(vec![instruction]); + + T::DeliveryHelper::ensure_successful_delivery(&origin, &origin, FeeReason::QueryPallet); + #[block] { executor.bench_process(xcm)?; @@ -422,6 +425,9 @@ mod benchmarks { use xcm_executor::traits::VersionChangeNotifier; // First we need to subscribe to notifications. let (origin, _) = T::transact_origin_and_runtime_call()?; + + T::DeliveryHelper::ensure_successful_delivery(&origin, &origin, FeeReason::QueryPallet); + let query_id = Default::default(); let max_response_weight = Default::default(); ::SubscriptionService::start( diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index e8cdd3b4931b..81fcea05cac2 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -70,6 +70,7 @@ runtime-benchmarks = [ "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", "xcm-runtime-apis/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/polkadot/xcm/pallet-xcm/src/benchmarking.rs b/polkadot/xcm/pallet-xcm/src/benchmarking.rs index e493d4838f5c..dd3c58c5dc77 100644 --- a/polkadot/xcm/pallet-xcm/src/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm/src/benchmarking.rs @@ -96,6 +96,13 @@ benchmarks! { )? .into(); let versioned_msg = VersionedXcm::from(msg); + + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &versioned_dest.clone().try_into().unwrap(), + FeeReason::ChargeFees, + ); }: _>(send_origin, Box::new(versioned_dest), Box::new(versioned_msg)) teleport_assets { @@ -164,7 +171,7 @@ benchmarks! { } // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) - let (_, _) = T::DeliveryHelper::ensure_successful_delivery( + T::DeliveryHelper::ensure_successful_delivery( &origin_location, &destination, FeeReason::ChargeFees, @@ -227,6 +234,13 @@ benchmarks! { let versioned_beneficiary: VersionedLocation = AccountId32 { network: None, id: recipient.into() }.into(); let versioned_assets: VersionedAssets = assets.into(); + + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &versioned_dest.clone().try_into().unwrap(), + FeeReason::ChargeFees, + ); }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0, WeightLimit::Unlimited) verify { // run provided verification function @@ -259,6 +273,14 @@ benchmarks! { BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), )? .into(); + + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &versioned_loc.clone().try_into().unwrap(), + FeeReason::ChargeFees, + ); + }: _(RawOrigin::Root, Box::new(versioned_loc)) force_unsubscribe_version_notify { @@ -266,6 +288,14 @@ benchmarks! { BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), )?; let versioned_loc: VersionedLocation = loc.clone().into(); + + // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + T::DeliveryHelper::ensure_successful_delivery( + &Default::default(), + &versioned_loc.clone().try_into().unwrap(), + FeeReason::ChargeFees, + ); + let _ = crate::Pallet::::request_version_notify(loc); }: _(RawOrigin::Root, Box::new(versioned_loc)) diff --git a/polkadot/xcm/src/v5/traits.rs b/polkadot/xcm/src/v5/traits.rs index 71b67e97d5fe..79d328561428 100644 --- a/polkadot/xcm/src/v5/traits.rs +++ b/polkadot/xcm/src/v5/traits.rs @@ -460,6 +460,10 @@ pub trait SendXcm { /// Actually carry out the delivery operation for a previously validated message sending. fn deliver(ticket: Self::Ticket) -> result::Result; + + /// Ensure `[Self::delivery]` is successful for the given `location` when called in benchmarks. + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(_location: Option) {} } #[impl_trait_for_tuples::impl_for_tuples(30)] @@ -500,6 +504,13 @@ impl SendXcm for Tuple { )* ); Err(SendError::Unroutable) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option) { + for_tuples!( #( + return Tuple::ensure_successful_delivery(location.clone()); + )* ); + } } /// Convenience function for using a `SendXcm` implementation. Just interprets the `dest` and wraps diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index 2819a0b0a555..e64ab1928132 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -59,6 +59,7 @@ runtime-benchmarks = [ "polkadot-test-runtime/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] std = [ "codec/std", diff --git a/polkadot/xcm/xcm-builder/src/pay.rs b/polkadot/xcm/xcm-builder/src/pay.rs index 978c6870cdaf..0093051290b7 100644 --- a/polkadot/xcm/xcm-builder/src/pay.rs +++ b/polkadot/xcm/xcm-builder/src/pay.rs @@ -70,8 +70,8 @@ impl< Router: SendXcm, Querier: QueryHandler, Timeout: Get, - Beneficiary: Clone, - AssetKind, + Beneficiary: Clone + core::fmt::Debug, + AssetKind: core::fmt::Debug, AssetKindToLocatableAsset: TryConvert, BeneficiaryRefToLocation: for<'a> TryConvert<&'a Beneficiary, Location>, > Pay @@ -144,10 +144,9 @@ impl< } #[cfg(feature = "runtime-benchmarks")] - fn ensure_successful(_: &Self::Beneficiary, _: Self::AssetKind, _: Self::Balance) { - // We cannot generally guarantee this will go through successfully since we don't have any - // control over the XCM transport layers. We just assume that the benchmark environment - // will be sending it somewhere sensible. + fn ensure_successful(_: &Self::Beneficiary, asset_kind: Self::AssetKind, _: Self::Balance) { + let locatable = AssetKindToLocatableAsset::try_convert(asset_kind).unwrap(); + Router::ensure_successful_delivery(Some(locatable.location)); } #[cfg(feature = "runtime-benchmarks")] diff --git a/polkadot/xcm/xcm-builder/src/routing.rs b/polkadot/xcm/xcm-builder/src/routing.rs index fc2de89d2128..5b0d0a5f9835 100644 --- a/polkadot/xcm/xcm-builder/src/routing.rs +++ b/polkadot/xcm/xcm-builder/src/routing.rs @@ -60,6 +60,11 @@ impl SendXcm for WithUniqueTopic { Inner::deliver(ticket)?; Ok(unique_id) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option) { + Inner::ensure_successful_delivery(location); + } } impl InspectMessageQueues for WithUniqueTopic { fn clear_messages() { @@ -114,6 +119,11 @@ impl SendXcm for WithTopicSource) { + Inner::ensure_successful_delivery(location); + } } /// Trait for a type which ensures all requirements for successful delivery with XCM transport @@ -211,4 +221,9 @@ impl SendXcm for EnsureDecodableXcm { fn deliver(ticket: Self::Ticket) -> Result { Inner::deliver(ticket) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option) { + Inner::ensure_successful_delivery(location); + } } diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs b/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs index 062faee2abd9..b4718edc6c98 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/pay.rs @@ -22,7 +22,7 @@ use frame_support::{assert_ok, traits::tokens::Pay}; /// Type representing both a location and an asset that is held at that location. /// The id of the held asset is relative to the location where it is being held. -#[derive(Encode, Decode, Clone, PartialEq, Eq)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug)] pub struct AssetKind { destination: Location, asset_id: AssetId, diff --git a/polkadot/xcm/xcm-builder/src/universal_exports.rs b/polkadot/xcm/xcm-builder/src/universal_exports.rs index aae8438c78d2..6b3c3adf737d 100644 --- a/polkadot/xcm/xcm-builder/src/universal_exports.rs +++ b/polkadot/xcm/xcm-builder/src/universal_exports.rs @@ -95,6 +95,9 @@ impl> SendXcm fn deliver(ticket: Exporter::Ticket) -> Result { Exporter::deliver(ticket) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(_: Option) {} } pub trait ExporterFor { @@ -261,6 +264,11 @@ impl Result { Router::deliver(validation) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option) { + Router::ensure_successful_delivery(location); + } } /// Implementation of `SendXcm` which wraps the message inside an `ExportMessage` instruction @@ -361,6 +369,11 @@ impl Result { Router::deliver(ticket) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(location: Option) { + Router::ensure_successful_delivery(location); + } } impl InspectMessageQueues @@ -613,6 +626,9 @@ mod tests { fn deliver(_ticket: Self::Ticket) -> Result { Ok([0; 32]) } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(_: Option) {} } impl> ExportXcm for OkFor { type Ticket = (); diff --git a/polkadot/xcm/xcm-executor/Cargo.toml b/polkadot/xcm/xcm-executor/Cargo.toml index 20ca40de5faa..eb558c0fcc19 100644 --- a/polkadot/xcm/xcm-executor/Cargo.toml +++ b/polkadot/xcm/xcm-executor/Cargo.toml @@ -32,6 +32,7 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "xcm/runtime-benchmarks", ] std = [ "codec/std", diff --git a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml index 7e6bfe967b90..a89dd74a44fa 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml +++ b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml @@ -13,9 +13,12 @@ workspace = true [dependencies] codec = { workspace = true, default-features = true } frame-support = { workspace = true } +frame-system = { workspace = true, default-features = true } futures = { workspace = true } pallet-transaction-payment = { workspace = true, default-features = true } +pallet-sudo = { workspace = true, default-features = true } pallet-xcm = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-test-client = { workspace = true } polkadot-test-runtime = { workspace = true } polkadot-test-service = { workspace = true } @@ -30,4 +33,4 @@ sp-core = { workspace = true, default-features = true } [features] default = ["std"] -std = ["frame-support/std", "sp-runtime/std", "xcm/std"] +std = ["frame-support/std", "frame-system/std", "pallet-sudo/std", "polkadot-runtime-parachains/std", "sp-runtime/std", "xcm/std"] diff --git a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs index 699a081e4f22..dfcc3fc4187f 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs +++ b/polkadot/xcm/xcm-executor/integration-tests/src/lib.rs @@ -375,6 +375,26 @@ fn deposit_reserve_asset_works_for_any_xcm_sender() { let mut block_builder = client.init_polkadot_block_builder(); + // Make the para available, so that `DMP` doesn't reject the XCM because the para is unknown. + let make_para_available = + construct_extrinsic( + &client, + polkadot_test_runtime::RuntimeCall::Sudo(pallet_sudo::Call::sudo { + call: Box::new(polkadot_test_runtime::RuntimeCall::System( + frame_system::Call::set_storage { + items: vec![( + polkadot_runtime_parachains::paras::Heads::< + polkadot_test_runtime::Runtime, + >::hashed_key_for(2000u32), + vec![1, 2, 3], + )], + }, + )), + }), + sp_keyring::Sr25519Keyring::Alice, + 0, + ); + // Simulate execution of an incoming XCM message at the reserve chain let execute = construct_extrinsic( &client, @@ -383,9 +403,12 @@ fn deposit_reserve_asset_works_for_any_xcm_sender() { max_weight: Weight::from_parts(1_000_000_000, 1024 * 1024), }), sp_keyring::Sr25519Keyring::Alice, - 0, + 1, ); + block_builder + .push_polkadot_extrinsic(make_para_available) + .expect("pushes extrinsic"); block_builder.push_polkadot_extrinsic(execute).expect("pushes extrinsic"); let block = block_builder.build().expect("Finalizes the block").block; diff --git a/polkadot/xcm/xcm-runtime-apis/Cargo.toml b/polkadot/xcm/xcm-runtime-apis/Cargo.toml index 9ccca76c321c..9ada69a1933b 100644 --- a/polkadot/xcm/xcm-runtime-apis/Cargo.toml +++ b/polkadot/xcm/xcm-runtime-apis/Cargo.toml @@ -60,4 +60,5 @@ runtime-benchmarks = [ "pallet-xcm/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/polkadot/xcm/xcm-simulator/example/Cargo.toml b/polkadot/xcm/xcm-simulator/example/Cargo.toml index 6fbe9243944a..43f36fc8991a 100644 --- a/polkadot/xcm/xcm-simulator/example/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/example/Cargo.toml @@ -50,4 +50,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml index 04f8ba115173..a2e36db95ba6 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml @@ -59,6 +59,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] [[bin]] diff --git a/prdoc/pr_6604.prdoc b/prdoc/pr_6604.prdoc new file mode 100644 index 000000000000..dc198287ff67 --- /dev/null +++ b/prdoc/pr_6604.prdoc @@ -0,0 +1,106 @@ +title: 'dmp: Check that the para exist before delivering a message' +doc: +- audience: Runtime Dev + description: | + Ensure that a para exists before trying to deliver a message to it. + Besides that `ensure_successful_delivery` function is added to `SendXcm`. This function + should be used by benchmarks to ensure that the delivery of a Xcm will work in the benchmark. +crates: +- name: polkadot-runtime-parachains + bump: major +- name: polkadot-runtime-common + bump: major +- name: polkadot-parachain-primitives + bump: major +- name: rococo-runtime + bump: major +- name: westend-runtime + bump: major +- name: pallet-xcm-benchmarks + bump: major +- name: pallet-xcm + bump: major +- name: cumulus-pallet-parachain-system + bump: major +- name: staging-xcm + bump: major +- name: staging-xcm-builder + bump: major +- name: bridge-runtime-common + bump: major +- name: pallet-xcm-bridge-hub-router + bump: major +- name: pallet-xcm-bridge-hub + bump: major +- name: snowbridge-pallet-inbound-queue + bump: major +- name: snowbridge-pallet-system + bump: major +- name: snowbridge-core + bump: major +- name: snowbridge-router-primitives + bump: major +- name: snowbridge-runtime-common + bump: major +- name: snowbridge-runtime-test-common + bump: major +- name: cumulus-pallet-dmp-queue + bump: major +- name: cumulus-pallet-xcmp-queue + bump: major +- name: parachains-common + bump: major +- name: asset-hub-rococo-runtime + bump: major +- name: asset-hub-westend-runtime + bump: major +- name: assets-common + bump: major +- name: bridge-hub-rococo-runtime + bump: major +- name: bridge-hub-westend-runtime + bump: major +- name: bridge-hub-common + bump: major +- name: collectives-westend-runtime + bump: major +- name: contracts-rococo-runtime + bump: major +- name: coretime-rococo-runtime + bump: major +- name: coretime-westend-runtime + bump: major +- name: glutton-westend-runtime + bump: major +- name: people-rococo-runtime + bump: major +- name: people-westend-runtime + bump: major +- name: penpal-runtime + bump: major +- name: rococo-parachain-runtime + bump: major +- name: polkadot-parachain-bin + bump: major +- name: cumulus-primitives-core + bump: major +- name: cumulus-primitives-utility + bump: major +- name: polkadot-service + bump: major +- name: staging-xcm-executor + bump: major +- name: xcm-runtime-apis + bump: major +- name: xcm-simulator-example + bump: major +- name: pallet-contracts + bump: major +- name: pallet-contracts-mock-network + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-mock-network + bump: major +- name: polkadot-sdk + bump: major diff --git a/substrate/frame/contracts/Cargo.toml b/substrate/frame/contracts/Cargo.toml index 316ea6813048..96351752918a 100644 --- a/substrate/frame/contracts/Cargo.toml +++ b/substrate/frame/contracts/Cargo.toml @@ -119,6 +119,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "wasm-instrument", "xcm-builder/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/substrate/frame/contracts/mock-network/Cargo.toml b/substrate/frame/contracts/mock-network/Cargo.toml index d6e2d51ef452..66137bc8a0c6 100644 --- a/substrate/frame/contracts/mock-network/Cargo.toml +++ b/substrate/frame/contracts/mock-network/Cargo.toml @@ -87,4 +87,5 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 2e069bacf737..e61554f5cfa0 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -119,6 +119,7 @@ runtime-benchmarks = [ "pallet-utility/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/substrate/frame/revive/mock-network/Cargo.toml b/substrate/frame/revive/mock-network/Cargo.toml index c5b18b3fa290..6208db45a91e 100644 --- a/substrate/frame/revive/mock-network/Cargo.toml +++ b/substrate/frame/revive/mock-network/Cargo.toml @@ -85,6 +85,7 @@ runtime-benchmarks = [ "sp-runtime/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm-executor/runtime-benchmarks", + "xcm/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index d5ef707d2b8f..68d71b4a5d5e 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -362,6 +362,7 @@ runtime-benchmarks = [ "staging-node-inspect?/runtime-benchmarks", "staging-xcm-builder?/runtime-benchmarks", "staging-xcm-executor?/runtime-benchmarks", + "staging-xcm?/runtime-benchmarks", "xcm-runtime-apis?/runtime-benchmarks", ] try-runtime = [ From 459b4a6521d35f3e84a036262e64fa547a5b1ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=B3nal=20Murray?= Date: Thu, 12 Dec 2024 21:23:25 +0100 Subject: [PATCH 156/340] [pallet-broker] Fix auto renew benchmarks (#6505) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the broker pallet auto-renew benchmarks which have been broken since #4424, yielding `Weightless` due to some prices being set too low, as reported in #6474. Upon further investigation it turned out that the auto-renew contribution to `rotate_sale` was always failing but the error was mapped. This is also fixed at the cost of a bit of setup overhead. Fixes #6474 TODO: - [x] Re-run weights --------- Co-authored-by: GitHub Action Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- .../src/weights/pallet_broker.rs | 246 +++++---- .../src/weights/pallet_broker.rs | 250 ++++----- prdoc/pr_6505.prdoc | 14 + substrate/frame/broker/src/benchmarking.rs | 191 ++++--- substrate/frame/broker/src/weights.rs | 486 +++++++++--------- 5 files changed, 639 insertions(+), 548 deletions(-) create mode 100644 prdoc/pr_6505.prdoc diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs index 35708f22de20..5cb01f62cd26 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_broker` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-06-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-x5tnzzy-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-acd6uxux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -54,8 +54,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_024_000 picoseconds. - Weight::from_parts(2_121_000, 0) + // Minimum execution time: 2_250_000 picoseconds. + Weight::from_parts(2_419_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,8 +65,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `10888` // Estimated: `13506` - // Minimum execution time: 21_654_000 picoseconds. - Weight::from_parts(22_591_000, 0) + // Minimum execution time: 25_785_000 picoseconds. + Weight::from_parts(26_335_000, 0) .saturating_add(Weight::from_parts(0, 13506)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -77,8 +77,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `12090` // Estimated: `13506` - // Minimum execution time: 20_769_000 picoseconds. - Weight::from_parts(21_328_000, 0) + // Minimum execution time: 24_549_000 picoseconds. + Weight::from_parts(25_010_000, 0) .saturating_add(Weight::from_parts(0, 13506)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -93,8 +93,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `466` // Estimated: `1951` - // Minimum execution time: 10_404_000 picoseconds. - Weight::from_parts(10_941_000, 0) + // Minimum execution time: 14_135_000 picoseconds. + Weight::from_parts(14_603_000, 0) .saturating_add(Weight::from_parts(0, 1951)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -121,6 +121,8 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::InstaPoolIo` (r:3 w:3) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(1002), added: 1497, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:0 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::Status` (r:0 w:1) @@ -132,31 +134,33 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `12599` // Estimated: `15065 + n * (1 ±0)` - // Minimum execution time: 44_085_000 picoseconds. - Weight::from_parts(127_668_002, 0) + // Minimum execution time: 54_087_000 picoseconds. + Weight::from_parts(145_466_213, 0) .saturating_add(Weight::from_parts(0, 15065)) - // Standard Error: 2_231 - .saturating_add(Weight::from_parts(20_604, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(59)) + // Standard Error: 2_407 + .saturating_add(Weight::from_parts(20_971, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(14)) + .saturating_add(T::DbWeight::get().writes(60)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Broker::Status` (r:1 w:0) /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Broker::Regions` (r:0 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) fn purchase() -> Weight { // Proof Size summary in bytes: - // Measured: `332` + // Measured: `437` // Estimated: `3593` - // Minimum execution time: 45_100_000 picoseconds. - Weight::from_parts(46_263_000, 0) + // Minimum execution time: 58_341_000 picoseconds. + Weight::from_parts(59_505_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Broker::Configuration` (r:1 w:0) @@ -169,16 +173,18 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn renew() -> Weight { // Proof Size summary in bytes: - // Measured: `553` + // Measured: `658` // Estimated: `4698` - // Minimum execution time: 65_944_000 picoseconds. - Weight::from_parts(68_666_000, 0) + // Minimum execution time: 92_983_000 picoseconds. + Weight::from_parts(99_237_000, 0) .saturating_add(Weight::from_parts(0, 4698)) - .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `Broker::Regions` (r:1 w:1) @@ -187,8 +193,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 13_794_000 picoseconds. - Weight::from_parts(14_450_000, 0) + // Minimum execution time: 17_512_000 picoseconds. + Weight::from_parts(18_099_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -199,8 +205,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 15_316_000 picoseconds. - Weight::from_parts(15_787_000, 0) + // Minimum execution time: 18_715_000 picoseconds. + Weight::from_parts(19_768_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -211,8 +217,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 16_375_000 picoseconds. - Weight::from_parts(17_113_000, 0) + // Minimum execution time: 20_349_000 picoseconds. + Weight::from_parts(21_050_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(3)) @@ -229,8 +235,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `937` // Estimated: `4681` - // Minimum execution time: 25_952_000 picoseconds. - Weight::from_parts(27_198_000, 0) + // Minimum execution time: 31_876_000 picoseconds. + Weight::from_parts(33_536_000, 0) .saturating_add(Weight::from_parts(0, 4681)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -249,8 +255,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1003` // Estimated: `5996` - // Minimum execution time: 31_790_000 picoseconds. - Weight::from_parts(32_920_000, 0) + // Minimum execution time: 39_500_000 picoseconds. + Weight::from_parts(40_666_000, 0) .saturating_add(Weight::from_parts(0, 5996)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) @@ -264,13 +270,13 @@ impl pallet_broker::WeightInfo for WeightInfo { /// The range of component `m` is `[1, 3]`. fn claim_revenue(m: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `652` + // Measured: `671` // Estimated: `6196 + m * (2520 ±0)` - // Minimum execution time: 56_286_000 picoseconds. - Weight::from_parts(56_946_240, 0) + // Minimum execution time: 65_843_000 picoseconds. + Weight::from_parts(65_768_512, 0) .saturating_add(Weight::from_parts(0, 6196)) - // Standard Error: 44_472 - .saturating_add(Weight::from_parts(1_684_838, 0).saturating_mul(m.into())) + // Standard Error: 40_994 + .saturating_add(Weight::from_parts(2_084_877, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -290,11 +296,11 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn purchase_credit() -> Weight { // Proof Size summary in bytes: - // Measured: `322` - // Estimated: `3787` - // Minimum execution time: 64_967_000 picoseconds. - Weight::from_parts(66_504_000, 0) - .saturating_add(Weight::from_parts(0, 3787)) + // Measured: `323` + // Estimated: `3788` + // Minimum execution time: 73_250_000 picoseconds. + Weight::from_parts(75_059_000, 0) + .saturating_add(Weight::from_parts(0, 3788)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -306,8 +312,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `466` // Estimated: `3551` - // Minimum execution time: 37_552_000 picoseconds. - Weight::from_parts(46_263_000, 0) + // Minimum execution time: 55_088_000 picoseconds. + Weight::from_parts(65_329_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -322,8 +328,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `463` // Estimated: `3533` - // Minimum execution time: 79_625_000 picoseconds. - Weight::from_parts(86_227_000, 0) + // Minimum execution time: 102_280_000 picoseconds. + Weight::from_parts(130_319_000, 0) .saturating_add(Weight::from_parts(0, 3533)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -338,10 +344,10 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn drop_history() -> Weight { // Proof Size summary in bytes: - // Measured: `857` + // Measured: `979` // Estimated: `3593` - // Minimum execution time: 88_005_000 picoseconds. - Weight::from_parts(92_984_000, 0) + // Minimum execution time: 78_195_000 picoseconds. + Weight::from_parts(105_946_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) @@ -354,8 +360,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `957` // Estimated: `4698` - // Minimum execution time: 38_877_000 picoseconds. - Weight::from_parts(40_408_000, 0) + // Minimum execution time: 41_642_000 picoseconds. + Weight::from_parts(48_286_000, 0) .saturating_add(Weight::from_parts(0, 4698)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -371,15 +377,13 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// The range of component `n` is `[0, 1000]`. - fn request_core_count(n: u32, ) -> Weight { + fn request_core_count(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 20_581_000 picoseconds. - Weight::from_parts(21_610_297, 0) + // Minimum execution time: 23_727_000 picoseconds. + Weight::from_parts(25_029_439, 0) .saturating_add(Weight::from_parts(0, 3539)) - // Standard Error: 119 - .saturating_add(Weight::from_parts(144, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -390,11 +394,11 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `266` // Estimated: `1487` - // Minimum execution time: 6_079_000 picoseconds. - Weight::from_parts(6_540_110, 0) + // Minimum execution time: 7_887_000 picoseconds. + Weight::from_parts(8_477_863, 0) .saturating_add(Weight::from_parts(0, 1487)) - // Standard Error: 14 - .saturating_add(Weight::from_parts(10, 0).saturating_mul(n.into())) + // Standard Error: 18 + .saturating_add(Weight::from_parts(76, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -406,36 +410,50 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn process_revenue() -> Weight { // Proof Size summary in bytes: - // Measured: `442` + // Measured: `461` // Estimated: `6196` - // Minimum execution time: 42_947_000 picoseconds. - Weight::from_parts(43_767_000, 0) + // Minimum execution time: 52_505_000 picoseconds. + Weight::from_parts(53_392_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::InstaPoolIo` (r:3 w:3) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) /// Storage: `Broker::Reservations` (r:1 w:0) /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(401), added: 896, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(1002), added: 1497, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::PotentialRenewals` (r:100 w:200) + /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:101 w:101) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:0 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) - /// Storage: `Broker::Workplan` (r:0 w:60) + /// Storage: `Broker::Workplan` (r:0 w:1000) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. fn rotate_sale(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `12514` - // Estimated: `13506` - // Minimum execution time: 93_426_000 picoseconds. - Weight::from_parts(96_185_447, 0) - .saturating_add(Weight::from_parts(0, 13506)) - // Standard Error: 116 - .saturating_add(Weight::from_parts(4, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(65)) + // Measured: `32497` + // Estimated: `233641 + n * (198 ±9)` + // Minimum execution time: 28_834_000 picoseconds. + Weight::from_parts(2_467_159_777, 0) + .saturating_add(Weight::from_parts(0, 233641)) + // Standard Error: 149_483 + .saturating_add(Weight::from_parts(4_045_956, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(126)) + .saturating_add(T::DbWeight::get().writes(181)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 198).saturating_mul(n.into())) } /// Storage: `Broker::InstaPoolIo` (r:1 w:0) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) @@ -445,8 +463,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `42` // Estimated: `3493` - // Minimum execution time: 5_842_000 picoseconds. - Weight::from_parts(6_077_000, 0) + // Minimum execution time: 7_689_000 picoseconds. + Weight::from_parts(7_988_000, 0) .saturating_add(Weight::from_parts(0, 3493)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -469,8 +487,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1321` // Estimated: `4786` - // Minimum execution time: 33_278_000 picoseconds. - Weight::from_parts(34_076_000, 0) + // Minimum execution time: 37_394_000 picoseconds. + Weight::from_parts(38_379_000, 0) .saturating_add(Weight::from_parts(0, 4786)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -489,8 +507,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 15_779_000 picoseconds. - Weight::from_parts(16_213_000, 0) + // Minimum execution time: 19_203_000 picoseconds. + Weight::from_parts(19_797_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -501,8 +519,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_774_000 picoseconds. - Weight::from_parts(1_873_000, 0) + // Minimum execution time: 2_129_000 picoseconds. + Weight::from_parts(2_266_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -512,8 +530,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_858_000 picoseconds. - Weight::from_parts(1_991_000, 0) + // Minimum execution time: 2_233_000 picoseconds. + Weight::from_parts(2_351_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -531,8 +549,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `408` // Estimated: `1893` - // Minimum execution time: 10_874_000 picoseconds. - Weight::from_parts(11_265_000, 0) + // Minimum execution time: 15_716_000 picoseconds. + Weight::from_parts(16_160_000, 0) .saturating_add(Weight::from_parts(0, 1893)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -543,8 +561,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `470` // Estimated: `1886` - // Minimum execution time: 6_525_000 picoseconds. - Weight::from_parts(6_769_000, 0) + // Minimum execution time: 8_887_000 picoseconds. + Weight::from_parts(9_178_000, 0) .saturating_add(Weight::from_parts(0, 1886)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -557,36 +575,36 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) /// Storage: `Broker::Status` (r:1 w:0) /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::AutoRenewals` (r:1 w:1) - /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(1002), added: 1497, mode: `MaxEncodedLen`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn enable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `914` - // Estimated: `4698` - // Minimum execution time: 51_938_000 picoseconds. - Weight::from_parts(55_025_000, 4698) - .saturating_add(T::DbWeight::get().reads(8_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + // Measured: `2829` + // Estimated: `6196` + // Minimum execution time: 130_799_000 picoseconds. + Weight::from_parts(139_893_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `Broker::AutoRenewals` (r:1 w:1) - /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(1002), added: 1497, mode: `MaxEncodedLen`) fn disable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `480` - // Estimated: `1516` - // Minimum execution time: 9_628_000 picoseconds. - Weight::from_parts(10_400_000, 1516) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } + // Measured: `1307` + // Estimated: `2487` + // Minimum execution time: 22_945_000 picoseconds. + Weight::from_parts(24_855_000, 0) + .saturating_add(Weight::from_parts(0, 2487)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -601,11 +619,11 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn on_new_timeslice() -> Weight { // Proof Size summary in bytes: - // Measured: `322` - // Estimated: `3787` - // Minimum execution time: 45_561_000 picoseconds. - Weight::from_parts(47_306_000, 0) - .saturating_add(Weight::from_parts(0, 3787)) + // Measured: `323` + // Estimated: `3788` + // Minimum execution time: 56_864_000 picoseconds. + Weight::from_parts(59_119_000, 0) + .saturating_add(Weight::from_parts(0, 3788)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs index 74b1c4e47029..ad71691b2174 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_broker` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-06-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-x5tnzzy-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-acd6uxux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -54,8 +54,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_899_000 picoseconds. - Weight::from_parts(2_051_000, 0) + // Minimum execution time: 2_274_000 picoseconds. + Weight::from_parts(2_421_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -65,8 +65,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `10888` // Estimated: `13506` - // Minimum execution time: 21_965_000 picoseconds. - Weight::from_parts(22_774_000, 0) + // Minimum execution time: 26_257_000 picoseconds. + Weight::from_parts(26_802_000, 0) .saturating_add(Weight::from_parts(0, 13506)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -77,8 +77,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `12090` // Estimated: `13506` - // Minimum execution time: 20_748_000 picoseconds. - Weight::from_parts(21_464_000, 0) + // Minimum execution time: 24_692_000 picoseconds. + Weight::from_parts(25_275_000, 0) .saturating_add(Weight::from_parts(0, 13506)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -93,8 +93,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `146` // Estimated: `1631` - // Minimum execution time: 10_269_000 picoseconds. - Weight::from_parts(10_508_000, 0) + // Minimum execution time: 13_872_000 picoseconds. + Weight::from_parts(14_509_000, 0) .saturating_add(Weight::from_parts(0, 1631)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -121,6 +121,8 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::LastRelayChainBlockNumber` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::InstaPoolIo` (r:3 w:3) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(201), added: 696, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:0 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::Status` (r:0 w:1) @@ -132,32 +134,34 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `12279` // Estimated: `14805 + n * (1 ±0)` - // Minimum execution time: 41_900_000 picoseconds. - Weight::from_parts(80_392_728, 0) + // Minimum execution time: 52_916_000 picoseconds. + Weight::from_parts(96_122_236, 0) .saturating_add(Weight::from_parts(0, 14805)) - // Standard Error: 870 - .saturating_add(Weight::from_parts(4_361, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(26)) + // Standard Error: 969 + .saturating_add(Weight::from_parts(5_732, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(14)) + .saturating_add(T::DbWeight::get().writes(27)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Broker::Status` (r:1 w:0) /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:0) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Broker::Regions` (r:0 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) fn purchase() -> Weight { // Proof Size summary in bytes: - // Measured: `332` + // Measured: `437` // Estimated: `3593` - // Minimum execution time: 40_911_000 picoseconds. - Weight::from_parts(43_102_000, 0) + // Minimum execution time: 56_955_000 picoseconds. + Weight::from_parts(59_005_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Broker::Configuration` (r:1 w:0) /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) @@ -169,16 +173,18 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn renew() -> Weight { // Proof Size summary in bytes: - // Measured: `450` + // Measured: `658` // Estimated: `4698` - // Minimum execution time: 70_257_000 picoseconds. - Weight::from_parts(73_889_000, 0) + // Minimum execution time: 108_853_000 picoseconds. + Weight::from_parts(117_467_000, 0) .saturating_add(Weight::from_parts(0, 4698)) - .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `Broker::Regions` (r:1 w:1) @@ -187,8 +193,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 13_302_000 picoseconds. - Weight::from_parts(13_852_000, 0) + // Minimum execution time: 16_922_000 picoseconds. + Weight::from_parts(17_544_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -199,8 +205,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 14_927_000 picoseconds. - Weight::from_parts(15_553_000, 0) + // Minimum execution time: 18_762_000 picoseconds. + Weight::from_parts(19_162_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -211,8 +217,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `358` // Estimated: `3551` - // Minimum execution time: 16_237_000 picoseconds. - Weight::from_parts(16_995_000, 0) + // Minimum execution time: 20_297_000 picoseconds. + Weight::from_parts(20_767_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(3)) @@ -229,8 +235,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `736` // Estimated: `4681` - // Minimum execution time: 24_621_000 picoseconds. - Weight::from_parts(25_165_000, 0) + // Minimum execution time: 31_347_000 picoseconds. + Weight::from_parts(32_259_000, 0) .saturating_add(Weight::from_parts(0, 4681)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) @@ -249,8 +255,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `802` // Estimated: `5996` - // Minimum execution time: 29_832_000 picoseconds. - Weight::from_parts(30_894_000, 0) + // Minimum execution time: 38_310_000 picoseconds. + Weight::from_parts(39_777_000, 0) .saturating_add(Weight::from_parts(0, 5996)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(5)) @@ -264,13 +270,13 @@ impl pallet_broker::WeightInfo for WeightInfo { /// The range of component `m` is `[1, 3]`. fn claim_revenue(m: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `652` + // Measured: `671` // Estimated: `6196 + m * (2520 ±0)` - // Minimum execution time: 55_390_000 picoseconds. - Weight::from_parts(56_124_789, 0) + // Minimum execution time: 65_960_000 picoseconds. + Weight::from_parts(66_194_985, 0) .saturating_add(Weight::from_parts(0, 6196)) - // Standard Error: 41_724 - .saturating_add(Weight::from_parts(1_551_266, 0).saturating_mul(m.into())) + // Standard Error: 42_455 + .saturating_add(Weight::from_parts(1_808_497, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) .saturating_add(T::DbWeight::get().writes(5)) @@ -290,11 +296,11 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn purchase_credit() -> Weight { // Proof Size summary in bytes: - // Measured: `320` - // Estimated: `3785` - // Minimum execution time: 59_759_000 picoseconds. - Weight::from_parts(61_310_000, 0) - .saturating_add(Weight::from_parts(0, 3785)) + // Measured: `321` + // Estimated: `3786` + // Minimum execution time: 69_918_000 picoseconds. + Weight::from_parts(72_853_000, 0) + .saturating_add(Weight::from_parts(0, 3786)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(3)) } @@ -306,8 +312,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `466` // Estimated: `3551` - // Minimum execution time: 37_007_000 picoseconds. - Weight::from_parts(51_927_000, 0) + // Minimum execution time: 44_775_000 picoseconds. + Weight::from_parts(58_978_000, 0) .saturating_add(Weight::from_parts(0, 3551)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -322,8 +328,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `463` // Estimated: `3533` - // Minimum execution time: 86_563_000 picoseconds. - Weight::from_parts(91_274_000, 0) + // Minimum execution time: 67_098_000 picoseconds. + Weight::from_parts(93_626_000, 0) .saturating_add(Weight::from_parts(0, 3533)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -338,10 +344,10 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn drop_history() -> Weight { // Proof Size summary in bytes: - // Measured: `857` + // Measured: `979` // Estimated: `3593` - // Minimum execution time: 93_655_000 picoseconds. - Weight::from_parts(98_160_000, 0) + // Minimum execution time: 89_463_000 picoseconds. + Weight::from_parts(113_286_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(1)) @@ -354,8 +360,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `556` // Estimated: `4698` - // Minimum execution time: 33_985_000 picoseconds. - Weight::from_parts(43_618_000, 0) + // Minimum execution time: 42_073_000 picoseconds. + Weight::from_parts(52_211_000, 0) .saturating_add(Weight::from_parts(0, 4698)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -371,30 +377,26 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// The range of component `n` is `[0, 1000]`. - fn request_core_count(n: u32, ) -> Weight { + fn request_core_count(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 18_778_000 picoseconds. - Weight::from_parts(19_543_425, 0) + // Minimum execution time: 22_937_000 picoseconds. + Weight::from_parts(23_898_154, 0) .saturating_add(Weight::from_parts(0, 3539)) - // Standard Error: 41 - .saturating_add(Weight::from_parts(33, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Broker::CoreCountInbox` (r:1 w:1) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. - fn process_core_count(n: u32, ) -> Weight { + fn process_core_count(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `266` // Estimated: `1487` - // Minimum execution time: 5_505_000 picoseconds. - Weight::from_parts(5_982_015, 0) + // Minimum execution time: 7_650_000 picoseconds. + Weight::from_parts(8_166_809, 0) .saturating_add(Weight::from_parts(0, 1487)) - // Standard Error: 13 - .saturating_add(Weight::from_parts(44, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -402,40 +404,54 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `Broker::RevenueInbox` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:2 w:1) + /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn process_revenue() -> Weight { // Proof Size summary in bytes: - // Measured: `442` + // Measured: `461` // Estimated: `6196` - // Minimum execution time: 38_128_000 picoseconds. - Weight::from_parts(40_979_000, 0) + // Minimum execution time: 53_023_000 picoseconds. + Weight::from_parts(54_564_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(4)) } + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::InstaPoolIo` (r:3 w:3) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) /// Storage: `Broker::Reservations` (r:1 w:0) /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(201), added: 696, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::PotentialRenewals` (r:20 w:40) + /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:21 w:20) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:0 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) - /// Storage: `Broker::Workplan` (r:0 w:20) + /// Storage: `Broker::Workplan` (r:0 w:1000) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. fn rotate_sale(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `12194` - // Estimated: `13506` - // Minimum execution time: 49_041_000 picoseconds. - Weight::from_parts(50_522_788, 0) - .saturating_add(Weight::from_parts(0, 13506)) - // Standard Error: 72 - .saturating_add(Weight::from_parts(78, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(25)) + // Measured: `16480` + // Estimated: `69404 + n * (8 ±1)` + // Minimum execution time: 29_313_000 picoseconds. + Weight::from_parts(746_062_644, 0) + .saturating_add(Weight::from_parts(0, 69404)) + // Standard Error: 22_496 + .saturating_add(Weight::from_parts(1_545_204, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(44)) + .saturating_add(T::DbWeight::get().writes(57)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(n.into())) } /// Storage: `Broker::InstaPoolIo` (r:1 w:0) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) @@ -445,8 +461,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `42` // Estimated: `3493` - // Minimum execution time: 5_903_000 picoseconds. - Weight::from_parts(6_202_000, 0) + // Minimum execution time: 7_625_000 picoseconds. + Weight::from_parts(7_910_000, 0) .saturating_add(Weight::from_parts(0, 3493)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -469,8 +485,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1321` // Estimated: `4786` - // Minimum execution time: 31_412_000 picoseconds. - Weight::from_parts(31_964_000, 0) + // Minimum execution time: 36_572_000 picoseconds. + Weight::from_parts(37_316_000, 0) .saturating_add(Weight::from_parts(0, 4786)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -489,8 +505,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 14_098_000 picoseconds. - Weight::from_parts(14_554_000, 0) + // Minimum execution time: 18_362_000 picoseconds. + Weight::from_parts(18_653_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) @@ -501,8 +517,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_723_000 picoseconds. - Weight::from_parts(1_822_000, 0) + // Minimum execution time: 2_193_000 picoseconds. + Weight::from_parts(2_393_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -512,8 +528,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_865_000 picoseconds. - Weight::from_parts(1_983_000, 0) + // Minimum execution time: 2_344_000 picoseconds. + Weight::from_parts(2_486_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -531,8 +547,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `408` // Estimated: `1893` - // Minimum execution time: 10_387_000 picoseconds. - Weight::from_parts(10_819_000, 0) + // Minimum execution time: 15_443_000 picoseconds. + Weight::from_parts(15_753_000, 0) .saturating_add(Weight::from_parts(0, 1893)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) @@ -543,8 +559,8 @@ impl pallet_broker::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `150` // Estimated: `1566` - // Minimum execution time: 5_996_000 picoseconds. - Weight::from_parts(6_278_000, 0) + // Minimum execution time: 8_637_000 picoseconds. + Weight::from_parts(8_883_000, 0) .saturating_add(Weight::from_parts(0, 1566)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -557,44 +573,44 @@ impl pallet_broker::WeightInfo for WeightInfo { /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) /// Storage: `Broker::Status` (r:1 w:0) /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `System::Account` (r:2 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `ParachainSystem::ValidationData` (r:1 w:0) + /// Proof: `ParachainSystem::ValidationData` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::AutoRenewals` (r:1 w:1) - /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(201), added: 696, mode: `MaxEncodedLen`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn enable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `914` - // Estimated: `4698` - // Minimum execution time: 51_938_000 picoseconds. - Weight::from_parts(55_025_000, 4698) - .saturating_add(T::DbWeight::get().reads(8_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + // Measured: `1451` + // Estimated: `6196` + // Minimum execution time: 120_585_000 picoseconds. + Weight::from_parts(148_755_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `Broker::AutoRenewals` (r:1 w:1) - /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(201), added: 696, mode: `MaxEncodedLen`) fn disable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `480` - // Estimated: `1516` - // Minimum execution time: 9_628_000 picoseconds. - Weight::from_parts(10_400_000, 1516) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } + // Measured: `506` + // Estimated: `1686` + // Minimum execution time: 18_235_000 picoseconds. + Weight::from_parts(19_113_000, 0) + .saturating_add(Weight::from_parts(0, 1686)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn on_new_timeslice() -> Weight { // Proof Size summary in bytes: - // Measured: `0` + // Measured: `103` // Estimated: `3593` - // Minimum execution time: 2_187_000 picoseconds. - Weight::from_parts(2_372_000, 0) + // Minimum execution time: 4_863_000 picoseconds. + Weight::from_parts(5_045_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) } diff --git a/prdoc/pr_6505.prdoc b/prdoc/pr_6505.prdoc new file mode 100644 index 000000000000..ae00dd17fed5 --- /dev/null +++ b/prdoc/pr_6505.prdoc @@ -0,0 +1,14 @@ +title: '[pallet-broker] Fix auto renew benchmarks' +doc: +- audience: Runtime Dev + description: |- + Fix the broker pallet auto-renew benchmarks which have been broken since #4424, yielding `Weightless` due to some prices being set too low, as reported in #6474. + + Upon further investigation it turned out that the auto-renew contribution to `rotate_sale` was always failing but the error was mapped. This is also fixed at the cost of a bit of setup overhead. +crates: +- name: pallet-broker + bump: patch +- name: coretime-rococo-runtime + bump: patch +- name: coretime-westend-runtime + bump: patch diff --git a/substrate/frame/broker/src/benchmarking.rs b/substrate/frame/broker/src/benchmarking.rs index 9ef9b1254435..044689b254c5 100644 --- a/substrate/frame/broker/src/benchmarking.rs +++ b/substrate/frame/broker/src/benchmarking.rs @@ -30,11 +30,11 @@ use frame_support::{ }, }; use frame_system::{Pallet as System, RawOrigin}; -use sp_arithmetic::{traits::Zero, Perbill}; +use sp_arithmetic::Perbill; use sp_core::Get; use sp_runtime::{ traits::{BlockNumberProvider, MaybeConvert}, - SaturatedConversion, Saturating, + Saturating, }; const SEED: u32 = 0; @@ -287,7 +287,7 @@ mod benches { ); let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); Broker::::do_assign(region, None, 1001, Final) .map_err(|_| BenchmarkError::Weightless)?; @@ -316,7 +316,7 @@ mod benches { ); let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); @@ -349,7 +349,7 @@ mod benches { ); let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); #[extrinsic_call] _(RawOrigin::Signed(caller), region, 2); @@ -381,7 +381,7 @@ mod benches { ); let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); #[extrinsic_call] _(RawOrigin::Signed(caller), region, 0x00000_fffff_fffff_00000.into()); @@ -417,7 +417,7 @@ mod benches { ); let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); #[extrinsic_call] _(RawOrigin::Signed(caller), region, 1000, Provisional); @@ -452,7 +452,7 @@ mod benches { ); let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); @@ -492,7 +492,7 @@ mod benches { ); let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); T::Currency::set_balance(&recipient.clone(), T::Currency::minimum_balance()); @@ -548,7 +548,7 @@ mod benches { T::Currency::set_balance(&Broker::::account_id(), T::Currency::minimum_balance()); let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); @@ -582,7 +582,7 @@ mod benches { ); let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); advance_to::( (T::TimeslicePeriod::get() * (region_len * 4).into()).try_into().ok().unwrap(), @@ -616,7 +616,7 @@ mod benches { ); let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + .expect("Offer not high enough for configuration."); let recipient: T::AccountId = account("recipient", 0, SEED); @@ -786,80 +786,97 @@ mod benches { #[benchmark] fn rotate_sale(n: Linear<0, { MAX_CORE_COUNT.into() }>) -> Result<(), BenchmarkError> { - let core_count = n.try_into().unwrap(); let config = new_config_record::(); + Configuration::::put(config.clone()); - let now = RCBlockNumberProviderOf::::current_block_number(); - let end_price = 10_000_000u32.into(); - let commit_timeslice = Broker::::latest_timeslice_ready_to_commit(&config); - let sale = SaleInfoRecordOf:: { - sale_start: now, - leadin_length: Zero::zero(), - end_price, - sellout_price: None, - region_begin: commit_timeslice, - region_end: commit_timeslice.saturating_add(config.region_length), - first_core: 0, - ideal_cores_sold: 0, - cores_offered: 0, - cores_sold: 0, - }; - - let status = StatusRecord { - core_count, - private_pool_size: 0, - system_pool_size: 0, - last_committed_timeslice: commit_timeslice.saturating_sub(1), - last_timeslice: Broker::::current_timeslice(), - }; + // Ensure there is one buyable core then use the rest to max out reservations and leases, if + // possible for worst case. + + // First allocate up to MaxReservedCores for reservations + let n_reservations = T::MaxReservedCores::get().min(n.saturating_sub(1)); + setup_reservations::(n_reservations); + // Then allocate remaining cores to leases, up to MaxLeasedCores + let n_leases = + T::MaxLeasedCores::get().min(n.saturating_sub(1).saturating_sub(n_reservations)); + setup_leases::(n_leases, 1, 20); + + // Start sales so we can test the auto-renewals. + Broker::::do_start_sales( + 10_000_000u32.into(), + n.saturating_sub(n_reservations) + .saturating_sub(n_leases) + .try_into() + .expect("Upper limit of n is a u16."), + ) + .expect("Configuration was initialized before; qed"); + + // Advance to the fixed price period. + advance_to::(2); - // Assume Reservations to be filled for worst case - setup_reservations::(T::MaxReservedCores::get()); + // Assume max auto renewals for worst case. This is between 1 and the value of + // MaxAutoRenewals. + let n_renewable = T::MaxAutoRenewals::get() + .min(n.saturating_sub(n_leases).saturating_sub(n_reservations)); - // Assume Leases to be filled for worst case - setup_leases::(T::MaxLeasedCores::get(), 1, 10); + let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap(); + let sale = SaleInfo::::get().expect("Sale has started."); - // Assume max auto renewals for worst case. - (0..T::MaxAutoRenewals::get()).try_for_each(|indx| -> Result<(), BenchmarkError> { + (0..n_renewable.into()).try_for_each(|indx| -> Result<(), BenchmarkError> { let task = 1000 + indx; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); T::Currency::set_balance( &caller.clone(), - T::Currency::minimum_balance().saturating_add(100u32.into()), + T::Currency::minimum_balance().saturating_add(100_000_000u32.into()), ); - let region = Broker::::do_purchase(caller.clone(), 10u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) + .expect("Offer not high enough for configuration."); Broker::::do_assign(region, None, task, Final) .map_err(|_| BenchmarkError::Weightless)?; - Broker::::do_enable_auto_renew(caller, region.core, task, None)?; + Broker::::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?; Ok(()) })?; + // Advance to the block before the rotate_sale in which the auto-renewals will take place. + let rotate_block = timeslice_period.saturating_mul(config.region_length) - 2; + advance_to::(rotate_block - 1); + + // Advance one block and manually tick so we can isolate the `rotate_sale` call. + System::::set_block_number(rotate_block.into()); + RCBlockNumberProviderOf::::set_block_number(rotate_block.into()); + let mut status = Status::::get().expect("Sale has started."); + let sale = SaleInfo::::get().expect("Sale has started."); + Broker::::process_core_count(&mut status); + Broker::::process_revenue(); + status.last_committed_timeslice = config.region_length; + #[block] { Broker::::rotate_sale(sale.clone(), &config, &status); } - assert!(SaleInfo::::get().is_some()); - let sale_start = RCBlockNumberProviderOf::::current_block_number() + - config.interlude_length; - assert_last_event::( + // Get prices from the actual price adapter. + let new_prices = T::PriceAdapter::adapt_price(SalePerformance::from_sale(&sale)); + let new_sale = SaleInfo::::get().expect("Sale has started."); + let now = RCBlockNumberProviderOf::::current_block_number(); + let sale_start = config.interlude_length.saturating_add(rotate_block.into()); + + assert_has_event::( Event::SaleInitialized { sale_start, leadin_length: 1u32.into(), - start_price: 1_000_000_000u32.into(), - end_price: 10_000_000u32.into(), + start_price: Broker::::sale_price(&new_sale, now), + end_price: new_prices.end_price, region_begin: sale.region_begin + config.region_length, region_end: sale.region_end + config.region_length, ideal_cores_sold: 0, cores_offered: n - .saturating_sub(T::MaxReservedCores::get()) - .saturating_sub(T::MaxLeasedCores::get()) + .saturating_sub(n_reservations) + .saturating_sub(n_leases) .try_into() .unwrap(), } @@ -867,18 +884,18 @@ mod benches { ); // Make sure all cores got renewed: - (0..T::MaxAutoRenewals::get()).for_each(|indx| { + (0..n_renewable).for_each(|indx| { let task = 1000 + indx; let who = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); assert_has_event::( Event::Renewed { who, - old_core: 10 + indx as u16, // first ten cores are allocated to leases. - core: 10 + indx as u16, - price: 10u32.saturated_into(), - begin: 7, - duration: 3, + old_core: n_reservations as u16 + n_leases as u16 + indx as u16, + core: n_reservations as u16 + n_leases as u16 + indx as u16, + price: 10_000_000u32.into(), + begin: new_sale.region_begin, + duration: config.region_length, workload: Schedule::truncate_from(vec![ScheduleItem { assignment: Task(task), mask: CoreMask::complete(), @@ -1018,56 +1035,62 @@ mod benches { #[benchmark] fn enable_auto_renew() -> Result<(), BenchmarkError> { - let _core = setup_and_start_sale::()?; + let _core_id = setup_and_start_sale::()?; advance_to::(2); + let sale = SaleInfo::::get().expect("Sale has already started."); // We assume max auto renewals for worst case. (0..T::MaxAutoRenewals::get() - 1).try_for_each(|indx| -> Result<(), BenchmarkError> { let task = 1000 + indx; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); + // Sovereign account needs sufficient funds to purchase and renew. T::Currency::set_balance( &caller.clone(), - T::Currency::minimum_balance().saturating_add(100u32.into()), + T::Currency::minimum_balance().saturating_add(100_000_000u32.into()), ); - let region = Broker::::do_purchase(caller.clone(), 10u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) + .expect("Offer not high enough for configuration."); Broker::::do_assign(region, None, task, Final) .map_err(|_| BenchmarkError::Weightless)?; - Broker::::do_enable_auto_renew(caller, region.core, task, Some(7))?; + Broker::::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?; Ok(()) })?; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(2001).expect("Failed to get sovereign account"); + // Sovereign account needs sufficient funds to purchase and renew. T::Currency::set_balance( &caller.clone(), - T::Currency::minimum_balance().saturating_add(100u32.into()), + T::Currency::minimum_balance().saturating_add(100_000_000u32.into()), ); // The region for which we benchmark enable auto renew. - let region = Broker::::do_purchase(caller.clone(), 10u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) + .expect("Offer not high enough for configuration."); Broker::::do_assign(region, None, 2001, Final) .map_err(|_| BenchmarkError::Weightless)?; // The most 'intensive' path is when we renew the core upon enabling auto-renewal. // Therefore, we advance to next bulk sale: - advance_to::(6); + let timeslice_period: u32 = T::TimeslicePeriod::get().try_into().ok().unwrap(); + let config = Configuration::::get().expect("Already configured."); + advance_to::(config.region_length * timeslice_period); #[extrinsic_call] _(RawOrigin::Signed(caller), region.core, 2001, None); assert_last_event::(Event::AutoRenewalEnabled { core: region.core, task: 2001 }.into()); // Make sure we indeed renewed: + let sale = SaleInfo::::get().expect("Sales have started."); assert!(PotentialRenewals::::get(PotentialRenewalId { core: region.core, - when: 10 // region end after renewal + when: sale.region_end, }) .is_some()); @@ -1076,37 +1099,41 @@ mod benches { #[benchmark] fn disable_auto_renew() -> Result<(), BenchmarkError> { - let _core = setup_and_start_sale::()?; + let core_id = setup_and_start_sale::()?; advance_to::(2); + let sale = SaleInfo::::get().expect("Sale has already started."); // We assume max auto renewals for worst case. - (0..T::MaxAutoRenewals::get() - 1).try_for_each(|indx| -> Result<(), BenchmarkError> { + (0..T::MaxAutoRenewals::get()).try_for_each(|indx| -> Result<(), BenchmarkError> { let task = 1000 + indx; let caller: T::AccountId = T::SovereignAccountOf::maybe_convert(task) .expect("Failed to get sovereign account"); T::Currency::set_balance( &caller.clone(), - T::Currency::minimum_balance().saturating_add(100u32.into()), + T::Currency::minimum_balance().saturating_add(10_000_000u32.into()), ); - let region = Broker::::do_purchase(caller.clone(), 10u32.into()) - .map_err(|_| BenchmarkError::Weightless)?; + let region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) + .expect("Offer not high enough for configuration."); Broker::::do_assign(region, None, task, Final) .map_err(|_| BenchmarkError::Weightless)?; - Broker::::do_enable_auto_renew(caller, region.core, task, Some(7))?; + Broker::::do_enable_auto_renew(caller, region.core, task, Some(sale.region_end))?; Ok(()) })?; + let task = 1000; + let caller: T::AccountId = - T::SovereignAccountOf::maybe_convert(1000).expect("Failed to get sovereign account"); + T::SovereignAccountOf::maybe_convert(task).expect("Failed to get sovereign account"); + #[extrinsic_call] - _(RawOrigin::Signed(caller), _core, 1000); + _(RawOrigin::Signed(caller), core_id, task); - assert_last_event::(Event::AutoRenewalDisabled { core: _core, task: 1000 }.into()); + assert_last_event::(Event::AutoRenewalDisabled { core: core_id, task }.into()); Ok(()) } @@ -1120,11 +1147,11 @@ mod benches { let caller: T::AccountId = whitelisted_caller(); T::Currency::set_balance( &caller.clone(), - T::Currency::minimum_balance().saturating_add(u32::MAX.into()), + T::Currency::minimum_balance().saturating_add(10_000_000u32.into()), ); - let _region = Broker::::do_purchase(caller.clone(), (u32::MAX / 2).into()) - .map_err(|_| BenchmarkError::Weightless)?; + let _region = Broker::::do_purchase(caller.clone(), 10_000_000u32.into()) + .expect("Offer not high enough for configuration."); let timeslice = Broker::::current_timeslice(); diff --git a/substrate/frame/broker/src/weights.rs b/substrate/frame/broker/src/weights.rs index 2f25fddc2050..894fed5a6a00 100644 --- a/substrate/frame/broker/src/weights.rs +++ b/substrate/frame/broker/src/weights.rs @@ -18,27 +18,25 @@ //! Autogenerated weights for `pallet_broker` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `sergej-B650-AORUS-ELITE-AX`, CPU: `AMD Ryzen 9 7900X3D 12-Core Processor` +//! HOSTNAME: `runner-acd6uxux-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/release/substrate-node +// target/production/substrate-node // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_broker -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/broker/src/weights.rs +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_broker +// --chain=dev // --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/broker/src/weights.rs // --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -80,9 +78,9 @@ pub trait WeightInfo { fn notify_revenue() -> Weight; fn do_tick_base() -> Weight; fn swap_leases() -> Weight; - fn on_new_timeslice() -> Weight; fn enable_auto_renew() -> Weight; fn disable_auto_renew() -> Weight; + fn on_new_timeslice() -> Weight; } /// Weights for `pallet_broker` using the Substrate node and recommended hardware. @@ -94,8 +92,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_593_000 picoseconds. - Weight::from_parts(1_703_000, 0) + // Minimum execution time: 2_498_000 picoseconds. + Weight::from_parts(2_660_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Broker::Reservations` (r:1 w:1) @@ -104,8 +102,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `5016` // Estimated: `7496` - // Minimum execution time: 12_864_000 picoseconds. - Weight::from_parts(13_174_000, 7496) + // Minimum execution time: 23_090_000 picoseconds. + Weight::from_parts(23_664_000, 7496) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -115,8 +113,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `6218` // Estimated: `7496` - // Minimum execution time: 12_284_000 picoseconds. - Weight::from_parts(13_566_000, 7496) + // Minimum execution time: 21_782_000 picoseconds. + Weight::from_parts(22_708_000, 7496) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -126,8 +124,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `239` // Estimated: `1526` - // Minimum execution time: 6_743_000 picoseconds. - Weight::from_parts(7_094_000, 1526) + // Minimum execution time: 14_966_000 picoseconds. + Weight::from_parts(15_592_000, 1526) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -152,10 +150,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `6330` // Estimated: `8499` - // Minimum execution time: 21_120_000 picoseconds. - Weight::from_parts(40_929_422, 8499) - // Standard Error: 471 - .saturating_add(Weight::from_parts(1_004, 0).saturating_mul(n.into())) + // Minimum execution time: 31_757_000 picoseconds. + Weight::from_parts(57_977_268, 8499) + // Standard Error: 576 + .saturating_add(Weight::from_parts(3_102, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(16_u64)) } @@ -163,19 +161,15 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Regions` (r:0 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) fn purchase() -> Weight { // Proof Size summary in bytes: - // Measured: `651` - // Estimated: `2136` - // Minimum execution time: 31_169_000 picoseconds. - Weight::from_parts(32_271_000, 2136) - .saturating_add(T::DbWeight::get().reads(4_u64)) + // Measured: `470` + // Estimated: `1542` + // Minimum execution time: 40_469_000 picoseconds. + Weight::from_parts(41_360_000, 1542) + .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } /// Storage: `Broker::Configuration` (r:1 w:0) @@ -186,19 +180,15 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::PotentialRenewals` (r:1 w:2) /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn renew() -> Weight { // Proof Size summary in bytes: - // Measured: `769` + // Measured: `588` // Estimated: `4698` - // Minimum execution time: 44_945_000 picoseconds. - Weight::from_parts(47_119_000, 4698) - .saturating_add(T::DbWeight::get().reads(6_u64)) + // Minimum execution time: 60_724_000 picoseconds. + Weight::from_parts(63_445_000, 4698) + .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } /// Storage: `Broker::Regions` (r:1 w:1) @@ -207,8 +197,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 11_562_000 picoseconds. - Weight::from_parts(11_943_000, 3551) + // Minimum execution time: 23_734_000 picoseconds. + Weight::from_parts(25_080_000, 3551) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -218,8 +208,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 13_075_000 picoseconds. - Weight::from_parts(13_616_000, 3551) + // Minimum execution time: 25_917_000 picoseconds. + Weight::from_parts(26_715_000, 3551) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -229,8 +219,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 13_695_000 picoseconds. - Weight::from_parts(14_658_000, 3551) + // Minimum execution time: 26_764_000 picoseconds. + Weight::from_parts(27_770_000, 3551) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -246,8 +236,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `741` // Estimated: `4681` - // Minimum execution time: 22_623_000 picoseconds. - Weight::from_parts(23_233_000, 4681) + // Minimum execution time: 37_617_000 picoseconds. + Weight::from_parts(39_333_000, 4681) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -265,8 +255,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `776` // Estimated: `5996` - // Minimum execution time: 26_901_000 picoseconds. - Weight::from_parts(27_472_000, 5996) + // Minimum execution time: 43_168_000 picoseconds. + Weight::from_parts(44_741_000, 5996) .saturating_add(T::DbWeight::get().reads(5_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -281,10 +271,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `878` // Estimated: `6196 + m * (2520 ±0)` - // Minimum execution time: 51_778_000 picoseconds. - Weight::from_parts(53_726_731, 6196) - // Standard Error: 45_279 - .saturating_add(Weight::from_parts(677_769, 0).saturating_mul(m.into())) + // Minimum execution time: 75_317_000 picoseconds. + Weight::from_parts(76_792_860, 6196) + // Standard Error: 55_267 + .saturating_add(Weight::from_parts(1_878_133, 0).saturating_mul(m.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) .saturating_add(T::DbWeight::get().writes(5_u64)) @@ -296,8 +286,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 31_790_000 picoseconds. - Weight::from_parts(32_601_000, 3593) + // Minimum execution time: 44_248_000 picoseconds. + Weight::from_parts(45_201_000, 3593) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -309,8 +299,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `604` // Estimated: `3551` - // Minimum execution time: 18_465_000 picoseconds. - Weight::from_parts(21_050_000, 3551) + // Minimum execution time: 39_853_000 picoseconds. + Weight::from_parts(44_136_000, 3551) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -324,8 +314,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `601` // Estimated: `3533` - // Minimum execution time: 23_825_000 picoseconds. - Weight::from_parts(26_250_000, 3533) + // Minimum execution time: 46_452_000 picoseconds. + Weight::from_parts(52_780_000, 3533) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -339,10 +329,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn drop_history() -> Weight { // Proof Size summary in bytes: - // Measured: `1014` + // Measured: `1117` // Estimated: `3593` - // Minimum execution time: 28_103_000 picoseconds. - Weight::from_parts(32_622_000, 3593) + // Minimum execution time: 64_905_000 picoseconds. + Weight::from_parts(72_914_000, 3593) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -354,8 +344,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `661` // Estimated: `4698` - // Minimum execution time: 16_751_000 picoseconds. - Weight::from_parts(17_373_000, 4698) + // Minimum execution time: 38_831_000 picoseconds. + Weight::from_parts(41_420_000, 4698) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -364,8 +354,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_705_000 picoseconds. - Weight::from_parts(2_991_768, 0) + // Minimum execution time: 4_595_000 picoseconds. + Weight::from_parts(4_964_606, 0) } /// Storage: `Broker::CoreCountInbox` (r:1 w:1) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -374,37 +364,58 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `404` // Estimated: `1487` - // Minimum execution time: 4_598_000 picoseconds. - Weight::from_parts(4_937_302, 1487) + // Minimum execution time: 8_640_000 picoseconds. + Weight::from_parts(9_153_332, 1487) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) - /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `Broker::RevenueInbox` (r:1 w:1) + /// Proof: `Broker::RevenueInbox` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn process_revenue() -> Weight { // Proof Size summary in bytes: - // Measured: `991` - // Estimated: `4456` - // Minimum execution time: 37_601_000 picoseconds. - Weight::from_parts(38_262_000, 4456) - .saturating_add(T::DbWeight::get().reads(5_u64)) + // Measured: `667` + // Estimated: `3593` + // Minimum execution time: 40_570_000 picoseconds. + Weight::from_parts(41_402_000, 3593) + .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } + /// Storage: `Broker::InstaPoolIo` (r:3 w:3) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:0) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`) + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::PotentialRenewals` (r:10 w:20) + /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:10 w:10) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:0 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:1000) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. - fn rotate_sale(_n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 0_000 picoseconds. - Weight::from_parts(0, 0) + fn rotate_sale(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `8548` + // Estimated: `38070` + // Minimum execution time: 29_370_000 picoseconds. + Weight::from_parts(334_030_189, 38070) + // Standard Error: 6_912 + .saturating_add(Weight::from_parts(1_268_750, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(26_u64)) + .saturating_add(T::DbWeight::get().writes(34_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } /// Storage: `Broker::InstaPoolIo` (r:1 w:0) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) @@ -414,8 +425,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `180` // Estimated: `3493` - // Minimum execution time: 5_391_000 picoseconds. - Weight::from_parts(5_630_000, 3493) + // Minimum execution time: 9_005_000 picoseconds. + Weight::from_parts(9_392_000, 3493) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -427,8 +438,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1423` // Estimated: `4681` - // Minimum execution time: 10_249_000 picoseconds. - Weight::from_parts(10_529_000, 4681) + // Minimum execution time: 19_043_000 picoseconds. + Weight::from_parts(20_089_000, 4681) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -436,8 +447,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 120_000 picoseconds. - Weight::from_parts(140_000, 0) + // Minimum execution time: 149_000 picoseconds. + Weight::from_parts(183_000, 0) } /// Storage: `Broker::CoreCountInbox` (r:0 w:1) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -445,8 +456,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_402_000 picoseconds. - Weight::from_parts(1_513_000, 0) + // Minimum execution time: 2_248_000 picoseconds. + Weight::from_parts(2_425_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Broker::RevenueInbox` (r:0 w:1) @@ -455,8 +466,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_902_000 picoseconds. - Weight::from_parts(2_116_000, 0) + // Minimum execution time: 2_413_000 picoseconds. + Weight::from_parts(2_640_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Broker::Status` (r:1 w:1) @@ -465,16 +476,16 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) /// Storage: `Broker::CoreCountInbox` (r:1 w:0) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) - /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `Broker::RevenueInbox` (r:1 w:0) + /// Proof: `Broker::RevenueInbox` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) fn do_tick_base() -> Weight { // Proof Size summary in bytes: - // Measured: `603` - // Estimated: `4068` - // Minimum execution time: 8_897_000 picoseconds. - Weight::from_parts(9_218_000, 4068) + // Measured: `441` + // Estimated: `1516` + // Minimum execution time: 17_083_000 picoseconds. + Weight::from_parts(18_077_000, 1516) .saturating_add(T::DbWeight::get().reads(4_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`) @@ -482,18 +493,11 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `239` // Estimated: `1526` - // Minimum execution time: 4_678_000 picoseconds. - Weight::from_parts(4_920_000, 1526) + // Minimum execution time: 11_620_000 picoseconds. + Weight::from_parts(12_063_000, 1526) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } - fn on_new_timeslice() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 229_000 picoseconds. - Weight::from_parts(268_000, 0) - } /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::PotentialRenewals` (r:1 w:2) @@ -504,34 +508,37 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::AutoRenewals` (r:1 w:1) /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn enable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `930` + // Measured: `1121` // Estimated: `4698` - // Minimum execution time: 51_597_000 picoseconds. - Weight::from_parts(52_609_000, 4698) - .saturating_add(T::DbWeight::get().reads(8_u64)) + // Minimum execution time: 85_270_000 picoseconds. + Weight::from_parts(90_457_000, 4698) + .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Broker::AutoRenewals` (r:1 w:1) /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) fn disable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `484` + // Measured: `578` // Estimated: `1586` - // Minimum execution time: 8_907_000 picoseconds. - Weight::from_parts(9_167_000, 1586) + // Minimum execution time: 22_479_000 picoseconds. + Weight::from_parts(23_687_000, 1586) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + fn on_new_timeslice() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 245_000 picoseconds. + Weight::from_parts(290_000, 0) + } } // For backwards compatibility and tests. @@ -542,8 +549,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_593_000 picoseconds. - Weight::from_parts(1_703_000, 0) + // Minimum execution time: 2_498_000 picoseconds. + Weight::from_parts(2_660_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Broker::Reservations` (r:1 w:1) @@ -552,8 +559,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `5016` // Estimated: `7496` - // Minimum execution time: 12_864_000 picoseconds. - Weight::from_parts(13_174_000, 7496) + // Minimum execution time: 23_090_000 picoseconds. + Weight::from_parts(23_664_000, 7496) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -563,8 +570,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `6218` // Estimated: `7496` - // Minimum execution time: 12_284_000 picoseconds. - Weight::from_parts(13_566_000, 7496) + // Minimum execution time: 21_782_000 picoseconds. + Weight::from_parts(22_708_000, 7496) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -574,8 +581,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `239` // Estimated: `1526` - // Minimum execution time: 6_743_000 picoseconds. - Weight::from_parts(7_094_000, 1526) + // Minimum execution time: 14_966_000 picoseconds. + Weight::from_parts(15_592_000, 1526) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -600,10 +607,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `6330` // Estimated: `8499` - // Minimum execution time: 21_120_000 picoseconds. - Weight::from_parts(40_929_422, 8499) - // Standard Error: 471 - .saturating_add(Weight::from_parts(1_004, 0).saturating_mul(n.into())) + // Minimum execution time: 31_757_000 picoseconds. + Weight::from_parts(57_977_268, 8499) + // Standard Error: 576 + .saturating_add(Weight::from_parts(3_102, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(16_u64)) } @@ -611,19 +618,15 @@ impl WeightInfo for () { /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Regions` (r:0 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) fn purchase() -> Weight { // Proof Size summary in bytes: - // Measured: `651` - // Estimated: `2136` - // Minimum execution time: 31_169_000 picoseconds. - Weight::from_parts(32_271_000, 2136) - .saturating_add(RocksDbWeight::get().reads(4_u64)) + // Measured: `470` + // Estimated: `1542` + // Minimum execution time: 40_469_000 picoseconds. + Weight::from_parts(41_360_000, 1542) + .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `Broker::Configuration` (r:1 w:0) @@ -634,19 +637,15 @@ impl WeightInfo for () { /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::PotentialRenewals` (r:1 w:2) /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn renew() -> Weight { // Proof Size summary in bytes: - // Measured: `769` + // Measured: `588` // Estimated: `4698` - // Minimum execution time: 44_945_000 picoseconds. - Weight::from_parts(47_119_000, 4698) - .saturating_add(RocksDbWeight::get().reads(6_u64)) + // Minimum execution time: 60_724_000 picoseconds. + Weight::from_parts(63_445_000, 4698) + .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } /// Storage: `Broker::Regions` (r:1 w:1) @@ -655,8 +654,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 11_562_000 picoseconds. - Weight::from_parts(11_943_000, 3551) + // Minimum execution time: 23_734_000 picoseconds. + Weight::from_parts(25_080_000, 3551) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -666,8 +665,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 13_075_000 picoseconds. - Weight::from_parts(13_616_000, 3551) + // Minimum execution time: 25_917_000 picoseconds. + Weight::from_parts(26_715_000, 3551) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -677,8 +676,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `496` // Estimated: `3551` - // Minimum execution time: 13_695_000 picoseconds. - Weight::from_parts(14_658_000, 3551) + // Minimum execution time: 26_764_000 picoseconds. + Weight::from_parts(27_770_000, 3551) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -694,8 +693,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `741` // Estimated: `4681` - // Minimum execution time: 22_623_000 picoseconds. - Weight::from_parts(23_233_000, 4681) + // Minimum execution time: 37_617_000 picoseconds. + Weight::from_parts(39_333_000, 4681) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -713,8 +712,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `776` // Estimated: `5996` - // Minimum execution time: 26_901_000 picoseconds. - Weight::from_parts(27_472_000, 5996) + // Minimum execution time: 43_168_000 picoseconds. + Weight::from_parts(44_741_000, 5996) .saturating_add(RocksDbWeight::get().reads(5_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -729,10 +728,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `878` // Estimated: `6196 + m * (2520 ±0)` - // Minimum execution time: 51_778_000 picoseconds. - Weight::from_parts(53_726_731, 6196) - // Standard Error: 45_279 - .saturating_add(Weight::from_parts(677_769, 0).saturating_mul(m.into())) + // Minimum execution time: 75_317_000 picoseconds. + Weight::from_parts(76_792_860, 6196) + // Standard Error: 55_267 + .saturating_add(Weight::from_parts(1_878_133, 0).saturating_mul(m.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(m.into()))) .saturating_add(RocksDbWeight::get().writes(5_u64)) @@ -744,8 +743,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `103` // Estimated: `3593` - // Minimum execution time: 31_790_000 picoseconds. - Weight::from_parts(32_601_000, 3593) + // Minimum execution time: 44_248_000 picoseconds. + Weight::from_parts(45_201_000, 3593) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -757,8 +756,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `604` // Estimated: `3551` - // Minimum execution time: 18_465_000 picoseconds. - Weight::from_parts(21_050_000, 3551) + // Minimum execution time: 39_853_000 picoseconds. + Weight::from_parts(44_136_000, 3551) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -772,8 +771,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `601` // Estimated: `3533` - // Minimum execution time: 23_825_000 picoseconds. - Weight::from_parts(26_250_000, 3533) + // Minimum execution time: 46_452_000 picoseconds. + Weight::from_parts(52_780_000, 3533) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -787,10 +786,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn drop_history() -> Weight { // Proof Size summary in bytes: - // Measured: `1014` + // Measured: `1117` // Estimated: `3593` - // Minimum execution time: 28_103_000 picoseconds. - Weight::from_parts(32_622_000, 3593) + // Minimum execution time: 64_905_000 picoseconds. + Weight::from_parts(72_914_000, 3593) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -802,8 +801,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `661` // Estimated: `4698` - // Minimum execution time: 16_751_000 picoseconds. - Weight::from_parts(17_373_000, 4698) + // Minimum execution time: 38_831_000 picoseconds. + Weight::from_parts(41_420_000, 4698) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -812,8 +811,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_705_000 picoseconds. - Weight::from_parts(2_991_768, 0) + // Minimum execution time: 4_595_000 picoseconds. + Weight::from_parts(4_964_606, 0) } /// Storage: `Broker::CoreCountInbox` (r:1 w:1) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -822,37 +821,58 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `404` // Estimated: `1487` - // Minimum execution time: 4_598_000 picoseconds. - Weight::from_parts(4_937_302, 1487) + // Minimum execution time: 8_640_000 picoseconds. + Weight::from_parts(9_153_332, 1487) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) - /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `Broker::RevenueInbox` (r:1 w:1) + /// Proof: `Broker::RevenueInbox` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) /// Storage: `Broker::InstaPoolHistory` (r:1 w:1) /// Proof: `Broker::InstaPoolHistory` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn process_revenue() -> Weight { // Proof Size summary in bytes: - // Measured: `991` - // Estimated: `4456` - // Minimum execution time: 37_601_000 picoseconds. - Weight::from_parts(38_262_000, 4456) - .saturating_add(RocksDbWeight::get().reads(5_u64)) + // Measured: `667` + // Estimated: `3593` + // Minimum execution time: 40_570_000 picoseconds. + Weight::from_parts(41_402_000, 3593) + .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } + /// Storage: `Broker::InstaPoolIo` (r:3 w:3) + /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:0) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`) + /// Storage: `Broker::Leases` (r:1 w:1) + /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`) + /// Storage: `Broker::AutoRenewals` (r:1 w:1) + /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) + /// Storage: `Broker::Configuration` (r:1 w:0) + /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::PotentialRenewals` (r:10 w:20) + /// Proof: `Broker::PotentialRenewals` (`max_values`: None, `max_size`: Some(1233), added: 3708, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:10 w:10) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Broker::SaleInfo` (r:0 w:1) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:1000) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 1000]`. - fn rotate_sale(_n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 0_000 picoseconds. - Weight::from_parts(0, 0) + fn rotate_sale(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `8548` + // Estimated: `38070` + // Minimum execution time: 29_370_000 picoseconds. + Weight::from_parts(334_030_189, 38070) + // Standard Error: 6_912 + .saturating_add(Weight::from_parts(1_268_750, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(26_u64)) + .saturating_add(RocksDbWeight::get().writes(34_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) } /// Storage: `Broker::InstaPoolIo` (r:1 w:0) /// Proof: `Broker::InstaPoolIo` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) @@ -862,8 +882,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `180` // Estimated: `3493` - // Minimum execution time: 5_391_000 picoseconds. - Weight::from_parts(5_630_000, 3493) + // Minimum execution time: 9_005_000 picoseconds. + Weight::from_parts(9_392_000, 3493) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -875,8 +895,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1423` // Estimated: `4681` - // Minimum execution time: 10_249_000 picoseconds. - Weight::from_parts(10_529_000, 4681) + // Minimum execution time: 19_043_000 picoseconds. + Weight::from_parts(20_089_000, 4681) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -884,8 +904,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 120_000 picoseconds. - Weight::from_parts(140_000, 0) + // Minimum execution time: 149_000 picoseconds. + Weight::from_parts(183_000, 0) } /// Storage: `Broker::CoreCountInbox` (r:0 w:1) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) @@ -893,8 +913,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_402_000 picoseconds. - Weight::from_parts(1_513_000, 0) + // Minimum execution time: 2_248_000 picoseconds. + Weight::from_parts(2_425_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Broker::RevenueInbox` (r:0 w:1) @@ -903,8 +923,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_902_000 picoseconds. - Weight::from_parts(2_116_000, 0) + // Minimum execution time: 2_413_000 picoseconds. + Weight::from_parts(2_640_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Broker::Status` (r:1 w:1) @@ -913,16 +933,16 @@ impl WeightInfo for () { /// Proof: `Broker::Configuration` (`max_values`: Some(1), `max_size`: Some(31), added: 526, mode: `MaxEncodedLen`) /// Storage: `Broker::CoreCountInbox` (r:1 w:0) /// Proof: `Broker::CoreCountInbox` (`max_values`: Some(1), `max_size`: Some(2), added: 497, mode: `MaxEncodedLen`) - /// Storage: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) - /// Proof: UNKNOWN KEY `0xf308d869daf021a7724e69c557dd8dbe` (r:1 w:1) + /// Storage: `Broker::RevenueInbox` (r:1 w:0) + /// Proof: `Broker::RevenueInbox` (`max_values`: Some(1), `max_size`: Some(20), added: 515, mode: `MaxEncodedLen`) fn do_tick_base() -> Weight { // Proof Size summary in bytes: - // Measured: `603` - // Estimated: `4068` - // Minimum execution time: 8_897_000 picoseconds. - Weight::from_parts(9_218_000, 4068) + // Measured: `441` + // Estimated: `1516` + // Minimum execution time: 17_083_000 picoseconds. + Weight::from_parts(18_077_000, 1516) .saturating_add(RocksDbWeight::get().reads(4_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`) @@ -930,18 +950,11 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `239` // Estimated: `1526` - // Minimum execution time: 4_678_000 picoseconds. - Weight::from_parts(4_920_000, 1526) + // Minimum execution time: 11_620_000 picoseconds. + Weight::from_parts(12_063_000, 1526) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - fn on_new_timeslice() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 229_000 picoseconds. - Weight::from_parts(268_000, 0) - } /// Storage: `Broker::SaleInfo` (r:1 w:1) /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) /// Storage: `Broker::PotentialRenewals` (r:1 w:2) @@ -952,32 +965,35 @@ impl WeightInfo for () { /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// Storage: `Authorship::Author` (r:1 w:0) - /// Proof: `Authorship::Author` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) - /// Storage: `System::Digest` (r:1 w:0) - /// Proof: `System::Digest` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::AutoRenewals` (r:1 w:1) /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) /// Storage: `Broker::Workplan` (r:0 w:1) /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) fn enable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `930` + // Measured: `1121` // Estimated: `4698` - // Minimum execution time: 51_597_000 picoseconds. - Weight::from_parts(52_609_000, 4698) - .saturating_add(RocksDbWeight::get().reads(8_u64)) + // Minimum execution time: 85_270_000 picoseconds. + Weight::from_parts(90_457_000, 4698) + .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Broker::AutoRenewals` (r:1 w:1) /// Proof: `Broker::AutoRenewals` (`max_values`: Some(1), `max_size`: Some(101), added: 596, mode: `MaxEncodedLen`) fn disable_auto_renew() -> Weight { // Proof Size summary in bytes: - // Measured: `484` + // Measured: `578` // Estimated: `1586` - // Minimum execution time: 8_907_000 picoseconds. - Weight::from_parts(9_167_000, 1586) + // Minimum execution time: 22_479_000 picoseconds. + Weight::from_parts(23_687_000, 1586) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } -} \ No newline at end of file + fn on_new_timeslice() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 245_000 picoseconds. + Weight::from_parts(290_000, 0) + } +} From 9712e2ec10bc6461051c99bd4dfd32526ab3c108 Mon Sep 17 00:00:00 2001 From: clangenb <37865735+clangenb@users.noreply.github.com> Date: Thu, 12 Dec 2024 23:08:46 +0100 Subject: [PATCH 157/340] [polkadot-runtime-parachains] migrate paras module to benchmarking v2 syntax (#6576) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [polkadot-runtime-parachains] migrate paras module to benchmarking v2 syntax Part of: * #6202 --------- Co-authored-by: Bastian Köcher --- .../parachains/src/paras/benchmarking.rs | 168 +++++++++++------- 1 file changed, 108 insertions(+), 60 deletions(-) diff --git a/polkadot/runtime/parachains/src/paras/benchmarking.rs b/polkadot/runtime/parachains/src/paras/benchmarking.rs index 7bf8b833ed91..4d617cbb05bb 100644 --- a/polkadot/runtime/parachains/src/paras/benchmarking.rs +++ b/polkadot/runtime/parachains/src/paras/benchmarking.rs @@ -17,7 +17,7 @@ use super::*; use crate::configuration::HostConfiguration; use alloc::vec; -use frame_benchmarking::benchmarks; +use frame_benchmarking::v2::*; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use polkadot_primitives::{ HeadData, Id as ParaId, ValidationCode, MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, @@ -84,41 +84,58 @@ fn generate_disordered_actions_queue() { }); } -benchmarks! { - force_set_current_code { - let c in MIN_CODE_SIZE .. MAX_CODE_SIZE; +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn force_set_current_code(c: Linear) { let new_code = ValidationCode(vec![0; c as usize]); let para_id = ParaId::from(c as u32); CurrentCodeHash::::insert(¶_id, new_code.hash()); generate_disordered_pruning::(); - }: _(RawOrigin::Root, para_id, new_code) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, para_id, new_code); + assert_last_event::(Event::CurrentCodeUpdated(para_id).into()); } - force_set_current_head { - let s in MIN_CODE_SIZE .. MAX_HEAD_DATA_SIZE; + + #[benchmark] + fn force_set_current_head(s: Linear) { let new_head = HeadData(vec![0; s as usize]); let para_id = ParaId::from(1000); - }: _(RawOrigin::Root, para_id, new_head) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, para_id, new_head); + assert_last_event::(Event::CurrentHeadUpdated(para_id).into()); } - force_set_most_recent_context { + + #[benchmark] + fn force_set_most_recent_context() { let para_id = ParaId::from(1000); let context = BlockNumberFor::::from(1000u32); - }: _(RawOrigin::Root, para_id, context) - force_schedule_code_upgrade { - let c in MIN_CODE_SIZE .. MAX_CODE_SIZE; + + #[extrinsic_call] + _(RawOrigin::Root, para_id, context); + } + + #[benchmark] + fn force_schedule_code_upgrade(c: Linear) { let new_code = ValidationCode(vec![0; c as usize]); let para_id = ParaId::from(c as u32); let block = BlockNumberFor::::from(c); generate_disordered_upgrades::(); - }: _(RawOrigin::Root, para_id, new_code, block) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, para_id, new_code, block); + assert_last_event::(Event::CodeUpgradeScheduled(para_id).into()); } - force_note_new_head { - let s in MIN_CODE_SIZE .. MAX_HEAD_DATA_SIZE; + + #[benchmark] + fn force_note_new_head(s: Linear) { let para_id = ParaId::from(1000); let new_head = HeadData(vec![0; s as usize]); let old_code_hash = ValidationCode(vec![0]).hash(); @@ -135,70 +152,101 @@ benchmarks! { &config, UpgradeStrategy::SetGoAheadSignal, ); - }: _(RawOrigin::Root, para_id, new_head) - verify { + + #[extrinsic_call] + _(RawOrigin::Root, para_id, new_head); + assert_last_event::(Event::NewHeadNoted(para_id).into()); } - force_queue_action { + + #[benchmark] + fn force_queue_action() { let para_id = ParaId::from(1000); generate_disordered_actions_queue::(); - }: _(RawOrigin::Root, para_id) - verify { - let next_session = crate::shared::CurrentSessionIndex::::get().saturating_add(One::one()); + + #[extrinsic_call] + _(RawOrigin::Root, para_id); + + let next_session = + crate::shared::CurrentSessionIndex::::get().saturating_add(One::one()); assert_last_event::(Event::ActionQueued(para_id, next_session).into()); } - add_trusted_validation_code { - let c in MIN_CODE_SIZE .. MAX_CODE_SIZE; + #[benchmark] + fn add_trusted_validation_code(c: Linear) { let new_code = ValidationCode(vec![0; c as usize]); pvf_check::prepare_bypassing_bench::(new_code.clone()); - }: _(RawOrigin::Root, new_code) - poke_unused_validation_code { + #[extrinsic_call] + _(RawOrigin::Root, new_code); + } + + #[benchmark] + fn poke_unused_validation_code() { let code_hash = [0; 32].into(); - }: _(RawOrigin::Root, code_hash) - include_pvf_check_statement { + #[extrinsic_call] + _(RawOrigin::Root, code_hash); + } + + #[benchmark] + fn include_pvf_check_statement() { let (stmt, signature) = pvf_check::prepare_inclusion_bench::(); - }: { - let _ = Pallet::::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + + #[block] + { + let _ = + Pallet::::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + } } - include_pvf_check_statement_finalize_upgrade_accept { - let (stmt, signature) = pvf_check::prepare_finalization_bench::( - VoteCause::Upgrade, - VoteOutcome::Accept, - ); - }: { - let _ = Pallet::::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + #[benchmark] + fn include_pvf_check_statement_finalize_upgrade_accept() { + let (stmt, signature) = + pvf_check::prepare_finalization_bench::(VoteCause::Upgrade, VoteOutcome::Accept); + + #[block] + { + let _ = + Pallet::::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + } } - include_pvf_check_statement_finalize_upgrade_reject { - let (stmt, signature) = pvf_check::prepare_finalization_bench::( - VoteCause::Upgrade, - VoteOutcome::Reject, - ); - }: { - let _ = Pallet::::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + #[benchmark] + fn include_pvf_check_statement_finalize_upgrade_reject() { + let (stmt, signature) = + pvf_check::prepare_finalization_bench::(VoteCause::Upgrade, VoteOutcome::Reject); + + #[block] + { + let _ = + Pallet::::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + } } - include_pvf_check_statement_finalize_onboarding_accept { - let (stmt, signature) = pvf_check::prepare_finalization_bench::( - VoteCause::Onboarding, - VoteOutcome::Accept, - ); - }: { - let _ = Pallet::::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + #[benchmark] + fn include_pvf_check_statement_finalize_onboarding_accept() { + let (stmt, signature) = + pvf_check::prepare_finalization_bench::(VoteCause::Onboarding, VoteOutcome::Accept); + + #[block] + { + let _ = + Pallet::::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + } } - include_pvf_check_statement_finalize_onboarding_reject { - let (stmt, signature) = pvf_check::prepare_finalization_bench::( - VoteCause::Onboarding, - VoteOutcome::Reject, - ); - }: { - let _ = Pallet::::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + #[benchmark] + fn include_pvf_check_statement_finalize_onboarding_reject() { + let (stmt, signature) = + pvf_check::prepare_finalization_bench::(VoteCause::Onboarding, VoteOutcome::Reject); + + #[block] + { + let _ = + Pallet::::include_pvf_check_statement(RawOrigin::None.into(), stmt, signature); + } } impl_benchmark_test_suite!( From 5153e2b5636a176c0b0d2313f95bfce32e481289 Mon Sep 17 00:00:00 2001 From: Tsvetomir Dimitrov Date: Fri, 13 Dec 2024 10:58:19 +0200 Subject: [PATCH 158/340] Collation fetching fairness (#4880) Related to https://github.com/paritytech/polkadot-sdk/issues/1797 # The problem When fetching collations in collator protocol/validator side we need to ensure that each parachain has got a fair core time share depending on its assignments in the claim queue. This means that the number of collations fetched per parachain should ideally be equal to (but definitely not bigger than) the number of claims for the particular parachain in the claim queue. # Why the current implementation is not good enough The current implementation doesn't guarantee such fairness. For each relay parent there is a `waiting_queue` (PerRelayParent -> Collations -> waiting_queue) which holds any unfetched collations advertised to the validator. The collations are fetched on first in first out principle which means that if two parachains share a core and one of the parachains is more aggressive it might starve the second parachain. How? At each relay parent up to `max_candidate_depth` candidates are accepted (enforced in `fn is_seconded_limit_reached`) so if one of the parachains is quick enough to fill in the queue with its advertisements the validator will never fetch anything from the rest of the parachains despite they are scheduled. This doesn't mean that the aggressive parachain will occupy all the core time (this is guaranteed by the runtime) but it will deny the rest of the parachains sharing the same core to have collations backed. # How to fix it The solution I am proposing is to limit fetches and advertisements based on the state of the claim queue. At each relay parent the claim queue for the core assigned to the validator is fetched. For each parachain a fetch limit is calculated (equal to the number of entries in the claim queue). Advertisements are not fetched for a parachain which has exceeded its claims in the claim queue. This solves the problem with aggressive parachains advertising too much collations. The second part is in collation fetching logic. The collator will keep track on which collations it has fetched so far. When a new collation needs to be fetched instead of popping the first entry from the `waiting_queue` the validator examines the claim queue and looks for the earliest claim which hasn't got a corresponding fetch. This way the collator will always try to prioritise the most urgent entries. ## How the 'fair share of coretime' for each parachain is determined? Thanks to async backing we can accept more than one candidate per relay parent (with some constraints). We also have got the claim queue which gives us a hint which parachain will be scheduled next on each core. So thanks to the claim queue we can determine the maximum number of claims per parachain. For example the claim queue is [A A A] at relay parent X so we know that at relay parent X we can accept three candidates for parachain A. There are two things to consider though: 1. If we accept more than one candidate at relay parent X we are claiming the slot of a future relay parent. So accepting two candidates for relay parent X means that we are claiming the slot at rp X+1 or rp X+2. 2. At the same time the slot at relay parent X could have been claimed by a previous relay parent(s). This means that we need to accept less candidates at X or even no candidates. There are a few cases worth considering: 1. Slot claimed by previous relay parent. CQ @ rp X: [A A A] Advertisements at X-1 for para A: 2 Advertisements at X-2 for para A: 2 Outcome - at rp X we can accept only 1 advertisement since our slots were already claimed. 2. Slot in our claim queue already claimed at future relay parent CQ @ rp X: [A A A] Advertisements at X+1 for para A: 1 Advertisements at X+2 for para A: 1 Outcome: at rp X we can accept only 1 advertisement since the slots in our relay parents were already claimed. The situation becomes more complicated with multiple leaves (forks). Imagine we have got a fork at rp X: ``` CQ @ rp X: [A A A] (rp X) -> (rp X+1) -> rp(X+2) \-> (rp X+1') ``` Now when we examine the claim queue at RP X we need to consider both forks. This means that accepting a candidate at X means that we should have a slot for it in *BOTH* leaves. If for example there are three candidates accepted at rp X+1' we can't accept any candidates at rp X because there will be no slot for it in one of the leaves. ## How the claims are counted There are two solutions for counting the claims at relay parent X: 1. Keep a state for the claim queue (number of claims and which of them are claimed) and look it up when accepting a collation. With this approach we need to keep the state up to date with each new advertisement and each new leaf update. 2. Calculate the state of the claim queue on the fly at each advertisement. This way we rebuild the state of the claim queue at each advertisements. Solution 1 is hard to implement with forks. There are too many variants to keep track of (different state for each leaf) and at the same time we might never need to use them. So I decided to go with option 2 - building claim queue state on the fly. To achieve this I've extended `View` from backing_implicit_view to keep track of the outer leaves. I've also added a method which accepts a relay parent and return all paths from an outer leaf to it. Let's call it `paths_to_relay_parent`. So how the counting works for relay parent X? First we examine the number of seconded and pending advertisements (more on pending in a second) from relay parent X to relay parent X-N (inclusive) where N is the length of the claim queue. Then we use `paths_to_relay_parent` to obtain all paths from outer leaves to relay parent X. We calculate the claims at relay parents X+1 to X+N (inclusive) for each leaf and get the maximum value. This way we guarantee that the candidate at rp X can be included in each leaf. This is the state of the claim queue which we use to decide if we can fetch one more advertisement at rp X or not. ## What is a pending advertisement I mentioned that we count seconded and pending advertisements at relay parent X. A pending advertisement is: 1. An advertisement which is being fetched right now. 2. An advertisement pending validation at backing subsystem. 3. An advertisement blocked for seconding by backing because we don't know on of its parent heads. Any of these is considered a 'pending fetch' and a slot for it is kept. All of them are already tracked in `State`. --------- Co-authored-by: Maciej Co-authored-by: command-bot <> Co-authored-by: Alin Dima --- .gitlab/pipeline/zombienet/polkadot.yml | 11 + .../network/collator-protocol/src/error.rs | 3 + .../src/validator_side/claim_queue_state.rs | 1055 +++++++++++++++ .../src/validator_side/collation.rs | 184 +-- .../src/validator_side/mod.rs | 599 +++++---- .../src/validator_side/tests/mod.rs | 570 ++------ .../tests/prospective_parachains.rs | 1192 +++++++++++++++-- .../src/backing_implicit_view.rs | 435 ++++-- ...astic-scaling-doesnt-break-parachains.toml | 2 +- ...stic-scaling-doesnt-break-parachains.zndsl | 2 +- .../0018-shared-core-idle-parachain.toml | 2 +- ...-coretime-collation-fetching-fairness.toml | 58 + ...coretime-collation-fetching-fairness.zndsl | 16 + .../functional/0019-verify-included-events.js | 51 + prdoc/pr_4880.prdoc | 31 + 15 files changed, 3196 insertions(+), 1015 deletions(-) create mode 100644 polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs create mode 100644 polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml create mode 100644 polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl create mode 100644 polkadot/zombienet_tests/functional/0019-verify-included-events.js create mode 100644 prdoc/pr_4880.prdoc diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index ac4bdac7ad15..e722239d890c 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -252,6 +252,17 @@ zombienet-polkadot-functional-0018-shared-core-idle-parachain: --local-dir="${LOCAL_DIR}/functional" --test="0018-shared-core-idle-parachain.zndsl" +zombienet-polkadot-functional-0019-coretime-collation-fetching-fairness: + extends: + - .zombienet-polkadot-common + before_script: + - !reference [ .zombienet-polkadot-common, before_script ] + - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/functional + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh + --local-dir="${LOCAL_DIR}/functional" + --test="0019-coretime-collation-fetching-fairness.zndsl" + zombienet-polkadot-smoke-0001-parachains-smoke-test: extends: - .zombienet-polkadot-common diff --git a/polkadot/node/network/collator-protocol/src/error.rs b/polkadot/node/network/collator-protocol/src/error.rs index 598cdcf43900..97fd4076bb8f 100644 --- a/polkadot/node/network/collator-protocol/src/error.rs +++ b/polkadot/node/network/collator-protocol/src/error.rs @@ -70,6 +70,9 @@ pub enum Error { #[error("Response receiver for claim queue request cancelled")] CancelledClaimQueue(oneshot::Canceled), + + #[error("No state for the relay parent")] + RelayParentStateNotFound, } /// An error happened on the validator side of the protocol when attempting diff --git a/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs new file mode 100644 index 000000000000..3a34cf52fec6 --- /dev/null +++ b/polkadot/node/network/collator-protocol/src/validator_side/claim_queue_state.rs @@ -0,0 +1,1055 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! `ClaimQueueState` tracks the state of the claim queue over a set of relay blocks. Refer to +//! [`ClaimQueueState`] for more details. + +use std::collections::VecDeque; + +use crate::LOG_TARGET; +use polkadot_primitives::{Hash, Id as ParaId}; + +/// Represents a single claim from the claim queue, mapped to the relay chain block where it could +/// be backed on-chain. +#[derive(Debug, PartialEq)] +struct ClaimInfo { + // Hash of the relay chain block. Can be `None` if it is still not known (a future block). + hash: Option, + /// Represents the `ParaId` scheduled for the block. Can be `None` if nothing is scheduled. + claim: Option, + /// The length of the claim queue at the block. It is used to determine the 'block window' + /// where a claim can be made. + claim_queue_len: usize, + /// A flag that indicates if the slot is claimed or not. + claimed: bool, +} + +/// Tracks the state of the claim queue over a set of relay blocks. +/// +/// Generally the claim queue represents the `ParaId` that should be scheduled at the current block +/// (the first element of the claim queue) and N other `ParaId`s which are supposed to be scheduled +/// on the next relay blocks. In other words the claim queue is a rolling window giving a hint what +/// should be built/fetched/accepted (depending on the context) at each block. +/// +/// Since the claim queue peeks into the future blocks there is a relation between the claim queue +/// state between the current block and the future blocks. +/// Let's see an example with 2 co-scheduled parachains: +/// - relay parent 1; Claim queue: [A, B, A] +/// - relay parent 2; Claim queue: [B, A, B] +/// - relay parent 3; Claim queue: [A, B, A] +/// - and so on +/// +/// Note that at rp1 the second element in the claim queue is equal to the first one in rp2. Also +/// the third element of the claim queue at rp1 is equal to the second one in rp2 and the first one +/// in rp3. +/// +/// So if we want to claim the third slot at rp 1 we are also claiming the second at rp2 and first +/// at rp3. To track this in a simple way we can project the claim queue onto the relay blocks like +/// this: +/// [A] [B] [A] -> this is the claim queue at rp3 +/// [B] [A] [B] -> this is the claim queue at rp2 +/// [A] [B] [A] -> this is the claim queue at rp1 +/// [RP 1][RP 2][RP 3][RP X][RP Y] -> relay blocks, RP x and RP Y are future blocks +/// +/// Note that the claims at each column are the same so we can simplify this by just projecting a +/// single claim over a block: +/// [A] [B] [A] [B] [A] -> claims effectively are the same +/// [RP 1][RP 2][RP 3][RP X][RP Y] -> relay blocks, RP x and RP Y are future blocks +/// +/// Basically this is how `ClaimQueueState` works. It keeps track of claims at each block by mapping +/// claims to relay blocks. +/// +/// How making a claim works? +/// At each relay block we keep track how long is the claim queue. This is a 'window' where we can +/// make a claim. So adding a claim just looks for a free spot at this window and claims it. +/// +/// Note on adding a new leaf. +/// When a new leaf is added we check if the first element in its claim queue matches with the +/// projection on the first element in 'future blocks'. If yes - the new relay block inherits this +/// claim. If not - this means that the claim queue changed for some reason so the claim can't be +/// inherited. This should not happen under normal circumstances. But if it happens it means that we +/// have got one claim which won't be satisfied in the worst case scenario. +pub(crate) struct ClaimQueueState { + block_state: VecDeque, + future_blocks: VecDeque, +} + +impl ClaimQueueState { + pub(crate) fn new() -> Self { + Self { block_state: VecDeque::new(), future_blocks: VecDeque::new() } + } + + // Appends a new leaf + pub(crate) fn add_leaf(&mut self, hash: &Hash, claim_queue: &Vec) { + if self.block_state.iter().any(|s| s.hash == Some(*hash)) { + return + } + + // First check if our view for the future blocks is consistent with the one in the claim + // queue of the new block. If not - the claim queue has changed for some reason and we need + // to readjust our view. + for (idx, expected_claim) in claim_queue.iter().enumerate() { + match self.future_blocks.get_mut(idx) { + Some(future_block) => + if future_block.claim.as_ref() != Some(expected_claim) { + // There is an inconsistency. Update our view with the one from the claim + // queue. `claimed` can't be true anymore since the `ParaId` has changed. + future_block.claimed = false; + future_block.claim = Some(*expected_claim); + }, + None => { + self.future_blocks.push_back(ClaimInfo { + hash: None, + claim: Some(*expected_claim), + // For future blocks we don't know the size of the claim queue. + // `claim_queue_len` could be an option but there is not much benefit from + // the extra boilerplate code to handle it. We set it to one since we + // usually know about one claim at each future block but this value is not + // used anywhere in the code. + claim_queue_len: 1, + claimed: false, + }); + }, + } + } + + // Now pop the first future block and add it as a leaf + let claim_info = if let Some(new_leaf) = self.future_blocks.pop_front() { + ClaimInfo { + hash: Some(*hash), + claim: claim_queue.first().copied(), + claim_queue_len: claim_queue.len(), + claimed: new_leaf.claimed, + } + } else { + // maybe the claim queue was empty but we still need to add a leaf + ClaimInfo { + hash: Some(*hash), + claim: claim_queue.first().copied(), + claim_queue_len: claim_queue.len(), + claimed: false, + } + }; + + // `future_blocks` can't be longer than the length of the claim queue at the last block - 1. + // For example this can happen if at relay block N we have got a claim queue of a length 4 + // and it's shrunk to 2. + self.future_blocks.truncate(claim_queue.len().saturating_sub(1)); + + self.block_state.push_back(claim_info); + } + + fn get_window<'a>( + &'a mut self, + relay_parent: &'a Hash, + ) -> impl Iterator + 'a { + let mut window = self + .block_state + .iter_mut() + .skip_while(|b| b.hash != Some(*relay_parent)) + .peekable(); + let cq_len = window.peek().map_or(0, |b| b.claim_queue_len); + window.chain(self.future_blocks.iter_mut()).take(cq_len) + } + + pub(crate) fn claim_at(&mut self, relay_parent: &Hash, para_id: &ParaId) -> bool { + gum::trace!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + "claim_at" + ); + self.find_a_claim(relay_parent, para_id, true) + } + + pub(crate) fn can_claim_at(&mut self, relay_parent: &Hash, para_id: &ParaId) -> bool { + gum::trace!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + "can_claim_at" + ); + + self.find_a_claim(relay_parent, para_id, false) + } + + // Returns `true` if there is a claim within `relay_parent`'s view of the claim queue for + // `para_id`. If `claim_it` is set to `true` the slot is claimed. Otherwise the function just + // reports the availability of the slot. + fn find_a_claim(&mut self, relay_parent: &Hash, para_id: &ParaId, claim_it: bool) -> bool { + let window = self.get_window(relay_parent); + + for w in window { + gum::trace!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + claim_info=?w, + ?claim_it, + "Checking claim" + ); + + if !w.claimed && w.claim == Some(*para_id) { + w.claimed = claim_it; + return true + } + } + + false + } + + pub(crate) fn unclaimed_at(&mut self, relay_parent: &Hash) -> Vec { + let window = self.get_window(relay_parent); + + window.filter(|b| !b.claimed).filter_map(|b| b.claim).collect() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn sane_initial_state() { + let mut state = ClaimQueueState::new(); + let relay_parent = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + + assert!(!state.can_claim_at(&relay_parent, ¶_id)); + assert!(!state.claim_at(&relay_parent, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent), vec![]); + } + + #[test] + fn add_leaf_works() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: false, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + // should be no op + state.add_leaf(&relay_parent_a, &claim_queue); + assert_eq!(state.block_state.len(), 1); + assert_eq!(state.future_blocks.len(), 2); + + // add another leaf + let relay_parent_b = Hash::from_low_u64_be(2); + state.add_leaf(&relay_parent_b, &claim_queue); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: false, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: false, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id, para_id, para_id]); + } + + #[test] + fn claims_at_separate_relay_parents_work() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let relay_parent_b = Hash::from_low_u64_be(2); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + state.add_leaf(&relay_parent_b, &claim_queue); + + // add one claim for a + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + // and one for b + assert!(state.can_claim_at(&relay_parent_b, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id, para_id, para_id]); + assert!(state.claim_at(&relay_parent_b, ¶_id)); + + // a should have one claim since the one for b was claimed + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id]); + // and two more for b + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id, para_id]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + } + + #[test] + fn claims_are_transferred_to_next_slot() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + + // add two claims, 2nd should be transferred to a new leaf + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id, para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id, para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + // one more + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id]); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true } + ]) + ); + + // no more claims + assert!(!state.can_claim_at(&relay_parent_a, ¶_id)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + } + + #[test] + fn claims_are_transferred_to_new_leaves() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id = ParaId::new(1); + let claim_queue = vec![para_id, para_id, para_id]; + + state.add_leaf(&relay_parent_a, &claim_queue); + + for _ in 0..3 { + assert!(state.can_claim_at(&relay_parent_a, ¶_id)); + assert!(state.claim_at(&relay_parent_a, ¶_id)); + } + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true } + ]) + ); + + // no more claims + assert!(!state.can_claim_at(&relay_parent_a, ¶_id)); + + // new leaf + let relay_parent_b = Hash::from_low_u64_be(2); + state.add_leaf(&relay_parent_b, &claim_queue); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: false } + ]) + ); + + // still no claims for a + assert!(!state.can_claim_at(&relay_parent_a, ¶_id)); + + // but can accept for b + assert!(state.can_claim_at(&relay_parent_b, ¶_id)); + assert!(state.claim_at(&relay_parent_b, ¶_id)); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id), + claim_queue_len: 3, + claimed: true, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id), claim_queue_len: 1, claimed: true } + ]) + ); + } + + #[test] + fn two_paras() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue = vec![para_id_a, para_id_b, para_id_a]; + + state.add_leaf(&relay_parent_a, &claim_queue); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_b, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_b, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_b]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true } + ]) + ); + + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true } + ]) + ); + } + + #[test] + fn claim_queue_changes_unexpectedly() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_a = vec![para_id_a, para_id_b, para_id_a]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true } + ]) + ); + + let relay_parent_b = Hash::from_low_u64_be(2); + let claim_queue_b = vec![para_id_a, para_id_a, para_id_a]; // should be [b, a, ...] + state.add_leaf(&relay_parent_b, &claim_queue_b); + + // because of the unexpected change in claim queue we lost the claim for paraB and have one + // unclaimed for paraA + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + } + ]) + ); + assert_eq!( + state.future_blocks, + // since the 3rd slot of the claim queue at rp1 is equal to the second one in rp2, this + // claim still exists + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_a), claim_queue_len: 1, claimed: true }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + } + + #[test] + fn claim_queue_changes_unexpectedly_with_two_blocks() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_a = vec![para_id_a, para_id_b, para_id_b]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.can_claim_at(&relay_parent_a, ¶_id_b)); + assert!(state.claim_at(&relay_parent_a, ¶_id_a)); + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert!(state.claim_at(&relay_parent_a, ¶_id_b)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true }, + ClaimInfo { hash: None, claim: Some(para_id_b), claim_queue_len: 1, claimed: true } + ]) + ); + + let relay_parent_b = Hash::from_low_u64_be(2); + let claim_queue_b = vec![para_id_a, para_id_a, para_id_a]; // should be [b, b, ...] + state.add_leaf(&relay_parent_b, &claim_queue_b); + + // because of the unexpected change in claim queue we lost both claims for paraB and have + // two unclaimed for paraA + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + } + + #[test] + fn empty_claim_queue() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let claim_queue_a = vec![]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: None, + claim_queue_len: 0, + claimed: false, + },]) + ); + // no claim queue so we know nothing about future blocks + assert!(state.future_blocks.is_empty()); + + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.claim_at(&relay_parent_a, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + let relay_parent_b = Hash::from_low_u64_be(2); + let claim_queue_b = vec![para_id_a]; + state.add_leaf(&relay_parent_b, &claim_queue_b); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: None, + claim_queue_len: 0, + claimed: false, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false, + }, + ]) + ); + // claim queue with length 1 doesn't say anything about future blocks + assert!(state.future_blocks.is_empty()); + + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.claim_at(&relay_parent_a, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + assert!(state.can_claim_at(&relay_parent_b, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id_a]); + assert!(state.claim_at(&relay_parent_b, ¶_id_a)); + + let relay_parent_c = Hash::from_low_u64_be(3); + let claim_queue_c = vec![para_id_a, para_id_a]; + state.add_leaf(&relay_parent_c, &claim_queue_c); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: None, + claim_queue_len: 0, + claimed: false, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: true, + }, + ClaimInfo { + hash: Some(relay_parent_c), + claim: Some(para_id_a), + claim_queue_len: 2, + claimed: false, + }, + ]) + ); + // claim queue with length 2 fills only one future block + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false, + },]) + ); + + assert!(!state.can_claim_at(&relay_parent_a, ¶_id_a)); + assert!(!state.claim_at(&relay_parent_a, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![]); + + // already claimed + assert!(!state.can_claim_at(&relay_parent_b, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![]); + assert!(!state.claim_at(&relay_parent_b, ¶_id_a)); + + assert!(state.can_claim_at(&relay_parent_c, ¶_id_a)); + assert_eq!(state.unclaimed_at(&relay_parent_c), vec![para_id_a, para_id_a]); + } + + #[test] + fn claim_queue_becomes_shorter() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_a = vec![para_id_a, para_id_b, para_id_a]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_b, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + },]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + } + ]) + ); + + let relay_parent_b = Hash::from_low_u64_be(2); + let claim_queue_b = vec![para_id_a, para_id_b]; // should be [b, a] + state.add_leaf(&relay_parent_b, &claim_queue_b); + + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id_a, para_id_b]); + // claims for `relay_parent_a` has changed. + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_a, para_id_b]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 3, + claimed: false, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_a), + claim_queue_len: 2, + claimed: false, + } + ]) + ); + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + },]) + ); + } + + #[test] + fn claim_queue_becomes_shorter_and_drops_future_claims() { + let mut state = ClaimQueueState::new(); + let relay_parent_a = Hash::from_low_u64_be(1); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_a = vec![para_id_a, para_id_b, para_id_a, para_id_b]; + + state.add_leaf(&relay_parent_a, &claim_queue_a); + + assert_eq!( + state.unclaimed_at(&relay_parent_a), + vec![para_id_a, para_id_b, para_id_a, para_id_b] + ); + + // We start with claim queue len 4. + assert_eq!( + state.block_state, + VecDeque::from(vec![ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 4, + claimed: false, + },]) + ); + // we have got three future blocks + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + }, + ClaimInfo { + hash: None, + claim: Some(para_id_b), + claim_queue_len: 1, + claimed: false + } + ]) + ); + + // The next claim len is 2, so we loose one future block + let relay_parent_b = Hash::from_low_u64_be(2); + let para_id_a = ParaId::new(1); + let para_id_b = ParaId::new(2); + let claim_queue_b = vec![para_id_b, para_id_a]; + state.add_leaf(&relay_parent_b, &claim_queue_b); + + assert_eq!(state.unclaimed_at(&relay_parent_a), vec![para_id_a, para_id_b, para_id_a]); + assert_eq!(state.unclaimed_at(&relay_parent_b), vec![para_id_b, para_id_a]); + + assert_eq!( + state.block_state, + VecDeque::from(vec![ + ClaimInfo { + hash: Some(relay_parent_a), + claim: Some(para_id_a), + claim_queue_len: 4, + claimed: false, + }, + ClaimInfo { + hash: Some(relay_parent_b), + claim: Some(para_id_b), + claim_queue_len: 2, + claimed: false, + } + ]) + ); + + assert_eq!( + state.future_blocks, + VecDeque::from(vec![ClaimInfo { + hash: None, + claim: Some(para_id_a), + claim_queue_len: 1, + claimed: false + },]) + ); + } +} diff --git a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs index cc0de1cb70f6..625140a73966 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/collation.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/collation.rs @@ -18,16 +18,28 @@ //! //! Usually a path of collations is as follows: //! 1. First, collation must be advertised by collator. -//! 2. If the advertisement was accepted, it's queued for fetch (per relay parent). -//! 3. Once it's requested, the collation is said to be Pending. -//! 4. Pending collation becomes Fetched once received, we send it to backing for validation. -//! 5. If it turns to be invalid or async backing allows seconding another candidate, carry on +//! 2. The validator inspects the claim queue and decides if the collation should be fetched +//! based on the entries there. A parachain can't have more fetched collations than the +//! entries in the claim queue at a specific relay parent. When calculating this limit the +//! validator counts all advertisements within its view not just at the relay parent. +//! 3. If the advertisement was accepted, it's queued for fetch (per relay parent). +//! 4. Once it's requested, the collation is said to be pending fetch +//! (`CollationStatus::Fetching`). +//! 5. Pending fetch collation becomes pending validation +//! (`CollationStatus::WaitingOnValidation`) once received, we send it to backing for +//! validation. +//! 6. If it turns to be invalid or async backing allows seconding another candidate, carry on //! with the next advertisement, otherwise we're done with this relay parent. //! -//! ┌──────────────────────────────────────────┐ -//! └─▶Advertised ─▶ Pending ─▶ Fetched ─▶ Validated - -use std::{collections::VecDeque, future::Future, pin::Pin, task::Poll}; +//! ┌───────────────────────────────────┐ +//! └─▶Waiting ─▶ Fetching ─▶ WaitingOnValidation + +use std::{ + collections::{BTreeMap, VecDeque}, + future::Future, + pin::Pin, + task::Poll, +}; use futures::{future::BoxFuture, FutureExt}; use polkadot_node_network_protocol::{ @@ -36,9 +48,7 @@ use polkadot_node_network_protocol::{ PeerId, }; use polkadot_node_primitives::PoV; -use polkadot_node_subsystem_util::{ - metrics::prometheus::prometheus::HistogramTimer, runtime::ProspectiveParachainsMode, -}; +use polkadot_node_subsystem_util::metrics::prometheus::prometheus::HistogramTimer; use polkadot_primitives::{ vstaging::CandidateReceiptV2 as CandidateReceipt, CandidateHash, CollatorId, Hash, HeadData, Id as ParaId, PersistedValidationData, @@ -187,12 +197,10 @@ pub struct PendingCollationFetch { pub enum CollationStatus { /// We are waiting for a collation to be advertised to us. Waiting, - /// We are currently fetching a collation. - Fetching, + /// We are currently fetching a collation for the specified `ParaId`. + Fetching(ParaId), /// We are waiting that a collation is being validated. WaitingOnValidation, - /// We have seconded a collation. - Seconded, } impl Default for CollationStatus { @@ -202,22 +210,22 @@ impl Default for CollationStatus { } impl CollationStatus { - /// Downgrades to `Waiting`, but only if `self != Seconded`. - fn back_to_waiting(&mut self, relay_parent_mode: ProspectiveParachainsMode) { - match self { - Self::Seconded => - if relay_parent_mode.is_enabled() { - // With async backing enabled it's allowed to - // second more candidates. - *self = Self::Waiting - }, - _ => *self = Self::Waiting, - } + /// Downgrades to `Waiting` + pub fn back_to_waiting(&mut self) { + *self = Self::Waiting } } +/// The number of claims in the claim queue and seconded candidates count for a specific `ParaId`. +#[derive(Default, Debug)] +struct CandidatesStatePerPara { + /// How many collations have been seconded. + pub seconded_per_para: usize, + // Claims in the claim queue for the `ParaId`. + pub claims_per_para: usize, +} + /// Information about collations per relay parent. -#[derive(Default)] pub struct Collations { /// What is the current status in regards to a collation for this relay parent? pub status: CollationStatus, @@ -226,75 +234,89 @@ pub struct Collations { /// This is the currently last started fetch, which did not exceed `MAX_UNSHARED_DOWNLOAD_TIME` /// yet. pub fetching_from: Option<(CollatorId, Option)>, - /// Collation that were advertised to us, but we did not yet fetch. - pub waiting_queue: VecDeque<(PendingCollation, CollatorId)>, - /// How many collations have been seconded. - pub seconded_count: usize, + /// Collation that were advertised to us, but we did not yet request or fetch. Grouped by + /// `ParaId`. + waiting_queue: BTreeMap>, + /// Number of seconded candidates and claims in the claim queue per `ParaId`. + candidates_state: BTreeMap, } impl Collations { + pub(super) fn new(group_assignments: &Vec) -> Self { + let mut candidates_state = BTreeMap::::new(); + + for para_id in group_assignments { + candidates_state.entry(*para_id).or_default().claims_per_para += 1; + } + + Self { + status: Default::default(), + fetching_from: None, + waiting_queue: Default::default(), + candidates_state, + } + } + /// Note a seconded collation for a given para. - pub(super) fn note_seconded(&mut self) { - self.seconded_count += 1 + pub(super) fn note_seconded(&mut self, para_id: ParaId) { + self.candidates_state.entry(para_id).or_default().seconded_per_para += 1; + gum::trace!( + target: LOG_TARGET, + ?para_id, + new_count=self.candidates_state.entry(para_id).or_default().seconded_per_para, + "Note seconded." + ); + self.status.back_to_waiting(); } - /// Returns the next collation to fetch from the `waiting_queue`. + /// Adds a new collation to the waiting queue for the relay parent. This function doesn't + /// perform any limits check. The caller should assure that the collation limit is respected. + pub(super) fn add_to_waiting_queue(&mut self, collation: (PendingCollation, CollatorId)) { + self.waiting_queue.entry(collation.0.para_id).or_default().push_back(collation); + } + + /// Picks a collation to fetch from the waiting queue. + /// When fetching collations we need to ensure that each parachain has got a fair core time + /// share depending on its assignments in the claim queue. This means that the number of + /// collations seconded per parachain should ideally be equal to the number of claims for the + /// particular parachain in the claim queue. /// - /// This will reset the status back to `Waiting` using [`CollationStatus::back_to_waiting`]. + /// To achieve this each seconded collation is mapped to an entry from the claim queue. The next + /// fetch is the first unfulfilled entry from the claim queue for which there is an + /// advertisement. /// - /// Returns `Some(_)` if there is any collation to fetch, the `status` is not `Seconded` and - /// the passed in `finished_one` is the currently `waiting_collation`. - pub(super) fn get_next_collation_to_fetch( + /// `unfulfilled_claim_queue_entries` represents all claim queue entries which are still not + /// fulfilled. + pub(super) fn pick_a_collation_to_fetch( &mut self, - finished_one: &(CollatorId, Option), - relay_parent_mode: ProspectiveParachainsMode, + unfulfilled_claim_queue_entries: Vec, ) -> Option<(PendingCollation, CollatorId)> { - // If finished one does not match waiting_collation, then we already dequeued another fetch - // to replace it. - if let Some((collator_id, maybe_candidate_hash)) = self.fetching_from.as_ref() { - // If a candidate hash was saved previously, `finished_one` must include this too. - if collator_id != &finished_one.0 && - maybe_candidate_hash.map_or(true, |hash| Some(&hash) != finished_one.1.as_ref()) + gum::trace!( + target: LOG_TARGET, + waiting_queue=?self.waiting_queue, + candidates_state=?self.candidates_state, + "Pick a collation to fetch." + ); + + for assignment in unfulfilled_claim_queue_entries { + // if there is an unfulfilled assignment - return it + if let Some(collation) = self + .waiting_queue + .get_mut(&assignment) + .and_then(|collations| collations.pop_front()) { - gum::trace!( - target: LOG_TARGET, - waiting_collation = ?self.fetching_from, - ?finished_one, - "Not proceeding to the next collation - has already been done." - ); - return None + return Some(collation) } } - self.status.back_to_waiting(relay_parent_mode); - - match self.status { - // We don't need to fetch any other collation when we already have seconded one. - CollationStatus::Seconded => None, - CollationStatus::Waiting => - if self.is_seconded_limit_reached(relay_parent_mode) { - None - } else { - self.waiting_queue.pop_front() - }, - CollationStatus::WaitingOnValidation | CollationStatus::Fetching => - unreachable!("We have reset the status above!"), - } + + None } - /// Checks the limit of seconded candidates. - pub(super) fn is_seconded_limit_reached( - &self, - relay_parent_mode: ProspectiveParachainsMode, - ) -> bool { - let seconded_limit = - if let ProspectiveParachainsMode::Enabled { max_candidate_depth, .. } = - relay_parent_mode - { - max_candidate_depth + 1 - } else { - 1 - }; - self.seconded_count >= seconded_limit + pub(super) fn seconded_for_para(&self, para_id: &ParaId) -> usize { + self.candidates_state + .get(¶_id) + .map(|state| state.seconded_per_para) + .unwrap_or_default() } } diff --git a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs index 36ec959c3406..5f5effcde9a8 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/mod.rs @@ -49,22 +49,25 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::{ backing_implicit_view::View as ImplicitView, reputation::{ReputationAggregator, REPUTATION_CHANGE_INTERVAL}, - request_claim_queue, request_session_index_for_child, - runtime::{prospective_parachains_mode, request_node_features, ProspectiveParachainsMode}, + request_async_backing_params, request_claim_queue, request_session_index_for_child, + runtime::{recv_runtime, request_node_features}, }; use polkadot_primitives::{ node_features, - vstaging::{CandidateDescriptorV2, CandidateDescriptorVersion, CoreState}, - CandidateHash, CollatorId, CoreIndex, Hash, HeadData, Id as ParaId, OccupiedCoreAssumption, - PersistedValidationData, SessionIndex, + vstaging::{CandidateDescriptorV2, CandidateDescriptorVersion}, + AsyncBackingParams, CandidateHash, CollatorId, CoreIndex, Hash, HeadData, Id as ParaId, + OccupiedCoreAssumption, PersistedValidationData, SessionIndex, }; use crate::error::{Error, FetchError, Result, SecondingError}; use self::collation::BlockedCollationId; +use self::claim_queue_state::ClaimQueueState; + use super::{modify_reputation, tick_stream, LOG_TARGET}; +mod claim_queue_state; mod collation; mod metrics; @@ -163,27 +166,19 @@ impl PeerData { fn update_view( &mut self, implicit_view: &ImplicitView, - active_leaves: &HashMap, - per_relay_parent: &HashMap, + active_leaves: &HashMap, new_view: View, ) { let old_view = std::mem::replace(&mut self.view, new_view); if let PeerState::Collating(ref mut peer_state) = self.state { for removed in old_view.difference(&self.view) { - // Remove relay parent advertisements if it went out - // of our (implicit) view. - let keep = per_relay_parent - .get(removed) - .map(|s| { - is_relay_parent_in_implicit_view( - removed, - s.prospective_parachains_mode, - implicit_view, - active_leaves, - peer_state.para_id, - ) - }) - .unwrap_or(false); + // Remove relay parent advertisements if it went out of our (implicit) view. + let keep = is_relay_parent_in_implicit_view( + removed, + implicit_view, + active_leaves, + peer_state.para_id, + ); if !keep { peer_state.advertisements.remove(&removed); @@ -196,8 +191,7 @@ impl PeerData { fn prune_old_advertisements( &mut self, implicit_view: &ImplicitView, - active_leaves: &HashMap, - per_relay_parent: &HashMap, + active_leaves: &HashMap, ) { if let PeerState::Collating(ref mut peer_state) = self.state { peer_state.advertisements.retain(|hash, _| { @@ -205,36 +199,30 @@ impl PeerData { // - Relay parent is an active leaf // - It belongs to allowed ancestry under some leaf // Discard otherwise. - per_relay_parent.get(hash).map_or(false, |s| { - is_relay_parent_in_implicit_view( - hash, - s.prospective_parachains_mode, - implicit_view, - active_leaves, - peer_state.para_id, - ) - }) + is_relay_parent_in_implicit_view( + hash, + implicit_view, + active_leaves, + peer_state.para_id, + ) }); } } - /// Note an advertisement by the collator. Returns `true` if the advertisement was imported - /// successfully. Fails if the advertisement is duplicate, out of view, or the peer has not - /// declared itself a collator. + /// Performs sanity check for an advertisement and notes it as advertised. fn insert_advertisement( &mut self, on_relay_parent: Hash, - relay_parent_mode: ProspectiveParachainsMode, candidate_hash: Option, implicit_view: &ImplicitView, - active_leaves: &HashMap, + active_leaves: &HashMap, + per_relay_parent: &PerRelayParent, ) -> std::result::Result<(CollatorId, ParaId), InsertAdvertisementError> { match self.state { PeerState::Connected(_) => Err(InsertAdvertisementError::UndeclaredCollator), PeerState::Collating(ref mut state) => { if !is_relay_parent_in_implicit_view( &on_relay_parent, - relay_parent_mode, implicit_view, active_leaves, state.para_id, @@ -242,53 +230,41 @@ impl PeerData { return Err(InsertAdvertisementError::OutOfOurView) } - match (relay_parent_mode, candidate_hash) { - (ProspectiveParachainsMode::Disabled, candidate_hash) => { - if state.advertisements.contains_key(&on_relay_parent) { - return Err(InsertAdvertisementError::Duplicate) - } - state - .advertisements - .insert(on_relay_parent, HashSet::from_iter(candidate_hash)); - }, - ( - ProspectiveParachainsMode::Enabled { max_candidate_depth, .. }, - candidate_hash, - ) => { - if let Some(candidate_hash) = candidate_hash { - if state - .advertisements - .get(&on_relay_parent) - .map_or(false, |candidates| candidates.contains(&candidate_hash)) - { - return Err(InsertAdvertisementError::Duplicate) - } - - let candidates = - state.advertisements.entry(on_relay_parent).or_default(); - - if candidates.len() > max_candidate_depth { - return Err(InsertAdvertisementError::PeerLimitReached) - } - candidates.insert(candidate_hash); - } else { - if self.version != CollationVersion::V1 { - gum::error!( - target: LOG_TARGET, - "Programming error, `candidate_hash` can not be `None` \ - for non `V1` networking.", - ); - } - - if state.advertisements.contains_key(&on_relay_parent) { - return Err(InsertAdvertisementError::Duplicate) - } - state - .advertisements - .insert(on_relay_parent, HashSet::from_iter(candidate_hash)); - }; - }, - } + if let Some(candidate_hash) = candidate_hash { + if state + .advertisements + .get(&on_relay_parent) + .map_or(false, |candidates| candidates.contains(&candidate_hash)) + { + return Err(InsertAdvertisementError::Duplicate) + } + + let candidates = state.advertisements.entry(on_relay_parent).or_default(); + + // Current assignments is equal to the length of the claim queue. No honest + // collator should send that many advertisements. + if candidates.len() > per_relay_parent.assignment.current.len() { + return Err(InsertAdvertisementError::PeerLimitReached) + } + + candidates.insert(candidate_hash); + } else { + if self.version != CollationVersion::V1 { + gum::error!( + target: LOG_TARGET, + "Programming error, `candidate_hash` can not be `None` \ + for non `V1` networking.", + ); + } + + if state.advertisements.contains_key(&on_relay_parent) { + return Err(InsertAdvertisementError::Duplicate) + } + + state + .advertisements + .insert(on_relay_parent, HashSet::from_iter(candidate_hash)); + }; state.last_active = Instant::now(); Ok((state.collator_id.clone(), state.para_id)) @@ -369,7 +345,6 @@ struct GroupAssignments { } struct PerRelayParent { - prospective_parachains_mode: ProspectiveParachainsMode, assignment: GroupAssignments, collations: Collations, v2_receipts: bool, @@ -390,11 +365,10 @@ struct State { /// ancestry of some active leaf, then it does support prospective parachains. implicit_view: ImplicitView, - /// All active leaves observed by us, including both that do and do not - /// support prospective parachains. This mapping works as a replacement for + /// All active leaves observed by us. This mapping works as a replacement for /// [`polkadot_node_network_protocol::View`] and can be dropped once the transition /// to asynchronous backing is done. - active_leaves: HashMap, + active_leaves: HashMap, /// State tracked per relay parent. per_relay_parent: HashMap, @@ -437,23 +411,69 @@ struct State { reputation: ReputationAggregator, } +impl State { + // Returns the number of seconded and pending collations for a specific `ParaId`. Pending + // collations are: + // 1. Collations being fetched from a collator. + // 2. Collations waiting for validation from backing subsystem. + // 3. Collations blocked from seconding due to parent not being known by backing subsystem. + fn seconded_and_pending_for_para(&self, relay_parent: &Hash, para_id: &ParaId) -> usize { + let seconded = self + .per_relay_parent + .get(relay_parent) + .map_or(0, |per_relay_parent| per_relay_parent.collations.seconded_for_para(para_id)); + + let pending_fetch = self.per_relay_parent.get(relay_parent).map_or(0, |rp_state| { + match rp_state.collations.status { + CollationStatus::Fetching(pending_para_id) if pending_para_id == *para_id => 1, + _ => 0, + } + }); + + let waiting_for_validation = self + .fetched_candidates + .keys() + .filter(|fc| fc.relay_parent == *relay_parent && fc.para_id == *para_id) + .count(); + + let blocked_from_seconding = + self.blocked_from_seconding.values().fold(0, |acc, blocked_collations| { + acc + blocked_collations + .iter() + .filter(|pc| { + pc.candidate_receipt.descriptor.para_id() == *para_id && + pc.candidate_receipt.descriptor.relay_parent() == *relay_parent + }) + .count() + }); + + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + seconded, + pending_fetch, + waiting_for_validation, + blocked_from_seconding, + "Seconded and pending collations for para", + ); + + seconded + pending_fetch + waiting_for_validation + blocked_from_seconding + } +} + fn is_relay_parent_in_implicit_view( relay_parent: &Hash, - relay_parent_mode: ProspectiveParachainsMode, implicit_view: &ImplicitView, - active_leaves: &HashMap, + active_leaves: &HashMap, para_id: ParaId, ) -> bool { - match relay_parent_mode { - ProspectiveParachainsMode::Disabled => active_leaves.contains_key(relay_parent), - ProspectiveParachainsMode::Enabled { .. } => active_leaves.iter().any(|(hash, mode)| { - mode.is_enabled() && - implicit_view - .known_allowed_relay_parents_under(hash, Some(para_id)) - .unwrap_or_default() - .contains(relay_parent) - }), - } + active_leaves.iter().any(|(hash, _)| { + implicit_view + .known_allowed_relay_parents_under(hash, Some(para_id)) + .unwrap_or_default() + .contains(relay_parent) + }) } async fn construct_per_relay_parent( @@ -461,7 +481,6 @@ async fn construct_per_relay_parent( current_assignments: &mut HashMap, keystore: &KeystorePtr, relay_parent: Hash, - relay_parent_mode: ProspectiveParachainsMode, v2_receipts: bool, session_index: SessionIndex, ) -> Result> @@ -479,39 +498,24 @@ where .await .map_err(Error::CancelledValidatorGroups)??; - let cores = polkadot_node_subsystem_util::request_availability_cores(relay_parent, sender) - .await - .await - .map_err(Error::CancelledAvailabilityCores)??; - let core_now = if let Some(group) = polkadot_node_subsystem_util::signing_key_and_index(&validators, keystore).and_then( |(_, index)| polkadot_node_subsystem_util::find_validator_group(&groups, index), ) { - rotation_info.core_for_group(group, cores.len()) + rotation_info.core_for_group(group, groups.len()) } else { gum::trace!(target: LOG_TARGET, ?relay_parent, "Not a validator"); return Ok(None) }; - let claim_queue = request_claim_queue(relay_parent, sender) + let mut claim_queue = request_claim_queue(relay_parent, sender) .await .await .map_err(Error::CancelledClaimQueue)??; - let paras_now = cores - .get(core_now.0 as usize) - .and_then(|c| match (c, relay_parent_mode) { - (CoreState::Occupied(_), ProspectiveParachainsMode::Disabled) => None, - ( - CoreState::Occupied(_), - ProspectiveParachainsMode::Enabled { max_candidate_depth: 0, .. }, - ) => None, - _ => claim_queue.get(&core_now).cloned(), - }) - .unwrap_or_else(|| VecDeque::new()); - - for para_id in paras_now.iter() { + let assigned_paras = claim_queue.remove(&core_now).unwrap_or_else(|| VecDeque::new()); + + for para_id in assigned_paras.iter() { let entry = current_assignments.entry(*para_id).or_default(); *entry += 1; if *entry == 1 { @@ -524,10 +528,12 @@ where } } + let assignment = GroupAssignments { current: assigned_paras.into_iter().collect() }; + let collations = Collations::new(&assignment.current); + Ok(Some(PerRelayParent { - prospective_parachains_mode: relay_parent_mode, - assignment: GroupAssignments { current: paras_now.into_iter().collect() }, - collations: Collations::default(), + assignment, + collations, v2_receipts, session_index, current_core: core_now, @@ -655,12 +661,7 @@ fn handle_peer_view_change(state: &mut State, peer_id: PeerId, view: View) { None => return, }; - peer_data.update_view( - &state.implicit_view, - &state.active_leaves, - &state.per_relay_parent, - view, - ); + peer_data.update_view(&state.implicit_view, &state.active_leaves, view); state.collation_requests_cancel_handles.retain(|pc, handle| { let keep = pc.peer_id != peer_id || peer_data.has_advertised(&pc.relay_parent, None); if !keep { @@ -693,7 +694,6 @@ async fn request_collation( .get_mut(&relay_parent) .ok_or(FetchError::RelayParentOutOfView)?; - // Relay parent mode is checked in `handle_advertisement`. let (requests, response_recv) = match (peer_protocol_version, prospective_candidate) { (CollationVersion::V1, _) => { let (req, response_recv) = OutgoingRequest::new( @@ -739,7 +739,7 @@ async fn request_collation( let maybe_candidate_hash = prospective_candidate.as_ref().map(ProspectiveCandidate::candidate_hash); - per_relay_parent.collations.status = CollationStatus::Fetching; + per_relay_parent.collations.status = CollationStatus::Fetching(para_id); per_relay_parent .collations .fetching_from @@ -1050,6 +1050,62 @@ async fn second_unblocked_collations( } } +fn ensure_seconding_limit_is_respected( + relay_parent: &Hash, + para_id: ParaId, + state: &State, +) -> std::result::Result<(), AdvertisementError> { + let paths = state.implicit_view.paths_via_relay_parent(relay_parent); + + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + ?paths, + "Checking seconding limit", + ); + + let mut has_claim_at_some_path = false; + for path in paths { + let mut cq_state = ClaimQueueState::new(); + for ancestor in &path { + let seconded_and_pending = state.seconded_and_pending_for_para(&ancestor, ¶_id); + cq_state.add_leaf( + &ancestor, + &state + .per_relay_parent + .get(ancestor) + .ok_or(AdvertisementError::RelayParentUnknown)? + .assignment + .current, + ); + for _ in 0..seconded_and_pending { + cq_state.claim_at(ancestor, ¶_id); + } + } + + if cq_state.can_claim_at(relay_parent, ¶_id) { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + ?para_id, + ?path, + "Seconding limit respected at path", + ); + has_claim_at_some_path = true; + break + } + } + + // If there is a place in the claim queue for the candidate at at least one path we will accept + // it. + if has_claim_at_some_path { + Ok(()) + } else { + Err(AdvertisementError::SecondedLimitReached) + } +} + async fn handle_advertisement( sender: &mut Sender, state: &mut State, @@ -1072,7 +1128,6 @@ where .get(&relay_parent) .ok_or(AdvertisementError::RelayParentUnknown)?; - let relay_parent_mode = per_relay_parent.prospective_parachains_mode; let assignment = &per_relay_parent.assignment; let collator_para_id = @@ -1088,32 +1143,29 @@ where let (collator_id, para_id) = peer_data .insert_advertisement( relay_parent, - relay_parent_mode, candidate_hash, &state.implicit_view, &state.active_leaves, + &per_relay_parent, ) .map_err(AdvertisementError::Invalid)?; - if per_relay_parent.collations.is_seconded_limit_reached(relay_parent_mode) { - return Err(AdvertisementError::SecondedLimitReached) - } + ensure_seconding_limit_is_respected(&relay_parent, para_id, state)?; if let Some((candidate_hash, parent_head_data_hash)) = prospective_candidate { // Check if backing subsystem allows to second this candidate. // // This is also only important when async backing or elastic scaling is enabled. - let seconding_not_allowed = relay_parent_mode.is_enabled() && - !can_second( - sender, - collator_para_id, - relay_parent, - candidate_hash, - parent_head_data_hash, - ) - .await; + let can_second = can_second( + sender, + collator_para_id, + relay_parent, + candidate_hash, + parent_head_data_hash, + ) + .await; - if seconding_not_allowed { + if !can_second { return Err(AdvertisementError::BlockedByBacking) } } @@ -1143,8 +1195,8 @@ where Ok(()) } -/// Enqueue collation for fetching. The advertisement is expected to be -/// validated. +/// Enqueue collation for fetching. The advertisement is expected to be validated and the seconding +/// limit checked. async fn enqueue_collation( sender: &mut Sender, state: &mut State, @@ -1179,7 +1231,6 @@ where return Ok(()) }, }; - let relay_parent_mode = per_relay_parent.prospective_parachains_mode; let prospective_candidate = prospective_candidate.map(|(candidate_hash, parent_head_data_hash)| ProspectiveCandidate { candidate_hash, @@ -1187,22 +1238,11 @@ where }); let collations = &mut per_relay_parent.collations; - if collations.is_seconded_limit_reached(relay_parent_mode) { - gum::trace!( - target: LOG_TARGET, - peer_id = ?peer_id, - %para_id, - ?relay_parent, - "Limit of seconded collations reached for valid advertisement", - ); - return Ok(()) - } - let pending_collation = PendingCollation::new(relay_parent, para_id, &peer_id, prospective_candidate); match collations.status { - CollationStatus::Fetching | CollationStatus::WaitingOnValidation => { + CollationStatus::Fetching(_) | CollationStatus::WaitingOnValidation => { gum::trace!( target: LOG_TARGET, peer_id = ?peer_id, @@ -1210,26 +1250,13 @@ where ?relay_parent, "Added collation to the pending list" ); - collations.waiting_queue.push_back((pending_collation, collator_id)); + collations.add_to_waiting_queue((pending_collation, collator_id)); }, CollationStatus::Waiting => { + // We were waiting for a collation to be advertised to us (we were idle) so we can fetch + // the new collation immediately fetch_collation(sender, state, pending_collation, collator_id).await?; }, - CollationStatus::Seconded if relay_parent_mode.is_enabled() => { - // Limit is not reached, it's allowed to second another - // collation. - fetch_collation(sender, state, pending_collation, collator_id).await?; - }, - CollationStatus::Seconded => { - gum::trace!( - target: LOG_TARGET, - peer_id = ?peer_id, - %para_id, - ?relay_parent, - ?relay_parent_mode, - "A collation has already been seconded", - ); - }, } Ok(()) @@ -1255,7 +1282,10 @@ where .await .await .map_err(Error::CancelledSessionIndex)??; - let mode = prospective_parachains_mode(sender, *leaf).await?; + + let async_backing_params = + recv_runtime(request_async_backing_params(*leaf, sender).await).await?; + let v2_receipts = request_node_features(*leaf, session_index, sender) .await? .unwrap_or_default() @@ -1268,7 +1298,6 @@ where &mut state.current_assignments, keystore, *leaf, - mode, v2_receipts, session_index, ) @@ -1277,53 +1306,53 @@ where continue }; - state.active_leaves.insert(*leaf, mode); + state.active_leaves.insert(*leaf, async_backing_params); state.per_relay_parent.insert(*leaf, per_relay_parent); - if mode.is_enabled() { - state - .implicit_view - .activate_leaf(sender, *leaf) - .await - .map_err(Error::ImplicitViewFetchError)?; - - // Order is always descending. - let allowed_ancestry = state - .implicit_view - .known_allowed_relay_parents_under(leaf, None) - .unwrap_or_default(); - for block_hash in allowed_ancestry { - if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { - // Safe to use the same v2 receipts config for the allowed relay parents as well - // as the same session index since they must be in the same session. - if let Some(per_relay_parent) = construct_per_relay_parent( - sender, - &mut state.current_assignments, - keystore, - *block_hash, - mode, - v2_receipts, - session_index, - ) - .await? - { - entry.insert(per_relay_parent); - } + state + .implicit_view + .activate_leaf(sender, *leaf) + .await + .map_err(Error::ImplicitViewFetchError)?; + + // Order is always descending. + let allowed_ancestry = state + .implicit_view + .known_allowed_relay_parents_under(leaf, None) + .unwrap_or_default(); + for block_hash in allowed_ancestry { + if let Entry::Vacant(entry) = state.per_relay_parent.entry(*block_hash) { + // Safe to use the same v2 receipts config for the allowed relay parents as well + // as the same session index since they must be in the same session. + if let Some(per_relay_parent) = construct_per_relay_parent( + sender, + &mut state.current_assignments, + keystore, + *block_hash, + v2_receipts, + session_index, + ) + .await? + { + entry.insert(per_relay_parent); } } } } - for (removed, mode) in removed { + for (removed, _) in removed { + gum::trace!( + target: LOG_TARGET, + ?view, + ?removed, + "handle_our_view_change - removed", + ); + state.active_leaves.remove(removed); // If the leaf is deactivated it still may stay in the view as a part // of implicit ancestry. Only update the state after the hash is actually // pruned from the block info storage. - let pruned = if mode.is_enabled() { - state.implicit_view.deactivate_leaf(*removed) - } else { - vec![*removed] - }; + let pruned = state.implicit_view.deactivate_leaf(*removed); for removed in pruned { if let Some(per_relay_parent) = state.per_relay_parent.remove(&removed) { @@ -1353,11 +1382,7 @@ where }); for (peer_id, peer_data) in state.peer_data.iter_mut() { - peer_data.prune_old_advertisements( - &state.implicit_view, - &state.active_leaves, - &state.per_relay_parent, - ); + peer_data.prune_old_advertisements(&state.implicit_view, &state.active_leaves); // Disconnect peers who are not relevant to our current or next para. // @@ -1490,8 +1515,9 @@ async fn process_msg( if let Some(CollationEvent { collator_id, pending_collation, .. }) = state.fetched_candidates.remove(&fetched_collation) { - let PendingCollation { relay_parent, peer_id, prospective_candidate, .. } = - pending_collation; + let PendingCollation { + relay_parent, peer_id, prospective_candidate, para_id, .. + } = pending_collation; note_good_collation( &mut state.reputation, ctx.sender(), @@ -1511,8 +1537,7 @@ async fn process_msg( } if let Some(rp_state) = state.per_relay_parent.get_mut(&parent) { - rp_state.collations.status = CollationStatus::Seconded; - rp_state.collations.note_seconded(); + rp_state.collations.note_seconded(para_id); } // See if we've unblocked other collations for seconding. @@ -1641,6 +1666,7 @@ async fn run_inner( disconnect_inactive_peers(ctx.sender(), &eviction_policy, &state.peer_data).await; }, resp = state.collation_requests.select_next_some() => { + let relay_parent = resp.0.pending_collation.relay_parent; let res = match handle_collation_fetch_response( &mut state, resp, @@ -1649,9 +1675,17 @@ async fn run_inner( ).await { Err(Some((peer_id, rep))) => { modify_reputation(&mut state.reputation, ctx.sender(), peer_id, rep).await; + // Reset the status for the relay parent + state.per_relay_parent.get_mut(&relay_parent).map(|rp| { + rp.collations.status.back_to_waiting(); + }); continue }, Err(None) => { + // Reset the status for the relay parent + state.per_relay_parent.get_mut(&relay_parent).map(|rp| { + rp.collations.status.back_to_waiting(); + }); continue }, Ok(res) => res @@ -1730,11 +1764,7 @@ async fn dequeue_next_collation_and_fetch( // The collator we tried to fetch from last, optionally which candidate. previous_fetch: (CollatorId, Option), ) { - while let Some((next, id)) = state.per_relay_parent.get_mut(&relay_parent).and_then(|state| { - state - .collations - .get_next_collation_to_fetch(&previous_fetch, state.prospective_parachains_mode) - }) { + while let Some((next, id)) = get_next_collation_to_fetch(&previous_fetch, relay_parent, state) { gum::debug!( target: LOG_TARGET, ?relay_parent, @@ -1843,9 +1873,7 @@ async fn kick_off_seconding( collation_event.collator_protocol_version, collation_event.pending_collation.prospective_candidate, ) { - (CollationVersion::V2, Some(ProspectiveCandidate { parent_head_data_hash, .. })) - if per_relay_parent.prospective_parachains_mode.is_enabled() => - { + (CollationVersion::V2, Some(ProspectiveCandidate { parent_head_data_hash, .. })) => { let pvd = request_prospective_validation_data( ctx.sender(), relay_parent, @@ -1857,8 +1885,7 @@ async fn kick_off_seconding( (pvd, maybe_parent_head_data, Some(parent_head_data_hash)) }, - // Support V2 collators without async backing enabled. - (CollationVersion::V2, Some(_)) | (CollationVersion::V1, _) => { + (CollationVersion::V1, _) => { let pvd = request_persisted_validation_data( ctx.sender(), candidate_receipt.descriptor().relay_parent(), @@ -2107,6 +2134,106 @@ async fn handle_collation_fetch_response( result } +// Returns the claim queue without fetched or pending advertisement. The resulting `Vec` keeps the +// order in the claim queue so the earlier an element is located in the `Vec` the higher its +// priority is. +fn unfulfilled_claim_queue_entries(relay_parent: &Hash, state: &State) -> Result> { + let relay_parent_state = state + .per_relay_parent + .get(relay_parent) + .ok_or(Error::RelayParentStateNotFound)?; + let scheduled_paras = relay_parent_state.assignment.current.iter().collect::>(); + let paths = state.implicit_view.paths_via_relay_parent(relay_parent); + + let mut claim_queue_states = Vec::new(); + for path in paths { + let mut cq_state = ClaimQueueState::new(); + for ancestor in &path { + cq_state.add_leaf( + &ancestor, + &state + .per_relay_parent + .get(&ancestor) + .ok_or(Error::RelayParentStateNotFound)? + .assignment + .current, + ); + + for para_id in &scheduled_paras { + let seconded_and_pending = state.seconded_and_pending_for_para(&ancestor, ¶_id); + for _ in 0..seconded_and_pending { + cq_state.claim_at(&ancestor, ¶_id); + } + } + } + claim_queue_states.push(cq_state); + } + + // From the claim queue state for each leaf we have to return a combined single one. Go for a + // simple solution and return the longest one. In theory we always prefer the earliest entries + // in the claim queue so there is a good chance that the longest path is the one with + // unsatisfied entries in the beginning. This is not guaranteed as we might have fetched 2nd or + // 3rd spot from the claim queue but it should be good enough. + let unfulfilled_entries = claim_queue_states + .iter_mut() + .map(|cq| cq.unclaimed_at(relay_parent)) + .max_by(|a, b| a.len().cmp(&b.len())) + .unwrap_or_default(); + + Ok(unfulfilled_entries) +} + +/// Returns the next collation to fetch from the `waiting_queue` and reset the status back to +/// `Waiting`. +fn get_next_collation_to_fetch( + finished_one: &(CollatorId, Option), + relay_parent: Hash, + state: &mut State, +) -> Option<(PendingCollation, CollatorId)> { + let unfulfilled_entries = match unfulfilled_claim_queue_entries(&relay_parent, &state) { + Ok(entries) => entries, + Err(err) => { + gum::error!( + target: LOG_TARGET, + ?relay_parent, + ?err, + "Failed to get unfulfilled claim queue entries" + ); + return None + }, + }; + let rp_state = match state.per_relay_parent.get_mut(&relay_parent) { + Some(rp_state) => rp_state, + None => { + gum::error!( + target: LOG_TARGET, + ?relay_parent, + "Failed to get relay parent state" + ); + return None + }, + }; + + // If finished one does not match waiting_collation, then we already dequeued another fetch + // to replace it. + if let Some((collator_id, maybe_candidate_hash)) = rp_state.collations.fetching_from.as_ref() { + // If a candidate hash was saved previously, `finished_one` must include this too. + if collator_id != &finished_one.0 && + maybe_candidate_hash.map_or(true, |hash| Some(&hash) != finished_one.1.as_ref()) + { + gum::trace!( + target: LOG_TARGET, + waiting_collation = ?rp_state.collations.fetching_from, + ?finished_one, + "Not proceeding to the next collation - has already been done." + ); + return None + } + } + rp_state.collations.status.back_to_waiting(); + rp_state.collations.pick_a_collation_to_fetch(unfulfilled_entries) +} + // Sanity check the candidate descriptor version. fn descriptor_version_sanity_check( descriptor: &CandidateDescriptorV2, diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs index f2f23c188a66..5a2e135419dd 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/mod.rs @@ -28,28 +28,24 @@ use std::{ time::Duration, }; +use self::prospective_parachains::update_view; use polkadot_node_network_protocol::{ - our_view, peer_set::CollationVersion, request_response::{Requests, ResponseSender}, ObservedRole, }; use polkadot_node_primitives::{BlockData, PoV}; -use polkadot_node_subsystem::{ - errors::RuntimeApiError, - messages::{AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest}, +use polkadot_node_subsystem::messages::{ + AllMessages, ReportPeerMessage, RuntimeApiMessage, RuntimeApiRequest, }; use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_node_subsystem_util::{reputation::add_reputation, TimeoutExt}; use polkadot_primitives::{ - node_features, - vstaging::{CandidateReceiptV2 as CandidateReceipt, CoreState, OccupiedCore}, - CollatorPair, CoreIndex, GroupIndex, GroupRotationInfo, HeadData, NodeFeatures, - PersistedValidationData, ScheduledCore, ValidatorId, ValidatorIndex, -}; -use polkadot_primitives_test_helpers::{ - dummy_candidate_descriptor, dummy_candidate_receipt_bad_sig, dummy_hash, + node_features, vstaging::CandidateReceiptV2 as CandidateReceipt, AsyncBackingParams, + CollatorPair, CoreIndex, GroupRotationInfo, HeadData, NodeFeatures, PersistedValidationData, + ValidatorId, ValidatorIndex, }; +use polkadot_primitives_test_helpers::{dummy_candidate_receipt_bad_sig, dummy_hash}; mod prospective_parachains; @@ -57,9 +53,6 @@ const ACTIVITY_TIMEOUT: Duration = Duration::from_millis(500); const DECLARE_TIMEOUT: Duration = Duration::from_millis(25); const REPUTATION_CHANGE_TEST_INTERVAL: Duration = Duration::from_millis(10); -const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError = - RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" }; - fn dummy_pvd() -> PersistedValidationData { PersistedValidationData { parent_head: HeadData(vec![7, 8, 9]), @@ -77,19 +70,17 @@ struct TestState { validator_public: Vec, validator_groups: Vec>, group_rotation_info: GroupRotationInfo, - cores: Vec, claim_queue: BTreeMap>, + async_backing_params: AsyncBackingParams, node_features: NodeFeatures, session_index: SessionIndex, + // Used by `update_view` to keep track of latest requested ancestor + last_known_block: Option, } impl Default for TestState { fn default() -> Self { - let chain_a = ParaId::from(1); - let chain_b = ParaId::from(2); - - let chain_ids = vec![chain_a, chain_b]; - let relay_parent = Hash::repeat_byte(0x05); + let relay_parent = Hash::from_low_u64_be(0x05); let collators = iter::repeat(()).map(|_| CollatorPair::generate().0).take(5).collect(); let validators = vec![ @@ -110,50 +101,103 @@ impl Default for TestState { let group_rotation_info = GroupRotationInfo { session_start_block: 0, group_rotation_frequency: 1, now: 0 }; - let cores = vec![ - CoreState::Scheduled(ScheduledCore { para_id: chain_ids[0], collator: None }), - CoreState::Free, - CoreState::Occupied(OccupiedCore { - next_up_on_available: Some(ScheduledCore { para_id: chain_ids[1], collator: None }), - occupied_since: 0, - time_out_at: 1, - next_up_on_time_out: None, - availability: Default::default(), - group_responsible: GroupIndex(0), - candidate_hash: Default::default(), - candidate_descriptor: { - let mut d = dummy_candidate_descriptor(dummy_hash()); - d.para_id = chain_ids[1]; - - d.into() - }, - }), - ]; - let mut claim_queue = BTreeMap::new(); - claim_queue.insert(CoreIndex(0), [chain_ids[0]].into_iter().collect()); + claim_queue.insert( + CoreIndex(0), + iter::repeat(ParaId::from(Self::CHAIN_IDS[0])) + .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize) + .collect(), + ); claim_queue.insert(CoreIndex(1), VecDeque::new()); - claim_queue.insert(CoreIndex(2), [chain_ids[1]].into_iter().collect()); + claim_queue.insert( + CoreIndex(2), + iter::repeat(ParaId::from(Self::CHAIN_IDS[1])) + .take(Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize) + .collect(), + ); let mut node_features = NodeFeatures::EMPTY; node_features.resize(node_features::FeatureIndex::CandidateReceiptV2 as usize + 1, false); node_features.set(node_features::FeatureIndex::CandidateReceiptV2 as u8 as usize, true); Self { - chain_ids, + chain_ids: Self::CHAIN_IDS.map(|id| ParaId::from(id)).to_vec(), relay_parent, collators, validator_public, validator_groups, group_rotation_info, - cores, claim_queue, + async_backing_params: Self::ASYNC_BACKING_PARAMS, node_features, session_index: 1, + last_known_block: None, } } } +impl TestState { + const CHAIN_IDS: [u32; 2] = [1, 2]; + const ASYNC_BACKING_PARAMS: AsyncBackingParams = + AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 }; + + fn with_shared_core() -> Self { + let mut state = Self::default(); + + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ + ParaId::from(Self::CHAIN_IDS[1]), + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[0]), + ] + .into_iter(), + ), + ); + state.validator_groups.truncate(1); + + assert!( + claim_queue.get(&CoreIndex(0)).unwrap().len() == + Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize + ); + + state.claim_queue = claim_queue; + + state + } + + fn with_one_scheduled_para() -> Self { + let mut state = Self::default(); + + let validator_groups = vec![vec![ValidatorIndex(0), ValidatorIndex(1)]]; + + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[0]), + ParaId::from(Self::CHAIN_IDS[0]), + ] + .into_iter(), + ), + ); + + assert!( + claim_queue.get(&CoreIndex(0)).unwrap().len() == + Self::ASYNC_BACKING_PARAMS.allowed_ancestry_len as usize + ); + + state.validator_groups = validator_groups; + state.claim_queue = claim_queue; + + state + } +} + type VirtualOverseer = polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; @@ -246,91 +290,6 @@ async fn overseer_signal(overseer: &mut VirtualOverseer, signal: OverseerSignal) .expect(&format!("{:?} is more than enough for sending signals.", TIMEOUT)); } -async fn respond_to_runtime_api_queries( - virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, - hash: Hash, -) { - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::SessionIndexForChild(tx) - )) => { - assert_eq!(rp, hash); - tx.send(Ok(test_state.session_index)).unwrap(); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::AsyncBackingParams(tx) - )) => { - assert_eq!(rp, hash); - tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::NodeFeatures(_, tx) - )) => { - assert_eq!(rp, hash); - tx.send(Ok(test_state.node_features.clone())).unwrap(); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::Validators(tx), - )) => { - let _ = tx.send(Ok(test_state.validator_public.clone())); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::ValidatorGroups(tx), - )) => { - assert_eq!(rp, hash); - let _ = tx.send(Ok(( - test_state.validator_groups.clone(), - test_state.group_rotation_info.clone(), - ))); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::AvailabilityCores(tx), - )) => { - assert_eq!(rp, hash); - let _ = tx.send(Ok(test_state.cores.clone())); - } - ); - - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - rp, - RuntimeApiRequest::ClaimQueue(tx), - )) => { - assert_eq!(rp, hash); - let _ = tx.send(Ok(test_state.claim_queue.clone())); - } - ); -} - /// Assert that the next message is a `CandidateBacking(Second())`. async fn assert_candidate_backing_second( virtual_overseer: &mut VirtualOverseer, @@ -506,138 +465,6 @@ async fn advertise_collation( .await; } -// As we receive a relevant advertisement act on it and issue a collation request. -#[test] -fn act_on_advertisement() { - let test_state = TestState::default(); - - test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { - let TestHarness { mut virtual_overseer, .. } = test_harness; - - let pair = CollatorPair::generate().0; - gum::trace!("activating"); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - - let peer_b = PeerId::random(); - - connect_and_declare_collator( - &mut virtual_overseer, - peer_b, - pair.clone(), - test_state.chain_ids[0], - CollationVersion::V1, - ) - .await; - - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; - - assert_fetch_collation_request( - &mut virtual_overseer, - test_state.relay_parent, - test_state.chain_ids[0], - None, - ) - .await; - - virtual_overseer - }); -} - -/// Tests that validator side works with v2 network protocol -/// before async backing is enabled. -#[test] -fn act_on_advertisement_v2() { - let test_state = TestState::default(); - - test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { - let TestHarness { mut virtual_overseer, .. } = test_harness; - - let pair = CollatorPair::generate().0; - gum::trace!("activating"); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - - let peer_b = PeerId::random(); - - connect_and_declare_collator( - &mut virtual_overseer, - peer_b, - pair.clone(), - test_state.chain_ids[0], - CollationVersion::V2, - ) - .await; - - let pov = PoV { block_data: BlockData(vec![]) }; - let mut candidate_a = - dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default())); - candidate_a.descriptor.para_id = test_state.chain_ids[0]; - candidate_a.descriptor.relay_parent = test_state.relay_parent; - candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); - - let candidate_hash = candidate_a.hash(); - let parent_head_data_hash = Hash::zero(); - // v2 advertisement. - advertise_collation( - &mut virtual_overseer, - peer_b, - test_state.relay_parent, - Some((candidate_hash, parent_head_data_hash)), - ) - .await; - - let response_channel = assert_fetch_collation_request( - &mut virtual_overseer, - test_state.relay_parent, - test_state.chain_ids[0], - Some(candidate_hash), - ) - .await; - - response_channel - .send(Ok(( - request_v1::CollationFetchingResponse::Collation( - candidate_a.clone().into(), - pov.clone(), - ) - .encode(), - ProtocolName::from(""), - ))) - .expect("Sending response should succeed"); - - assert_candidate_backing_second( - &mut virtual_overseer, - test_state.relay_parent, - test_state.chain_ids[0], - &pov, - // Async backing isn't enabled and thus it should do it the old way. - CollationVersion::V1, - ) - .await; - - virtual_overseer - }); -} - // Test that we verify the signatures on `Declare` and `AdvertiseCollation` messages. #[test] fn collator_authentication_verification_works() { @@ -687,31 +514,18 @@ fn collator_authentication_verification_works() { }); } -/// Tests that a validator fetches only one collation at any moment of time -/// per relay parent and ignores other advertisements once a candidate gets -/// seconded. +/// Tests that on a V1 Advertisement a validator fetches only one collation at any moment of time +/// per relay parent and ignores other V1 advertisements once a candidate gets seconded. #[test] -fn fetch_one_collation_at_a_time() { - let test_state = TestState::default(); +fn fetch_one_collation_at_a_time_for_v1_advertisement() { + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let second = Hash::random(); - - let our_view = our_view![test_state.relay_parent, second]; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), - ) - .await; - - // Iter over view since the order may change due to sorted invariant. - for hash in our_view.iter() { - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, *hash).await; - } + let second = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0), (second, 1)]) + .await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -734,8 +548,8 @@ fn fetch_one_collation_at_a_time() { ) .await; - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; - advertise_collation(&mut virtual_overseer, peer_c, test_state.relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_b, relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_c, relay_parent, None).await; let response_channel = assert_fetch_collation_request( &mut virtual_overseer, @@ -790,26 +604,14 @@ fn fetch_one_collation_at_a_time() { /// timeout and in case of an error. #[test] fn fetches_next_collation() { - let test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; + let first = test_state.relay_parent; let second = Hash::random(); - - let our_view = our_view![test_state.relay_parent, second]; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), - ) - .await; - - for hash in our_view.iter() { - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, *hash).await; - } + update_view(&mut virtual_overseer, &mut test_state, vec![(first, 0), (second, 1)]).await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -919,21 +721,13 @@ fn fetches_next_collation() { #[test] fn reject_connection_to_next_group() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -966,26 +760,13 @@ fn reject_connection_to_next_group() { // invalid. #[test] fn fetch_next_collation_on_invalid_collation() { - let test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let second = Hash::random(); - - let our_view = our_view![test_state.relay_parent, second]; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), - ) - .await; - - for hash in our_view.iter() { - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, *hash).await; - } + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); let peer_c = PeerId::random(); @@ -1008,12 +789,12 @@ fn fetch_next_collation_on_invalid_collation() { ) .await; - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; - advertise_collation(&mut virtual_overseer, peer_c, test_state.relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_b, relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_c, relay_parent, None).await; let response_channel = assert_fetch_collation_request( &mut virtual_overseer, - test_state.relay_parent, + relay_parent, test_state.chain_ids[0], None, ) @@ -1023,7 +804,7 @@ fn fetch_next_collation_on_invalid_collation() { let mut candidate_a = dummy_candidate_receipt_bad_sig(dummy_hash(), Some(Default::default())); candidate_a.descriptor.para_id = test_state.chain_ids[0]; - candidate_a.descriptor.relay_parent = test_state.relay_parent; + candidate_a.descriptor.relay_parent = relay_parent; candidate_a.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); response_channel .send(Ok(( @@ -1038,7 +819,7 @@ fn fetch_next_collation_on_invalid_collation() { let receipt = assert_candidate_backing_second( &mut virtual_overseer, - test_state.relay_parent, + relay_parent, test_state.chain_ids[0], &pov, CollationVersion::V1, @@ -1048,7 +829,7 @@ fn fetch_next_collation_on_invalid_collation() { // Inform that the candidate was invalid. overseer_send( &mut virtual_overseer, - CollatorProtocolMessage::Invalid(test_state.relay_parent, receipt), + CollatorProtocolMessage::Invalid(relay_parent, receipt), ) .await; @@ -1065,7 +846,7 @@ fn fetch_next_collation_on_invalid_collation() { // We should see a request for another collation. assert_fetch_collation_request( &mut virtual_overseer, - test_state.relay_parent, + relay_parent, test_state.chain_ids[0], None, ) @@ -1077,25 +858,15 @@ fn fetch_next_collation_on_invalid_collation() { #[test] fn inactive_disconnected() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; let pair = CollatorPair::generate().0; - let hash_a = test_state.relay_parent; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![hash_a], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -1107,11 +878,11 @@ fn inactive_disconnected() { CollationVersion::V1, ) .await; - advertise_collation(&mut virtual_overseer, peer_b, test_state.relay_parent, None).await; + advertise_collation(&mut virtual_overseer, peer_b, relay_parent, None).await; assert_fetch_collation_request( &mut virtual_overseer, - test_state.relay_parent, + relay_parent, test_state.chain_ids[0], None, ) @@ -1126,31 +897,24 @@ fn inactive_disconnected() { #[test] fn activity_extends_life() { - let test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; let pair = CollatorPair::generate().0; - let hash_a = test_state.relay_parent; - let hash_b = Hash::repeat_byte(1); - let hash_c = Hash::repeat_byte(2); + let hash_a = Hash::from_low_u64_be(12); + let hash_b = Hash::from_low_u64_be(11); + let hash_c = Hash::from_low_u64_be(10); - let our_view = our_view![hash_a, hash_b, hash_c]; - - overseer_send( + update_view( &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view.clone(), - )), + &mut test_state, + vec![(hash_a, 0), (hash_b, 1), (hash_c, 2)], ) .await; - for hash in our_view.iter() { - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, *hash).await; - } - let peer_b = PeerId::random(); connect_and_declare_collator( @@ -1208,21 +972,13 @@ fn activity_extends_life() { #[test] fn disconnect_if_no_declare() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; let peer_b = PeerId::random(); @@ -1245,26 +1001,16 @@ fn disconnect_if_no_declare() { #[test] fn disconnect_if_wrong_declare() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let pair = CollatorPair::generate().0; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - let peer_b = PeerId::random(); + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; + overseer_send( &mut virtual_overseer, CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( @@ -1307,26 +1053,16 @@ fn disconnect_if_wrong_declare() { #[test] fn delay_reputation_change() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| false), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; - let pair = CollatorPair::generate().0; - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - let peer_b = PeerId::random(); + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; + overseer_send( &mut virtual_overseer, CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::PeerConnected( @@ -1400,42 +1136,24 @@ fn view_change_clears_old_collators() { let pair = CollatorPair::generate().0; - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![test_state.relay_parent], - )), - ) - .await; - - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, test_state.relay_parent) - .await; - - let peer_b = PeerId::random(); + let peer = PeerId::random(); + let relay_parent = test_state.relay_parent; + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent, 0)]).await; connect_and_declare_collator( &mut virtual_overseer, - peer_b, + peer, pair.clone(), test_state.chain_ids[0], CollationVersion::V1, ) .await; - let hash_b = Hash::repeat_byte(69); - - overseer_send( - &mut virtual_overseer, - CollatorProtocolMessage::NetworkBridgeUpdate(NetworkBridgeEvent::OurViewChange( - our_view![hash_b], - )), - ) - .await; - test_state.group_rotation_info = test_state.group_rotation_info.bump_rotation(); - respond_to_runtime_api_queries(&mut virtual_overseer, &test_state, hash_b).await; - assert_collator_disconnect(&mut virtual_overseer, peer_b).await; + update_view(&mut virtual_overseer, &mut test_state, vec![]).await; + + assert_collator_disconnect(&mut virtual_overseer, peer).await; virtual_overseer }) diff --git a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs index eda26e8539a1..fac63aeb2097 100644 --- a/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/validator_side/tests/prospective_parachains.rs @@ -21,14 +21,11 @@ use super::*; use polkadot_node_subsystem::messages::ChainApiMessage; use polkadot_primitives::{ vstaging::{CommittedCandidateReceiptV2 as CommittedCandidateReceipt, MutateDescriptorV2}, - AsyncBackingParams, BlockNumber, CandidateCommitments, Header, SigningContext, ValidatorId, + BlockNumber, CandidateCommitments, Header, SigningContext, ValidatorId, }; use polkadot_primitives_test_helpers::dummy_committed_candidate_receipt_v2; use rstest::rstest; -const ASYNC_BACKING_PARAMETERS: AsyncBackingParams = - AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: 3 }; - fn get_parent_hash(hash: Hash) -> Hash { Hash::from_low_u64_be(hash.to_low_u64_be() + 1) } @@ -48,7 +45,8 @@ async fn assert_construct_per_relay_parent( msg, AllMessages::RuntimeApi( RuntimeApiMessage::Request(parent, RuntimeApiRequest::Validators(tx)) - ) if parent == hash => { + ) => { + assert_eq!(parent, hash); tx.send(Ok(test_state.validator_public.clone())).unwrap(); } ); @@ -65,15 +63,6 @@ async fn assert_construct_per_relay_parent( } ); - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx)) - ) if parent == hash => { - tx.send(Ok(test_state.cores.clone())).unwrap(); - } - ); - assert_matches!( overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( @@ -88,12 +77,11 @@ async fn assert_construct_per_relay_parent( /// Handle a view update. pub(super) async fn update_view( virtual_overseer: &mut VirtualOverseer, - test_state: &TestState, + test_state: &mut TestState, new_view: Vec<(Hash, u32)>, // Hash and block number. - activated: u8, // How many new heads does this update contain? ) -> Option { + let last_block_from_view = new_view.last().map(|t| t.1); let new_view: HashMap = HashMap::from_iter(new_view); - let our_view = OurView::new(new_view.keys().map(|hash| *hash), 0); overseer_send( @@ -103,9 +91,14 @@ pub(super) async fn update_view( .await; let mut next_overseer_message = None; - for _ in 0..activated { + for _ in 0..new_view.len() { + let msg = match next_overseer_message.take() { + Some(msg) => msg, + None => overseer_recv(virtual_overseer).await, + }; + let (leaf_hash, leaf_number) = assert_matches!( - overseer_recv(virtual_overseer).await, + msg, AllMessages::RuntimeApi(RuntimeApiMessage::Request( parent, RuntimeApiRequest::SessionIndexForChild(tx) @@ -121,7 +114,7 @@ pub(super) async fn update_view( _, RuntimeApiRequest::AsyncBackingParams(tx), )) => { - tx.send(Ok(ASYNC_BACKING_PARAMETERS)).unwrap(); + tx.send(Ok(test_state.async_backing_params)).unwrap(); } ); @@ -144,7 +137,8 @@ pub(super) async fn update_view( ) .await; - let min_number = leaf_number.saturating_sub(ASYNC_BACKING_PARAMETERS.allowed_ancestry_len); + let min_number = + leaf_number.saturating_sub(test_state.async_backing_params.allowed_ancestry_len); let ancestry_len = leaf_number + 1 - min_number; let ancestry_hashes = std::iter::successors(Some(leaf_hash), |h| Some(get_parent_hash(*h))) @@ -157,6 +151,10 @@ pub(super) async fn update_view( { let mut ancestry_iter = ancestry_iter.clone(); while let Some((hash, number)) = ancestry_iter.next() { + if Some(number) == test_state.last_known_block { + break; + } + // May be `None` for the last element. let parent_hash = ancestry_iter.peek().map(|(h, _)| *h).unwrap_or_else(|| get_parent_hash(hash)); @@ -204,6 +202,9 @@ pub(super) async fn update_view( // Skip the leaf. for (hash, number) in ancestry_iter.skip(1).take(requested_len.saturating_sub(1)) { + if Some(number) == test_state.last_known_block { + break; + } assert_construct_per_relay_parent( virtual_overseer, test_state, @@ -214,6 +215,9 @@ pub(super) async fn update_view( .await; } } + + test_state.last_known_block = last_block_from_view; + next_overseer_message } @@ -337,9 +341,140 @@ async fn assert_persisted_validation_data( } } +// Combines dummy candidate creation, advertisement and fetching in a single call +async fn submit_second_and_assert( + virtual_overseer: &mut VirtualOverseer, + keystore: KeystorePtr, + para_id: ParaId, + relay_parent: Hash, + collator: PeerId, + candidate_head_data: HeadData, +) { + let (candidate, commitments) = + create_dummy_candidate_and_commitments(para_id, candidate_head_data, relay_parent); + + let candidate_hash = candidate.hash(); + let parent_head_data_hash = Hash::zero(); + + assert_advertise_collation( + virtual_overseer, + collator, + relay_parent, + para_id, + (candidate_hash, parent_head_data_hash), + ) + .await; + + let response_channel = assert_fetch_collation_request( + virtual_overseer, + relay_parent, + para_id, + Some(candidate_hash), + ) + .await; + + let pov = PoV { block_data: BlockData(vec![1]) }; + + send_collation_and_assert_processing( + virtual_overseer, + keystore, + relay_parent, + para_id, + collator, + response_channel, + candidate, + commitments, + pov, + ) + .await; +} + +fn create_dummy_candidate_and_commitments( + para_id: ParaId, + candidate_head_data: HeadData, + relay_parent: Hash, +) -> (CandidateReceipt, CandidateCommitments) { + let mut candidate = dummy_candidate_receipt_bad_sig(relay_parent, Some(Default::default())); + candidate.descriptor.para_id = para_id; + candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); + let commitments = CandidateCommitments { + head_data: candidate_head_data, + horizontal_messages: Default::default(), + upward_messages: Default::default(), + new_validation_code: None, + processed_downward_messages: 0, + hrmp_watermark: 0, + }; + candidate.commitments_hash = commitments.hash(); + + (candidate.into(), commitments) +} + +async fn assert_advertise_collation( + virtual_overseer: &mut VirtualOverseer, + peer: PeerId, + relay_parent: Hash, + expected_para_id: ParaId, + candidate: (CandidateHash, Hash), +) { + advertise_collation(virtual_overseer, peer, relay_parent, Some(candidate)).await; + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::CandidateBacking( + CandidateBackingMessage::CanSecond(request, tx), + ) => { + assert_eq!(request.candidate_hash, candidate.0); + assert_eq!(request.candidate_para_id, expected_para_id); + assert_eq!(request.parent_head_data_hash, candidate.1); + tx.send(true).expect("receiving side should be alive"); + } + ); +} + +async fn send_collation_and_assert_processing( + virtual_overseer: &mut VirtualOverseer, + keystore: KeystorePtr, + relay_parent: Hash, + expected_para_id: ParaId, + expected_peer_id: PeerId, + response_channel: ResponseSender, + candidate: CandidateReceipt, + commitments: CandidateCommitments, + pov: PoV, +) { + response_channel + .send(Ok(( + request_v2::CollationFetchingResponse::Collation(candidate.clone(), pov.clone()) + .encode(), + ProtocolName::from(""), + ))) + .expect("Sending response should succeed"); + + assert_candidate_backing_second( + virtual_overseer, + relay_parent, + expected_para_id, + &pov, + CollationVersion::V2, + ) + .await; + + let candidate = CommittedCandidateReceipt { descriptor: candidate.descriptor, commitments }; + + send_seconded_statement(virtual_overseer, keystore.clone(), &candidate).await; + + assert_collation_seconded( + virtual_overseer, + relay_parent, + expected_peer_id, + CollationVersion::V2, + ) + .await; +} + #[test] fn v1_advertisement_accepted_and_seconded() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -349,7 +484,7 @@ fn v1_advertisement_accepted_and_seconded() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -377,7 +512,7 @@ fn v1_advertisement_accepted_and_seconded() { candidate.descriptor.para_id = test_state.chain_ids[0]; candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); let commitments = CandidateCommitments { - head_data: HeadData(vec![1 as u8]), + head_data: HeadData(vec![1u8]), horizontal_messages: Default::default(), upward_messages: Default::default(), new_validation_code: None, @@ -418,7 +553,7 @@ fn v1_advertisement_accepted_and_seconded() { #[test] fn v1_advertisement_rejected_on_non_active_leaf() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -428,7 +563,7 @@ fn v1_advertisement_rejected_on_non_active_leaf() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 5; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -460,7 +595,7 @@ fn v1_advertisement_rejected_on_non_active_leaf() { #[test] fn accept_advertisements_from_implicit_view() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -478,7 +613,7 @@ fn accept_advertisements_from_implicit_view() { let head_d = get_parent_hash(head_c); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); let peer_b = PeerId::random(); @@ -563,24 +698,26 @@ fn accept_advertisements_from_implicit_view() { #[test] fn second_multiple_candidates_per_relay_parent() { - let test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; let pair = CollatorPair::generate().0; - // Grandparent of head `a`. + let head_a = Hash::from_low_u64_be(130); + let head_a_num: u32 = 0; + let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 2; - // Grandparent of head `b`. - // Group rotation frequency is 1 by default, at `c` we're assigned - // to the first para. - let head_c = Hash::from_low_u64_be(130); - - // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + // Activated leaf is `a` and `b`.The collation will be based on `b`. + update_view( + &mut virtual_overseer, + &mut test_state, + vec![(head_a, head_a_num), (head_b, head_b_num)], + ) + .await; let peer_a = PeerId::random(); @@ -593,80 +730,17 @@ fn second_multiple_candidates_per_relay_parent() { ) .await; - for i in 0..(ASYNC_BACKING_PARAMETERS.max_candidate_depth + 1) { - let mut candidate = dummy_candidate_receipt_bad_sig(head_c, Some(Default::default())); - candidate.descriptor.para_id = test_state.chain_ids[0]; - candidate.descriptor.persisted_validation_data_hash = dummy_pvd().hash(); - let commitments = CandidateCommitments { - head_data: HeadData(vec![i as u8]), - horizontal_messages: Default::default(), - upward_messages: Default::default(), - new_validation_code: None, - processed_downward_messages: 0, - hrmp_watermark: 0, - }; - candidate.commitments_hash = commitments.hash(); - let candidate: CandidateReceipt = candidate.into(); - - let candidate_hash = candidate.hash(); - let parent_head_data_hash = Hash::zero(); - - advertise_collation( - &mut virtual_overseer, - peer_a, - head_c, - Some((candidate_hash, parent_head_data_hash)), - ) - .await; - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::CandidateBacking( - CandidateBackingMessage::CanSecond(request, tx), - ) => { - assert_eq!(request.candidate_hash, candidate_hash); - assert_eq!(request.candidate_para_id, test_state.chain_ids[0]); - assert_eq!(request.parent_head_data_hash, parent_head_data_hash); - tx.send(true).expect("receiving side should be alive"); - } - ); - - let response_channel = assert_fetch_collation_request( - &mut virtual_overseer, - head_c, - test_state.chain_ids[0], - Some(candidate_hash), - ) - .await; - - let pov = PoV { block_data: BlockData(vec![1]) }; - - response_channel - .send(Ok(( - request_v2::CollationFetchingResponse::Collation( - candidate.clone(), - pov.clone(), - ) - .encode(), - ProtocolName::from(""), - ))) - .expect("Sending response should succeed"); - - assert_candidate_backing_second( + // `allowed_ancestry_len` equals the size of the claim queue + for i in 0..test_state.async_backing_params.allowed_ancestry_len { + submit_second_and_assert( &mut virtual_overseer, - head_c, + keystore.clone(), test_state.chain_ids[0], - &pov, - CollationVersion::V2, + head_a, + peer_a, + HeadData(vec![i as u8]), ) .await; - - let candidate = - CommittedCandidateReceipt { descriptor: candidate.descriptor, commitments }; - - send_seconded_statement(&mut virtual_overseer, keystore.clone(), &candidate).await; - - assert_collation_seconded(&mut virtual_overseer, head_c, peer_a, CollationVersion::V2) - .await; } // No more advertisements can be made for this relay parent. @@ -674,21 +748,14 @@ fn second_multiple_candidates_per_relay_parent() { advertise_collation( &mut virtual_overseer, peer_a, - head_c, + head_a, Some((candidate_hash, Hash::zero())), ) .await; - // Reported because reached the limit of advertisements per relay parent. - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::NetworkBridgeTx( - NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(peer_id, rep)), - ) => { - assert_eq!(peer_a, peer_id); - assert_eq!(rep.value, COST_UNEXPECTED_MESSAGE.cost_or_benefit()); - } - ); + // Rejected but not reported because reached the limit of advertisements for the para_id + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); // By different peer too (not reported). let pair_b = CollatorPair::generate().0; @@ -707,7 +774,7 @@ fn second_multiple_candidates_per_relay_parent() { advertise_collation( &mut virtual_overseer, peer_b, - head_c, + head_a, Some((candidate_hash, Hash::zero())), ) .await; @@ -721,7 +788,7 @@ fn second_multiple_candidates_per_relay_parent() { #[test] fn fetched_collation_sanity_check() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -738,7 +805,7 @@ fn fetched_collation_sanity_check() { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -832,7 +899,7 @@ fn fetched_collation_sanity_check() { #[test] fn sanity_check_invalid_parent_head_data() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -842,7 +909,7 @@ fn sanity_check_invalid_parent_head_data() { let head_c = Hash::from_low_u64_be(130); let head_c_num = 3; - update_view(&mut virtual_overseer, &test_state, vec![(head_c, head_c_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_c, head_c_num)]).await; let peer_a = PeerId::random(); @@ -952,7 +1019,7 @@ fn sanity_check_invalid_parent_head_data() { #[test] fn advertisement_spam_protection() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -965,7 +1032,7 @@ fn advertisement_spam_protection() { let head_c = get_parent_hash(head_b); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); connect_and_declare_collator( @@ -1026,7 +1093,7 @@ fn advertisement_spam_protection() { #[case(true)] #[case(false)] fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) { - let test_state = TestState::default(); + let mut test_state = TestState::with_one_scheduled_para(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, keystore } = test_harness; @@ -1043,7 +1110,7 @@ fn child_blocked_from_seconding_by_parent(#[case] valid_parent: bool) { let head_c = Hash::from_low_u64_be(130); // Activated leaf is `b`, but the collation will be based on `c`. - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1344,7 +1411,7 @@ fn v2_descriptor(#[case] v2_feature_enabled: bool) { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1442,7 +1509,7 @@ fn v2_descriptor(#[case] v2_feature_enabled: bool) { #[test] fn invalid_v2_descriptor() { - let test_state = TestState::default(); + let mut test_state = TestState::default(); test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { let TestHarness { mut virtual_overseer, .. } = test_harness; @@ -1452,7 +1519,7 @@ fn invalid_v2_descriptor() { let head_b = Hash::from_low_u64_be(128); let head_b_num: u32 = 0; - update_view(&mut virtual_overseer, &test_state, vec![(head_b, head_b_num)], 1).await; + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; let peer_a = PeerId::random(); @@ -1545,3 +1612,868 @@ fn invalid_v2_descriptor() { virtual_overseer }); } + +#[test] +fn fair_collation_fetches() { + let mut test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let head_b = Hash::from_low_u64_be(128); + let head_b_num: u32 = 2; + + update_view(&mut virtual_overseer, &mut test_state, vec![(head_b, head_b_num)]).await; + + let peer_a = PeerId::random(); + let pair_a = CollatorPair::generate().0; + + connect_and_declare_collator( + &mut virtual_overseer, + peer_a, + pair_a.clone(), + test_state.chain_ids[0], + CollationVersion::V2, + ) + .await; + + let peer_b = PeerId::random(); + let pair_b = CollatorPair::generate().0; + + connect_and_declare_collator( + &mut virtual_overseer, + peer_b, + pair_b.clone(), + test_state.chain_ids[1], + CollationVersion::V2, + ) + .await; + + // `peer_a` sends two advertisements (its claim queue limit) + for i in 0..2u8 { + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + head_b, + peer_a, + HeadData(vec![i]), + ) + .await; + } + + // `peer_a` sends another advertisement and it is ignored + let candidate_hash = CandidateHash(Hash::repeat_byte(0xAA)); + advertise_collation( + &mut virtual_overseer, + peer_a, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // `peer_b` should still be able to advertise its collation + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[1]), + head_b, + peer_b, + HeadData(vec![0u8]), + ) + .await; + + // And no more advertisements can be made for this relay parent. + + // verify for peer_a + let candidate_hash = CandidateHash(Hash::repeat_byte(0xBB)); + advertise_collation( + &mut virtual_overseer, + peer_a, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // verify for peer_b + let candidate_hash = CandidateHash(Hash::repeat_byte(0xCC)); + advertise_collation( + &mut virtual_overseer, + peer_b, + head_b, + Some((candidate_hash, Hash::zero())), + ) + .await; + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + +#[test] +fn collation_fetching_prefer_entries_earlier_in_claim_queue() { + let mut test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + let pair_b = CollatorPair::generate().0; + let collator_b = PeerId::random(); + let para_id_b = test_state.chain_ids[1]; + + let head = Hash::from_low_u64_be(128); + let head_num: u32 = 2; + + update_view(&mut virtual_overseer, &mut test_state, vec![(head, head_num)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_b, + pair_b.clone(), + para_id_b, + CollationVersion::V2, + ) + .await; + + let (candidate_a1, commitments_a1) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![0u8]), head); + let (candidate_b1, commitments_b1) = + create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![1u8]), head); + let (candidate_a2, commitments_a2) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![2u8]), head); + let (candidate_a3, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), head); + let parent_head_data_a1 = HeadData(vec![0u8]); + let parent_head_data_b1 = HeadData(vec![1u8]); + let parent_head_data_a2 = HeadData(vec![2u8]); + let parent_head_data_a3 = HeadData(vec![3u8]); + + // advertise a collation for `para_id_a` but don't send the collation. This will be a + // pending fetch. + assert_advertise_collation( + &mut virtual_overseer, + collator_a, + head, + para_id_a, + (candidate_a1.hash(), parent_head_data_a1.hash()), + ) + .await; + + let response_channel_a1 = assert_fetch_collation_request( + &mut virtual_overseer, + head, + para_id_a, + Some(candidate_a1.hash()), + ) + .await; + + // advertise another collation for `para_id_a`. This one should be fetched last. + assert_advertise_collation( + &mut virtual_overseer, + collator_a, + head, + para_id_a, + (candidate_a2.hash(), parent_head_data_a2.hash()), + ) + .await; + + // There is a pending collation so nothing should be fetched + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Advertise a collation for `para_id_b`. This should be fetched second + assert_advertise_collation( + &mut virtual_overseer, + collator_b, + head, + para_id_b, + (candidate_b1.hash(), parent_head_data_b1.hash()), + ) + .await; + + // Again - no fetch because of the pending collation + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + //Now send a response for the first fetch and examine the second fetch + send_collation_and_assert_processing( + &mut virtual_overseer, + keystore.clone(), + head, + para_id_a, + collator_a, + response_channel_a1, + candidate_a1, + commitments_a1, + PoV { block_data: BlockData(vec![1]) }, + ) + .await; + + // The next fetch should be for `para_id_b` + let response_channel_b = assert_fetch_collation_request( + &mut virtual_overseer, + head, + para_id_b, + Some(candidate_b1.hash()), + ) + .await; + + send_collation_and_assert_processing( + &mut virtual_overseer, + keystore.clone(), + head, + para_id_b, + collator_b, + response_channel_b, + candidate_b1, + commitments_b1, + PoV { block_data: BlockData(vec![2]) }, + ) + .await; + + // and the final one for `para_id_a` + let response_channel_a2 = assert_fetch_collation_request( + &mut virtual_overseer, + head, + para_id_a, + Some(candidate_a2.hash()), + ) + .await; + + // Advertise another collation for `para_id_a`. This should be rejected as there is no slot + // in the claim queue for it. One is fetched and one is pending. + advertise_collation( + &mut virtual_overseer, + collator_a, + head, + Some((candidate_a3.hash(), parent_head_data_a3.hash())), + ) + .await; + + // `CanSecond` shouldn't be sent as the advertisement should be ignored + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Fetch the pending collation + send_collation_and_assert_processing( + &mut virtual_overseer, + keystore.clone(), + head, + para_id_a, + collator_a, + response_channel_a2, + candidate_a2, + commitments_a2, + PoV { block_data: BlockData(vec![3]) }, + ) + .await; + + virtual_overseer + }); +} + +#[test] +fn collation_fetching_considers_advertisements_from_the_whole_view() { + let mut test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + let pair_b = CollatorPair::generate().0; + let collator_b = PeerId::random(); + let para_id_b = test_state.chain_ids[1]; + + let relay_parent_2 = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + + assert_eq!( + *test_state.claim_queue.get(&CoreIndex(0)).unwrap(), + VecDeque::from([para_id_b, para_id_a, para_id_a]) + ); + + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_2, 2)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_b, + pair_b.clone(), + para_id_b, + CollationVersion::V2, + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_2, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_b, + relay_parent_2, + collator_b, + HeadData(vec![1u8]), + ) + .await; + + let relay_parent_3 = Hash::from_low_u64_be(relay_parent_2.to_low_u64_be() - 1); + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_a, para_id_a, para_id_b]); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_3, 3)]).await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_b, + relay_parent_3, + collator_b, + HeadData(vec![3u8]), + ) + .await; + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_3, + collator_a, + HeadData(vec![3u8]), + ) + .await; + + // At this point the claim queue is satisfied and any advertisement at `relay_parent_4` + // must be ignored + + let (candidate_a, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![5u8]), relay_parent_3); + let parent_head_data_a = HeadData(vec![5u8]); + + advertise_collation( + &mut virtual_overseer, + collator_a, + relay_parent_3, + Some((candidate_a.hash(), parent_head_data_a.hash())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + let (candidate_b, _) = + create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![6u8]), relay_parent_3); + let parent_head_data_b = HeadData(vec![6u8]); + + advertise_collation( + &mut virtual_overseer, + collator_b, + relay_parent_3, + Some((candidate_b.hash(), parent_head_data_b.hash())), + ) + .await; + + // `CanSecond` shouldn't be sent as the advertisement should be ignored + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // At `relay_parent_6` the advertisement for `para_id_b` falls out of the view so a new one + // can be accepted + let relay_parent_6 = Hash::from_low_u64_be(relay_parent_3.to_low_u64_be() - 2); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_6, 6)]).await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_6, + collator_a, + HeadData(vec![3u8]), + ) + .await; + + virtual_overseer + }); +} + +#[test] +fn collation_fetching_fairness_handles_old_claims() { + let mut test_state = TestState::with_shared_core(); + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + let pair_b = CollatorPair::generate().0; + let collator_b = PeerId::random(); + let para_id_b = test_state.chain_ids[1]; + + let relay_parent_2 = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_a, para_id_b, para_id_a]); + + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_2, 2)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_b, + pair_b.clone(), + para_id_b, + CollationVersion::V2, + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_2, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_b, + relay_parent_2, + collator_b, + HeadData(vec![1u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_2, + collator_a, + HeadData(vec![2u8]), + ) + .await; + + let relay_parent_3 = Hash::from_low_u64_be(relay_parent_2.to_low_u64_be() - 1); + + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_b, para_id_a, para_id_b]); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_3, 3)]).await; + + // nothing is advertised here + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + let relay_parent_4 = Hash::from_low_u64_be(relay_parent_3.to_low_u64_be() - 1); + + *test_state.claim_queue.get_mut(&CoreIndex(0)).unwrap() = + VecDeque::from([para_id_a, para_id_b, para_id_a]); + update_view(&mut virtual_overseer, &mut test_state, vec![(relay_parent_4, 4)]).await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_b, + relay_parent_4, + collator_b, + HeadData(vec![3u8]), + ) + .await; + + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + para_id_a, + relay_parent_4, + collator_a, + HeadData(vec![4u8]), + ) + .await; + + // At this point the claim queue is satisfied and any advertisement at `relay_parent_4` + // must be ignored + + // Advertisement for `para_id_a` at `relay_parent_4` which must be ignored + let (candidate_a, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![5u8]), relay_parent_4); + let parent_head_data_a = HeadData(vec![5u8]); + + advertise_collation( + &mut virtual_overseer, + collator_a, + relay_parent_4, + Some((candidate_a.hash(), parent_head_data_a.hash())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Advertisement for `para_id_b` at `relay_parent_4` which must be ignored + let (candidate_b, _) = + create_dummy_candidate_and_commitments(para_id_b, HeadData(vec![6u8]), relay_parent_4); + let parent_head_data_b = HeadData(vec![6u8]); + + advertise_collation( + &mut virtual_overseer, + collator_b, + relay_parent_4, + Some((candidate_b.hash(), parent_head_data_b.hash())), + ) + .await; + + // `CanSecond` shouldn't be sent as the advertisement should be ignored + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + +#[test] +fn claims_below_are_counted_correctly() { + let mut test_state = TestState::with_one_scheduled_para(); + + // Shorten the claim queue to make the test smaller + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ParaId::from(test_state.chain_ids[0]), ParaId::from(test_state.chain_ids[0])] + .into_iter(), + ), + ); + test_state.claim_queue = claim_queue; + test_state.async_backing_params.max_candidate_depth = 3; + test_state.async_backing_params.allowed_ancestry_len = 2; + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let hash_a = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); + let hash_b = Hash::from_low_u64_be(hash_a.to_low_u64_be() - 1); + let hash_c = Hash::from_low_u64_be(hash_b.to_low_u64_be() - 1); + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + update_view(&mut virtual_overseer, &mut test_state, vec![(hash_c, 2)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + // A collation at hash_a claims the spot at hash_a + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + // Another collation at hash_a claims the spot at hash_b + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![1u8]), + ) + .await; + + // Collation at hash_c claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_c, + collator_a, + HeadData(vec![2u8]), + ) + .await; + + // Collation at hash_b should be ignored because the claim queue is satisfied + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), hash_b); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_b, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + +#[test] +fn claims_above_are_counted_correctly() { + let mut test_state = TestState::with_one_scheduled_para(); + + // Shorten the claim queue to make the test smaller + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ParaId::from(test_state.chain_ids[0]), ParaId::from(test_state.chain_ids[0])] + .into_iter(), + ), + ); + test_state.claim_queue = claim_queue; + test_state.async_backing_params.max_candidate_depth = 3; + test_state.async_backing_params.allowed_ancestry_len = 2; + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let hash_a = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); // block 0 + let hash_b = Hash::from_low_u64_be(hash_a.to_low_u64_be() - 1); // block 1 + let hash_c = Hash::from_low_u64_be(hash_b.to_low_u64_be() - 1); // block 2 + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + update_view(&mut virtual_overseer, &mut test_state, vec![(hash_c, 2)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + // A collation at hash_b claims the spot at hash_b + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_b, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + // Another collation at hash_b claims the spot at hash_c + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_b, + collator_a, + HeadData(vec![1u8]), + ) + .await; + + // Collation at hash_a claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + // Another Collation at hash_a should be ignored because the claim queue is satisfied + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![2u8]), hash_a); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_a, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Same for hash_b + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), hash_b); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_b, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} + +#[test] +fn claim_fills_last_free_slot() { + let mut test_state = TestState::with_one_scheduled_para(); + + // Shorten the claim queue to make the test smaller + let mut claim_queue = BTreeMap::new(); + claim_queue.insert( + CoreIndex(0), + VecDeque::from_iter( + [ParaId::from(test_state.chain_ids[0]), ParaId::from(test_state.chain_ids[0])] + .into_iter(), + ), + ); + test_state.claim_queue = claim_queue; + test_state.async_backing_params.max_candidate_depth = 3; + test_state.async_backing_params.allowed_ancestry_len = 2; + + test_harness(ReputationAggregator::new(|_| true), |test_harness| async move { + let TestHarness { mut virtual_overseer, keystore } = test_harness; + + let hash_a = Hash::from_low_u64_be(test_state.relay_parent.to_low_u64_be() - 1); // block 0 + let hash_b = Hash::from_low_u64_be(hash_a.to_low_u64_be() - 1); // block 1 + let hash_c = Hash::from_low_u64_be(hash_b.to_low_u64_be() - 1); // block 2 + + let pair_a = CollatorPair::generate().0; + let collator_a = PeerId::random(); + let para_id_a = test_state.chain_ids[0]; + + update_view(&mut virtual_overseer, &mut test_state, vec![(hash_c, 2)]).await; + + connect_and_declare_collator( + &mut virtual_overseer, + collator_a, + pair_a.clone(), + para_id_a, + CollationVersion::V2, + ) + .await; + + // A collation at hash_a claims its spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_a, + collator_a, + HeadData(vec![0u8]), + ) + .await; + + // Collation at hash_b claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_b, + collator_a, + HeadData(vec![3u8]), + ) + .await; + + // Collation at hash_c claims its own spot + submit_second_and_assert( + &mut virtual_overseer, + keystore.clone(), + ParaId::from(test_state.chain_ids[0]), + hash_c, + collator_a, + HeadData(vec![2u8]), + ) + .await; + + // Another Collation at hash_a should be ignored because the claim queue is satisfied + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![3u8]), hash_a); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_a, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + // Same for hash_b + let (ignored_candidate, _) = + create_dummy_candidate_and_commitments(para_id_a, HeadData(vec![4u8]), hash_b); + + advertise_collation( + &mut virtual_overseer, + collator_a, + hash_b, + Some((ignored_candidate.hash(), Hash::random())), + ) + .await; + + test_helpers::Yield::new().await; + assert_matches!(virtual_overseer.recv().now_or_never(), None); + + virtual_overseer + }); +} diff --git a/polkadot/node/subsystem-util/src/backing_implicit_view.rs b/polkadot/node/subsystem-util/src/backing_implicit_view.rs index 6f2191e7add2..67f5dad518e1 100644 --- a/polkadot/node/subsystem-util/src/backing_implicit_view.rs +++ b/polkadot/node/subsystem-util/src/backing_implicit_view.rs @@ -22,12 +22,13 @@ use polkadot_node_subsystem::{ }; use polkadot_primitives::{AsyncBackingParams, BlockNumber, Hash, Id as ParaId}; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::{ inclusion_emulator::RelayChainBlockInfo, request_async_backing_params, request_session_index_for_child, runtime::{self, recv_runtime}, + LOG_TARGET, }; // Always aim to retain 1 block before the active leaves. @@ -173,13 +174,7 @@ impl View { return Err(FetchError::AlreadyKnown) } - let res = fetch_fresh_leaf_and_insert_ancestry( - leaf_hash, - &mut self.block_info_storage, - &mut *sender, - self.collating_for, - ) - .await; + let res = self.fetch_fresh_leaf_and_insert_ancestry(leaf_hash, &mut *sender).await; match res { Ok(fetched) => { @@ -323,6 +318,205 @@ impl View { .as_ref() .map(|mins| mins.allowed_relay_parents_for(para_id, block_info.block_number)) } + + /// Returns all paths from each leaf to the last block in state containing `relay_parent`. If no + /// paths exist the function will return an empty `Vec`. + pub fn paths_via_relay_parent(&self, relay_parent: &Hash) -> Vec> { + gum::trace!( + target: LOG_TARGET, + ?relay_parent, + leaves=?self.leaves, + block_info_storage=?self.block_info_storage, + "Finding paths via relay parent" + ); + + if self.leaves.is_empty() { + // No leaves so the view should be empty. Don't return any paths. + return vec![] + }; + + if !self.block_info_storage.contains_key(relay_parent) { + // `relay_parent` is not in the view - don't return any paths + return vec![] + } + + // Find all paths from each leaf to `relay_parent`. + let mut paths = Vec::new(); + for (leaf, _) in &self.leaves { + let mut path = Vec::new(); + let mut current_leaf = *leaf; + let mut visited = HashSet::new(); + let mut path_contains_target = false; + + // Start from the leaf and traverse all known blocks + loop { + if visited.contains(¤t_leaf) { + // There is a cycle - abandon this path + break + } + + current_leaf = match self.block_info_storage.get(¤t_leaf) { + Some(info) => { + // `current_leaf` is a known block - add it to the path and mark it as + // visited + path.push(current_leaf); + visited.insert(current_leaf); + + // `current_leaf` is the target `relay_parent`. Mark the path so that it's + // included in the result + if current_leaf == *relay_parent { + path_contains_target = true; + } + + // update `current_leaf` with the parent + info.parent_hash + }, + None => { + // path is complete + if path_contains_target { + // we want the path ordered from oldest to newest so reverse it + paths.push(path.into_iter().rev().collect()); + } + break + }, + }; + } + } + + paths + } + + async fn fetch_fresh_leaf_and_insert_ancestry( + &mut self, + leaf_hash: Hash, + sender: &mut Sender, + ) -> Result + where + Sender: SubsystemSender + + SubsystemSender + + SubsystemSender, + { + let leaf_header = { + let (tx, rx) = oneshot::channel(); + sender.send_message(ChainApiMessage::BlockHeader(leaf_hash, tx)).await; + + match rx.await { + Ok(Ok(Some(header))) => header, + Ok(Ok(None)) => + return Err(FetchError::BlockHeaderUnavailable( + leaf_hash, + BlockHeaderUnavailableReason::Unknown, + )), + Ok(Err(e)) => + return Err(FetchError::BlockHeaderUnavailable( + leaf_hash, + BlockHeaderUnavailableReason::Internal(e), + )), + Err(_) => + return Err(FetchError::BlockHeaderUnavailable( + leaf_hash, + BlockHeaderUnavailableReason::SubsystemUnavailable, + )), + } + }; + + // If the node is a collator, bypass prospective-parachains. We're only interested in the + // one paraid and the subsystem is not present. + let min_relay_parents = if let Some(para_id) = self.collating_for { + fetch_min_relay_parents_for_collator(leaf_hash, leaf_header.number, sender) + .await? + .map(|x| vec![(para_id, x)]) + .unwrap_or_default() + } else { + fetch_min_relay_parents_from_prospective_parachains(leaf_hash, sender).await? + }; + + let min_min = min_relay_parents.iter().map(|x| x.1).min().unwrap_or(leaf_header.number); + let expected_ancestry_len = (leaf_header.number.saturating_sub(min_min) as usize) + 1; + + let ancestry = if leaf_header.number > 0 { + let mut next_ancestor_number = leaf_header.number - 1; + let mut next_ancestor_hash = leaf_header.parent_hash; + + let mut ancestry = Vec::with_capacity(expected_ancestry_len); + ancestry.push(leaf_hash); + + // Ensure all ancestors up to and including `min_min` are in the + // block storage. When views advance incrementally, everything + // should already be present. + while next_ancestor_number >= min_min { + let parent_hash = if let Some(info) = + self.block_info_storage.get(&next_ancestor_hash) + { + info.parent_hash + } else { + // load the header and insert into block storage. + let (tx, rx) = oneshot::channel(); + sender.send_message(ChainApiMessage::BlockHeader(next_ancestor_hash, tx)).await; + + let header = match rx.await { + Ok(Ok(Some(header))) => header, + Ok(Ok(None)) => + return Err(FetchError::BlockHeaderUnavailable( + next_ancestor_hash, + BlockHeaderUnavailableReason::Unknown, + )), + Ok(Err(e)) => + return Err(FetchError::BlockHeaderUnavailable( + next_ancestor_hash, + BlockHeaderUnavailableReason::Internal(e), + )), + Err(_) => + return Err(FetchError::BlockHeaderUnavailable( + next_ancestor_hash, + BlockHeaderUnavailableReason::SubsystemUnavailable, + )), + }; + + self.block_info_storage.insert( + next_ancestor_hash, + BlockInfo { + block_number: next_ancestor_number, + parent_hash: header.parent_hash, + maybe_allowed_relay_parents: None, + }, + ); + + header.parent_hash + }; + + ancestry.push(next_ancestor_hash); + if next_ancestor_number == 0 { + break + } + + next_ancestor_number -= 1; + next_ancestor_hash = parent_hash; + } + + ancestry + } else { + vec![leaf_hash] + }; + + let fetched_ancestry = + FetchSummary { minimum_ancestor_number: min_min, leaf_number: leaf_header.number }; + + let allowed_relay_parents = AllowedRelayParents { + minimum_relay_parents: min_relay_parents.into_iter().collect(), + allowed_relay_parents_contiguous: ancestry, + }; + + let leaf_block_info = BlockInfo { + parent_hash: leaf_header.parent_hash, + block_number: leaf_header.number, + maybe_allowed_relay_parents: Some(allowed_relay_parents), + }; + + self.block_info_storage.insert(leaf_hash, leaf_block_info); + + Ok(fetched_ancestry) + } } /// Errors when fetching a leaf and associated ancestry. @@ -437,137 +631,6 @@ where Ok(Some(min)) } -async fn fetch_fresh_leaf_and_insert_ancestry( - leaf_hash: Hash, - block_info_storage: &mut HashMap, - sender: &mut Sender, - collating_for: Option, -) -> Result -where - Sender: SubsystemSender - + SubsystemSender - + SubsystemSender, -{ - let leaf_header = { - let (tx, rx) = oneshot::channel(); - sender.send_message(ChainApiMessage::BlockHeader(leaf_hash, tx)).await; - - match rx.await { - Ok(Ok(Some(header))) => header, - Ok(Ok(None)) => - return Err(FetchError::BlockHeaderUnavailable( - leaf_hash, - BlockHeaderUnavailableReason::Unknown, - )), - Ok(Err(e)) => - return Err(FetchError::BlockHeaderUnavailable( - leaf_hash, - BlockHeaderUnavailableReason::Internal(e), - )), - Err(_) => - return Err(FetchError::BlockHeaderUnavailable( - leaf_hash, - BlockHeaderUnavailableReason::SubsystemUnavailable, - )), - } - }; - - // If the node is a collator, bypass prospective-parachains. We're only interested in the one - // paraid and the subsystem is not present. - let min_relay_parents = if let Some(para_id) = collating_for { - fetch_min_relay_parents_for_collator(leaf_hash, leaf_header.number, sender) - .await? - .map(|x| vec![(para_id, x)]) - .unwrap_or_default() - } else { - fetch_min_relay_parents_from_prospective_parachains(leaf_hash, sender).await? - }; - - let min_min = min_relay_parents.iter().map(|x| x.1).min().unwrap_or(leaf_header.number); - let expected_ancestry_len = (leaf_header.number.saturating_sub(min_min) as usize) + 1; - - let ancestry = if leaf_header.number > 0 { - let mut next_ancestor_number = leaf_header.number - 1; - let mut next_ancestor_hash = leaf_header.parent_hash; - - let mut ancestry = Vec::with_capacity(expected_ancestry_len); - ancestry.push(leaf_hash); - - // Ensure all ancestors up to and including `min_min` are in the - // block storage. When views advance incrementally, everything - // should already be present. - while next_ancestor_number >= min_min { - let parent_hash = if let Some(info) = block_info_storage.get(&next_ancestor_hash) { - info.parent_hash - } else { - // load the header and insert into block storage. - let (tx, rx) = oneshot::channel(); - sender.send_message(ChainApiMessage::BlockHeader(next_ancestor_hash, tx)).await; - - let header = match rx.await { - Ok(Ok(Some(header))) => header, - Ok(Ok(None)) => - return Err(FetchError::BlockHeaderUnavailable( - next_ancestor_hash, - BlockHeaderUnavailableReason::Unknown, - )), - Ok(Err(e)) => - return Err(FetchError::BlockHeaderUnavailable( - next_ancestor_hash, - BlockHeaderUnavailableReason::Internal(e), - )), - Err(_) => - return Err(FetchError::BlockHeaderUnavailable( - next_ancestor_hash, - BlockHeaderUnavailableReason::SubsystemUnavailable, - )), - }; - - block_info_storage.insert( - next_ancestor_hash, - BlockInfo { - block_number: next_ancestor_number, - parent_hash: header.parent_hash, - maybe_allowed_relay_parents: None, - }, - ); - - header.parent_hash - }; - - ancestry.push(next_ancestor_hash); - if next_ancestor_number == 0 { - break - } - - next_ancestor_number -= 1; - next_ancestor_hash = parent_hash; - } - - ancestry - } else { - vec![leaf_hash] - }; - - let fetched_ancestry = - FetchSummary { minimum_ancestor_number: min_min, leaf_number: leaf_header.number }; - - let allowed_relay_parents = AllowedRelayParents { - minimum_relay_parents: min_relay_parents.into_iter().collect(), - allowed_relay_parents_contiguous: ancestry, - }; - - let leaf_block_info = BlockInfo { - parent_hash: leaf_header.parent_hash, - block_number: leaf_header.number, - maybe_allowed_relay_parents: Some(allowed_relay_parents), - }; - - block_info_storage.insert(leaf_hash, leaf_block_info); - - Ok(fetched_ancestry) -} - #[cfg(test)] mod tests { use super::*; @@ -798,6 +861,23 @@ mod tests { assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_A)), Some(&expected_ancestry[..(PARA_A_MIN_PARENT - 1) as usize])); assert_eq!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)), Some(&expected_ancestry[..])); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); + + assert_eq!(view.leaves.len(), 1); + assert!(view.leaves.contains_key(leaf)); + assert!(view.paths_via_relay_parent(&CHAIN_B[0]).is_empty()); + assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty()); + assert_eq!( + view.paths_via_relay_parent(&CHAIN_B[min_min_idx]), + vec![CHAIN_B[min_min_idx..].to_vec()] + ); + assert_eq!( + view.paths_via_relay_parent(&CHAIN_B[min_min_idx + 1]), + vec![CHAIN_B[min_min_idx..].to_vec()] + ); + assert_eq!( + view.paths_via_relay_parent(&leaf), + vec![CHAIN_B[min_min_idx..].to_vec()] + ); } ); @@ -918,6 +998,12 @@ mod tests { assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)).unwrap().is_empty()); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); + + assert!(view.paths_via_relay_parent(&CHAIN_A[0]).is_empty()); + assert_eq!( + view.paths_via_relay_parent(&CHAIN_B[min_min_idx]), + vec![CHAIN_B[min_min_idx..].to_vec()] + ); } ); @@ -986,6 +1072,12 @@ mod tests { assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_B)).unwrap().is_empty()); assert!(view.known_allowed_relay_parents_under(&leaf, Some(PARA_C)).unwrap().is_empty()); + + assert!(view.paths_via_relay_parent(&GENESIS_HASH).is_empty()); + assert_eq!( + view.paths_via_relay_parent(&CHAIN_A[0]), + vec![CHAIN_A.to_vec()] + ); } ); } @@ -1160,4 +1252,69 @@ mod tests { Some(hashes) if hashes == &[GENESIS_HASH] ); } + + #[test] + fn path_with_fork() { + let pool = TaskExecutor::new(); + let (mut ctx, mut ctx_handle) = make_subsystem_context::(pool); + + let mut view = View::default(); + + assert_eq!(view.collating_for, None); + + // Chain A + let prospective_response = vec![(PARA_A, 0)]; // was PARA_A_MIN_PARENT + let leaf = CHAIN_A.last().unwrap(); + let blocks = [&[GENESIS_HASH], CHAIN_A].concat(); + let leaf_idx = blocks.len() - 1; + + let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { + res.expect("`activate_leaf` timed out").unwrap(); + }); + let overseer_fut = async { + assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[leaf_idx..]).await; + assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; + assert_block_header_requests(&mut ctx_handle, CHAIN_A, &blocks[..leaf_idx]).await; + }; + futures::executor::block_on(join(fut, overseer_fut)); + + // Chain B + let prospective_response = vec![(PARA_A, 1)]; + + let leaf = CHAIN_B.last().unwrap(); + let leaf_idx = CHAIN_B.len() - 1; + + let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { + res.expect("`activate_leaf` timed out").unwrap(); + }); + let overseer_fut = async { + assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[leaf_idx..]).await; + assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; + assert_block_header_requests(&mut ctx_handle, CHAIN_B, &CHAIN_B[0..leaf_idx]).await; + }; + futures::executor::block_on(join(fut, overseer_fut)); + + assert_eq!(view.leaves.len(), 2); + + let mut paths_to_genesis = view.paths_via_relay_parent(&GENESIS_HASH); + paths_to_genesis.sort(); + let mut expected_paths_to_genesis = vec![ + [GENESIS_HASH].iter().chain(CHAIN_A.iter()).copied().collect::>(), + [GENESIS_HASH].iter().chain(CHAIN_B.iter()).copied().collect::>(), + ]; + expected_paths_to_genesis.sort(); + assert_eq!(paths_to_genesis, expected_paths_to_genesis); + + let path_to_leaf_in_a = view.paths_via_relay_parent(&CHAIN_A[1]); + let expected_path_to_leaf_in_a = + vec![[GENESIS_HASH].iter().chain(CHAIN_A.iter()).copied().collect::>()]; + assert_eq!(path_to_leaf_in_a, expected_path_to_leaf_in_a); + + let path_to_leaf_in_b = view.paths_via_relay_parent(&CHAIN_B[4]); + let expected_path_to_leaf_in_b = + vec![[GENESIS_HASH].iter().chain(CHAIN_B.iter()).copied().collect::>()]; + assert_eq!(path_to_leaf_in_b, expected_path_to_leaf_in_b); + + assert_eq!(view.paths_via_relay_parent(&Hash::repeat_byte(0x0A)), Vec::>::new()); + } } diff --git a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml index 9b3576eaa3c2..046d707cc1e8 100644 --- a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml +++ b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml @@ -37,4 +37,4 @@ onboard_as_parachain = false [parachains.collator] name = "collator2000" command = "polkadot-parachain" - args = [ "-lparachain=debug" ] + args = [ "-lparachain=debug", "--experimental-use-slot-based" ] diff --git a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl index 7ba896e1c903..0cfc29f532d1 100644 --- a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl +++ b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl @@ -12,7 +12,7 @@ validator: parachain 2000 block height is at least 10 within 200 seconds # Register the second core assigned to this parachain. alice: js-script ./assign-core.js with "0,2000,57600" return is 0 within 600 seconds -alice: js-script ./assign-core.js with "0,2000,57600" return is 0 within 600 seconds +alice: js-script ./assign-core.js with "1,2000,57600" return is 0 within 600 seconds validator: reports substrate_block_height{status="finalized"} is at least 35 within 100 seconds diff --git a/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml b/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml index 745c4f9e24b1..d3ff00002242 100644 --- a/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml +++ b/polkadot/zombienet_tests/functional/0018-shared-core-idle-parachain.toml @@ -36,4 +36,4 @@ chain = "glutton-westend-local-2000" name = "collator-2000" image = "{{CUMULUS_IMAGE}}" command = "polkadot-parachain" - args = ["-lparachain=debug"] + args = ["-lparachain=debug", "--experimental-use-slot-based"] diff --git a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml new file mode 100644 index 000000000000..43f3ef8f9e55 --- /dev/null +++ b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.toml @@ -0,0 +1,58 @@ +[settings] +timeout = 1000 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] + max_candidate_depth = 3 + allowed_ancestry_len = 2 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] + max_validators_per_core = 4 + num_cores = 1 + lookahead = 2 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] + needed_approvals = 3 + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +chain = "rococo-local" +command = "polkadot" + + [[relaychain.node_groups]] + name = "validator" + args = ["-lparachain=debug,parachain::collator-protocol=trace" ] + count = 4 + +[[parachains]] +id = 2000 +register_para = false +onboard_as_parachain = false +add_to_genesis = false +chain = "glutton-westend-local-2000" + [parachains.genesis.runtimeGenesis.patch.glutton] + compute = "50000000" + storage = "2500000000" + trashDataCount = 5120 + + [parachains.collator] + name = "collator-2000" + image = "{{CUMULUS_IMAGE}}" + command = "polkadot-parachain" + args = ["-lparachain=debug,parachain::collator-protocol=trace", "--experimental-use-slot-based"] + +[[parachains]] +id = 2001 +register_para = false +onboard_as_parachain = false +add_to_genesis = false +chain = "glutton-westend-local-2001" + [parachains.genesis.runtimeGenesis.patch.glutton] + compute = "50000000" + storage = "2500000000" + trashDataCount = 5120 + + [parachains.collator] + name = "collator-2001" + image = "{{CUMULUS_IMAGE}}" + command = "polkadot-parachain" + args = ["-lparachain=debug"] diff --git a/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl new file mode 100644 index 000000000000..8892b03ac29c --- /dev/null +++ b/polkadot/zombienet_tests/functional/0019-coretime-collation-fetching-fairness.zndsl @@ -0,0 +1,16 @@ +Description: CT shared core fairness test +Network: ./0019-coretime-collation-fetching-fairness.toml +Creds: config + +validator: reports node_roles is 4 + +validator-0: js-script ./force-register-paras.js with "2000,2001" return is 0 within 600 seconds +# core 0 is shared 3:1 between paras +validator-0: js-script ./assign-core.js with "0,2000,43200,2001,14400" return is 0 within 600 seconds + +collator-2000: reports block height is at least 9 within 200 seconds +collator-2001: reports block height is at least 3 within 10 seconds + +# hardcoded check to verify that included onchain events are indeed 3:1 +validator-0: js-script ./0019-verify-included-events.js return is 1 within 120 seconds + diff --git a/polkadot/zombienet_tests/functional/0019-verify-included-events.js b/polkadot/zombienet_tests/functional/0019-verify-included-events.js new file mode 100644 index 000000000000..6557a5a80e6b --- /dev/null +++ b/polkadot/zombienet_tests/functional/0019-verify-included-events.js @@ -0,0 +1,51 @@ +function parse_pjs_int(input) { + return parseInt(input.replace(/,/g, '')); +} + +async function run(nodeName, networkInfo) { + const { wsUri, userDefinedTypes } = networkInfo.nodesByName[nodeName]; + const api = await zombie.connect(wsUri, userDefinedTypes); + + let blocks_per_para = {}; + + await new Promise(async (resolve, _) => { + let block_count = 0; + const unsubscribe = await api.query.system.events(async (events, block_hash) => { + block_count++; + + events.forEach((record) => { + const event = record.event; + + if (event.method != 'CandidateIncluded') { + return; + } + + let included_para_id = parse_pjs_int(event.toHuman().data[0].descriptor.paraId); + let relay_parent = event.toHuman().data[0].descriptor.relayParent; + if (blocks_per_para[included_para_id] == undefined) { + blocks_per_para[included_para_id] = 1; + } else { + blocks_per_para[included_para_id]++; + } + console.log(`CandidateIncluded for ${included_para_id}: block_offset=${block_count} relay_parent=${relay_parent}`); + }); + + if (block_count == 12) { + unsubscribe(); + return resolve(); + } + }); + }); + + console.log(`Result: 2000: ${blocks_per_para[2000]}, 2001: ${blocks_per_para[2001]}`); + // This check assumes that para 2000 runs slot based collator which respects its claim queue + // and para 2001 runs lookahead which generates blocks for each relay parent. + // + // For 12 blocks there will be one session change. One block won't have anything backed/included. + // In the next there will be one backed so for 12 blocks we should expect 10 included events - no + // more than 4 for para 2001 and at least 6 for para 2000. This should also cover the unlucky + // case when we observe two session changes during the 12 block period. + return (blocks_per_para[2000] >= 6) && (blocks_per_para[2001] <= 4); +} + +module.exports = { run }; diff --git a/prdoc/pr_4880.prdoc b/prdoc/pr_4880.prdoc new file mode 100644 index 000000000000..1bcd09088b5f --- /dev/null +++ b/prdoc/pr_4880.prdoc @@ -0,0 +1,31 @@ +title: Collation fetching fairness in collator protocol + +doc: + - audience: "Node Dev" + description: | + Implements collation fetching fairness in the validator side of the collator protocol. With + core time in place if two (or more) parachains share a single core no fairness was guaranteed + between them in terms of collation fetching. The current implementation was accepting up to + `max_candidate_depth + 1` seconded collations per relay parent and once this limit is reached + no new collations are accepted. A misbehaving collator can abuse this fact and prevent other + collators/parachains from advertising collations by advertising `max_candidate_depth + 1` + collations of its own. + To address this issue two changes are made: + 1. For each parachain id the validator accepts advertisements until the number of entries in + the claim queue equals the number of seconded candidates. + 2. When new collation should be fetched the validator inspects what was seconded so far, + what's in the claim queue and picks the first slot which hasn't got a collation seconded + and there is no candidate pending seconding for it. If there is an advertisement in the + waiting queue for it it is fetched. Otherwise the next free slot is picked. + These two changes guarantee that: + 1. Validator doesn't accept more collations than it can actually back. + 2. Each parachain has got a fair share of core time based on its allocations in the claim + queue. + +crates: + - name: polkadot-collator-protocol + bump: patch + - name: polkadot + bump: patch + - name: polkadot-node-subsystem-util + bump: minor \ No newline at end of file From 9ce80f687b207072099aa2a1a11927ef12be34f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Fri, 13 Dec 2024 10:01:09 +0100 Subject: [PATCH 159/340] Rename PanicInfo to PanicHookInfo (#6865) Starting with Rust 1.82 `PanicInfo` is deprecated and will throw warnings when used. The new type is available since Rust 1.81 and should be available on our CI. --------- Co-authored-by: command-bot <> --- prdoc/pr_6865.prdoc | 9 +++++++++ substrate/primitives/panic-handler/src/lib.rs | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_6865.prdoc diff --git a/prdoc/pr_6865.prdoc b/prdoc/pr_6865.prdoc new file mode 100644 index 000000000000..c0581f2af24f --- /dev/null +++ b/prdoc/pr_6865.prdoc @@ -0,0 +1,9 @@ +title: Rename PanicInfo to PanicHookInfo +doc: +- audience: Node Dev + description: Starting with Rust 1.82 `PanicInfo` is deprecated and will throw warnings + when used. The new type is available since Rust 1.81 and should be available on + our CI. +crates: +- name: sp-panic-handler + bump: patch diff --git a/substrate/primitives/panic-handler/src/lib.rs b/substrate/primitives/panic-handler/src/lib.rs index c4a7eb8dc67c..81ccaaee828e 100644 --- a/substrate/primitives/panic-handler/src/lib.rs +++ b/substrate/primitives/panic-handler/src/lib.rs @@ -30,7 +30,7 @@ use std::{ cell::Cell, io::{self, Write}, marker::PhantomData, - panic::{self, PanicInfo}, + panic::{self, PanicHookInfo}, sync::LazyLock, thread, }; @@ -149,7 +149,7 @@ fn strip_control_codes(input: &str) -> std::borrow::Cow { } /// Function being called when a panic happens. -fn panic_hook(info: &PanicInfo, report_url: &str, version: &str) { +fn panic_hook(info: &PanicHookInfo, report_url: &str, version: &str) { let location = info.location(); let file = location.as_ref().map(|l| l.file()).unwrap_or(""); let line = location.as_ref().map(|l| l.line()).unwrap_or(0); From 0349789580fcc2c1f6040daafc8b3e94011ddfb2 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Fri, 13 Dec 2024 10:12:19 +0100 Subject: [PATCH 160/340] [pallet-revive] implement the call data size API (#6857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds an API method to query the contract call data input size. Part of #6770 --------- Signed-off-by: Cyrill Leutwiler Co-authored-by: command-bot <> Co-authored-by: Alexander Theißen --- prdoc/pr_6857.prdoc | 14 + .../fixtures/contracts/call_data_size.rs | 37 + .../frame/revive/src/benchmarking/mod.rs | 15 + substrate/frame/revive/src/tests.rs | 22 + substrate/frame/revive/src/wasm/runtime.rs | 19 + substrate/frame/revive/src/weights.rs | 815 +++++++++--------- substrate/frame/revive/uapi/src/host.rs | 7 + .../frame/revive/uapi/src/host/riscv64.rs | 3 +- 8 files changed, 533 insertions(+), 399 deletions(-) create mode 100644 prdoc/pr_6857.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/call_data_size.rs diff --git a/prdoc/pr_6857.prdoc b/prdoc/pr_6857.prdoc new file mode 100644 index 000000000000..3930f5910487 --- /dev/null +++ b/prdoc/pr_6857.prdoc @@ -0,0 +1,14 @@ +title: '[pallet-revive] implement the call data size API' +doc: +- audience: Runtime Dev + description: |- + This PR adds an API method to query the contract call data input size. + + Part of #6770 +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/call_data_size.rs b/substrate/frame/revive/fixtures/contracts/call_data_size.rs new file mode 100644 index 000000000000..32205b921d47 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_data_size.rs @@ -0,0 +1,37 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Returns the call data size back to the caller. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut buf = [0; 32]; + api::call_data_size(&mut buf); + + api::return_value(ReturnFlags::empty(), &buf); +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 94fac13d78e8..1fb4d7ab58a4 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -771,6 +771,21 @@ mod benchmarks { assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().minimum_balance()); } + #[benchmark(pov_mode = Measured)] + fn seal_call_data_size() { + let mut setup = CallSetup::::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![42u8; 128 as usize]); + let mut memory = memory!(vec![0u8; 32 as usize],); + let result; + #[block] + { + result = runtime.bench_call_data_size(memory.as_mut_slice(), 0); + } + assert_ok!(result); + assert_eq!(U256::from_little_endian(&memory[..]), U256::from(128)); + } + #[benchmark(pov_mode = Measured)] fn seal_block_number() { build_runtime!(runtime, memory: [[0u8;32], ]); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index b3cd591e4d0c..27ec7948e8fd 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4342,6 +4342,28 @@ fn create1_with_value_works() { }); } +#[test] +fn call_data_size_api_works() { + let (code, _) = compile_module("call_data_size").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the call data size API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::zero()); + + let received = builder::bare_call(addr).data(vec![1; 256]).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(&received.data), U256::from(256)); + }); +} + #[test] fn static_data_limit_is_enforced() { let (oom_rw_trailing, _) = compile_module("oom_rw_trailing").unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index d8b856b0b766..ac499171c772 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -271,6 +271,8 @@ pub enum RuntimeCosts { CallDataLoad, /// Weight of calling `seal_caller`. Caller, + /// Weight of calling `seal_call_data_size`. + CallDataSize, /// Weight of calling `seal_origin`. Origin, /// Weight of calling `seal_is_contract`. @@ -431,6 +433,7 @@ impl Token for RuntimeCosts { HostFn => cost_args!(noop_host_fn, 1), CopyToContract(len) => T::WeightInfo::seal_input(len), CopyFromContract(len) => T::WeightInfo::seal_return(len), + CallDataSize => T::WeightInfo::seal_call_data_size(), CallDataLoad => T::WeightInfo::seal_call_data_load(), Caller => T::WeightInfo::seal_caller(), Origin => T::WeightInfo::seal_origin(), @@ -1296,6 +1299,22 @@ pub mod env { ) } + /// Returns the total size of the contract call input data. + /// See [`pallet_revive_uapi::HostFn::call_data_size `]. + #[stable] + fn call_data_size(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::CallDataSize)?; + let value = + U256::from(self.input_data.as_ref().map(|input| input.len()).unwrap_or_default()); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &value.to_little_endian(), + false, + already_charged, + )?) + } + /// Remove the calling account and transfer remaining **free** balance. /// See [`pallet_revive_uapi::HostFn::terminate`]. #[mutating] diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index e9178287f8f9..e8fec31b19e5 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -18,9 +18,9 @@ //! Autogenerated weights for `pallet_revive` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-12-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `9fd11f1b2ec3`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `a0d5968554fc`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -80,6 +80,7 @@ pub trait WeightInfo { fn seal_set_immutable_data(n: u32, ) -> Weight; fn seal_value_transferred() -> Weight; fn seal_minimum_balance() -> Weight; + fn seal_call_data_size() -> Weight; fn seal_block_number() -> Weight; fn seal_block_hash() -> Weight; fn seal_now() -> Weight; @@ -134,8 +135,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_874_000 picoseconds. - Weight::from_parts(3_131_000, 1594) + // Minimum execution time: 2_921_000 picoseconds. + Weight::from_parts(3_048_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -145,10 +146,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 16_079_000 picoseconds. - Weight::from_parts(5_747_743, 415) - // Standard Error: 1_130 - .saturating_add(Weight::from_parts(1_181_775, 0).saturating_mul(k.into())) + // Minimum execution time: 16_060_000 picoseconds. + Weight::from_parts(3_234_033, 415) + // Standard Error: 1_160 + .saturating_add(Weight::from_parts(1_184_188, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -172,8 +173,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1536` // Estimated: `7476` - // Minimum execution time: 94_513_000 picoseconds. - Weight::from_parts(99_111_938, 7476) + // Minimum execution time: 93_624_000 picoseconds. + Weight::from_parts(98_332_129, 7476) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -197,12 +198,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `416` // Estimated: `6345` - // Minimum execution time: 195_917_000 picoseconds. - Weight::from_parts(175_835_928, 6345) + // Minimum execution time: 196_202_000 picoseconds. + Weight::from_parts(169_823_092, 6345) // Standard Error: 10 - .saturating_add(Weight::from_parts(10, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(30, 0).saturating_mul(c.into())) // Standard Error: 10 - .saturating_add(Weight::from_parts(4_554, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_487, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -225,10 +226,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1296` // Estimated: `4753` - // Minimum execution time: 162_583_000 picoseconds. - Weight::from_parts(143_621_658, 4753) + // Minimum execution time: 162_423_000 picoseconds. + Weight::from_parts(144_467_590, 4753) // Standard Error: 16 - .saturating_add(Weight::from_parts(4_499, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_405, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -248,8 +249,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1536` // Estimated: `7476` - // Minimum execution time: 145_642_000 picoseconds. - Weight::from_parts(152_866_000, 7476) + // Minimum execution time: 144_454_000 picoseconds. + Weight::from_parts(151_756_000, 7476) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -264,8 +265,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 51_664_000 picoseconds. - Weight::from_parts(53_863_257, 3574) + // Minimum execution time: 50_712_000 picoseconds. + Weight::from_parts(52_831_382, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -279,8 +280,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 44_879_000 picoseconds. - Weight::from_parts(46_401_000, 3750) + // Minimum execution time: 44_441_000 picoseconds. + Weight::from_parts(46_242_000, 3750) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -292,8 +293,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 27_833_000 picoseconds. - Weight::from_parts(29_013_000, 6469) + // Minimum execution time: 27_157_000 picoseconds. + Weight::from_parts(28_182_000, 6469) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -305,8 +306,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 40_611_000 picoseconds. - Weight::from_parts(41_336_000, 3574) + // Minimum execution time: 40_588_000 picoseconds. + Weight::from_parts(41_125_000, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -318,8 +319,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 32_576_000 picoseconds. - Weight::from_parts(33_300_000, 3521) + // Minimum execution time: 31_849_000 picoseconds. + Weight::from_parts(32_674_000, 3521) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -331,8 +332,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_978_000 picoseconds. - Weight::from_parts(14_573_000, 3610) + // Minimum execution time: 14_510_000 picoseconds. + Weight::from_parts(14_986_000, 3610) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -340,24 +341,24 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_877_000 picoseconds. - Weight::from_parts(8_471_206, 0) - // Standard Error: 226 - .saturating_add(Weight::from_parts(165_314, 0).saturating_mul(r.into())) + // Minimum execution time: 7_324_000 picoseconds. + Weight::from_parts(8_363_388, 0) + // Standard Error: 230 + .saturating_add(Weight::from_parts(170_510, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 290_000 picoseconds. - Weight::from_parts(345_000, 0) + // Minimum execution time: 275_000 picoseconds. + Weight::from_parts(326_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 243_000 picoseconds. - Weight::from_parts(303_000, 0) + // Minimum execution time: 263_000 picoseconds. + Weight::from_parts(292_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -365,8 +366,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_441_000 picoseconds. - Weight::from_parts(10_812_000, 3771) + // Minimum execution time: 10_011_000 picoseconds. + Weight::from_parts(10_476_000, 3771) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -375,16 +376,16 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_403_000 picoseconds. - Weight::from_parts(11_913_000, 3868) + // Minimum execution time: 11_253_000 picoseconds. + Weight::from_parts(11_642_000, 3868) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 259_000 picoseconds. - Weight::from_parts(306_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(318_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -394,44 +395,44 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_887_000 picoseconds. - Weight::from_parts(15_625_000, 3938) + // Minimum execution time: 14_904_000 picoseconds. + Weight::from_parts(15_281_000, 3938) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 315_000 picoseconds. - Weight::from_parts(389_000, 0) + // Minimum execution time: 382_000 picoseconds. + Weight::from_parts(422_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 294_000 picoseconds. - Weight::from_parts(322_000, 0) + // Minimum execution time: 258_000 picoseconds. + Weight::from_parts(310_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 239_000 picoseconds. - Weight::from_parts(299_000, 0) + // Minimum execution time: 283_000 picoseconds. + Weight::from_parts(315_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 628_000 picoseconds. - Weight::from_parts(703_000, 0) + // Minimum execution time: 637_000 picoseconds. + Weight::from_parts(726_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `103` // Estimated: `0` - // Minimum execution time: 4_816_000 picoseconds. - Weight::from_parts(5_078_000, 0) + // Minimum execution time: 4_649_000 picoseconds. + Weight::from_parts(4_860_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -441,8 +442,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 8_965_000 picoseconds. - Weight::from_parts(9_533_000, 3729) + // Minimum execution time: 9_053_000 picoseconds. + Weight::from_parts(9_480_000, 3729) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -452,10 +453,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 6_174_000 picoseconds. - Weight::from_parts(6_755_842, 3703) - // Standard Error: 4 - .saturating_add(Weight::from_parts(699, 0).saturating_mul(n.into())) + // Minimum execution time: 5_991_000 picoseconds. + Weight::from_parts(6_760_389, 3703) + // Standard Error: 5 + .saturating_add(Weight::from_parts(627, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -466,32 +467,39 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_977_000 picoseconds. - Weight::from_parts(2_175_653, 0) + // Minimum execution time: 2_062_000 picoseconds. + Weight::from_parts(2_277_051, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(633, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(530, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 259_000 picoseconds. - Weight::from_parts(298_000, 0) + // Minimum execution time: 267_000 picoseconds. + Weight::from_parts(299_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 277_000 picoseconds. - Weight::from_parts(330_000, 0) + // Minimum execution time: 263_000 picoseconds. + Weight::from_parts(318_000, 0) + } + fn seal_call_data_size() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 264_000 picoseconds. + Weight::from_parts(303_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 260_000 picoseconds. - Weight::from_parts(295_000, 0) + // Minimum execution time: 267_000 picoseconds. + Weight::from_parts(296_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -499,50 +507,50 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_607_000 picoseconds. - Weight::from_parts(3_760_000, 3495) + // Minimum execution time: 3_622_000 picoseconds. + Weight::from_parts(3_794_000, 3495) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 271_000 picoseconds. - Weight::from_parts(299_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(298_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_320_000 picoseconds. - Weight::from_parts(1_406_000, 0) + // Minimum execution time: 1_340_000 picoseconds. + Weight::from_parts(1_483_000, 0) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 250_000 picoseconds. - Weight::from_parts(285_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(295_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_input(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 411_000 picoseconds. - Weight::from_parts(514_738, 0) + // Minimum execution time: 475_000 picoseconds. + Weight::from_parts(427_145, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 282_000 picoseconds. - Weight::from_parts(463_520, 0) + // Minimum execution time: 291_000 picoseconds. + Weight::from_parts(846_264, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(295, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -559,10 +567,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `324 + n * (88 ±0)` // Estimated: `3791 + n * (2563 ±0)` - // Minimum execution time: 22_960_000 picoseconds. - Weight::from_parts(23_432_764, 3791) - // Standard Error: 12_030 - .saturating_add(Weight::from_parts(4_292_055, 0).saturating_mul(n.into())) + // Minimum execution time: 22_494_000 picoseconds. + Weight::from_parts(23_028_153, 3791) + // Standard Error: 12_407 + .saturating_add(Weight::from_parts(4_238_442, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -575,22 +583,22 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_346_000 picoseconds. - Weight::from_parts(4_208_327, 0) - // Standard Error: 2_509 - .saturating_add(Weight::from_parts(194_145, 0).saturating_mul(t.into())) - // Standard Error: 22 - .saturating_add(Weight::from_parts(1_084, 0).saturating_mul(n.into())) + // Minimum execution time: 4_383_000 picoseconds. + Weight::from_parts(4_364_292, 0) + // Standard Error: 2_775 + .saturating_add(Weight::from_parts(210_189, 0).saturating_mul(t.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(952, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 360_000 picoseconds. - Weight::from_parts(374_000, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(816, 0).saturating_mul(i.into())) + // Minimum execution time: 328_000 picoseconds. + Weight::from_parts(393_925, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(725, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -598,8 +606,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 8_066_000 picoseconds. - Weight::from_parts(8_425_000, 744) + // Minimum execution time: 7_649_000 picoseconds. + Weight::from_parts(8_025_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -608,8 +616,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 43_707_000 picoseconds. - Weight::from_parts(44_613_000, 10754) + // Minimum execution time: 43_439_000 picoseconds. + Weight::from_parts(44_296_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -618,8 +626,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 9_101_000 picoseconds. - Weight::from_parts(9_425_000, 744) + // Minimum execution time: 8_919_000 picoseconds. + Weight::from_parts(9_392_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -629,8 +637,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 45_990_000 picoseconds. - Weight::from_parts(46_945_000, 10754) + // Minimum execution time: 45_032_000 picoseconds. + Weight::from_parts(46_050_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -642,12 +650,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_229_000 picoseconds. - Weight::from_parts(10_039_961, 247) - // Standard Error: 39 - .saturating_add(Weight::from_parts(359, 0).saturating_mul(n.into())) - // Standard Error: 39 - .saturating_add(Weight::from_parts(424, 0).saturating_mul(o.into())) + // Minimum execution time: 9_272_000 picoseconds. + Weight::from_parts(10_022_838, 247) + // Standard Error: 43 + .saturating_add(Weight::from_parts(513, 0).saturating_mul(n.into())) + // Standard Error: 43 + .saturating_add(Weight::from_parts(625, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -659,10 +667,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_038_000 picoseconds. - Weight::from_parts(9_855_448, 247) + // Minimum execution time: 8_885_000 picoseconds. + Weight::from_parts(9_785_932, 247) // Standard Error: 55 - .saturating_add(Weight::from_parts(544, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(612, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -674,10 +682,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_533_000 picoseconds. - Weight::from_parts(9_485_405, 247) - // Standard Error: 60 - .saturating_add(Weight::from_parts(1_436, 0).saturating_mul(n.into())) + // Minimum execution time: 8_440_000 picoseconds. + Weight::from_parts(9_453_769, 247) + // Standard Error: 62 + .saturating_add(Weight::from_parts(1_529, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -688,10 +696,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_300_000 picoseconds. - Weight::from_parts(8_914_778, 247) - // Standard Error: 46 - .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) + // Minimum execution time: 8_212_000 picoseconds. + Weight::from_parts(8_880_676, 247) + // Standard Error: 54 + .saturating_add(Weight::from_parts(673, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -702,10 +710,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_384_000 picoseconds. - Weight::from_parts(10_500_656, 247) - // Standard Error: 64 - .saturating_add(Weight::from_parts(1_400, 0).saturating_mul(n.into())) + // Minimum execution time: 9_491_000 picoseconds. + Weight::from_parts(10_313_570, 247) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_681, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -714,36 +722,36 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_478_000 picoseconds. - Weight::from_parts(1_625_000, 0) + // Minimum execution time: 1_530_000 picoseconds. + Weight::from_parts(1_642_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_842_000 picoseconds. - Weight::from_parts(1_969_000, 0) + // Minimum execution time: 1_851_000 picoseconds. + Weight::from_parts(1_999_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_437_000 picoseconds. - Weight::from_parts(1_557_000, 0) + // Minimum execution time: 1_429_000 picoseconds. + Weight::from_parts(1_527_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_600_000 picoseconds. - Weight::from_parts(1_679_000, 0) + // Minimum execution time: 1_689_000 picoseconds. + Weight::from_parts(1_772_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_114_000 picoseconds. - Weight::from_parts(1_191_000, 0) + // Minimum execution time: 1_049_000 picoseconds. + Weight::from_parts(1_153_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -751,50 +759,52 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_326_000 picoseconds. - Weight::from_parts(2_451_799, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(391, 0).saturating_mul(n.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(361, 0).saturating_mul(o.into())) + // Minimum execution time: 2_338_000 picoseconds. + Weight::from_parts(2_514_685, 0) + // Standard Error: 15 + .saturating_add(Weight::from_parts(299, 0).saturating_mul(n.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(403, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_951_000 picoseconds. - Weight::from_parts(2_353_245, 0) + // Minimum execution time: 2_045_000 picoseconds. + Weight::from_parts(2_409_843, 0) // Standard Error: 16 - .saturating_add(Weight::from_parts(369, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(350, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_822_000 picoseconds. - Weight::from_parts(2_059_181, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(398, 0).saturating_mul(n.into())) + // Minimum execution time: 1_891_000 picoseconds. + Weight::from_parts(2_117_702, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(289, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_697_000 picoseconds. - Weight::from_parts(1_905_887, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(215, 0).saturating_mul(n.into())) + // Minimum execution time: 1_786_000 picoseconds. + Weight::from_parts(1_949_290, 0) + // Standard Error: 11 + .saturating_add(Weight::from_parts(232, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. - fn seal_take_transient_storage(_n: u32, ) -> Weight { + fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_533_000 picoseconds. - Weight::from_parts(2_759_660, 0) + // Minimum execution time: 2_465_000 picoseconds. + Weight::from_parts(2_712_107, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(79, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -812,12 +822,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1294 + t * (243 ±0)` // Estimated: `4759 + t * (2501 ±0)` - // Minimum execution time: 43_295_000 picoseconds. - Weight::from_parts(44_592_141, 4759) - // Standard Error: 60_598 - .saturating_add(Weight::from_parts(1_458_798, 0).saturating_mul(t.into())) + // Minimum execution time: 41_377_000 picoseconds. + Weight::from_parts(43_024_676, 4759) + // Standard Error: 44_099 + .saturating_add(Weight::from_parts(1_689_315, 0).saturating_mul(t.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(3, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) @@ -833,8 +843,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 37_787_000 picoseconds. - Weight::from_parts(38_510_000, 4702) + // Minimum execution time: 36_324_000 picoseconds. + Weight::from_parts(37_657_000, 4702) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -850,10 +860,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1273` // Estimated: `4736` - // Minimum execution time: 121_346_000 picoseconds. - Weight::from_parts(115_747_843, 4736) - // Standard Error: 10 - .saturating_add(Weight::from_parts(4_189, 0).saturating_mul(i.into())) + // Minimum execution time: 117_657_000 picoseconds. + Weight::from_parts(110_177_403, 4736) + // Standard Error: 11 + .saturating_add(Weight::from_parts(4_097, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -862,64 +872,64 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 696_000 picoseconds. - Weight::from_parts(3_319_775, 0) + // Minimum execution time: 650_000 picoseconds. + Weight::from_parts(4_208_007, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_500, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_396, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_070_000 picoseconds. - Weight::from_parts(4_463_019, 0) + // Minimum execution time: 1_101_000 picoseconds. + Weight::from_parts(4_521_803, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_689, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_609, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 617_000 picoseconds. - Weight::from_parts(3_175_243, 0) + // Minimum execution time: 654_000 picoseconds. + Weight::from_parts(3_060_461, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_617, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_531, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 616_000 picoseconds. - Weight::from_parts(3_420_409, 0) + // Minimum execution time: 628_000 picoseconds. + Weight::from_parts(3_784_567, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_623, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_526, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 45_562_000 picoseconds. - Weight::from_parts(34_462_046, 0) - // Standard Error: 11 - .saturating_add(Weight::from_parts(5_259, 0).saturating_mul(n.into())) + // Minimum execution time: 42_892_000 picoseconds. + Weight::from_parts(25_002_714, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(5_252, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 49_472_000 picoseconds. - Weight::from_parts(50_517_000, 0) + // Minimum execution time: 46_990_000 picoseconds. + Weight::from_parts(48_960_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_716_000 picoseconds. - Weight::from_parts(12_812_000, 0) + // Minimum execution time: 12_870_000 picoseconds. + Weight::from_parts(13_062_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -927,8 +937,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 17_891_000 picoseconds. - Weight::from_parts(18_833_000, 3765) + // Minimum execution time: 17_810_000 picoseconds. + Weight::from_parts(18_667_000, 3765) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -938,8 +948,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 14_523_000 picoseconds. - Weight::from_parts(14_812_000, 3803) + // Minimum execution time: 13_762_000 picoseconds. + Weight::from_parts(14_526_000, 3803) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -949,8 +959,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 13_114_000 picoseconds. - Weight::from_parts(13_567_000, 3561) + // Minimum execution time: 12_753_000 picoseconds. + Weight::from_parts(13_199_000, 3561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -959,10 +969,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_717_000 picoseconds. - Weight::from_parts(9_983_815, 0) - // Standard Error: 115 - .saturating_add(Weight::from_parts(72_253, 0).saturating_mul(r.into())) + // Minimum execution time: 9_060_000 picoseconds. + Weight::from_parts(10_131_024, 0) + // Standard Error: 72 + .saturating_add(Weight::from_parts(71_842, 0).saturating_mul(r.into())) } } @@ -974,8 +984,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_874_000 picoseconds. - Weight::from_parts(3_131_000, 1594) + // Minimum execution time: 2_921_000 picoseconds. + Weight::from_parts(3_048_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -985,10 +995,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 16_079_000 picoseconds. - Weight::from_parts(5_747_743, 415) - // Standard Error: 1_130 - .saturating_add(Weight::from_parts(1_181_775, 0).saturating_mul(k.into())) + // Minimum execution time: 16_060_000 picoseconds. + Weight::from_parts(3_234_033, 415) + // Standard Error: 1_160 + .saturating_add(Weight::from_parts(1_184_188, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1012,8 +1022,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1536` // Estimated: `7476` - // Minimum execution time: 94_513_000 picoseconds. - Weight::from_parts(99_111_938, 7476) + // Minimum execution time: 93_624_000 picoseconds. + Weight::from_parts(98_332_129, 7476) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1037,12 +1047,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `416` // Estimated: `6345` - // Minimum execution time: 195_917_000 picoseconds. - Weight::from_parts(175_835_928, 6345) + // Minimum execution time: 196_202_000 picoseconds. + Weight::from_parts(169_823_092, 6345) // Standard Error: 10 - .saturating_add(Weight::from_parts(10, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(30, 0).saturating_mul(c.into())) // Standard Error: 10 - .saturating_add(Weight::from_parts(4_554, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_487, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1065,10 +1075,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1296` // Estimated: `4753` - // Minimum execution time: 162_583_000 picoseconds. - Weight::from_parts(143_621_658, 4753) + // Minimum execution time: 162_423_000 picoseconds. + Weight::from_parts(144_467_590, 4753) // Standard Error: 16 - .saturating_add(Weight::from_parts(4_499, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_405, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1088,8 +1098,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1536` // Estimated: `7476` - // Minimum execution time: 145_642_000 picoseconds. - Weight::from_parts(152_866_000, 7476) + // Minimum execution time: 144_454_000 picoseconds. + Weight::from_parts(151_756_000, 7476) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1104,8 +1114,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 51_664_000 picoseconds. - Weight::from_parts(53_863_257, 3574) + // Minimum execution time: 50_712_000 picoseconds. + Weight::from_parts(52_831_382, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1119,8 +1129,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 44_879_000 picoseconds. - Weight::from_parts(46_401_000, 3750) + // Minimum execution time: 44_441_000 picoseconds. + Weight::from_parts(46_242_000, 3750) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1132,8 +1142,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 27_833_000 picoseconds. - Weight::from_parts(29_013_000, 6469) + // Minimum execution time: 27_157_000 picoseconds. + Weight::from_parts(28_182_000, 6469) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1145,8 +1155,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 40_611_000 picoseconds. - Weight::from_parts(41_336_000, 3574) + // Minimum execution time: 40_588_000 picoseconds. + Weight::from_parts(41_125_000, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1158,8 +1168,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 32_576_000 picoseconds. - Weight::from_parts(33_300_000, 3521) + // Minimum execution time: 31_849_000 picoseconds. + Weight::from_parts(32_674_000, 3521) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1171,8 +1181,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_978_000 picoseconds. - Weight::from_parts(14_573_000, 3610) + // Minimum execution time: 14_510_000 picoseconds. + Weight::from_parts(14_986_000, 3610) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1180,24 +1190,24 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_877_000 picoseconds. - Weight::from_parts(8_471_206, 0) - // Standard Error: 226 - .saturating_add(Weight::from_parts(165_314, 0).saturating_mul(r.into())) + // Minimum execution time: 7_324_000 picoseconds. + Weight::from_parts(8_363_388, 0) + // Standard Error: 230 + .saturating_add(Weight::from_parts(170_510, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 290_000 picoseconds. - Weight::from_parts(345_000, 0) + // Minimum execution time: 275_000 picoseconds. + Weight::from_parts(326_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 243_000 picoseconds. - Weight::from_parts(303_000, 0) + // Minimum execution time: 263_000 picoseconds. + Weight::from_parts(292_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1205,8 +1215,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_441_000 picoseconds. - Weight::from_parts(10_812_000, 3771) + // Minimum execution time: 10_011_000 picoseconds. + Weight::from_parts(10_476_000, 3771) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -1215,16 +1225,16 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_403_000 picoseconds. - Weight::from_parts(11_913_000, 3868) + // Minimum execution time: 11_253_000 picoseconds. + Weight::from_parts(11_642_000, 3868) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 259_000 picoseconds. - Weight::from_parts(306_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(318_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1234,44 +1244,44 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_887_000 picoseconds. - Weight::from_parts(15_625_000, 3938) + // Minimum execution time: 14_904_000 picoseconds. + Weight::from_parts(15_281_000, 3938) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 315_000 picoseconds. - Weight::from_parts(389_000, 0) + // Minimum execution time: 382_000 picoseconds. + Weight::from_parts(422_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 294_000 picoseconds. - Weight::from_parts(322_000, 0) + // Minimum execution time: 258_000 picoseconds. + Weight::from_parts(310_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 239_000 picoseconds. - Weight::from_parts(299_000, 0) + // Minimum execution time: 283_000 picoseconds. + Weight::from_parts(315_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 628_000 picoseconds. - Weight::from_parts(703_000, 0) + // Minimum execution time: 637_000 picoseconds. + Weight::from_parts(726_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `103` // Estimated: `0` - // Minimum execution time: 4_816_000 picoseconds. - Weight::from_parts(5_078_000, 0) + // Minimum execution time: 4_649_000 picoseconds. + Weight::from_parts(4_860_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1281,8 +1291,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 8_965_000 picoseconds. - Weight::from_parts(9_533_000, 3729) + // Minimum execution time: 9_053_000 picoseconds. + Weight::from_parts(9_480_000, 3729) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -1292,10 +1302,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 6_174_000 picoseconds. - Weight::from_parts(6_755_842, 3703) - // Standard Error: 4 - .saturating_add(Weight::from_parts(699, 0).saturating_mul(n.into())) + // Minimum execution time: 5_991_000 picoseconds. + Weight::from_parts(6_760_389, 3703) + // Standard Error: 5 + .saturating_add(Weight::from_parts(627, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1306,32 +1316,39 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_977_000 picoseconds. - Weight::from_parts(2_175_653, 0) + // Minimum execution time: 2_062_000 picoseconds. + Weight::from_parts(2_277_051, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(633, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(530, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 259_000 picoseconds. - Weight::from_parts(298_000, 0) + // Minimum execution time: 267_000 picoseconds. + Weight::from_parts(299_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 277_000 picoseconds. - Weight::from_parts(330_000, 0) + // Minimum execution time: 263_000 picoseconds. + Weight::from_parts(318_000, 0) + } + fn seal_call_data_size() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 264_000 picoseconds. + Weight::from_parts(303_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 260_000 picoseconds. - Weight::from_parts(295_000, 0) + // Minimum execution time: 267_000 picoseconds. + Weight::from_parts(296_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -1339,50 +1356,50 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_607_000 picoseconds. - Weight::from_parts(3_760_000, 3495) + // Minimum execution time: 3_622_000 picoseconds. + Weight::from_parts(3_794_000, 3495) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 271_000 picoseconds. - Weight::from_parts(299_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(298_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_320_000 picoseconds. - Weight::from_parts(1_406_000, 0) + // Minimum execution time: 1_340_000 picoseconds. + Weight::from_parts(1_483_000, 0) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 250_000 picoseconds. - Weight::from_parts(285_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(295_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_input(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 411_000 picoseconds. - Weight::from_parts(514_738, 0) + // Minimum execution time: 475_000 picoseconds. + Weight::from_parts(427_145, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 282_000 picoseconds. - Weight::from_parts(463_520, 0) + // Minimum execution time: 291_000 picoseconds. + Weight::from_parts(846_264, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(295, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1399,10 +1416,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `324 + n * (88 ±0)` // Estimated: `3791 + n * (2563 ±0)` - // Minimum execution time: 22_960_000 picoseconds. - Weight::from_parts(23_432_764, 3791) - // Standard Error: 12_030 - .saturating_add(Weight::from_parts(4_292_055, 0).saturating_mul(n.into())) + // Minimum execution time: 22_494_000 picoseconds. + Weight::from_parts(23_028_153, 3791) + // Standard Error: 12_407 + .saturating_add(Weight::from_parts(4_238_442, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -1415,22 +1432,22 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_346_000 picoseconds. - Weight::from_parts(4_208_327, 0) - // Standard Error: 2_509 - .saturating_add(Weight::from_parts(194_145, 0).saturating_mul(t.into())) - // Standard Error: 22 - .saturating_add(Weight::from_parts(1_084, 0).saturating_mul(n.into())) + // Minimum execution time: 4_383_000 picoseconds. + Weight::from_parts(4_364_292, 0) + // Standard Error: 2_775 + .saturating_add(Weight::from_parts(210_189, 0).saturating_mul(t.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(952, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 360_000 picoseconds. - Weight::from_parts(374_000, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(816, 0).saturating_mul(i.into())) + // Minimum execution time: 328_000 picoseconds. + Weight::from_parts(393_925, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(725, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -1438,8 +1455,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 8_066_000 picoseconds. - Weight::from_parts(8_425_000, 744) + // Minimum execution time: 7_649_000 picoseconds. + Weight::from_parts(8_025_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1448,8 +1465,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 43_707_000 picoseconds. - Weight::from_parts(44_613_000, 10754) + // Minimum execution time: 43_439_000 picoseconds. + Weight::from_parts(44_296_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1458,8 +1475,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 9_101_000 picoseconds. - Weight::from_parts(9_425_000, 744) + // Minimum execution time: 8_919_000 picoseconds. + Weight::from_parts(9_392_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1469,8 +1486,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 45_990_000 picoseconds. - Weight::from_parts(46_945_000, 10754) + // Minimum execution time: 45_032_000 picoseconds. + Weight::from_parts(46_050_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1482,12 +1499,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_229_000 picoseconds. - Weight::from_parts(10_039_961, 247) - // Standard Error: 39 - .saturating_add(Weight::from_parts(359, 0).saturating_mul(n.into())) - // Standard Error: 39 - .saturating_add(Weight::from_parts(424, 0).saturating_mul(o.into())) + // Minimum execution time: 9_272_000 picoseconds. + Weight::from_parts(10_022_838, 247) + // Standard Error: 43 + .saturating_add(Weight::from_parts(513, 0).saturating_mul(n.into())) + // Standard Error: 43 + .saturating_add(Weight::from_parts(625, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -1499,10 +1516,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_038_000 picoseconds. - Weight::from_parts(9_855_448, 247) + // Minimum execution time: 8_885_000 picoseconds. + Weight::from_parts(9_785_932, 247) // Standard Error: 55 - .saturating_add(Weight::from_parts(544, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(612, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1514,10 +1531,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_533_000 picoseconds. - Weight::from_parts(9_485_405, 247) - // Standard Error: 60 - .saturating_add(Weight::from_parts(1_436, 0).saturating_mul(n.into())) + // Minimum execution time: 8_440_000 picoseconds. + Weight::from_parts(9_453_769, 247) + // Standard Error: 62 + .saturating_add(Weight::from_parts(1_529, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1528,10 +1545,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_300_000 picoseconds. - Weight::from_parts(8_914_778, 247) - // Standard Error: 46 - .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) + // Minimum execution time: 8_212_000 picoseconds. + Weight::from_parts(8_880_676, 247) + // Standard Error: 54 + .saturating_add(Weight::from_parts(673, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1542,10 +1559,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_384_000 picoseconds. - Weight::from_parts(10_500_656, 247) - // Standard Error: 64 - .saturating_add(Weight::from_parts(1_400, 0).saturating_mul(n.into())) + // Minimum execution time: 9_491_000 picoseconds. + Weight::from_parts(10_313_570, 247) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_681, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1554,36 +1571,36 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_478_000 picoseconds. - Weight::from_parts(1_625_000, 0) + // Minimum execution time: 1_530_000 picoseconds. + Weight::from_parts(1_642_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_842_000 picoseconds. - Weight::from_parts(1_969_000, 0) + // Minimum execution time: 1_851_000 picoseconds. + Weight::from_parts(1_999_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_437_000 picoseconds. - Weight::from_parts(1_557_000, 0) + // Minimum execution time: 1_429_000 picoseconds. + Weight::from_parts(1_527_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_600_000 picoseconds. - Weight::from_parts(1_679_000, 0) + // Minimum execution time: 1_689_000 picoseconds. + Weight::from_parts(1_772_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_114_000 picoseconds. - Weight::from_parts(1_191_000, 0) + // Minimum execution time: 1_049_000 picoseconds. + Weight::from_parts(1_153_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -1591,50 +1608,52 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_326_000 picoseconds. - Weight::from_parts(2_451_799, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(391, 0).saturating_mul(n.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(361, 0).saturating_mul(o.into())) + // Minimum execution time: 2_338_000 picoseconds. + Weight::from_parts(2_514_685, 0) + // Standard Error: 15 + .saturating_add(Weight::from_parts(299, 0).saturating_mul(n.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(403, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_951_000 picoseconds. - Weight::from_parts(2_353_245, 0) + // Minimum execution time: 2_045_000 picoseconds. + Weight::from_parts(2_409_843, 0) // Standard Error: 16 - .saturating_add(Weight::from_parts(369, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(350, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_822_000 picoseconds. - Weight::from_parts(2_059_181, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(398, 0).saturating_mul(n.into())) + // Minimum execution time: 1_891_000 picoseconds. + Weight::from_parts(2_117_702, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(289, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_697_000 picoseconds. - Weight::from_parts(1_905_887, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(215, 0).saturating_mul(n.into())) + // Minimum execution time: 1_786_000 picoseconds. + Weight::from_parts(1_949_290, 0) + // Standard Error: 11 + .saturating_add(Weight::from_parts(232, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. - fn seal_take_transient_storage(_n: u32, ) -> Weight { + fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_533_000 picoseconds. - Weight::from_parts(2_759_660, 0) + // Minimum execution time: 2_465_000 picoseconds. + Weight::from_parts(2_712_107, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(79, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1652,12 +1671,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1294 + t * (243 ±0)` // Estimated: `4759 + t * (2501 ±0)` - // Minimum execution time: 43_295_000 picoseconds. - Weight::from_parts(44_592_141, 4759) - // Standard Error: 60_598 - .saturating_add(Weight::from_parts(1_458_798, 0).saturating_mul(t.into())) + // Minimum execution time: 41_377_000 picoseconds. + Weight::from_parts(43_024_676, 4759) + // Standard Error: 44_099 + .saturating_add(Weight::from_parts(1_689_315, 0).saturating_mul(t.into())) // Standard Error: 0 - .saturating_add(Weight::from_parts(3, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) @@ -1673,8 +1692,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 37_787_000 picoseconds. - Weight::from_parts(38_510_000, 4702) + // Minimum execution time: 36_324_000 picoseconds. + Weight::from_parts(37_657_000, 4702) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -1690,10 +1709,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1273` // Estimated: `4736` - // Minimum execution time: 121_346_000 picoseconds. - Weight::from_parts(115_747_843, 4736) - // Standard Error: 10 - .saturating_add(Weight::from_parts(4_189, 0).saturating_mul(i.into())) + // Minimum execution time: 117_657_000 picoseconds. + Weight::from_parts(110_177_403, 4736) + // Standard Error: 11 + .saturating_add(Weight::from_parts(4_097, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1702,64 +1721,64 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 696_000 picoseconds. - Weight::from_parts(3_319_775, 0) + // Minimum execution time: 650_000 picoseconds. + Weight::from_parts(4_208_007, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_500, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_396, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_070_000 picoseconds. - Weight::from_parts(4_463_019, 0) + // Minimum execution time: 1_101_000 picoseconds. + Weight::from_parts(4_521_803, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_689, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_609, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 617_000 picoseconds. - Weight::from_parts(3_175_243, 0) + // Minimum execution time: 654_000 picoseconds. + Weight::from_parts(3_060_461, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_617, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_531, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 616_000 picoseconds. - Weight::from_parts(3_420_409, 0) + // Minimum execution time: 628_000 picoseconds. + Weight::from_parts(3_784_567, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_623, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_526, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 45_562_000 picoseconds. - Weight::from_parts(34_462_046, 0) - // Standard Error: 11 - .saturating_add(Weight::from_parts(5_259, 0).saturating_mul(n.into())) + // Minimum execution time: 42_892_000 picoseconds. + Weight::from_parts(25_002_714, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(5_252, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 49_472_000 picoseconds. - Weight::from_parts(50_517_000, 0) + // Minimum execution time: 46_990_000 picoseconds. + Weight::from_parts(48_960_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_716_000 picoseconds. - Weight::from_parts(12_812_000, 0) + // Minimum execution time: 12_870_000 picoseconds. + Weight::from_parts(13_062_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1767,8 +1786,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 17_891_000 picoseconds. - Weight::from_parts(18_833_000, 3765) + // Minimum execution time: 17_810_000 picoseconds. + Weight::from_parts(18_667_000, 3765) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1778,8 +1797,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 14_523_000 picoseconds. - Weight::from_parts(14_812_000, 3803) + // Minimum execution time: 13_762_000 picoseconds. + Weight::from_parts(14_526_000, 3803) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1789,8 +1808,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 13_114_000 picoseconds. - Weight::from_parts(13_567_000, 3561) + // Minimum execution time: 12_753_000 picoseconds. + Weight::from_parts(13_199_000, 3561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1799,9 +1818,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_717_000 picoseconds. - Weight::from_parts(9_983_815, 0) - // Standard Error: 115 - .saturating_add(Weight::from_parts(72_253, 0).saturating_mul(r.into())) + // Minimum execution time: 9_060_000 picoseconds. + Weight::from_parts(10_131_024, 0) + // Standard Error: 72 + .saturating_add(Weight::from_parts(71_842, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index c6b9ef9d4fa2..a8c8a924aeeb 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -98,6 +98,13 @@ pub trait HostFn: private::Sealed { /// Returns the [EIP-155](https://eips.ethereum.org/EIPS/eip-155) chain ID. fn chain_id(output: &mut [u8; 32]); + /// Stores the call data size as little endian U256 value into the supplied buffer. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the call data size. + fn call_data_size(output: &mut [u8; 32]); + /// Stores the current block number of the current contract into the supplied buffer. /// /// # Parameters diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index a208fef7055a..4e2cc125bbe5 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -90,6 +90,7 @@ mod sys { data_ptr: *const u8, data_len: u32, ); + pub fn call_data_size(out_ptr: *mut u8); pub fn block_number(out_ptr: *mut u8); pub fn block_hash(block_number_ptr: *const u8, out_ptr: *mut u8); pub fn hash_sha2_256(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); @@ -465,7 +466,7 @@ impl HostFn for HostFnImpl { } impl_wrapper_for! { - [u8; 32] => block_number, balance, value_transferred, now, minimum_balance, chain_id; + [u8; 32] => call_data_size, block_number, balance, value_transferred, now, minimum_balance, chain_id; [u8; 20] => address, caller, origin; } From e1add3e8a8faa611e63e3f962b6c5b13ba37e449 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Fri, 13 Dec 2024 10:52:28 +0100 Subject: [PATCH 161/340] rpc: re-use server builder per rpc interface (#6652) This PR changes that the server builder is created once and shared/cloned for each connection to avoid some extra overhead to construct this for each connection (as it was before). I don't know why I constructed a new builder for each connection because it's not needed but shouldn't make a big difference to my understanding. --------- Co-authored-by: command-bot <> --- prdoc/pr_6652.prdoc | 13 +++ substrate/client/rpc-servers/src/lib.rs | 101 +++++++++++----------- substrate/client/rpc-servers/src/utils.rs | 10 ++- 3 files changed, 69 insertions(+), 55 deletions(-) create mode 100644 prdoc/pr_6652.prdoc diff --git a/prdoc/pr_6652.prdoc b/prdoc/pr_6652.prdoc new file mode 100644 index 000000000000..a303311e138f --- /dev/null +++ b/prdoc/pr_6652.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "rpc server: re-use server builder per rpc interface" + +doc: + - audience: Node Dev + description: | + This changes that the RPC server builder is re-used for each RPC interface which is more efficient than to build it for every connection. + +crates: + - name: sc-rpc-server + bump: patch diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index ff21e2da768e..4234ff3196ef 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -144,11 +144,56 @@ where local_addrs.push(local_addr); let cfg = cfg.clone(); - let id_provider2 = id_provider.clone(); + let RpcSettings { + batch_config, + max_connections, + max_payload_in_mb, + max_payload_out_mb, + max_buffer_capacity_per_connection, + max_subscriptions_per_connection, + rpc_methods, + rate_limit_trust_proxy_headers, + rate_limit_whitelisted_ips, + host_filter, + cors, + rate_limit, + } = listener.rpc_settings(); + + let http_middleware = tower::ServiceBuilder::new() + .option_layer(host_filter) + // Proxy `GET /health, /health/readiness` requests to the internal + // `system_health` method. + .layer(NodeHealthProxyLayer::default()) + .layer(cors); + + let mut builder = jsonrpsee::server::Server::builder() + .max_request_body_size(max_payload_in_mb.saturating_mul(MEGABYTE)) + .max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE)) + .max_connections(max_connections) + .max_subscriptions_per_connection(max_subscriptions_per_connection) + .enable_ws_ping( + PingConfig::new() + .ping_interval(Duration::from_secs(30)) + .inactive_limit(Duration::from_secs(60)) + .max_failures(3), + ) + .set_http_middleware(http_middleware) + .set_message_buffer_capacity(max_buffer_capacity_per_connection) + .set_batch_request_config(batch_config) + .custom_tokio_runtime(cfg.tokio_handle.clone()); + + if let Some(provider) = id_provider.clone() { + builder = builder.set_id_provider(provider); + } else { + builder = builder.set_id_provider(RandomStringIdProvider::new(16)); + }; + + let service_builder = builder.to_service_builder(); + let deny_unsafe = deny_unsafe(&local_addr, &rpc_methods); tokio_handle.spawn(async move { loop { - let (sock, remote_addr, rpc_cfg) = tokio::select! { + let (sock, remote_addr) = tokio::select! { res = listener.accept() => { match res { Ok(s) => s, @@ -161,56 +206,10 @@ where _ = cfg.stop_handle.clone().shutdown() => break, }; - let RpcSettings { - batch_config, - max_connections, - max_payload_in_mb, - max_payload_out_mb, - max_buffer_capacity_per_connection, - max_subscriptions_per_connection, - rpc_methods, - rate_limit_trust_proxy_headers, - rate_limit_whitelisted_ips, - host_filter, - cors, - rate_limit, - } = rpc_cfg; - - let http_middleware = tower::ServiceBuilder::new() - .option_layer(host_filter) - // Proxy `GET /health, /health/readiness` requests to the internal - // `system_health` method. - .layer(NodeHealthProxyLayer::default()) - .layer(cors); - - let mut builder = jsonrpsee::server::Server::builder() - .max_request_body_size(max_payload_in_mb.saturating_mul(MEGABYTE)) - .max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE)) - .max_connections(max_connections) - .max_subscriptions_per_connection(max_subscriptions_per_connection) - .enable_ws_ping( - PingConfig::new() - .ping_interval(Duration::from_secs(30)) - .inactive_limit(Duration::from_secs(60)) - .max_failures(3), - ) - .set_http_middleware(http_middleware) - .set_message_buffer_capacity(max_buffer_capacity_per_connection) - .set_batch_request_config(batch_config) - .custom_tokio_runtime(cfg.tokio_handle.clone()); - - if let Some(provider) = id_provider2.clone() { - builder = builder.set_id_provider(provider); - } else { - builder = builder.set_id_provider(RandomStringIdProvider::new(16)); - }; - - let service_builder = builder.to_service_builder(); - let deny_unsafe = deny_unsafe(&local_addr, &rpc_methods); - let ip = remote_addr.ip(); let cfg2 = cfg.clone(); let service_builder2 = service_builder.clone(); + let rate_limit_whitelisted_ips2 = rate_limit_whitelisted_ips.clone(); let svc = tower::service_fn(move |mut req: http::Request| { @@ -223,14 +222,14 @@ where let proxy_ip = if rate_limit_trust_proxy_headers { get_proxy_ip(&req) } else { None }; - let rate_limit_cfg = if rate_limit_whitelisted_ips + let rate_limit_cfg = if rate_limit_whitelisted_ips2 .iter() .any(|ips| ips.contains(proxy_ip.unwrap_or(ip))) { log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is trusted, disabling rate-limit", proxy_ip); None } else { - if !rate_limit_whitelisted_ips.is_empty() { + if !rate_limit_whitelisted_ips2.is_empty() { log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is not trusted, rate-limit enabled", proxy_ip); } rate_limit diff --git a/substrate/client/rpc-servers/src/utils.rs b/substrate/client/rpc-servers/src/utils.rs index 51cce6224298..b76cfced3401 100644 --- a/substrate/client/rpc-servers/src/utils.rs +++ b/substrate/client/rpc-servers/src/utils.rs @@ -176,17 +176,19 @@ pub(crate) struct Listener { impl Listener { /// Accepts a new connection. - pub(crate) async fn accept( - &mut self, - ) -> std::io::Result<(tokio::net::TcpStream, SocketAddr, RpcSettings)> { + pub(crate) async fn accept(&mut self) -> std::io::Result<(tokio::net::TcpStream, SocketAddr)> { let (sock, remote_addr) = self.listener.accept().await?; - Ok((sock, remote_addr, self.cfg.clone())) + Ok((sock, remote_addr)) } /// Returns the local address the listener is bound to. pub fn local_addr(&self) -> SocketAddr { self.local_addr } + + pub fn rpc_settings(&self) -> RpcSettings { + self.cfg.clone() + } } pub(crate) fn host_filtering(enabled: bool, addr: SocketAddr) -> Option { From 4b054c60b1641612ef0a76dcc75eed5dd23a18cf Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Fri, 13 Dec 2024 12:30:28 +0200 Subject: [PATCH 162/340] Expose DHT content providers API from `sc-network` (#6711) Expose the Kademlia content providers API for the use by `sc-network` client code: 1. Extend the `NetworkDHTProvider` trait with functions to start/stop providing content and query the DHT for the list of content providers for a given key. 2. Extend the `DhtEvent` enum with events reporting the found providers or query failures. 3. Implement the above for libp2p & litep2p network backends. --------- Co-authored-by: GitHub Action Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> --- prdoc/pr_6711.prdoc | 13 ++ .../client/authority-discovery/src/worker.rs | 3 + .../authority-discovery/src/worker/tests.rs | 12 ++ substrate/client/network/src/behaviour.rs | 27 +++ substrate/client/network/src/discovery.rs | 88 +++++++++- substrate/client/network/src/event.rs | 9 + .../client/network/src/litep2p/discovery.rs | 43 ++++- substrate/client/network/src/litep2p/mod.rs | 160 +++++++++++++----- .../client/network/src/litep2p/service.rs | 21 +++ substrate/client/network/src/service.rs | 24 +++ .../client/network/src/service/traits.rs | 21 +++ 11 files changed, 371 insertions(+), 50 deletions(-) create mode 100644 prdoc/pr_6711.prdoc diff --git a/prdoc/pr_6711.prdoc b/prdoc/pr_6711.prdoc new file mode 100644 index 000000000000..ec09035e1356 --- /dev/null +++ b/prdoc/pr_6711.prdoc @@ -0,0 +1,13 @@ +title: Expose DHT content providers API from `sc-network` +doc: +- audience: Node Dev + description: |- + Expose the Kademlia content providers API for the use by `sc-network` client code: + 1. Extend the `NetworkDHTProvider` trait with functions to start/stop providing content and query the DHT for the list of content providers for a given key. + 2. Extend the `DhtEvent` enum with events reporting the found providers or query failures. + 3. Implement the above for libp2p & litep2p network backends. +crates: +- name: sc-network + bump: major +- name: sc-authority-discovery + bump: major diff --git a/substrate/client/authority-discovery/src/worker.rs b/substrate/client/authority-discovery/src/worker.rs index ba82910efcdf..6630b7157d96 100644 --- a/substrate/client/authority-discovery/src/worker.rs +++ b/substrate/client/authority-discovery/src/worker.rs @@ -677,6 +677,9 @@ where metrics.dht_event_received.with_label_values(&["put_record_req"]).inc(); } }, + DhtEvent::StartProvidingFailed(..) => {}, + DhtEvent::ProvidersFound(..) => {}, + DhtEvent::ProvidersNotFound(..) => {}, } } diff --git a/substrate/client/authority-discovery/src/worker/tests.rs b/substrate/client/authority-discovery/src/worker/tests.rs index 6c3a3b56b1cb..c14771585655 100644 --- a/substrate/client/authority-discovery/src/worker/tests.rs +++ b/substrate/client/authority-discovery/src/worker/tests.rs @@ -231,6 +231,18 @@ impl NetworkDHTProvider for TestNetwork { .unbounded_send(TestNetworkEvent::StoreRecordCalled) .unwrap(); } + + fn start_providing(&self, _: KademliaKey) { + unimplemented!() + } + + fn stop_providing(&self, _: KademliaKey) { + unimplemented!() + } + + fn get_providers(&self, _: KademliaKey) { + unimplemented!() + } } impl NetworkStateInfo for TestNetwork { diff --git a/substrate/client/network/src/behaviour.rs b/substrate/client/network/src/behaviour.rs index dbb72381b660..cee80b6c1e86 100644 --- a/substrate/client/network/src/behaviour.rs +++ b/substrate/client/network/src/behaviour.rs @@ -310,6 +310,22 @@ impl Behaviour { ) { self.discovery.store_record(record_key, record_value, publisher, expires); } + + /// Start providing `key` on the DHT. + pub fn start_providing(&mut self, key: RecordKey) { + self.discovery.start_providing(key) + } + + /// Stop providing `key` on the DHT. + pub fn stop_providing(&mut self, key: &RecordKey) { + self.discovery.stop_providing(key) + } + + /// Start searching for providers on the DHT. Will later produce either a `ProvidersFound` + /// or `ProvidersNotFound` event. + pub fn get_providers(&mut self, key: RecordKey) { + self.discovery.get_providers(key) + } } impl From for BehaviourOut { @@ -387,6 +403,17 @@ impl From for BehaviourOut { ), DiscoveryOut::ValuePutFailed(key, duration) => BehaviourOut::Dht(DhtEvent::ValuePutFailed(key.into()), Some(duration)), + DiscoveryOut::StartProvidingFailed(key) => + BehaviourOut::Dht(DhtEvent::StartProvidingFailed(key.into()), None), + DiscoveryOut::ProvidersFound(key, providers, duration) => BehaviourOut::Dht( + DhtEvent::ProvidersFound( + key.into(), + providers.into_iter().map(Into::into).collect(), + ), + Some(duration), + ), + DiscoveryOut::ProvidersNotFound(key, duration) => + BehaviourOut::Dht(DhtEvent::ProvidersNotFound(key.into()), Some(duration)), DiscoveryOut::RandomKademliaStarted => BehaviourOut::RandomKademliaStarted, } } diff --git a/substrate/client/network/src/discovery.rs b/substrate/client/network/src/discovery.rs index 8080bda9a574..81baa00e201e 100644 --- a/substrate/client/network/src/discovery.rs +++ b/substrate/client/network/src/discovery.rs @@ -58,8 +58,8 @@ use libp2p::{ self, record::store::{MemoryStore, RecordStore}, Behaviour as Kademlia, BucketInserts, Config as KademliaConfig, Event as KademliaEvent, - GetClosestPeersError, GetRecordOk, PeerRecord, QueryId, QueryResult, Quorum, Record, - RecordKey, + GetClosestPeersError, GetProvidersError, GetProvidersOk, GetRecordOk, PeerRecord, QueryId, + QueryResult, Quorum, Record, RecordKey, }, mdns::{self, tokio::Behaviour as TokioMdns}, multiaddr::Protocol, @@ -466,6 +466,31 @@ impl DiscoveryBehaviour { } } } + + /// Register as a content provider on the DHT for `key`. + pub fn start_providing(&mut self, key: RecordKey) { + if let Some(kad) = self.kademlia.as_mut() { + if let Err(e) = kad.start_providing(key.clone()) { + warn!(target: "sub-libp2p", "Libp2p => Failed to start providing {key:?}: {e}."); + self.pending_events.push_back(DiscoveryOut::StartProvidingFailed(key)); + } + } + } + + /// Deregister as a content provider on the DHT for `key`. + pub fn stop_providing(&mut self, key: &RecordKey) { + if let Some(kad) = self.kademlia.as_mut() { + kad.stop_providing(key); + } + } + + /// Get content providers for `key` from the DHT. + pub fn get_providers(&mut self, key: RecordKey) { + if let Some(kad) = self.kademlia.as_mut() { + kad.get_providers(key); + } + } + /// Store a record in the Kademlia record store. pub fn store_record( &mut self, @@ -581,6 +606,15 @@ pub enum DiscoveryOut { /// Returning the corresponding key as well as the request duration. ValuePutFailed(RecordKey, Duration), + /// Starting providing a key failed. + StartProvidingFailed(RecordKey), + + /// The DHT yielded results for the providers request. + ProvidersFound(RecordKey, HashSet, Duration), + + /// Providers for the requested key were not found in the DHT. + ProvidersNotFound(RecordKey, Duration), + /// Started a random Kademlia query. /// /// Only happens if [`DiscoveryConfig::with_dht_random_walk`] has been configured to `true`. @@ -982,6 +1016,56 @@ impl NetworkBehaviour for DiscoveryBehaviour { }; return Poll::Ready(ToSwarm::GenerateEvent(ev)) }, + KademliaEvent::OutboundQueryProgressed { + result: QueryResult::GetProviders(res), + stats, + id, + .. + } => { + let ev = match res { + Ok(GetProvidersOk::FoundProviders { key, providers }) => { + debug!( + target: "sub-libp2p", + "Libp2p => Found providers {:?} for key {:?}, id {:?}, stats {:?}", + providers, + key, + id, + stats, + ); + + DiscoveryOut::ProvidersFound( + key, + providers, + stats.duration().unwrap_or_default(), + ) + }, + Ok(GetProvidersOk::FinishedWithNoAdditionalRecord { + closest_peers: _, + }) => { + debug!( + target: "sub-libp2p", + "Libp2p => Finished with no additional providers {:?}, stats {:?}, took {:?} ms", + id, + stats, + stats.duration().map(|val| val.as_millis()) + ); + + continue + }, + Err(GetProvidersError::Timeout { key, closest_peers: _ }) => { + debug!( + target: "sub-libp2p", + "Libp2p => Failed to get providers for {key:?} due to timeout.", + ); + + DiscoveryOut::ProvidersNotFound( + key, + stats.duration().unwrap_or_default(), + ) + }, + }; + return Poll::Ready(ToSwarm::GenerateEvent(ev)) + }, KademliaEvent::OutboundQueryProgressed { result: QueryResult::PutRecord(res), stats, diff --git a/substrate/client/network/src/event.rs b/substrate/client/network/src/event.rs index 626cf516a7ec..e8ec1eee2545 100644 --- a/substrate/client/network/src/event.rs +++ b/substrate/client/network/src/event.rs @@ -45,8 +45,17 @@ pub enum DhtEvent { /// An error has occurred while putting a record into the DHT. ValuePutFailed(Key), + /// An error occured while registering as a content provider on the DHT. + StartProvidingFailed(Key), + /// The DHT received a put record request. PutRecordRequest(Key, Vec, Option, Option), + + /// The providers for [`Key`] were found. + ProvidersFound(Key, Vec), + + /// The providers for [`Key`] were not found. + ProvidersNotFound(Key), } /// Type for events generated by networking layer. diff --git a/substrate/client/network/src/litep2p/discovery.rs b/substrate/client/network/src/litep2p/discovery.rs index 3a9454e317cc..2bea2e5a80dc 100644 --- a/substrate/client/network/src/litep2p/discovery.rs +++ b/substrate/client/network/src/litep2p/discovery.rs @@ -32,7 +32,7 @@ use litep2p::{ libp2p::{ identify::{Config as IdentifyConfig, IdentifyEvent}, kademlia::{ - Config as KademliaConfig, ConfigBuilder as KademliaConfigBuilder, + Config as KademliaConfig, ConfigBuilder as KademliaConfigBuilder, ContentProvider, IncomingRecordValidationMode, KademliaEvent, KademliaHandle, QueryId, Quorum, Record, RecordKey, RecordsType, }, @@ -144,6 +144,14 @@ pub enum DiscoveryEvent { query_id: QueryId, }, + /// Providers were successfully retrieved. + GetProvidersSuccess { + /// Query ID. + query_id: QueryId, + /// Found providers sorted by distance to provided key. + providers: Vec, + }, + /// Query failed. QueryFailed { /// Query ID. @@ -407,6 +415,21 @@ impl Discovery { .await; } + /// Start providing `key`. + pub async fn start_providing(&mut self, key: KademliaKey) { + self.kademlia_handle.start_providing(key.into()).await; + } + + /// Stop providing `key`. + pub async fn stop_providing(&mut self, key: KademliaKey) { + self.kademlia_handle.stop_providing(key.into()).await; + } + + /// Get providers for `key`. + pub async fn get_providers(&mut self, key: KademliaKey) -> QueryId { + self.kademlia_handle.get_providers(key.into()).await + } + /// Check if the observed address is a known address. fn is_known_address(known: &Multiaddr, observed: &Multiaddr) -> bool { let mut known = known.iter(); @@ -581,8 +604,22 @@ impl Stream for Discovery { return Poll::Ready(Some(DiscoveryEvent::IncomingRecord { record })) }, - // Content provider events are ignored for now. - Poll::Ready(Some(KademliaEvent::GetProvidersSuccess { .. })) | + Poll::Ready(Some(KademliaEvent::GetProvidersSuccess { + provided_key, + providers, + query_id, + })) => { + log::trace!( + target: LOG_TARGET, + "`GET_PROVIDERS` for {query_id:?} with {provided_key:?} yielded {providers:?}", + ); + + return Poll::Ready(Some(DiscoveryEvent::GetProvidersSuccess { + query_id, + providers, + })) + }, + // We do not validate incoming providers. Poll::Ready(Some(KademliaEvent::IncomingProvider { .. })) => {}, } diff --git a/substrate/client/network/src/litep2p/mod.rs b/substrate/client/network/src/litep2p/mod.rs index b6d64b34d64a..52b2970525df 100644 --- a/substrate/client/network/src/litep2p/mod.rs +++ b/substrate/client/network/src/litep2p/mod.rs @@ -143,6 +143,17 @@ struct ConnectionContext { num_connections: usize, } +/// Kademlia query we are tracking. +#[derive(Debug)] +enum KadQuery { + /// `GET_VALUE` query for key and when it was initiated. + GetValue(RecordKey, Instant), + /// `PUT_VALUE` query for key and when it was initiated. + PutValue(RecordKey, Instant), + /// `GET_PROVIDERS` query for key and when it was initiated. + GetProviders(RecordKey, Instant), +} + /// Networking backend for `litep2p`. pub struct Litep2pNetworkBackend { /// Main `litep2p` object. @@ -157,11 +168,8 @@ pub struct Litep2pNetworkBackend { /// `Peerset` handles to notification protocols. peerset_handles: HashMap, - /// Pending `GET_VALUE` queries. - pending_get_values: HashMap, - - /// Pending `PUT_VALUE` queries. - pending_put_values: HashMap, + /// Pending Kademlia queries. + pending_queries: HashMap, /// Discovery. discovery: Discovery, @@ -615,8 +623,7 @@ impl NetworkBackend for Litep2pNetworkBac peerset_handles: notif_protocols, num_connected, discovery, - pending_put_values: HashMap::new(), - pending_get_values: HashMap::new(), + pending_queries: HashMap::new(), peerstore_handle: peer_store_handle, block_announce_protocol, event_streams: out_events::OutChannels::new(None)?, @@ -704,21 +711,30 @@ impl NetworkBackend for Litep2pNetworkBac Some(command) => match command { NetworkServiceCommand::GetValue{ key } => { let query_id = self.discovery.get_value(key.clone()).await; - self.pending_get_values.insert(query_id, (key, Instant::now())); + self.pending_queries.insert(query_id, KadQuery::GetValue(key, Instant::now())); } NetworkServiceCommand::PutValue { key, value } => { let query_id = self.discovery.put_value(key.clone(), value).await; - self.pending_put_values.insert(query_id, (key, Instant::now())); + self.pending_queries.insert(query_id, KadQuery::PutValue(key, Instant::now())); } NetworkServiceCommand::PutValueTo { record, peers, update_local_storage} => { let kademlia_key = record.key.clone(); let query_id = self.discovery.put_value_to_peers(record.into(), peers, update_local_storage).await; - self.pending_put_values.insert(query_id, (kademlia_key, Instant::now())); + self.pending_queries.insert(query_id, KadQuery::PutValue(kademlia_key, Instant::now())); } - NetworkServiceCommand::StoreRecord { key, value, publisher, expires } => { self.discovery.store_record(key, value, publisher.map(Into::into), expires).await; } + NetworkServiceCommand::StartProviding { key } => { + self.discovery.start_providing(key).await; + } + NetworkServiceCommand::StopProviding { key } => { + self.discovery.stop_providing(key).await; + } + NetworkServiceCommand::GetProviders { key } => { + let query_id = self.discovery.get_providers(key.clone()).await; + self.pending_queries.insert(query_id, KadQuery::GetProviders(key, Instant::now())); + } NetworkServiceCommand::EventStream { tx } => { self.event_streams.push(tx); } @@ -821,12 +837,8 @@ impl NetworkBackend for Litep2pNetworkBac } } Some(DiscoveryEvent::GetRecordSuccess { query_id, records }) => { - match self.pending_get_values.remove(&query_id) { - None => log::warn!( - target: LOG_TARGET, - "`GET_VALUE` succeeded for a non-existent query", - ), - Some((key, started)) => { + match self.pending_queries.remove(&query_id) { + Some(KadQuery::GetValue(key, started)) => { log::trace!( target: LOG_TARGET, "`GET_VALUE` for {:?} ({query_id:?}) succeeded", @@ -848,16 +860,19 @@ impl NetworkBackend for Litep2pNetworkBac .with_label_values(&["value-get"]) .observe(started.elapsed().as_secs_f64()); } - } + }, + query => { + log::error!( + target: LOG_TARGET, + "Missing/invalid pending query for `GET_VALUE`: {query:?}" + ); + debug_assert!(false); + }, } } Some(DiscoveryEvent::PutRecordSuccess { query_id }) => { - match self.pending_put_values.remove(&query_id) { - None => log::warn!( - target: LOG_TARGET, - "`PUT_VALUE` succeeded for a non-existent query", - ), - Some((key, started)) => { + match self.pending_queries.remove(&query_id) { + Some(KadQuery::PutValue(key, started)) => { log::trace!( target: LOG_TARGET, "`PUT_VALUE` for {key:?} ({query_id:?}) succeeded", @@ -873,35 +888,50 @@ impl NetworkBackend for Litep2pNetworkBac .with_label_values(&["value-put"]) .observe(started.elapsed().as_secs_f64()); } + }, + query => { + log::error!( + target: LOG_TARGET, + "Missing/invalid pending query for `PUT_VALUE`: {query:?}" + ); + debug_assert!(false); } } } - Some(DiscoveryEvent::QueryFailed { query_id }) => { - match self.pending_get_values.remove(&query_id) { - None => match self.pending_put_values.remove(&query_id) { - None => log::warn!( + Some(DiscoveryEvent::GetProvidersSuccess { query_id, providers }) => { + match self.pending_queries.remove(&query_id) { + Some(KadQuery::GetProviders(key, started)) => { + log::trace!( target: LOG_TARGET, - "non-existent query failed ({query_id:?})", - ), - Some((key, started)) => { - log::debug!( - target: LOG_TARGET, - "`PUT_VALUE` ({query_id:?}) failed for key {key:?}", - ); + "`GET_PROVIDERS` for {key:?} ({query_id:?}) succeeded", + ); - self.event_streams.send(Event::Dht( - DhtEvent::ValuePutFailed(key) - )); + self.event_streams.send(Event::Dht( + DhtEvent::ProvidersFound( + key.into(), + providers.into_iter().map(|p| p.peer.into()).collect() + ) + )); - if let Some(ref metrics) = self.metrics { - metrics - .kademlia_query_duration - .with_label_values(&["value-put-failed"]) - .observe(started.elapsed().as_secs_f64()); - } + if let Some(ref metrics) = self.metrics { + metrics + .kademlia_query_duration + .with_label_values(&["providers-get"]) + .observe(started.elapsed().as_secs_f64()); } + }, + query => { + log::error!( + target: LOG_TARGET, + "Missing/invalid pending query for `GET_PROVIDERS`: {query:?}" + ); + debug_assert!(false); } - Some((key, started)) => { + } + } + Some(DiscoveryEvent::QueryFailed { query_id }) => { + match self.pending_queries.remove(&query_id) { + Some(KadQuery::GetValue(key, started)) => { log::debug!( target: LOG_TARGET, "`GET_VALUE` ({query_id:?}) failed for key {key:?}", @@ -917,6 +947,46 @@ impl NetworkBackend for Litep2pNetworkBac .with_label_values(&["value-get-failed"]) .observe(started.elapsed().as_secs_f64()); } + }, + Some(KadQuery::PutValue(key, started)) => { + log::debug!( + target: LOG_TARGET, + "`PUT_VALUE` ({query_id:?}) failed for key {key:?}", + ); + + self.event_streams.send(Event::Dht( + DhtEvent::ValuePutFailed(key) + )); + + if let Some(ref metrics) = self.metrics { + metrics + .kademlia_query_duration + .with_label_values(&["value-put-failed"]) + .observe(started.elapsed().as_secs_f64()); + } + }, + Some(KadQuery::GetProviders(key, started)) => { + log::debug!( + target: LOG_TARGET, + "`GET_PROVIDERS` ({query_id:?}) failed for key {key:?}" + ); + + self.event_streams.send(Event::Dht( + DhtEvent::ProvidersNotFound(key) + )); + + if let Some(ref metrics) = self.metrics { + metrics + .kademlia_query_duration + .with_label_values(&["providers-get-failed"]) + .observe(started.elapsed().as_secs_f64()); + } + }, + None => { + log::warn!( + target: LOG_TARGET, + "non-existent query failed ({query_id:?})", + ); } } } diff --git a/substrate/client/network/src/litep2p/service.rs b/substrate/client/network/src/litep2p/service.rs index fa1d47e5a1b7..d270e90efdf5 100644 --- a/substrate/client/network/src/litep2p/service.rs +++ b/substrate/client/network/src/litep2p/service.rs @@ -104,6 +104,15 @@ pub enum NetworkServiceCommand { expires: Option, }, + /// Start providing `key`. + StartProviding { key: KademliaKey }, + + /// Stop providing `key`. + StopProviding { key: KademliaKey }, + + /// Get providers for `key`. + GetProviders { key: KademliaKey }, + /// Query network status. Status { /// `oneshot::Sender` for sending the status. @@ -296,6 +305,18 @@ impl NetworkDHTProvider for Litep2pNetworkService { expires, }); } + + fn start_providing(&self, key: KademliaKey) { + let _ = self.cmd_tx.unbounded_send(NetworkServiceCommand::StartProviding { key }); + } + + fn stop_providing(&self, key: KademliaKey) { + let _ = self.cmd_tx.unbounded_send(NetworkServiceCommand::StopProviding { key }); + } + + fn get_providers(&self, key: KademliaKey) { + let _ = self.cmd_tx.unbounded_send(NetworkServiceCommand::GetProviders { key }); + } } #[async_trait::async_trait] diff --git a/substrate/client/network/src/service.rs b/substrate/client/network/src/service.rs index 5e5e4ee28589..803b81129139 100644 --- a/substrate/client/network/src/service.rs +++ b/substrate/client/network/src/service.rs @@ -973,6 +973,18 @@ where expires, )); } + + fn start_providing(&self, key: KademliaKey) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::StartProviding(key)); + } + + fn stop_providing(&self, key: KademliaKey) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::StopProviding(key)); + } + + fn get_providers(&self, key: KademliaKey) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::GetProviders(key)); + } } #[async_trait::async_trait] @@ -1333,6 +1345,9 @@ enum ServiceToWorkerMsg { update_local_storage: bool, }, StoreRecord(KademliaKey, Vec, Option, Option), + StartProviding(KademliaKey), + StopProviding(KademliaKey), + GetProviders(KademliaKey), AddKnownAddress(PeerId, Multiaddr), EventStream(out_events::Sender), Request { @@ -1466,6 +1481,12 @@ where .network_service .behaviour_mut() .store_record(key.into(), value, publisher, expires), + ServiceToWorkerMsg::StartProviding(key) => + self.network_service.behaviour_mut().start_providing(key.into()), + ServiceToWorkerMsg::StopProviding(key) => + self.network_service.behaviour_mut().stop_providing(&key.into()), + ServiceToWorkerMsg::GetProviders(key) => + self.network_service.behaviour_mut().get_providers(key.into()), ServiceToWorkerMsg::AddKnownAddress(peer_id, addr) => self.network_service.behaviour_mut().add_known_address(peer_id, addr), ServiceToWorkerMsg::EventStream(sender) => self.event_streams.push(sender), @@ -1678,6 +1699,9 @@ where DhtEvent::ValuePut(_) => "value-put", DhtEvent::ValuePutFailed(_) => "value-put-failed", DhtEvent::PutRecordRequest(_, _, _, _) => "put-record-request", + DhtEvent::StartProvidingFailed(_) => "start-providing-failed", + DhtEvent::ProvidersFound(_, _) => "providers-found", + DhtEvent::ProvidersNotFound(_) => "providers-not-found", }; metrics .kademlia_query_duration diff --git a/substrate/client/network/src/service/traits.rs b/substrate/client/network/src/service/traits.rs index f5dd2995acb1..acfed9ea894c 100644 --- a/substrate/client/network/src/service/traits.rs +++ b/substrate/client/network/src/service/traits.rs @@ -234,6 +234,15 @@ pub trait NetworkDHTProvider { publisher: Option, expires: Option, ); + + /// Register this node as a provider for `key` on the DHT. + fn start_providing(&self, key: KademliaKey); + + /// Deregister this node as a provider for `key` on the DHT. + fn stop_providing(&self, key: KademliaKey); + + /// Start getting the list of providers for `key` on the DHT. + fn get_providers(&self, key: KademliaKey); } impl NetworkDHTProvider for Arc @@ -262,6 +271,18 @@ where ) { T::store_record(self, key, value, publisher, expires) } + + fn start_providing(&self, key: KademliaKey) { + T::start_providing(self, key) + } + + fn stop_providing(&self, key: KademliaKey) { + T::stop_providing(self, key) + } + + fn get_providers(&self, key: KademliaKey) { + T::get_providers(self, key) + } } /// Provides an ability to set a fork sync request for a particular block. From b8da8faa0a675afbed1c9ed5d524a674e93910b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 13 Dec 2024 11:31:14 +0100 Subject: [PATCH 163/340] slot-based-collator: Implement dedicated block import (#6481) The `SlotBasedBlockImport` job is to collect the storage proofs of all blocks getting imported. These storage proofs alongside the block are being forwarded to the collation task. Right now they are just being thrown away. More logic will follow later. Basically this will be required to include multiple blocks into one `PoV` which will then be done by the collation task. --------- Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Co-authored-by: GitHub Action --- Cargo.lock | 2 + cumulus/client/consensus/aura/Cargo.toml | 1 + .../src/collators/slot_based/block_import.rs | 144 ++++++++++++++++++ .../collators/slot_based/collation_task.rs | 41 +++-- .../aura/src/collators/slot_based/mod.rs | 11 +- cumulus/polkadot-omni-node/lib/Cargo.toml | 1 + .../polkadot-omni-node/lib/src/common/spec.rs | 74 +++++++-- .../lib/src/common/types.rs | 14 +- .../polkadot-omni-node/lib/src/nodes/aura.rs | 119 ++++++++++++--- .../lib/src/nodes/manual_seal.rs | 18 ++- cumulus/test/service/src/lib.rs | 20 ++- prdoc/pr_6481.prdoc | 10 ++ 12 files changed, 395 insertions(+), 60 deletions(-) create mode 100644 cumulus/client/consensus/aura/src/collators/slot_based/block_import.rs create mode 100644 prdoc/pr_6481.prdoc diff --git a/Cargo.lock b/Cargo.lock index f2379d4ee6de..d0abba9d4cc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4656,6 +4656,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-timestamp 26.0.0", + "sp-trie 29.0.0", "substrate-prometheus-endpoint", "tokio", "tracing", @@ -18135,6 +18136,7 @@ dependencies = [ "serde_json", "sp-api 26.0.0", "sp-block-builder 26.0.0", + "sp-consensus", "sp-consensus-aura 0.32.0", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 6e0c124591cb..33f24e30ccfb 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -35,6 +35,7 @@ sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-consensus-aura = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-trie = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/block_import.rs b/cumulus/client/consensus/aura/src/collators/slot_based/block_import.rs new file mode 100644 index 000000000000..9c53da6a6b7d --- /dev/null +++ b/cumulus/client/consensus/aura/src/collators/slot_based/block_import.rs @@ -0,0 +1,144 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +use futures::{stream::FusedStream, StreamExt}; +use sc_consensus::{BlockImport, StateAction}; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; +use sp_api::{ApiExt, CallApiAt, CallContext, Core, ProvideRuntimeApi, StorageProof}; +use sp_runtime::traits::{Block as BlockT, Header as _}; +use sp_trie::proof_size_extension::ProofSizeExt; +use std::sync::Arc; + +/// Handle for receiving the block and the storage proof from the [`SlotBasedBlockImport`]. +/// +/// This handle should be passed to [`Params`](super::Params) or can also be dropped if the node is +/// not running as collator. +pub struct SlotBasedBlockImportHandle { + receiver: TracingUnboundedReceiver<(Block, StorageProof)>, +} + +impl SlotBasedBlockImportHandle { + /// Returns the next item. + /// + /// The future will never return when the internal channel is closed. + pub async fn next(&mut self) -> (Block, StorageProof) { + loop { + if self.receiver.is_terminated() { + futures::pending!() + } else if let Some(res) = self.receiver.next().await { + return res + } + } + } +} + +/// Special block import for the slot based collator. +pub struct SlotBasedBlockImport { + inner: BI, + client: Arc, + sender: TracingUnboundedSender<(Block, StorageProof)>, +} + +impl SlotBasedBlockImport { + /// Create a new instance. + /// + /// The returned [`SlotBasedBlockImportHandle`] needs to be passed to the + /// [`Params`](super::Params), so that this block import instance can communicate with the + /// collation task. If the node is not running as a collator, just dropping the handle is fine. + pub fn new(inner: BI, client: Arc) -> (Self, SlotBasedBlockImportHandle) { + let (sender, receiver) = tracing_unbounded("SlotBasedBlockImportChannel", 1000); + + (Self { sender, client, inner }, SlotBasedBlockImportHandle { receiver }) + } +} + +impl Clone for SlotBasedBlockImport { + fn clone(&self) -> Self { + Self { inner: self.inner.clone(), client: self.client.clone(), sender: self.sender.clone() } + } +} + +#[async_trait::async_trait] +impl BlockImport for SlotBasedBlockImport +where + Block: BlockT, + BI: BlockImport + Send + Sync, + BI::Error: Into, + Client: ProvideRuntimeApi + CallApiAt + Send + Sync, + Client::StateBackend: Send, + Client::Api: Core, +{ + type Error = sp_consensus::Error; + + async fn check_block( + &self, + block: sc_consensus::BlockCheckParams, + ) -> Result { + self.inner.check_block(block).await.map_err(Into::into) + } + + async fn import_block( + &self, + mut params: sc_consensus::BlockImportParams, + ) -> Result { + // If the channel exists and it is required to execute the block, we will execute the block + // here. This is done to collect the storage proof and to prevent re-execution, we push + // downwards the state changes. `StateAction::ApplyChanges` is ignored, because it either + // means that the node produced the block itself or the block was imported via state sync. + if !self.sender.is_closed() && !matches!(params.state_action, StateAction::ApplyChanges(_)) + { + let mut runtime_api = self.client.runtime_api(); + + runtime_api.set_call_context(CallContext::Onchain); + + runtime_api.record_proof(); + let recorder = runtime_api + .proof_recorder() + .expect("Proof recording is enabled in the line above; qed."); + runtime_api.register_extension(ProofSizeExt::new(recorder)); + + let parent_hash = *params.header.parent_hash(); + + let block = Block::new(params.header.clone(), params.body.clone().unwrap_or_default()); + + runtime_api + .execute_block(parent_hash, block.clone()) + .map_err(|e| Box::new(e) as Box<_>)?; + + let storage_proof = + runtime_api.extract_proof().expect("Proof recording was enabled above; qed"); + + let state = self.client.state_at(parent_hash).map_err(|e| Box::new(e) as Box<_>)?; + let gen_storage_changes = runtime_api + .into_storage_changes(&state, parent_hash) + .map_err(sp_consensus::Error::ChainLookup)?; + + if params.header.state_root() != &gen_storage_changes.transaction_storage_root { + return Err(sp_consensus::Error::Other(Box::new( + sp_blockchain::Error::InvalidStateRoot, + ))) + } + + params.state_action = StateAction::ApplyChanges(sc_consensus::StorageChanges::Changes( + gen_storage_changes, + )); + + let _ = self.sender.unbounded_send((block, storage_proof)); + } + + self.inner.import_block(params).await.map_err(Into::into) + } +} diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs b/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs index 5b8151f6302c..abaeb8319a40 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/collation_task.rs @@ -47,6 +47,8 @@ pub struct Params { pub collator_service: CS, /// Receiver channel for communication with the block builder task. pub collator_receiver: TracingUnboundedReceiver>, + /// The handle from the special slot based block import. + pub block_import_handle: super::SlotBasedBlockImportHandle, } /// Asynchronously executes the collation task for a parachain. @@ -55,28 +57,49 @@ pub struct Params { /// collations to the relay chain. It listens for new best relay chain block notifications and /// handles collator messages. If our parachain is scheduled on a core and we have a candidate, /// the task will build a collation and send it to the relay chain. -pub async fn run_collation_task(mut params: Params) -where +pub async fn run_collation_task( + Params { + relay_client, + collator_key, + para_id, + reinitialize, + collator_service, + mut collator_receiver, + mut block_import_handle, + }: Params, +) where Block: BlockT, CS: CollatorServiceInterface + Send + Sync + 'static, RClient: RelayChainInterface + Clone + 'static, { - let Ok(mut overseer_handle) = params.relay_client.overseer_handle() else { + let Ok(mut overseer_handle) = relay_client.overseer_handle() else { tracing::error!(target: LOG_TARGET, "Failed to get overseer handle."); return }; cumulus_client_collator::initialize_collator_subsystems( &mut overseer_handle, - params.collator_key, - params.para_id, - params.reinitialize, + collator_key, + para_id, + reinitialize, ) .await; - let collator_service = params.collator_service; - while let Some(collator_message) = params.collator_receiver.next().await { - handle_collation_message(collator_message, &collator_service, &mut overseer_handle).await; + loop { + futures::select! { + collator_message = collator_receiver.next() => { + let Some(message) = collator_message else { + return; + }; + + handle_collation_message(message, &collator_service, &mut overseer_handle).await; + }, + block_import_msg = block_import_handle.next().fuse() => { + // TODO: Implement me. + // Issue: https://github.com/paritytech/polkadot-sdk/issues/6495 + let _ = block_import_msg; + } + } } } diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs index 18e63681d578..09afa18e6fbb 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs @@ -54,11 +54,14 @@ use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Member}; use std::{sync::Arc, time::Duration}; +pub use block_import::{SlotBasedBlockImport, SlotBasedBlockImportHandle}; + mod block_builder_task; +mod block_import; mod collation_task; /// Parameters for [`run`]. -pub struct Params { +pub struct Params { /// Inherent data providers. Only non-consensus inherent data should be provided, i.e. /// the timestamp, slot, and paras inherents should be omitted, as they are set by this /// collator. @@ -90,6 +93,8 @@ pub struct Params, /// Spawner for spawning futures. pub spawner: Spawner, } @@ -111,8 +116,9 @@ pub fn run, + }: Params, ) where Block: BlockT, Client: ProvideRuntimeApi @@ -147,6 +153,7 @@ pub fn run(collator_task_params); diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index 4d003a69456e..afbe03ada89c 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -67,6 +67,7 @@ pallet-transaction-payment = { workspace = true, default-features = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } sp-consensus-aura = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-wasm-interface = { workspace = true, default-features = true } diff --git a/cumulus/polkadot-omni-node/lib/src/common/spec.rs b/cumulus/polkadot-omni-node/lib/src/common/spec.rs index 38f0e7d72881..868368f3ca1a 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/spec.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/spec.rs @@ -44,23 +44,28 @@ use sc_transaction_pool::TransactionPoolHandle; use sp_keystore::KeystorePtr; use std::{future::Future, pin::Pin, sync::Arc, time::Duration}; -pub(crate) trait BuildImportQueue { +pub(crate) trait BuildImportQueue< + Block: BlockT, + RuntimeApi, + BlockImport: sc_consensus::BlockImport, +> +{ fn build_import_queue( client: Arc>, - block_import: ParachainBlockImport, + block_import: ParachainBlockImport, config: &Configuration, telemetry_handle: Option, task_manager: &TaskManager, ) -> sc_service::error::Result>; } -pub(crate) trait StartConsensus +pub(crate) trait StartConsensus where RuntimeApi: ConstructNodeRuntimeApi>, { fn start_consensus( client: Arc>, - block_import: ParachainBlockImport, + block_import: ParachainBlockImport, prometheus_registry: Option<&Registry>, telemetry: Option, task_manager: &TaskManager, @@ -74,6 +79,7 @@ where announce_block: Arc>) + Send + Sync>, backend: Arc>, node_extra_args: NodeExtraArgs, + block_import_extra_return_value: BIAuxiliaryData, ) -> Result<(), sc_service::Error>; } @@ -92,6 +98,31 @@ fn warn_if_slow_hardware(hwbench: &sc_sysinfo::HwBench) { } } +pub(crate) trait InitBlockImport { + type BlockImport: sc_consensus::BlockImport + Clone + Send + Sync; + type BlockImportAuxiliaryData; + + fn init_block_import( + client: Arc>, + ) -> sc_service::error::Result<(Self::BlockImport, Self::BlockImportAuxiliaryData)>; +} + +pub(crate) struct ClientBlockImport; + +impl InitBlockImport for ClientBlockImport +where + RuntimeApi: Send + ConstructNodeRuntimeApi>, +{ + type BlockImport = Arc>; + type BlockImportAuxiliaryData = (); + + fn init_block_import( + client: Arc>, + ) -> sc_service::error::Result<(Self::BlockImport, Self::BlockImportAuxiliaryData)> { + Ok((client.clone(), ())) + } +} + pub(crate) trait BaseNodeSpec { type Block: NodeBlock; @@ -100,7 +131,13 @@ pub(crate) trait BaseNodeSpec { ParachainClient, >; - type BuildImportQueue: BuildImportQueue; + type BuildImportQueue: BuildImportQueue< + Self::Block, + Self::RuntimeApi, + >::BlockImport, + >; + + type InitBlockImport: self::InitBlockImport; /// Starts a `ServiceBuilder` for a full service. /// @@ -108,7 +145,14 @@ pub(crate) trait BaseNodeSpec { /// be able to perform chain operations. fn new_partial( config: &Configuration, - ) -> sc_service::error::Result> { + ) -> sc_service::error::Result< + ParachainService< + Self::Block, + Self::RuntimeApi, + >::BlockImport, + >::BlockImportAuxiliaryData + > + >{ let telemetry = config .telemetry_endpoints .clone() @@ -160,7 +204,10 @@ pub(crate) trait BaseNodeSpec { .build(), ); - let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); + let (block_import, block_import_auxiliary_data) = + Self::InitBlockImport::init_block_import(client.clone())?; + + let block_import = ParachainBlockImport::new(block_import, backend.clone()); let import_queue = Self::BuildImportQueue::build_import_queue( client.clone(), @@ -178,7 +225,7 @@ pub(crate) trait BaseNodeSpec { task_manager, transaction_pool, select_chain: (), - other: (block_import, telemetry, telemetry_worker_handle), + other: (block_import, telemetry, telemetry_worker_handle, block_import_auxiliary_data), }) } } @@ -190,7 +237,12 @@ pub(crate) trait NodeSpec: BaseNodeSpec { TransactionPoolHandle>, >; - type StartConsensus: StartConsensus; + type StartConsensus: StartConsensus< + Self::Block, + Self::RuntimeApi, + >::BlockImport, + >::BlockImportAuxiliaryData, + >; const SYBIL_RESISTANCE: CollatorSybilResistance; @@ -212,7 +264,8 @@ pub(crate) trait NodeSpec: BaseNodeSpec { let parachain_config = prepare_node_config(parachain_config); let params = Self::new_partial(¶chain_config)?; - let (block_import, mut telemetry, telemetry_worker_handle) = params.other; + let (block_import, mut telemetry, telemetry_worker_handle, block_import_auxiliary_data) = + params.other; let client = params.client.clone(); let backend = params.backend.clone(); let mut task_manager = params.task_manager; @@ -340,6 +393,7 @@ pub(crate) trait NodeSpec: BaseNodeSpec { announce_block, backend.clone(), node_extra_args, + block_import_auxiliary_data, )?; } diff --git a/cumulus/polkadot-omni-node/lib/src/common/types.rs b/cumulus/polkadot-omni-node/lib/src/common/types.rs index 4bc58dc9db7e..978368be2584 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/types.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/types.rs @@ -22,7 +22,6 @@ use sc_service::{PartialComponents, TFullBackend, TFullClient}; use sc_telemetry::{Telemetry, TelemetryWorkerHandle}; use sc_transaction_pool::TransactionPoolHandle; use sp_runtime::{generic, traits::BlakeTwo256}; -use std::sync::Arc; pub use parachains_common::{AccountId, Balance, Hash, Nonce}; @@ -42,15 +41,20 @@ pub type ParachainClient = pub type ParachainBackend = TFullBackend; -pub type ParachainBlockImport = - TParachainBlockImport>, ParachainBackend>; +pub type ParachainBlockImport = + TParachainBlockImport>; /// Assembly of PartialComponents (enough to run chain ops subcommands) -pub type ParachainService = PartialComponents< +pub type ParachainService = PartialComponents< ParachainClient, ParachainBackend, (), DefaultImportQueue, TransactionPoolHandle>, - (ParachainBlockImport, Option, Option), + ( + ParachainBlockImport, + Option, + Option, + BIExtraReturnValue, + ), >; diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs index 0b2c230f695d..816f76117a26 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs @@ -18,7 +18,10 @@ use crate::{ common::{ aura::{AuraIdT, AuraRuntimeApi}, rpc::BuildParachainRpcExtensions, - spec::{BaseNodeSpec, BuildImportQueue, NodeSpec, StartConsensus}, + spec::{ + BaseNodeSpec, BuildImportQueue, ClientBlockImport, InitBlockImport, NodeSpec, + StartConsensus, + }, types::{ AccountId, Balance, Hash, Nonce, ParachainBackend, ParachainBlockImport, ParachainClient, @@ -30,11 +33,14 @@ use crate::{ use cumulus_client_collator::service::{ CollatorService, ServiceInterface as CollatorServiceInterface, }; -use cumulus_client_consensus_aura::collators::lookahead::{self as aura, Params as AuraParams}; #[docify::export(slot_based_colator_import)] use cumulus_client_consensus_aura::collators::slot_based::{ self as slot_based, Params as SlotBasedParams, }; +use cumulus_client_consensus_aura::collators::{ + lookahead::{self as aura, Params as AuraParams}, + slot_based::{SlotBasedBlockImport, SlotBasedBlockImportHandle}, +}; use cumulus_client_consensus_proposer::{Proposer, ProposerInterface}; use cumulus_client_consensus_relay_chain::Verifier as RelayChainVerifier; #[allow(deprecated)] @@ -91,20 +97,23 @@ where /// Build the import queue for parachain runtimes that started with relay chain consensus and /// switched to aura. -pub(crate) struct BuildRelayToAuraImportQueue( - PhantomData<(Block, RuntimeApi, AuraId)>, +pub(crate) struct BuildRelayToAuraImportQueue( + PhantomData<(Block, RuntimeApi, AuraId, BlockImport)>, ); -impl BuildImportQueue - for BuildRelayToAuraImportQueue +impl + BuildImportQueue + for BuildRelayToAuraImportQueue where RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi, AuraId: AuraIdT + Sync, + BlockImport: + sc_consensus::BlockImport + Send + Sync + 'static, { fn build_import_queue( client: Arc>, - block_import: ParachainBlockImport, + block_import: ParachainBlockImport, config: &Configuration, telemetry_handle: Option, task_manager: &TaskManager, @@ -159,20 +168,20 @@ where /// Uses the lookahead collator to support async backing. /// /// Start an aura powered parachain node. Some system chains use this. -pub(crate) struct AuraNode( - pub PhantomData<(Block, RuntimeApi, AuraId, StartConsensus)>, +pub(crate) struct AuraNode( + pub PhantomData<(Block, RuntimeApi, AuraId, StartConsensus, InitBlockImport)>, ); -impl Default - for AuraNode +impl Default + for AuraNode { fn default() -> Self { Self(Default::default()) } } -impl BaseNodeSpec - for AuraNode +impl BaseNodeSpec + for AuraNode where Block: NodeBlock, RuntimeApi: ConstructNodeRuntimeApi>, @@ -180,14 +189,19 @@ where + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + substrate_frame_rpc_system::AccountNonceApi, AuraId: AuraIdT + Sync, + InitBlockImport: self::InitBlockImport + Send, + InitBlockImport::BlockImport: + sc_consensus::BlockImport + 'static, { type Block = Block; type RuntimeApi = RuntimeApi; - type BuildImportQueue = BuildRelayToAuraImportQueue; + type BuildImportQueue = + BuildRelayToAuraImportQueue; + type InitBlockImport = InitBlockImport; } -impl NodeSpec - for AuraNode +impl NodeSpec + for AuraNode where Block: NodeBlock, RuntimeApi: ConstructNodeRuntimeApi>, @@ -195,7 +209,15 @@ where + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + substrate_frame_rpc_system::AccountNonceApi, AuraId: AuraIdT + Sync, - StartConsensus: self::StartConsensus + 'static, + StartConsensus: self::StartConsensus< + Block, + RuntimeApi, + InitBlockImport::BlockImport, + InitBlockImport::BlockImportAuxiliaryData, + > + 'static, + InitBlockImport: self::InitBlockImport + Send, + InitBlockImport::BlockImport: + sc_consensus::BlockImport + 'static, { type BuildRpcExtensions = BuildParachainRpcExtensions; type StartConsensus = StartConsensus; @@ -219,6 +241,7 @@ where RuntimeApi, AuraId, StartSlotBasedAuraConsensus, + StartSlotBasedAuraConsensus, >::default()) } else { Box::new(AuraNode::< @@ -226,6 +249,7 @@ where RuntimeApi, AuraId, StartLookaheadAuraConsensus, + ClientBlockImport, >::default()) } } @@ -245,7 +269,15 @@ where #[docify::export_content] fn launch_slot_based_collator( params: SlotBasedParams< - ParachainBlockImport, + Block, + ParachainBlockImport< + Block, + SlotBasedBlockImport< + Block, + Arc>, + ParachainClient, + >, + >, CIDP, ParachainClient, ParachainBackend, @@ -267,8 +299,17 @@ where } } -impl, RuntimeApi, AuraId> StartConsensus - for StartSlotBasedAuraConsensus +impl, RuntimeApi, AuraId> + StartConsensus< + Block, + RuntimeApi, + SlotBasedBlockImport< + Block, + Arc>, + ParachainClient, + >, + SlotBasedBlockImportHandle, + > for StartSlotBasedAuraConsensus where RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi, @@ -276,7 +317,14 @@ where { fn start_consensus( client: Arc>, - block_import: ParachainBlockImport, + block_import: ParachainBlockImport< + Block, + SlotBasedBlockImport< + Block, + Arc>, + ParachainClient, + >, + >, prometheus_registry: Option<&Registry>, telemetry: Option, task_manager: &TaskManager, @@ -290,6 +338,7 @@ where announce_block: Arc>) + Send + Sync>, backend: Arc>, _node_extra_args: NodeExtraArgs, + block_import_handle: SlotBasedBlockImportHandle, ) -> Result<(), Error> { let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), @@ -325,6 +374,7 @@ where authoring_duration: Duration::from_millis(2000), reinitialize: false, slot_drift: Duration::from_secs(1), + block_import_handle, spawner: task_manager.spawn_handle(), }; @@ -336,6 +386,27 @@ where } } +impl, RuntimeApi, AuraId> InitBlockImport + for StartSlotBasedAuraConsensus +where + RuntimeApi: ConstructNodeRuntimeApi>, + RuntimeApi::RuntimeApi: AuraRuntimeApi, + AuraId: AuraIdT + Sync, +{ + type BlockImport = SlotBasedBlockImport< + Block, + Arc>, + ParachainClient, + >; + type BlockImportAuxiliaryData = SlotBasedBlockImportHandle; + + fn init_block_import( + client: Arc>, + ) -> sc_service::error::Result<(Self::BlockImport, Self::BlockImportAuxiliaryData)> { + Ok(SlotBasedBlockImport::new(client.clone(), client)) + } +} + /// Wait for the Aura runtime API to appear on chain. /// This is useful for chains that started out without Aura. Components that /// are depending on Aura functionality will wait until Aura appears in the runtime. @@ -364,7 +435,8 @@ pub(crate) struct StartLookaheadAuraConsensus( PhantomData<(Block, RuntimeApi, AuraId)>, ); -impl, RuntimeApi, AuraId> StartConsensus +impl, RuntimeApi, AuraId> + StartConsensus>, ()> for StartLookaheadAuraConsensus where RuntimeApi: ConstructNodeRuntimeApi>, @@ -373,7 +445,7 @@ where { fn start_consensus( client: Arc>, - block_import: ParachainBlockImport, + block_import: ParachainBlockImport>>, prometheus_registry: Option<&Registry>, telemetry: Option, task_manager: &TaskManager, @@ -387,6 +459,7 @@ where announce_block: Arc>) + Send + Sync>, backend: Arc>, node_extra_args: NodeExtraArgs, + _: (), ) -> Result<(), Error> { let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs b/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs index 7e36ce735af3..8b7921da30c0 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs @@ -16,7 +16,7 @@ use crate::common::{ rpc::BuildRpcExtensions as BuildRpcExtensionsT, - spec::{BaseNodeSpec, BuildImportQueue, NodeSpec as NodeSpecT}, + spec::{BaseNodeSpec, BuildImportQueue, ClientBlockImport, NodeSpec as NodeSpecT}, types::{Hash, ParachainBlockImport, ParachainClient}, }; use codec::Encode; @@ -32,12 +32,19 @@ use std::{marker::PhantomData, sync::Arc}; pub struct ManualSealNode(PhantomData); -impl BuildImportQueue - for ManualSealNode +impl + BuildImportQueue< + NodeSpec::Block, + NodeSpec::RuntimeApi, + Arc>, + > for ManualSealNode { fn build_import_queue( client: Arc>, - _block_import: ParachainBlockImport, + _block_import: ParachainBlockImport< + NodeSpec::Block, + Arc>, + >, config: &Configuration, _telemetry_handle: Option, task_manager: &TaskManager, @@ -54,6 +61,7 @@ impl BaseNodeSpec for ManualSealNode { type Block = NodeSpec::Block; type RuntimeApi = NodeSpec::RuntimeApi; type BuildImportQueue = Self; + type InitBlockImport = ClientBlockImport; } impl ManualSealNode { @@ -78,7 +86,7 @@ impl ManualSealNode { keystore_container, select_chain: _, transaction_pool, - other: (_, mut telemetry, _), + other: (_, mut telemetry, _, _), } = Self::new_partial(&config)?; let select_chain = LongestChain::new(backend.clone()); diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index f01da9becef1..2c13d20333a7 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -27,7 +27,10 @@ use cumulus_client_collator::service::CollatorService; use cumulus_client_consensus_aura::{ collators::{ lookahead::{self as aura, Params as AuraParams}, - slot_based::{self as slot_based, Params as SlotBasedParams}, + slot_based::{ + self as slot_based, Params as SlotBasedParams, SlotBasedBlockImport, + SlotBasedBlockImportHandle, + }, }, ImportQueueParams, }; @@ -131,7 +134,8 @@ pub type Client = TFullClient; /// The block-import type being used by the test service. -pub type ParachainBlockImport = TParachainBlockImport, Backend>; +pub type ParachainBlockImport = + TParachainBlockImport, Client>, Backend>; /// Transaction pool type used by the test service pub type TransactionPool = Arc>; @@ -184,7 +188,7 @@ pub type Service = PartialComponents< (), sc_consensus::import_queue::BasicQueue, sc_transaction_pool::TransactionPoolHandle, - ParachainBlockImport, + (ParachainBlockImport, SlotBasedBlockImportHandle), >; /// Starts a `ServiceBuilder` for a full service. @@ -217,7 +221,9 @@ pub fn new_partial( )?; let client = Arc::new(client); - let block_import = ParachainBlockImport::new(client.clone(), backend.clone()); + let (block_import, slot_based_handle) = + SlotBasedBlockImport::new(client.clone(), client.clone()); + let block_import = ParachainBlockImport::new(block_import, backend.clone()); let transaction_pool = Arc::from( sc_transaction_pool::Builder::new( @@ -260,7 +266,7 @@ pub fn new_partial( task_manager, transaction_pool, select_chain: (), - other: block_import, + other: (block_import, slot_based_handle), }; Ok(params) @@ -349,7 +355,8 @@ where let client = params.client.clone(); let backend = params.backend.clone(); - let block_import = params.other; + let block_import = params.other.0; + let slot_based_handle = params.other.1; let relay_chain_interface = build_relay_chain_interface( relay_chain_config, parachain_config.prometheus_registry(), @@ -497,6 +504,7 @@ where authoring_duration: Duration::from_millis(2000), reinitialize: false, slot_drift: Duration::from_secs(1), + block_import_handle: slot_based_handle, spawner: task_manager.spawn_handle(), }; diff --git a/prdoc/pr_6481.prdoc b/prdoc/pr_6481.prdoc new file mode 100644 index 000000000000..83ba0a32eb24 --- /dev/null +++ b/prdoc/pr_6481.prdoc @@ -0,0 +1,10 @@ +title: 'slot-based-collator: Implement dedicated block import' +doc: +- audience: Node Dev + description: |- + The `SlotBasedBlockImport` job is to collect the storage proofs of all blocks getting imported. These storage proofs alongside the block are being forwarded to the collation task. Right now they are just being thrown away. More logic will follow later. Basically this will be required to include multiple blocks into one `PoV` which will then be done by the collation task. +crates: +- name: cumulus-client-consensus-aura + bump: major +- name: polkadot-omni-node-lib + bump: major From 2dd2bb5a847d7bf006493d9fe9b10a13289118b6 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Fri, 13 Dec 2024 14:33:47 +0200 Subject: [PATCH 164/340] Fix approval-voting canonicalize off by one (#6864) Approval voting canonicalize is off by one that means if we are finalizing blocks one by one, approval-voting cleans it up every other block for example: - With 1, 2, 3, 4, 5, 6 blocks created, the stored range would be StoredBlockRange(1,7) - When block 3 is finalized the canonicalize works and StoredBlockRange is (4,7) - When block 4 is finalized the canonicalize exists early because of the `if range.0 > canon_number` break clause, so blocks are not cleaned up. - When block 5 is finalized the canonicalize works and StoredBlockRange becomes (6,7) and both block 4 and 5 are cleaned up. The consequences of this is that sometimes we keep block entries around after they are finalized, so at restart we consider this blocks and send them to approval-distribution. In most cases this is not a problem, but in the case when finality is lagging on restart approval-distribution will receive 4 as being the oldest block it needs to work on, and since BlockFinalized is never resent for block 4 after restart it won't get the opportunity to clean that up. Therefore it will end running approval-distribution aggression on block 4, because that is the oldest block it received from approval-voting for which it did not see a BlockFinalized signal. --------- Signed-off-by: Alexandru Gheorghe --- .../src/approval_db/v3/tests.rs | 52 +++++++++++++++++-- polkadot/node/core/approval-voting/src/ops.rs | 2 +- prdoc/pr_6864.prdoc | 18 +++++++ 3 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_6864.prdoc diff --git a/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs b/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs index 372dd49803cb..69278868fa3d 100644 --- a/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs +++ b/polkadot/node/core/approval-voting/src/approval_db/v3/tests.rs @@ -264,8 +264,8 @@ fn add_block_entry_adds_child() { fn canonicalize_works() { let (mut db, store) = make_db(); - // -> B1 -> C1 -> D1 - // A -> B2 -> C2 -> D2 + // -> B1 -> C1 -> D1 -> E1 + // A -> B2 -> C2 -> D2 -> E2 // // We'll canonicalize C1. Everything except D1 should disappear. // @@ -293,18 +293,22 @@ fn canonicalize_works() { let block_hash_c2 = Hash::repeat_byte(5); let block_hash_d1 = Hash::repeat_byte(6); let block_hash_d2 = Hash::repeat_byte(7); + let block_hash_e1 = Hash::repeat_byte(8); + let block_hash_e2 = Hash::repeat_byte(9); let candidate_receipt_genesis = make_candidate(ParaId::from(1_u32), genesis); let candidate_receipt_a = make_candidate(ParaId::from(2_u32), block_hash_a); let candidate_receipt_b = make_candidate(ParaId::from(3_u32), block_hash_a); let candidate_receipt_b1 = make_candidate(ParaId::from(4_u32), block_hash_b1); let candidate_receipt_c1 = make_candidate(ParaId::from(5_u32), block_hash_c1); + let candidate_receipt_e1 = make_candidate(ParaId::from(6_u32), block_hash_e1); let cand_hash_1 = candidate_receipt_genesis.hash(); let cand_hash_2 = candidate_receipt_a.hash(); let cand_hash_3 = candidate_receipt_b.hash(); let cand_hash_4 = candidate_receipt_b1.hash(); let cand_hash_5 = candidate_receipt_c1.hash(); + let cand_hash_6 = candidate_receipt_e1.hash(); let block_entry_a = make_block_entry(block_hash_a, genesis, 1, Vec::new()); let block_entry_b1 = make_block_entry(block_hash_b1, block_hash_a, 2, Vec::new()); @@ -326,6 +330,12 @@ fn canonicalize_works() { let block_entry_d2 = make_block_entry(block_hash_d2, block_hash_c2, 4, vec![(CoreIndex(0), cand_hash_5)]); + let block_entry_e1 = + make_block_entry(block_hash_e1, block_hash_d1, 5, vec![(CoreIndex(0), cand_hash_6)]); + + let block_entry_e2 = + make_block_entry(block_hash_e2, block_hash_d2, 5, vec![(CoreIndex(0), cand_hash_6)]); + let candidate_info = { let mut candidate_info = HashMap::new(); candidate_info.insert( @@ -345,6 +355,8 @@ fn canonicalize_works() { candidate_info .insert(cand_hash_5, NewCandidateInfo::new(candidate_receipt_c1, GroupIndex(5), None)); + candidate_info + .insert(cand_hash_6, NewCandidateInfo::new(candidate_receipt_e1, GroupIndex(6), None)); candidate_info }; @@ -357,6 +369,8 @@ fn canonicalize_works() { block_entry_c2.clone(), block_entry_d1.clone(), block_entry_d2.clone(), + block_entry_e1.clone(), + block_entry_e2.clone(), ]; let mut overlay_db = OverlayedBackend::new(&db); @@ -438,7 +452,7 @@ fn canonicalize_works() { assert_eq!( load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap().unwrap(), - StoredBlockRange(4, 5) + StoredBlockRange(4, 6) ); check_candidates_in_store(vec![ @@ -447,6 +461,7 @@ fn canonicalize_works() { (cand_hash_3, Some(vec![block_hash_d1])), (cand_hash_4, Some(vec![block_hash_d1])), (cand_hash_5, None), + (cand_hash_6, Some(vec![block_hash_e1])), ]); check_blocks_in_store(vec![ @@ -456,6 +471,37 @@ fn canonicalize_works() { (block_hash_c1, None), (block_hash_c2, None), (block_hash_d1, Some(vec![cand_hash_3, cand_hash_4])), + (block_hash_e1, Some(vec![cand_hash_6])), + (block_hash_d2, None), + ]); + + let mut overlay_db = OverlayedBackend::new(&db); + canonicalize(&mut overlay_db, 4, block_hash_d1).unwrap(); + let write_ops = overlay_db.into_write_ops(); + db.write(write_ops).unwrap(); + + assert_eq!( + load_stored_blocks(store.as_ref(), &TEST_CONFIG).unwrap().unwrap(), + StoredBlockRange(5, 6) + ); + + check_candidates_in_store(vec![ + (cand_hash_1, None), + (cand_hash_2, None), + (cand_hash_3, None), + (cand_hash_4, None), + (cand_hash_5, None), + (cand_hash_6, Some(vec![block_hash_e1])), + ]); + + check_blocks_in_store(vec![ + (block_hash_a, None), + (block_hash_b1, None), + (block_hash_b2, None), + (block_hash_c1, None), + (block_hash_c2, None), + (block_hash_d1, None), + (block_hash_e1, Some(vec![cand_hash_6])), (block_hash_d2, None), ]); } diff --git a/polkadot/node/core/approval-voting/src/ops.rs b/polkadot/node/core/approval-voting/src/ops.rs index f105580009fa..efdc8780da62 100644 --- a/polkadot/node/core/approval-voting/src/ops.rs +++ b/polkadot/node/core/approval-voting/src/ops.rs @@ -90,7 +90,7 @@ pub fn canonicalize( ) -> SubsystemResult<()> { let range = match overlay_db.load_stored_blocks()? { None => return Ok(()), - Some(range) if range.0 >= canon_number => return Ok(()), + Some(range) if range.0 > canon_number => return Ok(()), Some(range) => range, }; diff --git a/prdoc/pr_6864.prdoc b/prdoc/pr_6864.prdoc new file mode 100644 index 000000000000..6d6c84e22da4 --- /dev/null +++ b/prdoc/pr_6864.prdoc @@ -0,0 +1,18 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Fix approval-voting canonicalize off by one + +doc: + - audience: Node Dev + description: | + The approval-voting canonicalize was off by one, which lead to blocks being + cleaned up every other 2 blocks. Normally, this is not an issue, but on restart + we might end up sending NewBlocks to approval-distribution with finalized blocks. + This would be problematic in the case were finalization was already lagging before + restart, so after restart approval-distribution will trigger aggression on the wrong + already finalized block. + +crates: + - name: polkadot-node-core-approval-voting + bump: minor From 6d92ded53a3fceac22a67f07b81a2411628d2374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 13 Dec 2024 13:58:38 +0100 Subject: [PATCH 165/340] Update merkleized-metadata to 0.2.0 (#6863) 0.1.2 was yanked as it was breaking semver. --------- Co-authored-by: command-bot <> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- prdoc/pr_6863.prdoc | 9 +++++++++ 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_6863.prdoc diff --git a/Cargo.lock b/Cargo.lock index d0abba9d4cc5..43a7880362b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10722,9 +10722,9 @@ dependencies = [ [[package]] name = "merkleized-metadata" -version = "0.1.2" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943f6d92804ed0100803d51fa9b21fd9432b5d122ba4c713dc26fe6d2f619cf6" +checksum = "38c592efaf1b3250df14c8f3c2d952233f0302bb81d3586db2f303666c1cd607" dependencies = [ "array-bytes", "blake3", diff --git a/Cargo.toml b/Cargo.toml index 98ab6551c802..63f17efb98b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -854,7 +854,7 @@ macro_magic = { version = "0.5.1" } maplit = { version = "1.0.2" } memmap2 = { version = "0.9.3" } memory-db = { version = "0.32.0", default-features = false } -merkleized-metadata = { version = "0.1.2" } +merkleized-metadata = { version = "0.2.0" } merlin = { version = "3.0", default-features = false } messages-relay = { path = "bridges/relays/messages" } metered = { version = "0.6.1", default-features = false, package = "prioritized-metered-channel" } diff --git a/prdoc/pr_6863.prdoc b/prdoc/pr_6863.prdoc new file mode 100644 index 000000000000..0dd416e5e438 --- /dev/null +++ b/prdoc/pr_6863.prdoc @@ -0,0 +1,9 @@ +title: Update merkleized-metadata to 0.2.0 +doc: +- audience: Node Dev + description: |- + 0.1.2 was yanked as it was breaking semver. +crates: + - name: substrate-wasm-builder + bump: patch + validate: false From 482bf08290c2ff2812eb1235f551740f0e6576df Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Fri, 13 Dec 2024 10:07:58 -0500 Subject: [PATCH 166/340] Only one ParaId variable in the Parachain Template (#6744) Many problems can occur when building and testing a Parachain caused by misconfiguring the paraid. This can happen when there are 3 different places you need to update! This PR makes it so a SINGLE location is the source of truth for the ParaId. --- templates/parachain/node/src/chain_spec.rs | 14 ++++---------- .../runtime/src/genesis_config_presets.rs | 4 ++-- templates/parachain/runtime/src/lib.rs | 1 + 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/templates/parachain/node/src/chain_spec.rs b/templates/parachain/node/src/chain_spec.rs index 7ae3c4900e42..d4b3a41b8969 100644 --- a/templates/parachain/node/src/chain_spec.rs +++ b/templates/parachain/node/src/chain_spec.rs @@ -7,6 +7,8 @@ use serde::{Deserialize, Serialize}; /// Specialized `ChainSpec` for the normal parachain runtime. pub type ChainSpec = sc_service::GenericChainSpec; +/// The relay chain that you want to configure this parachain to connect to. +pub const RELAY_CHAIN: &str = "rococo-local"; /// The extensions for the [`ChainSpec`]. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension)] @@ -35,11 +37,7 @@ pub fn development_chain_spec() -> ChainSpec { ChainSpec::builder( runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), - Extensions { - relay_chain: "rococo-local".into(), - // You MUST set this to the correct network! - para_id: 1000, - }, + Extensions { relay_chain: RELAY_CHAIN.into(), para_id: runtime::PARACHAIN_ID }, ) .with_name("Development") .with_id("dev") @@ -59,11 +57,7 @@ pub fn local_chain_spec() -> ChainSpec { #[allow(deprecated)] ChainSpec::builder( runtime::WASM_BINARY.expect("WASM binary was not built, please build it!"), - Extensions { - relay_chain: "rococo-local".into(), - // You MUST set this to the correct network! - para_id: 1000, - }, + Extensions { relay_chain: RELAY_CHAIN.into(), para_id: runtime::PARACHAIN_ID }, ) .with_name("Local Testnet") .with_id("local_testnet") diff --git a/templates/parachain/runtime/src/genesis_config_presets.rs b/templates/parachain/runtime/src/genesis_config_presets.rs index aa1ff7895eb8..f1b24e437247 100644 --- a/templates/parachain/runtime/src/genesis_config_presets.rs +++ b/templates/parachain/runtime/src/genesis_config_presets.rs @@ -16,8 +16,8 @@ use sp_keyring::Sr25519Keyring; /// The default XCM version to set in genesis config. const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; -/// Parachain id used for gensis config presets of parachain template. -const PARACHAIN_ID: u32 = 1000; +/// Parachain id used for genesis config presets of parachain template. +pub const PARACHAIN_ID: u32 = 1000; /// Generate the session keys from individual elements. /// diff --git a/templates/parachain/runtime/src/lib.rs b/templates/parachain/runtime/src/lib.rs index 43e76dba0591..9669237af785 100644 --- a/templates/parachain/runtime/src/lib.rs +++ b/templates/parachain/runtime/src/lib.rs @@ -33,6 +33,7 @@ use frame_support::weights::{ constants::WEIGHT_REF_TIME_PER_SECOND, Weight, WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, }; +pub use genesis_config_presets::PARACHAIN_ID; pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; pub use sp_runtime::{MultiAddress, Perbill, Permill}; From ec69b612bfa082ada07ceb7d8115e07f943f6815 Mon Sep 17 00:00:00 2001 From: davidk-pt Date: Sat, 14 Dec 2024 01:05:14 +0200 Subject: [PATCH 167/340] Add `unstable-api` feature flag to `pallet-revive` (#6866) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow up refactor to https://github.com/paritytech/polkadot-sdk/pull/6844#pullrequestreview-2497225717 I still need to finish adding `#[cfg(feature = "unstable-api")]` to the rest of the tests and make sure all tests pass, I want to make sure I'm moving into right direction first @athei @xermicus --------- Co-authored-by: DavidK Co-authored-by: Alexander Theißen --- prdoc/pr_6866.prdoc | 13 + .../frame/revive/fixtures/build/_Cargo.toml | 2 +- substrate/frame/revive/src/wasm/runtime.rs | 620 +++++++++--------- substrate/frame/revive/uapi/Cargo.toml | 1 + substrate/frame/revive/uapi/src/host.rs | 527 ++++++++------- .../frame/revive/uapi/src/host/riscv64.rs | 260 ++++---- 6 files changed, 747 insertions(+), 676 deletions(-) create mode 100644 prdoc/pr_6866.prdoc diff --git a/prdoc/pr_6866.prdoc b/prdoc/pr_6866.prdoc new file mode 100644 index 000000000000..fac40dc103d7 --- /dev/null +++ b/prdoc/pr_6866.prdoc @@ -0,0 +1,13 @@ +title: Refactor `pallet-revive-uapi` pallet +doc: +- audience: Runtime Dev + description: Puts unstable host functions in `uapi` under + `unstable-api` feature while moving those functions after + stable functions. +crates: +- name: pallet-revive + bump: patch +- name: pallet-revive-fixtures + bump: patch +- name: pallet-revive-uapi + bump: major diff --git a/substrate/frame/revive/fixtures/build/_Cargo.toml b/substrate/frame/revive/fixtures/build/_Cargo.toml index beaabd83403e..8dc38e14c14b 100644 --- a/substrate/frame/revive/fixtures/build/_Cargo.toml +++ b/substrate/frame/revive/fixtures/build/_Cargo.toml @@ -12,7 +12,7 @@ edition = "2021" # All paths are injected dynamically by the build script. [dependencies] -uapi = { package = 'pallet-revive-uapi', path = "", default-features = false } +uapi = { package = 'pallet-revive-uapi', path = "", features = ["unstable-api"], default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } polkavm-derive = { version = "0.17.0" } diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index ac499171c772..648a1621c198 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -1149,19 +1149,6 @@ pub mod env { self.set_storage(memory, flags, key_ptr, key_len, value_ptr, value_len) } - /// Clear the value at the given key in the contract storage. - /// See [`pallet_revive_uapi::HostFn::clear_storage`] - #[mutating] - fn clear_storage( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - key_len: u32, - ) -> Result { - self.clear_storage(memory, flags, key_ptr, key_len) - } - /// Retrieve the value under the given key from storage. /// See [`pallet_revive_uapi::HostFn::get_storage`] #[stable] @@ -1177,33 +1164,6 @@ pub mod env { self.get_storage(memory, flags, key_ptr, key_len, out_ptr, out_len_ptr) } - /// Checks whether there is a value stored under the given key. - /// See [`pallet_revive_uapi::HostFn::contains_storage`] - fn contains_storage( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - key_len: u32, - ) -> Result { - self.contains_storage(memory, flags, key_ptr, key_len) - } - - /// Retrieve and remove the value under the given key from storage. - /// See [`pallet_revive_uapi::HostFn::take_storage`] - #[mutating] - fn take_storage( - &mut self, - memory: &mut M, - flags: u32, - key_ptr: u32, - key_len: u32, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result { - self.take_storage(memory, flags, key_ptr, key_len, out_ptr, out_len_ptr) - } - /// Make a call to another contract. /// See [`pallet_revive_uapi::HostFn::call`]. #[stable] @@ -1315,13 +1275,6 @@ pub mod env { )?) } - /// Remove the calling account and transfer remaining **free** balance. - /// See [`pallet_revive_uapi::HostFn::terminate`]. - #[mutating] - fn terminate(&mut self, memory: &mut M, beneficiary_ptr: u32) -> Result<(), TrapReason> { - self.terminate(memory, beneficiary_ptr) - } - /// Stores the input passed by the caller into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::input`]. #[stable] @@ -1412,14 +1365,6 @@ pub mod env { )?) } - /// Checks whether a specified address belongs to a contract. - /// See [`pallet_revive_uapi::HostFn::is_contract`]. - fn is_contract(&mut self, memory: &mut M, account_ptr: u32) -> Result { - self.charge_gas(RuntimeCosts::IsContract)?; - let address = memory.read_h160(account_ptr)?; - Ok(self.ext.is_contract(&address) as u32) - } - /// Retrieve the code hash for a specified contract address. /// See [`pallet_revive_uapi::HostFn::code_hash`]. #[stable] @@ -1450,34 +1395,6 @@ pub mod env { )?) } - /// Retrieve the code hash of the currently executing contract. - /// See [`pallet_revive_uapi::HostFn::own_code_hash`]. - fn own_code_hash(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::OwnCodeHash)?; - let code_hash = *self.ext.own_code_hash(); - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - code_hash.as_bytes(), - false, - already_charged, - )?) - } - - /// Checks whether the caller of the current contract is the origin of the whole call stack. - /// See [`pallet_revive_uapi::HostFn::caller_is_origin`]. - fn caller_is_origin(&mut self, _memory: &mut M) -> Result { - self.charge_gas(RuntimeCosts::CallerIsOrigin)?; - Ok(self.ext.caller_is_origin() as u32) - } - - /// Checks whether the caller of the current contract is root. - /// See [`pallet_revive_uapi::HostFn::caller_is_root`]. - fn caller_is_root(&mut self, _memory: &mut M) -> Result { - self.charge_gas(RuntimeCosts::CallerIsRoot)?; - Ok(self.ext.caller_is_root() as u32) - } - /// Stores the address of the current contract into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::address`]. #[stable] @@ -1514,26 +1431,6 @@ pub mod env { )?) } - /// Stores the amount of weight left into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::weight_left`]. - fn weight_left( - &mut self, - memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::WeightLeft)?; - let gas_left = &self.ext.gas_meter().gas_left().encode(); - Ok(self.write_sandbox_output( - memory, - out_ptr, - out_len_ptr, - gas_left, - false, - already_charged, - )?) - } - /// Stores the immutable data into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::get_immutable_data`]. #[stable] @@ -1639,19 +1536,6 @@ pub mod env { )?) } - /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::minimum_balance`]. - fn minimum_balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::MinimumBalance)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.minimum_balance().to_little_endian(), - false, - already_charged, - )?) - } - /// Deposit a contract event with the data buffer and optional list of topics. /// See [pallet_revive_uapi::HostFn::deposit_event] #[stable] @@ -1727,21 +1611,6 @@ pub mod env { )?) } - /// Computes the SHA2 256-bit hash on the given input buffer. - /// See [`pallet_revive_uapi::HostFn::hash_sha2_256`]. - fn hash_sha2_256( - &mut self, - memory: &mut M, - input_ptr: u32, - input_len: u32, - output_ptr: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::HashSha256(input_len))?; - Ok(self.compute_hash_on_intermediate_buffer( - memory, sha2_256, input_ptr, input_len, output_ptr, - )?) - } - /// Computes the KECCAK 256-bit hash on the given input buffer. /// See [`pallet_revive_uapi::HostFn::hash_keccak_256`]. #[stable] @@ -1758,34 +1627,44 @@ pub mod env { )?) } - /// Computes the BLAKE2 256-bit hash on the given input buffer. - /// See [`pallet_revive_uapi::HostFn::hash_blake2_256`]. - fn hash_blake2_256( - &mut self, - memory: &mut M, - input_ptr: u32, - input_len: u32, - output_ptr: u32, - ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::HashBlake256(input_len))?; - Ok(self.compute_hash_on_intermediate_buffer( - memory, blake2_256, input_ptr, input_len, output_ptr, + /// Stores the length of the data returned by the last call into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::return_data_size`]. + #[stable] + fn return_data_size(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &U256::from(self.ext.last_frame_output().data.len()).to_little_endian(), + false, + |len| Some(RuntimeCosts::CopyToContract(len)), )?) } - /// Computes the BLAKE2 128-bit hash on the given input buffer. - /// See [`pallet_revive_uapi::HostFn::hash_blake2_128`]. - fn hash_blake2_128( + /// Stores data returned by the last call, starting from `offset`, into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::return_data`]. + #[stable] + fn return_data_copy( &mut self, memory: &mut M, - input_ptr: u32, - input_len: u32, - output_ptr: u32, + out_ptr: u32, + out_len_ptr: u32, + offset: u32, ) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::HashBlake128(input_len))?; - Ok(self.compute_hash_on_intermediate_buffer( - memory, blake2_128, input_ptr, input_len, output_ptr, - )?) + let output = mem::take(self.ext.last_frame_output_mut()); + let result = if offset as usize > output.data.len() { + Err(Error::::OutOfBounds.into()) + } else { + self.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &output.data[offset as usize..], + false, + |len| Some(RuntimeCosts::CopyToContract(len)), + ) + }; + *self.ext.last_frame_output_mut() = output; + Ok(result?) } /// Call into the chain extension provided by the chain if any. @@ -1818,27 +1697,6 @@ pub mod env { ret } - /// Emit a custom debug message. - /// See [`pallet_revive_uapi::HostFn::debug_message`]. - fn debug_message( - &mut self, - memory: &mut M, - str_ptr: u32, - str_len: u32, - ) -> Result { - let str_len = str_len.min(limits::DEBUG_BUFFER_BYTES); - self.charge_gas(RuntimeCosts::DebugMessage(str_len))?; - if self.ext.append_debug_buffer("") { - let data = memory.read(str_ptr, str_len)?; - if let Some(msg) = core::str::from_utf8(&data).ok() { - self.ext.append_debug_buffer(msg); - } - Ok(ReturnErrorCode::Success) - } else { - Ok(ReturnErrorCode::LoggingDisabled) - } - } - /// Call some dispatchable of the runtime. /// See [`frame_support::traits::call_runtime`]. #[mutating] @@ -1858,80 +1716,63 @@ pub mod env { ) } - /// Execute an XCM program locally, using the contract's address as the origin. - /// See [`pallet_revive_uapi::HostFn::execute_xcm`]. + /// Checks whether the caller of the current contract is the origin of the whole call stack. + /// See [`pallet_revive_uapi::HostFn::caller_is_origin`]. + fn caller_is_origin(&mut self, _memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::CallerIsOrigin)?; + Ok(self.ext.caller_is_origin() as u32) + } + + /// Checks whether the caller of the current contract is root. + /// See [`pallet_revive_uapi::HostFn::caller_is_root`]. + fn caller_is_root(&mut self, _memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::CallerIsRoot)?; + Ok(self.ext.caller_is_root() as u32) + } + + /// Clear the value at the given key in the contract storage. + /// See [`pallet_revive_uapi::HostFn::clear_storage`] #[mutating] - fn xcm_execute( + fn clear_storage( &mut self, memory: &mut M, - msg_ptr: u32, - msg_len: u32, - ) -> Result { - use frame_support::dispatch::DispatchInfo; - use xcm::VersionedXcm; - use xcm_builder::{ExecuteController, ExecuteControllerWeightInfo}; - - self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; - let message: VersionedXcm> = memory.read_as_unbounded(msg_ptr, msg_len)?; + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + self.clear_storage(memory, flags, key_ptr, key_len) + } - let execute_weight = - <::Xcm as ExecuteController<_, _>>::WeightInfo::execute(); - let weight = self.ext.gas_meter().gas_left().max(execute_weight); - let dispatch_info = DispatchInfo { call_weight: weight, ..Default::default() }; + /// Checks whether there is a value stored under the given key. + /// See [`pallet_revive_uapi::HostFn::contains_storage`] + fn contains_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + ) -> Result { + self.contains_storage(memory, flags, key_ptr, key_len) + } - self.call_dispatchable::( - dispatch_info, - RuntimeCosts::CallXcmExecute, - |runtime| { - let origin = crate::RawOrigin::Signed(runtime.ext.account_id().clone()).into(); - let weight_used = <::Xcm>::execute( - origin, - Box::new(message), - weight.saturating_sub(execute_weight), - )?; - - Ok(Some(weight_used.saturating_add(execute_weight)).into()) - }, - ) - } - - /// Send an XCM program from the contract to the specified destination. - /// See [`pallet_revive_uapi::HostFn::send_xcm`]. - #[mutating] - fn xcm_send( + /// Emit a custom debug message. + /// See [`pallet_revive_uapi::HostFn::debug_message`]. + fn debug_message( &mut self, memory: &mut M, - dest_ptr: u32, - dest_len: u32, - msg_ptr: u32, - msg_len: u32, - output_ptr: u32, + str_ptr: u32, + str_len: u32, ) -> Result { - use xcm::{VersionedLocation, VersionedXcm}; - use xcm_builder::{SendController, SendControllerWeightInfo}; - - self.charge_gas(RuntimeCosts::CopyFromContract(dest_len))?; - let dest: VersionedLocation = memory.read_as_unbounded(dest_ptr, dest_len)?; - - self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; - let message: VersionedXcm<()> = memory.read_as_unbounded(msg_ptr, msg_len)?; - - let weight = <::Xcm as SendController<_>>::WeightInfo::send(); - self.charge_gas(RuntimeCosts::CallRuntime(weight))?; - let origin = crate::RawOrigin::Signed(self.ext.account_id().clone()).into(); - - match <::Xcm>::send(origin, dest.into(), message.into()) { - Ok(message_id) => { - memory.write(output_ptr, &message_id.encode())?; - Ok(ReturnErrorCode::Success) - }, - Err(e) => { - if self.ext.append_debug_buffer("") { - self.ext.append_debug_buffer("seal0::xcm_send failed with: "); - self.ext.append_debug_buffer(e.into()); - }; - Ok(ReturnErrorCode::XcmSendFailed) - }, + let str_len = str_len.min(limits::DEBUG_BUFFER_BYTES); + self.charge_gas(RuntimeCosts::DebugMessage(str_len))?; + if self.ext.append_debug_buffer("") { + let data = memory.read(str_ptr, str_len)?; + if let Some(msg) = core::str::from_utf8(&data).ok() { + self.ext.append_debug_buffer(msg); + } + Ok(ReturnErrorCode::Success) + } else { + Ok(ReturnErrorCode::LoggingDisabled) } } @@ -1965,46 +1806,6 @@ pub mod env { } } - /// Verify a sr25519 signature - /// See [`pallet_revive_uapi::HostFn::sr25519_verify`]. - fn sr25519_verify( - &mut self, - memory: &mut M, - signature_ptr: u32, - pub_key_ptr: u32, - message_len: u32, - message_ptr: u32, - ) -> Result { - self.charge_gas(RuntimeCosts::Sr25519Verify(message_len))?; - - let mut signature: [u8; 64] = [0; 64]; - memory.read_into_buf(signature_ptr, &mut signature)?; - - let mut pub_key: [u8; 32] = [0; 32]; - memory.read_into_buf(pub_key_ptr, &mut pub_key)?; - - let message: Vec = memory.read(message_ptr, message_len)?; - - if self.ext.sr25519_verify(&signature, &message, &pub_key) { - Ok(ReturnErrorCode::Success) - } else { - Ok(ReturnErrorCode::Sr25519VerifyFailed) - } - } - - /// Replace the contract code at the specified address with new code. - /// See [`pallet_revive_uapi::HostFn::set_code_hash`]. - /// - /// Disabled until the internal implementation takes care of collecting - /// the immutable data of the new code hash. - #[mutating] - fn set_code_hash(&mut self, memory: &mut M, code_hash_ptr: u32) -> Result<(), TrapReason> { - self.charge_gas(RuntimeCosts::SetCodeHash)?; - let code_hash: H256 = memory.read_h256(code_hash_ptr)?; - self.ext.set_code_hash(code_hash)?; - Ok(()) - } - /// Calculates Ethereum address from the ECDSA compressed public key and stores /// See [`pallet_revive_uapi::HostFn::ecdsa_to_eth_address`]. fn ecdsa_to_eth_address( @@ -2026,6 +1827,59 @@ pub mod env { } } + /// Computes the BLAKE2 128-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_blake2_128`]. + fn hash_blake2_128( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashBlake128(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, blake2_128, input_ptr, input_len, output_ptr, + )?) + } + + /// Computes the BLAKE2 256-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_blake2_256`]. + fn hash_blake2_256( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashBlake256(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, blake2_256, input_ptr, input_len, output_ptr, + )?) + } + + /// Computes the SHA2 256-bit hash on the given input buffer. + /// See [`pallet_revive_uapi::HostFn::hash_sha2_256`]. + fn hash_sha2_256( + &mut self, + memory: &mut M, + input_ptr: u32, + input_len: u32, + output_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::HashSha256(input_len))?; + Ok(self.compute_hash_on_intermediate_buffer( + memory, sha2_256, input_ptr, input_len, output_ptr, + )?) + } + + /// Checks whether a specified address belongs to a contract. + /// See [`pallet_revive_uapi::HostFn::is_contract`]. + fn is_contract(&mut self, memory: &mut M, account_ptr: u32) -> Result { + self.charge_gas(RuntimeCosts::IsContract)?; + let address = memory.read_h160(account_ptr)?; + Ok(self.ext.is_contract(&address) as u32) + } + /// Adds a new delegate dependency to the contract. /// See [`pallet_revive_uapi::HostFn::lock_delegate_dependency`]. #[mutating] @@ -2040,6 +1894,73 @@ pub mod env { Ok(()) } + /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::minimum_balance`]. + fn minimum_balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::MinimumBalance)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &self.ext.minimum_balance().to_little_endian(), + false, + already_charged, + )?) + } + + /// Retrieve the code hash of the currently executing contract. + /// See [`pallet_revive_uapi::HostFn::own_code_hash`]. + fn own_code_hash(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::OwnCodeHash)?; + let code_hash = *self.ext.own_code_hash(); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + code_hash.as_bytes(), + false, + already_charged, + )?) + } + + /// Replace the contract code at the specified address with new code. + /// See [`pallet_revive_uapi::HostFn::set_code_hash`]. + /// + /// Disabled until the internal implementation takes care of collecting + /// the immutable data of the new code hash. + #[mutating] + fn set_code_hash(&mut self, memory: &mut M, code_hash_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::SetCodeHash)?; + let code_hash: H256 = memory.read_h256(code_hash_ptr)?; + self.ext.set_code_hash(code_hash)?; + Ok(()) + } + + /// Verify a sr25519 signature + /// See [`pallet_revive_uapi::HostFn::sr25519_verify`]. + fn sr25519_verify( + &mut self, + memory: &mut M, + signature_ptr: u32, + pub_key_ptr: u32, + message_len: u32, + message_ptr: u32, + ) -> Result { + self.charge_gas(RuntimeCosts::Sr25519Verify(message_len))?; + + let mut signature: [u8; 64] = [0; 64]; + memory.read_into_buf(signature_ptr, &mut signature)?; + + let mut pub_key: [u8; 32] = [0; 32]; + memory.read_into_buf(pub_key_ptr, &mut pub_key)?; + + let message: Vec = memory.read(message_ptr, message_len)?; + + if self.ext.sr25519_verify(&signature, &message, &pub_key) { + Ok(ReturnErrorCode::Success) + } else { + Ok(ReturnErrorCode::Sr25519VerifyFailed) + } + } + /// Removes the delegate dependency from the contract. /// see [`pallet_revive_uapi::HostFn::unlock_delegate_dependency`]. #[mutating] @@ -2054,43 +1975,122 @@ pub mod env { Ok(()) } - /// Stores the length of the data returned by the last call into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::return_data_size`]. - #[stable] - fn return_data_size(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - Ok(self.write_fixed_sandbox_output( + /// Retrieve and remove the value under the given key from storage. + /// See [`pallet_revive_uapi::HostFn::take_storage`] + #[mutating] + fn take_storage( + &mut self, + memory: &mut M, + flags: u32, + key_ptr: u32, + key_len: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + self.take_storage(memory, flags, key_ptr, key_len, out_ptr, out_len_ptr) + } + + /// Remove the calling account and transfer remaining **free** balance. + /// See [`pallet_revive_uapi::HostFn::terminate`]. + #[mutating] + fn terminate(&mut self, memory: &mut M, beneficiary_ptr: u32) -> Result<(), TrapReason> { + self.terminate(memory, beneficiary_ptr) + } + + /// Stores the amount of weight left into the supplied buffer. + /// See [`pallet_revive_uapi::HostFn::weight_left`]. + fn weight_left( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::WeightLeft)?; + let gas_left = &self.ext.gas_meter().gas_left().encode(); + Ok(self.write_sandbox_output( memory, out_ptr, - &U256::from(self.ext.last_frame_output().data.len()).to_little_endian(), + out_len_ptr, + gas_left, false, - |len| Some(RuntimeCosts::CopyToContract(len)), + already_charged, )?) } - /// Stores data returned by the last call, starting from `offset`, into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::return_data`]. - #[stable] - fn return_data_copy( + /// Execute an XCM program locally, using the contract's address as the origin. + /// See [`pallet_revive_uapi::HostFn::execute_xcm`]. + #[mutating] + fn xcm_execute( &mut self, memory: &mut M, - out_ptr: u32, - out_len_ptr: u32, - offset: u32, - ) -> Result<(), TrapReason> { - let output = mem::take(self.ext.last_frame_output_mut()); - let result = if offset as usize > output.data.len() { - Err(Error::::OutOfBounds.into()) - } else { - self.write_sandbox_output( - memory, - out_ptr, - out_len_ptr, - &output.data[offset as usize..], - false, - |len| Some(RuntimeCosts::CopyToContract(len)), - ) - }; - *self.ext.last_frame_output_mut() = output; - Ok(result?) + msg_ptr: u32, + msg_len: u32, + ) -> Result { + use frame_support::dispatch::DispatchInfo; + use xcm::VersionedXcm; + use xcm_builder::{ExecuteController, ExecuteControllerWeightInfo}; + + self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; + let message: VersionedXcm> = memory.read_as_unbounded(msg_ptr, msg_len)?; + + let execute_weight = + <::Xcm as ExecuteController<_, _>>::WeightInfo::execute(); + let weight = self.ext.gas_meter().gas_left().max(execute_weight); + let dispatch_info = DispatchInfo { call_weight: weight, ..Default::default() }; + + self.call_dispatchable::( + dispatch_info, + RuntimeCosts::CallXcmExecute, + |runtime| { + let origin = crate::RawOrigin::Signed(runtime.ext.account_id().clone()).into(); + let weight_used = <::Xcm>::execute( + origin, + Box::new(message), + weight.saturating_sub(execute_weight), + )?; + + Ok(Some(weight_used.saturating_add(execute_weight)).into()) + }, + ) + } + + /// Send an XCM program from the contract to the specified destination. + /// See [`pallet_revive_uapi::HostFn::send_xcm`]. + #[mutating] + fn xcm_send( + &mut self, + memory: &mut M, + dest_ptr: u32, + dest_len: u32, + msg_ptr: u32, + msg_len: u32, + output_ptr: u32, + ) -> Result { + use xcm::{VersionedLocation, VersionedXcm}; + use xcm_builder::{SendController, SendControllerWeightInfo}; + + self.charge_gas(RuntimeCosts::CopyFromContract(dest_len))?; + let dest: VersionedLocation = memory.read_as_unbounded(dest_ptr, dest_len)?; + + self.charge_gas(RuntimeCosts::CopyFromContract(msg_len))?; + let message: VersionedXcm<()> = memory.read_as_unbounded(msg_ptr, msg_len)?; + + let weight = <::Xcm as SendController<_>>::WeightInfo::send(); + self.charge_gas(RuntimeCosts::CallRuntime(weight))?; + let origin = crate::RawOrigin::Signed(self.ext.account_id().clone()).into(); + + match <::Xcm>::send(origin, dest.into(), message.into()) { + Ok(message_id) => { + memory.write(output_ptr, &message_id.encode())?; + Ok(ReturnErrorCode::Success) + }, + Err(e) => { + if self.ext.append_debug_buffer("") { + self.ext.append_debug_buffer("seal0::xcm_send failed with: "); + self.ext.append_debug_buffer(e.into()); + }; + Ok(ReturnErrorCode::XcmSendFailed) + }, + } } } diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml index b55391dd5d6c..1af5b327dfc7 100644 --- a/substrate/frame/revive/uapi/Cargo.toml +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -29,3 +29,4 @@ default-target = ["riscv64imac-unknown-none-elf"] [features] default = ["scale"] scale = ["dep:codec", "scale-info"] +unstable-api = [] diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index a8c8a924aeeb..aa3203697898 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -45,17 +45,6 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the address. fn address(output: &mut [u8; 20]); - /// Lock a new delegate dependency to the contract. - /// - /// Traps if the maximum number of delegate_dependencies is reached or if - /// the delegate dependency already exists. - /// - /// # Parameters - /// - /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps - /// otherwise. - fn lock_delegate_dependency(code_hash: &[u8; 32]); - /// Get the contract immutable data. /// /// Traps if: @@ -105,21 +94,6 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the call data size. fn call_data_size(output: &mut [u8; 32]); - /// Stores the current block number of the current contract into the supplied buffer. - /// - /// # Parameters - /// - /// - `output`: A reference to the output data buffer to write the block number. - fn block_number(output: &mut [u8; 32]); - - /// Stores the block hash of the given block number into the supplied buffer. - /// - /// # Parameters - /// - /// - `block_number`: A reference to the block number buffer. - /// - `output`: A reference to the output data buffer to write the block number. - fn block_hash(block_number: &[u8; 32], output: &mut [u8; 32]); - /// Call (possibly transferring some amount of funds) into the specified account. /// /// # Parameters @@ -157,56 +131,6 @@ pub trait HostFn: private::Sealed { output: Option<&mut &mut [u8]>, ) -> Result; - /// Call into the chain extension provided by the chain if any. - /// - /// Handling of the input values is up to the specific chain extension and so is the - /// return value. The extension can decide to use the inputs as primitive inputs or as - /// in/out arguments by interpreting them as pointers. Any caller of this function - /// must therefore coordinate with the chain that it targets. - /// - /// # Note - /// - /// If no chain extension exists the contract will trap with the `NoChainExtension` - /// module error. - /// - /// # Parameters - /// - /// - `func_id`: The function id of the chain extension. - /// - `input`: The input data buffer. - /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` - /// is provided then the output buffer is not copied. - /// - /// # Return - /// - /// The chain extension returned value, if executed successfully. - fn call_chain_extension(func_id: u32, input: &[u8], output: Option<&mut &mut [u8]>) -> u32; - - /// Call some dispatchable of the runtime. - /// - /// # Parameters - /// - /// - `call`: The call data. - /// - /// # Return - /// - /// Returns `Error::Success` when the dispatchable was successfully executed and - /// returned `Ok`. When the dispatchable was executed but returned an error - /// `Error::CallRuntimeFailed` is returned. The full error is not - /// provided because it is not guaranteed to be stable. - /// - /// # Comparison with `ChainExtension` - /// - /// Just as a chain extension this API allows the runtime to extend the functionality - /// of contracts. While making use of this function is generally easier it cannot be - /// used in all cases. Consider writing a chain extension if you need to do perform - /// one of the following tasks: - /// - /// - Return data. - /// - Provide functionality **exclusively** to contracts. - /// - Provide custom weights. - /// - Avoid the need to keep the `Call` data structure stable. - fn call_runtime(call: &[u8]) -> Result; - /// Stores the address of the caller into the supplied buffer. /// /// If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the @@ -232,38 +156,6 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the origin's address. fn origin(output: &mut [u8; 20]); - /// Checks whether the caller of the current contract is the origin of the whole call stack. - /// - /// Prefer this over [`is_contract()`][`Self::is_contract`] when checking whether your contract - /// is being called by a contract or a plain account. The reason is that it performs better - /// since it does not need to do any storage lookups. - /// - /// # Return - /// - /// A return value of `true` indicates that this contract is being called by a plain account - /// and `false` indicates that the caller is another contract. - fn caller_is_origin() -> bool; - - /// Checks whether the caller of the current contract is root. - /// - /// Note that only the origin of the call stack can be root. Hence this function returning - /// `true` implies that the contract is being called by the origin. - /// - /// A return value of `true` indicates that this contract is being called by a root origin, - /// and `false` indicates that the caller is a signed origin. - fn caller_is_root() -> u32; - - /// Clear the value at the given key in the contract storage. - /// - /// # Parameters - /// - /// - `key`: The storage key. - /// - /// # Return - /// - /// Returns the size of the pre-existing value at the specified key if any. - fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option; - /// Retrieve the code hash for a specified contract address. /// /// # Parameters @@ -290,37 +182,6 @@ pub trait HostFn: private::Sealed { /// If `addr` is not a contract the `output` will be zero. fn code_size(addr: &[u8; 20], output: &mut [u8; 32]); - /// Checks whether there is a value stored under the given key. - /// - /// The key length must not exceed the maximum defined by the contracts module parameter. - /// - /// # Parameters - /// - `key`: The storage key. - /// - /// # Return - /// - /// Returns the size of the pre-existing value at the specified key if any. - fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option; - - /// Emit a custom debug message. - /// - /// No newlines are added to the supplied message. - /// Specifying invalid UTF-8 just drops the message with no trap. - /// - /// This is a no-op if debug message recording is disabled which is always the case - /// when the code is executing on-chain. The message is interpreted as UTF-8 and - /// appended to the debug buffer which is then supplied to the calling RPC client. - /// - /// # Note - /// - /// Even though no action is taken when debug message recording is disabled there is still - /// a non trivial overhead (and weight cost) associated with calling this function. Contract - /// languages should remove calls to this function (either at runtime or compile time) when - /// not being executed as an RPC. For example, they could allow users to disable logging - /// through compile time flags (cargo features) for on-chain deployment. Additionally, the - /// return value of this function can be cached in order to prevent further calls at runtime. - fn debug_message(str: &[u8]) -> Result; - /// Execute code in the context (storage, caller, value) of the current contract. /// /// Reentrancy protection is always disabled since the callee is allowed @@ -369,49 +230,6 @@ pub trait HostFn: private::Sealed { /// - `topics`: The topics list. It can't contain duplicates. fn deposit_event(topics: &[[u8; 32]], data: &[u8]); - /// Recovers the ECDSA public key from the given message hash and signature. - /// - /// Writes the public key into the given output buffer. - /// Assumes the secp256k1 curve. - /// - /// # Parameters - /// - /// - `signature`: The signature bytes. - /// - `message_hash`: The message hash bytes. - /// - `output`: A reference to the output data buffer to write the public key. - /// - /// # Errors - /// - /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] - fn ecdsa_recover( - signature: &[u8; 65], - message_hash: &[u8; 32], - output: &mut [u8; 33], - ) -> Result; - - /// Calculates Ethereum address from the ECDSA compressed public key and stores - /// it into the supplied buffer. - /// - /// # Parameters - /// - /// - `pubkey`: The public key bytes. - /// - `output`: A reference to the output data buffer to write the address. - /// - /// # Errors - /// - /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] - fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result; - - /// Stores the amount of weight left into the supplied buffer. - /// The data is encoded as Weight. - /// - /// If the available space in `output` is less than the size of the value a trap is triggered. - /// - /// # Parameters - /// - /// - `output`: A reference to the output data buffer to write the weight left. - fn weight_left(output: &mut &mut [u8]); - /// Retrieve the value under the given key from storage. /// /// The key length must not exceed the maximum defined by the contracts module parameter. @@ -425,10 +243,7 @@ pub trait HostFn: private::Sealed { /// [KeyNotFound][`crate::ReturnErrorCode::KeyNotFound] fn get_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result; - hash_fn!(sha2_256, 32); hash_fn!(keccak_256, 32); - hash_fn!(blake2_256, 32); - hash_fn!(blake2_128, 16); /// Stores the input passed by the caller into the supplied buffer. /// @@ -503,65 +318,294 @@ pub trait HostFn: private::Sealed { salt: Option<&[u8; 32]>, ) -> Result; - /// Checks whether a specified address belongs to a contract. + /// Load the latest block timestamp into the supplied buffer /// /// # Parameters /// - /// - `address`: The address to check + /// - `output`: A reference to the output data buffer to write the timestamp. + fn now(output: &mut [u8; 32]); + + /// Cease contract execution and save a data buffer as a result of the execution. + /// + /// This function never returns as it stops execution of the caller. + /// This is the only way to return a data buffer to the caller. Returning from + /// execution without calling this function is equivalent to calling: + /// ```nocompile + /// return_value(ReturnFlags::empty(), &[]) + /// ``` + /// + /// Using an unnamed non empty `ReturnFlags` triggers a trap. + /// + /// # Parameters + /// + /// - `flags`: Flag used to signal special return conditions to the supervisor. See + /// [`ReturnFlags`] for a documentation of the supported flags. + /// - `return_value`: The return value buffer. + fn return_value(flags: ReturnFlags, return_value: &[u8]) -> !; + + /// Set the value at the given key in the contract storage. + /// + /// The key and value lengths must not exceed the maximums defined by the contracts module + /// parameters. + /// + /// # Parameters + /// + /// - `key`: The storage key. + /// - `encoded_value`: The storage value. /// /// # Return /// - /// Returns `true` if the address belongs to a contract. - fn is_contract(address: &[u8; 20]) -> bool; + /// Returns the size of the pre-existing value at the specified key if any. + fn set_storage(flags: StorageFlags, key: &[u8], value: &[u8]) -> Option; - /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. + /// Stores the value transferred along with this call/instantiate into the supplied buffer. /// /// # Parameters /// - /// - `output`: A reference to the output data buffer to write the minimum balance. - fn minimum_balance(output: &mut [u8; 32]); + /// - `output`: A reference to the output data buffer to write the transferred value. + fn value_transferred(output: &mut [u8; 32]); - /// Retrieve the code hash of the currently executing contract. + /// Stores the price for the specified amount of gas into the supplied buffer. /// /// # Parameters /// - /// - `output`: A reference to the output data buffer to write the code hash. - fn own_code_hash(output: &mut [u8; 32]); + /// - `ref_time_limit`: The *ref_time* Weight limit to query the price for. + /// - `proof_size_limit`: The *proof_size* Weight limit to query the price for. + /// - `output`: A reference to the output data buffer to write the price. + fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]); - /// Load the latest block timestamp into the supplied buffer + /// Stores the size of the returned data of the last contract call or instantiation. /// /// # Parameters /// - /// - `output`: A reference to the output data buffer to write the timestamp. - fn now(output: &mut [u8; 32]); + /// - `output`: A reference to the output buffer to write the size. + fn return_data_size(output: &mut [u8; 32]); - /// Removes the delegate dependency from the contract. + /// Stores the returned data of the last contract call or contract instantiation. /// - /// Traps if the delegate dependency does not exist. + /// # Parameters + /// - `output`: A reference to the output buffer to write the data. + /// - `offset`: Byte offset into the returned data + fn return_data_copy(output: &mut &mut [u8], offset: u32); + + /// Stores the current block number of the current contract into the supplied buffer. + /// + /// # Parameters + /// + /// - `output`: A reference to the output data buffer to write the block number. + #[cfg(feature = "unstable-api")] + fn block_number(output: &mut [u8; 32]); + + /// Stores the block hash of the given block number into the supplied buffer. + /// + /// # Parameters + /// + /// - `block_number`: A reference to the block number buffer. + /// - `output`: A reference to the output data buffer to write the block number. + #[cfg(feature = "unstable-api")] + fn block_hash(block_number: &[u8; 32], output: &mut [u8; 32]); + + /// Call into the chain extension provided by the chain if any. + /// + /// Handling of the input values is up to the specific chain extension and so is the + /// return value. The extension can decide to use the inputs as primitive inputs or as + /// in/out arguments by interpreting them as pointers. Any caller of this function + /// must therefore coordinate with the chain that it targets. + /// + /// # Note + /// + /// If no chain extension exists the contract will trap with the `NoChainExtension` + /// module error. + /// + /// # Parameters + /// + /// - `func_id`: The function id of the chain extension. + /// - `input`: The input data buffer. + /// - `output`: A reference to the output data buffer to write the call output buffer. If `None` + /// is provided then the output buffer is not copied. + /// + /// # Return + /// + /// The chain extension returned value, if executed successfully. + #[cfg(feature = "unstable-api")] + fn call_chain_extension(func_id: u32, input: &[u8], output: Option<&mut &mut [u8]>) -> u32; + + /// Call some dispatchable of the runtime. + /// + /// # Parameters + /// + /// - `call`: The call data. + /// + /// # Return + /// + /// Returns `Error::Success` when the dispatchable was successfully executed and + /// returned `Ok`. When the dispatchable was executed but returned an error + /// `Error::CallRuntimeFailed` is returned. The full error is not + /// provided because it is not guaranteed to be stable. + /// + /// # Comparison with `ChainExtension` + /// + /// Just as a chain extension this API allows the runtime to extend the functionality + /// of contracts. While making use of this function is generally easier it cannot be + /// used in all cases. Consider writing a chain extension if you need to do perform + /// one of the following tasks: + /// + /// - Return data. + /// - Provide functionality **exclusively** to contracts. + /// - Provide custom weights. + /// - Avoid the need to keep the `Call` data structure stable. + #[cfg(feature = "unstable-api")] + fn call_runtime(call: &[u8]) -> Result; + + /// Checks whether the caller of the current contract is the origin of the whole call stack. + /// + /// Prefer this over [`is_contract()`][`Self::is_contract`] when checking whether your contract + /// is being called by a contract or a plain account. The reason is that it performs better + /// since it does not need to do any storage lookups. + /// + /// # Return + /// + /// A return value of `true` indicates that this contract is being called by a plain account + /// and `false` indicates that the caller is another contract. + #[cfg(feature = "unstable-api")] + fn caller_is_origin() -> bool; + + /// Checks whether the caller of the current contract is root. + /// + /// Note that only the origin of the call stack can be root. Hence this function returning + /// `true` implies that the contract is being called by the origin. + /// + /// A return value of `true` indicates that this contract is being called by a root origin, + /// and `false` indicates that the caller is a signed origin. + #[cfg(feature = "unstable-api")] + fn caller_is_root() -> u32; + + /// Clear the value at the given key in the contract storage. + /// + /// # Parameters + /// + /// - `key`: The storage key. + /// + /// # Return + /// + /// Returns the size of the pre-existing value at the specified key if any. + #[cfg(feature = "unstable-api")] + fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option; + + /// Checks whether there is a value stored under the given key. + /// + /// The key length must not exceed the maximum defined by the contracts module parameter. + /// + /// # Parameters + /// - `key`: The storage key. + /// + /// # Return + /// + /// Returns the size of the pre-existing value at the specified key if any. + #[cfg(feature = "unstable-api")] + fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option; + + /// Emit a custom debug message. + /// + /// No newlines are added to the supplied message. + /// Specifying invalid UTF-8 just drops the message with no trap. + /// + /// This is a no-op if debug message recording is disabled which is always the case + /// when the code is executing on-chain. The message is interpreted as UTF-8 and + /// appended to the debug buffer which is then supplied to the calling RPC client. + /// + /// # Note + /// + /// Even though no action is taken when debug message recording is disabled there is still + /// a non trivial overhead (and weight cost) associated with calling this function. Contract + /// languages should remove calls to this function (either at runtime or compile time) when + /// not being executed as an RPC. For example, they could allow users to disable logging + /// through compile time flags (cargo features) for on-chain deployment. Additionally, the + /// return value of this function can be cached in order to prevent further calls at runtime. + #[cfg(feature = "unstable-api")] + fn debug_message(str: &[u8]) -> Result; + + /// Recovers the ECDSA public key from the given message hash and signature. + /// + /// Writes the public key into the given output buffer. + /// Assumes the secp256k1 curve. + /// + /// # Parameters + /// + /// - `signature`: The signature bytes. + /// - `message_hash`: The message hash bytes. + /// - `output`: A reference to the output data buffer to write the public key. + /// + /// # Errors + /// + /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] + #[cfg(feature = "unstable-api")] + fn ecdsa_recover( + signature: &[u8; 65], + message_hash: &[u8; 32], + output: &mut [u8; 33], + ) -> Result; + + /// Calculates Ethereum address from the ECDSA compressed public key and stores + /// it into the supplied buffer. + /// + /// # Parameters + /// + /// - `pubkey`: The public key bytes. + /// - `output`: A reference to the output data buffer to write the address. + /// + /// # Errors + /// + /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] + #[cfg(feature = "unstable-api")] + fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result; + + #[cfg(feature = "unstable-api")] + hash_fn!(sha2_256, 32); + #[cfg(feature = "unstable-api")] + hash_fn!(blake2_256, 32); + #[cfg(feature = "unstable-api")] + hash_fn!(blake2_128, 16); + + /// Checks whether a specified address belongs to a contract. + /// + /// # Parameters + /// + /// - `address`: The address to check + /// + /// # Return + /// + /// Returns `true` if the address belongs to a contract. + #[cfg(feature = "unstable-api")] + fn is_contract(address: &[u8; 20]) -> bool; + + /// Lock a new delegate dependency to the contract. + /// + /// Traps if the maximum number of delegate_dependencies is reached or if + /// the delegate dependency already exists. /// /// # Parameters /// /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps /// otherwise. - fn unlock_delegate_dependency(code_hash: &[u8; 32]); + #[cfg(feature = "unstable-api")] + fn lock_delegate_dependency(code_hash: &[u8; 32]); - /// Cease contract execution and save a data buffer as a result of the execution. + /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. /// - /// This function never returns as it stops execution of the caller. - /// This is the only way to return a data buffer to the caller. Returning from - /// execution without calling this function is equivalent to calling: - /// ```nocompile - /// return_value(ReturnFlags::empty(), &[]) - /// ``` + /// # Parameters /// - /// Using an unnamed non empty `ReturnFlags` triggers a trap. + /// - `output`: A reference to the output data buffer to write the minimum balance. + #[cfg(feature = "unstable-api")] + fn minimum_balance(output: &mut [u8; 32]); + + /// Retrieve the code hash of the currently executing contract. /// /// # Parameters /// - /// - `flags`: Flag used to signal special return conditions to the supervisor. See - /// [`ReturnFlags`] for a documentation of the supported flags. - /// - `return_value`: The return value buffer. - fn return_value(flags: ReturnFlags, return_value: &[u8]) -> !; + /// - `output`: A reference to the output data buffer to write the code hash. + #[cfg(feature = "unstable-api")] + fn own_code_hash(output: &mut [u8; 32]); /// Replace the contract code at the specified address with new code. /// @@ -591,23 +635,9 @@ pub trait HostFn: private::Sealed { /// # Panics /// /// Panics if there is no code on-chain with the specified hash. + #[cfg(feature = "unstable-api")] fn set_code_hash(code_hash: &[u8; 32]); - /// Set the value at the given key in the contract storage. - /// - /// The key and value lengths must not exceed the maximums defined by the contracts module - /// parameters. - /// - /// # Parameters - /// - /// - `key`: The storage key. - /// - `encoded_value`: The storage value. - /// - /// # Return - /// - /// Returns the size of the pre-existing value at the specified key if any. - fn set_storage(flags: StorageFlags, key: &[u8], value: &[u8]) -> Option; - /// Verify a sr25519 signature /// /// # Parameters @@ -618,6 +648,7 @@ pub trait HostFn: private::Sealed { /// # Errors /// /// - [Sr25519VerifyFailed][`crate::ReturnErrorCode::Sr25519VerifyFailed] + #[cfg(feature = "unstable-api")] fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result; /// Retrieve and remove the value under the given key from storage. @@ -629,6 +660,7 @@ pub trait HostFn: private::Sealed { /// # Errors /// /// [KeyNotFound][`crate::ReturnErrorCode::KeyNotFound] + #[cfg(feature = "unstable-api")] fn take_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result; /// Remove the calling account and transfer remaining **free** balance. @@ -646,23 +678,30 @@ pub trait HostFn: private::Sealed { /// - The contract is live i.e is already on the call stack. /// - Failed to send the balance to the beneficiary. /// - The deletion queue is full. + #[cfg(feature = "unstable-api")] fn terminate(beneficiary: &[u8; 20]) -> !; - /// Stores the value transferred along with this call/instantiate into the supplied buffer. + /// Removes the delegate dependency from the contract. + /// + /// Traps if the delegate dependency does not exist. /// /// # Parameters /// - /// - `output`: A reference to the output data buffer to write the transferred value. - fn value_transferred(output: &mut [u8; 32]); + /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps + /// otherwise. + #[cfg(feature = "unstable-api")] + fn unlock_delegate_dependency(code_hash: &[u8; 32]); - /// Stores the price for the specified amount of gas into the supplied buffer. + /// Stores the amount of weight left into the supplied buffer. + /// The data is encoded as Weight. + /// + /// If the available space in `output` is less than the size of the value a trap is triggered. /// /// # Parameters /// - /// - `ref_time_limit`: The *ref_time* Weight limit to query the price for. - /// - `proof_size_limit`: The *proof_size* Weight limit to query the price for. - /// - `output`: A reference to the output data buffer to write the price. - fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]); + /// - `output`: A reference to the output data buffer to write the weight left. + #[cfg(feature = "unstable-api")] + fn weight_left(output: &mut &mut [u8]); /// Execute an XCM program locally, using the contract's address as the origin. /// This is equivalent to dispatching `pallet_xcm::execute` through call_runtime, except that @@ -678,6 +717,7 @@ pub trait HostFn: private::Sealed { /// /// Returns `Error::Success` when the XCM execution attempt is successful. When the XCM /// execution fails, `ReturnCode::XcmExecutionFailed` is returned + #[cfg(feature = "unstable-api")] fn xcm_execute(msg: &[u8]) -> Result; /// Send an XCM program from the contract to the specified destination. @@ -695,21 +735,8 @@ pub trait HostFn: private::Sealed { /// /// Returns `ReturnCode::Success` when the message was successfully sent. When the XCM /// execution fails, `ReturnErrorCode::XcmSendFailed` is returned. + #[cfg(feature = "unstable-api")] fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result; - - /// Stores the size of the returned data of the last contract call or instantiation. - /// - /// # Parameters - /// - /// - `output`: A reference to the output buffer to write the size. - fn return_data_size(output: &mut [u8; 32]); - - /// Stores the returned data of the last contract call or contract instantiation. - /// - /// # Parameters - /// - `output`: A reference to the output buffer to write the data. - /// - `offset`: Byte offset into the returned data - fn return_data_copy(output: &mut &mut [u8], offset: u32); } mod private { diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 4e2cc125bbe5..d5a695262a24 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -295,10 +295,6 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn caller_is_root() -> u32 { - unsafe { sys::caller_is_root() }.into_u32() - } - fn delegate_call( flags: CallFlags, address: &[u8; 20], @@ -368,17 +364,6 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option { - let ret_code = unsafe { sys::clear_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; - ret_code.into() - } - - fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option { - let ret_code = - unsafe { sys::contains_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; - ret_code.into() - } - fn get_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result { let mut output_len = output.len() as u32; let ret_code = { @@ -396,33 +381,79 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn take_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result { + fn input(output: &mut &mut [u8]) { let mut output_len = output.len() as u32; - let ret_code = { - unsafe { - sys::take_storage( - flags.bits(), - key.as_ptr(), - key.len() as u32, - output.as_mut_ptr(), - &mut output_len, - ) - } - }; + { + unsafe { sys::input(output.as_mut_ptr(), &mut output_len) }; + } extract_from_slice(output, output_len as usize); - ret_code.into() } - fn debug_message(str: &[u8]) -> Result { - let ret_code = unsafe { sys::debug_message(str.as_ptr(), str.len() as u32) }; - ret_code.into() + fn call_data_load(out_ptr: &mut [u8; 32], offset: u32) { + unsafe { sys::call_data_load(out_ptr.as_mut_ptr(), offset) }; } - fn terminate(beneficiary: &[u8; 20]) -> ! { - unsafe { sys::terminate(beneficiary.as_ptr()) } - panic!("terminate does not return"); + fn return_value(flags: ReturnFlags, return_value: &[u8]) -> ! { + unsafe { sys::seal_return(flags.bits(), return_value.as_ptr(), return_value.len() as u32) } + panic!("seal_return does not return"); + } + + impl_wrapper_for! { + [u8; 32] => call_data_size, balance, value_transferred, now, chain_id; + [u8; 20] => address, caller, origin; + } + + #[cfg(feature = "unstable-api")] + impl_wrapper_for! { + [u8; 32] => block_number, minimum_balance; + } + + fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]) { + unsafe { sys::weight_to_fee(ref_time_limit, proof_size_limit, output.as_mut_ptr()) }; + } + + impl_hash_fn!(keccak_256, 32); + + fn get_immutable_data(output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + unsafe { sys::get_immutable_data(output.as_mut_ptr(), &mut output_len) }; + extract_from_slice(output, output_len as usize); + } + + fn set_immutable_data(data: &[u8]) { + unsafe { sys::set_immutable_data(data.as_ptr(), data.len() as u32) } + } + + fn balance_of(address: &[u8; 20], output: &mut [u8; 32]) { + unsafe { sys::balance_of(address.as_ptr(), output.as_mut_ptr()) }; + } + + fn code_hash(address: &[u8; 20], output: &mut [u8; 32]) { + unsafe { sys::code_hash(address.as_ptr(), output.as_mut_ptr()) } + } + + fn code_size(address: &[u8; 20], output: &mut [u8; 32]) { + unsafe { sys::code_size(address.as_ptr(), output.as_mut_ptr()) } + } + + fn return_data_size(output: &mut [u8; 32]) { + unsafe { sys::return_data_size(output.as_mut_ptr()) }; + } + + fn return_data_copy(output: &mut &mut [u8], offset: u32) { + let mut output_len = output.len() as u32; + { + unsafe { sys::return_data_copy(output.as_mut_ptr(), &mut output_len, offset) }; + } + extract_from_slice(output, output_len as usize); + } + + #[cfg(feature = "unstable-api")] + fn block_hash(block_number_ptr: &[u8; 32], output: &mut [u8; 32]) { + unsafe { sys::block_hash(block_number_ptr.as_ptr(), output.as_mut_ptr()) }; } + #[cfg(feature = "unstable-api")] fn call_chain_extension(func_id: u32, input: &[u8], mut output: Option<&mut &mut [u8]>) -> u32 { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); let ret_code = { @@ -443,48 +474,43 @@ impl HostFn for HostFnImpl { ret_code.into_u32() } - fn input(output: &mut &mut [u8]) { - let mut output_len = output.len() as u32; - { - unsafe { sys::input(output.as_mut_ptr(), &mut output_len) }; - } - extract_from_slice(output, output_len as usize); + #[cfg(feature = "unstable-api")] + fn call_runtime(call: &[u8]) -> Result { + let ret_code = unsafe { sys::call_runtime(call.as_ptr(), call.len() as u32) }; + ret_code.into() } - fn call_data_load(out_ptr: &mut [u8; 32], offset: u32) { - unsafe { sys::call_data_load(out_ptr.as_mut_ptr(), offset) }; + #[cfg(feature = "unstable-api")] + fn caller_is_origin() -> bool { + let ret_val = unsafe { sys::caller_is_origin() }; + ret_val.into_bool() } - fn return_value(flags: ReturnFlags, return_value: &[u8]) -> ! { - unsafe { sys::seal_return(flags.bits(), return_value.as_ptr(), return_value.len() as u32) } - panic!("seal_return does not return"); + #[cfg(feature = "unstable-api")] + fn caller_is_root() -> u32 { + unsafe { sys::caller_is_root() }.into_u32() } - fn call_runtime(call: &[u8]) -> Result { - let ret_code = unsafe { sys::call_runtime(call.as_ptr(), call.len() as u32) }; + #[cfg(feature = "unstable-api")] + fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option { + let ret_code = unsafe { sys::clear_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; ret_code.into() } - impl_wrapper_for! { - [u8; 32] => call_data_size, block_number, balance, value_transferred, now, minimum_balance, chain_id; - [u8; 20] => address, caller, origin; - } - - fn weight_left(output: &mut &mut [u8]) { - let mut output_len = output.len() as u32; - unsafe { sys::weight_left(output.as_mut_ptr(), &mut output_len) } - extract_from_slice(output, output_len as usize) + #[cfg(feature = "unstable-api")] + fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option { + let ret_code = + unsafe { sys::contains_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; + ret_code.into() } - fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]) { - unsafe { sys::weight_to_fee(ref_time_limit, proof_size_limit, output.as_mut_ptr()) }; + #[cfg(feature = "unstable-api")] + fn debug_message(str: &[u8]) -> Result { + let ret_code = unsafe { sys::debug_message(str.as_ptr(), str.len() as u32) }; + ret_code.into() } - impl_hash_fn!(sha2_256, 32); - impl_hash_fn!(keccak_256, 32); - impl_hash_fn!(blake2_256, 32); - impl_hash_fn!(blake2_128, 16); - + #[cfg(feature = "unstable-api")] fn ecdsa_recover( signature: &[u8; 65], message_hash: &[u8; 32], @@ -496,76 +522,96 @@ impl HostFn for HostFnImpl { ret_code.into() } + #[cfg(feature = "unstable-api")] fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result { let ret_code = unsafe { sys::ecdsa_to_eth_address(pubkey.as_ptr(), output.as_mut_ptr()) }; ret_code.into() } - fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result { - let ret_code = unsafe { - sys::sr25519_verify( - signature.as_ptr(), - pub_key.as_ptr(), - message.len() as u32, - message.as_ptr(), - ) - }; - ret_code.into() - } + #[cfg(feature = "unstable-api")] + impl_hash_fn!(sha2_256, 32); + #[cfg(feature = "unstable-api")] + impl_hash_fn!(blake2_256, 32); + #[cfg(feature = "unstable-api")] + impl_hash_fn!(blake2_128, 16); + #[cfg(feature = "unstable-api")] fn is_contract(address: &[u8; 20]) -> bool { let ret_val = unsafe { sys::is_contract(address.as_ptr()) }; ret_val.into_bool() } - fn get_immutable_data(output: &mut &mut [u8]) { - let mut output_len = output.len() as u32; - unsafe { sys::get_immutable_data(output.as_mut_ptr(), &mut output_len) }; - extract_from_slice(output, output_len as usize); - } - - fn set_immutable_data(data: &[u8]) { - unsafe { sys::set_immutable_data(data.as_ptr(), data.len() as u32) } - } - - fn balance_of(address: &[u8; 20], output: &mut [u8; 32]) { - unsafe { sys::balance_of(address.as_ptr(), output.as_mut_ptr()) }; + #[cfg(feature = "unstable-api")] + fn lock_delegate_dependency(code_hash: &[u8; 32]) { + unsafe { sys::lock_delegate_dependency(code_hash.as_ptr()) } } - fn caller_is_origin() -> bool { - let ret_val = unsafe { sys::caller_is_origin() }; - ret_val.into_bool() + #[cfg(feature = "unstable-api")] + fn own_code_hash(output: &mut [u8; 32]) { + unsafe { sys::own_code_hash(output.as_mut_ptr()) } } + #[cfg(feature = "unstable-api")] fn set_code_hash(code_hash: &[u8; 32]) { unsafe { sys::set_code_hash(code_hash.as_ptr()) } } - fn code_hash(address: &[u8; 20], output: &mut [u8; 32]) { - unsafe { sys::code_hash(address.as_ptr(), output.as_mut_ptr()) } - } - - fn code_size(address: &[u8; 20], output: &mut [u8; 32]) { - unsafe { sys::code_size(address.as_ptr(), output.as_mut_ptr()) } + #[cfg(feature = "unstable-api")] + fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result { + let ret_code = unsafe { + sys::sr25519_verify( + signature.as_ptr(), + pub_key.as_ptr(), + message.len() as u32, + message.as_ptr(), + ) + }; + ret_code.into() } - fn own_code_hash(output: &mut [u8; 32]) { - unsafe { sys::own_code_hash(output.as_mut_ptr()) } + #[cfg(feature = "unstable-api")] + fn take_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result { + let mut output_len = output.len() as u32; + let ret_code = { + unsafe { + sys::take_storage( + flags.bits(), + key.as_ptr(), + key.len() as u32, + output.as_mut_ptr(), + &mut output_len, + ) + } + }; + extract_from_slice(output, output_len as usize); + ret_code.into() } - fn lock_delegate_dependency(code_hash: &[u8; 32]) { - unsafe { sys::lock_delegate_dependency(code_hash.as_ptr()) } + #[cfg(feature = "unstable-api")] + fn terminate(beneficiary: &[u8; 20]) -> ! { + unsafe { sys::terminate(beneficiary.as_ptr()) } + panic!("terminate does not return"); } + #[cfg(feature = "unstable-api")] fn unlock_delegate_dependency(code_hash: &[u8; 32]) { unsafe { sys::unlock_delegate_dependency(code_hash.as_ptr()) } } + #[cfg(feature = "unstable-api")] + fn weight_left(output: &mut &mut [u8]) { + let mut output_len = output.len() as u32; + unsafe { sys::weight_left(output.as_mut_ptr(), &mut output_len) } + extract_from_slice(output, output_len as usize) + } + + #[cfg(feature = "unstable-api")] fn xcm_execute(msg: &[u8]) -> Result { let ret_code = unsafe { sys::xcm_execute(msg.as_ptr(), msg.len() as _) }; ret_code.into() } + #[cfg(feature = "unstable-api")] fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result { let ret_code = unsafe { sys::xcm_send( @@ -578,20 +624,4 @@ impl HostFn for HostFnImpl { }; ret_code.into() } - - fn return_data_size(output: &mut [u8; 32]) { - unsafe { sys::return_data_size(output.as_mut_ptr()) }; - } - - fn return_data_copy(output: &mut &mut [u8], offset: u32) { - let mut output_len = output.len() as u32; - { - unsafe { sys::return_data_copy(output.as_mut_ptr(), &mut output_len, offset) }; - } - extract_from_slice(output, output_len as usize); - } - - fn block_hash(block_number_ptr: &[u8; 32], output: &mut [u8; 32]) { - unsafe { sys::block_hash(block_number_ptr.as_ptr(), output.as_mut_ptr()) }; - } } From bd2c35fcf18614c9012117edc21acce0f653a5fa Mon Sep 17 00:00:00 2001 From: Jarkko Sakkinen Date: Sat, 14 Dec 2024 21:05:28 +0200 Subject: [PATCH 168/340] sc-executor-polkavm: Migrate into PolkaVM 0.18.0 (#6533) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bump `polkavm` to 0.18.0, and update `sc-polkavm-executor` to be compatible with the API changes. In addition, bump also `polkavm-derive` and `polkavm-linker` in order to make sure that the all parts of the Polkadot SDK use the exact same ABI for `.polkavm` binaries. Purely relying on RV32E/RV64E ABI is not possible, as PolkaVM uses a RISCV-V alike ISA, which is derived from RV32E/RV64E but it is still its own microarchitecture, i.e. not fully binary compatible. --------- Signed-off-by: Jarkko Sakkinen Co-authored-by: Koute Co-authored-by: Alexander Theißen --- Cargo.lock | 95 +++++++- Cargo.toml | 6 +- polkadot/runtime/rococo/src/lib.rs | 11 + prdoc/pr_6533.prdoc | 20 ++ substrate/client/executor/common/src/error.rs | 4 +- .../common/src/runtime_blob/runtime_blob.rs | 13 +- substrate/client/executor/polkavm/src/lib.rs | 206 ++++++++++-------- 7 files changed, 252 insertions(+), 103 deletions(-) create mode 100644 prdoc/pr_6533.prdoc diff --git a/Cargo.lock b/Cargo.lock index 43a7880362b4..7f9f3198e570 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19907,6 +19907,19 @@ dependencies = [ "polkavm-linux-raw 0.17.0", ] +[[package]] +name = "polkavm" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd044ab1d3b11567ab6b98ca71259a992b4034220d5972988a0e96518e5d343d" +dependencies = [ + "libc", + "log", + "polkavm-assembler 0.18.0", + "polkavm-common 0.18.0", + "polkavm-linux-raw 0.18.0", +] + [[package]] name = "polkavm-assembler" version = "0.9.0" @@ -19934,6 +19947,15 @@ dependencies = [ "log", ] +[[package]] +name = "polkavm-assembler" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaad38dc420bfed79e6f731471c973ce5ff5e47ab403e63cf40358fef8a6368f" +dependencies = [ + "log", +] + [[package]] name = "polkavm-common" version = "0.8.0" @@ -19969,6 +19991,16 @@ dependencies = [ "polkavm-assembler 0.17.0", ] +[[package]] +name = "polkavm-common" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ff33982a807d8567645d4784b9b5d7ab87bcb494f534a57cadd9012688e102" +dependencies = [ + "log", + "polkavm-assembler 0.18.0", +] + [[package]] name = "polkavm-derive" version = "0.8.0" @@ -20005,6 +20037,15 @@ dependencies = [ "polkavm-derive-impl-macro 0.17.0", ] +[[package]] +name = "polkavm-derive" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2eb703f3b6404c13228402e98a5eae063fd16b8f58afe334073ec105ee4117e" +dependencies = [ + "polkavm-derive-impl-macro 0.18.0", +] + [[package]] name = "polkavm-derive-impl" version = "0.8.0" @@ -20053,6 +20094,18 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "polkavm-derive-impl" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d2840cc62a0550156b1676fed8392271ddf2fab4a00661db56231424674624" +dependencies = [ + "polkavm-common 0.18.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", +] + [[package]] name = "polkavm-derive-impl-macro" version = "0.8.0" @@ -20093,6 +20146,16 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c16669ddc7433e34c1007d31080b80901e3e8e523cb9d4b441c3910cf9294b" +dependencies = [ + "polkavm-derive-impl 0.18.0", + "syn 2.0.87", +] + [[package]] name = "polkavm-linker" version = "0.9.2" @@ -20139,6 +20202,22 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "polkavm-linker" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9bfe793b094d9ea5c99b7c43ba46e277b0f8f48f4bbfdbabf8d3ebf701a4bd3" +dependencies = [ + "dirs", + "gimli 0.31.1", + "hashbrown 0.14.5", + "log", + "object 0.36.1", + "polkavm-common 0.18.0", + "regalloc2 0.9.3", + "rustc-demangle", +] + [[package]] name = "polkavm-linux-raw" version = "0.9.0" @@ -20157,6 +20236,12 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64c3d93a58ffbc3099d1227f0da9675a025a9ea6c917038f266920c1de1e568" +[[package]] +name = "polkavm-linux-raw" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23eff02c070c70f31878a3d915e88a914ecf3e153741e2fb572dde28cce20fde" + [[package]] name = "polling" version = "2.8.0" @@ -22882,7 +22967,7 @@ dependencies = [ name = "sc-executor-common" version = "0.29.0" dependencies = [ - "polkavm 0.9.3", + "polkavm 0.18.0", "sc-allocator 23.0.0", "sp-maybe-compressed-blob 11.0.0", "sp-wasm-interface 20.0.0", @@ -22923,7 +23008,7 @@ name = "sc-executor-polkavm" version = "0.29.0" dependencies = [ "log", - "polkavm 0.9.3", + "polkavm 0.18.0", "sc-executor-common 0.29.0", "sp-wasm-interface 20.0.0", ] @@ -26713,7 +26798,7 @@ dependencies = [ "libsecp256k1", "log", "parity-scale-codec", - "polkavm-derive 0.17.0", + "polkavm-derive 0.18.0", "rustversion", "secp256k1 0.28.2", "sp-core 28.0.0", @@ -27197,7 +27282,7 @@ dependencies = [ "bytes", "impl-trait-for-tuples", "parity-scale-codec", - "polkavm-derive 0.17.0", + "polkavm-derive 0.18.0", "primitive-types 0.13.1", "rustversion", "sp-core 28.0.0", @@ -28841,7 +28926,7 @@ dependencies = [ "merkleized-metadata", "parity-scale-codec", "parity-wasm", - "polkavm-linker 0.17.1", + "polkavm-linker 0.18.0", "sc-executor 0.32.0", "shlex", "sp-core 28.0.0", diff --git a/Cargo.toml b/Cargo.toml index 63f17efb98b5..62a5ada6cd41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1089,9 +1089,9 @@ polkadot-subsystem-bench = { path = "polkadot/node/subsystem-bench" } polkadot-test-client = { path = "polkadot/node/test/client" } polkadot-test-runtime = { path = "polkadot/runtime/test-runtime" } polkadot-test-service = { path = "polkadot/node/test/service" } -polkavm = { version = "0.9.3", default-features = false } -polkavm-derive = "0.17.0" -polkavm-linker = "0.17.1" +polkavm = { version = "0.18.0", default-features = false } +polkavm-derive = "0.18.0" +polkavm-linker = "0.18.0" portpicker = { version = "0.1.1" } pretty_assertions = { version = "1.3.0" } primitive-types = { version = "0.13.1", default-features = false, features = [ diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 3304f89fc0cc..2303e1212634 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -20,6 +20,17 @@ // `construct_runtime!` does a lot of recursion and requires us to increase the limit. #![recursion_limit = "512"] +#[cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), target_feature = "e"))] +// Allocate 2 MiB stack. +// +// TODO: A workaround. Invoke polkavm_derive::min_stack_size!() instead +// later on. +::core::arch::global_asm!( + ".pushsection .polkavm_min_stack_size,\"R\",@note\n", + ".4byte 2097152", + ".popsection\n", +); + extern crate alloc; use alloc::{ diff --git a/prdoc/pr_6533.prdoc b/prdoc/pr_6533.prdoc new file mode 100644 index 000000000000..eb72a97db0f8 --- /dev/null +++ b/prdoc/pr_6533.prdoc @@ -0,0 +1,20 @@ +title: "Migrate executor into PolkaVM 0.18.0" +doc: + - audience: Runtime Dev + description: | + Bump `polkavm` to 0.18.0, and update `sc-polkavm-executor` to be + compatible with the API changes. In addition, bump also `polkavm-derive` + and `polkavm-linker` in order to make sure that the all parts of the + Polkadot SDK use the exact same ABI for `.polkavm` binaries. + + Purely relying on RV32E/RV64E ABI is not possible, as PolkaVM uses a + RISCV-V alike ISA, which is derived from RV32E/RV64E but it is still its + own microarchitecture, i.e. not fully binary compatible. + +crates: + - name: sc-executor-common + bump: major + - name: sc-executor-polkavm + bump: minor + - name: substrate-wasm-builder + bump: minor diff --git a/substrate/client/executor/common/src/error.rs b/substrate/client/executor/common/src/error.rs index 9d489eaae420..a94c1d493134 100644 --- a/substrate/client/executor/common/src/error.rs +++ b/substrate/client/executor/common/src/error.rs @@ -150,8 +150,8 @@ pub enum WasmError { Other(String), } -impl From for WasmError { - fn from(error: polkavm::ProgramParseError) -> Self { +impl From for WasmError { + fn from(error: polkavm::program::ProgramParseError) -> Self { WasmError::Other(error.to_string()) } } diff --git a/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs b/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs index d689083b2f85..e3f4b4ad9774 100644 --- a/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/substrate/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -17,6 +17,7 @@ // along with this program. If not, see . use crate::{error::WasmError, wasm_runtime::HeapAllocStrategy}; +use polkavm::ArcBytes; use wasm_instrument::parity_wasm::elements::{ deserialize_buffer, serialize, ExportEntry, External, Internal, MemorySection, MemoryType, Module, Section, @@ -29,7 +30,7 @@ pub struct RuntimeBlob(BlobKind); #[derive(Clone)] enum BlobKind { WebAssembly(Module), - PolkaVM(polkavm::ProgramBlob<'static>), + PolkaVM((polkavm::ProgramBlob, ArcBytes)), } impl RuntimeBlob { @@ -52,9 +53,9 @@ impl RuntimeBlob { pub fn new(raw_blob: &[u8]) -> Result { if raw_blob.starts_with(b"PVM\0") { if crate::is_polkavm_enabled() { - return Ok(Self(BlobKind::PolkaVM( - polkavm::ProgramBlob::parse(raw_blob)?.into_owned(), - ))); + let raw = ArcBytes::from(raw_blob); + let blob = polkavm::ProgramBlob::parse(raw.clone())?; + return Ok(Self(BlobKind::PolkaVM((blob, raw)))); } else { return Err(WasmError::Other("expected a WASM runtime blob, found a PolkaVM runtime blob; set the 'SUBSTRATE_ENABLE_POLKAVM' environment variable to enable the experimental PolkaVM-based executor".to_string())); } @@ -192,7 +193,7 @@ impl RuntimeBlob { match self.0 { BlobKind::WebAssembly(raw_module) => serialize(raw_module).expect("serializing into a vec should succeed; qed"), - BlobKind::PolkaVM(ref blob) => blob.as_bytes().to_vec(), + BlobKind::PolkaVM(ref blob) => blob.1.to_vec(), } } @@ -227,7 +228,7 @@ impl RuntimeBlob { pub fn as_polkavm_blob(&self) -> Option<&polkavm::ProgramBlob> { match self.0 { BlobKind::WebAssembly(..) => None, - BlobKind::PolkaVM(ref blob) => Some(blob), + BlobKind::PolkaVM((ref blob, _)) => Some(blob), } } } diff --git a/substrate/client/executor/polkavm/src/lib.rs b/substrate/client/executor/polkavm/src/lib.rs index 1bd72eb33d30..134f9ea3d8c4 100644 --- a/substrate/client/executor/polkavm/src/lib.rs +++ b/substrate/client/executor/polkavm/src/lib.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use polkavm::{Caller, Reg}; +use polkavm::{CallError, Caller, Reg}; use sc_executor_common::{ error::{Error, WasmError}, wasm_runtime::{AllocationStats, WasmInstance, WasmModule}, @@ -26,10 +26,10 @@ use sp_wasm_interface::{ }; #[repr(transparent)] -pub struct InstancePre(polkavm::InstancePre<()>); +pub struct InstancePre(polkavm::InstancePre<(), String>); #[repr(transparent)] -pub struct Instance(polkavm::Instance<()>); +pub struct Instance(polkavm::Instance<(), String>); impl WasmModule for InstancePre { fn new_instance(&self) -> Result, Error> { @@ -43,11 +43,13 @@ impl WasmInstance for Instance { name: &str, raw_data: &[u8], ) -> (Result, Error>, Option) { - let Some(method_index) = self.0.module().lookup_export(name) else { - return ( - Err(format!("cannot call into the runtime: export not found: '{name}'").into()), - None, - ); + let pc = match self.0.module().exports().find(|e| e.symbol() == name) { + Some(export) => export.program_counter(), + None => + return ( + Err(format!("cannot call into the runtime: export not found: '{name}'").into()), + None, + ), }; let Ok(raw_data_length) = u32::try_from(raw_data.len()) else { @@ -58,56 +60,60 @@ impl WasmInstance for Instance { }; // TODO: This will leak guest memory; find a better solution. - let mut state_args = polkavm::StateArgs::new(); - // Make sure the memory is cleared... - state_args.reset_memory(true); - // ...and allocate space for the input payload. - state_args.sbrk(raw_data_length); + // Make sure that the memory is cleared... + if let Err(err) = self.0.reset_memory() { + return ( + Err(format!( + "call into the runtime method '{name}' failed: reset memory failed: {err}" + ) + .into()), + None, + ); + } - match self.0.update_state(state_args) { - Ok(()) => {}, - Err(polkavm::ExecutionError::Trap(trap)) => { - return (Err(format!("call into the runtime method '{name}' failed: failed to prepare the guest's memory: {trap}").into()), None); - }, - Err(polkavm::ExecutionError::Error(error)) => { - return (Err(format!("call into the runtime method '{name}' failed: failed to prepare the guest's memory: {error}").into()), None); - }, - Err(polkavm::ExecutionError::OutOfGas) => unreachable!("gas metering is never enabled"), + // ... and allocate space for the input payload. + if let Err(err) = self.0.sbrk(raw_data_length) { + return ( + Err(format!( + "call into the runtime method '{name}' failed: reset memory failed: {err}" + ) + .into()), + None, + ); } // Grab the address of where the guest's heap starts; that's where we've just allocated // the memory for the input payload. let data_pointer = self.0.module().memory_map().heap_base(); - if let Err(error) = self.0.write_memory(data_pointer, raw_data) { - return (Err(format!("call into the runtime method '{name}': failed to write the input payload into guest memory: {error}").into()), None); + if let Err(err) = self.0.write_memory(data_pointer, raw_data) { + return (Err(format!("call into the runtime method '{name}': failed to write the input payload into guest memory: {err}").into()), None); } - let mut state = (); - let mut call_args = polkavm::CallArgs::new(&mut state, method_index); - call_args.args_untyped(&[data_pointer, raw_data_length]); - - match self.0.call(Default::default(), call_args) { + match self.0.call_typed(&mut (), pc, (data_pointer, raw_data_length)) { Ok(()) => {}, - Err(polkavm::ExecutionError::Trap(trap)) => { + Err(CallError::Trap) => return ( - Err(format!("call into the runtime method '{name}' failed: {trap}").into()), + Err(format!("call into the runtime method '{name}' failed: trap").into()), None, - ); - }, - Err(polkavm::ExecutionError::Error(error)) => { + ), + Err(CallError::Error(err)) => return ( - Err(format!("call into the runtime method '{name}' failed: {error}").into()), + Err(format!("call into the runtime method '{name}' failed: {err}").into()), None, - ); - }, - Err(polkavm::ExecutionError::OutOfGas) => unreachable!("gas metering is never enabled"), - } + ), + Err(CallError::User(err)) => + return ( + Err(format!("call into the runtime method '{name}' failed: {err}").into()), + None, + ), + Err(CallError::NotEnoughGas) => unreachable!("gas metering is never enabled"), + }; - let result_pointer = self.0.get_reg(Reg::A0); - let result_length = self.0.get_reg(Reg::A1); - let output = match self.0.read_memory_into_vec(result_pointer, result_length) { + let result_pointer = self.0.reg(Reg::A0); + let result_length = self.0.reg(Reg::A1); + let output = match self.0.read_memory(result_pointer as u32, result_length as u32) { Ok(output) => output, Err(error) => { return (Err(format!("call into the runtime method '{name}' failed: failed to read the return payload: {error}").into()), None) @@ -127,20 +133,31 @@ impl<'r, 'a> FunctionContext for Context<'r, 'a> { dest: &mut [u8], ) -> sp_wasm_interface::Result<()> { self.0 - .read_memory_into_slice(u32::from(address), dest) + .instance + .read_memory_into(u32::from(address), dest) .map_err(|error| error.to_string()) .map(|_| ()) } fn write_memory(&mut self, address: Pointer, data: &[u8]) -> sp_wasm_interface::Result<()> { - self.0.write_memory(u32::from(address), data).map_err(|error| error.to_string()) + self.0 + .instance + .write_memory(u32::from(address), data) + .map_err(|error| error.to_string()) } fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { - let pointer = self.0.sbrk(0).expect("fetching the current heap pointer never fails"); + let pointer = match self.0.instance.sbrk(0) { + Ok(pointer) => pointer.expect("fetching the current heap pointer never fails"), + Err(err) => return Err(format!("sbrk failed: {err}")), + }; // TODO: This will leak guest memory; find a better solution. - self.0.sbrk(size).ok_or_else(|| String::from("allocation failed"))?; + match self.0.instance.sbrk(size) { + Ok(Some(_)) => (), + Ok(None) => return Err(String::from("allocation error")), + Err(err) => return Err(format!("sbrk failed: {err}")), + } Ok(Pointer::new(pointer)) } @@ -155,41 +172,46 @@ impl<'r, 'a> FunctionContext for Context<'r, 'a> { } } -fn call_host_function( - caller: &mut Caller<()>, - function: &dyn Function, -) -> Result<(), polkavm::Trap> { +fn call_host_function(caller: &mut Caller<()>, function: &dyn Function) -> Result<(), String> { let mut args = [Value::I64(0); Reg::ARG_REGS.len()]; let mut nth_reg = 0; for (nth_arg, kind) in function.signature().args.iter().enumerate() { match kind { ValueType::I32 => { - args[nth_arg] = Value::I32(caller.get_reg(Reg::ARG_REGS[nth_reg]) as i32); + args[nth_arg] = Value::I32(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as i32); nth_reg += 1; }, ValueType::F32 => { - args[nth_arg] = Value::F32(caller.get_reg(Reg::ARG_REGS[nth_reg])); - nth_reg += 1; - }, - ValueType::I64 => { - let value_lo = caller.get_reg(Reg::ARG_REGS[nth_reg]); - nth_reg += 1; - - let value_hi = caller.get_reg(Reg::ARG_REGS[nth_reg]); - nth_reg += 1; - - args[nth_arg] = - Value::I64((u64::from(value_lo) | (u64::from(value_hi) << 32)) as i64); - }, - ValueType::F64 => { - let value_lo = caller.get_reg(Reg::ARG_REGS[nth_reg]); + args[nth_arg] = Value::F32(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as u32); nth_reg += 1; - - let value_hi = caller.get_reg(Reg::ARG_REGS[nth_reg]); - nth_reg += 1; - - args[nth_arg] = Value::F64(u64::from(value_lo) | (u64::from(value_hi) << 32)); }, + ValueType::I64 => + if caller.instance.is_64_bit() { + args[nth_arg] = Value::I64(caller.instance.reg(Reg::ARG_REGS[nth_reg]) as i64); + nth_reg += 1; + } else { + let value_lo = caller.instance.reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + let value_hi = caller.instance.reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + args[nth_arg] = + Value::I64((u64::from(value_lo) | (u64::from(value_hi) << 32)) as i64); + }, + ValueType::F64 => + if caller.instance.is_64_bit() { + args[nth_arg] = Value::F64(caller.instance.reg(Reg::ARG_REGS[nth_reg])); + nth_reg += 1; + } else { + let value_lo = caller.instance.reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + let value_hi = caller.instance.reg(Reg::ARG_REGS[nth_reg]); + nth_reg += 1; + + args[nth_arg] = Value::F64(u64::from(value_lo) | (u64::from(value_hi) << 32)); + }, } } @@ -204,27 +226,33 @@ fn call_host_function( { Ok(value) => value, Err(error) => { - log::warn!("Call into the host function '{}' failed: {error}", function.name()); - return Err(polkavm::Trap::default()); + let name = function.name(); + return Err(format!("call into the host function '{name}' failed: {error}")) }, }; if let Some(value) = value { match value { Value::I32(value) => { - caller.set_reg(Reg::A0, value as u32); + caller.instance.set_reg(Reg::A0, value as u64); }, Value::F32(value) => { - caller.set_reg(Reg::A0, value); - }, - Value::I64(value) => { - caller.set_reg(Reg::A0, value as u32); - caller.set_reg(Reg::A1, (value >> 32) as u32); - }, - Value::F64(value) => { - caller.set_reg(Reg::A0, value as u32); - caller.set_reg(Reg::A1, (value >> 32) as u32); + caller.instance.set_reg(Reg::A0, value as u64); }, + Value::I64(value) => + if caller.instance.is_64_bit() { + caller.instance.set_reg(Reg::A0, value as u64); + } else { + caller.instance.set_reg(Reg::A0, value as u64); + caller.instance.set_reg(Reg::A1, (value >> 32) as u64); + }, + Value::F64(value) => + if caller.instance.is_64_bit() { + caller.instance.set_reg(Reg::A0, value as u64); + } else { + caller.instance.set_reg(Reg::A0, value as u64); + caller.instance.set_reg(Reg::A1, (value >> 32) as u64); + }, } } @@ -250,12 +278,16 @@ where }, }; - let module = polkavm::Module::from_blob(&engine, &polkavm::ModuleConfig::default(), blob)?; - let mut linker = polkavm::Linker::new(&engine); + let module = + polkavm::Module::from_blob(&engine, &polkavm::ModuleConfig::default(), blob.clone())?; + + let mut linker = polkavm::Linker::new(); + for function in H::host_functions() { - linker.func_new(function.name(), |mut caller| call_host_function(&mut caller, function))?; + linker.define_untyped(function.name(), |mut caller: Caller<()>| { + call_host_function(&mut caller, function) + })?; } - let instance_pre = linker.instantiate_pre(&module)?; Ok(Box::new(InstancePre(instance_pre))) } From 88d255c2fd9d7d082449d9892543849b25d53407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Sun, 15 Dec 2024 22:39:34 +0100 Subject: [PATCH 169/340] Fix flaky `build-runtimes-polkavm` CI job (#6893) The timeout was too low which made the job not finish in time sometimes: Hence: - Bumping the timeout to 60 minutes which is in line with other jobs which are building substantial parts of the repo. - Roll all the runtime builds into a single cargo invocation so that it aborts after the first failure. It also allows for more parallel compiling. --- .github/workflows/build-misc.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-misc.yml b/.github/workflows/build-misc.yml index c4a7281b9ebc..335c26282027 100644 --- a/.github/workflows/build-misc.yml +++ b/.github/workflows/build-misc.yml @@ -20,7 +20,7 @@ jobs: uses: ./.github/workflows/reusable-preflight.yml build-runtimes-polkavm: - timeout-minutes: 20 + timeout-minutes: 60 needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} container: @@ -38,11 +38,7 @@ jobs: env: SUBSTRATE_RUNTIME_TARGET: riscv id: required - run: | - forklift cargo check -p minimal-template-runtime - forklift cargo check -p westend-runtime - forklift cargo check -p rococo-runtime - forklift cargo check -p polkadot-test-runtime + run: forklift cargo check -p minimal-template-runtime -p westend-runtime -p rococo-runtime -p polkadot-test-runtime - name: Stop all workflows if failed if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} uses: ./.github/actions/workflow-stopper From c88128832f95bb916c8b1b5eb45a34bf78ec998a Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Mon, 16 Dec 2024 08:03:52 +0200 Subject: [PATCH 170/340] Upgrade libp2p from 0.52.4 to 0.54.1 (#6248) # Description Fixes https://github.com/paritytech/polkadot-sdk/issues/5996 https://github.com/libp2p/rust-libp2p/releases/tag/libp2p-v0.53.0 https://github.com/libp2p/rust-libp2p/blob/master/CHANGELOG.md ## Integration Nothing special is needed, just note that `yamux_window_size` is no longer applicable to libp2p (litep2p seems to still have it though). ## Review Notes There are a few simplifications and improvements done in libp2p 0.53 regarding swarm interface, I'll list a few key/applicable here. https://github.com/libp2p/rust-libp2p/pull/4788 removed `write_length_prefixed` function, so I inlined its code instead. https://github.com/libp2p/rust-libp2p/pull/4120 introduced new `libp2p::SwarmBuilder` instead of now deprecated `libp2p::swarm::SwarmBuilder`, the transition is straightforward and quite ergonomic (can be seen in tests). https://github.com/libp2p/rust-libp2p/pull/4581 is the most annoying change I have seen that basically makes many enums `#[non_exhaustive]`. I mapped some, but those that couldn't be mapped I dealt with by printing log messages once they are hit (the best solution I could come up with, at least with stable Rust). https://github.com/libp2p/rust-libp2p/issues/4306 makes connection close as soon as there are no handler using it, so I had to replace `KeepAlive::Until` with an explicit future that flips internal boolean after timeout, achieving the old behavior, though it should ideally be removed completely at some point. `yamux_window_size` is no longer used by libp2p thanks to https://github.com/libp2p/rust-libp2p/pull/4970 and generally Yamux should have a higher performance now. I have resolved and cleaned up all deprecations related to libp2p except `BandwidthSinks`. Libp2p deprecated it (though it is still present in 0.54.1, which is why I didn't handle it just yet). Ideally Substrate would finally [switch to the official Prometheus client](https://github.com/paritytech/substrate/issues/12699), in which case we'd get metrics for free. Otherwise a bit of code will need to be copy-pasted to maintain current behavior with `BandwidthSinks` gone, which I left a TODO about. The biggest change in 0.54.0 is https://github.com/libp2p/rust-libp2p/pull/4568 that changed transport APIs and enabled unconditional potential port reuse, which can lead to very confusing errors if running two Substrate nodes on the same machine without changing listening port explicitly. Overall nothing scary here, but testing is always appreciated. # Checklist * [x] My PR includes a detailed description as outlined in the "Description" and its two subsections above. * [x] My PR follows the [labeling requirements]( https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process ) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. --- Polkadot Address: 1vSxzbyz2cJREAuVWjhXUT1ds8vBzoxn2w4asNpusQKwjJd --------- Co-authored-by: Dmitry Markin --- Cargo.lock | 682 ++++++++---------- Cargo.toml | 2 +- prdoc/pr_6248.prdoc | 16 + substrate/client/network/src/behaviour.rs | 1 + substrate/client/network/src/discovery.rs | 174 +++-- substrate/client/network/src/network_state.rs | 2 +- substrate/client/network/src/peer_info.rs | 91 +-- substrate/client/network/src/protocol.rs | 37 +- .../network/src/protocol/notifications.rs | 2 +- .../src/protocol/notifications/behaviour.rs | 102 ++- .../src/protocol/notifications/handler.rs | 123 ++-- .../src/protocol/notifications/tests.rs | 324 ++++----- .../notifications/upgrade/notifications.rs | 30 +- .../client/network/src/request_responses.rs | 193 ++--- substrate/client/network/src/service.rs | 88 +-- substrate/client/network/src/transport.rs | 27 +- substrate/client/network/sync/src/engine.rs | 9 +- substrate/client/network/types/Cargo.toml | 2 +- substrate/client/telemetry/Cargo.toml | 2 +- substrate/client/telemetry/src/node.rs | 13 +- 20 files changed, 850 insertions(+), 1070 deletions(-) create mode 100644 prdoc/pr_6248.prdoc diff --git a/Cargo.lock b/Cargo.lock index 7f9f3198e570..0902fe6fcfbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -841,30 +841,14 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" -[[package]] -name = "asn1-rs" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" -dependencies = [ - "asn1-rs-derive 0.4.0", - "asn1-rs-impl 0.1.0", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror", - "time", -] - [[package]] name = "asn1-rs" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ad1373757efa0f70ec53939aabc7152e1591cb485208052993070ac8d2429d" dependencies = [ - "asn1-rs-derive 0.5.0", - "asn1-rs-impl 0.2.0", + "asn1-rs-derive", + "asn1-rs-impl", "displaydoc", "nom", "num-traits", @@ -873,18 +857,6 @@ dependencies = [ "time", ] -[[package]] -name = "asn1-rs-derive" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 1.0.109", - "synstructure 0.12.6", -] - [[package]] name = "asn1-rs-derive" version = "0.5.0" @@ -897,17 +869,6 @@ dependencies = [ "synstructure 0.13.1", ] -[[package]] -name = "asn1-rs-impl" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 1.0.109", -] - [[package]] name = "asn1-rs-impl" version = "0.2.0" @@ -1618,6 +1579,19 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + [[package]] name = "atomic-take" version = "1.1.0" @@ -5906,9 +5880,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.4.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "data-encoding-macro" @@ -5949,27 +5923,13 @@ dependencies = [ "zeroize", ] -[[package]] -name = "der-parser" -version = "8.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" -dependencies = [ - "asn1-rs 0.5.2", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - [[package]] name = "der-parser" version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ - "asn1-rs 0.6.1", + "asn1-rs", "displaydoc", "nom", "num-bigint", @@ -6437,18 +6397,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "enum-as-inner" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" -dependencies = [ - "heck 0.4.1", - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 1.0.109", -] - [[package]] name = "enum-as-inner" version = "0.6.0" @@ -7588,7 +7536,7 @@ dependencies = [ "macro_magic", "parity-scale-codec", "pretty_assertions", - "proc-macro-warning 1.0.0", + "proc-macro-warning", "proc-macro2 1.0.86", "quote 1.0.37", "regex", @@ -7615,7 +7563,7 @@ dependencies = [ "frame-support-procedural-tools 13.0.0", "itertools 0.11.0", "macro_magic", - "proc-macro-warning 1.0.0", + "proc-macro-warning", "proc-macro2 1.0.86", "quote 1.0.37", "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -7898,9 +7846,9 @@ dependencies = [ [[package]] name = "futures-bounded" -version = "0.1.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b07bbbe7d7e78809544c6f718d875627addc73a7c3582447abc052cd3dc67e0" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" dependencies = [ "futures-timer", "futures-util", @@ -7981,12 +7929,13 @@ dependencies = [ [[package]] name = "futures-rustls" -version = "0.24.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd3cf68c183738046838e300353e4716c674dc5e56890de4826801a6622a28" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.21.7", + "rustls 0.23.18", + "rustls-pki-types", ] [[package]] @@ -8008,7 +7957,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers", - "send_wrapper 0.4.0", + "send_wrapper", ] [[package]] @@ -8471,7 +8420,7 @@ dependencies = [ "async-trait", "cfg-if", "data-encoding", - "enum-as-inner 0.6.0", + "enum-as-inner", "futures-channel", "futures-io", "futures-util", @@ -8479,6 +8428,7 @@ dependencies = [ "ipnet", "once_cell", "rand", + "socket2 0.5.7", "thiserror", "tinyvec", "tokio", @@ -8813,17 +8763,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.4.0" @@ -9826,9 +9765,31 @@ dependencies = [ "futures-timer", "getrandom", "instant", - "libp2p-allow-block-list", - "libp2p-connection-limits", - "libp2p-core", + "libp2p-allow-block-list 0.2.0", + "libp2p-connection-limits 0.2.1", + "libp2p-core 0.40.1", + "libp2p-identity", + "libp2p-swarm 0.43.7", + "multiaddr 0.18.1", + "pin-project", + "rw-stream-sink", + "thiserror", +] + +[[package]] +name = "libp2p" +version = "0.54.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbe80f9c7e00526cd6b838075b9c171919404a4732cb2fa8ece0a093223bfc4" +dependencies = [ + "bytes", + "either", + "futures", + "futures-timer", + "getrandom", + "libp2p-allow-block-list 0.4.0", + "libp2p-connection-limits 0.4.0", + "libp2p-core 0.42.0", "libp2p-dns", "libp2p-identify", "libp2p-identity", @@ -9839,10 +9800,9 @@ dependencies = [ "libp2p-ping", "libp2p-quic", "libp2p-request-response", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "libp2p-tcp", "libp2p-upnp", - "libp2p-wasm-ext", "libp2p-websocket", "libp2p-yamux", "multiaddr 0.18.1", @@ -9857,9 +9817,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55b46558c5c0bf99d3e2a1a38fd54ff5476ca66dd1737b12466a1824dd219311" dependencies = [ - "libp2p-core", + "libp2p-core 0.40.1", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.43.7", + "void", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1027ccf8d70320ed77e984f273bc8ce952f623762cb9bf2d126df73caef8041" +dependencies = [ + "libp2p-core 0.42.0", + "libp2p-identity", + "libp2p-swarm 0.45.1", "void", ] @@ -9869,9 +9841,21 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f5107ad45cb20b2f6c3628c7b6014b996fcb13a88053f4569c872c6e30abf58" dependencies = [ - "libp2p-core", + "libp2p-core 0.40.1", + "libp2p-identity", + "libp2p-swarm 0.43.7", + "void", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d003540ee8baef0d254f7b6bfd79bac3ddf774662ca0abf69186d517ef82ad8" +dependencies = [ + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", + "libp2p-swarm 0.45.1", "void", ] @@ -9903,42 +9887,70 @@ dependencies = [ "void", ] +[[package]] +name = "libp2p-core" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a61f26c83ed111104cd820fe9bc3aaabbac5f1652a1d213ed6e900b7918a1298" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-identity", + "multiaddr 0.18.1", + "multihash 0.19.1", + "multistream-select", + "once_cell", + "parking_lot 0.12.3", + "pin-project", + "quick-protobuf 0.8.1", + "rand", + "rw-stream-sink", + "smallvec", + "thiserror", + "tracing", + "unsigned-varint 0.8.0", + "void", + "web-time", +] + [[package]] name = "libp2p-dns" -version = "0.40.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6a18db73084b4da2871438f6239fef35190b05023de7656e877c18a00541a3b" +checksum = "97f37f30d5c7275db282ecd86e54f29dd2176bd3ac656f06abf43bedb21eb8bd" dependencies = [ "async-trait", "futures", - "libp2p-core", + "hickory-resolver", + "libp2p-core 0.42.0", "libp2p-identity", - "log", "parking_lot 0.12.3", "smallvec", - "trust-dns-resolver", + "tracing", ] [[package]] name = "libp2p-identify" -version = "0.43.1" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a96638a0a176bec0a4bcaebc1afa8cf909b114477209d7456ade52c61cd9cd" +checksum = "1711b004a273be4f30202778856368683bd9a83c4c7dcc8f848847606831a4e3" dependencies = [ - "asynchronous-codec", + "asynchronous-codec 0.7.0", "either", "futures", "futures-bounded", "futures-timer", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", - "log", + "libp2p-swarm 0.45.1", "lru 0.12.3", "quick-protobuf 0.8.1", "quick-protobuf-codec", "smallvec", "thiserror", + "tracing", "void", ] @@ -9962,83 +9974,84 @@ dependencies = [ [[package]] name = "libp2p-kad" -version = "0.44.6" +version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ea178dabba6dde6ffc260a8e0452ccdc8f79becf544946692fff9d412fc29d" +checksum = "ced237d0bd84bbebb7c2cad4c073160dacb4fe40534963c32ed6d4c6bb7702a3" dependencies = [ "arrayvec 0.7.4", - "asynchronous-codec", + "asynchronous-codec 0.7.0", "bytes", "either", "fnv", "futures", + "futures-bounded", "futures-timer", - "instant", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", - "log", + "libp2p-swarm 0.45.1", "quick-protobuf 0.8.1", "quick-protobuf-codec", "rand", "sha2 0.10.8", "smallvec", "thiserror", + "tracing", "uint 0.9.5", - "unsigned-varint 0.7.2", "void", + "web-time", ] [[package]] name = "libp2p-mdns" -version = "0.44.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a2567c305232f5ef54185e9604579a894fd0674819402bb0ac0246da82f52a" +checksum = "14b8546b6644032565eb29046b42744aee1e9f261ed99671b2c93fb140dba417" dependencies = [ "data-encoding", "futures", + "hickory-proto", "if-watch", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", - "log", + "libp2p-swarm 0.45.1", "rand", "smallvec", "socket2 0.5.7", "tokio", - "trust-dns-proto 0.22.0", + "tracing", "void", ] [[package]] name = "libp2p-metrics" -version = "0.13.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239ba7d28f8d0b5d77760dc6619c05c7e88e74ec8fbbe97f856f20a56745e620" +checksum = "77ebafa94a717c8442d8db8d3ae5d1c6a15e30f2d347e0cd31d057ca72e42566" dependencies = [ - "instant", - "libp2p-core", + "futures", + "libp2p-core 0.42.0", "libp2p-identify", "libp2p-identity", "libp2p-kad", "libp2p-ping", - "libp2p-swarm", - "once_cell", + "libp2p-swarm 0.45.1", + "pin-project", "prometheus-client", + "web-time", ] [[package]] name = "libp2p-noise" -version = "0.43.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2eeec39ad3ad0677551907dd304b2f13f17208ccebe333bef194076cd2e8921" +checksum = "36b137cb1ae86ee39f8e5d6245a296518912014eaa87427d24e6ff58cfc1b28c" dependencies = [ + "asynchronous-codec 0.7.0", "bytes", "curve25519-dalek 4.1.3", "futures", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "log", "multiaddr 0.18.1", "multihash 0.19.1", "once_cell", @@ -10048,68 +10061,71 @@ dependencies = [ "snow", "static_assertions", "thiserror", + "tracing", "x25519-dalek", "zeroize", ] [[package]] name = "libp2p-ping" -version = "0.43.1" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e702d75cd0827dfa15f8fd92d15b9932abe38d10d21f47c50438c71dd1b5dae3" +checksum = "005a34420359223b974ee344457095f027e51346e992d1e0dcd35173f4cdd422" dependencies = [ "either", "futures", "futures-timer", - "instant", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", - "log", + "libp2p-swarm 0.45.1", "rand", + "tracing", "void", + "web-time", ] [[package]] name = "libp2p-quic" -version = "0.9.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "130d451d83f21b81eb7b35b360bc7972aeafb15177784adc56528db082e6b927" +checksum = "46352ac5cd040c70e88e7ff8257a2ae2f891a4076abad2c439584a31c15fd24e" dependencies = [ "bytes", "futures", "futures-timer", "if-watch", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", "libp2p-tls", - "log", "parking_lot 0.12.3", - "quinn 0.10.2", + "quinn", "rand", - "ring 0.16.20", - "rustls 0.21.7", + "ring 0.17.8", + "rustls 0.23.18", "socket2 0.5.7", "thiserror", "tokio", + "tracing", ] [[package]] name = "libp2p-request-response" -version = "0.25.3" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e3b4d67870478db72bac87bfc260ee6641d0734e0e3e275798f089c3fecfd4" +checksum = "1356c9e376a94a75ae830c42cdaea3d4fe1290ba409a22c809033d1b7dcab0a6" dependencies = [ "async-trait", "futures", - "instant", - "libp2p-core", + "futures-bounded", + "futures-timer", + "libp2p-core 0.42.0", "libp2p-identity", - "libp2p-swarm", - "log", + "libp2p-swarm 0.45.1", "rand", "smallvec", + "tracing", "void", + "web-time", ] [[package]] @@ -10123,26 +10139,47 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.40.1", "libp2p-identity", - "libp2p-swarm-derive", "log", "multistream-select", "once_cell", "rand", "smallvec", + "void", +] + +[[package]] +name = "libp2p-swarm" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dd6741793d2c1fb2088f67f82cf07261f25272ebe3c0b0c311e0c6b50e851a" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-core 0.42.0", + "libp2p-identity", + "libp2p-swarm-derive", + "lru 0.12.3", + "multistream-select", + "once_cell", + "rand", + "smallvec", "tokio", + "tracing", "void", + "web-time", ] [[package]] name = "libp2p-swarm-derive" -version = "0.33.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d5ec2a3df00c7836d7696c136274c9c59705bac69133253696a6c932cd1d74" +checksum = "206e0aa0ebe004d778d79fb0966aa0de996c19894e2c0605ba2f8524dd4443d8" dependencies = [ - "heck 0.4.1", - "proc-macro-warning 0.4.2", + "heck 0.5.0", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.87", @@ -10150,102 +10187,90 @@ dependencies = [ [[package]] name = "libp2p-tcp" -version = "0.40.1" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b558dd40d1bcd1aaaed9de898e9ec6a436019ecc2420dd0016e712fbb61c5508" +checksum = "ad964f312c59dcfcac840acd8c555de8403e295d39edf96f5240048b5fcaa314" dependencies = [ "futures", "futures-timer", "if-watch", "libc", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "log", "socket2 0.5.7", "tokio", + "tracing", ] [[package]] name = "libp2p-tls" -version = "0.2.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8218d1d5482b122ccae396bbf38abdcb283ecc96fa54760e1dfd251f0546ac61" +checksum = "47b23dddc2b9c355f73c1e36eb0c3ae86f7dc964a3715f0731cfad352db4d847" dependencies = [ "futures", "futures-rustls", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "rcgen", - "ring 0.16.20", - "rustls 0.21.7", + "rcgen 0.11.3", + "ring 0.17.8", + "rustls 0.23.18", "rustls-webpki 0.101.4", "thiserror", - "x509-parser 0.15.1", + "x509-parser", "yasna", ] [[package]] name = "libp2p-upnp" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82775a47b34f10f787ad3e2a22e2c1541e6ebef4fe9f28f3ac553921554c94c1" +checksum = "01bf2d1b772bd3abca049214a3304615e6a36fa6ffc742bdd1ba774486200b8f" dependencies = [ "futures", "futures-timer", "igd-next", - "libp2p-core", - "libp2p-swarm", - "log", + "libp2p-core 0.42.0", + "libp2p-swarm 0.45.1", "tokio", + "tracing", "void", ] -[[package]] -name = "libp2p-wasm-ext" -version = "0.40.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e5d8e3a9e07da0ef5b55a9f26c009c8fb3c725d492d8bb4b431715786eea79c" -dependencies = [ - "futures", - "js-sys", - "libp2p-core", - "send_wrapper 0.6.0", - "wasm-bindgen", - "wasm-bindgen-futures", -] - [[package]] name = "libp2p-websocket" -version = "0.42.2" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004ee9c4a4631435169aee6aad2f62e3984dc031c43b6d29731e8e82a016c538" +checksum = "888b2ff2e5d8dcef97283daab35ad1043d18952b65e05279eecbe02af4c6e347" dependencies = [ "either", "futures", "futures-rustls", - "libp2p-core", + "libp2p-core 0.42.0", "libp2p-identity", - "log", "parking_lot 0.12.3", "pin-project-lite", "rw-stream-sink", "soketto 0.8.0", "thiserror", + "tracing", "url", "webpki-roots 0.25.2", ] [[package]] name = "libp2p-yamux" -version = "0.44.1" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eedcb62824c4300efb9cfd4e2a6edaf3ca097b9e68b36dabe45a44469fd6a85" +checksum = "788b61c80789dba9760d8c669a5bedb642c8267555c803fabd8396e4ca5c5882" dependencies = [ + "either", "futures", - "libp2p-core", - "log", + "libp2p-core 0.42.0", "thiserror", - "yamux", + "tracing", + "yamux 0.12.1", + "yamux 0.13.3", ] [[package]] @@ -10431,7 +10456,7 @@ dependencies = [ "prost 0.12.6", "prost-build", "rand", - "rcgen", + "rcgen 0.10.0", "ring 0.16.20", "rustls 0.20.9", "serde", @@ -10451,7 +10476,7 @@ dependencies = [ "unsigned-varint 0.8.0", "url", "x25519-dalek", - "x509-parser 0.16.0", + "x509-parser", "yasna", "zeroize", ] @@ -10644,12 +10669,6 @@ dependencies = [ "regex-automata 0.1.10", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "matrixmultiply" version = "0.3.7" @@ -11652,22 +11671,13 @@ dependencies = [ "memchr", ] -[[package]] -name = "oid-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" -dependencies = [ - "asn1-rs 0.5.2", -] - [[package]] name = "oid-registry" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c958dd45046245b9c3c2547369bb634eb461670b2e7e0de552905801a648d1d" dependencies = [ - "asn1-rs 0.6.1", + "asn1-rs", ] [[package]] @@ -20534,17 +20544,6 @@ version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" -[[package]] -name = "proc-macro-warning" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1eaa7fa0aa1929ffdf7eeb6eac234dde6268914a14ad44d23521ab6a9b258e" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 2.0.87", -] - [[package]] name = "proc-macro-warning" version = "1.0.0" @@ -20616,9 +20615,9 @@ dependencies = [ [[package]] name = "prometheus-client" -version = "0.21.2" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c99afa9a01501019ac3a14d71d9f94050346f55ca471ce90c799a15c58f61e2" +checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" dependencies = [ "dtoa", "itoa", @@ -20849,15 +20848,15 @@ dependencies = [ [[package]] name = "quick-protobuf-codec" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ededb1cd78531627244d51dd0c7139fbe736c7d57af0092a76f0ffb2f56e98" +checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" dependencies = [ - "asynchronous-codec", + "asynchronous-codec 0.7.0", "bytes", "quick-protobuf 0.8.1", "thiserror", - "unsigned-varint 0.7.2", + "unsigned-varint 0.8.0", ] [[package]] @@ -20882,24 +20881,6 @@ dependencies = [ "rand", ] -[[package]] -name = "quinn" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cc2c5017e4b43d5995dcea317bc46c1e09404c0a9664d2908f7f02dfe943d75" -dependencies = [ - "bytes", - "futures-io", - "pin-project-lite", - "quinn-proto 0.10.6", - "quinn-udp 0.4.1", - "rustc-hash 1.1.0", - "rustls 0.21.7", - "thiserror", - "tokio", - "tracing", -] - [[package]] name = "quinn" version = "0.11.5" @@ -20907,9 +20888,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", + "futures-io", "pin-project-lite", - "quinn-proto 0.11.8", - "quinn-udp 0.5.4", + "quinn-proto", + "quinn-udp", "rustc-hash 2.0.0", "rustls 0.23.18", "socket2 0.5.7", @@ -20918,23 +20900,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "quinn-proto" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "141bf7dfde2fbc246bfd3fe12f2455aa24b0fbd9af535d8c86c7bd1381ff2b1a" -dependencies = [ - "bytes", - "rand", - "ring 0.16.20", - "rustc-hash 1.1.0", - "rustls 0.21.7", - "slab", - "thiserror", - "tinyvec", - "tracing", -] - [[package]] name = "quinn-proto" version = "0.11.8" @@ -20943,7 +20908,7 @@ checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", "rand", - "ring 0.17.7", + "ring 0.17.8", "rustc-hash 2.0.0", "rustls 0.23.18", "slab", @@ -20952,19 +20917,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "quinn-udp" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "055b4e778e8feb9f93c4e439f71dc2156ef13360b432b799e179a8c4cdf0b1d7" -dependencies = [ - "bytes", - "libc", - "socket2 0.5.7", - "tracing", - "windows-sys 0.48.0", -] - [[package]] name = "quinn-udp" version = "0.5.4" @@ -21134,6 +21086,18 @@ dependencies = [ "yasna", ] +[[package]] +name = "rcgen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c4f3084aa3bc7dfbba4eff4fab2a54db4324965d8872ab933565e6fbd83bc6" +dependencies = [ + "pem 3.0.4", + "ring 0.16.20", + "time", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -21432,7 +21396,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "quinn 0.11.5", + "quinn", "rustls 0.23.18", "rustls-pemfile 2.0.0", "rustls-pki-types", @@ -21505,16 +21469,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -22028,7 +21993,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle 2.5.0", @@ -22043,7 +22008,7 @@ checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" dependencies = [ "log", "once_cell", - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle 2.5.0", @@ -22156,7 +22121,7 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "rustls-pki-types", "untrusted 0.9.0", ] @@ -23165,7 +23130,7 @@ dependencies = [ "assert_matches", "async-channel 1.9.0", "async-trait", - "asynchronous-codec", + "asynchronous-codec 0.6.2", "bytes", "cid 0.9.0", "criterion", @@ -23174,7 +23139,7 @@ dependencies = [ "futures", "futures-timer", "ip_network", - "libp2p", + "libp2p 0.54.1", "linked_hash_set", "litep2p", "log", @@ -23350,7 +23315,7 @@ dependencies = [ "async-trait", "futures", "futures-timer", - "libp2p", + "libp2p 0.54.1", "log", "parking_lot 0.12.3", "rand", @@ -23808,7 +23773,7 @@ version = "15.0.0" dependencies = [ "chrono", "futures", - "libp2p", + "libp2p 0.54.1", "log", "parking_lot 0.12.3", "pin-project", @@ -24358,12 +24323,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - [[package]] name = "separator" version = "0.4.1" @@ -24967,7 +24926,7 @@ dependencies = [ "chacha20poly1305", "curve25519-dalek 4.1.3", "rand_core 0.6.4", - "ring 0.17.7", + "ring 0.17.8", "rustc_version 0.4.0", "sha2 0.10.8", "subtle 2.5.0", @@ -27987,7 +27946,7 @@ name = "sp-version-proc-macro" version = "13.0.0" dependencies = [ "parity-scale-codec", - "proc-macro-warning 1.0.0", + "proc-macro-warning", "proc-macro2 1.0.86", "quote 1.0.37", "sp-version 29.0.0", @@ -30266,78 +30225,6 @@ dependencies = [ "keccak-hasher", ] -[[package]] -name = "trust-dns-proto" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7f83d1e4a0e4358ac54c5c3681e5d7da5efc5a7a632c90bb6d6669ddd9bc26" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner 0.5.1", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.2.3", - "ipnet", - "lazy_static", - "rand", - "smallvec", - "socket2 0.4.9", - "thiserror", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "trust-dns-proto" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3119112651c157f4488931a01e586aa459736e9d6046d3bd9105ffb69352d374" -dependencies = [ - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner 0.6.0", - "futures-channel", - "futures-io", - "futures-util", - "idna 0.4.0", - "ipnet", - "once_cell", - "rand", - "smallvec", - "thiserror", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a3e6c3aff1718b3c73e395d1f35202ba2ffa847c6a62eea0db8fb4cfe30be6" -dependencies = [ - "cfg-if", - "futures-util", - "ipconfig", - "lru-cache", - "once_cell", - "parking_lot 0.12.3", - "rand", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "tracing", - "trust-dns-proto 0.23.2", -] - [[package]] name = "try-lock" version = "0.2.4" @@ -30547,7 +30434,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" dependencies = [ - "asynchronous-codec", + "asynchronous-codec 0.6.2", "bytes", "futures-io", "futures-util", @@ -31296,7 +31183,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.7", + "ring 0.17.8", "untrusted 0.9.0", ] @@ -31862,35 +31749,18 @@ dependencies = [ "zeroize", ] -[[package]] -name = "x509-parser" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7069fba5b66b9193bd2c5d3d4ff12b839118f6bcbef5328efafafb5395cf63da" -dependencies = [ - "asn1-rs 0.5.2", - "data-encoding", - "der-parser 8.2.0", - "lazy_static", - "nom", - "oid-registry 0.6.1", - "rusticata-macros", - "thiserror", - "time", -] - [[package]] name = "x509-parser" version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" dependencies = [ - "asn1-rs 0.6.1", + "asn1-rs", "data-encoding", - "der-parser 9.0.0", + "der-parser", "lazy_static", "nom", - "oid-registry 0.7.0", + "oid-registry", "rusticata-macros", "thiserror", "time", @@ -32180,6 +32050,22 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "yamux" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31b5e376a8b012bee9c423acdbb835fc34d45001cfa3106236a624e4b738028" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot 0.12.3", + "pin-project", + "rand", + "static_assertions", + "web-time", +] + [[package]] name = "yansi" version = "0.5.1" @@ -32288,7 +32174,7 @@ dependencies = [ "futures", "glob-match", "hex", - "libp2p", + "libp2p 0.52.4", "libsecp256k1", "multiaddr 0.18.1", "rand", diff --git a/Cargo.toml b/Cargo.toml index 62a5ada6cd41..37765056196e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -841,7 +841,7 @@ kvdb-shared-tests = { version = "0.11.0" } landlock = { version = "0.3.0" } libc = { version = "0.2.155" } libfuzzer-sys = { version = "0.4" } -libp2p = { version = "0.52.4" } +libp2p = { version = "0.54.1" } libp2p-identity = { version = "0.2.9" } libsecp256k1 = { version = "0.7.0", default-features = false } linked-hash-map = { version = "0.5.4" } diff --git a/prdoc/pr_6248.prdoc b/prdoc/pr_6248.prdoc new file mode 100644 index 000000000000..71fb0891cac6 --- /dev/null +++ b/prdoc/pr_6248.prdoc @@ -0,0 +1,16 @@ +title: Upgrade libp2p to 0.54.1 + +doc: + - audience: [Node Dev, Node Operator] + description: | + Upgrade libp2p from 0.52.4 to 0.54.1 + +crates: + - name: sc-network + bump: major + - name: sc-network-types + bump: minor + - name: sc-network-sync + bump: patch + - name: sc-telemetry + bump: minor diff --git a/substrate/client/network/src/behaviour.rs b/substrate/client/network/src/behaviour.rs index cee80b6c1e86..e2a91e961668 100644 --- a/substrate/client/network/src/behaviour.rs +++ b/substrate/client/network/src/behaviour.rs @@ -68,6 +68,7 @@ pub struct Behaviour { } /// Event generated by `Behaviour`. +#[derive(Debug)] pub enum BehaviourOut { /// Started a random iterative Kademlia discovery query. RandomKademliaStarted, diff --git a/substrate/client/network/src/discovery.rs b/substrate/client/network/src/discovery.rs index 81baa00e201e..917449cf228c 100644 --- a/substrate/client/network/src/discovery.rs +++ b/substrate/client/network/src/discovery.rs @@ -53,13 +53,13 @@ use futures::prelude::*; use futures_timer::Delay; use ip_network::IpNetwork; use libp2p::{ - core::{Endpoint, Multiaddr}, + core::{transport::PortUse, Endpoint, Multiaddr}, kad::{ self, - record::store::{MemoryStore, RecordStore}, + store::{MemoryStore, RecordStore}, Behaviour as Kademlia, BucketInserts, Config as KademliaConfig, Event as KademliaEvent, - GetClosestPeersError, GetProvidersError, GetProvidersOk, GetRecordOk, PeerRecord, QueryId, - QueryResult, Quorum, Record, RecordKey, + Event, GetClosestPeersError, GetProvidersError, GetProvidersOk, GetRecordOk, PeerRecord, + QueryId, QueryResult, Quorum, Record, RecordKey, }, mdns::{self, tokio::Behaviour as TokioMdns}, multiaddr::Protocol, @@ -68,8 +68,8 @@ use libp2p::{ toggle::{Toggle, ToggleConnectionHandler}, DialFailure, ExternalAddrConfirmed, FromSwarm, }, - ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, PollParameters, - StreamProtocol, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, StreamProtocol, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, }, PeerId, }; @@ -214,23 +214,14 @@ impl DiscoveryConfig { enable_mdns, kademlia_disjoint_query_paths, kademlia_protocol, - kademlia_legacy_protocol, + kademlia_legacy_protocol: _, kademlia_replication_factor, } = self; let kademlia = if let Some(ref kademlia_protocol) = kademlia_protocol { - let mut config = KademliaConfig::default(); + let mut config = KademliaConfig::new(kademlia_protocol.clone()); config.set_replication_factor(kademlia_replication_factor); - // Populate kad with both the legacy and the new protocol names. - // Remove the legacy protocol: - // https://github.com/paritytech/polkadot-sdk/issues/504 - let kademlia_protocols = if let Some(legacy_protocol) = kademlia_legacy_protocol { - vec![kademlia_protocol.clone(), legacy_protocol] - } else { - vec![kademlia_protocol.clone()] - }; - config.set_protocol_names(kademlia_protocols.into_iter().map(Into::into).collect()); config.set_record_filtering(libp2p::kad::StoreInserts::FilterBoth); @@ -647,12 +638,14 @@ impl NetworkBehaviour for DiscoveryBehaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { self.kademlia.handle_established_outbound_connection( connection_id, peer, addr, role_override, + port_use, ) } @@ -724,7 +717,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { Ok(list.into_iter().collect()) } - fn on_swarm_event(&mut self, event: FromSwarm) { + fn on_swarm_event(&mut self, event: FromSwarm) { match event { FromSwarm::ConnectionEstablished(e) => { self.num_connections += 1; @@ -811,6 +804,10 @@ impl NetworkBehaviour for DiscoveryBehaviour { self.kademlia.on_swarm_event(FromSwarm::ExternalAddrConfirmed(e)); }, + event => { + debug!(target: "sub-libp2p", "New unknown `FromSwarm` libp2p event: {event:?}"); + self.kademlia.on_swarm_event(event); + }, } } @@ -823,11 +820,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { self.kademlia.on_connection_handler_event(peer_id, connection_id, event); } - fn poll( - &mut self, - cx: &mut Context, - params: &mut impl PollParameters, - ) -> Poll>> { + fn poll(&mut self, cx: &mut Context) -> Poll>> { // Immediately process the content of `discovered`. if let Some(ev) = self.pending_events.pop_front() { return Poll::Ready(ToSwarm::GenerateEvent(ev)) @@ -870,7 +863,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { } } - while let Poll::Ready(ev) = self.kademlia.poll(cx, params) { + while let Poll::Ready(ev) = self.kademlia.poll(cx) { match ev { ToSwarm::GenerateEvent(ev) => match ev { KademliaEvent::RoutingUpdated { peer, .. } => { @@ -1103,30 +1096,38 @@ impl NetworkBehaviour for DiscoveryBehaviour { e.key(), e, ), }, + KademliaEvent::OutboundQueryProgressed { + result: QueryResult::Bootstrap(res), + .. + } => match res { + Ok(ok) => debug!( + target: "sub-libp2p", + "Libp2p => DHT bootstrap progressed: {ok:?}", + ), + Err(e) => warn!( + target: "sub-libp2p", + "Libp2p => DHT bootstrap error: {e:?}", + ), + }, // We never start any other type of query. KademliaEvent::OutboundQueryProgressed { result: e, .. } => { warn!(target: "sub-libp2p", "Libp2p => Unhandled Kademlia event: {:?}", e) }, + Event::ModeChanged { new_mode } => { + debug!(target: "sub-libp2p", "Libp2p => Kademlia mode changed: {new_mode}") + }, }, ToSwarm::Dial { opts } => return Poll::Ready(ToSwarm::Dial { opts }), - ToSwarm::NotifyHandler { peer_id, handler, event } => - return Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }), - ToSwarm::CloseConnection { peer_id, connection } => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - ToSwarm::NewExternalAddrCandidate(observed) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - ToSwarm::ExternalAddrConfirmed(addr) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - ToSwarm::ExternalAddrExpired(addr) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - ToSwarm::ListenOn { opts } => return Poll::Ready(ToSwarm::ListenOn { opts }), - ToSwarm::RemoveListener { id } => - return Poll::Ready(ToSwarm::RemoveListener { id }), + event => { + return Poll::Ready(event.map_out(|_| { + unreachable!("`GenerateEvent` is handled in a branch above; qed") + })); + }, } } // Poll mDNS. - while let Poll::Ready(ev) = self.mdns.poll(cx, params) { + while let Poll::Ready(ev) = self.mdns.poll(cx) { match ev { ToSwarm::GenerateEvent(event) => match event { mdns::Event::Discovered(list) => { @@ -1148,17 +1149,17 @@ impl NetworkBehaviour for DiscoveryBehaviour { }, // `event` is an enum with no variant ToSwarm::NotifyHandler { event, .. } => match event {}, - ToSwarm::CloseConnection { peer_id, connection } => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - ToSwarm::NewExternalAddrCandidate(observed) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - ToSwarm::ExternalAddrConfirmed(addr) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - ToSwarm::ExternalAddrExpired(addr) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - ToSwarm::ListenOn { opts } => return Poll::Ready(ToSwarm::ListenOn { opts }), - ToSwarm::RemoveListener { id } => - return Poll::Ready(ToSwarm::RemoveListener { id }), + event => { + return Poll::Ready( + event + .map_in(|_| { + unreachable!("`NotifyHandler` is handled in a branch above; qed") + }) + .map_out(|_| { + unreachable!("`GenerateEvent` is handled in a branch above; qed") + }), + ); + }, } } @@ -1201,21 +1202,14 @@ mod tests { }, identity::Keypair, noise, - swarm::{Executor, Swarm, SwarmEvent}, + swarm::{Swarm, SwarmEvent}, yamux, Multiaddr, }; use sp_core::hash::H256; - use std::{collections::HashSet, pin::Pin, task::Poll}; + use std::{collections::HashSet, task::Poll, time::Duration}; - struct TokioExecutor(tokio::runtime::Runtime); - impl Executor for TokioExecutor { - fn exec(&self, f: Pin + Send>>) { - let _ = self.0.spawn(f); - } - } - - #[test] - fn discovery_working() { + #[tokio::test] + async fn discovery_working() { let mut first_swarm_peer_id_and_addr = None; let genesis_hash = H256::from_low_u64_be(1); @@ -1226,42 +1220,40 @@ mod tests { // the first swarm via `with_permanent_addresses`. let mut swarms = (0..25) .map(|i| { - let keypair = Keypair::generate_ed25519(); - - let transport = MemoryTransport::new() - .upgrade(upgrade::Version::V1) - .authenticate(noise::Config::new(&keypair).unwrap()) - .multiplex(yamux::Config::default()) - .boxed(); - - let behaviour = { - let mut config = DiscoveryConfig::new(keypair.public().to_peer_id()); - config - .with_permanent_addresses(first_swarm_peer_id_and_addr.clone()) - .allow_private_ip(true) - .allow_non_globals_in_dht(true) - .discovery_limit(50) - .with_kademlia(genesis_hash, fork_id, &protocol_id); - - config.finish() - }; - - let runtime = tokio::runtime::Runtime::new().unwrap(); - #[allow(deprecated)] - let mut swarm = libp2p::swarm::SwarmBuilder::with_executor( - transport, - behaviour, - keypair.public().to_peer_id(), - TokioExecutor(runtime), - ) - .build(); + let mut swarm = libp2p::SwarmBuilder::with_new_identity() + .with_tokio() + .with_other_transport(|keypair| { + MemoryTransport::new() + .upgrade(upgrade::Version::V1) + .authenticate(noise::Config::new(&keypair).unwrap()) + .multiplex(yamux::Config::default()) + .boxed() + }) + .unwrap() + .with_behaviour(|keypair| { + let mut config = DiscoveryConfig::new(keypair.public().to_peer_id()); + config + .with_permanent_addresses(first_swarm_peer_id_and_addr.clone()) + .allow_private_ip(true) + .allow_non_globals_in_dht(true) + .discovery_limit(50) + .with_kademlia(genesis_hash, fork_id, &protocol_id); + + config.finish() + }) + .unwrap() + .with_swarm_config(|config| { + // This is taken care of by notification protocols in non-test environment + config.with_idle_connection_timeout(Duration::from_secs(10)) + }) + .build(); let listen_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); if i == 0 { first_swarm_peer_id_and_addr = - Some((keypair.public().to_peer_id(), listen_addr.clone())) + Some((*swarm.local_peer_id(), listen_addr.clone())) } swarm.listen_on(listen_addr.clone()).unwrap(); @@ -1348,7 +1340,7 @@ mod tests { } }); - futures::executor::block_on(fut); + fut.await } #[test] diff --git a/substrate/client/network/src/network_state.rs b/substrate/client/network/src/network_state.rs index cf8b8b55a7ff..65fd494739ee 100644 --- a/substrate/client/network/src/network_state.rs +++ b/substrate/client/network/src/network_state.rs @@ -106,7 +106,7 @@ pub enum Endpoint { impl From for PeerEndpoint { fn from(endpoint: ConnectedPoint) -> Self { match endpoint { - ConnectedPoint::Dialer { address, role_override } => + ConnectedPoint::Dialer { address, role_override, port_use: _ } => Self::Dialing(address, role_override.into()), ConnectedPoint::Listener { local_addr, send_back_addr } => Self::Listening { local_addr, send_back_addr }, diff --git a/substrate/client/network/src/peer_info.rs b/substrate/client/network/src/peer_info.rs index 21eeea6bcc0c..a673f06fd622 100644 --- a/substrate/client/network/src/peer_info.rs +++ b/substrate/client/network/src/peer_info.rs @@ -25,7 +25,7 @@ use either::Either; use fnv::FnvHashMap; use futures::prelude::*; use libp2p::{ - core::{ConnectedPoint, Endpoint}, + core::{transport::PortUse, ConnectedPoint, Endpoint}, identify::{ Behaviour as Identify, Config as IdentifyConfig, Event as IdentifyEvent, Info as IdentifyInfo, @@ -38,8 +38,8 @@ use libp2p::{ ExternalAddrConfirmed, FromSwarm, ListenFailure, }, ConnectionDenied, ConnectionHandler, ConnectionHandlerSelect, ConnectionId, - NetworkBehaviour, NewExternalAddrCandidate, PollParameters, THandler, THandlerInEvent, - THandlerOutEvent, ToSwarm, + NetworkBehaviour, NewExternalAddrCandidate, THandler, THandlerInEvent, THandlerOutEvent, + ToSwarm, }, Multiaddr, PeerId, }; @@ -275,23 +275,26 @@ impl NetworkBehaviour for PeerInfoBehaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { let ping_handler = self.ping.handle_established_outbound_connection( connection_id, peer, addr, role_override, + port_use, )?; let identify_handler = self.identify.handle_established_outbound_connection( connection_id, peer, addr, role_override, + port_use, )?; Ok(ping_handler.select(identify_handler)) } - fn on_swarm_event(&mut self, event: FromSwarm) { + fn on_swarm_event(&mut self, event: FromSwarm) { match event { FromSwarm::ConnectionEstablished( e @ ConnectionEstablished { peer_id, endpoint, .. }, @@ -319,22 +322,21 @@ impl NetworkBehaviour for PeerInfoBehaviour { peer_id, connection_id, endpoint, - handler, + cause, remaining_established, }) => { - let (ping_handler, identity_handler) = handler.into_inner(); self.ping.on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { peer_id, connection_id, endpoint, - handler: ping_handler, + cause, remaining_established, })); self.identify.on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { peer_id, connection_id, endpoint, - handler: identity_handler, + cause, remaining_established, })); @@ -369,18 +371,21 @@ impl NetworkBehaviour for PeerInfoBehaviour { send_back_addr, error, connection_id, + peer_id, }) => { self.ping.on_swarm_event(FromSwarm::ListenFailure(ListenFailure { local_addr, send_back_addr, error, connection_id, + peer_id, })); self.identify.on_swarm_event(FromSwarm::ListenFailure(ListenFailure { local_addr, send_back_addr, error, connection_id, + peer_id, })); }, FromSwarm::ListenerError(e) => { @@ -438,6 +443,11 @@ impl NetworkBehaviour for PeerInfoBehaviour { self.ping.on_swarm_event(FromSwarm::NewListenAddr(e)); self.identify.on_swarm_event(FromSwarm::NewListenAddr(e)); }, + event => { + debug!(target: "sub-libp2p", "New unknown `FromSwarm` libp2p event: {event:?}"); + self.ping.on_swarm_event(event); + self.identify.on_swarm_event(event); + }, } } @@ -455,47 +465,29 @@ impl NetworkBehaviour for PeerInfoBehaviour { } } - fn poll( - &mut self, - cx: &mut Context, - params: &mut impl PollParameters, - ) -> Poll>> { + fn poll(&mut self, cx: &mut Context) -> Poll>> { if let Some(event) = self.pending_actions.pop_front() { return Poll::Ready(event) } loop { - match self.ping.poll(cx, params) { + match self.ping.poll(cx) { Poll::Pending => break, Poll::Ready(ToSwarm::GenerateEvent(ev)) => { if let PingEvent { peer, result: Ok(rtt), connection } = ev { self.handle_ping_report(&peer, rtt, connection) } }, - Poll::Ready(ToSwarm::Dial { opts }) => return Poll::Ready(ToSwarm::Dial { opts }), - Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }) => - return Poll::Ready(ToSwarm::NotifyHandler { - peer_id, - handler, - event: Either::Left(event), - }), - Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }) => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - Poll::Ready(ToSwarm::ExternalAddrExpired(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - Poll::Ready(ToSwarm::ListenOn { opts }) => - return Poll::Ready(ToSwarm::ListenOn { opts }), - Poll::Ready(ToSwarm::RemoveListener { id }) => - return Poll::Ready(ToSwarm::RemoveListener { id }), + Poll::Ready(event) => { + return Poll::Ready(event.map_in(Either::Left).map_out(|_| { + unreachable!("`GenerateEvent` is handled in a branch above; qed") + })); + }, } } loop { - match self.identify.poll(cx, params) { + match self.identify.poll(cx) { Poll::Pending => break, Poll::Ready(ToSwarm::GenerateEvent(event)) => match event { IdentifyEvent::Received { peer_id, info, .. } => { @@ -503,31 +495,20 @@ impl NetworkBehaviour for PeerInfoBehaviour { let event = PeerInfoEvent::Identified { peer_id, info }; return Poll::Ready(ToSwarm::GenerateEvent(event)) }, - IdentifyEvent::Error { peer_id, error } => { - debug!(target: "sub-libp2p", "Identification with peer {:?} failed => {}", peer_id, error) + IdentifyEvent::Error { connection_id, peer_id, error } => { + debug!( + target: "sub-libp2p", + "Identification with peer {peer_id:?}({connection_id}) failed => {error}" + ); }, IdentifyEvent::Pushed { .. } => {}, IdentifyEvent::Sent { .. } => {}, }, - Poll::Ready(ToSwarm::Dial { opts }) => return Poll::Ready(ToSwarm::Dial { opts }), - Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }) => - return Poll::Ready(ToSwarm::NotifyHandler { - peer_id, - handler, - event: Either::Right(event), - }), - Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }) => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - Poll::Ready(ToSwarm::ExternalAddrExpired(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - Poll::Ready(ToSwarm::ListenOn { opts }) => - return Poll::Ready(ToSwarm::ListenOn { opts }), - Poll::Ready(ToSwarm::RemoveListener { id }) => - return Poll::Ready(ToSwarm::RemoveListener { id }), + Poll::Ready(event) => { + return Poll::Ready(event.map_in(Either::Right).map_out(|_| { + unreachable!("`GenerateEvent` is handled in a branch above; qed") + })); + }, } } diff --git a/substrate/client/network/src/protocol.rs b/substrate/client/network/src/protocol.rs index 402baa7bb2a4..6da1d601b34f 100644 --- a/substrate/client/network/src/protocol.rs +++ b/substrate/client/network/src/protocol.rs @@ -27,10 +27,10 @@ use crate::{ use codec::Encode; use libp2p::{ - core::Endpoint, + core::{transport::PortUse, Endpoint}, swarm::{ - behaviour::FromSwarm, ConnectionDenied, ConnectionId, NetworkBehaviour, PollParameters, - THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + behaviour::FromSwarm, ConnectionDenied, ConnectionId, NetworkBehaviour, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, }, Multiaddr, PeerId, }; @@ -47,9 +47,7 @@ use notifications::{Notifications, NotificationsOut}; pub(crate) use notifications::ProtocolHandle; -pub use notifications::{ - notification_service, NotificationsSink, NotifsHandlerError, ProtocolHandlePair, Ready, -}; +pub use notifications::{notification_service, NotificationsSink, ProtocolHandlePair, Ready}; mod notifications; @@ -250,12 +248,14 @@ impl NetworkBehaviour for Protocol { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { self.behaviour.handle_established_outbound_connection( connection_id, peer, addr, role_override, + port_use, ) } @@ -271,7 +271,7 @@ impl NetworkBehaviour for Protocol { Ok(Vec::new()) } - fn on_swarm_event(&mut self, event: FromSwarm) { + fn on_swarm_event(&mut self, event: FromSwarm) { self.behaviour.on_swarm_event(event); } @@ -287,26 +287,15 @@ impl NetworkBehaviour for Protocol { fn poll( &mut self, cx: &mut std::task::Context, - params: &mut impl PollParameters, ) -> Poll>> { - let event = match self.behaviour.poll(cx, params) { + let event = match self.behaviour.poll(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(ToSwarm::GenerateEvent(ev)) => ev, - Poll::Ready(ToSwarm::Dial { opts }) => return Poll::Ready(ToSwarm::Dial { opts }), - Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }) => - return Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }), - Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }) => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - Poll::Ready(ToSwarm::ExternalAddrExpired(addr)) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - Poll::Ready(ToSwarm::ListenOn { opts }) => - return Poll::Ready(ToSwarm::ListenOn { opts }), - Poll::Ready(ToSwarm::RemoveListener { id }) => - return Poll::Ready(ToSwarm::RemoveListener { id }), + Poll::Ready(event) => { + return Poll::Ready(event.map_out(|_| { + unreachable!("`GenerateEvent` is handled in a branch above; qed") + })); + }, }; let outcome = match event { diff --git a/substrate/client/network/src/protocol/notifications.rs b/substrate/client/network/src/protocol/notifications.rs index 10fa329097d1..2691496234ad 100644 --- a/substrate/client/network/src/protocol/notifications.rs +++ b/substrate/client/network/src/protocol/notifications.rs @@ -21,7 +21,7 @@ pub use self::{ behaviour::{Notifications, NotificationsOut, ProtocolConfig}, - handler::{NotificationsSink, NotifsHandlerError, Ready}, + handler::{NotificationsSink, Ready}, service::{notification_service, ProtocolHandlePair}, }; diff --git a/substrate/client/network/src/protocol/notifications/behaviour.rs b/substrate/client/network/src/protocol/notifications/behaviour.rs index a562546145c8..e6909fcdefea 100644 --- a/substrate/client/network/src/protocol/notifications/behaviour.rs +++ b/substrate/client/network/src/protocol/notifications/behaviour.rs @@ -33,11 +33,11 @@ use bytes::BytesMut; use fnv::FnvHashMap; use futures::{future::BoxFuture, prelude::*, stream::FuturesUnordered}; use libp2p::{ - core::{Endpoint, Multiaddr}, + core::{transport::PortUse, Endpoint, Multiaddr}, swarm::{ behaviour::{ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm}, - ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, NotifyHandler, PollParameters, - THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, NotifyHandler, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, }, PeerId, }; @@ -49,6 +49,7 @@ use smallvec::SmallVec; use tokio::sync::oneshot::error::RecvError; use tokio_stream::StreamMap; +use libp2p::swarm::CloseConnection; use std::{ cmp, collections::{hash_map::Entry, VecDeque}, @@ -1233,11 +1234,12 @@ impl NetworkBehaviour for Notifications { peer: PeerId, _addr: &Multiaddr, _role_override: Endpoint, + _port_use: PortUse, ) -> Result, ConnectionDenied> { Ok(NotifsHandler::new(peer, self.notif_protocols.clone(), Some(self.metrics.clone()))) } - fn on_swarm_event(&mut self, event: FromSwarm) { + fn on_swarm_event(&mut self, event: FromSwarm) { match event { FromSwarm::ConnectionEstablished(ConnectionEstablished { peer_id, @@ -1670,6 +1672,9 @@ impl NetworkBehaviour for Notifications { FromSwarm::ExternalAddrConfirmed(_) => {}, FromSwarm::AddressChange(_) => {}, FromSwarm::NewListenAddr(_) => {}, + event => { + warn!(target: "sub-libp2p", "New unknown `FromSwarm` libp2p event: {event:?}"); + }, } } @@ -2217,14 +2222,19 @@ impl NetworkBehaviour for Notifications { ); } }, + NotifsHandlerOut::Close { protocol_index } => { + let set_id = SetId::from(protocol_index); + + trace!(target: "sub-libp2p", "Handler({}, {:?}) => SyncNotificationsClogged({:?})", peer_id, connection_id, set_id); + self.events.push_back(ToSwarm::CloseConnection { + peer_id, + connection: CloseConnection::One(connection_id), + }); + }, } } - fn poll( - &mut self, - cx: &mut Context, - _params: &mut impl PollParameters, - ) -> Poll>> { + fn poll(&mut self, cx: &mut Context) -> Poll>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(event) } @@ -2359,7 +2369,6 @@ impl NetworkBehaviour for Notifications { } #[cfg(test)] -#[allow(deprecated)] mod tests { use super::*; use crate::{ @@ -2386,17 +2395,6 @@ mod tests { } } - #[derive(Clone)] - struct MockPollParams {} - - impl PollParameters for MockPollParams { - type SupportedProtocolsIter = std::vec::IntoIter>; - - fn supported_protocols(&self) -> Self::SupportedProtocolsIter { - vec![].into_iter() - } - } - fn development_notifs( ) -> (Notifications, ProtocolController, Box) { @@ -2654,7 +2652,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -2854,7 +2852,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3007,7 +3005,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3051,7 +3049,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3121,7 +3119,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3269,7 +3267,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3395,7 +3393,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3469,7 +3467,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3532,7 +3530,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3546,7 +3544,7 @@ mod tests { peer_id: peer, connection_id: conn2, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3600,7 +3598,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3658,7 +3656,7 @@ mod tests { peer_id: peer, connection_id: conn2, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3719,7 +3717,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3788,7 +3786,7 @@ mod tests { peer_id: peer, connection_id: conn1, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3829,7 +3827,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3952,7 +3950,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -3972,11 +3970,9 @@ mod tests { assert!(notif.peers.get(&(peer, set_id)).is_some()); if tokio::time::timeout(Duration::from_secs(5), async { - let mut params = MockPollParams {}; - loop { futures::future::poll_fn(|cx| { - let _ = notif.poll(cx, &mut params); + let _ = notif.poll(cx); Poll::Ready(()) }) .await; @@ -4080,11 +4076,9 @@ mod tests { // verify that the code continues to keep the peer disabled by resetting the timer // after the first one expired. if tokio::time::timeout(Duration::from_secs(5), async { - let mut params = MockPollParams {}; - loop { futures::future::poll_fn(|cx| { - let _ = notif.poll(cx, &mut params); + let _ = notif.poll(cx); Poll::Ready(()) }) .await; @@ -4262,7 +4256,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4503,7 +4497,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4605,7 +4599,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4687,7 +4681,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(0), endpoint: &endpoint.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4804,7 +4798,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4839,7 +4833,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4890,7 +4884,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4937,7 +4931,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -4987,7 +4981,7 @@ mod tests { peer_id: peer, connection_id: ConnectionId::new_unchecked(1337), endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -5030,7 +5024,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); @@ -5041,7 +5035,7 @@ mod tests { peer_id: peer, connection_id: conn, endpoint: &connected.clone(), - handler: NotifsHandler::new(peer, vec![], None), + cause: None, remaining_established: 0usize, }, )); diff --git a/substrate/client/network/src/protocol/notifications/handler.rs b/substrate/client/network/src/protocol/notifications/handler.rs index bff60ba1125f..332de9f19c41 100644 --- a/substrate/client/network/src/protocol/notifications/handler.rs +++ b/substrate/client/network/src/protocol/notifications/handler.rs @@ -74,12 +74,12 @@ use futures::{ }; use libp2p::{ swarm::{ - handler::ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, KeepAlive, Stream, + handler::ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, Stream, SubstreamProtocol, }, PeerId, }; -use log::error; +use log::{error, warn}; use parking_lot::{Mutex, RwLock}; use std::{ collections::VecDeque, @@ -87,7 +87,7 @@ use std::{ pin::Pin, sync::Arc, task::{Context, Poll}, - time::{Duration, Instant}, + time::Duration, }; /// Number of pending notifications in asynchronous contexts. @@ -113,16 +113,18 @@ pub struct NotifsHandler { /// List of notification protocols, specified by the user at initialization. protocols: Vec, - /// When the connection with the remote has been successfully established. - when_connection_open: Instant, + /// Whether to keep connection alive + keep_alive: bool, + + /// Optional future that keeps connection alive for a certain amount of time. + // TODO: this should be safe to remove, see https://github.com/paritytech/polkadot-sdk/issues/6350 + keep_alive_timeout_future: Option + Send + 'static>>>, /// Remote we are connected to. peer_id: PeerId, /// Events to return in priority from `poll`. - events_queue: VecDeque< - ConnectionHandlerEvent, - >, + events_queue: VecDeque>, /// Metrics. metrics: Option>, @@ -149,7 +151,12 @@ impl NotifsHandler { }) .collect(), peer_id, - when_connection_open: Instant::now(), + // Keep connection alive initially until below timeout expires + keep_alive: true, + // A grace period of `INITIAL_KEEPALIVE_TIME` must be given to leave time for the remote + // to express desire to open substreams. + // TODO: This is a hack and ideally should not be necessary + keep_alive_timeout_future: Some(Box::pin(tokio::time::sleep(INITIAL_KEEPALIVE_TIME))), events_queue: VecDeque::with_capacity(16), metrics: metrics.map_or(None, |metrics| Some(Arc::new(metrics))), } @@ -327,6 +334,12 @@ pub enum NotifsHandlerOut { /// Message that has been received. message: BytesMut, }, + + /// Close connection + Close { + /// Index of the protocol in the list of protocols passed at initialization. + protocol_index: usize, + }, } /// Sink connected directly to the node background task. Allows sending notifications to the peer. @@ -465,17 +478,9 @@ impl<'a> Ready<'a> { } } -/// Error specific to the collection of protocols. -#[derive(Debug, thiserror::Error)] -pub enum NotifsHandlerError { - #[error("Channel of synchronous notifications is full.")] - SyncNotificationsClogged, -} - impl ConnectionHandler for NotifsHandler { type FromBehaviour = NotifsHandlerIn; type ToBehaviour = NotifsHandlerOut; - type Error = NotifsHandlerError; type InboundProtocol = UpgradeCollec; type OutboundProtocol = NotificationsOut; // Index within the `out_protocols`. @@ -616,6 +621,9 @@ impl ConnectionHandler for NotifsHandler { State::Open { .. } => debug_assert!(false), }, ConnectionEvent::ListenUpgradeError(_listen_upgrade_error) => {}, + event => { + warn!(target: "sub-libp2p", "New unknown `ConnectionEvent` libp2p event: {event:?}"); + }, } } @@ -711,35 +719,36 @@ impl ConnectionHandler for NotifsHandler { } } - fn connection_keep_alive(&self) -> KeepAlive { + fn connection_keep_alive(&self) -> bool { // `Yes` if any protocol has some activity. if self.protocols.iter().any(|p| !matches!(p.state, State::Closed { .. })) { - return KeepAlive::Yes + return true; } - // A grace period of `INITIAL_KEEPALIVE_TIME` must be given to leave time for the remote - // to express desire to open substreams. - #[allow(deprecated)] - KeepAlive::Until(self.when_connection_open + INITIAL_KEEPALIVE_TIME) + self.keep_alive } - #[allow(deprecated)] fn poll( &mut self, cx: &mut Context, ) -> Poll< - ConnectionHandlerEvent< - Self::OutboundProtocol, - Self::OutboundOpenInfo, - Self::ToBehaviour, - Self::Error, - >, + ConnectionHandlerEvent, > { + { + let maybe_keep_alive_timeout_future = &mut self.keep_alive_timeout_future; + if let Some(keep_alive_timeout_future) = maybe_keep_alive_timeout_future { + if keep_alive_timeout_future.poll_unpin(cx).is_ready() { + maybe_keep_alive_timeout_future.take(); + self.keep_alive = false; + } + } + } + if let Some(ev) = self.events_queue.pop_front() { return Poll::Ready(ev) } - // For each open substream, try send messages from `notifications_sink_rx` to the + // For each open substream, try to send messages from `notifications_sink_rx` to the // substream. for protocol_index in 0..self.protocols.len() { if let State::Open { @@ -750,11 +759,10 @@ impl ConnectionHandler for NotifsHandler { // Only proceed with `out_substream.poll_ready_unpin` if there is an element // available in `notifications_sink_rx`. This avoids waking up the task when // a substream is ready to send if there isn't actually something to send. - #[allow(deprecated)] match Pin::new(&mut *notifications_sink_rx).as_mut().poll_peek(cx) { Poll::Ready(Some(&NotificationsSinkMessage::ForceClose)) => - return Poll::Ready(ConnectionHandlerEvent::Close( - NotifsHandlerError::SyncNotificationsClogged, + return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + NotifsHandlerOut::Close { protocol_index }, )), Poll::Ready(Some(&NotificationsSinkMessage::Notification { .. })) => {}, Poll::Ready(None) | Poll::Pending => break, @@ -975,6 +983,17 @@ pub mod tests { rx_buffer: BytesMut, } + /// Mirror of `ActiveStreamCounter` in `libp2p` + #[allow(dead_code)] + struct MockActiveStreamCounter(Arc<()>); + + // Mirror of `Stream` in `libp2p` + #[allow(dead_code)] + struct MockStream { + stream: Negotiated, + counter: Option, + } + impl MockSubstream { /// Create new substream pair. pub fn new() -> (Self, Self) { @@ -1004,16 +1023,11 @@ pub mod tests { /// Unsafe substitute for `Stream::new` private constructor. fn stream_new(stream: Negotiated) -> Stream { + let stream = MockStream { stream, counter: None }; // Static asserts to make sure this doesn't break. const _: () = { - assert!( - core::mem::size_of::() == - core::mem::size_of::>() - ); - assert!( - core::mem::align_of::() == - core::mem::align_of::>() - ); + assert!(core::mem::size_of::() == core::mem::size_of::()); + assert!(core::mem::align_of::() == core::mem::align_of::()); }; unsafe { core::mem::transmute(stream) } @@ -1084,24 +1098,16 @@ pub mod tests { /// Create new [`NotifsHandler`]. fn notifs_handler() -> NotifsHandler { - let proto = Protocol { - config: ProtocolConfig { + NotifsHandler::new( + PeerId::random(), + vec![ProtocolConfig { name: "/foo".into(), fallback_names: vec![], handshake: Arc::new(RwLock::new(b"hello, world".to_vec())), max_notification_size: u64::MAX, - }, - in_upgrade: NotificationsIn::new("/foo", Vec::new(), u64::MAX), - state: State::Closed { pending_opening: false }, - }; - - NotifsHandler { - protocols: vec![proto], - when_connection_open: Instant::now(), - peer_id: PeerId::random(), - events_queue: VecDeque::new(), - metrics: None, - } + }], + None, + ) } // verify that if another substream is attempted to be opened by remote while an inbound @@ -1608,12 +1614,11 @@ pub mod tests { notifications_sink.send_sync_notification(vec![1, 3, 3, 9]); notifications_sink.send_sync_notification(vec![1, 3, 4, 0]); - #[allow(deprecated)] futures::future::poll_fn(|cx| { assert!(std::matches!( handler.poll(cx), - Poll::Ready(ConnectionHandlerEvent::Close( - NotifsHandlerError::SyncNotificationsClogged, + Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour( + NotifsHandlerOut::Close { .. } )) )); Poll::Ready(()) diff --git a/substrate/client/network/src/protocol/notifications/tests.rs b/substrate/client/network/src/protocol/notifications/tests.rs index a8eeb2bb1980..50f03b5911b6 100644 --- a/substrate/client/network/src/protocol/notifications/tests.rs +++ b/substrate/client/network/src/protocol/notifications/tests.rs @@ -30,30 +30,25 @@ use crate::{ use futures::{future::BoxFuture, prelude::*}; use libp2p::{ - core::{transport::MemoryTransport, upgrade, Endpoint}, + core::{ + transport::{MemoryTransport, PortUse}, + upgrade, Endpoint, + }, identity, noise, swarm::{ - self, behaviour::FromSwarm, ConnectionDenied, ConnectionId, Executor, NetworkBehaviour, - PollParameters, Swarm, SwarmEvent, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, + behaviour::FromSwarm, ConnectionDenied, ConnectionId, NetworkBehaviour, Swarm, SwarmEvent, + THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }, - yamux, Multiaddr, PeerId, Transport, + yamux, Multiaddr, PeerId, SwarmBuilder, Transport, }; use sc_utils::mpsc::tracing_unbounded; use std::{ iter, - pin::Pin, sync::Arc, task::{Context, Poll}, time::Duration, }; -struct TokioExecutor(tokio::runtime::Runtime); -impl Executor for TokioExecutor { - fn exec(&self, f: Pin + Send>>) { - let _ = self.0.spawn(f); - } -} - /// Builds two nodes that have each other as bootstrap nodes. /// This is to be used only for testing, and a panic will happen if something goes wrong. fn build_nodes() -> (Swarm, Swarm) { @@ -67,13 +62,6 @@ fn build_nodes() -> (Swarm, Swarm) { for index in 0..2 { let keypair = keypairs[index].clone(); - let transport = MemoryTransport::new() - .upgrade(upgrade::Version::V1) - .authenticate(noise::Config::new(&keypair).unwrap()) - .multiplex(yamux::Config::default()) - .timeout(Duration::from_secs(20)) - .boxed(); - let (protocol_handle_pair, mut notif_service) = crate::protocol::notifications::service::notification_service("/foo".into()); // The first swarm has the second peer ID present in the peerstore. @@ -102,39 +90,8 @@ fn build_nodes() -> (Swarm, Swarm) { ); let (notif_handle, command_stream) = protocol_handle_pair.split(); - let behaviour = CustomProtoWithAddr { - inner: Notifications::new( - vec![controller_handle], - from_controller, - NotificationMetrics::new(None), - iter::once(( - ProtocolConfig { - name: "/foo".into(), - fallback_names: Vec::new(), - handshake: Vec::new(), - max_notification_size: 1024 * 1024, - }, - notif_handle, - command_stream, - )), - ), - peer_store_future: peer_store.run().boxed(), - protocol_controller_future: controller.run().boxed(), - addrs: addrs - .iter() - .enumerate() - .filter_map(|(n, a)| { - if n != index { - Some((keypairs[n].public().to_peer_id(), a.clone())) - } else { - None - } - }) - .collect(), - }; - let runtime = tokio::runtime::Runtime::new().unwrap(); - runtime.spawn(async move { + tokio::spawn(async move { loop { if let NotificationEvent::ValidateInboundSubstream { result_tx, .. } = notif_service.next_event().await.unwrap() @@ -144,12 +101,49 @@ fn build_nodes() -> (Swarm, Swarm) { } }); - let mut swarm = Swarm::new( - transport, - behaviour, - keypairs[index].public().to_peer_id(), - swarm::Config::with_executor(TokioExecutor(runtime)), - ); + let mut swarm = SwarmBuilder::with_existing_identity(keypair) + .with_tokio() + .with_other_transport(|keypair| { + MemoryTransport::new() + .upgrade(upgrade::Version::V1) + .authenticate(noise::Config::new(&keypair).unwrap()) + .multiplex(yamux::Config::default()) + .timeout(Duration::from_secs(20)) + .boxed() + }) + .unwrap() + .with_behaviour(|_keypair| CustomProtoWithAddr { + inner: Notifications::new( + vec![controller_handle], + from_controller, + NotificationMetrics::new(None), + iter::once(( + ProtocolConfig { + name: "/foo".into(), + fallback_names: Vec::new(), + handshake: Vec::new(), + max_notification_size: 1024 * 1024, + }, + notif_handle, + command_stream, + )), + ), + peer_store_future: peer_store.run().boxed(), + protocol_controller_future: controller.run().boxed(), + addrs: addrs + .iter() + .enumerate() + .filter_map(|(n, a)| { + if n != index { + Some((keypairs[n].public().to_peer_id(), a.clone())) + } else { + None + } + }) + .collect(), + }) + .unwrap() + .build(); swarm.listen_on(addrs[index].clone()).unwrap(); out.push(swarm); } @@ -241,12 +235,18 @@ impl NetworkBehaviour for CustomProtoWithAddr { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { - self.inner - .handle_established_outbound_connection(connection_id, peer, addr, role_override) + self.inner.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) } - fn on_swarm_event(&mut self, event: FromSwarm) { + fn on_swarm_event(&mut self, event: FromSwarm) { self.inner.on_swarm_event(event); } @@ -259,19 +259,15 @@ impl NetworkBehaviour for CustomProtoWithAddr { self.inner.on_connection_handler_event(peer_id, connection_id, event); } - fn poll( - &mut self, - cx: &mut Context, - params: &mut impl PollParameters, - ) -> Poll>> { + fn poll(&mut self, cx: &mut Context) -> Poll>> { let _ = self.peer_store_future.poll_unpin(cx); let _ = self.protocol_controller_future.poll_unpin(cx); - self.inner.poll(cx, params) + self.inner.poll(cx) } } -#[test] -fn reconnect_after_disconnect() { +#[tokio::test] +async fn reconnect_after_disconnect() { // We connect two nodes together, then force a disconnect (through the API of the `Service`), // check that the disconnect worked, and finally check whether they successfully reconnect. @@ -288,108 +284,106 @@ fn reconnect_after_disconnect() { let mut service1_state = ServiceState::NotConnected; let mut service2_state = ServiceState::NotConnected; - futures::executor::block_on(async move { - loop { - // Grab next event from services. - let event = { - let s1 = service1.select_next_some(); - let s2 = service2.select_next_some(); - futures::pin_mut!(s1, s2); - match future::select(s1, s2).await { - future::Either::Left((ev, _)) => future::Either::Left(ev), - future::Either::Right((ev, _)) => future::Either::Right(ev), - } - }; - - match event { - future::Either::Left(SwarmEvent::Behaviour( - NotificationsOut::CustomProtocolOpen { .. }, - )) => match service1_state { - ServiceState::NotConnected => { - service1_state = ServiceState::FirstConnec; - if service2_state == ServiceState::FirstConnec { - service1 - .behaviour_mut() - .disconnect_peer(Swarm::local_peer_id(&service2), SetId::from(0)); - } - }, - ServiceState::Disconnected => service1_state = ServiceState::ConnectedAgain, - ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(), - }, - future::Either::Left(SwarmEvent::Behaviour( - NotificationsOut::CustomProtocolClosed { .. }, - )) => match service1_state { - ServiceState::FirstConnec => service1_state = ServiceState::Disconnected, - ServiceState::ConnectedAgain | - ServiceState::NotConnected | - ServiceState::Disconnected => panic!(), - }, - future::Either::Right(SwarmEvent::Behaviour( - NotificationsOut::CustomProtocolOpen { .. }, - )) => match service2_state { - ServiceState::NotConnected => { - service2_state = ServiceState::FirstConnec; - if service1_state == ServiceState::FirstConnec { - service1 - .behaviour_mut() - .disconnect_peer(Swarm::local_peer_id(&service2), SetId::from(0)); - } - }, - ServiceState::Disconnected => service2_state = ServiceState::ConnectedAgain, - ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(), - }, - future::Either::Right(SwarmEvent::Behaviour( - NotificationsOut::CustomProtocolClosed { .. }, - )) => match service2_state { - ServiceState::FirstConnec => service2_state = ServiceState::Disconnected, - ServiceState::ConnectedAgain | - ServiceState::NotConnected | - ServiceState::Disconnected => panic!(), - }, - _ => {}, + loop { + // Grab next event from services. + let event = { + let s1 = service1.select_next_some(); + let s2 = service2.select_next_some(); + futures::pin_mut!(s1, s2); + match future::select(s1, s2).await { + future::Either::Left((ev, _)) => future::Either::Left(ev), + future::Either::Right((ev, _)) => future::Either::Right(ev), } + }; - // Due to the bug in `Notifications`, the disconnected node does not always detect that - // it was disconnected. The closed inbound substream is tolerated by design, and the - // closed outbound substream is not detected until something is sent into it. - // See [PR #13396](https://github.com/paritytech/substrate/pull/13396). - // This happens if the disconnecting node reconnects to it fast enough. - // In this case the disconnected node does not transit via `ServiceState::NotConnected` - // and stays in `ServiceState::FirstConnec`. - // TODO: update this once the fix is finally merged. - if service1_state == ServiceState::ConnectedAgain && - service2_state == ServiceState::ConnectedAgain || - service1_state == ServiceState::ConnectedAgain && - service2_state == ServiceState::FirstConnec || - service1_state == ServiceState::FirstConnec && - service2_state == ServiceState::ConnectedAgain - { - break - } + match event { + future::Either::Left(SwarmEvent::Behaviour(NotificationsOut::CustomProtocolOpen { + .. + })) => match service1_state { + ServiceState::NotConnected => { + service1_state = ServiceState::FirstConnec; + if service2_state == ServiceState::FirstConnec { + service1 + .behaviour_mut() + .disconnect_peer(Swarm::local_peer_id(&service2), SetId::from(0)); + } + }, + ServiceState::Disconnected => service1_state = ServiceState::ConnectedAgain, + ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(), + }, + future::Either::Left(SwarmEvent::Behaviour( + NotificationsOut::CustomProtocolClosed { .. }, + )) => match service1_state { + ServiceState::FirstConnec => service1_state = ServiceState::Disconnected, + ServiceState::ConnectedAgain | + ServiceState::NotConnected | + ServiceState::Disconnected => panic!(), + }, + future::Either::Right(SwarmEvent::Behaviour( + NotificationsOut::CustomProtocolOpen { .. }, + )) => match service2_state { + ServiceState::NotConnected => { + service2_state = ServiceState::FirstConnec; + if service1_state == ServiceState::FirstConnec { + service1 + .behaviour_mut() + .disconnect_peer(Swarm::local_peer_id(&service2), SetId::from(0)); + } + }, + ServiceState::Disconnected => service2_state = ServiceState::ConnectedAgain, + ServiceState::FirstConnec | ServiceState::ConnectedAgain => panic!(), + }, + future::Either::Right(SwarmEvent::Behaviour( + NotificationsOut::CustomProtocolClosed { .. }, + )) => match service2_state { + ServiceState::FirstConnec => service2_state = ServiceState::Disconnected, + ServiceState::ConnectedAgain | + ServiceState::NotConnected | + ServiceState::Disconnected => panic!(), + }, + _ => {}, } - // Now that the two services have disconnected and reconnected, wait for 3 seconds and - // check whether they're still connected. - let mut delay = futures_timer::Delay::new(Duration::from_secs(3)); - - loop { - // Grab next event from services. - let event = { - let s1 = service1.select_next_some(); - let s2 = service2.select_next_some(); - futures::pin_mut!(s1, s2); - match future::select(future::select(s1, s2), &mut delay).await { - future::Either::Right(_) => break, // success - future::Either::Left((future::Either::Left((ev, _)), _)) => ev, - future::Either::Left((future::Either::Right((ev, _)), _)) => ev, - } - }; + // Due to the bug in `Notifications`, the disconnected node does not always detect that + // it was disconnected. The closed inbound substream is tolerated by design, and the + // closed outbound substream is not detected until something is sent into it. + // See [PR #13396](https://github.com/paritytech/substrate/pull/13396). + // This happens if the disconnecting node reconnects to it fast enough. + // In this case the disconnected node does not transit via `ServiceState::NotConnected` + // and stays in `ServiceState::FirstConnec`. + // TODO: update this once the fix is finally merged. + if service1_state == ServiceState::ConnectedAgain && + service2_state == ServiceState::ConnectedAgain || + service1_state == ServiceState::ConnectedAgain && + service2_state == ServiceState::FirstConnec || + service1_state == ServiceState::FirstConnec && + service2_state == ServiceState::ConnectedAgain + { + break + } + } - match event { - SwarmEvent::Behaviour(NotificationsOut::CustomProtocolOpen { .. }) | - SwarmEvent::Behaviour(NotificationsOut::CustomProtocolClosed { .. }) => panic!(), - _ => {}, + // Now that the two services have disconnected and reconnected, wait for 3 seconds and + // check whether they're still connected. + let mut delay = futures_timer::Delay::new(Duration::from_secs(3)); + + loop { + // Grab next event from services. + let event = { + let s1 = service1.select_next_some(); + let s2 = service2.select_next_some(); + futures::pin_mut!(s1, s2); + match future::select(future::select(s1, s2), &mut delay).await { + future::Either::Right(_) => break, // success + future::Either::Left((future::Either::Left((ev, _)), _)) => ev, + future::Either::Left((future::Either::Right((ev, _)), _)) => ev, } + }; + + match event { + SwarmEvent::Behaviour(NotificationsOut::CustomProtocolOpen { .. }) | + SwarmEvent::Behaviour(NotificationsOut::CustomProtocolClosed { .. }) => panic!(), + _ => {}, } - }); + } } diff --git a/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs b/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs index e01bcbe0bad7..9e8a03fc07c9 100644 --- a/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs +++ b/substrate/client/network/src/protocol/notifications/upgrade/notifications.rs @@ -39,12 +39,12 @@ use crate::types::ProtocolName; use asynchronous_codec::Framed; use bytes::BytesMut; use futures::prelude::*; -use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}; +use libp2p::core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use log::{error, warn}; use unsigned_varint::codec::UviBytes; use std::{ - io, mem, + fmt, io, mem, pin::Pin, task::{Context, Poll}, vec, @@ -187,6 +187,14 @@ pub struct NotificationsInOpen { pub substream: NotificationsInSubstream, } +impl fmt::Debug for NotificationsInOpen { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NotificationsInOpen") + .field("handshake", &self.handshake) + .finish_non_exhaustive() + } +} + impl NotificationsInSubstream where TSubstream: AsyncRead + AsyncWrite + Unpin, @@ -370,7 +378,14 @@ where fn upgrade_outbound(self, mut socket: TSubstream, negotiated_name: Self::Info) -> Self::Future { Box::pin(async move { - upgrade::write_length_prefixed(&mut socket, &self.initial_message).await?; + { + let mut len_data = unsigned_varint::encode::usize_buffer(); + let encoded_len = + unsigned_varint::encode::usize(self.initial_message.len(), &mut len_data).len(); + socket.write_all(&len_data[..encoded_len]).await?; + } + socket.write_all(&self.initial_message).await?; + socket.flush().await?; // Reading handshake. let handshake_len = unsigned_varint::aio::read_usize(&mut socket).await?; @@ -413,6 +428,15 @@ pub struct NotificationsOutOpen { pub substream: NotificationsOutSubstream, } +impl fmt::Debug for NotificationsOutOpen { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NotificationsOutOpen") + .field("handshake", &self.handshake) + .field("negotiated_fallback", &self.negotiated_fallback) + .finish_non_exhaustive() + } +} + impl Sink> for NotificationsOutSubstream where TSubstream: AsyncRead + AsyncWrite + Unpin, diff --git a/substrate/client/network/src/request_responses.rs b/substrate/client/network/src/request_responses.rs index 6c2631924df4..5fe34c781378 100644 --- a/substrate/client/network/src/request_responses.rs +++ b/substrate/client/network/src/request_responses.rs @@ -43,13 +43,11 @@ use crate::{ use futures::{channel::oneshot, prelude::*}; use libp2p::{ - core::{Endpoint, Multiaddr}, + core::{transport::PortUse, Endpoint, Multiaddr}, request_response::{self, Behaviour, Codec, Message, ProtocolSupport, ResponseChannel}, swarm::{ - behaviour::{ConnectionClosed, FromSwarm}, - handler::multi::MultiHandler, - ConnectionDenied, ConnectionId, NetworkBehaviour, PollParameters, THandler, - THandlerInEvent, THandlerOutEvent, ToSwarm, + behaviour::FromSwarm, handler::multi::MultiHandler, ConnectionDenied, ConnectionId, + NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }, PeerId, }; @@ -64,11 +62,11 @@ use std::{ time::{Duration, Instant}, }; -pub use libp2p::request_response::{Config, RequestId}; +pub use libp2p::request_response::{Config, InboundRequestId, OutboundRequestId}; /// Possible failures occurring in the context of sending an outbound request and receiving the /// response. -#[derive(Debug, thiserror::Error)] +#[derive(Debug, Clone, thiserror::Error)] pub enum OutboundFailure { /// The request could not be sent because a dialing attempt failed. #[error("Failed to dial the requested peer")] @@ -82,6 +80,9 @@ pub enum OutboundFailure { /// The remote supports none of the requested protocols. #[error("The remote supports none of the requested protocols")] UnsupportedProtocols, + /// An IO failure happened on an outbound stream. + #[error("An IO failure happened on an outbound stream")] + Io(Arc), } impl From for OutboundFailure { @@ -93,6 +94,7 @@ impl From for OutboundFailure { OutboundFailure::ConnectionClosed, request_response::OutboundFailure::UnsupportedProtocols => OutboundFailure::UnsupportedProtocols, + request_response::OutboundFailure::Io(error) => OutboundFailure::Io(Arc::new(error)), } } } @@ -114,6 +116,9 @@ pub enum InboundFailure { /// The local peer failed to respond to an inbound request #[error("The response channel was dropped without sending a response to the remote")] ResponseOmission, + /// An IO failure happened on an inbound stream. + #[error("An IO failure happened on an inbound stream")] + Io(Arc), } impl From for InboundFailure { @@ -124,6 +129,7 @@ impl From for InboundFailure { request_response::InboundFailure::ConnectionClosed => InboundFailure::ConnectionClosed, request_response::InboundFailure::UnsupportedProtocols => InboundFailure::UnsupportedProtocols, + request_response::InboundFailure::Io(error) => InboundFailure::Io(Arc::new(error)), } } } @@ -319,12 +325,12 @@ pub enum Event { /// requests. There is no uniqueness guarantee in a set of both inbound and outbound /// [`ProtocolRequestId`]s. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -struct ProtocolRequestId { +struct ProtocolRequestId { protocol: ProtocolName, request_id: RequestId, } -impl From<(ProtocolName, RequestId)> for ProtocolRequestId { +impl From<(ProtocolName, RequestId)> for ProtocolRequestId { fn from((protocol, request_id): (ProtocolName, RequestId)) -> Self { Self { protocol, request_id } } @@ -342,7 +348,7 @@ pub struct RequestResponsesBehaviour { >, /// Pending requests, passed down to a request-response [`Behaviour`], awaiting a reply. - pending_requests: HashMap, + pending_requests: HashMap, PendingRequest>, /// Whenever an incoming request arrives, a `Future` is added to this list and will yield the /// start time and the response to send back to the remote. @@ -351,11 +357,11 @@ pub struct RequestResponsesBehaviour { >, /// Whenever an incoming request arrives, the arrival [`Instant`] is recorded here. - pending_responses_arrival_time: HashMap, + pending_responses_arrival_time: HashMap, Instant>, /// Whenever a response is received on `pending_responses`, insert a channel to be notified /// when the request has been sent out. - send_feedback: HashMap>, + send_feedback: HashMap, oneshot::Sender<()>>, /// Primarily used to get a reputation of a node. peer_store: Arc, @@ -364,7 +370,7 @@ pub struct RequestResponsesBehaviour { /// Generated by the response builder and waiting to be processed. struct RequestProcessingOutcome { peer: PeerId, - request_id: RequestId, + request_id: InboundRequestId, protocol: ProtocolName, inner_channel: ResponseChannel, ()>>, response: OutgoingResponse, @@ -379,8 +385,7 @@ impl RequestResponsesBehaviour { ) -> Result { let mut protocols = HashMap::new(); for protocol in list { - let mut cfg = Config::default(); - cfg.set_request_timeout(protocol.request_timeout); + let cfg = Config::default().with_request_timeout(protocol.request_timeout); let protocol_support = if protocol.inbound_queue.is_some() { ProtocolSupport::Full @@ -455,7 +460,7 @@ impl RequestResponsesBehaviour { fn send_request_inner( behaviour: &mut Behaviour, - pending_requests: &mut HashMap, + pending_requests: &mut HashMap, PendingRequest>, target: &PeerId, protocol_name: ProtocolName, request: Vec, @@ -541,11 +546,16 @@ impl NetworkBehaviour for RequestResponsesBehaviour { peer: PeerId, addr: &Multiaddr, role_override: Endpoint, + port_use: PortUse, ) -> Result, ConnectionDenied> { let iter = self.protocols.iter_mut().filter_map(|(p, (r, _))| { - if let Ok(handler) = - r.handle_established_outbound_connection(connection_id, peer, addr, role_override) - { + if let Ok(handler) = r.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) { Some((p.to_string(), handler)) } else { None @@ -558,80 +568,9 @@ impl NetworkBehaviour for RequestResponsesBehaviour { )) } - fn on_swarm_event(&mut self, event: FromSwarm) { - match event { - FromSwarm::ConnectionEstablished(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ConnectionEstablished(e)); - }, - FromSwarm::ConnectionClosed(ConnectionClosed { - peer_id, - connection_id, - endpoint, - handler, - remaining_established, - }) => - for (p_name, p_handler) in handler.into_iter() { - if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { - proto.on_swarm_event(FromSwarm::ConnectionClosed(ConnectionClosed { - peer_id, - connection_id, - endpoint, - handler: p_handler, - remaining_established, - })); - } else { - log::error!( - target: "sub-libp2p", - "on_swarm_event/connection_closed: no request-response instance registered for protocol {:?}", - p_name, - ) - } - }, - FromSwarm::DialFailure(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::DialFailure(e)); - }, - FromSwarm::ListenerClosed(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ListenerClosed(e)); - }, - FromSwarm::ListenFailure(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ListenFailure(e)); - }, - FromSwarm::ListenerError(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ListenerError(e)); - }, - FromSwarm::ExternalAddrExpired(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ExternalAddrExpired(e)); - }, - FromSwarm::NewListener(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::NewListener(e)); - }, - FromSwarm::ExpiredListenAddr(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ExpiredListenAddr(e)); - }, - FromSwarm::NewExternalAddrCandidate(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::NewExternalAddrCandidate(e)); - }, - FromSwarm::ExternalAddrConfirmed(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::ExternalAddrConfirmed(e)); - }, - FromSwarm::AddressChange(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::AddressChange(e)); - }, - FromSwarm::NewListenAddr(e) => - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::on_swarm_event(p, FromSwarm::NewListenAddr(e)); - }, + fn on_swarm_event(&mut self, event: FromSwarm) { + for (protocol, _) in self.protocols.values_mut() { + protocol.on_swarm_event(event); } } @@ -653,11 +592,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } } - fn poll( - &mut self, - cx: &mut Context, - params: &mut impl PollParameters, - ) -> Poll>> { + fn poll(&mut self, cx: &mut Context) -> Poll>> { 'poll_all: loop { // Poll to see if any response is ready to be sent back. while let Poll::Ready(Some(outcome)) = self.pending_responses.poll_next_unpin(cx) { @@ -707,7 +642,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { // Poll request-responses protocols. for (protocol, (ref mut behaviour, ref mut resp_builder)) in &mut self.protocols { - 'poll_protocol: while let Poll::Ready(ev) = behaviour.poll(cx, params) { + 'poll_protocol: while let Poll::Ready(ev) = behaviour.poll(cx) { let ev = match ev { // Main events we are interested in. ToSwarm::GenerateEvent(ev) => ev, @@ -717,29 +652,23 @@ impl NetworkBehaviour for RequestResponsesBehaviour { ToSwarm::Dial { opts } => { if opts.get_peer_id().is_none() { log::error!( + target: "sub-libp2p", "The request-response isn't supposed to start dialing addresses" ); } return Poll::Ready(ToSwarm::Dial { opts }) }, - ToSwarm::NotifyHandler { peer_id, handler, event } => - return Poll::Ready(ToSwarm::NotifyHandler { - peer_id, - handler, - event: ((*protocol).to_string(), event), - }), - ToSwarm::CloseConnection { peer_id, connection } => - return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), - ToSwarm::NewExternalAddrCandidate(observed) => - return Poll::Ready(ToSwarm::NewExternalAddrCandidate(observed)), - ToSwarm::ExternalAddrConfirmed(addr) => - return Poll::Ready(ToSwarm::ExternalAddrConfirmed(addr)), - ToSwarm::ExternalAddrExpired(addr) => - return Poll::Ready(ToSwarm::ExternalAddrExpired(addr)), - ToSwarm::ListenOn { opts } => - return Poll::Ready(ToSwarm::ListenOn { opts }), - ToSwarm::RemoveListener { id } => - return Poll::Ready(ToSwarm::RemoveListener { id }), + event => { + return Poll::Ready( + event.map_in(|event| ((*protocol).to_string(), event)).map_out( + |_| { + unreachable!( + "`GenerateEvent` is handled in a branch above; qed" + ) + }, + ), + ); + }, }; match ev { @@ -859,6 +788,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { error, .. } => { + let error = OutboundFailure::from(error); let started = match self .pending_requests .remove(&(protocol.clone(), request_id).into()) @@ -870,9 +800,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { }) => { // Try using the fallback request if the protocol was not // supported. - if let request_response::OutboundFailure::UnsupportedProtocols = - error - { + if matches!(error, OutboundFailure::UnsupportedProtocols) { if let Some((fallback_request, fallback_protocol)) = fallback_request { @@ -893,7 +821,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } if response_tx - .send(Err(RequestFailure::Network(error.clone().into()))) + .send(Err(RequestFailure::Network(error.clone()))) .is_err() { log::debug!( @@ -920,7 +848,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { peer, protocol: protocol.clone(), duration: started.elapsed(), - result: Err(RequestFailure::Network(error.into())), + result: Err(RequestFailure::Network(error)), }; return Poll::Ready(ToSwarm::GenerateEvent(out)) @@ -1184,7 +1112,10 @@ mod tests { transport, behaviour, keypair.public().to_peer_id(), - SwarmConfig::with_executor(TokioExecutor(runtime)), + SwarmConfig::with_executor(TokioExecutor(runtime)) + // This is taken care of by notification protocols in non-test environment + // It is very slow in test environment for some reason, hence larger timeout + .with_idle_connection_timeout(Duration::from_secs(10)), ); let listen_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); @@ -1354,7 +1285,9 @@ mod tests { match swarm.select_next_some().await { SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { assert!(result.is_ok()); - break + }, + SwarmEvent::ConnectionClosed { .. } => { + break; }, _ => {}, } @@ -1394,20 +1327,20 @@ mod tests { } match response_receiver.unwrap().await.unwrap().unwrap_err() { - RequestFailure::Network(OutboundFailure::ConnectionClosed) => {}, - _ => panic!(), + RequestFailure::Network(OutboundFailure::Io(_)) => {}, + request_failure => panic!("Unexpected failure: {request_failure:?}"), } }); } - /// A [`RequestId`] is a unique identifier among either all inbound or all outbound requests for + /// A `RequestId` is a unique identifier among either all inbound or all outbound requests for /// a single [`RequestResponsesBehaviour`] behaviour. It is not guaranteed to be unique across - /// multiple [`RequestResponsesBehaviour`] behaviours. Thus when handling [`RequestId`] in the + /// multiple [`RequestResponsesBehaviour`] behaviours. Thus, when handling `RequestId` in the /// context of multiple [`RequestResponsesBehaviour`] behaviours, one needs to couple the - /// protocol name with the [`RequestId`] to get a unique request identifier. + /// protocol name with the `RequestId` to get a unique request identifier. /// /// This test ensures that two requests on different protocols can be handled concurrently - /// without a [`RequestId`] collision. + /// without a `RequestId` collision. /// /// See [`ProtocolRequestId`] for additional information. #[test] diff --git a/substrate/client/network/src/service.rs b/substrate/client/network/src/service.rs index 803b81129139..751183ae19a9 100644 --- a/substrate/client/network/src/service.rs +++ b/substrate/client/network/src/service.rs @@ -41,7 +41,7 @@ use crate::{ NetworkState, NotConnectedPeer as NetworkStateNotConnectedPeer, Peer as NetworkStatePeer, }, peer_store::{PeerStore, PeerStoreProvider}, - protocol::{self, NotifsHandlerError, Protocol, Ready}, + protocol::{self, Protocol, Ready}, protocol_controller::{self, ProtoSetConfig, ProtocolController, SetId}, request_responses::{IfDisconnected, ProtocolConfig as RequestResponseConfig, RequestFailure}, service::{ @@ -59,10 +59,7 @@ use crate::{ }; use codec::DecodeAll; -use either::Either; use futures::{channel::oneshot, prelude::*}; -#[allow(deprecated)] -use libp2p::swarm::THandlerErr; use libp2p::{ connection_limits::{ConnectionLimits, Exceeded}, core::{upgrade, ConnectedPoint, Endpoint}, @@ -94,7 +91,6 @@ pub use libp2p::identity::{DecodingError, Keypair, PublicKey}; pub use metrics::NotificationMetrics; pub use protocol::NotificationsSink; use std::{ - cmp, collections::{HashMap, HashSet}, fs, iter, marker::PhantomData, @@ -115,6 +111,7 @@ pub mod signature; pub mod traits; struct Libp2pBandwidthSink { + #[allow(deprecated)] sink: Arc, } @@ -336,7 +333,7 @@ where "🏷 Local node identity is: {}", local_peer_id.to_base58(), ); - log::info!(target: "sub-libp2p", "Running libp2p network backend"); + info!(target: "sub-libp2p", "Running libp2p network backend"); let (transport, bandwidth) = { let config_mem = match network_config.transport { @@ -344,46 +341,7 @@ where TransportConfig::Normal { .. } => false, }; - // The yamux buffer size limit is configured to be equal to the maximum frame size - // of all protocols. 10 bytes are added to each limit for the length prefix that - // is not included in the upper layer protocols limit but is still present in the - // yamux buffer. These 10 bytes correspond to the maximum size required to encode - // a variable-length-encoding 64bits number. In other words, we make the - // assumption that no notification larger than 2^64 will ever be sent. - let yamux_maximum_buffer_size = { - let requests_max = request_response_protocols - .iter() - .map(|cfg| usize::try_from(cfg.max_request_size).unwrap_or(usize::MAX)); - let responses_max = request_response_protocols - .iter() - .map(|cfg| usize::try_from(cfg.max_response_size).unwrap_or(usize::MAX)); - let notifs_max = notification_protocols - .iter() - .map(|cfg| usize::try_from(cfg.max_notification_size()).unwrap_or(usize::MAX)); - - // A "default" max is added to cover all the other protocols: ping, identify, - // kademlia, block announces, and transactions. - let default_max = cmp::max( - 1024 * 1024, - usize::try_from(protocol::BLOCK_ANNOUNCES_TRANSACTIONS_SUBSTREAM_SIZE) - .unwrap_or(usize::MAX), - ); - - iter::once(default_max) - .chain(requests_max) - .chain(responses_max) - .chain(notifs_max) - .max() - .expect("iterator known to always yield at least one element; qed") - .saturating_add(10) - }; - - transport::build_transport( - local_identity.clone().into(), - config_mem, - network_config.yamux_window_size, - yamux_maximum_buffer_size, - ) + transport::build_transport(local_identity.clone().into(), config_mem) }; let (to_notifications, from_protocol_controllers) = @@ -1522,8 +1480,7 @@ where } /// Process the next event coming from `Swarm`. - #[allow(deprecated)] - fn handle_swarm_event(&mut self, event: SwarmEvent>>) { + fn handle_swarm_event(&mut self, event: SwarmEvent) { match event { SwarmEvent::Behaviour(BehaviourOut::InboundRequest { protocol, result, .. }) => { if let Some(metrics) = self.metrics.as_ref() { @@ -1548,6 +1505,7 @@ where Some("busy-omitted"), ResponseFailure::Network(InboundFailure::ConnectionClosed) => Some("connection-closed"), + ResponseFailure::Network(InboundFailure::Io(_)) => Some("io"), }; if let Some(reason) = reason { @@ -1587,6 +1545,7 @@ where "connection-closed", RequestFailure::Network(OutboundFailure::UnsupportedProtocols) => "unsupported", + RequestFailure::Network(OutboundFailure::Io(_)) => "io", }; metrics @@ -1756,15 +1715,6 @@ where }; let reason = match cause { Some(ConnectionError::IO(_)) => "transport-error", - Some(ConnectionError::Handler(Either::Left(Either::Left( - Either::Left(Either::Right( - NotifsHandlerError::SyncNotificationsClogged, - )), - )))) => "sync-notifications-clogged", - Some(ConnectionError::Handler(Either::Left(Either::Left( - Either::Right(Either::Left(_)), - )))) => "ping-timeout", - Some(ConnectionError::Handler(_)) => "protocol-error", Some(ConnectionError::KeepAliveTimeout) => "keep-alive-timeout", None => "actively-closed", }; @@ -1803,7 +1753,12 @@ where not_reported.then(|| self.boot_node_ids.get(&peer_id)).flatten() { if let DialError::WrongPeerId { obtained, endpoint } = &error { - if let ConnectedPoint::Dialer { address, role_override: _ } = endpoint { + if let ConnectedPoint::Dialer { + address, + role_override: _, + port_use: _, + } = endpoint + { let address_without_peer_id = parse_addr(address.clone().into()) .map_or_else(|_| address.clone(), |r| r.1.into()); @@ -1824,7 +1779,6 @@ where } if let Some(metrics) = self.metrics.as_ref() { - #[allow(deprecated)] let reason = match error { DialError::Denied { cause } => if cause.downcast::().is_ok() { @@ -1864,7 +1818,6 @@ where "Libp2p => IncomingConnectionError({local_addr},{send_back_addr} via {connection_id:?}): {error}" ); if let Some(metrics) = self.metrics.as_ref() { - #[allow(deprecated)] let reason = match error { ListenError::Denied { cause } => if cause.downcast::().is_ok() { @@ -1917,6 +1870,21 @@ where metrics.listeners_errors_total.inc(); } }, + SwarmEvent::NewExternalAddrCandidate { address } => { + trace!(target: "sub-libp2p", "Libp2p => NewExternalAddrCandidate: {address:?}"); + }, + SwarmEvent::ExternalAddrConfirmed { address } => { + trace!(target: "sub-libp2p", "Libp2p => ExternalAddrConfirmed: {address:?}"); + }, + SwarmEvent::ExternalAddrExpired { address } => { + trace!(target: "sub-libp2p", "Libp2p => ExternalAddrExpired: {address:?}"); + }, + SwarmEvent::NewExternalAddrOfPeer { peer_id, address } => { + trace!(target: "sub-libp2p", "Libp2p => NewExternalAddrOfPeer({peer_id:?}): {address:?}") + }, + event => { + warn!(target: "sub-libp2p", "New unknown SwarmEvent libp2p event: {event:?}"); + }, } } } diff --git a/substrate/client/network/src/transport.rs b/substrate/client/network/src/transport.rs index ed7e7c574e16..2f6b7a643c48 100644 --- a/substrate/client/network/src/transport.rs +++ b/substrate/client/network/src/transport.rs @@ -29,6 +29,8 @@ use libp2p::{ }; use std::{sync::Arc, time::Duration}; +// TODO: Create a wrapper similar to upstream `BandwidthTransport` that tracks sent/received bytes +#[allow(deprecated)] pub use libp2p::bandwidth::BandwidthSinks; /// Builds the transport that serves as a common ground for all connections. @@ -36,21 +38,12 @@ pub use libp2p::bandwidth::BandwidthSinks; /// If `memory_only` is true, then only communication within the same process are allowed. Only /// addresses with the format `/memory/...` are allowed. /// -/// `yamux_window_size` is the maximum size of the Yamux receive windows. `None` to leave the -/// default (256kiB). -/// -/// `yamux_maximum_buffer_size` is the maximum allowed size of the Yamux buffer. This should be -/// set either to the maximum of all the maximum allowed sizes of messages frames of all -/// high-level protocols combined, or to some generously high value if you are sure that a maximum -/// size is enforced on all high-level protocols. -/// /// Returns a `BandwidthSinks` object that allows querying the average bandwidth produced by all /// the connections spawned with this transport. +#[allow(deprecated)] pub fn build_transport( keypair: identity::Keypair, memory_only: bool, - yamux_window_size: Option, - yamux_maximum_buffer_size: usize, ) -> (Boxed<(PeerId, StreamMuxerBox)>, Arc) { // Build the base layer of the transport. let transport = if !memory_only { @@ -81,19 +74,7 @@ pub fn build_transport( }; let authentication_config = noise::Config::new(&keypair).expect("Can create noise config. qed"); - let multiplexing_config = { - let mut yamux_config = libp2p::yamux::Config::default(); - // Enable proper flow-control: window updates are only sent when - // buffered data has been consumed. - yamux_config.set_window_update_mode(libp2p::yamux::WindowUpdateMode::on_read()); - yamux_config.set_max_buffer_size(yamux_maximum_buffer_size); - - if let Some(yamux_window_size) = yamux_window_size { - yamux_config.set_receive_window_size(yamux_window_size); - } - - yamux_config - }; + let multiplexing_config = libp2p::yamux::Config::default(); let transport = transport .upgrade(upgrade::Version::V1Lazy) diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index 349c41ee1f4a..0c39ea0b93c0 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -100,6 +100,8 @@ mod rep { pub const REFUSED: Rep = Rep::new(-(1 << 10), "Request refused"); /// Reputation change when a peer doesn't respond in time to our messages. pub const TIMEOUT: Rep = Rep::new(-(1 << 10), "Request timeout"); + /// Reputation change when a peer connection failed with IO error. + pub const IO: Rep = Rep::new(-(1 << 10), "IO error during request"); } struct Metrics { @@ -1019,9 +1021,14 @@ where debug_assert!( false, "Can not receive `RequestFailure::Obsolete` after dropping the \ - response receiver.", + response receiver.", ); }, + RequestFailure::Network(OutboundFailure::Io(_)) => { + self.network_service.report_peer(peer_id, rep::IO); + self.network_service + .disconnect_peer(peer_id, self.block_announce_protocol_name.clone()); + }, } }, Err(oneshot::Canceled) => { diff --git a/substrate/client/network/types/Cargo.toml b/substrate/client/network/types/Cargo.toml index 7438eaeffcd2..67814f135d39 100644 --- a/substrate/client/network/types/Cargo.toml +++ b/substrate/client/network/types/Cargo.toml @@ -14,7 +14,7 @@ bs58 = { workspace = true, default-features = true } bytes = { version = "1.4.0", default-features = false } ed25519-dalek = { workspace = true, default-features = true } libp2p-identity = { features = ["ed25519", "peerid", "rand"], workspace = true } -libp2p-kad = { version = "0.44.6", default-features = false } +libp2p-kad = { version = "0.46.2", default-features = false } litep2p = { workspace = true } log = { workspace = true, default-features = true } multiaddr = { workspace = true } diff --git a/substrate/client/telemetry/Cargo.toml b/substrate/client/telemetry/Cargo.toml index f87e8b66f731..db325a94ab21 100644 --- a/substrate/client/telemetry/Cargo.toml +++ b/substrate/client/telemetry/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] chrono = { workspace = true } futures = { workspace = true } -libp2p = { features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"], workspace = true } +libp2p = { features = ["dns", "tcp", "tokio", "websocket"], workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } pin-project = { workspace = true } diff --git a/substrate/client/telemetry/src/node.rs b/substrate/client/telemetry/src/node.rs index 0bbdbfb622ef..2c8d424c4340 100644 --- a/substrate/client/telemetry/src/node.rs +++ b/substrate/client/telemetry/src/node.rs @@ -18,7 +18,13 @@ use crate::TelemetryPayload; use futures::{channel::mpsc, prelude::*}; -use libp2p::{core::transport::Transport, Multiaddr}; +use libp2p::{ + core::{ + transport::{DialOpts, PortUse, Transport}, + Endpoint, + }, + Multiaddr, +}; use rand::Rng as _; use std::{ fmt, mem, @@ -229,7 +235,10 @@ where }, NodeSocket::ReconnectNow => { let addr = self.addr.clone(); - match self.transport.dial(addr) { + match self + .transport + .dial(addr, DialOpts { role: Endpoint::Dialer, port_use: PortUse::New }) + { Ok(d) => { log::trace!(target: "telemetry", "Re-dialing {}", self.addr); socket = NodeSocket::Dialing(d); From cee63ac0a34f356c66f1305073fa3d1210550a0c Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Mon, 16 Dec 2024 10:14:39 +0100 Subject: [PATCH 171/340] Omni-node: Detect pending code in storage and send go ahead signal in dev-mode. (#6885) We check if there is a pending validation code in storage. If there is, add the go-ahead signal in the relay chain storage proof. Not super elegant, but should get the job done for development. --------- Co-authored-by: command-bot <> --- cumulus/client/parachain-inherent/src/mock.rs | 13 ++++++----- .../lib/src/nodes/manual_seal.rs | 22 ++++++++++++++++++- prdoc/pr_6885.prdoc | 11 ++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 prdoc/pr_6885.prdoc diff --git a/cumulus/client/parachain-inherent/src/mock.rs b/cumulus/client/parachain-inherent/src/mock.rs index 950cba2aaa7d..e08aca932564 100644 --- a/cumulus/client/parachain-inherent/src/mock.rs +++ b/cumulus/client/parachain-inherent/src/mock.rs @@ -17,17 +17,17 @@ use crate::{ParachainInherentData, INHERENT_IDENTIFIER}; use codec::Decode; use cumulus_primitives_core::{ - relay_chain, InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData, + relay_chain, relay_chain::UpgradeGoAhead, InboundDownwardMessage, InboundHrmpMessage, ParaId, + PersistedValidationData, }; use cumulus_primitives_parachain_inherent::MessageQueueChain; +use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use sc_client_api::{Backend, StorageProvider}; use sp_crypto_hashing::twox_128; use sp_inherents::{InherentData, InherentDataProvider}; use sp_runtime::traits::Block; use std::collections::BTreeMap; -use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; - /// Relay chain slot duration, in milliseconds. pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; @@ -68,10 +68,12 @@ pub struct MockValidationDataInherentDataProvider { pub xcm_config: MockXcmConfig, /// Inbound downward XCM messages to be injected into the block. pub raw_downward_messages: Vec>, - // Inbound Horizontal messages sorted by channel. + /// Inbound Horizontal messages sorted by channel. pub raw_horizontal_messages: Vec<(ParaId, Vec)>, - // Additional key-value pairs that should be injected. + /// Additional key-value pairs that should be injected. pub additional_key_values: Option, Vec)>>, + /// Whether upgrade go ahead should be set. + pub upgrade_go_ahead: Option, } /// Something that can generate randomness. @@ -176,6 +178,7 @@ impl> InherentDataProvider sproof_builder.current_slot = ((relay_parent_number / RELAY_CHAIN_SLOT_DURATION_MILLIS) as u64).into(); + sproof_builder.upgrade_go_ahead = self.upgrade_go_ahead; // Process the downward messages and set up the correct head let mut downward_messages = Vec::new(); let mut dmq_mqc = MessageQueueChain::new(self.xcm_config.starting_dmq_mqc_head); diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs b/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs index 8b7921da30c0..f33865ad45cd 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/manual_seal.rs @@ -21,12 +21,14 @@ use crate::common::{ }; use codec::Encode; use cumulus_client_parachain_inherent::{MockValidationDataInherentDataProvider, MockXcmConfig}; -use cumulus_primitives_core::ParaId; +use cumulus_primitives_core::{CollectCollationInfo, ParaId}; +use polkadot_primitives::UpgradeGoAhead; use sc_consensus::{DefaultImportQueue, LongestChain}; use sc_consensus_manual_seal::rpc::{ManualSeal, ManualSealApiServer}; use sc_network::NetworkBackend; use sc_service::{Configuration, PartialComponents, TaskManager}; use sc_telemetry::TelemetryHandle; +use sp_api::ProvideRuntimeApi; use sp_runtime::traits::Header; use std::{marker::PhantomData, sync::Arc}; @@ -155,6 +157,18 @@ impl ManualSealNode { .header(block) .expect("Header lookup should succeed") .expect("Header passed in as parent should be present in backend."); + + let should_send_go_ahead = match client_for_cidp + .runtime_api() + .collect_collation_info(block, ¤t_para_head) + { + Ok(info) => info.new_validation_code.is_some(), + Err(e) => { + log::error!("Failed to collect collation info: {:?}", e); + false + }, + }; + let current_para_block_head = Some(polkadot_primitives::HeadData(current_para_head.encode())); let client_for_xcm = client_for_cidp.clone(); @@ -177,6 +191,12 @@ impl ManualSealNode { raw_downward_messages: vec![], raw_horizontal_messages: vec![], additional_key_values: None, + upgrade_go_ahead: should_send_go_ahead.then(|| { + log::info!( + "Detected pending validation code, sending go-ahead signal." + ); + UpgradeGoAhead::GoAhead + }), }; Ok(( // This is intentional, as the runtime that we expect to run against this diff --git a/prdoc/pr_6885.prdoc b/prdoc/pr_6885.prdoc new file mode 100644 index 000000000000..986d76962289 --- /dev/null +++ b/prdoc/pr_6885.prdoc @@ -0,0 +1,11 @@ +title: 'Omni-node: Detect pending code in storage and send go ahead signal in dev-mode.' +doc: +- audience: Runtime Dev + description: |- + When using the polkadot-omni-node with manual seal (`--dev-block-time`), it is now possible to perform runtime + upgrades. The node will detect the pending validation code and send a go-ahead signal to the parachain. +crates: +- name: cumulus-client-parachain-inherent + bump: major +- name: polkadot-omni-node-lib + bump: patch From adc0178f8702bb619bfebd570edf451f5253d3e0 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:35:28 +0200 Subject: [PATCH 172/340] polkadot-omni-node-lib: remove unused dep (#6889) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Redundant dep that made its way in #6450 . :sweat_smile:. It can be brought up when using `cargo udeps`. Added a github action that runs `cargo udeps` on the repo too. ## Integration N/A ## Review Notes N/A --------- Signed-off-by: Iulian Barbu Co-authored-by: Bastian Köcher --- Cargo.lock | 1 - cumulus/polkadot-omni-node/lib/Cargo.toml | 1 - prdoc/pr_6889.prdoc | 13 +++++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_6889.prdoc diff --git a/Cargo.lock b/Cargo.lock index 0902fe6fcfbc..13ba03af5065 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18160,7 +18160,6 @@ dependencies = [ "sp-timestamp 26.0.0", "sp-transaction-pool 26.0.0", "sp-version 29.0.0", - "sp-wasm-interface 20.0.0", "sp-weights 27.0.0", "substrate-frame-rpc-system", "substrate-prometheus-endpoint", diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index afbe03ada89c..43478b41a119 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -70,7 +70,6 @@ sp-api = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-consensus-aura = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-wasm-interface = { workspace = true, default-features = true } sc-consensus-manual-seal = { workspace = true, default-features = true } sc-sysinfo = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } diff --git a/prdoc/pr_6889.prdoc b/prdoc/pr_6889.prdoc new file mode 100644 index 000000000000..01edd49b685a --- /dev/null +++ b/prdoc/pr_6889.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Remove polkadot-omni-node-lib unused dependency + +doc: + - audience: Node Dev + description: + Removed an unused dependency for `polkadot-omni-node-lib`. + +crates: + - name: polkadot-omni-node-lib + bump: patch From 5b04b4598cc7b2c8e817a6304c7cdfaf002c1fee Mon Sep 17 00:00:00 2001 From: Jun Jiang Date: Tue, 17 Dec 2024 00:29:46 +0800 Subject: [PATCH 173/340] Upgrade nix and reqwest (#6898) # Description Upgrade `nix` and `reqwest` to reduce outdated dependencies and speed up compilation. --- Cargo.lock | 118 ++++++++++++++++++++++++++++++++++------------------- Cargo.toml | 4 +- 2 files changed, 79 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13ba03af5065..98f6873f7b3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3250,6 +3250,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chacha" version = "0.3.0" @@ -9037,7 +9043,7 @@ dependencies = [ "socket2 0.5.7", "widestring", "windows-sys 0.48.0", - "winreg 0.50.0", + "winreg", ] [[package]] @@ -11267,14 +11273,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", "cfg-if", "libc", - "static_assertions", ] [[package]] @@ -11290,13 +11295,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.28.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags 2.6.0", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.2.1", "libc", ] @@ -17072,7 +17077,7 @@ version = "6.0.0" dependencies = [ "assert_cmd", "color-eyre", - "nix 0.28.0", + "nix 0.29.0", "polkadot-cli", "polkadot-core-primitives 7.0.0", "polkadot-node-core-pvf", @@ -17821,7 +17826,7 @@ dependencies = [ "futures", "landlock", "libc", - "nix 0.28.0", + "nix 0.29.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-primitives 7.0.0", @@ -17846,7 +17851,7 @@ dependencies = [ "cfg-if", "cpu-time", "libc", - "nix 0.28.0", + "nix 0.29.0", "parity-scale-codec", "polkadot-node-core-pvf-common", "polkadot-node-primitives", @@ -17864,7 +17869,7 @@ dependencies = [ "cfg-if", "criterion", "libc", - "nix 0.28.0", + "nix 0.29.0", "parity-scale-codec", "polkadot-node-core-pvf-common", "polkadot-node-primitives", @@ -18117,7 +18122,7 @@ dependencies = [ "futures-timer", "jsonrpsee", "log", - "nix 0.28.0", + "nix 0.29.0", "pallet-transaction-payment 28.0.0", "pallet-transaction-payment-rpc", "pallet-transaction-payment-rpc-runtime-api 28.0.0", @@ -20336,7 +20341,7 @@ dependencies = [ "findshlibs", "libc", "log", - "nix 0.26.2", + "nix 0.26.4", "once_cell", "parking_lot 0.12.3", "smallvec", @@ -20787,7 +20792,7 @@ dependencies = [ "log", "names", "prost 0.11.9", - "reqwest 0.11.20", + "reqwest 0.11.27", "thiserror", "url", "winapi", @@ -21331,9 +21336,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ "base64 0.21.7", "bytes", @@ -21359,6 +21364,8 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls 0.24.1", @@ -21368,14 +21375,14 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots 0.25.2", - "winreg 0.50.0", + "winreg", ] [[package]] name = "reqwest" -version = "0.12.5" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -21402,7 +21409,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", "tokio-rustls 0.26.0", "tower-service", @@ -21411,7 +21418,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots 0.26.3", - "winreg 0.52.0", + "windows-registry", ] [[package]] @@ -28149,7 +28156,7 @@ dependencies = [ "jsonrpsee", "kitchensink-runtime", "log", - "nix 0.28.0", + "nix 0.29.0", "node-primitives", "node-rpc", "node-testing", @@ -28372,7 +28379,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" dependencies = [ "bitflags 1.3.2", - "cfg_aliases", + "cfg_aliases 0.1.1", "libc", "parking_lot 0.11.2", "parking_lot_core 0.8.6", @@ -28386,7 +28393,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" dependencies = [ - "cfg_aliases", + "cfg_aliases 0.1.1", "memchr", "proc-macro2 1.0.86", "quote 1.0.37", @@ -28577,7 +28584,7 @@ version = "0.1.0" dependencies = [ "assert_cmd", "futures", - "nix 0.28.0", + "nix 0.29.0", "node-primitives", "regex", "sc-cli", @@ -28950,7 +28957,7 @@ dependencies = [ "log", "num-format", "rand", - "reqwest 0.12.5", + "reqwest 0.12.9", "scale-info", "semver 1.0.18", "serde", @@ -29283,11 +29290,20 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" @@ -31475,6 +31491,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -31717,16 +31763,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wyz" version = "0.5.1" @@ -32132,7 +32168,7 @@ version = "1.0.0" dependencies = [ "futures-util", "parity-scale-codec", - "reqwest 0.11.20", + "reqwest 0.12.9", "serde", "serde_json", "thiserror", @@ -32152,7 +32188,7 @@ dependencies = [ "lazy_static", "multiaddr 0.18.1", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", @@ -32178,7 +32214,7 @@ dependencies = [ "multiaddr 0.18.1", "rand", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "serde", "serde_json", "sha2 0.10.8", @@ -32221,7 +32257,7 @@ dependencies = [ "kube", "nix 0.27.1", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "serde", "serde_json", "serde_yaml", @@ -32266,7 +32302,7 @@ dependencies = [ "nix 0.27.1", "rand", "regex", - "reqwest 0.11.20", + "reqwest 0.11.27", "thiserror", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 37765056196e..56ae70f8f993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -871,7 +871,7 @@ multihash = { version = "0.19.1", default-features = false } multihash-codetable = { version = "0.1.1" } multistream-select = { version = "0.13.0" } names = { version = "0.14.0", default-features = false } -nix = { version = "0.28.0" } +nix = { version = "0.29.0" } node-cli = { path = "substrate/bin/node/cli", package = "staging-node-cli" } node-inspect = { path = "substrate/bin/node/inspect", default-features = false, package = "staging-node-inspect" } node-primitives = { path = "substrate/bin/node/primitives", default-features = false } @@ -1123,7 +1123,7 @@ regex = { version = "1.10.2" } relay-substrate-client = { path = "bridges/relays/client-substrate" } relay-utils = { path = "bridges/relays/utils" } remote-externalities = { path = "substrate/utils/frame/remote-externalities", default-features = false, package = "frame-remote-externalities" } -reqwest = { version = "0.11", default-features = false } +reqwest = { version = "0.12.9", default-features = false } rlp = { version = "0.6.1", default-features = false } rococo-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/relays/rococo" } rococo-parachain-runtime = { path = "cumulus/parachains/runtimes/testing/rococo-parachain" } From 31179c4099f01e75979fb4281c8ab57b3a830d5b Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:52:39 +0100 Subject: [PATCH 174/340] ci: 5 retries for cargo (#6903) cc https://github.com/paritytech/ci_cd/issues/1038 --- .cargo/config.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.cargo/config.toml b/.cargo/config.toml index 1b8ffe1a1c82..68a0d7b552dc 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -9,3 +9,7 @@ rustdocflags = [ CC_x86_64_unknown_linux_musl = { value = ".cargo/musl-gcc", force = true, relative = true } CXX_x86_64_unknown_linux_musl = { value = ".cargo/musl-g++", force = true, relative = true } CARGO_WORKSPACE_ROOT_DIR = { value = "", relative = true } + +[net] +retry = 5 +# git-fetch-with-cli = true # commented because there is a risk that a runner can be banned by github From 05589737508b0e444c43268fbb9a697a433d0108 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Tue, 17 Dec 2024 20:19:14 +0100 Subject: [PATCH 175/340] Remove unused dependencies from pallet_revive (#6917) Removing apparently unused dependencies from `pallet_revive` and related crates. --------- Co-authored-by: command-bot <> --- Cargo.lock | 15 --------------- prdoc/pr_6917.prdoc | 14 ++++++++++++++ substrate/frame/revive/Cargo.toml | 3 --- substrate/frame/revive/fixtures/Cargo.toml | 5 +---- substrate/frame/revive/fixtures/src/lib.rs | 1 - substrate/frame/revive/mock-network/Cargo.toml | 13 ------------- substrate/frame/revive/rpc/Cargo.toml | 11 ++--------- 7 files changed, 17 insertions(+), 45 deletions(-) create mode 100644 prdoc/pr_6917.prdoc diff --git a/Cargo.lock b/Cargo.lock index 98f6873f7b3f..03348af568e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14827,7 +14827,6 @@ version = "0.1.0" dependencies = [ "array-bytes", "assert_matches", - "bitflags 1.3.2", "derive_more 0.99.17", "environmental", "ethereum-types 0.15.1", @@ -14863,7 +14862,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-std 14.0.0", "sp-tracing 16.0.0", - "sp-weights 27.0.0", "staging-xcm 7.0.0", "staging-xcm-builder 7.0.0", "subxt-signer", @@ -14910,7 +14908,6 @@ dependencies = [ "ethabi", "futures", "hex", - "hex-literal", "jsonrpsee", "log", "pallet-revive 0.1.0", @@ -14921,12 +14918,8 @@ dependencies = [ "sc-rpc", "sc-rpc-api", "sc-service", - "scale-info", - "secp256k1 0.28.2", - "serde_json", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", - "sp-runtime 31.0.1", "sp-weights 27.0.0", "static_init", "substrate-cli-test-utils", @@ -14942,12 +14935,9 @@ name = "pallet-revive-fixtures" version = "0.1.0" dependencies = [ "anyhow", - "frame-system 28.0.0", - "log", "polkavm-linker 0.17.1", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-runtime 31.0.1", "toml 0.8.12", ] @@ -14976,13 +14966,10 @@ dependencies = [ "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-message-queue 31.0.0", - "pallet-proxy 28.0.0", "pallet-revive 0.1.0", "pallet-revive-fixtures 0.1.0", - "pallet-revive-proc-macro 0.1.0", "pallet-revive-uapi 0.1.0", "pallet-timestamp 27.0.0", - "pallet-utility 28.0.0", "pallet-xcm 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", @@ -14990,10 +14977,8 @@ dependencies = [ "polkadot-runtime-parachains 7.0.0", "pretty_assertions", "scale-info", - "sp-api 26.0.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-tracing 16.0.0", "staging-xcm 7.0.0", diff --git a/prdoc/pr_6917.prdoc b/prdoc/pr_6917.prdoc new file mode 100644 index 000000000000..dd7f59b95126 --- /dev/null +++ b/prdoc/pr_6917.prdoc @@ -0,0 +1,14 @@ +title: Remove unused dependencies from pallet_revive +doc: +- audience: Runtime Dev + description: Removing apparently unused dependencies from `pallet_revive` and related + crates. +crates: +- name: pallet-revive + bump: major +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive-mock-network + bump: major +- name: pallet-revive-eth-rpc + bump: major diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index e61554f5cfa0..6e244ad4d652 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -20,7 +20,6 @@ targets = ["x86_64-unknown-linux-gnu"] environmental = { workspace = true } paste = { workspace = true } polkavm = { version = "0.17.0", default-features = false } -bitflags = { workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } scale-info = { features = ["derive"], workspace = true } log = { workspace = true } @@ -48,7 +47,6 @@ sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } -sp-weights = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } subxt-signer = { workspace = true, optional = true, features = [ @@ -102,7 +100,6 @@ std = [ "sp-keystore/std", "sp-runtime/std", "sp-std/std", - "sp-weights/std", "subxt-signer", "xcm-builder/std", "xcm/std", diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index 88921cca08ec..459ec1369434 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -15,12 +15,9 @@ exclude-from-umbrella = true workspace = true [dependencies] -frame-system = { workspace = true, default-features = true, optional = true } sp-core = { workspace = true, default-features = true, optional = true } sp-io = { workspace = true, default-features = true, optional = true } -sp-runtime = { workspace = true, default-features = true, optional = true } anyhow = { workspace = true, default-features = true, optional = true } -log = { workspace = true } [build-dependencies] toml = { workspace = true } @@ -30,4 +27,4 @@ anyhow = { workspace = true, default-features = true } [features] default = ["std"] # only when std is enabled all fixtures are available -std = ["anyhow", "frame-system", "log/std", "sp-core", "sp-io", "sp-runtime"] +std = ["anyhow", "sp-core", "sp-io"] diff --git a/substrate/frame/revive/fixtures/src/lib.rs b/substrate/frame/revive/fixtures/src/lib.rs index 24f6ee547dc7..38171edf1152 100644 --- a/substrate/frame/revive/fixtures/src/lib.rs +++ b/substrate/frame/revive/fixtures/src/lib.rs @@ -27,7 +27,6 @@ include!(concat!(env!("OUT_DIR"), "/fixture_location.rs")); pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec, sp_core::H256)> { let out_dir: std::path::PathBuf = FIXTURE_DIR.into(); let fixture_path = out_dir.join(format!("{fixture_name}.polkavm")); - log::debug!("Loading fixture from {fixture_path:?}"); let binary = std::fs::read(fixture_path)?; let code_hash = sp_io::hashing::keccak_256(&binary); Ok((binary, sp_core::H256(code_hash))) diff --git a/substrate/frame/revive/mock-network/Cargo.toml b/substrate/frame/revive/mock-network/Cargo.toml index 6208db45a91e..0d8814f81a9c 100644 --- a/substrate/frame/revive/mock-network/Cargo.toml +++ b/substrate/frame/revive/mock-network/Cargo.toml @@ -20,20 +20,15 @@ pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-revive = { workspace = true, default-features = true } pallet-revive-uapi = { workspace = true } -pallet-revive-proc-macro = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } -pallet-proxy = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } pallet-xcm = { workspace = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } scale-info = { features = ["derive"], workspace = true } -sp-api = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } -sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true } sp-tracing = { workspace = true, default-features = true } xcm = { workspace = true } @@ -53,17 +48,13 @@ std = [ "frame-support/std", "frame-system/std", "pallet-balances/std", - "pallet-proxy/std", "pallet-revive-fixtures/std", "pallet-revive/std", "pallet-timestamp/std", - "pallet-utility/std", "pallet-xcm/std", "scale-info/std", - "sp-api/std", "sp-core/std", "sp-io/std", - "sp-keystore/std", "sp-runtime/std", "xcm-executor/std", "xcm/std", @@ -74,10 +65,8 @@ runtime-benchmarks = [ "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", - "pallet-proxy/runtime-benchmarks", "pallet-revive/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", - "pallet-utility/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", @@ -93,10 +82,8 @@ try-runtime = [ "pallet-assets/try-runtime", "pallet-balances/try-runtime", "pallet-message-queue/try-runtime", - "pallet-proxy/try-runtime", "pallet-revive/try-runtime", "pallet-timestamp/try-runtime", - "pallet-utility/try-runtime", "pallet-xcm/try-runtime", "polkadot-runtime-parachains/try-runtime", "sp-runtime/try-runtime", diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 674abdd5b73e..31b8f505dedc 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -42,40 +42,33 @@ clap = { workspace = true, features = ["derive"] } anyhow = { workspace = true } futures = { workspace = true, features = ["thread-pool"] } jsonrpsee = { workspace = true, features = ["full"] } -serde_json = { workspace = true } thiserror = { workspace = true } sp-crypto-hashing = { workspace = true } subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"] } tokio = { workspace = true, features = ["full"] } codec = { workspace = true, features = ["derive"] } -log.workspace = true +log = { workspace = true } pallet-revive = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-weights = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } - rlp = { workspace = true, optional = true } subxt-signer = { workspace = true, optional = true, features = [ "unstable-eth", ] } hex = { workspace = true } -hex-literal = { workspace = true, optional = true } -scale-info = { workspace = true } -secp256k1 = { workspace = true, optional = true, features = ["recovery"] } ethabi = { version = "18.0.0" } [features] -example = ["hex-literal", "rlp", "secp256k1", "subxt-signer"] +example = ["rlp", "subxt-signer"] [dev-dependencies] env_logger = { workspace = true } static_init = { workspace = true } -hex-literal = { workspace = true } pallet-revive-fixtures = { workspace = true, default-features = true } substrate-cli-test-utils = { workspace = true } subxt-signer = { workspace = true, features = ["unstable-eth"] } From e6ddd3925693adbf538a438c94dc66e66eba9bed Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Tue, 17 Dec 2024 21:44:01 +0100 Subject: [PATCH 176/340] omni-node: Tolerate failing metadata check (#6923) #6450 introduced metadata checks. Supported are metadata v14 and higher. However, of course old chain-specs have a genesis code blob that might be on older version. This needs to be tolerated. We should just skip the checks in that case. Fixes #6921 --------- Co-authored-by: command-bot <> --- Cargo.lock | 1 - cumulus/polkadot-omni-node/lib/Cargo.toml | 1 - cumulus/polkadot-omni-node/lib/src/common/runtime.rs | 6 +++++- prdoc/pr_6923.prdoc | 12 ++++++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_6923.prdoc diff --git a/Cargo.lock b/Cargo.lock index 03348af568e6..c6438fdffa3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18142,7 +18142,6 @@ dependencies = [ "sp-crypto-hashing 0.1.0", "sp-genesis-builder 0.8.0", "sp-inherents 26.0.0", - "sp-io 30.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-session 27.0.0", diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index 43478b41a119..b1937427be66 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -69,7 +69,6 @@ sp-inherents = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-consensus-aura = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } sc-consensus-manual-seal = { workspace = true, default-features = true } sc-sysinfo = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } diff --git a/cumulus/polkadot-omni-node/lib/src/common/runtime.rs b/cumulus/polkadot-omni-node/lib/src/common/runtime.rs index 2a95f41495a6..fcc1d7f0643e 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/runtime.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/runtime.rs @@ -103,7 +103,11 @@ pub struct DefaultRuntimeResolver; impl RuntimeResolver for DefaultRuntimeResolver { fn runtime(&self, chain_spec: &dyn ChainSpec) -> sc_cli::Result { - let metadata_inspector = MetadataInspector::new(chain_spec)?; + let Ok(metadata_inspector) = MetadataInspector::new(chain_spec) else { + log::info!("Unable to check metadata. Skipping metadata checks. Metadata checks are supported for metadata versions v14 and higher."); + return Ok(Runtime::Omni(BlockNumber::U32, Consensus::Aura(AuraConsensusId::Sr25519))) + }; + let block_number = match metadata_inspector.block_number() { Some(inner) => inner, None => { diff --git a/prdoc/pr_6923.prdoc b/prdoc/pr_6923.prdoc new file mode 100644 index 000000000000..5d88d7158e7f --- /dev/null +++ b/prdoc/pr_6923.prdoc @@ -0,0 +1,12 @@ +title: 'omni-node: Tolerate failing metadata check' +doc: +- audience: Node Operator + description: |- + #6450 introduced metadata checks. Supported are metadata v14 and higher. + + However, of course old chain-specs have a genesis code blob that might be on older version. This needs to be tolerated. We should just skip the checks in that case. + + Fixes #6921 +crates: +- name: polkadot-omni-node-lib + bump: patch From 08bfa8602a955064aa4928e24ace78c055629484 Mon Sep 17 00:00:00 2001 From: Frazz <59382025+Sudo-Whodo@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:21:23 -0800 Subject: [PATCH 177/340] adding stkd bootnodes (#6912) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Opening this PR to add our bootnodes for the IBP. These nodes are located in Santiago Chile, we own and manage the underlying hardware. If you need any more information please let me know. ## Integration ``` docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain asset-hub-westend \ --reserved-only \ --reserved-nodes "/dns/asset-hub-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWDUPyF2q8b6fVFEuwxBbRV3coAy1kzuCPU3D9TRiLnUfE" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain bridge-hub-westend \ --reserved-only \ --reserved-nodes "/dns/bridge-hub-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWJEfDZxrEKehoPbW2Mfg6rypttMXCMgMiybmapKqcByc1" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain collectives-westend \ --reserved-only \ --reserved-nodes "/dns/collectives-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWFH7UZnWESzuRSgrLvNSfALjtpr9PmG7QGyRNCizWEHcd" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain people-westend \ --reserved-only \ --reserved-nodes "/dns/people-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWJzL4R3kq9Ms88gsV6bS9zGT8DHySdqwau5SHNqTzToNM" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain coretime-westend \ --reserved-only \ --reserved-nodes "/dns/coretime-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWCFNzjaiq45ZpW2qStmQdG5w7ZHrmi3RWUeG8cV2pPc2Y" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain asset-hub-kusama \ --reserved-only \ --reserved-nodes "/dns/asset-hub-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWNCg821LyWDVrAJ2mG6ScDeeBFuDPiJtLYc9jCGNCyMoq" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain bridge-hub-kusama \ --reserved-only \ --reserved-nodes "/dns/bridge-hub-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWBE1ZhrYqMC3ECFK6qbufS9kgKuF57XpvvZU6LKsPUSnF" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain coretime-kusama \ --reserved-only \ --reserved-nodes "/dns/coretime-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWMPc6jEjzFLRCK7QgbcNh3gvxCzGvDKhU4F66QWf2kZmq" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain people-kusama \ --reserved-only \ --reserved-nodes "/dns/people-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWN32MmhPgZN8e1Dmc8DzEUKsfC2hga3Lqekko4VWvrbhq" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain bridge-hub-polkadot \ --reserved-only \ --reserved-nodes "/dns/bridge-hub-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWSBpo6fYU8CUr4fwA14CKSDUSj5jSgZzQDBNL1B8Dnmaw" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain collectives-polkadot \ --reserved-only \ --reserved-nodes "/dns/collectives-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWNscpobBzjPEdjbbjjKRYh9j1whYJvagRJwb9UH68zCPC" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain people-polkadot \ --reserved-only \ --reserved-nodes "/dns/people-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWDf2aLDKHQyLkDzdEGs6exNzWWw62s2EK9g1wrujJzRZt" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain coretime-polkadot \ --reserved-only \ --reserved-nodes "/dns/coretime-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWFG9WQQTf3MX3YQypZjJtoJM5zCQgJcqYdxxTStsbhZGU" docker run --platform=linux/amd64 --rm parity/polkadot-parachain \ --base-path /tmp/polkadot-data \ --no-hardware-benchmarks --no-mdns \ --chain asset-hub-polkadot \ --reserved-only \ --reserved-nodes "/dns/asset-hub-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWJUhizuk3crSvpyKLGycHBtnP93rwjksVueveU6x6k6RY" ``` ## Review Notes None Co-authored-by: Bastian Köcher --- cumulus/parachains/chain-specs/asset-hub-kusama.json | 3 ++- cumulus/parachains/chain-specs/asset-hub-polkadot.json | 3 ++- cumulus/parachains/chain-specs/asset-hub-westend.json | 3 ++- cumulus/parachains/chain-specs/bridge-hub-kusama.json | 3 ++- cumulus/parachains/chain-specs/bridge-hub-polkadot.json | 3 ++- cumulus/parachains/chain-specs/bridge-hub-westend.json | 3 ++- cumulus/parachains/chain-specs/collectives-polkadot.json | 3 ++- cumulus/parachains/chain-specs/collectives-westend.json | 3 ++- cumulus/parachains/chain-specs/coretime-kusama.json | 3 ++- cumulus/parachains/chain-specs/coretime-polkadot.json | 3 ++- cumulus/parachains/chain-specs/coretime-westend.json | 3 ++- cumulus/parachains/chain-specs/people-kusama.json | 3 ++- cumulus/parachains/chain-specs/people-polkadot.json | 3 ++- cumulus/parachains/chain-specs/people-westend.json | 3 ++- 14 files changed, 28 insertions(+), 14 deletions(-) diff --git a/cumulus/parachains/chain-specs/asset-hub-kusama.json b/cumulus/parachains/chain-specs/asset-hub-kusama.json index 58b8ac019227..ae4409e4f44f 100644 --- a/cumulus/parachains/chain-specs/asset-hub-kusama.json +++ b/cumulus/parachains/chain-specs/asset-hub-kusama.json @@ -28,7 +28,8 @@ "/dns/mine14.rotko.net/tcp/35524/wss/p2p/12D3KooWJUFnjR2PNbsJhudwPVaWCoZy1acPGKjM2cSuGj345BBu", "/dns/asset-hub-kusama.bootnodes.polkadotters.com/tcp/30511/p2p/12D3KooWDpk7wVH7RgjErEvbvAZ2kY5VeaAwRJP5ojmn1e8b8UbU", "/dns/asset-hub-kusama.bootnodes.polkadotters.com/tcp/30513/wss/p2p/12D3KooWDpk7wVH7RgjErEvbvAZ2kY5VeaAwRJP5ojmn1e8b8UbU", - "/dns/boot-kusama-assethub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWSwaeFs6FNgpgh54fdoxSDAA4nJNaPE3PAcse2GRrG7b3" + "/dns/boot-kusama-assethub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWSwaeFs6FNgpgh54fdoxSDAA4nJNaPE3PAcse2GRrG7b3", + "/dns/asset-hub-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWNCg821LyWDVrAJ2mG6ScDeeBFuDPiJtLYc9jCGNCyMoq" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/asset-hub-polkadot.json b/cumulus/parachains/chain-specs/asset-hub-polkadot.json index 3e46501b0078..62efb924c171 100644 --- a/cumulus/parachains/chain-specs/asset-hub-polkadot.json +++ b/cumulus/parachains/chain-specs/asset-hub-polkadot.json @@ -28,7 +28,8 @@ "/dns/mint14.rotko.net/tcp/35514/wss/p2p/12D3KooWKkzLjYF6M5eEs7nYiqEtRqY8SGVouoCwo3nCWsRnThDW", "/dns/asset-hub-polkadot.bootnodes.polkadotters.com/tcp/30508/p2p/12D3KooWKbfY9a9oywxMJKiALmt7yhrdQkjXMtvxhhDDN23vG93R", "/dns/asset-hub-polkadot.bootnodes.polkadotters.com/tcp/30510/wss/p2p/12D3KooWKbfY9a9oywxMJKiALmt7yhrdQkjXMtvxhhDDN23vG93R", - "/dns/boot-polkadot-assethub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWDR9M7CjV1xdjCRbRwkFn1E7sjMaL4oYxGyDWxuLrFc2J" + "/dns/boot-polkadot-assethub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWDR9M7CjV1xdjCRbRwkFn1E7sjMaL4oYxGyDWxuLrFc2J", + "/dns/asset-hub-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWJUhizuk3crSvpyKLGycHBtnP93rwjksVueveU6x6k6RY" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/asset-hub-westend.json b/cumulus/parachains/chain-specs/asset-hub-westend.json index 42717974a0b3..67a208c2787b 100644 --- a/cumulus/parachains/chain-specs/asset-hub-westend.json +++ b/cumulus/parachains/chain-specs/asset-hub-westend.json @@ -29,7 +29,8 @@ "/dns/wmint14.rotko.net/tcp/34534/ws/p2p/12D3KooWE4UDXqgtTcMCyUQ8S4uvaT8VMzzTBA6NWmKuYwTacWuN", "/dns/wmint14.rotko.net/tcp/35534/wss/p2p/12D3KooWE4UDXqgtTcMCyUQ8S4uvaT8VMzzTBA6NWmKuYwTacWuN", "/dns/asset-hub-westend.bootnodes.polkadotters.com/tcp/30514/p2p/12D3KooWNFYysCqmojxqjjaTfD2VkWBNngfyUKWjcR4WFixfHNTk", - "/dns/asset-hub-westend.bootnodes.polkadotters.com/tcp/30516/wss/p2p/12D3KooWNFYysCqmojxqjjaTfD2VkWBNngfyUKWjcR4WFixfHNTk" + "/dns/asset-hub-westend.bootnodes.polkadotters.com/tcp/30516/wss/p2p/12D3KooWNFYysCqmojxqjjaTfD2VkWBNngfyUKWjcR4WFixfHNTk", + "/dns/asset-hub-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWDUPyF2q8b6fVFEuwxBbRV3coAy1kzuCPU3D9TRiLnUfE" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/bridge-hub-kusama.json b/cumulus/parachains/chain-specs/bridge-hub-kusama.json index 36558b325bbf..83910965584f 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-kusama.json +++ b/cumulus/parachains/chain-specs/bridge-hub-kusama.json @@ -28,7 +28,8 @@ "/dns/kbr13.rotko.net/tcp/35553/wss/p2p/12D3KooWAmBp54mUEYtvsk2kxNEsDbAvdUMcaghxKXgUQxmPEQ66", "/dns/bridge-hub-kusama.bootnodes.polkadotters.com/tcp/30520/p2p/12D3KooWH3pucezRRS5esoYyzZsUkKWcPSByQxEvmM819QL1HPLV", "/dns/bridge-hub-kusama.bootnodes.polkadotters.com/tcp/30522/wss/p2p/12D3KooWH3pucezRRS5esoYyzZsUkKWcPSByQxEvmM819QL1HPLV", - "/dns/boot-kusama-bridgehub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWQybw6AFmAvrFfwUQnNxUpS12RovapD6oorh2mAJr4xyd" + "/dns/boot-kusama-bridgehub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWQybw6AFmAvrFfwUQnNxUpS12RovapD6oorh2mAJr4xyd", + "/dns/bridge-hub-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWBE1ZhrYqMC3ECFK6qbufS9kgKuF57XpvvZU6LKsPUSnF" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/bridge-hub-polkadot.json b/cumulus/parachains/chain-specs/bridge-hub-polkadot.json index eb22e09035f3..30585efaf4f1 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-polkadot.json +++ b/cumulus/parachains/chain-specs/bridge-hub-polkadot.json @@ -28,7 +28,8 @@ "/dns/bridge-hub-polkadot.bootnodes.polkadotters.com/tcp/30519/wss/p2p/12D3KooWLUNE3LHPDa1WrrZaYT7ArK66CLM1bPv7kKz74UcLnQRB", "/dns/boot-polkadot-bridgehub.luckyfriday.io/tcp/443/wss/p2p/12D3KooWKf3mBXHjLbwtPqv1BdbQuwbFNcQQYxASS7iQ25264AXH", "/dns/bridge-hub-polkadot.bootnode.amforc.com/tcp/29999/wss/p2p/12D3KooWGT5E56rAHfT5dY1pMLTrpAgV72yfDtD1Y5tPCHaTsifp", - "/dns/bridge-hub-polkadot.bootnode.amforc.com/tcp/30010/p2p/12D3KooWGT5E56rAHfT5dY1pMLTrpAgV72yfDtD1Y5tPCHaTsifp" + "/dns/bridge-hub-polkadot.bootnode.amforc.com/tcp/30010/p2p/12D3KooWGT5E56rAHfT5dY1pMLTrpAgV72yfDtD1Y5tPCHaTsifp", + "/dns/bridge-hub-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWSBpo6fYU8CUr4fwA14CKSDUSj5jSgZzQDBNL1B8Dnmaw" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/bridge-hub-westend.json b/cumulus/parachains/chain-specs/bridge-hub-westend.json index 40c7c7460c23..05d679a3e23f 100644 --- a/cumulus/parachains/chain-specs/bridge-hub-westend.json +++ b/cumulus/parachains/chain-specs/bridge-hub-westend.json @@ -29,7 +29,8 @@ "/dns/bridge-hub-westend.bootnodes.polkadotters.com/tcp/30523/p2p/12D3KooWPkwgJofp4GeeRwNgXqkp2aFwdLkCWv3qodpBJLwK43Jj", "/dns/bridge-hub-westend.bootnodes.polkadotters.com/tcp/30525/wss/p2p/12D3KooWPkwgJofp4GeeRwNgXqkp2aFwdLkCWv3qodpBJLwK43Jj", "/dns/bridge-hub-westend.bootnode.amforc.com/tcp/29999/wss/p2p/12D3KooWDSWod2gMtHxunXot538oEMw9p42pnPrpRELdsfYyT8R6", - "/dns/bridge-hub-westend.bootnode.amforc.com/tcp/30007/p2p/12D3KooWDSWod2gMtHxunXot538oEMw9p42pnPrpRELdsfYyT8R6" + "/dns/bridge-hub-westend.bootnode.amforc.com/tcp/30007/p2p/12D3KooWDSWod2gMtHxunXot538oEMw9p42pnPrpRELdsfYyT8R6", + "/dns/bridge-hub-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWJEfDZxrEKehoPbW2Mfg6rypttMXCMgMiybmapKqcByc1" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/collectives-polkadot.json b/cumulus/parachains/chain-specs/collectives-polkadot.json index 5ccccbec9053..458530baf336 100644 --- a/cumulus/parachains/chain-specs/collectives-polkadot.json +++ b/cumulus/parachains/chain-specs/collectives-polkadot.json @@ -27,7 +27,8 @@ "/dns/pch16.rotko.net/tcp/35576/wss/p2p/12D3KooWKrm3XmuGzJH17Wcn4HRDGsEjLZGDgN77q3ZhwnnQP7y1", "/dns/collectives-polkadot.bootnodes.polkadotters.com/tcp/30526/p2p/12D3KooWNohUjvJtGKUa8Vhy8C1ZBB5N8JATB6e7rdLVCioeb3ff", "/dns/collectives-polkadot.bootnodes.polkadotters.com/tcp/30528/wss/p2p/12D3KooWNohUjvJtGKUa8Vhy8C1ZBB5N8JATB6e7rdLVCioeb3ff", - "/dns/boot-polkadot-collectives.luckyfriday.io/tcp/443/wss/p2p/12D3KooWCzifnPooTt4kvTnXT7FTKTymVL7xn7DURQLsS2AKpf6w" + "/dns/boot-polkadot-collectives.luckyfriday.io/tcp/443/wss/p2p/12D3KooWCzifnPooTt4kvTnXT7FTKTymVL7xn7DURQLsS2AKpf6w", + "/dns/collectives-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWNscpobBzjPEdjbbjjKRYh9j1whYJvagRJwb9UH68zCPC" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/collectives-westend.json b/cumulus/parachains/chain-specs/collectives-westend.json index f583eddcef1f..aa0204df1a06 100644 --- a/cumulus/parachains/chain-specs/collectives-westend.json +++ b/cumulus/parachains/chain-specs/collectives-westend.json @@ -29,7 +29,8 @@ "/dns/wch13.rotko.net/tcp/34593/ws/p2p/12D3KooWPG85zhuSRoyptjLkFD4iJFistjiBmc15JgQ96B4fdXYr", "/dns/wch13.rotko.net/tcp/35593/wss/p2p/12D3KooWPG85zhuSRoyptjLkFD4iJFistjiBmc15JgQ96B4fdXYr", "/dns/collectives-westend.bootnodes.polkadotters.com/tcp/30529/p2p/12D3KooWAFkXNSBfyPduZVgfS7pj5NuVpbU8Ee5gHeF8wvos7Yqn", - "/dns/collectives-westend.bootnodes.polkadotters.com/tcp/30531/wss/p2p/12D3KooWAFkXNSBfyPduZVgfS7pj5NuVpbU8Ee5gHeF8wvos7Yqn" + "/dns/collectives-westend.bootnodes.polkadotters.com/tcp/30531/wss/p2p/12D3KooWAFkXNSBfyPduZVgfS7pj5NuVpbU8Ee5gHeF8wvos7Yqn", + "/dns/collectives-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWFH7UZnWESzuRSgrLvNSfALjtpr9PmG7QGyRNCizWEHcd" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/coretime-kusama.json b/cumulus/parachains/chain-specs/coretime-kusama.json index 3e4ffae403bd..8352588a1e4b 100644 --- a/cumulus/parachains/chain-specs/coretime-kusama.json +++ b/cumulus/parachains/chain-specs/coretime-kusama.json @@ -26,7 +26,8 @@ "/dns/coretime-kusama-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWFzW9AgxNfkVNCepVByS7URDCRDAA5p3XzBLVptqZvWoL", "/dns/coretime-kusama-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWFzW9AgxNfkVNCepVByS7URDCRDAA5p3XzBLVptqZvWoL", "/dns/coretime-kusama.bootnode.amforc.com/tcp/29999/wss/p2p/12D3KooWPrgxrrumrANp6Bp2SMEwMQHPHDbPzA1HbcrakZrbFi5P", - "/dns/coretime-kusama.bootnode.amforc.com/tcp/30013/p2p/12D3KooWPrgxrrumrANp6Bp2SMEwMQHPHDbPzA1HbcrakZrbFi5P" + "/dns/coretime-kusama.bootnode.amforc.com/tcp/30013/p2p/12D3KooWPrgxrrumrANp6Bp2SMEwMQHPHDbPzA1HbcrakZrbFi5P", + "/dns/coretime-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWMPc6jEjzFLRCK7QgbcNh3gvxCzGvDKhU4F66QWf2kZmq" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/coretime-polkadot.json b/cumulus/parachains/chain-specs/coretime-polkadot.json index e4f947d2afc9..7c12ee155b41 100644 --- a/cumulus/parachains/chain-specs/coretime-polkadot.json +++ b/cumulus/parachains/chain-specs/coretime-polkadot.json @@ -12,7 +12,8 @@ "/dns/coretime-polkadot-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWGpmytHjdthrkKgkXDZyKm9ABtJ2PtGk9NStJDG4pChy9", "/dns/coretime-polkadot-boot-ng.dwellir.com/tcp/30361/p2p/12D3KooWGpmytHjdthrkKgkXDZyKm9ABtJ2PtGk9NStJDG4pChy9", "/dns/coretime-polkadot-bootnode.radiumblock.com/tcp/30333/p2p/12D3KooWFsQphSqvqjVyKcEdR1D7LPcXHqjmy6ASuJrTr5isk9JU", - "/dns/coretime-polkadot-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWFsQphSqvqjVyKcEdR1D7LPcXHqjmy6ASuJrTr5isk9JU" + "/dns/coretime-polkadot-bootnode.radiumblock.com/tcp/30336/wss/p2p/12D3KooWFsQphSqvqjVyKcEdR1D7LPcXHqjmy6ASuJrTr5isk9JU", + "/dns/coretime-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWFG9WQQTf3MX3YQypZjJtoJM5zCQgJcqYdxxTStsbhZGU" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/coretime-westend.json b/cumulus/parachains/chain-specs/coretime-westend.json index 42f67526c29a..de6923bd7669 100644 --- a/cumulus/parachains/chain-specs/coretime-westend.json +++ b/cumulus/parachains/chain-specs/coretime-westend.json @@ -30,7 +30,8 @@ "/dns/coretime-westend.bootnodes.polkadotters.com/tcp/30358/wss/p2p/12D3KooWDc9T2vQ8rHvX7hAt9eLWktD9Q89NDTcLm5STkuNbzUGf", "/dns/coretime-westend.bootnodes.polkadotters.com/tcp/30356/p2p/12D3KooWDc9T2vQ8rHvX7hAt9eLWktD9Q89NDTcLm5STkuNbzUGf", "/dns/coretime-westend.bootnode.amforc.com/tcp/29999/wss/p2p/12D3KooWG9a9H9An96E3kgXL1sirHta117iuacJXnJRaUywkMiSd", - "/dns/coretime-westend.bootnode.amforc.com/tcp/30013/p2p/12D3KooWG9a9H9An96E3kgXL1sirHta117iuacJXnJRaUywkMiSd" + "/dns/coretime-westend.bootnode.amforc.com/tcp/30013/p2p/12D3KooWG9a9H9An96E3kgXL1sirHta117iuacJXnJRaUywkMiSd", + "/dns/coretime-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWCFNzjaiq45ZpW2qStmQdG5w7ZHrmi3RWUeG8cV2pPc2Y" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/people-kusama.json b/cumulus/parachains/chain-specs/people-kusama.json index 300b9fcfb183..701e6e7dc1ec 100644 --- a/cumulus/parachains/chain-specs/people-kusama.json +++ b/cumulus/parachains/chain-specs/people-kusama.json @@ -28,7 +28,8 @@ "/dns/ibp-boot-kusama-people.luckyfriday.io/tcp/30342/p2p/12D3KooWM4bRafMH2StfBEQtyj5cMWfGLYbuikCZmvKv9m1MQVPn", "/dns/ibp-boot-kusama-people.luckyfriday.io/tcp/443/wss/p2p/12D3KooWM4bRafMH2StfBEQtyj5cMWfGLYbuikCZmvKv9m1MQVPn", "/dns4/people-kusama.boot.stake.plus/tcp/30332/wss/p2p/12D3KooWRuKr3ogzXwD8zE2CTWenGdy8vSfViAjYMwGiwvFCsz8n", - "/dns/people-kusama.boot.stake.plus/tcp/31332/wss/p2p/12D3KooWFkDKdFxBJFyj9zumuJ4Mmctec2GqdYHcKYq8MTVe8dxf" + "/dns/people-kusama.boot.stake.plus/tcp/31332/wss/p2p/12D3KooWFkDKdFxBJFyj9zumuJ4Mmctec2GqdYHcKYq8MTVe8dxf", + "/dns/people-kusama-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWN32MmhPgZN8e1Dmc8DzEUKsfC2hga3Lqekko4VWvrbhq" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/people-polkadot.json b/cumulus/parachains/chain-specs/people-polkadot.json index 083c0fbf44a4..ff8d57b9284d 100644 --- a/cumulus/parachains/chain-specs/people-polkadot.json +++ b/cumulus/parachains/chain-specs/people-polkadot.json @@ -8,7 +8,8 @@ "/dns/polkadot-people-connect-0.polkadot.io/tcp/443/wss/p2p/12D3KooWP7BoJ7nAF9QnsreN8Eft1yHNUhvhxFiQyKFEUePi9mu3", "/dns/polkadot-people-connect-1.polkadot.io/tcp/443/wss/p2p/12D3KooWSSfWY3fTGJvGkuNUNBSNVCdLLNJnwkZSNQt7GCRYXu4o", "/dns/people-polkadot-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWKMYu1L28TkDf1ooMW8D8PHcztLnjV3bausH9eiVTRUYN", - "/dns/people-polkadot-boot-ng.dwellir.com/tcp/30346/p2p/12D3KooWKMYu1L28TkDf1ooMW8D8PHcztLnjV3bausH9eiVTRUYN" + "/dns/people-polkadot-boot-ng.dwellir.com/tcp/30346/p2p/12D3KooWKMYu1L28TkDf1ooMW8D8PHcztLnjV3bausH9eiVTRUYN", + "/dns/people-polkadot-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWDf2aLDKHQyLkDzdEGs6exNzWWw62s2EK9g1wrujJzRZt" ], "telemetryEndpoints": null, "protocolId": null, diff --git a/cumulus/parachains/chain-specs/people-westend.json b/cumulus/parachains/chain-specs/people-westend.json index ac24b2e64359..e52d7b299e1d 100644 --- a/cumulus/parachains/chain-specs/people-westend.json +++ b/cumulus/parachains/chain-specs/people-westend.json @@ -28,7 +28,8 @@ "/dns/wppl16.rotko.net/tcp/33766/p2p/12D3KooWHwUXBUo2WRMUBwPLC2ttVbnEk1KvDyESYAeKcNoCn7WS", "/dns/wppl16.rotko.net/tcp/35766/wss/p2p/12D3KooWHwUXBUo2WRMUBwPLC2ttVbnEk1KvDyESYAeKcNoCn7WS", "/dns/people-westend-boot-ng.dwellir.com/tcp/443/wss/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX", - "/dns/people-westend-boot-ng.dwellir.com/tcp/30355/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX" + "/dns/people-westend-boot-ng.dwellir.com/tcp/30355/p2p/12D3KooWBdCpCabhgBpLn67LWcXE2JJCCTMhuJHrfDNiTiCCr3KX", + "/dns/people-westend-01.bootnode.stkd.io/tcp/30633/wss/p2p/12D3KooWJzL4R3kq9Ms88gsV6bS9zGT8DHySdqwau5SHNqTzToNM" ], "telemetryEndpoints": null, "protocolId": null, From 4a0e3f624f42816cb4ca687d3ff9bba8e1a6bf19 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 18 Dec 2024 08:25:03 +0100 Subject: [PATCH 178/340] [pallet-revive] implement the call data copy API (#6880) This PR implements the call data copy API by adjusting the input method. Closes #6770 --------- Signed-off-by: xermicus Co-authored-by: command-bot <> --- prdoc/pr_6880.prdoc | 14 +++++ .../fixtures/contracts/call_data_copy.rs | 53 +++++++++++++++++ .../fixtures/contracts/common/src/lib.rs | 5 +- .../rpc/examples/js/pvm/ErrorTester.polkavm | Bin 8919 -> 7289 bytes .../rpc/examples/js/pvm/EventExample.polkavm | Bin 4104 -> 2631 bytes .../rpc/examples/js/pvm/Flipper.polkavm | Bin 1842 -> 1754 bytes .../rpc/examples/js/pvm/FlipperCaller.polkavm | Bin 5803 -> 4723 bytes .../rpc/examples/js/pvm/PiggyBank.polkavm | Bin 6470 -> 5269 bytes .../frame/revive/src/benchmarking/mod.rs | 37 +++++++++--- substrate/frame/revive/src/tests.rs | 16 +++++ substrate/frame/revive/src/wasm/runtime.rs | 56 ++++++++++++++---- substrate/frame/revive/src/weights.rs | 35 ++++++++--- substrate/frame/revive/uapi/src/flags.rs | 2 +- substrate/frame/revive/uapi/src/host.rs | 18 ++++-- .../frame/revive/uapi/src/host/riscv64.rs | 15 ++--- 15 files changed, 210 insertions(+), 41 deletions(-) create mode 100644 prdoc/pr_6880.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/call_data_copy.rs diff --git a/prdoc/pr_6880.prdoc b/prdoc/pr_6880.prdoc new file mode 100644 index 000000000000..9d59382f0e0b --- /dev/null +++ b/prdoc/pr_6880.prdoc @@ -0,0 +1,14 @@ +title: '[pallet-revive] implement the call data copy API' +doc: +- audience: Runtime Dev + description: |- + This PR implements the call data copy API by adjusting the input method. + + Closes #6770 +crates: +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major \ No newline at end of file diff --git a/substrate/frame/revive/fixtures/contracts/call_data_copy.rs b/substrate/frame/revive/fixtures/contracts/call_data_copy.rs new file mode 100644 index 000000000000..ccf1664058e8 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/call_data_copy.rs @@ -0,0 +1,53 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Expects a call data of [0xFF; 32] and executes the test vectors from +//! [https://www.evm.codes/?fork=cancun#37] and some additional tests. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api}; + +const TEST_DATA: [u8; 32] = [ + 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +]; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut buf = [0; 32]; + + api::call_data_copy(&mut &mut buf[..], 0); + assert_eq!(buf, [255; 32]); + + api::call_data_copy(&mut &mut buf[..8], 31); + assert_eq!(buf, TEST_DATA); + + api::call_data_copy(&mut &mut buf[..], 32); + assert_eq!(buf, [0; 32]); + + let mut buf = [255; 32]; + api::call_data_copy(&mut &mut buf[..], u32::MAX); + assert_eq!(buf, [0; 32]); +} diff --git a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs index abfba282bec1..1666cdf85ede 100644 --- a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs +++ b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs @@ -121,8 +121,9 @@ macro_rules! input { // e.g input!(buffer, 512, var1: u32, var2: [u8], ); ($buffer:ident, $size:expr, $($rest:tt)*) => { let mut $buffer = [0u8; $size]; - let $buffer = &mut &mut $buffer[..]; - $crate::api::input($buffer); + let input_size = $crate::u64_output!($crate::api::call_data_size,); + let $buffer = &mut &mut $buffer[..$size.min(input_size as usize)]; + $crate::api::call_data_copy($buffer, 0); input!(@inner $buffer, 0, $($rest)*); }; diff --git a/substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm index ffdbbe2f9c4b52b7a37a159bc88e2bdac706c561..af60be273d74d130967cd91c903ef85de1bf69bd 100644 GIT binary patch literal 7289 zcmc&&4R9OBb-p_sum|jc*aLrVZ%IiIe6k=l{zY<)oDanaY#9IHHsQhq|wr*3w83~mlWPo z28H*nhjIs$Iw&b9ZBQO+@9Nsz(cas>`Jo*<`!eXT|4@H&z`tT8m{XU_6T`lObCnhVyoziej_>=0R>eK2QYFY3z!QTe&Ec=%-U#Ke788XNzvRRwcV&SL5 zjpf_RpQ)Ivc&FlLm20Yk^e*}tn(8lmjL0YyxK9oUg80dOcc+IHY456V-|RhiK|?@B z&+%TMKkqn)-$tyQ_fs){+4bzyBbyjzr!toVpDx&%+ zUKB!~lhGhd?<0XX-$N*AcM_>@P=GmPMfNvo0i{BakP-xa1P{#Q)fVN|Gazrs$aW|Q z%FxbdDf`BIAtVc;1IUW9Uls^-IhydEMdn4Ta!$0WE{IO0AOT3ryA1ciD4(}KDEncb zvH~bAZ`N|C7~oCI8!qnF$`G*##68q=dr<0(h_ccoYGUWT;%yBVpC0}8E~vlpi)ZA` zKGa~)jSqe^fExByKVwd-p@x5ib}6_Bm?B;eF-5#SWZzZoPAZChpU~w~Y6%WFsfrih zMQBpB9U`{HhK07+ekeI%H&V{@AL8d^?FFFG9snDptDXeueMRYDtk@k1#=a^9VPr6N z0Lp$SPeI8+fidVvX&ik3#-$dxJQgiMy zLf^x1Q$#7tw*=?h0jGRNL)TyY@N4@#sNw5Jzwl&G2{ru9zdrZ$CU$9ab6Pa1l>yK{gl(6=`6leKI{R3&J>EJxF#6Q!$mqB_ zjnos0npNCfL_Ll&eUYM_{3Kv?%?TDPW@V@-9fecm3R|us`bgyXC4^1|I3#wLAg0gy znX!r)E19u^88ytPW(H%1!3@d_of%cksANV3Gs>9}W`@QL!i*3z%9s&khRO`gi~uuW z5``Iv89rusnISX7b4Xx@)Xxm@j9@$Sg1v1{Q2!S1RxV$;a)n<88`LrP75BbrY||w3 zD@ZW|C9N+A(pkBv_MG4&Q5jVBmsED<1mHBLuZl2zCDT_hy@u)4OlM3tm`<6lGrfxG zl}xW-dO6d>OxKuBm>yz!8PkJISDB8P9$>nk=?c?fE+5mqOqZGNVYyLCU59F#$^q{uZUo$BXil@f>0akA8F z^7~n8!^z(wbQ+qkW+`CHkBy~nUr=$Gr9MSL#T5%GUdvLyi|V_9>w$2}p$Eq=Cr7^n9QbU@QM~qv>b0j=^@%M{AoZBq zG6P}9bKV8@KYajZ7?EzE{=MBG(FaR{`uBMszd15lzpQ-(0_h@he{8_SD{r)P-P`|* z&}E=CSX$J$j-I`dAgu{f8l)9LN`h1kQYA{hl9Hx zV!nl7dI#|G>rh^WG6m)Ib^XO-JZ`+MzmUecpEzCe6W||seA&mP7f&%xKN=8>OTEl2Hql{65QOc;!XceQCj8-sO&S;oXjZwmAh|w}egN&+-Vnzdu z`WaOiMU46w^)f0m>S0u3ROI;&Q;Ih{@u#qUi?`K(b65vUP~Wc0VeZ;|r18pJ2v^2!H-3fCuiv|t&Oc6%lLlR` zjf+!BP_gNKDt??E=R5DdKQ3Uuj9b7dZ*;EM%v3Eu10$g@r2%fN7E+ll2 ze^?K_4vF2YV0B%9&=TTKNFWz2qV>nWMCd;*+tBQ+tuxmub{AOuQ`npdJeUjc>?M6L zU>;QL4#=Sr%IFiL}^D}j<;Hq&DM#8T0rXkJPUOpm&_NL;Djn2j}+CO1-1Y3SUK|w$@Ee6EK(nXMlZ%PR()83HvtR$ER4XKO5{8E02m&IX)aX*mg;U1d3SI7=)ii?f>LwBl^oavYp3 zx123FTVXl-1)Qz4oSisZWjQ(cqg&1(REp(HLglxdVWjv!&#X-Mx6Cp@NNKSecWnsAL7~MqVYZ) z`7ealytCxbK8U}6YktAs6CZJAe}T}?=HcGMS=&x6oR{syGC5*rBK%m3ua<0Q1Dv&; zX@|^tk}H3Ycj* zCqnPOCBl7@{a}dT8*A)rK<+EVl6Mq9 zo&9@q=CSbuX1)THPUa4ojj1o1Yx1!pWXMd^%eTP1Pv(v!4;*31#_-0)U93-SG*?HH z>(?d2YwF|LRg0S&>kAy?BWC_^{r&aPZXP4RjmTm7W=m^hTPI^(0I4^Q0t-I>8RxA8(ffCP}-FQK^)rIYw4PcBWoCA&Mm> z1d|_INjNlz0+EySY-|E?0;V|>636KeyZynCv<+>yq=oL%o^6*Nwyh7eXG7W2v|F;F z{CK|^Sx!O<1$uI}K7JbM&7C**eed4y-FsihvX61xf226~k0;z?ZQ^mRO)W((Lt(|*?)8O@o7S(}fLo;-ZrHSO#mcpX?o}%` zu5fy$h5J{m-Bjq_xPHaD4XX?5*B4fK!@}r`;`J-mt{pw?TUF>=d*f!`{Z}s*?&B)l zIQK>FU%6I(BY!9VMgB4VIiB;RJdNT?(eJ&*`=al?te_r5VAR&KPUL(IG zHz`@=-<9_Sp9}74_?u84{Stj&_zU5$hvmrkA{!c6^kDSI(Zw-cU8$~9f2w^`7mVi^ zAAe8$+W1ZJPsZ> zQZbf{geN2YyhudJM}=KIgmOZV=R{`+k1!zUOZo8i+DcEW=vnD$aXy+wPY+3o9^xZD zD*AoYr}4$`af`NEu@*~hF2>q0W=<7JnLS9xQg7HU>-HgPkLmVd$sSimB;^oMwv!OK zx&EfW_g^Pui(RJnp^QDY+&cQ#SbX_xk$%bsT&A0Fn4sNur4L>-OVgbl;kNNm2R3ouiLg zah?CGdw;~eKl}OT?-nOT(dVpV_6zv&f}dn}#P2pAYY1fx{o2k;q^`MV_^!F9&`zRF z@`5(WJIkP*;%)s5ZyP6hn@#fPEX3~`x*fylmK@Ff(6>7P^^03yHMf$&X1REDDshiu z-sA6l++RF2RoLt|HXeSgzRn*9sA+Evm{aBdY<{L=v({u&wRG!?oNSZ4<5SZ_Ff_?cYN)>J7<44 zPm1$jHRsEP38nbtRAQIU+!g3t6DTg6Dm)r6AC=>+eBX9bc$j>s(sU$Wkzra*C*u2( zbN2E16aM^GKk>&fxkZ3fdar})W%vk~0C&LBC_lR0(jPaO6;m~JP8{9LHp3;W$GgnTAwGo5#hyUjk_VUI6| zaqk-4H+oR{E>XTJDHW*<`ex^!HB}~O=UZ#pn^&H@sDr{yd|1FOBq~eHZju2<6g)M&baG-M_U<1Cw zmVmiMwgF*bt6bEQypPTvQfNvLE35#(t&kej%PE_ zxb0jA`GIWrf-b`pkR%oxSJy#W>$L=J*9Ha(})OQ1&_&`AFR&x4n4Mm?qJArl_Z368f zv@x^;XuHuy(5h(Lc#aqJ%l;i7^ue`M23?u6+xc0{UN2jOvtwbGp1eV?SaudlzvG$>>u5S2{?wm zcgC>q3J9xnFlQe%e{zP9I|4SAPAb-UnPgu9+ij3S*+=@0yli`%wK{_-okBZ_23`In znavCQk+u4h9QGT3eOi~aL9t)^XQoy6*#6O-@Yr$X5u%jq9@{%Jv15KK4nuUj^^ohW za9<|DDw}`%zl7Z9w>9``1TNC#XT}_6%x1=l8Z%U8#F!CfMk6yK z%m_1sG9$!{24)1Ap)f;cMt~U@L}CVEhL0IuW{Ax2e4S&4Foq9)T~pCcoZ*_D=sY3! zORr2#O*y-a4Ry>hNm;8n*4K0}e^AOlAu<1_xsdd_?Kt`W3w0hnS1LN@?a^anQr*9`!66Og?g(Bhu70^s86L%+8KeDU3b z??TD@62iWgr|7%!tMB{XjbH8cVcJe?@WJPQPsrWAw}=fcdmIuQeQsuu9Vz?(+Q*{;1C-d(m%#>+9V9PWj>`@4K1TH@tPeXr<8PYu*}P zwAo{sb9VR<@WnBI{Lt}b+n4PPk$WY8;iC(F_CJK|^u7fYi;h8}gjc|C7kf{?{?1u& zj`zr#x@Y zhV3cn|8JhN-V5Klcm(sd+3-Wq=zKOzZ~iSIm$-_aWkXQ(#Z&fevEd%!d^WuLho=bn zBs$*)8-o5~pu8xqE*%y`B}s2~HW`p&2)zv;yWl2KzKL#i_8RmC*3+Av-DU;7+1YMf zLZml48%~W6ew`=M zt%%m+#&J&gbsTYAEd(P=W%W3c{#pc3Yevy#dIqz5fZ-nJf z$9RkX403xPK?LhWpi8fNF+{%X=jv#7d=v`(eg@397x#VpO?WQ%hhGzN=r5Y){8MjE zbNPi(|K#gu-SRJk=9WRD^J#9`@P8BXx2~dRY3~0&===O@=hNKs7hWJ_1f6e#=D>2` zvh#yJQBKmWPArXZuK{<1;5MayhU2!@IBx4%j@vrJao|V3$Vgt4RyjKPsMQT!l-GED z5~h#DZj}=}B@q5Q2@FHtVjvAbS)=Y3pZEae|MOKRdnq=xnN57UIEMnGrJcYy z_)c<~WP5)7JR$pEvC(N8CphH8wvK1m1g9V&tAR4&RyLio=uHrG&4kw-c~o0QB#~QL z+?>p9@xJgp#8=~o=sS5rB~!i z*wUBFl?ZApa;4GId*n*g(sy%mC1&Y;az(ZDDt>5|z6}@2(kF27Tlxqt0ZSjlMYi+< zxF{A(L#_lZ{UKZ$Ed2y7Aqz$&S1cX&fFcf3V7Ve#Fj~3dvEb}-MYMFv%N4H$vzIGA z$I0aZ;uJsYzFxfQzX&;XW@Zzt*K|;ef76KKu`|Me>%pkTmpyp?p);k`NW)vPG^hfW zs?;`;rG2^+Kshee0t2&+4*W!=4Tg$Z+qA9Le4rxO`f^7`tDxj(8%0;9XGX?0`e0zT zwwqHnqfArfV9s`Y8#iszFitnrv5gVVm4lZ#a$_~Q183!I_7M6o(o>v*0%ZIIZc#gN zoSQan7^!QRHha1zccvzHvL-iqR?d!}LLWw=ys}kx_z~u9s~kUV;xN{uNf^BoUcrD6 zVU>@x(lw-fq=l}*_h!1rTRxJcYsB&qov!hek5IZsC?ApN8lINR`&(%_RNmi0%MIoI z&9odW?@!XQQr@rAvRvLzX*obkQhA?5OaAg;D=lf|!4_Il%Y)6d6e|xVX(?JB)M=@) zJV%4(qx_8%!xcg71ouXcsu6OCQOP9K&a}NOep8W;#KUX5C zCz0NMFsLVjx*pV{L7fJ5C8$e5y`JT-|1zXkoaQl}yHz=P{>%@N+lZgV@EHr|H%>s; zf5$JJ^BniUN~lhs=~P^q0~TFw#X2lClly9Jr)9J$B}q9leX3-!mT4j!0evS!(xrm% z70f4hofU8O(Jdc<5*jM{IPkHw=E8MOUFjmmL#UQFjpa&Bt2;-0{nE^i6mK;rBs<>L zbj(WV`D2o0I7Ez!WBzWpAhwCOV#3GWE$2EPy?M)XgsgL zoW&T687!u=n8so%i^W(h%3_Ty7Gbe4i%}K}u~-9(1zAjCF`2~zEaqo1iNy$u`B)4i ziY(?~F@eSSe)5eOMs0OB%{G+ZT{@-tI|%cJ$WM@92>or2v3T9I{{ivy1h$Ux8AfB`ywjPnJN2xWT+jO}d>#)^KG2GM0A%5~Dw{dHpg{}FLw{k0=2!})pH;XS)|cXVck%eP;% zR zd#W@%^Umxry}y}HN9H+C&rO}9O_m#~rXQ%9?P;^FAfECkBSRTS(itH=^Vfdfs~(_3 zW~N=d)6C|o!&jeotER<{+p6Z`NGg*~Uj|SCsQZRtJoa&nn_7~F+Gka)cI@F&gTr`P z4gj9*kF=So@ch&u4G*5vD^$~F&XL5JKxeR;Nks-T=^eFIs!hek%>~XM&gQb&;cBir z<}Tm#K=bED+~pdcUeyQMuW7Hr2jw#9+9>X4x_JpVly=@U{{o<@(IL#_TxmX?ip+N; zF^fS=5|07pT>$xq)oP|a9m%E!^B-{rZiX40yBg7X+cMEX{HO2F1j0kWClwib01v{3 z&qDG?zCSPydvHKW)tAi`8Zsk;>C{lhjC{oX95kWSym{IC&2#n5;@+?$<_Krq4?JgP zYp>jM!JDhOY$P=-W~&lzMN`99FX_BpdGNCArOteF?YEt4O4+-(VT^EXu6k~-KZytD V%})b=$2aO0=DrCNDJ{O0`#(LIA`$=q diff --git a/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm index 4b013b35852e668fd21ef7ce1dbd61936d7433bd..67f02042c784cdc354e1b2507ecbe67ab1f59050 100644 GIT binary patch literal 2631 zcmcIlUu+yl8K2!-@9h1ty>~v}jwg=2u{Lm*HspxX#w|r0maEO_5ul9iT`>Y9TL1LNv55eL?|VEL8DSP=zR@&7b4j zb)7sA@y6=DnVXsK`+fgsM#r8;=&1nmJ`SVbOrZ^w^2O6eK0l+DW)AOLd;Lu8-471= z9{Tc|Cr?B^KG^>AkA|YS`v(V~tt4jtAuOJeT-gqwUZ6cdNuaL+rGa#yBh&f(B%EAh4TGXerEdU{nebcV7Mz|_NY;qnX~36jiPaEe$JTAPtF?i$7het8S|6Y^Uoij zpFW&7Jiy{~{fyf6 zy~X{W`!i?pOMH!glmC#HeUJMpzP-X5!cAeH_%(4{+z?Cn@3=?WC1uMU3$lPa178;) zM%=zrj~R7|uk=Ssn_oEy8vy}#p@0-ee8P#wFw9v;z$wHV6*##6uM3gNG3%6SudCK~ zRr{K1yb>(@I$*uRRj(zks)@=$>>n}8{_H|Hc-pLT=1)2KYWBUp%l~Y96#EZ6_@o#6 zDSxRIHk!fUHS=Q2SPjYV^Xsin|Hoa(3~%W2px&L-V{x-P6$x)fikyH2NtF4$ttp&D zJg-s?3^%tx0S>RF^`lI8?!% z_!|OV1b9&noCHl7mXrC?A_5FS68t0Kfb<07vD7Z$DdYnrF}%$m$^w6nfSE~vu?_ZJ zl#VCvc47f>CZv)R`~s5U$PBA3UA}?z?hlY2yN=B6O$1SLy@Yt^IVa%G4=cg~pL2~F z;lf99myuOci(bXQ{i6^6)>rxVwtIyKxmS3wdylZ3!<7ywLVC--$mKg2_j}>wtDcGX zOpm^JrxQDEP)e>Nu{mJ;*MBi?Wrd8v;UE8JQZ{tc@shF?FGwZb1wt##bSC0w(L}ro zw21B{F{oGSp!y2954^pf+{V~xO@jM8vz&=U6V2w2P9)wz6Nz6Sy$)=j7f9dxz&X-l zw!>D_teIkKqilV=aiVd$aj~%;`V|hnCxslzuBxGTF+|i$dS6So3VME2e{P%-NU zO1Di>R=)>eTnl2x>?vbaqNM*ZO8SzN?4+cZk{y&#N_r?Elqi(MDCwpoN=X+b5lX_8 z$dq(a(m_c(B_T?Jlmsa8Qvyhs5|NTNN(4%Ll<<^rEH}=q(NH;9#G9>F%dKT*nOYe$ zt1xz9>}@JxJa<=rE~T4;{Hp31nil1O8@DiKcU>#ZG;}{z`cqWtqsmUI^ipL9RVY<@ zs6wcsP$fo{ZmLA7(nXaBRl-z}snSW64yv?MB}A1VRRUD;Q$?Z*V2M;|ql!QkA60m& za1`Impf(AL|8l2@H@2-cP0fK0D2!E@nHsBL{M$ROSxz14W|}!?VM;Qm3R9eUZKbl@ z6kl?y2wF^(zt9edXFVB{fGf<7%L@{-RJ%SXJJ_x#Wk#FPscAdzM&#ud| z!`pR9b~w4rtfX9)?IuWc+D(w?u$v&!ZZ|<9WH&(~Xg5J3U^hX+&+N3kKs*hKry=HP z==L;3Jq=x+hKQ#j>>>Ai$bBC2P7k@)L*C&b)1q``J0_69WpJS|R%cdftZuJKa=8af z>$h1>Z8$6!ZcvIPP?S)RuXO~3rg0#p1U3>$cl2%w6yK9jr6Ap|@Dh4Wn6w0}Bow{`aRWXS!wL@Y+e&D$oyC zys_->EeEu6@SDvbGe^N5!;y^BLG5%vb0n4@XStMdB4k{b3^3{)sTkOHPy0z=?9$jN z9Qe~M{)$kj;ENqqJ|Fa_b+!Zw93qu6WQh(9!HV+5Qqg=p6>DABk zu+JI1#hGwfnBe~vn8umeglUv_rXZlY31%mMcoX9=m_6EX?25{)@x~=kR5!CPP6j2!PljX|FoD6Z!VP|!=x1cTBI(PG)kdQ6xPEF-Ury?k z_ykcoYml!963^_yh_H3H?pHwVrO!AM9`LQV!+qZF-N&$r4zyjpDZmqX;0>5KQGWEt zH__`$ntzWL&8Bx}YUj1RYv+HtlHI?1H>?7$#fAp7Og1vGB=hNXwq^svGwFfj>E<)( zweJ0im#WohWWd%IwM6ET%%NJeJ96k}@ww)lAyyKTltyPtqXRbx1F_JI=wa~Xb{A|D z8^fKkDmNkJaIj*iC4&vEWhF9T)I@13gH2l6KSWNXT2-ayN2OS5ROLroS#ds`)-`?S zFi9h|{ZsXHJF_$M%scPLGw;kc?|nv+R8z|S5SD(`sjNs*U!?2dx!1;uR&O-=zV^uL zFB}iw+8O-GpX&;6;Y&N)Mz$XMNQwK!6j9{EL*RJ;p2y(n-q+ikIJ`g6cj$=QA5RP< z4)(hT`;PV>N$l&5A5QciJ$&#;qCf5)IB>LoUsrD;zJFi;zJYzcM-%b>!}|^%c`kAI zaAJQ>NIuo12wA6|^@Fh9Luvmj?OWti-+fOCpgi;tnnb@tw^5#4EHH z`p)`Z^Ih}3?|V$SpzO`Lle61z;1PUL{Zrtxz;695y~Fs>(1MQ#pAB{dThh75OaSdGIG#8 z8S&;L?ohqDu^C)v|>*dJSk)2p;N~E=xV)nMqio$%<5`FcJ>k3=bd$E2`g= zx36{LYDo?vNf9dgx_r%|h$+5K994YSkNu{i`AxrtoX`q0Ygl15 zE2?q|qad`U2qY+K#BZvQ zmghSx(qWk$mfC^k!KR!L_Xxja@>#;?R6cJ^s>UU3jNm+cd}F1+0a)o6_e_2%#%J64 zT$Il@&kW6s%uLSAi3wLUV^s5|BF3WXW+Kp&r|y;qVKu*LJOXo{3aB-bjMbomI~B0L zNeS^J5w6J%!*Cf-n%p63N(FyuO}@~&+sse@k%tutIY0=cXo?huAt!W!neD8wnH9yH z!cOqWhL#1ZS-2wqH-9L8@NirOU&l4^768}BbqGS`Sn-rnHig>8w93WGg|@_h=Pb%Mdw`+l`QS#%>BsNSxodE zGZzsnd;_tfS>zP1psx`2!3RM$)06-ND2{=qaTN#~*MPdrOQCoGcnYi<16EyPJ)Ma4 zwj*O%V@U@U53Vq$5A7XcZVc@`i}6)ZZr#|b8-K^Z8s6(5XEkcCs(gPRN(}4v?m%lM zz}OPx}G>e#E@|XyAUstsuV1r z%^D1>0pIw|>Z_k){8xWAR3h*@RZ7a9pro{#lFgKqP*N;{L^e@ED6uFhqNI?L0!s2J z2~!fH#H1vTl3YrHlo*uglmsZzC;>^B5mozBGj^|RYa{qY86l`pITvRg{WmxE00>a)Cy9|pq5Up0JSt~snh~lern}V zOQDvJS~9f|h0P7&5j21Fd$9DIIRp5Y8+3DkZX=jO47f006kcoK6|@(xohT2#n8TCR zNKX^yHtXq4%&pebl(|toT@04$=@JNwp56@GS5KERw^L7-F}Gb$o6J2e>FE%2`}A~} zxhJ5J&)k%rE@18~GzyuUfvSkP7of73I|-G_+#67V6W@R;z}yw6bmlHXWia(F9-h?WTx$i;6Tmm`*MQ%YuX08M+A9L?OOJQJ+p3Y&e3YA|3N*}}wjFLx0qFK)X zk9U8D@mU3Ibq7`MGz0m#6Lm8tcWc}!BJ#cHiMbgS_9!%A^SVolCDpuYE}G^~2{z8jGnZ%1&17btFy=7pse;TDL2&PZxWnNg zGO#h5jj>2e2V}XjQ|xqk60#mZ#rCQx`vl`wS>$415Oh453mtXP0Vi0hlQ}A~Q$Qj( z#UQ1_`DKr)t-vY9&hndr5Dd)wxrJxaXk6tjc?<;T2ZB9M#Jj6zcEYj29WpcOO@bvd>?EFy%nXXdKdm{| z3fz=io{z)#haA^cC=65FRR~;DjuAFwvg0aC!6jv@`(%WrtJ$%t4lBY^HOMvPK}D6t zN!x^+J<)p{u0tHILtI>kLMsTqP{hr>ZZoK|hl5%jgme(IgQy*9Q*7vYxM5`h^>;tQ zxLN>$Bm$1x6(l7=LV~0qNX#HHfHA`(rd%Ew#_bRiEU|^IAd?0JM*(wTXt;O0#0oU%&=NMtUenWt>0spSB%=@_46>#ZaAZc#$(xuqBUWk z3`gxo`=ni0FIwUs4vF9Bc+A#94K0P^_PE_xpUHeN4v>pgsQzNCVxnTy42^EggZ{>0 zH4+}r)Yrp1{3{yl(fSGy6ozU~wj7UD+#iH-d%}jn^+Mn#L?N&Z;qiZwx-LwVQpKJ# z6SIx=6M(9ys~^t51o&rS^%>B{4vl6ls}Gk~gtf?hbF#BfRD{Y~S|9{3f!DS*OoYE( WU$2eYyTak`gV?&KTN=vCrGEkjk_2@C diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm index 7fb299e464dd75177e916f6631f6fa7dd7cf3ea9..ebb06b6949e3856a1e367e99c1c7481fdc32e6ae 100644 GIT binary patch literal 1754 zcmbVMUuYaf7@wV++nK#wb9$L9 zB2V&8=L)C1fvJhy$;n}FV!|5=$MM;NB;Y-pk%zqT!q|-bQF8*HHS=g5okOcAA$3aq z(x~*Yw1{uu!O-WSW-?Bi!kKV3Y|Yf4Q3;Jj-XIZ#q!-U0^p+W3*qbPB9C{T35kebK z1SI43>7+*CZA}xZBkV1}huWwC`SH89dcbac$=2HZ#!MozTl|jl7#;IN$ zmr@#45$=|C6rwVfRienM{BVcmmPTlXFkM!eyG7{@L>Q193-t$&zTlBX_%v*(dd=Cs zI!Ff*_EQ_Mc{|Uo_uUkCQ>vR{F2%)O8h1vyb6s^-xl>`zT6CF3uhZxPjnTIVodbGV zjr{hQQq#$+xHyM^gD|4>#v?3=FlD=7KPUuD;rKuK(XS!DiBQMqkVqgNu+)F?I7SJQ zd~jAG3PCD-^5goJt$vT}#_y1=eS`eQ4Fo3DR3?hR^$-&URFVjIRFfKEFs(_g>a<6A z)qz7ibPe01D$eVK^-6Im|2pli{PN?sM=|X_@@x6vy$2SIfFuX4~ech^#YqUiDMIgZ)p%k%ZUqEcP=**&?(z1cXizQnyWn z`X)d4i_%9@U0!{@I8ru zKf=a-X$Dj8?3Z)J96A-c|@ z6=mfz^x){C9=)ZET-IS6V-FE7!dg2IZ6t;_Xz%+;JXEH z7F-D4Bsdp5CAcPdqu@!w8w5`X9v55{JSKR(;JXBm3SK98L~uoLCO8#b7CbCC5j-R~ z7F@D@E?9NzyMNxK^gStP>u0w(T6Or96iIBKi#?Y%V{{#UlC8@CsBkM(xOEOagXKZ( zx0`8Hb*?+B%&Dl(8c)n_q2@bMqKLMl>(p(%3H)CIRsmO$-@K8yvtfN@-{$r$)xOE> zb$lE9SF2ogflhgQqxz#iD0Q|1`Luejt0Oqdy#xZNC7j$O@at-c!Ap`=gKna0^M=x4 zB&}@PES>MG+ZQhx?Ml3pNrusG?l*f%ZN}1T#{IVP{mA8uZ+3O{bk3`tOgHAst-7w8 z=9+1hvir1e6cmA;fvmP(6@*~P9=h+HTeOPwVnT`|(2~vI8CZ>Aa>QzEc~I_&C=&V`Z<3+- literal 1842 zcmb7EO>7%Q7@b{@cXk~&-gV;H%_KBV*!)TrQ2`OepNS0hQZ))DplDYs$7zf=A#FqA z#7PTMN?YPqJ+xMf5>>8}s#W5)hf;(ZS|NoKy&xe@91zN(2P6)lo~lq{zI75PJ@7N~ z8?U~Z`R1GV-b~Ls`w-eqP`PmgeHFp0D1rwBH&6kP0kwc#fKEUCA8{AC8{BtXl_&1G>sj;c#ckd} z?=kOL?@jM*Z+yD)u#8E-_bT=w#65ND`P7`q&(;UCYcD(p0Ust+$R{Giw@p=3I3*vS z!X%9N)GQ4A^;Lx&WzCc|GbEc-bVA{vvKE}=Fu|fA^M!sQB7O&lu)D&;XL+rgQ}(L^ zo=)r;@N~FcEB5r0F!m6E2r`xgSx`AgSvBQOGt^d@k^4zM;vIDjPGqLF`1`i1*{W=-qD}a0w^y--HET(>mNly= zT37vZqJN3_XGws3u)UM-#|=UbTf?%o6tkB5tYXBv+EX}Em@Ui|itd72lK-SMJRkP2 zh(ozBL;~VoJ`1}Evi}mSec30HzS zWKB+Q1PDf)1Nv}S!YBwsj)F?2`~aDuUy(_FLQV*AK+ATi)lWmd4|&Z06De2#niC=r zo{-!xAMl^303}*Z*Usbmd9L@g)UwiYy?4d$zwPV2E|n=H(x+U)Rj-hJFB>L%ioe|Z zW|<><-j06~4g~hRcK`A#=QuN{*$s2ZZa4$T0cH@d&LEd&^$fDKIb_x3ki}-uBSf#d zdEJVjgoNal9LhQTZ!391aZ8!ig>nw_}U1TN|~?9?;5 zlhG#_tz)#7QO0NuqZ*?sqm zY1On^%ss-k;XAD~*w7jh!Rr5!{as|stUGW279i~$(ETq`l%3%^WYZmw$}xks^iUU2 z?IfuBn2L`X%T`8hWy)3@Tj_VxfTG|lzP5ieiv_;3G%#BY7>~`NU>249Y4Ui1ChIe6 zdQ2^5s*z1+lwDEb5(%%>rYpiftP|2)`aiCCu)$Bp(}LW%2upw7uvASK5;Xi-Uo9&X3f5ymP-VFBUlyZwk*~wfk k7YjD#Uynu`8}oX;H`r*zp^RnOc(!d{X!p7HHXWls01Lvaxc~qF diff --git a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm index f112ca275df4ba33a26faa9c69efdd8d5292a98e..56ae81c7e5b06c34b302d3f1158ae48c4ec29092 100644 GIT binary patch literal 4723 zcmcInYj7LY6~6oM-j#M`trSaMHV#(Y^^**^CA6kMN4Ns-%8p0K#ua<3iJe$z*ES?^ z9##@Mb*FAEr#up2upQ7eO&$E0wxpEQA%TuN?KGjl4AaT9^hX)D(+B(_ffk&1&#vt_ zlrT`H)#&VM@7{aPJ?DJixks_KbqH)wtnlj zO}!oIRBvkA_TC4V*I)f?Pu-g8=vluoxD{4e4?M7aTgQf;l(WJfS=6(mqi1_+{kGnY z2e;mz>g`Q+7WnALRMy_WV>fV}sm(o`c5?gr*3gfwO``!cgyv8srBm(H7Ai@P(J#~Q z&?}hh**3PQprfFayOH}dm&7H!#d}?=T#vf;yME@XcOP&+>wa6P634|~i0eGhd4BK7 z6do)5TVbL12Jg+@_|C#z5{td=J6ShE)J=PCO^x#OV5z@v?luS_uNBDj%^Fgj5vVnSRc0XU_s#itP%LJ7PNMVem`5~S+|2l>;vkj~ z-N1zqgE`Detc#PlZrb#PFB{TPWCRW)BRGl7z$^kC@nABTNCsocpq31V>2$bR@R4+k zmI<2DwqNG%ex&Zk z`JJo?n`-OJ_vMdmU^GOxyW8R7N`Q`@+IAd6G>iKYC%NCegz+6DT}$`%+sas1Gv$N( zS~*m`;Gp;-%ved8wJfIKE$>?xEQ46JlzL*ffAYs|Lj<^Onz1}Ez{ zsi+6mzH$jd4q@g80YpyB-Fy+EG4BBrh@tzMT>ynT8(_iPVg6G~^D{hz-^l`Rqb}=8je3n?T{21vCN z1jZ>ns{jZgqo>N~t~I)3dIHGq;J9oYlp*jULAvWwGrS^~D)RM;yh@Qv6nUj0D~f!b zBFlt6$`LQmCTu#=k{dOLqu)r8&)k3MPq75?y@l1fkKs1mr z4RnjoaFY7>vl!P?;0(}=lPdlIk2h1Eigr5WspzL=GaUjI1F&Q-NEW!URKAS#C~c<0 z@b+n%m;kT?w5MX6W({)^#@k{1M>L)8r<;9VT*c7o5YvopE;GceWuX2rq^pX7$gUg> zwN18VF#je@3d1DQ8Dor2jr3@Y(ZiGOFk^IwA-s$Yc;CEHOwuwj!SO1nJDgN?jx&8c zvG~dnVun{{L#0`&73x-Z?2Z{ATjf;ij7vO*TQV-}iWH1}CT@xjM%Nazie2AEenpp_+X*kB_5wYGvq-zdcDKr@Ca3(D9V9nbf zV0;&p?+^1}cm83A2QOps*b)zBuJ9mm`Pw|V@o7*t5A_*N?Yh*+|BDAx2#D|wT&TY; zFyS=>$uZK1B6cizxP_UtL?tWgKx#p%Kth0UHa@~hweP==aU6E(oGD*;3!WmEJQZOZ z@)U1^8YA5a3pRt8)YWLuP{0Lv`w-=zy&qN#P=NLme6bshpSVbwiKU}#F)ej*c=3~q#7dA6@C@LfkYS;;c@02=THDOQIS8X> z_@=@07w9yy32aJ0a2W{B!wG?y0?`f#v<}NLv8lTdk=t*Bm(WZt7ND=RfwAWoOHa-(1Jz@ zx)7y>M&_ea1ie6rvO*&#G=XBfS+R~w)~mAkim=$N3^9U!oe-6UMnz~^`O!H-bd}Ip zA~anuG?ZG?JXp3~m8?ElJYn~B_OY?kB{aB%C@(a+g(ksv0>-acnZZKTBQ%OaQ=y=F zmo;Q%@g%?K#30UR^83MG@pHD~CsN@6_35e5dX97-0CpY)B2QApI!t{$6{^mChzi=#cQGzH=TKo5R3xBVa;PwC z|0S@g0HZb)PM@XIbFe33o(2o^6zO^krq5A68@+>WalgB3dWoKs&=t>~r89Q@GGm++ zZ2=?Co?X;#H@cuA2JMwoDZmhmnw&QG4MPeU0oe#j@vX#PWgaP2j|iz3IrTSU%W*N) zB&to=fA5^Jf={(DY70tzS4atLNvEK87PK5LNIfm2jufazIQ11Fb&yW=(y1GS)X%xf z?#i#Xc8g+)u54^wBY1^gQp~Gcamj$74)85Ay!r$#c}7s5aktF4OYU<4U0O!nUU#EW z1g4fU*ZjyCjK@$u&Dsrm{yw7Qr^!k_f4p62xBrGg$KCFNj=LRe&^$>Y5|BwyGJRF> zkCTUXl9OVMpy6w50W9CZgzSrKUz~m&#+tR=TcB{lWKN2QVCouAAOvmuot{96Cm?$Q zMV^4<35cEm?+L&()z;bi!D%mM5e+350;x9Jr5LppRG+b5wyAKVI4Mzb1|1quT{UV^ zTr1aedt$Zu~Q4*xEj)%2`X&r<^fdrz?}t5)l`=uv-lB%+p2 zspUg^T65!(dbRpcBm%eiByx<>l32 zyR7UxTK$0J^Mxa7edO1^N3vSYK~44Rx*xW62o~g8A0Josd-Tk2eHDVC>Ic=;e*NIxWqY!t q_Cq8$u3Nd`E6bu$$6&eZp2;}BTGgx70f|;ue+wqmKiL}Bc=T`6wA4fZ literal 5803 zcmcIoeQ+Dcb-z0tuy@!4u?G@|${s?2bntScMawfB%Uba4N! zM-Dt>?cZaR92j5yy}RInS48!kSK>IKz0jZ`_)Zl>6yXGG5?D-I zi@g0JPhLo^Z2rdE^_;D)nuMA`Vrr5dj0Duxz!)zgQSuPsi3}o~;Nv;b(d81x1={OU zcZY^uT_S9{)48;Xt_+HZF62QTBFY}(X?EM{r5h&c;>}aOYL~53*ywSt))B>c(Ya}TrFA*|imZH> zGwoRGv9#>8qy78?gnkU~eKZ2evcpHi zK1zMG!AFUYDn2UtD2y~5UG!BLJI~vrBH~5a==G?ef@S+WME^Ft`BOF=!>uxIlW@Bi zci`N(v+djr$t_Uuka7~?HXm+Ra7P{9=Fb(Ku;dmyJ;@wVoV+EFuA#q9)l1n{={kB-6ZLEO=Rw`q8L z=wodyn)X0>+{|VMx4y>>ZhZ^nXCSK}S3zC}SpvBPavo$6WDevsP?lh?azg$RCybot zMERzOdWGHDf8dyXmCKDsAO&IHfEsY};8w~HZY3ZR2*!1HLpZD0ZBebDezw}Qdzd(aAPD3@B7y(*{n zxO;x-VZ`sO)e=wSoYb?`3^3y~FynDxMquLLX@?nWHg8p7&Wu|W{wH1(J-}cU zIvbLmJ1-F25C*nueX40jY@XrMPsy!|tuLk*6-d$ai*kiUk$i&(?&DnL@saTey7!eI zTs{3A5#9UhA$I*5@4xqnzk2VXRo)B?3`Pg|!Dt^y0wl%@p&0K_ZK#iDbbx1#37+XO z-f`eM+5SkZkZOf-hh(7Hib?ik9~h4z>oB%2-VC2oj8k&|W!XM=6Y7w07&XoFqtg=5 zMg%=04n`r=Q3!PuLLKEm3Lv8(Hb@2pQY;Fo7KJp6t~zN%LG%nSG_8J^MxjyO3?*2| zhOa)RWuS^xt4-U*_Yk^AWcmO*_un%?x!|C&JOi6?2(bo6zZ4WrEhdZsQ>3yU(s2Y7 z2x2PBv;k|(!)T)XB%^&mxQ#%#Bu8}(>zY^BWL-nL=Fv5`u8F$l(ltTXc-@nq?IQW+ z{A?%5Z_dxQk^H9oY=q>)`B_TxP5D_u@_K$oBDqFlll)W{$%XP$og}B_r`kv^n4gM} zTtj|}l3aa$ijZ6&UzCWgk`XDt*hOrAezB9-b@|0MV*BzJBVc1f5=egIm|hZo zeN3Gcb5gCEW*cJ~1EQ`_16z-&X7enRapGEwY3IMagU~*q7Sq^z+5T$z$?{BjL3tS| zE0R*EMD;QPW~r2~&j}D!v$4$#cbZLIrXCT_IT0T7kUOVAT|{7^0V^x%v{|Q{b-GEX zVVySVRM%;vPN_~qI@NR<)MvbB?sj5?=Q@>8@bn4ToqEoC>uTEv1f*zz(k51h> z6?N)*K&L`Vr~GrA3A&&Lg<|FLTpa|oWf8eiwk&=Sl5 zGOZxMK|&P;q68V~A)ox|FA%c$N~!`ifR8-nQ_Jw|OT0_V_yA7S4qXB*BKW$Bu`zTL zK2T@iDdnNRg6}Y(o0>Gj`=SOfniRUWP{s^x2mEt4S>Tf3(wx(I&%bu9H8Wi+HR9R=rQc!>TH=-M3Sne#x% z%hS23h*S5A5tAld^gM*6&kW6RrdCC&09nw>LjYa}v|a_YE&*CE-9jruDS%Ct^gjFj z|3s+wCM5i?Vr1+VM)Ea`WIx16(@h5;O`SD#m?4PL?;i$bJSd~UfUL30dJZ_QS=Tn} z+9q8K>sphp>AKdaYe0n|UDI?esA~G>U7PgYsvu}-!h0{Gh-~c2hfMV9RO9A zzR!ZA6z~ZkzxRj#j?jZ^EKKob881nA*^5_h*VHcg@Unte>hP71X=)1szRcq#ye!}q z*KJzOB{yCc@rnmuK_6`+omR8p!j~KI62;3pUb$Vfy0i%|hw;j0eC1=B)q)3KR`C+S z%K^Mne>)q3c)0oSZ^9|LikH6U z-a=^e2mhOxz4g8@4t_U7ogR{U5T^gr`;efFb^#*-K>|NIlr)K23l3^RozOb`e?X?z zWDvU^=0b6O%$F-;u9_rb2C1o$)BzS~vtMd9Uc%OQJ;wKy{)>urpJLpH0uQX3 z^^(=^GWt2|kFkZt@E&aJard8hTi?LeOK#&Ok8uTCX9R1%U~R+JUwc|dTECngQ4~vP z{bG7M_T&95BpC-#cmf*}QvU_Xcov19$Hw#C{tMplAIi|A!SUGd{i4|bOVy(Hm8)+; zz+(XqPz)d&;aBu_wVlQ%V1mHFt0qiHUtsMA*m+nBks{_ zPNF_C0$;p`jPAb1{rQ9;cNh(+#FjhepD~gh-4prf#BoVgPn1Y>g2cK@rS#Kh4K)x5 zlqM3T^p<4FSpVxtP8rDn{1fkKuRe}D@6v&iVMMnidy=W>^T!RjbXItJ;iaJ^YYULA;cLb)4cfaZO#tCrk*^Xo>UMi7|{)kQ_V1(Kp_o~sB zmZQZYQ6D{1I_pgqjpUXs$ur%?1A$a2od6TpO@kP2Fml6vswd^Fk_L-9eKoZtlMT6HA;+ diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm index 4423c605f746aa424937c84a10dfb9a4f7b6f8e5..416577ad8f23e9229df7580011424dea4ec4f21f 100644 GIT binary patch literal 5269 zcmcIne{3Abb>7|EyP5l?n>&%*-O))pUYFE~a2>XA+N)yJXM(Zrh>E?Q2;s1cgN`CE zySHR)hN8tKsRC>&gGVV&#VZYwbPg2^y5bKcb^a(s$!e;`0ZdZ=s6h%CMVc~58~Bf) zR?{R_Y+CA@J<_z=SU?Swz~kP|&b;~Nz3+W9lRb5U<6ddtoIf6M|B&Ktb189hBfwfj zun1TT>`Abvz@7p7W3ba;7T9^PDX_lY!9ixH`+G-vPxrkrG`9CP_(E_0-rL#oc!|B0 zhK9Zp*4EGwP;nK*9!_V&x{^UCwaJJMtc)H$r7Dko7{N)!%de01&oDr;L$IrRm z!52%XM}~W!Kl`Q9@NlW$9TsK|iEO@^7Ega^Xz+zG&tHBcF3db>a2L2~?wed#=n(RP zC461@5BG=eL9~j_<8kk+-aFn_U&;5J|0Vxl`@iG&2i5{T!F$2ceXs3Hhh7W)n6!j{ zJDeNaH%>$p@;xj1IFA3^#Xl&`VPWcUWOVBbzYh&Q5$)%EnB#<_c0&__XU-N;j1x*z z@L})Qe$EWv)<{xoY}c9+W@9Q6-in;#MI>Sm5o$$*IYE%RU145oLWC2Jdt#gmd60*Q zUJvp7lsCh%hZ=c@(;Dw`TGKjbHg0j?BiAINl!#*&sYQ3C!WZH58aa zA@m$C7^!YiH`2VB)yt{cXn8zVt5}s-U_o0Zo-MT6FO*QBhxjY)v%WR55>l{~T zy$n_Xo8)*?*^10>RdSGnH+ zOBff-tDhJmB5(kS^8Ml?gt~O2T^OC@AbKJez1@7+hZ{Hn;UJ`fb3qWj;oSj&w_t!G zBHc_vPYmtM&AxAb6hb0EZWj?RdPR=FC*a365X1!xaREhKz!BFY366|{ub;a`FC&QyYo_gEEqjS}vaFSdocw=5c74k&7;FvQLYOzc!F~St-vobh_q!ZAuHSk6Q z=dfN*N3XCF)y-^l2|Km=9ao`M<_oO^41;|t((Gf9a==Ze`T$Q>ACdJjeVpjSP*wTp zh+iEE=zT@?7!-T@-;(N5dN5`YQ{Uf2~ITQhv08W zGqzyFzrpLf(0mo1KLg#o&36NR1cMX05k!*+AZUVK#)58f!Y4HLJ;ulodIkz<4hrZp zSOskIvlP;#;9lGQEM@U$EG|w#+pyL(nEws1C9tc)r!oF<6@69ME9k!eS<1WHmpp$T zp$1;JhV*M`eJQUmr}UL6KVeNe0(LNyrv zPaGsbAIO&&9gDS5-85?zpj2(1Y{)d~r%@k`dTA8VsE0<~G%C`li$(<+G$9)itAfdTn7oZGJ4x0n~HPS#a*mnPXq zq-;m`v-&lxFX_v&zC!c?vozt?&)B7}3hGyJ>5@M>5l|=4$#tX;=1ZGcFBVH)ue#(b zEqhD0s9r#&F&88vx`xzCpjEb7_4e3a^?NAVj#ZcMl%qje4$2KdnFQrP zP{u(SdaKM~{228A+XuWkmjXIVfcXujJapBr6a)HJY#N=G1sik z>y}+xA)B(bnk07)TdVEl&LL|xLGB#1R%7H&)LNCvodeb?A$O>?g2|>rHi*Mz$>DO7 z!{tVY%MA{f_d8sUI9v{sb?k6CZ9GfJb4`2zq`Ggmz2Jo z)mQSMD3#ik+A>fdwg%u`;06xZOj#h;f$Io%SZ4>F-iBRFxB?&?eQE#TzYuzHn`Oz& zm!`Z_IZTy9R5?hMC{+$ng;GVKicA%WDos>rq)G!-_ERN7l`vHZRYFwRN0lH|0#xx+ z#YYuzf~kV2;-QM0Dk4=}S2?N(V^rbag8kpr2a179{&RSHXRFbzta=dxBTg*=*i&m- zxk$Yj;eYuMNCS*=z@|OqcTWJ=b!Y^zX}E|`>2Qonhp2RrN>M5upc18$LM53>5|x^$ z)JUZUD($CIgi2v55h{hKw2w+bDg~(Ir;?9KUMgWKfiDl0+*A^&pt66!i3fp9ix`s+OyxKroD<{<(xHRRDu6i{q zrLLlrbEtGSpgt{@I`EN=BNtCj`vPBsuI)$O>{;-K{QcX}gjY@YdKSd!(>~asduHK| z*uAZpL>rW~zL<6 za8tUK0G~Ts{>ILhuU$`t@V)!Ik#;hc^pHR71J6IY?_?|qYl0&%^PXqkP3FB=dy`Da z)~zHtf7rU!PR<{)ZY9Y1gVwDWIUlud$>jV2>lPvBsr4o%6ABq8Olc=1rnD1HOlc4YTl> zF0&}Wy&H}U-5$6q7Qu1x=+xjZ-J|dCZl2v3c44l={kiwp&f3ui58gvM#12<1zP+OK;GnshH(Uwf6s?N+VR%g|=Wi=Czt1X$f>cvXB zD>u{AP)&bc{Xt^!2fq2)=Q=yPIxdio4z9Vm^X19O$sHFjRVs_>D~sx%{LU+Crb`g2 zvx^IA%XCBhi}9Is=Y{&WnXX*b?3{&3V77R?TJ5P;JHKeeO*_wUIK!p}kdf)m z*E)JCI2`6XJK?>RhG*1>^F5P!EuQJhoq+MaaCpa2TP9Z}nQB*8wKG#ukG{^l!kX*O z>;GpLm&kAJPR#l<_>c5C+F2Ddl_HGFRn(Uu09CaybD~4dv>nYvTAHhiv%7FV_PbdB z)uA>vzFb*MH&@kkhnRsOE6%C_%N~eGT=m8>N2gWqjQR;=(6*cg@bSH#RjxXFk#(y# zg~Q>Q&d$#2h4lB;8MU*z*tXjhk_M5hLf2eZy^VdJ%>nY=F7+Qy*fURMxJ(-aAF`Z@ P#4~YAt@dP^o4Nl6n+B?f literal 6470 zcmbVQe{dUBe&1cM_pPLr^(1S5=t+rXvxyxjSI-Ss487(=&~Z|@bA>EGv#+Y11jVbh z3A!ZMvYne|xPaxr-6d)uPCy;gCd5u|rcB!-4)L^Uk12-GncNIFL;tE<%9P=bYuYPU z0}1K(t!;3e9>bl+-(BzS`|*9>_xXIj-`&gu-{QD?BAol<5%>2A@dB3+_dn-lcSJDu zj|xV>ZU(y(?CW3;f^7%ev2AdWS#fuNac}RQy^rkP)?X~`DnGh+cX98dyLa_Iv~BOU z-W`vWM^<0&DUBBQ6t@lbF5TP%T}ux?{OI0o+Xsto4|ZpHu%T^(j~09P?%uX*&(7lR z-NlC*gTm8)EV4o<+}pcz^2ql`pP*S!wg{@PDo8v8KOhy43WuraJ;}1-9Y=tOs`m|0eW) z=;KhZIopiFFNBNg!)kV*@}qboKPHx2cP}d%-O*O3mcY-Elsx7cqq6K8sbGH$`TgpeF$@cpXWr@qd^!J zjP8cu4t;w=m)Nkqp_3gt#D+c;7aNd-BrJL)EF}ax$X&DWB4??WIZJz&vsI3VfwP*3 z5~3WJuzdG5gk(XKVfgJ0ee95DhYTkSfwS&!zh&b-WOIV8FTk>_ek1$sf^HOaT+rnL z5{A1yLFyRv6sFS#ossEjWl~m7A!Qr|&=fE9$Gdsj-@)6Rv=pxomqz3Ds#A?C)3Rk7 z&TzawS(=C|N0HSp!)?nhY@Fo^8(##Q2Akse!p2GX=MY#GY(H1f$_1@b(CmWN$MJ$a z#R<-SP8jGDJ=a9kE$rw&!j*>O)Sl#M2?j1~d<*(r0-FcB!11&sTcs2&8CEG_*?Fr! zBiI*UkOUjR7l$$AwNCt|KCGRypydl%rl6$?8f?p&$yRp5`=hG}jZz2GQz<%~r!xt9 zI#VC3kJl&bGc2fQJ<5c~ITlxD`f_?`E%TZ~~J3Y(#)bHww~z zgF&dtg2yeiA5+_qD>C59(=7#;;Dt9|4Iq)@Z2&SadPFV+kL=)us}}AOEVWaxv<|^m z69PbI#nrGE#=DF}?~T9#V2KUA2$0#!kl8D+OAo-aeXRYfxiO{v+2}^GWhb9|R#Nu( za&yv&c{WC!*5>_rN8vQeW~-MOB}kFUOf4|M;Zh7 zR)2o%$J-l43Ffd!rO>*_7g{fay$iO$3;F`j(5YYMsj25wb-+Y`K+hsRgZn@T5-f4<2B z%bzU>xX=n*Xaz2`0?_p-&Rx)p95rS+8ky!Onc`M0=ntjOTaaSEaV_?oWIpHV>0h;= z(|mcFE$9y_?Z5&T7!_eK1m5ksH^dGRCR=5?2^LY_ z0}xybx$9W~0D>^if=vVX0C+$FAv(1Z#I+HCNLl(@Fm}q)FTp8c>GQm$U04S01-IXl zN&>%N<9FzgU375oT{xwvwg7o;DZOa_zKGE44VK1Sh2dnNUxiL5V2TDE^Ym1nM*6JC z6i;VTG-_MXX&w}hM$1<8MZSKlJ`71hIqI!fm4jZuxN|zrWN%ugk-Qa|1*c1PY&>-NhvNm5RXJuu^Q$NOH1_3+g zDM8L)*0APsKJWH^{V>2Uxokhg&JCPhe-adj1+K4t43`X|`H1EvnulnJ zXcEyHi6#=QfoKBJcp^E+yRg&Z9Ph-=dgpitcGfw^%yTOI&QK!JP88%<;K9eV~uvcJZua4*6g^iXE~Em#~&GpZN@m z-6C7jvU!WIXWZ9vmXDP@z=)O^K6i(b8&?8~!$Jh5b4Ta@Af#P`tu1IqLBj=2E=b*} zv2TO8Kobey9DoKNeB#&QpdCTK7V~R{Ukm#+?AH{(Ci^vLXR@Jw=bc|7bnz3xcIzrr z(V359TXg0PY&SUb7~6vLhKvV!7f84L<9{La!z%y}4}0g3f^6Ze zVud6Vfoj#QT3z9{B7N)y$tXvw`~#JM{0`z9CdQ_do&{M<6^m5+JIT+69v^Bb2D z`m2vWO+7?wA=-MPts`2DXi=gOqD6=XI;s;*BbrLIFwvTc79v`ZXqadLqBRlCPc&tf z*2-(-c8UYR9RbDtF-IgB*kM;hv%;#x;wVU+w`bT3JSmQP%~24KJm`e2@8=|(*xW#k zDn~=`0@=JDD(piX5GhvinH42lrs1-Ax2NZv!m6)PpE(MHTKqw#;uSDaekHDMo9`Gq+85Id{IscysJ^zub6i~N7;FofJW+i7Ok3)m~ zA5kM?kGGiPPyv9^S}ltU_N)(AQJ+VxGQ{sXtRcU7xLAv`4-MFDu1%6gsw0 zJd27G9?R&oB5`v9N_{0Z=dq$4<{avoM8#2`MH1#2+0s(QC#3ds?M_e0Yel=10gn|k z+ArpgLxuVSpm8epg2#Npn>#L3lAzJ9*vnohG;)(rXP2+8_uz+qrCf@}I+WL#8naquX1P|e&L#r#4?g7eP8T#?5>6e3 zVZZYs)hjSA$oh4q3L8CBKaa-@XRZtHYjNf}@xJxWTnFB_&Y6qjeKBXw!26=k9LD>I zb6&<{5j={SZj9+nH^wxk8)GWdjj=G(jj?8?8)G4+8>2xyA~W3>4KUppZDP7H>Sww! zsxaLc^)cNT^)lTU@!$%RjS<9TV?<)IG1ACnV?<=KG19Bb|`MAVTf0!7He82IVTAfMt3XKmoOX`APv|+P6=cduZKf=ox6}+^Df?3PNh^|2O0sfh z`OAex*rqRiys-3mKwd8hKY|_3J_vA=aQ3f%j*t&3HTbMx=^e!f5z$-XL|;$zbwrO5 zJxX*!^a#-nqU%K0h^`VnO!Q`=hlm~|IwpF6=uJfT6I~&?kLX^adx$O*9mbM~-bi$j z=nX^{hz>PiXZv)v)V{N4QW}y!4SKZ8oR!&*%mMs!bFvj-^|Teixo2d80&$ik+pZpA=2SBeoijOuDwIx8*^+a7q)EH5tL?uLx5Y-^6PE?JkDpA8kZ6<1ns6nD) zqC)#7qWX!d5Y2m5GXoDiIY%6p7kERDr1c5c<0nU>IE{<3IxVm79QnaPA32 z8UyH`K&ylyrrKhoTdh9O3+|nTyB$!g{&q_H5J+tiw0020wgi>!HYja(;v&=A-B91c zqsE<3;qHMFcNa)5Jmz;3DF-~Y$NQLCkwo#JtGHD!(7t%kYaWF7%0(9CxOLV#CtELL zYu2DRPt^?7Qg*Pf89p>aip#(E9>7b&*1x^SojhQAZZ^Z&)P{8JAb#GGTg@%WrHKK=_=kLw;srBN{13vycH)CiBvLq8$39Gx75sZ&1`dj)v6dgbf{XjlgS#i zXAjaFHl*44U^QDiVg`e(UA6jVHB~bYXS3Ores5N*ZtEJ{ht_w6m{-Bz;e*wb844cG zg(s4swotll!mLf7Fw<=}Cm&C>C6gj%t)9FmX--t#U%V}QIAzujq-wcr&D^@hOmEuo zd@`B5sjZe|-qgT_R+#j$YU=B4hnJgWz4z6!Tj42mS!#6M@o^&<=D zTST}6gWLl%C6hR~X;WKUa3aZK%6`dLa%O14Ox8}+*s80wBiUzGjo^;9$(`~*$5(y= zp$TtF*2Hwx9rf^uG=%1U)+iNfTjkgR*}afq-8%ibxT@7~y3H{UpSbP*`OMZoJ;7$^*pN)Eb|UAA iL&xw2*p1Ids&UZtz8Xy9It5z;Xi2(P5aCj5EBAlUKU0_h diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 1fb4d7ab58a4..1947d310baf2 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -362,10 +362,10 @@ mod benchmarks { // We just call a dummy contract to measure the overhead of the call extrinsic. // The size of the data has no influence on the costs of this extrinsic as long as the contract - // won't call `seal_input` in its constructor to copy the data to contract memory. + // won't call `seal_call_data_copy` in its constructor to copy the data to contract memory. // The dummy contract used here does not do this. The costs for the data copy is billed as - // part of `seal_input`. The costs for invoking a contract of a specific size are not part - // of this benchmark because we cannot know the size of the contract when issuing a call + // part of `seal_call_data_copy`. The costs for invoking a contract of a specific size are not + // part of this benchmark because we cannot know the size of the contract when issuing a call // transaction. See `call_with_code_per_byte` for this. #[benchmark(pov_mode = Measured)] fn call() -> Result<(), BenchmarkError> { @@ -853,6 +853,29 @@ mod benchmarks { assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().get_weight_price(weight)); } + #[benchmark(pov_mode = Measured)] + fn seal_copy_to_contract(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) { + let mut setup = CallSetup::::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![]); + let mut memory = memory!(n.encode(), vec![0u8; n as usize],); + let result; + #[block] + { + result = runtime.write_sandbox_output( + memory.as_mut_slice(), + 4, + 0, + &vec![42u8; n as usize], + false, + |_| None, + ); + } + assert_ok!(result); + assert_eq!(&memory[..4], &n.encode()); + assert_eq!(&memory[4..], &vec![42u8; n as usize]); + } + #[benchmark(pov_mode = Measured)] fn seal_call_data_load() { let mut setup = CallSetup::::default(); @@ -869,18 +892,18 @@ mod benchmarks { } #[benchmark(pov_mode = Measured)] - fn seal_input(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) { + fn seal_call_data_copy(n: Linear<0, { limits::code::BLOB_BYTES }>) { let mut setup = CallSetup::::default(); let (mut ext, _) = setup.ext(); let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![42u8; n as usize]); - let mut memory = memory!(n.to_le_bytes(), vec![0u8; n as usize],); + let mut memory = memory!(vec![0u8; n as usize],); let result; #[block] { - result = runtime.bench_input(memory.as_mut_slice(), 4, 0); + result = runtime.bench_call_data_copy(memory.as_mut_slice(), 0, n, 0); } assert_ok!(result); - assert_eq!(&memory[4..], &vec![42u8; n as usize]); + assert_eq!(&memory[..], &vec![42u8; n as usize]); } #[benchmark(pov_mode = Measured)] diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 27ec7948e8fd..7ca08303316e 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4364,6 +4364,22 @@ fn call_data_size_api_works() { }); } +#[test] +fn call_data_copy_api_works() { + let (code, _) = compile_module("call_data_copy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call fixture: Expects an input of [255; 32] and executes tests. + assert_ok!(builder::call(addr).data(vec![255; 32]).build()); + }); +} + #[test] fn static_data_limit_is_enforced() { let (oom_rw_trailing, _) = compile_module("oom_rw_trailing").unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 648a1621c198..8f944b530e86 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -65,6 +65,13 @@ pub trait Memory { /// - designated area is not within the bounds of the sandbox memory. fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError>; + /// Zero the designated location in the sandbox memory. + /// + /// Returns `Err` if one of the following conditions occurs: + /// + /// - designated area is not within the bounds of the sandbox memory. + fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError>; + /// Read designated chunk from the sandbox memory. /// /// Returns `Err` if one of the following conditions occurs: @@ -162,6 +169,10 @@ impl Memory for [u8] { bound_checked.copy_from_slice(buf); Ok(()) } + + fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError> { + <[u8] as Memory>::write(self, ptr, &vec![0; len as usize]) + } } impl Memory for polkavm::RawInstance { @@ -174,6 +185,10 @@ impl Memory for polkavm::RawInstance { fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> { self.write_memory(ptr, buf).map_err(|_| Error::::OutOfBounds.into()) } + + fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError> { + self.zero_memory(ptr, len).map_err(|_| Error::::OutOfBounds.into()) + } } impl PolkaVmInstance for polkavm::RawInstance { @@ -269,6 +284,8 @@ pub enum RuntimeCosts { CopyToContract(u32), /// Weight of calling `seal_call_data_load``. CallDataLoad, + /// Weight of calling `seal_call_data_copy`. + CallDataCopy(u32), /// Weight of calling `seal_caller`. Caller, /// Weight of calling `seal_call_data_size`. @@ -431,10 +448,11 @@ impl Token for RuntimeCosts { use self::RuntimeCosts::*; match *self { HostFn => cost_args!(noop_host_fn, 1), - CopyToContract(len) => T::WeightInfo::seal_input(len), + CopyToContract(len) => T::WeightInfo::seal_copy_to_contract(len), CopyFromContract(len) => T::WeightInfo::seal_return(len), CallDataSize => T::WeightInfo::seal_call_data_size(), CallDataLoad => T::WeightInfo::seal_call_data_load(), + CallDataCopy(len) => T::WeightInfo::seal_call_data_copy(len), Caller => T::WeightInfo::seal_caller(), Origin => T::WeightInfo::seal_origin(), IsContract => T::WeightInfo::seal_is_contract(), @@ -1276,18 +1294,34 @@ pub mod env { } /// Stores the input passed by the caller into the supplied buffer. - /// See [`pallet_revive_uapi::HostFn::input`]. + /// See [`pallet_revive_uapi::HostFn::call_data_copy`]. #[stable] - fn input(&mut self, memory: &mut M, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { - if let Some(input) = self.input_data.take() { - self.write_sandbox_output(memory, out_ptr, out_len_ptr, &input, false, |len| { - Some(RuntimeCosts::CopyToContract(len)) - })?; - self.input_data = Some(input); - Ok(()) - } else { - Err(Error::::InputForwarded.into()) + fn call_data_copy( + &mut self, + memory: &mut M, + out_ptr: u32, + out_len: u32, + offset: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::CallDataCopy(out_len))?; + + let Some(input) = self.input_data.as_ref() else { + return Err(Error::::InputForwarded.into()); + }; + + let start = offset as usize; + if start >= input.len() { + memory.zero(out_ptr, out_len)?; + return Ok(()); } + + let end = start.saturating_add(out_len as usize).min(input.len()); + memory.write(out_ptr, &input[start..end])?; + + let bytes_written = (end - start) as u32; + memory.zero(out_ptr.saturating_add(bytes_written), out_len - bytes_written)?; + + Ok(()) } /// Stores the U256 value at given call input `offset` into the supplied buffer. diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index e8fec31b19e5..94bd0397a0d5 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -86,7 +86,8 @@ pub trait WeightInfo { fn seal_now() -> Weight; fn seal_weight_to_fee() -> Weight; fn seal_call_data_load() -> Weight; - fn seal_input(n: u32, ) -> Weight; + fn seal_copy_to_contract(n: u32, ) -> Weight; + fn seal_call_data_copy(n: u32, ) -> Weight; fn seal_return(n: u32, ) -> Weight; fn seal_terminate(n: u32, ) -> Weight; fn seal_deposit_event(t: u32, n: u32, ) -> Weight; @@ -533,12 +534,22 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(295_000, 0) } /// The range of component `n` is `[0, 262140]`. - fn seal_input(n: u32, ) -> Weight { + fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 475_000 picoseconds. - Weight::from_parts(427_145, 0) + // Minimum execution time: 369_000 picoseconds. + Weight::from_parts(544_048, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 262144]`. + fn seal_call_data_copy(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(123_748, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) } @@ -1382,12 +1393,22 @@ impl WeightInfo for () { Weight::from_parts(295_000, 0) } /// The range of component `n` is `[0, 262140]`. - fn seal_input(n: u32, ) -> Weight { + fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 475_000 picoseconds. - Weight::from_parts(427_145, 0) + // Minimum execution time: 369_000 picoseconds. + Weight::from_parts(544_048, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) + } + /// The range of component `n` is `[0, 262144]`. + fn seal_call_data_copy(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(123_748, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) } diff --git a/substrate/frame/revive/uapi/src/flags.rs b/substrate/frame/revive/uapi/src/flags.rs index 763a89d6c030..6a0f47c38c2c 100644 --- a/substrate/frame/revive/uapi/src/flags.rs +++ b/substrate/frame/revive/uapi/src/flags.rs @@ -38,7 +38,7 @@ bitflags! { /// /// A forwarding call will consume the current contracts input. Any attempt to /// access the input after this call returns will lead to [`Error::InputForwarded`]. - /// It does not matter if this is due to calling `seal_input` or trying another + /// It does not matter if this is due to calling `call_data_copy` or trying another /// forwarding call. Consider using [`Self::CLONE_INPUT`] in order to preserve /// the input. const FORWARD_INPUT = 0b0000_0001; diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index aa3203697898..505ec0a82f07 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -245,18 +245,28 @@ pub trait HostFn: private::Sealed { hash_fn!(keccak_256, 32); - /// Stores the input passed by the caller into the supplied buffer. + /// Stores the input data passed by the caller into the supplied `output` buffer, + /// starting from the given input data `offset`. + /// + /// The `output` buffer is guaranteed to always be fully populated: + /// - If the call data (starting from the given `offset`) is larger than the `output` buffer, + /// only what fits into the `output` buffer is written. + /// - If the `output` buffer size exceeds the call data size (starting from `offset`), remaining + /// bytes in the `output` buffer are zeroed out. + /// - If the provided call data `offset` is out-of-bounds, the whole `output` buffer is zeroed + /// out. /// /// # Note /// /// This function traps if: - /// - the input is larger than the available space. /// - the input was previously forwarded by a [`call()`][`Self::call()`]. + /// - the `output` buffer is located in an PolkaVM invalid memory range. /// /// # Parameters /// - /// - `output`: A reference to the output data buffer to write the input data. - fn input(output: &mut &mut [u8]); + /// - `output`: A reference to the output data buffer to write the call data. + /// - `offset`: The offset index into the call data from where to start copying. + fn call_data_copy(output: &mut [u8], offset: u32); /// Stores the U256 value at given `offset` from the input passed by the caller /// into the supplied buffer. diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index d5a695262a24..40de12b25f36 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -62,7 +62,7 @@ mod sys { pub fn delegate_call(ptr: *const u8) -> ReturnCode; pub fn instantiate(ptr: *const u8) -> ReturnCode; pub fn terminate(beneficiary_ptr: *const u8); - pub fn input(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn call_data_copy(out_ptr: *mut u8, out_len: u32, offset: u32); pub fn call_data_load(out_ptr: *mut u8, offset: u32); pub fn seal_return(flags: u32, data_ptr: *const u8, data_len: u32); pub fn caller(out_ptr: *mut u8); @@ -381,14 +381,6 @@ impl HostFn for HostFnImpl { ret_code.into() } - fn input(output: &mut &mut [u8]) { - let mut output_len = output.len() as u32; - { - unsafe { sys::input(output.as_mut_ptr(), &mut output_len) }; - } - extract_from_slice(output, output_len as usize); - } - fn call_data_load(out_ptr: &mut [u8; 32], offset: u32) { unsafe { sys::call_data_load(out_ptr.as_mut_ptr(), offset) }; } @@ -474,6 +466,11 @@ impl HostFn for HostFnImpl { ret_code.into_u32() } + fn call_data_copy(output: &mut [u8], offset: u32) { + let len = output.len() as u32; + unsafe { sys::call_data_copy(output.as_mut_ptr(), len, offset) }; + } + #[cfg(feature = "unstable-api")] fn call_runtime(call: &[u8]) -> Result { let ret_code = unsafe { sys::call_runtime(call.as_ptr(), call.len() as u32) }; From f24007ef9490d20f55f156203b2856121854ad86 Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Wed, 18 Dec 2024 10:01:00 +0200 Subject: [PATCH 179/340] elastic scaling RFC 103 end-to-end test (#6452) Adds a new zombienet-sdk test which verifies that elastic scaling works correctly both with the MVP and the new RFC 103 implementation which sends the core selector as a UMP signal. Also enables the V2 receipts node feature for testnet genesis config. Part of https://github.com/paritytech/polkadot-sdk/issues/5049 --------- Co-authored-by: Javier Viola Co-authored-by: Javier Viola <363911+pepoviola@users.noreply.github.com> --- .gitlab/pipeline/zombienet/polkadot.yml | 19 ++ Cargo.lock | 25 +-- Cargo.toml | 2 +- .../testing/rococo-parachain/Cargo.toml | 2 +- cumulus/test/runtime/Cargo.toml | 1 + cumulus/test/runtime/build.rs | 8 + cumulus/test/runtime/src/lib.rs | 5 + cumulus/test/service/src/chain_spec.rs | 10 ++ cumulus/test/service/src/cli.rs | 8 +- .../rococo/src/genesis_config_presets.rs | 4 +- .../westend/src/genesis_config_presets.rs | 3 +- polkadot/zombienet-sdk-tests/build.rs | 65 ++++--- .../tests/elastic_scaling/helpers.rs | 60 +++++++ .../tests/elastic_scaling/mod.rs | 8 + .../elastic_scaling/slot_based_3cores.rs | 166 ++++++++++++++++++ polkadot/zombienet-sdk-tests/tests/lib.rs | 3 + .../tests/smoke/coretime_revenue.rs | 4 +- .../zombienet-sdk-tests/tests/smoke/mod.rs | 1 - prdoc/pr_6452.prdoc | 16 ++ 19 files changed, 369 insertions(+), 41 deletions(-) create mode 100644 polkadot/zombienet-sdk-tests/tests/elastic_scaling/helpers.rs create mode 100644 polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs create mode 100644 polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs create mode 100644 prdoc/pr_6452.prdoc diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index e722239d890c..14a235bcda86 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -63,6 +63,8 @@ LOCAL_SDK_TEST: "/builds/parity/mirrors/polkadot-sdk/polkadot/zombienet-sdk-tests" FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 RUN_IN_CONTAINER: "1" + # don't retry sdk tests + NEXTEST_RETRIES: 0 artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" when: always @@ -190,6 +192,7 @@ zombienet-polkadot-elastic-scaling-0001-basic-3cores-6s-blocks: --local-dir="${LOCAL_DIR}/elastic_scaling" --test="0002-elastic-scaling-doesnt-break-parachains.zndsl" + .zombienet-polkadot-functional-0012-spam-statement-distribution-requests: extends: - .zombienet-polkadot-common @@ -397,3 +400,19 @@ zombienet-polkadot-malus-0001-dispute-valid: - unset NEXTEST_FAILURE_OUTPUT - unset NEXTEST_SUCCESS_OUTPUT - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- smoke::coretime_revenue::coretime_revenue_test + +zombienet-polkadot-elastic-scaling-slot-based-3cores: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - export CUMULUS_IMAGE="docker.io/paritypr/test-parachain:${PIPELINE_IMAGE_TAG}" + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- elastic_scaling::slot_based_3cores::slot_based_3cores_test diff --git a/Cargo.lock b/Cargo.lock index c6438fdffa3f..41c149c11d41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32164,9 +32164,9 @@ dependencies = [ [[package]] name = "zombienet-configuration" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad4fc5b0f1aa54de6bf2d6771c449b41cad47e1cf30559af0a71452686b47ab" +checksum = "d716b3ff8112d98ced15f53b0c72454f8cde533fe2b68bb04379228961efbd80" dependencies = [ "anyhow", "lazy_static", @@ -32184,9 +32184,9 @@ dependencies = [ [[package]] name = "zombienet-orchestrator" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a7dd25842ded75c7f4dc4f38f05fef567bd0b37fd3057c223d4ee34d8fa817" +checksum = "4098a7d33b729b59e32c41a87aa4d484bd1b8771a059bbd4edfb4d430b3b2d74" dependencies = [ "anyhow", "async-trait", @@ -32217,9 +32217,9 @@ dependencies = [ [[package]] name = "zombienet-prom-metrics-parser" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a63e0c6024dd19b0f8b28afa94f78c211e5c163350ecda4a48084532d74d7cfe" +checksum = "961e30be45b34f6ebeabf29ee2f47b0cd191ea62e40c064752572207509a6f5c" dependencies = [ "pest", "pest_derive", @@ -32228,9 +32228,9 @@ dependencies = [ [[package]] name = "zombienet-provider" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d87c29390a342d0f4f62b6796861fb82e0e56c49929a272b689e8dbf24eaab9" +checksum = "ab0f7f01780b7c99a6c40539d195d979f234305f32808d547438b50829d44262" dependencies = [ "anyhow", "async-trait", @@ -32259,14 +32259,15 @@ dependencies = [ [[package]] name = "zombienet-sdk" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829e5111182caf00ba57cd63656cf0bde6ce6add7f6a9747d15821c202a3f27e" +checksum = "99a3c5f2d657235b3ab7dc384677e63cde21983029e99106766ecd49e9f8d7f3" dependencies = [ "async-trait", "futures", "lazy_static", "subxt", + "subxt-signer", "tokio", "zombienet-configuration", "zombienet-orchestrator", @@ -32276,9 +32277,9 @@ dependencies = [ [[package]] name = "zombienet-support" -version = "0.2.16" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99568384a1d9645458ab9de377b3517cb543a1ece5aba905aeb58d269139df4e" +checksum = "296f887ea88e07edd771f8e1d0dec5297a58b422f4b884a6292a21ebe03277cb" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index 56ae70f8f993..64a11a340d10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1389,7 +1389,7 @@ xcm-procedural = { path = "polkadot/xcm/procedural", default-features = false } xcm-runtime-apis = { path = "polkadot/xcm/xcm-runtime-apis", default-features = false } xcm-simulator = { path = "polkadot/xcm/xcm-simulator", default-features = false } zeroize = { version = "1.7.0", default-features = false } -zombienet-sdk = { version = "0.2.16" } +zombienet-sdk = { version = "0.2.19" } zstd = { version = "0.12.4", default-features = false } [profile.release] diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml index 035d0ac94be6..2ddb3364fc09 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml @@ -51,7 +51,7 @@ polkadot-runtime-common = { workspace = true } # Cumulus cumulus-pallet-aura-ext = { workspace = true } pallet-message-queue = { workspace = true } -cumulus-pallet-parachain-system = { workspace = true, features = ["experimental-ump-signals"] } +cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-ping = { workspace = true } diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index 8117e6e69709..b80170af3e83 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -96,3 +96,4 @@ std = [ ] increment-spec-version = [] elastic-scaling = [] +experimental-ump-signals = ["cumulus-pallet-parachain-system/experimental-ump-signals"] diff --git a/cumulus/test/runtime/build.rs b/cumulus/test/runtime/build.rs index 7a7fe8ffaa82..43e60c1074a0 100644 --- a/cumulus/test/runtime/build.rs +++ b/cumulus/test/runtime/build.rs @@ -29,6 +29,14 @@ fn main() { .with_current_project() .enable_feature("elastic-scaling") .import_memory() + .set_file_name("wasm_binary_elastic_scaling_mvp.rs") + .build(); + + WasmBuilder::new() + .with_current_project() + .enable_feature("elastic-scaling") + .enable_feature("experimental-ump-signals") + .import_memory() .set_file_name("wasm_binary_elastic_scaling.rs") .build(); } diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index b1649c410581..4abc10276af1 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -27,6 +27,11 @@ pub mod wasm_spec_version_incremented { include!(concat!(env!("OUT_DIR"), "/wasm_binary_spec_version_incremented.rs")); } +pub mod elastic_scaling_mvp { + #[cfg(feature = "std")] + include!(concat!(env!("OUT_DIR"), "/wasm_binary_elastic_scaling_mvp.rs")); +} + pub mod elastic_scaling { #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary_elastic_scaling.rs")); diff --git a/cumulus/test/service/src/chain_spec.rs b/cumulus/test/service/src/chain_spec.rs index 3d4e4dca5f8d..5ebcc14592d7 100644 --- a/cumulus/test/service/src/chain_spec.rs +++ b/cumulus/test/service/src/chain_spec.rs @@ -116,3 +116,13 @@ pub fn get_elastic_scaling_chain_spec(id: Option) -> ChainSpec { .expect("WASM binary was not built, please build it!"), ) } + +/// Get the chain spec for a specific parachain ID. +pub fn get_elastic_scaling_mvp_chain_spec(id: Option) -> ChainSpec { + get_chain_spec_with_extra_endowed( + id, + Default::default(), + cumulus_test_runtime::elastic_scaling_mvp::WASM_BINARY + .expect("WASM binary was not built, please build it!"), + ) +} diff --git a/cumulus/test/service/src/cli.rs b/cumulus/test/service/src/cli.rs index 220b0449f339..e019089e70fe 100644 --- a/cumulus/test/service/src/cli.rs +++ b/cumulus/test/service/src/cli.rs @@ -262,10 +262,16 @@ impl SubstrateCli for TestCollatorCli { tracing::info!("Using default test service chain spec."); Box::new(cumulus_test_service::get_chain_spec(Some(ParaId::from(2000)))) as Box<_> }, + "elastic-scaling-mvp" => { + tracing::info!("Using elastic-scaling mvp chain spec."); + Box::new(cumulus_test_service::get_elastic_scaling_mvp_chain_spec(Some( + ParaId::from(2100), + ))) as Box<_> + }, "elastic-scaling" => { tracing::info!("Using elastic-scaling chain spec."); Box::new(cumulus_test_service::get_elastic_scaling_chain_spec(Some(ParaId::from( - 2100, + 2200, )))) as Box<_> }, path => { diff --git a/polkadot/runtime/rococo/src/genesis_config_presets.rs b/polkadot/runtime/rococo/src/genesis_config_presets.rs index bdbf6f37d92c..a96a509b0e4d 100644 --- a/polkadot/runtime/rococo/src/genesis_config_presets.rs +++ b/polkadot/runtime/rococo/src/genesis_config_presets.rs @@ -129,7 +129,9 @@ fn default_parachains_host_configuration( allowed_ancestry_len: 2, }, node_features: bitvec::vec::BitVec::from_element( - 1u8 << (FeatureIndex::ElasticScalingMVP as usize), + 1u8 << (FeatureIndex::ElasticScalingMVP as usize) | + 1u8 << (FeatureIndex::EnableAssignmentsV2 as usize) | + 1u8 << (FeatureIndex::CandidateReceiptV2 as usize), ), scheduler_params: SchedulerParams { lookahead: 2, diff --git a/polkadot/runtime/westend/src/genesis_config_presets.rs b/polkadot/runtime/westend/src/genesis_config_presets.rs index b8f7710089e0..ea5aff554e8c 100644 --- a/polkadot/runtime/westend/src/genesis_config_presets.rs +++ b/polkadot/runtime/westend/src/genesis_config_presets.rs @@ -133,7 +133,8 @@ fn default_parachains_host_configuration( }, node_features: bitvec::vec::BitVec::from_element( 1u8 << (FeatureIndex::ElasticScalingMVP as usize) | - 1u8 << (FeatureIndex::EnableAssignmentsV2 as usize), + 1u8 << (FeatureIndex::EnableAssignmentsV2 as usize) | + 1u8 << (FeatureIndex::CandidateReceiptV2 as usize), ), scheduler_params: SchedulerParams { lookahead: 2, diff --git a/polkadot/zombienet-sdk-tests/build.rs b/polkadot/zombienet-sdk-tests/build.rs index 240d86386af2..f7a62a53a8ac 100644 --- a/polkadot/zombienet-sdk-tests/build.rs +++ b/polkadot/zombienet-sdk-tests/build.rs @@ -25,39 +25,47 @@ fn make_env_key(k: &str) -> String { replace_dashes(&k.to_ascii_uppercase()) } +fn wasm_sub_path(chain: &str) -> String { + let (package, runtime_name) = + if let Some(cumulus_test_runtime) = chain.strip_prefix("cumulus-test-runtime-") { + ( + "cumulus-test-runtime".to_string(), + format!("wasm_binary_{}.rs", replace_dashes(cumulus_test_runtime)), + ) + } else { + (format!("{chain}-runtime"), replace_dashes(&format!("{chain}-runtime"))) + }; + + format!("{}/{}.wasm", package, runtime_name) +} + fn find_wasm(chain: &str) -> Option { const PROFILES: [&str; 2] = ["release", "testnet"]; let manifest_path = env::var("CARGO_WORKSPACE_ROOT_DIR").unwrap(); let manifest_path = manifest_path.strip_suffix('/').unwrap(); debug_output!("manifest_path is : {}", manifest_path); - let package = format!("{chain}-runtime"); + + let sub_path = wasm_sub_path(chain); + let profile = PROFILES.into_iter().find(|p| { - let full_path = format!( - "{}/target/{}/wbuild/{}/{}.wasm", - manifest_path, - p, - &package, - replace_dashes(&package) - ); + let full_path = format!("{}/target/{}/wbuild/{}", manifest_path, p, sub_path); debug_output!("checking wasm at : {}", full_path); matches!(path::PathBuf::from(&full_path).try_exists(), Ok(true)) }); debug_output!("profile is : {:?}", profile); profile.map(|profile| { - PathBuf::from(&format!( - "{}/target/{}/wbuild/{}/{}.wasm", - manifest_path, - profile, - &package, - replace_dashes(&package) - )) + PathBuf::from(&format!("{}/target/{}/wbuild/{}", manifest_path, profile, sub_path)) }) } // based on https://gist.github.com/s0me0ne-unkn0wn/bbd83fe32ce10327086adbf13e750eec fn build_wasm(chain: &str) -> PathBuf { - let package = format!("{chain}-runtime"); + let package = if chain.starts_with("cumulus-test-runtime-") { + String::from("cumulus-test-runtime") + } else { + format!("{chain}-runtime") + }; let cargo = env::var("CARGO").unwrap(); let target = env::var("TARGET").unwrap(); @@ -81,11 +89,7 @@ fn build_wasm(chain: &str) -> PathBuf { .status() .unwrap(); - let wasm_path = &format!( - "{target_dir}/{target}/release/wbuild/{}/{}.wasm", - &package, - replace_dashes(&package) - ); + let wasm_path = &format!("{target_dir}/{target}/release/wbuild/{}", wasm_sub_path(chain)); PathBuf::from(wasm_path) } @@ -128,6 +132,10 @@ fn main() { const METADATA_DIR: &str = "metadata-files"; const CHAINS: [&str; 2] = ["rococo", "coretime-rococo"]; + // Add some cumulus test runtimes if needed. Formatted like + // "cumulus-test-runtime-elastic-scaling". + const CUMULUS_TEST_RUNTIMES: [&str; 0] = []; + let metadata_path = format!("{manifest_path}/{METADATA_DIR}"); for chain in CHAINS { @@ -145,6 +153,21 @@ fn main() { }; } + for chain in CUMULUS_TEST_RUNTIMES { + let full_path = format!("{metadata_path}/{chain}-local.scale"); + let output_path = path::PathBuf::from(&full_path); + + match output_path.try_exists() { + Ok(true) => { + debug_output!("got: {}", full_path); + }, + _ => { + debug_output!("needs: {}", full_path); + fetch_metadata_file(chain, &output_path); + }, + }; + } + substrate_build_script_utils::generate_cargo_keys(); substrate_build_script_utils::rerun_if_git_head_changed(); println!("cargo:rerun-if-changed={}", metadata_path); diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/helpers.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/helpers.rs new file mode 100644 index 000000000000..7d4ad4a1dd8b --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/helpers.rs @@ -0,0 +1,60 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +use super::rococo; +use std::{collections::HashMap, ops::Range}; +use subxt::{OnlineClient, PolkadotConfig}; + +// Helper function for asserting the throughput of parachains (total number of backed candidates in +// a window of relay chain blocks), after the first session change. +pub async fn assert_para_throughput( + relay_client: &OnlineClient, + stop_at: u32, + expected_candidate_ranges: HashMap>, +) -> Result<(), anyhow::Error> { + let mut blocks_sub = relay_client.blocks().subscribe_finalized().await?; + let mut candidate_count: HashMap = HashMap::new(); + let mut current_block_count = 0; + let mut had_first_session_change = false; + + while let Some(block) = blocks_sub.next().await { + let block = block?; + log::debug!("Finalized relay chain block {}", block.number()); + let events = block.events().await?; + let is_session_change = events.has::()?; + + if !had_first_session_change && is_session_change { + had_first_session_change = true; + } + + if had_first_session_change && !is_session_change { + current_block_count += 1; + + for event in events.find::() { + *(candidate_count.entry(event?.0.descriptor.para_id.0).or_default()) += 1; + } + } + + if current_block_count == stop_at { + break; + } + } + + log::info!( + "Reached {} finalized relay chain blocks that contain backed candidates. The per-parachain distribution is: {:#?}", + stop_at, + candidate_count + ); + + for (para_id, expected_candidate_range) in expected_candidate_ranges { + let actual = candidate_count + .get(¶_id) + .expect("ParaId did not have any backed candidates"); + assert!( + expected_candidate_range.contains(actual), + "Candidate count {actual} not within range {expected_candidate_range:?}" + ); + } + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs new file mode 100644 index 000000000000..bb296a419df1 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs @@ -0,0 +1,8 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +#[subxt::subxt(runtime_metadata_path = "metadata-files/rococo-local.scale")] +pub mod rococo {} + +mod helpers; +mod slot_based_3cores; diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs new file mode 100644 index 000000000000..41ec1250ecc4 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs @@ -0,0 +1,166 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test that parachains that use a single slot-based collator with elastic scaling MVP and with +// elastic scaling with RFC103 can achieve full throughput of 3 candidates per block. + +use anyhow::anyhow; + +use super::{ + helpers::assert_para_throughput, + rococo, + rococo::runtime_types::{ + pallet_broker::coretime_interface::CoreAssignment, + polkadot_runtime_parachains::assigner_coretime::PartsOf57600, + }, +}; +use serde_json::json; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn slot_based_3cores_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + // Num cores is 4, because 2 extra will be added automatically when registering the paras. + "num_cores": 4, + "max_validators_per_core": 2 + }, + "async_backing_params": { + "max_candidate_depth": 6, + "allowed_ancestry_len": 2 + } + } + } + })) + // Have to set a `with_node` outside of the loop below, so that `r` has the right + // type. + .with_node(|node| node.with_name("validator-0")); + + (1..12) + .fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + // Para 2100 uses the old elastic scaling mvp, which doesn't send the new UMP signal + // commitment for selecting the core index. + p.with_id(2100) + .with_default_command("test-parachain") + .with_default_image(images.cumulus.as_str()) + .with_chain("elastic-scaling-mvp") + .with_default_args(vec![("--experimental-use-slot-based").into()]) + .with_default_args(vec![ + ("--experimental-use-slot-based").into(), + ("-lparachain=debug,aura=debug").into(), + ]) + .with_collator(|n| n.with_name("collator-elastic-mvp")) + }) + .with_parachain(|p| { + // Para 2200 uses the new RFC103-enabled collator which sends the UMP signal commitment + // for selecting the core index + p.with_id(2200) + .with_default_command("test-parachain") + .with_default_image(images.cumulus.as_str()) + .with_chain("elastic-scaling") + .with_default_args(vec![ + ("--experimental-use-slot-based").into(), + ("-lparachain=debug,aura=debug").into(), + ]) + .with_collator(|n| n.with_name("collator-elastic")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + + let relay_client: OnlineClient = relay_node.wait_client().await?; + let alice = dev::alice(); + + // Assign two extra cores to each parachain. + relay_client + .tx() + .sign_and_submit_then_watch_default( + &rococo::tx() + .sudo() + .sudo(rococo::runtime_types::rococo_runtime::RuntimeCall::Utility( + rococo::runtime_types::pallet_utility::pallet::Call::batch { + calls: vec![ + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 0, + begin: 0, + assignment: vec![(CoreAssignment::Task(2100), PartsOf57600(57600))], + end_hint: None + } + ), + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 1, + begin: 0, + assignment: vec![(CoreAssignment::Task(2100), PartsOf57600(57600))], + end_hint: None + } + ), + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 2, + begin: 0, + assignment: vec![(CoreAssignment::Task(2200), PartsOf57600(57600))], + end_hint: None + } + ), + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 3, + begin: 0, + assignment: vec![(CoreAssignment::Task(2200), PartsOf57600(57600))], + end_hint: None + } + ) + ], + }, + )), + &alice, + ) + .await? + .wait_for_finalized_success() + .await?; + + log::info!("2 more cores assigned to each parachain"); + + // Expect a backed candidate count of at least 39 for each parachain in 15 relay chain blocks + // (2.6 candidates per para per relay chain block). + // Note that only blocks after the first session change and blocks that don't contain a session + // change will be counted. + assert_para_throughput( + &relay_client, + 15, + [(2100, 39..46), (2200, 39..46)].into_iter().collect(), + ) + .await?; + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/lib.rs b/polkadot/zombienet-sdk-tests/tests/lib.rs index 74cdc0765600..977e0f90b1c9 100644 --- a/polkadot/zombienet-sdk-tests/tests/lib.rs +++ b/polkadot/zombienet-sdk-tests/tests/lib.rs @@ -1,4 +1,7 @@ // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 +#[cfg(feature = "zombie-metadata")] +mod elastic_scaling; +#[cfg(feature = "zombie-metadata")] mod smoke; diff --git a/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs b/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs index 7880dc782d05..2da2436a1111 100644 --- a/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs +++ b/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs @@ -180,7 +180,7 @@ where #[tokio::test(flavor = "multi_thread")] async fn coretime_revenue_test() -> Result<(), anyhow::Error> { - env_logger::init_from_env( + let _ = env_logger::try_init_from_env( env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), ); @@ -499,7 +499,7 @@ async fn coretime_revenue_test() -> Result<(), anyhow::Error> { assert_total_issuance(relay_client.clone(), para_client.clone(), total_issuance).await; - log::info!("Test finished successfuly"); + log::info!("Test finished successfully"); Ok(()) } diff --git a/polkadot/zombienet-sdk-tests/tests/smoke/mod.rs b/polkadot/zombienet-sdk-tests/tests/smoke/mod.rs index a3fe15382674..072a9d54ecda 100644 --- a/polkadot/zombienet-sdk-tests/tests/smoke/mod.rs +++ b/polkadot/zombienet-sdk-tests/tests/smoke/mod.rs @@ -1,5 +1,4 @@ // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "zombie-metadata")] mod coretime_revenue; diff --git a/prdoc/pr_6452.prdoc b/prdoc/pr_6452.prdoc new file mode 100644 index 000000000000..f2cb69875e95 --- /dev/null +++ b/prdoc/pr_6452.prdoc @@ -0,0 +1,16 @@ +title: "elastic scaling RFC 103 end-to-end tests" + +doc: + - audience: [Node Dev, Runtime Dev] + description: | + Adds end-to-end zombienet-sdk tests for elastic scaling using the RFC103 implementation. + Only notable user-facing change is that the default chain configurations of westend and rococo + now enable by default the CandidateReceiptV2 node feature. + +crates: + - name: westend-runtime + bump: patch + - name: rococo-runtime + bump: patch + - name: rococo-parachain-runtime + bump: patch From 6dd4a71f702c1ce0afd4141b6d1b14809b809ba7 Mon Sep 17 00:00:00 2001 From: clangenb <37865735+clangenb@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:16:28 +0100 Subject: [PATCH 180/340] Migrate pallet-xcm benchmarks to benchmark v2 syntax (#6501) Migrates pallet-xcm benchmarks to benchmark v2 syntax * Part of #6202 --- polkadot/xcm/pallet-xcm/src/benchmarking.rs | 446 ++++++++++++++------ 1 file changed, 314 insertions(+), 132 deletions(-) diff --git a/polkadot/xcm/pallet-xcm/src/benchmarking.rs b/polkadot/xcm/pallet-xcm/src/benchmarking.rs index dd3c58c5dc77..3ca048057ee4 100644 --- a/polkadot/xcm/pallet-xcm/src/benchmarking.rs +++ b/polkadot/xcm/pallet-xcm/src/benchmarking.rs @@ -15,7 +15,7 @@ // along with Polkadot. If not, see . use super::*; -use frame_benchmarking::{benchmarks, whitelisted_caller, BenchmarkError, BenchmarkResult}; +use frame_benchmarking::v2::*; use frame_support::{assert_ok, weights::Weight}; use frame_system::RawOrigin; use xcm::latest::prelude::*; @@ -83,32 +83,41 @@ pub trait Config: crate::Config { fn get_asset() -> Asset; } -benchmarks! { - send { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn send() -> Result<(), BenchmarkError> { let send_origin = T::SendXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; if T::SendXcmOrigin::try_origin(send_origin.clone()).is_err() { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } let msg = Xcm(vec![ClearOrigin]); - let versioned_dest: VersionedLocation = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )? - .into(); + let versioned_dest: VersionedLocation = T::reachable_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))? + .into(); let versioned_msg = VersionedXcm::from(msg); - // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) T::DeliveryHelper::ensure_successful_delivery( &Default::default(), &versioned_dest.clone().try_into().unwrap(), FeeReason::ChargeFees, ); - }: _>(send_origin, Box::new(versioned_dest), Box::new(versioned_msg)) - teleport_assets { - let (asset, destination) = T::teleportable_asset_and_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )?; + #[extrinsic_call] + _(send_origin as RuntimeOrigin, Box::new(versioned_dest), Box::new(versioned_msg)); + + Ok(()) + } + + #[benchmark] + fn teleport_assets() -> Result<(), BenchmarkError> { + let (asset, destination) = T::teleportable_asset_and_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let assets: Assets = asset.clone().into(); @@ -116,11 +125,13 @@ benchmarks! { let send_origin = RawOrigin::Signed(caller.clone()); let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into()) .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; - if !T::XcmTeleportFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) { + if !T::XcmTeleportFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) + { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } - // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) let (_, _) = T::DeliveryHelper::ensure_successful_delivery( &origin_location, &destination, @@ -134,18 +145,23 @@ benchmarks! { &Asset { fun: Fungible(*amount), id: asset.id }, &origin_location, None, - ).map_err(|error| { - tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error); - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + ) + .map_err(|error| { + tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + })?; + }, + NonFungible(_instance) => { + ::AssetTransactor::deposit_asset( + &asset, + &origin_location, + None, + ) + .map_err(|error| { + tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) })?; }, - NonFungible(instance) => { - ::AssetTransactor::deposit_asset(&asset, &origin_location, None) - .map_err(|error| { - tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) - })?; - } }; let recipient = [0u8; 32]; @@ -153,12 +169,23 @@ benchmarks! { let versioned_beneficiary: VersionedLocation = AccountId32 { network: None, id: recipient.into() }.into(); let versioned_assets: VersionedAssets = assets.into(); - }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) - reserve_transfer_assets { - let (asset, destination) = T::reserve_transferable_asset_and_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )?; + #[extrinsic_call] + _( + send_origin, + Box::new(versioned_dest), + Box::new(versioned_beneficiary), + Box::new(versioned_assets), + 0, + ); + + Ok(()) + } + + #[benchmark] + fn reserve_transfer_assets() -> Result<(), BenchmarkError> { + let (asset, destination) = T::reserve_transferable_asset_and_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let assets: Assets = asset.clone().into(); @@ -166,12 +193,16 @@ benchmarks! { let send_origin = RawOrigin::Signed(caller.clone()); let origin_location = T::ExecuteXcmOrigin::try_origin(send_origin.clone().into()) .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; - if !T::XcmReserveTransferFilter::contains(&(origin_location.clone(), assets.clone().into_inner())) { + if !T::XcmReserveTransferFilter::contains(&( + origin_location.clone(), + assets.clone().into_inner(), + )) { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } - // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) - T::DeliveryHelper::ensure_successful_delivery( + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) + let (_, _) = T::DeliveryHelper::ensure_successful_delivery( &origin_location, &destination, FeeReason::ChargeFees, @@ -184,18 +215,23 @@ benchmarks! { &Asset { fun: Fungible(*amount), id: asset.id.clone() }, &origin_location, None, - ).map_err(|error| { - tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error); - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + ) + .map_err(|error| { + tracing::error!("Fungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) + })?; + }, + NonFungible(_instance) => { + ::AssetTransactor::deposit_asset( + &asset, + &origin_location, + None, + ) + .map_err(|error| { + tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); + BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) })?; }, - NonFungible(instance) => { - ::AssetTransactor::deposit_asset(&asset, &origin_location, None) - .map_err(|error| { - tracing::error!("Nonfungible asset couldn't be deposited, error: {:?}", error); - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)) - })?; - } }; let recipient = [0u8; 32]; @@ -203,8 +239,16 @@ benchmarks! { let versioned_beneficiary: VersionedLocation = AccountId32 { network: None, id: recipient.into() }.into(); let versioned_assets: VersionedAssets = assets.into(); - }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0) - verify { + + #[extrinsic_call] + _( + send_origin, + Box::new(versioned_dest), + Box::new(versioned_beneficiary), + Box::new(versioned_assets), + 0, + ); + match &asset.fun { Fungible(amount) => { assert_ok!(::AssetTransactor::withdraw_asset( @@ -213,20 +257,22 @@ benchmarks! { None, )); }, - NonFungible(instance) => { + NonFungible(_instance) => { assert_ok!(::AssetTransactor::withdraw_asset( &asset, &destination, None, )); - } + }, }; + + Ok(()) } - transfer_assets { - let (assets, fee_index, destination, verify) = T::set_up_complex_asset_transfer().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )?; + #[benchmark] + fn transfer_assets() -> Result<(), BenchmarkError> { + let (assets, _fee_index, destination, verify_fn) = T::set_up_complex_asset_transfer() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let caller: T::AccountId = whitelisted_caller(); let send_origin = RawOrigin::Signed(caller.clone()); let recipient = [0u8; 32]; @@ -235,19 +281,31 @@ benchmarks! { AccountId32 { network: None, id: recipient.into() }.into(); let versioned_assets: VersionedAssets = assets.into(); - // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) T::DeliveryHelper::ensure_successful_delivery( &Default::default(), &versioned_dest.clone().try_into().unwrap(), FeeReason::ChargeFees, ); - }: _>(send_origin.into(), Box::new(versioned_dest), Box::new(versioned_beneficiary), Box::new(versioned_assets), 0, WeightLimit::Unlimited) - verify { + + #[extrinsic_call] + _( + send_origin, + Box::new(versioned_dest), + Box::new(versioned_beneficiary), + Box::new(versioned_assets), + 0, + WeightLimit::Unlimited, + ); + // run provided verification function - verify(); + verify_fn(); + Ok(()) } - execute { + #[benchmark] + fn execute() -> Result<(), BenchmarkError> { let execute_origin = T::ExecuteXcmOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let origin_location = T::ExecuteXcmOrigin::try_origin(execute_origin.clone()) @@ -257,39 +315,59 @@ benchmarks! { return Err(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX))) } let versioned_msg = VersionedXcm::from(msg); - }: _>(execute_origin, Box::new(versioned_msg), Weight::MAX) - force_xcm_version { - let loc = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )?; + #[extrinsic_call] + _(execute_origin as RuntimeOrigin, Box::new(versioned_msg), Weight::MAX); + + Ok(()) + } + + #[benchmark] + fn force_xcm_version() -> Result<(), BenchmarkError> { + let loc = T::reachable_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let xcm_version = 2; - }: _(RawOrigin::Root, Box::new(loc), xcm_version) - force_default_xcm_version {}: _(RawOrigin::Root, Some(2)) + #[extrinsic_call] + _(RawOrigin::Root, Box::new(loc), xcm_version); + + Ok(()) + } - force_subscribe_version_notify { - let versioned_loc: VersionedLocation = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )? - .into(); + #[benchmark] + fn force_default_xcm_version() { + #[extrinsic_call] + _(RawOrigin::Root, Some(2)) + } - // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + #[benchmark] + fn force_subscribe_version_notify() -> Result<(), BenchmarkError> { + let versioned_loc: VersionedLocation = T::reachable_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))? + .into(); + + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) T::DeliveryHelper::ensure_successful_delivery( &Default::default(), &versioned_loc.clone().try_into().unwrap(), FeeReason::ChargeFees, ); - }: _(RawOrigin::Root, Box::new(versioned_loc)) + #[extrinsic_call] + _(RawOrigin::Root, Box::new(versioned_loc)); + + Ok(()) + } - force_unsubscribe_version_notify { - let loc = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)), - )?; + #[benchmark] + fn force_unsubscribe_version_notify() -> Result<(), BenchmarkError> { + let loc = T::reachable_dest() + .ok_or(BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let versioned_loc: VersionedLocation = loc.clone().into(); - // Ensure that origin can send to destination (e.g. setup delivery fees, ensure router setup, ...) + // Ensure that origin can send to destination + // (e.g. setup delivery fees, ensure router setup, ...) T::DeliveryHelper::ensure_successful_delivery( &Default::default(), &versioned_loc.clone().try_into().unwrap(), @@ -297,123 +375,227 @@ benchmarks! { ); let _ = crate::Pallet::::request_version_notify(loc); - }: _(RawOrigin::Root, Box::new(versioned_loc)) - force_suspension {}: _(RawOrigin::Root, true) + #[extrinsic_call] + _(RawOrigin::Root, Box::new(versioned_loc)); + + Ok(()) + } - migrate_supported_version { + #[benchmark] + fn force_suspension() { + #[extrinsic_call] + _(RawOrigin::Root, true) + } + + #[benchmark] + fn migrate_supported_version() { let old_version = XCM_VERSION - 1; let loc = VersionedLocation::from(Location::from(Parent)); SupportedVersion::::insert(old_version, loc, old_version); - }: { - crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateSupportedVersion, Weight::zero()); + + #[block] + { + crate::Pallet::::check_xcm_version_change( + VersionMigrationStage::MigrateSupportedVersion, + Weight::zero(), + ); + } } - migrate_version_notifiers { + #[benchmark] + fn migrate_version_notifiers() { let old_version = XCM_VERSION - 1; let loc = VersionedLocation::from(Location::from(Parent)); VersionNotifiers::::insert(old_version, loc, 0); - }: { - crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateVersionNotifiers, Weight::zero()); + + #[block] + { + crate::Pallet::::check_xcm_version_change( + VersionMigrationStage::MigrateVersionNotifiers, + Weight::zero(), + ); + } } - already_notified_target { - let loc = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads(1))), - )?; + #[benchmark] + fn already_notified_target() -> Result<(), BenchmarkError> { + let loc = T::reachable_dest().ok_or(BenchmarkError::Override( + BenchmarkResult::from_weight(T::DbWeight::get().reads(1)), + ))?; let loc = VersionedLocation::from(loc); let current_version = T::AdvertisedXcmVersion::get(); - VersionNotifyTargets::::insert(current_version, loc, (0, Weight::zero(), current_version)); - }: { - crate::Pallet::::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero()); + VersionNotifyTargets::::insert( + current_version, + loc, + (0, Weight::zero(), current_version), + ); + + #[block] + { + crate::Pallet::::check_xcm_version_change( + VersionMigrationStage::NotifyCurrentTargets(None), + Weight::zero(), + ); + } + + Ok(()) } - notify_current_targets { - let loc = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))), - )?; + #[benchmark] + fn notify_current_targets() -> Result<(), BenchmarkError> { + let loc = T::reachable_dest().ok_or(BenchmarkError::Override( + BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3)), + ))?; let loc = VersionedLocation::from(loc); let current_version = T::AdvertisedXcmVersion::get(); let old_version = current_version - 1; VersionNotifyTargets::::insert(current_version, loc, (0, Weight::zero(), old_version)); - }: { - crate::Pallet::::check_xcm_version_change(VersionMigrationStage::NotifyCurrentTargets(None), Weight::zero()); + + #[block] + { + crate::Pallet::::check_xcm_version_change( + VersionMigrationStage::NotifyCurrentTargets(None), + Weight::zero(), + ); + } + + Ok(()) } - notify_target_migration_fail { + #[benchmark] + fn notify_target_migration_fail() { let newer_xcm_version = xcm::prelude::XCM_VERSION; let older_xcm_version = newer_xcm_version - 1; - let bad_location: Location = Plurality { - id: BodyId::Unit, - part: BodyPart::Voice, - }.into(); + let bad_location: Location = Plurality { id: BodyId::Unit, part: BodyPart::Voice }.into(); let bad_location = VersionedLocation::from(bad_location) .into_version(older_xcm_version) .expect("Version convertion should work"); let current_version = T::AdvertisedXcmVersion::get(); - VersionNotifyTargets::::insert(current_version, bad_location, (0, Weight::zero(), current_version)); - }: { - crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + VersionNotifyTargets::::insert( + current_version, + bad_location, + (0, Weight::zero(), current_version), + ); + + #[block] + { + crate::Pallet::::check_xcm_version_change( + VersionMigrationStage::MigrateAndNotifyOldTargets, + Weight::zero(), + ); + } } - migrate_version_notify_targets { + #[benchmark] + fn migrate_version_notify_targets() { let current_version = T::AdvertisedXcmVersion::get(); let old_version = current_version - 1; let loc = VersionedLocation::from(Location::from(Parent)); VersionNotifyTargets::::insert(old_version, loc, (0, Weight::zero(), current_version)); - }: { - crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + + #[block] + { + crate::Pallet::::check_xcm_version_change( + VersionMigrationStage::MigrateAndNotifyOldTargets, + Weight::zero(), + ); + } } - migrate_and_notify_old_targets { - let loc = T::reachable_dest().ok_or( - BenchmarkError::Override(BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3))), - )?; + #[benchmark] + fn migrate_and_notify_old_targets() -> Result<(), BenchmarkError> { + let loc = T::reachable_dest().ok_or(BenchmarkError::Override( + BenchmarkResult::from_weight(T::DbWeight::get().reads_writes(1, 3)), + ))?; let loc = VersionedLocation::from(loc); let old_version = T::AdvertisedXcmVersion::get() - 1; VersionNotifyTargets::::insert(old_version, loc, (0, Weight::zero(), old_version)); - }: { - crate::Pallet::::check_xcm_version_change(VersionMigrationStage::MigrateAndNotifyOldTargets, Weight::zero()); + + #[block] + { + crate::Pallet::::check_xcm_version_change( + VersionMigrationStage::MigrateAndNotifyOldTargets, + Weight::zero(), + ); + } + + Ok(()) } - new_query { + #[benchmark] + fn new_query() { let responder = Location::from(Parent); let timeout = 1u32.into(); let match_querier = Location::from(Here); - }: { - crate::Pallet::::new_query(responder, timeout, match_querier); + + #[block] + { + crate::Pallet::::new_query(responder, timeout, match_querier); + } } - take_response { + #[benchmark] + fn take_response() { let responder = Location::from(Parent); let timeout = 1u32.into(); let match_querier = Location::from(Here); let query_id = crate::Pallet::::new_query(responder, timeout, match_querier); - let infos = (0 .. xcm::v3::MaxPalletsInfo::get()).map(|_| PalletInfo::new( - u32::MAX, - (0..xcm::v3::MaxPalletNameLen::get()).map(|_| 97u8).collect::>().try_into().unwrap(), - (0..xcm::v3::MaxPalletNameLen::get()).map(|_| 97u8).collect::>().try_into().unwrap(), - u32::MAX, - u32::MAX, - u32::MAX, - ).unwrap()).collect::>(); - crate::Pallet::::expect_response(query_id, Response::PalletsInfo(infos.try_into().unwrap())); - }: { - as QueryHandler>::take_response(query_id); + let infos = (0..xcm::v3::MaxPalletsInfo::get()) + .map(|_| { + PalletInfo::new( + u32::MAX, + (0..xcm::v3::MaxPalletNameLen::get()) + .map(|_| 97u8) + .collect::>() + .try_into() + .unwrap(), + (0..xcm::v3::MaxPalletNameLen::get()) + .map(|_| 97u8) + .collect::>() + .try_into() + .unwrap(), + u32::MAX, + u32::MAX, + u32::MAX, + ) + .unwrap() + }) + .collect::>(); + crate::Pallet::::expect_response( + query_id, + Response::PalletsInfo(infos.try_into().unwrap()), + ); + + #[block] + { + as QueryHandler>::take_response(query_id); + } } - claim_assets { + #[benchmark] + fn claim_assets() -> Result<(), BenchmarkError> { let claim_origin = RawOrigin::Signed(whitelisted_caller()); - let claim_location = T::ExecuteXcmOrigin::try_origin(claim_origin.clone().into()).map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; + let claim_location = T::ExecuteXcmOrigin::try_origin(claim_origin.clone().into()) + .map_err(|_| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?; let asset: Asset = T::get_asset(); // Trap assets for claiming later crate::Pallet::::drop_assets( &claim_location, asset.clone().into(), - &XcmContext { origin: None, message_id: [0u8; 32], topic: None } + &XcmContext { origin: None, message_id: [0u8; 32], topic: None }, ); let versioned_assets = VersionedAssets::from(Assets::from(asset)); - }: _>(claim_origin.into(), Box::new(versioned_assets), Box::new(VersionedLocation::from(claim_location))) + + #[extrinsic_call] + _( + claim_origin, + Box::new(versioned_assets), + Box::new(VersionedLocation::from(claim_location)), + ); + + Ok(()) + } impl_benchmark_test_suite!( Pallet, From d8df46c7a1488f2358e69368813fd772164c4dac Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Wed, 18 Dec 2024 10:59:55 +0100 Subject: [PATCH 181/340] Improve pallet claims file structure (#6779) Linked to issue #590. I moved the mod, tests, mock and benchmarking to their own seperate file to reduce the bloat inside claims.rs --------- Co-authored-by: Guillaume Thiolliere --- polkadot/runtime/common/src/claims.rs | 1758 ----------------- .../runtime/common/src/claims/benchmarking.rs | 318 +++ polkadot/runtime/common/src/claims/mock.rs | 129 ++ polkadot/runtime/common/src/claims/mod.rs | 723 +++++++ polkadot/runtime/common/src/claims/tests.rs | 666 +++++++ 5 files changed, 1836 insertions(+), 1758 deletions(-) delete mode 100644 polkadot/runtime/common/src/claims.rs create mode 100644 polkadot/runtime/common/src/claims/benchmarking.rs create mode 100644 polkadot/runtime/common/src/claims/mock.rs create mode 100644 polkadot/runtime/common/src/claims/mod.rs create mode 100644 polkadot/runtime/common/src/claims/tests.rs diff --git a/polkadot/runtime/common/src/claims.rs b/polkadot/runtime/common/src/claims.rs deleted file mode 100644 index 1ee80dd76e2d..000000000000 --- a/polkadot/runtime/common/src/claims.rs +++ /dev/null @@ -1,1758 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Pallet to process claims from Ethereum addresses. - -#[cfg(not(feature = "std"))] -use alloc::{format, string::String}; -use alloc::{vec, vec::Vec}; -use codec::{Decode, Encode, MaxEncodedLen}; -use core::fmt::Debug; -use frame_support::{ - ensure, - traits::{Currency, Get, IsSubType, VestingSchedule}, - weights::Weight, - DefaultNoBound, -}; -pub use pallet::*; -use polkadot_primitives::ValidityError; -use scale_info::TypeInfo; -use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; -use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; -use sp_runtime::{ - impl_tx_ext_default, - traits::{ - AsSystemOriginSigner, AsTransactionAuthorizedOrigin, CheckedSub, DispatchInfoOf, - Dispatchable, TransactionExtension, Zero, - }, - transaction_validity::{ - InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, - ValidTransaction, - }, - RuntimeDebug, -}; - -type CurrencyOf = <::VestingSchedule as VestingSchedule< - ::AccountId, ->>::Currency; -type BalanceOf = as Currency<::AccountId>>::Balance; - -pub trait WeightInfo { - fn claim() -> Weight; - fn mint_claim() -> Weight; - fn claim_attest() -> Weight; - fn attest() -> Weight; - fn move_claim() -> Weight; - fn prevalidate_attests() -> Weight; -} - -pub struct TestWeightInfo; -impl WeightInfo for TestWeightInfo { - fn claim() -> Weight { - Weight::zero() - } - fn mint_claim() -> Weight { - Weight::zero() - } - fn claim_attest() -> Weight { - Weight::zero() - } - fn attest() -> Weight { - Weight::zero() - } - fn move_claim() -> Weight { - Weight::zero() - } - fn prevalidate_attests() -> Weight { - Weight::zero() - } -} - -/// The kind of statement an account needs to make for a claim to be valid. -#[derive( - Encode, - Decode, - Clone, - Copy, - Eq, - PartialEq, - RuntimeDebug, - TypeInfo, - Serialize, - Deserialize, - MaxEncodedLen, -)] -pub enum StatementKind { - /// Statement required to be made by non-SAFT holders. - Regular, - /// Statement required to be made by SAFT holders. - Saft, -} - -impl StatementKind { - /// Convert this to the (English) statement it represents. - fn to_text(self) -> &'static [u8] { - match self { - StatementKind::Regular => - &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \ - Qmc1XYqT6S39WNp2UeiRUrZichUWUPpGEThDE6dAb3f6Ny. (This may be found at the URL: \ - https://statement.polkadot.network/regular.html)"[..], - StatementKind::Saft => - &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \ - QmXEkMahfhHJPzT3RjkXiZVFi77ZeVeuxtAjhojGRNYckz. (This may be found at the URL: \ - https://statement.polkadot.network/saft.html)"[..], - } - } -} - -impl Default for StatementKind { - fn default() -> Self { - StatementKind::Regular - } -} - -/// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account). -/// -/// This gets serialized to the 0x-prefixed hex representation. -#[derive( - Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, -)] -pub struct EthereumAddress([u8; 20]); - -impl Serialize for EthereumAddress { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]); - serializer.serialize_str(&format!("0x{}", hex)) - } -} - -impl<'de> Deserialize<'de> for EthereumAddress { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let base_string = String::deserialize(deserializer)?; - let offset = if base_string.starts_with("0x") { 2 } else { 0 }; - let s = &base_string[offset..]; - if s.len() != 40 { - Err(serde::de::Error::custom( - "Bad length of Ethereum address (should be 42 including '0x')", - ))?; - } - let raw: Vec = rustc_hex::FromHex::from_hex(s) - .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; - let mut r = Self::default(); - r.0.copy_from_slice(&raw); - Ok(r) - } -} - -#[derive(Encode, Decode, Clone, TypeInfo, MaxEncodedLen)] -pub struct EcdsaSignature(pub [u8; 65]); - -impl PartialEq for EcdsaSignature { - fn eq(&self, other: &Self) -> bool { - &self.0[..] == &other.0[..] - } -} - -impl core::fmt::Debug for EcdsaSignature { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "EcdsaSignature({:?})", &self.0[..]) - } -} - -#[frame_support::pallet] -pub mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - - #[pallet::pallet] - pub struct Pallet(_); - - /// Configuration trait. - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - type VestingSchedule: VestingSchedule>; - #[pallet::constant] - type Prefix: Get<&'static [u8]>; - type MoveClaimOrigin: EnsureOrigin; - type WeightInfo: WeightInfo; - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// Someone claimed some DOTs. - Claimed { who: T::AccountId, ethereum_address: EthereumAddress, amount: BalanceOf }, - } - - #[pallet::error] - pub enum Error { - /// Invalid Ethereum signature. - InvalidEthereumSignature, - /// Ethereum address has no claim. - SignerHasNoClaim, - /// Account ID sending transaction has no claim. - SenderHasNoClaim, - /// There's not enough in the pot to pay out some unvested amount. Generally implies a - /// logic error. - PotUnderflow, - /// A needed statement was not included. - InvalidStatement, - /// The account already has a vested balance. - VestedBalanceExists, - } - - #[pallet::storage] - pub type Claims = StorageMap<_, Identity, EthereumAddress, BalanceOf>; - - #[pallet::storage] - pub type Total = StorageValue<_, BalanceOf, ValueQuery>; - - /// Vesting schedule for a claim. - /// First balance is the total amount that should be held for vesting. - /// Second balance is how much should be unlocked per block. - /// The block number is when the vesting should start. - #[pallet::storage] - pub type Vesting = - StorageMap<_, Identity, EthereumAddress, (BalanceOf, BalanceOf, BlockNumberFor)>; - - /// The statement kind that must be signed, if any. - #[pallet::storage] - pub(super) type Signing = StorageMap<_, Identity, EthereumAddress, StatementKind>; - - /// Pre-claimed Ethereum accounts, by the Account ID that they are claimed to. - #[pallet::storage] - pub(super) type Preclaims = StorageMap<_, Identity, T::AccountId, EthereumAddress>; - - #[pallet::genesis_config] - #[derive(DefaultNoBound)] - pub struct GenesisConfig { - pub claims: - Vec<(EthereumAddress, BalanceOf, Option, Option)>, - pub vesting: Vec<(EthereumAddress, (BalanceOf, BalanceOf, BlockNumberFor))>, - } - - #[pallet::genesis_build] - impl BuildGenesisConfig for GenesisConfig { - fn build(&self) { - // build `Claims` - self.claims.iter().map(|(a, b, _, _)| (*a, *b)).for_each(|(a, b)| { - Claims::::insert(a, b); - }); - // build `Total` - Total::::put( - self.claims - .iter() - .fold(Zero::zero(), |acc: BalanceOf, &(_, b, _, _)| acc + b), - ); - // build `Vesting` - self.vesting.iter().for_each(|(k, v)| { - Vesting::::insert(k, v); - }); - // build `Signing` - self.claims - .iter() - .filter_map(|(a, _, _, s)| Some((*a, (*s)?))) - .for_each(|(a, s)| { - Signing::::insert(a, s); - }); - // build `Preclaims` - self.claims.iter().filter_map(|(a, _, i, _)| Some((i.clone()?, *a))).for_each( - |(i, a)| { - Preclaims::::insert(i, a); - }, - ); - } - } - - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::call] - impl Pallet { - /// Make a claim to collect your DOTs. - /// - /// The dispatch origin for this call must be _None_. - /// - /// Unsigned Validation: - /// A call to claim is deemed valid if the signature provided matches - /// the expected signed message of: - /// - /// > Ethereum Signed Message: - /// > (configured prefix string)(address) - /// - /// and `address` matches the `dest` account. - /// - /// Parameters: - /// - `dest`: The destination account to payout the claim. - /// - `ethereum_signature`: The signature of an ethereum signed message matching the format - /// described above. - /// - /// - /// The weight of this call is invariant over the input parameters. - /// Weight includes logic to validate unsigned `claim` call. - /// - /// Total Complexity: O(1) - /// - #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::claim())] - pub fn claim( - origin: OriginFor, - dest: T::AccountId, - ethereum_signature: EcdsaSignature, - ) -> DispatchResult { - ensure_none(origin)?; - - let data = dest.using_encoded(to_ascii_hex); - let signer = Self::eth_recover(ðereum_signature, &data, &[][..]) - .ok_or(Error::::InvalidEthereumSignature)?; - ensure!(Signing::::get(&signer).is_none(), Error::::InvalidStatement); - - Self::process_claim(signer, dest)?; - Ok(()) - } - - /// Mint a new claim to collect DOTs. - /// - /// The dispatch origin for this call must be _Root_. - /// - /// Parameters: - /// - `who`: The Ethereum address allowed to collect this claim. - /// - `value`: The number of DOTs that will be claimed. - /// - `vesting_schedule`: An optional vesting schedule for these DOTs. - /// - /// - /// The weight of this call is invariant over the input parameters. - /// We assume worst case that both vesting and statement is being inserted. - /// - /// Total Complexity: O(1) - /// - #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::mint_claim())] - pub fn mint_claim( - origin: OriginFor, - who: EthereumAddress, - value: BalanceOf, - vesting_schedule: Option<(BalanceOf, BalanceOf, BlockNumberFor)>, - statement: Option, - ) -> DispatchResult { - ensure_root(origin)?; - - Total::::mutate(|t| *t += value); - Claims::::insert(who, value); - if let Some(vs) = vesting_schedule { - Vesting::::insert(who, vs); - } - if let Some(s) = statement { - Signing::::insert(who, s); - } - Ok(()) - } - - /// Make a claim to collect your DOTs by signing a statement. - /// - /// The dispatch origin for this call must be _None_. - /// - /// Unsigned Validation: - /// A call to `claim_attest` is deemed valid if the signature provided matches - /// the expected signed message of: - /// - /// > Ethereum Signed Message: - /// > (configured prefix string)(address)(statement) - /// - /// and `address` matches the `dest` account; the `statement` must match that which is - /// expected according to your purchase arrangement. - /// - /// Parameters: - /// - `dest`: The destination account to payout the claim. - /// - `ethereum_signature`: The signature of an ethereum signed message matching the format - /// described above. - /// - `statement`: The identity of the statement which is being attested to in the - /// signature. - /// - /// - /// The weight of this call is invariant over the input parameters. - /// Weight includes logic to validate unsigned `claim_attest` call. - /// - /// Total Complexity: O(1) - /// - #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::claim_attest())] - pub fn claim_attest( - origin: OriginFor, - dest: T::AccountId, - ethereum_signature: EcdsaSignature, - statement: Vec, - ) -> DispatchResult { - ensure_none(origin)?; - - let data = dest.using_encoded(to_ascii_hex); - let signer = Self::eth_recover(ðereum_signature, &data, &statement) - .ok_or(Error::::InvalidEthereumSignature)?; - if let Some(s) = Signing::::get(signer) { - ensure!(s.to_text() == &statement[..], Error::::InvalidStatement); - } - Self::process_claim(signer, dest)?; - Ok(()) - } - - /// Attest to a statement, needed to finalize the claims process. - /// - /// WARNING: Insecure unless your chain includes `PrevalidateAttests` as a - /// `TransactionExtension`. - /// - /// Unsigned Validation: - /// A call to attest is deemed valid if the sender has a `Preclaim` registered - /// and provides a `statement` which is expected for the account. - /// - /// Parameters: - /// - `statement`: The identity of the statement which is being attested to in the - /// signature. - /// - /// - /// The weight of this call is invariant over the input parameters. - /// Weight includes logic to do pre-validation on `attest` call. - /// - /// Total Complexity: O(1) - /// - #[pallet::call_index(3)] - #[pallet::weight(( - T::WeightInfo::attest(), - DispatchClass::Normal, - Pays::No - ))] - pub fn attest(origin: OriginFor, statement: Vec) -> DispatchResult { - let who = ensure_signed(origin)?; - let signer = Preclaims::::get(&who).ok_or(Error::::SenderHasNoClaim)?; - if let Some(s) = Signing::::get(signer) { - ensure!(s.to_text() == &statement[..], Error::::InvalidStatement); - } - Self::process_claim(signer, who.clone())?; - Preclaims::::remove(&who); - Ok(()) - } - - #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::move_claim())] - pub fn move_claim( - origin: OriginFor, - old: EthereumAddress, - new: EthereumAddress, - maybe_preclaim: Option, - ) -> DispatchResultWithPostInfo { - T::MoveClaimOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?; - - Claims::::take(&old).map(|c| Claims::::insert(&new, c)); - Vesting::::take(&old).map(|c| Vesting::::insert(&new, c)); - Signing::::take(&old).map(|c| Signing::::insert(&new, c)); - maybe_preclaim.map(|preclaim| { - Preclaims::::mutate(&preclaim, |maybe_o| { - if maybe_o.as_ref().map_or(false, |o| o == &old) { - *maybe_o = Some(new) - } - }) - }); - Ok(Pays::No.into()) - } - } - - #[pallet::validate_unsigned] - impl ValidateUnsigned for Pallet { - type Call = Call; - - fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { - const PRIORITY: u64 = 100; - - let (maybe_signer, maybe_statement) = match call { - // - // The weight of this logic is included in the `claim` dispatchable. - // - Call::claim { dest: account, ethereum_signature } => { - let data = account.using_encoded(to_ascii_hex); - (Self::eth_recover(ðereum_signature, &data, &[][..]), None) - }, - // - // The weight of this logic is included in the `claim_attest` dispatchable. - // - Call::claim_attest { dest: account, ethereum_signature, statement } => { - let data = account.using_encoded(to_ascii_hex); - ( - Self::eth_recover(ðereum_signature, &data, &statement), - Some(statement.as_slice()), - ) - }, - _ => return Err(InvalidTransaction::Call.into()), - }; - - let signer = maybe_signer.ok_or(InvalidTransaction::Custom( - ValidityError::InvalidEthereumSignature.into(), - ))?; - - let e = InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()); - ensure!(Claims::::contains_key(&signer), e); - - let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); - match Signing::::get(signer) { - None => ensure!(maybe_statement.is_none(), e), - Some(s) => ensure!(Some(s.to_text()) == maybe_statement, e), - } - - Ok(ValidTransaction { - priority: PRIORITY, - requires: vec![], - provides: vec![("claims", signer).encode()], - longevity: TransactionLongevity::max_value(), - propagate: true, - }) - } - } -} - -/// Converts the given binary data into ASCII-encoded hex. It will be twice the length. -fn to_ascii_hex(data: &[u8]) -> Vec { - let mut r = Vec::with_capacity(data.len() * 2); - let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n }); - for &b in data.iter() { - push_nibble(b / 16); - push_nibble(b % 16); - } - r -} - -impl Pallet { - // Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign. - fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec { - let prefix = T::Prefix::get(); - let mut l = prefix.len() + what.len() + extra.len(); - let mut rev = Vec::new(); - while l > 0 { - rev.push(b'0' + (l % 10) as u8); - l /= 10; - } - let mut v = b"\x19Ethereum Signed Message:\n".to_vec(); - v.extend(rev.into_iter().rev()); - v.extend_from_slice(prefix); - v.extend_from_slice(what); - v.extend_from_slice(extra); - v - } - - // Attempts to recover the Ethereum address from a message signature signed by using - // the Ethereum RPC's `personal_sign` and `eth_sign`. - fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option { - let msg = keccak_256(&Self::ethereum_signable_message(what, extra)); - let mut res = EthereumAddress::default(); - res.0 - .copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]); - Some(res) - } - - fn process_claim(signer: EthereumAddress, dest: T::AccountId) -> sp_runtime::DispatchResult { - let balance_due = Claims::::get(&signer).ok_or(Error::::SignerHasNoClaim)?; - - let new_total = - Total::::get().checked_sub(&balance_due).ok_or(Error::::PotUnderflow)?; - - let vesting = Vesting::::get(&signer); - if vesting.is_some() && T::VestingSchedule::vesting_balance(&dest).is_some() { - return Err(Error::::VestedBalanceExists.into()) - } - - // We first need to deposit the balance to ensure that the account exists. - let _ = CurrencyOf::::deposit_creating(&dest, balance_due); - - // Check if this claim should have a vesting schedule. - if let Some(vs) = vesting { - // This can only fail if the account already has a vesting schedule, - // but this is checked above. - T::VestingSchedule::add_vesting_schedule(&dest, vs.0, vs.1, vs.2) - .expect("No other vesting schedule exists, as checked above; qed"); - } - - Total::::put(new_total); - Claims::::remove(&signer); - Vesting::::remove(&signer); - Signing::::remove(&signer); - - // Let's deposit an event to let the outside world know this happened. - Self::deposit_event(Event::::Claimed { - who: dest, - ethereum_address: signer, - amount: balance_due, - }); - - Ok(()) - } -} - -/// Validate `attest` calls prior to execution. Needed to avoid a DoS attack since they are -/// otherwise free to place on chain. -#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] -#[scale_info(skip_type_params(T))] -pub struct PrevalidateAttests(core::marker::PhantomData); - -impl Debug for PrevalidateAttests -where - ::RuntimeCall: IsSubType>, -{ - #[cfg(feature = "std")] - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "PrevalidateAttests") - } - - #[cfg(not(feature = "std"))] - fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result { - Ok(()) - } -} - -impl PrevalidateAttests -where - ::RuntimeCall: IsSubType>, -{ - /// Create new `TransactionExtension` to check runtime version. - pub fn new() -> Self { - Self(core::marker::PhantomData) - } -} - -impl TransactionExtension for PrevalidateAttests -where - ::RuntimeCall: IsSubType>, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: - AsSystemOriginSigner + AsTransactionAuthorizedOrigin + Clone, -{ - const IDENTIFIER: &'static str = "PrevalidateAttests"; - type Implicit = (); - type Pre = (); - type Val = (); - - fn weight(&self, call: &T::RuntimeCall) -> Weight { - if let Some(Call::attest { .. }) = call.is_sub_type() { - T::WeightInfo::prevalidate_attests() - } else { - Weight::zero() - } - } - - fn validate( - &self, - origin: ::RuntimeOrigin, - call: &T::RuntimeCall, - _info: &DispatchInfoOf, - _len: usize, - _self_implicit: Self::Implicit, - _inherited_implication: &impl Encode, - _source: TransactionSource, - ) -> Result< - (ValidTransaction, Self::Val, ::RuntimeOrigin), - TransactionValidityError, - > { - if let Some(Call::attest { statement: attested_statement }) = call.is_sub_type() { - let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?; - let signer = Preclaims::::get(who) - .ok_or(InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()))?; - if let Some(s) = Signing::::get(signer) { - let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); - ensure!(&attested_statement[..] == s.to_text(), e); - } - } - Ok((ValidTransaction::default(), (), origin)) - } - - impl_tx_ext_default!(T::RuntimeCall; prepare); -} - -#[cfg(any(test, feature = "runtime-benchmarks"))] -mod secp_utils { - use super::*; - - pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey { - libsecp256k1::PublicKey::from_secret_key(secret) - } - pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress { - let mut res = EthereumAddress::default(); - res.0.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]); - res - } - pub fn sig( - secret: &libsecp256k1::SecretKey, - what: &[u8], - extra: &[u8], - ) -> EcdsaSignature { - let msg = keccak_256(&super::Pallet::::ethereum_signable_message( - &to_ascii_hex(what)[..], - extra, - )); - let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret); - let mut r = [0u8; 65]; - r[0..64].copy_from_slice(&sig.serialize()[..]); - r[64] = recovery_id.serialize(); - EcdsaSignature(r) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use hex_literal::hex; - use secp_utils::*; - use sp_runtime::transaction_validity::TransactionSource::External; - - use codec::Encode; - // The testing primitives are very useful for avoiding having to work with signatures - // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. - use crate::claims; - use claims::Call as ClaimsCall; - use frame_support::{ - assert_err, assert_noop, assert_ok, derive_impl, - dispatch::{GetDispatchInfo, Pays}, - ord_parameter_types, parameter_types, - traits::{ExistenceRequirement, WithdrawReasons}, - }; - use pallet_balances; - use sp_runtime::{ - traits::{DispatchTransaction, Identity}, - transaction_validity::TransactionLongevity, - BuildStorage, - DispatchError::BadOrigin, - TokenError, - }; - - type Block = frame_system::mocking::MockBlock; - - frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Balances: pallet_balances, - Vesting: pallet_vesting, - Claims: claims, - } - ); - - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] - impl frame_system::Config for Test { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type AccountData = pallet_balances::AccountData; - type MaxConsumers = frame_support::traits::ConstU32<16>; - } - - #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] - impl pallet_balances::Config for Test { - type AccountStore = System; - } - - parameter_types! { - pub const MinVestedTransfer: u64 = 1; - pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = - WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); - } - - impl pallet_vesting::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type BlockNumberToBalance = Identity; - type MinVestedTransfer = MinVestedTransfer; - type WeightInfo = (); - type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; - type BlockNumberProvider = System; - const MAX_VESTING_SCHEDULES: u32 = 28; - } - - parameter_types! { - pub Prefix: &'static [u8] = b"Pay RUSTs to the TEST account:"; - } - ord_parameter_types! { - pub const Six: u64 = 6; - } - - impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type VestingSchedule = Vesting; - type Prefix = Prefix; - type MoveClaimOrigin = frame_system::EnsureSignedBy; - type WeightInfo = TestWeightInfo; - } - - fn alice() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() - } - fn bob() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&keccak_256(b"Bob")).unwrap() - } - fn dave() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&keccak_256(b"Dave")).unwrap() - } - fn eve() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&keccak_256(b"Eve")).unwrap() - } - fn frank() -> libsecp256k1::SecretKey { - libsecp256k1::SecretKey::parse(&keccak_256(b"Frank")).unwrap() - } - - // This function basically just builds a genesis storage key/value store according to - // our desired mockup. - pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - // We use default for brevity, but you can configure as desired if needed. - pallet_balances::GenesisConfig::::default() - .assimilate_storage(&mut t) - .unwrap(); - claims::GenesisConfig:: { - claims: vec![ - (eth(&alice()), 100, None, None), - (eth(&dave()), 200, None, Some(StatementKind::Regular)), - (eth(&eve()), 300, Some(42), Some(StatementKind::Saft)), - (eth(&frank()), 400, Some(43), None), - ], - vesting: vec![(eth(&alice()), (50, 10, 1))], - } - .assimilate_storage(&mut t) - .unwrap(); - t.into() - } - - fn total_claims() -> u64 { - 100 + 200 + 300 + 400 - } - - #[test] - fn basic_setup_works() { - new_test_ext().execute_with(|| { - assert_eq!(claims::Total::::get(), total_claims()); - assert_eq!(claims::Claims::::get(ð(&alice())), Some(100)); - assert_eq!(claims::Claims::::get(ð(&dave())), Some(200)); - assert_eq!(claims::Claims::::get(ð(&eve())), Some(300)); - assert_eq!(claims::Claims::::get(ð(&frank())), Some(400)); - assert_eq!(claims::Claims::::get(&EthereumAddress::default()), None); - assert_eq!(claims::Vesting::::get(ð(&alice())), Some((50, 10, 1))); - }); - } - - #[test] - fn serde_works() { - let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]); - let y = serde_json::to_string(&x).unwrap(); - assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\""); - let z: EthereumAddress = serde_json::from_str(&y).unwrap(); - assert_eq!(x, z); - } - - #[test] - fn claiming_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&alice(), &42u64.encode(), &[][..]) - )); - assert_eq!(Balances::free_balance(&42), 100); - assert_eq!(Vesting::vesting_balance(&42), Some(50)); - assert_eq!(claims::Total::::get(), total_claims() - 100); - }); - } - - #[test] - fn basic_claim_moving_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::move_claim(RuntimeOrigin::signed(1), eth(&alice()), eth(&bob()), None), - BadOrigin - ); - assert_ok!(Claims::move_claim( - RuntimeOrigin::signed(6), - eth(&alice()), - eth(&bob()), - None - )); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&alice(), &42u64.encode(), &[][..]) - ), - Error::::SignerHasNoClaim - ); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&bob(), &42u64.encode(), &[][..]) - )); - assert_eq!(Balances::free_balance(&42), 100); - assert_eq!(Vesting::vesting_balance(&42), Some(50)); - assert_eq!(claims::Total::::get(), total_claims() - 100); - }); - } - - #[test] - fn claim_attest_moving_works() { - new_test_ext().execute_with(|| { - assert_ok!(Claims::move_claim( - RuntimeOrigin::signed(6), - eth(&dave()), - eth(&bob()), - None - )); - let s = sig::(&bob(), &42u64.encode(), StatementKind::Regular.to_text()); - assert_ok!(Claims::claim_attest( - RuntimeOrigin::none(), - 42, - s, - StatementKind::Regular.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&42), 200); - }); - } - - #[test] - fn attest_moving_works() { - new_test_ext().execute_with(|| { - assert_ok!(Claims::move_claim( - RuntimeOrigin::signed(6), - eth(&eve()), - eth(&bob()), - Some(42) - )); - assert_ok!(Claims::attest( - RuntimeOrigin::signed(42), - StatementKind::Saft.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&42), 300); - }); - } - - #[test] - fn claiming_does_not_bypass_signing() { - new_test_ext().execute_with(|| { - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&alice(), &42u64.encode(), &[][..]) - )); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&dave(), &42u64.encode(), &[][..]) - ), - Error::::InvalidStatement, - ); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&eve(), &42u64.encode(), &[][..]) - ), - Error::::InvalidStatement, - ); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&frank(), &42u64.encode(), &[][..]) - )); - }); - } - - #[test] - fn attest_claiming_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - let s = sig::(&dave(), &42u64.encode(), StatementKind::Saft.to_text()); - let r = Claims::claim_attest( - RuntimeOrigin::none(), - 42, - s.clone(), - StatementKind::Saft.to_text().to_vec(), - ); - assert_noop!(r, Error::::InvalidStatement); - - let r = Claims::claim_attest( - RuntimeOrigin::none(), - 42, - s, - StatementKind::Regular.to_text().to_vec(), - ); - assert_noop!(r, Error::::SignerHasNoClaim); - // ^^^ we use ecdsa_recover, so an invalid signature just results in a random signer id - // being recovered, which realistically will never have a claim. - - let s = sig::(&dave(), &42u64.encode(), StatementKind::Regular.to_text()); - assert_ok!(Claims::claim_attest( - RuntimeOrigin::none(), - 42, - s, - StatementKind::Regular.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&42), 200); - assert_eq!(claims::Total::::get(), total_claims() - 200); - - let s = sig::(&dave(), &42u64.encode(), StatementKind::Regular.to_text()); - let r = Claims::claim_attest( - RuntimeOrigin::none(), - 42, - s, - StatementKind::Regular.to_text().to_vec(), - ); - assert_noop!(r, Error::::SignerHasNoClaim); - }); - } - - #[test] - fn attesting_works() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::attest(RuntimeOrigin::signed(69), StatementKind::Saft.to_text().to_vec()), - Error::::SenderHasNoClaim - ); - assert_noop!( - Claims::attest( - RuntimeOrigin::signed(42), - StatementKind::Regular.to_text().to_vec() - ), - Error::::InvalidStatement - ); - assert_ok!(Claims::attest( - RuntimeOrigin::signed(42), - StatementKind::Saft.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&42), 300); - assert_eq!(claims::Total::::get(), total_claims() - 300); - }); - } - - #[test] - fn claim_cannot_clobber_preclaim() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - // Alice's claim is 100 - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&alice(), &42u64.encode(), &[][..]) - )); - assert_eq!(Balances::free_balance(&42), 100); - // Eve's claim is 300 through Account 42 - assert_ok!(Claims::attest( - RuntimeOrigin::signed(42), - StatementKind::Saft.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&42), 100 + 300); - assert_eq!(claims::Total::::get(), total_claims() - 400); - }); - } - - #[test] - fn valid_attest_transactions_are_free() { - new_test_ext().execute_with(|| { - let p = PrevalidateAttests::::new(); - let c = RuntimeCall::Claims(ClaimsCall::attest { - statement: StatementKind::Saft.to_text().to_vec(), - }); - let di = c.get_dispatch_info(); - assert_eq!(di.pays_fee, Pays::No); - let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0); - assert_eq!(r.unwrap().0, ValidTransaction::default()); - }); - } - - #[test] - fn invalid_attest_transactions_are_recognized() { - new_test_ext().execute_with(|| { - let p = PrevalidateAttests::::new(); - let c = RuntimeCall::Claims(ClaimsCall::attest { - statement: StatementKind::Regular.to_text().to_vec(), - }); - let di = c.get_dispatch_info(); - let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0); - assert!(r.is_err()); - let c = RuntimeCall::Claims(ClaimsCall::attest { - statement: StatementKind::Saft.to_text().to_vec(), - }); - let di = c.get_dispatch_info(); - let r = p.validate_only(Some(69).into(), &c, &di, 20, External, 0); - assert!(r.is_err()); - }); - } - - #[test] - fn cannot_bypass_attest_claiming() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - let s = sig::(&dave(), &42u64.encode(), &[]); - let r = Claims::claim(RuntimeOrigin::none(), 42, s.clone()); - assert_noop!(r, Error::::InvalidStatement); - }); - } - - #[test] - fn add_claim_works() { - new_test_ext().execute_with(|| { - assert_noop!( - Claims::mint_claim(RuntimeOrigin::signed(42), eth(&bob()), 200, None, None), - sp_runtime::traits::BadOrigin, - ); - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 69, - sig::(&bob(), &69u64.encode(), &[][..]) - ), - Error::::SignerHasNoClaim, - ); - assert_ok!(Claims::mint_claim(RuntimeOrigin::root(), eth(&bob()), 200, None, None)); - assert_eq!(claims::Total::::get(), total_claims() + 200); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 69, - sig::(&bob(), &69u64.encode(), &[][..]) - )); - assert_eq!(Balances::free_balance(&69), 200); - assert_eq!(Vesting::vesting_balance(&69), None); - assert_eq!(claims::Total::::get(), total_claims()); - }); - } - - #[test] - fn add_claim_with_vesting_works() { - new_test_ext().execute_with(|| { - assert_noop!( - Claims::mint_claim( - RuntimeOrigin::signed(42), - eth(&bob()), - 200, - Some((50, 10, 1)), - None - ), - sp_runtime::traits::BadOrigin, - ); - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 69, - sig::(&bob(), &69u64.encode(), &[][..]) - ), - Error::::SignerHasNoClaim, - ); - assert_ok!(Claims::mint_claim( - RuntimeOrigin::root(), - eth(&bob()), - 200, - Some((50, 10, 1)), - None - )); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 69, - sig::(&bob(), &69u64.encode(), &[][..]) - )); - assert_eq!(Balances::free_balance(&69), 200); - assert_eq!(Vesting::vesting_balance(&69), Some(50)); - - // Make sure we can not transfer the vested balance. - assert_err!( - >::transfer( - &69, - &80, - 180, - ExistenceRequirement::AllowDeath - ), - TokenError::Frozen, - ); - }); - } - - #[test] - fn add_claim_with_statement_works() { - new_test_ext().execute_with(|| { - assert_noop!( - Claims::mint_claim( - RuntimeOrigin::signed(42), - eth(&bob()), - 200, - None, - Some(StatementKind::Regular) - ), - sp_runtime::traits::BadOrigin, - ); - assert_eq!(Balances::free_balance(42), 0); - let signature = sig::(&bob(), &69u64.encode(), StatementKind::Regular.to_text()); - assert_noop!( - Claims::claim_attest( - RuntimeOrigin::none(), - 69, - signature.clone(), - StatementKind::Regular.to_text().to_vec() - ), - Error::::SignerHasNoClaim - ); - assert_ok!(Claims::mint_claim( - RuntimeOrigin::root(), - eth(&bob()), - 200, - None, - Some(StatementKind::Regular) - )); - assert_noop!( - Claims::claim_attest(RuntimeOrigin::none(), 69, signature.clone(), vec![],), - Error::::SignerHasNoClaim - ); - assert_ok!(Claims::claim_attest( - RuntimeOrigin::none(), - 69, - signature.clone(), - StatementKind::Regular.to_text().to_vec() - )); - assert_eq!(Balances::free_balance(&69), 200); - }); - } - - #[test] - fn origin_signed_claiming_fail() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_err!( - Claims::claim( - RuntimeOrigin::signed(42), - 42, - sig::(&alice(), &42u64.encode(), &[][..]) - ), - sp_runtime::traits::BadOrigin, - ); - }); - } - - #[test] - fn double_claiming_doesnt_work() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_ok!(Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&alice(), &42u64.encode(), &[][..]) - )); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&alice(), &42u64.encode(), &[][..]) - ), - Error::::SignerHasNoClaim - ); - }); - } - - #[test] - fn claiming_while_vested_doesnt_work() { - new_test_ext().execute_with(|| { - CurrencyOf::::make_free_balance_be(&69, total_claims()); - assert_eq!(Balances::free_balance(69), total_claims()); - // A user is already vested - assert_ok!(::VestingSchedule::add_vesting_schedule( - &69, - total_claims(), - 100, - 10 - )); - assert_ok!(Claims::mint_claim( - RuntimeOrigin::root(), - eth(&bob()), - 200, - Some((50, 10, 1)), - None - )); - // New total - assert_eq!(claims::Total::::get(), total_claims() + 200); - - // They should not be able to claim - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 69, - sig::(&bob(), &69u64.encode(), &[][..]) - ), - Error::::VestedBalanceExists, - ); - }); - } - - #[test] - fn non_sender_sig_doesnt_work() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&alice(), &69u64.encode(), &[][..]) - ), - Error::::SignerHasNoClaim - ); - }); - } - - #[test] - fn non_claimant_doesnt_work() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::free_balance(42), 0); - assert_noop!( - Claims::claim( - RuntimeOrigin::none(), - 42, - sig::(&bob(), &69u64.encode(), &[][..]) - ), - Error::::SignerHasNoClaim - ); - }); - } - - #[test] - fn real_eth_sig_works() { - new_test_ext().execute_with(|| { - // "Pay RUSTs to the TEST account:2a00000000000000" - let sig = hex!["444023e89b67e67c0562ed0305d252a5dd12b2af5ac51d6d3cb69a0b486bc4b3191401802dc29d26d586221f7256cd3329fe82174bdf659baea149a40e1c495d1c"]; - let sig = EcdsaSignature(sig); - let who = 42u64.using_encoded(to_ascii_hex); - let signer = Claims::eth_recover(&sig, &who, &[][..]).unwrap(); - assert_eq!(signer.0, hex!["6d31165d5d932d571f3b44695653b46dcc327e84"]); - }); - } - - #[test] - fn validate_unsigned_works() { - use sp_runtime::traits::ValidateUnsigned; - let source = sp_runtime::transaction_validity::TransactionSource::External; - - new_test_ext().execute_with(|| { - assert_eq!( - Pallet::::validate_unsigned( - source, - &ClaimsCall::claim { - dest: 1, - ethereum_signature: sig::(&alice(), &1u64.encode(), &[][..]) - } - ), - Ok(ValidTransaction { - priority: 100, - requires: vec![], - provides: vec![("claims", eth(&alice())).encode()], - longevity: TransactionLongevity::max_value(), - propagate: true, - }) - ); - assert_eq!( - Pallet::::validate_unsigned( - source, - &ClaimsCall::claim { dest: 0, ethereum_signature: EcdsaSignature([0; 65]) } - ), - InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(), - ); - assert_eq!( - Pallet::::validate_unsigned( - source, - &ClaimsCall::claim { - dest: 1, - ethereum_signature: sig::(&bob(), &1u64.encode(), &[][..]) - } - ), - InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), - ); - let s = sig::(&dave(), &1u64.encode(), StatementKind::Regular.to_text()); - let call = ClaimsCall::claim_attest { - dest: 1, - ethereum_signature: s, - statement: StatementKind::Regular.to_text().to_vec(), - }; - assert_eq!( - Pallet::::validate_unsigned(source, &call), - Ok(ValidTransaction { - priority: 100, - requires: vec![], - provides: vec![("claims", eth(&dave())).encode()], - longevity: TransactionLongevity::max_value(), - propagate: true, - }) - ); - assert_eq!( - Pallet::::validate_unsigned( - source, - &ClaimsCall::claim_attest { - dest: 1, - ethereum_signature: EcdsaSignature([0; 65]), - statement: StatementKind::Regular.to_text().to_vec() - } - ), - InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(), - ); - - let s = sig::(&bob(), &1u64.encode(), StatementKind::Regular.to_text()); - let call = ClaimsCall::claim_attest { - dest: 1, - ethereum_signature: s, - statement: StatementKind::Regular.to_text().to_vec(), - }; - assert_eq!( - Pallet::::validate_unsigned(source, &call), - InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), - ); - - let s = sig::(&dave(), &1u64.encode(), StatementKind::Saft.to_text()); - let call = ClaimsCall::claim_attest { - dest: 1, - ethereum_signature: s, - statement: StatementKind::Regular.to_text().to_vec(), - }; - assert_eq!( - Pallet::::validate_unsigned(source, &call), - InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), - ); - - let s = sig::(&dave(), &1u64.encode(), StatementKind::Saft.to_text()); - let call = ClaimsCall::claim_attest { - dest: 1, - ethereum_signature: s, - statement: StatementKind::Saft.to_text().to_vec(), - }; - assert_eq!( - Pallet::::validate_unsigned(source, &call), - InvalidTransaction::Custom(ValidityError::InvalidStatement.into()).into(), - ); - }); - } -} - -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking { - use super::*; - use crate::claims::Call; - use frame_benchmarking::v2::*; - use frame_support::{ - dispatch::{DispatchInfo, GetDispatchInfo}, - traits::UnfilteredDispatchable, - }; - use frame_system::RawOrigin; - use secp_utils::*; - use sp_runtime::{ - traits::{DispatchTransaction, ValidateUnsigned}, - DispatchResult, - }; - - const SEED: u32 = 0; - - const MAX_CLAIMS: u32 = 10_000; - const VALUE: u32 = 1_000_000; - - fn create_claim(input: u32) -> DispatchResult { - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap(); - let eth_address = eth(&secret_key); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - super::Pallet::::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - None, - )?; - Ok(()) - } - - fn create_claim_attest(input: u32) -> DispatchResult { - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap(); - let eth_address = eth(&secret_key); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - super::Pallet::::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - Some(Default::default()), - )?; - Ok(()) - } - - #[benchmarks( - where - ::RuntimeCall: IsSubType> + From>, - ::RuntimeCall: Dispatchable + GetDispatchInfo, - <::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner + AsTransactionAuthorizedOrigin + Clone, - <::RuntimeCall as Dispatchable>::PostInfo: Default, - )] - mod benchmarks { - use super::*; - - // Benchmark `claim` including `validate_unsigned` logic. - #[benchmark] - fn claim() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::(c)?; - create_claim_attest::(u32::MAX - c)?; - } - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&c.encode())).unwrap(); - let eth_address = eth(&secret_key); - let account: T::AccountId = account("user", c, SEED); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - let signature = sig::(&secret_key, &account.encode(), &[][..]); - super::Pallet::::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - None, - )?; - assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); - let source = sp_runtime::transaction_validity::TransactionSource::External; - let call_enc = - Call::::claim { dest: account.clone(), ethereum_signature: signature.clone() } - .encode(); - - #[block] - { - let call = as Decode>::decode(&mut &*call_enc) - .expect("call is encoded above, encoding must be correct"); - super::Pallet::::validate_unsigned(source, &call) - .map_err(|e| -> &'static str { e.into() })?; - call.dispatch_bypass_filter(RawOrigin::None.into())?; - } - - assert_eq!(Claims::::get(eth_address), None); - Ok(()) - } - - // Benchmark `mint_claim` when there already exists `c` claims in storage. - #[benchmark] - fn mint_claim() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::(c)?; - create_claim_attest::(u32::MAX - c)?; - } - let eth_address = account("eth_address", 0, SEED); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - let statement = StatementKind::Regular; - - #[extrinsic_call] - _(RawOrigin::Root, eth_address, VALUE.into(), vesting, Some(statement)); - - assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); - Ok(()) - } - - // Benchmark `claim_attest` including `validate_unsigned` logic. - #[benchmark] - fn claim_attest() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::(c)?; - create_claim_attest::(u32::MAX - c)?; - } - // Crate signature - let attest_c = u32::MAX - c; - let secret_key = - libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); - let eth_address = eth(&secret_key); - let account: T::AccountId = account("user", c, SEED); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - let statement = StatementKind::Regular; - let signature = sig::(&secret_key, &account.encode(), statement.to_text()); - super::Pallet::::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - Some(statement), - )?; - assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); - let call_enc = Call::::claim_attest { - dest: account.clone(), - ethereum_signature: signature.clone(), - statement: StatementKind::Regular.to_text().to_vec(), - } - .encode(); - let source = sp_runtime::transaction_validity::TransactionSource::External; - - #[block] - { - let call = as Decode>::decode(&mut &*call_enc) - .expect("call is encoded above, encoding must be correct"); - super::Pallet::::validate_unsigned(source, &call) - .map_err(|e| -> &'static str { e.into() })?; - call.dispatch_bypass_filter(RawOrigin::None.into())?; - } - - assert_eq!(Claims::::get(eth_address), None); - Ok(()) - } - - // Benchmark `attest` including prevalidate logic. - #[benchmark] - fn attest() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::(c)?; - create_claim_attest::(u32::MAX - c)?; - } - let attest_c = u32::MAX - c; - let secret_key = - libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); - let eth_address = eth(&secret_key); - let account: T::AccountId = account("user", c, SEED); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - let statement = StatementKind::Regular; - super::Pallet::::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - Some(statement), - )?; - Preclaims::::insert(&account, eth_address); - assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); - - let stmt = StatementKind::Regular.to_text().to_vec(); - - #[extrinsic_call] - _(RawOrigin::Signed(account), stmt); - - assert_eq!(Claims::::get(eth_address), None); - Ok(()) - } - - #[benchmark] - fn move_claim() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::(c)?; - create_claim_attest::(u32::MAX - c)?; - } - let attest_c = u32::MAX - c; - let secret_key = - libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); - let eth_address = eth(&secret_key); - - let new_secret_key = - libsecp256k1::SecretKey::parse(&keccak_256(&(u32::MAX / 2).encode())).unwrap(); - let new_eth_address = eth(&new_secret_key); - - let account: T::AccountId = account("user", c, SEED); - Preclaims::::insert(&account, eth_address); - - assert!(Claims::::contains_key(eth_address)); - assert!(!Claims::::contains_key(new_eth_address)); - - #[extrinsic_call] - _(RawOrigin::Root, eth_address, new_eth_address, Some(account)); - - assert!(!Claims::::contains_key(eth_address)); - assert!(Claims::::contains_key(new_eth_address)); - Ok(()) - } - - // Benchmark the time it takes to do `repeat` number of keccak256 hashes - #[benchmark(extra)] - fn keccak256(i: Linear<0, 10_000>) { - let bytes = (i).encode(); - - #[block] - { - for _ in 0..i { - let _hash = keccak_256(&bytes); - } - } - } - - // Benchmark the time it takes to do `repeat` number of `eth_recover` - #[benchmark(extra)] - fn eth_recover(i: Linear<0, 1_000>) { - // Crate signature - let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&i.encode())).unwrap(); - let account: T::AccountId = account("user", i, SEED); - let signature = sig::(&secret_key, &account.encode(), &[][..]); - let data = account.using_encoded(to_ascii_hex); - let extra = StatementKind::default().to_text(); - - #[block] - { - for _ in 0..i { - assert!(super::Pallet::::eth_recover(&signature, &data, extra).is_some()); - } - } - } - - #[benchmark] - fn prevalidate_attests() -> Result<(), BenchmarkError> { - let c = MAX_CLAIMS; - for _ in 0..c / 2 { - create_claim::(c)?; - create_claim_attest::(u32::MAX - c)?; - } - let ext = PrevalidateAttests::::new(); - let call = super::Call::attest { statement: StatementKind::Regular.to_text().to_vec() }; - let call: ::RuntimeCall = call.into(); - let info = call.get_dispatch_info(); - let attest_c = u32::MAX - c; - let secret_key = - libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); - let eth_address = eth(&secret_key); - let account: T::AccountId = account("user", c, SEED); - let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); - let statement = StatementKind::Regular; - super::Pallet::::mint_claim( - RawOrigin::Root.into(), - eth_address, - VALUE.into(), - vesting, - Some(statement), - )?; - Preclaims::::insert(&account, eth_address); - assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); - - #[block] - { - assert!(ext - .test_run(RawOrigin::Signed(account).into(), &call, &info, 0, 0, |_| { - Ok(Default::default()) - }) - .unwrap() - .is_ok()); - } - - Ok(()) - } - - impl_benchmark_test_suite!( - Pallet, - crate::claims::tests::new_test_ext(), - crate::claims::tests::Test, - ); - } -} diff --git a/polkadot/runtime/common/src/claims/benchmarking.rs b/polkadot/runtime/common/src/claims/benchmarking.rs new file mode 100644 index 000000000000..f9150f7980e5 --- /dev/null +++ b/polkadot/runtime/common/src/claims/benchmarking.rs @@ -0,0 +1,318 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Benchmarking for claims pallet + +#[cfg(feature = "runtime-benchmarks")] +use super::*; +use crate::claims::Call; +use frame_benchmarking::v2::*; +use frame_support::{ + dispatch::{DispatchInfo, GetDispatchInfo}, + traits::UnfilteredDispatchable, +}; +use frame_system::RawOrigin; +use secp_utils::*; +use sp_runtime::{ + traits::{DispatchTransaction, ValidateUnsigned}, + DispatchResult, +}; + +const SEED: u32 = 0; + +const MAX_CLAIMS: u32 = 10_000; +const VALUE: u32 = 1_000_000; + +fn create_claim(input: u32) -> DispatchResult { + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap(); + let eth_address = eth(&secret_key); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + super::Pallet::::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + None, + )?; + Ok(()) +} + +fn create_claim_attest(input: u32) -> DispatchResult { + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&input.encode())).unwrap(); + let eth_address = eth(&secret_key); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + super::Pallet::::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + Some(Default::default()), + )?; + Ok(()) +} + +#[benchmarks( + where + ::RuntimeCall: IsSubType> + From>, + ::RuntimeCall: Dispatchable + GetDispatchInfo, + <::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner + AsTransactionAuthorizedOrigin + Clone, + <::RuntimeCall as Dispatchable>::PostInfo: Default, + )] +mod benchmarks { + use super::*; + + // Benchmark `claim` including `validate_unsigned` logic. + #[benchmark] + fn claim() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::(c)?; + create_claim_attest::(u32::MAX - c)?; + } + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&c.encode())).unwrap(); + let eth_address = eth(&secret_key); + let account: T::AccountId = account("user", c, SEED); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + let signature = sig::(&secret_key, &account.encode(), &[][..]); + super::Pallet::::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + None, + )?; + assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); + let source = sp_runtime::transaction_validity::TransactionSource::External; + let call_enc = + Call::::claim { dest: account.clone(), ethereum_signature: signature.clone() } + .encode(); + + #[block] + { + let call = as Decode>::decode(&mut &*call_enc) + .expect("call is encoded above, encoding must be correct"); + super::Pallet::::validate_unsigned(source, &call) + .map_err(|e| -> &'static str { e.into() })?; + call.dispatch_bypass_filter(RawOrigin::None.into())?; + } + + assert_eq!(Claims::::get(eth_address), None); + Ok(()) + } + + // Benchmark `mint_claim` when there already exists `c` claims in storage. + #[benchmark] + fn mint_claim() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::(c)?; + create_claim_attest::(u32::MAX - c)?; + } + let eth_address = account("eth_address", 0, SEED); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + let statement = StatementKind::Regular; + + #[extrinsic_call] + _(RawOrigin::Root, eth_address, VALUE.into(), vesting, Some(statement)); + + assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); + Ok(()) + } + + // Benchmark `claim_attest` including `validate_unsigned` logic. + #[benchmark] + fn claim_attest() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::(c)?; + create_claim_attest::(u32::MAX - c)?; + } + // Crate signature + let attest_c = u32::MAX - c; + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let eth_address = eth(&secret_key); + let account: T::AccountId = account("user", c, SEED); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + let statement = StatementKind::Regular; + let signature = sig::(&secret_key, &account.encode(), statement.to_text()); + super::Pallet::::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + Some(statement), + )?; + assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); + let call_enc = Call::::claim_attest { + dest: account.clone(), + ethereum_signature: signature.clone(), + statement: StatementKind::Regular.to_text().to_vec(), + } + .encode(); + let source = sp_runtime::transaction_validity::TransactionSource::External; + + #[block] + { + let call = as Decode>::decode(&mut &*call_enc) + .expect("call is encoded above, encoding must be correct"); + super::Pallet::::validate_unsigned(source, &call) + .map_err(|e| -> &'static str { e.into() })?; + call.dispatch_bypass_filter(RawOrigin::None.into())?; + } + + assert_eq!(Claims::::get(eth_address), None); + Ok(()) + } + + // Benchmark `attest` including prevalidate logic. + #[benchmark] + fn attest() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::(c)?; + create_claim_attest::(u32::MAX - c)?; + } + let attest_c = u32::MAX - c; + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let eth_address = eth(&secret_key); + let account: T::AccountId = account("user", c, SEED); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + let statement = StatementKind::Regular; + super::Pallet::::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + Some(statement), + )?; + Preclaims::::insert(&account, eth_address); + assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); + + let stmt = StatementKind::Regular.to_text().to_vec(); + + #[extrinsic_call] + _(RawOrigin::Signed(account), stmt); + + assert_eq!(Claims::::get(eth_address), None); + Ok(()) + } + + #[benchmark] + fn move_claim() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::(c)?; + create_claim_attest::(u32::MAX - c)?; + } + let attest_c = u32::MAX - c; + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let eth_address = eth(&secret_key); + + let new_secret_key = + libsecp256k1::SecretKey::parse(&keccak_256(&(u32::MAX / 2).encode())).unwrap(); + let new_eth_address = eth(&new_secret_key); + + let account: T::AccountId = account("user", c, SEED); + Preclaims::::insert(&account, eth_address); + + assert!(Claims::::contains_key(eth_address)); + assert!(!Claims::::contains_key(new_eth_address)); + + #[extrinsic_call] + _(RawOrigin::Root, eth_address, new_eth_address, Some(account)); + + assert!(!Claims::::contains_key(eth_address)); + assert!(Claims::::contains_key(new_eth_address)); + Ok(()) + } + + // Benchmark the time it takes to do `repeat` number of keccak256 hashes + #[benchmark(extra)] + fn keccak256(i: Linear<0, 10_000>) { + let bytes = (i).encode(); + + #[block] + { + for _ in 0..i { + let _hash = keccak_256(&bytes); + } + } + } + + // Benchmark the time it takes to do `repeat` number of `eth_recover` + #[benchmark(extra)] + fn eth_recover(i: Linear<0, 1_000>) { + // Crate signature + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&i.encode())).unwrap(); + let account: T::AccountId = account("user", i, SEED); + let signature = sig::(&secret_key, &account.encode(), &[][..]); + let data = account.using_encoded(to_ascii_hex); + let extra = StatementKind::default().to_text(); + + #[block] + { + for _ in 0..i { + assert!(super::Pallet::::eth_recover(&signature, &data, extra).is_some()); + } + } + } + + #[benchmark] + fn prevalidate_attests() -> Result<(), BenchmarkError> { + let c = MAX_CLAIMS; + for _ in 0..c / 2 { + create_claim::(c)?; + create_claim_attest::(u32::MAX - c)?; + } + let ext = PrevalidateAttests::::new(); + let call = super::Call::attest { statement: StatementKind::Regular.to_text().to_vec() }; + let call: ::RuntimeCall = call.into(); + let info = call.get_dispatch_info(); + let attest_c = u32::MAX - c; + let secret_key = libsecp256k1::SecretKey::parse(&keccak_256(&attest_c.encode())).unwrap(); + let eth_address = eth(&secret_key); + let account: T::AccountId = account("user", c, SEED); + let vesting = Some((100_000u32.into(), 1_000u32.into(), 100u32.into())); + let statement = StatementKind::Regular; + super::Pallet::::mint_claim( + RawOrigin::Root.into(), + eth_address, + VALUE.into(), + vesting, + Some(statement), + )?; + Preclaims::::insert(&account, eth_address); + assert_eq!(Claims::::get(eth_address), Some(VALUE.into())); + + #[block] + { + assert!(ext + .test_run(RawOrigin::Signed(account).into(), &call, &info, 0, 0, |_| { + Ok(Default::default()) + }) + .unwrap() + .is_ok()); + } + + Ok(()) + } + + impl_benchmark_test_suite!( + Pallet, + crate::claims::mock::new_test_ext(), + crate::claims::mock::Test, + ); +} diff --git a/polkadot/runtime/common/src/claims/mock.rs b/polkadot/runtime/common/src/claims/mock.rs new file mode 100644 index 000000000000..640df6ec6a8a --- /dev/null +++ b/polkadot/runtime/common/src/claims/mock.rs @@ -0,0 +1,129 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Mocking utilities for testing in claims pallet. + +#[cfg(test)] +use super::*; +use secp_utils::*; + +// The testing primitives are very useful for avoiding having to work with signatures +// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. +use crate::claims; +use frame_support::{derive_impl, ord_parameter_types, parameter_types, traits::WithdrawReasons}; +use pallet_balances; +use sp_runtime::{traits::Identity, BuildStorage}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Vesting: pallet_vesting, + Claims: claims, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type AccountData = pallet_balances::AccountData; + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +parameter_types! { + pub const MinVestedTransfer: u64 = 1; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BlockNumberToBalance = Identity; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = (); + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; + const MAX_VESTING_SCHEDULES: u32 = 28; +} + +parameter_types! { + pub Prefix: &'static [u8] = b"Pay RUSTs to the TEST account:"; +} +ord_parameter_types! { + pub const Six: u64 = 6; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type VestingSchedule = Vesting; + type Prefix = Prefix; + type MoveClaimOrigin = frame_system::EnsureSignedBy; + type WeightInfo = TestWeightInfo; +} + +pub fn alice() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Alice")).unwrap() +} +pub fn bob() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Bob")).unwrap() +} +pub fn dave() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Dave")).unwrap() +} +pub fn eve() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Eve")).unwrap() +} +pub fn frank() -> libsecp256k1::SecretKey { + libsecp256k1::SecretKey::parse(&keccak_256(b"Frank")).unwrap() +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + // We use default for brevity, but you can configure as desired if needed. + pallet_balances::GenesisConfig::::default() + .assimilate_storage(&mut t) + .unwrap(); + claims::GenesisConfig:: { + claims: vec![ + (eth(&alice()), 100, None, None), + (eth(&dave()), 200, None, Some(StatementKind::Regular)), + (eth(&eve()), 300, Some(42), Some(StatementKind::Saft)), + (eth(&frank()), 400, Some(43), None), + ], + vesting: vec![(eth(&alice()), (50, 10, 1))], + } + .assimilate_storage(&mut t) + .unwrap(); + t.into() +} + +pub fn total_claims() -> u64 { + 100 + 200 + 300 + 400 +} diff --git a/polkadot/runtime/common/src/claims/mod.rs b/polkadot/runtime/common/src/claims/mod.rs new file mode 100644 index 000000000000..f48e40ee1887 --- /dev/null +++ b/polkadot/runtime/common/src/claims/mod.rs @@ -0,0 +1,723 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Pallet to process claims from Ethereum addresses. + +#[cfg(not(feature = "std"))] +use alloc::{format, string::String}; +use alloc::{vec, vec::Vec}; +use codec::{Decode, Encode, MaxEncodedLen}; +use core::fmt::Debug; +use frame_support::{ + ensure, + traits::{Currency, Get, IsSubType, VestingSchedule}, + weights::Weight, + DefaultNoBound, +}; +pub use pallet::*; +use polkadot_primitives::ValidityError; +use scale_info::TypeInfo; +use serde::{self, Deserialize, Deserializer, Serialize, Serializer}; +use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256}; +use sp_runtime::{ + impl_tx_ext_default, + traits::{ + AsSystemOriginSigner, AsTransactionAuthorizedOrigin, CheckedSub, DispatchInfoOf, + Dispatchable, TransactionExtension, Zero, + }, + transaction_validity::{ + InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, + ValidTransaction, + }, + RuntimeDebug, +}; + +type CurrencyOf = <::VestingSchedule as VestingSchedule< + ::AccountId, +>>::Currency; +type BalanceOf = as Currency<::AccountId>>::Balance; + +pub trait WeightInfo { + fn claim() -> Weight; + fn mint_claim() -> Weight; + fn claim_attest() -> Weight; + fn attest() -> Weight; + fn move_claim() -> Weight; + fn prevalidate_attests() -> Weight; +} + +pub struct TestWeightInfo; +impl WeightInfo for TestWeightInfo { + fn claim() -> Weight { + Weight::zero() + } + fn mint_claim() -> Weight { + Weight::zero() + } + fn claim_attest() -> Weight { + Weight::zero() + } + fn attest() -> Weight { + Weight::zero() + } + fn move_claim() -> Weight { + Weight::zero() + } + fn prevalidate_attests() -> Weight { + Weight::zero() + } +} + +/// The kind of statement an account needs to make for a claim to be valid. +#[derive( + Encode, + Decode, + Clone, + Copy, + Eq, + PartialEq, + RuntimeDebug, + TypeInfo, + Serialize, + Deserialize, + MaxEncodedLen, +)] +pub enum StatementKind { + /// Statement required to be made by non-SAFT holders. + Regular, + /// Statement required to be made by SAFT holders. + Saft, +} + +impl StatementKind { + /// Convert this to the (English) statement it represents. + fn to_text(self) -> &'static [u8] { + match self { + StatementKind::Regular => + &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \ + Qmc1XYqT6S39WNp2UeiRUrZichUWUPpGEThDE6dAb3f6Ny. (This may be found at the URL: \ + https://statement.polkadot.network/regular.html)"[..], + StatementKind::Saft => + &b"I hereby agree to the terms of the statement whose SHA-256 multihash is \ + QmXEkMahfhHJPzT3RjkXiZVFi77ZeVeuxtAjhojGRNYckz. (This may be found at the URL: \ + https://statement.polkadot.network/saft.html)"[..], + } + } +} + +impl Default for StatementKind { + fn default() -> Self { + StatementKind::Regular + } +} + +/// An Ethereum address (i.e. 20 bytes, used to represent an Ethereum account). +/// +/// This gets serialized to the 0x-prefixed hex representation. +#[derive( + Clone, Copy, PartialEq, Eq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] +pub struct EthereumAddress([u8; 20]); + +impl Serialize for EthereumAddress { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let hex: String = rustc_hex::ToHex::to_hex(&self.0[..]); + serializer.serialize_str(&format!("0x{}", hex)) + } +} + +impl<'de> Deserialize<'de> for EthereumAddress { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let base_string = String::deserialize(deserializer)?; + let offset = if base_string.starts_with("0x") { 2 } else { 0 }; + let s = &base_string[offset..]; + if s.len() != 40 { + Err(serde::de::Error::custom( + "Bad length of Ethereum address (should be 42 including '0x')", + ))?; + } + let raw: Vec = rustc_hex::FromHex::from_hex(s) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; + let mut r = Self::default(); + r.0.copy_from_slice(&raw); + Ok(r) + } +} + +#[derive(Encode, Decode, Clone, TypeInfo, MaxEncodedLen)] +pub struct EcdsaSignature(pub [u8; 65]); + +impl PartialEq for EcdsaSignature { + fn eq(&self, other: &Self) -> bool { + &self.0[..] == &other.0[..] + } +} + +impl core::fmt::Debug for EcdsaSignature { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "EcdsaSignature({:?})", &self.0[..]) + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + /// Configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type VestingSchedule: VestingSchedule>; + #[pallet::constant] + type Prefix: Get<&'static [u8]>; + type MoveClaimOrigin: EnsureOrigin; + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Someone claimed some DOTs. + Claimed { who: T::AccountId, ethereum_address: EthereumAddress, amount: BalanceOf }, + } + + #[pallet::error] + pub enum Error { + /// Invalid Ethereum signature. + InvalidEthereumSignature, + /// Ethereum address has no claim. + SignerHasNoClaim, + /// Account ID sending transaction has no claim. + SenderHasNoClaim, + /// There's not enough in the pot to pay out some unvested amount. Generally implies a + /// logic error. + PotUnderflow, + /// A needed statement was not included. + InvalidStatement, + /// The account already has a vested balance. + VestedBalanceExists, + } + + #[pallet::storage] + pub type Claims = StorageMap<_, Identity, EthereumAddress, BalanceOf>; + + #[pallet::storage] + pub type Total = StorageValue<_, BalanceOf, ValueQuery>; + + /// Vesting schedule for a claim. + /// First balance is the total amount that should be held for vesting. + /// Second balance is how much should be unlocked per block. + /// The block number is when the vesting should start. + #[pallet::storage] + pub type Vesting = + StorageMap<_, Identity, EthereumAddress, (BalanceOf, BalanceOf, BlockNumberFor)>; + + /// The statement kind that must be signed, if any. + #[pallet::storage] + pub(super) type Signing = StorageMap<_, Identity, EthereumAddress, StatementKind>; + + /// Pre-claimed Ethereum accounts, by the Account ID that they are claimed to. + #[pallet::storage] + pub(super) type Preclaims = StorageMap<_, Identity, T::AccountId, EthereumAddress>; + + #[pallet::genesis_config] + #[derive(DefaultNoBound)] + pub struct GenesisConfig { + pub claims: + Vec<(EthereumAddress, BalanceOf, Option, Option)>, + pub vesting: Vec<(EthereumAddress, (BalanceOf, BalanceOf, BlockNumberFor))>, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + // build `Claims` + self.claims.iter().map(|(a, b, _, _)| (*a, *b)).for_each(|(a, b)| { + Claims::::insert(a, b); + }); + // build `Total` + Total::::put( + self.claims + .iter() + .fold(Zero::zero(), |acc: BalanceOf, &(_, b, _, _)| acc + b), + ); + // build `Vesting` + self.vesting.iter().for_each(|(k, v)| { + Vesting::::insert(k, v); + }); + // build `Signing` + self.claims + .iter() + .filter_map(|(a, _, _, s)| Some((*a, (*s)?))) + .for_each(|(a, s)| { + Signing::::insert(a, s); + }); + // build `Preclaims` + self.claims.iter().filter_map(|(a, _, i, _)| Some((i.clone()?, *a))).for_each( + |(i, a)| { + Preclaims::::insert(i, a); + }, + ); + } + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// Make a claim to collect your DOTs. + /// + /// The dispatch origin for this call must be _None_. + /// + /// Unsigned Validation: + /// A call to claim is deemed valid if the signature provided matches + /// the expected signed message of: + /// + /// > Ethereum Signed Message: + /// > (configured prefix string)(address) + /// + /// and `address` matches the `dest` account. + /// + /// Parameters: + /// - `dest`: The destination account to payout the claim. + /// - `ethereum_signature`: The signature of an ethereum signed message matching the format + /// described above. + /// + /// + /// The weight of this call is invariant over the input parameters. + /// Weight includes logic to validate unsigned `claim` call. + /// + /// Total Complexity: O(1) + /// + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::claim())] + pub fn claim( + origin: OriginFor, + dest: T::AccountId, + ethereum_signature: EcdsaSignature, + ) -> DispatchResult { + ensure_none(origin)?; + + let data = dest.using_encoded(to_ascii_hex); + let signer = Self::eth_recover(ðereum_signature, &data, &[][..]) + .ok_or(Error::::InvalidEthereumSignature)?; + ensure!(Signing::::get(&signer).is_none(), Error::::InvalidStatement); + + Self::process_claim(signer, dest)?; + Ok(()) + } + + /// Mint a new claim to collect DOTs. + /// + /// The dispatch origin for this call must be _Root_. + /// + /// Parameters: + /// - `who`: The Ethereum address allowed to collect this claim. + /// - `value`: The number of DOTs that will be claimed. + /// - `vesting_schedule`: An optional vesting schedule for these DOTs. + /// + /// + /// The weight of this call is invariant over the input parameters. + /// We assume worst case that both vesting and statement is being inserted. + /// + /// Total Complexity: O(1) + /// + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::mint_claim())] + pub fn mint_claim( + origin: OriginFor, + who: EthereumAddress, + value: BalanceOf, + vesting_schedule: Option<(BalanceOf, BalanceOf, BlockNumberFor)>, + statement: Option, + ) -> DispatchResult { + ensure_root(origin)?; + + Total::::mutate(|t| *t += value); + Claims::::insert(who, value); + if let Some(vs) = vesting_schedule { + Vesting::::insert(who, vs); + } + if let Some(s) = statement { + Signing::::insert(who, s); + } + Ok(()) + } + + /// Make a claim to collect your DOTs by signing a statement. + /// + /// The dispatch origin for this call must be _None_. + /// + /// Unsigned Validation: + /// A call to `claim_attest` is deemed valid if the signature provided matches + /// the expected signed message of: + /// + /// > Ethereum Signed Message: + /// > (configured prefix string)(address)(statement) + /// + /// and `address` matches the `dest` account; the `statement` must match that which is + /// expected according to your purchase arrangement. + /// + /// Parameters: + /// - `dest`: The destination account to payout the claim. + /// - `ethereum_signature`: The signature of an ethereum signed message matching the format + /// described above. + /// - `statement`: The identity of the statement which is being attested to in the + /// signature. + /// + /// + /// The weight of this call is invariant over the input parameters. + /// Weight includes logic to validate unsigned `claim_attest` call. + /// + /// Total Complexity: O(1) + /// + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::claim_attest())] + pub fn claim_attest( + origin: OriginFor, + dest: T::AccountId, + ethereum_signature: EcdsaSignature, + statement: Vec, + ) -> DispatchResult { + ensure_none(origin)?; + + let data = dest.using_encoded(to_ascii_hex); + let signer = Self::eth_recover(ðereum_signature, &data, &statement) + .ok_or(Error::::InvalidEthereumSignature)?; + if let Some(s) = Signing::::get(signer) { + ensure!(s.to_text() == &statement[..], Error::::InvalidStatement); + } + Self::process_claim(signer, dest)?; + Ok(()) + } + + /// Attest to a statement, needed to finalize the claims process. + /// + /// WARNING: Insecure unless your chain includes `PrevalidateAttests` as a + /// `TransactionExtension`. + /// + /// Unsigned Validation: + /// A call to attest is deemed valid if the sender has a `Preclaim` registered + /// and provides a `statement` which is expected for the account. + /// + /// Parameters: + /// - `statement`: The identity of the statement which is being attested to in the + /// signature. + /// + /// + /// The weight of this call is invariant over the input parameters. + /// Weight includes logic to do pre-validation on `attest` call. + /// + /// Total Complexity: O(1) + /// + #[pallet::call_index(3)] + #[pallet::weight(( + T::WeightInfo::attest(), + DispatchClass::Normal, + Pays::No + ))] + pub fn attest(origin: OriginFor, statement: Vec) -> DispatchResult { + let who = ensure_signed(origin)?; + let signer = Preclaims::::get(&who).ok_or(Error::::SenderHasNoClaim)?; + if let Some(s) = Signing::::get(signer) { + ensure!(s.to_text() == &statement[..], Error::::InvalidStatement); + } + Self::process_claim(signer, who.clone())?; + Preclaims::::remove(&who); + Ok(()) + } + + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::move_claim())] + pub fn move_claim( + origin: OriginFor, + old: EthereumAddress, + new: EthereumAddress, + maybe_preclaim: Option, + ) -> DispatchResultWithPostInfo { + T::MoveClaimOrigin::try_origin(origin).map(|_| ()).or_else(ensure_root)?; + + Claims::::take(&old).map(|c| Claims::::insert(&new, c)); + Vesting::::take(&old).map(|c| Vesting::::insert(&new, c)); + Signing::::take(&old).map(|c| Signing::::insert(&new, c)); + maybe_preclaim.map(|preclaim| { + Preclaims::::mutate(&preclaim, |maybe_o| { + if maybe_o.as_ref().map_or(false, |o| o == &old) { + *maybe_o = Some(new) + } + }) + }); + Ok(Pays::No.into()) + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + const PRIORITY: u64 = 100; + + let (maybe_signer, maybe_statement) = match call { + // + // The weight of this logic is included in the `claim` dispatchable. + // + Call::claim { dest: account, ethereum_signature } => { + let data = account.using_encoded(to_ascii_hex); + (Self::eth_recover(ðereum_signature, &data, &[][..]), None) + }, + // + // The weight of this logic is included in the `claim_attest` dispatchable. + // + Call::claim_attest { dest: account, ethereum_signature, statement } => { + let data = account.using_encoded(to_ascii_hex); + ( + Self::eth_recover(ðereum_signature, &data, &statement), + Some(statement.as_slice()), + ) + }, + _ => return Err(InvalidTransaction::Call.into()), + }; + + let signer = maybe_signer.ok_or(InvalidTransaction::Custom( + ValidityError::InvalidEthereumSignature.into(), + ))?; + + let e = InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()); + ensure!(Claims::::contains_key(&signer), e); + + let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); + match Signing::::get(signer) { + None => ensure!(maybe_statement.is_none(), e), + Some(s) => ensure!(Some(s.to_text()) == maybe_statement, e), + } + + Ok(ValidTransaction { + priority: PRIORITY, + requires: vec![], + provides: vec![("claims", signer).encode()], + longevity: TransactionLongevity::max_value(), + propagate: true, + }) + } + } +} + +/// Converts the given binary data into ASCII-encoded hex. It will be twice the length. +fn to_ascii_hex(data: &[u8]) -> Vec { + let mut r = Vec::with_capacity(data.len() * 2); + let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n }); + for &b in data.iter() { + push_nibble(b / 16); + push_nibble(b % 16); + } + r +} + +impl Pallet { + // Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign` would sign. + fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec { + let prefix = T::Prefix::get(); + let mut l = prefix.len() + what.len() + extra.len(); + let mut rev = Vec::new(); + while l > 0 { + rev.push(b'0' + (l % 10) as u8); + l /= 10; + } + let mut v = b"\x19Ethereum Signed Message:\n".to_vec(); + v.extend(rev.into_iter().rev()); + v.extend_from_slice(prefix); + v.extend_from_slice(what); + v.extend_from_slice(extra); + v + } + + // Attempts to recover the Ethereum address from a message signature signed by using + // the Ethereum RPC's `personal_sign` and `eth_sign`. + fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option { + let msg = keccak_256(&Self::ethereum_signable_message(what, extra)); + let mut res = EthereumAddress::default(); + res.0 + .copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]); + Some(res) + } + + fn process_claim(signer: EthereumAddress, dest: T::AccountId) -> sp_runtime::DispatchResult { + let balance_due = Claims::::get(&signer).ok_or(Error::::SignerHasNoClaim)?; + + let new_total = + Total::::get().checked_sub(&balance_due).ok_or(Error::::PotUnderflow)?; + + let vesting = Vesting::::get(&signer); + if vesting.is_some() && T::VestingSchedule::vesting_balance(&dest).is_some() { + return Err(Error::::VestedBalanceExists.into()) + } + + // We first need to deposit the balance to ensure that the account exists. + let _ = CurrencyOf::::deposit_creating(&dest, balance_due); + + // Check if this claim should have a vesting schedule. + if let Some(vs) = vesting { + // This can only fail if the account already has a vesting schedule, + // but this is checked above. + T::VestingSchedule::add_vesting_schedule(&dest, vs.0, vs.1, vs.2) + .expect("No other vesting schedule exists, as checked above; qed"); + } + + Total::::put(new_total); + Claims::::remove(&signer); + Vesting::::remove(&signer); + Signing::::remove(&signer); + + // Let's deposit an event to let the outside world know this happened. + Self::deposit_event(Event::::Claimed { + who: dest, + ethereum_address: signer, + amount: balance_due, + }); + + Ok(()) + } +} + +/// Validate `attest` calls prior to execution. Needed to avoid a DoS attack since they are +/// otherwise free to place on chain. +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct PrevalidateAttests(core::marker::PhantomData); + +impl Debug for PrevalidateAttests +where + ::RuntimeCall: IsSubType>, +{ + #[cfg(feature = "std")] + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "PrevalidateAttests") + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result { + Ok(()) + } +} + +impl PrevalidateAttests +where + ::RuntimeCall: IsSubType>, +{ + /// Create new `TransactionExtension` to check runtime version. + pub fn new() -> Self { + Self(core::marker::PhantomData) + } +} + +impl TransactionExtension for PrevalidateAttests +where + ::RuntimeCall: IsSubType>, + <::RuntimeCall as Dispatchable>::RuntimeOrigin: + AsSystemOriginSigner + AsTransactionAuthorizedOrigin + Clone, +{ + const IDENTIFIER: &'static str = "PrevalidateAttests"; + type Implicit = (); + type Pre = (); + type Val = (); + + fn weight(&self, call: &T::RuntimeCall) -> Weight { + if let Some(Call::attest { .. }) = call.is_sub_type() { + T::WeightInfo::prevalidate_attests() + } else { + Weight::zero() + } + } + + fn validate( + &self, + origin: ::RuntimeOrigin, + call: &T::RuntimeCall, + _info: &DispatchInfoOf, + _len: usize, + _self_implicit: Self::Implicit, + _inherited_implication: &impl Encode, + _source: TransactionSource, + ) -> Result< + (ValidTransaction, Self::Val, ::RuntimeOrigin), + TransactionValidityError, + > { + if let Some(Call::attest { statement: attested_statement }) = call.is_sub_type() { + let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?; + let signer = Preclaims::::get(who) + .ok_or(InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()))?; + if let Some(s) = Signing::::get(signer) { + let e = InvalidTransaction::Custom(ValidityError::InvalidStatement.into()); + ensure!(&attested_statement[..] == s.to_text(), e); + } + } + Ok((ValidTransaction::default(), (), origin)) + } + + impl_tx_ext_default!(T::RuntimeCall; prepare); +} + +#[cfg(any(test, feature = "runtime-benchmarks"))] +mod secp_utils { + use super::*; + + pub fn public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey { + libsecp256k1::PublicKey::from_secret_key(secret) + } + pub fn eth(secret: &libsecp256k1::SecretKey) -> EthereumAddress { + let mut res = EthereumAddress::default(); + res.0.copy_from_slice(&keccak_256(&public(secret).serialize()[1..65])[12..]); + res + } + pub fn sig( + secret: &libsecp256k1::SecretKey, + what: &[u8], + extra: &[u8], + ) -> EcdsaSignature { + let msg = keccak_256(&super::Pallet::::ethereum_signable_message( + &to_ascii_hex(what)[..], + extra, + )); + let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret); + let mut r = [0u8; 65]; + r[0..64].copy_from_slice(&sig.serialize()[..]); + r[64] = recovery_id.serialize(); + EcdsaSignature(r) + } +} + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; diff --git a/polkadot/runtime/common/src/claims/tests.rs b/polkadot/runtime/common/src/claims/tests.rs new file mode 100644 index 000000000000..dff2623cb934 --- /dev/null +++ b/polkadot/runtime/common/src/claims/tests.rs @@ -0,0 +1,666 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Tests for the claims pallet. + +#[cfg(test)] +use super::*; +use crate::{claims, claims::mock::*}; +use claims::Call as ClaimsCall; +use hex_literal::hex; +use secp_utils::*; +use sp_runtime::transaction_validity::TransactionSource::External; + +use codec::Encode; +// The testing primitives are very useful for avoiding having to work with signatures +// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. +use frame_support::{ + assert_err, assert_noop, assert_ok, + dispatch::{GetDispatchInfo, Pays}, + traits::ExistenceRequirement, +}; +use sp_runtime::{ + traits::DispatchTransaction, transaction_validity::TransactionLongevity, + DispatchError::BadOrigin, TokenError, +}; + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with(|| { + assert_eq!(claims::Total::::get(), total_claims()); + assert_eq!(claims::Claims::::get(ð(&alice())), Some(100)); + assert_eq!(claims::Claims::::get(ð(&dave())), Some(200)); + assert_eq!(claims::Claims::::get(ð(&eve())), Some(300)); + assert_eq!(claims::Claims::::get(ð(&frank())), Some(400)); + assert_eq!(claims::Claims::::get(&EthereumAddress::default()), None); + assert_eq!(claims::Vesting::::get(ð(&alice())), Some((50, 10, 1))); + }); +} + +#[test] +fn serde_works() { + let x = EthereumAddress(hex!["0123456789abcdef0123456789abcdef01234567"]); + let y = serde_json::to_string(&x).unwrap(); + assert_eq!(y, "\"0x0123456789abcdef0123456789abcdef01234567\""); + let z: EthereumAddress = serde_json::from_str(&y).unwrap(); + assert_eq!(x, z); +} + +#[test] +fn claiming_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&alice(), &42u64.encode(), &[][..]) + )); + assert_eq!(Balances::free_balance(&42), 100); + assert_eq!(claims::mock::Vesting::vesting_balance(&42), Some(50)); + assert_eq!(claims::Total::::get(), total_claims() - 100); + }); +} + +#[test] +fn basic_claim_moving_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::move_claim( + RuntimeOrigin::signed(1), + eth(&alice()), + eth(&bob()), + None + ), + BadOrigin + ); + assert_ok!(claims::mock::Claims::move_claim( + RuntimeOrigin::signed(6), + eth(&alice()), + eth(&bob()), + None + )); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&alice(), &42u64.encode(), &[][..]) + ), + Error::::SignerHasNoClaim + ); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&bob(), &42u64.encode(), &[][..]) + )); + assert_eq!(Balances::free_balance(&42), 100); + assert_eq!(claims::mock::Vesting::vesting_balance(&42), Some(50)); + assert_eq!(claims::Total::::get(), total_claims() - 100); + }); +} + +#[test] +fn claim_attest_moving_works() { + new_test_ext().execute_with(|| { + assert_ok!(claims::mock::Claims::move_claim( + RuntimeOrigin::signed(6), + eth(&dave()), + eth(&bob()), + None + )); + let s = sig::(&bob(), &42u64.encode(), StatementKind::Regular.to_text()); + assert_ok!(claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 42, + s, + StatementKind::Regular.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&42), 200); + }); +} + +#[test] +fn attest_moving_works() { + new_test_ext().execute_with(|| { + assert_ok!(claims::mock::Claims::move_claim( + RuntimeOrigin::signed(6), + eth(&eve()), + eth(&bob()), + Some(42) + )); + assert_ok!(claims::mock::Claims::attest( + RuntimeOrigin::signed(42), + StatementKind::Saft.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&42), 300); + }); +} + +#[test] +fn claiming_does_not_bypass_signing() { + new_test_ext().execute_with(|| { + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&alice(), &42u64.encode(), &[][..]) + )); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&dave(), &42u64.encode(), &[][..]) + ), + Error::::InvalidStatement, + ); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&eve(), &42u64.encode(), &[][..]) + ), + Error::::InvalidStatement, + ); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&frank(), &42u64.encode(), &[][..]) + )); + }); +} + +#[test] +fn attest_claiming_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + let s = sig::(&dave(), &42u64.encode(), StatementKind::Saft.to_text()); + let r = claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 42, + s.clone(), + StatementKind::Saft.to_text().to_vec(), + ); + assert_noop!(r, Error::::InvalidStatement); + + let r = claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 42, + s, + StatementKind::Regular.to_text().to_vec(), + ); + assert_noop!(r, Error::::SignerHasNoClaim); + // ^^^ we use ecdsa_recover, so an invalid signature just results in a random signer id + // being recovered, which realistically will never have a claim. + + let s = sig::(&dave(), &42u64.encode(), StatementKind::Regular.to_text()); + assert_ok!(claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 42, + s, + StatementKind::Regular.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&42), 200); + assert_eq!(claims::Total::::get(), total_claims() - 200); + + let s = sig::(&dave(), &42u64.encode(), StatementKind::Regular.to_text()); + let r = claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 42, + s, + StatementKind::Regular.to_text().to_vec(), + ); + assert_noop!(r, Error::::SignerHasNoClaim); + }); +} + +#[test] +fn attesting_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::attest( + RuntimeOrigin::signed(69), + StatementKind::Saft.to_text().to_vec() + ), + Error::::SenderHasNoClaim + ); + assert_noop!( + claims::mock::Claims::attest( + RuntimeOrigin::signed(42), + StatementKind::Regular.to_text().to_vec() + ), + Error::::InvalidStatement + ); + assert_ok!(claims::mock::Claims::attest( + RuntimeOrigin::signed(42), + StatementKind::Saft.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&42), 300); + assert_eq!(claims::Total::::get(), total_claims() - 300); + }); +} + +#[test] +fn claim_cannot_clobber_preclaim() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + // Alice's claim is 100 + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&alice(), &42u64.encode(), &[][..]) + )); + assert_eq!(Balances::free_balance(&42), 100); + // Eve's claim is 300 through Account 42 + assert_ok!(claims::mock::Claims::attest( + RuntimeOrigin::signed(42), + StatementKind::Saft.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&42), 100 + 300); + assert_eq!(claims::Total::::get(), total_claims() - 400); + }); +} + +#[test] +fn valid_attest_transactions_are_free() { + new_test_ext().execute_with(|| { + let p = PrevalidateAttests::::new(); + let c = claims::mock::RuntimeCall::Claims(ClaimsCall::attest { + statement: StatementKind::Saft.to_text().to_vec(), + }); + let di = c.get_dispatch_info(); + assert_eq!(di.pays_fee, Pays::No); + let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0); + assert_eq!(r.unwrap().0, ValidTransaction::default()); + }); +} + +#[test] +fn invalid_attest_transactions_are_recognized() { + new_test_ext().execute_with(|| { + let p = PrevalidateAttests::::new(); + let c = claims::mock::RuntimeCall::Claims(ClaimsCall::attest { + statement: StatementKind::Regular.to_text().to_vec(), + }); + let di = c.get_dispatch_info(); + let r = p.validate_only(Some(42).into(), &c, &di, 20, External, 0); + assert!(r.is_err()); + let c = claims::mock::RuntimeCall::Claims(ClaimsCall::attest { + statement: StatementKind::Saft.to_text().to_vec(), + }); + let di = c.get_dispatch_info(); + let r = p.validate_only(Some(69).into(), &c, &di, 20, External, 0); + assert!(r.is_err()); + }); +} + +#[test] +fn cannot_bypass_attest_claiming() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + let s = sig::(&dave(), &42u64.encode(), &[]); + let r = claims::mock::Claims::claim(RuntimeOrigin::none(), 42, s.clone()); + assert_noop!(r, Error::::InvalidStatement); + }); +} + +#[test] +fn add_claim_works() { + new_test_ext().execute_with(|| { + assert_noop!( + claims::mock::Claims::mint_claim( + RuntimeOrigin::signed(42), + eth(&bob()), + 200, + None, + None + ), + sp_runtime::traits::BadOrigin, + ); + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 69, + sig::(&bob(), &69u64.encode(), &[][..]) + ), + Error::::SignerHasNoClaim, + ); + assert_ok!(claims::mock::Claims::mint_claim( + RuntimeOrigin::root(), + eth(&bob()), + 200, + None, + None + )); + assert_eq!(claims::Total::::get(), total_claims() + 200); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 69, + sig::(&bob(), &69u64.encode(), &[][..]) + )); + assert_eq!(Balances::free_balance(&69), 200); + assert_eq!(claims::mock::Vesting::vesting_balance(&69), None); + assert_eq!(claims::Total::::get(), total_claims()); + }); +} + +#[test] +fn add_claim_with_vesting_works() { + new_test_ext().execute_with(|| { + assert_noop!( + claims::mock::Claims::mint_claim( + RuntimeOrigin::signed(42), + eth(&bob()), + 200, + Some((50, 10, 1)), + None + ), + sp_runtime::traits::BadOrigin, + ); + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 69, + sig::(&bob(), &69u64.encode(), &[][..]) + ), + Error::::SignerHasNoClaim, + ); + assert_ok!(claims::mock::Claims::mint_claim( + RuntimeOrigin::root(), + eth(&bob()), + 200, + Some((50, 10, 1)), + None + )); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 69, + sig::(&bob(), &69u64.encode(), &[][..]) + )); + assert_eq!(Balances::free_balance(&69), 200); + assert_eq!(claims::mock::Vesting::vesting_balance(&69), Some(50)); + + // Make sure we can not transfer the vested balance. + assert_err!( + >::transfer(&69, &80, 180, ExistenceRequirement::AllowDeath), + TokenError::Frozen, + ); + }); +} + +#[test] +fn add_claim_with_statement_works() { + new_test_ext().execute_with(|| { + assert_noop!( + claims::mock::Claims::mint_claim( + RuntimeOrigin::signed(42), + eth(&bob()), + 200, + None, + Some(StatementKind::Regular) + ), + sp_runtime::traits::BadOrigin, + ); + assert_eq!(Balances::free_balance(42), 0); + let signature = sig::(&bob(), &69u64.encode(), StatementKind::Regular.to_text()); + assert_noop!( + claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 69, + signature.clone(), + StatementKind::Regular.to_text().to_vec() + ), + Error::::SignerHasNoClaim + ); + assert_ok!(claims::mock::Claims::mint_claim( + RuntimeOrigin::root(), + eth(&bob()), + 200, + None, + Some(StatementKind::Regular) + )); + assert_noop!( + claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 69, + signature.clone(), + vec![], + ), + Error::::SignerHasNoClaim + ); + assert_ok!(claims::mock::Claims::claim_attest( + RuntimeOrigin::none(), + 69, + signature.clone(), + StatementKind::Regular.to_text().to_vec() + )); + assert_eq!(Balances::free_balance(&69), 200); + }); +} + +#[test] +fn origin_signed_claiming_fail() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_err!( + claims::mock::Claims::claim( + RuntimeOrigin::signed(42), + 42, + sig::(&alice(), &42u64.encode(), &[][..]) + ), + sp_runtime::traits::BadOrigin, + ); + }); +} + +#[test] +fn double_claiming_doesnt_work() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_ok!(claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&alice(), &42u64.encode(), &[][..]) + )); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&alice(), &42u64.encode(), &[][..]) + ), + Error::::SignerHasNoClaim + ); + }); +} + +#[test] +fn claiming_while_vested_doesnt_work() { + new_test_ext().execute_with(|| { + CurrencyOf::::make_free_balance_be(&69, total_claims()); + assert_eq!(Balances::free_balance(69), total_claims()); + // A user is already vested + assert_ok!(::VestingSchedule::add_vesting_schedule( + &69, + total_claims(), + 100, + 10 + )); + assert_ok!(claims::mock::Claims::mint_claim( + RuntimeOrigin::root(), + eth(&bob()), + 200, + Some((50, 10, 1)), + None + )); + // New total + assert_eq!(claims::Total::::get(), total_claims() + 200); + + // They should not be able to claim + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 69, + sig::(&bob(), &69u64.encode(), &[][..]) + ), + Error::::VestedBalanceExists, + ); + }); +} + +#[test] +fn non_sender_sig_doesnt_work() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&alice(), &69u64.encode(), &[][..]) + ), + Error::::SignerHasNoClaim + ); + }); +} + +#[test] +fn non_claimant_doesnt_work() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(42), 0); + assert_noop!( + claims::mock::Claims::claim( + RuntimeOrigin::none(), + 42, + sig::(&bob(), &69u64.encode(), &[][..]) + ), + Error::::SignerHasNoClaim + ); + }); +} + +#[test] +fn real_eth_sig_works() { + new_test_ext().execute_with(|| { + // "Pay RUSTs to the TEST account:2a00000000000000" + let sig = hex!["444023e89b67e67c0562ed0305d252a5dd12b2af5ac51d6d3cb69a0b486bc4b3191401802dc29d26d586221f7256cd3329fe82174bdf659baea149a40e1c495d1c"]; + let sig = EcdsaSignature(sig); + let who = 42u64.using_encoded(to_ascii_hex); + let signer = claims::mock::Claims::eth_recover(&sig, &who, &[][..]).unwrap(); + assert_eq!(signer.0, hex!["6d31165d5d932d571f3b44695653b46dcc327e84"]); + }); +} + +#[test] +fn validate_unsigned_works() { + use sp_runtime::traits::ValidateUnsigned; + let source = sp_runtime::transaction_validity::TransactionSource::External; + + new_test_ext().execute_with(|| { + assert_eq!( + Pallet::::validate_unsigned( + source, + &ClaimsCall::claim { + dest: 1, + ethereum_signature: sig::(&alice(), &1u64.encode(), &[][..]) + } + ), + Ok(ValidTransaction { + priority: 100, + requires: vec![], + provides: vec![("claims", eth(&alice())).encode()], + longevity: TransactionLongevity::max_value(), + propagate: true, + }) + ); + assert_eq!( + Pallet::::validate_unsigned( + source, + &ClaimsCall::claim { dest: 0, ethereum_signature: EcdsaSignature([0; 65]) } + ), + InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(), + ); + assert_eq!( + Pallet::::validate_unsigned( + source, + &ClaimsCall::claim { + dest: 1, + ethereum_signature: sig::(&bob(), &1u64.encode(), &[][..]) + } + ), + InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), + ); + let s = sig::(&dave(), &1u64.encode(), StatementKind::Regular.to_text()); + let call = ClaimsCall::claim_attest { + dest: 1, + ethereum_signature: s, + statement: StatementKind::Regular.to_text().to_vec(), + }; + assert_eq!( + Pallet::::validate_unsigned(source, &call), + Ok(ValidTransaction { + priority: 100, + requires: vec![], + provides: vec![("claims", eth(&dave())).encode()], + longevity: TransactionLongevity::max_value(), + propagate: true, + }) + ); + assert_eq!( + Pallet::::validate_unsigned( + source, + &ClaimsCall::claim_attest { + dest: 1, + ethereum_signature: EcdsaSignature([0; 65]), + statement: StatementKind::Regular.to_text().to_vec() + } + ), + InvalidTransaction::Custom(ValidityError::InvalidEthereumSignature.into()).into(), + ); + + let s = sig::(&bob(), &1u64.encode(), StatementKind::Regular.to_text()); + let call = ClaimsCall::claim_attest { + dest: 1, + ethereum_signature: s, + statement: StatementKind::Regular.to_text().to_vec(), + }; + assert_eq!( + Pallet::::validate_unsigned(source, &call), + InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), + ); + + let s = sig::(&dave(), &1u64.encode(), StatementKind::Saft.to_text()); + let call = ClaimsCall::claim_attest { + dest: 1, + ethereum_signature: s, + statement: StatementKind::Regular.to_text().to_vec(), + }; + assert_eq!( + Pallet::::validate_unsigned(source, &call), + InvalidTransaction::Custom(ValidityError::SignerHasNoClaim.into()).into(), + ); + + let s = sig::(&dave(), &1u64.encode(), StatementKind::Saft.to_text()); + let call = ClaimsCall::claim_attest { + dest: 1, + ethereum_signature: s, + statement: StatementKind::Saft.to_text().to_vec(), + }; + assert_eq!( + Pallet::::validate_unsigned(source, &call), + InvalidTransaction::Custom(ValidityError::InvalidStatement.into()).into(), + ); + }); +} From 586ab7f65ed64e46088466f3a90d0ac79513a6b4 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:42:25 +0000 Subject: [PATCH 182/340] add another token generation step (#6941) Closes #6940 --- .github/workflows/cmd.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index b6a50ea0d15e..2a4c1c6243fa 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -400,6 +400,14 @@ jobs: name: command-output path: /tmp/cmd/command_output.log + # Generate token for commit, as the earlier token expires after 1 hour, while cmd can take longer + - name: Generate token for commit + uses: actions/create-github-app-token@v1 + id: generate_token_commit + with: + app-id: ${{ secrets.CMD_BOT_APP_ID }} + private-key: ${{ secrets.CMD_BOT_APP_KEY }} + - name: Commit changes run: | if [ -n "$(git status --porcelain)" ]; then @@ -410,7 +418,7 @@ jobs: # Push the results to the target branch git remote add \ github \ - "https://token:${{ steps.generate_token.outputs.token }}@github.com/${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}.git" || : + "https://token:${{ steps.generate_token_commit.outputs.token }}@github.com/${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}.git" || : push_changes() { git push github "HEAD:${{ needs.get-pr-branch.outputs.pr-branch }}" From 9da3394c382de6f431f1db92b0c7d388e31284cc Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Wed, 18 Dec 2024 13:29:02 +0100 Subject: [PATCH 183/340] `pallet_xcm::execute` weights (#6919) Relates to: https://github.com/paritytech/polkadot-sdk/issues/6918 --------- Co-authored-by: command-bot <> --- .../src/weights/pallet_xcm.rs | 187 +++++++------ .../src/weights/pallet_xcm.rs | 187 +++++++------ .../src/weights/pallet_xcm.rs | 169 ++++++------ .../src/weights/pallet_xcm.rs | 169 ++++++------ .../src/weights/pallet_xcm.rs | 179 +++++++------ .../coretime-rococo/src/weights/pallet_xcm.rs | 173 ++++++------ .../src/weights/pallet_xcm.rs | 173 ++++++------ .../people-rococo/src/weights/pallet_xcm.rs | 177 ++++++------ .../people-westend/src/weights/pallet_xcm.rs | 177 ++++++------ .../runtime/rococo/src/weights/pallet_xcm.rs | 251 ++++++++++-------- .../runtime/westend/src/weights/pallet_xcm.rs | 241 +++++++++-------- 11 files changed, 1113 insertions(+), 970 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs index 51b6543bae82..8506125d4133 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `55b2c3410882`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=asset-hub-rococo-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=asset-hub-rococo-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,14 +66,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 22_136_000 picoseconds. - Weight::from_parts(22_518_000, 0) + // Minimum execution time: 28_401_000 picoseconds. + Weight::from_parts(29_326_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -90,18 +94,20 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 92_277_000 picoseconds. - Weight::from_parts(94_843_000, 0) + // Minimum execution time: 109_686_000 picoseconds. + Weight::from_parts(114_057_000, 0) .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) - /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -111,25 +117,29 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) - /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) - /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: Some(105506), added: 107981, mode: `MaxEncodedLen`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: // Measured: `400` // Estimated: `6196` - // Minimum execution time: 120_110_000 picoseconds. - Weight::from_parts(122_968_000, 0) + // Minimum execution time: 137_693_000 picoseconds. + Weight::from_parts(142_244_000, 0) .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Assets::Asset` (r:1 w:1) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) /// Storage: `Assets::Account` (r:2 w:2) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) @@ -146,23 +156,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `496` + // Measured: `537` // Estimated: `6208` - // Minimum execution time: 143_116_000 picoseconds. - Weight::from_parts(147_355_000, 0) + // Minimum execution time: 178_291_000 picoseconds. + Weight::from_parts(185_648_000, 0) .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().writes(7)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `103` + // Estimated: `1588` + // Minimum execution time: 14_014_000 picoseconds. + Weight::from_parts(14_522_000, 0) + .saturating_add(Weight::from_parts(0, 1588)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -170,8 +181,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_517_000 picoseconds. - Weight::from_parts(6_756_000, 0) + // Minimum execution time: 7_195_000 picoseconds. + Weight::from_parts(7_440_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -181,8 +192,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_894_000 picoseconds. - Weight::from_parts(2_024_000, 0) + // Minimum execution time: 2_278_000 picoseconds. + Weight::from_parts(2_488_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -208,8 +219,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 27_314_000 picoseconds. - Weight::from_parts(28_787_000, 0) + // Minimum execution time: 35_095_000 picoseconds. + Weight::from_parts(36_347_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -234,8 +245,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 29_840_000 picoseconds. - Weight::from_parts(30_589_000, 0) + // Minimum execution time: 38_106_000 picoseconds. + Weight::from_parts(38_959_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -246,45 +257,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_893_000 picoseconds. - Weight::from_parts(2_017_000, 0) + // Minimum execution time: 2_307_000 picoseconds. + Weight::from_parts(2_478_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `159` - // Estimated: `13524` - // Minimum execution time: 19_211_000 picoseconds. - Weight::from_parts(19_552_000, 0) - .saturating_add(Weight::from_parts(0, 13524)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15999` + // Minimum execution time: 25_238_000 picoseconds. + Weight::from_parts(25_910_000, 0) + .saturating_add(Weight::from_parts(0, 15999)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `163` - // Estimated: `13528` - // Minimum execution time: 19_177_000 picoseconds. - Weight::from_parts(19_704_000, 0) - .saturating_add(Weight::from_parts(0, 13528)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16003` + // Minimum execution time: 25_626_000 picoseconds. + Weight::from_parts(26_147_000, 0) + .saturating_add(Weight::from_parts(0, 16003)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `173` - // Estimated: `16013` - // Minimum execution time: 20_449_000 picoseconds. - Weight::from_parts(21_075_000, 0) - .saturating_add(Weight::from_parts(0, 16013)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18488` + // Minimum execution time: 28_528_000 picoseconds. + Weight::from_parts(28_882_000, 0) + .saturating_add(Weight::from_parts(0, 18488)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -304,36 +315,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 26_578_000 picoseconds. - Weight::from_parts(27_545_000, 0) + // Minimum execution time: 33_042_000 picoseconds. + Weight::from_parts(34_444_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `206` - // Estimated: `11096` - // Minimum execution time: 11_646_000 picoseconds. - Weight::from_parts(11_944_000, 0) - .saturating_add(Weight::from_parts(0, 11096)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `176` + // Estimated: `13541` + // Minimum execution time: 18_218_000 picoseconds. + Weight::from_parts(18_622_000, 0) + .saturating_add(Weight::from_parts(0, 13541)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `170` - // Estimated: `13535` - // Minimum execution time: 19_301_000 picoseconds. - Weight::from_parts(19_664_000, 0) - .saturating_add(Weight::from_parts(0, 13535)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16010` + // Minimum execution time: 25_838_000 picoseconds. + Weight::from_parts(26_276_000, 0) + .saturating_add(Weight::from_parts(0, 16010)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -350,11 +361,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `212` - // Estimated: `13577` - // Minimum execution time: 35_715_000 picoseconds. - Weight::from_parts(36_915_000, 0) - .saturating_add(Weight::from_parts(0, 13577)) - .saturating_add(T::DbWeight::get().reads(11)) + // Estimated: `16052` + // Minimum execution time: 46_196_000 picoseconds. + Weight::from_parts(47_859_000, 0) + .saturating_add(Weight::from_parts(0, 16052)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -365,8 +376,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 4_871_000 picoseconds. - Weight::from_parts(5_066_000, 0) + // Minimum execution time: 7_068_000 picoseconds. + Weight::from_parts(7_442_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -377,22 +388,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 25_150_000 picoseconds. - Weight::from_parts(26_119_000, 0) + // Minimum execution time: 31_497_000 picoseconds. + Weight::from_parts(31_975_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `160` // Estimated: `3625` - // Minimum execution time: 38_248_000 picoseconds. - Weight::from_parts(39_122_000, 0) + // Minimum execution time: 44_534_000 picoseconds. + Weight::from_parts(46_175_000, 0) .saturating_add(Weight::from_parts(0, 3625)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs index be3d7661ab3c..93409463d4e5 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-01, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-f3xfxtob-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `c0a5c14955e4`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=asset-hub-westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=asset-hub-westend-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,14 +66,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 21_050_000 picoseconds. - Weight::from_parts(21_834_000, 0) + // Minimum execution time: 28_333_000 picoseconds. + Weight::from_parts(29_115_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -90,18 +94,20 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 92_497_000 picoseconds. - Weight::from_parts(95_473_000, 0) + // Minimum execution time: 111_150_000 picoseconds. + Weight::from_parts(113_250_000, 0) .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) - /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -111,25 +117,29 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) - /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) - /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: Some(105506), added: 107981, mode: `MaxEncodedLen`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `367` + // Measured: `400` // Estimated: `6196` - // Minimum execution time: 120_059_000 picoseconds. - Weight::from_parts(122_894_000, 0) + // Minimum execution time: 135_730_000 picoseconds. + Weight::from_parts(140_479_000, 0) .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Assets::Asset` (r:1 w:1) /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) /// Storage: `Assets::Account` (r:2 w:2) /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) @@ -146,21 +156,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `496` + // Measured: `571` // Estimated: `6208` - // Minimum execution time: 141_977_000 picoseconds. - Weight::from_parts(145_981_000, 0) + // Minimum execution time: 174_654_000 picoseconds. + Weight::from_parts(182_260_000, 0) .saturating_add(Weight::from_parts(0, 6208)) - .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().writes(7)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 7_426_000 picoseconds. - Weight::from_parts(7_791_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `103` + // Estimated: `1588` + // Minimum execution time: 12_750_000 picoseconds. + Weight::from_parts(13_124_000, 0) + .saturating_add(Weight::from_parts(0, 1588)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -168,8 +181,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_224_000 picoseconds. - Weight::from_parts(6_793_000, 0) + // Minimum execution time: 7_083_000 picoseconds. + Weight::from_parts(7_353_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -179,8 +192,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_812_000 picoseconds. - Weight::from_parts(2_008_000, 0) + // Minimum execution time: 2_254_000 picoseconds. + Weight::from_parts(2_408_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -206,8 +219,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 26_586_000 picoseconds. - Weight::from_parts(27_181_000, 0) + // Minimum execution time: 34_983_000 picoseconds. + Weight::from_parts(35_949_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -232,8 +245,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 28_295_000 picoseconds. - Weight::from_parts(29_280_000, 0) + // Minimum execution time: 38_226_000 picoseconds. + Weight::from_parts(39_353_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -244,45 +257,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_803_000 picoseconds. - Weight::from_parts(1_876_000, 0) + // Minimum execution time: 2_254_000 picoseconds. + Weight::from_parts(2_432_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `159` - // Estimated: `13524` - // Minimum execution time: 18_946_000 picoseconds. - Weight::from_parts(19_456_000, 0) - .saturating_add(Weight::from_parts(0, 13524)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15999` + // Minimum execution time: 25_561_000 picoseconds. + Weight::from_parts(26_274_000, 0) + .saturating_add(Weight::from_parts(0, 15999)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `163` - // Estimated: `13528` - // Minimum execution time: 19_080_000 picoseconds. - Weight::from_parts(19_498_000, 0) - .saturating_add(Weight::from_parts(0, 13528)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16003` + // Minimum execution time: 25_950_000 picoseconds. + Weight::from_parts(26_532_000, 0) + .saturating_add(Weight::from_parts(0, 16003)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `173` - // Estimated: `16013` - // Minimum execution time: 20_637_000 picoseconds. - Weight::from_parts(21_388_000, 0) - .saturating_add(Weight::from_parts(0, 16013)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18488` + // Minimum execution time: 28_508_000 picoseconds. + Weight::from_parts(29_178_000, 0) + .saturating_add(Weight::from_parts(0, 18488)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -302,36 +315,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 25_701_000 picoseconds. - Weight::from_parts(26_269_000, 0) + // Minimum execution time: 33_244_000 picoseconds. + Weight::from_parts(33_946_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `206` - // Estimated: `11096` - // Minimum execution time: 11_949_000 picoseconds. - Weight::from_parts(12_249_000, 0) - .saturating_add(Weight::from_parts(0, 11096)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `176` + // Estimated: `13541` + // Minimum execution time: 18_071_000 picoseconds. + Weight::from_parts(18_677_000, 0) + .saturating_add(Weight::from_parts(0, 13541)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `170` - // Estimated: `13535` - // Minimum execution time: 19_278_000 picoseconds. - Weight::from_parts(19_538_000, 0) - .saturating_add(Weight::from_parts(0, 13535)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16010` + // Minimum execution time: 25_605_000 picoseconds. + Weight::from_parts(26_284_000, 0) + .saturating_add(Weight::from_parts(0, 16010)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -348,11 +361,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `212` - // Estimated: `13577` - // Minimum execution time: 35_098_000 picoseconds. - Weight::from_parts(35_871_000, 0) - .saturating_add(Weight::from_parts(0, 13577)) - .saturating_add(T::DbWeight::get().reads(11)) + // Estimated: `16052` + // Minimum execution time: 46_991_000 picoseconds. + Weight::from_parts(47_866_000, 0) + .saturating_add(Weight::from_parts(0, 16052)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -363,8 +376,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 3_862_000 picoseconds. - Weight::from_parts(4_082_000, 0) + // Minimum execution time: 5_685_000 picoseconds. + Weight::from_parts(5_816_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -375,22 +388,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 25_423_000 picoseconds. - Weight::from_parts(25_872_000, 0) + // Minimum execution time: 31_271_000 picoseconds. + Weight::from_parts(32_195_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `160` // Estimated: `3625` - // Minimum execution time: 37_148_000 picoseconds. - Weight::from_parts(37_709_000, 0) + // Minimum execution time: 43_530_000 picoseconds. + Weight::from_parts(44_942_000, 0) .saturating_add(Weight::from_parts(0, 3625)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs index a732e1a57343..0a085b858251 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `902e7ad7764b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=bridge-hub-rococo-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=bridge-hub-rococo-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,14 +66,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 18_513_000 picoseconds. - Weight::from_parts(19_156_000, 0) + // Minimum execution time: 25_273_000 picoseconds. + Weight::from_parts(25_810_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -90,10 +94,10 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `70` // Estimated: `3593` - // Minimum execution time: 88_096_000 picoseconds. - Weight::from_parts(89_732_000, 0) + // Minimum execution time: 112_156_000 picoseconds. + Weight::from_parts(115_999_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -108,6 +112,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -126,21 +132,22 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `70` // Estimated: `3593` - // Minimum execution time: 88_239_000 picoseconds. - Weight::from_parts(89_729_000, 0) + // Minimum execution time: 110_987_000 picoseconds. + Weight::from_parts(114_735_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 12_068_000 picoseconds. + Weight::from_parts(12_565_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -148,8 +155,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_955_000 picoseconds. - Weight::from_parts(6_266_000, 0) + // Minimum execution time: 7_155_000 picoseconds. + Weight::from_parts(7_606_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -159,8 +166,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_868_000 picoseconds. - Weight::from_parts(1_961_000, 0) + // Minimum execution time: 2_325_000 picoseconds. + Weight::from_parts(2_442_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -186,8 +193,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 24_388_000 picoseconds. - Weight::from_parts(25_072_000, 0) + // Minimum execution time: 31_747_000 picoseconds. + Weight::from_parts(33_122_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -212,8 +219,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 26_762_000 picoseconds. - Weight::from_parts(27_631_000, 0) + // Minimum execution time: 36_396_000 picoseconds. + Weight::from_parts(37_638_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -224,45 +231,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_856_000 picoseconds. - Weight::from_parts(2_033_000, 0) + // Minimum execution time: 2_470_000 picoseconds. + Weight::from_parts(2_594_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 17_718_000 picoseconds. - Weight::from_parts(18_208_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 22_530_000 picoseconds. + Weight::from_parts(22_987_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 17_597_000 picoseconds. - Weight::from_parts(18_090_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 23_016_000 picoseconds. + Weight::from_parts(23_461_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 19_533_000 picoseconds. - Weight::from_parts(20_164_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 26_216_000 picoseconds. + Weight::from_parts(26_832_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -282,36 +289,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 24_958_000 picoseconds. - Weight::from_parts(25_628_000, 0) + // Minimum execution time: 31_060_000 picoseconds. + Weight::from_parts(32_513_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 12_209_000 picoseconds. - Weight::from_parts(12_612_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 17_334_000 picoseconds. + Weight::from_parts(17_747_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 17_844_000 picoseconds. - Weight::from_parts(18_266_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 22_535_000 picoseconds. + Weight::from_parts(23_386_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -328,11 +335,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 34_131_000 picoseconds. - Weight::from_parts(34_766_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(11)) + // Estimated: `15946` + // Minimum execution time: 43_437_000 picoseconds. + Weight::from_parts(44_588_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -343,8 +350,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_525_000 picoseconds. - Weight::from_parts(3_724_000, 0) + // Minimum execution time: 4_941_000 picoseconds. + Weight::from_parts(5_088_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -355,22 +362,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 24_975_000 picoseconds. - Weight::from_parts(25_517_000, 0) + // Minimum execution time: 29_996_000 picoseconds. + Weight::from_parts(30_700_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 33_761_000 picoseconds. - Weight::from_parts(34_674_000, 0) + // Minimum execution time: 41_828_000 picoseconds. + Weight::from_parts(43_026_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs index a78ff2355efa..fdae0c9a1522 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `27f89d982f9b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=bridge-hub-westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=bridge-hub-westend-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -64,14 +66,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 19_527_000 picoseconds. - Weight::from_parts(19_839_000, 0) + // Minimum execution time: 24_819_000 picoseconds. + Weight::from_parts(25_795_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -90,10 +94,10 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `107` // Estimated: `3593` - // Minimum execution time: 90_938_000 picoseconds. - Weight::from_parts(92_822_000, 0) + // Minimum execution time: 110_536_000 picoseconds. + Weight::from_parts(115_459_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -108,6 +112,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -126,21 +132,22 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `107` // Estimated: `3593` - // Minimum execution time: 90_133_000 picoseconds. - Weight::from_parts(92_308_000, 0) + // Minimum execution time: 109_742_000 picoseconds. + Weight::from_parts(114_362_000, 0) .saturating_add(Weight::from_parts(0, 3593)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 12_252_000 picoseconds. + Weight::from_parts(12_681_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -148,8 +155,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_205_000 picoseconds. - Weight::from_parts(6_595_000, 0) + // Minimum execution time: 6_988_000 picoseconds. + Weight::from_parts(7_161_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -159,8 +166,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_927_000 picoseconds. - Weight::from_parts(2_062_000, 0) + // Minimum execution time: 2_249_000 picoseconds. + Weight::from_parts(2_479_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -186,8 +193,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 25_078_000 picoseconds. - Weight::from_parts(25_782_000, 0) + // Minimum execution time: 31_668_000 picoseconds. + Weight::from_parts(32_129_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -212,8 +219,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 28_188_000 picoseconds. - Weight::from_parts(28_826_000, 0) + // Minimum execution time: 36_002_000 picoseconds. + Weight::from_parts(37_341_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -224,45 +231,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_886_000 picoseconds. - Weight::from_parts(1_991_000, 0) + // Minimum execution time: 2_349_000 picoseconds. + Weight::from_parts(2_511_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 17_443_000 picoseconds. - Weight::from_parts(17_964_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 22_283_000 picoseconds. + Weight::from_parts(22_654_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 17_357_000 picoseconds. - Weight::from_parts(18_006_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 22_717_000 picoseconds. + Weight::from_parts(23_256_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 18_838_000 picoseconds. - Weight::from_parts(19_688_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 25_988_000 picoseconds. + Weight::from_parts(26_794_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -282,36 +289,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 25_517_000 picoseconds. - Weight::from_parts(26_131_000, 0) + // Minimum execution time: 31_112_000 picoseconds. + Weight::from_parts(32_395_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 11_587_000 picoseconds. - Weight::from_parts(11_963_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 17_401_000 picoseconds. + Weight::from_parts(17_782_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 17_490_000 picoseconds. - Weight::from_parts(18_160_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 22_772_000 picoseconds. + Weight::from_parts(23_194_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -328,11 +335,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 34_088_000 picoseconds. - Weight::from_parts(34_598_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(11)) + // Estimated: `15946` + // Minimum execution time: 43_571_000 picoseconds. + Weight::from_parts(44_891_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -343,8 +350,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_566_000 picoseconds. - Weight::from_parts(3_754_000, 0) + // Minimum execution time: 4_896_000 picoseconds. + Weight::from_parts(5_112_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -355,22 +362,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 25_078_000 picoseconds. - Weight::from_parts(25_477_000, 0) + // Minimum execution time: 30_117_000 picoseconds. + Weight::from_parts(31_027_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 34_661_000 picoseconds. - Weight::from_parts(35_411_000, 0) + // Minimum execution time: 41_870_000 picoseconds. + Weight::from_parts(42_750_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs index 5d427d850046..ccf88873c2cd 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `47a5bbdc8de3`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=collectives-westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=collectives-westend-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,6 +50,8 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo(PhantomData); impl pallet_xcm::WeightInfo for WeightInfo { + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -62,16 +66,18 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 21_813_000 picoseconds. - Weight::from_parts(22_332_000, 0) - .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `214` + // Estimated: `3679` + // Minimum execution time: 32_779_000 picoseconds. + Weight::from_parts(33_417_000, 0) + .saturating_add(Weight::from_parts(0, 3679)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -90,10 +96,10 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `214` // Estimated: `3679` - // Minimum execution time: 93_243_000 picoseconds. - Weight::from_parts(95_650_000, 0) + // Minimum execution time: 116_031_000 picoseconds. + Weight::from_parts(118_863_000, 0) .saturating_add(Weight::from_parts(0, 3679)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -108,6 +114,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) @@ -126,21 +134,22 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `214` // Estimated: `3679` - // Minimum execution time: 96_199_000 picoseconds. - Weight::from_parts(98_620_000, 0) + // Minimum execution time: 116_267_000 picoseconds. + Weight::from_parts(119_519_000, 0) .saturating_add(Weight::from_parts(0, 3679)) - .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `103` + // Estimated: `1588` + // Minimum execution time: 12_718_000 picoseconds. + Weight::from_parts(13_572_000, 0) + .saturating_add(Weight::from_parts(0, 1588)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -148,8 +157,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_442_000 picoseconds. - Weight::from_parts(6_682_000, 0) + // Minimum execution time: 7_568_000 picoseconds. + Weight::from_parts(7_913_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -159,8 +168,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_833_000 picoseconds. - Weight::from_parts(1_973_000, 0) + // Minimum execution time: 2_225_000 picoseconds. + Weight::from_parts(2_473_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -186,8 +195,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 27_318_000 picoseconds. - Weight::from_parts(28_224_000, 0) + // Minimum execution time: 35_869_000 picoseconds. + Weight::from_parts(37_848_000, 0) .saturating_add(Weight::from_parts(0, 3610)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(5)) @@ -212,8 +221,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `363` // Estimated: `3828` - // Minimum execution time: 29_070_000 picoseconds. - Weight::from_parts(30_205_000, 0) + // Minimum execution time: 38_649_000 picoseconds. + Weight::from_parts(39_842_000, 0) .saturating_add(Weight::from_parts(0, 3828)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -224,45 +233,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_904_000 picoseconds. - Weight::from_parts(2_033_000, 0) + // Minimum execution time: 2_223_000 picoseconds. + Weight::from_parts(2_483_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `159` - // Estimated: `13524` - // Minimum execution time: 18_348_000 picoseconds. - Weight::from_parts(18_853_000, 0) - .saturating_add(Weight::from_parts(0, 13524)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15999` + // Minimum execution time: 24_164_000 picoseconds. + Weight::from_parts(24_972_000, 0) + .saturating_add(Weight::from_parts(0, 15999)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `163` - // Estimated: `13528` - // Minimum execution time: 17_964_000 picoseconds. - Weight::from_parts(18_548_000, 0) - .saturating_add(Weight::from_parts(0, 13528)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16003` + // Minimum execution time: 24_604_000 picoseconds. + Weight::from_parts(25_047_000, 0) + .saturating_add(Weight::from_parts(0, 16003)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `173` - // Estimated: `16013` - // Minimum execution time: 19_708_000 picoseconds. - Weight::from_parts(20_157_000, 0) - .saturating_add(Weight::from_parts(0, 16013)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18488` + // Minimum execution time: 28_088_000 picoseconds. + Weight::from_parts(28_431_000, 0) + .saturating_add(Weight::from_parts(0, 18488)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -282,36 +291,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `212` // Estimated: `6152` - // Minimum execution time: 26_632_000 picoseconds. - Weight::from_parts(27_314_000, 0) + // Minimum execution time: 33_814_000 picoseconds. + Weight::from_parts(34_741_000, 0) .saturating_add(Weight::from_parts(0, 6152)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `206` - // Estimated: `11096` - // Minimum execution time: 11_929_000 picoseconds. - Weight::from_parts(12_304_000, 0) - .saturating_add(Weight::from_parts(0, 11096)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `176` + // Estimated: `13541` + // Minimum execution time: 18_242_000 picoseconds. + Weight::from_parts(18_636_000, 0) + .saturating_add(Weight::from_parts(0, 13541)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `170` - // Estimated: `13535` - // Minimum execution time: 18_599_000 picoseconds. - Weight::from_parts(19_195_000, 0) - .saturating_add(Weight::from_parts(0, 13535)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `16010` + // Minimum execution time: 24_249_000 picoseconds. + Weight::from_parts(24_768_000, 0) + .saturating_add(Weight::from_parts(0, 16010)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -328,11 +337,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `212` - // Estimated: `13577` - // Minimum execution time: 35_524_000 picoseconds. - Weight::from_parts(36_272_000, 0) - .saturating_add(Weight::from_parts(0, 13577)) - .saturating_add(T::DbWeight::get().reads(11)) + // Estimated: `16052` + // Minimum execution time: 47_602_000 picoseconds. + Weight::from_parts(48_378_000, 0) + .saturating_add(Weight::from_parts(0, 16052)) + .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -343,8 +352,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `103` // Estimated: `1588` - // Minimum execution time: 4_044_000 picoseconds. - Weight::from_parts(4_238_000, 0) + // Minimum execution time: 5_566_000 picoseconds. + Weight::from_parts(5_768_000, 0) .saturating_add(Weight::from_parts(0, 1588)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -355,22 +364,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7740` // Estimated: `11205` - // Minimum execution time: 25_741_000 picoseconds. - Weight::from_parts(26_301_000, 0) + // Minimum execution time: 30_821_000 picoseconds. + Weight::from_parts(31_250_000, 0) .saturating_add(Weight::from_parts(0, 11205)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `160` // Estimated: `3625` - // Minimum execution time: 35_925_000 picoseconds. - Weight::from_parts(36_978_000, 0) + // Minimum execution time: 43_463_000 picoseconds. + Weight::from_parts(44_960_000, 0) .saturating_add(Weight::from_parts(0, 3625)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs index 7fb492173dad..b2b8cd6e5349 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-05-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-unxyhko3-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `902e7ad7764b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=coretime-rococo-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=coretime-rococo-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,14 +64,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 19_121_000 picoseconds. - Weight::from_parts(19_582_000, 0) + // Minimum execution time: 23_660_000 picoseconds. + Weight::from_parts(24_537_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -84,18 +88,20 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 61_722_000 picoseconds. - Weight::from_parts(63_616_000, 0) + // Minimum execution time: 74_005_000 picoseconds. + Weight::from_parts(75_355_000, 0) .saturating_add(Weight::from_parts(0, 3571)) - .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Regions` (r:1 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) - /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -107,17 +113,17 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) - /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) - /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: Some(105506), added: 107981, mode: `MaxEncodedLen`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: // Measured: `377` // Estimated: `3842` - // Minimum execution time: 97_823_000 picoseconds. - Weight::from_parts(102_022_000, 0) + // Minimum execution time: 116_231_000 picoseconds. + Weight::from_parts(121_254_000, 0) .saturating_add(Weight::from_parts(0, 3842)) - .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -130,13 +136,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 8_397_000 picoseconds. - Weight::from_parts(8_773_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 11_498_000 picoseconds. + Weight::from_parts(11_867_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -144,8 +153,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_806_000 picoseconds. - Weight::from_parts(6_106_000, 0) + // Minimum execution time: 7_163_000 picoseconds. + Weight::from_parts(7_501_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -155,8 +164,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_802_000 picoseconds. - Weight::from_parts(1_939_000, 0) + // Minimum execution time: 2_188_000 picoseconds. + Weight::from_parts(2_356_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -180,8 +189,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 24_300_000 picoseconds. - Weight::from_parts(25_359_000, 0) + // Minimum execution time: 30_503_000 picoseconds. + Weight::from_parts(31_361_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -204,8 +213,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `292` // Estimated: `3757` - // Minimum execution time: 27_579_000 picoseconds. - Weight::from_parts(28_414_000, 0) + // Minimum execution time: 35_562_000 picoseconds. + Weight::from_parts(36_710_000, 0) .saturating_add(Weight::from_parts(0, 3757)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -216,45 +225,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_762_000 picoseconds. - Weight::from_parts(1_884_000, 0) + // Minimum execution time: 2_223_000 picoseconds. + Weight::from_parts(2_432_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 16_512_000 picoseconds. - Weight::from_parts(16_818_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 21_863_000 picoseconds. + Weight::from_parts(22_213_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 16_368_000 picoseconds. - Weight::from_parts(16_887_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 22_044_000 picoseconds. + Weight::from_parts(22_548_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 17_661_000 picoseconds. - Weight::from_parts(17_963_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 24_336_000 picoseconds. + Weight::from_parts(25_075_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -272,36 +281,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `142` // Estimated: `6082` - // Minimum execution time: 24_498_000 picoseconds. - Weight::from_parts(25_339_000, 0) + // Minimum execution time: 30_160_000 picoseconds. + Weight::from_parts(30_807_000, 0) .saturating_add(Weight::from_parts(0, 6082)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 10_675_000 picoseconds. - Weight::from_parts(11_106_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 16_129_000 picoseconds. + Weight::from_parts(16_686_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 16_520_000 picoseconds. - Weight::from_parts(16_915_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 21_844_000 picoseconds. + Weight::from_parts(22_452_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -316,11 +325,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `142` - // Estimated: `13507` - // Minimum execution time: 32_851_000 picoseconds. - Weight::from_parts(33_772_000, 0) - .saturating_add(Weight::from_parts(0, 13507)) - .saturating_add(T::DbWeight::get().reads(10)) + // Estimated: `15982` + // Minimum execution time: 42_336_000 picoseconds. + Weight::from_parts(43_502_000, 0) + .saturating_add(Weight::from_parts(0, 15982)) + .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -331,8 +340,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_373_000 picoseconds. - Weight::from_parts(3_534_000, 0) + // Minimum execution time: 4_682_000 picoseconds. + Weight::from_parts(4_902_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -343,22 +352,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 26_027_000 picoseconds. - Weight::from_parts(26_467_000, 0) + // Minimum execution time: 27_848_000 picoseconds. + Weight::from_parts(28_267_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 35_692_000 picoseconds. - Weight::from_parts(36_136_000, 0) + // Minimum execution time: 41_653_000 picoseconds. + Weight::from_parts(42_316_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs index fa588e982f09..7659b8a1ac7e 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-05-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-unxyhko3-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `eded932c29e2`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=coretime-westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=coretime-westend-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,14 +64,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 18_707_000 picoseconds. - Weight::from_parts(19_391_000, 0) + // Minimum execution time: 23_956_000 picoseconds. + Weight::from_parts(24_860_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -84,18 +88,20 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `3571` - // Minimum execution time: 61_874_000 picoseconds. - Weight::from_parts(63_862_000, 0) + // Minimum execution time: 74_020_000 picoseconds. + Weight::from_parts(76_288_000, 0) .saturating_add(Weight::from_parts(0, 3571)) - .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Broker::Regions` (r:1 w:1) /// Proof: `Broker::Regions` (`max_values`: None, `max_size`: Some(86), added: 2561, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::DeliveryFeeFactor` (r:1 w:0) - /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::DeliveryFeeFactor` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -107,17 +113,17 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Storage: `ParachainSystem::RelevantMessagingState` (r:1 w:0) /// Proof: `ParachainSystem::RelevantMessagingState` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmpQueue::OutboundXcmpStatus` (r:1 w:1) - /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpStatus` (`max_values`: Some(1), `max_size`: Some(1282), added: 1777, mode: `MaxEncodedLen`) /// Storage: `XcmpQueue::OutboundXcmpMessages` (r:0 w:1) - /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Proof: `XcmpQueue::OutboundXcmpMessages` (`max_values`: None, `max_size`: Some(105506), added: 107981, mode: `MaxEncodedLen`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: // Measured: `377` // Estimated: `3842` - // Minimum execution time: 98_657_000 picoseconds. - Weight::from_parts(101_260_000, 0) + // Minimum execution time: 118_691_000 picoseconds. + Weight::from_parts(128_472_000, 0) .saturating_add(Weight::from_parts(0, 3842)) - .saturating_add(T::DbWeight::get().reads(9)) + .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -130,13 +136,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 8_455_000 picoseconds. - Weight::from_parts(8_842_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 11_608_000 picoseconds. + Weight::from_parts(12_117_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -144,8 +153,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_850_000 picoseconds. - Weight::from_parts(6_044_000, 0) + // Minimum execution time: 7_574_000 picoseconds. + Weight::from_parts(8_305_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -155,8 +164,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_754_000 picoseconds. - Weight::from_parts(1_832_000, 0) + // Minimum execution time: 2_438_000 picoseconds. + Weight::from_parts(2_663_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -180,8 +189,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `74` // Estimated: `3539` - // Minimum execution time: 24_886_000 picoseconds. - Weight::from_parts(25_403_000, 0) + // Minimum execution time: 31_482_000 picoseconds. + Weight::from_parts(33_926_000, 0) .saturating_add(Weight::from_parts(0, 3539)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -204,8 +213,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `292` // Estimated: `3757` - // Minimum execution time: 28_114_000 picoseconds. - Weight::from_parts(28_414_000, 0) + // Minimum execution time: 35_869_000 picoseconds. + Weight::from_parts(37_030_000, 0) .saturating_add(Weight::from_parts(0, 3757)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -216,45 +225,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_713_000 picoseconds. - Weight::from_parts(1_810_000, 0) + // Minimum execution time: 2_385_000 picoseconds. + Weight::from_parts(2_588_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 15_910_000 picoseconds. - Weight::from_parts(16_256_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 21_919_000 picoseconds. + Weight::from_parts(22_926_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 15_801_000 picoseconds. - Weight::from_parts(16_298_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 22_588_000 picoseconds. + Weight::from_parts(23_144_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 17_976_000 picoseconds. - Weight::from_parts(18_390_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 25_527_000 picoseconds. + Weight::from_parts(26_002_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -272,36 +281,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `142` // Estimated: `6082` - // Minimum execution time: 24_723_000 picoseconds. - Weight::from_parts(25_531_000, 0) + // Minimum execution time: 30_751_000 picoseconds. + Weight::from_parts(31_977_000, 0) .saturating_add(Weight::from_parts(0, 6082)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 10_954_000 picoseconds. - Weight::from_parts(11_199_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 16_496_000 picoseconds. + Weight::from_parts(16_800_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 16_561_000 picoseconds. - Weight::from_parts(16_908_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 22_667_000 picoseconds. + Weight::from_parts(23_049_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -316,11 +325,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `142` - // Estimated: `13507` - // Minimum execution time: 33_279_000 picoseconds. - Weight::from_parts(33_869_000, 0) - .saturating_add(Weight::from_parts(0, 13507)) - .saturating_add(T::DbWeight::get().reads(10)) + // Estimated: `15982` + // Minimum execution time: 43_208_000 picoseconds. + Weight::from_parts(44_012_000, 0) + .saturating_add(Weight::from_parts(0, 15982)) + .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -331,8 +340,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_405_000 picoseconds. - Weight::from_parts(3_489_000, 0) + // Minimum execution time: 4_726_000 picoseconds. + Weight::from_parts(4_989_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -343,22 +352,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 24_387_000 picoseconds. - Weight::from_parts(25_143_000, 0) + // Minimum execution time: 28_064_000 picoseconds. + Weight::from_parts(28_676_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 35_229_000 picoseconds. - Weight::from_parts(36_035_000, 0) + // Minimum execution time: 41_106_000 picoseconds. + Weight::from_parts(41_949_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs index fabce29b5fd9..d50afdbee475 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `902e7ad7764b`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-rococo-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=people-rococo-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=people-rococo-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,6 +50,8 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo(PhantomData); impl pallet_xcm::WeightInfo for WeightInfo { + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -60,16 +64,18 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 17_830_000 picoseconds. - Weight::from_parts(18_411_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 29_029_000 picoseconds. + Weight::from_parts(29_911_000, 0) + .saturating_add(Weight::from_parts(0, 3572)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -82,12 +88,12 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 55_456_000 picoseconds. - Weight::from_parts(56_808_000, 0) - .saturating_add(Weight::from_parts(0, 3535)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 73_046_000 picoseconds. + Weight::from_parts(76_061_000, 0) + .saturating_add(Weight::from_parts(0, 3572)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -110,15 +116,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 11_580_000 picoseconds. + Weight::from_parts(12_050_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -126,8 +133,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_996_000 picoseconds. - Weight::from_parts(6_154_000, 0) + // Minimum execution time: 6_963_000 picoseconds. + Weight::from_parts(7_371_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -137,8 +144,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_768_000 picoseconds. - Weight::from_parts(1_914_000, 0) + // Minimum execution time: 2_281_000 picoseconds. + Weight::from_parts(2_417_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -162,8 +169,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 24_120_000 picoseconds. - Weight::from_parts(24_745_000, 0) + // Minimum execution time: 30_422_000 picoseconds. + Weight::from_parts(31_342_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -186,8 +193,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 26_630_000 picoseconds. - Weight::from_parts(27_289_000, 0) + // Minimum execution time: 35_290_000 picoseconds. + Weight::from_parts(36_161_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -198,45 +205,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_821_000 picoseconds. - Weight::from_parts(1_946_000, 0) + // Minimum execution time: 2_115_000 picoseconds. + Weight::from_parts(2_389_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 16_586_000 picoseconds. - Weight::from_parts(16_977_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 22_355_000 picoseconds. + Weight::from_parts(23_011_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 16_923_000 picoseconds. - Weight::from_parts(17_415_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 22_043_000 picoseconds. + Weight::from_parts(22_506_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 18_596_000 picoseconds. - Weight::from_parts(18_823_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 26_143_000 picoseconds. + Weight::from_parts(26_577_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -254,36 +261,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 23_817_000 picoseconds. - Weight::from_parts(24_520_000, 0) + // Minimum execution time: 30_489_000 picoseconds. + Weight::from_parts(31_415_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 11_042_000 picoseconds. - Weight::from_parts(11_578_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 16_848_000 picoseconds. + Weight::from_parts(17_169_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 17_306_000 picoseconds. - Weight::from_parts(17_817_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 22_556_000 picoseconds. + Weight::from_parts(22_875_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -298,11 +305,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 32_141_000 picoseconds. - Weight::from_parts(32_954_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(10)) + // Estimated: `15946` + // Minimum execution time: 42_772_000 picoseconds. + Weight::from_parts(43_606_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -313,8 +320,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_410_000 picoseconds. - Weight::from_parts(3_556_000, 0) + // Minimum execution time: 4_811_000 picoseconds. + Weight::from_parts(5_060_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -325,22 +332,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 25_021_000 picoseconds. - Weight::from_parts(25_240_000, 0) + // Minimum execution time: 31_925_000 picoseconds. + Weight::from_parts(32_294_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 33_801_000 picoseconds. - Weight::from_parts(34_655_000, 0) + // Minimum execution time: 41_804_000 picoseconds. + Weight::from_parts(42_347_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs index c337289243b7..f06669209a18 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `4105cf7eb2c7`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=people-westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=people-westend-dev -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,6 +50,8 @@ use core::marker::PhantomData; /// Weight functions for `pallet_xcm`. pub struct WeightInfo(PhantomData); impl pallet_xcm::WeightInfo for WeightInfo { + /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) + /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -60,16 +64,18 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 17_856_000 picoseconds. - Weight::from_parts(18_473_000, 0) - .saturating_add(Weight::from_parts(0, 3503)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 29_434_000 picoseconds. + Weight::from_parts(30_114_000, 0) + .saturating_add(Weight::from_parts(0, 3572)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) @@ -82,12 +88,12 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 56_112_000 picoseconds. - Weight::from_parts(57_287_000, 0) - .saturating_add(Weight::from_parts(0, 3535)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `107` + // Estimated: `3572` + // Minimum execution time: 73_433_000 picoseconds. + Weight::from_parts(75_377_000, 0) + .saturating_add(Weight::from_parts(0, 3572)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Benchmark::Override` (r:0 w:0) @@ -110,15 +116,16 @@ impl pallet_xcm::WeightInfo for WeightInfo { Weight::from_parts(18_446_744_073_709_551_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `32` + // Estimated: `1517` + // Minimum execution time: 11_627_000 picoseconds. + Weight::from_parts(12_034_000, 0) + .saturating_add(Weight::from_parts(0, 1517)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -126,8 +133,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_186_000 picoseconds. - Weight::from_parts(6_420_000, 0) + // Minimum execution time: 7_075_000 picoseconds. + Weight::from_parts(7_406_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -137,8 +144,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_824_000 picoseconds. - Weight::from_parts(1_999_000, 0) + // Minimum execution time: 2_308_000 picoseconds. + Weight::from_parts(2_485_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -162,8 +169,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `38` // Estimated: `3503` - // Minimum execution time: 23_833_000 picoseconds. - Weight::from_parts(24_636_000, 0) + // Minimum execution time: 29_939_000 picoseconds. + Weight::from_parts(30_795_000, 0) .saturating_add(Weight::from_parts(0, 3503)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) @@ -186,8 +193,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `255` // Estimated: `3720` - // Minimum execution time: 26_557_000 picoseconds. - Weight::from_parts(27_275_000, 0) + // Minimum execution time: 34_830_000 picoseconds. + Weight::from_parts(35_677_000, 0) .saturating_add(Weight::from_parts(0, 3720)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -198,45 +205,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_921_000 picoseconds. - Weight::from_parts(2_040_000, 0) + // Minimum execution time: 2_363_000 picoseconds. + Weight::from_parts(2_517_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SupportedVersion` (r:5 w:2) + /// Storage: `PolkadotXcm::SupportedVersion` (r:6 w:2) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `89` - // Estimated: `13454` - // Minimum execution time: 16_832_000 picoseconds. - Weight::from_parts(17_312_000, 0) - .saturating_add(Weight::from_parts(0, 13454)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15929` + // Minimum execution time: 22_322_000 picoseconds. + Weight::from_parts(22_709_000, 0) + .saturating_add(Weight::from_parts(0, 15929)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifiers` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifiers` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `93` - // Estimated: `13458` - // Minimum execution time: 16_687_000 picoseconds. - Weight::from_parts(17_123_000, 0) - .saturating_add(Weight::from_parts(0, 13458)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15933` + // Minimum execution time: 22_418_000 picoseconds. + Weight::from_parts(22_834_000, 0) + .saturating_add(Weight::from_parts(0, 15933)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:7 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `15946` - // Minimum execution time: 18_164_000 picoseconds. - Weight::from_parts(18_580_000, 0) - .saturating_add(Weight::from_parts(0, 15946)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18421` + // Minimum execution time: 26_310_000 picoseconds. + Weight::from_parts(26_623_000, 0) + .saturating_add(Weight::from_parts(0, 18421)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -254,36 +261,36 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `106` // Estimated: `6046` - // Minimum execution time: 23_577_000 picoseconds. - Weight::from_parts(24_324_000, 0) + // Minimum execution time: 29_863_000 picoseconds. + Weight::from_parts(30_467_000, 0) .saturating_add(Weight::from_parts(0, 6046)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:4 w:0) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `136` - // Estimated: `11026` - // Minimum execution time: 11_014_000 picoseconds. - Weight::from_parts(11_223_000, 0) - .saturating_add(Weight::from_parts(0, 11026)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `109` + // Estimated: `13474` + // Minimum execution time: 17_075_000 picoseconds. + Weight::from_parts(17_578_000, 0) + .saturating_add(Weight::from_parts(0, 13474)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `100` - // Estimated: `13465` - // Minimum execution time: 16_887_000 picoseconds. - Weight::from_parts(17_361_000, 0) - .saturating_add(Weight::from_parts(0, 13465)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15940` + // Minimum execution time: 22_816_000 picoseconds. + Weight::from_parts(23_175_000, 0) + .saturating_add(Weight::from_parts(0, 15940)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:2) + /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -298,11 +305,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: // Measured: `106` - // Estimated: `13471` - // Minimum execution time: 31_705_000 picoseconds. - Weight::from_parts(32_166_000, 0) - .saturating_add(Weight::from_parts(0, 13471)) - .saturating_add(T::DbWeight::get().reads(10)) + // Estimated: `15946` + // Minimum execution time: 42_767_000 picoseconds. + Weight::from_parts(43_308_000, 0) + .saturating_add(Weight::from_parts(0, 15946)) + .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) @@ -313,8 +320,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `32` // Estimated: `1517` - // Minimum execution time: 3_568_000 picoseconds. - Weight::from_parts(3_669_000, 0) + // Minimum execution time: 4_864_000 picoseconds. + Weight::from_parts(5_010_000, 0) .saturating_add(Weight::from_parts(0, 1517)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -325,22 +332,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7669` // Estimated: `11134` - // Minimum execution time: 24_823_000 picoseconds. - Weight::from_parts(25_344_000, 0) + // Minimum execution time: 30_237_000 picoseconds. + Weight::from_parts(30_662_000, 0) .saturating_add(Weight::from_parts(0, 11134)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) + /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `90` // Estimated: `3555` - // Minimum execution time: 34_516_000 picoseconds. - Weight::from_parts(35_478_000, 0) + // Minimum execution time: 41_418_000 picoseconds. + Weight::from_parts(42_011_000, 0) .saturating_add(Weight::from_parts(0, 3555)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs index d5cf33515e6b..b60165934f92 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_xcm.rs @@ -17,27 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `65a7f4d3191f`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet +// --extrinsic=* // --chain=rococo-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --pallet=pallet_xcm -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./polkadot/file_header.txt -// --output=./polkadot/runtime/rococo/src/weights/ +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,38 +56,46 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `180` - // Estimated: `3645` - // Minimum execution time: 25_521_000 picoseconds. - Weight::from_parts(25_922_000, 0) - .saturating_add(Weight::from_parts(0, 3645)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `245` + // Estimated: `3710` + // Minimum execution time: 37_787_000 picoseconds. + Weight::from_parts(39_345_000, 0) + .saturating_add(Weight::from_parts(0, 3710)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `180` - // Estimated: `3645` - // Minimum execution time: 112_185_000 picoseconds. - Weight::from_parts(115_991_000, 0) - .saturating_add(Weight::from_parts(0, 3645)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `245` + // Estimated: `3710` + // Minimum execution time: 138_755_000 picoseconds. + Weight::from_parts(142_908_000, 0) + .saturating_add(Weight::from_parts(0, 3710)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) @@ -96,45 +104,54 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `232` - // Estimated: `3697` - // Minimum execution time: 108_693_000 picoseconds. - Weight::from_parts(111_853_000, 0) - .saturating_add(Weight::from_parts(0, 3697)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `297` + // Estimated: `3762` + // Minimum execution time: 134_917_000 picoseconds. + Weight::from_parts(138_809_000, 0) + .saturating_add(Weight::from_parts(0, 3762)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `180` - // Estimated: `3645` - // Minimum execution time: 113_040_000 picoseconds. - Weight::from_parts(115_635_000, 0) - .saturating_add(Weight::from_parts(0, 3645)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `245` + // Estimated: `3710` + // Minimum execution time: 141_303_000 picoseconds. + Weight::from_parts(144_640_000, 0) + .saturating_add(Weight::from_parts(0, 3710)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `0` - // Minimum execution time: 6_979_000 picoseconds. - Weight::from_parts(7_342_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Estimated: `1485` + // Minimum execution time: 9_872_000 picoseconds. + Weight::from_parts(10_402_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `XcmPallet::SupportedVersion` (r:0 w:1) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -142,8 +159,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_144_000 picoseconds. - Weight::from_parts(7_297_000, 0) + // Minimum execution time: 8_312_000 picoseconds. + Weight::from_parts(8_867_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -151,8 +168,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_886_000 picoseconds. - Weight::from_parts(1_995_000, 0) + // Minimum execution time: 2_524_000 picoseconds. + Weight::from_parts(2_800_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) @@ -165,18 +182,20 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::Queries` (r:0 w:1) /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `180` - // Estimated: `3645` - // Minimum execution time: 31_238_000 picoseconds. - Weight::from_parts(31_955_000, 0) - .saturating_add(Weight::from_parts(0, 3645)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `245` + // Estimated: `3710` + // Minimum execution time: 45_426_000 picoseconds. + Weight::from_parts(48_021_000, 0) + .saturating_add(Weight::from_parts(0, 3710)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) @@ -187,18 +206,20 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::Queries` (r:0 w:1) /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `360` - // Estimated: `3825` - // Minimum execution time: 37_237_000 picoseconds. - Weight::from_parts(38_569_000, 0) - .saturating_add(Weight::from_parts(0, 3825)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `425` + // Estimated: `3890` + // Minimum execution time: 50_854_000 picoseconds. + Weight::from_parts(52_044_000, 0) + .saturating_add(Weight::from_parts(0, 3890)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `XcmPallet::XcmExecutionSuspended` (r:0 w:1) @@ -207,45 +228,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_884_000 picoseconds. - Weight::from_parts(2_028_000, 0) + // Minimum execution time: 2_566_000 picoseconds. + Weight::from_parts(2_771_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `XcmPallet::SupportedVersion` (r:5 w:2) + /// Storage: `XcmPallet::SupportedVersion` (r:6 w:2) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `22` - // Estimated: `13387` - // Minimum execution time: 16_048_000 picoseconds. - Weight::from_parts(16_617_000, 0) - .saturating_add(Weight::from_parts(0, 13387)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15862` + // Minimum execution time: 21_854_000 picoseconds. + Weight::from_parts(22_528_000, 0) + .saturating_add(Weight::from_parts(0, 15862)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifiers` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifiers` (r:6 w:2) /// Proof: `XcmPallet::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `26` - // Estimated: `13391` - // Minimum execution time: 16_073_000 picoseconds. - Weight::from_parts(16_672_000, 0) - .saturating_add(Weight::from_parts(0, 13391)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15866` + // Minimum execution time: 21_821_000 picoseconds. + Weight::from_parts(22_368_000, 0) + .saturating_add(Weight::from_parts(0, 15866)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:0) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:7 w:0) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `40` - // Estimated: `15880` - // Minimum execution time: 18_422_000 picoseconds. - Weight::from_parts(18_900_000, 0) - .saturating_add(Weight::from_parts(0, 15880)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18355` + // Minimum execution time: 25_795_000 picoseconds. + Weight::from_parts(26_284_000, 0) + .saturating_add(Weight::from_parts(0, 18355)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `XcmPallet::VersionNotifyTargets` (r:2 w:1) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -253,62 +274,62 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) + /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:0) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `216` - // Estimated: `6156` - // Minimum execution time: 30_373_000 picoseconds. - Weight::from_parts(30_972_000, 0) - .saturating_add(Weight::from_parts(0, 6156)) + // Measured: `244` + // Estimated: `6184` + // Minimum execution time: 33_182_000 picoseconds. + Weight::from_parts(34_506_000, 0) + .saturating_add(Weight::from_parts(0, 6184)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:4 w:0) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:0) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `69` - // Estimated: `10959` - // Minimum execution time: 11_863_000 picoseconds. - Weight::from_parts(12_270_000, 0) - .saturating_add(Weight::from_parts(0, 10959)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `40` + // Estimated: `13405` + // Minimum execution time: 17_573_000 picoseconds. + Weight::from_parts(18_154_000, 0) + .saturating_add(Weight::from_parts(0, 13405)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:2) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `33` - // Estimated: `13398` - // Minimum execution time: 16_733_000 picoseconds. - Weight::from_parts(17_094_000, 0) - .saturating_add(Weight::from_parts(0, 13398)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15873` + // Minimum execution time: 22_491_000 picoseconds. + Weight::from_parts(22_793_000, 0) + .saturating_add(Weight::from_parts(0, 15873)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:1) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) + /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:0) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `216` - // Estimated: `13581` - // Minimum execution time: 39_236_000 picoseconds. - Weight::from_parts(40_587_000, 0) - .saturating_add(Weight::from_parts(0, 13581)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `244` + // Estimated: `16084` + // Minimum execution time: 44_441_000 picoseconds. + Weight::from_parts(45_782_000, 0) + .saturating_add(Weight::from_parts(0, 16084)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `XcmPallet::QueryCounter` (r:1 w:1) /// Proof: `XcmPallet::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -318,8 +339,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 2_145_000 picoseconds. - Weight::from_parts(2_255_000, 0) + // Minimum execution time: 2_809_000 picoseconds. + Weight::from_parts(2_960_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -330,22 +351,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7576` // Estimated: `11041` - // Minimum execution time: 22_518_000 picoseconds. - Weight::from_parts(22_926_000, 0) + // Minimum execution time: 26_248_000 picoseconds. + Weight::from_parts(26_996_000, 0) .saturating_add(Weight::from_parts(0, 11041)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::AssetTraps` (r:1 w:1) /// Proof: `XcmPallet::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `23` // Estimated: `3488` - // Minimum execution time: 34_438_000 picoseconds. - Weight::from_parts(35_514_000, 0) + // Minimum execution time: 40_299_000 picoseconds. + Weight::from_parts(41_396_000, 0) .saturating_add(Weight::from_parts(0, 3488)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } diff --git a/polkadot/runtime/westend/src/weights/pallet_xcm.rs b/polkadot/runtime/westend/src/weights/pallet_xcm.rs index 10725cecf249..e2c0232139fb 100644 --- a/polkadot/runtime/westend/src/weights/pallet_xcm.rs +++ b/polkadot/runtime/westend/src/weights/pallet_xcm.rs @@ -17,25 +17,27 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `3a528d69c69e`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: // target/production/polkadot // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=westend-dev +// --pallet=pallet_xcm +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm -// --chain=westend-dev -// --header=./polkadot/file_header.txt -// --output=./polkadot/runtime/westend/src/weights/ +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,38 +56,46 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `147` - // Estimated: `3612` - // Minimum execution time: 25_725_000 picoseconds. - Weight::from_parts(26_174_000, 0) - .saturating_add(Weight::from_parts(0, 3612)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `212` + // Estimated: `3677` + // Minimum execution time: 41_425_000 picoseconds. + Weight::from_parts(43_275_000, 0) + .saturating_add(Weight::from_parts(0, 3677)) + .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `250` + // Measured: `315` // Estimated: `6196` - // Minimum execution time: 113_140_000 picoseconds. - Weight::from_parts(116_204_000, 0) + // Minimum execution time: 145_227_000 picoseconds. + Weight::from_parts(151_656_000, 0) .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(4)) } + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) @@ -94,47 +104,54 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn reserve_transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `302` + // Measured: `367` // Estimated: `6196` - // Minimum execution time: 108_571_000 picoseconds. - Weight::from_parts(110_650_000, 0) + // Minimum execution time: 141_439_000 picoseconds. + Weight::from_parts(146_252_000, 0) .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `System::Account` (r:2 w:2) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `250` + // Measured: `315` // Estimated: `6196` - // Minimum execution time: 111_836_000 picoseconds. - Weight::from_parts(114_435_000, 0) + // Minimum execution time: 146_651_000 picoseconds. + Weight::from_parts(150_134_000, 0) .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(4)) } - /// Storage: `Benchmark::Override` (r:0 w:0) - /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `0` - // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. - Weight::from_parts(18_446_744_073_709_551_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Estimated: `1485` + // Minimum execution time: 9_663_000 picoseconds. + Weight::from_parts(10_012_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `XcmPallet::SupportedVersion` (r:0 w:1) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -142,8 +159,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_160_000 picoseconds. - Weight::from_parts(7_477_000, 0) + // Minimum execution time: 8_113_000 picoseconds. + Weight::from_parts(8_469_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -151,8 +168,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_934_000 picoseconds. - Weight::from_parts(2_053_000, 0) + // Minimum execution time: 2_493_000 picoseconds. + Weight::from_parts(2_630_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) @@ -165,18 +182,20 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::Queries` (r:0 w:1) /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `147` - // Estimated: `3612` - // Minimum execution time: 31_123_000 picoseconds. - Weight::from_parts(31_798_000, 0) - .saturating_add(Weight::from_parts(0, 3612)) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `212` + // Estimated: `3677` + // Minimum execution time: 47_890_000 picoseconds. + Weight::from_parts(49_994_000, 0) + .saturating_add(Weight::from_parts(0, 3677)) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `XcmPallet::VersionNotifiers` (r:1 w:1) @@ -187,18 +206,20 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::Queries` (r:0 w:1) /// Proof: `XcmPallet::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `327` - // Estimated: `3792` - // Minimum execution time: 35_175_000 picoseconds. - Weight::from_parts(36_098_000, 0) - .saturating_add(Weight::from_parts(0, 3792)) - .saturating_add(T::DbWeight::get().reads(5)) + // Measured: `392` + // Estimated: `3857` + // Minimum execution time: 52_967_000 picoseconds. + Weight::from_parts(55_345_000, 0) + .saturating_add(Weight::from_parts(0, 3857)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `XcmPallet::XcmExecutionSuspended` (r:0 w:1) @@ -207,45 +228,45 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_974_000 picoseconds. - Weight::from_parts(2_096_000, 0) + // Minimum execution time: 2_451_000 picoseconds. + Weight::from_parts(2_623_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `XcmPallet::SupportedVersion` (r:5 w:2) + /// Storage: `XcmPallet::SupportedVersion` (r:6 w:2) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: // Measured: `22` - // Estimated: `13387` - // Minimum execution time: 16_626_000 picoseconds. - Weight::from_parts(17_170_000, 0) - .saturating_add(Weight::from_parts(0, 13387)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15862` + // Minimum execution time: 22_292_000 picoseconds. + Weight::from_parts(22_860_000, 0) + .saturating_add(Weight::from_parts(0, 15862)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifiers` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifiers` (r:6 w:2) /// Proof: `XcmPallet::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: // Measured: `26` - // Estimated: `13391` - // Minimum execution time: 16_937_000 picoseconds. - Weight::from_parts(17_447_000, 0) - .saturating_add(Weight::from_parts(0, 13391)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15866` + // Minimum execution time: 21_847_000 picoseconds. + Weight::from_parts(22_419_000, 0) + .saturating_add(Weight::from_parts(0, 15866)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:0) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:7 w:0) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: // Measured: `40` - // Estimated: `15880` - // Minimum execution time: 19_157_000 picoseconds. - Weight::from_parts(19_659_000, 0) - .saturating_add(Weight::from_parts(0, 15880)) - .saturating_add(T::DbWeight::get().reads(6)) + // Estimated: `18355` + // Minimum execution time: 24_764_000 picoseconds. + Weight::from_parts(25_873_000, 0) + .saturating_add(Weight::from_parts(0, 18355)) + .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `XcmPallet::VersionNotifyTargets` (r:2 w:1) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -253,62 +274,62 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) + /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:0) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `183` - // Estimated: `6123` - // Minimum execution time: 30_699_000 picoseconds. - Weight::from_parts(31_537_000, 0) - .saturating_add(Weight::from_parts(0, 6123)) + // Measured: `211` + // Estimated: `6151` + // Minimum execution time: 36_482_000 picoseconds. + Weight::from_parts(37_672_000, 0) + .saturating_add(Weight::from_parts(0, 6151)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:4 w:0) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:0) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `69` - // Estimated: `10959` - // Minimum execution time: 12_303_000 picoseconds. - Weight::from_parts(12_670_000, 0) - .saturating_add(Weight::from_parts(0, 10959)) - .saturating_add(T::DbWeight::get().reads(4)) + // Measured: `40` + // Estimated: `13405` + // Minimum execution time: 17_580_000 picoseconds. + Weight::from_parts(17_908_000, 0) + .saturating_add(Weight::from_parts(0, 13405)) + .saturating_add(T::DbWeight::get().reads(5)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:2) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: // Measured: `33` - // Estimated: `13398` - // Minimum execution time: 17_129_000 picoseconds. - Weight::from_parts(17_668_000, 0) - .saturating_add(Weight::from_parts(0, 13398)) - .saturating_add(T::DbWeight::get().reads(5)) + // Estimated: `15873` + // Minimum execution time: 21_946_000 picoseconds. + Weight::from_parts(22_548_000, 0) + .saturating_add(Weight::from_parts(0, 15873)) + .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: `XcmPallet::VersionNotifyTargets` (r:5 w:2) + /// Storage: `XcmPallet::VersionNotifyTargets` (r:6 w:1) /// Proof: `XcmPallet::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Dmp::DeliveryFeeFactor` (r:1 w:0) /// Proof: `Dmp::DeliveryFeeFactor` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::SupportedVersion` (r:1 w:0) /// Proof: `XcmPallet::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:1) + /// Storage: `Dmp::DownwardMessageQueues` (r:1 w:0) /// Proof: `Dmp::DownwardMessageQueues` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Dmp::DownwardMessageQueueHeads` (r:1 w:1) - /// Proof: `Dmp::DownwardMessageQueueHeads` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Paras::Heads` (r:1 w:0) + /// Proof: `Paras::Heads` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `183` - // Estimated: `13548` - // Minimum execution time: 39_960_000 picoseconds. - Weight::from_parts(41_068_000, 0) - .saturating_add(Weight::from_parts(0, 13548)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `211` + // Estimated: `16051` + // Minimum execution time: 47_261_000 picoseconds. + Weight::from_parts(48_970_000, 0) + .saturating_add(Weight::from_parts(0, 16051)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `XcmPallet::QueryCounter` (r:1 w:1) /// Proof: `XcmPallet::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -318,8 +339,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `1485` - // Minimum execution time: 2_333_000 picoseconds. - Weight::from_parts(2_504_000, 0) + // Minimum execution time: 2_794_000 picoseconds. + Weight::from_parts(2_895_000, 0) .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) @@ -330,22 +351,24 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `7576` // Estimated: `11041` - // Minimum execution time: 22_932_000 picoseconds. - Weight::from_parts(23_307_000, 0) + // Minimum execution time: 25_946_000 picoseconds. + Weight::from_parts(26_503_000, 0) .saturating_add(Weight::from_parts(0, 11041)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `XcmPallet::ShouldRecordXcm` (r:1 w:0) + /// Proof: `XcmPallet::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `XcmPallet::AssetTraps` (r:1 w:1) /// Proof: `XcmPallet::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: // Measured: `23` // Estimated: `3488` - // Minimum execution time: 34_558_000 picoseconds. - Weight::from_parts(35_299_000, 0) + // Minimum execution time: 40_780_000 picoseconds. + Weight::from_parts(41_910_000, 0) .saturating_add(Weight::from_parts(0, 3488)) - .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } } From 6ad748857e0e63c38cb7bb9435831ae73ab708cf Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 18 Dec 2024 14:02:19 +0100 Subject: [PATCH 184/340] [pallet-revive] implement the ref_time_left API (#6908) This PR implements the ref_time_left API method. Solidity knows only a single "gas" dimension; Solidity contracts will use this to query the gas left. --------- Signed-off-by: xermicus Signed-off-by: Cyrill Leutwiler Co-authored-by: command-bot <> --- prdoc/pr_6908.prdoc | 12 + .../fixtures/contracts/ref_time_left.rs | 34 + .../frame/revive/src/benchmarking/mod.rs | 12 + substrate/frame/revive/src/tests.rs | 21 + substrate/frame/revive/src/wasm/runtime.rs | 11 + substrate/frame/revive/src/weights.rs | 917 +++++++++--------- substrate/frame/revive/uapi/src/host.rs | 3 + .../frame/revive/uapi/src/host/riscv64.rs | 5 + 8 files changed, 562 insertions(+), 453 deletions(-) create mode 100644 prdoc/pr_6908.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/ref_time_left.rs diff --git a/prdoc/pr_6908.prdoc b/prdoc/pr_6908.prdoc new file mode 100644 index 000000000000..0be9e613f88a --- /dev/null +++ b/prdoc/pr_6908.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] implement the ref_time_left API' +doc: +- audience: Runtime Dev + description: This PR implements the ref_time_left API method. Solidity knows only + a single "gas" dimension; Solidity contracts will use this to query the gas left. +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/ref_time_left.rs b/substrate/frame/revive/fixtures/contracts/ref_time_left.rs new file mode 100644 index 000000000000..aa892a8ba440 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/ref_time_left.rs @@ -0,0 +1,34 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() { + assert!(api::ref_time_left() > api::ref_time_left()); +} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::return_value(ReturnFlags::empty(), &api::ref_time_left().to_le_bytes()); +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 1947d310baf2..28736cd8d5da 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -671,6 +671,18 @@ mod benchmarks { ); } + #[benchmark(pov_mode = Measured)] + fn seal_ref_time_left() { + build_runtime!(runtime, memory: [vec![], ]); + + let result; + #[block] + { + result = runtime.bench_ref_time_left(memory.as_mut_slice()); + } + assert_eq!(result.unwrap(), runtime.ext().gas_meter().gas_left().ref_time()); + } + #[benchmark(pov_mode = Measured)] fn seal_balance() { build_runtime!(runtime, memory: [[0u8;32], ]); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 7ca08303316e..b73f50e34bc5 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -1874,6 +1874,27 @@ fn lazy_batch_removal_works() { }); } +#[test] +fn ref_time_left_api_works() { + let (code, _) = compile_module("ref_time_left").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor calls ref_time_left twice and asserts it to decrease + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the ref_time returned by the ref_time_left API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + + let returned_value = u64::from_le_bytes(received.data[..8].try_into().unwrap()); + assert!(returned_value > 0); + assert!(returned_value < GAS_LIMIT.ref_time()); + }); +} + #[test] fn lazy_removal_partial_remove_works() { let (code, _hash) = compile_module("self_destruct").unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 8f944b530e86..47fdfa8bab09 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -306,6 +306,8 @@ pub enum RuntimeCosts { CallerIsRoot, /// Weight of calling `seal_address`. Address, + /// Weight of calling `seal_ref_time_left`. + RefTimeLeft, /// Weight of calling `seal_weight_left`. WeightLeft, /// Weight of calling `seal_balance`. @@ -462,6 +464,7 @@ impl Token for RuntimeCosts { CallerIsOrigin => T::WeightInfo::seal_caller_is_origin(), CallerIsRoot => T::WeightInfo::seal_caller_is_root(), Address => T::WeightInfo::seal_address(), + RefTimeLeft => T::WeightInfo::seal_ref_time_left(), WeightLeft => T::WeightInfo::seal_weight_left(), Balance => T::WeightInfo::seal_balance(), BalanceOf => T::WeightInfo::seal_balance_of(), @@ -1701,6 +1704,14 @@ pub mod env { Ok(result?) } + /// Returns the amount of ref_time left. + /// See [`pallet_revive_uapi::HostFn::ref_time_left`]. + #[stable] + fn ref_time_left(&mut self, memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::RefTimeLeft)?; + Ok(self.ext.gas_meter().gas_left().ref_time()) + } + /// Call into the chain extension provided by the chain if any. /// See [`pallet_revive_uapi::HostFn::call_chain_extension`]. fn call_chain_extension( diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 94bd0397a0d5..03899fb3f20a 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -18,9 +18,9 @@ //! Autogenerated weights for `pallet_revive` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-12-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `a0d5968554fc`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `4ca2a44ee243`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -74,6 +74,7 @@ pub trait WeightInfo { fn seal_caller_is_root() -> Weight; fn seal_address() -> Weight; fn seal_weight_left() -> Weight; + fn seal_ref_time_left() -> Weight; fn seal_balance() -> Weight; fn seal_balance_of() -> Weight; fn seal_get_immutable_data(n: u32, ) -> Weight; @@ -85,8 +86,8 @@ pub trait WeightInfo { fn seal_block_hash() -> Weight; fn seal_now() -> Weight; fn seal_weight_to_fee() -> Weight; - fn seal_call_data_load() -> Weight; fn seal_copy_to_contract(n: u32, ) -> Weight; + fn seal_call_data_load() -> Weight; fn seal_call_data_copy(n: u32, ) -> Weight; fn seal_return(n: u32, ) -> Weight; fn seal_terminate(n: u32, ) -> Weight; @@ -136,8 +137,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_921_000 picoseconds. - Weight::from_parts(3_048_000, 1594) + // Minimum execution time: 2_729_000 picoseconds. + Weight::from_parts(2_919_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -147,10 +148,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 16_060_000 picoseconds. - Weight::from_parts(3_234_033, 415) - // Standard Error: 1_160 - .saturating_add(Weight::from_parts(1_184_188, 0).saturating_mul(k.into())) + // Minimum execution time: 16_062_000 picoseconds. + Weight::from_parts(2_790_037, 415) + // Standard Error: 1_371 + .saturating_add(Weight::from_parts(1_187_192, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -172,10 +173,10 @@ impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1536` - // Estimated: `7476` - // Minimum execution time: 93_624_000 picoseconds. - Weight::from_parts(98_332_129, 7476) + // Measured: `1465` + // Estimated: `7405` + // Minimum execution time: 94_592_000 picoseconds. + Weight::from_parts(100_095_688, 7405) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -198,13 +199,13 @@ impl WeightInfo for SubstrateWeight { fn instantiate_with_code(c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `416` - // Estimated: `6345` - // Minimum execution time: 196_202_000 picoseconds. - Weight::from_parts(169_823_092, 6345) + // Estimated: `6348` + // Minimum execution time: 205_430_000 picoseconds. + Weight::from_parts(190_302_613, 6348) // Standard Error: 10 - .saturating_add(Weight::from_parts(30, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(2, 0).saturating_mul(c.into())) // Standard Error: 10 - .saturating_add(Weight::from_parts(4_487, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_465, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -225,12 +226,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1296` - // Estimated: `4753` - // Minimum execution time: 162_423_000 picoseconds. - Weight::from_parts(144_467_590, 4753) - // Standard Error: 16 - .saturating_add(Weight::from_parts(4_405, 0).saturating_mul(i.into())) + // Measured: `1309` + // Estimated: `4760` + // Minimum execution time: 168_842_000 picoseconds. + Weight::from_parts(154_652_310, 4760) + // Standard Error: 15 + .saturating_add(Weight::from_parts(4_407, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -248,10 +249,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1536` - // Estimated: `7476` - // Minimum execution time: 144_454_000 picoseconds. - Weight::from_parts(151_756_000, 7476) + // Measured: `1465` + // Estimated: `7405` + // Minimum execution time: 144_703_000 picoseconds. + Weight::from_parts(151_937_000, 7405) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -266,8 +267,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 50_712_000 picoseconds. - Weight::from_parts(52_831_382, 3574) + // Minimum execution time: 52_912_000 picoseconds. + Weight::from_parts(54_905_094, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -281,8 +282,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 44_441_000 picoseconds. - Weight::from_parts(46_242_000, 3750) + // Minimum execution time: 46_323_000 picoseconds. + Weight::from_parts(47_075_000, 3750) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -294,8 +295,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 27_157_000 picoseconds. - Weight::from_parts(28_182_000, 6469) + // Minimum execution time: 27_120_000 picoseconds. + Weight::from_parts(28_635_000, 6469) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -307,8 +308,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 40_588_000 picoseconds. - Weight::from_parts(41_125_000, 3574) + // Minimum execution time: 42_489_000 picoseconds. + Weight::from_parts(43_230_000, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -320,8 +321,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 31_849_000 picoseconds. - Weight::from_parts(32_674_000, 3521) + // Minimum execution time: 34_042_000 picoseconds. + Weight::from_parts(34_758_000, 3521) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -333,8 +334,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 14_510_000 picoseconds. - Weight::from_parts(14_986_000, 3610) + // Minimum execution time: 14_322_000 picoseconds. + Weight::from_parts(14_761_000, 3610) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -342,24 +343,24 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_324_000 picoseconds. - Weight::from_parts(8_363_388, 0) - // Standard Error: 230 - .saturating_add(Weight::from_parts(170_510, 0).saturating_mul(r.into())) + // Minimum execution time: 14_300_000 picoseconds. + Weight::from_parts(14_435_272, 0) + // Standard Error: 357 + .saturating_add(Weight::from_parts(151_410, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 275_000 picoseconds. - Weight::from_parts(326_000, 0) + // Minimum execution time: 315_000 picoseconds. + Weight::from_parts(355_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 263_000 picoseconds. - Weight::from_parts(292_000, 0) + // Minimum execution time: 252_000 picoseconds. + Weight::from_parts(300_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -367,8 +368,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_011_000 picoseconds. - Weight::from_parts(10_476_000, 3771) + // Minimum execution time: 10_073_000 picoseconds. + Weight::from_parts(10_791_000, 3771) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -377,16 +378,16 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_253_000 picoseconds. - Weight::from_parts(11_642_000, 3868) + // Minimum execution time: 11_216_000 picoseconds. + Weight::from_parts(11_917_000, 3868) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(318_000, 0) + // Minimum execution time: 269_000 picoseconds. + Weight::from_parts(308_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -396,44 +397,51 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_904_000 picoseconds. - Weight::from_parts(15_281_000, 3938) + // Minimum execution time: 15_159_000 picoseconds. + Weight::from_parts(15_933_000, 3938) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 382_000 picoseconds. - Weight::from_parts(422_000, 0) + // Minimum execution time: 367_000 picoseconds. + Weight::from_parts(391_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 258_000 picoseconds. - Weight::from_parts(310_000, 0) + // Minimum execution time: 294_000 picoseconds. + Weight::from_parts(331_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 283_000 picoseconds. - Weight::from_parts(315_000, 0) + // Minimum execution time: 275_000 picoseconds. + Weight::from_parts(318_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 637_000 picoseconds. - Weight::from_parts(726_000, 0) + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(697_000, 0) + } + fn seal_ref_time_left() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 288_000 picoseconds. + Weight::from_parts(306_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `103` + // Measured: `140` // Estimated: `0` - // Minimum execution time: 4_649_000 picoseconds. - Weight::from_parts(4_860_000, 0) + // Minimum execution time: 5_577_000 picoseconds. + Weight::from_parts(5_918_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -443,8 +451,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 9_053_000 picoseconds. - Weight::from_parts(9_480_000, 3729) + // Minimum execution time: 9_264_000 picoseconds. + Weight::from_parts(9_589_000, 3729) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -454,10 +462,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_991_000 picoseconds. - Weight::from_parts(6_760_389, 3703) - // Standard Error: 5 - .saturating_add(Weight::from_parts(627, 0).saturating_mul(n.into())) + // Minimum execution time: 6_082_000 picoseconds. + Weight::from_parts(6_789_222, 3703) + // Standard Error: 4 + .saturating_add(Weight::from_parts(670, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -468,39 +476,39 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_062_000 picoseconds. - Weight::from_parts(2_277_051, 0) + // Minimum execution time: 1_950_000 picoseconds. + Weight::from_parts(2_244_232, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(530, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(574, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 267_000 picoseconds. - Weight::from_parts(299_000, 0) + // Minimum execution time: 254_000 picoseconds. + Weight::from_parts(304_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 263_000 picoseconds. - Weight::from_parts(318_000, 0) + // Minimum execution time: 251_000 picoseconds. + Weight::from_parts(292_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 264_000 picoseconds. - Weight::from_parts(303_000, 0) + // Minimum execution time: 262_000 picoseconds. + Weight::from_parts(288_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 267_000 picoseconds. - Weight::from_parts(296_000, 0) + // Minimum execution time: 269_000 picoseconds. + Weight::from_parts(302_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -508,60 +516,60 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_622_000 picoseconds. - Weight::from_parts(3_794_000, 3495) + // Minimum execution time: 3_690_000 picoseconds. + Weight::from_parts(3_791_000, 3495) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(298_000, 0) + // Minimum execution time: 261_000 picoseconds. + Weight::from_parts(307_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_340_000 picoseconds. - Weight::from_parts(1_483_000, 0) + // Minimum execution time: 1_417_000 picoseconds. + Weight::from_parts(1_547_000, 0) } - fn seal_call_data_load() -> Weight { + /// The range of component `n` is `[0, 262140]`. + fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(295_000, 0) + // Minimum execution time: 364_000 picoseconds. + Weight::from_parts(566_499, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(237, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 262140]`. - fn seal_copy_to_contract(n: u32, ) -> Weight { + fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 369_000 picoseconds. - Weight::from_parts(544_048, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) + // Minimum execution time: 279_000 picoseconds. + Weight::from_parts(305_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(123_748, 0) + // Minimum execution time: 265_000 picoseconds. + Weight::from_parts(359_300, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 291_000 picoseconds. - Weight::from_parts(846_264, 0) + // Minimum execution time: 278_000 picoseconds. + Weight::from_parts(474_421, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(237, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -577,11 +585,11 @@ impl WeightInfo for SubstrateWeight { fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `324 + n * (88 ±0)` - // Estimated: `3791 + n * (2563 ±0)` - // Minimum execution time: 22_494_000 picoseconds. - Weight::from_parts(23_028_153, 3791) - // Standard Error: 12_407 - .saturating_add(Weight::from_parts(4_238_442, 0).saturating_mul(n.into())) + // Estimated: `3790 + n * (2563 ±0)` + // Minimum execution time: 23_182_000 picoseconds. + Weight::from_parts(23_833_588, 3790) + // Standard Error: 12_448 + .saturating_add(Weight::from_parts(4_277_757, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -594,22 +602,22 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_383_000 picoseconds. - Weight::from_parts(4_364_292, 0) - // Standard Error: 2_775 - .saturating_add(Weight::from_parts(210_189, 0).saturating_mul(t.into())) - // Standard Error: 24 - .saturating_add(Weight::from_parts(952, 0).saturating_mul(n.into())) + // Minimum execution time: 4_433_000 picoseconds. + Weight::from_parts(4_321_363, 0) + // Standard Error: 2_536 + .saturating_add(Weight::from_parts(207_597, 0).saturating_mul(t.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(957, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 328_000 picoseconds. - Weight::from_parts(393_925, 0) + // Minimum execution time: 353_000 picoseconds. + Weight::from_parts(78_798, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(725, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(762, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -617,8 +625,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 7_649_000 picoseconds. - Weight::from_parts(8_025_000, 744) + // Minimum execution time: 7_701_000 picoseconds. + Weight::from_parts(8_043_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -627,8 +635,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 43_439_000 picoseconds. - Weight::from_parts(44_296_000, 10754) + // Minimum execution time: 42_961_000 picoseconds. + Weight::from_parts(44_719_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -637,8 +645,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 8_919_000 picoseconds. - Weight::from_parts(9_392_000, 744) + // Minimum execution time: 8_575_000 picoseconds. + Weight::from_parts(9_239_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -648,8 +656,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 45_032_000 picoseconds. - Weight::from_parts(46_050_000, 10754) + // Minimum execution time: 43_585_000 picoseconds. + Weight::from_parts(45_719_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -661,12 +669,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_272_000 picoseconds. - Weight::from_parts(10_022_838, 247) - // Standard Error: 43 - .saturating_add(Weight::from_parts(513, 0).saturating_mul(n.into())) - // Standard Error: 43 - .saturating_add(Weight::from_parts(625, 0).saturating_mul(o.into())) + // Minimum execution time: 9_147_000 picoseconds. + Weight::from_parts(9_851_872, 247) + // Standard Error: 40 + .saturating_add(Weight::from_parts(222, 0).saturating_mul(n.into())) + // Standard Error: 40 + .saturating_add(Weight::from_parts(411, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -678,10 +686,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_885_000 picoseconds. - Weight::from_parts(9_785_932, 247) + // Minimum execution time: 8_859_000 picoseconds. + Weight::from_parts(9_633_190, 247) // Standard Error: 55 - .saturating_add(Weight::from_parts(612, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(995, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -693,10 +701,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_440_000 picoseconds. - Weight::from_parts(9_453_769, 247) - // Standard Error: 62 - .saturating_add(Weight::from_parts(1_529, 0).saturating_mul(n.into())) + // Minimum execution time: 8_270_000 picoseconds. + Weight::from_parts(9_208_849, 247) + // Standard Error: 67 + .saturating_add(Weight::from_parts(1_686, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -707,10 +715,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_212_000 picoseconds. - Weight::from_parts(8_880_676, 247) - // Standard Error: 54 - .saturating_add(Weight::from_parts(673, 0).saturating_mul(n.into())) + // Minimum execution time: 8_002_000 picoseconds. + Weight::from_parts(8_695_892, 247) + // Standard Error: 48 + .saturating_add(Weight::from_parts(721, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -721,10 +729,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_491_000 picoseconds. - Weight::from_parts(10_313_570, 247) - // Standard Error: 65 - .saturating_add(Weight::from_parts(1_681, 0).saturating_mul(n.into())) + // Minimum execution time: 9_204_000 picoseconds. + Weight::from_parts(10_176_756, 247) + // Standard Error: 57 + .saturating_add(Weight::from_parts(1_550, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -733,36 +741,36 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_530_000 picoseconds. - Weight::from_parts(1_642_000, 0) + // Minimum execution time: 1_518_000 picoseconds. + Weight::from_parts(1_578_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_851_000 picoseconds. - Weight::from_parts(1_999_000, 0) + // Minimum execution time: 1_846_000 picoseconds. + Weight::from_parts(1_996_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_429_000 picoseconds. - Weight::from_parts(1_527_000, 0) + // Minimum execution time: 1_442_000 picoseconds. + Weight::from_parts(1_562_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_689_000 picoseconds. - Weight::from_parts(1_772_000, 0) + // Minimum execution time: 1_602_000 picoseconds. + Weight::from_parts(1_730_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_049_000 picoseconds. - Weight::from_parts(1_153_000, 0) + // Minimum execution time: 1_096_000 picoseconds. + Weight::from_parts(1_176_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -770,52 +778,52 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_338_000 picoseconds. - Weight::from_parts(2_514_685, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(299, 0).saturating_mul(n.into())) - // Standard Error: 15 - .saturating_add(Weight::from_parts(403, 0).saturating_mul(o.into())) + // Minimum execution time: 2_328_000 picoseconds. + Weight::from_parts(2_470_198, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(256, 0).saturating_mul(n.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(441, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_045_000 picoseconds. - Weight::from_parts(2_409_843, 0) - // Standard Error: 16 - .saturating_add(Weight::from_parts(350, 0).saturating_mul(n.into())) + // Minimum execution time: 2_037_000 picoseconds. + Weight::from_parts(2_439_061, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(303, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_891_000 picoseconds. - Weight::from_parts(2_117_702, 0) + // Minimum execution time: 1_900_000 picoseconds. + Weight::from_parts(2_095_135, 0) // Standard Error: 12 - .saturating_add(Weight::from_parts(289, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(310, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_786_000 picoseconds. - Weight::from_parts(1_949_290, 0) - // Standard Error: 11 - .saturating_add(Weight::from_parts(232, 0).saturating_mul(n.into())) + // Minimum execution time: 1_772_000 picoseconds. + Weight::from_parts(1_964_542, 0) + // Standard Error: 16 + .saturating_add(Weight::from_parts(298, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_465_000 picoseconds. - Weight::from_parts(2_712_107, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(79, 0).saturating_mul(n.into())) + // Minimum execution time: 2_555_000 picoseconds. + Weight::from_parts(2_864_143, 0) + // Standard Error: 22 + .saturating_add(Weight::from_parts(45, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -829,20 +837,18 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `t` is `[0, 1]`. /// The range of component `i` is `[0, 262144]`. - fn seal_call(t: u32, i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1294 + t * (243 ±0)` - // Estimated: `4759 + t * (2501 ±0)` - // Minimum execution time: 41_377_000 picoseconds. - Weight::from_parts(43_024_676, 4759) - // Standard Error: 44_099 - .saturating_add(Weight::from_parts(1_689_315, 0).saturating_mul(t.into())) - // Standard Error: 0 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) + fn seal_call(t: u32, _i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1292 + t * (280 ±0)` + // Estimated: `4757 + t * (2518 ±0)` + // Minimum execution time: 40_760_000 picoseconds. + Weight::from_parts(45_131_001, 4757) + // Standard Error: 302_594 + .saturating_add(Weight::from_parts(2_769_232, 0).saturating_mul(t.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 2501).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 2518).saturating_mul(t.into())) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -854,8 +860,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 36_324_000 picoseconds. - Weight::from_parts(37_657_000, 4702) + // Minimum execution time: 36_975_000 picoseconds. + Weight::from_parts(38_368_000, 4702) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -869,12 +875,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1273` - // Estimated: `4736` - // Minimum execution time: 117_657_000 picoseconds. - Weight::from_parts(110_177_403, 4736) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_097, 0).saturating_mul(i.into())) + // Measured: `1310` + // Estimated: `4769` + // Minimum execution time: 122_553_000 picoseconds. + Weight::from_parts(117_325_822, 4769) + // Standard Error: 10 + .saturating_add(Weight::from_parts(4_147, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -883,64 +889,64 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 650_000 picoseconds. - Weight::from_parts(4_208_007, 0) + // Minimum execution time: 657_000 picoseconds. + Weight::from_parts(3_531_259, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_396, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_428, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_101_000 picoseconds. - Weight::from_parts(4_521_803, 0) + // Minimum execution time: 1_072_000 picoseconds. + Weight::from_parts(5_487_006, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_609, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_634, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 654_000 picoseconds. - Weight::from_parts(3_060_461, 0) + // Minimum execution time: 638_000 picoseconds. + Weight::from_parts(3_097_177, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_531, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_551, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 628_000 picoseconds. - Weight::from_parts(3_784_567, 0) + // Minimum execution time: 682_000 picoseconds. + Weight::from_parts(2_963_774, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_526, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_561, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_892_000 picoseconds. - Weight::from_parts(25_002_714, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(5_252, 0).saturating_mul(n.into())) + // Minimum execution time: 42_791_000 picoseconds. + Weight::from_parts(27_471_391, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(5_246, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_990_000 picoseconds. - Weight::from_parts(48_960_000, 0) + // Minimum execution time: 46_565_000 picoseconds. + Weight::from_parts(48_251_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_870_000 picoseconds. - Weight::from_parts(13_062_000, 0) + // Minimum execution time: 12_562_000 picoseconds. + Weight::from_parts(12_664_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -948,8 +954,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 17_810_000 picoseconds. - Weight::from_parts(18_667_000, 3765) + // Minimum execution time: 18_527_000 picoseconds. + Weight::from_parts(19_134_000, 3765) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -959,8 +965,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 13_762_000 picoseconds. - Weight::from_parts(14_526_000, 3803) + // Minimum execution time: 13_843_000 picoseconds. + Weight::from_parts(14_750_000, 3803) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -970,8 +976,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 12_753_000 picoseconds. - Weight::from_parts(13_199_000, 3561) + // Minimum execution time: 13_013_000 picoseconds. + Weight::from_parts(13_612_000, 3561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -980,10 +986,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_060_000 picoseconds. - Weight::from_parts(10_131_024, 0) - // Standard Error: 72 - .saturating_add(Weight::from_parts(71_842, 0).saturating_mul(r.into())) + // Minimum execution time: 15_182_000 picoseconds. + Weight::from_parts(16_987_060, 0) + // Standard Error: 105 + .saturating_add(Weight::from_parts(72_086, 0).saturating_mul(r.into())) } } @@ -995,8 +1001,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_921_000 picoseconds. - Weight::from_parts(3_048_000, 1594) + // Minimum execution time: 2_729_000 picoseconds. + Weight::from_parts(2_919_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1006,10 +1012,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 16_060_000 picoseconds. - Weight::from_parts(3_234_033, 415) - // Standard Error: 1_160 - .saturating_add(Weight::from_parts(1_184_188, 0).saturating_mul(k.into())) + // Minimum execution time: 16_062_000 picoseconds. + Weight::from_parts(2_790_037, 415) + // Standard Error: 1_371 + .saturating_add(Weight::from_parts(1_187_192, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1031,10 +1037,10 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1536` - // Estimated: `7476` - // Minimum execution time: 93_624_000 picoseconds. - Weight::from_parts(98_332_129, 7476) + // Measured: `1465` + // Estimated: `7405` + // Minimum execution time: 94_592_000 picoseconds. + Weight::from_parts(100_095_688, 7405) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1057,13 +1063,13 @@ impl WeightInfo for () { fn instantiate_with_code(c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `416` - // Estimated: `6345` - // Minimum execution time: 196_202_000 picoseconds. - Weight::from_parts(169_823_092, 6345) + // Estimated: `6348` + // Minimum execution time: 205_430_000 picoseconds. + Weight::from_parts(190_302_613, 6348) // Standard Error: 10 - .saturating_add(Weight::from_parts(30, 0).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(2, 0).saturating_mul(c.into())) // Standard Error: 10 - .saturating_add(Weight::from_parts(4_487, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_465, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1084,12 +1090,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1296` - // Estimated: `4753` - // Minimum execution time: 162_423_000 picoseconds. - Weight::from_parts(144_467_590, 4753) - // Standard Error: 16 - .saturating_add(Weight::from_parts(4_405, 0).saturating_mul(i.into())) + // Measured: `1309` + // Estimated: `4760` + // Minimum execution time: 168_842_000 picoseconds. + Weight::from_parts(154_652_310, 4760) + // Standard Error: 15 + .saturating_add(Weight::from_parts(4_407, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1107,10 +1113,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1536` - // Estimated: `7476` - // Minimum execution time: 144_454_000 picoseconds. - Weight::from_parts(151_756_000, 7476) + // Measured: `1465` + // Estimated: `7405` + // Minimum execution time: 144_703_000 picoseconds. + Weight::from_parts(151_937_000, 7405) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1125,8 +1131,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 50_712_000 picoseconds. - Weight::from_parts(52_831_382, 3574) + // Minimum execution time: 52_912_000 picoseconds. + Weight::from_parts(54_905_094, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1140,8 +1146,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 44_441_000 picoseconds. - Weight::from_parts(46_242_000, 3750) + // Minimum execution time: 46_323_000 picoseconds. + Weight::from_parts(47_075_000, 3750) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1153,8 +1159,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 27_157_000 picoseconds. - Weight::from_parts(28_182_000, 6469) + // Minimum execution time: 27_120_000 picoseconds. + Weight::from_parts(28_635_000, 6469) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1166,8 +1172,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 40_588_000 picoseconds. - Weight::from_parts(41_125_000, 3574) + // Minimum execution time: 42_489_000 picoseconds. + Weight::from_parts(43_230_000, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1179,8 +1185,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 31_849_000 picoseconds. - Weight::from_parts(32_674_000, 3521) + // Minimum execution time: 34_042_000 picoseconds. + Weight::from_parts(34_758_000, 3521) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1192,8 +1198,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 14_510_000 picoseconds. - Weight::from_parts(14_986_000, 3610) + // Minimum execution time: 14_322_000 picoseconds. + Weight::from_parts(14_761_000, 3610) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1201,24 +1207,24 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_324_000 picoseconds. - Weight::from_parts(8_363_388, 0) - // Standard Error: 230 - .saturating_add(Weight::from_parts(170_510, 0).saturating_mul(r.into())) + // Minimum execution time: 14_300_000 picoseconds. + Weight::from_parts(14_435_272, 0) + // Standard Error: 357 + .saturating_add(Weight::from_parts(151_410, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 275_000 picoseconds. - Weight::from_parts(326_000, 0) + // Minimum execution time: 315_000 picoseconds. + Weight::from_parts(355_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 263_000 picoseconds. - Weight::from_parts(292_000, 0) + // Minimum execution time: 252_000 picoseconds. + Weight::from_parts(300_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1226,8 +1232,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_011_000 picoseconds. - Weight::from_parts(10_476_000, 3771) + // Minimum execution time: 10_073_000 picoseconds. + Weight::from_parts(10_791_000, 3771) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -1236,16 +1242,16 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_253_000 picoseconds. - Weight::from_parts(11_642_000, 3868) + // Minimum execution time: 11_216_000 picoseconds. + Weight::from_parts(11_917_000, 3868) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(318_000, 0) + // Minimum execution time: 269_000 picoseconds. + Weight::from_parts(308_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1255,44 +1261,51 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_904_000 picoseconds. - Weight::from_parts(15_281_000, 3938) + // Minimum execution time: 15_159_000 picoseconds. + Weight::from_parts(15_933_000, 3938) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 382_000 picoseconds. - Weight::from_parts(422_000, 0) + // Minimum execution time: 367_000 picoseconds. + Weight::from_parts(391_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 258_000 picoseconds. - Weight::from_parts(310_000, 0) + // Minimum execution time: 294_000 picoseconds. + Weight::from_parts(331_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 283_000 picoseconds. - Weight::from_parts(315_000, 0) + // Minimum execution time: 275_000 picoseconds. + Weight::from_parts(318_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 637_000 picoseconds. - Weight::from_parts(726_000, 0) + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(697_000, 0) + } + fn seal_ref_time_left() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 288_000 picoseconds. + Weight::from_parts(306_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `103` + // Measured: `140` // Estimated: `0` - // Minimum execution time: 4_649_000 picoseconds. - Weight::from_parts(4_860_000, 0) + // Minimum execution time: 5_577_000 picoseconds. + Weight::from_parts(5_918_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1302,8 +1315,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 9_053_000 picoseconds. - Weight::from_parts(9_480_000, 3729) + // Minimum execution time: 9_264_000 picoseconds. + Weight::from_parts(9_589_000, 3729) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -1313,10 +1326,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_991_000 picoseconds. - Weight::from_parts(6_760_389, 3703) - // Standard Error: 5 - .saturating_add(Weight::from_parts(627, 0).saturating_mul(n.into())) + // Minimum execution time: 6_082_000 picoseconds. + Weight::from_parts(6_789_222, 3703) + // Standard Error: 4 + .saturating_add(Weight::from_parts(670, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1327,39 +1340,39 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_062_000 picoseconds. - Weight::from_parts(2_277_051, 0) + // Minimum execution time: 1_950_000 picoseconds. + Weight::from_parts(2_244_232, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(530, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(574, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 267_000 picoseconds. - Weight::from_parts(299_000, 0) + // Minimum execution time: 254_000 picoseconds. + Weight::from_parts(304_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 263_000 picoseconds. - Weight::from_parts(318_000, 0) + // Minimum execution time: 251_000 picoseconds. + Weight::from_parts(292_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 264_000 picoseconds. - Weight::from_parts(303_000, 0) + // Minimum execution time: 262_000 picoseconds. + Weight::from_parts(288_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 267_000 picoseconds. - Weight::from_parts(296_000, 0) + // Minimum execution time: 269_000 picoseconds. + Weight::from_parts(302_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -1367,60 +1380,60 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_622_000 picoseconds. - Weight::from_parts(3_794_000, 3495) + // Minimum execution time: 3_690_000 picoseconds. + Weight::from_parts(3_791_000, 3495) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(298_000, 0) + // Minimum execution time: 261_000 picoseconds. + Weight::from_parts(307_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_340_000 picoseconds. - Weight::from_parts(1_483_000, 0) + // Minimum execution time: 1_417_000 picoseconds. + Weight::from_parts(1_547_000, 0) } - fn seal_call_data_load() -> Weight { + /// The range of component `n` is `[0, 262140]`. + fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(295_000, 0) + // Minimum execution time: 364_000 picoseconds. + Weight::from_parts(566_499, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(237, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 262140]`. - fn seal_copy_to_contract(n: u32, ) -> Weight { + fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 369_000 picoseconds. - Weight::from_parts(544_048, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) + // Minimum execution time: 279_000 picoseconds. + Weight::from_parts(305_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(123_748, 0) + // Minimum execution time: 265_000 picoseconds. + Weight::from_parts(359_300, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 291_000 picoseconds. - Weight::from_parts(846_264, 0) + // Minimum execution time: 278_000 picoseconds. + Weight::from_parts(474_421, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(200, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(237, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1436,11 +1449,11 @@ impl WeightInfo for () { fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `324 + n * (88 ±0)` - // Estimated: `3791 + n * (2563 ±0)` - // Minimum execution time: 22_494_000 picoseconds. - Weight::from_parts(23_028_153, 3791) - // Standard Error: 12_407 - .saturating_add(Weight::from_parts(4_238_442, 0).saturating_mul(n.into())) + // Estimated: `3790 + n * (2563 ±0)` + // Minimum execution time: 23_182_000 picoseconds. + Weight::from_parts(23_833_588, 3790) + // Standard Error: 12_448 + .saturating_add(Weight::from_parts(4_277_757, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -1453,22 +1466,22 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_383_000 picoseconds. - Weight::from_parts(4_364_292, 0) - // Standard Error: 2_775 - .saturating_add(Weight::from_parts(210_189, 0).saturating_mul(t.into())) - // Standard Error: 24 - .saturating_add(Weight::from_parts(952, 0).saturating_mul(n.into())) + // Minimum execution time: 4_433_000 picoseconds. + Weight::from_parts(4_321_363, 0) + // Standard Error: 2_536 + .saturating_add(Weight::from_parts(207_597, 0).saturating_mul(t.into())) + // Standard Error: 22 + .saturating_add(Weight::from_parts(957, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 328_000 picoseconds. - Weight::from_parts(393_925, 0) + // Minimum execution time: 353_000 picoseconds. + Weight::from_parts(78_798, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(725, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(762, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -1476,8 +1489,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 7_649_000 picoseconds. - Weight::from_parts(8_025_000, 744) + // Minimum execution time: 7_701_000 picoseconds. + Weight::from_parts(8_043_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1486,8 +1499,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 43_439_000 picoseconds. - Weight::from_parts(44_296_000, 10754) + // Minimum execution time: 42_961_000 picoseconds. + Weight::from_parts(44_719_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1496,8 +1509,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 8_919_000 picoseconds. - Weight::from_parts(9_392_000, 744) + // Minimum execution time: 8_575_000 picoseconds. + Weight::from_parts(9_239_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1507,8 +1520,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 45_032_000 picoseconds. - Weight::from_parts(46_050_000, 10754) + // Minimum execution time: 43_585_000 picoseconds. + Weight::from_parts(45_719_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1520,12 +1533,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_272_000 picoseconds. - Weight::from_parts(10_022_838, 247) - // Standard Error: 43 - .saturating_add(Weight::from_parts(513, 0).saturating_mul(n.into())) - // Standard Error: 43 - .saturating_add(Weight::from_parts(625, 0).saturating_mul(o.into())) + // Minimum execution time: 9_147_000 picoseconds. + Weight::from_parts(9_851_872, 247) + // Standard Error: 40 + .saturating_add(Weight::from_parts(222, 0).saturating_mul(n.into())) + // Standard Error: 40 + .saturating_add(Weight::from_parts(411, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -1537,10 +1550,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_885_000 picoseconds. - Weight::from_parts(9_785_932, 247) + // Minimum execution time: 8_859_000 picoseconds. + Weight::from_parts(9_633_190, 247) // Standard Error: 55 - .saturating_add(Weight::from_parts(612, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(995, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1552,10 +1565,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_440_000 picoseconds. - Weight::from_parts(9_453_769, 247) - // Standard Error: 62 - .saturating_add(Weight::from_parts(1_529, 0).saturating_mul(n.into())) + // Minimum execution time: 8_270_000 picoseconds. + Weight::from_parts(9_208_849, 247) + // Standard Error: 67 + .saturating_add(Weight::from_parts(1_686, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1566,10 +1579,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_212_000 picoseconds. - Weight::from_parts(8_880_676, 247) - // Standard Error: 54 - .saturating_add(Weight::from_parts(673, 0).saturating_mul(n.into())) + // Minimum execution time: 8_002_000 picoseconds. + Weight::from_parts(8_695_892, 247) + // Standard Error: 48 + .saturating_add(Weight::from_parts(721, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1580,10 +1593,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_491_000 picoseconds. - Weight::from_parts(10_313_570, 247) - // Standard Error: 65 - .saturating_add(Weight::from_parts(1_681, 0).saturating_mul(n.into())) + // Minimum execution time: 9_204_000 picoseconds. + Weight::from_parts(10_176_756, 247) + // Standard Error: 57 + .saturating_add(Weight::from_parts(1_550, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1592,36 +1605,36 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_530_000 picoseconds. - Weight::from_parts(1_642_000, 0) + // Minimum execution time: 1_518_000 picoseconds. + Weight::from_parts(1_578_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_851_000 picoseconds. - Weight::from_parts(1_999_000, 0) + // Minimum execution time: 1_846_000 picoseconds. + Weight::from_parts(1_996_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_429_000 picoseconds. - Weight::from_parts(1_527_000, 0) + // Minimum execution time: 1_442_000 picoseconds. + Weight::from_parts(1_562_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_689_000 picoseconds. - Weight::from_parts(1_772_000, 0) + // Minimum execution time: 1_602_000 picoseconds. + Weight::from_parts(1_730_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_049_000 picoseconds. - Weight::from_parts(1_153_000, 0) + // Minimum execution time: 1_096_000 picoseconds. + Weight::from_parts(1_176_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -1629,52 +1642,52 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_338_000 picoseconds. - Weight::from_parts(2_514_685, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(299, 0).saturating_mul(n.into())) - // Standard Error: 15 - .saturating_add(Weight::from_parts(403, 0).saturating_mul(o.into())) + // Minimum execution time: 2_328_000 picoseconds. + Weight::from_parts(2_470_198, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(256, 0).saturating_mul(n.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(441, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_045_000 picoseconds. - Weight::from_parts(2_409_843, 0) - // Standard Error: 16 - .saturating_add(Weight::from_parts(350, 0).saturating_mul(n.into())) + // Minimum execution time: 2_037_000 picoseconds. + Weight::from_parts(2_439_061, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(303, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_891_000 picoseconds. - Weight::from_parts(2_117_702, 0) + // Minimum execution time: 1_900_000 picoseconds. + Weight::from_parts(2_095_135, 0) // Standard Error: 12 - .saturating_add(Weight::from_parts(289, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(310, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_786_000 picoseconds. - Weight::from_parts(1_949_290, 0) - // Standard Error: 11 - .saturating_add(Weight::from_parts(232, 0).saturating_mul(n.into())) + // Minimum execution time: 1_772_000 picoseconds. + Weight::from_parts(1_964_542, 0) + // Standard Error: 16 + .saturating_add(Weight::from_parts(298, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_465_000 picoseconds. - Weight::from_parts(2_712_107, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(79, 0).saturating_mul(n.into())) + // Minimum execution time: 2_555_000 picoseconds. + Weight::from_parts(2_864_143, 0) + // Standard Error: 22 + .saturating_add(Weight::from_parts(45, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1688,20 +1701,18 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `t` is `[0, 1]`. /// The range of component `i` is `[0, 262144]`. - fn seal_call(t: u32, i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1294 + t * (243 ±0)` - // Estimated: `4759 + t * (2501 ±0)` - // Minimum execution time: 41_377_000 picoseconds. - Weight::from_parts(43_024_676, 4759) - // Standard Error: 44_099 - .saturating_add(Weight::from_parts(1_689_315, 0).saturating_mul(t.into())) - // Standard Error: 0 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(i.into())) + fn seal_call(t: u32, _i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1292 + t * (280 ±0)` + // Estimated: `4757 + t * (2518 ±0)` + // Minimum execution time: 40_760_000 picoseconds. + Weight::from_parts(45_131_001, 4757) + // Standard Error: 302_594 + .saturating_add(Weight::from_parts(2_769_232, 0).saturating_mul(t.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 2501).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 2518).saturating_mul(t.into())) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1713,8 +1724,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 36_324_000 picoseconds. - Weight::from_parts(37_657_000, 4702) + // Minimum execution time: 36_975_000 picoseconds. + Weight::from_parts(38_368_000, 4702) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -1728,12 +1739,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1273` - // Estimated: `4736` - // Minimum execution time: 117_657_000 picoseconds. - Weight::from_parts(110_177_403, 4736) - // Standard Error: 11 - .saturating_add(Weight::from_parts(4_097, 0).saturating_mul(i.into())) + // Measured: `1310` + // Estimated: `4769` + // Minimum execution time: 122_553_000 picoseconds. + Weight::from_parts(117_325_822, 4769) + // Standard Error: 10 + .saturating_add(Weight::from_parts(4_147, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1742,64 +1753,64 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 650_000 picoseconds. - Weight::from_parts(4_208_007, 0) + // Minimum execution time: 657_000 picoseconds. + Weight::from_parts(3_531_259, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_396, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_428, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_101_000 picoseconds. - Weight::from_parts(4_521_803, 0) + // Minimum execution time: 1_072_000 picoseconds. + Weight::from_parts(5_487_006, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_609, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_634, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 654_000 picoseconds. - Weight::from_parts(3_060_461, 0) + // Minimum execution time: 638_000 picoseconds. + Weight::from_parts(3_097_177, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_531, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_551, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 628_000 picoseconds. - Weight::from_parts(3_784_567, 0) + // Minimum execution time: 682_000 picoseconds. + Weight::from_parts(2_963_774, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_526, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_561, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_892_000 picoseconds. - Weight::from_parts(25_002_714, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(5_252, 0).saturating_mul(n.into())) + // Minimum execution time: 42_791_000 picoseconds. + Weight::from_parts(27_471_391, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(5_246, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_990_000 picoseconds. - Weight::from_parts(48_960_000, 0) + // Minimum execution time: 46_565_000 picoseconds. + Weight::from_parts(48_251_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_870_000 picoseconds. - Weight::from_parts(13_062_000, 0) + // Minimum execution time: 12_562_000 picoseconds. + Weight::from_parts(12_664_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1807,8 +1818,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 17_810_000 picoseconds. - Weight::from_parts(18_667_000, 3765) + // Minimum execution time: 18_527_000 picoseconds. + Weight::from_parts(19_134_000, 3765) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1818,8 +1829,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 13_762_000 picoseconds. - Weight::from_parts(14_526_000, 3803) + // Minimum execution time: 13_843_000 picoseconds. + Weight::from_parts(14_750_000, 3803) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1829,8 +1840,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 12_753_000 picoseconds. - Weight::from_parts(13_199_000, 3561) + // Minimum execution time: 13_013_000 picoseconds. + Weight::from_parts(13_612_000, 3561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1839,9 +1850,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_060_000 picoseconds. - Weight::from_parts(10_131_024, 0) - // Standard Error: 72 - .saturating_add(Weight::from_parts(71_842, 0).saturating_mul(r.into())) + // Minimum execution time: 15_182_000 picoseconds. + Weight::from_parts(16_987_060, 0) + // Standard Error: 105 + .saturating_add(Weight::from_parts(72_086, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 505ec0a82f07..2214563faf02 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -398,6 +398,9 @@ pub trait HostFn: private::Sealed { /// - `offset`: Byte offset into the returned data fn return_data_copy(output: &mut &mut [u8], offset: u32); + /// Returns the amount of ref_time left. + fn ref_time_left() -> u64; + /// Stores the current block number of the current contract into the supplied buffer. /// /// # Parameters diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 40de12b25f36..a73a13ed1af5 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -76,6 +76,7 @@ mod sys { pub fn address(out_ptr: *mut u8); pub fn weight_to_fee(ref_time: u64, proof_size: u64, out_ptr: *mut u8); pub fn weight_left(out_ptr: *mut u8, out_len_ptr: *mut u32); + pub fn ref_time_left() -> u64; pub fn get_immutable_data(out_ptr: *mut u8, out_len_ptr: *mut u32); pub fn set_immutable_data(ptr: *const u8, len: u32); pub fn balance(out_ptr: *mut u8); @@ -440,6 +441,10 @@ impl HostFn for HostFnImpl { extract_from_slice(output, output_len as usize); } + fn ref_time_left() -> u64 { + unsafe { sys::ref_time_left() } + } + #[cfg(feature = "unstable-api")] fn block_hash(block_number_ptr: &[u8; 32], output: &mut [u8; 32]) { unsafe { sys::block_hash(block_number_ptr.as_ptr(), output.as_mut_ptr()) }; From c4d66ccfa9a2b9fc8876d1f205f74c1c1f69cec8 Mon Sep 17 00:00:00 2001 From: Egor_P Date: Wed, 18 Dec 2024 14:44:07 +0100 Subject: [PATCH 185/340] [CI/CD] Fixes fro the docker images publishing pipeline (#6938) This PR contains a small number of fixes that have been made to the release pipeline: - Fixes docker image publishing for polkadot - Fixes docker owner in one of the jobs for the polakdot container - Fixes a description of one of the inputs --- .github/scripts/common/lib.sh | 1 - .github/workflows/release-50_publish-docker.yml | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/scripts/common/lib.sh b/.github/scripts/common/lib.sh index 00f8c089831e..c9be21e45dcb 100755 --- a/.github/scripts/common/lib.sh +++ b/.github/scripts/common/lib.sh @@ -297,7 +297,6 @@ fetch_release_artifacts_from_s3() { pwd ls -al --color popd > /dev/null - unset OUTPUT_DIR } # Pass the name of the binary as input, it will diff --git a/.github/workflows/release-50_publish-docker.yml b/.github/workflows/release-50_publish-docker.yml index 5c3c3a6e854d..a3c49598d6b1 100644 --- a/.github/workflows/release-50_publish-docker.yml +++ b/.github/workflows/release-50_publish-docker.yml @@ -46,7 +46,7 @@ on: required: true stable_tag: - description: Tag matching the actual stable release version in the format stableYYMM or stableYYMM-X for patch releases + description: Tag matching the actual stable release version in the format polkadpt-stableYYMM(-rcX) or plkadot-stableYYMM-X(-rcX) for patch releases required: true permissions: @@ -311,9 +311,9 @@ jobs: # TODO: The owner should be used below but buildx does not resolve the VARs # TODO: It would be good to get rid of this GHA that we don't really need. tags: | - egorpop/polkadot:${{ steps.fetch-data.outputs.stable }} - egorpop/polkadot:latest - egorpop/polkadot:${{ needs.fetch-latest-debian-package-version.outputs.polkadot_container_tag }} + parity/polkadot:${{ steps.fetch-data.outputs.stable }} + parity/polkadot:latest + parity/polkadot:${{ needs.fetch-latest-debian-package-version.outputs.polkadot_container_tag }} build-args: | VCS_REF=${{ github.ref }} POLKADOT_VERSION=${{ needs.fetch-latest-debian-package-version.outputs.polkadot_apt_version }} From fd0fb76567c04be8a76ffda2b6156640654b652a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 18 Dec 2024 15:27:33 +0100 Subject: [PATCH 186/340] slot-based-collator: Refactor some internals (#6935) Move `RelayChainDataFetcher` to its own module and rename it to `RelayChainDataCache`. Also move the `core_selector` function to the `slot_based` module. Related issue: https://github.com/paritytech/polkadot-sdk/issues/6495 --- .../slot_based/block_builder_task.rs | 144 ++---------------- .../aura/src/collators/slot_based/mod.rs | 35 ++++- .../slot_based/relay_chain_data_cache.rs | 127 +++++++++++++++ 3 files changed, 172 insertions(+), 134 deletions(-) create mode 100644 cumulus/client/consensus/aura/src/collators/slot_based/relay_chain_data_cache.rs diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs index 425151230704..41751f1db530 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs @@ -23,30 +23,32 @@ use cumulus_primitives_aura::AuraUnincludedSegmentApi; use cumulus_primitives_core::{GetCoreSelectorApi, PersistedValidationData}; use cumulus_relay_chain_interface::RelayChainInterface; -use polkadot_primitives::{ - vstaging::{ClaimQueueOffset, CoreSelector, DEFAULT_CLAIM_QUEUE_OFFSET}, - BlockId, CoreIndex, Hash as RelayHash, Header as RelayHeader, Id as ParaId, - OccupiedCoreAssumption, -}; +use polkadot_primitives::Id as ParaId; use futures::prelude::*; use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf, UsageProvider}; use sc_consensus::BlockImport; -use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; use sp_consensus_aura::{AuraApi, Slot}; -use sp_core::{crypto::Pair, U256}; +use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member, One}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member}; use sp_timestamp::Timestamp; -use std::{collections::BTreeSet, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; use super::CollatorMessage; use crate::{ collator::{self as collator_util}, - collators::{check_validation_code_or_log, cores_scheduled_for_para}, + collators::{ + check_validation_code_or_log, + slot_based::{ + core_selector, + relay_chain_data_cache::{RelayChainData, RelayChainDataCache}, + }, + }, LOG_TARGET, }; @@ -218,7 +220,7 @@ where collator_util::Collator::::new(params) }; - let mut relay_chain_fetcher = RelayChainCachingFetcher::new(relay_client.clone(), para_id); + let mut relay_chain_data_cache = RelayChainDataCache::new(relay_client.clone(), para_id); loop { // We wait here until the next slot arrives. @@ -242,7 +244,7 @@ where // Retrieve the core selector. let (core_selector, claim_queue_offset) = - match core_selector(&*para_client, &parent).await { + match core_selector(&*para_client, parent.hash, *parent.header.number()) { Ok(core_selector) => core_selector, Err(err) => { tracing::trace!( @@ -259,7 +261,7 @@ where max_pov_size, scheduled_cores, claimed_cores, - }) = relay_chain_fetcher + }) = relay_chain_data_cache .get_mut_relay_chain_data(relay_parent, claim_queue_offset) .await else { @@ -419,119 +421,3 @@ where } } } - -/// Contains relay chain data necessary for parachain block building. -#[derive(Clone)] -struct RelayChainData { - /// Current relay chain parent header. - pub relay_parent_header: RelayHeader, - /// The cores on which the para is scheduled at the configured claim queue offset. - pub scheduled_cores: Vec, - /// Maximum configured PoV size on the relay chain. - pub max_pov_size: u32, - /// The claimed cores at a relay parent. - pub claimed_cores: BTreeSet, -} - -/// Simple helper to fetch relay chain data and cache it based on the current relay chain best block -/// hash. -struct RelayChainCachingFetcher { - relay_client: RI, - para_id: ParaId, - last_data: Option<(RelayHash, RelayChainData)>, -} - -impl RelayChainCachingFetcher -where - RI: RelayChainInterface + Clone + 'static, -{ - pub fn new(relay_client: RI, para_id: ParaId) -> Self { - Self { relay_client, para_id, last_data: None } - } - - /// Fetch required [`RelayChainData`] from the relay chain. - /// If this data has been fetched in the past for the incoming hash, it will reuse - /// cached data. - pub async fn get_mut_relay_chain_data( - &mut self, - relay_parent: RelayHash, - claim_queue_offset: ClaimQueueOffset, - ) -> Result<&mut RelayChainData, ()> { - match &self.last_data { - Some((last_seen_hash, _)) if *last_seen_hash == relay_parent => { - tracing::trace!(target: crate::LOG_TARGET, %relay_parent, "Using cached data for relay parent."); - Ok(&mut self.last_data.as_mut().expect("last_data is Some").1) - }, - _ => { - tracing::trace!(target: crate::LOG_TARGET, %relay_parent, "Relay chain best block changed, fetching new data from relay chain."); - let data = self.update_for_relay_parent(relay_parent, claim_queue_offset).await?; - self.last_data = Some((relay_parent, data)); - Ok(&mut self.last_data.as_mut().expect("last_data was just set above").1) - }, - } - } - - /// Fetch fresh data from the relay chain for the given relay parent hash. - async fn update_for_relay_parent( - &self, - relay_parent: RelayHash, - claim_queue_offset: ClaimQueueOffset, - ) -> Result { - let scheduled_cores = cores_scheduled_for_para( - relay_parent, - self.para_id, - &self.relay_client, - claim_queue_offset, - ) - .await; - - let Ok(Some(relay_parent_header)) = - self.relay_client.header(BlockId::Hash(relay_parent)).await - else { - tracing::warn!(target: crate::LOG_TARGET, "Unable to fetch latest relay chain block header."); - return Err(()) - }; - - let max_pov_size = match self - .relay_client - .persisted_validation_data(relay_parent, self.para_id, OccupiedCoreAssumption::Included) - .await - { - Ok(None) => return Err(()), - Ok(Some(pvd)) => pvd.max_pov_size, - Err(err) => { - tracing::error!(target: crate::LOG_TARGET, ?err, "Failed to gather information from relay-client"); - return Err(()) - }, - }; - - Ok(RelayChainData { - relay_parent_header, - scheduled_cores, - max_pov_size, - claimed_cores: BTreeSet::new(), - }) - } -} - -async fn core_selector( - para_client: &Client, - parent: &consensus_common::PotentialParent, -) -> Result<(CoreSelector, ClaimQueueOffset), sp_api::ApiError> -where - Client: ProvideRuntimeApi + Send + Sync, - Client::Api: GetCoreSelectorApi, -{ - let block_hash = parent.hash; - let runtime_api = para_client.runtime_api(); - - if runtime_api.has_api::>(block_hash)? { - Ok(runtime_api.core_selector(block_hash)?) - } else { - let next_block_number: U256 = (*parent.header.number() + One::one()).into(); - - // If the runtime API does not support the core selector API, fallback to some default - // values. - Ok((CoreSelector(next_block_number.byte(0)), ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET))) - } -} diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs index 09afa18e6fbb..ab78b31fbd80 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/mod.rs @@ -35,23 +35,24 @@ use cumulus_client_collator::service::ServiceInterface as CollatorServiceInterfa use cumulus_client_consensus_common::{self as consensus_common, ParachainBlockImportMarker}; use cumulus_client_consensus_proposer::ProposerInterface; use cumulus_primitives_aura::AuraUnincludedSegmentApi; -use cumulus_primitives_core::GetCoreSelectorApi; +use cumulus_primitives_core::{ClaimQueueOffset, CoreSelector, GetCoreSelectorApi}; use cumulus_relay_chain_interface::RelayChainInterface; use futures::FutureExt; use polkadot_primitives::{ - CollatorPair, CoreIndex, Hash as RelayHash, Id as ParaId, ValidationCodeHash, + vstaging::DEFAULT_CLAIM_QUEUE_OFFSET, CollatorPair, CoreIndex, Hash as RelayHash, Id as ParaId, + ValidationCodeHash, }; use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf, UsageProvider}; use sc_consensus::BlockImport; use sc_utils::mpsc::tracing_unbounded; -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_application_crypto::AppPublic; use sp_blockchain::HeaderBackend; use sp_consensus_aura::AuraApi; -use sp_core::{crypto::Pair, traits::SpawnNamed}; +use sp_core::{crypto::Pair, traits::SpawnNamed, U256}; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; -use sp_runtime::traits::{Block as BlockT, Member}; +use sp_runtime::traits::{Block as BlockT, Member, NumberFor, One}; use std::{sync::Arc, time::Duration}; pub use block_import::{SlotBasedBlockImport, SlotBasedBlockImportHandle}; @@ -59,6 +60,7 @@ pub use block_import::{SlotBasedBlockImport, SlotBasedBlockImportHandle}; mod block_builder_task; mod block_import; mod collation_task; +mod relay_chain_data_cache; /// Parameters for [`run`]. pub struct Params { @@ -204,3 +206,26 @@ struct CollatorMessage { /// Core index that this block should be submitted on pub core_index: CoreIndex, } + +/// Fetch the `CoreSelector` and `ClaimQueueOffset` for `parent_hash`. +fn core_selector( + para_client: &Client, + parent_hash: Block::Hash, + parent_number: NumberFor, +) -> Result<(CoreSelector, ClaimQueueOffset), sp_api::ApiError> +where + Client: ProvideRuntimeApi + Send + Sync, + Client::Api: GetCoreSelectorApi, +{ + let runtime_api = para_client.runtime_api(); + + if runtime_api.has_api::>(parent_hash)? { + Ok(runtime_api.core_selector(parent_hash)?) + } else { + let next_block_number: U256 = (parent_number + One::one()).into(); + + // If the runtime API does not support the core selector API, fallback to some default + // values. + Ok((CoreSelector(next_block_number.byte(0)), ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET))) + } +} diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/relay_chain_data_cache.rs b/cumulus/client/consensus/aura/src/collators/slot_based/relay_chain_data_cache.rs new file mode 100644 index 000000000000..be30ec2f747d --- /dev/null +++ b/cumulus/client/consensus/aura/src/collators/slot_based/relay_chain_data_cache.rs @@ -0,0 +1,127 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Utility for caching [`RelayChainData`] for different relay blocks. + +use crate::collators::cores_scheduled_for_para; +use cumulus_primitives_core::ClaimQueueOffset; +use cumulus_relay_chain_interface::RelayChainInterface; +use polkadot_primitives::{ + CoreIndex, Hash as RelayHash, Header as RelayHeader, Id as ParaId, OccupiedCoreAssumption, +}; +use sp_runtime::generic::BlockId; +use std::collections::BTreeSet; + +/// Contains relay chain data necessary for parachain block building. +#[derive(Clone)] +pub struct RelayChainData { + /// Current relay chain parent header. + pub relay_parent_header: RelayHeader, + /// The cores on which the para is scheduled at the configured claim queue offset. + pub scheduled_cores: Vec, + /// Maximum configured PoV size on the relay chain. + pub max_pov_size: u32, + /// The claimed cores at a relay parent. + pub claimed_cores: BTreeSet, +} + +/// Simple helper to fetch relay chain data and cache it based on the current relay chain best block +/// hash. +pub struct RelayChainDataCache { + relay_client: RI, + para_id: ParaId, + cached_data: schnellru::LruMap, +} + +impl RelayChainDataCache +where + RI: RelayChainInterface + Clone + 'static, +{ + pub fn new(relay_client: RI, para_id: ParaId) -> Self { + Self { + relay_client, + para_id, + // 50 cached relay chain blocks should be more than enough. + cached_data: schnellru::LruMap::new(schnellru::ByLength::new(50)), + } + } + + /// Fetch required [`RelayChainData`] from the relay chain. + /// If this data has been fetched in the past for the incoming hash, it will reuse + /// cached data. + pub async fn get_mut_relay_chain_data( + &mut self, + relay_parent: RelayHash, + claim_queue_offset: ClaimQueueOffset, + ) -> Result<&mut RelayChainData, ()> { + let insert_data = if self.cached_data.peek(&relay_parent).is_some() { + tracing::trace!(target: crate::LOG_TARGET, %relay_parent, "Using cached data for relay parent."); + None + } else { + tracing::trace!(target: crate::LOG_TARGET, %relay_parent, "Relay chain best block changed, fetching new data from relay chain."); + Some(self.update_for_relay_parent(relay_parent, claim_queue_offset).await?) + }; + + Ok(self + .cached_data + .get_or_insert(relay_parent, || { + insert_data.expect("`insert_data` exists if not cached yet; qed") + }) + .expect("There is space for at least one element; qed")) + } + + /// Fetch fresh data from the relay chain for the given relay parent hash. + async fn update_for_relay_parent( + &self, + relay_parent: RelayHash, + claim_queue_offset: ClaimQueueOffset, + ) -> Result { + let scheduled_cores = cores_scheduled_for_para( + relay_parent, + self.para_id, + &self.relay_client, + claim_queue_offset, + ) + .await; + + let Ok(Some(relay_parent_header)) = + self.relay_client.header(BlockId::Hash(relay_parent)).await + else { + tracing::warn!(target: crate::LOG_TARGET, "Unable to fetch latest relay chain block header."); + return Err(()) + }; + + let max_pov_size = match self + .relay_client + .persisted_validation_data(relay_parent, self.para_id, OccupiedCoreAssumption::Included) + .await + { + Ok(None) => return Err(()), + Ok(Some(pvd)) => pvd.max_pov_size, + Err(err) => { + tracing::error!(target: crate::LOG_TARGET, ?err, "Failed to gather information from relay-client"); + return Err(()) + }, + }; + + Ok(RelayChainData { + relay_parent_header, + scheduled_cores, + max_pov_size, + claimed_cores: BTreeSet::new(), + }) + } +} From 53f6473c9c8c9d18b5ef0ed02a587757495d1dbf Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 18 Dec 2024 16:09:39 +0100 Subject: [PATCH 187/340] [pallet-revive] change some getter APIs to return value in register (#6920) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Call data, return data and code sizes can never exceed `u32::MAX`; they are also not generic. Hence we know that they are guaranteed to always fit into a 64bit register and `revive` can just zero extend them into a 256bit integer value. Which is slightly more efficient than passing them on the stack. --------- Signed-off-by: Cyrill Leutwiler Signed-off-by: xermicus Co-authored-by: command-bot <> Co-authored-by: Alexander Theißen --- prdoc/pr_6920.prdoc | 14 + .../fixtures/contracts/call_data_size.rs | 5 +- .../fixtures/contracts/common/src/lib.rs | 2 +- .../revive/fixtures/contracts/extcodesize.rs | 4 +- .../fixtures/contracts/return_data_api.rs | 4 +- .../rpc/examples/js/pvm/ErrorTester.polkavm | Bin 7289 -> 7251 bytes .../rpc/examples/js/pvm/EventExample.polkavm | Bin 2631 -> 2604 bytes .../rpc/examples/js/pvm/Flipper.polkavm | Bin 1754 -> 1727 bytes .../rpc/examples/js/pvm/FlipperCaller.polkavm | Bin 4723 -> 4394 bytes .../rpc/examples/js/pvm/PiggyBank.polkavm | Bin 5269 -> 5012 bytes .../frame/revive/src/benchmarking/mod.rs | 33 +- substrate/frame/revive/src/exec.rs | 4 +- substrate/frame/revive/src/tests.rs | 4 +- substrate/frame/revive/src/wasm/mod.rs | 2 +- substrate/frame/revive/src/wasm/runtime.rs | 46 +- substrate/frame/revive/src/weights.rs | 837 +++++++++--------- substrate/frame/revive/uapi/src/host.rs | 21 +- .../frame/revive/uapi/src/host/riscv64.rs | 20 +- 18 files changed, 508 insertions(+), 488 deletions(-) create mode 100644 prdoc/pr_6920.prdoc diff --git a/prdoc/pr_6920.prdoc b/prdoc/pr_6920.prdoc new file mode 100644 index 000000000000..d80a77e0a71f --- /dev/null +++ b/prdoc/pr_6920.prdoc @@ -0,0 +1,14 @@ +title: '[pallet-revive] change some getter APIs to return value in register' +doc: +- audience: Runtime Dev + description: Call data, return data and code sizes can never exceed `u32::MAX`; + they are also not generic. Hence we know that they are guaranteed to always fit + into a 64bit register and `revive` can just zero extend them into a 256bit integer + value. Which is slightly more efficient than passing them on the stack. +crates: +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major diff --git a/substrate/frame/revive/fixtures/contracts/call_data_size.rs b/substrate/frame/revive/fixtures/contracts/call_data_size.rs index 32205b921d47..7caf18d440b8 100644 --- a/substrate/frame/revive/fixtures/contracts/call_data_size.rs +++ b/substrate/frame/revive/fixtures/contracts/call_data_size.rs @@ -30,8 +30,5 @@ pub extern "C" fn deploy() {} #[no_mangle] #[polkavm_derive::polkavm_export] pub extern "C" fn call() { - let mut buf = [0; 32]; - api::call_data_size(&mut buf); - - api::return_value(ReturnFlags::empty(), &buf); + api::return_value(ReturnFlags::empty(), &api::call_data_size().to_le_bytes()); } diff --git a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs index 1666cdf85ede..302608ccf87c 100644 --- a/substrate/frame/revive/fixtures/contracts/common/src/lib.rs +++ b/substrate/frame/revive/fixtures/contracts/common/src/lib.rs @@ -121,7 +121,7 @@ macro_rules! input { // e.g input!(buffer, 512, var1: u32, var2: [u8], ); ($buffer:ident, $size:expr, $($rest:tt)*) => { let mut $buffer = [0u8; $size]; - let input_size = $crate::u64_output!($crate::api::call_data_size,); + let input_size = $crate::api::call_data_size(); let $buffer = &mut &mut $buffer[..$size.min(input_size as usize)]; $crate::api::call_data_copy($buffer, 0); input!(@inner $buffer, 0, $($rest)*); diff --git a/substrate/frame/revive/fixtures/contracts/extcodesize.rs b/substrate/frame/revive/fixtures/contracts/extcodesize.rs index 0a1171be30e9..3f51b69b46db 100644 --- a/substrate/frame/revive/fixtures/contracts/extcodesize.rs +++ b/substrate/frame/revive/fixtures/contracts/extcodesize.rs @@ -18,7 +18,7 @@ #![no_std] #![no_main] -use common::{input, u64_output}; +use common::input; use uapi::{HostFn, HostFnImpl as api}; #[no_mangle] @@ -30,7 +30,7 @@ pub extern "C" fn deploy() {} pub extern "C" fn call() { input!(address: &[u8; 20], expected: u64,); - let received = u64_output!(api::code_size, address); + let received = api::code_size(address); assert_eq!(expected, received); } diff --git a/substrate/frame/revive/fixtures/contracts/return_data_api.rs b/substrate/frame/revive/fixtures/contracts/return_data_api.rs index 2a390296a419..1d483373cffd 100644 --- a/substrate/frame/revive/fixtures/contracts/return_data_api.rs +++ b/substrate/frame/revive/fixtures/contracts/return_data_api.rs @@ -75,9 +75,7 @@ fn recursion_guard() -> [u8; 20] { /// Assert [api::return_data_size] to match the `expected` value. fn assert_return_data_size_of(expected: u64) { - let mut return_data_size = [0xff; 32]; - api::return_data_size(&mut return_data_size); - assert_eq!(return_data_size, u256_bytes(expected)); + assert_eq!(api::return_data_size(), expected); } /// Assert the return data to be reset after a balance transfer. diff --git a/substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm index af60be273d74d130967cd91c903ef85de1bf69bd..5c3995bffe35c6595cf3758f7ac98f295c6e69a5 100644 GIT binary patch delta 2114 zcmZ`&4Qv!e6yDj}-Pya|&+hixURzr4mT74#vmN+l3MYY{=gA2h@tBA^MHXbd2hgqUCuLD2|^LaU;0?|O(PPWEPA=Dqj5 z_kC}!cl5E*%H*k&7^ZOmlU@ij2mL8!bE})XZ}x%(oAQMF=Pq8bXoaw)V-&lo^d4pj zvz*z;{KeFuIy4QfM7OeWb{pHnsfg^ZPlm5SUubC-HMy*=KK@@)C%xq>`j39Uhs z{1;-97|T{`LP50V{^ACU(q36M>21}s>xe5H~KQKN_ z7jnMhHX9DTyyM9=4$%{OYX4TcF(-|CTfsnsc5#nO<2VkF=R^Q6C6FZ2YJMo{p|kiB z{>bkbze88?&#@&AdcfhuYAM{X4mwG0_bs>2h%<<7A&?u3Utk1@tn9;h7VQz%6&@2a zPA%>9AweoX(Tnj1A`Ljb^jW+@5prQUtnLnnT+hBhU)^p(9^->^F?>?_x?k0SS@&c)bD%*J!CafG|QH4WU38h;IJUoF20QM6dU!si#;Rj!5(Jgtf#B+ zw^GS9s_0ubn{txP_CEUZFBqSob6mqcTljvQF>k?LC%wr%z;pWsFqc9!z}&&Fv_vc% zbRDLY?avrH8o~xZMhTw}Rv*2?JvjHwC5%5s89fnaC!lD02rVev#zK0?J)0j2ab8;C zsm?vaur2A4iZId%KVQW77@g-?DJ}x5k)LX}(#tasx3+*&GXLg?|Dp%y+nFq*i4sI# zwuV6;l}gZuv{QPMbzPvk{34o9&&yrtFnvbZfJV}raz~+SbXx9wMCqa2ZN=%f;LnWM zeg1SCr{LMm{Q5_X57H;RrTlwfZQc8Vmmnc^9^++wX{F~Z0=uy^ zrLE)6S(9Bb1`a@3IP4s=JxT0U#2XElZC7ha&?IGnpcqIbN!iYFU02qS=-On_nvPh2 z9yN@x9uA5!E6TwyMQeq-$nkL06a$K?stF@$Bn(*B4`Y0K5luUqNF*e+ZiuF7q`{m| z$`qZLnxdNBRl2EZ9fl<9CdqzMKt)=YLc5BpTQ#`WG=MjuylD`G^p5T#J)#7(BDtYb zS7ce&t4ve#*0onelvJ1~a;l?S-5GJn#}f$>iLNJQZIUUa$X$T1c4*|QCs#X|K$t<%%A7OToU^t-a8AO;}QZLC%s))^C(t)rr!ksW7Pa^seXt7m0vu lMm9ZNS7{?wBuRybzziVVw8G8)A!U2ZrdTNJr|5wI^EY!QHYWf8 delta 2179 zcmbtUeQX@n5#P7ByR*B#kKOg1?`$WQvw5axD>yFkM|Jc9{kKm~@6FB3`_24j=Jt>4XV)8Bwr=LQeJi-qBEY@XSu~y)Y!UA5>+e6@A?>*D z{{G$jq@Vq!gFoDvng4=67`QF)S)e`mN>B<_g>s>4?X-4}KBLDCW~3{3RUQhT3eSc= zj5JgQgjh%HHnx^*UiW5JTc3@zWUJ!&NT;Ds8GDHUf-GtL(O!@_o{x6%I*C?+#_>t1 zp7Vjko+Mvt`t}0A1SiPovfa1MJGOX7isSb}nJYJx&1=2T%RVNZ{Ka{;mXEZAIUeMI z0G*tVALtTw0frDm()@4F15^vplaC2XmwvGeXkPJ-zb6+XWMEBHIPEWIT`y_ji7x<7v3_5p zcmSml_JnVpxEZAkJA+aLr5dIRn{IyhD!_XcJvoi=4Iw*=UuF zF(PRcr|5sS727RAxl-tNLHX#2le)f{r zwfLD!0B0qV*Stycx(UfESn(iYyn6qOvl9~Q3wG2)l&R`re!u9^lA7=S9N=E|hI%k|R`_35Ew6nB@Fwf4I9T0!&A80!rWhJm3ak&p ztlED!zen<@)b8*t<&pJa2?UU|);s?Vu+7IvuvTmTGRo~f!5eBr+(1f?run5uAMlIG zum96UfOE_WtP33zuOZzsin=3ia7F0WJtz&JGvaI%AM#*onwDYeaj;9Vzfz5Ub#8E_ zcd-^j!x_kQzGsDC%^-9UfGk|H=~@HE0yae@f(--!tsTL1@FVsljjJ1 z5Wf^M+4^u#ewy3*FhG2+CwCbs_m(1fL%hR}06J5vK9cJH0njkZl+caF=a;XQMU%Ar z)OmmpC)%TbBjV+c0KQ;#)pdg_r@aa2kKu(V-_xeKXNRfMM8la>gYAxNsaU1Ev{BI$ zGNH85ZnM*FoEC4PlkxFM^~mTZ%StDVgsk(jo|u%ql^Ub(r!uaSpq6EEx}p;u*V3i` zc*t8R#~CMt40bzVv=%MSoXoa*zb<->^D$&Xn z<4tBk(+k(>c*pA2?O_MeT&n8@$4Qx{X`8Mf&~A4p@~~E&ah$a6ZbDN!QyisZ@r>2& zI+j_)#Aw9+dd7J{TJ~0SJM%cTO*3sd&UKMwy~vr}I-9O;&Y&6@mlrL#cdfQ;(0 z3>L-`wd1adXUtl|H=CWvF`x{Z07dbNH;*6r0;93)5!G!yX(K874Eyi0o!FJhTS zF+P?uEz77}WtI)gXJ*m2ta{uXcN3Odujq6r75=K%uqEFDwwM|PEOWecrYOQSHQGkQ ns{aB>QP=ZyE%U}=3Qrsid}Mq5#Ep}AT@O%3l(tO6fnn}H2O>p| diff --git a/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm index 67f02042c784cdc354e1b2507ecbe67ab1f59050..7880647f379277d3bcb7f6aea40fc4ba52938b9c 100644 GIT binary patch delta 434 zcmXAhO-NK>6o$WZ?z!K&_hQYxml>5xOPlg1!GzI%HVv~V@1#?Z3Ikh|;HITSE!>4mlSZA%ck}Qro@XWeI_zJ)-mO%Nqy7(A zeU8ugpSm7x)1BMtbQbi`@K}2Ep?-YvFlL=q^-FCi4P&qX1^5a*n8rz*!*95S^~R_X zp=b1ys&s>n>sNY!e=u0K)WV0l%|6%ai}(+Km@Mt$ncYD?tuTe&AfHvpri8FT@9r=! zDOodWq!%y2tSn-!A6EuuQraV)Qf0SdSk^ECS7ZZQq}sT3-YFMiRn)oP_TLtnkCIi& zW0n}mPMIaStcK@{A)YYR{;PZeuN4kk<#I^feG#FCttlhYx|mXcbKlV8c!nJCTFp>%`c zE5k1aNycWz&5S1*Z!=~xwJ=R$TF>-^Nr2gkxt&>;Wf{v47JW8*wg|QZY~}3l*d;j> zC(AMJH8~I^U=by15GAIOA?hF`_&}(Pk%gUwgH3=*_kRMr0Rt10xF3@sqqrEm1p|{i zn*#$gJKN+xjOvVUCaW-IGB!=_W0KQIU|?n{_hS=eVQ*jn>E#Lf$IdRv!XBL=!NTds z3KR#4^4Uzj!lc8>0y1e5bDUswdIe*-i8zY~L|SOhH!j>=mpG5HYdh$upQ` z7{exSWj0|{ocxMeo9XMH$>J;;jHf3%u=p_6P3~n`%6Mb4F>3{5{Ny#PfsDMH|FN1e zYG3=w&K~)Pg?#}-bcO<(fYvb}-}6s&Mn!bGL~1*GMh8$L6DXnc{paL(_B2M<$;a5Y zG4@TasNJyzgfXTxnXi^qXL7<0HsmF>ykCmcEn!OzzB0@@8Dh(={o~E8& LlSC#Laqa>D2Hb-q diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm index ebb06b6949e3856a1e367e99c1c7481fdc32e6ae..1f036fa37948fa66ab9928b41b8658718bfd513b 100644 GIT binary patch delta 414 zcmcb`yPsDmAk3G6VLuxK7%;Lk98{PnG|N(vB{?xCr&WV3CAASMpwpg#%jg_rrAs$%tQxEa6QBAje25a@h!PblWn^Jz;b0SB zTAskp!NBClB$&W1z`(@LIyse5ov~nYFJmSX+yBWw7-cmS7?_#L{a6KA*c%u?T6nJg zV`rCSVUNy`VBz#*0g8h}`3_9BWisXL0?IvPV&UYN+`!by$^|lWG9Pmsn{K}DZ4$1?7n?8;)tSU0(arJ9N9&tyhcXF;$Q6LA(Hh_P~OCx^1GU}E__`9G^% zz0PkIb~XVebs)vaCZHn!n_+2-3a6HeNRWr>QY8}=r%5WRicTDg4J|6FE7d$bg<4gD z+FPVrr$i}EzSPm-;n^YJ#G#jB3L-)ZRq*5=pyD6krKg^}s3_h94+g|-^<5r(zTfwGZ_~SJZ+>BpkX=Uh z2RL~j#@^GJ8N712*<26V)oW|bo447+agVOcI_Z!e*(M%Da0yo75p?Mrx?(&x#&HeD z%+M^wpXW}zt2w7?!^dkQ`NnYQxu3jSfE+F6&}S>$B9vw;)P?K_cL<$jJ~24cRX7Su z`VQQf{rQJ;xmb5-t4(V4QMAymxG5_`D7VtcLt@ZY#dMLk$)5AzhonD-e6cZ_6fxm@ zbm+A%(CML8l6|3QCr>fu=mzbXi5Xh@7hQ&X`l3;Qs%{(Q|Ma7=1Y7zTo`vhWiFfJu zf&PZ)-2Ic2S@iz7bDR2%xlMok)?dxs{mcM4bB-KJ7BENf0om#ZyC5>vQXtjF{DBEq zi7DHSED&N!PRp{&i}UK-r;%Y*4U&N6T!AlYJ%7ws=^#gVoHuMXY`wJW6bCUo7 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm index 56ae81c7e5b06c34b302d3f1158ae48c4ec29092..92304419dda704a2d4de38445d3362d995ca6487 100644 GIT binary patch delta 1967 zcmZuxZ)_aJ6`z@%+u8Nq`gX5A`{G#j-f4aESyp0#i>xN1$Mqn)1|OHLYs_t39U-vm z!&1lxVmm3ilX{6S2&`nsg$BtA1gB}zFF-2l@<%d=ilPcNst}^6A1wGl0Eq-sHHlrv zeRsjMNIl)#nR##Cy!ZRfoA`Z2;uGA z#k=$?1yw3UBadOqs3fZ7Y>}yml$O>oR!A&*Eg=)p%W!WpH$A{#}7vI-K_rmy(p zB#!^ePm$Kh;*BtS9HY9W5ko!8-yvV%*Z2itCoc2LLfe)ONJmmA(~&?XbF$_@Mv!sq zy}XmQl_46bQHH5ZQ7k8Eo>o~es8gg6eUubhJ>o?xB%&?bt(z#?1M7@^DNwnQCuFx* zfYh2KzPBk-j_e{e`>s*~8;MzdTBJR)waF?NaY>A(*09hkmI_)CEwcZ`tDT|;Cn zxroP-FM|9Q*NA&qME!6%MX!~L_}ATI*EDi5*%Sb27P2hNe+;W zv1B*d@Z4ok?;?(;7Q8f3l379vo=H5nUJ~~Z;_4))g`)r_$F%-7JuT#zeYoJ}J?$D_ z4z{S>Dbdp;$II`Gb>11crNK;Rr~$dZjkedzj-sA`e!YH~JzKAb*{cD*C_Nd?QH`;O zH#YC9clsJ*d-*|mJK3=T@LSooUeH80_}lVSc^NsbS#U4GkQUr4Xe=26^eTu1f*LG5 zBdUEzuQIm(#y6$2V+vLe+yMMoNv5#`U{S%%ff z9`;-v^b?r4>ANwIz_0+I$FSqZ98Y(~`nz{gc*95Uldw?G_wxu!n1=1vrf)pFJA!U zURqRtSONF(b;r#MZjm_dsN5WwY)PQ?o^*eCQ zSH5QKRNX4L6(IdvxD{vub+{D_9HzI=9A1Thr71lu9atq(j;jD&c~Kpl1pneHSHer> zmsS`%iRRt;QD@8iNvBZ4sy{jlecPKLGXO5~n(U{Y!dRq7Q>JN%CQO)m% z6YisdMlJd;l&RHjApAg{{3xREH*WhZ5~jqA`At+E(7W5@r+; zC<@5m$kmGljRb~P>c@|mFkn#~O|PIcRYUGET5PlHOrqb&WU~u~#SN=!#C_w$c{O9! zj%Q1!Gkzg{S0=LO3_U(qvwb6Fb)~EYf7myyY}WP{66cJpzu^1Eo{VwsX!Y4l2Ml(* zrln*pBjIZG`1_PnaQlHZ7>RSNeIKh_*ZO-hHM3f)z3{y6FJvN??U(#HBi@sGyH;B; zezjoiIWcIbEGu#>+s~iUjB-^?MB<5$P0KzqTpQ-9fuMa>*O4mv53|7t@&Et; delta 2391 zcma)7aZDT68Nc`LeDBVj@tuRg0vZnMAO_OJ4M{|ou5uhF+%bV%7ZG+((VQ&Voo%Eg zn^u#i6HK#&iQ1*{(Gk+3q0CVTYL~T*lqg##rcT5ScyL;dJzVE&7_xs*=bKy_4Kysj$VZIw+DnCS+U&qb|-Wxfmao;>TG<2?m zKYrrm(6>+X=kwd~x!o31V8)pRW*ah48l6H%@DzRve}uQNTe$(Q#&guu#P8t$%pW0* zLPqe54~ah#FN&{=aqo=xn)hy1L@Gvo>M}V*zC$@D$<*C-5?!P7{H_xUP>?P+cvN8QjgQ3`=ScGzlEEAqWq8^9fs@6}ZNGd#NCUE?96_AeCny^Ss=oZdPri>B*R-;! zJmFl&e@C;<47+b+XpNA!A5bd_XAs691MozOSEyky5qxk?hQ;|lQK4{X$gcJ=uzzvW zeprAlDmw{o%EijY920`QZDsVnDG#$|@FZ*2+N>QMWdWvC?PlurAKxdWk8+#{uACQe zImSX_$5_b9E5P(AvOzmDnkJC8nHj8LurI`VI5V5XR+0XSzScBWeXXN7WLqkPD1giJAVt7v9eyrOrgZ_^Rt#2O!qk>cdj|Vj%a}9mSU0_P^gSl_Iz?RP&TARu(Kn${@-cZ@e-IULm>~+ zOxKCYFKC(&Ln92LmTp=CJYuXRqa&?dAW@JAhzj&@sYzare)=gPDF{)y8N2SlEb+kC z8guzVf^9L#w2p(rNe~--nW|v~M8fJbs6y2!coYy&RRle_o7S@k${V><9dsV?HgxoI zfa%6{-kXDce!GV3!Ogq(c`xZo!NSkhoX34lK=eG`pf~k2&BUZhLhU5Y>C#PMx(HxF zs{!CLMHSxtAW2O(RgfX>QQ)mW7S1RR5?;O1h!@o&HNnwZ+IfB z{|1kK^J7A4Rx9w#Lx@4@!ZQHy%)2exg$L9wJc}#XT7W}Qdl9x+Ml^c|>=%$CT@|G< zuXNFyo!jJWR`3h+D|pzgQaj1ZJx{_e*H)anzWI^omI#?*rY+9x$kSh^_0w~-L74tU zy2|}@c%j3lMd+|;0-qBqt27v*wX&^5wz%I0j_`8wYY^cu=K=R)tm+OScc}Dd(DmtO zPC?;;F)yc7Fx~D8s?dM-`+|+WV91I1>+olb&VGN*NZXPdZ$N_w;^o1{CFXKL6Wg_# zl-^>L&L*pxm2f!RVqDaWuqK|@#%t!}c)XycwggMJy^jP+#rBrba$=&S8AiKdgkLDb zTR2=QX+fix$XB0If)w8<=tP+U?9Bx8OgpJ#JLQz2Fohs;{tNhzG z9{(|xOA`|b$RTAIr3gUqC4*8~HhSZrNnL6)QY8%%KcE?xwB~?u=}`D=@q#-^l*&e? kH1UOBPfumL7V(MMl+dghZCXLbTJsOUApYb2lrAv;0%d)9E&u=k diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm index 416577ad8f23e9229df7580011424dea4ec4f21f..b29c640a2fee562cca818f8289bcca2cbe285fa9 100644 GIT binary patch delta 2865 zcmZuy4{Q_H8Na)n{odIQKD!Aq!N4WDI8KJZqtJO7tFDpT+@azbCuFDh6e$p_yEtJ> zLq-y^DH+QeC$v)H3nK(&LX{B-RXe6_BG68QtrDTV_?3rqAQfp&gV2(1g%tLlN_bm5iZixGh;}4E)=rDR3?-tsGmxT|6S{HVI#(l$G z?OFDON{^IE>)u?ad*Aoo^;U|CsEU@tJ})g35tDNN*H|9$*eP=7+mXm{@e*G<3NRdU zSU+L6)_1KT^g7F9SUFhIZa;MUkzlx^sKA#QA!eiXxob$29*LO92 zuk?2C)=wGKM1OJaEj=rD0lIkVdspGbJUn&Y)oSk289|K7*qtWk_@Y!!6MPVum~Vftth_SD85Pqs9$}npYXN^OkkZvAJFf z&S2UZ4xU1uF+|Nsa2~^p7tNNmg;wBzWv~yxmY96}5&NLwrQ$&V7omLtI!KR+v=>t& z8tik+eIDAO$PI`X;pR&oYN)|pM(zc4opeUzaiLj2!3OME#LZ(KugAetQ|Xz&Jp(Rz z05(N?MltOQ_bkE;AgVpRsOJ=-ojUD`^qdjeLV;nnEhK_rFwoS-CI1^s$?ucJ0J$QP zOPE~du5cy;I$Pu265NuDGbA?7QzOh$Q)1c92n+dSiK(z9oe36L6Jr1dg%~|BsKhW~ z(23Fhs8Y&4dk>+naD}p2FQ?(_09P6sVM{}2S!@~#;N^hKMRu(;w8*Z(-LPvWxD(?| zKuQqfDr|iVK5ww5F#_W+SVj5Ss{w!I;OQ3zl+@_a6Me zf&v?giX8v~QE-ZYg~252OL#*;u-hfBs0>r@_+Os03Ysj1Fo;7Tlq@SUf0@wx}!TSs83SR%% zi#|jkp#dP(17?D?7a~(_Ic;r!%noV?stM`{BnveK`6-lCG4P08tnmmvkLf&J2+#{6 z?NU=iZhBBp9p~iZICa7u9P-FRsQCt>-O<#~F>O~;f*{YkQVT+AfR{&6YOn+X2reS| z1ZZEkvaXPD^Ir%#*QlAWuDJr@Jbc}G$lDFi=d&k2bV*GEF!`g0teMF7Fc~^nTWAJi znFPy#jc^5)$rngRlpG0=Lkj5=Nta57Fgd7`UhWuYj&N4OT_$V-Eb_3B>K!!^rx^PE zKM~sa&`$HgNU9dn9JSM|*lAYhPSdn*z2*Mle#kZSW+_?g6vM0kK&WArHFaLFRgUAx z;Q%#(w2L=d|2x=Hu?ok~^9r3;>4Hu#B<6bOzBPAd?gHe%9NQv~VaqF43@dhRUdE|0 zckoq#q@(hysCfpZj(FtVe5w)G-mINy9(Q?O5=d9L_KlWVx7V%n!LT5QT`jYGaJS2| zAhb+F=V;`KiAlFeIs)V%5G(ZZtzQHx#~x4v+hK`zSYbP?2=M4ikSqIiayda3RS21O z2Rt|6VxlxH0jOsNr!$i0rqFp>f_HLB@!}m1Si{C82BdD=?Y|==0dxZTSIX{UAWm_> z+O+Pu;YySqkBWm^(&G_vaC3S*EDml;k4xfUFg+d+2REk2MRBk)ox$S32FM+>2ih5c zQ;rHtYin!v{~b*$@~lA9(bk35amXza`tC!INcT%#l!mtm*DtXq^o%I<6Q>v{2Qo>R z2QpDSoM%&FF0mXKFT|bjm$U?3NU0m!a@SFRznGpK@Ba;<;QjnfsdQeU3kiBbpBtLX z%$=HBfHDAZ=*ynV*fU_K161X?ggX1Be6zlN4=#^8l}`wi?9MEJ{pucY0G&sz{iViU zm3Aqf6Yit*w=WVW;@ioxdVfZDUb049Tsm z*dkeq_epE_x+k|oGq($3qs+Q zeC=WDcVc4S1ZSqN!b^{6_f%6?x}VJ(-PmMeYdWY{8) zXEHhYWKP~UH9IZCw|#zojKz}KiQFrhOeR)6H7}M|Hv-pcpU?NU8jAt!uv{}f0ep99 zd;C?gn7StkFXaoerZvi1eVyj7smkW2ZLA`00bNV_Uuk?P^B?=O)45O1&hN>InqRIu zZogM$ciItUTjX8Bl)M1xm>yNL$vs+j^CPy8P_qTruBnz?T1M9PTTNw~gT*b${Ni4} flHDUu$pGZr<+uL$1T8JA){e`u$=n{7z%u^^WC|(` delta 3306 zcmai0e{37o9lv`%`@OR-^>avKCpE2OE~yiBG~<`74uOeW;)~YEbF;|0w`^Wh>&}-< zGo@=Tai&BpU7VHzvC}mPWUH!lO`5@&wy6O!mcb+n{KGaTp>41rHiBD30caf>;Ke4H8Lf^Ls2Bw-E z2h!Pr?+!VNPXy7_tr|DZO>$3i9@LJK$Uu*vf7w5=58!1yLWaf1#aG2xk>8 zuIF7YX+`R;x>Pl|<@A=A`?ULQ+T{6?rx@YCdi$D6!)k4tS{Kr5Bh{Yu>chN%1>&G+ zK7|Q~khja`;k|X3a;U=*;B44Y{29NqHF*Q$r+9(DlKr-%^%b|iVjMb*%SYhGRJ|>Q zQ@=!LH;+EJR=menH(J9XOsWJN;cRHIOY{hMjI+E+FJ8mAUcgKb3t|_6d24pdlh@o> z;CQ`Fz`P&|9EDf9XKe#W+kmwVXxo6d4TvY|097CX2dy3itoQAL2;2k$ygwtTsk-U3 zramJH3^}A`vL)_c@!6iTBhBK}g*?3W1b1i0&#T^f?F9~ZuvgCgvk6r*?MEEA<7puB2f^L3_xlaE z{!#d10_`)xCIR_T0coe;P(l}C+6rg6(4K}N#k55jbOvw14Y1^ZCD;Y9HI6UQj94^x zt#K8WT7;QI)#J=m^;q8^P>mpMo$C@jOnaAO`Z~vFE)~z(x3xz6GlXSg{*zdmfYiqQ ziv)hS@$8b2dk~N*O35$~1al-mz*%g=p zL_K>DXHR05F@;ZPhNK8#|S9+3Xr-4 z_Ac0Zx#ske#@0V$2L%Qt2gL?DhQfmYmg}w(hxq?1@Hjh5*dkl*<_}?Kjg8 zA^9PaKIZc0BsquoUBGN0nSO<^R4Ofs@}e`nB&JP49>?zVkPQyxU%~P*C=j#Q?Q9m0 zeT1>&2Gg^}h{x**81VSd>w?uxJsxXz*%>&n8zz5!o!7IaekemXghGO1qF@A!vz2Kl z9#H#|>VrP@KuSGC)qY*g5p}<*9z?@P=WJGL%6f)ITW*^2-#^4yyxN)fov{?WAx z(_1@W{)G)-%Ff2vVuUTl*}3F=ZazPMa()T&2h|8Nm!2V#NsN2|PA{FunZZCw`;!~c zCeTT63F&swwyXca_LTsqt-cY}-q5kHE;wxL!`*qfzl8(6!lMN_Ds}7kF zmyZ%Sd(R@^_N=IxRD(z`dGiwp4;mCoxG@L4Z7<+nfJwlO6%F@y?t7PkI73c(=QY54gW^?`p-l)!;q{a^`BuakKHcA z5w;j(OG$PvK7Vlj2lJ25p93N26WiqpprfZ}NOhhsJ}P-fV`?TWKSRB@Pyu9P+zDcL_u&DO8?Rc(x&gFI;*zku$Ippk~6a05Or6sX@8hTXM zhN@F_z^C>F)cv6fr@Mc13FGj`(Az92`Yb87SyBuGq5iD;>}XPbCa$i;An0r$AYA}< z5oUw{6xttXL3&xt%m(1MdOqSNm#*+ytQ5GggWl5z&~IHS1uhJMgX_wIPg?N01)rLK zj^=#EYM72}H&)x|$Tnj&L`QBmRs(dzZ>;+0$ktI~mC}(0<2gce^>mmT%VC=H8p~}o zS7$7TXs*^+4$xeUvFxL{n~Y^jbJfNIVFeXEE$Zkvq=+LNOO#0}M&6uQg2pM@oO}G~# z<@+GSbgKpqll4#a6f5Z|(lgUzV)D*qW<=osK@#qb7!+w7!D`jerMb;Yl1_eFM(3gF zhS6Yv*Wr#!Ld7{~M81ee@lW7|RZGN*pl;Js2!X+WItTonN&#}JWn$n%`{3nD6YC<- zwYhftu8UR=Xm5pqchG3?B6nt77F*?-M65|E9F4mgJ;7kGsj;0jj*ZJrOU;TBY-(vz zl!81py-=8zTbE>IG#Hedl-9!0e5^Au)m>AF-6_8wT6o<#JAHpN+Sxu%+uOOu#^~{} zv9V2zBl-M-d}2ZV+LuqrN+&{v>4iDDX|g7`CpZ<0j+fs}btVdWbQ-*XyI`TP z+1oBFtu0D*Q)6Lax0X`~AHhGqYI}}c7g+R$lP^k_oK|?NdD~*lZe*qHm$L|0D diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 28736cd8d5da..6f84ecdd1525 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -596,19 +596,15 @@ mod benchmarks { #[benchmark(pov_mode = Measured)] fn seal_code_size() { let contract = Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); - build_runtime!(runtime, memory: [contract.address.encode(), vec![0u8; 32], ]); + build_runtime!(runtime, memory: [contract.address.encode(),]); let result; #[block] { - result = runtime.bench_code_size(memory.as_mut_slice(), 0, 20); + result = runtime.bench_code_size(memory.as_mut_slice(), 0); } - assert_ok!(result); - assert_eq!( - U256::from_little_endian(&memory[20..]), - U256::from(WasmModule::dummy().code.len()) - ); + assert_eq!(result.unwrap(), WasmModule::dummy().code.len() as u64); } #[benchmark(pov_mode = Measured)] @@ -783,19 +779,34 @@ mod benchmarks { assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().minimum_balance()); } + #[benchmark(pov_mode = Measured)] + fn seal_return_data_size() { + let mut setup = CallSetup::::default(); + let (mut ext, _) = setup.ext(); + let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![]); + let mut memory = memory!(vec![],); + *runtime.ext().last_frame_output_mut() = + ExecReturnValue { data: vec![42; 256], ..Default::default() }; + let result; + #[block] + { + result = runtime.bench_return_data_size(memory.as_mut_slice()); + } + assert_eq!(result.unwrap(), 256); + } + #[benchmark(pov_mode = Measured)] fn seal_call_data_size() { let mut setup = CallSetup::::default(); let (mut ext, _) = setup.ext(); let mut runtime = crate::wasm::Runtime::new(&mut ext, vec![42u8; 128 as usize]); - let mut memory = memory!(vec![0u8; 32 as usize],); + let mut memory = memory!(vec![0u8; 4],); let result; #[block] { - result = runtime.bench_call_data_size(memory.as_mut_slice(), 0); + result = runtime.bench_call_data_size(memory.as_mut_slice()); } - assert_ok!(result); - assert_eq!(U256::from_little_endian(&memory[..]), U256::from(128)); + assert_eq!(result.unwrap(), 128); } #[benchmark(pov_mode = Measured)] diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index b6f0e3ae1a81..a6a259149768 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -298,7 +298,7 @@ pub trait Ext: sealing::Sealed { fn code_hash(&self, address: &H160) -> H256; /// Returns the code size of the contract at the given `address` or zero. - fn code_size(&self, address: &H160) -> U256; + fn code_size(&self, address: &H160) -> u64; /// Returns the code hash of the contract being executed. fn own_code_hash(&mut self) -> &H256; @@ -1663,7 +1663,7 @@ where }) } - fn code_size(&self, address: &H160) -> U256 { + fn code_size(&self, address: &H160) -> u64 { >::get(&address) .and_then(|contract| CodeInfoOf::::get(contract.code_hash)) .map(|info| info.code_len()) diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index b73f50e34bc5..90c032bd0840 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4377,11 +4377,11 @@ fn call_data_size_api_works() { // Call the contract: It echoes back the value returned by the call data size API. let received = builder::bare_call(addr).build_and_unwrap_result(); assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(U256::from_little_endian(&received.data), U256::zero()); + assert_eq!(u64::from_le_bytes(received.data.try_into().unwrap()), 0); let received = builder::bare_call(addr).data(vec![1; 256]).build_and_unwrap_result(); assert_eq!(received.flags, ReturnFlags::empty()); - assert_eq!(U256::from_little_endian(&received.data), U256::from(256)); + assert_eq!(u64::from_le_bytes(received.data.try_into().unwrap()), 256); }); } diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index e963895dafae..b24de61314f9 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -242,7 +242,7 @@ impl CodeInfo { } /// Returns the code length. - pub fn code_len(&self) -> U256 { + pub fn code_len(&self) -> u64 { self.code_len.into() } } diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 47fdfa8bab09..d1a14ca1a93a 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -290,6 +290,8 @@ pub enum RuntimeCosts { Caller, /// Weight of calling `seal_call_data_size`. CallDataSize, + /// Weight of calling `seal_return_data_size`. + ReturnDataSize, /// Weight of calling `seal_origin`. Origin, /// Weight of calling `seal_is_contract`. @@ -453,6 +455,7 @@ impl Token for RuntimeCosts { CopyToContract(len) => T::WeightInfo::seal_copy_to_contract(len), CopyFromContract(len) => T::WeightInfo::seal_return(len), CallDataSize => T::WeightInfo::seal_call_data_size(), + ReturnDataSize => T::WeightInfo::seal_return_data_size(), CallDataLoad => T::WeightInfo::seal_call_data_load(), CallDataCopy(len) => T::WeightInfo::seal_call_data_copy(len), Caller => T::WeightInfo::seal_caller(), @@ -1283,17 +1286,13 @@ pub mod env { /// Returns the total size of the contract call input data. /// See [`pallet_revive_uapi::HostFn::call_data_size `]. #[stable] - fn call_data_size(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + fn call_data_size(&mut self, memory: &mut M) -> Result { self.charge_gas(RuntimeCosts::CallDataSize)?; - let value = - U256::from(self.input_data.as_ref().map(|input| input.len()).unwrap_or_default()); - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &value.to_little_endian(), - false, - already_charged, - )?) + Ok(self + .input_data + .as_ref() + .map(|input| input.len().try_into().expect("usize fits into u64; qed")) + .unwrap_or_default()) } /// Stores the input passed by the caller into the supplied buffer. @@ -1420,16 +1419,10 @@ pub mod env { /// Retrieve the code size for a given contract address. /// See [`pallet_revive_uapi::HostFn::code_size`]. #[stable] - fn code_size(&mut self, memory: &mut M, addr_ptr: u32, out_ptr: u32) -> Result<(), TrapReason> { + fn code_size(&mut self, memory: &mut M, addr_ptr: u32) -> Result { self.charge_gas(RuntimeCosts::CodeSize)?; let address = memory.read_h160(addr_ptr)?; - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &self.ext.code_size(&address).to_little_endian(), - false, - already_charged, - )?) + Ok(self.ext.code_size(&address)) } /// Stores the address of the current contract into the supplied buffer. @@ -1667,14 +1660,15 @@ pub mod env { /// Stores the length of the data returned by the last call into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::return_data_size`]. #[stable] - fn return_data_size(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { - Ok(self.write_fixed_sandbox_output( - memory, - out_ptr, - &U256::from(self.ext.last_frame_output().data.len()).to_little_endian(), - false, - |len| Some(RuntimeCosts::CopyToContract(len)), - )?) + fn return_data_size(&mut self, memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::ReturnDataSize)?; + Ok(self + .ext + .last_frame_output() + .data + .len() + .try_into() + .expect("usize fits into u64; qed")) } /// Stores data returned by the last call, starting from `offset`, into the supplied buffer. diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 03899fb3f20a..db3c34a75878 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -20,7 +20,7 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 //! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `4ca2a44ee243`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `28f02a6d927a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -81,6 +81,7 @@ pub trait WeightInfo { fn seal_set_immutable_data(n: u32, ) -> Weight; fn seal_value_transferred() -> Weight; fn seal_minimum_balance() -> Weight; + fn seal_return_data_size() -> Weight; fn seal_call_data_size() -> Weight; fn seal_block_number() -> Weight; fn seal_block_hash() -> Weight; @@ -137,8 +138,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_729_000 picoseconds. - Weight::from_parts(2_919_000, 1594) + // Minimum execution time: 2_752_000 picoseconds. + Weight::from_parts(2_990_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -148,10 +149,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 16_062_000 picoseconds. - Weight::from_parts(2_790_037, 415) - // Standard Error: 1_371 - .saturating_add(Weight::from_parts(1_187_192, 0).saturating_mul(k.into())) + // Minimum execution time: 16_130_000 picoseconds. + Weight::from_parts(3_413_527, 415) + // Standard Error: 1_190 + .saturating_add(Weight::from_parts(1_184_912, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -175,8 +176,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1465` // Estimated: `7405` - // Minimum execution time: 94_592_000 picoseconds. - Weight::from_parts(100_095_688, 7405) + // Minimum execution time: 91_977_000 picoseconds. + Weight::from_parts(96_482_355, 7405) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -196,16 +197,14 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(c: u32, i: u32, ) -> Weight { + fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `416` // Estimated: `6348` - // Minimum execution time: 205_430_000 picoseconds. - Weight::from_parts(190_302_613, 6348) - // Standard Error: 10 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(c.into())) - // Standard Error: 10 - .saturating_add(Weight::from_parts(4_465, 0).saturating_mul(i.into())) + // Minimum execution time: 197_911_000 picoseconds. + Weight::from_parts(185_839_401, 6348) + // Standard Error: 9 + .saturating_add(Weight::from_parts(4_419, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -228,10 +227,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1309` // Estimated: `4760` - // Minimum execution time: 168_842_000 picoseconds. - Weight::from_parts(154_652_310, 4760) + // Minimum execution time: 162_062_000 picoseconds. + Weight::from_parts(146_040_237, 4760) // Standard Error: 15 - .saturating_add(Weight::from_parts(4_407, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_410, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -251,8 +250,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1465` // Estimated: `7405` - // Minimum execution time: 144_703_000 picoseconds. - Weight::from_parts(151_937_000, 7405) + // Minimum execution time: 143_737_000 picoseconds. + Weight::from_parts(151_572_000, 7405) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -267,8 +266,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 52_912_000 picoseconds. - Weight::from_parts(54_905_094, 3574) + // Minimum execution time: 52_301_000 picoseconds. + Weight::from_parts(54_773_649, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -282,8 +281,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 46_323_000 picoseconds. - Weight::from_parts(47_075_000, 3750) + // Minimum execution time: 45_699_000 picoseconds. + Weight::from_parts(46_961_000, 3750) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -295,8 +294,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 27_120_000 picoseconds. - Weight::from_parts(28_635_000, 6469) + // Minimum execution time: 26_501_000 picoseconds. + Weight::from_parts(27_913_000, 6469) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -308,8 +307,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 42_489_000 picoseconds. - Weight::from_parts(43_230_000, 3574) + // Minimum execution time: 41_673_000 picoseconds. + Weight::from_parts(42_360_000, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -321,8 +320,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 34_042_000 picoseconds. - Weight::from_parts(34_758_000, 3521) + // Minimum execution time: 32_530_000 picoseconds. + Weight::from_parts(33_997_000, 3521) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -334,8 +333,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 14_322_000 picoseconds. - Weight::from_parts(14_761_000, 3610) + // Minimum execution time: 13_327_000 picoseconds. + Weight::from_parts(13_976_000, 3610) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -343,24 +342,24 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 14_300_000 picoseconds. - Weight::from_parts(14_435_272, 0) - // Standard Error: 357 - .saturating_add(Weight::from_parts(151_410, 0).saturating_mul(r.into())) + // Minimum execution time: 7_317_000 picoseconds. + Weight::from_parts(7_742_783, 0) + // Standard Error: 274 + .saturating_add(Weight::from_parts(166_272, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 315_000 picoseconds. - Weight::from_parts(355_000, 0) + // Minimum execution time: 300_000 picoseconds. + Weight::from_parts(349_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 252_000 picoseconds. - Weight::from_parts(300_000, 0) + // Minimum execution time: 248_000 picoseconds. + Weight::from_parts(293_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -368,8 +367,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_073_000 picoseconds. - Weight::from_parts(10_791_000, 3771) + // Minimum execution time: 10_018_000 picoseconds. + Weight::from_parts(10_399_000, 3771) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -378,16 +377,16 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_216_000 picoseconds. - Weight::from_parts(11_917_000, 3868) + // Minimum execution time: 11_209_000 picoseconds. + Weight::from_parts(11_640_000, 3868) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 269_000 picoseconds. - Weight::from_parts(308_000, 0) + // Minimum execution time: 280_000 picoseconds. + Weight::from_parts(309_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -397,51 +396,51 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 15_159_000 picoseconds. - Weight::from_parts(15_933_000, 3938) + // Minimum execution time: 14_718_000 picoseconds. + Weight::from_parts(15_292_000, 3938) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 367_000 picoseconds. + // Minimum execution time: 336_000 picoseconds. Weight::from_parts(391_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 294_000 picoseconds. - Weight::from_parts(331_000, 0) + // Minimum execution time: 275_000 picoseconds. + Weight::from_parts(296_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 275_000 picoseconds. - Weight::from_parts(318_000, 0) + // Minimum execution time: 262_000 picoseconds. + Weight::from_parts(304_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(697_000, 0) + // Minimum execution time: 628_000 picoseconds. + Weight::from_parts(714_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 288_000 picoseconds. - Weight::from_parts(306_000, 0) + // Minimum execution time: 246_000 picoseconds. + Weight::from_parts(265_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 5_577_000 picoseconds. - Weight::from_parts(5_918_000, 0) + // Minimum execution time: 5_605_000 picoseconds. + Weight::from_parts(5_769_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -451,8 +450,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 9_264_000 picoseconds. - Weight::from_parts(9_589_000, 3729) + // Minimum execution time: 8_990_000 picoseconds. + Weight::from_parts(9_223_000, 3729) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -462,10 +461,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 6_082_000 picoseconds. - Weight::from_parts(6_789_222, 3703) + // Minimum execution time: 6_001_000 picoseconds. + Weight::from_parts(6_630_017, 3703) // Standard Error: 4 - .saturating_add(Weight::from_parts(670, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(622, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -476,39 +475,46 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_950_000 picoseconds. - Weight::from_parts(2_244_232, 0) + // Minimum execution time: 2_026_000 picoseconds. + Weight::from_parts(2_271_985, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(574, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(537, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 254_000 picoseconds. - Weight::from_parts(304_000, 0) + // Minimum execution time: 287_000 picoseconds. + Weight::from_parts(323_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 251_000 picoseconds. - Weight::from_parts(292_000, 0) + // Minimum execution time: 230_000 picoseconds. + Weight::from_parts(275_000, 0) + } + fn seal_return_data_size() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 242_000 picoseconds. + Weight::from_parts(268_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 262_000 picoseconds. - Weight::from_parts(288_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(271_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 269_000 picoseconds. - Weight::from_parts(302_000, 0) + // Minimum execution time: 266_000 picoseconds. + Weight::from_parts(304_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -516,60 +522,60 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_690_000 picoseconds. - Weight::from_parts(3_791_000, 3495) + // Minimum execution time: 3_559_000 picoseconds. + Weight::from_parts(3_697_000, 3495) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 261_000 picoseconds. - Weight::from_parts(307_000, 0) + // Minimum execution time: 242_000 picoseconds. + Weight::from_parts(294_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_417_000 picoseconds. - Weight::from_parts(1_547_000, 0) + // Minimum execution time: 1_222_000 picoseconds. + Weight::from_parts(1_387_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 364_000 picoseconds. - Weight::from_parts(566_499, 0) + // Minimum execution time: 392_000 picoseconds. + Weight::from_parts(397_500, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(237, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(206, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 279_000 picoseconds. - Weight::from_parts(305_000, 0) + // Minimum execution time: 267_000 picoseconds. + Weight::from_parts(322_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 265_000 picoseconds. - Weight::from_parts(359_300, 0) + // Minimum execution time: 234_000 picoseconds. + Weight::from_parts(291_182, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 278_000 picoseconds. - Weight::from_parts(474_421, 0) + // Minimum execution time: 253_000 picoseconds. + Weight::from_parts(271_000, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(237, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(212, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -586,10 +592,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `324 + n * (88 ±0)` // Estimated: `3790 + n * (2563 ±0)` - // Minimum execution time: 23_182_000 picoseconds. - Weight::from_parts(23_833_588, 3790) - // Standard Error: 12_448 - .saturating_add(Weight::from_parts(4_277_757, 0).saturating_mul(n.into())) + // Minimum execution time: 22_082_000 picoseconds. + Weight::from_parts(22_815_417, 3790) + // Standard Error: 9_515 + .saturating_add(Weight::from_parts(4_283_767, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -602,22 +608,22 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_433_000 picoseconds. - Weight::from_parts(4_321_363, 0) - // Standard Error: 2_536 - .saturating_add(Weight::from_parts(207_597, 0).saturating_mul(t.into())) - // Standard Error: 22 - .saturating_add(Weight::from_parts(957, 0).saturating_mul(n.into())) + // Minimum execution time: 4_242_000 picoseconds. + Weight::from_parts(4_360_337, 0) + // Standard Error: 3_223 + .saturating_add(Weight::from_parts(201_105, 0).saturating_mul(t.into())) + // Standard Error: 28 + .saturating_add(Weight::from_parts(723, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 353_000 picoseconds. - Weight::from_parts(78_798, 0) + // Minimum execution time: 340_000 picoseconds. + Weight::from_parts(773_824, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(762, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(722, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -625,8 +631,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 7_701_000 picoseconds. - Weight::from_parts(8_043_000, 744) + // Minimum execution time: 7_741_000 picoseconds. + Weight::from_parts(8_048_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -635,8 +641,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 42_961_000 picoseconds. - Weight::from_parts(44_719_000, 10754) + // Minimum execution time: 42_314_000 picoseconds. + Weight::from_parts(43_255_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -645,8 +651,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 8_575_000 picoseconds. - Weight::from_parts(9_239_000, 744) + // Minimum execution time: 8_741_000 picoseconds. + Weight::from_parts(9_123_000, 744) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -656,8 +662,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 43_585_000 picoseconds. - Weight::from_parts(45_719_000, 10754) + // Minimum execution time: 44_703_000 picoseconds. + Weight::from_parts(46_403_000, 10754) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -669,12 +675,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_147_000 picoseconds. - Weight::from_parts(9_851_872, 247) - // Standard Error: 40 - .saturating_add(Weight::from_parts(222, 0).saturating_mul(n.into())) - // Standard Error: 40 - .saturating_add(Weight::from_parts(411, 0).saturating_mul(o.into())) + // Minimum execution time: 9_285_000 picoseconds. + Weight::from_parts(10_046_720, 247) + // Standard Error: 37 + .saturating_add(Weight::from_parts(365, 0).saturating_mul(n.into())) + // Standard Error: 37 + .saturating_add(Weight::from_parts(273, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -686,10 +692,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_859_000 picoseconds. - Weight::from_parts(9_633_190, 247) - // Standard Error: 55 - .saturating_add(Weight::from_parts(995, 0).saturating_mul(n.into())) + // Minimum execution time: 8_879_000 picoseconds. + Weight::from_parts(9_736_050, 247) + // Standard Error: 48 + .saturating_add(Weight::from_parts(514, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -701,10 +707,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_270_000 picoseconds. - Weight::from_parts(9_208_849, 247) - // Standard Error: 67 - .saturating_add(Weight::from_parts(1_686, 0).saturating_mul(n.into())) + // Minimum execution time: 8_475_000 picoseconds. + Weight::from_parts(9_410_206, 247) + // Standard Error: 58 + .saturating_add(Weight::from_parts(1_409, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -715,10 +721,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_002_000 picoseconds. - Weight::from_parts(8_695_892, 247) - // Standard Error: 48 - .saturating_add(Weight::from_parts(721, 0).saturating_mul(n.into())) + // Minimum execution time: 8_017_000 picoseconds. + Weight::from_parts(8_879_089, 247) + // Standard Error: 51 + .saturating_add(Weight::from_parts(512, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -729,10 +735,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_204_000 picoseconds. - Weight::from_parts(10_176_756, 247) - // Standard Error: 57 - .saturating_add(Weight::from_parts(1_550, 0).saturating_mul(n.into())) + // Minimum execution time: 9_196_000 picoseconds. + Weight::from_parts(10_285_787, 247) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_553, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -741,36 +747,36 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_518_000 picoseconds. - Weight::from_parts(1_578_000, 0) + // Minimum execution time: 1_456_000 picoseconds. + Weight::from_parts(1_593_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_846_000 picoseconds. - Weight::from_parts(1_996_000, 0) + // Minimum execution time: 1_897_000 picoseconds. + Weight::from_parts(2_059_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_442_000 picoseconds. - Weight::from_parts(1_562_000, 0) + // Minimum execution time: 1_487_000 picoseconds. + Weight::from_parts(1_588_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_602_000 picoseconds. - Weight::from_parts(1_730_000, 0) + // Minimum execution time: 1_622_000 picoseconds. + Weight::from_parts(1_732_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_096_000 picoseconds. - Weight::from_parts(1_176_000, 0) + // Minimum execution time: 1_188_000 picoseconds. + Weight::from_parts(1_239_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -778,52 +784,50 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_328_000 picoseconds. - Weight::from_parts(2_470_198, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(256, 0).saturating_mul(n.into())) - // Standard Error: 14 - .saturating_add(Weight::from_parts(441, 0).saturating_mul(o.into())) + // Minimum execution time: 2_269_000 picoseconds. + Weight::from_parts(2_528_717, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(163, 0).saturating_mul(n.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(332, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_037_000 picoseconds. - Weight::from_parts(2_439_061, 0) - // Standard Error: 17 - .saturating_add(Weight::from_parts(303, 0).saturating_mul(n.into())) + // Minimum execution time: 2_051_000 picoseconds. + Weight::from_parts(2_507_009, 0) + // Standard Error: 20 + .saturating_add(Weight::from_parts(309, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_900_000 picoseconds. - Weight::from_parts(2_095_135, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(310, 0).saturating_mul(n.into())) + // Minimum execution time: 1_829_000 picoseconds. + Weight::from_parts(2_052_749, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(350, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_772_000 picoseconds. - Weight::from_parts(1_964_542, 0) - // Standard Error: 16 - .saturating_add(Weight::from_parts(298, 0).saturating_mul(n.into())) + // Minimum execution time: 1_717_000 picoseconds. + Weight::from_parts(1_930_820, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(161, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. - fn seal_take_transient_storage(n: u32, ) -> Weight { + fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_555_000 picoseconds. - Weight::from_parts(2_864_143, 0) - // Standard Error: 22 - .saturating_add(Weight::from_parts(45, 0).saturating_mul(n.into())) + // Minimum execution time: 2_502_000 picoseconds. + Weight::from_parts(2_829_951, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -837,14 +841,16 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `t` is `[0, 1]`. /// The range of component `i` is `[0, 262144]`. - fn seal_call(t: u32, _i: u32, ) -> Weight { + fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `1292 + t * (280 ±0)` // Estimated: `4757 + t * (2518 ±0)` - // Minimum execution time: 40_760_000 picoseconds. - Weight::from_parts(45_131_001, 4757) - // Standard Error: 302_594 - .saturating_add(Weight::from_parts(2_769_232, 0).saturating_mul(t.into())) + // Minimum execution time: 40_791_000 picoseconds. + Weight::from_parts(42_421_336, 4757) + // Standard Error: 53_086 + .saturating_add(Weight::from_parts(2_057_850, 0).saturating_mul(t.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) @@ -860,8 +866,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 36_975_000 picoseconds. - Weight::from_parts(38_368_000, 4702) + // Minimum execution time: 35_825_000 picoseconds. + Weight::from_parts(37_377_000, 4702) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -877,10 +883,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1310` // Estimated: `4769` - // Minimum execution time: 122_553_000 picoseconds. - Weight::from_parts(117_325_822, 4769) + // Minimum execution time: 121_920_000 picoseconds. + Weight::from_parts(115_842_357, 4769) // Standard Error: 10 - .saturating_add(Weight::from_parts(4_147, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_062, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -890,63 +896,63 @@ impl WeightInfo for SubstrateWeight { // Measured: `0` // Estimated: `0` // Minimum execution time: 657_000 picoseconds. - Weight::from_parts(3_531_259, 0) + Weight::from_parts(2_219_539, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_428, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_413, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_072_000 picoseconds. - Weight::from_parts(5_487_006, 0) + // Minimum execution time: 1_091_000 picoseconds. + Weight::from_parts(4_036_613, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_634, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_600, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 638_000 picoseconds. - Weight::from_parts(3_097_177, 0) + // Minimum execution time: 635_000 picoseconds. + Weight::from_parts(4_636_213, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_551, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_514, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 682_000 picoseconds. - Weight::from_parts(2_963_774, 0) + // Minimum execution time: 648_000 picoseconds. + Weight::from_parts(3_658_083, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_561, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_516, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_791_000 picoseconds. - Weight::from_parts(27_471_391, 0) + // Minimum execution time: 42_722_000 picoseconds. + Weight::from_parts(28_496_037, 0) // Standard Error: 13 - .saturating_add(Weight::from_parts(5_246, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(5_235, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_565_000 picoseconds. - Weight::from_parts(48_251_000, 0) + // Minimum execution time: 46_924_000 picoseconds. + Weight::from_parts(48_639_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_562_000 picoseconds. - Weight::from_parts(12_664_000, 0) + // Minimum execution time: 12_882_000 picoseconds. + Weight::from_parts(13_108_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -954,8 +960,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 18_527_000 picoseconds. - Weight::from_parts(19_134_000, 3765) + // Minimum execution time: 17_907_000 picoseconds. + Weight::from_parts(18_634_000, 3765) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -965,8 +971,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 13_843_000 picoseconds. - Weight::from_parts(14_750_000, 3803) + // Minimum execution time: 14_091_000 picoseconds. + Weight::from_parts(14_393_000, 3803) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -976,8 +982,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 13_013_000 picoseconds. - Weight::from_parts(13_612_000, 3561) + // Minimum execution time: 12_824_000 picoseconds. + Weight::from_parts(13_304_000, 3561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -986,10 +992,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 15_182_000 picoseconds. - Weight::from_parts(16_987_060, 0) - // Standard Error: 105 - .saturating_add(Weight::from_parts(72_086, 0).saturating_mul(r.into())) + // Minimum execution time: 9_185_000 picoseconds. + Weight::from_parts(10_532_230, 0) + // Standard Error: 189 + .saturating_add(Weight::from_parts(71_786, 0).saturating_mul(r.into())) } } @@ -1001,8 +1007,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_729_000 picoseconds. - Weight::from_parts(2_919_000, 1594) + // Minimum execution time: 2_752_000 picoseconds. + Weight::from_parts(2_990_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1012,10 +1018,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 16_062_000 picoseconds. - Weight::from_parts(2_790_037, 415) - // Standard Error: 1_371 - .saturating_add(Weight::from_parts(1_187_192, 0).saturating_mul(k.into())) + // Minimum execution time: 16_130_000 picoseconds. + Weight::from_parts(3_413_527, 415) + // Standard Error: 1_190 + .saturating_add(Weight::from_parts(1_184_912, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1039,8 +1045,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1465` // Estimated: `7405` - // Minimum execution time: 94_592_000 picoseconds. - Weight::from_parts(100_095_688, 7405) + // Minimum execution time: 91_977_000 picoseconds. + Weight::from_parts(96_482_355, 7405) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1060,16 +1066,14 @@ impl WeightInfo for () { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(c: u32, i: u32, ) -> Weight { + fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `416` // Estimated: `6348` - // Minimum execution time: 205_430_000 picoseconds. - Weight::from_parts(190_302_613, 6348) - // Standard Error: 10 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(c.into())) - // Standard Error: 10 - .saturating_add(Weight::from_parts(4_465, 0).saturating_mul(i.into())) + // Minimum execution time: 197_911_000 picoseconds. + Weight::from_parts(185_839_401, 6348) + // Standard Error: 9 + .saturating_add(Weight::from_parts(4_419, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1092,10 +1096,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1309` // Estimated: `4760` - // Minimum execution time: 168_842_000 picoseconds. - Weight::from_parts(154_652_310, 4760) + // Minimum execution time: 162_062_000 picoseconds. + Weight::from_parts(146_040_237, 4760) // Standard Error: 15 - .saturating_add(Weight::from_parts(4_407, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_410, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1115,8 +1119,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1465` // Estimated: `7405` - // Minimum execution time: 144_703_000 picoseconds. - Weight::from_parts(151_937_000, 7405) + // Minimum execution time: 143_737_000 picoseconds. + Weight::from_parts(151_572_000, 7405) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1131,8 +1135,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 52_912_000 picoseconds. - Weight::from_parts(54_905_094, 3574) + // Minimum execution time: 52_301_000 picoseconds. + Weight::from_parts(54_773_649, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1146,8 +1150,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 46_323_000 picoseconds. - Weight::from_parts(47_075_000, 3750) + // Minimum execution time: 45_699_000 picoseconds. + Weight::from_parts(46_961_000, 3750) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1159,8 +1163,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 27_120_000 picoseconds. - Weight::from_parts(28_635_000, 6469) + // Minimum execution time: 26_501_000 picoseconds. + Weight::from_parts(27_913_000, 6469) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1172,8 +1176,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 42_489_000 picoseconds. - Weight::from_parts(43_230_000, 3574) + // Minimum execution time: 41_673_000 picoseconds. + Weight::from_parts(42_360_000, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1185,8 +1189,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 34_042_000 picoseconds. - Weight::from_parts(34_758_000, 3521) + // Minimum execution time: 32_530_000 picoseconds. + Weight::from_parts(33_997_000, 3521) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1198,8 +1202,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 14_322_000 picoseconds. - Weight::from_parts(14_761_000, 3610) + // Minimum execution time: 13_327_000 picoseconds. + Weight::from_parts(13_976_000, 3610) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1207,24 +1211,24 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 14_300_000 picoseconds. - Weight::from_parts(14_435_272, 0) - // Standard Error: 357 - .saturating_add(Weight::from_parts(151_410, 0).saturating_mul(r.into())) + // Minimum execution time: 7_317_000 picoseconds. + Weight::from_parts(7_742_783, 0) + // Standard Error: 274 + .saturating_add(Weight::from_parts(166_272, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 315_000 picoseconds. - Weight::from_parts(355_000, 0) + // Minimum execution time: 300_000 picoseconds. + Weight::from_parts(349_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 252_000 picoseconds. - Weight::from_parts(300_000, 0) + // Minimum execution time: 248_000 picoseconds. + Weight::from_parts(293_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1232,8 +1236,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_073_000 picoseconds. - Weight::from_parts(10_791_000, 3771) + // Minimum execution time: 10_018_000 picoseconds. + Weight::from_parts(10_399_000, 3771) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -1242,16 +1246,16 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_216_000 picoseconds. - Weight::from_parts(11_917_000, 3868) + // Minimum execution time: 11_209_000 picoseconds. + Weight::from_parts(11_640_000, 3868) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 269_000 picoseconds. - Weight::from_parts(308_000, 0) + // Minimum execution time: 280_000 picoseconds. + Weight::from_parts(309_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1261,51 +1265,51 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 15_159_000 picoseconds. - Weight::from_parts(15_933_000, 3938) + // Minimum execution time: 14_718_000 picoseconds. + Weight::from_parts(15_292_000, 3938) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 367_000 picoseconds. + // Minimum execution time: 336_000 picoseconds. Weight::from_parts(391_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 294_000 picoseconds. - Weight::from_parts(331_000, 0) + // Minimum execution time: 275_000 picoseconds. + Weight::from_parts(296_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 275_000 picoseconds. - Weight::from_parts(318_000, 0) + // Minimum execution time: 262_000 picoseconds. + Weight::from_parts(304_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 660_000 picoseconds. - Weight::from_parts(697_000, 0) + // Minimum execution time: 628_000 picoseconds. + Weight::from_parts(714_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 288_000 picoseconds. - Weight::from_parts(306_000, 0) + // Minimum execution time: 246_000 picoseconds. + Weight::from_parts(265_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 5_577_000 picoseconds. - Weight::from_parts(5_918_000, 0) + // Minimum execution time: 5_605_000 picoseconds. + Weight::from_parts(5_769_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1315,8 +1319,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 9_264_000 picoseconds. - Weight::from_parts(9_589_000, 3729) + // Minimum execution time: 8_990_000 picoseconds. + Weight::from_parts(9_223_000, 3729) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -1326,10 +1330,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 6_082_000 picoseconds. - Weight::from_parts(6_789_222, 3703) + // Minimum execution time: 6_001_000 picoseconds. + Weight::from_parts(6_630_017, 3703) // Standard Error: 4 - .saturating_add(Weight::from_parts(670, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(622, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1340,39 +1344,46 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_950_000 picoseconds. - Weight::from_parts(2_244_232, 0) + // Minimum execution time: 2_026_000 picoseconds. + Weight::from_parts(2_271_985, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(574, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(537, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 254_000 picoseconds. - Weight::from_parts(304_000, 0) + // Minimum execution time: 287_000 picoseconds. + Weight::from_parts(323_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 251_000 picoseconds. - Weight::from_parts(292_000, 0) + // Minimum execution time: 230_000 picoseconds. + Weight::from_parts(275_000, 0) + } + fn seal_return_data_size() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 242_000 picoseconds. + Weight::from_parts(268_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 262_000 picoseconds. - Weight::from_parts(288_000, 0) + // Minimum execution time: 244_000 picoseconds. + Weight::from_parts(271_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 269_000 picoseconds. - Weight::from_parts(302_000, 0) + // Minimum execution time: 266_000 picoseconds. + Weight::from_parts(304_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -1380,60 +1391,60 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_690_000 picoseconds. - Weight::from_parts(3_791_000, 3495) + // Minimum execution time: 3_559_000 picoseconds. + Weight::from_parts(3_697_000, 3495) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 261_000 picoseconds. - Weight::from_parts(307_000, 0) + // Minimum execution time: 242_000 picoseconds. + Weight::from_parts(294_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_417_000 picoseconds. - Weight::from_parts(1_547_000, 0) + // Minimum execution time: 1_222_000 picoseconds. + Weight::from_parts(1_387_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 364_000 picoseconds. - Weight::from_parts(566_499, 0) + // Minimum execution time: 392_000 picoseconds. + Weight::from_parts(397_500, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(237, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(206, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 279_000 picoseconds. - Weight::from_parts(305_000, 0) + // Minimum execution time: 267_000 picoseconds. + Weight::from_parts(322_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 265_000 picoseconds. - Weight::from_parts(359_300, 0) + // Minimum execution time: 234_000 picoseconds. + Weight::from_parts(291_182, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(148, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 278_000 picoseconds. - Weight::from_parts(474_421, 0) + // Minimum execution time: 253_000 picoseconds. + Weight::from_parts(271_000, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(237, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(212, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1450,10 +1461,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `324 + n * (88 ±0)` // Estimated: `3790 + n * (2563 ±0)` - // Minimum execution time: 23_182_000 picoseconds. - Weight::from_parts(23_833_588, 3790) - // Standard Error: 12_448 - .saturating_add(Weight::from_parts(4_277_757, 0).saturating_mul(n.into())) + // Minimum execution time: 22_082_000 picoseconds. + Weight::from_parts(22_815_417, 3790) + // Standard Error: 9_515 + .saturating_add(Weight::from_parts(4_283_767, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -1466,22 +1477,22 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_433_000 picoseconds. - Weight::from_parts(4_321_363, 0) - // Standard Error: 2_536 - .saturating_add(Weight::from_parts(207_597, 0).saturating_mul(t.into())) - // Standard Error: 22 - .saturating_add(Weight::from_parts(957, 0).saturating_mul(n.into())) + // Minimum execution time: 4_242_000 picoseconds. + Weight::from_parts(4_360_337, 0) + // Standard Error: 3_223 + .saturating_add(Weight::from_parts(201_105, 0).saturating_mul(t.into())) + // Standard Error: 28 + .saturating_add(Weight::from_parts(723, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 353_000 picoseconds. - Weight::from_parts(78_798, 0) + // Minimum execution time: 340_000 picoseconds. + Weight::from_parts(773_824, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(762, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(722, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -1489,8 +1500,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 7_701_000 picoseconds. - Weight::from_parts(8_043_000, 744) + // Minimum execution time: 7_741_000 picoseconds. + Weight::from_parts(8_048_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1499,8 +1510,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 42_961_000 picoseconds. - Weight::from_parts(44_719_000, 10754) + // Minimum execution time: 42_314_000 picoseconds. + Weight::from_parts(43_255_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1509,8 +1520,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `744` // Estimated: `744` - // Minimum execution time: 8_575_000 picoseconds. - Weight::from_parts(9_239_000, 744) + // Minimum execution time: 8_741_000 picoseconds. + Weight::from_parts(9_123_000, 744) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1520,8 +1531,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10754` // Estimated: `10754` - // Minimum execution time: 43_585_000 picoseconds. - Weight::from_parts(45_719_000, 10754) + // Minimum execution time: 44_703_000 picoseconds. + Weight::from_parts(46_403_000, 10754) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1533,12 +1544,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_147_000 picoseconds. - Weight::from_parts(9_851_872, 247) - // Standard Error: 40 - .saturating_add(Weight::from_parts(222, 0).saturating_mul(n.into())) - // Standard Error: 40 - .saturating_add(Weight::from_parts(411, 0).saturating_mul(o.into())) + // Minimum execution time: 9_285_000 picoseconds. + Weight::from_parts(10_046_720, 247) + // Standard Error: 37 + .saturating_add(Weight::from_parts(365, 0).saturating_mul(n.into())) + // Standard Error: 37 + .saturating_add(Weight::from_parts(273, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -1550,10 +1561,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_859_000 picoseconds. - Weight::from_parts(9_633_190, 247) - // Standard Error: 55 - .saturating_add(Weight::from_parts(995, 0).saturating_mul(n.into())) + // Minimum execution time: 8_879_000 picoseconds. + Weight::from_parts(9_736_050, 247) + // Standard Error: 48 + .saturating_add(Weight::from_parts(514, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1565,10 +1576,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_270_000 picoseconds. - Weight::from_parts(9_208_849, 247) - // Standard Error: 67 - .saturating_add(Weight::from_parts(1_686, 0).saturating_mul(n.into())) + // Minimum execution time: 8_475_000 picoseconds. + Weight::from_parts(9_410_206, 247) + // Standard Error: 58 + .saturating_add(Weight::from_parts(1_409, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1579,10 +1590,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_002_000 picoseconds. - Weight::from_parts(8_695_892, 247) - // Standard Error: 48 - .saturating_add(Weight::from_parts(721, 0).saturating_mul(n.into())) + // Minimum execution time: 8_017_000 picoseconds. + Weight::from_parts(8_879_089, 247) + // Standard Error: 51 + .saturating_add(Weight::from_parts(512, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1593,10 +1604,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_204_000 picoseconds. - Weight::from_parts(10_176_756, 247) - // Standard Error: 57 - .saturating_add(Weight::from_parts(1_550, 0).saturating_mul(n.into())) + // Minimum execution time: 9_196_000 picoseconds. + Weight::from_parts(10_285_787, 247) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_553, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1605,36 +1616,36 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_518_000 picoseconds. - Weight::from_parts(1_578_000, 0) + // Minimum execution time: 1_456_000 picoseconds. + Weight::from_parts(1_593_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_846_000 picoseconds. - Weight::from_parts(1_996_000, 0) + // Minimum execution time: 1_897_000 picoseconds. + Weight::from_parts(2_059_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_442_000 picoseconds. - Weight::from_parts(1_562_000, 0) + // Minimum execution time: 1_487_000 picoseconds. + Weight::from_parts(1_588_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_602_000 picoseconds. - Weight::from_parts(1_730_000, 0) + // Minimum execution time: 1_622_000 picoseconds. + Weight::from_parts(1_732_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_096_000 picoseconds. - Weight::from_parts(1_176_000, 0) + // Minimum execution time: 1_188_000 picoseconds. + Weight::from_parts(1_239_000, 0) } /// The range of component `n` is `[0, 512]`. /// The range of component `o` is `[0, 512]`. @@ -1642,52 +1653,50 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_328_000 picoseconds. - Weight::from_parts(2_470_198, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(256, 0).saturating_mul(n.into())) - // Standard Error: 14 - .saturating_add(Weight::from_parts(441, 0).saturating_mul(o.into())) + // Minimum execution time: 2_269_000 picoseconds. + Weight::from_parts(2_528_717, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(163, 0).saturating_mul(n.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(332, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 512]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_037_000 picoseconds. - Weight::from_parts(2_439_061, 0) - // Standard Error: 17 - .saturating_add(Weight::from_parts(303, 0).saturating_mul(n.into())) + // Minimum execution time: 2_051_000 picoseconds. + Weight::from_parts(2_507_009, 0) + // Standard Error: 20 + .saturating_add(Weight::from_parts(309, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_900_000 picoseconds. - Weight::from_parts(2_095_135, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(310, 0).saturating_mul(n.into())) + // Minimum execution time: 1_829_000 picoseconds. + Weight::from_parts(2_052_749, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(350, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_772_000 picoseconds. - Weight::from_parts(1_964_542, 0) - // Standard Error: 16 - .saturating_add(Weight::from_parts(298, 0).saturating_mul(n.into())) + // Minimum execution time: 1_717_000 picoseconds. + Weight::from_parts(1_930_820, 0) + // Standard Error: 12 + .saturating_add(Weight::from_parts(161, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 512]`. - fn seal_take_transient_storage(n: u32, ) -> Weight { + fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_555_000 picoseconds. - Weight::from_parts(2_864_143, 0) - // Standard Error: 22 - .saturating_add(Weight::from_parts(45, 0).saturating_mul(n.into())) + // Minimum execution time: 2_502_000 picoseconds. + Weight::from_parts(2_829_951, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1701,14 +1710,16 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `t` is `[0, 1]`. /// The range of component `i` is `[0, 262144]`. - fn seal_call(t: u32, _i: u32, ) -> Weight { + fn seal_call(t: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `1292 + t * (280 ±0)` // Estimated: `4757 + t * (2518 ±0)` - // Minimum execution time: 40_760_000 picoseconds. - Weight::from_parts(45_131_001, 4757) - // Standard Error: 302_594 - .saturating_add(Weight::from_parts(2_769_232, 0).saturating_mul(t.into())) + // Minimum execution time: 40_791_000 picoseconds. + Weight::from_parts(42_421_336, 4757) + // Standard Error: 53_086 + .saturating_add(Weight::from_parts(2_057_850, 0).saturating_mul(t.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) @@ -1724,8 +1735,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 36_975_000 picoseconds. - Weight::from_parts(38_368_000, 4702) + // Minimum execution time: 35_825_000 picoseconds. + Weight::from_parts(37_377_000, 4702) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -1741,10 +1752,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1310` // Estimated: `4769` - // Minimum execution time: 122_553_000 picoseconds. - Weight::from_parts(117_325_822, 4769) + // Minimum execution time: 121_920_000 picoseconds. + Weight::from_parts(115_842_357, 4769) // Standard Error: 10 - .saturating_add(Weight::from_parts(4_147, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_062, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1754,63 +1765,63 @@ impl WeightInfo for () { // Measured: `0` // Estimated: `0` // Minimum execution time: 657_000 picoseconds. - Weight::from_parts(3_531_259, 0) + Weight::from_parts(2_219_539, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_428, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_413, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_072_000 picoseconds. - Weight::from_parts(5_487_006, 0) + // Minimum execution time: 1_091_000 picoseconds. + Weight::from_parts(4_036_613, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_634, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_600, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 638_000 picoseconds. - Weight::from_parts(3_097_177, 0) + // Minimum execution time: 635_000 picoseconds. + Weight::from_parts(4_636_213, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_551, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_514, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 682_000 picoseconds. - Weight::from_parts(2_963_774, 0) + // Minimum execution time: 648_000 picoseconds. + Weight::from_parts(3_658_083, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_561, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_516, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_791_000 picoseconds. - Weight::from_parts(27_471_391, 0) + // Minimum execution time: 42_722_000 picoseconds. + Weight::from_parts(28_496_037, 0) // Standard Error: 13 - .saturating_add(Weight::from_parts(5_246, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(5_235, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_565_000 picoseconds. - Weight::from_parts(48_251_000, 0) + // Minimum execution time: 46_924_000 picoseconds. + Weight::from_parts(48_639_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_562_000 picoseconds. - Weight::from_parts(12_664_000, 0) + // Minimum execution time: 12_882_000 picoseconds. + Weight::from_parts(13_108_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1818,8 +1829,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 18_527_000 picoseconds. - Weight::from_parts(19_134_000, 3765) + // Minimum execution time: 17_907_000 picoseconds. + Weight::from_parts(18_634_000, 3765) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1829,8 +1840,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 13_843_000 picoseconds. - Weight::from_parts(14_750_000, 3803) + // Minimum execution time: 14_091_000 picoseconds. + Weight::from_parts(14_393_000, 3803) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1840,8 +1851,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 13_013_000 picoseconds. - Weight::from_parts(13_612_000, 3561) + // Minimum execution time: 12_824_000 picoseconds. + Weight::from_parts(13_304_000, 3561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1850,9 +1861,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 15_182_000 picoseconds. - Weight::from_parts(16_987_060, 0) - // Standard Error: 105 - .saturating_add(Weight::from_parts(72_086, 0).saturating_mul(r.into())) + // Minimum execution time: 9_185_000 picoseconds. + Weight::from_parts(10_532_230, 0) + // Standard Error: 189 + .saturating_add(Weight::from_parts(71_786, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 2214563faf02..ae4479cd1549 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -87,12 +87,8 @@ pub trait HostFn: private::Sealed { /// Returns the [EIP-155](https://eips.ethereum.org/EIPS/eip-155) chain ID. fn chain_id(output: &mut [u8; 32]); - /// Stores the call data size as little endian U256 value into the supplied buffer. - /// - /// # Parameters - /// - /// - `output`: A reference to the output data buffer to write the call data size. - fn call_data_size(output: &mut [u8; 32]); + /// Returns the call data size. + fn call_data_size() -> u64; /// Call (possibly transferring some amount of funds) into the specified account. /// @@ -170,17 +166,16 @@ pub trait HostFn: private::Sealed { /// otherwise `zero`. fn code_hash(addr: &[u8; 20], output: &mut [u8; 32]); - /// Retrieve the code size for a specified contract address. + /// Returns the code size for a specified contract address. /// /// # Parameters /// /// - `addr`: The address of the contract. - /// - `output`: A reference to the output data buffer to write the code size. /// /// # Note /// /// If `addr` is not a contract the `output` will be zero. - fn code_size(addr: &[u8; 20], output: &mut [u8; 32]); + fn code_size(addr: &[u8; 20]) -> u64; /// Execute code in the context (storage, caller, value) of the current contract. /// @@ -384,12 +379,8 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the price. fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]); - /// Stores the size of the returned data of the last contract call or instantiation. - /// - /// # Parameters - /// - /// - `output`: A reference to the output buffer to write the size. - fn return_data_size(output: &mut [u8; 32]); + /// Returns the size of the returned data of the last contract call or instantiation. + fn return_data_size() -> u64; /// Stores the returned data of the last contract call or contract instantiation. /// diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index a73a13ed1af5..d45e0d65646c 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -69,7 +69,7 @@ mod sys { pub fn origin(out_ptr: *mut u8); pub fn is_contract(account_ptr: *const u8) -> ReturnCode; pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8); - pub fn code_size(address_ptr: *const u8, out_ptr: *mut u8); + pub fn code_size(address_ptr: *const u8) -> u64; pub fn own_code_hash(out_ptr: *mut u8); pub fn caller_is_origin() -> ReturnCode; pub fn caller_is_root() -> ReturnCode; @@ -91,7 +91,7 @@ mod sys { data_ptr: *const u8, data_len: u32, ); - pub fn call_data_size(out_ptr: *mut u8); + pub fn call_data_size() -> u64; pub fn block_number(out_ptr: *mut u8); pub fn block_hash(block_number_ptr: *const u8, out_ptr: *mut u8); pub fn hash_sha2_256(input_ptr: *const u8, input_len: u32, out_ptr: *mut u8); @@ -131,7 +131,7 @@ mod sys { msg_len: u32, out_ptr: *mut u8, ) -> ReturnCode; - pub fn return_data_size(out_ptr: *mut u8); + pub fn return_data_size() -> u64; pub fn return_data_copy(out_ptr: *mut u8, out_len_ptr: *mut u32, offset: u32); } } @@ -386,13 +386,17 @@ impl HostFn for HostFnImpl { unsafe { sys::call_data_load(out_ptr.as_mut_ptr(), offset) }; } + fn call_data_size() -> u64 { + unsafe { sys::call_data_size() } + } + fn return_value(flags: ReturnFlags, return_value: &[u8]) -> ! { unsafe { sys::seal_return(flags.bits(), return_value.as_ptr(), return_value.len() as u32) } panic!("seal_return does not return"); } impl_wrapper_for! { - [u8; 32] => call_data_size, balance, value_transferred, now, chain_id; + [u8; 32] => balance, value_transferred, now, chain_id; [u8; 20] => address, caller, origin; } @@ -425,12 +429,12 @@ impl HostFn for HostFnImpl { unsafe { sys::code_hash(address.as_ptr(), output.as_mut_ptr()) } } - fn code_size(address: &[u8; 20], output: &mut [u8; 32]) { - unsafe { sys::code_size(address.as_ptr(), output.as_mut_ptr()) } + fn code_size(address: &[u8; 20]) -> u64 { + unsafe { sys::code_size(address.as_ptr()) } } - fn return_data_size(output: &mut [u8; 32]) { - unsafe { sys::return_data_size(output.as_mut_ptr()) }; + fn return_data_size() -> u64 { + unsafe { sys::return_data_size() } } fn return_data_copy(output: &mut &mut [u8], offset: u32) { From 1c0820d3f29be04e7ebb0017b09d434f614d6864 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:45:16 +0000 Subject: [PATCH 188/340] fixed token (#6958) #6940 --- .github/workflows/cmd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 2a4c1c6243fa..42b2eab3b9e4 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -418,7 +418,7 @@ jobs: # Push the results to the target branch git remote add \ github \ - "https://token:${{ steps.generate_token_commit.outputs.token }}@github.com/${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}.git" || : + "https://x-access-token:${{ steps.generate_token_commit.outputs.token }}@github.com/${{ needs.get-pr-branch.outputs.repo }}.git" || : push_changes() { git push github "HEAD:${{ needs.get-pr-branch.outputs.pr-branch }}" From ddfc608962febad82f154dc1ec39768d6675b329 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 18 Dec 2024 18:20:45 +0100 Subject: [PATCH 189/340] [pallet-revive] implement the gas limit API (#6926) This PR implements the gas limit API, returning the maximum ref_time per block. Solidity contracts only know a single weight dimension and can use this method to get the block ref_time limit. --------- Signed-off-by: Cyrill Leutwiler Signed-off-by: xermicus Co-authored-by: command-bot <> --- prdoc/pr_6926.prdoc | 13 + .../create_transient_storage_and_call.rs | 2 +- .../revive/fixtures/contracts/gas_limit.rs | 34 + .../frame/revive/src/benchmarking/mod.rs | 11 + substrate/frame/revive/src/limits.rs | 2 +- substrate/frame/revive/src/tests.rs | 28 +- substrate/frame/revive/src/wasm/runtime.rs | 11 + substrate/frame/revive/src/weights.rs | 941 +++++++++--------- substrate/frame/revive/uapi/src/host.rs | 3 + .../frame/revive/uapi/src/host/riscv64.rs | 5 + 10 files changed, 586 insertions(+), 464 deletions(-) create mode 100644 prdoc/pr_6926.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/gas_limit.rs diff --git a/prdoc/pr_6926.prdoc b/prdoc/pr_6926.prdoc new file mode 100644 index 000000000000..788d6c110873 --- /dev/null +++ b/prdoc/pr_6926.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] implement the gas limit API' +doc: +- audience: Runtime Dev + description: This PR implements the gas limit API, returning the maximum ref_time + per block. Solidity contracts only know a single weight dimension and can use + this method to get the block ref_time limit. +crates: +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive + bump: major +- name: pallet-revive-uapi + bump: major diff --git a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs index d2efb26e5ceb..cf12fed27563 100644 --- a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs @@ -22,7 +22,7 @@ use common::input; use uapi::{HostFn, HostFnImpl as api, StorageFlags}; -static BUFFER: [u8; 512] = [0u8; 512]; +static BUFFER: [u8; 448] = [0u8; 448]; #[no_mangle] #[polkavm_derive::polkavm_export] diff --git a/substrate/frame/revive/fixtures/contracts/gas_limit.rs b/substrate/frame/revive/fixtures/contracts/gas_limit.rs new file mode 100644 index 000000000000..9ce82227b64d --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/gas_limit.rs @@ -0,0 +1,34 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Returns the block ref_time limit back to the caller. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::return_value(ReturnFlags::empty(), &api::gas_limit().to_le_bytes()); +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 6f84ecdd1525..4ddd6dfbc372 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -809,6 +809,17 @@ mod benchmarks { assert_eq!(result.unwrap(), 128); } + #[benchmark(pov_mode = Measured)] + fn seal_gas_limit() { + build_runtime!(runtime, memory: []); + let result; + #[block] + { + result = runtime.bench_gas_limit(&mut memory); + } + assert_eq!(result.unwrap(), T::BlockWeights::get().max_block.ref_time()); + } + #[benchmark(pov_mode = Measured)] fn seal_block_number() { build_runtime!(runtime, memory: [[0u8;32], ]); diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 2e112baae301..3b55106c67d8 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -47,7 +47,7 @@ pub const NUM_EVENT_TOPICS: u32 = 4; pub const DELEGATE_DEPENDENCIES: u32 = 32; /// Maximum size of events (including topics) and storage values. -pub const PAYLOAD_BYTES: u32 = 512; +pub const PAYLOAD_BYTES: u32 = 448; /// The maximum size of the transient storage in bytes. /// diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 90c032bd0840..b863b52af2af 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -374,7 +374,7 @@ impl RegisteredChainExtension for TempStorageExtension { parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + Weight::from_parts(2 * WEIGHT_REF_TIME_PER_SECOND, u64::MAX), ); pub static ExistentialDeposit: u64 = 1; } @@ -382,6 +382,7 @@ parameter_types! { #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type Block = Block; + type BlockWeights = BlockWeights; type AccountId = AccountId32; type Lookup = IdentityLookup; type AccountData = pallet_balances::AccountData; @@ -438,7 +439,7 @@ parameter_types! { pub static DepositPerByte: BalanceOf = 1; pub const DepositPerItem: BalanceOf = 2; pub static CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0); - pub static ChainId: u64 = 384; + pub static ChainId: u64 = 448; } impl Convert> for Test { @@ -3504,7 +3505,7 @@ fn deposit_limit_in_nested_calls() { // Require more than the sender's balance. // Limit the sub call to little balance so it should fail in there let ret = builder::bare_call(addr_caller) - .data((512u32, &addr_callee, U256::from(1u64)).encode()) + .data((448, &addr_callee, U256::from(1u64)).encode()) .build_and_unwrap_result(); assert_return_code!(ret, RuntimeReturnCode::OutOfResources); @@ -4855,6 +4856,27 @@ fn skip_transfer_works() { }); } +#[test] +fn gas_limit_api_works() { + let (code, _) = compile_module("gas_limit").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the gas limit API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!( + u64::from_le_bytes(received.data[..].try_into().unwrap()), + ::BlockWeights::get().max_block.ref_time() + ); + }); +} + #[test] fn unknown_syscall_rejected() { let (code, _) = compile_module("unknown_syscall").unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index d1a14ca1a93a..cdf6b3b08fdc 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -326,6 +326,8 @@ pub enum RuntimeCosts { BlockHash, /// Weight of calling `seal_now`. Now, + /// Weight of calling `seal_gas_limit`. + GasLimit, /// Weight of calling `seal_weight_to_fee`. WeightToFee, /// Weight of calling `seal_terminate`, passing the number of locked dependencies. @@ -476,6 +478,7 @@ impl Token for RuntimeCosts { BlockNumber => T::WeightInfo::seal_block_number(), BlockHash => T::WeightInfo::seal_block_hash(), Now => T::WeightInfo::seal_now(), + GasLimit => T::WeightInfo::seal_gas_limit(), WeightToFee => T::WeightInfo::seal_weight_to_fee(), Terminate(locked_dependencies) => T::WeightInfo::seal_terminate(locked_dependencies), DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), @@ -1538,6 +1541,14 @@ pub mod env { )?) } + /// Returns the block ref_time limit. + /// See [`pallet_revive_uapi::HostFn::gas_limit`]. + #[stable] + fn gas_limit(&mut self, memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::GasLimit)?; + Ok(::BlockWeights::get().max_block.ref_time()) + } + /// Stores the value transferred along with this call/instantiate into the supplied buffer. /// See [`pallet_revive_uapi::HostFn::value_transferred`]. #[stable] diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index db3c34a75878..3f7ede275928 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -20,7 +20,7 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 //! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `28f02a6d927a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `c3bb6290af79`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -83,6 +83,7 @@ pub trait WeightInfo { fn seal_minimum_balance() -> Weight; fn seal_return_data_size() -> Weight; fn seal_call_data_size() -> Weight; + fn seal_gas_limit() -> Weight; fn seal_block_number() -> Weight; fn seal_block_hash() -> Weight; fn seal_now() -> Weight; @@ -138,8 +139,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_752_000 picoseconds. - Weight::from_parts(2_990_000, 1594) + // Minimum execution time: 2_749_000 picoseconds. + Weight::from_parts(2_844_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -149,10 +150,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 16_130_000 picoseconds. - Weight::from_parts(3_413_527, 415) - // Standard Error: 1_190 - .saturating_add(Weight::from_parts(1_184_912, 0).saturating_mul(k.into())) + // Minimum execution time: 15_364_000 picoseconds. + Weight::from_parts(3_092_479, 415) + // Standard Error: 1_592 + .saturating_add(Weight::from_parts(1_189_460, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -176,8 +177,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1465` // Estimated: `7405` - // Minimum execution time: 91_977_000 picoseconds. - Weight::from_parts(96_482_355, 7405) + // Minimum execution time: 92_898_000 picoseconds. + Weight::from_parts(97_241_513, 7405) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -197,14 +198,16 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { + fn instantiate_with_code(c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `416` // Estimated: `6348` - // Minimum execution time: 197_911_000 picoseconds. - Weight::from_parts(185_839_401, 6348) - // Standard Error: 9 - .saturating_add(Weight::from_parts(4_419, 0).saturating_mul(i.into())) + // Minimum execution time: 196_248_000 picoseconds. + Weight::from_parts(162_338_484, 6348) + // Standard Error: 16 + .saturating_add(Weight::from_parts(71, 0).saturating_mul(c.into())) + // Standard Error: 16 + .saturating_add(Weight::from_parts(4_579, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -227,10 +230,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1309` // Estimated: `4760` - // Minimum execution time: 162_062_000 picoseconds. - Weight::from_parts(146_040_237, 4760) + // Minimum execution time: 162_002_000 picoseconds. + Weight::from_parts(146_333_459, 4760) // Standard Error: 15 - .saturating_add(Weight::from_parts(4_410, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_482, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -250,8 +253,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1465` // Estimated: `7405` - // Minimum execution time: 143_737_000 picoseconds. - Weight::from_parts(151_572_000, 7405) + // Minimum execution time: 144_493_000 picoseconds. + Weight::from_parts(150_783_000, 7405) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -262,12 +265,14 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. - fn upload_code(_c: u32, ) -> Weight { + fn upload_code(c: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 52_301_000 picoseconds. - Weight::from_parts(54_773_649, 3574) + // Minimum execution time: 51_261_000 picoseconds. + Weight::from_parts(53_656_457, 3574) + // Standard Error: 0 + .saturating_add(Weight::from_parts(2, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -281,8 +286,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 45_699_000 picoseconds. - Weight::from_parts(46_961_000, 3750) + // Minimum execution time: 44_921_000 picoseconds. + Weight::from_parts(46_970_000, 3750) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -294,8 +299,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 26_501_000 picoseconds. - Weight::from_parts(27_913_000, 6469) + // Minimum execution time: 27_070_000 picoseconds. + Weight::from_parts(27_897_000, 6469) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -307,8 +312,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 41_673_000 picoseconds. - Weight::from_parts(42_360_000, 3574) + // Minimum execution time: 40_939_000 picoseconds. + Weight::from_parts(42_250_000, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -320,8 +325,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 32_530_000 picoseconds. - Weight::from_parts(33_997_000, 3521) + // Minimum execution time: 32_804_000 picoseconds. + Weight::from_parts(33_965_000, 3521) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -333,8 +338,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_327_000 picoseconds. - Weight::from_parts(13_976_000, 3610) + // Minimum execution time: 13_616_000 picoseconds. + Weight::from_parts(14_164_000, 3610) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -342,24 +347,24 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_317_000 picoseconds. - Weight::from_parts(7_742_783, 0) - // Standard Error: 274 - .saturating_add(Weight::from_parts(166_272, 0).saturating_mul(r.into())) + // Minimum execution time: 7_403_000 picoseconds. + Weight::from_parts(8_174_105, 0) + // Standard Error: 181 + .saturating_add(Weight::from_parts(162_824, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 300_000 picoseconds. - Weight::from_parts(349_000, 0) + // Minimum execution time: 278_000 picoseconds. + Weight::from_parts(312_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 248_000 picoseconds. - Weight::from_parts(293_000, 0) + // Minimum execution time: 232_000 picoseconds. + Weight::from_parts(252_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -367,8 +372,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_018_000 picoseconds. - Weight::from_parts(10_399_000, 3771) + // Minimum execution time: 10_239_000 picoseconds. + Weight::from_parts(10_730_000, 3771) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -377,16 +382,16 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_209_000 picoseconds. - Weight::from_parts(11_640_000, 3868) + // Minimum execution time: 11_016_000 picoseconds. + Weight::from_parts(11_331_000, 3868) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 280_000 picoseconds. - Weight::from_parts(309_000, 0) + // Minimum execution time: 261_000 picoseconds. + Weight::from_parts(298_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -396,51 +401,51 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_718_000 picoseconds. - Weight::from_parts(15_292_000, 3938) + // Minimum execution time: 14_413_000 picoseconds. + Weight::from_parts(15_066_000, 3938) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 336_000 picoseconds. - Weight::from_parts(391_000, 0) + // Minimum execution time: 303_000 picoseconds. + Weight::from_parts(340_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 275_000 picoseconds. - Weight::from_parts(296_000, 0) + // Minimum execution time: 246_000 picoseconds. + Weight::from_parts(266_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 262_000 picoseconds. - Weight::from_parts(304_000, 0) + // Minimum execution time: 260_000 picoseconds. + Weight::from_parts(287_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 628_000 picoseconds. - Weight::from_parts(714_000, 0) + // Minimum execution time: 616_000 picoseconds. + Weight::from_parts(726_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 246_000 picoseconds. - Weight::from_parts(265_000, 0) + // Minimum execution time: 253_000 picoseconds. + Weight::from_parts(282_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 5_605_000 picoseconds. - Weight::from_parts(5_769_000, 0) + // Minimum execution time: 5_380_000 picoseconds. + Weight::from_parts(5_740_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -450,8 +455,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 8_990_000 picoseconds. - Weight::from_parts(9_223_000, 3729) + // Minimum execution time: 8_826_000 picoseconds. + Weight::from_parts(9_166_000, 3729) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -461,10 +466,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 6_001_000 picoseconds. - Weight::from_parts(6_630_017, 3703) + // Minimum execution time: 5_971_000 picoseconds. + Weight::from_parts(6_578_727, 3703) // Standard Error: 4 - .saturating_add(Weight::from_parts(622, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(732, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -475,46 +480,53 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_026_000 picoseconds. - Weight::from_parts(2_271_985, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(537, 0).saturating_mul(n.into())) + // Minimum execution time: 1_866_000 picoseconds. + Weight::from_parts(2_158_746, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(637, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 287_000 picoseconds. - Weight::from_parts(323_000, 0) + // Minimum execution time: 223_000 picoseconds. + Weight::from_parts(279_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 230_000 picoseconds. - Weight::from_parts(275_000, 0) + // Minimum execution time: 220_000 picoseconds. + Weight::from_parts(245_000, 0) } fn seal_return_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 242_000 picoseconds. - Weight::from_parts(268_000, 0) + // Minimum execution time: 208_000 picoseconds. + Weight::from_parts(245_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(271_000, 0) + // Minimum execution time: 211_000 picoseconds. + Weight::from_parts(252_000, 0) + } + fn seal_gas_limit() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 282_000 picoseconds. + Weight::from_parts(310_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 266_000 picoseconds. - Weight::from_parts(304_000, 0) + // Minimum execution time: 216_000 picoseconds. + Weight::from_parts(242_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -522,60 +534,60 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_559_000 picoseconds. - Weight::from_parts(3_697_000, 3495) + // Minimum execution time: 3_410_000 picoseconds. + Weight::from_parts(3_595_000, 3495) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 242_000 picoseconds. - Weight::from_parts(294_000, 0) + // Minimum execution time: 214_000 picoseconds. + Weight::from_parts(234_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_222_000 picoseconds. - Weight::from_parts(1_387_000, 0) + // Minimum execution time: 1_344_000 picoseconds. + Weight::from_parts(1_503_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 392_000 picoseconds. - Weight::from_parts(397_500, 0) + // Minimum execution time: 372_000 picoseconds. + Weight::from_parts(613_654, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(206, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(295, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 267_000 picoseconds. - Weight::from_parts(322_000, 0) + // Minimum execution time: 213_000 picoseconds. + Weight::from_parts(243_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 234_000 picoseconds. - Weight::from_parts(291_182, 0) + // Minimum execution time: 230_000 picoseconds. + Weight::from_parts(252_625, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(150, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 253_000 picoseconds. - Weight::from_parts(271_000, 0) + // Minimum execution time: 231_000 picoseconds. + Weight::from_parts(378_784, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(212, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(296, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -592,10 +604,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `324 + n * (88 ±0)` // Estimated: `3790 + n * (2563 ±0)` - // Minimum execution time: 22_082_000 picoseconds. - Weight::from_parts(22_815_417, 3790) - // Standard Error: 9_515 - .saturating_add(Weight::from_parts(4_283_767, 0).saturating_mul(n.into())) + // Minimum execution time: 22_246_000 picoseconds. + Weight::from_parts(22_824_813, 3790) + // Standard Error: 11_423 + .saturating_add(Weight::from_parts(4_328_279, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -603,56 +615,56 @@ impl WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(0, 2563).saturating_mul(n.into())) } /// The range of component `t` is `[0, 4]`. - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_deposit_event(t: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_242_000 picoseconds. - Weight::from_parts(4_360_337, 0) - // Standard Error: 3_223 - .saturating_add(Weight::from_parts(201_105, 0).saturating_mul(t.into())) - // Standard Error: 28 - .saturating_add(Weight::from_parts(723, 0).saturating_mul(n.into())) + // Minimum execution time: 4_199_000 picoseconds. + Weight::from_parts(4_174_861, 0) + // Standard Error: 2_974 + .saturating_add(Weight::from_parts(211_154, 0).saturating_mul(t.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(1_037, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 340_000 picoseconds. - Weight::from_parts(773_824, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(722, 0).saturating_mul(i.into())) + // Minimum execution time: 311_000 picoseconds. + Weight::from_parts(326_000, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(815, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_empty() -> Weight { // Proof Size summary in bytes: - // Measured: `744` - // Estimated: `744` - // Minimum execution time: 7_741_000 picoseconds. - Weight::from_parts(8_048_000, 744) + // Measured: `680` + // Estimated: `680` + // Minimum execution time: 7_584_000 picoseconds. + Weight::from_parts(8_006_000, 680) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_full() -> Weight { // Proof Size summary in bytes: - // Measured: `10754` - // Estimated: `10754` - // Minimum execution time: 42_314_000 picoseconds. - Weight::from_parts(43_255_000, 10754) + // Measured: `10690` + // Estimated: `10690` + // Minimum execution time: 42_716_000 picoseconds. + Weight::from_parts(43_583_000, 10690) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_storage_empty() -> Weight { // Proof Size summary in bytes: - // Measured: `744` - // Estimated: `744` - // Minimum execution time: 8_741_000 picoseconds. - Weight::from_parts(9_123_000, 744) + // Measured: `680` + // Estimated: `680` + // Minimum execution time: 8_727_000 picoseconds. + Weight::from_parts(9_056_000, 680) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -660,85 +672,85 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_storage_full() -> Weight { // Proof Size summary in bytes: - // Measured: `10754` - // Estimated: `10754` - // Minimum execution time: 44_703_000 picoseconds. - Weight::from_parts(46_403_000, 10754) + // Measured: `10690` + // Estimated: `10690` + // Minimum execution time: 44_882_000 picoseconds. + Weight::from_parts(45_933_000, 10690) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 512]`. - /// The range of component `o` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. + /// The range of component `o` is `[0, 448]`. fn seal_set_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_285_000 picoseconds. - Weight::from_parts(10_046_720, 247) - // Standard Error: 37 - .saturating_add(Weight::from_parts(365, 0).saturating_mul(n.into())) - // Standard Error: 37 - .saturating_add(Weight::from_parts(273, 0).saturating_mul(o.into())) + // Minimum execution time: 9_150_000 picoseconds. + Weight::from_parts(9_621_151, 247) + // Standard Error: 43 + .saturating_add(Weight::from_parts(554, 0).saturating_mul(n.into())) + // Standard Error: 43 + .saturating_add(Weight::from_parts(645, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_clear_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_879_000 picoseconds. - Weight::from_parts(9_736_050, 247) - // Standard Error: 48 - .saturating_add(Weight::from_parts(514, 0).saturating_mul(n.into())) + // Minimum execution time: 8_670_000 picoseconds. + Weight::from_parts(9_528_913, 247) + // Standard Error: 56 + .saturating_add(Weight::from_parts(805, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_get_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_475_000 picoseconds. - Weight::from_parts(9_410_206, 247) - // Standard Error: 58 - .saturating_add(Weight::from_parts(1_409, 0).saturating_mul(n.into())) + // Minimum execution time: 8_214_000 picoseconds. + Weight::from_parts(9_195_285, 247) + // Standard Error: 70 + .saturating_add(Weight::from_parts(1_452, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_contains_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_017_000 picoseconds. - Weight::from_parts(8_879_089, 247) - // Standard Error: 51 - .saturating_add(Weight::from_parts(512, 0).saturating_mul(n.into())) + // Minimum execution time: 7_947_000 picoseconds. + Weight::from_parts(8_633_252, 247) + // Standard Error: 53 + .saturating_add(Weight::from_parts(832, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_take_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_196_000 picoseconds. - Weight::from_parts(10_285_787, 247) - // Standard Error: 65 - .saturating_add(Weight::from_parts(1_553, 0).saturating_mul(n.into())) + // Minimum execution time: 9_414_000 picoseconds. + Weight::from_parts(10_289_881, 247) + // Standard Error: 66 + .saturating_add(Weight::from_parts(1_325, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -747,87 +759,87 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_456_000 picoseconds. - Weight::from_parts(1_593_000, 0) + // Minimum execution time: 1_424_000 picoseconds. + Weight::from_parts(1_511_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_897_000 picoseconds. - Weight::from_parts(2_059_000, 0) + // Minimum execution time: 1_797_000 picoseconds. + Weight::from_parts(1_961_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_487_000 picoseconds. - Weight::from_parts(1_588_000, 0) + // Minimum execution time: 1_498_000 picoseconds. + Weight::from_parts(1_562_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_622_000 picoseconds. - Weight::from_parts(1_732_000, 0) + // Minimum execution time: 1_610_000 picoseconds. + Weight::from_parts(1_703_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_188_000 picoseconds. - Weight::from_parts(1_239_000, 0) + // Minimum execution time: 1_100_000 picoseconds. + Weight::from_parts(1_197_000, 0) } - /// The range of component `n` is `[0, 512]`. - /// The range of component `o` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. + /// The range of component `o` is `[0, 448]`. fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_269_000 picoseconds. - Weight::from_parts(2_528_717, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(163, 0).saturating_mul(n.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(332, 0).saturating_mul(o.into())) + // Minimum execution time: 2_232_000 picoseconds. + Weight::from_parts(2_371_207, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(385, 0).saturating_mul(n.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(471, 0).saturating_mul(o.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_051_000 picoseconds. - Weight::from_parts(2_507_009, 0) - // Standard Error: 20 - .saturating_add(Weight::from_parts(309, 0).saturating_mul(n.into())) + // Minimum execution time: 2_015_000 picoseconds. + Weight::from_parts(2_374_096, 0) + // Standard Error: 18 + .saturating_add(Weight::from_parts(462, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_829_000 picoseconds. - Weight::from_parts(2_052_749, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(350, 0).saturating_mul(n.into())) + // Minimum execution time: 1_788_000 picoseconds. + Weight::from_parts(1_983_300, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(404, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_717_000 picoseconds. - Weight::from_parts(1_930_820, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(161, 0).saturating_mul(n.into())) + // Minimum execution time: 1_678_000 picoseconds. + Weight::from_parts(1_845_442, 0) + // Standard Error: 15 + .saturating_add(Weight::from_parts(228, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_502_000 picoseconds. - Weight::from_parts(2_829_951, 0) + // Minimum execution time: 2_489_000 picoseconds. + Weight::from_parts(2_786_607, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -845,10 +857,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1292 + t * (280 ±0)` // Estimated: `4757 + t * (2518 ±0)` - // Minimum execution time: 40_791_000 picoseconds. - Weight::from_parts(42_421_336, 4757) - // Standard Error: 53_086 - .saturating_add(Weight::from_parts(2_057_850, 0).saturating_mul(t.into())) + // Minimum execution time: 41_653_000 picoseconds. + Weight::from_parts(43_075_070, 4757) + // Standard Error: 42_656 + .saturating_add(Weight::from_parts(1_667_094, 0).saturating_mul(t.into())) // Standard Error: 0 .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) @@ -866,8 +878,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 35_825_000 picoseconds. - Weight::from_parts(37_377_000, 4702) + // Minimum execution time: 36_908_000 picoseconds. + Weight::from_parts(37_754_000, 4702) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -883,10 +895,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1310` // Estimated: `4769` - // Minimum execution time: 121_920_000 picoseconds. - Weight::from_parts(115_842_357, 4769) + // Minimum execution time: 120_576_000 picoseconds. + Weight::from_parts(112_786_790, 4769) // Standard Error: 10 - .saturating_add(Weight::from_parts(4_062, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_192, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -895,64 +907,64 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 657_000 picoseconds. - Weight::from_parts(2_219_539, 0) + // Minimum execution time: 621_000 picoseconds. + Weight::from_parts(3_506_910, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_413, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_489, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_091_000 picoseconds. - Weight::from_parts(4_036_613, 0) + // Minimum execution time: 1_054_000 picoseconds. + Weight::from_parts(5_395_465, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_600, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_688, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 635_000 picoseconds. - Weight::from_parts(4_636_213, 0) + // Minimum execution time: 626_000 picoseconds. + Weight::from_parts(3_549_376, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_514, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_596, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 648_000 picoseconds. - Weight::from_parts(3_658_083, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_516, 0).saturating_mul(n.into())) + // Minimum execution time: 598_000 picoseconds. + Weight::from_parts(2_618_039, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_616, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_722_000 picoseconds. - Weight::from_parts(28_496_037, 0) + // Minimum execution time: 42_715_000 picoseconds. + Weight::from_parts(25_484_960, 0) // Standard Error: 13 - .saturating_add(Weight::from_parts(5_235, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(5_315, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_924_000 picoseconds. - Weight::from_parts(48_639_000, 0) + // Minimum execution time: 47_123_000 picoseconds. + Weight::from_parts(48_956_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_882_000 picoseconds. - Weight::from_parts(13_108_000, 0) + // Minimum execution time: 12_650_000 picoseconds. + Weight::from_parts(12_768_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -960,8 +972,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 17_907_000 picoseconds. - Weight::from_parts(18_634_000, 3765) + // Minimum execution time: 18_061_000 picoseconds. + Weight::from_parts(18_851_000, 3765) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -971,8 +983,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 14_091_000 picoseconds. - Weight::from_parts(14_393_000, 3803) + // Minimum execution time: 13_779_000 picoseconds. + Weight::from_parts(14_320_000, 3803) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -982,8 +994,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 12_824_000 picoseconds. - Weight::from_parts(13_304_000, 3561) + // Minimum execution time: 12_327_000 picoseconds. + Weight::from_parts(13_156_000, 3561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -992,10 +1004,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_185_000 picoseconds. - Weight::from_parts(10_532_230, 0) - // Standard Error: 189 - .saturating_add(Weight::from_parts(71_786, 0).saturating_mul(r.into())) + // Minimum execution time: 9_048_000 picoseconds. + Weight::from_parts(10_590_154, 0) + // Standard Error: 82 + .saturating_add(Weight::from_parts(72_658, 0).saturating_mul(r.into())) } } @@ -1007,8 +1019,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_752_000 picoseconds. - Weight::from_parts(2_990_000, 1594) + // Minimum execution time: 2_749_000 picoseconds. + Weight::from_parts(2_844_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1018,10 +1030,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 16_130_000 picoseconds. - Weight::from_parts(3_413_527, 415) - // Standard Error: 1_190 - .saturating_add(Weight::from_parts(1_184_912, 0).saturating_mul(k.into())) + // Minimum execution time: 15_364_000 picoseconds. + Weight::from_parts(3_092_479, 415) + // Standard Error: 1_592 + .saturating_add(Weight::from_parts(1_189_460, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1045,8 +1057,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1465` // Estimated: `7405` - // Minimum execution time: 91_977_000 picoseconds. - Weight::from_parts(96_482_355, 7405) + // Minimum execution time: 92_898_000 picoseconds. + Weight::from_parts(97_241_513, 7405) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1066,14 +1078,16 @@ impl WeightInfo for () { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { + fn instantiate_with_code(c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `416` // Estimated: `6348` - // Minimum execution time: 197_911_000 picoseconds. - Weight::from_parts(185_839_401, 6348) - // Standard Error: 9 - .saturating_add(Weight::from_parts(4_419, 0).saturating_mul(i.into())) + // Minimum execution time: 196_248_000 picoseconds. + Weight::from_parts(162_338_484, 6348) + // Standard Error: 16 + .saturating_add(Weight::from_parts(71, 0).saturating_mul(c.into())) + // Standard Error: 16 + .saturating_add(Weight::from_parts(4_579, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1096,10 +1110,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1309` // Estimated: `4760` - // Minimum execution time: 162_062_000 picoseconds. - Weight::from_parts(146_040_237, 4760) + // Minimum execution time: 162_002_000 picoseconds. + Weight::from_parts(146_333_459, 4760) // Standard Error: 15 - .saturating_add(Weight::from_parts(4_410, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_482, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1119,8 +1133,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1465` // Estimated: `7405` - // Minimum execution time: 143_737_000 picoseconds. - Weight::from_parts(151_572_000, 7405) + // Minimum execution time: 144_493_000 picoseconds. + Weight::from_parts(150_783_000, 7405) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1131,12 +1145,14 @@ impl WeightInfo for () { /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. - fn upload_code(_c: u32, ) -> Weight { + fn upload_code(c: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 52_301_000 picoseconds. - Weight::from_parts(54_773_649, 3574) + // Minimum execution time: 51_261_000 picoseconds. + Weight::from_parts(53_656_457, 3574) + // Standard Error: 0 + .saturating_add(Weight::from_parts(2, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1150,8 +1166,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 45_699_000 picoseconds. - Weight::from_parts(46_961_000, 3750) + // Minimum execution time: 44_921_000 picoseconds. + Weight::from_parts(46_970_000, 3750) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1163,8 +1179,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 26_501_000 picoseconds. - Weight::from_parts(27_913_000, 6469) + // Minimum execution time: 27_070_000 picoseconds. + Weight::from_parts(27_897_000, 6469) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1176,8 +1192,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 41_673_000 picoseconds. - Weight::from_parts(42_360_000, 3574) + // Minimum execution time: 40_939_000 picoseconds. + Weight::from_parts(42_250_000, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1189,8 +1205,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 32_530_000 picoseconds. - Weight::from_parts(33_997_000, 3521) + // Minimum execution time: 32_804_000 picoseconds. + Weight::from_parts(33_965_000, 3521) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1202,8 +1218,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_327_000 picoseconds. - Weight::from_parts(13_976_000, 3610) + // Minimum execution time: 13_616_000 picoseconds. + Weight::from_parts(14_164_000, 3610) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1211,24 +1227,24 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_317_000 picoseconds. - Weight::from_parts(7_742_783, 0) - // Standard Error: 274 - .saturating_add(Weight::from_parts(166_272, 0).saturating_mul(r.into())) + // Minimum execution time: 7_403_000 picoseconds. + Weight::from_parts(8_174_105, 0) + // Standard Error: 181 + .saturating_add(Weight::from_parts(162_824, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 300_000 picoseconds. - Weight::from_parts(349_000, 0) + // Minimum execution time: 278_000 picoseconds. + Weight::from_parts(312_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 248_000 picoseconds. - Weight::from_parts(293_000, 0) + // Minimum execution time: 232_000 picoseconds. + Weight::from_parts(252_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1236,8 +1252,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_018_000 picoseconds. - Weight::from_parts(10_399_000, 3771) + // Minimum execution time: 10_239_000 picoseconds. + Weight::from_parts(10_730_000, 3771) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -1246,16 +1262,16 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_209_000 picoseconds. - Weight::from_parts(11_640_000, 3868) + // Minimum execution time: 11_016_000 picoseconds. + Weight::from_parts(11_331_000, 3868) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 280_000 picoseconds. - Weight::from_parts(309_000, 0) + // Minimum execution time: 261_000 picoseconds. + Weight::from_parts(298_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1265,51 +1281,51 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_718_000 picoseconds. - Weight::from_parts(15_292_000, 3938) + // Minimum execution time: 14_413_000 picoseconds. + Weight::from_parts(15_066_000, 3938) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 336_000 picoseconds. - Weight::from_parts(391_000, 0) + // Minimum execution time: 303_000 picoseconds. + Weight::from_parts(340_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 275_000 picoseconds. - Weight::from_parts(296_000, 0) + // Minimum execution time: 246_000 picoseconds. + Weight::from_parts(266_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 262_000 picoseconds. - Weight::from_parts(304_000, 0) + // Minimum execution time: 260_000 picoseconds. + Weight::from_parts(287_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 628_000 picoseconds. - Weight::from_parts(714_000, 0) + // Minimum execution time: 616_000 picoseconds. + Weight::from_parts(726_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 246_000 picoseconds. - Weight::from_parts(265_000, 0) + // Minimum execution time: 253_000 picoseconds. + Weight::from_parts(282_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: // Measured: `140` // Estimated: `0` - // Minimum execution time: 5_605_000 picoseconds. - Weight::from_parts(5_769_000, 0) + // Minimum execution time: 5_380_000 picoseconds. + Weight::from_parts(5_740_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1319,8 +1335,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 8_990_000 picoseconds. - Weight::from_parts(9_223_000, 3729) + // Minimum execution time: 8_826_000 picoseconds. + Weight::from_parts(9_166_000, 3729) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -1330,10 +1346,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 6_001_000 picoseconds. - Weight::from_parts(6_630_017, 3703) + // Minimum execution time: 5_971_000 picoseconds. + Weight::from_parts(6_578_727, 3703) // Standard Error: 4 - .saturating_add(Weight::from_parts(622, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(732, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1344,46 +1360,53 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_026_000 picoseconds. - Weight::from_parts(2_271_985, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(537, 0).saturating_mul(n.into())) + // Minimum execution time: 1_866_000 picoseconds. + Weight::from_parts(2_158_746, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(637, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 287_000 picoseconds. - Weight::from_parts(323_000, 0) + // Minimum execution time: 223_000 picoseconds. + Weight::from_parts(279_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 230_000 picoseconds. - Weight::from_parts(275_000, 0) + // Minimum execution time: 220_000 picoseconds. + Weight::from_parts(245_000, 0) } fn seal_return_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 242_000 picoseconds. - Weight::from_parts(268_000, 0) + // Minimum execution time: 208_000 picoseconds. + Weight::from_parts(245_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 244_000 picoseconds. - Weight::from_parts(271_000, 0) + // Minimum execution time: 211_000 picoseconds. + Weight::from_parts(252_000, 0) + } + fn seal_gas_limit() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 282_000 picoseconds. + Weight::from_parts(310_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 266_000 picoseconds. - Weight::from_parts(304_000, 0) + // Minimum execution time: 216_000 picoseconds. + Weight::from_parts(242_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -1391,60 +1414,60 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_559_000 picoseconds. - Weight::from_parts(3_697_000, 3495) + // Minimum execution time: 3_410_000 picoseconds. + Weight::from_parts(3_595_000, 3495) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 242_000 picoseconds. - Weight::from_parts(294_000, 0) + // Minimum execution time: 214_000 picoseconds. + Weight::from_parts(234_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_222_000 picoseconds. - Weight::from_parts(1_387_000, 0) + // Minimum execution time: 1_344_000 picoseconds. + Weight::from_parts(1_503_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 392_000 picoseconds. - Weight::from_parts(397_500, 0) + // Minimum execution time: 372_000 picoseconds. + Weight::from_parts(613_654, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(206, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(295, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 267_000 picoseconds. - Weight::from_parts(322_000, 0) + // Minimum execution time: 213_000 picoseconds. + Weight::from_parts(243_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 234_000 picoseconds. - Weight::from_parts(291_182, 0) + // Minimum execution time: 230_000 picoseconds. + Weight::from_parts(252_625, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(113, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(150, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 253_000 picoseconds. - Weight::from_parts(271_000, 0) + // Minimum execution time: 231_000 picoseconds. + Weight::from_parts(378_784, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(212, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(296, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1461,10 +1484,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `324 + n * (88 ±0)` // Estimated: `3790 + n * (2563 ±0)` - // Minimum execution time: 22_082_000 picoseconds. - Weight::from_parts(22_815_417, 3790) - // Standard Error: 9_515 - .saturating_add(Weight::from_parts(4_283_767, 0).saturating_mul(n.into())) + // Minimum execution time: 22_246_000 picoseconds. + Weight::from_parts(22_824_813, 3790) + // Standard Error: 11_423 + .saturating_add(Weight::from_parts(4_328_279, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -1472,56 +1495,56 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(0, 2563).saturating_mul(n.into())) } /// The range of component `t` is `[0, 4]`. - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_deposit_event(t: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_242_000 picoseconds. - Weight::from_parts(4_360_337, 0) - // Standard Error: 3_223 - .saturating_add(Weight::from_parts(201_105, 0).saturating_mul(t.into())) - // Standard Error: 28 - .saturating_add(Weight::from_parts(723, 0).saturating_mul(n.into())) + // Minimum execution time: 4_199_000 picoseconds. + Weight::from_parts(4_174_861, 0) + // Standard Error: 2_974 + .saturating_add(Weight::from_parts(211_154, 0).saturating_mul(t.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(1_037, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 340_000 picoseconds. - Weight::from_parts(773_824, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(722, 0).saturating_mul(i.into())) + // Minimum execution time: 311_000 picoseconds. + Weight::from_parts(326_000, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(815, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_empty() -> Weight { // Proof Size summary in bytes: - // Measured: `744` - // Estimated: `744` - // Minimum execution time: 7_741_000 picoseconds. - Weight::from_parts(8_048_000, 744) + // Measured: `680` + // Estimated: `680` + // Minimum execution time: 7_584_000 picoseconds. + Weight::from_parts(8_006_000, 680) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_full() -> Weight { // Proof Size summary in bytes: - // Measured: `10754` - // Estimated: `10754` - // Minimum execution time: 42_314_000 picoseconds. - Weight::from_parts(43_255_000, 10754) + // Measured: `10690` + // Estimated: `10690` + // Minimum execution time: 42_716_000 picoseconds. + Weight::from_parts(43_583_000, 10690) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_storage_empty() -> Weight { // Proof Size summary in bytes: - // Measured: `744` - // Estimated: `744` - // Minimum execution time: 8_741_000 picoseconds. - Weight::from_parts(9_123_000, 744) + // Measured: `680` + // Estimated: `680` + // Minimum execution time: 8_727_000 picoseconds. + Weight::from_parts(9_056_000, 680) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1529,85 +1552,85 @@ impl WeightInfo for () { /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_storage_full() -> Weight { // Proof Size summary in bytes: - // Measured: `10754` - // Estimated: `10754` - // Minimum execution time: 44_703_000 picoseconds. - Weight::from_parts(46_403_000, 10754) + // Measured: `10690` + // Estimated: `10690` + // Minimum execution time: 44_882_000 picoseconds. + Weight::from_parts(45_933_000, 10690) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 512]`. - /// The range of component `o` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. + /// The range of component `o` is `[0, 448]`. fn seal_set_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_285_000 picoseconds. - Weight::from_parts(10_046_720, 247) - // Standard Error: 37 - .saturating_add(Weight::from_parts(365, 0).saturating_mul(n.into())) - // Standard Error: 37 - .saturating_add(Weight::from_parts(273, 0).saturating_mul(o.into())) + // Minimum execution time: 9_150_000 picoseconds. + Weight::from_parts(9_621_151, 247) + // Standard Error: 43 + .saturating_add(Weight::from_parts(554, 0).saturating_mul(n.into())) + // Standard Error: 43 + .saturating_add(Weight::from_parts(645, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_clear_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_879_000 picoseconds. - Weight::from_parts(9_736_050, 247) - // Standard Error: 48 - .saturating_add(Weight::from_parts(514, 0).saturating_mul(n.into())) + // Minimum execution time: 8_670_000 picoseconds. + Weight::from_parts(9_528_913, 247) + // Standard Error: 56 + .saturating_add(Weight::from_parts(805, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_get_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_475_000 picoseconds. - Weight::from_parts(9_410_206, 247) - // Standard Error: 58 - .saturating_add(Weight::from_parts(1_409, 0).saturating_mul(n.into())) + // Minimum execution time: 8_214_000 picoseconds. + Weight::from_parts(9_195_285, 247) + // Standard Error: 70 + .saturating_add(Weight::from_parts(1_452, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_contains_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_017_000 picoseconds. - Weight::from_parts(8_879_089, 247) - // Standard Error: 51 - .saturating_add(Weight::from_parts(512, 0).saturating_mul(n.into())) + // Minimum execution time: 7_947_000 picoseconds. + Weight::from_parts(8_633_252, 247) + // Standard Error: 53 + .saturating_add(Weight::from_parts(832, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_take_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_196_000 picoseconds. - Weight::from_parts(10_285_787, 247) - // Standard Error: 65 - .saturating_add(Weight::from_parts(1_553, 0).saturating_mul(n.into())) + // Minimum execution time: 9_414_000 picoseconds. + Weight::from_parts(10_289_881, 247) + // Standard Error: 66 + .saturating_add(Weight::from_parts(1_325, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1616,87 +1639,87 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_456_000 picoseconds. - Weight::from_parts(1_593_000, 0) + // Minimum execution time: 1_424_000 picoseconds. + Weight::from_parts(1_511_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_897_000 picoseconds. - Weight::from_parts(2_059_000, 0) + // Minimum execution time: 1_797_000 picoseconds. + Weight::from_parts(1_961_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_487_000 picoseconds. - Weight::from_parts(1_588_000, 0) + // Minimum execution time: 1_498_000 picoseconds. + Weight::from_parts(1_562_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_622_000 picoseconds. - Weight::from_parts(1_732_000, 0) + // Minimum execution time: 1_610_000 picoseconds. + Weight::from_parts(1_703_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_188_000 picoseconds. - Weight::from_parts(1_239_000, 0) + // Minimum execution time: 1_100_000 picoseconds. + Weight::from_parts(1_197_000, 0) } - /// The range of component `n` is `[0, 512]`. - /// The range of component `o` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. + /// The range of component `o` is `[0, 448]`. fn seal_set_transient_storage(n: u32, o: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_269_000 picoseconds. - Weight::from_parts(2_528_717, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(163, 0).saturating_mul(n.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(332, 0).saturating_mul(o.into())) + // Minimum execution time: 2_232_000 picoseconds. + Weight::from_parts(2_371_207, 0) + // Standard Error: 13 + .saturating_add(Weight::from_parts(385, 0).saturating_mul(n.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(471, 0).saturating_mul(o.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_051_000 picoseconds. - Weight::from_parts(2_507_009, 0) - // Standard Error: 20 - .saturating_add(Weight::from_parts(309, 0).saturating_mul(n.into())) + // Minimum execution time: 2_015_000 picoseconds. + Weight::from_parts(2_374_096, 0) + // Standard Error: 18 + .saturating_add(Weight::from_parts(462, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_829_000 picoseconds. - Weight::from_parts(2_052_749, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(350, 0).saturating_mul(n.into())) + // Minimum execution time: 1_788_000 picoseconds. + Weight::from_parts(1_983_300, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(404, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_717_000 picoseconds. - Weight::from_parts(1_930_820, 0) - // Standard Error: 12 - .saturating_add(Weight::from_parts(161, 0).saturating_mul(n.into())) + // Minimum execution time: 1_678_000 picoseconds. + Weight::from_parts(1_845_442, 0) + // Standard Error: 15 + .saturating_add(Weight::from_parts(228, 0).saturating_mul(n.into())) } - /// The range of component `n` is `[0, 512]`. + /// The range of component `n` is `[0, 448]`. fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_502_000 picoseconds. - Weight::from_parts(2_829_951, 0) + // Minimum execution time: 2_489_000 picoseconds. + Weight::from_parts(2_786_607, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1714,10 +1737,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1292 + t * (280 ±0)` // Estimated: `4757 + t * (2518 ±0)` - // Minimum execution time: 40_791_000 picoseconds. - Weight::from_parts(42_421_336, 4757) - // Standard Error: 53_086 - .saturating_add(Weight::from_parts(2_057_850, 0).saturating_mul(t.into())) + // Minimum execution time: 41_653_000 picoseconds. + Weight::from_parts(43_075_070, 4757) + // Standard Error: 42_656 + .saturating_add(Weight::from_parts(1_667_094, 0).saturating_mul(t.into())) // Standard Error: 0 .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) @@ -1735,8 +1758,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 35_825_000 picoseconds. - Weight::from_parts(37_377_000, 4702) + // Minimum execution time: 36_908_000 picoseconds. + Weight::from_parts(37_754_000, 4702) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -1752,10 +1775,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1310` // Estimated: `4769` - // Minimum execution time: 121_920_000 picoseconds. - Weight::from_parts(115_842_357, 4769) + // Minimum execution time: 120_576_000 picoseconds. + Weight::from_parts(112_786_790, 4769) // Standard Error: 10 - .saturating_add(Weight::from_parts(4_062, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_192, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1764,64 +1787,64 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 657_000 picoseconds. - Weight::from_parts(2_219_539, 0) + // Minimum execution time: 621_000 picoseconds. + Weight::from_parts(3_506_910, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_413, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_489, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_091_000 picoseconds. - Weight::from_parts(4_036_613, 0) + // Minimum execution time: 1_054_000 picoseconds. + Weight::from_parts(5_395_465, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(3_600, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(3_688, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 635_000 picoseconds. - Weight::from_parts(4_636_213, 0) + // Minimum execution time: 626_000 picoseconds. + Weight::from_parts(3_549_376, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_514, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_596, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 648_000 picoseconds. - Weight::from_parts(3_658_083, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_516, 0).saturating_mul(n.into())) + // Minimum execution time: 598_000 picoseconds. + Weight::from_parts(2_618_039, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_616, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_722_000 picoseconds. - Weight::from_parts(28_496_037, 0) + // Minimum execution time: 42_715_000 picoseconds. + Weight::from_parts(25_484_960, 0) // Standard Error: 13 - .saturating_add(Weight::from_parts(5_235, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(5_315, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 46_924_000 picoseconds. - Weight::from_parts(48_639_000, 0) + // Minimum execution time: 47_123_000 picoseconds. + Weight::from_parts(48_956_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_882_000 picoseconds. - Weight::from_parts(13_108_000, 0) + // Minimum execution time: 12_650_000 picoseconds. + Weight::from_parts(12_768_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1829,8 +1852,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 17_907_000 picoseconds. - Weight::from_parts(18_634_000, 3765) + // Minimum execution time: 18_061_000 picoseconds. + Weight::from_parts(18_851_000, 3765) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1840,8 +1863,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 14_091_000 picoseconds. - Weight::from_parts(14_393_000, 3803) + // Minimum execution time: 13_779_000 picoseconds. + Weight::from_parts(14_320_000, 3803) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1851,8 +1874,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 12_824_000 picoseconds. - Weight::from_parts(13_304_000, 3561) + // Minimum execution time: 12_327_000 picoseconds. + Weight::from_parts(13_156_000, 3561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1861,9 +1884,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_185_000 picoseconds. - Weight::from_parts(10_532_230, 0) - // Standard Error: 189 - .saturating_add(Weight::from_parts(71_786, 0).saturating_mul(r.into())) + // Minimum execution time: 9_048_000 picoseconds. + Weight::from_parts(10_590_154, 0) + // Standard Error: 82 + .saturating_add(Weight::from_parts(72_658, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index ae4479cd1549..16d6f0945427 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -330,6 +330,9 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the timestamp. fn now(output: &mut [u8; 32]); + /// Returns the block ref_time limit. + fn gas_limit() -> u64; + /// Cease contract execution and save a data buffer as a result of the execution. /// /// This function never returns as it stops execution of the caller. diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index d45e0d65646c..9f080ddbfbf5 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -84,6 +84,7 @@ mod sys { pub fn chain_id(out_ptr: *mut u8); pub fn value_transferred(out_ptr: *mut u8); pub fn now(out_ptr: *mut u8); + pub fn gas_limit() -> u64; pub fn minimum_balance(out_ptr: *mut u8); pub fn deposit_event( topics_ptr: *const [u8; 32], @@ -386,6 +387,10 @@ impl HostFn for HostFnImpl { unsafe { sys::call_data_load(out_ptr.as_mut_ptr(), offset) }; } + fn gas_limit() -> u64 { + unsafe { sys::gas_limit() } + } + fn call_data_size() -> u64 { unsafe { sys::call_data_size() } } From ef8886570ea692133c6b49ecb2f1117c0a366ebd Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Wed, 18 Dec 2024 20:36:03 +0100 Subject: [PATCH 190/340] [pallet-revive] bump polkavm to 0.18 (#6937) Update to the latest polkavm version, containing a linker fix I need for revive. --------- Signed-off-by: Cyrill Leutwiler Signed-off-by: xermicus Co-authored-by: command-bot <> --- Cargo.lock | 91 +----------------- prdoc/pr_6937.prdoc | 12 +++ substrate/frame/revive/Cargo.toml | 2 +- substrate/frame/revive/fixtures/Cargo.toml | 2 +- .../frame/revive/fixtures/build/_Cargo.toml | 2 +- .../rpc/examples/js/pvm/ErrorTester.polkavm | Bin 7251 -> 7274 bytes .../rpc/examples/js/pvm/EventExample.polkavm | Bin 2604 -> 2615 bytes .../rpc/examples/js/pvm/Flipper.polkavm | Bin 1727 -> 1738 bytes .../rpc/examples/js/pvm/FlipperCaller.polkavm | Bin 4394 -> 4532 bytes .../rpc/examples/js/pvm/PiggyBank.polkavm | Bin 5012 -> 5062 bytes substrate/frame/revive/uapi/Cargo.toml | 2 +- 11 files changed, 19 insertions(+), 92 deletions(-) create mode 100644 prdoc/pr_6937.prdoc diff --git a/Cargo.lock b/Cargo.lock index 41c149c11d41..9ae662452539 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14847,7 +14847,7 @@ dependencies = [ "pallet-utility 28.0.0", "parity-scale-codec", "paste", - "polkavm 0.17.0", + "polkavm 0.18.0", "pretty_assertions", "rlp 0.6.1", "scale-info", @@ -14935,7 +14935,7 @@ name = "pallet-revive-fixtures" version = "0.1.0" dependencies = [ "anyhow", - "polkavm-linker 0.17.1", + "polkavm-linker 0.18.0", "sp-core 28.0.0", "sp-io 30.0.0", "toml 0.8.12", @@ -15049,7 +15049,7 @@ dependencies = [ "bitflags 1.3.2", "parity-scale-codec", "paste", - "polkavm-derive 0.17.0", + "polkavm-derive 0.18.0", "scale-info", ] @@ -19892,19 +19892,6 @@ dependencies = [ "polkavm-linux-raw 0.10.0", ] -[[package]] -name = "polkavm" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84979be196ba2855f73616413e7b1d18258128aa396b3dc23f520a00a807720e" -dependencies = [ - "libc", - "log", - "polkavm-assembler 0.17.0", - "polkavm-common 0.17.0", - "polkavm-linux-raw 0.17.0", -] - [[package]] name = "polkavm" version = "0.18.0" @@ -19936,15 +19923,6 @@ dependencies = [ "log", ] -[[package]] -name = "polkavm-assembler" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba7b434ff630b0f73a1560e8baea807246ca22098abe49f97821e0e2d2accc4" -dependencies = [ - "log", -] - [[package]] name = "polkavm-assembler" version = "0.18.0" @@ -19979,16 +19957,6 @@ dependencies = [ "polkavm-assembler 0.10.0", ] -[[package]] -name = "polkavm-common" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0dbafef4ab6ceecb4982ac3b550df430ef4f9fdbf07c108b7d4f91a0682fce" -dependencies = [ - "log", - "polkavm-assembler 0.17.0", -] - [[package]] name = "polkavm-common" version = "0.18.0" @@ -20026,15 +19994,6 @@ dependencies = [ "polkavm-derive-impl-macro 0.10.0", ] -[[package]] -name = "polkavm-derive" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c3dbb6c8c7bd3e5f5b05aa7fc9355acf14df7ce5d392911e77d01090a38d0d" -dependencies = [ - "polkavm-derive-impl-macro 0.17.0", -] - [[package]] name = "polkavm-derive" version = "0.18.0" @@ -20080,18 +20039,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "polkavm-derive-impl" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42565aed4adbc4034612d0b17dea8db3681fb1bd1aed040d6edc5455a9f478a1" -dependencies = [ - "polkavm-common 0.17.0", - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 2.0.87", -] - [[package]] name = "polkavm-derive-impl" version = "0.18.0" @@ -20134,16 +20081,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "polkavm-derive-impl-macro" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d9838e95241b0bce4fe269cdd4af96464160505840ed5a8ac8536119ba19e2" -dependencies = [ - "polkavm-derive-impl 0.17.0", - "syn 2.0.87", -] - [[package]] name = "polkavm-derive-impl-macro" version = "0.18.0" @@ -20184,22 +20121,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "polkavm-linker" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0422ead3030d5cde69e2206dbc7d65da872b121876507cd5363f6c6e6aa45157" -dependencies = [ - "dirs", - "gimli 0.31.1", - "hashbrown 0.14.5", - "log", - "object 0.36.1", - "polkavm-common 0.17.0", - "regalloc2 0.9.3", - "rustc-demangle", -] - [[package]] name = "polkavm-linker" version = "0.18.0" @@ -20228,12 +20149,6 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26e45fa59c7e1bb12ef5289080601e9ec9b31435f6e32800a5c90c132453d126" -[[package]] -name = "polkavm-linux-raw" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64c3d93a58ffbc3099d1227f0da9675a025a9ea6c917038f266920c1de1e568" - [[package]] name = "polkavm-linux-raw" version = "0.18.0" diff --git a/prdoc/pr_6937.prdoc b/prdoc/pr_6937.prdoc new file mode 100644 index 000000000000..5c6806df0b5c --- /dev/null +++ b/prdoc/pr_6937.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] bump polkavm to 0.18' +doc: +- audience: Runtime Dev + description: Update to the latest polkavm version, containing a linker fix I need + for revive. +crates: +- name: pallet-revive + bump: patch +- name: pallet-revive-fixtures + bump: patch +- name: pallet-revive-uapi + bump: patch diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 6e244ad4d652..5d2bfb4f795e 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] environmental = { workspace = true } paste = { workspace = true } -polkavm = { version = "0.17.0", default-features = false } +polkavm = { version = "0.18.0", default-features = false } codec = { features = ["derive", "max-encoded-len"], workspace = true } scale-info = { features = ["derive"], workspace = true } log = { workspace = true } diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index 459ec1369434..1095f962ac1b 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -21,7 +21,7 @@ anyhow = { workspace = true, default-features = true, optional = true } [build-dependencies] toml = { workspace = true } -polkavm-linker = { version = "0.17.0" } +polkavm-linker = { version = "0.18.0" } anyhow = { workspace = true, default-features = true } [features] diff --git a/substrate/frame/revive/fixtures/build/_Cargo.toml b/substrate/frame/revive/fixtures/build/_Cargo.toml index 8dc38e14c14b..5d1c922f9002 100644 --- a/substrate/frame/revive/fixtures/build/_Cargo.toml +++ b/substrate/frame/revive/fixtures/build/_Cargo.toml @@ -14,7 +14,7 @@ edition = "2021" [dependencies] uapi = { package = 'pallet-revive-uapi', path = "", features = ["unstable-api"], default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } -polkavm-derive = { version = "0.17.0" } +polkavm-derive = { version = "0.18.0" } [profile.release] opt-level = 3 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm index 5c3995bffe35c6595cf3758f7ac98f295c6e69a5..77de4ff3b1b3fe1f378ae31bbba24ddb38cc6300 100644 GIT binary patch literal 7274 zcmc&&4R9OPo!^yY?bAwjR*LLh*^VPCn?wnBOdO@HbIoyXMtPQU9HUiD>(X+GiH+AE z*<{xada^0wD^4vrShWR5ZF6v$kk(&ZhZ{T-lW>P9Ic&$w95(|)=GuaXIeKon_RO_M zO-RW7pX85(OEZue292KH>b?Ja@Be=P?~Q!puNbDel*xWrj(Mqy$uL!xt{)&eQUHz4 zZHM+HXjRan(C&qHsIIm3Kz&_%-GM_#j>gyiwH~RfU;EeA)X~t`(0-t;{fJT5*wEHc z*LuKcXg_9XZ7|w&`0%mzy8BuivK!HnWx5CIT8}jxXgBJ#w)-0lqoF>}X&Urd=)Qy7 zto02?TaU!8PfUEpG`RC#<^iUY8D=c(E_Ofr9kz=Nnhevp=^fLbntx-ivi#2Sc;0U7 zW|T(nq9NNJ{%!uW{S|w;h-WFg+(DOqA(Ya@f=Hu*6Y!-+b$-FB+-soimh-cYY;~uuQ`qHCMzH|)w zfB4nomIGF$+9N4npX8P5-jc`VSx2<`CopcxUjYoUH$n`tH-_vL2WgbpSnTVf`lr7H?In%7fCg*0)46rhw_#m$d0mEXD~9|p0L>J%NodbPdj{GlG+4u(&#f!o z1nY{I*UiJ+&!Jspj3Y+K|2os)ZGiqYMnBdB{i{r!*Jud&XBcl&n>XY?&(x?*ZMloq zM}*Rgmlf_`?Vu@aoX{(Qoa;f#_f)rj>t|2*nw9Dwk34idA4RLb|4&an`Xmriuxi9j zKd{Fet3XH=o+a=AItG0NdH}jH1U&8G zLlxthSuS%HWe7?oyqPmxU(B1elec)ib&=8GFuh4)<0BwN%CO#y*off$myZ1LW6_00Cl&=PuESyh7W1*_z@iK(_xZ8- z!p&Lyca&o>m}OA{!Ebr>Lxp=8hAE2@n?o_jVMD-lOfyOY=2oq(Bw(-8I*S9A2efvI zDBvurfaxIswO#9^s2&DbVc6LYz#ai)HGtL|aI|R;Qg|BxUl`@<0b!s*4VuAeZC0%_ zPiveFxd@k?yJK5!5~X&^A-&5Ia3>%S>?7#1^$Z#f7&Fvf(ZL&=SzP*c>C* znRI=en$Zby2_n~Z6V$fA(1!IE#Kt@0Y8SYfs{u}L1q-}yk>Smx8T`e_N=OqrV$9X7 zJy2h;#AKeg5m=cy%aay~%}k;2mMP%jS!kosPO>LT<~z>K*Z-qFCFy4c{duIHiJy(D zC$q=cy#bE5((oN%{Qh_UKm(+I{tpWGrWHaPjde8SDb*iAm?;39pFcYB;d9no!#QSM z!P#;cj;sRZiU`}=;IL-vXLM4#tv7K}f8vELML6v9c2fULozy7&u-d0{Qhkhq9?H9s zm%6?61BE-3cfAYvBE8g`mQQ-A9kc(da2XiB)=Po6{KCA{u4NY2ywuJuz~Y8w7O(eG zf6Ubz;OoKRtcTtO%?S=j0NmGsn<{|ud~j2aHgHPEz$t+j%Q~exaQr}Hh4H*5@cmxD zaf>EVZ(cQtd(2=D0Z5j2)%{-KYR%dFN|M5o5SCCF-4*K80~`%&5#h^!0Tf?02WVdG z@_%*N+tBplI>)k{5C5_3Zo#yA_FJYEbFBQ2-znVBOgVF`BBLQP5h2giR&-P(E2b)b z3O?|O=@EPP!w+rEbjH}}hq=s{V|otKj;HdL+5fXsu)~n)ChYI&0*V+M3G9E(|KP1T zlkIEVdm)j|B6r7D*tqOwM_1(m#bt17a5M{%Okj`pctcWYNRmR5J0uAq$q|xJNCKc` zDvZ6a|Bu4Wu{mD|9#f`S{&My4+2D>t&bORQ9|6C75!!3eW}szm7%w_#-gv`!5g(^< zB9n^~5FcoM8KcxIQVf;>*M$(lz*K4GS4+mK$an-Yzn;t)lo_*C3_-d`^SO@fhGpwJ0LsU%anyuQLYh*&U}a5g48%NK0ilsa(S+Hcrk_k&Jt$#4e zYIx{gIKReUYr8e9^K($&Em~Z;%5;DJ`%zA`F)IMwd1X=Ie!q&(r^}4b-z4VT^$cwI z^yeRPZY3{vT)k1miCp?`g?s)rVm8F3nbm+SN5=r@7IyFS6<7a4JWdgYUOan0m! z6>b8CX;8eyh!e1TwN6@OK&&Z%7?ck&$ngNg9O!vN>!hUyWYx6R$VMEk)51b349_Nr zKCFtl8c12|DQgko8=HWk;wv$A0hAp)1T9$gW+>>O{?HFygu-r~(OVZFwS>GA3dj|U zsD1A@3ir-s0>dY^R-NC9NGn+UBlh!i{3{8bRxgvo{P|%->Y;`Tp^Aqq#vvfYNQ=Pg zXKMASh<-MdNpqPyX%(7+S~8jSEI;J*=c=My@^XA@*10S}MRrdY92L8q#S9YI$uqQw zWIvB864EnB&fMJFE9H*Lu;+7yGikoaO!qbhB2VgcKWoGDA43ux!9fqU8u&tx4O(8lv zBxPplYlw>76}LTf&fNP1y);Nss>rFs|O9@*42K- zAceYm)F4H=nt(r|u699(bae_ko30K*$Ls1Sbaq`m37tb%UxO}RSD%HhKv!p=Tc@iT z=yZ6?VGuA$cvnSCI$X;jW*zj*AQl=J4U(tBy8(k(snr@Wj#iT^#(V$mzgD=-AFjr; zPZRGySXz$vLPSu1`zwWecM0?!WKGmckQY(?eF=dyd8t^lua=0q17uB_BJh_qg+QH2 zbJem&gZ@&Tg`~BX(lioytwfsCEX~1H8q#zW^pG_6Gnu%JG$$BPdeYLxEGs^!a5fhq zO@mC9ZYoDNnxi|pMn{^Tg)!LaH3oE*w9LSiKw2}*vH^fDk9)x+sF7#spiHK83pu*^ z6}kx%Eu{O-!A?**ciO4X15Ks{H)Z+)PptxW+74@J4kS!=I#Lsr+8mdf*rCl?Qxkq| zE-y9V)#faz2}zqXrzQk#&Xk%!+8nE0NDWtN7uKbQcW4(1Qp0}jLVjx4t6gxUh9&KS zJvA(77kF(JrG}6;YfB|7wOKKh+@Z}DrILPawlJ0SYP0TCQqpGEr;>s;>(XX~R03%; z&QxWkHnTBRxkH;NOI7-{nGLB*uQpSfs+6>ul2oOj%@k`dOQ}kvw@R!w;{m&f2+h_7 zg}d@W4D=~r14D$^u$tNO@ZK=MwEAybpa$By1v;=It!#n$QMYW{F1w?l%|G((>JEm| zr{y3jxAga)mfw)Kg-`Vdf88H>x<7hbeYDu=+-3`-@*wgCk+R84Kgo2>rf}HD4+u^t z8!*NoY`u6_Gtrw?`ur{QRy&zDRjO;KL`0#TNdiZ`77fi*H!4){KH4PkZ#Z1Q^OZ{02v^M8Oo BH1Gfb literal 7251 zcmc&&4R934m7bZM-R|9t#8C;M({VFVkT7e_ z4&qM|(t=V~x>$}@id_c+69JJ!Ir#MFq_|72kiogbv8#}fuVOp#K~<7E-H&tJ<#LvD zPAmh4d%dd_U}MUr%H=Kf_NwP~_v`QV``&v~+wfIE*ib5DJ|Yrc2nlC|D!J#cF(1i; z#-FW#b}zIlXm!vUp*`H(+PY~=b4T;0hj;AUbN#>89nD*=|JR<}n`lXNY-;b=(bn9O zXiqe^ZfZ+(>~7oM4x`C!+je&}Z*EOw7UClda-GesyAzu_+M2hwKayx`OKfqg(omkv z*BvVIY)R~F-Lc1W^x)^Ep|VC{m(VFZDO?lo7T1V>E_RFeNiEX%r1MhP^{=kg@_)($ z?mzYDxEG(t-}bKbz3O|CoFuBULD{YRlk%n#^1tpc&O4O%M&2Lt9?YN5KT5x-{!B%I zwm_udD+L{ehYHUZzF6cc{;gJ|FVXAvS$=E}sktRe1sYX(pkS<8b zjXYHLdZ_0&AbZB7m1fZ@vp8ZGRRz@9x?&ks$vEVpc;_{Qek8~k=5BU1@vn{it5%RY zVgJmh$Fyk^vIWW3WIi~am;E?CK}=H!)szKT##MwYFEpbX=D zmAo@o^N}owHoz##URj{<$X11`CY=|}qB+qlz9`y7f^^d<0|PM2Hi%!n|2{_A6)?gti@R z{>U$cL?{9ME5febN$6h|nnP^~e`HDsCEG*($V)=Koovq@bc=_U{`(b#&JwsIqLkuC z0t;^e``){@^)G(;{eBl(`!^$h`Iryqul?3fo;vU($zXNA_9tl~wN!zLuL-5SN#m^DtVt@A?ZNn~vm#L`I&m)WwhSPBmv zT`V2*@;ly#5c-mw^QAHa)W^hfC|EaUjaFG>wbuB?;qKwbhYt^rE7M3hg_RU`(jny} zN_K@Z4szaSw$2GAY-T2PQ5pdwa)nI?iyj(!^)f<#@8gJAt%8_1>t(^EELg^ZOIWa! z1xr|vv0#t|br#fEu$TplSg?=<3s^9~f+`DA7R+bCJQnn`pu&QL1$`_Ci(nQ+Ea+iD zHw(%v=sG5_ptOet#TNz3o)@gGbAs}1(uuEJyLOG=1q;+M1~O-F7;TtjUX1V(Usmgj zf^=5Ss68imXt)eima;0_a{@3I)0T#qR>rg?OeUiPqMgaL;Yg+T0{oj8sh4kKl^4C3i_n z4~z9@y~+ftPkHmbL#T22kl46I9xc~w@UtW4RxC>0 zlHCI&b4^BOD>&fhIibd7IY1X9xll#}UBb1DJ8&wP7nJ9`7R*#m`K+zFs7&I<<&&Vp z3239xj)`{3Y~b{4-QSv%x_OG4&tdbVbV{<1Wws$98P#}rD3gVV`}gngkQVvfI|#k% zfsn>SIS+Mc?PZ8DK7jeTofGf=z;kCX@ABkgi48ke!PWB66@LYLB|IOKqE>ruLs6ey z2}=e%pFBnV1E;7__!75&G>UpcKzNV)Rvfi*?;jERy8C7V_%u1{HTnGj1<(hesX#vzaL)&l%7bw~kd)F6 zl(ZWt2{%(<51|WE z))}j;(U>(+YdyPhc<*rk@Z|7MfdG%zkC2K-ODfJR?Go!BMaq~`KLaVpQTKxOU-&y% z;gED2?H||&5*@H5X#Zt*_nk3G#iH_M(!$PPtb}z-Z?|(*F-TkjN`sw6m8DHi4-sdwAp$}J&n&yRbk2s+RiJdCxJalQt+6U=ycTl4KIJ4<#=OIa zAyMQR-*evN;Sl`J$8QKy7r@A`kcU3_=35B;Tp+M!#U7551;dFHru|EqUdHq#OfO}6 z3DX(VgG|?%t}(rs=|xO0WO@P9158($PMMz1^gO2fnXWLMFx|&=FViv85z{?PcQajP zx{K)&(?wqQFr2yGu^q7g26uz^&Y;fLE2}j*z=N8Ht_!`DkBYsJyJrK=Lll;Odl8 zY<~LQ`xD~&v;T|G1Pp&LA^yn0MkXQJ&8rItGkFkx{1ASWP6#*9``TzXuQ4E<=0!#( z*wAhn$`Kd*mLTwmHbeEQjJI&rJhb6+pb&dcv@gPC2ZG>*s_8%_2Ze|E@CB&oW(BkL zA|#cNaYE&o^N5XCe}~Y&U9n);uBb99u+<6<|2Q#bd>hg}UbZBT_>3di+5*K>3=cm# zJPy&I%i2ms^W;WzverBmQwEXp5HCL+C?fka#PS_v{-`P+^jDw(7c) z!3n=Wu53>a&?X^h(@+zwwwz>{Qwlt7omup{Vw7aiH{J8D6aR59F>XBO9&>4k06p9 z$FOtqWt!~Lm9t3M3xjS#WTJQohBpG3zZZa~XZx;)dKf-c{*&p>Cw z+YOQekGue#WWupX%4OQK(8)Y3l9bzocLF5k;Z94s5HBNhzWYe^e`JhgWk-@}Y9&M5teICzmc0sWZ6#~rUn?1d>ufny3pX0> zFTb;puWsZrEh%`NWF_s4Ow*!_mD~yU&}!KyD0{q?lNR98TU&dCg^LeYIFpH3$sr*l zH<^_i&B`6SE@wF>U<_7zMS#0%ZJmNg)M`B=EI0t%1o!cb9Ms2L?qXJMHYYbB z@d`S58diduxtnmxgPY7NZLah=ANLAaX$8#X84yi$E6zkEok7mTDmvqFCL(mk?M#H| zjOA>(kVt?)SXIfw(26C3W8sFsKYKIboGxS+$Y%!jtHS) zF|DnE_l2_1>i*J2?x3w-fDXK9`#l$hXL=3q3Zt+tw(ObkYNLA4SXN$*%X@l_Wn;_Z z@xWt7U0*MI?4VhG;I(u*7LQe9Rl;iZFEIbao;F^O)eYkEKsX%kN%y9E((qaA(p8y{ z5skjq)6+x4YogJpv3}4omIdO*U^EaOJia167>yoGQ#C$l*48qNvALm|ArJ{l+w4@O}=JPL;gLCwLmA&T*X$60(`pghXd^>@Vssv3{4 z7#xfit{I4|3+#^>0d@bur zYBfh;yl&8_OUD96`8VGu_8_e5Q{x8~Cd1j*)$KoMtf*UWz&b17T+6DXH-cxZuU=T% zyS{vRdG<8w`fzpl1`W%rqf$D$EIf$S{Wl!a=!q2;edr0{Wc}&10TAy`Kc0T5H+@`H z)&0F`ZppC$4$1lt7)R}o6_)c|UW(p`W){DL*J0#%K4Xj>2lQjraC-x5GS**IIUF|v z{n0R3Z6NauoU0q#y`Hu6ZP@(a^e30!i>L- diff --git a/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm index 7880647f379277d3bcb7f6aea40fc4ba52938b9c..6dbc5ca8b108c1ad04cc248b735b2d7d4f43f2a4 100644 GIT binary patch literal 2615 zcmcIle{3699e;Or{LXgTUR)>lnx@Sqa=PFY!YUJ0Prjl}w%1O9Atl*te9m0Q zm!$PO(?8Ow$d29IQmdkFR*7y(6(_3zfv0U$0wYDN8UpQ)ff#>9Xc`h4QiTvI{bjy) zX;UOB{^R8Ddw%bIzn|~#_o6R8htRMKSs#bduO?6vg>C*wGLxB>^V2Usy>{(<*9RXR zvwbP}OuQJn3hY zQ*)CqXOb4+>B-Eg^~4fi`gcP+lbE!|jtTV5^>$DGeMbZ4T#%~UESu5z8rzMc5+(v^SspNTmS zKKZ04=Iqv&TU?{=p1D!E+-j`0XMdxwx8lxQL#WbmOA`gHvv7(F;gAp&`Qg?P96-E_ z=Z@eJ#DA?VjBK$s92M=(sOb0%B$}EyqICweE`OynTfaCzDw)op8 zfPYU2GnD{i+e2q}9(xeSc9d5kd0faDLLwYhI^>on{t;=NA0e&lCaQF9qK95WJhT)$ zWX9zM$zIS?Cb)~^PN%M*#k}mbYW&^TKmFUK;&&domwS?XxhK2#RhDA;{0Ye(*-D0I z{eBGhTH(}7mWkV@dtbjF$E*#C#|%f;Sz!FS^RCYfN zLMu#Vihl_y{u0nU+D@WdE7m~uRd648du+hVT8F`Xo~4;GSVuFNgfjRZQU-s5v>LEI zERg-5+>7DoTWpH4)d;JH*;Dy2A7F^6mGu2BEuGUc zQSH?+eH9dHsR?aLjf(Pl0OMK^Gd4BRSQRMgJw(a=044h<>7isVCEb+lp@dK(QPM?8 zCna7=c2nY^q=OQXl3kSSq+|yr?UcAFaZ%!=1duQ#4occ6u~TBBgr|gKsWFz0Hg&fb zZ?;-3vzA#}-Ow>h=gODL?@}R#XCG*-{PN*iKh}r@pcA%CqeO_?|X6ck+rJ2!Jq?jL9VhsDnhiQ3naPQ3nZ^ zQ3nYpn~G!$#L^&H8oDeEot6f#rD3J`HMS^)P6=`0=ni|%o{LQ)m)oJ8q zEggUxY7t7-yrDD@gqTg_)d<8)Q=vWlpZ+U`zje=S7iO*xd<<*l9`qJVnPIGy;2;eL zPqp1)Db*|!#&5i4S_S&yQg19cf2q4Rboa08Zl*@To(2;d757HPwZR0I8Dpt%<4k+w zrqBSR-V?m8NA7LB}ITI?d+cpm~0A344VfKFmXnjoi(va9OC}|5cdAm|BM^s+LX~x9$5F zHqk-<`?o{zL>_Fw#6`ttuHQ!2&iR}NeBO9uK&f8zeR=I-Vx6Oj$7q$hh&GC+abQ4Es-r@ma-zDj;<_;9KM{`z F=-*Hb7FhrQ literal 2604 zcmcIlZEPD!8J=BlcGe$BW}P@Youg40uY+=#zX4cF}t28}Zn47TXty6Qe)>Ls~#+o}lb86O_n<%{a z;_11m<3-B{oS!P5wkGCgrcTX%*_xTLPPVCH<*wwxR0SDuiqmCzH)tybf z@D<&;pf-;f+48k`}GGNA%W>4IXrK0Lqwg<>diY^nS^$_D9B1r+=^ua-a}^3Eo8^Gkng3LcSBw@4rtP%nD=;$2MdTVLf%JD!!E$E81h*4HPr>Hz{%1aJnzrlbV>?ukMRpXF*0}BF;^$E=UWK-V9_24=R_b8- z8n_R#z3m|DwkE)Rkr&)tqKRgTCv%CnQ7-WVWY$6KgWBl(<9&=rTHJB@dYU&=d}EYv zjyKLURvT9un~@*m$h%6!Rh+6Gc?Uy8{e1Ve%tFa5j+!rwi|b&}%ukw*Eff7404B6} z-sR4uwXQJQf0WU_B%^y6?PYW~qm0pAj8aB5Mtc~IG1|>&7o$-|RYnP;os4!c+RkW% z(J-SSMuUt35@u9pw2e`T(Ey_&qXN&5^TKGP9G=Hptyaq`V_vY0GUkQS*yXXen1b=_ zL-U1{X%C3&x=%DMlfAd^Vay+TEbTO$a;EjCnAXR%JxuFm+HR&XrtM-HWtzsc9;U^Z z*3GmorbU^iGL0~;lW84HYiC-7X?m=@JxVQbrl3&md=t8hCtR>Ale4?J3K9hr8TyXRp@a<>XYockwA=lMx-!K)%TT{3xU zA0VFhb!-Z*a3@X{6<*Mt`T%jUQ%@3CcIt8BwmEg3xRO(+#0@xgLR`_QE5sE@nHQ3z zOq?cYbUIDY=x~~#(e5-sBjPkcBkVLmBjhweBgmaJS){%N&9~6wTZs7zJ;i7 zLG_XQedInLd5@3W>m%>>k=eX*Z6_v>!4+_!G*;(@)L7lwP)KFZ9m`y~88G;n6PRdF7iQ6wu2FWb=#iBGyqPw= z8Le(*zd8|CkT?+`&|wN)P)DM^K6PDHFGTk` z#*&f99L@~aNGv-1UGj`^@#?a#apC;X5K@C`v|2TWYHRy5wV_&VIUT)VWNJIVXsWiq zW_$>Cc2;V}rw66;YuzhDdoLKl9a*6ESit*_GQ*j*i&<|qU0bdKZ19oD(({iM{l)HM fVj8I{Lts6V$=24$U^;urNNfzQvEfVIQ4#$Iih!l% diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Flipper.polkavm index 1f036fa37948fa66ab9928b41b8658718bfd513b..488ee684f0c4aee5d64f8b691d048ebefdd51044 100644 GIT binary patch literal 1738 zcmbVMU2NM_6ux$x-WzutPDq@)|1|QdwtJ!V5G#W%pmNt+G$}D1DuSuamMm$>);UdQ zJC#sCD=kEWENrOT)5KD>=@V#^x}TR!;sGHc!NVjT`+zEekU%_5E3BL()2fNRFe{(! zdwlMf^L^*o(YKEv^nD9z90EpnBdCTVQsHw_k8A<#0CWLf1VjJ^;27XUJd+tt#B=fC z6XR!`ja6nmp4eDTjOLT6WNvsOH=d2Bk`u{zW;mP7O=eF|Ky>ufsmWaYcqZ8(sz)B> zosDNElf$`e{Pe`hWHy^jxV_@^Ua8JI-P4jtp2>_mEg!e~#OVXa&{;Hvt{_R+CkzPh z2?Zf9&WncYf@`yMTxxaqxL+w5HrG)76s@27k?4ma z8b`rQD+loi5{tnVDA!EUgvo=H2NfP996Oeb1$&Iz3yQr+?JBXCYICHvfNL|@z;Eif z0Q6Bs7T}+_ol+dDfPh0Mx^5B5J1M}o8DOgZU@?pfEd>;gjD zg4Cvo&|iP?pBVmK$flHY)_H3`n8`azh8XSkZ?>c}$)8~Km0NTXPK{qGp|H^;8uUL3Nk5ql}xJ=Hx^ zTPC%tr~5L@;AgX;+AYud%OSWo$SQIy*lVU>7`NqM`#n^*#Q{(36uDuO)1T~SbQhyL z8QsC?c1E``x|LDNXpm8r(Ey`{#p#bW7Ws{*)87GaQJ9Y@-@E!yRNMnco`wyJ_mpfw?l{9 z3+NdvulK$=Ok;|@U@sE8s@O|3KediNxFh5(v|d}ry!|Hde+^g!Tt&t0EBQMsiEqr? z)V!sbH;K9I+;-Ae8(gh*oh{8mLk}Lr@Vo1E`MB|3H}2q7mL&+Fa7TDehfh~{CtQT6 zq6OB_{5j3jrTLBiJ@esx=lc4~6|Il7Nu^RnyQg*XuS$Ktm7>>5#@>Xn&Fk&-cwZ-N zg_2UfQYaL(J=J#3!_yicF!+G)(%^vBHxLeo+k91s94VLkWL~~lDPPpOsu#7sa;03> wV!rSmUe+G}I@|aWqdW#Von7C~&Q`Qb6)kpQfQK*C7Xf~BcJPa?PEA7p0;=WV!2kdN literal 1727 zcmbVMU1$_n6uxu!?%X?@H9Nb>&R(z9Ou}r8BB)@g#e!Q=+_5x-!M5oPL$aE2Ct)=) zf14O6p(Neam)2EFY!Q6ekZhpP(uep%slN54Pzshlrci`Jp`}lSv~JIAv?0`&I?S25 z_sqH9J>PfEou0SfMCfZ7H7<#whdO$IGGz8c7I>P$c7p8%dl@VPW`Vh2XLE%@-#~6W z*LQY!WO{Q}7|soB&c+5Wd3kTVZ)|*cG?(|ryj-Dg)El1|y)Xve!Sm-Q#&c&1UIQrb zJdK;o6(+pC@zLCcv2)(&s5cOfvldZ9x&B zjQ32nY82kq6rno8UI{*Ij#`i(|J_!ZolHzfgc2sJc&VQ9Wd3>Xd$o2!pj}LcPJGCwOELo`n4y*R1WU{j?up zKeYy#+IenWbW_|-scwq76c@W`+!^A|lIkpTr^cL>=mLu_(P)Xr=o^I20=%q7WZ21c z@+vOQBETSwDBbZ0YlURlwt!m*kizk2@zEciP`aH^$LA18z#pJAKX?+O1W7)~C=rDq z6+ZcKy>6>NB0KpVvbArJpIk#*SSGFj)=!uyTUbUI2(`(wLD3%M4f02LXc5~(D$eVK zbxUz6{~A46`|am%Ph)!W#2?k8zo6L3x2%u#_y1LeJx>+(d{weI5>#nliX6qR*6V6L zu9kJR%!a~SfUHY)UiDLdg7RDZNWv?42KyPJ)CuhlfwE2!LO1k-(zf3Co6=vUro6hl zI9NOkI=WpSA-Agx1{EkRKtlzkflzG-LLW*_p5rNSDLMdxtWV4Re~1lB&xoCqTy4tL zMqJHzwf+|%*8d-3nMShwtG_61l^PwFb(VXbTEg6`UcM1NI;5!B1A-E8HyWm{#l;E1+ zNx@qMZxK8pcwBH*@R;Duf^QQ%DtMFN5y2I~nc!4#S@5vnMDUQ{Sa8YqxnPadcVgK4 zT`B137neBNQ20=aBsPx3uFuwEbQOMvoeN-4;ZCS<=PY^w$%EQ&G}DmkEIG@}sj1Ei zPt4TOj{8!gi0aW*>UQ1%{LjIb!EPge$6Dh4n)QW!kK1=u`wp{L@jdL{ZeTS9Jmrl< zb+S&WQxE9V#*uD};1qWgaG;LxzI6hBt&ZLBl4R9rTStp?hO*aawX*5Ojzh+=iji(V z#@c7+jC8HjG!yfNRhkp?*X{Oe-5ll{4$&N?WQxQc%{{STen3@0p diff --git a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm index 92304419dda704a2d4de38445d3362d995ca6487..585fbb392a314c15a35e7e529106738bde3be02a 100644 GIT binary patch literal 4532 zcmcInYiu0V6}~gO>pMGk%-DF>lLutH8Lyl)0&kYmq=36vo5{>N!0h0_mKJOiZ)Vpq ziD%+u@7Po_)Ros+*0Q&uwo@vlk&z$$(MUC~L{*l`a>7fZt!P6fR2T7ss#PICOPY|l zXU1_7N`4eXS9|7h@44sRbIx}jqdssCLbDaf`B50X96?1CareEm!X9x0RRRe>w*Y+$ zs1fKspcbILKs`WD0gVFfZ0qc_sinQGyRBvC-Y)CvTj$=k_N#CE(mlzYd)t#O_Jf}6 zZrRtp*KE_1W^z|cclxnpOJ{Odx0&qTZ|=D&?AmvG-M(a7XUo#yJ`j<9?6LjbZI5;) zofY=TWuXV!I`=1Ay3Mvd`*tNwGuiH`poez5?b8gICGE+s&b?O2@Z?5%sP|!X0QI8Z zp_SAflt%5O?xS<`3HmRz$5rW$xf#z6kH4h0)2b^ zROw{ty0W{d_~k#vC!^U{0YUcDt=k)A0QN+9L1p4AkmnglT*XMI)*|SL$3=BfO?9bDE=TU-|sSIL3>lqn=F4WGX?XBVS5f@T;5NGqU%kul*ST`Oh|59Q@~KxLkW}dodtqgm1vu5X01q`T0K*mr zUaJ7~&abZl16qu9!hudR?|Q{$bf}gtP+^9psf>On72kIJjWb6MxZ>M>z5jb3U`pHe z!`XAYCR|HCGj#cLZUcP)+8r{P_@C8z?)KoRPC z80ax{^|~0aW^4dN0H#ashgc$6{naaQUGwpUIQGEK5da6oPbfoDawtpE zYG|BvB$N$($e12O#?&w}CbL&ze)9=f_XbV$ghgy<*th`OJ5c>{YVQLfOKNw}N{|3` z0i}Ri>93%6E4{3CN+kmvoP`W9h8`ghR-pD$)(|e9kK-F?P|yKxgiNbsCJ}l(lnafA zW{NN2;;T%NIM6+X!7xtp`7>pt1*5ah*j;aQ@YW=RyMyGwF%b3}z-a=IJWCjfUn}u9 zOZ-g|zeeI$OZ+N{Un%jt#8*qaU*dfdUnTK3N_?fnS4cc3@i$0(xx}xK_%ewvm3UU- zy%G;{u*8>0yhq~Q67Sj}@w6uK)K8F+;v`03OTcNNOrAhY%c1chC%_!PH~|u2y_f+3g`$OX50{5DkpyM?>MFa)PnOGT!7b)kQ^-1f*f3+`JCPd zDfc~C^C7(`2hY)qO7MMpF{dAa;m>H20&pRh9CW+fMv8Ie^dgLxxh#EwW+@H7OS25M zqgpUsEI2}iQ$V&k1~i2X{Rp!2jQ%Fv;H;Ltb5ORU+P%uo=V7#=?$dL2UfJ-`ISv2K1IHl{X(ds*|!l0HQUDHFdz^LVye{v;1~e# zwm-O*8iCti{#P9T{$eJTSS~@A{j_-sCXQbu=>!=C4FLi&8B37yhyn2lNjKtDc8CQ? z6YU4d?nv=P2I7@;%Eec%S1R{j1R($!4JR*JlbICAlp%|&+_%328^tez9lLY^Qa9us z87ioi5?oLdaRvtK>}Wj)p#Bi(eV_#-IBFuWJE#eVu+`+KoQ|kmYGEnRcuM8&{0HO( z;Dx-<@W+3~@n1h9?VP!-!jw5vsWf9Z<}(bW!~8gxpW&g#1C~wMXDU@@%x^YT=d%oy zwEPU0KfxD|v&AVU56~yHDO09m#%Qz2^+gs_@t85U*;Ha~2f=pdkUzoYU+0Ujm0oTg z23@AI(v0zD(<*cO>M!z`v75}MHRkr4&24M*GYssUf1S%`_~I$M$+9!JvD4%@ z1!DTba>z!0_DLN7^1^b+{x6Nu6Bn$EQ&D74BJp*fD^Y&4di@y7D9Dj1&hWF*7zO0mV8fN}<{%gWF7*AuKIuF}B5P$Og=O8YB zANJZ~&vxk%yUSRHcm&G34ey$;V6Q9{0>Y@0pc>*)#WC$y^T4ZlY(8 zGVs|nd!VFW?@yf4nPL+6zjlh0YNaNrX6Cx^$!D0kXT0+R-np&#Y>xm-mZe7G^tp{bne1_=*_J+QpSCD<3Aupl_>fSwTPd@Mt+#sza z-a3n(?H9cp#41g$eKv4ge>9uT9um_9ad23S7DR2Tk?~d(3+G;tQEvqR(xw%y&tY>Y+WEwThqwYxVg&4qf#UiDHNK2 zJRy!WMx$CGrWK~-S|tYiXt8Kij0V1`C^L#ykZS^g^-Aq}rFjDO@s5bou*-;8TT>8~ zEB~k*Q+exO#7I2{O= literal 4394 zcmcIne{37o9e?+HzIXPW>$4j>*K43oc1e@0f`B@f4Tg+m=v|TqiNA_|6Tq zO@G*JnXojsb~;uSQ%KUyTIo{KWdC$*C8Y%^Oxp@8XlUXOnD$4sO&Vj;U}YMmU7CIG zlC*Sm+J@N4U*Fw(-}im*`~7~tKQ6X@Ekdn6WM33PuY}Mv3e$zxd256Q@&ko{ZUOo- zPy{Frv=QhLpvQp@0B!0U7_ewVf8UP24V$)X-F@wCU`t>BwYTl&p7f?I{pk(XLnggr z!}c9p2KzGU!Ss%ugO6TY|H*fYb$hyRV8i_2c35RT^2p8|eH#bT_6locLC&teft~3M zI|ln6-TrWTa4_BP^x=buwhnNRF#YMR16y`8hi+Mg4+=@N4edg2pc?89YAy8*ssr!G zFW}!|3`6wlQ2Q%AoCvc1bK%%&VGmeh;_K?T>D%=9_Bm~5u$EA@Rjrki%To~xtTk`h?JWqOOcyJ2)E>G zf&}jIJWWCfr%Nz#?P)18d>?26s|EZ!DVoqMBjE8A5fJWn_)f;-Gx1RddxJ(r!4>Mn??c z6lcJnD+MyLZ7GsKm?j0p2=2dLA>?a%rVZx`mN44YP5I!yS`4+!>&5>HIF?gJ8%-#f zWqtF*OHN<$l5bLAlLE32B#Z;!hx$O9ey)u4R|}}RyLw>dcy=VTvfMk)?jmz=0UlA^ zFafpJ<-P2)`nf~EI)bV>Ez?{rfqKbcJ{jDa3>wK`%BY=v8(WKj{8{(!?jb0YbTu3(sewgTH2&|fH8bQgXLqKJq z0y37(_(x~rPiiBgHZ16wW^I_&Gws?iK7tKU52SG+5D4g)H@^SuEFol0&$j8~ihd%Y zPlWZ!Saol;#80vOgsVCVyJX8D{zaELYVGJ{-?@T8$#zDlc^uZBzXB!)GqWYYj%{-{ zTL++FJr>3sIM?k0D70I+3f>R=-(BR-vtWMP3oN)6mu;ueNCdwazQU{~4!gWX+j3-ah)EgRFUZr~W>nylcMolZy|R9P`f7GRk%S0!0ARSw$Zg^b?{!!RnKr zK=oROjp|jkbky-FpWMLtd$dgKhR(;6mgna^W%(U~C@*M6SkJSXq3B@v*j1d?&0Qws zU94xqfC>nlYECzlYFRg9)log0NVXP`HZg*<6NivCUcQF!o6o_zldvv<^VUulAi(JM zG5i9tyWNMem7E|o;T zB>E&#kVLN}E|El!B=VBTNupa4U6KfLh$J$S=#)fS5*_O#5yvHwdIo8RAh98ESv6%T6PlnMU2TkC2A}r}KDR zwfiC=8zF;UkH@{$Cu}^vM))&}c$~V5$J^&_jK^D6fv~`wg-5eJJ^cT|;{*agz7K!Y zALbGH27*kZWoF<8>b?uNup{#USXuJ$o42c<3Z5M`q-1+4uPpw6I-ObEsYDCim}3w4y^I+(TT z*=CxX2iodsn6t_T0OJk}yZ(8WG7^hrWWgWpfBHKizn<02ggy)^fvXjLB%zOnH3&Ay zpJBT?1G1rL#SiG4!~AL1Ld1Z=zjmW?SDFPO01fC7X7M;>fJ_Cj!3bY1z((dQ7|xMN zS=kh_FH}LO+ZgH^L&2c)JakMKq2%5uZ0@=MlhA}LMDN|VfjDVa?-#f=K28xh)z5V- zHFGQ^FmqHeCq*d708LH_aB2stP49I88T&J7s;w&X#{OT@KgzNBdojljIgj2lNB$P$| zRdzv%5~=2^M?hcx3zpU^t;$z7PV9cfPU*k7ycmJukN!r;%;m)h{BL@g=P%=&T>}(G zh@6Jy&tA4;PO)R|^Dh7pZV+q|^kOm1NC>4Buz4IJDS8*|-FVqT@j@A1RO-G4nBgve zPA~@JdD)jQ!S*(ie|+gWBr7j`NXYlCSx2T|7sCZ47py)RkX*1j>F}+ky_X2$O{srH2mQQ!_au@Nho6(lC=^lsNgVOuCG)LF zL92R?Cs5}Jh@L==Cm?tNyeGhV0>CC)GJBU_@Ddtf$Z}vvx!cNqXuWgo1#5D>0yl~g z65$Kzcu{t>$~AGN@p!{(ITDSI$?8+GT9oT6@{^N7L@6JNW{*WGTK#R03b1{F42 z_(h9yBOIUsu6;_RDEJ~}rC2Wi;Mq!LECS4xY~`?A-`euCa(PUCdQ5)c$&Pr7s``hb ztN)F}q5zY&xcY`@QE2ejH~dafG}uIqM}4(wY5d{H-bH{&BiM=4HIfvR^uH#6|i{2P^GBILkEVMI)MMk;U?H@fqep| d;TcS^uB9kMDu*Kxwm$6lE8vBGwQ*k&{S)65e#-y= diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm index b29c640a2fee562cca818f8289bcca2cbe285fa9..3f96fdfc21d8d8fc5bf68a34f0c9f7b055afa2a4 100644 GIT binary patch literal 5062 zcmcIne{37qeZM=Ax_3v(KFX5r&Ze!4Mys@11Y~Hebe6fSz&zgx@j!CvMr2tn*_4m6 zm5Wc7&396Vshu)XV$I2%3d_oX8LbTK2he2z4a-RYF)e5PaEqmX6c~yD6Hqi*w_!=U zt#N+X+V_rOB(rC}P zK$<`lkS9S7gM10(Igl4Y20*eP;~-NYr+NnlY&ntc9qK)C>g99R&eOolz3H8&^Zmnp z_EX=W-ZymO{LssTy?WnZ-{})W{V(;M80b4aG}t$EVbIvo^y|;|?(==U11D;e=RrmP zOD|m*>ODEo=j^a&wv=A%9k|eUVra0}IDfitaIi1!_A--y?6MD^{Azu=@7%!4R(&|wPf_&r#F6qevT|+1*^TGF2orpmeTq7Q zD2kaO&A-pWm?mLXp_pDGX-R}3}2wbY#7A_=EhMLQ4C*4A7urK`8~Hn z)g|0)TySx5q5d7Z;8iwF;h!k8w8`Q9OBi$NiqhX}P1$25d!G4zxBhd0h zUCRE7+g~chGy@XnGiqjnN7){VDTo_@+0-LxQxhdk6f_ZOtd->(XmTb(ZU|&fBJ+qW zRHjko23yIoNfy(V9_3v`SF@Qo>5qo8mxh&4zL-}kbI8m@idiKzeQ8{&TwzT;0Mlkh z(OQYtdQNN2XstbT?a)U~Ob?s7R1oh0m`CO*+FGzitV~as+YGb3n`ESmxn`1=E@c(c zZy%v`OjBCNBuE})l%fmr#_-jR^ks8eGOq|k513aF(Ie&+YsSiqQnn(G0=hg}#}e## z4P*}F8ubaVtYIs2?bo)1Sx^8@;+Y2twwvfdD?3Vo^};C3@ict|HB(FiHG(UsIxr#+ zKCNwN_|g3YD~DN3 z#-G0ZJt}eRPwrm7^rO1uvHX8r{r=b7+pJI@@x>M^)K6wbmz(IRZ1xXD*AchYkz%xt zIEV@o-D24z%4~#R0|wc0d5YvDFfz#rrXD3ZWa=u(!S=v~`oyu<;0ABGlE;q! z+1@9==lU$j^FU<>x&O~ymTvpY#WO^owjE3KNf@F;&%>Y+cnjzLCUHKE(8^=(S7VjU zXk8%$!X`+FnF(CX!W|V$uTZ>;Ci*HxG8HP@|27rM7VRLrR8T6f^FaR4)#6o~{t#Xe z2g}ol^aqx&*a1p1!R0yRMCy0zw2phg?e{?b6=dU+IBt6)_!y)c4*vlu;0cl;iCAP< zAcJ;HEf0CBLk;9qwEDCi>UVB6KvGy9q^g5}B4zdmtK-}qj$M8lRhH15Ttl)!c9Bf< z<^;M)_$slZ;pP}3X9G8vUj)U*)t8iPky<5Cj6Ty>Daqjg9o* zn@f`Ux?tWy=56bBD?>S4sYSJ=D>U!Q>OmT=1;<07(D_g|CNaa3fqK08X{y zJC^88NGSAX0d|}Eo1iFe>J?a8g~wI8ppU}%14yCQXdbx4BRa|cyUoBP`?GquF9eJL zf`#Oqa=EVva(RF6EW7? ziZelD{XlUhV65*i&M3yZuQ($a>-&l`g0a50coi9Ivau=@$0Np?SR4-;YpuocfU(w6 z99N9B=Hj?ytnDd|3&vVgF^`N@ud#xPBx0=YE|Q?Jx~oV6#%d!JMspx#tTX`p^sO)z z2;6?(vH4d-S5u8#zWMi)?9W+%~dwKB{lI0Fn4 zh8E|*;;#~H{U*u8HA&JWL6eY%{y-fu!>r5cZIlUE@UsVDyN~hGux) zAoFC(9JD)tQOAVa+AYlud#|8F7I}@yvyNoqr>F}hsz#^%e=#7&RB7% z%sN!Acc|xyhr;`3$TnPNje<#?d?`JcqFlV=09FGES z$aau_w@ujjCM*%}J0^^hIhD+(?BE{^{YB{2&;qchkZY^v5cGrv>q}LOh2|MLyYdaT ze48`RM(z4?8-FClAe@n zIlxr|{K|}L`57M4%gPk=vt2GT1K0)glw_U+s5Sq7p$)(de@t{_`$e$*60rRe1Y~F9 z=Ibf*cHCT2!DVDXs;mOXBjk(%I=2$m+E&6cZ$*>nsgJX|trkjBTzK*y{wu*Mpg9hu z8t&xeD}zW_6tUQf#TG0!V{s1_o3QA`qJYKSSlorhMl3d9(St=EiyRga7FjITW6_O8 z7Z&TV$Y7DitnFB<&33GHz;>*)-*&9!vmI;gvmI;gwH-@j@CM01`yc`2W^QA1bJPBF zK+Z&~GaPjP6Y~?};H?BV^br(ANM1?y4E%zgSIK6eNJh*|G%f_+|7C(r!aUq2vf$k2 zTxNr|K%moQq0QWYQ=&7uwhscGA8lLrskdO4@1yPLiIVv^Sx7Hnzy;0oz}~B=~X*8&L2658IoM!M1tN!4`9{rQ*WVfAwC1{Wq8dY^j>vJ_EEg zgi#;F(EtQeKSWY5gwkndG9adUjS&c{=ffxsc;dn{2wE|p`>C$LuimvwR@X0J zpyNX9=kHS2$NXHUzd5NMyxx9zESAsbr^e6|V^mXVYIeqdaC~ZZ%70KR#ge7&WNB`; zuvCltT_}bxwL!_>`LbSm-1?N$={+K@A7M%hqR`H zPn2e-w!uEsJ6rqJ<^Rg@rgkj}*LTjG_a6j1`n9fyTm0PR9YBr=w7+&u&NgYV$gjmN z#ccQs_Vy$w+5tX>G)7H!x5FyX_i!HOI!pf8mep$WqyFpmvaLn2Sj_MBUiLpxI{b=S NQaG=FzSPx4{V!`iF+czS literal 5012 zcmcIneQX=ab>CerIkWsQhqR>K9nvTPOLBl5lBNlQpiyzSAWZ_q z=~sLow)$o%ozKCYfgD-_Z)bOQ=Iw9Z`@Ii#pL$vl=5`D2Pej5u4dIrMlqc5%d`1R| zfEXZ0K#qcZ8syJFPJ=igUjV6q^!E)8ayfmrZ?x}p|Fc8m51$5~?K}JMX{2fFSg!96J@Id^i^jYaI zr3vYuJ^$u8f}TO2!cQrA<;%)1l!HF(|A>Fh-yXOd=m?Gkja{$q%7uOwdNV{xlB7v? zeAfjcqmb`2vQH4ik4}H0Fps6m{>a$oCqE7oJ{j#6d{_{qL*?dH1Y@==BSVl16?pOR zZnt2E?^*;~Es^I%8Ohj7q-(v12vS#5xEE=HggnSgWW`IoKNsz=v1O4T3s%c(g4Mbv z*e#nvB)pkzl~Gd0hL_-hErh-<$`}-!Y3k*d9KUo6(s=;k{w6Iu-ivHOveiw1YZVjO z=kltSS4m#Qc_fW>`NPafuxo@ZX>1v@mB2g>Ttk5h3ZXw0rDC#6X2rBU_2rOaCPB>#meZDkh1TG<~7=vlcHuA0IeCtrY)y}u}ij9vx^2O{0tEk!PJpl8_ zydX*|;2gVGR<>l+I6u2V40lIwoj#b$4GRP9h72$ngNf#vh%6qoR2o!*SeCPo}Cs;8q zjZF$*JsHbNml*cpC^!oTxo?}mh)Q^8L*QTTBc#iS*$G&QVV^v zATAq^IbVZNoXkaol_6r{tZdn_{xLYCAr)^4U2>Qe*92zY62#JLLVe8P@l|@!2)yD0 z-q$ZXmpStxvLFPa^O%)l(MvownH`TVVK+z@n(`g@fYx_GegU%ie)Q&^h(8ADrb~T5 z3V7mdRAb}p0%5~EoT8(Cb2PyEljf5=%1_=1Kt6~L3+6DO$XTVhIi;LbQ1nS0Sj8tR zfl$CBGdnpnjfcuUv&;iw=ptqVv7uF%0gdUQVKj6Ju~Lo=#D`u|@*SF(?;s!;1Y%^* zX5<^2*?+KBHR}~(-N4pO=@rQqAZ~U7S}GbM&r30p*@h^V;vzUswCtqVRO%La4k(sk zNLs}q7}8eJh9PGad$+T0^B=v1&^f6Qo2@21^hv>vNio=QMZ|V73F;OB%gf?cuwzx+ zg1jMa*|6Ixz6y%Et>P_M`W<*&6Yb(8jNgS!c}463-gKdG2>lAMRcI8^sFy}PG%C|*6OBqVD$;UUJPS#tUsTf&rai$Ei-t0_i zWPP_YMaX)@sbaDgCbzLuPLQ>bQ;w6hT~0Yh)`C#~tic?)9RT>*8?q4d{OEVC&5x>b zq(QFe?casazaxmtG8ldc#0Kg8plq3bOpNwEv_GUqNS2T$A(24J1llz+<_=Bbk1%XI zr^nef%$C@)##RU$NEasj>`bokf@Hpc3orVk69IDqom@j~Fj3gRtT$Ou6m!W}SXK&U z*}RAf<4xd*=qfT_gglfpYf4X9F~5VNacnmEPF8%;&+*LEb3sAMIWUL}oy@-V|3S#R z#q939rsXw~*Ki(pr6-;P=d=cp&q3B^-uzO%)@kMT43a)S81D32K3$hJzZc zHo3y~GOYi{2hfhVb<~6^3T**cbOxjfGWo7$t^R~HqFHB?)+n(C($)mF&g86NUg#%X zOE!7~D9}k4in$k5TMzya=05RYo4Ii>>G;?GMrh9i&fGKc!a-nef-^VCnVYU(W=^iY zLN+w#dV<{9?_7_QJNumLF>+_GbKM|!qRw@V+}Yz?C*%%wRxsJn$vSblth!uob-CQ) za=F>%@@|*Q5tqwhvW8tQhg>f2a=9FIxg2o0?032BbGfXL+b)%nOJ%Q1Wsg%f$eQew zHL}*^lnGgroH6W@8~RHld(cZ__x}r_C+~}PPF8qVg}ac)1KAET?~Vx%{{)t(_gxbv z*;1M<=XmfB*Z-#eQhf#3V^{W@6%4JPJ^pEP96@^w4P46wGJ zwNtiiz)q}w%{l{68~%ObPXOG7cUTc~zYy-1823vYkPUQOujH(o-PURvT*d~qz#4Eo z!5jm0ZYQku?Sy6BNQUso-VuvjEmT!6Ir92{B4mK(ZYaxeCO2P^vPM;vs;yLQp=vW# zcT+V&)i6~FRYO$WMb#ix161`>)kjr@s+g*Xs$Qyks47#niK-G+MT)p%$M1+ z9pB3xJ09hZ9pA$pJ5IS{%Q|=i>LDv>0QtPKxwW;${{t{5X)Y+xe)Jx(maGEU4!)X%OJx!CnDM}#F(0ZYz+=Qx|JXG2Lrswe6 zJJ$W!&;A3U=-WHdlVr=?Y$XSIzgoXkUxow)x*qg35P zRZ3Nzs{c21IFEn2gO7J!JQJUqhYWLSo+5-_deg;b?qEw~0NWQ~5`4Lh4XF43hwatJ zVB5OqVoSN$a$fS}*X|6uK*1y=W?j&E{Sv0T0wPx08J+)fuz*o#n>f)lA`D`{E&SVbPraL3cU%W{FcFt=1yYE-4 z=}eV$b_#86iyekxWHKw6Yz?=yw;#$JIF#x6+<{E3HaTe?SnkMV+ApSO(`F>x)_AvA zn=@(nstHTXscLo6{K}&F=d%lQCOq@cyJISqtxYd}wOXyF+Gm$YOM53kZ4ZaTKS-xi zpw>JvH4SKwWllueQmOQbEZnp#o0&|fnK^tY6FJaUTb$#ncGNbMXR?v6c7Cb)p7pi4 z#dkmLu303Rh}rf6zi+D@2u}sy>{KfVQ zIB1%mN15H(GHa$D0?N9}o{l7IC(Kz`WQLDLIQ$hp59W34&`=d=Hx(muXo|1eR?gqT q+;X}`GVSjHB-G}n`Lf0o7;H9`O4Tl=zu(-RdBIH0ES~TwqVT^DJ~9UY diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml index 1af5b327dfc7..8274bf36204b 100644 --- a/substrate/frame/revive/uapi/Cargo.toml +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -21,7 +21,7 @@ codec = { features = [ ], optional = true, workspace = true } [target.'cfg(target_arch = "riscv64")'.dependencies] -polkavm-derive = { version = "0.17.0" } +polkavm-derive = { version = "0.18.0" } [package.metadata.docs.rs] default-target = ["riscv64imac-unknown-none-elf"] From 91bef33b3948dfce988a495ecd05066d4816ce61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 18 Dec 2024 22:39:47 +0100 Subject: [PATCH 191/340] pallet-revive: Fix docs.rs (#6896) - Fixed failing docs.rs build for `pallet-revive-uapi` by fixing a wring attribute in the manifest (we were using `default-target` instead of `targets`) - Removed the macros defining host functions because the cfg attributes introduced in #6866 won't work on them - Added an docs.rs specific attribute so that the `unstable-hostfn` feature tag will show up on the functions that are guarded behind it. --------- Co-authored-by: command-bot <> --- Cargo.lock | 1 + prdoc/pr_6896.prdoc | 16 ++ substrate/frame/contracts/uapi/Cargo.toml | 2 +- .../frame/revive/fixtures/build/_Cargo.toml | 2 +- substrate/frame/revive/proc-macro/src/lib.rs | 11 ++ substrate/frame/revive/uapi/Cargo.toml | 6 +- substrate/frame/revive/uapi/src/host.rs | 123 ++++++++++------ .../frame/revive/uapi/src/host/riscv64.rs | 138 +++++++++--------- substrate/frame/revive/uapi/src/lib.rs | 1 + 9 files changed, 183 insertions(+), 117 deletions(-) create mode 100644 prdoc/pr_6896.prdoc diff --git a/Cargo.lock b/Cargo.lock index 9ae662452539..726c8f5a1885 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15047,6 +15047,7 @@ name = "pallet-revive-uapi" version = "0.1.0" dependencies = [ "bitflags 1.3.2", + "pallet-revive-proc-macro 0.1.0", "parity-scale-codec", "paste", "polkavm-derive 0.18.0", diff --git a/prdoc/pr_6896.prdoc b/prdoc/pr_6896.prdoc new file mode 100644 index 000000000000..a56e4303d9af --- /dev/null +++ b/prdoc/pr_6896.prdoc @@ -0,0 +1,16 @@ +title: 'pallet-revive: Fix docs.rs' +doc: +- audience: Runtime Dev + description: |- + - Fixed failing docs.rs build for `pallet-revive-uapi` by fixing a writing attribute in the manifest (we were using `default-target` instead of `targets`) + - Removed the macros defining host functions because the cfg attributes introduced in #6866 won't work on them + - Added an docs.rs specific attribute so that the `unstable-hostfn` feature tag will show up on the functions that are guarded behind it. +crates: +- name: pallet-contracts-uapi + bump: major +- name: pallet-revive-uapi + bump: major +- name: pallet-revive-fixtures + bump: major +- name: pallet-revive-proc-macro + bump: major diff --git a/substrate/frame/contracts/uapi/Cargo.toml b/substrate/frame/contracts/uapi/Cargo.toml index 09c70c287899..45f8c2cb84af 100644 --- a/substrate/frame/contracts/uapi/Cargo.toml +++ b/substrate/frame/contracts/uapi/Cargo.toml @@ -21,7 +21,7 @@ codec = { features = [ ], optional = true, workspace = true } [package.metadata.docs.rs] -default-target = ["wasm32-unknown-unknown"] +targets = ["wasm32-unknown-unknown"] [features] default = ["scale"] diff --git a/substrate/frame/revive/fixtures/build/_Cargo.toml b/substrate/frame/revive/fixtures/build/_Cargo.toml index 5d1c922f9002..bfb9aaedd6f5 100644 --- a/substrate/frame/revive/fixtures/build/_Cargo.toml +++ b/substrate/frame/revive/fixtures/build/_Cargo.toml @@ -12,7 +12,7 @@ edition = "2021" # All paths are injected dynamically by the build script. [dependencies] -uapi = { package = 'pallet-revive-uapi', path = "", features = ["unstable-api"], default-features = false } +uapi = { package = 'pallet-revive-uapi', path = "", features = ["unstable-hostfn"], default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } polkavm-derive = { version = "0.18.0" } diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index ed1798e5b689..b6ea1a06d94e 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -25,6 +25,17 @@ use proc_macro2::{Literal, Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens}; use syn::{parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma, FnArg, Ident}; +#[proc_macro_attribute] +pub fn unstable_hostfn(_attr: TokenStream, item: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(item as syn::Item); + let expanded = quote! { + #[cfg(feature = "unstable-hostfn")] + #[cfg_attr(docsrs, doc(cfg(feature = "unstable-hostfn")))] + #input + }; + expanded.into() +} + /// Defines a host functions set that can be imported by contract wasm code. /// /// **NB**: Be advised that all functions defined by this macro diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml index 8274bf36204b..948c2c6e4f83 100644 --- a/substrate/frame/revive/uapi/Cargo.toml +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -19,14 +19,16 @@ codec = { features = [ "derive", "max-encoded-len", ], optional = true, workspace = true } +pallet-revive-proc-macro = { workspace = true } [target.'cfg(target_arch = "riscv64")'.dependencies] polkavm-derive = { version = "0.18.0" } [package.metadata.docs.rs] -default-target = ["riscv64imac-unknown-none-elf"] +features = ["unstable-hostfn"] +targets = ["riscv64imac-unknown-none-elf"] [features] default = ["scale"] scale = ["dep:codec", "scale-info"] -unstable-api = [] +unstable-hostfn = [] diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 16d6f0945427..476e3a26817a 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -12,27 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::{CallFlags, Result, ReturnFlags, StorageFlags}; -use paste::paste; +use pallet_revive_proc_macro::unstable_hostfn; #[cfg(target_arch = "riscv64")] mod riscv64; -macro_rules! hash_fn { - ( $name:ident, $bytes:literal ) => { - paste! { - #[doc = "Computes the " $name " " $bytes "-bit hash on the given input buffer."] - #[doc = "\n# Notes\n"] - #[doc = "- The `input` and `output` buffer may overlap."] - #[doc = "- The output buffer is expected to hold at least " $bytes " bits."] - #[doc = "- It is the callers responsibility to provide an output buffer that is large enough to hold the expected amount of bytes returned by the hash function."] - #[doc = "\n# Parameters\n"] - #[doc = "- `input`: The input data buffer."] - #[doc = "- `output`: The output buffer to write the hash result to."] - fn [](input: &[u8], output: &mut [u8; $bytes]); - } - }; -} - /// Implements [`HostFn`] when compiled on supported architectures (RISC-V). pub enum HostFnImpl {} @@ -238,7 +222,18 @@ pub trait HostFn: private::Sealed { /// [KeyNotFound][`crate::ReturnErrorCode::KeyNotFound] fn get_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result; - hash_fn!(keccak_256, 32); + /// Computes the keccak_256 32-bit hash on the given input buffer. + /// + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 32 bits. + /// - It is the callers responsibility to provide an output buffer that is large enough to hold + /// the expected amount of bytes returned by the hash function. + /// + /// # Parameters + /// + /// - `input`: The input data buffer. + /// - `output`: The output buffer to write the hash result to. + fn hash_keccak_256(input: &[u8], output: &mut [u8; 32]); /// Stores the input data passed by the caller into the supplied `output` buffer, /// starting from the given input data `offset`. @@ -400,7 +395,7 @@ pub trait HostFn: private::Sealed { /// # Parameters /// /// - `output`: A reference to the output data buffer to write the block number. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn block_number(output: &mut [u8; 32]); /// Stores the block hash of the given block number into the supplied buffer. @@ -409,7 +404,7 @@ pub trait HostFn: private::Sealed { /// /// - `block_number`: A reference to the block number buffer. /// - `output`: A reference to the output data buffer to write the block number. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn block_hash(block_number: &[u8; 32], output: &mut [u8; 32]); /// Call into the chain extension provided by the chain if any. @@ -434,7 +429,7 @@ pub trait HostFn: private::Sealed { /// # Return /// /// The chain extension returned value, if executed successfully. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn call_chain_extension(func_id: u32, input: &[u8], output: Option<&mut &mut [u8]>) -> u32; /// Call some dispatchable of the runtime. @@ -461,7 +456,7 @@ pub trait HostFn: private::Sealed { /// - Provide functionality **exclusively** to contracts. /// - Provide custom weights. /// - Avoid the need to keep the `Call` data structure stable. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn call_runtime(call: &[u8]) -> Result; /// Checks whether the caller of the current contract is the origin of the whole call stack. @@ -474,7 +469,7 @@ pub trait HostFn: private::Sealed { /// /// A return value of `true` indicates that this contract is being called by a plain account /// and `false` indicates that the caller is another contract. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn caller_is_origin() -> bool; /// Checks whether the caller of the current contract is root. @@ -484,7 +479,7 @@ pub trait HostFn: private::Sealed { /// /// A return value of `true` indicates that this contract is being called by a root origin, /// and `false` indicates that the caller is a signed origin. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn caller_is_root() -> u32; /// Clear the value at the given key in the contract storage. @@ -496,7 +491,7 @@ pub trait HostFn: private::Sealed { /// # Return /// /// Returns the size of the pre-existing value at the specified key if any. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option; /// Checks whether there is a value stored under the given key. @@ -509,7 +504,7 @@ pub trait HostFn: private::Sealed { /// # Return /// /// Returns the size of the pre-existing value at the specified key if any. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option; /// Emit a custom debug message. @@ -529,7 +524,7 @@ pub trait HostFn: private::Sealed { /// not being executed as an RPC. For example, they could allow users to disable logging /// through compile time flags (cargo features) for on-chain deployment. Additionally, the /// return value of this function can be cached in order to prevent further calls at runtime. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn debug_message(str: &[u8]) -> Result; /// Recovers the ECDSA public key from the given message hash and signature. @@ -546,7 +541,7 @@ pub trait HostFn: private::Sealed { /// # Errors /// /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn ecdsa_recover( signature: &[u8; 65], message_hash: &[u8; 32], @@ -564,15 +559,49 @@ pub trait HostFn: private::Sealed { /// # Errors /// /// - [EcdsaRecoveryFailed][`crate::ReturnErrorCode::EcdsaRecoveryFailed] - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result; - #[cfg(feature = "unstable-api")] - hash_fn!(sha2_256, 32); - #[cfg(feature = "unstable-api")] - hash_fn!(blake2_256, 32); - #[cfg(feature = "unstable-api")] - hash_fn!(blake2_128, 16); + /// Computes the sha2_256 32-bit hash on the given input buffer. + /// + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 32 bits. + /// - It is the callers responsibility to provide an output buffer that is large enough to hold + /// the expected amount of bytes returned by the hash function. + /// + /// # Parameters + /// + /// - `input`: The input data buffer. + /// - `output`: The output buffer to write the hash result to. + #[unstable_hostfn] + fn hash_sha2_256(input: &[u8], output: &mut [u8; 32]); + + /// Computes the blake2_256 32-bit hash on the given input buffer. + /// + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 32 bits. + /// - It is the callers responsibility to provide an output buffer that is large enough to hold + /// the expected amount of bytes returned by the hash function. + /// + /// # Parameters + /// */ + /// - `input`: The input data buffer. + /// - `output`: The output buffer to write the hash result to. + #[unstable_hostfn] + fn hash_blake2_256(input: &[u8], output: &mut [u8; 32]); + + /// Computes the blake2_128 16-bit hash on the given input buffer. + /// + /// - The `input` and `output` buffer may overlap. + /// - The output buffer is expected to hold at least 16 bits. + /// - It is the callers responsibility to provide an output buffer that is large enough to hold + /// the expected amount of bytes returned by the hash function. + /// # Parameters + /// + /// - `input`: The input data buffer. + /// - `output`: The output buffer to write the hash result to. + #[unstable_hostfn] + fn hash_blake2_128(input: &[u8], output: &mut [u8; 16]); /// Checks whether a specified address belongs to a contract. /// @@ -583,7 +612,7 @@ pub trait HostFn: private::Sealed { /// # Return /// /// Returns `true` if the address belongs to a contract. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn is_contract(address: &[u8; 20]) -> bool; /// Lock a new delegate dependency to the contract. @@ -595,7 +624,7 @@ pub trait HostFn: private::Sealed { /// /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps /// otherwise. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn lock_delegate_dependency(code_hash: &[u8; 32]); /// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. @@ -603,7 +632,7 @@ pub trait HostFn: private::Sealed { /// # Parameters /// /// - `output`: A reference to the output data buffer to write the minimum balance. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn minimum_balance(output: &mut [u8; 32]); /// Retrieve the code hash of the currently executing contract. @@ -611,7 +640,7 @@ pub trait HostFn: private::Sealed { /// # Parameters /// /// - `output`: A reference to the output data buffer to write the code hash. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn own_code_hash(output: &mut [u8; 32]); /// Replace the contract code at the specified address with new code. @@ -642,7 +671,7 @@ pub trait HostFn: private::Sealed { /// # Panics /// /// Panics if there is no code on-chain with the specified hash. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn set_code_hash(code_hash: &[u8; 32]); /// Verify a sr25519 signature @@ -655,7 +684,7 @@ pub trait HostFn: private::Sealed { /// # Errors /// /// - [Sr25519VerifyFailed][`crate::ReturnErrorCode::Sr25519VerifyFailed] - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result; /// Retrieve and remove the value under the given key from storage. @@ -667,7 +696,7 @@ pub trait HostFn: private::Sealed { /// # Errors /// /// [KeyNotFound][`crate::ReturnErrorCode::KeyNotFound] - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn take_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result; /// Remove the calling account and transfer remaining **free** balance. @@ -685,7 +714,7 @@ pub trait HostFn: private::Sealed { /// - The contract is live i.e is already on the call stack. /// - Failed to send the balance to the beneficiary. /// - The deletion queue is full. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn terminate(beneficiary: &[u8; 20]) -> !; /// Removes the delegate dependency from the contract. @@ -696,7 +725,7 @@ pub trait HostFn: private::Sealed { /// /// - `code_hash`: The code hash of the dependency. Should be decodable as an `T::Hash`. Traps /// otherwise. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn unlock_delegate_dependency(code_hash: &[u8; 32]); /// Stores the amount of weight left into the supplied buffer. @@ -707,7 +736,7 @@ pub trait HostFn: private::Sealed { /// # Parameters /// /// - `output`: A reference to the output data buffer to write the weight left. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn weight_left(output: &mut &mut [u8]); /// Execute an XCM program locally, using the contract's address as the origin. @@ -724,7 +753,7 @@ pub trait HostFn: private::Sealed { /// /// Returns `Error::Success` when the XCM execution attempt is successful. When the XCM /// execution fails, `ReturnCode::XcmExecutionFailed` is returned - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn xcm_execute(msg: &[u8]) -> Result; /// Send an XCM program from the contract to the specified destination. @@ -742,7 +771,7 @@ pub trait HostFn: private::Sealed { /// /// Returns `ReturnCode::Success` when the message was successfully sent. When the XCM /// execution fails, `ReturnErrorCode::XcmSendFailed` is returned. - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result; } diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 9f080ddbfbf5..8376263b2348 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -18,6 +18,7 @@ use crate::{ host::{CallFlags, HostFn, HostFnImpl, Result, StorageFlags}, ReturnFlags, }; +use pallet_revive_proc_macro::unstable_hostfn; mod sys { use crate::ReturnCode; @@ -137,38 +138,6 @@ mod sys { } } -/// A macro to implement all Host functions with a signature of `fn(&mut [u8; n])`. -macro_rules! impl_wrapper_for { - (@impl_fn $name:ident, $n: literal) => { - fn $name(output: &mut [u8; $n]) { - unsafe { sys::$name(output.as_mut_ptr()) } - } - }; - - () => {}; - - ([u8; $n: literal] => $($name:ident),*; $($tail:tt)*) => { - $(impl_wrapper_for!(@impl_fn $name, $n);)* - impl_wrapper_for!($($tail)*); - }; -} - -macro_rules! impl_hash_fn { - ( $name:ident, $bytes_result:literal ) => { - paste::item! { - fn [](input: &[u8], output: &mut [u8; $bytes_result]) { - unsafe { - sys::[]( - input.as_ptr(), - input.len() as u32, - output.as_mut_ptr(), - ) - } - } - } - }; -} - #[inline(always)] fn extract_from_slice(output: &mut &mut [u8], new_len: usize) { debug_assert!(new_len <= output.len()); @@ -400,21 +369,45 @@ impl HostFn for HostFnImpl { panic!("seal_return does not return"); } - impl_wrapper_for! { - [u8; 32] => balance, value_transferred, now, chain_id; - [u8; 20] => address, caller, origin; + fn balance(output: &mut [u8; 32]) { + unsafe { sys::balance(output.as_mut_ptr()) } + } + + fn value_transferred(output: &mut [u8; 32]) { + unsafe { sys::value_transferred(output.as_mut_ptr()) } + } + + fn now(output: &mut [u8; 32]) { + unsafe { sys::now(output.as_mut_ptr()) } + } + + fn chain_id(output: &mut [u8; 32]) { + unsafe { sys::chain_id(output.as_mut_ptr()) } } - #[cfg(feature = "unstable-api")] - impl_wrapper_for! { - [u8; 32] => block_number, minimum_balance; + fn address(output: &mut [u8; 20]) { + unsafe { sys::address(output.as_mut_ptr()) } + } + + fn caller(output: &mut [u8; 20]) { + unsafe { sys::caller(output.as_mut_ptr()) } + } + + fn origin(output: &mut [u8; 20]) { + unsafe { sys::origin(output.as_mut_ptr()) } + } + + fn block_number(output: &mut [u8; 32]) { + unsafe { sys::block_number(output.as_mut_ptr()) } } fn weight_to_fee(ref_time_limit: u64, proof_size_limit: u64, output: &mut [u8; 32]) { unsafe { sys::weight_to_fee(ref_time_limit, proof_size_limit, output.as_mut_ptr()) }; } - impl_hash_fn!(keccak_256, 32); + fn hash_keccak_256(input: &[u8], output: &mut [u8; 32]) { + unsafe { sys::hash_keccak_256(input.as_ptr(), input.len() as u32, output.as_mut_ptr()) } + } fn get_immutable_data(output: &mut &mut [u8]) { let mut output_len = output.len() as u32; @@ -454,12 +447,12 @@ impl HostFn for HostFnImpl { unsafe { sys::ref_time_left() } } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn block_hash(block_number_ptr: &[u8; 32], output: &mut [u8; 32]) { unsafe { sys::block_hash(block_number_ptr.as_ptr(), output.as_mut_ptr()) }; } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn call_chain_extension(func_id: u32, input: &[u8], mut output: Option<&mut &mut [u8]>) -> u32 { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); let ret_code = { @@ -485,43 +478,43 @@ impl HostFn for HostFnImpl { unsafe { sys::call_data_copy(output.as_mut_ptr(), len, offset) }; } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn call_runtime(call: &[u8]) -> Result { let ret_code = unsafe { sys::call_runtime(call.as_ptr(), call.len() as u32) }; ret_code.into() } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn caller_is_origin() -> bool { let ret_val = unsafe { sys::caller_is_origin() }; ret_val.into_bool() } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn caller_is_root() -> u32 { unsafe { sys::caller_is_root() }.into_u32() } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn clear_storage(flags: StorageFlags, key: &[u8]) -> Option { let ret_code = unsafe { sys::clear_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; ret_code.into() } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option { let ret_code = unsafe { sys::contains_storage(flags.bits(), key.as_ptr(), key.len() as u32) }; ret_code.into() } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn debug_message(str: &[u8]) -> Result { let ret_code = unsafe { sys::debug_message(str.as_ptr(), str.len() as u32) }; ret_code.into() } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn ecdsa_recover( signature: &[u8; 65], message_hash: &[u8; 32], @@ -533,41 +526,54 @@ impl HostFn for HostFnImpl { ret_code.into() } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn ecdsa_to_eth_address(pubkey: &[u8; 33], output: &mut [u8; 20]) -> Result { let ret_code = unsafe { sys::ecdsa_to_eth_address(pubkey.as_ptr(), output.as_mut_ptr()) }; ret_code.into() } - #[cfg(feature = "unstable-api")] - impl_hash_fn!(sha2_256, 32); - #[cfg(feature = "unstable-api")] - impl_hash_fn!(blake2_256, 32); - #[cfg(feature = "unstable-api")] - impl_hash_fn!(blake2_128, 16); + #[unstable_hostfn] + fn hash_sha2_256(input: &[u8], output: &mut [u8; 32]) { + unsafe { sys::hash_sha2_256(input.as_ptr(), input.len() as u32, output.as_mut_ptr()) } + } + + #[unstable_hostfn] + fn hash_blake2_256(input: &[u8], output: &mut [u8; 32]) { + unsafe { sys::hash_blake2_256(input.as_ptr(), input.len() as u32, output.as_mut_ptr()) } + } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] + fn hash_blake2_128(input: &[u8], output: &mut [u8; 16]) { + unsafe { sys::hash_blake2_128(input.as_ptr(), input.len() as u32, output.as_mut_ptr()) } + } + + #[unstable_hostfn] fn is_contract(address: &[u8; 20]) -> bool { let ret_val = unsafe { sys::is_contract(address.as_ptr()) }; ret_val.into_bool() } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn lock_delegate_dependency(code_hash: &[u8; 32]) { unsafe { sys::lock_delegate_dependency(code_hash.as_ptr()) } } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] + fn minimum_balance(output: &mut [u8; 32]) { + unsafe { sys::minimum_balance(output.as_mut_ptr()) } + } + + #[unstable_hostfn] fn own_code_hash(output: &mut [u8; 32]) { unsafe { sys::own_code_hash(output.as_mut_ptr()) } } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn set_code_hash(code_hash: &[u8; 32]) { unsafe { sys::set_code_hash(code_hash.as_ptr()) } } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn sr25519_verify(signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> Result { let ret_code = unsafe { sys::sr25519_verify( @@ -580,7 +586,7 @@ impl HostFn for HostFnImpl { ret_code.into() } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn take_storage(flags: StorageFlags, key: &[u8], output: &mut &mut [u8]) -> Result { let mut output_len = output.len() as u32; let ret_code = { @@ -598,31 +604,31 @@ impl HostFn for HostFnImpl { ret_code.into() } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn terminate(beneficiary: &[u8; 20]) -> ! { unsafe { sys::terminate(beneficiary.as_ptr()) } panic!("terminate does not return"); } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn unlock_delegate_dependency(code_hash: &[u8; 32]) { unsafe { sys::unlock_delegate_dependency(code_hash.as_ptr()) } } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn weight_left(output: &mut &mut [u8]) { let mut output_len = output.len() as u32; unsafe { sys::weight_left(output.as_mut_ptr(), &mut output_len) } extract_from_slice(output, output_len as usize) } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn xcm_execute(msg: &[u8]) -> Result { let ret_code = unsafe { sys::xcm_execute(msg.as_ptr(), msg.len() as _) }; ret_code.into() } - #[cfg(feature = "unstable-api")] + #[unstable_hostfn] fn xcm_send(dest: &[u8], msg: &[u8], output: &mut [u8; 32]) -> Result { let ret_code = unsafe { sys::xcm_send( diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs index 14a5e3f28889..ef1798b4bf61 100644 --- a/substrate/frame/revive/uapi/src/lib.rs +++ b/substrate/frame/revive/uapi/src/lib.rs @@ -17,6 +17,7 @@ //! Refer to substrate FRAME contract module for more documentation. #![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] mod flags; pub use flags::*; From 03e5db1fed6693c1695bb7aa910d84d8e302dc02 Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Thu, 19 Dec 2024 02:13:55 +0100 Subject: [PATCH 192/340] Improve pallet purchase file structure (#6780) Linked to issue #590 I moved the mod, tests, mock and benchmarking to their own seperate file to reduce the bloat inside purchase.rs --------- Co-authored-by: Guillaume Thiolliere Co-authored-by: Shawn Tabrizi --- polkadot/runtime/common/src/purchase.rs | 1178 ----------------- polkadot/runtime/common/src/purchase/mock.rs | 181 +++ polkadot/runtime/common/src/purchase/mod.rs | 482 +++++++ polkadot/runtime/common/src/purchase/tests.rs | 547 ++++++++ 4 files changed, 1210 insertions(+), 1178 deletions(-) delete mode 100644 polkadot/runtime/common/src/purchase.rs create mode 100644 polkadot/runtime/common/src/purchase/mock.rs create mode 100644 polkadot/runtime/common/src/purchase/mod.rs create mode 100644 polkadot/runtime/common/src/purchase/tests.rs diff --git a/polkadot/runtime/common/src/purchase.rs b/polkadot/runtime/common/src/purchase.rs deleted file mode 100644 index cec92540654c..000000000000 --- a/polkadot/runtime/common/src/purchase.rs +++ /dev/null @@ -1,1178 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Pallet to process purchase of DOTs. - -use alloc::vec::Vec; -use codec::{Decode, Encode}; -use frame_support::{ - pallet_prelude::*, - traits::{Currency, EnsureOrigin, ExistenceRequirement, Get, VestingSchedule}, -}; -use frame_system::pallet_prelude::*; -pub use pallet::*; -use scale_info::TypeInfo; -use sp_core::sr25519; -use sp_runtime::{ - traits::{CheckedAdd, Saturating, Verify, Zero}, - AnySignature, DispatchError, DispatchResult, Permill, RuntimeDebug, -}; - -type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; - -/// The kind of statement an account needs to make for a claim to be valid. -#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub enum AccountValidity { - /// Account is not valid. - Invalid, - /// Account has initiated the account creation process. - Initiated, - /// Account is pending validation. - Pending, - /// Account is valid with a low contribution amount. - ValidLow, - /// Account is valid with a high contribution amount. - ValidHigh, - /// Account has completed the purchase process. - Completed, -} - -impl Default for AccountValidity { - fn default() -> Self { - AccountValidity::Invalid - } -} - -impl AccountValidity { - fn is_valid(&self) -> bool { - match self { - Self::Invalid => false, - Self::Initiated => false, - Self::Pending => false, - Self::ValidLow => true, - Self::ValidHigh => true, - Self::Completed => false, - } - } -} - -/// All information about an account regarding the purchase of DOTs. -#[derive(Encode, Decode, Default, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct AccountStatus { - /// The current validity status of the user. Will denote if the user has passed KYC, - /// how much they are able to purchase, and when their purchase process has completed. - validity: AccountValidity, - /// The amount of free DOTs they have purchased. - free_balance: Balance, - /// The amount of locked DOTs they have purchased. - locked_balance: Balance, - /// Their sr25519/ed25519 signature verifying they have signed our required statement. - signature: Vec, - /// The percentage of VAT the purchaser is responsible for. This is already factored into - /// account balance. - vat: Permill, -} - -#[frame_support::pallet] -pub mod pallet { - use super::*; - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(_); - - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// Balances Pallet - type Currency: Currency; - - /// Vesting Pallet - type VestingSchedule: VestingSchedule< - Self::AccountId, - Moment = BlockNumberFor, - Currency = Self::Currency, - >; - - /// The origin allowed to set account status. - type ValidityOrigin: EnsureOrigin; - - /// The origin allowed to make configurations to the pallet. - type ConfigurationOrigin: EnsureOrigin; - - /// The maximum statement length for the statement users to sign when creating an account. - #[pallet::constant] - type MaxStatementLength: Get; - - /// The amount of purchased locked DOTs that we will unlock for basic actions on the chain. - #[pallet::constant] - type UnlockedProportion: Get; - - /// The maximum amount of locked DOTs that we will unlock. - #[pallet::constant] - type MaxUnlocked: Get>; - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// A new account was created. - AccountCreated { who: T::AccountId }, - /// Someone's account validity was updated. - ValidityUpdated { who: T::AccountId, validity: AccountValidity }, - /// Someone's purchase balance was updated. - BalanceUpdated { who: T::AccountId, free: BalanceOf, locked: BalanceOf }, - /// A payout was made to a purchaser. - PaymentComplete { who: T::AccountId, free: BalanceOf, locked: BalanceOf }, - /// A new payment account was set. - PaymentAccountSet { who: T::AccountId }, - /// A new statement was set. - StatementUpdated, - /// A new statement was set. `[block_number]` - UnlockBlockUpdated { block_number: BlockNumberFor }, - } - - #[pallet::error] - pub enum Error { - /// Account is not currently valid to use. - InvalidAccount, - /// Account used in the purchase already exists. - ExistingAccount, - /// Provided signature is invalid - InvalidSignature, - /// Account has already completed the purchase process. - AlreadyCompleted, - /// An overflow occurred when doing calculations. - Overflow, - /// The statement is too long to be stored on chain. - InvalidStatement, - /// The unlock block is in the past! - InvalidUnlockBlock, - /// Vesting schedule already exists for this account. - VestingScheduleExists, - } - - // A map of all participants in the DOT purchase process. - #[pallet::storage] - pub(super) type Accounts = - StorageMap<_, Blake2_128Concat, T::AccountId, AccountStatus>, ValueQuery>; - - // The account that will be used to payout participants of the DOT purchase process. - #[pallet::storage] - pub(super) type PaymentAccount = StorageValue<_, T::AccountId, OptionQuery>; - - // The statement purchasers will need to sign to participate. - #[pallet::storage] - pub(super) type Statement = StorageValue<_, Vec, ValueQuery>; - - // The block where all locked dots will unlock. - #[pallet::storage] - pub(super) type UnlockBlock = StorageValue<_, BlockNumberFor, ValueQuery>; - - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::call] - impl Pallet { - /// Create a new account. Proof of existence through a valid signed message. - /// - /// We check that the account does not exist at this stage. - /// - /// Origin must match the `ValidityOrigin`. - #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(200_000_000, 0) + T::DbWeight::get().reads_writes(4, 1))] - pub fn create_account( - origin: OriginFor, - who: T::AccountId, - signature: Vec, - ) -> DispatchResult { - T::ValidityOrigin::ensure_origin(origin)?; - // Account is already being tracked by the pallet. - ensure!(!Accounts::::contains_key(&who), Error::::ExistingAccount); - // Account should not have a vesting schedule. - ensure!( - T::VestingSchedule::vesting_balance(&who).is_none(), - Error::::VestingScheduleExists - ); - - // Verify the signature provided is valid for the statement. - Self::verify_signature(&who, &signature)?; - - // Create a new pending account. - let status = AccountStatus { - validity: AccountValidity::Initiated, - signature, - free_balance: Zero::zero(), - locked_balance: Zero::zero(), - vat: Permill::zero(), - }; - Accounts::::insert(&who, status); - Self::deposit_event(Event::::AccountCreated { who }); - Ok(()) - } - - /// Update the validity status of an existing account. If set to completed, the account - /// will no longer be able to continue through the crowdfund process. - /// - /// We check that the account exists at this stage, but has not completed the process. - /// - /// Origin must match the `ValidityOrigin`. - #[pallet::call_index(1)] - #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] - pub fn update_validity_status( - origin: OriginFor, - who: T::AccountId, - validity: AccountValidity, - ) -> DispatchResult { - T::ValidityOrigin::ensure_origin(origin)?; - ensure!(Accounts::::contains_key(&who), Error::::InvalidAccount); - Accounts::::try_mutate( - &who, - |status: &mut AccountStatus>| -> DispatchResult { - ensure!( - status.validity != AccountValidity::Completed, - Error::::AlreadyCompleted - ); - status.validity = validity; - Ok(()) - }, - )?; - Self::deposit_event(Event::::ValidityUpdated { who, validity }); - Ok(()) - } - - /// Update the balance of a valid account. - /// - /// We check that the account is valid for a balance transfer at this point. - /// - /// Origin must match the `ValidityOrigin`. - #[pallet::call_index(2)] - #[pallet::weight(T::DbWeight::get().reads_writes(2, 1))] - pub fn update_balance( - origin: OriginFor, - who: T::AccountId, - free_balance: BalanceOf, - locked_balance: BalanceOf, - vat: Permill, - ) -> DispatchResult { - T::ValidityOrigin::ensure_origin(origin)?; - - Accounts::::try_mutate( - &who, - |status: &mut AccountStatus>| -> DispatchResult { - // Account has a valid status (not Invalid, Pending, or Completed)... - ensure!(status.validity.is_valid(), Error::::InvalidAccount); - - free_balance.checked_add(&locked_balance).ok_or(Error::::Overflow)?; - status.free_balance = free_balance; - status.locked_balance = locked_balance; - status.vat = vat; - Ok(()) - }, - )?; - Self::deposit_event(Event::::BalanceUpdated { - who, - free: free_balance, - locked: locked_balance, - }); - Ok(()) - } - - /// Pay the user and complete the purchase process. - /// - /// We reverify all assumptions about the state of an account, and complete the process. - /// - /// Origin must match the configured `PaymentAccount` (if it is not configured then this - /// will always fail with `BadOrigin`). - #[pallet::call_index(3)] - #[pallet::weight(T::DbWeight::get().reads_writes(4, 2))] - pub fn payout(origin: OriginFor, who: T::AccountId) -> DispatchResult { - // Payments must be made directly by the `PaymentAccount`. - let payment_account = ensure_signed(origin)?; - let test_against = PaymentAccount::::get().ok_or(DispatchError::BadOrigin)?; - ensure!(payment_account == test_against, DispatchError::BadOrigin); - - // Account should not have a vesting schedule. - ensure!( - T::VestingSchedule::vesting_balance(&who).is_none(), - Error::::VestingScheduleExists - ); - - Accounts::::try_mutate( - &who, - |status: &mut AccountStatus>| -> DispatchResult { - // Account has a valid status (not Invalid, Pending, or Completed)... - ensure!(status.validity.is_valid(), Error::::InvalidAccount); - - // Transfer funds from the payment account into the purchasing user. - let total_balance = status - .free_balance - .checked_add(&status.locked_balance) - .ok_or(Error::::Overflow)?; - T::Currency::transfer( - &payment_account, - &who, - total_balance, - ExistenceRequirement::AllowDeath, - )?; - - if !status.locked_balance.is_zero() { - let unlock_block = UnlockBlock::::get(); - // We allow some configurable portion of the purchased locked DOTs to be - // unlocked for basic usage. - let unlocked = (T::UnlockedProportion::get() * status.locked_balance) - .min(T::MaxUnlocked::get()); - let locked = status.locked_balance.saturating_sub(unlocked); - // We checked that this account has no existing vesting schedule. So this - // function should never fail, however if it does, not much we can do about - // it at this point. - let _ = T::VestingSchedule::add_vesting_schedule( - // Apply vesting schedule to this user - &who, - // For this much amount - locked, - // Unlocking the full amount after one block - locked, - // When everything unlocks - unlock_block, - ); - } - - // Setting the user account to `Completed` ends the purchase process for this - // user. - status.validity = AccountValidity::Completed; - Self::deposit_event(Event::::PaymentComplete { - who: who.clone(), - free: status.free_balance, - locked: status.locked_balance, - }); - Ok(()) - }, - )?; - Ok(()) - } - - /* Configuration Operations */ - - /// Set the account that will be used to payout users in the DOT purchase process. - /// - /// Origin must match the `ConfigurationOrigin` - #[pallet::call_index(4)] - #[pallet::weight(T::DbWeight::get().writes(1))] - pub fn set_payment_account(origin: OriginFor, who: T::AccountId) -> DispatchResult { - T::ConfigurationOrigin::ensure_origin(origin)?; - // Possibly this is worse than having the caller account be the payment account? - PaymentAccount::::put(who.clone()); - Self::deposit_event(Event::::PaymentAccountSet { who }); - Ok(()) - } - - /// Set the statement that must be signed for a user to participate on the DOT sale. - /// - /// Origin must match the `ConfigurationOrigin` - #[pallet::call_index(5)] - #[pallet::weight(T::DbWeight::get().writes(1))] - pub fn set_statement(origin: OriginFor, statement: Vec) -> DispatchResult { - T::ConfigurationOrigin::ensure_origin(origin)?; - ensure!( - (statement.len() as u32) < T::MaxStatementLength::get(), - Error::::InvalidStatement - ); - // Possibly this is worse than having the caller account be the payment account? - Statement::::set(statement); - Self::deposit_event(Event::::StatementUpdated); - Ok(()) - } - - /// Set the block where locked DOTs will become unlocked. - /// - /// Origin must match the `ConfigurationOrigin` - #[pallet::call_index(6)] - #[pallet::weight(T::DbWeight::get().writes(1))] - pub fn set_unlock_block( - origin: OriginFor, - unlock_block: BlockNumberFor, - ) -> DispatchResult { - T::ConfigurationOrigin::ensure_origin(origin)?; - ensure!( - unlock_block > frame_system::Pallet::::block_number(), - Error::::InvalidUnlockBlock - ); - // Possibly this is worse than having the caller account be the payment account? - UnlockBlock::::set(unlock_block); - Self::deposit_event(Event::::UnlockBlockUpdated { block_number: unlock_block }); - Ok(()) - } - } -} - -impl Pallet { - fn verify_signature(who: &T::AccountId, signature: &[u8]) -> Result<(), DispatchError> { - // sr25519 always expects a 64 byte signature. - let signature: AnySignature = sr25519::Signature::try_from(signature) - .map_err(|_| Error::::InvalidSignature)? - .into(); - - // In Polkadot, the AccountId is always the same as the 32 byte public key. - let account_bytes: [u8; 32] = account_to_bytes(who)?; - let public_key = sr25519::Public::from_raw(account_bytes); - - let message = Statement::::get(); - - // Check if everything is good or not. - match signature.verify(message.as_slice(), &public_key) { - true => Ok(()), - false => Err(Error::::InvalidSignature)?, - } - } -} - -// This function converts a 32 byte AccountId to its byte-array equivalent form. -fn account_to_bytes(account: &AccountId) -> Result<[u8; 32], DispatchError> -where - AccountId: Encode, -{ - let account_vec = account.encode(); - ensure!(account_vec.len() == 32, "AccountId must be 32 bytes."); - let mut bytes = [0u8; 32]; - bytes.copy_from_slice(&account_vec); - Ok(bytes) -} - -/// WARNING: Executing this function will clear all storage used by this pallet. -/// Be sure this is what you want... -pub fn remove_pallet() -> frame_support::weights::Weight -where - T: frame_system::Config, -{ - #[allow(deprecated)] - use frame_support::migration::remove_storage_prefix; - #[allow(deprecated)] - remove_storage_prefix(b"Purchase", b"Accounts", b""); - #[allow(deprecated)] - remove_storage_prefix(b"Purchase", b"PaymentAccount", b""); - #[allow(deprecated)] - remove_storage_prefix(b"Purchase", b"Statement", b""); - #[allow(deprecated)] - remove_storage_prefix(b"Purchase", b"UnlockBlock", b""); - - ::BlockWeights::get().max_block -} - -#[cfg(test)] -mod tests { - use super::*; - - use sp_core::{crypto::AccountId32, H256}; - use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; - // The testing primitives are very useful for avoiding having to work with signatures - // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. - use crate::purchase; - use frame_support::{ - assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types, - traits::{Currency, WithdrawReasons}, - }; - use sp_runtime::{ - traits::{BlakeTwo256, Dispatchable, Identity, IdentityLookup}, - ArithmeticError, BuildStorage, - DispatchError::BadOrigin, - }; - - type Block = frame_system::mocking::MockBlock; - - frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Balances: pallet_balances, - Vesting: pallet_vesting, - Purchase: purchase, - } - ); - - type AccountId = AccountId32; - - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] - impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - } - - #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] - impl pallet_balances::Config for Test { - type AccountStore = System; - } - - parameter_types! { - pub const MinVestedTransfer: u64 = 1; - pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = - WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); - } - - impl pallet_vesting::Config for Test { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type BlockNumberToBalance = Identity; - type MinVestedTransfer = MinVestedTransfer; - type WeightInfo = (); - type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; - type BlockNumberProvider = System; - const MAX_VESTING_SCHEDULES: u32 = 28; - } - - parameter_types! { - pub const MaxStatementLength: u32 = 1_000; - pub const UnlockedProportion: Permill = Permill::from_percent(10); - pub const MaxUnlocked: u64 = 10; - } - - ord_parameter_types! { - pub const ValidityOrigin: AccountId = AccountId32::from([0u8; 32]); - pub const PaymentOrigin: AccountId = AccountId32::from([1u8; 32]); - pub const ConfigurationOrigin: AccountId = AccountId32::from([2u8; 32]); - } - - impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type VestingSchedule = Vesting; - type ValidityOrigin = frame_system::EnsureSignedBy; - type ConfigurationOrigin = frame_system::EnsureSignedBy; - type MaxStatementLength = MaxStatementLength; - type UnlockedProportion = UnlockedProportion; - type MaxUnlocked = MaxUnlocked; - } - - // This function basically just builds a genesis storage key/value store according to - // our desired mockup. It also executes our `setup` function which sets up this pallet for use. - pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| setup()); - ext - } - - fn setup() { - let statement = b"Hello, World".to_vec(); - let unlock_block = 100; - Purchase::set_statement(RuntimeOrigin::signed(configuration_origin()), statement).unwrap(); - Purchase::set_unlock_block(RuntimeOrigin::signed(configuration_origin()), unlock_block) - .unwrap(); - Purchase::set_payment_account( - RuntimeOrigin::signed(configuration_origin()), - payment_account(), - ) - .unwrap(); - Balances::make_free_balance_be(&payment_account(), 100_000); - } - - fn alice() -> AccountId { - Sr25519Keyring::Alice.to_account_id() - } - - fn alice_ed25519() -> AccountId { - Ed25519Keyring::Alice.to_account_id() - } - - fn bob() -> AccountId { - Sr25519Keyring::Bob.to_account_id() - } - - fn alice_signature() -> [u8; 64] { - // echo -n "Hello, World" | subkey -s sign "bottom drive obey lake curtain smoke basket hold - // race lonely fit walk//Alice" - hex_literal::hex!("20e0faffdf4dfe939f2faa560f73b1d01cde8472e2b690b7b40606a374244c3a2e9eb9c8107c10b605138374003af8819bd4387d7c24a66ee9253c2e688ab881") - } - - fn bob_signature() -> [u8; 64] { - // echo -n "Hello, World" | subkey -s sign "bottom drive obey lake curtain smoke basket hold - // race lonely fit walk//Bob" - hex_literal::hex!("d6d460187ecf530f3ec2d6e3ac91b9d083c8fbd8f1112d92a82e4d84df552d18d338e6da8944eba6e84afaacf8a9850f54e7b53a84530d649be2e0119c7ce889") - } - - fn alice_signature_ed25519() -> [u8; 64] { - // echo -n "Hello, World" | subkey -e sign "bottom drive obey lake curtain smoke basket hold - // race lonely fit walk//Alice" - hex_literal::hex!("ee3f5a6cbfc12a8f00c18b811dc921b550ddf272354cda4b9a57b1d06213fcd8509f5af18425d39a279d13622f14806c3e978e2163981f2ec1c06e9628460b0e") - } - - fn validity_origin() -> AccountId { - ValidityOrigin::get() - } - - fn configuration_origin() -> AccountId { - ConfigurationOrigin::get() - } - - fn payment_account() -> AccountId { - [42u8; 32].into() - } - - #[test] - fn set_statement_works_and_handles_basic_errors() { - new_test_ext().execute_with(|| { - let statement = b"Test Set Statement".to_vec(); - // Invalid origin - assert_noop!( - Purchase::set_statement(RuntimeOrigin::signed(alice()), statement.clone()), - BadOrigin, - ); - // Too Long - let long_statement = [0u8; 10_000].to_vec(); - assert_noop!( - Purchase::set_statement( - RuntimeOrigin::signed(configuration_origin()), - long_statement - ), - Error::::InvalidStatement, - ); - // Just right... - assert_ok!(Purchase::set_statement( - RuntimeOrigin::signed(configuration_origin()), - statement.clone() - )); - assert_eq!(Statement::::get(), statement); - }); - } - - #[test] - fn set_unlock_block_works_and_handles_basic_errors() { - new_test_ext().execute_with(|| { - let unlock_block = 69; - // Invalid origin - assert_noop!( - Purchase::set_unlock_block(RuntimeOrigin::signed(alice()), unlock_block), - BadOrigin, - ); - // Block Number in Past - let bad_unlock_block = 50; - System::set_block_number(bad_unlock_block); - assert_noop!( - Purchase::set_unlock_block( - RuntimeOrigin::signed(configuration_origin()), - bad_unlock_block - ), - Error::::InvalidUnlockBlock, - ); - // Just right... - assert_ok!(Purchase::set_unlock_block( - RuntimeOrigin::signed(configuration_origin()), - unlock_block - )); - assert_eq!(UnlockBlock::::get(), unlock_block); - }); - } - - #[test] - fn set_payment_account_works_and_handles_basic_errors() { - new_test_ext().execute_with(|| { - let payment_account: AccountId = [69u8; 32].into(); - // Invalid Origin - assert_noop!( - Purchase::set_payment_account( - RuntimeOrigin::signed(alice()), - payment_account.clone() - ), - BadOrigin, - ); - // Just right... - assert_ok!(Purchase::set_payment_account( - RuntimeOrigin::signed(configuration_origin()), - payment_account.clone() - )); - assert_eq!(PaymentAccount::::get(), Some(payment_account)); - }); - } - - #[test] - fn signature_verification_works() { - new_test_ext().execute_with(|| { - assert_ok!(Purchase::verify_signature(&alice(), &alice_signature())); - assert_ok!(Purchase::verify_signature(&alice_ed25519(), &alice_signature_ed25519())); - assert_ok!(Purchase::verify_signature(&bob(), &bob_signature())); - - // Mixing and matching fails - assert_noop!( - Purchase::verify_signature(&alice(), &bob_signature()), - Error::::InvalidSignature - ); - assert_noop!( - Purchase::verify_signature(&bob(), &alice_signature()), - Error::::InvalidSignature - ); - }); - } - - #[test] - fn account_creation_works() { - new_test_ext().execute_with(|| { - assert!(!Accounts::::contains_key(alice())); - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec(), - )); - assert_eq!( - Accounts::::get(alice()), - AccountStatus { - validity: AccountValidity::Initiated, - free_balance: Zero::zero(), - locked_balance: Zero::zero(), - signature: alice_signature().to_vec(), - vat: Permill::zero(), - } - ); - }); - } - - #[test] - fn account_creation_handles_basic_errors() { - new_test_ext().execute_with(|| { - // Wrong Origin - assert_noop!( - Purchase::create_account( - RuntimeOrigin::signed(alice()), - alice(), - alice_signature().to_vec() - ), - BadOrigin, - ); - - // Wrong Account/Signature - assert_noop!( - Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - bob_signature().to_vec() - ), - Error::::InvalidSignature, - ); - - // Account with vesting - Balances::make_free_balance_be(&alice(), 100); - assert_ok!(::VestingSchedule::add_vesting_schedule( - &alice(), - 100, - 1, - 50 - )); - assert_noop!( - Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec() - ), - Error::::VestingScheduleExists, - ); - - // Duplicate Purchasing Account - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - bob(), - bob_signature().to_vec() - )); - assert_noop!( - Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - bob(), - bob_signature().to_vec() - ), - Error::::ExistingAccount, - ); - }); - } - - #[test] - fn update_validity_status_works() { - new_test_ext().execute_with(|| { - // Alice account is created. - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec(), - )); - // She submits KYC, and we update the status to `Pending`. - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::Pending, - )); - // KYC comes back negative, so we mark the account invalid. - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::Invalid, - )); - assert_eq!( - Accounts::::get(alice()), - AccountStatus { - validity: AccountValidity::Invalid, - free_balance: Zero::zero(), - locked_balance: Zero::zero(), - signature: alice_signature().to_vec(), - vat: Permill::zero(), - } - ); - // She fixes it, we mark her account valid. - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::ValidLow, - )); - assert_eq!( - Accounts::::get(alice()), - AccountStatus { - validity: AccountValidity::ValidLow, - free_balance: Zero::zero(), - locked_balance: Zero::zero(), - signature: alice_signature().to_vec(), - vat: Permill::zero(), - } - ); - }); - } - - #[test] - fn update_validity_status_handles_basic_errors() { - new_test_ext().execute_with(|| { - // Wrong Origin - assert_noop!( - Purchase::update_validity_status( - RuntimeOrigin::signed(alice()), - alice(), - AccountValidity::Pending, - ), - BadOrigin - ); - // Inactive Account - assert_noop!( - Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::Pending, - ), - Error::::InvalidAccount - ); - // Already Completed - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec(), - )); - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::Completed, - )); - assert_noop!( - Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::Pending, - ), - Error::::AlreadyCompleted - ); - }); - } - - #[test] - fn update_balance_works() { - new_test_ext().execute_with(|| { - // Alice account is created - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec() - )); - // And approved for basic contribution - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::ValidLow, - )); - // We set a balance on the user based on the payment they made. 50 locked, 50 free. - assert_ok!(Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - 50, - 50, - Permill::from_rational(77u32, 1000u32), - )); - assert_eq!( - Accounts::::get(alice()), - AccountStatus { - validity: AccountValidity::ValidLow, - free_balance: 50, - locked_balance: 50, - signature: alice_signature().to_vec(), - vat: Permill::from_parts(77000), - } - ); - // We can update the balance based on new information. - assert_ok!(Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - 25, - 50, - Permill::zero(), - )); - assert_eq!( - Accounts::::get(alice()), - AccountStatus { - validity: AccountValidity::ValidLow, - free_balance: 25, - locked_balance: 50, - signature: alice_signature().to_vec(), - vat: Permill::zero(), - } - ); - }); - } - - #[test] - fn update_balance_handles_basic_errors() { - new_test_ext().execute_with(|| { - // Wrong Origin - assert_noop!( - Purchase::update_balance( - RuntimeOrigin::signed(alice()), - alice(), - 50, - 50, - Permill::zero(), - ), - BadOrigin - ); - // Inactive Account - assert_noop!( - Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - 50, - 50, - Permill::zero(), - ), - Error::::InvalidAccount - ); - // Overflow - assert_noop!( - Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - u64::MAX, - u64::MAX, - Permill::zero(), - ), - Error::::InvalidAccount - ); - }); - } - - #[test] - fn payout_works() { - new_test_ext().execute_with(|| { - // Alice and Bob accounts are created - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec() - )); - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - bob(), - bob_signature().to_vec() - )); - // Alice is approved for basic contribution - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::ValidLow, - )); - // Bob is approved for high contribution - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - bob(), - AccountValidity::ValidHigh, - )); - // We set a balance on the users based on the payment they made. 50 locked, 50 free. - assert_ok!(Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - 50, - 50, - Permill::zero(), - )); - assert_ok!(Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - bob(), - 100, - 150, - Permill::zero(), - )); - // Now we call payout for Alice and Bob. - assert_ok!(Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),)); - assert_ok!(Purchase::payout(RuntimeOrigin::signed(payment_account()), bob(),)); - // Payment is made. - assert_eq!(::Currency::free_balance(&payment_account()), 99_650); - assert_eq!(::Currency::free_balance(&alice()), 100); - // 10% of the 50 units is unlocked automatically for Alice - assert_eq!(::VestingSchedule::vesting_balance(&alice()), Some(45)); - assert_eq!(::Currency::free_balance(&bob()), 250); - // A max of 10 units is unlocked automatically for Bob - assert_eq!(::VestingSchedule::vesting_balance(&bob()), Some(140)); - // Status is completed. - assert_eq!( - Accounts::::get(alice()), - AccountStatus { - validity: AccountValidity::Completed, - free_balance: 50, - locked_balance: 50, - signature: alice_signature().to_vec(), - vat: Permill::zero(), - } - ); - assert_eq!( - Accounts::::get(bob()), - AccountStatus { - validity: AccountValidity::Completed, - free_balance: 100, - locked_balance: 150, - signature: bob_signature().to_vec(), - vat: Permill::zero(), - } - ); - // Vesting lock is removed in whole on block 101 (100 blocks after block 1) - System::set_block_number(100); - let vest_call = RuntimeCall::Vesting(pallet_vesting::Call::::vest {}); - assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(alice()))); - assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(bob()))); - assert_eq!(::VestingSchedule::vesting_balance(&alice()), Some(45)); - assert_eq!(::VestingSchedule::vesting_balance(&bob()), Some(140)); - System::set_block_number(101); - assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(alice()))); - assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(bob()))); - assert_eq!(::VestingSchedule::vesting_balance(&alice()), None); - assert_eq!(::VestingSchedule::vesting_balance(&bob()), None); - }); - } - - #[test] - fn payout_handles_basic_errors() { - new_test_ext().execute_with(|| { - // Wrong Origin - assert_noop!(Purchase::payout(RuntimeOrigin::signed(alice()), alice(),), BadOrigin); - // Account with Existing Vesting Schedule - Balances::make_free_balance_be(&bob(), 100); - assert_ok!( - ::VestingSchedule::add_vesting_schedule(&bob(), 100, 1, 50,) - ); - assert_noop!( - Purchase::payout(RuntimeOrigin::signed(payment_account()), bob(),), - Error::::VestingScheduleExists - ); - // Invalid Account (never created) - assert_noop!( - Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),), - Error::::InvalidAccount - ); - // Invalid Account (created, but not valid) - assert_ok!(Purchase::create_account( - RuntimeOrigin::signed(validity_origin()), - alice(), - alice_signature().to_vec() - )); - assert_noop!( - Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),), - Error::::InvalidAccount - ); - // Not enough funds in payment account - assert_ok!(Purchase::update_validity_status( - RuntimeOrigin::signed(validity_origin()), - alice(), - AccountValidity::ValidHigh, - )); - assert_ok!(Purchase::update_balance( - RuntimeOrigin::signed(validity_origin()), - alice(), - 100_000, - 100_000, - Permill::zero(), - )); - assert_noop!( - Purchase::payout(RuntimeOrigin::signed(payment_account()), alice()), - ArithmeticError::Underflow - ); - }); - } - - #[test] - fn remove_pallet_works() { - new_test_ext().execute_with(|| { - let account_status = AccountStatus { - validity: AccountValidity::Completed, - free_balance: 1234, - locked_balance: 4321, - signature: b"my signature".to_vec(), - vat: Permill::from_percent(50), - }; - - // Add some storage. - Accounts::::insert(alice(), account_status.clone()); - Accounts::::insert(bob(), account_status); - PaymentAccount::::put(alice()); - Statement::::put(b"hello, world!".to_vec()); - UnlockBlock::::put(4); - - // Verify storage exists. - assert_eq!(Accounts::::iter().count(), 2); - assert!(PaymentAccount::::exists()); - assert!(Statement::::exists()); - assert!(UnlockBlock::::exists()); - - // Remove storage. - remove_pallet::(); - - // Verify storage is gone. - assert_eq!(Accounts::::iter().count(), 0); - assert!(!PaymentAccount::::exists()); - assert!(!Statement::::exists()); - assert!(!UnlockBlock::::exists()); - }); - } -} diff --git a/polkadot/runtime/common/src/purchase/mock.rs b/polkadot/runtime/common/src/purchase/mock.rs new file mode 100644 index 000000000000..ec8599f3b792 --- /dev/null +++ b/polkadot/runtime/common/src/purchase/mock.rs @@ -0,0 +1,181 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Mocking utilities for testing in purchase pallet. + +#[cfg(test)] +use super::*; + +use sp_core::{crypto::AccountId32, H256}; +use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; +// The testing primitives are very useful for avoiding having to work with signatures +// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. +use crate::purchase; +use frame_support::{ + derive_impl, ord_parameter_types, parameter_types, + traits::{Currency, WithdrawReasons}, +}; +use sp_runtime::{ + traits::{BlakeTwo256, Identity, IdentityLookup}, + BuildStorage, +}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Vesting: pallet_vesting, + Purchase: purchase, + } +); + +type AccountId = AccountId32; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +parameter_types! { + pub const MinVestedTransfer: u64 = 1; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BlockNumberToBalance = Identity; + type MinVestedTransfer = MinVestedTransfer; + type WeightInfo = (); + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type BlockNumberProvider = System; + const MAX_VESTING_SCHEDULES: u32 = 28; +} + +parameter_types! { + pub const MaxStatementLength: u32 = 1_000; + pub const UnlockedProportion: Permill = Permill::from_percent(10); + pub const MaxUnlocked: u64 = 10; +} + +ord_parameter_types! { + pub const ValidityOrigin: AccountId = AccountId32::from([0u8; 32]); + pub const PaymentOrigin: AccountId = AccountId32::from([1u8; 32]); + pub const ConfigurationOrigin: AccountId = AccountId32::from([2u8; 32]); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type VestingSchedule = Vesting; + type ValidityOrigin = frame_system::EnsureSignedBy; + type ConfigurationOrigin = frame_system::EnsureSignedBy; + type MaxStatementLength = MaxStatementLength; + type UnlockedProportion = UnlockedProportion; + type MaxUnlocked = MaxUnlocked; +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. It also executes our `setup` function which sets up this pallet for use. +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| setup()); + ext +} + +pub fn setup() { + let statement = b"Hello, World".to_vec(); + let unlock_block = 100; + Purchase::set_statement(RuntimeOrigin::signed(configuration_origin()), statement).unwrap(); + Purchase::set_unlock_block(RuntimeOrigin::signed(configuration_origin()), unlock_block) + .unwrap(); + Purchase::set_payment_account(RuntimeOrigin::signed(configuration_origin()), payment_account()) + .unwrap(); + Balances::make_free_balance_be(&payment_account(), 100_000); +} + +pub fn alice() -> AccountId { + Sr25519Keyring::Alice.to_account_id() +} + +pub fn alice_ed25519() -> AccountId { + Ed25519Keyring::Alice.to_account_id() +} + +pub fn bob() -> AccountId { + Sr25519Keyring::Bob.to_account_id() +} + +pub fn alice_signature() -> [u8; 64] { + // echo -n "Hello, World" | subkey -s sign "bottom drive obey lake curtain smoke basket hold + // race lonely fit walk//Alice" + hex_literal::hex!("20e0faffdf4dfe939f2faa560f73b1d01cde8472e2b690b7b40606a374244c3a2e9eb9c8107c10b605138374003af8819bd4387d7c24a66ee9253c2e688ab881") +} + +pub fn bob_signature() -> [u8; 64] { + // echo -n "Hello, World" | subkey -s sign "bottom drive obey lake curtain smoke basket hold + // race lonely fit walk//Bob" + hex_literal::hex!("d6d460187ecf530f3ec2d6e3ac91b9d083c8fbd8f1112d92a82e4d84df552d18d338e6da8944eba6e84afaacf8a9850f54e7b53a84530d649be2e0119c7ce889") +} + +pub fn alice_signature_ed25519() -> [u8; 64] { + // echo -n "Hello, World" | subkey -e sign "bottom drive obey lake curtain smoke basket hold + // race lonely fit walk//Alice" + hex_literal::hex!("ee3f5a6cbfc12a8f00c18b811dc921b550ddf272354cda4b9a57b1d06213fcd8509f5af18425d39a279d13622f14806c3e978e2163981f2ec1c06e9628460b0e") +} + +pub fn validity_origin() -> AccountId { + ValidityOrigin::get() +} + +pub fn configuration_origin() -> AccountId { + ConfigurationOrigin::get() +} + +pub fn payment_account() -> AccountId { + [42u8; 32].into() +} diff --git a/polkadot/runtime/common/src/purchase/mod.rs b/polkadot/runtime/common/src/purchase/mod.rs new file mode 100644 index 000000000000..71dc5b579670 --- /dev/null +++ b/polkadot/runtime/common/src/purchase/mod.rs @@ -0,0 +1,482 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Pallet to process purchase of DOTs. + +use alloc::vec::Vec; +use codec::{Decode, Encode}; +use frame_support::{ + pallet_prelude::*, + traits::{Currency, EnsureOrigin, ExistenceRequirement, Get, VestingSchedule}, +}; +use frame_system::pallet_prelude::*; +pub use pallet::*; +use scale_info::TypeInfo; +use sp_core::sr25519; +use sp_runtime::{ + traits::{CheckedAdd, Saturating, Verify, Zero}, + AnySignature, DispatchError, DispatchResult, Permill, RuntimeDebug, +}; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +/// The kind of statement an account needs to make for a claim to be valid. +#[derive(Encode, Decode, Clone, Copy, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub enum AccountValidity { + /// Account is not valid. + Invalid, + /// Account has initiated the account creation process. + Initiated, + /// Account is pending validation. + Pending, + /// Account is valid with a low contribution amount. + ValidLow, + /// Account is valid with a high contribution amount. + ValidHigh, + /// Account has completed the purchase process. + Completed, +} + +impl Default for AccountValidity { + fn default() -> Self { + AccountValidity::Invalid + } +} + +impl AccountValidity { + fn is_valid(&self) -> bool { + match self { + Self::Invalid => false, + Self::Initiated => false, + Self::Pending => false, + Self::ValidLow => true, + Self::ValidHigh => true, + Self::Completed => false, + } + } +} + +/// All information about an account regarding the purchase of DOTs. +#[derive(Encode, Decode, Default, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct AccountStatus { + /// The current validity status of the user. Will denote if the user has passed KYC, + /// how much they are able to purchase, and when their purchase process has completed. + validity: AccountValidity, + /// The amount of free DOTs they have purchased. + free_balance: Balance, + /// The amount of locked DOTs they have purchased. + locked_balance: Balance, + /// Their sr25519/ed25519 signature verifying they have signed our required statement. + signature: Vec, + /// The percentage of VAT the purchaser is responsible for. This is already factored into + /// account balance. + vat: Permill, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Balances Pallet + type Currency: Currency; + + /// Vesting Pallet + type VestingSchedule: VestingSchedule< + Self::AccountId, + Moment = BlockNumberFor, + Currency = Self::Currency, + >; + + /// The origin allowed to set account status. + type ValidityOrigin: EnsureOrigin; + + /// The origin allowed to make configurations to the pallet. + type ConfigurationOrigin: EnsureOrigin; + + /// The maximum statement length for the statement users to sign when creating an account. + #[pallet::constant] + type MaxStatementLength: Get; + + /// The amount of purchased locked DOTs that we will unlock for basic actions on the chain. + #[pallet::constant] + type UnlockedProportion: Get; + + /// The maximum amount of locked DOTs that we will unlock. + #[pallet::constant] + type MaxUnlocked: Get>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A new account was created. + AccountCreated { who: T::AccountId }, + /// Someone's account validity was updated. + ValidityUpdated { who: T::AccountId, validity: AccountValidity }, + /// Someone's purchase balance was updated. + BalanceUpdated { who: T::AccountId, free: BalanceOf, locked: BalanceOf }, + /// A payout was made to a purchaser. + PaymentComplete { who: T::AccountId, free: BalanceOf, locked: BalanceOf }, + /// A new payment account was set. + PaymentAccountSet { who: T::AccountId }, + /// A new statement was set. + StatementUpdated, + /// A new statement was set. `[block_number]` + UnlockBlockUpdated { block_number: BlockNumberFor }, + } + + #[pallet::error] + pub enum Error { + /// Account is not currently valid to use. + InvalidAccount, + /// Account used in the purchase already exists. + ExistingAccount, + /// Provided signature is invalid + InvalidSignature, + /// Account has already completed the purchase process. + AlreadyCompleted, + /// An overflow occurred when doing calculations. + Overflow, + /// The statement is too long to be stored on chain. + InvalidStatement, + /// The unlock block is in the past! + InvalidUnlockBlock, + /// Vesting schedule already exists for this account. + VestingScheduleExists, + } + + // A map of all participants in the DOT purchase process. + #[pallet::storage] + pub(super) type Accounts = + StorageMap<_, Blake2_128Concat, T::AccountId, AccountStatus>, ValueQuery>; + + // The account that will be used to payout participants of the DOT purchase process. + #[pallet::storage] + pub(super) type PaymentAccount = StorageValue<_, T::AccountId, OptionQuery>; + + // The statement purchasers will need to sign to participate. + #[pallet::storage] + pub(super) type Statement = StorageValue<_, Vec, ValueQuery>; + + // The block where all locked dots will unlock. + #[pallet::storage] + pub(super) type UnlockBlock = StorageValue<_, BlockNumberFor, ValueQuery>; + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// Create a new account. Proof of existence through a valid signed message. + /// + /// We check that the account does not exist at this stage. + /// + /// Origin must match the `ValidityOrigin`. + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(200_000_000, 0) + T::DbWeight::get().reads_writes(4, 1))] + pub fn create_account( + origin: OriginFor, + who: T::AccountId, + signature: Vec, + ) -> DispatchResult { + T::ValidityOrigin::ensure_origin(origin)?; + // Account is already being tracked by the pallet. + ensure!(!Accounts::::contains_key(&who), Error::::ExistingAccount); + // Account should not have a vesting schedule. + ensure!( + T::VestingSchedule::vesting_balance(&who).is_none(), + Error::::VestingScheduleExists + ); + + // Verify the signature provided is valid for the statement. + Self::verify_signature(&who, &signature)?; + + // Create a new pending account. + let status = AccountStatus { + validity: AccountValidity::Initiated, + signature, + free_balance: Zero::zero(), + locked_balance: Zero::zero(), + vat: Permill::zero(), + }; + Accounts::::insert(&who, status); + Self::deposit_event(Event::::AccountCreated { who }); + Ok(()) + } + + /// Update the validity status of an existing account. If set to completed, the account + /// will no longer be able to continue through the crowdfund process. + /// + /// We check that the account exists at this stage, but has not completed the process. + /// + /// Origin must match the `ValidityOrigin`. + #[pallet::call_index(1)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] + pub fn update_validity_status( + origin: OriginFor, + who: T::AccountId, + validity: AccountValidity, + ) -> DispatchResult { + T::ValidityOrigin::ensure_origin(origin)?; + ensure!(Accounts::::contains_key(&who), Error::::InvalidAccount); + Accounts::::try_mutate( + &who, + |status: &mut AccountStatus>| -> DispatchResult { + ensure!( + status.validity != AccountValidity::Completed, + Error::::AlreadyCompleted + ); + status.validity = validity; + Ok(()) + }, + )?; + Self::deposit_event(Event::::ValidityUpdated { who, validity }); + Ok(()) + } + + /// Update the balance of a valid account. + /// + /// We check that the account is valid for a balance transfer at this point. + /// + /// Origin must match the `ValidityOrigin`. + #[pallet::call_index(2)] + #[pallet::weight(T::DbWeight::get().reads_writes(2, 1))] + pub fn update_balance( + origin: OriginFor, + who: T::AccountId, + free_balance: BalanceOf, + locked_balance: BalanceOf, + vat: Permill, + ) -> DispatchResult { + T::ValidityOrigin::ensure_origin(origin)?; + + Accounts::::try_mutate( + &who, + |status: &mut AccountStatus>| -> DispatchResult { + // Account has a valid status (not Invalid, Pending, or Completed)... + ensure!(status.validity.is_valid(), Error::::InvalidAccount); + + free_balance.checked_add(&locked_balance).ok_or(Error::::Overflow)?; + status.free_balance = free_balance; + status.locked_balance = locked_balance; + status.vat = vat; + Ok(()) + }, + )?; + Self::deposit_event(Event::::BalanceUpdated { + who, + free: free_balance, + locked: locked_balance, + }); + Ok(()) + } + + /// Pay the user and complete the purchase process. + /// + /// We reverify all assumptions about the state of an account, and complete the process. + /// + /// Origin must match the configured `PaymentAccount` (if it is not configured then this + /// will always fail with `BadOrigin`). + #[pallet::call_index(3)] + #[pallet::weight(T::DbWeight::get().reads_writes(4, 2))] + pub fn payout(origin: OriginFor, who: T::AccountId) -> DispatchResult { + // Payments must be made directly by the `PaymentAccount`. + let payment_account = ensure_signed(origin)?; + let test_against = PaymentAccount::::get().ok_or(DispatchError::BadOrigin)?; + ensure!(payment_account == test_against, DispatchError::BadOrigin); + + // Account should not have a vesting schedule. + ensure!( + T::VestingSchedule::vesting_balance(&who).is_none(), + Error::::VestingScheduleExists + ); + + Accounts::::try_mutate( + &who, + |status: &mut AccountStatus>| -> DispatchResult { + // Account has a valid status (not Invalid, Pending, or Completed)... + ensure!(status.validity.is_valid(), Error::::InvalidAccount); + + // Transfer funds from the payment account into the purchasing user. + let total_balance = status + .free_balance + .checked_add(&status.locked_balance) + .ok_or(Error::::Overflow)?; + T::Currency::transfer( + &payment_account, + &who, + total_balance, + ExistenceRequirement::AllowDeath, + )?; + + if !status.locked_balance.is_zero() { + let unlock_block = UnlockBlock::::get(); + // We allow some configurable portion of the purchased locked DOTs to be + // unlocked for basic usage. + let unlocked = (T::UnlockedProportion::get() * status.locked_balance) + .min(T::MaxUnlocked::get()); + let locked = status.locked_balance.saturating_sub(unlocked); + // We checked that this account has no existing vesting schedule. So this + // function should never fail, however if it does, not much we can do about + // it at this point. + let _ = T::VestingSchedule::add_vesting_schedule( + // Apply vesting schedule to this user + &who, + // For this much amount + locked, + // Unlocking the full amount after one block + locked, + // When everything unlocks + unlock_block, + ); + } + + // Setting the user account to `Completed` ends the purchase process for this + // user. + status.validity = AccountValidity::Completed; + Self::deposit_event(Event::::PaymentComplete { + who: who.clone(), + free: status.free_balance, + locked: status.locked_balance, + }); + Ok(()) + }, + )?; + Ok(()) + } + + /* Configuration Operations */ + + /// Set the account that will be used to payout users in the DOT purchase process. + /// + /// Origin must match the `ConfigurationOrigin` + #[pallet::call_index(4)] + #[pallet::weight(T::DbWeight::get().writes(1))] + pub fn set_payment_account(origin: OriginFor, who: T::AccountId) -> DispatchResult { + T::ConfigurationOrigin::ensure_origin(origin)?; + // Possibly this is worse than having the caller account be the payment account? + PaymentAccount::::put(who.clone()); + Self::deposit_event(Event::::PaymentAccountSet { who }); + Ok(()) + } + + /// Set the statement that must be signed for a user to participate on the DOT sale. + /// + /// Origin must match the `ConfigurationOrigin` + #[pallet::call_index(5)] + #[pallet::weight(T::DbWeight::get().writes(1))] + pub fn set_statement(origin: OriginFor, statement: Vec) -> DispatchResult { + T::ConfigurationOrigin::ensure_origin(origin)?; + ensure!( + (statement.len() as u32) < T::MaxStatementLength::get(), + Error::::InvalidStatement + ); + // Possibly this is worse than having the caller account be the payment account? + Statement::::set(statement); + Self::deposit_event(Event::::StatementUpdated); + Ok(()) + } + + /// Set the block where locked DOTs will become unlocked. + /// + /// Origin must match the `ConfigurationOrigin` + #[pallet::call_index(6)] + #[pallet::weight(T::DbWeight::get().writes(1))] + pub fn set_unlock_block( + origin: OriginFor, + unlock_block: BlockNumberFor, + ) -> DispatchResult { + T::ConfigurationOrigin::ensure_origin(origin)?; + ensure!( + unlock_block > frame_system::Pallet::::block_number(), + Error::::InvalidUnlockBlock + ); + // Possibly this is worse than having the caller account be the payment account? + UnlockBlock::::set(unlock_block); + Self::deposit_event(Event::::UnlockBlockUpdated { block_number: unlock_block }); + Ok(()) + } + } +} + +impl Pallet { + fn verify_signature(who: &T::AccountId, signature: &[u8]) -> Result<(), DispatchError> { + // sr25519 always expects a 64 byte signature. + let signature: AnySignature = sr25519::Signature::try_from(signature) + .map_err(|_| Error::::InvalidSignature)? + .into(); + + // In Polkadot, the AccountId is always the same as the 32 byte public key. + let account_bytes: [u8; 32] = account_to_bytes(who)?; + let public_key = sr25519::Public::from_raw(account_bytes); + + let message = Statement::::get(); + + // Check if everything is good or not. + match signature.verify(message.as_slice(), &public_key) { + true => Ok(()), + false => Err(Error::::InvalidSignature)?, + } + } +} + +// This function converts a 32 byte AccountId to its byte-array equivalent form. +fn account_to_bytes(account: &AccountId) -> Result<[u8; 32], DispatchError> +where + AccountId: Encode, +{ + let account_vec = account.encode(); + ensure!(account_vec.len() == 32, "AccountId must be 32 bytes."); + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(&account_vec); + Ok(bytes) +} + +/// WARNING: Executing this function will clear all storage used by this pallet. +/// Be sure this is what you want... +pub fn remove_pallet() -> frame_support::weights::Weight +where + T: frame_system::Config, +{ + #[allow(deprecated)] + use frame_support::migration::remove_storage_prefix; + #[allow(deprecated)] + remove_storage_prefix(b"Purchase", b"Accounts", b""); + #[allow(deprecated)] + remove_storage_prefix(b"Purchase", b"PaymentAccount", b""); + #[allow(deprecated)] + remove_storage_prefix(b"Purchase", b"Statement", b""); + #[allow(deprecated)] + remove_storage_prefix(b"Purchase", b"UnlockBlock", b""); + + ::BlockWeights::get().max_block +} + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; diff --git a/polkadot/runtime/common/src/purchase/tests.rs b/polkadot/runtime/common/src/purchase/tests.rs new file mode 100644 index 000000000000..8cf2a124d245 --- /dev/null +++ b/polkadot/runtime/common/src/purchase/tests.rs @@ -0,0 +1,547 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Tests for the purchase pallet. + +#[cfg(test)] +use super::*; + +use sp_core::crypto::AccountId32; +// The testing primitives are very useful for avoiding having to work with signatures +// or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. +use frame_support::{assert_noop, assert_ok, traits::Currency}; +use sp_runtime::{traits::Dispatchable, ArithmeticError, DispatchError::BadOrigin}; + +use crate::purchase::mock::*; + +#[test] +fn set_statement_works_and_handles_basic_errors() { + new_test_ext().execute_with(|| { + let statement = b"Test Set Statement".to_vec(); + // Invalid origin + assert_noop!( + Purchase::set_statement(RuntimeOrigin::signed(alice()), statement.clone()), + BadOrigin, + ); + // Too Long + let long_statement = [0u8; 10_000].to_vec(); + assert_noop!( + Purchase::set_statement(RuntimeOrigin::signed(configuration_origin()), long_statement), + Error::::InvalidStatement, + ); + // Just right... + assert_ok!(Purchase::set_statement( + RuntimeOrigin::signed(configuration_origin()), + statement.clone() + )); + assert_eq!(Statement::::get(), statement); + }); +} + +#[test] +fn set_unlock_block_works_and_handles_basic_errors() { + new_test_ext().execute_with(|| { + let unlock_block = 69; + // Invalid origin + assert_noop!( + Purchase::set_unlock_block(RuntimeOrigin::signed(alice()), unlock_block), + BadOrigin, + ); + // Block Number in Past + let bad_unlock_block = 50; + System::set_block_number(bad_unlock_block); + assert_noop!( + Purchase::set_unlock_block( + RuntimeOrigin::signed(configuration_origin()), + bad_unlock_block + ), + Error::::InvalidUnlockBlock, + ); + // Just right... + assert_ok!(Purchase::set_unlock_block( + RuntimeOrigin::signed(configuration_origin()), + unlock_block + )); + assert_eq!(UnlockBlock::::get(), unlock_block); + }); +} + +#[test] +fn set_payment_account_works_and_handles_basic_errors() { + new_test_ext().execute_with(|| { + let payment_account: AccountId32 = [69u8; 32].into(); + // Invalid Origin + assert_noop!( + Purchase::set_payment_account(RuntimeOrigin::signed(alice()), payment_account.clone()), + BadOrigin, + ); + // Just right... + assert_ok!(Purchase::set_payment_account( + RuntimeOrigin::signed(configuration_origin()), + payment_account.clone() + )); + assert_eq!(PaymentAccount::::get(), Some(payment_account)); + }); +} + +#[test] +fn signature_verification_works() { + new_test_ext().execute_with(|| { + assert_ok!(Purchase::verify_signature(&alice(), &alice_signature())); + assert_ok!(Purchase::verify_signature(&alice_ed25519(), &alice_signature_ed25519())); + assert_ok!(Purchase::verify_signature(&bob(), &bob_signature())); + + // Mixing and matching fails + assert_noop!( + Purchase::verify_signature(&alice(), &bob_signature()), + Error::::InvalidSignature + ); + assert_noop!( + Purchase::verify_signature(&bob(), &alice_signature()), + Error::::InvalidSignature + ); + }); +} + +#[test] +fn account_creation_works() { + new_test_ext().execute_with(|| { + assert!(!Accounts::::contains_key(alice())); + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec(), + )); + assert_eq!( + Accounts::::get(alice()), + AccountStatus { + validity: AccountValidity::Initiated, + free_balance: Zero::zero(), + locked_balance: Zero::zero(), + signature: alice_signature().to_vec(), + vat: Permill::zero(), + } + ); + }); +} + +#[test] +fn account_creation_handles_basic_errors() { + new_test_ext().execute_with(|| { + // Wrong Origin + assert_noop!( + Purchase::create_account( + RuntimeOrigin::signed(alice()), + alice(), + alice_signature().to_vec() + ), + BadOrigin, + ); + + // Wrong Account/Signature + assert_noop!( + Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + bob_signature().to_vec() + ), + Error::::InvalidSignature, + ); + + // Account with vesting + Balances::make_free_balance_be(&alice(), 100); + assert_ok!(::VestingSchedule::add_vesting_schedule(&alice(), 100, 1, 50)); + assert_noop!( + Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec() + ), + Error::::VestingScheduleExists, + ); + + // Duplicate Purchasing Account + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + bob(), + bob_signature().to_vec() + )); + assert_noop!( + Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + bob(), + bob_signature().to_vec() + ), + Error::::ExistingAccount, + ); + }); +} + +#[test] +fn update_validity_status_works() { + new_test_ext().execute_with(|| { + // Alice account is created. + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec(), + )); + // She submits KYC, and we update the status to `Pending`. + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::Pending, + )); + // KYC comes back negative, so we mark the account invalid. + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::Invalid, + )); + assert_eq!( + Accounts::::get(alice()), + AccountStatus { + validity: AccountValidity::Invalid, + free_balance: Zero::zero(), + locked_balance: Zero::zero(), + signature: alice_signature().to_vec(), + vat: Permill::zero(), + } + ); + // She fixes it, we mark her account valid. + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::ValidLow, + )); + assert_eq!( + Accounts::::get(alice()), + AccountStatus { + validity: AccountValidity::ValidLow, + free_balance: Zero::zero(), + locked_balance: Zero::zero(), + signature: alice_signature().to_vec(), + vat: Permill::zero(), + } + ); + }); +} + +#[test] +fn update_validity_status_handles_basic_errors() { + new_test_ext().execute_with(|| { + // Wrong Origin + assert_noop!( + Purchase::update_validity_status( + RuntimeOrigin::signed(alice()), + alice(), + AccountValidity::Pending, + ), + BadOrigin + ); + // Inactive Account + assert_noop!( + Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::Pending, + ), + Error::::InvalidAccount + ); + // Already Completed + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec(), + )); + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::Completed, + )); + assert_noop!( + Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::Pending, + ), + Error::::AlreadyCompleted + ); + }); +} + +#[test] +fn update_balance_works() { + new_test_ext().execute_with(|| { + // Alice account is created + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec() + )); + // And approved for basic contribution + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::ValidLow, + )); + // We set a balance on the user based on the payment they made. 50 locked, 50 free. + assert_ok!(Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + 50, + 50, + Permill::from_rational(77u32, 1000u32), + )); + assert_eq!( + Accounts::::get(alice()), + AccountStatus { + validity: AccountValidity::ValidLow, + free_balance: 50, + locked_balance: 50, + signature: alice_signature().to_vec(), + vat: Permill::from_parts(77000), + } + ); + // We can update the balance based on new information. + assert_ok!(Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + 25, + 50, + Permill::zero(), + )); + assert_eq!( + Accounts::::get(alice()), + AccountStatus { + validity: AccountValidity::ValidLow, + free_balance: 25, + locked_balance: 50, + signature: alice_signature().to_vec(), + vat: Permill::zero(), + } + ); + }); +} + +#[test] +fn update_balance_handles_basic_errors() { + new_test_ext().execute_with(|| { + // Wrong Origin + assert_noop!( + Purchase::update_balance( + RuntimeOrigin::signed(alice()), + alice(), + 50, + 50, + Permill::zero(), + ), + BadOrigin + ); + // Inactive Account + assert_noop!( + Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + 50, + 50, + Permill::zero(), + ), + Error::::InvalidAccount + ); + // Overflow + assert_noop!( + Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + u64::MAX, + u64::MAX, + Permill::zero(), + ), + Error::::InvalidAccount + ); + }); +} + +#[test] +fn payout_works() { + new_test_ext().execute_with(|| { + // Alice and Bob accounts are created + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec() + )); + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + bob(), + bob_signature().to_vec() + )); + // Alice is approved for basic contribution + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::ValidLow, + )); + // Bob is approved for high contribution + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + bob(), + AccountValidity::ValidHigh, + )); + // We set a balance on the users based on the payment they made. 50 locked, 50 free. + assert_ok!(Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + 50, + 50, + Permill::zero(), + )); + assert_ok!(Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + bob(), + 100, + 150, + Permill::zero(), + )); + // Now we call payout for Alice and Bob. + assert_ok!(Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),)); + assert_ok!(Purchase::payout(RuntimeOrigin::signed(payment_account()), bob(),)); + // Payment is made. + assert_eq!(::Currency::free_balance(&payment_account()), 99_650); + assert_eq!(::Currency::free_balance(&alice()), 100); + // 10% of the 50 units is unlocked automatically for Alice + assert_eq!(::VestingSchedule::vesting_balance(&alice()), Some(45)); + assert_eq!(::Currency::free_balance(&bob()), 250); + // A max of 10 units is unlocked automatically for Bob + assert_eq!(::VestingSchedule::vesting_balance(&bob()), Some(140)); + // Status is completed. + assert_eq!( + Accounts::::get(alice()), + AccountStatus { + validity: AccountValidity::Completed, + free_balance: 50, + locked_balance: 50, + signature: alice_signature().to_vec(), + vat: Permill::zero(), + } + ); + assert_eq!( + Accounts::::get(bob()), + AccountStatus { + validity: AccountValidity::Completed, + free_balance: 100, + locked_balance: 150, + signature: bob_signature().to_vec(), + vat: Permill::zero(), + } + ); + // Vesting lock is removed in whole on block 101 (100 blocks after block 1) + System::set_block_number(100); + let vest_call = RuntimeCall::Vesting(pallet_vesting::Call::::vest {}); + assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(alice()))); + assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(bob()))); + assert_eq!(::VestingSchedule::vesting_balance(&alice()), Some(45)); + assert_eq!(::VestingSchedule::vesting_balance(&bob()), Some(140)); + System::set_block_number(101); + assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(alice()))); + assert_ok!(vest_call.clone().dispatch(RuntimeOrigin::signed(bob()))); + assert_eq!(::VestingSchedule::vesting_balance(&alice()), None); + assert_eq!(::VestingSchedule::vesting_balance(&bob()), None); + }); +} + +#[test] +fn payout_handles_basic_errors() { + new_test_ext().execute_with(|| { + // Wrong Origin + assert_noop!(Purchase::payout(RuntimeOrigin::signed(alice()), alice(),), BadOrigin); + // Account with Existing Vesting Schedule + Balances::make_free_balance_be(&bob(), 100); + assert_ok!(::VestingSchedule::add_vesting_schedule(&bob(), 100, 1, 50,)); + assert_noop!( + Purchase::payout(RuntimeOrigin::signed(payment_account()), bob(),), + Error::::VestingScheduleExists + ); + // Invalid Account (never created) + assert_noop!( + Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),), + Error::::InvalidAccount + ); + // Invalid Account (created, but not valid) + assert_ok!(Purchase::create_account( + RuntimeOrigin::signed(validity_origin()), + alice(), + alice_signature().to_vec() + )); + assert_noop!( + Purchase::payout(RuntimeOrigin::signed(payment_account()), alice(),), + Error::::InvalidAccount + ); + // Not enough funds in payment account + assert_ok!(Purchase::update_validity_status( + RuntimeOrigin::signed(validity_origin()), + alice(), + AccountValidity::ValidHigh, + )); + assert_ok!(Purchase::update_balance( + RuntimeOrigin::signed(validity_origin()), + alice(), + 100_000, + 100_000, + Permill::zero(), + )); + assert_noop!( + Purchase::payout(RuntimeOrigin::signed(payment_account()), alice()), + ArithmeticError::Underflow + ); + }); +} + +#[test] +fn remove_pallet_works() { + new_test_ext().execute_with(|| { + let account_status = AccountStatus { + validity: AccountValidity::Completed, + free_balance: 1234, + locked_balance: 4321, + signature: b"my signature".to_vec(), + vat: Permill::from_percent(50), + }; + + // Add some storage. + Accounts::::insert(alice(), account_status.clone()); + Accounts::::insert(bob(), account_status); + PaymentAccount::::put(alice()); + Statement::::put(b"hello, world!".to_vec()); + UnlockBlock::::put(4); + + // Verify storage exists. + assert_eq!(Accounts::::iter().count(), 2); + assert!(PaymentAccount::::exists()); + assert!(Statement::::exists()); + assert!(UnlockBlock::::exists()); + + // Remove storage. + remove_pallet::(); + + // Verify storage is gone. + assert_eq!(Accounts::::iter().count(), 0); + assert!(!PaymentAccount::::exists()); + assert!(!Statement::::exists()); + assert!(!UnlockBlock::::exists()); + }); +} From 0de6854563bbec2f6b9c46dad14d3a3628148e4b Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Thu, 19 Dec 2024 02:25:47 +0100 Subject: [PATCH 193/340] Pallet:Auction move tests and benchmark to seperate files (#6746) # Description Linked to issue #590. I moved the tests and benchmarking to their own seperate file to reduce the bloat inside auctions.rs Co-authored-by: Shawn Tabrizi --- polkadot/runtime/common/src/auctions.rs | 1980 ----------------- .../common/src/auctions/benchmarking.rs | 282 +++ polkadot/runtime/common/src/auctions/mock.rs | 258 +++ polkadot/runtime/common/src/auctions/mod.rs | 677 ++++++ polkadot/runtime/common/src/auctions/tests.rs | 821 +++++++ 5 files changed, 2038 insertions(+), 1980 deletions(-) delete mode 100644 polkadot/runtime/common/src/auctions.rs create mode 100644 polkadot/runtime/common/src/auctions/benchmarking.rs create mode 100644 polkadot/runtime/common/src/auctions/mock.rs create mode 100644 polkadot/runtime/common/src/auctions/mod.rs create mode 100644 polkadot/runtime/common/src/auctions/tests.rs diff --git a/polkadot/runtime/common/src/auctions.rs b/polkadot/runtime/common/src/auctions.rs deleted file mode 100644 index 3ac1ba2dc4e0..000000000000 --- a/polkadot/runtime/common/src/auctions.rs +++ /dev/null @@ -1,1980 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Polkadot. - -// Polkadot is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Polkadot is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Polkadot. If not, see . - -//! Auctioning system to determine the set of Parachains in operation. This includes logic for the -//! auctioning mechanism and for reserving balance as part of the "payment". Unreserving the balance -//! happens elsewhere. - -use crate::{ - slot_range::SlotRange, - traits::{AuctionStatus, Auctioneer, LeaseError, Leaser, Registrar}, -}; -use alloc::{vec, vec::Vec}; -use codec::Decode; -use core::mem::swap; -use frame_support::{ - dispatch::DispatchResult, - ensure, - traits::{Currency, Get, Randomness, ReservableCurrency}, - weights::Weight, -}; -use frame_system::pallet_prelude::BlockNumberFor; -pub use pallet::*; -use polkadot_primitives::Id as ParaId; -use sp_runtime::traits::{CheckedSub, One, Saturating, Zero}; - -type CurrencyOf = <::Leaser as Leaser>>::Currency; -type BalanceOf = <<::Leaser as Leaser>>::Currency as Currency< - ::AccountId, ->>::Balance; - -pub trait WeightInfo { - fn new_auction() -> Weight; - fn bid() -> Weight; - fn cancel_auction() -> Weight; - fn on_initialize() -> Weight; -} - -pub struct TestWeightInfo; -impl WeightInfo for TestWeightInfo { - fn new_auction() -> Weight { - Weight::zero() - } - fn bid() -> Weight { - Weight::zero() - } - fn cancel_auction() -> Weight { - Weight::zero() - } - fn on_initialize() -> Weight { - Weight::zero() - } -} - -/// An auction index. We count auctions in this type. -pub type AuctionIndex = u32; - -type LeasePeriodOf = <::Leaser as Leaser>>::LeasePeriod; - -// Winning data type. This encodes the top bidders of each range together with their bid. -type WinningData = [Option<(::AccountId, ParaId, BalanceOf)>; - SlotRange::SLOT_RANGE_COUNT]; -// Winners data type. This encodes each of the final winners of a parachain auction, the parachain -// index assigned to them, their winning bid and the range that they won. -type WinnersData = - Vec<(::AccountId, ParaId, BalanceOf, SlotRange)>; - -#[frame_support::pallet] -pub mod pallet { - use super::*; - use frame_support::{dispatch::DispatchClass, pallet_prelude::*, traits::EnsureOrigin}; - use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; - - #[pallet::pallet] - pub struct Pallet(_); - - /// The module's configuration trait. - #[pallet::config] - pub trait Config: frame_system::Config { - /// The overarching event type. - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// The type representing the leasing system. - type Leaser: Leaser< - BlockNumberFor, - AccountId = Self::AccountId, - LeasePeriod = BlockNumberFor, - >; - - /// The parachain registrar type. - type Registrar: Registrar; - - /// The number of blocks over which an auction may be retroactively ended. - #[pallet::constant] - type EndingPeriod: Get>; - - /// The length of each sample to take during the ending period. - /// - /// `EndingPeriod` / `SampleLength` = Total # of Samples - #[pallet::constant] - type SampleLength: Get>; - - /// Something that provides randomness in the runtime. - type Randomness: Randomness>; - - /// The origin which may initiate auctions. - type InitiateOrigin: EnsureOrigin; - - /// Weight Information for the Extrinsics in the Pallet - type WeightInfo: WeightInfo; - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// An auction started. Provides its index and the block number where it will begin to - /// close and the first lease period of the quadruplet that is auctioned. - AuctionStarted { - auction_index: AuctionIndex, - lease_period: LeasePeriodOf, - ending: BlockNumberFor, - }, - /// An auction ended. All funds become unreserved. - AuctionClosed { auction_index: AuctionIndex }, - /// Funds were reserved for a winning bid. First balance is the extra amount reserved. - /// Second is the total. - Reserved { bidder: T::AccountId, extra_reserved: BalanceOf, total_amount: BalanceOf }, - /// Funds were unreserved since bidder is no longer active. `[bidder, amount]` - Unreserved { bidder: T::AccountId, amount: BalanceOf }, - /// Someone attempted to lease the same slot twice for a parachain. The amount is held in - /// reserve but no parachain slot has been leased. - ReserveConfiscated { para_id: ParaId, leaser: T::AccountId, amount: BalanceOf }, - /// A new bid has been accepted as the current winner. - BidAccepted { - bidder: T::AccountId, - para_id: ParaId, - amount: BalanceOf, - first_slot: LeasePeriodOf, - last_slot: LeasePeriodOf, - }, - /// The winning offset was chosen for an auction. This will map into the `Winning` storage - /// map. - WinningOffset { auction_index: AuctionIndex, block_number: BlockNumberFor }, - } - - #[pallet::error] - pub enum Error { - /// This auction is already in progress. - AuctionInProgress, - /// The lease period is in the past. - LeasePeriodInPast, - /// Para is not registered - ParaNotRegistered, - /// Not a current auction. - NotCurrentAuction, - /// Not an auction. - NotAuction, - /// Auction has already ended. - AuctionEnded, - /// The para is already leased out for part of this range. - AlreadyLeasedOut, - } - - /// Number of auctions started so far. - #[pallet::storage] - pub type AuctionCounter = StorageValue<_, AuctionIndex, ValueQuery>; - - /// Information relating to the current auction, if there is one. - /// - /// The first item in the tuple is the lease period index that the first of the four - /// contiguous lease periods on auction is for. The second is the block number when the - /// auction will "begin to end", i.e. the first block of the Ending Period of the auction. - #[pallet::storage] - pub type AuctionInfo = StorageValue<_, (LeasePeriodOf, BlockNumberFor)>; - - /// Amounts currently reserved in the accounts of the bidders currently winning - /// (sub-)ranges. - #[pallet::storage] - pub type ReservedAmounts = - StorageMap<_, Twox64Concat, (T::AccountId, ParaId), BalanceOf>; - - /// The winning bids for each of the 10 ranges at each sample in the final Ending Period of - /// the current auction. The map's key is the 0-based index into the Sample Size. The - /// first sample of the ending period is 0; the last is `Sample Size - 1`. - #[pallet::storage] - pub type Winning = StorageMap<_, Twox64Concat, BlockNumberFor, WinningData>; - - #[pallet::extra_constants] - impl Pallet { - #[pallet::constant_name(SlotRangeCount)] - fn slot_range_count() -> u32 { - SlotRange::SLOT_RANGE_COUNT as u32 - } - - #[pallet::constant_name(LeasePeriodsPerSlot)] - fn lease_periods_per_slot() -> u32 { - SlotRange::LEASE_PERIODS_PER_SLOT as u32 - } - } - - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(n: BlockNumberFor) -> Weight { - let mut weight = T::DbWeight::get().reads(1); - - // If the current auction was in its ending period last block, then ensure that the - // (sub-)range winner information is duplicated from the previous block in case no bids - // happened in the last block. - if let AuctionStatus::EndingPeriod(offset, _sub_sample) = Self::auction_status(n) { - weight = weight.saturating_add(T::DbWeight::get().reads(1)); - if !Winning::::contains_key(&offset) { - weight = weight.saturating_add(T::DbWeight::get().writes(1)); - let winning_data = offset - .checked_sub(&One::one()) - .and_then(Winning::::get) - .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); - Winning::::insert(offset, winning_data); - } - } - - // Check to see if an auction just ended. - if let Some((winning_ranges, auction_lease_period_index)) = Self::check_auction_end(n) { - // Auction is ended now. We have the winning ranges and the lease period index which - // acts as the offset. Handle it. - Self::manage_auction_end(auction_lease_period_index, winning_ranges); - weight = weight.saturating_add(T::WeightInfo::on_initialize()); - } - - weight - } - } - - #[pallet::call] - impl Pallet { - /// Create a new auction. - /// - /// This can only happen when there isn't already an auction in progress and may only be - /// called by the root origin. Accepts the `duration` of this auction and the - /// `lease_period_index` of the initial lease period of the four that are to be auctioned. - #[pallet::call_index(0)] - #[pallet::weight((T::WeightInfo::new_auction(), DispatchClass::Operational))] - pub fn new_auction( - origin: OriginFor, - #[pallet::compact] duration: BlockNumberFor, - #[pallet::compact] lease_period_index: LeasePeriodOf, - ) -> DispatchResult { - T::InitiateOrigin::ensure_origin(origin)?; - Self::do_new_auction(duration, lease_period_index) - } - - /// Make a new bid from an account (including a parachain account) for deploying a new - /// parachain. - /// - /// Multiple simultaneous bids from the same bidder are allowed only as long as all active - /// bids overlap each other (i.e. are mutually exclusive). Bids cannot be redacted. - /// - /// - `sub` is the sub-bidder ID, allowing for multiple competing bids to be made by (and - /// funded by) the same account. - /// - `auction_index` is the index of the auction to bid on. Should just be the present - /// value of `AuctionCounter`. - /// - `first_slot` is the first lease period index of the range to bid on. This is the - /// absolute lease period index value, not an auction-specific offset. - /// - `last_slot` is the last lease period index of the range to bid on. This is the - /// absolute lease period index value, not an auction-specific offset. - /// - `amount` is the amount to bid to be held as deposit for the parachain should the - /// bid win. This amount is held throughout the range. - #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::bid())] - pub fn bid( - origin: OriginFor, - #[pallet::compact] para: ParaId, - #[pallet::compact] auction_index: AuctionIndex, - #[pallet::compact] first_slot: LeasePeriodOf, - #[pallet::compact] last_slot: LeasePeriodOf, - #[pallet::compact] amount: BalanceOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::handle_bid(who, para, auction_index, first_slot, last_slot, amount)?; - Ok(()) - } - - /// Cancel an in-progress auction. - /// - /// Can only be called by Root origin. - #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::cancel_auction())] - pub fn cancel_auction(origin: OriginFor) -> DispatchResult { - ensure_root(origin)?; - // Unreserve all bids. - for ((bidder, _), amount) in ReservedAmounts::::drain() { - CurrencyOf::::unreserve(&bidder, amount); - } - #[allow(deprecated)] - Winning::::remove_all(None); - AuctionInfo::::kill(); - Ok(()) - } - } -} - -impl Auctioneer> for Pallet { - type AccountId = T::AccountId; - type LeasePeriod = BlockNumberFor; - type Currency = CurrencyOf; - - fn new_auction( - duration: BlockNumberFor, - lease_period_index: LeasePeriodOf, - ) -> DispatchResult { - Self::do_new_auction(duration, lease_period_index) - } - - // Returns the status of the auction given the current block number. - fn auction_status(now: BlockNumberFor) -> AuctionStatus> { - let early_end = match AuctionInfo::::get() { - Some((_, early_end)) => early_end, - None => return AuctionStatus::NotStarted, - }; - - let after_early_end = match now.checked_sub(&early_end) { - Some(after_early_end) => after_early_end, - None => return AuctionStatus::StartingPeriod, - }; - - let ending_period = T::EndingPeriod::get(); - if after_early_end < ending_period { - let sample_length = T::SampleLength::get().max(One::one()); - let sample = after_early_end / sample_length; - let sub_sample = after_early_end % sample_length; - return AuctionStatus::EndingPeriod(sample, sub_sample); - } else { - // This is safe because of the comparison operator above - return AuctionStatus::VrfDelay(after_early_end - ending_period); - } - } - - fn place_bid( - bidder: T::AccountId, - para: ParaId, - first_slot: LeasePeriodOf, - last_slot: LeasePeriodOf, - amount: BalanceOf, - ) -> DispatchResult { - Self::handle_bid(bidder, para, AuctionCounter::::get(), first_slot, last_slot, amount) - } - - fn lease_period_index(b: BlockNumberFor) -> Option<(Self::LeasePeriod, bool)> { - T::Leaser::lease_period_index(b) - } - - #[cfg(any(feature = "runtime-benchmarks", test))] - fn lease_period_length() -> (BlockNumberFor, BlockNumberFor) { - T::Leaser::lease_period_length() - } - - fn has_won_an_auction(para: ParaId, bidder: &T::AccountId) -> bool { - !T::Leaser::deposit_held(para, bidder).is_zero() - } -} - -impl Pallet { - // A trick to allow me to initialize large arrays with nothing in them. - const EMPTY: Option<(::AccountId, ParaId, BalanceOf)> = None; - - /// Create a new auction. - /// - /// This can only happen when there isn't already an auction in progress. Accepts the `duration` - /// of this auction and the `lease_period_index` of the initial lease period of the four that - /// are to be auctioned. - fn do_new_auction( - duration: BlockNumberFor, - lease_period_index: LeasePeriodOf, - ) -> DispatchResult { - let maybe_auction = AuctionInfo::::get(); - ensure!(maybe_auction.is_none(), Error::::AuctionInProgress); - let now = frame_system::Pallet::::block_number(); - if let Some((current_lease_period, _)) = T::Leaser::lease_period_index(now) { - // If there is no active lease period, then we don't need to make this check. - ensure!(lease_period_index >= current_lease_period, Error::::LeasePeriodInPast); - } - - // Bump the counter. - let n = AuctionCounter::::mutate(|n| { - *n += 1; - *n - }); - - // Set the information. - let ending = frame_system::Pallet::::block_number().saturating_add(duration); - AuctionInfo::::put((lease_period_index, ending)); - - Self::deposit_event(Event::::AuctionStarted { - auction_index: n, - lease_period: lease_period_index, - ending, - }); - Ok(()) - } - - /// Actually place a bid in the current auction. - /// - /// - `bidder`: The account that will be funding this bid. - /// - `auction_index`: The auction index of the bid. For this to succeed, must equal - /// the current value of `AuctionCounter`. - /// - `first_slot`: The first lease period index of the range to be bid on. - /// - `last_slot`: The last lease period index of the range to be bid on (inclusive). - /// - `amount`: The total amount to be the bid for deposit over the range. - pub fn handle_bid( - bidder: T::AccountId, - para: ParaId, - auction_index: u32, - first_slot: LeasePeriodOf, - last_slot: LeasePeriodOf, - amount: BalanceOf, - ) -> DispatchResult { - // Ensure para is registered before placing a bid on it. - ensure!(T::Registrar::is_registered(para), Error::::ParaNotRegistered); - // Bidding on latest auction. - ensure!(auction_index == AuctionCounter::::get(), Error::::NotCurrentAuction); - // Assume it's actually an auction (this should never fail because of above). - let (first_lease_period, _) = AuctionInfo::::get().ok_or(Error::::NotAuction)?; - - // Get the auction status and the current sample block. For the starting period, the sample - // block is zero. - let auction_status = Self::auction_status(frame_system::Pallet::::block_number()); - // The offset into the ending samples of the auction. - let offset = match auction_status { - AuctionStatus::NotStarted => return Err(Error::::AuctionEnded.into()), - AuctionStatus::StartingPeriod => Zero::zero(), - AuctionStatus::EndingPeriod(o, _) => o, - AuctionStatus::VrfDelay(_) => return Err(Error::::AuctionEnded.into()), - }; - - // We also make sure that the bid is not for any existing leases the para already has. - ensure!( - !T::Leaser::already_leased(para, first_slot, last_slot), - Error::::AlreadyLeasedOut - ); - - // Our range. - let range = SlotRange::new_bounded(first_lease_period, first_slot, last_slot)?; - // Range as an array index. - let range_index = range as u8 as usize; - - // The current winning ranges. - let mut current_winning = Winning::::get(offset) - .or_else(|| offset.checked_sub(&One::one()).and_then(Winning::::get)) - .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); - - // If this bid beat the previous winner of our range. - if current_winning[range_index].as_ref().map_or(true, |last| amount > last.2) { - // Ok; we are the new winner of this range - reserve the additional amount and record. - - // Get the amount already held on deposit if this is a renewal bid (i.e. there's - // an existing lease on the same para by the same leaser). - let existing_lease_deposit = T::Leaser::deposit_held(para, &bidder); - let reserve_required = amount.saturating_sub(existing_lease_deposit); - - // Get the amount already reserved in any prior and still active bids by us. - let bidder_para = (bidder.clone(), para); - let already_reserved = ReservedAmounts::::get(&bidder_para).unwrap_or_default(); - - // If these don't already cover the bid... - if let Some(additional) = reserve_required.checked_sub(&already_reserved) { - // ...then reserve some more funds from their account, failing if there's not - // enough funds. - CurrencyOf::::reserve(&bidder, additional)?; - // ...and record the amount reserved. - ReservedAmounts::::insert(&bidder_para, reserve_required); - - Self::deposit_event(Event::::Reserved { - bidder: bidder.clone(), - extra_reserved: additional, - total_amount: reserve_required, - }); - } - - // Return any funds reserved for the previous winner if we are not in the ending period - // and they no longer have any active bids. - let mut outgoing_winner = Some((bidder.clone(), para, amount)); - swap(&mut current_winning[range_index], &mut outgoing_winner); - if let Some((who, para, _amount)) = outgoing_winner { - if auction_status.is_starting() && - current_winning - .iter() - .filter_map(Option::as_ref) - .all(|&(ref other, other_para, _)| other != &who || other_para != para) - { - // Previous bidder is no longer winning any ranges: unreserve their funds. - if let Some(amount) = ReservedAmounts::::take(&(who.clone(), para)) { - // It really should be reserved; there's not much we can do here on fail. - let err_amt = CurrencyOf::::unreserve(&who, amount); - debug_assert!(err_amt.is_zero()); - Self::deposit_event(Event::::Unreserved { bidder: who, amount }); - } - } - } - - // Update the range winner. - Winning::::insert(offset, ¤t_winning); - Self::deposit_event(Event::::BidAccepted { - bidder, - para_id: para, - amount, - first_slot, - last_slot, - }); - } - Ok(()) - } - - /// Some when the auction's end is known (with the end block number). None if it is unknown. - /// If `Some` then the block number must be at most the previous block and at least the - /// previous block minus `T::EndingPeriod::get()`. - /// - /// This mutates the state, cleaning up `AuctionInfo` and `Winning` in the case of an auction - /// ending. An immediately subsequent call with the same argument will always return `None`. - fn check_auction_end(now: BlockNumberFor) -> Option<(WinningData, LeasePeriodOf)> { - if let Some((lease_period_index, early_end)) = AuctionInfo::::get() { - let ending_period = T::EndingPeriod::get(); - let late_end = early_end.saturating_add(ending_period); - let is_ended = now >= late_end; - if is_ended { - // auction definitely ended. - // check to see if we can determine the actual ending point. - let (raw_offset, known_since) = T::Randomness::random(&b"para_auction"[..]); - - if late_end <= known_since { - // Our random seed was known only after the auction ended. Good to use. - let raw_offset_block_number = >::decode( - &mut raw_offset.as_ref(), - ) - .expect("secure hashes should always be bigger than the block number; qed"); - let offset = (raw_offset_block_number % ending_period) / - T::SampleLength::get().max(One::one()); - - let auction_counter = AuctionCounter::::get(); - Self::deposit_event(Event::::WinningOffset { - auction_index: auction_counter, - block_number: offset, - }); - let res = Winning::::get(offset) - .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); - // This `remove_all` statement should remove at most `EndingPeriod` / - // `SampleLength` items, which should be bounded and sensibly configured in the - // runtime. - #[allow(deprecated)] - Winning::::remove_all(None); - AuctionInfo::::kill(); - return Some((res, lease_period_index)); - } - } - } - None - } - - /// Auction just ended. We have the current lease period, the auction's lease period (which - /// is guaranteed to be at least the current period) and the bidders that were winning each - /// range at the time of the auction's close. - fn manage_auction_end( - auction_lease_period_index: LeasePeriodOf, - winning_ranges: WinningData, - ) { - // First, unreserve all amounts that were reserved for the bids. We will later re-reserve - // the amounts from the bidders that ended up being assigned the slot so there's no need to - // special-case them here. - for ((bidder, _), amount) in ReservedAmounts::::drain() { - CurrencyOf::::unreserve(&bidder, amount); - } - - // Next, calculate the winning combination of slots and thus the final winners of the - // auction. - let winners = Self::calculate_winners(winning_ranges); - - // Go through those winners and re-reserve their bid, updating our table of deposits - // accordingly. - for (leaser, para, amount, range) in winners.into_iter() { - let begin_offset = LeasePeriodOf::::from(range.as_pair().0 as u32); - let period_begin = auction_lease_period_index + begin_offset; - let period_count = LeasePeriodOf::::from(range.len() as u32); - - match T::Leaser::lease_out(para, &leaser, amount, period_begin, period_count) { - Err(LeaseError::ReserveFailed) | - Err(LeaseError::AlreadyEnded) | - Err(LeaseError::NoLeasePeriod) => { - // Should never happen since we just unreserved this amount (and our offset is - // from the present period). But if it does, there's not much we can do. - }, - Err(LeaseError::AlreadyLeased) => { - // The leaser attempted to get a second lease on the same para ID, possibly - // griefing us. Let's keep the amount reserved and let governance sort it out. - if CurrencyOf::::reserve(&leaser, amount).is_ok() { - Self::deposit_event(Event::::ReserveConfiscated { - para_id: para, - leaser, - amount, - }); - } - }, - Ok(()) => {}, // Nothing to report. - } - } - - Self::deposit_event(Event::::AuctionClosed { - auction_index: AuctionCounter::::get(), - }); - } - - /// Calculate the final winners from the winning slots. - /// - /// This is a simple dynamic programming algorithm designed by Al, the original code is at: - /// `https://github.com/w3f/consensus/blob/master/NPoS/auctiondynamicthing.py` - fn calculate_winners(mut winning: WinningData) -> WinnersData { - let winning_ranges = { - let mut best_winners_ending_at: [(Vec, BalanceOf); - SlotRange::LEASE_PERIODS_PER_SLOT] = Default::default(); - let best_bid = |range: SlotRange| { - winning[range as u8 as usize] - .as_ref() - .map(|(_, _, amount)| *amount * (range.len() as u32).into()) - }; - for i in 0..SlotRange::LEASE_PERIODS_PER_SLOT { - let r = SlotRange::new_bounded(0, 0, i as u32).expect("`i < LPPS`; qed"); - if let Some(bid) = best_bid(r) { - best_winners_ending_at[i] = (vec![r], bid); - } - for j in 0..i { - let r = SlotRange::new_bounded(0, j as u32 + 1, i as u32) - .expect("`i < LPPS`; `j < i`; `j + 1 < LPPS`; qed"); - if let Some(mut bid) = best_bid(r) { - bid += best_winners_ending_at[j].1; - if bid > best_winners_ending_at[i].1 { - let mut new_winners = best_winners_ending_at[j].0.clone(); - new_winners.push(r); - best_winners_ending_at[i] = (new_winners, bid); - } - } else { - if best_winners_ending_at[j].1 > best_winners_ending_at[i].1 { - best_winners_ending_at[i] = best_winners_ending_at[j].clone(); - } - } - } - } - best_winners_ending_at[SlotRange::LEASE_PERIODS_PER_SLOT - 1].0.clone() - }; - - winning_ranges - .into_iter() - .filter_map(|range| { - winning[range as u8 as usize] - .take() - .map(|(bidder, para, amount)| (bidder, para, amount, range)) - }) - .collect::>() - } -} - -/// tests for this module -#[cfg(test)] -mod tests { - use super::*; - use crate::{auctions, mock::TestRegistrar}; - use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, derive_impl, ord_parameter_types, - parameter_types, - traits::{EitherOfDiverse, OnFinalize, OnInitialize}, - }; - use frame_system::{EnsureRoot, EnsureSignedBy}; - use pallet_balances; - use polkadot_primitives::{BlockNumber, Id as ParaId}; - use polkadot_primitives_test_helpers::{dummy_hash, dummy_head_data, dummy_validation_code}; - use sp_core::H256; - use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, - DispatchError::BadOrigin, - }; - use std::{cell::RefCell, collections::BTreeMap}; - - type Block = frame_system::mocking::MockBlockU32; - - frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Balances: pallet_balances, - Auctions: auctions, - } - ); - - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] - impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - } - - #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] - impl pallet_balances::Config for Test { - type AccountStore = System; - } - - #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] - pub struct LeaseData { - leaser: u64, - amount: u64, - } - - thread_local! { - pub static LEASES: - RefCell> = RefCell::new(BTreeMap::new()); - } - - fn leases() -> Vec<((ParaId, BlockNumber), LeaseData)> { - LEASES.with(|p| (&*p.borrow()).clone().into_iter().collect::>()) - } - - pub struct TestLeaser; - impl Leaser for TestLeaser { - type AccountId = u64; - type LeasePeriod = BlockNumber; - type Currency = Balances; - - fn lease_out( - para: ParaId, - leaser: &Self::AccountId, - amount: >::Balance, - period_begin: Self::LeasePeriod, - period_count: Self::LeasePeriod, - ) -> Result<(), LeaseError> { - LEASES.with(|l| { - let mut leases = l.borrow_mut(); - let now = System::block_number(); - let (current_lease_period, _) = - Self::lease_period_index(now).ok_or(LeaseError::NoLeasePeriod)?; - if period_begin < current_lease_period { - return Err(LeaseError::AlreadyEnded); - } - for period in period_begin..(period_begin + period_count) { - if leases.contains_key(&(para, period)) { - return Err(LeaseError::AlreadyLeased); - } - leases.insert((para, period), LeaseData { leaser: *leaser, amount }); - } - Ok(()) - }) - } - - fn deposit_held( - para: ParaId, - leaser: &Self::AccountId, - ) -> >::Balance { - leases() - .iter() - .filter_map(|((id, _period), data)| { - if id == ¶ && &data.leaser == leaser { - Some(data.amount) - } else { - None - } - }) - .max() - .unwrap_or_default() - } - - fn lease_period_length() -> (BlockNumber, BlockNumber) { - (10, 0) - } - - fn lease_period_index(b: BlockNumber) -> Option<(Self::LeasePeriod, bool)> { - let (lease_period_length, offset) = Self::lease_period_length(); - let b = b.checked_sub(offset)?; - - let lease_period = b / lease_period_length; - let first_block = (b % lease_period_length).is_zero(); - - Some((lease_period, first_block)) - } - - fn already_leased( - para_id: ParaId, - first_period: Self::LeasePeriod, - last_period: Self::LeasePeriod, - ) -> bool { - leases().into_iter().any(|((para, period), _data)| { - para == para_id && first_period <= period && period <= last_period - }) - } - } - - ord_parameter_types! { - pub const Six: u64 = 6; - } - - type RootOrSix = EitherOfDiverse, EnsureSignedBy>; - - thread_local! { - pub static LAST_RANDOM: RefCell> = RefCell::new(None); - } - fn set_last_random(output: H256, known_since: u32) { - LAST_RANDOM.with(|p| *p.borrow_mut() = Some((output, known_since))) - } - pub struct TestPastRandomness; - impl Randomness for TestPastRandomness { - fn random(_subject: &[u8]) -> (H256, u32) { - LAST_RANDOM.with(|p| { - if let Some((output, known_since)) = &*p.borrow() { - (*output, *known_since) - } else { - (H256::zero(), frame_system::Pallet::::block_number()) - } - }) - } - } - - parameter_types! { - pub static EndingPeriod: BlockNumber = 3; - pub static SampleLength: BlockNumber = 1; - } - - impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type Leaser = TestLeaser; - type Registrar = TestRegistrar; - type EndingPeriod = EndingPeriod; - type SampleLength = SampleLength; - type Randomness = TestPastRandomness; - type InitiateOrigin = RootOrSix; - type WeightInfo = crate::auctions::TestWeightInfo; - } - - // This function basically just builds a genesis storage key/value store according to - // our desired mock up. - pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { - balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], - } - .assimilate_storage(&mut t) - .unwrap(); - let mut ext: sp_io::TestExternalities = t.into(); - ext.execute_with(|| { - // Register para 0, 1, 2, and 3 for tests - assert_ok!(TestRegistrar::::register( - 1, - 0.into(), - dummy_head_data(), - dummy_validation_code() - )); - assert_ok!(TestRegistrar::::register( - 1, - 1.into(), - dummy_head_data(), - dummy_validation_code() - )); - assert_ok!(TestRegistrar::::register( - 1, - 2.into(), - dummy_head_data(), - dummy_validation_code() - )); - assert_ok!(TestRegistrar::::register( - 1, - 3.into(), - dummy_head_data(), - dummy_validation_code() - )); - }); - ext - } - - fn run_to_block(n: BlockNumber) { - while System::block_number() < n { - Auctions::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Auctions::on_initialize(System::block_number()); - } - } - - #[test] - fn basic_setup_works() { - new_test_ext().execute_with(|| { - assert_eq!(AuctionCounter::::get(), 0); - assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::NotStarted - ); - - run_to_block(10); - - assert_eq!(AuctionCounter::::get(), 0); - assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::NotStarted - ); - }); - } - - #[test] - fn can_start_auction() { - new_test_ext().execute_with(|| { - run_to_block(1); - - assert_noop!(Auctions::new_auction(RuntimeOrigin::signed(1), 5, 1), BadOrigin); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - - assert_eq!(AuctionCounter::::get(), 1); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - }); - } - - #[test] - fn bidding_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); - - assert_eq!(Balances::reserved_balance(1), 5); - assert_eq!(Balances::free_balance(1), 5); - assert_eq!( - Winning::::get(0).unwrap()[SlotRange::ZeroThree as u8 as usize], - Some((1, 0.into(), 5)) - ); - }); - } - - #[test] - fn under_bidding_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); - - assert_storage_noop!({ - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 1)); - }); - }); - } - - #[test] - fn over_bidding_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 6)); - - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(Balances::free_balance(1), 10); - assert_eq!(Balances::reserved_balance(2), 6); - assert_eq!(Balances::free_balance(2), 14); - assert_eq!( - Winning::::get(0).unwrap()[SlotRange::ZeroThree as u8 as usize], - Some((2, 0.into(), 6)) - ); - }); - } - - #[test] - fn auction_proceeds_correctly() { - new_test_ext().execute_with(|| { - run_to_block(1); - - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - - assert_eq!(AuctionCounter::::get(), 1); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(2); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(3); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(4); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(5); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(6); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(0, 0) - ); - - run_to_block(7); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(1, 0) - ); - - run_to_block(8); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(2, 0) - ); - - run_to_block(9); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::NotStarted - ); - }); - } - - #[test] - fn can_win_auction() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); - assert_eq!(Balances::reserved_balance(1), 1); - assert_eq!(Balances::free_balance(1), 9); - run_to_block(9); - - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 4), LeaseData { leaser: 1, amount: 1 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); - }); - } - - #[test] - fn can_win_auction_with_late_randomness() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); - assert_eq!(Balances::reserved_balance(1), 1); - assert_eq!(Balances::free_balance(1), 9); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - run_to_block(8); - // Auction has not yet ended. - assert_eq!(leases(), vec![]); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(2, 0) - ); - // This will prevent the auction's winner from being decided in the next block, since - // the random seed was known before the final bids were made. - set_last_random(H256::zero(), 8); - // Auction definitely ended now, but we don't know exactly when in the last 3 blocks yet - // since no randomness available yet. - run_to_block(9); - // Auction has now ended... But auction winner still not yet decided, so no leases yet. - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::VrfDelay(0) - ); - assert_eq!(leases(), vec![]); - - // Random seed now updated to a value known at block 9, when the auction ended. This - // means that the winner can now be chosen. - set_last_random(H256::zero(), 9); - run_to_block(10); - // Auction ended and winner selected - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::NotStarted - ); - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 4), LeaseData { leaser: 1, amount: 1 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); - }); - } - - #[test] - fn can_win_incomplete_auction() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 5)); - run_to_block(9); - - assert_eq!(leases(), vec![((0.into(), 4), LeaseData { leaser: 1, amount: 5 }),]); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); - }); - } - - #[test] - fn should_choose_best_combination() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 2, 3, 4)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 0.into(), 1, 4, 4, 2)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 1, 1, 4, 2)); - run_to_block(9); - - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 2), LeaseData { leaser: 2, amount: 4 }), - ((0.into(), 3), LeaseData { leaser: 2, amount: 4 }), - ((0.into(), 4), LeaseData { leaser: 3, amount: 2 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); - assert_eq!(TestLeaser::deposit_held(1.into(), &1), 0); - assert_eq!(TestLeaser::deposit_held(0.into(), &2), 4); - assert_eq!(TestLeaser::deposit_held(0.into(), &3), 2); - }); - } - - #[test] - fn gap_bid_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - - // User 1 will make a bid for period 1 and 4 for the same Para 0 - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 4)); - - // User 2 and 3 will make a bid for para 1 on period 2 and 3 respectively - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 1.into(), 1, 2, 2, 2)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 1.into(), 1, 3, 3, 3)); - - // Total reserved should be the max of the two - assert_eq!(Balances::reserved_balance(1), 4); - - // Other people are reserved correctly too - assert_eq!(Balances::reserved_balance(2), 2); - assert_eq!(Balances::reserved_balance(3), 3); - - // End the auction. - run_to_block(9); - - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 4), LeaseData { leaser: 1, amount: 4 }), - ((1.into(), 2), LeaseData { leaser: 2, amount: 2 }), - ((1.into(), 3), LeaseData { leaser: 3, amount: 3 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 4); - assert_eq!(TestLeaser::deposit_held(1.into(), &2), 2); - assert_eq!(TestLeaser::deposit_held(1.into(), &3), 3); - }); - } - - #[test] - fn deposit_credit_should_work() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); - assert_eq!(Balances::reserved_balance(1), 5); - run_to_block(10); - - assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); - - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 2)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 2, 2, 6)); - // Only 1 reserved since we have a deposit credit of 5. - assert_eq!(Balances::reserved_balance(1), 1); - run_to_block(20); - - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 5 }), - ((0.into(), 2), LeaseData { leaser: 1, amount: 6 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 6); - }); - } - - #[test] - fn deposit_credit_on_alt_para_should_not_count() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); - assert_eq!(Balances::reserved_balance(1), 5); - run_to_block(10); - - assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); - - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 2)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 2, 2, 2, 6)); - // 6 reserved since we are bidding on a new para; only works because we don't - assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(20); - - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 1, amount: 5 }), - ((1.into(), 2), LeaseData { leaser: 1, amount: 6 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); - assert_eq!(TestLeaser::deposit_held(1.into(), &1), 6); - }); - } - - #[test] - fn multiple_bids_work_pre_ending() { - new_test_ext().execute_with(|| { - run_to_block(1); - - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - - for i in 1..6u64 { - run_to_block(i as _); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); - for j in 1..6 { - assert_eq!(Balances::reserved_balance(j), if j == i { j } else { 0 }); - assert_eq!(Balances::free_balance(j), if j == i { j * 9 } else { j * 10 }); - } - } - - run_to_block(9); - assert_eq!( - leases(), - vec![ - ((0.into(), 1), LeaseData { leaser: 5, amount: 5 }), - ((0.into(), 2), LeaseData { leaser: 5, amount: 5 }), - ((0.into(), 3), LeaseData { leaser: 5, amount: 5 }), - ((0.into(), 4), LeaseData { leaser: 5, amount: 5 }), - ] - ); - }); - } - - #[test] - fn multiple_bids_work_post_ending() { - new_test_ext().execute_with(|| { - run_to_block(1); - - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 0, 1)); - - for i in 1..6u64 { - run_to_block(((i - 1) / 2 + 1) as _); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); - for j in 1..6 { - assert_eq!(Balances::reserved_balance(j), if j <= i { j } else { 0 }); - assert_eq!(Balances::free_balance(j), if j <= i { j * 9 } else { j * 10 }); - } - } - for i in 1..6u64 { - assert_eq!(ReservedAmounts::::get((i, ParaId::from(0))).unwrap(), i); - } - - run_to_block(5); - assert_eq!( - leases(), - (1..=4) - .map(|i| ((0.into(), i), LeaseData { leaser: 2, amount: 2 })) - .collect::>() - ); - }); - } - - #[test] - fn incomplete_calculate_winners_works() { - let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; - winning[SlotRange::ThreeThree as u8 as usize] = Some((1, 0.into(), 1)); - - let winners = vec![(1, 0.into(), 1, SlotRange::ThreeThree)]; - - assert_eq!(Auctions::calculate_winners(winning), winners); - } - - #[test] - fn first_incomplete_calculate_winners_works() { - let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; - winning[0] = Some((1, 0.into(), 1)); - - let winners = vec![(1, 0.into(), 1, SlotRange::ZeroZero)]; - - assert_eq!(Auctions::calculate_winners(winning), winners); - } - - #[test] - fn calculate_winners_works() { - let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; - winning[SlotRange::ZeroZero as u8 as usize] = Some((2, 0.into(), 2)); - winning[SlotRange::ZeroThree as u8 as usize] = Some((1, 100.into(), 1)); - winning[SlotRange::OneOne as u8 as usize] = Some((3, 1.into(), 1)); - winning[SlotRange::TwoTwo as u8 as usize] = Some((1, 2.into(), 53)); - winning[SlotRange::ThreeThree as u8 as usize] = Some((5, 3.into(), 1)); - - let winners = vec![ - (2, 0.into(), 2, SlotRange::ZeroZero), - (3, 1.into(), 1, SlotRange::OneOne), - (1, 2.into(), 53, SlotRange::TwoTwo), - (5, 3.into(), 1, SlotRange::ThreeThree), - ]; - assert_eq!(Auctions::calculate_winners(winning), winners); - - winning[SlotRange::ZeroOne as u8 as usize] = Some((4, 10.into(), 3)); - let winners = vec![ - (4, 10.into(), 3, SlotRange::ZeroOne), - (1, 2.into(), 53, SlotRange::TwoTwo), - (5, 3.into(), 1, SlotRange::ThreeThree), - ]; - assert_eq!(Auctions::calculate_winners(winning), winners); - - winning[SlotRange::ZeroThree as u8 as usize] = Some((1, 100.into(), 100)); - let winners = vec![(1, 100.into(), 100, SlotRange::ZeroThree)]; - assert_eq!(Auctions::calculate_winners(winning), winners); - } - - #[test] - fn lower_bids_are_correctly_refunded() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 1, 1)); - let para_1 = ParaId::from(1_u32); - let para_2 = ParaId::from(2_u32); - - // Make a bid and reserve a balance - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9)); - assert_eq!(Balances::reserved_balance(1), 9); - assert_eq!(ReservedAmounts::::get((1, para_1)), Some(9)); - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(ReservedAmounts::::get((2, para_2)), None); - - // Bigger bid, reserves new balance and returns funds - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 1, 4, 19)); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(ReservedAmounts::::get((1, para_1)), None); - assert_eq!(Balances::reserved_balance(2), 19); - assert_eq!(ReservedAmounts::::get((2, para_2)), Some(19)); - }); - } - - #[test] - fn initialize_winners_in_ending_period_works() { - new_test_ext().execute_with(|| { - let ed: u64 = ::ExistentialDeposit::get(); - assert_eq!(ed, 1); - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 1)); - let para_1 = ParaId::from(1_u32); - let para_2 = ParaId::from(2_u32); - let para_3 = ParaId::from(3_u32); - - // Make bids - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 3, 4, 19)); - - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; - winning[SlotRange::ZeroThree as u8 as usize] = Some((1, para_1, 9)); - winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); - assert_eq!(Winning::::get(0), Some(winning)); - - run_to_block(9); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(10); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(0, 0) - ); - assert_eq!(Winning::::get(0), Some(winning)); - - run_to_block(11); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(1, 0) - ); - assert_eq!(Winning::::get(1), Some(winning)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 3, 4, 29)); - - run_to_block(12); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(2, 0) - ); - winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); - assert_eq!(Winning::::get(2), Some(winning)); - }); - } - - #[test] - fn handle_bid_requires_registered_para() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_noop!( - Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1), - Error::::ParaNotRegistered - ); - assert_ok!(TestRegistrar::::register( - 1, - 1337.into(), - dummy_head_data(), - dummy_validation_code() - )); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1)); - }); - } - - #[test] - fn handle_bid_checks_existing_lease_periods() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 2, 3, 1)); - assert_eq!(Balances::reserved_balance(1), 1); - assert_eq!(Balances::free_balance(1), 9); - run_to_block(9); - - assert_eq!( - leases(), - vec![ - ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), - ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), - ] - ); - assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); - - // Para 1 just won an auction above and won some lease periods. - // No bids can work which overlap these periods. - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_noop!( - Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 4, 1), - Error::::AlreadyLeasedOut, - ); - assert_noop!( - Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 2, 1), - Error::::AlreadyLeasedOut, - ); - assert_noop!( - Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 3, 4, 1), - Error::::AlreadyLeasedOut, - ); - // This is okay, not an overlapping bid. - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 1, 1)); - }); - } - - // Here we will test that taking only 10 samples during the ending period works as expected. - #[test] - fn less_winning_samples_work() { - new_test_ext().execute_with(|| { - let ed: u64 = ::ExistentialDeposit::get(); - assert_eq!(ed, 1); - EndingPeriod::set(30); - SampleLength::set(10); - - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); - let para_1 = ParaId::from(1_u32); - let para_2 = ParaId::from(2_u32); - let para_3 = ParaId::from(3_u32); - - // Make bids - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 11, 14, 9)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 13, 14, 19)); - - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; - winning[SlotRange::ZeroThree as u8 as usize] = Some((1, para_1, 9)); - winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); - assert_eq!(Winning::::get(0), Some(winning)); - - run_to_block(9); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(10); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(0, 0) - ); - assert_eq!(Winning::::get(0), Some(winning)); - - // New bids update the current winning - assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 14, 14, 29)); - winning[SlotRange::ThreeThree as u8 as usize] = Some((3, para_3, 29)); - assert_eq!(Winning::::get(0), Some(winning)); - - run_to_block(20); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(1, 0) - ); - assert_eq!(Winning::::get(1), Some(winning)); - run_to_block(25); - // Overbid mid sample - assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 13, 14, 29)); - winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); - assert_eq!(Winning::::get(1), Some(winning)); - - run_to_block(30); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(2, 0) - ); - assert_eq!(Winning::::get(2), Some(winning)); - - set_last_random(H256::from([254; 32]), 40); - run_to_block(40); - // Auction ended and winner selected - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::NotStarted - ); - assert_eq!( - leases(), - vec![ - ((3.into(), 13), LeaseData { leaser: 3, amount: 29 }), - ((3.into(), 14), LeaseData { leaser: 3, amount: 29 }), - ] - ); - }); - } - - #[test] - fn auction_status_works() { - new_test_ext().execute_with(|| { - EndingPeriod::set(30); - SampleLength::set(10); - set_last_random(dummy_hash(), 0); - - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::NotStarted - ); - - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); - - run_to_block(9); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::StartingPeriod - ); - - run_to_block(10); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(0, 0) - ); - - run_to_block(11); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(0, 1) - ); - - run_to_block(19); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(0, 9) - ); - - run_to_block(20); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(1, 0) - ); - - run_to_block(25); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(1, 5) - ); - - run_to_block(30); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(2, 0) - ); - - run_to_block(39); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::EndingPeriod(2, 9) - ); - - run_to_block(40); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::VrfDelay(0) - ); - - run_to_block(44); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::VrfDelay(4) - ); - - set_last_random(dummy_hash(), 45); - run_to_block(45); - assert_eq!( - Auctions::auction_status(System::block_number()), - AuctionStatus::::NotStarted - ); - }); - } - - #[test] - fn can_cancel_auction() { - new_test_ext().execute_with(|| { - run_to_block(1); - assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); - assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); - assert_eq!(Balances::reserved_balance(1), 1); - assert_eq!(Balances::free_balance(1), 9); - - assert_noop!(Auctions::cancel_auction(RuntimeOrigin::signed(6)), BadOrigin); - assert_ok!(Auctions::cancel_auction(RuntimeOrigin::root())); - - assert!(AuctionInfo::::get().is_none()); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(ReservedAmounts::::iter().count(), 0); - assert_eq!(Winning::::iter().count(), 0); - }); - } -} - -#[cfg(feature = "runtime-benchmarks")] -mod benchmarking { - use super::{Pallet as Auctions, *}; - use frame_support::{ - assert_ok, - traits::{EnsureOrigin, OnInitialize}, - }; - use frame_system::RawOrigin; - use polkadot_runtime_parachains::paras; - use sp_runtime::{traits::Bounded, SaturatedConversion}; - - use frame_benchmarking::v2::*; - - fn assert_last_event(generic_event: ::RuntimeEvent) { - let events = frame_system::Pallet::::events(); - let system_event: ::RuntimeEvent = generic_event.into(); - // compare to the last event record - let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; - assert_eq!(event, &system_event); - } - - fn fill_winners(lease_period_index: LeasePeriodOf) { - let auction_index = AuctionCounter::::get(); - let minimum_balance = CurrencyOf::::minimum_balance(); - - for n in 1..=SlotRange::SLOT_RANGE_COUNT as u32 { - let owner = account("owner", n, 0); - let worst_validation_code = T::Registrar::worst_validation_code(); - let worst_head_data = T::Registrar::worst_head_data(); - CurrencyOf::::make_free_balance_be(&owner, BalanceOf::::max_value()); - - assert!(T::Registrar::register( - owner, - ParaId::from(n), - worst_head_data, - worst_validation_code - ) - .is_ok()); - } - assert_ok!(paras::Pallet::::add_trusted_validation_code( - frame_system::Origin::::Root.into(), - T::Registrar::worst_validation_code(), - )); - - T::Registrar::execute_pending_transitions(); - - for n in 1..=SlotRange::SLOT_RANGE_COUNT as u32 { - let bidder = account("bidder", n, 0); - CurrencyOf::::make_free_balance_be(&bidder, BalanceOf::::max_value()); - - let slot_range = SlotRange::n((n - 1) as u8).unwrap(); - let (start, end) = slot_range.as_pair(); - - assert!(Auctions::::bid( - RawOrigin::Signed(bidder).into(), - ParaId::from(n), - auction_index, - lease_period_index + start.into(), // First Slot - lease_period_index + end.into(), // Last slot - minimum_balance.saturating_mul(n.into()), // Amount - ) - .is_ok()); - } - } - - #[benchmarks( - where T: pallet_babe::Config + paras::Config, - )] - mod benchmarks { - use super::*; - - #[benchmark] - fn new_auction() -> Result<(), BenchmarkError> { - let duration = BlockNumberFor::::max_value(); - let lease_period_index = LeasePeriodOf::::max_value(); - let origin = T::InitiateOrigin::try_successful_origin() - .map_err(|_| BenchmarkError::Weightless)?; - - #[extrinsic_call] - _(origin as T::RuntimeOrigin, duration, lease_period_index); - - assert_last_event::( - Event::::AuctionStarted { - auction_index: AuctionCounter::::get(), - lease_period: LeasePeriodOf::::max_value(), - ending: BlockNumberFor::::max_value(), - } - .into(), - ); - - Ok(()) - } - - // Worst case scenario a new bid comes in which kicks out an existing bid for the same slot. - #[benchmark] - fn bid() -> Result<(), BenchmarkError> { - // If there is an offset, we need to be on that block to be able to do lease things. - let (_, offset) = T::Leaser::lease_period_length(); - frame_system::Pallet::::set_block_number(offset + One::one()); - - // Create a new auction - let duration = BlockNumberFor::::max_value(); - let lease_period_index = LeasePeriodOf::::zero(); - let origin = T::InitiateOrigin::try_successful_origin() - .expect("InitiateOrigin has no successful origin required for the benchmark"); - Auctions::::new_auction(origin, duration, lease_period_index)?; - - let para = ParaId::from(0); - let new_para = ParaId::from(1_u32); - - // Register the paras - let owner = account("owner", 0, 0); - CurrencyOf::::make_free_balance_be(&owner, BalanceOf::::max_value()); - let worst_head_data = T::Registrar::worst_head_data(); - let worst_validation_code = T::Registrar::worst_validation_code(); - T::Registrar::register( - owner.clone(), - para, - worst_head_data.clone(), - worst_validation_code.clone(), - )?; - T::Registrar::register( - owner, - new_para, - worst_head_data, - worst_validation_code.clone(), - )?; - assert_ok!(paras::Pallet::::add_trusted_validation_code( - frame_system::Origin::::Root.into(), - worst_validation_code, - )); - - T::Registrar::execute_pending_transitions(); - - // Make an existing bid - let auction_index = AuctionCounter::::get(); - let first_slot = AuctionInfo::::get().unwrap().0; - let last_slot = first_slot + 3u32.into(); - let first_amount = CurrencyOf::::minimum_balance(); - let first_bidder: T::AccountId = account("first_bidder", 0, 0); - CurrencyOf::::make_free_balance_be(&first_bidder, BalanceOf::::max_value()); - Auctions::::bid( - RawOrigin::Signed(first_bidder.clone()).into(), - para, - auction_index, - first_slot, - last_slot, - first_amount, - )?; - - let caller: T::AccountId = whitelisted_caller(); - CurrencyOf::::make_free_balance_be(&caller, BalanceOf::::max_value()); - let bigger_amount = CurrencyOf::::minimum_balance().saturating_mul(10u32.into()); - assert_eq!(CurrencyOf::::reserved_balance(&first_bidder), first_amount); - - #[extrinsic_call] - _( - RawOrigin::Signed(caller.clone()), - new_para, - auction_index, - first_slot, - last_slot, - bigger_amount, - ); - - // Confirms that we unreserved funds from a previous bidder, which is worst case - // scenario. - assert_eq!(CurrencyOf::::reserved_balance(&caller), bigger_amount); - - Ok(()) - } - - // Worst case: 10 bidders taking all wining spots, and we need to calculate the winner for - // auction end. Entire winner map should be full and removed at the end of the benchmark. - #[benchmark] - fn on_initialize() -> Result<(), BenchmarkError> { - // If there is an offset, we need to be on that block to be able to do lease things. - let (lease_length, offset) = T::Leaser::lease_period_length(); - frame_system::Pallet::::set_block_number(offset + One::one()); - - // Create a new auction - let duration: BlockNumberFor = lease_length / 2u32.into(); - let lease_period_index = LeasePeriodOf::::zero(); - let now = frame_system::Pallet::::block_number(); - let origin = T::InitiateOrigin::try_successful_origin() - .expect("InitiateOrigin has no successful origin required for the benchmark"); - Auctions::::new_auction(origin, duration, lease_period_index)?; - - fill_winners::(lease_period_index); - - for winner in Winning::::get(BlockNumberFor::::from(0u32)).unwrap().iter() { - assert!(winner.is_some()); - } - - let winning_data = Winning::::get(BlockNumberFor::::from(0u32)).unwrap(); - // Make winning map full - for i in 0u32..(T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { - Winning::::insert(BlockNumberFor::::from(i), winning_data.clone()); - } - - // Move ahead to the block we want to initialize - frame_system::Pallet::::set_block_number(duration + now + T::EndingPeriod::get()); - - // Trigger epoch change for new random number value: - { - pallet_babe::EpochStart::::set((Zero::zero(), u32::MAX.into())); - pallet_babe::Pallet::::on_initialize(duration + now + T::EndingPeriod::get()); - let authorities = pallet_babe::Pallet::::authorities(); - // Check for non empty authority set since it otherwise emits a No-OP warning. - if !authorities.is_empty() { - pallet_babe::Pallet::::enact_epoch_change( - authorities.clone(), - authorities, - None, - ); - } - } - - #[block] - { - Auctions::::on_initialize(duration + now + T::EndingPeriod::get()); - } - - let auction_index = AuctionCounter::::get(); - assert_last_event::(Event::::AuctionClosed { auction_index }.into()); - assert!(Winning::::iter().count().is_zero()); - - Ok(()) - } - - // Worst case: 10 bidders taking all wining spots, and winning data is full. - #[benchmark] - fn cancel_auction() -> Result<(), BenchmarkError> { - // If there is an offset, we need to be on that block to be able to do lease things. - let (lease_length, offset) = T::Leaser::lease_period_length(); - frame_system::Pallet::::set_block_number(offset + One::one()); - - // Create a new auction - let duration: BlockNumberFor = lease_length / 2u32.into(); - let lease_period_index = LeasePeriodOf::::zero(); - let origin = T::InitiateOrigin::try_successful_origin() - .expect("InitiateOrigin has no successful origin required for the benchmark"); - Auctions::::new_auction(origin, duration, lease_period_index)?; - - fill_winners::(lease_period_index); - - let winning_data = Winning::::get(BlockNumberFor::::from(0u32)).unwrap(); - for winner in winning_data.iter() { - assert!(winner.is_some()); - } - - // Make winning map full - for i in 0u32..(T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { - Winning::::insert(BlockNumberFor::::from(i), winning_data.clone()); - } - assert!(AuctionInfo::::get().is_some()); - - #[extrinsic_call] - _(RawOrigin::Root); - - assert!(AuctionInfo::::get().is_none()); - Ok(()) - } - - impl_benchmark_test_suite!( - Auctions, - crate::integration_tests::new_test_ext(), - crate::integration_tests::Test, - ); - } -} diff --git a/polkadot/runtime/common/src/auctions/benchmarking.rs b/polkadot/runtime/common/src/auctions/benchmarking.rs new file mode 100644 index 000000000000..6d52cd850b6f --- /dev/null +++ b/polkadot/runtime/common/src/auctions/benchmarking.rs @@ -0,0 +1,282 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Benchmarking for auctions pallet + +#![cfg(feature = "runtime-benchmarks")] +use super::{Pallet as Auctions, *}; +use frame_support::{ + assert_ok, + traits::{EnsureOrigin, OnInitialize}, +}; +use frame_system::RawOrigin; +use polkadot_runtime_parachains::paras; +use sp_runtime::{traits::Bounded, SaturatedConversion}; + +use frame_benchmarking::v2::*; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +fn fill_winners(lease_period_index: LeasePeriodOf) { + let auction_index = AuctionCounter::::get(); + let minimum_balance = CurrencyOf::::minimum_balance(); + + for n in 1..=SlotRange::SLOT_RANGE_COUNT as u32 { + let owner = account("owner", n, 0); + let worst_validation_code = T::Registrar::worst_validation_code(); + let worst_head_data = T::Registrar::worst_head_data(); + CurrencyOf::::make_free_balance_be(&owner, BalanceOf::::max_value()); + + assert!(T::Registrar::register( + owner, + ParaId::from(n), + worst_head_data, + worst_validation_code + ) + .is_ok()); + } + assert_ok!(paras::Pallet::::add_trusted_validation_code( + frame_system::Origin::::Root.into(), + T::Registrar::worst_validation_code(), + )); + + T::Registrar::execute_pending_transitions(); + + for n in 1..=SlotRange::SLOT_RANGE_COUNT as u32 { + let bidder = account("bidder", n, 0); + CurrencyOf::::make_free_balance_be(&bidder, BalanceOf::::max_value()); + + let slot_range = SlotRange::n((n - 1) as u8).unwrap(); + let (start, end) = slot_range.as_pair(); + + assert!(Auctions::::bid( + RawOrigin::Signed(bidder).into(), + ParaId::from(n), + auction_index, + lease_period_index + start.into(), // First Slot + lease_period_index + end.into(), // Last slot + minimum_balance.saturating_mul(n.into()), // Amount + ) + .is_ok()); + } +} + +#[benchmarks( + where T: pallet_babe::Config + paras::Config, + )] +mod benchmarks { + use super::*; + + #[benchmark] + fn new_auction() -> Result<(), BenchmarkError> { + let duration = BlockNumberFor::::max_value(); + let lease_period_index = LeasePeriodOf::::max_value(); + let origin = + T::InitiateOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, duration, lease_period_index); + + assert_last_event::( + Event::::AuctionStarted { + auction_index: AuctionCounter::::get(), + lease_period: LeasePeriodOf::::max_value(), + ending: BlockNumberFor::::max_value(), + } + .into(), + ); + + Ok(()) + } + + // Worst case scenario a new bid comes in which kicks out an existing bid for the same slot. + #[benchmark] + fn bid() -> Result<(), BenchmarkError> { + // If there is an offset, we need to be on that block to be able to do lease things. + let (_, offset) = T::Leaser::lease_period_length(); + frame_system::Pallet::::set_block_number(offset + One::one()); + + // Create a new auction + let duration = BlockNumberFor::::max_value(); + let lease_period_index = LeasePeriodOf::::zero(); + let origin = T::InitiateOrigin::try_successful_origin() + .expect("InitiateOrigin has no successful origin required for the benchmark"); + Auctions::::new_auction(origin, duration, lease_period_index)?; + + let para = ParaId::from(0); + let new_para = ParaId::from(1_u32); + + // Register the paras + let owner = account("owner", 0, 0); + CurrencyOf::::make_free_balance_be(&owner, BalanceOf::::max_value()); + let worst_head_data = T::Registrar::worst_head_data(); + let worst_validation_code = T::Registrar::worst_validation_code(); + T::Registrar::register( + owner.clone(), + para, + worst_head_data.clone(), + worst_validation_code.clone(), + )?; + T::Registrar::register(owner, new_para, worst_head_data, worst_validation_code.clone())?; + assert_ok!(paras::Pallet::::add_trusted_validation_code( + frame_system::Origin::::Root.into(), + worst_validation_code, + )); + + T::Registrar::execute_pending_transitions(); + + // Make an existing bid + let auction_index = AuctionCounter::::get(); + let first_slot = AuctionInfo::::get().unwrap().0; + let last_slot = first_slot + 3u32.into(); + let first_amount = CurrencyOf::::minimum_balance(); + let first_bidder: T::AccountId = account("first_bidder", 0, 0); + CurrencyOf::::make_free_balance_be(&first_bidder, BalanceOf::::max_value()); + Auctions::::bid( + RawOrigin::Signed(first_bidder.clone()).into(), + para, + auction_index, + first_slot, + last_slot, + first_amount, + )?; + + let caller: T::AccountId = whitelisted_caller(); + CurrencyOf::::make_free_balance_be(&caller, BalanceOf::::max_value()); + let bigger_amount = CurrencyOf::::minimum_balance().saturating_mul(10u32.into()); + assert_eq!(CurrencyOf::::reserved_balance(&first_bidder), first_amount); + + #[extrinsic_call] + _( + RawOrigin::Signed(caller.clone()), + new_para, + auction_index, + first_slot, + last_slot, + bigger_amount, + ); + + // Confirms that we unreserved funds from a previous bidder, which is worst case + // scenario. + assert_eq!(CurrencyOf::::reserved_balance(&caller), bigger_amount); + + Ok(()) + } + + // Worst case: 10 bidders taking all wining spots, and we need to calculate the winner for + // auction end. Entire winner map should be full and removed at the end of the benchmark. + #[benchmark] + fn on_initialize() -> Result<(), BenchmarkError> { + // If there is an offset, we need to be on that block to be able to do lease things. + let (lease_length, offset) = T::Leaser::lease_period_length(); + frame_system::Pallet::::set_block_number(offset + One::one()); + + // Create a new auction + let duration: BlockNumberFor = lease_length / 2u32.into(); + let lease_period_index = LeasePeriodOf::::zero(); + let now = frame_system::Pallet::::block_number(); + let origin = T::InitiateOrigin::try_successful_origin() + .expect("InitiateOrigin has no successful origin required for the benchmark"); + Auctions::::new_auction(origin, duration, lease_period_index)?; + + fill_winners::(lease_period_index); + + for winner in Winning::::get(BlockNumberFor::::from(0u32)).unwrap().iter() { + assert!(winner.is_some()); + } + + let winning_data = Winning::::get(BlockNumberFor::::from(0u32)).unwrap(); + // Make winning map full + for i in 0u32..(T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { + Winning::::insert(BlockNumberFor::::from(i), winning_data.clone()); + } + + // Move ahead to the block we want to initialize + frame_system::Pallet::::set_block_number(duration + now + T::EndingPeriod::get()); + + // Trigger epoch change for new random number value: + { + pallet_babe::EpochStart::::set((Zero::zero(), u32::MAX.into())); + pallet_babe::Pallet::::on_initialize(duration + now + T::EndingPeriod::get()); + let authorities = pallet_babe::Pallet::::authorities(); + // Check for non empty authority set since it otherwise emits a No-OP warning. + if !authorities.is_empty() { + pallet_babe::Pallet::::enact_epoch_change( + authorities.clone(), + authorities, + None, + ); + } + } + + #[block] + { + Auctions::::on_initialize(duration + now + T::EndingPeriod::get()); + } + + let auction_index = AuctionCounter::::get(); + assert_last_event::(Event::::AuctionClosed { auction_index }.into()); + assert!(Winning::::iter().count().is_zero()); + + Ok(()) + } + + // Worst case: 10 bidders taking all wining spots, and winning data is full. + #[benchmark] + fn cancel_auction() -> Result<(), BenchmarkError> { + // If there is an offset, we need to be on that block to be able to do lease things. + let (lease_length, offset) = T::Leaser::lease_period_length(); + frame_system::Pallet::::set_block_number(offset + One::one()); + + // Create a new auction + let duration: BlockNumberFor = lease_length / 2u32.into(); + let lease_period_index = LeasePeriodOf::::zero(); + let origin = T::InitiateOrigin::try_successful_origin() + .expect("InitiateOrigin has no successful origin required for the benchmark"); + Auctions::::new_auction(origin, duration, lease_period_index)?; + + fill_winners::(lease_period_index); + + let winning_data = Winning::::get(BlockNumberFor::::from(0u32)).unwrap(); + for winner in winning_data.iter() { + assert!(winner.is_some()); + } + + // Make winning map full + for i in 0u32..(T::EndingPeriod::get() / T::SampleLength::get()).saturated_into() { + Winning::::insert(BlockNumberFor::::from(i), winning_data.clone()); + } + assert!(AuctionInfo::::get().is_some()); + + #[extrinsic_call] + _(RawOrigin::Root); + + assert!(AuctionInfo::::get().is_none()); + Ok(()) + } + + impl_benchmark_test_suite!( + Auctions, + crate::integration_tests::new_test_ext(), + crate::integration_tests::Test, + ); +} diff --git a/polkadot/runtime/common/src/auctions/mock.rs b/polkadot/runtime/common/src/auctions/mock.rs new file mode 100644 index 000000000000..9fe19e579cfa --- /dev/null +++ b/polkadot/runtime/common/src/auctions/mock.rs @@ -0,0 +1,258 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Mocking utilities for testing in auctions pallet. + +#[cfg(test)] +use super::*; +use crate::{auctions, mock::TestRegistrar}; +use frame_support::{ + assert_ok, derive_impl, ord_parameter_types, parameter_types, + traits::{EitherOfDiverse, OnFinalize, OnInitialize}, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use pallet_balances; +use polkadot_primitives::{BlockNumber, Id as ParaId}; +use polkadot_primitives_test_helpers::{dummy_head_data, dummy_validation_code}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +use std::{cell::RefCell, collections::BTreeMap}; + +type Block = frame_system::mocking::MockBlockU32; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Auctions: auctions, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type AccountStore = System; +} + +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] +pub struct LeaseData { + pub leaser: u64, + pub amount: u64, +} + +thread_local! { + pub static LEASES: + RefCell> = RefCell::new(BTreeMap::new()); +} + +pub fn leases() -> Vec<((ParaId, BlockNumber), LeaseData)> { + LEASES.with(|p| (&*p.borrow()).clone().into_iter().collect::>()) +} + +pub struct TestLeaser; +impl Leaser for TestLeaser { + type AccountId = u64; + type LeasePeriod = BlockNumber; + type Currency = Balances; + + fn lease_out( + para: ParaId, + leaser: &Self::AccountId, + amount: >::Balance, + period_begin: Self::LeasePeriod, + period_count: Self::LeasePeriod, + ) -> Result<(), LeaseError> { + LEASES.with(|l| { + let mut leases = l.borrow_mut(); + let now = System::block_number(); + let (current_lease_period, _) = + Self::lease_period_index(now).ok_or(LeaseError::NoLeasePeriod)?; + if period_begin < current_lease_period { + return Err(LeaseError::AlreadyEnded); + } + for period in period_begin..(period_begin + period_count) { + if leases.contains_key(&(para, period)) { + return Err(LeaseError::AlreadyLeased); + } + leases.insert((para, period), LeaseData { leaser: *leaser, amount }); + } + Ok(()) + }) + } + + fn deposit_held( + para: ParaId, + leaser: &Self::AccountId, + ) -> >::Balance { + leases() + .iter() + .filter_map(|((id, _period), data)| { + if id == ¶ && &data.leaser == leaser { + Some(data.amount) + } else { + None + } + }) + .max() + .unwrap_or_default() + } + + fn lease_period_length() -> (BlockNumber, BlockNumber) { + (10, 0) + } + + fn lease_period_index(b: BlockNumber) -> Option<(Self::LeasePeriod, bool)> { + let (lease_period_length, offset) = Self::lease_period_length(); + let b = b.checked_sub(offset)?; + + let lease_period = b / lease_period_length; + let first_block = (b % lease_period_length).is_zero(); + + Some((lease_period, first_block)) + } + + fn already_leased( + para_id: ParaId, + first_period: Self::LeasePeriod, + last_period: Self::LeasePeriod, + ) -> bool { + leases().into_iter().any(|((para, period), _data)| { + para == para_id && first_period <= period && period <= last_period + }) + } +} + +ord_parameter_types! { + pub const Six: u64 = 6; +} + +type RootOrSix = EitherOfDiverse, EnsureSignedBy>; + +thread_local! { + pub static LAST_RANDOM: RefCell> = RefCell::new(None); +} +pub fn set_last_random(output: H256, known_since: u32) { + LAST_RANDOM.with(|p| *p.borrow_mut() = Some((output, known_since))) +} +pub struct TestPastRandomness; +impl Randomness for TestPastRandomness { + fn random(_subject: &[u8]) -> (H256, u32) { + LAST_RANDOM.with(|p| { + if let Some((output, known_since)) = &*p.borrow() { + (*output, *known_since) + } else { + (H256::zero(), frame_system::Pallet::::block_number()) + } + }) + } +} + +parameter_types! { + pub static EndingPeriod: BlockNumber = 3; + pub static SampleLength: BlockNumber = 1; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type Leaser = TestLeaser; + type Registrar = TestRegistrar; + type EndingPeriod = EndingPeriod; + type SampleLength = SampleLength; + type Randomness = TestPastRandomness; + type InitiateOrigin = RootOrSix; + type WeightInfo = crate::auctions::TestWeightInfo; +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mock up. +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext: sp_io::TestExternalities = t.into(); + ext.execute_with(|| { + // Register para 0, 1, 2, and 3 for tests + assert_ok!(TestRegistrar::::register( + 1, + 0.into(), + dummy_head_data(), + dummy_validation_code() + )); + assert_ok!(TestRegistrar::::register( + 1, + 1.into(), + dummy_head_data(), + dummy_validation_code() + )); + assert_ok!(TestRegistrar::::register( + 1, + 2.into(), + dummy_head_data(), + dummy_validation_code() + )); + assert_ok!(TestRegistrar::::register( + 1, + 3.into(), + dummy_head_data(), + dummy_validation_code() + )); + }); + ext +} + +pub fn run_to_block(n: BlockNumber) { + while System::block_number() < n { + Auctions::on_finalize(System::block_number()); + Balances::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + Balances::on_initialize(System::block_number()); + Auctions::on_initialize(System::block_number()); + } +} diff --git a/polkadot/runtime/common/src/auctions/mod.rs b/polkadot/runtime/common/src/auctions/mod.rs new file mode 100644 index 000000000000..84d8a3846d40 --- /dev/null +++ b/polkadot/runtime/common/src/auctions/mod.rs @@ -0,0 +1,677 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Auctioning system to determine the set of Parachains in operation. This includes logic for the +//! auctioning mechanism and for reserving balance as part of the "payment". Unreserving the balance +//! happens elsewhere. + +use crate::{ + slot_range::SlotRange, + traits::{AuctionStatus, Auctioneer, LeaseError, Leaser, Registrar}, +}; +use alloc::{vec, vec::Vec}; +use codec::Decode; +use core::mem::swap; +use frame_support::{ + dispatch::DispatchResult, + ensure, + traits::{Currency, Get, Randomness, ReservableCurrency}, + weights::Weight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +pub use pallet::*; +use polkadot_primitives::Id as ParaId; +use sp_runtime::traits::{CheckedSub, One, Saturating, Zero}; + +type CurrencyOf = <::Leaser as Leaser>>::Currency; +type BalanceOf = <<::Leaser as Leaser>>::Currency as Currency< + ::AccountId, +>>::Balance; + +pub trait WeightInfo { + fn new_auction() -> Weight; + fn bid() -> Weight; + fn cancel_auction() -> Weight; + fn on_initialize() -> Weight; +} + +pub struct TestWeightInfo; +impl WeightInfo for TestWeightInfo { + fn new_auction() -> Weight { + Weight::zero() + } + fn bid() -> Weight { + Weight::zero() + } + fn cancel_auction() -> Weight { + Weight::zero() + } + fn on_initialize() -> Weight { + Weight::zero() + } +} + +/// An auction index. We count auctions in this type. +pub type AuctionIndex = u32; + +type LeasePeriodOf = <::Leaser as Leaser>>::LeasePeriod; + +// Winning data type. This encodes the top bidders of each range together with their bid. +type WinningData = [Option<(::AccountId, ParaId, BalanceOf)>; + SlotRange::SLOT_RANGE_COUNT]; +// Winners data type. This encodes each of the final winners of a parachain auction, the parachain +// index assigned to them, their winning bid and the range that they won. +type WinnersData = + Vec<(::AccountId, ParaId, BalanceOf, SlotRange)>; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{dispatch::DispatchClass, pallet_prelude::*, traits::EnsureOrigin}; + use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; + + #[pallet::pallet] + pub struct Pallet(_); + + /// The module's configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The type representing the leasing system. + type Leaser: Leaser< + BlockNumberFor, + AccountId = Self::AccountId, + LeasePeriod = BlockNumberFor, + >; + + /// The parachain registrar type. + type Registrar: Registrar; + + /// The number of blocks over which an auction may be retroactively ended. + #[pallet::constant] + type EndingPeriod: Get>; + + /// The length of each sample to take during the ending period. + /// + /// `EndingPeriod` / `SampleLength` = Total # of Samples + #[pallet::constant] + type SampleLength: Get>; + + /// Something that provides randomness in the runtime. + type Randomness: Randomness>; + + /// The origin which may initiate auctions. + type InitiateOrigin: EnsureOrigin; + + /// Weight Information for the Extrinsics in the Pallet + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// An auction started. Provides its index and the block number where it will begin to + /// close and the first lease period of the quadruplet that is auctioned. + AuctionStarted { + auction_index: AuctionIndex, + lease_period: LeasePeriodOf, + ending: BlockNumberFor, + }, + /// An auction ended. All funds become unreserved. + AuctionClosed { auction_index: AuctionIndex }, + /// Funds were reserved for a winning bid. First balance is the extra amount reserved. + /// Second is the total. + Reserved { bidder: T::AccountId, extra_reserved: BalanceOf, total_amount: BalanceOf }, + /// Funds were unreserved since bidder is no longer active. `[bidder, amount]` + Unreserved { bidder: T::AccountId, amount: BalanceOf }, + /// Someone attempted to lease the same slot twice for a parachain. The amount is held in + /// reserve but no parachain slot has been leased. + ReserveConfiscated { para_id: ParaId, leaser: T::AccountId, amount: BalanceOf }, + /// A new bid has been accepted as the current winner. + BidAccepted { + bidder: T::AccountId, + para_id: ParaId, + amount: BalanceOf, + first_slot: LeasePeriodOf, + last_slot: LeasePeriodOf, + }, + /// The winning offset was chosen for an auction. This will map into the `Winning` storage + /// map. + WinningOffset { auction_index: AuctionIndex, block_number: BlockNumberFor }, + } + + #[pallet::error] + pub enum Error { + /// This auction is already in progress. + AuctionInProgress, + /// The lease period is in the past. + LeasePeriodInPast, + /// Para is not registered + ParaNotRegistered, + /// Not a current auction. + NotCurrentAuction, + /// Not an auction. + NotAuction, + /// Auction has already ended. + AuctionEnded, + /// The para is already leased out for part of this range. + AlreadyLeasedOut, + } + + /// Number of auctions started so far. + #[pallet::storage] + pub type AuctionCounter = StorageValue<_, AuctionIndex, ValueQuery>; + + /// Information relating to the current auction, if there is one. + /// + /// The first item in the tuple is the lease period index that the first of the four + /// contiguous lease periods on auction is for. The second is the block number when the + /// auction will "begin to end", i.e. the first block of the Ending Period of the auction. + #[pallet::storage] + pub type AuctionInfo = StorageValue<_, (LeasePeriodOf, BlockNumberFor)>; + + /// Amounts currently reserved in the accounts of the bidders currently winning + /// (sub-)ranges. + #[pallet::storage] + pub type ReservedAmounts = + StorageMap<_, Twox64Concat, (T::AccountId, ParaId), BalanceOf>; + + /// The winning bids for each of the 10 ranges at each sample in the final Ending Period of + /// the current auction. The map's key is the 0-based index into the Sample Size. The + /// first sample of the ending period is 0; the last is `Sample Size - 1`. + #[pallet::storage] + pub type Winning = StorageMap<_, Twox64Concat, BlockNumberFor, WinningData>; + + #[pallet::extra_constants] + impl Pallet { + #[pallet::constant_name(SlotRangeCount)] + fn slot_range_count() -> u32 { + SlotRange::SLOT_RANGE_COUNT as u32 + } + + #[pallet::constant_name(LeasePeriodsPerSlot)] + fn lease_periods_per_slot() -> u32 { + SlotRange::LEASE_PERIODS_PER_SLOT as u32 + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: BlockNumberFor) -> Weight { + let mut weight = T::DbWeight::get().reads(1); + + // If the current auction was in its ending period last block, then ensure that the + // (sub-)range winner information is duplicated from the previous block in case no bids + // happened in the last block. + if let AuctionStatus::EndingPeriod(offset, _sub_sample) = Self::auction_status(n) { + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + if !Winning::::contains_key(&offset) { + weight = weight.saturating_add(T::DbWeight::get().writes(1)); + let winning_data = offset + .checked_sub(&One::one()) + .and_then(Winning::::get) + .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); + Winning::::insert(offset, winning_data); + } + } + + // Check to see if an auction just ended. + if let Some((winning_ranges, auction_lease_period_index)) = Self::check_auction_end(n) { + // Auction is ended now. We have the winning ranges and the lease period index which + // acts as the offset. Handle it. + Self::manage_auction_end(auction_lease_period_index, winning_ranges); + weight = weight.saturating_add(T::WeightInfo::on_initialize()); + } + + weight + } + } + + #[pallet::call] + impl Pallet { + /// Create a new auction. + /// + /// This can only happen when there isn't already an auction in progress and may only be + /// called by the root origin. Accepts the `duration` of this auction and the + /// `lease_period_index` of the initial lease period of the four that are to be auctioned. + #[pallet::call_index(0)] + #[pallet::weight((T::WeightInfo::new_auction(), DispatchClass::Operational))] + pub fn new_auction( + origin: OriginFor, + #[pallet::compact] duration: BlockNumberFor, + #[pallet::compact] lease_period_index: LeasePeriodOf, + ) -> DispatchResult { + T::InitiateOrigin::ensure_origin(origin)?; + Self::do_new_auction(duration, lease_period_index) + } + + /// Make a new bid from an account (including a parachain account) for deploying a new + /// parachain. + /// + /// Multiple simultaneous bids from the same bidder are allowed only as long as all active + /// bids overlap each other (i.e. are mutually exclusive). Bids cannot be redacted. + /// + /// - `sub` is the sub-bidder ID, allowing for multiple competing bids to be made by (and + /// funded by) the same account. + /// - `auction_index` is the index of the auction to bid on. Should just be the present + /// value of `AuctionCounter`. + /// - `first_slot` is the first lease period index of the range to bid on. This is the + /// absolute lease period index value, not an auction-specific offset. + /// - `last_slot` is the last lease period index of the range to bid on. This is the + /// absolute lease period index value, not an auction-specific offset. + /// - `amount` is the amount to bid to be held as deposit for the parachain should the + /// bid win. This amount is held throughout the range. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::bid())] + pub fn bid( + origin: OriginFor, + #[pallet::compact] para: ParaId, + #[pallet::compact] auction_index: AuctionIndex, + #[pallet::compact] first_slot: LeasePeriodOf, + #[pallet::compact] last_slot: LeasePeriodOf, + #[pallet::compact] amount: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::handle_bid(who, para, auction_index, first_slot, last_slot, amount)?; + Ok(()) + } + + /// Cancel an in-progress auction. + /// + /// Can only be called by Root origin. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::cancel_auction())] + pub fn cancel_auction(origin: OriginFor) -> DispatchResult { + ensure_root(origin)?; + // Unreserve all bids. + for ((bidder, _), amount) in ReservedAmounts::::drain() { + CurrencyOf::::unreserve(&bidder, amount); + } + #[allow(deprecated)] + Winning::::remove_all(None); + AuctionInfo::::kill(); + Ok(()) + } + } +} + +impl Auctioneer> for Pallet { + type AccountId = T::AccountId; + type LeasePeriod = BlockNumberFor; + type Currency = CurrencyOf; + + fn new_auction( + duration: BlockNumberFor, + lease_period_index: LeasePeriodOf, + ) -> DispatchResult { + Self::do_new_auction(duration, lease_period_index) + } + + // Returns the status of the auction given the current block number. + fn auction_status(now: BlockNumberFor) -> AuctionStatus> { + let early_end = match AuctionInfo::::get() { + Some((_, early_end)) => early_end, + None => return AuctionStatus::NotStarted, + }; + + let after_early_end = match now.checked_sub(&early_end) { + Some(after_early_end) => after_early_end, + None => return AuctionStatus::StartingPeriod, + }; + + let ending_period = T::EndingPeriod::get(); + if after_early_end < ending_period { + let sample_length = T::SampleLength::get().max(One::one()); + let sample = after_early_end / sample_length; + let sub_sample = after_early_end % sample_length; + return AuctionStatus::EndingPeriod(sample, sub_sample) + } else { + // This is safe because of the comparison operator above + return AuctionStatus::VrfDelay(after_early_end - ending_period) + } + } + + fn place_bid( + bidder: T::AccountId, + para: ParaId, + first_slot: LeasePeriodOf, + last_slot: LeasePeriodOf, + amount: BalanceOf, + ) -> DispatchResult { + Self::handle_bid(bidder, para, AuctionCounter::::get(), first_slot, last_slot, amount) + } + + fn lease_period_index(b: BlockNumberFor) -> Option<(Self::LeasePeriod, bool)> { + T::Leaser::lease_period_index(b) + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn lease_period_length() -> (BlockNumberFor, BlockNumberFor) { + T::Leaser::lease_period_length() + } + + fn has_won_an_auction(para: ParaId, bidder: &T::AccountId) -> bool { + !T::Leaser::deposit_held(para, bidder).is_zero() + } +} + +impl Pallet { + // A trick to allow me to initialize large arrays with nothing in them. + const EMPTY: Option<(::AccountId, ParaId, BalanceOf)> = None; + + /// Create a new auction. + /// + /// This can only happen when there isn't already an auction in progress. Accepts the `duration` + /// of this auction and the `lease_period_index` of the initial lease period of the four that + /// are to be auctioned. + fn do_new_auction( + duration: BlockNumberFor, + lease_period_index: LeasePeriodOf, + ) -> DispatchResult { + let maybe_auction = AuctionInfo::::get(); + ensure!(maybe_auction.is_none(), Error::::AuctionInProgress); + let now = frame_system::Pallet::::block_number(); + if let Some((current_lease_period, _)) = T::Leaser::lease_period_index(now) { + // If there is no active lease period, then we don't need to make this check. + ensure!(lease_period_index >= current_lease_period, Error::::LeasePeriodInPast); + } + + // Bump the counter. + let n = AuctionCounter::::mutate(|n| { + *n += 1; + *n + }); + + // Set the information. + let ending = frame_system::Pallet::::block_number().saturating_add(duration); + AuctionInfo::::put((lease_period_index, ending)); + + Self::deposit_event(Event::::AuctionStarted { + auction_index: n, + lease_period: lease_period_index, + ending, + }); + Ok(()) + } + + /// Actually place a bid in the current auction. + /// + /// - `bidder`: The account that will be funding this bid. + /// - `auction_index`: The auction index of the bid. For this to succeed, must equal + /// the current value of `AuctionCounter`. + /// - `first_slot`: The first lease period index of the range to be bid on. + /// - `last_slot`: The last lease period index of the range to be bid on (inclusive). + /// - `amount`: The total amount to be the bid for deposit over the range. + pub fn handle_bid( + bidder: T::AccountId, + para: ParaId, + auction_index: u32, + first_slot: LeasePeriodOf, + last_slot: LeasePeriodOf, + amount: BalanceOf, + ) -> DispatchResult { + // Ensure para is registered before placing a bid on it. + ensure!(T::Registrar::is_registered(para), Error::::ParaNotRegistered); + // Bidding on latest auction. + ensure!(auction_index == AuctionCounter::::get(), Error::::NotCurrentAuction); + // Assume it's actually an auction (this should never fail because of above). + let (first_lease_period, _) = AuctionInfo::::get().ok_or(Error::::NotAuction)?; + + // Get the auction status and the current sample block. For the starting period, the sample + // block is zero. + let auction_status = Self::auction_status(frame_system::Pallet::::block_number()); + // The offset into the ending samples of the auction. + let offset = match auction_status { + AuctionStatus::NotStarted => return Err(Error::::AuctionEnded.into()), + AuctionStatus::StartingPeriod => Zero::zero(), + AuctionStatus::EndingPeriod(o, _) => o, + AuctionStatus::VrfDelay(_) => return Err(Error::::AuctionEnded.into()), + }; + + // We also make sure that the bid is not for any existing leases the para already has. + ensure!( + !T::Leaser::already_leased(para, first_slot, last_slot), + Error::::AlreadyLeasedOut + ); + + // Our range. + let range = SlotRange::new_bounded(first_lease_period, first_slot, last_slot)?; + // Range as an array index. + let range_index = range as u8 as usize; + + // The current winning ranges. + let mut current_winning = Winning::::get(offset) + .or_else(|| offset.checked_sub(&One::one()).and_then(Winning::::get)) + .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); + + // If this bid beat the previous winner of our range. + if current_winning[range_index].as_ref().map_or(true, |last| amount > last.2) { + // Ok; we are the new winner of this range - reserve the additional amount and record. + + // Get the amount already held on deposit if this is a renewal bid (i.e. there's + // an existing lease on the same para by the same leaser). + let existing_lease_deposit = T::Leaser::deposit_held(para, &bidder); + let reserve_required = amount.saturating_sub(existing_lease_deposit); + + // Get the amount already reserved in any prior and still active bids by us. + let bidder_para = (bidder.clone(), para); + let already_reserved = ReservedAmounts::::get(&bidder_para).unwrap_or_default(); + + // If these don't already cover the bid... + if let Some(additional) = reserve_required.checked_sub(&already_reserved) { + // ...then reserve some more funds from their account, failing if there's not + // enough funds. + CurrencyOf::::reserve(&bidder, additional)?; + // ...and record the amount reserved. + ReservedAmounts::::insert(&bidder_para, reserve_required); + + Self::deposit_event(Event::::Reserved { + bidder: bidder.clone(), + extra_reserved: additional, + total_amount: reserve_required, + }); + } + + // Return any funds reserved for the previous winner if we are not in the ending period + // and they no longer have any active bids. + let mut outgoing_winner = Some((bidder.clone(), para, amount)); + swap(&mut current_winning[range_index], &mut outgoing_winner); + if let Some((who, para, _amount)) = outgoing_winner { + if auction_status.is_starting() && + current_winning + .iter() + .filter_map(Option::as_ref) + .all(|&(ref other, other_para, _)| other != &who || other_para != para) + { + // Previous bidder is no longer winning any ranges: unreserve their funds. + if let Some(amount) = ReservedAmounts::::take(&(who.clone(), para)) { + // It really should be reserved; there's not much we can do here on fail. + let err_amt = CurrencyOf::::unreserve(&who, amount); + debug_assert!(err_amt.is_zero()); + Self::deposit_event(Event::::Unreserved { bidder: who, amount }); + } + } + } + + // Update the range winner. + Winning::::insert(offset, ¤t_winning); + Self::deposit_event(Event::::BidAccepted { + bidder, + para_id: para, + amount, + first_slot, + last_slot, + }); + } + Ok(()) + } + + /// Some when the auction's end is known (with the end block number). None if it is unknown. + /// If `Some` then the block number must be at most the previous block and at least the + /// previous block minus `T::EndingPeriod::get()`. + /// + /// This mutates the state, cleaning up `AuctionInfo` and `Winning` in the case of an auction + /// ending. An immediately subsequent call with the same argument will always return `None`. + fn check_auction_end(now: BlockNumberFor) -> Option<(WinningData, LeasePeriodOf)> { + if let Some((lease_period_index, early_end)) = AuctionInfo::::get() { + let ending_period = T::EndingPeriod::get(); + let late_end = early_end.saturating_add(ending_period); + let is_ended = now >= late_end; + if is_ended { + // auction definitely ended. + // check to see if we can determine the actual ending point. + let (raw_offset, known_since) = T::Randomness::random(&b"para_auction"[..]); + + if late_end <= known_since { + // Our random seed was known only after the auction ended. Good to use. + let raw_offset_block_number = >::decode( + &mut raw_offset.as_ref(), + ) + .expect("secure hashes should always be bigger than the block number; qed"); + let offset = (raw_offset_block_number % ending_period) / + T::SampleLength::get().max(One::one()); + + let auction_counter = AuctionCounter::::get(); + Self::deposit_event(Event::::WinningOffset { + auction_index: auction_counter, + block_number: offset, + }); + let res = Winning::::get(offset) + .unwrap_or([Self::EMPTY; SlotRange::SLOT_RANGE_COUNT]); + // This `remove_all` statement should remove at most `EndingPeriod` / + // `SampleLength` items, which should be bounded and sensibly configured in the + // runtime. + #[allow(deprecated)] + Winning::::remove_all(None); + AuctionInfo::::kill(); + return Some((res, lease_period_index)) + } + } + } + None + } + + /// Auction just ended. We have the current lease period, the auction's lease period (which + /// is guaranteed to be at least the current period) and the bidders that were winning each + /// range at the time of the auction's close. + fn manage_auction_end( + auction_lease_period_index: LeasePeriodOf, + winning_ranges: WinningData, + ) { + // First, unreserve all amounts that were reserved for the bids. We will later re-reserve + // the amounts from the bidders that ended up being assigned the slot so there's no need to + // special-case them here. + for ((bidder, _), amount) in ReservedAmounts::::drain() { + CurrencyOf::::unreserve(&bidder, amount); + } + + // Next, calculate the winning combination of slots and thus the final winners of the + // auction. + let winners = Self::calculate_winners(winning_ranges); + + // Go through those winners and re-reserve their bid, updating our table of deposits + // accordingly. + for (leaser, para, amount, range) in winners.into_iter() { + let begin_offset = LeasePeriodOf::::from(range.as_pair().0 as u32); + let period_begin = auction_lease_period_index + begin_offset; + let period_count = LeasePeriodOf::::from(range.len() as u32); + + match T::Leaser::lease_out(para, &leaser, amount, period_begin, period_count) { + Err(LeaseError::ReserveFailed) | + Err(LeaseError::AlreadyEnded) | + Err(LeaseError::NoLeasePeriod) => { + // Should never happen since we just unreserved this amount (and our offset is + // from the present period). But if it does, there's not much we can do. + }, + Err(LeaseError::AlreadyLeased) => { + // The leaser attempted to get a second lease on the same para ID, possibly + // griefing us. Let's keep the amount reserved and let governance sort it out. + if CurrencyOf::::reserve(&leaser, amount).is_ok() { + Self::deposit_event(Event::::ReserveConfiscated { + para_id: para, + leaser, + amount, + }); + } + }, + Ok(()) => {}, // Nothing to report. + } + } + + Self::deposit_event(Event::::AuctionClosed { + auction_index: AuctionCounter::::get(), + }); + } + + /// Calculate the final winners from the winning slots. + /// + /// This is a simple dynamic programming algorithm designed by Al, the original code is at: + /// `https://github.com/w3f/consensus/blob/master/NPoS/auctiondynamicthing.py` + fn calculate_winners(mut winning: WinningData) -> WinnersData { + let winning_ranges = { + let mut best_winners_ending_at: [(Vec, BalanceOf); + SlotRange::LEASE_PERIODS_PER_SLOT] = Default::default(); + let best_bid = |range: SlotRange| { + winning[range as u8 as usize] + .as_ref() + .map(|(_, _, amount)| *amount * (range.len() as u32).into()) + }; + for i in 0..SlotRange::LEASE_PERIODS_PER_SLOT { + let r = SlotRange::new_bounded(0, 0, i as u32).expect("`i < LPPS`; qed"); + if let Some(bid) = best_bid(r) { + best_winners_ending_at[i] = (vec![r], bid); + } + for j in 0..i { + let r = SlotRange::new_bounded(0, j as u32 + 1, i as u32) + .expect("`i < LPPS`; `j < i`; `j + 1 < LPPS`; qed"); + if let Some(mut bid) = best_bid(r) { + bid += best_winners_ending_at[j].1; + if bid > best_winners_ending_at[i].1 { + let mut new_winners = best_winners_ending_at[j].0.clone(); + new_winners.push(r); + best_winners_ending_at[i] = (new_winners, bid); + } + } else { + if best_winners_ending_at[j].1 > best_winners_ending_at[i].1 { + best_winners_ending_at[i] = best_winners_ending_at[j].clone(); + } + } + } + } + best_winners_ending_at[SlotRange::LEASE_PERIODS_PER_SLOT - 1].0.clone() + }; + + winning_ranges + .into_iter() + .filter_map(|range| { + winning[range as u8 as usize] + .take() + .map(|(bidder, para, amount)| (bidder, para, amount, range)) + }) + .collect::>() + } +} + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; diff --git a/polkadot/runtime/common/src/auctions/tests.rs b/polkadot/runtime/common/src/auctions/tests.rs new file mode 100644 index 000000000000..07574eeb295d --- /dev/null +++ b/polkadot/runtime/common/src/auctions/tests.rs @@ -0,0 +1,821 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Tests for the auctions pallet. + +#[cfg(test)] +use super::*; +use crate::{auctions::mock::*, mock::TestRegistrar}; +use frame_support::{assert_noop, assert_ok, assert_storage_noop}; +use pallet_balances; +use polkadot_primitives::Id as ParaId; +use polkadot_primitives_test_helpers::{dummy_hash, dummy_head_data, dummy_validation_code}; +use sp_core::H256; +use sp_runtime::DispatchError::BadOrigin; + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with(|| { + assert_eq!(AuctionCounter::::get(), 0); + assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::NotStarted + ); + + run_to_block(10); + + assert_eq!(AuctionCounter::::get(), 0); + assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::NotStarted + ); + }); +} + +#[test] +fn can_start_auction() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_noop!(Auctions::new_auction(RuntimeOrigin::signed(1), 5, 1), BadOrigin); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + + assert_eq!(AuctionCounter::::get(), 1); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + }); +} + +#[test] +fn bidding_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); + + assert_eq!(Balances::reserved_balance(1), 5); + assert_eq!(Balances::free_balance(1), 5); + assert_eq!( + Winning::::get(0).unwrap()[SlotRange::ZeroThree as u8 as usize], + Some((1, 0.into(), 5)) + ); + }); +} + +#[test] +fn under_bidding_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); + + assert_storage_noop!({ + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 1)); + }); + }); +} + +#[test] +fn over_bidding_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 6)); + + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::reserved_balance(2), 6); + assert_eq!(Balances::free_balance(2), 14); + assert_eq!( + Winning::::get(0).unwrap()[SlotRange::ZeroThree as u8 as usize], + Some((2, 0.into(), 6)) + ); + }); +} + +#[test] +fn auction_proceeds_correctly() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + + assert_eq!(AuctionCounter::::get(), 1); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(2); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(3); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(4); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(5); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(6); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(0, 0) + ); + + run_to_block(7); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(1, 0) + ); + + run_to_block(8); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(2, 0) + ); + + run_to_block(9); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::NotStarted + ); + }); +} + +#[test] +fn can_win_auction() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); + assert_eq!(Balances::reserved_balance(1), 1); + assert_eq!(Balances::free_balance(1), 9); + run_to_block(9); + + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 4), LeaseData { leaser: 1, amount: 1 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); + }); +} + +#[test] +fn can_win_auction_with_late_randomness() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); + assert_eq!(Balances::reserved_balance(1), 1); + assert_eq!(Balances::free_balance(1), 9); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + run_to_block(8); + // Auction has not yet ended. + assert_eq!(leases(), vec![]); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(2, 0) + ); + // This will prevent the auction's winner from being decided in the next block, since + // the random seed was known before the final bids were made. + set_last_random(H256::zero(), 8); + // Auction definitely ended now, but we don't know exactly when in the last 3 blocks yet + // since no randomness available yet. + run_to_block(9); + // Auction has now ended... But auction winner still not yet decided, so no leases yet. + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::VrfDelay(0) + ); + assert_eq!(leases(), vec![]); + + // Random seed now updated to a value known at block 9, when the auction ended. This + // means that the winner can now be chosen. + set_last_random(H256::zero(), 9); + run_to_block(10); + // Auction ended and winner selected + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::NotStarted + ); + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 4), LeaseData { leaser: 1, amount: 1 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); + }); +} + +#[test] +fn can_win_incomplete_auction() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 5)); + run_to_block(9); + + assert_eq!(leases(), vec![((0.into(), 4), LeaseData { leaser: 1, amount: 5 }),]); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); + }); +} + +#[test] +fn should_choose_best_combination() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 2, 3, 4)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 0.into(), 1, 4, 4, 2)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 1, 1, 4, 2)); + run_to_block(9); + + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 2), LeaseData { leaser: 2, amount: 4 }), + ((0.into(), 3), LeaseData { leaser: 2, amount: 4 }), + ((0.into(), 4), LeaseData { leaser: 3, amount: 2 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); + assert_eq!(TestLeaser::deposit_held(1.into(), &1), 0); + assert_eq!(TestLeaser::deposit_held(0.into(), &2), 4); + assert_eq!(TestLeaser::deposit_held(0.into(), &3), 2); + }); +} + +#[test] +fn gap_bid_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + + // User 1 will make a bid for period 1 and 4 for the same Para 0 + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 4)); + + // User 2 and 3 will make a bid for para 1 on period 2 and 3 respectively + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 1.into(), 1, 2, 2, 2)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 1.into(), 1, 3, 3, 3)); + + // Total reserved should be the max of the two + assert_eq!(Balances::reserved_balance(1), 4); + + // Other people are reserved correctly too + assert_eq!(Balances::reserved_balance(2), 2); + assert_eq!(Balances::reserved_balance(3), 3); + + // End the auction. + run_to_block(9); + + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 4), LeaseData { leaser: 1, amount: 4 }), + ((1.into(), 2), LeaseData { leaser: 2, amount: 2 }), + ((1.into(), 3), LeaseData { leaser: 3, amount: 3 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 4); + assert_eq!(TestLeaser::deposit_held(1.into(), &2), 2); + assert_eq!(TestLeaser::deposit_held(1.into(), &3), 3); + }); +} + +#[test] +fn deposit_credit_should_work() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); + assert_eq!(Balances::reserved_balance(1), 5); + run_to_block(10); + + assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); + + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 2)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 2, 2, 6)); + // Only 1 reserved since we have a deposit credit of 5. + assert_eq!(Balances::reserved_balance(1), 1); + run_to_block(20); + + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 5 }), + ((0.into(), 2), LeaseData { leaser: 1, amount: 6 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 6); + }); +} + +#[test] +fn deposit_credit_on_alt_para_should_not_count() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); + assert_eq!(Balances::reserved_balance(1), 5); + run_to_block(10); + + assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); + + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 2)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 2, 2, 2, 6)); + // 6 reserved since we are bidding on a new para; only works because we don't + assert_eq!(Balances::reserved_balance(1), 6); + run_to_block(20); + + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 1, amount: 5 }), + ((1.into(), 2), LeaseData { leaser: 1, amount: 6 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); + assert_eq!(TestLeaser::deposit_held(1.into(), &1), 6); + }); +} + +#[test] +fn multiple_bids_work_pre_ending() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + + for i in 1..6u64 { + run_to_block(i as _); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); + for j in 1..6 { + assert_eq!(Balances::reserved_balance(j), if j == i { j } else { 0 }); + assert_eq!(Balances::free_balance(j), if j == i { j * 9 } else { j * 10 }); + } + } + + run_to_block(9); + assert_eq!( + leases(), + vec![ + ((0.into(), 1), LeaseData { leaser: 5, amount: 5 }), + ((0.into(), 2), LeaseData { leaser: 5, amount: 5 }), + ((0.into(), 3), LeaseData { leaser: 5, amount: 5 }), + ((0.into(), 4), LeaseData { leaser: 5, amount: 5 }), + ] + ); + }); +} + +#[test] +fn multiple_bids_work_post_ending() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 0, 1)); + + for i in 1..6u64 { + run_to_block(((i - 1) / 2 + 1) as _); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); + for j in 1..6 { + assert_eq!(Balances::reserved_balance(j), if j <= i { j } else { 0 }); + assert_eq!(Balances::free_balance(j), if j <= i { j * 9 } else { j * 10 }); + } + } + for i in 1..6u64 { + assert_eq!(ReservedAmounts::::get((i, ParaId::from(0))).unwrap(), i); + } + + run_to_block(5); + assert_eq!( + leases(), + (1..=4) + .map(|i| ((0.into(), i), LeaseData { leaser: 2, amount: 2 })) + .collect::>() + ); + }); +} + +#[test] +fn incomplete_calculate_winners_works() { + let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; + winning[SlotRange::ThreeThree as u8 as usize] = Some((1, 0.into(), 1)); + + let winners = vec![(1, 0.into(), 1, SlotRange::ThreeThree)]; + + assert_eq!(Auctions::calculate_winners(winning), winners); +} + +#[test] +fn first_incomplete_calculate_winners_works() { + let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; + winning[0] = Some((1, 0.into(), 1)); + + let winners = vec![(1, 0.into(), 1, SlotRange::ZeroZero)]; + + assert_eq!(Auctions::calculate_winners(winning), winners); +} + +#[test] +fn calculate_winners_works() { + let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; + winning[SlotRange::ZeroZero as u8 as usize] = Some((2, 0.into(), 2)); + winning[SlotRange::ZeroThree as u8 as usize] = Some((1, 100.into(), 1)); + winning[SlotRange::OneOne as u8 as usize] = Some((3, 1.into(), 1)); + winning[SlotRange::TwoTwo as u8 as usize] = Some((1, 2.into(), 53)); + winning[SlotRange::ThreeThree as u8 as usize] = Some((5, 3.into(), 1)); + + let winners = vec![ + (2, 0.into(), 2, SlotRange::ZeroZero), + (3, 1.into(), 1, SlotRange::OneOne), + (1, 2.into(), 53, SlotRange::TwoTwo), + (5, 3.into(), 1, SlotRange::ThreeThree), + ]; + assert_eq!(Auctions::calculate_winners(winning), winners); + + winning[SlotRange::ZeroOne as u8 as usize] = Some((4, 10.into(), 3)); + let winners = vec![ + (4, 10.into(), 3, SlotRange::ZeroOne), + (1, 2.into(), 53, SlotRange::TwoTwo), + (5, 3.into(), 1, SlotRange::ThreeThree), + ]; + assert_eq!(Auctions::calculate_winners(winning), winners); + + winning[SlotRange::ZeroThree as u8 as usize] = Some((1, 100.into(), 100)); + let winners = vec![(1, 100.into(), 100, SlotRange::ZeroThree)]; + assert_eq!(Auctions::calculate_winners(winning), winners); +} + +#[test] +fn lower_bids_are_correctly_refunded() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 1, 1)); + let para_1 = ParaId::from(1_u32); + let para_2 = ParaId::from(2_u32); + + // Make a bid and reserve a balance + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9)); + assert_eq!(Balances::reserved_balance(1), 9); + assert_eq!(ReservedAmounts::::get((1, para_1)), Some(9)); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(ReservedAmounts::::get((2, para_2)), None); + + // Bigger bid, reserves new balance and returns funds + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 1, 4, 19)); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(ReservedAmounts::::get((1, para_1)), None); + assert_eq!(Balances::reserved_balance(2), 19); + assert_eq!(ReservedAmounts::::get((2, para_2)), Some(19)); + }); +} + +#[test] +fn initialize_winners_in_ending_period_works() { + new_test_ext().execute_with(|| { + let ed: u64 = ::ExistentialDeposit::get(); + assert_eq!(ed, 1); + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 1)); + let para_1 = ParaId::from(1_u32); + let para_2 = ParaId::from(2_u32); + let para_3 = ParaId::from(3_u32); + + // Make bids + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 1, 4, 9)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 3, 4, 19)); + + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; + winning[SlotRange::ZeroThree as u8 as usize] = Some((1, para_1, 9)); + winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); + assert_eq!(Winning::::get(0), Some(winning)); + + run_to_block(9); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(10); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(0, 0) + ); + assert_eq!(Winning::::get(0), Some(winning)); + + run_to_block(11); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(1, 0) + ); + assert_eq!(Winning::::get(1), Some(winning)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 3, 4, 29)); + + run_to_block(12); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(2, 0) + ); + winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); + assert_eq!(Winning::::get(2), Some(winning)); + }); +} + +#[test] +fn handle_bid_requires_registered_para() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_noop!( + Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1), + Error::::ParaNotRegistered + ); + assert_ok!(TestRegistrar::::register( + 1, + 1337.into(), + dummy_head_data(), + dummy_validation_code() + )); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1)); + }); +} + +#[test] +fn handle_bid_checks_existing_lease_periods() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 2, 3, 1)); + assert_eq!(Balances::reserved_balance(1), 1); + assert_eq!(Balances::free_balance(1), 9); + run_to_block(9); + + assert_eq!( + leases(), + vec![ + ((0.into(), 2), LeaseData { leaser: 1, amount: 1 }), + ((0.into(), 3), LeaseData { leaser: 1, amount: 1 }), + ] + ); + assert_eq!(TestLeaser::deposit_held(0.into(), &1), 1); + + // Para 1 just won an auction above and won some lease periods. + // No bids can work which overlap these periods. + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_noop!( + Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 4, 1), + Error::::AlreadyLeasedOut, + ); + assert_noop!( + Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 2, 1), + Error::::AlreadyLeasedOut, + ); + assert_noop!( + Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 3, 4, 1), + Error::::AlreadyLeasedOut, + ); + // This is okay, not an overlapping bid. + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 1, 1, 1)); + }); +} + +// Here we will test that taking only 10 samples during the ending period works as expected. +#[test] +fn less_winning_samples_work() { + new_test_ext().execute_with(|| { + let ed: u64 = ::ExistentialDeposit::get(); + assert_eq!(ed, 1); + EndingPeriod::set(30); + SampleLength::set(10); + + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); + let para_1 = ParaId::from(1_u32); + let para_2 = ParaId::from(2_u32); + let para_3 = ParaId::from(3_u32); + + // Make bids + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), para_1, 1, 11, 14, 9)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), para_2, 1, 13, 14, 19)); + + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + let mut winning = [None; SlotRange::SLOT_RANGE_COUNT]; + winning[SlotRange::ZeroThree as u8 as usize] = Some((1, para_1, 9)); + winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); + assert_eq!(Winning::::get(0), Some(winning)); + + run_to_block(9); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(10); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(0, 0) + ); + assert_eq!(Winning::::get(0), Some(winning)); + + // New bids update the current winning + assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 14, 14, 29)); + winning[SlotRange::ThreeThree as u8 as usize] = Some((3, para_3, 29)); + assert_eq!(Winning::::get(0), Some(winning)); + + run_to_block(20); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(1, 0) + ); + assert_eq!(Winning::::get(1), Some(winning)); + run_to_block(25); + // Overbid mid sample + assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 13, 14, 29)); + winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); + assert_eq!(Winning::::get(1), Some(winning)); + + run_to_block(30); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(2, 0) + ); + assert_eq!(Winning::::get(2), Some(winning)); + + set_last_random(H256::from([254; 32]), 40); + run_to_block(40); + // Auction ended and winner selected + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::NotStarted + ); + assert_eq!( + leases(), + vec![ + ((3.into(), 13), LeaseData { leaser: 3, amount: 29 }), + ((3.into(), 14), LeaseData { leaser: 3, amount: 29 }), + ] + ); + }); +} + +#[test] +fn auction_status_works() { + new_test_ext().execute_with(|| { + EndingPeriod::set(30); + SampleLength::set(10); + set_last_random(dummy_hash(), 0); + + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::NotStarted + ); + + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); + + run_to_block(9); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::StartingPeriod + ); + + run_to_block(10); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(0, 0) + ); + + run_to_block(11); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(0, 1) + ); + + run_to_block(19); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(0, 9) + ); + + run_to_block(20); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(1, 0) + ); + + run_to_block(25); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(1, 5) + ); + + run_to_block(30); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(2, 0) + ); + + run_to_block(39); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::EndingPeriod(2, 9) + ); + + run_to_block(40); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::VrfDelay(0) + ); + + run_to_block(44); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::VrfDelay(4) + ); + + set_last_random(dummy_hash(), 45); + run_to_block(45); + assert_eq!( + Auctions::auction_status(System::block_number()), + AuctionStatus::::NotStarted + ); + }); +} + +#[test] +fn can_cancel_auction() { + new_test_ext().execute_with(|| { + run_to_block(1); + assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); + assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); + assert_eq!(Balances::reserved_balance(1), 1); + assert_eq!(Balances::free_balance(1), 9); + + assert_noop!(Auctions::cancel_auction(RuntimeOrigin::signed(6)), BadOrigin); + assert_ok!(Auctions::cancel_auction(RuntimeOrigin::root())); + + assert!(AuctionInfo::::get().is_none()); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(ReservedAmounts::::iter().count(), 0); + assert_eq!(Winning::::iter().count(), 0); + }); +} From cbeb66fbf4c7a950ed90a979373bbcca412dc63c Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Thu, 19 Dec 2024 02:55:50 +0100 Subject: [PATCH 194/340] Improve pallet paras_registrar file structure (#6783) Linked to issue #590 Extracted code from mod.rs to new tests, mock and benchmarking files --- .../src/paras_registrar/benchmarking.rs | 171 ++++ .../common/src/paras_registrar/mock.rs | 254 +++++ .../runtime/common/src/paras_registrar/mod.rs | 965 +----------------- .../common/src/paras_registrar/tests.rs | 588 +++++++++++ 4 files changed, 1017 insertions(+), 961 deletions(-) create mode 100644 polkadot/runtime/common/src/paras_registrar/benchmarking.rs create mode 100644 polkadot/runtime/common/src/paras_registrar/mock.rs create mode 100644 polkadot/runtime/common/src/paras_registrar/tests.rs diff --git a/polkadot/runtime/common/src/paras_registrar/benchmarking.rs b/polkadot/runtime/common/src/paras_registrar/benchmarking.rs new file mode 100644 index 000000000000..95df8a969576 --- /dev/null +++ b/polkadot/runtime/common/src/paras_registrar/benchmarking.rs @@ -0,0 +1,171 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Benchmarking for paras_registrar pallet + +#[cfg(feature = "runtime-benchmarks")] +use super::{Pallet as Registrar, *}; +use crate::traits::Registrar as RegistrarT; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use polkadot_primitives::{MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MIN_CODE_SIZE}; +use polkadot_runtime_parachains::{paras, shared, Origin as ParaOrigin}; +use sp_runtime::traits::Bounded; + +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +fn register_para(id: u32) -> ParaId { + let para = ParaId::from(id); + let genesis_head = Registrar::::worst_head_data(); + let validation_code = Registrar::::worst_validation_code(); + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + assert_ok!(Registrar::::reserve(RawOrigin::Signed(caller.clone()).into())); + assert_ok!(Registrar::::register( + RawOrigin::Signed(caller).into(), + para, + genesis_head, + validation_code.clone() + )); + assert_ok!(polkadot_runtime_parachains::paras::Pallet::::add_trusted_validation_code( + frame_system::Origin::::Root.into(), + validation_code, + )); + return para +} + +fn para_origin(id: u32) -> ParaOrigin { + ParaOrigin::Parachain(id.into()) +} + +// This function moves forward to the next scheduled session for parachain lifecycle upgrades. +fn next_scheduled_session() { + shared::Pallet::::set_session_index(shared::Pallet::::scheduled_session()); + paras::Pallet::::test_on_new_session(); +} + +benchmarks! { + where_clause { where ParaOrigin: Into<::RuntimeOrigin> } + + reserve { + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert_last_event::(Event::::Reserved { para_id: LOWEST_PUBLIC_ID, who: caller }.into()); + assert!(Paras::::get(LOWEST_PUBLIC_ID).is_some()); + assert_eq!(paras::Pallet::::lifecycle(LOWEST_PUBLIC_ID), None); + } + + register { + let para = LOWEST_PUBLIC_ID; + let genesis_head = Registrar::::worst_head_data(); + let validation_code = Registrar::::worst_validation_code(); + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + assert_ok!(Registrar::::reserve(RawOrigin::Signed(caller.clone()).into())); + }: _(RawOrigin::Signed(caller.clone()), para, genesis_head, validation_code.clone()) + verify { + assert_last_event::(Event::::Registered{ para_id: para, manager: caller }.into()); + assert_eq!(paras::Pallet::::lifecycle(para), Some(ParaLifecycle::Onboarding)); + assert_ok!(polkadot_runtime_parachains::paras::Pallet::::add_trusted_validation_code( + frame_system::Origin::::Root.into(), + validation_code, + )); + next_scheduled_session::(); + assert_eq!(paras::Pallet::::lifecycle(para), Some(ParaLifecycle::Parathread)); + } + + force_register { + let manager: T::AccountId = account("manager", 0, 0); + let deposit = 0u32.into(); + let para = ParaId::from(69); + let genesis_head = Registrar::::worst_head_data(); + let validation_code = Registrar::::worst_validation_code(); + }: _(RawOrigin::Root, manager.clone(), deposit, para, genesis_head, validation_code.clone()) + verify { + assert_last_event::(Event::::Registered { para_id: para, manager }.into()); + assert_eq!(paras::Pallet::::lifecycle(para), Some(ParaLifecycle::Onboarding)); + assert_ok!(polkadot_runtime_parachains::paras::Pallet::::add_trusted_validation_code( + frame_system::Origin::::Root.into(), + validation_code, + )); + next_scheduled_session::(); + assert_eq!(paras::Pallet::::lifecycle(para), Some(ParaLifecycle::Parathread)); + } + + deregister { + let para = register_para::(LOWEST_PUBLIC_ID.into()); + next_scheduled_session::(); + let caller: T::AccountId = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), para) + verify { + assert_last_event::(Event::::Deregistered { para_id: para }.into()); + } + + swap { + // On demand parachain + let parathread = register_para::(LOWEST_PUBLIC_ID.into()); + let parachain = register_para::((LOWEST_PUBLIC_ID + 1).into()); + + let parachain_origin = para_origin(parachain.into()); + + // Actually finish registration process + next_scheduled_session::(); + + // Upgrade the parachain + Registrar::::make_parachain(parachain)?; + next_scheduled_session::(); + + assert_eq!(paras::Pallet::::lifecycle(parachain), Some(ParaLifecycle::Parachain)); + assert_eq!(paras::Pallet::::lifecycle(parathread), Some(ParaLifecycle::Parathread)); + + let caller: T::AccountId = whitelisted_caller(); + Registrar::::swap(parachain_origin.into(), parachain, parathread)?; + }: _(RawOrigin::Signed(caller.clone()), parathread, parachain) + verify { + next_scheduled_session::(); + // Swapped! + assert_eq!(paras::Pallet::::lifecycle(parachain), Some(ParaLifecycle::Parathread)); + assert_eq!(paras::Pallet::::lifecycle(parathread), Some(ParaLifecycle::Parachain)); + } + + schedule_code_upgrade { + let b in MIN_CODE_SIZE .. MAX_CODE_SIZE; + let new_code = ValidationCode(vec![0; b as usize]); + let para_id = ParaId::from(1000); + }: _(RawOrigin::Root, para_id, new_code) + + set_current_head { + let b in 1 .. MAX_HEAD_DATA_SIZE; + let new_head = HeadData(vec![0; b as usize]); + let para_id = ParaId::from(1000); + }: _(RawOrigin::Root, para_id, new_head) + + impl_benchmark_test_suite!( + Registrar, + crate::integration_tests::new_test_ext(), + crate::integration_tests::Test, + ); +} diff --git a/polkadot/runtime/common/src/paras_registrar/mock.rs b/polkadot/runtime/common/src/paras_registrar/mock.rs new file mode 100644 index 000000000000..1627fd70365d --- /dev/null +++ b/polkadot/runtime/common/src/paras_registrar/mock.rs @@ -0,0 +1,254 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Mocking utilities for testing in paras_registrar pallet. + +#[cfg(test)] +use super::*; +use crate::paras_registrar; +use alloc::collections::btree_map::BTreeMap; +use frame_support::{ + derive_impl, parameter_types, + traits::{OnFinalize, OnInitialize}, +}; +use frame_system::limits; +use polkadot_primitives::{Balance, BlockNumber, MAX_CODE_SIZE}; +use polkadot_runtime_parachains::{configuration, origin, shared}; +use sp_core::H256; +use sp_io::TestExternalities; +use sp_keyring::Sr25519Keyring; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + transaction_validity::TransactionPriority, + BuildStorage, Perbill, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlockU32; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Configuration: configuration, + Parachains: paras, + ParasShared: shared, + Registrar: paras_registrar, + ParachainsOrigin: origin, + } +); + +impl frame_system::offchain::CreateTransactionBase for Test +where + RuntimeCall: From, +{ + type Extrinsic = UncheckedExtrinsic; + type RuntimeCall = RuntimeCall; +} + +impl frame_system::offchain::CreateInherent for Test +where + RuntimeCall: From, +{ + fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic { + UncheckedExtrinsic::new_bare(call) + } +} + +const NORMAL_RATIO: Perbill = Perbill::from_percent(75); +parameter_types! { + pub BlockWeights: limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX)); + pub BlockLength: limits::BlockLength = + limits::BlockLength::max_with_normal_ratio(4 * 1024 * 1024, NORMAL_RATIO); +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type DbWeight = (); + type BlockWeights = BlockWeights; + type BlockLength = BlockLength; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 1; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + +impl shared::Config for Test { + type DisabledValidators = (); +} + +impl origin::Config for Test {} + +parameter_types! { + pub const ParasUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); +} + +impl paras::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = paras::TestWeightInfo; + type UnsignedPriority = ParasUnsignedPriority; + type QueueFootprinter = (); + type NextSessionRotation = crate::mock::TestNextSessionRotation; + type OnNewHead = (); + type AssignCoretime = (); +} + +impl configuration::Config for Test { + type WeightInfo = configuration::TestWeightInfo; +} + +parameter_types! { + pub const ParaDeposit: Balance = 10; + pub const DataDepositPerByte: Balance = 1; + pub const MaxRetries: u32 = 3; +} + +impl Config for Test { + type RuntimeOrigin = RuntimeOrigin; + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type OnSwap = MockSwap; + type ParaDeposit = ParaDeposit; + type DataDepositPerByte = DataDepositPerByte; + type WeightInfo = TestWeightInfo; +} + +pub fn new_test_ext() -> TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + configuration::GenesisConfig:: { + config: configuration::HostConfiguration { + max_code_size: MAX_CODE_SIZE, + max_head_data_size: 1 * 1024 * 1024, // 1 MB + ..Default::default() + }, + } + .assimilate_storage(&mut t) + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10_000_000), (2, 10_000_000), (3, 10_000_000)], + } + .assimilate_storage(&mut t) + .unwrap(); + + t.into() +} + +parameter_types! { + pub static SwapData: BTreeMap = BTreeMap::new(); +} + +pub struct MockSwap; +impl OnSwap for MockSwap { + fn on_swap(one: ParaId, other: ParaId) { + let mut swap_data = SwapData::get(); + let one_data = swap_data.remove(&one).unwrap_or_default(); + let other_data = swap_data.remove(&other).unwrap_or_default(); + swap_data.insert(one, other_data); + swap_data.insert(other, one_data); + SwapData::set(swap_data); + } +} + +pub const BLOCKS_PER_SESSION: u32 = 3; + +pub const VALIDATORS: &[Sr25519Keyring] = &[ + Sr25519Keyring::Alice, + Sr25519Keyring::Bob, + Sr25519Keyring::Charlie, + Sr25519Keyring::Dave, + Sr25519Keyring::Ferdie, +]; + +pub fn run_to_block(n: BlockNumber) { + // NOTE that this function only simulates modules of interest. Depending on new pallet may + // require adding it here. + assert!(System::block_number() < n); + while System::block_number() < n { + let b = System::block_number(); + + if System::block_number() > 1 { + System::on_finalize(System::block_number()); + } + // Session change every 3 blocks. + if (b + 1) % BLOCKS_PER_SESSION == 0 { + let session_index = shared::CurrentSessionIndex::::get() + 1; + let validators_pub_keys = VALIDATORS.iter().map(|v| v.public().into()).collect(); + + shared::Pallet::::set_session_index(session_index); + shared::Pallet::::set_active_validators_ascending(validators_pub_keys); + + Parachains::test_on_new_session(); + } + System::set_block_number(b + 1); + System::on_initialize(System::block_number()); + } +} + +pub fn run_to_session(n: BlockNumber) { + let block_number = n * BLOCKS_PER_SESSION; + run_to_block(block_number); +} + +pub fn test_genesis_head(size: usize) -> HeadData { + HeadData(vec![0u8; size]) +} + +pub fn test_validation_code(size: usize) -> ValidationCode { + let validation_code = vec![0u8; size as usize]; + ValidationCode(validation_code) +} + +pub fn para_origin(id: ParaId) -> RuntimeOrigin { + polkadot_runtime_parachains::Origin::Parachain(id).into() +} + +pub fn max_code_size() -> u32 { + configuration::ActiveConfig::::get().max_code_size +} + +pub fn max_head_size() -> u32 { + configuration::ActiveConfig::::get().max_head_data_size +} diff --git a/polkadot/runtime/common/src/paras_registrar/mod.rs b/polkadot/runtime/common/src/paras_registrar/mod.rs index 2ead621dedf0..07832bba18ed 100644 --- a/polkadot/runtime/common/src/paras_registrar/mod.rs +++ b/polkadot/runtime/common/src/paras_registrar/mod.rs @@ -713,967 +713,10 @@ impl OnNewHead for Pallet { } #[cfg(test)] -mod tests { - use super::*; - use crate::{ - mock::conclude_pvf_checking, paras_registrar, traits::Registrar as RegistrarTrait, - }; - use alloc::collections::btree_map::BTreeMap; - use frame_support::{ - assert_noop, assert_ok, derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, - }; - use frame_system::limits; - use pallet_balances::Error as BalancesError; - use polkadot_primitives::{Balance, BlockNumber, SessionIndex, MAX_CODE_SIZE}; - use polkadot_runtime_parachains::{configuration, origin, shared}; - use sp_core::H256; - use sp_io::TestExternalities; - use sp_keyring::Sr25519Keyring; - use sp_runtime::{ - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, - transaction_validity::TransactionPriority, - BuildStorage, Perbill, - }; - - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - type Block = frame_system::mocking::MockBlockU32; - - frame_support::construct_runtime!( - pub enum Test - { - System: frame_system, - Balances: pallet_balances, - Configuration: configuration, - Parachains: paras, - ParasShared: shared, - Registrar: paras_registrar, - ParachainsOrigin: origin, - } - ); - - impl frame_system::offchain::CreateTransactionBase for Test - where - RuntimeCall: From, - { - type Extrinsic = UncheckedExtrinsic; - type RuntimeCall = RuntimeCall; - } - - impl frame_system::offchain::CreateInherent for Test - where - RuntimeCall: From, - { - fn create_inherent(call: Self::RuntimeCall) -> Self::Extrinsic { - UncheckedExtrinsic::new_bare(call) - } - } - - const NORMAL_RATIO: Perbill = Perbill::from_percent(75); - parameter_types! { - pub BlockWeights: limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, u64::MAX)); - pub BlockLength: limits::BlockLength = - limits::BlockLength::max_with_normal_ratio(4 * 1024 * 1024, NORMAL_RATIO); - } - - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] - impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - type Nonce = u64; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Block = Block; - type RuntimeEvent = RuntimeEvent; - type DbWeight = (); - type BlockWeights = BlockWeights; - type BlockLength = BlockLength; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - } - - parameter_types! { - pub const ExistentialDeposit: Balance = 1; - } - - #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] - impl pallet_balances::Config for Test { - type Balance = Balance; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - } - - impl shared::Config for Test { - type DisabledValidators = (); - } - - impl origin::Config for Test {} - - parameter_types! { - pub const ParasUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); - } - - impl paras::Config for Test { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = paras::TestWeightInfo; - type UnsignedPriority = ParasUnsignedPriority; - type QueueFootprinter = (); - type NextSessionRotation = crate::mock::TestNextSessionRotation; - type OnNewHead = (); - type AssignCoretime = (); - } - - impl configuration::Config for Test { - type WeightInfo = configuration::TestWeightInfo; - } - - parameter_types! { - pub const ParaDeposit: Balance = 10; - pub const DataDepositPerByte: Balance = 1; - pub const MaxRetries: u32 = 3; - } - - impl Config for Test { - type RuntimeOrigin = RuntimeOrigin; - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type OnSwap = MockSwap; - type ParaDeposit = ParaDeposit; - type DataDepositPerByte = DataDepositPerByte; - type WeightInfo = TestWeightInfo; - } - - pub fn new_test_ext() -> TestExternalities { - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - - configuration::GenesisConfig:: { - config: configuration::HostConfiguration { - max_code_size: MAX_CODE_SIZE, - max_head_data_size: 1 * 1024 * 1024, // 1 MB - ..Default::default() - }, - } - .assimilate_storage(&mut t) - .unwrap(); - - pallet_balances::GenesisConfig:: { - balances: vec![(1, 10_000_000), (2, 10_000_000), (3, 10_000_000)], - } - .assimilate_storage(&mut t) - .unwrap(); - - t.into() - } - - parameter_types! { - pub static SwapData: BTreeMap = BTreeMap::new(); - } - - pub struct MockSwap; - impl OnSwap for MockSwap { - fn on_swap(one: ParaId, other: ParaId) { - let mut swap_data = SwapData::get(); - let one_data = swap_data.remove(&one).unwrap_or_default(); - let other_data = swap_data.remove(&other).unwrap_or_default(); - swap_data.insert(one, other_data); - swap_data.insert(other, one_data); - SwapData::set(swap_data); - } - } - - const BLOCKS_PER_SESSION: u32 = 3; - - const VALIDATORS: &[Sr25519Keyring] = &[ - Sr25519Keyring::Alice, - Sr25519Keyring::Bob, - Sr25519Keyring::Charlie, - Sr25519Keyring::Dave, - Sr25519Keyring::Ferdie, - ]; - - fn run_to_block(n: BlockNumber) { - // NOTE that this function only simulates modules of interest. Depending on new pallet may - // require adding it here. - assert!(System::block_number() < n); - while System::block_number() < n { - let b = System::block_number(); - - if System::block_number() > 1 { - System::on_finalize(System::block_number()); - } - // Session change every 3 blocks. - if (b + 1) % BLOCKS_PER_SESSION == 0 { - let session_index = shared::CurrentSessionIndex::::get() + 1; - let validators_pub_keys = VALIDATORS.iter().map(|v| v.public().into()).collect(); - - shared::Pallet::::set_session_index(session_index); - shared::Pallet::::set_active_validators_ascending(validators_pub_keys); - - Parachains::test_on_new_session(); - } - System::set_block_number(b + 1); - System::on_initialize(System::block_number()); - } - } - - fn run_to_session(n: BlockNumber) { - let block_number = n * BLOCKS_PER_SESSION; - run_to_block(block_number); - } - - fn test_genesis_head(size: usize) -> HeadData { - HeadData(vec![0u8; size]) - } - - fn test_validation_code(size: usize) -> ValidationCode { - let validation_code = vec![0u8; size as usize]; - ValidationCode(validation_code) - } - - fn para_origin(id: ParaId) -> RuntimeOrigin { - polkadot_runtime_parachains::Origin::Parachain(id).into() - } - - fn max_code_size() -> u32 { - configuration::ActiveConfig::::get().max_code_size - } - - fn max_head_size() -> u32 { - configuration::ActiveConfig::::get().max_head_data_size - } - - #[test] - fn basic_setup_works() { - new_test_ext().execute_with(|| { - assert_eq!(PendingSwap::::get(&ParaId::from(0u32)), None); - assert_eq!(Paras::::get(&ParaId::from(0u32)), None); - }); - } +mod mock; - #[test] - fn end_to_end_scenario_works() { - new_test_ext().execute_with(|| { - let para_id = LOWEST_PUBLIC_ID; - - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - // first para is not yet registered - assert!(!Parachains::is_parathread(para_id)); - // We register the Para ID - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - // It is now a parathread (on-demand parachain). - assert!(Parachains::is_parathread(para_id)); - assert!(!Parachains::is_parachain(para_id)); - // Some other external process will elevate on-demand to lease holding parachain - assert_ok!(Registrar::make_parachain(para_id)); - run_to_session(START_SESSION_INDEX + 4); - // It is now a lease holding parachain. - assert!(!Parachains::is_parathread(para_id)); - assert!(Parachains::is_parachain(para_id)); - // Turn it back into a parathread (on-demand parachain) - assert_ok!(Registrar::make_parathread(para_id)); - run_to_session(START_SESSION_INDEX + 6); - assert!(Parachains::is_parathread(para_id)); - assert!(!Parachains::is_parachain(para_id)); - // Deregister it - assert_ok!(Registrar::deregister(RuntimeOrigin::root(), para_id,)); - run_to_session(START_SESSION_INDEX + 8); - // It is nothing - assert!(!Parachains::is_parathread(para_id)); - assert!(!Parachains::is_parachain(para_id)); - }); - } - - #[test] - fn register_works() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - let para_id = LOWEST_PUBLIC_ID; - assert!(!Parachains::is_parathread(para_id)); - - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_eq!(Balances::reserved_balance(&1), ::ParaDeposit::get()); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - assert!(Parachains::is_parathread(para_id)); - // Even though the registered validation code has a smaller size than the maximum the - // para manager's deposit is reserved as though they registered the maximum-sized code. - // Consequently, they can upgrade their code to the maximum size at any point without - // additional cost. - let validation_code_deposit = - max_code_size() as BalanceOf * ::DataDepositPerByte::get(); - let head_deposit = 32 * ::DataDepositPerByte::get(); - assert_eq!( - Balances::reserved_balance(&1), - ::ParaDeposit::get() + head_deposit + validation_code_deposit - ); - }); - } - - #[test] - fn schedule_code_upgrade_validates_code() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - let para_id = LOWEST_PUBLIC_ID; - assert!(!Parachains::is_parathread(para_id)); - - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_eq!(Balances::reserved_balance(&1), ::ParaDeposit::get()); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - assert!(Parachains::is_parathread(para_id)); - - let new_code = test_validation_code(0); - assert_noop!( - Registrar::schedule_code_upgrade( - RuntimeOrigin::signed(1), - para_id, - new_code.clone(), - ), - paras::Error::::InvalidCode - ); - - let new_code = test_validation_code(max_code_size() as usize + 1); - assert_noop!( - Registrar::schedule_code_upgrade( - RuntimeOrigin::signed(1), - para_id, - new_code.clone(), - ), - paras::Error::::InvalidCode - ); - }); - } - - #[test] - fn register_handles_basic_errors() { - new_test_ext().execute_with(|| { - let para_id = LOWEST_PUBLIC_ID; - - assert_noop!( - Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(max_head_size() as usize), - test_validation_code(max_code_size() as usize), - ), - Error::::NotReserved - ); - - // Successfully register para - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - - assert_noop!( - Registrar::register( - RuntimeOrigin::signed(2), - para_id, - test_genesis_head(max_head_size() as usize), - test_validation_code(max_code_size() as usize), - ), - Error::::NotOwner - ); - - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(max_head_size() as usize), - test_validation_code(max_code_size() as usize), - )); - // Can skip pre-check and deregister para which's still onboarding. - run_to_session(2); - - assert_ok!(Registrar::deregister(RuntimeOrigin::root(), para_id)); - - // Can't do it again - assert_noop!( - Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(max_head_size() as usize), - test_validation_code(max_code_size() as usize), - ), - Error::::NotReserved - ); - - // Head Size Check - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(2))); - assert_noop!( - Registrar::register( - RuntimeOrigin::signed(2), - para_id + 1, - test_genesis_head((max_head_size() + 1) as usize), - test_validation_code(max_code_size() as usize), - ), - Error::::HeadDataTooLarge - ); - - // Code Size Check - assert_noop!( - Registrar::register( - RuntimeOrigin::signed(2), - para_id + 1, - test_genesis_head(max_head_size() as usize), - test_validation_code((max_code_size() + 1) as usize), - ), - Error::::CodeTooLarge - ); - - // Needs enough funds for deposit - assert_noop!( - Registrar::reserve(RuntimeOrigin::signed(1337)), - BalancesError::::InsufficientBalance - ); - }); - } - - #[test] - fn deregister_works() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - let para_id = LOWEST_PUBLIC_ID; - assert!(!Parachains::is_parathread(para_id)); - - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - assert!(Parachains::is_parathread(para_id)); - assert_ok!(Registrar::deregister(RuntimeOrigin::root(), para_id,)); - run_to_session(START_SESSION_INDEX + 4); - assert!(paras::Pallet::::lifecycle(para_id).is_none()); - assert_eq!(Balances::reserved_balance(&1), 0); - }); - } - - #[test] - fn deregister_handles_basic_errors() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - let para_id = LOWEST_PUBLIC_ID; - assert!(!Parachains::is_parathread(para_id)); - - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - assert!(Parachains::is_parathread(para_id)); - // Owner check - assert_noop!(Registrar::deregister(RuntimeOrigin::signed(2), para_id,), BadOrigin); - assert_ok!(Registrar::make_parachain(para_id)); - run_to_session(START_SESSION_INDEX + 4); - // Cant directly deregister parachain - assert_noop!( - Registrar::deregister(RuntimeOrigin::root(), para_id,), - Error::::NotParathread - ); - }); - } - - #[test] - fn swap_works() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - // Successfully register first two parachains - let para_1 = LOWEST_PUBLIC_ID; - let para_2 = LOWEST_PUBLIC_ID + 1; - - let validation_code = test_validation_code(max_code_size() as usize); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_1, - test_genesis_head(max_head_size() as usize), - validation_code.clone(), - )); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(2))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(2), - para_2, - test_genesis_head(max_head_size() as usize), - validation_code.clone(), - )); - conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); - - run_to_session(START_SESSION_INDEX + 2); - - // Upgrade para 1 into a parachain - assert_ok!(Registrar::make_parachain(para_1)); - - // Set some mock swap data. - let mut swap_data = SwapData::get(); - swap_data.insert(para_1, 69); - swap_data.insert(para_2, 1337); - SwapData::set(swap_data); - - run_to_session(START_SESSION_INDEX + 4); - - // Roles are as we expect - assert!(Parachains::is_parachain(para_1)); - assert!(!Parachains::is_parathread(para_1)); - assert!(!Parachains::is_parachain(para_2)); - assert!(Parachains::is_parathread(para_2)); - - // Both paras initiate a swap - // Swap between parachain and parathread - assert_ok!(Registrar::swap(para_origin(para_1), para_1, para_2,)); - assert_ok!(Registrar::swap(para_origin(para_2), para_2, para_1,)); - System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { - para_id: para_2, - other_id: para_1, - })); - - run_to_session(START_SESSION_INDEX + 6); - - // Roles are swapped - assert!(!Parachains::is_parachain(para_1)); - assert!(Parachains::is_parathread(para_1)); - assert!(Parachains::is_parachain(para_2)); - assert!(!Parachains::is_parathread(para_2)); - - // Data is swapped - assert_eq!(SwapData::get().get(¶_1).unwrap(), &1337); - assert_eq!(SwapData::get().get(¶_2).unwrap(), &69); - - // Both paras initiate a swap - // Swap between parathread and parachain - assert_ok!(Registrar::swap(para_origin(para_1), para_1, para_2,)); - assert_ok!(Registrar::swap(para_origin(para_2), para_2, para_1,)); - System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { - para_id: para_2, - other_id: para_1, - })); - - // Data is swapped - assert_eq!(SwapData::get().get(¶_1).unwrap(), &69); - assert_eq!(SwapData::get().get(¶_2).unwrap(), &1337); - - // Parachain to parachain swap - let para_3 = LOWEST_PUBLIC_ID + 2; - let validation_code = test_validation_code(max_code_size() as usize); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(3))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(3), - para_3, - test_genesis_head(max_head_size() as usize), - validation_code.clone(), - )); - conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX + 6); - - run_to_session(START_SESSION_INDEX + 8); - - // Upgrade para 3 into a parachain - assert_ok!(Registrar::make_parachain(para_3)); - - // Set some mock swap data. - let mut swap_data = SwapData::get(); - swap_data.insert(para_3, 777); - SwapData::set(swap_data); - - run_to_session(START_SESSION_INDEX + 10); - - // Both are parachains - assert!(Parachains::is_parachain(para_3)); - assert!(!Parachains::is_parathread(para_3)); - assert!(Parachains::is_parachain(para_1)); - assert!(!Parachains::is_parathread(para_1)); - - // Both paras initiate a swap - // Swap between parachain and parachain - assert_ok!(Registrar::swap(para_origin(para_1), para_1, para_3,)); - assert_ok!(Registrar::swap(para_origin(para_3), para_3, para_1,)); - System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { - para_id: para_3, - other_id: para_1, - })); - - // Data is swapped - assert_eq!(SwapData::get().get(¶_3).unwrap(), &69); - assert_eq!(SwapData::get().get(¶_1).unwrap(), &777); - }); - } - - #[test] - fn para_lock_works() { - new_test_ext().execute_with(|| { - run_to_block(1); - - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - let para_id = LOWEST_PUBLIC_ID; - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_id, - vec![1; 3].into(), - test_validation_code(32) - )); - - assert_noop!(Registrar::add_lock(RuntimeOrigin::signed(2), para_id), BadOrigin); - - // Once they produces new block, we lock them in. - Registrar::on_new_head(para_id, &Default::default()); - - // Owner cannot pass origin check when checking lock - assert_noop!( - Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id), - BadOrigin - ); - // Owner cannot remove lock. - assert_noop!(Registrar::remove_lock(RuntimeOrigin::signed(1), para_id), BadOrigin); - // Para can. - assert_ok!(Registrar::remove_lock(para_origin(para_id), para_id)); - // Owner can pass origin check again - assert_ok!(Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id)); - - // Won't lock again after it is unlocked - Registrar::on_new_head(para_id, &Default::default()); - - assert_ok!(Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id)); - }); - } - - #[test] - fn swap_handles_bad_states() { - new_test_ext().execute_with(|| { - const START_SESSION_INDEX: SessionIndex = 1; - run_to_session(START_SESSION_INDEX); - - let para_1 = LOWEST_PUBLIC_ID; - let para_2 = LOWEST_PUBLIC_ID + 1; - - // paras are not yet registered - assert!(!Parachains::is_parathread(para_1)); - assert!(!Parachains::is_parathread(para_2)); - - // Cannot even start a swap - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_1, para_2), - Error::::NotRegistered - ); - - // We register Paras 1 and 2 - let validation_code = test_validation_code(32); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(1))); - assert_ok!(Registrar::reserve(RuntimeOrigin::signed(2))); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(1), - para_1, - test_genesis_head(32), - validation_code.clone(), - )); - assert_ok!(Registrar::register( - RuntimeOrigin::signed(2), - para_2, - test_genesis_head(32), - validation_code.clone(), - )); - conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::::CannotSwap - ); - - run_to_session(START_SESSION_INDEX + 2); - - // They are now parathreads (on-demand parachains). - assert!(Parachains::is_parathread(para_1)); - assert!(Parachains::is_parathread(para_2)); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::::CannotSwap - ); - - // Some other external process will elevate one on-demand - // parachain to a lease holding parachain - assert_ok!(Registrar::make_parachain(para_1)); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::::CannotSwap - ); - - run_to_session(START_SESSION_INDEX + 3); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::::CannotSwap - ); - - run_to_session(START_SESSION_INDEX + 4); - - // It is now a lease holding parachain. - assert!(Parachains::is_parachain(para_1)); - assert!(Parachains::is_parathread(para_2)); - - // Swap works here. - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_2, para_1)); - assert!(System::events().iter().any(|r| matches!( - r.event, - RuntimeEvent::Registrar(paras_registrar::Event::Swapped { .. }) - ))); - - run_to_session(START_SESSION_INDEX + 5); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::::CannotSwap - ); - - run_to_session(START_SESSION_INDEX + 6); - - // Swap worked! - assert!(Parachains::is_parachain(para_2)); - assert!(Parachains::is_parathread(para_1)); - assert!(System::events().iter().any(|r| matches!( - r.event, - RuntimeEvent::Registrar(paras_registrar::Event::Swapped { .. }) - ))); - - // Something starts to downgrade a para - assert_ok!(Registrar::make_parathread(para_2)); - - run_to_session(START_SESSION_INDEX + 7); - - // Cannot swap - assert_ok!(Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); - assert_noop!( - Registrar::swap(RuntimeOrigin::root(), para_2, para_1), - Error::::CannotSwap - ); - - run_to_session(START_SESSION_INDEX + 8); - - assert!(Parachains::is_parathread(para_1)); - assert!(Parachains::is_parathread(para_2)); - }); - } -} +#[cfg(test)] +mod tests; #[cfg(feature = "runtime-benchmarks")] -mod benchmarking { - use super::{Pallet as Registrar, *}; - use crate::traits::Registrar as RegistrarT; - use frame_support::assert_ok; - use frame_system::RawOrigin; - use polkadot_primitives::{MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MIN_CODE_SIZE}; - use polkadot_runtime_parachains::{paras, shared, Origin as ParaOrigin}; - use sp_runtime::traits::Bounded; - - use frame_benchmarking::{account, benchmarks, whitelisted_caller}; - - fn assert_last_event(generic_event: ::RuntimeEvent) { - let events = frame_system::Pallet::::events(); - let system_event: ::RuntimeEvent = generic_event.into(); - // compare to the last event record - let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; - assert_eq!(event, &system_event); - } - - fn register_para(id: u32) -> ParaId { - let para = ParaId::from(id); - let genesis_head = Registrar::::worst_head_data(); - let validation_code = Registrar::::worst_validation_code(); - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - assert_ok!(Registrar::::reserve(RawOrigin::Signed(caller.clone()).into())); - assert_ok!(Registrar::::register( - RawOrigin::Signed(caller).into(), - para, - genesis_head, - validation_code.clone() - )); - assert_ok!(polkadot_runtime_parachains::paras::Pallet::::add_trusted_validation_code( - frame_system::Origin::::Root.into(), - validation_code, - )); - return para - } - - fn para_origin(id: u32) -> ParaOrigin { - ParaOrigin::Parachain(id.into()) - } - - // This function moves forward to the next scheduled session for parachain lifecycle upgrades. - fn next_scheduled_session() { - shared::Pallet::::set_session_index(shared::Pallet::::scheduled_session()); - paras::Pallet::::test_on_new_session(); - } - - benchmarks! { - where_clause { where ParaOrigin: Into<::RuntimeOrigin> } - - reserve { - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - }: _(RawOrigin::Signed(caller.clone())) - verify { - assert_last_event::(Event::::Reserved { para_id: LOWEST_PUBLIC_ID, who: caller }.into()); - assert!(Paras::::get(LOWEST_PUBLIC_ID).is_some()); - assert_eq!(paras::Pallet::::lifecycle(LOWEST_PUBLIC_ID), None); - } - - register { - let para = LOWEST_PUBLIC_ID; - let genesis_head = Registrar::::worst_head_data(); - let validation_code = Registrar::::worst_validation_code(); - let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - assert_ok!(Registrar::::reserve(RawOrigin::Signed(caller.clone()).into())); - }: _(RawOrigin::Signed(caller.clone()), para, genesis_head, validation_code.clone()) - verify { - assert_last_event::(Event::::Registered{ para_id: para, manager: caller }.into()); - assert_eq!(paras::Pallet::::lifecycle(para), Some(ParaLifecycle::Onboarding)); - assert_ok!(polkadot_runtime_parachains::paras::Pallet::::add_trusted_validation_code( - frame_system::Origin::::Root.into(), - validation_code, - )); - next_scheduled_session::(); - assert_eq!(paras::Pallet::::lifecycle(para), Some(ParaLifecycle::Parathread)); - } - - force_register { - let manager: T::AccountId = account("manager", 0, 0); - let deposit = 0u32.into(); - let para = ParaId::from(69); - let genesis_head = Registrar::::worst_head_data(); - let validation_code = Registrar::::worst_validation_code(); - }: _(RawOrigin::Root, manager.clone(), deposit, para, genesis_head, validation_code.clone()) - verify { - assert_last_event::(Event::::Registered { para_id: para, manager }.into()); - assert_eq!(paras::Pallet::::lifecycle(para), Some(ParaLifecycle::Onboarding)); - assert_ok!(polkadot_runtime_parachains::paras::Pallet::::add_trusted_validation_code( - frame_system::Origin::::Root.into(), - validation_code, - )); - next_scheduled_session::(); - assert_eq!(paras::Pallet::::lifecycle(para), Some(ParaLifecycle::Parathread)); - } - - deregister { - let para = register_para::(LOWEST_PUBLIC_ID.into()); - next_scheduled_session::(); - let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), para) - verify { - assert_last_event::(Event::::Deregistered { para_id: para }.into()); - } - - swap { - // On demand parachain - let parathread = register_para::(LOWEST_PUBLIC_ID.into()); - let parachain = register_para::((LOWEST_PUBLIC_ID + 1).into()); - - let parachain_origin = para_origin(parachain.into()); - - // Actually finish registration process - next_scheduled_session::(); - - // Upgrade the parachain - Registrar::::make_parachain(parachain)?; - next_scheduled_session::(); - - assert_eq!(paras::Pallet::::lifecycle(parachain), Some(ParaLifecycle::Parachain)); - assert_eq!(paras::Pallet::::lifecycle(parathread), Some(ParaLifecycle::Parathread)); - - let caller: T::AccountId = whitelisted_caller(); - Registrar::::swap(parachain_origin.into(), parachain, parathread)?; - }: _(RawOrigin::Signed(caller.clone()), parathread, parachain) - verify { - next_scheduled_session::(); - // Swapped! - assert_eq!(paras::Pallet::::lifecycle(parachain), Some(ParaLifecycle::Parathread)); - assert_eq!(paras::Pallet::::lifecycle(parathread), Some(ParaLifecycle::Parachain)); - } - - schedule_code_upgrade { - let b in MIN_CODE_SIZE .. MAX_CODE_SIZE; - let new_code = ValidationCode(vec![0; b as usize]); - let para_id = ParaId::from(1000); - }: _(RawOrigin::Root, para_id, new_code) - - set_current_head { - let b in 1 .. MAX_HEAD_DATA_SIZE; - let new_head = HeadData(vec![0; b as usize]); - let para_id = ParaId::from(1000); - }: _(RawOrigin::Root, para_id, new_head) - - impl_benchmark_test_suite!( - Registrar, - crate::integration_tests::new_test_ext(), - crate::integration_tests::Test, - ); - } -} +mod benchmarking; diff --git a/polkadot/runtime/common/src/paras_registrar/tests.rs b/polkadot/runtime/common/src/paras_registrar/tests.rs new file mode 100644 index 000000000000..252de8f349da --- /dev/null +++ b/polkadot/runtime/common/src/paras_registrar/tests.rs @@ -0,0 +1,588 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Tests for the paras_registrar pallet. + +#[cfg(test)] +use super::*; +use crate::{ + mock::conclude_pvf_checking, paras_registrar, paras_registrar::mock::*, + traits::Registrar as RegistrarTrait, +}; +use frame_support::{assert_noop, assert_ok}; +use pallet_balances::Error as BalancesError; +use polkadot_primitives::SessionIndex; +use sp_runtime::traits::BadOrigin; + +#[test] +fn end_to_end_scenario_works() { + new_test_ext().execute_with(|| { + let para_id = LOWEST_PUBLIC_ID; + + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + // first para is not yet registered + assert!(!Parachains::is_parathread(para_id)); + // We register the Para ID + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + // It is now a parathread (on-demand parachain). + assert!(Parachains::is_parathread(para_id)); + assert!(!Parachains::is_parachain(para_id)); + // Some other external process will elevate on-demand to lease holding parachain + assert_ok!(mock::Registrar::make_parachain(para_id)); + run_to_session(START_SESSION_INDEX + 4); + // It is now a lease holding parachain. + assert!(!Parachains::is_parathread(para_id)); + assert!(Parachains::is_parachain(para_id)); + // Turn it back into a parathread (on-demand parachain) + assert_ok!(mock::Registrar::make_parathread(para_id)); + run_to_session(START_SESSION_INDEX + 6); + assert!(Parachains::is_parathread(para_id)); + assert!(!Parachains::is_parachain(para_id)); + // Deregister it + assert_ok!(mock::Registrar::deregister(RuntimeOrigin::root(), para_id,)); + run_to_session(START_SESSION_INDEX + 8); + // It is nothing + assert!(!Parachains::is_parathread(para_id)); + assert!(!Parachains::is_parachain(para_id)); + }); +} + +#[test] +fn register_works() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_id = LOWEST_PUBLIC_ID; + assert!(!Parachains::is_parathread(para_id)); + + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_eq!(Balances::reserved_balance(&1), ::ParaDeposit::get()); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + assert!(Parachains::is_parathread(para_id)); + // Even though the registered validation code has a smaller size than the maximum the + // para manager's deposit is reserved as though they registered the maximum-sized code. + // Consequently, they can upgrade their code to the maximum size at any point without + // additional cost. + let validation_code_deposit = + max_code_size() as BalanceOf * ::DataDepositPerByte::get(); + let head_deposit = 32 * ::DataDepositPerByte::get(); + assert_eq!( + Balances::reserved_balance(&1), + ::ParaDeposit::get() + head_deposit + validation_code_deposit + ); + }); +} + +#[test] +fn schedule_code_upgrade_validates_code() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_id = LOWEST_PUBLIC_ID; + assert!(!Parachains::is_parathread(para_id)); + + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_eq!(Balances::reserved_balance(&1), ::ParaDeposit::get()); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + assert!(Parachains::is_parathread(para_id)); + + let new_code = test_validation_code(0); + assert_noop!( + mock::Registrar::schedule_code_upgrade( + RuntimeOrigin::signed(1), + para_id, + new_code.clone(), + ), + paras::Error::::InvalidCode + ); + + let new_code = test_validation_code(max_code_size() as usize + 1); + assert_noop!( + mock::Registrar::schedule_code_upgrade( + RuntimeOrigin::signed(1), + para_id, + new_code.clone(), + ), + paras::Error::::InvalidCode + ); + }); +} + +#[test] +fn register_handles_basic_errors() { + new_test_ext().execute_with(|| { + let para_id = LOWEST_PUBLIC_ID; + + assert_noop!( + mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(max_head_size() as usize), + test_validation_code(max_code_size() as usize), + ), + Error::::NotReserved + ); + + // Successfully register para + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + + assert_noop!( + mock::Registrar::register( + RuntimeOrigin::signed(2), + para_id, + test_genesis_head(max_head_size() as usize), + test_validation_code(max_code_size() as usize), + ), + Error::::NotOwner + ); + + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(max_head_size() as usize), + test_validation_code(max_code_size() as usize), + )); + // Can skip pre-check and deregister para which's still onboarding. + run_to_session(2); + + assert_ok!(mock::Registrar::deregister(RuntimeOrigin::root(), para_id)); + + // Can't do it again + assert_noop!( + mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(max_head_size() as usize), + test_validation_code(max_code_size() as usize), + ), + Error::::NotReserved + ); + + // Head Size Check + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(2))); + assert_noop!( + mock::Registrar::register( + RuntimeOrigin::signed(2), + para_id + 1, + test_genesis_head((max_head_size() + 1) as usize), + test_validation_code(max_code_size() as usize), + ), + Error::::HeadDataTooLarge + ); + + // Code Size Check + assert_noop!( + mock::Registrar::register( + RuntimeOrigin::signed(2), + para_id + 1, + test_genesis_head(max_head_size() as usize), + test_validation_code((max_code_size() + 1) as usize), + ), + Error::::CodeTooLarge + ); + + // Needs enough funds for deposit + assert_noop!( + mock::Registrar::reserve(RuntimeOrigin::signed(1337)), + BalancesError::::InsufficientBalance + ); + }); +} + +#[test] +fn deregister_works() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_id = LOWEST_PUBLIC_ID; + assert!(!Parachains::is_parathread(para_id)); + + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + assert!(Parachains::is_parathread(para_id)); + assert_ok!(mock::Registrar::deregister(RuntimeOrigin::root(), para_id,)); + run_to_session(START_SESSION_INDEX + 4); + assert!(paras::Pallet::::lifecycle(para_id).is_none()); + assert_eq!(Balances::reserved_balance(&1), 0); + }); +} + +#[test] +fn deregister_handles_basic_errors() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_id = LOWEST_PUBLIC_ID; + assert!(!Parachains::is_parathread(para_id)); + + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + assert!(Parachains::is_parathread(para_id)); + // Owner check + assert_noop!(mock::Registrar::deregister(RuntimeOrigin::signed(2), para_id,), BadOrigin); + assert_ok!(mock::Registrar::make_parachain(para_id)); + run_to_session(START_SESSION_INDEX + 4); + // Cant directly deregister parachain + assert_noop!( + mock::Registrar::deregister(RuntimeOrigin::root(), para_id,), + Error::::NotParathread + ); + }); +} + +#[test] +fn swap_works() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + // Successfully register first two parachains + let para_1 = LOWEST_PUBLIC_ID; + let para_2 = LOWEST_PUBLIC_ID + 1; + + let validation_code = test_validation_code(max_code_size() as usize); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_1, + test_genesis_head(max_head_size() as usize), + validation_code.clone(), + )); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(2))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(2), + para_2, + test_genesis_head(max_head_size() as usize), + validation_code.clone(), + )); + conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); + + run_to_session(START_SESSION_INDEX + 2); + + // Upgrade para 1 into a parachain + assert_ok!(mock::Registrar::make_parachain(para_1)); + + // Set some mock swap data. + let mut swap_data = SwapData::get(); + swap_data.insert(para_1, 69); + swap_data.insert(para_2, 1337); + SwapData::set(swap_data); + + run_to_session(START_SESSION_INDEX + 4); + + // Roles are as we expect + assert!(Parachains::is_parachain(para_1)); + assert!(!Parachains::is_parathread(para_1)); + assert!(!Parachains::is_parachain(para_2)); + assert!(Parachains::is_parathread(para_2)); + + // Both paras initiate a swap + // Swap between parachain and parathread + assert_ok!(mock::Registrar::swap(para_origin(para_1), para_1, para_2,)); + assert_ok!(mock::Registrar::swap(para_origin(para_2), para_2, para_1,)); + System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { + para_id: para_2, + other_id: para_1, + })); + + run_to_session(START_SESSION_INDEX + 6); + + // Roles are swapped + assert!(!Parachains::is_parachain(para_1)); + assert!(Parachains::is_parathread(para_1)); + assert!(Parachains::is_parachain(para_2)); + assert!(!Parachains::is_parathread(para_2)); + + // Data is swapped + assert_eq!(SwapData::get().get(¶_1).unwrap(), &1337); + assert_eq!(SwapData::get().get(¶_2).unwrap(), &69); + + // Both paras initiate a swap + // Swap between parathread and parachain + assert_ok!(mock::Registrar::swap(para_origin(para_1), para_1, para_2,)); + assert_ok!(mock::Registrar::swap(para_origin(para_2), para_2, para_1,)); + System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { + para_id: para_2, + other_id: para_1, + })); + + // Data is swapped + assert_eq!(SwapData::get().get(¶_1).unwrap(), &69); + assert_eq!(SwapData::get().get(¶_2).unwrap(), &1337); + + // Parachain to parachain swap + let para_3 = LOWEST_PUBLIC_ID + 2; + let validation_code = test_validation_code(max_code_size() as usize); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(3))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(3), + para_3, + test_genesis_head(max_head_size() as usize), + validation_code.clone(), + )); + conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX + 6); + + run_to_session(START_SESSION_INDEX + 8); + + // Upgrade para 3 into a parachain + assert_ok!(mock::Registrar::make_parachain(para_3)); + + // Set some mock swap data. + let mut swap_data = SwapData::get(); + swap_data.insert(para_3, 777); + SwapData::set(swap_data); + + run_to_session(START_SESSION_INDEX + 10); + + // Both are parachains + assert!(Parachains::is_parachain(para_3)); + assert!(!Parachains::is_parathread(para_3)); + assert!(Parachains::is_parachain(para_1)); + assert!(!Parachains::is_parathread(para_1)); + + // Both paras initiate a swap + // Swap between parachain and parachain + assert_ok!(mock::Registrar::swap(para_origin(para_1), para_1, para_3,)); + assert_ok!(mock::Registrar::swap(para_origin(para_3), para_3, para_1,)); + System::assert_last_event(RuntimeEvent::Registrar(paras_registrar::Event::Swapped { + para_id: para_3, + other_id: para_1, + })); + + // Data is swapped + assert_eq!(SwapData::get().get(¶_3).unwrap(), &69); + assert_eq!(SwapData::get().get(¶_1).unwrap(), &777); + }); +} + +#[test] +fn para_lock_works() { + new_test_ext().execute_with(|| { + run_to_block(1); + + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + let para_id = LOWEST_PUBLIC_ID; + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_id, + vec![1; 3].into(), + test_validation_code(32) + )); + + assert_noop!(mock::Registrar::add_lock(RuntimeOrigin::signed(2), para_id), BadOrigin); + + // Once they produces new block, we lock them in. + mock::Registrar::on_new_head(para_id, &Default::default()); + + // Owner cannot pass origin check when checking lock + assert_noop!( + mock::Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id), + BadOrigin + ); + // Owner cannot remove lock. + assert_noop!(mock::Registrar::remove_lock(RuntimeOrigin::signed(1), para_id), BadOrigin); + // Para can. + assert_ok!(mock::Registrar::remove_lock(para_origin(para_id), para_id)); + // Owner can pass origin check again + assert_ok!(mock::Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id)); + + // Won't lock again after it is unlocked + mock::Registrar::on_new_head(para_id, &Default::default()); + + assert_ok!(mock::Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id)); + }); +} + +#[test] +fn swap_handles_bad_states() { + new_test_ext().execute_with(|| { + const START_SESSION_INDEX: SessionIndex = 1; + run_to_session(START_SESSION_INDEX); + + let para_1 = LOWEST_PUBLIC_ID; + let para_2 = LOWEST_PUBLIC_ID + 1; + + // paras are not yet registered + assert!(!Parachains::is_parathread(para_1)); + assert!(!Parachains::is_parathread(para_2)); + + // Cannot even start a swap + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2), + Error::::NotRegistered + ); + + // We register Paras 1 and 2 + let validation_code = test_validation_code(32); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(1))); + assert_ok!(mock::Registrar::reserve(RuntimeOrigin::signed(2))); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(1), + para_1, + test_genesis_head(32), + validation_code.clone(), + )); + assert_ok!(mock::Registrar::register( + RuntimeOrigin::signed(2), + para_2, + test_genesis_head(32), + validation_code.clone(), + )); + conclude_pvf_checking::(&validation_code, VALIDATORS, START_SESSION_INDEX); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::::CannotSwap + ); + + run_to_session(START_SESSION_INDEX + 2); + + // They are now parathreads (on-demand parachains). + assert!(Parachains::is_parathread(para_1)); + assert!(Parachains::is_parathread(para_2)); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::::CannotSwap + ); + + // Some other external process will elevate one on-demand + // parachain to a lease holding parachain + assert_ok!(mock::Registrar::make_parachain(para_1)); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::::CannotSwap + ); + + run_to_session(START_SESSION_INDEX + 3); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::::CannotSwap + ); + + run_to_session(START_SESSION_INDEX + 4); + + // It is now a lease holding parachain. + assert!(Parachains::is_parachain(para_1)); + assert!(Parachains::is_parathread(para_2)); + + // Swap works here. + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1)); + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::Registrar(paras_registrar::Event::Swapped { .. }) + ))); + + run_to_session(START_SESSION_INDEX + 5); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::::CannotSwap + ); + + run_to_session(START_SESSION_INDEX + 6); + + // Swap worked! + assert!(Parachains::is_parachain(para_2)); + assert!(Parachains::is_parathread(para_1)); + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::Registrar(paras_registrar::Event::Swapped { .. }) + ))); + + // Something starts to downgrade a para + assert_ok!(mock::Registrar::make_parathread(para_2)); + + run_to_session(START_SESSION_INDEX + 7); + + // Cannot swap + assert_ok!(mock::Registrar::swap(RuntimeOrigin::root(), para_1, para_2)); + assert_noop!( + mock::Registrar::swap(RuntimeOrigin::root(), para_2, para_1), + Error::::CannotSwap + ); + + run_to_session(START_SESSION_INDEX + 8); + + assert!(Parachains::is_parathread(para_1)); + assert!(Parachains::is_parathread(para_2)); + }); +} From 2cbb4377d36af0aec9ae29dcabc5f5b1e88ed1db Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Thu, 19 Dec 2024 10:41:42 +0100 Subject: [PATCH 195/340] [pallet-revive] implement the gas price API (#6954) This PR implements the EVM gas price syscall API method. Currently this is a compile time constant in revive, but in the EVM it is an opcode. Thus we should provide an opcode for this in the pallet. --------- Signed-off-by: Cyrill Leutwiler Signed-off-by: xermicus Co-authored-by: command-bot <> --- prdoc/pr_6954.prdoc | 13 + .../revive/fixtures/contracts/gas_price.rs | 34 + .../frame/revive/src/benchmarking/mod.rs | 12 + substrate/frame/revive/src/tests.rs | 20 +- substrate/frame/revive/src/wasm/runtime.rs | 12 + substrate/frame/revive/src/weights.rs | 941 +++++++++--------- substrate/frame/revive/uapi/src/host.rs | 4 + .../frame/revive/uapi/src/host/riscv64.rs | 5 + 8 files changed, 571 insertions(+), 470 deletions(-) create mode 100644 prdoc/pr_6954.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/gas_price.rs diff --git a/prdoc/pr_6954.prdoc b/prdoc/pr_6954.prdoc new file mode 100644 index 000000000000..8e8faf5fffd2 --- /dev/null +++ b/prdoc/pr_6954.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] implement the gas price API' +doc: +- audience: Runtime Dev + description: This PR implements the EVM gas price syscall API method. Currently + this is a compile time constant in revive, but in the EVM it is an opcode. Thus + we should provide an opcode for this in the pallet. +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/gas_price.rs b/substrate/frame/revive/fixtures/contracts/gas_price.rs new file mode 100644 index 000000000000..c1c8109fafbe --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/gas_price.rs @@ -0,0 +1,34 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Returns the gas price back to the caller. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + api::return_value(ReturnFlags::empty(), &api::gas_price().to_le_bytes()); +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 4ddd6dfbc372..093f7ef70674 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -23,6 +23,7 @@ mod call_builder; mod code; use self::{call_builder::CallSetup, code::WasmModule}; use crate::{ + evm::runtime::GAS_PRICE, exec::{Key, MomentOf}, limits, storage::WriteOutcome, @@ -820,6 +821,17 @@ mod benchmarks { assert_eq!(result.unwrap(), T::BlockWeights::get().max_block.ref_time()); } + #[benchmark(pov_mode = Measured)] + fn seal_gas_price() { + build_runtime!(runtime, memory: []); + let result; + #[block] + { + result = runtime.bench_gas_price(memory.as_mut_slice()); + } + assert_eq!(result.unwrap(), u64::from(GAS_PRICE)); + } + #[benchmark(pov_mode = Measured)] fn seal_block_number() { build_runtime!(runtime, memory: [[0u8;32], ]); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index b863b52af2af..a09930eaac64 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -29,7 +29,7 @@ use crate::{ ChainExtension, Environment, Ext, RegisteredChainExtension, Result as ExtensionResult, RetVal, ReturnFlags, }, - evm::GenericTransaction, + evm::{runtime::GAS_PRICE, GenericTransaction}, exec::Key, limits, primitives::CodeUploadReturnValue, @@ -4364,6 +4364,24 @@ fn create1_with_value_works() { }); } +#[test] +fn gas_price_api_works() { + let (code, _) = compile_module("gas_price").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the gas price API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(u64::from_le_bytes(received.data[..].try_into().unwrap()), u64::from(GAS_PRICE)); + }); +} + #[test] fn call_data_size_api_works() { let (code, _) = compile_module("call_data_size").unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index cdf6b3b08fdc..3268e0c59c2b 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -19,6 +19,7 @@ use crate::{ address::AddressMapper, + evm::runtime::GAS_PRICE, exec::{ExecError, ExecResult, Ext, Key}, gas::{ChargedAmount, Token}, limits, @@ -324,6 +325,8 @@ pub enum RuntimeCosts { BlockNumber, /// Weight of calling `seal_block_hash`. BlockHash, + /// Weight of calling `seal_gas_price`. + GasPrice, /// Weight of calling `seal_now`. Now, /// Weight of calling `seal_gas_limit`. @@ -477,6 +480,7 @@ impl Token for RuntimeCosts { MinimumBalance => T::WeightInfo::seal_minimum_balance(), BlockNumber => T::WeightInfo::seal_block_number(), BlockHash => T::WeightInfo::seal_block_hash(), + GasPrice => T::WeightInfo::seal_gas_price(), Now => T::WeightInfo::seal_now(), GasLimit => T::WeightInfo::seal_gas_limit(), WeightToFee => T::WeightInfo::seal_weight_to_fee(), @@ -1563,6 +1567,14 @@ pub mod env { )?) } + /// Returns the simulated ethereum `GASPRICE` value. + /// See [`pallet_revive_uapi::HostFn::gas_price`]. + #[stable] + fn gas_price(&mut self, memory: &mut M) -> Result { + self.charge_gas(RuntimeCosts::GasPrice)?; + Ok(GAS_PRICE.into()) + } + /// Load the latest block timestamp into the supplied buffer /// See [`pallet_revive_uapi::HostFn::now`]. #[stable] diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 3f7ede275928..78eb6740c10a 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -20,7 +20,7 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 //! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `c3bb6290af79`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `8a4618716d33`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -84,6 +84,7 @@ pub trait WeightInfo { fn seal_return_data_size() -> Weight; fn seal_call_data_size() -> Weight; fn seal_gas_limit() -> Weight; + fn seal_gas_price() -> Weight; fn seal_block_number() -> Weight; fn seal_block_hash() -> Weight; fn seal_now() -> Weight; @@ -139,8 +140,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_749_000 picoseconds. - Weight::from_parts(2_844_000, 1594) + // Minimum execution time: 2_700_000 picoseconds. + Weight::from_parts(2_858_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -150,10 +151,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 15_364_000 picoseconds. - Weight::from_parts(3_092_479, 415) - // Standard Error: 1_592 - .saturating_add(Weight::from_parts(1_189_460, 0).saturating_mul(k.into())) + // Minimum execution time: 15_542_000 picoseconds. + Weight::from_parts(3_353_401, 415) + // Standard Error: 1_167 + .saturating_add(Weight::from_parts(1_194_349, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -175,10 +176,10 @@ impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1465` - // Estimated: `7405` - // Minimum execution time: 92_898_000 picoseconds. - Weight::from_parts(97_241_513, 7405) + // Measured: `1502` + // Estimated: `7442` + // Minimum execution time: 93_827_000 picoseconds. + Weight::from_parts(98_408_848, 7442) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -198,16 +199,14 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(c: u32, i: u32, ) -> Weight { + fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `416` - // Estimated: `6348` - // Minimum execution time: 196_248_000 picoseconds. - Weight::from_parts(162_338_484, 6348) - // Standard Error: 16 - .saturating_add(Weight::from_parts(71, 0).saturating_mul(c.into())) - // Standard Error: 16 - .saturating_add(Weight::from_parts(4_579, 0).saturating_mul(i.into())) + // Measured: `403` + // Estimated: `6343` + // Minimum execution time: 197_900_000 picoseconds. + Weight::from_parts(189_732_698, 6343) + // Standard Error: 9 + .saturating_add(Weight::from_parts(4_465, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -230,10 +229,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1309` // Estimated: `4760` - // Minimum execution time: 162_002_000 picoseconds. - Weight::from_parts(146_333_459, 4760) - // Standard Error: 15 - .saturating_add(Weight::from_parts(4_482, 0).saturating_mul(i.into())) + // Minimum execution time: 162_798_000 picoseconds. + Weight::from_parts(148_006_239, 4760) + // Standard Error: 14 + .saturating_add(Weight::from_parts(4_424, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -251,10 +250,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1465` - // Estimated: `7405` - // Minimum execution time: 144_493_000 picoseconds. - Weight::from_parts(150_783_000, 7405) + // Measured: `1502` + // Estimated: `7442` + // Minimum execution time: 144_505_000 picoseconds. + Weight::from_parts(149_799_000, 7442) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -265,14 +264,12 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. - fn upload_code(c: u32, ) -> Weight { + fn upload_code(_c: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 51_261_000 picoseconds. - Weight::from_parts(53_656_457, 3574) - // Standard Error: 0 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(c.into())) + // Minimum execution time: 52_117_000 picoseconds. + Weight::from_parts(55_103_397, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -286,8 +283,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 44_921_000 picoseconds. - Weight::from_parts(46_970_000, 3750) + // Minimum execution time: 46_384_000 picoseconds. + Weight::from_parts(47_327_000, 3750) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -299,8 +296,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 27_070_000 picoseconds. - Weight::from_parts(27_897_000, 6469) + // Minimum execution time: 27_210_000 picoseconds. + Weight::from_parts(28_226_000, 6469) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -312,8 +309,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 40_939_000 picoseconds. - Weight::from_parts(42_250_000, 3574) + // Minimum execution time: 41_028_000 picoseconds. + Weight::from_parts(42_438_000, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -325,8 +322,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 32_804_000 picoseconds. - Weight::from_parts(33_965_000, 3521) + // Minimum execution time: 33_271_000 picoseconds. + Weight::from_parts(35_037_000, 3521) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -338,8 +335,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_616_000 picoseconds. - Weight::from_parts(14_164_000, 3610) + // Minimum execution time: 13_455_000 picoseconds. + Weight::from_parts(14_144_000, 3610) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -347,24 +344,24 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_403_000 picoseconds. - Weight::from_parts(8_174_105, 0) - // Standard Error: 181 - .saturating_add(Weight::from_parts(162_824, 0).saturating_mul(r.into())) + // Minimum execution time: 7_514_000 picoseconds. + Weight::from_parts(8_642_516, 0) + // Standard Error: 190 + .saturating_add(Weight::from_parts(168_973, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 278_000 picoseconds. - Weight::from_parts(312_000, 0) + // Minimum execution time: 341_000 picoseconds. + Weight::from_parts(373_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 232_000 picoseconds. - Weight::from_parts(252_000, 0) + // Minimum execution time: 280_000 picoseconds. + Weight::from_parts(329_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -372,8 +369,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_239_000 picoseconds. - Weight::from_parts(10_730_000, 3771) + // Minimum execution time: 10_296_000 picoseconds. + Weight::from_parts(10_757_000, 3771) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -382,16 +379,16 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_016_000 picoseconds. - Weight::from_parts(11_331_000, 3868) + // Minimum execution time: 11_453_000 picoseconds. + Weight::from_parts(12_071_000, 3868) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 261_000 picoseconds. - Weight::from_parts(298_000, 0) + // Minimum execution time: 266_000 picoseconds. + Weight::from_parts(360_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -401,51 +398,51 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_413_000 picoseconds. - Weight::from_parts(15_066_000, 3938) + // Minimum execution time: 14_463_000 picoseconds. + Weight::from_parts(15_085_000, 3938) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 303_000 picoseconds. - Weight::from_parts(340_000, 0) + // Minimum execution time: 329_000 picoseconds. + Weight::from_parts(394_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 246_000 picoseconds. - Weight::from_parts(266_000, 0) + // Minimum execution time: 265_000 picoseconds. + Weight::from_parts(327_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 260_000 picoseconds. - Weight::from_parts(287_000, 0) + // Minimum execution time: 306_000 picoseconds. + Weight::from_parts(359_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 616_000 picoseconds. - Weight::from_parts(726_000, 0) + // Minimum execution time: 653_000 picoseconds. + Weight::from_parts(727_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 253_000 picoseconds. - Weight::from_parts(282_000, 0) + // Minimum execution time: 257_000 picoseconds. + Weight::from_parts(328_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `140` + // Measured: `174` // Estimated: `0` - // Minimum execution time: 5_380_000 picoseconds. - Weight::from_parts(5_740_000, 0) + // Minimum execution time: 5_572_000 picoseconds. + Weight::from_parts(5_858_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -455,8 +452,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 8_826_000 picoseconds. - Weight::from_parts(9_166_000, 3729) + // Minimum execution time: 9_006_000 picoseconds. + Weight::from_parts(9_371_000, 3729) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -466,10 +463,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_971_000 picoseconds. - Weight::from_parts(6_578_727, 3703) + // Minimum execution time: 5_853_000 picoseconds. + Weight::from_parts(6_592_851, 3703) // Standard Error: 4 - .saturating_add(Weight::from_parts(732, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(665, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -480,53 +477,60 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_866_000 picoseconds. - Weight::from_parts(2_158_746, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(637, 0).saturating_mul(n.into())) + // Minimum execution time: 2_040_000 picoseconds. + Weight::from_parts(2_288_695, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(570, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 223_000 picoseconds. - Weight::from_parts(279_000, 0) + // Minimum execution time: 263_000 picoseconds. + Weight::from_parts(305_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 220_000 picoseconds. - Weight::from_parts(245_000, 0) + // Minimum execution time: 273_000 picoseconds. + Weight::from_parts(303_000, 0) } fn seal_return_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 208_000 picoseconds. - Weight::from_parts(245_000, 0) + // Minimum execution time: 260_000 picoseconds. + Weight::from_parts(304_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 211_000 picoseconds. - Weight::from_parts(252_000, 0) + // Minimum execution time: 277_000 picoseconds. + Weight::from_parts(309_000, 0) } fn seal_gas_limit() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 282_000 picoseconds. - Weight::from_parts(310_000, 0) + // Minimum execution time: 298_000 picoseconds. + Weight::from_parts(356_000, 0) + } + fn seal_gas_price() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 261_000 picoseconds. + Weight::from_parts(293_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 216_000 picoseconds. - Weight::from_parts(242_000, 0) + // Minimum execution time: 257_000 picoseconds. + Weight::from_parts(325_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -534,48 +538,48 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_410_000 picoseconds. - Weight::from_parts(3_595_000, 3495) + // Minimum execution time: 3_458_000 picoseconds. + Weight::from_parts(3_785_000, 3495) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 214_000 picoseconds. - Weight::from_parts(234_000, 0) + // Minimum execution time: 273_000 picoseconds. + Weight::from_parts(328_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_344_000 picoseconds. - Weight::from_parts(1_503_000, 0) + // Minimum execution time: 1_383_000 picoseconds. + Weight::from_parts(1_517_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 372_000 picoseconds. - Weight::from_parts(613_654, 0) + // Minimum execution time: 373_000 picoseconds. + Weight::from_parts(630_750, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(295, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(235, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 213_000 picoseconds. - Weight::from_parts(243_000, 0) + // Minimum execution time: 265_000 picoseconds. + Weight::from_parts(297_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 230_000 picoseconds. - Weight::from_parts(252_625, 0) + // Minimum execution time: 250_000 picoseconds. + Weight::from_parts(219_823, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(150, 0).saturating_mul(n.into())) } @@ -584,10 +588,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 231_000 picoseconds. - Weight::from_parts(378_784, 0) + // Minimum execution time: 293_000 picoseconds. + Weight::from_parts(492_148, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(296, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(236, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -602,12 +606,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[0, 32]`. fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `324 + n * (88 ±0)` - // Estimated: `3790 + n * (2563 ±0)` - // Minimum execution time: 22_246_000 picoseconds. - Weight::from_parts(22_824_813, 3790) - // Standard Error: 11_423 - .saturating_add(Weight::from_parts(4_328_279, 0).saturating_mul(n.into())) + // Measured: `322 + n * (88 ±0)` + // Estimated: `3788 + n * (2563 ±0)` + // Minimum execution time: 22_378_000 picoseconds. + Weight::from_parts(21_359_808, 3788) + // Standard Error: 12_515 + .saturating_add(Weight::from_parts(4_433_373, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -620,22 +624,22 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_199_000 picoseconds. - Weight::from_parts(4_174_861, 0) - // Standard Error: 2_974 - .saturating_add(Weight::from_parts(211_154, 0).saturating_mul(t.into())) - // Standard Error: 30 - .saturating_add(Weight::from_parts(1_037, 0).saturating_mul(n.into())) + // Minimum execution time: 4_250_000 picoseconds. + Weight::from_parts(4_275_643, 0) + // Standard Error: 2_911 + .saturating_add(Weight::from_parts(197_045, 0).saturating_mul(t.into())) + // Standard Error: 29 + .saturating_add(Weight::from_parts(978, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 311_000 picoseconds. - Weight::from_parts(326_000, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(815, 0).saturating_mul(i.into())) + // Minimum execution time: 362_000 picoseconds. + Weight::from_parts(68_341, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(762, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -643,8 +647,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 7_584_000 picoseconds. - Weight::from_parts(8_006_000, 680) + // Minimum execution time: 7_649_000 picoseconds. + Weight::from_parts(7_908_000, 680) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -653,8 +657,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 42_716_000 picoseconds. - Weight::from_parts(43_583_000, 10690) + // Minimum execution time: 42_646_000 picoseconds. + Weight::from_parts(44_107_000, 10690) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -663,8 +667,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 8_727_000 picoseconds. - Weight::from_parts(9_056_000, 680) + // Minimum execution time: 8_978_000 picoseconds. + Weight::from_parts(9_343_000, 680) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -674,8 +678,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 44_882_000 picoseconds. - Weight::from_parts(45_933_000, 10690) + // Minimum execution time: 44_678_000 picoseconds. + Weight::from_parts(46_166_000, 10690) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -687,12 +691,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_150_000 picoseconds. - Weight::from_parts(9_621_151, 247) - // Standard Error: 43 - .saturating_add(Weight::from_parts(554, 0).saturating_mul(n.into())) - // Standard Error: 43 - .saturating_add(Weight::from_parts(645, 0).saturating_mul(o.into())) + // Minimum execution time: 9_216_000 picoseconds. + Weight::from_parts(9_774_592, 247) + // Standard Error: 51 + .saturating_add(Weight::from_parts(532, 0).saturating_mul(n.into())) + // Standard Error: 51 + .saturating_add(Weight::from_parts(504, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -704,10 +708,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_670_000 picoseconds. - Weight::from_parts(9_528_913, 247) - // Standard Error: 56 - .saturating_add(Weight::from_parts(805, 0).saturating_mul(n.into())) + // Minimum execution time: 8_800_000 picoseconds. + Weight::from_parts(9_758_732, 247) + // Standard Error: 70 + .saturating_add(Weight::from_parts(387, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -719,10 +723,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_214_000 picoseconds. - Weight::from_parts(9_195_285, 247) - // Standard Error: 70 - .saturating_add(Weight::from_parts(1_452, 0).saturating_mul(n.into())) + // Minimum execution time: 8_502_000 picoseconds. + Weight::from_parts(9_415_872, 247) + // Standard Error: 72 + .saturating_add(Weight::from_parts(1_304, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -733,10 +737,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 7_947_000 picoseconds. - Weight::from_parts(8_633_252, 247) - // Standard Error: 53 - .saturating_add(Weight::from_parts(832, 0).saturating_mul(n.into())) + // Minimum execution time: 8_003_000 picoseconds. + Weight::from_parts(8_757_027, 247) + // Standard Error: 64 + .saturating_add(Weight::from_parts(508, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -747,10 +751,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_414_000 picoseconds. - Weight::from_parts(10_289_881, 247) - // Standard Error: 66 - .saturating_add(Weight::from_parts(1_325, 0).saturating_mul(n.into())) + // Minimum execution time: 9_369_000 picoseconds. + Weight::from_parts(10_394_508, 247) + // Standard Error: 70 + .saturating_add(Weight::from_parts(1_404, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -759,36 +763,36 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_424_000 picoseconds. - Weight::from_parts(1_511_000, 0) + // Minimum execution time: 1_457_000 picoseconds. + Weight::from_parts(1_595_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_797_000 picoseconds. - Weight::from_parts(1_961_000, 0) + // Minimum execution time: 1_894_000 picoseconds. + Weight::from_parts(2_062_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_498_000 picoseconds. - Weight::from_parts(1_562_000, 0) + // Minimum execution time: 1_535_000 picoseconds. + Weight::from_parts(1_586_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_610_000 picoseconds. - Weight::from_parts(1_703_000, 0) + // Minimum execution time: 1_706_000 picoseconds. + Weight::from_parts(1_850_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_100_000 picoseconds. - Weight::from_parts(1_197_000, 0) + // Minimum execution time: 1_198_000 picoseconds. + Weight::from_parts(1_325_000, 0) } /// The range of component `n` is `[0, 448]`. /// The range of component `o` is `[0, 448]`. @@ -796,50 +800,50 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_232_000 picoseconds. - Weight::from_parts(2_371_207, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(385, 0).saturating_mul(n.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(471, 0).saturating_mul(o.into())) + // Minimum execution time: 2_324_000 picoseconds. + Weight::from_parts(2_397_504, 0) + // Standard Error: 16 + .saturating_add(Weight::from_parts(379, 0).saturating_mul(n.into())) + // Standard Error: 16 + .saturating_add(Weight::from_parts(524, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 448]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_015_000 picoseconds. - Weight::from_parts(2_374_096, 0) - // Standard Error: 18 - .saturating_add(Weight::from_parts(462, 0).saturating_mul(n.into())) + // Minimum execution time: 2_072_000 picoseconds. + Weight::from_parts(2_408_702, 0) + // Standard Error: 23 + .saturating_add(Weight::from_parts(432, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_788_000 picoseconds. - Weight::from_parts(1_983_300, 0) - // Standard Error: 17 - .saturating_add(Weight::from_parts(404, 0).saturating_mul(n.into())) + // Minimum execution time: 1_930_000 picoseconds. + Weight::from_parts(2_120_317, 0) + // Standard Error: 18 + .saturating_add(Weight::from_parts(391, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_678_000 picoseconds. - Weight::from_parts(1_845_442, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(228, 0).saturating_mul(n.into())) + // Minimum execution time: 1_755_000 picoseconds. + Weight::from_parts(1_968_623, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(191, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_489_000 picoseconds. - Weight::from_parts(2_786_607, 0) + // Minimum execution time: 2_567_000 picoseconds. + Weight::from_parts(2_841_579, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -853,20 +857,18 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `t` is `[0, 1]`. /// The range of component `i` is `[0, 262144]`. - fn seal_call(t: u32, i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1292 + t * (280 ±0)` - // Estimated: `4757 + t * (2518 ±0)` - // Minimum execution time: 41_653_000 picoseconds. - Weight::from_parts(43_075_070, 4757) - // Standard Error: 42_656 - .saturating_add(Weight::from_parts(1_667_094, 0).saturating_mul(t.into())) - // Standard Error: 0 - .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) + fn seal_call(t: u32, _i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1292 + t * (314 ±0)` + // Estimated: `4757 + t * (2535 ±0)` + // Minimum execution time: 40_925_000 picoseconds. + Weight::from_parts(42_866_040, 4757) + // Standard Error: 99_028 + .saturating_add(Weight::from_parts(2_467_746, 0).saturating_mul(t.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 2518).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 2535).saturating_mul(t.into())) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -878,8 +880,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 36_908_000 picoseconds. - Weight::from_parts(37_754_000, 4702) + // Minimum execution time: 36_048_000 picoseconds. + Weight::from_parts(37_921_000, 4702) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -893,12 +895,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1310` - // Estimated: `4769` - // Minimum execution time: 120_576_000 picoseconds. - Weight::from_parts(112_786_790, 4769) + // Measured: `1331` + // Estimated: `4796` + // Minimum execution time: 121_507_000 picoseconds. + Weight::from_parts(115_250_696, 4796) // Standard Error: 10 - .saturating_add(Weight::from_parts(4_192, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_136, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -907,64 +909,64 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 621_000 picoseconds. - Weight::from_parts(3_506_910, 0) + // Minimum execution time: 649_000 picoseconds. + Weight::from_parts(3_208_110, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_489, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_439, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_054_000 picoseconds. - Weight::from_parts(5_395_465, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(3_688, 0).saturating_mul(n.into())) + // Minimum execution time: 1_078_000 picoseconds. + Weight::from_parts(3_361_333, 0) + // Standard Error: 4 + .saturating_add(Weight::from_parts(3_648, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 626_000 picoseconds. - Weight::from_parts(3_549_376, 0) + // Minimum execution time: 673_000 picoseconds. + Weight::from_parts(3_115_184, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_596, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_557, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 598_000 picoseconds. - Weight::from_parts(2_618_039, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(1_616, 0).saturating_mul(n.into())) + // Minimum execution time: 685_000 picoseconds. + Weight::from_parts(3_752_567, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_549, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_715_000 picoseconds. - Weight::from_parts(25_484_960, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(5_315, 0).saturating_mul(n.into())) + // Minimum execution time: 42_901_000 picoseconds. + Weight::from_parts(30_989_396, 0) + // Standard Error: 11 + .saturating_add(Weight::from_parts(5_414, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 47_123_000 picoseconds. - Weight::from_parts(48_956_000, 0) + // Minimum execution time: 47_249_000 picoseconds. + Weight::from_parts(48_530_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_650_000 picoseconds. - Weight::from_parts(12_768_000, 0) + // Minimum execution time: 12_873_000 picoseconds. + Weight::from_parts(13_127_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -972,8 +974,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 18_061_000 picoseconds. - Weight::from_parts(18_851_000, 3765) + // Minimum execution time: 18_436_000 picoseconds. + Weight::from_parts(19_107_000, 3765) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -983,8 +985,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 13_779_000 picoseconds. - Weight::from_parts(14_320_000, 3803) + // Minimum execution time: 13_894_000 picoseconds. + Weight::from_parts(14_355_000, 3803) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -994,8 +996,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 12_327_000 picoseconds. - Weight::from_parts(13_156_000, 3561) + // Minimum execution time: 12_516_000 picoseconds. + Weight::from_parts(13_223_000, 3561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -1004,10 +1006,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_048_000 picoseconds. - Weight::from_parts(10_590_154, 0) - // Standard Error: 82 - .saturating_add(Weight::from_parts(72_658, 0).saturating_mul(r.into())) + // Minimum execution time: 9_177_000 picoseconds. + Weight::from_parts(11_420_562, 0) + // Standard Error: 99 + .saturating_add(Weight::from_parts(72_860, 0).saturating_mul(r.into())) } } @@ -1019,8 +1021,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_749_000 picoseconds. - Weight::from_parts(2_844_000, 1594) + // Minimum execution time: 2_700_000 picoseconds. + Weight::from_parts(2_858_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1030,10 +1032,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 15_364_000 picoseconds. - Weight::from_parts(3_092_479, 415) - // Standard Error: 1_592 - .saturating_add(Weight::from_parts(1_189_460, 0).saturating_mul(k.into())) + // Minimum execution time: 15_542_000 picoseconds. + Weight::from_parts(3_353_401, 415) + // Standard Error: 1_167 + .saturating_add(Weight::from_parts(1_194_349, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1055,10 +1057,10 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1465` - // Estimated: `7405` - // Minimum execution time: 92_898_000 picoseconds. - Weight::from_parts(97_241_513, 7405) + // Measured: `1502` + // Estimated: `7442` + // Minimum execution time: 93_827_000 picoseconds. + Weight::from_parts(98_408_848, 7442) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1078,16 +1080,14 @@ impl WeightInfo for () { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(c: u32, i: u32, ) -> Weight { + fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `416` - // Estimated: `6348` - // Minimum execution time: 196_248_000 picoseconds. - Weight::from_parts(162_338_484, 6348) - // Standard Error: 16 - .saturating_add(Weight::from_parts(71, 0).saturating_mul(c.into())) - // Standard Error: 16 - .saturating_add(Weight::from_parts(4_579, 0).saturating_mul(i.into())) + // Measured: `403` + // Estimated: `6343` + // Minimum execution time: 197_900_000 picoseconds. + Weight::from_parts(189_732_698, 6343) + // Standard Error: 9 + .saturating_add(Weight::from_parts(4_465, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1110,10 +1110,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1309` // Estimated: `4760` - // Minimum execution time: 162_002_000 picoseconds. - Weight::from_parts(146_333_459, 4760) - // Standard Error: 15 - .saturating_add(Weight::from_parts(4_482, 0).saturating_mul(i.into())) + // Minimum execution time: 162_798_000 picoseconds. + Weight::from_parts(148_006_239, 4760) + // Standard Error: 14 + .saturating_add(Weight::from_parts(4_424, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1131,10 +1131,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1465` - // Estimated: `7405` - // Minimum execution time: 144_493_000 picoseconds. - Weight::from_parts(150_783_000, 7405) + // Measured: `1502` + // Estimated: `7442` + // Minimum execution time: 144_505_000 picoseconds. + Weight::from_parts(149_799_000, 7442) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1145,14 +1145,12 @@ impl WeightInfo for () { /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. - fn upload_code(c: u32, ) -> Weight { + fn upload_code(_c: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 51_261_000 picoseconds. - Weight::from_parts(53_656_457, 3574) - // Standard Error: 0 - .saturating_add(Weight::from_parts(2, 0).saturating_mul(c.into())) + // Minimum execution time: 52_117_000 picoseconds. + Weight::from_parts(55_103_397, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1166,8 +1164,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 44_921_000 picoseconds. - Weight::from_parts(46_970_000, 3750) + // Minimum execution time: 46_384_000 picoseconds. + Weight::from_parts(47_327_000, 3750) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1179,8 +1177,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 27_070_000 picoseconds. - Weight::from_parts(27_897_000, 6469) + // Minimum execution time: 27_210_000 picoseconds. + Weight::from_parts(28_226_000, 6469) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1192,8 +1190,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 40_939_000 picoseconds. - Weight::from_parts(42_250_000, 3574) + // Minimum execution time: 41_028_000 picoseconds. + Weight::from_parts(42_438_000, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1205,8 +1203,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 32_804_000 picoseconds. - Weight::from_parts(33_965_000, 3521) + // Minimum execution time: 33_271_000 picoseconds. + Weight::from_parts(35_037_000, 3521) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1218,8 +1216,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_616_000 picoseconds. - Weight::from_parts(14_164_000, 3610) + // Minimum execution time: 13_455_000 picoseconds. + Weight::from_parts(14_144_000, 3610) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1227,24 +1225,24 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_403_000 picoseconds. - Weight::from_parts(8_174_105, 0) - // Standard Error: 181 - .saturating_add(Weight::from_parts(162_824, 0).saturating_mul(r.into())) + // Minimum execution time: 7_514_000 picoseconds. + Weight::from_parts(8_642_516, 0) + // Standard Error: 190 + .saturating_add(Weight::from_parts(168_973, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 278_000 picoseconds. - Weight::from_parts(312_000, 0) + // Minimum execution time: 341_000 picoseconds. + Weight::from_parts(373_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 232_000 picoseconds. - Weight::from_parts(252_000, 0) + // Minimum execution time: 280_000 picoseconds. + Weight::from_parts(329_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1252,8 +1250,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_239_000 picoseconds. - Weight::from_parts(10_730_000, 3771) + // Minimum execution time: 10_296_000 picoseconds. + Weight::from_parts(10_757_000, 3771) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -1262,16 +1260,16 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_016_000 picoseconds. - Weight::from_parts(11_331_000, 3868) + // Minimum execution time: 11_453_000 picoseconds. + Weight::from_parts(12_071_000, 3868) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 261_000 picoseconds. - Weight::from_parts(298_000, 0) + // Minimum execution time: 266_000 picoseconds. + Weight::from_parts(360_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1281,51 +1279,51 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_413_000 picoseconds. - Weight::from_parts(15_066_000, 3938) + // Minimum execution time: 14_463_000 picoseconds. + Weight::from_parts(15_085_000, 3938) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 303_000 picoseconds. - Weight::from_parts(340_000, 0) + // Minimum execution time: 329_000 picoseconds. + Weight::from_parts(394_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 246_000 picoseconds. - Weight::from_parts(266_000, 0) + // Minimum execution time: 265_000 picoseconds. + Weight::from_parts(327_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 260_000 picoseconds. - Weight::from_parts(287_000, 0) + // Minimum execution time: 306_000 picoseconds. + Weight::from_parts(359_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 616_000 picoseconds. - Weight::from_parts(726_000, 0) + // Minimum execution time: 653_000 picoseconds. + Weight::from_parts(727_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 253_000 picoseconds. - Weight::from_parts(282_000, 0) + // Minimum execution time: 257_000 picoseconds. + Weight::from_parts(328_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `140` + // Measured: `174` // Estimated: `0` - // Minimum execution time: 5_380_000 picoseconds. - Weight::from_parts(5_740_000, 0) + // Minimum execution time: 5_572_000 picoseconds. + Weight::from_parts(5_858_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1335,8 +1333,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 8_826_000 picoseconds. - Weight::from_parts(9_166_000, 3729) + // Minimum execution time: 9_006_000 picoseconds. + Weight::from_parts(9_371_000, 3729) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -1346,10 +1344,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_971_000 picoseconds. - Weight::from_parts(6_578_727, 3703) + // Minimum execution time: 5_853_000 picoseconds. + Weight::from_parts(6_592_851, 3703) // Standard Error: 4 - .saturating_add(Weight::from_parts(732, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(665, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1360,53 +1358,60 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_866_000 picoseconds. - Weight::from_parts(2_158_746, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(637, 0).saturating_mul(n.into())) + // Minimum execution time: 2_040_000 picoseconds. + Weight::from_parts(2_288_695, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(570, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 223_000 picoseconds. - Weight::from_parts(279_000, 0) + // Minimum execution time: 263_000 picoseconds. + Weight::from_parts(305_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 220_000 picoseconds. - Weight::from_parts(245_000, 0) + // Minimum execution time: 273_000 picoseconds. + Weight::from_parts(303_000, 0) } fn seal_return_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 208_000 picoseconds. - Weight::from_parts(245_000, 0) + // Minimum execution time: 260_000 picoseconds. + Weight::from_parts(304_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 211_000 picoseconds. - Weight::from_parts(252_000, 0) + // Minimum execution time: 277_000 picoseconds. + Weight::from_parts(309_000, 0) } fn seal_gas_limit() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 282_000 picoseconds. - Weight::from_parts(310_000, 0) + // Minimum execution time: 298_000 picoseconds. + Weight::from_parts(356_000, 0) + } + fn seal_gas_price() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 261_000 picoseconds. + Weight::from_parts(293_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 216_000 picoseconds. - Weight::from_parts(242_000, 0) + // Minimum execution time: 257_000 picoseconds. + Weight::from_parts(325_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -1414,48 +1419,48 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_410_000 picoseconds. - Weight::from_parts(3_595_000, 3495) + // Minimum execution time: 3_458_000 picoseconds. + Weight::from_parts(3_785_000, 3495) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 214_000 picoseconds. - Weight::from_parts(234_000, 0) + // Minimum execution time: 273_000 picoseconds. + Weight::from_parts(328_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_344_000 picoseconds. - Weight::from_parts(1_503_000, 0) + // Minimum execution time: 1_383_000 picoseconds. + Weight::from_parts(1_517_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 372_000 picoseconds. - Weight::from_parts(613_654, 0) + // Minimum execution time: 373_000 picoseconds. + Weight::from_parts(630_750, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(295, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(235, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 213_000 picoseconds. - Weight::from_parts(243_000, 0) + // Minimum execution time: 265_000 picoseconds. + Weight::from_parts(297_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 230_000 picoseconds. - Weight::from_parts(252_625, 0) + // Minimum execution time: 250_000 picoseconds. + Weight::from_parts(219_823, 0) // Standard Error: 0 .saturating_add(Weight::from_parts(150, 0).saturating_mul(n.into())) } @@ -1464,10 +1469,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 231_000 picoseconds. - Weight::from_parts(378_784, 0) + // Minimum execution time: 293_000 picoseconds. + Weight::from_parts(492_148, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(296, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(236, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1482,12 +1487,12 @@ impl WeightInfo for () { /// The range of component `n` is `[0, 32]`. fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `324 + n * (88 ±0)` - // Estimated: `3790 + n * (2563 ±0)` - // Minimum execution time: 22_246_000 picoseconds. - Weight::from_parts(22_824_813, 3790) - // Standard Error: 11_423 - .saturating_add(Weight::from_parts(4_328_279, 0).saturating_mul(n.into())) + // Measured: `322 + n * (88 ±0)` + // Estimated: `3788 + n * (2563 ±0)` + // Minimum execution time: 22_378_000 picoseconds. + Weight::from_parts(21_359_808, 3788) + // Standard Error: 12_515 + .saturating_add(Weight::from_parts(4_433_373, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -1500,22 +1505,22 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_199_000 picoseconds. - Weight::from_parts(4_174_861, 0) - // Standard Error: 2_974 - .saturating_add(Weight::from_parts(211_154, 0).saturating_mul(t.into())) - // Standard Error: 30 - .saturating_add(Weight::from_parts(1_037, 0).saturating_mul(n.into())) + // Minimum execution time: 4_250_000 picoseconds. + Weight::from_parts(4_275_643, 0) + // Standard Error: 2_911 + .saturating_add(Weight::from_parts(197_045, 0).saturating_mul(t.into())) + // Standard Error: 29 + .saturating_add(Weight::from_parts(978, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 311_000 picoseconds. - Weight::from_parts(326_000, 0) - // Standard Error: 0 - .saturating_add(Weight::from_parts(815, 0).saturating_mul(i.into())) + // Minimum execution time: 362_000 picoseconds. + Weight::from_parts(68_341, 0) + // Standard Error: 1 + .saturating_add(Weight::from_parts(762, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -1523,8 +1528,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 7_584_000 picoseconds. - Weight::from_parts(8_006_000, 680) + // Minimum execution time: 7_649_000 picoseconds. + Weight::from_parts(7_908_000, 680) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1533,8 +1538,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 42_716_000 picoseconds. - Weight::from_parts(43_583_000, 10690) + // Minimum execution time: 42_646_000 picoseconds. + Weight::from_parts(44_107_000, 10690) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1543,8 +1548,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 8_727_000 picoseconds. - Weight::from_parts(9_056_000, 680) + // Minimum execution time: 8_978_000 picoseconds. + Weight::from_parts(9_343_000, 680) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1554,8 +1559,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 44_882_000 picoseconds. - Weight::from_parts(45_933_000, 10690) + // Minimum execution time: 44_678_000 picoseconds. + Weight::from_parts(46_166_000, 10690) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1567,12 +1572,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_150_000 picoseconds. - Weight::from_parts(9_621_151, 247) - // Standard Error: 43 - .saturating_add(Weight::from_parts(554, 0).saturating_mul(n.into())) - // Standard Error: 43 - .saturating_add(Weight::from_parts(645, 0).saturating_mul(o.into())) + // Minimum execution time: 9_216_000 picoseconds. + Weight::from_parts(9_774_592, 247) + // Standard Error: 51 + .saturating_add(Weight::from_parts(532, 0).saturating_mul(n.into())) + // Standard Error: 51 + .saturating_add(Weight::from_parts(504, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -1584,10 +1589,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_670_000 picoseconds. - Weight::from_parts(9_528_913, 247) - // Standard Error: 56 - .saturating_add(Weight::from_parts(805, 0).saturating_mul(n.into())) + // Minimum execution time: 8_800_000 picoseconds. + Weight::from_parts(9_758_732, 247) + // Standard Error: 70 + .saturating_add(Weight::from_parts(387, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1599,10 +1604,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_214_000 picoseconds. - Weight::from_parts(9_195_285, 247) - // Standard Error: 70 - .saturating_add(Weight::from_parts(1_452, 0).saturating_mul(n.into())) + // Minimum execution time: 8_502_000 picoseconds. + Weight::from_parts(9_415_872, 247) + // Standard Error: 72 + .saturating_add(Weight::from_parts(1_304, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1613,10 +1618,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 7_947_000 picoseconds. - Weight::from_parts(8_633_252, 247) - // Standard Error: 53 - .saturating_add(Weight::from_parts(832, 0).saturating_mul(n.into())) + // Minimum execution time: 8_003_000 picoseconds. + Weight::from_parts(8_757_027, 247) + // Standard Error: 64 + .saturating_add(Weight::from_parts(508, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1627,10 +1632,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_414_000 picoseconds. - Weight::from_parts(10_289_881, 247) - // Standard Error: 66 - .saturating_add(Weight::from_parts(1_325, 0).saturating_mul(n.into())) + // Minimum execution time: 9_369_000 picoseconds. + Weight::from_parts(10_394_508, 247) + // Standard Error: 70 + .saturating_add(Weight::from_parts(1_404, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1639,36 +1644,36 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_424_000 picoseconds. - Weight::from_parts(1_511_000, 0) + // Minimum execution time: 1_457_000 picoseconds. + Weight::from_parts(1_595_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_797_000 picoseconds. - Weight::from_parts(1_961_000, 0) + // Minimum execution time: 1_894_000 picoseconds. + Weight::from_parts(2_062_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_498_000 picoseconds. - Weight::from_parts(1_562_000, 0) + // Minimum execution time: 1_535_000 picoseconds. + Weight::from_parts(1_586_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_610_000 picoseconds. - Weight::from_parts(1_703_000, 0) + // Minimum execution time: 1_706_000 picoseconds. + Weight::from_parts(1_850_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_100_000 picoseconds. - Weight::from_parts(1_197_000, 0) + // Minimum execution time: 1_198_000 picoseconds. + Weight::from_parts(1_325_000, 0) } /// The range of component `n` is `[0, 448]`. /// The range of component `o` is `[0, 448]`. @@ -1676,50 +1681,50 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_232_000 picoseconds. - Weight::from_parts(2_371_207, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(385, 0).saturating_mul(n.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(471, 0).saturating_mul(o.into())) + // Minimum execution time: 2_324_000 picoseconds. + Weight::from_parts(2_397_504, 0) + // Standard Error: 16 + .saturating_add(Weight::from_parts(379, 0).saturating_mul(n.into())) + // Standard Error: 16 + .saturating_add(Weight::from_parts(524, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 448]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_015_000 picoseconds. - Weight::from_parts(2_374_096, 0) - // Standard Error: 18 - .saturating_add(Weight::from_parts(462, 0).saturating_mul(n.into())) + // Minimum execution time: 2_072_000 picoseconds. + Weight::from_parts(2_408_702, 0) + // Standard Error: 23 + .saturating_add(Weight::from_parts(432, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_788_000 picoseconds. - Weight::from_parts(1_983_300, 0) - // Standard Error: 17 - .saturating_add(Weight::from_parts(404, 0).saturating_mul(n.into())) + // Minimum execution time: 1_930_000 picoseconds. + Weight::from_parts(2_120_317, 0) + // Standard Error: 18 + .saturating_add(Weight::from_parts(391, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_678_000 picoseconds. - Weight::from_parts(1_845_442, 0) - // Standard Error: 15 - .saturating_add(Weight::from_parts(228, 0).saturating_mul(n.into())) + // Minimum execution time: 1_755_000 picoseconds. + Weight::from_parts(1_968_623, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(191, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_take_transient_storage(_n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_489_000 picoseconds. - Weight::from_parts(2_786_607, 0) + // Minimum execution time: 2_567_000 picoseconds. + Weight::from_parts(2_841_579, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1733,20 +1738,18 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `t` is `[0, 1]`. /// The range of component `i` is `[0, 262144]`. - fn seal_call(t: u32, i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1292 + t * (280 ±0)` - // Estimated: `4757 + t * (2518 ±0)` - // Minimum execution time: 41_653_000 picoseconds. - Weight::from_parts(43_075_070, 4757) - // Standard Error: 42_656 - .saturating_add(Weight::from_parts(1_667_094, 0).saturating_mul(t.into())) - // Standard Error: 0 - .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) + fn seal_call(t: u32, _i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1292 + t * (314 ±0)` + // Estimated: `4757 + t * (2535 ±0)` + // Minimum execution time: 40_925_000 picoseconds. + Weight::from_parts(42_866_040, 4757) + // Standard Error: 99_028 + .saturating_add(Weight::from_parts(2_467_746, 0).saturating_mul(t.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 2518).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 2535).saturating_mul(t.into())) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1758,8 +1761,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 36_908_000 picoseconds. - Weight::from_parts(37_754_000, 4702) + // Minimum execution time: 36_048_000 picoseconds. + Weight::from_parts(37_921_000, 4702) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -1773,12 +1776,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1310` - // Estimated: `4769` - // Minimum execution time: 120_576_000 picoseconds. - Weight::from_parts(112_786_790, 4769) + // Measured: `1331` + // Estimated: `4796` + // Minimum execution time: 121_507_000 picoseconds. + Weight::from_parts(115_250_696, 4796) // Standard Error: 10 - .saturating_add(Weight::from_parts(4_192, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(4_136, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1787,64 +1790,64 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 621_000 picoseconds. - Weight::from_parts(3_506_910, 0) + // Minimum execution time: 649_000 picoseconds. + Weight::from_parts(3_208_110, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_489, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_439, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_054_000 picoseconds. - Weight::from_parts(5_395_465, 0) - // Standard Error: 3 - .saturating_add(Weight::from_parts(3_688, 0).saturating_mul(n.into())) + // Minimum execution time: 1_078_000 picoseconds. + Weight::from_parts(3_361_333, 0) + // Standard Error: 4 + .saturating_add(Weight::from_parts(3_648, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 626_000 picoseconds. - Weight::from_parts(3_549_376, 0) + // Minimum execution time: 673_000 picoseconds. + Weight::from_parts(3_115_184, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_596, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_557, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 598_000 picoseconds. - Weight::from_parts(2_618_039, 0) - // Standard Error: 2 - .saturating_add(Weight::from_parts(1_616, 0).saturating_mul(n.into())) + // Minimum execution time: 685_000 picoseconds. + Weight::from_parts(3_752_567, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_549, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_715_000 picoseconds. - Weight::from_parts(25_484_960, 0) - // Standard Error: 13 - .saturating_add(Weight::from_parts(5_315, 0).saturating_mul(n.into())) + // Minimum execution time: 42_901_000 picoseconds. + Weight::from_parts(30_989_396, 0) + // Standard Error: 11 + .saturating_add(Weight::from_parts(5_414, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 47_123_000 picoseconds. - Weight::from_parts(48_956_000, 0) + // Minimum execution time: 47_249_000 picoseconds. + Weight::from_parts(48_530_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_650_000 picoseconds. - Weight::from_parts(12_768_000, 0) + // Minimum execution time: 12_873_000 picoseconds. + Weight::from_parts(13_127_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1852,8 +1855,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 18_061_000 picoseconds. - Weight::from_parts(18_851_000, 3765) + // Minimum execution time: 18_436_000 picoseconds. + Weight::from_parts(19_107_000, 3765) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1863,8 +1866,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 13_779_000 picoseconds. - Weight::from_parts(14_320_000, 3803) + // Minimum execution time: 13_894_000 picoseconds. + Weight::from_parts(14_355_000, 3803) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1874,8 +1877,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 12_327_000 picoseconds. - Weight::from_parts(13_156_000, 3561) + // Minimum execution time: 12_516_000 picoseconds. + Weight::from_parts(13_223_000, 3561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1884,9 +1887,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_048_000 picoseconds. - Weight::from_parts(10_590_154, 0) - // Standard Error: 82 - .saturating_add(Weight::from_parts(72_658, 0).saturating_mul(r.into())) + // Minimum execution time: 9_177_000 picoseconds. + Weight::from_parts(11_420_562, 0) + // Standard Error: 99 + .saturating_add(Weight::from_parts(72_860, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 476e3a26817a..86ccb623a1d9 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -71,6 +71,10 @@ pub trait HostFn: private::Sealed { /// Returns the [EIP-155](https://eips.ethereum.org/EIPS/eip-155) chain ID. fn chain_id(output: &mut [u8; 32]); + /// Returns the price per ref_time, akin to the EVM + /// [GASPRICE](https://www.evm.codes/?fork=cancun#3a) opcode. + fn gas_price() -> u64; + /// Returns the call data size. fn call_data_size() -> u64; diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 8376263b2348..045ebf0fbf75 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -93,6 +93,7 @@ mod sys { data_ptr: *const u8, data_len: u32, ); + pub fn gas_price() -> u64; pub fn call_data_size() -> u64; pub fn block_number(out_ptr: *mut u8); pub fn block_hash(block_number_ptr: *const u8, out_ptr: *mut u8); @@ -369,6 +370,10 @@ impl HostFn for HostFnImpl { panic!("seal_return does not return"); } + fn gas_price() -> u64 { + unsafe { sys::gas_price() } + } + fn balance(output: &mut [u8; 32]) { unsafe { sys::balance(output.as_mut_ptr()) } } From ade1f755cbba62cd6800601d1d78202ee5f629a5 Mon Sep 17 00:00:00 2001 From: clangenb <37865735+clangenb@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:53:37 +0100 Subject: [PATCH 196/340] [polkadot-runtime-parachains] migrate disputes and disputes/slashing to benchmarking to bench v2 syntax (#6577) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [polkadot-runtime-parachains] migrate disputes and disputes/slashing to benchmarking to bench v2 syntax Part of: * #6202 --------- Co-authored-by: Giuseppe Re Co-authored-by: Bastian Köcher --- .../parachains/src/disputes/benchmarking.rs | 16 +++++--- .../parachains/src/disputes/slashing.rs | 6 +-- .../src/disputes/slashing/benchmarking.rs | 38 +++++++++---------- ...ot_runtime_parachains_disputes_slashing.rs | 2 +- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/polkadot/runtime/parachains/src/disputes/benchmarking.rs b/polkadot/runtime/parachains/src/disputes/benchmarking.rs index 05f4b3f1ac81..571c44d1ac24 100644 --- a/polkadot/runtime/parachains/src/disputes/benchmarking.rs +++ b/polkadot/runtime/parachains/src/disputes/benchmarking.rs @@ -16,15 +16,21 @@ use super::*; -use frame_benchmarking::benchmarks; +use frame_benchmarking::v2::*; use frame_system::RawOrigin; use sp_runtime::traits::One; -benchmarks! { - force_unfreeze { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn force_unfreeze() { Frozen::::set(Some(One::one())); - }: _(RawOrigin::Root) - verify { + + #[extrinsic_call] + _(RawOrigin::Root); + assert!(Frozen::::get().is_none()) } diff --git a/polkadot/runtime/parachains/src/disputes/slashing.rs b/polkadot/runtime/parachains/src/disputes/slashing.rs index 2e09ea667f74..95dbf2ba42bb 100644 --- a/polkadot/runtime/parachains/src/disputes/slashing.rs +++ b/polkadot/runtime/parachains/src/disputes/slashing.rs @@ -355,12 +355,12 @@ impl HandleReports for () { } pub trait WeightInfo { - fn report_dispute_lost(validator_count: ValidatorSetCount) -> Weight; + fn report_dispute_lost_unsigned(validator_count: ValidatorSetCount) -> Weight; } pub struct TestWeightInfo; impl WeightInfo for TestWeightInfo { - fn report_dispute_lost(_validator_count: ValidatorSetCount) -> Weight { + fn report_dispute_lost_unsigned(_validator_count: ValidatorSetCount) -> Weight { Weight::zero() } } @@ -445,7 +445,7 @@ pub mod pallet { #[pallet::call] impl Pallet { #[pallet::call_index(0)] - #[pallet::weight(::WeightInfo::report_dispute_lost( + #[pallet::weight(::WeightInfo::report_dispute_lost_unsigned( key_owner_proof.validator_count() ))] pub fn report_dispute_lost_unsigned( diff --git a/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs b/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs index b53f98caeea3..bfd46d752438 100644 --- a/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs +++ b/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs @@ -18,7 +18,7 @@ use super::*; use crate::{disputes::SlashingHandler, initializer, shared}; use codec::Decode; -use frame_benchmarking::{benchmarks, whitelist_account}; +use frame_benchmarking::v2::*; use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_staking::testing_utils::create_validators; @@ -29,6 +29,11 @@ use sp_session::MembershipProof; // Candidate hash of the disputed candidate. const CANDIDATE_HASH: CandidateHash = CandidateHash(Hash::zero()); +// Simplify getting the value in the benchmark +pub const fn max_validators_for() -> u32 { + <::BenchmarkingConfig as BenchmarkingConfiguration>::MAX_VALIDATORS +} + pub trait Config: pallet_session::Config + pallet_session::historical::Config @@ -106,6 +111,7 @@ where (session_index, key_owner_proof, validator_id) } +/// Submits a single `ForInvalid` dispute. fn setup_dispute(session_index: SessionIndex, validator_id: ValidatorId) -> DisputeProof where T: Config, @@ -125,6 +131,7 @@ where dispute_proof(session_index, validator_id, validator_index) } +/// Creates a `ForInvalid` dispute proof. fn dispute_proof( session_index: SessionIndex, validator_id: ValidatorId, @@ -136,27 +143,20 @@ fn dispute_proof( DisputeProof { time_slot, kind, validator_index, validator_id } } -benchmarks! { - where_clause { - where T: Config, - } - - // in this setup we have a single `ForInvalid` dispute - // submitted for a past session - report_dispute_lost { - let n in 4..<::BenchmarkingConfig as BenchmarkingConfiguration>::MAX_VALIDATORS; +#[benchmarks(where T: Config)] +mod benchmarks { + use super::*; - let origin = RawOrigin::None.into(); + #[benchmark] + fn report_dispute_lost_unsigned(n: Linear<4, { max_validators_for::() }>) { let (session_index, key_owner_proof, validator_id) = setup_validator_set::(n); + + // submit a single `ForInvalid` dispute for a past session. let dispute_proof = setup_dispute::(session_index, validator_id); - }: { - let result = Pallet::::report_dispute_lost_unsigned( - origin, - Box::new(dispute_proof), - key_owner_proof, - ); - assert!(result.is_ok()); - } verify { + + #[extrinsic_call] + _(RawOrigin::None, Box::new(dispute_proof), key_owner_proof); + let unapplied = >::get(session_index, CANDIDATE_HASH); assert!(unapplied.is_none()); } diff --git a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes_slashing.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes_slashing.rs index a035ea2b0b5e..f4dbca0f29ff 100644 --- a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes_slashing.rs +++ b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_disputes_slashing.rs @@ -85,7 +85,7 @@ impl polkadot_runtime_parachains::disputes::slashing::W /// Storage: Staking UnappliedSlashes (r:1 w:1) /// Proof Skipped: Staking UnappliedSlashes (max_values: None, max_size: None, mode: Measured) /// The range of component `n` is `[4, 300]`. - fn report_dispute_lost(n: u32, ) -> Weight { + fn report_dispute_lost_unsigned(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `4531 + n * (189 ±0)` // Estimated: `7843 + n * (192 ±0)` From 243b751abbb94369bbd92c83d8ab159ddfc3c556 Mon Sep 17 00:00:00 2001 From: Cyrill Leutwiler Date: Thu, 19 Dec 2024 16:45:38 +0100 Subject: [PATCH 197/340] [pallet-revive] implement the base fee API (#6964) This PR implements the base fee syscall API method. Currently this is implemented as a compile time constant in the revive compiler, returning 0. However, since this is an opocde, if we ever need to implement it for compatibility reasons with [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md), it would break already deployed contracts. Thus we provide a syscall method instead. --------- Signed-off-by: xermicus Signed-off-by: Cyrill Leutwiler Co-authored-by: command-bot <> --- prdoc/pr_6964.prdoc | 15 + .../revive/fixtures/contracts/base_fee.rs | 36 + .../frame/revive/src/benchmarking/mod.rs | 12 + substrate/frame/revive/src/tests.rs | 18 + substrate/frame/revive/src/wasm/runtime.rs | 17 + substrate/frame/revive/src/weights.rs | 959 +++++++++--------- substrate/frame/revive/uapi/src/host.rs | 4 + .../frame/revive/uapi/src/host/riscv64.rs | 5 + 8 files changed, 602 insertions(+), 464 deletions(-) create mode 100644 prdoc/pr_6964.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/base_fee.rs diff --git a/prdoc/pr_6964.prdoc b/prdoc/pr_6964.prdoc new file mode 100644 index 000000000000..3a88fa72e963 --- /dev/null +++ b/prdoc/pr_6964.prdoc @@ -0,0 +1,15 @@ +title: '[pallet-revive] implement the base fee API' +doc: +- audience: Runtime Dev + description: This PR implements the base fee syscall API method. Currently this + is implemented as a compile time constant in the revive compiler, returning 0. + However, since this is an opocde, if we ever need to implement it for compatibility + reasons with [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md), + it would break already deployed contracts. Thus we provide a syscall method instead. +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/base_fee.rs b/substrate/frame/revive/fixtures/contracts/base_fee.rs new file mode 100644 index 000000000000..157909463ee4 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/base_fee.rs @@ -0,0 +1,36 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Returns the base fee back to the caller. + +#![no_std] +#![no_main] + +extern crate common; +use uapi::{HostFn, HostFnImpl as api, ReturnFlags}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + let mut buf = [0; 32]; + api::base_fee(&mut buf); + api::return_value(ReturnFlags::empty(), &buf); +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 093f7ef70674..e67c39ec0899 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -832,6 +832,18 @@ mod benchmarks { assert_eq!(result.unwrap(), u64::from(GAS_PRICE)); } + #[benchmark(pov_mode = Measured)] + fn seal_base_fee() { + build_runtime!(runtime, memory: [[1u8;32], ]); + let result; + #[block] + { + result = runtime.bench_base_fee(memory.as_mut_slice(), 0); + } + assert_ok!(result); + assert_eq!(U256::from_little_endian(&memory[..]), U256::zero()); + } + #[benchmark(pov_mode = Measured)] fn seal_block_number() { build_runtime!(runtime, memory: [[0u8;32], ]); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index a09930eaac64..664578bf7672 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4382,6 +4382,24 @@ fn gas_price_api_works() { }); } +#[test] +fn base_fee_api_works() { + let (code, _) = compile_module("base_fee").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + // Create fixture: Constructor does nothing + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + // Call the contract: It echoes back the value returned by the base fee API. + let received = builder::bare_call(addr).build_and_unwrap_result(); + assert_eq!(received.flags, ReturnFlags::empty()); + assert_eq!(U256::from_little_endian(received.data[..].try_into().unwrap()), U256::zero()); + }); +} + #[test] fn call_data_size_api_works() { let (code, _) = compile_module("call_data_size").unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 3268e0c59c2b..52f79f2eb55a 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -327,6 +327,8 @@ pub enum RuntimeCosts { BlockHash, /// Weight of calling `seal_gas_price`. GasPrice, + /// Weight of calling `seal_base_fee`. + BaseFee, /// Weight of calling `seal_now`. Now, /// Weight of calling `seal_gas_limit`. @@ -481,6 +483,7 @@ impl Token for RuntimeCosts { BlockNumber => T::WeightInfo::seal_block_number(), BlockHash => T::WeightInfo::seal_block_hash(), GasPrice => T::WeightInfo::seal_gas_price(), + BaseFee => T::WeightInfo::seal_base_fee(), Now => T::WeightInfo::seal_now(), GasLimit => T::WeightInfo::seal_gas_limit(), WeightToFee => T::WeightInfo::seal_weight_to_fee(), @@ -1575,6 +1578,20 @@ pub mod env { Ok(GAS_PRICE.into()) } + /// Returns the simulated ethereum `BASEFEE` value. + /// See [`pallet_revive_uapi::HostFn::base_fee`]. + #[stable] + fn base_fee(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::BaseFee)?; + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &U256::zero().to_little_endian(), + false, + already_charged, + )?) + } + /// Load the latest block timestamp into the supplied buffer /// See [`pallet_revive_uapi::HostFn::now`]. #[stable] diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 78eb6740c10a..e35ba5ca0766 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -18,9 +18,9 @@ //! Autogenerated weights for `pallet_revive` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-19, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `8a4618716d33`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `19e0eeaa3bc2`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: @@ -85,6 +85,7 @@ pub trait WeightInfo { fn seal_call_data_size() -> Weight; fn seal_gas_limit() -> Weight; fn seal_gas_price() -> Weight; + fn seal_base_fee() -> Weight; fn seal_block_number() -> Weight; fn seal_block_hash() -> Weight; fn seal_now() -> Weight; @@ -140,8 +141,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_700_000 picoseconds. - Weight::from_parts(2_858_000, 1594) + // Minimum execution time: 2_859_000 picoseconds. + Weight::from_parts(3_007_000, 1594) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -151,10 +152,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 15_542_000 picoseconds. - Weight::from_parts(3_353_401, 415) - // Standard Error: 1_167 - .saturating_add(Weight::from_parts(1_194_349, 0).saturating_mul(k.into())) + // Minimum execution time: 15_640_000 picoseconds. + Weight::from_parts(1_609_026, 415) + // Standard Error: 1_359 + .saturating_add(Weight::from_parts(1_204_420, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes(2_u64)) @@ -176,10 +177,10 @@ impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 93_827_000 picoseconds. - Weight::from_parts(98_408_848, 7442) + // Measured: `1463` + // Estimated: `7403` + // Minimum execution time: 89_437_000 picoseconds. + Weight::from_parts(94_285_182, 7403) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -199,14 +200,16 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { + fn instantiate_with_code(c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `403` - // Estimated: `6343` - // Minimum execution time: 197_900_000 picoseconds. - Weight::from_parts(189_732_698, 6343) - // Standard Error: 9 - .saturating_add(Weight::from_parts(4_465, 0).saturating_mul(i.into())) + // Measured: `364` + // Estimated: `6327` + // Minimum execution time: 187_904_000 picoseconds. + Weight::from_parts(153_252_081, 6327) + // Standard Error: 11 + .saturating_add(Weight::from_parts(49, 0).saturating_mul(c.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(4_528, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -227,12 +230,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1309` - // Estimated: `4760` - // Minimum execution time: 162_798_000 picoseconds. - Weight::from_parts(148_006_239, 4760) - // Standard Error: 14 - .saturating_add(Weight::from_parts(4_424, 0).saturating_mul(i.into())) + // Measured: `1296` + // Estimated: `4758` + // Minimum execution time: 154_656_000 picoseconds. + Weight::from_parts(139_308_398, 4758) + // Standard Error: 16 + .saturating_add(Weight::from_parts(4_421, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -250,10 +253,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 144_505_000 picoseconds. - Weight::from_parts(149_799_000, 7442) + // Measured: `1463` + // Estimated: `7403` + // Minimum execution time: 138_815_000 picoseconds. + Weight::from_parts(149_067_000, 7403) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -264,12 +267,14 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. - fn upload_code(_c: u32, ) -> Weight { + fn upload_code(c: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 52_117_000 picoseconds. - Weight::from_parts(55_103_397, 3574) + // Minimum execution time: 49_978_000 picoseconds. + Weight::from_parts(51_789_325, 3574) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1, 0).saturating_mul(c.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -283,8 +288,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 46_384_000 picoseconds. - Weight::from_parts(47_327_000, 3750) + // Minimum execution time: 43_833_000 picoseconds. + Weight::from_parts(44_660_000, 3750) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -296,8 +301,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 27_210_000 picoseconds. - Weight::from_parts(28_226_000, 6469) + // Minimum execution time: 26_717_000 picoseconds. + Weight::from_parts(28_566_000, 6469) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -309,8 +314,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 41_028_000 picoseconds. - Weight::from_parts(42_438_000, 3574) + // Minimum execution time: 39_401_000 picoseconds. + Weight::from_parts(40_542_000, 3574) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -322,8 +327,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 33_271_000 picoseconds. - Weight::from_parts(35_037_000, 3521) + // Minimum execution time: 31_570_000 picoseconds. + Weight::from_parts(32_302_000, 3521) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -335,8 +340,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_455_000 picoseconds. - Weight::from_parts(14_144_000, 3610) + // Minimum execution time: 13_607_000 picoseconds. + Weight::from_parts(13_903_000, 3610) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -344,24 +349,24 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_514_000 picoseconds. - Weight::from_parts(8_642_516, 0) - // Standard Error: 190 - .saturating_add(Weight::from_parts(168_973, 0).saturating_mul(r.into())) + // Minimum execution time: 7_400_000 picoseconds. + Weight::from_parts(8_388_251, 0) + // Standard Error: 283 + .saturating_add(Weight::from_parts(165_630, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 341_000 picoseconds. - Weight::from_parts(373_000, 0) + // Minimum execution time: 275_000 picoseconds. + Weight::from_parts(305_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 280_000 picoseconds. - Weight::from_parts(329_000, 0) + // Minimum execution time: 224_000 picoseconds. + Weight::from_parts(265_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -369,8 +374,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_296_000 picoseconds. - Weight::from_parts(10_757_000, 3771) + // Minimum execution time: 10_004_000 picoseconds. + Weight::from_parts(10_336_000, 3771) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -379,16 +384,16 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_453_000 picoseconds. - Weight::from_parts(12_071_000, 3868) + // Minimum execution time: 11_054_000 picoseconds. + Weight::from_parts(11_651_000, 3868) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 266_000 picoseconds. - Weight::from_parts(360_000, 0) + // Minimum execution time: 252_000 picoseconds. + Weight::from_parts(305_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -398,51 +403,51 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_463_000 picoseconds. - Weight::from_parts(15_085_000, 3938) + // Minimum execution time: 14_461_000 picoseconds. + Weight::from_parts(15_049_000, 3938) .saturating_add(T::DbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 329_000 picoseconds. - Weight::from_parts(394_000, 0) + // Minimum execution time: 312_000 picoseconds. + Weight::from_parts(338_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 265_000 picoseconds. - Weight::from_parts(327_000, 0) + // Minimum execution time: 243_000 picoseconds. + Weight::from_parts(299_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 306_000 picoseconds. - Weight::from_parts(359_000, 0) + // Minimum execution time: 231_000 picoseconds. + Weight::from_parts(271_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 653_000 picoseconds. - Weight::from_parts(727_000, 0) + // Minimum execution time: 683_000 picoseconds. + Weight::from_parts(732_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 257_000 picoseconds. - Weight::from_parts(328_000, 0) + // Minimum execution time: 226_000 picoseconds. + Weight::from_parts(273_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `174` + // Measured: `102` // Estimated: `0` - // Minimum execution time: 5_572_000 picoseconds. - Weight::from_parts(5_858_000, 0) + // Minimum execution time: 4_626_000 picoseconds. + Weight::from_parts(4_842_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -452,8 +457,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 9_006_000 picoseconds. - Weight::from_parts(9_371_000, 3729) + // Minimum execution time: 12_309_000 picoseconds. + Weight::from_parts(12_653_000, 3729) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -463,10 +468,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_853_000 picoseconds. - Weight::from_parts(6_592_851, 3703) - // Standard Error: 4 - .saturating_add(Weight::from_parts(665, 0).saturating_mul(n.into())) + // Minimum execution time: 5_838_000 picoseconds. + Weight::from_parts(9_570_778, 3703) + // Standard Error: 19 + .saturating_add(Weight::from_parts(721, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -477,60 +482,67 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_040_000 picoseconds. - Weight::from_parts(2_288_695, 0) + // Minimum execution time: 1_910_000 picoseconds. + Weight::from_parts(2_205_396, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(570, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(538, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 263_000 picoseconds. - Weight::from_parts(305_000, 0) + // Minimum execution time: 224_000 picoseconds. + Weight::from_parts(274_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 273_000 picoseconds. - Weight::from_parts(303_000, 0) + // Minimum execution time: 231_000 picoseconds. + Weight::from_parts(279_000, 0) } fn seal_return_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 260_000 picoseconds. - Weight::from_parts(304_000, 0) + // Minimum execution time: 229_000 picoseconds. + Weight::from_parts(267_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 277_000 picoseconds. - Weight::from_parts(309_000, 0) + // Minimum execution time: 218_000 picoseconds. + Weight::from_parts(267_000, 0) } fn seal_gas_limit() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 298_000 picoseconds. - Weight::from_parts(356_000, 0) + // Minimum execution time: 225_000 picoseconds. + Weight::from_parts(280_000, 0) } fn seal_gas_price() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 261_000 picoseconds. - Weight::from_parts(293_000, 0) + // Minimum execution time: 274_000 picoseconds. + Weight::from_parts(323_000, 0) + } + fn seal_base_fee() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 239_000 picoseconds. + Weight::from_parts(290_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 257_000 picoseconds. - Weight::from_parts(325_000, 0) + // Minimum execution time: 224_000 picoseconds. + Weight::from_parts(274_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -538,60 +550,60 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_458_000 picoseconds. - Weight::from_parts(3_785_000, 3495) + // Minimum execution time: 3_430_000 picoseconds. + Weight::from_parts(3_692_000, 3495) .saturating_add(T::DbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 273_000 picoseconds. - Weight::from_parts(328_000, 0) + // Minimum execution time: 241_000 picoseconds. + Weight::from_parts(290_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_383_000 picoseconds. - Weight::from_parts(1_517_000, 0) + // Minimum execution time: 1_355_000 picoseconds. + Weight::from_parts(1_493_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 373_000 picoseconds. - Weight::from_parts(630_750, 0) + // Minimum execution time: 348_000 picoseconds. + Weight::from_parts(1_004_890, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(235, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(202, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 265_000 picoseconds. - Weight::from_parts(297_000, 0) + // Minimum execution time: 222_000 picoseconds. + Weight::from_parts(256_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 250_000 picoseconds. - Weight::from_parts(219_823, 0) + // Minimum execution time: 240_000 picoseconds. + Weight::from_parts(330_609, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(150, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 293_000 picoseconds. - Weight::from_parts(492_148, 0) + // Minimum execution time: 232_000 picoseconds. + Weight::from_parts(264_000, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(236, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(208, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -607,11 +619,11 @@ impl WeightInfo for SubstrateWeight { fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `322 + n * (88 ±0)` - // Estimated: `3788 + n * (2563 ±0)` - // Minimum execution time: 22_378_000 picoseconds. - Weight::from_parts(21_359_808, 3788) - // Standard Error: 12_515 - .saturating_add(Weight::from_parts(4_433_373, 0).saturating_mul(n.into())) + // Estimated: `3787 + n * (2563 ±0)` + // Minimum execution time: 21_920_000 picoseconds. + Weight::from_parts(21_725_868, 3787) + // Standard Error: 11_165 + .saturating_add(Weight::from_parts(4_317_986, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) @@ -624,22 +636,22 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_250_000 picoseconds. - Weight::from_parts(4_275_643, 0) - // Standard Error: 2_911 - .saturating_add(Weight::from_parts(197_045, 0).saturating_mul(t.into())) - // Standard Error: 29 - .saturating_add(Weight::from_parts(978, 0).saturating_mul(n.into())) + // Minimum execution time: 4_140_000 picoseconds. + Weight::from_parts(4_259_301, 0) + // Standard Error: 3_362 + .saturating_add(Weight::from_parts(194_546, 0).saturating_mul(t.into())) + // Standard Error: 34 + .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 362_000 picoseconds. - Weight::from_parts(68_341, 0) + // Minimum execution time: 340_000 picoseconds. + Weight::from_parts(306_527, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(762, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(728, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -647,8 +659,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 7_649_000 picoseconds. - Weight::from_parts(7_908_000, 680) + // Minimum execution time: 10_747_000 picoseconds. + Weight::from_parts(11_276_000, 680) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -657,8 +669,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 42_646_000 picoseconds. - Weight::from_parts(44_107_000, 10690) + // Minimum execution time: 42_076_000 picoseconds. + Weight::from_parts(43_381_000, 10690) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -667,8 +679,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 8_978_000 picoseconds. - Weight::from_parts(9_343_000, 680) + // Minimum execution time: 11_703_000 picoseconds. + Weight::from_parts(12_308_000, 680) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -678,8 +690,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 44_678_000 picoseconds. - Weight::from_parts(46_166_000, 10690) + // Minimum execution time: 43_460_000 picoseconds. + Weight::from_parts(45_165_000, 10690) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -691,12 +703,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_216_000 picoseconds. - Weight::from_parts(9_774_592, 247) - // Standard Error: 51 - .saturating_add(Weight::from_parts(532, 0).saturating_mul(n.into())) - // Standard Error: 51 - .saturating_add(Weight::from_parts(504, 0).saturating_mul(o.into())) + // Minimum execution time: 9_087_000 picoseconds. + Weight::from_parts(11_787_486, 247) + // Standard Error: 179 + .saturating_add(Weight::from_parts(976, 0).saturating_mul(n.into())) + // Standard Error: 179 + .saturating_add(Weight::from_parts(3_151, 0).saturating_mul(o.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -708,10 +720,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_800_000 picoseconds. - Weight::from_parts(9_758_732, 247) - // Standard Error: 70 - .saturating_add(Weight::from_parts(387, 0).saturating_mul(n.into())) + // Minimum execution time: 8_611_000 picoseconds. + Weight::from_parts(11_791_390, 247) + // Standard Error: 308 + .saturating_add(Weight::from_parts(3_943, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -723,10 +735,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_502_000 picoseconds. - Weight::from_parts(9_415_872, 247) - // Standard Error: 72 - .saturating_add(Weight::from_parts(1_304, 0).saturating_mul(n.into())) + // Minimum execution time: 8_389_000 picoseconds. + Weight::from_parts(11_625_480, 247) + // Standard Error: 315 + .saturating_add(Weight::from_parts(4_487, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -737,10 +749,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_003_000 picoseconds. - Weight::from_parts(8_757_027, 247) - // Standard Error: 64 - .saturating_add(Weight::from_parts(508, 0).saturating_mul(n.into())) + // Minimum execution time: 7_947_000 picoseconds. + Weight::from_parts(10_970_587, 247) + // Standard Error: 310 + .saturating_add(Weight::from_parts(3_675, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -751,10 +763,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_369_000 picoseconds. - Weight::from_parts(10_394_508, 247) - // Standard Error: 70 - .saturating_add(Weight::from_parts(1_404, 0).saturating_mul(n.into())) + // Minimum execution time: 9_071_000 picoseconds. + Weight::from_parts(12_525_027, 247) + // Standard Error: 328 + .saturating_add(Weight::from_parts(4_427, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -763,36 +775,36 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_457_000 picoseconds. - Weight::from_parts(1_595_000, 0) + // Minimum execution time: 1_487_000 picoseconds. + Weight::from_parts(1_611_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_894_000 picoseconds. - Weight::from_parts(2_062_000, 0) + // Minimum execution time: 1_852_000 picoseconds. + Weight::from_parts(1_982_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_535_000 picoseconds. - Weight::from_parts(1_586_000, 0) + // Minimum execution time: 1_467_000 picoseconds. + Weight::from_parts(1_529_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_706_000 picoseconds. - Weight::from_parts(1_850_000, 0) + // Minimum execution time: 1_630_000 picoseconds. + Weight::from_parts(1_712_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_198_000 picoseconds. - Weight::from_parts(1_325_000, 0) + // Minimum execution time: 1_188_000 picoseconds. + Weight::from_parts(1_268_000, 0) } /// The range of component `n` is `[0, 448]`. /// The range of component `o` is `[0, 448]`. @@ -800,50 +812,52 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_324_000 picoseconds. - Weight::from_parts(2_397_504, 0) - // Standard Error: 16 - .saturating_add(Weight::from_parts(379, 0).saturating_mul(n.into())) - // Standard Error: 16 - .saturating_add(Weight::from_parts(524, 0).saturating_mul(o.into())) + // Minimum execution time: 2_197_000 picoseconds. + Weight::from_parts(2_464_654, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(296, 0).saturating_mul(n.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(342, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 448]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_072_000 picoseconds. - Weight::from_parts(2_408_702, 0) + // Minimum execution time: 2_005_000 picoseconds. + Weight::from_parts(2_381_053, 0) // Standard Error: 23 - .saturating_add(Weight::from_parts(432, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(322, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_930_000 picoseconds. - Weight::from_parts(2_120_317, 0) - // Standard Error: 18 - .saturating_add(Weight::from_parts(391, 0).saturating_mul(n.into())) + // Minimum execution time: 1_853_000 picoseconds. + Weight::from_parts(2_082_772, 0) + // Standard Error: 20 + .saturating_add(Weight::from_parts(322, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_755_000 picoseconds. - Weight::from_parts(1_968_623, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(191, 0).saturating_mul(n.into())) + // Minimum execution time: 1_711_000 picoseconds. + Weight::from_parts(1_899_649, 0) + // Standard Error: 16 + .saturating_add(Weight::from_parts(208, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. - fn seal_take_transient_storage(_n: u32, ) -> Weight { + fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_567_000 picoseconds. - Weight::from_parts(2_841_579, 0) + // Minimum execution time: 2_460_000 picoseconds. + Weight::from_parts(2_684_364, 0) + // Standard Error: 22 + .saturating_add(Weight::from_parts(56, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -857,18 +871,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `t` is `[0, 1]`. /// The range of component `i` is `[0, 262144]`. - fn seal_call(t: u32, _i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1292 + t * (314 ±0)` - // Estimated: `4757 + t * (2535 ±0)` - // Minimum execution time: 40_925_000 picoseconds. - Weight::from_parts(42_866_040, 4757) - // Standard Error: 99_028 - .saturating_add(Weight::from_parts(2_467_746, 0).saturating_mul(t.into())) + fn seal_call(t: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1292 + t * (203 ±0)` + // Estimated: `4757 + t * (2480 ±0)` + // Minimum execution time: 40_031_000 picoseconds. + Weight::from_parts(41_527_691, 4757) + // Standard Error: 50_351 + .saturating_add(Weight::from_parts(1_112_950, 0).saturating_mul(t.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(T::DbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 2535).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 2480).saturating_mul(t.into())) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -880,8 +896,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 36_048_000 picoseconds. - Weight::from_parts(37_921_000, 4702) + // Minimum execution time: 35_759_000 picoseconds. + Weight::from_parts(37_086_000, 4702) .saturating_add(T::DbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -895,12 +911,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1331` - // Estimated: `4796` - // Minimum execution time: 121_507_000 picoseconds. - Weight::from_parts(115_250_696, 4796) - // Standard Error: 10 - .saturating_add(Weight::from_parts(4_136, 0).saturating_mul(i.into())) + // Measured: `1271` + // Estimated: `4710` + // Minimum execution time: 116_485_000 picoseconds. + Weight::from_parts(108_907_717, 4710) + // Standard Error: 12 + .saturating_add(Weight::from_parts(4_125, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -909,64 +925,64 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 649_000 picoseconds. - Weight::from_parts(3_208_110, 0) + // Minimum execution time: 651_000 picoseconds. + Weight::from_parts(3_867_609, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_439, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_384, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_078_000 picoseconds. - Weight::from_parts(3_361_333, 0) - // Standard Error: 4 - .saturating_add(Weight::from_parts(3_648, 0).saturating_mul(n.into())) + // Minimum execution time: 1_090_000 picoseconds. + Weight::from_parts(5_338_460, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(3_601, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 673_000 picoseconds. - Weight::from_parts(3_115_184, 0) + // Minimum execution time: 717_000 picoseconds. + Weight::from_parts(2_629_461, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_557, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_528, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 685_000 picoseconds. - Weight::from_parts(3_752_567, 0) + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(4_807_814, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_549, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_509, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_901_000 picoseconds. - Weight::from_parts(30_989_396, 0) - // Standard Error: 11 - .saturating_add(Weight::from_parts(5_414, 0).saturating_mul(n.into())) + // Minimum execution time: 42_829_000 picoseconds. + Weight::from_parts(24_650_992, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(5_212, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 47_249_000 picoseconds. - Weight::from_parts(48_530_000, 0) + // Minimum execution time: 46_902_000 picoseconds. + Weight::from_parts(48_072_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_873_000 picoseconds. - Weight::from_parts(13_127_000, 0) + // Minimum execution time: 12_713_000 picoseconds. + Weight::from_parts(12_847_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -974,8 +990,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 18_436_000 picoseconds. - Weight::from_parts(19_107_000, 3765) + // Minimum execution time: 17_657_000 picoseconds. + Weight::from_parts(18_419_000, 3765) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -985,8 +1001,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 13_894_000 picoseconds. - Weight::from_parts(14_355_000, 3803) + // Minimum execution time: 13_650_000 picoseconds. + Weight::from_parts(14_209_000, 3803) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -996,8 +1012,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 12_516_000 picoseconds. - Weight::from_parts(13_223_000, 3561) + // Minimum execution time: 12_341_000 picoseconds. + Weight::from_parts(13_011_000, 3561) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -1006,10 +1022,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_177_000 picoseconds. - Weight::from_parts(11_420_562, 0) - // Standard Error: 99 - .saturating_add(Weight::from_parts(72_860, 0).saturating_mul(r.into())) + // Minimum execution time: 8_899_000 picoseconds. + Weight::from_parts(10_489_171, 0) + // Standard Error: 104 + .saturating_add(Weight::from_parts(73_814, 0).saturating_mul(r.into())) } } @@ -1021,8 +1037,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `1594` - // Minimum execution time: 2_700_000 picoseconds. - Weight::from_parts(2_858_000, 1594) + // Minimum execution time: 2_859_000 picoseconds. + Weight::from_parts(3_007_000, 1594) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1032,10 +1048,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `425 + k * (69 ±0)` // Estimated: `415 + k * (70 ±0)` - // Minimum execution time: 15_542_000 picoseconds. - Weight::from_parts(3_353_401, 415) - // Standard Error: 1_167 - .saturating_add(Weight::from_parts(1_194_349, 0).saturating_mul(k.into())) + // Minimum execution time: 15_640_000 picoseconds. + Weight::from_parts(1_609_026, 415) + // Standard Error: 1_359 + .saturating_add(Weight::from_parts(1_204_420, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes(2_u64)) @@ -1057,10 +1073,10 @@ impl WeightInfo for () { /// The range of component `c` is `[0, 262144]`. fn call_with_code_per_byte(_c: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 93_827_000 picoseconds. - Weight::from_parts(98_408_848, 7442) + // Measured: `1463` + // Estimated: `7403` + // Minimum execution time: 89_437_000 picoseconds. + Weight::from_parts(94_285_182, 7403) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1080,14 +1096,16 @@ impl WeightInfo for () { /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. /// The range of component `i` is `[0, 262144]`. - fn instantiate_with_code(_c: u32, i: u32, ) -> Weight { + fn instantiate_with_code(c: u32, i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `403` - // Estimated: `6343` - // Minimum execution time: 197_900_000 picoseconds. - Weight::from_parts(189_732_698, 6343) - // Standard Error: 9 - .saturating_add(Weight::from_parts(4_465, 0).saturating_mul(i.into())) + // Measured: `364` + // Estimated: `6327` + // Minimum execution time: 187_904_000 picoseconds. + Weight::from_parts(153_252_081, 6327) + // Standard Error: 11 + .saturating_add(Weight::from_parts(49, 0).saturating_mul(c.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(4_528, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1108,12 +1126,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1309` - // Estimated: `4760` - // Minimum execution time: 162_798_000 picoseconds. - Weight::from_parts(148_006_239, 4760) - // Standard Error: 14 - .saturating_add(Weight::from_parts(4_424, 0).saturating_mul(i.into())) + // Measured: `1296` + // Estimated: `4758` + // Minimum execution time: 154_656_000 picoseconds. + Weight::from_parts(139_308_398, 4758) + // Standard Error: 16 + .saturating_add(Weight::from_parts(4_421, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1131,10 +1149,10 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) fn call() -> Weight { // Proof Size summary in bytes: - // Measured: `1502` - // Estimated: `7442` - // Minimum execution time: 144_505_000 picoseconds. - Weight::from_parts(149_799_000, 7442) + // Measured: `1463` + // Estimated: `7403` + // Minimum execution time: 138_815_000 picoseconds. + Weight::from_parts(149_067_000, 7403) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1145,12 +1163,14 @@ impl WeightInfo for () { /// Storage: `Revive::PristineCode` (r:0 w:1) /// Proof: `Revive::PristineCode` (`max_values`: None, `max_size`: Some(262180), added: 264655, mode: `Measured`) /// The range of component `c` is `[0, 262144]`. - fn upload_code(_c: u32, ) -> Weight { + fn upload_code(c: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 52_117_000 picoseconds. - Weight::from_parts(55_103_397, 3574) + // Minimum execution time: 49_978_000 picoseconds. + Weight::from_parts(51_789_325, 3574) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1, 0).saturating_mul(c.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1164,8 +1184,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `285` // Estimated: `3750` - // Minimum execution time: 46_384_000 picoseconds. - Weight::from_parts(47_327_000, 3750) + // Minimum execution time: 43_833_000 picoseconds. + Weight::from_parts(44_660_000, 3750) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1177,8 +1197,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `529` // Estimated: `6469` - // Minimum execution time: 27_210_000 picoseconds. - Weight::from_parts(28_226_000, 6469) + // Minimum execution time: 26_717_000 picoseconds. + Weight::from_parts(28_566_000, 6469) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1190,8 +1210,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `109` // Estimated: `3574` - // Minimum execution time: 41_028_000 picoseconds. - Weight::from_parts(42_438_000, 3574) + // Minimum execution time: 39_401_000 picoseconds. + Weight::from_parts(40_542_000, 3574) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1203,8 +1223,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `56` // Estimated: `3521` - // Minimum execution time: 33_271_000 picoseconds. - Weight::from_parts(35_037_000, 3521) + // Minimum execution time: 31_570_000 picoseconds. + Weight::from_parts(32_302_000, 3521) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1216,8 +1236,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3610` - // Minimum execution time: 13_455_000 picoseconds. - Weight::from_parts(14_144_000, 3610) + // Minimum execution time: 13_607_000 picoseconds. + Weight::from_parts(13_903_000, 3610) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// The range of component `r` is `[0, 1600]`. @@ -1225,24 +1245,24 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_514_000 picoseconds. - Weight::from_parts(8_642_516, 0) - // Standard Error: 190 - .saturating_add(Weight::from_parts(168_973, 0).saturating_mul(r.into())) + // Minimum execution time: 7_400_000 picoseconds. + Weight::from_parts(8_388_251, 0) + // Standard Error: 283 + .saturating_add(Weight::from_parts(165_630, 0).saturating_mul(r.into())) } fn seal_caller() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 341_000 picoseconds. - Weight::from_parts(373_000, 0) + // Minimum execution time: 275_000 picoseconds. + Weight::from_parts(305_000, 0) } fn seal_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 280_000 picoseconds. - Weight::from_parts(329_000, 0) + // Minimum execution time: 224_000 picoseconds. + Weight::from_parts(265_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1250,8 +1270,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `306` // Estimated: `3771` - // Minimum execution time: 10_296_000 picoseconds. - Weight::from_parts(10_757_000, 3771) + // Minimum execution time: 10_004_000 picoseconds. + Weight::from_parts(10_336_000, 3771) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) @@ -1260,16 +1280,16 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `403` // Estimated: `3868` - // Minimum execution time: 11_453_000 picoseconds. - Weight::from_parts(12_071_000, 3868) + // Minimum execution time: 11_054_000 picoseconds. + Weight::from_parts(11_651_000, 3868) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_own_code_hash() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 266_000 picoseconds. - Weight::from_parts(360_000, 0) + // Minimum execution time: 252_000 picoseconds. + Weight::from_parts(305_000, 0) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1279,51 +1299,51 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `473` // Estimated: `3938` - // Minimum execution time: 14_463_000 picoseconds. - Weight::from_parts(15_085_000, 3938) + // Minimum execution time: 14_461_000 picoseconds. + Weight::from_parts(15_049_000, 3938) .saturating_add(RocksDbWeight::get().reads(2_u64)) } fn seal_caller_is_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 329_000 picoseconds. - Weight::from_parts(394_000, 0) + // Minimum execution time: 312_000 picoseconds. + Weight::from_parts(338_000, 0) } fn seal_caller_is_root() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 265_000 picoseconds. - Weight::from_parts(327_000, 0) + // Minimum execution time: 243_000 picoseconds. + Weight::from_parts(299_000, 0) } fn seal_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 306_000 picoseconds. - Weight::from_parts(359_000, 0) + // Minimum execution time: 231_000 picoseconds. + Weight::from_parts(271_000, 0) } fn seal_weight_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 653_000 picoseconds. - Weight::from_parts(727_000, 0) + // Minimum execution time: 683_000 picoseconds. + Weight::from_parts(732_000, 0) } fn seal_ref_time_left() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 257_000 picoseconds. - Weight::from_parts(328_000, 0) + // Minimum execution time: 226_000 picoseconds. + Weight::from_parts(273_000, 0) } fn seal_balance() -> Weight { // Proof Size summary in bytes: - // Measured: `174` + // Measured: `102` // Estimated: `0` - // Minimum execution time: 5_572_000 picoseconds. - Weight::from_parts(5_858_000, 0) + // Minimum execution time: 4_626_000 picoseconds. + Weight::from_parts(4_842_000, 0) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1333,8 +1353,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `264` // Estimated: `3729` - // Minimum execution time: 9_006_000 picoseconds. - Weight::from_parts(9_371_000, 3729) + // Minimum execution time: 12_309_000 picoseconds. + Weight::from_parts(12_653_000, 3729) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Revive::ImmutableDataOf` (r:1 w:0) @@ -1344,10 +1364,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `238 + n * (1 ±0)` // Estimated: `3703 + n * (1 ±0)` - // Minimum execution time: 5_853_000 picoseconds. - Weight::from_parts(6_592_851, 3703) - // Standard Error: 4 - .saturating_add(Weight::from_parts(665, 0).saturating_mul(n.into())) + // Minimum execution time: 5_838_000 picoseconds. + Weight::from_parts(9_570_778, 3703) + // Standard Error: 19 + .saturating_add(Weight::from_parts(721, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1358,60 +1378,67 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_040_000 picoseconds. - Weight::from_parts(2_288_695, 0) + // Minimum execution time: 1_910_000 picoseconds. + Weight::from_parts(2_205_396, 0) // Standard Error: 2 - .saturating_add(Weight::from_parts(570, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(538, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn seal_value_transferred() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 263_000 picoseconds. - Weight::from_parts(305_000, 0) + // Minimum execution time: 224_000 picoseconds. + Weight::from_parts(274_000, 0) } fn seal_minimum_balance() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 273_000 picoseconds. - Weight::from_parts(303_000, 0) + // Minimum execution time: 231_000 picoseconds. + Weight::from_parts(279_000, 0) } fn seal_return_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 260_000 picoseconds. - Weight::from_parts(304_000, 0) + // Minimum execution time: 229_000 picoseconds. + Weight::from_parts(267_000, 0) } fn seal_call_data_size() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 277_000 picoseconds. - Weight::from_parts(309_000, 0) + // Minimum execution time: 218_000 picoseconds. + Weight::from_parts(267_000, 0) } fn seal_gas_limit() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 298_000 picoseconds. - Weight::from_parts(356_000, 0) + // Minimum execution time: 225_000 picoseconds. + Weight::from_parts(280_000, 0) } fn seal_gas_price() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 261_000 picoseconds. - Weight::from_parts(293_000, 0) + // Minimum execution time: 274_000 picoseconds. + Weight::from_parts(323_000, 0) + } + fn seal_base_fee() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 239_000 picoseconds. + Weight::from_parts(290_000, 0) } fn seal_block_number() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 257_000 picoseconds. - Weight::from_parts(325_000, 0) + // Minimum execution time: 224_000 picoseconds. + Weight::from_parts(274_000, 0) } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `Measured`) @@ -1419,60 +1446,60 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `30` // Estimated: `3495` - // Minimum execution time: 3_458_000 picoseconds. - Weight::from_parts(3_785_000, 3495) + // Minimum execution time: 3_430_000 picoseconds. + Weight::from_parts(3_692_000, 3495) .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn seal_now() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 273_000 picoseconds. - Weight::from_parts(328_000, 0) + // Minimum execution time: 241_000 picoseconds. + Weight::from_parts(290_000, 0) } fn seal_weight_to_fee() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_383_000 picoseconds. - Weight::from_parts(1_517_000, 0) + // Minimum execution time: 1_355_000 picoseconds. + Weight::from_parts(1_493_000, 0) } /// The range of component `n` is `[0, 262140]`. fn seal_copy_to_contract(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 373_000 picoseconds. - Weight::from_parts(630_750, 0) + // Minimum execution time: 348_000 picoseconds. + Weight::from_parts(1_004_890, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(235, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(202, 0).saturating_mul(n.into())) } fn seal_call_data_load() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 265_000 picoseconds. - Weight::from_parts(297_000, 0) + // Minimum execution time: 222_000 picoseconds. + Weight::from_parts(256_000, 0) } /// The range of component `n` is `[0, 262144]`. fn seal_call_data_copy(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 250_000 picoseconds. - Weight::from_parts(219_823, 0) + // Minimum execution time: 240_000 picoseconds. + Weight::from_parts(330_609, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(150, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(114, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262140]`. fn seal_return(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 293_000 picoseconds. - Weight::from_parts(492_148, 0) + // Minimum execution time: 232_000 picoseconds. + Weight::from_parts(264_000, 0) // Standard Error: 0 - .saturating_add(Weight::from_parts(236, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(208, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1488,11 +1515,11 @@ impl WeightInfo for () { fn seal_terminate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `322 + n * (88 ±0)` - // Estimated: `3788 + n * (2563 ±0)` - // Minimum execution time: 22_378_000 picoseconds. - Weight::from_parts(21_359_808, 3788) - // Standard Error: 12_515 - .saturating_add(Weight::from_parts(4_433_373, 0).saturating_mul(n.into())) + // Estimated: `3787 + n * (2563 ±0)` + // Minimum execution time: 21_920_000 picoseconds. + Weight::from_parts(21_725_868, 3787) + // Standard Error: 11_165 + .saturating_add(Weight::from_parts(4_317_986, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) @@ -1505,22 +1532,22 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_250_000 picoseconds. - Weight::from_parts(4_275_643, 0) - // Standard Error: 2_911 - .saturating_add(Weight::from_parts(197_045, 0).saturating_mul(t.into())) - // Standard Error: 29 - .saturating_add(Weight::from_parts(978, 0).saturating_mul(n.into())) + // Minimum execution time: 4_140_000 picoseconds. + Weight::from_parts(4_259_301, 0) + // Standard Error: 3_362 + .saturating_add(Weight::from_parts(194_546, 0).saturating_mul(t.into())) + // Standard Error: 34 + .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) } /// The range of component `i` is `[0, 262144]`. fn seal_debug_message(i: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 362_000 picoseconds. - Weight::from_parts(68_341, 0) + // Minimum execution time: 340_000 picoseconds. + Weight::from_parts(306_527, 0) // Standard Error: 1 - .saturating_add(Weight::from_parts(762, 0).saturating_mul(i.into())) + .saturating_add(Weight::from_parts(728, 0).saturating_mul(i.into())) } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -1528,8 +1555,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 7_649_000 picoseconds. - Weight::from_parts(7_908_000, 680) + // Minimum execution time: 10_747_000 picoseconds. + Weight::from_parts(11_276_000, 680) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1538,8 +1565,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 42_646_000 picoseconds. - Weight::from_parts(44_107_000, 10690) + // Minimum execution time: 42_076_000 picoseconds. + Weight::from_parts(43_381_000, 10690) .saturating_add(RocksDbWeight::get().reads(1_u64)) } /// Storage: `Skipped::Metadata` (r:0 w:0) @@ -1548,8 +1575,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `680` // Estimated: `680` - // Minimum execution time: 8_978_000 picoseconds. - Weight::from_parts(9_343_000, 680) + // Minimum execution time: 11_703_000 picoseconds. + Weight::from_parts(12_308_000, 680) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1559,8 +1586,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `10690` // Estimated: `10690` - // Minimum execution time: 44_678_000 picoseconds. - Weight::from_parts(46_166_000, 10690) + // Minimum execution time: 43_460_000 picoseconds. + Weight::from_parts(45_165_000, 10690) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1572,12 +1599,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + o * (1 ±0)` // Estimated: `247 + o * (1 ±0)` - // Minimum execution time: 9_216_000 picoseconds. - Weight::from_parts(9_774_592, 247) - // Standard Error: 51 - .saturating_add(Weight::from_parts(532, 0).saturating_mul(n.into())) - // Standard Error: 51 - .saturating_add(Weight::from_parts(504, 0).saturating_mul(o.into())) + // Minimum execution time: 9_087_000 picoseconds. + Weight::from_parts(11_787_486, 247) + // Standard Error: 179 + .saturating_add(Weight::from_parts(976, 0).saturating_mul(n.into())) + // Standard Error: 179 + .saturating_add(Weight::from_parts(3_151, 0).saturating_mul(o.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(o.into())) @@ -1589,10 +1616,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_800_000 picoseconds. - Weight::from_parts(9_758_732, 247) - // Standard Error: 70 - .saturating_add(Weight::from_parts(387, 0).saturating_mul(n.into())) + // Minimum execution time: 8_611_000 picoseconds. + Weight::from_parts(11_791_390, 247) + // Standard Error: 308 + .saturating_add(Weight::from_parts(3_943, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1604,10 +1631,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_502_000 picoseconds. - Weight::from_parts(9_415_872, 247) - // Standard Error: 72 - .saturating_add(Weight::from_parts(1_304, 0).saturating_mul(n.into())) + // Minimum execution time: 8_389_000 picoseconds. + Weight::from_parts(11_625_480, 247) + // Standard Error: 315 + .saturating_add(Weight::from_parts(4_487, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1618,10 +1645,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 8_003_000 picoseconds. - Weight::from_parts(8_757_027, 247) - // Standard Error: 64 - .saturating_add(Weight::from_parts(508, 0).saturating_mul(n.into())) + // Minimum execution time: 7_947_000 picoseconds. + Weight::from_parts(10_970_587, 247) + // Standard Error: 310 + .saturating_add(Weight::from_parts(3_675, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } @@ -1632,10 +1659,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `248 + n * (1 ±0)` // Estimated: `247 + n * (1 ±0)` - // Minimum execution time: 9_369_000 picoseconds. - Weight::from_parts(10_394_508, 247) - // Standard Error: 70 - .saturating_add(Weight::from_parts(1_404, 0).saturating_mul(n.into())) + // Minimum execution time: 9_071_000 picoseconds. + Weight::from_parts(12_525_027, 247) + // Standard Error: 328 + .saturating_add(Weight::from_parts(4_427, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) @@ -1644,36 +1671,36 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_457_000 picoseconds. - Weight::from_parts(1_595_000, 0) + // Minimum execution time: 1_487_000 picoseconds. + Weight::from_parts(1_611_000, 0) } fn set_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_894_000 picoseconds. - Weight::from_parts(2_062_000, 0) + // Minimum execution time: 1_852_000 picoseconds. + Weight::from_parts(1_982_000, 0) } fn get_transient_storage_empty() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_535_000 picoseconds. - Weight::from_parts(1_586_000, 0) + // Minimum execution time: 1_467_000 picoseconds. + Weight::from_parts(1_529_000, 0) } fn get_transient_storage_full() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_706_000 picoseconds. - Weight::from_parts(1_850_000, 0) + // Minimum execution time: 1_630_000 picoseconds. + Weight::from_parts(1_712_000, 0) } fn rollback_transient_storage() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_198_000 picoseconds. - Weight::from_parts(1_325_000, 0) + // Minimum execution time: 1_188_000 picoseconds. + Weight::from_parts(1_268_000, 0) } /// The range of component `n` is `[0, 448]`. /// The range of component `o` is `[0, 448]`. @@ -1681,50 +1708,52 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_324_000 picoseconds. - Weight::from_parts(2_397_504, 0) - // Standard Error: 16 - .saturating_add(Weight::from_parts(379, 0).saturating_mul(n.into())) - // Standard Error: 16 - .saturating_add(Weight::from_parts(524, 0).saturating_mul(o.into())) + // Minimum execution time: 2_197_000 picoseconds. + Weight::from_parts(2_464_654, 0) + // Standard Error: 17 + .saturating_add(Weight::from_parts(296, 0).saturating_mul(n.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(342, 0).saturating_mul(o.into())) } /// The range of component `n` is `[0, 448]`. fn seal_clear_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_072_000 picoseconds. - Weight::from_parts(2_408_702, 0) + // Minimum execution time: 2_005_000 picoseconds. + Weight::from_parts(2_381_053, 0) // Standard Error: 23 - .saturating_add(Weight::from_parts(432, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(322, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_get_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_930_000 picoseconds. - Weight::from_parts(2_120_317, 0) - // Standard Error: 18 - .saturating_add(Weight::from_parts(391, 0).saturating_mul(n.into())) + // Minimum execution time: 1_853_000 picoseconds. + Weight::from_parts(2_082_772, 0) + // Standard Error: 20 + .saturating_add(Weight::from_parts(322, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. fn seal_contains_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_755_000 picoseconds. - Weight::from_parts(1_968_623, 0) - // Standard Error: 14 - .saturating_add(Weight::from_parts(191, 0).saturating_mul(n.into())) + // Minimum execution time: 1_711_000 picoseconds. + Weight::from_parts(1_899_649, 0) + // Standard Error: 16 + .saturating_add(Weight::from_parts(208, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 448]`. - fn seal_take_transient_storage(_n: u32, ) -> Weight { + fn seal_take_transient_storage(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_567_000 picoseconds. - Weight::from_parts(2_841_579, 0) + // Minimum execution time: 2_460_000 picoseconds. + Weight::from_parts(2_684_364, 0) + // Standard Error: 22 + .saturating_add(Weight::from_parts(56, 0).saturating_mul(n.into())) } /// Storage: `Revive::AddressSuffix` (r:1 w:0) /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) @@ -1738,18 +1767,20 @@ impl WeightInfo for () { /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `Measured`) /// The range of component `t` is `[0, 1]`. /// The range of component `i` is `[0, 262144]`. - fn seal_call(t: u32, _i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `1292 + t * (314 ±0)` - // Estimated: `4757 + t * (2535 ±0)` - // Minimum execution time: 40_925_000 picoseconds. - Weight::from_parts(42_866_040, 4757) - // Standard Error: 99_028 - .saturating_add(Weight::from_parts(2_467_746, 0).saturating_mul(t.into())) + fn seal_call(t: u32, i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1292 + t * (203 ±0)` + // Estimated: `4757 + t * (2480 ±0)` + // Minimum execution time: 40_031_000 picoseconds. + Weight::from_parts(41_527_691, 4757) + // Standard Error: 50_351 + .saturating_add(Weight::from_parts(1_112_950, 0).saturating_mul(t.into())) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) .saturating_add(RocksDbWeight::get().writes(1_u64)) - .saturating_add(Weight::from_parts(0, 2535).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 2480).saturating_mul(t.into())) } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) @@ -1761,8 +1792,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `1237` // Estimated: `4702` - // Minimum execution time: 36_048_000 picoseconds. - Weight::from_parts(37_921_000, 4702) + // Minimum execution time: 35_759_000 picoseconds. + Weight::from_parts(37_086_000, 4702) .saturating_add(RocksDbWeight::get().reads(3_u64)) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) @@ -1776,12 +1807,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 262144]`. fn seal_instantiate(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1331` - // Estimated: `4796` - // Minimum execution time: 121_507_000 picoseconds. - Weight::from_parts(115_250_696, 4796) - // Standard Error: 10 - .saturating_add(Weight::from_parts(4_136, 0).saturating_mul(i.into())) + // Measured: `1271` + // Estimated: `4710` + // Minimum execution time: 116_485_000 picoseconds. + Weight::from_parts(108_907_717, 4710) + // Standard Error: 12 + .saturating_add(Weight::from_parts(4_125, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1790,64 +1821,64 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 649_000 picoseconds. - Weight::from_parts(3_208_110, 0) + // Minimum execution time: 651_000 picoseconds. + Weight::from_parts(3_867_609, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_439, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_384, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_keccak_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_078_000 picoseconds. - Weight::from_parts(3_361_333, 0) - // Standard Error: 4 - .saturating_add(Weight::from_parts(3_648, 0).saturating_mul(n.into())) + // Minimum execution time: 1_090_000 picoseconds. + Weight::from_parts(5_338_460, 0) + // Standard Error: 3 + .saturating_add(Weight::from_parts(3_601, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_256(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 673_000 picoseconds. - Weight::from_parts(3_115_184, 0) + // Minimum execution time: 717_000 picoseconds. + Weight::from_parts(2_629_461, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_557, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_528, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 262144]`. fn seal_hash_blake2_128(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 685_000 picoseconds. - Weight::from_parts(3_752_567, 0) + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(4_807_814, 0) // Standard Error: 3 - .saturating_add(Weight::from_parts(1_549, 0).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(1_509, 0).saturating_mul(n.into())) } /// The range of component `n` is `[0, 261889]`. fn seal_sr25519_verify(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 42_901_000 picoseconds. - Weight::from_parts(30_989_396, 0) - // Standard Error: 11 - .saturating_add(Weight::from_parts(5_414, 0).saturating_mul(n.into())) + // Minimum execution time: 42_829_000 picoseconds. + Weight::from_parts(24_650_992, 0) + // Standard Error: 14 + .saturating_add(Weight::from_parts(5_212, 0).saturating_mul(n.into())) } fn seal_ecdsa_recover() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 47_249_000 picoseconds. - Weight::from_parts(48_530_000, 0) + // Minimum execution time: 46_902_000 picoseconds. + Weight::from_parts(48_072_000, 0) } fn seal_ecdsa_to_eth_address() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_873_000 picoseconds. - Weight::from_parts(13_127_000, 0) + // Minimum execution time: 12_713_000 picoseconds. + Weight::from_parts(12_847_000, 0) } /// Storage: `Revive::CodeInfoOf` (r:1 w:1) /// Proof: `Revive::CodeInfoOf` (`max_values`: None, `max_size`: Some(96), added: 2571, mode: `Measured`) @@ -1855,8 +1886,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `300` // Estimated: `3765` - // Minimum execution time: 18_436_000 picoseconds. - Weight::from_parts(19_107_000, 3765) + // Minimum execution time: 17_657_000 picoseconds. + Weight::from_parts(18_419_000, 3765) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1866,8 +1897,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3803` - // Minimum execution time: 13_894_000 picoseconds. - Weight::from_parts(14_355_000, 3803) + // Minimum execution time: 13_650_000 picoseconds. + Weight::from_parts(14_209_000, 3803) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1877,8 +1908,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `338` // Estimated: `3561` - // Minimum execution time: 12_516_000 picoseconds. - Weight::from_parts(13_223_000, 3561) + // Minimum execution time: 12_341_000 picoseconds. + Weight::from_parts(13_011_000, 3561) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1887,9 +1918,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 9_177_000 picoseconds. - Weight::from_parts(11_420_562, 0) - // Standard Error: 99 - .saturating_add(Weight::from_parts(72_860, 0).saturating_mul(r.into())) + // Minimum execution time: 8_899_000 picoseconds. + Weight::from_parts(10_489_171, 0) + // Standard Error: 104 + .saturating_add(Weight::from_parts(73_814, 0).saturating_mul(r.into())) } } diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 86ccb623a1d9..eced4843b552 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -75,6 +75,10 @@ pub trait HostFn: private::Sealed { /// [GASPRICE](https://www.evm.codes/?fork=cancun#3a) opcode. fn gas_price() -> u64; + /// Returns the base fee, akin to the EVM + /// [BASEFEE](https://www.evm.codes/?fork=cancun#48) opcode. + fn base_fee(output: &mut [u8; 32]); + /// Returns the call data size. fn call_data_size() -> u64; diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 045ebf0fbf75..6fdda86892d5 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -94,6 +94,7 @@ mod sys { data_len: u32, ); pub fn gas_price() -> u64; + pub fn base_fee(out_ptr: *mut u8); pub fn call_data_size() -> u64; pub fn block_number(out_ptr: *mut u8); pub fn block_hash(block_number_ptr: *const u8, out_ptr: *mut u8); @@ -374,6 +375,10 @@ impl HostFn for HostFnImpl { unsafe { sys::gas_price() } } + fn base_fee(output: &mut [u8; 32]) { + unsafe { sys::base_fee(output.as_mut_ptr()) } + } + fn balance(output: &mut [u8; 32]) { unsafe { sys::balance(output.as_mut_ptr()) } } From e964644163578df4b7c4092849f03df7b816539a Mon Sep 17 00:00:00 2001 From: Egor_P Date: Thu, 19 Dec 2024 17:48:18 +0100 Subject: [PATCH 198/340] [Backport] Version bumps and `prdocs` reordering form 2412 (#6928) This PR includes backport of the regular version bumps and `prdocs` reordering from the `stable2412` branch back ro master --------- Co-authored-by: ParityReleases Co-authored-by: command-bot <> --- .github/workflows/publish-check-compile.yml | 2 +- .../assets/asset-hub-rococo/src/lib.rs | 2 +- .../assets/asset-hub-westend/src/lib.rs | 2 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 2 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 2 +- .../collectives-westend/src/lib.rs | 2 +- .../contracts/contracts-rococo/src/lib.rs | 2 +- .../coretime/coretime-rococo/src/lib.rs | 2 +- .../coretime/coretime-westend/src/lib.rs | 2 +- .../glutton/glutton-westend/src/lib.rs | 2 +- .../runtimes/people/people-rococo/src/lib.rs | 2 +- .../runtimes/people/people-westend/src/lib.rs | 4 +-- polkadot/node/primitives/src/lib.rs | 2 +- polkadot/runtime/rococo/src/lib.rs | 2 +- polkadot/runtime/westend/src/lib.rs | 4 +-- prdoc/pr_6928.prdoc | 34 +++++++++++++++++++ prdoc/{ => stable2412}/pr_3151.prdoc | 0 prdoc/{ => stable2412}/pr_3685.prdoc | 0 prdoc/{ => stable2412}/pr_3881.prdoc | 0 prdoc/{ => stable2412}/pr_3970.prdoc | 0 prdoc/{ => stable2412}/pr_4012.prdoc | 0 prdoc/{ => stable2412}/pr_4251.prdoc | 0 prdoc/{ => stable2412}/pr_4257.prdoc | 0 prdoc/{ => stable2412}/pr_4639.prdoc | 0 prdoc/{ => stable2412}/pr_4826.prdoc | 0 prdoc/{ => stable2412}/pr_4834.prdoc | 0 prdoc/{ => stable2412}/pr_4837.prdoc | 0 prdoc/{ => stable2412}/pr_4846.prdoc | 0 prdoc/{ => stable2412}/pr_4849.prdoc | 0 prdoc/{ => stable2412}/pr_4851.prdoc | 0 prdoc/{ => stable2412}/pr_4889.prdoc | 0 prdoc/{ => stable2412}/pr_4974.prdoc | 0 prdoc/{ => stable2412}/pr_4982.prdoc | 0 prdoc/{ => stable2412}/pr_5038.prdoc | 0 prdoc/{ => stable2412}/pr_5194.prdoc | 0 prdoc/{ => stable2412}/pr_5198.prdoc | 0 prdoc/{ => stable2412}/pr_5201.prdoc | 0 prdoc/{ => stable2412}/pr_5274.prdoc | 0 prdoc/{ => stable2412}/pr_5311.prdoc | 0 prdoc/{ => stable2412}/pr_5322.prdoc | 0 prdoc/{ => stable2412}/pr_5343.prdoc | 0 prdoc/{ => stable2412}/pr_5372.prdoc | 0 prdoc/{ => stable2412}/pr_5390.prdoc | 0 prdoc/{ => stable2412}/pr_5420.prdoc | 0 prdoc/{ => stable2412}/pr_5423.prdoc | 0 prdoc/{ => stable2412}/pr_5435.prdoc | 0 prdoc/{ => stable2412}/pr_5461.prdoc | 0 prdoc/{ => stable2412}/pr_5469.prdoc | 0 prdoc/{ => stable2412}/pr_5502.prdoc | 0 prdoc/{ => stable2412}/pr_5515.prdoc | 0 prdoc/{ => stable2412}/pr_5521.prdoc | 0 prdoc/{ => stable2412}/pr_5526.prdoc | 0 prdoc/{ => stable2412}/pr_5540.prdoc | 0 prdoc/{ => stable2412}/pr_5548.prdoc | 0 prdoc/{ => stable2412}/pr_5554.prdoc | 0 prdoc/{ => stable2412}/pr_5555.prdoc | 0 prdoc/{ => stable2412}/pr_5556.prdoc | 0 prdoc/{ => stable2412}/pr_5572.prdoc | 0 prdoc/{ => stable2412}/pr_5585.prdoc | 0 prdoc/{ => stable2412}/pr_5592.prdoc | 0 prdoc/{ => stable2412}/pr_5601.prdoc | 0 prdoc/{ => stable2412}/pr_5606.prdoc | 0 prdoc/{ => stable2412}/pr_5608.prdoc | 0 prdoc/{ => stable2412}/pr_5609.prdoc | 0 prdoc/{ => stable2412}/pr_5616.prdoc | 0 prdoc/{ => stable2412}/pr_5623.prdoc | 0 prdoc/{ => stable2412}/pr_5630.prdoc | 0 prdoc/{ => stable2412}/pr_5635.prdoc | 0 prdoc/{ => stable2412}/pr_5640.prdoc | 0 prdoc/{ => stable2412}/pr_5664.prdoc | 0 prdoc/{ => stable2412}/pr_5665.prdoc | 0 prdoc/{ => stable2412}/pr_5666.prdoc | 0 prdoc/{ => stable2412}/pr_5675.prdoc | 0 prdoc/{ => stable2412}/pr_5676.prdoc | 0 prdoc/{ => stable2412}/pr_5679.prdoc | 0 prdoc/{ => stable2412}/pr_5682.prdoc | 0 prdoc/{ => stable2412}/pr_5684.prdoc | 0 prdoc/{ => stable2412}/pr_5686.prdoc | 0 prdoc/{ => stable2412}/pr_5687.prdoc | 0 prdoc/{ => stable2412}/pr_5693.prdoc | 0 prdoc/{ => stable2412}/pr_5701.prdoc | 0 prdoc/{ => stable2412}/pr_5707.prdoc | 0 prdoc/{ => stable2412}/pr_5716.prdoc | 0 prdoc/{ => stable2412}/pr_5726.prdoc | 0 prdoc/{ => stable2412}/pr_5732.prdoc | 0 prdoc/{ => stable2412}/pr_5737.prdoc | 0 prdoc/{ => stable2412}/pr_5741.prdoc | 0 prdoc/{ => stable2412}/pr_5743.prdoc | 0 prdoc/{ => stable2412}/pr_5745.prdoc | 0 prdoc/{ => stable2412}/pr_5756.prdoc | 0 prdoc/{ => stable2412}/pr_5762.prdoc | 0 prdoc/{ => stable2412}/pr_5765.prdoc | 0 prdoc/{ => stable2412}/pr_5768.prdoc | 0 prdoc/{ => stable2412}/pr_5774.prdoc | 0 prdoc/{ => stable2412}/pr_5779.prdoc | 0 prdoc/{ => stable2412}/pr_5787.prdoc | 0 prdoc/{ => stable2412}/pr_5789.prdoc | 0 prdoc/{ => stable2412}/pr_5796.prdoc | 0 prdoc/{ => stable2412}/pr_5804.prdoc | 0 prdoc/{ => stable2412}/pr_5807.prdoc | 0 prdoc/{ => stable2412}/pr_5811.prdoc | 0 prdoc/{ => stable2412}/pr_5813.prdoc | 0 prdoc/{ => stable2412}/pr_5824.prdoc | 0 prdoc/{ => stable2412}/pr_5830.prdoc | 0 prdoc/{ => stable2412}/pr_5838.prdoc | 0 prdoc/{ => stable2412}/pr_5839.prdoc | 0 prdoc/{ => stable2412}/pr_5845.prdoc | 0 prdoc/{ => stable2412}/pr_5847.prdoc | 0 prdoc/{ => stable2412}/pr_5856.prdoc | 0 prdoc/{ => stable2412}/pr_5857.prdoc | 0 prdoc/{ => stable2412}/pr_5859.prdoc | 0 prdoc/{ => stable2412}/pr_5861.prdoc | 0 prdoc/{ => stable2412}/pr_5866.prdoc | 0 prdoc/{ => stable2412}/pr_5872.prdoc | 0 prdoc/{ => stable2412}/pr_5875.prdoc | 0 prdoc/{ => stable2412}/pr_5876.prdoc | 0 prdoc/{ => stable2412}/pr_5880.prdoc | 0 prdoc/{ => stable2412}/pr_5883.prdoc | 0 prdoc/{ => stable2412}/pr_5886.prdoc | 0 prdoc/{ => stable2412}/pr_5888.prdoc | 0 prdoc/{ => stable2412}/pr_5891.prdoc | 0 prdoc/{ => stable2412}/pr_5892.prdoc | 0 prdoc/{ => stable2412}/pr_5901.prdoc | 0 prdoc/{ => stable2412}/pr_5908.prdoc | 0 prdoc/{ => stable2412}/pr_5911.prdoc | 0 prdoc/{ => stable2412}/pr_5915.prdoc | 0 prdoc/{ => stable2412}/pr_5917.prdoc | 0 prdoc/{ => stable2412}/pr_5919.prdoc | 0 prdoc/{ => stable2412}/pr_5924.prdoc | 0 prdoc/{ => stable2412}/pr_5939.prdoc | 0 prdoc/{ => stable2412}/pr_5941.prdoc | 0 prdoc/{ => stable2412}/pr_5946.prdoc | 0 prdoc/{ => stable2412}/pr_5954.prdoc | 0 prdoc/{ => stable2412}/pr_5961.prdoc | 0 prdoc/{ => stable2412}/pr_5971.prdoc | 0 prdoc/{ => stable2412}/pr_5984.prdoc | 0 prdoc/{ => stable2412}/pr_5994.prdoc | 0 prdoc/{ => stable2412}/pr_5995.prdoc | 0 prdoc/{ => stable2412}/pr_5997.prdoc | 0 prdoc/{ => stable2412}/pr_5998.prdoc | 0 prdoc/{ => stable2412}/pr_5999.prdoc | 0 prdoc/{ => stable2412}/pr_6011.prdoc | 0 prdoc/{ => stable2412}/pr_6015.prdoc | 0 prdoc/{ => stable2412}/pr_6016.prdoc | 0 prdoc/{ => stable2412}/pr_6022.prdoc | 0 prdoc/{ => stable2412}/pr_6023.prdoc | 0 prdoc/{ => stable2412}/pr_6025.prdoc | 0 prdoc/{ => stable2412}/pr_6027.prdoc | 0 prdoc/{ => stable2412}/pr_6032.prdoc | 0 prdoc/{ => stable2412}/pr_6039.prdoc | 0 prdoc/{ => stable2412}/pr_6045.prdoc | 0 prdoc/{ => stable2412}/pr_6058.prdoc | 0 prdoc/{ => stable2412}/pr_6061.prdoc | 0 prdoc/{ => stable2412}/pr_6073.prdoc | 0 prdoc/{ => stable2412}/pr_6077.prdoc | 0 prdoc/{ => stable2412}/pr_6080.prdoc | 0 prdoc/{ => stable2412}/pr_6087.prdoc | 0 prdoc/{ => stable2412}/pr_6088.prdoc | 0 prdoc/{ => stable2412}/pr_6094.prdoc | 0 prdoc/{ => stable2412}/pr_6096.prdoc | 0 prdoc/{ => stable2412}/pr_6104.prdoc | 0 prdoc/{ => stable2412}/pr_6105.prdoc | 0 prdoc/{ => stable2412}/pr_6129.prdoc | 0 prdoc/{ => stable2412}/pr_6141.prdoc | 0 prdoc/{ => stable2412}/pr_6147.prdoc | 0 prdoc/{ => stable2412}/pr_6148.prdoc | 0 prdoc/{ => stable2412}/pr_6156.prdoc | 0 prdoc/{ => stable2412}/pr_6169.prdoc | 0 prdoc/{ => stable2412}/pr_6171.prdoc | 0 prdoc/{ => stable2412}/pr_6174.prdoc | 0 prdoc/{ => stable2412}/pr_6187.prdoc | 0 prdoc/{ => stable2412}/pr_6192.prdoc | 0 prdoc/{ => stable2412}/pr_6205.prdoc | 0 prdoc/{ => stable2412}/pr_6212.prdoc | 0 prdoc/{ => stable2412}/pr_6214.prdoc | 0 prdoc/{ => stable2412}/pr_6217.prdoc | 0 prdoc/{ => stable2412}/pr_6218.prdoc | 0 prdoc/{ => stable2412}/pr_6221.prdoc | 0 prdoc/{ => stable2412}/pr_6228.prdoc | 0 prdoc/{ => stable2412}/pr_6246.prdoc | 0 prdoc/{ => stable2412}/pr_6255.prdoc | 0 prdoc/{ => stable2412}/pr_6257.prdoc | 0 prdoc/{ => stable2412}/pr_6260.prdoc | 0 prdoc/{ => stable2412}/pr_6261.prdoc | 0 prdoc/{ => stable2412}/pr_6263.prdoc | 0 prdoc/{ => stable2412}/pr_6264.prdoc | 0 prdoc/{ => stable2412}/pr_6268.prdoc | 0 prdoc/{ => stable2412}/pr_6278.prdoc | 0 prdoc/{ => stable2412}/pr_6288.prdoc | 0 prdoc/{ => stable2412}/pr_6291.prdoc | 0 prdoc/{ => stable2412}/pr_6295.prdoc | 0 prdoc/{ => stable2412}/pr_6296.prdoc | 0 prdoc/{ => stable2412}/pr_6298.prdoc | 0 prdoc/{ => stable2412}/pr_6299.prdoc | 0 prdoc/{ => stable2412}/pr_6304.prdoc | 0 prdoc/{ => stable2412}/pr_6305.prdoc | 0 prdoc/{ => stable2412}/pr_6314.prdoc | 0 prdoc/{ => stable2412}/pr_6315.prdoc | 0 prdoc/{ => stable2412}/pr_6316.prdoc | 0 prdoc/{ => stable2412}/pr_6317.prdoc | 0 prdoc/{ => stable2412}/pr_6318.prdoc | 0 prdoc/{ => stable2412}/pr_6323.prdoc | 0 prdoc/{ => stable2412}/pr_6337.prdoc | 0 prdoc/{ => stable2412}/pr_6353.prdoc | 0 prdoc/{ => stable2412}/pr_6357.prdoc | 0 prdoc/{ => stable2412}/pr_6360.prdoc | 0 prdoc/{ => stable2412}/pr_6365.prdoc | 0 prdoc/{ => stable2412}/pr_6373.prdoc | 0 prdoc/{ => stable2412}/pr_6380.prdoc | 0 prdoc/{ => stable2412}/pr_6382.prdoc | 0 prdoc/{ => stable2412}/pr_6384.prdoc | 0 prdoc/{ => stable2412}/pr_6406.prdoc | 0 prdoc/{ => stable2412}/pr_6418.prdoc | 0 prdoc/{ => stable2412}/pr_6454.prdoc | 0 prdoc/{ => stable2412}/pr_6484.prdoc | 0 prdoc/{ => stable2412}/pr_6505.prdoc | 0 prdoc/{ => stable2412}/pr_6536.prdoc | 0 prdoc/{ => stable2412}/pr_6566.prdoc | 0 prdoc/{ => stable2412}/pr_6588.prdoc | 0 prdoc/{ => stable2412}/pr_6603.prdoc | 0 prdoc/{ => stable2412}/pr_6643.prdoc | 0 prdoc/{ => stable2412}/pr_6645.prdoc | 0 prdoc/{ => stable2412}/pr_6646.prdoc | 0 prdoc/{ => stable2412}/pr_6652.prdoc | 0 prdoc/{ => stable2412}/pr_6677.prdoc | 0 prdoc/{ => stable2412}/pr_6690.prdoc | 0 prdoc/{ => stable2412}/pr_6696.prdoc | 0 prdoc/{ => stable2412}/pr_6729.prdoc | 0 prdoc/{ => stable2412}/pr_6742.prdoc | 0 prdoc/{ => stable2412}/pr_6760.prdoc | 0 prdoc/{ => stable2412}/pr_6781.prdoc | 0 prdoc/{ => stable2412}/pr_6814.prdoc | 0 prdoc/{ => stable2412}/pr_6860.prdoc | 0 prdoc/{ => stable2412}/pr_6863.prdoc | 0 prdoc/{ => stable2412}/pr_6864.prdoc | 0 prdoc/{ => stable2412}/pr_6885.prdoc | 0 236 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 prdoc/pr_6928.prdoc rename prdoc/{ => stable2412}/pr_3151.prdoc (100%) rename prdoc/{ => stable2412}/pr_3685.prdoc (100%) rename prdoc/{ => stable2412}/pr_3881.prdoc (100%) rename prdoc/{ => stable2412}/pr_3970.prdoc (100%) rename prdoc/{ => stable2412}/pr_4012.prdoc (100%) rename prdoc/{ => stable2412}/pr_4251.prdoc (100%) rename prdoc/{ => stable2412}/pr_4257.prdoc (100%) rename prdoc/{ => stable2412}/pr_4639.prdoc (100%) rename prdoc/{ => stable2412}/pr_4826.prdoc (100%) rename prdoc/{ => stable2412}/pr_4834.prdoc (100%) rename prdoc/{ => stable2412}/pr_4837.prdoc (100%) rename prdoc/{ => stable2412}/pr_4846.prdoc (100%) rename prdoc/{ => stable2412}/pr_4849.prdoc (100%) rename prdoc/{ => stable2412}/pr_4851.prdoc (100%) rename prdoc/{ => stable2412}/pr_4889.prdoc (100%) rename prdoc/{ => stable2412}/pr_4974.prdoc (100%) rename prdoc/{ => stable2412}/pr_4982.prdoc (100%) rename prdoc/{ => stable2412}/pr_5038.prdoc (100%) rename prdoc/{ => stable2412}/pr_5194.prdoc (100%) rename prdoc/{ => stable2412}/pr_5198.prdoc (100%) rename prdoc/{ => stable2412}/pr_5201.prdoc (100%) rename prdoc/{ => stable2412}/pr_5274.prdoc (100%) rename prdoc/{ => stable2412}/pr_5311.prdoc (100%) rename prdoc/{ => stable2412}/pr_5322.prdoc (100%) rename prdoc/{ => stable2412}/pr_5343.prdoc (100%) rename prdoc/{ => stable2412}/pr_5372.prdoc (100%) rename prdoc/{ => stable2412}/pr_5390.prdoc (100%) rename prdoc/{ => stable2412}/pr_5420.prdoc (100%) rename prdoc/{ => stable2412}/pr_5423.prdoc (100%) rename prdoc/{ => stable2412}/pr_5435.prdoc (100%) rename prdoc/{ => stable2412}/pr_5461.prdoc (100%) rename prdoc/{ => stable2412}/pr_5469.prdoc (100%) rename prdoc/{ => stable2412}/pr_5502.prdoc (100%) rename prdoc/{ => stable2412}/pr_5515.prdoc (100%) rename prdoc/{ => stable2412}/pr_5521.prdoc (100%) rename prdoc/{ => stable2412}/pr_5526.prdoc (100%) rename prdoc/{ => stable2412}/pr_5540.prdoc (100%) rename prdoc/{ => stable2412}/pr_5548.prdoc (100%) rename prdoc/{ => stable2412}/pr_5554.prdoc (100%) rename prdoc/{ => stable2412}/pr_5555.prdoc (100%) rename prdoc/{ => stable2412}/pr_5556.prdoc (100%) rename prdoc/{ => stable2412}/pr_5572.prdoc (100%) rename prdoc/{ => stable2412}/pr_5585.prdoc (100%) rename prdoc/{ => stable2412}/pr_5592.prdoc (100%) rename prdoc/{ => stable2412}/pr_5601.prdoc (100%) rename prdoc/{ => stable2412}/pr_5606.prdoc (100%) rename prdoc/{ => stable2412}/pr_5608.prdoc (100%) rename prdoc/{ => stable2412}/pr_5609.prdoc (100%) rename prdoc/{ => stable2412}/pr_5616.prdoc (100%) rename prdoc/{ => stable2412}/pr_5623.prdoc (100%) rename prdoc/{ => stable2412}/pr_5630.prdoc (100%) rename prdoc/{ => stable2412}/pr_5635.prdoc (100%) rename prdoc/{ => stable2412}/pr_5640.prdoc (100%) rename prdoc/{ => stable2412}/pr_5664.prdoc (100%) rename prdoc/{ => stable2412}/pr_5665.prdoc (100%) rename prdoc/{ => stable2412}/pr_5666.prdoc (100%) rename prdoc/{ => stable2412}/pr_5675.prdoc (100%) rename prdoc/{ => stable2412}/pr_5676.prdoc (100%) rename prdoc/{ => stable2412}/pr_5679.prdoc (100%) rename prdoc/{ => stable2412}/pr_5682.prdoc (100%) rename prdoc/{ => stable2412}/pr_5684.prdoc (100%) rename prdoc/{ => stable2412}/pr_5686.prdoc (100%) rename prdoc/{ => stable2412}/pr_5687.prdoc (100%) rename prdoc/{ => stable2412}/pr_5693.prdoc (100%) rename prdoc/{ => stable2412}/pr_5701.prdoc (100%) rename prdoc/{ => stable2412}/pr_5707.prdoc (100%) rename prdoc/{ => stable2412}/pr_5716.prdoc (100%) rename prdoc/{ => stable2412}/pr_5726.prdoc (100%) rename prdoc/{ => stable2412}/pr_5732.prdoc (100%) rename prdoc/{ => stable2412}/pr_5737.prdoc (100%) rename prdoc/{ => stable2412}/pr_5741.prdoc (100%) rename prdoc/{ => stable2412}/pr_5743.prdoc (100%) rename prdoc/{ => stable2412}/pr_5745.prdoc (100%) rename prdoc/{ => stable2412}/pr_5756.prdoc (100%) rename prdoc/{ => stable2412}/pr_5762.prdoc (100%) rename prdoc/{ => stable2412}/pr_5765.prdoc (100%) rename prdoc/{ => stable2412}/pr_5768.prdoc (100%) rename prdoc/{ => stable2412}/pr_5774.prdoc (100%) rename prdoc/{ => stable2412}/pr_5779.prdoc (100%) rename prdoc/{ => stable2412}/pr_5787.prdoc (100%) rename prdoc/{ => stable2412}/pr_5789.prdoc (100%) rename prdoc/{ => stable2412}/pr_5796.prdoc (100%) rename prdoc/{ => stable2412}/pr_5804.prdoc (100%) rename prdoc/{ => stable2412}/pr_5807.prdoc (100%) rename prdoc/{ => stable2412}/pr_5811.prdoc (100%) rename prdoc/{ => stable2412}/pr_5813.prdoc (100%) rename prdoc/{ => stable2412}/pr_5824.prdoc (100%) rename prdoc/{ => stable2412}/pr_5830.prdoc (100%) rename prdoc/{ => stable2412}/pr_5838.prdoc (100%) rename prdoc/{ => stable2412}/pr_5839.prdoc (100%) rename prdoc/{ => stable2412}/pr_5845.prdoc (100%) rename prdoc/{ => stable2412}/pr_5847.prdoc (100%) rename prdoc/{ => stable2412}/pr_5856.prdoc (100%) rename prdoc/{ => stable2412}/pr_5857.prdoc (100%) rename prdoc/{ => stable2412}/pr_5859.prdoc (100%) rename prdoc/{ => stable2412}/pr_5861.prdoc (100%) rename prdoc/{ => stable2412}/pr_5866.prdoc (100%) rename prdoc/{ => stable2412}/pr_5872.prdoc (100%) rename prdoc/{ => stable2412}/pr_5875.prdoc (100%) rename prdoc/{ => stable2412}/pr_5876.prdoc (100%) rename prdoc/{ => stable2412}/pr_5880.prdoc (100%) rename prdoc/{ => stable2412}/pr_5883.prdoc (100%) rename prdoc/{ => stable2412}/pr_5886.prdoc (100%) rename prdoc/{ => stable2412}/pr_5888.prdoc (100%) rename prdoc/{ => stable2412}/pr_5891.prdoc (100%) rename prdoc/{ => stable2412}/pr_5892.prdoc (100%) rename prdoc/{ => stable2412}/pr_5901.prdoc (100%) rename prdoc/{ => stable2412}/pr_5908.prdoc (100%) rename prdoc/{ => stable2412}/pr_5911.prdoc (100%) rename prdoc/{ => stable2412}/pr_5915.prdoc (100%) rename prdoc/{ => stable2412}/pr_5917.prdoc (100%) rename prdoc/{ => stable2412}/pr_5919.prdoc (100%) rename prdoc/{ => stable2412}/pr_5924.prdoc (100%) rename prdoc/{ => stable2412}/pr_5939.prdoc (100%) rename prdoc/{ => stable2412}/pr_5941.prdoc (100%) rename prdoc/{ => stable2412}/pr_5946.prdoc (100%) rename prdoc/{ => stable2412}/pr_5954.prdoc (100%) rename prdoc/{ => stable2412}/pr_5961.prdoc (100%) rename prdoc/{ => stable2412}/pr_5971.prdoc (100%) rename prdoc/{ => stable2412}/pr_5984.prdoc (100%) rename prdoc/{ => stable2412}/pr_5994.prdoc (100%) rename prdoc/{ => stable2412}/pr_5995.prdoc (100%) rename prdoc/{ => stable2412}/pr_5997.prdoc (100%) rename prdoc/{ => stable2412}/pr_5998.prdoc (100%) rename prdoc/{ => stable2412}/pr_5999.prdoc (100%) rename prdoc/{ => stable2412}/pr_6011.prdoc (100%) rename prdoc/{ => stable2412}/pr_6015.prdoc (100%) rename prdoc/{ => stable2412}/pr_6016.prdoc (100%) rename prdoc/{ => stable2412}/pr_6022.prdoc (100%) rename prdoc/{ => stable2412}/pr_6023.prdoc (100%) rename prdoc/{ => stable2412}/pr_6025.prdoc (100%) rename prdoc/{ => stable2412}/pr_6027.prdoc (100%) rename prdoc/{ => stable2412}/pr_6032.prdoc (100%) rename prdoc/{ => stable2412}/pr_6039.prdoc (100%) rename prdoc/{ => stable2412}/pr_6045.prdoc (100%) rename prdoc/{ => stable2412}/pr_6058.prdoc (100%) rename prdoc/{ => stable2412}/pr_6061.prdoc (100%) rename prdoc/{ => stable2412}/pr_6073.prdoc (100%) rename prdoc/{ => stable2412}/pr_6077.prdoc (100%) rename prdoc/{ => stable2412}/pr_6080.prdoc (100%) rename prdoc/{ => stable2412}/pr_6087.prdoc (100%) rename prdoc/{ => stable2412}/pr_6088.prdoc (100%) rename prdoc/{ => stable2412}/pr_6094.prdoc (100%) rename prdoc/{ => stable2412}/pr_6096.prdoc (100%) rename prdoc/{ => stable2412}/pr_6104.prdoc (100%) rename prdoc/{ => stable2412}/pr_6105.prdoc (100%) rename prdoc/{ => stable2412}/pr_6129.prdoc (100%) rename prdoc/{ => stable2412}/pr_6141.prdoc (100%) rename prdoc/{ => stable2412}/pr_6147.prdoc (100%) rename prdoc/{ => stable2412}/pr_6148.prdoc (100%) rename prdoc/{ => stable2412}/pr_6156.prdoc (100%) rename prdoc/{ => stable2412}/pr_6169.prdoc (100%) rename prdoc/{ => stable2412}/pr_6171.prdoc (100%) rename prdoc/{ => stable2412}/pr_6174.prdoc (100%) rename prdoc/{ => stable2412}/pr_6187.prdoc (100%) rename prdoc/{ => stable2412}/pr_6192.prdoc (100%) rename prdoc/{ => stable2412}/pr_6205.prdoc (100%) rename prdoc/{ => stable2412}/pr_6212.prdoc (100%) rename prdoc/{ => stable2412}/pr_6214.prdoc (100%) rename prdoc/{ => stable2412}/pr_6217.prdoc (100%) rename prdoc/{ => stable2412}/pr_6218.prdoc (100%) rename prdoc/{ => stable2412}/pr_6221.prdoc (100%) rename prdoc/{ => stable2412}/pr_6228.prdoc (100%) rename prdoc/{ => stable2412}/pr_6246.prdoc (100%) rename prdoc/{ => stable2412}/pr_6255.prdoc (100%) rename prdoc/{ => stable2412}/pr_6257.prdoc (100%) rename prdoc/{ => stable2412}/pr_6260.prdoc (100%) rename prdoc/{ => stable2412}/pr_6261.prdoc (100%) rename prdoc/{ => stable2412}/pr_6263.prdoc (100%) rename prdoc/{ => stable2412}/pr_6264.prdoc (100%) rename prdoc/{ => stable2412}/pr_6268.prdoc (100%) rename prdoc/{ => stable2412}/pr_6278.prdoc (100%) rename prdoc/{ => stable2412}/pr_6288.prdoc (100%) rename prdoc/{ => stable2412}/pr_6291.prdoc (100%) rename prdoc/{ => stable2412}/pr_6295.prdoc (100%) rename prdoc/{ => stable2412}/pr_6296.prdoc (100%) rename prdoc/{ => stable2412}/pr_6298.prdoc (100%) rename prdoc/{ => stable2412}/pr_6299.prdoc (100%) rename prdoc/{ => stable2412}/pr_6304.prdoc (100%) rename prdoc/{ => stable2412}/pr_6305.prdoc (100%) rename prdoc/{ => stable2412}/pr_6314.prdoc (100%) rename prdoc/{ => stable2412}/pr_6315.prdoc (100%) rename prdoc/{ => stable2412}/pr_6316.prdoc (100%) rename prdoc/{ => stable2412}/pr_6317.prdoc (100%) rename prdoc/{ => stable2412}/pr_6318.prdoc (100%) rename prdoc/{ => stable2412}/pr_6323.prdoc (100%) rename prdoc/{ => stable2412}/pr_6337.prdoc (100%) rename prdoc/{ => stable2412}/pr_6353.prdoc (100%) rename prdoc/{ => stable2412}/pr_6357.prdoc (100%) rename prdoc/{ => stable2412}/pr_6360.prdoc (100%) rename prdoc/{ => stable2412}/pr_6365.prdoc (100%) rename prdoc/{ => stable2412}/pr_6373.prdoc (100%) rename prdoc/{ => stable2412}/pr_6380.prdoc (100%) rename prdoc/{ => stable2412}/pr_6382.prdoc (100%) rename prdoc/{ => stable2412}/pr_6384.prdoc (100%) rename prdoc/{ => stable2412}/pr_6406.prdoc (100%) rename prdoc/{ => stable2412}/pr_6418.prdoc (100%) rename prdoc/{ => stable2412}/pr_6454.prdoc (100%) rename prdoc/{ => stable2412}/pr_6484.prdoc (100%) rename prdoc/{ => stable2412}/pr_6505.prdoc (100%) rename prdoc/{ => stable2412}/pr_6536.prdoc (100%) rename prdoc/{ => stable2412}/pr_6566.prdoc (100%) rename prdoc/{ => stable2412}/pr_6588.prdoc (100%) rename prdoc/{ => stable2412}/pr_6603.prdoc (100%) rename prdoc/{ => stable2412}/pr_6643.prdoc (100%) rename prdoc/{ => stable2412}/pr_6645.prdoc (100%) rename prdoc/{ => stable2412}/pr_6646.prdoc (100%) rename prdoc/{ => stable2412}/pr_6652.prdoc (100%) rename prdoc/{ => stable2412}/pr_6677.prdoc (100%) rename prdoc/{ => stable2412}/pr_6690.prdoc (100%) rename prdoc/{ => stable2412}/pr_6696.prdoc (100%) rename prdoc/{ => stable2412}/pr_6729.prdoc (100%) rename prdoc/{ => stable2412}/pr_6742.prdoc (100%) rename prdoc/{ => stable2412}/pr_6760.prdoc (100%) rename prdoc/{ => stable2412}/pr_6781.prdoc (100%) rename prdoc/{ => stable2412}/pr_6814.prdoc (100%) rename prdoc/{ => stable2412}/pr_6860.prdoc (100%) rename prdoc/{ => stable2412}/pr_6863.prdoc (100%) rename prdoc/{ => stable2412}/pr_6864.prdoc (100%) rename prdoc/{ => stable2412}/pr_6885.prdoc (100%) diff --git a/.github/workflows/publish-check-compile.yml b/.github/workflows/publish-check-compile.yml index ada8635e314e..ce1b2cb231d0 100644 --- a/.github/workflows/publish-check-compile.yml +++ b/.github/workflows/publish-check-compile.yml @@ -16,7 +16,7 @@ jobs: preflight: uses: ./.github/workflows/reusable-preflight.yml - check-publish: + check-publish-compile: timeout-minutes: 90 needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 7e1fb247ad3c..dd1535826152 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -124,7 +124,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("statemine"), impl_name: alloc::borrow::Cow::Borrowed("statemine"), authoring_version: 1, - spec_version: 1_016_002, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index ffd54ce4c8ac..707d1c52f743 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -125,7 +125,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_017_002, + spec_version: 1_017_003, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index d87ff9b43fef..492b731610ce 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -240,7 +240,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("bridge-hub-rococo"), impl_name: alloc::borrow::Cow::Borrowed("bridge-hub-rococo"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index ae3dbfa06cba..edf79ea0c315 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -226,7 +226,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("bridge-hub-westend"), impl_name: alloc::borrow::Cow::Borrowed("bridge-hub-westend"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index f4c62f212e8c..5c2ba2e24c22 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -126,7 +126,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("collectives-westend"), impl_name: alloc::borrow::Cow::Borrowed("collectives-westend"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 6, diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 2951662a979b..594c9b26f57e 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -144,7 +144,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("contracts-rococo"), impl_name: alloc::borrow::Cow::Borrowed("contracts-rococo"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 7, diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index ae3ad93a9e85..e8f6e6659e13 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -150,7 +150,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("coretime-rococo"), impl_name: alloc::borrow::Cow::Borrowed("coretime-rococo"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 67f7ad7afc6b..ce965f0ad1ba 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -150,7 +150,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("coretime-westend"), impl_name: alloc::borrow::Cow::Borrowed("coretime-westend"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index fdf467ab64b8..763f8abea34a 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -102,7 +102,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("glutton-westend"), impl_name: alloc::borrow::Cow::Borrowed("glutton-westend"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index dc5f2ac0997c..b8db687da625 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -137,7 +137,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("people-rococo"), impl_name: alloc::borrow::Cow::Borrowed("people-rococo"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 3265062a0441..620ec41c071c 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -136,10 +136,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("people-westend"), impl_name: alloc::borrow::Cow::Borrowed("people-westend"), authoring_version: 1, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 1, + transaction_version: 2, system_version: 1, }; diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index 6985e86098b0..1e5ce6489bc8 100644 --- a/polkadot/node/primitives/src/lib.rs +++ b/polkadot/node/primitives/src/lib.rs @@ -59,7 +59,7 @@ pub use disputes::{ /// relatively rare. /// /// The associated worker binaries should use the same version as the node that spawns them. -pub const NODE_VERSION: &'static str = "1.16.1"; +pub const NODE_VERSION: &'static str = "1.17.0"; // For a 16-ary Merkle Prefix Trie, we can expect at most 16 32-byte hashes per node // plus some overhead: diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 2303e1212634..da4f039624a3 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -182,7 +182,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("rococo"), impl_name: alloc::borrow::Cow::Borrowed("parity-rococo-v2.0"), authoring_version: 0, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 26, diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index f9ef74fee29c..cbf2e02ce428 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -172,10 +172,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westend"), impl_name: alloc::borrow::Cow::Borrowed("parity-westend"), authoring_version: 2, - spec_version: 1_016_001, + spec_version: 1_017_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, - transaction_version: 26, + transaction_version: 27, system_version: 1, }; diff --git a/prdoc/pr_6928.prdoc b/prdoc/pr_6928.prdoc new file mode 100644 index 000000000000..4b9023ab03a6 --- /dev/null +++ b/prdoc/pr_6928.prdoc @@ -0,0 +1,34 @@ +title: '[Backport] Version bumps and `prdocs` reordering form 2412' +doc: +- audience: Runtime Dev + description: This PR includes backport of the regular version bumps and `prdocs` + reordering from the `stable2412` branch back ro master +crates: +- name: polkadot-node-primitives + bump: none +- name: asset-hub-rococo-runtime + bump: none +- name: bridge-hub-rococo-runtime + bump: none +- name: bridge-hub-westend-runtime + bump: none +- name: collectives-westend-runtime + bump: none +- name: contracts-rococo-runtime + bump: none +- name: coretime-rococo-runtime + bump: none +- name: coretime-westend-runtime + bump: none +- name: glutton-westend-runtime + bump: none +- name: people-rococo-runtime + bump: none +- name: people-westend-runtime + bump: none +- name: rococo-runtime + bump: none +- name: westend-runtime + bump: none +- name: asset-hub-westend-runtime + bump: none diff --git a/prdoc/pr_3151.prdoc b/prdoc/stable2412/pr_3151.prdoc similarity index 100% rename from prdoc/pr_3151.prdoc rename to prdoc/stable2412/pr_3151.prdoc diff --git a/prdoc/pr_3685.prdoc b/prdoc/stable2412/pr_3685.prdoc similarity index 100% rename from prdoc/pr_3685.prdoc rename to prdoc/stable2412/pr_3685.prdoc diff --git a/prdoc/pr_3881.prdoc b/prdoc/stable2412/pr_3881.prdoc similarity index 100% rename from prdoc/pr_3881.prdoc rename to prdoc/stable2412/pr_3881.prdoc diff --git a/prdoc/pr_3970.prdoc b/prdoc/stable2412/pr_3970.prdoc similarity index 100% rename from prdoc/pr_3970.prdoc rename to prdoc/stable2412/pr_3970.prdoc diff --git a/prdoc/pr_4012.prdoc b/prdoc/stable2412/pr_4012.prdoc similarity index 100% rename from prdoc/pr_4012.prdoc rename to prdoc/stable2412/pr_4012.prdoc diff --git a/prdoc/pr_4251.prdoc b/prdoc/stable2412/pr_4251.prdoc similarity index 100% rename from prdoc/pr_4251.prdoc rename to prdoc/stable2412/pr_4251.prdoc diff --git a/prdoc/pr_4257.prdoc b/prdoc/stable2412/pr_4257.prdoc similarity index 100% rename from prdoc/pr_4257.prdoc rename to prdoc/stable2412/pr_4257.prdoc diff --git a/prdoc/pr_4639.prdoc b/prdoc/stable2412/pr_4639.prdoc similarity index 100% rename from prdoc/pr_4639.prdoc rename to prdoc/stable2412/pr_4639.prdoc diff --git a/prdoc/pr_4826.prdoc b/prdoc/stable2412/pr_4826.prdoc similarity index 100% rename from prdoc/pr_4826.prdoc rename to prdoc/stable2412/pr_4826.prdoc diff --git a/prdoc/pr_4834.prdoc b/prdoc/stable2412/pr_4834.prdoc similarity index 100% rename from prdoc/pr_4834.prdoc rename to prdoc/stable2412/pr_4834.prdoc diff --git a/prdoc/pr_4837.prdoc b/prdoc/stable2412/pr_4837.prdoc similarity index 100% rename from prdoc/pr_4837.prdoc rename to prdoc/stable2412/pr_4837.prdoc diff --git a/prdoc/pr_4846.prdoc b/prdoc/stable2412/pr_4846.prdoc similarity index 100% rename from prdoc/pr_4846.prdoc rename to prdoc/stable2412/pr_4846.prdoc diff --git a/prdoc/pr_4849.prdoc b/prdoc/stable2412/pr_4849.prdoc similarity index 100% rename from prdoc/pr_4849.prdoc rename to prdoc/stable2412/pr_4849.prdoc diff --git a/prdoc/pr_4851.prdoc b/prdoc/stable2412/pr_4851.prdoc similarity index 100% rename from prdoc/pr_4851.prdoc rename to prdoc/stable2412/pr_4851.prdoc diff --git a/prdoc/pr_4889.prdoc b/prdoc/stable2412/pr_4889.prdoc similarity index 100% rename from prdoc/pr_4889.prdoc rename to prdoc/stable2412/pr_4889.prdoc diff --git a/prdoc/pr_4974.prdoc b/prdoc/stable2412/pr_4974.prdoc similarity index 100% rename from prdoc/pr_4974.prdoc rename to prdoc/stable2412/pr_4974.prdoc diff --git a/prdoc/pr_4982.prdoc b/prdoc/stable2412/pr_4982.prdoc similarity index 100% rename from prdoc/pr_4982.prdoc rename to prdoc/stable2412/pr_4982.prdoc diff --git a/prdoc/pr_5038.prdoc b/prdoc/stable2412/pr_5038.prdoc similarity index 100% rename from prdoc/pr_5038.prdoc rename to prdoc/stable2412/pr_5038.prdoc diff --git a/prdoc/pr_5194.prdoc b/prdoc/stable2412/pr_5194.prdoc similarity index 100% rename from prdoc/pr_5194.prdoc rename to prdoc/stable2412/pr_5194.prdoc diff --git a/prdoc/pr_5198.prdoc b/prdoc/stable2412/pr_5198.prdoc similarity index 100% rename from prdoc/pr_5198.prdoc rename to prdoc/stable2412/pr_5198.prdoc diff --git a/prdoc/pr_5201.prdoc b/prdoc/stable2412/pr_5201.prdoc similarity index 100% rename from prdoc/pr_5201.prdoc rename to prdoc/stable2412/pr_5201.prdoc diff --git a/prdoc/pr_5274.prdoc b/prdoc/stable2412/pr_5274.prdoc similarity index 100% rename from prdoc/pr_5274.prdoc rename to prdoc/stable2412/pr_5274.prdoc diff --git a/prdoc/pr_5311.prdoc b/prdoc/stable2412/pr_5311.prdoc similarity index 100% rename from prdoc/pr_5311.prdoc rename to prdoc/stable2412/pr_5311.prdoc diff --git a/prdoc/pr_5322.prdoc b/prdoc/stable2412/pr_5322.prdoc similarity index 100% rename from prdoc/pr_5322.prdoc rename to prdoc/stable2412/pr_5322.prdoc diff --git a/prdoc/pr_5343.prdoc b/prdoc/stable2412/pr_5343.prdoc similarity index 100% rename from prdoc/pr_5343.prdoc rename to prdoc/stable2412/pr_5343.prdoc diff --git a/prdoc/pr_5372.prdoc b/prdoc/stable2412/pr_5372.prdoc similarity index 100% rename from prdoc/pr_5372.prdoc rename to prdoc/stable2412/pr_5372.prdoc diff --git a/prdoc/pr_5390.prdoc b/prdoc/stable2412/pr_5390.prdoc similarity index 100% rename from prdoc/pr_5390.prdoc rename to prdoc/stable2412/pr_5390.prdoc diff --git a/prdoc/pr_5420.prdoc b/prdoc/stable2412/pr_5420.prdoc similarity index 100% rename from prdoc/pr_5420.prdoc rename to prdoc/stable2412/pr_5420.prdoc diff --git a/prdoc/pr_5423.prdoc b/prdoc/stable2412/pr_5423.prdoc similarity index 100% rename from prdoc/pr_5423.prdoc rename to prdoc/stable2412/pr_5423.prdoc diff --git a/prdoc/pr_5435.prdoc b/prdoc/stable2412/pr_5435.prdoc similarity index 100% rename from prdoc/pr_5435.prdoc rename to prdoc/stable2412/pr_5435.prdoc diff --git a/prdoc/pr_5461.prdoc b/prdoc/stable2412/pr_5461.prdoc similarity index 100% rename from prdoc/pr_5461.prdoc rename to prdoc/stable2412/pr_5461.prdoc diff --git a/prdoc/pr_5469.prdoc b/prdoc/stable2412/pr_5469.prdoc similarity index 100% rename from prdoc/pr_5469.prdoc rename to prdoc/stable2412/pr_5469.prdoc diff --git a/prdoc/pr_5502.prdoc b/prdoc/stable2412/pr_5502.prdoc similarity index 100% rename from prdoc/pr_5502.prdoc rename to prdoc/stable2412/pr_5502.prdoc diff --git a/prdoc/pr_5515.prdoc b/prdoc/stable2412/pr_5515.prdoc similarity index 100% rename from prdoc/pr_5515.prdoc rename to prdoc/stable2412/pr_5515.prdoc diff --git a/prdoc/pr_5521.prdoc b/prdoc/stable2412/pr_5521.prdoc similarity index 100% rename from prdoc/pr_5521.prdoc rename to prdoc/stable2412/pr_5521.prdoc diff --git a/prdoc/pr_5526.prdoc b/prdoc/stable2412/pr_5526.prdoc similarity index 100% rename from prdoc/pr_5526.prdoc rename to prdoc/stable2412/pr_5526.prdoc diff --git a/prdoc/pr_5540.prdoc b/prdoc/stable2412/pr_5540.prdoc similarity index 100% rename from prdoc/pr_5540.prdoc rename to prdoc/stable2412/pr_5540.prdoc diff --git a/prdoc/pr_5548.prdoc b/prdoc/stable2412/pr_5548.prdoc similarity index 100% rename from prdoc/pr_5548.prdoc rename to prdoc/stable2412/pr_5548.prdoc diff --git a/prdoc/pr_5554.prdoc b/prdoc/stable2412/pr_5554.prdoc similarity index 100% rename from prdoc/pr_5554.prdoc rename to prdoc/stable2412/pr_5554.prdoc diff --git a/prdoc/pr_5555.prdoc b/prdoc/stable2412/pr_5555.prdoc similarity index 100% rename from prdoc/pr_5555.prdoc rename to prdoc/stable2412/pr_5555.prdoc diff --git a/prdoc/pr_5556.prdoc b/prdoc/stable2412/pr_5556.prdoc similarity index 100% rename from prdoc/pr_5556.prdoc rename to prdoc/stable2412/pr_5556.prdoc diff --git a/prdoc/pr_5572.prdoc b/prdoc/stable2412/pr_5572.prdoc similarity index 100% rename from prdoc/pr_5572.prdoc rename to prdoc/stable2412/pr_5572.prdoc diff --git a/prdoc/pr_5585.prdoc b/prdoc/stable2412/pr_5585.prdoc similarity index 100% rename from prdoc/pr_5585.prdoc rename to prdoc/stable2412/pr_5585.prdoc diff --git a/prdoc/pr_5592.prdoc b/prdoc/stable2412/pr_5592.prdoc similarity index 100% rename from prdoc/pr_5592.prdoc rename to prdoc/stable2412/pr_5592.prdoc diff --git a/prdoc/pr_5601.prdoc b/prdoc/stable2412/pr_5601.prdoc similarity index 100% rename from prdoc/pr_5601.prdoc rename to prdoc/stable2412/pr_5601.prdoc diff --git a/prdoc/pr_5606.prdoc b/prdoc/stable2412/pr_5606.prdoc similarity index 100% rename from prdoc/pr_5606.prdoc rename to prdoc/stable2412/pr_5606.prdoc diff --git a/prdoc/pr_5608.prdoc b/prdoc/stable2412/pr_5608.prdoc similarity index 100% rename from prdoc/pr_5608.prdoc rename to prdoc/stable2412/pr_5608.prdoc diff --git a/prdoc/pr_5609.prdoc b/prdoc/stable2412/pr_5609.prdoc similarity index 100% rename from prdoc/pr_5609.prdoc rename to prdoc/stable2412/pr_5609.prdoc diff --git a/prdoc/pr_5616.prdoc b/prdoc/stable2412/pr_5616.prdoc similarity index 100% rename from prdoc/pr_5616.prdoc rename to prdoc/stable2412/pr_5616.prdoc diff --git a/prdoc/pr_5623.prdoc b/prdoc/stable2412/pr_5623.prdoc similarity index 100% rename from prdoc/pr_5623.prdoc rename to prdoc/stable2412/pr_5623.prdoc diff --git a/prdoc/pr_5630.prdoc b/prdoc/stable2412/pr_5630.prdoc similarity index 100% rename from prdoc/pr_5630.prdoc rename to prdoc/stable2412/pr_5630.prdoc diff --git a/prdoc/pr_5635.prdoc b/prdoc/stable2412/pr_5635.prdoc similarity index 100% rename from prdoc/pr_5635.prdoc rename to prdoc/stable2412/pr_5635.prdoc diff --git a/prdoc/pr_5640.prdoc b/prdoc/stable2412/pr_5640.prdoc similarity index 100% rename from prdoc/pr_5640.prdoc rename to prdoc/stable2412/pr_5640.prdoc diff --git a/prdoc/pr_5664.prdoc b/prdoc/stable2412/pr_5664.prdoc similarity index 100% rename from prdoc/pr_5664.prdoc rename to prdoc/stable2412/pr_5664.prdoc diff --git a/prdoc/pr_5665.prdoc b/prdoc/stable2412/pr_5665.prdoc similarity index 100% rename from prdoc/pr_5665.prdoc rename to prdoc/stable2412/pr_5665.prdoc diff --git a/prdoc/pr_5666.prdoc b/prdoc/stable2412/pr_5666.prdoc similarity index 100% rename from prdoc/pr_5666.prdoc rename to prdoc/stable2412/pr_5666.prdoc diff --git a/prdoc/pr_5675.prdoc b/prdoc/stable2412/pr_5675.prdoc similarity index 100% rename from prdoc/pr_5675.prdoc rename to prdoc/stable2412/pr_5675.prdoc diff --git a/prdoc/pr_5676.prdoc b/prdoc/stable2412/pr_5676.prdoc similarity index 100% rename from prdoc/pr_5676.prdoc rename to prdoc/stable2412/pr_5676.prdoc diff --git a/prdoc/pr_5679.prdoc b/prdoc/stable2412/pr_5679.prdoc similarity index 100% rename from prdoc/pr_5679.prdoc rename to prdoc/stable2412/pr_5679.prdoc diff --git a/prdoc/pr_5682.prdoc b/prdoc/stable2412/pr_5682.prdoc similarity index 100% rename from prdoc/pr_5682.prdoc rename to prdoc/stable2412/pr_5682.prdoc diff --git a/prdoc/pr_5684.prdoc b/prdoc/stable2412/pr_5684.prdoc similarity index 100% rename from prdoc/pr_5684.prdoc rename to prdoc/stable2412/pr_5684.prdoc diff --git a/prdoc/pr_5686.prdoc b/prdoc/stable2412/pr_5686.prdoc similarity index 100% rename from prdoc/pr_5686.prdoc rename to prdoc/stable2412/pr_5686.prdoc diff --git a/prdoc/pr_5687.prdoc b/prdoc/stable2412/pr_5687.prdoc similarity index 100% rename from prdoc/pr_5687.prdoc rename to prdoc/stable2412/pr_5687.prdoc diff --git a/prdoc/pr_5693.prdoc b/prdoc/stable2412/pr_5693.prdoc similarity index 100% rename from prdoc/pr_5693.prdoc rename to prdoc/stable2412/pr_5693.prdoc diff --git a/prdoc/pr_5701.prdoc b/prdoc/stable2412/pr_5701.prdoc similarity index 100% rename from prdoc/pr_5701.prdoc rename to prdoc/stable2412/pr_5701.prdoc diff --git a/prdoc/pr_5707.prdoc b/prdoc/stable2412/pr_5707.prdoc similarity index 100% rename from prdoc/pr_5707.prdoc rename to prdoc/stable2412/pr_5707.prdoc diff --git a/prdoc/pr_5716.prdoc b/prdoc/stable2412/pr_5716.prdoc similarity index 100% rename from prdoc/pr_5716.prdoc rename to prdoc/stable2412/pr_5716.prdoc diff --git a/prdoc/pr_5726.prdoc b/prdoc/stable2412/pr_5726.prdoc similarity index 100% rename from prdoc/pr_5726.prdoc rename to prdoc/stable2412/pr_5726.prdoc diff --git a/prdoc/pr_5732.prdoc b/prdoc/stable2412/pr_5732.prdoc similarity index 100% rename from prdoc/pr_5732.prdoc rename to prdoc/stable2412/pr_5732.prdoc diff --git a/prdoc/pr_5737.prdoc b/prdoc/stable2412/pr_5737.prdoc similarity index 100% rename from prdoc/pr_5737.prdoc rename to prdoc/stable2412/pr_5737.prdoc diff --git a/prdoc/pr_5741.prdoc b/prdoc/stable2412/pr_5741.prdoc similarity index 100% rename from prdoc/pr_5741.prdoc rename to prdoc/stable2412/pr_5741.prdoc diff --git a/prdoc/pr_5743.prdoc b/prdoc/stable2412/pr_5743.prdoc similarity index 100% rename from prdoc/pr_5743.prdoc rename to prdoc/stable2412/pr_5743.prdoc diff --git a/prdoc/pr_5745.prdoc b/prdoc/stable2412/pr_5745.prdoc similarity index 100% rename from prdoc/pr_5745.prdoc rename to prdoc/stable2412/pr_5745.prdoc diff --git a/prdoc/pr_5756.prdoc b/prdoc/stable2412/pr_5756.prdoc similarity index 100% rename from prdoc/pr_5756.prdoc rename to prdoc/stable2412/pr_5756.prdoc diff --git a/prdoc/pr_5762.prdoc b/prdoc/stable2412/pr_5762.prdoc similarity index 100% rename from prdoc/pr_5762.prdoc rename to prdoc/stable2412/pr_5762.prdoc diff --git a/prdoc/pr_5765.prdoc b/prdoc/stable2412/pr_5765.prdoc similarity index 100% rename from prdoc/pr_5765.prdoc rename to prdoc/stable2412/pr_5765.prdoc diff --git a/prdoc/pr_5768.prdoc b/prdoc/stable2412/pr_5768.prdoc similarity index 100% rename from prdoc/pr_5768.prdoc rename to prdoc/stable2412/pr_5768.prdoc diff --git a/prdoc/pr_5774.prdoc b/prdoc/stable2412/pr_5774.prdoc similarity index 100% rename from prdoc/pr_5774.prdoc rename to prdoc/stable2412/pr_5774.prdoc diff --git a/prdoc/pr_5779.prdoc b/prdoc/stable2412/pr_5779.prdoc similarity index 100% rename from prdoc/pr_5779.prdoc rename to prdoc/stable2412/pr_5779.prdoc diff --git a/prdoc/pr_5787.prdoc b/prdoc/stable2412/pr_5787.prdoc similarity index 100% rename from prdoc/pr_5787.prdoc rename to prdoc/stable2412/pr_5787.prdoc diff --git a/prdoc/pr_5789.prdoc b/prdoc/stable2412/pr_5789.prdoc similarity index 100% rename from prdoc/pr_5789.prdoc rename to prdoc/stable2412/pr_5789.prdoc diff --git a/prdoc/pr_5796.prdoc b/prdoc/stable2412/pr_5796.prdoc similarity index 100% rename from prdoc/pr_5796.prdoc rename to prdoc/stable2412/pr_5796.prdoc diff --git a/prdoc/pr_5804.prdoc b/prdoc/stable2412/pr_5804.prdoc similarity index 100% rename from prdoc/pr_5804.prdoc rename to prdoc/stable2412/pr_5804.prdoc diff --git a/prdoc/pr_5807.prdoc b/prdoc/stable2412/pr_5807.prdoc similarity index 100% rename from prdoc/pr_5807.prdoc rename to prdoc/stable2412/pr_5807.prdoc diff --git a/prdoc/pr_5811.prdoc b/prdoc/stable2412/pr_5811.prdoc similarity index 100% rename from prdoc/pr_5811.prdoc rename to prdoc/stable2412/pr_5811.prdoc diff --git a/prdoc/pr_5813.prdoc b/prdoc/stable2412/pr_5813.prdoc similarity index 100% rename from prdoc/pr_5813.prdoc rename to prdoc/stable2412/pr_5813.prdoc diff --git a/prdoc/pr_5824.prdoc b/prdoc/stable2412/pr_5824.prdoc similarity index 100% rename from prdoc/pr_5824.prdoc rename to prdoc/stable2412/pr_5824.prdoc diff --git a/prdoc/pr_5830.prdoc b/prdoc/stable2412/pr_5830.prdoc similarity index 100% rename from prdoc/pr_5830.prdoc rename to prdoc/stable2412/pr_5830.prdoc diff --git a/prdoc/pr_5838.prdoc b/prdoc/stable2412/pr_5838.prdoc similarity index 100% rename from prdoc/pr_5838.prdoc rename to prdoc/stable2412/pr_5838.prdoc diff --git a/prdoc/pr_5839.prdoc b/prdoc/stable2412/pr_5839.prdoc similarity index 100% rename from prdoc/pr_5839.prdoc rename to prdoc/stable2412/pr_5839.prdoc diff --git a/prdoc/pr_5845.prdoc b/prdoc/stable2412/pr_5845.prdoc similarity index 100% rename from prdoc/pr_5845.prdoc rename to prdoc/stable2412/pr_5845.prdoc diff --git a/prdoc/pr_5847.prdoc b/prdoc/stable2412/pr_5847.prdoc similarity index 100% rename from prdoc/pr_5847.prdoc rename to prdoc/stable2412/pr_5847.prdoc diff --git a/prdoc/pr_5856.prdoc b/prdoc/stable2412/pr_5856.prdoc similarity index 100% rename from prdoc/pr_5856.prdoc rename to prdoc/stable2412/pr_5856.prdoc diff --git a/prdoc/pr_5857.prdoc b/prdoc/stable2412/pr_5857.prdoc similarity index 100% rename from prdoc/pr_5857.prdoc rename to prdoc/stable2412/pr_5857.prdoc diff --git a/prdoc/pr_5859.prdoc b/prdoc/stable2412/pr_5859.prdoc similarity index 100% rename from prdoc/pr_5859.prdoc rename to prdoc/stable2412/pr_5859.prdoc diff --git a/prdoc/pr_5861.prdoc b/prdoc/stable2412/pr_5861.prdoc similarity index 100% rename from prdoc/pr_5861.prdoc rename to prdoc/stable2412/pr_5861.prdoc diff --git a/prdoc/pr_5866.prdoc b/prdoc/stable2412/pr_5866.prdoc similarity index 100% rename from prdoc/pr_5866.prdoc rename to prdoc/stable2412/pr_5866.prdoc diff --git a/prdoc/pr_5872.prdoc b/prdoc/stable2412/pr_5872.prdoc similarity index 100% rename from prdoc/pr_5872.prdoc rename to prdoc/stable2412/pr_5872.prdoc diff --git a/prdoc/pr_5875.prdoc b/prdoc/stable2412/pr_5875.prdoc similarity index 100% rename from prdoc/pr_5875.prdoc rename to prdoc/stable2412/pr_5875.prdoc diff --git a/prdoc/pr_5876.prdoc b/prdoc/stable2412/pr_5876.prdoc similarity index 100% rename from prdoc/pr_5876.prdoc rename to prdoc/stable2412/pr_5876.prdoc diff --git a/prdoc/pr_5880.prdoc b/prdoc/stable2412/pr_5880.prdoc similarity index 100% rename from prdoc/pr_5880.prdoc rename to prdoc/stable2412/pr_5880.prdoc diff --git a/prdoc/pr_5883.prdoc b/prdoc/stable2412/pr_5883.prdoc similarity index 100% rename from prdoc/pr_5883.prdoc rename to prdoc/stable2412/pr_5883.prdoc diff --git a/prdoc/pr_5886.prdoc b/prdoc/stable2412/pr_5886.prdoc similarity index 100% rename from prdoc/pr_5886.prdoc rename to prdoc/stable2412/pr_5886.prdoc diff --git a/prdoc/pr_5888.prdoc b/prdoc/stable2412/pr_5888.prdoc similarity index 100% rename from prdoc/pr_5888.prdoc rename to prdoc/stable2412/pr_5888.prdoc diff --git a/prdoc/pr_5891.prdoc b/prdoc/stable2412/pr_5891.prdoc similarity index 100% rename from prdoc/pr_5891.prdoc rename to prdoc/stable2412/pr_5891.prdoc diff --git a/prdoc/pr_5892.prdoc b/prdoc/stable2412/pr_5892.prdoc similarity index 100% rename from prdoc/pr_5892.prdoc rename to prdoc/stable2412/pr_5892.prdoc diff --git a/prdoc/pr_5901.prdoc b/prdoc/stable2412/pr_5901.prdoc similarity index 100% rename from prdoc/pr_5901.prdoc rename to prdoc/stable2412/pr_5901.prdoc diff --git a/prdoc/pr_5908.prdoc b/prdoc/stable2412/pr_5908.prdoc similarity index 100% rename from prdoc/pr_5908.prdoc rename to prdoc/stable2412/pr_5908.prdoc diff --git a/prdoc/pr_5911.prdoc b/prdoc/stable2412/pr_5911.prdoc similarity index 100% rename from prdoc/pr_5911.prdoc rename to prdoc/stable2412/pr_5911.prdoc diff --git a/prdoc/pr_5915.prdoc b/prdoc/stable2412/pr_5915.prdoc similarity index 100% rename from prdoc/pr_5915.prdoc rename to prdoc/stable2412/pr_5915.prdoc diff --git a/prdoc/pr_5917.prdoc b/prdoc/stable2412/pr_5917.prdoc similarity index 100% rename from prdoc/pr_5917.prdoc rename to prdoc/stable2412/pr_5917.prdoc diff --git a/prdoc/pr_5919.prdoc b/prdoc/stable2412/pr_5919.prdoc similarity index 100% rename from prdoc/pr_5919.prdoc rename to prdoc/stable2412/pr_5919.prdoc diff --git a/prdoc/pr_5924.prdoc b/prdoc/stable2412/pr_5924.prdoc similarity index 100% rename from prdoc/pr_5924.prdoc rename to prdoc/stable2412/pr_5924.prdoc diff --git a/prdoc/pr_5939.prdoc b/prdoc/stable2412/pr_5939.prdoc similarity index 100% rename from prdoc/pr_5939.prdoc rename to prdoc/stable2412/pr_5939.prdoc diff --git a/prdoc/pr_5941.prdoc b/prdoc/stable2412/pr_5941.prdoc similarity index 100% rename from prdoc/pr_5941.prdoc rename to prdoc/stable2412/pr_5941.prdoc diff --git a/prdoc/pr_5946.prdoc b/prdoc/stable2412/pr_5946.prdoc similarity index 100% rename from prdoc/pr_5946.prdoc rename to prdoc/stable2412/pr_5946.prdoc diff --git a/prdoc/pr_5954.prdoc b/prdoc/stable2412/pr_5954.prdoc similarity index 100% rename from prdoc/pr_5954.prdoc rename to prdoc/stable2412/pr_5954.prdoc diff --git a/prdoc/pr_5961.prdoc b/prdoc/stable2412/pr_5961.prdoc similarity index 100% rename from prdoc/pr_5961.prdoc rename to prdoc/stable2412/pr_5961.prdoc diff --git a/prdoc/pr_5971.prdoc b/prdoc/stable2412/pr_5971.prdoc similarity index 100% rename from prdoc/pr_5971.prdoc rename to prdoc/stable2412/pr_5971.prdoc diff --git a/prdoc/pr_5984.prdoc b/prdoc/stable2412/pr_5984.prdoc similarity index 100% rename from prdoc/pr_5984.prdoc rename to prdoc/stable2412/pr_5984.prdoc diff --git a/prdoc/pr_5994.prdoc b/prdoc/stable2412/pr_5994.prdoc similarity index 100% rename from prdoc/pr_5994.prdoc rename to prdoc/stable2412/pr_5994.prdoc diff --git a/prdoc/pr_5995.prdoc b/prdoc/stable2412/pr_5995.prdoc similarity index 100% rename from prdoc/pr_5995.prdoc rename to prdoc/stable2412/pr_5995.prdoc diff --git a/prdoc/pr_5997.prdoc b/prdoc/stable2412/pr_5997.prdoc similarity index 100% rename from prdoc/pr_5997.prdoc rename to prdoc/stable2412/pr_5997.prdoc diff --git a/prdoc/pr_5998.prdoc b/prdoc/stable2412/pr_5998.prdoc similarity index 100% rename from prdoc/pr_5998.prdoc rename to prdoc/stable2412/pr_5998.prdoc diff --git a/prdoc/pr_5999.prdoc b/prdoc/stable2412/pr_5999.prdoc similarity index 100% rename from prdoc/pr_5999.prdoc rename to prdoc/stable2412/pr_5999.prdoc diff --git a/prdoc/pr_6011.prdoc b/prdoc/stable2412/pr_6011.prdoc similarity index 100% rename from prdoc/pr_6011.prdoc rename to prdoc/stable2412/pr_6011.prdoc diff --git a/prdoc/pr_6015.prdoc b/prdoc/stable2412/pr_6015.prdoc similarity index 100% rename from prdoc/pr_6015.prdoc rename to prdoc/stable2412/pr_6015.prdoc diff --git a/prdoc/pr_6016.prdoc b/prdoc/stable2412/pr_6016.prdoc similarity index 100% rename from prdoc/pr_6016.prdoc rename to prdoc/stable2412/pr_6016.prdoc diff --git a/prdoc/pr_6022.prdoc b/prdoc/stable2412/pr_6022.prdoc similarity index 100% rename from prdoc/pr_6022.prdoc rename to prdoc/stable2412/pr_6022.prdoc diff --git a/prdoc/pr_6023.prdoc b/prdoc/stable2412/pr_6023.prdoc similarity index 100% rename from prdoc/pr_6023.prdoc rename to prdoc/stable2412/pr_6023.prdoc diff --git a/prdoc/pr_6025.prdoc b/prdoc/stable2412/pr_6025.prdoc similarity index 100% rename from prdoc/pr_6025.prdoc rename to prdoc/stable2412/pr_6025.prdoc diff --git a/prdoc/pr_6027.prdoc b/prdoc/stable2412/pr_6027.prdoc similarity index 100% rename from prdoc/pr_6027.prdoc rename to prdoc/stable2412/pr_6027.prdoc diff --git a/prdoc/pr_6032.prdoc b/prdoc/stable2412/pr_6032.prdoc similarity index 100% rename from prdoc/pr_6032.prdoc rename to prdoc/stable2412/pr_6032.prdoc diff --git a/prdoc/pr_6039.prdoc b/prdoc/stable2412/pr_6039.prdoc similarity index 100% rename from prdoc/pr_6039.prdoc rename to prdoc/stable2412/pr_6039.prdoc diff --git a/prdoc/pr_6045.prdoc b/prdoc/stable2412/pr_6045.prdoc similarity index 100% rename from prdoc/pr_6045.prdoc rename to prdoc/stable2412/pr_6045.prdoc diff --git a/prdoc/pr_6058.prdoc b/prdoc/stable2412/pr_6058.prdoc similarity index 100% rename from prdoc/pr_6058.prdoc rename to prdoc/stable2412/pr_6058.prdoc diff --git a/prdoc/pr_6061.prdoc b/prdoc/stable2412/pr_6061.prdoc similarity index 100% rename from prdoc/pr_6061.prdoc rename to prdoc/stable2412/pr_6061.prdoc diff --git a/prdoc/pr_6073.prdoc b/prdoc/stable2412/pr_6073.prdoc similarity index 100% rename from prdoc/pr_6073.prdoc rename to prdoc/stable2412/pr_6073.prdoc diff --git a/prdoc/pr_6077.prdoc b/prdoc/stable2412/pr_6077.prdoc similarity index 100% rename from prdoc/pr_6077.prdoc rename to prdoc/stable2412/pr_6077.prdoc diff --git a/prdoc/pr_6080.prdoc b/prdoc/stable2412/pr_6080.prdoc similarity index 100% rename from prdoc/pr_6080.prdoc rename to prdoc/stable2412/pr_6080.prdoc diff --git a/prdoc/pr_6087.prdoc b/prdoc/stable2412/pr_6087.prdoc similarity index 100% rename from prdoc/pr_6087.prdoc rename to prdoc/stable2412/pr_6087.prdoc diff --git a/prdoc/pr_6088.prdoc b/prdoc/stable2412/pr_6088.prdoc similarity index 100% rename from prdoc/pr_6088.prdoc rename to prdoc/stable2412/pr_6088.prdoc diff --git a/prdoc/pr_6094.prdoc b/prdoc/stable2412/pr_6094.prdoc similarity index 100% rename from prdoc/pr_6094.prdoc rename to prdoc/stable2412/pr_6094.prdoc diff --git a/prdoc/pr_6096.prdoc b/prdoc/stable2412/pr_6096.prdoc similarity index 100% rename from prdoc/pr_6096.prdoc rename to prdoc/stable2412/pr_6096.prdoc diff --git a/prdoc/pr_6104.prdoc b/prdoc/stable2412/pr_6104.prdoc similarity index 100% rename from prdoc/pr_6104.prdoc rename to prdoc/stable2412/pr_6104.prdoc diff --git a/prdoc/pr_6105.prdoc b/prdoc/stable2412/pr_6105.prdoc similarity index 100% rename from prdoc/pr_6105.prdoc rename to prdoc/stable2412/pr_6105.prdoc diff --git a/prdoc/pr_6129.prdoc b/prdoc/stable2412/pr_6129.prdoc similarity index 100% rename from prdoc/pr_6129.prdoc rename to prdoc/stable2412/pr_6129.prdoc diff --git a/prdoc/pr_6141.prdoc b/prdoc/stable2412/pr_6141.prdoc similarity index 100% rename from prdoc/pr_6141.prdoc rename to prdoc/stable2412/pr_6141.prdoc diff --git a/prdoc/pr_6147.prdoc b/prdoc/stable2412/pr_6147.prdoc similarity index 100% rename from prdoc/pr_6147.prdoc rename to prdoc/stable2412/pr_6147.prdoc diff --git a/prdoc/pr_6148.prdoc b/prdoc/stable2412/pr_6148.prdoc similarity index 100% rename from prdoc/pr_6148.prdoc rename to prdoc/stable2412/pr_6148.prdoc diff --git a/prdoc/pr_6156.prdoc b/prdoc/stable2412/pr_6156.prdoc similarity index 100% rename from prdoc/pr_6156.prdoc rename to prdoc/stable2412/pr_6156.prdoc diff --git a/prdoc/pr_6169.prdoc b/prdoc/stable2412/pr_6169.prdoc similarity index 100% rename from prdoc/pr_6169.prdoc rename to prdoc/stable2412/pr_6169.prdoc diff --git a/prdoc/pr_6171.prdoc b/prdoc/stable2412/pr_6171.prdoc similarity index 100% rename from prdoc/pr_6171.prdoc rename to prdoc/stable2412/pr_6171.prdoc diff --git a/prdoc/pr_6174.prdoc b/prdoc/stable2412/pr_6174.prdoc similarity index 100% rename from prdoc/pr_6174.prdoc rename to prdoc/stable2412/pr_6174.prdoc diff --git a/prdoc/pr_6187.prdoc b/prdoc/stable2412/pr_6187.prdoc similarity index 100% rename from prdoc/pr_6187.prdoc rename to prdoc/stable2412/pr_6187.prdoc diff --git a/prdoc/pr_6192.prdoc b/prdoc/stable2412/pr_6192.prdoc similarity index 100% rename from prdoc/pr_6192.prdoc rename to prdoc/stable2412/pr_6192.prdoc diff --git a/prdoc/pr_6205.prdoc b/prdoc/stable2412/pr_6205.prdoc similarity index 100% rename from prdoc/pr_6205.prdoc rename to prdoc/stable2412/pr_6205.prdoc diff --git a/prdoc/pr_6212.prdoc b/prdoc/stable2412/pr_6212.prdoc similarity index 100% rename from prdoc/pr_6212.prdoc rename to prdoc/stable2412/pr_6212.prdoc diff --git a/prdoc/pr_6214.prdoc b/prdoc/stable2412/pr_6214.prdoc similarity index 100% rename from prdoc/pr_6214.prdoc rename to prdoc/stable2412/pr_6214.prdoc diff --git a/prdoc/pr_6217.prdoc b/prdoc/stable2412/pr_6217.prdoc similarity index 100% rename from prdoc/pr_6217.prdoc rename to prdoc/stable2412/pr_6217.prdoc diff --git a/prdoc/pr_6218.prdoc b/prdoc/stable2412/pr_6218.prdoc similarity index 100% rename from prdoc/pr_6218.prdoc rename to prdoc/stable2412/pr_6218.prdoc diff --git a/prdoc/pr_6221.prdoc b/prdoc/stable2412/pr_6221.prdoc similarity index 100% rename from prdoc/pr_6221.prdoc rename to prdoc/stable2412/pr_6221.prdoc diff --git a/prdoc/pr_6228.prdoc b/prdoc/stable2412/pr_6228.prdoc similarity index 100% rename from prdoc/pr_6228.prdoc rename to prdoc/stable2412/pr_6228.prdoc diff --git a/prdoc/pr_6246.prdoc b/prdoc/stable2412/pr_6246.prdoc similarity index 100% rename from prdoc/pr_6246.prdoc rename to prdoc/stable2412/pr_6246.prdoc diff --git a/prdoc/pr_6255.prdoc b/prdoc/stable2412/pr_6255.prdoc similarity index 100% rename from prdoc/pr_6255.prdoc rename to prdoc/stable2412/pr_6255.prdoc diff --git a/prdoc/pr_6257.prdoc b/prdoc/stable2412/pr_6257.prdoc similarity index 100% rename from prdoc/pr_6257.prdoc rename to prdoc/stable2412/pr_6257.prdoc diff --git a/prdoc/pr_6260.prdoc b/prdoc/stable2412/pr_6260.prdoc similarity index 100% rename from prdoc/pr_6260.prdoc rename to prdoc/stable2412/pr_6260.prdoc diff --git a/prdoc/pr_6261.prdoc b/prdoc/stable2412/pr_6261.prdoc similarity index 100% rename from prdoc/pr_6261.prdoc rename to prdoc/stable2412/pr_6261.prdoc diff --git a/prdoc/pr_6263.prdoc b/prdoc/stable2412/pr_6263.prdoc similarity index 100% rename from prdoc/pr_6263.prdoc rename to prdoc/stable2412/pr_6263.prdoc diff --git a/prdoc/pr_6264.prdoc b/prdoc/stable2412/pr_6264.prdoc similarity index 100% rename from prdoc/pr_6264.prdoc rename to prdoc/stable2412/pr_6264.prdoc diff --git a/prdoc/pr_6268.prdoc b/prdoc/stable2412/pr_6268.prdoc similarity index 100% rename from prdoc/pr_6268.prdoc rename to prdoc/stable2412/pr_6268.prdoc diff --git a/prdoc/pr_6278.prdoc b/prdoc/stable2412/pr_6278.prdoc similarity index 100% rename from prdoc/pr_6278.prdoc rename to prdoc/stable2412/pr_6278.prdoc diff --git a/prdoc/pr_6288.prdoc b/prdoc/stable2412/pr_6288.prdoc similarity index 100% rename from prdoc/pr_6288.prdoc rename to prdoc/stable2412/pr_6288.prdoc diff --git a/prdoc/pr_6291.prdoc b/prdoc/stable2412/pr_6291.prdoc similarity index 100% rename from prdoc/pr_6291.prdoc rename to prdoc/stable2412/pr_6291.prdoc diff --git a/prdoc/pr_6295.prdoc b/prdoc/stable2412/pr_6295.prdoc similarity index 100% rename from prdoc/pr_6295.prdoc rename to prdoc/stable2412/pr_6295.prdoc diff --git a/prdoc/pr_6296.prdoc b/prdoc/stable2412/pr_6296.prdoc similarity index 100% rename from prdoc/pr_6296.prdoc rename to prdoc/stable2412/pr_6296.prdoc diff --git a/prdoc/pr_6298.prdoc b/prdoc/stable2412/pr_6298.prdoc similarity index 100% rename from prdoc/pr_6298.prdoc rename to prdoc/stable2412/pr_6298.prdoc diff --git a/prdoc/pr_6299.prdoc b/prdoc/stable2412/pr_6299.prdoc similarity index 100% rename from prdoc/pr_6299.prdoc rename to prdoc/stable2412/pr_6299.prdoc diff --git a/prdoc/pr_6304.prdoc b/prdoc/stable2412/pr_6304.prdoc similarity index 100% rename from prdoc/pr_6304.prdoc rename to prdoc/stable2412/pr_6304.prdoc diff --git a/prdoc/pr_6305.prdoc b/prdoc/stable2412/pr_6305.prdoc similarity index 100% rename from prdoc/pr_6305.prdoc rename to prdoc/stable2412/pr_6305.prdoc diff --git a/prdoc/pr_6314.prdoc b/prdoc/stable2412/pr_6314.prdoc similarity index 100% rename from prdoc/pr_6314.prdoc rename to prdoc/stable2412/pr_6314.prdoc diff --git a/prdoc/pr_6315.prdoc b/prdoc/stable2412/pr_6315.prdoc similarity index 100% rename from prdoc/pr_6315.prdoc rename to prdoc/stable2412/pr_6315.prdoc diff --git a/prdoc/pr_6316.prdoc b/prdoc/stable2412/pr_6316.prdoc similarity index 100% rename from prdoc/pr_6316.prdoc rename to prdoc/stable2412/pr_6316.prdoc diff --git a/prdoc/pr_6317.prdoc b/prdoc/stable2412/pr_6317.prdoc similarity index 100% rename from prdoc/pr_6317.prdoc rename to prdoc/stable2412/pr_6317.prdoc diff --git a/prdoc/pr_6318.prdoc b/prdoc/stable2412/pr_6318.prdoc similarity index 100% rename from prdoc/pr_6318.prdoc rename to prdoc/stable2412/pr_6318.prdoc diff --git a/prdoc/pr_6323.prdoc b/prdoc/stable2412/pr_6323.prdoc similarity index 100% rename from prdoc/pr_6323.prdoc rename to prdoc/stable2412/pr_6323.prdoc diff --git a/prdoc/pr_6337.prdoc b/prdoc/stable2412/pr_6337.prdoc similarity index 100% rename from prdoc/pr_6337.prdoc rename to prdoc/stable2412/pr_6337.prdoc diff --git a/prdoc/pr_6353.prdoc b/prdoc/stable2412/pr_6353.prdoc similarity index 100% rename from prdoc/pr_6353.prdoc rename to prdoc/stable2412/pr_6353.prdoc diff --git a/prdoc/pr_6357.prdoc b/prdoc/stable2412/pr_6357.prdoc similarity index 100% rename from prdoc/pr_6357.prdoc rename to prdoc/stable2412/pr_6357.prdoc diff --git a/prdoc/pr_6360.prdoc b/prdoc/stable2412/pr_6360.prdoc similarity index 100% rename from prdoc/pr_6360.prdoc rename to prdoc/stable2412/pr_6360.prdoc diff --git a/prdoc/pr_6365.prdoc b/prdoc/stable2412/pr_6365.prdoc similarity index 100% rename from prdoc/pr_6365.prdoc rename to prdoc/stable2412/pr_6365.prdoc diff --git a/prdoc/pr_6373.prdoc b/prdoc/stable2412/pr_6373.prdoc similarity index 100% rename from prdoc/pr_6373.prdoc rename to prdoc/stable2412/pr_6373.prdoc diff --git a/prdoc/pr_6380.prdoc b/prdoc/stable2412/pr_6380.prdoc similarity index 100% rename from prdoc/pr_6380.prdoc rename to prdoc/stable2412/pr_6380.prdoc diff --git a/prdoc/pr_6382.prdoc b/prdoc/stable2412/pr_6382.prdoc similarity index 100% rename from prdoc/pr_6382.prdoc rename to prdoc/stable2412/pr_6382.prdoc diff --git a/prdoc/pr_6384.prdoc b/prdoc/stable2412/pr_6384.prdoc similarity index 100% rename from prdoc/pr_6384.prdoc rename to prdoc/stable2412/pr_6384.prdoc diff --git a/prdoc/pr_6406.prdoc b/prdoc/stable2412/pr_6406.prdoc similarity index 100% rename from prdoc/pr_6406.prdoc rename to prdoc/stable2412/pr_6406.prdoc diff --git a/prdoc/pr_6418.prdoc b/prdoc/stable2412/pr_6418.prdoc similarity index 100% rename from prdoc/pr_6418.prdoc rename to prdoc/stable2412/pr_6418.prdoc diff --git a/prdoc/pr_6454.prdoc b/prdoc/stable2412/pr_6454.prdoc similarity index 100% rename from prdoc/pr_6454.prdoc rename to prdoc/stable2412/pr_6454.prdoc diff --git a/prdoc/pr_6484.prdoc b/prdoc/stable2412/pr_6484.prdoc similarity index 100% rename from prdoc/pr_6484.prdoc rename to prdoc/stable2412/pr_6484.prdoc diff --git a/prdoc/pr_6505.prdoc b/prdoc/stable2412/pr_6505.prdoc similarity index 100% rename from prdoc/pr_6505.prdoc rename to prdoc/stable2412/pr_6505.prdoc diff --git a/prdoc/pr_6536.prdoc b/prdoc/stable2412/pr_6536.prdoc similarity index 100% rename from prdoc/pr_6536.prdoc rename to prdoc/stable2412/pr_6536.prdoc diff --git a/prdoc/pr_6566.prdoc b/prdoc/stable2412/pr_6566.prdoc similarity index 100% rename from prdoc/pr_6566.prdoc rename to prdoc/stable2412/pr_6566.prdoc diff --git a/prdoc/pr_6588.prdoc b/prdoc/stable2412/pr_6588.prdoc similarity index 100% rename from prdoc/pr_6588.prdoc rename to prdoc/stable2412/pr_6588.prdoc diff --git a/prdoc/pr_6603.prdoc b/prdoc/stable2412/pr_6603.prdoc similarity index 100% rename from prdoc/pr_6603.prdoc rename to prdoc/stable2412/pr_6603.prdoc diff --git a/prdoc/pr_6643.prdoc b/prdoc/stable2412/pr_6643.prdoc similarity index 100% rename from prdoc/pr_6643.prdoc rename to prdoc/stable2412/pr_6643.prdoc diff --git a/prdoc/pr_6645.prdoc b/prdoc/stable2412/pr_6645.prdoc similarity index 100% rename from prdoc/pr_6645.prdoc rename to prdoc/stable2412/pr_6645.prdoc diff --git a/prdoc/pr_6646.prdoc b/prdoc/stable2412/pr_6646.prdoc similarity index 100% rename from prdoc/pr_6646.prdoc rename to prdoc/stable2412/pr_6646.prdoc diff --git a/prdoc/pr_6652.prdoc b/prdoc/stable2412/pr_6652.prdoc similarity index 100% rename from prdoc/pr_6652.prdoc rename to prdoc/stable2412/pr_6652.prdoc diff --git a/prdoc/pr_6677.prdoc b/prdoc/stable2412/pr_6677.prdoc similarity index 100% rename from prdoc/pr_6677.prdoc rename to prdoc/stable2412/pr_6677.prdoc diff --git a/prdoc/pr_6690.prdoc b/prdoc/stable2412/pr_6690.prdoc similarity index 100% rename from prdoc/pr_6690.prdoc rename to prdoc/stable2412/pr_6690.prdoc diff --git a/prdoc/pr_6696.prdoc b/prdoc/stable2412/pr_6696.prdoc similarity index 100% rename from prdoc/pr_6696.prdoc rename to prdoc/stable2412/pr_6696.prdoc diff --git a/prdoc/pr_6729.prdoc b/prdoc/stable2412/pr_6729.prdoc similarity index 100% rename from prdoc/pr_6729.prdoc rename to prdoc/stable2412/pr_6729.prdoc diff --git a/prdoc/pr_6742.prdoc b/prdoc/stable2412/pr_6742.prdoc similarity index 100% rename from prdoc/pr_6742.prdoc rename to prdoc/stable2412/pr_6742.prdoc diff --git a/prdoc/pr_6760.prdoc b/prdoc/stable2412/pr_6760.prdoc similarity index 100% rename from prdoc/pr_6760.prdoc rename to prdoc/stable2412/pr_6760.prdoc diff --git a/prdoc/pr_6781.prdoc b/prdoc/stable2412/pr_6781.prdoc similarity index 100% rename from prdoc/pr_6781.prdoc rename to prdoc/stable2412/pr_6781.prdoc diff --git a/prdoc/pr_6814.prdoc b/prdoc/stable2412/pr_6814.prdoc similarity index 100% rename from prdoc/pr_6814.prdoc rename to prdoc/stable2412/pr_6814.prdoc diff --git a/prdoc/pr_6860.prdoc b/prdoc/stable2412/pr_6860.prdoc similarity index 100% rename from prdoc/pr_6860.prdoc rename to prdoc/stable2412/pr_6860.prdoc diff --git a/prdoc/pr_6863.prdoc b/prdoc/stable2412/pr_6863.prdoc similarity index 100% rename from prdoc/pr_6863.prdoc rename to prdoc/stable2412/pr_6863.prdoc diff --git a/prdoc/pr_6864.prdoc b/prdoc/stable2412/pr_6864.prdoc similarity index 100% rename from prdoc/pr_6864.prdoc rename to prdoc/stable2412/pr_6864.prdoc diff --git a/prdoc/pr_6885.prdoc b/prdoc/stable2412/pr_6885.prdoc similarity index 100% rename from prdoc/pr_6885.prdoc rename to prdoc/stable2412/pr_6885.prdoc From 4e805ca05067f6ed970f33f9be51483185b0cc0b Mon Sep 17 00:00:00 2001 From: runcomet Date: Fri, 20 Dec 2024 02:47:56 -0800 Subject: [PATCH 199/340] Migrate `pallet-atomic-swap` to umbrella crate (#6601) Part of https://github.com/paritytech/polkadot-sdk/issues/6504 --------- Co-authored-by: Giuseppe Re --- Cargo.lock | 6 +----- substrate/frame/atomic-swap/Cargo.toml | 16 +++------------- substrate/frame/atomic-swap/src/lib.rs | 16 ++++------------ substrate/frame/atomic-swap/src/tests.rs | 8 +++----- 4 files changed, 11 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 726c8f5a1885..6151ed33c5b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12121,14 +12121,10 @@ dependencies = [ name = "pallet-atomic-swap" version = "28.0.0" dependencies = [ - "frame-support 28.0.0", - "frame-system 28.0.0", "pallet-balances 28.0.0", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] diff --git a/substrate/frame/atomic-swap/Cargo.toml b/substrate/frame/atomic-swap/Cargo.toml index db89a58da8f0..1f97f60149ba 100644 --- a/substrate/frame/atomic-swap/Cargo.toml +++ b/substrate/frame/atomic-swap/Cargo.toml @@ -18,11 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } +frame = { workspace = true, features = ["experimental", "runtime"] } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } @@ -31,17 +27,11 @@ pallet-balances = { workspace = true, default-features = true } default = ["std"] std = [ "codec/std", - "frame-support/std", - "frame-system/std", + "frame/std", "pallet-balances/std", "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", + "frame/try-runtime", "pallet-balances/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/substrate/frame/atomic-swap/src/lib.rs b/substrate/frame/atomic-swap/src/lib.rs index c3010f5c9c03..9521f20fe009 100644 --- a/substrate/frame/atomic-swap/src/lib.rs +++ b/substrate/frame/atomic-swap/src/lib.rs @@ -50,17 +50,11 @@ use core::{ marker::PhantomData, ops::{Deref, DerefMut}, }; -use frame_support::{ - dispatch::DispatchResult, - pallet_prelude::MaxEncodedLen, - traits::{BalanceStatus, Currency, Get, ReservableCurrency}, - weights::Weight, - RuntimeDebugNoBound, +use frame::{ + prelude::*, + traits::{BalanceStatus, Currency, ReservableCurrency}, }; -use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; -use sp_io::hashing::blake2_256; -use sp_runtime::RuntimeDebug; /// Pending atomic swap operation. #[derive(Clone, Eq, PartialEq, RuntimeDebugNoBound, Encode, Decode, TypeInfo, MaxEncodedLen)] @@ -159,11 +153,9 @@ where pub use pallet::*; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; /// Atomic swap's pallet configuration trait. #[pallet::config] diff --git a/substrate/frame/atomic-swap/src/tests.rs b/substrate/frame/atomic-swap/src/tests.rs index 47ebe6a8f0ac..6fcc5571a523 100644 --- a/substrate/frame/atomic-swap/src/tests.rs +++ b/substrate/frame/atomic-swap/src/tests.rs @@ -19,13 +19,11 @@ use super::*; use crate as pallet_atomic_swap; - -use frame_support::{derive_impl, traits::ConstU32}; -use sp_runtime::BuildStorage; +use frame::testing_prelude::*; type Block = frame_system::mocking::MockBlock; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -54,7 +52,7 @@ impl Config for Test { const A: u64 = 1; const B: u64 = 2; -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext() -> TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let genesis = pallet_balances::GenesisConfig:: { balances: vec![(A, 100), (B, 200)] }; genesis.assimilate_storage(&mut t).unwrap(); From a843d15eb44275685d5e0fdd3372f4ee3da4d016 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Sat, 21 Dec 2024 01:35:23 +0800 Subject: [PATCH 200/340] Reorder dependencies' keys (#6967) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It doesn't make sense to only reorder the features array. For example: This makes it hard for me to compare the dependencies and features, especially some crates have a really really long dependencies list. ```toml​ [dependencies] c = "*" a = "*" b = "*" [features] std = [ "a", "b", "c", ] ``` This makes my life easier. ```toml​ [dependencies] a = "*" b = "*" c = "*" [features] std = [ "a", "b", "c", ] ``` --------- Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> --- .config/taplo.toml | 7 + .../chain-bridge-hub-cumulus/Cargo.toml | 4 +- .../chains/chain-bridge-hub-kusama/Cargo.toml | 2 +- .../chain-bridge-hub-polkadot/Cargo.toml | 2 +- .../chains/chain-bridge-hub-rococo/Cargo.toml | 2 +- .../chain-bridge-hub-westend/Cargo.toml | 2 +- bridges/modules/beefy/Cargo.toml | 4 +- bridges/modules/grandpa/Cargo.toml | 2 +- bridges/modules/messages/Cargo.toml | 2 +- bridges/modules/relayers/Cargo.toml | 8 +- bridges/modules/xcm-bridge-hub/Cargo.toml | 8 +- bridges/primitives/beefy/Cargo.toml | 2 +- bridges/primitives/header-chain/Cargo.toml | 2 +- bridges/primitives/messages/Cargo.toml | 6 +- bridges/primitives/relayers/Cargo.toml | 2 +- .../xcm-bridge-hub-router/Cargo.toml | 2 +- bridges/primitives/xcm-bridge-hub/Cargo.toml | 6 +- bridges/relays/client-substrate/Cargo.toml | 6 +- bridges/relays/lib-substrate-relay/Cargo.toml | 10 +- bridges/relays/utils/Cargo.toml | 8 +- .../pallets/ethereum-client/Cargo.toml | 22 +- .../ethereum-client/fixtures/Cargo.toml | 4 +- .../pallets/inbound-queue/Cargo.toml | 16 +- .../pallets/inbound-queue/fixtures/Cargo.toml | 4 +- .../pallets/outbound-queue/Cargo.toml | 10 +- .../outbound-queue/merkle-tree/Cargo.toml | 4 +- .../outbound-queue/runtime-api/Cargo.toml | 6 +- bridges/snowbridge/pallets/system/Cargo.toml | 8 +- .../pallets/system/runtime-api/Cargo.toml | 4 +- .../snowbridge/primitives/beacon/Cargo.toml | 14 +- bridges/snowbridge/primitives/core/Cargo.toml | 10 +- .../snowbridge/primitives/ethereum/Cargo.toml | 10 +- .../snowbridge/primitives/router/Cargo.toml | 2 +- .../runtime/runtime-common/Cargo.toml | 4 +- .../snowbridge/runtime/test-common/Cargo.toml | 2 +- cumulus/bin/pov-validator/Cargo.toml | 14 +- cumulus/client/cli/Cargo.toml | 4 +- cumulus/client/collator/Cargo.toml | 6 +- cumulus/client/consensus/aura/Cargo.toml | 16 +- cumulus/client/consensus/common/Cargo.toml | 4 +- .../client/consensus/relay-chain/Cargo.toml | 2 +- cumulus/client/network/Cargo.toml | 6 +- cumulus/client/pov-recovery/Cargo.toml | 14 +- .../Cargo.toml | 4 +- .../client/relay-chain-interface/Cargo.toml | 8 +- .../relay-chain-minimal-node/Cargo.toml | 18 +- .../relay-chain-rpc-interface/Cargo.toml | 34 +- cumulus/client/service/Cargo.toml | 14 +- cumulus/pallets/collator-selection/Cargo.toml | 14 +- cumulus/pallets/dmp-queue/Cargo.toml | 2 +- cumulus/pallets/parachain-system/Cargo.toml | 10 +- .../parachain-system/proc-macro/Cargo.toml | 4 +- .../pallets/session-benchmarking/Cargo.toml | 4 +- cumulus/pallets/xcm/Cargo.toml | 4 +- cumulus/pallets/xcmp-queue/Cargo.toml | 12 +- cumulus/parachains/common/Cargo.toml | 2 +- .../assets/asset-hub-rococo/Cargo.toml | 6 +- .../assets/asset-hub-westend/Cargo.toml | 8 +- .../bridges/bridge-hub-rococo/Cargo.toml | 8 +- .../bridges/bridge-hub-westend/Cargo.toml | 8 +- .../collectives-westend/Cargo.toml | 6 +- .../coretime/coretime-rococo/Cargo.toml | 6 +- .../coretime/coretime-westend/Cargo.toml | 6 +- .../people/people-rococo/Cargo.toml | 4 +- .../people/people-westend/Cargo.toml | 4 +- .../parachains/testing/penpal/Cargo.toml | 4 +- .../emulated/chains/relays/rococo/Cargo.toml | 10 +- .../emulated/chains/relays/westend/Cargo.toml | 12 +- .../emulated/common/Cargo.toml | 28 +- .../networks/rococo-system/Cargo.toml | 8 +- .../networks/rococo-westend-system/Cargo.toml | 6 +- .../networks/westend-system/Cargo.toml | 6 +- .../tests/assets/asset-hub-rococo/Cargo.toml | 18 +- .../tests/assets/asset-hub-westend/Cargo.toml | 22 +- .../bridges/bridge-hub-rococo/Cargo.toml | 14 +- .../bridges/bridge-hub-westend/Cargo.toml | 16 +- .../collectives-westend/Cargo.toml | 18 +- .../tests/coretime/coretime-rococo/Cargo.toml | 2 +- .../coretime/coretime-westend/Cargo.toml | 2 +- .../tests/people/people-rococo/Cargo.toml | 2 +- .../tests/people/people-westend/Cargo.toml | 4 +- cumulus/parachains/pallets/ping/Cargo.toml | 4 +- .../assets/asset-hub-rococo/Cargo.toml | 16 +- .../assets/asset-hub-westend/Cargo.toml | 14 +- .../runtimes/assets/common/Cargo.toml | 10 +- .../runtimes/assets/test-utils/Cargo.toml | 10 +- .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 18 +- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 22 +- .../runtimes/bridge-hubs/common/Cargo.toml | 8 +- .../bridge-hubs/test-utils/Cargo.toml | 12 +- .../collectives-westend/Cargo.toml | 20 +- .../contracts/contracts-rococo/Cargo.toml | 36 +- .../coretime/coretime-rococo/Cargo.toml | 6 +- .../coretime/coretime-westend/Cargo.toml | 6 +- .../glutton/glutton-westend/Cargo.toml | 4 +- .../runtimes/people/people-rococo/Cargo.toml | 2 +- .../runtimes/people/people-westend/Cargo.toml | 2 +- .../parachains/runtimes/test-utils/Cargo.toml | 12 +- .../runtimes/testing/penpal/Cargo.toml | 12 +- .../testing/rococo-parachain/Cargo.toml | 6 +- cumulus/polkadot-omni-node/lib/Cargo.toml | 60 +- cumulus/polkadot-parachain/Cargo.toml | 18 +- .../proof-size-hostfunction/Cargo.toml | 4 +- .../storage-weight-reclaim/Cargo.toml | 2 +- cumulus/primitives/utility/Cargo.toml | 4 +- cumulus/test/client/Cargo.toml | 34 +- cumulus/test/runtime/Cargo.toml | 18 +- cumulus/test/service/Cargo.toml | 44 +- cumulus/xcm/xcm-emulator/Cargo.toml | 26 +- docs/sdk/Cargo.toml | 92 +-- .../packages/guides/first-pallet/Cargo.toml | 4 +- .../chain_spec_runtime/Cargo.toml | 8 +- polkadot/Cargo.toml | 4 +- polkadot/cli/Cargo.toml | 20 +- polkadot/core-primitives/Cargo.toml | 4 +- polkadot/erasure-coding/Cargo.toml | 8 +- polkadot/erasure-coding/fuzzer/Cargo.toml | 4 +- polkadot/node/collation-generation/Cargo.toml | 8 +- .../core/approval-voting-parallel/Cargo.toml | 28 +- polkadot/node/core/approval-voting/Cargo.toml | 38 +- polkadot/node/core/av-store/Cargo.toml | 16 +- polkadot/node/core/backing/Cargo.toml | 32 +- .../node/core/bitfield-signing/Cargo.toml | 4 +- .../node/core/candidate-validation/Cargo.toml | 20 +- polkadot/node/core/chain-api/Cargo.toml | 6 +- polkadot/node/core/chain-selection/Cargo.toml | 12 +- .../node/core/dispute-coordinator/Cargo.toml | 20 +- .../node/core/parachains-inherent/Cargo.toml | 4 +- .../core/prospective-parachains/Cargo.toml | 10 +- polkadot/node/core/provisioner/Cargo.toml | 14 +- polkadot/node/core/pvf-checker/Cargo.toml | 14 +- polkadot/node/core/pvf/Cargo.toml | 8 +- .../node/core/pvf/execute-worker/Cargo.toml | 4 +- .../node/core/pvf/prepare-worker/Cargo.toml | 4 +- polkadot/node/core/runtime-api/Cargo.toml | 10 +- polkadot/node/gum/Cargo.toml | 2 +- polkadot/node/gum/proc-macro/Cargo.toml | 8 +- polkadot/node/malus/Cargo.toml | 28 +- polkadot/node/metrics/Cargo.toml | 22 +- .../network/approval-distribution/Cargo.toml | 8 +- .../availability-distribution/Cargo.toml | 26 +- .../network/availability-recovery/Cargo.toml | 24 +- .../network/bitfield-distribution/Cargo.toml | 16 +- polkadot/node/network/bridge/Cargo.toml | 18 +- .../node/network/collator-protocol/Cargo.toml | 16 +- .../network/dispute-distribution/Cargo.toml | 24 +- .../node/network/gossip-support/Cargo.toml | 12 +- polkadot/node/network/protocol/Cargo.toml | 18 +- .../network/statement-distribution/Cargo.toml | 42 +- polkadot/node/overseer/Cargo.toml | 22 +- polkadot/node/primitives/Cargo.toml | 14 +- polkadot/node/service/Cargo.toml | 76 +- polkadot/node/subsystem-bench/Cargo.toml | 84 +- .../node/subsystem-test-helpers/Cargo.toml | 10 +- polkadot/node/subsystem-types/Cargo.toml | 20 +- polkadot/node/subsystem-util/Cargo.toml | 26 +- polkadot/node/subsystem/Cargo.toml | 2 +- polkadot/node/test/client/Cargo.toml | 24 +- polkadot/node/test/service/Cargo.toml | 30 +- .../node/zombienet-backchannel/Cargo.toml | 12 +- polkadot/parachain/Cargo.toml | 8 +- polkadot/parachain/test-parachains/Cargo.toml | 2 +- .../test-parachains/adder/Cargo.toml | 4 +- .../test-parachains/adder/collator/Cargo.toml | 14 +- .../parachain/test-parachains/halt/Cargo.toml | 2 +- .../test-parachains/undying/Cargo.toml | 4 +- .../undying/collator/Cargo.toml | 14 +- polkadot/primitives/Cargo.toml | 12 +- polkadot/primitives/test-helpers/Cargo.toml | 8 +- polkadot/rpc/Cargo.toml | 28 +- polkadot/runtime/common/Cargo.toml | 34 +- .../common/slot_range_helper/Cargo.toml | 4 +- polkadot/runtime/metrics/Cargo.toml | 4 +- polkadot/runtime/parachains/Cargo.toml | 46 +- polkadot/runtime/rococo/Cargo.toml | 60 +- polkadot/runtime/rococo/constants/Cargo.toml | 2 +- polkadot/runtime/test-runtime/Cargo.toml | 44 +- polkadot/runtime/westend/Cargo.toml | 56 +- polkadot/runtime/westend/constants/Cargo.toml | 2 +- polkadot/statement-table/Cargo.toml | 4 +- .../remote-ext-tests/bags-list/Cargo.toml | 4 +- polkadot/xcm/Cargo.toml | 14 +- polkadot/xcm/docs/Cargo.toml | 16 +- polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml | 12 +- polkadot/xcm/pallet-xcm/Cargo.toml | 4 +- polkadot/xcm/procedural/Cargo.toml | 2 +- polkadot/xcm/xcm-builder/Cargo.toml | 26 +- polkadot/xcm/xcm-executor/Cargo.toml | 12 +- .../xcm-executor/integration-tests/Cargo.toml | 6 +- polkadot/xcm/xcm-runtime-apis/Cargo.toml | 14 +- polkadot/xcm/xcm-simulator/Cargo.toml | 12 +- polkadot/xcm/xcm-simulator/example/Cargo.toml | 18 +- polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml | 20 +- polkadot/zombienet-sdk-tests/Cargo.toml | 8 +- substrate/bin/node/bench/Cargo.toml | 34 +- substrate/bin/node/cli/Cargo.toml | 26 +- substrate/bin/node/inspect/Cargo.toml | 2 +- substrate/bin/node/rpc/Cargo.toml | 6 +- substrate/bin/node/runtime/Cargo.toml | 4 +- substrate/bin/node/testing/Cargo.toml | 12 +- .../bin/utils/chain-spec-builder/Cargo.toml | 4 +- substrate/client/allocator/Cargo.toml | 2 +- substrate/client/api/Cargo.toml | 2 +- .../client/authority-discovery/Cargo.toml | 8 +- substrate/client/block-builder/Cargo.toml | 2 +- substrate/client/chain-spec/Cargo.toml | 20 +- substrate/client/cli/Cargo.toml | 6 +- substrate/client/consensus/aura/Cargo.toml | 4 +- substrate/client/consensus/babe/Cargo.toml | 6 +- .../client/consensus/babe/rpc/Cargo.toml | 10 +- substrate/client/consensus/beefy/Cargo.toml | 8 +- .../client/consensus/beefy/rpc/Cargo.toml | 10 +- substrate/client/consensus/common/Cargo.toml | 4 +- substrate/client/consensus/grandpa/Cargo.toml | 20 +- .../client/consensus/grandpa/rpc/Cargo.toml | 8 +- .../client/consensus/manual-seal/Cargo.toml | 8 +- substrate/client/consensus/pow/Cargo.toml | 2 +- substrate/client/db/Cargo.toml | 8 +- substrate/client/executor/Cargo.toml | 20 +- substrate/client/executor/common/Cargo.toml | 6 +- substrate/client/executor/wasmtime/Cargo.toml | 20 +- substrate/client/informant/Cargo.toml | 2 +- substrate/client/keystore/Cargo.toml | 2 +- .../client/merkle-mountain-range/Cargo.toml | 6 +- substrate/client/network-gossip/Cargo.toml | 6 +- substrate/client/network/Cargo.toml | 36 +- substrate/client/network/light/Cargo.toml | 6 +- substrate/client/network/statement/Cargo.toml | 2 +- substrate/client/network/sync/Cargo.toml | 16 +- substrate/client/network/test/Cargo.toml | 6 +- .../client/network/transactions/Cargo.toml | 2 +- substrate/client/offchain/Cargo.toml | 12 +- substrate/client/rpc-api/Cargo.toml | 10 +- substrate/client/rpc-spec-v2/Cargo.toml | 56 +- substrate/client/rpc/Cargo.toml | 8 +- substrate/client/runtime-utilities/Cargo.toml | 4 +- substrate/client/service/Cargo.toml | 86 +- substrate/client/service/test/Cargo.toml | 10 +- substrate/client/statement-store/Cargo.toml | 12 +- substrate/client/storage-monitor/Cargo.toml | 4 +- substrate/client/sync-state-rpc/Cargo.toml | 6 +- substrate/client/sysinfo/Cargo.toml | 4 +- substrate/client/telemetry/Cargo.toml | 4 +- substrate/client/tracing/Cargo.toml | 20 +- substrate/client/transaction-pool/Cargo.toml | 6 +- .../client/transaction-pool/api/Cargo.toml | 2 +- substrate/frame/Cargo.toml | 16 +- substrate/frame/alliance/Cargo.toml | 4 +- substrate/frame/asset-conversion/Cargo.toml | 8 +- .../frame/asset-conversion/ops/Cargo.toml | 8 +- substrate/frame/asset-rate/Cargo.toml | 6 +- substrate/frame/assets-freezer/Cargo.toml | 8 +- substrate/frame/assets/Cargo.toml | 4 +- substrate/frame/atomic-swap/Cargo.toml | 2 +- substrate/frame/aura/Cargo.toml | 4 +- .../frame/authority-discovery/Cargo.toml | 2 +- substrate/frame/authorship/Cargo.toml | 4 +- substrate/frame/babe/Cargo.toml | 4 +- substrate/frame/bags-list/Cargo.toml | 12 +- substrate/frame/bags-list/fuzzer/Cargo.toml | 4 +- .../frame/bags-list/remote-tests/Cargo.toml | 10 +- substrate/frame/balances/Cargo.toml | 10 +- substrate/frame/beefy-mmr/Cargo.toml | 10 +- substrate/frame/beefy/Cargo.toml | 6 +- substrate/frame/benchmarking/Cargo.toml | 10 +- substrate/frame/benchmarking/pov/Cargo.toml | 2 +- substrate/frame/bounties/Cargo.toml | 4 +- substrate/frame/broker/Cargo.toml | 12 +- substrate/frame/child-bounties/Cargo.toml | 4 +- substrate/frame/collective/Cargo.toml | 4 +- substrate/frame/contracts/Cargo.toml | 20 +- substrate/frame/contracts/fixtures/Cargo.toml | 4 +- .../frame/contracts/fixtures/build/Cargo.toml | 2 +- .../frame/contracts/mock-network/Cargo.toml | 4 +- substrate/frame/contracts/uapi/Cargo.toml | 4 +- substrate/frame/conviction-voting/Cargo.toml | 4 +- substrate/frame/core-fellowship/Cargo.toml | 6 +- substrate/frame/delegated-staking/Cargo.toml | 18 +- substrate/frame/democracy/Cargo.toml | 10 +- .../election-provider-multi-phase/Cargo.toml | 14 +- .../test-staking-e2e/Cargo.toml | 24 +- .../election-provider-support/Cargo.toml | 4 +- .../solution-type/Cargo.toml | 6 +- .../solution-type/fuzzer/Cargo.toml | 4 +- substrate/frame/elections-phragmen/Cargo.toml | 4 +- substrate/frame/examples/Cargo.toml | 4 +- substrate/frame/examples/basic/Cargo.toml | 4 +- .../frame/examples/default-config/Cargo.toml | 4 +- substrate/frame/examples/dev-mode/Cargo.toml | 4 +- .../multi-block-migrations/Cargo.toml | 4 +- .../frame/examples/offchain-worker/Cargo.toml | 4 +- .../single-block-migrations/Cargo.toml | 10 +- substrate/frame/examples/tasks/Cargo.toml | 2 +- substrate/frame/executive/Cargo.toml | 4 +- substrate/frame/fast-unstake/Cargo.toml | 10 +- substrate/frame/glutton/Cargo.toml | 6 +- substrate/frame/grandpa/Cargo.toml | 4 +- substrate/frame/identity/Cargo.toml | 4 +- substrate/frame/im-online/Cargo.toml | 4 +- substrate/frame/indices/Cargo.toml | 2 +- .../Cargo.toml | 4 +- substrate/frame/lottery/Cargo.toml | 2 +- substrate/frame/membership/Cargo.toml | 4 +- .../frame/merkle-mountain-range/Cargo.toml | 6 +- substrate/frame/message-queue/Cargo.toml | 10 +- .../frame/metadata-hash-extension/Cargo.toml | 18 +- substrate/frame/migrations/Cargo.toml | 2 +- substrate/frame/multisig/Cargo.toml | 2 +- .../frame/nft-fractionalization/Cargo.toml | 4 +- substrate/frame/nfts/Cargo.toml | 4 +- substrate/frame/nis/Cargo.toml | 2 +- substrate/frame/node-authorization/Cargo.toml | 4 +- substrate/frame/nomination-pools/Cargo.toml | 6 +- .../nomination-pools/benchmarking/Cargo.toml | 4 +- .../frame/nomination-pools/fuzzer/Cargo.toml | 6 +- .../nomination-pools/runtime-api/Cargo.toml | 2 +- .../test-delegate-stake/Cargo.toml | 20 +- .../test-transfer-stake/Cargo.toml | 18 +- substrate/frame/offences/Cargo.toml | 6 +- .../frame/offences/benchmarking/Cargo.toml | 4 +- substrate/frame/paged-list/Cargo.toml | 2 +- substrate/frame/paged-list/fuzzer/Cargo.toml | 2 +- substrate/frame/parameters/Cargo.toml | 10 +- substrate/frame/preimage/Cargo.toml | 4 +- substrate/frame/proxy/Cargo.toml | 2 +- substrate/frame/ranked-collective/Cargo.toml | 6 +- substrate/frame/recovery/Cargo.toml | 2 +- substrate/frame/referenda/Cargo.toml | 8 +- substrate/frame/remark/Cargo.toml | 4 +- substrate/frame/revive/Cargo.toml | 26 +- substrate/frame/revive/fixtures/Cargo.toml | 6 +- .../frame/revive/mock-network/Cargo.toml | 4 +- substrate/frame/revive/rpc/Cargo.toml | 28 +- substrate/frame/revive/uapi/Cargo.toml | 4 +- substrate/frame/root-offences/Cargo.toml | 2 +- substrate/frame/root-testing/Cargo.toml | 2 +- substrate/frame/safe-mode/Cargo.toml | 14 +- substrate/frame/salary/Cargo.toml | 6 +- substrate/frame/sassafras/Cargo.toml | 2 +- substrate/frame/scheduler/Cargo.toml | 6 +- substrate/frame/scored-pool/Cargo.toml | 2 +- substrate/frame/session/Cargo.toml | 8 +- .../frame/session/benchmarking/Cargo.toml | 4 +- substrate/frame/society/Cargo.toml | 8 +- substrate/frame/staking/Cargo.toml | 30 +- .../frame/state-trie-migration/Cargo.toml | 14 +- substrate/frame/statement/Cargo.toml | 10 +- substrate/frame/sudo/Cargo.toml | 2 +- substrate/frame/support/Cargo.toml | 60 +- substrate/frame/support/procedural/Cargo.toml | 22 +- .../frame/support/procedural/tools/Cargo.toml | 2 +- substrate/frame/support/test/Cargo.toml | 24 +- .../support/test/compile_pass/Cargo.toml | 2 +- .../frame/support/test/pallet/Cargo.toml | 4 +- substrate/frame/system/Cargo.toml | 4 +- .../frame/system/benchmarking/Cargo.toml | 4 +- .../frame/system/rpc/runtime-api/Cargo.toml | 2 +- substrate/frame/timestamp/Cargo.toml | 4 +- substrate/frame/tips/Cargo.toml | 6 +- .../frame/transaction-payment/Cargo.toml | 6 +- .../asset-conversion-tx-payment/Cargo.toml | 8 +- .../asset-tx-payment/Cargo.toml | 2 +- .../frame/transaction-storage/Cargo.toml | 6 +- substrate/frame/treasury/Cargo.toml | 12 +- substrate/frame/tx-pause/Cargo.toml | 12 +- substrate/frame/uniques/Cargo.toml | 4 +- substrate/frame/utility/Cargo.toml | 4 +- substrate/frame/verify-signature/Cargo.toml | 4 +- substrate/frame/vesting/Cargo.toml | 4 +- substrate/frame/whitelist/Cargo.toml | 2 +- substrate/primitives/api/Cargo.toml | 18 +- .../primitives/api/proc-macro/Cargo.toml | 10 +- substrate/primitives/api/test/Cargo.toml | 16 +- .../primitives/application-crypto/Cargo.toml | 2 +- substrate/primitives/arithmetic/Cargo.toml | 4 +- substrate/primitives/blockchain/Cargo.toml | 4 +- .../primitives/consensus/beefy/Cargo.toml | 2 +- .../primitives/consensus/common/Cargo.toml | 2 +- substrate/primitives/core/Cargo.toml | 40 +- .../primitives/crypto/ec-utils/Cargo.toml | 14 +- .../crypto/hashing/proc-macro/Cargo.toml | 2 +- substrate/primitives/debug-derive/Cargo.toml | 2 +- .../primitives/genesis-builder/Cargo.toml | 2 +- substrate/primitives/inherents/Cargo.toml | 4 +- substrate/primitives/io/Cargo.toml | 16 +- substrate/primitives/keyring/Cargo.toml | 2 +- .../merkle-mountain-range/Cargo.toml | 2 +- .../primitives/runtime-interface/Cargo.toml | 20 +- .../runtime-interface/proc-macro/Cargo.toml | 2 +- .../runtime-interface/test/Cargo.toml | 4 +- substrate/primitives/runtime/Cargo.toml | 6 +- substrate/primitives/session/Cargo.toml | 2 +- substrate/primitives/staking/Cargo.toml | 4 +- substrate/primitives/state-machine/Cargo.toml | 10 +- .../primitives/state-machine/fuzz/Cargo.toml | 2 +- .../primitives/statement-store/Cargo.toml | 14 +- substrate/primitives/timestamp/Cargo.toml | 2 +- substrate/primitives/trie/Cargo.toml | 8 +- substrate/primitives/version/Cargo.toml | 2 +- .../primitives/wasm-interface/Cargo.toml | 2 +- substrate/primitives/weights/Cargo.toml | 2 +- .../ci/node-template-release/Cargo.toml | 2 +- substrate/test-utils/Cargo.toml | 2 +- substrate/test-utils/cli/Cargo.toml | 12 +- substrate/test-utils/client/Cargo.toml | 4 +- substrate/test-utils/runtime/Cargo.toml | 52 +- .../runtime/transaction-pool/Cargo.toml | 2 +- substrate/utils/binary-merkle-tree/Cargo.toml | 6 +- .../utils/frame/benchmarking-cli/Cargo.toml | 40 +- .../utils/frame/generate-bags/Cargo.toml | 2 +- .../generate-bags/node-runtime/Cargo.toml | 2 +- substrate/utils/frame/omni-bencher/Cargo.toml | 8 +- .../frame/remote-externalities/Cargo.toml | 12 +- substrate/utils/frame/rpc/client/Cargo.toml | 6 +- substrate/utils/frame/rpc/support/Cargo.toml | 10 +- substrate/utils/frame/rpc/system/Cargo.toml | 8 +- substrate/utils/wasm-builder/Cargo.toml | 20 +- templates/minimal/node/Cargo.toml | 4 +- templates/minimal/pallets/template/Cargo.toml | 2 +- templates/minimal/runtime/Cargo.toml | 2 +- templates/parachain/node/Cargo.toml | 12 +- templates/parachain/runtime/Cargo.toml | 6 +- templates/solochain/node/Cargo.toml | 30 +- templates/solochain/runtime/Cargo.toml | 8 +- templates/zombienet/Cargo.toml | 2 +- umbrella/Cargo.toml | 752 +++++++++--------- 426 files changed, 2643 insertions(+), 2636 deletions(-) diff --git a/.config/taplo.toml b/.config/taplo.toml index 7cbc1b075125..4b8afc74a52e 100644 --- a/.config/taplo.toml +++ b/.config/taplo.toml @@ -40,3 +40,10 @@ keys = ["workspace.dependencies"] [rule.formatting] reorder_keys = true + +[[rule]] +include = ["**/Cargo.toml"] +keys = ["build-dependencies", "dependencies", "dev-dependencies"] + +[rule.formatting] +reorder_keys = true diff --git a/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml b/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml index 99ba721991ee..b9eb1d2d69c1 100644 --- a/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-cumulus/Cargo.toml @@ -16,14 +16,14 @@ workspace = true [dependencies] # Bridge Dependencies -bp-polkadot-core = { workspace = true } bp-messages = { workspace = true } +bp-polkadot-core = { workspace = true } bp-runtime = { workspace = true } # Substrate Based Dependencies -frame-system = { workspace = true } frame-support = { workspace = true } +frame-system = { workspace = true } sp-api = { workspace = true } sp-std = { workspace = true } diff --git a/bridges/chains/chain-bridge-hub-kusama/Cargo.toml b/bridges/chains/chain-bridge-hub-kusama/Cargo.toml index 39f7b44daa55..136832d0199d 100644 --- a/bridges/chains/chain-bridge-hub-kusama/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-kusama/Cargo.toml @@ -17,8 +17,8 @@ workspace = true # Bridge Dependencies bp-bridge-hub-cumulus = { workspace = true } -bp-runtime = { workspace = true } bp-messages = { workspace = true } +bp-runtime = { workspace = true } # Substrate Based Dependencies diff --git a/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml b/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml index 3b0ac96e7cd3..04ce144b7906 100644 --- a/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-polkadot/Cargo.toml @@ -18,8 +18,8 @@ workspace = true # Bridge Dependencies bp-bridge-hub-cumulus = { workspace = true } -bp-runtime = { workspace = true } bp-messages = { workspace = true } +bp-runtime = { workspace = true } # Substrate Based Dependencies diff --git a/bridges/chains/chain-bridge-hub-rococo/Cargo.toml b/bridges/chains/chain-bridge-hub-rococo/Cargo.toml index 23fbd9a2742f..08a704add2b7 100644 --- a/bridges/chains/chain-bridge-hub-rococo/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-rococo/Cargo.toml @@ -18,8 +18,8 @@ codec = { features = ["derive"], workspace = true } # Bridge Dependencies bp-bridge-hub-cumulus = { workspace = true } -bp-runtime = { workspace = true } bp-messages = { workspace = true } +bp-runtime = { workspace = true } bp-xcm-bridge-hub = { workspace = true } # Substrate Based Dependencies diff --git a/bridges/chains/chain-bridge-hub-westend/Cargo.toml b/bridges/chains/chain-bridge-hub-westend/Cargo.toml index 61357e6aa6c8..35932371d0a9 100644 --- a/bridges/chains/chain-bridge-hub-westend/Cargo.toml +++ b/bridges/chains/chain-bridge-hub-westend/Cargo.toml @@ -18,8 +18,8 @@ codec = { features = ["derive"], workspace = true } # Bridge Dependencies bp-bridge-hub-cumulus = { workspace = true } -bp-runtime = { workspace = true } bp-messages = { workspace = true } +bp-runtime = { workspace = true } bp-xcm-bridge-hub = { workspace = true } # Substrate Based Dependencies diff --git a/bridges/modules/beefy/Cargo.toml b/bridges/modules/beefy/Cargo.toml index cffc62d29082..adbf79e28b5a 100644 --- a/bridges/modules/beefy/Cargo.toml +++ b/bridges/modules/beefy/Cargo.toml @@ -31,13 +31,13 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } [dev-dependencies] -sp-consensus-beefy = { workspace = true, default-features = true } +bp-test-utils = { workspace = true, default-features = true } mmr-lib = { workspace = true } pallet-beefy-mmr = { workspace = true, default-features = true } pallet-mmr = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -bp-test-utils = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/modules/grandpa/Cargo.toml b/bridges/modules/grandpa/Cargo.toml index 6d1419ae5b03..fdca48ac6f07 100644 --- a/bridges/modules/grandpa/Cargo.toml +++ b/bridges/modules/grandpa/Cargo.toml @@ -19,8 +19,8 @@ scale-info = { features = ["derive"], workspace = true } # Bridge Dependencies -bp-runtime = { workspace = true } bp-header-chain = { workspace = true } +bp-runtime = { workspace = true } # Substrate Dependencies diff --git a/bridges/modules/messages/Cargo.toml b/bridges/modules/messages/Cargo.toml index 9df318587e38..6248c9e65e16 100644 --- a/bridges/modules/messages/Cargo.toml +++ b/bridges/modules/messages/Cargo.toml @@ -33,8 +33,8 @@ bp-runtime = { features = ["test-helpers"], workspace = true } bp-test-utils = { workspace = true } pallet-balances = { workspace = true } pallet-bridge-grandpa = { workspace = true } -sp-io = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } [features] default = ["std"] diff --git a/bridges/modules/relayers/Cargo.toml b/bridges/modules/relayers/Cargo.toml index 04e7b52ed86c..97ed61a9004e 100644 --- a/bridges/modules/relayers/Cargo.toml +++ b/bridges/modules/relayers/Cargo.toml @@ -34,15 +34,15 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } [dev-dependencies] -bp-runtime = { workspace = true } -pallet-balances = { workspace = true, default-features = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } bp-parachains = { workspace = true } bp-polkadot-core = { workspace = true } +bp-runtime = { workspace = true } bp-test-utils = { workspace = true } +pallet-balances = { workspace = true, default-features = true } pallet-utility = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } [features] default = ["std"] diff --git a/bridges/modules/xcm-bridge-hub/Cargo.toml b/bridges/modules/xcm-bridge-hub/Cargo.toml index ef49b3396b5c..b5e365874443 100644 --- a/bridges/modules/xcm-bridge-hub/Cargo.toml +++ b/bridges/modules/xcm-bridge-hub/Cargo.toml @@ -34,13 +34,13 @@ xcm-builder = { workspace = true } xcm-executor = { workspace = true } [dev-dependencies] -pallet-balances = { workspace = true } -sp-io = { workspace = true } -bp-runtime = { workspace = true } bp-header-chain = { workspace = true } -pallet-xcm-bridge-hub-router = { workspace = true } +bp-runtime = { workspace = true } bp-xcm-bridge-hub-router = { workspace = true } +pallet-balances = { workspace = true } +pallet-xcm-bridge-hub-router = { workspace = true } polkadot-parachain-primitives = { workspace = true } +sp-io = { workspace = true } [features] default = ["std"] diff --git a/bridges/primitives/beefy/Cargo.toml b/bridges/primitives/beefy/Cargo.toml index 404acaff30af..b32cf1e407eb 100644 --- a/bridges/primitives/beefy/Cargo.toml +++ b/bridges/primitives/beefy/Cargo.toml @@ -23,10 +23,10 @@ bp-runtime = { workspace = true } # Substrate Dependencies binary-merkle-tree = { workspace = true } -sp-consensus-beefy = { workspace = true } frame-support = { workspace = true } pallet-beefy-mmr = { workspace = true } pallet-mmr = { workspace = true } +sp-consensus-beefy = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } diff --git a/bridges/primitives/header-chain/Cargo.toml b/bridges/primitives/header-chain/Cargo.toml index 081bda479495..b17dcb2f7491 100644 --- a/bridges/primitives/header-chain/Cargo.toml +++ b/bridges/primitives/header-chain/Cargo.toml @@ -23,8 +23,8 @@ bp-runtime = { workspace = true } # Substrate Dependencies frame-support = { workspace = true } -sp-core = { features = ["serde"], workspace = true } sp-consensus-grandpa = { features = ["serde"], workspace = true } +sp-core = { features = ["serde"], workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-std = { workspace = true } diff --git a/bridges/primitives/messages/Cargo.toml b/bridges/primitives/messages/Cargo.toml index 87c8cbe88180..dd1bd083371f 100644 --- a/bridges/primitives/messages/Cargo.toml +++ b/bridges/primitives/messages/Cargo.toml @@ -16,19 +16,19 @@ scale-info = { features = ["bit-vec", "derive"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true } # Bridge dependencies -bp-runtime = { workspace = true } bp-header-chain = { workspace = true } +bp-runtime = { workspace = true } # Substrate Dependencies frame-support = { workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } sp-io = { workspace = true } +sp-std = { workspace = true } [dev-dependencies] +bp-runtime = { workspace = true } hex = { workspace = true, default-features = true } hex-literal = { workspace = true, default-features = true } -bp-runtime = { workspace = true } [features] default = ["std"] diff --git a/bridges/primitives/relayers/Cargo.toml b/bridges/primitives/relayers/Cargo.toml index 34be38bed4ac..9219bae1e131 100644 --- a/bridges/primitives/relayers/Cargo.toml +++ b/bridges/primitives/relayers/Cargo.toml @@ -21,8 +21,8 @@ bp-parachains = { workspace = true } bp-runtime = { workspace = true } # Substrate Dependencies -frame-system = { workspace = true } frame-support = { workspace = true } +frame-system = { workspace = true } pallet-utility = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } diff --git a/bridges/primitives/xcm-bridge-hub-router/Cargo.toml b/bridges/primitives/xcm-bridge-hub-router/Cargo.toml index ba0c51152bd2..b8a21ec35024 100644 --- a/bridges/primitives/xcm-bridge-hub-router/Cargo.toml +++ b/bridges/primitives/xcm-bridge-hub-router/Cargo.toml @@ -15,8 +15,8 @@ codec = { features = ["bit-vec", "derive"], workspace = true } scale-info = { features = ["bit-vec", "derive"], workspace = true } # Substrate Dependencies -sp-runtime = { workspace = true } sp-core = { workspace = true } +sp-runtime = { workspace = true } # Polkadot Dependencies xcm = { workspace = true } diff --git a/bridges/primitives/xcm-bridge-hub/Cargo.toml b/bridges/primitives/xcm-bridge-hub/Cargo.toml index 79201a8756f9..800e2a3da3a3 100644 --- a/bridges/primitives/xcm-bridge-hub/Cargo.toml +++ b/bridges/primitives/xcm-bridge-hub/Cargo.toml @@ -20,10 +20,10 @@ bp-messages = { workspace = true } bp-runtime = { workspace = true } # Substrate Dependencies -sp-std = { workspace = true } -sp-io = { workspace = true } -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-std = { workspace = true } # Polkadot Dependencies xcm = { workspace = true } diff --git a/bridges/relays/client-substrate/Cargo.toml b/bridges/relays/client-substrate/Cargo.toml index 6065c23773e3..6a59688b2d8c 100644 --- a/bridges/relays/client-substrate/Cargo.toml +++ b/bridges/relays/client-substrate/Cargo.toml @@ -18,16 +18,16 @@ futures = { workspace = true } jsonrpsee = { features = ["macros", "ws-client"], workspace = true } log = { workspace = true } num-traits = { workspace = true, default-features = true } +quick_cache = { workspace = true } rand = { workspace = true, default-features = true } -serde_json = { workspace = true } scale-info = { features = [ "derive", ], workspace = true, default-features = true } +serde_json = { workspace = true } +thiserror = { workspace = true } tokio = { features = [ "rt-multi-thread", ], workspace = true, default-features = true } -thiserror = { workspace = true } -quick_cache = { workspace = true } # Bridge dependencies diff --git a/bridges/relays/lib-substrate-relay/Cargo.toml b/bridges/relays/lib-substrate-relay/Cargo.toml index b0f93e5b5485..b418a2a3abb8 100644 --- a/bridges/relays/lib-substrate-relay/Cargo.toml +++ b/bridges/relays/lib-substrate-relay/Cargo.toml @@ -32,29 +32,29 @@ bp-relayers = { workspace = true, default-features = true } equivocation-detector = { workspace = true } finality-relay = { workspace = true } -parachains-relay = { workspace = true } -relay-utils = { workspace = true } messages-relay = { workspace = true } +parachains-relay = { workspace = true } relay-substrate-client = { workspace = true } +relay-utils = { workspace = true } pallet-bridge-grandpa = { workspace = true, default-features = true } pallet-bridge-messages = { workspace = true, default-features = true } pallet-bridge-parachains = { workspace = true, default-features = true } -bp-runtime = { workspace = true, default-features = true } bp-messages = { workspace = true, default-features = true } +bp-runtime = { workspace = true, default-features = true } # Substrate Dependencies frame-support = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-grandpa = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-trie = { workspace = true } [dev-dependencies] -scale-info = { features = ["derive"], workspace = true } pallet-transaction-payment = { workspace = true, default-features = true } relay-substrate-client = { features = ["test-helpers"], workspace = true } +scale-info = { features = ["derive"], workspace = true } diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml index 4c25566607dc..8592ca780eaa 100644 --- a/bridges/relays/utils/Cargo.toml +++ b/bridges/relays/utils/Cargo.toml @@ -16,18 +16,18 @@ async-std = { workspace = true } async-trait = { workspace = true } backoff = { workspace = true } console = { workspace = true } -isahc = { workspace = true } -sp-tracing = { workspace = true, default-features = true } futures = { workspace = true } +isahc = { workspace = true } jsonpath_lib = { workspace = true } log = { workspace = true } num-traits = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } sysinfo = { workspace = true } +thiserror = { workspace = true } time = { features = ["formatting", "local-offset", "std"], workspace = true } tokio = { features = ["rt"], workspace = true, default-features = true } -thiserror = { workspace = true } # Bridge dependencies @@ -35,5 +35,5 @@ bp-runtime = { workspace = true, default-features = true } # Substrate dependencies -sp-runtime = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } diff --git a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml index 262d9a7f380d..ebd8a1c6ed11 100644 --- a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml @@ -15,37 +15,37 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { optional = true, workspace = true, default-features = true } -serde_json = { optional = true, workspace = true, default-features = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } hex-literal = { optional = true, workspace = true, default-features = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } +serde_json = { optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } -sp-runtime = { workspace = true } sp-io = { optional = true, workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +pallet-timestamp = { optional = true, workspace = true } +snowbridge-beacon-primitives = { workspace = true } snowbridge-core = { workspace = true } snowbridge-ethereum = { workspace = true } snowbridge-pallet-ethereum-client-fixtures = { optional = true, workspace = true } -snowbridge-beacon-primitives = { workspace = true } static_assertions = { workspace = true } -pallet-timestamp = { optional = true, workspace = true } [dev-dependencies] -rand = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } hex-literal = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } snowbridge-pallet-ethereum-client-fixtures = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -serde = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml index 87f0cf9a5513..74bfe580ec36 100644 --- a/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/fixtures/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] hex-literal = { workspace = true, default-features = true } +snowbridge-beacon-primitives = { workspace = true } +snowbridge-core = { workspace = true } sp-core = { workspace = true } sp-std = { workspace = true } -snowbridge-core = { workspace = true } -snowbridge-beacon-primitives = { workspace = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml index c0789940a9e8..5d4e8ad67662 100644 --- a/bridges/snowbridge/pallets/inbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/Cargo.toml @@ -15,35 +15,35 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { optional = true, workspace = true, default-features = true } +alloy-core = { workspace = true, features = ["sol-types"] } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } hex-literal = { optional = true, workspace = true, default-features = true } log = { workspace = true } -alloy-core = { workspace = true, features = ["sol-types"] } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-balances = { workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } -snowbridge-core = { workspace = true } -snowbridge-router-primitives = { workspace = true } snowbridge-beacon-primitives = { workspace = true } +snowbridge-core = { workspace = true } snowbridge-pallet-inbound-queue-fixtures = { optional = true, workspace = true } +snowbridge-router-primitives = { workspace = true } [dev-dependencies] frame-benchmarking = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -snowbridge-pallet-ethereum-client = { workspace = true, default-features = true } hex-literal = { workspace = true, default-features = true } +snowbridge-pallet-ethereum-client = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml index 6162a17728b6..c698dbbf1003 100644 --- a/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] hex-literal = { workspace = true, default-features = true } +snowbridge-beacon-primitives = { workspace = true } +snowbridge-core = { workspace = true } sp-core = { workspace = true } sp-std = { workspace = true } -snowbridge-core = { workspace = true } -snowbridge-beacon-primitives = { workspace = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml index 78546e258daa..f4910e6e6457 100644 --- a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml @@ -15,24 +15,24 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { features = ["alloc", "derive"], workspace = true } codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } +serde = { features = ["alloc", "derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } -sp-runtime = { workspace = true } sp-io = { workspace = true } -sp-arithmetic = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } bridge-hub-common = { workspace = true } +ethabi = { workspace = true } snowbridge-core = { features = ["serde"], workspace = true } snowbridge-outbound-queue-merkle-tree = { workspace = true } -ethabi = { workspace = true } [dev-dependencies] pallet-message-queue = { workspace = true } diff --git a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml index 16241428df80..2a0616b4f954 100644 --- a/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/merkle-tree/Cargo.toml @@ -22,9 +22,9 @@ sp-core = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] -hex-literal = { workspace = true, default-features = true } -hex = { workspace = true, default-features = true } array-bytes = { workspace = true, default-features = true } +hex = { workspace = true, default-features = true } +hex-literal = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } diff --git a/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml index d35bdde5a81e..18f7dde22c93 100644 --- a/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/runtime-api/Cargo.toml @@ -16,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -sp-std = { workspace = true } -sp-api = { workspace = true } frame-support = { workspace = true } -snowbridge-outbound-queue-merkle-tree = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-outbound-queue-merkle-tree = { workspace = true } +sp-api = { workspace = true } +sp-std = { workspace = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/system/Cargo.toml b/bridges/snowbridge/pallets/system/Cargo.toml index d8e124d73d14..3544925956b4 100644 --- a/bridges/snowbridge/pallets/system/Cargo.toml +++ b/bridges/snowbridge/pallets/system/Cargo.toml @@ -18,16 +18,16 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } @@ -38,10 +38,10 @@ snowbridge-core = { workspace = true } hex = { workspace = true, default-features = true } hex-literal = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } snowbridge-pallet-outbound-queue = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml index 7c524dd2edad..fc377b460d33 100644 --- a/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/system/runtime-api/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -sp-std = { workspace = true } +snowbridge-core = { workspace = true } sp-api = { workspace = true } +sp-std = { workspace = true } xcm = { workspace = true } -snowbridge-core = { workspace = true } [features] default = ["std"] diff --git a/bridges/snowbridge/primitives/beacon/Cargo.toml b/bridges/snowbridge/primitives/beacon/Cargo.toml index 9ced99fbf3fd..bf5d6838f7bb 100644 --- a/bridges/snowbridge/primitives/beacon/Cargo.toml +++ b/bridges/snowbridge/primitives/beacon/Cargo.toml @@ -12,24 +12,24 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { optional = true, features = ["derive"], workspace = true, default-features = true } -hex = { workspace = true } codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } +hex = { workspace = true } rlp = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } frame-support = { workspace = true } -sp-runtime = { workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } +byte-slice-cast = { workspace = true } ssz_rs = { workspace = true } ssz_rs_derive = { workspace = true } -byte-slice-cast = { workspace = true } -snowbridge-ethereum = { workspace = true } milagro-bls = { workspace = true } +snowbridge-ethereum = { workspace = true } [dev-dependencies] hex-literal = { workspace = true, default-features = true } diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index af002a5a965c..514579400aca 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -12,10 +12,10 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { optional = true, features = ["alloc", "derive"], workspace = true } codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } hex-literal = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } polkadot-parachain-primitives = { workspace = true } xcm = { workspace = true } @@ -23,11 +23,11 @@ xcm-builder = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-arithmetic = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } -sp-io = { workspace = true } -sp-core = { workspace = true } -sp-arithmetic = { workspace = true } snowbridge-beacon-primitives = { workspace = true } diff --git a/bridges/snowbridge/primitives/ethereum/Cargo.toml b/bridges/snowbridge/primitives/ethereum/Cargo.toml index 764ce90b8139..44ea2d0d222b 100644 --- a/bridges/snowbridge/primitives/ethereum/Cargo.toml +++ b/bridges/snowbridge/primitives/ethereum/Cargo.toml @@ -12,26 +12,26 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -serde = { optional = true, features = ["derive"], workspace = true, default-features = true } -serde-big-array = { optional = true, features = ["const-generics"], workspace = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } ethbloom = { workspace = true } ethereum-types = { features = ["codec", "rlp", "serialize"], workspace = true } hex-literal = { workspace = true } parity-bytes = { workspace = true } rlp = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, features = ["derive"], workspace = true, default-features = true } +serde-big-array = { optional = true, features = ["const-generics"], workspace = true } sp-io = { workspace = true } -sp-std = { workspace = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } ethabi = { workspace = true } [dev-dependencies] -wasm-bindgen-test = { workspace = true } rand = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } +wasm-bindgen-test = { workspace = true } [features] default = ["std"] diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index 1f7f489c6b18..e44cca077ef3 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -13,8 +13,8 @@ workspace = true [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } sp-core = { workspace = true } diff --git a/bridges/snowbridge/runtime/runtime-common/Cargo.toml b/bridges/snowbridge/runtime/runtime-common/Cargo.toml index 514a4c186696..23cd0adf1226 100644 --- a/bridges/snowbridge/runtime/runtime-common/Cargo.toml +++ b/bridges/snowbridge/runtime/runtime-common/Cargo.toml @@ -12,11 +12,11 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] -log = { workspace = true } codec = { workspace = true } frame-support = { workspace = true } -sp-std = { workspace = true } +log = { workspace = true } sp-arithmetic = { workspace = true } +sp-std = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } diff --git a/bridges/snowbridge/runtime/test-common/Cargo.toml b/bridges/snowbridge/runtime/test-common/Cargo.toml index cc1ed1288ca0..184a0ff2329f 100644 --- a/bridges/snowbridge/runtime/test-common/Cargo.toml +++ b/bridges/snowbridge/runtime/test-common/Cargo.toml @@ -19,8 +19,8 @@ codec = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-balances = { workspace = true } -pallet-session = { workspace = true } pallet-message-queue = { workspace = true } +pallet-session = { workspace = true } pallet-timestamp = { workspace = true } pallet-utility = { workspace = true } sp-core = { workspace = true } diff --git a/cumulus/bin/pov-validator/Cargo.toml b/cumulus/bin/pov-validator/Cargo.toml index 9be92960ad77..d7af29a6bcb2 100644 --- a/cumulus/bin/pov-validator/Cargo.toml +++ b/cumulus/bin/pov-validator/Cargo.toml @@ -9,18 +9,18 @@ homepage.workspace = true description = "A tool for validating PoVs locally" [dependencies] -codec.workspace = true +anyhow.workspace = true clap = { workspace = true, features = ["derive"] } -sc-executor.workspace = true -sp-io.workspace = true -sp-core.workspace = true -sp-maybe-compressed-blob.workspace = true +codec.workspace = true polkadot-node-primitives.workspace = true polkadot-parachain-primitives.workspace = true polkadot-primitives.workspace = true -anyhow.workspace = true -tracing.workspace = true +sc-executor.workspace = true +sp-core.workspace = true +sp-io.workspace = true +sp-maybe-compressed-blob.workspace = true tracing-subscriber.workspace = true +tracing.workspace = true [lints] workspace = true diff --git a/cumulus/client/cli/Cargo.toml b/cumulus/client/cli/Cargo.toml index 198f9428f1dd..bdc0236e368f 100644 --- a/cumulus/client/cli/Cargo.toml +++ b/cumulus/client/cli/Cargo.toml @@ -17,10 +17,10 @@ codec = { workspace = true, default-features = true } url = { workspace = true } # Substrate +sc-chain-spec = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -sc-chain-spec = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } diff --git a/cumulus/client/collator/Cargo.toml b/cumulus/client/collator/Cargo.toml index 83a3f2661e7a..ff591c2d6e3a 100644 --- a/cumulus/client/collator/Cargo.toml +++ b/cumulus/client/collator/Cargo.toml @@ -12,15 +12,15 @@ repository.workspace = true workspace = true [dependencies] -parking_lot = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true, default-features = true } futures = { workspace = true } +parking_lot = { workspace = true, default-features = true } tracing = { workspace = true, default-features = true } # Substrate sc-client-api = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } @@ -48,5 +48,5 @@ polkadot-node-subsystem-test-helpers = { workspace = true } # Cumulus cumulus-test-client = { workspace = true } -cumulus-test-runtime = { workspace = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } +cumulus-test-runtime = { workspace = true } diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 33f24e30ccfb..702230938645 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -16,18 +16,19 @@ async-trait = { workspace = true } codec = { features = ["derive"], workspace = true, default-features = true } futures = { workspace = true } parking_lot = { workspace = true } -tracing = { workspace = true, default-features = true } schnellru = { workspace = true } tokio = { workspace = true, features = ["macros"] } +tracing = { workspace = true, default-features = true } # Substrate +prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-consensus-aura = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-slots = { workspace = true, default-features = true } -sc-utils = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } +sc-utils = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } @@ -35,29 +36,28 @@ sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-consensus-aura = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-trie = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } +sp-trie = { workspace = true, default-features = true } # Cumulus +cumulus-client-collator = { workspace = true, default-features = true } cumulus-client-consensus-common = { workspace = true, default-features = true } -cumulus-relay-chain-interface = { workspace = true, default-features = true } cumulus-client-consensus-proposer = { workspace = true, default-features = true } cumulus-client-parachain-inherent = { workspace = true, default-features = true } cumulus-primitives-aura = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } -cumulus-client-collator = { workspace = true, default-features = true } +cumulus-relay-chain-interface = { workspace = true, default-features = true } # Polkadot -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } [features] # Allows collator to use full PoV size for block building diff --git a/cumulus/client/consensus/common/Cargo.toml b/cumulus/client/consensus/common/Cargo.toml index 0f532a2101c4..5bc5160601e7 100644 --- a/cumulus/client/consensus/common/Cargo.toml +++ b/cumulus/client/consensus/common/Cargo.toml @@ -20,6 +20,7 @@ log = { workspace = true, default-features = true } tracing = { workspace = true, default-features = true } # Substrate +prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } @@ -31,15 +32,14 @@ sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } # Polkadot polkadot-primitives = { workspace = true, default-features = true } # Cumulus +cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } -cumulus-client-pov-recovery = { workspace = true, default-features = true } schnellru = { workspace = true } [dev-dependencies] diff --git a/cumulus/client/consensus/relay-chain/Cargo.toml b/cumulus/client/consensus/relay-chain/Cargo.toml index 7f0f4333c961..fdc343dc65de 100644 --- a/cumulus/client/consensus/relay-chain/Cargo.toml +++ b/cumulus/client/consensus/relay-chain/Cargo.toml @@ -18,6 +18,7 @@ parking_lot = { workspace = true, default-features = true } tracing = { workspace = true, default-features = true } # Substrate +prometheus-endpoint = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } @@ -26,7 +27,6 @@ sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } # Cumulus cumulus-client-consensus-common = { workspace = true, default-features = true } diff --git a/cumulus/client/network/Cargo.toml b/cumulus/client/network/Cargo.toml index b78df8d73eae..11025f8f62e6 100644 --- a/cumulus/client/network/Cargo.toml +++ b/cumulus/client/network/Cargo.toml @@ -21,28 +21,28 @@ tracing = { workspace = true, default-features = true } # Substrate sc-client-api = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } # Polkadot polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } # Cumulus cumulus-relay-chain-interface = { workspace = true, default-features = true } [dev-dependencies] portpicker = { workspace = true } +rstest = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } url = { workspace = true } -rstest = { workspace = true } # Substrate sc-cli = { workspace = true, default-features = true } diff --git a/cumulus/client/pov-recovery/Cargo.toml b/cumulus/client/pov-recovery/Cargo.toml index 762837e0bb11..7e7da7244a86 100644 --- a/cumulus/client/pov-recovery/Cargo.toml +++ b/cumulus/client/pov-recovery/Cargo.toml @@ -21,10 +21,10 @@ tracing = { workspace = true, default-features = true } # Substrate sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } # Polkadot @@ -34,19 +34,19 @@ polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } # Cumulus +async-trait = { workspace = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } -async-trait = { workspace = true } [dev-dependencies] -rstest = { workspace = true } -tokio = { features = ["macros"], workspace = true, default-features = true } -portpicker = { workspace = true } -sp-blockchain = { workspace = true, default-features = true } +assert_matches = { workspace = true } cumulus-test-client = { workspace = true } +portpicker = { workspace = true } +rstest = { workspace = true } sc-utils = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -assert_matches = { workspace = true } +tokio = { features = ["macros"], workspace = true, default-features = true } # Cumulus cumulus-test-service = { workspace = true } diff --git a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml index 9e6e8da929bb..2a590bbca562 100644 --- a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml +++ b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml @@ -19,9 +19,9 @@ futures-timer = { workspace = true } # Substrate sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } +sc-sysinfo = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } -sc-sysinfo = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } @@ -42,9 +42,9 @@ cumulus-relay-chain-interface = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } # Polkadot +metered = { features = ["futures_channel"], workspace = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-test-client = { workspace = true } -metered = { features = ["futures_channel"], workspace = true } # Cumulus cumulus-test-service = { workspace = true } diff --git a/cumulus/client/relay-chain-interface/Cargo.toml b/cumulus/client/relay-chain-interface/Cargo.toml index 2b9e72bbeca6..659d3b0f5b27 100644 --- a/cumulus/client/relay-chain-interface/Cargo.toml +++ b/cumulus/client/relay-chain-interface/Cargo.toml @@ -16,14 +16,14 @@ polkadot-overseer = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } sp-version = { workspace = true } -futures = { workspace = true } async-trait = { workspace = true } -thiserror = { workspace = true } -jsonrpsee-core = { workspace = true } codec = { workspace = true, default-features = true } +futures = { workspace = true } +jsonrpsee-core = { workspace = true } +thiserror = { workspace = true } diff --git a/cumulus/client/relay-chain-minimal-node/Cargo.toml b/cumulus/client/relay-chain-minimal-node/Cargo.toml index 0fad188bb1ab..5b1e30cea9ba 100644 --- a/cumulus/client/relay-chain-minimal-node/Cargo.toml +++ b/cumulus/client/relay-chain-minimal-node/Cargo.toml @@ -13,37 +13,37 @@ workspace = true [dependencies] # polkadot deps -polkadot-primitives = { workspace = true, default-features = true } polkadot-core-primitives = { workspace = true, default-features = true } -polkadot-overseer = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-network-bridge = { workspace = true, default-features = true } polkadot-service = { workspace = true, default-features = true } # substrate deps +prometheus-endpoint = { workspace = true, default-features = true } sc-authority-discovery = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } tokio = { features = ["macros"], workspace = true, default-features = true } # cumulus deps +cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } cumulus-relay-chain-rpc-interface = { workspace = true, default-features = true } -cumulus-primitives-core = { workspace = true, default-features = true } array-bytes = { workspace = true, default-features = true } -tracing = { workspace = true, default-features = true } async-trait = { workspace = true } futures = { workspace = true } +tracing = { workspace = true, default-features = true } diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml index 162f5ad0e9e8..50b438e34237 100644 --- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml +++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml @@ -20,36 +20,36 @@ polkadot-overseer = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-rpc-api = { workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } sp-authority-discovery = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } sp-storage = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-rpc-api = { workspace = true, default-features = true } -sc-service = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } tokio = { features = ["sync"], workspace = true, default-features = true } tokio-util = { features = ["compat"], workspace = true } +async-trait = { workspace = true } +codec = { workspace = true, default-features = true } +either = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } -codec = { workspace = true, default-features = true } jsonrpsee = { features = ["ws-client"], workspace = true } -tracing = { workspace = true, default-features = true } -async-trait = { workspace = true } -url = { workspace = true } -serde_json = { workspace = true, default-features = true } -serde = { workspace = true, default-features = true } +pin-project = { workspace = true } +prometheus = { workspace = true } +rand = { workspace = true, default-features = true } schnellru = { workspace = true } +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } smoldot = { default_features = false, features = ["std"], workspace = true } smoldot-light = { default_features = false, features = ["std"], workspace = true } -either = { workspace = true, default-features = true } thiserror = { workspace = true } -rand = { workspace = true, default-features = true } -pin-project = { workspace = true } -prometheus = { workspace = true } +tracing = { workspace = true, default-features = true } +url = { workspace = true } diff --git a/cumulus/client/service/Cargo.toml b/cumulus/client/service/Cargo.toml index 193283648f19..c88386b985a4 100644 --- a/cumulus/client/service/Cargo.toml +++ b/cumulus/client/service/Cargo.toml @@ -18,22 +18,22 @@ futures-timer = { workspace = true } # Substrate sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } -sc-transaction-pool = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sc-network-sync = { workspace = true, default-features = true } +sc-network-transactions = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } sc-sysinfo = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -sc-network-sync = { workspace = true, default-features = true } +sc-transaction-pool = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } -sc-network-transactions = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-transaction-pool = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } # Polkadot polkadot-primitives = { workspace = true, default-features = true } @@ -42,10 +42,10 @@ polkadot-primitives = { workspace = true, default-features = true } cumulus-client-cli = { workspace = true, default-features = true } cumulus-client-collator = { workspace = true, default-features = true } cumulus-client-consensus-common = { workspace = true, default-features = true } -cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-client-network = { workspace = true, default-features = true } +cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } -cumulus-relay-chain-interface = { workspace = true, default-features = true } cumulus-relay-chain-inprocess-interface = { workspace = true, default-features = true } +cumulus-relay-chain-interface = { workspace = true, default-features = true } cumulus-relay-chain-minimal-node = { workspace = true, default-features = true } diff --git a/cumulus/pallets/collator-selection/Cargo.toml b/cumulus/pallets/collator-selection/Cargo.toml index 8d67db3daf8b..651cceebbc6e 100644 --- a/cumulus/pallets/collator-selection/Cargo.toml +++ b/cumulus/pallets/collator-selection/Cargo.toml @@ -16,29 +16,29 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { workspace = true } codec = { features = ["derive"], workspace = true } +log = { workspace = true } rand = { features = ["std_rng"], workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-runtime = { workspace = true } -sp-staking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } pallet-session = { workspace = true } +sp-runtime = { workspace = true } +sp-staking = { workspace = true } frame-benchmarking = { optional = true, workspace = true } [dev-dependencies] +pallet-aura = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +sp-consensus-aura = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } -sp-consensus-aura = { workspace = true, default-features = true } -pallet-aura = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/pallets/dmp-queue/Cargo.toml b/cumulus/pallets/dmp-queue/Cargo.toml index ae85a108fe72..4f5bbc97bfc2 100644 --- a/cumulus/pallets/dmp-queue/Cargo.toml +++ b/cumulus/pallets/dmp-queue/Cargo.toml @@ -21,8 +21,8 @@ scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-runtime = { workspace = true } sp-io = { workspace = true } +sp-runtime = { workspace = true } # Polkadot xcm = { workspace = true } diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index c911f8531da2..6b6bc4fbcefe 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -17,8 +17,8 @@ codec = { features = ["derive"], workspace = true } environmental = { workspace = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true } -trie-db = { workspace = true } scale-info = { features = ["derive"], workspace = true } +trie-db = { workspace = true } # Substrate frame-benchmarking = { optional = true, workspace = true } @@ -49,18 +49,18 @@ cumulus-primitives-proof-size-hostfunction = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } +futures = { workspace = true } hex-literal = { workspace = true, default-features = true } -trie-standardmap = { workspace = true } rand = { workspace = true, default-features = true } -futures = { workspace = true } +trie-standardmap = { workspace = true } # Substrate sc-client-api = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } +sp-consensus-slots = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -sp-consensus-slots = { workspace = true, default-features = true } # Cumulus cumulus-test-client = { workspace = true } diff --git a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml index 629818f9c4cc..d4485a400cb8 100644 --- a/cumulus/pallets/parachain-system/proc-macro/Cargo.toml +++ b/cumulus/pallets/parachain-system/proc-macro/Cargo.toml @@ -15,10 +15,10 @@ workspace = true proc-macro = true [dependencies] -syn = { workspace = true } +proc-macro-crate = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } -proc-macro-crate = { workspace = true } +syn = { workspace = true } [features] default = ["std"] diff --git a/cumulus/pallets/session-benchmarking/Cargo.toml b/cumulus/pallets/session-benchmarking/Cargo.toml index 5af94434e0af..6d77e567c9b6 100644 --- a/cumulus/pallets/session-benchmarking/Cargo.toml +++ b/cumulus/pallets/session-benchmarking/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -sp-runtime = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } pallet-session = { workspace = true } +sp-runtime = { workspace = true } [features] default = ["std"] diff --git a/cumulus/pallets/xcm/Cargo.toml b/cumulus/pallets/xcm/Cargo.toml index ff9be866d48f..25938763c956 100644 --- a/cumulus/pallets/xcm/Cargo.toml +++ b/cumulus/pallets/xcm/Cargo.toml @@ -15,10 +15,10 @@ workspace = true codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } xcm = { workspace = true } diff --git a/cumulus/pallets/xcmp-queue/Cargo.toml b/cumulus/pallets/xcmp-queue/Cargo.toml index 432be3027e05..43dfae8927d2 100644 --- a/cumulus/pallets/xcmp-queue/Cargo.toml +++ b/cumulus/pallets/xcmp-queue/Cargo.toml @@ -19,24 +19,24 @@ scale-info = { features = ["derive"], workspace = true } # Substrate frame-support = { workspace = true } frame-system = { workspace = true } -sp-io = { workspace = true } +pallet-message-queue = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } -pallet-message-queue = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true } polkadot-runtime-parachains = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } # Cumulus cumulus-primitives-core = { workspace = true } # Optional import for benchmarking -frame-benchmarking = { optional = true, workspace = true } bounded-collections = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } # Bridges bp-xcm-bridge-hub-router = { optional = true, workspace = true } @@ -44,9 +44,9 @@ bp-xcm-bridge-hub-router = { optional = true, workspace = true } [dev-dependencies] # Substrate -sp-core = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } frame-support = { features = ["experimental"], workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } # Cumulus cumulus-pallet-parachain-system = { workspace = true, default-features = true } diff --git a/cumulus/parachains/common/Cargo.toml b/cumulus/parachains/common/Cargo.toml index ae4d7fc1d115..6c52c3201c71 100644 --- a/cumulus/parachains/common/Cargo.toml +++ b/cumulus/parachains/common/Cargo.toml @@ -39,9 +39,9 @@ xcm = { workspace = true } xcm-executor = { workspace = true } # Cumulus -pallet-collator-selection = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } +pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } [dev-dependencies] diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml index 25796e7d64b4..a164a8197f72 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml @@ -13,15 +13,15 @@ workspace = true [dependencies] # Substrate +frame-support = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } -frame-support = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } +asset-hub-rococo-runtime = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } -asset-hub-rococo-runtime = { workspace = true, default-features = true } +parachains-common = { workspace = true, default-features = true } rococo-emulated-chain = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml index 8e423ebbf9c2..c67b94d0db73 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/Cargo.toml @@ -13,17 +13,17 @@ workspace = true [dependencies] # Substrate +frame-support = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } -frame-support = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } +asset-hub-westend-runtime = { workspace = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } -asset-hub-westend-runtime = { workspace = true } -westend-emulated-chain = { workspace = true, default-features = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } +westend-emulated-chain = { workspace = true, default-features = true } # Polkadot xcm = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml index 231265085eda..8b16d8ac27ae 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/Cargo.toml @@ -13,9 +13,9 @@ workspace = true [dependencies] # Substrate +frame-support = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } -frame-support = { workspace = true } # Polkadot Dependencies xcm = { workspace = true } @@ -24,8 +24,8 @@ xcm = { workspace = true } bp-messages = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } -emulated-integration-tests-common = { workspace = true } -bridge-hub-rococo-runtime = { workspace = true, default-features = true } bridge-hub-common = { workspace = true } +bridge-hub-rococo-runtime = { workspace = true, default-features = true } +emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["rococo"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml index 8292e132809c..292b5bd3e434 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/Cargo.toml @@ -13,9 +13,9 @@ workspace = true [dependencies] # Substrate +frame-support = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } -frame-support = { workspace = true } # Polkadot Dependencies xcm = { workspace = true } @@ -24,8 +24,8 @@ xcm = { workspace = true } bp-messages = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } -emulated-integration-tests-common = { workspace = true } -bridge-hub-westend-runtime = { workspace = true, default-features = true } bridge-hub-common = { workspace = true } +bridge-hub-westend-runtime = { workspace = true, default-features = true } +emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml index 87dfd73ab05b..55e3ad6743ed 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/Cargo.toml @@ -13,12 +13,12 @@ workspace = true [dependencies] # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } +collectives-westend-runtime = { workspace = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } -collectives-westend-runtime = { workspace = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml index 94d43c5eee2f..8f12dc675199 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/Cargo.toml @@ -13,12 +13,12 @@ workspace = true [dependencies] # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } -cumulus-primitives-core = { workspace = true } coretime-rococo-runtime = { workspace = true, default-features = true } +cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["rococo"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml index 2640c27d016b..fad1000ac66c 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/Cargo.toml @@ -13,12 +13,12 @@ workspace = true [dependencies] # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } -cumulus-primitives-core = { workspace = true } coretime-westend-runtime = { workspace = true, default-features = true } +cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml index 1549d6a2ab6b..c98e8629e31d 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/Cargo.toml @@ -10,12 +10,12 @@ publish = false [dependencies] # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } people-rococo-runtime = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml index 9c5ac0bca9de..598ba5488f85 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/Cargo.toml @@ -10,12 +10,12 @@ publish = false [dependencies] # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } +sp-core = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } people-westend-runtime = { workspace = true } testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml index 743cd7dc54a2..7e92e3bf9448 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/Cargo.toml @@ -13,15 +13,15 @@ workspace = true [dependencies] # Substrate +frame-support = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } -frame-support = { workspace = true } # Polkadot xcm = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } penpal-runtime = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml index 6db1263df8c7..ccf3854e67d8 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/Cargo.toml @@ -13,18 +13,18 @@ workspace = true [dependencies] # Substrate -sp-core = { workspace = true } -sp-keyring = { workspace = true } +sc-consensus-grandpa = { workspace = true } sp-authority-discovery = { workspace = true } sp-consensus-babe = { workspace = true } sp-consensus-beefy = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true } +sp-core = { workspace = true } +sp-keyring = { workspace = true } # Polkadot polkadot-primitives = { workspace = true } -rococo-runtime-constants = { workspace = true } rococo-runtime = { workspace = true } +rococo-runtime-constants = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml index de285d9885a2..9b980d7d39cc 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/Cargo.toml @@ -13,21 +13,21 @@ workspace = true [dependencies] # Substrate -sp-core = { workspace = true } -sp-runtime = { workspace = true } +pallet-staking = { workspace = true } +sc-consensus-grandpa = { workspace = true } sp-authority-discovery = { workspace = true } sp-consensus-babe = { workspace = true } sp-consensus-beefy = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true } -pallet-staking = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } # Polkadot polkadot-primitives = { workspace = true } -westend-runtime-constants = { workspace = true } westend-runtime = { workspace = true } +westend-runtime-constants = { workspace = true } xcm = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus -parachains-common = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml index 8282d12d317f..e921deb9c628 100644 --- a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml @@ -16,36 +16,36 @@ codec = { workspace = true } paste = { workspace = true, default-features = true } # Substrate -sp-consensus-beefy = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true, default-features = true } -sp-authority-discovery = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } +sc-consensus-grandpa = { workspace = true, default-features = true } +sp-authority-discovery = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } # Polkadot -polkadot-primitives = { workspace = true, default-features = true } +pallet-xcm = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } xcm = { workspace = true, default-features = true } -pallet-xcm = { workspace = true, default-features = true } # Cumulus -parachains-common = { workspace = true, default-features = true } +asset-test-utils = { workspace = true, default-features = true } +cumulus-pallet-parachain-system = { workspace = true, default-features = true } +cumulus-pallet-xcmp-queue = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } +parachains-common = { workspace = true, default-features = true } xcm-emulator = { workspace = true, default-features = true } -cumulus-pallet-xcmp-queue = { workspace = true, default-features = true } -cumulus-pallet-parachain-system = { workspace = true, default-features = true } -asset-test-utils = { workspace = true, default-features = true } # Bridges bp-messages = { workspace = true, default-features = true } bp-xcm-bridge-hub = { workspace = true, default-features = true } +bridge-runtime-common = { workspace = true, default-features = true } pallet-bridge-messages = { workspace = true, default-features = true } pallet-xcm-bridge-hub = { workspace = true, default-features = true } -bridge-runtime-common = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml b/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml index 864f3c6edd7e..2f8889e48162 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-system/Cargo.toml @@ -12,10 +12,10 @@ workspace = true [dependencies] # Cumulus -emulated-integration-tests-common = { workspace = true } -rococo-emulated-chain = { workspace = true } asset-hub-rococo-emulated-chain = { workspace = true } bridge-hub-rococo-emulated-chain = { workspace = true } -people-rococo-emulated-chain = { workspace = true } -penpal-emulated-chain = { workspace = true } coretime-rococo-emulated-chain = { workspace = true } +emulated-integration-tests-common = { workspace = true } +penpal-emulated-chain = { workspace = true } +people-rococo-emulated-chain = { workspace = true } +rococo-emulated-chain = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml index cd0cb272b7f5..1b789b21c7df 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/networks/rococo-westend-system/Cargo.toml @@ -12,11 +12,11 @@ workspace = true [dependencies] # Cumulus -emulated-integration-tests-common = { workspace = true } -rococo-emulated-chain = { workspace = true } -westend-emulated-chain = { workspace = true, default-features = true } asset-hub-rococo-emulated-chain = { workspace = true } asset-hub-westend-emulated-chain = { workspace = true } bridge-hub-rococo-emulated-chain = { workspace = true } bridge-hub-westend-emulated-chain = { workspace = true } +emulated-integration-tests-common = { workspace = true } penpal-emulated-chain = { workspace = true } +rococo-emulated-chain = { workspace = true } +westend-emulated-chain = { workspace = true, default-features = true } diff --git a/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml b/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml index cec2e3733b2a..50e75a6bdd74 100644 --- a/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/networks/westend-system/Cargo.toml @@ -12,11 +12,11 @@ workspace = true [dependencies] # Cumulus -emulated-integration-tests-common = { workspace = true } -westend-emulated-chain = { workspace = true } asset-hub-westend-emulated-chain = { workspace = true } bridge-hub-westend-emulated-chain = { workspace = true } collectives-westend-emulated-chain = { workspace = true } +coretime-westend-emulated-chain = { workspace = true } +emulated-integration-tests-common = { workspace = true } penpal-emulated-chain = { workspace = true } people-westend-emulated-chain = { workspace = true } -coretime-westend-emulated-chain = { workspace = true } +westend-emulated-chain = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml index 3d40db6b03ab..9e8b8f2a52d7 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml @@ -11,31 +11,31 @@ publish = false workspace = true [dependencies] -codec = { workspace = true } assert_matches = { workspace = true } +codec = { workspace = true } # Substrate -sp-runtime = { workspace = true } -sp-core = { workspace = true } frame-support = { workspace = true } -pallet-balances = { workspace = true } -pallet-assets = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-assets = { workspace = true } +pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } # Polkadot -xcm = { workspace = true } pallet-xcm = { workspace = true } -xcm-executor = { workspace = true } -xcm-runtime-apis = { workspace = true, default-features = true } polkadot-runtime-common = { workspace = true, default-features = true } rococo-runtime-constants = { workspace = true, default-features = true } +xcm = { workspace = true } +xcm-executor = { workspace = true } +xcm-runtime-apis = { workspace = true, default-features = true } # Cumulus asset-test-utils = { workspace = true, default-features = true } cumulus-pallet-parachain-system = { workspace = true } -parachains-common = { workspace = true, default-features = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } rococo-system-emulated-network = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml index 7117124b1d1f..5cd00c239e60 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml @@ -11,36 +11,36 @@ publish = false workspace = true [dependencies] -codec = { workspace = true } assert_matches = { workspace = true } +codec = { workspace = true } # Substrate -sp-runtime = { workspace = true } -sp-core = { workspace = true } frame-metadata-hash-extension = { workspace = true, default-features = true } frame-support = { workspace = true } frame-system = { workspace = true } -pallet-balances = { workspace = true } -pallet-assets = { workspace = true } pallet-asset-conversion = { workspace = true } -pallet-treasury = { workspace = true } +pallet-asset-tx-payment = { workspace = true } +pallet-assets = { workspace = true } +pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } pallet-transaction-payment = { workspace = true } -pallet-asset-tx-payment = { workspace = true } +pallet-treasury = { workspace = true } +sp-core = { workspace = true } +sp-runtime = { workspace = true } # Polkadot +pallet-xcm = { workspace = true } polkadot-runtime-common = { workspace = true, default-features = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -pallet-xcm = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus -assets-common = { workspace = true } -parachains-common = { workspace = true, default-features = true } asset-test-utils = { workspace = true, default-features = true } -cumulus-pallet-xcmp-queue = { workspace = true } +assets-common = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } +cumulus-pallet-xcmp-queue = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } westend-system-emulated-network = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml index 9f6fe78a33ee..7bb7277df45c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/Cargo.toml @@ -12,21 +12,21 @@ workspace = true [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } hex-literal = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } # Substrate -sp-core = { workspace = true } frame-support = { workspace = true } -pallet-assets = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-assets = { workspace = true } pallet-balances = { workspace = true } pallet-message-queue = { workspace = true, default-features = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } # Polkadot -xcm = { workspace = true } pallet-xcm = { workspace = true } +xcm = { workspace = true } xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } @@ -44,7 +44,7 @@ testnet-parachains-constants = { features = ["rococo", "westend"], workspace = t # Snowbridge snowbridge-core = { workspace = true } -snowbridge-router-primitives = { workspace = true } -snowbridge-pallet-system = { workspace = true } -snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-pallet-inbound-queue-fixtures = { workspace = true, default-features = true } +snowbridge-pallet-outbound-queue = { workspace = true } +snowbridge-pallet-system = { workspace = true } +snowbridge-router-primitives = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index b87f25ac0f01..dc3bbb269d70 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -11,23 +11,23 @@ publish = false workspace = true [dependencies] -hex-literal = { workspace = true, default-features = true } codec = { workspace = true } +hex-literal = { workspace = true, default-features = true } log = { workspace = true } scale-info = { workspace = true } # Substrate frame-support = { workspace = true } -pallet-assets = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-assets = { workspace = true } pallet-balances = { workspace = true } pallet-message-queue = { workspace = true, default-features = true } sp-core = { workspace = true } sp-runtime = { workspace = true } # Polkadot -xcm = { workspace = true } pallet-xcm = { workspace = true } +xcm = { workspace = true } xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } @@ -36,18 +36,18 @@ pallet-bridge-messages = { workspace = true } pallet-xcm-bridge-hub = { workspace = true } # Cumulus +asset-hub-westend-runtime = { workspace = true } +bridge-hub-westend-runtime = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } emulated-integration-tests-common = { workspace = true } parachains-common = { workspace = true, default-features = true } rococo-westend-system-emulated-network = { workspace = true } testnet-parachains-constants = { features = ["rococo", "westend"], workspace = true, default-features = true } -asset-hub-westend-runtime = { workspace = true } -bridge-hub-westend-runtime = { workspace = true } # Snowbridge snowbridge-core = { workspace = true } -snowbridge-router-primitives = { workspace = true } -snowbridge-pallet-system = { workspace = true } -snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-inbound-queue-fixtures = { workspace = true } +snowbridge-pallet-outbound-queue = { workspace = true } +snowbridge-pallet-system = { workspace = true } +snowbridge-router-primitives = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/Cargo.toml index c4d281b75a77..1d4e93d40da4 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/collectives/collectives-westend/Cargo.toml @@ -11,31 +11,31 @@ publish = false workspace = true [dependencies] -codec = { workspace = true } assert_matches = { workspace = true } +codec = { workspace = true } # Substrate -sp-runtime = { workspace = true } frame-support = { workspace = true } -pallet-balances = { workspace = true } pallet-asset-rate = { workspace = true } pallet-assets = { workspace = true } -pallet-treasury = { workspace = true } +pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } +pallet-treasury = { workspace = true } pallet-utility = { workspace = true } pallet-whitelist = { workspace = true } +sp-runtime = { workspace = true } # Polkadot +pallet-xcm = { workspace = true } polkadot-runtime-common = { workspace = true, default-features = true } +westend-runtime-constants = { workspace = true, default-features = true } xcm = { workspace = true } xcm-executor = { workspace = true } -pallet-xcm = { workspace = true } -westend-runtime-constants = { workspace = true, default-features = true } # Cumulus -parachains-common = { workspace = true, default-features = true } -testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } -cumulus-pallet-xcmp-queue = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } +cumulus-pallet-xcmp-queue = { workspace = true } emulated-integration-tests-common = { workspace = true } +parachains-common = { workspace = true, default-features = true } +testnet-parachains-constants = { features = ["westend"], workspace = true, default-features = true } westend-system-emulated-network = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml index 28d9da0993ff..61397b1b8d40 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-rococo/Cargo.toml @@ -13,8 +13,8 @@ publish = false frame-support = { workspace = true } pallet-balances = { workspace = true } pallet-broker = { workspace = true, default-features = true } -pallet-message-queue = { workspace = true } pallet-identity = { workspace = true } +pallet-message-queue = { workspace = true } sp-runtime = { workspace = true } # Polkadot diff --git a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml index d57e7926b0ec..9f0eadf13650 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/coretime/coretime-westend/Cargo.toml @@ -13,8 +13,8 @@ publish = false frame-support = { workspace = true } pallet-balances = { workspace = true } pallet-broker = { workspace = true, default-features = true } -pallet-message-queue = { workspace = true } pallet-identity = { workspace = true } +pallet-message-queue = { workspace = true } sp-runtime = { workspace = true } # Polkadot diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml index 011be93ecac7..8b12897ef018 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-rococo/Cargo.toml @@ -13,8 +13,8 @@ codec = { workspace = true } # Substrate frame-support = { workspace = true } pallet-balances = { workspace = true } -pallet-message-queue = { workspace = true } pallet-identity = { workspace = true } +pallet-message-queue = { workspace = true } sp-runtime = { workspace = true } # Polkadot diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml index 53acd038cdf5..e069c1f61783 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/Cargo.toml @@ -13,15 +13,15 @@ codec = { workspace = true } # Substrate frame-support = { workspace = true } pallet-balances = { workspace = true } -pallet-message-queue = { workspace = true } pallet-identity = { workspace = true } +pallet-message-queue = { workspace = true } pallet-xcm = { workspace = true } sp-runtime = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true, default-features = true } -westend-runtime-constants = { workspace = true, default-features = true } westend-runtime = { workspace = true } +westend-runtime-constants = { workspace = true, default-features = true } xcm = { workspace = true } xcm-executor = { workspace = true } diff --git a/cumulus/parachains/pallets/ping/Cargo.toml b/cumulus/parachains/pallets/ping/Cargo.toml index ceb38f39fd80..248b5d7202fa 100644 --- a/cumulus/parachains/pallets/ping/Cargo.toml +++ b/cumulus/parachains/pallets/ping/Cargo.toml @@ -15,14 +15,14 @@ workspace = true codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-runtime = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-runtime = { workspace = true } xcm = { workspace = true } -cumulus-primitives-core = { workspace = true } cumulus-pallet-xcm = { workspace = true } +cumulus-primitives-core = { workspace = true } [features] default = ["std"] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index 81ebc7e09494..c954ddb7b8c7 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -27,10 +27,10 @@ frame-system = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { optional = true, workspace = true } +pallet-asset-conversion = { workspace = true } +pallet-asset-conversion-ops = { workspace = true } pallet-asset-conversion-tx-payment = { workspace = true } pallet-assets = { workspace = true } -pallet-asset-conversion-ops = { workspace = true } -pallet-asset-conversion = { workspace = true } pallet-assets-freezer = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } @@ -51,9 +51,9 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-keyring = { workspace = true } -sp-inherents = { workspace = true } sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -65,17 +65,18 @@ sp-weights = { workspace = true } primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } # Polkadot -rococo-runtime-constants = { workspace = true } pallet-xcm = { workspace = true } pallet-xcm-benchmarks = { optional = true, workspace = true } polkadot-parachain-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +rococo-runtime-constants = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus +assets-common = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } @@ -83,20 +84,19 @@ cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-utility = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } +cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true } -assets-common = { workspace = true } # Bridges -pallet-xcm-bridge-hub-router = { workspace = true } bp-asset-hub-rococo = { workspace = true } bp-asset-hub-westend = { workspace = true } bp-bridge-hub-rococo = { workspace = true } bp-bridge-hub-westend = { workspace = true } +pallet-xcm-bridge-hub-router = { workspace = true } snowbridge-router-primitives = { workspace = true } [dev-dependencies] diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 7dd2a4ab4b51..7c31745d8f6e 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -27,10 +27,10 @@ frame-system = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { optional = true, workspace = true } +pallet-asset-conversion = { workspace = true } pallet-asset-conversion-ops = { workspace = true } pallet-asset-conversion-tx-payment = { workspace = true } pallet-assets = { workspace = true } -pallet-asset-conversion = { workspace = true } pallet-assets-freezer = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } @@ -40,21 +40,21 @@ pallet-nft-fractionalization = { workspace = true } pallet-nfts = { workspace = true } pallet-nfts-runtime-api = { workspace = true } pallet-proxy = { workspace = true } +pallet-revive = { workspace = true } pallet-session = { workspace = true } pallet-state-trie-migration = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-uniques = { workspace = true } -pallet-revive = { workspace = true } pallet-utility = { workspace = true } sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -77,28 +77,28 @@ xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus +assets-common = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } -pallet-message-queue = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-utility = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } +cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } +pallet-message-queue = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["westend"], workspace = true } -assets-common = { workspace = true } # Bridges -pallet-xcm-bridge-hub-router = { workspace = true } bp-asset-hub-rococo = { workspace = true } bp-asset-hub-westend = { workspace = true } bp-bridge-hub-rococo = { workspace = true } bp-bridge-hub-westend = { workspace = true } +pallet-xcm-bridge-hub-router = { workspace = true } snowbridge-router-primitives = { workspace = true } [dev-dependencies] diff --git a/cumulus/parachains/runtimes/assets/common/Cargo.toml b/cumulus/parachains/runtimes/assets/common/Cargo.toml index 552afa4daa68..de74f59f43c0 100644 --- a/cumulus/parachains/runtimes/assets/common/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/common/Cargo.toml @@ -13,16 +13,16 @@ workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } -log = { workspace = true } impl-trait-for-tuples = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } # Substrate frame-support = { workspace = true } +pallet-asset-conversion = { workspace = true } +pallet-assets = { workspace = true } sp-api = { workspace = true } sp-runtime = { workspace = true } -pallet-assets = { workspace = true } -pallet-asset-conversion = { workspace = true } # Polkadot pallet-xcm = { workspace = true } @@ -31,8 +31,8 @@ xcm-builder = { workspace = true } xcm-executor = { workspace = true } # Cumulus -parachains-common = { workspace = true } cumulus-primitives-core = { workspace = true } +parachains-common = { workspace = true } [build-dependencies] substrate-wasm-builder = { workspace = true, default-features = true } diff --git a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml index 393d06f95b15..cad8d10a7da3 100644 --- a/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/test-utils/Cargo.toml @@ -17,28 +17,28 @@ codec = { features = ["derive", "max-encoded-len"], workspace = true } # Substrate frame-support = { workspace = true } frame-system = { workspace = true } -pallet-assets = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-assets = { workspace = true } pallet-balances = { workspace = true } -pallet-timestamp = { workspace = true } pallet-session = { workspace = true } +pallet-timestamp = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } # Cumulus cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } -pallet-collator-selection = { workspace = true } -parachains-common = { workspace = true } cumulus-primitives-core = { workspace = true } +pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } +parachains-common = { workspace = true } parachains-runtimes-test-utils = { workspace = true } # Polkadot +pallet-xcm = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -pallet-xcm = { workspace = true } xcm-runtime-apis = { workspace = true } # Bridges diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index ff50223ef575..3fabea3b02f4 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -34,9 +34,9 @@ frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } -pallet-session = { workspace = true } pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } +pallet-session = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } @@ -45,10 +45,10 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -58,11 +58,11 @@ sp-transaction-pool = { workspace = true } sp-version = { workspace = true } # Polkadot -rococo-runtime-constants = { workspace = true } pallet-xcm = { workspace = true } pallet-xcm-benchmarks = { optional = true, workspace = true } polkadot-parachain-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +rococo-runtime-constants = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } @@ -95,28 +95,28 @@ bp-parachains = { workspace = true } bp-polkadot-bulletin = { workspace = true } bp-polkadot-core = { workspace = true } bp-relayers = { workspace = true } -bp-runtime = { workspace = true } bp-rococo = { workspace = true } +bp-runtime = { workspace = true } bp-westend = { workspace = true } bp-xcm-bridge-hub-router = { workspace = true } +bridge-runtime-common = { workspace = true } pallet-bridge-grandpa = { workspace = true } pallet-bridge-messages = { workspace = true } pallet-bridge-parachains = { workspace = true } pallet-bridge-relayers = { workspace = true } pallet-xcm-bridge-hub = { workspace = true } -bridge-runtime-common = { workspace = true } # Ethereum Bridge (Snowbridge) snowbridge-beacon-primitives = { workspace = true } -snowbridge-pallet-system = { workspace = true } -snowbridge-system-runtime-api = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-pallet-ethereum-client = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } -snowbridge-outbound-queue-runtime-api = { workspace = true } +snowbridge-pallet-system = { workspace = true } snowbridge-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } +snowbridge-system-runtime-api = { workspace = true } bridge-hub-common = { workspace = true } @@ -124,8 +124,8 @@ bridge-hub-common = { workspace = true } bridge-hub-test-utils = { workspace = true, default-features = true } bridge-runtime-common = { features = ["integrity-test"], workspace = true, default-features = true } pallet-bridge-relayers = { features = ["integrity-test"], workspace = true } -snowbridge-runtime-test-common = { workspace = true, default-features = true } parachains-runtimes-test-utils = { workspace = true, default-features = true } +snowbridge-runtime-test-common = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index efdd0abbb8ee..644aa72d1311 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -34,9 +34,9 @@ frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } -pallet-session = { workspace = true } pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } +pallet-session = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } @@ -45,10 +45,10 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -58,11 +58,11 @@ sp-transaction-pool = { workspace = true } sp-version = { workspace = true } # Polkadot -westend-runtime-constants = { workspace = true } pallet-xcm = { workspace = true } pallet-xcm-benchmarks = { optional = true, workspace = true } polkadot-parachain-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +westend-runtime-constants = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } @@ -76,8 +76,8 @@ cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-utility = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } +cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } @@ -94,37 +94,37 @@ bp-messages = { workspace = true } bp-parachains = { workspace = true } bp-polkadot-core = { workspace = true } bp-relayers = { workspace = true } -bp-runtime = { workspace = true } bp-rococo = { workspace = true } +bp-runtime = { workspace = true } bp-westend = { workspace = true } bp-xcm-bridge-hub-router = { workspace = true } +bridge-hub-common = { workspace = true } +bridge-runtime-common = { workspace = true } pallet-bridge-grandpa = { workspace = true } pallet-bridge-messages = { workspace = true } pallet-bridge-parachains = { workspace = true } pallet-bridge-relayers = { workspace = true } pallet-xcm-bridge-hub = { workspace = true } -bridge-runtime-common = { workspace = true } -bridge-hub-common = { workspace = true } # Ethereum Bridge (Snowbridge) snowbridge-beacon-primitives = { workspace = true } -snowbridge-pallet-system = { workspace = true } -snowbridge-system-runtime-api = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-pallet-ethereum-client = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } -snowbridge-outbound-queue-runtime-api = { workspace = true } +snowbridge-pallet-system = { workspace = true } snowbridge-router-primitives = { workspace = true } snowbridge-runtime-common = { workspace = true } +snowbridge-system-runtime-api = { workspace = true } [dev-dependencies] bridge-hub-test-utils = { workspace = true, default-features = true } bridge-runtime-common = { features = ["integrity-test"], workspace = true, default-features = true } pallet-bridge-relayers = { features = ["integrity-test"], workspace = true } -snowbridge-runtime-test-common = { workspace = true, default-features = true } parachains-runtimes-test-utils = { workspace = true, default-features = true } +snowbridge-runtime-test-common = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml index 9eacb27639a3..2fbb96d75163 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/common/Cargo.toml @@ -10,15 +10,15 @@ repository.workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } +cumulus-primitives-core = { workspace = true } frame-support = { workspace = true } +pallet-message-queue = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +snowbridge-core = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } -cumulus-primitives-core = { workspace = true } xcm = { workspace = true } -pallet-message-queue = { workspace = true } -snowbridge-core = { workspace = true } [features] default = ["std"] diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml index 16fef951f328..ace23e71c4d1 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -19,14 +19,14 @@ log = { workspace = true } # Substrate frame-support = { workspace = true } frame-system = { workspace = true } +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } +pallet-utility = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-keyring = { workspace = true, default-features = true } sp-runtime = { workspace = true } sp-tracing = { workspace = true, default-features = true } -pallet-balances = { workspace = true } -pallet-utility = { workspace = true } -pallet-timestamp = { workspace = true } # Cumulus asset-test-utils = { workspace = true, default-features = true } @@ -36,10 +36,10 @@ parachains-common = { workspace = true } parachains-runtimes-test-utils = { workspace = true } # Polkadot +pallet-xcm = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -pallet-xcm = { workspace = true } # Bridges bp-header-chain = { workspace = true } @@ -50,12 +50,12 @@ bp-relayers = { workspace = true } bp-runtime = { workspace = true } bp-test-utils = { workspace = true } bp-xcm-bridge-hub = { workspace = true } +bridge-runtime-common = { workspace = true } pallet-bridge-grandpa = { workspace = true } -pallet-bridge-parachains = { workspace = true } pallet-bridge-messages = { features = ["test-helpers"], workspace = true } +pallet-bridge-parachains = { workspace = true } pallet-bridge-relayers = { workspace = true } pallet-xcm-bridge-hub = { workspace = true } -bridge-runtime-common = { workspace = true } [features] default = ["std"] diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 2e35fe761c04..9c70b65060dd 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -26,15 +26,19 @@ frame-system = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { optional = true, workspace = true } -pallet-asset-rate = { workspace = true } pallet-alliance = { workspace = true } +pallet-asset-rate = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } pallet-collective = { workspace = true } +pallet-core-fellowship = { workspace = true } pallet-multisig = { workspace = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } +pallet-ranked-collective = { workspace = true } +pallet-referenda = { workspace = true } +pallet-salary = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-state-trie-migration = { workspace = true } @@ -43,18 +47,14 @@ pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } -pallet-referenda = { workspace = true } -pallet-ranked-collective = { workspace = true } -pallet-core-fellowship = { workspace = true } -pallet-salary = { workspace = true } sp-api = { workspace = true } sp-arithmetic = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-keyring = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -67,23 +67,23 @@ sp-version = { workspace = true } pallet-xcm = { workspace = true } polkadot-parachain-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +westend-runtime-constants = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -westend-runtime-constants = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus cumulus-pallet-aura-ext = { workspace = true } -pallet-message-queue = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-utility = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } +cumulus-primitives-utility = { workspace = true } +pallet-message-queue = { workspace = true } pallet-collator-selection = { workspace = true } pallet-collective-content = { workspace = true } @@ -95,8 +95,8 @@ testnet-parachains-constants = { features = ["westend"], workspace = true } substrate-wasm-builder = { optional = true, workspace = true, default-features = true } [dev-dependencies] -sp-io = { features = ["std"], workspace = true, default-features = true } parachains-runtimes-test-utils = { workspace = true, default-features = true } +sp-io = { features = ["std"], workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index 260c748819ae..cb0655d70cf2 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -24,37 +24,37 @@ log = { workspace = true } scale-info = { features = ["derive"], workspace = true } # Substrate -sp-api = { workspace = true } -sp-block-builder = { workspace = true } -sp-consensus-aura = { workspace = true } -sp-core = { workspace = true } -sp-genesis-builder = { workspace = true } -sp-inherents = { workspace = true } -sp-offchain = { workspace = true } -sp-runtime = { workspace = true } -sp-session = { workspace = true } -sp-storage = { workspace = true } -sp-transaction-pool = { workspace = true } -sp-version = { workspace = true } frame-benchmarking = { optional = true, workspace = true } -frame-try-runtime = { optional = true, workspace = true } frame-executive = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } frame-system-rpc-runtime-api = { workspace = true } +frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } -pallet-insecure-randomness-collective-flip = { workspace = true } pallet-balances = { workspace = true } +pallet-contracts = { workspace = true } +pallet-insecure-randomness-collective-flip = { workspace = true } pallet-multisig = { workspace = true } pallet-session = { workspace = true } +pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-utility = { workspace = true } -pallet-sudo = { workspace = true } -pallet-contracts = { workspace = true } +sp-api = { workspace = true } +sp-block-builder = { workspace = true } +sp-consensus-aura = { workspace = true } +sp-core = { workspace = true } +sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } +sp-offchain = { workspace = true } +sp-runtime = { workspace = true } +sp-session = { workspace = true } +sp-storage = { workspace = true } +sp-transaction-pool = { workspace = true } +sp-version = { workspace = true } # Polkadot pallet-xcm = { workspace = true } @@ -68,15 +68,15 @@ xcm-runtime-apis = { workspace = true } # Cumulus cumulus-pallet-aura-ext = { workspace = true } -pallet-message-queue = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-utility = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } +cumulus-primitives-utility = { workspace = true } +pallet-message-queue = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index aa692c3c7e74..2b5fab329293 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -33,8 +33,8 @@ frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } -pallet-message-queue = { workspace = true } pallet-broker = { workspace = true } +pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } pallet-proxy = { workspace = true } pallet-session = { workspace = true } @@ -47,8 +47,8 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-inherents = { workspace = true } sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -75,8 +75,8 @@ cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-utility = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } +cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index 226e1c817bb8..03df782bc266 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -33,8 +33,8 @@ frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } -pallet-message-queue = { workspace = true } pallet-broker = { workspace = true } +pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } pallet-proxy = { workspace = true } pallet-session = { workspace = true } @@ -46,8 +46,8 @@ sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } sp-core = { workspace = true } -sp-inherents = { workspace = true } sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } @@ -74,8 +74,8 @@ cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-utility = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } +cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml index f2922b710e24..1c1041a4317e 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/Cargo.toml @@ -20,11 +20,12 @@ frame-benchmarking = { optional = true, workspace = true } frame-executive = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-system-rpc-runtime-api = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } +frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { optional = true, workspace = true } pallet-aura = { workspace = true } pallet-glutton = { workspace = true } +pallet-message-queue = { workspace = true } pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } sp-api = { workspace = true } @@ -33,7 +34,6 @@ sp-consensus-aura = { workspace = true } sp-core = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } -pallet-message-queue = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index 4984f6314f87..de2898046c0d 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -72,8 +72,8 @@ cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-utility = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } +cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index 7822df585a58..65bc8264934f 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -72,8 +72,8 @@ cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-utility = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } +cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } diff --git a/cumulus/parachains/runtimes/test-utils/Cargo.toml b/cumulus/parachains/runtimes/test-utils/Cargo.toml index 17c81ae4921a..cc8f29524514 100644 --- a/cumulus/parachains/runtimes/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/test-utils/Cargo.toml @@ -21,27 +21,27 @@ pallet-balances = { workspace = true } pallet-session = { workspace = true } pallet-timestamp = { workspace = true } sp-consensus-aura = { workspace = true } +sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-tracing = { workspace = true, default-features = true } -sp-core = { workspace = true } # Cumulus cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } -pallet-collator-selection = { workspace = true } -parachain-info = { workspace = true } -parachains-common = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-parachain-inherent = { workspace = true } cumulus-test-relay-sproof-builder = { workspace = true } +pallet-collator-selection = { workspace = true } +parachain-info = { workspace = true } +parachains-common = { workspace = true } # Polkadot +pallet-xcm = { workspace = true } +polkadot-parachain-primitives = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } -pallet-xcm = { workspace = true } xcm-runtime-apis = { workspace = true } -polkadot-parachain-primitives = { workspace = true } [dev-dependencies] hex-literal = { workspace = true, default-features = true } diff --git a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml index 3bd1e5c6f436..5b17f4f57388 100644 --- a/cumulus/parachains/runtimes/testing/penpal/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/penpal/Cargo.toml @@ -32,6 +32,9 @@ frame-system = { workspace = true } frame-system-benchmarking = { optional = true, workspace = true } frame-system-rpc-runtime-api = { workspace = true } frame-try-runtime = { optional = true, workspace = true } +pallet-asset-conversion = { workspace = true } +pallet-asset-tx-payment = { workspace = true } +pallet-assets = { workspace = true } pallet-aura = { workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } @@ -40,9 +43,6 @@ pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } -pallet-asset-tx-payment = { workspace = true } -pallet-assets = { workspace = true } -pallet-asset-conversion = { workspace = true } sp-api = { workspace = true } sp-block-builder = { workspace = true } sp-consensus-aura = { workspace = true } @@ -57,9 +57,9 @@ sp-transaction-pool = { workspace = true } sp-version = { workspace = true } # Polkadot -polkadot-primitives = { workspace = true } pallet-xcm = { workspace = true } polkadot-parachain-primitives = { workspace = true } +polkadot-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } @@ -67,8 +67,8 @@ xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } # Cumulus +assets-common = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } -pallet-message-queue = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } cumulus-pallet-xcm = { workspace = true } @@ -76,9 +76,9 @@ cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } +pallet-message-queue = { workspace = true } parachain-info = { workspace = true } parachains-common = { workspace = true } -assets-common = { workspace = true } snowbridge-router-primitives = { workspace = true } primitive-types = { version = "0.12.1", default-features = false, features = ["codec", "num-traits", "scale-info"] } diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml index 2ddb3364fc09..e8761445f161 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml @@ -43,14 +43,13 @@ sp-version = { workspace = true } # Polkadot pallet-xcm = { workspace = true } polkadot-parachain-primitives = { workspace = true } +polkadot-runtime-common = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -polkadot-runtime-common = { workspace = true } # Cumulus cumulus-pallet-aura-ext = { workspace = true } -pallet-message-queue = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } @@ -59,9 +58,10 @@ cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } +pallet-message-queue = { workspace = true } +parachain-info = { workspace = true } parachains-common = { workspace = true } testnet-parachains-constants = { features = ["rococo"], workspace = true } -parachain-info = { workspace = true } [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index b1937427be66..018fc88a2aea 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -19,11 +19,11 @@ async-trait = { workspace = true } clap = { features = ["derive"], workspace = true } codec = { workspace = true, default-features = true } color-print = { workspace = true } +docify = { workspace = true } futures = { workspace = true } log = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -docify = { workspace = true } # Local jsonrpsee = { features = ["server"], workspace = true } @@ -34,46 +34,46 @@ subxt-metadata = { workspace = true, default-features = true } # Substrate frame-benchmarking = { optional = true, workspace = true, default-features = true } frame-benchmarking-cli = { workspace = true, default-features = true } -sp-crypto-hashing = { workspace = true } -sp-runtime = { workspace = true } -sp-core = { workspace = true, default-features = true } -sp-session = { workspace = true, default-features = true } -frame-try-runtime = { optional = true, workspace = true, default-features = true } -sc-consensus = { workspace = true, default-features = true } frame-support = { optional = true, workspace = true, default-features = true } +frame-system-rpc-runtime-api = { workspace = true, default-features = true } +frame-try-runtime = { optional = true, workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } +pallet-transaction-payment-rpc = { workspace = true, default-features = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +sc-basic-authorship = { workspace = true, default-features = true } +sc-chain-spec = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-client-db = { workspace = true, default-features = true } +sc-consensus = { workspace = true, default-features = true } +sc-consensus-manual-seal = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true } +sc-runtime-utilities = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } +sc-sysinfo = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } +sc-tracing = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } -sp-transaction-pool = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -sc-basic-authorship = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } -sp-genesis-builder = { workspace = true } +sp-api = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-consensus-aura = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true } +sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } -sc-chain-spec = { workspace = true, default-features = true } -sc-rpc = { workspace = true, default-features = true } +sp-runtime = { workspace = true } +sp-session = { workspace = true, default-features = true } +sp-storage = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } +sp-transaction-pool = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } sp-weights = { workspace = true, default-features = true } -sc-tracing = { workspace = true, default-features = true } -sc-runtime-utilities = { workspace = true, default-features = true } -sp-storage = { workspace = true, default-features = true } -frame-system-rpc-runtime-api = { workspace = true, default-features = true } -pallet-transaction-payment = { workspace = true, default-features = true } -pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } -sp-inherents = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -sp-consensus-aura = { workspace = true, default-features = true } -sc-consensus-manual-seal = { workspace = true, default-features = true } -sc-sysinfo = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } substrate-frame-rpc-system = { workspace = true, default-features = true } -pallet-transaction-payment-rpc = { workspace = true, default-features = true } substrate-state-trie-migration-rpc = { workspace = true, default-features = true } # Polkadot @@ -84,9 +84,9 @@ polkadot-primitives = { workspace = true, default-features = true } cumulus-client-cli = { workspace = true, default-features = true } cumulus-client-collator = { workspace = true, default-features = true } cumulus-client-consensus-aura = { workspace = true, default-features = true } -cumulus-client-consensus-relay-chain = { workspace = true, default-features = true } cumulus-client-consensus-common = { workspace = true, default-features = true } cumulus-client-consensus-proposer = { workspace = true, default-features = true } +cumulus-client-consensus-relay-chain = { workspace = true, default-features = true } cumulus-client-parachain-inherent = { workspace = true, default-features = true } cumulus-client-service = { workspace = true, default-features = true } cumulus-primitives-aura = { workspace = true, default-features = true } @@ -96,10 +96,10 @@ futures-timer = "3.0.3" [dev-dependencies] assert_cmd = { workspace = true } +cumulus-test-runtime = { workspace = true } nix = { features = ["signal"], workspace = true } tokio = { version = "1.32.0", features = ["macros", "parking_lot", "time"] } wait-timeout = { workspace = true } -cumulus-test-runtime = { workspace = true } [features] default = [] diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 3bfb79610448..9130f60ceb38 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -24,29 +24,29 @@ serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } # Local -polkadot-omni-node-lib = { features = ["rococo-native", "westend-native"], workspace = true } -rococo-parachain-runtime = { workspace = true } -glutton-westend-runtime = { workspace = true } asset-hub-rococo-runtime = { workspace = true, default-features = true } asset-hub-westend-runtime = { workspace = true } +bridge-hub-rococo-runtime = { workspace = true, default-features = true } +bridge-hub-westend-runtime = { workspace = true, default-features = true } collectives-westend-runtime = { workspace = true } contracts-rococo-runtime = { workspace = true } -bridge-hub-rococo-runtime = { workspace = true, default-features = true } coretime-rococo-runtime = { workspace = true } coretime-westend-runtime = { workspace = true } -bridge-hub-westend-runtime = { workspace = true, default-features = true } +glutton-westend-runtime = { workspace = true } +parachains-common = { workspace = true, default-features = true } penpal-runtime = { workspace = true } people-rococo-runtime = { workspace = true } people-westend-runtime = { workspace = true } -parachains-common = { workspace = true, default-features = true } +polkadot-omni-node-lib = { features = ["rococo-native", "westend-native"], workspace = true } +rococo-parachain-runtime = { workspace = true } # Substrate -sp-core = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } +sc-chain-spec = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -sc-chain-spec = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-genesis-builder = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } # Polkadot xcm = { workspace = true, default-features = true } diff --git a/cumulus/primitives/proof-size-hostfunction/Cargo.toml b/cumulus/primitives/proof-size-hostfunction/Cargo.toml index 6e8168091892..b3b300d66ef3 100644 --- a/cumulus/primitives/proof-size-hostfunction/Cargo.toml +++ b/cumulus/primitives/proof-size-hostfunction/Cargo.toml @@ -12,14 +12,14 @@ repository.workspace = true workspace = true [dependencies] -sp-runtime-interface = { workspace = true } sp-externalities = { workspace = true } +sp-runtime-interface = { workspace = true } sp-trie = { workspace = true } [dev-dependencies] -sp-state-machine = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/primitives/storage-weight-reclaim/Cargo.toml b/cumulus/primitives/storage-weight-reclaim/Cargo.toml index 3c358bc25edb..4bcbabc1f16c 100644 --- a/cumulus/primitives/storage-weight-reclaim/Cargo.toml +++ b/cumulus/primitives/storage-weight-reclaim/Cargo.toml @@ -27,9 +27,9 @@ cumulus-primitives-proof-size-hostfunction = { workspace = true } docify = { workspace = true } [dev-dependencies] +cumulus-test-runtime = { workspace = true } sp-io = { workspace = true } sp-trie = { workspace = true } -cumulus-test-runtime = { workspace = true } [features] default = ["std"] diff --git a/cumulus/primitives/utility/Cargo.toml b/cumulus/primitives/utility/Cargo.toml index f26e34a29509..84039b9345b2 100644 --- a/cumulus/primitives/utility/Cargo.toml +++ b/cumulus/primitives/utility/Cargo.toml @@ -17,14 +17,14 @@ log = { workspace = true } # Substrate frame-support = { workspace = true } -sp-runtime = { workspace = true } pallet-asset-conversion = { workspace = true } +sp-runtime = { workspace = true } # Polkadot polkadot-runtime-common = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } # Cumulus cumulus-primitives-core = { workspace = true } diff --git a/cumulus/test/client/Cargo.toml b/cumulus/test/client/Cargo.toml index 33023816c718..2c72ca98f35a 100644 --- a/cumulus/test/client/Cargo.toml +++ b/cumulus/test/client/Cargo.toml @@ -12,40 +12,40 @@ workspace = true codec = { features = ["derive"], workspace = true } # Substrate -sc-service = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } +sc-block-builder = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-consensus-aura = { workspace = true, default-features = true } -sc-block-builder = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-executor-common = { workspace = true, default-features = true } -substrate-test-client = { workspace = true } -sp-application-crypto = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-consensus-aura = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } +sp-consensus-aura = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } -pallet-transaction-payment = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } +substrate-test-client = { workspace = true } # Polkadot -polkadot-primitives = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } # Cumulus -cumulus-test-runtime = { workspace = true } -cumulus-test-service = { workspace = true } -cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } -cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } cumulus-primitives-parachain-inherent = { workspace = true, default-features = true } +cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } cumulus-primitives-storage-weight-reclaim = { workspace = true, default-features = true } +cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } +cumulus-test-runtime = { workspace = true } +cumulus-test-service = { workspace = true } [features] runtime-benchmarks = [ diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index b80170af3e83..150838e5e96e 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -18,37 +18,37 @@ frame-executive = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } frame-system-rpc-runtime-api = { workspace = true } +pallet-aura = { workspace = true } +pallet-authorship = { workspace = true } pallet-balances = { workspace = true } +pallet-glutton = { workspace = true } pallet-message-queue = { workspace = true } +pallet-session = { workspace = true } pallet-sudo = { workspace = true } -pallet-aura = { workspace = true } -pallet-authorship = { workspace = true } pallet-timestamp = { workspace = true } -pallet-glutton = { workspace = true } pallet-transaction-payment = { workspace = true } -pallet-session = { workspace = true } sp-api = { workspace = true } sp-block-builder = { workspace = true } +sp-consensus-aura = { workspace = true } sp-core = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } -sp-consensus-aura = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } -sp-keyring = { workspace = true } # Cumulus +cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } -parachain-info = { workspace = true } cumulus-primitives-aura = { workspace = true } -pallet-collator-selection = { workspace = true } -cumulus-pallet-aura-ext = { workspace = true } cumulus-primitives-core = { workspace = true } cumulus-primitives-storage-weight-reclaim = { workspace = true } +pallet-collator-selection = { workspace = true } +parachain-info = { workspace = true } [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index 86a8c48bb54f..b3d92444c7d1 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -22,80 +22,80 @@ prometheus = { workspace = true } rand = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } +tempfile = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } tracing = { workspace = true, default-features = true } url = { workspace = true } -tempfile = { workspace = true } # Substrate frame-system = { workspace = true, default-features = true } frame-system-rpc-runtime-api = { workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } sc-basic-authorship = { workspace = true, default-features = true } +sc-block-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } +sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-consensus-aura = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } +sc-executor-common = { workspace = true, default-features = true } +sc-executor-wasmtime = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } +sc-telemetry = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } -sc-telemetry = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-consensus-aura = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-genesis-builder = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -sp-genesis-builder = { workspace = true, default-features = true } sp-runtime = { workspace = true } sp-state-machine = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -sp-consensus-aura = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } substrate-test-client = { workspace = true } -sc-cli = { workspace = true, default-features = true } -sc-block-builder = { workspace = true, default-features = true } -sc-executor-wasmtime = { workspace = true, default-features = true } -sc-executor-common = { workspace = true, default-features = true } # Polkadot -polkadot-primitives = { workspace = true, default-features = true } -polkadot-service = { workspace = true, default-features = true } -polkadot-test-service = { workspace = true } polkadot-cli = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-service = { workspace = true, default-features = true } +polkadot-test-service = { workspace = true } # Cumulus cumulus-client-cli = { workspace = true, default-features = true } -parachains-common = { workspace = true, default-features = true } +cumulus-client-collator = { workspace = true, default-features = true } +cumulus-client-consensus-aura = { workspace = true, default-features = true } cumulus-client-consensus-common = { workspace = true, default-features = true } cumulus-client-consensus-proposer = { workspace = true, default-features = true } -cumulus-client-consensus-aura = { workspace = true, default-features = true } cumulus-client-consensus-relay-chain = { workspace = true, default-features = true } cumulus-client-parachain-inherent = { workspace = true, default-features = true } +cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-client-service = { workspace = true, default-features = true } -cumulus-client-collator = { workspace = true, default-features = true } +cumulus-pallet-parachain-system = { workspace = true } cumulus-primitives-core = { workspace = true, default-features = true } +cumulus-primitives-storage-weight-reclaim = { workspace = true, default-features = true } cumulus-relay-chain-inprocess-interface = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } -cumulus-test-runtime = { workspace = true } cumulus-relay-chain-minimal-node = { workspace = true, default-features = true } -cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } -cumulus-pallet-parachain-system = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true, default-features = true } +cumulus-test-runtime = { workspace = true } pallet-timestamp = { workspace = true, default-features = true } +parachains-common = { workspace = true, default-features = true } [dev-dependencies] +cumulus-test-client = { workspace = true } futures = { workspace = true } portpicker = { workspace = true } sp-authority-discovery = { workspace = true, default-features = true } -cumulus-test-client = { workspace = true } # Polkadot dependencies polkadot-test-service = { workspace = true } diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index d0c637d64d01..ae8cb79bb55e 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -12,36 +12,36 @@ repository.workspace = true workspace = true [dependencies] +array-bytes = { workspace = true } codec = { workspace = true, default-features = true } -paste = { workspace = true, default-features = true } -log = { workspace = true } impl-trait-for-tuples = { workspace = true } -array-bytes = { workspace = true } +log = { workspace = true } +paste = { workspace = true, default-features = true } # Substrate frame-support = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +pallet-message-queue = { workspace = true, default-features = true } +sp-arithmetic = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-arithmetic = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -pallet-message-queue = { workspace = true, default-features = true } # Cumulus -cumulus-primitives-core = { workspace = true, default-features = true } -cumulus-pallet-xcmp-queue = { workspace = true, default-features = true } cumulus-pallet-parachain-system = { workspace = true, default-features = true } +cumulus-pallet-xcmp-queue = { workspace = true, default-features = true } +cumulus-primitives-core = { workspace = true, default-features = true } cumulus-primitives-parachain-inherent = { workspace = true, default-features = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } parachains-common = { workspace = true, default-features = true } # Polkadot -xcm = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } +xcm = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index 0e4a5c001382..a856e94f42b5 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -16,112 +16,112 @@ workspace = true [dependencies] # Needed for all FRAME-based code codec = { workspace = true } -scale-info = { workspace = true } frame = { features = [ "experimental", "runtime", ], workspace = true, default-features = true } -pallet-examples = { workspace = true } pallet-contracts = { workspace = true } pallet-default-config-example = { workspace = true, default-features = true } pallet-example-offchain-worker = { workspace = true, default-features = true } +pallet-examples = { workspace = true } +scale-info = { workspace = true } # How we build docs in rust-docs -simple-mermaid = "0.1.1" docify = { workspace = true } serde_json = { workspace = true } +simple-mermaid = "0.1.1" # Polkadot SDK deps, typically all should only be in scope such that we can link to their doc item. -polkadot-sdk = { features = ["runtime-full"], workspace = true, default-features = true } -node-cli = { workspace = true } -kitchensink-runtime = { workspace = true } chain-spec-builder = { workspace = true, default-features = true } -subkey = { workspace = true, default-features = true } -frame-system = { workspace = true } -frame-support = { workspace = true } -frame-executive = { workspace = true } frame-benchmarking = { workspace = true } -pallet-example-authorization-tx-extension = { workspace = true, default-features = true } -pallet-example-single-block-migrations = { workspace = true, default-features = true } +frame-executive = { workspace = true } frame-metadata-hash-extension = { workspace = true, default-features = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +kitchensink-runtime = { workspace = true } log = { workspace = true, default-features = true } +node-cli = { workspace = true } +pallet-example-authorization-tx-extension = { workspace = true, default-features = true } +pallet-example-single-block-migrations = { workspace = true, default-features = true } +polkadot-sdk = { features = ["runtime-full"], workspace = true, default-features = true } +subkey = { workspace = true, default-features = true } # Substrate Client -sc-network = { workspace = true, default-features = true } -sc-rpc-api = { workspace = true, default-features = true } -sc-rpc = { workspace = true, default-features = true } -sc-client-db = { workspace = true, default-features = true } +sc-chain-spec = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } +sc-client-db = { workspace = true, default-features = true } sc-consensus-aura = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-beefy = { workspace = true, default-features = true } +sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-manual-seal = { workspace = true, default-features = true } sc-consensus-pow = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true } +sc-rpc-api = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -sc-chain-spec = { workspace = true, default-features = true } substrate-wasm-builder = { workspace = true, default-features = true } # Cumulus +cumulus-client-service = { workspace = true, default-features = true } cumulus-pallet-aura-ext = { workspace = true, default-features = true } cumulus-pallet-parachain-system = { workspace = true, default-features = true } -parachain-info = { workspace = true, default-features = true } cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } -cumulus-client-service = { workspace = true, default-features = true } cumulus-primitives-storage-weight-reclaim = { workspace = true, default-features = true } +parachain-info = { workspace = true, default-features = true } # Omni Node polkadot-omni-node-lib = { workspace = true, default-features = true } # Pallets and FRAME internals -pallet-aura = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -pallet-assets = { workspace = true, default-features = true } -pallet-preimage = { workspace = true, default-features = true } -pallet-transaction-payment = { workspace = true, default-features = true } -pallet-asset-tx-payment = { workspace = true, default-features = true } -pallet-skip-feeless-payment = { workspace = true, default-features = true } pallet-asset-conversion-tx-payment = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } -pallet-multisig = { workspace = true, default-features = true } -pallet-proxy = { workspace = true, default-features = true } +pallet-asset-tx-payment = { workspace = true, default-features = true } +pallet-assets = { workspace = true, default-features = true } +pallet-aura = { workspace = true, default-features = true } pallet-authorship = { workspace = true, default-features = true } +pallet-babe = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } +pallet-broker = { workspace = true, default-features = true } pallet-collective = { workspace = true, default-features = true } pallet-democracy = { workspace = true, default-features = true } -pallet-uniques = { workspace = true, default-features = true } +pallet-grandpa = { workspace = true, default-features = true } +pallet-multisig = { workspace = true, default-features = true } pallet-nfts = { workspace = true, default-features = true } -pallet-scheduler = { workspace = true, default-features = true } +pallet-preimage = { workspace = true, default-features = true } +pallet-proxy = { workspace = true, default-features = true } pallet-referenda = { workspace = true, default-features = true } -pallet-broker = { workspace = true, default-features = true } -pallet-babe = { workspace = true, default-features = true } -pallet-grandpa = { workspace = true, default-features = true } +pallet-scheduler = { workspace = true, default-features = true } +pallet-skip-feeless-payment = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } +pallet-uniques = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } # Primitives -sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } -sp-runtime-interface = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-genesis-builder = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-offchain = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-runtime-interface = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } sp-weights = { workspace = true, default-features = true } # XCM +pallet-xcm = { workspace = true } xcm = { workspace = true, default-features = true } xcm-builder = { workspace = true } xcm-docs = { workspace = true } xcm-executor = { workspace = true } xcm-simulator = { workspace = true } -pallet-xcm = { workspace = true } # runtime guides @@ -129,12 +129,12 @@ chain-spec-guide-runtime = { workspace = true, default-features = true } # Templates minimal-template-runtime = { workspace = true, default-features = true } -solochain-template-runtime = { workspace = true, default-features = true } parachain-template-runtime = { workspace = true, default-features = true } +solochain-template-runtime = { workspace = true, default-features = true } # local packages -first-runtime = { workspace = true, default-features = true } first-pallet = { workspace = true, default-features = true } +first-runtime = { workspace = true, default-features = true } [dev-dependencies] assert_cmd = "2.0.14" diff --git a/docs/sdk/packages/guides/first-pallet/Cargo.toml b/docs/sdk/packages/guides/first-pallet/Cargo.toml index dad5b8863494..a1411580119d 100644 --- a/docs/sdk/packages/guides/first-pallet/Cargo.toml +++ b/docs/sdk/packages/guides/first-pallet/Cargo.toml @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } docify = { workspace = true } +frame = { workspace = true, features = ["experimental", "runtime"] } +scale-info = { workspace = true } [features] default = ["std"] diff --git a/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml b/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml index 307e2daa38c6..925cb7bb2e65 100644 --- a/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml +++ b/docs/sdk/src/reference_docs/chain_spec_runtime/Cargo.toml @@ -10,8 +10,8 @@ edition.workspace = true publish = false [dependencies] -docify = { workspace = true } codec = { workspace = true } +docify = { workspace = true } frame-support = { workspace = true } scale-info = { workspace = true } serde = { workspace = true } @@ -31,11 +31,11 @@ pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } # genesis builder that allows us to interact with runtime genesis config -sp-genesis-builder = { workspace = true } -sp-runtime = { features = ["serde"], workspace = true } +sp-application-crypto = { features = ["serde"], workspace = true } sp-core = { workspace = true } +sp-genesis-builder = { workspace = true } sp-keyring = { workspace = true } -sp-application-crypto = { features = ["serde"], workspace = true } +sp-runtime = { features = ["serde"], workspace = true } [build-dependencies] substrate-wasm-builder = { optional = true, workspace = true, default-features = true } diff --git a/polkadot/Cargo.toml b/polkadot/Cargo.toml index 101caac0e313..ded8157ad90e 100644 --- a/polkadot/Cargo.toml +++ b/polkadot/Cargo.toml @@ -46,10 +46,10 @@ tikv-jemallocator = { version = "0.5.0", features = ["unprefixed_malloc_on_suppo [dev-dependencies] assert_cmd = { workspace = true } nix = { features = ["signal"], workspace = true } +polkadot-core-primitives = { workspace = true, default-features = true } +substrate-rpc-client = { workspace = true, default-features = true } tempfile = { workspace = true } tokio = { workspace = true, default-features = true } -substrate-rpc-client = { workspace = true, default-features = true } -polkadot-core-primitives = { workspace = true, default-features = true } [build-dependencies] substrate-build-script-utils = { workspace = true, default-features = true } diff --git a/polkadot/cli/Cargo.toml b/polkadot/cli/Cargo.toml index 3eff525b7b1e..6909d142b3a6 100644 --- a/polkadot/cli/Cargo.toml +++ b/polkadot/cli/Cargo.toml @@ -22,27 +22,27 @@ crate-type = ["cdylib", "rlib"] [dependencies] cfg-if = { workspace = true } clap = { features = ["derive"], optional = true, workspace = true } -log = { workspace = true, default-features = true } -thiserror = { workspace = true } futures = { workspace = true } +log = { workspace = true, default-features = true } pyroscope = { optional = true, workspace = true } pyroscope_pprofrs = { optional = true, workspace = true } +thiserror = { workspace = true } polkadot-service = { optional = true, workspace = true } -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-maybe-compressed-blob = { workspace = true, default-features = true } frame-benchmarking-cli = { optional = true, workspace = true, default-features = true } -sc-cli = { optional = true, workspace = true, default-features = true } -sc-service = { optional = true, workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -sc-tracing = { optional = true, workspace = true, default-features = true } -sc-sysinfo = { workspace = true, default-features = true } +sc-cli = { optional = true, workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } +sc-service = { optional = true, workspace = true, default-features = true } sc-storage-monitor = { workspace = true, default-features = true } +sc-sysinfo = { workspace = true, default-features = true } +sc-tracing = { optional = true, workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-maybe-compressed-blob = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } [build-dependencies] diff --git a/polkadot/core-primitives/Cargo.toml b/polkadot/core-primitives/Cargo.toml index 33869f216f78..1fb14e9d58e7 100644 --- a/polkadot/core-primitives/Cargo.toml +++ b/polkadot/core-primitives/Cargo.toml @@ -12,10 +12,10 @@ repository.workspace = true workspace = true [dependencies] +codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -codec = { features = ["derive"], workspace = true } [features] default = ["std"] diff --git a/polkadot/erasure-coding/Cargo.toml b/polkadot/erasure-coding/Cargo.toml index 528b955c4db3..ba712a89613b 100644 --- a/polkadot/erasure-coding/Cargo.toml +++ b/polkadot/erasure-coding/Cargo.toml @@ -12,17 +12,17 @@ repository.workspace = true workspace = true [dependencies] -polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } -novelpoly = { workspace = true } codec = { features = ["derive", "std"], workspace = true } +novelpoly = { workspace = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } thiserror = { workspace = true } [dev-dependencies] -quickcheck = { workspace = true } criterion = { features = ["cargo_bench_support"], workspace = true } +quickcheck = { workspace = true } [[bench]] name = "scaling_with_validators" diff --git a/polkadot/erasure-coding/fuzzer/Cargo.toml b/polkadot/erasure-coding/fuzzer/Cargo.toml index 6f451f0319b2..5f1c2bda4058 100644 --- a/polkadot/erasure-coding/fuzzer/Cargo.toml +++ b/polkadot/erasure-coding/fuzzer/Cargo.toml @@ -10,10 +10,10 @@ publish = false workspace = true [dependencies] -polkadot-erasure-coding = { workspace = true, default-features = true } honggfuzz = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } +polkadot-erasure-coding = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } [[bin]] name = "reconstruct" diff --git a/polkadot/node/collation-generation/Cargo.toml b/polkadot/node/collation-generation/Cargo.toml index c1716e2e6eb8..eb9568cc22bc 100644 --- a/polkadot/node/collation-generation/Cargo.toml +++ b/polkadot/node/collation-generation/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true workspace = true [dependencies] +codec = { features = ["bit-vec", "derive"], workspace = true } futures = { workspace = true } gum = { workspace = true, default-features = true } polkadot-erasure-coding = { workspace = true, default-features = true } @@ -19,16 +20,15 @@ polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } +schnellru = { workspace = true } sp-core = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } thiserror = { workspace = true } -codec = { features = ["bit-vec", "derive"], workspace = true } -schnellru = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } +polkadot-primitives = { workspace = true, features = ["test"] } polkadot-primitives-test-helpers = { workspace = true } -assert_matches = { workspace = true } rstest = { workspace = true } sp-keyring = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, features = ["test"] } diff --git a/polkadot/node/core/approval-voting-parallel/Cargo.toml b/polkadot/node/core/approval-voting-parallel/Cargo.toml index 995687fb4c11..a3b3e97da497 100644 --- a/polkadot/node/core/approval-voting-parallel/Cargo.toml +++ b/polkadot/node/core/approval-voting-parallel/Cargo.toml @@ -19,38 +19,38 @@ gum = { workspace = true } itertools = { workspace = true } thiserror = { workspace = true } -polkadot-node-core-approval-voting = { workspace = true, default-features = true } polkadot-approval-distribution = { workspace = true, default-features = true } +polkadot-node-core-approval-voting = { workspace = true, default-features = true } +polkadot-node-metrics = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } -polkadot-node-metrics = { workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = false } +sp-application-crypto = { workspace = true, default-features = false, features = ["full_crypto"] } sp-consensus = { workspace = true, default-features = false } sp-consensus-slots = { workspace = true, default-features = false } -sp-application-crypto = { workspace = true, default-features = false, features = ["full_crypto"] } sp-runtime = { workspace = true, default-features = false } rand = { workspace = true } -rand_core = { workspace = true } rand_chacha = { workspace = true } +rand_core = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } async-trait = { workspace = true } +kvdb-memorydb = { workspace = true } +log = { workspace = true, default-features = true } parking_lot = { workspace = true } -sp-keyring = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } -sp-tracing = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true, default-features = true } -assert_matches = { workspace = true } -kvdb-memorydb = { workspace = true } polkadot-primitives-test-helpers = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } polkadot-subsystem-bench = { workspace = true, default-features = true } schnorrkel = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-tracing = { workspace = true } diff --git a/polkadot/node/core/approval-voting/Cargo.toml b/polkadot/node/core/approval-voting/Cargo.toml index 80f5dcb7f318..2c292ba5efcb 100644 --- a/polkadot/node/core/approval-voting/Cargo.toml +++ b/polkadot/node/core/approval-voting/Cargo.toml @@ -12,50 +12,50 @@ repository.workspace = true workspace = true [dependencies] +async-trait = { workspace = true } +bitvec = { features = ["alloc"], workspace = true } +codec = { features = ["bit-vec", "derive"], workspace = true } +derive_more = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } -codec = { features = ["bit-vec", "derive"], workspace = true } gum = { workspace = true, default-features = true } -bitvec = { features = ["alloc"], workspace = true } -schnellru = { workspace = true } +itertools = { workspace = true } +kvdb = { workspace = true } merlin = { workspace = true, default-features = true } +schnellru = { workspace = true } schnorrkel = { workspace = true, default-features = true } -kvdb = { workspace = true } -derive_more = { workspace = true, default-features = true } thiserror = { workspace = true } -itertools = { workspace = true } -async-trait = { workspace = true } +polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } sc-keystore = { workspace = true } +sp-application-crypto = { features = ["full_crypto"], workspace = true } sp-consensus = { workspace = true } sp-consensus-slots = { workspace = true } -sp-application-crypto = { features = ["full_crypto"], workspace = true } sp-runtime = { workspace = true } # rand_core should match schnorrkel -rand_core = { workspace = true } -rand_chacha = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +rand_chacha = { workspace = true, default-features = true } +rand_core = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } async-trait = { workspace = true } +kvdb-memorydb = { workspace = true } +log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } -assert_matches = { workspace = true } -kvdb-memorydb = { workspace = true } +polkadot-primitives = { workspace = true, features = ["test"] } polkadot-primitives-test-helpers = { workspace = true } -log = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true } -polkadot-primitives = { workspace = true, features = ["test"] } polkadot-subsystem-bench = { workspace = true } diff --git a/polkadot/node/core/av-store/Cargo.toml b/polkadot/node/core/av-store/Cargo.toml index 9f6864269cef..f3bd1f09caea 100644 --- a/polkadot/node/core/av-store/Cargo.toml +++ b/polkadot/node/core/av-store/Cargo.toml @@ -12,31 +12,31 @@ repository.workspace = true workspace = true [dependencies] +bitvec = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } +gum = { workspace = true, default-features = true } kvdb = { workspace = true } thiserror = { workspace = true } -gum = { workspace = true, default-features = true } -bitvec = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true, default-features = true } polkadot-erasure-coding = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } sp-consensus = { workspace = true } [dev-dependencies] -log = { workspace = true, default-features = true } assert_matches = { workspace = true } kvdb-memorydb = { workspace = true } +log = { workspace = true, default-features = true } sp-tracing = { workspace = true } -sp-core = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-subsystem-test-helpers = { workspace = true } -sp-keyring = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } +polkadot-node-subsystem-test-helpers = { workspace = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-primitives-test-helpers = { workspace = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } diff --git a/polkadot/node/core/backing/Cargo.toml b/polkadot/node/core/backing/Cargo.toml index a81fe9486c63..be829a84ee6e 100644 --- a/polkadot/node/core/backing/Cargo.toml +++ b/polkadot/node/core/backing/Cargo.toml @@ -12,30 +12,30 @@ repository.workspace = true workspace = true [dependencies] +bitvec = { features = ["alloc"], workspace = true } +fatality = { workspace = true } futures = { workspace = true } -sp-keystore = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +gum = { workspace = true, default-features = true } +polkadot-erasure-coding = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-erasure-coding = { workspace = true, default-features = true } +polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-statement-table = { workspace = true, default-features = true } -bitvec = { features = ["alloc"], workspace = true } -gum = { workspace = true, default-features = true } -thiserror = { workspace = true } -fatality = { workspace = true } schnellru = { workspace = true } +sp-keystore = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } -futures = { features = ["thread-pool"], workspace = true } assert_matches = { workspace = true } -rstest = { workspace = true } +futures = { features = ["thread-pool"], workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } polkadot-primitives = { workspace = true, features = ["test"] } +polkadot-primitives-test-helpers = { workspace = true } +rstest = { workspace = true } +sc-keystore = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } diff --git a/polkadot/node/core/bitfield-signing/Cargo.toml b/polkadot/node/core/bitfield-signing/Cargo.toml index f00ba5712661..e75404729dbd 100644 --- a/polkadot/node/core/bitfield-signing/Cargo.toml +++ b/polkadot/node/core/bitfield-signing/Cargo.toml @@ -14,12 +14,12 @@ workspace = true [dependencies] futures = { workspace = true } gum = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } -wasm-timer = { workspace = true } thiserror = { workspace = true } +wasm-timer = { workspace = true } [dev-dependencies] polkadot-node-subsystem-test-helpers = { workspace = true } diff --git a/polkadot/node/core/candidate-validation/Cargo.toml b/polkadot/node/core/candidate-validation/Cargo.toml index fea16b1c7604..e92976609f9e 100644 --- a/polkadot/node/core/candidate-validation/Cargo.toml +++ b/polkadot/node/core/candidate-validation/Cargo.toml @@ -17,28 +17,28 @@ futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -sp-keystore = { workspace = true } -sp-application-crypto = { workspace = true } codec = { features = ["bit-vec", "derive"], workspace = true } +sp-application-crypto = { workspace = true } +sp-keystore = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } -polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-metrics = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } +polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } [target.'cfg(not(any(target_os = "android", target_os = "unknown")))'.dependencies] polkadot-node-core-pvf = { workspace = true, default-features = true } [dev-dependencies] -sp-keyring = { workspace = true, default-features = true } -futures = { features = ["thread-pool"], workspace = true } assert_matches = { workspace = true } +futures = { features = ["thread-pool"], workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } -sp-maybe-compressed-blob = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, features = ["test"] } polkadot-primitives-test-helpers = { workspace = true } rstest = { workspace = true } -polkadot-primitives = { workspace = true, features = ["test"] } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-maybe-compressed-blob = { workspace = true, default-features = true } diff --git a/polkadot/node/core/chain-api/Cargo.toml b/polkadot/node/core/chain-api/Cargo.toml index 0f443868dada..0689a41233c7 100644 --- a/polkadot/node/core/chain-api/Cargo.toml +++ b/polkadot/node/core/chain-api/Cargo.toml @@ -21,11 +21,11 @@ sc-client-api = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } [dev-dependencies] +codec = { workspace = true, default-features = true } futures = { features = ["thread-pool"], workspace = true } maplit = { workspace = true } -codec = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } -sp-core = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } diff --git a/polkadot/node/core/chain-selection/Cargo.toml b/polkadot/node/core/chain-selection/Cargo.toml index d2cc425a4816..e425b9f862a5 100644 --- a/polkadot/node/core/chain-selection/Cargo.toml +++ b/polkadot/node/core/chain-selection/Cargo.toml @@ -12,20 +12,20 @@ repository.workspace = true workspace = true [dependencies] +codec = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +kvdb = { workspace = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -kvdb = { workspace = true } +polkadot-primitives = { workspace = true, default-features = true } thiserror = { workspace = true } -codec = { workspace = true, default-features = true } [dev-dependencies] -polkadot-node-subsystem-test-helpers = { workspace = true } -sp-core = { workspace = true, default-features = true } -parking_lot = { workspace = true, default-features = true } assert_matches = { workspace = true } kvdb-memorydb = { workspace = true } +parking_lot = { workspace = true, default-features = true } +polkadot-node-subsystem-test-helpers = { workspace = true } +sp-core = { workspace = true, default-features = true } diff --git a/polkadot/node/core/dispute-coordinator/Cargo.toml b/polkadot/node/core/dispute-coordinator/Cargo.toml index 11b4ac645c23..6eb3020a0432 100644 --- a/polkadot/node/core/dispute-coordinator/Cargo.toml +++ b/polkadot/node/core/dispute-coordinator/Cargo.toml @@ -12,34 +12,34 @@ repository.workspace = true workspace = true [dependencies] +codec = { workspace = true, default-features = true } +fatality = { workspace = true } futures = { workspace = true } gum = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } kvdb = { workspace = true } -thiserror = { workspace = true } schnellru = { workspace = true } -fatality = { workspace = true } +thiserror = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = true } [dev-dependencies] +assert_matches = { workspace = true } +futures-timer = { workspace = true } kvdb-memorydb = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } -sp-keyring = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -assert_matches = { workspace = true } +polkadot-primitives = { workspace = true, features = ["test"] } polkadot-primitives-test-helpers = { workspace = true } -futures-timer = { workspace = true } sp-application-crypto = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, features = ["test"] } [features] # If not enabled, the dispute coordinator will do nothing. diff --git a/polkadot/node/core/parachains-inherent/Cargo.toml b/polkadot/node/core/parachains-inherent/Cargo.toml index b1cd5e971b00..264b8da2b44d 100644 --- a/polkadot/node/core/parachains-inherent/Cargo.toml +++ b/polkadot/node/core/parachains-inherent/Cargo.toml @@ -12,13 +12,13 @@ repository.workspace = true workspace = true [dependencies] +async-trait = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -thiserror = { workspace = true } -async-trait = { workspace = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } +thiserror = { workspace = true } diff --git a/polkadot/node/core/prospective-parachains/Cargo.toml b/polkadot/node/core/prospective-parachains/Cargo.toml index ced6c30c64b6..0d0ede8d1d9b 100644 --- a/polkadot/node/core/prospective-parachains/Cargo.toml +++ b/polkadot/node/core/prospective-parachains/Cargo.toml @@ -12,21 +12,21 @@ repository.workspace = true workspace = true [dependencies] +fatality = { workspace = true } futures = { workspace = true } gum = { workspace = true, default-features = true } thiserror = { workspace = true } -fatality = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } polkadot-primitives = { workspace = true, features = ["test"] } -sp-tracing = { workspace = true } -sp-core = { workspace = true, default-features = true } +polkadot-primitives-test-helpers = { workspace = true } rand = { workspace = true } rstest = { workspace = true } +sp-core = { workspace = true, default-features = true } +sp-tracing = { workspace = true } diff --git a/polkadot/node/core/provisioner/Cargo.toml b/polkadot/node/core/provisioner/Cargo.toml index 26dca1adbc79..a3880d5a0f13 100644 --- a/polkadot/node/core/provisioner/Cargo.toml +++ b/polkadot/node/core/provisioner/Cargo.toml @@ -13,22 +13,22 @@ workspace = true [dependencies] bitvec = { features = ["alloc"], workspace = true } +fatality = { workspace = true } futures = { workspace = true } +futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -thiserror = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -futures-timer = { workspace = true } -fatality = { workspace = true } +polkadot-primitives = { workspace = true, default-features = true } schnellru = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] -sp-application-crypto = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } polkadot-primitives = { workspace = true, features = ["test"] } +polkadot-primitives-test-helpers = { workspace = true } +sp-application-crypto = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } rstest = { workspace = true } diff --git a/polkadot/node/core/pvf-checker/Cargo.toml b/polkadot/node/core/pvf-checker/Cargo.toml index cb7e3eadcf0a..fac5f85b6b56 100644 --- a/polkadot/node/core/pvf-checker/Cargo.toml +++ b/polkadot/node/core/pvf-checker/Cargo.toml @@ -13,23 +13,23 @@ workspace = true [dependencies] futures = { workspace = true } -thiserror = { workspace = true } gum = { workspace = true, default-features = true } +thiserror = { workspace = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } [dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } +futures-timer = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } +sc-keystore = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } -futures-timer = { workspace = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } diff --git a/polkadot/node/core/pvf/Cargo.toml b/polkadot/node/core/pvf/Cargo.toml index 1b2a16ae8b55..f47f7b734285 100644 --- a/polkadot/node/core/pvf/Cargo.toml +++ b/polkadot/node/core/pvf/Cargo.toml @@ -23,28 +23,28 @@ is_executable = { optional = true, workspace = true } pin-project = { workspace = true } rand = { workspace = true, default-features = true } slotmap = { workspace = true } +strum = { features = ["derive"], workspace = true, default-features = true } tempfile = { workspace = true } thiserror = { workspace = true } tokio = { features = ["fs", "process"], workspace = true, default-features = true } -strum = { features = ["derive"], workspace = true, default-features = true } codec = { features = [ "derive", ], workspace = true } -polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-core-primitives = { workspace = true, default-features = true } polkadot-node-core-pvf-common = { workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } +polkadot-node-core-pvf-execute-worker = { optional = true, workspace = true, default-features = true } +polkadot-node-core-pvf-prepare-worker = { optional = true, workspace = true, default-features = true } sc-tracing = { workspace = true } sp-core = { workspace = true, default-features = true } sp-maybe-compressed-blob = { optional = true, workspace = true, default-features = true } -polkadot-node-core-pvf-prepare-worker = { optional = true, workspace = true, default-features = true } -polkadot-node-core-pvf-execute-worker = { optional = true, workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } diff --git a/polkadot/node/core/pvf/execute-worker/Cargo.toml b/polkadot/node/core/pvf/execute-worker/Cargo.toml index 8327cf8058cd..4df425dfd199 100644 --- a/polkadot/node/core/pvf/execute-worker/Cargo.toml +++ b/polkadot/node/core/pvf/execute-worker/Cargo.toml @@ -12,11 +12,11 @@ repository.workspace = true workspace = true [dependencies] +cfg-if = { workspace = true } cpu-time = { workspace = true } gum = { workspace = true, default-features = true } -cfg-if = { workspace = true } -nix = { features = ["process", "resource", "sched"], workspace = true } libc = { workspace = true } +nix = { features = ["process", "resource", "sched"], workspace = true } codec = { features = ["derive"], workspace = true } diff --git a/polkadot/node/core/pvf/prepare-worker/Cargo.toml b/polkadot/node/core/pvf/prepare-worker/Cargo.toml index 9dc800a8ef56..aa551c196c37 100644 --- a/polkadot/node/core/pvf/prepare-worker/Cargo.toml +++ b/polkadot/node/core/pvf/prepare-worker/Cargo.toml @@ -16,11 +16,11 @@ blake3 = { workspace = true } cfg-if = { workspace = true } gum = { workspace = true, default-features = true } libc = { workspace = true } +nix = { features = ["process", "resource", "sched"], workspace = true } rayon = { workspace = true } -tracking-allocator = { workspace = true, default-features = true } tikv-jemalloc-ctl = { optional = true, workspace = true } tikv-jemallocator = { optional = true, workspace = true } -nix = { features = ["process", "resource", "sched"], workspace = true } +tracking-allocator = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true } diff --git a/polkadot/node/core/runtime-api/Cargo.toml b/polkadot/node/core/runtime-api/Cargo.toml index 15cbf4665d06..65c92dc5c070 100644 --- a/polkadot/node/core/runtime-api/Cargo.toml +++ b/polkadot/node/core/runtime-api/Cargo.toml @@ -18,17 +18,17 @@ schnellru = { workspace = true } sp-consensus-babe = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-types = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } [dev-dependencies] -sp-api = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } async-trait = { workspace = true } futures = { features = ["thread-pool"], workspace = true } -polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } +sp-api = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } diff --git a/polkadot/node/gum/Cargo.toml b/polkadot/node/gum/Cargo.toml index 84875ea121b6..f4c22dd7595e 100644 --- a/polkadot/node/gum/Cargo.toml +++ b/polkadot/node/gum/Cargo.toml @@ -13,6 +13,6 @@ workspace = true [dependencies] coarsetime = { workspace = true } -tracing = { workspace = true, default-features = true } gum-proc-macro = { workspace = true, default-features = true } polkadot-primitives = { features = ["std"], workspace = true, default-features = true } +tracing = { workspace = true, default-features = true } diff --git a/polkadot/node/gum/proc-macro/Cargo.toml b/polkadot/node/gum/proc-macro/Cargo.toml index b4a3401b15e4..0b69d8b67cf1 100644 --- a/polkadot/node/gum/proc-macro/Cargo.toml +++ b/polkadot/node/gum/proc-macro/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { features = ["extra-traits", "full"], workspace = true } -quote = { workspace = true } -proc-macro2 = { workspace = true } -proc-macro-crate = { workspace = true } expander = { workspace = true } +proc-macro-crate = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { features = ["extra-traits", "full"], workspace = true } [dev-dependencies] assert_matches = { workspace = true } diff --git a/polkadot/node/malus/Cargo.toml b/polkadot/node/malus/Cargo.toml index 49434606a61c..84a58f382e20 100644 --- a/polkadot/node/malus/Cargo.toml +++ b/polkadot/node/malus/Cargo.toml @@ -29,27 +29,27 @@ path = "../../src/bin/prepare-worker.rs" doc = false [dependencies] -polkadot-cli = { features = ["malus", "rococo-native", "westend-native"], workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-subsystem-types = { workspace = true, default-features = true } -polkadot-node-core-dispute-coordinator = { workspace = true, default-features = true } -polkadot-node-core-candidate-validation = { workspace = true, default-features = true } -polkadot-node-core-backing = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } -color-eyre = { workspace = true } assert_matches = { workspace = true } async-trait = { workspace = true } -sp-keystore = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } clap = { features = ["derive"], workspace = true } +color-eyre = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } +polkadot-cli = { features = ["malus", "rococo-native", "westend-native"], workspace = true, default-features = true } polkadot-erasure-coding = { workspace = true, default-features = true } +polkadot-node-core-backing = { workspace = true, default-features = true } +polkadot-node-core-candidate-validation = { workspace = true, default-features = true } +polkadot-node-core-dispute-coordinator = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-node-subsystem-types = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } # Required for worker binaries to build. polkadot-node-core-pvf-common = { workspace = true, default-features = true } @@ -57,9 +57,9 @@ polkadot-node-core-pvf-execute-worker = { workspace = true, default-features = t polkadot-node-core-pvf-prepare-worker = { workspace = true, default-features = true } [dev-dependencies] +futures = { features = ["thread-pool"], workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } sp-core = { workspace = true, default-features = true } -futures = { features = ["thread-pool"], workspace = true } [build-dependencies] substrate-build-script-utils = { workspace = true, default-features = true } diff --git a/polkadot/node/metrics/Cargo.toml b/polkadot/node/metrics/Cargo.toml index 05344993a75e..454337cb63f8 100644 --- a/polkadot/node/metrics/Cargo.toml +++ b/polkadot/node/metrics/Cargo.toml @@ -18,28 +18,28 @@ gum = { workspace = true, default-features = true } metered = { features = ["futures_channel"], workspace = true } # Both `sc-service` and `sc-cli` are required by runtime metrics `logger_hook()`. -sc-service = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } -sc-tracing = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } bs58 = { features = ["alloc"], workspace = true, default-features = true } +codec = { workspace = true, default-features = true } log = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +sc-tracing = { workspace = true, default-features = true } [dev-dependencies] assert_cmd = { workspace = true } -tempfile = { workspace = true } -hyper-util = { features = ["client-legacy", "tokio"], workspace = true } -hyper = { workspace = true } http-body-util = { workspace = true } -tokio = { workspace = true, default-features = true } +hyper = { workspace = true } +hyper-util = { features = ["client-legacy", "tokio"], workspace = true } polkadot-test-service = { features = ["runtime-metrics"], workspace = true } -substrate-test-utils = { workspace = true } +prometheus-parse = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -prometheus-parse = { workspace = true } +substrate-test-utils = { workspace = true } +tempfile = { workspace = true } +tokio = { workspace = true, default-features = true } [features] default = [] diff --git a/polkadot/node/network/approval-distribution/Cargo.toml b/polkadot/node/network/approval-distribution/Cargo.toml index abf345552f89..d9d3fd8635a6 100644 --- a/polkadot/node/network/approval-distribution/Cargo.toml +++ b/polkadot/node/network/approval-distribution/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true workspace = true [dependencies] +itertools = { workspace = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } @@ -19,12 +20,11 @@ polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } -itertools = { workspace = true } +bitvec = { features = ["alloc"], workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -bitvec = { features = ["alloc"], workspace = true } [dev-dependencies] sc-keystore = { workspace = true } @@ -38,7 +38,7 @@ polkadot-primitives-test-helpers = { workspace = true } assert_matches = { workspace = true } schnorrkel = { workspace = true } # rand_core should match schnorrkel -rand_core = { workspace = true } +log = { workspace = true, default-features = true } rand_chacha = { workspace = true, default-features = true } +rand_core = { workspace = true } sp-tracing = { workspace = true } -log = { workspace = true, default-features = true } diff --git a/polkadot/node/network/availability-distribution/Cargo.toml b/polkadot/node/network/availability-distribution/Cargo.toml index e87103d99f72..7de8cb191599 100644 --- a/polkadot/node/network/availability-distribution/Cargo.toml +++ b/polkadot/node/network/availability-distribution/Cargo.toml @@ -12,35 +12,35 @@ repository.workspace = true workspace = true [dependencies] +codec = { features = ["std"], workspace = true, default-features = true } +derive_more = { workspace = true, default-features = true } +fatality = { workspace = true } futures = { workspace = true } gum = { workspace = true, default-features = true } -codec = { features = ["std"], workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-erasure-coding = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } +schnellru = { workspace = true } sp-core = { features = ["std"], workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } thiserror = { workspace = true } -rand = { workspace = true, default-features = true } -derive_more = { workspace = true, default-features = true } -schnellru = { workspace = true } -fatality = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } +futures-timer = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } +polkadot-primitives-test-helpers = { workspace = true } +polkadot-subsystem-bench = { workspace = true } +rstest = { workspace = true } +sc-network = { workspace = true, default-features = true } sp-core = { features = ["std"], workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -futures-timer = { workspace = true } -assert_matches = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } -rstest = { workspace = true } -polkadot-subsystem-bench = { workspace = true } [[bench]] diff --git a/polkadot/node/network/availability-recovery/Cargo.toml b/polkadot/node/network/availability-recovery/Cargo.toml index be4323e74f02..8d4e6893b0a5 100644 --- a/polkadot/node/network/availability-recovery/Cargo.toml +++ b/polkadot/node/network/availability-recovery/Cargo.toml @@ -12,35 +12,35 @@ repository.workspace = true workspace = true [dependencies] +async-trait = { workspace = true } +fatality = { workspace = true } futures = { workspace = true } -tokio = { workspace = true, default-features = true } -schnellru = { workspace = true } +gum = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } -fatality = { workspace = true } +schnellru = { workspace = true } thiserror = { workspace = true } -async-trait = { workspace = true } -gum = { workspace = true, default-features = true } +tokio = { workspace = true, default-features = true } +codec = { features = ["derive"], workspace = true } polkadot-erasure-coding = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true } +polkadot-primitives = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } futures-timer = { workspace = true } -rstest = { workspace = true } log = { workspace = true, default-features = true } +rstest = { workspace = true } -sp-tracing = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } diff --git a/polkadot/node/network/bitfield-distribution/Cargo.toml b/polkadot/node/network/bitfield-distribution/Cargo.toml index 2ff30489b6c1..74a205276906 100644 --- a/polkadot/node/network/bitfield-distribution/Cargo.toml +++ b/polkadot/node/network/bitfield-distribution/Cargo.toml @@ -16,21 +16,21 @@ always-assert = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } [dev-dependencies] -polkadot-node-subsystem-test-helpers = { workspace = true } +assert_matches = { workspace = true } bitvec = { features = ["alloc"], workspace = true } -sp-core = { workspace = true, default-features = true } +maplit = { workspace = true } +polkadot-node-subsystem-test-helpers = { workspace = true } +rand_chacha = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } sp-authority-discovery = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -maplit = { workspace = true } +sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true } -assert_matches = { workspace = true } -rand_chacha = { workspace = true, default-features = true } diff --git a/polkadot/node/network/bridge/Cargo.toml b/polkadot/node/network/bridge/Cargo.toml index c4b46c1dc001..cdc1bc3f6c1b 100644 --- a/polkadot/node/network/bridge/Cargo.toml +++ b/polkadot/node/network/bridge/Cargo.toml @@ -14,26 +14,26 @@ workspace = true [dependencies] always-assert = { workspace = true } async-trait = { workspace = true } +bytes = { workspace = true, default-features = true } +codec = { features = ["derive"], workspace = true } +fatality = { workspace = true } futures = { workspace = true } gum = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true } -sc-network = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } -parking_lot = { workspace = true, default-features = true } -bytes = { workspace = true, default-features = true } -fatality = { workspace = true } +polkadot-primitives = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } thiserror = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } +futures-timer = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives-test-helpers = { workspace = true } sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -futures-timer = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } diff --git a/polkadot/node/network/collator-protocol/Cargo.toml b/polkadot/node/network/collator-protocol/Cargo.toml index a51d24c70807..a02b281b6fc4 100644 --- a/polkadot/node/network/collator-protocol/Cargo.toml +++ b/polkadot/node/network/collator-protocol/Cargo.toml @@ -19,28 +19,28 @@ gum = { workspace = true, default-features = true } schnellru.workspace = true sp-core = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +fatality = { workspace = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } -fatality = { workspace = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } thiserror = { workspace = true } tokio-util = { workspace = true } [dev-dependencies] -sp-tracing = { workspace = true } assert_matches = { workspace = true } rstest = { workspace = true } +sp-tracing = { workspace = true } -sp-core = { features = ["std"], workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } +codec = { features = ["std"], workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } -codec = { features = ["std"], workspace = true, default-features = true } +sp-core = { features = ["std"], workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } diff --git a/polkadot/node/network/dispute-distribution/Cargo.toml b/polkadot/node/network/dispute-distribution/Cargo.toml index 4f2f9ccadf8b..079a37ca0aff 100644 --- a/polkadot/node/network/dispute-distribution/Cargo.toml +++ b/polkadot/node/network/dispute-distribution/Cargo.toml @@ -12,32 +12,32 @@ repository.workspace = true workspace = true [dependencies] +codec = { features = ["std"], workspace = true, default-features = true } +derive_more = { workspace = true, default-features = true } +fatality = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -derive_more = { workspace = true, default-features = true } -codec = { features = ["std"], workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +indexmap = { workspace = true } polkadot-erasure-coding = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } +schnellru = { workspace = true } sp-application-crypto = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } thiserror = { workspace = true } -fatality = { workspace = true } -schnellru = { workspace = true } -indexmap = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } async-channel = { workspace = true } async-trait = { workspace = true } +futures-timer = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } +polkadot-primitives-test-helpers = { workspace = true } +sc-keystore = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -futures-timer = { workspace = true } -assert_matches = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } diff --git a/polkadot/node/network/gossip-support/Cargo.toml b/polkadot/node/network/gossip-support/Cargo.toml index 7d17ea45eab9..1ba556fc46b0 100644 --- a/polkadot/node/network/gossip-support/Cargo.toml +++ b/polkadot/node/network/gossip-support/Cargo.toml @@ -12,12 +12,12 @@ repository.workspace = true workspace = true [dependencies] +sc-network = { workspace = true, default-features = true } +sc-network-common = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -sc-network-common = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } @@ -26,15 +26,15 @@ polkadot-primitives = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } +gum = { workspace = true, default-features = true } rand = { workspace = true } rand_chacha = { workspace = true } -gum = { workspace = true, default-features = true } [dev-dependencies] -sp-keyring = { workspace = true, default-features = true } +sp-authority-discovery = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -sp-authority-discovery = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } diff --git a/polkadot/node/network/protocol/Cargo.toml b/polkadot/node/network/protocol/Cargo.toml index 0bcf224332bc..83a24959f60a 100644 --- a/polkadot/node/network/protocol/Cargo.toml +++ b/polkadot/node/network/protocol/Cargo.toml @@ -14,22 +14,22 @@ workspace = true [dependencies] async-channel = { workspace = true } async-trait = { workspace = true } +bitvec = { workspace = true, default-features = true } +codec = { features = ["derive"], workspace = true } +derive_more = { workspace = true, default-features = true } +fatality = { workspace = true } +futures = { workspace = true } +gum = { workspace = true, default-features = true } hex = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true } +polkadot-primitives = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } +sc-authority-discovery = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } -sc-authority-discovery = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } strum = { features = ["derive"], workspace = true, default-features = true } -futures = { workspace = true } thiserror = { workspace = true } -fatality = { workspace = true } -rand = { workspace = true, default-features = true } -derive_more = { workspace = true, default-features = true } -gum = { workspace = true, default-features = true } -bitvec = { workspace = true, default-features = true } [dev-dependencies] rand_chacha = { workspace = true, default-features = true } diff --git a/polkadot/node/network/statement-distribution/Cargo.toml b/polkadot/node/network/statement-distribution/Cargo.toml index d737c7bf8968..8bd058b8c849 100644 --- a/polkadot/node/network/statement-distribution/Cargo.toml +++ b/polkadot/node/network/statement-distribution/Cargo.toml @@ -12,41 +12,41 @@ repository.workspace = true workspace = true [dependencies] +arrayvec = { workspace = true } +bitvec = { workspace = true, default-features = true } +codec = { features = ["derive"], workspace = true } +fatality = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } gum = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } -sp-staking = { workspace = true } -sp-keystore = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } +indexmap = { workspace = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } -arrayvec = { workspace = true } -indexmap = { workspace = true } -codec = { features = ["derive"], workspace = true } +polkadot-primitives = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-staking = { workspace = true } thiserror = { workspace = true } -fatality = { workspace = true } -bitvec = { workspace = true, default-features = true } [dev-dependencies] -async-channel = { workspace = true } assert_matches = { workspace = true } -polkadot-node-subsystem-test-helpers = { workspace = true } -sp-authority-discovery = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } +async-channel = { workspace = true } futures-timer = { workspace = true } +polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives = { workspace = true, features = ["test"] } polkadot-primitives-test-helpers = { workspace = true } -rand_chacha = { workspace = true, default-features = true } polkadot-subsystem-bench = { workspace = true } +rand_chacha = { workspace = true, default-features = true } rstest = { workspace = true } +sc-keystore = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } +sp-authority-discovery = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } [[bench]] name = "statement-distribution-regression-bench" diff --git a/polkadot/node/overseer/Cargo.toml b/polkadot/node/overseer/Cargo.toml index 62634c1da090..fd7f1e039247 100644 --- a/polkadot/node/overseer/Cargo.toml +++ b/polkadot/node/overseer/Cargo.toml @@ -12,30 +12,30 @@ repository.workspace = true workspace = true [dependencies] -sc-client-api = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } +async-trait = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } +gum = { workspace = true, default-features = true } +orchestra = { features = ["futures_channel"], workspace = true } parking_lot = { workspace = true, default-features = true } +polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem-types = { workspace = true, default-features = true } -polkadot-node-metrics = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -orchestra = { features = ["futures_channel"], workspace = true } -gum = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -async-trait = { workspace = true } tikv-jemalloc-ctl = { optional = true, workspace = true } [dev-dependencies] -metered = { features = ["futures_channel"], workspace = true } -sp-core = { workspace = true, default-features = true } -futures = { features = ["thread-pool"], workspace = true } -femme = { workspace = true } assert_matches = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } +femme = { workspace = true } +futures = { features = ["thread-pool"], workspace = true } +metered = { features = ["futures_channel"], workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } +polkadot-primitives-test-helpers = { workspace = true } +sp-core = { workspace = true, default-features = true } [target.'cfg(target_os = "linux")'.dependencies] tikv-jemalloc-ctl = "0.5.0" diff --git a/polkadot/node/primitives/Cargo.toml b/polkadot/node/primitives/Cargo.toml index 50ee3a80ddb8..d138b77dea8f 100644 --- a/polkadot/node/primitives/Cargo.toml +++ b/polkadot/node/primitives/Cargo.toml @@ -12,24 +12,24 @@ repository.workspace = true workspace = true [dependencies] +bitvec = { features = ["alloc"], workspace = true } bounded-vec = { workspace = true } +codec = { features = ["derive"], workspace = true } futures = { workspace = true } futures-timer = { workspace = true } +polkadot-parachain-primitives = { workspace = true } polkadot-primitives = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true } -sp-core = { workspace = true, default-features = true } +sc-keystore = { workspace = true } +schnorrkel = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } sp-consensus-slots = { workspace = true } +sp-core = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -polkadot-parachain-primitives = { workspace = true } -schnorrkel = { workspace = true, default-features = true } thiserror = { workspace = true } -bitvec = { features = ["alloc"], workspace = true } -serde = { features = ["derive"], workspace = true, default-features = true } -sc-keystore = { workspace = true } [target.'cfg(not(target_os = "unknown"))'.dependencies] zstd = { version = "0.12.4", default-features = false } diff --git a/polkadot/node/service/Cargo.toml b/polkadot/node/service/Cargo.toml index c1e06dd830b5..122040a9b207 100644 --- a/polkadot/node/service/Cargo.toml +++ b/polkadot/node/service/Cargo.toml @@ -14,98 +14,98 @@ workspace = true [dependencies] # Substrate Client -sc-authority-discovery = { workspace = true, default-features = true } -sc-consensus-babe = { workspace = true, default-features = true } -sc-consensus-beefy = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true, default-features = true } mmr-gadget = { workspace = true, default-features = true } -sp-mmr-primitives = { workspace = true, default-features = true } -sp-genesis-builder = { workspace = true, default-features = true } +sc-authority-discovery = { workspace = true, default-features = true } +sc-basic-authorship = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } +sc-consensus-babe = { workspace = true, default-features = true } +sc-consensus-beefy = { workspace = true, default-features = true } +sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-slots = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } +sc-keystore = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sc-transaction-pool = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -sc-sync-state-rpc = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sc-basic-authorship = { workspace = true, default-features = true } sc-offchain = { workspace = true, default-features = true } -sc-sysinfo = { workspace = true, default-features = true } sc-service = { workspace = true } +sc-sync-state-rpc = { workspace = true, default-features = true } +sc-sysinfo = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } +sc-transaction-pool = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } +sp-genesis-builder = { workspace = true, default-features = true } +sp-mmr-primitives = { workspace = true, default-features = true } # Substrate Primitives +pallet-transaction-payment = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } sp-authority-discovery = { workspace = true, default-features = true } +sp-block-builder = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } sp-consensus-beefy = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } -sp-inherents = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sp-block-builder = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-inherents = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-offchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-session = { workspace = true, default-features = true } -sp-transaction-pool = { workspace = true, default-features = true } -pallet-transaction-payment = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } -sp-weights = { workspace = true, default-features = true } +sp-transaction-pool = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } +sp-weights = { workspace = true, default-features = true } # Substrate Pallets -pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } frame-metadata-hash-extension = { optional = true, workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } # Substrate Other +frame-benchmarking = { workspace = true, default-features = true } +frame-benchmarking-cli = { workspace = true, default-features = true } frame-system-rpc-runtime-api = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } -frame-benchmarking-cli = { workspace = true, default-features = true } -frame-benchmarking = { workspace = true, default-features = true } # External Crates async-trait = { workspace = true } +codec = { workspace = true, default-features = true } futures = { workspace = true } -is_executable = { workspace = true } gum = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } +is_executable = { workspace = true } kvdb = { workspace = true } kvdb-rocksdb = { optional = true, workspace = true } +log = { workspace = true, default-features = true } parity-db = { optional = true, workspace = true } -codec = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } # Polkadot polkadot-core-primitives = { workspace = true, default-features = true } polkadot-node-core-parachains-inherent = { workspace = true, default-features = true } -polkadot-overseer = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-rpc = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-node-subsystem-types = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-rpc = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } -polkadot-node-network-protocol = { workspace = true, default-features = true } # Polkadot Runtime Constants rococo-runtime-constants = { optional = true, workspace = true, default-features = true } westend-runtime-constants = { optional = true, workspace = true, default-features = true } # Polkadot Runtimes -westend-runtime = { optional = true, workspace = true } rococo-runtime = { optional = true, workspace = true } +westend-runtime = { optional = true, workspace = true } # Polkadot Subsystems polkadot-approval-distribution = { optional = true, workspace = true, default-features = true } @@ -137,11 +137,11 @@ xcm = { workspace = true, default-features = true } xcm-runtime-apis = { workspace = true, default-features = true } [dev-dependencies] -polkadot-test-client = { workspace = true } +assert_matches = { workspace = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } +polkadot-test-client = { workspace = true } sp-tracing = { workspace = true } -assert_matches = { workspace = true } tempfile = { workspace = true } [features] diff --git a/polkadot/node/subsystem-bench/Cargo.toml b/polkadot/node/subsystem-bench/Cargo.toml index 8633818e775d..e288ee2b78d3 100644 --- a/polkadot/node/subsystem-bench/Cargo.toml +++ b/polkadot/node/subsystem-bench/Cargo.toml @@ -21,79 +21,79 @@ doc = false [dependencies] -tikv-jemallocator = { features = ["profiling", "unprefixed_malloc_on_supported_platforms"], workspace = true, optional = true } -jemalloc_pprof = { workspace = true, optional = true } -polkadot-service = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-node-subsystem-util = { workspace = true, default-features = true } -polkadot-node-subsystem-types = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, features = ["test"] } -polkadot-node-network-protocol = { workspace = true, default-features = true } -polkadot-availability-recovery = { features = ["subsystem-benchmarks"], workspace = true, default-features = true } -polkadot-availability-distribution = { workspace = true, default-features = true } -polkadot-statement-distribution = { workspace = true, default-features = true } -polkadot-node-core-av-store = { workspace = true, default-features = true } -polkadot-node-core-chain-api = { workspace = true, default-features = true } -polkadot-availability-bitfield-distribution = { workspace = true, default-features = true } -color-eyre = { workspace = true } -polkadot-overseer = { workspace = true, default-features = true } -colored = { workspace = true } assert_matches = { workspace = true } async-trait = { workspace = true } -sp-keystore = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +bincode = { workspace = true } clap = { features = ["derive"], workspace = true } +color-eyre = { workspace = true } +colored = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } -bincode = { workspace = true } -sha1 = { workspace = true } -hex = { workspace = true, default-features = true } gum = { workspace = true, default-features = true } -polkadot-erasure-coding = { workspace = true, default-features = true } +hex = { workspace = true, default-features = true } +jemalloc_pprof = { workspace = true, optional = true } log = { workspace = true, default-features = true } -sp-tracing = { workspace = true } +polkadot-availability-bitfield-distribution = { workspace = true, default-features = true } +polkadot-availability-distribution = { workspace = true, default-features = true } +polkadot-availability-recovery = { features = ["subsystem-benchmarks"], workspace = true, default-features = true } +polkadot-erasure-coding = { workspace = true, default-features = true } +polkadot-node-core-av-store = { workspace = true, default-features = true } +polkadot-node-core-chain-api = { workspace = true, default-features = true } +polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-node-subsystem-types = { workspace = true, default-features = true } +polkadot-node-subsystem-util = { workspace = true, default-features = true } +polkadot-overseer = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, features = ["test"] } +polkadot-service = { workspace = true, default-features = true } +polkadot-statement-distribution = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sc-keystore = { workspace = true, default-features = true } +sha1 = { workspace = true } +sp-core = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-tracing = { workspace = true } +tikv-jemallocator = { features = ["profiling", "unprefixed_malloc_on_supported_platforms"], workspace = true, optional = true } # `rand` only supports uniform distribution, we need normal distribution for latency. -rand_distr = { workspace = true } bitvec = { workspace = true, default-features = true } kvdb-memorydb = { workspace = true } +rand_distr = { workspace = true } -codec = { features = ["derive", "std"], workspace = true, default-features = true } -tokio = { features = ["parking_lot", "rt-multi-thread"], workspace = true, default-features = true } clap-num = { workspace = true } +codec = { features = ["derive", "std"], workspace = true, default-features = true } +itertools = { workspace = true } +polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } -sp-keyring = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } +polkadot-primitives-test-helpers = { workspace = true } +prometheus = { workspace = true } +prometheus-endpoint = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -polkadot-node-metrics = { workspace = true, default-features = true } -itertools = { workspace = true } -polkadot-primitives-test-helpers = { workspace = true } -prometheus-endpoint = { workspace = true, default-features = true } -prometheus = { workspace = true } serde = { workspace = true, default-features = true } -serde_yaml = { workspace = true } serde_json = { workspace = true } +serde_yaml = { workspace = true } +sp-application-crypto = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +tokio = { features = ["parking_lot", "rt-multi-thread"], workspace = true, default-features = true } +polkadot-approval-distribution = { workspace = true, default-features = true } polkadot-node-core-approval-voting = { workspace = true, default-features = true } polkadot-node-core-approval-voting-parallel = { workspace = true, default-features = true } -polkadot-approval-distribution = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } sp-runtime = { workspace = true } sp-timestamp = { workspace = true, default-features = true } schnorrkel = { workspace = true } # rand_core should match schnorrkel -rand_core = { workspace = true } -rand_chacha = { workspace = true, default-features = true } -paste = { workspace = true, default-features = true } orchestra = { features = ["futures_channel"], workspace = true } +paste = { workspace = true, default-features = true } pyroscope = { workspace = true } pyroscope_pprofrs = { workspace = true } +rand_chacha = { workspace = true, default-features = true } +rand_core = { workspace = true } strum = { features = ["derive"], workspace = true, default-features = true } [features] diff --git a/polkadot/node/subsystem-test-helpers/Cargo.toml b/polkadot/node/subsystem-test-helpers/Cargo.toml index d3229291673c..4e660b15c1e2 100644 --- a/polkadot/node/subsystem-test-helpers/Cargo.toml +++ b/polkadot/node/subsystem-test-helpers/Cargo.toml @@ -14,16 +14,16 @@ workspace = true async-trait = { workspace = true } futures = { workspace = true } parking_lot = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-erasure-coding = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } +sc-keystore = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } diff --git a/polkadot/node/subsystem-types/Cargo.toml b/polkadot/node/subsystem-types/Cargo.toml index 44bb7036d63d..6c88a4474137 100644 --- a/polkadot/node/subsystem-types/Cargo.toml +++ b/polkadot/node/subsystem-types/Cargo.toml @@ -12,25 +12,25 @@ repository.workspace = true workspace = true [dependencies] +async-trait = { workspace = true } +bitvec = { features = ["alloc"], workspace = true } derive_more = { workspace = true, default-features = true } fatality = { workspace = true } futures = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } +orchestra = { features = ["futures_channel"], workspace = true } polkadot-node-network-protocol = { workspace = true, default-features = true } +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-statement-table = { workspace = true, default-features = true } -orchestra = { features = ["futures_channel"], workspace = true } +prometheus-endpoint = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } +smallvec = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } +sp-authority-discovery = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-authority-discovery = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -smallvec = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } thiserror = { workspace = true } -async-trait = { workspace = true } -bitvec = { features = ["alloc"], workspace = true } diff --git a/polkadot/node/subsystem-util/Cargo.toml b/polkadot/node/subsystem-util/Cargo.toml index 9c21fede1c47..0e6ebf611997 100644 --- a/polkadot/node/subsystem-util/Cargo.toml +++ b/polkadot/node/subsystem-util/Cargo.toml @@ -13,33 +13,33 @@ workspace = true [dependencies] async-trait = { workspace = true } +codec = { features = ["derive"], workspace = true } +derive_more = { workspace = true, default-features = true } +fatality = { workspace = true } futures = { workspace = true } futures-channel = { workspace = true } +gum = { workspace = true, default-features = true } itertools = { workspace = true } -codec = { features = ["derive"], workspace = true } parking_lot = { workspace = true, default-features = true } pin-project = { workspace = true } rand = { workspace = true, default-features = true } -thiserror = { workspace = true } -fatality = { workspace = true } -gum = { workspace = true, default-features = true } -derive_more = { workspace = true, default-features = true } schnellru = { workspace = true } +thiserror = { workspace = true } +metered = { features = ["futures_channel"], workspace = true } polkadot-erasure-coding = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-node-subsystem-types = { workspace = true, default-features = true } polkadot-node-metrics = { workspace = true, default-features = true } polkadot-node-network-protocol = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-node-subsystem-types = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } -metered = { features = ["futures_channel"], workspace = true } +polkadot-primitives = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } kvdb = { workspace = true } parity-db = { workspace = true } @@ -47,9 +47,9 @@ parity-db = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } futures = { features = ["thread-pool"], workspace = true } +kvdb-memorydb = { workspace = true } +kvdb-shared-tests = { workspace = true } log = { workspace = true, default-features = true } polkadot-node-subsystem-test-helpers = { workspace = true } polkadot-primitives-test-helpers = { workspace = true } -kvdb-shared-tests = { workspace = true } tempfile = { workspace = true } -kvdb-memorydb = { workspace = true } diff --git a/polkadot/node/subsystem/Cargo.toml b/polkadot/node/subsystem/Cargo.toml index 4f30d3ce9c09..8b4a26e33ee6 100644 --- a/polkadot/node/subsystem/Cargo.toml +++ b/polkadot/node/subsystem/Cargo.toml @@ -12,5 +12,5 @@ repository.workspace = true workspace = true [dependencies] -polkadot-overseer = { workspace = true, default-features = true } polkadot-node-subsystem-types = { workspace = true, default-features = true } +polkadot-overseer = { workspace = true, default-features = true } diff --git a/polkadot/node/test/client/Cargo.toml b/polkadot/node/test/client/Cargo.toml index 587af659fbd2..13b14c0b9231 100644 --- a/polkadot/node/test/client/Cargo.toml +++ b/polkadot/node/test/client/Cargo.toml @@ -13,32 +13,32 @@ workspace = true codec = { features = ["derive"], workspace = true } # Polkadot dependencies +polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-test-runtime = { workspace = true } polkadot-test-service = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } # Substrate dependencies -substrate-test-client = { workspace = true } -sc-service = { workspace = true, default-features = true } +frame-benchmarking = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-offchain = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-inherents = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } -sp-state-machine = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-inherents = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -frame-benchmarking = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } +substrate-test-client = { workspace = true } [dev-dependencies] -sp-keyring = { workspace = true, default-features = true } futures = { workspace = true } +sp-keyring = { workspace = true, default-features = true } [features] runtime-benchmarks = [ diff --git a/polkadot/node/test/service/Cargo.toml b/polkadot/node/test/service/Cargo.toml index 4ef9d88621fb..54db2a0ac942 100644 --- a/polkadot/node/test/service/Cargo.toml +++ b/polkadot/node/test/service/Cargo.toml @@ -11,50 +11,50 @@ workspace = true [dependencies] futures = { workspace = true } -hex = { workspace = true, default-features = true } gum = { workspace = true, default-features = true } +hex = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } tempfile = { workspace = true } tokio = { workspace = true, default-features = true } # Polkadot dependencies +polkadot-node-primitives = { workspace = true, default-features = true } +polkadot-node-subsystem = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-rpc = { workspace = true, default-features = true } polkadot-runtime-common = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-service = { workspace = true, default-features = true } -polkadot-node-subsystem = { workspace = true, default-features = true } -polkadot-node-primitives = { workspace = true, default-features = true } polkadot-test-runtime = { workspace = true } test-runtime-constants = { workspace = true, default-features = true } -polkadot-runtime-parachains = { workspace = true, default-features = true } # Substrate dependencies -sp-authority-discovery = { workspace = true, default-features = true } -sc-authority-discovery = { workspace = true, default-features = true } -sc-consensus-babe = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true, default-features = true } -sp-consensus-grandpa = { workspace = true, default-features = true } -sp-inherents = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } +sc-authority-discovery = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } +sc-consensus-babe = { workspace = true, default-features = true } +sc-consensus-grandpa = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } +sc-service = { workspace = true } sc-tracing = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } -sc-service = { workspace = true } sp-arithmetic = { workspace = true, default-features = true } +sp-authority-discovery = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-consensus-grandpa = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-inherents = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } diff --git a/polkadot/node/zombienet-backchannel/Cargo.toml b/polkadot/node/zombienet-backchannel/Cargo.toml index 56c49a1ec305..0d04012e28a8 100644 --- a/polkadot/node/zombienet-backchannel/Cargo.toml +++ b/polkadot/node/zombienet-backchannel/Cargo.toml @@ -12,13 +12,13 @@ license.workspace = true workspace = true [dependencies] -tokio = { features = ["macros", "net", "rt-multi-thread", "sync"], workspace = true } -url = { workspace = true } -tokio-tungstenite = { workspace = true } -futures-util = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true, default-features = true } -reqwest = { features = ["rustls-tls"], workspace = true } -thiserror = { workspace = true } +futures-util = { workspace = true, default-features = true } gum = { workspace = true, default-features = true } +reqwest = { features = ["rustls-tls"], workspace = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } +thiserror = { workspace = true } +tokio = { features = ["macros", "net", "rt-multi-thread", "sync"], workspace = true } +tokio-tungstenite = { workspace = true } +url = { workspace = true } diff --git a/polkadot/parachain/Cargo.toml b/polkadot/parachain/Cargo.toml index ea6c4423dc19..0dd103d58b25 100644 --- a/polkadot/parachain/Cargo.toml +++ b/polkadot/parachain/Cargo.toml @@ -15,14 +15,14 @@ workspace = true # note: special care is taken to avoid inclusion of `sp-io` externals when compiling # this crate for WASM. This is critical to avoid forcing all parachain WASM into implementing # various unnecessary Substrate-specific endpoints. +bounded-collections = { features = ["serde"], workspace = true } codec = { features = ["derive"], workspace = true } +derive_more = { workspace = true, default-features = true } +polkadot-core-primitives = { workspace = true } scale-info = { features = ["derive", "serde"], workspace = true } -sp-runtime = { features = ["serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } +sp-runtime = { features = ["serde"], workspace = true } sp-weights = { workspace = true } -polkadot-core-primitives = { workspace = true } -derive_more = { workspace = true, default-features = true } -bounded-collections = { features = ["serde"], workspace = true } # all optional crates. serde = { features = ["alloc", "derive"], workspace = true } diff --git a/polkadot/parachain/test-parachains/Cargo.toml b/polkadot/parachain/test-parachains/Cargo.toml index 9f35653f957f..2a1e1722bff9 100644 --- a/polkadot/parachain/test-parachains/Cargo.toml +++ b/polkadot/parachain/test-parachains/Cargo.toml @@ -11,8 +11,8 @@ publish = false workspace = true [dependencies] -tiny-keccak = { features = ["keccak"], workspace = true } codec = { features = ["derive"], workspace = true } +tiny-keccak = { features = ["keccak"], workspace = true } test-parachain-adder = { workspace = true } test-parachain-halt = { workspace = true } diff --git a/polkadot/parachain/test-parachains/adder/Cargo.toml b/polkadot/parachain/test-parachains/adder/Cargo.toml index 7a150b75d5cd..945b0e156904 100644 --- a/polkadot/parachain/test-parachains/adder/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/Cargo.toml @@ -12,10 +12,10 @@ publish = false workspace = true [dependencies] -polkadot-parachain-primitives = { features = ["wasm-api"], workspace = true } codec = { features = ["derive"], workspace = true } -tiny-keccak = { features = ["keccak"], workspace = true } dlmalloc = { features = ["global"], workspace = true } +polkadot-parachain-primitives = { features = ["wasm-api"], workspace = true } +tiny-keccak = { features = ["keccak"], workspace = true } # We need to make sure the global allocator is disabled until we have support of full substrate externalities sp-io = { features = ["disable_allocator"], workspace = true } diff --git a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml index 061378a76a82..20305dc07c3a 100644 --- a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml @@ -15,30 +15,30 @@ name = "adder-collator" path = "src/main.rs" [dependencies] -codec = { features = ["derive"], workspace = true } clap = { features = ["derive"], workspace = true } +codec = { features = ["derive"], workspace = true } futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } -test-parachain-adder = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-cli = { workspace = true, default-features = true } -polkadot-service = { features = ["rococo-native"], workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-service = { features = ["rococo-native"], workspace = true, default-features = true } +test-parachain-adder = { workspace = true } sc-cli = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } [dev-dependencies] +polkadot-node-core-pvf = { features = ["test-utils"], workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-test-service = { workspace = true } -polkadot-node-core-pvf = { features = ["test-utils"], workspace = true, default-features = true } -substrate-test-utils = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } +substrate-test-utils = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/polkadot/parachain/test-parachains/halt/Cargo.toml b/polkadot/parachain/test-parachains/halt/Cargo.toml index f8272f6ed196..ea8372ccd121 100644 --- a/polkadot/parachain/test-parachains/halt/Cargo.toml +++ b/polkadot/parachain/test-parachains/halt/Cargo.toml @@ -14,8 +14,8 @@ workspace = true [dependencies] [build-dependencies] -substrate-wasm-builder = { workspace = true, default-features = true } rustversion = { workspace = true } +substrate-wasm-builder = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/polkadot/parachain/test-parachains/undying/Cargo.toml b/polkadot/parachain/test-parachains/undying/Cargo.toml index 4b2e12ebf435..43b5a3352434 100644 --- a/polkadot/parachain/test-parachains/undying/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/Cargo.toml @@ -12,11 +12,11 @@ license.workspace = true workspace = true [dependencies] -polkadot-parachain-primitives = { features = ["wasm-api"], workspace = true } codec = { features = ["derive"], workspace = true } -tiny-keccak = { features = ["keccak"], workspace = true } dlmalloc = { features = ["global"], workspace = true } log = { workspace = true } +polkadot-parachain-primitives = { features = ["wasm-api"], workspace = true } +tiny-keccak = { features = ["keccak"], workspace = true } # We need to make sure the global allocator is disabled until we have support of full substrate externalities sp-io = { features = ["disable_allocator"], workspace = true } diff --git a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml index 5760258c70ea..b964b4dc49ce 100644 --- a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml @@ -15,30 +15,30 @@ name = "undying-collator" path = "src/main.rs" [dependencies] -codec = { features = ["derive"], workspace = true } clap = { features = ["derive"], workspace = true } +codec = { features = ["derive"], workspace = true } futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } -test-parachain-undying = { workspace = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-cli = { workspace = true, default-features = true } -polkadot-service = { features = ["rococo-native"], workspace = true, default-features = true } polkadot-node-primitives = { workspace = true, default-features = true } polkadot-node-subsystem = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +polkadot-service = { features = ["rococo-native"], workspace = true, default-features = true } +test-parachain-undying = { workspace = true } sc-cli = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } [dev-dependencies] +polkadot-node-core-pvf = { features = ["test-utils"], workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-test-service = { workspace = true } -polkadot-node-core-pvf = { features = ["test-utils"], workspace = true, default-features = true } -substrate-test-utils = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } +substrate-test-utils = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/polkadot/primitives/Cargo.toml b/polkadot/primitives/Cargo.toml index 150aaf153fa7..e693fe8c4a8c 100644 --- a/polkadot/primitives/Cargo.toml +++ b/polkadot/primitives/Cargo.toml @@ -13,23 +13,23 @@ workspace = true [dependencies] bitvec = { features = ["alloc", "serde"], workspace = true } -hex-literal = { workspace = true, default-features = true } codec = { features = ["bit-vec", "derive"], workspace = true } -scale-info = { features = ["bit-vec", "derive", "serde"], workspace = true } +hex-literal = { workspace = true, default-features = true } log = { workspace = true } +scale-info = { features = ["bit-vec", "derive", "serde"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true } thiserror = { workspace = true, optional = true } -sp-application-crypto = { features = ["serde"], workspace = true } -sp-inherents = { workspace = true } -sp-core = { workspace = true } -sp-runtime = { workspace = true } sp-api = { workspace = true } +sp-application-crypto = { features = ["serde"], workspace = true } sp-arithmetic = { features = ["serde"], workspace = true } sp-authority-discovery = { features = ["serde"], workspace = true } sp-consensus-slots = { features = ["serde"], workspace = true } +sp-core = { workspace = true } +sp-inherents = { workspace = true } sp-io = { workspace = true } sp-keystore = { optional = true, workspace = true } +sp-runtime = { workspace = true } sp-staking = { features = ["serde"], workspace = true } sp-std = { workspace = true, optional = true } diff --git a/polkadot/primitives/test-helpers/Cargo.toml b/polkadot/primitives/test-helpers/Cargo.toml index 27de3c4b9c56..962b210848c8 100644 --- a/polkadot/primitives/test-helpers/Cargo.toml +++ b/polkadot/primitives/test-helpers/Cargo.toml @@ -10,9 +10,9 @@ license.workspace = true workspace = true [dependencies] -sp-keyring = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true } -sp-runtime = { workspace = true, default-features = true } -sp-core = { features = ["std"], workspace = true, default-features = true } polkadot-primitives = { features = ["test"], workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true } +sp-core = { features = ["std"], workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } diff --git a/polkadot/rpc/Cargo.toml b/polkadot/rpc/Cargo.toml index 48980dde4bbc..33ce3ff4acc6 100644 --- a/polkadot/rpc/Cargo.toml +++ b/polkadot/rpc/Cargo.toml @@ -13,19 +13,11 @@ workspace = true [dependencies] jsonrpsee = { features = ["server"], workspace = true } +mmr-rpc = { workspace = true, default-features = true } +pallet-transaction-payment-rpc = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -sp-consensus-babe = { workspace = true, default-features = true } -sp-consensus-beefy = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } -sc-rpc = { workspace = true, default-features = true } -sc-rpc-spec-v2 = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-babe-rpc = { workspace = true, default-features = true } sc-consensus-beefy = { workspace = true, default-features = true } @@ -33,10 +25,18 @@ sc-consensus-beefy-rpc = { workspace = true, default-features = true } sc-consensus-epochs = { workspace = true, default-features = true } sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-grandpa-rpc = { workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true } +sc-rpc-spec-v2 = { workspace = true, default-features = true } sc-sync-state-rpc = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } -substrate-frame-rpc-system = { workspace = true, default-features = true } -mmr-rpc = { workspace = true, default-features = true } -pallet-transaction-payment-rpc = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-consensus-babe = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +substrate-frame-rpc-system = { workspace = true, default-features = true } substrate-state-trie-migration-rpc = { workspace = true, default-features = true } diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml index 4b307b56bcbe..4ffa5c475ed7 100644 --- a/polkadot/runtime/common/Cargo.toml +++ b/polkadot/runtime/common/Cargo.toml @@ -12,9 +12,9 @@ repository.workspace = true workspace = true [dependencies] -impl-trait-for-tuples = { workspace = true } bitvec = { features = ["alloc"], workspace = true } codec = { features = ["derive"], workspace = true } +impl-trait-for-tuples = { workspace = true } log = { workspace = true } rustc-hex = { workspace = true } scale-info = { features = ["derive"], workspace = true } @@ -23,55 +23,55 @@ serde_derive = { workspace = true } static_assertions = { workspace = true, default-features = true } sp-api = { workspace = true } +sp-core = { features = ["serde"], workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } +sp-npos-elections = { features = ["serde"], workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-session = { workspace = true } sp-staking = { features = ["serde"], workspace = true } -sp-core = { features = ["serde"], workspace = true } -sp-keyring = { workspace = true } -sp-npos-elections = { features = ["serde"], workspace = true } +frame-election-provider-support = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-asset-rate = { optional = true, workspace = true } pallet-authorship = { workspace = true } pallet-balances = { workspace = true } pallet-broker = { workspace = true } +pallet-election-provider-multi-phase = { workspace = true } pallet-fast-unstake = { workspace = true } pallet-identity = { workspace = true } pallet-session = { workspace = true } -frame-support = { workspace = true } pallet-staking = { workspace = true } pallet-staking-reward-fn = { workspace = true } -frame-system = { workspace = true } pallet-timestamp = { workspace = true } -pallet-vesting = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-treasury = { workspace = true } -pallet-asset-rate = { optional = true, workspace = true } -pallet-election-provider-multi-phase = { workspace = true } -frame-election-provider-support = { workspace = true } +pallet-vesting = { workspace = true } frame-benchmarking = { optional = true, workspace = true } pallet-babe = { optional = true, workspace = true } -polkadot-primitives = { workspace = true } libsecp256k1 = { workspace = true } +polkadot-primitives = { workspace = true } polkadot-runtime-parachains = { workspace = true } slot-range-helper = { workspace = true } xcm = { workspace = true } -xcm-executor = { optional = true, workspace = true } xcm-builder = { workspace = true } +xcm-executor = { optional = true, workspace = true } [dev-dependencies] -hex-literal = { workspace = true, default-features = true } frame-support-test = { workspace = true } +hex-literal = { workspace = true, default-features = true } +libsecp256k1 = { workspace = true, default-features = true } pallet-babe = { workspace = true, default-features = true } pallet-treasury = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -libsecp256k1 = { workspace = true, default-features = true } polkadot-primitives-test-helpers = { workspace = true } +serde_json = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/polkadot/runtime/common/slot_range_helper/Cargo.toml b/polkadot/runtime/common/slot_range_helper/Cargo.toml index 3f110bdd76c6..684cdcd01e14 100644 --- a/polkadot/runtime/common/slot_range_helper/Cargo.toml +++ b/polkadot/runtime/common/slot_range_helper/Cargo.toml @@ -12,9 +12,9 @@ repository.workspace = true workspace = true [dependencies] -paste = { workspace = true, default-features = true } -enumn = { workspace = true } codec = { features = ["derive"], workspace = true } +enumn = { workspace = true } +paste = { workspace = true, default-features = true } sp-runtime = { workspace = true } [features] diff --git a/polkadot/runtime/metrics/Cargo.toml b/polkadot/runtime/metrics/Cargo.toml index 0415e4754009..beb7e3236d5a 100644 --- a/polkadot/runtime/metrics/Cargo.toml +++ b/polkadot/runtime/metrics/Cargo.toml @@ -12,10 +12,10 @@ repository.workspace = true workspace = true [dependencies] -sp-tracing = { workspace = true } codec = { workspace = true } -polkadot-primitives = { workspace = true } frame-benchmarking = { optional = true, workspace = true } +polkadot-primitives = { workspace = true } +sp-tracing = { workspace = true } bs58 = { features = ["alloc"], workspace = true } diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index b583e9c6cc50..7c00995d2291 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -12,32 +12,35 @@ repository.workspace = true workspace = true [dependencies] -impl-trait-for-tuples = { workspace = true } +bitflags = { workspace = true } bitvec = { features = ["alloc"], workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } +derive_more = { workspace = true, default-features = true } +impl-trait-for-tuples = { workspace = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true } -derive_more = { workspace = true, default-features = true } -bitflags = { workspace = true } sp-api = { workspace = true } +sp-application-crypto = { optional = true, workspace = true } +sp-arithmetic = { workspace = true } +sp-core = { features = ["serde"], workspace = true } sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keystore = { optional = true, workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-session = { workspace = true } sp-staking = { features = ["serde"], workspace = true } -sp-core = { features = ["serde"], workspace = true } -sp-keystore = { optional = true, workspace = true } -sp-application-crypto = { optional = true, workspace = true } -sp-tracing = { optional = true, workspace = true } -sp-arithmetic = { workspace = true } sp-std = { workspace = true, optional = true } +sp-tracing = { optional = true, workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } pallet-authority-discovery = { workspace = true } pallet-authorship = { workspace = true } -pallet-balances = { workspace = true } pallet-babe = { workspace = true } +pallet-balances = { workspace = true } pallet-broker = { workspace = true } pallet-message-queue = { workspace = true } pallet-mmr = { workspace = true, optional = true } @@ -45,36 +48,33 @@ pallet-session = { workspace = true } pallet-staking = { workspace = true } pallet-timestamp = { workspace = true } pallet-vesting = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +polkadot-primitives = { workspace = true } xcm = { workspace = true } xcm-executor = { workspace = true } -polkadot-primitives = { workspace = true } +polkadot-core-primitives = { workspace = true } +polkadot-parachain-primitives = { workspace = true } +polkadot-runtime-metrics = { workspace = true } rand = { workspace = true } rand_chacha = { workspace = true } static_assertions = { optional = true, workspace = true, default-features = true } -polkadot-parachain-primitives = { workspace = true } -polkadot-runtime-metrics = { workspace = true } -polkadot-core-primitives = { workspace = true } [dev-dependencies] polkadot-primitives = { workspace = true, features = ["test"] } +assert_matches = { workspace = true } +frame-support-test = { workspace = true } futures = { workspace = true } hex-literal = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -frame-support-test = { workspace = true } -sc-keystore = { workspace = true, default-features = true } polkadot-primitives-test-helpers = { workspace = true } -sp-tracing = { workspace = true, default-features = true } -sp-crypto-hashing = { workspace = true, default-features = true } -thousands = { workspace = true } -assert_matches = { workspace = true } rstest = { workspace = true } +sc-keystore = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } +thousands = { workspace = true } [features] default = ["std"] diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index 1fd32c5d0c32..e7f463566e3a 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -13,39 +13,44 @@ repository.workspace = true workspace = true [dependencies] +bitvec = { features = ["alloc"], workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } -scale-info = { features = ["derive"], workspace = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } serde = { workspace = true } serde_derive = { optional = true, workspace = true } serde_json = { features = ["alloc"], workspace = true } -static_assertions = { workspace = true, default-features = true } smallvec = { workspace = true, default-features = true } -bitvec = { features = ["alloc"], workspace = true } +static_assertions = { workspace = true, default-features = true } +binary-merkle-tree = { workspace = true } +rococo-runtime-constants = { workspace = true } +sp-api = { workspace = true } +sp-arithmetic = { workspace = true } sp-authority-discovery = { workspace = true } +sp-block-builder = { workspace = true } sp-consensus-babe = { workspace = true } sp-consensus-beefy = { workspace = true } sp-consensus-grandpa = { workspace = true } -binary-merkle-tree = { workspace = true } -rococo-runtime-constants = { workspace = true } -sp-api = { workspace = true } +sp-core = { workspace = true } sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } -sp-offchain = { workspace = true } -sp-arithmetic = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } sp-mmr-primitives = { workspace = true } +sp-offchain = { workspace = true } sp-runtime = { workspace = true } -sp-staking = { workspace = true } -sp-core = { workspace = true } sp-session = { workspace = true } +sp-staking = { workspace = true } sp-storage = { workspace = true } -sp-version = { workspace = true } sp-transaction-pool = { workspace = true } -sp-block-builder = { workspace = true } -sp-keyring = { workspace = true } +sp-version = { workspace = true } +frame-executive = { workspace = true } +frame-support = { features = ["tuples-96"], workspace = true } +frame-system = { workspace = true } +frame-system-rpc-runtime-api = { workspace = true } +pallet-asset-rate = { workspace = true } pallet-authority-discovery = { workspace = true } pallet-authorship = { workspace = true } pallet-babe = { workspace = true } @@ -54,15 +59,10 @@ pallet-beefy = { workspace = true } pallet-beefy-mmr = { workspace = true } pallet-bounties = { workspace = true } pallet-child-bounties = { workspace = true } -pallet-state-trie-migration = { workspace = true } -pallet-transaction-payment = { workspace = true } -pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-collective = { workspace = true } pallet-conviction-voting = { workspace = true } pallet-democracy = { workspace = true } pallet-elections-phragmen = { workspace = true } -pallet-asset-rate = { workspace = true } -frame-executive = { workspace = true } pallet-grandpa = { workspace = true } pallet-identity = { workspace = true } pallet-indices = { workspace = true } @@ -79,48 +79,48 @@ pallet-proxy = { workspace = true } pallet-ranked-collective = { workspace = true } pallet-recovery = { workspace = true } pallet-referenda = { workspace = true } +pallet-root-testing = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-society = { workspace = true } -pallet-sudo = { workspace = true } -frame-support = { features = ["tuples-96"], workspace = true } pallet-staking = { workspace = true } -frame-system = { workspace = true } -frame-system-rpc-runtime-api = { workspace = true } +pallet-state-trie-migration = { workspace = true } +pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } pallet-tips = { workspace = true } +pallet-transaction-payment = { workspace = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } pallet-vesting = { workspace = true } pallet-whitelist = { workspace = true } pallet-xcm = { workspace = true } pallet-xcm-benchmarks = { optional = true, workspace = true } -pallet-root-testing = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-metadata-hash-extension = { workspace = true } -frame-try-runtime = { optional = true, workspace = true } frame-system-benchmarking = { optional = true, workspace = true } +frame-try-runtime = { optional = true, workspace = true } hex-literal = { workspace = true, default-features = true } +polkadot-parachain-primitives = { workspace = true } +polkadot-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } polkadot-runtime-parachains = { workspace = true } -polkadot-primitives = { workspace = true } -polkadot-parachain-primitives = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } [dev-dependencies] -tiny-keccak = { features = ["keccak"], workspace = true } -sp-keyring = { workspace = true, default-features = true } remote-externalities = { workspace = true, default-features = true } -sp-trie = { workspace = true, default-features = true } separator = { workspace = true } serde_json = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true } +sp-trie = { workspace = true, default-features = true } +tiny-keccak = { features = ["keccak"], workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } [build-dependencies] diff --git a/polkadot/runtime/rococo/constants/Cargo.toml b/polkadot/runtime/rococo/constants/Cargo.toml index 921bc8f5fe92..cc62d230d2c0 100644 --- a/polkadot/runtime/rococo/constants/Cargo.toml +++ b/polkadot/runtime/rococo/constants/Cargo.toml @@ -20,9 +20,9 @@ smallvec = { workspace = true, default-features = true } frame-support = { workspace = true } polkadot-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } -sp-core = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index 8b33bf9cebc6..f35bb53ac904 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -16,59 +16,59 @@ log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { workspace = true } +frame-election-provider-support = { workspace = true } +sp-api = { workspace = true } sp-authority-discovery = { workspace = true } +sp-block-builder = { workspace = true } sp-consensus-babe = { workspace = true } sp-consensus-beefy = { workspace = true } -sp-api = { workspace = true } -sp-inherents = { workspace = true } -sp-offchain = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } -sp-staking = { workspace = true } sp-core = { workspace = true } sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } +sp-io = { workspace = true } sp-mmr-primitives = { workspace = true } +sp-offchain = { workspace = true } +sp-runtime = { workspace = true } sp-session = { workspace = true } -sp-version = { workspace = true } -frame-election-provider-support = { workspace = true } +sp-staking = { workspace = true } sp-transaction-pool = { workspace = true } -sp-block-builder = { workspace = true } +sp-version = { workspace = true } +frame-executive = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +frame-system-rpc-runtime-api = { workspace = true } pallet-authority-discovery = { workspace = true } pallet-authorship = { workspace = true } pallet-babe = { workspace = true } pallet-balances = { workspace = true } -pallet-transaction-payment = { workspace = true } -pallet-transaction-payment-rpc-runtime-api = { workspace = true } -frame-executive = { workspace = true } pallet-grandpa = { workspace = true } pallet-indices = { workspace = true } pallet-offences = { workspace = true } pallet-session = { workspace = true } -frame-support = { workspace = true } pallet-staking = { workspace = true } pallet-staking-reward-curve = { workspace = true, default-features = true } -frame-system = { workspace = true } -frame-system-rpc-runtime-api = { workspace = true } -test-runtime-constants = { workspace = true } -pallet-timestamp = { workspace = true } pallet-sudo = { workspace = true } +pallet-timestamp = { workspace = true } +pallet-transaction-payment = { workspace = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true } pallet-vesting = { workspace = true } +test-runtime-constants = { workspace = true } -polkadot-runtime-common = { workspace = true } -polkadot-primitives = { workspace = true } pallet-xcm = { workspace = true } +polkadot-primitives = { workspace = true } +polkadot-runtime-common = { workspace = true } polkadot-runtime-parachains = { workspace = true } +xcm = { workspace = true } xcm-builder = { workspace = true } xcm-executor = { workspace = true } -xcm = { workspace = true } [dev-dependencies] hex-literal = { workspace = true, default-features = true } -tiny-keccak = { features = ["keccak"], workspace = true } +serde_json = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } +tiny-keccak = { features = ["keccak"], workspace = true } [build-dependencies] substrate-wasm-builder = { workspace = true, default-features = true } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 13e39b5aa317..e945e64e7fc0 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -15,36 +15,36 @@ workspace = true [dependencies] bitvec = { features = ["alloc"], workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } -scale-info = { features = ["derive"], workspace = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } serde = { workspace = true } serde_derive = { optional = true, workspace = true } serde_json = { features = ["alloc"], workspace = true } smallvec = { workspace = true, default-features = true } -sp-authority-discovery = { workspace = true } -sp-consensus-babe = { workspace = true } -sp-consensus-beefy = { workspace = true } -sp-consensus-grandpa = { workspace = true } binary-merkle-tree = { workspace = true } -sp-inherents = { workspace = true } -sp-offchain = { workspace = true } sp-api = { workspace = true } sp-application-crypto = { workspace = true } sp-arithmetic = { workspace = true } +sp-authority-discovery = { workspace = true } +sp-block-builder = { workspace = true } +sp-consensus-babe = { workspace = true } +sp-consensus-beefy = { workspace = true } +sp-consensus-grandpa = { workspace = true } +sp-core = { workspace = true } sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } sp-io = { workspace = true } +sp-keyring = { workspace = true } sp-mmr-primitives = { workspace = true } +sp-npos-elections = { workspace = true } +sp-offchain = { workspace = true } sp-runtime = { workspace = true } -sp-staking = { workspace = true } -sp-core = { workspace = true } sp-session = { workspace = true } +sp-staking = { workspace = true } sp-storage = { workspace = true } -sp-version = { workspace = true } sp-transaction-pool = { workspace = true } -sp-block-builder = { workspace = true } -sp-npos-elections = { workspace = true } -sp-keyring = { workspace = true } +sp-version = { workspace = true } frame-election-provider-support = { workspace = true } frame-executive = { workspace = true } @@ -52,7 +52,6 @@ frame-metadata-hash-extension = { workspace = true } frame-support = { features = ["experimental", "tuples-96"], workspace = true } frame-system = { workspace = true } frame-system-rpc-runtime-api = { workspace = true } -westend-runtime-constants = { workspace = true } pallet-asset-rate = { workspace = true } pallet-authority-discovery = { workspace = true } pallet-authorship = { workspace = true } @@ -62,9 +61,11 @@ pallet-balances = { workspace = true } pallet-beefy = { workspace = true } pallet-beefy-mmr = { workspace = true } pallet-collective = { workspace = true } +pallet-conviction-voting = { workspace = true } +pallet-delegated-staking = { workspace = true } pallet-democracy = { workspace = true } -pallet-elections-phragmen = { workspace = true } pallet-election-provider-multi-phase = { workspace = true } +pallet-elections-phragmen = { workspace = true } pallet-fast-unstake = { workspace = true } pallet-grandpa = { workspace = true } pallet-identity = { workspace = true } @@ -75,60 +76,59 @@ pallet-migrations = { workspace = true } pallet-mmr = { workspace = true } pallet-multisig = { workspace = true } pallet-nomination-pools = { workspace = true } -pallet-conviction-voting = { workspace = true } +pallet-nomination-pools-runtime-api = { workspace = true } pallet-offences = { workspace = true } pallet-parameters = { workspace = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } pallet-recovery = { workspace = true } pallet-referenda = { workspace = true } +pallet-root-testing = { workspace = true } pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-society = { workspace = true } pallet-staking = { workspace = true } pallet-staking-runtime-api = { workspace = true } -pallet-delegated-staking = { workspace = true } pallet-state-trie-migration = { workspace = true } pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } pallet-transaction-payment = { workspace = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true } -pallet-nomination-pools-runtime-api = { workspace = true } pallet-treasury = { workspace = true } pallet-utility = { workspace = true } pallet-vesting = { workspace = true } pallet-whitelist = { workspace = true } pallet-xcm = { workspace = true } pallet-xcm-benchmarks = { optional = true, workspace = true } -pallet-root-testing = { workspace = true } +westend-runtime-constants = { workspace = true } frame-benchmarking = { optional = true, workspace = true } -frame-try-runtime = { optional = true, workspace = true } frame-system-benchmarking = { optional = true, workspace = true } +frame-try-runtime = { optional = true, workspace = true } +hex-literal = { workspace = true, default-features = true } pallet-election-provider-support-benchmarking = { optional = true, workspace = true } pallet-nomination-pools-benchmarking = { optional = true, workspace = true } pallet-offences-benchmarking = { optional = true, workspace = true } pallet-session-benchmarking = { optional = true, workspace = true } -hex-literal = { workspace = true, default-features = true } -polkadot-runtime-common = { workspace = true } -polkadot-primitives = { workspace = true } polkadot-parachain-primitives = { workspace = true } +polkadot-primitives = { workspace = true } +polkadot-runtime-common = { workspace = true } polkadot-runtime-parachains = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } [dev-dependencies] approx = { workspace = true } -tiny-keccak = { features = ["keccak"], workspace = true } -sp-keyring = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } remote-externalities = { workspace = true, default-features = true } -tokio = { features = ["macros"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true } +tiny-keccak = { features = ["keccak"], workspace = true } +tokio = { features = ["macros"], workspace = true, default-features = true } [build-dependencies] substrate-wasm-builder = { workspace = true, default-features = true } diff --git a/polkadot/runtime/westend/constants/Cargo.toml b/polkadot/runtime/westend/constants/Cargo.toml index a50e2f9cc639..f3dbcc309ee1 100644 --- a/polkadot/runtime/westend/constants/Cargo.toml +++ b/polkadot/runtime/westend/constants/Cargo.toml @@ -20,9 +20,9 @@ smallvec = { workspace = true, default-features = true } frame-support = { workspace = true } polkadot-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } -sp-core = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } diff --git a/polkadot/statement-table/Cargo.toml b/polkadot/statement-table/Cargo.toml index d9519dafe12d..1155600f3d0c 100644 --- a/polkadot/statement-table/Cargo.toml +++ b/polkadot/statement-table/Cargo.toml @@ -13,6 +13,6 @@ workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -sp-core = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } gum = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } diff --git a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml index 206ca8cf19a9..1a6c23e0518e 100644 --- a/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml +++ b/polkadot/utils/remote-ext-tests/bags-list/Cargo.toml @@ -13,10 +13,10 @@ workspace = true westend-runtime = { workspace = true } westend-runtime-constants = { workspace = true, default-features = true } -pallet-bags-list-remote-tests = { workspace = true } -sp-tracing = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } +pallet-bags-list-remote-tests = { workspace = true } sp-core = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } clap = { features = ["derive"], workspace = true } log = { workspace = true, default-features = true } diff --git a/polkadot/xcm/Cargo.toml b/polkadot/xcm/Cargo.toml index 7ac12dc1e377..e90354e4e6ac 100644 --- a/polkadot/xcm/Cargo.toml +++ b/polkadot/xcm/Cargo.toml @@ -14,23 +14,23 @@ workspace = true [dependencies] array-bytes = { workspace = true, default-features = true } bounded-collections = { features = ["serde"], workspace = true } +codec = { features = ["derive", "max-encoded-len"], workspace = true } derivative = { features = ["use_core"], workspace = true } +environmental = { workspace = true } +frame-support = { workspace = true } +hex-literal = { workspace = true, default-features = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true } -codec = { features = ["derive", "max-encoded-len"], workspace = true } scale-info = { features = ["derive", "serde"], workspace = true } +schemars = { default-features = true, optional = true, workspace = true } +serde = { features = ["alloc", "derive", "rc"], workspace = true } sp-runtime = { workspace = true } sp-weights = { features = ["serde"], workspace = true } -serde = { features = ["alloc", "derive", "rc"], workspace = true } -schemars = { default-features = true, optional = true, workspace = true } xcm-procedural = { workspace = true, default-features = true } -environmental = { workspace = true } -hex-literal = { workspace = true, default-features = true } -frame-support = { workspace = true } [dev-dependencies] -sp-io = { workspace = true, default-features = true } hex = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/polkadot/xcm/docs/Cargo.toml b/polkadot/xcm/docs/Cargo.toml index 9d8f4c0a6430..6fa7ea9a23a9 100644 --- a/polkadot/xcm/docs/Cargo.toml +++ b/polkadot/xcm/docs/Cargo.toml @@ -10,30 +10,30 @@ publish = false [dependencies] # For XCM stuff +pallet-xcm = { workspace = true, default-features = true } xcm = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } xcm-builder = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } xcm-simulator = { workspace = true, default-features = true } -pallet-xcm = { workspace = true, default-features = true } # For building FRAME runtimes -frame = { features = ["experimental", "runtime"], workspace = true, default-features = true } codec = { workspace = true, default-features = true } -scale-info = { workspace = true } +frame = { features = ["experimental", "runtime"], workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } -polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } +scale-info = { workspace = true } +sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-std = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } # Some pallets -pallet-message-queue = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +pallet-message-queue = { workspace = true, default-features = true } # For building docs -simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", branch = "main" } docify = { workspace = true } +simple-mermaid = { git = "https://github.com/kianenigma/simple-mermaid.git", branch = "main" } [dev-dependencies] test-log = { workspace = true } diff --git a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml index d4131cc53ee6..5d5926ae01e0 100644 --- a/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml +++ b/polkadot/xcm/pallet-xcm-benchmarks/Cargo.toml @@ -16,20 +16,20 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } +frame-benchmarking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-runtime = { workspace = true } +log = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } -xcm-executor = { workspace = true } -frame-benchmarking = { workspace = true } +sp-runtime = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } -log = { workspace = true, default-features = true } +xcm-executor = { workspace = true } [dev-dependencies] -pallet-balances = { workspace = true, default-features = true } pallet-assets = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } xcm = { workspace = true, default-features = true } # temp diff --git a/polkadot/xcm/pallet-xcm/Cargo.toml b/polkadot/xcm/pallet-xcm/Cargo.toml index 81fcea05cac2..85beba03b157 100644 --- a/polkadot/xcm/pallet-xcm/Cargo.toml +++ b/polkadot/xcm/pallet-xcm/Cargo.toml @@ -25,8 +25,8 @@ sp-io = { workspace = true } sp-runtime = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } xcm-runtime-apis = { workspace = true } # marked optional, used in benchmarking @@ -35,8 +35,8 @@ pallet-balances = { optional = true, workspace = true } [dev-dependencies] pallet-assets = { workspace = true, default-features = true } -polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/polkadot/xcm/procedural/Cargo.toml b/polkadot/xcm/procedural/Cargo.toml index 88ed3c94ddf4..0843da86f038 100644 --- a/polkadot/xcm/procedural/Cargo.toml +++ b/polkadot/xcm/procedural/Cargo.toml @@ -16,10 +16,10 @@ workspace = true proc-macro = true [dependencies] +Inflector = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true } -Inflector = { workspace = true } [dev-dependencies] trybuild = { features = ["diff"], workspace = true } diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index e64ab1928132..f75c984c068e 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -12,35 +12,35 @@ repository.workspace = true workspace = true [dependencies] -impl-trait-for-tuples = { workspace = true } codec = { features = ["derive"], workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } +pallet-asset-conversion = { workspace = true } +pallet-transaction-payment = { workspace = true } scale-info = { features = ["derive"], workspace = true } -xcm = { workspace = true } -xcm-executor = { workspace = true } sp-arithmetic = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } -pallet-transaction-payment = { workspace = true } -pallet-asset-conversion = { workspace = true } -log = { workspace = true } +xcm = { workspace = true } +xcm-executor = { workspace = true } # Polkadot dependencies polkadot-parachain-primitives = { workspace = true } [dev-dependencies] -sp-core = { workspace = true, default-features = true } -primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } +assert_matches = { workspace = true } +pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -pallet-xcm = { workspace = true, default-features = true } pallet-salary = { workspace = true, default-features = true } -pallet-assets = { workspace = true, default-features = true } +pallet-xcm = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } -assert_matches = { workspace = true } polkadot-test-runtime = { workspace = true } +primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } +sp-core = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/polkadot/xcm/xcm-executor/Cargo.toml b/polkadot/xcm/xcm-executor/Cargo.toml index eb558c0fcc19..381dca54a5fb 100644 --- a/polkadot/xcm/xcm-executor/Cargo.toml +++ b/polkadot/xcm/xcm-executor/Cargo.toml @@ -12,19 +12,19 @@ repository.workspace = true workspace = true [dependencies] -impl-trait-for-tuples = { workspace = true } -environmental = { workspace = true } codec = { features = ["derive"], workspace = true } +environmental = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +impl-trait-for-tuples = { workspace = true } scale-info = { features = ["derive", "serde"], workspace = true } -xcm = { workspace = true } -sp-io = { workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } -frame-support = { workspace = true } tracing = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } +xcm = { workspace = true } [features] default = ["std"] diff --git a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml index a89dd74a44fa..6c2e56669bc3 100644 --- a/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml +++ b/polkadot/xcm/xcm-executor/integration-tests/Cargo.toml @@ -15,21 +15,21 @@ codec = { workspace = true, default-features = true } frame-support = { workspace = true } frame-system = { workspace = true, default-features = true } futures = { workspace = true } -pallet-transaction-payment = { workspace = true, default-features = true } pallet-sudo = { workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } pallet-xcm = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-test-client = { workspace = true } polkadot-test-runtime = { workspace = true } polkadot-test-service = { workspace = true } sp-consensus = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-runtime = { workspace = true } sp-state-machine = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } xcm = { workspace = true } xcm-executor = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/polkadot/xcm/xcm-runtime-apis/Cargo.toml b/polkadot/xcm/xcm-runtime-apis/Cargo.toml index 9ada69a1933b..96afb10e5397 100644 --- a/polkadot/xcm/xcm-runtime-apis/Cargo.toml +++ b/polkadot/xcm/xcm-runtime-apis/Cargo.toml @@ -21,17 +21,17 @@ xcm = { workspace = true } xcm-executor = { workspace = true } [dev-dependencies] +frame-executive = { workspace = true } frame-system = { workspace = true } -sp-io = { workspace = true } -xcm-builder = { workspace = true } hex-literal = { workspace = true } -pallet-xcm = { workspace = true } -pallet-balances = { workspace = true } -pallet-assets = { workspace = true } -xcm-executor = { workspace = true } -frame-executive = { workspace = true } log = { workspace = true } +pallet-assets = { workspace = true } +pallet-balances = { workspace = true } +pallet-xcm = { workspace = true } +sp-io = { workspace = true } sp-tracing = { workspace = true, default-features = true } +xcm-builder = { workspace = true } +xcm-executor = { workspace = true } [features] default = ["std"] diff --git a/polkadot/xcm/xcm-simulator/Cargo.toml b/polkadot/xcm/xcm-simulator/Cargo.toml index 47900e226d48..10c6f14bc8b9 100644 --- a/polkadot/xcm/xcm-simulator/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/Cargo.toml @@ -13,19 +13,19 @@ workspace = true [dependencies] codec = { workspace = true, default-features = true } -scale-info = { workspace = true } paste = { workspace = true, default-features = true } +scale-info = { workspace = true } frame-support = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } -xcm = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } -xcm-builder = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } polkadot-core-primitives = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } +xcm = { workspace = true, default-features = true } +xcm-builder = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } diff --git a/polkadot/xcm/xcm-simulator/example/Cargo.toml b/polkadot/xcm/xcm-simulator/example/Cargo.toml index 43f36fc8991a..ccf0ecc39c4c 100644 --- a/polkadot/xcm/xcm-simulator/example/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/example/Cargo.toml @@ -13,28 +13,28 @@ workspace = true [dependencies] codec = { workspace = true, default-features = true } -scale-info = { features = ["derive"], workspace = true, default-features = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } pallet-uniques = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -xcm = { workspace = true, default-features = true } -xcm-simulator = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } -xcm-builder = { workspace = true, default-features = true } pallet-xcm = { workspace = true, default-features = true } polkadot-core-primitives = { workspace = true, default-features = true } -polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } +xcm = { workspace = true, default-features = true } +xcm-builder = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } +xcm-simulator = { workspace = true, default-features = true } [features] default = [] diff --git a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml index a2e36db95ba6..62a047975c87 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml +++ b/polkadot/xcm/xcm-simulator/fuzzer/Cargo.toml @@ -11,30 +11,30 @@ publish = false workspace = true [dependencies] +arbitrary = { workspace = true } codec = { workspace = true, default-features = true } honggfuzz = { workspace = true } -arbitrary = { workspace = true } scale-info = { features = ["derive"], workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } frame-executive = { workspace = true, default-features = true } +frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } frame-try-runtime = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } -xcm = { workspace = true, default-features = true } -xcm-simulator = { workspace = true, default-features = true } -xcm-executor = { workspace = true, default-features = true } -xcm-builder = { workspace = true, default-features = true } pallet-xcm = { workspace = true, default-features = true } polkadot-core-primitives = { workspace = true, default-features = true } -polkadot-runtime-parachains = { workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-runtime-parachains = { workspace = true, default-features = true } +xcm = { workspace = true, default-features = true } +xcm-builder = { workspace = true, default-features = true } +xcm-executor = { workspace = true, default-features = true } +xcm-simulator = { workspace = true, default-features = true } [features] try-runtime = [ diff --git a/polkadot/zombienet-sdk-tests/Cargo.toml b/polkadot/zombienet-sdk-tests/Cargo.toml index 4eac7af49f8a..120857c9a42e 100644 --- a/polkadot/zombienet-sdk-tests/Cargo.toml +++ b/polkadot/zombienet-sdk-tests/Cargo.toml @@ -8,16 +8,16 @@ license.workspace = true publish = false [dependencies] +anyhow = { workspace = true } +codec = { workspace = true, features = ["derive"] } env_logger = { workspace = true } log = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } subxt = { workspace = true, features = ["substrate-compat"] } subxt-signer = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"] } -anyhow = { workspace = true } zombienet-sdk = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -codec = { workspace = true, features = ["derive"] } [features] zombie-metadata = [] diff --git a/substrate/bin/node/bench/Cargo.toml b/substrate/bin/node/bench/Cargo.toml index 447f947107c1..83f7b82cd2b5 100644 --- a/substrate/bin/node/bench/Cargo.toml +++ b/substrate/bin/node/bench/Cargo.toml @@ -15,33 +15,33 @@ workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-trait = { workspace = true } array-bytes = { workspace = true, default-features = true } +async-trait = { workspace = true } clap = { features = ["derive"], workspace = true } +derive_more = { features = ["display"], workspace = true } +fs_extra = { workspace = true } +futures = { features = ["thread-pool"], workspace = true } +hash-db = { workspace = true, default-features = true } +kitchensink-runtime = { workspace = true } +kvdb = { workspace = true } +kvdb-rocksdb = { workspace = true } log = { workspace = true, default-features = true } node-primitives = { workspace = true, default-features = true } node-testing = { workspace = true } -kitchensink-runtime = { workspace = true } +parity-db = { workspace = true } +rand = { features = ["small_rng"], workspace = true, default-features = true } +sc-basic-authorship = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-state-machine = { workspace = true, default-features = true } +sc-transaction-pool = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } serde = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -derive_more = { features = ["display"], workspace = true } -kvdb = { workspace = true } -kvdb-rocksdb = { workspace = true } -sp-trie = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } -sc-basic-authorship = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } sp-timestamp = { workspace = true } sp-tracing = { workspace = true, default-features = true } -hash-db = { workspace = true, default-features = true } +sp-trie = { workspace = true, default-features = true } tempfile = { workspace = true } -fs_extra = { workspace = true } -rand = { features = ["small_rng"], workspace = true, default-features = true } -parity-db = { workspace = true } -sc-transaction-pool = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -futures = { features = ["thread-pool"], workspace = true } diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index c179579c1885..9e063ee3cde0 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -40,11 +40,11 @@ crate-type = ["cdylib", "rlib"] array-bytes = { workspace = true, default-features = true } clap = { features = ["derive"], optional = true, workspace = true } codec = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -jsonrpsee = { features = ["server"], workspace = true } futures = { workspace = true } +jsonrpsee = { features = ["server"], workspace = true } log = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } subxt-signer = { workspace = true, features = ["unstable-eth"] } @@ -135,32 +135,32 @@ polkadot-sdk = { features = [ # Shared code between the staging node and kitchensink runtime: kitchensink-runtime = { workspace = true } -node-rpc = { workspace = true } -node-primitives = { workspace = true, default-features = true } node-inspect = { optional = true, workspace = true, default-features = true } +node-primitives = { workspace = true, default-features = true } +node-rpc = { workspace = true } [dev-dependencies] -futures = { workspace = true } -tempfile = { workspace = true } assert_cmd = { workspace = true } +criterion = { features = ["async_tokio"], workspace = true, default-features = true } +futures = { workspace = true } nix = { features = ["signal"], workspace = true } -regex = { workspace = true } platforms = { workspace = true } +pretty_assertions.workspace = true +regex = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } soketto = { workspace = true } -criterion = { features = ["async_tokio"], workspace = true, default-features = true } +sp-keyring = { workspace = true } +tempfile = { workspace = true } tokio = { features = ["macros", "parking_lot", "time"], workspace = true, default-features = true } tokio-util = { features = ["compat"], workspace = true } wait-timeout = { workspace = true } wat = { workspace = true } -serde_json = { workspace = true, default-features = true } -scale-info = { features = ["derive", "serde"], workspace = true, default-features = true } -sp-keyring = { workspace = true } -pretty_assertions.workspace = true # These testing-only dependencies are not exported by the Polkadot-SDK crate: node-testing = { workspace = true } -substrate-cli-test-utils = { workspace = true } sc-service-test = { workspace = true } +substrate-cli-test-utils = { workspace = true } [build-dependencies] clap = { optional = true, workspace = true } diff --git a/substrate/bin/node/inspect/Cargo.toml b/substrate/bin/node/inspect/Cargo.toml index 6c8a4e59f68d..0cf13bef71f1 100644 --- a/substrate/bin/node/inspect/Cargo.toml +++ b/substrate/bin/node/inspect/Cargo.toml @@ -17,7 +17,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] clap = { features = ["derive"], workspace = true } codec = { workspace = true, default-features = true } -thiserror = { workspace = true } sc-cli = { workspace = true } sc-client-api = { workspace = true, default-features = true } sc-service = { workspace = true } @@ -26,6 +25,7 @@ sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } +thiserror = { workspace = true } [features] runtime-benchmarks = [ diff --git a/substrate/bin/node/rpc/Cargo.toml b/substrate/bin/node/rpc/Cargo.toml index 02f5d9a4a702..c8b20287650b 100644 --- a/substrate/bin/node/rpc/Cargo.toml +++ b/substrate/bin/node/rpc/Cargo.toml @@ -17,16 +17,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { features = ["server"], workspace = true } +mmr-rpc = { workspace = true, default-features = true } node-primitives = { workspace = true, default-features = true } pallet-transaction-payment-rpc = { workspace = true, default-features = true } -mmr-rpc = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-babe-rpc = { workspace = true, default-features = true } sc-consensus-beefy = { workspace = true, default-features = true } sc-consensus-beefy-rpc = { workspace = true, default-features = true } -sp-consensus-beefy = { workspace = true, default-features = true } sc-consensus-grandpa = { workspace = true, default-features = true } sc-consensus-grandpa-rpc = { workspace = true, default-features = true } sc-mixnet = { workspace = true, default-features = true } @@ -34,13 +33,14 @@ sc-rpc = { workspace = true, default-features = true } sc-sync-state-rpc = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-consensus-babe = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } substrate-frame-rpc-system = { workspace = true, default-features = true } substrate-state-trie-migration-rpc = { workspace = true, default-features = true } diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 3ad6315561d0..6d377cc92cce 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -23,11 +23,11 @@ codec = { features = [ "derive", "max-encoded-len", ], workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } -static_assertions = { workspace = true, default-features = true } log = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } sp-debug-derive = { workspace = true, features = ["force-debug"] } +static_assertions = { workspace = true, default-features = true } # pallet-asset-conversion: turn on "num-traits" feature primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } diff --git a/substrate/bin/node/testing/Cargo.toml b/substrate/bin/node/testing/Cargo.toml index 1972c03a368b..13477a172fb8 100644 --- a/substrate/bin/node/testing/Cargo.toml +++ b/substrate/bin/node/testing/Cargo.toml @@ -17,20 +17,19 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true, default-features = true } +frame-metadata-hash-extension = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } fs_extra = { workspace = true } futures = { workspace = true } +kitchensink-runtime = { workspace = true } log = { workspace = true, default-features = true } -tempfile = { workspace = true } -frame-metadata-hash-extension = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } node-cli = { workspace = true } node-primitives = { workspace = true, default-features = true } -kitchensink-runtime = { workspace = true } pallet-asset-conversion = { workspace = true, default-features = true } -pallet-assets = { workspace = true, default-features = true } -pallet-revive = { workspace = true, default-features = true } pallet-asset-conversion-tx-payment = { workspace = true, default-features = true } pallet-asset-tx-payment = { workspace = true, default-features = true } +pallet-assets = { workspace = true, default-features = true } +pallet-revive = { workspace = true, default-features = true } pallet-skip-feeless-payment = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } @@ -50,3 +49,4 @@ sp-keyring = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true } substrate-test-client = { workspace = true } +tempfile = { workspace = true } diff --git a/substrate/bin/utils/chain-spec-builder/Cargo.toml b/substrate/bin/utils/chain-spec-builder/Cargo.toml index b71e935a918f..f3adc5682969 100644 --- a/substrate/bin/utils/chain-spec-builder/Cargo.toml +++ b/substrate/bin/utils/chain-spec-builder/Cargo.toml @@ -34,14 +34,14 @@ log = { workspace = true, default-features = true } sc-chain-spec = { features = [ "clap", ], workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } [dev-dependencies] -substrate-test-runtime = { workspace = true } cmd_lib = { workspace = true } docify = { workspace = true } +substrate-test-runtime = { workspace = true } [features] # `cargo build --feature=generate-readme` updates the `README.md` file. diff --git a/substrate/client/allocator/Cargo.toml b/substrate/client/allocator/Cargo.toml index a8b3bdc864c9..c0ce640566b0 100644 --- a/substrate/client/allocator/Cargo.toml +++ b/substrate/client/allocator/Cargo.toml @@ -18,6 +18,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = { workspace = true, default-features = true } -thiserror = { workspace = true } sp-core = { workspace = true, default-features = true } sp-wasm-interface = { workspace = true, default-features = true } +thiserror = { workspace = true } diff --git a/substrate/client/api/Cargo.toml b/substrate/client/api/Cargo.toml index 670c74684467..fe961b4690fc 100644 --- a/substrate/client/api/Cargo.toml +++ b/substrate/client/api/Cargo.toml @@ -41,6 +41,6 @@ sp-storage = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } [dev-dependencies] -thiserror = { workspace = true } sp-test-primitives = { workspace = true } substrate-test-runtime = { workspace = true } +thiserror = { workspace = true } diff --git a/substrate/client/authority-discovery/Cargo.toml b/substrate/client/authority-discovery/Cargo.toml index fc88d07ef936..ac1891451ec0 100644 --- a/substrate/client/authority-discovery/Cargo.toml +++ b/substrate/client/authority-discovery/Cargo.toml @@ -20,17 +20,17 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = { workspace = true } [dependencies] +async-trait = { workspace = true } codec = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } ip_network = { workspace = true } -multihash = { workspace = true } linked_hash_set = { workspace = true } log = { workspace = true, default-features = true } +multihash = { workspace = true } +prometheus-endpoint = { workspace = true, default-features = true } prost = { workspace = true } rand = { workspace = true, default-features = true } -thiserror = { workspace = true } -prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } @@ -40,7 +40,7 @@ sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -async-trait = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] quickcheck = { workspace = true } diff --git a/substrate/client/block-builder/Cargo.toml b/substrate/client/block-builder/Cargo.toml index 08392e18227f..c61a5a7ad3c1 100644 --- a/substrate/client/block-builder/Cargo.toml +++ b/substrate/client/block-builder/Cargo.toml @@ -23,9 +23,9 @@ sp-api = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-trie = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +sp-trie = { workspace = true, default-features = true } [dev-dependencies] sp-state-machine = { workspace = true, default-features = true } diff --git a/substrate/client/chain-spec/Cargo.toml b/substrate/client/chain-spec/Cargo.toml index 2e885240936f..f63ff6c64447 100644 --- a/substrate/client/chain-spec/Cargo.toml +++ b/substrate/client/chain-spec/Cargo.toml @@ -16,31 +16,31 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +array-bytes = { workspace = true, default-features = true } clap = { features = ["derive"], optional = true, workspace = true } codec = { features = ["derive"], workspace = true } +docify = { workspace = true } +log = { workspace = true } memmap2 = { workspace = true } -serde = { features = ["derive"], workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } sc-chain-spec-derive = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } -sp-io = { workspace = true } sc-network = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } sp-genesis-builder = { workspace = true, default-features = true } +sp-io = { workspace = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } -log = { workspace = true } sp-tracing = { workspace = true, default-features = true } -array-bytes = { workspace = true, default-features = true } -docify = { workspace = true } [dev-dependencies] -substrate-test-runtime = { workspace = true } -sp-keyring = { workspace = true, default-features = true } +regex = { workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } sp-consensus-babe = { features = ["serde"], workspace = true } -regex = { workspace = true } +sp-keyring = { workspace = true, default-features = true } +substrate-test-runtime = { workspace = true } diff --git a/substrate/client/cli/Cargo.toml b/substrate/client/cli/Cargo.toml index f0b9f8f9b905..d7b4489b6cc5 100644 --- a/substrate/client/cli/Cargo.toml +++ b/substrate/client/cli/Cargo.toml @@ -19,13 +19,13 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = { workspace = true, default-features = true } chrono = { workspace = true } clap = { features = ["derive", "string", "wrap_help"], workspace = true } +codec = { workspace = true, default-features = true } fdlimit = { workspace = true } futures = { workspace = true } itertools = { workspace = true } libp2p-identity = { features = ["ed25519", "peerid"], workspace = true } log = { workspace = true, default-features = true } names = { workspace = true } -codec = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } regex = { workspace = true } rpassword = { workspace = true } @@ -34,7 +34,6 @@ serde_json = { workspace = true, default-features = true } thiserror = { workspace = true } # personal fork here as workaround for: https://github.com/rust-bitcoin/rust-bip39/pull/64 bip39 = { package = "parity-bip39", version = "2.0.1", features = ["rand"] } -tokio = { features = ["parking_lot", "rt-multi-thread", "signal"], workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-client-db = { workspace = true } sc-keystore = { workspace = true, default-features = true } @@ -52,11 +51,12 @@ sp-keystore = { workspace = true, default-features = true } sp-panic-handler = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } +tokio = { features = ["parking_lot", "rt-multi-thread", "signal"], workspace = true, default-features = true } [dev-dependencies] -tempfile = { workspace = true } futures-timer = { workspace = true } sp-tracing = { workspace = true, default-features = true } +tempfile = { workspace = true } [features] default = ["rocksdb"] diff --git a/substrate/client/consensus/aura/Cargo.toml b/substrate/client/consensus/aura/Cargo.toml index 98e8ad676be3..6af673617118 100644 --- a/substrate/client/consensus/aura/Cargo.toml +++ b/substrate/client/consensus/aura/Cargo.toml @@ -20,7 +20,6 @@ async-trait = { workspace = true } codec = { workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } -thiserror = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } @@ -38,10 +37,10 @@ sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] parking_lot = { workspace = true, default-features = true } -tempfile = { workspace = true } sc-keystore = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-test = { workspace = true } @@ -49,4 +48,5 @@ sp-keyring = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tempfile = { workspace = true } tokio = { workspace = true, default-features = true } diff --git a/substrate/client/consensus/babe/Cargo.toml b/substrate/client/consensus/babe/Cargo.toml index af55e72a9b7e..305409b80c78 100644 --- a/substrate/client/consensus/babe/Cargo.toml +++ b/substrate/client/consensus/babe/Cargo.toml @@ -19,14 +19,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { workspace = true } codec = { features = ["derive"], workspace = true, default-features = true } +fork-tree = { workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } num-bigint = { workspace = true } num-rational = { workspace = true } num-traits = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -thiserror = { workspace = true } -fork-tree = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } @@ -46,11 +45,12 @@ sp-crypto-hashing = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] sc-block-builder = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } sc-network-test = { workspace = true } +sp-keyring = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } diff --git a/substrate/client/consensus/babe/rpc/Cargo.toml b/substrate/client/consensus/babe/rpc/Cargo.toml index ce5b1baec0b5..3e3834189938 100644 --- a/substrate/client/consensus/babe/rpc/Cargo.toml +++ b/substrate/client/consensus/babe/rpc/Cargo.toml @@ -16,13 +16,12 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } futures = { workspace = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } +jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-epochs = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } @@ -31,12 +30,13 @@ sp-consensus-babe = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] -serde_json = { workspace = true, default-features = true } -tokio = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-keystore = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/client/consensus/beefy/Cargo.toml b/substrate/client/consensus/beefy/Cargo.toml index 900a44b95e04..bfe7e2c3d5dc 100644 --- a/substrate/client/consensus/beefy/Cargo.toml +++ b/substrate/client/consensus/beefy/Cargo.toml @@ -20,8 +20,6 @@ fnv = { workspace = true } futures = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -thiserror = { workspace = true } -wasm-timer = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } @@ -40,18 +38,20 @@ sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } tokio = { workspace = true, default-features = true } +wasm-timer = { workspace = true } [dev-dependencies] -serde = { workspace = true, default-features = true } -tempfile = { workspace = true } sc-block-builder = { workspace = true, default-features = true } sc-network-test = { workspace = true } +serde = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-mmr-primitives = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tempfile = { workspace = true } [features] # This feature adds BLS crypto primitives. It should not be used in production since diff --git a/substrate/client/consensus/beefy/rpc/Cargo.toml b/substrate/client/consensus/beefy/rpc/Cargo.toml index e1956dacf396..f8f24250ad93 100644 --- a/substrate/client/consensus/beefy/rpc/Cargo.toml +++ b/substrate/client/consensus/beefy/rpc/Cargo.toml @@ -17,17 +17,17 @@ futures = { workspace = true } jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } sc-consensus-beefy = { workspace = true, default-features = true } -sp-consensus-beefy = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +sp-application-crypto = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-application-crypto = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] -serde_json = { workspace = true, default-features = true } sc-rpc = { features = ["test-helpers"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/substrate/client/consensus/common/Cargo.toml b/substrate/client/consensus/common/Cargo.toml index 77cd50ad784b..1b0f799f81bc 100644 --- a/substrate/client/consensus/common/Cargo.toml +++ b/substrate/client/consensus/common/Cargo.toml @@ -21,18 +21,18 @@ futures = { features = ["thread-pool"], workspace = true } log = { workspace = true, default-features = true } mockall = { workspace = true } parking_lot = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] sp-test-primitives = { workspace = true } diff --git a/substrate/client/consensus/grandpa/Cargo.toml b/substrate/client/consensus/grandpa/Cargo.toml index 65ba39d34c21..f361fac54af7 100644 --- a/substrate/client/consensus/grandpa/Cargo.toml +++ b/substrate/client/consensus/grandpa/Cargo.toml @@ -20,48 +20,48 @@ targets = ["x86_64-unknown-linux-gnu"] ahash = { workspace = true } array-bytes = { workspace = true, default-features = true } async-trait = { workspace = true } +codec = { features = ["derive"], workspace = true, default-features = true } dyn-clone = { workspace = true } finality-grandpa = { features = ["derive-codec"], workspace = true, default-features = true } +fork-tree = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -rand = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } -fork-tree = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } -sc-network-gossip = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } +sc-network-gossip = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-application-crypto = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-consensus-grandpa = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -sp-consensus-grandpa = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } finality-grandpa = { features = ["derive-codec", "test-helpers"], workspace = true, default-features = true } -serde = { workspace = true, default-features = true } -tokio = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-test = { workspace = true } +serde = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/client/consensus/grandpa/rpc/Cargo.toml b/substrate/client/consensus/grandpa/rpc/Cargo.toml index 86513ac5df15..1fb8bd9367c4 100644 --- a/substrate/client/consensus/grandpa/rpc/Cargo.toml +++ b/substrate/client/consensus/grandpa/rpc/Cargo.toml @@ -13,25 +13,25 @@ homepage.workspace = true workspace = true [dependencies] +codec = { features = ["derive"], workspace = true, default-features = true } finality-grandpa = { features = ["derive-codec"], workspace = true, default-features = true } futures = { workspace = true } jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } log = { workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } sc-client-api = { workspace = true, default-features = true } sc-consensus-grandpa = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] sc-block-builder = { workspace = true, default-features = true } sc-rpc = { features = ["test-helpers"], workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/substrate/client/consensus/manual-seal/Cargo.toml b/substrate/client/consensus/manual-seal/Cargo.toml index 49111434015a..4d232f7256cb 100644 --- a/substrate/client/consensus/manual-seal/Cargo.toml +++ b/substrate/client/consensus/manual-seal/Cargo.toml @@ -16,15 +16,13 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } assert_matches = { workspace = true } async-trait = { workspace = true } codec = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } +jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } log = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } @@ -33,6 +31,7 @@ sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-epochs = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } @@ -44,9 +43,10 @@ sp-inherents = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] -tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } sc-basic-authorship = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } substrate-test-runtime-transaction-pool = { workspace = true } +tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } diff --git a/substrate/client/consensus/pow/Cargo.toml b/substrate/client/consensus/pow/Cargo.toml index bc89deb0b50d..a051bf3f4779 100644 --- a/substrate/client/consensus/pow/Cargo.toml +++ b/substrate/client/consensus/pow/Cargo.toml @@ -22,7 +22,6 @@ futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -thiserror = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } @@ -34,3 +33,4 @@ sp-consensus-pow = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } diff --git a/substrate/client/db/Cargo.toml b/substrate/client/db/Cargo.toml index 5725155579fc..7e02558e007c 100644 --- a/substrate/client/db/Cargo.toml +++ b/substrate/client/db/Cargo.toml @@ -39,15 +39,15 @@ sp-state-machine = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } [dev-dependencies] +array-bytes = { workspace = true, default-features = true } criterion = { workspace = true, default-features = true } +kitchensink-runtime = { workspace = true } kvdb-rocksdb = { workspace = true } -rand = { workspace = true, default-features = true } -tempfile = { workspace = true } quickcheck = { workspace = true } -kitchensink-runtime = { workspace = true } +rand = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } -array-bytes = { workspace = true, default-features = true } +tempfile = { workspace = true } [features] default = [] diff --git a/substrate/client/executor/Cargo.toml b/substrate/client/executor/Cargo.toml index ca78afd47068..5cb4936e7534 100644 --- a/substrate/client/executor/Cargo.toml +++ b/substrate/client/executor/Cargo.toml @@ -38,21 +38,21 @@ sp-wasm-interface = { workspace = true, default-features = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } -wat = { workspace = true } +criterion = { workspace = true, default-features = true } +num_cpus = { workspace = true } +paste = { workspace = true, default-features = true } +regex = { workspace = true } sc-runtime-test = { workspace = true } -substrate-test-runtime = { workspace = true } +sc-tracing = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -sp-state-machine = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } -sc-tracing = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -tracing-subscriber = { workspace = true } -paste = { workspace = true, default-features = true } -regex = { workspace = true } -criterion = { workspace = true, default-features = true } -num_cpus = { workspace = true } +substrate-test-runtime = { workspace = true } tempfile = { workspace = true } +tracing-subscriber = { workspace = true } +wat = { workspace = true } [[bench]] name = "bench" diff --git a/substrate/client/executor/common/Cargo.toml b/substrate/client/executor/common/Cargo.toml index 58fb0b423f24..aaf13a8ae768 100644 --- a/substrate/client/executor/common/Cargo.toml +++ b/substrate/client/executor/common/Cargo.toml @@ -17,12 +17,12 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -thiserror = { workspace = true } -wasm-instrument = { workspace = true, default-features = true } +polkavm = { workspace = true } sc-allocator = { workspace = true, default-features = true } sp-maybe-compressed-blob = { workspace = true, default-features = true } sp-wasm-interface = { workspace = true, default-features = true } -polkavm = { workspace = true } +thiserror = { workspace = true } +wasm-instrument = { workspace = true, default-features = true } [features] default = [] diff --git a/substrate/client/executor/wasmtime/Cargo.toml b/substrate/client/executor/wasmtime/Cargo.toml index ef8e5da876aa..7ea94568e1b7 100644 --- a/substrate/client/executor/wasmtime/Cargo.toml +++ b/substrate/client/executor/wasmtime/Cargo.toml @@ -16,13 +16,18 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { workspace = true, default-features = true } cfg-if = { workspace = true } libc = { workspace = true } +log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } # When bumping wasmtime do not forget to also bump rustix # to exactly the same version as used by wasmtime! +anyhow = { workspace = true } +sc-allocator = { workspace = true, default-features = true } +sc-executor-common = { workspace = true, default-features = true } +sp-runtime-interface = { workspace = true, default-features = true } +sp-wasm-interface = { features = ["wasmtime"], workspace = true, default-features = true } wasmtime = { features = [ "cache", "cranelift", @@ -30,11 +35,6 @@ wasmtime = { features = [ "parallel-compilation", "pooling-allocator", ], workspace = true } -anyhow = { workspace = true } -sc-allocator = { workspace = true, default-features = true } -sc-executor-common = { workspace = true, default-features = true } -sp-runtime-interface = { workspace = true, default-features = true } -sp-wasm-interface = { features = ["wasmtime"], workspace = true, default-features = true } # Here we include the rustix crate in the exactly same semver-compatible version as used by # wasmtime and enable its 'use-libc' flag. @@ -45,10 +45,10 @@ sp-wasm-interface = { features = ["wasmtime"], workspace = true, default-feature rustix = { features = ["fs", "mm", "param", "std", "use-libc"], workspace = true } [dev-dependencies] -wat = { workspace = true } +cargo_metadata = { workspace = true } +codec = { workspace = true, default-features = true } +paste = { workspace = true, default-features = true } sc-runtime-test = { workspace = true } sp-io = { workspace = true, default-features = true } tempfile = { workspace = true } -paste = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } -cargo_metadata = { workspace = true } +wat = { workspace = true } diff --git a/substrate/client/informant/Cargo.toml b/substrate/client/informant/Cargo.toml index 87a4be320d68..209964e02ef3 100644 --- a/substrate/client/informant/Cargo.toml +++ b/substrate/client/informant/Cargo.toml @@ -21,8 +21,8 @@ futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } diff --git a/substrate/client/keystore/Cargo.toml b/substrate/client/keystore/Cargo.toml index d338bb1af61a..e46fafbc3729 100644 --- a/substrate/client/keystore/Cargo.toml +++ b/substrate/client/keystore/Cargo.toml @@ -20,10 +20,10 @@ targets = ["x86_64-unknown-linux-gnu"] array-bytes = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } sp-application-crypto = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/substrate/client/merkle-mountain-range/Cargo.toml b/substrate/client/merkle-mountain-range/Cargo.toml index 6639a10d33f1..7849eac5f516 100644 --- a/substrate/client/merkle-mountain-range/Cargo.toml +++ b/substrate/client/merkle-mountain-range/Cargo.toml @@ -17,14 +17,14 @@ workspace = true codec = { workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-offchain = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sp-consensus-beefy = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-consensus-beefy = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-mmr-primitives = { workspace = true, default-features = true } -sc-offchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } [dev-dependencies] diff --git a/substrate/client/network-gossip/Cargo.toml b/substrate/client/network-gossip/Cargo.toml index 94bc9a671f84..ea52913aea16 100644 --- a/substrate/client/network-gossip/Cargo.toml +++ b/substrate/client/network-gossip/Cargo.toml @@ -21,18 +21,18 @@ ahash = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } -schnellru = { workspace = true } -tracing = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } +schnellru = { workspace = true } sp-runtime = { workspace = true, default-features = true } +tracing = { workspace = true, default-features = true } [dev-dependencies] -tokio = { workspace = true, default-features = true } async-trait = { workspace = true } codec = { features = ["derive"], workspace = true, default-features = true } quickcheck = { workspace = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/client/network/Cargo.toml b/substrate/client/network/Cargo.toml index c8fd28e08109..19af70867658 100644 --- a/substrate/client/network/Cargo.toml +++ b/substrate/client/network/Cargo.toml @@ -34,54 +34,54 @@ futures-timer = { workspace = true } ip_network = { workspace = true } libp2p = { features = ["dns", "identify", "kad", "macros", "mdns", "noise", "ping", "request-response", "tcp", "tokio", "websocket", "yamux"], workspace = true } linked_hash_set = { workspace = true } +litep2p = { workspace = true } log = { workspace = true, default-features = true } mockall = { workspace = true } +once_cell = { workspace = true } parking_lot = { workspace = true, default-features = true } partial_sort = { workspace = true } pin-project = { workspace = true } -rand = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -smallvec = { workspace = true, default-features = true } -thiserror = { workspace = true } -tokio = { features = ["macros", "sync"], workspace = true, default-features = true } -tokio-stream = { workspace = true } -unsigned-varint = { features = ["asynchronous_codec", "futures"], workspace = true } -zeroize = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } prost = { workspace = true } +rand = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +schnellru = { workspace = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +smallvec = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -wasm-timer = { workspace = true } -litep2p = { workspace = true } -once_cell = { workspace = true } +thiserror = { workspace = true } +tokio = { features = ["macros", "sync"], workspace = true, default-features = true } +tokio-stream = { workspace = true } +unsigned-varint = { features = ["asynchronous_codec", "futures"], workspace = true } void = { workspace = true } -schnellru = { workspace = true } +wasm-timer = { workspace = true } +zeroize = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } mockall = { workspace = true } multistream-select = { workspace = true } rand = { workspace = true, default-features = true } -tempfile = { workspace = true } -tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } -tokio-util = { features = ["compat"], workspace = true } -tokio-test = { workspace = true } sc-block-builder = { workspace = true, default-features = true } sc-network-light = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sp-crypto-hashing = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true, default-features = true } sp-test-primitives = { workspace = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime = { workspace = true } substrate-test-runtime-client = { workspace = true } +tempfile = { workspace = true } +tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } +tokio-test = { workspace = true } +tokio-util = { features = ["compat"], workspace = true } criterion = { workspace = true, default-features = true, features = ["async_tokio"] } sc-consensus = { workspace = true, default-features = true } diff --git a/substrate/client/network/light/Cargo.toml b/substrate/client/network/light/Cargo.toml index 34ba4f061c44..fad7ae425858 100644 --- a/substrate/client/network/light/Cargo.toml +++ b/substrate/client/network/light/Cargo.toml @@ -19,18 +19,18 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = { workspace = true } [dependencies] -async-channel = { workspace = true } array-bytes = { workspace = true, default-features = true } +async-channel = { workspace = true } codec = { features = [ "derive", ], workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } prost = { workspace = true } -sp-blockchain = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -sc-network-types = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } +sc-network-types = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } thiserror = { workspace = true } diff --git a/substrate/client/network/statement/Cargo.toml b/substrate/client/network/statement/Cargo.toml index 43933f066edd..dd3a8bef8a2f 100644 --- a/substrate/client/network/statement/Cargo.toml +++ b/substrate/client/network/statement/Cargo.toml @@ -22,10 +22,10 @@ codec = { features = ["derive"], workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } diff --git a/substrate/client/network/sync/Cargo.toml b/substrate/client/network/sync/Cargo.toml index 378b7c12e9b7..fdc290a2d01e 100644 --- a/substrate/client/network/sync/Cargo.toml +++ b/substrate/client/network/sync/Cargo.toml @@ -23,30 +23,30 @@ array-bytes = { workspace = true, default-features = true } async-channel = { workspace = true } async-trait = { workspace = true } codec = { features = ["derive"], workspace = true, default-features = true } +fork-tree = { workspace = true, default-features = true } futures = { workspace = true } futures-timer = { workspace = true } log = { workspace = true, default-features = true } mockall = { workspace = true } -prost = { workspace = true } -schnellru = { workspace = true } -smallvec = { workspace = true, default-features = true } -thiserror = { workspace = true } -tokio-stream = { workspace = true } -tokio = { features = ["macros", "time"], workspace = true, default-features = true } -fork-tree = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +prost = { workspace = true } sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +schnellru = { workspace = true } +smallvec = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } +tokio = { features = ["macros", "time"], workspace = true, default-features = true } +tokio-stream = { workspace = true } [dev-dependencies] mockall = { workspace = true } diff --git a/substrate/client/network/test/Cargo.toml b/substrate/client/network/test/Cargo.toml index 6340d1dfb2f4..783d47f21fa7 100644 --- a/substrate/client/network/test/Cargo.toml +++ b/substrate/client/network/test/Cargo.toml @@ -16,7 +16,6 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -tokio = { workspace = true, default-features = true } async-trait = { workspace = true } futures = { workspace = true } futures-timer = { workspace = true } @@ -29,11 +28,11 @@ sc-client-api = { workspace = true, default-features = true } sc-consensus = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } -sc-network-types = { workspace = true, default-features = true } -sc-utils = { workspace = true, default-features = true } sc-network-light = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } +sc-network-types = { workspace = true, default-features = true } sc-service = { workspace = true } +sc-utils = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } @@ -41,3 +40,4 @@ sp-runtime = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime = { workspace = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/client/network/transactions/Cargo.toml b/substrate/client/network/transactions/Cargo.toml index 2ffd6f5f4660..ef9ea1c46197 100644 --- a/substrate/client/network/transactions/Cargo.toml +++ b/substrate/client/network/transactions/Cargo.toml @@ -26,5 +26,5 @@ sc-network-common = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } sc-network-types = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } diff --git a/substrate/client/offchain/Cargo.toml b/substrate/client/offchain/Cargo.toml index 71b40211e126..bfdb29cc4c35 100644 --- a/substrate/client/offchain/Cargo.toml +++ b/substrate/client/offchain/Cargo.toml @@ -26,13 +26,12 @@ http-body-util = { workspace = true } hyper = { features = ["http1", "http2"], workspace = true, default-features = true } hyper-rustls = { workspace = true } hyper-util = { features = ["client-legacy", "http1", "http2"], workspace = true } +log = { workspace = true, default-features = true } num_cpus = { workspace = true } once_cell = { workspace = true } parking_lot = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } rustls = { workspace = true } -threadpool = { workspace = true } -tracing = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } @@ -41,15 +40,15 @@ sc-transaction-pool-api = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-externalities = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } sp-offchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-externalities = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } +threadpool = { workspace = true } +tracing = { workspace = true, default-features = true } [dev-dependencies] async-trait = { workspace = true } -tokio = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-client-db = { default-features = true, workspace = true } sc-transaction-pool = { workspace = true, default-features = true } @@ -57,6 +56,7 @@ sc-transaction-pool-api = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } [features] default = [] diff --git a/substrate/client/rpc-api/Cargo.toml b/substrate/client/rpc-api/Cargo.toml index 3263285aa2b1..e7bb723d8839 100644 --- a/substrate/client/rpc-api/Cargo.toml +++ b/substrate/client/rpc-api/Cargo.toml @@ -17,15 +17,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true, default-features = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } +jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } sc-chain-spec = { workspace = true, default-features = true } sc-mixnet = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-rpc = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } +thiserror = { workspace = true } diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml index 70f68436767f..ebe7e7eca7b4 100644 --- a/substrate/client/rpc-spec-v2/Cargo.toml +++ b/substrate/client/rpc-spec-v2/Cargo.toml @@ -20,45 +20,45 @@ jsonrpsee = { workspace = true, features = ["client-core", "macros", "server-cor # Internal chain structures for "chain_spec". sc-chain-spec = { workspace = true, default-features = true } # Pool for submitting extrinsics required by "transaction" +array-bytes = { workspace = true, default-features = true } +codec = { workspace = true, default-features = true } +futures = { workspace = true } +futures-util = { workspace = true } +hex = { workspace = true, default-features = true } +itertools = { workspace = true } +log = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } +schnellru = { workspace = true } +serde = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-rpc = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-rpc = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-rpc = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } thiserror = { workspace = true } -serde = { workspace = true, default-features = true } -hex = { workspace = true, default-features = true } -futures = { workspace = true } -parking_lot = { workspace = true, default-features = true } -tokio-stream = { features = ["sync"], workspace = true } tokio = { features = ["sync"], workspace = true, default-features = true } -array-bytes = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } -futures-util = { workspace = true } -rand = { workspace = true, default-features = true } -schnellru = { workspace = true } -itertools = { workspace = true } +tokio-stream = { features = ["sync"], workspace = true } [dev-dependencies] +assert_matches = { workspace = true } async-trait = { workspace = true } jsonrpsee = { workspace = true, features = ["server", "ws-client"] } -serde_json = { workspace = true, default-features = true } -tokio = { features = ["macros"], workspace = true, default-features = true } -substrate-test-runtime-client = { workspace = true } -substrate-test-runtime = { workspace = true } -substrate-test-runtime-transaction-pool = { workspace = true } -sp-consensus = { workspace = true, default-features = true } -sp-externalities = { workspace = true, default-features = true } -sp-maybe-compressed-blob = { workspace = true, default-features = true } +pretty_assertions = { workspace = true } sc-block-builder = { workspace = true, default-features = true } -sc-service = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true, features = ["test-helpers"] } -assert_matches = { workspace = true } -pretty_assertions = { workspace = true } +sc-service = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-externalities = { workspace = true, default-features = true } +sp-maybe-compressed-blob = { workspace = true, default-features = true } +substrate-test-runtime = { workspace = true } +substrate-test-runtime-client = { workspace = true } +substrate-test-runtime-transaction-pool = { workspace = true } +tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml index 6fe28a3873e9..8be932f02ed4 100644 --- a/substrate/client/rpc/Cargo.toml +++ b/substrate/client/rpc/Cargo.toml @@ -21,7 +21,6 @@ futures = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } @@ -30,6 +29,7 @@ sc-rpc-api = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } @@ -38,22 +38,22 @@ sp-offchain = { workspace = true, default-features = true } sp-rpc = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-session = { workspace = true, default-features = true } -sp-version = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } tokio = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } +pretty_assertions = { workspace = true } sc-block-builder = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -tokio = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } -pretty_assertions = { workspace = true } +tokio = { workspace = true, default-features = true } [features] test-helpers = [] diff --git a/substrate/client/runtime-utilities/Cargo.toml b/substrate/client/runtime-utilities/Cargo.toml index 5e026a5eff1a..716b577d384a 100644 --- a/substrate/client/runtime-utilities/Cargo.toml +++ b/substrate/client/runtime-utilities/Cargo.toml @@ -22,8 +22,8 @@ sc-executor = { workspace = true, default-features = true } sc-executor-common = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -sp-wasm-interface = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } +sp-wasm-interface = { workspace = true, default-features = true } thiserror = { workspace = true } @@ -31,6 +31,6 @@ thiserror = { workspace = true } [dev-dependencies] cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } cumulus-test-runtime = { workspace = true, default-features = true } -sp-version = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } subxt = { workspace = true, features = ["native"] } diff --git a/substrate/client/service/Cargo.toml b/substrate/client/service/Cargo.toml index 3981395d9768..e46b252f30bf 100644 --- a/substrate/client/service/Cargo.toml +++ b/substrate/client/service/Cargo.toml @@ -26,64 +26,64 @@ runtime-benchmarks = [ ] [dependencies] -jsonrpsee = { features = ["server"], workspace = true } -thiserror = { workspace = true } +async-trait = { workspace = true } +codec = { workspace = true, default-features = true } +directories = { workspace = true } +exit-future = { workspace = true } futures = { workspace = true } -rand = { workspace = true, default-features = true } -parking_lot = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } futures-timer = { workspace = true } -exit-future = { workspace = true } +jsonrpsee = { features = ["server"], workspace = true } +log = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } pin-project = { workspace = true } -serde = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-trie = { workspace = true, default-features = true } -sp-externalities = { workspace = true, default-features = true } -sc-utils = { workspace = true, default-features = true } -sp-version = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-keystore = { workspace = true, default-features = true } -sp-session = { workspace = true, default-features = true } -sp-state-machine = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +rand = { workspace = true, default-features = true } +sc-chain-spec = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-client-db = { workspace = true } sc-consensus = { workspace = true, default-features = true } -sp-storage = { workspace = true, default-features = true } +sc-executor = { workspace = true, default-features = true } +sc-informant = { workspace = true, default-features = true } +sc-keystore = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } sc-network-common = { workspace = true, default-features = true } sc-network-light = { workspace = true, default-features = true } sc-network-sync = { workspace = true, default-features = true } -sc-network-types = { workspace = true, default-features = true } sc-network-transactions = { workspace = true, default-features = true } -sc-chain-spec = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sc-client-db = { workspace = true } -codec = { workspace = true, default-features = true } -sc-executor = { workspace = true, default-features = true } -sc-transaction-pool = { workspace = true, default-features = true } -sp-transaction-pool = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -sp-transaction-storage-proof = { workspace = true, default-features = true } -sc-rpc-server = { workspace = true, default-features = true } +sc-network-types = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } +sc-rpc-server = { workspace = true, default-features = true } sc-rpc-spec-v2 = { workspace = true, default-features = true } -sc-informant = { workspace = true, default-features = true } +sc-sysinfo = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } -sc-sysinfo = { workspace = true, default-features = true } +sc-transaction-pool = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } +sc-utils = { workspace = true, default-features = true } +schnellru = { workspace = true } +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-externalities = { workspace = true, default-features = true } +sp-keystore = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-session = { workspace = true, default-features = true } +sp-state-machine = { workspace = true, default-features = true } +sp-storage = { workspace = true, default-features = true } +sp-transaction-pool = { workspace = true, default-features = true } +sp-transaction-storage-proof = { workspace = true, default-features = true } +sp-trie = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } +static_init = { workspace = true } +tempfile = { workspace = true } +thiserror = { workspace = true } +tokio = { features = ["parking_lot", "rt-multi-thread", "time"], workspace = true, default-features = true } tracing = { workspace = true, default-features = true } tracing-futures = { workspace = true } -async-trait = { workspace = true } -tokio = { features = ["parking_lot", "rt-multi-thread", "time"], workspace = true, default-features = true } -tempfile = { workspace = true } -directories = { workspace = true } -static_init = { workspace = true } -schnellru = { workspace = true } [dev-dependencies] -substrate-test-runtime-client = { workspace = true } substrate-test-runtime = { workspace = true } +substrate-test-runtime-client = { workspace = true } diff --git a/substrate/client/service/test/Cargo.toml b/substrate/client/service/test/Cargo.toml index 632b98104f6b..45b2d8c5eea3 100644 --- a/substrate/client/service/test/Cargo.toml +++ b/substrate/client/service/test/Cargo.toml @@ -15,15 +15,13 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-channel = { workspace = true } array-bytes = { workspace = true, default-features = true } +async-channel = { workspace = true } +codec = { workspace = true, default-features = true } fdlimit = { workspace = true } futures = { workspace = true } log = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -tempfile = { workspace = true } -tokio = { features = ["time"], workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-client-db = { workspace = true } @@ -37,11 +35,13 @@ sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } sp-storage = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } substrate-test-runtime = { workspace = true } substrate-test-runtime-client = { workspace = true } +tempfile = { workspace = true } +tokio = { features = ["time"], workspace = true, default-features = true } diff --git a/substrate/client/statement-store/Cargo.toml b/substrate/client/statement-store/Cargo.toml index e5087eae6eca..c0219b294ced 100644 --- a/substrate/client/statement-store/Cargo.toml +++ b/substrate/client/statement-store/Cargo.toml @@ -17,18 +17,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = { workspace = true, default-features = true } -parking_lot = { workspace = true, default-features = true } parity-db = { workspace = true } -tokio = { features = ["time"], workspace = true, default-features = true } -sp-statement-store = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-keystore = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-keystore = { workspace = true, default-features = true } +sp-statement-store = { workspace = true, default-features = true } +tokio = { features = ["time"], workspace = true, default-features = true } [dev-dependencies] -tempfile = { workspace = true } sp-tracing = { workspace = true } +tempfile = { workspace = true } diff --git a/substrate/client/storage-monitor/Cargo.toml b/substrate/client/storage-monitor/Cargo.toml index c017184ced66..3d8cb72b1a92 100644 --- a/substrate/client/storage-monitor/Cargo.toml +++ b/substrate/client/storage-monitor/Cargo.toml @@ -13,8 +13,8 @@ workspace = true [dependencies] clap = { features = ["derive", "string"], workspace = true } -log = { workspace = true, default-features = true } fs4 = { workspace = true } +log = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -tokio = { features = ["time"], workspace = true, default-features = true } thiserror = { workspace = true } +tokio = { features = ["time"], workspace = true, default-features = true } diff --git a/substrate/client/sync-state-rpc/Cargo.toml b/substrate/client/sync-state-rpc/Cargo.toml index cbab8f4d7b0d..91c30f5aa2cc 100644 --- a/substrate/client/sync-state-rpc/Cargo.toml +++ b/substrate/client/sync-state-rpc/Cargo.toml @@ -17,13 +17,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true, default-features = true } jsonrpsee = { features = ["client-core", "macros", "server-core"], workspace = true } -serde = { features = ["derive"], workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } sc-chain-spec = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-consensus-babe = { workspace = true, default-features = true } sc-consensus-epochs = { workspace = true, default-features = true } sc-consensus-grandpa = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +thiserror = { workspace = true } diff --git a/substrate/client/sysinfo/Cargo.toml b/substrate/client/sysinfo/Cargo.toml index 190e6e279b90..c7eed77eda7f 100644 --- a/substrate/client/sysinfo/Cargo.toml +++ b/substrate/client/sysinfo/Cargo.toml @@ -17,16 +17,16 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +derive_more = { workspace = true, default-features = true } futures = { workspace = true } libc = { workspace = true } log = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } rand_pcg = { workspace = true } -derive_more = { workspace = true, default-features = true } regex = { workspace = true } +sc-telemetry = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -sc-telemetry = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } diff --git a/substrate/client/telemetry/Cargo.toml b/substrate/client/telemetry/Cargo.toml index db325a94ab21..4a41a6b6deca 100644 --- a/substrate/client/telemetry/Cargo.toml +++ b/substrate/client/telemetry/Cargo.toml @@ -23,9 +23,9 @@ libp2p = { features = ["dns", "tcp", "tokio", "websocket"], workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } pin-project = { workspace = true } -sc-utils = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sc-utils = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } thiserror = { workspace = true } diff --git a/substrate/client/tracing/Cargo.toml b/substrate/client/tracing/Cargo.toml index b8f5e40caf83..949f6f6018ad 100644 --- a/substrate/client/tracing/Cargo.toml +++ b/substrate/client/tracing/Cargo.toml @@ -16,30 +16,30 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -console = { workspace = true } -is-terminal = { workspace = true } chrono = { workspace = true } codec = { workspace = true, default-features = true } +console = { workspace = true } +is-terminal = { workspace = true } libc = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } rustc-hash = { workspace = true } -serde = { workspace = true, default-features = true } -thiserror = { workspace = true } -tracing = { workspace = true, default-features = true } -tracing-log = { workspace = true } -tracing-subscriber = { workspace = true, features = [ - "env-filter", - "parking_lot", -] } sc-client-api = { workspace = true, default-features = true } sc-tracing-proc-macro = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-rpc = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } +thiserror = { workspace = true } +tracing = { workspace = true, default-features = true } +tracing-log = { workspace = true } +tracing-subscriber = { workspace = true, features = [ + "env-filter", + "parking_lot", +] } [dev-dependencies] criterion = { workspace = true, default-features = true } diff --git a/substrate/client/transaction-pool/Cargo.toml b/substrate/client/transaction-pool/Cargo.toml index d346add93a64..72586b984920 100644 --- a/substrate/client/transaction-pool/Cargo.toml +++ b/substrate/client/transaction-pool/Cargo.toml @@ -25,12 +25,11 @@ itertools = { workspace = true } linked-hash-map = { workspace = true } log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } prometheus-endpoint = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } @@ -38,8 +37,9 @@ sp-crypto-hashing = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } sp-transaction-pool = { workspace = true, default-features = true } -tokio-stream = { workspace = true } +thiserror = { workspace = true } tokio = { workspace = true, default-features = true, features = ["macros", "time"] } +tokio-stream = { workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } diff --git a/substrate/client/transaction-pool/api/Cargo.toml b/substrate/client/transaction-pool/api/Cargo.toml index c55ee70b2cf5..6671492a4e92 100644 --- a/substrate/client/transaction-pool/api/Cargo.toml +++ b/substrate/client/transaction-pool/api/Cargo.toml @@ -17,10 +17,10 @@ codec = { workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } -thiserror = { workspace = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true } sp-runtime = { workspace = true } +thiserror = { workspace = true } [dev-dependencies] serde_json = { workspace = true, default-features = true } diff --git a/substrate/frame/Cargo.toml b/substrate/frame/Cargo.toml index 2d0daf82997d..8fc0d8468430 100644 --- a/substrate/frame/Cargo.toml +++ b/substrate/frame/Cargo.toml @@ -26,28 +26,28 @@ scale-info = { features = [ ], workspace = true } # primitive deps, used for developing FRAME pallets. -sp-runtime = { workspace = true } -sp-io = { workspace = true } -sp-core = { workspace = true } sp-arithmetic = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } # frame deps, for developing FRAME pallets. frame-support = { workspace = true } frame-system = { workspace = true } # primitive types used for developing FRAME runtimes. -sp-version = { optional = true, workspace = true } sp-api = { optional = true, workspace = true } sp-block-builder = { optional = true, workspace = true } -sp-transaction-pool = { optional = true, workspace = true } -sp-offchain = { optional = true, workspace = true } -sp-session = { optional = true, workspace = true } sp-consensus-aura = { optional = true, workspace = true } sp-consensus-grandpa = { optional = true, workspace = true } sp-genesis-builder = { optional = true, workspace = true } sp-inherents = { optional = true, workspace = true } -sp-storage = { optional = true, workspace = true } sp-keyring = { optional = true, workspace = true } +sp-offchain = { optional = true, workspace = true } +sp-session = { optional = true, workspace = true } +sp-storage = { optional = true, workspace = true } +sp-transaction-pool = { optional = true, workspace = true } +sp-version = { optional = true, workspace = true } frame-executive = { optional = true, workspace = true } frame-system-rpc-runtime-api = { optional = true, workspace = true } diff --git a/substrate/frame/alliance/Cargo.toml b/substrate/frame/alliance/Cargo.toml index 451b86b35dde..9d21b9e964c9 100644 --- a/substrate/frame/alliance/Cargo.toml +++ b/substrate/frame/alliance/Cargo.toml @@ -31,14 +31,14 @@ frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -pallet-identity = { workspace = true } pallet-collective = { optional = true, workspace = true } +pallet-identity = { workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } -sp-crypto-hashing = { workspace = true } pallet-balances = { workspace = true, default-features = true } pallet-collective = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/asset-conversion/Cargo.toml b/substrate/frame/asset-conversion/Cargo.toml index 10a118e95639..8987e44ee000 100644 --- a/substrate/frame/asset-conversion/Cargo.toml +++ b/substrate/frame/asset-conversion/Cargo.toml @@ -17,20 +17,20 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } +log = { workspace = true } scale-info = { features = ["derive"], workspace = true } sp-api = { workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-arithmetic = { workspace = true } [dev-dependencies] -pallet-balances = { workspace = true, default-features = true } pallet-assets = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } [features] diff --git a/substrate/frame/asset-conversion/ops/Cargo.toml b/substrate/frame/asset-conversion/ops/Cargo.toml index 66333f973d7f..ebd31bd296de 100644 --- a/substrate/frame/asset-conversion/ops/Cargo.toml +++ b/substrate/frame/asset-conversion/ops/Cargo.toml @@ -16,20 +16,20 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } +log = { workspace = true } pallet-asset-conversion = { workspace = true } scale-info = { features = ["derive"], workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-arithmetic = { workspace = true } [dev-dependencies] -pallet-balances = { workspace = true, default-features = true } pallet-assets = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } [features] diff --git a/substrate/frame/asset-rate/Cargo.toml b/substrate/frame/asset-rate/Cargo.toml index 514b6fa40c2b..01a5ca21b199 100644 --- a/substrate/frame/asset-rate/Cargo.toml +++ b/substrate/frame/asset-rate/Cargo.toml @@ -18,17 +18,17 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-runtime = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { optional = true, workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } sp-core = { workspace = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/assets-freezer/Cargo.toml b/substrate/frame/assets-freezer/Cargo.toml index 68bfdd7cfb62..3fffa4d0627f 100644 --- a/substrate/frame/assets-freezer/Cargo.toml +++ b/substrate/frame/assets-freezer/Cargo.toml @@ -16,18 +16,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-assets = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] -sp-io = { workspace = true } -sp-core = { workspace = true } pallet-balances = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/assets/Cargo.toml b/substrate/frame/assets/Cargo.toml index e20b576d0836..a062a68d4220 100644 --- a/substrate/frame/assets/Cargo.toml +++ b/substrate/frame/assets/Cargo.toml @@ -25,13 +25,13 @@ sp-runtime = { workspace = true } # Needed for type-safe access to storage DB. frame-support = { workspace = true } # `system` module provides us with all sorts of useful stuff and macros depend on it being around. -frame-system = { workspace = true } frame-benchmarking = { optional = true, workspace = true } +frame-system = { workspace = true } sp-core = { workspace = true } [dev-dependencies] -sp-io = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/atomic-swap/Cargo.toml b/substrate/frame/atomic-swap/Cargo.toml index 1f97f60149ba..785bfee71b68 100644 --- a/substrate/frame/atomic-swap/Cargo.toml +++ b/substrate/frame/atomic-swap/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame = { workspace = true, features = ["experimental", "runtime"] } +scale-info = { features = ["derive"], workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/aura/Cargo.toml b/substrate/frame/aura/Cargo.toml index 94b057d665d4..94a47e4d96cd 100644 --- a/substrate/frame/aura/Cargo.toml +++ b/substrate/frame/aura/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-timestamp = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-application-crypto = { workspace = true } sp-consensus-aura = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/authority-discovery/Cargo.toml b/substrate/frame/authority-discovery/Cargo.toml index 01f574a262ad..506c292c837b 100644 --- a/substrate/frame/authority-discovery/Cargo.toml +++ b/substrate/frame/authority-discovery/Cargo.toml @@ -19,12 +19,12 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-session = { features = [ "historical", ], workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-application-crypto = { workspace = true } sp-authority-discovery = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/authorship/Cargo.toml b/substrate/frame/authorship/Cargo.toml index 74a4a93147a8..f8b587d44909 100644 --- a/substrate/frame/authorship/Cargo.toml +++ b/substrate/frame/authorship/Cargo.toml @@ -19,10 +19,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -impl-trait-for-tuples = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +impl-trait-for-tuples = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] diff --git a/substrate/frame/babe/Cargo.toml b/substrate/frame/babe/Cargo.toml index f0a7f4648c0a..8673e08472eb 100644 --- a/substrate/frame/babe/Cargo.toml +++ b/substrate/frame/babe/Cargo.toml @@ -17,14 +17,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-authorship = { workspace = true } pallet-session = { workspace = true } pallet-timestamp = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } sp-consensus-babe = { features = ["serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } diff --git a/substrate/frame/bags-list/Cargo.toml b/substrate/frame/bags-list/Cargo.toml index 647f5d26686a..6b1c4809f773 100644 --- a/substrate/frame/bags-list/Cargo.toml +++ b/substrate/frame/bags-list/Cargo.toml @@ -27,14 +27,14 @@ scale-info = { features = [ sp-runtime = { workspace = true } # FRAME +frame-election-provider-support = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-election-provider-support = { workspace = true } # third party -log = { workspace = true } -docify = { workspace = true } aquamarine = { workspace = true } +docify = { workspace = true } +log = { workspace = true } # Optional imports for benchmarking frame-benchmarking = { optional = true, workspace = true } @@ -44,12 +44,12 @@ sp-io = { optional = true, workspace = true } sp-tracing = { optional = true, workspace = true } [dev-dependencies] +frame-benchmarking = { workspace = true, default-features = true } +frame-election-provider-support = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -frame-election-provider-support = { workspace = true, default-features = true } -frame-benchmarking = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/bags-list/fuzzer/Cargo.toml b/substrate/frame/bags-list/fuzzer/Cargo.toml index b52fc8848237..db46bc6fe446 100644 --- a/substrate/frame/bags-list/fuzzer/Cargo.toml +++ b/substrate/frame/bags-list/fuzzer/Cargo.toml @@ -13,10 +13,10 @@ publish = false workspace = true [dependencies] -honggfuzz = { workspace = true } -rand = { features = ["small_rng", "std"], workspace = true, default-features = true } frame-election-provider-support = { features = ["fuzz"], workspace = true, default-features = true } +honggfuzz = { workspace = true } pallet-bags-list = { features = ["fuzz"], workspace = true, default-features = true } +rand = { features = ["small_rng", "std"], workspace = true, default-features = true } [[bin]] name = "bags-list" diff --git a/substrate/frame/bags-list/remote-tests/Cargo.toml b/substrate/frame/bags-list/remote-tests/Cargo.toml index 12d61b61c06d..99b203e73fb0 100644 --- a/substrate/frame/bags-list/remote-tests/Cargo.toml +++ b/substrate/frame/bags-list/remote-tests/Cargo.toml @@ -17,18 +17,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # frame -pallet-staking = { workspace = true, default-features = true } -pallet-bags-list = { features = ["fuzz"], workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } +pallet-bags-list = { features = ["fuzz"], workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } # core -sp-storage = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-std = { workspace = true, default-features = true } +sp-storage = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } # utils remote-externalities = { workspace = true, default-features = true } diff --git a/substrate/frame/balances/Cargo.toml b/substrate/frame/balances/Cargo.toml index f0117555c37e..03bc7fcb3fcc 100644 --- a/substrate/frame/balances/Cargo.toml +++ b/substrate/frame/balances/Cargo.toml @@ -17,20 +17,20 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } +docify = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } -docify = { workspace = true } [dev-dependencies] -pallet-transaction-payment = { workspace = true, default-features = true } frame-support = { features = ["experimental"], workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } +paste = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -paste = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/beefy-mmr/Cargo.toml b/substrate/frame/beefy-mmr/Cargo.toml index d67ac20ee922..54343bb9ce51 100644 --- a/substrate/frame/beefy-mmr/Cargo.toml +++ b/substrate/frame/beefy-mmr/Cargo.toml @@ -13,22 +13,22 @@ workspace = true [dependencies] array-bytes = { optional = true, workspace = true, default-features = true } -codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } binary-merkle-tree = { workspace = true } +codec = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-beefy = { workspace = true } pallet-mmr = { workspace = true } pallet-session = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } +sp-api = { workspace = true } sp-consensus-beefy = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-api = { workspace = true } sp-state-machine = { workspace = true } [dev-dependencies] diff --git a/substrate/frame/beefy/Cargo.toml b/substrate/frame/beefy/Cargo.toml index 05af974e89a7..b8e952dfbd66 100644 --- a/substrate/frame/beefy/Cargo.toml +++ b/substrate/frame/beefy/Cargo.toml @@ -13,13 +13,13 @@ workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-authorship = { workspace = true } pallet-session = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } sp-consensus-beefy = { features = ["serde"], workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-session = { workspace = true } diff --git a/substrate/frame/benchmarking/Cargo.toml b/substrate/frame/benchmarking/Cargo.toml index 0c74d94b33b8..fabeb9a03195 100644 --- a/substrate/frame/benchmarking/Cargo.toml +++ b/substrate/frame/benchmarking/Cargo.toml @@ -17,14 +17,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } +frame-support = { workspace = true } +frame-support-procedural = { workspace = true } +frame-system = { workspace = true } linregress = { optional = true, workspace = true } log = { workspace = true } paste = { workspace = true, default-features = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, workspace = true, default-features = true } -frame-support = { workspace = true } -frame-support-procedural = { workspace = true } -frame-system = { workspace = true } sp-api = { workspace = true } sp-application-crypto = { workspace = true } sp-core = { workspace = true } @@ -37,10 +37,10 @@ static_assertions = { workspace = true, default-features = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } rusty-fork = { workspace = true } -sp-keystore = { workspace = true, default-features = true } sc-client-db = { workspace = true } -sp-state-machine = { workspace = true } sp-externalities = { workspace = true } +sp-keystore = { workspace = true, default-features = true } +sp-state-machine = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/benchmarking/pov/Cargo.toml b/substrate/frame/benchmarking/pov/Cargo.toml index ce89dceed3c3..47c6d6e5e4bc 100644 --- a/substrate/frame/benchmarking/pov/Cargo.toml +++ b/substrate/frame/benchmarking/pov/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/bounties/Cargo.toml b/substrate/frame/bounties/Cargo.toml index a272153fed07..926af60d1acb 100644 --- a/substrate/frame/bounties/Cargo.toml +++ b/substrate/frame/bounties/Cargo.toml @@ -19,12 +19,12 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-treasury = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/broker/Cargo.toml b/substrate/frame/broker/Cargo.toml index aead49013ef0..a4cfe49d3b35 100644 --- a/substrate/frame/broker/Cargo.toml +++ b/substrate/frame/broker/Cargo.toml @@ -15,22 +15,22 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { workspace = true } +bitvec = { workspace = true } codec = { features = ["derive"], workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -bitvec = { workspace = true } sp-api = { workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } [dev-dependencies] +pretty_assertions = { workspace = true } sp-io = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -pretty_assertions = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/child-bounties/Cargo.toml b/substrate/frame/child-bounties/Cargo.toml index a250886b5e3d..b7d9d245892a 100644 --- a/substrate/frame/child-bounties/Cargo.toml +++ b/substrate/frame/child-bounties/Cargo.toml @@ -19,13 +19,13 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-bounties = { workspace = true } pallet-treasury = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/collective/Cargo.toml b/substrate/frame/collective/Cargo.toml index 59a9d23f7b19..8e53000352ae 100644 --- a/substrate/frame/collective/Cargo.toml +++ b/substrate/frame/collective/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } docify = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { features = ["experimental"], workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/contracts/Cargo.toml b/substrate/frame/contracts/Cargo.toml index 96351752918a..e39128639e3e 100644 --- a/substrate/frame/contracts/Cargo.toml +++ b/substrate/frame/contracts/Cargo.toml @@ -18,25 +18,25 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -paste = { workspace = true } bitflags = { workspace = true } codec = { features = [ "derive", "max-encoded-len", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } +impl-trait-for-tuples = { workspace = true } log = { workspace = true } +paste = { workspace = true } +scale-info = { features = ["derive"], workspace = true } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } smallvec = { features = [ "const_generics", ], workspace = true } wasmi = { workspace = true } -impl-trait-for-tuples = { workspace = true } # Only used in benchmarking to generate contract code -wasm-instrument = { optional = true, workspace = true } rand = { optional = true, workspace = true } rand_pcg = { optional = true, workspace = true } +wasm-instrument = { optional = true, workspace = true } # Substrate Dependencies environmental = { workspace = true } @@ -44,8 +44,8 @@ frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-balances = { optional = true, workspace = true } -pallet-contracts-uapi = { workspace = true, default-features = true } pallet-contracts-proc-macro = { workspace = true, default-features = true } +pallet-contracts-uapi = { workspace = true, default-features = true } sp-api = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } @@ -58,21 +58,21 @@ xcm-builder = { workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } +pallet-contracts-fixtures = { workspace = true } pretty_assertions = { workspace = true } wat = { workspace = true } -pallet-contracts-fixtures = { workspace = true } # Polkadot Dependencies xcm-builder = { workspace = true, default-features = true } # Substrate Dependencies +pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } -pallet-message-queue = { workspace = true, default-features = true } pallet-insecure-randomness-collective-flip = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } -pallet-assets = { workspace = true, default-features = true } +pallet-message-queue = { workspace = true, default-features = true } pallet-proxy = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } diff --git a/substrate/frame/contracts/fixtures/Cargo.toml b/substrate/frame/contracts/fixtures/Cargo.toml index 4c01c1f061b7..cf31f9eccc9c 100644 --- a/substrate/frame/contracts/fixtures/Cargo.toml +++ b/substrate/frame/contracts/fixtures/Cargo.toml @@ -11,13 +11,13 @@ description = "Fixtures for testing contracts pallet." workspace = true [dependencies] +anyhow = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -anyhow = { workspace = true, default-features = true } [build-dependencies] +anyhow = { workspace = true, default-features = true } parity-wasm = { workspace = true } tempfile = { workspace = true } toml = { workspace = true } twox-hash = { workspace = true, default-features = true } -anyhow = { workspace = true, default-features = true } diff --git a/substrate/frame/contracts/fixtures/build/Cargo.toml b/substrate/frame/contracts/fixtures/build/Cargo.toml index ba487a2bb5ca..18e8c2767d5f 100644 --- a/substrate/frame/contracts/fixtures/build/Cargo.toml +++ b/substrate/frame/contracts/fixtures/build/Cargo.toml @@ -8,9 +8,9 @@ edition = "2021" # All paths or versions are injected dynamically by the build script. [dependencies] -uapi = { package = 'pallet-contracts-uapi', path = "", default-features = false } common = { package = 'pallet-contracts-fixtures-common', path = "" } polkavm-derive = { version = "" } +uapi = { package = 'pallet-contracts-uapi', path = "", default-features = false } [profile.release] opt-level = 3 diff --git a/substrate/frame/contracts/mock-network/Cargo.toml b/substrate/frame/contracts/mock-network/Cargo.toml index 66137bc8a0c6..a7423b33abc1 100644 --- a/substrate/frame/contracts/mock-network/Cargo.toml +++ b/substrate/frame/contracts/mock-network/Cargo.toml @@ -19,8 +19,8 @@ frame-system = { workspace = true } pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-contracts = { workspace = true, default-features = true } -pallet-contracts-uapi = { workspace = true } pallet-contracts-proc-macro = { workspace = true, default-features = true } +pallet-contracts-uapi = { workspace = true } pallet-insecure-randomness-collective-flip = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } pallet-proxy = { workspace = true, default-features = true } @@ -44,8 +44,8 @@ xcm-simulator = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } -pretty_assertions = { workspace = true } pallet-contracts-fixtures = { workspace = true } +pretty_assertions = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/contracts/uapi/Cargo.toml b/substrate/frame/contracts/uapi/Cargo.toml index 45f8c2cb84af..8297c35b31db 100644 --- a/substrate/frame/contracts/uapi/Cargo.toml +++ b/substrate/frame/contracts/uapi/Cargo.toml @@ -12,13 +12,13 @@ description = "Exposes all the host functions that a contract can import." workspace = true [dependencies] -paste = { workspace = true } bitflags = { workspace = true } -scale-info = { features = ["derive"], optional = true, workspace = true } codec = { features = [ "derive", "max-encoded-len", ], optional = true, workspace = true } +paste = { workspace = true } +scale-info = { features = ["derive"], optional = true, workspace = true } [package.metadata.docs.rs] targets = ["wasm32-unknown-unknown"] diff --git a/substrate/frame/conviction-voting/Cargo.toml b/substrate/frame/conviction-voting/Cargo.toml index fdb4310610d9..2d23f493ea01 100644 --- a/substrate/frame/conviction-voting/Cargo.toml +++ b/substrate/frame/conviction-voting/Cargo.toml @@ -21,11 +21,11 @@ codec = { features = [ "derive", "max-encoded-len", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/core-fellowship/Cargo.toml b/substrate/frame/core-fellowship/Cargo.toml index 3d73ec58d613..c0017f477251 100644 --- a/substrate/frame/core-fellowship/Cargo.toml +++ b/substrate/frame/core-fellowship/Cargo.toml @@ -17,16 +17,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +pallet-ranked-collective = { optional = true, workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -pallet-ranked-collective = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/frame/delegated-staking/Cargo.toml b/substrate/frame/delegated-staking/Cargo.toml index 8d5ccd342b6b..576276dced52 100644 --- a/substrate/frame/delegated-staking/Cargo.toml +++ b/substrate/frame/delegated-staking/Cargo.toml @@ -15,23 +15,23 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } scale-info = { features = ["derive"], workspace = true } +sp-io = { workspace = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } -sp-io = { workspace = true } -log = { workspace = true } [dev-dependencies] +frame-election-provider-support = { workspace = true } +pallet-balances = { workspace = true, default-features = true } +pallet-nomination-pools = { workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } +pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } sp-tracing = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } -pallet-nomination-pools = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } -pallet-staking-reward-curve = { workspace = true, default-features = true } -frame-election-provider-support = { workspace = true } +substrate-test-utils = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/democracy/Cargo.toml b/substrate/frame/democracy/Cargo.toml index 3cfea8bb3129..189d64ccaa74 100644 --- a/substrate/frame/democracy/Cargo.toml +++ b/substrate/frame/democracy/Cargo.toml @@ -19,20 +19,20 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } +sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-core = { workspace = true } -log = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -pallet-scheduler = { workspace = true, default-features = true } pallet-preimage = { workspace = true, default-features = true } +pallet-scheduler = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/election-provider-multi-phase/Cargo.toml b/substrate/frame/election-provider-multi-phase/Cargo.toml index ff2a997fafe0..9a4a2a839346 100644 --- a/substrate/frame/election-provider-multi-phase/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/Cargo.toml @@ -18,20 +18,20 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } +log = { workspace = true } scale-info = { features = [ "derive", ], workspace = true } -log = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-io = { workspace = true } +frame-election-provider-support = { workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } -sp-runtime = { workspace = true } +sp-io = { workspace = true } sp-npos-elections = { workspace = true } -sp-arithmetic = { workspace = true } -frame-election-provider-support = { workspace = true } +sp-runtime = { workspace = true } # Optional imports for benchmarking frame-benchmarking = { optional = true, workspace = true } @@ -40,14 +40,14 @@ rand = { features = ["alloc", "small_rng"], optional = true, workspace = true } strum = { features = ["derive"], optional = true, workspace = true } [dev-dependencies] +frame-benchmarking = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true, default-features = true } sp-npos-elections = { workspace = true } sp-tracing = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -frame-benchmarking = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml index 771376e06656..5009d3d54d56 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml @@ -16,30 +16,30 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dev-dependencies] -parking_lot = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true, default-features = true } -scale-info = { features = ["derive"], workspace = true, default-features = true } log = { workspace = true } +parking_lot = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } -sp-staking = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-npos-elections = { workspace = true } +sp-runtime = { workspace = true, default-features = true } +sp-staking = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } +frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } -pallet-election-provider-multi-phase = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } -pallet-nomination-pools = { workspace = true, default-features = true } pallet-bags-list = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } +pallet-election-provider-multi-phase = { workspace = true, default-features = true } +pallet-nomination-pools = { workspace = true, default-features = true } pallet-session = { workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } [features] try-runtime = [ diff --git a/substrate/frame/election-provider-support/Cargo.toml b/substrate/frame/election-provider-support/Cargo.toml index cae20d1b46a4..32fa381e1d27 100644 --- a/substrate/frame/election-provider-support/Cargo.toml +++ b/substrate/frame/election-provider-support/Cargo.toml @@ -16,14 +16,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-election-provider-solution-type = { workspace = true, default-features = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true } +sp-core = { workspace = true } sp-npos-elections = { workspace = true } sp-runtime = { workspace = true } -sp-core = { workspace = true } [dev-dependencies] rand = { features = ["small_rng"], workspace = true, default-features = true } diff --git a/substrate/frame/election-provider-support/solution-type/Cargo.toml b/substrate/frame/election-provider-support/solution-type/Cargo.toml index e24ed7f079fe..c2f307016f6b 100644 --- a/substrate/frame/election-provider-support/solution-type/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { features = ["full", "visit"], workspace = true } -quote = { workspace = true } -proc-macro2 = { workspace = true } proc-macro-crate = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { features = ["full", "visit"], workspace = true } [dev-dependencies] codec = { workspace = true, default-features = true } diff --git a/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml index 86abbf9677e0..d82a8acb2f84 100644 --- a/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/substrate/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -21,14 +21,14 @@ honggfuzz = { workspace = true } rand = { features = ["small_rng", "std"], workspace = true, default-features = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-election-provider-solution-type = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } # used by generate_solution_type: -sp-npos-elections = { workspace = true } frame-support = { workspace = true, default-features = true } +sp-npos-elections = { workspace = true } [[bin]] name = "compact" diff --git a/substrate/frame/elections-phragmen/Cargo.toml b/substrate/frame/elections-phragmen/Cargo.toml index c1b12b3da4d8..b24ec7bd637e 100644 --- a/substrate/frame/elections-phragmen/Cargo.toml +++ b/substrate/frame/elections-phragmen/Cargo.toml @@ -19,11 +19,11 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-npos-elections = { workspace = true } diff --git a/substrate/frame/examples/Cargo.toml b/substrate/frame/examples/Cargo.toml index 0c6ad5ef0978..9eac53f0d98b 100644 --- a/substrate/frame/examples/Cargo.toml +++ b/substrate/frame/examples/Cargo.toml @@ -18,14 +18,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] pallet-default-config-example = { workspace = true } pallet-dev-mode = { workspace = true } +pallet-example-authorization-tx-extension = { workspace = true } pallet-example-basic = { workspace = true } pallet-example-frame-crate = { workspace = true } pallet-example-kitchensink = { workspace = true } pallet-example-offchain-worker = { workspace = true } -pallet-example-split = { workspace = true } pallet-example-single-block-migrations = { workspace = true } +pallet-example-split = { workspace = true } pallet-example-tasks = { workspace = true } -pallet-example-authorization-tx-extension = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/examples/basic/Cargo.toml b/substrate/frame/examples/basic/Cargo.toml index f7e2b653c2d1..1deb82cc6ea5 100644 --- a/substrate/frame/examples/basic/Cargo.toml +++ b/substrate/frame/examples/basic/Cargo.toml @@ -18,12 +18,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-balances = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/examples/default-config/Cargo.toml b/substrate/frame/examples/default-config/Cargo.toml index fa376b4f9136..87485aa08ef0 100644 --- a/substrate/frame/examples/default-config/Cargo.toml +++ b/substrate/frame/examples/default-config/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/examples/dev-mode/Cargo.toml b/substrate/frame/examples/dev-mode/Cargo.toml index 6625fb3a5851..7589abb929d5 100644 --- a/substrate/frame/examples/dev-mode/Cargo.toml +++ b/substrate/frame/examples/dev-mode/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-balances = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/examples/multi-block-migrations/Cargo.toml b/substrate/frame/examples/multi-block-migrations/Cargo.toml index 98569964a9c9..6e8e89784266 100644 --- a/substrate/frame/examples/multi-block-migrations/Cargo.toml +++ b/substrate/frame/examples/multi-block-migrations/Cargo.toml @@ -14,11 +14,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -pallet-migrations = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } log = { workspace = true } +pallet-migrations = { workspace = true } scale-info = { workspace = true } sp-io = { workspace = true } diff --git a/substrate/frame/examples/offchain-worker/Cargo.toml b/substrate/frame/examples/offchain-worker/Cargo.toml index a5664dd912d4..fabdfb0f9e0c 100644 --- a/substrate/frame/examples/offchain-worker/Cargo.toml +++ b/substrate/frame/examples/offchain-worker/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } lite-json = { workspace = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-keystore = { optional = true, workspace = true } diff --git a/substrate/frame/examples/single-block-migrations/Cargo.toml b/substrate/frame/examples/single-block-migrations/Cargo.toml index 26a3a9fff753..4df8693e0f37 100644 --- a/substrate/frame/examples/single-block-migrations/Cargo.toml +++ b/substrate/frame/examples/single-block-migrations/Cargo.toml @@ -13,18 +13,18 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -docify = { workspace = true } -log = { workspace = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } -frame-support = { workspace = true } +docify = { workspace = true } frame-executive = { workspace = true } +frame-support = { workspace = true } frame-system = { workspace = true } frame-try-runtime = { optional = true, workspace = true } +log = { workspace = true } pallet-balances = { workspace = true } -sp-runtime = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } +sp-runtime = { workspace = true } sp-version = { workspace = true } [features] diff --git a/substrate/frame/examples/tasks/Cargo.toml b/substrate/frame/examples/tasks/Cargo.toml index 00695ceddf19..48f4d9e66e9c 100644 --- a/substrate/frame/examples/tasks/Cargo.toml +++ b/substrate/frame/examples/tasks/Cargo.toml @@ -22,9 +22,9 @@ scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-core = { workspace = true } frame-benchmarking = { optional = true, workspace = true } diff --git a/substrate/frame/executive/Cargo.toml b/substrate/frame/executive/Cargo.toml index 76d084f49d9f..ee24a9fef13d 100644 --- a/substrate/frame/executive/Cargo.toml +++ b/substrate/frame/executive/Cargo.toml @@ -20,11 +20,11 @@ aquamarine = { workspace = true } codec = { features = [ "derive", ], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } frame-try-runtime = { optional = true, workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/fast-unstake/Cargo.toml b/substrate/frame/fast-unstake/Cargo.toml index c1d0e80551c2..98a9655074e7 100644 --- a/substrate/frame/fast-unstake/Cargo.toml +++ b/substrate/frame/fast-unstake/Cargo.toml @@ -22,23 +22,23 @@ scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +frame-election-provider-support = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } -frame-election-provider-support = { workspace = true } frame-benchmarking = { optional = true, workspace = true } docify = { workspace = true } [dev-dependencies] +pallet-balances = { workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true } -substrate-test-utils = { workspace = true } sp-tracing = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } +substrate-test-utils = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/glutton/Cargo.toml b/substrate/frame/glutton/Cargo.toml index 6717176ffc95..317a9ea8b760 100644 --- a/substrate/frame/glutton/Cargo.toml +++ b/substrate/frame/glutton/Cargo.toml @@ -18,15 +18,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] blake2 = { workspace = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } -log = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } +sp-inherents = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-inherents = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/grandpa/Cargo.toml b/substrate/frame/grandpa/Cargo.toml index 86ace358d05d..4072d65b6267 100644 --- a/substrate/frame/grandpa/Cargo.toml +++ b/substrate/frame/grandpa/Cargo.toml @@ -17,13 +17,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-authorship = { workspace = true } pallet-session = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } sp-consensus-grandpa = { features = ["serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } diff --git a/substrate/frame/identity/Cargo.toml b/substrate/frame/identity/Cargo.toml index bf974221b857..4ea7f797d9ee 100644 --- a/substrate/frame/identity/Cargo.toml +++ b/substrate/frame/identity/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } enumflags2 = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/im-online/Cargo.toml b/substrate/frame/im-online/Cargo.toml index 6c32c8ae898e..179c4c3ce3b1 100644 --- a/substrate/frame/im-online/Cargo.toml +++ b/substrate/frame/im-online/Cargo.toml @@ -17,12 +17,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-authorship = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } sp-io = { workspace = true } diff --git a/substrate/frame/indices/Cargo.toml b/substrate/frame/indices/Cargo.toml index d81b2d5cabf1..a0030b5b0edf 100644 --- a/substrate/frame/indices/Cargo.toml +++ b/substrate/frame/indices/Cargo.toml @@ -17,10 +17,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-keyring = { optional = true, workspace = true } diff --git a/substrate/frame/insecure-randomness-collective-flip/Cargo.toml b/substrate/frame/insecure-randomness-collective-flip/Cargo.toml index 1a47030812da..1682b52dfbf4 100644 --- a/substrate/frame/insecure-randomness-collective-flip/Cargo.toml +++ b/substrate/frame/insecure-randomness-collective-flip/Cargo.toml @@ -17,10 +17,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -safe-mix = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +safe-mix = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] diff --git a/substrate/frame/lottery/Cargo.toml b/substrate/frame/lottery/Cargo.toml index eb6e0b703d08..23eb19c7ffa7 100644 --- a/substrate/frame/lottery/Cargo.toml +++ b/substrate/frame/lottery/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] diff --git a/substrate/frame/membership/Cargo.toml b/substrate/frame/membership/Cargo.toml index 67aa3503ac0a..738d09b4b354 100644 --- a/substrate/frame/membership/Cargo.toml +++ b/substrate/frame/membership/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } sp-io = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } diff --git a/substrate/frame/merkle-mountain-range/Cargo.toml b/substrate/frame/merkle-mountain-range/Cargo.toml index 4daa394a82d7..04f5ab64100d 100644 --- a/substrate/frame/merkle-mountain-range/Cargo.toml +++ b/substrate/frame/merkle-mountain-range/Cargo.toml @@ -16,11 +16,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-mmr-primitives = { workspace = true } @@ -28,8 +28,8 @@ sp-runtime = { workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } itertools = { workspace = true } +sp-tracing = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/message-queue/Cargo.toml b/substrate/frame/message-queue/Cargo.toml index a6de61d70abf..7b0de7c1e4ff 100644 --- a/substrate/frame/message-queue/Cargo.toml +++ b/substrate/frame/message-queue/Cargo.toml @@ -13,15 +13,15 @@ workspace = true [dependencies] codec = { features = ["derive"], workspace = true } +environmental = { workspace = true } +log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, features = ["derive"], workspace = true, default-features = true } -log = { workspace = true } -environmental = { workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-arithmetic = { workspace = true } sp-weights = { workspace = true } frame-benchmarking = { optional = true, workspace = true } @@ -29,10 +29,10 @@ frame-support = { workspace = true } frame-system = { workspace = true } [dev-dependencies] -sp-crypto-hashing = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } rand_distr = { workspace = true } +sp-crypto-hashing = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/metadata-hash-extension/Cargo.toml b/substrate/frame/metadata-hash-extension/Cargo.toml index 8f4ba922984c..c7a417795ffe 100644 --- a/substrate/frame/metadata-hash-extension/Cargo.toml +++ b/substrate/frame/metadata-hash-extension/Cargo.toml @@ -11,22 +11,22 @@ description = "FRAME signed extension for verifying the metadata hash" [dependencies] array-bytes = { workspace = true, default-features = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } -sp-runtime = { features = ["serde"], workspace = true } +const-hex = { workspace = true } +docify = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } -docify = { workspace = true } -const-hex = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } +sp-runtime = { features = ["serde"], workspace = true } [dev-dependencies] -substrate-wasm-builder = { features = ["metadata-hash"], workspace = true, default-features = true } -substrate-test-runtime-client = { workspace = true } -sp-api = { workspace = true, default-features = true } -sp-transaction-pool = { workspace = true, default-features = true } -merkleized-metadata = { workspace = true } frame-metadata = { features = ["current", "unstable"], workspace = true, default-features = true } +merkleized-metadata = { workspace = true } +sp-api = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } +sp-transaction-pool = { workspace = true, default-features = true } +substrate-test-runtime-client = { workspace = true } +substrate-wasm-builder = { features = ["metadata-hash"], workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/migrations/Cargo.toml b/substrate/frame/migrations/Cargo.toml index a32e48e65280..469592780beb 100644 --- a/substrate/frame/migrations/Cargo.toml +++ b/substrate/frame/migrations/Cargo.toml @@ -11,8 +11,8 @@ repository.workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { features = ["derive"], workspace = true } cfg-if = { workspace = true } +codec = { features = ["derive"], workspace = true } docify = { workspace = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true, default-features = true } diff --git a/substrate/frame/multisig/Cargo.toml b/substrate/frame/multisig/Cargo.toml index c96be908faef..0d175617c9c2 100644 --- a/substrate/frame/multisig/Cargo.toml +++ b/substrate/frame/multisig/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame = { workspace = true, features = ["experimental", "runtime"] } +scale-info = { features = ["derive"], workspace = true } # third party log = { workspace = true } diff --git a/substrate/frame/nft-fractionalization/Cargo.toml b/substrate/frame/nft-fractionalization/Cargo.toml index 6a064204b895..7f6df86ed0e5 100644 --- a/substrate/frame/nft-fractionalization/Cargo.toml +++ b/substrate/frame/nft-fractionalization/Cargo.toml @@ -17,13 +17,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-assets = { workspace = true } pallet-nfts = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] diff --git a/substrate/frame/nfts/Cargo.toml b/substrate/frame/nfts/Cargo.toml index a97b49e56524..18895018e1c5 100644 --- a/substrate/frame/nfts/Cargo.toml +++ b/substrate/frame/nfts/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } enumflags2 = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/nis/Cargo.toml b/substrate/frame/nis/Cargo.toml index 78e086d0ed12..ec1a5d93bcba 100644 --- a/substrate/frame/nis/Cargo.toml +++ b/substrate/frame/nis/Cargo.toml @@ -17,10 +17,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/node-authorization/Cargo.toml b/substrate/frame/node-authorization/Cargo.toml index 82aecc21d0b5..174736493934 100644 --- a/substrate/frame/node-authorization/Cargo.toml +++ b/substrate/frame/node-authorization/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/nomination-pools/Cargo.toml b/substrate/frame/nomination-pools/Cargo.toml index aa90e4d81339..a5e8da17eb23 100644 --- a/substrate/frame/nomination-pools/Cargo.toml +++ b/substrate/frame/nomination-pools/Cargo.toml @@ -26,11 +26,11 @@ scale-info = { features = [ # FRAME frame-support = { workspace = true } frame-system = { workspace = true } -sp-runtime = { workspace = true } -sp-staking = { workspace = true } +log = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } -log = { workspace = true } +sp-runtime = { workspace = true } +sp-staking = { workspace = true } # Optional: use for testing and/or fuzzing pallet-balances = { optional = true, workspace = true } diff --git a/substrate/frame/nomination-pools/benchmarking/Cargo.toml b/substrate/frame/nomination-pools/benchmarking/Cargo.toml index 7dd826a91224..0b3ac228e86f 100644 --- a/substrate/frame/nomination-pools/benchmarking/Cargo.toml +++ b/substrate/frame/nomination-pools/benchmarking/Cargo.toml @@ -26,9 +26,9 @@ frame-election-provider-support = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-bags-list = { workspace = true } -pallet-staking = { workspace = true } pallet-delegated-staking = { workspace = true } pallet-nomination-pools = { workspace = true } +pallet-staking = { workspace = true } # Substrate Primitives sp-runtime = { workspace = true } @@ -37,8 +37,8 @@ sp-staking = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true } -pallet-timestamp = { workspace = true, default-features = true } pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } diff --git a/substrate/frame/nomination-pools/fuzzer/Cargo.toml b/substrate/frame/nomination-pools/fuzzer/Cargo.toml index e1518ed099ae..2f84004ece94 100644 --- a/substrate/frame/nomination-pools/fuzzer/Cargo.toml +++ b/substrate/frame/nomination-pools/fuzzer/Cargo.toml @@ -21,15 +21,15 @@ honggfuzz = { workspace = true } pallet-nomination-pools = { features = ["fuzzing"], workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -rand = { features = ["small_rng"], workspace = true, default-features = true } log = { workspace = true, default-features = true } +rand = { features = ["small_rng"], workspace = true, default-features = true } [[bin]] name = "call" diff --git a/substrate/frame/nomination-pools/runtime-api/Cargo.toml b/substrate/frame/nomination-pools/runtime-api/Cargo.toml index 6de9fc8c8844..337cc31c7cbb 100644 --- a/substrate/frame/nomination-pools/runtime-api/Cargo.toml +++ b/substrate/frame/nomination-pools/runtime-api/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -sp-api = { workspace = true } pallet-nomination-pools = { workspace = true } +sp-api = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml index 70e1591409b8..fe3743d7e5da 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml +++ b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml @@ -19,23 +19,23 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = ["derive"], workspace = true, default-features = true } scale-info = { features = ["derive"], workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-staking = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } -frame-support = { features = ["experimental"], workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } +frame-support = { features = ["experimental"], workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } +pallet-bags-list = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } pallet-delegated-staking = { workspace = true, default-features = true } -pallet-bags-list = { workspace = true, default-features = true } -pallet-staking-reward-curve = { workspace = true, default-features = true } pallet-nomination-pools = { workspace = true, default-features = true } +pallet-staking = { workspace = true, default-features = true } +pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } log = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } diff --git a/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml b/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml index 7398404c2351..2cdc4c41a083 100644 --- a/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml +++ b/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml @@ -19,22 +19,22 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = ["derive"], workspace = true, default-features = true } scale-info = { features = ["derive"], workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-staking = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +sp-std = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } +frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } +pallet-bags-list = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +pallet-nomination-pools = { workspace = true, default-features = true } pallet-staking = { workspace = true, default-features = true } -pallet-bags-list = { workspace = true, default-features = true } pallet-staking-reward-curve = { workspace = true, default-features = true } -pallet-nomination-pools = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } log = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } diff --git a/substrate/frame/offences/Cargo.toml b/substrate/frame/offences/Cargo.toml index 98c320e1f808..4dd9d7f10c9f 100644 --- a/substrate/frame/offences/Cargo.toml +++ b/substrate/frame/offences/Cargo.toml @@ -17,12 +17,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-balances = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } diff --git a/substrate/frame/offences/benchmarking/Cargo.toml b/substrate/frame/offences/benchmarking/Cargo.toml index 28c7895180c4..76b167ebdb33 100644 --- a/substrate/frame/offences/benchmarking/Cargo.toml +++ b/substrate/frame/offences/benchmarking/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { workspace = true } frame-election-provider-support = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-babe = { workspace = true } pallet-balances = { workspace = true } pallet-grandpa = { workspace = true } @@ -29,9 +29,9 @@ pallet-im-online = { workspace = true } pallet-offences = { workspace = true } pallet-session = { workspace = true } pallet-staking = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } -log = { workspace = true } [dev-dependencies] pallet-staking-reward-curve = { workspace = true, default-features = true } diff --git a/substrate/frame/paged-list/Cargo.toml b/substrate/frame/paged-list/Cargo.toml index a680139c5fdc..da029bdd7423 100644 --- a/substrate/frame/paged-list/Cargo.toml +++ b/substrate/frame/paged-list/Cargo.toml @@ -23,10 +23,10 @@ frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-runtime = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-metadata-ir = { optional = true, workspace = true } +sp-runtime = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/paged-list/fuzzer/Cargo.toml b/substrate/frame/paged-list/fuzzer/Cargo.toml index d0108254ed2d..7e6162df09ba 100644 --- a/substrate/frame/paged-list/fuzzer/Cargo.toml +++ b/substrate/frame/paged-list/fuzzer/Cargo.toml @@ -21,5 +21,5 @@ arbitrary = { workspace = true } honggfuzz = { workspace = true } frame-support = { features = ["std"], workspace = true } -sp-io = { features = ["std"], workspace = true } pallet-paged-list = { features = ["std"], workspace = true } +sp-io = { features = ["std"], workspace = true } diff --git a/substrate/frame/parameters/Cargo.toml b/substrate/frame/parameters/Cargo.toml index a97ba1172a50..dda218b618c4 100644 --- a/substrate/frame/parameters/Cargo.toml +++ b/substrate/frame/parameters/Cargo.toml @@ -9,22 +9,22 @@ edition.workspace = true [dependencies] codec = { features = ["max-encoded-len"], workspace = true } -scale-info = { features = ["derive"], workspace = true } +docify = { workspace = true } paste = { workspace = true } +scale-info = { features = ["derive"], workspace = true } serde = { features = ["derive"], optional = true, workspace = true, default-features = true } -docify = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { features = ["experimental"], workspace = true } frame-system = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } [dev-dependencies] +pallet-balances = { features = ["std"], workspace = true, default-features = true } +pallet-example-basic = { features = ["std"], workspace = true, default-features = true } sp-core = { features = ["std"], workspace = true, default-features = true } sp-io = { features = ["std"], workspace = true, default-features = true } -pallet-example-basic = { features = ["std"], workspace = true, default-features = true } -pallet-balances = { features = ["std"], workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/preimage/Cargo.toml b/substrate/frame/preimage/Cargo.toml index 1356ac403d38..fae6627b6315 100644 --- a/substrate/frame/preimage/Cargo.toml +++ b/substrate/frame/preimage/Cargo.toml @@ -13,14 +13,14 @@ workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { optional = true, workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -log = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/proxy/Cargo.toml b/substrate/frame/proxy/Cargo.toml index 8897c66419c7..a36b2c1cb9c3 100644 --- a/substrate/frame/proxy/Cargo.toml +++ b/substrate/frame/proxy/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["max-encoded-len"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame = { workspace = true, features = ["experimental", "runtime"] } +scale-info = { features = ["derive"], workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/ranked-collective/Cargo.toml b/substrate/frame/ranked-collective/Cargo.toml index eca59cf7fc22..78a02bec8e97 100644 --- a/substrate/frame/ranked-collective/Cargo.toml +++ b/substrate/frame/ranked-collective/Cargo.toml @@ -17,16 +17,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -impl-trait-for-tuples = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/recovery/Cargo.toml b/substrate/frame/recovery/Cargo.toml index 44335e8f575c..4f3a734d9868 100644 --- a/substrate/frame/recovery/Cargo.toml +++ b/substrate/frame/recovery/Cargo.toml @@ -17,10 +17,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/referenda/Cargo.toml b/substrate/frame/referenda/Cargo.toml index e9b4eb04ed51..0f35dc74382e 100644 --- a/substrate/frame/referenda/Cargo.toml +++ b/substrate/frame/referenda/Cargo.toml @@ -20,15 +20,15 @@ assert_matches = { optional = true, workspace = true } codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], optional = true, workspace = true, default-features = true } -sp-arithmetic = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } +sp-arithmetic = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -log = { workspace = true } [dev-dependencies] assert_matches = { workspace = true } diff --git a/substrate/frame/remark/Cargo.toml b/substrate/frame/remark/Cargo.toml index 487bada593cd..a40b577b52ea 100644 --- a/substrate/frame/remark/Cargo.toml +++ b/substrate/frame/remark/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 5d2bfb4f795e..fa008f8e836a 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -17,29 +17,29 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { features = ["derive", "max-encoded-len"], workspace = true } +derive_more = { workspace = true } environmental = { workspace = true } +ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] } +hex = { workspace = true } +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } paste = { workspace = true } polkavm = { version = "0.18.0", default-features = false } -codec = { features = ["derive", "max-encoded-len"], workspace = true } +rlp = { workspace = true } scale-info = { features = ["derive"], workspace = true } -log = { workspace = true } serde = { features = [ "alloc", "derive", ], workspace = true, default-features = false } -impl-trait-for-tuples = { workspace = true } -rlp = { workspace = true } -derive_more = { workspace = true } -hex = { workspace = true } -ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] } # Polkadot SDK Dependencies frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-revive-fixtures = { workspace = true, optional = true } -pallet-revive-uapi = { workspace = true, features = ["scale"] } pallet-revive-proc-macro = { workspace = true } +pallet-revive-uapi = { workspace = true, features = ["scale"] } pallet-transaction-payment = { workspace = true } sp-api = { workspace = true } sp-arithmetic = { workspace = true } @@ -47,26 +47,26 @@ sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } -xcm = { workspace = true } -xcm-builder = { workspace = true } subxt-signer = { workspace = true, optional = true, features = [ "unstable-eth", ] } +xcm = { workspace = true } +xcm-builder = { workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } assert_matches = { workspace = true } +hex-literal = { workspace = true } pretty_assertions = { workspace = true } secp256k1 = { workspace = true, features = ["recovery"] } serde_json = { workspace = true } -hex-literal = { workspace = true } # Polkadot SDK Dependencies pallet-balances = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } pallet-proxy = { workspace = true, default-features = true } pallet-revive-fixtures = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } xcm-builder = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index 1095f962ac1b..e17bc88a3847 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -15,14 +15,14 @@ exclude-from-umbrella = true workspace = true [dependencies] +anyhow = { workspace = true, default-features = true, optional = true } sp-core = { workspace = true, default-features = true, optional = true } sp-io = { workspace = true, default-features = true, optional = true } -anyhow = { workspace = true, default-features = true, optional = true } [build-dependencies] -toml = { workspace = true } -polkavm-linker = { version = "0.18.0" } anyhow = { workspace = true, default-features = true } +polkavm-linker = { version = "0.18.0" } +toml = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/revive/mock-network/Cargo.toml b/substrate/frame/revive/mock-network/Cargo.toml index 0d8814f81a9c..1ebeb2c95db7 100644 --- a/substrate/frame/revive/mock-network/Cargo.toml +++ b/substrate/frame/revive/mock-network/Cargo.toml @@ -18,9 +18,9 @@ frame-support = { workspace = true } frame-system = { workspace = true } pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +pallet-message-queue = { workspace = true, default-features = true } pallet-revive = { workspace = true, default-features = true } pallet-revive-uapi = { workspace = true } -pallet-message-queue = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } pallet-xcm = { workspace = true } polkadot-parachain-primitives = { workspace = true, default-features = true } @@ -38,8 +38,8 @@ xcm-simulator = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } -pretty_assertions = { workspace = true } pallet-revive-fixtures = { workspace = true } +pretty_assertions = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 31b8f505dedc..cfaaa102fc3d 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -38,37 +38,37 @@ path = "examples/rust/remark-extrinsic.rs" required-features = ["example"] [dependencies] -clap = { workspace = true, features = ["derive"] } anyhow = { workspace = true } +clap = { workspace = true, features = ["derive"] } +codec = { workspace = true, features = ["derive"] } +ethabi = { version = "18.0.0" } futures = { workspace = true, features = ["thread-pool"] } +hex = { workspace = true } jsonrpsee = { workspace = true, features = ["full"] } -thiserror = { workspace = true } -sp-crypto-hashing = { workspace = true } -subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"] } -tokio = { workspace = true, features = ["full"] } -codec = { workspace = true, features = ["derive"] } log = { workspace = true } pallet-revive = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-weights = { workspace = true, default-features = true } +prometheus-endpoint = { workspace = true, default-features = true } +rlp = { workspace = true, optional = true } +sc-cli = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } -sc-cli = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -prometheus-endpoint = { workspace = true, default-features = true } -rlp = { workspace = true, optional = true } +sp-core = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true } +sp-weights = { workspace = true, default-features = true } +subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"] } subxt-signer = { workspace = true, optional = true, features = [ "unstable-eth", ] } -hex = { workspace = true } -ethabi = { version = "18.0.0" } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["full"] } [features] example = ["rlp", "subxt-signer"] [dev-dependencies] env_logger = { workspace = true } -static_init = { workspace = true } pallet-revive-fixtures = { workspace = true, default-features = true } +static_init = { workspace = true } substrate-cli-test-utils = { workspace = true } subxt-signer = { workspace = true, features = ["unstable-eth"] } diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml index 948c2c6e4f83..7241d667fcdc 100644 --- a/substrate/frame/revive/uapi/Cargo.toml +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -12,14 +12,14 @@ description = "Exposes all the host functions that a contract can import." workspace = true [dependencies] -paste = { workspace = true } bitflags = { workspace = true } -scale-info = { features = ["derive"], optional = true, workspace = true } codec = { features = [ "derive", "max-encoded-len", ], optional = true, workspace = true } pallet-revive-proc-macro = { workspace = true } +paste = { workspace = true } +scale-info = { features = ["derive"], optional = true, workspace = true } [target.'cfg(target_arch = "riscv64")'.dependencies] polkavm-derive = { version = "0.18.0" } diff --git a/substrate/frame/root-offences/Cargo.toml b/substrate/frame/root-offences/Cargo.toml index f80fed11b971..dedde9956b6f 100644 --- a/substrate/frame/root-offences/Cargo.toml +++ b/substrate/frame/root-offences/Cargo.toml @@ -29,8 +29,8 @@ sp-staking = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true } diff --git a/substrate/frame/root-testing/Cargo.toml b/substrate/frame/root-testing/Cargo.toml index ee3ce8011009..fd0f4da2e80c 100644 --- a/substrate/frame/root-testing/Cargo.toml +++ b/substrate/frame/root-testing/Cargo.toml @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/safe-mode/Cargo.toml b/substrate/frame/safe-mode/Cargo.toml index e7f165ae67d8..3f1f6bc1f1d6 100644 --- a/substrate/frame/safe-mode/Cargo.toml +++ b/substrate/frame/safe-mode/Cargo.toml @@ -20,20 +20,20 @@ docify = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +pallet-balances = { optional = true, workspace = true } +pallet-proxy = { optional = true, workspace = true } +pallet-utility = { optional = true, workspace = true } scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true } sp-runtime = { workspace = true } -pallet-balances = { optional = true, workspace = true } -pallet-utility = { optional = true, workspace = true } -pallet-proxy = { optional = true, workspace = true } [dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } +frame-support = { features = ["experimental"], workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } pallet-proxy = { workspace = true, default-features = true } -frame-support = { features = ["experimental"], workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/salary/Cargo.toml b/substrate/frame/salary/Cargo.toml index 9e4cf06288dd..b3ed95bf1de5 100644 --- a/substrate/frame/salary/Cargo.toml +++ b/substrate/frame/salary/Cargo.toml @@ -17,16 +17,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +pallet-ranked-collective = { optional = true, workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -pallet-ranked-collective = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/frame/sassafras/Cargo.toml b/substrate/frame/sassafras/Cargo.toml index 7eb2bda96ffc..dd091b6f8ed7 100644 --- a/substrate/frame/sassafras/Cargo.toml +++ b/substrate/frame/sassafras/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-consensus-sassafras = { features = ["serde"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/scheduler/Cargo.toml b/substrate/frame/scheduler/Cargo.toml index 1432ada91335..0506470e72c3 100644 --- a/substrate/frame/scheduler/Cargo.toml +++ b/substrate/frame/scheduler/Cargo.toml @@ -14,15 +14,15 @@ workspace = true [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } +docify = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-weights = { workspace = true } -docify = { workspace = true } [dev-dependencies] pallet-preimage = { workspace = true, default-features = true } diff --git a/substrate/frame/scored-pool/Cargo.toml b/substrate/frame/scored-pool/Cargo.toml index d945ef42a47b..227868fa2a4f 100644 --- a/substrate/frame/scored-pool/Cargo.toml +++ b/substrate/frame/scored-pool/Cargo.toml @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/session/Cargo.toml b/substrate/frame/session/Cargo.toml index b82112681e67..737678bea8a3 100644 --- a/substrate/frame/session/Cargo.toml +++ b/substrate/frame/session/Cargo.toml @@ -17,19 +17,19 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -impl-trait-for-tuples = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } pallet-timestamp = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } sp-core = { features = ["serde"], workspace = true } sp-io = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-session = { workspace = true } sp-staking = { features = ["serde"], workspace = true } -sp-trie = { optional = true, workspace = true } sp-state-machine = { workspace = true } +sp-trie = { optional = true, workspace = true } [features] default = ["historical", "std"] diff --git a/substrate/frame/session/benchmarking/Cargo.toml b/substrate/frame/session/benchmarking/Cargo.toml index 264bc10a33f6..72e4b3deabfd 100644 --- a/substrate/frame/session/benchmarking/Cargo.toml +++ b/substrate/frame/session/benchmarking/Cargo.toml @@ -17,22 +17,22 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -rand = { features = ["std_rng"], workspace = true } frame-benchmarking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-session = { workspace = true } pallet-staking = { workspace = true } +rand = { features = ["std_rng"], workspace = true } sp-runtime = { workspace = true } sp-session = { workspace = true } [dev-dependencies] codec = { features = ["derive"], workspace = true, default-features = true } -scale-info = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-staking-reward-curve = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } +scale-info = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } diff --git a/substrate/frame/society/Cargo.toml b/substrate/frame/society/Cargo.toml index 555dee68ba01..d5860518fdda 100644 --- a/substrate/frame/society/Cargo.toml +++ b/substrate/frame/society/Cargo.toml @@ -16,17 +16,17 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { features = ["derive"], workspace = true } log = { workspace = true } rand_chacha = { workspace = true } scale-info = { features = ["derive"], workspace = true } -codec = { features = ["derive"], workspace = true } -sp-io = { workspace = true } -sp-arithmetic = { workspace = true } -sp-runtime = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +sp-arithmetic = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] frame-support-test = { workspace = true } diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml index a6a0ccd3b0a7..22176b6d720b 100644 --- a/substrate/frame/staking/Cargo.toml +++ b/substrate/frame/staking/Cargo.toml @@ -16,40 +16,40 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { features = ["alloc", "derive"], workspace = true } codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive", "serde"], workspace = true } -sp-io = { workspace = true } -sp-runtime = { features = ["serde"], workspace = true } -sp-staking = { features = ["serde"], workspace = true } +frame-election-provider-support = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +pallet-authorship = { workspace = true } pallet-session = { features = [ "historical", ], workspace = true } -pallet-authorship = { workspace = true } +scale-info = { features = ["derive", "serde"], workspace = true } +serde = { features = ["alloc", "derive"], workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } -frame-election-provider-support = { workspace = true } -log = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { features = ["serde"], workspace = true } +sp-staking = { features = ["serde"], workspace = true } # Optional imports for benchmarking frame-benchmarking = { optional = true, workspace = true } rand_chacha = { optional = true, workspace = true } [dev-dependencies] +frame-benchmarking = { workspace = true, default-features = true } +frame-election-provider-support = { workspace = true, default-features = true } +pallet-bags-list = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } +pallet-staking-reward-curve = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } +rand_chacha = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-npos-elections = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } -pallet-staking-reward-curve = { workspace = true, default-features = true } -pallet-bags-list = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } substrate-test-utils = { workspace = true } -frame-benchmarking = { workspace = true, default-features = true } -frame-election-provider-support = { workspace = true, default-features = true } -rand_chacha = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/state-trie-migration/Cargo.toml b/substrate/frame/state-trie-migration/Cargo.toml index 8c82bc38da97..1f1f6fc5be3a 100644 --- a/substrate/frame/state-trie-migration/Cargo.toml +++ b/substrate/frame/state-trie-migration/Cargo.toml @@ -16,25 +16,25 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } -thousands = { optional = true, workspace = true } -zstd = { optional = true, workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } remote-externalities = { optional = true, workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } substrate-state-trie-migration-rpc = { optional = true, workspace = true, default-features = true } +thousands = { optional = true, workspace = true } +zstd = { optional = true, workspace = true } [dev-dependencies] -parking_lot = { workspace = true, default-features = true } -tokio = { features = ["macros"], workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +parking_lot = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } +tokio = { features = ["macros"], workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/statement/Cargo.toml b/substrate/frame/statement/Cargo.toml index e601881cd720..b1449fa24416 100644 --- a/substrate/frame/statement/Cargo.toml +++ b/substrate/frame/statement/Cargo.toml @@ -16,15 +16,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -sp-statement-store = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-api = { workspace = true } -sp-runtime = { workspace = true } -sp-io = { workspace = true } sp-core = { workspace = true } -log = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-statement-store = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } diff --git a/substrate/frame/sudo/Cargo.toml b/substrate/frame/sudo/Cargo.toml index 9b362019b29b..e2096bf0668a 100644 --- a/substrate/frame/sudo/Cargo.toml +++ b/substrate/frame/sudo/Cargo.toml @@ -18,9 +18,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/support/Cargo.toml b/substrate/frame/support/Cargo.toml index 4348bd275605..1f4fdd5d46cd 100644 --- a/substrate/frame/support/Cargo.toml +++ b/substrate/frame/support/Cargo.toml @@ -18,59 +18,59 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = { workspace = true } binary-merkle-tree.workspace = true -serde = { features = ["alloc", "derive"], workspace = true } +bitflags = { workspace = true } codec = { features = [ "derive", "max-encoded-len", ], workspace = true } -scale-info = { features = [ - "derive", -], workspace = true } +docify = { workspace = true } +environmental = { workspace = true } frame-metadata = { features = [ "current", "unstable", ], workspace = true } +frame-support-procedural = { workspace = true } +impl-trait-for-tuples = { workspace = true } +k256 = { features = ["ecdsa"], workspace = true } +log = { workspace = true } +macro_magic = { workspace = true } +paste = { workspace = true, default-features = true } +scale-info = { features = [ + "derive", +], workspace = true } +serde = { features = ["alloc", "derive"], workspace = true } +serde_json = { features = ["alloc"], workspace = true } +smallvec = { workspace = true, default-features = true } sp-api = { features = [ "frame-metadata", ], workspace = true } -sp-std = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { features = ["serde"], workspace = true } -sp-tracing = { workspace = true } -sp-core = { workspace = true } sp-arithmetic = { workspace = true } -sp-inherents = { workspace = true } -sp-staking = { workspace = true } -sp-weights = { workspace = true } +sp-core = { workspace = true } +sp-crypto-hashing-proc-macro = { workspace = true, default-features = true } sp-debug-derive = { workspace = true } +sp-genesis-builder = { workspace = true } +sp-inherents = { workspace = true } +sp-io = { workspace = true } sp-metadata-ir = { workspace = true } -sp-trie = { workspace = true } -tt-call = { workspace = true } -macro_magic = { workspace = true } -frame-support-procedural = { workspace = true } -paste = { workspace = true, default-features = true } +sp-runtime = { features = ["serde"], workspace = true } +sp-staking = { workspace = true } sp-state-machine = { optional = true, workspace = true } -bitflags = { workspace = true } -impl-trait-for-tuples = { workspace = true } -smallvec = { workspace = true, default-features = true } -log = { workspace = true } -sp-crypto-hashing-proc-macro = { workspace = true, default-features = true } -k256 = { features = ["ecdsa"], workspace = true } -environmental = { workspace = true } -sp-genesis-builder = { workspace = true } -serde_json = { features = ["alloc"], workspace = true } -docify = { workspace = true } +sp-std = { workspace = true } +sp-tracing = { workspace = true } +sp-trie = { workspace = true } +sp-weights = { workspace = true } static_assertions = { workspace = true, default-features = true } +tt-call = { workspace = true } aquamarine = { workspace = true } [dev-dependencies] +Inflector = { workspace = true } assert_matches = { workspace = true } -pretty_assertions = { workspace = true } -sp-timestamp = { workspace = true } frame-system = { workspace = true, default-features = true } +pretty_assertions = { workspace = true } sp-crypto-hashing = { workspace = true, default-features = true } -Inflector = { workspace = true } +sp-timestamp = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index 51790062b2c2..624562187617 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -18,36 +18,36 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -derive-syn-parse = { workspace = true } -docify = { workspace = true } Inflector = { workspace = true } cfg-expr = { workspace = true } -itertools = { workspace = true } -proc-macro2 = { workspace = true } -quote = { workspace = true } -syn = { features = ["full", "parsing", "visit-mut"], workspace = true } +derive-syn-parse = { workspace = true } +docify = { workspace = true } +expander = { workspace = true } frame-support-procedural-tools = { workspace = true, default-features = true } +itertools = { workspace = true } macro_magic = { features = ["proc_support"], workspace = true } proc-macro-warning = { workspace = true } -expander = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } sp-crypto-hashing = { workspace = true } +syn = { features = ["full", "parsing", "visit-mut"], workspace = true } [dev-dependencies] codec = { features = [ "derive", "max-encoded-len", ], workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pretty_assertions = { workspace = true } regex = { workspace = true } -sp-metadata-ir = { workspace = true } scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } +sp-metadata-ir = { workspace = true } sp-runtime = { features = [ "serde", ], workspace = true } -frame-system = { workspace = true } -frame-support = { workspace = true } -pretty_assertions = { workspace = true } static_assertions = { workspace = true } [features] diff --git a/substrate/frame/support/procedural/tools/Cargo.toml b/substrate/frame/support/procedural/tools/Cargo.toml index e61e17e8ac75..cbb2fde9e816 100644 --- a/substrate/frame/support/procedural/tools/Cargo.toml +++ b/substrate/frame/support/procedural/tools/Cargo.toml @@ -15,8 +15,8 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +frame-support-procedural-tools-derive = { workspace = true, default-features = true } proc-macro-crate = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } syn = { features = ["extra-traits", "full", "visit"], workspace = true } -frame-support-procedural-tools-derive = { workspace = true, default-features = true } diff --git a/substrate/frame/support/test/Cargo.toml b/substrate/frame/support/test/Cargo.toml index 17ee3130b741..ca122e6bd544 100644 --- a/substrate/frame/support/test/Cargo.toml +++ b/substrate/frame/support/test/Cargo.toml @@ -15,26 +15,26 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -static_assertions = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } +frame-benchmarking = { workspace = true } +frame-executive = { workspace = true } frame-metadata = { features = ["current", "unstable"], workspace = true } +frame-support = { features = ["experimental"], workspace = true } +frame-system = { workspace = true } +pretty_assertions = { workspace = true } +rustversion = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], workspace = true } sp-api = { workspace = true } sp-arithmetic = { workspace = true } +sp-core = { workspace = true } sp-io = { workspace = true } -sp-state-machine = { optional = true, workspace = true, default-features = true } -frame-support = { features = ["experimental"], workspace = true } -frame-benchmarking = { workspace = true } +sp-metadata-ir = { workspace = true } sp-runtime = { workspace = true } -sp-core = { workspace = true } +sp-state-machine = { optional = true, workspace = true, default-features = true } sp-version = { workspace = true } -sp-metadata-ir = { workspace = true } +static_assertions = { workspace = true, default-features = true } trybuild = { features = ["diff"], workspace = true } -pretty_assertions = { workspace = true } -rustversion = { workspace = true } -frame-system = { workspace = true } -frame-executive = { workspace = true } # The "std" feature for this pallet is never activated on purpose, in order to test construct_runtime error message test-pallet = { workspace = true } diff --git a/substrate/frame/support/test/compile_pass/Cargo.toml b/substrate/frame/support/test/compile_pass/Cargo.toml index 9e0a7ff7c675..988135d64dbf 100644 --- a/substrate/frame/support/test/compile_pass/Cargo.toml +++ b/substrate/frame/support/test/compile_pass/Cargo.toml @@ -16,9 +16,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } sp-version = { workspace = true } diff --git a/substrate/frame/support/test/pallet/Cargo.toml b/substrate/frame/support/test/pallet/Cargo.toml index f03377dc21eb..dc5558b1d4b8 100644 --- a/substrate/frame/support/test/pallet/Cargo.toml +++ b/substrate/frame/support/test/pallet/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [features] diff --git a/substrate/frame/system/Cargo.toml b/substrate/frame/system/Cargo.toml index 38349c7edbd9..1340b2c55c53 100644 --- a/substrate/frame/system/Cargo.toml +++ b/substrate/frame/system/Cargo.toml @@ -18,17 +18,17 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] cfg-if = { workspace = true } codec = { features = ["derive"], workspace = true } +docify = { workspace = true } +frame-support = { workspace = true } log = { workspace = true } scale-info = { features = ["derive", "serde"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true } -frame-support = { workspace = true } sp-core = { features = ["serde"], workspace = true } sp-io = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-std = { workspace = true } sp-version = { features = ["serde"], workspace = true } sp-weights = { features = ["serde"], workspace = true } -docify = { workspace = true } [dev-dependencies] criterion = { workspace = true, default-features = true } diff --git a/substrate/frame/system/benchmarking/Cargo.toml b/substrate/frame/system/benchmarking/Cargo.toml index d9b5e7083bd2..e9aac6e519f3 100644 --- a/substrate/frame/system/benchmarking/Cargo.toml +++ b/substrate/frame/system/benchmarking/Cargo.toml @@ -17,16 +17,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] -sp-io = { workspace = true, default-features = true } sp-externalities = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } [features] diff --git a/substrate/frame/system/rpc/runtime-api/Cargo.toml b/substrate/frame/system/rpc/runtime-api/Cargo.toml index 8e968a536756..3fd1985619bd 100644 --- a/substrate/frame/system/rpc/runtime-api/Cargo.toml +++ b/substrate/frame/system/rpc/runtime-api/Cargo.toml @@ -17,8 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -sp-api = { workspace = true } docify = { workspace = true } +sp-api = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/timestamp/Cargo.toml b/substrate/frame/timestamp/Cargo.toml index 0eff0530c7e2..75788aef348a 100644 --- a/substrate/frame/timestamp/Cargo.toml +++ b/substrate/frame/timestamp/Cargo.toml @@ -18,11 +18,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-inherents = { workspace = true } sp-io = { optional = true, workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/tips/Cargo.toml b/substrate/frame/tips/Cargo.toml index 7c7a2d6aa909..6b5b89e7a197 100644 --- a/substrate/frame/tips/Cargo.toml +++ b/substrate/frame/tips/Cargo.toml @@ -17,13 +17,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-treasury = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/frame/transaction-payment/Cargo.toml b/substrate/frame/transaction-payment/Cargo.toml index afa03ceb12eb..2639bda18b6c 100644 --- a/substrate/frame/transaction-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/Cargo.toml @@ -19,18 +19,18 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] -serde_json = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml b/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml index 7c98d157f6ff..147859fdb26a 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml @@ -17,21 +17,21 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # Substrate dependencies -sp-runtime = { workspace = true } +codec = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-asset-conversion = { workspace = true } pallet-transaction-payment = { workspace = true } -codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] +pallet-assets = { workspace = true, default-features = true } +pallet-balances = { workspace = true, default-features = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-storage = { workspace = true } -pallet-assets = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml index 89fe5bfe7a42..2924860c5201 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/substrate/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -21,10 +21,10 @@ sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-transaction-payment = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } # Other dependencies codec = { features = ["derive"], workspace = true } diff --git a/substrate/frame/transaction-storage/Cargo.toml b/substrate/frame/transaction-storage/Cargo.toml index f5d6bd1c364c..0ca38e9dd60d 100644 --- a/substrate/frame/transaction-storage/Cargo.toml +++ b/substrate/frame/transaction-storage/Cargo.toml @@ -18,17 +18,17 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] array-bytes = { optional = true, workspace = true, default-features = true } codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } pallet-balances = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } sp-inherents = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } sp-transaction-storage-proof = { workspace = true } -log = { workspace = true } [dev-dependencies] sp-core = { workspace = true } diff --git a/substrate/frame/treasury/Cargo.toml b/substrate/frame/treasury/Cargo.toml index 93a3d9bea93d..c6f059f5fa03 100644 --- a/substrate/frame/treasury/Cargo.toml +++ b/substrate/frame/treasury/Cargo.toml @@ -21,21 +21,21 @@ codec = { features = [ "max-encoded-len", ], workspace = true } docify = { workspace = true } -impl-trait-for-tuples = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -serde = { features = ["derive"], optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +impl-trait-for-tuples = { workspace = true } +log = { workspace = true } pallet-balances = { workspace = true } -sp-runtime = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["derive"], optional = true, workspace = true, default-features = true } sp-core = { optional = true, workspace = true } -log = { workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] -sp-io = { workspace = true, default-features = true } pallet-utility = { workspace = true, default-features = true } sp-core = { workspace = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/tx-pause/Cargo.toml b/substrate/frame/tx-pause/Cargo.toml index 03c700ec053c..6298645fb2b3 100644 --- a/substrate/frame/tx-pause/Cargo.toml +++ b/substrate/frame/tx-pause/Cargo.toml @@ -20,18 +20,18 @@ docify = { workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -sp-runtime = { workspace = true } pallet-balances = { optional = true, workspace = true } -pallet-utility = { optional = true, workspace = true } pallet-proxy = { optional = true, workspace = true } +pallet-utility = { optional = true, workspace = true } +scale-info = { features = ["derive"], workspace = true } +sp-runtime = { workspace = true } [dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } pallet-proxy = { workspace = true, default-features = true } +pallet-utility = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/uniques/Cargo.toml b/substrate/frame/uniques/Cargo.toml index abd456d97556..135292fb4ecd 100644 --- a/substrate/frame/uniques/Cargo.toml +++ b/substrate/frame/uniques/Cargo.toml @@ -17,11 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] diff --git a/substrate/frame/utility/Cargo.toml b/substrate/frame/utility/Cargo.toml index e2d35fc1699f..c9a4432648ea 100644 --- a/substrate/frame/utility/Cargo.toml +++ b/substrate/frame/utility/Cargo.toml @@ -17,18 +17,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -pallet-root-testing = { workspace = true, default-features = true } pallet-collective = { workspace = true, default-features = true } +pallet-root-testing = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } diff --git a/substrate/frame/verify-signature/Cargo.toml b/substrate/frame/verify-signature/Cargo.toml index 3c5fd5e65157..37cc6c0b3065 100644 --- a/substrate/frame/verify-signature/Cargo.toml +++ b/substrate/frame/verify-signature/Cargo.toml @@ -17,10 +17,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } @@ -28,8 +28,8 @@ sp-weights = { features = ["serde"], workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -pallet-root-testing = { workspace = true, default-features = true } pallet-collective = { workspace = true, default-features = true } +pallet-root-testing = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } diff --git a/substrate/frame/vesting/Cargo.toml b/substrate/frame/vesting/Cargo.toml index f896c3962eaa..882ce5f81373 100644 --- a/substrate/frame/vesting/Cargo.toml +++ b/substrate/frame/vesting/Cargo.toml @@ -19,11 +19,11 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = [ "derive", ], workspace = true } -log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } [dev-dependencies] diff --git a/substrate/frame/whitelist/Cargo.toml b/substrate/frame/whitelist/Cargo.toml index a347174ed2eb..68ecc5d0d78e 100644 --- a/substrate/frame/whitelist/Cargo.toml +++ b/substrate/frame/whitelist/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -scale-info = { features = ["derive"], workspace = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-api = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/primitives/api/Cargo.toml b/substrate/primitives/api/Cargo.toml index e0a4d06b2d81..7295adbc11ca 100644 --- a/substrate/primitives/api/Cargo.toml +++ b/substrate/primitives/api/Cargo.toml @@ -17,22 +17,22 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } +docify = { workspace = true } +hash-db = { optional = true, workspace = true, default-features = true } +log = { workspace = true } +scale-info = { features = [ + "derive", +], workspace = true } sp-api-proc-macro = { workspace = true } sp-core = { workspace = true } +sp-externalities = { optional = true, workspace = true } +sp-metadata-ir = { optional = true, workspace = true } sp-runtime = { workspace = true } sp-runtime-interface = { workspace = true } -sp-externalities = { optional = true, workspace = true } -sp-version = { workspace = true } sp-state-machine = { optional = true, workspace = true } sp-trie = { optional = true, workspace = true } -hash-db = { optional = true, workspace = true, default-features = true } +sp-version = { workspace = true } thiserror = { optional = true, workspace = true } -scale-info = { features = [ - "derive", -], workspace = true } -sp-metadata-ir = { optional = true, workspace = true } -log = { workspace = true } -docify = { workspace = true } [dev-dependencies] sp-test-primitives = { workspace = true } diff --git a/substrate/primitives/api/proc-macro/Cargo.toml b/substrate/primitives/api/proc-macro/Cargo.toml index 191578f432ad..2f414597fb74 100644 --- a/substrate/primitives/api/proc-macro/Cargo.toml +++ b/substrate/primitives/api/proc-macro/Cargo.toml @@ -19,13 +19,13 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = { workspace = true } -syn = { features = ["extra-traits", "fold", "full", "visit", "visit-mut"], workspace = true } -proc-macro2 = { workspace = true } +Inflector = { workspace = true } blake2 = { workspace = true } -proc-macro-crate = { workspace = true } expander = { workspace = true } -Inflector = { workspace = true } +proc-macro-crate = { workspace = true } +proc-macro2 = { workspace = true } +quote = { workspace = true } +syn = { features = ["extra-traits", "fold", "full", "visit", "visit-mut"], workspace = true } [dev-dependencies] assert_matches = { workspace = true } diff --git a/substrate/primitives/api/test/Cargo.toml b/substrate/primitives/api/test/Cargo.toml index 27f6dafa24bf..9b02cf125eae 100644 --- a/substrate/primitives/api/test/Cargo.toml +++ b/substrate/primitives/api/test/Cargo.toml @@ -15,19 +15,19 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { workspace = true, default-features = true } +rustversion = { workspace = true } +sc-block-builder = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } sp-api = { workspace = true, default-features = true } -substrate-test-runtime-client = { workspace = true } -sp-version = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-metadata-ir = { workspace = true, default-features = true } -sc-block-builder = { workspace = true, default-features = true } -codec = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } +substrate-test-runtime-client = { workspace = true } trybuild = { workspace = true } -rustversion = { workspace = true } -scale-info = { features = ["derive"], workspace = true } [dev-dependencies] criterion = { workspace = true, default-features = true } diff --git a/substrate/primitives/application-crypto/Cargo.toml b/substrate/primitives/application-crypto/Cargo.toml index 1161d43ded5a..9589cce042f5 100644 --- a/substrate/primitives/application-crypto/Cargo.toml +++ b/substrate/primitives/application-crypto/Cargo.toml @@ -18,10 +18,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { workspace = true } codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, features = ["alloc", "derive"], workspace = true } +sp-core = { workspace = true } sp-io = { workspace = true } [features] diff --git a/substrate/primitives/arithmetic/Cargo.toml b/substrate/primitives/arithmetic/Cargo.toml index 485656bf30bb..77b82fbe6468 100644 --- a/substrate/primitives/arithmetic/Cargo.toml +++ b/substrate/primitives/arithmetic/Cargo.toml @@ -21,18 +21,18 @@ codec = { features = [ "derive", "max-encoded-len", ], workspace = true } +docify = { workspace = true } integer-sqrt = { workspace = true } num-traits = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], optional = true, workspace = true } static_assertions = { workspace = true, default-features = true } -docify = { workspace = true } [dev-dependencies] criterion = { workspace = true, default-features = true } primitive-types = { workspace = true, default-features = true } -sp-crypto-hashing = { workspace = true, default-features = true } rand = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/primitives/blockchain/Cargo.toml b/substrate/primitives/blockchain/Cargo.toml index 93158274d98f..aed09a684bda 100644 --- a/substrate/primitives/blockchain/Cargo.toml +++ b/substrate/primitives/blockchain/Cargo.toml @@ -21,11 +21,11 @@ codec = { features = ["derive"], workspace = true } futures = { workspace = true } parking_lot = { workspace = true, default-features = true } schnellru = { workspace = true } -thiserror = { workspace = true } sp-api = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-database = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } +thiserror = { workspace = true } tracing = { workspace = true, default-features = true } diff --git a/substrate/primitives/consensus/beefy/Cargo.toml b/substrate/primitives/consensus/beefy/Cargo.toml index 13d80683c853..572e46d8de8d 100644 --- a/substrate/primitives/consensus/beefy/Cargo.toml +++ b/substrate/primitives/consensus/beefy/Cargo.toml @@ -23,9 +23,9 @@ sp-application-crypto = { workspace = true } sp-core = { workspace = true } sp-crypto-hashing = { workspace = true } sp-io = { workspace = true } +sp-keystore = { workspace = true } sp-mmr-primitives = { workspace = true } sp-runtime = { workspace = true } -sp-keystore = { workspace = true } sp-weights = { workspace = true } strum = { features = ["derive"], workspace = true } diff --git a/substrate/primitives/consensus/common/Cargo.toml b/substrate/primitives/consensus/common/Cargo.toml index 764ef1d97346..3a6ffd031ec5 100644 --- a/substrate/primitives/consensus/common/Cargo.toml +++ b/substrate/primitives/consensus/common/Cargo.toml @@ -20,11 +20,11 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = { workspace = true } futures = { features = ["thread-pool"], workspace = true } log = { workspace = true, default-features = true } -thiserror = { workspace = true } sp-core = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } +thiserror = { workspace = true } [dev-dependencies] futures = { workspace = true } diff --git a/substrate/primitives/core/Cargo.toml b/substrate/primitives/core/Cargo.toml index f6bc17bccaca..0ea885abd22d 100644 --- a/substrate/primitives/core/Cargo.toml +++ b/substrate/primitives/core/Cargo.toml @@ -16,47 +16,47 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { features = ["derive", "max-encoded-len"], workspace = true } -scale-info = { features = ["derive"], workspace = true } -log = { workspace = true } -serde = { optional = true, features = ["alloc", "derive"], workspace = true } bounded-collections = { workspace = true } -primitive-types = { features = ["codec", "scale-info"], workspace = true } -impl-serde = { optional = true, workspace = true } +bs58 = { optional = true, workspace = true } +codec = { features = ["derive", "max-encoded-len"], workspace = true } hash-db = { workspace = true } hash256-std-hasher = { workspace = true } -bs58 = { optional = true, workspace = true } +impl-serde = { optional = true, workspace = true } +log = { workspace = true } +primitive-types = { features = ["codec", "scale-info"], workspace = true } rand = { features = [ "small_rng", ], optional = true, workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, features = ["alloc", "derive"], workspace = true } substrate-bip39 = { workspace = true } # personal fork here as workaround for: https://github.com/rust-bitcoin/rust-bip39/pull/64 bip39 = { package = "parity-bip39", version = "2.0.1", default-features = false, features = [ "alloc", ] } -zeroize = { workspace = true } -secrecy = { features = ["alloc"], workspace = true } +bitflags = { workspace = true } +dyn-clonable = { optional = true, workspace = true } +futures = { optional = true, workspace = true } +itertools = { optional = true, workspace = true } parking_lot = { optional = true, workspace = true, default-features = true } -ss58-registry = { workspace = true } -sp-std = { workspace = true } +paste = { workspace = true, default-features = true } +secrecy = { features = ["alloc"], workspace = true } sp-debug-derive = { workspace = true } -sp-storage = { workspace = true } sp-externalities = { optional = true, workspace = true } -futures = { optional = true, workspace = true } -dyn-clonable = { optional = true, workspace = true } +sp-std = { workspace = true } +sp-storage = { workspace = true } +ss58-registry = { workspace = true } thiserror = { optional = true, workspace = true } tracing = { optional = true, workspace = true, default-features = true } -bitflags = { workspace = true } -paste = { workspace = true, default-features = true } -itertools = { optional = true, workspace = true } +zeroize = { workspace = true } # full crypto array-bytes = { workspace = true, default-features = true } -ed25519-zebra = { workspace = true } blake2 = { optional = true, workspace = true } +ed25519-zebra = { workspace = true } libsecp256k1 = { features = ["static-context"], workspace = true } -schnorrkel = { features = ["preaudit_deprecated"], workspace = true } merlin = { workspace = true } +schnorrkel = { features = ["preaudit_deprecated"], workspace = true } sp-crypto-hashing = { workspace = true } sp-runtime-interface = { workspace = true } # k256 crate, better portability, intended to be used in substrate-runtimes (no-std) @@ -76,8 +76,8 @@ bandersnatch_vrfs = { git = "https://github.com/w3f/ring-vrf", rev = "0fef826", [dev-dependencies] criterion = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } regex = { workspace = true } +serde_json = { workspace = true, default-features = true } [[bench]] name = "bench" diff --git a/substrate/primitives/crypto/ec-utils/Cargo.toml b/substrate/primitives/crypto/ec-utils/Cargo.toml index 29e30133ebea..1e5964f85575 100644 --- a/substrate/primitives/crypto/ec-utils/Cargo.toml +++ b/substrate/primitives/crypto/ec-utils/Cargo.toml @@ -15,17 +15,17 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -ark-ec = { optional = true, workspace = true } -ark-bls12-377-ext = { optional = true, workspace = true } ark-bls12-377 = { features = ["curve"], optional = true, workspace = true } -ark-bls12-381-ext = { optional = true, workspace = true } +ark-bls12-377-ext = { optional = true, workspace = true } ark-bls12-381 = { features = ["curve"], optional = true, workspace = true } -ark-bw6-761-ext = { optional = true, workspace = true } +ark-bls12-381-ext = { optional = true, workspace = true } ark-bw6-761 = { optional = true, workspace = true } -ark-ed-on-bls12-381-bandersnatch-ext = { optional = true, workspace = true } -ark-ed-on-bls12-381-bandersnatch = { optional = true, workspace = true } -ark-ed-on-bls12-377-ext = { optional = true, workspace = true } +ark-bw6-761-ext = { optional = true, workspace = true } +ark-ec = { optional = true, workspace = true } ark-ed-on-bls12-377 = { optional = true, workspace = true } +ark-ed-on-bls12-377-ext = { optional = true, workspace = true } +ark-ed-on-bls12-381-bandersnatch = { optional = true, workspace = true } +ark-ed-on-bls12-381-bandersnatch-ext = { optional = true, workspace = true } ark-scale = { features = ["hazmat"], optional = true, workspace = true } sp-runtime-interface = { optional = true, workspace = true } diff --git a/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml b/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml index 6f974a3e2c8a..e09661d41c11 100644 --- a/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml +++ b/substrate/primitives/crypto/hashing/proc-macro/Cargo.toml @@ -20,5 +20,5 @@ proc-macro = true [dependencies] quote = { workspace = true } -syn = { features = ["full", "parsing"], workspace = true } sp-crypto-hashing = { workspace = true } +syn = { features = ["full", "parsing"], workspace = true } diff --git a/substrate/primitives/debug-derive/Cargo.toml b/substrate/primitives/debug-derive/Cargo.toml index 4979b89155ab..a26cbbf62ada 100644 --- a/substrate/primitives/debug-derive/Cargo.toml +++ b/substrate/primitives/debug-derive/Cargo.toml @@ -19,9 +19,9 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] +proc-macro2 = { workspace = true } quote = { workspace = true } syn = { workspace = true } -proc-macro2 = { workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/genesis-builder/Cargo.toml b/substrate/primitives/genesis-builder/Cargo.toml index 285b214907ad..f1fa60d023be 100644 --- a/substrate/primitives/genesis-builder/Cargo.toml +++ b/substrate/primitives/genesis-builder/Cargo.toml @@ -19,9 +19,9 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = ["bytes"], workspace = true } scale-info = { features = ["derive"], workspace = true } +serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } sp-api = { workspace = true } sp-runtime = { workspace = true } -serde_json = { features = ["alloc", "arbitrary_precision"], workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/inherents/Cargo.toml b/substrate/primitives/inherents/Cargo.toml index 271308c9cbf1..19966919047f 100644 --- a/substrate/primitives/inherents/Cargo.toml +++ b/substrate/primitives/inherents/Cargo.toml @@ -19,10 +19,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { optional = true, workspace = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } impl-trait-for-tuples = { workspace = true } -thiserror = { optional = true, workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { optional = true, workspace = true } +thiserror = { optional = true, workspace = true } [dev-dependencies] futures = { workspace = true } diff --git a/substrate/primitives/io/Cargo.toml b/substrate/primitives/io/Cargo.toml index 97940759a987..b0c99002910b 100644 --- a/substrate/primitives/io/Cargo.toml +++ b/substrate/primitives/io/Cargo.toml @@ -22,20 +22,20 @@ bytes = { workspace = true } codec = { features = [ "bytes", ], workspace = true } -sp-core = { workspace = true } -sp-crypto-hashing = { workspace = true } -sp-keystore = { optional = true, workspace = true } libsecp256k1 = { optional = true, workspace = true, default-features = true } -sp-state-machine = { optional = true, workspace = true } -sp-runtime-interface = { workspace = true } -sp-trie = { optional = true, workspace = true } -sp-externalities = { workspace = true } -sp-tracing = { workspace = true } log = { optional = true, workspace = true, default-features = true } secp256k1 = { features = [ "global-context", "recovery", ], optional = true, workspace = true, default-features = true } +sp-core = { workspace = true } +sp-crypto-hashing = { workspace = true } +sp-externalities = { workspace = true } +sp-keystore = { optional = true, workspace = true } +sp-runtime-interface = { workspace = true } +sp-state-machine = { optional = true, workspace = true } +sp-tracing = { workspace = true } +sp-trie = { optional = true, workspace = true } tracing = { workspace = true } tracing-core = { workspace = true } diff --git a/substrate/primitives/keyring/Cargo.toml b/substrate/primitives/keyring/Cargo.toml index 27f7304a9358..9ffcf50c7b45 100644 --- a/substrate/primitives/keyring/Cargo.toml +++ b/substrate/primitives/keyring/Cargo.toml @@ -17,9 +17,9 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -strum = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } +strum = { features = ["derive"], workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/merkle-mountain-range/Cargo.toml b/substrate/primitives/merkle-mountain-range/Cargo.toml index 6f944a3f6a8d..5f861ca7acf1 100644 --- a/substrate/primitives/merkle-mountain-range/Cargo.toml +++ b/substrate/primitives/merkle-mountain-range/Cargo.toml @@ -16,9 +16,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -scale-info = { features = ["derive"], workspace = true } log = { workspace = true } mmr-lib = { package = "polkadot-ckb-merkle-mountain-range", version = "0.7.0", default-features = false } +scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-api = { workspace = true } sp-core = { workspace = true } diff --git a/substrate/primitives/runtime-interface/Cargo.toml b/substrate/primitives/runtime-interface/Cargo.toml index ee44d90fa959..2d82838ca0b3 100644 --- a/substrate/primitives/runtime-interface/Cargo.toml +++ b/substrate/primitives/runtime-interface/Cargo.toml @@ -18,26 +18,26 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] bytes = { workspace = true } -sp-wasm-interface = { workspace = true } -sp-std = { workspace = true } -sp-tracing = { workspace = true } -sp-runtime-interface-proc-macro = { workspace = true, default-features = true } -sp-externalities = { workspace = true } codec = { features = ["bytes"], workspace = true } -static_assertions = { workspace = true, default-features = true } +impl-trait-for-tuples = { workspace = true } primitive-types = { workspace = true } +sp-externalities = { workspace = true } +sp-runtime-interface-proc-macro = { workspace = true, default-features = true } +sp-std = { workspace = true } sp-storage = { workspace = true } -impl-trait-for-tuples = { workspace = true } +sp-tracing = { workspace = true } +sp-wasm-interface = { workspace = true } +static_assertions = { workspace = true, default-features = true } [target.'cfg(all(any(target_arch = "riscv32", target_arch = "riscv64"), substrate_runtime))'.dependencies] polkavm-derive = { workspace = true } [dev-dependencies] -sp-runtime-interface-test-wasm = { workspace = true } -sp-state-machine = { workspace = true, default-features = true } +rustversion = { workspace = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -rustversion = { workspace = true } +sp-runtime-interface-test-wasm = { workspace = true } +sp-state-machine = { workspace = true, default-features = true } trybuild = { workspace = true } [features] diff --git a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml index 3fd5f073f025..2112d5bc0693 100644 --- a/substrate/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/substrate/primitives/runtime-interface/proc-macro/Cargo.toml @@ -20,8 +20,8 @@ proc-macro = true [dependencies] Inflector = { workspace = true } +expander = { workspace = true } proc-macro-crate = { workspace = true } proc-macro2 = { workspace = true } quote = { workspace = true } -expander = { workspace = true } syn = { features = ["extra-traits", "fold", "full", "visit"], workspace = true } diff --git a/substrate/primitives/runtime-interface/test/Cargo.toml b/substrate/primitives/runtime-interface/test/Cargo.toml index 29ef0f6b4892..ebcf4222bda3 100644 --- a/substrate/primitives/runtime-interface/test/Cargo.toml +++ b/substrate/primitives/runtime-interface/test/Cargo.toml @@ -15,8 +15,6 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -tracing = { workspace = true, default-features = true } -tracing-core = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-executor-common = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } @@ -25,3 +23,5 @@ sp-runtime-interface = { workspace = true, default-features = true } sp-runtime-interface-test-wasm = { workspace = true } sp-runtime-interface-test-wasm-deprecated = { workspace = true } sp-state-machine = { workspace = true, default-features = true } +tracing = { workspace = true, default-features = true } +tracing-core = { workspace = true, default-features = true } diff --git a/substrate/primitives/runtime/Cargo.toml b/substrate/primitives/runtime/Cargo.toml index 8a812c3a5772..89c221d574fc 100644 --- a/substrate/primitives/runtime/Cargo.toml +++ b/substrate/primitives/runtime/Cargo.toml @@ -17,7 +17,9 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +binary-merkle-tree = { workspace = true } codec = { features = ["derive", "max-encoded-len"], workspace = true } +docify = { workspace = true } either = { workspace = true } hash256-std-hasher = { workspace = true } impl-trait-for-tuples = { workspace = true } @@ -34,9 +36,7 @@ sp-io = { workspace = true } sp-std = { workspace = true } sp-trie = { workspace = true } sp-weights = { workspace = true } -docify = { workspace = true } tracing = { workspace = true, features = ["log"], default-features = false } -binary-merkle-tree = { workspace = true } simple-mermaid = { version = "0.1.1", optional = true } tuplex = { version = "0.1.2", default-features = false } @@ -44,11 +44,11 @@ tuplex = { version = "0.1.2", default-features = false } [dev-dependencies] rand = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -zstd = { workspace = true } sp-api = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +zstd = { workspace = true } [features] runtime-benchmarks = [] diff --git a/substrate/primitives/session/Cargo.toml b/substrate/primitives/session/Cargo.toml index 6abf83505530..72be81c1222e 100644 --- a/substrate/primitives/session/Cargo.toml +++ b/substrate/primitives/session/Cargo.toml @@ -20,9 +20,9 @@ codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } sp-api = { workspace = true } sp-core = { workspace = true } +sp-keystore = { optional = true, workspace = true } sp-runtime = { optional = true, workspace = true } sp-staking = { workspace = true } -sp-keystore = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/staking/Cargo.toml b/substrate/primitives/staking/Cargo.toml index 35e7e4f60413..42694cdbb674 100644 --- a/substrate/primitives/staking/Cargo.toml +++ b/substrate/primitives/staking/Cargo.toml @@ -16,10 +16,10 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { features = ["alloc", "derive"], optional = true, workspace = true } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } impl-trait-for-tuples = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } diff --git a/substrate/primitives/state-machine/Cargo.toml b/substrate/primitives/state-machine/Cargo.toml index e1c67feb7ac5..5bc06b8cb509 100644 --- a/substrate/primitives/state-machine/Cargo.toml +++ b/substrate/primitives/state-machine/Cargo.toml @@ -17,28 +17,28 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +arbitrary = { features = ["derive"], optional = true, workspace = true } codec = { workspace = true } hash-db = { workspace = true } log = { workspace = true } parking_lot = { optional = true, workspace = true, default-features = true } rand = { optional = true, workspace = true, default-features = true } smallvec = { workspace = true, default-features = true } -thiserror = { optional = true, workspace = true } -tracing = { optional = true, workspace = true, default-features = true } sp-core = { workspace = true } sp-externalities = { workspace = true } sp-panic-handler = { optional = true, workspace = true, default-features = true } sp-trie = { workspace = true } +thiserror = { optional = true, workspace = true } +tracing = { optional = true, workspace = true, default-features = true } trie-db = { workspace = true } -arbitrary = { features = ["derive"], optional = true, workspace = true } [dev-dependencies] +arbitrary = { features = ["derive"], workspace = true } array-bytes = { workspace = true, default-features = true } +assert_matches = { workspace = true } pretty_assertions = { workspace = true } rand = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -assert_matches = { workspace = true } -arbitrary = { features = ["derive"], workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/state-machine/fuzz/Cargo.toml b/substrate/primitives/state-machine/fuzz/Cargo.toml index 416c00c34fda..16bf5b92025f 100644 --- a/substrate/primitives/state-machine/fuzz/Cargo.toml +++ b/substrate/primitives/state-machine/fuzz/Cargo.toml @@ -13,8 +13,8 @@ libfuzzer-sys = "0.4" sp-runtime = { path = "../../runtime" } [dependencies.sp-state-machine] -path = ".." features = ["fuzzing"] +path = ".." # Prevent this from interfering with workspaces [workspace] diff --git a/substrate/primitives/statement-store/Cargo.toml b/substrate/primitives/statement-store/Cargo.toml index aac676caedc9..df66cfcfc2e6 100644 --- a/substrate/primitives/statement-store/Cargo.toml +++ b/substrate/primitives/statement-store/Cargo.toml @@ -18,23 +18,23 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } +sp-api = { workspace = true } +sp-application-crypto = { workspace = true } sp-core = { workspace = true } sp-crypto-hashing = { workspace = true } +sp-externalities = { workspace = true } sp-runtime = { workspace = true } -sp-api = { workspace = true } -sp-application-crypto = { workspace = true } sp-runtime-interface = { workspace = true } -sp-externalities = { workspace = true } thiserror = { optional = true, workspace = true } # ECIES dependencies -ed25519-dalek = { optional = true, workspace = true, default-features = true } -x25519-dalek = { optional = true, features = ["static_secrets"], workspace = true } -curve25519-dalek = { optional = true, workspace = true } aes-gcm = { optional = true, workspace = true } +curve25519-dalek = { optional = true, workspace = true } +ed25519-dalek = { optional = true, workspace = true, default-features = true } hkdf = { optional = true, workspace = true } -sha2 = { optional = true, workspace = true, default-features = true } rand = { features = ["small_rng"], optional = true, workspace = true, default-features = true } +sha2 = { optional = true, workspace = true, default-features = true } +x25519-dalek = { optional = true, features = ["static_secrets"], workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/timestamp/Cargo.toml b/substrate/primitives/timestamp/Cargo.toml index 0fcd5be98e6f..619f1eaa142b 100644 --- a/substrate/primitives/timestamp/Cargo.toml +++ b/substrate/primitives/timestamp/Cargo.toml @@ -18,9 +18,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { optional = true, workspace = true } codec = { features = ["derive"], workspace = true } -thiserror = { optional = true, workspace = true } sp-inherents = { workspace = true } sp-runtime = { workspace = true } +thiserror = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/trie/Cargo.toml b/substrate/primitives/trie/Cargo.toml index 7f27bb097290..65a9727ed2ae 100644 --- a/substrate/primitives/trie/Cargo.toml +++ b/substrate/primitives/trie/Cargo.toml @@ -29,20 +29,20 @@ nohash-hasher = { optional = true, workspace = true } parking_lot = { optional = true, workspace = true, default-features = true } rand = { optional = true, workspace = true, default-features = true } scale-info = { features = ["derive"], workspace = true } +schnellru = { optional = true, workspace = true } +sp-core = { workspace = true } +sp-externalities = { workspace = true } thiserror = { optional = true, workspace = true } tracing = { optional = true, workspace = true, default-features = true } trie-db = { workspace = true } trie-root = { workspace = true } -sp-core = { workspace = true } -sp-externalities = { workspace = true } -schnellru = { optional = true, workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } criterion = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } trie-bench = { workspace = true } trie-standardmap = { workspace = true } -sp-runtime = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/primitives/version/Cargo.toml b/substrate/primitives/version/Cargo.toml index 0424304989b7..7fa983d02823 100644 --- a/substrate/primitives/version/Cargo.toml +++ b/substrate/primitives/version/Cargo.toml @@ -22,11 +22,11 @@ impl-serde = { optional = true, workspace = true } parity-wasm = { optional = true, workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], optional = true, workspace = true } -thiserror = { optional = true, workspace = true } sp-crypto-hashing-proc-macro = { workspace = true, default-features = true } sp-runtime = { workspace = true } sp-std = { workspace = true } sp-version-proc-macro = { workspace = true } +thiserror = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/wasm-interface/Cargo.toml b/substrate/primitives/wasm-interface/Cargo.toml index 9d0310fd22e8..9f8eea5102d6 100644 --- a/substrate/primitives/wasm-interface/Cargo.toml +++ b/substrate/primitives/wasm-interface/Cargo.toml @@ -17,11 +17,11 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +anyhow = { optional = true, workspace = true } codec = { features = ["derive"], workspace = true } impl-trait-for-tuples = { workspace = true } log = { optional = true, workspace = true, default-features = true } wasmtime = { optional = true, workspace = true } -anyhow = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/primitives/weights/Cargo.toml b/substrate/primitives/weights/Cargo.toml index c4e1897dbb8e..9cd0d9ac2e20 100644 --- a/substrate/primitives/weights/Cargo.toml +++ b/substrate/primitives/weights/Cargo.toml @@ -19,11 +19,11 @@ targets = ["x86_64-unknown-linux-gnu"] bounded-collections = { workspace = true } codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } +schemars = { optional = true, workspace = true } serde = { optional = true, features = ["alloc", "derive"], workspace = true } smallvec = { workspace = true, default-features = true } sp-arithmetic = { workspace = true } sp-debug-derive = { workspace = true } -schemars = { optional = true, workspace = true } [features] default = ["std"] diff --git a/substrate/scripts/ci/node-template-release/Cargo.toml b/substrate/scripts/ci/node-template-release/Cargo.toml index d335dbcf3971..5b90044d44dd 100644 --- a/substrate/scripts/ci/node-template-release/Cargo.toml +++ b/substrate/scripts/ci/node-template-release/Cargo.toml @@ -18,7 +18,7 @@ clap = { features = ["derive"], workspace = true } flate2 = { workspace = true } fs_extra = { workspace = true } glob = { workspace = true } +itertools = { workspace = true } tar = { workspace = true } tempfile = { workspace = true } toml_edit = { workspace = true } -itertools = { workspace = true } diff --git a/substrate/test-utils/Cargo.toml b/substrate/test-utils/Cargo.toml index 4f7a70906859..87c9cb731e3a 100644 --- a/substrate/test-utils/Cargo.toml +++ b/substrate/test-utils/Cargo.toml @@ -20,5 +20,5 @@ futures = { workspace = true } tokio = { features = ["macros", "time"], workspace = true, default-features = true } [dev-dependencies] -trybuild = { features = ["diff"], workspace = true } sc-service = { workspace = true, default-features = true } +trybuild = { features = ["diff"], workspace = true } diff --git a/substrate/test-utils/cli/Cargo.toml b/substrate/test-utils/cli/Cargo.toml index 3fbcf2090683..b11e67bc49bc 100644 --- a/substrate/test-utils/cli/Cargo.toml +++ b/substrate/test-utils/cli/Cargo.toml @@ -16,17 +16,17 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -substrate-rpc-client = { workspace = true, default-features = true } -sp-rpc = { workspace = true, default-features = true } assert_cmd = { workspace = true } +futures = { workspace = true } nix = { features = ["signal"], workspace = true } -regex = { workspace = true } -tokio = { features = ["full"], workspace = true, default-features = true } -node-primitives = { workspace = true, default-features = true } node-cli = { workspace = true } +node-primitives = { workspace = true, default-features = true } +regex = { workspace = true } sc-cli = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -futures = { workspace = true } +sp-rpc = { workspace = true, default-features = true } +substrate-rpc-client = { workspace = true, default-features = true } +tokio = { features = ["full"], workspace = true, default-features = true } [features] try-runtime = ["node-cli/try-runtime"] diff --git a/substrate/test-utils/client/Cargo.toml b/substrate/test-utils/client/Cargo.toml index a67c91fc5f79..e7ab4c8c8367 100644 --- a/substrate/test-utils/client/Cargo.toml +++ b/substrate/test-utils/client/Cargo.toml @@ -20,8 +20,6 @@ array-bytes = { workspace = true, default-features = true } async-trait = { workspace = true } codec = { workspace = true, default-features = true } futures = { workspace = true } -serde = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } sc-client-db = { features = [ "test-helpers", @@ -30,6 +28,8 @@ sc-consensus = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-offchain = { workspace = true, default-features = true } sc-service = { workspace = true } +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } diff --git a/substrate/test-utils/runtime/Cargo.toml b/substrate/test-utils/runtime/Cargo.toml index 96a888052876..7af692b437f6 100644 --- a/substrate/test-utils/runtime/Cargo.toml +++ b/substrate/test-utils/runtime/Cargo.toml @@ -16,43 +16,43 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { features = ["derive"], workspace = true } +frame-executive = { workspace = true } +frame-metadata-hash-extension = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +frame-system-rpc-runtime-api = { workspace = true } +pallet-babe = { workspace = true } +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } +sc-service = { optional = true, workspace = true } +scale-info = { features = ["derive"], workspace = true } +sp-api = { workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } +sp-block-builder = { workspace = true } sp-consensus-aura = { features = ["serde"], workspace = true } sp-consensus-babe = { features = ["serde"], workspace = true } +sp-consensus-grandpa = { features = ["serde"], workspace = true } +sp-core = { features = ["serde"], workspace = true } +sp-crypto-hashing = { workspace = true } +sp-externalities = { workspace = true } sp-genesis-builder = { workspace = true } -sp-block-builder = { workspace = true } -codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } sp-inherents = { workspace = true } +sp-io = { workspace = true } sp-keyring = { workspace = true } sp-offchain = { workspace = true } -sp-core = { features = ["serde"], workspace = true } -sp-crypto-hashing = { workspace = true } -sp-io = { workspace = true } -frame-support = { workspace = true } -sp-version = { workspace = true } -sp-session = { workspace = true } -sp-api = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } -pallet-babe = { workspace = true } -pallet-balances = { workspace = true } -frame-executive = { workspace = true } -frame-metadata-hash-extension = { workspace = true } -frame-system = { workspace = true } -frame-system-rpc-runtime-api = { workspace = true } -pallet-timestamp = { workspace = true } -sp-consensus-grandpa = { features = ["serde"], workspace = true } -sp-trie = { workspace = true } +sp-session = { workspace = true } +sp-state-machine = { workspace = true } sp-transaction-pool = { workspace = true } +sp-trie = { workspace = true } +sp-version = { workspace = true } trie-db = { workspace = true } -sc-service = { optional = true, workspace = true } -sp-state-machine = { workspace = true } -sp-externalities = { workspace = true } # 3rd party array-bytes = { optional = true, workspace = true, default-features = true } -serde_json = { workspace = true, features = ["alloc"] } log = { workspace = true } +serde_json = { workspace = true, features = ["alloc"] } tracing = { workspace = true, default-features = false } [dev-dependencies] @@ -61,11 +61,11 @@ sc-block-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-executor-common = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -substrate-test-runtime-client = { workspace = true } -sp-tracing = { workspace = true, default-features = true } serde = { features = ["alloc", "derive"], workspace = true } serde_json = { features = ["alloc"], workspace = true } +sp-consensus = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } +substrate-test-runtime-client = { workspace = true } [build-dependencies] substrate-wasm-builder = { optional = true, features = ["metadata-hash"], workspace = true, default-features = true } diff --git a/substrate/test-utils/runtime/transaction-pool/Cargo.toml b/substrate/test-utils/runtime/transaction-pool/Cargo.toml index 3cdaea642263..501c9f99ebf1 100644 --- a/substrate/test-utils/runtime/transaction-pool/Cargo.toml +++ b/substrate/test-utils/runtime/transaction-pool/Cargo.toml @@ -19,9 +19,9 @@ codec = { workspace = true, default-features = true } futures = { workspace = true } log = { workspace = true } parking_lot = { workspace = true, default-features = true } -thiserror = { workspace = true } sc-transaction-pool = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +thiserror = { workspace = true } diff --git a/substrate/utils/binary-merkle-tree/Cargo.toml b/substrate/utils/binary-merkle-tree/Cargo.toml index 9577d94ef0bf..86d64face80e 100644 --- a/substrate/utils/binary-merkle-tree/Cargo.toml +++ b/substrate/utils/binary-merkle-tree/Cargo.toml @@ -12,16 +12,16 @@ homepage.workspace = true workspace = true [dependencies] -codec = { workspace = true, features = ["derive"] } array-bytes = { optional = true, workspace = true, default-features = true } -log = { optional = true, workspace = true } +codec = { workspace = true, features = ["derive"] } hash-db = { workspace = true } +log = { optional = true, workspace = true } [dev-dependencies] array-bytes = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } [features] debug = ["array-bytes", "log"] diff --git a/substrate/utils/frame/benchmarking-cli/Cargo.toml b/substrate/utils/frame/benchmarking-cli/Cargo.toml index 6d86346ee180..c38a7e4f77d8 100644 --- a/substrate/utils/frame/benchmarking-cli/Cargo.toml +++ b/substrate/utils/frame/benchmarking-cli/Cargo.toml @@ -16,25 +16,27 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +Inflector = { workspace = true } array-bytes = { workspace = true, default-features = true } chrono = { workspace = true } clap = { features = ["derive"], workspace = true } codec = { workspace = true, default-features = true } comfy-table = { workspace = true } +cumulus-client-parachain-inherent = { workspace = true, default-features = true } +cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } +frame-benchmarking = { workspace = true, default-features = true } +frame-support = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } +gethostname = { workspace = true } handlebars = { workspace = true } -Inflector = { workspace = true } +hex = { workspace = true, default-features = true } itertools = { workspace = true } linked-hash-map = { workspace = true } log = { workspace = true, default-features = true } +polkadot-parachain-primitives = { workspace = true, default-features = true } +polkadot-primitives = { workspace = true, default-features = true } rand = { features = ["small_rng"], workspace = true, default-features = true } rand_pcg = { workspace = true } -serde = { workspace = true, default-features = true } -serde_json = { workspace = true, default-features = true } -thiserror = { workspace = true } -thousands = { workspace = true } -frame-benchmarking = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } sc-block-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true } sc-cli = { workspace = true } @@ -42,36 +44,34 @@ sc-client-api = { workspace = true, default-features = true } sc-client-db = { workspace = true } sc-executor = { workspace = true, default-features = true } sc-executor-common = { workspace = true } +sc-runtime-utilities = { workspace = true, default-features = true } sc-service = { workspace = true } sc-sysinfo = { workspace = true, default-features = true } -sc-runtime-utilities = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } +serde_json = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } +sp-block-builder = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +sp-crypto-hashing = { workspace = true, default-features = true } sp-database = { workspace = true, default-features = true } sp-externalities = { workspace = true, default-features = true } sp-genesis-builder = { workspace = true, default-features = true } sp-inherents = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } -sp-crypto-hashing = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-state-machine = { workspace = true, default-features = true } sp-storage = { workspace = true, default-features = true } -sp-trie = { workspace = true, default-features = true } -sp-block-builder = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } sp-transaction-pool = { workspace = true, default-features = true } +sp-trie = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } sp-wasm-interface = { workspace = true, default-features = true } subxt = { workspace = true, features = ["native"] } subxt-signer = { workspace = true, features = ["unstable-eth"] } -cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } -cumulus-client-parachain-inherent = { workspace = true, default-features = true } -polkadot-parachain-primitives = { workspace = true, default-features = true } -polkadot-primitives = { workspace = true, default-features = true } -gethostname = { workspace = true } -hex = { workspace = true, default-features = true } +thiserror = { workspace = true } +thousands = { workspace = true } [dev-dependencies] cumulus-test-runtime = { workspace = true, default-features = true } diff --git a/substrate/utils/frame/generate-bags/Cargo.toml b/substrate/utils/frame/generate-bags/Cargo.toml index c37c42646699..c03f85ece05d 100644 --- a/substrate/utils/frame/generate-bags/Cargo.toml +++ b/substrate/utils/frame/generate-bags/Cargo.toml @@ -13,8 +13,8 @@ workspace = true [dependencies] # FRAME -frame-support = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } +frame-support = { workspace = true, default-features = true } frame-system = { workspace = true, default-features = true } pallet-staking = { workspace = true, default-features = true } sp-staking = { workspace = true, default-features = true } diff --git a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml index 3d5748647257..aace0f4ad23f 100644 --- a/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/substrate/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -13,8 +13,8 @@ publish = false workspace = true [dependencies] -kitchensink-runtime = { workspace = true } generate-bags = { workspace = true, default-features = true } +kitchensink-runtime = { workspace = true } # third-party clap = { features = ["derive"], workspace = true } diff --git a/substrate/utils/frame/omni-bencher/Cargo.toml b/substrate/utils/frame/omni-bencher/Cargo.toml index 345a7288d45b..d0d7f1a3428f 100644 --- a/substrate/utils/frame/omni-bencher/Cargo.toml +++ b/substrate/utils/frame/omni-bencher/Cargo.toml @@ -15,16 +15,16 @@ workspace = true clap = { features = ["derive"], workspace = true } cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } frame-benchmarking-cli = { workspace = true } +log = { workspace = true } sc-cli = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-statement-store = { workspace = true, default-features = true } tracing-subscriber = { workspace = true } -log = { workspace = true } [dev-dependencies] -tempfile = { workspace = true } assert_cmd = { workspace = true } cumulus-test-runtime = { workspace = true } -sp-tracing = { workspace = true, default-features = true } -sp-genesis-builder = { workspace = true, default-features = true } sc-chain-spec = { workspace = true } +sp-genesis-builder = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } +tempfile = { workspace = true } diff --git a/substrate/utils/frame/remote-externalities/Cargo.toml b/substrate/utils/frame/remote-externalities/Cargo.toml index 41a0091027c1..4ed0e1edf3e4 100644 --- a/substrate/utils/frame/remote-externalities/Cargo.toml +++ b/substrate/utils/frame/remote-externalities/Cargo.toml @@ -15,20 +15,20 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { features = ["http-client"], workspace = true } codec = { workspace = true, default-features = true } +futures = { workspace = true } +indicatif = { workspace = true } +jsonrpsee = { features = ["http-client"], workspace = true } log = { workspace = true, default-features = true } serde = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } -sp-state-machine = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } -substrate-rpc-client = { workspace = true, default-features = true } -futures = { workspace = true } -indicatif = { workspace = true } +sp-state-machine = { workspace = true, default-features = true } spinners = { workspace = true } +substrate-rpc-client = { workspace = true, default-features = true } +tokio = { features = ["macros", "rt-multi-thread"], workspace = true, default-features = true } tokio-retry = { workspace = true } [dev-dependencies] diff --git a/substrate/utils/frame/rpc/client/Cargo.toml b/substrate/utils/frame/rpc/client/Cargo.toml index d26be3a13124..6282621e1c75 100644 --- a/substrate/utils/frame/rpc/client/Cargo.toml +++ b/substrate/utils/frame/rpc/client/Cargo.toml @@ -15,13 +15,13 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = { workspace = true } jsonrpsee = { features = ["ws-client"], workspace = true } +log = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } -async-trait = { workspace = true } serde = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -log = { workspace = true, default-features = true } [dev-dependencies] -tokio = { features = ["macros", "rt-multi-thread", "sync"], workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } +tokio = { features = ["macros", "rt-multi-thread", "sync"], workspace = true, default-features = true } diff --git a/substrate/utils/frame/rpc/support/Cargo.toml b/substrate/utils/frame/rpc/support/Cargo.toml index 82652c8fa262..45b2bc6fa9b3 100644 --- a/substrate/utils/frame/rpc/support/Cargo.toml +++ b/substrate/utils/frame/rpc/support/Cargo.toml @@ -16,16 +16,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true, default-features = true } -jsonrpsee = { features = ["jsonrpsee-types"], workspace = true } -serde = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } +jsonrpsee = { features = ["jsonrpsee-types"], workspace = true } sc-rpc-api = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true } sp-storage = { workspace = true, default-features = true } [dev-dependencies] -scale-info = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } jsonrpsee = { features = ["jsonrpsee-types", "ws-client"], workspace = true } -tokio = { workspace = true, default-features = true } +scale-info = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/utils/frame/rpc/system/Cargo.toml b/substrate/utils/frame/rpc/system/Cargo.toml index 5757a48498c7..68dfbb833c6f 100644 --- a/substrate/utils/frame/rpc/system/Cargo.toml +++ b/substrate/utils/frame/rpc/system/Cargo.toml @@ -16,16 +16,16 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -futures = { workspace = true } codec = { workspace = true, default-features = true } docify = { workspace = true } +frame-system-rpc-runtime-api = { workspace = true, default-features = true } +futures = { workspace = true } jsonrpsee = { features = [ "client-core", "macros", "server-core", ], workspace = true } log = { workspace = true, default-features = true } -frame-system-rpc-runtime-api = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } @@ -35,8 +35,8 @@ sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } [dev-dependencies] -sc-transaction-pool = { workspace = true, default-features = true } -tokio = { workspace = true, default-features = true } assert_matches = { workspace = true } +sc-transaction-pool = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } +tokio = { workspace = true, default-features = true } diff --git a/substrate/utils/wasm-builder/Cargo.toml b/substrate/utils/wasm-builder/Cargo.toml index fb15e8619a38..6645dd1803bf 100644 --- a/substrate/utils/wasm-builder/Cargo.toml +++ b/substrate/utils/wasm-builder/Cargo.toml @@ -18,28 +18,28 @@ targets = ["x86_64-unknown-linux-gnu"] build-helper = { workspace = true } cargo_metadata = { workspace = true } console = { workspace = true } +filetime = { workspace = true } +jobserver = { workspace = true } +parity-wasm = { workspace = true } +polkavm-linker = { workspace = true } +sp-maybe-compressed-blob = { workspace = true, default-features = true } strum = { features = ["derive"], workspace = true, default-features = true } tempfile = { workspace = true } toml = { workspace = true } walkdir = { workspace = true } -sp-maybe-compressed-blob = { workspace = true, default-features = true } -filetime = { workspace = true } wasm-opt = { workspace = true } -parity-wasm = { workspace = true } -polkavm-linker = { workspace = true } -jobserver = { workspace = true } # Dependencies required for the `metadata-hash` feature. +array-bytes = { optional = true, workspace = true, default-features = true } +codec = { optional = true, workspace = true, default-features = true } +frame-metadata = { features = ["current", "unstable"], optional = true, workspace = true, default-features = true } merkleized-metadata = { optional = true, workspace = true } sc-executor = { optional = true, workspace = true, default-features = true } +shlex = { workspace = true } sp-core = { optional = true, workspace = true, default-features = true } sp-io = { optional = true, workspace = true, default-features = true } -sp-version = { optional = true, workspace = true, default-features = true } -frame-metadata = { features = ["current", "unstable"], optional = true, workspace = true, default-features = true } -codec = { optional = true, workspace = true, default-features = true } -array-bytes = { optional = true, workspace = true, default-features = true } sp-tracing = { optional = true, workspace = true, default-features = true } -shlex = { workspace = true } +sp-version = { optional = true, workspace = true, default-features = true } [features] # Enable support for generating the metadata hash. diff --git a/templates/minimal/node/Cargo.toml b/templates/minimal/node/Cargo.toml index 956efca34532..a2a999f02671 100644 --- a/templates/minimal/node/Cargo.toml +++ b/templates/minimal/node/Cargo.toml @@ -14,15 +14,15 @@ build = "build.rs" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -docify = { workspace = true } clap = { features = ["derive"], workspace = true } +docify = { workspace = true } futures = { features = ["thread-pool"], workspace = true } futures-timer = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } serde_json = { workspace = true, default-features = true } -polkadot-sdk = { workspace = true, features = ["experimental", "node"] } minimal-template-runtime = { workspace = true } +polkadot-sdk = { workspace = true, features = ["experimental", "node"] } [build-dependencies] polkadot-sdk = { workspace = true, features = ["substrate-build-script-utils"] } diff --git a/templates/minimal/pallets/template/Cargo.toml b/templates/minimal/pallets/template/Cargo.toml index 9a02d4daeaac..e11ce0e9955c 100644 --- a/templates/minimal/pallets/template/Cargo.toml +++ b/templates/minimal/pallets/template/Cargo.toml @@ -14,11 +14,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } polkadot-sdk = { workspace = true, default-features = false, features = [ "experimental", "runtime", ] } +scale-info = { features = ["derive"], workspace = true } [features] diff --git a/templates/minimal/runtime/Cargo.toml b/templates/minimal/runtime/Cargo.toml index b803c74539ef..1554e92c0bf5 100644 --- a/templates/minimal/runtime/Cargo.toml +++ b/templates/minimal/runtime/Cargo.toml @@ -11,7 +11,6 @@ publish = false [dependencies] codec = { workspace = true } -scale-info = { workspace = true } polkadot-sdk = { workspace = true, features = [ "pallet-balances", "pallet-sudo", @@ -20,6 +19,7 @@ polkadot-sdk = { workspace = true, features = [ "pallet-transaction-payment-rpc-runtime-api", "runtime", ] } +scale-info = { workspace = true } serde_json = { workspace = true, default-features = false, features = ["alloc"] } # local pallet templates diff --git a/templates/parachain/node/Cargo.toml b/templates/parachain/node/Cargo.toml index ba5f1212b79c..ec4b13b184fc 100644 --- a/templates/parachain/node/Cargo.toml +++ b/templates/parachain/node/Cargo.toml @@ -12,22 +12,22 @@ build = "build.rs" [dependencies] clap = { features = ["derive"], workspace = true } -log = { workspace = true, default-features = true } codec = { workspace = true, default-features = true } -serde = { features = ["derive"], workspace = true, default-features = true } -jsonrpsee = { features = ["server"], workspace = true } +color-print = { workspace = true } +docify = { workspace = true } futures = { workspace = true } +jsonrpsee = { features = ["server"], workspace = true } +log = { workspace = true, default-features = true } +serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -docify = { workspace = true } -color-print = { workspace = true } polkadot-sdk = { workspace = true, features = ["node"] } parachain-template-runtime = { workspace = true } # Substrate -sc-tracing = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } +sc-tracing = { workspace = true, default-features = true } [build-dependencies] polkadot-sdk = { workspace = true, features = ["substrate-build-script-utils"] } diff --git a/templates/parachain/runtime/Cargo.toml b/templates/parachain/runtime/Cargo.toml index f1d33b4143e4..9a0548106ed7 100644 --- a/templates/parachain/runtime/Cargo.toml +++ b/templates/parachain/runtime/Cargo.toml @@ -13,17 +13,17 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [build-dependencies] -substrate-wasm-builder = { optional = true, workspace = true, default-features = true } docify = { workspace = true } +substrate-wasm-builder = { optional = true, workspace = true, default-features = true } [dependencies] codec = { features = ["derive"], workspace = true } +docify = { workspace = true } hex-literal = { optional = true, workspace = true, default-features = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -smallvec = { workspace = true, default-features = true } -docify = { workspace = true } serde_json = { workspace = true, default-features = false, features = ["alloc"] } +smallvec = { workspace = true, default-features = true } # Local pallet-parachain-template = { workspace = true } diff --git a/templates/solochain/node/Cargo.toml b/templates/solochain/node/Cargo.toml index 4c0ab31df95e..90f576c88c23 100644 --- a/templates/solochain/node/Cargo.toml +++ b/templates/solochain/node/Cargo.toml @@ -17,41 +17,41 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] clap = { features = ["derive"], workspace = true } futures = { features = ["thread-pool"], workspace = true } -serde_json = { workspace = true, default-features = true } jsonrpsee = { features = ["server"], workspace = true } +serde_json = { workspace = true, default-features = true } # substrate client +sc-basic-authorship = { workspace = true, default-features = true } sc-cli = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-consensus = { workspace = true, default-features = true } +sc-consensus-aura = { workspace = true, default-features = true } +sc-consensus-grandpa = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } sc-network = { workspace = true, default-features = true } +sc-offchain = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } sc-telemetry = { workspace = true, default-features = true } sc-transaction-pool = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } -sc-offchain = { workspace = true, default-features = true } -sc-consensus = { workspace = true, default-features = true } -sc-consensus-aura = { workspace = true, default-features = true } sp-consensus-aura = { workspace = true, default-features = true } -sc-consensus-grandpa = { workspace = true, default-features = true } sp-consensus-grandpa = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } sp-genesis-builder = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-basic-authorship = { workspace = true, default-features = true } # substrate primitives -sp-runtime = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } -sp-inherents = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } sp-block-builder = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } +sp-inherents = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } # frame and pallets -frame-system = { workspace = true, default-features = true } frame-metadata-hash-extension = { workspace = true, default-features = true } +frame-system = { workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } pallet-transaction-payment-rpc = { workspace = true, default-features = true } substrate-frame-rpc-system = { workspace = true, default-features = true } diff --git a/templates/solochain/runtime/Cargo.toml b/templates/solochain/runtime/Cargo.toml index 837849e844b1..1cff982fbf3c 100644 --- a/templates/solochain/runtime/Cargo.toml +++ b/templates/solochain/runtime/Cargo.toml @@ -23,11 +23,11 @@ scale-info = { features = [ serde_json = { workspace = true, default-features = false, features = ["alloc"] } # frame +frame-executive = { workspace = true } +frame-metadata-hash-extension = { workspace = true } frame-support = { features = ["experimental"], workspace = true } frame-system = { workspace = true } frame-try-runtime = { optional = true, workspace = true } -frame-executive = { workspace = true } -frame-metadata-hash-extension = { workspace = true } # frame pallets pallet-aura = { workspace = true } @@ -46,11 +46,12 @@ sp-consensus-aura = { features = [ sp-consensus-grandpa = { features = [ "serde", ], workspace = true } -sp-keyring = { workspace = true } sp-core = { features = [ "serde", ], workspace = true } +sp-genesis-builder = { workspace = true } sp-inherents = { workspace = true } +sp-keyring = { workspace = true } sp-offchain = { workspace = true } sp-runtime = { features = [ "serde", @@ -61,7 +62,6 @@ sp-transaction-pool = { workspace = true } sp-version = { features = [ "serde", ], workspace = true } -sp-genesis-builder = { workspace = true } # RPC related frame-system-rpc-runtime-api = { workspace = true } diff --git a/templates/zombienet/Cargo.toml b/templates/zombienet/Cargo.toml index f29325dbe6a9..805e4ddbcee2 100644 --- a/templates/zombienet/Cargo.toml +++ b/templates/zombienet/Cargo.toml @@ -10,10 +10,10 @@ edition.workspace = true publish = false [dependencies] +anyhow = { workspace = true } env_logger = { workspace = true } log = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread"] } -anyhow = { workspace = true } zombienet-sdk = { workspace = true } [features] diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 68d71b4a5d5e..f36d39d63f6a 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -624,1884 +624,1884 @@ workspace = true workspace = true [dependencies.assets-common] -path = "../cumulus/parachains/runtimes/assets/common" default-features = false optional = true +path = "../cumulus/parachains/runtimes/assets/common" [dependencies.binary-merkle-tree] -path = "../substrate/utils/binary-merkle-tree" default-features = false optional = true +path = "../substrate/utils/binary-merkle-tree" [dependencies.bp-header-chain] -path = "../bridges/primitives/header-chain" default-features = false optional = true +path = "../bridges/primitives/header-chain" [dependencies.bp-messages] -path = "../bridges/primitives/messages" default-features = false optional = true +path = "../bridges/primitives/messages" [dependencies.bp-parachains] -path = "../bridges/primitives/parachains" default-features = false optional = true +path = "../bridges/primitives/parachains" [dependencies.bp-polkadot] -path = "../bridges/chains/chain-polkadot" default-features = false optional = true +path = "../bridges/chains/chain-polkadot" [dependencies.bp-polkadot-core] -path = "../bridges/primitives/polkadot-core" default-features = false optional = true +path = "../bridges/primitives/polkadot-core" [dependencies.bp-relayers] -path = "../bridges/primitives/relayers" default-features = false optional = true +path = "../bridges/primitives/relayers" [dependencies.bp-runtime] -path = "../bridges/primitives/runtime" default-features = false optional = true +path = "../bridges/primitives/runtime" [dependencies.bp-test-utils] -path = "../bridges/primitives/test-utils" default-features = false optional = true +path = "../bridges/primitives/test-utils" [dependencies.bp-xcm-bridge-hub] -path = "../bridges/primitives/xcm-bridge-hub" default-features = false optional = true +path = "../bridges/primitives/xcm-bridge-hub" [dependencies.bp-xcm-bridge-hub-router] -path = "../bridges/primitives/xcm-bridge-hub-router" default-features = false optional = true +path = "../bridges/primitives/xcm-bridge-hub-router" [dependencies.bridge-hub-common] -path = "../cumulus/parachains/runtimes/bridge-hubs/common" default-features = false optional = true +path = "../cumulus/parachains/runtimes/bridge-hubs/common" [dependencies.bridge-runtime-common] -path = "../bridges/bin/runtime-common" default-features = false optional = true +path = "../bridges/bin/runtime-common" [dependencies.cumulus-pallet-aura-ext] -path = "../cumulus/pallets/aura-ext" default-features = false optional = true +path = "../cumulus/pallets/aura-ext" [dependencies.cumulus-pallet-dmp-queue] -path = "../cumulus/pallets/dmp-queue" default-features = false optional = true +path = "../cumulus/pallets/dmp-queue" [dependencies.cumulus-pallet-parachain-system] -path = "../cumulus/pallets/parachain-system" default-features = false optional = true +path = "../cumulus/pallets/parachain-system" [dependencies.cumulus-pallet-parachain-system-proc-macro] -path = "../cumulus/pallets/parachain-system/proc-macro" default-features = false optional = true +path = "../cumulus/pallets/parachain-system/proc-macro" [dependencies.cumulus-pallet-session-benchmarking] -path = "../cumulus/pallets/session-benchmarking" default-features = false optional = true +path = "../cumulus/pallets/session-benchmarking" [dependencies.cumulus-pallet-solo-to-para] -path = "../cumulus/pallets/solo-to-para" default-features = false optional = true +path = "../cumulus/pallets/solo-to-para" [dependencies.cumulus-pallet-xcm] -path = "../cumulus/pallets/xcm" default-features = false optional = true +path = "../cumulus/pallets/xcm" [dependencies.cumulus-pallet-xcmp-queue] -path = "../cumulus/pallets/xcmp-queue" default-features = false optional = true +path = "../cumulus/pallets/xcmp-queue" [dependencies.cumulus-ping] -path = "../cumulus/parachains/pallets/ping" default-features = false optional = true +path = "../cumulus/parachains/pallets/ping" [dependencies.cumulus-primitives-aura] -path = "../cumulus/primitives/aura" default-features = false optional = true +path = "../cumulus/primitives/aura" [dependencies.cumulus-primitives-core] -path = "../cumulus/primitives/core" default-features = false optional = true +path = "../cumulus/primitives/core" [dependencies.cumulus-primitives-parachain-inherent] -path = "../cumulus/primitives/parachain-inherent" default-features = false optional = true +path = "../cumulus/primitives/parachain-inherent" [dependencies.cumulus-primitives-proof-size-hostfunction] -path = "../cumulus/primitives/proof-size-hostfunction" default-features = false optional = true +path = "../cumulus/primitives/proof-size-hostfunction" [dependencies.cumulus-primitives-storage-weight-reclaim] -path = "../cumulus/primitives/storage-weight-reclaim" default-features = false optional = true +path = "../cumulus/primitives/storage-weight-reclaim" [dependencies.cumulus-primitives-timestamp] -path = "../cumulus/primitives/timestamp" default-features = false optional = true +path = "../cumulus/primitives/timestamp" [dependencies.cumulus-primitives-utility] -path = "../cumulus/primitives/utility" default-features = false optional = true +path = "../cumulus/primitives/utility" [dependencies.frame-benchmarking] -path = "../substrate/frame/benchmarking" default-features = false optional = true +path = "../substrate/frame/benchmarking" [dependencies.frame-benchmarking-pallet-pov] -path = "../substrate/frame/benchmarking/pov" default-features = false optional = true +path = "../substrate/frame/benchmarking/pov" [dependencies.frame-election-provider-solution-type] -path = "../substrate/frame/election-provider-support/solution-type" default-features = false optional = true +path = "../substrate/frame/election-provider-support/solution-type" [dependencies.frame-election-provider-support] -path = "../substrate/frame/election-provider-support" default-features = false optional = true +path = "../substrate/frame/election-provider-support" [dependencies.frame-executive] -path = "../substrate/frame/executive" default-features = false optional = true +path = "../substrate/frame/executive" [dependencies.frame-metadata-hash-extension] -path = "../substrate/frame/metadata-hash-extension" default-features = false optional = true +path = "../substrate/frame/metadata-hash-extension" [dependencies.frame-support] -path = "../substrate/frame/support" default-features = false optional = true +path = "../substrate/frame/support" [dependencies.frame-support-procedural] -path = "../substrate/frame/support/procedural" default-features = false optional = true +path = "../substrate/frame/support/procedural" [dependencies.frame-support-procedural-tools-derive] -path = "../substrate/frame/support/procedural/tools/derive" default-features = false optional = true +path = "../substrate/frame/support/procedural/tools/derive" [dependencies.frame-system] -path = "../substrate/frame/system" default-features = false optional = true +path = "../substrate/frame/system" [dependencies.frame-system-benchmarking] -path = "../substrate/frame/system/benchmarking" default-features = false optional = true +path = "../substrate/frame/system/benchmarking" [dependencies.frame-system-rpc-runtime-api] -path = "../substrate/frame/system/rpc/runtime-api" default-features = false optional = true +path = "../substrate/frame/system/rpc/runtime-api" [dependencies.frame-try-runtime] -path = "../substrate/frame/try-runtime" default-features = false optional = true +path = "../substrate/frame/try-runtime" [dependencies.pallet-alliance] -path = "../substrate/frame/alliance" default-features = false optional = true +path = "../substrate/frame/alliance" [dependencies.pallet-asset-conversion] -path = "../substrate/frame/asset-conversion" default-features = false optional = true +path = "../substrate/frame/asset-conversion" [dependencies.pallet-asset-conversion-ops] -path = "../substrate/frame/asset-conversion/ops" default-features = false optional = true +path = "../substrate/frame/asset-conversion/ops" [dependencies.pallet-asset-conversion-tx-payment] -path = "../substrate/frame/transaction-payment/asset-conversion-tx-payment" default-features = false optional = true +path = "../substrate/frame/transaction-payment/asset-conversion-tx-payment" [dependencies.pallet-asset-rate] -path = "../substrate/frame/asset-rate" default-features = false optional = true +path = "../substrate/frame/asset-rate" [dependencies.pallet-asset-tx-payment] -path = "../substrate/frame/transaction-payment/asset-tx-payment" default-features = false optional = true +path = "../substrate/frame/transaction-payment/asset-tx-payment" [dependencies.pallet-assets] -path = "../substrate/frame/assets" default-features = false optional = true +path = "../substrate/frame/assets" [dependencies.pallet-assets-freezer] -path = "../substrate/frame/assets-freezer" default-features = false optional = true +path = "../substrate/frame/assets-freezer" [dependencies.pallet-atomic-swap] -path = "../substrate/frame/atomic-swap" default-features = false optional = true +path = "../substrate/frame/atomic-swap" [dependencies.pallet-aura] -path = "../substrate/frame/aura" default-features = false optional = true +path = "../substrate/frame/aura" [dependencies.pallet-authority-discovery] -path = "../substrate/frame/authority-discovery" default-features = false optional = true +path = "../substrate/frame/authority-discovery" [dependencies.pallet-authorship] -path = "../substrate/frame/authorship" default-features = false optional = true +path = "../substrate/frame/authorship" [dependencies.pallet-babe] -path = "../substrate/frame/babe" default-features = false optional = true +path = "../substrate/frame/babe" [dependencies.pallet-bags-list] -path = "../substrate/frame/bags-list" default-features = false optional = true +path = "../substrate/frame/bags-list" [dependencies.pallet-balances] -path = "../substrate/frame/balances" default-features = false optional = true +path = "../substrate/frame/balances" [dependencies.pallet-beefy] -path = "../substrate/frame/beefy" default-features = false optional = true +path = "../substrate/frame/beefy" [dependencies.pallet-beefy-mmr] -path = "../substrate/frame/beefy-mmr" default-features = false optional = true +path = "../substrate/frame/beefy-mmr" [dependencies.pallet-bounties] -path = "../substrate/frame/bounties" default-features = false optional = true +path = "../substrate/frame/bounties" [dependencies.pallet-bridge-grandpa] -path = "../bridges/modules/grandpa" default-features = false optional = true +path = "../bridges/modules/grandpa" [dependencies.pallet-bridge-messages] -path = "../bridges/modules/messages" default-features = false optional = true +path = "../bridges/modules/messages" [dependencies.pallet-bridge-parachains] -path = "../bridges/modules/parachains" default-features = false optional = true +path = "../bridges/modules/parachains" [dependencies.pallet-bridge-relayers] -path = "../bridges/modules/relayers" default-features = false optional = true +path = "../bridges/modules/relayers" [dependencies.pallet-broker] -path = "../substrate/frame/broker" default-features = false optional = true +path = "../substrate/frame/broker" [dependencies.pallet-child-bounties] -path = "../substrate/frame/child-bounties" default-features = false optional = true +path = "../substrate/frame/child-bounties" [dependencies.pallet-collator-selection] -path = "../cumulus/pallets/collator-selection" default-features = false optional = true +path = "../cumulus/pallets/collator-selection" [dependencies.pallet-collective] -path = "../substrate/frame/collective" default-features = false optional = true +path = "../substrate/frame/collective" [dependencies.pallet-collective-content] -path = "../cumulus/parachains/pallets/collective-content" default-features = false optional = true +path = "../cumulus/parachains/pallets/collective-content" [dependencies.pallet-contracts] -path = "../substrate/frame/contracts" default-features = false optional = true +path = "../substrate/frame/contracts" [dependencies.pallet-contracts-proc-macro] -path = "../substrate/frame/contracts/proc-macro" default-features = false optional = true +path = "../substrate/frame/contracts/proc-macro" [dependencies.pallet-contracts-uapi] -path = "../substrate/frame/contracts/uapi" default-features = false optional = true +path = "../substrate/frame/contracts/uapi" [dependencies.pallet-conviction-voting] -path = "../substrate/frame/conviction-voting" default-features = false optional = true +path = "../substrate/frame/conviction-voting" [dependencies.pallet-core-fellowship] -path = "../substrate/frame/core-fellowship" default-features = false optional = true +path = "../substrate/frame/core-fellowship" [dependencies.pallet-delegated-staking] -path = "../substrate/frame/delegated-staking" default-features = false optional = true +path = "../substrate/frame/delegated-staking" [dependencies.pallet-democracy] -path = "../substrate/frame/democracy" default-features = false optional = true +path = "../substrate/frame/democracy" [dependencies.pallet-dev-mode] -path = "../substrate/frame/examples/dev-mode" default-features = false optional = true +path = "../substrate/frame/examples/dev-mode" [dependencies.pallet-election-provider-multi-phase] -path = "../substrate/frame/election-provider-multi-phase" default-features = false optional = true +path = "../substrate/frame/election-provider-multi-phase" [dependencies.pallet-election-provider-support-benchmarking] -path = "../substrate/frame/election-provider-support/benchmarking" default-features = false optional = true +path = "../substrate/frame/election-provider-support/benchmarking" [dependencies.pallet-elections-phragmen] -path = "../substrate/frame/elections-phragmen" default-features = false optional = true +path = "../substrate/frame/elections-phragmen" [dependencies.pallet-fast-unstake] -path = "../substrate/frame/fast-unstake" default-features = false optional = true +path = "../substrate/frame/fast-unstake" [dependencies.pallet-glutton] -path = "../substrate/frame/glutton" default-features = false optional = true +path = "../substrate/frame/glutton" [dependencies.pallet-grandpa] -path = "../substrate/frame/grandpa" default-features = false optional = true +path = "../substrate/frame/grandpa" [dependencies.pallet-identity] -path = "../substrate/frame/identity" default-features = false optional = true +path = "../substrate/frame/identity" [dependencies.pallet-im-online] -path = "../substrate/frame/im-online" default-features = false optional = true +path = "../substrate/frame/im-online" [dependencies.pallet-indices] -path = "../substrate/frame/indices" default-features = false optional = true +path = "../substrate/frame/indices" [dependencies.pallet-insecure-randomness-collective-flip] -path = "../substrate/frame/insecure-randomness-collective-flip" default-features = false optional = true +path = "../substrate/frame/insecure-randomness-collective-flip" [dependencies.pallet-lottery] -path = "../substrate/frame/lottery" default-features = false optional = true +path = "../substrate/frame/lottery" [dependencies.pallet-membership] -path = "../substrate/frame/membership" default-features = false optional = true +path = "../substrate/frame/membership" [dependencies.pallet-message-queue] -path = "../substrate/frame/message-queue" default-features = false optional = true +path = "../substrate/frame/message-queue" [dependencies.pallet-migrations] -path = "../substrate/frame/migrations" default-features = false optional = true +path = "../substrate/frame/migrations" [dependencies.pallet-mixnet] -path = "../substrate/frame/mixnet" default-features = false optional = true +path = "../substrate/frame/mixnet" [dependencies.pallet-mmr] -path = "../substrate/frame/merkle-mountain-range" default-features = false optional = true +path = "../substrate/frame/merkle-mountain-range" [dependencies.pallet-multisig] -path = "../substrate/frame/multisig" default-features = false optional = true +path = "../substrate/frame/multisig" [dependencies.pallet-nft-fractionalization] -path = "../substrate/frame/nft-fractionalization" default-features = false optional = true +path = "../substrate/frame/nft-fractionalization" [dependencies.pallet-nfts] -path = "../substrate/frame/nfts" default-features = false optional = true +path = "../substrate/frame/nfts" [dependencies.pallet-nfts-runtime-api] -path = "../substrate/frame/nfts/runtime-api" default-features = false optional = true +path = "../substrate/frame/nfts/runtime-api" [dependencies.pallet-nis] -path = "../substrate/frame/nis" default-features = false optional = true +path = "../substrate/frame/nis" [dependencies.pallet-node-authorization] -path = "../substrate/frame/node-authorization" default-features = false optional = true +path = "../substrate/frame/node-authorization" [dependencies.pallet-nomination-pools] -path = "../substrate/frame/nomination-pools" default-features = false optional = true +path = "../substrate/frame/nomination-pools" [dependencies.pallet-nomination-pools-benchmarking] -path = "../substrate/frame/nomination-pools/benchmarking" default-features = false optional = true +path = "../substrate/frame/nomination-pools/benchmarking" [dependencies.pallet-nomination-pools-runtime-api] -path = "../substrate/frame/nomination-pools/runtime-api" default-features = false optional = true +path = "../substrate/frame/nomination-pools/runtime-api" [dependencies.pallet-offences] -path = "../substrate/frame/offences" default-features = false optional = true +path = "../substrate/frame/offences" [dependencies.pallet-offences-benchmarking] -path = "../substrate/frame/offences/benchmarking" default-features = false optional = true +path = "../substrate/frame/offences/benchmarking" [dependencies.pallet-paged-list] -path = "../substrate/frame/paged-list" default-features = false optional = true +path = "../substrate/frame/paged-list" [dependencies.pallet-parameters] -path = "../substrate/frame/parameters" default-features = false optional = true +path = "../substrate/frame/parameters" [dependencies.pallet-preimage] -path = "../substrate/frame/preimage" default-features = false optional = true +path = "../substrate/frame/preimage" [dependencies.pallet-proxy] -path = "../substrate/frame/proxy" default-features = false optional = true +path = "../substrate/frame/proxy" [dependencies.pallet-ranked-collective] -path = "../substrate/frame/ranked-collective" default-features = false optional = true +path = "../substrate/frame/ranked-collective" [dependencies.pallet-recovery] -path = "../substrate/frame/recovery" default-features = false optional = true +path = "../substrate/frame/recovery" [dependencies.pallet-referenda] -path = "../substrate/frame/referenda" default-features = false optional = true +path = "../substrate/frame/referenda" [dependencies.pallet-remark] -path = "../substrate/frame/remark" default-features = false optional = true +path = "../substrate/frame/remark" [dependencies.pallet-revive] -path = "../substrate/frame/revive" default-features = false optional = true +path = "../substrate/frame/revive" [dependencies.pallet-revive-proc-macro] -path = "../substrate/frame/revive/proc-macro" default-features = false optional = true +path = "../substrate/frame/revive/proc-macro" [dependencies.pallet-revive-uapi] -path = "../substrate/frame/revive/uapi" default-features = false optional = true +path = "../substrate/frame/revive/uapi" [dependencies.pallet-root-offences] -path = "../substrate/frame/root-offences" default-features = false optional = true +path = "../substrate/frame/root-offences" [dependencies.pallet-root-testing] -path = "../substrate/frame/root-testing" default-features = false optional = true +path = "../substrate/frame/root-testing" [dependencies.pallet-safe-mode] -path = "../substrate/frame/safe-mode" default-features = false optional = true +path = "../substrate/frame/safe-mode" [dependencies.pallet-salary] -path = "../substrate/frame/salary" default-features = false optional = true +path = "../substrate/frame/salary" [dependencies.pallet-scheduler] -path = "../substrate/frame/scheduler" default-features = false optional = true +path = "../substrate/frame/scheduler" [dependencies.pallet-scored-pool] -path = "../substrate/frame/scored-pool" default-features = false optional = true +path = "../substrate/frame/scored-pool" [dependencies.pallet-session] -path = "../substrate/frame/session" default-features = false optional = true +path = "../substrate/frame/session" [dependencies.pallet-session-benchmarking] -path = "../substrate/frame/session/benchmarking" default-features = false optional = true +path = "../substrate/frame/session/benchmarking" [dependencies.pallet-skip-feeless-payment] -path = "../substrate/frame/transaction-payment/skip-feeless-payment" default-features = false optional = true +path = "../substrate/frame/transaction-payment/skip-feeless-payment" [dependencies.pallet-society] -path = "../substrate/frame/society" default-features = false optional = true +path = "../substrate/frame/society" [dependencies.pallet-staking] -path = "../substrate/frame/staking" default-features = false optional = true +path = "../substrate/frame/staking" [dependencies.pallet-staking-reward-curve] -path = "../substrate/frame/staking/reward-curve" default-features = false optional = true +path = "../substrate/frame/staking/reward-curve" [dependencies.pallet-staking-reward-fn] -path = "../substrate/frame/staking/reward-fn" default-features = false optional = true +path = "../substrate/frame/staking/reward-fn" [dependencies.pallet-staking-runtime-api] -path = "../substrate/frame/staking/runtime-api" default-features = false optional = true +path = "../substrate/frame/staking/runtime-api" [dependencies.pallet-state-trie-migration] -path = "../substrate/frame/state-trie-migration" default-features = false optional = true +path = "../substrate/frame/state-trie-migration" [dependencies.pallet-statement] -path = "../substrate/frame/statement" default-features = false optional = true +path = "../substrate/frame/statement" [dependencies.pallet-sudo] -path = "../substrate/frame/sudo" default-features = false optional = true +path = "../substrate/frame/sudo" [dependencies.pallet-timestamp] -path = "../substrate/frame/timestamp" default-features = false optional = true +path = "../substrate/frame/timestamp" [dependencies.pallet-tips] -path = "../substrate/frame/tips" default-features = false optional = true +path = "../substrate/frame/tips" [dependencies.pallet-transaction-payment] -path = "../substrate/frame/transaction-payment" default-features = false optional = true +path = "../substrate/frame/transaction-payment" [dependencies.pallet-transaction-payment-rpc-runtime-api] -path = "../substrate/frame/transaction-payment/rpc/runtime-api" default-features = false optional = true +path = "../substrate/frame/transaction-payment/rpc/runtime-api" [dependencies.pallet-transaction-storage] -path = "../substrate/frame/transaction-storage" default-features = false optional = true +path = "../substrate/frame/transaction-storage" [dependencies.pallet-treasury] -path = "../substrate/frame/treasury" default-features = false optional = true +path = "../substrate/frame/treasury" [dependencies.pallet-tx-pause] -path = "../substrate/frame/tx-pause" default-features = false optional = true +path = "../substrate/frame/tx-pause" [dependencies.pallet-uniques] -path = "../substrate/frame/uniques" default-features = false optional = true +path = "../substrate/frame/uniques" [dependencies.pallet-utility] -path = "../substrate/frame/utility" default-features = false optional = true +path = "../substrate/frame/utility" [dependencies.pallet-verify-signature] -path = "../substrate/frame/verify-signature" default-features = false optional = true +path = "../substrate/frame/verify-signature" [dependencies.pallet-vesting] -path = "../substrate/frame/vesting" default-features = false optional = true +path = "../substrate/frame/vesting" [dependencies.pallet-whitelist] -path = "../substrate/frame/whitelist" default-features = false optional = true +path = "../substrate/frame/whitelist" [dependencies.pallet-xcm] -path = "../polkadot/xcm/pallet-xcm" default-features = false optional = true +path = "../polkadot/xcm/pallet-xcm" [dependencies.pallet-xcm-benchmarks] -path = "../polkadot/xcm/pallet-xcm-benchmarks" default-features = false optional = true +path = "../polkadot/xcm/pallet-xcm-benchmarks" [dependencies.pallet-xcm-bridge-hub] -path = "../bridges/modules/xcm-bridge-hub" default-features = false optional = true +path = "../bridges/modules/xcm-bridge-hub" [dependencies.pallet-xcm-bridge-hub-router] -path = "../bridges/modules/xcm-bridge-hub-router" default-features = false optional = true +path = "../bridges/modules/xcm-bridge-hub-router" [dependencies.parachains-common] -path = "../cumulus/parachains/common" default-features = false optional = true +path = "../cumulus/parachains/common" [dependencies.polkadot-core-primitives] -path = "../polkadot/core-primitives" default-features = false optional = true +path = "../polkadot/core-primitives" [dependencies.polkadot-parachain-primitives] -path = "../polkadot/parachain" default-features = false optional = true +path = "../polkadot/parachain" [dependencies.polkadot-primitives] -path = "../polkadot/primitives" default-features = false optional = true +path = "../polkadot/primitives" [dependencies.polkadot-runtime-common] -path = "../polkadot/runtime/common" default-features = false optional = true +path = "../polkadot/runtime/common" [dependencies.polkadot-runtime-metrics] -path = "../polkadot/runtime/metrics" default-features = false optional = true +path = "../polkadot/runtime/metrics" [dependencies.polkadot-runtime-parachains] -path = "../polkadot/runtime/parachains" default-features = false optional = true +path = "../polkadot/runtime/parachains" [dependencies.polkadot-sdk-frame] -path = "../substrate/frame" default-features = false optional = true +path = "../substrate/frame" [dependencies.sc-chain-spec-derive] -path = "../substrate/client/chain-spec/derive" default-features = false optional = true +path = "../substrate/client/chain-spec/derive" [dependencies.sc-tracing-proc-macro] -path = "../substrate/client/tracing/proc-macro" default-features = false optional = true +path = "../substrate/client/tracing/proc-macro" [dependencies.slot-range-helper] -path = "../polkadot/runtime/common/slot_range_helper" default-features = false optional = true +path = "../polkadot/runtime/common/slot_range_helper" [dependencies.snowbridge-beacon-primitives] -path = "../bridges/snowbridge/primitives/beacon" default-features = false optional = true +path = "../bridges/snowbridge/primitives/beacon" [dependencies.snowbridge-core] -path = "../bridges/snowbridge/primitives/core" default-features = false optional = true +path = "../bridges/snowbridge/primitives/core" [dependencies.snowbridge-ethereum] -path = "../bridges/snowbridge/primitives/ethereum" default-features = false optional = true +path = "../bridges/snowbridge/primitives/ethereum" [dependencies.snowbridge-outbound-queue-merkle-tree] -path = "../bridges/snowbridge/pallets/outbound-queue/merkle-tree" default-features = false optional = true +path = "../bridges/snowbridge/pallets/outbound-queue/merkle-tree" [dependencies.snowbridge-outbound-queue-runtime-api] -path = "../bridges/snowbridge/pallets/outbound-queue/runtime-api" default-features = false optional = true +path = "../bridges/snowbridge/pallets/outbound-queue/runtime-api" [dependencies.snowbridge-pallet-ethereum-client] -path = "../bridges/snowbridge/pallets/ethereum-client" default-features = false optional = true +path = "../bridges/snowbridge/pallets/ethereum-client" [dependencies.snowbridge-pallet-ethereum-client-fixtures] -path = "../bridges/snowbridge/pallets/ethereum-client/fixtures" default-features = false optional = true +path = "../bridges/snowbridge/pallets/ethereum-client/fixtures" [dependencies.snowbridge-pallet-inbound-queue] -path = "../bridges/snowbridge/pallets/inbound-queue" default-features = false optional = true +path = "../bridges/snowbridge/pallets/inbound-queue" [dependencies.snowbridge-pallet-inbound-queue-fixtures] -path = "../bridges/snowbridge/pallets/inbound-queue/fixtures" default-features = false optional = true +path = "../bridges/snowbridge/pallets/inbound-queue/fixtures" [dependencies.snowbridge-pallet-outbound-queue] -path = "../bridges/snowbridge/pallets/outbound-queue" default-features = false optional = true +path = "../bridges/snowbridge/pallets/outbound-queue" [dependencies.snowbridge-pallet-system] -path = "../bridges/snowbridge/pallets/system" default-features = false optional = true +path = "../bridges/snowbridge/pallets/system" [dependencies.snowbridge-router-primitives] -path = "../bridges/snowbridge/primitives/router" default-features = false optional = true +path = "../bridges/snowbridge/primitives/router" [dependencies.snowbridge-runtime-common] -path = "../bridges/snowbridge/runtime/runtime-common" default-features = false optional = true +path = "../bridges/snowbridge/runtime/runtime-common" [dependencies.snowbridge-system-runtime-api] -path = "../bridges/snowbridge/pallets/system/runtime-api" default-features = false optional = true +path = "../bridges/snowbridge/pallets/system/runtime-api" [dependencies.sp-api] -path = "../substrate/primitives/api" default-features = false optional = true +path = "../substrate/primitives/api" [dependencies.sp-api-proc-macro] -path = "../substrate/primitives/api/proc-macro" default-features = false optional = true +path = "../substrate/primitives/api/proc-macro" [dependencies.sp-application-crypto] -path = "../substrate/primitives/application-crypto" default-features = false optional = true +path = "../substrate/primitives/application-crypto" [dependencies.sp-arithmetic] -path = "../substrate/primitives/arithmetic" default-features = false optional = true +path = "../substrate/primitives/arithmetic" [dependencies.sp-authority-discovery] -path = "../substrate/primitives/authority-discovery" default-features = false optional = true +path = "../substrate/primitives/authority-discovery" [dependencies.sp-block-builder] -path = "../substrate/primitives/block-builder" default-features = false optional = true +path = "../substrate/primitives/block-builder" [dependencies.sp-consensus-aura] -path = "../substrate/primitives/consensus/aura" default-features = false optional = true +path = "../substrate/primitives/consensus/aura" [dependencies.sp-consensus-babe] -path = "../substrate/primitives/consensus/babe" default-features = false optional = true +path = "../substrate/primitives/consensus/babe" [dependencies.sp-consensus-beefy] -path = "../substrate/primitives/consensus/beefy" default-features = false optional = true +path = "../substrate/primitives/consensus/beefy" [dependencies.sp-consensus-grandpa] -path = "../substrate/primitives/consensus/grandpa" default-features = false optional = true +path = "../substrate/primitives/consensus/grandpa" [dependencies.sp-consensus-pow] -path = "../substrate/primitives/consensus/pow" default-features = false optional = true +path = "../substrate/primitives/consensus/pow" [dependencies.sp-consensus-slots] -path = "../substrate/primitives/consensus/slots" default-features = false optional = true +path = "../substrate/primitives/consensus/slots" [dependencies.sp-core] -path = "../substrate/primitives/core" default-features = false optional = true +path = "../substrate/primitives/core" [dependencies.sp-crypto-ec-utils] -path = "../substrate/primitives/crypto/ec-utils" default-features = false optional = true +path = "../substrate/primitives/crypto/ec-utils" [dependencies.sp-crypto-hashing] -path = "../substrate/primitives/crypto/hashing" default-features = false optional = true +path = "../substrate/primitives/crypto/hashing" [dependencies.sp-crypto-hashing-proc-macro] -path = "../substrate/primitives/crypto/hashing/proc-macro" default-features = false optional = true +path = "../substrate/primitives/crypto/hashing/proc-macro" [dependencies.sp-debug-derive] -path = "../substrate/primitives/debug-derive" default-features = false optional = true +path = "../substrate/primitives/debug-derive" [dependencies.sp-externalities] -path = "../substrate/primitives/externalities" default-features = false optional = true +path = "../substrate/primitives/externalities" [dependencies.sp-genesis-builder] -path = "../substrate/primitives/genesis-builder" default-features = false optional = true +path = "../substrate/primitives/genesis-builder" [dependencies.sp-inherents] -path = "../substrate/primitives/inherents" default-features = false optional = true +path = "../substrate/primitives/inherents" [dependencies.sp-io] -path = "../substrate/primitives/io" default-features = false optional = true +path = "../substrate/primitives/io" [dependencies.sp-keyring] -path = "../substrate/primitives/keyring" default-features = false optional = true +path = "../substrate/primitives/keyring" [dependencies.sp-keystore] -path = "../substrate/primitives/keystore" default-features = false optional = true +path = "../substrate/primitives/keystore" [dependencies.sp-metadata-ir] -path = "../substrate/primitives/metadata-ir" default-features = false optional = true +path = "../substrate/primitives/metadata-ir" [dependencies.sp-mixnet] -path = "../substrate/primitives/mixnet" default-features = false optional = true +path = "../substrate/primitives/mixnet" [dependencies.sp-mmr-primitives] -path = "../substrate/primitives/merkle-mountain-range" default-features = false optional = true +path = "../substrate/primitives/merkle-mountain-range" [dependencies.sp-npos-elections] -path = "../substrate/primitives/npos-elections" default-features = false optional = true +path = "../substrate/primitives/npos-elections" [dependencies.sp-offchain] -path = "../substrate/primitives/offchain" default-features = false optional = true +path = "../substrate/primitives/offchain" [dependencies.sp-runtime] -path = "../substrate/primitives/runtime" default-features = false optional = true +path = "../substrate/primitives/runtime" [dependencies.sp-runtime-interface] -path = "../substrate/primitives/runtime-interface" default-features = false optional = true +path = "../substrate/primitives/runtime-interface" [dependencies.sp-runtime-interface-proc-macro] -path = "../substrate/primitives/runtime-interface/proc-macro" default-features = false optional = true +path = "../substrate/primitives/runtime-interface/proc-macro" [dependencies.sp-session] -path = "../substrate/primitives/session" default-features = false optional = true +path = "../substrate/primitives/session" [dependencies.sp-staking] -path = "../substrate/primitives/staking" default-features = false optional = true +path = "../substrate/primitives/staking" [dependencies.sp-state-machine] -path = "../substrate/primitives/state-machine" default-features = false optional = true +path = "../substrate/primitives/state-machine" [dependencies.sp-statement-store] -path = "../substrate/primitives/statement-store" default-features = false optional = true +path = "../substrate/primitives/statement-store" [dependencies.sp-std] -path = "../substrate/primitives/std" default-features = false optional = true +path = "../substrate/primitives/std" [dependencies.sp-storage] -path = "../substrate/primitives/storage" default-features = false optional = true +path = "../substrate/primitives/storage" [dependencies.sp-timestamp] -path = "../substrate/primitives/timestamp" default-features = false optional = true +path = "../substrate/primitives/timestamp" [dependencies.sp-tracing] -path = "../substrate/primitives/tracing" default-features = false optional = true +path = "../substrate/primitives/tracing" [dependencies.sp-transaction-pool] -path = "../substrate/primitives/transaction-pool" default-features = false optional = true +path = "../substrate/primitives/transaction-pool" [dependencies.sp-transaction-storage-proof] -path = "../substrate/primitives/transaction-storage-proof" default-features = false optional = true +path = "../substrate/primitives/transaction-storage-proof" [dependencies.sp-trie] -path = "../substrate/primitives/trie" default-features = false optional = true +path = "../substrate/primitives/trie" [dependencies.sp-version] -path = "../substrate/primitives/version" default-features = false optional = true +path = "../substrate/primitives/version" [dependencies.sp-version-proc-macro] -path = "../substrate/primitives/version/proc-macro" default-features = false optional = true +path = "../substrate/primitives/version/proc-macro" [dependencies.sp-wasm-interface] -path = "../substrate/primitives/wasm-interface" default-features = false optional = true +path = "../substrate/primitives/wasm-interface" [dependencies.sp-weights] -path = "../substrate/primitives/weights" default-features = false optional = true +path = "../substrate/primitives/weights" [dependencies.staging-parachain-info] -path = "../cumulus/parachains/pallets/parachain-info" default-features = false optional = true +path = "../cumulus/parachains/pallets/parachain-info" [dependencies.staging-xcm] -path = "../polkadot/xcm" default-features = false optional = true +path = "../polkadot/xcm" [dependencies.staging-xcm-builder] -path = "../polkadot/xcm/xcm-builder" default-features = false optional = true +path = "../polkadot/xcm/xcm-builder" [dependencies.staging-xcm-executor] -path = "../polkadot/xcm/xcm-executor" default-features = false optional = true +path = "../polkadot/xcm/xcm-executor" [dependencies.substrate-bip39] -path = "../substrate/utils/substrate-bip39" default-features = false optional = true +path = "../substrate/utils/substrate-bip39" [dependencies.testnet-parachains-constants] -path = "../cumulus/parachains/runtimes/constants" default-features = false optional = true +path = "../cumulus/parachains/runtimes/constants" [dependencies.tracing-gum-proc-macro] -path = "../polkadot/node/gum/proc-macro" default-features = false optional = true +path = "../polkadot/node/gum/proc-macro" [dependencies.xcm-procedural] -path = "../polkadot/xcm/procedural" default-features = false optional = true +path = "../polkadot/xcm/procedural" [dependencies.xcm-runtime-apis] -path = "../polkadot/xcm/xcm-runtime-apis" default-features = false optional = true +path = "../polkadot/xcm/xcm-runtime-apis" [dependencies.asset-test-utils] -path = "../cumulus/parachains/runtimes/assets/test-utils" default-features = false optional = true +path = "../cumulus/parachains/runtimes/assets/test-utils" [dependencies.bridge-hub-test-utils] -path = "../cumulus/parachains/runtimes/bridge-hubs/test-utils" default-features = false optional = true +path = "../cumulus/parachains/runtimes/bridge-hubs/test-utils" [dependencies.cumulus-client-cli] -path = "../cumulus/client/cli" default-features = false optional = true +path = "../cumulus/client/cli" [dependencies.cumulus-client-collator] -path = "../cumulus/client/collator" default-features = false optional = true +path = "../cumulus/client/collator" [dependencies.cumulus-client-consensus-aura] -path = "../cumulus/client/consensus/aura" default-features = false optional = true +path = "../cumulus/client/consensus/aura" [dependencies.cumulus-client-consensus-common] -path = "../cumulus/client/consensus/common" default-features = false optional = true +path = "../cumulus/client/consensus/common" [dependencies.cumulus-client-consensus-proposer] -path = "../cumulus/client/consensus/proposer" default-features = false optional = true +path = "../cumulus/client/consensus/proposer" [dependencies.cumulus-client-consensus-relay-chain] -path = "../cumulus/client/consensus/relay-chain" default-features = false optional = true +path = "../cumulus/client/consensus/relay-chain" [dependencies.cumulus-client-network] -path = "../cumulus/client/network" default-features = false optional = true +path = "../cumulus/client/network" [dependencies.cumulus-client-parachain-inherent] -path = "../cumulus/client/parachain-inherent" default-features = false optional = true +path = "../cumulus/client/parachain-inherent" [dependencies.cumulus-client-pov-recovery] -path = "../cumulus/client/pov-recovery" default-features = false optional = true +path = "../cumulus/client/pov-recovery" [dependencies.cumulus-client-service] -path = "../cumulus/client/service" default-features = false optional = true +path = "../cumulus/client/service" [dependencies.cumulus-relay-chain-inprocess-interface] -path = "../cumulus/client/relay-chain-inprocess-interface" default-features = false optional = true +path = "../cumulus/client/relay-chain-inprocess-interface" [dependencies.cumulus-relay-chain-interface] -path = "../cumulus/client/relay-chain-interface" default-features = false optional = true +path = "../cumulus/client/relay-chain-interface" [dependencies.cumulus-relay-chain-minimal-node] -path = "../cumulus/client/relay-chain-minimal-node" default-features = false optional = true +path = "../cumulus/client/relay-chain-minimal-node" [dependencies.cumulus-relay-chain-rpc-interface] -path = "../cumulus/client/relay-chain-rpc-interface" default-features = false optional = true +path = "../cumulus/client/relay-chain-rpc-interface" [dependencies.cumulus-test-relay-sproof-builder] -path = "../cumulus/test/relay-sproof-builder" default-features = false optional = true +path = "../cumulus/test/relay-sproof-builder" [dependencies.emulated-integration-tests-common] -path = "../cumulus/parachains/integration-tests/emulated/common" default-features = false optional = true +path = "../cumulus/parachains/integration-tests/emulated/common" [dependencies.fork-tree] -path = "../substrate/utils/fork-tree" default-features = false optional = true +path = "../substrate/utils/fork-tree" [dependencies.frame-benchmarking-cli] -path = "../substrate/utils/frame/benchmarking-cli" default-features = false optional = true +path = "../substrate/utils/frame/benchmarking-cli" [dependencies.frame-remote-externalities] -path = "../substrate/utils/frame/remote-externalities" default-features = false optional = true +path = "../substrate/utils/frame/remote-externalities" [dependencies.frame-support-procedural-tools] -path = "../substrate/frame/support/procedural/tools" default-features = false optional = true +path = "../substrate/frame/support/procedural/tools" [dependencies.generate-bags] -path = "../substrate/utils/frame/generate-bags" default-features = false optional = true +path = "../substrate/utils/frame/generate-bags" [dependencies.mmr-gadget] -path = "../substrate/client/merkle-mountain-range" default-features = false optional = true +path = "../substrate/client/merkle-mountain-range" [dependencies.mmr-rpc] -path = "../substrate/client/merkle-mountain-range/rpc" default-features = false optional = true +path = "../substrate/client/merkle-mountain-range/rpc" [dependencies.pallet-contracts-mock-network] -path = "../substrate/frame/contracts/mock-network" default-features = false optional = true +path = "../substrate/frame/contracts/mock-network" [dependencies.pallet-revive-eth-rpc] -path = "../substrate/frame/revive/rpc" default-features = false optional = true +path = "../substrate/frame/revive/rpc" [dependencies.pallet-revive-mock-network] -path = "../substrate/frame/revive/mock-network" default-features = false optional = true +path = "../substrate/frame/revive/mock-network" [dependencies.pallet-transaction-payment-rpc] -path = "../substrate/frame/transaction-payment/rpc" default-features = false optional = true +path = "../substrate/frame/transaction-payment/rpc" [dependencies.parachains-runtimes-test-utils] -path = "../cumulus/parachains/runtimes/test-utils" default-features = false optional = true +path = "../cumulus/parachains/runtimes/test-utils" [dependencies.polkadot-approval-distribution] -path = "../polkadot/node/network/approval-distribution" default-features = false optional = true +path = "../polkadot/node/network/approval-distribution" [dependencies.polkadot-availability-bitfield-distribution] -path = "../polkadot/node/network/bitfield-distribution" default-features = false optional = true +path = "../polkadot/node/network/bitfield-distribution" [dependencies.polkadot-availability-distribution] -path = "../polkadot/node/network/availability-distribution" default-features = false optional = true +path = "../polkadot/node/network/availability-distribution" [dependencies.polkadot-availability-recovery] -path = "../polkadot/node/network/availability-recovery" default-features = false optional = true +path = "../polkadot/node/network/availability-recovery" [dependencies.polkadot-cli] -path = "../polkadot/cli" default-features = false optional = true +path = "../polkadot/cli" [dependencies.polkadot-collator-protocol] -path = "../polkadot/node/network/collator-protocol" default-features = false optional = true +path = "../polkadot/node/network/collator-protocol" [dependencies.polkadot-dispute-distribution] -path = "../polkadot/node/network/dispute-distribution" default-features = false optional = true +path = "../polkadot/node/network/dispute-distribution" [dependencies.polkadot-erasure-coding] -path = "../polkadot/erasure-coding" default-features = false optional = true +path = "../polkadot/erasure-coding" [dependencies.polkadot-gossip-support] -path = "../polkadot/node/network/gossip-support" default-features = false optional = true +path = "../polkadot/node/network/gossip-support" [dependencies.polkadot-network-bridge] -path = "../polkadot/node/network/bridge" default-features = false optional = true +path = "../polkadot/node/network/bridge" [dependencies.polkadot-node-collation-generation] -path = "../polkadot/node/collation-generation" default-features = false optional = true +path = "../polkadot/node/collation-generation" [dependencies.polkadot-node-core-approval-voting] -path = "../polkadot/node/core/approval-voting" default-features = false optional = true +path = "../polkadot/node/core/approval-voting" [dependencies.polkadot-node-core-approval-voting-parallel] -path = "../polkadot/node/core/approval-voting-parallel" default-features = false optional = true +path = "../polkadot/node/core/approval-voting-parallel" [dependencies.polkadot-node-core-av-store] -path = "../polkadot/node/core/av-store" default-features = false optional = true +path = "../polkadot/node/core/av-store" [dependencies.polkadot-node-core-backing] -path = "../polkadot/node/core/backing" default-features = false optional = true +path = "../polkadot/node/core/backing" [dependencies.polkadot-node-core-bitfield-signing] -path = "../polkadot/node/core/bitfield-signing" default-features = false optional = true +path = "../polkadot/node/core/bitfield-signing" [dependencies.polkadot-node-core-candidate-validation] -path = "../polkadot/node/core/candidate-validation" default-features = false optional = true +path = "../polkadot/node/core/candidate-validation" [dependencies.polkadot-node-core-chain-api] -path = "../polkadot/node/core/chain-api" default-features = false optional = true +path = "../polkadot/node/core/chain-api" [dependencies.polkadot-node-core-chain-selection] -path = "../polkadot/node/core/chain-selection" default-features = false optional = true +path = "../polkadot/node/core/chain-selection" [dependencies.polkadot-node-core-dispute-coordinator] -path = "../polkadot/node/core/dispute-coordinator" default-features = false optional = true +path = "../polkadot/node/core/dispute-coordinator" [dependencies.polkadot-node-core-parachains-inherent] -path = "../polkadot/node/core/parachains-inherent" default-features = false optional = true +path = "../polkadot/node/core/parachains-inherent" [dependencies.polkadot-node-core-prospective-parachains] -path = "../polkadot/node/core/prospective-parachains" default-features = false optional = true +path = "../polkadot/node/core/prospective-parachains" [dependencies.polkadot-node-core-provisioner] -path = "../polkadot/node/core/provisioner" default-features = false optional = true +path = "../polkadot/node/core/provisioner" [dependencies.polkadot-node-core-pvf] -path = "../polkadot/node/core/pvf" default-features = false optional = true +path = "../polkadot/node/core/pvf" [dependencies.polkadot-node-core-pvf-checker] -path = "../polkadot/node/core/pvf-checker" default-features = false optional = true +path = "../polkadot/node/core/pvf-checker" [dependencies.polkadot-node-core-pvf-common] -path = "../polkadot/node/core/pvf/common" default-features = false optional = true +path = "../polkadot/node/core/pvf/common" [dependencies.polkadot-node-core-pvf-execute-worker] -path = "../polkadot/node/core/pvf/execute-worker" default-features = false optional = true +path = "../polkadot/node/core/pvf/execute-worker" [dependencies.polkadot-node-core-pvf-prepare-worker] -path = "../polkadot/node/core/pvf/prepare-worker" default-features = false optional = true +path = "../polkadot/node/core/pvf/prepare-worker" [dependencies.polkadot-node-core-runtime-api] -path = "../polkadot/node/core/runtime-api" default-features = false optional = true +path = "../polkadot/node/core/runtime-api" [dependencies.polkadot-node-metrics] -path = "../polkadot/node/metrics" default-features = false optional = true +path = "../polkadot/node/metrics" [dependencies.polkadot-node-network-protocol] -path = "../polkadot/node/network/protocol" default-features = false optional = true +path = "../polkadot/node/network/protocol" [dependencies.polkadot-node-primitives] -path = "../polkadot/node/primitives" default-features = false optional = true +path = "../polkadot/node/primitives" [dependencies.polkadot-node-subsystem] -path = "../polkadot/node/subsystem" default-features = false optional = true +path = "../polkadot/node/subsystem" [dependencies.polkadot-node-subsystem-types] -path = "../polkadot/node/subsystem-types" default-features = false optional = true +path = "../polkadot/node/subsystem-types" [dependencies.polkadot-node-subsystem-util] -path = "../polkadot/node/subsystem-util" default-features = false optional = true +path = "../polkadot/node/subsystem-util" [dependencies.polkadot-omni-node-lib] -path = "../cumulus/polkadot-omni-node/lib" default-features = false optional = true +path = "../cumulus/polkadot-omni-node/lib" [dependencies.polkadot-overseer] -path = "../polkadot/node/overseer" default-features = false optional = true +path = "../polkadot/node/overseer" [dependencies.polkadot-rpc] -path = "../polkadot/rpc" default-features = false optional = true +path = "../polkadot/rpc" [dependencies.polkadot-service] -path = "../polkadot/node/service" default-features = false optional = true +path = "../polkadot/node/service" [dependencies.polkadot-statement-distribution] -path = "../polkadot/node/network/statement-distribution" default-features = false optional = true +path = "../polkadot/node/network/statement-distribution" [dependencies.polkadot-statement-table] -path = "../polkadot/statement-table" default-features = false optional = true +path = "../polkadot/statement-table" [dependencies.sc-allocator] -path = "../substrate/client/allocator" default-features = false optional = true +path = "../substrate/client/allocator" [dependencies.sc-authority-discovery] -path = "../substrate/client/authority-discovery" default-features = false optional = true +path = "../substrate/client/authority-discovery" [dependencies.sc-basic-authorship] -path = "../substrate/client/basic-authorship" default-features = false optional = true +path = "../substrate/client/basic-authorship" [dependencies.sc-block-builder] -path = "../substrate/client/block-builder" default-features = false optional = true +path = "../substrate/client/block-builder" [dependencies.sc-chain-spec] -path = "../substrate/client/chain-spec" default-features = false optional = true +path = "../substrate/client/chain-spec" [dependencies.sc-cli] -path = "../substrate/client/cli" default-features = false optional = true +path = "../substrate/client/cli" [dependencies.sc-client-api] -path = "../substrate/client/api" default-features = false optional = true +path = "../substrate/client/api" [dependencies.sc-client-db] -path = "../substrate/client/db" default-features = false optional = true +path = "../substrate/client/db" [dependencies.sc-consensus] -path = "../substrate/client/consensus/common" default-features = false optional = true +path = "../substrate/client/consensus/common" [dependencies.sc-consensus-aura] -path = "../substrate/client/consensus/aura" default-features = false optional = true +path = "../substrate/client/consensus/aura" [dependencies.sc-consensus-babe] -path = "../substrate/client/consensus/babe" default-features = false optional = true +path = "../substrate/client/consensus/babe" [dependencies.sc-consensus-babe-rpc] -path = "../substrate/client/consensus/babe/rpc" default-features = false optional = true +path = "../substrate/client/consensus/babe/rpc" [dependencies.sc-consensus-beefy] -path = "../substrate/client/consensus/beefy" default-features = false optional = true +path = "../substrate/client/consensus/beefy" [dependencies.sc-consensus-beefy-rpc] -path = "../substrate/client/consensus/beefy/rpc" default-features = false optional = true +path = "../substrate/client/consensus/beefy/rpc" [dependencies.sc-consensus-epochs] -path = "../substrate/client/consensus/epochs" default-features = false optional = true +path = "../substrate/client/consensus/epochs" [dependencies.sc-consensus-grandpa] -path = "../substrate/client/consensus/grandpa" default-features = false optional = true +path = "../substrate/client/consensus/grandpa" [dependencies.sc-consensus-grandpa-rpc] -path = "../substrate/client/consensus/grandpa/rpc" default-features = false optional = true +path = "../substrate/client/consensus/grandpa/rpc" [dependencies.sc-consensus-manual-seal] -path = "../substrate/client/consensus/manual-seal" default-features = false optional = true +path = "../substrate/client/consensus/manual-seal" [dependencies.sc-consensus-pow] -path = "../substrate/client/consensus/pow" default-features = false optional = true +path = "../substrate/client/consensus/pow" [dependencies.sc-consensus-slots] -path = "../substrate/client/consensus/slots" default-features = false optional = true +path = "../substrate/client/consensus/slots" [dependencies.sc-executor] -path = "../substrate/client/executor" default-features = false optional = true +path = "../substrate/client/executor" [dependencies.sc-executor-common] -path = "../substrate/client/executor/common" default-features = false optional = true +path = "../substrate/client/executor/common" [dependencies.sc-executor-polkavm] -path = "../substrate/client/executor/polkavm" default-features = false optional = true +path = "../substrate/client/executor/polkavm" [dependencies.sc-executor-wasmtime] -path = "../substrate/client/executor/wasmtime" default-features = false optional = true +path = "../substrate/client/executor/wasmtime" [dependencies.sc-informant] -path = "../substrate/client/informant" default-features = false optional = true +path = "../substrate/client/informant" [dependencies.sc-keystore] -path = "../substrate/client/keystore" default-features = false optional = true +path = "../substrate/client/keystore" [dependencies.sc-mixnet] -path = "../substrate/client/mixnet" default-features = false optional = true +path = "../substrate/client/mixnet" [dependencies.sc-network] -path = "../substrate/client/network" default-features = false optional = true +path = "../substrate/client/network" [dependencies.sc-network-common] -path = "../substrate/client/network/common" default-features = false optional = true +path = "../substrate/client/network/common" [dependencies.sc-network-gossip] -path = "../substrate/client/network-gossip" default-features = false optional = true +path = "../substrate/client/network-gossip" [dependencies.sc-network-light] -path = "../substrate/client/network/light" default-features = false optional = true +path = "../substrate/client/network/light" [dependencies.sc-network-statement] -path = "../substrate/client/network/statement" default-features = false optional = true +path = "../substrate/client/network/statement" [dependencies.sc-network-sync] -path = "../substrate/client/network/sync" default-features = false optional = true +path = "../substrate/client/network/sync" [dependencies.sc-network-transactions] -path = "../substrate/client/network/transactions" default-features = false optional = true +path = "../substrate/client/network/transactions" [dependencies.sc-network-types] -path = "../substrate/client/network/types" default-features = false optional = true +path = "../substrate/client/network/types" [dependencies.sc-offchain] -path = "../substrate/client/offchain" default-features = false optional = true +path = "../substrate/client/offchain" [dependencies.sc-proposer-metrics] -path = "../substrate/client/proposer-metrics" default-features = false optional = true +path = "../substrate/client/proposer-metrics" [dependencies.sc-rpc] -path = "../substrate/client/rpc" default-features = false optional = true +path = "../substrate/client/rpc" [dependencies.sc-rpc-api] -path = "../substrate/client/rpc-api" default-features = false optional = true +path = "../substrate/client/rpc-api" [dependencies.sc-rpc-server] -path = "../substrate/client/rpc-servers" default-features = false optional = true +path = "../substrate/client/rpc-servers" [dependencies.sc-rpc-spec-v2] -path = "../substrate/client/rpc-spec-v2" default-features = false optional = true +path = "../substrate/client/rpc-spec-v2" [dependencies.sc-runtime-utilities] -path = "../substrate/client/runtime-utilities" default-features = false optional = true +path = "../substrate/client/runtime-utilities" [dependencies.sc-service] -path = "../substrate/client/service" default-features = false optional = true +path = "../substrate/client/service" [dependencies.sc-state-db] -path = "../substrate/client/state-db" default-features = false optional = true +path = "../substrate/client/state-db" [dependencies.sc-statement-store] -path = "../substrate/client/statement-store" default-features = false optional = true +path = "../substrate/client/statement-store" [dependencies.sc-storage-monitor] -path = "../substrate/client/storage-monitor" default-features = false optional = true +path = "../substrate/client/storage-monitor" [dependencies.sc-sync-state-rpc] -path = "../substrate/client/sync-state-rpc" default-features = false optional = true +path = "../substrate/client/sync-state-rpc" [dependencies.sc-sysinfo] -path = "../substrate/client/sysinfo" default-features = false optional = true +path = "../substrate/client/sysinfo" [dependencies.sc-telemetry] -path = "../substrate/client/telemetry" default-features = false optional = true +path = "../substrate/client/telemetry" [dependencies.sc-tracing] -path = "../substrate/client/tracing" default-features = false optional = true +path = "../substrate/client/tracing" [dependencies.sc-transaction-pool] -path = "../substrate/client/transaction-pool" default-features = false optional = true +path = "../substrate/client/transaction-pool" [dependencies.sc-transaction-pool-api] -path = "../substrate/client/transaction-pool/api" default-features = false optional = true +path = "../substrate/client/transaction-pool/api" [dependencies.sc-utils] -path = "../substrate/client/utils" default-features = false optional = true +path = "../substrate/client/utils" [dependencies.snowbridge-runtime-test-common] -path = "../bridges/snowbridge/runtime/test-common" default-features = false optional = true +path = "../bridges/snowbridge/runtime/test-common" [dependencies.sp-blockchain] -path = "../substrate/primitives/blockchain" default-features = false optional = true +path = "../substrate/primitives/blockchain" [dependencies.sp-consensus] -path = "../substrate/primitives/consensus/common" default-features = false optional = true +path = "../substrate/primitives/consensus/common" [dependencies.sp-core-hashing] -path = "../substrate/deprecated/hashing" default-features = false optional = true +path = "../substrate/deprecated/hashing" [dependencies.sp-core-hashing-proc-macro] -path = "../substrate/deprecated/hashing/proc-macro" default-features = false optional = true +path = "../substrate/deprecated/hashing/proc-macro" [dependencies.sp-database] -path = "../substrate/primitives/database" default-features = false optional = true +path = "../substrate/primitives/database" [dependencies.sp-maybe-compressed-blob] -path = "../substrate/primitives/maybe-compressed-blob" default-features = false optional = true +path = "../substrate/primitives/maybe-compressed-blob" [dependencies.sp-panic-handler] -path = "../substrate/primitives/panic-handler" default-features = false optional = true +path = "../substrate/primitives/panic-handler" [dependencies.sp-rpc] -path = "../substrate/primitives/rpc" default-features = false optional = true +path = "../substrate/primitives/rpc" [dependencies.staging-chain-spec-builder] -path = "../substrate/bin/utils/chain-spec-builder" default-features = false optional = true +path = "../substrate/bin/utils/chain-spec-builder" [dependencies.staging-node-inspect] -path = "../substrate/bin/node/inspect" default-features = false optional = true +path = "../substrate/bin/node/inspect" [dependencies.staging-tracking-allocator] -path = "../polkadot/node/tracking-allocator" default-features = false optional = true +path = "../polkadot/node/tracking-allocator" [dependencies.subkey] -path = "../substrate/bin/utils/subkey" default-features = false optional = true +path = "../substrate/bin/utils/subkey" [dependencies.substrate-build-script-utils] -path = "../substrate/utils/build-script-utils" default-features = false optional = true +path = "../substrate/utils/build-script-utils" [dependencies.substrate-frame-rpc-support] -path = "../substrate/utils/frame/rpc/support" default-features = false optional = true +path = "../substrate/utils/frame/rpc/support" [dependencies.substrate-frame-rpc-system] -path = "../substrate/utils/frame/rpc/system" default-features = false optional = true +path = "../substrate/utils/frame/rpc/system" [dependencies.substrate-prometheus-endpoint] -path = "../substrate/utils/prometheus" default-features = false optional = true +path = "../substrate/utils/prometheus" [dependencies.substrate-rpc-client] -path = "../substrate/utils/frame/rpc/client" default-features = false optional = true +path = "../substrate/utils/frame/rpc/client" [dependencies.substrate-state-trie-migration-rpc] -path = "../substrate/utils/frame/rpc/state-trie-migration-rpc" default-features = false optional = true +path = "../substrate/utils/frame/rpc/state-trie-migration-rpc" [dependencies.substrate-wasm-builder] -path = "../substrate/utils/wasm-builder" default-features = false optional = true +path = "../substrate/utils/wasm-builder" [dependencies.tracing-gum] -path = "../polkadot/node/gum" default-features = false optional = true +path = "../polkadot/node/gum" [dependencies.xcm-emulator] -path = "../cumulus/xcm/xcm-emulator" default-features = false optional = true +path = "../cumulus/xcm/xcm-emulator" [dependencies.xcm-simulator] -path = "../polkadot/xcm/xcm-simulator" default-features = false optional = true +path = "../polkadot/xcm/xcm-simulator" [package.metadata.docs.rs] features = ["node", "runtime-full"] From 97d3b8600fa16e72eab2e79f9917d852da2971d7 Mon Sep 17 00:00:00 2001 From: FT <140458077+zeevick10@users.noreply.github.com> Date: Fri, 20 Dec 2024 18:26:54 +0100 Subject: [PATCH 201/340] fix: typos in documentation files (#6961) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrected WebsSocket to WebSocket Co-authored-by: Bastian Köcher --- cumulus/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cumulus/README.md b/cumulus/README.md index 7e145ad7b4ab..0c47df999022 100644 --- a/cumulus/README.md +++ b/cumulus/README.md @@ -60,7 +60,7 @@ polkadot-parachain \ ``` #### External Relay Chain Node -An external relay chain node is connected via WebsSocket RPC by using the `--relay-chain-rpc-urls` command line +An external relay chain node is connected via WebSocket RPC by using the `--relay-chain-rpc-urls` command line argument. This option accepts one or more space-separated WebSocket URLs to a full relay chain node. By default, only the first URL will be used, with the rest as a backup in case the connection to the first node is lost. From d0c8a0734591c420041692cf8a1c095a51b22f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 20 Dec 2024 18:39:08 +0100 Subject: [PATCH 202/340] grandpa: Ensure `WarpProof` stays in its limits (#6963) There was the chance that a `WarpProof` was bigger than the maximum warp sync proof size. This could have happened when inserting the last justification, which then may pushed the total proof size above the maximum. The solution is simply to ensure that the last justfication also fits into the limits. Close: https://github.com/paritytech/polkadot-sdk/issues/6957 --------- Co-authored-by: command-bot <> --- prdoc/pr_6963.prdoc | 10 ++++++++++ .../client/consensus/grandpa/src/warp_proof.rs | 16 +++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_6963.prdoc diff --git a/prdoc/pr_6963.prdoc b/prdoc/pr_6963.prdoc new file mode 100644 index 000000000000..7657349277b3 --- /dev/null +++ b/prdoc/pr_6963.prdoc @@ -0,0 +1,10 @@ +title: 'grandpa: Ensure `WarpProof` stays in its limits' +doc: +- audience: Node Dev + description: |- + There was the chance that a `WarpProof` was bigger than the maximum warp sync proof size. This could have happened when inserting the last justification, which then may pushed the total proof size above the maximum. The solution is simply to ensure that the last justfication also fits into the limits. + + Close: https://github.com/paritytech/polkadot-sdk/issues/6957 +crates: +- name: sc-consensus-grandpa + bump: patch diff --git a/substrate/client/consensus/grandpa/src/warp_proof.rs b/substrate/client/consensus/grandpa/src/warp_proof.rs index a79581b1e9f1..ada3a45e186e 100644 --- a/substrate/client/consensus/grandpa/src/warp_proof.rs +++ b/substrate/client/consensus/grandpa/src/warp_proof.rs @@ -174,10 +174,20 @@ impl WarpSyncProof { let header = blockchain.header(latest_justification.target().1)? .expect("header hash corresponds to a justification in db; must exist in db as well; qed."); - proofs.push(WarpSyncFragment { header, justification: latest_justification }) + let proof = WarpSyncFragment { header, justification: latest_justification }; + + // Check for the limit. We remove some bytes from the maximum size, because we're + // only counting the size of the `WarpSyncFragment`s. The extra margin is here + // to leave room for rest of the data (the size of the `Vec` and the boolean). + if proofs_encoded_len + proof.encoded_size() >= MAX_WARP_SYNC_PROOF_SIZE - 50 { + false + } else { + proofs.push(proof); + true + } + } else { + true } - - true }; let final_outcome = WarpSyncProof { proofs, is_finished }; From f9cdf41ad9c146324eb1c9d40048daa400501a0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=B3nal=20Murray?= Date: Sat, 21 Dec 2024 22:29:13 +0100 Subject: [PATCH 203/340] [pallet-broker] add extrinsic to reserve a system core without having to wait two sale boundaries (#4273) When calling the reserve extrinsic after sales have started, the assignment will be reserved, but two sale period boundaries must pass before the core is actually assigned. Since this can take between 28 and 56 days on production networks, a new extrinsic is introduced to shorten the timeline. This essentially performs three actions: 1. Reserve it (applies after two sale boundaries) 2. Add it to the Workplan for the next sale period 3. Add it to the Workplan for the rest of the current sale period from the next timeslice to be commmitted. The caller must ensure that a core is first added, with most relay chain implementations having a delay of two session boundaries until it comes into effect. Alternatively the extrinsic can be called on a core whose workload can be clobbered from now until the reservation kicks in (the sale period after the next). Any workplan entries for that core at other timeslices should be first removed by the caller. --------- Co-authored-by: command-bot <> --- .../src/weights/pallet_broker.rs | 18 ++ .../src/weights/pallet_broker.rs | 18 ++ prdoc/pr_4273.prdoc | 19 ++ substrate/frame/broker/src/benchmarking.rs | 41 +++ .../frame/broker/src/dispatchable_impls.rs | 21 ++ substrate/frame/broker/src/lib.rs | 26 ++ substrate/frame/broker/src/tests.rs | 303 ++++++++++++++++++ substrate/frame/broker/src/weights.rs | 35 ++ 8 files changed, 481 insertions(+) create mode 100644 prdoc/pr_4273.prdoc diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs index 5cb01f62cd26..3e4bbf379c3f 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_broker.rs @@ -555,6 +555,24 @@ impl pallet_broker::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `Broker::SaleInfo` (r:1 w:0) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:2) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn force_reserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `11125` + // Estimated: `13506` + // Minimum execution time: 32_286_000 picoseconds. + Weight::from_parts(33_830_000, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(401), added: 896, mode: `MaxEncodedLen`) fn swap_leases() -> Weight { diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs index ad71691b2174..a0eee2d99efa 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_broker.rs @@ -553,6 +553,24 @@ impl pallet_broker::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `Broker::SaleInfo` (r:1 w:0) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(12021), added: 12516, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:2) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn force_reserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `11125` + // Estimated: `13506` + // Minimum execution time: 31_464_000 picoseconds. + Weight::from_parts(32_798_000, 0) + .saturating_add(Weight::from_parts(0, 13506)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(81), added: 576, mode: `MaxEncodedLen`) fn swap_leases() -> Weight { diff --git a/prdoc/pr_4273.prdoc b/prdoc/pr_4273.prdoc new file mode 100644 index 000000000000..1ff0a5782a41 --- /dev/null +++ b/prdoc/pr_4273.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[pallet-broker] add extrinsic to reserve a system core without having to wait two sale boundaries" + +doc: + - audience: Runtime User + description: | + When calling the reserve extrinsic after sales have started, the assignment will be reserved, + but two sale period boundaries must pass before the core is actually assigned. A new + `force_reserve` extrinsic is introduced to allow a core to be immediately assigned. + +crates: + - name: pallet-broker + bump: major + - name: coretime-rococo-runtime + bump: patch + - name: coretime-westend-runtime + bump: patch diff --git a/substrate/frame/broker/src/benchmarking.rs b/substrate/frame/broker/src/benchmarking.rs index 044689b254c5..516518740f7d 100644 --- a/substrate/frame/broker/src/benchmarking.rs +++ b/substrate/frame/broker/src/benchmarking.rs @@ -1016,6 +1016,47 @@ mod benches { Ok(()) } + #[benchmark] + fn force_reserve() -> Result<(), BenchmarkError> { + Configuration::::put(new_config_record::()); + // Assume Reservations to be almost filled for worst case. + let reservation_count = T::MaxReservedCores::get().saturating_sub(1); + setup_reservations::(reservation_count); + + // Assume leases to be filled for worst case + setup_leases::(T::MaxLeasedCores::get(), 1, 10); + + let origin = + T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + + // Sales must be started. + Broker::::do_start_sales(100u32.into(), CoreIndex::try_from(reservation_count).unwrap()) + .map_err(|_| BenchmarkError::Weightless)?; + + // Add a core. + let status = Status::::get().unwrap(); + Broker::::do_request_core_count(status.core_count + 1).unwrap(); + + advance_to::(T::TimeslicePeriod::get().try_into().ok().unwrap()); + let schedule = new_schedule(); + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, schedule.clone(), status.core_count); + + assert_eq!(Reservations::::decode_len().unwrap(), T::MaxReservedCores::get() as usize); + + let sale_info = SaleInfo::::get().unwrap(); + assert_eq!( + Workplan::::get((sale_info.region_begin, status.core_count)), + Some(schedule.clone()) + ); + // We called at timeslice 1, therefore 2 was already processed and 3 is the next possible + // assignment point. + assert_eq!(Workplan::::get((3, status.core_count)), Some(schedule)); + + Ok(()) + } + #[benchmark] fn swap_leases() -> Result<(), BenchmarkError> { let admin_origin = diff --git a/substrate/frame/broker/src/dispatchable_impls.rs b/substrate/frame/broker/src/dispatchable_impls.rs index 733d96625da0..489be12bdd15 100644 --- a/substrate/frame/broker/src/dispatchable_impls.rs +++ b/substrate/frame/broker/src/dispatchable_impls.rs @@ -60,6 +60,27 @@ impl Pallet { Ok(()) } + pub(crate) fn do_force_reserve(workload: Schedule, core: CoreIndex) -> DispatchResult { + // Sales must have started, otherwise reserve is equivalent. + let sale = SaleInfo::::get().ok_or(Error::::NoSales)?; + + // Reserve - starts at second sale period boundary from now. + Self::do_reserve(workload.clone())?; + + // Add to workload - grants one region from the next sale boundary. + Workplan::::insert((sale.region_begin, core), &workload); + + // Assign now until the next sale boundary unless the next timeslice is already the sale + // boundary. + let status = Status::::get().ok_or(Error::::Uninitialized)?; + let timeslice = status.last_committed_timeslice.saturating_add(1); + if timeslice < sale.region_begin { + Workplan::::insert((timeslice, core), &workload); + } + + Ok(()) + } + pub(crate) fn do_set_lease(task: TaskId, until: Timeslice) -> DispatchResult { let mut r = Leases::::get(); ensure!(until > Self::current_timeslice(), Error::::AlreadyExpired); diff --git a/substrate/frame/broker/src/lib.rs b/substrate/frame/broker/src/lib.rs index ed16b98d26cc..01368fd6404d 100644 --- a/substrate/frame/broker/src/lib.rs +++ b/substrate/frame/broker/src/lib.rs @@ -585,6 +585,9 @@ pub mod pallet { /// Reserve a core for a workload. /// + /// The workload will be given a reservation, but two sale period boundaries must pass + /// before the core is actually assigned. + /// /// - `origin`: Must be Root or pass `AdminOrigin`. /// - `workload`: The workload which should be permanently placed on a core. #[pallet::call_index(1)] @@ -943,6 +946,29 @@ pub mod pallet { Ok(()) } + /// Reserve a core for a workload immediately. + /// + /// - `origin`: Must be Root or pass `AdminOrigin`. + /// - `workload`: The workload which should be permanently placed on a core starting + /// immediately. + /// - `core`: The core to which the assignment should be made until the reservation takes + /// effect. It is left to the caller to either add this new core or reassign any other + /// tasks to this existing core. + /// + /// This reserves the workload and then injects the workload into the Workplan for the next + /// two sale periods. This overwrites any existing assignments for this core at the start of + /// the next sale period. + #[pallet::call_index(23)] + pub fn force_reserve( + origin: OriginFor, + workload: Schedule, + core: CoreIndex, + ) -> DispatchResultWithPostInfo { + T::AdminOrigin::ensure_origin_or_root(origin)?; + Self::do_force_reserve(workload, core)?; + Ok(Pays::No.into()) + } + #[pallet::call_index(99)] #[pallet::weight(T::WeightInfo::swap_leases())] pub fn swap_leases(origin: OriginFor, id: TaskId, other: TaskId) -> DispatchResult { diff --git a/substrate/frame/broker/src/tests.rs b/substrate/frame/broker/src/tests.rs index f3fd5234e4ca..a130a2050d9a 100644 --- a/substrate/frame/broker/src/tests.rs +++ b/substrate/frame/broker/src/tests.rs @@ -1837,3 +1837,306 @@ fn start_sales_sets_correct_core_count() { System::assert_has_event(Event::::CoreCountRequested { core_count: 9 }.into()); }) } + +// Reservations currently need two sale period boundaries to pass before coming into effect. +#[test] +fn reserve_works() { + TestExt::new().execute_with(|| { + assert_ok!(Broker::do_start_sales(100, 0)); + // Advance forward from start_sales, but not into the first sale. + advance_to(1); + + let system_workload = Schedule::truncate_from(vec![ScheduleItem { + mask: CoreMask::complete(), + assignment: Task(1004), + }]); + + // This shouldn't work, as the reservation will never be assigned a core unless one is + // available. + // assert_noop!(Broker::do_reserve(system_workload.clone()), Error::::Unavailable); + + // Add another core and create the reservation. + let status = Status::::get().unwrap(); + assert_ok!(Broker::request_core_count(RuntimeOrigin::root(), status.core_count + 1)); + assert_ok!(Broker::reserve(RuntimeOrigin::root(), system_workload.clone())); + + // This is added to reservations. + System::assert_last_event( + Event::ReservationMade { index: 0, workload: system_workload.clone() }.into(), + ); + assert_eq!(Reservations::::get(), vec![system_workload.clone()]); + + // But not yet in workplan for any of the next few regions. + for i in 0..20 { + assert_eq!(Workplan::::get((i, 0)), None); + } + // And it hasn't been assigned a core. + assert_eq!(CoretimeTrace::get(), vec![]); + + // Go to next sale. Rotate sale puts it in the workplan. + advance_sale_period(); + assert_eq!(Workplan::::get((7, 0)), Some(system_workload.clone())); + // But it still hasn't been assigned a core. + assert_eq!(CoretimeTrace::get(), vec![]); + + // Go to the second sale after reserving. + advance_sale_period(); + // Core is assigned at block 14 (timeslice 7) after being reserved all the way back at + // timeslice 1! Since the mock periods are 3 timeslices long, this means that reservations + // made in period 0 will only come into effect in period 2. + assert_eq!( + CoretimeTrace::get(), + vec![( + 12, + AssignCore { + core: 0, + begin: 14, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + )] + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 14, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + + // And it's in the workplan for the next period. + assert_eq!(Workplan::::get((10, 0)), Some(system_workload.clone())); + }); +} + +// We can use a hack to accelerate this by injecting it into the workplan. +#[test] +fn can_reserve_workloads_quickly() { + TestExt::new().execute_with(|| { + // Start sales. + assert_ok!(Broker::do_start_sales(100, 0)); + advance_to(2); + + let system_workload = Schedule::truncate_from(vec![ScheduleItem { + mask: CoreMask::complete(), + assignment: Task(1004), + }]); + + // This shouldn't work, as the reservation will never be assigned a core unless one is + // available. + // assert_noop!(Broker::do_reserve(system_workload.clone()), Error::::Unavailable); + + // Add another core and create the reservation. + let core_count = Status::::get().unwrap().core_count; + assert_ok!(Broker::request_core_count(RuntimeOrigin::root(), core_count + 1)); + assert_ok!(Broker::reserve(RuntimeOrigin::root(), system_workload.clone())); + + // These are the additional steps to onboard this immediately. + let core_index = core_count; + // In a real network this would call the relay chain + // `assigner_coretime::assign_core` extrinsic directly. + ::assign_core( + core_index, + 2, + vec![(Task(1004), 57600)], + None, + ); + // Inject into the workplan to ensure it's scheduled in the next rotate_sale. + Workplan::::insert((4, core_index), system_workload.clone()); + + // Reservation is added for the workload. + System::assert_has_event( + Event::ReservationMade { index: 0, workload: system_workload.clone() }.into(), + ); + System::assert_has_event(Event::CoreCountRequested { core_count: 1 }.into()); + + // It is also in the workplan for the next region. + assert_eq!(Workplan::::get((4, 0)), Some(system_workload.clone())); + + // Go to next sale. Rotate sale puts it in the workplan. + advance_sale_period(); + assert_eq!(Workplan::::get((7, 0)), Some(system_workload.clone())); + + // Go to the second sale after reserving. + advance_sale_period(); + + // Check the trace to ensure it has a core in every region. + assert_eq!( + CoretimeTrace::get(), + vec![ + ( + 2, + AssignCore { + core: 0, + begin: 2, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ), + ( + 6, + AssignCore { + core: 0, + begin: 8, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ), + ( + 12, + AssignCore { + core: 0, + begin: 14, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ) + ] + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 8, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 14, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 14, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + + // And it's in the workplan for the next period. + assert_eq!(Workplan::::get((10, 0)), Some(system_workload.clone())); + }); +} + +// Add an extrinsic to do it properly. +#[test] +fn force_reserve_works() { + TestExt::new().execute_with(|| { + let system_workload = Schedule::truncate_from(vec![ScheduleItem { + mask: CoreMask::complete(), + assignment: Task(1004), + }]); + + // Not intended to work before sales are started. + assert_noop!( + Broker::force_reserve(RuntimeOrigin::root(), system_workload.clone(), 0), + Error::::NoSales + ); + + // Start sales. + assert_ok!(Broker::do_start_sales(100, 0)); + advance_to(1); + + // Add a new core. With the mock this is instant, with current relay implementation it + // takes two sessions to come into effect. + assert_ok!(Broker::do_request_core_count(1)); + + // Force reserve should now work. + assert_ok!(Broker::force_reserve(RuntimeOrigin::root(), system_workload.clone(), 0)); + + // Reservation is added for the workload. + System::assert_has_event( + Event::ReservationMade { index: 0, workload: system_workload.clone() }.into(), + ); + System::assert_has_event(Event::CoreCountRequested { core_count: 1 }.into()); + assert_eq!(Reservations::::get(), vec![system_workload.clone()]); + + // Advance to where that timeslice will be committed. + advance_to(3); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 4, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + + // It is also in the workplan for the next region. + assert_eq!(Workplan::::get((4, 0)), Some(system_workload.clone())); + + // Go to next sale. Rotate sale puts it in the workplan. + advance_sale_period(); + assert_eq!(Workplan::::get((7, 0)), Some(system_workload.clone())); + + // Go to the second sale after reserving. + advance_sale_period(); + + // Check the trace to ensure it has a core in every region. + assert_eq!( + CoretimeTrace::get(), + vec![ + ( + 2, + AssignCore { + core: 0, + begin: 4, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ), + ( + 6, + AssignCore { + core: 0, + begin: 8, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ), + ( + 12, + AssignCore { + core: 0, + begin: 14, + assignment: vec![(Task(1004), 57600)], + end_hint: None + } + ) + ] + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 8, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 14, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + System::assert_has_event( + Event::CoreAssigned { + core: 0, + when: 14, + assignment: vec![(CoreAssignment::Task(1004), 57600)], + } + .into(), + ); + + // And it's in the workplan for the next period. + assert_eq!(Workplan::::get((10, 0)), Some(system_workload.clone())); + }); +} diff --git a/substrate/frame/broker/src/weights.rs b/substrate/frame/broker/src/weights.rs index 894fed5a6a00..87e588551661 100644 --- a/substrate/frame/broker/src/weights.rs +++ b/substrate/frame/broker/src/weights.rs @@ -77,6 +77,7 @@ pub trait WeightInfo { fn notify_core_count() -> Weight; fn notify_revenue() -> Weight; fn do_tick_base() -> Weight; + fn force_reserve() -> Weight; fn swap_leases() -> Weight; fn enable_auto_renew() -> Weight; fn disable_auto_renew() -> Weight; @@ -487,6 +488,23 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `Broker::SaleInfo` (r:1 w:0) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:2) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn force_reserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `5253` + // Estimated: `7496` + // Minimum execution time: 28_363_000 picoseconds. + Weight::from_parts(29_243_000, 7496) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`) fn swap_leases() -> Weight { @@ -944,6 +962,23 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `Broker::SaleInfo` (r:1 w:0) + /// Proof: `Broker::SaleInfo` (`max_values`: Some(1), `max_size`: Some(57), added: 552, mode: `MaxEncodedLen`) + /// Storage: `Broker::Reservations` (r:1 w:1) + /// Proof: `Broker::Reservations` (`max_values`: Some(1), `max_size`: Some(6011), added: 6506, mode: `MaxEncodedLen`) + /// Storage: `Broker::Status` (r:1 w:0) + /// Proof: `Broker::Status` (`max_values`: Some(1), `max_size`: Some(18), added: 513, mode: `MaxEncodedLen`) + /// Storage: `Broker::Workplan` (r:0 w:2) + /// Proof: `Broker::Workplan` (`max_values`: None, `max_size`: Some(1216), added: 3691, mode: `MaxEncodedLen`) + fn force_reserve() -> Weight { + // Proof Size summary in bytes: + // Measured: `5253` + // Estimated: `7496` + // Minimum execution time: 28_363_000 picoseconds. + Weight::from_parts(29_243_000, 7496) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } /// Storage: `Broker::Leases` (r:1 w:1) /// Proof: `Broker::Leases` (`max_values`: Some(1), `max_size`: Some(41), added: 536, mode: `MaxEncodedLen`) fn swap_leases() -> Weight { From 88d900afbff7ebe600dfe5e3ee9f87fe52c93d1f Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Mon, 23 Dec 2024 06:18:58 +0800 Subject: [PATCH 204/340] Make pallet-recovery supports `BlockNumberProvider` (#6446) Make pallet-recovery supports `BlockNumberProvider`. Part of #6297. --- Polkadot address: 156HGo9setPcU2qhFMVWLkcmtCEGySLwNqa3DaEiYSWtte4Y --------- Co-authored-by: Guillaume Thiolliere Co-authored-by: GitHub Action --- polkadot/runtime/rococo/src/lib.rs | 1 + polkadot/runtime/westend/src/lib.rs | 1 + prdoc/pr_6446.prdoc | 16 ++++++++++++++++ substrate/bin/node/runtime/src/lib.rs | 1 + substrate/frame/recovery/src/lib.rs | 25 ++++++++++++++++--------- substrate/frame/recovery/src/mock.rs | 1 + 6 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 prdoc/pr_6446.prdoc diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index da4f039624a3..4034f8bc1431 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -792,6 +792,7 @@ impl pallet_recovery::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type RuntimeCall = RuntimeCall; + type BlockNumberProvider = System; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; type FriendDepositFactor = FriendDepositFactor; diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index cbf2e02ce428..cd8eb4d2505a 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1018,6 +1018,7 @@ impl pallet_recovery::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type RuntimeCall = RuntimeCall; + type BlockNumberProvider = System; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; type FriendDepositFactor = FriendDepositFactor; diff --git a/prdoc/pr_6446.prdoc b/prdoc/pr_6446.prdoc new file mode 100644 index 000000000000..3bfe7d0c7a60 --- /dev/null +++ b/prdoc/pr_6446.prdoc @@ -0,0 +1,16 @@ +title: Make pallet-recovery supports `BlockNumberProvider` +doc: +- audience: Runtime Dev + description: |- + pallet-recovery now allows configuring the block provider to be utilized within this pallet. This block is employed for the delay in the recovery process. + + A new associated type has been introduced in the `Config` trait: `BlockNumberProvider`. This can be assigned to `System` to maintain the previous behavior, or it can be set to another block number provider, such as `RelayChain`. + + If the block provider is configured with a value different from `System`, a migration will be necessary for the `Recoverable` and `ActiveRecoveries` storage items. +crates: +- name: rococo-runtime + bump: major +- name: westend-runtime + bump: major +- name: pallet-recovery + bump: major diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index faffcd23fbcf..45ae378cc00e 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1653,6 +1653,7 @@ impl pallet_recovery::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = pallet_recovery::weights::SubstrateWeight; type RuntimeCall = RuntimeCall; + type BlockNumberProvider = System; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; type FriendDepositFactor = FriendDepositFactor; diff --git a/substrate/frame/recovery/src/lib.rs b/substrate/frame/recovery/src/lib.rs index f8622880538e..d8f3c33fbea9 100644 --- a/substrate/frame/recovery/src/lib.rs +++ b/substrate/frame/recovery/src/lib.rs @@ -156,7 +156,10 @@ use alloc::{boxed::Box, vec::Vec}; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{CheckedAdd, CheckedMul, Dispatchable, SaturatedConversion, StaticLookup}, + traits::{ + BlockNumberProvider, CheckedAdd, CheckedMul, Dispatchable, SaturatedConversion, + StaticLookup, + }, RuntimeDebug, }; @@ -178,11 +181,12 @@ mod mock; mod tests; pub mod weights; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; - +type BlockNumberFromProviderOf = + <::BlockNumberProvider as BlockNumberProvider>::BlockNumber; type FriendsOf = BoundedVec<::AccountId, ::MaxFriends>; -type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; /// An active recovery process. #[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen)] @@ -190,7 +194,7 @@ pub struct ActiveRecovery { /// The block number when the recovery process started. created: BlockNumber, /// The amount held in reserve of the `depositor`, - /// To be returned once this recovery process is closed. + /// to be returned once this recovery process is closed. deposit: Balance, /// The friends which have vouched so far. Always sorted. friends: Friends, @@ -236,6 +240,9 @@ pub mod pallet { + GetDispatchInfo + From>; + /// Provider for the block number. Normally this is the `frame_system` pallet. + type BlockNumberProvider: BlockNumberProvider; + /// The currency mechanism. type Currency: ReservableCurrency; @@ -339,7 +346,7 @@ pub mod pallet { _, Twox64Concat, T::AccountId, - RecoveryConfig, BalanceOf, FriendsOf>, + RecoveryConfig, BalanceOf, FriendsOf>, >; /// Active recovery attempts. @@ -354,7 +361,7 @@ pub mod pallet { T::AccountId, Twox64Concat, T::AccountId, - ActiveRecovery, BalanceOf, FriendsOf>, + ActiveRecovery, BalanceOf, FriendsOf>, >; /// The list of allowed proxy accounts. @@ -445,7 +452,7 @@ pub mod pallet { origin: OriginFor, friends: Vec, threshold: u16, - delay_period: BlockNumberFor, + delay_period: BlockNumberFromProviderOf, ) -> DispatchResult { let who = ensure_signed(origin)?; // Check account is not already set up for recovery @@ -511,7 +518,7 @@ pub mod pallet { T::Currency::reserve(&who, recovery_deposit)?; // Create an active recovery status let recovery_status = ActiveRecovery { - created: >::block_number(), + created: T::BlockNumberProvider::current_block_number(), deposit: recovery_deposit, friends: Default::default(), }; @@ -596,7 +603,7 @@ pub mod pallet { Self::active_recovery(&account, &who).ok_or(Error::::NotStarted)?; ensure!(!Proxy::::contains_key(&who), Error::::AlreadyProxy); // Make sure the delay period has passed - let current_block_number = >::block_number(); + let current_block_number = T::BlockNumberProvider::current_block_number(); let recoverable_block_number = active_recovery .created .checked_add(&recovery_config.delay_period) diff --git a/substrate/frame/recovery/src/mock.rs b/substrate/frame/recovery/src/mock.rs index 8e30cbe997e1..3930db82d6c7 100644 --- a/substrate/frame/recovery/src/mock.rs +++ b/substrate/frame/recovery/src/mock.rs @@ -66,6 +66,7 @@ impl Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type RuntimeCall = RuntimeCall; + type BlockNumberProvider = System; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; type FriendDepositFactor = FriendDepositFactor; From ca7817922148c1e6f6856138998f7135f42f3f4f Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Mon, 23 Dec 2024 18:20:37 +0800 Subject: [PATCH 205/340] Update prometheus binding failure logging format (#6979) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using `{:#?}` for the error details is a bit annoying, this change makes a more consistent formatting style for error messages. --------- Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> Co-authored-by: Sebastian Kunert --- prdoc/pr_6979.prdoc | 8 ++++++++ substrate/utils/prometheus/src/lib.rs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_6979.prdoc diff --git a/prdoc/pr_6979.prdoc b/prdoc/pr_6979.prdoc new file mode 100644 index 000000000000..fae7feeec2df --- /dev/null +++ b/prdoc/pr_6979.prdoc @@ -0,0 +1,8 @@ +title: Update prometheus binding failure logging format +doc: +- audience: Node Dev + description: |- + Using `{:#?}` for the error details is a bit annoying, this change makes a more consistent formatting style for error messages. +crates: +- name: substrate-prometheus-endpoint + bump: patch diff --git a/substrate/utils/prometheus/src/lib.rs b/substrate/utils/prometheus/src/lib.rs index 5edac2e6650f..ae39cb4a7dd3 100644 --- a/substrate/utils/prometheus/src/lib.rs +++ b/substrate/utils/prometheus/src/lib.rs @@ -87,7 +87,7 @@ async fn request_metrics( /// to serve metrics. pub async fn init_prometheus(prometheus_addr: SocketAddr, registry: Registry) -> Result<(), Error> { let listener = tokio::net::TcpListener::bind(&prometheus_addr).await.map_err(|e| { - log::error!(target: "prometheus", "Error binding to '{:#?}': {:#?}", prometheus_addr, e); + log::error!(target: "prometheus", "Error binding to '{prometheus_addr:?}': {e:?}"); Error::PortInUse(prometheus_addr) })?; From b7afe48ed0bfef30836e7ca6359c2d8bb594d16e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 27 Dec 2024 13:43:13 +0100 Subject: [PATCH 206/340] paras-registrar: Improve error reporting (#6989) This pr improves the error reporting by paras registrar when an owner wants to access a locked parachain. Closes: https://github.com/paritytech/polkadot-sdk/issues/6745 --------- Co-authored-by: command-bot <> --- .../runtime/common/src/paras_registrar/mod.rs | 31 ++++++++++--------- .../common/src/paras_registrar/tests.rs | 2 +- prdoc/pr_6989.prdoc | 10 ++++++ 3 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 prdoc/pr_6989.prdoc diff --git a/polkadot/runtime/common/src/paras_registrar/mod.rs b/polkadot/runtime/common/src/paras_registrar/mod.rs index 07832bba18ed..aed0729c9d51 100644 --- a/polkadot/runtime/common/src/paras_registrar/mod.rs +++ b/polkadot/runtime/common/src/paras_registrar/mod.rs @@ -561,15 +561,16 @@ impl Pallet { origin: ::RuntimeOrigin, id: ParaId, ) -> DispatchResult { - ensure_signed(origin.clone()) - .map_err(|e| e.into()) - .and_then(|who| -> DispatchResult { - let para_info = Paras::::get(id).ok_or(Error::::NotRegistered)?; + if let Ok(who) = ensure_signed(origin.clone()) { + let para_info = Paras::::get(id).ok_or(Error::::NotRegistered)?; + + if para_info.manager == who { ensure!(!para_info.is_locked(), Error::::ParaLocked); - ensure!(para_info.manager == who, Error::::NotOwner); - Ok(()) - }) - .or_else(|_| -> DispatchResult { Self::ensure_root_or_para(origin, id) }) + return Ok(()) + } + } + + Self::ensure_root_or_para(origin, id) } /// Ensure the origin is one of Root or the `para` itself. @@ -577,14 +578,14 @@ impl Pallet { origin: ::RuntimeOrigin, id: ParaId, ) -> DispatchResult { - if let Ok(caller_id) = ensure_parachain(::RuntimeOrigin::from(origin.clone())) - { - // Check if matching para id... - ensure!(caller_id == id, Error::::NotOwner); - } else { - // Check if root... - ensure_root(origin.clone())?; + if ensure_root(origin.clone()).is_ok() { + return Ok(()) } + + let caller_id = ensure_parachain(::RuntimeOrigin::from(origin))?; + // Check if matching para id... + ensure!(caller_id == id, Error::::NotOwner); + Ok(()) } diff --git a/polkadot/runtime/common/src/paras_registrar/tests.rs b/polkadot/runtime/common/src/paras_registrar/tests.rs index 252de8f349da..66fef31c9afd 100644 --- a/polkadot/runtime/common/src/paras_registrar/tests.rs +++ b/polkadot/runtime/common/src/paras_registrar/tests.rs @@ -442,7 +442,7 @@ fn para_lock_works() { // Owner cannot pass origin check when checking lock assert_noop!( mock::Registrar::ensure_root_para_or_owner(RuntimeOrigin::signed(1), para_id), - BadOrigin + Error::::ParaLocked, ); // Owner cannot remove lock. assert_noop!(mock::Registrar::remove_lock(RuntimeOrigin::signed(1), para_id), BadOrigin); diff --git a/prdoc/pr_6989.prdoc b/prdoc/pr_6989.prdoc new file mode 100644 index 000000000000..86c56698d41e --- /dev/null +++ b/prdoc/pr_6989.prdoc @@ -0,0 +1,10 @@ +title: 'paras-registrar: Improve error reporting' +doc: +- audience: Runtime User + description: |- + This pr improves the error reporting by paras registrar when an owner wants to access a locked parachain. + + Closes: https://github.com/paritytech/polkadot-sdk/issues/6745 +crates: +- name: polkadot-runtime-common + bump: patch From cdf3a2dc1385debf50096d54d4abf838c6cad4f7 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Mon, 30 Dec 2024 06:52:03 +0800 Subject: [PATCH 207/340] Migrate inclusion benchmark to v2 (#6368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate inclusion benchmark to v2. --- Polkadot address: 156HGo9setPcU2qhFMVWLkcmtCEGySLwNqa3DaEiYSWtte4Y --------- Co-authored-by: GitHub Action Co-authored-by: Bastian Köcher --- .../parachains/src/inclusion/benchmarking.rs | 63 ++++++++++--------- prdoc/pr_6368.prdoc | 7 +++ 2 files changed, 41 insertions(+), 29 deletions(-) create mode 100644 prdoc/pr_6368.prdoc diff --git a/polkadot/runtime/parachains/src/inclusion/benchmarking.rs b/polkadot/runtime/parachains/src/inclusion/benchmarking.rs index 1dac3c92cf16..ab95c5c2366a 100644 --- a/polkadot/runtime/parachains/src/inclusion/benchmarking.rs +++ b/polkadot/runtime/parachains/src/inclusion/benchmarking.rs @@ -14,6 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . +use bitvec::{bitvec, prelude::Lsb0}; +use frame_benchmarking::v2::*; +use pallet_message_queue as mq; +use polkadot_primitives::{ + vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CandidateCommitments, + HrmpChannelId, OutboundHrmpMessage, SessionIndex, +}; + use super::*; use crate::{ builder::generate_validator_pairs, @@ -21,13 +29,6 @@ use crate::{ hrmp::{HrmpChannel, HrmpChannels}, initializer, HeadData, ValidationCode, }; -use bitvec::{bitvec, prelude::Lsb0}; -use frame_benchmarking::benchmarks; -use pallet_message_queue as mq; -use polkadot_primitives::{ - vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CandidateCommitments, - HrmpChannelId, OutboundHrmpMessage, SessionIndex, -}; fn create_candidate_commitments( para_id: ParaId, @@ -70,7 +71,7 @@ fn create_candidate_commitments( BoundedVec::truncate_from(unbounded) }; - let new_validation_code = code_upgrade.then_some(ValidationCode(vec![42u8; 1024])); + let new_validation_code = code_upgrade.then_some(ValidationCode(vec![42_u8; 1024])); CandidateCommitments:: { upward_messages, @@ -87,18 +88,13 @@ fn create_messages(msg_len: usize, n_msgs: usize) -> Vec> { vec![vec![best_number; msg_len]; n_msgs] } -benchmarks! { - where_clause { - where - T: mq::Config + configuration::Config + initializer::Config, - } - - enact_candidate { - let u in 0 .. 2; - let h in 0 .. 2; - let c in 0 .. 1; +#[benchmarks(where T: mq::Config + configuration::Config + initializer::Config)] +mod benchmarks { + use super::*; - let para = 42_u32.into(); // not especially important. + #[benchmark] + fn enact_candidate(u: Linear<0, 2>, h: Linear<0, 2>, c: Linear<0, 1>) { + let para = 42_u32.into(); // not especially important. let max_len = mq::MaxMessageLenOf::::get() as usize; @@ -106,7 +102,7 @@ benchmarks! { let n_validators = config.max_validators.unwrap_or(500); let validators = generate_validator_pairs::(n_validators); - let session = SessionIndex::from(0u32); + let session = SessionIndex::from(0_u32); initializer::Pallet::::test_trigger_on_new_session( false, session, @@ -116,7 +112,7 @@ benchmarks! { let backing_group_size = config.scheduler_params.max_validators_per_core.unwrap_or(5); let head_data = HeadData(vec![0xFF; 1024]); - let relay_parent_number = BlockNumberFor::::from(10u32); + let relay_parent_number = BlockNumberFor::::from(10_u32); let commitments = create_candidate_commitments::(para, head_data, max_len, u, h, c != 0); let backers = bitvec![u8, Lsb0; 1; backing_group_size as usize]; let availability_votes = bitvec![u8, Lsb0; 1; n_validators as usize]; @@ -135,17 +131,26 @@ benchmarks! { ValidationCode(vec![1, 2, 3]).hash(), ); - let receipt = CommittedCandidateReceipt:: { - descriptor, - commitments, - }; + let receipt = CommittedCandidateReceipt:: { descriptor, commitments }; - Pallet::::receive_upward_messages(para, vec![vec![0; max_len]; 1].as_slice()); - } : { Pallet::::enact_candidate(relay_parent_number, receipt, backers, availability_votes, core_index, backing_group) } + Pallet::::receive_upward_messages(para, &vec![vec![0; max_len]; 1]); - impl_benchmark_test_suite!( + #[block] + { + Pallet::::enact_candidate( + relay_parent_number, + receipt, + backers, + availability_votes, + core_index, + backing_group, + ); + } + } + + impl_benchmark_test_suite! { Pallet, crate::mock::new_test_ext(Default::default()), crate::mock::Test - ); + } } diff --git a/prdoc/pr_6368.prdoc b/prdoc/pr_6368.prdoc new file mode 100644 index 000000000000..4fd3963eb05e --- /dev/null +++ b/prdoc/pr_6368.prdoc @@ -0,0 +1,7 @@ +title: Migrate inclusion benchmark to v2 +doc: +- audience: Runtime Dev + description: Migrate inclusion benchmark to v2. +crates: +- name: polkadot-runtime-parachains + bump: patch From f19640bdf98f72c788e60f647628b3fc98192bb1 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Mon, 30 Dec 2024 10:44:47 +0200 Subject: [PATCH 208/340] Log peerset set ID -> protocol name mapping (#7005) To simplify debugging of peerset related issues like https://github.com/paritytech/polkadot-sdk/issues/6573#issuecomment-2563091343. --------- Co-authored-by: command-bot <> --- prdoc/pr_7005.prdoc | 7 +++++++ substrate/client/network/src/protocol.rs | 11 +++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_7005.prdoc diff --git a/prdoc/pr_7005.prdoc b/prdoc/pr_7005.prdoc new file mode 100644 index 000000000000..a61f7c5b9b71 --- /dev/null +++ b/prdoc/pr_7005.prdoc @@ -0,0 +1,7 @@ +title: Log peerset set ID -> protocol name mapping +doc: +- audience: Node Dev + description: To simplify debugging of peerset related issues like https://github.com/paritytech/polkadot-sdk/issues/6573#issuecomment-2563091343. +crates: +- name: sc-network + bump: patch diff --git a/substrate/client/network/src/protocol.rs b/substrate/client/network/src/protocol.rs index 6da1d601b34f..81e1848adefa 100644 --- a/substrate/client/network/src/protocol.rs +++ b/substrate/client/network/src/protocol.rs @@ -34,7 +34,7 @@ use libp2p::{ }, Multiaddr, PeerId, }; -use log::warn; +use log::{debug, warn}; use codec::DecodeAll; use sc_network_common::role::Roles; @@ -53,6 +53,9 @@ mod notifications; pub mod message; +// Log target for this file. +const LOG_TARGET: &str = "sub-libp2p"; + /// Maximum size used for notifications in the block announce and transaction protocols. // Must be equal to `max(MAX_BLOCK_ANNOUNCE_SIZE, MAX_TRANSACTIONS_SIZE)`. pub(crate) const BLOCK_ANNOUNCES_TRANSACTIONS_SUBSTREAM_SIZE: u64 = MAX_RESPONSE_SIZE; @@ -124,6 +127,10 @@ impl Protocol { handle.set_metrics(notification_metrics.clone()); }); + protocol_configs.iter().enumerate().for_each(|(i, (p, _, _))| { + debug!(target: LOG_TARGET, "Notifications protocol {:?}: {}", SetId::from(i), p.name); + }); + ( Notifications::new( protocol_controller_handles, @@ -164,7 +171,7 @@ impl Protocol { { self.behaviour.disconnect_peer(peer_id, SetId::from(position)); } else { - warn!(target: "sub-libp2p", "disconnect_peer() with invalid protocol name") + warn!(target: LOG_TARGET, "disconnect_peer() with invalid protocol name") } } From 997db8e2035ce180f502ccb54eb06ab464d95dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 30 Dec 2024 11:34:31 +0100 Subject: [PATCH 209/340] pallet-bounties: Fix benchmarks for 0 ED (#7013) Closes: https://github.com/paritytech/polkadot-sdk/issues/7009 --------- Co-authored-by: command-bot <> --- prdoc/pr_7013.prdoc | 7 ++++++ substrate/frame/bounties/src/benchmarking.rs | 24 ++++++++++++-------- substrate/frame/bounties/src/lib.rs | 1 + 3 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 prdoc/pr_7013.prdoc diff --git a/prdoc/pr_7013.prdoc b/prdoc/pr_7013.prdoc new file mode 100644 index 000000000000..138fa7f23102 --- /dev/null +++ b/prdoc/pr_7013.prdoc @@ -0,0 +1,7 @@ +title: 'pallet-bounties: Fix benchmarks for 0 ED' +doc: +- audience: Runtime Dev + description: 'Closes: https://github.com/paritytech/polkadot-sdk/issues/7009' +crates: +- name: pallet-bounties + bump: patch diff --git a/substrate/frame/bounties/src/benchmarking.rs b/substrate/frame/bounties/src/benchmarking.rs index 1e931958898d..b5155909e3cd 100644 --- a/substrate/frame/bounties/src/benchmarking.rs +++ b/substrate/frame/bounties/src/benchmarking.rs @@ -15,9 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! bounties pallet benchmarking. - -#![cfg(feature = "runtime-benchmarks")] +//! Bounties pallet benchmarking. use super::*; @@ -37,6 +35,16 @@ fn set_block_number, I: 'static>(n: BlockNumberFor) { >::BlockNumberProvider::set_block_number(n); } +fn minimum_balance, I: 'static>() -> BalanceOf { + let minimum_balance = T::Currency::minimum_balance(); + + if minimum_balance.is_zero() { + 1u32.into() + } else { + minimum_balance + } +} + // Create bounties that are approved for use in `on_initialize`. fn create_approved_bounties, I: 'static>(n: u32) -> Result<(), BenchmarkError> { for i in 0..n { @@ -62,12 +70,10 @@ fn setup_bounty, I: 'static>( let fee = value / 2u32.into(); let deposit = T::BountyDepositBase::get() + T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into(); - let _ = T::Currency::make_free_balance_be(&caller, deposit + T::Currency::minimum_balance()); + let _ = T::Currency::make_free_balance_be(&caller, deposit + minimum_balance::()); let curator = account("curator", u, SEED); - let _ = T::Currency::make_free_balance_be( - &curator, - fee / 2u32.into() + T::Currency::minimum_balance(), - ); + let _ = + T::Currency::make_free_balance_be(&curator, fee / 2u32.into() + minimum_balance::()); let reason = vec![0; d as usize]; (caller, curator, fee, value, reason) } @@ -91,7 +97,7 @@ fn create_bounty, I: 'static>( fn setup_pot_account, I: 'static>() { let pot_account = Bounties::::account_id(); - let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000u32.into()); + let value = minimum_balance::().saturating_mul(1_000_000_000u32.into()); let _ = T::Currency::make_free_balance_be(&pot_account, value); } diff --git a/substrate/frame/bounties/src/lib.rs b/substrate/frame/bounties/src/lib.rs index 729c76b5cc75..d9accc5061cf 100644 --- a/substrate/frame/bounties/src/lib.rs +++ b/substrate/frame/bounties/src/lib.rs @@ -84,6 +84,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "runtime-benchmarks")] mod benchmarking; pub mod migrations; mod tests; From b63555510b0c9750b24f6bea9c24ef031404e643 Mon Sep 17 00:00:00 2001 From: Maciej Date: Mon, 30 Dec 2024 11:07:53 +0000 Subject: [PATCH 210/340] Excluding chainlink domain for link checker CI (#6524) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Excludes the chainlink domain through a lychee config to fix the complaining link checker CI test. Chainlink is gated behind a captcha. --------- Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> --- .config/lychee.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.config/lychee.toml b/.config/lychee.toml index b1f08de33340..58f8d068d9d1 100644 --- a/.config/lychee.toml +++ b/.config/lychee.toml @@ -28,7 +28,7 @@ exclude = [ "http://visitme/", "https://visitme/", - # TODO + # TODO meta issue: "https://docs.substrate.io/main-docs/build/custom-rpc/#public-rpcs", "https://docs.substrate.io/rustdocs/latest/sp_api/macro.decl_runtime_apis.html", "https://github.com/ipfs/js-ipfs-bitswap/blob/", @@ -50,8 +50,10 @@ exclude = [ "https://w3f.github.io/parachain-implementers-guide/runtime/session_info.html", # Behind a captcha (code 403): + "https://chainlist.org/chain/*", "https://iohk.io/en/blog/posts/2023/11/03/partner-chains-are-coming-to-cardano/", "https://www.reddit.com/r/rust/comments/3spfh1/does_collect_allocate_more_than_once_while/", + # 403 rate limited: "https://etherscan.io/block/11090290", "https://subscan.io/", From b4177a9fe173ae592ccf581d290e869aff1cafc4 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Mon, 30 Dec 2024 16:28:15 +0200 Subject: [PATCH 211/340] sync: Send already connected peers to new subscribers (#7011) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce `SyncEvent::InitialPeers` message sent to new subscribers to allow them correctly tracking sync peers. This resolves a race condition described in https://github.com/paritytech/polkadot-sdk/issues/6573#issuecomment-2563091343. Fixes https://github.com/paritytech/polkadot-sdk/issues/6573. --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- prdoc/pr_7011.prdoc | 16 +++++++++++++ substrate/client/network-gossip/src/bridge.rs | 10 ++++---- substrate/client/network-gossip/src/lib.rs | 13 +++++++---- substrate/client/network/statement/src/lib.rs | 23 ++++++++++++++----- substrate/client/network/sync/src/engine.rs | 6 ++++- substrate/client/network/sync/src/types.rs | 4 ++++ .../client/network/transactions/src/lib.rs | 23 ++++++++++++++----- 7 files changed, 73 insertions(+), 22 deletions(-) create mode 100644 prdoc/pr_7011.prdoc diff --git a/prdoc/pr_7011.prdoc b/prdoc/pr_7011.prdoc new file mode 100644 index 000000000000..55fe0c73ca09 --- /dev/null +++ b/prdoc/pr_7011.prdoc @@ -0,0 +1,16 @@ +title: 'sync: Send already connected peers to new subscribers' +doc: +- audience: Node Dev + description: |- + Introduce `SyncEvent::InitialPeers` message sent to new subscribers to allow them correctly tracking sync peers. This resolves a race condition described in https://github.com/paritytech/polkadot-sdk/issues/6573#issuecomment-2563091343. + + Fixes https://github.com/paritytech/polkadot-sdk/issues/6573. +crates: +- name: sc-network-gossip + bump: major +- name: sc-network-statement + bump: patch +- name: sc-network-sync + bump: major +- name: sc-network-transactions + bump: patch diff --git a/substrate/client/network-gossip/src/bridge.rs b/substrate/client/network-gossip/src/bridge.rs index 2daf1e49ee4b..bff258a9a011 100644 --- a/substrate/client/network-gossip/src/bridge.rs +++ b/substrate/client/network-gossip/src/bridge.rs @@ -254,10 +254,12 @@ impl Future for GossipEngine { match sync_event_stream { Poll::Ready(Some(event)) => match event { - SyncEvent::PeerConnected(remote) => - this.network.add_set_reserved(remote, this.protocol.clone()), - SyncEvent::PeerDisconnected(remote) => - this.network.remove_set_reserved(remote, this.protocol.clone()), + SyncEvent::InitialPeers(peer_ids) => + this.network.add_set_reserved(peer_ids, this.protocol.clone()), + SyncEvent::PeerConnected(peer_id) => + this.network.add_set_reserved(vec![peer_id], this.protocol.clone()), + SyncEvent::PeerDisconnected(peer_id) => + this.network.remove_set_reserved(peer_id, this.protocol.clone()), }, // The sync event stream closed. Do the same for [`GossipValidator`]. Poll::Ready(None) => { diff --git a/substrate/client/network-gossip/src/lib.rs b/substrate/client/network-gossip/src/lib.rs index 20d9922200c2..2ec573bf9e3e 100644 --- a/substrate/client/network-gossip/src/lib.rs +++ b/substrate/client/network-gossip/src/lib.rs @@ -82,15 +82,18 @@ mod validator; /// Abstraction over a network. pub trait Network: NetworkPeers + NetworkEventStream { - fn add_set_reserved(&self, who: PeerId, protocol: ProtocolName) { - let addr = Multiaddr::empty().with(Protocol::P2p(*who.as_ref())); - let result = self.add_peers_to_reserved_set(protocol, iter::once(addr).collect()); + fn add_set_reserved(&self, peer_ids: Vec, protocol: ProtocolName) { + let addrs = peer_ids + .into_iter() + .map(|peer_id| Multiaddr::empty().with(Protocol::P2p(peer_id.into()))) + .collect(); + let result = self.add_peers_to_reserved_set(protocol, addrs); if let Err(err) = result { log::error!(target: "gossip", "add_set_reserved failed: {}", err); } } - fn remove_set_reserved(&self, who: PeerId, protocol: ProtocolName) { - let result = self.remove_peers_from_reserved_set(protocol, iter::once(who).collect()); + fn remove_set_reserved(&self, peer_id: PeerId, protocol: ProtocolName) { + let result = self.remove_peers_from_reserved_set(protocol, iter::once(peer_id).collect()); if let Err(err) = result { log::error!(target: "gossip", "remove_set_reserved failed: {}", err); } diff --git a/substrate/client/network/statement/src/lib.rs b/substrate/client/network/statement/src/lib.rs index df93788696e3..586a15cadd68 100644 --- a/substrate/client/network/statement/src/lib.rs +++ b/substrate/client/network/statement/src/lib.rs @@ -33,7 +33,8 @@ use futures::{channel::oneshot, prelude::*, stream::FuturesUnordered, FutureExt} use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; use sc_network::{ config::{NonReservedPeerMode, SetConfig}, - error, multiaddr, + error, + multiaddr::{Multiaddr, Protocol}, peer_store::PeerStoreProvider, service::{ traits::{NotificationEvent, NotificationService, ValidationResult}, @@ -296,9 +297,19 @@ where fn handle_sync_event(&mut self, event: SyncEvent) { match event { - SyncEvent::PeerConnected(remote) => { - let addr = iter::once(multiaddr::Protocol::P2p(remote.into())) - .collect::(); + SyncEvent::InitialPeers(peer_ids) => { + let addrs = peer_ids + .into_iter() + .map(|peer_id| Multiaddr::empty().with(Protocol::P2p(peer_id.into()))) + .collect(); + let result = + self.network.add_peers_to_reserved_set(self.protocol_name.clone(), addrs); + if let Err(err) = result { + log::error!(target: LOG_TARGET, "Add reserved peers failed: {}", err); + } + }, + SyncEvent::PeerConnected(peer_id) => { + let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into())); let result = self.network.add_peers_to_reserved_set( self.protocol_name.clone(), iter::once(addr).collect(), @@ -307,10 +318,10 @@ where log::error!(target: LOG_TARGET, "Add reserved peer failed: {}", err); } }, - SyncEvent::PeerDisconnected(remote) => { + SyncEvent::PeerDisconnected(peer_id) => { let result = self.network.remove_peers_from_reserved_set( self.protocol_name.clone(), - iter::once(remote).collect(), + iter::once(peer_id).collect(), ); if let Err(err) = result { log::error!(target: LOG_TARGET, "Failed to remove reserved peer: {err}"); diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index 0c39ea0b93c0..4003361525e1 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -656,7 +656,11 @@ where ToServiceCommand::SetSyncForkRequest(peers, hash, number) => { self.strategy.set_sync_fork_request(peers, &hash, number); }, - ToServiceCommand::EventStream(tx) => self.event_streams.push(tx), + ToServiceCommand::EventStream(tx) => { + let _ = tx + .unbounded_send(SyncEvent::InitialPeers(self.peers.keys().cloned().collect())); + self.event_streams.push(tx); + }, ToServiceCommand::RequestJustification(hash, number) => self.strategy.request_justification(&hash, number), ToServiceCommand::ClearJustificationRequests => diff --git a/substrate/client/network/sync/src/types.rs b/substrate/client/network/sync/src/types.rs index 5745a34378df..a72a2f7c1ffe 100644 --- a/substrate/client/network/sync/src/types.rs +++ b/substrate/client/network/sync/src/types.rs @@ -127,6 +127,10 @@ where /// Syncing-related events that other protocols can subscribe to. pub enum SyncEvent { + /// All connected peers that the syncing implementation is tracking. + /// Always sent as the first message to the stream. + InitialPeers(Vec), + /// Peer that the syncing implementation is tracking connected. PeerConnected(PeerId), diff --git a/substrate/client/network/transactions/src/lib.rs b/substrate/client/network/transactions/src/lib.rs index 44fa702ef6d4..49f429a04ee2 100644 --- a/substrate/client/network/transactions/src/lib.rs +++ b/substrate/client/network/transactions/src/lib.rs @@ -35,7 +35,8 @@ use log::{debug, trace, warn}; use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; use sc_network::{ config::{NonReservedPeerMode, ProtocolId, SetConfig}, - error, multiaddr, + error, + multiaddr::{Multiaddr, Protocol}, peer_store::PeerStoreProvider, service::{ traits::{NotificationEvent, NotificationService, ValidationResult}, @@ -377,9 +378,19 @@ where fn handle_sync_event(&mut self, event: SyncEvent) { match event { - SyncEvent::PeerConnected(remote) => { - let addr = iter::once(multiaddr::Protocol::P2p(remote.into())) - .collect::(); + SyncEvent::InitialPeers(peer_ids) => { + let addrs = peer_ids + .into_iter() + .map(|peer_id| Multiaddr::empty().with(Protocol::P2p(peer_id.into()))) + .collect(); + let result = + self.network.add_peers_to_reserved_set(self.protocol_name.clone(), addrs); + if let Err(err) = result { + log::error!(target: LOG_TARGET, "Add reserved peers failed: {}", err); + } + }, + SyncEvent::PeerConnected(peer_id) => { + let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into())); let result = self.network.add_peers_to_reserved_set( self.protocol_name.clone(), iter::once(addr).collect(), @@ -388,10 +399,10 @@ where log::error!(target: LOG_TARGET, "Add reserved peer failed: {}", err); } }, - SyncEvent::PeerDisconnected(remote) => { + SyncEvent::PeerDisconnected(peer_id) => { let result = self.network.remove_peers_from_reserved_set( self.protocol_name.clone(), - iter::once(remote).collect(), + iter::once(peer_id).collect(), ); if let Err(err) = result { log::error!(target: LOG_TARGET, "Remove reserved peer failed: {}", err); From 5abdc5c34c544aaf21d98778eae31ccc2349c422 Mon Sep 17 00:00:00 2001 From: SihanoukSolver29 <150921296+SihanoukSolver29@users.noreply.github.com> Date: Mon, 30 Dec 2024 15:44:28 +0000 Subject: [PATCH 212/340] correct path in cumulus README (#7001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR fixes the file path in `cumulus/README.md` so the link to the container documentation points to the correct location. ## Review Notes The only change are the links in `cumulus/README.md` from `./docs/contributor/container.md` to `../docs/contributor/container.md`. # Checklist * [x] My PR includes a detailed description as outlined in the "Description" and its two subsections above. * [x] My PR follows the [labeling requirements]( https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process ) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. * [ ] I have made corresponding changes to the documentation (if applicable) * [ ] I have added tests that prove my fix is effective or that my feature works (if applicable) Co-authored-by: Guillaume Thiolliere Co-authored-by: Bastian Köcher --- cumulus/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cumulus/README.md b/cumulus/README.md index 0c47df999022..400f9481c3fb 100644 --- a/cumulus/README.md +++ b/cumulus/README.md @@ -4,7 +4,7 @@ This repository contains both the Cumulus SDK and also specific chains implemented on top of this SDK. -If you only want to run a **Polkadot Parachain Node**, check out our [container section](./docs/contributor/container.md). +If you only want to run a **Polkadot Parachain Node**, check out our [container section](../docs/contributor/container.md). ## Cumulus SDK @@ -34,7 +34,7 @@ A Polkadot [collator](https://wiki.polkadot.network/docs/en/learn-collator) for `polkadot-parachain` binary (previously called `polkadot-collator`). You may run `polkadot-parachain` locally after building it or using one of the container option described -[here](./docs/contributor/container.md). +[here](../docs/contributor/container.md). ### Relay Chain Interaction To operate a parachain node, a connection to the corresponding relay chain is necessary. This can be achieved in one of From 9d760a9f569cf58bf6f6c19bac93d0d33f54a454 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 2 Jan 2025 13:13:45 +0100 Subject: [PATCH 213/340] [CI] Skip SemVer on R0-silent and update docs (#6285) Changes: - Make R0-silent not run the semver check again. Originally I thought this would be good to have a bullet-proof check, but it now often triggers when CI or unrelated files are changed. In the end, the developer has to make the right choice here - and always will need to. So bringing back the R0 label gives more power to the devs and should increase dev velocity. We still need to ensure that every use of this label is well understood, and not just used out of lazyness. - Fix `/cmd prdoc` bump levels - Update docs --------- Signed-off-by: Oliver Tale-Yazdi --- .github/scripts/generate-prdoc.py | 18 +++- .github/workflows/check-semver.yml | 10 ++- .github/workflows/command-prdoc.yml | 2 +- docs/contributor/prdoc.md | 124 ++++++++++++++++------------ 4 files changed, 97 insertions(+), 57 deletions(-) diff --git a/.github/scripts/generate-prdoc.py b/.github/scripts/generate-prdoc.py index 780fa0012976..9154f185e64b 100644 --- a/.github/scripts/generate-prdoc.py +++ b/.github/scripts/generate-prdoc.py @@ -36,6 +36,21 @@ def from_pr_number(n, audience, bump, force): create_prdoc(n, audience, pr.title, pr.body, patch, bump, force) +def translate_audience(audience): + aliases = { + 'runtime_dev': 'Runtime Dev', + 'runtime_user': 'Runtime Operator', + 'node_dev': 'Node Dev', + 'node_user': 'Node User', + } + + if audience in aliases: + to = aliases[audience] + print(f"Translated audience '{audience}' to '{to}'") + audience = to + + return audience + def create_prdoc(pr, audience, title, description, patch, bump, force): path = f"prdoc/pr_{pr}.prdoc" @@ -49,6 +64,7 @@ def create_prdoc(pr, audience, title, description, patch, bump, force): print(f"No preexisting PrDoc for PR {pr}") prdoc = { "title": title, "doc": [{}], "crates": [] } + audience = translate_audience(audience) prdoc["doc"][0]["audience"] = audience prdoc["doc"][0]["description"] = description @@ -117,7 +133,7 @@ def setup_parser(parser=None, pr_required=True): parser = argparse.ArgumentParser() parser.add_argument("--pr", type=int, required=pr_required, help="The PR number to generate the PrDoc for.") parser.add_argument("--audience", type=str, nargs='*', choices=allowed_audiences, default=["todo"], help="The audience of whom the changes may concern. Example: --audience runtime_dev node_dev") - parser.add_argument("--bump", type=str, default="major", choices=["patch", "minor", "major", "silent", "ignore", "no_change"], help="A default bump level for all crates. Example: --bump patch") + parser.add_argument("--bump", type=str, default="major", choices=["patch", "minor", "major", "silent", "ignore", "none"], help="A default bump level for all crates. Example: --bump patch") parser.add_argument("--force", action="store_true", help="Whether to overwrite any existing PrDoc.") return parser diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index 16028c8de770..0da3e54ef60b 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -2,7 +2,7 @@ name: Check semver on: pull_request: - types: [opened, synchronize, reopened, ready_for_review] + types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] workflow_dispatch: merge_group: @@ -62,21 +62,29 @@ jobs: echo "PRDOC_EXTRA_ARGS=--max-bump minor" >> $GITHUB_ENV + - name: Echo Skip + if: ${{ contains(github.event.pull_request.labels.*.name, 'R0-silent') }} + run: echo "Skipping this PR because it is labeled as R0-silent." + - name: Rust Cache + if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 with: cache-on-failure: true - name: Rust compilation prerequisites + if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} run: | rustup default $TOOLCHAIN rustup component add rust-src --toolchain $TOOLCHAIN - name: install parity-publish + if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} # Set the target dir to cache the build. run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.3 --locked -q - name: check semver + if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} run: | if [ -z "$PR" ]; then echo "Skipping master/merge queue" diff --git a/.github/workflows/command-prdoc.yml b/.github/workflows/command-prdoc.yml index 7022e8e0e006..71dbcfbd2289 100644 --- a/.github/workflows/command-prdoc.yml +++ b/.github/workflows/command-prdoc.yml @@ -14,7 +14,7 @@ on: required: true options: - "TODO" - - "no_change" + - "none" - "patch" - "minor" - "major" diff --git a/docs/contributor/prdoc.md b/docs/contributor/prdoc.md index 4a1a3c1f0688..1f6252425e69 100644 --- a/docs/contributor/prdoc.md +++ b/docs/contributor/prdoc.md @@ -1,73 +1,88 @@ # PRDoc -A [prdoc](https://github.com/paritytech/prdoc) is like a changelog but for a Pull Request. We use this approach to -record changes on a crate level. This information is then processed by the release team to apply the correct crate -version bumps and to generate the CHANGELOG of the next release. +A [prdoc](https://github.com/paritytech/prdoc) is like a changelog but for a Pull Request. We use +this approach to record changes on a crate level. This information is then processed by the release +team to apply the correct crate version bumps and to generate the CHANGELOG of the next release. ## Requirements -When creating a PR, the author needs to decide with the `R0-silent` label whether the PR has to contain a prdoc. The -`R0` label should only be placed for No-OP changes like correcting a typo in a comment or CI stuff. If unsure, ping -the [CODEOWNERS](../../.github/CODEOWNERS) for advice. +When creating a PR, the author needs to decide with the `R0-silent` label whether the PR has to +contain a prdoc. The `R0` label should only be placed for No-OP changes like correcting a typo in a +comment or CI stuff. If unsure, ping the [CODEOWNERS](../../.github/CODEOWNERS) for advice. -## PRDoc How-To +## Auto Generation -A `.prdoc` file is a YAML file with a defined structure (ie JSON Schema). Please follow these steps to generate one: - -1. Install the [`prdoc` CLI](https://github.com/paritytech/prdoc) by running `cargo install parity-prdoc`. -1. Open a Pull Request and get the PR number. -1. Generate the file with `prdoc generate `. The output filename will be printed. -1. Optional: Install the `prdoc/schema_user.json` schema in your editor, for example - [VsCode](https://github.com/paritytech/prdoc?tab=readme-ov-file#schemas). -1. Edit your `.prdoc` file according to the [Audience](#pick-an-audience) and [SemVer](#record-semver-changes) sections. -1. Check your prdoc with `prdoc check -n `. This is optional since the CI will also check it. - -> **Tip:** GitHub CLI and jq can be used to provide the number of your PR to generate the correct file: -> `prdoc generate $(gh pr view --json number | jq '.number') -o prdoc` - -Alternatively you can call the prdoc from PR via `/cmd prdoc` (see args with `/cmd prdoc --help`) -in a comment to PR to trigger it from CI. +You can create a PrDoc by using the `/cmd prdoc` command (see args with `/cmd prdoc --help`) in a +comment on your PR. Options: -- `pr`: The PR number to generate the PrDoc for. -- `audience`: The audience of whom the changes may concern. -- `bump`: A default bump level for all crates. - The PrDoc will likely need to be edited to reflect the actual changes after generation. -- `force`: Whether to overwrite any existing PrDoc. +- `audience` The audience of whom the changes may concern. + - `runtime_dev`: Anyone building a runtime themselves. For example parachain teams, or people + providing template runtimes. Also devs using pallets, FRAME etc directly. These are people who + care about the protocol (WASM), not the meta-protocol (client). + - `runtime_user`: Anyone using the runtime. Can be front-end devs reading the state, exchanges + listening for events, libraries that have hard-coded pallet indices etc. Anything that would + result in an observable change to the runtime behaviour must be marked with this. + - `node_dev`: Those who build around the client side code. Alternative client builders, SMOLDOT, + those who consume RPCs. These are people who are oblivious to the runtime changes. They only care + about the meta-protocol, not the protocol itself. + - `node_operator`: People who run the node. Think of validators, exchanges, indexer services, CI + actions. Anything that modifies how the binary behaves (its arguments, default arguments, error + messags, etc) must be marked with this. +- `bump:`: The default bump level for all crates. The PrDoc will likely need to be edited to reflect + the actual changes after generation. More details in the section below. + - `none`: There is no observable change. So to say: if someone were handed the old and the new + version of our software, it would be impossible to figure out what version is which. + - `patch`: Fixes that will never cause compilation errors if someone updates to this version. No + functionality has been changed. Should be limited to fixing bugs or No-OP implementation + changes. + - `minor`: Additions that will never cause compilation errors if someone updates to this version. + No functionality has been changed. Should be limited to adding new features. + - `major`: Anything goes. +- `force: true|false`: Whether to overwrite any existing PrDoc file. -## Pick An Audience - -While describing a PR, the author needs to consider which audience(s) need to be addressed. -The list of valid audiences is described and documented in the JSON schema as follow: +### Example -- `Node Dev`: Those who build around the client side code. Alternative client builders, SMOLDOT, those who consume RPCs. - These are people who are oblivious to the runtime changes. They only care about the meta-protocol, not the protocol - itself. +```bash +/cmd prdoc --audience runtime_dev --bump patch +``` -- `Runtime Dev`: All of those who rely on the runtime. A parachain team that is using a pallet. A DApp that is using a - pallet. These are people who care about the protocol (WASM), not the meta-protocol (client). +## Local Generation -- `Node Operator`: Those who don't write any code and only run code. +A `.prdoc` file is a YAML file with a defined structure (ie JSON Schema). Please follow these steps +to generate one: -- `Runtime User`: Anyone using the runtime. This can be a token holder or a dev writing a front end for a chain. +1. Install the [`prdoc` CLI](https://github.com/paritytech/prdoc) by running `cargo install + parity-prdoc`. +1. Open a Pull Request and get the PR number. +1. Generate the file with `prdoc generate `. The output filename will be printed. +1. Optional: Install the `prdoc/schema_user.json` schema in your editor, for example + [VsCode](https://github.com/paritytech/prdoc?tab=readme-ov-file#schemas). +1. Edit your `.prdoc` file according to the [Audience](#pick-an-audience) and + [SemVer](#record-semver-changes) sections. +1. Check your prdoc with `prdoc check -n `. This is optional since the CI will also check + it. -If you have a change that affects multiple audiences, you can either list them all, or write multiple sections and -re-phrase the changes for each audience. +> **Tip:** GitHub CLI and jq can be used to provide the number of your PR to generate the correct +> file: +> `prdoc generate $(gh pr view --json number | jq '.number') -o prdoc` ## Record SemVer Changes -All published crates that got modified need to have an entry in the `crates` section of your `PRDoc`. This entry tells -the release team how to bump the crate version prior to the next release. It is very important that this information is -correct, otherwise it could break the code of downstream teams. +All published crates that got modified need to have an entry in the `crates` section of your +`PRDoc`. This entry tells the release team how to bump the crate version prior to the next release. +It is very important that this information is correct, otherwise it could break the code of +downstream teams. The bump can either be `major`, `minor`, `patch` or `none`. The three first options are defined by -[rust-lang.org](https://doc.rust-lang.org/cargo/reference/semver.html), whereas `None` should be picked if no other -applies. The `None` option is equivalent to the `R0-silent` label, but on a crate level. Experimental and private APIs -are exempt from bumping and can be broken at any time. Please read the [Crate Section](../RELEASE.md) of the RELEASE doc -about them. +[rust-lang.org](https://doc.rust-lang.org/cargo/reference/semver.html), whereas `None` should be +picked if no other applies. The `None` option is equivalent to the `R0-silent` label, but on a crate +level. Experimental and private APIs are exempt from bumping and can be broken at any time. Please +read the [Crate Section](../RELEASE.md) of the RELEASE doc about them. -> **Note**: There is currently no CI in place to sanity check this information, but should be added soon. +> **Note**: There is currently no CI in place to sanity check this information, but should be added +> soon. ### Example @@ -81,12 +96,13 @@ crates: bump: minor ``` -It means that downstream code using `frame-example-pallet` is still guaranteed to work as before, while code using -`frame-example` might break. +It means that downstream code using `frame-example-pallet` is still guaranteed to work as before, +while code using `frame-example` might break. ### Dependencies -A crate that depends on another crate will automatically inherit its `major` bumps. This means that you do not need to -bump a crate that had a SemVer breaking change only from re-exporting another crate with a breaking change. -`minor` an `patch` bumps do not need to be inherited, since `cargo` will automatically update them to the latest -compatible version. +A crate that depends on another crate will automatically inherit its `major` bumps. This means that +you do not need to bump a crate that had a SemVer breaking change only from re-exporting another +crate with a breaking change. +`minor` an `patch` bumps do not need to be inherited, since `cargo` will automatically update them +to the latest compatible version. From fcbc0ef2d109c9c96c6821959c9899a3d3dd20a1 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Thu, 2 Jan 2025 17:54:03 +0100 Subject: [PATCH 214/340] Add workflow for networking benchmarks (#7029) # Description Adds charts for networking benchmarks --- .github/workflows/networking-benchmarks.yml | 107 ++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 .github/workflows/networking-benchmarks.yml diff --git a/.github/workflows/networking-benchmarks.yml b/.github/workflows/networking-benchmarks.yml new file mode 100644 index 000000000000..e45ae601105d --- /dev/null +++ b/.github/workflows/networking-benchmarks.yml @@ -0,0 +1,107 @@ +name: Networking Benchmarks + +on: + push: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + preflight: + uses: ./.github/workflows/reusable-preflight.yml + + build: + timeout-minutes: 80 + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER_BENCHMARK }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + strategy: + fail-fast: false + matrix: + features: + [ + { + bench: "notifications_protocol", + }, + { + bench: "request_response_protocol", + }, + ] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Run Benchmarks + id: run-benchmarks + run: | + mkdir -p ./charts + forklift cargo bench -p sc-network --bench ${{ matrix.features.bench }} -- --output-format bencher | grep "^test" | tee ./charts/networking-bench.txt || echo "Benchmarks failed" + ls -lsa ./charts + + - name: Upload artifacts + uses: actions/upload-artifact@v4.3.6 + with: + name: ${{ matrix.features.bench }}-${{ github.sha }} + path: ./charts + + publish-benchmarks: + timeout-minutes: 60 + needs: [build] + if: github.ref == 'refs/heads/master' + environment: subsystem-benchmarks + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: gh-pages + fetch-depth: 0 + + - run: git checkout master -- + + - name: Download artifacts + uses: actions/download-artifact@v4.1.8 + with: + name: networking-bench-${{ github.sha }} + path: ./charts + + - name: Setup git + run: | + # Fixes "detected dubious ownership" error in the ci + git config --global --add safe.directory '*' + ls -lsR ./charts + + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.POLKADOTSDK_GHPAGES_APP_ID }} + private-key: ${{ secrets.POLKADOTSDK_GHPAGES_APP_KEY }} + + - name: Generate ${{ env.BENCH }} + env: + BENCH: notifications_protocol + uses: benchmark-action/github-action-benchmark@v1 + with: + tool: "cargo" + output-file-path: ./charts/${{ env.BENCH }}.txt + benchmark-data-dir-path: ./bench/${{ env.BENCH }} + github-token: ${{ steps.app-token.outputs.token }} + auto-push: true + + - name: Generate ${{ env.BENCH }} + env: + BENCH: request_response_protocol + uses: benchmark-action/github-action-benchmark@v1 + with: + tool: "cargo" + output-file-path: ./charts/${{ env.BENCH }}.txt + benchmark-data-dir-path: ./bench/${{ env.BENCH }} + github-token: ${{ steps.app-token.outputs.token }} + auto-push: true From 20513d6fec617acf783fef8db872beb0584b6a9b Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Thu, 2 Jan 2025 19:36:45 +0100 Subject: [PATCH 215/340] [pallet-revive] fix file case (#6981) fix https://github.com/paritytech/polkadot-sdk/issues/6970 --------- Co-authored-by: command-bot <> --- prdoc/pr_6981.prdoc | 7 ++ .../js/abi/{ErrorTester.json => Errors.json} | 0 .../js/abi/{ErrorTester.ts => Errors.ts} | 2 +- .../revive/rpc/examples/js/abi/errorTester.ts | 106 ------------------ .../contracts/{ErrorTester.sol => Errors.sol} | 2 +- .../{ErrorTester.polkavm => Errors.polkavm} | Bin .../rpc/examples/js/src/geth-diff.test.ts | 54 ++++----- substrate/frame/revive/rpc/src/tests.rs | 2 +- 8 files changed, 37 insertions(+), 136 deletions(-) create mode 100644 prdoc/pr_6981.prdoc rename substrate/frame/revive/rpc/examples/js/abi/{ErrorTester.json => Errors.json} (100%) rename substrate/frame/revive/rpc/examples/js/abi/{ErrorTester.ts => Errors.ts} (98%) delete mode 100644 substrate/frame/revive/rpc/examples/js/abi/errorTester.ts rename substrate/frame/revive/rpc/examples/js/contracts/{ErrorTester.sol => Errors.sol} (98%) rename substrate/frame/revive/rpc/examples/js/pvm/{ErrorTester.polkavm => Errors.polkavm} (100%) diff --git a/prdoc/pr_6981.prdoc b/prdoc/pr_6981.prdoc new file mode 100644 index 000000000000..8ed70e51ef45 --- /dev/null +++ b/prdoc/pr_6981.prdoc @@ -0,0 +1,7 @@ +title: '[pallet-revive] fix file case' +doc: +- audience: Runtime Dev + description: "fix https://github.com/paritytech/polkadot-sdk/issues/6970\r\n" +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json b/substrate/frame/revive/rpc/examples/js/abi/Errors.json similarity index 100% rename from substrate/frame/revive/rpc/examples/js/abi/ErrorTester.json rename to substrate/frame/revive/rpc/examples/js/abi/Errors.json diff --git a/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.ts b/substrate/frame/revive/rpc/examples/js/abi/Errors.ts similarity index 98% rename from substrate/frame/revive/rpc/examples/js/abi/ErrorTester.ts rename to substrate/frame/revive/rpc/examples/js/abi/Errors.ts index f3776e498fd5..b39567531c6d 100644 --- a/substrate/frame/revive/rpc/examples/js/abi/ErrorTester.ts +++ b/substrate/frame/revive/rpc/examples/js/abi/Errors.ts @@ -1,4 +1,4 @@ -export const ErrorTesterAbi = [ +export const ErrorsAbi = [ { inputs: [ { diff --git a/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts b/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts deleted file mode 100644 index f3776e498fd5..000000000000 --- a/substrate/frame/revive/rpc/examples/js/abi/errorTester.ts +++ /dev/null @@ -1,106 +0,0 @@ -export const ErrorTesterAbi = [ - { - inputs: [ - { - internalType: "string", - name: "message", - type: "string", - }, - ], - name: "CustomError", - type: "error", - }, - { - inputs: [ - { - internalType: "bool", - name: "newState", - type: "bool", - }, - ], - name: "setState", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "state", - outputs: [ - { - internalType: "bool", - name: "", - type: "bool", - }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "triggerAssertError", - outputs: [], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "triggerCustomError", - outputs: [], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "triggerDivisionByZero", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "triggerOutOfBoundsError", - outputs: [ - { - internalType: "uint256", - name: "", - type: "uint256", - }, - ], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "triggerRequireError", - outputs: [], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "triggerRevertError", - outputs: [], - stateMutability: "pure", - type: "function", - }, - { - inputs: [ - { - internalType: "uint256", - name: "value", - type: "uint256", - }, - ], - name: "valueMatch", - outputs: [], - stateMutability: "payable", - type: "function", - }, -] as const; diff --git a/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol b/substrate/frame/revive/rpc/examples/js/contracts/Errors.sol similarity index 98% rename from substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol rename to substrate/frame/revive/rpc/examples/js/contracts/Errors.sol index f1fdd219624a..abbdba8d32eb 100644 --- a/substrate/frame/revive/rpc/examples/js/contracts/ErrorTester.sol +++ b/substrate/frame/revive/rpc/examples/js/contracts/Errors.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -contract ErrorTester { +contract Errors { bool public state; // Payable function that can be used to test insufficient funds errors diff --git a/substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm similarity index 100% rename from substrate/frame/revive/rpc/examples/js/pvm/ErrorTester.polkavm rename to substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index 37ebbc9ea3b3..b9ee877927bb 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -1,7 +1,7 @@ import { jsonRpcErrors, procs, createEnv, getByteCode } from './geth-diff-setup.ts' import { afterAll, afterEach, beforeAll, describe, expect, test } from 'bun:test' import { encodeFunctionData, Hex, parseEther } from 'viem' -import { ErrorTesterAbi } from '../abi/ErrorTester' +import { ErrorsAbi } from '../abi/Errors' import { FlipperCallerAbi } from '../abi/FlipperCaller' import { FlipperAbi } from '../abi/Flipper' @@ -17,19 +17,19 @@ const envs = await Promise.all([createEnv('geth'), createEnv('kitchensink')]) for (const env of envs) { describe(env.serverWallet.chain.name, () => { - let errorTesterAddr: Hex = '0x' + let errorsAddr: Hex = '0x' let flipperAddr: Hex = '0x' let flipperCallerAddr: Hex = '0x' beforeAll(async () => { { const hash = await env.serverWallet.deployContract({ - abi: ErrorTesterAbi, - bytecode: getByteCode('errorTester', env.evm), + abi: ErrorsAbi, + bytecode: getByteCode('errors', env.evm), }) const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) if (!deployReceipt.contractAddress) throw new Error('Contract address should be set') - errorTesterAddr = deployReceipt.contractAddress + errorsAddr = deployReceipt.contractAddress } { @@ -60,8 +60,8 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.readContract({ - address: errorTesterAddr, - abi: ErrorTesterAbi, + address: errorsAddr, + abi: ErrorsAbi, functionName: 'triggerAssertError', }) } catch (err) { @@ -78,8 +78,8 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.readContract({ - address: errorTesterAddr, - abi: ErrorTesterAbi, + address: errorsAddr, + abi: ErrorsAbi, functionName: 'triggerRevertError', }) } catch (err) { @@ -96,8 +96,8 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.readContract({ - address: errorTesterAddr, - abi: ErrorTesterAbi, + address: errorsAddr, + abi: ErrorsAbi, functionName: 'triggerDivisionByZero', }) } catch (err) { @@ -116,8 +116,8 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.readContract({ - address: errorTesterAddr, - abi: ErrorTesterAbi, + address: errorsAddr, + abi: ErrorsAbi, functionName: 'triggerOutOfBoundsError', }) } catch (err) { @@ -136,8 +136,8 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.readContract({ - address: errorTesterAddr, - abi: ErrorTesterAbi, + address: errorsAddr, + abi: ErrorsAbi, functionName: 'triggerCustomError', }) } catch (err) { @@ -154,8 +154,8 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.simulateContract({ - address: errorTesterAddr, - abi: ErrorTesterAbi, + address: errorsAddr, + abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], @@ -187,8 +187,8 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.estimateContractGas({ - address: errorTesterAddr, - abi: ErrorTesterAbi, + address: errorsAddr, + abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], @@ -205,8 +205,8 @@ for (const env of envs) { expect.assertions(3) try { await env.accountWallet.estimateContractGas({ - address: errorTesterAddr, - abi: ErrorTesterAbi, + address: errorsAddr, + abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], @@ -223,8 +223,8 @@ for (const env of envs) { expect.assertions(3) try { await env.serverWallet.estimateContractGas({ - address: errorTesterAddr, - abi: ErrorTesterAbi, + address: errorsAddr, + abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('11'), args: [parseEther('10')], @@ -255,8 +255,8 @@ for (const env of envs) { expect(balance).toBe(0n) await env.accountWallet.estimateContractGas({ - address: errorTesterAddr, - abi: ErrorTesterAbi, + address: errorsAddr, + abi: ErrorsAbi, functionName: 'setState', args: [true], }) @@ -273,7 +273,7 @@ for (const env of envs) { expect(balance).toBe(0n) const data = encodeFunctionData({ - abi: ErrorTesterAbi, + abi: ErrorsAbi, functionName: 'setState', args: [true], }) @@ -284,7 +284,7 @@ for (const env of envs) { { data, from: env.accountWallet.account.address, - to: errorTesterAddr, + to: errorsAddr, }, ], }) diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 43b600c33d78..e64e16d45b2a 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -222,7 +222,7 @@ async fn deploy_and_call() -> anyhow::Result<()> { async fn revert_call() -> anyhow::Result<()> { let _lock = SHARED_RESOURCES.write(); let client = SharedResources::client().await; - let (bytecode, contract) = get_contract("ErrorTester")?; + let (bytecode, contract) = get_contract("Errors")?; let receipt = TransactionBuilder::default() .input(bytecode) .send_and_wait_for_receipt(&client) From bdd11933dd9399f39d9eb74915117e6c94a905f1 Mon Sep 17 00:00:00 2001 From: 0xLucca <95830307+0xLucca@users.noreply.github.com> Date: Thu, 2 Jan 2025 16:14:21 -0300 Subject: [PATCH 216/340] Remove warning log from frame-omni-bencher CLI (#7020) # Description This PR removes the outdated warning message from the `frame-omni-bencher` CLI that states the tool is "not yet battle tested". Fixes #7019 ## Integration No integration steps are required. ## Review Notes The functionality of the tool remains unchanged. Removes the warning message from the CLI output. --------- Co-authored-by: Oliver Tale-Yazdi Co-authored-by: command-bot <> --- prdoc/pr_7020.prdoc | 18 ++++++++++++++++++ substrate/utils/frame/omni-bencher/src/main.rs | 2 -- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_7020.prdoc diff --git a/prdoc/pr_7020.prdoc b/prdoc/pr_7020.prdoc new file mode 100644 index 000000000000..5bbdb44c45a0 --- /dev/null +++ b/prdoc/pr_7020.prdoc @@ -0,0 +1,18 @@ +title: Remove warning log from frame-omni-bencher CLI +doc: +- audience: Node Operator + description: |- + # Description + + This PR removes the outdated warning message from the `frame-omni-bencher` CLI that states the tool is "not yet battle tested". Fixes #7019 + + ## Integration + + No integration steps are required. + + ## Review Notes + + The functionality of the tool remains unchanged. Removes the warning message from the CLI output. +crates: +- name: frame-omni-bencher + bump: patch diff --git a/substrate/utils/frame/omni-bencher/src/main.rs b/substrate/utils/frame/omni-bencher/src/main.rs index 7d8aa891dc4a..f0f9ab753b07 100644 --- a/substrate/utils/frame/omni-bencher/src/main.rs +++ b/substrate/utils/frame/omni-bencher/src/main.rs @@ -24,8 +24,6 @@ use tracing_subscriber::EnvFilter; fn main() -> Result<()> { setup_logger(); - log::warn!("The FRAME omni-bencher is not yet battle tested - double check the results.",); - command::Command::parse().run() } From 472945703925a1beb094439fd7e43149c44960d5 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Fri, 3 Jan 2025 04:29:44 +0900 Subject: [PATCH 217/340] Fix polkadot sdk doc. (#7022) If you see the doc https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/frame_runtime/index.html The runtime part introduction is missing. Co-authored-by: Oliver Tale-Yazdi --- docs/sdk/src/polkadot_sdk/frame_runtime.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sdk/src/polkadot_sdk/frame_runtime.rs b/docs/sdk/src/polkadot_sdk/frame_runtime.rs index 8acf19f76413..24595e445fdd 100644 --- a/docs/sdk/src/polkadot_sdk/frame_runtime.rs +++ b/docs/sdk/src/polkadot_sdk/frame_runtime.rs @@ -57,6 +57,7 @@ //! The following example showcases a minimal pallet. #![doc = docify::embed!("src/polkadot_sdk/frame_runtime.rs", pallet)] //! +//! ## Runtime //! //! A runtime is a collection of pallets that are amalgamated together. Each pallet typically has //! some configurations (exposed as a `trait Config`) that needs to be *specified* in the runtime. From b7e2695163e97fcacd8264a4291375ce66a95afc Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Fri, 3 Jan 2025 05:18:18 +0800 Subject: [PATCH 218/340] Improve remote externalities logging (#7021) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Automatically detect if current env is tty. If not disable the spinner logging. - Add `Result` type. - Format log style. Originally reported from: - https://github.com/hack-ink/polkadot-runtime-releaser/blob/4811d2b419649a73edd5bd1f748a858b846eb139/action/try-runtime/action.yml#L75-L91 - https://github.com/hack-ink/polkadot-runtime-releaser-workshop/pull/3#issuecomment-2563883943 Closes #7010. --- Polkadot address: 156HGo9setPcU2qhFMVWLkcmtCEGySLwNqa3DaEiYSWtte4Y --------- Signed-off-by: Xavier Lau Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher Co-authored-by: Oliver Tale-Yazdi --- prdoc/pr_7021.prdoc | 8 + .../frame/remote-externalities/src/lib.rs | 219 +++++++++--------- .../frame/remote-externalities/src/logging.rs | 86 +++++++ 3 files changed, 205 insertions(+), 108 deletions(-) create mode 100644 prdoc/pr_7021.prdoc create mode 100644 substrate/utils/frame/remote-externalities/src/logging.rs diff --git a/prdoc/pr_7021.prdoc b/prdoc/pr_7021.prdoc new file mode 100644 index 000000000000..5443579bbd92 --- /dev/null +++ b/prdoc/pr_7021.prdoc @@ -0,0 +1,8 @@ +title: Improve remote externalities logging +doc: +- audience: Node Dev + description: |- + Automatically detect if current env is tty. If not disable the spinner logging. +crates: +- name: frame-remote-externalities + bump: patch diff --git a/substrate/utils/frame/remote-externalities/src/lib.rs b/substrate/utils/frame/remote-externalities/src/lib.rs index 75a2ac2aef41..4c49663260bb 100644 --- a/substrate/utils/frame/remote-externalities/src/lib.rs +++ b/substrate/utils/frame/remote-externalities/src/lib.rs @@ -20,6 +20,8 @@ //! An equivalent of `sp_io::TestExternalities` that can load its state from a remote substrate //! based chain, or a local state snapshot file. +mod logging; + use codec::{Compact, Decode, Encode}; use indicatif::{ProgressBar, ProgressStyle}; use jsonrpsee::{core::params::ArrayParams, http_client::HttpClient}; @@ -37,7 +39,6 @@ use sp_runtime::{ StateVersion, }; use sp_state_machine::TestExternalities; -use spinners::{Spinner, Spinners}; use std::{ cmp::{max, min}, fs, @@ -49,6 +50,8 @@ use std::{ use substrate_rpc_client::{rpc_params, BatchRequestBuilder, ChainApi, ClientT, StateApi}; use tokio_retry::{strategy::FixedInterval, Retry}; +type Result = std::result::Result; + type KeyValue = (StorageKey, StorageData); type TopKeyValues = Vec; type ChildKeyValues = Vec<(ChildInfo, Vec)>; @@ -87,7 +90,7 @@ impl Snapshot { } } - fn load(path: &PathBuf) -> Result, &'static str> { + fn load(path: &PathBuf) -> Result> { let bytes = fs::read(path).map_err(|_| "fs::read failed.")?; // The first item in the SCALE encoded struct bytes is the snapshot version. We decode and // check that first, before proceeding to decode the rest of the snapshot. @@ -168,9 +171,9 @@ impl Transport { } // Build an HttpClient from a URI. - async fn init(&mut self) -> Result<(), &'static str> { + async fn init(&mut self) -> Result<()> { if let Self::Uri(uri) = self { - log::debug!(target: LOG_TARGET, "initializing remote client to {:?}", uri); + debug!(target: LOG_TARGET, "initializing remote client to {uri:?}"); // If we have a ws uri, try to convert it to an http uri. // We use an HTTP client rather than WS because WS starts to choke with "accumulated @@ -178,11 +181,11 @@ impl Transport { // from a node running a default configuration. let uri = if uri.starts_with("ws://") { let uri = uri.replace("ws://", "http://"); - log::info!(target: LOG_TARGET, "replacing ws:// in uri with http://: {:?} (ws is currently unstable for fetching remote storage, for more see https://github.com/paritytech/jsonrpsee/issues/1086)", uri); + info!(target: LOG_TARGET, "replacing ws:// in uri with http://: {uri:?} (ws is currently unstable for fetching remote storage, for more see https://github.com/paritytech/jsonrpsee/issues/1086)"); uri } else if uri.starts_with("wss://") { let uri = uri.replace("wss://", "https://"); - log::info!(target: LOG_TARGET, "replacing wss:// in uri with https://: {:?} (ws is currently unstable for fetching remote storage, for more see https://github.com/paritytech/jsonrpsee/issues/1086)", uri); + info!(target: LOG_TARGET, "replacing wss:// in uri with https://: {uri:?} (ws is currently unstable for fetching remote storage, for more see https://github.com/paritytech/jsonrpsee/issues/1086)"); uri } else { uri.clone() @@ -193,7 +196,7 @@ impl Transport { .request_timeout(std::time::Duration::from_secs(60 * 5)) .build(uri) .map_err(|e| { - log::error!(target: LOG_TARGET, "error: {:?}", e); + error!(target: LOG_TARGET, "error: {e:?}"); "failed to build http client" })?; @@ -364,23 +367,23 @@ where &self, key: StorageKey, maybe_at: Option, - ) -> Result, &'static str> { + ) -> Result> { trace!(target: LOG_TARGET, "rpc: get_storage"); self.as_online().rpc_client().storage(key, maybe_at).await.map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); + error!(target: LOG_TARGET, "Error = {e:?}"); "rpc get_storage failed." }) } /// Get the latest finalized head. - async fn rpc_get_head(&self) -> Result { + async fn rpc_get_head(&self) -> Result { trace!(target: LOG_TARGET, "rpc: finalized_head"); // sadly this pretty much unreadable... ChainApi::<(), _, B::Header, ()>::finalized_head(self.as_online().rpc_client()) .await .map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); + error!(target: LOG_TARGET, "Error = {e:?}"); "rpc finalized_head failed." }) } @@ -390,13 +393,13 @@ where prefix: Option, start_key: Option, at: B::Hash, - ) -> Result, &'static str> { + ) -> Result> { self.as_online() .rpc_client() .storage_keys_paged(prefix, Self::DEFAULT_KEY_DOWNLOAD_PAGE, start_key, Some(at)) .await .map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); + error!(target: LOG_TARGET, "Error = {e:?}"); "rpc get_keys failed" }) } @@ -407,7 +410,7 @@ where prefix: &StorageKey, block: B::Hash, parallel: usize, - ) -> Result, &'static str> { + ) -> Result> { /// Divide the workload and return the start key of each chunks. Guaranteed to return a /// non-empty list. fn gen_start_keys(prefix: &StorageKey) -> Vec { @@ -491,7 +494,7 @@ where block: B::Hash, start_key: Option<&StorageKey>, end_key: Option<&StorageKey>, - ) -> Result, &'static str> { + ) -> Result> { let mut last_key: Option<&StorageKey> = start_key; let mut keys: Vec = vec![]; @@ -518,11 +521,11 @@ where // scraping out of range or no more matches, // we are done either way if page_len < Self::DEFAULT_KEY_DOWNLOAD_PAGE as usize { - log::debug!(target: LOG_TARGET, "last page received: {}", page_len); + debug!(target: LOG_TARGET, "last page received: {page_len}"); break } - log::debug!( + debug!( target: LOG_TARGET, "new total = {}, full page received: {}", keys.len(), @@ -589,11 +592,10 @@ where let total_payloads = payloads.len(); while start_index < total_payloads { - log::debug!( + debug!( target: LOG_TARGET, - "Remaining payloads: {} Batch request size: {}", + "Remaining payloads: {} Batch request size: {batch_size}", total_payloads - start_index, - batch_size, ); let end_index = usize::min(start_index + batch_size, total_payloads); @@ -620,18 +622,16 @@ where retries += 1; let failure_log = format!( - "Batch request failed ({}/{} retries). Error: {}", - retries, - Self::MAX_RETRIES, - e + "Batch request failed ({retries}/{} retries). Error: {e}", + Self::MAX_RETRIES ); // after 2 subsequent failures something very wrong is happening. log a warning // and reset the batch size down to 1. if retries >= 2 { - log::warn!("{}", failure_log); + warn!("{failure_log}"); batch_size = 1; } else { - log::debug!("{}", failure_log); + debug!("{failure_log}"); // Decrease batch size by DECREASE_FACTOR batch_size = (batch_size as f32 * Self::BATCH_SIZE_DECREASE_FACTOR) as usize; @@ -655,13 +655,11 @@ where ) }; - log::debug!( + debug!( target: LOG_TARGET, - "Request duration: {:?} Target duration: {:?} Last batch size: {} Next batch size: {}", - request_duration, + "Request duration: {request_duration:?} Target duration: {:?} Last batch size: {} Next batch size: {batch_size}", Self::REQUEST_DURATION_TARGET, end_index - start_index, - batch_size ); let batch_response_len = batch_response.len(); @@ -689,21 +687,24 @@ where prefix: StorageKey, at: B::Hash, pending_ext: &mut TestExternalities>, - ) -> Result, &'static str> { - let start = Instant::now(); - let mut sp = Spinner::with_timer(Spinners::Dots, "Scraping keys...".into()); - // TODO We could start downloading when having collected the first batch of keys - // https://github.com/paritytech/polkadot-sdk/issues/2494 - let keys = self - .rpc_get_keys_parallel(&prefix, at, Self::PARALLEL_REQUESTS) - .await? - .into_iter() - .collect::>(); - sp.stop_with_message(format!( - "✅ Found {} keys ({:.2}s)", - keys.len(), - start.elapsed().as_secs_f32() - )); + ) -> Result> { + let keys = logging::with_elapsed_async( + || async { + // TODO: We could start downloading when having collected the first batch of keys. + // https://github.com/paritytech/polkadot-sdk/issues/2494 + let keys = self + .rpc_get_keys_parallel(&prefix, at, Self::PARALLEL_REQUESTS) + .await? + .into_iter() + .collect::>(); + + Ok(keys) + }, + "Scraping keys...", + |keys| format!("Found {} keys", keys.len()), + ) + .await?; + if keys.is_empty() { return Ok(Default::default()) } @@ -735,7 +736,7 @@ where let storage_data = match storage_data_result { Ok(storage_data) => storage_data.into_iter().flatten().collect::>(), Err(e) => { - log::error!(target: LOG_TARGET, "Error while getting storage data: {}", e); + error!(target: LOG_TARGET, "Error while getting storage data: {e}"); return Err("Error while getting storage data") }, }; @@ -751,27 +752,31 @@ where .map(|(key, maybe_value)| match maybe_value { Some(data) => (key.clone(), data), None => { - log::warn!(target: LOG_TARGET, "key {:?} had none corresponding value.", &key); + warn!(target: LOG_TARGET, "key {key:?} had none corresponding value."); let data = StorageData(vec![]); (key.clone(), data) }, }) .collect::>(); - let mut sp = Spinner::with_timer(Spinners::Dots, "Inserting keys into DB...".into()); - let start = Instant::now(); - pending_ext.batch_insert(key_values.clone().into_iter().filter_map(|(k, v)| { - // Don't insert the child keys here, they need to be inserted separately with all their - // data in the load_child_remote function. - match is_default_child_storage_key(&k.0) { - true => None, - false => Some((k.0, v.0)), - } - })); - sp.stop_with_message(format!( - "✅ Inserted keys into DB ({:.2}s)", - start.elapsed().as_secs_f32() - )); + logging::with_elapsed( + || { + pending_ext.batch_insert(key_values.clone().into_iter().filter_map(|(k, v)| { + // Don't insert the child keys here, they need to be inserted separately with + // all their data in the load_child_remote function. + match is_default_child_storage_key(&k.0) { + true => None, + false => Some((k.0, v.0)), + } + })); + + Ok(()) + }, + "Inserting keys into DB...", + |_| "Inserted keys into DB".into(), + ) + .expect("must succeed; qed"); + Ok(key_values) } @@ -781,7 +786,7 @@ where prefixed_top_key: &StorageKey, child_keys: Vec, at: B::Hash, - ) -> Result, &'static str> { + ) -> Result> { let child_keys_len = child_keys.len(); let payloads = child_keys @@ -803,7 +808,7 @@ where match Self::get_storage_data_dynamic_batch_size(client, payloads, &bar).await { Ok(storage_data) => storage_data, Err(e) => { - log::error!(target: LOG_TARGET, "batch processing failed: {:?}", e); + error!(target: LOG_TARGET, "batch processing failed: {e:?}"); return Err("batch processing failed") }, }; @@ -816,7 +821,7 @@ where .map(|(key, maybe_value)| match maybe_value { Some(v) => (key.clone(), v), None => { - log::warn!(target: LOG_TARGET, "key {:?} had no corresponding value.", &key); + warn!(target: LOG_TARGET, "key {key:?} had no corresponding value."); (key.clone(), StorageData(vec![])) }, }) @@ -828,7 +833,7 @@ where prefixed_top_key: &StorageKey, child_prefix: StorageKey, at: B::Hash, - ) -> Result, &'static str> { + ) -> Result> { let retry_strategy = FixedInterval::new(Self::KEYS_PAGE_RETRY_INTERVAL).take(Self::MAX_RETRIES); let mut all_child_keys = Vec::new(); @@ -850,7 +855,7 @@ where let child_keys = Retry::spawn(retry_strategy.clone(), get_child_keys_closure) .await .map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); + error!(target: LOG_TARGET, "Error = {e:?}"); "rpc child_get_keys failed." })?; @@ -896,7 +901,7 @@ where &self, top_kv: &[KeyValue], pending_ext: &mut TestExternalities>, - ) -> Result { + ) -> Result { let child_roots = top_kv .iter() .filter(|(k, _)| is_default_child_storage_key(k.as_ref())) @@ -904,7 +909,7 @@ where .collect::>(); if child_roots.is_empty() { - info!(target: LOG_TARGET, "👩‍👦 no child roots found to scrape",); + info!(target: LOG_TARGET, "👩‍👦 no child roots found to scrape"); return Ok(Default::default()) } @@ -930,7 +935,7 @@ where let un_prefixed = match ChildType::from_prefixed_key(&prefixed_top_key) { Some((ChildType::ParentKeyId, storage_key)) => storage_key, None => { - log::error!(target: LOG_TARGET, "invalid key: {:?}", prefixed_top_key); + error!(target: LOG_TARGET, "invalid key: {prefixed_top_key:?}"); return Err("Invalid child key") }, }; @@ -954,13 +959,13 @@ where async fn load_top_remote( &self, pending_ext: &mut TestExternalities>, - ) -> Result { + ) -> Result { let config = self.as_online(); let at = self .as_online() .at .expect("online config must be initialized by this point; qed."); - log::info!(target: LOG_TARGET, "scraping key-pairs from remote at block height {:?}", at); + info!(target: LOG_TARGET, "scraping key-pairs from remote at block height {at:?}"); let mut keys_and_values = Vec::new(); for prefix in &config.hashed_prefixes { @@ -968,7 +973,7 @@ where let additional_key_values = self.rpc_get_pairs(StorageKey(prefix.to_vec()), at, pending_ext).await?; let elapsed = now.elapsed(); - log::info!( + info!( target: LOG_TARGET, "adding data for hashed prefix: {:?}, took {:.2}s", HexDisplay::from(prefix), @@ -979,7 +984,7 @@ where for key in &config.hashed_keys { let key = StorageKey(key.to_vec()); - log::info!( + info!( target: LOG_TARGET, "adding data for hashed key: {:?}", HexDisplay::from(&key) @@ -990,7 +995,7 @@ where keys_and_values.push((key, value)); }, None => { - log::warn!( + warn!( target: LOG_TARGET, "no data found for hashed key: {:?}", HexDisplay::from(&key) @@ -1005,17 +1010,16 @@ where /// The entry point of execution, if `mode` is online. /// /// initializes the remote client in `transport`, and sets the `at` field, if not specified. - async fn init_remote_client(&mut self) -> Result<(), &'static str> { + async fn init_remote_client(&mut self) -> Result<()> { // First, initialize the http client. self.as_online_mut().transport.init().await?; // Then, if `at` is not set, set it. if self.as_online().at.is_none() { let at = self.rpc_get_head().await?; - log::info!( + info!( target: LOG_TARGET, - "since no at is provided, setting it to latest finalized head, {:?}", - at + "since no at is provided, setting it to latest finalized head, {at:?}", ); self.as_online_mut().at = Some(at); } @@ -1040,7 +1044,7 @@ where .filter(|p| *p != DEFAULT_CHILD_STORAGE_KEY_PREFIX) .count() == 0 { - log::info!( + info!( target: LOG_TARGET, "since no prefix is filtered, the data for all pallets will be downloaded" ); @@ -1050,7 +1054,7 @@ where Ok(()) } - async fn load_header(&self) -> Result { + async fn load_header(&self) -> Result { let retry_strategy = FixedInterval::new(Self::KEYS_PAGE_RETRY_INTERVAL).take(Self::MAX_RETRIES); let get_header_closure = || { @@ -1069,14 +1073,12 @@ where /// `load_child_remote`. /// /// Must be called after `init_remote_client`. - async fn load_remote_and_maybe_save( - &mut self, - ) -> Result>, &'static str> { + async fn load_remote_and_maybe_save(&mut self) -> Result>> { let state_version = StateApi::::runtime_version(self.as_online().rpc_client(), None) .await .map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); + error!(target: LOG_TARGET, "Error = {e:?}"); "rpc runtime_version failed." }) .map(|v| v.state_version())?; @@ -1100,11 +1102,10 @@ where self.load_header().await?, ); let encoded = snapshot.encode(); - log::info!( + info!( target: LOG_TARGET, - "writing snapshot of {} bytes to {:?}", + "writing snapshot of {} bytes to {path:?}", encoded.len(), - path ); std::fs::write(path, encoded).map_err(|_| "fs::write failed")?; @@ -1119,33 +1120,35 @@ where Ok(pending_ext) } - async fn do_load_remote(&mut self) -> Result, &'static str> { + async fn do_load_remote(&mut self) -> Result> { self.init_remote_client().await?; let inner_ext = self.load_remote_and_maybe_save().await?; Ok(RemoteExternalities { header: self.load_header().await?, inner_ext }) } - fn do_load_offline( - &mut self, - config: OfflineConfig, - ) -> Result, &'static str> { - let mut sp = Spinner::with_timer(Spinners::Dots, "Loading snapshot...".into()); - let start = Instant::now(); - info!(target: LOG_TARGET, "Loading snapshot from {:?}", &config.state_snapshot.path); - let Snapshot { snapshot_version: _, header, state_version, raw_storage, storage_root } = - Snapshot::::load(&config.state_snapshot.path)?; - - let inner_ext = TestExternalities::from_raw_snapshot( - raw_storage, - storage_root, - self.overwrite_state_version.unwrap_or(state_version), - ); - sp.stop_with_message(format!("✅ Loaded snapshot ({:.2}s)", start.elapsed().as_secs_f32())); + fn do_load_offline(&mut self, config: OfflineConfig) -> Result> { + let (header, inner_ext) = logging::with_elapsed( + || { + info!(target: LOG_TARGET, "Loading snapshot from {:?}", &config.state_snapshot.path); + + let Snapshot { header, state_version, raw_storage, storage_root, .. } = + Snapshot::::load(&config.state_snapshot.path)?; + let inner_ext = TestExternalities::from_raw_snapshot( + raw_storage, + storage_root, + self.overwrite_state_version.unwrap_or(state_version), + ); + + Ok((header, inner_ext)) + }, + "Loading snapshot...", + |_| "Loaded snapshot".into(), + )?; Ok(RemoteExternalities { inner_ext, header }) } - pub(crate) async fn pre_build(mut self) -> Result, &'static str> { + pub(crate) async fn pre_build(mut self) -> Result> { let mut ext = match self.mode.clone() { Mode::Offline(config) => self.do_load_offline(config)?, Mode::Online(_) => self.do_load_remote().await?, @@ -1159,7 +1162,7 @@ where // inject manual key values. if !self.hashed_key_values.is_empty() { - log::info!( + info!( target: LOG_TARGET, "extending externalities with {} manually injected key-values", self.hashed_key_values.len() @@ -1169,7 +1172,7 @@ where // exclude manual key values. if !self.hashed_blacklist.is_empty() { - log::info!( + info!( target: LOG_TARGET, "excluding externalities from {} keys", self.hashed_blacklist.len() @@ -1221,7 +1224,7 @@ where self } - pub async fn build(self) -> Result, &'static str> { + pub async fn build(self) -> Result> { let mut ext = self.pre_build().await?; ext.commit_all().unwrap(); diff --git a/substrate/utils/frame/remote-externalities/src/logging.rs b/substrate/utils/frame/remote-externalities/src/logging.rs new file mode 100644 index 000000000000..7ab901c004de --- /dev/null +++ b/substrate/utils/frame/remote-externalities/src/logging.rs @@ -0,0 +1,86 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::{ + future::Future, + io::{self, IsTerminal}, + time::Instant, +}; + +use spinners::{Spinner, Spinners}; + +use super::Result; + +// A simple helper to time a operation with a nice spinner, start message, and end message. +// +// The spinner is only displayed when stdout is a terminal. +pub(super) fn with_elapsed(f: F, start_msg: &str, end_msg: EndMsg) -> Result +where + F: FnOnce() -> Result, + EndMsg: FnOnce(&R) -> String, +{ + let timer = Instant::now(); + let mut maybe_sp = start(start_msg); + + Ok(end(f()?, timer, maybe_sp.as_mut(), end_msg)) +} + +// A simple helper to time an async operation with a nice spinner, start message, and end message. +// +// The spinner is only displayed when stdout is a terminal. +pub(super) async fn with_elapsed_async( + f: F, + start_msg: &str, + end_msg: EndMsg, +) -> Result +where + F: FnOnce() -> Fut, + Fut: Future>, + EndMsg: FnOnce(&R) -> String, +{ + let timer = Instant::now(); + let mut maybe_sp = start(start_msg); + + Ok(end(f().await?, timer, maybe_sp.as_mut(), end_msg)) +} + +fn start(start_msg: &str) -> Option { + let msg = format!("⏳ {start_msg}"); + + if io::stdout().is_terminal() { + Some(Spinner::new(Spinners::Dots, msg)) + } else { + println!("{msg}"); + + None + } +} + +fn end(val: T, timer: Instant, maybe_sp: Option<&mut Spinner>, end_msg: EndMsg) -> T +where + EndMsg: FnOnce(&T) -> String, +{ + let msg = format!("✅ {} in {:.2}s", end_msg(&val), timer.elapsed().as_secs_f32()); + + if let Some(sp) = maybe_sp { + sp.stop_with_message(msg); + } else { + println!("{msg}"); + } + + val +} From f3ab3854e1df9e0498599f01ba4f9f152426432a Mon Sep 17 00:00:00 2001 From: Utkarsh Bhardwaj Date: Fri, 3 Jan 2025 10:39:39 +0000 Subject: [PATCH 219/340] migrate pallet-mixnet to umbrella crate (#6986) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Migrate pallet-mixnet to use umbrella crate whilst adding a few types and traits in the frame prelude that are used by other pallets as well. ## Review Notes * This PR migrates `pallet-mixnet` to use the umbrella crate. * Note that some imports like `use sp_application_crypto::RuntimeAppPublic;` and imports from `sp_mixnet::types::` have not been migrated to the umbrella crate as they are not used in any / many other places and are relevant only to the `pallet-mixnet`. * Transaction related helpers to submit transactions from `frame-system` have been added to the main `prelude` as they have usage across various pallets. ```Rust pub use frame_system::offchain::*; ``` * Exporting `arithmetic` module in the main `prelude` since this is used a lot throughout various pallets. * Nightly formatting has been applied using `cargo fmt` * Benchmarking dependencies have been removed from`palet-mixnet` as there is no benchmarking.rs present for `pallet-mixnet`. For the same reason, `"pallet-mixnet?/runtime-benchmarks"` has been removed from `umbrella/Cargo.toml`. --------- Co-authored-by: Dónal Murray --- Cargo.lock | 7 +--- prdoc/pr_6986.prdoc | 18 ++++++++++ substrate/frame/mixnet/Cargo.toml | 24 ++----------- substrate/frame/mixnet/src/lib.rs | 60 ++++++++++++++----------------- substrate/frame/src/lib.rs | 19 ++++++++-- umbrella/Cargo.toml | 1 - 6 files changed, 64 insertions(+), 65 deletions(-) create mode 100644 prdoc/pr_6986.prdoc diff --git a/Cargo.lock b/Cargo.lock index 6151ed33c5b6..3c55a14256c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14029,18 +14029,13 @@ dependencies = [ name = "pallet-mixnet" version = "0.4.0" dependencies = [ - "frame-benchmarking 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", "log", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", "serde", "sp-application-crypto 30.0.0", - "sp-arithmetic 23.0.0", - "sp-io 30.0.0", "sp-mixnet 0.4.0", - "sp-runtime 31.0.1", ] [[package]] diff --git a/prdoc/pr_6986.prdoc b/prdoc/pr_6986.prdoc new file mode 100644 index 000000000000..8deb6b04bd1c --- /dev/null +++ b/prdoc/pr_6986.prdoc @@ -0,0 +1,18 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: '[pallet-mixnet] Migrate to using frame umbrella crate' + +doc: + - audience: Runtime Dev + description: This PR migrates the pallet-mixnet to use the frame umbrella crate. This + is part of the ongoing effort to migrate all pallets to use the frame umbrella crate. + The effort is tracked [here](https://github.com/paritytech/polkadot-sdk/issues/6504). + +crates: + - name: pallet-mixnet + bump: minor + - name: polkadot-sdk-frame + bump: minor + - name: polkadot-sdk + bump: none \ No newline at end of file diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml index bb5e84864566..0ae3b3938c60 100644 --- a/substrate/frame/mixnet/Cargo.toml +++ b/substrate/frame/mixnet/Cargo.toml @@ -17,42 +17,24 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +frame = { workspace = true, features = ["experimental", "runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["derive"], workspace = true } sp-application-crypto = { workspace = true } -sp-arithmetic = { workspace = true } -sp-io = { workspace = true } sp-mixnet = { workspace = true } -sp-runtime = { workspace = true } [features] default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "scale-info/std", "serde/std", "sp-application-crypto/std", - "sp-arithmetic/std", - "sp-io/std", "sp-mixnet/std", - "sp-runtime/std", -] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", + "frame/try-runtime", ] diff --git a/substrate/frame/mixnet/src/lib.rs b/substrate/frame/mixnet/src/lib.rs index 6579ed678ae7..984981817676 100644 --- a/substrate/frame/mixnet/src/lib.rs +++ b/substrate/frame/mixnet/src/lib.rs @@ -23,28 +23,23 @@ extern crate alloc; +pub use pallet::*; + use alloc::vec::Vec; -use codec::{Decode, Encode, MaxEncodedLen}; use core::cmp::Ordering; -use frame_support::{ - traits::{EstimateNextSessionRotation, Get, OneSessionHandler}, - BoundedVec, +use frame::{ + deps::{ + sp_io::{self, MultiRemovalResults}, + sp_runtime, + }, + prelude::*, }; -use frame_system::{ - offchain::{CreateInherent, SubmitTransaction}, - pallet_prelude::BlockNumberFor, -}; -pub use pallet::*; -use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_application_crypto::RuntimeAppPublic; -use sp_arithmetic::traits::{CheckedSub, Saturating, UniqueSaturatedInto, Zero}; -use sp_io::MultiRemovalResults; use sp_mixnet::types::{ AuthorityId, AuthoritySignature, KxPublic, Mixnode, MixnodesErr, PeerId, SessionIndex, SessionPhase, SessionStatus, KX_PUBLIC_SIZE, }; -use sp_runtime::RuntimeDebug; const LOG_TARGET: &str = "runtime::mixnet"; @@ -168,12 +163,9 @@ fn twox>( // The pallet //////////////////////////////////////////////////////////////////////////////// -#[frame_support::pallet(dev_mode)] +#[frame::pallet(dev_mode)] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; - #[pallet::pallet] pub struct Pallet(_); @@ -254,7 +246,7 @@ pub mod pallet { StorageDoubleMap<_, Identity, SessionIndex, Identity, AuthorityIndex, BoundedMixnodeFor>; #[pallet::genesis_config] - #[derive(frame_support::DefaultNoBound)] + #[derive(DefaultNoBound)] pub struct GenesisConfig { /// The mixnode set for the very first session. pub mixnodes: BoundedVec, T::MaxAuthorities>, @@ -308,7 +300,7 @@ pub mod pallet { fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { let Self::Call::register { registration, signature } = call else { - return InvalidTransaction::Call.into() + return InvalidTransaction::Call.into(); }; // Check session index matches @@ -320,16 +312,16 @@ pub mod pallet { // Check authority index is valid if registration.authority_index >= T::MaxAuthorities::get() { - return InvalidTransaction::BadProof.into() + return InvalidTransaction::BadProof.into(); } let Some(authority_id) = NextAuthorityIds::::get(registration.authority_index) else { - return InvalidTransaction::BadProof.into() + return InvalidTransaction::BadProof.into(); }; // Check the authority hasn't registered a mixnode yet if Self::already_registered(registration.session_index, registration.authority_index) { - return InvalidTransaction::Stale.into() + return InvalidTransaction::Stale.into(); } // Check signature. Note that we don't use regular signed transactions for registration @@ -339,7 +331,7 @@ pub mod pallet { authority_id.verify(&encoded_registration, signature) }); if !signature_ok { - return InvalidTransaction::BadProof.into() + return InvalidTransaction::BadProof.into(); } ValidTransaction::with_tag_prefix("MixnetRegistration") @@ -368,12 +360,12 @@ impl Pallet { .saturating_sub(CurrentSessionStartBlock::::get()); let Some(block_in_phase) = block_in_phase.checked_sub(&T::NumCoverToCurrentBlocks::get()) else { - return SessionPhase::CoverToCurrent + return SessionPhase::CoverToCurrent; }; let Some(block_in_phase) = block_in_phase.checked_sub(&T::NumRequestsToCurrentBlocks::get()) else { - return SessionPhase::RequestsToCurrent + return SessionPhase::RequestsToCurrent; }; if block_in_phase < T::NumCoverToPrevBlocks::get() { SessionPhase::CoverToPrev @@ -411,7 +403,7 @@ impl Pallet { return Err(MixnodesErr::InsufficientRegistrations { num: 0, min: T::MinMixnodes::get(), - }) + }); }; Self::mixnodes(prev_session_index) } @@ -430,7 +422,7 @@ impl Pallet { // registering let block_in_session = block_number.saturating_sub(CurrentSessionStartBlock::::get()); if block_in_session < T::NumRegisterStartSlackBlocks::get() { - return false + return false; } let (Some(end_block), _weight) = @@ -438,7 +430,7 @@ impl Pallet { else { // Things aren't going to work terribly well in this case as all the authorities will // just pile in after the slack period... - return true + return true; }; let remaining_blocks = end_block @@ -447,7 +439,7 @@ impl Pallet { if remaining_blocks.is_zero() { // Into the slack time at the end of the session. Not necessarily too late; // registrations are accepted right up until the session ends. - return true + return true; } // Want uniform distribution over the remaining blocks, so pick this block with probability @@ -496,7 +488,7 @@ impl Pallet { "Session {session_index} registration attempted, \ but current session is {current_session_index}", ); - return false + return false; } let block_number = frame_system::Pallet::::block_number(); @@ -505,7 +497,7 @@ impl Pallet { target: LOG_TARGET, "Waiting for the session to progress further before registering", ); - return false + return false; } let Some((authority_index, authority_id)) = Self::next_local_authority() else { @@ -513,7 +505,7 @@ impl Pallet { target: LOG_TARGET, "Not an authority in the next session; cannot register a mixnode", ); - return false + return false; }; if Self::already_registered(session_index, authority_index) { @@ -521,14 +513,14 @@ impl Pallet { target: LOG_TARGET, "Already registered a mixnode for the next session", ); - return false + return false; } let registration = Registration { block_number, session_index, authority_index, mixnode: mixnode.into() }; let Some(signature) = authority_id.sign(®istration.encode()) else { log::debug!(target: LOG_TARGET, "Failed to sign registration"); - return false + return false; }; let call = Call::register { registration, signature }; let xt = T::create_inherent(call.into()); diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 8031ddf96e6a..b3e340cbcbff 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -203,12 +203,18 @@ pub mod prelude { /// Dispatch types from `frame-support`, other fundamental traits #[doc(no_inline)] pub use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; - pub use frame_support::traits::{Contains, IsSubType, OnRuntimeUpgrade}; + pub use frame_support::traits::{ + Contains, EstimateNextSessionRotation, IsSubType, OnRuntimeUpgrade, OneSessionHandler, + }; /// Pallet prelude of `frame-system`. #[doc(no_inline)] pub use frame_system::pallet_prelude::*; + /// Transaction related helpers to submit transactions. + #[doc(no_inline)] + pub use frame_system::offchain::*; + /// All FRAME-relevant derive macros. #[doc(no_inline)] pub use super::derive::*; @@ -216,6 +222,9 @@ pub mod prelude { /// All hashing related things pub use super::hashing::*; + /// All arithmetic types and traits used for safe math. + pub use super::arithmetic::*; + /// Runtime traits #[doc(no_inline)] pub use sp_runtime::traits::{ @@ -223,9 +232,11 @@ pub mod prelude { Saturating, StaticLookup, TrailingZeroInput, }; - /// Other error/result types for runtime + /// Other runtime types and traits #[doc(no_inline)] - pub use sp_runtime::{DispatchErrorWithPostInfo, DispatchResultWithInfo, TokenError}; + pub use sp_runtime::{ + BoundToRuntimeAppPublic, DispatchErrorWithPostInfo, DispatchResultWithInfo, TokenError, + }; } #[cfg(any(feature = "try-runtime", test))] @@ -509,6 +520,8 @@ pub mod traits { } /// The arithmetic types used for safe math. +/// +/// This is already part of the [`prelude`]. pub mod arithmetic { pub use sp_arithmetic::{traits::*, *}; } diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index f36d39d63f6a..d2a47ade7f87 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -290,7 +290,6 @@ runtime-benchmarks = [ "pallet-membership?/runtime-benchmarks", "pallet-message-queue?/runtime-benchmarks", "pallet-migrations?/runtime-benchmarks", - "pallet-mixnet?/runtime-benchmarks", "pallet-mmr?/runtime-benchmarks", "pallet-multisig?/runtime-benchmarks", "pallet-nft-fractionalization?/runtime-benchmarks", From 659f4848a7564c45d8d3a3d13c7596801050da82 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 3 Jan 2025 13:29:29 +0100 Subject: [PATCH 220/340] [docs] Fix release naming (#7032) - **[docs] Fix release naming** - **Remove outdated and unmaintained file** Closes https://github.com/paritytech/polkadot-sdk/issues/6998 --------- Signed-off-by: Oliver Tale-Yazdi --- README.md | 4 +- cumulus/docs/release.md | 135 ---------------------------------------- docs/RELEASE.md | 6 +- 3 files changed, 7 insertions(+), 138 deletions(-) delete mode 100644 cumulus/docs/release.md diff --git a/README.md b/README.md index 6c0dfbb2e7e4..24352cc28a1a 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/paritytec ![Current Stable Release](https://raw.githubusercontent.com/paritytech/release-registry/main/badges/polkadot-sdk-latest.svg)  ![Next Stable Release](https://raw.githubusercontent.com/paritytech/release-registry/main/badges/polkadot-sdk-next.svg) -The Polkadot SDK is released every three months as a `stableYYMMDD` release. They are supported for +The Polkadot SDK is released every three months as a `stableYYMM` release. They are supported for one year with patches. See the next upcoming versions in the [Release -Registry](https://github.com/paritytech/release-registry/). +Registry](https://github.com/paritytech/release-registry/) and more docs in [RELEASE.md](./docs/RELEASE.md). You can use [`psvm`](https://github.com/paritytech/psvm) to update all dependencies to a specific version without needing to manually select the correct version for each crate. diff --git a/cumulus/docs/release.md b/cumulus/docs/release.md deleted file mode 100644 index 8302b7b9b7fc..000000000000 --- a/cumulus/docs/release.md +++ /dev/null @@ -1,135 +0,0 @@ -# Releases - -## Versioning - -### Example #1 - -``` -| Polkadot | v 0. 9.22 | -| Client | v 0. 9.22 0 | -| Runtime | v 9 22 0 | => 9220 -| semver | 0. 9.22 0 | -``` - -### Example #2 - -``` -| Polkadot | v 0.10.42 | -| Client | v 0.10.42 0 | -| Runtime | v 10.42 0 | => 10420 -| semver | 0.10.42 0 | -``` - -### Example #3 - -``` -| Polkadot | v 1. 2.18 | -| Client | v 1. 2.18 0 | -| Runtime | v 1 2 18 0 | => 102180 -| semver | 1. 2.18 0 | -``` - - -This document contains information related to the releasing process and describes a few of the steps and checks that are -performed during the release process. - -## Client - -### Burn In - -Ensure that Parity DevOps has run the new release on Westend and Kusama Asset Hub collators for 12h prior to publishing -the release. - -### Build Artifacts - -Add any necessary assets to the release. They should include: - -- Linux binaries - - GPG signature - - SHA256 checksum -- WASM binaries of the runtimes -- Source code - - -## Runtimes - -### Spec Version - -A new runtime release must bump the `spec_version`. This may follow a pattern with the client release (e.g. runtime -v9220 corresponds to v0.9.22). - -### Runtime version bump between RCs - -The clients need to be aware of runtime changes. However, we do not want to bump the `spec_version` for every single -release candidate. Instead, we can bump the `impl` field of the version to signal the change to the client. This applies -only to runtimes that have been deployed. - -### Old Migrations Removed - -Previous `on_runtime_upgrade` functions from old upgrades should be removed. - -### New Migrations - -Ensure that any migrations that are required due to storage or logic changes are included in the `on_runtime_upgrade` -function of the appropriate pallets. - -### Extrinsic Ordering & Storage - -Offline signing libraries depend on a consistent ordering of call indices and functions. Compare the metadata of the -current and new runtimes and ensure that the `module index, call index` tuples map to the same set of functions. It also -checks if there have been any changes in `storage`. In case of a breaking change, increase `transaction_version`. - -To verify the order has not changed, manually start the following -[Github Action](https://github.com/paritytech/polkadot-sdk/cumulus/.github/workflows/release-20_extrinsic-ordering-check-from-bin.yml). -It takes around a minute to run and will produce the report as artifact you need to manually check. - -To run it, in the _Run Workflow_ dropdown: -1. **Use workflow from**: to ignore, leave `master` as default -2. **The WebSocket url of the reference node**: - Asset Hub Polkadot: `wss://statemint-rpc.polkadot.io` - - Asset Hub Kusama: `wss://statemine-rpc.polkadot.io` - - Asset Hub Westend: `wss://westmint-rpc.polkadot.io` -3. **A url to a Linux binary for the node containing the runtime to test**: Paste the URL of the latest - release-candidate binary from the draft-release on Github. The binary has to previously be uploaded to S3 (Github url - link to the binary is constantly changing) - - E.g: https://releases.parity.io/cumulus/v0.9.270-rc3/polkadot-parachain -4. **The name of the chain under test. Usually, you would pass a local chain**: - Asset Hub Polkadot: - `asset-hub-polkadot-local` - - Asset Hub Kusama: `asset-hub-kusama-local` - - Asset Hub Westend: `asset-hub-westend-local` -5. Click **Run workflow** - -When the workflow is done, click on it and download the zip artifact, inside you'll find an `output.txt` file. The -things to look for in the output are lines like: - -- `[Identity] idx 28 -> 25 (calls 15)` - indicates the index for Identity has changed -- `[+] Society, Recovery` - indicates the new version includes 2 additional modules/pallets. -- If no indices have changed, every modules line should look something like `[Identity] idx 25 (calls 15)` - -**Note**: Adding new functions to the runtime does not constitute a breaking change as long as the indexes did not -change. - -**Note**: Extrinsic function signatures changes (adding/removing & ordering arguments) are not caught by the job, so -those changes should be reviewed "manually" - -### Benchmarks - -The Benchmarks can now be started from the CI. First find the CI pipeline from -[here](https://gitlab.parity.io/parity/mirrors/cumulus/-/pipelines?page=1&scope=all&ref=release-parachains-v9220) and -pick the latest. [Guide](https://github.com/paritytech/ci_cd/wiki/Benchmarks:-cumulus) - -### Integration Tests - -Until https://github.com/paritytech/ci_cd/issues/499 is done, tests will have to be run manually. -1. Go to https://github.com/paritytech/parachains-integration-tests and check out the release branch. E.g. -https://github.com/paritytech/parachains-integration-tests/tree/release-v9270-v0.9.27 for `release-parachains-v0.9.270` -2. Clone `release-parachains-` branch from Cumulus -3. `cargo build --release` -4. Copy `./target/polkadot-parachain` to `./bin` -5. Clone `it/release--fast-sudo` from Polkadot In case the branch does not exists (it is a manual process): - cherry pick `paritytech/polkadot@791c8b8` and run: - `find . -type f -name "*.toml" -print0 | xargs -0 sed -i '' -e 's/polkadot-vX.X.X/polkadot-v/g'` -6. `cargo build --release --features fast-runtime` -7. Copy `./target/polkadot` into `./bin` (in Cumulus) -8. Run the tests: - - Asset Hub Polkadot: `yarn zombienet-test -c ./examples/statemint/config.toml -t ./examples/statemint` - - Asset Hub Kusama: `yarn zombienet-test -c ./examples/statemine/config.toml -t ./examples/statemine` diff --git a/docs/RELEASE.md b/docs/RELEASE.md index bea367411359..677cb5465b67 100644 --- a/docs/RELEASE.md +++ b/docs/RELEASE.md @@ -14,7 +14,11 @@ Merging to it is restricted to [Backports](#backports). We are releasing multiple different things from this repository in one release, but we don't want to use the same version for everything. Thus, in the following we explain the versioning story for the crates, node and Westend & -Rococo. To easily refer to a release, it shall be named by its date in the form `stableYYMMDD`. +Rococo. + +To easily refer to a release, it shall be named by its date in the form `stableYYMM`. Patches to stable releases are +tagged in the form of `stableYYMM-PATCH`, with `PATCH` ranging from 1 to 99. For example, the fourth patch to +`stable2409` would be `stable2409-4`. ## Crate From 721f6d97613b0ece9c8414e8ec8ba31d2f67d40c Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Fri, 3 Jan 2025 14:19:18 +0100 Subject: [PATCH 221/340] [WIP] Fix networking-benchmarks (#7036) cc https://github.com/paritytech/ci_cd/issues/1094 --- ...nchmarks.yml => benchmarks-networking.yml} | 20 ++++++++++--------- ...enchmarks.yml => benchmarks-subsystem.yml} | 0 2 files changed, 11 insertions(+), 9 deletions(-) rename .github/workflows/{networking-benchmarks.yml => benchmarks-networking.yml} (86%) rename .github/workflows/{subsystem-benchmarks.yml => benchmarks-subsystem.yml} (100%) diff --git a/.github/workflows/networking-benchmarks.yml b/.github/workflows/benchmarks-networking.yml similarity index 86% rename from .github/workflows/networking-benchmarks.yml rename to .github/workflows/benchmarks-networking.yml index e45ae601105d..79494b9a015c 100644 --- a/.github/workflows/networking-benchmarks.yml +++ b/.github/workflows/benchmarks-networking.yml @@ -17,7 +17,7 @@ jobs: uses: ./.github/workflows/reusable-preflight.yml build: - timeout-minutes: 80 + timeout-minutes: 50 needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER_BENCHMARK }} container: @@ -27,12 +27,8 @@ jobs: matrix: features: [ - { - bench: "notifications_protocol", - }, - { - bench: "request_response_protocol", - }, + { bench: "notifications_protocol" }, + { bench: "request_response_protocol" }, ] steps: - name: Checkout @@ -42,7 +38,7 @@ jobs: id: run-benchmarks run: | mkdir -p ./charts - forklift cargo bench -p sc-network --bench ${{ matrix.features.bench }} -- --output-format bencher | grep "^test" | tee ./charts/networking-bench.txt || echo "Benchmarks failed" + forklift cargo bench -p sc-network --bench ${{ matrix.features.bench }} -- --output-format bencher | grep "^test" | tee ./charts/${{ matrix.features.bench }}.txt || echo "Benchmarks failed" ls -lsa ./charts - name: Upload artifacts @@ -69,7 +65,13 @@ jobs: - name: Download artifacts uses: actions/download-artifact@v4.1.8 with: - name: networking-bench-${{ github.sha }} + name: notifications_protocol-${{ github.sha }} + path: ./charts + + - name: Download artifacts + uses: actions/download-artifact@v4.1.8 + with: + name: request_response_protocol-${{ github.sha }} path: ./charts - name: Setup git diff --git a/.github/workflows/subsystem-benchmarks.yml b/.github/workflows/benchmarks-subsystem.yml similarity index 100% rename from .github/workflows/subsystem-benchmarks.yml rename to .github/workflows/benchmarks-subsystem.yml From 0b4f131b000e01f1aca3f023937a36dcc281d5e2 Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Sat, 4 Jan 2025 06:22:12 +0800 Subject: [PATCH 222/340] Replace duplicated whitelist with whitelisted_storage_keys (#7024) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit related issue: #7018 replaced duplicated whitelists with `AllPalletsWithSystem::whitelisted_storage_keys();` in this PR --------- Co-authored-by: Guillaume Thiolliere Co-authored-by: Bastian Köcher --- .../runtimes/assets/asset-hub-rococo/src/lib.rs | 16 ++-------------- .../runtimes/assets/asset-hub-westend/src/lib.rs | 16 ++-------------- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 14 ++------------ .../bridge-hubs/bridge-hub-westend/src/lib.rs | 14 ++------------ .../collectives/collectives-westend/src/lib.rs | 14 ++------------ .../contracts/contracts-rococo/src/lib.rs | 14 ++------------ .../runtimes/coretime/coretime-rococo/src/lib.rs | 14 ++------------ .../coretime/coretime-westend/src/lib.rs | 14 ++------------ .../runtimes/people/people-rococo/src/lib.rs | 14 ++------------ .../runtimes/people/people-westend/src/lib.rs | 14 ++------------ .../runtimes/testing/penpal/src/lib.rs | 14 ++------------ 11 files changed, 22 insertions(+), 136 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index dd1535826152..8f4ae4670acd 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -1854,20 +1854,8 @@ impl_runtime_apis! { type ToWestend = XcmBridgeHubRouterBench; - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - //TODO: use from relay_well_known_keys::ACTIVE_CONFIG - hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 707d1c52f743..26ef3219a1e9 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -2030,20 +2030,8 @@ impl_runtime_apis! { type ToRococo = XcmBridgeHubRouterBench; - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - //TODO: use from relay_well_known_keys::ACTIVE_CONFIG - hex_literal::hex!("06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 492b731610ce..88146cecb9ef 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -1498,18 +1498,8 @@ impl_runtime_apis! { } } - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index edf79ea0c315..1ca709f0d8cb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -1315,18 +1315,8 @@ impl_runtime_apis! { } } - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 5c2ba2e24c22..d3cd285ba67a 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -1139,18 +1139,8 @@ impl_runtime_apis! { } } - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 594c9b26f57e..be369565dba9 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -849,18 +849,8 @@ impl_runtime_apis! { } } - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index e8f6e6659e13..c4d43e4361fa 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -1140,18 +1140,8 @@ impl_runtime_apis! { type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index ce965f0ad1ba..431bfc8a63ba 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -1135,18 +1135,8 @@ impl_runtime_apis! { type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index b8db687da625..ef3c90ace826 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -1055,18 +1055,8 @@ impl_runtime_apis! { type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index 620ec41c071c..ebf8fcb33bd8 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -1055,18 +1055,8 @@ impl_runtime_apis! { type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index b51670c792d6..51dc95bf2c71 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -1132,18 +1132,8 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} - let whitelist: Vec = vec![ - // Block Number - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), - // Total Issuance - hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), - // Execution Phase - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), - // Event Count - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), - // System Events - hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), - ]; + use frame_support::traits::WhitelistedStorageKeys; + let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); let mut batches = Vec::::new(); let params = (&config, &whitelist); From b5a5ac4487890046d226bedb0238eaccb423ae42 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Sat, 4 Jan 2025 11:03:30 +0900 Subject: [PATCH 223/340] Make `TransactionExtension` tuple of tuple transparent for implication (#7028) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently `(A, B, C)` and `((A, B), C)` change the order of implications in the transaction extension pipeline. This order is not accessible in the metadata, because the metadata is just a vector of transaction extension, the nested structure is not visible. This PR make the implementation for tuple of `TransactionExtension` better for tuple of tuple. `(A, B, C)` and `((A, B), C)` don't change the implication for the validation A. This is a breaking change but only when using the trait `TransactionExtension` the code implementing the trait is not breaking (surprising rust behavior but fine). --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- prdoc/pr_7028.prdoc | 25 ++ .../src/extensions/check_non_zero_sender.rs | 4 +- .../system/src/extensions/check_nonce.rs | 6 +- .../skip-feeless-payment/src/lib.rs | 5 +- .../primitives/runtime/src/traits/mod.rs | 3 +- .../dispatch_transaction.rs | 2 +- .../src/traits/transaction_extension/mod.rs | 258 +++++++++++++++++- 7 files changed, 279 insertions(+), 24 deletions(-) create mode 100644 prdoc/pr_7028.prdoc diff --git a/prdoc/pr_7028.prdoc b/prdoc/pr_7028.prdoc new file mode 100644 index 000000000000..ead918fc2e00 --- /dev/null +++ b/prdoc/pr_7028.prdoc @@ -0,0 +1,25 @@ +title: 'Fix implication order in implementation of `TransactionExtension` for tuple' +doc: +- audience: + - Runtime Dev + - Runtime User + description: |- + Before this PR, the implications were different in the pipeline `(A, B, C)` and `((A, B), C)`. + This PR fixes this behavior and make nested tuple transparant, the implication order of tuple of + tuple is now the same as in a single tuple. + + For runtime users this mean that the implication can be breaking depending on the pipeline used + in the runtime. + + For runtime developers this breaks usage of `TransactionExtension::validate`. + When calling `TransactionExtension::validate` the implication must now implement `Implication` + trait, you can use `TxBaseImplication` to wrap the type and use it as the base implication. + E.g. instead of `&(extension_version, call),` you can write `&TxBaseImplication((extension_version, call))`. + +crates: +- name: sp-runtime + bump: major +- name: pallet-skip-feeless-payment + bump: major +- name: frame-system + bump: major diff --git a/substrate/frame/system/src/extensions/check_non_zero_sender.rs b/substrate/frame/system/src/extensions/check_non_zero_sender.rs index 577e2b324fca..978eebaf3dac 100644 --- a/substrate/frame/system/src/extensions/check_non_zero_sender.rs +++ b/substrate/frame/system/src/extensions/check_non_zero_sender.rs @@ -86,7 +86,7 @@ mod tests { use crate::mock::{new_test_ext, Test, CALL}; use frame_support::{assert_ok, dispatch::DispatchInfo}; use sp_runtime::{ - traits::{AsTransactionAuthorizedOrigin, DispatchTransaction}, + traits::{AsTransactionAuthorizedOrigin, DispatchTransaction, TxBaseImplication}, transaction_validity::{TransactionSource::External, TransactionValidityError}, }; @@ -118,7 +118,7 @@ mod tests { let info = DispatchInfo::default(); let len = 0_usize; let (_, _, origin) = CheckNonZeroSender::::new() - .validate(None.into(), CALL, &info, len, (), CALL, External) + .validate(None.into(), CALL, &info, len, (), &TxBaseImplication(CALL), External) .unwrap(); assert!(!origin.is_transaction_authorized()); }) diff --git a/substrate/frame/system/src/extensions/check_nonce.rs b/substrate/frame/system/src/extensions/check_nonce.rs index 004ec08a26f2..bc19a09e06a9 100644 --- a/substrate/frame/system/src/extensions/check_nonce.rs +++ b/substrate/frame/system/src/extensions/check_nonce.rs @@ -186,7 +186,7 @@ mod tests { assert_ok, assert_storage_noop, dispatch::GetDispatchInfo, traits::OriginTrait, }; use sp_runtime::{ - traits::{AsTransactionAuthorizedOrigin, DispatchTransaction}, + traits::{AsTransactionAuthorizedOrigin, DispatchTransaction, TxBaseImplication}, transaction_validity::TransactionSource::External, }; @@ -335,7 +335,7 @@ mod tests { let info = DispatchInfo::default(); let len = 0_usize; let (_, val, origin) = CheckNonce::(1u64.into()) - .validate(None.into(), CALL, &info, len, (), CALL, External) + .validate(None.into(), CALL, &info, len, (), &TxBaseImplication(CALL), External) .unwrap(); assert!(!origin.is_transaction_authorized()); assert_ok!(CheckNonce::(1u64.into()).prepare(val, &origin, CALL, &info, len)); @@ -359,7 +359,7 @@ mod tests { let len = 0_usize; // run the validation step let (_, val, origin) = CheckNonce::(1u64.into()) - .validate(Some(1).into(), CALL, &info, len, (), CALL, External) + .validate(Some(1).into(), CALL, &info, len, (), &TxBaseImplication(CALL), External) .unwrap(); // mutate `AccountData` for the caller crate::Account::::mutate(1, |info| { diff --git a/substrate/frame/transaction-payment/skip-feeless-payment/src/lib.rs b/substrate/frame/transaction-payment/skip-feeless-payment/src/lib.rs index dd907f6fcbb7..5ba1d1297679 100644 --- a/substrate/frame/transaction-payment/skip-feeless-payment/src/lib.rs +++ b/substrate/frame/transaction-payment/skip-feeless-payment/src/lib.rs @@ -46,7 +46,8 @@ use frame_support::{ use scale_info::{StaticTypeInfo, TypeInfo}; use sp_runtime::{ traits::{ - DispatchInfoOf, DispatchOriginOf, PostDispatchInfoOf, TransactionExtension, ValidateResult, + DispatchInfoOf, DispatchOriginOf, Implication, PostDispatchInfoOf, TransactionExtension, + ValidateResult, }, transaction_validity::TransactionValidityError, }; @@ -147,7 +148,7 @@ where info: &DispatchInfoOf, len: usize, self_implicit: S::Implicit, - inherited_implication: &impl Encode, + inherited_implication: &impl Implication, source: TransactionSource, ) -> ValidateResult { if call.is_feeless(&origin) { diff --git a/substrate/primitives/runtime/src/traits/mod.rs b/substrate/primitives/runtime/src/traits/mod.rs index cfcc3e5a354d..d371152dc40a 100644 --- a/substrate/primitives/runtime/src/traits/mod.rs +++ b/substrate/primitives/runtime/src/traits/mod.rs @@ -55,7 +55,8 @@ use std::str::FromStr; pub mod transaction_extension; pub use transaction_extension::{ - DispatchTransaction, TransactionExtension, TransactionExtensionMetadata, ValidateResult, + DispatchTransaction, Implication, ImplicationParts, TransactionExtension, + TransactionExtensionMetadata, TxBaseImplication, ValidateResult, }; /// A lazy value. diff --git a/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs b/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs index 28030d12fc9f..1fbaab0d45ac 100644 --- a/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs +++ b/substrate/primitives/runtime/src/traits/transaction_extension/dispatch_transaction.rs @@ -111,7 +111,7 @@ where info, len, self.implicit()?, - &(extension_version, call), + &TxBaseImplication((extension_version, call)), source, ) { // After validation, some origin must have been authorized. diff --git a/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs b/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs index f8c5dc6a724e..27f33acb69cc 100644 --- a/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs +++ b/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs @@ -43,6 +43,72 @@ mod dispatch_transaction; pub use as_transaction_extension::AsTransactionExtension; pub use dispatch_transaction::DispatchTransaction; +/// Provides `Sealed` trait. +mod private { + /// Special trait that prevents the implementation of some traits outside of this crate. + pub trait Sealed {} +} + +/// The base implication in a transaction. +/// +/// This struct is used to represent the base implication in the transaction, that is +/// the implication not part of any transaction extensions. It usually comprises of the call and +/// the transaction extension version. +/// +/// The concept of implication in the transaction extension pipeline is explained in the trait +/// documentation: [`TransactionExtension`]. +#[derive(Encode)] +pub struct TxBaseImplication(pub T); + +impl Implication for TxBaseImplication { + fn parts(&self) -> ImplicationParts<&impl Encode, &impl Encode, &impl Encode> { + ImplicationParts { base: self, explicit: &(), implicit: &() } + } +} + +impl private::Sealed for TxBaseImplication {} + +/// The implication in a transaction. +/// +/// The concept of implication in the transaction extension pipeline is explained in the trait +/// documentation: [`TransactionExtension`]. +#[derive(Encode)] +pub struct ImplicationParts { + /// The base implication, that is implication not part of any transaction extension, usually + /// the call and the transaction extension version. + pub base: Base, + /// The explicit implication in transaction extensions. + pub explicit: Explicit, + /// The implicit implication in transaction extensions. + pub implicit: Implicit, +} + +impl Implication + for ImplicationParts +{ + fn parts(&self) -> ImplicationParts<&impl Encode, &impl Encode, &impl Encode> { + ImplicationParts { base: &self.base, explicit: &self.explicit, implicit: &self.implicit } + } +} + +impl private::Sealed for ImplicationParts {} + +/// Interface of implications in the transaction extension pipeline. +/// +/// Implications can be encoded, this is useful for checking signature on the implications. +/// Implications can be split into parts, this allow to destructure and restructure the +/// implications, this is useful for nested pipeline. +/// +/// This trait is sealed, consider using [`TxBaseImplication`] and [`ImplicationParts`] +/// implementations. +/// +/// The concept of implication in the transaction extension pipeline is explained in the trait +/// documentation: [`TransactionExtension`]. +pub trait Implication: Encode + private::Sealed { + /// Destructure the implication into its parts. + fn parts(&self) -> ImplicationParts<&impl Encode, &impl Encode, &impl Encode>; +} + /// Shortcut for the result value of the `validate` function. pub type ValidateResult = Result<(ValidTransaction, Val, DispatchOriginOf), TransactionValidityError>; @@ -244,7 +310,7 @@ pub trait TransactionExtension: info: &DispatchInfoOf, len: usize, self_implicit: Self::Implicit, - inherited_implication: &impl Encode, + inherited_implication: &impl Implication, source: TransactionSource, ) -> ValidateResult; @@ -499,7 +565,7 @@ impl TransactionExtension for Tuple { info: &DispatchInfoOf, len: usize, self_implicit: Self::Implicit, - inherited_implication: &impl Encode, + inherited_implication: &impl Implication, source: TransactionSource, ) -> Result< (ValidTransaction, Self::Val, ::RuntimeOrigin), @@ -510,23 +576,20 @@ impl TransactionExtension for Tuple { let following_explicit_implications = for_tuples!( ( #( &self.Tuple ),* ) ); let following_implicit_implications = self_implicit; + let implication_parts = inherited_implication.parts(); + for_tuples!(#( // Implication of this pipeline element not relevant for later items, so we pop it. let (_item, following_explicit_implications) = following_explicit_implications.pop_front(); let (item_implicit, following_implicit_implications) = following_implicit_implications.pop_front(); let (item_valid, item_val, origin) = { - let implications = ( - // The first is the implications born of the fact we return the mutated - // origin. - inherited_implication, - // This is the explicitly made implication born of the fact the new origin is - // passed into the next items in this pipeline-tuple. - &following_explicit_implications, - // This is the implicitly made implication born of the fact the new origin is - // passed into the next items in this pipeline-tuple. - &following_implicit_implications, - ); - Tuple.validate(origin, call, info, len, item_implicit, &implications, source)? + Tuple.validate(origin, call, info, len, item_implicit, + &ImplicationParts { + base: implication_parts.base, + explicit: (&following_explicit_implications, implication_parts.explicit), + implicit: (&following_implicit_implications, implication_parts.implicit), + }, + source)? }; let valid = valid.combine_with(item_valid); let val = val.push_back(item_val); @@ -620,7 +683,7 @@ impl TransactionExtension for () { _info: &DispatchInfoOf, _len: usize, _self_implicit: Self::Implicit, - _inherited_implication: &impl Encode, + _inherited_implication: &impl Implication, _source: TransactionSource, ) -> Result< (ValidTransaction, (), ::RuntimeOrigin), @@ -639,3 +702,168 @@ impl TransactionExtension for () { Ok(()) } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_implications_on_nested_structure() { + use scale_info::TypeInfo; + use std::cell::RefCell; + + #[derive(Clone, Debug, Eq, PartialEq, Encode, Decode, TypeInfo)] + struct MockExtension { + also_implicit: u8, + explicit: u8, + } + + const CALL_IMPLICIT: u8 = 23; + + thread_local! { + static COUNTER: RefCell = RefCell::new(1); + } + + impl TransactionExtension<()> for MockExtension { + const IDENTIFIER: &'static str = "MockExtension"; + type Implicit = u8; + fn implicit(&self) -> Result { + Ok(self.also_implicit) + } + type Val = (); + type Pre = (); + fn weight(&self, _call: &()) -> Weight { + Weight::zero() + } + fn prepare( + self, + _val: Self::Val, + _origin: &DispatchOriginOf<()>, + _call: &(), + _info: &DispatchInfoOf<()>, + _len: usize, + ) -> Result { + Ok(()) + } + fn validate( + &self, + origin: DispatchOriginOf<()>, + _call: &(), + _info: &DispatchInfoOf<()>, + _len: usize, + self_implicit: Self::Implicit, + inherited_implication: &impl Implication, + _source: TransactionSource, + ) -> ValidateResult { + COUNTER.with(|c| { + let mut counter = c.borrow_mut(); + + assert_eq!(self_implicit, *counter); + assert_eq!( + self, + &MockExtension { also_implicit: *counter, explicit: *counter + 1 } + ); + + // Implications must be call then 1 to 22 then 1 to 22 odd. + let mut assert_implications = Vec::new(); + assert_implications.push(CALL_IMPLICIT); + for i in *counter + 2..23 { + assert_implications.push(i); + } + for i in *counter + 2..23 { + if i % 2 == 1 { + assert_implications.push(i); + } + } + assert_eq!(inherited_implication.encode(), assert_implications); + + *counter += 2; + }); + Ok((ValidTransaction::default(), (), origin)) + } + fn post_dispatch_details( + _pre: Self::Pre, + _info: &DispatchInfoOf<()>, + _post_info: &PostDispatchInfoOf<()>, + _len: usize, + _result: &DispatchResult, + ) -> Result { + Ok(Weight::zero()) + } + } + + // Test for one nested structure + + let ext = ( + MockExtension { also_implicit: 1, explicit: 2 }, + MockExtension { also_implicit: 3, explicit: 4 }, + ( + MockExtension { also_implicit: 5, explicit: 6 }, + MockExtension { also_implicit: 7, explicit: 8 }, + ( + MockExtension { also_implicit: 9, explicit: 10 }, + MockExtension { also_implicit: 11, explicit: 12 }, + ), + MockExtension { also_implicit: 13, explicit: 14 }, + MockExtension { also_implicit: 15, explicit: 16 }, + ), + MockExtension { also_implicit: 17, explicit: 18 }, + (MockExtension { also_implicit: 19, explicit: 20 },), + MockExtension { also_implicit: 21, explicit: 22 }, + ); + + let implicit = ext.implicit().unwrap(); + + let res = ext + .validate( + (), + &(), + &DispatchInfoOf::<()>::default(), + 0, + implicit, + &TxBaseImplication(CALL_IMPLICIT), + TransactionSource::Local, + ) + .expect("valid"); + + assert_eq!(res.0, ValidTransaction::default()); + + // Test for another nested structure + + COUNTER.with(|c| { + *c.borrow_mut() = 1; + }); + + let ext = ( + MockExtension { also_implicit: 1, explicit: 2 }, + MockExtension { also_implicit: 3, explicit: 4 }, + MockExtension { also_implicit: 5, explicit: 6 }, + MockExtension { also_implicit: 7, explicit: 8 }, + MockExtension { also_implicit: 9, explicit: 10 }, + MockExtension { also_implicit: 11, explicit: 12 }, + ( + MockExtension { also_implicit: 13, explicit: 14 }, + MockExtension { also_implicit: 15, explicit: 16 }, + MockExtension { also_implicit: 17, explicit: 18 }, + MockExtension { also_implicit: 19, explicit: 20 }, + MockExtension { also_implicit: 21, explicit: 22 }, + ), + ); + + let implicit = ext.implicit().unwrap(); + + let res = ext + .validate( + (), + &(), + &DispatchInfoOf::<()>::default(), + 0, + implicit, + &TxBaseImplication(CALL_IMPLICIT), + TransactionSource::Local, + ) + .expect("valid"); + + assert_eq!(res.0, ValidTransaction::default()); + } +} From 63c73bf6db1c8982ad3f2310a40799c5987f8900 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Sun, 5 Jan 2025 12:25:52 +0900 Subject: [PATCH 224/340] Implement cumulus StorageWeightReclaim as wrapping transaction extension + frame system ReclaimWeight (#6140) (rebasing of https://github.com/paritytech/polkadot-sdk/pull/5234) ## Issues: * Transaction extensions have weights and refund weight. So the reclaiming of unused weight must happen last in the transaction extension pipeline. Currently it is inside `CheckWeight`. * cumulus storage weight reclaim transaction extension misses the proof size of logic happening prior to itself. ## Done: * a new storage `ExtrinsicWeightReclaimed` in frame-system. Any logic which attempts to do some reclaim must use this storage to avoid double reclaim. * a new function `reclaim_weight` in frame-system pallet: info and post info in arguments, read the already reclaimed weight, calculate the new unused weight from info and post info. do the more accurate reclaim if higher. * `CheckWeight` is unchanged and still reclaim the weight in post dispatch * `ReclaimWeight` is a new transaction extension in frame system. For solo chains it must be used last in the transactino extension pipeline. It does the final most accurate reclaim * `StorageWeightReclaim` is moved from cumulus primitives into its own pallet (in order to define benchmark) and is changed into a wrapping transaction extension. It does the recording of proof size and does the reclaim using this recording and the info and post info. So parachains don't need to use `ReclaimWeight`. But also if they use it, there is no bug. ```rust /// The TransactionExtension to the basic transaction logic. pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< Runtime, ( frame_system::CheckNonZeroSender, frame_system::CheckSpecVersion, frame_system::CheckTxVersion, frame_system::CheckGenesis, frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, BridgeRejectObsoleteHeadersAndMessages, (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages,), frame_metadata_hash_extension::CheckMetadataHash, ), >; ``` --------- Co-authored-by: GitHub Action Co-authored-by: georgepisaltu <52418509+georgepisaltu@users.noreply.github.com> Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Sebastian Kunert Co-authored-by: command-bot <> --- .github/workflows/runtimes-matrix.json | 2 +- Cargo.lock | 50 +- Cargo.toml | 2 + cumulus/pallets/weight-reclaim/Cargo.toml | 63 + .../pallets/weight-reclaim/src/benchmarks.rs | 71 ++ cumulus/pallets/weight-reclaim/src/lib.rs | 311 +++++ cumulus/pallets/weight-reclaim/src/tests.rs | 1050 +++++++++++++++++ cumulus/pallets/weight-reclaim/src/weights.rs | 74 ++ .../assets/asset-hub-rococo/Cargo.toml | 6 +- .../assets/asset-hub-rococo/src/lib.rs | 32 +- .../weights/cumulus_pallet_weight_reclaim.rs | 67 ++ .../src/weights/frame_system_extensions.rs | 87 +- .../asset-hub-rococo/src/weights/mod.rs | 1 + .../assets/asset-hub-westend/Cargo.toml | 6 +- .../assets/asset-hub-westend/src/lib.rs | 34 +- .../weights/cumulus_pallet_weight_reclaim.rs | 67 ++ .../src/weights/frame_system_extensions.rs | 87 +- .../asset-hub-westend/src/weights/mod.rs | 1 + .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 6 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 111 +- .../weights/cumulus_pallet_weight_reclaim.rs | 67 ++ .../src/weights/frame_system_extensions.rs | 101 +- .../bridge-hub-rococo/src/weights/mod.rs | 1 + .../bridge-hub-rococo/tests/snowbridge.rs | 4 +- .../bridge-hub-rococo/tests/tests.rs | 1 - .../bridge-hubs/bridge-hub-westend/Cargo.toml | 6 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 107 +- .../weights/cumulus_pallet_weight_reclaim.rs | 67 ++ .../src/weights/frame_system_extensions.rs | 101 +- .../bridge-hub-westend/src/weights/mod.rs | 1 + .../bridge-hub-westend/tests/snowbridge.rs | 4 +- .../bridge-hub-westend/tests/tests.rs | 1 - .../collectives-westend/Cargo.toml | 6 +- .../collectives-westend/src/lib.rs | 29 +- .../weights/cumulus_pallet_weight_reclaim.rs | 67 ++ .../src/weights/frame_system_extensions.rs | 101 +- .../collectives-westend/src/weights/mod.rs | 1 + .../contracts/contracts-rococo/Cargo.toml | 6 +- .../contracts/contracts-rococo/src/lib.rs | 30 +- .../coretime/coretime-rococo/Cargo.toml | 6 +- .../coretime/coretime-rococo/src/lib.rs | 32 +- .../weights/cumulus_pallet_weight_reclaim.rs | 67 ++ .../src/weights/frame_system_extensions.rs | 14 + .../coretime-rococo/src/weights/mod.rs | 1 + .../coretime/coretime-westend/Cargo.toml | 6 +- .../coretime/coretime-westend/src/lib.rs | 32 +- .../weights/cumulus_pallet_weight_reclaim.rs | 67 ++ .../src/weights/frame_system_extensions.rs | 14 + .../coretime-westend/src/weights/mod.rs | 1 + .../glutton/glutton-westend/src/lib.rs | 1 + .../src/weights/frame_system_extensions.rs | 94 +- .../runtimes/people/people-rococo/Cargo.toml | 6 +- .../runtimes/people/people-rococo/src/lib.rs | 30 +- .../weights/cumulus_pallet_weight_reclaim.rs | 67 ++ .../src/weights/frame_system_extensions.rs | 14 + .../people/people-rococo/src/weights/mod.rs | 1 + .../runtimes/people/people-westend/Cargo.toml | 6 +- .../runtimes/people/people-westend/src/lib.rs | 30 +- .../weights/cumulus_pallet_weight_reclaim.rs | 67 ++ .../src/weights/frame_system_extensions.rs | 14 + .../people/people-westend/src/weights/mod.rs | 1 + .../runtimes/testing/penpal/src/lib.rs | 1 + .../testing/rococo-parachain/Cargo.toml | 5 +- .../testing/rococo-parachain/src/lib.rs | 30 +- .../storage-weight-reclaim/src/lib.rs | 33 +- .../storage-weight-reclaim/src/tests.rs | 15 + cumulus/test/client/Cargo.toml | 3 +- cumulus/test/client/src/lib.rs | 3 +- cumulus/test/runtime/Cargo.toml | 4 +- cumulus/test/runtime/src/lib.rs | 27 +- cumulus/test/service/Cargo.toml | 3 +- cumulus/test/service/src/lib.rs | 3 +- docs/sdk/Cargo.toml | 2 +- docs/sdk/src/guides/enable_pov_reclaim.rs | 6 +- .../reference_docs/transaction_extensions.rs | 8 +- polkadot/node/service/src/benchmarking.rs | 4 + polkadot/node/test/service/src/lib.rs | 2 + polkadot/runtime/rococo/src/lib.rs | 2 + .../src/weights/frame_system_extensions.rs | 93 +- polkadot/runtime/test-runtime/src/lib.rs | 2 + polkadot/runtime/westend/src/lib.rs | 2 + .../src/weights/frame_system_extensions.rs | 92 +- .../xcm/xcm-builder/src/tests/pay/mock.rs | 1 + polkadot/xcm/xcm-runtime-apis/tests/mock.rs | 3 +- prdoc/pr_6140.prdoc | 95 ++ substrate/bin/node/cli/src/service.rs | 5 + substrate/bin/node/runtime/src/lib.rs | 3 + substrate/bin/node/testing/src/keyring.rs | 1 + substrate/frame/executive/src/tests.rs | 5 + .../metadata-hash-extension/src/tests.rs | 1 + substrate/frame/src/lib.rs | 1 + substrate/frame/support/src/dispatch.rs | 13 + .../system/benchmarking/src/extensions.rs | 46 +- .../frame/system/benchmarking/src/mock.rs | 4 + .../system/src/extensions/check_weight.rs | 142 ++- substrate/frame/system/src/extensions/mod.rs | 1 + .../system/src/extensions/weight_reclaim.rs | 401 +++++++ .../frame/system/src/extensions/weights.rs | 23 + substrate/frame/system/src/lib.rs | 56 +- substrate/frame/system/src/tests.rs | 64 + .../runtime/src/generic/checked_extrinsic.rs | 1 - .../src/traits/transaction_extension/mod.rs | 8 +- substrate/test-utils/runtime/src/extrinsic.rs | 1 + substrate/test-utils/runtime/src/lib.rs | 1 + templates/minimal/runtime/src/lib.rs | 4 + templates/parachain/runtime/Cargo.toml | 2 +- templates/parachain/runtime/src/benchmarks.rs | 1 + .../parachain/runtime/src/configs/mod.rs | 5 + templates/parachain/runtime/src/lib.rs | 28 +- templates/solochain/node/src/benchmarking.rs | 2 + templates/solochain/runtime/src/lib.rs | 1 + umbrella/Cargo.toml | 10 +- umbrella/src/lib.rs | 4 + 113 files changed, 4007 insertions(+), 666 deletions(-) create mode 100644 cumulus/pallets/weight-reclaim/Cargo.toml create mode 100644 cumulus/pallets/weight-reclaim/src/benchmarks.rs create mode 100644 cumulus/pallets/weight-reclaim/src/lib.rs create mode 100644 cumulus/pallets/weight-reclaim/src/tests.rs create mode 100644 cumulus/pallets/weight-reclaim/src/weights.rs create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs create mode 100644 cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_weight_reclaim.rs create mode 100644 cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_weight_reclaim.rs create mode 100644 cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_weight_reclaim.rs create mode 100644 cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_weight_reclaim.rs create mode 100644 cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_weight_reclaim.rs create mode 100644 prdoc/pr_6140.prdoc create mode 100644 substrate/frame/system/src/extensions/weight_reclaim.rs diff --git a/.github/workflows/runtimes-matrix.json b/.github/workflows/runtimes-matrix.json index 104e73521331..ff16b7397247 100644 --- a/.github/workflows/runtimes-matrix.json +++ b/.github/workflows/runtimes-matrix.json @@ -145,7 +145,7 @@ { "name": "glutton-westend", "package": "glutton-westend-runtime", - "path": "cumulus/parachains/runtimes/gluttons/glutton-westend", + "path": "cumulus/parachains/runtimes/glutton/glutton-westend", "header": "cumulus/file_header.txt", "template": "cumulus/templates/xcm-bench-template.hbs", "bench_features": "runtime-benchmarks", diff --git a/Cargo.lock b/Cargo.lock index 3c55a14256c5..b0fb0586be38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -959,11 +959,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -1095,11 +1095,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -2666,11 +2666,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -2905,11 +2905,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -3645,11 +3645,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -3952,11 +3952,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -4095,11 +4095,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -4196,11 +4196,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -5074,6 +5074,25 @@ dependencies = [ "sp-runtime 39.0.2", ] +[[package]] +name = "cumulus-pallet-weight-reclaim" +version = "1.0.0" +dependencies = [ + "cumulus-primitives-proof-size-hostfunction 0.2.0", + "cumulus-primitives-storage-weight-reclaim 1.0.0", + "derivative", + "docify", + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "log", + "parity-scale-codec", + "scale-info", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-trie 29.0.0", +] + [[package]] name = "cumulus-pallet-xcm" version = "0.7.0" @@ -5524,10 +5543,10 @@ dependencies = [ name = "cumulus-test-client" version = "0.1.0" dependencies = [ + "cumulus-pallet-weight-reclaim", "cumulus-primitives-core 0.7.0", "cumulus-primitives-parachain-inherent 0.7.0", "cumulus-primitives-proof-size-hostfunction 0.2.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-test-relay-sproof-builder 0.7.0", "cumulus-test-runtime", "cumulus-test-service", @@ -5589,9 +5608,9 @@ version = "0.1.0" dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", + "cumulus-pallet-weight-reclaim", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "frame-executive 28.0.0", "frame-support 28.0.0", "frame-system 28.0.0", @@ -5643,8 +5662,8 @@ dependencies = [ "cumulus-client-pov-recovery", "cumulus-client-service", "cumulus-pallet-parachain-system 0.7.0", + "cumulus-pallet-weight-reclaim", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-relay-chain-inprocess-interface", "cumulus-relay-chain-interface", "cumulus-relay-chain-minimal-node", @@ -16742,11 +16761,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "enumflags2", "frame-benchmarking 28.0.0", @@ -16845,11 +16864,11 @@ dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-session-benchmarking 9.0.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "enumflags2", "frame-benchmarking 28.0.0", @@ -18645,6 +18664,7 @@ dependencies = [ "cumulus-pallet-parachain-system-proc-macro 0.6.0", "cumulus-pallet-session-benchmarking 9.0.0", "cumulus-pallet-solo-to-para 0.7.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-ping 0.7.0", @@ -19233,8 +19253,8 @@ dependencies = [ "cumulus-client-service", "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", + "cumulus-pallet-weight-reclaim", "cumulus-primitives-proof-size-hostfunction 0.2.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "docify", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", @@ -21447,12 +21467,12 @@ version = "0.6.0" dependencies = [ "cumulus-pallet-aura-ext 0.7.0", "cumulus-pallet-parachain-system 0.7.0", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-ping 0.7.0", "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", - "cumulus-primitives-storage-weight-reclaim 1.0.0", "cumulus-primitives-utility 0.7.0", "frame-benchmarking 28.0.0", "frame-executive 28.0.0", diff --git a/Cargo.toml b/Cargo.toml index 64a11a340d10..c917a8a8fead 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,6 +83,7 @@ members = [ "cumulus/pallets/parachain-system/proc-macro", "cumulus/pallets/session-benchmarking", "cumulus/pallets/solo-to-para", + "cumulus/pallets/weight-reclaim", "cumulus/pallets/xcm", "cumulus/pallets/xcmp-queue", "cumulus/parachains/common", @@ -717,6 +718,7 @@ cumulus-pallet-parachain-system = { path = "cumulus/pallets/parachain-system", d cumulus-pallet-parachain-system-proc-macro = { path = "cumulus/pallets/parachain-system/proc-macro", default-features = false } cumulus-pallet-session-benchmarking = { path = "cumulus/pallets/session-benchmarking", default-features = false } cumulus-pallet-solo-to-para = { path = "cumulus/pallets/solo-to-para", default-features = false } +cumulus-pallet-weight-reclaim = { path = "cumulus/pallets/weight-reclaim", default-features = false } cumulus-pallet-xcm = { path = "cumulus/pallets/xcm", default-features = false } cumulus-pallet-xcmp-queue = { path = "cumulus/pallets/xcmp-queue", default-features = false } cumulus-ping = { path = "cumulus/parachains/pallets/ping", default-features = false } diff --git a/cumulus/pallets/weight-reclaim/Cargo.toml b/cumulus/pallets/weight-reclaim/Cargo.toml new file mode 100644 index 000000000000..8bde6abaff6a --- /dev/null +++ b/cumulus/pallets/weight-reclaim/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "cumulus-pallet-weight-reclaim" +version = "1.0.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "pallet and transaction extensions for accurate proof size reclaim" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# Substrate dependencies +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-trie = { workspace = true } + +cumulus-primitives-storage-weight-reclaim = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } + +# Other dependencies +codec = { features = ["derive"], workspace = true } +derivative = { features = ["use_core"], workspace = true } +docify = { workspace = true } +log = { workspace = true, default-features = true } +scale-info = { features = ["derive"], workspace = true } + +[dev-dependencies] +cumulus-primitives-proof-size-hostfunction = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-primitives-proof-size-hostfunction/std", + "cumulus-primitives-storage-weight-reclaim/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-trie/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/cumulus/pallets/weight-reclaim/src/benchmarks.rs b/cumulus/pallets/weight-reclaim/src/benchmarks.rs new file mode 100644 index 000000000000..78bebc967d96 --- /dev/null +++ b/cumulus/pallets/weight-reclaim/src/benchmarks.rs @@ -0,0 +1,71 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_support::pallet_prelude::{DispatchClass, Pays}; +use frame_system::RawOrigin; +use sp_runtime::traits::{AsTransactionAuthorizedOrigin, DispatchTransaction}; + +#[frame_benchmarking::v2::benchmarks( + where T: Send + Sync, + ::RuntimeCall: + Dispatchable, + ::RuntimeOrigin: AsTransactionAuthorizedOrigin, +)] +mod bench { + use super::*; + use frame_benchmarking::impl_test_function; + + #[benchmark] + fn storage_weight_reclaim() { + let ext = StorageWeightReclaim::::new(()); + + let origin = RawOrigin::Root.into(); + let call = T::RuntimeCall::from(frame_system::Call::remark { remark: alloc::vec![] }); + + let overestimate = 10_000; + let info = DispatchInfo { + call_weight: Weight::zero().add_proof_size(overestimate), + extension_weight: Weight::zero(), + class: DispatchClass::Normal, + pays_fee: Pays::No, + }; + + let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Pays::No }; + + let mut block_weight = frame_system::ConsumedWeight::default(); + block_weight.accrue(Weight::from_parts(0, overestimate), info.class); + + frame_system::BlockWeight::::put(block_weight); + + #[block] + { + assert!(ext.test_run(origin, &call, &info, 0, 0, |_| Ok(post_info)).unwrap().is_ok()); + } + + let final_block_proof_size = + frame_system::BlockWeight::::get().get(info.class).proof_size(); + + assert!( + final_block_proof_size < overestimate, + "The proof size measured should be less than {overestimate}" + ); + } + + impl_benchmark_test_suite!(Pallet, crate::tests::setup_test_ext_default(), crate::tests::Test); +} diff --git a/cumulus/pallets/weight-reclaim/src/lib.rs b/cumulus/pallets/weight-reclaim/src/lib.rs new file mode 100644 index 000000000000..bd9929033af1 --- /dev/null +++ b/cumulus/pallets/weight-reclaim/src/lib.rs @@ -0,0 +1,311 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Pallet and transaction extensions to reclaim PoV proof size weight after an extrinsic has been +//! applied. +//! +//! This crate provides: +//! * [`StorageWeightReclaim`] transaction extension: it must wrap the whole transaction extension +//! pipeline. +//! * The pallet required for the transaction extensions weight information and benchmarks. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use codec::{Decode, Encode}; +use cumulus_primitives_storage_weight_reclaim::get_proof_size; +use derivative::Derivative; +use frame_support::{ + dispatch::{DispatchInfo, PostDispatchInfo}, + pallet_prelude::Weight, + traits::Defensive, +}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, Implication, PostDispatchInfoOf, TransactionExtension}, + transaction_validity::{TransactionSource, TransactionValidityError, ValidTransaction}, + DispatchResult, +}; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarks; +#[cfg(test)] +mod tests; +mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +const LOG_TARGET: &'static str = "runtime::storage_reclaim_pallet"; + +/// Pallet to use alongside the transaction extension [`StorageWeightReclaim`], the pallet provides +/// weight information and benchmarks. +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } +} + +/// Storage weight reclaim mechanism. +/// +/// This extension must wrap all the transaction extensions: +#[doc = docify::embed!("./src/tests.rs", Tx)] +/// +/// This extension checks the size of the node-side storage proof before and after executing a given +/// extrinsic using the proof size host function. The difference between benchmarked and used weight +/// is reclaimed. +/// +/// If the benchmark was underestimating the proof size, then it is added to the block weight. +/// +/// For the time part of the weight, it does same as system `WeightReclaim` extension, it +/// calculates the unused weight using the post information and reclaim the unused weight. +/// So this extension can be used as a drop-in replacement for `WeightReclaim` extension for +/// parachains. +#[derive(Encode, Decode, TypeInfo, Derivative)] +#[derivative( + Clone(bound = "S: Clone"), + Eq(bound = "S: Eq"), + PartialEq(bound = "S: PartialEq"), + Default(bound = "S: Default") +)] +#[scale_info(skip_type_params(T))] +pub struct StorageWeightReclaim(pub S, core::marker::PhantomData); + +impl StorageWeightReclaim { + /// Create a new `StorageWeightReclaim` instance. + pub fn new(s: S) -> Self { + Self(s, Default::default()) + } +} + +impl From for StorageWeightReclaim { + fn from(s: S) -> Self { + Self::new(s) + } +} + +impl core::fmt::Debug for StorageWeightReclaim { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + #[cfg(feature = "std")] + let _ = write!(f, "StorageWeightReclaim<{:?}>", self.0); + + #[cfg(not(feature = "std"))] + let _ = write!(f, "StorageWeightReclaim"); + + Ok(()) + } +} + +impl> + TransactionExtension for StorageWeightReclaim +where + T::RuntimeCall: Dispatchable, +{ + const IDENTIFIER: &'static str = "StorageWeightReclaim"; + + type Implicit = S::Implicit; + + // Initial proof size and inner extension value. + type Val = (Option, S::Val); + + // Initial proof size and inner extension pre. + type Pre = (Option, S::Pre); + + fn implicit(&self) -> Result { + self.0.implicit() + } + + fn metadata() -> Vec { + let mut inner = S::metadata(); + inner.push(sp_runtime::traits::TransactionExtensionMetadata { + identifier: "StorageWeightReclaim", + ty: scale_info::meta_type::<()>(), + implicit: scale_info::meta_type::<()>(), + }); + inner + } + + fn weight(&self, call: &T::RuntimeCall) -> Weight { + T::WeightInfo::storage_weight_reclaim().saturating_add(self.0.weight(call)) + } + + fn validate( + &self, + origin: T::RuntimeOrigin, + call: &T::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + self_implicit: Self::Implicit, + inherited_implication: &impl Implication, + source: TransactionSource, + ) -> Result<(ValidTransaction, Self::Val, T::RuntimeOrigin), TransactionValidityError> { + let proof_size = get_proof_size(); + + self.0 + .validate(origin, call, info, len, self_implicit, inherited_implication, source) + .map(|(validity, val, origin)| (validity, (proof_size, val), origin)) + } + + fn prepare( + self, + val: Self::Val, + origin: &T::RuntimeOrigin, + call: &T::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + let (proof_size, inner_val) = val; + self.0.prepare(inner_val, origin, call, info, len).map(|pre| (proof_size, pre)) + } + + fn post_dispatch_details( + pre: Self::Pre, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + ) -> Result { + let (proof_size_before_dispatch, inner_pre) = pre; + + let mut post_info_with_inner = *post_info; + S::post_dispatch(inner_pre, info, &mut post_info_with_inner, len, result)?; + + let inner_refund = if let (Some(before_weight), Some(after_weight)) = + (post_info.actual_weight, post_info_with_inner.actual_weight) + { + before_weight.saturating_sub(after_weight) + } else { + Weight::zero() + }; + + let Some(proof_size_before_dispatch) = proof_size_before_dispatch else { + // We have no proof size information, there is nothing we can do. + return Ok(inner_refund); + }; + + let Some(proof_size_after_dispatch) = get_proof_size().defensive_proof( + "Proof recording enabled during prepare, now disabled. This should not happen.", + ) else { + return Ok(inner_refund) + }; + + // The consumed proof size as measured by the host. + let measured_proof_size = + proof_size_after_dispatch.saturating_sub(proof_size_before_dispatch); + + // The consumed weight as benchmarked. Calculated from post info and info. + // NOTE: `calc_actual_weight` will take the minimum of `post_info` and `info` weights. + // This means any underestimation of compute time in the pre dispatch info will not be + // taken into account. + let benchmarked_actual_weight = post_info_with_inner.calc_actual_weight(info); + + let benchmarked_actual_proof_size = benchmarked_actual_weight.proof_size(); + if benchmarked_actual_proof_size < measured_proof_size { + log::error!( + target: LOG_TARGET, + "Benchmarked storage weight smaller than consumed storage weight. \ + benchmarked: {benchmarked_actual_proof_size} consumed: {measured_proof_size}" + ); + } else { + log::trace!( + target: LOG_TARGET, + "Reclaiming storage weight. benchmarked: {benchmarked_actual_proof_size}, + consumed: {measured_proof_size}" + ); + } + + let accurate_weight = benchmarked_actual_weight.set_proof_size(measured_proof_size); + + let pov_size_missing_from_node = frame_system::BlockWeight::::mutate(|current_weight| { + let already_reclaimed = frame_system::ExtrinsicWeightReclaimed::::get(); + current_weight.accrue(already_reclaimed, info.class); + current_weight.reduce(info.total_weight(), info.class); + current_weight.accrue(accurate_weight, info.class); + + // If we encounter a situation where the node-side proof size is already higher than + // what we have in the runtime bookkeeping, we add the difference to the `BlockWeight`. + // This prevents that the proof size grows faster than the runtime proof size. + let extrinsic_len = frame_system::AllExtrinsicsLen::::get().unwrap_or(0); + let node_side_pov_size = proof_size_after_dispatch.saturating_add(extrinsic_len.into()); + let block_weight_proof_size = current_weight.total().proof_size(); + let pov_size_missing_from_node = + node_side_pov_size.saturating_sub(block_weight_proof_size); + if pov_size_missing_from_node > 0 { + log::warn!( + target: LOG_TARGET, + "Node-side PoV size higher than runtime proof size weight. node-side: \ + {node_side_pov_size} extrinsic_len: {extrinsic_len} runtime: \ + {block_weight_proof_size}, missing: {pov_size_missing_from_node}. Setting to \ + node-side proof size." + ); + current_weight + .accrue(Weight::from_parts(0, pov_size_missing_from_node), info.class); + } + + pov_size_missing_from_node + }); + + // The saturation will happen if the pre-dispatch weight is underestimating the proof + // size or if the node-side proof size is higher than expected. + // In this case the extrinsic proof size weight reclaimed is 0 and not a negative reclaim. + let accurate_unspent = info + .total_weight() + .saturating_sub(accurate_weight) + .saturating_sub(Weight::from_parts(0, pov_size_missing_from_node)); + frame_system::ExtrinsicWeightReclaimed::::put(accurate_unspent); + + // Call have already returned their unspent amount. + // (also transaction extension prior in the pipeline, but there shouldn't be any.) + let already_unspent_in_tx_ext_pipeline = post_info.calc_unspent(info); + Ok(accurate_unspent.saturating_sub(already_unspent_in_tx_ext_pipeline)) + } + + fn bare_validate( + call: &T::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + ) -> frame_support::pallet_prelude::TransactionValidity { + S::bare_validate(call, info, len) + } + + fn bare_validate_and_prepare( + call: &T::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + ) -> Result<(), TransactionValidityError> { + S::bare_validate_and_prepare(call, info, len) + } + + fn bare_post_dispatch( + info: &DispatchInfoOf, + post_info: &mut PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + S::bare_post_dispatch(info, post_info, len, result)?; + + frame_system::Pallet::::reclaim_weight(info, post_info) + } +} diff --git a/cumulus/pallets/weight-reclaim/src/tests.rs b/cumulus/pallets/weight-reclaim/src/tests.rs new file mode 100644 index 000000000000..b87c107c7ec7 --- /dev/null +++ b/cumulus/pallets/weight-reclaim/src/tests.rs @@ -0,0 +1,1050 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +#![cfg(test)] + +use super::*; +use cumulus_primitives_proof_size_hostfunction::PROOF_RECORDING_DISABLED; +use frame_support::{ + assert_ok, derive_impl, dispatch::GetDispatchInfo, pallet_prelude::DispatchClass, +}; +use sp_runtime::{ + generic, + traits::{Applyable, BlakeTwo256, DispatchTransaction, Get}, + BuildStorage, +}; +use sp_trie::proof_size_extension::ProofSizeExt; + +thread_local! { + static CHECK_WEIGHT_WEIGHT: core::cell::RefCell = Default::default(); + static STORAGE_WEIGHT_RECLAIM_WEIGHT: core::cell::RefCell = Default::default(); + static MOCK_EXT_WEIGHT: core::cell::RefCell = Default::default(); + static MOCK_EXT_REFUND: core::cell::RefCell = Default::default(); +} + +/// An extension which has some proof_size weight and some proof_size refund. +#[derive(Encode, Decode, Debug, Clone, PartialEq, Eq, scale_info::TypeInfo)] +pub struct MockExtensionWithRefund; + +impl TransactionExtension for MockExtensionWithRefund { + const IDENTIFIER: &'static str = "mock_extension_with_refund"; + type Implicit = (); + type Val = (); + type Pre = (); + fn weight(&self, _: &RuntimeCall) -> Weight { + MOCK_EXT_WEIGHT.with_borrow(|v| *v) + } + fn post_dispatch_details( + _pre: Self::Pre, + _info: &DispatchInfoOf, + _post_info: &PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result { + Ok(MOCK_EXT_REFUND.with_borrow(|v| *v)) + } + fn bare_post_dispatch( + _info: &DispatchInfoOf, + post_info: &mut PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + if let Some(ref mut w) = post_info.actual_weight { + *w -= MOCK_EXT_REFUND.with_borrow(|v| *v); + } + Ok(()) + } + + sp_runtime::impl_tx_ext_default!(RuntimeCall; validate prepare); +} + +pub type Tx = + crate::StorageWeightReclaim, MockExtensionWithRefund)>; +type AccountId = u64; +type Extrinsic = generic::UncheckedExtrinsic; +type Block = generic::Block, Extrinsic>; + +#[frame_support::runtime] +mod runtime { + #[runtime::runtime] + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId, + RuntimeTask + )] + pub struct Test; + + #[runtime::pallet_index(0)] + pub type System = frame_system::Pallet; + + #[runtime::pallet_index(1)] + pub type WeightReclaim = crate::Pallet; +} + +pub struct MockWeightInfo; + +impl frame_system::ExtensionsWeightInfo for MockWeightInfo { + fn check_genesis() -> Weight { + Default::default() + } + fn check_mortality_mortal_transaction() -> Weight { + Default::default() + } + fn check_mortality_immortal_transaction() -> Weight { + Default::default() + } + fn check_non_zero_sender() -> Weight { + Default::default() + } + fn check_nonce() -> Weight { + Default::default() + } + fn check_spec_version() -> Weight { + Default::default() + } + fn check_tx_version() -> Weight { + Default::default() + } + fn check_weight() -> Weight { + CHECK_WEIGHT_WEIGHT.with_borrow(|v| *v) + } + fn weight_reclaim() -> Weight { + Default::default() + } +} + +impl frame_system::WeightInfo for MockWeightInfo { + fn remark(_b: u32) -> Weight { + Weight::from_parts(400, 0) + } + fn set_code() -> Weight { + Weight::zero() + } + fn set_storage(_i: u32) -> Weight { + Weight::zero() + } + fn kill_prefix(_p: u32) -> Weight { + Weight::zero() + } + fn kill_storage(_i: u32) -> Weight { + Weight::zero() + } + fn set_heap_pages() -> Weight { + Weight::zero() + } + fn remark_with_event(_b: u32) -> Weight { + Weight::zero() + } + fn authorize_upgrade() -> Weight { + Weight::zero() + } + fn apply_authorized_upgrade() -> Weight { + Weight::zero() + } +} + +impl crate::WeightInfo for MockWeightInfo { + fn storage_weight_reclaim() -> Weight { + STORAGE_WEIGHT_RECLAIM_WEIGHT.with_borrow(|v| *v) + } +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountData = (); + type MaxConsumers = frame_support::traits::ConstU32<3>; + type ExtensionsWeightInfo = MockWeightInfo; +} + +impl crate::Config for Test { + type WeightInfo = MockWeightInfo; +} + +fn new_test_ext() -> sp_io::TestExternalities { + RuntimeGenesisConfig::default().build_storage().unwrap().into() +} + +struct TestRecorder { + return_values: Box<[usize]>, + counter: core::sync::atomic::AtomicUsize, +} + +impl TestRecorder { + fn new(values: &[usize]) -> Self { + TestRecorder { return_values: values.into(), counter: Default::default() } + } +} + +impl sp_trie::ProofSizeProvider for TestRecorder { + fn estimate_encoded_size(&self) -> usize { + let counter = self.counter.fetch_add(1, core::sync::atomic::Ordering::Relaxed); + self.return_values[counter] + } +} + +fn setup_test_externalities(proof_values: &[usize]) -> sp_io::TestExternalities { + let mut test_ext = new_test_ext(); + let test_recorder = TestRecorder::new(proof_values); + test_ext.register_extension(ProofSizeExt::new(test_recorder)); + test_ext +} + +#[cfg(feature = "runtime-benchmarks")] +pub fn setup_test_ext_default() -> sp_io::TestExternalities { + setup_test_externalities(&[0; 32]) +} + +fn set_current_storage_weight(new_weight: u64) { + frame_system::BlockWeight::::mutate(|current_weight| { + current_weight.set(Weight::from_parts(0, new_weight), DispatchClass::Normal); + }); +} + +fn get_storage_weight() -> Weight { + *frame_system::BlockWeight::::get().get(DispatchClass::Normal) +} + +const CALL: &::RuntimeCall = + &RuntimeCall::System(frame_system::Call::set_heap_pages { pages: 0u64 }); +const ALICE_ORIGIN: frame_system::Origin = frame_system::Origin::::Signed(99); +const LEN: usize = 150; + +fn new_tx_ext() -> Tx { + Tx::new((frame_system::CheckWeight::new(), MockExtensionWithRefund)) +} + +fn new_extrinsic() -> generic::CheckedExtrinsic { + generic::CheckedExtrinsic { + format: generic::ExtrinsicFormat::Signed(99, new_tx_ext()), + function: RuntimeCall::System(frame_system::Call::remark { remark: vec![] }), + } +} + +#[allow(unused)] +mod doc { + type Runtime = super::Test; + use crate::StorageWeightReclaim; + + #[docify::export(Tx)] + type Tx = StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonce, + frame_system::CheckWeight, + // ... all other extensions + // No need for `frame_system::WeightReclaim` as the reclaim. + ), + >; +} + +#[test] +fn basic_refund_no_post_info() { + // The real cost will be 100 bytes of storage size + let mut test_ext = setup_test_externalities(&[0, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { call_weight: Weight::from_parts(0, 500), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + + let tx_ext = new_tx_ext(); + + // Check weight should add 500 + 150 (len) to weight. + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(0)); + + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight, None); + assert_eq!(get_storage_weight().proof_size(), 1250); + }); +} + +#[test] +fn basic_refund_some_post_info() { + // The real cost will be 100 bytes of storage size + let mut test_ext = setup_test_externalities(&[0, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { call_weight: Weight::from_parts(0, 500), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = Some(info.total_weight()); + + let tx_ext = new_tx_ext(); + + // Check weight should add 500 + 150 (len) to weight. + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(0)); + + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(0, 100)); + assert_eq!(get_storage_weight().proof_size(), 1250); + }); +} + +#[test] +fn does_nothing_without_extension() { + // Proof size extension not registered + let mut test_ext = new_test_ext(); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 500 + let info = DispatchInfo { call_weight: Weight::from_parts(0, 500), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = Some(info.total_weight()); + + let tx_ext = new_tx_ext(); + + // Check weight should add 500 + 150 (len) to weight. + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, None); + + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), info.total_weight()); + assert_eq!(get_storage_weight().proof_size(), 1650); + }) +} + +#[test] +fn negative_refund_is_added_to_weight() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 100 + let info = DispatchInfo { call_weight: Weight::from_parts(0, 100), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = Some(info.total_weight()); + + let tx_ext = new_tx_ext(); + + // Weight added should be 100 + 150 (len) + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(100)); + + // We expect no refund + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), info.total_weight()); + assert_eq!( + get_storage_weight().proof_size(), + 1100 + LEN as u64 + info.total_weight().proof_size() + ); + }) +} + +#[test] +fn test_zero_proof_size() { + let mut test_ext = setup_test_externalities(&[0, 0]); + + test_ext.execute_with(|| { + let info = DispatchInfo { call_weight: Weight::from_parts(0, 500), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = Some(info.total_weight()); + + let tx_ext = new_tx_ext(); + + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(0)); + + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(0, 0)); + // Proof size should be exactly equal to extrinsic length + assert_eq!(get_storage_weight().proof_size(), LEN as u64); + }); +} + +#[test] +fn test_larger_pre_dispatch_proof_size() { + let mut test_ext = setup_test_externalities(&[300, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(1300); + + let info = DispatchInfo { call_weight: Weight::from_parts(0, 500), ..Default::default() }; + let mut post_info = PostDispatchInfo::default(); + post_info.actual_weight = Some(info.total_weight()); + + let tx_ext = new_tx_ext(); + + // Adds 500 + 150 (len) weight, total weight is 1950 + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(300)); + + // check weight: + // Refund 500 unspent weight according to `post_info`, total weight is now 1650 + // + // storage reclaim: + // Recorded proof size is negative -200, total weight is now 1450 + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(0, 0)); + assert_eq!(get_storage_weight().proof_size(), 1450); + }); +} + +#[test] +fn test_incorporates_check_weight_unspent_weight() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + + // Benchmarked storage weight: 300 + let info = DispatchInfo { call_weight: Weight::from_parts(100, 300), ..Default::default() }; + + // Actual weight is 50 + let mut post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 250)), + pays_fee: Default::default(), + }; + + let tx_ext = new_tx_ext(); + + // Check weight should add 300 + 150 (len) of weight + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + assert_eq!(pre.0, Some(100)); + + // The `CheckWeight` extension will refund `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(50, 350 - LEN as u64)); + // Reclaimed 100 + assert_eq!(get_storage_weight().proof_size(), 1350); + }) +} + +#[test] +fn test_incorporates_check_weight_unspent_weight_on_negative() { + let mut test_ext = setup_test_externalities(&[100, 300]); + + test_ext.execute_with(|| { + set_current_storage_weight(1000); + // Benchmarked storage weight: 50 + let info = DispatchInfo { call_weight: Weight::from_parts(100, 50), ..Default::default() }; + + // Actual weight is 25 + let mut post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 25)), + pays_fee: Default::default(), + }; + + let tx_ext = new_tx_ext(); + + // Adds 50 + 150 (len) weight, total weight 1200 + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + assert_eq!(pre.0, Some(100)); + + // The `CheckWeight` extension will refund `actual_weight` from `PostDispatchInfo` + // CheckWeight: refunds unspent 25 weight according to `post_info`, 1175 + // + // storage reclaim: + // Adds 200 - 25 (unspent) == 175 weight, total weight 1350 + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(50, 25)); + assert_eq!(get_storage_weight().proof_size(), 1350); + }) +} + +#[test] +fn test_nothing_reclaimed() { + let mut test_ext = setup_test_externalities(&[0, 100]); + + test_ext.execute_with(|| { + set_current_storage_weight(0); + // Benchmarked storage weight: 100 + let info = DispatchInfo { call_weight: Weight::from_parts(100, 100), ..Default::default() }; + + // Actual proof size is 100 + let mut post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 100)), + pays_fee: Default::default(), + }; + + let tx_ext = new_tx_ext(); + + // Adds benchmarked weight 100 + 150 (len), total weight is now 250 + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + + // Weight should go up by 150 len + 100 proof size weight, total weight 250 + assert_eq!(get_storage_weight().proof_size(), 250); + + // Should return `setup_test_externalities` proof recorder value: 100. + assert_eq!(pre.0, Some(0)); + + // The `CheckWeight` extension will refund `actual_weight` from `PostDispatchInfo` + // we always need to call `post_dispatch` to verify that they interoperate correctly. + // Nothing to refund, unspent is 0, total weight 250 + // + // weight reclaim: + // `setup_test_externalities` proof recorder value: 200, so this means the extrinsic + // actually used 100 proof size. + // Nothing to refund or add, weight matches proof recorder + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight.unwrap(), Weight::from_parts(50, 100)); + // Check block len weight was not reclaimed: + // 100 weight + 150 extrinsic len == 250 proof size + assert_eq!(get_storage_weight().proof_size(), 250); + }) +} + +// Test for refund of calls and related proof size +#[test] +fn test_series() { + struct TestCfg { + measured_proof_size_pre_dispatch: u64, + measured_proof_size_post_dispatch: u64, + info_call_weight: Weight, + info_extension_weight: Weight, + post_info_actual_weight: Option, + block_weight_pre_dispatch: Weight, + mock_ext_refund: Weight, + assert_post_info_weight: Option, + assert_block_weight_post_dispatch: Weight, + } + + let base_extrinsic = <::BlockWeights as Get< + frame_system::limits::BlockWeights, + >>::get() + .per_class + .get(DispatchClass::Normal) + .base_extrinsic; + + let tests = vec![ + // Info is exact, no post info, no refund. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 400, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: None, + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(0, 0), + assert_post_info_weight: None, + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1100, 1300 + LEN as u64), + }, + // some tx ext refund is ignored, because post info is None. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 400, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: None, + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(20, 20), + assert_post_info_weight: None, + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1100, 1300 + LEN as u64), + }, + // some tx ext refund is ignored on proof size because lower than actual measure. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 400, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: Some(Weight::from_parts(100, 300)), + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(20, 20), + assert_post_info_weight: Some(Weight::from_parts(80, 300)), + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1080, 1300 + LEN as u64), + }, + // post info doesn't double refund the call and is missing some. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 350, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: Some(Weight::from_parts(60, 200)), + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(20, 20), + // 50 are missed in pov because 100 is unspent in post info but it should be only 50. + assert_post_info_weight: Some(Weight::from_parts(40, 200)), + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1040, 1250 + LEN as u64), + }, + // post info doesn't double refund the call and is accurate. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 250, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: Some(Weight::from_parts(60, 200)), + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(20, 20), + assert_post_info_weight: Some(Weight::from_parts(40, 150)), + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1040, 1150 + LEN as u64), + }, + // post info doesn't double refund the call and is accurate. Even if mock ext is refunding + // too much. + TestCfg { + measured_proof_size_pre_dispatch: 100, + measured_proof_size_post_dispatch: 250, + info_call_weight: Weight::from_parts(40, 100), + info_extension_weight: Weight::from_parts(60, 200), + post_info_actual_weight: Some(Weight::from_parts(60, 200)), + block_weight_pre_dispatch: Weight::from_parts(1000, 1000), + mock_ext_refund: Weight::from_parts(20, 300), + assert_post_info_weight: Some(Weight::from_parts(40, 150)), + assert_block_weight_post_dispatch: base_extrinsic + + Weight::from_parts(1040, 1150 + LEN as u64), + }, + ]; + + for (i, test) in tests.into_iter().enumerate() { + dbg!("test number: ", i); + MOCK_EXT_REFUND.with_borrow_mut(|v| *v = test.mock_ext_refund); + let mut test_ext = setup_test_externalities(&[ + test.measured_proof_size_pre_dispatch as usize, + test.measured_proof_size_post_dispatch as usize, + ]); + + test_ext.execute_with(|| { + frame_system::BlockWeight::::mutate(|current_weight| { + current_weight.set(test.block_weight_pre_dispatch, DispatchClass::Normal); + }); + // Benchmarked storage weight: 50 + let info = DispatchInfo { + call_weight: test.info_call_weight, + extension_weight: test.info_extension_weight, + ..Default::default() + }; + let mut post_info = PostDispatchInfo { + actual_weight: test.post_info_actual_weight, + pays_fee: Default::default(), + }; + let tx_ext = new_tx_ext(); + let (pre, _) = tx_ext + .validate_and_prepare(ALICE_ORIGIN.clone().into(), CALL, &info, LEN, 0) + .unwrap(); + assert_ok!(Tx::post_dispatch(pre, &info, &mut post_info, LEN, &Ok(()))); + + assert_eq!(post_info.actual_weight, test.assert_post_info_weight); + assert_eq!( + *frame_system::BlockWeight::::get().get(DispatchClass::Normal), + test.assert_block_weight_post_dispatch, + ); + }) + } +} + +#[test] +fn storage_size_reported_correctly() { + let mut test_ext = setup_test_externalities(&[1000]); + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), Some(1000)); + }); + + let mut test_ext = new_test_ext(); + + let test_recorder = TestRecorder::new(&[0]); + + test_ext.register_extension(ProofSizeExt::new(test_recorder)); + + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), Some(0)); + }); +} + +#[test] +fn storage_size_disabled_reported_correctly() { + let mut test_ext = setup_test_externalities(&[PROOF_RECORDING_DISABLED as usize]); + + test_ext.execute_with(|| { + assert_eq!(get_proof_size(), None); + }); +} + +#[test] +fn full_basic_refund() { + // Settings for the test: + let actual_used_proof_size = 200; + let check_weight = 100; + let storage_weight_reclaim = 100; + let mock_ext = 142; + let mock_ext_refund = 100; + + // Test execution: + CHECK_WEIGHT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(1, check_weight)); + STORAGE_WEIGHT_RECLAIM_WEIGHT + .with_borrow_mut(|v| *v = Weight::from_parts(1, storage_weight_reclaim)); + MOCK_EXT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(36, mock_ext)); + MOCK_EXT_REFUND.with_borrow_mut(|v| *v = Weight::from_parts(35, mock_ext_refund)); + + let initial_storage_weight = 1212u64; + + let mut test_ext = setup_test_externalities(&[ + initial_storage_weight as usize, + initial_storage_weight as usize + actual_used_proof_size, + ]); + + test_ext.execute_with(|| { + set_current_storage_weight(initial_storage_weight); + + let extrinsic = new_extrinsic(); + let call_info = extrinsic.function.get_dispatch_info(); + + let info = extrinsic.get_dispatch_info(); + let post_info = extrinsic.apply::(&info, LEN).unwrap().unwrap(); + + // Assertions: + assert_eq!( + post_info.actual_weight.unwrap().ref_time(), + call_info.call_weight.ref_time() + 3, + ); + assert_eq!( + post_info.actual_weight.unwrap().proof_size(), + // LEN is part of the base extrinsic, not the post info weight actual weight. + actual_used_proof_size as u64, + ); + assert_eq!( + get_storage_weight().proof_size(), + initial_storage_weight + actual_used_proof_size as u64 + LEN as u64 + ); + }); +} + +#[test] +fn full_accrue() { + // Settings for the test: + let actual_used_proof_size = 400; + let check_weight = 100; + let storage_weight_reclaim = 100; + let mock_ext = 142; + let mock_ext_refund = 100; + + // Test execution: + CHECK_WEIGHT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(1, check_weight)); + STORAGE_WEIGHT_RECLAIM_WEIGHT + .with_borrow_mut(|v| *v = Weight::from_parts(1, storage_weight_reclaim)); + MOCK_EXT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(36, mock_ext)); + MOCK_EXT_REFUND.with_borrow_mut(|v| *v = Weight::from_parts(35, mock_ext_refund)); + + let initial_storage_weight = 1212u64; + + let mut test_ext = setup_test_externalities(&[ + initial_storage_weight as usize, + initial_storage_weight as usize + actual_used_proof_size, + ]); + + test_ext.execute_with(|| { + set_current_storage_weight(initial_storage_weight); + + let extrinsic = new_extrinsic(); + let call_info = extrinsic.function.get_dispatch_info(); + + let info = extrinsic.get_dispatch_info(); + let post_info = extrinsic.apply::(&info, LEN).unwrap().unwrap(); + + // Assertions: + assert_eq!( + post_info.actual_weight.unwrap().ref_time(), + call_info.call_weight.ref_time() + 3, + ); + assert_eq!( + post_info.actual_weight.unwrap().proof_size(), + info.total_weight().proof_size(), // The post info doesn't get the accrue. + ); + assert_eq!( + get_storage_weight().proof_size(), + initial_storage_weight + actual_used_proof_size as u64 + LEN as u64 + ); + }); +} + +#[test] +fn bare_is_reclaimed() { + let mut test_ext = setup_test_externalities(&[]); + test_ext.execute_with(|| { + let info = DispatchInfo { + call_weight: Weight::from_parts(100, 100), + extension_weight: Weight::from_parts(100, 100), + class: DispatchClass::Normal, + pays_fee: Default::default(), + }; + let mut post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(100, 100)), + pays_fee: Default::default(), + }; + MOCK_EXT_REFUND.with_borrow_mut(|v| *v = Weight::from_parts(10, 10)); + + frame_system::BlockWeight::::mutate(|current_weight| { + current_weight + .set(Weight::from_parts(45, 45) + info.total_weight(), DispatchClass::Normal); + }); + + StorageWeightReclaim::::bare_post_dispatch( + &info, + &mut post_info, + 0, + &Ok(()), + ) + .expect("tx is valid"); + + assert_eq!( + *frame_system::BlockWeight::::get().get(DispatchClass::Normal), + Weight::from_parts(45 + 90, 45 + 90), + ); + }); +} + +#[test] +fn sets_to_node_storage_proof_if_higher() { + struct TestCfg { + initial_proof_size: u64, + post_dispatch_proof_size: u64, + mock_ext_proof_size: u64, + pre_dispatch_block_proof_size: u64, + assert_final_block_proof_size: u64, + } + + let tests = vec![ + // The storage proof reported by the proof recorder is higher than what is stored on + // the runtime side. + TestCfg { + initial_proof_size: 1000, + post_dispatch_proof_size: 1005, + mock_ext_proof_size: 0, + pre_dispatch_block_proof_size: 5, + // We expect that the storage weight was set to the node-side proof size (1005) + + // extrinsics length (150) + assert_final_block_proof_size: 1155, + }, + // In this second scenario the proof size on the node side is only lower + // after reclaim happened. + TestCfg { + initial_proof_size: 175, + post_dispatch_proof_size: 180, + mock_ext_proof_size: 100, + pre_dispatch_block_proof_size: 85, + // After the pre_dispatch, the BlockWeight proof size will be + // 85 (initial) + 100 (benched) + 150 (tx length) = 335 + // + // We expect that the storage weight was set to the node-side proof weight + // First we will reclaim 95, which leaves us with 240 BlockWeight. + // This is lower than 180 (proof size hf) + 150 (length). + // So we expect it to be set to 330. + assert_final_block_proof_size: 330, + }, + ]; + + for test in tests { + let mut test_ext = setup_test_externalities(&[ + test.initial_proof_size as usize, + test.post_dispatch_proof_size as usize, + ]); + + CHECK_WEIGHT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(0, 0)); + STORAGE_WEIGHT_RECLAIM_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(0, 0)); + MOCK_EXT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(0, test.mock_ext_proof_size)); + + test_ext.execute_with(|| { + set_current_storage_weight(test.pre_dispatch_block_proof_size); + + let extrinsic = new_extrinsic(); + let call_info = extrinsic.function.get_dispatch_info(); + assert_eq!(call_info.call_weight.proof_size(), 0); + + let info = extrinsic.get_dispatch_info(); + let _post_info = extrinsic.apply::(&info, LEN).unwrap().unwrap(); + + assert_eq!(get_storage_weight().proof_size(), test.assert_final_block_proof_size); + }) + } +} + +#[test] +fn test_pov_missing_from_node_reclaim() { + // Test scenario: after dispatch the pov size from node side is less than block weight. + // Ensure `pov_size_missing_from_node` is calculated correctly, and `ExtrinsicWeightReclaimed` + // is updated correctly. + + // Proof size: + let bench_pre_dispatch_call = 220; + let bench_post_dispatch_actual = 90; + let len = 20; // Only one extrinsic in the scenario. So all extrinsics length. + let block_pre_dispatch = 100; + let missing_from_node = 50; + let node_diff = 70; + + let node_pre_dispatch = block_pre_dispatch + missing_from_node; + let node_post_dispatch = node_pre_dispatch + node_diff; + + // Initialize the test. + let mut test_ext = + setup_test_externalities(&[node_pre_dispatch as usize, node_post_dispatch as usize]); + + test_ext.execute_with(|| { + set_current_storage_weight(block_pre_dispatch); + let info = DispatchInfo { + call_weight: Weight::from_parts(0, bench_pre_dispatch_call), + extension_weight: Weight::from_parts(0, 0), + ..Default::default() + }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(0, bench_post_dispatch_actual)), + ..Default::default() + }; + + // Execute the transaction. + let tx_ext = StorageWeightReclaim::>::new( + frame_system::CheckWeight::new(), + ); + tx_ext + .test_run(ALICE_ORIGIN.clone().into(), CALL, &info, len as usize, 0, |_| Ok(post_info)) + .expect("valid") + .expect("success"); + + // Assert the results. + assert_eq!( + frame_system::BlockWeight::::get().get(DispatchClass::Normal).proof_size(), + node_post_dispatch + len, + ); + assert_eq!( + frame_system::ExtrinsicWeightReclaimed::::get().proof_size(), + bench_pre_dispatch_call - node_diff - missing_from_node, + ); + }); +} + +#[test] +fn test_ref_time_weight_reclaim() { + // Test scenario: after dispatch the time weight is refunded correctly. + + // Time weight: + let bench_pre_dispatch_call = 145; + let bench_post_dispatch_actual = 104; + let bench_mock_ext_weight = 63; + let bench_mock_ext_refund = 22; + let len = 20; // Only one extrinsic in the scenario. So all extrinsics length. + let block_pre_dispatch = 121; + let node_pre_dispatch = 0; + let node_post_dispatch = 0; + + // Initialize the test. + CHECK_WEIGHT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(0, 0)); + STORAGE_WEIGHT_RECLAIM_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(0, 0)); + MOCK_EXT_WEIGHT.with_borrow_mut(|v| *v = Weight::from_parts(bench_mock_ext_weight, 0)); + MOCK_EXT_REFUND.with_borrow_mut(|v| *v = Weight::from_parts(bench_mock_ext_refund, 0)); + + let base_extrinsic = <::BlockWeights as Get< + frame_system::limits::BlockWeights, + >>::get() + .per_class + .get(DispatchClass::Normal) + .base_extrinsic; + + let mut test_ext = + setup_test_externalities(&[node_pre_dispatch as usize, node_post_dispatch as usize]); + + test_ext.execute_with(|| { + frame_system::BlockWeight::::mutate(|current_weight| { + current_weight.set(Weight::from_parts(block_pre_dispatch, 0), DispatchClass::Normal); + }); + let info = DispatchInfo { + call_weight: Weight::from_parts(bench_pre_dispatch_call, 0), + extension_weight: Weight::from_parts(bench_mock_ext_weight, 0), + ..Default::default() + }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(bench_post_dispatch_actual, 0)), + ..Default::default() + }; + + type InnerTxExt = (frame_system::CheckWeight, MockExtensionWithRefund); + // Execute the transaction. + let tx_ext = StorageWeightReclaim::::new(( + frame_system::CheckWeight::new(), + MockExtensionWithRefund, + )); + tx_ext + .test_run(ALICE_ORIGIN.clone().into(), CALL, &info, len as usize, 0, |_| Ok(post_info)) + .expect("valid transaction extension pipeline") + .expect("success"); + + // Assert the results. + assert_eq!( + frame_system::BlockWeight::::get().get(DispatchClass::Normal).ref_time(), + block_pre_dispatch + + base_extrinsic.ref_time() + + bench_post_dispatch_actual + + bench_mock_ext_weight - + bench_mock_ext_refund, + ); + assert_eq!( + frame_system::ExtrinsicWeightReclaimed::::get().ref_time(), + bench_pre_dispatch_call - bench_post_dispatch_actual + bench_mock_ext_refund, + ); + }); +} + +#[test] +fn test_metadata() { + assert_eq!( + StorageWeightReclaim::>::metadata() + .iter() + .map(|m| m.identifier) + .collect::>(), + vec!["CheckWeight", "StorageWeightReclaim"] + ); +} diff --git a/cumulus/pallets/weight-reclaim/src/weights.rs b/cumulus/pallets/weight-reclaim/src/weights.rs new file mode 100644 index 000000000000..e651c8a78318 --- /dev/null +++ b/cumulus/pallets/weight-reclaim/src/weights.rs @@ -0,0 +1,74 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `fedora`, CPU: `13th Gen Intel(R) Core(TM) i7-1360P` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// ./target/release/parachain-template-node +// benchmark +// pallet +// --pallet +// cumulus-pallet-weight-reclaim +// --chain +// dev +// --output +// cumulus/pallets/weight-reclaim/src/weights.rs +// --template +// substrate/.maintain/frame-weight-template.hbs +// --extrinsic +// * + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `cumulus_pallet_weight_reclaim`. +pub trait WeightInfo { + fn storage_weight_reclaim() -> Weight; +} + +/// Weights for `cumulus_pallet_weight_reclaim` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_247_000 picoseconds. + Weight::from_parts(2_466_000, 0) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_247_000 picoseconds. + Weight::from_parts(2_466_000, 0) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index c954ddb7b8c7..abe59a8439a8 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -80,11 +80,11 @@ assets-common = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } @@ -112,6 +112,7 @@ runtime-benchmarks = [ "assets-common/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -151,6 +152,7 @@ runtime-benchmarks = [ try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", @@ -192,11 +194,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 8f4ae4670acd..1db152e39fd9 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -182,6 +182,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -958,6 +962,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 3, ParachainInfo: parachain_info = 4, + WeightReclaim: cumulus_pallet_weight_reclaim = 5, // Monetary stuff. Balances: pallet_balances = 10, @@ -1012,18 +1017,20 @@ pub type SignedBlock = generic::SignedBlock; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, - frame_metadata_hash_extension::CheckMetadataHash, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, + frame_metadata_hash_extension::CheckMetadataHash, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; @@ -1207,6 +1214,7 @@ mod benches { // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 000000000000..c8f9bb7cd56c --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=asset-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 7_301_000 picoseconds. + Weight::from_parts(7_536_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system_extensions.rs index 182410f20fff..a5c9fea3cdf5 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/frame_system_extensions.rs @@ -16,28 +16,29 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-10-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `697235d969a1`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: -// ./target/release/polkadot-parachain +// frame-omni-bencher +// v1 // benchmark // pallet -// --wasm-execution=compiled +// --extrinsic=* +// --runtime=target/release/wbuild/asset-hub-rococo-runtime/asset_hub_rococo_runtime.wasm // --pallet=frame_system_extensions +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ -// --chain=asset-hub-rococo-dev +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,8 +57,8 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `54` // Estimated: `3509` - // Minimum execution time: 3_637_000 picoseconds. - Weight::from_parts(6_382_000, 0) + // Minimum execution time: 8_313_000 picoseconds. + Weight::from_parts(8_528_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -67,8 +68,8 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `92` // Estimated: `3509` - // Minimum execution time: 5_841_000 picoseconds. - Weight::from_parts(8_776_000, 0) + // Minimum execution time: 12_527_000 picoseconds. + Weight::from_parts(13_006_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -78,8 +79,8 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `92` // Estimated: `3509` - // Minimum execution time: 5_841_000 picoseconds. - Weight::from_parts(8_776_000, 0) + // Minimum execution time: 12_380_000 picoseconds. + Weight::from_parts(12_922_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -87,44 +88,64 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 561_000 picoseconds. - Weight::from_parts(2_705_000, 0) + // Minimum execution time: 782_000 picoseconds. + Weight::from_parts(855_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_316_000 picoseconds. - Weight::from_parts(5_771_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 11_743_000 picoseconds. + Weight::from_parts(12_067_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 511_000 picoseconds. - Weight::from_parts(2_575_000, 0) + // Minimum execution time: 644_000 picoseconds. + Weight::from_parts(697_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 501_000 picoseconds. - Weight::from_parts(2_595_000, 0) + // Minimum execution time: 605_000 picoseconds. + Weight::from_parts(700_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: // Measured: `24` // Estimated: `1533` - // Minimum execution time: 3_687_000 picoseconds. - Weight::from_parts(6_192_000, 0) + // Minimum execution time: 9_796_000 picoseconds. + Weight::from_parts(10_365_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1533` + // Minimum execution time: 4_855_000 picoseconds. + Weight::from_parts(5_050_000, 0) .saturating_add(Weight::from_parts(0, 1533)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs index 33f111009ed0..ae78a56d8b3c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs @@ -16,6 +16,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index 7c31745d8f6e..cb10ae9a4800 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -81,11 +81,11 @@ assets-common = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } pallet-message-queue = { workspace = true } @@ -114,6 +114,7 @@ runtime-benchmarks = [ "assets-common/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -155,6 +156,7 @@ runtime-benchmarks = [ try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", @@ -198,11 +200,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 26ef3219a1e9..5fb495e4e8cf 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -183,6 +183,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -1000,6 +1004,7 @@ construct_runtime!( // RandomnessCollectiveFlip = 2 removed Timestamp: pallet_timestamp = 3, ParachainInfo: parachain_info = 4, + WeightReclaim: cumulus_pallet_weight_reclaim = 5, // Monetary stuff. Balances: pallet_balances = 10, @@ -1057,18 +1062,20 @@ pub type SignedBlock = generic::SignedBlock; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, - frame_metadata_hash_extension::CheckMetadataHash, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, + frame_metadata_hash_extension::CheckMetadataHash, + ), +>; /// Default extensions applied to Ethereum transactions. #[derive(Clone, PartialEq, Eq, Debug)] @@ -1088,9 +1095,9 @@ impl EthExtra for EthExtraImpl { frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from(tip, None), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::::new(), frame_metadata_hash_extension::CheckMetadataHash::::new(false), ) + .into() } } @@ -1337,6 +1344,7 @@ mod benches { // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 000000000000..1573a278e246 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=asset-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 7_470_000 picoseconds. + Weight::from_parts(7_695_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system_extensions.rs index e8dd9763c282..a1bb92cf7008 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/frame_system_extensions.rs @@ -16,28 +16,29 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-10-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 +//! HOSTNAME: `697235d969a1`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: -// ./target/release/polkadot-parachain +// frame-omni-bencher +// v1 // benchmark // pallet -// --wasm-execution=compiled +// --extrinsic=* +// --runtime=target/release/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.wasm // --pallet=frame_system_extensions +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ -// --chain=asset-hub-westend-dev +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,8 +57,8 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `54` // Estimated: `3509` - // Minimum execution time: 3_206_000 picoseconds. - Weight::from_parts(6_212_000, 0) + // Minimum execution time: 6_329_000 picoseconds. + Weight::from_parts(6_665_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -67,8 +68,8 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `92` // Estimated: `3509` - // Minimum execution time: 5_851_000 picoseconds. - Weight::from_parts(8_847_000, 0) + // Minimum execution time: 12_110_000 picoseconds. + Weight::from_parts(12_883_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -78,8 +79,8 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `92` // Estimated: `3509` - // Minimum execution time: 5_851_000 picoseconds. - Weight::from_parts(8_847_000, 0) + // Minimum execution time: 12_241_000 picoseconds. + Weight::from_parts(12_780_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -87,44 +88,64 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 631_000 picoseconds. - Weight::from_parts(3_086_000, 0) + // Minimum execution time: 825_000 picoseconds. + Weight::from_parts(890_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_446_000 picoseconds. - Weight::from_parts(5_911_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 10_159_000 picoseconds. + Weight::from_parts(10_461_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 481_000 picoseconds. - Weight::from_parts(2_916_000, 0) + // Minimum execution time: 578_000 picoseconds. + Weight::from_parts(660_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 501_000 picoseconds. - Weight::from_parts(2_595_000, 0) + // Minimum execution time: 618_000 picoseconds. + Weight::from_parts(682_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: // Measured: `24` // Estimated: `1533` - // Minimum execution time: 3_927_000 picoseconds. - Weight::from_parts(6_613_000, 0) + // Minimum execution time: 9_964_000 picoseconds. + Weight::from_parts(10_419_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1533` + // Minimum execution time: 4_890_000 picoseconds. + Weight::from_parts(5_163_000, 0) .saturating_add(Weight::from_parts(0, 1533)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs index b0f986768f40..442b58635f48 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs @@ -15,6 +15,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 3fabea3b02f4..3dba65ae99f1 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -72,11 +72,11 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } @@ -151,11 +151,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking/std", "frame-executive/std", @@ -230,6 +230,7 @@ runtime-benchmarks = [ "bridge-runtime-common/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -272,6 +273,7 @@ runtime-benchmarks = [ try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 88146cecb9ef..35af034310d9 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -124,20 +124,22 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The TransactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - BridgeRejectObsoleteHeadersAndMessages, - (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages,), - frame_metadata_hash_extension::CheckMetadataHash, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + BridgeRejectObsoleteHeadersAndMessages, + (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages,), + frame_metadata_hash_extension::CheckMetadataHash, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -313,6 +315,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -555,6 +561,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 2, ParachainInfo: parachain_info = 3, + WeightReclaim: cumulus_pallet_weight_reclaim = 4, // Monetary stuff. Balances: pallet_balances = 10, @@ -667,6 +674,7 @@ mod benches { [pallet_collator_selection, CollatorSelection] [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] + [cumulus_pallet_weight_reclaim, WeightReclaim] // XCM [pallet_xcm, PalletXcmExtrinsicsBenchmark::] // NOTE: Make sure you point to the individual modules below. @@ -1547,41 +1555,44 @@ mod tests { use bp_polkadot_core::SuffixedCommonTransactionExtensionExt; sp_io::TestExternalities::default().execute_with(|| { - frame_system::BlockHash::::insert(BlockNumber::zero(), Hash::default()); - let payload: TxExtension = ( - frame_system::CheckNonZeroSender::new(), - frame_system::CheckSpecVersion::new(), - frame_system::CheckTxVersion::new(), - frame_system::CheckGenesis::new(), - frame_system::CheckEra::from(Era::Immortal), - frame_system::CheckNonce::from(10), - frame_system::CheckWeight::new(), - pallet_transaction_payment::ChargeTransactionPayment::from(10), - BridgeRejectObsoleteHeadersAndMessages, - ( - bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), - ), - frame_metadata_hash_extension::CheckMetadataHash::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), - ); - - // for BridgeHubRococo - { - let bhr_indirect_payload = bp_bridge_hub_rococo::TransactionExtension::from_params( - VERSION.spec_version, - VERSION.transaction_version, - bp_runtime::TransactionEra::Immortal, - System::block_hash(BlockNumber::zero()), - 10, - 10, - (((), ()), ((), ())), - ); - assert_eq!(payload.encode().split_last().unwrap().1, bhr_indirect_payload.encode()); - assert_eq!( - TxExtension::implicit(&payload).unwrap().encode().split_last().unwrap().1, - sp_runtime::traits::TransactionExtension::::implicit(&bhr_indirect_payload).unwrap().encode() - ) - } - }); + frame_system::BlockHash::::insert(BlockNumber::zero(), Hash::default()); + let payload: TxExtension = ( + frame_system::CheckNonZeroSender::new(), + frame_system::CheckSpecVersion::new(), + frame_system::CheckTxVersion::new(), + frame_system::CheckGenesis::new(), + frame_system::CheckEra::from(Era::Immortal), + frame_system::CheckNonce::from(10), + frame_system::CheckWeight::new(), + pallet_transaction_payment::ChargeTransactionPayment::from(10), + BridgeRejectObsoleteHeadersAndMessages, + ( + bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(), + ), + frame_metadata_hash_extension::CheckMetadataHash::new(false), + ).into(); + + // for BridgeHubRococo + { + let bhr_indirect_payload = bp_bridge_hub_rococo::TransactionExtension::from_params( + VERSION.spec_version, + VERSION.transaction_version, + bp_runtime::TransactionEra::Immortal, + System::block_hash(BlockNumber::zero()), + 10, + 10, + (((), ()), ((), ())), + ); + assert_eq!(payload.encode().split_last().unwrap().1, bhr_indirect_payload.encode()); + assert_eq!( + TxExtension::implicit(&payload).unwrap().encode().split_last().unwrap().1, + sp_runtime::traits::TransactionExtension::::implicit( + &bhr_indirect_payload + ) + .unwrap() + .encode() + ) + } + }); } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 000000000000..ca1d8dcbe567 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=bridge-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 6_988_000 picoseconds. + Weight::from_parts(7_361_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system_extensions.rs index 64eef1b4f740..93fb6f3bbbe3 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/frame_system_extensions.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/release/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* // --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=frame_system_extensions -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json +// --chain=bridge-hub-rococo-dev // --header=./cumulus/file_header.txt // --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ -// --chain=bridge-hub-rococo-dev #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,81 +48,92 @@ use core::marker::PhantomData; /// Weight functions for `frame_system_extensions`. pub struct WeightInfo(PhantomData); impl frame_system::ExtensionsWeightInfo for WeightInfo { - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: // Measured: `54` - // Estimated: `3509` - // Minimum execution time: 3_136_000 picoseconds. - Weight::from_parts(5_842_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 4_211_000 picoseconds. + Weight::from_parts(4_470_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_771_000 picoseconds. - Weight::from_parts(8_857_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 8_792_000 picoseconds. + Weight::from_parts(9_026_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_771_000 picoseconds. - Weight::from_parts(8_857_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 8_700_000 picoseconds. + Weight::from_parts(9_142_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } fn check_non_zero_sender() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 732_000 picoseconds. - Weight::from_parts(2_875_000, 0) + // Minimum execution time: 487_000 picoseconds. + Weight::from_parts(534_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_627_000 picoseconds. - Weight::from_parts(6_322_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 6_719_000 picoseconds. + Weight::from_parts(6_846_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 471_000 picoseconds. - Weight::from_parts(2_455_000, 0) + // Minimum execution time: 410_000 picoseconds. + Weight::from_parts(442_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 491_000 picoseconds. - Weight::from_parts(2_916_000, 0) + // Minimum execution time: 390_000 picoseconds. + Weight::from_parts(425_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: // Measured: `24` // Estimated: `1533` - // Minimum execution time: 3_798_000 picoseconds. - Weight::from_parts(6_272_000, 0) + // Minimum execution time: 5_965_000 picoseconds. + Weight::from_parts(6_291_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1533` + // Minimum execution time: 2_738_000 picoseconds. + Weight::from_parts(2_915_000, 0) .saturating_add(Weight::from_parts(0, 1533)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs index 74796e626a2e..7a0accf2e7a4 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/mod.rs @@ -24,6 +24,7 @@ use ::pallet_bridge_relayers::WeightInfo as _; pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs index d5baa1c71dfd..c40aae5a82a9 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs @@ -184,8 +184,8 @@ fn construct_extrinsic( BridgeRejectObsoleteHeadersAndMessages::default(), (OnBridgeHubRococoRefundBridgeHubWestendMessages::default(),), frame_metadata_hash_extension::CheckMetadataHash::::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), - ); + ) + .into(); let payload = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap(); let signature = payload.using_encoded(|e| sender.sign(e)); UncheckedExtrinsic::new_signed(call, account_id.into(), Signature::Sr25519(signature), tx_ext) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs index 8d74b221a609..b0f4366e29cf 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs @@ -63,7 +63,6 @@ fn construct_extrinsic( BridgeRejectObsoleteHeadersAndMessages::default(), (bridge_to_westend_config::OnBridgeHubRococoRefundBridgeHubWestendMessages::default(),), frame_metadata_hash_extension::CheckMetadataHash::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), ) .into(); let payload = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap(); diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 644aa72d1311..444023eac722 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -72,11 +72,11 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { features = ["bridging"], workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } @@ -148,11 +148,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking/std", "frame-executive/std", @@ -227,6 +227,7 @@ runtime-benchmarks = [ "bridge-runtime-common/runtime-benchmarks", "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -269,6 +270,7 @@ runtime-benchmarks = [ try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 1ca709f0d8cb..2c2e01b4d21d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -120,20 +120,22 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The TransactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - BridgeRejectObsoleteHeadersAndMessages, - (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages,), - frame_metadata_hash_extension::CheckMetadataHash, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + BridgeRejectObsoleteHeadersAndMessages, + (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages,), + frame_metadata_hash_extension::CheckMetadataHash, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -299,6 +301,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -532,6 +538,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 2, ParachainInfo: parachain_info = 3, + WeightReclaim: cumulus_pallet_weight_reclaim = 4, // Monetary stuff. Balances: pallet_balances = 10, @@ -622,6 +629,7 @@ mod benches { [snowbridge_pallet_outbound_queue, EthereumOutboundQueue] [snowbridge_pallet_system, EthereumSystem] [snowbridge_pallet_ethereum_client, EthereumBeaconClient] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } @@ -1369,40 +1377,43 @@ mod tests { use bp_polkadot_core::SuffixedCommonTransactionExtensionExt; sp_io::TestExternalities::default().execute_with(|| { - frame_system::BlockHash::::insert(BlockNumber::zero(), Hash::default()); - let payload: TxExtension = ( - frame_system::CheckNonZeroSender::new(), - frame_system::CheckSpecVersion::new(), - frame_system::CheckTxVersion::new(), - frame_system::CheckGenesis::new(), - frame_system::CheckEra::from(Era::Immortal), - frame_system::CheckNonce::from(10), - frame_system::CheckWeight::new(), - pallet_transaction_payment::ChargeTransactionPayment::from(10), - BridgeRejectObsoleteHeadersAndMessages, - ( - bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(), - ), + frame_system::BlockHash::::insert(BlockNumber::zero(), Hash::default()); + let payload: TxExtension = ( + frame_system::CheckNonZeroSender::new(), + frame_system::CheckSpecVersion::new(), + frame_system::CheckTxVersion::new(), + frame_system::CheckGenesis::new(), + frame_system::CheckEra::from(Era::Immortal), + frame_system::CheckNonce::from(10), + frame_system::CheckWeight::new(), + pallet_transaction_payment::ChargeTransactionPayment::from(10), + BridgeRejectObsoleteHeadersAndMessages, + ( + bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(), + ), frame_metadata_hash_extension::CheckMetadataHash::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), - ); - - { - let bh_indirect_payload = bp_bridge_hub_westend::TransactionExtension::from_params( - VERSION.spec_version, - VERSION.transaction_version, - bp_runtime::TransactionEra::Immortal, - System::block_hash(BlockNumber::zero()), - 10, - 10, - (((), ()), ((), ())), - ); - assert_eq!(payload.encode().split_last().unwrap().1, bh_indirect_payload.encode()); - assert_eq!( - TxExtension::implicit(&payload).unwrap().encode().split_last().unwrap().1, - sp_runtime::traits::TransactionExtension::::implicit(&bh_indirect_payload).unwrap().encode() - ) - } - }); + ).into(); + + { + let bh_indirect_payload = bp_bridge_hub_westend::TransactionExtension::from_params( + VERSION.spec_version, + VERSION.transaction_version, + bp_runtime::TransactionEra::Immortal, + System::block_hash(BlockNumber::zero()), + 10, + 10, + (((), ()), ((), ())), + ); + assert_eq!(payload.encode().split_last().unwrap().1, bh_indirect_payload.encode()); + assert_eq!( + TxExtension::implicit(&payload).unwrap().encode().split_last().unwrap().1, + sp_runtime::traits::TransactionExtension::::implicit( + &bh_indirect_payload + ) + .unwrap() + .encode() + ) + } + }); } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 000000000000..955b27325456 --- /dev/null +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=bridge-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 6_810_000 picoseconds. + Weight::from_parts(7_250_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system_extensions.rs index 459b137d3b84..21cadac25e16 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/frame_system_extensions.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/release/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* // --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=frame_system_extensions -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json +// --chain=bridge-hub-westend-dev // --header=./cumulus/file_header.txt // --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/ -// --chain=bridge-hub-westend-dev #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,81 +48,92 @@ use core::marker::PhantomData; /// Weight functions for `frame_system_extensions`. pub struct WeightInfo(PhantomData); impl frame_system::ExtensionsWeightInfo for WeightInfo { - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: // Measured: `54` - // Estimated: `3509` - // Minimum execution time: 3_166_000 picoseconds. - Weight::from_parts(6_021_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 4_363_000 picoseconds. + Weight::from_parts(4_521_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_651_000 picoseconds. - Weight::from_parts(9_177_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 8_522_000 picoseconds. + Weight::from_parts(8_847_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_651_000 picoseconds. - Weight::from_parts(9_177_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 8_617_000 picoseconds. + Weight::from_parts(8_789_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } fn check_non_zero_sender() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 601_000 picoseconds. - Weight::from_parts(2_805_000, 0) + // Minimum execution time: 485_000 picoseconds. + Weight::from_parts(557_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_727_000 picoseconds. - Weight::from_parts(6_051_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 6_682_000 picoseconds. + Weight::from_parts(6_821_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 471_000 picoseconds. - Weight::from_parts(2_494_000, 0) + // Minimum execution time: 390_000 picoseconds. + Weight::from_parts(441_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 521_000 picoseconds. - Weight::from_parts(2_655_000, 0) + // Minimum execution time: 395_000 picoseconds. + Weight::from_parts(455_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: // Measured: `24` // Estimated: `1533` - // Minimum execution time: 3_808_000 picoseconds. - Weight::from_parts(6_402_000, 0) + // Minimum execution time: 6_134_000 picoseconds. + Weight::from_parts(6_308_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1533` + // Minimum execution time: 2_764_000 picoseconds. + Weight::from_parts(2_893_000, 0) .saturating_add(Weight::from_parts(0, 1533)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs index c1c5c337aca8..313da55831c8 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs @@ -24,6 +24,7 @@ use ::pallet_bridge_relayers::WeightInfo as _; pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs index d71400fa71b6..bc570ef7f74b 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs @@ -185,8 +185,8 @@ fn construct_extrinsic( BridgeRejectObsoleteHeadersAndMessages::default(), (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(),), frame_metadata_hash_extension::CheckMetadataHash::::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), - ); + ) + .into(); let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap(); let signature = payload.using_encoded(|e| sender.sign(e)); UncheckedExtrinsic::new_signed(call, account_id.into(), Signature::Sr25519(signature), extra) diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs index 9d32f28f4fc6..d7e7fbe0c72e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs @@ -95,7 +95,6 @@ fn construct_extrinsic( BridgeRejectObsoleteHeadersAndMessages::default(), (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(),), frame_metadata_hash_extension::CheckMetadataHash::new(false), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(), ) .into(); let payload = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap(); diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 9c70b65060dd..2786321e48e2 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -77,11 +77,11 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-message-queue = { workspace = true } @@ -103,6 +103,7 @@ default = ["std"] runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -143,6 +144,7 @@ runtime-benchmarks = [ try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", @@ -182,11 +184,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index d3cd285ba67a..e9adc4d1eae7 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -191,6 +191,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -669,6 +673,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 2, ParachainInfo: parachain_info = 3, + WeightReclaim: cumulus_pallet_weight_reclaim = 4, // Monetary stuff. Balances: pallet_balances = 10, @@ -735,16 +740,19 @@ pub type SignedBlock = generic::SignedBlock; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + ), +>; + /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; @@ -806,6 +814,7 @@ mod benches { [pallet_salary, AmbassadorSalary] [pallet_treasury, FellowshipTreasury] [pallet_asset_rate, AssetRate] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 000000000000..c286ba132022 --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=collectives-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 6_745_000 picoseconds. + Weight::from_parts(6_948_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system_extensions.rs index f32f27303135..8c2abcd4e8c8 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/frame_system_extensions.rs @@ -16,28 +16,26 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/release/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* // --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json // --pallet=frame_system_extensions -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json +// --chain=collectives-westend-dev // --header=./cumulus/file_header.txt // --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/ -// --chain=collectives-westend-dev #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,81 +48,92 @@ use core::marker::PhantomData; /// Weight functions for `frame_system_extensions`. pub struct WeightInfo(PhantomData); impl frame_system::ExtensionsWeightInfo for WeightInfo { - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: // Measured: `54` - // Estimated: `3509` - // Minimum execution time: 3_497_000 picoseconds. - Weight::from_parts(5_961_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 4_206_000 picoseconds. + Weight::from_parts(4_485_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_240_000 picoseconds. - Weight::from_parts(8_175_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 7_537_000 picoseconds. + Weight::from_parts(7_706_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_240_000 picoseconds. - Weight::from_parts(8_175_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Estimated: `0` + // Minimum execution time: 7_512_000 picoseconds. + Weight::from_parts(7_655_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } fn check_non_zero_sender() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 671_000 picoseconds. - Weight::from_parts(3_005_000, 0) + // Minimum execution time: 447_000 picoseconds. + Weight::from_parts(499_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_426_000 picoseconds. - Weight::from_parts(6_131_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 6_667_000 picoseconds. + Weight::from_parts(6_868_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 501_000 picoseconds. - Weight::from_parts(2_715_000, 0) + // Minimum execution time: 389_000 picoseconds. + Weight::from_parts(420_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 491_000 picoseconds. - Weight::from_parts(2_635_000, 0) + // Minimum execution time: 379_000 picoseconds. + Weight::from_parts(420_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `System::BlockWeight` (r:1 w:1) /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: // Measured: `24` // Estimated: `1533` - // Minimum execution time: 3_958_000 picoseconds. - Weight::from_parts(6_753_000, 0) + // Minimum execution time: 6_330_000 picoseconds. + Weight::from_parts(6_605_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1533` + // Minimum execution time: 2_784_000 picoseconds. + Weight::from_parts(2_960_000, 0) .saturating_add(Weight::from_parts(0, 1533)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs index 00b3bd92d5ef..a1663dc98a34 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs @@ -15,6 +15,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml index cb0655d70cf2..067c4df3b536 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/Cargo.toml @@ -70,11 +70,11 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-message-queue = { workspace = true } @@ -90,11 +90,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", @@ -148,6 +148,7 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -179,6 +180,7 @@ runtime-benchmarks = [ try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index be369565dba9..3348a635df01 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -88,17 +88,19 @@ pub type SignedBlock = generic::SignedBlock; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; @@ -201,6 +203,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = (); +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -402,6 +408,7 @@ construct_runtime!( RandomnessCollectiveFlip: pallet_insecure_randomness_collective_flip = 2, Timestamp: pallet_timestamp = 3, ParachainInfo: parachain_info = 4, + WeightReclaim: cumulus_pallet_weight_reclaim = 5, // Monetary stuff. Balances: pallet_balances = 10, @@ -448,6 +455,7 @@ mod benches { [cumulus_pallet_parachain_system, ParachainSystem] [pallet_contracts, Contracts] [pallet_xcm, PalletXcmExtrinsicsBenchmark::] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml index 2b5fab329293..668b4cc6c7b9 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/Cargo.toml @@ -71,11 +71,11 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } @@ -92,11 +92,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", @@ -154,6 +154,7 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -186,6 +187,7 @@ runtime-benchmarks = [ try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index c4d43e4361fa..e9171c79afae 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -99,18 +99,20 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The TransactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, - frame_metadata_hash_extension::CheckMetadataHash, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + frame_metadata_hash_extension::CheckMetadataHash, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -221,6 +223,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -622,6 +628,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 3, ParachainInfo: parachain_info = 4, + WeightReclaim: cumulus_pallet_weight_reclaim = 5, // Monetary stuff. Balances: pallet_balances = 10, @@ -672,6 +679,7 @@ mod benches { // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 000000000000..6298fd6e7f69 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=coretime-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 6_638_000 picoseconds. + Weight::from_parts(6_806_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system_extensions.rs index a4d09696a1a1..04b695b57693 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/frame_system_extensions.rs @@ -129,4 +129,18 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 3_687_000 picoseconds. + Weight::from_parts(6_192_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs index 24c4f50e6ab8..7fee4a728b9e 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/mod.rs @@ -19,6 +19,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml index 03df782bc266..915926ff9894 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/Cargo.toml @@ -70,11 +70,11 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } @@ -92,11 +92,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", @@ -152,6 +152,7 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -183,6 +184,7 @@ runtime-benchmarks = [ try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 431bfc8a63ba..975856b3b6ff 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -99,18 +99,20 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The TransactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, - frame_metadata_hash_extension::CheckMetadataHash, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + frame_metadata_hash_extension::CheckMetadataHash, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -221,6 +223,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -617,6 +623,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 3, ParachainInfo: parachain_info = 4, + WeightReclaim: cumulus_pallet_weight_reclaim = 5, // Monetary stuff. Balances: pallet_balances = 10, @@ -664,6 +671,7 @@ mod benches { // NOTE: Make sure you point to the individual modules below. [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 000000000000..55d52f4b04c8 --- /dev/null +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=coretime-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 6_658_000 picoseconds. + Weight::from_parts(6_905_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system_extensions.rs index d928b73613a3..9527e0c5549a 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/frame_system_extensions.rs @@ -129,4 +129,18 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 3_687_000 picoseconds. + Weight::from_parts(6_192_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs index 24c4f50e6ab8..7fee4a728b9e 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/mod.rs @@ -19,6 +19,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs index 763f8abea34a..75f45297fe2c 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/lib.rs @@ -300,6 +300,7 @@ pub type TxExtension = ( frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, + frame_system::WeightReclaim, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system_extensions.rs index 4fbbb8d6f781..db9a14e2cf24 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/frame_system_extensions.rs @@ -16,28 +16,30 @@ //! Autogenerated weights for `frame_system_extensions` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-12-21, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-10-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("glutton-westend-dev-1300")`, DB CACHE: 1024 +//! HOSTNAME: `697235d969a1`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: -// ./target/release/polkadot-parachain +// frame-omni-bencher +// v1 // benchmark // pallet -// --wasm-execution=compiled +// --extrinsic=* +// --runtime=target/release/wbuild/glutton-westend-runtime/glutton_westend_runtime.wasm // --pallet=frame_system_extensions +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/glutton/glutton-westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --steps=2 -// --repeat=2 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/ -// --chain=glutton-westend-dev-1300 +// --no-median-slopes +// --genesis-builder-policy=none #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,10 +56,10 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: - // Measured: `54` + // Measured: `0` // Estimated: `3509` - // Minimum execution time: 3_908_000 picoseconds. - Weight::from_parts(4_007_000, 0) + // Minimum execution time: 2_572_000 picoseconds. + Weight::from_parts(2_680_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -65,10 +67,10 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` + // Measured: `0` // Estimated: `3509` - // Minimum execution time: 5_510_000 picoseconds. - Weight::from_parts(6_332_000, 0) + // Minimum execution time: 5_818_000 picoseconds. + Weight::from_parts(6_024_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -76,10 +78,10 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` + // Measured: `14` // Estimated: `3509` - // Minimum execution time: 5_510_000 picoseconds. - Weight::from_parts(6_332_000, 0) + // Minimum execution time: 7_364_000 picoseconds. + Weight::from_parts(7_676_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -87,44 +89,52 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 651_000 picoseconds. - Weight::from_parts(851_000, 0) + // Minimum execution time: 657_000 picoseconds. + Weight::from_parts(686_000, 0) .saturating_add(Weight::from_parts(0, 0)) } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`) fn check_nonce() -> Weight { // Proof Size summary in bytes: // Measured: `0` - // Estimated: `0` - // Minimum execution time: 3_387_000 picoseconds. - Weight::from_parts(3_646_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Estimated: `3529` + // Minimum execution time: 6_931_000 picoseconds. + Weight::from_parts(7_096_000, 0) + .saturating_add(Weight::from_parts(0, 3529)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn check_spec_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 491_000 picoseconds. - Weight::from_parts(651_000, 0) + // Minimum execution time: 518_000 picoseconds. + Weight::from_parts(539_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 451_000 picoseconds. - Weight::from_parts(662_000, 0) + // Minimum execution time: 530_000 picoseconds. + Weight::from_parts(550_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) - /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: - // Measured: `24` - // Estimated: `1489` - // Minimum execution time: 3_537_000 picoseconds. - Weight::from_parts(4_208_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_691_000 picoseconds. + Weight::from_parts(5_955_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_249_000 picoseconds. + Weight::from_parts(3_372_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml index de2898046c0d..6391f8c3eeb9 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-rococo/Cargo.toml @@ -68,11 +68,11 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } @@ -89,11 +89,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "enumflags2/std", "frame-benchmarking?/std", @@ -150,6 +150,7 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -182,6 +183,7 @@ runtime-benchmarks = [ try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index ef3c90ace826..ffdd86c500e5 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -92,17 +92,19 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The TransactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -196,6 +198,10 @@ impl frame_system::Config for Runtime { type MultiBlockMigrator = MultiBlockMigrations; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -567,6 +573,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 2, ParachainInfo: parachain_info = 3, + WeightReclaim: cumulus_pallet_weight_reclaim = 4, // Monetary stuff. Balances: pallet_balances = 10, @@ -626,6 +633,7 @@ mod benches { [pallet_xcm, PalletXcmExtrinsicsBenchmark::] [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 000000000000..439855f85719 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=people-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 7_097_000 picoseconds. + Weight::from_parts(7_419_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/frame_system_extensions.rs index fb2b69e23e82..3f12b25540ea 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/frame_system_extensions.rs @@ -129,4 +129,18 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 3_687_000 picoseconds. + Weight::from_parts(6_192_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } } diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs index fab3c629ab3f..81906a11fe1c 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/mod.rs @@ -17,6 +17,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml index 65bc8264934f..fae0fd2e3332 100644 --- a/cumulus/parachains/runtimes/people/people-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/people/people-westend/Cargo.toml @@ -68,11 +68,11 @@ xcm-runtime-apis = { workspace = true } cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-session-benchmarking = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } @@ -89,11 +89,11 @@ std = [ "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "enumflags2/std", "frame-benchmarking?/std", @@ -150,6 +150,7 @@ std = [ runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", @@ -182,6 +183,7 @@ runtime-benchmarks = [ try-runtime = [ "cumulus-pallet-aura-ext/try-runtime", "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-weight-reclaim/try-runtime", "cumulus-pallet-xcm/try-runtime", "cumulus-pallet-xcmp-queue/try-runtime", "frame-executive/try-runtime", diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index ebf8fcb33bd8..ee6b0db55b91 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -92,17 +92,19 @@ pub type SignedBlock = generic::SignedBlock; pub type BlockId = generic::BlockId; /// The transactionExtension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -195,6 +197,10 @@ impl frame_system::Config for Runtime { type MultiBlockMigrator = MultiBlockMigrations; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = weights::cumulus_pallet_weight_reclaim::WeightInfo; +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -566,6 +572,7 @@ construct_runtime!( ParachainSystem: cumulus_pallet_parachain_system = 1, Timestamp: pallet_timestamp = 2, ParachainInfo: parachain_info = 3, + WeightReclaim: cumulus_pallet_weight_reclaim = 4, // Monetary stuff. Balances: pallet_balances = 10, @@ -624,6 +631,7 @@ mod benches { [pallet_xcm, PalletXcmExtrinsicsBenchmark::] [pallet_xcm_benchmarks::fungible, XcmBalances] [pallet_xcm_benchmarks::generic, XcmGeneric] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_weight_reclaim.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_weight_reclaim.rs new file mode 100644 index 000000000000..fd3018ec9740 --- /dev/null +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_weight_reclaim.rs @@ -0,0 +1,67 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `cumulus_pallet_weight_reclaim` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=cumulus_pallet_weight_reclaim +// --chain=people-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `cumulus_pallet_weight_reclaim`. +pub struct WeightInfo(PhantomData); +impl cumulus_pallet_weight_reclaim::WeightInfo for WeightInfo { + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + /// Storage: `System::ExtrinsicWeightReclaimed` (r:1 w:1) + /// Proof: `System::ExtrinsicWeightReclaimed` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) + /// Storage: `System::AllExtrinsicsLen` (r:1 w:0) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn storage_weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 7_006_000 picoseconds. + Weight::from_parts(7_269_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/frame_system_extensions.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/frame_system_extensions.rs index 0a4b9e8e2681..422b8566ad08 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/frame_system_extensions.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/frame_system_extensions.rs @@ -129,4 +129,18 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::BlockWeight` (r:1 w:1) + /// Proof: `System::BlockWeight` (`max_values`: Some(1), `max_size`: Some(48), added: 543, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1533` + // Minimum execution time: 3_687_000 picoseconds. + Weight::from_parts(6_192_000, 0) + .saturating_add(Weight::from_parts(0, 1533)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs index fab3c629ab3f..81906a11fe1c 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/mod.rs @@ -17,6 +17,7 @@ pub mod block_weights; pub mod cumulus_pallet_parachain_system; +pub mod cumulus_pallet_weight_reclaim; pub mod cumulus_pallet_xcmp_queue; pub mod extrinsic_weights; pub mod frame_system; diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs index 51dc95bf2c71..38ddf3bc1991 100644 --- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs @@ -140,6 +140,7 @@ pub type TxExtension = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_asset_tx_payment::ChargeAssetTxPayment, + frame_system::WeightReclaim, ); /// Unchecked extrinsic type as expected by this runtime. diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml index e8761445f161..826a2e9764fc 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/Cargo.toml @@ -51,12 +51,12 @@ xcm-executor = { workspace = true } # Cumulus cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-pallet-xcm = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } cumulus-ping = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } cumulus-primitives-utility = { workspace = true } pallet-message-queue = { workspace = true } parachain-info = { workspace = true } @@ -72,12 +72,12 @@ std = [ "codec/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-pallet-xcm/std", "cumulus-pallet-xcmp-queue/std", "cumulus-ping/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "cumulus-primitives-utility/std", "frame-benchmarking?/std", "frame-executive/std", @@ -117,6 +117,7 @@ std = [ ] runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-pallet-xcmp-queue/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-primitives-utility/runtime-benchmarks", diff --git a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs index 42556e0b493c..89cd17d5450a 100644 --- a/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs +++ b/cumulus/parachains/runtimes/testing/rococo-parachain/src/lib.rs @@ -226,6 +226,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = (); +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; @@ -617,6 +621,7 @@ construct_runtime! { Timestamp: pallet_timestamp, Sudo: pallet_sudo, TransactionPayment: pallet_transaction_payment, + WeightReclaim: cumulus_pallet_weight_reclaim, ParachainSystem: cumulus_pallet_parachain_system = 20, ParachainInfo: parachain_info = 21, @@ -657,17 +662,20 @@ pub type SignedBlock = generic::SignedBlock; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + ), +>; + /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; diff --git a/cumulus/primitives/storage-weight-reclaim/src/lib.rs b/cumulus/primitives/storage-weight-reclaim/src/lib.rs index 5cbe662e2700..62ff60811904 100644 --- a/cumulus/primitives/storage-weight-reclaim/src/lib.rs +++ b/cumulus/primitives/storage-weight-reclaim/src/lib.rs @@ -100,15 +100,30 @@ pub fn get_proof_size() -> Option { (proof_size != PROOF_RECORDING_DISABLED).then_some(proof_size) } -/// Storage weight reclaim mechanism. -/// -/// This extension checks the size of the node-side storage proof -/// before and after executing a given extrinsic. The difference between -/// benchmarked and spent weight can be reclaimed. -#[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] -#[scale_info(skip_type_params(T))] -pub struct StorageWeightReclaim(PhantomData); +// Encapsulate into a mod so that macro generated code doesn't trigger a warning about deprecated +// usage. +#[allow(deprecated)] +mod allow_deprecated { + use super::*; + + /// Storage weight reclaim mechanism. + /// + /// This extension checks the size of the node-side storage proof + /// before and after executing a given extrinsic. The difference between + /// benchmarked and spent weight can be reclaimed. + #[deprecated(note = "This extension doesn't provide accurate reclaim for storage intensive \ + transaction extension pipeline; it ignores the validation and preparation of extensions prior \ + to itself and ignores the post dispatch logic for extensions subsequent to itself, it also + doesn't provide weight information. \ + Use `StorageWeightReclaim` in the `cumulus-pallet-weight-reclaim` crate")] + #[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] + #[scale_info(skip_type_params(T))] + pub struct StorageWeightReclaim(pub(super) PhantomData); +} +#[allow(deprecated)] +pub use allow_deprecated::StorageWeightReclaim; +#[allow(deprecated)] impl StorageWeightReclaim { /// Create a new `StorageWeightReclaim` instance. pub fn new() -> Self { @@ -116,6 +131,7 @@ impl StorageWeightReclaim { } } +#[allow(deprecated)] impl core::fmt::Debug for StorageWeightReclaim { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { let _ = write!(f, "StorageWeightReclaim"); @@ -123,6 +139,7 @@ impl core::fmt::Debug for StorageWeightReclaim { } } +#[allow(deprecated)] impl TransactionExtension for StorageWeightReclaim where T::RuntimeCall: Dispatchable, diff --git a/cumulus/primitives/storage-weight-reclaim/src/tests.rs b/cumulus/primitives/storage-weight-reclaim/src/tests.rs index ab83762cc0db..379b39afee0c 100644 --- a/cumulus/primitives/storage-weight-reclaim/src/tests.rs +++ b/cumulus/primitives/storage-weight-reclaim/src/tests.rs @@ -74,6 +74,7 @@ fn get_storage_weight() -> PerDispatchClass { } #[test] +#[allow(deprecated)] fn basic_refund() { // The real cost will be 100 bytes of storage size let mut test_ext = setup_test_externalities(&[0, 100]); @@ -109,6 +110,7 @@ fn basic_refund() { } #[test] +#[allow(deprecated)] fn underestimating_refund() { // We fixed a bug where `pre dispatch info weight > consumed weight > post info weight` // resulted in error. @@ -149,6 +151,7 @@ fn underestimating_refund() { } #[test] +#[allow(deprecated)] fn sets_to_node_storage_proof_if_higher() { // The storage proof reported by the proof recorder is higher than what is stored on // the runtime side. @@ -240,6 +243,7 @@ fn sets_to_node_storage_proof_if_higher() { } #[test] +#[allow(deprecated)] fn does_nothing_without_extension() { let mut test_ext = new_test_ext(); @@ -274,6 +278,7 @@ fn does_nothing_without_extension() { } #[test] +#[allow(deprecated)] fn negative_refund_is_added_to_weight() { let mut test_ext = setup_test_externalities(&[100, 300]); @@ -310,6 +315,7 @@ fn negative_refund_is_added_to_weight() { } #[test] +#[allow(deprecated)] fn test_zero_proof_size() { let mut test_ext = setup_test_externalities(&[0, 0]); @@ -340,6 +346,7 @@ fn test_zero_proof_size() { } #[test] +#[allow(deprecated)] fn test_larger_pre_dispatch_proof_size() { let mut test_ext = setup_test_externalities(&[300, 100]); @@ -374,6 +381,7 @@ fn test_larger_pre_dispatch_proof_size() { } #[test] +#[allow(deprecated)] fn test_incorporates_check_weight_unspent_weight() { let mut test_ext = setup_test_externalities(&[100, 300]); @@ -415,6 +423,7 @@ fn test_incorporates_check_weight_unspent_weight() { } #[test] +#[allow(deprecated)] fn test_incorporates_check_weight_unspent_weight_on_negative() { let mut test_ext = setup_test_externalities(&[100, 300]); @@ -456,6 +465,7 @@ fn test_incorporates_check_weight_unspent_weight_on_negative() { } #[test] +#[allow(deprecated)] fn test_nothing_relcaimed() { let mut test_ext = setup_test_externalities(&[0, 100]); @@ -505,6 +515,7 @@ fn test_nothing_relcaimed() { } #[test] +#[allow(deprecated)] fn test_incorporates_check_weight_unspent_weight_reverse_order() { let mut test_ext = setup_test_externalities(&[100, 300]); @@ -548,6 +559,7 @@ fn test_incorporates_check_weight_unspent_weight_reverse_order() { } #[test] +#[allow(deprecated)] fn test_incorporates_check_weight_unspent_weight_on_negative_reverse_order() { let mut test_ext = setup_test_externalities(&[100, 300]); @@ -616,6 +628,7 @@ fn storage_size_disabled_reported_correctly() { } #[test] +#[allow(deprecated)] fn test_reclaim_helper() { let mut test_ext = setup_test_externalities(&[1000, 1300, 1800]); @@ -635,6 +648,7 @@ fn test_reclaim_helper() { } #[test] +#[allow(deprecated)] fn test_reclaim_helper_does_not_reclaim_negative() { // Benchmarked weight does not change at all let mut test_ext = setup_test_externalities(&[1000, 1300]); @@ -669,6 +683,7 @@ fn get_benched_weight() -> Weight { /// Just here for doc purposes fn do_work() {} +#[allow(deprecated)] #[docify::export_content(simple_reclaimer_example)] fn reclaim_with_weight_meter() { let mut remaining_weight_meter = WeightMeter::with_limit(Weight::from_parts(10, 10)); diff --git a/cumulus/test/client/Cargo.toml b/cumulus/test/client/Cargo.toml index 2c72ca98f35a..f64ee832ace3 100644 --- a/cumulus/test/client/Cargo.toml +++ b/cumulus/test/client/Cargo.toml @@ -39,16 +39,17 @@ polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } # Cumulus +cumulus-pallet-weight-reclaim = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-primitives-parachain-inherent = { workspace = true, default-features = true } cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true, default-features = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } cumulus-test-runtime = { workspace = true } cumulus-test-service = { workspace = true } [features] runtime-benchmarks = [ + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-test-service/runtime-benchmarks", "frame-system/runtime-benchmarks", diff --git a/cumulus/test/client/src/lib.rs b/cumulus/test/client/src/lib.rs index 26cf02b3dea9..7861a42372a6 100644 --- a/cumulus/test/client/src/lib.rs +++ b/cumulus/test/client/src/lib.rs @@ -143,7 +143,6 @@ pub fn generate_extrinsic_with_pair( frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::::new(), ) .into(); @@ -152,7 +151,7 @@ pub fn generate_extrinsic_with_pair( let raw_payload = SignedPayload::from_raw( function.clone(), tx_ext.clone(), - ((), VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), + ((), VERSION.spec_version, genesis_block, current_block_hash, (), (), ()), ); let signature = raw_payload.using_encoded(|e| origin.sign(e)); diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index 150838e5e96e..4cc4f483c028 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -44,9 +44,9 @@ sp-version = { workspace = true } # Cumulus cumulus-pallet-aura-ext = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true } pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } @@ -59,9 +59,9 @@ std = [ "codec/std", "cumulus-pallet-aura-ext/std", "cumulus-pallet-parachain-system/std", + "cumulus-pallet-weight-reclaim/std", "cumulus-primitives-aura/std", "cumulus-primitives-core/std", - "cumulus-primitives-storage-weight-reclaim/std", "frame-executive/std", "frame-support/std", "frame-system-rpc-runtime-api/std", diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index 4abc10276af1..01ce3427c1f1 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -232,6 +232,10 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = (); +} + parameter_types! { pub const MinimumPeriod: u64 = SLOT_DURATION / 2; pub const PotId: PalletId = PalletId(*b"PotStake"); @@ -347,6 +351,7 @@ construct_runtime! { Glutton: pallet_glutton, Aura: pallet_aura, AuraExt: cumulus_pallet_aura_ext, + WeightReclaim: cumulus_pallet_weight_reclaim, } } @@ -377,16 +382,18 @@ pub type SignedBlock = generic::SignedBlock; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index b3d92444c7d1..794007532621 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -81,8 +81,8 @@ cumulus-client-parachain-inherent = { workspace = true, default-features = true cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-client-service = { workspace = true, default-features = true } cumulus-pallet-parachain-system = { workspace = true } +cumulus-pallet-weight-reclaim = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true, default-features = true } cumulus-relay-chain-inprocess-interface = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } cumulus-relay-chain-minimal-node = { workspace = true, default-features = true } @@ -107,6 +107,7 @@ substrate-test-utils = { workspace = true } [features] runtime-benchmarks = [ "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-pallet-weight-reclaim/runtime-benchmarks", "cumulus-primitives-core/runtime-benchmarks", "cumulus-test-client/runtime-benchmarks", "frame-system/runtime-benchmarks", diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 2c13d20333a7..f3f04cbb6383 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -976,13 +976,12 @@ pub fn construct_extrinsic( frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::::new(), ) .into(); let raw_payload = runtime::SignedPayload::from_raw( function.clone(), tx_ext.clone(), - ((), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), (), ()), + ((), runtime::VERSION.spec_version, genesis_block, current_block_hash, (), (), ()), ); let signature = raw_payload.using_encoded(|e| caller.sign(e)); runtime::UncheckedExtrinsic::new_signed( diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index a856e94f42b5..f526c07796ea 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -68,8 +68,8 @@ substrate-wasm-builder = { workspace = true, default-features = true } cumulus-client-service = { workspace = true, default-features = true } cumulus-pallet-aura-ext = { workspace = true, default-features = true } cumulus-pallet-parachain-system = { workspace = true, default-features = true } +cumulus-pallet-weight-reclaim = { workspace = true, default-features = true } cumulus-primitives-proof-size-hostfunction = { workspace = true, default-features = true } -cumulus-primitives-storage-weight-reclaim = { workspace = true, default-features = true } parachain-info = { workspace = true, default-features = true } # Omni Node diff --git a/docs/sdk/src/guides/enable_pov_reclaim.rs b/docs/sdk/src/guides/enable_pov_reclaim.rs index cb6960b3df4e..71abeacd18c8 100644 --- a/docs/sdk/src/guides/enable_pov_reclaim.rs +++ b/docs/sdk/src/guides/enable_pov_reclaim.rs @@ -62,8 +62,10 @@ //! //! In your runtime, you will find a list of TransactionExtensions. //! To enable the reclaiming, -//! add [`StorageWeightReclaim`](cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim) -//! to that list. For maximum efficiency, make sure that `StorageWeightReclaim` is last in the list. +//! set [`StorageWeightReclaim`](cumulus_pallet_weight_reclaim::StorageWeightReclaim) +//! as a warpper of that list. +//! It is necessary that this extension wraps all the other transaction extensions in order to catch +//! the whole PoV size of the transactions. //! The extension will check the size of the storage proof before and after an extrinsic execution. //! It reclaims the difference between the calculated size and the benchmarked size. #![doc = docify::embed!("../../templates/parachain/runtime/src/lib.rs", template_signed_extra)] diff --git a/docs/sdk/src/reference_docs/transaction_extensions.rs b/docs/sdk/src/reference_docs/transaction_extensions.rs index 0f8198e8372d..fe213458b25c 100644 --- a/docs/sdk/src/reference_docs/transaction_extensions.rs +++ b/docs/sdk/src/reference_docs/transaction_extensions.rs @@ -47,9 +47,11 @@ //! to include the so-called metadata hash. This is required by chains to support the generic //! Ledger application and other similar offline wallets. //! -//! - [`StorageWeightReclaim`](cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim): A -//! transaction extension for parachains that reclaims unused storage weight after executing a -//! transaction. +//! - [`WeightReclaim`](frame_system::WeightReclaim): A transaction extension for the relay chain +//! that reclaims unused weight after executing a transaction. +//! +//! - [`StorageWeightReclaim`](cumulus_pallet_weight_reclaim::StorageWeightReclaim): A transaction +//! extension for parachains that reclaims unused storage weight after executing a transaction. //! //! For more information about these extensions, follow the link to the type documentation. //! diff --git a/polkadot/node/service/src/benchmarking.rs b/polkadot/node/service/src/benchmarking.rs index 0cf16edc03cc..5b814a22d2f8 100644 --- a/polkadot/node/service/src/benchmarking.rs +++ b/polkadot/node/service/src/benchmarking.rs @@ -155,6 +155,7 @@ fn westend_sign_call( frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), frame_metadata_hash_extension::CheckMetadataHash::::new(false), + frame_system::WeightReclaim::::new(), ) .into(); @@ -171,6 +172,7 @@ fn westend_sign_call( (), (), None, + (), ), ); @@ -210,6 +212,7 @@ fn rococo_sign_call( frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), frame_metadata_hash_extension::CheckMetadataHash::::new(false), + frame_system::WeightReclaim::::new(), ) .into(); @@ -226,6 +229,7 @@ fn rococo_sign_call( (), (), None, + (), ), ); diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index f34bb62a7cf0..75fd0d9af301 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -423,6 +423,7 @@ pub fn construct_extrinsic( frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + frame_system::WeightReclaim::::new(), ) .into(); let raw_payload = SignedPayload::from_raw( @@ -437,6 +438,7 @@ pub fn construct_extrinsic( (), (), (), + (), ), ); let signature = raw_payload.using_encoded(|e| caller.sign(e)); diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 4034f8bc1431..cab4394eb5a8 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -674,6 +674,7 @@ where frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), frame_metadata_hash_extension::CheckMetadataHash::new(true), + frame_system::WeightReclaim::::new(), ) .into(); let raw_payload = SignedPayload::new(call, tx_ext) @@ -1617,6 +1618,7 @@ pub type TxExtension = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, frame_metadata_hash_extension::CheckMetadataHash, + frame_system::WeightReclaim, ); /// Unchecked extrinsic type as expected by this runtime. diff --git a/polkadot/runtime/rococo/src/weights/frame_system_extensions.rs b/polkadot/runtime/rococo/src/weights/frame_system_extensions.rs index 99dac1ba75f0..88596a37cc01 100644 --- a/polkadot/runtime/rococo/src/weights/frame_system_extensions.rs +++ b/polkadot/runtime/rococo/src/weights/frame_system_extensions.rs @@ -17,25 +17,23 @@ //! Autogenerated weights for `frame_system_extensions` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet -// --chain=rococo-dev // --steps=50 // --repeat=20 -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --pallet=frame_system_extensions // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=frame_system_extensions +// --chain=rococo-dev // --header=./polkadot/file_header.txt // --output=./polkadot/runtime/rococo/src/weights/ @@ -50,45 +48,36 @@ use core::marker::PhantomData; /// Weight functions for `frame_system_extensions`. pub struct WeightInfo(PhantomData); impl frame_system::ExtensionsWeightInfo for WeightInfo { - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: - // Measured: `54` - // Estimated: `3509` - // Minimum execution time: 3_262_000 picoseconds. - Weight::from_parts(3_497_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `30` + // Estimated: `0` + // Minimum execution time: 3_528_000 picoseconds. + Weight::from_parts(3_657_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_416_000 picoseconds. - Weight::from_parts(5_690_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `68` + // Estimated: `0` + // Minimum execution time: 6_456_000 picoseconds. + Weight::from_parts(6_706_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 5_416_000 picoseconds. - Weight::from_parts(5_690_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `68` + // Estimated: `0` + // Minimum execution time: 6_210_000 picoseconds. + Weight::from_parts(6_581_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } fn check_non_zero_sender() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 471_000 picoseconds. - Weight::from_parts(552_000, 0) + // Minimum execution time: 529_000 picoseconds. + Weight::from_parts(561_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::Account` (r:1 w:1) @@ -97,8 +86,8 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 4_847_000 picoseconds. - Weight::from_parts(5_091_000, 0) + // Minimum execution time: 6_935_000 picoseconds. + Weight::from_parts(7_264_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -107,28 +96,32 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 388_000 picoseconds. - Weight::from_parts(421_000, 0) + // Minimum execution time: 452_000 picoseconds. + Weight::from_parts(474_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 378_000 picoseconds. - Weight::from_parts(440_000, 0) + // Minimum execution time: 422_000 picoseconds. + Weight::from_parts(460_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) - /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: - // Measured: `24` - // Estimated: `1489` - // Minimum execution time: 3_402_000 picoseconds. - Weight::from_parts(3_627_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_632_000 picoseconds. + Weight::from_parts(3_784_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_209_000 picoseconds. + Weight::from_parts(2_335_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index d4031f7ac57a..82564d5c278c 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -443,6 +443,7 @@ where frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + frame_system::WeightReclaim::::new(), ) .into(); let raw_payload = SignedPayload::new(call, tx_ext) @@ -834,6 +835,7 @@ pub type TxExtension = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + frame_system::WeightReclaim, ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index cd8eb4d2505a..166f3fc42eef 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -923,6 +923,7 @@ where frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(tip), frame_metadata_hash_extension::CheckMetadataHash::::new(true), + frame_system::WeightReclaim::::new(), ) .into(); let raw_payload = SignedPayload::new(call, tx_ext) @@ -1814,6 +1815,7 @@ pub type TxExtension = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, frame_metadata_hash_extension::CheckMetadataHash, + frame_system::WeightReclaim, ); parameter_types! { diff --git a/polkadot/runtime/westend/src/weights/frame_system_extensions.rs b/polkadot/runtime/westend/src/weights/frame_system_extensions.rs index 048f23fbcb91..75f4f6d00b56 100644 --- a/polkadot/runtime/westend/src/weights/frame_system_extensions.rs +++ b/polkadot/runtime/westend/src/weights/frame_system_extensions.rs @@ -17,24 +17,25 @@ //! Autogenerated weights for `frame_system_extensions` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-09-12, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-30, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `gleipnir`, CPU: `AMD Ryzen 9 7900X 12-Core Processor` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/debug/polkadot +// target/production/polkadot // benchmark // pallet -// --steps=2 -// --repeat=2 +// --steps=50 +// --repeat=20 // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --pallet=frame-system-extensions +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=frame_system_extensions // --chain=westend-dev -// --output=./polkadot/runtime/westend/src/weights/ // --header=./polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights/ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,45 +48,36 @@ use core::marker::PhantomData; /// Weight functions for `frame_system_extensions`. pub struct WeightInfo(PhantomData); impl frame_system::ExtensionsWeightInfo for WeightInfo { - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_genesis() -> Weight { // Proof Size summary in bytes: - // Measured: `54` - // Estimated: `3509` - // Minimum execution time: 75_764_000 picoseconds. - Weight::from_parts(85_402_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `30` + // Estimated: `0` + // Minimum execution time: 3_357_000 picoseconds. + Weight::from_parts(3_484_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_mortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 118_233_000 picoseconds. - Weight::from_parts(126_539_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `68` + // Estimated: `0` + // Minimum execution time: 6_242_000 picoseconds. + Weight::from_parts(6_566_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::BlockHash` (r:1 w:0) - /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn check_mortality_immortal_transaction() -> Weight { // Proof Size summary in bytes: - // Measured: `92` - // Estimated: `3509` - // Minimum execution time: 118_233_000 picoseconds. - Weight::from_parts(126_539_000, 0) - .saturating_add(Weight::from_parts(0, 3509)) - .saturating_add(T::DbWeight::get().reads(1)) + // Measured: `68` + // Estimated: `0` + // Minimum execution time: 6_268_000 picoseconds. + Weight::from_parts(6_631_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } fn check_non_zero_sender() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_885_000 picoseconds. - Weight::from_parts(12_784_000, 0) + // Minimum execution time: 567_000 picoseconds. + Weight::from_parts(617_000, 0) .saturating_add(Weight::from_parts(0, 0)) } /// Storage: `System::Account` (r:1 w:1) @@ -94,8 +86,8 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 104_237_000 picoseconds. - Weight::from_parts(110_910_000, 0) + // Minimum execution time: 6_990_000 picoseconds. + Weight::from_parts(7_343_000, 0) .saturating_add(Weight::from_parts(0, 3593)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) @@ -104,28 +96,32 @@ impl frame_system::ExtensionsWeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_141_000 picoseconds. - Weight::from_parts(11_502_000, 0) + // Minimum execution time: 422_000 picoseconds. + Weight::from_parts(475_000, 0) .saturating_add(Weight::from_parts(0, 0)) } fn check_tx_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_192_000 picoseconds. - Weight::from_parts(11_481_000, 0) + // Minimum execution time: 434_000 picoseconds. + Weight::from_parts(519_000, 0) .saturating_add(Weight::from_parts(0, 0)) } - /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) - /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn check_weight() -> Weight { // Proof Size summary in bytes: - // Measured: `24` - // Estimated: `1489` - // Minimum execution time: 87_616_000 picoseconds. - Weight::from_parts(93_607_000, 0) - .saturating_add(Weight::from_parts(0, 1489)) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_524_000 picoseconds. + Weight::from_parts(3_706_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + } + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_216_000 picoseconds. + Weight::from_parts(2_337_000, 0) + .saturating_add(Weight::from_parts(0, 0)) } } diff --git a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs index 26ea226313f0..6ebf6476f7e5 100644 --- a/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs +++ b/polkadot/xcm/xcm-builder/src/tests/pay/mock.rs @@ -37,6 +37,7 @@ pub type TxExtension = ( frame_system::CheckMortality, frame_system::CheckNonce, frame_system::CheckWeight, + frame_system::WeightReclaim, ); pub type Address = sp_runtime::MultiAddress; pub type UncheckedExtrinsic = diff --git a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs index fb5d1ae7c0e5..56a77094f177 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs @@ -60,7 +60,8 @@ construct_runtime! { } } -pub type TxExtension = (frame_system::CheckWeight,); +pub type TxExtension = + (frame_system::CheckWeight, frame_system::WeightReclaim); // we only use the hash type from this, so using the mock should be fine. pub(crate) type Extrinsic = sp_runtime::generic::UncheckedExtrinsic< diff --git a/prdoc/pr_6140.prdoc b/prdoc/pr_6140.prdoc new file mode 100644 index 000000000000..7e2bd3802cd7 --- /dev/null +++ b/prdoc/pr_6140.prdoc @@ -0,0 +1,95 @@ +title: Accurate weight reclaim with frame_system::WeightReclaim and cumulus `StorageWeightReclaim` transaction extensions + +doc: + - audience: Runtime Dev + description: | + Since the introduction of transaction extension, the transaction extension weight is no longer part of base extrinsic weight. As a consequence some weight of transaction extensions are missed when calculating post dispatch weight and reclaiming unused block weight. + + For solo chains, in order to reclaim the weight accurately `frame_system::WeightReclaim` transaction extension must be used at the end of the transaction extension pipeline. + + For para chains `StorageWeightReclaim` in `cumulus-primitives-storage-weight-reclaim` is deprecated. + A new transaction extension `StorageWeightReclaim` in `cumulus-pallet-weight-reclaim` is introduced. + `StorageWeightReclaim` is meant to be used as a wrapping of the whole transaction extension pipeline, and will take into account all proof size accurately. + + The new wrapping transaction extension is used like this: + ```rust + /// The TransactionExtension to the basic transaction logic. + pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + pallet_transaction_payment::ChargeTransactionPayment, + BridgeRejectObsoleteHeadersAndMessages, + (bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages,), + frame_metadata_hash_extension::CheckMetadataHash, + frame_system::CheckWeight, + ), + >; + ``` + + NOTE: prior to transaction extension, `StorageWeightReclaim` also missed the some proof size used by other transaction extension prior to itself. This is also fixed by the wrapping `StorageWeightReclaim`. + +crates: +- name: cumulus-primitives-storage-weight-reclaim + bump: minor +- name: sp-runtime + bump: patch +- name: polkadot-sdk + bump: minor +- name: asset-hub-rococo-runtime + bump: major +- name: asset-hub-westend-runtime + bump: major +- name: bridge-hub-rococo-runtime + bump: major +- name: bridge-hub-westend-runtime + bump: major +- name: collectives-westend-runtime + bump: major +- name: coretime-rococo-runtime + bump: major +- name: coretime-westend-runtime + bump: major +- name: people-rococo-runtime + bump: major +- name: people-westend-runtime + bump: major +- name: contracts-rococo-runtime + bump: major +- name: frame-support + bump: minor +- name: frame-executive + bump: patch +- name: frame-system + bump: major +- name: staging-xcm-builder + bump: patch +- name: xcm-runtime-apis + bump: patch +- name: cumulus-pallet-weight-reclaim + bump: major +- name: polkadot-service + bump: major +- name: westend-runtime + bump: major +- name: frame-metadata-hash-extension + bump: patch +- name: frame-system-benchmarking + bump: major +- name: polkadot-sdk-frame + bump: major +- name: rococo-runtime + bump: major +- name: cumulus-pov-validator + bump: patch +- name: penpal-runtime + bump: major +- name: glutton-westend-runtime + bump: major +- name: rococo-parachain-runtime + bump: major diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs index 5f6806c235f6..e531097dbb5e 100644 --- a/substrate/bin/node/cli/src/service.rs +++ b/substrate/bin/node/cli/src/service.rs @@ -138,6 +138,7 @@ pub fn create_extrinsic( >::from(tip, None), ), frame_metadata_hash_extension::CheckMetadataHash::new(false), + frame_system::WeightReclaim::::new(), ); let raw_payload = kitchensink_runtime::SignedPayload::from_raw( @@ -153,6 +154,7 @@ pub fn create_extrinsic( (), (), None, + (), ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); @@ -1060,6 +1062,7 @@ mod tests { let tx_payment = pallet_skip_feeless_payment::SkipCheckIfFeeless::from( pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(0, None), ); + let weight_reclaim = frame_system::WeightReclaim::new(); let metadata_hash = frame_metadata_hash_extension::CheckMetadataHash::new(false); let tx_ext: TxExtension = ( check_non_zero_sender, @@ -1071,6 +1074,7 @@ mod tests { check_weight, tx_payment, metadata_hash, + weight_reclaim, ); let raw_payload = SignedPayload::from_raw( function, @@ -1085,6 +1089,7 @@ mod tests { (), (), None, + (), ), ); let signature = raw_payload.using_encoded(|payload| signer.sign(payload)); diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 45ae378cc00e..93b134e8165f 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1532,6 +1532,7 @@ where ), ), frame_metadata_hash_extension::CheckMetadataHash::new(false), + frame_system::WeightReclaim::::new(), ); let raw_payload = SignedPayload::new(call, tx_ext) @@ -2674,6 +2675,7 @@ pub type TxExtension = ( pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, >, frame_metadata_hash_extension::CheckMetadataHash, + frame_system::WeightReclaim, ); #[derive(Clone, PartialEq, Eq, Debug)] @@ -2695,6 +2697,7 @@ impl EthExtra for EthExtraImpl { pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from(tip, None) .into(), frame_metadata_hash_extension::CheckMetadataHash::::new(false), + frame_system::WeightReclaim::::new(), ) } } diff --git a/substrate/bin/node/testing/src/keyring.rs b/substrate/bin/node/testing/src/keyring.rs index e5b0299f01a8..08d6ad6dcc35 100644 --- a/substrate/bin/node/testing/src/keyring.rs +++ b/substrate/bin/node/testing/src/keyring.rs @@ -86,6 +86,7 @@ pub fn tx_ext(nonce: Nonce, extra_fee: Balance) -> TxExtension { pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(extra_fee, None), ), frame_metadata_hash_extension::CheckMetadataHash::new(false), + frame_system::WeightReclaim::new(), ) } diff --git a/substrate/frame/executive/src/tests.rs b/substrate/frame/executive/src/tests.rs index 3841b010325b..882d875f3d80 100644 --- a/substrate/frame/executive/src/tests.rs +++ b/substrate/frame/executive/src/tests.rs @@ -335,6 +335,9 @@ impl frame_system::ExtensionsWeightInfo for MockExtensionsWeights { fn check_weight() -> Weight { Weight::from_parts(10, 0) } + fn weight_reclaim() -> Weight { + Weight::zero() + } } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] @@ -452,6 +455,7 @@ type TxExtension = ( frame_system::CheckNonce, frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, + frame_system::WeightReclaim, ); type UncheckedXt = sp_runtime::generic::UncheckedExtrinsic< u64, @@ -560,6 +564,7 @@ fn tx_ext(nonce: u64, fee: Balance) -> TxExtension { frame_system::CheckNonce::from(nonce), frame_system::CheckWeight::new(), pallet_transaction_payment::ChargeTransactionPayment::from(fee), + frame_system::WeightReclaim::new(), ) .into() } diff --git a/substrate/frame/metadata-hash-extension/src/tests.rs b/substrate/frame/metadata-hash-extension/src/tests.rs index 11a3345ee15c..7a6966f46290 100644 --- a/substrate/frame/metadata-hash-extension/src/tests.rs +++ b/substrate/frame/metadata-hash-extension/src/tests.rs @@ -144,6 +144,7 @@ mod docs { // Add the `CheckMetadataHash` extension. // The position in this list is not important, so we could also add it to beginning. frame_metadata_hash_extension::CheckMetadataHash, + frame_system::WeightReclaim, ); /// In your runtime this will be your real address type. diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index b3e340cbcbff..b0338b682314 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -495,6 +495,7 @@ pub mod runtime { frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, + frame_system::WeightReclaim, ); } diff --git a/substrate/frame/support/src/dispatch.rs b/substrate/frame/support/src/dispatch.rs index 483a3dce77f6..990996830030 100644 --- a/substrate/frame/support/src/dispatch.rs +++ b/substrate/frame/support/src/dispatch.rs @@ -308,6 +308,19 @@ impl PostDispatchInfo { /// Calculate how much weight was actually spent by the `Dispatchable`. pub fn calc_actual_weight(&self, info: &DispatchInfo) -> Weight { if let Some(actual_weight) = self.actual_weight { + let info_total_weight = info.total_weight(); + if actual_weight.any_gt(info_total_weight) { + log::error!( + target: crate::LOG_TARGET, + "Post dispatch weight is greater than pre dispatch weight. \ + Pre dispatch weight may underestimating the actual weight. \ + Greater post dispatch weight components are ignored. + Pre dispatch weight: {:?}, + Post dispatch weight: {:?}", + actual_weight, + info_total_weight, + ); + } actual_weight.min(info.total_weight()) } else { info.total_weight() diff --git a/substrate/frame/system/benchmarking/src/extensions.rs b/substrate/frame/system/benchmarking/src/extensions.rs index 01e4687bc4bc..25d6ea035578 100644 --- a/substrate/frame/system/benchmarking/src/extensions.rs +++ b/substrate/frame/system/benchmarking/src/extensions.rs @@ -29,7 +29,7 @@ use frame_support::{ use frame_system::{ pallet_prelude::*, CheckGenesis, CheckMortality, CheckNonZeroSender, CheckNonce, CheckSpecVersion, CheckTxVersion, CheckWeight, Config, ExtensionsWeightInfo, Pallet as System, - RawOrigin, + RawOrigin, WeightReclaim, }; use sp_runtime::{ generic::Era, @@ -254,5 +254,49 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn weight_reclaim() -> Result<(), BenchmarkError> { + let caller = account("caller", 0, 0); + let base_extrinsic = ::BlockWeights::get() + .get(DispatchClass::Normal) + .base_extrinsic; + let extension_weight = ::ExtensionsWeightInfo::weight_reclaim(); + let info = DispatchInfo { + call_weight: Weight::from_parts(base_extrinsic.ref_time() * 5, 0), + extension_weight, + class: DispatchClass::Normal, + ..Default::default() + }; + let call: T::RuntimeCall = frame_system::Call::remark { remark: vec![] }.into(); + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(base_extrinsic.ref_time() * 2, 0)), + pays_fee: Default::default(), + }; + let len = 0_usize; + let ext = WeightReclaim::::new(); + + let initial_block_weight = Weight::from_parts(base_extrinsic.ref_time() * 2, 0); + frame_system::BlockWeight::::mutate(|current_weight| { + current_weight.set(Weight::zero(), DispatchClass::Mandatory); + current_weight.set(initial_block_weight, DispatchClass::Normal); + current_weight.accrue(base_extrinsic + info.total_weight(), DispatchClass::Normal); + }); + + #[block] + { + ext.test_run(RawOrigin::Signed(caller).into(), &call, &info, len, 0, |_| Ok(post_info)) + .unwrap() + .unwrap(); + } + + assert_eq!( + System::::block_weight().total(), + initial_block_weight + + base_extrinsic + + post_info.actual_weight.unwrap().saturating_add(extension_weight), + ); + Ok(()) + } + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test,); } diff --git a/substrate/frame/system/benchmarking/src/mock.rs b/substrate/frame/system/benchmarking/src/mock.rs index 6b126619ce5b..61b5b885ec62 100644 --- a/substrate/frame/system/benchmarking/src/mock.rs +++ b/substrate/frame/system/benchmarking/src/mock.rs @@ -65,6 +65,10 @@ impl frame_system::ExtensionsWeightInfo for MockWeights { fn check_weight() -> Weight { Weight::from_parts(10, 0) } + + fn weight_reclaim() -> Weight { + Weight::from_parts(10, 0) + } } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/substrate/frame/system/src/extensions/check_weight.rs b/substrate/frame/system/src/extensions/check_weight.rs index ee91478b90f3..de0303defd0c 100644 --- a/substrate/frame/system/src/extensions/check_weight.rs +++ b/substrate/frame/system/src/extensions/check_weight.rs @@ -135,30 +135,12 @@ where Ok(()) } + #[deprecated(note = "Use `frame_system::Pallet::reclaim_weight` instead.")] pub fn do_post_dispatch( info: &DispatchInfoOf, post_info: &PostDispatchInfoOf, ) -> Result<(), TransactionValidityError> { - let unspent = post_info.calc_unspent(info); - if unspent.any_gt(Weight::zero()) { - crate::BlockWeight::::mutate(|current_weight| { - current_weight.reduce(unspent, info.class); - }) - } - - log::trace!( - target: LOG_TARGET, - "Used block weight: {:?}", - crate::BlockWeight::::get(), - ); - - log::trace!( - target: LOG_TARGET, - "Used block length: {:?}", - Pallet::::all_extrinsics_len(), - ); - - Ok(()) + crate::Pallet::::reclaim_weight(info, post_info) } } @@ -279,8 +261,7 @@ where _len: usize, _result: &DispatchResult, ) -> Result { - Self::do_post_dispatch(info, post_info)?; - Ok(Weight::zero()) + crate::Pallet::::reclaim_weight(info, post_info).map(|()| Weight::zero()) } fn bare_validate( @@ -306,7 +287,7 @@ where _len: usize, _result: &DispatchResult, ) -> Result<(), TransactionValidityError> { - Self::do_post_dispatch(info, post_info) + crate::Pallet::::reclaim_weight(info, post_info) } } @@ -744,6 +725,121 @@ mod tests { }) } + #[test] + fn extrinsic_already_refunded_more_precisely() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let accurate_refund = Weight::from_parts(510, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::::mutate(|current_weight| { + current_weight.set(Weight::zero(), DispatchClass::Mandatory); + current_weight.set(prior_block_weight, DispatchClass::Normal); + }); + + // Validate and prepare extrinsic + let pre = CheckWeight::(PhantomData) + .validate_and_prepare(Some(1).into(), CALL, &info, len, 0) + .unwrap() + .0; + + assert_eq!( + BlockWeight::::get().total(), + info.total_weight() + prior_block_weight + base_extrinsic + ); + + // Refund more accurately than the benchmark + BlockWeight::::mutate(|current_weight| { + current_weight.reduce(accurate_refund, DispatchClass::Normal); + }); + crate::ExtrinsicWeightReclaimed::::put(accurate_refund); + + // Do the post dispatch + assert_ok!(CheckWeight::::post_dispatch_details( + pre, + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund is used + assert_eq!(crate::ExtrinsicWeightReclaimed::::get(), accurate_refund); + assert_eq!( + BlockWeight::::get().total(), + info.total_weight() - accurate_refund + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn extrinsic_already_refunded_less_precisely() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let inaccurate_refund = Weight::from_parts(110, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::::mutate(|current_weight| { + current_weight.set(Weight::zero(), DispatchClass::Mandatory); + current_weight.set(prior_block_weight, DispatchClass::Normal); + }); + + // Validate and prepare extrinsic + let pre = CheckWeight::(PhantomData) + .validate_and_prepare(Some(1).into(), CALL, &info, len, 0) + .unwrap() + .0; + + assert_eq!( + BlockWeight::::get().total(), + info.total_weight() + prior_block_weight + base_extrinsic + ); + + // Refund less accurately than the benchmark + BlockWeight::::mutate(|current_weight| { + current_weight.reduce(inaccurate_refund, DispatchClass::Normal); + }); + crate::ExtrinsicWeightReclaimed::::put(inaccurate_refund); + + // Do the post dispatch + assert_ok!(CheckWeight::::post_dispatch_details( + pre, + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + BlockWeight::::get().total(), + post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic + ); + }) + } + #[test] fn zero_weight_extrinsic_still_has_base_weight() { new_test_ext().execute_with(|| { diff --git a/substrate/frame/system/src/extensions/mod.rs b/substrate/frame/system/src/extensions/mod.rs index d79104d22403..66a8b17d30ae 100644 --- a/substrate/frame/system/src/extensions/mod.rs +++ b/substrate/frame/system/src/extensions/mod.rs @@ -22,6 +22,7 @@ pub mod check_nonce; pub mod check_spec_version; pub mod check_tx_version; pub mod check_weight; +pub mod weight_reclaim; pub mod weights; pub use weights::WeightInfo; diff --git a/substrate/frame/system/src/extensions/weight_reclaim.rs b/substrate/frame/system/src/extensions/weight_reclaim.rs new file mode 100644 index 000000000000..0c37422a843b --- /dev/null +++ b/substrate/frame/system/src/extensions/weight_reclaim.rs @@ -0,0 +1,401 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::Config; +use codec::{Decode, Encode}; +use frame_support::dispatch::{DispatchInfo, PostDispatchInfo}; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{ + DispatchInfoOf, Dispatchable, PostDispatchInfoOf, TransactionExtension, ValidateResult, + }, + transaction_validity::{TransactionSource, TransactionValidityError, ValidTransaction}, + DispatchResult, +}; +use sp_weights::Weight; + +/// Reclaim the unused weight using the post dispatch information +/// +/// After the dispatch of the extrinsic, calculate the unused weight using the post dispatch +/// information and update the block consumed weight according to the new calculated extrinsic +/// weight. +#[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct WeightReclaim(core::marker::PhantomData); + +impl WeightReclaim +where + T::RuntimeCall: Dispatchable, +{ + /// Creates new `TransactionExtension` to recalculate the extrinsic weight after dispatch. + pub fn new() -> Self { + Self(Default::default()) + } +} + +impl TransactionExtension for WeightReclaim +where + T::RuntimeCall: Dispatchable, +{ + const IDENTIFIER: &'static str = "WeightReclaim"; + type Implicit = (); + type Pre = (); + type Val = (); + + fn weight(&self, _: &T::RuntimeCall) -> Weight { + ::weight_reclaim() + } + + fn validate( + &self, + origin: T::RuntimeOrigin, + _call: &T::RuntimeCall, + _info: &DispatchInfoOf, + _len: usize, + _self_implicit: Self::Implicit, + _inherited_implication: &impl Encode, + _source: TransactionSource, + ) -> ValidateResult { + Ok((ValidTransaction::default(), (), origin)) + } + + fn prepare( + self, + _val: Self::Val, + _origin: &T::RuntimeOrigin, + _call: &T::RuntimeCall, + _info: &DispatchInfoOf, + _len: usize, + ) -> Result { + Ok(()) + } + + fn post_dispatch_details( + _pre: Self::Pre, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result { + crate::Pallet::::reclaim_weight(info, post_info).map(|()| Weight::zero()) + } + + fn bare_validate( + _call: &T::RuntimeCall, + _info: &DispatchInfoOf, + _len: usize, + ) -> frame_support::pallet_prelude::TransactionValidity { + Ok(ValidTransaction::default()) + } + + fn bare_validate_and_prepare( + _call: &T::RuntimeCall, + _info: &DispatchInfoOf, + _len: usize, + ) -> Result<(), TransactionValidityError> { + Ok(()) + } + + fn bare_post_dispatch( + info: &DispatchInfoOf, + post_info: &mut PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + crate::Pallet::::reclaim_weight(info, post_info) + } +} + +impl core::fmt::Debug for WeightReclaim +where + T::RuntimeCall: Dispatchable, +{ + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{}", Self::IDENTIFIER) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + mock::{new_test_ext, Test}, + BlockWeight, DispatchClass, + }; + use frame_support::{assert_ok, weights::Weight}; + + fn block_weights() -> crate::limits::BlockWeights { + ::BlockWeights::get() + } + + #[test] + fn extrinsic_already_refunded_more_precisely() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let accurate_refund = Weight::from_parts(510, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Normal); + current_weight.accrue( + base_extrinsic + info.total_weight() - accurate_refund, + DispatchClass::Normal, + ); + }); + crate::ExtrinsicWeightReclaimed::::put(accurate_refund); + + // Do the post dispatch + assert_ok!(WeightReclaim::::post_dispatch_details( + (), + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund is used + assert_eq!(crate::ExtrinsicWeightReclaimed::::get(), accurate_refund); + assert_eq!( + *BlockWeight::::get().get(DispatchClass::Normal), + info.total_weight() - accurate_refund + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn extrinsic_already_refunded_less_precisely() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let inaccurate_refund = Weight::from_parts(110, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Normal); + current_weight.accrue( + base_extrinsic + info.total_weight() - inaccurate_refund, + DispatchClass::Normal, + ); + }); + crate::ExtrinsicWeightReclaimed::::put(inaccurate_refund); + + // Do the post dispatch + assert_ok!(WeightReclaim::::post_dispatch_details( + (), + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + *BlockWeight::::get().get(DispatchClass::Normal), + post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn extrinsic_not_refunded_before() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Normal); + current_weight.accrue(base_extrinsic + info.total_weight(), DispatchClass::Normal); + }); + + // Do the post dispatch + assert_ok!(WeightReclaim::::post_dispatch_details( + (), + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + *BlockWeight::::get().get(DispatchClass::Normal), + post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn no_actual_post_dispatch_weight() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = + DispatchInfo { call_weight: Weight::from_parts(512, 0), ..Default::default() }; + let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }; + let prior_block_weight = Weight::from_parts(64, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; + + // Set initial info + BlockWeight::::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Normal); + current_weight.accrue(base_extrinsic + info.total_weight(), DispatchClass::Normal); + }); + + // Do the post dispatch + assert_ok!(WeightReclaim::::post_dispatch_details( + (), + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + *BlockWeight::::get().get(DispatchClass::Normal), + info.total_weight() + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn different_dispatch_class() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = DispatchInfo { + call_weight: Weight::from_parts(512, 0), + class: DispatchClass::Operational, + ..Default::default() + }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Operational).base_extrinsic; + + // Set initial info + BlockWeight::::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Operational); + current_weight + .accrue(base_extrinsic + info.total_weight(), DispatchClass::Operational); + }); + + // Do the post dispatch + assert_ok!(WeightReclaim::::post_dispatch_details( + (), + &info, + &post_info, + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + *BlockWeight::::get().get(DispatchClass::Operational), + post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic + ); + }) + } + + #[test] + fn bare_also_works() { + new_test_ext().execute_with(|| { + // This is half of the max block weight + let info = DispatchInfo { + call_weight: Weight::from_parts(512, 0), + class: DispatchClass::Operational, + ..Default::default() + }; + let post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(128, 0)), + pays_fee: Default::default(), + }; + let prior_block_weight = Weight::from_parts(64, 0); + let len = 0_usize; + let base_extrinsic = block_weights().get(DispatchClass::Operational).base_extrinsic; + + // Set initial info + BlockWeight::::mutate(|current_weight| { + current_weight.set(prior_block_weight, DispatchClass::Operational); + current_weight + .accrue(base_extrinsic + info.total_weight(), DispatchClass::Operational); + }); + + // Do the bare post dispatch + assert_ok!(WeightReclaim::::bare_post_dispatch( + &info, + &mut post_info.clone(), + len, + &Ok(()) + )); + + // Ensure the accurate refund from benchmark is used + assert_eq!( + crate::ExtrinsicWeightReclaimed::::get(), + post_info.calc_unspent(&info) + ); + assert_eq!( + *BlockWeight::::get().get(DispatchClass::Operational), + post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic + ); + }) + } +} diff --git a/substrate/frame/system/src/extensions/weights.rs b/substrate/frame/system/src/extensions/weights.rs index b3c296899be5..670bb9a0e6fa 100644 --- a/substrate/frame/system/src/extensions/weights.rs +++ b/substrate/frame/system/src/extensions/weights.rs @@ -59,6 +59,7 @@ pub trait WeightInfo { fn check_spec_version() -> Weight; fn check_tx_version() -> Weight; fn check_weight() -> Weight; + fn weight_reclaim() -> Weight; } /// Weights for `frame_system_extensions` using the Substrate node and recommended hardware. @@ -133,6 +134,17 @@ impl WeightInfo for SubstrateWeight { // Minimum execution time: 2_887_000 picoseconds. Weight::from_parts(3_006_000, 0) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1489` + // Minimum execution time: 4_375_000 picoseconds. + Weight::from_parts(4_747_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } // For backwards compatibility and tests. @@ -206,4 +218,15 @@ impl WeightInfo for () { // Minimum execution time: 2_887_000 picoseconds. Weight::from_parts(3_006_000, 0) } + /// Storage: `System::AllExtrinsicsLen` (r:1 w:1) + /// Proof: `System::AllExtrinsicsLen` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + fn weight_reclaim() -> Weight { + // Proof Size summary in bytes: + // Measured: `24` + // Estimated: `1489` + // Minimum execution time: 4_375_000 picoseconds. + Weight::from_parts(4_747_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } } diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 862fb4cf9faf..4fc69c8755f3 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -146,6 +146,10 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_core::storage::well_known_keys; +use sp_runtime::{ + traits::{DispatchInfoOf, PostDispatchInfoOf}, + transaction_validity::TransactionValidityError, +}; use sp_weights::{RuntimeDbWeight, Weight}; #[cfg(any(feature = "std", test))] @@ -170,7 +174,7 @@ pub use extensions::{ check_genesis::CheckGenesis, check_mortality::CheckMortality, check_non_zero_sender::CheckNonZeroSender, check_nonce::CheckNonce, check_spec_version::CheckSpecVersion, check_tx_version::CheckTxVersion, - check_weight::CheckWeight, WeightInfo as ExtensionsWeightInfo, + check_weight::CheckWeight, weight_reclaim::WeightReclaim, WeightInfo as ExtensionsWeightInfo, }; // Backward compatible re-export. pub use extensions::check_mortality::CheckMortality as CheckEra; @@ -1039,6 +1043,17 @@ pub mod pallet { pub(super) type AuthorizedUpgrade = StorageValue<_, CodeUpgradeAuthorization, OptionQuery>; + /// The weight reclaimed for the extrinsic. + /// + /// This information is available until the end of the extrinsic execution. + /// More precisely this information is removed in `note_applied_extrinsic`. + /// + /// Logic doing some post dispatch weight reduction must update this storage to avoid duplicate + /// reduction. + #[pallet::storage] + #[pallet::whitelist_storage] + pub type ExtrinsicWeightReclaimed = StorageValue<_, Weight, ValueQuery>; + #[derive(frame_support::DefaultNoBound)] #[pallet::genesis_config] pub struct GenesisConfig { @@ -2073,10 +2088,23 @@ impl Pallet { }, }); + log::trace!( + target: LOG_TARGET, + "Used block weight: {:?}", + BlockWeight::::get(), + ); + + log::trace!( + target: LOG_TARGET, + "Used block length: {:?}", + Pallet::::all_extrinsics_len(), + ); + let next_extrinsic_index = Self::extrinsic_index().unwrap_or_default() + 1u32; storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &next_extrinsic_index); ExecutionPhase::::put(Phase::ApplyExtrinsic(next_extrinsic_index)); + ExtrinsicWeightReclaimed::::kill(); } /// To be called immediately after `note_applied_extrinsic` of the last extrinsic of the block @@ -2174,6 +2202,32 @@ impl Pallet { } Ok(actual_hash) } + + /// Reclaim the weight for the extrinsic given info and post info. + /// + /// This function will check the already reclaimed weight, and reclaim more if the + /// difference between pre dispatch and post dispatch weight is higher. + pub fn reclaim_weight( + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + ) -> Result<(), TransactionValidityError> + where + T::RuntimeCall: Dispatchable, + { + let already_reclaimed = crate::ExtrinsicWeightReclaimed::::get(); + let unspent = post_info.calc_unspent(info); + let accurate_reclaim = already_reclaimed.max(unspent); + // Saturation never happens, we took the maximum above. + let to_reclaim_more = accurate_reclaim.saturating_sub(already_reclaimed); + if to_reclaim_more != Weight::zero() { + crate::BlockWeight::::mutate(|current_weight| { + current_weight.reduce(to_reclaim_more, info.class); + }); + crate::ExtrinsicWeightReclaimed::::put(accurate_reclaim); + } + + Ok(()) + } } /// Returns a 32 byte datum which is guaranteed to be universally unique. `entropy` is provided diff --git a/substrate/frame/system/src/tests.rs b/substrate/frame/system/src/tests.rs index 6b903f5b7e79..6415380b2848 100644 --- a/substrate/frame/system/src/tests.rs +++ b/substrate/frame/system/src/tests.rs @@ -892,3 +892,67 @@ fn test_default_account_nonce() { assert_eq!(System::account_nonce(&1), 5u64.into()); }); } + +#[test] +fn extrinsic_weight_refunded_is_cleaned() { + new_test_ext().execute_with(|| { + crate::ExtrinsicWeightReclaimed::::put(Weight::from_parts(1, 2)); + assert_eq!(crate::ExtrinsicWeightReclaimed::::get(), Weight::from_parts(1, 2)); + System::note_applied_extrinsic(&Ok(().into()), Default::default()); + assert_eq!(crate::ExtrinsicWeightReclaimed::::get(), Weight::zero()); + + crate::ExtrinsicWeightReclaimed::::put(Weight::from_parts(1, 2)); + assert_eq!(crate::ExtrinsicWeightReclaimed::::get(), Weight::from_parts(1, 2)); + System::note_applied_extrinsic(&Err(DispatchError::BadOrigin.into()), Default::default()); + assert_eq!(crate::ExtrinsicWeightReclaimed::::get(), Weight::zero()); + }); +} + +#[test] +fn reclaim_works() { + new_test_ext().execute_with(|| { + let info = DispatchInfo { call_weight: Weight::from_parts(100, 200), ..Default::default() }; + crate::Pallet::::reclaim_weight( + &info, + &PostDispatchInfo { + actual_weight: Some(Weight::from_parts(50, 100)), + ..Default::default() + }, + ) + .unwrap(); + assert_eq!(crate::ExtrinsicWeightReclaimed::::get(), Weight::from_parts(50, 100)); + + crate::Pallet::::reclaim_weight( + &info, + &PostDispatchInfo { + actual_weight: Some(Weight::from_parts(25, 200)), + ..Default::default() + }, + ) + .unwrap(); + assert_eq!(crate::ExtrinsicWeightReclaimed::::get(), Weight::from_parts(75, 100)); + + crate::Pallet::::reclaim_weight( + &info, + &PostDispatchInfo { + actual_weight: Some(Weight::from_parts(300, 50)), + ..Default::default() + }, + ) + .unwrap(); + assert_eq!(crate::ExtrinsicWeightReclaimed::::get(), Weight::from_parts(75, 150)); + + crate::Pallet::::reclaim_weight( + &info, + &PostDispatchInfo { + actual_weight: Some(Weight::from_parts(300, 300)), + ..Default::default() + }, + ) + .unwrap(); + assert_eq!(crate::ExtrinsicWeightReclaimed::::get(), Weight::from_parts(75, 150)); + + System::note_applied_extrinsic(&Ok(().into()), Default::default()); + assert_eq!(crate::ExtrinsicWeightReclaimed::::get(), Weight::zero()); + }); +} diff --git a/substrate/primitives/runtime/src/generic/checked_extrinsic.rs b/substrate/primitives/runtime/src/generic/checked_extrinsic.rs index 1842b1631621..dec818598472 100644 --- a/substrate/primitives/runtime/src/generic/checked_extrinsic.rs +++ b/substrate/primitives/runtime/src/generic/checked_extrinsic.rs @@ -85,7 +85,6 @@ where match self.format { ExtrinsicFormat::Bare => { let inherent_validation = I::validate_unsigned(source, &self.function)?; - #[allow(deprecated)] let legacy_validation = Extension::bare_validate(&self.function, info, len)?; Ok(legacy_validation.combine_with(inherent_validation)) }, diff --git a/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs b/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs index 27f33acb69cc..4d95e5e6f3a4 100644 --- a/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs +++ b/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs @@ -487,7 +487,7 @@ pub trait TransactionExtension: #[macro_export] macro_rules! impl_tx_ext_default { ($call:ty ; , $( $rest:tt )*) => { - impl_tx_ext_default!{$call ; $( $rest )*} + $crate::impl_tx_ext_default!{$call ; $( $rest )*} }; ($call:ty ; validate $( $rest:tt )*) => { fn validate( @@ -502,7 +502,7 @@ macro_rules! impl_tx_ext_default { ) -> $crate::traits::ValidateResult { Ok((Default::default(), Default::default(), origin)) } - impl_tx_ext_default!{$call ; $( $rest )*} + $crate::impl_tx_ext_default!{$call ; $( $rest )*} }; ($call:ty ; prepare $( $rest:tt )*) => { fn prepare( @@ -515,13 +515,13 @@ macro_rules! impl_tx_ext_default { ) -> Result { Ok(Default::default()) } - impl_tx_ext_default!{$call ; $( $rest )*} + $crate::impl_tx_ext_default!{$call ; $( $rest )*} }; ($call:ty ; weight $( $rest:tt )*) => { fn weight(&self, _call: &$call) -> $crate::Weight { $crate::Weight::zero() } - impl_tx_ext_default!{$call ; $( $rest )*} + $crate::impl_tx_ext_default!{$call ; $( $rest )*} }; ($call:ty ;) => {}; } diff --git a/substrate/test-utils/runtime/src/extrinsic.rs b/substrate/test-utils/runtime/src/extrinsic.rs index 491086bef497..49dc6ba035c9 100644 --- a/substrate/test-utils/runtime/src/extrinsic.rs +++ b/substrate/test-utils/runtime/src/extrinsic.rs @@ -212,6 +212,7 @@ impl ExtrinsicBuilder { self.metadata_hash .map(CheckMetadataHash::new_with_custom_hash) .unwrap_or_else(|| CheckMetadataHash::new(false)), + frame_system::WeightReclaim::new(), ); let raw_payload = SignedPayload::from_raw( self.function.clone(), diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index 666776865316..4d24354f99a7 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -155,6 +155,7 @@ pub type TxExtension = ( (CheckNonce, CheckWeight), CheckSubstrateCall, frame_metadata_hash_extension::CheckMetadataHash, + frame_system::WeightReclaim, ); /// The payload being signed in transactions. pub type SignedPayload = sp_runtime::generic::SignedPayload; diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 72eded5bfd13..972c7500f399 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -118,6 +118,10 @@ type TxExtension = ( // Ensures that the sender has enough funds to pay for the transaction // and deducts the fee from the sender's account. pallet_transaction_payment::ChargeTransactionPayment, + // Reclaim the unused weight from the block using post dispatch information. + // It must be last in the pipeline in order to catch the refund in previous transaction + // extensions + frame_system::WeightReclaim, ); // Composes the runtime by adding all the used pallets and deriving necessary types. diff --git a/templates/parachain/runtime/Cargo.toml b/templates/parachain/runtime/Cargo.toml index 9a0548106ed7..83d7bf4c9b72 100644 --- a/templates/parachain/runtime/Cargo.toml +++ b/templates/parachain/runtime/Cargo.toml @@ -48,11 +48,11 @@ polkadot-sdk = { workspace = true, default-features = false, features = [ "cumulus-pallet-aura-ext", "cumulus-pallet-session-benchmarking", + "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-primitives-aura", "cumulus-primitives-core", - "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-utility", "pallet-collator-selection", "parachains-common", diff --git a/templates/parachain/runtime/src/benchmarks.rs b/templates/parachain/runtime/src/benchmarks.rs index aae50e7258c0..ca9d423bf856 100644 --- a/templates/parachain/runtime/src/benchmarks.rs +++ b/templates/parachain/runtime/src/benchmarks.rs @@ -33,4 +33,5 @@ polkadot_sdk::frame_benchmarking::define_benchmarks!( [pallet_collator_selection, CollatorSelection] [cumulus_pallet_parachain_system, ParachainSystem] [cumulus_pallet_xcmp_queue, XcmpQueue] + [cumulus_pallet_weight_reclaim, WeightReclaim] ); diff --git a/templates/parachain/runtime/src/configs/mod.rs b/templates/parachain/runtime/src/configs/mod.rs index ba4c71c7f218..1e9155f59a57 100644 --- a/templates/parachain/runtime/src/configs/mod.rs +++ b/templates/parachain/runtime/src/configs/mod.rs @@ -129,6 +129,11 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } +/// Configure the palelt weight reclaim tx. +impl cumulus_pallet_weight_reclaim::Config for Runtime { + type WeightInfo = (); +} + impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; diff --git a/templates/parachain/runtime/src/lib.rs b/templates/parachain/runtime/src/lib.rs index 9669237af785..0be27ecce739 100644 --- a/templates/parachain/runtime/src/lib.rs +++ b/templates/parachain/runtime/src/lib.rs @@ -75,18 +75,20 @@ pub type BlockId = generic::BlockId; /// The extension to the basic transaction logic. #[docify::export(template_signed_extra)] -pub type TxExtension = ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim, - frame_metadata_hash_extension::CheckMetadataHash, -); +pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< + Runtime, + ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + frame_metadata_hash_extension::CheckMetadataHash, + ), +>; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = @@ -272,6 +274,8 @@ mod runtime { pub type Timestamp = pallet_timestamp; #[runtime::pallet_index(3)] pub type ParachainInfo = parachain_info; + #[runtime::pallet_index(4)] + pub type WeightReclaim = cumulus_pallet_weight_reclaim; // Monetary stuff. #[runtime::pallet_index(10)] diff --git a/templates/solochain/node/src/benchmarking.rs b/templates/solochain/node/src/benchmarking.rs index 0d60230cd19c..467cad4c0aaa 100644 --- a/templates/solochain/node/src/benchmarking.rs +++ b/templates/solochain/node/src/benchmarking.rs @@ -122,6 +122,7 @@ pub fn create_benchmark_extrinsic( frame_system::CheckWeight::::new(), pallet_transaction_payment::ChargeTransactionPayment::::from(0), frame_metadata_hash_extension::CheckMetadataHash::::new(false), + frame_system::WeightReclaim::::new(), ); let raw_payload = runtime::SignedPayload::from_raw( @@ -137,6 +138,7 @@ pub fn create_benchmark_extrinsic( (), (), None, + (), ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); diff --git a/templates/solochain/runtime/src/lib.rs b/templates/solochain/runtime/src/lib.rs index ae0ea16ae42e..6a2149ec8b63 100644 --- a/templates/solochain/runtime/src/lib.rs +++ b/templates/solochain/runtime/src/lib.rs @@ -157,6 +157,7 @@ pub type TxExtension = ( frame_system::CheckWeight, pallet_transaction_payment::ChargeTransactionPayment, frame_metadata_hash_extension::CheckMetadataHash, + frame_system::WeightReclaim, ); /// Unchecked extrinsic type as expected by this runtime. diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index d2a47ade7f87..17a7c02e8259 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -29,6 +29,7 @@ std = [ "cumulus-pallet-parachain-system?/std", "cumulus-pallet-session-benchmarking?/std", "cumulus-pallet-solo-to-para?/std", + "cumulus-pallet-weight-reclaim?/std", "cumulus-pallet-xcm?/std", "cumulus-pallet-xcmp-queue?/std", "cumulus-ping?/std", @@ -239,6 +240,7 @@ runtime-benchmarks = [ "cumulus-pallet-dmp-queue?/runtime-benchmarks", "cumulus-pallet-parachain-system?/runtime-benchmarks", "cumulus-pallet-session-benchmarking?/runtime-benchmarks", + "cumulus-pallet-weight-reclaim?/runtime-benchmarks", "cumulus-pallet-xcmp-queue?/runtime-benchmarks", "cumulus-primitives-core?/runtime-benchmarks", "cumulus-primitives-utility?/runtime-benchmarks", @@ -369,6 +371,7 @@ try-runtime = [ "cumulus-pallet-dmp-queue?/try-runtime", "cumulus-pallet-parachain-system?/try-runtime", "cumulus-pallet-solo-to-para?/try-runtime", + "cumulus-pallet-weight-reclaim?/try-runtime", "cumulus-pallet-xcm?/try-runtime", "cumulus-pallet-xcmp-queue?/try-runtime", "cumulus-ping?/try-runtime", @@ -540,7 +543,7 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] -runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", @@ -722,6 +725,11 @@ default-features = false optional = true path = "../cumulus/pallets/solo-to-para" +[dependencies.cumulus-pallet-weight-reclaim] +default-features = false +optional = true +path = "../cumulus/pallets/weight-reclaim" + [dependencies.cumulus-pallet-xcm] default-features = false optional = true diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 7b3c869588f0..3504f081f295 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -141,6 +141,10 @@ pub use cumulus_pallet_session_benchmarking; #[cfg(feature = "cumulus-pallet-solo-to-para")] pub use cumulus_pallet_solo_to_para; +/// pallet and transaction extensions for accurate proof size reclaim. +#[cfg(feature = "cumulus-pallet-weight-reclaim")] +pub use cumulus_pallet_weight_reclaim; + /// Pallet for stuff specific to parachains' usage of XCM. #[cfg(feature = "cumulus-pallet-xcm")] pub use cumulus_pallet_xcm; From 8d2130cce6701195d7c1e97947f49dc3963ea996 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Mon, 6 Jan 2025 17:19:29 +0900 Subject: [PATCH 225/340] Print taplo version in CI (#7041) I can't find taplo version in the log, and current version is incompatible with latest version. --- .github/workflows/reusable-preflight.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/reusable-preflight.yml b/.github/workflows/reusable-preflight.yml index e1799adddcaf..8487ab107d7c 100644 --- a/.github/workflows/reusable-preflight.yml +++ b/.github/workflows/reusable-preflight.yml @@ -203,6 +203,7 @@ jobs: echo $( substrate-contracts-node --version | awk 'NF' ) estuary --version cargo-contract --version + taplo --version - name: Info forklift run: forklift version From 6eca7647dc99dd0e78aacb740ba931e99e6ba71f Mon Sep 17 00:00:00 2001 From: taozui472 Date: Mon, 6 Jan 2025 16:44:06 +0800 Subject: [PATCH 226/340] chore: delete repeat words (#7034) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dónal Murray --- polkadot/primitives/src/v8/mod.rs | 2 +- polkadot/roadmap/implementers-guide/src/architecture.md | 2 +- polkadot/roadmap/implementers-guide/src/protocol-approval.md | 2 +- .../implementers-guide/src/protocol-validator-disabling.md | 2 +- polkadot/roadmap/implementers-guide/src/runtime/session_info.md | 2 +- substrate/client/network/README.md | 2 +- substrate/frame/democracy/README.md | 2 +- substrate/frame/democracy/src/lib.rs | 2 +- substrate/frame/recovery/README.md | 2 +- substrate/frame/recovery/src/lib.rs | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/polkadot/primitives/src/v8/mod.rs b/polkadot/primitives/src/v8/mod.rs index fdcb9fe8fb7e..7fc4c5b5c3f1 100644 --- a/polkadot/primitives/src/v8/mod.rs +++ b/polkadot/primitives/src/v8/mod.rs @@ -1900,7 +1900,7 @@ pub struct SessionInfo { /// participating in parachain consensus. See /// [`max_validators`](https://github.com/paritytech/polkadot/blob/a52dca2be7840b23c19c153cf7e110b1e3e475f8/runtime/parachains/src/configuration.rs#L148). /// - /// `SessionInfo::validators` will be limited to to `max_validators` when set. + /// `SessionInfo::validators` will be limited to `max_validators` when set. pub validators: IndexedVec, /// Validators' authority discovery keys for the session in canonical ordering. /// diff --git a/polkadot/roadmap/implementers-guide/src/architecture.md b/polkadot/roadmap/implementers-guide/src/architecture.md index b75270662005..e2be92e4cddb 100644 --- a/polkadot/roadmap/implementers-guide/src/architecture.md +++ b/polkadot/roadmap/implementers-guide/src/architecture.md @@ -93,7 +93,7 @@ Runtime logic is divided up into Modules and APIs. Modules encapsulate particula consist of storage, routines, and entry-points. Routines are invoked by entry points, by other modules, upon block initialization or closing. Routines can read and alter the storage of the module. Entry-points are the means by which new information is introduced to a module and can limit the origins (user, root, parachain) that they accept being -called by. Each block in the blockchain contains a set of Extrinsics. Each extrinsic targets a a specific entry point to +called by. Each block in the blockchain contains a set of Extrinsics. Each extrinsic targets a specific entry point to trigger and which data should be passed to it. Runtime APIs provide a means for Node-side behavior to extract meaningful information from the state of a single fork. diff --git a/polkadot/roadmap/implementers-guide/src/protocol-approval.md b/polkadot/roadmap/implementers-guide/src/protocol-approval.md index b6aa16646ad2..25d4fa5dadae 100644 --- a/polkadot/roadmap/implementers-guide/src/protocol-approval.md +++ b/polkadot/roadmap/implementers-guide/src/protocol-approval.md @@ -84,7 +84,7 @@ slashing risk for validator operators. In future, we shall determine which among the several hardening techniques best benefits the network as a whole. We could provide a multi-process multi-machine architecture for validators, perhaps even reminiscent of GNUNet, or perhaps -more resembling smart HSM tooling. We might instead design a system that more resembled full systems, like like Cosmos' +more resembling smart HSM tooling. We might instead design a system that more resembled full systems, like Cosmos' sentry nodes. In either case, approval assignments might be handled by a slightly hardened machine, but not necessarily nearly as hardened as approval votes, but approval votes machines must similarly run foreign WASM code, which increases their risk, so assignments being separate sounds helpful. diff --git a/polkadot/roadmap/implementers-guide/src/protocol-validator-disabling.md b/polkadot/roadmap/implementers-guide/src/protocol-validator-disabling.md index 9fd44c00fa0a..c2861b4035e7 100644 --- a/polkadot/roadmap/implementers-guide/src/protocol-validator-disabling.md +++ b/polkadot/roadmap/implementers-guide/src/protocol-validator-disabling.md @@ -111,7 +111,7 @@ checking (% for 30-ish malicious in a row). There are also censorship or liveness issues if backing is suddenly dominated by malicious nodes but in general even if some honest blocks get backed liveness should be preserved. -> **Note:** It is worth noting that is is fundamentally a defense in depth strategy because if we assume disputes are +> **Note:** It is worth noting that is fundamentally a defense in depth strategy because if we assume disputes are > perfect it should not be a real concern. In reality disputes and determinism are difficult to get right, and > non-determinism and happen so defense in depth is crucial when handling those subsystems. diff --git a/polkadot/roadmap/implementers-guide/src/runtime/session_info.md b/polkadot/roadmap/implementers-guide/src/runtime/session_info.md index fa7f55c4f0b4..daf7e5c7fd80 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/session_info.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/session_info.md @@ -16,7 +16,7 @@ struct SessionInfo { /// in parachain consensus. See /// [`max_validators`](https://github.com/paritytech/polkadot/blob/a52dca2be7840b23c19c153cf7e110b1e3e475f8/runtime/parachains/src/configuration.rs#L148). /// - /// `SessionInfo::validators` will be limited to to `max_validators` when set. + /// `SessionInfo::validators` will be limited to `max_validators` when set. validators: Vec, /// Validators' authority discovery keys for the session in canonical ordering. /// diff --git a/substrate/client/network/README.md b/substrate/client/network/README.md index f4031fbd3085..9903109d847c 100644 --- a/substrate/client/network/README.md +++ b/substrate/client/network/README.md @@ -261,7 +261,7 @@ data. I.e. it is unable to serve bock bodies and headers other than the most rec nodes have block history available, a background sync process is started that downloads all the missing blocks. It is run in parallel with the keep-up sync and does not interfere with downloading of the recent blocks. During this download we also import GRANDPA justifications for blocks with authority set changes, so that -the warp-synced node has all the data to serve for other nodes nodes that might want to sync from it with +the warp-synced node has all the data to serve for other nodes that might want to sync from it with any method. # Usage diff --git a/substrate/frame/democracy/README.md b/substrate/frame/democracy/README.md index bbc5f1c65586..d9d21e624477 100644 --- a/substrate/frame/democracy/README.md +++ b/substrate/frame/democracy/README.md @@ -96,7 +96,7 @@ This call can only be made by the `CancellationOrigin`. This call can only be made by the `ExternalOrigin`. -- `external_propose` - Schedules a proposal to become a referendum once it is is legal +- `external_propose` - Schedules a proposal to become a referendum once it is legal for an externally proposed referendum. #### External Majority Origin diff --git a/substrate/frame/democracy/src/lib.rs b/substrate/frame/democracy/src/lib.rs index 27bc36a756e4..2c662fbad26a 100644 --- a/substrate/frame/democracy/src/lib.rs +++ b/substrate/frame/democracy/src/lib.rs @@ -113,7 +113,7 @@ //! //! This call can only be made by the `ExternalOrigin`. //! -//! - `external_propose` - Schedules a proposal to become a referendum once it is is legal for an +//! - `external_propose` - Schedules a proposal to become a referendum once it is legal for an //! externally proposed referendum. //! //! #### External Majority Origin diff --git a/substrate/frame/recovery/README.md b/substrate/frame/recovery/README.md index 7e2dd7a23619..fdaef5784fdb 100644 --- a/substrate/frame/recovery/README.md +++ b/substrate/frame/recovery/README.md @@ -67,7 +67,7 @@ permissionless. However, the recovery deposit is an economic deterrent that should disincentivize would-be attackers from trying to maliciously recover accounts. -The recovery deposit can always be claimed by the account which is trying to +The recovery deposit can always be claimed by the account which is trying to be recovered. In the case of a malicious recovery attempt, the account owner who still has access to their account can claim the deposit and essentially punish the malicious user. diff --git a/substrate/frame/recovery/src/lib.rs b/substrate/frame/recovery/src/lib.rs index d8f3c33fbea9..4de1919cdc33 100644 --- a/substrate/frame/recovery/src/lib.rs +++ b/substrate/frame/recovery/src/lib.rs @@ -80,7 +80,7 @@ //! should disincentivize would-be attackers from trying to maliciously recover //! accounts. //! -//! The recovery deposit can always be claimed by the account which is trying to +//! The recovery deposit can always be claimed by the account which is trying //! to be recovered. In the case of a malicious recovery attempt, the account //! owner who still has access to their account can claim the deposit and //! essentially punish the malicious user. From ffa90d0f2b9b4438e2f0fa3d4d532923d7ba978f Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Mon, 6 Jan 2025 11:57:29 +0200 Subject: [PATCH 227/340] fix chunk fetching network compatibility zombienet test (#6988) Fix this zombienet test It was failing because in https://github.com/paritytech/polkadot-sdk/pull/6452 I enabled the v2 receipts for testnet genesis, so the collators started sending v2 receipts with zeroed collator signatures to old validators that were still checking those signatures (which lead to disputes, since new validators considered the candidates valid). The fix is to also use an old image for collators, so that we don't create v2 receipts. We cannot remove this test yet because collators also perform chunk recovery, so until all collators are upgraded, we need to maintain this compatibility with the old protocol version (which is also why systematic recovery was not yet enabled) --- .../0014-chunk-fetching-network-compatibility.toml | 3 ++- prdoc/pr_6988.prdoc | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_6988.prdoc diff --git a/polkadot/zombienet_tests/functional/0014-chunk-fetching-network-compatibility.toml b/polkadot/zombienet_tests/functional/0014-chunk-fetching-network-compatibility.toml index 881abab64fd0..874b8a09bb24 100644 --- a/polkadot/zombienet_tests/functional/0014-chunk-fetching-network-compatibility.toml +++ b/polkadot/zombienet_tests/functional/0014-chunk-fetching-network-compatibility.toml @@ -42,7 +42,8 @@ chain = "glutton-westend-local-{{id}}" [parachains.collator] name = "collator" - image = "{{CUMULUS_IMAGE}}" + # Use an old image that does not send out v2 receipts, as the old validators will still check the collator signatures. + image = "docker.io/paritypr/polkadot-parachain-debug:master-bde0bbe5" args = ["-lparachain=debug"] {% endfor %} diff --git a/prdoc/pr_6988.prdoc b/prdoc/pr_6988.prdoc new file mode 100644 index 000000000000..18f70f9fd97f --- /dev/null +++ b/prdoc/pr_6988.prdoc @@ -0,0 +1,5 @@ +doc: [] + +crates: + - name: polkadot + bump: none \ No newline at end of file From 1dcff3df39b85fa43c7ca1dafe10f802cd812234 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Mon, 6 Jan 2025 14:09:06 +0100 Subject: [PATCH 228/340] Avoid incomplete block import pipeline with full verifying import queue (#7050) ## Problem In the parachain template we use the [fully verifying import queue ](https://github.com/paritytech/polkadot-sdk/blob/3d9eddbeb262277c79f2b93b9efb5af95a3a35a8/cumulus/client/consensus/aura/src/equivocation_import_queue.rs#L224-L224) which does extra equivocation checks. However, when we import a warp synced block with state, we don't set a fork choice, leading to an incomplete block import pipeline and error here: https://github.com/paritytech/polkadot-sdk/blob/3d9eddbeb262277c79f2b93b9efb5af95a3a35a8/substrate/client/service/src/client/client.rs#L488-L488 This renders warp sync useless for chains using this import queue. ## Fix The fix is to always import a block with state as best block, as we already do in the normal Aura Verifier. In a follow up we should also take another look into unifying the usage of the different import queues. fixes https://github.com/paritytech/project-mythical/issues/256 --------- Co-authored-by: command-bot <> --- .../consensus/aura/src/equivocation_import_queue.rs | 1 + prdoc/pr_7050.prdoc | 11 +++++++++++ 2 files changed, 12 insertions(+) create mode 100644 prdoc/pr_7050.prdoc diff --git a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs index 68f2d37c8748..dbd9d5ba6a6f 100644 --- a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs +++ b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs @@ -97,6 +97,7 @@ where // This is done for example when gap syncing and it is expected that the block after the gap // was checked/chosen properly, e.g. by warp syncing to this block using a finality proof. if block_params.state_action.skip_execution_checks() || block_params.with_state() { + block_params.fork_choice = Some(ForkChoiceStrategy::Custom(block_params.with_state())); return Ok(block_params) } diff --git a/prdoc/pr_7050.prdoc b/prdoc/pr_7050.prdoc new file mode 100644 index 000000000000..da9dd808033d --- /dev/null +++ b/prdoc/pr_7050.prdoc @@ -0,0 +1,11 @@ +title: Avoid incomplete block import pipeline with full verifying import queue +doc: +- audience: Node Dev + description: |- + When warp syncing a node using the equivocation checking verifier, we now properly set the fork_choice rule. + Affected are mostly nodes that are derived from the parachain template. Omni-node is not affected. + + The prevents the error `ClientImport("Incomplete block import pipeline.")` after state sync. +crates: +- name: cumulus-client-consensus-aura + bump: patch From 568231a9a85d94954c002532a0f4351a3bb59e83 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 6 Jan 2025 14:52:07 +0100 Subject: [PATCH 229/340] [core-fellowship] Add permissionless import_member (#7030) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Add call `import_member` to the core-fellowship pallet. - Move common logic between `import` and `import_member` into `do_import`. ## `import_member` Can be used to induct an arbitrary collective member and is callable by any signed origin. Pays no fees upon success. This is useful in the case that members did not induct themselves and are idling on their rank. --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> --- .../pallet_core_fellowship_ambassador_core.rs | 160 +++++++++------ .../pallet_core_fellowship_fellowship_core.rs | 183 +++++++++++------- prdoc/pr_7030.prdoc | 24 +++ .../frame/core-fellowship/src/benchmarking.rs | 18 ++ substrate/frame/core-fellowship/src/lib.rs | 67 +++++-- .../core-fellowship/src/tests/integration.rs | 38 +++- .../frame/core-fellowship/src/tests/unit.rs | 62 ++++++ .../frame/core-fellowship/src/weights.rs | 71 ++++--- 8 files changed, 448 insertions(+), 175 deletions(-) create mode 100644 prdoc/pr_7030.prdoc diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs index 6bedfcc7e012..4d092ec80313 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_ambassador_core.rs @@ -1,4 +1,4 @@ -// Copyright Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Cumulus. // Cumulus is free software: you can redistribute it and/or modify @@ -16,25 +16,29 @@ //! Autogenerated weights for `pallet_core_fellowship` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-08-11, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cob`, CPU: `` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `623e9e4b814e`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: -// target/release/polkadot-parachain +// frame-omni-bencher +// v1 // benchmark // pallet -// --chain=collectives-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_core_fellowship // --extrinsic=* -// --steps=2 -// --repeat=2 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/src/weights/ +// --runtime=target/production/wbuild/collectives-westend-runtime/collectives_westend_runtime.wasm +// --pallet=pallet_core_fellowship +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -48,25 +52,26 @@ use core::marker::PhantomData; pub struct WeightInfo(PhantomData); impl pallet_core_fellowship::WeightInfo for WeightInfo { /// Storage: `AmbassadorCore::Params` (r:0 w:1) - /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) fn set_params() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_000_000 picoseconds. - Weight::from_parts(11_000_000, 0) + // Minimum execution time: 9_131_000 picoseconds. + Weight::from_parts(9_371_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `AmbassadorCore::Params` (r:0 w:1) - /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCore::Params` (r:1 w:1) + /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) fn set_partial_params() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 11_000_000 picoseconds. - Weight::from_parts(11_000_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `471` + // Estimated: `1853` + // Minimum execution time: 18_375_000 picoseconds. + Weight::from_parts(18_872_000, 0) + .saturating_add(Weight::from_parts(0, 1853)) + .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `AmbassadorCore::Member` (r:1 w:1) @@ -74,44 +79,48 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< /// Storage: `AmbassadorCollective::Members` (r:1 w:1) /// Proof: `AmbassadorCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::Params` (r:1 w:0) - /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCollective::MemberCount` (r:1 w:1) /// Proof: `AmbassadorCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) - /// Storage: `AmbassadorCollective::IdToIndex` (r:1 w:0) + /// Storage: `AmbassadorCollective::IdToIndex` (r:1 w:1) /// Proof: `AmbassadorCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::MemberEvidence` (r:1 w:1) /// Proof: `AmbassadorCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::IndexToId` (r:0 w:1) + /// Proof: `AmbassadorCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn bump_offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `66011` + // Measured: `66402` // Estimated: `69046` - // Minimum execution time: 96_000_000 picoseconds. - Weight::from_parts(111_000_000, 0) + // Minimum execution time: 156_752_000 picoseconds. + Weight::from_parts(164_242_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `AmbassadorCore::Member` (r:1 w:1) /// Proof: `AmbassadorCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCollective::Members` (r:1 w:1) /// Proof: `AmbassadorCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::Params` (r:1 w:0) - /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCollective::MemberCount` (r:1 w:1) /// Proof: `AmbassadorCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) - /// Storage: `AmbassadorCollective::IdToIndex` (r:1 w:0) + /// Storage: `AmbassadorCollective::IdToIndex` (r:1 w:1) /// Proof: `AmbassadorCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::MemberEvidence` (r:1 w:1) /// Proof: `AmbassadorCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::IndexToId` (r:0 w:1) + /// Proof: `AmbassadorCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn bump_demote() -> Weight { // Proof Size summary in bytes: - // Measured: `66121` + // Measured: `66512` // Estimated: `69046` - // Minimum execution time: 99_000_000 picoseconds. - Weight::from_parts(116_000_000, 0) + // Minimum execution time: 158_877_000 picoseconds. + Weight::from_parts(165_228_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `AmbassadorCollective::Members` (r:1 w:0) /// Proof: `AmbassadorCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -121,8 +130,8 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `360` // Estimated: `3514` - // Minimum execution time: 21_000_000 picoseconds. - Weight::from_parts(22_000_000, 0) + // Minimum execution time: 25_056_000 picoseconds. + Weight::from_parts(26_028_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -141,8 +150,8 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `118` // Estimated: `3514` - // Minimum execution time: 36_000_000 picoseconds. - Weight::from_parts(36_000_000, 0) + // Minimum execution time: 34_784_000 picoseconds. + Weight::from_parts(35_970_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(5)) @@ -152,7 +161,7 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< /// Storage: `AmbassadorCore::Member` (r:1 w:1) /// Proof: `AmbassadorCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::Params` (r:1 w:0) - /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `AmbassadorCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCollective::MemberCount` (r:1 w:1) /// Proof: `AmbassadorCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) /// Storage: `AmbassadorCore::MemberEvidence` (r:1 w:1) @@ -163,25 +172,40 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< /// Proof: `AmbassadorCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn promote() -> Weight { // Proof Size summary in bytes: - // Measured: `65989` + // Measured: `66055` // Estimated: `69046` - // Minimum execution time: 95_000_000 picoseconds. - Weight::from_parts(110_000_000, 0) + // Minimum execution time: 147_616_000 picoseconds. + Weight::from_parts(154_534_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(6)) } + /// Storage: `AmbassadorCollective::Members` (r:1 w:1) + /// Proof: `AmbassadorCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCore::Member` (r:1 w:1) + /// Proof: `AmbassadorCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::MemberCount` (r:9 w:9) + /// Proof: `AmbassadorCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCore::MemberEvidence` (r:1 w:1) + /// Proof: `AmbassadorCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::IndexToId` (r:0 w:9) + /// Proof: `AmbassadorCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::IdToIndex` (r:0 w:9) + /// Proof: `AmbassadorCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) + /// The range of component `r` is `[1, 9]`. + /// The range of component `r` is `[1, 9]`. fn promote_fast(r: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `16844` - // Estimated: `19894 + r * (2489 ±0)` - // Minimum execution time: 45_065_000 picoseconds. - Weight::from_parts(34_090_392, 19894) - // Standard Error: 18_620 - .saturating_add(Weight::from_parts(13_578_046, 0).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Measured: `65968` + // Estimated: `69046 + r * (2489 ±0)` + // Minimum execution time: 138_323_000 picoseconds. + Weight::from_parts(125_497_264, 0) + .saturating_add(Weight::from_parts(0, 69046)) + // Standard Error: 56_050 + .saturating_add(Weight::from_parts(19_863_853, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2489).saturating_mul(r.into())) } @@ -193,10 +217,10 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< /// Proof: `AmbassadorCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) fn offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `331` + // Measured: `265` // Estimated: `3514` - // Minimum execution time: 21_000_000 picoseconds. - Weight::from_parts(22_000_000, 0) + // Minimum execution time: 26_903_000 picoseconds. + Weight::from_parts(27_645_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -209,8 +233,22 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `285` // Estimated: `3514` - // Minimum execution time: 20_000_000 picoseconds. - Weight::from_parts(21_000_000, 0) + // Minimum execution time: 23_286_000 picoseconds. + Weight::from_parts(23_848_000, 0) + .saturating_add(Weight::from_parts(0, 3514)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AmbassadorCore::Member` (r:1 w:1) + /// Proof: `AmbassadorCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `AmbassadorCollective::Members` (r:1 w:0) + /// Proof: `AmbassadorCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) + fn import_member() -> Weight { + // Proof Size summary in bytes: + // Measured: `285` + // Estimated: `3514` + // Minimum execution time: 23_239_000 picoseconds. + Weight::from_parts(23_684_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -225,8 +263,8 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `65967` // Estimated: `69046` - // Minimum execution time: 78_000_000 picoseconds. - Weight::from_parts(104_000_000, 0) + // Minimum execution time: 125_987_000 picoseconds. + Weight::from_parts(130_625_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) @@ -239,8 +277,8 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `151` // Estimated: `69046` - // Minimum execution time: 43_000_000 picoseconds. - Weight::from_parts(44_000_000, 0) + // Minimum execution time: 104_431_000 picoseconds. + Weight::from_parts(106_646_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs index 05014e273f00..acb1f82985db 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_core_fellowship_fellowship_core.rs @@ -1,39 +1,44 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_core_fellowship` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-08-11, STEPS: `2`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `cob`, CPU: `` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `623e9e4b814e`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: -// target/release/polkadot-parachain +// frame-omni-bencher +// v1 // benchmark // pallet -// --chain=collectives-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_core_fellowship // --extrinsic=* -// --steps=2 -// --repeat=2 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/src/weights/ +// --runtime=target/production/wbuild/collectives-westend-runtime/collectives_westend_runtime.wasm +// --pallet=pallet_core_fellowship +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -47,25 +52,26 @@ use core::marker::PhantomData; pub struct WeightInfo(PhantomData); impl pallet_core_fellowship::WeightInfo for WeightInfo { /// Storage: `FellowshipCore::Params` (r:0 w:1) - /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) fn set_params() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_000_000 picoseconds. - Weight::from_parts(12_000_000, 0) + // Minimum execution time: 9_115_000 picoseconds. + Weight::from_parts(9_523_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `FellowshipCore::Params` (r:0 w:1) - /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCore::Params` (r:1 w:1) + /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) fn set_partial_params() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 11_000_000 picoseconds. - Weight::from_parts(12_000_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `504` + // Estimated: `1853` + // Minimum execution time: 18_294_000 picoseconds. + Weight::from_parts(18_942_000, 0) + .saturating_add(Weight::from_parts(0, 1853)) + .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `FellowshipCore::Member` (r:1 w:1) @@ -73,44 +79,48 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< /// Storage: `FellowshipCollective::Members` (r:1 w:1) /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::Params` (r:1 w:0) - /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `FellowshipCollective::MemberCount` (r:1 w:1) /// Proof: `FellowshipCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) - /// Storage: `FellowshipCollective::IdToIndex` (r:1 w:0) + /// Storage: `FellowshipCollective::IdToIndex` (r:1 w:1) /// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::MemberEvidence` (r:1 w:1) /// Proof: `FellowshipCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::IndexToId` (r:0 w:1) + /// Proof: `FellowshipCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn bump_offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `66144` + // Measured: `66535` // Estimated: `69046` - // Minimum execution time: 109_000_000 picoseconds. - Weight::from_parts(125_000_000, 0) + // Minimum execution time: 152_823_000 picoseconds. + Weight::from_parts(158_737_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `FellowshipCore::Member` (r:1 w:1) /// Proof: `FellowshipCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) /// Storage: `FellowshipCollective::Members` (r:1 w:1) /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::Params` (r:1 w:0) - /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `FellowshipCollective::MemberCount` (r:1 w:1) /// Proof: `FellowshipCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) - /// Storage: `FellowshipCollective::IdToIndex` (r:1 w:0) + /// Storage: `FellowshipCollective::IdToIndex` (r:1 w:1) /// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::MemberEvidence` (r:1 w:1) /// Proof: `FellowshipCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::IndexToId` (r:0 w:1) + /// Proof: `FellowshipCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn bump_demote() -> Weight { // Proof Size summary in bytes: - // Measured: `66254` + // Measured: `66645` // Estimated: `69046` - // Minimum execution time: 112_000_000 picoseconds. - Weight::from_parts(114_000_000, 0) + // Minimum execution time: 157_605_000 picoseconds. + Weight::from_parts(162_341_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `FellowshipCollective::Members` (r:1 w:0) /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -120,8 +130,8 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `493` // Estimated: `3514` - // Minimum execution time: 22_000_000 picoseconds. - Weight::from_parts(27_000_000, 0) + // Minimum execution time: 25_194_000 picoseconds. + Weight::from_parts(26_262_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -140,8 +150,8 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `251` // Estimated: `3514` - // Minimum execution time: 35_000_000 picoseconds. - Weight::from_parts(36_000_000, 0) + // Minimum execution time: 35_479_000 picoseconds. + Weight::from_parts(36_360_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(5)) @@ -151,7 +161,7 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< /// Storage: `FellowshipCore::Member` (r:1 w:1) /// Proof: `FellowshipCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::Params` (r:1 w:0) - /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(364), added: 859, mode: `MaxEncodedLen`) + /// Proof: `FellowshipCore::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) /// Storage: `FellowshipCollective::MemberCount` (r:1 w:1) /// Proof: `FellowshipCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) /// Storage: `FellowshipCore::MemberEvidence` (r:1 w:1) @@ -162,25 +172,40 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< /// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) fn promote() -> Weight { // Proof Size summary in bytes: - // Measured: `66122` + // Measured: `66188` // Estimated: `69046` - // Minimum execution time: 97_000_000 picoseconds. - Weight::from_parts(129_000_000, 0) + // Minimum execution time: 147_993_000 picoseconds. + Weight::from_parts(153_943_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(6)) } + /// Storage: `FellowshipCollective::Members` (r:1 w:1) + /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCore::Member` (r:1 w:1) + /// Proof: `FellowshipCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::MemberCount` (r:9 w:9) + /// Proof: `FellowshipCollective::MemberCount` (`max_values`: None, `max_size`: Some(14), added: 2489, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCore::MemberEvidence` (r:1 w:1) + /// Proof: `FellowshipCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::IndexToId` (r:0 w:9) + /// Proof: `FellowshipCollective::IndexToId` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::IdToIndex` (r:0 w:9) + /// Proof: `FellowshipCollective::IdToIndex` (`max_values`: None, `max_size`: Some(54), added: 2529, mode: `MaxEncodedLen`) + /// The range of component `r` is `[1, 9]`. + /// The range of component `r` is `[1, 9]`. fn promote_fast(r: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `16844` - // Estimated: `19894 + r * (2489 ±0)` - // Minimum execution time: 45_065_000 picoseconds. - Weight::from_parts(34_090_392, 19894) - // Standard Error: 18_620 - .saturating_add(Weight::from_parts(13_578_046, 0).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) + // Measured: `66101` + // Estimated: `69046 + r * (2489 ±0)` + // Minimum execution time: 138_444_000 picoseconds. + Weight::from_parts(125_440_035, 0) + .saturating_add(Weight::from_parts(0, 69046)) + // Standard Error: 55_452 + .saturating_add(Weight::from_parts(19_946_954, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes(3)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2489).saturating_mul(r.into())) } @@ -192,10 +217,10 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< /// Proof: `FellowshipCore::MemberEvidence` (`max_values`: None, `max_size`: Some(65581), added: 68056, mode: `MaxEncodedLen`) fn offboard() -> Weight { // Proof Size summary in bytes: - // Measured: `464` + // Measured: `398` // Estimated: `3514` - // Minimum execution time: 22_000_000 picoseconds. - Weight::from_parts(22_000_000, 0) + // Minimum execution time: 27_392_000 picoseconds. + Weight::from_parts(28_134_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -208,8 +233,22 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `418` // Estimated: `3514` - // Minimum execution time: 20_000_000 picoseconds. - Weight::from_parts(24_000_000, 0) + // Minimum execution time: 23_523_000 picoseconds. + Weight::from_parts(24_046_000, 0) + .saturating_add(Weight::from_parts(0, 3514)) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `FellowshipCore::Member` (r:1 w:1) + /// Proof: `FellowshipCore::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) + /// Storage: `FellowshipCollective::Members` (r:1 w:0) + /// Proof: `FellowshipCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) + fn import_member() -> Weight { + // Proof Size summary in bytes: + // Measured: `418` + // Estimated: `3514` + // Minimum execution time: 23_369_000 picoseconds. + Weight::from_parts(24_088_000, 0) .saturating_add(Weight::from_parts(0, 3514)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -224,8 +263,8 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `66100` // Estimated: `69046` - // Minimum execution time: 89_000_000 picoseconds. - Weight::from_parts(119_000_000, 0) + // Minimum execution time: 127_137_000 picoseconds. + Weight::from_parts(131_638_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) @@ -238,8 +277,8 @@ impl pallet_core_fellowship::WeightInfo for WeightInfo< // Proof Size summary in bytes: // Measured: `184` // Estimated: `69046` - // Minimum execution time: 43_000_000 picoseconds. - Weight::from_parts(52_000_000, 0) + // Minimum execution time: 103_212_000 picoseconds. + Weight::from_parts(105_488_000, 0) .saturating_add(Weight::from_parts(0, 69046)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) diff --git a/prdoc/pr_7030.prdoc b/prdoc/pr_7030.prdoc new file mode 100644 index 000000000000..3b1f7be558d8 --- /dev/null +++ b/prdoc/pr_7030.prdoc @@ -0,0 +1,24 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "[core-fellowship] Add permissionless import_member" + +doc: + - audience: [Runtime Dev, Runtime User] + description: | + Changes: + - Add call `import_member` to the core-fellowship pallet. + - Move common logic between `import` and `import_member` into `do_import`. + + This is a minor change as to not impact UI and downstream integration. + + ## `import_member` + + Can be used to induct an arbitrary collective member and is callable by any signed origin. Pays no fees upon success. + This is useful in the case that members did not induct themselves and are idling on their rank. + +crates: +- name: pallet-core-fellowship + bump: major +- name: collectives-westend-runtime + bump: patch diff --git a/substrate/frame/core-fellowship/src/benchmarking.rs b/substrate/frame/core-fellowship/src/benchmarking.rs index adb8a4a091b8..ac0d489953c1 100644 --- a/substrate/frame/core-fellowship/src/benchmarking.rs +++ b/substrate/frame/core-fellowship/src/benchmarking.rs @@ -50,6 +50,7 @@ mod benchmarks { for _ in 0..rank { T::Members::promote(&member)?; } + #[allow(deprecated)] CoreFellowship::::import(RawOrigin::Signed(member.clone()).into())?; Ok(member) } @@ -260,6 +261,23 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn import_member() -> Result<(), BenchmarkError> { + let member = account("member", 0, SEED); + let sender = account("sender", 0, SEED); + + T::Members::induct(&member)?; + T::Members::promote(&member)?; + + assert!(!Member::::contains_key(&member)); + + #[extrinsic_call] + _(RawOrigin::Signed(sender), member.clone()); + + assert!(Member::::contains_key(&member)); + Ok(()) + } + #[benchmark] fn approve() -> Result<(), BenchmarkError> { let member = make_member::(1)?; diff --git a/substrate/frame/core-fellowship/src/lib.rs b/substrate/frame/core-fellowship/src/lib.rs index c61447e36280..22ba63b26161 100644 --- a/substrate/frame/core-fellowship/src/lib.rs +++ b/substrate/frame/core-fellowship/src/lib.rs @@ -21,6 +21,7 @@ //! This only handles members of non-zero rank. //! //! # Process Flow +//! //! - Begin with a call to `induct`, where some privileged origin (perhaps a pre-existing member of //! `rank > 1`) is able to make a candidate from an account and introduce it to be tracked in this //! pallet in order to allow evidence to be submitted and promotion voted on. @@ -36,8 +37,9 @@ //! `bump` to demote the candidate by one rank. //! - If a candidate fails to be promoted to a member within the `offboard_timeout` period, then //! anyone may call `bump` to remove the account's candidacy. -//! - Pre-existing members may call `import` to have their rank recognised and be inducted into this -//! pallet (to gain a salary and allow for eventual promotion). +//! - Pre-existing members may call `import_member` on themselves (formerly `import`) to have their +//! rank recognised and be inducted into this pallet (to gain a salary and allow for eventual +//! promotion). //! - If, externally to this pallet, a member or candidate has their rank removed completely, then //! `offboard` may be called to remove them entirely from this pallet. //! @@ -585,28 +587,44 @@ pub mod pallet { Ok(if replaced { Pays::Yes } else { Pays::No }.into()) } - /// Introduce an already-ranked individual of the collective into this pallet. The rank may - /// still be zero. + /// Introduce an already-ranked individual of the collective into this pallet. /// - /// This resets `last_proof` to the current block and `last_promotion` will be set to zero, - /// thereby delaying any automatic demotion but allowing immediate promotion. + /// The rank may still be zero. This resets `last_proof` to the current block and + /// `last_promotion` will be set to zero, thereby delaying any automatic demotion but + /// allowing immediate promotion. /// /// - `origin`: A signed origin of a ranked, but not tracked, account. #[pallet::weight(T::WeightInfo::import())] #[pallet::call_index(8)] + #[deprecated = "Use `import_member` instead"] + #[allow(deprecated)] // Otherwise FRAME will complain about using something deprecated. pub fn import(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); - let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; + Self::do_import(who)?; - let now = frame_system::Pallet::::block_number(); - Member::::insert( - &who, - MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now }, - ); - Self::deposit_event(Event::::Imported { who, rank }); + Ok(Pays::No.into()) // Successful imports are free + } - Ok(Pays::No.into()) + /// Introduce an already-ranked individual of the collective into this pallet. + /// + /// The rank may still be zero. Can be called by anyone on any collective member - including + /// the sender. + /// + /// This resets `last_proof` to the current block and `last_promotion` will be set to zero, + /// thereby delaying any automatic demotion but allowing immediate promotion. + /// + /// - `origin`: A signed origin of a ranked, but not tracked, account. + /// - `who`: The account ID of the collective member to be inducted. + #[pallet::weight(T::WeightInfo::set_partial_params())] + #[pallet::call_index(11)] + pub fn import_member( + origin: OriginFor, + who: T::AccountId, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + Self::do_import(who)?; + + Ok(Pays::No.into()) // Successful imports are free } /// Set the parameters partially. @@ -661,6 +679,24 @@ pub mod pallet { } } } + + /// Import `who` into the core-fellowship pallet. + /// + /// `who` must be a member of the collective but *not* already imported. + pub(crate) fn do_import(who: T::AccountId) -> DispatchResult { + ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); + let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; + + let now = frame_system::Pallet::::block_number(); + Member::::insert( + &who, + MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now }, + ); + Self::deposit_event(Event::::Imported { who, rank }); + + Ok(()) + } + /// Convert a rank into a `0..RANK_COUNT` index suitable for the arrays in Params. /// /// Rank 1 becomes index 0, rank `RANK_COUNT` becomes index `RANK_COUNT - 1`. Any rank not @@ -766,6 +802,7 @@ impl, I: 'static> pallet_ranked_collective::BenchmarkSetup<::AccountId> for Pallet { fn ensure_member(who: &::AccountId) { + #[allow(deprecated)] Self::import(frame_system::RawOrigin::Signed(who.clone()).into()).unwrap(); } } diff --git a/substrate/frame/core-fellowship/src/tests/integration.rs b/substrate/frame/core-fellowship/src/tests/integration.rs index 7a48ed9783e7..b2149336547d 100644 --- a/substrate/frame/core-fellowship/src/tests/integration.rs +++ b/substrate/frame/core-fellowship/src/tests/integration.rs @@ -17,8 +17,10 @@ //! Integration test together with the ranked-collective pallet. +#![allow(deprecated)] + use frame_support::{ - assert_noop, assert_ok, derive_impl, hypothetically, ord_parameter_types, + assert_noop, assert_ok, derive_impl, hypothetically, hypothetically_ok, ord_parameter_types, pallet_prelude::Weight, parameter_types, traits::{ConstU16, EitherOf, IsInVec, MapSuccess, NoOpPoll, TryMapSuccess}, @@ -170,6 +172,37 @@ fn evidence(e: u32) -> Evidence { .expect("Static length matches") } +#[test] +fn import_simple_works() { + new_test_ext().execute_with(|| { + for i in 0u16..9 { + let acc = i as u64; + + // Does not work yet + assert_noop!(CoreFellowship::import(signed(acc)), Error::::Unranked); + assert_noop!( + CoreFellowship::import_member(signed(acc + 1), acc), + Error::::Unranked + ); + + assert_ok!(Club::add_member(RuntimeOrigin::root(), acc)); + promote_n_times(acc, i); + + hypothetically_ok!(CoreFellowship::import(signed(acc))); + hypothetically_ok!(CoreFellowship::import_member(signed(acc), acc)); + // Works from other accounts + assert_ok!(CoreFellowship::import_member(signed(acc + 1), acc)); + + // Does not work again + assert_noop!(CoreFellowship::import(signed(acc)), Error::::AlreadyInducted); + assert_noop!( + CoreFellowship::import_member(signed(acc + 1), acc), + Error::::AlreadyInducted + ); + } + }); +} + #[test] fn swap_simple_works() { new_test_ext().execute_with(|| { @@ -178,7 +211,8 @@ fn swap_simple_works() { assert_ok!(Club::add_member(RuntimeOrigin::root(), acc)); promote_n_times(acc, i); - assert_ok!(CoreFellowship::import(signed(acc))); + hypothetically_ok!(CoreFellowship::import(signed(acc))); + assert_ok!(CoreFellowship::import_member(signed(acc), acc)); // Swapping normally works: assert_ok!(Club::exchange_member(RuntimeOrigin::root(), acc, acc + 10)); diff --git a/substrate/frame/core-fellowship/src/tests/unit.rs b/substrate/frame/core-fellowship/src/tests/unit.rs index 11d1ea9fe5b7..f4418ed439d0 100644 --- a/substrate/frame/core-fellowship/src/tests/unit.rs +++ b/substrate/frame/core-fellowship/src/tests/unit.rs @@ -17,6 +17,8 @@ //! The crate's tests. +#![allow(deprecated)] + use std::collections::BTreeMap; use core::cell::RefCell; @@ -222,6 +224,66 @@ fn set_partial_params_works() { }); } +#[test] +fn import_member_works() { + new_test_ext().execute_with(|| { + assert_noop!(CoreFellowship::import_member(signed(0), 0), Error::::Unranked); + assert_noop!(CoreFellowship::import(signed(0)), Error::::Unranked); + + // Make induction work: + set_rank(0, 1); + assert!(!Member::::contains_key(0), "not yet imported"); + + // `import_member` can be used to induct ourselves: + hypothetically!({ + assert_ok!(CoreFellowship::import_member(signed(0), 0)); + assert!(Member::::contains_key(0), "got imported"); + + // Twice does not work: + assert_noop!( + CoreFellowship::import_member(signed(0), 0), + Error::::AlreadyInducted + ); + assert_noop!(CoreFellowship::import(signed(0)), Error::::AlreadyInducted); + }); + + // But we could have also used `import`: + hypothetically!({ + assert_ok!(CoreFellowship::import(signed(0))); + assert!(Member::::contains_key(0), "got imported"); + + // Twice does not work: + assert_noop!( + CoreFellowship::import_member(signed(0), 0), + Error::::AlreadyInducted + ); + assert_noop!(CoreFellowship::import(signed(0)), Error::::AlreadyInducted); + }); + }); +} + +#[test] +fn import_member_same_as_import() { + new_test_ext().execute_with(|| { + for rank in 0..=9 { + set_rank(0, rank); + + let import_root = hypothetically!({ + assert_ok!(CoreFellowship::import(signed(0))); + sp_io::storage::root(sp_runtime::StateVersion::V1) + }); + + let import_member_root = hypothetically!({ + assert_ok!(CoreFellowship::import_member(signed(1), 0)); + sp_io::storage::root(sp_runtime::StateVersion::V1) + }); + + // `import` and `import_member` do exactly the same thing. + assert_eq!(import_root, import_member_root); + } + }); +} + #[test] fn induct_works() { new_test_ext().execute_with(|| { diff --git a/substrate/frame/core-fellowship/src/weights.rs b/substrate/frame/core-fellowship/src/weights.rs index 9bca8cb56094..e6381c854d34 100644 --- a/substrate/frame/core-fellowship/src/weights.rs +++ b/substrate/frame/core-fellowship/src/weights.rs @@ -61,6 +61,7 @@ pub trait WeightInfo { fn promote_fast(r: u32, ) -> Weight; fn offboard() -> Weight; fn import() -> Weight; + fn import_member() -> Weight; fn approve() -> Weight; fn submit_evidence() -> Weight; } @@ -76,7 +77,7 @@ impl WeightInfo for SubstrateWeight { // Estimated: `0` // Minimum execution time: 6_652_000 picoseconds. Weight::from_parts(7_082_000, 0) - .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `CoreFellowship::Params` (r:1 w:1) /// Proof: `CoreFellowship::Params` (`max_values`: Some(1), `max_size`: Some(368), added: 863, mode: `MaxEncodedLen`) @@ -86,8 +87,8 @@ impl WeightInfo for SubstrateWeight { // Estimated: `1853` // Minimum execution time: 12_485_000 picoseconds. Weight::from_parts(12_784_000, 1853) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `CoreFellowship::Member` (r:1 w:1) /// Proof: `CoreFellowship::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) @@ -109,8 +110,8 @@ impl WeightInfo for SubstrateWeight { // Estimated: `19894` // Minimum execution time: 61_243_000 picoseconds. Weight::from_parts(63_033_000, 19894) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `CoreFellowship::Member` (r:1 w:1) /// Proof: `CoreFellowship::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) @@ -132,8 +133,8 @@ impl WeightInfo for SubstrateWeight { // Estimated: `19894` // Minimum execution time: 65_063_000 picoseconds. Weight::from_parts(67_047_000, 19894) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `RankedCollective::Members` (r:1 w:0) /// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -145,8 +146,8 @@ impl WeightInfo for SubstrateWeight { // Estimated: `3514` // Minimum execution time: 21_924_000 picoseconds. Weight::from_parts(22_691_000, 3514) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `CoreFellowship::Member` (r:1 w:1) /// Proof: `CoreFellowship::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) @@ -164,8 +165,8 @@ impl WeightInfo for SubstrateWeight { // Estimated: `3514` // Minimum execution time: 24_720_000 picoseconds. Weight::from_parts(25_580_000, 3514) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(5_u64)) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `RankedCollective::Members` (r:1 w:1) /// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -187,8 +188,8 @@ impl WeightInfo for SubstrateWeight { // Estimated: `19894` // Minimum execution time: 58_481_000 picoseconds. Weight::from_parts(59_510_000, 19894) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `RankedCollective::Members` (r:1 w:1) /// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -211,10 +212,10 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(42_220_685, 19894) // Standard Error: 18_061 .saturating_add(Weight::from_parts(13_858_309, 0).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3_u64)) - .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(r.into()))) .saturating_add(Weight::from_parts(0, 2489).saturating_mul(r.into())) } /// Storage: `RankedCollective::Members` (r:1 w:0) @@ -229,8 +230,8 @@ impl WeightInfo for SubstrateWeight { // Estimated: `3514` // Minimum execution time: 17_492_000 picoseconds. Weight::from_parts(18_324_000, 3514) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `CoreFellowship::Member` (r:1 w:1) /// Proof: `CoreFellowship::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) @@ -242,8 +243,18 @@ impl WeightInfo for SubstrateWeight { // Estimated: `3514` // Minimum execution time: 16_534_000 picoseconds. Weight::from_parts(17_046_000, 3514) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + fn import_member() -> Weight { + // Proof Size summary in bytes: + // Measured: `285` + // Estimated: `3514` + // Minimum execution time: 23_239_000 picoseconds. + Weight::from_parts(23_684_000, 0) + .saturating_add(Weight::from_parts(0, 3514)) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } /// Storage: `RankedCollective::Members` (r:1 w:0) /// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) @@ -257,8 +268,8 @@ impl WeightInfo for SubstrateWeight { // Estimated: `19894` // Minimum execution time: 42_264_000 picoseconds. Weight::from_parts(43_281_000, 19894) - .saturating_add(T::DbWeight::get().reads(3_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } /// Storage: `CoreFellowship::Member` (r:1 w:0) /// Proof: `CoreFellowship::Member` (`max_values`: None, `max_size`: Some(49), added: 2524, mode: `MaxEncodedLen`) @@ -270,8 +281,8 @@ impl WeightInfo for SubstrateWeight { // Estimated: `19894` // Minimum execution time: 25_461_000 picoseconds. Weight::from_parts(26_014_000, 19894) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } @@ -454,6 +465,16 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + fn import_member() -> Weight { + // Proof Size summary in bytes: + // Measured: `285` + // Estimated: `3514` + // Minimum execution time: 23_239_000 picoseconds. + Weight::from_parts(23_684_000, 0) + .saturating_add(Weight::from_parts(0, 3514)) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) + } /// Storage: `RankedCollective::Members` (r:1 w:0) /// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`) /// Storage: `CoreFellowship::Member` (r:1 w:1) From 6b6c70b0165b2c38e239eb740a7561e9ed4570de Mon Sep 17 00:00:00 2001 From: jasmy <3776356370@qq.com> Date: Tue, 7 Jan 2025 03:16:08 +0800 Subject: [PATCH 230/340] Fix typos (#7027) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dónal Murray --- bridges/SECURITY.md | 2 +- bridges/modules/messages/README.md | 2 +- cumulus/docs/overview.md | 2 +- substrate/client/network/README.md | 2 +- substrate/frame/bounties/README.md | 2 +- substrate/frame/bounties/src/lib.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bridges/SECURITY.md b/bridges/SECURITY.md index 9f215c887654..ea19eca42cc3 100644 --- a/bridges/SECURITY.md +++ b/bridges/SECURITY.md @@ -13,6 +13,6 @@ If you think that your report might be eligible for the Bug Bounty Program, plea Please check up-to-date [Parity Bug Bounty Program rules](https://www.parity.io/bug-bounty) to find out the information about our Bug Bounty Program. -**Warning**: This is an unified SECURITY.md file for Paritytech GitHub Organization. The presence of this file does not +**Warning**: This is a unified SECURITY.md file for Paritytech GitHub Organization. The presence of this file does not mean that this repository is covered by the Bug Bounty program. Please always check the Bug Bounty Program scope for information. diff --git a/bridges/modules/messages/README.md b/bridges/modules/messages/README.md index a78c86802498..7d9a23b4ba14 100644 --- a/bridges/modules/messages/README.md +++ b/bridges/modules/messages/README.md @@ -13,7 +13,7 @@ module and the final goal is to hand message to the message dispatch mechanism. ## Overview -Message lane is an unidirectional channel, where messages are sent from source chain to the target chain. At the same +Message lane is a unidirectional channel, where messages are sent from source chain to the target chain. At the same time, a single instance of messages module supports both outbound lanes and inbound lanes. So the chain where the module is deployed (this chain), may act as a source chain for outbound messages (heading to a bridged chain) and as a target chain for inbound messages (coming from a bridged chain). diff --git a/cumulus/docs/overview.md b/cumulus/docs/overview.md index 402c56042c49..82603257a871 100644 --- a/cumulus/docs/overview.md +++ b/cumulus/docs/overview.md @@ -70,7 +70,7 @@ A Parachain validator needs to validate a given PoVBlock, but without requiring the Parachain. To still make it possible to validate the Parachain block, the PoVBlock contains the witness data. The witness data is a proof that is collected while building the block. The proof will contain all trie nodes that are read during the block production. Cumulus uses the witness data to -reconstruct a partial trie and uses this a storage when executing the block. +reconstruct a partial trie and uses this as storage when executing the block. The outgoing messages are also collected at block production. These are messages from the Parachain the block is built for to other Parachains or to the relay chain itself. diff --git a/substrate/client/network/README.md b/substrate/client/network/README.md index 9903109d847c..4336bb78533c 100644 --- a/substrate/client/network/README.md +++ b/substrate/client/network/README.md @@ -245,7 +245,7 @@ only downloads finalized authority set changes. GRANDPA keeps justifications for each finalized authority set change. Each change is signed by the authorities from the previous set. By downloading and verifying these signed hand-offs starting from genesis, we arrive at a recent header faster than downloading full header chain. Each `WarpSyncRequest` contains a block -hash to a to start collecting proofs from. `WarpSyncResponse` contains a sequence of block headers and +hash to start collecting proofs from. `WarpSyncResponse` contains a sequence of block headers and justifications. The proof downloader checks the justifications and continues requesting proofs from the last header hash, until it arrives at some recent header. diff --git a/substrate/frame/bounties/README.md b/substrate/frame/bounties/README.md index 232334cb1edd..2293ae161e28 100644 --- a/substrate/frame/bounties/README.md +++ b/substrate/frame/bounties/README.md @@ -19,7 +19,7 @@ curator or once the bounty is active or payout is pending, resulting in the slas curator's deposit. This pallet may opt into using a [`ChildBountyManager`] that enables bounties to be split into -sub-bounties, as children of anh established bounty (called the parent in the context of it's +sub-bounties, as children of an established bounty (called the parent in the context of it's children). > NOTE: The parent bounty cannot be closed if it has a non-zero number of it has active child diff --git a/substrate/frame/bounties/src/lib.rs b/substrate/frame/bounties/src/lib.rs index d9accc5061cf..9b6e3c06e914 100644 --- a/substrate/frame/bounties/src/lib.rs +++ b/substrate/frame/bounties/src/lib.rs @@ -36,7 +36,7 @@ //! curator's deposit. //! //! This pallet may opt into using a [`ChildBountyManager`] that enables bounties to be split into -//! sub-bounties, as children of anh established bounty (called the parent in the context of it's +//! sub-bounties, as children of an established bounty (called the parent in the context of it's //! children). //! //! > NOTE: The parent bounty cannot be closed if it has a non-zero number of it has active child From c139739868eddbda495d642219a57602f63c18f5 Mon Sep 17 00:00:00 2001 From: Jeeyong Um Date: Tue, 7 Jan 2025 15:57:06 +0800 Subject: [PATCH 231/340] Remove usage of `sp-std` from Substrate (#7043) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR removes usage of deprecated `sp-std` from Substrate. (following PR of #5010) ## Integration This PR doesn't remove re-exported `sp_std` from any crates yet, so downstream projects using re-exported `sp_std` will not be affected. ## Review Notes The existing code using `sp-std` is refactored to use `alloc` and `core` directly. The key-value maps are instantiated from a vector of tuples directly instead of using `sp_std::map!` macro. `sp_std::Writer` is a helper type to use `Vec` with `core::fmt::Write` trait. This PR copied it into `sp-runtime`, because all crates using `sp_std::Writer` (including `sp-runtime` itself, `frame-support`, etc.) depend on `sp-runtime`. If this PR is merged, I would write following PRs to remove remaining usage of `sp-std` from `bridges` and `cumulus`. --------- Co-authored-by: command-bot <> Co-authored-by: Guillaume Thiolliere Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- Cargo.lock | 11 ---- prdoc/pr_7043.prdoc | 51 +++++++++++++++++++ substrate/client/sysinfo/Cargo.toml | 1 - .../frame/bags-list/remote-tests/Cargo.toml | 1 - substrate/frame/contracts/Cargo.toml | 2 - .../frame/contracts/proc-macro/src/lib.rs | 7 ++- .../frame/contracts/src/transient_storage.rs | 4 +- .../test-staking-e2e/Cargo.toml | 1 - .../frame/nft-fractionalization/Cargo.toml | 1 - .../test-delegate-stake/Cargo.toml | 1 - .../test-transfer-stake/Cargo.toml | 1 - substrate/frame/revive/Cargo.toml | 2 - substrate/frame/revive/proc-macro/src/lib.rs | 7 ++- .../frame/revive/src/transient_storage.rs | 4 +- substrate/frame/root-offences/Cargo.toml | 1 - .../procedural/src/pallet/expand/config.rs | 6 +-- substrate/frame/support/src/lib.rs | 7 +-- substrate/frame/system/Cargo.toml | 2 - substrate/frame/system/src/lib.rs | 16 +++--- substrate/frame/uniques/Cargo.toml | 1 - .../src/generic/unchecked_extrinsic.rs | 2 +- .../runtime/src/proving_trie/base16.rs | 4 +- .../runtime/src/proving_trie/base2.rs | 4 +- .../runtime/src/proving_trie/mod.rs | 2 +- .../primitives/runtime/src/runtime_logger.rs | 6 +-- .../primitives/runtime/src/traits/mod.rs | 2 +- .../src/traits/transaction_extension/mod.rs | 11 ++-- 27 files changed, 92 insertions(+), 66 deletions(-) create mode 100644 prdoc/pr_7043.prdoc diff --git a/Cargo.lock b/Cargo.lock index b0fb0586be38..ef0eb9f7e3dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7716,7 +7716,6 @@ dependencies = [ "sp-externalities 0.25.0", "sp-io 30.0.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", "sp-version 29.0.0", "sp-weights 27.0.0", "substrate-test-runtime-client", @@ -12371,7 +12370,6 @@ dependencies = [ "pallet-staking 28.0.0", "sp-core 28.0.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", "sp-storage 19.0.0", "sp-tracing 16.0.0", ] @@ -12967,7 +12965,6 @@ dependencies = [ "sp-io 30.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", "sp-tracing 16.0.0", "staging-xcm 7.0.0", "staging-xcm-builder 7.0.0", @@ -13352,7 +13349,6 @@ dependencies = [ "sp-npos-elections 26.0.0", "sp-runtime 31.0.1", "sp-staking 26.0.0", - "sp-std 14.0.0", "sp-tracing 16.0.0", ] @@ -14157,7 +14153,6 @@ dependencies = [ "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", ] [[package]] @@ -14432,7 +14427,6 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-staking 26.0.0", - "sp-std 14.0.0", "sp-tracing 16.0.0", ] @@ -14456,7 +14450,6 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-staking 26.0.0", - "sp-std 14.0.0", "sp-tracing 16.0.0", ] @@ -14870,7 +14863,6 @@ dependencies = [ "sp-io 30.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", "sp-tracing 16.0.0", "staging-xcm 7.0.0", "staging-xcm-builder 7.0.0", @@ -15095,7 +15087,6 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-staking 26.0.0", - "sp-std 14.0.0", ] [[package]] @@ -15941,7 +15932,6 @@ dependencies = [ "sp-core 28.0.0", "sp-io 30.0.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", ] [[package]] @@ -23681,7 +23671,6 @@ dependencies = [ "sp-crypto-hashing 0.1.0", "sp-io 30.0.0", "sp-runtime 31.0.1", - "sp-std 14.0.0", ] [[package]] diff --git a/prdoc/pr_7043.prdoc b/prdoc/pr_7043.prdoc new file mode 100644 index 000000000000..d7f6cd6907c8 --- /dev/null +++ b/prdoc/pr_7043.prdoc @@ -0,0 +1,51 @@ +title: Remove usage of `sp-std` from Substrate +doc: +- audience: Runtime Dev + description: |- + # Description + + This PR removes usage of deprecated `sp-std` from Substrate. (following PR of #5010) + + ## Integration + + This PR doesn't remove re-exported `sp_std` from any crates yet, so downstream projects using re-exported `sp_std` will not be affected. + + ## Review Notes + + The existing code using `sp-std` is refactored to use `alloc` and `core` directly. The key-value maps are instantiated from an array of tuples directly instead of using `sp_std::map!` macro. + + This PR replaces `sp_std::Writer`, a helper type for using `Vec` with `core::fmt::Write` trait, with `alloc::string::String`. + +crates: +- name: pallet-contracts + bump: patch +- name: pallet-revive + bump: patch +- name: sp-runtime + bump: patch +- name: frame-support-procedural + bump: patch +- name: frame-system + bump: patch +- name: pallet-contracts-proc-macro + bump: patch +- name: pallet-revive-proc-macro + bump: patch +- name: frame-support + bump: patch +- name: sc-sysinfo + bump: patch +- name: pallet-bags-list-remote-tests + bump: patch +- name: pallet-election-provider-e2e-test + bump: patch +- name: pallet-nft-fractionalization + bump: patch +- name: pallet-nomination-pools-test-delegate-stake + bump: patch +- name: pallet-nomination-pools-test-transfer-stake + bump: patch +- name: pallet-root-offences + bump: patch +- name: pallet-uniques + bump: patch diff --git a/substrate/client/sysinfo/Cargo.toml b/substrate/client/sysinfo/Cargo.toml index c7eed77eda7f..afc464c35881 100644 --- a/substrate/client/sysinfo/Cargo.toml +++ b/substrate/client/sysinfo/Cargo.toml @@ -30,7 +30,6 @@ serde_json = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } [dev-dependencies] sp-runtime = { workspace = true, default-features = true } diff --git a/substrate/frame/bags-list/remote-tests/Cargo.toml b/substrate/frame/bags-list/remote-tests/Cargo.toml index 99b203e73fb0..e3215803a020 100644 --- a/substrate/frame/bags-list/remote-tests/Cargo.toml +++ b/substrate/frame/bags-list/remote-tests/Cargo.toml @@ -26,7 +26,6 @@ pallet-staking = { workspace = true, default-features = true } # core sp-core = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } sp-storage = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } diff --git a/substrate/frame/contracts/Cargo.toml b/substrate/frame/contracts/Cargo.toml index e39128639e3e..5784e6dd1553 100644 --- a/substrate/frame/contracts/Cargo.toml +++ b/substrate/frame/contracts/Cargo.toml @@ -50,7 +50,6 @@ sp-api = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-std = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } @@ -98,7 +97,6 @@ std = [ "sp-io/std", "sp-keystore/std", "sp-runtime/std", - "sp-std/std", "wasm-instrument?/std", "wasmi/std", "xcm-builder/std", diff --git a/substrate/frame/contracts/proc-macro/src/lib.rs b/substrate/frame/contracts/proc-macro/src/lib.rs index 4aba1d24dbd5..5c3c34e6ef32 100644 --- a/substrate/frame/contracts/proc-macro/src/lib.rs +++ b/substrate/frame/contracts/proc-macro/src/lib.rs @@ -650,10 +650,9 @@ fn expand_functions(def: &EnvDef, expand_mode: ExpandMode) -> TokenStream2 { let result = #body; if ::log::log_enabled!(target: "runtime::contracts::strace", ::log::Level::Trace) { use core::fmt::Write; - let mut w = sp_std::Writer::default(); - let _ = core::write!(&mut w, #trace_fmt_str, #( #trace_fmt_args, )* result); - let msg = core::str::from_utf8(&w.inner()).unwrap_or_default(); - ctx.ext().append_debug_buffer(msg); + let mut msg = alloc::string::String::default(); + let _ = core::write!(&mut msg, #trace_fmt_str, #( #trace_fmt_args, )* result); + ctx.ext().append_debug_buffer(&msg); } result } diff --git a/substrate/frame/contracts/src/transient_storage.rs b/substrate/frame/contracts/src/transient_storage.rs index c795a966385a..c9b1dac1ad75 100644 --- a/substrate/frame/contracts/src/transient_storage.rs +++ b/substrate/frame/contracts/src/transient_storage.rs @@ -22,11 +22,11 @@ use crate::{ storage::WriteOutcome, Config, Error, }; +use alloc::{collections::BTreeMap, vec::Vec}; use codec::Encode; -use core::marker::PhantomData; +use core::{marker::PhantomData, mem}; use frame_support::DefaultNoBound; use sp_runtime::{DispatchError, DispatchResult, Saturating}; -use sp_std::{collections::btree_map::BTreeMap, mem, vec::Vec}; /// Meter entry tracks transaction allocations. #[derive(Default, Debug)] diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml index 5009d3d54d56..7a48ae868a5a 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml @@ -26,7 +26,6 @@ sp-io = { workspace = true, default-features = true } sp-npos-elections = { workspace = true } sp-runtime = { workspace = true, default-features = true } sp-staking = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } diff --git a/substrate/frame/nft-fractionalization/Cargo.toml b/substrate/frame/nft-fractionalization/Cargo.toml index 7f6df86ed0e5..23537b227893 100644 --- a/substrate/frame/nft-fractionalization/Cargo.toml +++ b/substrate/frame/nft-fractionalization/Cargo.toml @@ -30,7 +30,6 @@ sp-runtime = { workspace = true } pallet-balances = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml index fe3743d7e5da..62c2fb625fc4 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml +++ b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml @@ -23,7 +23,6 @@ sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-staking = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } frame-support = { features = ["experimental"], workspace = true, default-features = true } diff --git a/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml b/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml index 2cdc4c41a083..0b21d5f4e8cf 100644 --- a/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml +++ b/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml @@ -23,7 +23,6 @@ sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-staking = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } frame-support = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index fa008f8e836a..1284f5ee8947 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -46,7 +46,6 @@ sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -sp-std = { workspace = true } subxt-signer = { workspace = true, optional = true, features = [ "unstable-eth", ] } @@ -99,7 +98,6 @@ std = [ "sp-io/std", "sp-keystore/std", "sp-runtime/std", - "sp-std/std", "subxt-signer", "xcm-builder/std", "xcm/std", diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index b6ea1a06d94e..b09bdef14632 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -512,10 +512,9 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { let result = (|| #body)(); if ::log::log_enabled!(target: "runtime::revive::strace", ::log::Level::Trace) { use core::fmt::Write; - let mut w = sp_std::Writer::default(); - let _ = core::write!(&mut w, #trace_fmt_str, #( #trace_fmt_args, )* result); - let msg = core::str::from_utf8(&w.inner()).unwrap_or_default(); - self.ext().append_debug_buffer(msg); + let mut msg = alloc::string::String::default(); + let _ = core::write!(&mut msg, #trace_fmt_str, #( #trace_fmt_args, )* result); + self.ext().append_debug_buffer(&msg); } result } diff --git a/substrate/frame/revive/src/transient_storage.rs b/substrate/frame/revive/src/transient_storage.rs index 298e0296fe69..d88adc437359 100644 --- a/substrate/frame/revive/src/transient_storage.rs +++ b/substrate/frame/revive/src/transient_storage.rs @@ -22,11 +22,11 @@ use crate::{ storage::WriteOutcome, Config, Error, }; +use alloc::{collections::BTreeMap, vec::Vec}; use codec::Encode; -use core::marker::PhantomData; +use core::{marker::PhantomData, mem}; use frame_support::DefaultNoBound; use sp_runtime::{DispatchError, DispatchResult, Saturating}; -use sp_std::{collections::btree_map::BTreeMap, mem, vec::Vec}; /// Meter entry tracks transaction allocations. #[derive(Default, Debug)] diff --git a/substrate/frame/root-offences/Cargo.toml b/substrate/frame/root-offences/Cargo.toml index dedde9956b6f..c539f1dc4dc1 100644 --- a/substrate/frame/root-offences/Cargo.toml +++ b/substrate/frame/root-offences/Cargo.toml @@ -34,7 +34,6 @@ pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true } -sp-std = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } diff --git a/substrate/frame/support/procedural/src/pallet/expand/config.rs b/substrate/frame/support/procedural/src/pallet/expand/config.rs index 0a583f1359ba..d39f27672360 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/config.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/config.rs @@ -126,7 +126,7 @@ pub fn expand_config_metadata(def: &Def) -> proc_macro2::TokenStream { ty: #frame_support::__private::scale_info::meta_type::< ::#ident >(), - docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ], + docs: #frame_support::__private::vec![ #( #doc ),* ], } }) }); @@ -136,9 +136,9 @@ pub fn expand_config_metadata(def: &Def) -> proc_macro2::TokenStream { #[doc(hidden)] pub fn pallet_associated_types_metadata() - -> #frame_support::__private::sp_std::vec::Vec<#frame_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR> + -> #frame_support::__private::vec::Vec<#frame_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR> { - #frame_support::__private::sp_std::vec![ #( #types ),* ] + #frame_support::__private::vec![ #( #types ),* ] } } ) diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index c64987b17d35..a6969260e6a2 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -44,6 +44,7 @@ pub mod __private { pub use alloc::{ boxed::Box, rc::Rc, + string::String, vec, vec::{IntoIter, Vec}, }; @@ -502,9 +503,9 @@ macro_rules! runtime_print { ($($arg:tt)+) => { { use core::fmt::Write; - let mut w = $crate::__private::sp_std::Writer::default(); - let _ = core::write!(&mut w, $($arg)+); - $crate::__private::sp_io::misc::print_utf8(&w.inner()) + let mut msg = $crate::__private::String::default(); + let _ = core::write!(&mut msg, $($arg)+); + $crate::__private::sp_io::misc::print_utf8(msg.as_bytes()) } } } diff --git a/substrate/frame/system/Cargo.toml b/substrate/frame/system/Cargo.toml index 1340b2c55c53..8883ebd4c41d 100644 --- a/substrate/frame/system/Cargo.toml +++ b/substrate/frame/system/Cargo.toml @@ -26,7 +26,6 @@ serde = { features = ["alloc", "derive"], workspace = true } sp-core = { features = ["serde"], workspace = true } sp-io = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } -sp-std = { workspace = true } sp-version = { features = ["serde"], workspace = true } sp-weights = { features = ["serde"], workspace = true } @@ -47,7 +46,6 @@ std = [ "sp-externalities/std", "sp-io/std", "sp-runtime/std", - "sp-std/std", "sp-version/std", "sp-weights/std", ] diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 4fc69c8755f3..894e1898ed15 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -120,8 +120,6 @@ use sp_runtime::{ }, DispatchError, RuntimeDebug, }; -#[cfg(any(feature = "std", test))] -use sp_std::map; use sp_version::RuntimeVersion; use codec::{Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen}; @@ -1920,12 +1918,14 @@ impl Pallet { #[cfg(any(feature = "std", test))] pub fn externalities() -> TestExternalities { TestExternalities::new(sp_core::storage::Storage { - top: map![ - >::hashed_key_for(BlockNumberFor::::zero()) => [69u8; 32].encode(), - >::hashed_key().to_vec() => BlockNumberFor::::one().encode(), - >::hashed_key().to_vec() => [69u8; 32].encode() - ], - children_default: map![], + top: [ + (>::hashed_key_for(BlockNumberFor::::zero()), [69u8; 32].encode()), + (>::hashed_key().to_vec(), BlockNumberFor::::one().encode()), + (>::hashed_key().to_vec(), [69u8; 32].encode()), + ] + .into_iter() + .collect(), + children_default: Default::default(), }) } diff --git a/substrate/frame/uniques/Cargo.toml b/substrate/frame/uniques/Cargo.toml index 135292fb4ecd..a2473c51ee75 100644 --- a/substrate/frame/uniques/Cargo.toml +++ b/substrate/frame/uniques/Cargo.toml @@ -28,7 +28,6 @@ sp-runtime = { workspace = true } pallet-balances = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-std = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs index d8510a60a789..6b8471f84846 100644 --- a/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/substrate/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -683,7 +683,7 @@ mod legacy { Extra: Encode, { fn encode(&self) -> Vec { - let mut tmp = Vec::with_capacity(sp_std::mem::size_of::()); + let mut tmp = Vec::with_capacity(core::mem::size_of::()); // 1 byte version id. match self.signature.as_ref() { diff --git a/substrate/primitives/runtime/src/proving_trie/base16.rs b/substrate/primitives/runtime/src/proving_trie/base16.rs index da05c551c6d9..abdf6ed178bb 100644 --- a/substrate/primitives/runtime/src/proving_trie/base16.rs +++ b/substrate/primitives/runtime/src/proving_trie/base16.rs @@ -26,8 +26,8 @@ use super::{ProofToHashes, ProvingTrie, TrieError}; use crate::{Decode, DispatchError, Encode}; +use alloc::vec::Vec; use codec::MaxEncodedLen; -use sp_std::vec::Vec; use sp_trie::{ trie_types::{TrieDBBuilder, TrieDBMutBuilderV1}, LayoutV1, MemoryDB, Trie, TrieMut, @@ -197,7 +197,7 @@ mod tests { use super::*; use crate::traits::BlakeTwo256; use sp_core::H256; - use sp_std::collections::btree_map::BTreeMap; + use std::collections::BTreeMap; // A trie which simulates a trie of accounts (u32) and balances (u128). type BalanceTrie = BasicProvingTrie; diff --git a/substrate/primitives/runtime/src/proving_trie/base2.rs b/substrate/primitives/runtime/src/proving_trie/base2.rs index 2b14a59ab056..8a7cfaa5149d 100644 --- a/substrate/primitives/runtime/src/proving_trie/base2.rs +++ b/substrate/primitives/runtime/src/proving_trie/base2.rs @@ -22,9 +22,9 @@ use super::{ProofToHashes, ProvingTrie, TrieError}; use crate::{Decode, DispatchError, Encode}; +use alloc::{collections::BTreeMap, vec::Vec}; use binary_merkle_tree::{merkle_proof, merkle_root, MerkleProof}; use codec::MaxEncodedLen; -use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; /// A helper structure for building a basic base-2 merkle trie and creating compact proofs for that /// trie. @@ -161,7 +161,7 @@ mod tests { use super::*; use crate::traits::BlakeTwo256; use sp_core::H256; - use sp_std::collections::btree_map::BTreeMap; + use std::collections::BTreeMap; // A trie which simulates a trie of accounts (u32) and balances (u128). type BalanceTrie = BasicProvingTrie; diff --git a/substrate/primitives/runtime/src/proving_trie/mod.rs b/substrate/primitives/runtime/src/proving_trie/mod.rs index 009aa6d4935f..32b2284b4d79 100644 --- a/substrate/primitives/runtime/src/proving_trie/mod.rs +++ b/substrate/primitives/runtime/src/proving_trie/mod.rs @@ -23,7 +23,7 @@ pub mod base2; use crate::{Decode, DispatchError, Encode, MaxEncodedLen, TypeInfo}; #[cfg(feature = "serde")] use crate::{Deserialize, Serialize}; -use sp_std::vec::Vec; +use alloc::vec::Vec; use sp_trie::{trie_types::TrieError as SpTrieError, VerifyError}; /// A runtime friendly error type for tries. diff --git a/substrate/primitives/runtime/src/runtime_logger.rs b/substrate/primitives/runtime/src/runtime_logger.rs index 79984b135672..ec5251d978f1 100644 --- a/substrate/primitives/runtime/src/runtime_logger.rs +++ b/substrate/primitives/runtime/src/runtime_logger.rs @@ -54,10 +54,10 @@ impl log::Log for RuntimeLogger { fn log(&self, record: &log::Record) { use core::fmt::Write; - let mut w = sp_std::Writer::default(); - let _ = ::core::write!(&mut w, "{}", record.args()); + let mut msg = alloc::string::String::default(); + let _ = ::core::write!(&mut msg, "{}", record.args()); - sp_io::logging::log(record.level().into(), record.target(), w.inner()); + sp_io::logging::log(record.level().into(), record.target(), msg.as_bytes()); } fn flush(&self) {} diff --git a/substrate/primitives/runtime/src/traits/mod.rs b/substrate/primitives/runtime/src/traits/mod.rs index d371152dc40a..5b6cacc7e008 100644 --- a/substrate/primitives/runtime/src/traits/mod.rs +++ b/substrate/primitives/runtime/src/traits/mod.rs @@ -1710,7 +1710,7 @@ pub trait SignedExtension: /// This method provides a default implementation that returns a vec containing a single /// [`TransactionExtensionMetadata`]. fn metadata() -> Vec { - sp_std::vec![TransactionExtensionMetadata { + alloc::vec![TransactionExtensionMetadata { identifier: Self::IDENTIFIER, ty: scale_info::meta_type::(), implicit: scale_info::meta_type::() diff --git a/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs b/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs index 4d95e5e6f3a4..15be1e4c8e0a 100644 --- a/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs +++ b/substrate/primitives/runtime/src/traits/transaction_extension/mod.rs @@ -24,11 +24,12 @@ use crate::{ }, DispatchResult, }; +use alloc::vec::Vec; use codec::{Codec, Decode, Encode}; -use impl_trait_for_tuples::impl_for_tuples; +use core::fmt::Debug; #[doc(hidden)] -pub use sp_std::marker::PhantomData; -use sp_std::{self, fmt::Debug, prelude::*}; +pub use core::marker::PhantomData; +use impl_trait_for_tuples::impl_for_tuples; use sp_weights::Weight; use tuplex::{PopFront, PushBack}; @@ -258,7 +259,7 @@ pub trait TransactionExtension: /// This method provides a default implementation that returns a vec containing a single /// [`TransactionExtensionMetadata`]. fn metadata() -> Vec { - sp_std::vec![TransactionExtensionMetadata { + alloc::vec![TransactionExtensionMetadata { identifier: Self::IDENTIFIER, ty: scale_info::meta_type::(), implicit: scale_info::meta_type::() @@ -668,7 +669,7 @@ impl TransactionExtension for Tuple { impl TransactionExtension for () { const IDENTIFIER: &'static str = "UnitTransactionExtension"; type Implicit = (); - fn implicit(&self) -> sp_std::result::Result { + fn implicit(&self) -> core::result::Result { Ok(()) } type Val = (); From 1059be75c36634dff26a9b8711447a0c66926582 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:14:13 +0200 Subject: [PATCH 232/340] workflows: add debug input for sync templates act (#7057) # Description Introduce a workflow `debug` input for `misc-sync-templates.yml` and use it instead of the `runner.debug` context variable, which is set to '1' when `ACTIONS_RUNNER_DEBUG` env/secret is set (https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/troubleshooting-workflows/enabling-debug-logging#enabling-runner-diagnostic-logging). This is useful for controlling when to show debug prints. ## Integration N/A ## Review Notes Using `runner.debug` requires setting the `ACTIONS_RUNNER_DEBUG` env variable, but setting it to false/true is doable through an input, or by importing a variable from the github env file (which requires a code change). This input alone can replace the entire `runner.debug` + `ACTIONS_RUNNER_DEBUG` setup, which simplifies debug printing, but it doesn't look as standard as `runner.debug`. I don't think it is a big deal overall, for this action alone, but happy to account for other opinions. Note: setting the `ACTIONS_RUNNER_DEBUG` whenever we want in a separate branch wouldn't be useful because we can not run the `misc-sync-templates.yml` action from other branch than `master` (due to branch protection rules), so we need to expose this input to be controllable from `master`. --------- Signed-off-by: Iulian Barbu --- .github/workflows/misc-sync-templates.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/misc-sync-templates.yml b/.github/workflows/misc-sync-templates.yml index 7ff0705fe249..8d06d89621d7 100644 --- a/.github/workflows/misc-sync-templates.yml +++ b/.github/workflows/misc-sync-templates.yml @@ -21,6 +21,10 @@ on: stable_release_branch: description: 'Stable release branch, e.g. stable2407' required: true + debug: + description: Enable runner debug logging + required: false + default: false jobs: sync-templates: @@ -86,7 +90,7 @@ jobs: EOF [ ${{ matrix.template }} != "solochain" ] && echo "# Leave out the node compilation from regular template usage." \ - && echo "\"default-members\" = [\"pallets/template\", \"runtime\"]" >> Cargo.toml + && echo "default-members = [\"pallets/template\", \"runtime\"]" >> Cargo.toml [ ${{ matrix.template }} == "solochain" ] && echo "# The node isn't yet replaceable by Omni Node." cat << EOF >> Cargo.toml members = [ @@ -115,8 +119,9 @@ jobs: toml set templates/${{ matrix.template }}/Cargo.toml 'workspace.package.edition' "$(toml get --raw Cargo.toml 'workspace.package.edition')" > Cargo.temp mv Cargo.temp ./templates/${{ matrix.template }}/Cargo.toml working-directory: polkadot-sdk + - name: Print the result Cargo.tomls for debugging - if: runner.debug == '1' + if: ${{ github.event.inputs.debug }} run: find . -type f -name 'Cargo.toml' -exec cat {} \; working-directory: polkadot-sdk/templates/${{ matrix.template }}/ @@ -142,6 +147,12 @@ jobs: done; working-directory: "${{ env.template-path }}" + - name: Print the result Cargo.tomls for debugging after copying required workspace dependencies + if: ${{ github.event.inputs.debug }} + run: find . -type f -name 'Cargo.toml' -exec cat {} \; + working-directory: polkadot-sdk/templates/${{ matrix.template }}/ + + # 3. Verify the build. Push the changes or create a PR. # We've run into out-of-disk error when compiling in the next step, so we free up some space this way. From d2c157a467f8dd72b86da0b2070d960d5dcad60d Mon Sep 17 00:00:00 2001 From: Utkarsh Bhardwaj Date: Tue, 7 Jan 2025 12:39:14 +0000 Subject: [PATCH 233/340] migrate pallet-node-authorization to use umbrella crate (#7040) # Description Migrate pallet-node-authorization to use umbrella crate. Part of #6504 ## Review Notes * This PR migrates pallet-node-authorization to use the umbrella crate. * Some imports like below have not been added to any prelude as they have very limited usage across the various pallets. ```rust use sp_core::OpaquePeerId as PeerId; ``` * Added a commonly used runtime trait for testing in the `testing_prelude` in `substrate/frame/src/lib.rs`: ```rust pub use sp_runtime::traits::BadOrigin; ``` * `weights.rs` uses the `weights_prelude` like: ```rust use frame::weights_prelude::*; ``` * `tests.rs` and `mock.rs` use the `testing_prelude`: ```rust use frame::testing_prelude::*; ``` * `lib.rs` uses the main `prelude` like: ```rust use frame::prelude::*; ``` * For testing: Checked that local build works and tests run successfully. --- Cargo.lock | 6 +----- prdoc/pr_7040.prdoc | 16 ++++++++++++++++ substrate/frame/node-authorization/Cargo.toml | 16 +++------------- substrate/frame/node-authorization/src/lib.rs | 14 +++++++------- substrate/frame/node-authorization/src/mock.rs | 8 +++----- substrate/frame/node-authorization/src/tests.rs | 3 +-- .../frame/node-authorization/src/weights.rs | 3 +-- substrate/frame/src/lib.rs | 3 +++ 8 files changed, 35 insertions(+), 34 deletions(-) create mode 100644 prdoc/pr_7040.prdoc diff --git a/Cargo.lock b/Cargo.lock index ef0eb9f7e3dd..cc34514aeb93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14264,14 +14264,10 @@ dependencies = [ name = "pallet-node-authorization" version = "28.0.0" dependencies = [ - "frame-support 28.0.0", - "frame-system 28.0.0", "log", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] diff --git a/prdoc/pr_7040.prdoc b/prdoc/pr_7040.prdoc new file mode 100644 index 000000000000..f88e96a70371 --- /dev/null +++ b/prdoc/pr_7040.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: '[pallet-node-authorization] Migrate to using frame umbrella crate' + +doc: + - audience: Runtime Dev + description: This PR migrates the pallet-node-authorization to use the frame umbrella crate. This + is part of the ongoing effort to migrate all pallets to use the frame umbrella crate. + The effort is tracked [here](https://github.com/paritytech/polkadot-sdk/issues/6504). + +crates: + - name: pallet-node-authorization + bump: minor + - name: polkadot-sdk-frame + bump: minor diff --git a/substrate/frame/node-authorization/Cargo.toml b/substrate/frame/node-authorization/Cargo.toml index 174736493934..7e55ad178091 100644 --- a/substrate/frame/node-authorization/Cargo.toml +++ b/substrate/frame/node-authorization/Cargo.toml @@ -16,28 +16,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +frame = { workspace = true, features = ["experimental", "runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } [features] default = ["std"] std = [ "codec/std", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", + "frame/try-runtime", ] diff --git a/substrate/frame/node-authorization/src/lib.rs b/substrate/frame/node-authorization/src/lib.rs index 7682b54ea0f2..3cec0d3bcb63 100644 --- a/substrate/frame/node-authorization/src/lib.rs +++ b/substrate/frame/node-authorization/src/lib.rs @@ -47,18 +47,18 @@ pub mod weights; extern crate alloc; use alloc::{collections::btree_set::BTreeSet, vec::Vec}; +use frame::{ + deps::{sp_core::OpaquePeerId as PeerId, sp_io}, + prelude::*, +}; pub use pallet::*; -use sp_core::OpaquePeerId as PeerId; -use sp_runtime::traits::StaticLookup; pub use weights::WeightInfo; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::without_storage_info] @@ -111,7 +111,7 @@ pub mod pallet { StorageMap<_, Blake2_128Concat, PeerId, BTreeSet, ValueQuery>; #[pallet::genesis_config] - #[derive(frame_support::DefaultNoBound)] + #[derive(DefaultNoBound)] pub struct GenesisConfig { pub nodes: Vec<(PeerId, T::AccountId)>, } @@ -171,7 +171,7 @@ pub mod pallet { impl Hooks> for Pallet { /// Set reserved node every block. It may not be enabled depends on the offchain /// worker settings when starting the node. - fn offchain_worker(now: frame_system::pallet_prelude::BlockNumberFor) { + fn offchain_worker(now: BlockNumberFor) { let network_state = sp_io::offchain::network_state(); match network_state { Err(_) => log::error!( diff --git a/substrate/frame/node-authorization/src/mock.rs b/substrate/frame/node-authorization/src/mock.rs index 656d2bfa39ad..c6665a479e11 100644 --- a/substrate/frame/node-authorization/src/mock.rs +++ b/substrate/frame/node-authorization/src/mock.rs @@ -20,13 +20,11 @@ use super::*; use crate as pallet_node_authorization; -use frame_support::{derive_impl, ord_parameter_types, traits::ConstU32}; -use frame_system::EnsureSignedBy; -use sp_runtime::BuildStorage; +use frame::testing_prelude::*; type Block = frame_system::mocking::MockBlock; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -61,7 +59,7 @@ pub fn test_node(id: u8) -> PeerId { PeerId(vec![id]) } -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext() -> TestState { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_node_authorization::GenesisConfig:: { nodes: vec![(test_node(10), 10), (test_node(20), 20), (test_node(30), 30)], diff --git a/substrate/frame/node-authorization/src/tests.rs b/substrate/frame/node-authorization/src/tests.rs index 4704b5adf269..cf60ab6efbd8 100644 --- a/substrate/frame/node-authorization/src/tests.rs +++ b/substrate/frame/node-authorization/src/tests.rs @@ -19,8 +19,7 @@ use super::*; use crate::mock::*; -use frame_support::{assert_noop, assert_ok}; -use sp_runtime::traits::BadOrigin; +use frame::testing_prelude::*; #[test] fn add_well_known_node_works() { diff --git a/substrate/frame/node-authorization/src/weights.rs b/substrate/frame/node-authorization/src/weights.rs index 881eeaf7a4c0..cd2935458b9d 100644 --- a/substrate/frame/node-authorization/src/weights.rs +++ b/substrate/frame/node-authorization/src/weights.rs @@ -21,8 +21,7 @@ #![allow(unused_parens)] #![allow(unused_imports)] -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use core::marker::PhantomData; +use frame::weights_prelude::*; pub trait WeightInfo { fn add_well_known_node() -> Weight; diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index b0338b682314..15601ebde1f2 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -328,6 +328,9 @@ pub mod testing_prelude { pub use sp_io::TestExternalities; pub use sp_io::TestExternalities as TestState; + + /// Commonly used runtime traits for testing. + pub use sp_runtime::traits::BadOrigin; } /// All of the types and tools needed to build FRAME-based runtimes. From be20c65743cefdaa5a3bfb29c2fc668fc57d8925 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Tue, 7 Jan 2025 13:59:23 +0100 Subject: [PATCH 234/340] Implement NetworkRequest for litep2p (#7073) # Description Implements NetworkRequest::request for litep2p that we need for networking benchmarks ## Review Notes Duplicates implementation for NetworkService https://github.com/paritytech/polkadot-sdk/blob/5bf9dd2aa9bf944434203128783925bdc2ad8c01/substrate/client/network/src/service.rs#L1186-L1205 --------- Co-authored-by: command-bot <> --- prdoc/pr_7073.prdoc | 16 ++++++++++++ .../client/network/src/litep2p/service.rs | 26 +++++++++++++------ 2 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 prdoc/pr_7073.prdoc diff --git a/prdoc/pr_7073.prdoc b/prdoc/pr_7073.prdoc new file mode 100644 index 000000000000..3bcd129d0317 --- /dev/null +++ b/prdoc/pr_7073.prdoc @@ -0,0 +1,16 @@ +title: Implement NetworkRequest for litep2p +doc: +- audience: Node Dev + description: |- + # Description + + Implements NetworkRequest::request for litep2p that we need for networking benchmarks + + + ## Review Notes + + Duplicates implementation for NetworkService + https://github.com/paritytech/polkadot-sdk/blob/5bf9dd2aa9bf944434203128783925bdc2ad8c01/substrate/client/network/src/service.rs#L1186-L1205 +crates: +- name: sc-network + bump: patch diff --git a/substrate/client/network/src/litep2p/service.rs b/substrate/client/network/src/litep2p/service.rs index d270e90efdf5..2d4a117d1563 100644 --- a/substrate/client/network/src/litep2p/service.rs +++ b/substrate/client/network/src/litep2p/service.rs @@ -28,8 +28,8 @@ use crate::{ peer_store::PeerStoreProvider, service::out_events, Event, IfDisconnected, NetworkDHTProvider, NetworkEventStream, NetworkPeers, NetworkRequest, - NetworkSigner, NetworkStateInfo, NetworkStatus, NetworkStatusProvider, ProtocolName, - RequestFailure, Signature, + NetworkSigner, NetworkStateInfo, NetworkStatus, NetworkStatusProvider, OutboundFailure, + ProtocolName, RequestFailure, Signature, }; use codec::DecodeAll; @@ -526,13 +526,23 @@ impl NetworkStateInfo for Litep2pNetworkService { impl NetworkRequest for Litep2pNetworkService { async fn request( &self, - _target: PeerId, - _protocol: ProtocolName, - _request: Vec, - _fallback_request: Option<(Vec, ProtocolName)>, - _connect: IfDisconnected, + target: PeerId, + protocol: ProtocolName, + request: Vec, + fallback_request: Option<(Vec, ProtocolName)>, + connect: IfDisconnected, ) -> Result<(Vec, ProtocolName), RequestFailure> { - unimplemented!(); + let (tx, rx) = oneshot::channel(); + + self.start_request(target, protocol, request, fallback_request, tx, connect); + + match rx.await { + Ok(v) => v, + // The channel can only be closed if the network worker no longer exists. If the + // network worker no longer exists, then all connections to `target` are necessarily + // closed, and we legitimately report this situation as a "ConnectionClosed". + Err(_) => Err(RequestFailure::Network(OutboundFailure::ConnectionClosed)), + } } fn start_request( From 064f10c495993c3f6bb4e015780e1ffb0dac3732 Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Tue, 7 Jan 2025 15:28:21 +0200 Subject: [PATCH 235/340] rewrite some flaky zombienet polkadot tests to zombienet-sdk (#6757) Will fix: https://github.com/paritytech/polkadot-sdk/issues/6574 https://github.com/paritytech/polkadot-sdk/issues/6644 https://github.com/paritytech/polkadot-sdk/issues/6062 --------- Co-authored-by: Javier Viola --- .gitlab/pipeline/zombienet/polkadot.yml | 113 +++-- Cargo.lock | 418 +++--------------- Cargo.toml | 2 +- polkadot/zombienet-sdk-tests/Cargo.toml | 1 + .../tests/elastic_scaling/basic_3cores.rs | 135 ++++++ .../doesnt_break_parachains.rs | 133 ++++++ .../tests/elastic_scaling/mod.rs | 6 +- .../elastic_scaling/slot_based_3cores.rs | 18 +- .../async_backing_6_seconds_rate.rs | 95 ++++ .../tests/functional/mod.rs | 5 + .../tests/functional/sync_backing.rs | 74 ++++ .../helpers.rs => helpers/mod.rs} | 29 +- polkadot/zombienet-sdk-tests/tests/lib.rs | 5 + .../tests/smoke/coretime_revenue.rs | 23 +- .../0001-basic-3cores-6s-blocks.toml | 49 -- .../0001-basic-3cores-6s-blocks.zndsl | 28 -- ...astic-scaling-doesnt-break-parachains.toml | 40 -- ...stic-scaling-doesnt-break-parachains.zndsl | 20 - .../elastic_scaling/assign-core.js | 1 - .../0011-async-backing-6-seconds-rate.toml | 54 --- .../0011-async-backing-6-seconds-rate.zndsl | 20 - .../functional/0017-sync-backing.toml | 48 -- .../functional/0017-sync-backing.zndsl | 22 - 23 files changed, 637 insertions(+), 702 deletions(-) create mode 100644 polkadot/zombienet-sdk-tests/tests/elastic_scaling/basic_3cores.rs create mode 100644 polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs create mode 100644 polkadot/zombienet-sdk-tests/tests/functional/async_backing_6_seconds_rate.rs create mode 100644 polkadot/zombienet-sdk-tests/tests/functional/mod.rs create mode 100644 polkadot/zombienet-sdk-tests/tests/functional/sync_backing.rs rename polkadot/zombienet-sdk-tests/tests/{elastic_scaling/helpers.rs => helpers/mod.rs} (65%) delete mode 100644 polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml delete mode 100644 polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl delete mode 100644 polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml delete mode 100644 polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl delete mode 120000 polkadot/zombienet_tests/elastic_scaling/assign-core.js delete mode 100644 polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml delete mode 100644 polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl delete mode 100644 polkadot/zombienet_tests/functional/0017-sync-backing.toml delete mode 100644 polkadot/zombienet_tests/functional/0017-sync-backing.zndsl diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml index 14a235bcda86..878f241317a4 100644 --- a/.gitlab/pipeline/zombienet/polkadot.yml +++ b/.gitlab/pipeline/zombienet/polkadot.yml @@ -160,39 +160,6 @@ zombienet-polkadot-functional-0010-validator-disabling: --local-dir="${LOCAL_DIR}/functional" --test="0010-validator-disabling.zndsl" -.zombienet-polkadot-functional-0011-async-backing-6-seconds-rate: - extends: - - .zombienet-polkadot-common - script: - - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh - --local-dir="${LOCAL_DIR}/functional" - --test="0011-async-backing-6-seconds-rate.zndsl" - -zombienet-polkadot-elastic-scaling-0001-basic-3cores-6s-blocks: - extends: - - .zombienet-polkadot-common - variables: - FORCED_INFRA_INSTANCE: "spot-iops" - before_script: - - !reference [ .zombienet-polkadot-common, before_script ] - - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/elastic_scaling - script: - - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh - --local-dir="${LOCAL_DIR}/elastic_scaling" - --test="0001-basic-3cores-6s-blocks.zndsl" - -.zombienet-polkadot-elastic-scaling-0002-elastic-scaling-doesnt-break-parachains: - extends: - - .zombienet-polkadot-common - before_script: - - !reference [ .zombienet-polkadot-common, before_script ] - - cp --remove-destination ${LOCAL_DIR}/assign-core.js ${LOCAL_DIR}/elastic_scaling - script: - - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh - --local-dir="${LOCAL_DIR}/elastic_scaling" - --test="0002-elastic-scaling-doesnt-break-parachains.zndsl" - - .zombienet-polkadot-functional-0012-spam-statement-distribution-requests: extends: - .zombienet-polkadot-common @@ -236,14 +203,6 @@ zombienet-polkadot-functional-0015-coretime-shared-core: --local-dir="${LOCAL_DIR}/functional" --test="0016-approval-voting-parallel.zndsl" -.zombienet-polkadot-functional-0017-sync-backing: - extends: - - .zombienet-polkadot-common - script: - - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh - --local-dir="${LOCAL_DIR}/functional" - --test="0017-sync-backing.zndsl" - zombienet-polkadot-functional-0018-shared-core-idle-parachain: extends: - .zombienet-polkadot-common @@ -386,6 +345,8 @@ zombienet-polkadot-malus-0001-dispute-valid: --local-dir="${LOCAL_DIR}/integrationtests" --test="0001-dispute-valid-block.zndsl" +# sdk tests + .zombienet-polkadot-coretime-revenue: extends: - .zombienet-polkadot-common @@ -411,8 +372,78 @@ zombienet-polkadot-elastic-scaling-slot-based-3cores: - !reference [ ".zombienet-polkadot-common", "before_script" ] - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" - export CUMULUS_IMAGE="docker.io/paritypr/test-parachain:${PIPELINE_IMAGE_TAG}" + - export X_INFRA_INSTANCE=spot # use spot by default script: # we want to use `--no-capture` in zombienet tests. - unset NEXTEST_FAILURE_OUTPUT - unset NEXTEST_SUCCESS_OUTPUT - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- elastic_scaling::slot_based_3cores::slot_based_3cores_test + +zombienet-polkadot-elastic-scaling-doesnt-break-parachains: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - export X_INFRA_INSTANCE=spot # use spot by default + variables: + KUBERNETES_CPU_REQUEST: "1" + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - RUST_LOG=info,zombienet_=trace cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- elastic_scaling::doesnt_break_parachains::doesnt_break_parachains_test + +zombienet-polkadot-elastic-scaling-basic-3cores: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - export CUMULUS_IMAGE="${COL_IMAGE}" + - export X_INFRA_INSTANCE=spot # use spot by default + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- elastic_scaling::basic_3cores::basic_3cores_test + +zombienet-polkadot-functional-sync-backing: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + # Hardcoded to an old polkadot-parachain image, pre async backing. + - export CUMULUS_IMAGE="docker.io/paritypr/polkadot-parachain-debug:master-99623e62" + - export X_INFRA_INSTANCE=spot # use spot by default + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- functional::sync_backing::sync_backing_test + +zombienet-polkadot-functional-async-backing-6-seconds-rate: + extends: + - .zombienet-polkadot-common + needs: + - job: build-polkadot-zombienet-tests + artifacts: true + before_script: + - !reference [ ".zombienet-polkadot-common", "before_script" ] + - export POLKADOT_IMAGE="${ZOMBIENET_INTEGRATION_TEST_IMAGE}" + - export X_INFRA_INSTANCE=spot # use spot by default + script: + # we want to use `--no-capture` in zombienet tests. + - unset NEXTEST_FAILURE_OUTPUT + - unset NEXTEST_SUCCESS_OUTPUT + - cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- functional::async_backing_6_seconds_rate::async_backing_6_seconds_rate_test diff --git a/Cargo.lock b/Cargo.lock index cc34514aeb93..0a22179eb3d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6213,7 +6213,7 @@ dependencies = [ "regex", "syn 2.0.87", "termcolor", - "toml 0.8.12", + "toml 0.8.19", "walkdir", ] @@ -9777,29 +9777,6 @@ dependencies = [ "libc", ] -[[package]] -name = "libp2p" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94495eb319a85b70a68b85e2389a95bb3555c71c49025b78c691a854a7e6464" -dependencies = [ - "bytes", - "either", - "futures", - "futures-timer", - "getrandom", - "instant", - "libp2p-allow-block-list 0.2.0", - "libp2p-connection-limits 0.2.1", - "libp2p-core 0.40.1", - "libp2p-identity", - "libp2p-swarm 0.43.7", - "multiaddr 0.18.1", - "pin-project", - "rw-stream-sink", - "thiserror", -] - [[package]] name = "libp2p" version = "0.54.1" @@ -9811,9 +9788,9 @@ dependencies = [ "futures", "futures-timer", "getrandom", - "libp2p-allow-block-list 0.4.0", - "libp2p-connection-limits 0.4.0", - "libp2p-core 0.42.0", + "libp2p-allow-block-list", + "libp2p-connection-limits", + "libp2p-core", "libp2p-dns", "libp2p-identify", "libp2p-identity", @@ -9824,7 +9801,7 @@ dependencies = [ "libp2p-ping", "libp2p-quic", "libp2p-request-response", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "libp2p-tcp", "libp2p-upnp", "libp2p-websocket", @@ -9835,39 +9812,15 @@ dependencies = [ "thiserror", ] -[[package]] -name = "libp2p-allow-block-list" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55b46558c5c0bf99d3e2a1a38fd54ff5476ca66dd1737b12466a1824dd219311" -dependencies = [ - "libp2p-core 0.40.1", - "libp2p-identity", - "libp2p-swarm 0.43.7", - "void", -] - [[package]] name = "libp2p-allow-block-list" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1027ccf8d70320ed77e984f273bc8ce952f623762cb9bf2d126df73caef8041" dependencies = [ - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", - "void", -] - -[[package]] -name = "libp2p-connection-limits" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5107ad45cb20b2f6c3628c7b6014b996fcb13a88053f4569c872c6e30abf58" -dependencies = [ - "libp2p-core 0.40.1", - "libp2p-identity", - "libp2p-swarm 0.43.7", + "libp2p-swarm", "void", ] @@ -9877,37 +9830,9 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d003540ee8baef0d254f7b6bfd79bac3ddf774662ca0abf69186d517ef82ad8" dependencies = [ - "libp2p-core 0.42.0", - "libp2p-identity", - "libp2p-swarm 0.45.1", - "void", -] - -[[package]] -name = "libp2p-core" -version = "0.40.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd44289ab25e4c9230d9246c475a22241e301b23e8f4061d3bdef304a1a99713" -dependencies = [ - "either", - "fnv", - "futures", - "futures-timer", - "instant", + "libp2p-core", "libp2p-identity", - "log", - "multiaddr 0.18.1", - "multihash 0.19.1", - "multistream-select", - "once_cell", - "parking_lot 0.12.3", - "pin-project", - "quick-protobuf 0.8.1", - "rand", - "rw-stream-sink", - "smallvec", - "thiserror", - "unsigned-varint 0.7.2", + "libp2p-swarm", "void", ] @@ -9948,7 +9873,7 @@ dependencies = [ "async-trait", "futures", "hickory-resolver", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "parking_lot 0.12.3", "smallvec", @@ -9966,9 +9891,9 @@ dependencies = [ "futures", "futures-bounded", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "lru 0.12.3", "quick-protobuf 0.8.1", "quick-protobuf-codec", @@ -10010,9 +9935,9 @@ dependencies = [ "futures", "futures-bounded", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "quick-protobuf 0.8.1", "quick-protobuf-codec", "rand", @@ -10035,9 +9960,9 @@ dependencies = [ "futures", "hickory-proto", "if-watch", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "rand", "smallvec", "socket2 0.5.7", @@ -10053,12 +9978,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ebafa94a717c8442d8db8d3ae5d1c6a15e30f2d347e0cd31d057ca72e42566" dependencies = [ "futures", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identify", "libp2p-identity", "libp2p-kad", "libp2p-ping", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "pin-project", "prometheus-client", "web-time", @@ -10074,7 +9999,7 @@ dependencies = [ "bytes", "curve25519-dalek 4.1.3", "futures", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "multiaddr 0.18.1", "multihash 0.19.1", @@ -10099,9 +10024,9 @@ dependencies = [ "either", "futures", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "rand", "tracing", "void", @@ -10118,7 +10043,7 @@ dependencies = [ "futures", "futures-timer", "if-watch", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "libp2p-tls", "parking_lot 0.12.3", @@ -10142,9 +10067,9 @@ dependencies = [ "futures", "futures-bounded", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", - "libp2p-swarm 0.45.1", + "libp2p-swarm", "rand", "smallvec", "tracing", @@ -10152,27 +10077,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "libp2p-swarm" -version = "0.43.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "580189e0074af847df90e75ef54f3f30059aedda37ea5a1659e8b9fca05c0141" -dependencies = [ - "either", - "fnv", - "futures", - "futures-timer", - "instant", - "libp2p-core 0.40.1", - "libp2p-identity", - "log", - "multistream-select", - "once_cell", - "rand", - "smallvec", - "void", -] - [[package]] name = "libp2p-swarm" version = "0.45.1" @@ -10183,7 +10087,7 @@ dependencies = [ "fnv", "futures", "futures-timer", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "libp2p-swarm-derive", "lru 0.12.3", @@ -10219,7 +10123,7 @@ dependencies = [ "futures-timer", "if-watch", "libc", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "socket2 0.5.7", "tokio", @@ -10234,7 +10138,7 @@ checksum = "47b23dddc2b9c355f73c1e36eb0c3ae86f7dc964a3715f0731cfad352db4d847" dependencies = [ "futures", "futures-rustls", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "rcgen 0.11.3", "ring 0.17.8", @@ -10254,8 +10158,8 @@ dependencies = [ "futures", "futures-timer", "igd-next", - "libp2p-core 0.42.0", - "libp2p-swarm 0.45.1", + "libp2p-core", + "libp2p-swarm", "tokio", "tracing", "void", @@ -10270,7 +10174,7 @@ dependencies = [ "either", "futures", "futures-rustls", - "libp2p-core 0.42.0", + "libp2p-core", "libp2p-identity", "parking_lot 0.12.3", "pin-project-lite", @@ -10290,7 +10194,7 @@ checksum = "788b61c80789dba9760d8c669a5bedb642c8267555c803fabd8396e4ca5c5882" dependencies = [ "either", "futures", - "libp2p-core 0.42.0", + "libp2p-core", "thiserror", "tracing", "yamux 0.12.1", @@ -11300,17 +11204,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "libc", -] - [[package]] name = "nix" version = "0.29.0" @@ -13015,7 +12908,7 @@ dependencies = [ "parity-wasm", "sp-runtime 31.0.1", "tempfile", - "toml 0.8.12", + "toml 0.8.19", "twox-hash", ] @@ -14936,7 +14829,7 @@ dependencies = [ "polkavm-linker 0.18.0", "sp-core 28.0.0", "sp-io 30.0.0", - "toml 0.8.12", + "toml 0.8.19", ] [[package]] @@ -14951,7 +14844,7 @@ dependencies = [ "polkavm-linker 0.10.0", "sp-runtime 39.0.2", "tempfile", - "toml 0.8.12", + "toml 0.8.19", ] [[package]] @@ -19854,6 +19747,7 @@ dependencies = [ "env_logger 0.11.3", "log", "parity-scale-codec", + "polkadot-primitives 7.0.0", "serde", "serde_json", "substrate-build-script-utils", @@ -19930,12 +19824,6 @@ dependencies = [ "log", ] -[[package]] -name = "polkavm-common" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c99f7eee94e7be43ba37eef65ad0ee8cbaf89b7c00001c3f6d2be985cb1817" - [[package]] name = "polkavm-common" version = "0.9.0" @@ -19965,15 +19853,6 @@ dependencies = [ "polkavm-assembler 0.18.0", ] -[[package]] -name = "polkavm-derive" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79fa916f7962348bd1bb1a65a83401675e6fc86c51a0fdbcf92a3108e58e6125" -dependencies = [ - "polkavm-derive-impl-macro 0.8.0", -] - [[package]] name = "polkavm-derive" version = "0.9.1" @@ -20001,18 +19880,6 @@ dependencies = [ "polkavm-derive-impl-macro 0.18.0", ] -[[package]] -name = "polkavm-derive-impl" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10b2654a8a10a83c260bfb93e97b262cf0017494ab94a65d389e0eda6de6c9c" -dependencies = [ - "polkavm-common 0.8.0", - "proc-macro2 1.0.86", - "quote 1.0.37", - "syn 2.0.87", -] - [[package]] name = "polkavm-derive-impl" version = "0.9.0" @@ -20049,16 +19916,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "polkavm-derive-impl-macro" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e85319a0d5129dc9f021c62607e0804f5fb777a05cdda44d750ac0732def66" -dependencies = [ - "polkavm-derive-impl 0.8.0", - "syn 2.0.87", -] - [[package]] name = "polkavm-derive-impl-macro" version = "0.9.0" @@ -23042,7 +22899,7 @@ dependencies = [ "futures", "futures-timer", "ip_network", - "libp2p 0.54.1", + "libp2p", "linked_hash_set", "litep2p", "log", @@ -23218,7 +23075,7 @@ dependencies = [ "async-trait", "futures", "futures-timer", - "libp2p 0.54.1", + "libp2p", "log", "parking_lot 0.12.3", "rand", @@ -23675,7 +23532,7 @@ version = "15.0.0" dependencies = [ "chrono", "futures", - "libp2p 0.54.1", + "libp2p", "log", "parking_lot 0.12.3", "pin-project", @@ -26177,53 +26034,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "sp-core" -version = "31.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d7a0fd8f16dcc3761198fc83be12872f823b37b749bc72a3a6a1f702509366" -dependencies = [ - "array-bytes", - "bitflags 1.3.2", - "blake2 0.10.6", - "bounded-collections", - "bs58", - "dyn-clonable", - "ed25519-zebra 3.1.0", - "futures", - "hash-db", - "hash256-std-hasher", - "impl-serde 0.4.0", - "itertools 0.10.5", - "k256", - "libsecp256k1", - "log", - "merlin", - "parity-bip39", - "parity-scale-codec", - "parking_lot 0.12.3", - "paste", - "primitive-types 0.12.2", - "rand", - "scale-info", - "schnorrkel 0.11.4", - "secp256k1 0.28.2", - "secrecy 0.8.0", - "serde", - "sp-crypto-hashing 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-externalities 0.27.0", - "sp-runtime-interface 26.0.0", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-storage 20.0.0", - "ss58-registry", - "substrate-bip39 0.5.0", - "thiserror", - "tracing", - "w3f-bls", - "zeroize", -] - [[package]] name = "sp-core" version = "32.0.0" @@ -26564,18 +26374,6 @@ dependencies = [ "sp-storage 19.0.0", ] -[[package]] -name = "sp-externalities" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d6a4572eadd4a63cff92509a210bf425501a0c5e76574b30a366ac77653787" -dependencies = [ - "environmental", - "parity-scale-codec", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-storage 20.0.0", -] - [[package]] name = "sp-externalities" version = "0.28.0" @@ -27160,26 +26958,6 @@ dependencies = [ "trybuild", ] -[[package]] -name = "sp-runtime-interface" -version = "26.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a675ea4858333d4d755899ed5ed780174aa34fec15953428d516af5452295" -dependencies = [ - "bytes", - "impl-trait-for-tuples", - "parity-scale-codec", - "polkavm-derive 0.8.0", - "primitive-types 0.12.2", - "sp-externalities 0.27.0", - "sp-runtime-interface-proc-macro 18.0.0", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-storage 20.0.0", - "sp-tracing 16.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-wasm-interface 20.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "static_assertions", -] - [[package]] name = "sp-runtime-interface" version = "27.0.0" @@ -27537,20 +27315,6 @@ dependencies = [ "sp-debug-derive 14.0.0", ] -[[package]] -name = "sp-storage" -version = "20.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dba5791cb3978e95daf99dad919ecb3ec35565604e88cd38d805d9d4981e8bd" -dependencies = [ - "impl-serde 0.4.0", - "parity-scale-codec", - "ref-cast", - "serde", - "sp-debug-derive 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "sp-storage" version = "21.0.0" @@ -27622,19 +27386,6 @@ dependencies = [ "tracing-subscriber 0.3.18", ] -[[package]] -name = "sp-tracing" -version = "16.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0351810b9d074df71c4514c5228ed05c250607cba131c1c9d1526760ab69c05c" -dependencies = [ - "parity-scale-codec", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing", - "tracing-core", - "tracing-subscriber 0.2.25", -] - [[package]] name = "sp-tracing" version = "17.0.1" @@ -27891,20 +27642,6 @@ dependencies = [ "wasmtime", ] -[[package]] -name = "sp-wasm-interface" -version = "20.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef97172c42eb4c6c26506f325f48463e9bc29b2034a587f1b9e48c751229bee" -dependencies = [ - "anyhow", - "impl-trait-for-tuples", - "log", - "parity-scale-codec", - "sp-std 14.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmtime", -] - [[package]] name = "sp-wasm-interface" version = "21.0.1" @@ -28444,19 +28181,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "substrate-bip39" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b564c293e6194e8b222e52436bcb99f60de72043c7f845cf6c4406db4df121" -dependencies = [ - "hmac 0.12.1", - "pbkdf2", - "schnorrkel 0.11.4", - "sha2 0.10.8", - "zeroize", -] - [[package]] name = "substrate-bip39" version = "0.6.0" @@ -28797,7 +28521,7 @@ dependencies = [ "sp-version 29.0.0", "strum 0.26.3", "tempfile", - "toml 0.8.12", + "toml 0.8.19", "walkdir", "wasm-opt", ] @@ -28818,7 +28542,7 @@ dependencies = [ "sp-maybe-compressed-blob 11.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.26.3", "tempfile", - "toml 0.8.12", + "toml 0.8.19", "walkdir", "wasm-opt", ] @@ -29815,33 +29539,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", -] - -[[package]] -name = "toml" -version = "0.8.12" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.12", + "toml_edit 0.22.22", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -29853,8 +29565,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ "indexmap 2.7.0", - "serde", - "serde_spanned", "toml_datetime", "winnow 0.5.15", ] @@ -29872,9 +29582,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.12" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap 2.7.0", "serde", @@ -32076,9 +31786,9 @@ dependencies = [ [[package]] name = "zombienet-configuration" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d716b3ff8112d98ced15f53b0c72454f8cde533fe2b68bb04379228961efbd80" +checksum = "5ced2fca1322821431f03d06dcf2ea74d3a7369760b6c587b372de6eada3ce43" dependencies = [ "anyhow", "lazy_static", @@ -32089,23 +31799,23 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "toml 0.7.8", + "toml 0.8.19", "url", "zombienet-support", ] [[package]] name = "zombienet-orchestrator" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4098a7d33b729b59e32c41a87aa4d484bd1b8771a059bbd4edfb4d430b3b2d74" +checksum = "86ecd17133c3129547b6472591b5e58d4aee1fc63c965a3418fd56d33a8a4e82" dependencies = [ "anyhow", "async-trait", "futures", "glob-match", "hex", - "libp2p 0.52.4", + "libp2p", "libsecp256k1", "multiaddr 0.18.1", "rand", @@ -32114,7 +31824,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", - "sp-core 31.0.0", + "sp-core 34.0.0", "subxt", "subxt-signer", "thiserror", @@ -32129,9 +31839,9 @@ dependencies = [ [[package]] name = "zombienet-prom-metrics-parser" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961e30be45b34f6ebeabf29ee2f47b0cd191ea62e40c064752572207509a6f5c" +checksum = "23702db0819a050c8a0130a769b105695137020a64207b4597aa021f06924552" dependencies = [ "pest", "pest_derive", @@ -32140,9 +31850,9 @@ dependencies = [ [[package]] name = "zombienet-provider" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab0f7f01780b7c99a6c40539d195d979f234305f32808d547438b50829d44262" +checksum = "83e903843c62cd811e7730ccc618dcd14444d20e8aadfcd7d7561c7b47d8f984" dependencies = [ "anyhow", "async-trait", @@ -32151,7 +31861,7 @@ dependencies = [ "hex", "k8s-openapi", "kube", - "nix 0.27.1", + "nix 0.29.0", "regex", "reqwest 0.11.27", "serde", @@ -32171,9 +31881,9 @@ dependencies = [ [[package]] name = "zombienet-sdk" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a3c5f2d657235b3ab7dc384677e63cde21983029e99106766ecd49e9f8d7f3" +checksum = "e457b12c8fdc7003c12dd56855da09812ac11dd232e4ec01acccb2899fe05e44" dependencies = [ "async-trait", "futures", @@ -32189,14 +31899,14 @@ dependencies = [ [[package]] name = "zombienet-support" -version = "0.2.19" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "296f887ea88e07edd771f8e1d0dec5297a58b422f4b884a6292a21ebe03277cb" +checksum = "43547d65b19a92cf0ee44380239d82ef345e7d26f7b04b9e0ecf48496af6346b" dependencies = [ "anyhow", "async-trait", "futures", - "nix 0.27.1", + "nix 0.29.0", "rand", "regex", "reqwest 0.11.27", diff --git a/Cargo.toml b/Cargo.toml index c917a8a8fead..c30a9949e85e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1391,7 +1391,7 @@ xcm-procedural = { path = "polkadot/xcm/procedural", default-features = false } xcm-runtime-apis = { path = "polkadot/xcm/xcm-runtime-apis", default-features = false } xcm-simulator = { path = "polkadot/xcm/xcm-simulator", default-features = false } zeroize = { version = "1.7.0", default-features = false } -zombienet-sdk = { version = "0.2.19" } +zombienet-sdk = { version = "0.2.20" } zstd = { version = "0.12.4", default-features = false } [profile.release] diff --git a/polkadot/zombienet-sdk-tests/Cargo.toml b/polkadot/zombienet-sdk-tests/Cargo.toml index 120857c9a42e..ba7517ddce66 100644 --- a/polkadot/zombienet-sdk-tests/Cargo.toml +++ b/polkadot/zombienet-sdk-tests/Cargo.toml @@ -12,6 +12,7 @@ anyhow = { workspace = true } codec = { workspace = true, features = ["derive"] } env_logger = { workspace = true } log = { workspace = true } +polkadot-primitives = { workspace = true, default-features = true } serde = { workspace = true } serde_json = { workspace = true } subxt = { workspace = true, features = ["substrate-compat"] } diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/basic_3cores.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/basic_3cores.rs new file mode 100644 index 000000000000..42aa83d9da7a --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/basic_3cores.rs @@ -0,0 +1,135 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test that a parachain that uses a basic collator (like adder-collator) with elastic scaling +// can achieve full throughput of 3 candidates per block. + +use anyhow::anyhow; + +use crate::helpers::{ + assert_para_throughput, rococo, + rococo::runtime_types::{ + pallet_broker::coretime_interface::CoreAssignment, + polkadot_runtime_parachains::assigner_coretime::PartsOf57600, + }, +}; +use polkadot_primitives::Id as ParaId; +use serde_json::json; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn basic_3cores_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "num_cores": 2, + "max_validators_per_core": 1 + }, + "async_backing_params": { + "max_candidate_depth": 6, + "allowed_ancestry_len": 2 + } + } + } + })) + // Have to set a `with_node` outside of the loop below, so that `r` has the right + // type. + .with_node(|node| node.with_name("validator-0")); + + (1..4).fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + p.with_id(2000) + .with_default_command("adder-collator") + .cumulus_based(false) + .with_default_image(images.cumulus.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_collator(|n| n.with_name("adder-2000")) + }) + .with_parachain(|p| { + p.with_id(2001) + .with_default_command("adder-collator") + .cumulus_based(false) + .with_default_image(images.cumulus.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_collator(|n| n.with_name("adder-2001")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + + let relay_client: OnlineClient = relay_node.wait_client().await?; + let alice = dev::alice(); + + // Assign two extra cores to adder-2000. + relay_client + .tx() + .sign_and_submit_then_watch_default( + &rococo::tx() + .sudo() + .sudo(rococo::runtime_types::rococo_runtime::RuntimeCall::Utility( + rococo::runtime_types::pallet_utility::pallet::Call::batch { + calls: vec![ + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 0, + begin: 0, + assignment: vec![(CoreAssignment::Task(2000), PartsOf57600(57600))], + end_hint: None + } + ), + rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 1, + begin: 0, + assignment: vec![(CoreAssignment::Task(2000), PartsOf57600(57600))], + end_hint: None + } + ), + ], + }, + )), + &alice, + ) + .await? + .wait_for_finalized_success() + .await?; + + log::info!("2 more cores assigned to adder-2000"); + + assert_para_throughput( + &relay_client, + 15, + [(ParaId::from(2000), 40..46), (ParaId::from(2001), 12..16)] + .into_iter() + .collect(), + ) + .await?; + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs new file mode 100644 index 000000000000..f83400d2b22a --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs @@ -0,0 +1,133 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test that a paraid that doesn't use elastic scaling which acquired multiple cores does not brick +// itself if ElasticScalingMVP feature is enabled in genesis. + +use anyhow::anyhow; + +use crate::helpers::{ + assert_finalized_block_height, assert_para_throughput, rococo, + rococo::runtime_types::{ + pallet_broker::coretime_interface::CoreAssignment, + polkadot_runtime_parachains::assigner_coretime::PartsOf57600, + }, +}; +use polkadot_primitives::{CoreIndex, Id as ParaId}; +use serde_json::json; +use std::collections::{BTreeMap, VecDeque}; +use subxt::{OnlineClient, PolkadotConfig}; +use subxt_signer::sr25519::dev; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn doesnt_break_parachains_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "num_cores": 1, + "max_validators_per_core": 2 + }, + "async_backing_params": { + "max_candidate_depth": 6, + "allowed_ancestry_len": 2 + } + } + } + })) + // Have to set a `with_node` outside of the loop below, so that `r` has the right + // type. + .with_node(|node| node.with_name("validator-0")); + + (1..4).fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + // Use rococo-parachain default, which has 6 second slot time. Also, don't use + // slot-based collator. + p.with_id(2000) + .with_default_command("polkadot-parachain") + .with_default_image(images.cumulus.as_str()) + .with_default_args(vec![("-lparachain=debug,aura=debug").into()]) + .with_collator(|n| n.with_name("collator-2000")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + let para_node = network.get_node("collator-2000")?; + + let relay_client: OnlineClient = relay_node.wait_client().await?; + let alice = dev::alice(); + + relay_client + .tx() + .sign_and_submit_then_watch_default( + &rococo::tx() + .sudo() + .sudo(rococo::runtime_types::rococo_runtime::RuntimeCall::Coretime( + rococo::runtime_types::polkadot_runtime_parachains::coretime::pallet::Call::assign_core { + core: 0, + begin: 0, + assignment: vec![(CoreAssignment::Task(2000), PartsOf57600(57600))], + end_hint: None + } + )), + &alice, + ) + .await? + .wait_for_finalized_success() + .await?; + + log::info!("1 more core assigned to the parachain"); + + let para_id = ParaId::from(2000); + // Expect the parachain to be making normal progress, 1 candidate backed per relay chain block. + assert_para_throughput(&relay_client, 15, [(para_id, 13..16)].into_iter().collect()).await?; + + let para_client = para_node.wait_client().await?; + // Assert the parachain finalized block height is also on par with the number of backed + // candidates. + assert_finalized_block_height(¶_client, 12..16).await?; + + // Sanity check that indeed the parachain has two assigned cores. + let cq = relay_client + .runtime_api() + .at_latest() + .await? + .call_raw::>>("ParachainHost_claim_queue", None) + .await?; + + assert_eq!( + cq, + [ + (CoreIndex(0), [para_id, para_id].into_iter().collect()), + (CoreIndex(1), [para_id, para_id].into_iter().collect()), + ] + .into_iter() + .collect() + ); + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs index bb296a419df1..9cfd5db5a096 100644 --- a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/mod.rs @@ -1,8 +1,6 @@ // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 -#[subxt::subxt(runtime_metadata_path = "metadata-files/rococo-local.scale")] -pub mod rococo {} - -mod helpers; +mod basic_3cores; +mod doesnt_break_parachains; mod slot_based_3cores; diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs index 41ec1250ecc4..aa9f41320135 100644 --- a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs @@ -6,14 +6,14 @@ use anyhow::anyhow; -use super::{ - helpers::assert_para_throughput, - rococo, +use crate::helpers::{ + assert_finalized_block_height, assert_para_throughput, rococo, rococo::runtime_types::{ pallet_broker::coretime_interface::CoreAssignment, polkadot_runtime_parachains::assigner_coretime::PartsOf57600, }, }; +use polkadot_primitives::Id as ParaId; use serde_json::json; use subxt::{OnlineClient, PolkadotConfig}; use subxt_signer::sr25519::dev; @@ -63,7 +63,6 @@ async fn slot_based_3cores_test() -> Result<(), anyhow::Error> { .with_default_command("test-parachain") .with_default_image(images.cumulus.as_str()) .with_chain("elastic-scaling-mvp") - .with_default_args(vec![("--experimental-use-slot-based").into()]) .with_default_args(vec![ ("--experimental-use-slot-based").into(), ("-lparachain=debug,aura=debug").into(), @@ -93,6 +92,8 @@ async fn slot_based_3cores_test() -> Result<(), anyhow::Error> { let network = spawn_fn(config).await?; let relay_node = network.get_node("validator-0")?; + let para_node_elastic = network.get_node("collator-elastic")?; + let para_node_elastic_mvp = network.get_node("collator-elastic-mvp")?; let relay_client: OnlineClient = relay_node.wait_client().await?; let alice = dev::alice(); @@ -156,10 +157,17 @@ async fn slot_based_3cores_test() -> Result<(), anyhow::Error> { assert_para_throughput( &relay_client, 15, - [(2100, 39..46), (2200, 39..46)].into_iter().collect(), + [(ParaId::from(2100), 39..46), (ParaId::from(2200), 39..46)] + .into_iter() + .collect(), ) .await?; + // Assert the parachain finalized block height is also on par with the number of backed + // candidates. + assert_finalized_block_height(¶_node_elastic.wait_client().await?, 36..46).await?; + assert_finalized_block_height(¶_node_elastic_mvp.wait_client().await?, 36..46).await?; + log::info!("Test finished successfully"); Ok(()) diff --git a/polkadot/zombienet-sdk-tests/tests/functional/async_backing_6_seconds_rate.rs b/polkadot/zombienet-sdk-tests/tests/functional/async_backing_6_seconds_rate.rs new file mode 100644 index 000000000000..14f86eb130f7 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/functional/async_backing_6_seconds_rate.rs @@ -0,0 +1,95 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test we are producing 12-second parachain blocks if using an old collator, pre async-backing. + +use anyhow::anyhow; + +use crate::helpers::{assert_finalized_block_height, assert_para_throughput}; +use polkadot_primitives::Id as ParaId; +use serde_json::json; +use subxt::{OnlineClient, PolkadotConfig}; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn async_backing_6_seconds_rate_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "group_rotation_frequency": 4, + "lookahead": 2, + "max_candidate_depth": 3, + "allowed_ancestry_len": 2, + }, + } + } + })) + .with_node(|node| node.with_name("validator-0")); + + (1..12) + .fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + p.with_id(2000) + .with_default_command("adder-collator") + .with_default_image( + std::env::var("COL_IMAGE") + .unwrap_or("docker.io/paritypr/colander:latest".to_string()) + .as_str(), + ) + .cumulus_based(false) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_collator(|n| n.with_name("collator-adder-2000")) + }) + .with_parachain(|p| { + p.with_id(2001) + .with_default_command("polkadot-parachain") + .with_default_image(images.cumulus.as_str()) + .with_default_args(vec![("-lparachain=debug,aura=debug").into()]) + .with_collator(|n| n.with_name("collator-2001")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + let para_node_2001 = network.get_node("collator-2001")?; + + let relay_client: OnlineClient = relay_node.wait_client().await?; + + assert_para_throughput( + &relay_client, + 15, + [(ParaId::from(2000), 11..16), (ParaId::from(2001), 11..16)] + .into_iter() + .collect(), + ) + .await?; + + // Assert the parachain finalized block height is also on par with the number of backed + // candidates. We can only do this for the collator based on cumulus. + assert_finalized_block_height(¶_node_2001.wait_client().await?, 10..16).await?; + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/functional/mod.rs b/polkadot/zombienet-sdk-tests/tests/functional/mod.rs new file mode 100644 index 000000000000..ecdab38e1d28 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/functional/mod.rs @@ -0,0 +1,5 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +mod async_backing_6_seconds_rate; +mod sync_backing; diff --git a/polkadot/zombienet-sdk-tests/tests/functional/sync_backing.rs b/polkadot/zombienet-sdk-tests/tests/functional/sync_backing.rs new file mode 100644 index 000000000000..6da45e284491 --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/functional/sync_backing.rs @@ -0,0 +1,74 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test we are producing 12-second parachain blocks if using an old collator, pre async-backing. + +use anyhow::anyhow; + +use crate::helpers::{assert_finalized_block_height, assert_para_throughput}; +use polkadot_primitives::Id as ParaId; +use serde_json::json; +use subxt::{OnlineClient, PolkadotConfig}; +use zombienet_sdk::NetworkConfigBuilder; + +#[tokio::test(flavor = "multi_thread")] +async fn sync_backing_test() -> Result<(), anyhow::Error> { + let _ = env_logger::try_init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + let images = zombienet_sdk::environment::get_images_from_env(); + + let config = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("rococo-local") + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "group_rotation_frequency": 4, + }, + } + } + })) + .with_node(|node| node.with_name("validator-0")); + + (1..5).fold(r, |acc, i| acc.with_node(|node| node.with_name(&format!("validator-{i}")))) + }) + .with_parachain(|p| { + p.with_id(2000) + .with_default_command("polkadot-parachain") + // This must be a very old polkadot-parachain image, pre async backing + .with_default_image(images.cumulus.as_str()) + .with_default_args(vec![("-lparachain=debug,aura=debug").into()]) + .with_collator(|n| n.with_name("collator-2000")) + }) + .build() + .map_err(|e| { + let errs = e.into_iter().map(|e| e.to_string()).collect::>().join(" "); + anyhow!("config errs: {errs}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + let network = spawn_fn(config).await?; + + let relay_node = network.get_node("validator-0")?; + let para_node = network.get_node("collator-2000")?; + + let relay_client: OnlineClient = relay_node.wait_client().await?; + + assert_para_throughput(&relay_client, 15, [(ParaId::from(2000), 5..9)].into_iter().collect()) + .await?; + + // Assert the parachain finalized block height is also on par with the number of backed + // candidates. + assert_finalized_block_height(¶_node.wait_client().await?, 5..9).await?; + + log::info!("Test finished successfully"); + + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/helpers.rs b/polkadot/zombienet-sdk-tests/tests/helpers/mod.rs similarity index 65% rename from polkadot/zombienet-sdk-tests/tests/elastic_scaling/helpers.rs rename to polkadot/zombienet-sdk-tests/tests/helpers/mod.rs index 7d4ad4a1dd8b..470345ca4d62 100644 --- a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/helpers.rs +++ b/polkadot/zombienet-sdk-tests/tests/helpers/mod.rs @@ -1,19 +1,22 @@ // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 -use super::rococo; +use polkadot_primitives::Id as ParaId; use std::{collections::HashMap, ops::Range}; use subxt::{OnlineClient, PolkadotConfig}; +#[subxt::subxt(runtime_metadata_path = "metadata-files/rococo-local.scale")] +pub mod rococo {} + // Helper function for asserting the throughput of parachains (total number of backed candidates in // a window of relay chain blocks), after the first session change. pub async fn assert_para_throughput( relay_client: &OnlineClient, stop_at: u32, - expected_candidate_ranges: HashMap>, + expected_candidate_ranges: HashMap>, ) -> Result<(), anyhow::Error> { let mut blocks_sub = relay_client.blocks().subscribe_finalized().await?; - let mut candidate_count: HashMap = HashMap::new(); + let mut candidate_count: HashMap = HashMap::new(); let mut current_block_count = 0; let mut had_first_session_change = false; @@ -31,7 +34,7 @@ pub async fn assert_para_throughput( current_block_count += 1; for event in events.find::() { - *(candidate_count.entry(event?.0.descriptor.para_id.0).or_default()) += 1; + *(candidate_count.entry(event?.0.descriptor.para_id.0.into()).or_default()) += 1; } } @@ -58,3 +61,21 @@ pub async fn assert_para_throughput( Ok(()) } + +// Helper function for retrieving the latest finalized block height and asserting it's within a +// range. +pub async fn assert_finalized_block_height( + client: &OnlineClient, + expected_range: Range, +) -> Result<(), anyhow::Error> { + if let Some(block) = client.blocks().subscribe_finalized().await?.next().await { + let height = block?.number(); + log::info!("Finalized block number {height}"); + + assert!( + expected_range.contains(&height), + "Finalized block number {height} not within range {expected_range:?}" + ); + } + Ok(()) +} diff --git a/polkadot/zombienet-sdk-tests/tests/lib.rs b/polkadot/zombienet-sdk-tests/tests/lib.rs index 977e0f90b1c9..9feb9775e450 100644 --- a/polkadot/zombienet-sdk-tests/tests/lib.rs +++ b/polkadot/zombienet-sdk-tests/tests/lib.rs @@ -1,7 +1,12 @@ // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 +#[cfg(feature = "zombie-metadata")] +mod helpers; + #[cfg(feature = "zombie-metadata")] mod elastic_scaling; #[cfg(feature = "zombie-metadata")] +mod functional; +#[cfg(feature = "zombie-metadata")] mod smoke; diff --git a/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs b/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs index 2da2436a1111..59a71a83e01e 100644 --- a/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs +++ b/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs @@ -10,21 +10,24 @@ //! normal parachain runtime WILL mess things up. use anyhow::anyhow; -#[subxt::subxt(runtime_metadata_path = "metadata-files/rococo-local.scale")] -pub mod rococo {} #[subxt::subxt(runtime_metadata_path = "metadata-files/coretime-rococo-local.scale")] mod coretime_rococo {} -use rococo::runtime_types::{ - staging_xcm::v4::{ - asset::{Asset, AssetId, Assets, Fungibility}, - junction::Junction, - junctions::Junctions, - location::Location, +use crate::helpers::rococo::{ + self as rococo_api, + runtime_types::{ + polkadot_parachain_primitives::primitives, + staging_xcm::v4::{ + asset::{Asset, AssetId, Assets, Fungibility}, + junction::Junction, + junctions::Junctions, + location::Location, + }, + xcm::{VersionedAssets, VersionedLocation}, }, - xcm::{VersionedAssets, VersionedLocation}, }; + use serde_json::json; use std::{fmt::Display, sync::Arc}; use subxt::{events::StaticEvent, utils::AccountId32, OnlineClient, PolkadotConfig}; @@ -41,8 +44,6 @@ use coretime_rococo::{ }, }; -use rococo::{self as rococo_api, runtime_types::polkadot_parachain_primitives::primitives}; - type CoretimeRuntimeCall = coretime_api::runtime_types::coretime_rococo_runtime::RuntimeCall; type CoretimeUtilityCall = coretime_api::runtime_types::pallet_utility::pallet::Call; type CoretimeBrokerCall = coretime_api::runtime_types::pallet_broker::pallet::Call; diff --git a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml deleted file mode 100644 index 611978a33a5f..000000000000 --- a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.toml +++ /dev/null @@ -1,49 +0,0 @@ -[settings] -timeout = 1000 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] - max_candidate_depth = 6 - allowed_ancestry_len = 2 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - max_validators_per_core = 1 - num_cores = 3 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.approval_voting_params] - max_approval_coalesce_count = 5 - -[relaychain] -default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" -chain = "rococo-local" -default_command = "polkadot" - - [relaychain.default_resources] - limits = { memory = "4G", cpu = "3" } - requests = { memory = "4G", cpu = "3" } - - [[relaychain.node_groups]] - name = "elastic-validator" - count = 5 - args = [ "-lparachain=debug,parachain::candidate-backing=trace,parachain::provisioner=trace,parachain::prospective-parachains=trace,runtime=debug"] - -{% for id in range(2000,2002) %} -[[parachains]] -id = {{id}} -addToGenesis = true - [parachains.default_resources] - limits = { memory = "4G", cpu = "3" } - requests = { memory = "4G", cpu = "3" } - - [parachains.collator] - name = "some-parachain" - image = "{{COL_IMAGE}}" - command = "adder-collator" - args = ["-lparachain::collation-generation=trace,parachain::collator-protocol=trace,parachain=debug"] - -{% endfor %} - -# This represents the layout of the adder collator block header. -[types.Header] -number = "u64" -parent_hash = "Hash" -post_state = "Hash" diff --git a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl b/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl deleted file mode 100644 index d47ef8f415f7..000000000000 --- a/polkadot/zombienet_tests/elastic_scaling/0001-basic-3cores-6s-blocks.zndsl +++ /dev/null @@ -1,28 +0,0 @@ -Description: Test with adder collator using 3 cores and async backing -Network: ./0001-basic-3cores-6s-blocks.toml -Creds: config - -# Check authority status. -elastic-validator-0: reports node_roles is 4 -elastic-validator-1: reports node_roles is 4 -elastic-validator-2: reports node_roles is 4 -elastic-validator-3: reports node_roles is 4 -elastic-validator-4: reports node_roles is 4 - - -# Register 2 extra cores to this some-parachain. -elastic-validator-0: js-script ./assign-core.js with "0,2000,57600" return is 0 within 600 seconds -elastic-validator-0: js-script ./assign-core.js with "1,2000,57600" return is 0 within 600 seconds - -# Wait for 20 relay chain blocks -elastic-validator-0: reports substrate_block_height{status="best"} is at least 20 within 600 seconds - -# Non elastic parachain should progress normally -some-parachain-1: count of log lines containing "Parachain velocity: 1" is at least 5 within 20 seconds -# Sanity -some-parachain-1: count of log lines containing "Parachain velocity: 2" is 0 - -# Parachain should progress 3 blocks per relay chain block ideally, however CI might not be -# the most performant environment so we'd just use a lower bound of 2 blocks per RCB -elastic-validator-0: parachain 2000 block height is at least 20 within 200 seconds - diff --git a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml deleted file mode 100644 index 046d707cc1e8..000000000000 --- a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.toml +++ /dev/null @@ -1,40 +0,0 @@ -[settings] -timeout = 1000 -bootnode = true - -[relaychain.genesis.runtimeGenesis.patch.configuration.config] - needed_approvals = 4 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - max_validators_per_core = 2 - num_cores = 2 - -[relaychain] -default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" -chain = "rococo-local" -default_command = "polkadot" - -[relaychain.default_resources] -limits = { memory = "4G", cpu = "2" } -requests = { memory = "2G", cpu = "1" } - - [[relaychain.nodes]] - name = "alice" - validator = "true" - - [[relaychain.node_groups]] - name = "validator" - count = 3 - args = [ "-lparachain=debug,runtime=debug"] - -[[parachains]] -id = 2000 -default_command = "polkadot-parachain" -add_to_genesis = false -register_para = true -onboard_as_parachain = false - - [parachains.collator] - name = "collator2000" - command = "polkadot-parachain" - args = [ "-lparachain=debug", "--experimental-use-slot-based" ] diff --git a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl b/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl deleted file mode 100644 index 0cfc29f532d1..000000000000 --- a/polkadot/zombienet_tests/elastic_scaling/0002-elastic-scaling-doesnt-break-parachains.zndsl +++ /dev/null @@ -1,20 +0,0 @@ -Description: Test that a paraid acquiring multiple cores does not brick itself if ElasticScalingMVP feature is enabled in genesis -Network: ./0002-elastic-scaling-doesnt-break-parachains.toml -Creds: config - -# Check authority status. -validator: reports node_roles is 4 - -validator: reports substrate_block_height{status="finalized"} is at least 10 within 100 seconds - -# Ensure parachain was able to make progress. -validator: parachain 2000 block height is at least 10 within 200 seconds - -# Register the second core assigned to this parachain. -alice: js-script ./assign-core.js with "0,2000,57600" return is 0 within 600 seconds -alice: js-script ./assign-core.js with "1,2000,57600" return is 0 within 600 seconds - -validator: reports substrate_block_height{status="finalized"} is at least 35 within 100 seconds - -# Ensure parachain is now making progress. -validator: parachain 2000 block height is at least 30 within 200 seconds diff --git a/polkadot/zombienet_tests/elastic_scaling/assign-core.js b/polkadot/zombienet_tests/elastic_scaling/assign-core.js deleted file mode 120000 index eeb6402c06f5..000000000000 --- a/polkadot/zombienet_tests/elastic_scaling/assign-core.js +++ /dev/null @@ -1 +0,0 @@ -../assign-core.js \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml deleted file mode 100644 index b776622fdce3..000000000000 --- a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.toml +++ /dev/null @@ -1,54 +0,0 @@ -[settings] -timeout = 1000 - -[relaychain] -default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" -chain = "rococo-local" - -[relaychain.genesis.runtimeGenesis.patch.configuration.config] - needed_approvals = 4 - relay_vrf_modulo_samples = 6 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] - max_candidate_depth = 3 - allowed_ancestry_len = 2 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - lookahead = 2 - group_rotation_frequency = 4 - - -[relaychain.default_resources] -limits = { memory = "4G", cpu = "2" } -requests = { memory = "2G", cpu = "1" } - - [[relaychain.node_groups]] - name = "alice" - args = [ "-lparachain=debug" ] - count = 12 - -[[parachains]] -id = 2000 -addToGenesis = true -genesis_state_generator = "undying-collator export-genesis-state --pov-size=100000 --pvf-complexity=1" - - [parachains.collator] - name = "collator01" - image = "{{COL_IMAGE}}" - command = "undying-collator" - args = ["-lparachain=debug", "--pov-size=100000", "--pvf-complexity=1", "--parachain-id=2000"] - -[[parachains]] -id = 2001 -cumulus_based = true - - [parachains.collator] - name = "collator02" - image = "{{CUMULUS_IMAGE}}" - command = "polkadot-parachain" - args = ["-lparachain=debug"] - -[types.Header] -number = "u64" -parent_hash = "Hash" -post_state = "Hash" \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl b/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl deleted file mode 100644 index 0d01af82833e..000000000000 --- a/polkadot/zombienet_tests/functional/0011-async-backing-6-seconds-rate.zndsl +++ /dev/null @@ -1,20 +0,0 @@ -Description: Test we are producing blocks at 6 seconds clip -Network: ./0011-async-backing-6-seconds-rate.toml -Creds: config - -# Check authority status. -alice: reports node_roles is 4 - -# Ensure parachains are registered. -alice: parachain 2000 is registered within 60 seconds -alice: parachain 2001 is registered within 60 seconds - -# Ensure parachains made progress. -alice: reports substrate_block_height{status="finalized"} is at least 10 within 100 seconds - -# This parachains should produce blocks at 6s clip, let's assume an 8s rate, allowing for -# some slots to be missed on slower machines -alice: parachain 2000 block height is at least 30 within 240 seconds -# This should already have produced the needed blocks -alice: parachain 2001 block height is at least 30 within 6 seconds - diff --git a/polkadot/zombienet_tests/functional/0017-sync-backing.toml b/polkadot/zombienet_tests/functional/0017-sync-backing.toml deleted file mode 100644 index 2550054c8dad..000000000000 --- a/polkadot/zombienet_tests/functional/0017-sync-backing.toml +++ /dev/null @@ -1,48 +0,0 @@ -[settings] -timeout = 1000 - -[relaychain] -default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" -chain = "rococo-local" - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.async_backing_params] - max_candidate_depth = 0 - allowed_ancestry_len = 0 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - lookahead = 2 - group_rotation_frequency = 4 - -[relaychain.default_resources] -limits = { memory = "4G", cpu = "2" } -requests = { memory = "2G", cpu = "1" } - - [[relaychain.node_groups]] - name = "alice" - args = [ "-lparachain=debug" ] - count = 10 - -[[parachains]] -id = 2000 -addToGenesis = true - - [parachains.collator] - name = "collator01" - image = "{{COL_IMAGE}}" - command = "adder-collator" - args = ["-lparachain=debug"] - -[[parachains]] -id = 2001 -cumulus_based = true - - [parachains.collator] - name = "collator02" - image = "{{CUMULUS_IMAGE}}" - command = "polkadot-parachain" - args = ["-lparachain=debug"] - -[types.Header] -number = "u64" -parent_hash = "Hash" -post_state = "Hash" \ No newline at end of file diff --git a/polkadot/zombienet_tests/functional/0017-sync-backing.zndsl b/polkadot/zombienet_tests/functional/0017-sync-backing.zndsl deleted file mode 100644 index a53de784b2d1..000000000000 --- a/polkadot/zombienet_tests/functional/0017-sync-backing.zndsl +++ /dev/null @@ -1,22 +0,0 @@ -Description: Test we are producing 12-second parachain blocks if sync backing is configured -Network: ./0017-sync-backing.toml -Creds: config - -# Check authority status. -alice: reports node_roles is 4 - -# Ensure parachains are registered. -alice: parachain 2000 is registered within 60 seconds -alice: parachain 2001 is registered within 60 seconds - -# Ensure parachains made progress. -alice: reports substrate_block_height{status="finalized"} is at least 10 within 100 seconds - -# This parachains should produce blocks at 12s clip, let's assume an 14s rate, allowing for -# some slots to be missed on slower machines -alice: parachain 2000 block height is at least 21 within 300 seconds -alice: parachain 2000 block height is lower than 25 within 2 seconds - -# This should already have produced the needed blocks -alice: parachain 2001 block height is at least 21 within 10 seconds -alice: parachain 2001 block height is lower than 25 within 2 seconds From baa3bcc60ddab6a700a713e241ad6599feb046dd Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Tue, 7 Jan 2025 14:28:28 +0100 Subject: [PATCH 236/340] Fix defensive! macro to be used in umbrella crates (#7069) PR for #7054 Replaced frame_support with $crate from @gui1117 's suggestion to fix the dependency issue --------- Co-authored-by: command-bot <> --- prdoc/pr_7069.prdoc | 10 ++++++++++ substrate/frame/support/src/traits/misc.rs | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 prdoc/pr_7069.prdoc diff --git a/prdoc/pr_7069.prdoc b/prdoc/pr_7069.prdoc new file mode 100644 index 000000000000..a0fc5cafb020 --- /dev/null +++ b/prdoc/pr_7069.prdoc @@ -0,0 +1,10 @@ +title: Fix defensive! macro to be used in umbrella crates +doc: +- audience: Runtime Dev + description: |- + PR for #7054 + + Replaced frame_support with $crate from @gui1117 's suggestion to fix the dependency issue +crates: +- name: frame-support + bump: patch diff --git a/substrate/frame/support/src/traits/misc.rs b/substrate/frame/support/src/traits/misc.rs index 0dc3abdce956..9fef4383ad67 100644 --- a/substrate/frame/support/src/traits/misc.rs +++ b/substrate/frame/support/src/traits/misc.rs @@ -66,7 +66,7 @@ impl Get for VariantCountOf { #[macro_export] macro_rules! defensive { () => { - frame_support::__private::log::error!( + $crate::__private::log::error!( target: "runtime::defensive", "{}", $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR @@ -74,7 +74,7 @@ macro_rules! defensive { debug_assert!(false, "{}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR); }; ($error:expr $(,)?) => { - frame_support::__private::log::error!( + $crate::__private::log::error!( target: "runtime::defensive", "{}: {:?}", $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR, @@ -83,7 +83,7 @@ macro_rules! defensive { debug_assert!(false, "{}: {:?}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR, $error); }; ($error:expr, $proof:expr $(,)?) => { - frame_support::__private::log::error!( + $crate::__private::log::error!( target: "runtime::defensive", "{}: {:?}: {:?}", $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR, From f4f56f6cf819472fcbab7ef367ec521f26cb85cb Mon Sep 17 00:00:00 2001 From: wmjae Date: Tue, 7 Jan 2025 23:11:42 +0800 Subject: [PATCH 237/340] fix typos (#7068) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dónal Murray Co-authored-by: Dónal Murray Co-authored-by: Shawn Tabrizi --- docs/sdk/src/reference_docs/frame_benchmarking_weight.rs | 2 +- substrate/frame/balances/src/impl_currency.rs | 2 +- substrate/frame/benchmarking/src/v1.rs | 2 +- substrate/frame/elections-phragmen/src/lib.rs | 2 +- substrate/frame/recovery/src/lib.rs | 2 +- substrate/frame/support/src/storage/child.rs | 2 +- substrate/frame/support/src/storage/unhashed.rs | 2 +- substrate/frame/support/src/traits/preimages.rs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs b/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs index 68d7d31f67f3..98192bfd2a90 100644 --- a/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs +++ b/docs/sdk/src/reference_docs/frame_benchmarking_weight.rs @@ -96,7 +96,7 @@ //! Two ways exist to run the benchmarks of a runtime. //! //! 1. The old school way: Most Polkadot-SDK based nodes (such as the ones integrated in -//! [`templates`]) have an a `benchmark` subcommand integrated into themselves. +//! [`templates`]) have a `benchmark` subcommand integrated into themselves. //! 2. The more [`crate::reference_docs::omni_node`] compatible way of running the benchmarks would //! be using [`frame-omni-bencher`] CLI, which only relies on a runtime. //! diff --git a/substrate/frame/balances/src/impl_currency.rs b/substrate/frame/balances/src/impl_currency.rs index 23feb46b72ca..bc7e77c191db 100644 --- a/substrate/frame/balances/src/impl_currency.rs +++ b/substrate/frame/balances/src/impl_currency.rs @@ -632,7 +632,7 @@ where /// /// This is `Polite` and thus will not repatriate any funds which would lead the total balance /// to be less than the frozen amount. Returns `Ok` with the actual amount of funds moved, - /// which may be less than `value` since the operation is done an a `BestEffort` basis. + /// which may be less than `value` since the operation is done on a `BestEffort` basis. fn repatriate_reserved( slashed: &T::AccountId, beneficiary: &T::AccountId, diff --git a/substrate/frame/benchmarking/src/v1.rs b/substrate/frame/benchmarking/src/v1.rs index 64f93b22cf1b..99aad0301c12 100644 --- a/substrate/frame/benchmarking/src/v1.rs +++ b/substrate/frame/benchmarking/src/v1.rs @@ -1894,7 +1894,7 @@ macro_rules! add_benchmark { /// This macro allows users to easily generate a list of benchmarks for the pallets configured /// in the runtime. /// -/// To use this macro, first create a an object to store the list: +/// To use this macro, first create an object to store the list: /// /// ```ignore /// let mut list = Vec::::new(); diff --git a/substrate/frame/elections-phragmen/src/lib.rs b/substrate/frame/elections-phragmen/src/lib.rs index effbb6e786c0..fa1c48ee65ed 100644 --- a/substrate/frame/elections-phragmen/src/lib.rs +++ b/substrate/frame/elections-phragmen/src/lib.rs @@ -616,7 +616,7 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// A new term with new_members. This indicates that enough candidates existed to run - /// the election, not that enough have has been elected. The inner value must be examined + /// the election, not that enough have been elected. The inner value must be examined /// for this purpose. A `NewTerm(\[\])` indicates that some candidates got their bond /// slashed and none were elected, whilst `EmptyTerm` means that no candidates existed to /// begin with. diff --git a/substrate/frame/recovery/src/lib.rs b/substrate/frame/recovery/src/lib.rs index 4de1919cdc33..5a97b03cd239 100644 --- a/substrate/frame/recovery/src/lib.rs +++ b/substrate/frame/recovery/src/lib.rs @@ -403,7 +403,7 @@ pub mod pallet { .map_err(|e| e.error) } - /// Allow ROOT to bypass the recovery process and set an a rescuer account + /// Allow ROOT to bypass the recovery process and set a rescuer account /// for a lost account directly. /// /// The dispatch origin for this call must be _ROOT_. diff --git a/substrate/frame/support/src/storage/child.rs b/substrate/frame/support/src/storage/child.rs index 5ebba2693658..7109e9213b0f 100644 --- a/substrate/frame/support/src/storage/child.rs +++ b/substrate/frame/support/src/storage/child.rs @@ -163,7 +163,7 @@ pub fn kill_storage(child_info: &ChildInfo, limit: Option) -> KillStorageRe /// operating on the same prefix should pass `Some` and this value should be equal to the /// previous call result's `maybe_cursor` field. The only exception to this is when you can /// guarantee that the subsequent call is in a new block; in this case the previous call's result -/// cursor need not be passed in an a `None` may be passed instead. This exception may be useful +/// cursor need not be passed in and a `None` may be passed instead. This exception may be useful /// then making this call solely from a block-hook such as `on_initialize`. /// Returns [`MultiRemovalResults`] to inform about the result. Once the resultant `maybe_cursor` diff --git a/substrate/frame/support/src/storage/unhashed.rs b/substrate/frame/support/src/storage/unhashed.rs index 7f9bc93d7d81..495c50caa2d6 100644 --- a/substrate/frame/support/src/storage/unhashed.rs +++ b/substrate/frame/support/src/storage/unhashed.rs @@ -124,7 +124,7 @@ pub fn kill_prefix(prefix: &[u8], limit: Option) -> sp_io::KillStorageResul /// operating on the same prefix should pass `Some` and this value should be equal to the /// previous call result's `maybe_cursor` field. The only exception to this is when you can /// guarantee that the subsequent call is in a new block; in this case the previous call's result -/// cursor need not be passed in an a `None` may be passed instead. This exception may be useful +/// cursor need not be passed in and a `None` may be passed instead. This exception may be useful /// then making this call solely from a block-hook such as `on_initialize`. /// /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once the diff --git a/substrate/frame/support/src/traits/preimages.rs b/substrate/frame/support/src/traits/preimages.rs index 80020d8d0080..6e46a7489654 100644 --- a/substrate/frame/support/src/traits/preimages.rs +++ b/substrate/frame/support/src/traits/preimages.rs @@ -38,7 +38,7 @@ pub enum Bounded { /// for transitioning from legacy state. In the future we will make this a pure /// `Dummy` item storing only the final `dummy` field. Legacy { hash: H::Output, dummy: core::marker::PhantomData }, - /// A an bounded `Call`. Its encoding must be at most 128 bytes. + /// A bounded `Call`. Its encoding must be at most 128 bytes. Inline(BoundedInline), /// A hash of the call together with an upper limit for its size.` Lookup { hash: H::Output, len: u32 }, From a5780527041e39268fc8b05b0f3d098cde204883 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Tue, 7 Jan 2025 17:25:16 +0200 Subject: [PATCH 238/340] release: unset SKIP_WASM_BUILD (#7074) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Seems like I added `SKIP_WASM_BUILD=1` 💀 for arch64 binaries, which results in various errors like: https://github.com/paritytech/polkadot-sdk/issues/6966. This PR unsets the variable. Closes #6966. ## Integration People who found workarounds as in #6966 can consume the fixed binaries again. ## Review Notes I introduced SKIP_WASM_BUILD=1 for some reason for aarch64 (probably to speed up testing) and forgot to remove it. It slipped through and interfered with `stable2412` release artifacts. Needs backporting to `stable2412` and then rebuilding/overwriting the aarch64 artifacts. --------- Signed-off-by: Iulian Barbu --- .github/workflows/release-reusable-rc-buid.yml | 1 - prdoc/pr_7074.prdoc | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_7074.prdoc diff --git a/.github/workflows/release-reusable-rc-buid.yml b/.github/workflows/release-reusable-rc-buid.yml index 0222b2aa91e2..035b547603e1 100644 --- a/.github/workflows/release-reusable-rc-buid.yml +++ b/.github/workflows/release-reusable-rc-buid.yml @@ -149,7 +149,6 @@ jobs: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_DEFAULT_REGION: ${{ secrets.AWS_DEFAULT_REGION }} - SKIP_WASM_BUILD: 1 steps: - name: Checkout sources uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 diff --git a/prdoc/pr_7074.prdoc b/prdoc/pr_7074.prdoc new file mode 100644 index 000000000000..d49e5f8d831f --- /dev/null +++ b/prdoc/pr_7074.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Unset SKIP_WASM_BUILD=1 for aarch64 binaries release + +doc: + - audience: [ Node Dev, Runtime Dev] + description: + Fix the release pipeline environment by unsetting SKIP_WASM_BUILD=1 + so that aarch64 binaries are built so that they contain runtimes + accordingly. + +crates: [ ] From 645878a27115db52e5d63115699b4bbb89034067 Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Tue, 7 Jan 2025 18:17:10 +0100 Subject: [PATCH 239/340] adding warning when using default substrateWeight in production (#7046) PR for #3581 Added a cfg to show a deprecated warning message when using std --------- Co-authored-by: command-bot <> Co-authored-by: Adrian Catangiu --- prdoc/pr_7046.prdoc | 7 +++++++ templates/parachain/pallets/template/src/weights.rs | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 prdoc/pr_7046.prdoc diff --git a/prdoc/pr_7046.prdoc b/prdoc/pr_7046.prdoc new file mode 100644 index 000000000000..113cc9c7aac5 --- /dev/null +++ b/prdoc/pr_7046.prdoc @@ -0,0 +1,7 @@ +title: adding warning when using default substrateWeight in production +doc: +- audience: Runtime Dev + description: |- + PR for #3581 + Added a cfg to show a deprecated warning message when using std +crates: [] diff --git a/templates/parachain/pallets/template/src/weights.rs b/templates/parachain/pallets/template/src/weights.rs index 9295492bc20b..4d6dd5642a1e 100644 --- a/templates/parachain/pallets/template/src/weights.rs +++ b/templates/parachain/pallets/template/src/weights.rs @@ -39,6 +39,12 @@ pub trait WeightInfo { } /// Weights for pallet_template using the Substrate node and recommended hardware. +#[cfg_attr( + not(feature = "std"), + deprecated( + note = "SubstrateWeight is auto-generated and should not be used in production. Replace it with runtime benchmarked weights." + ) +)] pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// Storage: Template Something (r:0 w:1) From 4059282fc7b6ec965cc22a9a0df5920a4f3a4101 Mon Sep 17 00:00:00 2001 From: Alistair Singh Date: Tue, 7 Jan 2025 23:23:45 +0200 Subject: [PATCH 240/340] Snowbridge: Support bridging native ETH (#6855) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: 1. Use the 0x0000000000000000000000000000000000000000 token address as Native ETH. 2. Convert it to/from `{ parents: 2, interior: X1(GlobalConsensus(Ethereum{chain_id: 1})) }` when encountered. Onchain changes: This will require a governance request to register native ETH (with the above location) in the foreign assets pallet and make it sufficient. Related solidity changes: https://github.com/Snowfork/snowbridge/pull/1354 TODO: - [x] Emulated Tests --------- Co-authored-by: Vincent Geddes <117534+vgeddes@users.noreply.github.com> Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- .../pallets/inbound-queue/fixtures/src/lib.rs | 1 + .../fixtures/src/send_native_eth.rs | 95 +++++++++ .../primitives/router/src/inbound/mock.rs | 48 +++++ .../primitives/router/src/inbound/mod.rs | 16 +- .../primitives/router/src/inbound/tests.rs | 88 ++++++-- .../primitives/router/src/outbound/mod.rs | 5 + .../primitives/router/src/outbound/tests.rs | 40 ++++ .../bridges/bridge-hub-rococo/src/lib.rs | 3 +- .../bridges/bridge-hub-rococo/src/lib.rs | 1 + .../bridge-hub-rococo/src/tests/snowbridge.rs | 196 ++++++++++++++++-- prdoc/pr_6855.prdoc | 16 ++ 11 files changed, 478 insertions(+), 31 deletions(-) create mode 100755 bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_native_eth.rs create mode 100644 bridges/snowbridge/primitives/router/src/inbound/mock.rs create mode 100644 prdoc/pr_6855.prdoc diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs index 00adcdfa186a..cb4232376c6f 100644 --- a/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/lib.rs @@ -3,5 +3,6 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod register_token; +pub mod send_native_eth; pub mod send_token; pub mod send_token_to_penpal; diff --git a/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_native_eth.rs b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_native_eth.rs new file mode 100755 index 000000000000..d3e8d76e6b39 --- /dev/null +++ b/bridges/snowbridge/pallets/inbound-queue/fixtures/src/send_native_eth.rs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2023 Snowfork +// Generated, do not edit! +// See ethereum client README.md for instructions to generate + +use hex_literal::hex; +use snowbridge_beacon_primitives::{ + types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, +}; +use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; +use sp_core::U256; +use sp_std::vec; + +pub fn make_send_native_eth_message() -> InboundQueueFixture { + InboundQueueFixture { + message: Message { + event_log: Log { + address: hex!("87d1f7fdfee7f651fabc8bfcb6e086c278b77a7d").into(), + topics: vec![ + hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), + hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), + hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + ], + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa0000000000010000000000000000000000000000000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e8764817000000000000000000000000").into(), + }, + proof: Proof { + receipt_proof: (vec![ + hex!("17cd4d05dde30703008a4f213205923630cff8e6bc9d5d95a52716bfb5551fd7").to_vec(), + ], vec![ + hex!("f903b4822080b903ae02f903aa018301a7fcb9010000000000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000000000080000000000020000000000000000000800010100000000000000000000000000000000000200000000000000000000000000001000000040080008000000000000000000040000000021000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000200000000000000f9029ff9015d9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df884a024c5d2de620c6e25186ae16f6919eba93b6e2c1a33857cc419d9f3a00d6967e9a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000090a987b944cb1dcce5564e5fdecd7a54d3de27fea000000000000000000000000000000000000000000000000000000000000003e8b8c000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000208eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48f9013c9487d1f7fdfee7f651fabc8bfcb6e086c278b77a7df863a07153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84fa0c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539a05f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0b8c000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000005f00a736aa0000000000010000000000000000000000000000000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000e8764817000000000000000000000000").to_vec(), + ]), + execution_proof: ExecutionProof { + header: BeaconHeader { + slot: 246, + proposer_index: 7, + parent_root: hex!("4faaac5d2fa0b8884fe1175c7cac1c92aac9eba5a20b4302edb98a56428c5974").into(), + state_root: hex!("882c13f1d56df781e3444a78cae565bfa1c89822c86cdb0daea71f5351231580").into(), + body_root: hex!("c47eb72204b1ca567396dacef8b0214027eb7f0789330b55166085d1f9cb4c65").into(), + }, + ancestry_proof: Some(AncestryProof { + header_branch: vec![ + hex!("38e2454bc93c4cfafcea772b8531e4802bbd2561366620699096dd4e591bc488").into(), + hex!("3d7389fb144ccaeca8b8e1667ce1d1538dfceb50bf1e49c4b368a223f051fda3").into(), + hex!("0d49c9c24137ad4d86ebca2f36a159573a68b5d5d60e317776c77cc8b6093034").into(), + hex!("0fadc6735bcdc2793a5039a806fbf39984c39374ed4d272c1147e1c23df88983").into(), + hex!("3a058ad4b169eebb4c754c8488d41e56a7a0e5f8b55b5ec67452a8d326585c69").into(), + hex!("de200426caa9bc03f8e0033b4ef4df1db6501924b5c10fb7867e76db942b903c").into(), + hex!("48b578632bc40eebb517501f179ffdd06d762c03e9383df16fc651eeddd18806").into(), + hex!("98d9d6904b2a6a285db4c4ae59a07100cd38ec4d9fb7a16a10fe83ec99e6ba1d").into(), + hex!("1b2bbae6e684864b714654a60778664e63ba6c3c9bed8074ec1a0380fe5042e6").into(), + hex!("eb907a888eadf5a7e2bd0a3a5a9369e409c7aa688bd4cde758d5b608c6c82785").into(), + hex!("ffff0ad7e659772f9534c195c815efc4014ef1e1daed4404c06385d11192e92b").into(), + hex!("6cf04127db05441cd833107a52be852868890e4317e6a02ab47683aa75964220").into(), + hex!("b7d05f875f140027ef5118a2247bbb84ce8f2f0f1123623085daf7960c329f5f").into(), + ], + finalized_block_root: hex!("440615588532ce496a93d189cb0ef1df7cf67d529faee0fd03213ce26ea115e5").into(), + }), + execution_header: VersionedExecutionPayloadHeader::Deneb(deneb::ExecutionPayloadHeader { + parent_hash: hex!("a8c89213b7d7d2ac76462d89e6a7384374db905b657ad803d3c86f88f86c39df").into(), + fee_recipient: hex!("0000000000000000000000000000000000000000").into(), + state_root: hex!("a1e8175213a6a43da17fae65109245867cbc60e3ada16b8ac28c6b208761c772").into(), + receipts_root: hex!("17cd4d05dde30703008a4f213205923630cff8e6bc9d5d95a52716bfb5551fd7").into(), + logs_bloom: hex!("00000000000000000000000020000000000000000000004000000000000000000400000000000000000000001000000000000000000000000000000000000000000000000000000001080000000000000000000000000000000000000000080000000000020000000000000000000800010100000000000000000000000000000000000200000000000000000000000000001000000040080008000000000000000000040000000021000000002000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220000000000000000000000000000000000000000000000000000200000000000000").into(), + prev_randao: hex!("b9b26dc14ea8c57d069fde0c94ad31c2558365c3986a0c06558470f8c02e62ce").into(), + block_number: 246, + gas_limit: 62908420, + gas_used: 108540, + timestamp: 1734718384, + extra_data: hex!("d983010e08846765746888676f312e32322e358664617277696e").into(), + base_fee_per_gas: U256::from(7u64), + block_hash: hex!("878195e2ea83c74d475363d03d41a7fbfc4026d6e5bcffb713928253984a64a7").into(), + transactions_root: hex!("909139b3137666b4551b629ce6d9fb7e5e6f6def8a48d078448ec6600fe63c7f").into(), + withdrawals_root: hex!("792930bbd5baac43bcc798ee49aa8185ef76bb3b44ba62b91d86ae569e4bb535").into(), + blob_gas_used: 0, + excess_blob_gas: 0, + }), + execution_branch: vec![ + hex!("5d78e26ea639df17c2194ff925f782b9522009d58cfc60e3d34ba79a19f8faf1").into(), + hex!("b46f0c01805fe212e15907981b757e6c496b0cb06664224655613dcec82505bb").into(), + hex!("db56114e00fdd4c1f85c892bf35ac9a89289aaecb1ebd0a96cde606a748b5d71").into(), + hex!("3d84b2809a36450186e5169995a5e3cab55d751aee90fd8456b33d871ccaa463").into(), + ], + } + }, + }, + finalized_header: BeaconHeader { + slot: 608, + proposer_index: 3, + parent_root: hex!("f10c2349530dbd339a72886270e2e304bb68155af68c918c850acd9ab341350f").into(), + state_root: hex!("6df0ef4cbb4986a84ff0763727402b88636e6b5535022cd3ad6967b8dd799402").into(), + body_root: hex!("f66fc1c022f07f91c777ad5c464625fc0b43d3e7a45650567dce60011210f574").into(), + }, + block_roots_root: hex!("1c0dbf54db070770f5e573b72afe0aac2b0e3cf312107d1cd73bf64d7a2ed90c").into(), + } +} diff --git a/bridges/snowbridge/primitives/router/src/inbound/mock.rs b/bridges/snowbridge/primitives/router/src/inbound/mock.rs new file mode 100644 index 000000000000..537853b324f6 --- /dev/null +++ b/bridges/snowbridge/primitives/router/src/inbound/mock.rs @@ -0,0 +1,48 @@ +use crate::inbound::{MessageToXcm, TokenId}; +use frame_support::parameter_types; +use sp_runtime::{ + traits::{IdentifyAccount, MaybeEquivalence, Verify}, + MultiSignature, +}; +use xcm::{latest::WESTEND_GENESIS_HASH, prelude::*}; + +pub const CHAIN_ID: u64 = 11155111; +pub const NETWORK: NetworkId = Ethereum { chain_id: CHAIN_ID }; + +parameter_types! { + pub EthereumNetwork: NetworkId = NETWORK; + + pub const CreateAssetCall: [u8;2] = [53, 0]; + pub const CreateAssetExecutionFee: u128 = 2_000_000_000; + pub const CreateAssetDeposit: u128 = 100_000_000_000; + pub const SendTokenExecutionFee: u128 = 1_000_000_000; + pub const InboundQueuePalletInstance: u8 = 80; + pub UniversalLocation: InteriorLocation = + [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1002)].into(); + pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),Parachain(1000)]); +} + +type Signature = MultiSignature; +type AccountId = <::Signer as IdentifyAccount>::AccountId; +type Balance = u128; + +pub(crate) struct MockTokenIdConvert; +impl MaybeEquivalence for MockTokenIdConvert { + fn convert(_id: &TokenId) -> Option { + Some(Location::parent()) + } + fn convert_back(_loc: &Location) -> Option { + None + } +} + +pub(crate) type MessageConverter = MessageToXcm< + CreateAssetCall, + CreateAssetDeposit, + InboundQueuePalletInstance, + AccountId, + Balance, + MockTokenIdConvert, + UniversalLocation, + AssetHubFromEthereum, +>; diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index bc5d401cd4f7..1c210afb1f74 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -2,6 +2,8 @@ // SPDX-FileCopyrightText: 2023 Snowfork //! Converts messages from Ethereum to XCM messages +#[cfg(test)] +mod mock; #[cfg(test)] mod tests; @@ -394,10 +396,16 @@ where // Convert ERC20 token address to a location that can be understood by Assets Hub. fn convert_token_address(network: NetworkId, token: H160) -> Location { - Location::new( - 2, - [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], - ) + // If the token is `0x0000000000000000000000000000000000000000` then return the location of + // native Ether. + if token == H160([0; 20]) { + Location::new(2, [GlobalConsensus(network)]) + } else { + Location::new( + 2, + [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], + ) + } } /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign diff --git a/bridges/snowbridge/primitives/router/src/inbound/tests.rs b/bridges/snowbridge/primitives/router/src/inbound/tests.rs index 786aa594f653..11d7928602c6 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/tests.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/tests.rs @@ -1,21 +1,12 @@ use super::EthereumLocationsConverterFor; -use crate::inbound::CallIndex; -use frame_support::{assert_ok, parameter_types}; +use crate::inbound::{ + mock::*, Command, ConvertMessage, Destination, MessageV1, VersionedMessage, H160, +}; +use frame_support::assert_ok; use hex_literal::hex; use xcm::prelude::*; use xcm_executor::traits::ConvertLocation; -const NETWORK: NetworkId = Ethereum { chain_id: 11155111 }; - -parameter_types! { - pub EthereumNetwork: NetworkId = NETWORK; - - pub const CreateAssetCall: CallIndex = [1, 1]; - pub const CreateAssetExecutionFee: u128 = 123; - pub const CreateAssetDeposit: u128 = 891; - pub const SendTokenExecutionFee: u128 = 592; -} - #[test] fn test_ethereum_network_converts_successfully() { let expected_account: [u8; 32] = @@ -81,3 +72,74 @@ fn test_reanchor_all_assets() { assert_eq!(reanchored_asset_with_ethereum_context, asset.clone()); } } + +#[test] +fn test_convert_send_token_with_weth() { + const WETH: H160 = H160([0xff; 20]); + const AMOUNT: u128 = 1_000_000; + const FEE: u128 = 1_000; + const ACCOUNT_ID: [u8; 32] = [0xBA; 32]; + const MESSAGE: VersionedMessage = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH, + destination: Destination::AccountId32 { id: ACCOUNT_ID }, + amount: AMOUNT, + fee: FEE, + }, + }); + let result = MessageConverter::convert([1; 32].into(), MESSAGE); + assert_ok!(&result); + let (xcm, fee) = result.unwrap(); + assert_eq!(FEE, fee); + + let expected_assets = ReserveAssetDeposited( + vec![Asset { + id: AssetId(Location { + parents: 2, + interior: Junctions::X2( + [GlobalConsensus(NETWORK), AccountKey20 { network: None, key: WETH.into() }] + .into(), + ), + }), + fun: Fungible(AMOUNT), + }] + .into(), + ); + let actual_assets = xcm.into_iter().find(|x| matches!(x, ReserveAssetDeposited(..))); + assert_eq!(actual_assets, Some(expected_assets)) +} + +#[test] +fn test_convert_send_token_with_eth() { + const ETH: H160 = H160([0x00; 20]); + const AMOUNT: u128 = 1_000_000; + const FEE: u128 = 1_000; + const ACCOUNT_ID: [u8; 32] = [0xBA; 32]; + const MESSAGE: VersionedMessage = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: ETH, + destination: Destination::AccountId32 { id: ACCOUNT_ID }, + amount: AMOUNT, + fee: FEE, + }, + }); + let result = MessageConverter::convert([1; 32].into(), MESSAGE); + assert_ok!(&result); + let (xcm, fee) = result.unwrap(); + assert_eq!(FEE, fee); + + let expected_assets = ReserveAssetDeposited( + vec![Asset { + id: AssetId(Location { + parents: 2, + interior: Junctions::X1([GlobalConsensus(NETWORK)].into()), + }), + fun: Fungible(AMOUNT), + }] + .into(), + ); + let actual_assets = xcm.into_iter().find(|x| matches!(x, ReserveAssetDeposited(..))); + assert_eq!(actual_assets, Some(expected_assets)) +} diff --git a/bridges/snowbridge/primitives/router/src/outbound/mod.rs b/bridges/snowbridge/primitives/router/src/outbound/mod.rs index 3b5dbdb77c89..622c40807015 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/mod.rs @@ -289,8 +289,13 @@ where let (token, amount) = match reserve_asset { Asset { id: AssetId(inner_location), fun: Fungible(amount) } => match inner_location.unpack() { + // Get the ERC20 contract address of the token. (0, [AccountKey20 { network, key }]) if self.network_matches(network) => Some((H160(*key), *amount)), + // If there is no ERC20 contract address in the location then signal to the + // gateway that is a native Ether transfer by using + // `0x0000000000000000000000000000000000000000` as the token address. + (0, []) => Some((H160([0; 20]), *amount)), _ => None, }, _ => None, diff --git a/bridges/snowbridge/primitives/router/src/outbound/tests.rs b/bridges/snowbridge/primitives/router/src/outbound/tests.rs index 44f81ce31b3a..2a60f9f3e0ea 100644 --- a/bridges/snowbridge/primitives/router/src/outbound/tests.rs +++ b/bridges/snowbridge/primitives/router/src/outbound/tests.rs @@ -515,6 +515,46 @@ fn xcm_converter_convert_with_wildcard_all_asset_filter_succeeds() { assert_eq!(result, Ok((expected_payload, [0; 32]))); } +#[test] +fn xcm_converter_convert_with_native_eth_succeeds() { + let network = BridgedNetwork::get(); + + let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000"); + + // The asset is `{ parents: 0, interior: X1(Here) }` relative to ethereum. + let assets: Assets = vec![Asset { id: AssetId([].into()), fun: Fungible(1000) }].into(); + let filter: AssetFilter = Wild(All); + + let message: Xcm<()> = vec![ + WithdrawAsset(assets.clone()), + ClearOrigin, + BuyExecution { fees: assets.get(0).unwrap().clone(), weight_limit: Unlimited }, + DepositAsset { + assets: filter, + beneficiary: AccountKey20 { network: None, key: beneficiary_address }.into(), + }, + SetTopic([0; 32]), + ] + .into(); + + let mut converter = + XcmConverter::::new(&message, network, Default::default()); + + // The token address that is expected to be sent should be + // `0x0000000000000000000000000000000000000000`. The solidity will + // interpret this as a transfer of ETH. + let expected_payload = Command::AgentExecute { + agent_id: Default::default(), + command: AgentExecuteCommand::TransferToken { + token: H160([0; 20]), + recipient: beneficiary_address.into(), + amount: 1000, + }, + }; + let result = converter.convert(); + assert_eq!(result, Ok((expected_payload, [0; 32]))); +} + #[test] fn xcm_converter_convert_with_fees_less_than_reserve_yields_success() { let network = BridgedNetwork::get(); diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs index 5ef0993f70a1..43398eb8bd48 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/lib.rs @@ -16,7 +16,8 @@ pub mod genesis; pub use bridge_hub_rococo_runtime::{ - xcm_config::XcmConfig as BridgeHubRococoXcmConfig, EthereumBeaconClient, EthereumInboundQueue, + self as bridge_hub_rococo_runtime, xcm_config::XcmConfig as BridgeHubRococoXcmConfig, + EthereumBeaconClient, EthereumInboundQueue, ExistentialDeposit as BridgeHubRococoExistentialDeposit, RuntimeOrigin as BridgeHubRococoRuntimeOrigin, }; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs index 54bc395c86f0..f84d42cb29f8 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/lib.rs @@ -50,6 +50,7 @@ mod imports { AssetHubWestendParaPallet as AssetHubWestendPallet, }, bridge_hub_rococo_emulated_chain::{ + bridge_hub_rococo_runtime::bridge_to_ethereum_config::EthereumGatewayAddress, genesis::ED as BRIDGE_HUB_ROCOCO_ED, BridgeHubRococoExistentialDeposit, BridgeHubRococoParaPallet as BridgeHubRococoPallet, BridgeHubRococoRuntimeOrigin, BridgeHubRococoXcmConfig, EthereumBeaconClient, EthereumInboundQueue, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index c72d5045ddc0..6364ff9fe959 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -20,8 +20,8 @@ use hex_literal::hex; use rococo_westend_system_emulated_network::BridgeHubRococoParaSender as BridgeHubRococoSender; use snowbridge_core::{inbound::InboundQueueFixture, outbound::OperatingMode}; use snowbridge_pallet_inbound_queue_fixtures::{ - register_token::make_register_token_message, send_token::make_send_token_message, - send_token_to_penpal::make_send_token_to_penpal_message, + register_token::make_register_token_message, send_native_eth::make_send_native_eth_message, + send_token::make_send_token_message, send_token_to_penpal::make_send_token_to_penpal_message, }; use snowbridge_pallet_system; use snowbridge_router_primitives::inbound::{ @@ -238,7 +238,7 @@ fn register_weth_token_from_ethereum_to_asset_hub() { /// Tests the registering of a token as an asset on AssetHub, and then subsequently sending /// a token from Ethereum to AssetHub. #[test] -fn send_token_from_ethereum_to_asset_hub() { +fn send_weth_token_from_ethereum_to_asset_hub() { BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id().into(), INITIAL_FUND); // Fund ethereum sovereign on AssetHub @@ -278,7 +278,7 @@ fn send_token_from_ethereum_to_asset_hub() { /// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is /// still located on AssetHub. #[test] -fn send_token_from_ethereum_to_penpal() { +fn send_weth_from_ethereum_to_penpal() { let asset_hub_sovereign = BridgeHubRococo::sovereign_account_id_of(Location::new( 1, [Parachain(AssetHubRococo::para_id().into())], @@ -515,6 +515,176 @@ fn send_weth_asset_from_asset_hub_to_ethereum() { }); } +/// Tests the full cycle of eth transfers: +/// - sending a token to AssetHub +/// - returning the token to Ethereum +#[test] +fn send_eth_asset_from_asset_hub_to_ethereum_and_back() { + let ethereum_network: NetworkId = EthereumNetwork::get().into(); + let origin_location = (Parent, Parent, ethereum_network).into(); + + use ahr_xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; + let assethub_location = BridgeHubRococo::sibling_location_of(AssetHubRococo::para_id()); + let assethub_sovereign = BridgeHubRococo::sovereign_account_id_of(assethub_location); + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + + AssetHubRococo::force_default_xcm_version(Some(XCM_VERSION)); + BridgeHubRococo::force_default_xcm_version(Some(XCM_VERSION)); + AssetHubRococo::force_xcm_version(origin_location.clone(), XCM_VERSION); + + BridgeHubRococo::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + AssetHubRococo::fund_accounts(vec![ + (AssetHubRococoReceiver::get(), INITIAL_FUND), + (ethereum_sovereign.clone(), INITIAL_FUND), + ]); + + // Register ETH + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + origin_location.clone(), + ethereum_sovereign.into(), + true, + 1000, + )); + + assert_expected_events!( + AssetHubRococo, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::ForceCreated { .. }) => {}, + ] + ); + }); + const ETH_AMOUNT: u128 = 1_000_000_000_000_000_000; + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + // Set the gateway. This is needed because new fixtures use a different gateway address. + assert_ok!(::System::set_storage( + RuntimeOrigin::root(), + vec![( + EthereumGatewayAddress::key().to_vec(), + sp_core::H160(hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d")).encode(), + )], + )); + + // Construct SendToken message and sent to inbound queue + assert_ok!(send_inbound_message(make_send_native_eth_message())); + + // Check that the send token message was sent using xcm + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let _issued_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { + asset_id: origin_location.clone(), + owner: AssetHubRococoReceiver::get().into(), + amount: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + assert_expected_events!( + AssetHubRococo, + vec![ + _issued_event => {}, + ] + ); + let assets = + vec![Asset { id: AssetId(origin_location.clone()), fun: Fungible(ETH_AMOUNT) }]; + let multi_assets = VersionedAssets::from(Assets::from(assets)); + + let destination = origin_location.clone().into(); + + let beneficiary = VersionedLocation::from(Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + )); + + let free_balance_before = ::Balances::free_balance( + AssetHubRococoReceiver::get(), + ); + // Send the Weth back to Ethereum + ::PolkadotXcm::limited_reserve_transfer_assets( + RuntimeOrigin::signed(AssetHubRococoReceiver::get()), + Box::new(destination), + Box::new(beneficiary), + Box::new(multi_assets), + 0, + Unlimited, + ) + .unwrap(); + + let _burned_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { + asset_id: origin_location.clone(), + owner: AssetHubRococoReceiver::get().into(), + balance: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + let _destination = origin_location.clone(); + assert_expected_events!( + AssetHubRococo, + vec![ + _burned_event => {}, + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { + destination: _destination, .. + }) => {}, + ] + ); + + let free_balance_after = ::Balances::free_balance( + AssetHubRococoReceiver::get(), + ); + // Assert at least DefaultBridgeHubEthereumBaseFee charged from the sender + let free_balance_diff = free_balance_before - free_balance_after; + assert!(free_balance_diff > DefaultBridgeHubEthereumBaseFee::get()); + }); + + BridgeHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubRococo, + vec![ + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageAccepted {..}) => {}, + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued {..}) => {}, + ] + ); + + let events = BridgeHubRococo::events(); + // Check that the local fee was credited to the Snowbridge sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) + if *who == TREASURY_ACCOUNT.into() && *amount == 16903333 + )), + "Snowbridge sovereign takes local fee." + ); + // Check that the remote fee was credited to the AssetHub sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount }) + if *who == assethub_sovereign && *amount == 2680000000000, + )), + "AssetHub sovereign takes remote fee." + ); + }); +} + #[test] fn send_token_from_ethereum_to_asset_hub_fail_for_insufficient_fund() { // Insufficient fund @@ -565,7 +735,7 @@ fn register_weth_token_in_asset_hub_fail_for_insufficient_fee() { }); } -fn send_token_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u128) { +fn send_weth_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u128) { let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); let weth_asset_location: Location = Location::new(2, [ethereum_network_v5.into(), AccountKey20 { network: None, key: WETH }]); @@ -623,8 +793,8 @@ fn send_token_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u12 } #[test] -fn send_token_from_ethereum_to_existent_account_on_asset_hub() { - send_token_from_ethereum_to_asset_hub_with_fee(AssetHubRococoSender::get().into(), XCM_FEE); +fn send_weth_from_ethereum_to_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee(AssetHubRococoSender::get().into(), XCM_FEE); AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -640,8 +810,8 @@ fn send_token_from_ethereum_to_existent_account_on_asset_hub() { } #[test] -fn send_token_from_ethereum_to_non_existent_account_on_asset_hub() { - send_token_from_ethereum_to_asset_hub_with_fee([1; 32], XCM_FEE); +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], XCM_FEE); AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -657,8 +827,8 @@ fn send_token_from_ethereum_to_non_existent_account_on_asset_hub() { } #[test] -fn send_token_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficient_fee() { - send_token_from_ethereum_to_asset_hub_with_fee([1; 32], INSUFFICIENT_XCM_FEE); +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficient_fee() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], INSUFFICIENT_XCM_FEE); AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; @@ -675,10 +845,10 @@ fn send_token_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficie } #[test] -fn send_token_from_ethereum_to_non_existent_account_on_asset_hub_with_sufficient_fee_but_do_not_satisfy_ed( +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_sufficient_fee_but_do_not_satisfy_ed( ) { // On AH the xcm fee is 26_789_690 and the ED is 3_300_000 - send_token_from_ethereum_to_asset_hub_with_fee([1; 32], 30_000_000); + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], 30_000_000); AssetHubRococo::execute_with(|| { type RuntimeEvent = ::RuntimeEvent; diff --git a/prdoc/pr_6855.prdoc b/prdoc/pr_6855.prdoc new file mode 100644 index 000000000000..a665115ce6c7 --- /dev/null +++ b/prdoc/pr_6855.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Snowbridge - Support bridging native ETH + +doc: + - audience: Runtime User + description: + Support Native ETH as an asset type instead of only supporting WETH. WETH is still supported, but adds + support for ETH in the inbound and outbound routers. + +crates: + - name: snowbridge-router-primitives + bump: minor + - name: snowbridge-pallet-inbound-queue-fixtures + bump: minor From cdf107de700388a52a17b2fb852c98420c78278e Mon Sep 17 00:00:00 2001 From: wmjae Date: Thu, 9 Jan 2025 19:51:38 +0800 Subject: [PATCH 241/340] fix typo (#7096) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Dónal Murray --- .../node/core/approval-voting/src/persisted_entries.rs | 2 +- polkadot/node/core/pvf-checker/src/interest_view.rs | 2 +- polkadot/node/network/approval-distribution/src/tests.rs | 8 ++++---- .../src/node/approval/approval-voting.md | 2 +- polkadot/runtime/rococo/src/xcm_config.rs | 2 +- substrate/client/allocator/src/freeing_bump.rs | 2 +- substrate/client/api/src/proof_provider.rs | 2 +- substrate/frame/preimage/src/lib.rs | 2 +- substrate/frame/recovery/README.md | 2 +- substrate/frame/recovery/src/lib.rs | 2 +- substrate/frame/support/src/dispatch_context.rs | 2 +- substrate/primitives/api/src/lib.rs | 2 +- substrate/primitives/runtime/src/traits/mod.rs | 2 +- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/polkadot/node/core/approval-voting/src/persisted_entries.rs b/polkadot/node/core/approval-voting/src/persisted_entries.rs index d891af01c3ab..a5d42d9fd6e6 100644 --- a/polkadot/node/core/approval-voting/src/persisted_entries.rs +++ b/polkadot/node/core/approval-voting/src/persisted_entries.rs @@ -561,7 +561,7 @@ impl BlockEntry { self.distributed_assignments.resize(new_len, false); self.distributed_assignments |= bitfield; - // If the an operation did not change our current bitfield, we return true. + // If an operation did not change our current bitfield, we return true. let distributed = total_one_bits == self.distributed_assignments.count_ones(); distributed diff --git a/polkadot/node/core/pvf-checker/src/interest_view.rs b/polkadot/node/core/pvf-checker/src/interest_view.rs index 05a6f12de5d8..617d0e0b5d88 100644 --- a/polkadot/node/core/pvf-checker/src/interest_view.rs +++ b/polkadot/node/core/pvf-checker/src/interest_view.rs @@ -58,7 +58,7 @@ impl PvfData { Self { judgement: None, seen_in } } - /// Mark a the `PvfData` as seen in the provided relay-chain block referenced by `relay_hash`. + /// Mark the `PvfData` as seen in the provided relay-chain block referenced by `relay_hash`. pub fn seen_in(&mut self, relay_hash: Hash) { self.seen_in.insert(relay_hash); } diff --git a/polkadot/node/network/approval-distribution/src/tests.rs b/polkadot/node/network/approval-distribution/src/tests.rs index 323b2cb08fec..5d79260e3ad2 100644 --- a/polkadot/node/network/approval-distribution/src/tests.rs +++ b/polkadot/node/network/approval-distribution/src/tests.rs @@ -1255,7 +1255,7 @@ fn import_approval_happy_path_v1_v2_peers() { } ); - // send the an approval from peer_b + // send an approval from peer_b let approval = IndirectSignedApprovalVoteV2 { block_hash: hash, candidate_indices: candidate_index.into(), @@ -1385,7 +1385,7 @@ fn import_approval_happy_path_v2() { } ); - // send the an approval from peer_b + // send an approval from peer_b let approval = IndirectSignedApprovalVoteV2 { block_hash: hash, candidate_indices, @@ -1893,7 +1893,7 @@ fn import_approval_bad() { .unwrap() .unwrap(); - // send the an approval from peer_b, we don't have an assignment yet + // send an approval from peer_b, we don't have an assignment yet let approval = IndirectSignedApprovalVoteV2 { block_hash: hash, candidate_indices: candidate_index.into(), @@ -4172,7 +4172,7 @@ fn import_versioned_approval() { } ); - // send the an approval from peer_a + // send an approval from peer_a let approval = IndirectSignedApprovalVote { block_hash: hash, candidate_index, diff --git a/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md index 40394412d81b..7e155cdf7d58 100644 --- a/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md +++ b/polkadot/roadmap/implementers-guide/src/node/approval/approval-voting.md @@ -406,7 +406,7 @@ Some(core_index), response_sender)` * Construct a `IndirectSignedApprovalVote` using the information about the vote. * Dispatch `ApprovalDistributionMessage::DistributeApproval`. * ELSE - * Re-arm the timer with latest tick we have the send a the vote. + * Re-arm the timer with latest tick we have then send the vote. ### Determining Approval of Candidate diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index 82a3136cc0d9..bb77ec0000e5 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -84,7 +84,7 @@ pub type LocalAssetTransactor = FungibleAdapter< LocalCheckAccount, >; -/// The means that we convert an the XCM message origin location into a local dispatch origin. +/// The means that we convert the XCM message origin location into a local dispatch origin. type LocalOriginConverter = ( // A `Signed` origin of the sovereign account that the original location controls. SovereignSignedViaLocation, diff --git a/substrate/client/allocator/src/freeing_bump.rs b/substrate/client/allocator/src/freeing_bump.rs index 144c0764540d..405916adc3c3 100644 --- a/substrate/client/allocator/src/freeing_bump.rs +++ b/substrate/client/allocator/src/freeing_bump.rs @@ -182,7 +182,7 @@ const NIL_MARKER: u32 = u32::MAX; enum Link { /// Nil, denotes that there is no next element. Nil, - /// Link to the next element represented as a pointer to the a header. + /// Link to the next element represented as a pointer to the header. Ptr(u32), } diff --git a/substrate/client/api/src/proof_provider.rs b/substrate/client/api/src/proof_provider.rs index 7f60f856ae80..9043d3482723 100644 --- a/substrate/client/api/src/proof_provider.rs +++ b/substrate/client/api/src/proof_provider.rs @@ -82,7 +82,7 @@ pub trait ProofProvider { ) -> sp_blockchain::Result>; /// Verify read storage proof for a set of keys. - /// Returns collected key-value pairs and a the nested state + /// Returns collected key-value pairs and the nested state /// depth of current iteration or 0 if completed. fn verify_range_proof( &self, diff --git a/substrate/frame/preimage/src/lib.rs b/substrate/frame/preimage/src/lib.rs index 658e7fec5348..849ffddf4fb3 100644 --- a/substrate/frame/preimage/src/lib.rs +++ b/substrate/frame/preimage/src/lib.rs @@ -236,7 +236,7 @@ pub mod pallet { Self::do_unrequest_preimage(&hash) } - /// Ensure that the a bulk of pre-images is upgraded. + /// Ensure that the bulk of pre-images is upgraded. /// /// The caller pays no fee if at least 90% of pre-images were successfully updated. #[pallet::call_index(4)] diff --git a/substrate/frame/recovery/README.md b/substrate/frame/recovery/README.md index fdaef5784fdb..39f691407046 100644 --- a/substrate/frame/recovery/README.md +++ b/substrate/frame/recovery/README.md @@ -62,7 +62,7 @@ The intended life cycle of a successful recovery takes the following steps: ### Malicious Recovery Attempts -Initializing a the recovery process for a recoverable account is open and +Initializing the recovery process for a recoverable account is open and permissionless. However, the recovery deposit is an economic deterrent that should disincentivize would-be attackers from trying to maliciously recover accounts. diff --git a/substrate/frame/recovery/src/lib.rs b/substrate/frame/recovery/src/lib.rs index 5a97b03cd239..42fb641983f6 100644 --- a/substrate/frame/recovery/src/lib.rs +++ b/substrate/frame/recovery/src/lib.rs @@ -75,7 +75,7 @@ //! //! ### Malicious Recovery Attempts //! -//! Initializing a the recovery process for a recoverable account is open and +//! Initializing the recovery process for a recoverable account is open and //! permissionless. However, the recovery deposit is an economic deterrent that //! should disincentivize would-be attackers from trying to maliciously recover //! accounts. diff --git a/substrate/frame/support/src/dispatch_context.rs b/substrate/frame/support/src/dispatch_context.rs index b34c6bdada3d..42776e71cb88 100644 --- a/substrate/frame/support/src/dispatch_context.rs +++ b/substrate/frame/support/src/dispatch_context.rs @@ -140,7 +140,7 @@ impl Value<'_, T> { /// Runs the given `callback` in the dispatch context and gives access to some user defined value. /// -/// Passes the a mutable reference of [`Value`] to the callback. The value will be of type `T` and +/// Passes a mutable reference of [`Value`] to the callback. The value will be of type `T` and /// is identified using the [`TypeId`] of `T`. This means that `T` should be some unique type to /// make the value unique. If no value is set yet [`Value::get()`] and [`Value::get_mut()`] will /// return `None`. It is totally valid to have some `T` that is shared between different callers to diff --git a/substrate/primitives/api/src/lib.rs b/substrate/primitives/api/src/lib.rs index b412d4b52fed..8909d2b2e486 100644 --- a/substrate/primitives/api/src/lib.rs +++ b/substrate/primitives/api/src/lib.rs @@ -666,7 +666,7 @@ pub struct CallApiAtParams<'a, Block: BlockT> { pub extensions: &'a RefCell, } -/// Something that can call into the an api at a given block. +/// Something that can call into an api at a given block. #[cfg(feature = "std")] pub trait CallApiAt { /// The state backend that is used to store the block states. diff --git a/substrate/primitives/runtime/src/traits/mod.rs b/substrate/primitives/runtime/src/traits/mod.rs index 5b6cacc7e008..8f5b484e4e3f 100644 --- a/substrate/primitives/runtime/src/traits/mod.rs +++ b/substrate/primitives/runtime/src/traits/mod.rs @@ -1963,7 +1963,7 @@ pub trait AccountIdConversion: Sized { Self::try_from_sub_account::<()>(a).map(|x| x.0) } - /// Convert this value amalgamated with the a secondary "sub" value into an account ID, + /// Convert this value amalgamated with a secondary "sub" value into an account ID, /// truncating any unused bytes. This is infallible. /// /// NOTE: The account IDs from this and from `into_account` are *not* guaranteed to be distinct From 2f179585229880a596ab3b8b04a4be6c7db15efa Mon Sep 17 00:00:00 2001 From: seemantaggarwal <32275622+seemantaggarwal@users.noreply.github.com> Date: Thu, 9 Jan 2025 20:18:59 +0530 Subject: [PATCH 242/340] Migrating salary pallet to use umbrella crate (#7048) # Description Migrating salary pallet to use umbrella crate. It is a follow-up from https://github.com/paritytech/polkadot-sdk/pull/7025 Why did I create this new branch? I did this, so that the unnecessary cargo fmt changes from the previous branch are discarded and hence opened this new PR. ## Review Notes This PR migrates pallet-salary to use the umbrella crate. Added change: Explanation requested for why `TestExternalities` was replaced by `TestState` as testing_prelude already includes it `pub use sp_io::TestExternalities as TestState;` I have also modified the defensive! macro to be compatible with umbrella crate as it was being used in the salary pallet --- Cargo.lock | 8 +----- prdoc/pr_7048.prdoc | 17 ++++++++++++ substrate/frame/salary/Cargo.toml | 26 +++--------------- substrate/frame/salary/src/benchmarking.rs | 7 ++--- substrate/frame/salary/src/lib.rs | 27 +++++-------------- .../frame/salary/src/tests/integration.rs | 25 +++++------------ substrate/frame/salary/src/tests/unit.rs | 24 ++++++----------- substrate/frame/salary/src/weights.rs | 2 +- substrate/frame/src/lib.rs | 23 +++++++++------- 9 files changed, 60 insertions(+), 99 deletions(-) create mode 100644 prdoc/pr_7048.prdoc diff --git a/Cargo.lock b/Cargo.lock index 0a22179eb3d7..4e2272bdc988 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15064,17 +15064,11 @@ dependencies = [ name = "pallet-salary" version = "13.0.0" dependencies = [ - "frame-benchmarking 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", "log", "pallet-ranked-collective 28.0.0", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-arithmetic 23.0.0", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] diff --git a/prdoc/pr_7048.prdoc b/prdoc/pr_7048.prdoc new file mode 100644 index 000000000000..0f3856bc1287 --- /dev/null +++ b/prdoc/pr_7048.prdoc @@ -0,0 +1,17 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: '[pallet-salary] Migrate to using frame umbrella crate' + +doc: + - audience: Runtime Dev + description: > + This PR migrates the `pallet-salary` to use the FRAME umbrella crate. + This is part of the ongoing effort to migrate all pallets to use the FRAME umbrella crate. + The effort is tracked [here](https://github.com/paritytech/polkadot-sdk/issues/6504). + +crates: + - name: pallet-salary + bump: minor + - name: polkadot-sdk-frame + bump: minor diff --git a/substrate/frame/salary/Cargo.toml b/substrate/frame/salary/Cargo.toml index b3ed95bf1de5..626993a0547b 100644 --- a/substrate/frame/salary/Cargo.toml +++ b/substrate/frame/salary/Cargo.toml @@ -17,43 +17,25 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +frame = { workspace = true, features = ["experimental", "runtime"] } log = { workspace = true } pallet-ranked-collective = { optional = true, workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-arithmetic = { workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } -sp-runtime = { workspace = true } [features] default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/experimental", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "pallet-ranked-collective/std", "scale-info/std", - "sp-arithmetic/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", + "frame/runtime-benchmarks", "pallet-ranked-collective/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", + "frame/try-runtime", "pallet-ranked-collective?/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/substrate/frame/salary/src/benchmarking.rs b/substrate/frame/salary/src/benchmarking.rs index aeae8d2d67f8..6dfd6f6dd488 100644 --- a/substrate/frame/salary/src/benchmarking.rs +++ b/substrate/frame/salary/src/benchmarking.rs @@ -22,10 +22,7 @@ use super::*; use crate::Pallet as Salary; -use frame_benchmarking::v2::*; -use frame_system::{Pallet as System, RawOrigin}; -use sp_core::Get; - +use frame::benchmarking::prelude::*; const SEED: u32 = 0; fn ensure_member_with_salary, I: 'static>(who: &T::AccountId) { @@ -37,7 +34,7 @@ fn ensure_member_with_salary, I: 'static>(who: &T::AccountId) { for _ in 0..255 { let r = T::Members::rank_of(who).expect("prior guard ensures `who` is a member; qed"); if !T::Salary::get_salary(r, &who).is_zero() { - break + break; } T::Members::promote(who).unwrap(); } diff --git a/substrate/frame/salary/src/lib.rs b/substrate/frame/salary/src/lib.rs index efb4f5d3c542..6a843625f4a7 100644 --- a/substrate/frame/salary/src/lib.rs +++ b/substrate/frame/salary/src/lib.rs @@ -19,20 +19,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode, MaxEncodedLen}; use core::marker::PhantomData; -use scale_info::TypeInfo; -use sp_arithmetic::traits::{Saturating, Zero}; -use sp_runtime::{Perbill, RuntimeDebug}; - -use frame_support::{ - defensive, - dispatch::DispatchResultWithPostInfo, - ensure, - traits::{ - tokens::{GetSalary, Pay, PaymentStatus}, - RankedMembers, RankedMembersSwapHandler, - }, +use frame::{ + prelude::*, + traits::tokens::{GetSalary, Pay, PaymentStatus}, }; #[cfg(test)] @@ -85,12 +75,9 @@ pub struct ClaimantStatus { status: ClaimState, } -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::{dispatch::Pays, pallet_prelude::*}; - use frame_system::pallet_prelude::*; - #[pallet::pallet] pub struct Pallet(PhantomData<(T, I)>); @@ -460,15 +447,15 @@ impl, I: 'static> ) { if who == new_who { defensive!("Should not try to swap with self"); - return + return; } if Claimant::::contains_key(new_who) { defensive!("Should not try to overwrite existing claimant"); - return + return; } let Some(claimant) = Claimant::::take(who) else { - frame_support::defensive!("Claimant should exist when swapping"); + defensive!("Claimant should exist when swapping"); return; }; diff --git a/substrate/frame/salary/src/tests/integration.rs b/substrate/frame/salary/src/tests/integration.rs index 0c1fb8bbdcba..e4e9c8f6a31b 100644 --- a/substrate/frame/salary/src/tests/integration.rs +++ b/substrate/frame/salary/src/tests/integration.rs @@ -19,25 +19,14 @@ use crate as pallet_salary; use crate::*; -use frame_support::{ - assert_noop, assert_ok, derive_impl, hypothetically, - pallet_prelude::Weight, - parameter_types, - traits::{ConstU64, EitherOf, MapSuccess, NoOpPoll}, -}; +use frame::{deps::sp_io, testing_prelude::*}; use pallet_ranked_collective::{EnsureRanked, Geometric}; -use sp_core::{ConstU16, Get}; -use sp_runtime::{ - traits::{Convert, ReduceBy, ReplaceWithDefault}, - BuildStorage, -}; type Rank = u16; type Block = frame_system::mocking::MockBlock; -frame_support::construct_runtime!( - pub enum Test - { +construct_runtime!( + pub struct Test { System: frame_system, Salary: pallet_salary, Club: pallet_ranked_collective, @@ -145,9 +134,9 @@ impl pallet_ranked_collective::Config for Test { type BenchmarkSetup = Salary; } -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext() -> TestState { let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); + let mut ext = TestState::new(t); ext.execute_with(|| System::set_block_number(1)); ext } @@ -194,7 +183,7 @@ fn swap_exhaustive_works() { // The events mess up the storage root: System::reset_events(); - sp_io::storage::root(sp_runtime::StateVersion::V1) + sp_io::storage::root(StateVersion::V1) }); let root_swap = hypothetically!({ @@ -207,7 +196,7 @@ fn swap_exhaustive_works() { // The events mess up the storage root: System::reset_events(); - sp_io::storage::root(sp_runtime::StateVersion::V1) + sp_io::storage::root(StateVersion::V1) }); assert_eq!(root_add, root_swap); diff --git a/substrate/frame/salary/src/tests/unit.rs b/substrate/frame/salary/src/tests/unit.rs index db1c8b947ef5..3bb7bc4adf1e 100644 --- a/substrate/frame/salary/src/tests/unit.rs +++ b/substrate/frame/salary/src/tests/unit.rs @@ -17,23 +17,15 @@ //! The crate's tests. -use std::collections::BTreeMap; - -use core::cell::RefCell; -use frame_support::{ - assert_noop, assert_ok, derive_impl, - pallet_prelude::Weight, - parameter_types, - traits::{tokens::ConvertRank, ConstU64}, -}; -use sp_runtime::{traits::Identity, BuildStorage, DispatchResult}; - use crate as pallet_salary; use crate::*; +use core::cell::RefCell; +use frame::{deps::sp_runtime::traits::Identity, testing_prelude::*, traits::tokens::ConvertRank}; +use std::collections::BTreeMap; -type Block = frame_system::mocking::MockBlock; +type Block = MockBlock; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -124,7 +116,7 @@ impl RankedMembers for TestClub { } fn demote(who: &Self::AccountId) -> DispatchResult { CLUB.with(|club| match club.borrow().get(who) { - None => Err(sp_runtime::DispatchError::Unavailable), + None => Err(DispatchError::Unavailable), Some(&0) => { club.borrow_mut().remove(&who); Ok(()) @@ -156,9 +148,9 @@ impl Config for Test { type Budget = Budget; } -pub fn new_test_ext() -> sp_io::TestExternalities { +pub fn new_test_ext() -> TestState { let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); + let mut ext = TestState::new(t); ext.execute_with(|| System::set_block_number(1)); ext } diff --git a/substrate/frame/salary/src/weights.rs b/substrate/frame/salary/src/weights.rs index f1cdaaa225a4..43c001b30d33 100644 --- a/substrate/frame/salary/src/weights.rs +++ b/substrate/frame/salary/src/weights.rs @@ -46,8 +46,8 @@ #![allow(unused_imports)] #![allow(missing_docs)] -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use core::marker::PhantomData; +use frame::weights_prelude::*; /// Weight functions needed for `pallet_salary`. pub trait WeightInfo { diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 15601ebde1f2..23d22683be2d 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -203,8 +203,12 @@ pub mod prelude { /// Dispatch types from `frame-support`, other fundamental traits #[doc(no_inline)] pub use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; - pub use frame_support::traits::{ - Contains, EstimateNextSessionRotation, IsSubType, OnRuntimeUpgrade, OneSessionHandler, + pub use frame_support::{ + defensive, defensive_assert, + traits::{ + Contains, EitherOf, EstimateNextSessionRotation, IsSubType, MapSuccess, NoOpPoll, + OnRuntimeUpgrade, OneSessionHandler, RankedMembers, RankedMembersSwapHandler, + }, }; /// Pallet prelude of `frame-system`. @@ -228,11 +232,10 @@ pub mod prelude { /// Runtime traits #[doc(no_inline)] pub use sp_runtime::traits::{ - BlockNumberProvider, Bounded, DispatchInfoOf, Dispatchable, SaturatedConversion, - Saturating, StaticLookup, TrailingZeroInput, + BlockNumberProvider, Bounded, Convert, DispatchInfoOf, Dispatchable, ReduceBy, + ReplaceWithDefault, SaturatedConversion, Saturating, StaticLookup, TrailingZeroInput, }; - - /// Other runtime types and traits + /// Other error/result types for runtime #[doc(no_inline)] pub use sp_runtime::{ BoundToRuntimeAppPublic, DispatchErrorWithPostInfo, DispatchResultWithInfo, TokenError, @@ -262,7 +265,7 @@ pub mod benchmarking { pub use frame_benchmarking::benchmarking::*; // The system origin, which is very often needed in benchmarking code. Might be tricky only // if the pallet defines its own `#[pallet::origin]` and call it `RawOrigin`. - pub use frame_system::RawOrigin; + pub use frame_system::{Pallet as System, RawOrigin}; } #[deprecated( @@ -319,7 +322,7 @@ pub mod testing_prelude { /// Other helper macros from `frame_support` that help with asserting in tests. pub use frame_support::{ assert_err, assert_err_ignore_postinfo, assert_error_encoded_size, assert_noop, assert_ok, - assert_storage_noop, storage_alias, + assert_storage_noop, hypothetically, storage_alias, }; pub use frame_system::{self, mocking::*}; @@ -330,7 +333,7 @@ pub mod testing_prelude { pub use sp_io::TestExternalities as TestState; /// Commonly used runtime traits for testing. - pub use sp_runtime::traits::BadOrigin; + pub use sp_runtime::{traits::BadOrigin, StateVersion}; } /// All of the types and tools needed to build FRAME-based runtimes. @@ -508,7 +511,7 @@ pub mod runtime { #[cfg(feature = "std")] pub mod testing_prelude { pub use sp_core::storage::Storage; - pub use sp_runtime::BuildStorage; + pub use sp_runtime::{BuildStorage, DispatchError}; } } From 6bfe4523acf597ef47dfdcefd11b0eee396bc5c5 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Thu, 9 Jan 2025 19:20:07 +0100 Subject: [PATCH 243/340] networking-bench: Update benchmarks payload (#7056) # Description - Used 10 notifications and requests within the benchmarks. After moving the network workers' initialization out of the benchmarks, it is acceptable to use this small number without losing precision. - Removed the 128MB payload that consumed most of the execution time. --- .github/workflows/benchmarks-networking.yml | 2 + .../network/benches/notifications_protocol.rs | 99 ++++++++--------- .../benches/request_response_protocol.rs | 102 ++++++++++-------- 3 files changed, 103 insertions(+), 100 deletions(-) diff --git a/.github/workflows/benchmarks-networking.yml b/.github/workflows/benchmarks-networking.yml index 79494b9a015c..8f4246c79548 100644 --- a/.github/workflows/benchmarks-networking.yml +++ b/.github/workflows/benchmarks-networking.yml @@ -92,6 +92,7 @@ jobs: uses: benchmark-action/github-action-benchmark@v1 with: tool: "cargo" + name: ${{ env.BENCH }} output-file-path: ./charts/${{ env.BENCH }}.txt benchmark-data-dir-path: ./bench/${{ env.BENCH }} github-token: ${{ steps.app-token.outputs.token }} @@ -103,6 +104,7 @@ jobs: uses: benchmark-action/github-action-benchmark@v1 with: tool: "cargo" + name: ${{ env.BENCH }} output-file-path: ./charts/${{ env.BENCH }}.txt benchmark-data-dir-path: ./bench/${{ env.BENCH }} github-token: ${{ steps.app-token.outputs.token }} diff --git a/substrate/client/network/benches/notifications_protocol.rs b/substrate/client/network/benches/notifications_protocol.rs index 40a810d616b5..a406e328d5a6 100644 --- a/substrate/client/network/benches/notifications_protocol.rs +++ b/substrate/client/network/benches/notifications_protocol.rs @@ -36,19 +36,16 @@ use std::{sync::Arc, time::Duration}; use substrate_test_runtime_client::runtime; use tokio::{sync::Mutex, task::JoinHandle}; -const SMALL_PAYLOAD: &[(u32, usize, &'static str)] = &[ - // (Exponent of size, number of notifications, label) - (6, 100, "64B"), - (9, 100, "512B"), - (12, 100, "4KB"), - (15, 100, "64KB"), -]; -const LARGE_PAYLOAD: &[(u32, usize, &'static str)] = &[ - // (Exponent of size, number of notifications, label) - (18, 10, "256KB"), - (21, 10, "2MB"), - (24, 10, "16MB"), - (27, 10, "128MB"), +const NUMBER_OF_NOTIFICATIONS: usize = 100; +const PAYLOAD: &[(u32, &'static str)] = &[ + // (Exponent of size, label) + (6, "64B"), + (9, "512B"), + (12, "4KB"), + (15, "64KB"), + (18, "256KB"), + (21, "2MB"), + (24, "16MB"), ]; const MAX_SIZE: u64 = 2u64.pow(30); @@ -156,12 +153,19 @@ where tokio::select! { Some(event) = notification_service1.next_event() => { if let NotificationEvent::NotificationStreamOpened { .. } = event { - break; + // Send a 32MB notification to preheat the network + notification_service1.send_async_notification(&peer_id2, vec![0; 2usize.pow(25)]).await.unwrap(); } }, Some(event) = notification_service2.next_event() => { - if let NotificationEvent::ValidateInboundSubstream { result_tx, .. } = event { - result_tx.send(sc_network::service::traits::ValidationResult::Accept).unwrap(); + match event { + NotificationEvent::ValidateInboundSubstream { result_tx, .. } => { + result_tx.send(sc_network::service::traits::ValidationResult::Accept).unwrap(); + }, + NotificationEvent::NotificationReceived { .. } => { + break; + } + _ => {} } }, } @@ -255,64 +259,53 @@ async fn run_with_backpressure(setup: Arc, size: usize, limit: usize let _ = tokio::join!(network1, network2); } -fn run_benchmark(c: &mut Criterion, payload: &[(u32, usize, &'static str)], group: &str) { +fn run_benchmark(c: &mut Criterion) { let rt = tokio::runtime::Runtime::new().unwrap(); let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); - let mut group = c.benchmark_group(group); + let mut group = c.benchmark_group("notifications_protocol"); group.plot_config(plot_config); + group.sample_size(10); let libp2p_setup = setup_workers::>(&rt); - for &(exponent, limit, label) in payload.iter() { + for &(exponent, label) in PAYLOAD.iter() { let size = 2usize.pow(exponent); - group.throughput(Throughput::Bytes(limit as u64 * size as u64)); - group.bench_with_input( - BenchmarkId::new("libp2p/serially", label), - &(size, limit), - |b, &(size, limit)| { - b.to_async(&rt).iter(|| run_serially(Arc::clone(&libp2p_setup), size, limit)); - }, - ); + group.throughput(Throughput::Bytes(NUMBER_OF_NOTIFICATIONS as u64 * size as u64)); + group.bench_with_input(BenchmarkId::new("libp2p/serially", label), &size, |b, &size| { + b.to_async(&rt) + .iter(|| run_serially(Arc::clone(&libp2p_setup), size, NUMBER_OF_NOTIFICATIONS)); + }); group.bench_with_input( BenchmarkId::new("libp2p/with_backpressure", label), - &(size, limit), - |b, &(size, limit)| { - b.to_async(&rt) - .iter(|| run_with_backpressure(Arc::clone(&libp2p_setup), size, limit)); + &size, + |b, &size| { + b.to_async(&rt).iter(|| { + run_with_backpressure(Arc::clone(&libp2p_setup), size, NUMBER_OF_NOTIFICATIONS) + }); }, ); } drop(libp2p_setup); let litep2p_setup = setup_workers::(&rt); - for &(exponent, limit, label) in payload.iter() { + for &(exponent, label) in PAYLOAD.iter() { let size = 2usize.pow(exponent); - group.throughput(Throughput::Bytes(limit as u64 * size as u64)); - group.bench_with_input( - BenchmarkId::new("litep2p/serially", label), - &(size, limit), - |b, &(size, limit)| { - b.to_async(&rt).iter(|| run_serially(Arc::clone(&litep2p_setup), size, limit)); - }, - ); + group.throughput(Throughput::Bytes(NUMBER_OF_NOTIFICATIONS as u64 * size as u64)); + group.bench_with_input(BenchmarkId::new("litep2p/serially", label), &size, |b, &size| { + b.to_async(&rt) + .iter(|| run_serially(Arc::clone(&litep2p_setup), size, NUMBER_OF_NOTIFICATIONS)); + }); group.bench_with_input( BenchmarkId::new("litep2p/with_backpressure", label), - &(size, limit), - |b, &(size, limit)| { - b.to_async(&rt) - .iter(|| run_with_backpressure(Arc::clone(&litep2p_setup), size, limit)); + &size, + |b, &size| { + b.to_async(&rt).iter(|| { + run_with_backpressure(Arc::clone(&litep2p_setup), size, NUMBER_OF_NOTIFICATIONS) + }); }, ); } drop(litep2p_setup); } -fn run_benchmark_with_small_payload(c: &mut Criterion) { - run_benchmark(c, SMALL_PAYLOAD, "notifications_protocol/small_payload"); -} - -fn run_benchmark_with_large_payload(c: &mut Criterion) { - run_benchmark(c, LARGE_PAYLOAD, "notifications_protocol/large_payload"); -} - -criterion_group!(benches, run_benchmark_with_small_payload, run_benchmark_with_large_payload); +criterion_group!(benches, run_benchmark); criterion_main!(benches); diff --git a/substrate/client/network/benches/request_response_protocol.rs b/substrate/client/network/benches/request_response_protocol.rs index 85381112b753..97c6d72ddf1e 100644 --- a/substrate/client/network/benches/request_response_protocol.rs +++ b/substrate/client/network/benches/request_response_protocol.rs @@ -37,19 +37,16 @@ use substrate_test_runtime_client::runtime; use tokio::{sync::Mutex, task::JoinHandle}; const MAX_SIZE: u64 = 2u64.pow(30); -const SMALL_PAYLOAD: &[(u32, usize, &'static str)] = &[ - // (Exponent of size, number of requests, label) - (6, 100, "64B"), - (9, 100, "512B"), - (12, 100, "4KB"), - (15, 100, "64KB"), -]; -const LARGE_PAYLOAD: &[(u32, usize, &'static str)] = &[ - // (Exponent of size, number of requests, label) - (18, 10, "256KB"), - (21, 10, "2MB"), - (24, 10, "16MB"), - (27, 10, "128MB"), +const NUMBER_OF_REQUESTS: usize = 100; +const PAYLOAD: &[(u32, &'static str)] = &[ + // (Exponent of size, label) + (6, "64B"), + (9, "512B"), + (12, "4KB"), + (15, "64KB"), + (18, "256KB"), + (21, "2MB"), + (24, "16MB"), ]; pub fn create_network_worker() -> ( @@ -154,6 +151,21 @@ where let handle1 = tokio::spawn(worker1.run()); let handle2 = tokio::spawn(worker2.run()); + let _ = tokio::spawn({ + let rx2 = rx2.clone(); + + async move { + let req = rx2.recv().await.unwrap(); + req.pending_response + .send(OutgoingResponse { + result: Ok(vec![0; 2usize.pow(25)]), + reputation_changes: vec![], + sent_feedback: None, + }) + .unwrap(); + } + }); + let ready = tokio::spawn({ let network_service1 = Arc::clone(&network_service1); @@ -165,6 +177,16 @@ where network_service2.listen_addresses()[0].clone() }; network_service1.add_known_address(peer_id2, listen_address2.into()); + let _ = network_service1 + .request( + peer_id2.into(), + "/request-response/1".into(), + vec![0; 2], + None, + IfDisconnected::TryConnect, + ) + .await + .unwrap(); } }); @@ -210,8 +232,8 @@ async fn run_serially(setup: Arc, size: usize, limit: usize) { async move { loop { tokio::select! { - res = rx2.recv() => { - let IncomingRequest { pending_response, .. } = res.unwrap(); + req = rx2.recv() => { + let IncomingRequest { pending_response, .. } = req.unwrap(); pending_response.send(OutgoingResponse { result: Ok(vec![0; size]), reputation_changes: vec![], @@ -269,49 +291,35 @@ async fn run_with_backpressure(setup: Arc, size: usize, limit: usize let _ = tokio::join!(network1, network2); } -fn run_benchmark(c: &mut Criterion, payload: &[(u32, usize, &'static str)], group: &str) { +fn run_benchmark(c: &mut Criterion) { let rt = tokio::runtime::Runtime::new().unwrap(); let plot_config = PlotConfiguration::default().summary_scale(AxisScale::Logarithmic); - let mut group = c.benchmark_group(group); + let mut group = c.benchmark_group("request_response_protocol"); group.plot_config(plot_config); + group.sample_size(10); let libp2p_setup = setup_workers::>(&rt); - for &(exponent, limit, label) in payload.iter() { + for &(exponent, label) in PAYLOAD.iter() { let size = 2usize.pow(exponent); - group.throughput(Throughput::Bytes(limit as u64 * size as u64)); - group.bench_with_input( - BenchmarkId::new("libp2p/serially", label), - &(size, limit), - |b, &(size, limit)| { - b.to_async(&rt).iter(|| run_serially(Arc::clone(&libp2p_setup), size, limit)); - }, - ); + group.throughput(Throughput::Bytes(NUMBER_OF_REQUESTS as u64 * size as u64)); + group.bench_with_input(BenchmarkId::new("libp2p/serially", label), &size, |b, &size| { + b.to_async(&rt) + .iter(|| run_serially(Arc::clone(&libp2p_setup), size, NUMBER_OF_REQUESTS)); + }); } drop(libp2p_setup); - // TODO: NetworkRequest::request should be implemented for Litep2pNetworkService let litep2p_setup = setup_workers::(&rt); - // for &(exponent, limit, label) in payload.iter() { - // let size = 2usize.pow(exponent); - // group.throughput(Throughput::Bytes(limit as u64 * size as u64)); - // group.bench_with_input( - // BenchmarkId::new("litep2p/serially", label), - // &(size, limit), - // |b, &(size, limit)| { - // b.to_async(&rt).iter(|| run_serially(Arc::clone(&litep2p_setup), size, limit)); - // }, - // ); - // } + for &(exponent, label) in PAYLOAD.iter() { + let size = 2usize.pow(exponent); + group.throughput(Throughput::Bytes(NUMBER_OF_REQUESTS as u64 * size as u64)); + group.bench_with_input(BenchmarkId::new("litep2p/serially", label), &size, |b, &size| { + b.to_async(&rt) + .iter(|| run_serially(Arc::clone(&litep2p_setup), size, NUMBER_OF_REQUESTS)); + }); + } drop(litep2p_setup); } -fn run_benchmark_with_small_payload(c: &mut Criterion) { - run_benchmark(c, SMALL_PAYLOAD, "request_response_benchmark/small_payload"); -} - -fn run_benchmark_with_large_payload(c: &mut Criterion) { - run_benchmark(c, LARGE_PAYLOAD, "request_response_benchmark/large_payload"); -} - -criterion_group!(benches, run_benchmark_with_small_payload, run_benchmark_with_large_payload); +criterion_group!(benches, run_benchmark); criterion_main!(benches); From e051f3edd3d6a0699a9261c8f8985d2e8e95c276 Mon Sep 17 00:00:00 2001 From: Francisco Aguirre Date: Thu, 9 Jan 2025 23:20:01 -0300 Subject: [PATCH 244/340] Add XCM benchmarks to collectives-westend (#6820) Collectives-westend was using `FixedWeightBounds`, meaning the same weight per instruction. Added proper benchmarks. --------- Co-authored-by: GitHub Action Co-authored-by: Branislav Kontur --- .../collectives-westend/src/weights/mod.rs | 1 + .../src/weights/xcm/mod.rs | 273 ++++++++++++++ .../xcm/pallet_xcm_benchmarks_fungible.rs | 211 +++++++++++ .../xcm/pallet_xcm_benchmarks_generic.rs | 355 ++++++++++++++++++ .../collectives-westend/src/xcm_config.rs | 30 +- prdoc/pr_6820.prdoc | 8 + 6 files changed, 864 insertions(+), 14 deletions(-) create mode 100644 cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/mod.rs create mode 100644 cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs create mode 100644 cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs create mode 100644 prdoc/pr_6820.prdoc diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs index a1663dc98a34..ce85d23b21cb 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/mod.rs @@ -47,6 +47,7 @@ pub mod pallet_utility; pub mod pallet_xcm; pub mod paritydb_weights; pub mod rocksdb_weights; +pub mod xcm; pub use block_weights::constants::BlockExecutionWeight; pub use extrinsic_weights::constants::ExtrinsicBaseWeight; diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/mod.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/mod.rs new file mode 100644 index 000000000000..d73ce8c440fc --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/mod.rs @@ -0,0 +1,273 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod pallet_xcm_benchmarks_fungible; +mod pallet_xcm_benchmarks_generic; + +use crate::{xcm_config::MaxAssetsIntoHolding, Runtime}; +use alloc::vec::Vec; +use frame_support::weights::Weight; +use pallet_xcm_benchmarks_fungible::WeightInfo as XcmFungibleWeight; +use pallet_xcm_benchmarks_generic::WeightInfo as XcmGeneric; +use sp_runtime::BoundedVec; +use xcm::{ + latest::{prelude::*, AssetTransferFilter}, + DoubleEncoded, +}; + +trait WeighAssets { + fn weigh_assets(&self, weight: Weight) -> Weight; +} + +// Collectives only knows about WND. +const MAX_ASSETS: u64 = 1; + +impl WeighAssets for AssetFilter { + fn weigh_assets(&self, weight: Weight) -> Weight { + match self { + Self::Definite(assets) => weight.saturating_mul(assets.inner().iter().count() as u64), + Self::Wild(asset) => match asset { + All => weight.saturating_mul(MAX_ASSETS), + AllOf { fun, .. } => match fun { + WildFungibility::Fungible => weight, + // Magic number 2 has to do with the fact that we could have up to 2 times + // MaxAssetsIntoHolding in the worst-case scenario. + WildFungibility::NonFungible => + weight.saturating_mul((MaxAssetsIntoHolding::get() * 2) as u64), + }, + AllCounted(count) => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + AllOfCounted { count, .. } => weight.saturating_mul(MAX_ASSETS.min(*count as u64)), + }, + } + } +} + +impl WeighAssets for Assets { + fn weigh_assets(&self, weight: Weight) -> Weight { + weight.saturating_mul(self.inner().iter().count() as u64) + } +} + +pub struct CollectivesWestendXcmWeight(core::marker::PhantomData); +impl XcmWeightInfo for CollectivesWestendXcmWeight { + fn withdraw_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::withdraw_asset()) + } + fn reserve_asset_deposited(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::reserve_asset_deposited()) + } + fn receive_teleported_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::receive_teleported_asset()) + } + fn query_response( + _query_id: &u64, + _response: &Response, + _max_weight: &Weight, + _querier: &Option, + ) -> Weight { + XcmGeneric::::query_response() + } + fn transfer_asset(assets: &Assets, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_asset()) + } + fn transfer_reserve_asset(assets: &Assets, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::transfer_reserve_asset()) + } + fn transact( + _origin_type: &OriginKind, + _fallback_max_weight: &Option, + _call: &DoubleEncoded, + ) -> Weight { + XcmGeneric::::transact() + } + fn hrmp_new_channel_open_request( + _sender: &u32, + _max_message_size: &u32, + _max_capacity: &u32, + ) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_accepted(_recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn hrmp_channel_closing(_initiator: &u32, _sender: &u32, _recipient: &u32) -> Weight { + // XCM Executor does not currently support HRMP channel operations + Weight::MAX + } + fn clear_origin() -> Weight { + XcmGeneric::::clear_origin() + } + fn descend_origin(_who: &InteriorLocation) -> Weight { + XcmGeneric::::descend_origin() + } + fn report_error(_query_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_error() + } + + fn deposit_asset(assets: &AssetFilter, _dest: &Location) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_asset()) + } + fn deposit_reserve_asset(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::deposit_reserve_asset()) + } + fn exchange_asset(_give: &AssetFilter, _receive: &Assets, _maximal: &bool) -> Weight { + Weight::MAX + } + fn initiate_reserve_withdraw( + assets: &AssetFilter, + _reserve: &Location, + _xcm: &Xcm<()>, + ) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::initiate_reserve_withdraw()) + } + fn initiate_teleport(assets: &AssetFilter, _dest: &Location, _xcm: &Xcm<()>) -> Weight { + assets.weigh_assets(XcmFungibleWeight::::initiate_teleport()) + } + fn initiate_transfer( + _dest: &Location, + remote_fees: &Option, + _preserve_origin: &bool, + assets: &Vec, + _xcm: &Xcm<()>, + ) -> Weight { + let mut weight = if let Some(remote_fees) = remote_fees { + let fees = remote_fees.inner(); + fees.weigh_assets(XcmFungibleWeight::::initiate_transfer()) + } else { + Weight::zero() + }; + for asset_filter in assets { + let assets = asset_filter.inner(); + let extra = assets.weigh_assets(XcmFungibleWeight::::initiate_transfer()); + weight = weight.saturating_add(extra); + } + weight + } + fn report_holding(_response_info: &QueryResponseInfo, _assets: &AssetFilter) -> Weight { + XcmGeneric::::report_holding() + } + fn buy_execution(_fees: &Asset, _weight_limit: &WeightLimit) -> Weight { + XcmGeneric::::buy_execution() + } + fn pay_fees(_asset: &Asset) -> Weight { + XcmGeneric::::pay_fees() + } + fn refund_surplus() -> Weight { + XcmGeneric::::refund_surplus() + } + fn set_error_handler(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_error_handler() + } + fn set_appendix(_xcm: &Xcm) -> Weight { + XcmGeneric::::set_appendix() + } + fn clear_error() -> Weight { + XcmGeneric::::clear_error() + } + fn set_hints(hints: &BoundedVec) -> Weight { + let mut weight = Weight::zero(); + for hint in hints { + match hint { + AssetClaimer { .. } => { + weight = weight.saturating_add(XcmGeneric::::asset_claimer()); + }, + } + } + weight + } + fn claim_asset(_assets: &Assets, _ticket: &Location) -> Weight { + XcmGeneric::::claim_asset() + } + fn trap(_code: &u64) -> Weight { + XcmGeneric::::trap() + } + fn subscribe_version(_query_id: &QueryId, _max_response_weight: &Weight) -> Weight { + XcmGeneric::::subscribe_version() + } + fn unsubscribe_version() -> Weight { + XcmGeneric::::unsubscribe_version() + } + fn burn_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::burn_asset()) + } + fn expect_asset(assets: &Assets) -> Weight { + assets.weigh_assets(XcmGeneric::::expect_asset()) + } + fn expect_origin(_origin: &Option) -> Weight { + XcmGeneric::::expect_origin() + } + fn expect_error(_error: &Option<(u32, XcmError)>) -> Weight { + XcmGeneric::::expect_error() + } + fn expect_transact_status(_transact_status: &MaybeErrorCode) -> Weight { + XcmGeneric::::expect_transact_status() + } + fn query_pallet(_module_name: &Vec, _response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::query_pallet() + } + fn expect_pallet( + _index: &u32, + _name: &Vec, + _module_name: &Vec, + _crate_major: &u32, + _min_crate_minor: &u32, + ) -> Weight { + XcmGeneric::::expect_pallet() + } + fn report_transact_status(_response_info: &QueryResponseInfo) -> Weight { + XcmGeneric::::report_transact_status() + } + fn clear_transact_status() -> Weight { + XcmGeneric::::clear_transact_status() + } + fn universal_origin(_: &Junction) -> Weight { + Weight::MAX + } + fn export_message(_: &NetworkId, _: &Junctions, _: &Xcm<()>) -> Weight { + Weight::MAX + } + fn lock_asset(_: &Asset, _: &Location) -> Weight { + Weight::MAX + } + fn unlock_asset(_: &Asset, _: &Location) -> Weight { + Weight::MAX + } + fn note_unlockable(_: &Asset, _: &Location) -> Weight { + Weight::MAX + } + fn request_unlock(_: &Asset, _: &Location) -> Weight { + Weight::MAX + } + fn set_fees_mode(_: &bool) -> Weight { + XcmGeneric::::set_fees_mode() + } + fn set_topic(_topic: &[u8; 32]) -> Weight { + XcmGeneric::::set_topic() + } + fn clear_topic() -> Weight { + XcmGeneric::::clear_topic() + } + fn alias_origin(_: &Location) -> Weight { + XcmGeneric::::alias_origin() + } + fn unpaid_execution(_: &WeightLimit, _: &Option) -> Weight { + XcmGeneric::::unpaid_execution() + } + fn execute_with_origin(_: &Option, _: &Xcm) -> Weight { + XcmGeneric::::execute_with_origin() + } +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs new file mode 100644 index 000000000000..00826cbb8d79 --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -0,0 +1,211 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_xcm_benchmarks::fungible` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-10-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-augrssgt-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("collectives-westend-dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::fungible +// --chain=collectives-westend-dev +// --header=./cumulus/file_header.txt +// --template=./cumulus/templates/xcm-bench-template.hbs +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::fungible`. +pub struct WeightInfo(PhantomData); +impl WeightInfo { + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + pub fn withdraw_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `3593` + // Minimum execution time: 30_401_000 picoseconds. + Weight::from_parts(30_813_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + pub fn transfer_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `153` + // Estimated: `6196` + // Minimum execution time: 43_150_000 picoseconds. + Weight::from_parts(43_919_000, 6196) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn transfer_reserve_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `223` + // Estimated: `6196` + // Minimum execution time: 67_808_000 picoseconds. + Weight::from_parts(69_114_000, 6196) + .saturating_add(T::DbWeight::get().reads(8)) + .saturating_add(T::DbWeight::get().writes(4)) + } + // Storage: `Benchmark::Override` (r:0 w:0) + // Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) + pub fn reserve_asset_deposited() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 18_446_744_073_709_551_000 picoseconds. + Weight::from_parts(18_446_744_073_709_551_000, 0) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn initiate_reserve_withdraw() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 29_312_000 picoseconds. + Weight::from_parts(30_347_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + pub fn receive_teleported_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_283_000 picoseconds. + Weight::from_parts(2_448_000, 0) + } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + pub fn deposit_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `52` + // Estimated: `3593` + // Minimum execution time: 23_556_000 picoseconds. + Weight::from_parts(24_419_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn deposit_reserve_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `122` + // Estimated: `3593` + // Minimum execution time: 58_342_000 picoseconds. + Weight::from_parts(59_598_000, 3593) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn initiate_teleport() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 28_285_000 picoseconds. + Weight::from_parts(29_016_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn initiate_transfer() -> Weight { + // Proof Size summary in bytes: + // Measured: `122` + // Estimated: `3593` + // Minimum execution time: 65_211_000 picoseconds. + Weight::from_parts(67_200_000, 3593) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) + } +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs new file mode 100644 index 000000000000..ae94edc3d731 --- /dev/null +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -0,0 +1,355 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_xcm_benchmarks::generic` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2024-08-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-svzsllib-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: Compiled, CHAIN: Some("collectives-westend-dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_xcm_benchmarks::generic +// --chain=collectives-westend-dev +// --header=./cumulus/file_header.txt +// --template=./cumulus/templates/xcm-bench-template.hbs +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weights for `pallet_xcm_benchmarks::generic`. +pub struct WeightInfo(PhantomData); +impl WeightInfo { + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn report_holding() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 29_015_000 picoseconds. + Weight::from_parts(30_359_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + pub fn buy_execution() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 572_000 picoseconds. + Weight::from_parts(637_000, 0) + } + pub fn pay_fees() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_550_000 picoseconds. + Weight::from_parts(1_604_000, 0) + } + // Storage: `PolkadotXcm::Queries` (r:1 w:0) + // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) + pub fn query_response() -> Weight { + // Proof Size summary in bytes: + // Measured: `32` + // Estimated: `3497` + // Minimum execution time: 7_354_000 picoseconds. + Weight::from_parts(7_808_000, 3497) + .saturating_add(T::DbWeight::get().reads(1)) + } + pub fn transact() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_716_000 picoseconds. + Weight::from_parts(7_067_000, 0) + } + pub fn refund_surplus() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_280_000 picoseconds. + Weight::from_parts(1_355_000, 0) + } + pub fn set_error_handler() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 587_000 picoseconds. + Weight::from_parts(645_000, 0) + } + pub fn set_appendix() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 629_000 picoseconds. + Weight::from_parts(662_000, 0) + } + pub fn clear_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 590_000 picoseconds. + Weight::from_parts(639_000, 0) + } + pub fn descend_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 651_000 picoseconds. + Weight::from_parts(688_000, 0) + } + pub fn clear_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 601_000 picoseconds. + Weight::from_parts(630_000, 0) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn report_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 25_650_000 picoseconds. + Weight::from_parts(26_440_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) + // Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) + pub fn claim_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `90` + // Estimated: `3555` + // Minimum execution time: 10_492_000 picoseconds. + Weight::from_parts(10_875_000, 3555) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn trap() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 597_000 picoseconds. + Weight::from_parts(647_000, 0) + } + // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) + // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn subscribe_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `38` + // Estimated: `3503` + // Minimum execution time: 23_732_000 picoseconds. + Weight::from_parts(24_290_000, 3503) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(3)) + } + // Storage: `PolkadotXcm::VersionNotifyTargets` (r:0 w:1) + // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + pub fn unsubscribe_version() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_446_000 picoseconds. + Weight::from_parts(2_613_000, 0) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn burn_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 960_000 picoseconds. + Weight::from_parts(1_045_000, 0) + } + pub fn expect_asset() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 703_000 picoseconds. + Weight::from_parts(739_000, 0) + } + pub fn expect_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 616_000 picoseconds. + Weight::from_parts(651_000, 0) + } + pub fn expect_error() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 621_000 picoseconds. + Weight::from_parts(660_000, 0) + } + pub fn expect_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 794_000 picoseconds. + Weight::from_parts(831_000, 0) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn query_pallet() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 29_527_000 picoseconds. + Weight::from_parts(30_614_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + pub fn expect_pallet() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_189_000 picoseconds. + Weight::from_parts(3_296_000, 0) + } + // Storage: `ParachainInfo::ParachainId` (r:1 w:0) + // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) + // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) + // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) + // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) + // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + pub fn report_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `70` + // Estimated: `3535` + // Minimum execution time: 25_965_000 picoseconds. + Weight::from_parts(26_468_000, 3535) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + pub fn clear_transact_status() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 618_000 picoseconds. + Weight::from_parts(659_000, 0) + } + pub fn set_topic() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 593_000 picoseconds. + Weight::from_parts(618_000, 0) + } + pub fn clear_topic() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 603_000 picoseconds. + Weight::from_parts(634_000, 0) + } + pub fn alias_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(2_000_000, 0) + } + pub fn set_fees_mode() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 568_000 picoseconds. + Weight::from_parts(629_000, 0) + } + pub fn unpaid_execution() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 598_000 picoseconds. + Weight::from_parts(655_000, 0) + } + pub fn asset_claimer() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 707_000 picoseconds. + Weight::from_parts(749_000, 0) + } + pub fn execute_with_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 713_000 picoseconds. + Weight::from_parts(776_000, 0) + } +} diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs index 9eb9b85a3918..c5ab21fe8f90 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/xcm_config.rs @@ -21,7 +21,6 @@ use super::{ use frame_support::{ parameter_types, traits::{tokens::imbalance::ResolveTo, ConstU32, Contains, Equals, Everything, Nothing}, - weights::Weight, }; use frame_system::EnsureRoot; use pallet_collator_selection::StakingPotAccountId; @@ -39,12 +38,12 @@ use xcm_builder::{ AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain, AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily, - EnsureXcmOrigin, FixedWeightBounds, FrameTransactionalProcessor, FungibleAdapter, - HashedDescription, IsConcrete, LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, - ParentIsPreset, RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, - SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32, - SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents, - WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents, + EnsureXcmOrigin, FrameTransactionalProcessor, FungibleAdapter, HashedDescription, IsConcrete, + LocatableAssetId, OriginToPluralityVoice, ParentAsSuperuser, ParentIsPreset, + RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia, + SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, + TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, + XcmFeeManagerFromComponents, }; use xcm_executor::XcmExecutor; @@ -125,11 +124,6 @@ pub type XcmOriginToTransactDispatchOrigin = ( ); parameter_types! { - /// The amount of weight an XCM operation takes. This is a safe overestimate. - pub const BaseXcmWeight: Weight = Weight::from_parts(1_000_000_000, 1024); - /// A temporary weight value for each XCM instruction. - /// NOTE: This should be removed after we account for PoV weights. - pub const TempFixedXcmWeight: Weight = Weight::from_parts(1_000_000_000, 0); pub const MaxInstructions: u32 = 100; pub const MaxAssetsIntoHolding: u32 = 64; // Fellows pluralistic body. @@ -208,7 +202,11 @@ impl xcm_executor::Config for XcmConfig { type IsTeleporter = TrustedTeleporters; type UniversalLocation = UniversalLocation; type Barrier = Barrier; - type Weigher = FixedWeightBounds; + type Weigher = WeightInfoBounds< + crate::weights::xcm::CollectivesWestendXcmWeight, + RuntimeCall, + MaxInstructions, + >; type Trader = UsingComponents< WeightToFee, WndLocation, @@ -275,7 +273,11 @@ impl pallet_xcm::Config for Runtime { type XcmExecutor = XcmExecutor; type XcmTeleportFilter = Everything; type XcmReserveTransferFilter = Nothing; // This parachain is not meant as a reserve location. - type Weigher = FixedWeightBounds; + type Weigher = WeightInfoBounds< + crate::weights::xcm::CollectivesWestendXcmWeight, + RuntimeCall, + MaxInstructions, + >; type UniversalLocation = UniversalLocation; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; diff --git a/prdoc/pr_6820.prdoc b/prdoc/pr_6820.prdoc new file mode 100644 index 000000000000..85249a33341d --- /dev/null +++ b/prdoc/pr_6820.prdoc @@ -0,0 +1,8 @@ +title: Add XCM benchmarks to collectives-westend +doc: +- audience: Runtime Dev + description: Collectives-westend was using `FixedWeightBounds`, meaning the same + weight per instruction. Added proper benchmarks. +crates: +- name: collectives-westend-runtime + bump: patch From 738282a2c4127f5e6a1c8d50235ba126b9f05025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sat, 11 Jan 2025 11:32:50 +0100 Subject: [PATCH 245/340] Fix incorrected deprecated message (#7118) --- substrate/frame/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 23d22683be2d..f79a52bc6c5b 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -327,7 +327,7 @@ pub mod testing_prelude { pub use frame_system::{self, mocking::*}; - #[deprecated(note = "Use `frame::testing_prelude::TestExternalities` instead.")] + #[deprecated(note = "Use `frame::testing_prelude::TestState` instead.")] pub use sp_io::TestExternalities; pub use sp_io::TestExternalities as TestState; From 7d8e3a434ea1e760190456e8df1359aa8137e16a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 13 Jan 2025 13:32:01 +0100 Subject: [PATCH 246/340] reference-docs: Start `state` and mention well known keys (#7037) Closes: https://github.com/paritytech/polkadot-sdk/issues/7033 --- Cargo.lock | 1 + docs/sdk/Cargo.toml | 1 + docs/sdk/src/reference_docs/mod.rs | 3 +++ docs/sdk/src/reference_docs/state.rs | 12 ++++++++++++ substrate/primitives/storage/src/lib.rs | 4 ++++ 5 files changed, 21 insertions(+) create mode 100644 docs/sdk/src/reference_docs/state.rs diff --git a/Cargo.lock b/Cargo.lock index 4e2272bdc988..cfb805fbe847 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19202,6 +19202,7 @@ dependencies = [ "sp-runtime 31.0.1", "sp-runtime-interface 24.0.0", "sp-std 14.0.0", + "sp-storage 19.0.0", "sp-tracing 16.0.0", "sp-version 29.0.0", "sp-weights 27.0.0", diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml index f526c07796ea..4d83e2045ab0 100644 --- a/docs/sdk/Cargo.toml +++ b/docs/sdk/Cargo.toml @@ -110,6 +110,7 @@ sp-offchain = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-runtime-interface = { workspace = true, default-features = true } sp-std = { workspace = true, default-features = true } +sp-storage = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } sp-weights = { workspace = true, default-features = true } diff --git a/docs/sdk/src/reference_docs/mod.rs b/docs/sdk/src/reference_docs/mod.rs index e47eece784c4..7ad8a37241bf 100644 --- a/docs/sdk/src/reference_docs/mod.rs +++ b/docs/sdk/src/reference_docs/mod.rs @@ -111,3 +111,6 @@ pub mod custom_runtime_api_rpc; /// The [`polkadot-omni-node`](https://crates.io/crates/polkadot-omni-node) and its related binaries. pub mod omni_node; + +/// Learn about the state in Substrate. +pub mod state; diff --git a/docs/sdk/src/reference_docs/state.rs b/docs/sdk/src/reference_docs/state.rs new file mode 100644 index 000000000000..a8138caebf1e --- /dev/null +++ b/docs/sdk/src/reference_docs/state.rs @@ -0,0 +1,12 @@ +//! # State +//! +//! The state is abstracted as a key-value like database. Every item that +//! needs to be persisted by the [State Transition +//! Function](crate::reference_docs::blockchain_state_machines) is written to the state. +//! +//! ## Special keys +//! +//! The key-value pairs in the state are represented as byte sequences. The node +//! doesn't know how to interpret most the key-value pairs. However, there exist some +//! special keys and its values that are known to the node, the so-called +//! [`well-known-keys`](sp_storage::well_known_keys). diff --git a/substrate/primitives/storage/src/lib.rs b/substrate/primitives/storage/src/lib.rs index 4b25f85fba68..df7570a18548 100644 --- a/substrate/primitives/storage/src/lib.rs +++ b/substrate/primitives/storage/src/lib.rs @@ -191,11 +191,15 @@ pub mod well_known_keys { /// Wasm code of the runtime. /// /// Stored as a raw byte vector. Required by substrate. + /// + /// Encodes to `0x3A636F6465`. pub const CODE: &[u8] = b":code"; /// Number of wasm linear memory pages required for execution of the runtime. /// /// The type of this value is encoded `u64`. + /// + /// Encodes to `0x307833413633364636343635` pub const HEAP_PAGES: &[u8] = b":heappages"; /// Current extrinsic index (u32) is stored under this key. From 2f7cf417136537d007d5302d1d08a8958f8a5c97 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 13 Jan 2025 15:44:09 +0100 Subject: [PATCH 247/340] xcm: Fixes for `UnpaidLocalExporter` (#7126) ## Description This PR deprecates `UnpaidLocalExporter` in favor of the new `LocalExporter`. First, the name is misleading, as it can be used in both paid and unpaid scenarios. Second, it contains a hard-coded channel 0, whereas `LocalExporter` uses the same algorithm as `xcm-exporter`. ## Future Improvements Remove the `channel` argument and slightly modify the `ExportXcm::validate` signature as part of [this issue](https://github.com/orgs/paritytech/projects/145/views/8?pane=issue&itemId=84899273). --------- Co-authored-by: command-bot <> --- polkadot/xcm/xcm-builder/src/lib.rs | 6 +- .../src/tests/bridging/local_para_para.rs | 2 +- .../src/tests/bridging/local_relay_relay.rs | 2 +- .../xcm/xcm-builder/src/tests/bridging/mod.rs | 2 +- .../xcm/xcm-builder/src/universal_exports.rs | 62 +++++++++++++++++-- prdoc/pr_7126.prdoc | 7 +++ 6 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 prdoc/pr_7126.prdoc diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs index 3d68d8ed16ae..e23412a97ebc 100644 --- a/polkadot/xcm/xcm-builder/src/lib.rs +++ b/polkadot/xcm/xcm-builder/src/lib.rs @@ -132,11 +132,13 @@ pub use routing::{ mod transactional; pub use transactional::FrameTransactionalProcessor; +#[allow(deprecated)] +pub use universal_exports::UnpaidLocalExporter; mod universal_exports; pub use universal_exports::{ ensure_is_remote, BridgeBlobDispatcher, BridgeMessage, DispatchBlob, DispatchBlobError, - ExporterFor, HaulBlob, HaulBlobError, HaulBlobExporter, NetworkExportTable, - NetworkExportTableItem, SovereignPaidRemoteExporter, UnpaidLocalExporter, UnpaidRemoteExporter, + ExporterFor, HaulBlob, HaulBlobError, HaulBlobExporter, LocalExporter, NetworkExportTable, + NetworkExportTableItem, SovereignPaidRemoteExporter, UnpaidRemoteExporter, }; mod weight; diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs index ea584bf9d485..5e930fe575c2 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/local_para_para.rs @@ -28,7 +28,7 @@ parameter_types! { type TheBridge = TestBridge>; type Router = TestTopic< - UnpaidLocalExporter< + LocalExporter< HaulBlobExporter, UniversalLocation, >, diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs index 38ffe2532d58..a41f09721812 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/local_relay_relay.rs @@ -28,7 +28,7 @@ parameter_types! { type TheBridge = TestBridge>; type Router = TestTopic< - UnpaidLocalExporter< + LocalExporter< HaulBlobExporter, UniversalLocation, >, diff --git a/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs b/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs index 767575e7f2dd..90ad9921d65a 100644 --- a/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs +++ b/polkadot/xcm/xcm-builder/src/tests/bridging/mod.rs @@ -209,7 +209,7 @@ impl, Remote: Get, RemoteExporter: ExportXcm> S let origin = Local::get().relative_to(&Remote::get()); AllowUnpaidFrom::set(vec![origin.clone()]); set_exporter_override(price::, deliver::); - // The we execute it: + // Then we execute it: let mut id = fake_id(); let outcome = XcmExecutor::::prepare_and_execute( origin, diff --git a/polkadot/xcm/xcm-builder/src/universal_exports.rs b/polkadot/xcm/xcm-builder/src/universal_exports.rs index 6b3c3adf737d..e215aea3ab68 100644 --- a/polkadot/xcm/xcm-builder/src/universal_exports.rs +++ b/polkadot/xcm/xcm-builder/src/universal_exports.rs @@ -16,6 +16,8 @@ //! Traits and utilities to help with origin mutation and bridging. +#![allow(deprecated)] + use crate::InspectMessageQueues; use alloc::{vec, vec::Vec}; use codec::{Decode, Encode}; @@ -58,6 +60,8 @@ pub fn ensure_is_remote( /// that the message sending cannot be abused in any way. /// /// This is only useful when the local chain has bridging capabilities. +#[deprecated(note = "Will be removed after July 2025; It uses hard-coded channel `0`, \ + use `xcm_builder::LocalExporter` directly instead.")] pub struct UnpaidLocalExporter( PhantomData<(Exporter, UniversalLocation)>, ); @@ -100,6 +104,54 @@ impl> SendXcm fn ensure_successful_delivery(_: Option) {} } +/// Implementation of `SendXcm` which uses the given `ExportXcm` implementation in order to forward +/// the message over a bridge. +/// +/// This is only useful when the local chain has bridging capabilities. +pub struct LocalExporter(PhantomData<(Exporter, UniversalLocation)>); +impl> SendXcm + for LocalExporter +{ + type Ticket = Exporter::Ticket; + + fn validate( + dest: &mut Option, + msg: &mut Option>, + ) -> SendResult { + // This `clone` ensures that `dest` is not consumed in any case. + let d = dest.clone().take().ok_or(MissingArgument)?; + let universal_source = UniversalLocation::get(); + let devolved = ensure_is_remote(universal_source.clone(), d).map_err(|_| NotApplicable)?; + let (remote_network, remote_location) = devolved; + let xcm = msg.take().ok_or(MissingArgument)?; + + let hash = + (Some(Location::here()), &remote_location).using_encoded(sp_io::hashing::blake2_128); + let channel = u32::decode(&mut hash.as_ref()).unwrap_or(0); + + validate_export::( + remote_network, + channel, + universal_source, + remote_location, + xcm.clone(), + ) + .inspect_err(|err| { + if let NotApplicable = err { + // We need to make sure that msg is not consumed in case of `NotApplicable`. + *msg = Some(xcm); + } + }) + } + + fn deliver(ticket: Exporter::Ticket) -> Result { + Exporter::deliver(ticket) + } + + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful_delivery(_: Option) {} +} + pub trait ExporterFor { /// Return the locally-routable bridge (if any) capable of forwarding `message` to the /// `remote_location` on the remote `network`, together with the payment which is required. @@ -703,9 +755,9 @@ mod tests { let local_dest: Location = (Parent, Parachain(5678)).into(); assert!(ensure_is_remote(UniversalLocation::get(), local_dest.clone()).is_err()); - // UnpaidLocalExporter + // LocalExporter ensure_validate_does_not_consume_dest_or_msg::< - UnpaidLocalExporter, + LocalExporter, >(local_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); // 2. check with not applicable from the inner router (using `NotApplicableBridgeSender`) @@ -713,14 +765,14 @@ mod tests { (Parent, Parent, DifferentRemote::get(), RemoteDestination::get()).into(); assert!(ensure_is_remote(UniversalLocation::get(), remote_dest.clone()).is_ok()); - // UnpaidLocalExporter + // LocalExporter ensure_validate_does_not_consume_dest_or_msg::< - UnpaidLocalExporter, + LocalExporter, >(remote_dest.clone(), |result| assert_eq!(Err(NotApplicable), result)); // 3. Ok - deliver // UnpaidRemoteExporter - assert_ok!(send_xcm::>( + assert_ok!(send_xcm::>( remote_dest, Xcm::default() )); diff --git a/prdoc/pr_7126.prdoc b/prdoc/pr_7126.prdoc new file mode 100644 index 000000000000..1a86af1b2d1d --- /dev/null +++ b/prdoc/pr_7126.prdoc @@ -0,0 +1,7 @@ +title: 'xcm: Fixes for `UnpaidLocalExporter`' +doc: +- audience: Runtime Dev + description: This PR deprecates `UnpaidLocalExporter` in favor of the new `LocalExporter`. First, the name is misleading, as it can be used in both paid and unpaid scenarios. Second, it contains a hard-coded channel 0, whereas `LocalExporter` uses the same algorithm as `xcm-exporter`. +crates: +- name: staging-xcm-builder + bump: minor From ba572ae892d4e4fae89ca053d8a137117b0f3a17 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Mon, 13 Jan 2025 15:49:37 +0100 Subject: [PATCH 248/340] [pallet-revive] Update gas encoding (#6689) Update the current approach to attach the `ref_time`, `pov` and `deposit` parameters to an Ethereum transaction. Previously we will pass these 3 parameters along with the signed payload, and check that the fees resulting from `gas x gas_price` match the actual fees paid by the user for the extrinsic. This approach unfortunately can be attacked. A malicious actor could force such a transaction to fail by injecting low values for some of these extra parameters as they are not part of the signed payload. The new approach encodes these 3 extra parameters in the lower digits of the transaction gas, approximating the the log2 of the actual values to encode each components on 2 digits --------- Co-authored-by: GitHub Action Co-authored-by: command-bot <> --- .../assets/asset-hub-westend/src/lib.rs | 1 + prdoc/pr_6689.prdoc | 19 ++ substrate/bin/node/runtime/src/lib.rs | 1 + .../frame/revive/rpc/examples/js/bun.lockb | Bin 40649 -> 40649 bytes .../frame/revive/rpc/examples/js/package.json | 4 +- .../rpc/examples/js/src/geth-diff.test.ts | 22 -- .../revive/rpc/examples/js/src/piggy-bank.ts | 15 +- .../frame/revive/rpc/revive_chain.metadata | Bin 659977 -> 661594 bytes substrate/frame/revive/rpc/src/client.rs | 4 +- substrate/frame/revive/rpc/src/lib.rs | 23 +- substrate/frame/revive/src/evm.rs | 2 + substrate/frame/revive/src/evm/api/byte.rs | 5 +- substrate/frame/revive/src/evm/gas_encoder.rs | 174 +++++++++++++ substrate/frame/revive/src/evm/runtime.rs | 228 +++++++++--------- substrate/frame/revive/src/lib.rs | 31 ++- 15 files changed, 340 insertions(+), 189 deletions(-) create mode 100644 prdoc/pr_6689.prdoc create mode 100644 substrate/frame/revive/src/evm/gas_encoder.rs diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 5fb495e4e8cf..71cfdc58cceb 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -981,6 +981,7 @@ impl pallet_revive::Config for Runtime { type Xcm = pallet_xcm::Pallet; type ChainId = ConstU64<420_420_421>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. + type EthGasEncoder = (); } impl TryFrom for pallet_revive::Call { diff --git a/prdoc/pr_6689.prdoc b/prdoc/pr_6689.prdoc new file mode 100644 index 000000000000..2cbb49cd7dd2 --- /dev/null +++ b/prdoc/pr_6689.prdoc @@ -0,0 +1,19 @@ +title: '[pallet-revive] Update gas encoding' +doc: +- audience: Runtime Dev + description: |- + Update the current approach to attach the `ref_time`, `pov` and `deposit` parameters to an Ethereum transaction. +Previously, these three parameters were passed along with the signed payload, and the fees resulting from gas × gas_price were checked to ensure they matched the actual fees paid by the user for the extrinsic + + This approach unfortunately can be attacked. A malicious actor could force such a transaction to fail by injecting low values for some of these extra parameters as they are not part of the signed payload. + + The new approach encodes these 3 extra parameters in the lower digits of the transaction gas, using the log2 of the actual values to encode each components on 2 digits +crates: +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-mock-network + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 93b134e8165f..7de04b27ff83 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1468,6 +1468,7 @@ impl pallet_revive::Config for Runtime { type Xcm = (); type ChainId = ConstU64<420_420_420>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. + type EthGasEncoder = (); } impl pallet_sudo::Config for Runtime { diff --git a/substrate/frame/revive/rpc/examples/js/bun.lockb b/substrate/frame/revive/rpc/examples/js/bun.lockb index 46994bb147547bfb9b960b7630d1a6d274ee75dd..67df5841e43fba141c7a146a1e4a8958b4c7a84c 100755 GIT binary patch delta 279 zcmV+y0qFk8y#mR-0+22soiH!oX)-nTSX|in0HhK|x0gz@V`@A~BLYiBe~%4#u}+#2 zlVl7ilMoXalcWwPlaLz;vsn|wBtT0(J!NEn1~X2p?UXB7Jvi1>R<9n);G^G0U~|Q5 zMB7D`Fj;o&?!D+#cbOwVUcVM=HzK{nVywxpEIqXw>`RkfI2b@*8u|njLIC6<#`-5a zC$;wp^T;@qwKuAj(~g=w!jb0XqITSH4R$p8^RE0000m0000WvzcsD2a_Ol8k6jEG_yK%I~4&l dlW~tAlTe%nlg@(=1Tio!GLvzDAG6Mh`xGRLa$o=e delta 274 zcmV+t0qy?Dy#mR-0+22sm$qsC-QFrj08A7+@VAjr|M5l`Sq&6l^96BcMvse?wBtSbdFKlv(4w$;pImPzg)KO3c)E*{sr!?C>Pa^u})5WrH z?2dCjw;3D6xm-<8UmEmHQmoo8gdP^y(@S_h*pic7I2b^vs4PK)kA=h%S6+OX6+SMP!aeojwTtkMl1`0TE2v1*0>msHX$nBE8?5g-|}wKyj* z0XwtZXb%AeIsgCwJG1d=p8^Rm0000W0000EvzcsD2a_Cg5|bEoB9rWM8nZfdI~9|J YfgqDloClN6gAV~QlVOG;v(Ab86w_&Re*gdg diff --git a/substrate/frame/revive/rpc/examples/js/package.json b/substrate/frame/revive/rpc/examples/js/package.json index 6d8d00fd4214..0119f4f34a17 100644 --- a/substrate/frame/revive/rpc/examples/js/package.json +++ b/substrate/frame/revive/rpc/examples/js/package.json @@ -9,10 +9,10 @@ "preview": "vite preview" }, "dependencies": { + "@parity/revive": "^0.0.5", "ethers": "^6.13.4", "solc": "^0.8.28", - "viem": "^2.21.47", - "@parity/revive": "^0.0.5" + "viem": "^2.21.47" }, "devDependencies": { "prettier": "^3.3.3", diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index b9ee877927bb..871adeccbc9a 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -289,27 +289,5 @@ for (const env of envs) { ], }) }) - - test.only('eth_estimate (no gas specified) child_call', async () => { - let balance = await env.serverWallet.getBalance(env.accountWallet.account) - expect(balance).toBe(0n) - - const data = encodeFunctionData({ - abi: FlipperCallerAbi, - functionName: 'callFlip', - }) - - await env.accountWallet.request({ - method: 'eth_estimateGas', - params: [ - { - data, - from: env.accountWallet.account.address, - to: flipperCallerAddr, - gas: `0x${Number(1000000).toString(16)}`, - }, - ], - }) - }) }) } diff --git a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts index 0040b0c78dc4..8289ac8b76e3 100644 --- a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts +++ b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts @@ -1,9 +1,9 @@ import { assert, getByteCode, walletClient } from './lib.ts' -import { abi } from '../abi/piggyBank.ts' +import { PiggyBankAbi } from '../abi/piggyBank.ts' import { parseEther } from 'viem' const hash = await walletClient.deployContract({ - abi, + abi: PiggyBankAbi, bytecode: getByteCode('piggyBank'), }) const deployReceipt = await walletClient.waitForTransactionReceipt({ hash }) @@ -16,7 +16,7 @@ assert(contractAddress, 'Contract address should be set') const result = await walletClient.estimateContractGas({ account: walletClient.account, address: contractAddress, - abi, + abi: PiggyBankAbi, functionName: 'deposit', value: parseEther('10'), }) @@ -26,7 +26,7 @@ assert(contractAddress, 'Contract address should be set') const { request } = await walletClient.simulateContract({ account: walletClient.account, address: contractAddress, - abi, + abi: PiggyBankAbi, functionName: 'deposit', value: parseEther('10'), }) @@ -36,9 +36,6 @@ assert(contractAddress, 'Contract address should be set') const receipt = await walletClient.waitForTransactionReceipt({ hash }) console.log(`Deposit receipt: ${receipt.status}`) - if (process.env.STOP) { - process.exit(0) - } } // Withdraw 5 WST @@ -46,7 +43,7 @@ assert(contractAddress, 'Contract address should be set') const { request } = await walletClient.simulateContract({ account: walletClient.account, address: contractAddress, - abi, + abi: PiggyBankAbi, functionName: 'withdraw', args: [parseEther('5')], }) @@ -58,7 +55,7 @@ assert(contractAddress, 'Contract address should be set') // Check remaining balance const balance = await walletClient.readContract({ address: contractAddress, - abi, + abi: PiggyBankAbi, functionName: 'getDeposit', }) diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index 64b1f2014dd06815fcea6a87bc96306eb00eda8b..402e8c2d22b21471929e9c61acd2cc968af614cf 100644 GIT binary patch delta 4640 zcmaJ_dr%eE8Q*j7Ue4X?W7i0Zh{+N~A%Pn(V6jRvK5&AG&q$0l3@-N`VCCN3>+W72 z8bzj|;2(C9rsmW%w$a8`nsH(#V^=%csYXprCp9=4<6AUpe1O4;&DcEJ@7zVX*PE$> z?Cv?=V-8_D!+JO4PcsL;Vz4=qx|5#6w=!h~sO z;@yLC9MLtL@_$9oP&B3|poBssC|5Uwl4e~avaYc=IiSOOz)~BC>7|vP z{V0?Eag|4IDUZQy)Q$R(t_0=Aeh1WoYJege#3V+8VyU{8Cs#);*=p2fQwb3{LJU<0 z=z3HODuzl-IUF@DIii^6ud7LoZV;K_46ql~l$C%L1!KWb(^P9U67+L>^5tc8REsCi z{`V@+%}lWfm;$8AQAxbC>!Fkpbl6*-H227YZS+s8JYxpSYB)@Ss$!8)Go8EEGZ9Ur z->mXHGYUed7#29EM2@HNSG@6tC#%KL877(~n{|3L1hB!eN|4A=4MH9R8fU3&DfPr$ zfMl7})Ib>ga=FE92$33afbQ7EB4@BKqJo`U4b_4evgErDXuxe|za#(_3#bBqU`fcFF6%Xm*auS) zpBnx=vr&mug@|I`_>CuH&~P4QMwDijW+f=|1rnGVEorZK#{>UU?232r1UG%}&6HWW zpdmGQhC>A-+RVZ?k>8o9MHY%m3yKwG;X(FFKFTxHC z$VR0i@=9~zWP=jvc|A;ct`yi1$CUxyLSjZkmKW$=;mc4t(0b?9^V};8(%>?~;JI>< zqoOF?% zq6)hB7|upZ=;~wmvBD)HFHc`&5Hd%Jc==~hE6=}xN;saY1X!_OE=AyRJ56{IgKBt zA6>-5(N=o;B7OCREFRaEO?weji-S0n8GvNxV&Iz0@mHhSa&;E*~2xT+v~7?dYGb=tluNW zmk=byK?>+^ht7XOj=sStswZ=})iX2@7%)$j2nB z2gyu*ega)#GF_oVSIAI|p+~M_sjwJ$1}=-qEYhXG7`rVd1NprITk5b*j%7?H&HEC+ zm(53wn2XZN6F6gMa*jlm z^LV&0$AJQ$vE+qQIfCB3hR4$#*P(9DxQ@ecGrfNuF3u(NyX$yj?vez*R3`hS(veun zxW>|zH*j#NlE6cfd5FWq9=ta_ol_GS!pRI_haoJ{?nCZWI_D-{>{`+P@z&iXbk{_u^7C5SC^~Xfyq)7pKzS_uy?Us}mRt;u#B)7+VvF+gO0^ zt7!w-_rl+=hb{5mb<%4Rw+>!i>m_cz9Q=UcC7lA?avS?_ z9$avT`tXE72ibnMmpd$ZDca&mwFlqE-wr{~?euz|7Fw3w@9+{@LlQ(>j+f&y--a$3 zdhl4=2z`01i-e)g>8%O0%l-YKpUXl{L#=AC8IDa&vDv3VQA0{K3{3;$28?geII}Lg zWk3SFkwBC+bAAvthmB4VP^g+P?f@xt-w#MONEn*;AS}zvG~FUqN+c3eNwDfZsAYa` z1(KiBYt?`p)Y+I}LKiAWbePqGsu@wN*@3zNYZ6G8i9hr#yC)HTDqas!iO#^ z4Z0eXgHg~Ffc-r1Nk3crEWvBf5(M=Fly2|XBUF0m!~2D7ltX9k7dnR}w%)m72kqG} z%tQIK^h2QpO{NVW3Liil{n!CvCYomJ2ZZg&T_~2(M-B-`+!bOm?K>na_vMSRI#wYP zNLQdQAwH5H0E)$9)eRgi>rMPa+jPnxS> zd?2hG=z0yfM3hTu`5ht8{`wo?x2}}fN@Z+?x(>L<(SnN*+n)ajlFRYfa5ukR^p;Wd zy^y*g*Y`^l-LT6y39a66%eNdl@}D8!p+W1U)R_3!qz(4tE?);qSoxdVw+n$!8$G`1 zJk*O_6s7pa&{H1YSa9hN9^YcrX}^%-YvZBcJ>&CjMtkiwgM57m?Xx38d{u6A(Ed~c Jo$kXD_dmxEok9Qr delta 3431 zcmZu!3s6+o8NTQ4UH&~QdM^cZ@sWUvLPyAA0AU0J=yoJFD#l0E(#@*Zu*$Ny4oG!GI28I7aePHWI8l)M#r&Noj55;X(3azI->-OiA1S@g5s-MqtkP_Y}wn}nT2!r zod5gIcfNDa|DU~nGNtx-N|Z+qxWu+lR815)6UBdW>|;w^?B4Uoc;);tQ=pT~Qr_4h zJh75eJhD$%G%0a;nVjfzRcuc5xTj29EcB$0bQ4)=cv2}(yZ9F4`Pi%BfDUWKVbn#ftOZjay~4i;-!vRUBvuwX9Q z;qwWcLko7`z&9`z_jJGvQjDK=z*O=MUhIGbbalWqw0;BMvkn%sg_NMP6Ozdatm%Xw zv*u(c*lepA4X6I&OGS=-IKdJUayS>>?1W{pB_gpGSCq*xE!tj+iSb;!26 zbwWL2q*9N{tC#A*_UmwkY{B3C1D4r?I&ifL-WgtIbp&`PduFCotEeQL-v!y2)WueO zxC`=02s^tVmDHf=1|+A}>I@DU4Gw8sby#u(uH(`h5RV1l!uiw#I$*sKSg!%=RbU*> z`3_RZ5q$4E$RG_^@ICyToWO!!$QK(#A}r&GO|D{w`A^~8n=mE4LATOIb){t^``H+= z(6ZrmHmbC*;qjX=ku>2KHzA2N-8W$_n}UtJj(Od%(0)oM_>58T8BMU-@JqRK8tIj8 zMsq>a-K^8y{Qq<}>vXq7>TWUUZqcUb>V*mDz6BFVBZh84T6&v~q&_o{}!tK!ezhWt65q9w1==k~Y*?kd?CsIHX# z(u&zx+3YpvZipmtZ7@4?ZenIB$G&l#Nnnh2u3N;*gD?><-GNxrqxm#2yubXAh`W1X z8qs!^?-_|4-mN}uxBBp-e?ksFFfuf^8;ai;(k-JW(u5v^2~S3lRd*nte;%pjWuz8D za91x(ACZEm@4#ea?oVZknelP?l54{TUojAAVMex{y;@RaQh6CE;iW->8JG4!F`;;< z4~j@U-soeQVv0oUTKdXUEu6=XqRPof~D@St7F7piCNi$d zRUR}-;3zX>G#ihV4B2?BG<+b{hL4fB7}b**c0JjEkW5E!V<6A>4)$DFH+4+!&r&>*mSV<~) z2+8SNbPi-P(}H0GGDmkHsF4Nnb^KO=GM!&aQrL|OT3cnY&v<-7P4X3}PLJlVpvD6gJE%s8@8 zk<6C2=-z{(ML8(aveRU;LcXG<=9rYiO83g}&Cc6(*-17`P0|*1Tm$Fg;@DFddXBGW?)e}-DFJ_r48&FRU*3ZkL`3I zP8KbJt!ZM)x^nOO%}(Z7S*BZ2F4FnDxJ8`q54ZxdhutJvAgIvG^bfo#B-SW}y>t?f z2`1~Crg&1TEHPOR5v8GnPK;)o_xUZlTi7oOf5y;lIt|y|rW472+SLCvy4ALNBDI)Cw z?IzvBFzHDoEJU8GxV0v;lz}rI((Qb{gqeeM2fqG@0&yraAJJXJk|su%%N5c)c;Ycl z2!AV4E8Zj{+551tX6vr!in2#FnDb;#@M67wd=2omps1+>^jOn3qxGl>1A-j@N zux68LECA~!vJ*cA>tDvyNO8lwb<%!ihsC;&=z2t}^(2;Tj#T8Nwjqp qX;gk6ZLJa5g)J?{+Ca`Jmtw3#gj`UXW35gTX;zXZu*pm<689g-;Mqz5 diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 901c15e9756b..de97844eccbb 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -17,7 +17,7 @@ //! The client connects to the source substrate chain //! and is used by the rpc server to query and send transactions to the substrate chain. use crate::{ - runtime::GAS_PRICE, + runtime::gas_from_fee, subxt_client::{ revive::{calls::types::EthTransact, events::ContractEmitted}, runtime_types::pallet_revive::storage::ContractInfo, @@ -771,7 +771,7 @@ impl Client { pub async fn evm_block(&self, block: Arc) -> Result { let runtime_api = self.inner.api.runtime_api().at(block.hash()); let max_fee = Self::weight_to_fee(&runtime_api, self.max_block_weight()).await?; - let gas_limit = U256::from(max_fee / GAS_PRICE as u128); + let gas_limit = gas_from_fee(max_fee); let header = block.header(); let timestamp = extract_block_timestamp(&block).await.unwrap_or_default(); diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index ccd8bb043e90..230f2f8b7ef9 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -148,31 +148,12 @@ impl EthRpcServer for EthRpcServerImpl { async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult { let hash = H256(keccak_256(&transaction.0)); - - let tx = TransactionSigned::decode(&transaction.0).map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to decode transaction: {err:?}"); - EthRpcError::from(err) - })?; - - let eth_addr = tx.recover_eth_address().map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to recover eth address: {err:?}"); - EthRpcError::InvalidSignature - })?; - - let tx = GenericTransaction::from_signed(tx, Some(eth_addr)); - - // Dry run the transaction to get the weight limit and storage deposit limit - let dry_run = self.client.dry_run(tx, BlockTag::Latest.into()).await?; - - let call = subxt_client::tx().revive().eth_transact( - transaction.0, - dry_run.gas_required.into(), - dry_run.storage_deposit, - ); + let call = subxt_client::tx().revive().eth_transact(transaction.0); self.client.submit(call).await.map_err(|err| { log::debug!(target: LOG_TARGET, "submit call failed: {err:?}"); err })?; + log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}"); Ok(hash) } diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs index c3495fc0559d..c8c967fbe091 100644 --- a/substrate/frame/revive/src/evm.rs +++ b/substrate/frame/revive/src/evm.rs @@ -19,4 +19,6 @@ mod api; pub use api::*; +mod gas_encoder; +pub use gas_encoder::*; pub mod runtime; diff --git a/substrate/frame/revive/src/evm/api/byte.rs b/substrate/frame/revive/src/evm/api/byte.rs index df4ed1740ecd..c2d64f8e5e42 100644 --- a/substrate/frame/revive/src/evm/api/byte.rs +++ b/substrate/frame/revive/src/evm/api/byte.rs @@ -116,7 +116,10 @@ macro_rules! impl_hex { impl Debug for $type { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - write!(f, concat!(stringify!($type), "({})"), self.0.to_hex()) + let hex_str = self.0.to_hex(); + let truncated = &hex_str[..hex_str.len().min(100)]; + let ellipsis = if hex_str.len() > 100 { "..." } else { "" }; + write!(f, concat!(stringify!($type), "({}{})"), truncated,ellipsis) } } diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs new file mode 100644 index 000000000000..ffdf8b13c043 --- /dev/null +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -0,0 +1,174 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//! Encodes/Decodes EVM gas values. + +use crate::Weight; +use core::ops::{Div, Rem}; +use frame_support::pallet_prelude::CheckedShl; +use sp_arithmetic::traits::{One, Zero}; +use sp_core::U256; + +// We use 3 digits to store each component. +const SCALE: u128 = 100; + +/// Rounds up the given value to the nearest multiple of the mask. +/// +/// # Panics +/// Panics if the `mask` is zero. +fn round_up(value: T, mask: T) -> T +where + T: One + Zero + Copy + Rem + Div, + ::Output: PartialEq, +{ + let rest = if value % mask == T::zero() { T::zero() } else { T::one() }; + value / mask + rest +} + +/// Rounds up the log2 of the given value to the nearest integer. +fn log2_round_up(val: T) -> u128 +where + T: Into, +{ + let val = val.into(); + val.checked_ilog2() + .map(|v| if 1u128 << v == val { v } else { v + 1 }) + .unwrap_or(0) as u128 +} + +mod private { + pub trait Sealed {} + impl Sealed for () {} +} + +/// Encodes/Decodes EVM gas values. +/// +/// # Note +/// +/// This is defined as a trait rather than standalone functions to allow +/// it to be added as an associated type to [`crate::Config`]. This way, +/// it can be invoked without requiring the implementation bounds to be +/// explicitly specified. +/// +/// This trait is sealed and cannot be implemented by downstream crates. +pub trait GasEncoder: private::Sealed { + /// Encodes all components (deposit limit, weight reference time, and proof size) into a single + /// gas value. + fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256; + + /// Decodes the weight and deposit from the encoded gas value. + /// Returns `None` if the gas value is invalid + fn decode(gas: U256) -> Option<(Weight, Balance)>; +} + +impl GasEncoder for () +where + Balance: Zero + One + CheckedShl + Into, +{ + /// The encoding follows the pattern `g...grrppdd`, where: + /// - `dd`: log2 Deposit value, encoded in the lowest 2 digits. + /// - `pp`: log2 Proof size, encoded in the next 2 digits. + /// - `rr`: log2 Reference time, encoded in the next 2 digits. + /// - `g...g`: Gas limit, encoded in the highest digits. + /// + /// # Note + /// - The deposit value is maxed by 2^99 + fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256 { + let deposit: u128 = deposit.into(); + let deposit_component = log2_round_up(deposit); + + let proof_size = weight.proof_size(); + let proof_size_component = SCALE * log2_round_up(proof_size); + + let ref_time = weight.ref_time(); + let ref_time_component = SCALE.pow(2) * log2_round_up(ref_time); + + let components = U256::from(deposit_component + proof_size_component + ref_time_component); + + let raw_gas_mask = U256::from(SCALE).pow(3.into()); + let raw_gas_component = if gas_limit < raw_gas_mask.saturating_add(components) { + raw_gas_mask + } else { + round_up(gas_limit, raw_gas_mask).saturating_mul(raw_gas_mask) + }; + + components.saturating_add(raw_gas_component) + } + + fn decode(gas: U256) -> Option<(Weight, Balance)> { + let deposit = gas % SCALE; + + // Casting with as_u32 is safe since all values are maxed by `SCALE`. + let deposit = deposit.as_u32(); + let proof_time = ((gas / SCALE) % SCALE).as_u32(); + let ref_time = ((gas / SCALE.pow(2)) % SCALE).as_u32(); + + let weight = Weight::from_parts( + if ref_time == 0 { 0 } else { 1u64.checked_shl(ref_time)? }, + if proof_time == 0 { 0 } else { 1u64.checked_shl(proof_time)? }, + ); + let deposit = + if deposit == 0 { Balance::zero() } else { Balance::one().checked_shl(deposit)? }; + + Some((weight, deposit)) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_gas_encoding_decoding_works() { + let raw_gas_limit = 111_111_999_999_999u128; + let weight = Weight::from_parts(222_999_999, 333_999_999); + let deposit = 444_999_999u64; + + let encoded_gas = <() as GasEncoder>::encode(raw_gas_limit.into(), weight, deposit); + assert_eq!(encoded_gas, U256::from(111_112_000_282_929u128)); + assert!(encoded_gas > raw_gas_limit.into()); + + let (decoded_weight, decoded_deposit) = + <() as GasEncoder>::decode(encoded_gas).unwrap(); + assert!(decoded_weight.all_gte(weight)); + assert!(weight.mul(2).all_gte(weight)); + + assert!(decoded_deposit >= deposit); + assert!(deposit * 2 >= decoded_deposit); + } + + #[test] + fn test_encoding_zero_values_work() { + let encoded_gas = <() as GasEncoder>::encode( + Default::default(), + Default::default(), + Default::default(), + ); + + assert_eq!(encoded_gas, U256::from(1_00_00_00)); + + let (decoded_weight, decoded_deposit) = + <() as GasEncoder>::decode(encoded_gas).unwrap(); + assert_eq!(Weight::default(), decoded_weight); + assert_eq!(0u64, decoded_deposit); + } + + #[test] + fn test_overflow() { + assert_eq!(None, <() as GasEncoder>::decode(65_00u128.into()), "Invalid proof size"); + assert_eq!(None, <() as GasEncoder>::decode(65_00_00u128.into()), "Invalid ref_time"); + } +} diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 24b75de83569..d4b344e20eb8 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -16,9 +16,13 @@ // limitations under the License. //! Runtime types for integrating `pallet-revive` with the EVM. use crate::{ - evm::api::{GenericTransaction, TransactionSigned}, - AccountIdOf, AddressMapper, BalanceOf, MomentOf, Weight, LOG_TARGET, + evm::{ + api::{GenericTransaction, TransactionSigned}, + GasEncoder, + }, + AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, LOG_TARGET, }; +use alloc::vec::Vec; use codec::{Decode, Encode}; use frame_support::{ dispatch::{DispatchInfo, GetDispatchInfo}, @@ -26,20 +30,17 @@ use frame_support::{ }; use pallet_transaction_payment::OnChargeTransaction; use scale_info::{StaticTypeInfo, TypeInfo}; -use sp_arithmetic::Percent; use sp_core::{Get, H256, U256}; use sp_runtime::{ generic::{self, CheckedExtrinsic, ExtrinsicFormat}, traits::{ - self, Checkable, Dispatchable, ExtrinsicLike, ExtrinsicMetadata, IdentifyAccount, Member, - TransactionExtension, + self, AtLeast32BitUnsigned, Checkable, Dispatchable, ExtrinsicLike, ExtrinsicMetadata, + IdentifyAccount, Member, TransactionExtension, }, transaction_validity::{InvalidTransaction, TransactionValidityError}, OpaqueExtrinsic, RuntimeDebug, Saturating, }; -use alloc::vec::Vec; - type CallOf = ::RuntimeCall; /// The EVM gas price. @@ -48,7 +49,28 @@ type CallOf = ::RuntimeCall; /// We use a fixed value for the gas price. /// This let us calculate the gas estimate for a transaction with the formula: /// `estimate_gas = substrate_fee / gas_price`. -pub const GAS_PRICE: u32 = 1u32; +/// +/// The chosen constant value is: +/// - Not too high, ensuring the gas value is large enough (at least 7 digits) to encode the +/// ref_time, proof_size, and deposit into the less significant (6 lower) digits of the gas value. +/// - Not too low, enabling users to adjust the gas price to define a tip. +pub const GAS_PRICE: u32 = 1_000u32; + +/// Convert a `Balance` into a gas value, using the fixed `GAS_PRICE`. +/// The gas is calculated as `balance / GAS_PRICE`, rounded up to the nearest integer. +pub fn gas_from_fee(fee: Balance) -> U256 +where + u32: Into, + Balance: Into + AtLeast32BitUnsigned + Copy, +{ + let gas_price = GAS_PRICE.into(); + let remainder = fee % gas_price; + if remainder.is_zero() { + (fee / gas_price).into() + } else { + (fee.saturating_add(gas_price) / gas_price).into() + } +} /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. @@ -140,15 +162,8 @@ where fn check(self, lookup: &Lookup) -> Result { if !self.0.is_signed() { if let Ok(call) = self.0.function.clone().try_into() { - if let crate::Call::eth_transact { payload, gas_limit, storage_deposit_limit } = - call - { - let checked = E::try_into_checked_extrinsic( - payload, - gas_limit, - storage_deposit_limit, - self.encoded_size(), - )?; + if let crate::Call::eth_transact { payload } = call { + let checked = E::try_into_checked_extrinsic(payload, self.encoded_size())?; return Ok(checked) }; } @@ -251,7 +266,7 @@ where /// EthExtra convert an unsigned [`crate::Call::eth_transact`] into a [`CheckedExtrinsic`]. pub trait EthExtra { /// The Runtime configuration. - type Config: crate::Config + pallet_transaction_payment::Config; + type Config: Config + pallet_transaction_payment::Config; /// The Runtime's transaction extension. /// It should include at least: @@ -281,8 +296,6 @@ pub trait EthExtra { /// - `encoded_len`: The encoded length of the extrinsic. fn try_into_checked_extrinsic( payload: Vec, - gas_limit: Weight, - storage_deposit_limit: BalanceOf, encoded_len: usize, ) -> Result< CheckedExtrinsic, CallOf, Self::Extension>, @@ -307,12 +320,16 @@ pub trait EthExtra { InvalidTransaction::BadProof })?; - let signer = - ::AddressMapper::to_fallback_account_id(&signer); + let signer = ::AddressMapper::to_fallback_account_id(&signer); let GenericTransaction { nonce, chain_id, to, value, input, gas, gas_price, .. } = GenericTransaction::from_signed(tx, None); - if chain_id.unwrap_or_default() != ::ChainId::get().into() { + let Some(gas) = gas else { + log::debug!(target: LOG_TARGET, "No gas provided"); + return Err(InvalidTransaction::Call); + }; + + if chain_id.unwrap_or_default() != ::ChainId::get().into() { log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}"); return Err(InvalidTransaction::Call); } @@ -324,6 +341,13 @@ pub trait EthExtra { })?; let data = input.unwrap_or_default().0; + + let (gas_limit, storage_deposit_limit) = + ::EthGasEncoder::decode(gas).ok_or_else(|| { + log::debug!(target: LOG_TARGET, "Failed to decode gas: {gas:?}"); + InvalidTransaction::Call + })?; + let call = if let Some(dest) = to { crate::Call::call:: { dest, @@ -359,13 +383,13 @@ pub trait EthExtra { // Fees calculated with the fixed `GAS_PRICE` // When we dry-run the transaction, we set the gas to `Fee / GAS_PRICE` let eth_fee_no_tip = U256::from(GAS_PRICE) - .saturating_mul(gas.unwrap_or_default()) + .saturating_mul(gas) .try_into() .map_err(|_| InvalidTransaction::Call)?; // Fees with the actual gas_price from the transaction. let eth_fee: BalanceOf = U256::from(gas_price.unwrap_or_default()) - .saturating_mul(gas.unwrap_or_default()) + .saturating_mul(gas) .try_into() .map_err(|_| InvalidTransaction::Call)?; @@ -380,27 +404,17 @@ pub trait EthExtra { Default::default(), ) .into(); - log::trace!(target: LOG_TARGET, "try_into_checked_extrinsic: encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); + log::debug!(target: LOG_TARGET, "try_into_checked_extrinsic: gas_price: {gas_price:?}, encoded_len: {encoded_len:?} actual_fee: {actual_fee:?} eth_fee: {eth_fee:?}"); // The fees from the Ethereum transaction should be greater or equal to the actual fees paid // by the account. if eth_fee < actual_fee { - log::debug!(target: LOG_TARGET, "fees {eth_fee:?} too low for the extrinsic {actual_fee:?}"); + log::debug!(target: LOG_TARGET, "eth fees {eth_fee:?} too low, actual fees: {actual_fee:?}"); return Err(InvalidTransaction::Payment.into()) } - let min = actual_fee.min(eth_fee_no_tip); - let max = actual_fee.max(eth_fee_no_tip); - let diff = Percent::from_rational(max - min, min); - if diff > Percent::from_percent(10) { - log::trace!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?} should be no more than 10% got {diff:?}"); - return Err(InvalidTransaction::Call.into()) - } else { - log::trace!(target: LOG_TARGET, "Difference between the extrinsic fees {actual_fee:?} and the Ethereum gas fees {eth_fee_no_tip:?}: {diff:?}"); - } - let tip = eth_fee.saturating_sub(eth_fee_no_tip); - log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce {nonce:?} and tip: {tip:?}"); + log::debug!(target: LOG_TARGET, "Created checked Ethereum transaction with nonce: {nonce:?} and tip: {tip:?}"); Ok(CheckedExtrinsic { format: ExtrinsicFormat::Signed(signer.into(), Self::get_eth_extension(nonce, tip)), function, @@ -415,6 +429,7 @@ mod test { evm::*, test_utils::*, tests::{ExtBuilder, RuntimeCall, RuntimeOrigin, Test}, + Weight, }; use frame_support::{error::LookupError, traits::fungible::Mutate}; use pallet_revive_fixtures::compile_module; @@ -456,8 +471,6 @@ mod test { #[derive(Clone)] struct UncheckedExtrinsicBuilder { tx: GenericTransaction, - gas_limit: Weight, - storage_deposit_limit: BalanceOf, before_validate: Option>, } @@ -467,12 +480,10 @@ mod test { Self { tx: GenericTransaction { from: Some(Account::default().address()), - chain_id: Some(::ChainId::get().into()), + chain_id: Some(::ChainId::get().into()), gas_price: Some(U256::from(GAS_PRICE)), ..Default::default() }, - gas_limit: Weight::zero(), - storage_deposit_limit: 0, before_validate: None, } } @@ -500,7 +511,6 @@ mod test { fn call_with(dest: H160) -> Self { let mut builder = Self::new(); builder.tx.to = Some(dest); - ExtBuilder::default().build().execute_with(|| builder.estimate_gas()); builder } @@ -508,45 +518,42 @@ mod test { fn instantiate_with(code: Vec, data: Vec) -> Self { let mut builder = Self::new(); builder.tx.input = Some(Bytes(code.into_iter().chain(data.into_iter()).collect())); - ExtBuilder::default().build().execute_with(|| builder.estimate_gas()); builder } - /// Update the transaction with the given function. - fn update(mut self, f: impl FnOnce(&mut GenericTransaction) -> ()) -> Self { - f(&mut self.tx); - self - } /// Set before_validate function. fn before_validate(mut self, f: impl Fn() + Send + Sync + 'static) -> Self { self.before_validate = Some(std::sync::Arc::new(f)); self } + fn check( + self, + ) -> Result<(RuntimeCall, SignedExtra, GenericTransaction), TransactionValidityError> { + self.mutate_estimate_and_check(Box::new(|_| ())) + } + /// Call `check` on the unchecked extrinsic, and `pre_dispatch` on the signed extension. - fn check(&self) -> Result<(RuntimeCall, SignedExtra), TransactionValidityError> { + fn mutate_estimate_and_check( + mut self, + f: Box ()>, + ) -> Result<(RuntimeCall, SignedExtra, GenericTransaction), TransactionValidityError> { + ExtBuilder::default().build().execute_with(|| self.estimate_gas()); + f(&mut self.tx); ExtBuilder::default().build().execute_with(|| { - let UncheckedExtrinsicBuilder { - tx, - gas_limit, - storage_deposit_limit, - before_validate, - } = self.clone(); + let UncheckedExtrinsicBuilder { tx, before_validate, .. } = self.clone(); // Fund the account. let account = Account::default(); - let _ = ::Currency::set_balance( + let _ = ::Currency::set_balance( &account.substrate_account(), 100_000_000_000_000, ); - let payload = - account.sign_transaction(tx.try_into_unsigned().unwrap()).signed_payload(); - let call = RuntimeCall::Contracts(crate::Call::eth_transact { - payload, - gas_limit, - storage_deposit_limit, - }); + let payload = account + .sign_transaction(tx.clone().try_into_unsigned().unwrap()) + .signed_payload(); + let call = RuntimeCall::Contracts(crate::Call::eth_transact { payload }); let encoded_len = call.encoded_size(); let uxt: Ex = generic::UncheckedExtrinsic::new_bare(call).into(); @@ -565,7 +572,7 @@ mod test { 0, )?; - Ok((result.function, extra)) + Ok((result.function, extra, tx)) }) } } @@ -573,14 +580,18 @@ mod test { #[test] fn check_eth_transact_call_works() { let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); + let (call, _, tx) = builder.check().unwrap(); + let (gas_limit, storage_deposit_limit) = + <::EthGasEncoder as GasEncoder<_>>::decode(tx.gas.unwrap()).unwrap(); + assert_eq!( - builder.check().unwrap().0, + call, crate::Call::call:: { - dest: builder.tx.to.unwrap(), - value: builder.tx.value.unwrap_or_default().as_u64(), - gas_limit: builder.gas_limit, - storage_deposit_limit: builder.storage_deposit_limit, - data: builder.tx.input.unwrap_or_default().0 + dest: tx.to.unwrap(), + value: tx.value.unwrap_or_default().as_u64(), + data: tx.input.unwrap_or_default().0, + gas_limit, + storage_deposit_limit } .into() ); @@ -591,16 +602,19 @@ mod test { let (code, _) = compile_module("dummy").unwrap(); let data = vec![]; let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()); + let (call, _, tx) = builder.check().unwrap(); + let (gas_limit, storage_deposit_limit) = + <::EthGasEncoder as GasEncoder<_>>::decode(tx.gas.unwrap()).unwrap(); assert_eq!( - builder.check().unwrap().0, + call, crate::Call::instantiate_with_code:: { - value: builder.tx.value.unwrap_or_default().as_u64(), - gas_limit: builder.gas_limit, - storage_deposit_limit: builder.storage_deposit_limit, + value: tx.value.unwrap_or_default().as_u64(), code, data, - salt: None + salt: None, + gas_limit, + storage_deposit_limit } .into() ); @@ -608,11 +622,10 @@ mod test { #[test] fn check_eth_transact_nonce_works() { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) - .update(|tx| tx.nonce = Some(1u32.into())); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); assert_eq!( - builder.check(), + builder.mutate_estimate_and_check(Box::new(|tx| tx.nonce = Some(1u32.into()))), Err(TransactionValidityError::Invalid(InvalidTransaction::Future)) ); @@ -629,11 +642,10 @@ mod test { #[test] fn check_eth_transact_chain_id_works() { - let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) - .update(|tx| tx.chain_id = Some(42.into())); + let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])); assert_eq!( - builder.check(), + builder.mutate_estimate_and_check(Box::new(|tx| tx.chain_id = Some(42.into()))), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) ); } @@ -646,14 +658,14 @@ mod test { // Fail because the tx input fail to get the blob length assert_eq!( - builder.clone().update(|tx| tx.input = Some(Bytes(vec![1, 2, 3]))).check(), + builder.mutate_estimate_and_check(Box::new(|tx| tx.input = Some(Bytes(vec![1, 2, 3])))), Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) ); } #[test] fn check_transaction_fees() { - let scenarios: [(_, Box, _); 5] = [ + let scenarios: Vec<(_, Box, _)> = vec![ ( "Eth fees too low", Box::new(|tx| { @@ -661,42 +673,20 @@ mod test { }), InvalidTransaction::Payment, ), - ( - "Gas fees too high", - Box::new(|tx| { - tx.gas = Some(tx.gas.unwrap() * 2); - }), - InvalidTransaction::Call, - ), ( "Gas fees too low", Box::new(|tx| { - tx.gas = Some(tx.gas.unwrap() * 2); - }), - InvalidTransaction::Call, - ), - ( - "Diff > 10%", - Box::new(|tx| { - tx.gas = Some(tx.gas.unwrap() * 111 / 100); + tx.gas = Some(tx.gas.unwrap() / 2); }), - InvalidTransaction::Call, - ), - ( - "Diff < 10%", - Box::new(|tx| { - tx.gas_price = Some(tx.gas_price.unwrap() * 2); - tx.gas = Some(tx.gas.unwrap() * 89 / 100); - }), - InvalidTransaction::Call, + InvalidTransaction::Payment, ), ]; for (msg, update_tx, err) in scenarios { - let builder = - UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).update(update_tx); + let res = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])) + .mutate_estimate_and_check(update_tx); - assert_eq!(builder.check(), Err(TransactionValidityError::Invalid(err)), "{}", msg); + assert_eq!(res, Err(TransactionValidityError::Invalid(err)), "{}", msg); } } @@ -704,16 +694,16 @@ mod test { fn check_transaction_tip() { let (code, _) = compile_module("dummy").unwrap(); let data = vec![]; - let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) - .update(|tx| { - tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100); - log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price); - }); + let (_, extra, tx) = + UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone()) + .mutate_estimate_and_check(Box::new(|tx| { + tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100); + log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price); + })) + .unwrap(); - let tx = &builder.tx; let expected_tip = tx.gas_price.unwrap() * tx.gas.unwrap() - U256::from(GAS_PRICE) * tx.gas.unwrap(); - let (_, extra) = builder.check().unwrap(); assert_eq!(U256::from(extra.1.tip()), expected_tip); } } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index b9a39e7ce4d3..04bce264a188 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -41,7 +41,10 @@ pub mod test_utils; pub mod weights; use crate::{ - evm::{runtime::GAS_PRICE, GenericTransaction}, + evm::{ + runtime::{gas_from_fee, GAS_PRICE}, + GasEncoder, GenericTransaction, + }, exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, @@ -295,6 +298,11 @@ pub mod pallet { /// The ratio between the decimal representation of the native token and the ETH token. #[pallet::constant] type NativeToEthRatio: Get; + + /// Encode and decode Ethereum gas values. + /// Only valid value is `()`. See [`GasEncoder`]. + #[pallet::no_default_bounds] + type EthGasEncoder: GasEncoder>; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -368,6 +376,7 @@ pub mod pallet { type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; type ChainId = ConstU64<0>; type NativeToEthRatio = ConstU32<1>; + type EthGasEncoder = (); } } @@ -560,6 +569,8 @@ pub mod pallet { AccountUnmapped, /// Tried to map an account that is already mapped. AccountAlreadyMapped, + /// The transaction used to dry-run a contract is invalid. + InvalidGenericTransaction, } /// A reason for the pallet contracts placing a hold on funds. @@ -761,12 +772,7 @@ pub mod pallet { #[allow(unused_variables)] #[pallet::call_index(0)] #[pallet::weight(Weight::MAX)] - pub fn eth_transact( - origin: OriginFor, - payload: Vec, - gas_limit: Weight, - #[pallet::compact] storage_deposit_limit: BalanceOf, - ) -> DispatchResultWithPostInfo { + pub fn eth_transact(origin: OriginFor, payload: Vec) -> DispatchResultWithPostInfo { Err(frame_system::Error::CallFiltered::.into()) } @@ -1406,11 +1412,8 @@ where return Err(EthTransactError::Message("Invalid transaction".into())); }; - let eth_dispatch_call = crate::Call::::eth_transact { - payload: unsigned_tx.dummy_signed_payload(), - gas_limit: result.gas_required, - storage_deposit_limit: result.storage_deposit, - }; + let eth_dispatch_call = + crate::Call::::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; let encoded_len = utx_encoded_size(eth_dispatch_call); let fee = pallet_transaction_payment::Pallet::::compute_fee( encoded_len, @@ -1418,7 +1421,9 @@ where 0u32.into(), ) .into(); - let eth_gas: U256 = (fee / GAS_PRICE.into()).into(); + let eth_gas = gas_from_fee(fee); + let eth_gas = + T::EthGasEncoder::encode(eth_gas, result.gas_required, result.storage_deposit); if eth_gas == result.eth_gas { log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); From f0eec07f93759331e6520ccc67f3d3291f0122c4 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:38:52 +0200 Subject: [PATCH 249/340] Increase the number of pvf execute workers (#7116) Reference hardware requirements have been bumped to at least 8 cores so we can no allocate 50% of that capacity to PVF execution. --------- Signed-off-by: Alexandru Gheorghe --- polkadot/node/service/src/lib.rs | 11 +++-------- prdoc/pr_7116.prdoc | 8 ++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 prdoc/pr_7116.prdoc diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs index 227bc5253994..820cce8d083a 100644 --- a/polkadot/node/service/src/lib.rs +++ b/polkadot/node/service/src/lib.rs @@ -944,14 +944,9 @@ pub fn new_full< secure_validator_mode, prep_worker_path, exec_worker_path, - pvf_execute_workers_max_num: execute_workers_max_num.unwrap_or_else( - || match config.chain_spec.identify_chain() { - // The intention is to use this logic for gradual increasing from 2 to 4 - // of this configuration chain by chain until it reaches production chain. - Chain::Polkadot | Chain::Kusama => 2, - Chain::Rococo | Chain::Westend | Chain::Unknown => 4, - }, - ), + // Default execution workers is 4 because we have 8 cores on the reference hardware, + // and this accounts for 50% of that cpu capacity. + pvf_execute_workers_max_num: execute_workers_max_num.unwrap_or(4), pvf_prepare_workers_soft_max_num: prepare_workers_soft_max_num.unwrap_or(1), pvf_prepare_workers_hard_max_num: prepare_workers_hard_max_num.unwrap_or(2), }) diff --git a/prdoc/pr_7116.prdoc b/prdoc/pr_7116.prdoc new file mode 100644 index 000000000000..95a5254778a4 --- /dev/null +++ b/prdoc/pr_7116.prdoc @@ -0,0 +1,8 @@ +title: Increase the number of pvf execution workers from 2 to 4 +doc: +- audience: Node Dev + description: |- + Increase the number of pvf execution workers from 2 to 4. +crates: +- name: polkadot-service + bump: patch From 0e0fa4782e2872ea74d8038ebedb9f6e6be53457 Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Mon, 13 Jan 2025 18:42:22 +0100 Subject: [PATCH 250/340] `fatxpool`: rotator cache size now depends on pool's limits (#7102) # Description This PR modifies the hard-coded size of extrinsics cache within [`PoolRotator`](https://github.com/paritytech/polkadot-sdk/blob/cdf107de700388a52a17b2fb852c98420c78278e/substrate/client/transaction-pool/src/graph/rotator.rs#L36-L45) to be inline with pool limits. The problem was, that due to small size (comparing to number of txs in single block) of hard coded size: https://github.com/paritytech/polkadot-sdk/blob/cdf107de700388a52a17b2fb852c98420c78278e/substrate/client/transaction-pool/src/graph/rotator.rs#L34 excessive number of unnecessary verification were performed in `prune_tags`: https://github.com/paritytech/polkadot-sdk/blob/cdf107de700388a52a17b2fb852c98420c78278e/substrate/client/transaction-pool/src/graph/pool.rs#L369-L370 This was resulting in quite long durations of `prune_tags` execution time (which was ok for 6s, but becomes noticable for 2s blocks): ``` Pruning at HashAndNumber { number: 83, ... }. Resubmitting transactions: 6142, reverification took: 237.818955ms Pruning at HashAndNumber { number: 84, ... }. Resubmitting transactions: 5985, reverification took: 222.118218ms Pruning at HashAndNumber { number: 85, ... }. Resubmitting transactions: 5981, reverification took: 215.546847ms ``` The fix reduces the overhead: ``` Pruning at HashAndNumber { number: 92, ... }. Resubmitting transactions: 6325, reverification took: 14.728354ms Pruning at HashAndNumber { number: 93, ... }. Resubmitting transactions: 7030, reverification took: 23.973607ms Pruning at HashAndNumber { number: 94, ... }. Resubmitting transactions: 4465, reverification took: 9.532472ms ``` ## Review Notes I decided to leave the hardocded `EXPECTED_SIZE` for the legacy transaction pool. Removing verification of transactions during re-submission may negatively impact the behavior of the legacy (single-state) pool. As in long-term we probably want to deprecate old pool, I did not invest time to assess the impact of rotator change in behavior of the legacy pool. --------- Co-authored-by: command-bot <> Co-authored-by: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> --- prdoc/pr_7102.prdoc | 8 +++ .../client/transaction-pool/benches/basics.rs | 12 ++++- .../transaction-pool/src/common/tests.rs | 2 +- .../src/fork_aware_txpool/dropped_watcher.rs | 4 +- .../fork_aware_txpool/fork_aware_txpool.rs | 2 +- .../client/transaction-pool/src/graph/pool.rs | 49 ++++++++++++++----- .../transaction-pool/src/graph/rotator.rs | 42 ++++++++++++---- .../src/graph/validated_pool.rs | 31 ++++++++++-- .../src/single_state_txpool/revalidation.rs | 12 ++++- .../single_state_txpool.rs | 12 ++++- .../client/transaction-pool/tests/fatp.rs | 4 +- .../client/transaction-pool/tests/pool.rs | 4 +- 12 files changed, 144 insertions(+), 38 deletions(-) create mode 100644 prdoc/pr_7102.prdoc diff --git a/prdoc/pr_7102.prdoc b/prdoc/pr_7102.prdoc new file mode 100644 index 000000000000..b1923aafc3db --- /dev/null +++ b/prdoc/pr_7102.prdoc @@ -0,0 +1,8 @@ +title: '`fatxpool`: rotator cache size now depends on pool''s limits' +doc: +- audience: Node Dev + description: |- + This PR modifies the hard-coded size of extrinsics cache within `PoolRotator` to be inline with pool limits. It only applies to fork-aware transaction pool. For the legacy (single-state) transaction pool the logic remains untouched. +crates: +- name: sc-transaction-pool + bump: minor diff --git a/substrate/client/transaction-pool/benches/basics.rs b/substrate/client/transaction-pool/benches/basics.rs index 5e40b0fb72d6..5ba9dd40c156 100644 --- a/substrate/client/transaction-pool/benches/basics.rs +++ b/substrate/client/transaction-pool/benches/basics.rs @@ -197,14 +197,22 @@ fn benchmark_main(c: &mut Criterion) { c.bench_function("sequential 50 tx", |b| { b.iter(|| { let api = Arc::from(TestApi::new_dependant()); - bench_configured(Pool::new(Default::default(), true.into(), api.clone()), 50, api); + bench_configured( + Pool::new_with_staticly_sized_rotator(Default::default(), true.into(), api.clone()), + 50, + api, + ); }); }); c.bench_function("random 100 tx", |b| { b.iter(|| { let api = Arc::from(TestApi::default()); - bench_configured(Pool::new(Default::default(), true.into(), api.clone()), 100, api); + bench_configured( + Pool::new_with_staticly_sized_rotator(Default::default(), true.into(), api.clone()), + 100, + api, + ); }); }); } diff --git a/substrate/client/transaction-pool/src/common/tests.rs b/substrate/client/transaction-pool/src/common/tests.rs index b00cf5fbfede..7f2cbe24d8ef 100644 --- a/substrate/client/transaction-pool/src/common/tests.rs +++ b/substrate/client/transaction-pool/src/common/tests.rs @@ -222,5 +222,5 @@ pub(crate) fn uxt(transfer: Transfer) -> Extrinsic { pub(crate) fn pool() -> (Pool, Arc) { let api = Arc::new(TestApi::default()); - (Pool::new(Default::default(), true.into(), api.clone()), api) + (Pool::new_with_staticly_sized_rotator(Default::default(), true.into(), api.clone()), api) } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs index 7679e3b169d2..d69aa37c94a1 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs @@ -329,14 +329,14 @@ where let stream_map = futures::stream::unfold(ctx, |mut ctx| async move { loop { if let Some(dropped) = ctx.get_pending_dropped_transaction() { - debug!("dropped_watcher: sending out (pending): {dropped:?}"); + trace!("dropped_watcher: sending out (pending): {dropped:?}"); return Some((dropped, ctx)); } tokio::select! { biased; Some(event) = next_event(&mut ctx.stream_map) => { if let Some(dropped) = ctx.handle_event(event.0, event.1) { - debug!("dropped_watcher: sending out: {dropped:?}"); + trace!("dropped_watcher: sending out: {dropped:?}"); return Some((dropped, ctx)); } }, diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs index 4ec87f1fefa4..e57256943ccf 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs @@ -318,7 +318,7 @@ where pool_api.clone(), listener.clone(), metrics.clone(), - TXMEMPOOL_TRANSACTION_LIMIT_MULTIPLIER * (options.ready.count + options.future.count), + TXMEMPOOL_TRANSACTION_LIMIT_MULTIPLIER * options.total_count(), options.ready.total_bytes + options.future.total_bytes, )); diff --git a/substrate/client/transaction-pool/src/graph/pool.rs b/substrate/client/transaction-pool/src/graph/pool.rs index ff9cc1541af4..4c0ace0b1c73 100644 --- a/substrate/client/transaction-pool/src/graph/pool.rs +++ b/substrate/client/transaction-pool/src/graph/pool.rs @@ -158,6 +158,13 @@ impl Default for Options { } } +impl Options { + /// Total (ready+future) maximal number of transactions in the pool. + pub fn total_count(&self) -> usize { + self.ready.count + self.future.count + } +} + /// Should we check that the transaction is banned /// in the pool, before we verify it? #[derive(Copy, Clone)] @@ -172,6 +179,21 @@ pub struct Pool { } impl Pool { + /// Create a new transaction pool with statically sized rotator. + pub fn new_with_staticly_sized_rotator( + options: Options, + is_validator: IsValidator, + api: Arc, + ) -> Self { + Self { + validated_pool: Arc::new(ValidatedPool::new_with_staticly_sized_rotator( + options, + is_validator, + api, + )), + } + } + /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { Self { validated_pool: Arc::new(ValidatedPool::new(options, is_validator, api)) } @@ -284,6 +306,7 @@ impl Pool { let mut validated_counter: usize = 0; let mut future_tags = Vec::new(); + let now = Instant::now(); for (extrinsic, in_pool_tags) in all { match in_pool_tags { // reuse the tags for extrinsics that were found in the pool @@ -319,7 +342,7 @@ impl Pool { } } - log::trace!(target: LOG_TARGET,"prune: validated_counter:{validated_counter}"); + log::debug!(target: LOG_TARGET,"prune: validated_counter:{validated_counter}, took:{:?}", now.elapsed()); self.prune_tags(at, future_tags, in_pool_hashes).await } @@ -351,6 +374,7 @@ impl Pool { tags: impl IntoIterator, known_imported_hashes: impl IntoIterator> + Clone, ) { + let now = Instant::now(); log::trace!(target: LOG_TARGET, "Pruning at {:?}", at); // Prune all transactions that provide given tags let prune_status = self.validated_pool.prune_tags(tags); @@ -369,9 +393,8 @@ impl Pool { let reverified_transactions = self.verify(at, pruned_transactions, CheckBannedBeforeVerify::Yes).await; - let pruned_hashes = reverified_transactions.keys().map(Clone::clone).collect(); - - log::trace!(target: LOG_TARGET, "Pruning at {:?}. Resubmitting transactions: {}", &at, reverified_transactions.len()); + let pruned_hashes = reverified_transactions.keys().map(Clone::clone).collect::>(); + log::debug!(target: LOG_TARGET, "Pruning at {:?}. Resubmitting transactions: {}, reverification took: {:?}", &at, reverified_transactions.len(), now.elapsed()); log_xt_trace!(data: tuple, target: LOG_TARGET, &reverified_transactions, "[{:?}] Resubmitting transaction: {:?}"); // And finally - submit reverified transactions back to the pool @@ -580,7 +603,7 @@ mod tests { fn should_reject_unactionable_transactions() { // given let api = Arc::new(TestApi::default()); - let pool = Pool::new( + let pool = Pool::new_with_staticly_sized_rotator( Default::default(), // the node does not author blocks false.into(), @@ -767,7 +790,7 @@ mod tests { let options = Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; let api = Arc::new(TestApi::default()); - let pool = Pool::new(options, true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(options, true.into(), api.clone()); let hash1 = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt.into())).unwrap(); @@ -803,7 +826,7 @@ mod tests { let options = Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; let api = Arc::new(TestApi::default()); - let pool = Pool::new(options, true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(options, true.into(), api.clone()); // when block_on( @@ -1036,7 +1059,7 @@ mod tests { Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; let api = Arc::new(TestApi::default()); - let pool = Pool::new(options, true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(options, true.into(), api.clone()); let xt = uxt(Transfer { from: Alice.into(), @@ -1074,7 +1097,7 @@ mod tests { Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; let api = Arc::new(TestApi::default()); - let pool = Pool::new(options, true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(options, true.into(), api.clone()); // after validation `IncludeData` will have priority set to 9001 // (validate_transaction mock) @@ -1106,7 +1129,7 @@ mod tests { Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; let api = Arc::new(TestApi::default()); - let pool = Pool::new(options, true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(options, true.into(), api.clone()); let han_of_block0 = api.expect_hash_and_number(0); @@ -1151,7 +1174,11 @@ mod tests { let mut api = TestApi::default(); api.delay = Arc::new(Mutex::new(rx.into())); let api = Arc::new(api); - let pool = Arc::new(Pool::new(Default::default(), true.into(), api.clone())); + let pool = Arc::new(Pool::new_with_staticly_sized_rotator( + Default::default(), + true.into(), + api.clone(), + )); let han_of_block0 = api.expect_hash_and_number(0); diff --git a/substrate/client/transaction-pool/src/graph/rotator.rs b/substrate/client/transaction-pool/src/graph/rotator.rs index 9a2e269b5eed..80d8f24144c8 100644 --- a/substrate/client/transaction-pool/src/graph/rotator.rs +++ b/substrate/client/transaction-pool/src/graph/rotator.rs @@ -31,7 +31,10 @@ use std::{ use super::base_pool::Transaction; /// Expected size of the banned extrinsics cache. -const EXPECTED_SIZE: usize = 2048; +const DEFAULT_EXPECTED_SIZE: usize = 2048; + +/// The default duration, in seconds, for which an extrinsic is banned. +const DEFAULT_BAN_TIME_SECS: u64 = 30 * 60; /// Pool rotator is responsible to only keep fresh extrinsics in the pool. /// @@ -42,18 +45,39 @@ pub struct PoolRotator { ban_time: Duration, /// Currently banned extrinsics. banned_until: RwLock>, + /// Expected size of the banned extrinsics cache. + expected_size: usize, +} + +impl Clone for PoolRotator { + fn clone(&self) -> Self { + Self { + ban_time: self.ban_time, + banned_until: RwLock::new(self.banned_until.read().clone()), + expected_size: self.expected_size, + } + } } impl Default for PoolRotator { fn default() -> Self { - Self { ban_time: Duration::from_secs(60 * 30), banned_until: Default::default() } + Self { + ban_time: Duration::from_secs(DEFAULT_BAN_TIME_SECS), + banned_until: Default::default(), + expected_size: DEFAULT_EXPECTED_SIZE, + } } } impl PoolRotator { /// New rotator instance with specified ban time. pub fn new(ban_time: Duration) -> Self { - Self { ban_time, banned_until: Default::default() } + Self { ban_time, ..Self::default() } + } + + /// New rotator instance with specified ban time and expected cache size. + pub fn new_with_expected_size(ban_time: Duration, expected_size: usize) -> Self { + Self { expected_size, ..Self::new(ban_time) } } /// Returns `true` if extrinsic hash is currently banned. @@ -69,8 +93,8 @@ impl PoolRotator { banned.insert(hash, *now + self.ban_time); } - if banned.len() > 2 * EXPECTED_SIZE { - while banned.len() > EXPECTED_SIZE { + if banned.len() > 2 * self.expected_size { + while banned.len() > self.expected_size { if let Some(key) = banned.keys().next().cloned() { banned.remove(&key); } @@ -201,16 +225,16 @@ mod tests { let past_block = 0; // when - for i in 0..2 * EXPECTED_SIZE { + for i in 0..2 * DEFAULT_EXPECTED_SIZE { let tx = tx_with(i as u64, past_block); assert!(rotator.ban_if_stale(&now, past_block, &tx)); } - assert_eq!(rotator.banned_until.read().len(), 2 * EXPECTED_SIZE); + assert_eq!(rotator.banned_until.read().len(), 2 * DEFAULT_EXPECTED_SIZE); // then - let tx = tx_with(2 * EXPECTED_SIZE as u64, past_block); + let tx = tx_with(2 * DEFAULT_EXPECTED_SIZE as u64, past_block); // trigger a garbage collection assert!(rotator.ban_if_stale(&now, past_block, &tx)); - assert_eq!(rotator.banned_until.read().len(), EXPECTED_SIZE); + assert_eq!(rotator.banned_until.read().len(), DEFAULT_EXPECTED_SIZE); } } diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index 14df63d9673e..3f7bf4773de7 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -121,16 +121,41 @@ impl Clone for ValidatedPool { listener: Default::default(), pool: RwLock::from(self.pool.read().clone()), import_notification_sinks: Default::default(), - rotator: PoolRotator::default(), + rotator: self.rotator.clone(), } } } impl ValidatedPool { + /// Create a new transaction pool with statically sized rotator. + pub fn new_with_staticly_sized_rotator( + options: Options, + is_validator: IsValidator, + api: Arc, + ) -> Self { + let ban_time = options.ban_time; + Self::new_with_rotator(options, is_validator, api, PoolRotator::new(ban_time)) + } + /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { - let base_pool = base::BasePool::new(options.reject_future_transactions); let ban_time = options.ban_time; + let total_count = options.total_count(); + Self::new_with_rotator( + options, + is_validator, + api, + PoolRotator::new_with_expected_size(ban_time, total_count), + ) + } + + fn new_with_rotator( + options: Options, + is_validator: IsValidator, + api: Arc, + rotator: PoolRotator>, + ) -> Self { + let base_pool = base::BasePool::new(options.reject_future_transactions); Self { is_validator, options, @@ -138,7 +163,7 @@ impl ValidatedPool { api, pool: RwLock::new(base_pool), import_notification_sinks: Default::default(), - rotator: PoolRotator::new(ban_time), + rotator, } } diff --git a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs index f22fa2ddabde..caa09585b28b 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs @@ -384,7 +384,11 @@ mod tests { #[test] fn revalidation_queue_works() { let api = Arc::new(TestApi::default()); - let pool = Arc::new(Pool::new(Default::default(), true.into(), api.clone())); + let pool = Arc::new(Pool::new_with_staticly_sized_rotator( + Default::default(), + true.into(), + api.clone(), + )); let queue = Arc::new(RevalidationQueue::new(api.clone(), pool.clone())); let uxt = uxt(Transfer { @@ -414,7 +418,11 @@ mod tests { #[test] fn revalidation_queue_skips_revalidation_for_unknown_block_hash() { let api = Arc::new(TestApi::default()); - let pool = Arc::new(Pool::new(Default::default(), true.into(), api.clone())); + let pool = Arc::new(Pool::new_with_staticly_sized_rotator( + Default::default(), + true.into(), + api.clone(), + )); let queue = Arc::new(RevalidationQueue::new(api.clone(), pool.clone())); let uxt0 = uxt(Transfer { diff --git a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs index e7504012ca67..2b32704945c7 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs @@ -141,7 +141,11 @@ where finalized_hash: Block::Hash, options: graph::Options, ) -> (Self, Pin + Send>>) { - let pool = Arc::new(graph::Pool::new(options, true.into(), pool_api.clone())); + let pool = Arc::new(graph::Pool::new_with_staticly_sized_rotator( + options, + true.into(), + pool_api.clone(), + )); let (revalidation_queue, background_task) = revalidation::RevalidationQueue::new_background( pool_api.clone(), pool.clone(), @@ -177,7 +181,11 @@ where best_block_hash: Block::Hash, finalized_hash: Block::Hash, ) -> Self { - let pool = Arc::new(graph::Pool::new(options, is_validator, pool_api.clone())); + let pool = Arc::new(graph::Pool::new_with_staticly_sized_rotator( + options, + is_validator, + pool_api.clone(), + )); let (revalidation_queue, background_task) = match revalidation_type { RevalidationType::Light => (revalidation::RevalidationQueue::new(pool_api.clone(), pool.clone()), None), diff --git a/substrate/client/transaction-pool/tests/fatp.rs b/substrate/client/transaction-pool/tests/fatp.rs index 8bf08122995c..dd82c52a6047 100644 --- a/substrate/client/transaction-pool/tests/fatp.rs +++ b/substrate/client/transaction-pool/tests/fatp.rs @@ -2199,7 +2199,7 @@ fn import_sink_works3() { pool.submit_one(genesis, SOURCE, xt1.clone()), ]; - let x = block_on(futures::future::join_all(submissions)); + block_on(futures::future::join_all(submissions)); let header01a = api.push_block(1, vec![], true); let header01b = api.push_block(1, vec![], true); @@ -2213,8 +2213,6 @@ fn import_sink_works3() { assert_pool_status!(header01a.hash(), &pool, 1, 1); assert_pool_status!(header01b.hash(), &pool, 1, 1); - log::debug!("xxx {x:#?}"); - let import_events = futures::executor::block_on_stream(import_stream).take(1).collect::>(); diff --git a/substrate/client/transaction-pool/tests/pool.rs b/substrate/client/transaction-pool/tests/pool.rs index 20997606c607..de35726435f0 100644 --- a/substrate/client/transaction-pool/tests/pool.rs +++ b/substrate/client/transaction-pool/tests/pool.rs @@ -49,7 +49,7 @@ const LOG_TARGET: &str = "txpool"; fn pool() -> (Pool, Arc) { let api = Arc::new(TestApi::with_alice_nonce(209)); - (Pool::new(Default::default(), true.into(), api.clone()), api) + (Pool::new_with_staticly_sized_rotator(Default::default(), true.into(), api.clone()), api) } fn maintained_pool() -> (BasicPool, Arc, futures::executor::ThreadPool) { @@ -224,7 +224,7 @@ fn should_correctly_prune_transactions_providing_more_than_one_tag() { api.set_valid_modifier(Box::new(|v: &mut ValidTransaction| { v.provides.push(vec![155]); })); - let pool = Pool::new(Default::default(), true.into(), api.clone()); + let pool = Pool::new_with_staticly_sized_rotator(Default::default(), true.into(), api.clone()); let xt0 = Arc::from(uxt(Alice, 209)); block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, xt0.clone())) .expect("1. Imported"); From cccefdd965c39498825f34e105979c447b315359 Mon Sep 17 00:00:00 2001 From: "polka.dom" Date: Mon, 13 Jan 2025 16:22:32 -0500 Subject: [PATCH 251/340] Remove usage of the pallet::getter macro from pallet-grandpa (#4529) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per #3326, removes pallet::getter macro usage from pallet-grandpa. The syntax `StorageItem::::get()` should be used instead. cc @muraca --------- Co-authored-by: Bastian Köcher --- polkadot/runtime/rococo/src/lib.rs | 2 +- polkadot/runtime/test-runtime/src/lib.rs | 2 +- polkadot/runtime/westend/src/lib.rs | 2 +- prdoc/pr_4529.prdoc | 22 ++++ substrate/bin/node/runtime/src/lib.rs | 2 +- substrate/frame/grandpa/src/benchmarking.rs | 4 +- substrate/frame/grandpa/src/equivocation.rs | 2 +- substrate/frame/grandpa/src/lib.rs | 106 +++++++++++++------- substrate/frame/grandpa/src/tests.rs | 89 ++++++++-------- 9 files changed, 144 insertions(+), 87 deletions(-) create mode 100644 prdoc/pr_4529.prdoc diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index cab4394eb5a8..e5d703700fee 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -2276,7 +2276,7 @@ sp_api::impl_runtime_apis! { } fn current_set_id() -> fg_primitives::SetId { - Grandpa::current_set_id() + pallet_grandpa::CurrentSetId::::get() } fn submit_report_equivocation_unsigned_extrinsic( diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 82564d5c278c..4f9ba8d8508c 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -1186,7 +1186,7 @@ sp_api::impl_runtime_apis! { } fn current_set_id() -> fg_primitives::SetId { - Grandpa::current_set_id() + pallet_grandpa::CurrentSetId::::get() } fn submit_report_equivocation_unsigned_extrinsic( diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 166f3fc42eef..9d77a5e5eea1 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -2300,7 +2300,7 @@ sp_api::impl_runtime_apis! { } fn current_set_id() -> fg_primitives::SetId { - Grandpa::current_set_id() + pallet_grandpa::CurrentSetId::::get() } fn submit_report_equivocation_unsigned_extrinsic( diff --git a/prdoc/pr_4529.prdoc b/prdoc/pr_4529.prdoc new file mode 100644 index 000000000000..32beea17ad6b --- /dev/null +++ b/prdoc/pr_4529.prdoc @@ -0,0 +1,22 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Removed `pallet::getter` usage from pallet-grandpa + +doc: + - audience: Runtime Dev + description: | + This PR removed the `pallet::getter`s from `pallet-grandpa`. + The syntax `StorageItem::::get()` should be used instead + +crates: + - name: pallet-grandpa + bump: minor + - name: kitchensink-runtime + bump: none + - name: westend-runtime + bump: none + - name: polkadot-test-runtime + bump: none + - name: rococo-runtime + bump: none diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 7de04b27ff83..e11a009c1c3f 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -2979,7 +2979,7 @@ impl_runtime_apis! { } fn current_set_id() -> sp_consensus_grandpa::SetId { - Grandpa::current_set_id() + pallet_grandpa::CurrentSetId::::get() } fn submit_report_equivocation_unsigned_extrinsic( diff --git a/substrate/frame/grandpa/src/benchmarking.rs b/substrate/frame/grandpa/src/benchmarking.rs index 0a10e5882776..56048efa22ca 100644 --- a/substrate/frame/grandpa/src/benchmarking.rs +++ b/substrate/frame/grandpa/src/benchmarking.rs @@ -17,7 +17,7 @@ //! Benchmarks for the GRANDPA pallet. -use super::{Pallet as Grandpa, *}; +use super::*; use frame_benchmarking::v2::*; use frame_system::RawOrigin; use sp_core::H256; @@ -69,7 +69,7 @@ mod benchmarks { #[extrinsic_call] _(RawOrigin::Root, delay, best_finalized_block_number); - assert!(Grandpa::::stalled().is_some()); + assert!(Stalled::::get().is_some()); } impl_benchmark_test_suite!( diff --git a/substrate/frame/grandpa/src/equivocation.rs b/substrate/frame/grandpa/src/equivocation.rs index 2366c957e9ab..4ebdbc1eecd3 100644 --- a/substrate/frame/grandpa/src/equivocation.rs +++ b/substrate/frame/grandpa/src/equivocation.rs @@ -177,7 +177,7 @@ where evidence: (EquivocationProof>, T::KeyOwnerProof), ) -> Result<(), DispatchError> { let (equivocation_proof, key_owner_proof) = evidence; - let reporter = reporter.or_else(|| >::author()); + let reporter = reporter.or_else(|| pallet_authorship::Pallet::::author()); let offender = equivocation_proof.offender().clone(); // We check the equivocation within the context of its set id (and diff --git a/substrate/frame/grandpa/src/lib.rs b/substrate/frame/grandpa/src/lib.rs index 4f69aeaef523..9017eec2ca8f 100644 --- a/substrate/frame/grandpa/src/lib.rs +++ b/substrate/frame/grandpa/src/lib.rs @@ -127,7 +127,7 @@ pub mod pallet { impl Hooks> for Pallet { fn on_finalize(block_number: BlockNumberFor) { // check for scheduled pending authority set changes - if let Some(pending_change) = >::get() { + if let Some(pending_change) = PendingChange::::get() { // emit signal if we're at the block that scheduled the change if block_number == pending_change.scheduled_at { let next_authorities = pending_change.next_authorities.to_vec(); @@ -150,12 +150,12 @@ pub mod pallet { Self::deposit_event(Event::NewAuthorities { authority_set: pending_change.next_authorities.into_inner(), }); - >::kill(); + PendingChange::::kill(); } } // check for scheduled pending state changes - match >::get() { + match State::::get() { StoredState::PendingPause { scheduled_at, delay } => { // signal change to pause if block_number == scheduled_at { @@ -164,7 +164,7 @@ pub mod pallet { // enact change to paused state if block_number == scheduled_at + delay { - >::put(StoredState::Paused); + State::::put(StoredState::Paused); Self::deposit_event(Event::Paused); } }, @@ -176,7 +176,7 @@ pub mod pallet { // enact change to live state if block_number == scheduled_at + delay { - >::put(StoredState::Live); + State::::put(StoredState::Live); Self::deposit_event(Event::Resumed); } }, @@ -297,37 +297,32 @@ pub mod pallet { } #[pallet::type_value] - pub(super) fn DefaultForState() -> StoredState> { + pub fn DefaultForState() -> StoredState> { StoredState::Live } /// State of the current authority set. #[pallet::storage] - #[pallet::getter(fn state)] - pub(super) type State = + pub type State = StorageValue<_, StoredState>, ValueQuery, DefaultForState>; /// Pending change: (signaled at, scheduled change). #[pallet::storage] - #[pallet::getter(fn pending_change)] - pub(super) type PendingChange = + pub type PendingChange = StorageValue<_, StoredPendingChange, T::MaxAuthorities>>; /// next block number where we can force a change. #[pallet::storage] - #[pallet::getter(fn next_forced)] - pub(super) type NextForced = StorageValue<_, BlockNumberFor>; + pub type NextForced = StorageValue<_, BlockNumberFor>; /// `true` if we are currently stalled. #[pallet::storage] - #[pallet::getter(fn stalled)] - pub(super) type Stalled = StorageValue<_, (BlockNumberFor, BlockNumberFor)>; + pub type Stalled = StorageValue<_, (BlockNumberFor, BlockNumberFor)>; /// The number of changes (both in terms of keys and underlying economic responsibilities) /// in the "set" of Grandpa validators from genesis. #[pallet::storage] - #[pallet::getter(fn current_set_id)] - pub(super) type CurrentSetId = StorageValue<_, SetId, ValueQuery>; + pub type CurrentSetId = StorageValue<_, SetId, ValueQuery>; /// A mapping from grandpa set ID to the index of the *most recent* session for which its /// members were responsible. @@ -340,12 +335,11 @@ pub mod pallet { /// /// TWOX-NOTE: `SetId` is not under user control. #[pallet::storage] - #[pallet::getter(fn session_for_set)] - pub(super) type SetIdSession = StorageMap<_, Twox64Concat, SetId, SessionIndex>; + pub type SetIdSession = StorageMap<_, Twox64Concat, SetId, SessionIndex>; /// The current list of authorities. #[pallet::storage] - pub(crate) type Authorities = + pub type Authorities = StorageValue<_, BoundedAuthorityList, ValueQuery>; #[derive(frame_support::DefaultNoBound)] @@ -432,6 +426,44 @@ pub enum StoredState { } impl Pallet { + /// State of the current authority set. + pub fn state() -> StoredState> { + State::::get() + } + + /// Pending change: (signaled at, scheduled change). + pub fn pending_change() -> Option, T::MaxAuthorities>> { + PendingChange::::get() + } + + /// next block number where we can force a change. + pub fn next_forced() -> Option> { + NextForced::::get() + } + + /// `true` if we are currently stalled. + pub fn stalled() -> Option<(BlockNumberFor, BlockNumberFor)> { + Stalled::::get() + } + + /// The number of changes (both in terms of keys and underlying economic responsibilities) + /// in the "set" of Grandpa validators from genesis. + pub fn current_set_id() -> SetId { + CurrentSetId::::get() + } + + /// A mapping from grandpa set ID to the index of the *most recent* session for which its + /// members were responsible. + /// + /// This is only used for validating equivocation proofs. An equivocation proof must + /// contains a key-ownership proof for a given session, therefore we need a way to tie + /// together sessions and GRANDPA set ids, i.e. we need to validate that a validator + /// was the owner of a given key on a given session, and what the active set ID was + /// during that session. + pub fn session_for_set(set_id: SetId) -> Option { + SetIdSession::::get(set_id) + } + /// Get the current set of authorities, along with their respective weights. pub fn grandpa_authorities() -> AuthorityList { Authorities::::get().into_inner() @@ -440,9 +472,9 @@ impl Pallet { /// Schedule GRANDPA to pause starting in the given number of blocks. /// Cannot be done when already paused. pub fn schedule_pause(in_blocks: BlockNumberFor) -> DispatchResult { - if let StoredState::Live = >::get() { - let scheduled_at = >::block_number(); - >::put(StoredState::PendingPause { delay: in_blocks, scheduled_at }); + if let StoredState::Live = State::::get() { + let scheduled_at = frame_system::Pallet::::block_number(); + State::::put(StoredState::PendingPause { delay: in_blocks, scheduled_at }); Ok(()) } else { @@ -452,9 +484,9 @@ impl Pallet { /// Schedule a resume of GRANDPA after pausing. pub fn schedule_resume(in_blocks: BlockNumberFor) -> DispatchResult { - if let StoredState::Paused = >::get() { - let scheduled_at = >::block_number(); - >::put(StoredState::PendingResume { delay: in_blocks, scheduled_at }); + if let StoredState::Paused = State::::get() { + let scheduled_at = frame_system::Pallet::::block_number(); + State::::put(StoredState::PendingResume { delay: in_blocks, scheduled_at }); Ok(()) } else { @@ -481,17 +513,17 @@ impl Pallet { in_blocks: BlockNumberFor, forced: Option>, ) -> DispatchResult { - if !>::exists() { - let scheduled_at = >::block_number(); + if !PendingChange::::exists() { + let scheduled_at = frame_system::Pallet::::block_number(); if forced.is_some() { - if Self::next_forced().map_or(false, |next| next > scheduled_at) { + if NextForced::::get().map_or(false, |next| next > scheduled_at) { return Err(Error::::TooSoon.into()) } // only allow the next forced change when twice the window has passed since // this one. - >::put(scheduled_at + in_blocks * 2u32.into()); + NextForced::::put(scheduled_at + in_blocks * 2u32.into()); } let next_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::force_from( @@ -502,7 +534,7 @@ impl Pallet { ), ); - >::put(StoredPendingChange { + PendingChange::::put(StoredPendingChange { delay: in_blocks, scheduled_at, next_authorities, @@ -518,7 +550,7 @@ impl Pallet { /// Deposit one of this module's logs. fn deposit_log(log: ConsensusLog>) { let log = DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode()); - >::deposit_log(log); + frame_system::Pallet::::deposit_log(log); } // Perform module initialization, abstracted so that it can be called either through genesis @@ -554,7 +586,7 @@ impl Pallet { // when we record old authority sets we could try to figure out _who_ // failed. until then, we can't meaningfully guard against // `next == last` the way that normal session changes do. - >::put((further_wait, median)); + Stalled::::put((further_wait, median)); } } @@ -583,10 +615,10 @@ where // Always issue a change if `session` says that the validators have changed. // Even if their session keys are the same as before, the underlying economic // identities have changed. - let current_set_id = if changed || >::exists() { + let current_set_id = if changed || Stalled::::exists() { let next_authorities = validators.map(|(_, k)| (k, 1)).collect::>(); - let res = if let Some((further_wait, median)) = >::take() { + let res = if let Some((further_wait, median)) = Stalled::::take() { Self::schedule_change(next_authorities, further_wait, Some(median)) } else { Self::schedule_change(next_authorities, Zero::zero(), None) @@ -608,17 +640,17 @@ where // either the session module signalled that the validators have changed // or the set was stalled. but since we didn't successfully schedule // an authority set change we do not increment the set id. - Self::current_set_id() + CurrentSetId::::get() } } else { // nothing's changed, neither economic conditions nor session keys. update the pointer // of the current set. - Self::current_set_id() + CurrentSetId::::get() }; // update the mapping to note that the current set corresponds to the // latest equivalent session (i.e. now). - let session_index = >::current_index(); + let session_index = pallet_session::Pallet::::current_index(); SetIdSession::::insert(current_set_id, &session_index); } diff --git a/substrate/frame/grandpa/src/tests.rs b/substrate/frame/grandpa/src/tests.rs index 383f77f00de7..f4720966b179 100644 --- a/substrate/frame/grandpa/src/tests.rs +++ b/substrate/frame/grandpa/src/tests.rs @@ -110,7 +110,7 @@ fn cannot_schedule_change_when_one_pending() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { initialize_block(1, Default::default()); Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 1, None).unwrap(); - assert!(>::exists()); + assert!(PendingChange::::exists()); assert_noop!( Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None), Error::::ChangePending @@ -120,7 +120,7 @@ fn cannot_schedule_change_when_one_pending() { let header = System::finalize(); initialize_block(2, header.hash()); - assert!(>::exists()); + assert!(PendingChange::::exists()); assert_noop!( Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None), Error::::ChangePending @@ -130,7 +130,7 @@ fn cannot_schedule_change_when_one_pending() { let header = System::finalize(); initialize_block(3, header.hash()); - assert!(!>::exists()); + assert!(!PendingChange::::exists()); assert_ok!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None)); Grandpa::on_finalize(3); @@ -144,7 +144,7 @@ fn dispatch_forced_change() { initialize_block(1, Default::default()); Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 5, Some(0)).unwrap(); - assert!(>::exists()); + assert!(PendingChange::::exists()); assert_noop!( Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, Some(0)), Error::::ChangePending @@ -155,8 +155,8 @@ fn dispatch_forced_change() { for i in 2..7 { initialize_block(i, header.hash()); - assert!(>::get().unwrap().forced.is_some()); - assert_eq!(Grandpa::next_forced(), Some(11)); + assert!(PendingChange::::get().unwrap().forced.is_some()); + assert_eq!(NextForced::::get(), Some(11)); assert_noop!( Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None), Error::::ChangePending @@ -174,7 +174,7 @@ fn dispatch_forced_change() { // add a normal change. { initialize_block(7, header.hash()); - assert!(!>::exists()); + assert!(!PendingChange::::exists()); assert_eq!( Grandpa::grandpa_authorities(), to_authorities(vec![(4, 1), (5, 1), (6, 1)]) @@ -187,7 +187,7 @@ fn dispatch_forced_change() { // run the normal change. { initialize_block(8, header.hash()); - assert!(>::exists()); + assert!(PendingChange::::exists()); assert_eq!( Grandpa::grandpa_authorities(), to_authorities(vec![(4, 1), (5, 1), (6, 1)]) @@ -204,9 +204,9 @@ fn dispatch_forced_change() { // time. for i in 9..11 { initialize_block(i, header.hash()); - assert!(!>::exists()); + assert!(!PendingChange::::exists()); assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(5, 1)])); - assert_eq!(Grandpa::next_forced(), Some(11)); + assert_eq!(NextForced::::get(), Some(11)); assert_noop!( Grandpa::schedule_change(to_authorities(vec![(5, 1), (6, 1)]), 5, Some(0)), Error::::TooSoon @@ -217,13 +217,13 @@ fn dispatch_forced_change() { { initialize_block(11, header.hash()); - assert!(!>::exists()); + assert!(!PendingChange::::exists()); assert_ok!(Grandpa::schedule_change( to_authorities(vec![(5, 1), (6, 1), (7, 1)]), 5, Some(0) )); - assert_eq!(Grandpa::next_forced(), Some(21)); + assert_eq!(NextForced::::get(), Some(21)); Grandpa::on_finalize(11); header = System::finalize(); } @@ -239,7 +239,10 @@ fn schedule_pause_only_when_live() { Grandpa::schedule_pause(1).unwrap(); // we've switched to the pending pause state - assert_eq!(Grandpa::state(), StoredState::PendingPause { scheduled_at: 1u64, delay: 1 }); + assert_eq!( + State::::get(), + StoredState::PendingPause { scheduled_at: 1u64, delay: 1 } + ); Grandpa::on_finalize(1); let _ = System::finalize(); @@ -253,7 +256,7 @@ fn schedule_pause_only_when_live() { let _ = System::finalize(); // after finalizing block 2 the set should have switched to paused state - assert_eq!(Grandpa::state(), StoredState::Paused); + assert_eq!(State::::get(), StoredState::Paused); }); } @@ -265,14 +268,14 @@ fn schedule_resume_only_when_paused() { // the set is currently live, resuming it is an error assert_noop!(Grandpa::schedule_resume(1), Error::::ResumeFailed); - assert_eq!(Grandpa::state(), StoredState::Live); + assert_eq!(State::::get(), StoredState::Live); // we schedule a pause to be applied instantly Grandpa::schedule_pause(0).unwrap(); Grandpa::on_finalize(1); let _ = System::finalize(); - assert_eq!(Grandpa::state(), StoredState::Paused); + assert_eq!(State::::get(), StoredState::Paused); // we schedule the set to go back live in 2 blocks initialize_block(2, Default::default()); @@ -289,7 +292,7 @@ fn schedule_resume_only_when_paused() { let _ = System::finalize(); // it should be live at block 4 - assert_eq!(Grandpa::state(), StoredState::Live); + assert_eq!(State::::get(), StoredState::Live); }); } @@ -342,7 +345,7 @@ fn report_equivocation_current_set_works() { let equivocation_key = &authorities[equivocation_authority_index].0; let equivocation_keyring = extract_keyring(equivocation_key); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::::get(); // generate an equivocation proof, with two votes in the same round for // different block hashes signed by the same key @@ -424,7 +427,7 @@ fn report_equivocation_old_set_works() { let equivocation_keyring = extract_keyring(equivocation_key); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::::get(); // generate an equivocation proof for the old set, let equivocation_proof = generate_equivocation_proof( @@ -487,7 +490,7 @@ fn report_equivocation_invalid_set_id() { let key_owner_proof = Historical::prove((sp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::::get(); // generate an equivocation for a future set let equivocation_proof = generate_equivocation_proof( @@ -527,7 +530,7 @@ fn report_equivocation_invalid_session() { start_era(2); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::::get(); // generate an equivocation proof at set id = 2 let equivocation_proof = generate_equivocation_proof( @@ -568,7 +571,7 @@ fn report_equivocation_invalid_key_owner_proof() { let equivocation_key = &authorities[equivocation_authority_index].0; let equivocation_keyring = extract_keyring(equivocation_key); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::::get(); // generate an equivocation proof for the authority at index 0 let equivocation_proof = generate_equivocation_proof( @@ -611,7 +614,7 @@ fn report_equivocation_invalid_equivocation_proof() { let key_owner_proof = Historical::prove((sp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::::get(); let assert_invalid_equivocation_proof = |equivocation_proof| { assert_err!( @@ -675,7 +678,7 @@ fn report_equivocation_validate_unsigned_prevents_duplicates() { let equivocation_authority_index = 0; let equivocation_key = &authorities[equivocation_authority_index].0; let equivocation_keyring = extract_keyring(equivocation_key); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::::get(); let equivocation_proof = generate_equivocation_proof( set_id, @@ -748,12 +751,12 @@ fn report_equivocation_validate_unsigned_prevents_duplicates() { #[test] fn on_new_session_doesnt_start_new_set_if_schedule_change_failed() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { - assert_eq!(Grandpa::current_set_id(), 0); + assert_eq!(CurrentSetId::::get(), 0); // starting a new era should lead to a change in the session // validators and trigger a new set start_era(1); - assert_eq!(Grandpa::current_set_id(), 1); + assert_eq!(CurrentSetId::::get(), 1); // we schedule a change delayed by 2 blocks, this should make it so that // when we try to rotate the session at the beginning of the era we will @@ -761,22 +764,22 @@ fn on_new_session_doesnt_start_new_set_if_schedule_change_failed() { // not increment the set id. Grandpa::schedule_change(to_authorities(vec![(1, 1)]), 2, None).unwrap(); start_era(2); - assert_eq!(Grandpa::current_set_id(), 1); + assert_eq!(CurrentSetId::::get(), 1); // everything should go back to normal after. start_era(3); - assert_eq!(Grandpa::current_set_id(), 2); + assert_eq!(CurrentSetId::::get(), 2); // session rotation might also fail to schedule a change if it's for a // forced change (i.e. grandpa is stalled) and it is too soon. - >::put(1000); - >::put((30, 1)); + NextForced::::put(1000); + Stalled::::put((30, 1)); // NOTE: we cannot go through normal era rotation since having `Stalled` // defined will also trigger a new set (regardless of whether the // session validators changed) Grandpa::on_new_session(true, std::iter::empty(), std::iter::empty()); - assert_eq!(Grandpa::current_set_id(), 2); + assert_eq!(CurrentSetId::::get(), 2); }); } @@ -790,19 +793,19 @@ fn cleans_up_old_set_id_session_mappings() { // we should have a session id mapping for all the set ids from // `max_set_id_session_entries` eras we have observed for i in 1..=max_set_id_session_entries { - assert!(Grandpa::session_for_set(i as u64).is_some()); + assert!(SetIdSession::::get(i as u64).is_some()); } start_era(max_set_id_session_entries * 2); // we should keep tracking the new mappings for new eras for i in max_set_id_session_entries + 1..=max_set_id_session_entries * 2 { - assert!(Grandpa::session_for_set(i as u64).is_some()); + assert!(SetIdSession::::get(i as u64).is_some()); } // but the old ones should have been pruned by now for i in 1..=max_set_id_session_entries { - assert!(Grandpa::session_for_set(i as u64).is_none()); + assert!(SetIdSession::::get(i as u64).is_none()); } }); } @@ -812,24 +815,24 @@ fn always_schedules_a_change_on_new_session_when_stalled() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { start_era(1); - assert!(Grandpa::pending_change().is_none()); - assert_eq!(Grandpa::current_set_id(), 1); + assert!(PendingChange::::get().is_none()); + assert_eq!(CurrentSetId::::get(), 1); // if the session handler reports no change then we should not schedule // any pending change Grandpa::on_new_session(false, std::iter::empty(), std::iter::empty()); - assert!(Grandpa::pending_change().is_none()); - assert_eq!(Grandpa::current_set_id(), 1); + assert!(PendingChange::::get().is_none()); + assert_eq!(CurrentSetId::::get(), 1); // if grandpa is stalled then we should **always** schedule a forced // change on a new session - >::put((10, 1)); + Stalled::::put((10, 1)); Grandpa::on_new_session(false, std::iter::empty(), std::iter::empty()); - assert!(Grandpa::pending_change().is_some()); - assert!(Grandpa::pending_change().unwrap().forced.is_some()); - assert_eq!(Grandpa::current_set_id(), 2); + assert!(PendingChange::::get().is_some()); + assert!(PendingChange::::get().unwrap().forced.is_some()); + assert_eq!(CurrentSetId::::get(), 2); }); } @@ -861,7 +864,7 @@ fn valid_equivocation_reports_dont_pay_fees() { let equivocation_key = &Grandpa::grandpa_authorities()[0].0; let equivocation_keyring = extract_keyring(equivocation_key); - let set_id = Grandpa::current_set_id(); + let set_id = CurrentSetId::::get(); // generate an equivocation proof. let equivocation_proof = generate_equivocation_proof( From ddffa027d7b78af330a2d3d18b7dfdbd00e431f0 Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Tue, 14 Jan 2025 10:40:50 +0200 Subject: [PATCH 252/340] forbid v1 descriptors with UMP signals (#7127) --- .../node/core/candidate-validation/src/lib.rs | 15 ++-- .../core/candidate-validation/src/tests.rs | 71 +++++++++++++++++-- polkadot/primitives/src/vstaging/mod.rs | 30 ++++++-- prdoc/pr_7127.prdoc | 9 +++ 4 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 prdoc/pr_7127.prdoc diff --git a/polkadot/node/core/candidate-validation/src/lib.rs b/polkadot/node/core/candidate-validation/src/lib.rs index 25614349486e..2a4643031bf8 100644 --- a/polkadot/node/core/candidate-validation/src/lib.rs +++ b/polkadot/node/core/candidate-validation/src/lib.rs @@ -912,15 +912,10 @@ async fn validate_candidate_exhaustive( // invalid. Ok(ValidationResult::Invalid(InvalidCandidate::CommitmentsHashMismatch)) } else { - let core_index = candidate_receipt.descriptor.core_index(); - - match (core_index, exec_kind) { + match exec_kind { // Core selectors are optional for V2 descriptors, but we still check the // descriptor core index. - ( - Some(_core_index), - PvfExecKind::Backing(_) | PvfExecKind::BackingSystemParas(_), - ) => { + PvfExecKind::Backing(_) | PvfExecKind::BackingSystemParas(_) => { let Some(claim_queue) = maybe_claim_queue else { let error = "cannot fetch the claim queue from the runtime"; gum::warn!( @@ -937,9 +932,9 @@ async fn validate_candidate_exhaustive( { gum::warn!( target: LOG_TARGET, - ?err, candidate_hash = ?candidate_receipt.hash(), - "Candidate core index is invalid", + "Candidate core index is invalid: {}", + err ); return Ok(ValidationResult::Invalid( InvalidCandidate::InvalidCoreIndex, @@ -947,7 +942,7 @@ async fn validate_candidate_exhaustive( } }, // No checks for approvals and disputes - (_, _) => {}, + _ => {}, } Ok(ValidationResult::Valid( diff --git a/polkadot/node/core/candidate-validation/src/tests.rs b/polkadot/node/core/candidate-validation/src/tests.rs index 98e34a1cb4c1..795d7c93f8a7 100644 --- a/polkadot/node/core/candidate-validation/src/tests.rs +++ b/polkadot/node/core/candidate-validation/src/tests.rs @@ -30,8 +30,8 @@ use polkadot_node_subsystem_util::reexports::SubsystemContext; use polkadot_overseer::ActivatedLeaf; use polkadot_primitives::{ vstaging::{ - CandidateDescriptorV2, ClaimQueueOffset, CoreSelector, MutateDescriptorV2, UMPSignal, - UMP_SEPARATOR, + CandidateDescriptorV2, CandidateDescriptorVersion, ClaimQueueOffset, CoreSelector, + MutateDescriptorV2, UMPSignal, UMP_SEPARATOR, }, CandidateDescriptor, CoreIndex, GroupIndex, HeadData, Id as ParaId, OccupiedCoreAssumption, SessionInfo, UpwardMessage, ValidatorId, @@ -851,7 +851,7 @@ fn invalid_session_or_core_index() { )) .unwrap(); - // Validation doesn't fail for approvals, core/session index is not checked. + // Validation doesn't fail for disputes, core/session index is not checked. assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); assert_eq!(outputs.upward_messages, commitments.upward_messages); @@ -911,6 +911,69 @@ fn invalid_session_or_core_index() { assert_eq!(outputs.hrmp_watermark, 0); assert_eq!(used_validation_data, validation_data); }); + + // Test that a v1 candidate that outputs the core selector UMP signal is invalid. + let descriptor_v1 = make_valid_candidate_descriptor( + ParaId::from(1_u32), + dummy_hash(), + dummy_hash(), + pov.hash(), + validation_code.hash(), + validation_result.head_data.hash(), + dummy_hash(), + sp_keyring::Sr25519Keyring::Ferdie, + ); + let descriptor: CandidateDescriptorV2 = descriptor_v1.into(); + + perform_basic_checks(&descriptor, validation_data.max_pov_size, &pov, &validation_code.hash()) + .unwrap(); + assert_eq!(descriptor.version(), CandidateDescriptorVersion::V1); + let candidate_receipt = CandidateReceipt { descriptor, commitments_hash: commitments.hash() }; + + for exec_kind in + [PvfExecKind::Backing(dummy_hash()), PvfExecKind::BackingSystemParas(dummy_hash())] + { + let result = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + exec_kind, + &Default::default(), + Some(Default::default()), + )) + .unwrap(); + assert_matches!(result, ValidationResult::Invalid(InvalidCandidate::InvalidCoreIndex)); + } + + // Validation doesn't fail for approvals and disputes, core/session index is not checked. + for exec_kind in [PvfExecKind::Approval, PvfExecKind::Dispute] { + let v = executor::block_on(validate_candidate_exhaustive( + Some(1), + MockValidateCandidateBackend::with_hardcoded_result(Ok(validation_result.clone())), + validation_data.clone(), + validation_code.clone(), + candidate_receipt.clone(), + Arc::new(pov.clone()), + ExecutorParams::default(), + exec_kind, + &Default::default(), + Default::default(), + )) + .unwrap(); + + assert_matches!(v, ValidationResult::Valid(outputs, used_validation_data) => { + assert_eq!(outputs.head_data, HeadData(vec![1, 1, 1])); + assert_eq!(outputs.upward_messages, commitments.upward_messages); + assert_eq!(outputs.horizontal_messages, Vec::new()); + assert_eq!(outputs.new_validation_code, Some(vec![2, 2, 2].into())); + assert_eq!(outputs.hrmp_watermark, 0); + assert_eq!(used_validation_data, validation_data); + }); + } } #[test] @@ -1407,7 +1470,7 @@ fn compressed_code_works() { ExecutorParams::default(), PvfExecKind::Backing(dummy_hash()), &Default::default(), - Default::default(), + Some(Default::default()), )); assert_matches!(v, Ok(ValidationResult::Valid(_, _))); diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index 271f78efe090..c52f3539c3e5 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -505,6 +505,10 @@ pub enum CommittedCandidateReceiptError { /// Currenly only one such message is allowed. #[cfg_attr(feature = "std", error("Too many UMP signals"))] TooManyUMPSignals, + /// If the parachain runtime started sending core selectors, v1 descriptors are no longer + /// allowed. + #[cfg_attr(feature = "std", error("Version 1 receipt does not support core selectors"))] + CoreSelectorWithV1Decriptor, } macro_rules! impl_getter { @@ -603,15 +607,25 @@ impl CommittedCandidateReceiptV2 { &self, cores_per_para: &TransposedClaimQueue, ) -> Result<(), CommittedCandidateReceiptError> { + let maybe_core_selector = self.commitments.core_selector()?; + match self.descriptor.version() { - // Don't check v1 descriptors. - CandidateDescriptorVersion::V1 => return Ok(()), + CandidateDescriptorVersion::V1 => { + // If the parachain runtime started sending core selectors, v1 descriptors are no + // longer allowed. + if maybe_core_selector.is_some() { + return Err(CommittedCandidateReceiptError::CoreSelectorWithV1Decriptor) + } else { + // Nothing else to check for v1 descriptors. + return Ok(()) + } + }, CandidateDescriptorVersion::V2 => {}, CandidateDescriptorVersion::Unknown => return Err(CommittedCandidateReceiptError::UnknownVersion(self.descriptor.version)), } - let (maybe_core_index_selector, cq_offset) = self.commitments.core_selector()?.map_or_else( + let (maybe_core_index_selector, cq_offset) = maybe_core_selector.map_or_else( || (None, ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET)), |(sel, off)| (Some(sel), off), ); @@ -1207,8 +1221,7 @@ mod tests { assert_eq!(new_ccr.hash(), v2_ccr.hash()); } - // Only check descriptor `core_index` field of v2 descriptors. If it is v1, that field - // will be garbage. + // V1 descriptors are forbidden once the parachain runtime started sending UMP signals. #[test] fn test_v1_descriptors_with_ump_signal() { let mut ccr = dummy_old_committed_candidate_receipt(); @@ -1234,9 +1247,12 @@ mod tests { cq.insert(CoreIndex(0), vec![v1_ccr.descriptor.para_id()].into()); cq.insert(CoreIndex(1), vec![v1_ccr.descriptor.para_id()].into()); - assert!(v1_ccr.check_core_index(&transpose_claim_queue(cq)).is_ok()); - assert_eq!(v1_ccr.descriptor.core_index(), None); + + assert_eq!( + v1_ccr.check_core_index(&transpose_claim_queue(cq)), + Err(CommittedCandidateReceiptError::CoreSelectorWithV1Decriptor) + ); } #[test] diff --git a/prdoc/pr_7127.prdoc b/prdoc/pr_7127.prdoc new file mode 100644 index 000000000000..761ddd04dbe1 --- /dev/null +++ b/prdoc/pr_7127.prdoc @@ -0,0 +1,9 @@ +title: 'Forbid v1 descriptors with UMP signals' +doc: +- audience: [Runtime Dev, Node Dev] + description: Adds a check that parachain candidates do not send out UMP signals with v1 descriptors. +crates: +- name: polkadot-node-core-candidate-validation + bump: minor +- name: polkadot-primitives + bump: major From f4743b009280e47398790bd85943819540a9ce0a Mon Sep 17 00:00:00 2001 From: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:09:01 +0100 Subject: [PATCH 253/340] `fatxpool`: proper handling of priorities when mempool is full (#6647) Higher-priority transactions can now replace lower-priority transactions even when the internal _tx_mem_pool_ is full. **Notes for reviewers:** - The _tx_mem_pool_ now maintains information about transaction priority. Although _tx_mem_pool_ itself is stateless, transaction priority is updated after submission to the view. An alternative approach could involve validating transactions at the `at` block, but this is computationally expensive. To avoid additional validation overhead, I opted to use the priority obtained from runtime during submission to the view. This is the rationale behind introducing the `SubmitOutcome` struct, which synchronously communicates transaction priority from the view to the pool. This results in a very brief window during which the transaction priority remains unknown - those transaction are not taken into consideration while dropping takes place. In the future, if needed, we could update transaction priority using view revalidation results to keep this information fully up-to-date (as priority of transaction may change with chain-state evolution). - When _tx_mem_pool_ becomes full (an event anticipated to be rare), transaction priority must be known to perform priority-based removal. In such cases, the most recent block known is utilized for validation. I think that speculative submission to the view and re-using the priority from this submission would be an unnecessary complication. - Once the priority is determined, lower-priority transactions whose cumulative size meets or exceeds the size of the new transaction are collected to ensure the pool size limit is not exceeded. - Transaction removed from _tx_mem_pool_ , also needs to be removed from all the views with appropriate event (which is done by `remove_transaction_subtree`). To ensure complete removal, the `PendingTxReplacement` struct was re-factored to more generic `PendingPreInsertTask` (introduced in #6405) which covers removal and submssion of transaction in the view which may be potentially created in the background. This is to ensure that removed transaction will not re-enter to the newly created view. - `submit_local` implementation was also improved to properly handle priorities in case when mempool is full. Some missing tests for this method were also added. Closes: #5809 --------- Co-authored-by: command-bot <> Co-authored-by: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> --- prdoc/pr_6647.prdoc | 8 + .../src/fork_aware_txpool/dropped_watcher.rs | 18 +- .../fork_aware_txpool/fork_aware_txpool.rs | 238 +++++++++-- .../src/fork_aware_txpool/tx_mem_pool.rs | 402 ++++++++++++++++-- .../src/fork_aware_txpool/view.rs | 22 +- .../src/fork_aware_txpool/view_store.rs | 261 +++++++++--- .../transaction-pool/src/graph/base_pool.rs | 44 +- .../transaction-pool/src/graph/listener.rs | 4 +- .../client/transaction-pool/src/graph/mod.rs | 8 +- .../client/transaction-pool/src/graph/pool.rs | 84 ++-- .../transaction-pool/src/graph/ready.rs | 10 +- .../transaction-pool/src/graph/tracked_map.rs | 5 + .../src/graph/validated_pool.rs | 119 +++++- .../src/single_state_txpool/revalidation.rs | 5 +- .../single_state_txpool.rs | 30 +- .../transaction-pool/tests/fatp_common/mod.rs | 19 +- .../transaction-pool/tests/fatp_prios.rs | 317 +++++++++++++- .../client/transaction-pool/tests/pool.rs | 14 +- .../runtime/transaction-pool/src/lib.rs | 36 +- 19 files changed, 1393 insertions(+), 251 deletions(-) create mode 100644 prdoc/pr_6647.prdoc diff --git a/prdoc/pr_6647.prdoc b/prdoc/pr_6647.prdoc new file mode 100644 index 000000000000..47af9924ef1c --- /dev/null +++ b/prdoc/pr_6647.prdoc @@ -0,0 +1,8 @@ +title: '`fatxpool`: proper handling of priorities when mempool is full' +doc: +- audience: Node Dev + description: |- + Higher-priority transactions can now replace lower-priority transactions even when the internal _tx_mem_pool_ is full. +crates: +- name: sc-transaction-pool + bump: minor diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs index d69aa37c94a1..bf61558b00b0 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/dropped_watcher.rs @@ -53,11 +53,13 @@ pub struct DroppedTransaction { } impl DroppedTransaction { - fn new_usurped(tx_hash: Hash, by: Hash) -> Self { + /// Creates a new instance with reason set to `DroppedReason::Usurped(by)`. + pub fn new_usurped(tx_hash: Hash, by: Hash) -> Self { Self { reason: DroppedReason::Usurped(by), tx_hash } } - fn new_enforced_by_limts(tx_hash: Hash) -> Self { + /// Creates a new instance with reason set to `DroppedReason::LimitsEnforced`. + pub fn new_enforced_by_limts(tx_hash: Hash) -> Self { Self { reason: DroppedReason::LimitsEnforced, tx_hash } } } @@ -256,11 +258,13 @@ where self.future_transaction_views.entry(tx_hash).or_default().insert(block_hash); }, TransactionStatus::Ready | TransactionStatus::InBlock(..) => { - // note: if future transaction was once seens as the ready we may want to treat it - // as ready transactions. Unreferenced future transactions are more likely to be - // removed when the last referencing view is removed then ready transactions. - // Transcaction seen as ready is likely quite close to be included in some - // future fork. + // note: if future transaction was once seen as the ready we may want to treat it + // as ready transaction. The rationale behind this is as follows: we want to remove + // unreferenced future transactions when the last referencing view is removed (to + // avoid clogging mempool). For ready transactions we prefer to keep them in mempool + // even if no view is currently referencing them. Future transcaction once seen as + // ready is likely quite close to be included in some future fork (it is close to be + // ready, so we make exception and treat such transaction as ready). if let Some(mut views) = self.future_transaction_views.remove(&tx_hash) { views.insert(block_hash); self.ready_transaction_views.insert(tx_hash, views); diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs index e57256943ccf..766045718252 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/fork_aware_txpool.rs @@ -31,7 +31,10 @@ use crate::{ api::FullChainApi, common::log_xt::log_xt_trace, enactment_state::{EnactmentAction, EnactmentState}, - fork_aware_txpool::{dropped_watcher::DroppedReason, revalidation_worker}, + fork_aware_txpool::{ + dropped_watcher::{DroppedReason, DroppedTransaction}, + revalidation_worker, + }, graph::{ self, base_pool::{TimedTransactionSource, Transaction}, @@ -49,14 +52,16 @@ use futures::{ use parking_lot::Mutex; use prometheus_endpoint::Registry as PrometheusRegistry; use sc_transaction_pool_api::{ - ChainEvent, ImportNotificationStream, MaintainedTransactionPool, PoolStatus, TransactionFor, - TransactionPool, TransactionSource, TransactionStatusStreamFor, TxHash, + error::Error as TxPoolApiError, ChainEvent, ImportNotificationStream, + MaintainedTransactionPool, PoolStatus, TransactionFor, TransactionPool, TransactionPriority, + TransactionSource, TransactionStatusStreamFor, TxHash, }; use sp_blockchain::{HashAndNumber, TreeRoute}; use sp_core::traits::SpawnEssentialNamed; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, NumberFor}, + transaction_validity::{TransactionValidityError, ValidTransaction}, }; use std::{ collections::{HashMap, HashSet}, @@ -287,7 +292,7 @@ where DroppedReason::LimitsEnforced => {}, }; - mempool.remove_dropped_transaction(&dropped_tx_hash).await; + mempool.remove_transaction(&dropped_tx_hash); view_store.listener.transaction_dropped(dropped); import_notification_sink.clean_notified_items(&[dropped_tx_hash]); } @@ -598,7 +603,7 @@ where /// out: /// [ Ok(xth0), Ok(xth1), Err ] /// ``` -fn reduce_multiview_result(input: HashMap>>) -> Vec> { +fn reduce_multiview_result(input: HashMap>>) -> Vec> { let mut values = input.values(); let Some(first) = values.next() else { return Default::default(); @@ -650,9 +655,28 @@ where let mempool_results = self.mempool.extend_unwatched(source, &xts); if view_store.is_empty() { - return Ok(mempool_results.into_iter().map(|r| r.map(|r| r.hash)).collect::>()) + return Ok(mempool_results + .into_iter() + .map(|r| r.map(|r| r.hash).map_err(Into::into)) + .collect::>()) } + // Submit all the transactions to the mempool + let retries = mempool_results + .into_iter() + .zip(xts.clone()) + .map(|(result, xt)| async move { + match result { + Err(TxPoolApiError::ImmediatelyDropped) => + self.attempt_transaction_replacement(source, false, xt).await, + _ => result, + } + }) + .collect::>(); + + let mempool_results = futures::future::join_all(retries).await; + + // Collect transactions that were successfully submitted to the mempool... let to_be_submitted = mempool_results .iter() .zip(xts) @@ -664,22 +688,47 @@ where self.metrics .report(|metrics| metrics.submitted_transactions.inc_by(to_be_submitted.len() as _)); + // ... and submit them to the view_store. Please note that transactions rejected by mempool + // are not sent here. let mempool = self.mempool.clone(); let results_map = view_store.submit(to_be_submitted.into_iter()).await; let mut submission_results = reduce_multiview_result(results_map).into_iter(); + // Note for composing final result: + // + // For each failed insertion into the mempool, the mempool result should be placed into + // the returned vector. + // + // For each successful insertion into the mempool, the corresponding + // view_store submission result needs to be examined: + // - If there is an error during view_store submission, the transaction is removed from + // the mempool, and the final result recorded in the vector for this transaction is the + // view_store submission error. + // + // - If the view_store submission is successful, the transaction priority is updated in the + // mempool. + // + // Finally, it collects the hashes of updated transactions or submission errors (either + // from the mempool or view_store) into a returned vector. Ok(mempool_results .into_iter() .map(|result| { - result.and_then(|insertion| { - submission_results - .next() - .expect("The number of Ok results in mempool is exactly the same as the size of to-views-submission result. qed.") - .inspect_err(|_| - mempool.remove(insertion.hash) - ) + result + .map_err(Into::into) + .and_then(|insertion| { + submission_results + .next() + .expect("The number of Ok results in mempool is exactly the same as the size of view_store submission result. qed.") + .inspect_err(|_|{ + mempool.remove_transaction(&insertion.hash); + }) }) + }) + .map(|r| r.map(|r| { + mempool.update_transaction_priority(&r); + r.hash() + })) .collect::>()) } @@ -712,10 +761,13 @@ where ) -> Result>>, Self::Error> { log::trace!(target: LOG_TARGET, "[{:?}] fatp::submit_and_watch views:{}", self.tx_hash(&xt), self.active_views_count()); let xt = Arc::from(xt); - let InsertionInfo { hash: xt_hash, source: timed_source } = + + let InsertionInfo { hash: xt_hash, source: timed_source, .. } = match self.mempool.push_watched(source, xt.clone()) { Ok(result) => result, - Err(e) => return Err(e), + Err(TxPoolApiError::ImmediatelyDropped) => + self.attempt_transaction_replacement(source, true, xt.clone()).await?, + Err(e) => return Err(e.into()), }; self.metrics.report(|metrics| metrics.submitted_transactions.inc()); @@ -723,7 +775,13 @@ where self.view_store .submit_and_watch(at, timed_source, xt) .await - .inspect_err(|_| self.mempool.remove(xt_hash)) + .inspect_err(|_| { + self.mempool.remove_transaction(&xt_hash); + }) + .map(|mut outcome| { + self.mempool.update_transaction_priority(&outcome); + outcome.expect_watcher() + }) } /// Intended to remove transactions identified by the given hashes, and any dependent @@ -828,22 +886,16 @@ where } } -impl sc_transaction_pool_api::LocalTransactionPool - for ForkAwareTxPool, Block> +impl sc_transaction_pool_api::LocalTransactionPool + for ForkAwareTxPool where Block: BlockT, + ChainApi: 'static + graph::ChainApi, ::Hash: Unpin, - Client: sp_api::ProvideRuntimeApi - + sc_client_api::BlockBackend - + sc_client_api::blockchain::HeaderBackend - + sp_runtime::traits::BlockIdTo - + sp_blockchain::HeaderMetadata, - Client: Send + Sync + 'static, - Client::Api: sp_transaction_pool::runtime_api::TaggedTransactionQueue, { type Block = Block; - type Hash = ExtrinsicHash>; - type Error = as graph::ChainApi>::Error; + type Hash = ExtrinsicHash; + type Error = ChainApi::Error; fn submit_local( &self, @@ -852,12 +904,29 @@ where ) -> Result { log::debug!(target: LOG_TARGET, "fatp::submit_local views:{}", self.active_views_count()); let xt = Arc::from(xt); - let InsertionInfo { hash: xt_hash, .. } = self - .mempool - .extend_unwatched(TransactionSource::Local, &[xt.clone()]) - .remove(0)?; - self.view_store.submit_local(xt).or_else(|_| Ok(xt_hash)) + let result = + self.mempool.extend_unwatched(TransactionSource::Local, &[xt.clone()]).remove(0); + + let insertion = match result { + Err(TxPoolApiError::ImmediatelyDropped) => self.attempt_transaction_replacement_sync( + TransactionSource::Local, + false, + xt.clone(), + ), + _ => result, + }?; + + self.view_store + .submit_local(xt) + .inspect_err(|_| { + self.mempool.remove_transaction(&insertion.hash); + }) + .map(|outcome| { + self.mempool.update_transaction_priority(&outcome); + outcome.hash() + }) + .or_else(|_| Ok(insertion.hash)) } } @@ -1109,7 +1178,11 @@ where .await .into_iter() .zip(hashes) - .map(|(result, tx_hash)| result.or_else(|_| Err(tx_hash))) + .map(|(result, tx_hash)| { + result + .map(|outcome| self.mempool.update_transaction_priority(&outcome.into())) + .or_else(|_| Err(tx_hash)) + }) .collect::>(); let submitted_count = watched_results.len(); @@ -1131,7 +1204,7 @@ where for result in watched_results { if let Err(tx_hash) = result { self.view_store.listener.invalidate_transactions(&[tx_hash]); - self.mempool.remove(tx_hash); + self.mempool.remove_transaction(&tx_hash); } } } @@ -1263,6 +1336,101 @@ where fn tx_hash(&self, xt: &TransactionFor) -> TxHash { self.api.hash_and_length(xt).0 } + + /// Attempts to find and replace a lower-priority transaction in the transaction pool with a new + /// one. + /// + /// This asynchronous function verifies the new transaction against the most recent view. If a + /// transaction with a lower priority exists in the transaction pool, it is replaced with the + /// new transaction. + /// + /// If no lower-priority transaction is found, the function returns an error indicating the + /// transaction was dropped immediately. + async fn attempt_transaction_replacement( + &self, + source: TransactionSource, + watched: bool, + xt: ExtrinsicFor, + ) -> Result>, TxPoolApiError> { + let at = self + .view_store + .most_recent_view + .read() + .ok_or(TxPoolApiError::ImmediatelyDropped)?; + + let (best_view, _) = self + .view_store + .get_view_at(at, false) + .ok_or(TxPoolApiError::ImmediatelyDropped)?; + + let (xt_hash, validated_tx) = best_view + .pool + .verify_one( + best_view.at.hash, + best_view.at.number, + TimedTransactionSource::from_transaction_source(source, false), + xt.clone(), + crate::graph::CheckBannedBeforeVerify::Yes, + ) + .await; + + let Some(priority) = validated_tx.priority() else { + return Err(TxPoolApiError::ImmediatelyDropped) + }; + + self.attempt_transaction_replacement_inner(xt, xt_hash, priority, source, watched) + } + + /// Sync version of [`Self::attempt_transaction_replacement`]. + fn attempt_transaction_replacement_sync( + &self, + source: TransactionSource, + watched: bool, + xt: ExtrinsicFor, + ) -> Result>, TxPoolApiError> { + let at = self + .view_store + .most_recent_view + .read() + .ok_or(TxPoolApiError::ImmediatelyDropped)?; + + let ValidTransaction { priority, .. } = self + .api + .validate_transaction_blocking(at, TransactionSource::Local, Arc::from(xt.clone())) + .map_err(|_| TxPoolApiError::ImmediatelyDropped)? + .map_err(|e| match e { + TransactionValidityError::Invalid(i) => TxPoolApiError::InvalidTransaction(i), + TransactionValidityError::Unknown(u) => TxPoolApiError::UnknownTransaction(u), + })?; + let xt_hash = self.hash_of(&xt); + self.attempt_transaction_replacement_inner(xt, xt_hash, priority, source, watched) + } + + fn attempt_transaction_replacement_inner( + &self, + xt: ExtrinsicFor, + tx_hash: ExtrinsicHash, + priority: TransactionPriority, + source: TransactionSource, + watched: bool, + ) -> Result>, TxPoolApiError> { + let insertion_info = + self.mempool.try_insert_with_replacement(xt, priority, source, watched)?; + + for worst_hash in &insertion_info.removed { + log::trace!(target: LOG_TARGET, "removed: {worst_hash:?} replaced by {tx_hash:?}"); + self.view_store + .listener + .transaction_dropped(DroppedTransaction::new_enforced_by_limts(*worst_hash)); + + self.view_store + .remove_transaction_subtree(*worst_hash, |listener, removed_tx_hash| { + listener.limits_enforced(&removed_tx_hash); + }); + } + + return Ok(insertion_info) + } } #[async_trait] @@ -1410,7 +1578,7 @@ mod reduce_multiview_result_tests { fn empty() { sp_tracing::try_init_simple(); let input = HashMap::default(); - let r = reduce_multiview_result::(input); + let r = reduce_multiview_result::(input); assert!(r.is_empty()); } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs index 989ae4425dc4..c8a4d0c72dd3 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/tx_mem_pool.rs @@ -26,7 +26,10 @@ //! it), while on other forks tx can be valid. Depending on which view is chosen to be cloned, //! such transaction could not be present in the newly created view. -use super::{metrics::MetricsLink as PrometheusMetrics, multi_view_listener::MultiViewListener}; +use super::{ + metrics::MetricsLink as PrometheusMetrics, multi_view_listener::MultiViewListener, + view_store::ViewStoreSubmitOutcome, +}; use crate::{ common::log_xt::log_xt_trace, graph, @@ -35,15 +38,20 @@ use crate::{ }; use futures::FutureExt; use itertools::Itertools; -use sc_transaction_pool_api::TransactionSource; +use parking_lot::RwLock; +use sc_transaction_pool_api::{TransactionPriority, TransactionSource}; use sp_blockchain::HashAndNumber; use sp_runtime::{ traits::Block as BlockT, transaction_validity::{InvalidTransaction, TransactionValidityError}, }; use std::{ + cmp::Ordering, collections::HashMap, - sync::{atomic, atomic::AtomicU64, Arc}, + sync::{ + atomic::{self, AtomicU64}, + Arc, + }, time::Instant, }; @@ -77,6 +85,9 @@ where source: TimedTransactionSource, /// When the transaction was revalidated, used to periodically revalidate the mem pool buffer. validated_at: AtomicU64, + /// Priority of transaction at some block. It is assumed it will not be changed often. None if + /// not known. + priority: RwLock>, //todo: we need to add future / ready status at finalized block. //If future transactions are stuck in tx_mem_pool (due to limits being hit), we need a means // to replace them somehow with newly coming transactions. @@ -101,23 +112,50 @@ where /// Creates a new instance of wrapper for unwatched transaction. fn new_unwatched(source: TransactionSource, tx: ExtrinsicFor, bytes: usize) -> Self { - Self { - watched: false, - tx, - source: TimedTransactionSource::from_transaction_source(source, true), - validated_at: AtomicU64::new(0), - bytes, - } + Self::new(false, source, tx, bytes) } /// Creates a new instance of wrapper for watched transaction. fn new_watched(source: TransactionSource, tx: ExtrinsicFor, bytes: usize) -> Self { + Self::new(true, source, tx, bytes) + } + + /// Creates a new instance of wrapper for a transaction with no priority. + fn new( + watched: bool, + source: TransactionSource, + tx: ExtrinsicFor, + bytes: usize, + ) -> Self { + Self::new_with_optional_priority(watched, source, tx, bytes, None) + } + + /// Creates a new instance of wrapper for a transaction with given priority. + fn new_with_priority( + watched: bool, + source: TransactionSource, + tx: ExtrinsicFor, + bytes: usize, + priority: TransactionPriority, + ) -> Self { + Self::new_with_optional_priority(watched, source, tx, bytes, Some(priority)) + } + + /// Creates a new instance of wrapper for a transaction with optional priority. + fn new_with_optional_priority( + watched: bool, + source: TransactionSource, + tx: ExtrinsicFor, + bytes: usize, + priority: Option, + ) -> Self { Self { - watched: true, + watched, tx, source: TimedTransactionSource::from_transaction_source(source, true), validated_at: AtomicU64::new(0), bytes, + priority: priority.into(), } } @@ -132,6 +170,11 @@ where pub(crate) fn source(&self) -> TimedTransactionSource { self.source.clone() } + + /// Returns the priority of the transaction. + pub(crate) fn priority(&self) -> Option { + *self.priority.read() + } } impl Size for Arc> @@ -191,11 +234,15 @@ where pub(super) struct InsertionInfo { pub(super) hash: Hash, pub(super) source: TimedTransactionSource, + pub(super) removed: Vec, } impl InsertionInfo { fn new(hash: Hash, source: TimedTransactionSource) -> Self { - Self { hash, source } + Self::new_with_removed(hash, source, Default::default()) + } + fn new_with_removed(hash: Hash, source: TimedTransactionSource, removed: Vec) -> Self { + Self { hash, source, removed } } } @@ -279,27 +326,109 @@ where &self, hash: ExtrinsicHash, tx: TxInMemPool, - ) -> Result>, ChainApi::Error> { - let bytes = self.transactions.bytes(); + ) -> Result>, sc_transaction_pool_api::error::Error> { let mut transactions = self.transactions.write(); + + let bytes = self.transactions.bytes(); + let result = match ( - !self.is_limit_exceeded(transactions.len() + 1, bytes + tx.bytes), + self.is_limit_exceeded(transactions.len() + 1, bytes + tx.bytes), transactions.contains_key(&hash), ) { - (true, false) => { + (false, false) => { let source = tx.source(); transactions.insert(hash, Arc::from(tx)); Ok(InsertionInfo::new(hash, source)) }, (_, true) => - Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash)).into()), - (false, _) => Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped.into()), + Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash))), + (true, _) => Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped), }; log::trace!(target: LOG_TARGET, "[{:?}] mempool::try_insert: {:?}", hash, result.as_ref().map(|r| r.hash)); result } + /// Attempts to insert a new transaction in the memory pool and drop some worse existing + /// transactions. + /// + /// A "worse" transaction means transaction with lower priority, or older transaction with the + /// same prio. + /// + /// This operation will not overflow the limit of the mempool. It means that cumulative + /// size of removed transactions will be equal (or greated) then size of newly inserted + /// transaction. + /// + /// Returns a `Result` containing `InsertionInfo` if the new transaction is successfully + /// inserted; otherwise, returns an appropriate error indicating the failure. + pub(super) fn try_insert_with_replacement( + &self, + new_tx: ExtrinsicFor, + priority: TransactionPriority, + source: TransactionSource, + watched: bool, + ) -> Result>, sc_transaction_pool_api::error::Error> { + let (hash, length) = self.api.hash_and_length(&new_tx); + let new_tx = TxInMemPool::new_with_priority(watched, source, new_tx, length, priority); + if new_tx.bytes > self.max_transactions_total_bytes { + return Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped); + } + + let mut transactions = self.transactions.write(); + + if transactions.contains_key(&hash) { + return Err(sc_transaction_pool_api::error::Error::AlreadyImported(Box::new(hash))); + } + + let mut sorted = transactions + .iter() + .filter_map(|(h, v)| v.priority().map(|_| (*h, v.clone()))) + .collect::>(); + + // When pushing higher prio transaction, we need to find a number of lower prio txs, such + // that the sum of their bytes is ge then size of new tx. Otherwise we could overflow size + // limits. Naive way to do it - rev-sort by priority and eat the tail. + + // reverse (oldest, lowest prio last) + sorted.sort_by(|(_, a), (_, b)| match b.priority().cmp(&a.priority()) { + Ordering::Equal => match (a.source.timestamp, b.source.timestamp) { + (Some(a), Some(b)) => b.cmp(&a), + _ => Ordering::Equal, + }, + ordering => ordering, + }); + + let mut total_size_removed = 0usize; + let mut to_be_removed = vec![]; + let free_bytes = self.max_transactions_total_bytes - self.transactions.bytes(); + + loop { + let Some((worst_hash, worst_tx)) = sorted.pop() else { + return Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped); + }; + + if worst_tx.priority() >= new_tx.priority() { + return Err(sc_transaction_pool_api::error::Error::ImmediatelyDropped); + } + + total_size_removed += worst_tx.bytes; + to_be_removed.push(worst_hash); + + if free_bytes + total_size_removed >= new_tx.bytes { + break; + } + } + + let source = new_tx.source(); + transactions.insert(hash, Arc::from(new_tx)); + for worst_hash in &to_be_removed { + transactions.remove(worst_hash); + } + debug_assert!(!self.is_limit_exceeded(transactions.len(), self.transactions.bytes())); + + Ok(InsertionInfo::new_with_removed(hash, source, to_be_removed)) + } + /// Adds a new unwatched transactions to the internal buffer not exceeding the limit. /// /// Returns the vector of results for each transaction, the order corresponds to the input @@ -308,7 +437,8 @@ where &self, source: TransactionSource, xts: &[ExtrinsicFor], - ) -> Vec>, ChainApi::Error>> { + ) -> Vec>, sc_transaction_pool_api::error::Error>> + { let result = xts .iter() .map(|xt| { @@ -325,20 +455,11 @@ where &self, source: TransactionSource, xt: ExtrinsicFor, - ) -> Result>, ChainApi::Error> { + ) -> Result>, sc_transaction_pool_api::error::Error> { let (hash, length) = self.api.hash_and_length(&xt); self.try_insert(hash, TxInMemPool::new_watched(source, xt.clone(), length)) } - /// Removes transaction from the memory pool which are specified by the given list of hashes. - pub(super) async fn remove_dropped_transaction( - &self, - dropped: &ExtrinsicHash, - ) -> Option>> { - log::debug!(target: LOG_TARGET, "[{:?}] mempool::remove_dropped_transaction", dropped); - self.transactions.write().remove(dropped) - } - /// Clones and returns a `HashMap` of references to all unwatched transactions in the memory /// pool. pub(super) fn clone_unwatched( @@ -362,9 +483,13 @@ where .collect::>() } - /// Removes a transaction from the memory pool based on a given hash. - pub(super) fn remove(&self, hash: ExtrinsicHash) { - let _ = self.transactions.write().remove(&hash); + /// Removes a transaction with given hash from the memory pool. + pub(super) fn remove_transaction( + &self, + hash: &ExtrinsicHash, + ) -> Option>> { + log::debug!(target: LOG_TARGET, "[{hash:?}] mempool::remove_transaction"); + self.transactions.write().remove(hash) } /// Revalidates a batch of transactions against the provided finalized block. @@ -462,6 +587,17 @@ where }); self.listener.invalidate_transactions(&invalid_hashes); } + + /// Updates the priority of transaction stored in mempool using provided view_store submission + /// outcome. + pub(super) fn update_transaction_priority(&self, outcome: &ViewStoreSubmitOutcome) { + outcome.priority().map(|priority| { + self.transactions + .write() + .get_mut(&outcome.hash()) + .map(|p| *p.priority.write() = Some(priority)) + }); + } } #[cfg(test)] @@ -583,6 +719,9 @@ mod tx_mem_pool_tests { assert_eq!(mempool.unwatched_and_watched_count(), (10, 5)); } + /// size of large extrinsic + const LARGE_XT_SIZE: usize = 1129; + fn large_uxt(x: usize) -> Extrinsic { ExtrinsicBuilder::new_include_data(vec![x as u8; 1024]).build() } @@ -592,8 +731,7 @@ mod tx_mem_pool_tests { sp_tracing::try_init_simple(); let max = 10; let api = Arc::from(TestApi::default()); - //size of large extrinsic is: 1129 - let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * 1129); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * LARGE_XT_SIZE); let xts = (0..max).map(|x| Arc::from(large_uxt(x))).collect::>(); @@ -617,4 +755,200 @@ mod tx_mem_pool_tests { sc_transaction_pool_api::error::Error::ImmediatelyDropped )); } + + #[test] + fn replacing_txs_works_for_same_tx_size() { + sp_tracing::try_init_simple(); + let max = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * LARGE_XT_SIZE); + + let xts = (0..max).map(|x| Arc::from(large_uxt(x))).collect::>(); + + let low_prio = 0u64; + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let (submit_outcomes, hashes): (Vec<_>, Vec<_>) = xts + .iter() + .map(|t| { + let h = api.hash_and_length(t).0; + (ViewStoreSubmitOutcome::new(h, Some(low_prio)), h) + }) + .unzip(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); + + let xt = Arc::from(large_uxt(98)); + let hash = api.hash_and_length(&xt).0; + let result = mempool + .try_insert_with_replacement(xt, hi_prio, TransactionSource::External, false) + .unwrap(); + + assert_eq!(result.hash, hash); + assert_eq!(result.removed, hashes[0..1]); + } + + #[test] + fn replacing_txs_removes_proper_size_of_txs() { + sp_tracing::try_init_simple(); + let max = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, max * LARGE_XT_SIZE); + + let xts = (0..max).map(|x| Arc::from(large_uxt(x))).collect::>(); + + let low_prio = 0u64; + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let (submit_outcomes, hashes): (Vec<_>, Vec<_>) = xts + .iter() + .map(|t| { + let h = api.hash_and_length(t).0; + (ViewStoreSubmitOutcome::new(h, Some(low_prio)), h) + }) + .unzip(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + assert_eq!(total_xts_bytes, max * LARGE_XT_SIZE); + + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); + + //this one should drop 2 xts (size: 1130): + let xt = Arc::from(ExtrinsicBuilder::new_include_data(vec![98 as u8; 1025]).build()); + let (hash, length) = api.hash_and_length(&xt); + assert_eq!(length, 1130); + let result = mempool + .try_insert_with_replacement(xt, hi_prio, TransactionSource::External, false) + .unwrap(); + + assert_eq!(result.hash, hash); + assert_eq!(result.removed, hashes[0..2]); + } + + #[test] + fn replacing_txs_removes_proper_size_and_prios() { + sp_tracing::try_init_simple(); + const COUNT: usize = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, COUNT * LARGE_XT_SIZE); + + let xts = (0..COUNT).map(|x| Arc::from(large_uxt(x))).collect::>(); + + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let (submit_outcomes, hashes): (Vec<_>, Vec<_>) = xts + .iter() + .enumerate() + .map(|(prio, t)| { + let h = api.hash_and_length(t).0; + (ViewStoreSubmitOutcome::new(h, Some((COUNT - prio).try_into().unwrap())), h) + }) + .unzip(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); + + //this one should drop 3 xts (each of size 1129) + let xt = Arc::from(ExtrinsicBuilder::new_include_data(vec![98 as u8; 2154]).build()); + let (hash, length) = api.hash_and_length(&xt); + // overhead is 105, thus length: 105 + 2154 + assert_eq!(length, 2 * LARGE_XT_SIZE + 1); + let result = mempool + .try_insert_with_replacement(xt, hi_prio, TransactionSource::External, false) + .unwrap(); + + assert_eq!(result.hash, hash); + assert!(result.removed.iter().eq(hashes[COUNT - 3..COUNT].iter().rev())); + } + + #[test] + fn replacing_txs_skips_lower_prio_tx() { + sp_tracing::try_init_simple(); + const COUNT: usize = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, COUNT * LARGE_XT_SIZE); + + let xts = (0..COUNT).map(|x| Arc::from(large_uxt(x))).collect::>(); + + let hi_prio = 100u64; + let low_prio = 10u64; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + let submit_outcomes = xts + .iter() + .map(|t| { + let h = api.hash_and_length(t).0; + ViewStoreSubmitOutcome::new(h, Some(hi_prio)) + }) + .collect::>(); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + submit_outcomes + .into_iter() + .for_each(|o| mempool.update_transaction_priority(&o)); + + let xt = Arc::from(large_uxt(98)); + let result = + mempool.try_insert_with_replacement(xt, low_prio, TransactionSource::External, false); + + // lower prio tx is rejected immediately + assert!(matches!( + result.unwrap_err(), + sc_transaction_pool_api::error::Error::ImmediatelyDropped + )); + } + + #[test] + fn replacing_txs_is_skipped_if_prios_are_not_set() { + sp_tracing::try_init_simple(); + const COUNT: usize = 10; + let api = Arc::from(TestApi::default()); + let mempool = TxMemPool::new_test(api.clone(), usize::MAX, COUNT * LARGE_XT_SIZE); + + let xts = (0..COUNT).map(|x| Arc::from(large_uxt(x))).collect::>(); + + let hi_prio = u64::MAX; + + let total_xts_bytes = xts.iter().fold(0, |r, x| r + api.hash_and_length(&x).1); + + let results = mempool.extend_unwatched(TransactionSource::External, &xts); + assert!(results.iter().all(Result::is_ok)); + assert_eq!(mempool.bytes(), total_xts_bytes); + + //this one could drop 3 xts (each of size 1129) + let xt = Arc::from(ExtrinsicBuilder::new_include_data(vec![98 as u8; 2154]).build()); + let length = api.hash_and_length(&xt).1; + // overhead is 105, thus length: 105 + 2154 + assert_eq!(length, 2 * LARGE_XT_SIZE + 1); + + let result = + mempool.try_insert_with_replacement(xt, hi_prio, TransactionSource::External, false); + + // we did not update priorities (update_transaction_priority was not called): + assert!(matches!( + result.unwrap_err(), + sc_transaction_pool_api::error::Error::ImmediatelyDropped + )); + } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs index 3cbb8fa4871d..a35d68120a3a 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view.rs @@ -28,7 +28,7 @@ use crate::{ common::log_xt::log_xt_trace, graph::{ self, base_pool::TimedTransactionSource, watcher::Watcher, ExtrinsicFor, ExtrinsicHash, - IsValidator, ValidatedTransaction, ValidatedTransactionFor, + IsValidator, ValidatedPoolSubmitOutcome, ValidatedTransaction, ValidatedTransactionFor, }, LOG_TARGET, }; @@ -158,7 +158,7 @@ where pub(super) async fn submit_many( &self, xts: impl IntoIterator)>, - ) -> Vec, ChainApi::Error>> { + ) -> Vec, ChainApi::Error>> { if log::log_enabled!(target: LOG_TARGET, log::Level::Trace) { let xts = xts.into_iter().collect::>(); log_xt_trace!(target: LOG_TARGET, xts.iter().map(|(_,xt)| self.pool.validated_pool().api().hash_and_length(xt).0), "[{:?}] view::submit_many at:{}", self.at.hash); @@ -173,7 +173,7 @@ where &self, source: TimedTransactionSource, xt: ExtrinsicFor, - ) -> Result, ExtrinsicHash>, ChainApi::Error> { + ) -> Result, ChainApi::Error> { log::trace!(target: LOG_TARGET, "[{:?}] view::submit_and_watch at:{}", self.pool.validated_pool().api().hash_and_length(&xt).0, self.at.hash); self.pool.submit_and_watch(&self.at, source, xt).await } @@ -182,7 +182,7 @@ where pub(super) fn submit_local( &self, xt: ExtrinsicFor, - ) -> Result, ChainApi::Error> { + ) -> Result, ChainApi::Error> { let (hash, length) = self.pool.validated_pool().api().hash_and_length(&xt); log::trace!(target: LOG_TARGET, "[{:?}] view::submit_local at:{}", hash, self.at.hash); @@ -460,4 +460,18 @@ where const IGNORE_BANNED: bool = false; self.pool.validated_pool().check_is_known(tx_hash, IGNORE_BANNED).is_err() } + + /// Removes the whole transaction subtree from the inner pool. + /// + /// Refer to [`crate::graph::ValidatedPool::remove_subtree`] for more details. + pub fn remove_subtree( + &self, + tx_hash: ExtrinsicHash, + listener_action: F, + ) -> Vec> + where + F: Fn(&mut crate::graph::Listener, ExtrinsicHash), + { + self.pool.validated_pool().remove_subtree(tx_hash, listener_action) + } } diff --git a/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs b/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs index a06c051f0a7e..43ed5bbf8869 100644 --- a/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs +++ b/substrate/client/transaction-pool/src/fork_aware_txpool/view_store.rs @@ -27,7 +27,7 @@ use crate::{ graph::{ self, base_pool::{TimedTransactionSource, Transaction}, - ExtrinsicFor, ExtrinsicHash, TransactionFor, + BaseSubmitOutcome, ExtrinsicFor, ExtrinsicHash, TransactionFor, ValidatedPoolSubmitOutcome, }, ReadyIteratorFor, LOG_TARGET, }; @@ -38,20 +38,18 @@ use sc_transaction_pool_api::{error::Error as PoolError, PoolStatus}; use sp_blockchain::TreeRoute; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use std::{ - collections::{hash_map::Entry, HashMap}, + collections::{hash_map::Entry, HashMap, HashSet}, sync::Arc, time::Instant, }; -/// Helper struct to keep the context for transaction replacements. +/// Helper struct to maintain the context for pending transaction submission, executed for +/// newly inserted views. #[derive(Clone)] -struct PendingTxReplacement +struct PendingTxSubmission where ChainApi: graph::ChainApi, { - /// Indicates if the new transaction was already submitted to all the views in the view_store. - /// If true, it can be removed after inserting any new view. - processed: bool, /// New transaction replacing the old one. xt: ExtrinsicFor, /// Source of the transaction. @@ -60,13 +58,84 @@ where watched: bool, } -impl PendingTxReplacement +/// Helper type representing the callback allowing to trigger per-transaction events on +/// `ValidatedPool`'s listener. +type RemovalListener = + Arc, ExtrinsicHash) + Send + Sync>; + +/// Helper struct to maintain the context for pending transaction removal, executed for +/// newly inserted views. +struct PendingTxRemoval +where + ChainApi: graph::ChainApi, +{ + /// Hash of the transaction that will be removed, + xt_hash: ExtrinsicHash, + /// Action that shall be executed on underlying `ValidatedPool`'s listener. + listener_action: RemovalListener, +} + +/// This enum represents an action that should be executed on the newly built +/// view before this view is inserted into the view store. +enum PreInsertAction +where + ChainApi: graph::ChainApi, +{ + /// Represents the action of submitting a new transaction. Intended to use to handle usurped + /// transactions. + SubmitTx(PendingTxSubmission), + + /// Represents the action of removing a subtree of transactions. + RemoveSubtree(PendingTxRemoval), +} + +/// Represents a task awaiting execution, to be performed immediately prior to the view insertion +/// into the view store. +struct PendingPreInsertTask +where + ChainApi: graph::ChainApi, +{ + /// The action to be applied when inserting a new view. + action: PreInsertAction, + /// Indicates if the action was already applied to all the views in the view_store. + /// If true, it can be removed after inserting any new view. + processed: bool, +} + +impl PendingPreInsertTask where ChainApi: graph::ChainApi, { - /// Creates new unprocessed instance of pending transaction replacement. - fn new(xt: ExtrinsicFor, source: TimedTransactionSource, watched: bool) -> Self { - Self { processed: false, xt, source, watched } + /// Creates new unprocessed instance of pending transaction submission. + fn new_submission_action( + xt: ExtrinsicFor, + source: TimedTransactionSource, + watched: bool, + ) -> Self { + Self { + processed: false, + action: PreInsertAction::SubmitTx(PendingTxSubmission { xt, source, watched }), + } + } + + /// Creates new unprocessed instance of pending transaction removal. + fn new_removal_action( + xt_hash: ExtrinsicHash, + listener: RemovalListener, + ) -> Self { + Self { + processed: false, + action: PreInsertAction::RemoveSubtree(PendingTxRemoval { + xt_hash, + listener_action: listener, + }), + } + } + + /// Marks a task as done for every view present in view store. Basically means that can be + /// removed on new view insertion. + fn mark_processed(&mut self) { + self.processed = true; } } @@ -100,9 +169,20 @@ where /// notifcication threads. It is meant to assure that replaced transaction is also removed from /// newly built views in maintain process. /// - /// The map's key is hash of replaced extrinsic. - pending_txs_replacements: - RwLock, PendingTxReplacement>>, + /// The map's key is hash of actionable extrinsic (to avoid duplicated entries). + pending_txs_tasks: RwLock, PendingPreInsertTask>>, +} + +/// Type alias to outcome of submission to `ViewStore`. +pub(super) type ViewStoreSubmitOutcome = + BaseSubmitOutcome>; + +impl From> + for ViewStoreSubmitOutcome +{ + fn from(value: ValidatedPoolSubmitOutcome) -> Self { + Self::new(value.hash(), value.priority()) + } } impl ViewStore @@ -124,7 +204,7 @@ where listener, most_recent_view: RwLock::from(None), dropped_stream_controller, - pending_txs_replacements: Default::default(), + pending_txs_tasks: Default::default(), } } @@ -132,7 +212,7 @@ where pub(super) async fn submit( &self, xts: impl IntoIterator)> + Clone, - ) -> HashMap, ChainApi::Error>>> { + ) -> HashMap, ChainApi::Error>>> { let submit_futures = { let active_views = self.active_views.read(); active_views @@ -140,7 +220,16 @@ where .map(|(_, view)| { let view = view.clone(); let xts = xts.clone(); - async move { (view.at.hash, view.submit_many(xts).await) } + async move { + ( + view.at.hash, + view.submit_many(xts) + .await + .into_iter() + .map(|r| r.map(Into::into)) + .collect::>(), + ) + } }) .collect::>() }; @@ -153,7 +242,7 @@ where pub(super) fn submit_local( &self, xt: ExtrinsicFor, - ) -> Result, ChainApi::Error> { + ) -> Result, ChainApi::Error> { let active_views = self .active_views .read() @@ -168,12 +257,14 @@ where .map(|view| view.submit_local(xt.clone())) .find_or_first(Result::is_ok); - if let Some(Err(err)) = result { - log::trace!(target: LOG_TARGET, "[{:?}] submit_local: err: {}", tx_hash, err); - return Err(err) - }; - - Ok(tx_hash) + match result { + Some(Err(err)) => { + log::trace!(target: LOG_TARGET, "[{:?}] submit_local: err: {}", tx_hash, err); + Err(err) + }, + None => Ok(ViewStoreSubmitOutcome::new(tx_hash, None)), + Some(Ok(r)) => Ok(r.into()), + } } /// Import a single extrinsic and starts to watch its progress in the pool. @@ -188,7 +279,7 @@ where _at: Block::Hash, source: TimedTransactionSource, xt: ExtrinsicFor, - ) -> Result, ChainApi::Error> { + ) -> Result, ChainApi::Error> { let tx_hash = self.api.hash_and_length(&xt).0; let Some(external_watcher) = self.listener.create_external_watcher_for_tx(tx_hash) else { return Err(PoolError::AlreadyImported(Box::new(tx_hash)).into()) @@ -203,13 +294,13 @@ where let source = source.clone(); async move { match view.submit_and_watch(source, xt).await { - Ok(watcher) => { + Ok(mut result) => { self.listener.add_view_watcher_for_tx( tx_hash, view.at.hash, - watcher.into_stream().boxed(), + result.expect_watcher().into_stream().boxed(), ); - Ok(()) + Ok(result) }, Err(e) => Err(e), } @@ -217,17 +308,20 @@ where }) .collect::>() }; - let maybe_error = futures::future::join_all(submit_and_watch_futures) + let result = futures::future::join_all(submit_and_watch_futures) .await .into_iter() .find_or_first(Result::is_ok); - if let Some(Err(err)) = maybe_error { - log::trace!(target: LOG_TARGET, "[{:?}] submit_and_watch: err: {}", tx_hash, err); - return Err(err); - }; - - Ok(external_watcher) + match result { + Some(Err(err)) => { + log::trace!(target: LOG_TARGET, "[{:?}] submit_and_watch: err: {}", tx_hash, err); + return Err(err); + }, + Some(Ok(result)) => + Ok(ViewStoreSubmitOutcome::from(result).with_watcher(external_watcher)), + None => Ok(ViewStoreSubmitOutcome::new(tx_hash, None).with_watcher(external_watcher)), + } } /// Returns the pool status for every active view. @@ -575,8 +669,12 @@ where replaced: ExtrinsicHash, watched: bool, ) { - if let Entry::Vacant(entry) = self.pending_txs_replacements.write().entry(replaced) { - entry.insert(PendingTxReplacement::new(xt.clone(), source.clone(), watched)); + if let Entry::Vacant(entry) = self.pending_txs_tasks.write().entry(replaced) { + entry.insert(PendingPreInsertTask::new_submission_action( + xt.clone(), + source.clone(), + watched, + )); } else { return }; @@ -586,8 +684,8 @@ where self.replace_transaction_in_views(source, xt, xt_hash, replaced, watched).await; - if let Some(replacement) = self.pending_txs_replacements.write().get_mut(&replaced) { - replacement.processed = true; + if let Some(replacement) = self.pending_txs_tasks.write().get_mut(&replaced) { + replacement.mark_processed(); } } @@ -596,18 +694,25 @@ where /// After application, all already processed replacements are removed. async fn apply_pending_tx_replacements(&self, view: Arc>) { let mut futures = vec![]; - for replacement in self.pending_txs_replacements.read().values() { - let xt_hash = self.api.hash_and_length(&replacement.xt).0; - futures.push(self.replace_transaction_in_view( - view.clone(), - replacement.source.clone(), - replacement.xt.clone(), - xt_hash, - replacement.watched, - )); + for replacement in self.pending_txs_tasks.read().values() { + match replacement.action { + PreInsertAction::SubmitTx(ref submission) => { + let xt_hash = self.api.hash_and_length(&submission.xt).0; + futures.push(self.replace_transaction_in_view( + view.clone(), + submission.source.clone(), + submission.xt.clone(), + xt_hash, + submission.watched, + )); + }, + PreInsertAction::RemoveSubtree(ref removal) => { + view.remove_subtree(removal.xt_hash, &*removal.listener_action); + }, + } } let _results = futures::future::join_all(futures).await; - self.pending_txs_replacements.write().retain(|_, r| r.processed); + self.pending_txs_tasks.write().retain(|_, r| r.processed); } /// Submits `xt` to the given view. @@ -623,11 +728,11 @@ where ) { if watched { match view.submit_and_watch(source, xt).await { - Ok(watcher) => { + Ok(mut result) => { self.listener.add_view_watcher_for_tx( xt_hash, view.at.hash, - watcher.into_stream().boxed(), + result.expect_watcher().into_stream().boxed(), ); }, Err(e) => { @@ -690,4 +795,58 @@ where }; let _results = futures::future::join_all(submit_futures).await; } + + /// Removes a transaction subtree from every view in the view_store, starting from the given + /// transaction hash. + /// + /// This function traverses the dependency graph of transactions and removes the specified + /// transaction along with all its descendant transactions from every view. + /// + /// A `listener_action` callback function is invoked for every transaction that is removed, + /// providing a reference to the pool's listener and the hash of the removed transaction. This + /// allows to trigger the required events. Note that listener may be called multiple times for + /// the same hash. + /// + /// Function will also schedule view pre-insertion actions to ensure that transactions will be + /// removed from newly created view. + /// + /// Returns a vector containing the hashes of all removed transactions, including the root + /// transaction specified by `tx_hash`. Vector contains only unique hashes. + pub(super) fn remove_transaction_subtree( + &self, + xt_hash: ExtrinsicHash, + listener_action: F, + ) -> Vec> + where + F: Fn(&mut crate::graph::Listener, ExtrinsicHash) + + Clone + + Send + + Sync + + 'static, + { + if let Entry::Vacant(entry) = self.pending_txs_tasks.write().entry(xt_hash) { + entry.insert(PendingPreInsertTask::new_removal_action( + xt_hash, + Arc::from(listener_action.clone()), + )); + }; + + let mut seen = HashSet::new(); + + let removed = self + .active_views + .read() + .iter() + .chain(self.inactive_views.read().iter()) + .filter(|(_, view)| view.is_imported(&xt_hash)) + .flat_map(|(_, view)| view.remove_subtree(xt_hash, &listener_action)) + .filter(|xt_hash| seen.insert(*xt_hash)) + .collect(); + + if let Some(removal_action) = self.pending_txs_tasks.write().get_mut(&xt_hash) { + removal_action.mark_processed(); + } + + removed + } } diff --git a/substrate/client/transaction-pool/src/graph/base_pool.rs b/substrate/client/transaction-pool/src/graph/base_pool.rs index 04eaa998f42e..3b4afc88b789 100644 --- a/substrate/client/transaction-pool/src/graph/base_pool.rs +++ b/substrate/client/transaction-pool/src/graph/base_pool.rs @@ -453,27 +453,29 @@ impl BasePool, _>(|worst, current| { - let transaction = ¤t.transaction; - worst - .map(|worst| { - // Here we don't use `TransactionRef`'s ordering implementation because - // while it prefers priority like need here, it also prefers older - // transactions for inclusion purposes and limit enforcement needs to prefer - // newer transactions instead and drop the older ones. - match worst.transaction.priority.cmp(&transaction.transaction.priority) { - Ordering::Less => worst, - Ordering::Equal => - if worst.insertion_id > transaction.insertion_id { - transaction.clone() - } else { - worst - }, - Ordering::Greater => transaction.clone(), - } - }) - .or_else(|| Some(transaction.clone())) - }); + let worst = + self.ready.fold::>, _>(None, |worst, current| { + let transaction = ¤t.transaction; + worst + .map(|worst| { + // Here we don't use `TransactionRef`'s ordering implementation because + // while it prefers priority like need here, it also prefers older + // transactions for inclusion purposes and limit enforcement needs to + // prefer newer transactions instead and drop the older ones. + match worst.transaction.priority.cmp(&transaction.transaction.priority) + { + Ordering::Less => worst, + Ordering::Equal => + if worst.insertion_id > transaction.insertion_id { + transaction.clone() + } else { + worst + }, + Ordering::Greater => transaction.clone(), + } + }) + .or_else(|| Some(transaction.clone())) + }); if let Some(worst) = worst { removed.append(&mut self.remove_subtree(&[worst.transaction.hash.clone()])) diff --git a/substrate/client/transaction-pool/src/graph/listener.rs b/substrate/client/transaction-pool/src/graph/listener.rs index 41daf5491f70..7b09ee4c6409 100644 --- a/substrate/client/transaction-pool/src/graph/listener.rs +++ b/substrate/client/transaction-pool/src/graph/listener.rs @@ -126,8 +126,8 @@ impl Listener Pool { &self, at: &HashAndNumber, xts: impl IntoIterator)>, - ) -> Vec, B::Error>> { + ) -> Vec, B::Error>> { let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::Yes).await; self.validated_pool.submit(validated_transactions.into_values()) } @@ -216,7 +216,7 @@ impl Pool { &self, at: &HashAndNumber, xts: impl IntoIterator)>, - ) -> Vec, B::Error>> { + ) -> Vec, B::Error>> { let validated_transactions = self.verify(at, xts, CheckBannedBeforeVerify::No).await; self.validated_pool.submit(validated_transactions.into_values()) } @@ -227,7 +227,7 @@ impl Pool { at: &HashAndNumber, source: base::TimedTransactionSource, xt: ExtrinsicFor, - ) -> Result, B::Error> { + ) -> Result, B::Error> { let res = self.submit_at(at, std::iter::once((source, xt))).await.pop(); res.expect("One extrinsic passed; one result returned; qed") } @@ -238,7 +238,7 @@ impl Pool { at: &HashAndNumber, source: base::TimedTransactionSource, xt: ExtrinsicFor, - ) -> Result, ExtrinsicHash>, B::Error> { + ) -> Result, B::Error> { let (_, tx) = self .verify_one(at.hash, at.number, source, xt, CheckBannedBeforeVerify::Yes) .await; @@ -432,7 +432,7 @@ impl Pool { } /// Returns future that validates single transaction at given block. - async fn verify_one( + pub(crate) async fn verify_one( &self, block_hash: ::Hash, block_number: NumberFor, @@ -539,6 +539,7 @@ mod tests { .into(), ), ) + .map(|outcome| outcome.hash()) .unwrap(); // then @@ -567,7 +568,10 @@ mod tests { // when let txs = txs.into_iter().map(|x| (SOURCE, Arc::from(x))).collect::>(); - let hashes = block_on(pool.submit_at(&api.expect_hash_and_number(0), txs)); + let hashes = block_on(pool.submit_at(&api.expect_hash_and_number(0), txs)) + .into_iter() + .map(|r| r.map(|o| o.hash())) + .collect::>(); log::debug!("--> {hashes:#?}"); // then @@ -591,7 +595,8 @@ mod tests { // when pool.validated_pool.ban(&Instant::now(), vec![pool.hash_of(&uxt)]); - let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())); + let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())) + .map(|o| o.hash()); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 0); @@ -614,7 +619,8 @@ mod tests { let uxt = ExtrinsicBuilder::new_include_data(vec![42]).build(); // when - let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())); + let res = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, uxt.into())) + .map(|o| o.hash()); // then assert_matches!(res.unwrap_err(), error::Error::Unactionable); @@ -642,7 +648,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); let hash1 = block_on( pool.submit_one( &han_of_block0, @@ -656,7 +663,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // future doesn't count let _hash = block_on( pool.submit_one( @@ -671,7 +679,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); assert_eq!(pool.validated_pool().status().ready, 2); assert_eq!(pool.validated_pool().status().future, 1); @@ -704,7 +713,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); let hash2 = block_on( pool.submit_one( &han_of_block0, @@ -718,7 +728,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); let hash3 = block_on( pool.submit_one( &han_of_block0, @@ -732,7 +743,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // when pool.validated_pool.clear_stale(&api.expect_hash_and_number(5)); @@ -764,7 +776,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // when block_on(pool.prune_tags(&api.expect_hash_and_number(1), vec![vec![0]], vec![hash1])); @@ -792,8 +805,9 @@ mod tests { let api = Arc::new(TestApi::default()); let pool = Pool::new_with_staticly_sized_rotator(options, true.into(), api.clone()); - let hash1 = - block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt.into())).unwrap(); + let hash1 = block_on(pool.submit_one(&api.expect_hash_and_number(0), SOURCE, xt.into())) + .unwrap() + .hash(); assert_eq!(pool.validated_pool().status().future, 1); // when @@ -810,7 +824,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .hash(); // then assert_eq!(pool.validated_pool().status().future, 1); @@ -842,6 +857,7 @@ mod tests { .into(), ), ) + .map(|o| o.hash()) .unwrap_err(); // then @@ -868,6 +884,7 @@ mod tests { .into(), ), ) + .map(|o| o.hash()) .unwrap_err(); // then @@ -896,7 +913,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(pool.validated_pool().status().future, 0); @@ -933,7 +951,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(pool.validated_pool().status().future, 0); @@ -972,7 +991,8 @@ mod tests { .into(), ), ) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 0); assert_eq!(pool.validated_pool().status().future, 1); @@ -1011,7 +1031,8 @@ mod tests { }); let watcher = block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, uxt.into())) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -1036,7 +1057,8 @@ mod tests { }); let watcher = block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, uxt.into())) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -1069,7 +1091,8 @@ mod tests { }); let watcher = block_on(pool.submit_and_watch(&api.expect_hash_and_number(0), SOURCE, xt.into())) - .unwrap(); + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // when @@ -1136,7 +1159,9 @@ mod tests { // after validation `IncludeData` will have priority set to 9001 // (validate_transaction mock) let xt = ExtrinsicBuilder::new_include_data(Vec::new()).build(); - block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())).unwrap(); + block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())) + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 1); // after validation `Transfer` will have priority set to 4 (validate_transaction @@ -1147,8 +1172,9 @@ mod tests { amount: 5, nonce: 0, }); - let watcher = - block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())).unwrap(); + let watcher = block_on(pool.submit_and_watch(&han_of_block0, SOURCE, xt.into())) + .unwrap() + .expect_watcher(); assert_eq!(pool.validated_pool().status().ready, 2); // when diff --git a/substrate/client/transaction-pool/src/graph/ready.rs b/substrate/client/transaction-pool/src/graph/ready.rs index 9061d0e25581..b8aef99e638d 100644 --- a/substrate/client/transaction-pool/src/graph/ready.rs +++ b/substrate/client/transaction-pool/src/graph/ready.rs @@ -232,12 +232,10 @@ impl ReadyTransactions { Ok(replaced) } - /// Fold a list of ready transactions to compute a single value. - pub fn fold, &ReadyTx) -> Option>( - &mut self, - f: F, - ) -> Option { - self.ready.read().values().fold(None, f) + /// Fold a list of ready transactions to compute a single value using initial value of + /// accumulator. + pub fn fold) -> R>(&self, init: R, f: F) -> R { + self.ready.read().values().fold(init, f) } /// Returns true if given transaction is part of the queue. diff --git a/substrate/client/transaction-pool/src/graph/tracked_map.rs b/substrate/client/transaction-pool/src/graph/tracked_map.rs index 6c3bbbf34b55..fe15c6eca308 100644 --- a/substrate/client/transaction-pool/src/graph/tracked_map.rs +++ b/substrate/client/transaction-pool/src/graph/tracked_map.rs @@ -173,6 +173,11 @@ where pub fn len(&mut self) -> usize { self.inner_guard.len() } + + /// Returns an iterator over all key-value pairs. + pub fn iter(&self) -> Iter<'_, K, V> { + self.inner_guard.iter() + } } #[cfg(test)] diff --git a/substrate/client/transaction-pool/src/graph/validated_pool.rs b/substrate/client/transaction-pool/src/graph/validated_pool.rs index 3f7bf4773de7..bc2b07896dba 100644 --- a/substrate/client/transaction-pool/src/graph/validated_pool.rs +++ b/substrate/client/transaction-pool/src/graph/validated_pool.rs @@ -18,25 +18,22 @@ use std::{ collections::{HashMap, HashSet}, - hash, sync::Arc, }; use crate::{common::log_xt::log_xt_trace, LOG_TARGET}; use futures::channel::mpsc::{channel, Sender}; use parking_lot::{Mutex, RwLock}; -use sc_transaction_pool_api::{error, PoolStatus, ReadyTransactions}; -use serde::Serialize; +use sc_transaction_pool_api::{error, PoolStatus, ReadyTransactions, TransactionPriority}; use sp_blockchain::HashAndNumber; use sp_runtime::{ - traits::{self, SaturatedConversion}, + traits::SaturatedConversion, transaction_validity::{TransactionTag as Tag, ValidTransaction}, }; use std::time::Instant; use super::{ base_pool::{self as base, PruneStatus}, - listener::Listener, pool::{ BlockHash, ChainApi, EventStream, ExtrinsicFor, ExtrinsicHash, Options, TransactionFor, }, @@ -79,12 +76,23 @@ impl ValidatedTransaction { valid_till: at.saturated_into::().saturating_add(validity.longevity), }) } + + /// Returns priority for valid transaction, None if transaction is not valid. + pub fn priority(&self) -> Option { + match self { + ValidatedTransaction::Valid(base::Transaction { priority, .. }) => Some(*priority), + _ => None, + } + } } -/// A type of validated transaction stored in the pool. +/// A type of validated transaction stored in the validated pool. pub type ValidatedTransactionFor = ValidatedTransaction, ExtrinsicFor, ::Error>; +/// A type alias representing ValidatedPool listener for given ChainApi type. +pub type Listener = super::listener::Listener, B>; + /// A closure that returns true if the local node is a validator that can author blocks. #[derive(Clone)] pub struct IsValidator(Arc bool + Send + Sync>>); @@ -101,12 +109,56 @@ impl From bool + Send + Sync>> for IsValidator { } } +/// Represents the result of `submit` or `submit_and_watch` operations. +pub struct BaseSubmitOutcome { + /// The hash of the submitted transaction. + hash: ExtrinsicHash, + /// A transaction watcher. This is `Some` for `submit_and_watch` and `None` for `submit`. + watcher: Option, + + /// The priority of the transaction. Defaults to None if unknown. + priority: Option, +} + +/// Type alias to outcome of submission to `ValidatedPool`. +pub type ValidatedPoolSubmitOutcome = + BaseSubmitOutcome, ExtrinsicHash>>; + +impl BaseSubmitOutcome { + /// Creates a new instance with given hash and priority. + pub fn new(hash: ExtrinsicHash, priority: Option) -> Self { + Self { hash, priority, watcher: None } + } + + /// Sets the transaction watcher. + pub fn with_watcher(mut self, watcher: W) -> Self { + self.watcher = Some(watcher); + self + } + + /// Provides priority of submitted transaction. + pub fn priority(&self) -> Option { + self.priority + } + + /// Provides hash of submitted transaction. + pub fn hash(&self) -> ExtrinsicHash { + self.hash + } + + /// Provides a watcher. Should only be called on outcomes of `submit_and_watch`. Otherwise will + /// panic (that would mean logical error in program). + pub fn expect_watcher(&mut self) -> W { + self.watcher.take().expect("watcher was set in submit_and_watch. qed") + } +} + /// Pool that deals with validated transactions. pub struct ValidatedPool { api: Arc, is_validator: IsValidator, options: Options, - listener: RwLock, B>>, + listener: RwLock>, pub(crate) pool: RwLock, ExtrinsicFor>>, import_notification_sinks: Mutex>>>, rotator: PoolRotator>, @@ -200,7 +252,7 @@ impl ValidatedPool { pub fn submit( &self, txs: impl IntoIterator>, - ) -> Vec, B::Error>> { + ) -> Vec, B::Error>> { let results = txs .into_iter() .map(|validated_tx| self.submit_one(validated_tx)) @@ -216,7 +268,7 @@ impl ValidatedPool { results .into_iter() .map(|res| match res { - Ok(ref hash) if removed.contains(hash) => + Ok(outcome) if removed.contains(&outcome.hash) => Err(error::Error::ImmediatelyDropped.into()), other => other, }) @@ -224,9 +276,13 @@ impl ValidatedPool { } /// Submit single pre-validated transaction to the pool. - fn submit_one(&self, tx: ValidatedTransactionFor) -> Result, B::Error> { + fn submit_one( + &self, + tx: ValidatedTransactionFor, + ) -> Result, B::Error> { match tx { ValidatedTransaction::Valid(tx) => { + let priority = tx.priority; log::trace!(target: LOG_TARGET, "[{:?}] ValidatedPool::submit_one", tx.hash); if !tx.propagate && !(self.is_validator.0)() { return Err(error::Error::Unactionable.into()) @@ -254,7 +310,7 @@ impl ValidatedPool { let mut listener = self.listener.write(); fire_events(&mut *listener, &imported); - Ok(*imported.hash()) + Ok(ValidatedPoolSubmitOutcome::new(*imported.hash(), Some(priority))) }, ValidatedTransaction::Invalid(hash, err) => { log::trace!(target: LOG_TARGET, "[{:?}] ValidatedPool::submit_one invalid: {:?}", hash, err); @@ -305,7 +361,7 @@ impl ValidatedPool { // run notifications let mut listener = self.listener.write(); for h in &removed { - listener.limit_enforced(h); + listener.limits_enforced(h); } removed @@ -318,7 +374,7 @@ impl ValidatedPool { pub fn submit_and_watch( &self, tx: ValidatedTransactionFor, - ) -> Result, ExtrinsicHash>, B::Error> { + ) -> Result, B::Error> { match tx { ValidatedTransaction::Valid(tx) => { let hash = self.api.hash_and_length(&tx.data).0; @@ -326,7 +382,7 @@ impl ValidatedPool { self.submit(std::iter::once(ValidatedTransaction::Valid(tx))) .pop() .expect("One extrinsic passed; one result returned; qed") - .map(|_| watcher) + .map(|outcome| outcome.with_watcher(watcher)) }, ValidatedTransaction::Invalid(hash, err) => { self.rotator.ban(&Instant::now(), std::iter::once(hash)); @@ -711,11 +767,42 @@ impl ValidatedPool { listener.future(&f.hash); }); } + + /// Removes a transaction subtree from the pool, starting from the given transaction hash. + /// + /// This function traverses the dependency graph of transactions and removes the specified + /// transaction along with all its descendant transactions from the pool. + /// + /// A `listener_action` callback function is invoked for every transaction that is removed, + /// providing a reference to the pool's listener and the hash of the removed transaction. This + /// allows to trigger the required events. + /// + /// Returns a vector containing the hashes of all removed transactions, including the root + /// transaction specified by `tx_hash`. + pub fn remove_subtree( + &self, + tx_hash: ExtrinsicHash, + listener_action: F, + ) -> Vec> + where + F: Fn(&mut Listener, ExtrinsicHash), + { + self.pool + .write() + .remove_subtree(&[tx_hash]) + .into_iter() + .map(|tx| { + let removed_tx_hash = tx.hash; + let mut listener = self.listener.write(); + listener_action(&mut *listener, removed_tx_hash); + removed_tx_hash + }) + .collect::>() + } } -fn fire_events(listener: &mut Listener, imported: &base::Imported) +fn fire_events(listener: &mut Listener, imported: &base::Imported, Ex>) where - H: hash::Hash + Eq + traits::Member + Serialize, B: ChainApi, { match *imported { diff --git a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs index caa09585b28b..2a691ae35eaf 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/revalidation.rs @@ -405,7 +405,8 @@ mod tests { TimedTransactionSource::new_external(false), uxt.clone().into(), )) - .expect("Should be valid"); + .expect("Should be valid") + .hash(); block_on(queue.revalidate_later(han_of_block0.hash, vec![uxt_hash])); @@ -448,7 +449,7 @@ mod tests { vec![(source.clone(), uxt0.into()), (source, uxt1.into())], )) .into_iter() - .map(|r| r.expect("Should be valid")) + .map(|r| r.expect("Should be valid").hash()) .collect::>(); assert_eq!(api.validation_requests().len(), 2); diff --git a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs index 2b32704945c7..3598f9dbc2af 100644 --- a/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs +++ b/substrate/client/transaction-pool/src/single_state_txpool/single_state_txpool.rs @@ -274,7 +274,12 @@ where let number = self.api.resolve_block_number(at); let at = HashAndNumber { hash: at, number: number? }; - Ok(pool.submit_at(&at, xts).await) + Ok(pool + .submit_at(&at, xts) + .await + .into_iter() + .map(|result| result.map(|outcome| outcome.hash())) + .collect()) } async fn submit_one( @@ -292,6 +297,7 @@ where let at = HashAndNumber { hash: at, number: number? }; pool.submit_one(&at, TimedTransactionSource::from_transaction_source(source, false), xt) .await + .map(|outcome| outcome.hash()) } async fn submit_and_watch( @@ -308,15 +314,13 @@ where let number = self.api.resolve_block_number(at); let at = HashAndNumber { hash: at, number: number? }; - let watcher = pool - .submit_and_watch( - &at, - TimedTransactionSource::from_transaction_source(source, false), - xt, - ) - .await?; - - Ok(watcher.into_stream().boxed()) + pool.submit_and_watch( + &at, + TimedTransactionSource::from_transaction_source(source, false), + xt, + ) + .await + .map(|mut outcome| outcome.expect_watcher().into_stream().boxed()) } fn remove_invalid(&self, hashes: &[TxHash]) -> Vec> { @@ -484,7 +488,11 @@ where validity, ); - self.pool.validated_pool().submit(vec![validated]).remove(0) + self.pool + .validated_pool() + .submit(vec![validated]) + .remove(0) + .map(|outcome| outcome.hash()) } } diff --git a/substrate/client/transaction-pool/tests/fatp_common/mod.rs b/substrate/client/transaction-pool/tests/fatp_common/mod.rs index aaffebc0db0a..530c25caf88e 100644 --- a/substrate/client/transaction-pool/tests/fatp_common/mod.rs +++ b/substrate/client/transaction-pool/tests/fatp_common/mod.rs @@ -192,12 +192,9 @@ macro_rules! assert_ready_iterator { let output: Vec<_> = ready_iterator.collect(); log::debug!(target:LOG_TARGET, "expected: {:#?}", expected); log::debug!(target:LOG_TARGET, "output: {:#?}", output); + let output = output.into_iter().map(|t|t.hash).collect::>(); assert_eq!(expected.len(), output.len()); - assert!( - output.iter().zip(expected.iter()).all(|(o,e)| { - o.hash == *e - }) - ); + assert_eq!(output,expected); }}; } @@ -215,6 +212,18 @@ macro_rules! assert_future_iterator { }}; } +#[macro_export] +macro_rules! assert_watcher_stream { + ($stream:ident, [$( $event:expr ),*]) => {{ + let expected = vec![ $($event),*]; + log::debug!(target:LOG_TARGET, "expected: {:#?} {}, block now:", expected, expected.len()); + let output = futures::executor::block_on_stream($stream).take(expected.len()).collect::>(); + log::debug!(target:LOG_TARGET, "output: {:#?}", output); + assert_eq!(expected.len(), output.len()); + assert_eq!(output, expected); + }}; +} + pub const SOURCE: TransactionSource = TransactionSource::External; #[cfg(test)] diff --git a/substrate/client/transaction-pool/tests/fatp_prios.rs b/substrate/client/transaction-pool/tests/fatp_prios.rs index 4ed9b4503861..af5e7e8c5a6a 100644 --- a/substrate/client/transaction-pool/tests/fatp_prios.rs +++ b/substrate/client/transaction-pool/tests/fatp_prios.rs @@ -20,13 +20,15 @@ pub mod fatp_common; -use fatp_common::{new_best_block_event, TestPoolBuilder, LOG_TARGET, SOURCE}; +use fatp_common::{invalid_hash, new_best_block_event, TestPoolBuilder, LOG_TARGET, SOURCE}; use futures::{executor::block_on, FutureExt}; use sc_transaction_pool::ChainApi; -use sc_transaction_pool_api::{MaintainedTransactionPool, TransactionPool, TransactionStatus}; +use sc_transaction_pool_api::{ + error::Error as TxPoolError, LocalTransactionPool, MaintainedTransactionPool, TransactionPool, + TransactionStatus, +}; use substrate_test_runtime_client::Sr25519Keyring::*; use substrate_test_runtime_transaction_pool::uxt; - #[test] fn fatp_prio_ready_higher_evicts_lower() { sp_tracing::try_init_simple(); @@ -247,3 +249,312 @@ fn fatp_prio_watcher_future_lower_prio_gets_dropped_from_all_views() { assert_ready_iterator!(header01.hash(), pool, [xt2, xt1]); assert_ready_iterator!(header02.hash(), pool, [xt2, xt1]); } + +#[test] +fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + api.set_nonce(api.genesis_hash(), Eve.into(), 600); + api.set_nonce(api.genesis_hash(), Ferdie.into(), 700); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + + let xt3 = uxt(Dave, 500); + + let xt4 = uxt(Eve, 600); + let xt5 = uxt(Ferdie, 700); + + api.set_priority(&xt0, 1); + api.set_priority(&xt1, 2); + api.set_priority(&xt2, 3); + api.set_priority(&xt3, 4); + + api.set_priority(&xt4, 5); + api.set_priority(&xt5, 6); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 2); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let _xt2_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let _xt3_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 4); + + let header03 = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash()))); + + let _xt4_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + let _xt5_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).unwrap(); + + assert_pool_status!(header03.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 4); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + + assert_ready_iterator!(header01.hash(), pool, []); + assert_ready_iterator!(header02.hash(), pool, [xt3, xt2]); + assert_ready_iterator!(header03.hash(), pool, [xt5, xt4]); +} + +#[test] +fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted_with_subtree() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(4).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + let xt3 = uxt(Bob, 300); + let xt4 = uxt(Charlie, 400); + + api.set_priority(&xt0, 1); + api.set_priority(&xt1, 3); + api.set_priority(&xt2, 3); + api.set_priority(&xt3, 2); + api.set_priority(&xt4, 2); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_ready_iterator!(header01.hash(), pool, [xt3, xt0, xt1, xt2]); + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_eq!(pool.mempool_len().1, 4); + + let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_ready_iterator!(header01.hash(), pool, [xt3, xt4]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted_with_subtree2() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(4).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Alice, 201); + let xt2 = uxt(Alice, 202); + let xt3 = uxt(Bob, 300); + let xt4 = uxt(Charlie, 400); + + api.set_priority(&xt0, 1); + api.set_priority(&xt1, 3); + api.set_priority(&xt2, 3); + api.set_priority(&xt3, 2); + api.set_priority(&xt4, 2); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_ready_iterator!(header01.hash(), pool, [xt3, xt0, xt1, xt2]); + assert_pool_status!(header01.hash(), &pool, 4, 0); + assert_eq!(pool.mempool_len().1, 4); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap(); + assert_ready_iterator!(header01.hash(), pool, [xt3]); + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_ready_iterator!(header02.hash(), pool, [xt3, xt4]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_prios_watcher_full_mempool_lower_prio_gets_rejected() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(2).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 2); + api.set_priority(&xt2, 2); + api.set_priority(&xt3, 1); + + let _xt0_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let _xt1_watcher = + block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 2); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().1, 2); + + assert_ready_iterator!(header01.hash(), pool, [xt0, xt1]); + assert_ready_iterator!(header02.hash(), pool, [xt0, xt1]); + + let result2 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).map(|_| ()); + assert!(matches!(result2.as_ref().unwrap_err().0, TxPoolError::ImmediatelyDropped)); + let result3 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).map(|_| ()); + assert!(matches!(result3.as_ref().unwrap_err().0, TxPoolError::ImmediatelyDropped)); +} + +#[test] +fn fatp_prios_watcher_full_mempool_does_not_keep_dropped_transaction() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + let xt3 = uxt(Dave, 500); + + api.set_priority(&xt0, 2); + api.set_priority(&xt1, 2); + api.set_priority(&xt2, 2); + api.set_priority(&xt3, 2); + + let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap(); + let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap(); + let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap(); + let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]); + + assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]); + assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]); + assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]); +} + +#[test] +fn fatp_prios_submit_local_full_mempool_higher_prio_is_accepted() { + sp_tracing::try_init_simple(); + + let builder = TestPoolBuilder::new(); + let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build(); + api.set_nonce(api.genesis_hash(), Bob.into(), 300); + api.set_nonce(api.genesis_hash(), Charlie.into(), 400); + api.set_nonce(api.genesis_hash(), Dave.into(), 500); + api.set_nonce(api.genesis_hash(), Eve.into(), 600); + api.set_nonce(api.genesis_hash(), Ferdie.into(), 700); + + let header01 = api.push_block(1, vec![], true); + let event = new_best_block_event(&pool, None, header01.hash()); + block_on(pool.maintain(event)); + + let xt0 = uxt(Alice, 200); + let xt1 = uxt(Bob, 300); + let xt2 = uxt(Charlie, 400); + + let xt3 = uxt(Dave, 500); + + let xt4 = uxt(Eve, 600); + let xt5 = uxt(Ferdie, 700); + + api.set_priority(&xt0, 1); + api.set_priority(&xt1, 2); + api.set_priority(&xt2, 3); + api.set_priority(&xt3, 4); + + api.set_priority(&xt4, 5); + api.set_priority(&xt5, 6); + pool.submit_local(invalid_hash(), xt0.clone()).unwrap(); + pool.submit_local(invalid_hash(), xt1.clone()).unwrap(); + + assert_pool_status!(header01.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().0, 2); + + let header02 = api.push_block_with_parent(header01.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash()))); + + pool.submit_local(invalid_hash(), xt2.clone()).unwrap(); + pool.submit_local(invalid_hash(), xt3.clone()).unwrap(); + + assert_pool_status!(header02.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().0, 4); + + let header03 = api.push_block_with_parent(header02.hash(), vec![], true); + block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash()))); + + pool.submit_local(invalid_hash(), xt4.clone()).unwrap(); + pool.submit_local(invalid_hash(), xt5.clone()).unwrap(); + + assert_pool_status!(header03.hash(), &pool, 2, 0); + assert_eq!(pool.mempool_len().0, 4); + + assert_ready_iterator!(header01.hash(), pool, []); + assert_ready_iterator!(header02.hash(), pool, [xt3, xt2]); + assert_ready_iterator!(header03.hash(), pool, [xt5, xt4]); +} diff --git a/substrate/client/transaction-pool/tests/pool.rs b/substrate/client/transaction-pool/tests/pool.rs index de35726435f0..c70f45483314 100644 --- a/substrate/client/transaction-pool/tests/pool.rs +++ b/substrate/client/transaction-pool/tests/pool.rs @@ -158,6 +158,7 @@ fn prune_tags_should_work() { let (pool, api) = pool(); let hash209 = block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 209).into())) + .map(|o| o.hash()) .unwrap(); block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt(Alice, 210).into())) .unwrap(); @@ -184,10 +185,13 @@ fn prune_tags_should_work() { fn should_ban_invalid_transactions() { let (pool, api) = pool(); let uxt = Arc::from(uxt(Alice, 209)); - let hash = - block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())).unwrap(); + let hash = block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())) + .unwrap() + .hash(); pool.validated_pool().remove_invalid(&[hash]); - block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())) + .map(|_| ()) + .unwrap_err(); // when let pending: Vec<_> = pool @@ -198,7 +202,9 @@ fn should_ban_invalid_transactions() { assert_eq!(pending, Vec::::new()); // then - block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())).unwrap_err(); + block_on(pool.submit_one(&api.expect_hash_and_number(0), TSOURCE, uxt.clone())) + .map(|_| ()) + .unwrap_err(); } #[test] diff --git a/substrate/test-utils/runtime/transaction-pool/src/lib.rs b/substrate/test-utils/runtime/transaction-pool/src/lib.rs index 93e5855eefc6..f88694fb1071 100644 --- a/substrate/test-utils/runtime/transaction-pool/src/lib.rs +++ b/substrate/test-utils/runtime/transaction-pool/src/lib.rs @@ -352,9 +352,18 @@ impl ChainApi for TestApi { fn validate_transaction( &self, at: ::Hash, - _source: TransactionSource, + source: TransactionSource, uxt: Arc<::Extrinsic>, ) -> Self::ValidationFuture { + ready(self.validate_transaction_blocking(at, source, uxt)) + } + + fn validate_transaction_blocking( + &self, + at: ::Hash, + _source: TransactionSource, + uxt: Arc<::Extrinsic>, + ) -> Result { let uxt = (*uxt).clone(); self.validation_requests.write().push(uxt.clone()); let block_number; @@ -374,16 +383,12 @@ impl ChainApi for TestApi { // the transaction. (This is not required for this test function, but in real // environment it would fail because of this). if !found_best { - return ready(Ok(Err(TransactionValidityError::Invalid( - InvalidTransaction::Custom(1), - )))) + return Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(1)))) } }, Ok(None) => - return ready(Ok(Err(TransactionValidityError::Invalid( - InvalidTransaction::Custom(2), - )))), - Err(e) => return ready(Err(e)), + return Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(2)))), + Err(e) => return Err(e), } let (requires, provides) = if let Ok(transfer) = TransferData::try_from(&uxt) { @@ -423,7 +428,7 @@ impl ChainApi for TestApi { if self.enable_stale_check && transfer.nonce < chain_nonce { log::info!("test_api::validate_transaction: invalid_transaction(stale)...."); - return ready(Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Stale)))) + return Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))) } (requires, provides) @@ -433,7 +438,7 @@ impl ChainApi for TestApi { if self.chain.read().invalid_hashes.contains(&self.hash_and_length(&uxt).0) { log::info!("test_api::validate_transaction: invalid_transaction...."); - return ready(Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(0))))) + return Ok(Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(0)))) } let priority = self.chain.read().priorities.get(&self.hash_and_length(&uxt).0).cloned(); @@ -447,16 +452,7 @@ impl ChainApi for TestApi { (self.valid_modifier.read())(&mut validity); - ready(Ok(Ok(validity))) - } - - fn validate_transaction_blocking( - &self, - _at: ::Hash, - _source: TransactionSource, - _uxt: Arc<::Extrinsic>, - ) -> Result { - unimplemented!(); + Ok(Ok(validity)) } fn block_id_to_number( From 105c5b94f5d3bf394a3ddf1d10ab0932ce93181b Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:30:05 +0200 Subject: [PATCH 254/340] litep2p: Sufix litep2p to the identify agent version for visibility (#7133) This PR adds the `(litep2p)` suffix to the agent version (user agent) of the identify protocol. The change is needed to gain visibility into network backends and determine exactly the number of validators that are running litep2p. Using tools like subp2p-explorer, we can determine if the validators are running litep2p nodes. This reflects on the identify protocol: ``` info=Identify { protocol_version: Some("/substrate/1.0"), agent_version: Some("polkadot-parachain/v1.17.0-967989c5d94 (kusama-node-name-01) (litep2p)") ... } ``` cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile --- prdoc/pr_7133.prdoc | 15 +++++++++++++++ substrate/client/network/src/litep2p/discovery.rs | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_7133.prdoc diff --git a/prdoc/pr_7133.prdoc b/prdoc/pr_7133.prdoc new file mode 100644 index 000000000000..ca0d2bb0bd48 --- /dev/null +++ b/prdoc/pr_7133.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Sufix litep2p to the identify agent version for visibility + +doc: + - audience: [Node Dev, Node Operator] + description: | + This PR adds the `(litep2p)` suffix to the agent version (user agent) of the identify protocol. + The change is needed to gain visibility into network backends and determine exactly the number of validators that are running litep2p. + Using tools like subp2p-explorer, we can determine if the validators are running litep2p nodes. + +crates: +- name: sc-network + bump: patch diff --git a/substrate/client/network/src/litep2p/discovery.rs b/substrate/client/network/src/litep2p/discovery.rs index 2bea2e5a80dc..b55df374f60e 100644 --- a/substrate/client/network/src/litep2p/discovery.rs +++ b/substrate/client/network/src/litep2p/discovery.rs @@ -254,7 +254,7 @@ impl Discovery { _peerstore_handle: Arc, ) -> (Self, PingConfig, IdentifyConfig, KademliaConfig, Option) { let (ping_config, ping_event_stream) = PingConfig::default(); - let user_agent = format!("{} ({})", config.client_version, config.node_name); + let user_agent = format!("{} ({}) (litep2p)", config.client_version, config.node_name); let (identify_config, identify_event_stream) = IdentifyConfig::new("/substrate/1.0".to_string(), Some(user_agent)); From 023763da2043333c3524bd7f12ac6c7b2d084b39 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Tue, 14 Jan 2025 14:41:24 +0100 Subject: [PATCH 255/340] [pallet-revive-eth-rpc] persist eth transaction hash (#6836) Add an option to persist EVM transaction hash to a SQL db. This should make it possible to run a full archive ETH RPC node (assuming the substrate node is also a full archive node) Some queries such as eth_getTransactionByHash, eth_getBlockTransactionCountByHash, and other need to work with a transaction hash indexes, which are not stored in Substrate and need to be stored by the eth-rpc proxy. The refactoring break down the Client into a `BlockInfoProvider` and `ReceiptProvider` - BlockInfoProvider does not need any persistence data, as we can fetch all block info from the source substrate chain - ReceiptProvider comes in two flavor, - An in memory cache implementation - This is the one we had so far. - A DB implementation - This one persist rows with the block_hash, the transaction_index and the transaction_hash, so that we can later fetch the block and extrinsic for that receipt and reconstruct the ReceiptInfo object. This PR also adds a new binary eth-indexer, that iterate past and new blocks and write the receipt hashes to the DB using the new ReceiptProvider. --------- Co-authored-by: GitHub Action Co-authored-by: command-bot <> --- .cargo/config.toml | 1 + .github/workflows/build-publish-eth-rpc.yml | 37 +- Cargo.lock | 476 +++++++++++++-- prdoc/pr_6836.prdoc | 17 + ...c1135227c1150f2c5083d1c7c6086b717ada0.json | 12 + ...68c427245f94b80d37ec3aef04cd96fb36298.json | 20 + ...332be50096d4e37be04ed8b6f46ac5c242043.json | 26 + substrate/frame/revive/rpc/Cargo.toml | 11 + .../rpc/dockerfiles/eth-indexer/Dockerfile | 28 + .../rpc/{ => dockerfiles/eth-rpc}/Dockerfile | 0 .../frame/revive/rpc/examples/js/bun.lockb | Bin 40649 -> 46862 bytes .../frame/revive/rpc/examples/js/package.json | 14 +- .../rpc/examples/js/src/build-contracts.ts | 7 +- .../rpc/examples/js/src/geth-diff.test.ts | 66 +- .../frame/revive/rpc/examples/js/src/lib.ts | 1 - .../revive/rpc/examples/js/src/piggy-bank.ts | 4 +- .../revive/rpc/examples/js/src/spammer.ts | 104 ++++ .../js/src/{geth-diff-setup.ts => util.ts} | 74 +-- .../rpc/examples/westend_local_network.toml | 8 +- ...241205165418_create_transaction_hashes.sql | 15 + .../revive/rpc/src/block_info_provider.rs | 250 ++++++++ substrate/frame/revive/rpc/src/cli.rs | 61 +- substrate/frame/revive/rpc/src/client.rs | 571 ++++++++---------- substrate/frame/revive/rpc/src/eth-indexer.rs | 88 +++ substrate/frame/revive/rpc/src/lib.rs | 27 +- .../frame/revive/rpc/src/receipt_provider.rs | 240 ++++++++ .../revive/rpc/src/receipt_provider/cache.rs | 148 +++++ .../revive/rpc/src/receipt_provider/db.rs | 216 +++++++ substrate/frame/revive/rpc/src/rpc_health.rs | 9 + .../frame/revive/rpc/src/rpc_methods_gen.rs | 4 + .../frame/revive/src/evm/api/rpc_types.rs | 12 +- .../frame/revive/src/evm/api/rpc_types_gen.rs | 10 +- substrate/frame/revive/src/wasm/mod.rs | 5 +- 33 files changed, 2090 insertions(+), 472 deletions(-) create mode 100644 prdoc/pr_6836.prdoc create mode 100644 substrate/frame/revive/rpc/.sqlx/query-027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0.json create mode 100644 substrate/frame/revive/rpc/.sqlx/query-2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298.json create mode 100644 substrate/frame/revive/rpc/.sqlx/query-29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043.json create mode 100644 substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile rename substrate/frame/revive/rpc/{ => dockerfiles/eth-rpc}/Dockerfile (100%) create mode 100644 substrate/frame/revive/rpc/examples/js/src/spammer.ts rename substrate/frame/revive/rpc/examples/js/src/{geth-diff-setup.ts => util.ts} (62%) create mode 100644 substrate/frame/revive/rpc/migrations/20241205165418_create_transaction_hashes.sql create mode 100644 substrate/frame/revive/rpc/src/block_info_provider.rs create mode 100644 substrate/frame/revive/rpc/src/eth-indexer.rs create mode 100644 substrate/frame/revive/rpc/src/receipt_provider.rs create mode 100644 substrate/frame/revive/rpc/src/receipt_provider/cache.rs create mode 100644 substrate/frame/revive/rpc/src/receipt_provider/db.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 68a0d7b552dc..8573f582e258 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -9,6 +9,7 @@ rustdocflags = [ CC_x86_64_unknown_linux_musl = { value = ".cargo/musl-gcc", force = true, relative = true } CXX_x86_64_unknown_linux_musl = { value = ".cargo/musl-g++", force = true, relative = true } CARGO_WORKSPACE_ROOT_DIR = { value = "", relative = true } +SQLX_OFFLINE = "true" [net] retry = 5 diff --git a/.github/workflows/build-publish-eth-rpc.yml b/.github/workflows/build-publish-eth-rpc.yml index 3aa1624096df..a98b3881a145 100644 --- a/.github/workflows/build-publish-eth-rpc.yml +++ b/.github/workflows/build-publish-eth-rpc.yml @@ -12,7 +12,8 @@ concurrency: cancel-in-progress: true env: - IMAGE_NAME: "docker.io/paritypr/eth-rpc" + ETH_RPC_IMAGE_NAME: "docker.io/paritypr/eth-rpc" + ETH_INDEXER_IMAGE_NAME: "docker.io/paritypr/eth-indexer" jobs: set-variables: @@ -34,7 +35,7 @@ jobs: echo "set VERSION=${VERSION}" build_docker: - name: Build docker image + name: Build docker images runs-on: parity-large needs: [set-variables] env: @@ -43,17 +44,26 @@ jobs: - name: Check out the repo uses: actions/checkout@v4 - - name: Build Docker image + - name: Build eth-rpc Docker image uses: docker/build-push-action@v6 with: context: . - file: ./substrate/frame/revive/rpc/Dockerfile + file: ./substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile push: false tags: | - ${{ env.IMAGE_NAME }}:${{ env.VERSION }} + ${{ env.ETH_RPC_IMAGE_NAME }}:${{ env.VERSION }} + + - name: Build eth-indexer Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile + push: false + tags: | + ${{ env.ETH_INDEXER_IMAGE_NAME }}:${{ env.VERSION }} build_push_docker: - name: Build and push docker image + name: Build and push docker images runs-on: parity-large if: github.ref == 'refs/heads/master' needs: [set-variables] @@ -69,11 +79,20 @@ jobs: username: ${{ secrets.PARITYPR_DOCKERHUB_USERNAME }} password: ${{ secrets.PARITYPR_DOCKERHUB_PASSWORD }} - - name: Build Docker image + - name: Build eth-rpc Docker image + uses: docker/build-push-action@v6 + with: + context: . + file: ./substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile + push: true + tags: | + ${{ env.ETH_RPC_IMAGE_NAME }}:${{ env.VERSION }} + + - name: Build eth-indexer Docker image uses: docker/build-push-action@v6 with: context: . - file: ./substrate/frame/revive/rpc/Dockerfile + file: ./substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile push: true tags: | - ${{ env.IMAGE_NAME }}:${{ env.VERSION }} + ${{ env.ETH_INDEXER_IMAGE_NAME }}:${{ env.VERSION }} diff --git a/Cargo.lock b/Cargo.lock index cfb805fbe847..3eab84d5ed16 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1396,7 +1396,7 @@ dependencies = [ "futures-lite 2.3.0", "parking", "polling 3.4.0", - "rustix 0.38.21", + "rustix 0.38.42", "slab", "tracing", "windows-sys 0.52.0", @@ -1478,7 +1478,7 @@ dependencies = [ "cfg-if", "event-listener 5.3.1", "futures-lite 2.3.0", - "rustix 0.38.21", + "rustix 0.38.42", "tracing", ] @@ -1494,7 +1494,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.21", + "rustix 0.38.42", "signal-hook-registry", "slab", "windows-sys 0.52.0", @@ -1592,6 +1592,15 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + [[package]] name = "atomic-take" version = "1.1.0" @@ -1880,6 +1889,9 @@ name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] [[package]] name = "bitvec" @@ -4391,6 +4403,21 @@ dependencies = [ "wasmtime-types", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.3.2" @@ -5945,6 +5972,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" dependencies = [ "const-oid", + "pem-rfc7468", "zeroize", ] @@ -6226,6 +6254,12 @@ dependencies = [ "litrs", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast" version = "0.11.0" @@ -6351,6 +6385,9 @@ name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +dependencies = [ + "serde", +] [[package]] name = "elliptic-curve" @@ -6559,23 +6596,23 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.2" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "etcetera" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ - "cc", - "libc", + "cfg-if", + "home", + "windows-sys 0.48.0", ] [[package]] @@ -6772,9 +6809,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fastrlp" @@ -6989,6 +7026,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -7837,7 +7885,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f9df8a11882c4e3335eb2d18a0137c505d9ca927470b0cac9c6f0ae07d28f7" dependencies = [ - "rustix 0.38.21", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -7906,6 +7954,17 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.12.3", +] + [[package]] name = "futures-io" version = "0.3.31" @@ -7933,7 +7992,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.1.0", + "fastrand 2.3.0", "futures-core", "futures-io", "parking", @@ -8369,6 +8428,15 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "heck" version = "0.3.3" @@ -9100,7 +9168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi 0.3.9", - "rustix 0.38.21", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -9701,6 +9769,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] [[package]] name = "lazycell" @@ -9716,9 +9787,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libflate" @@ -10264,6 +10335,17 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.12" @@ -10323,9 +10405,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lioness" @@ -10607,6 +10689,16 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.7.4" @@ -10782,7 +10874,7 @@ dependencies = [ "c2-chacha", "curve25519-dalek 4.1.3", "either", - "hashlink", + "hashlink 0.8.4", "lioness", "log", "parking_lot 0.12.3", @@ -11453,6 +11545,23 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-complex" version = "0.4.4" @@ -14809,9 +14918,11 @@ dependencies = [ "sc-rpc", "sc-rpc-api", "sc-service", + "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", "sp-weights 27.0.0", + "sqlx", "static_init", "substrate-cli-test-utils", "substrate-prometheus-endpoint", @@ -16516,6 +16627,15 @@ dependencies = [ "serde", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "penpal-emulated-chain" version = "0.0.0" @@ -16890,6 +17010,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + [[package]] name = "pkcs8" version = "0.10.2" @@ -20030,7 +20161,7 @@ dependencies = [ "cfg-if", "concurrent-queue", "pin-project-lite", - "rustix 0.38.21", + "rustix 0.38.42", "tracing", "windows-sys 0.52.0", ] @@ -20338,7 +20469,7 @@ dependencies = [ "hex", "lazy_static", "procfs-core", - "rustix 0.38.21", + "rustix 0.38.42", ] [[package]] @@ -20871,11 +21002,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] @@ -21533,6 +21664,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "rsa" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af6c4b23d99685a1408194da11270ef8e9809aff951cc70ec9b17350b087e474" +dependencies = [ + "const-oid", + "digest 0.10.7", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle 2.5.0", + "zeroize", +] + [[package]] name = "rstest" version = "0.18.2" @@ -21707,15 +21858,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", - "linux-raw-sys 0.4.10", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.14", + "windows-sys 0.59.0", ] [[package]] @@ -24439,6 +24590,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "smol" @@ -27690,6 +27844,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spinners" @@ -27712,6 +27869,210 @@ dependencies = [ "der", ] +[[package]] +name = "sqlformat" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" +dependencies = [ + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +dependencies = [ + "atoi", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener 5.3.1", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.14.5", + "hashlink 0.9.1", + "hex", + "indexmap 2.7.0", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +dependencies = [ + "proc-macro2 1.0.86", + "quote 1.0.37", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.87", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +dependencies = [ + "dotenvy", + "either", + "heck 0.5.0", + "hex", + "once_cell", + "proc-macro2 1.0.86", + "quote 1.0.37", + "serde", + "serde_json", + "sha2 0.10.8", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.87", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "bytes", + "crc", + "digest 0.10.7", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array 0.14.7", + "hex", + "hkdf", + "hmac 0.12.1", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.6.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac 0.12.1", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2 0.10.8", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", +] + [[package]] name = "ss58-registry" version = "1.43.0" @@ -28039,6 +28400,17 @@ dependencies = [ "serde", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.8.0" @@ -29004,15 +29376,15 @@ checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tempfile" -version = "3.8.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", - "fastrand 2.1.0", - "redox_syscall 0.4.1", - "rustix 0.38.21", - "windows-sys 0.48.0", + "fastrand 2.3.0", + "once_cell", + "rustix 0.38.42", + "windows-sys 0.59.0", ] [[package]] @@ -29041,7 +29413,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.21", + "rustix 0.38.42", "windows-sys 0.48.0", ] @@ -29992,6 +30364,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + [[package]] name = "unicode-segmentation" version = "1.11.0" @@ -30016,6 +30394,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + [[package]] name = "universal-hash" version = "0.5.1" @@ -30259,6 +30643,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.95" @@ -30998,6 +31388,16 @@ dependencies = [ "westend-emulated-chain", ] +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall 0.5.8", + "wasite", +] + [[package]] name = "wide" version = "0.7.11" diff --git a/prdoc/pr_6836.prdoc b/prdoc/pr_6836.prdoc new file mode 100644 index 000000000000..1de081bbaa40 --- /dev/null +++ b/prdoc/pr_6836.prdoc @@ -0,0 +1,17 @@ +title: '[pallet-revive-eth-rpc] persist eth transaction hash' +doc: +- audience: Runtime Dev + description: |- + Add an option to persist EVM transaction hash to a SQL db. + This make it possible to run a full archive ETH RPC node (assuming the substrate node is also a full archive node) + + Some queries such as eth_getTransactionByHash, eth_getBlockTransactionCountByHash, and other need to work with a transaction hash index, which is not available in Substrate and need to be stored by the eth-rpc proxy. + + The refactoring break down the Client into a `BlockInfoProvider` and `ReceiptProvider` + - BlockInfoProvider does not need any persistence data, as we can fetch all block info from the source substrate chain + - ReceiptProvider comes in two flavor, + - An in memory cache implementation - This is the one we had so far. + - A DB implementation - This one persist rows with the block_hash, the transaction_index and the transaction_hash, so that we can later fetch the block and extrinsic for that receipt and reconstruct the ReceiptInfo object. +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/.sqlx/query-027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0.json b/substrate/frame/revive/rpc/.sqlx/query-027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0.json new file mode 100644 index 000000000000..016276144901 --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "\n\t\t\t\tINSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index)\n\t\t\t\tVALUES ($1, $2, $3)\n\n\t\t\t\tON CONFLICT(transaction_hash) DO UPDATE SET\n\t\t\t\tblock_hash = EXCLUDED.block_hash,\n\t\t\t\ttransaction_index = EXCLUDED.transaction_index\n\t\t\t\t", + "describe": { + "columns": [], + "parameters": { + "Right": 3 + }, + "nullable": [] + }, + "hash": "027a434a38822c2ba4439e8f9f9c1135227c1150f2c5083d1c7c6086b717ada0" +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298.json b/substrate/frame/revive/rpc/.sqlx/query-2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298.json new file mode 100644 index 000000000000..507564cd05c5 --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298.json @@ -0,0 +1,20 @@ +{ + "db_name": "SQLite", + "query": "\n SELECT COUNT(*) as count\n FROM transaction_hashes\n WHERE block_hash = $1\n ", + "describe": { + "columns": [ + { + "name": "count", + "ordinal": 0, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false + ] + }, + "hash": "2348bd412ca114197996e4395fd68c427245f94b80d37ec3aef04cd96fb36298" +} diff --git a/substrate/frame/revive/rpc/.sqlx/query-29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043.json b/substrate/frame/revive/rpc/.sqlx/query-29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043.json new file mode 100644 index 000000000000..2443035c433d --- /dev/null +++ b/substrate/frame/revive/rpc/.sqlx/query-29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n\t\t\tSELECT block_hash, transaction_index\n\t\t\tFROM transaction_hashes\n\t\t\tWHERE transaction_hash = $1\n\t\t\t", + "describe": { + "columns": [ + { + "name": "block_hash", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "transaction_index", + "ordinal": 1, + "type_info": "Integer" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false + ] + }, + "hash": "29af64347f700919dc2ee12463f332be50096d4e37be04ed8b6f46ac5c242043" +} diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index cfaaa102fc3d..9d822f5ff8e2 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -7,11 +7,16 @@ license = "Apache-2.0" homepage.workspace = true repository.workspace = true description = "An Ethereum JSON-RPC server for pallet-revive." +default-run = "eth-rpc" [[bin]] name = "eth-rpc" path = "src/main.rs" +[[bin]] +name = "eth-indexer" +path = "src/eth-indexer.rs" + [[example]] name = "deploy" path = "examples/rust/deploy.rs" @@ -53,9 +58,15 @@ sc-cli = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } +sp-arithmetic = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true } sp-weights = { workspace = true, default-features = true } +sqlx = { version = "0.8.2", features = [ + "macros", + "runtime-tokio", + "sqlite", +] } subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"] } subxt-signer = { workspace = true, optional = true, features = [ "unstable-eth", diff --git a/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile b/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile new file mode 100644 index 000000000000..77fa846a145c --- /dev/null +++ b/substrate/frame/revive/rpc/dockerfiles/eth-indexer/Dockerfile @@ -0,0 +1,28 @@ +FROM rust AS builder + +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + protobuf-compiler \ + clang libclang-dev + +WORKDIR /polkadot +COPY . /polkadot +RUN rustup component add rust-src +RUN cargo build --locked --profile production -p pallet-revive-eth-rpc --bin eth-indexer + +FROM docker.io/parity/base-bin:latest +COPY --from=builder /polkadot/target/production/eth-indexer /usr/local/bin + +USER root +RUN useradd -m -u 1001 -U -s /bin/sh -d /polkadot polkadot && \ +# unclutter and minimize the attack surface + rm -rf /usr/bin /usr/sbin && \ +# check if executable works in this container + /usr/local/bin/eth-indexer --help + +USER polkadot + +ENTRYPOINT ["/usr/local/bin/eth-indexer"] + +# We call the help by default +CMD ["--help"] diff --git a/substrate/frame/revive/rpc/Dockerfile b/substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile similarity index 100% rename from substrate/frame/revive/rpc/Dockerfile rename to substrate/frame/revive/rpc/dockerfiles/eth-rpc/Dockerfile diff --git a/substrate/frame/revive/rpc/examples/js/bun.lockb b/substrate/frame/revive/rpc/examples/js/bun.lockb index 67df5841e43fba141c7a146a1e4a8958b4c7a84c..39a1d0906b70e1c5cecc7df65976f4db77f41d17 100755 GIT binary patch literal 46862 zcmeHwhgTF$(=XtP$fJOYV!#JX7|Gdy1Qo=HVpfo_$Rf#Iq5+8#1O+5YP;!zSBnjtK z{bTpnvpcZstBCU6d%pV}Tbt>b?yl;tuCA)?sh&Ns&pBPAc8*phJ1dhj?4#3@%@G|F zQ(~URsS=fraVhGgs5Hk67jqLMBO_i+pb_fj4w^CH7MkV`9js<3nF5_asl# zB=tw~;3F=*Mio5C6QMV#9hDIhwHG6^@2-@qdvN-xE@2d_OiB^y43#oTT&W0uHshL` zB_~ALUp@&&wbXDx ztjCjW0(X)sS(T*Hq~Rj^V#MXhk!U2kBo#S^TP@%Yq$D^zf|R+`op(rWms0u2!VTqS z;O|b6c$zK2{9y+&RB1|)8>dlbD3jAPsVSPY7>&k=x`0a>QEEa;LXsSnoT7>~GE!=y z(^ZMFMo+dF8QBN~VMs`0a!O7dk`tD4f@eW@sfFB)6v#xmTrH<)VJX$Wl&V`w<;$sL zIdNZ31S28Q$3sr+lM@zlg6A!bjJ8S``0I`Zr}t8CmU;L+8c3w-;|l6|%z6Ks+@mQjQ*KaPd_~jr z?liwe`(~&OvHh`MJoQj14=Np^js(+tb8hzDoRsCvL)3qa+fr;@`P1kQi*YRcxu z7z_GB1^h0WCbnPXuG^cb`XF~4a_82q^yv!`&`innTs85KG*a5gMnlx&I&v z6dQ@RwcnZ9Av^KTq~;BhO}GY%3|>hKkUV5^jAM z#qR*~%VjglJI=M#q2{WmH1<$Kr6J(X3F}@Hl4 zU(9_j;VH_sdr6(QphQ2M;x7jhr~+eE61{`xH(J_R^VG4eN8F9+E!1usH&H>{1y`y= zZ}T|ch=+n5xX;9YC(rV%>kTgRQgX?L>r{V%dy_cdY>VGD^`%%wM^O>e6_O+Rmb>b1IrS zO}!qbN!$&3x6&fD(106#^Sz$PO(B-l6@ZE*YHB(RJ}9}k*^=A2%Jl@#^IM+Z`}~?Q zcfACabI)!Fi&KOCrLo}>lr}2n3Ci0I{CT$bax!@Gi1%R{^l{@kuDrnA0f)?ZGUyx^ z@^l0>=6Fyu25phv(tG|!&Ghxa7Vh`qkr&r2tN^Ks#t$hTxo|HPZ8J^d>exgK^0)~m z0T1zzKYa#AEzl%}>X@8+^YG-thI>mx9CBjx)=n9w}e@UR(wqECk?_r!53Qbu!g zgv$*YJ5Dn&Y|2cJ(zi=4l*bb<_{$TnppvaTu>U8_M;#C*4cYW5fvZ)hEE*UaWX{iN& z(gzv=1TVP>f{3f7rD(1QJc^W?K7`e_Lxyl*osp5F03enau)bCuWB~C1a6$kOHCKyW zl$8O{S3n395aikLy92>9fN)u+f|!xN3*kHjfg~72!vrN@$gL_Yb)pXtoMkeIs{2+| zf0+#21_&qRPKe4f>|X(r%N+v!JY+fwPLeZL3*a6A_{)6&Nq?J+)CdR?LkpDhqE)1S z738M!dqB7-Qvrs)SXr$q$Pjjd2PHBJ^r9214Kjcm0B}xjfq3}4EucC;@Dvclbie{& z)nZ<50=J{)(yYYd?6*h465}t)1p#ukQUyhqcqRaqBrOB3Gzihkv?wR^&XGF~oQ7TU z1!mcRK?&T*9eD{TK7tZL*@|XhbXOH*0Gx|j0WvMIgj_XO$qEj}1-a61Gt?p=NJNmWjo+1TCxB8C4Pp!gSFUzq$bEPW0RHkY zh<^NT3+OW-NP0rl6kyS(RRy^T+z$v>MH}QNzuN{MkWb>qKW#!P062*j2mpeFzuO0D z1q45t7sNoUHpqSEWnE4gItaZ06`i`!HZ&7bhSZ-!0!P;GMxlJ z3R+@lR~2LkC$MuV%}Vh=P~dl|5DNg3Aund5XaVf7RRy^PGzJKP@<52Ef-P4QWC%O~ z2ulpT+S&qx48ae!jwGRF3J88!O^^Y+0sx5$q7t@ne_Pe&z|OcTi14QXxEv6qN%^M` zc47gtCIGQ(1wbQfg~0QG5V{sRUVxbsvK9dJZmkfwZmkghg)muDC*sx$fQHrzfv46A z!4DI0O;n&afN)_g%)sSqg|HRcWlc@EyH)_y3JB-cM1}UXLU6!Kv^6y$VXXjYXsr;8 z@j}W|kWpw!q*oneC36&S+8(S002Knly|qB#mbJFQXOEH5&9yXv-U7m-wLsurKv1m( zLKt?aqSgX{#sFbWov_6F)hBCdLIwaR*8+f>00ABgf6mY+@eX%Q0H_fV)_s4_PsZs~Ag&O6fV~x>O$1MY^}gRp2^;D1!b~aFAFA3ivLFBK*l!!0|>D?ZQf1 z3i?+89|I-@{i}F>3Q@G*4^i-AAfgE3D)NI6#q(f95mvg2@*#)<-X%oQo-2qVh^xrQ z;!O%ax{Ca3x_ogJy!AkLFRlV^gzo-FS6CiK>AL*?AFqNJsaV&6|4$NP%%}u^#9&hy z{zy;WA|V2agm@x?nJ)gFcYo*I|D^f(zkA;Cf+a7=Q&5_R|Ajw*XKW-x)(5T-zBPE; z)^w2iu5-n~urN1nJi7ZXG_?v|S5;gd$t6#$;6>C-^U3j4q2j!Af&4WtdM^!M;bt&;wM*|lV zxXFu)spC40{u{n63vPyGRJs43`&6suPcB>SQq`95XzQgOZ|;QO+GiSx+{*n&qPd@X zlr()RWxKTjQlWz6px`<`>h!BMLa3E@-N*+GX1#ppWr8#&c<#F>p>OBG% zuafomUQ&K4<)4A$<^a_?T%$t%Y`mS{P;J)X3-IrAbm0$pS2_49HWc87xzk26^Oh+s zG_f2MS8r4C`Bb1Ogl}J!r`^q0z+DkU@8Vp7voqJb(8t3kRQ%N}5(U0^9itvIHFa#` zZ|FRn7+ZMZI#=DceMCk6hiDOwOoeDpE_}Coe7NM0L9c~@B?kp{(@+qP?MM*V(??H^a>)s9L`&h; zc=v81zu9&>h5L{B@&^Ny`lh)e%zfWO8orU`bNB*xt!M*YDR@;6L?(82=^TGQcV9 zC^;yi&j0iJJiI&MCYrmB->35I6<*-d3;W=H`jHE1amRh`@}ugQmxtVV)Jweu3{-f_ z+TQIne@myy0DHI+_P{AIm%HIO*+c#CtE=C-hl@^9AIM*7fLUlpIVdcsEXUYk9~=zl z9pINv?|S1`p&;E~_NJ-z_@#@lf_SZ`q)5Z3T0=kGWW1O~1@ z=gRHdxY7IwckQMj3upTFm}f2nWTgReys=ygicKndLle&1;PyWV@5|07m_2zslWoqQ zZQy~|O0_mT2KFAI;b1tFjv`jVSFp>33NHZpIF*AxI*z(x2tJ%dn@e5cmf6XTFLErn zbg$)6o=ppQ#1#e&5cYr^6g-{)>ha7W8a%)EA^ZVxEMfa8DoF;?!7R9U7SjhVjtYk} z@hlZn#W7ndaP$*Xao{9%+HB&+hq!BexvjJZHmp}?7 zPwfo76cljT7T&8p$-H~?d&1$d)eU}#ZzzwxS@RqXT-xnJ zqc;wpq{d8!FDu+I^VJHT3B>R<)8gwSAH^*j1FXWTm4iY%UQZ8afgg+L8;|pt2b@T! zciU1IT!eYXJti4bvmzRk>8m^3iU&a8N}f*QO74Ch1E12bD(bq5`A|x=+YQ+oiPUZ^;b#?9=*+^DPDIi1%=UdYCjAY-{}kV)(uX|4NqQiZ7{b{O*+q?s@ z6;@!;I;fX2jCKouOy{PXC%r=etuXm?ASgQz&S~BS{XBjOv#w70+WaN89i`?tU&yv; z`$L;Cxyr$DLpfZQ(F+3o8-#P+hASti;#y4ldT8D8EKGWOw~M~|od=sOLfXBh1$%hnx8AsA>cd4; zkzhd!$33||HEKJY-3{={+Xw==BGncGxy3RR%@$lVKqELqu+0RCN-9slY?K`BQT&m zwr||BgFdBU=FZSNS1#Sn9rjS84d$2;?RMP5MO?ALpv&^kh(NBO+(!>D&?vVZaF4u7 z#i#j03`FLKpqG9SPt_2R1N_!8lfT@ik1o{zczrM?=}5BknfvJ9XKwuG=t(6gFlvHI zz0|smYeLWC9K;t4(g%~f++u)OEcfN05N3wYZnUl)Qlnunwb_ER;H_S6XYUZfZ-Ovo z`!KnyPTaFT0i`gt=}8o&_FGz(j(LG)48)ng&^$m(2|x(7=FP zN__?`kT-!uD-`(g&$H*?1z&;=7vI|F66eWN z0bq-12>l9ra~(X_=M=G@+MsmDp$z8uZ8*J257=;*${vAg!_*p{^}qo8F1hmpyQ9b1 zkSBgLex94T@&V1E>ZuF|47cp~v8hQ9G`7|f%9%dlp?o{2vc z$@To@K3JfG8lkQS;ro1dJK8h=n)ISk5cUSR#ipGc6g+zoM-&cYW_AWr)m7__56oeE zKHAN_&(6^3VQSk2YoyMbhFaI*<7~?}A|%#L}orZRu;U;6u$}hZ_#-GojZ40{$8i2PE zO98wN*_eM6qJ6v*`h~#shOL&Fy=^U(X$a_9{2PD)toRpx6T&Y-R_HtS*dJGY$Ar zuVN>6yg)C?(_cV5)LL;3bbm>pJC`^?R}@j#CaAaZCk*4>3}*RTU;eo3rWy6dn!cpD zbum=sbq554IY|rjE(*h6Yk*#CWy(Q;`uQ+>#$)_3&x&9QEb_$G!?%H_n`UFNW+}Ef z!zCB!n>z8IRGJNj5UO^gvLiUC(Zp4^jNQTA4G!yF?sDw`h7j!c1ra~v^bS<_`*a?- zK}810vFE=O6qqZ6RCbnnc+nw)-zy$cZz4_}J%>pAMg?JacfW=J7<+Z;-f1X{eeRi% zSk!mgI)DrAQ0}ETYP0~8)$T?Q>ULv}ec2@}o7saGK*?fyZ-Cc+2`eU->vfv9QXBFhB+PuSO3#4+X!)wT`Npz}~ORolmP`&xSo7C`dJ1`Bu zztC{`&PUMsm0kqR700c&X>9*@-QA~;7RFWaA?U(}E%E&R290btz6Iu-cZX^9;SNm& zpLe4nFc(%9D4BQo8NGW5-BN|)VvV*u9C{F^YCbsyMqvmV4qEb?+o9a{Fmyd;Mg=!K z#Tvc*yYb9_59n^4rAY{LO>& zA1huTqq3J=?|&i1iR;(hqM{?aBWdJxCKWmDzhwp!aoC4SE?dBmg@ijpg>hVQEu4D| zP#)4piD_TPgO{l0!1mo#WKM++H2)--^Dt=(O|#C3Imz8Scr@o(VC*i+OLx8=3G0$( zdfKAvG%JHRYY%>5~M$n#u%EHLp*~MiU3PAZG)Ons2aG zhdhjftQ_G|Q_E8ZSPvUuZ8v%e+i&pHEAH8TI|%I5x5wv&SjTx_?c7Oksmj3_w>2<` zFxC2Cs(syp#aJ~|B93T=Q|s{;TxuU{DW)q|J7vL&Yv6h;V3IdEg5qr+M-7l4F+kpQ zf^vgleC9!=w{Qc^+E8gi`q8^i;oO4m<1i@9_A$iDm%%O&#xukgcFg6-3P2olr5O=9W)6*T(?&aedq< zJo}I*s2nnC1_n&NNBI2*M;YDXMjtMWqUqB->Gq2Hwo-!y7WRFRt~C}Xu{`O)8Cq;` zv~bA*9#*G3Qdk6_HE(b5$1_K%SQX9h?6NU72B=Tyqt>7&{r8|0rXW6Rk~f^+9lqa# zX0~y~1zOz2mA9zJ3PSh;hH{$QZ8y@peF5C0;9lPdo_qlf`0g3c!a(ogi8K{t4h?OF znrgWCoWH&_p(2d|W=DO@)PEKR;K(}8J^zdc+(VuwKtML6q;QjOI85Nr=zeM7&3ioW z8N*fP-0cDDuOCc40RDRyOhdQ0)C#MHv784Nsmcy=ZW!(C^M%lUw~=NaQSEsH+)f6# z7ogM@Azj~~2X8P_%R$w_v(K;-)fdbIZhmNxn!oI$zPlLbfn-R4X#oCg9glOS;|W{V zYp{my2IDOc#k*7$UXES8u|g|%fT`6@qbCe7%Wp~rb_Gw^QX7qNVa7|IO!S0Yn1(GH&lv86YOmE0uNP}Dx8a=;{3(+~}W8wb#+3>4+e~QaottgeAyaG*>uh zLp@;;$Q37&vAXRzX>y&vDb1+k*#l>cUl|WOT;c-ftB_VLJYdjmH+|ff{LUz2Uz@sL zg)|Z8L9Fyj^fQc!q1QRS1E7@A9R=7Mml+6xWX!c(5f+;5TtIR|~{!et2nG@Gyuh;b78 z>9){2{{mqfh}}R#;g6ghxFT}hMM%{F|JPXI4T8NoZbA8#c^M$~TtbTx)?k_x1?)N$ z0Xd3ogVWrTrKE2G4{3CVr#?;qdtg{1|Ff& z=&ZZ1xHT3HtG>f6_Ah)P$%~F+$D@NsW$a>yAMPDXLBTz{*S&g8pPVs^KN`byr+g*s zlP(&?NpL#|^;rRnwfAi`a&=UOlsIv!j`twv1r)+=6I zuY}n7OwI08XTzU@@G50_w1p>u9Dp6tY1EvCFxKBP(bvxxZNO_)_Lx*vkRScryMyLk zV^q*8@2+m$>$1lTYt07g$D5IC8jiU6>KIQxhWC&aRlI@`O!Y1fAVxnApN_$57gKI1!ZMfkkgyok^2Oh+!ZHT)ujBX*19NYwW z(^skIA0FRidw{FVZaCApl?H0G1nYxB%ER9Kb+Bpsx}e>WhOd%jX95%e` zm-y>Fd>z0ERO;ec(<|vhw>|-beDpNjlaw7YB;45tRn+ZmxsL|I>|y6k-lVR>FfA5s zwhLq26&r+5Q|YcQ*D;UlAQV4h=~0lw|p49TEy%2%DhtGwf%h){5#h$F`127qO ztHEJMz(4u}EnGk3prDVN8Q;bz!VddO8o|WT&xVsL~P2U)6W91be^T^)U7E>`kf(aptOU2yfVn5|>FI%@L;% zmV#m*jH`E$*uzwIkOt{%vR_78I8~VOTX(Kqhs8bKd1y>vaV+A!O3)^(nLq5`kqr~@ zV~*Mv3|h~_AqiA?+>FZ~;S~@Ds3@DujJXbi=Np$MOP$u@J+p*W=!}NUJDa%p)lM$i zwwuO5&}OV>-*bZrbVPf=S*Wt1U@G!-zKh|-3o|N?ak;jU^U?o&6s$s@T0FSzNR%;u zy@_d7d=h4AJGGnfn`>Ncnn(qk^iT^28#yRwI+G_&V@zQZwmE|{KJvH;^?HQ>Yil>u2X(=cBRJ-NY5?Av~zIzp&ln8cpv~Sz+-}3GLl} zJygP}ken^f$_Q~)z{0NI!o}7>*Rc&$kHM^k#kIJ>_#a2^!`3y1JlFbFB10U5!tX5T zqYX%oZ#Qr&x?YP^BYiP~Qtb>f=7N8qF7kJ&_?x#^`2I*x>J69J?c;VGa)=Wne|_Yd zOBklf>(=WnxaG17cF?i0w3kbwVb+05lY*fod;d}T?xWJP{N+qAFxQDu82Hx`_UI(n z9k7JCaLJo%9-49CLn?RLiM`1{E>GMlov}BDkCKp_0s1&C%^~3XFsMVAs+IfiJ>jom zkQVLd+_J5_v-qpe$y3~Hi>|eD=XJ*3-MuYO`7_>;Mu5L(Aa3*Taeern09eL6z+bnR z!NM4c-gCv6-k+u_T~8?&C#H_u2iAkF(1sQGzQ?IOK2(r$4w|cpOTDsBKuwQRhZePoc#UyRA2kH@fG-x@ z+^>Kw^)>rtDlNFDbLqw{`V6>K>}b?YEa#geoK8=i~%QLGTG{U(GzNAO^9@Rd*q z&&PP99Bbtdq1WrZD-f+3xR9E~H4;|PSfD*`fVOT6mi*tS)nW^eyHF>ZIFEsD1sgBi z4Y0`KFW7z>1lQI=78dS=Vb*DeK^^7}V*^36rV_l5*|}cb<+2KuWu6H!>4%gk@hl zfm(4GmV?Jm@YK5tTpj_vUu&20z!X3#xon*cjIN^VzNSVzEevP=0LpZ6%h}gd?dL@u zn<0R^F^mJ}Y=a!Ksq;E~10dg8@W~dgiv;6u)G?Nrw;}pHwZsbQ{rU5o_gbae*)q0Q zXYoEATEF9Z+W80J)Z(wecb8saV)Sl4XpB*w1o}RV^oJPgi`s#?SAP!c&qQ8~Q$oJh z($r&mzn&W69jNoNgj1^&F6!f?d93=l0w3Tx{cOMBPio9~0LG{ThmS@?a9tvIg)6L% zaNbk;3`&m$r_itk^g%uiC0x$|p5o20_anjP3()-C=dqOQkO(8TOb1?Lr(Z4MHiQ2t z5Qpj`zo_IQo9kF}HEzG+N?nOOZ52rM|3oT}<1Ju5_97N}J_Vb4O-8n`W{N`jTM*W> z4NtMwoPeIKqFlU?vc5qBLZMc3i5aBWmy3~H<%8Fhb-ZZzSSB^FUed>WpGs)T728W# z78L#SjPqUG)L5vzbqMD6R4Q=;^(sxc0&5g%`_~Q2v1Sh=jNcoouvSo^<*TDKx*?V7 zj&jcv!QEf(QT-*%3A{D#;z36zU5~{?x~z{j8pdJ01%?cJ;5-5~(g@*D<>Tc*A8kRj zFAx{CN5vWxU`DCo=z$BF4zcU5q+#jYf9b)&&?6+&S26xIIY74IJ4y2osP&~5t?xY zj(R8pYp6oge%6Xe;L$N8Lxrq;{a}?M&b@?*ISH0qgp8_sc!e7A?Wz}Nc;xoFmr*{@Rf~aC zbL$n4y@Yrkb_)&X@qKB&*eEHd=|?vY*ueCgv%)OokpsG3`vaTo)G8r{F6li$4fiAY zt$#2qBv7`S8?iFPPRtM&WMiHV@$?C7nbgqxfHVtQ%%sj_^sa^TU-F0W3mBDtwQ~T! zfhA78?({({RNLs&qu3NIRohKz;Fu1#80_l;sV=}{iKg~m#k3se4_P#p`Y6ZL7VW7p z2~2}gjqg0xQU6{~Dvdn^@mA&raCq%`hvv><`_1_*&)wmV_o>H{r|bjoqTL z8q|Q{Z{JF}Hc9JW!F0cTp9-U1QmJQr`fi7abP1vC{f1LIOF^*-R2iU}SgQBB66Ag{ zhDwe6(35@-8r+Xn!_*OI_zE6^6WCSAi}&>Y5!bnbqr0#aqKQ4|U}+5XL>+`ZH6sj? zLlz<205-433xIt*kYlblvf)c;g>_eOmlm99#^@1!coZE;!+71t)lZyvGBzk7h=zFF z{(!UiKv@y2)Qa8w9;Qh#&zvyj31Dc0Q6k=)O>I5*km`l`@h|s<*q_AbbEn1uddTjc_Oy_XgD&2$`4b8iH2+TfZv;Rd*{0Ods`pvD;hs&6ajC;8J=;1 z0Sc#rZQz@{M_iz!g3u&r$o%9NR332d3RP;THi616KjpUT^!eCXjKo)%4CQHeEL2?N zk4>#v%>7}1%fnoF1@95wa`|ChcZJZpqmLJ@%D2>CWI1R2n8rlJm7^uyxE!B zw4R6TFulg`rMrzj7jqma?|SrQ%P35)(gR^)BWc&>^-v+rsfVf7Hw>h20y#dP!=6Fs z-t}6Pl8JR!A0^(E-=}tZcSN%l&ORrPD{oMp(5;h6JQ~9jcPJnF90pJg7n)+<<-@fY zu1*bf;&(LT2!A>E5c+TtewUE%)l}d_jbXyvZ`y420P1sw`z5rPK0;dD)5pt=&(6a+ z0BiOTkf)0pXv)e4(x(^$*&BQd`zMF@gVCPtr4JAJYZ~=$!XmKb{*Ij&sf3}#Tfl;K zN3FMKJmvRKvDrNQ^y=*^RIy>xQ|*gv?V9YqK1%mgNYM6Ocyst7d?Q|E*FYcifjlBlF3UjQV^VG8=SnJP1V1$CL z2D2a1+k0n~wh8;W2>WRJBWd~~bk%2Sx^ybs2A>YZ+M)fn!kH@{ZR3V}5<;z5e5l`7 zA<`-D0ri=0zxz_G8S|()6LxZCF!i3`IiA1hW_FSLj?(8y>M>mpvjA_svNuvS7N3KD z>$v#-xg=lErU^TaN4Ufd#41RN=6-x|YfFfh!dk32`Lz@jSSI26!K9^Jo=(N3{H(1rH$=j$f`C#{WehcKafKmNoRXfn<2=DL;r!KDHDuQpUn;Pd z2(2~OBbPcX26o~ql^+d^vH)+j@xWCpHN8`x#eD6jPj~|!0)>zFLH0Cp0UIjwkii{W zpE**ENi;HYH9VCNhD#)9-PKi0=gZ!h0;%;Nq8 z%%axwm;znf@7=)gy?e9;mWzz`IV(Ziv!h7?$5Q^N<$|f+8M0LQ@;@ z44;YIrnU#E;J)cj_xN>iA}@TS}VyY{n~S0lIWo$!%J8rXo{0qlxr10|#r_nwBR`3x5@DqNot$@$ql zcb`94J8E}oAs>M~S|2G42kyCapNjVJCs(WodZFLOA5h-ELJCq3*rh}4Qx(yWt&pLE zd${A6)m2*f2gnwA<~28k!ulIcpausRk@NeVv%oj?N!XQG>O% z8Q;ZTGk>**Y%hestc&wKdydPn^m;2CNY zt)b&eUh(2NNSMASH0*`;4yrK8dmbD}1OJbl0cVfx*`j*5m+I{=!zTR}hy~5t9D^>$ z>0^GuAO3~Vnmdo>RU6(V9LEsBXYx4Kyt$m4K!av6+xU$WmMTruWybBX+we7NyO7;j zzfp5a*b_%C+71VntZh!1{UaXcT+Zd$(1^WY$@&nQF~IypAM*|;uCTm|1s`5Q6C6OV zVA^~=!yk@h*;|H0XP8IuuvlG`9e1YE=MUWCd^X@~<=E59d=2Zd8MctT&Y@r{RU;;Ahy(46|io(a>~?l$%aZ^ygy zAQzqDa@bKknUO{V-jKwj`?&i$)n3J!4>+2GQ+$t6AsU+d_{x|&hDIiu|;tTrX!}j9p zBoeC(QeE9Q?f2l62~Gk?N^F(Y{}$6Hpxs8@m)w6u7Ew_Cu>dFMm_*g@4AYMQ{x5ze zXeIxe0T<%D1P+S)_t{clk$(rl1t$}5E`oB!L#6IV5!872e+BdZ;-UK=-!*9->V6V} z$Kb+_LH_Ua*8g}i{6A=*SRIHUUqxC_U;cRuDzTR~ApU1k|F5wk#_MnROM$->_)CGm z6!=SlzZCdOfxi^^OM$->_)CGm6!=SlzZCdOfxi^^OM$->_-|7H&MsPzoSUdkn4i|D zW1LjUnzX3IM5jdE?}$5vtCdPWdp8$*jq15FCGLd1hkaC{>Pd1+9A4y!zzgu`(EBHz zJw_C}n|R$Pg~#|S?#14$`~|isq}S;p6(b>naJLWz#fGkYfJk^k$iT!gMUjrvT}^Qn zADa?@h4+BS6A0uFaEVv@B2T#5%OAxOufRn)yh@Xv<6Qiq>6_; zq8kX#O~x&P`RR0bH$M>kXQW3NQByQhP*M<0P+Sm2G(n=OaF!MR!NObj8lpH6CMfHI zD9*Twv!>z*rZ^2L4kaEzJc@V>@i^iML`Os?MA2N)M?n!mDM3|1Er}4q4NMGyAci2j zpp@|35rj}73U@6*dNIU;5NU{l?4tjo=Z_J^Pz$n0AqpahVU9)=K77KjPI$S!M9f0W zM5F@zd4-sR_#E*CBISucx#Ca0_){eQyuqJr#Mk1c5P#AUuOf;wTjC6rKcX|D`09-V zVlZMLVhG|z#8AW_#B+$}5icNKLA-=0xY7bqjH?)DiCY4Y5Qh%8BMPS2g($wGv=4DV z;#S0Mh|&jjrElnpk|K&)JRn=sB=kfSC`4bjAQ~fz_KC3*W2irV zrnp~^Xo4tDW5G!4bWt#p$QS*R%ZjvIR=x_@a$3F$-jM4EUJ*Paa0@!gWdvCSdGMM{ zB99<|HKI%&0Yi|?9`Rqqy@=DIy96%I(7yNx3@etxcLmJqZ|MkbMwuAci4cN4$o36;bfj zWkkVSg6}RO3O*D(c>(b}qToZpkAg=75d9Ga9}9jygDB>BETU*{3}Q556yjsVNW=(4 zF`dM;64UEG;yuK>h<6ZgBi=#`M~p{&il{TY@-Rxt61l*u|1LYShniMJj@$wJbn;5&I#bPLg@$W8??=9tv4oUe2zeKNvF6NFTX0-r1v*80;2Tw|#G827vadJhsel&#E#(rmj z9t8Utda#Fw!2dJ!zsi@p_p|ykaekJcqbuME`S_0@57(bOb|Qz%{iwmj7`bjv=>5-H zk9>)GYlFTr<({XAX%4~Vg(FG>!RKCcp0zP|%kBLEt63QzlWJfRuhUJdcnuCDE%K}- zT(GT;BS3P|t^3H9CJ$G7T8H0*6Z6CddC1(1s0P_PB$jz^&duJNld_yK12CIK<)4uR z`4T(+j3mgH81l#5id=~ue$44|-5*X8WJ*(r`q6AHqxJ~@SfxYMkzf;J!G?m5e@<+H zI76YM2(|6ysZg9n&BZBvsn>2!KA=S*3T%}4q|U-OdF%E|7mApdlf#k@d|e>!bl(P{uU;U9*x6flE7QT zw|bbn_%lvOEJ(~I$<$w{sHfS|m>b2x@wybTmFI3yqr`67u>qTI6U;~>@wPY+H4nFF z7WrZ(U~IM_Uqa7y{{9X&sqiZvMJ^`q&-u+;JGW&gL>y_z#aW&L>fVcU0hK~oNV5bk z;s7oUduW`-=KceG;18g6+EJCPl3LECIaHkrGH_dl#EARQ%9SOZHP0{H6MaK#y+PMYCDj|eGW0!I}Y&xBM6o90UGrw*C&+Ac*u>Kc4wIw!#tAI zEB%}s-M}tJYMn#c^+bF=-IBTj#597SmojB{M5_a5f-*P2W&SJjzy&~Ul$xSRi_vI| z9!BAeRT+P|Y(~P+@qr-ra5sw6C^MAFY505&+^_R(aN1-D2g6zwg5K_^ZIq})T?Fy9 z_G%a&Wt-RWi1m^N(nM*qZ*Wf_m2IJ__$pfN@jF*wDuxE=?7hn8#qQ1IY)0Kgj3q}YapG?Y0s zovy)mkGR$i(80ih73`2JsSq2gJCO}W#5aJre;K;$^BH-~?%;kLHLat!AQ|Z41T&4A zP+Q^x4VB_+s5m6rb?_zfAyfs2QsJZGXjXjj|B{rc6te>_DM!=kI#o@OW z8gQd;z6$6Hv<_mE-^e9YGy^Al57Q*>*2`{?v*G(+hu>hJwzD`hGpARgf2l+s4szhw z_%@qh{;F4CUM}Eu8Rw#>AepLhuxPn}g_VwwE`jthlD?W+nk;ATlQVH(qc10chs+Lf z^>VhyQg(@gzQjjvIhnPb87ya-ZSgx!)5bXJQ@)&gAGugPf;1%?u2cO5oN49!xs)$wb3W|;UH`|=UftOYq%HZPV>{D_! z4O~z?QlC9c&C8i#a%SRV9PL;Hw^!jC0n7PMkZ&Um3eNah?Zx3q?z^^}?FE4=O(wqx z6MSf?hKJ4glh75~70=2N28t>O^|2cOMK))>W9uX6sYb$>L$pbzVg$0WVSwyOwCxLz zneXPZL(BQkk#8sRp}uj1ddMFqeX4k2xd1hy1eUy%YnsN@v563a<6vePK7Ew|(D)(6 zBNrT7FWR=8&2@6N50AXKW?_XB*;RVklgQpLHSaCG#{srx`g&jsKD4p~-)5JznE{rb zj-bXI51be)rLJde{q^UDP`VgE|1P9aze0JaE~vvG3mEj47*rv$E6LEHMkD8_>HhZ;cG3DD)RQhusW zJ)PXVppSo2U0zvngKk@7C*=zD2Z?X6zpk~{oz7=LjU`7V-b_=glArk5yDo2?_!w@T z7^P`qBsegs=B%kp4dbWMI$D+NEHW%HKpm%fXzw$cP18UxW+>H<_=7#=-H5xy)Bg54 zZAOJYp7`=~mIW__$PEjnb_uZ9V#Mx^>(4fCjQqvUgT^^JJy}Y`s8y+H{;5%F>_|AP zl^LoGWqev%s^+9K?w-IzQ)fFRrzRz6oKnuZc-lE1jcN zsqSuiX089aG&ry;i_yP!5OflkS5@0Z{l2cE!OLUli<)9A zeV2(Ok7|@=CGOBGB|xAsAPAfM7qnVN?WRNRwkjg)b_h^MmqINUtQdhV!rk8Q3|6OD zFRZ{D(k30MOIN9~*Du1lC}K8_i9u@)PZwi2CE zvH&C{3;e4a7yqbObyQqjwzD!> zs~_VRp!C}V0*OtbU%(PuuBa(4=%CnV6%}Gql9Hm5V;z-Q5Gfknol+f#^-M~pqgolO zQY&N9G)c;&6m>R)jxs7~g&t!o1Ss%V5&`b+0z-<%5f1=og?dY6P^=D zq^|%&TR`+s+N=S6v3vasn&@{6HT7@N18n{LqP}iZ2~~9|iL$?v2Hz7G)xUovhSU=q z73lm5U0xb4Kv+s+3ScR}x|+ISU#_lu0Q6#;WM%By{e9HcCLFa}`=XLgl_jUdD#c!> zi(|AhEy~Grg`Vh5Uu+%y0h9O_SG&`ND%ymjeye_gOs_AfFS_KY-9Hoc z(vxFVF)Bw%VXx3T{bB&3e=Dfwi`lp`seh^~AxT$P^*^(%hDq zBQ+}KY19)XHW5>jSLnWeF+kD3b%fy3i7wfyaTErKV_M2nW%AOabampg48XbD6_9-| z2E?YBB!^b0qmM`Grq;f41X_&GuSKg`2v&8BG;5@xj8!UAF_qGk$+3``s#J+Wj$(?6 z-JBI#@`KU<{e!1q`(L+9-pvCDIl0`%6#we;@5D8IIEmT12` z?11uvr;@^yM$$hOhOaXsD4HC%LO%=(0<2sI)YA!fZ4m)S^~6b;oB@@Wniv(M)Kyic zWu~Z~I>kge#;FpO+G36|@lmRk_+7skkm}!pp!g^CB2}G|mg1NQwYx$OmJ0#U@{Kf# z9>1Kv5}6FUAc$c21hBBPC15>KqvLVuiH>nAv8Y}t0MomJR27@0t7_GM5`2kJ`lBF` z>sJt;W~1)YY?Sr>!?hMR>sCEV^`o9c|!*X*34_Y$XQ&ll3F)4{D*z!$DSH~zFlcI!3TcH&OpYwb?36TY0WLR++j07aW{ls1ax(bDbBuTWiA0`+w%h&8bhEFk#HnNg}N$JEpq zFOQ`siK^uEEXOEy(sxg?^vY<`lVR=ZKgm=jySo`YiH%a@p}`|9XjK#z^)i0U>1a)? zK`AXLgHkZ`)hVi2gI3AU^ji@Juf*@cE%isQ{n}?g>bJ(-*ClJIbt$RJ4i+4WDWol$er|{6r6|_NiK>iOKknr%9?*jhBZqaRmlg>V_7MTmuYY z_&|>@Ru4_rswH5$`#*xE>-Q2Y-TiOEGN3GA8QlLSEQ7HFEQ9;sgr(0M0HuHXn~?Na z4}kRUeiM!!TLX^X-EYFtvXL}p07vickKjntaS4ue_eXG~nY9E*y8BHyGUq_F0FK_> zZ^DteMhi#p?hoOhFKCYp2Y0^-XIZiW%<|1|LRl8sfU4J(!U8qO9pK_mePL&L(3^z7{AXaOGW|uvXs_qM=WfA zZ0IICYrO|nP#W3-0HaL^OIvJV2n$5})<7w?=9K=ra=KFbBJ#T|J?4i>G_0l_I&{~| z!!b#d5%XR45;c(hBsqPB+@y3(;&<74cpa749R4nQ3ATVN^5f%vT0%hoVG+F<`P1gh zYW8OxNKMI9s)aq5sQhUsC4v2e+~xW6UE7!E#82A=ru_lLWhNG#UV%@(LugoUnSBhO z>v7YcfCdSF1R0P22(TfMmLVHH|0B@L1G5Zv`PmH z10uaS@jVi~tUp5V-IQDg|6Rr(0oRgRi05UIu#DkHxobqTB%6MOWhwKIkbEy6moa>w z^GD#9h#^EfM9DJv?{n4+{s&?~t5Vh`7?#!2a@(~`w4C!tZP%l(WUlITsKMhu0<5ov zm)orWbd_NBH9Y`nRX+AZ#4f9D?@4w}SV4|2I~M@P^3C$=GGUO&UwHe)&WciZ&T0Kb{^$KQ|35aS-^c&} literal 40649 zcmeHw30zHI^zW^%N;F93C`CnX^DJr9M4AlI;8r)?rkn00-+T7&x9oHF+UvXbUVH7~>~n5QakzRoJ51f18KTYz ziIDdW4-o^R1+jbr{g^=vnjb4Hn9iX^Xov|?D3mQ4>W`UPy3NzwD?U?pNl!Sn!7ke; zz}QYv(B5j{YVjz;t&j-q!;i;@{2)s8|@hC=%SKbRT93}&)9 z5E1FJkdE}=2>B6zt{4}u5n_aA3QV|+eFXpXgSZs#QT}UiKM>+TR3QlM>lwl1Fi^T5 zn-ReX;jlwlY>p3`P5B7QL;8CI{~+8DdI*c@OQA5>-r>w3U&={P(f~-`4l&aAa$d~g z#h$#_7GhK%U5G&kvBP<>I4^FIU%p63a42AsTA(n+$fp^~%V)#F{7L+y&;(UllK)e@Xd59Bv z>3$F^!o4NL3J{NkSPo)gUi=D_gX9Y!M(GD3M&+!5cnHJ^5F`D9ZVCN{hA}uCCbCHG z{S+t+*6TCJ1M_Pu^g~t9DcSR?zA9%c3RPR!*>;sW6aR+m}>gQW+ z+r8k`eH*9zi2nDCMvqx=!(?N4q5J2d`=Z2L-Lit#s8aXNx;RYG?PMWCglaqg^eW?` z?3Rp{9kWdQSBpK=7Y~k7-V%BdSMgKa$=yZMV!1GqZdzojtFICFc zJ3sb-88hi`kNC^}JU^}U{>LeDQ}c2ch|O7|yeXgZBu89vsaWsk4;%^2lILfx=y)2~ z6~5KlJUB{ErXteNY40$<%7V&AU!6C#ucr!?DP`UBOVsJ)xB&@ompyW)-z=<~D7r}X z?#{51z+>+0M4KPScaMnink+X$;ZpeuDaCOXks^(!N;0H|UQu~HIcV07pXDiM%(5mh zJ;KH-r;87;e7*HhM831hZB^N!efDl0qH*@lYo8g{n`7l-c1>Bkc5Izuz{<2`-u=Jz zl$4Wc9xbh+EwG|G|YS@R&->khIyLTDPc)Ly@@wYW~@0r_u`9`eSHU5f2vu}`H(Tb z`5g0%@je~l;Fpgc+Pso{J~^~}mb;ZZsE8jC*8tD9ZCOuAN~eShsN z+8Dhppp*&AOSb|nGem$nfT9KC72U&pM^PtI|NIY@Ukh0+Kpsn=d7(1_%a=puu^^Ay z)1xzjygXRG4+vs;Sjsvbu>3@jH{_KMOKhhDmQMzGeV#mO1I&G$3Rpg$RQ}(zp8`O7 zyz)`Ooz(#@-wZBL|D(F2&{cUh$kRX`mUpy{bvoekPs7C+kVk^34mcg%cNB0w5!mNU zJLrnvi@I#JhER@9@1ZD18%1rbet`( z{jgQ)^7!)yd89v7HmZMT11^6D$XoKtM`>Lh|1Uva7v#~ngMAeR)X}Mc%hv)yJpTHC zWLNFi3*_UQ@ue)9C{}_-*;~%O)%>A7m1GxT2K;D$6f7FJqmj4dq(fp6(5GLGqD&X>sV8A(m zJihO0`O82a+27xmp9k`4Jb9$MuGar8$eVyXYBaLp&Ia7xG0^d-{n&20+JDPH9+i*q zkZf1=_Y>sJc6oq`6VEa*MF23r~gh4r{jE8AU__; zN9Ce4JdSX_-^Ey7wl{_1Oltq{^0!aN@*yCP?H`T7uJ&IV$fNx$nmd?v+TY`R zcR(KXAF@3-=je36@^aAO8X%AC59zS0_CF8g(fC7nsO?>Ce>TXY{WmHXmEYCp7Xdij zG6#8Ve<beBF@`@232jZpwe} zro1DZ$LoUr(Aqb!qY1wq56}Nm@X}yrH{|btyjwTq#|-XX`Ab3Gr5oi}g1kpJnG`JN5Sj`Ptnl|0>8cx*a#RbEb> zfBud3-(7wF^aOd_ek88|)R}Q#r&l5o-7fCIJ9OQA14mrX7n zn4%!W@?W228}L9cf2ZToVXl3nwk=Oq=rtoX?6HQlTxgi&l2?26pO-#4;wAk?#rw@( z#_6%FeljFnH232`EjQU^I`ymB;J5Y9R#qi9SY2*~+9xUtO784g5H!ij| zRcUhk6M=r$Qlpe7SftQ2?i{F}?`v1z@O(fl2^XzxI8Ys?F0~4M6u&t|SI%XAaN6RT`3Vm;paD_1_#MJk385=GxT~<1N_oP(KOu0bcSBJ9;EJfd$ z9x*#R%2&Cx<*UbsDM!wYXn1FT)_ZxZ`nWND9cf`p)*M zw_Uv1{pY7=P1!Z3FkeBVx4gah^iMlRew5nX<6uL*k=D;)dn(IJ_Wr!$zG;`%)1T4% z1p>#;GPb*LEM%CtH3=8312|AMu4H--GwIcwx^HEQ+3w7c4+g4(4d?WTD)BWiooSK& z@Y0&?8JjPwNmW>`58CVJa(w^9T*EU|r``3V_bBH(?|(?bh1W!_fx7Pfr9kDV(uADG z4?1T*50>2>XV;?b6H>I&`@t#gJ+(>6pM!6|oa?jl6mz%3nZ(rRs#Al%n0?>p<3!fTb*KnI;ra`Y6~{_)Pap<`n2lo(>dre5L(%#kB=b^|x#aoLB47Ja2B~^Lam}u1d7HOu|Kb zBpj%p_C&~kf9XvR_Y_Z;V9AyDShDba*_0Z`hb{{9Jx2^_yj?w9vc}!wgcNl_ze%Sy z{5)qgDJA8dTd#FtZx_Eh9KV)?OWrqA2QdXM7_9LscW%OPxt9Z#)y2Bx-Zk>=-`r9ZZbHYgiC%Npi&%nD8KwX zVTIQ9irMq;T0dPE-eT;nUgf>My85zN&tuMNPflFZzB4_OmR+OXH*@LmUMt+?%Ek=+ z)<6E4V6*mR6%sD`)&U3VN~y&4%L=z_sSJPMVx(`A86RS7zUchjhl7@9k3CS%SSS{g zF)H29aOG$Dk>Xd9vJSm788UM4$D*2nBiuFyC2kl(!iCoXt$`{@ce)eqefX%&xmDA8 ztUSI+@5lFb>qRDwSvlD!yhnXwl+b}ygHD-PW#@h~7}N?TWZB>H||!P?}cfMX|XC-v_i@qEs{BQp-ZFZx0EcV-+L zsJzEWUDJ8N&`+P69t%s)AJTZ_%q|^WGA>&CaG=_UYiP&pd>QC!V|}Z7%AmBzA8khW zqg%_ApBa$*#PRtu<1MA1k9_H=)30Zr{@XasEvqyO23N}$R*0S7Ay;#?ScFtxI96^A z)L0Mq2}hqQYI`{>HNNBX?T$r^Nor|JZ4oW9ZfcrF=Bysp4~~SWkDJl-I?>7eRiW_XohU&X5|}l@=Bi9EY|K{nli>s zW=wf=?~x&Q)>p?aKC$GmcHM=BfW_ZuoZPULlYB9{Lg!n|vx5C3T=blV1J%f~sQLA_ z&ygp^H_2q>e^aiMYf$)Nxn|jerBjA$_~|yArJc?xUgCIQznZ5)MnPlgxgysk;btP! zmwVJUY;}#ANy3HW$<{!n>5R9TZIW&E@b=6zZael`Pf#cn_T7AMdH-)~T0RE3!o|;%2%P$0Ds4;Ztm(^T-%N0_dIO$n4 z){$_@XQ0&AoE|xx=fO=9zR#A+ADvM4uCU_v?d&Dlqk2ZX9yicF`RIP{Cm#DAjN6v+ zOxQCyJ!Rh%<2@}81Wwld`!TV)!Tz;X=tf(qQ*MO0M4^xw-YxlGrd30UxnOpr^?w%Vy?~~=o%Qr~4=s6k( z>X1ig`tR9&{Cq-{sMbqXBtLVG#l5pWzu{BVWE`81RF}-m${pUw%<*BFkob_$Uml$__LQYsv zuh6sBJ4>~etK580GDSeUu|lbb#`L?2Hv%#ZPdte_U2-eD)*1bl0q>*G^F9vLociVC z_x@ChxJ7l}7es$M_ejoSr|E+iUi~oN-pyG2%5d$-9di4PWy~&5i?#RLB(XSk@TKy> zr3VIG^8GSm!jb&dBwTcUfCE+2@w^muR9(@Pu;7oQ=;Hki`g|@>TKJ@?@os^Gnau{V z;Vv89dz@+VGtaEs?XrUYavuA}K#%XY^;XpQJ(_wrmXAM;VKh=d5zCO}V*Aw!v=1mB(u4^qmzWB(F`id+Ki2<92aH z;GG-WNcBbM95_&$++H8q`L6E6*5X+nbLXjSdLZo{w0VnY+|>(x?^b!;aoD32#7SEu zUg*29Nb*eID?=43wwzc!hsk+D`}4N}3ghD`m@$UB5>`NUTcsdSlr%n$eBL<|N$V zWZaBtyT(rwJH9$@;@Et*MXsSmL8{+%F3qAY9KAf|p6LXZ+C8Bu2`YoVju=NKs3}Yl zebHdBY@)=HoA<}X`xQ@ieMQ1mAmg?a*4wXB>^C?;EMDpS?2wBI@xoE#nl``e|9-(7 zbKgOW!qpwUT=LHPZF8yr!7}cxrDjFFwz=@L&=f!Vy6VauPf56nWZa^NYjHbvR#7~z zoGeMS^&^t<9MN^=u8WjH=?fMv8b8n=_~yyTD-U}FM_QO`Lk#8}|DyB!`HB=J_2Xp`&+A^FJJsiYX+c4~ z>sHly1@gXFaH}!EyZ`#eLI-^LqBgwcA#>83|E=sPdI6ELQAbiwxHP@<> zEj#i)3jK?tLY^#Mh-DIdv|@xX0OX%w`t$Xnrd>oNm``1 zAoqNbl+~QzSJiLirySVKxT2M9BYiiecE!N5qhgYQc?Kk06*BJKv+f_0;#wNwbX;f5 z9QF0>eS<6X)5c9&^Y=?{lt_48R3>5cvL@f_`j+d84Ph(~s)X6ABIn9+qn0wgZ^}iV zl_lYhBIB})-?-(RJWv)QVl~)RLFY{0;t9EfZco`Owc<|o>1VP%R}D9NKz(KC*)(t0 z%{gC=?fuAMt{!|y{6vNA+)pur32YMXXfkf*_-*fm@~r06YTE<`?5<$39vHf&KKWW; z?&9`M*QZ46rA~3?{n{Njtq)Bpkg7ShV5V(~Tt>r+*M_%m_B`v6YDL0TCF5Qw&Hc1w z*R?A*66(j#IrZAQR?DPTJFIT_{^#cJa_4)pACi=ZT&djFKvSh2ku| zZ(YC26j&9$<9*y%*ZHD@;t%AGjTHW#q!hd7=ok_%jg0$IHsjcph?u4z-Ho3c3fDOu z+qN;s$RqUS6M^CwSCq!SXOegOAz{p?w2wO;jn*M$~MHj__XGK(+^T z-irfu$-~|6b@!@VDEwxb5ICd%ebeYv$M>f_Eq2|k7gk$(&bwGWdVO+I@tBa6)kcDj z-x;Di=gd_9w7fyd{<6BmqAj(g`r>nAgiW(s-;3ZLwJ zJYg35YM|ut~)pL#Z4H&}Nk#_hP)A6@U;GiMq$9YvjKfm z?z|4{Yd3A!*Qd!F54n1%D;0YQ*)FOzUAW=k_nlLUmg|vlwaB>rGq0bJ5Sr{_Uahfi zUtSbDc93CTvEc`m8k_pKD$T2VxzJ3}Q+ewUmzlLPlm`+;M`9K~jI%9S6Ofi7BA*>9 zoxW+y=jxdgS8w|AXp-JNM)CG^?ezm5TfiX+-aqKH!OeUxcIevo$cs;G2V|AVZ%c{G zeAWNFrVsPoO$X_r8bd_yZOk;;6@A##W&Y@Tqfx?pu(m0K%`Q=^Re6yPvhT>=zZa|!`@zvy=4_kM4i>M zrP$}LABkTy!iGa-w?UZx~m^qv6+>LkgCjeXabdK5+FAESBhxuyPmM()Uz$4~c0 zuU&k8pz8U8B~P4B(vzsc0^v{Y+kbM)cUDW;{Y7|EcyYn(2Wh%OBwT$A3NbZ$1aptR zq4(NB|n`VzCW)H$OH5}{8W7C>0FKC<-(v?DF!( zg=Z!#S@d2&rILh;--94{YMS^>k@j7oCW)O!Y{9bJ@DmojkFHhdADv&hAk464LxW8~ysGF859e1YSECCB z%3tIZgb?-R;-dF4I8ZAhr}fUCKk$0r!P({M>9;BbOLv%!zx~!aL05hHlFL;~eKaQb zy!0_Hy=2iAou?{vfln@*mKqg2+aY>wa;b38;@%`&c&FDIsHY>FgnfiRWT+QcHVW?3 zvssjThjsd!=Ec{C?O003-N(M`Z63E>eUO#6;fT$};WqVQ@)P<=Og=q`DfQy^>SvC2 z1TK|0{Jy!*<+t;qk4S&YNg~36*kHWxIw*7`CN0}ZHRTDLKX4MV7 z?)@q+KiK;HzFrK&;fdqx##xuG7c9*hCYgJpTv4*p*IvKOUG$23lG;Gc0`nU|(%*Qv zXdIhhP>89LVjK3SA6lEN;5Pe{(}!o4TG3MaQJ)Nq#y*ry-TK_IsM)Pg;FGYOp4(bZ zU2?nUI>f9=y7p5a(TpSBLnaBVHcsK;#sb!OGVW{}iAj%IrZ&APJXCYcEAp0b#vC1o zxU9DV*MyvN4;%ed8`jr(RNrGRk4~)VWiB}_=lSy0qeh6yoilSNm~B7L^c{gq5tRo_ zQ!=h>_N2>?=N$VMUg>1!BG&hvmHAQC5*^(+dK>T8XV-jM_27fxY(iqGLiYXonZ{>@zo6d~u&~ z>xI@=?x;!2RY<<_dWp>w&w6X?Np?@1XMGszD`lFuPk)ekdF_PIs4@aK7X99ucwdO2 zspWm{e%~t5r%_{9_W46wFI|{!CJ|NFEA-&;)a&sBFFY4*Ib1D}t2Qi7;$6>Exj$qM zOn%PlEpRToI9TwD{rymCHGxYda4j$>#MH!Nhc_!}kIwV-%8yaZ7Wu9%H(mGQfXJE8 zb{b`?pF3DT&`arp>a<~-=PEZv4V_Gv8d!8Xq4%bQ{a+_NH}*8!&)rv3MA6x#B^g)w zk+06crkfLPc$VCiW6cykom?n1ATz&exxD6(HGGOpyw)mB#GL#0CHWZzHt5>t~|?Z~m3>AJ;~TIfDM zW1sj4f%Q%C`*+;&*-!7EzQOXne9%unMtSPVS*v?&^jF%2ev>DF-nv0NfsA`zv7uPy zwecK<^6Q%-X6CKG{NB^EJm`fhRZ8Vz;Pz+7tXYR8@-H09+vchF}k^9{Yc7^;`!N`HQ1)orH8 zzD%(DZt=DvFz5J^lvuZoGR|3P;-%L`OApw3l^7(J|CF0W;1bgV`j)UYP-iSVsTp%G z>1wYzmo&7(9YfL*x+NvFs;UP9m?HnD`lwhlJiF$6$xEGs%CbaM4j6P z`}3!?ch}R@c=bhgX4{Gi_Tc^bp2UuleeYh}e^6qer<5l9`M{^tQ7c~We!p>);)F;g zU!h5y>P-8YBkWXGR_5nFP>MTac7A56rElKh8*VFO9C-Cb^Mf53_h>^wl9%{;@6;iQ z;tF-^HNBeeuitt=E|aNpMziwXiAO1S3e>X$;`C#WH~SyjV{fti!P5^qRtwyPWn^Tp zuJd8>aAN_}o{W1!WZ~VKf^*uJ-W*BDvoAVvJ@-n9`=m|Eo92ysH&rNxZjkrWeBG4! zA7Yjt*0OVL9L_PUzH!>aMRCBBuTqrH?-Y5srhtjwtK&eeeWkc@hF6iPwadEKd+w_( zy>;kv^xi|C$F!8KHseU-M(zq(UUezvj_ZWanPKW?*9K(m(_TBGCS#k2FKh8L;Vka^ z0E#HmFMdytu&L!IhdQJmz7usU)MJ6LY;CciIj1((s62D1ZldqV!^^c6S3H__&a&QB zef-yHl{5BAHp`n&f4wtpu*j#S){V2~j3eqRNQ`6rexJY%YHp!K1P517NK#0heOGv2 zkjgtNwWpWf)LtC-Tq1I`yXm+=>k3vhOJ;D+m>a!_Ixz5AQ+(f+O-8S_oL2cd;c6&> z8%w;8o{Ru_fqGZ|iQFFNafY)N)4~)FFAlNEZj^m3o6=9vX(6RJSoTBR!gXtRumUet zTzMuPJ0Q5n{P5z5GvetniMsybgOxnP30&@bGDi#wF}3&WtaZK{^d?zo+xjF@Ojf+n zIjHdTq{@kvZ$sl8j;~(rQLcIC#S+@_Ax()sd+8&twZH;0Qo^PpcEc^_;k66Ic>RX- z>qN%AJpF?D8H&eR4{xu-{lAsYRv&mbv-EAFyrR0s*{cP`gKrfsZ5bo{u_7a@>|L|8 zrTd1Q=fhsx$dBt?Gl`*J`zn}(>rBRF%<-+e*=#*wpvt0nx(xDiG% zDbJi2K@p7wO!%g-HBjeg zkDz!yKGULhyJ}0{br(G@GZxzgms|>&mtiS=y?88=O1#*S4g zY-#3hBUOg;`u^!Bg5UdmoEE2%mc8zZ@T#?weM&>k)oz}l&B)ibS!;X4YhUSNwM|Y^ z_aBH$&D`GdhQQ^nZ&S&*_vcs{Z`q(>lkj5WNvQ#!m*%{(p0aR*vclp1bB3;U|HR4c z{rLKf{sV27eN(LZRz6CmUqs-oz^{h`9i~oNDbhQTd#=pg_e~??YKJ~~HYoJ$JlCEN z%j))3>_1ucS^CYxwG<<%<(xkHlP(Kut}jg1w|%Kv9RAAT*kwV5L~&V{1Gz_TK09M+ z_-*+fqQ2aDeL5NUTDJQ@U7fN0+{bDD{IIYzeu2!~<~b^h&&sCQmDESOuW%dBkPT3{ z8o5cq>$L6G?VIh#x(>Laa_ZSss`_n@z7=i+E_WQmH+rpsy1Z)9%>82hrpH{Hd@((; zGTD0Hn6KY#wM1=SkJ|3FSGY&g(?`)g*QD2*2<6YP3^uIT{LND3x>nicVDC4S_kC=* zdf?*X?|l$B_4e)t`E|Vw5?Aze%xH6%j|BvH?1;q2X>kxO{oY`7iz_rLIh&*X@^>$RF+#p@Yo0-?1 zRz3+69w_$ib8_tG(B!1?vg#FHDH&PJWS`A_G*g$d!h`x-?aQ!o@D0nVGh8fB=z+U0 zaVO(`bVwRyPM3J{wAMi?Y*pC!?{BunoU=JmaaR8LgtTu>JEiB))(Lu_$YVFNj>QJL zm9F%5|NgUxZMD1X!q`lMV-_S_{CyLlhZPfDXi;whwrsWfy1Sp7bNWq>KF9B7*5p3? ze0p)vIriYXhL<_wZ}n~^yjMBdv!ObpZ(Z zdqj9$ivBv|jtq_aJP)H7@V&^m%e89kXCw@NtQo)WPUFl)!)g=vGF_{O9=m!-_P9&^ zkm}J=4i|cD_}Q}Xf!+P10UMR_&MT-#W!wusnp@CMps%SfufAx0m_^17TJgi{p{HH( zG%<(8Q(Gij)EgVW$7(%pURSuqEL(Ncj9LAgUTnITv+>jr^YnVp;NSp_QwK9F_P-h< z=Otp=IPf@u%bnNhWL$y72jv_spVh(hUN{L%S1SG_zcN0j+|O{lWuc?S3aMM$^+HXm zOtPlO-?Ka^xO*RS|A-;_8>n9dUZ;QCkQ9EDe17Il#$BQ>UGDF7PUW;4OLRuXt@^W< z4+N*5)(crXy5gYHp^P~z7Y~(g&>uG5p)x$NXTe3)n(Yow+LMcqDeqXevZ|#ii&S49 zGVY2%3mtKZ=gV)5y)ZBJOO;u2dZl);xLf@++uW~7! zeV$4+KjPH&ZdJE<537pkOTzUfDiM`lz{9s#^~^U-NVBKmVcc=sgEZVL1^U ztVlM@ovTVaxq4CN{h+CGKP581A79{L9nf3Mb9H61+=|3y(hK#Eiapwua&_=j*5=WQ zFB;q@livsUk#Ua}T&^i&&Ke(nElSl)@LKZZv(87$#_KO<_|ubQ(jIblUDCVFUTBiV z@gMG_aaADYGTUVJMy8IC$@(6Vb7rWfk?K2}jH~3BG`1##V=k8IG-vG&xv8--Q~kB; zua3Pj{%KPLCrf|d(cv#0Qyq#&sOQ9Qbc^ocuD&eh*qY&=p61&3^LsOQ7YWy&jJxCV z_(ii^jQ5F5yZYSWbmHP(J+k(bUv^f|ya@7W%FqU8DEEC>92SS4~7f?`|(RXpaLL>8Ha0YnShplqc0?b z%0#%Ry+~K6JxD)D9}Yn1dp1WP^qm;`b{pwL9Y_NR{dNNV9s&J!0Db4L45R`y3TQNt zD$p1p8W3tfsw-*>syosJYCCEx2Pgt45-18N8fY%iJfIk$`9KSR76QcrEdq)IS_~8q zlmLXjQ$gR9pzl9s0nvfHfqZ~`flwc!KAR2X4}??|0EAS9bRP_a^o{h3^f?D83<$lw zMc)FU?{?5PHj{wH0io}=#{;PWjQ~QwL!AI*24oFn17r(i4rBpj31kH{5y%b**-=j* z2_Q+JWk9At=yw7NK#D-{T7>x9f#^5H(m;KImIEaMp?v#z{al7B522&}Mmj*cKsrHX z!?8bskIF}OfcgdX52_RDD-j@M4^$u_AVDBBy6`>(lIib&Ev7a1K0Z&ae_)oj_O%wK{0~~EN_>XIENCx}b0gkSk zF5=)f&e#hQ!O>BJjzBg7eH@GZHVF!NYN%--j|SMI8ukbU96cysO^5gn1Jw`voFZfl z)U*s*y~bg$SHRIy)6!Ja(Z~MZu>UGS0S#yl8adcw8}>LQC}5zbX`lv1hkeLlA6S9{ znou=l4cN;Z_L9Ztp#A{+j>Ueo5Jwk*5j~4I*y9%B=n$MB;KKNc#Xh+ZM~~n{^ElYc z6(4O0kAwYsq4q$Fpdjc0qUUfg$OxE>gBpO|{#y?8DN%+0mh*cKZqI*Z7Vuc;ci}Mq z#A2Uff&#$NM7{mr8UTlAYM0SqVE(?Pb6LKi1sL^u)wR}^3optD=)r`C>eRlsr36M3 zIVqIwkONt-Kl{+nFOpeI839kwjT%fEnZQ9ddRte<)$QR&5v)lRcT2K!5nTUP9AsZzIR;yP<>=h1VG++*aYGc2L z*l!zf&>RBgU=NMh!yMP*wPAL_J{z&mIl|sF(BS^B4tOf~g(bpl@Y^w`P0SS7*B`{6Z`N3G#HCW+1N`a_VPzq zh&JM2KbqJNAivhw(JVEvfrs)OI(@X_!$ZrEod=m716U}eHyQL$G?;Gh){dI9@Q#eN?N4$-IB11a{P zNQK!FDgd(!_ECy`Eb=+niz)WP$md``r`S&;pMyQ2Vo#2I4)&FbeLeCy*qbW$2Fd3v zhD|-vANCi?=U|Vl*rOz$gMGGQpObtJ_UekgQt~<2?<@9O$>(6tu-LODpM!nJV&9j1 z4)!jKy<_q@*#9i{pULN754G2M9ya+L?86rO;N)|#ms{+mlh46^aIqgxJ_mcs#hyO- zob6IvU%J>AD4&D9?P71Cd=B;_i~V4t*^;}5#U6dzjZ;_{un$`7Lz9Z;5Mqadz0_ha zn|u!TV~hQ8@;TVkE%wyO=U`vB*q0}tgT3WqZ=ZY)_NR;ef$}-n<1Y3X%I9F8yf1k^ zMfn`;wHJFG<#VuKU+kBZ&%vI5vFB1g2m1!bzD@ZY>^&HJKjm|g*h2!|4lp&_Ff7rh5_uJVES)A^P$xYmLFI+f3Ys;s%h!LV)9=Z zyjKq8JxLyEeWD~E@NxHWYF1G&MTgLXTsUFOkl7~kn*4`NgVu*l4%-JU36#9AGP}dp zCCebk<0wt?(N5#HL+703uwjx1Puw1N+3LtCxjK!AXuRi2HO}n0rfi?^Fb4Qc57pKpT^pK)u)Kp9>cEU(uz5HB zbhbZ(9m#+{rpWORp@nMc>UB_Hn|x4mn|m7fi4WDF1Bf=|0tB}LG~!{TbE4e(gDCd` zMA07Y52f-|VhEB*HHILF9;iqPqq95GHdZC%LJ$xEf+p-jJ6*IEuV}4KHJA5L4>G(f zkV5-MD9%$`-eeqXnqdRjfvSn~0uU!+zHkQ;k!;8w$)<5n98gK#;eLLMuy77DD4gR* zGoVGliMTJF(~+K7Q2@e<0&_qcVZb0fwKIa592kGvrgYyhx}RUPIwOQl3t@!?1K`|_ z;C?LtMYI7xU4aVhxG&n&1VJDl^0;O+;z{)n2`z=staOb_v;F`{6I zvUzumFhBSJofSz7WB4+|7(N_!Fe8{177c@#K@aYr7vRYfihzHK5v8q-N?@^RkN`Lx z$hGDH>DFta--&KOQyF(E>;QwC0kx3$p%CaqpWBHY$dH=_lH^-x0X#Oc4q3mWB|PF9Xd}8Z@qN)ebs>G<@N~{SO5bCjn3{5ySEUrvQJD;GQag2={_U zuGJqzNo@gn)Fc}BtOCh}hxjsmm^5Mm?4WhzYyd*OMOrh2(b%!7f4lU+Oxiio|1Le~ z5&RFOwpq=g;@oAO7~8r?Hk9rYNT1E11+ZBm9Wirm8d}e399AGB zq%|o#EQmh`z;DtzD6IW7fX+IId8h*!aydjZx%(9w6bt?H=gBz?O{`%)#Hb;<(wD&q zg`vb@g!sao$qXelL_>oPX6+7Z|KHpI{R<2*`#;wUAISg&zU0@j1AV|D4~W3OkX!qY z#z|@{NRjTqQ2xBXY1KWy+X3Ylsl*~j^rUGhY$VlzK%*gk9kiouMu5dKpgrE?&dq{w zn6uRwArY|Tg$B`m7(7u1Cz2HwsOCeb`7whS+-x)-e>$@xdnaduLdmzFQ238%MQ9j{ z!=eSjvfDum_?ZBRe?#=5&L4*_p{BM?KuTzv0$6Z7g0NGyovPJ;72c{SaxW0-`V(ScG{^=}!W4my_)xpi zB!%A4bv#9(9Dso$Flu;theSQmn=&;G`Is2kAQN~R@CAoICj_4OeAsMsu18}8doz4} z8NMAD3_gnhe0)h5`=6#j-YF!A@vdMS0f&(KsA{21AC?a*hy^Estne@&1}&J5X0#4! z(Iy{Ywz(&a_fKm)zf4r6)(Y?q0;!sNY^Vl5Des^z+)RMNy`T{Xi@39R$Fd!W^KyVZ z?+Ru$beM<`Ofi$rjG~2x`snGjrUWrV!lP*Pu;6wnQKUTV@DSMSl2am?A=+APQhe!Q zkk}@P3(cg%BQh=@hO{@^w@ofCRGVC|&ktiUecRLur;%&n2VWDlhih#Qss7xwU$vX9 zZKx5|T02%KBcweZw|bx-5+1LuZ4+??+os|wwoT<$D2T-hnN5P_riL-uJ`w*(4Q7V2 z^>i3P9T-4sGq^Zd0t}*U0?95qkH)K2D;V$oudsOSZpGr=cM+?NW&x|seHXFX^c`Te zx$hzt**E}-eA`7NlIZ~u>8^`7B(nw_(p?vExMoBQ8Neak{S^)|99waSyT8IAMpi2h zao0s0Y&kHs01oM{i#XV7xHzP{zr=yIKz%R{+;tI$KeGZ1|E7y5{3#ny_%~fd;aUrt zKtw-ziUtw@9g^SU3J89h% zKwDa`N%ioBtv{UShNyGj8h5a0a5Df5_X2IS@%b)4Gw1qwPTaYr7_@axE;F{8zCdUzu#|jAoO2m zA&to2Rv$03ziU7!E0Ph0_H#ju-!_t%uz$hjkDqpR=Z}fs)(edG7l?c#Mvd;kPTCdN zR*r8zZPQ8C^f%C;!e1dn@?QbBRT3Y$ZTeq9^E-wQ%TN0&NUkPZErE~Qj?oS9cBa_A zh<5C56hs;m?F%8X{;Gg>Ly`~Qj`3IETvem#nLj1)OZXMHo5i%wO}{FomHAhNw4WdO zCA8=K6@05Q(9{l72_L>ar`zzqm=?H8O7|uP{<6fcJJ*Q#oWH6&Nx#H)mA8hrN&YKf z@+!=)GdZ=Bu;i5`A4PTWI8smO;@jvh1{{ { + if (!process.env.USE_LIVE_SERVERS) { + procs.push( + // Run geth on port 8546 + await (async () => { + killProcessOnPort(8546) + const proc = spawn( + 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( + ' ' + ), + { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } + ) + + await waitForHealth('http://localhost:8546').catch() + return proc + })(), + //Run the substate node + (() => { + killProcessOnPort(9944) + return spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: polkadotSdkPath, + } + ) + })(), + // Run eth-rpc on 8545 + await (async () => { + killProcessOnPort(8545) + const proc = spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: polkadotSdkPath, + } + ) + await waitForHealth('http://localhost:8545').catch() + return proc + })() + ) + } +}) afterEach(() => { jsonRpcErrors.length = 0 diff --git a/substrate/frame/revive/rpc/examples/js/src/lib.ts b/substrate/frame/revive/rpc/examples/js/src/lib.ts index e1f0e780d95b..1470f492e34d 100644 --- a/substrate/frame/revive/rpc/examples/js/src/lib.ts +++ b/substrate/frame/revive/rpc/examples/js/src/lib.ts @@ -50,7 +50,6 @@ if (geth) { child.unref() await new Promise((resolve) => setTimeout(resolve, 500)) } - const rpcUrl = proxy ? 'http://localhost:8080' : westend diff --git a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts index 8289ac8b76e3..4983a6f3b301 100644 --- a/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts +++ b/substrate/frame/revive/rpc/examples/js/src/piggy-bank.ts @@ -4,7 +4,7 @@ import { parseEther } from 'viem' const hash = await walletClient.deployContract({ abi: PiggyBankAbi, - bytecode: getByteCode('piggyBank'), + bytecode: getByteCode('PiggyBank'), }) const deployReceipt = await walletClient.waitForTransactionReceipt({ hash }) const contractAddress = deployReceipt.contractAddress @@ -31,9 +31,7 @@ assert(contractAddress, 'Contract address should be set') value: parseEther('10'), }) - request.nonce = 0 const hash = await walletClient.writeContract(request) - const receipt = await walletClient.waitForTransactionReceipt({ hash }) console.log(`Deposit receipt: ${receipt.status}`) } diff --git a/substrate/frame/revive/rpc/examples/js/src/spammer.ts b/substrate/frame/revive/rpc/examples/js/src/spammer.ts new file mode 100644 index 000000000000..c038afa71f0a --- /dev/null +++ b/substrate/frame/revive/rpc/examples/js/src/spammer.ts @@ -0,0 +1,104 @@ +import { spawn } from 'bun' +import { + createEnv, + getByteCode, + killProcessOnPort, + polkadotSdkPath, + timeout, + wait, + waitForHealth, +} from './util' +import { FlipperAbi } from '../abi/Flipper' + +//Run the substate node +console.log('🚀 Start kitchensink...') +killProcessOnPort(9944) +spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: polkadotSdkPath, + } +) + +// Run eth-indexer +console.log('🔍 Start indexer...') +spawn( + [ + './target/debug/eth-indexer', + '--node-rpc-url=ws://localhost:9944', + '-l=eth-rpc=debug', + '--database-url ${polkadotSdkPath}/substrate/frame/revive/rpc/tx_hashes.db', + ], + { + stdout: Bun.file('/tmp/eth-indexer.out.log'), + stderr: Bun.file('/tmp/eth-indexer.err.log'), + cwd: polkadotSdkPath, + } +) + +// Run eth-rpc on 8545 +console.log('💻 Start eth-rpc...') +killProcessOnPort(8545) +spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: polkadotSdkPath, + } +) +await waitForHealth('http://localhost:8545').catch() + +const env = await createEnv('kitchensink') +const wallet = env.accountWallet + +console.log('🚀 Deploy flipper...') +const hash = await wallet.deployContract({ + abi: FlipperAbi, + bytecode: getByteCode('Flipper'), +}) + +const deployReceipt = await wallet.waitForTransactionReceipt({ hash }) +if (!deployReceipt.contractAddress) throw new Error('Contract address should be set') +const flipperAddr = deployReceipt.contractAddress + +let nonce = await wallet.getTransactionCount(wallet.account) +let callCount = 0 + +console.log('🔄 Starting nonce:', nonce) +console.log('🔄 Starting loop...') +try { + while (true) { + callCount++ + console.log(`🔄 Call flip (${callCount})...`) + const { request } = await wallet.simulateContract({ + account: wallet.account, + address: flipperAddr, + abi: FlipperAbi, + functionName: 'flip', + }) + + console.log(`🔄 Submit flip (call ${callCount}...`) + + await Promise.race([ + (async () => { + const hash = await wallet.writeContract(request) + await wallet.waitForTransactionReceipt({ hash }) + })(), + timeout(15_000), + ]) + } +} catch (err) { + console.error('Failed with error:', err) +} diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts b/substrate/frame/revive/rpc/examples/js/src/util.ts similarity index 62% rename from substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts rename to substrate/frame/revive/rpc/examples/js/src/util.ts index 3db2453f2475..bdc64eea1ef5 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff-setup.ts +++ b/substrate/frame/revive/rpc/examples/js/src/util.ts @@ -1,10 +1,10 @@ -import { spawn, spawnSync, Subprocess } from 'bun' +import { spawnSync } from 'bun' import { resolve } from 'path' import { readFileSync } from 'fs' import { createWalletClient, defineChain, Hex, http, publicActions } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' +import { privateKeyToAccount, nonceManager } from 'viem/accounts' -export function getByteCode(name: string, evm: boolean): Hex { +export function getByteCode(name: string, evm: boolean = false): Hex { const bytecode = evm ? readFileSync(`evm/${name}.bin`) : readFileSync(`pvm/${name}.polkavm`) return `0x${Buffer.from(bytecode).toString('hex')}` } @@ -15,6 +15,8 @@ export type JsonRpcError = { data: Hex } +export const polkadotSdkPath = resolve(__dirname, '../../../../../../..') + export function killProcessOnPort(port: number) { // Check which process is using the specified port const result = spawnSync(['lsof', '-ti', `:${port}`]) @@ -76,7 +78,8 @@ export async function createEnv(name: 'geth' | 'kitchensink') { const accountWallet = createWalletClient({ account: privateKeyToAccount( - '0xa872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f' + '0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133', + { nonceManager } ), transport, chain, @@ -85,6 +88,14 @@ export async function createEnv(name: 'geth' | 'kitchensink') { return { serverWallet, accountWallet, evm: name == 'geth' } } +export function wait(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +export function timeout(ms: number) { + return new Promise((_resolve, reject) => setTimeout(() => reject(new Error('timeout hit')), ms)) +} + // wait for http request to return 200 export function waitForHealth(url: string) { return new Promise((resolve, reject) => { @@ -120,58 +131,3 @@ export function waitForHealth(url: string) { }, 1000) }) } - -export const procs: Subprocess[] = [] -const polkadotSdkPath = resolve(__dirname, '../../../../../../..') -if (!process.env.USE_LIVE_SERVERS) { - procs.push( - // Run geth on port 8546 - await (async () => { - killProcessOnPort(8546) - const proc = spawn( - 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( - ' ' - ), - { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } - ) - - await waitForHealth('http://localhost:8546').catch() - return proc - })(), - //Run the substate node - (() => { - killProcessOnPort(9944) - return spawn( - [ - './target/debug/substrate-node', - '--dev', - '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', - ], - { - stdout: Bun.file('/tmp/kitchensink.out.log'), - stderr: Bun.file('/tmp/kitchensink.err.log'), - cwd: polkadotSdkPath, - } - ) - })(), - // Run eth-rpc on 8545 - await (async () => { - killProcessOnPort(8545) - const proc = spawn( - [ - './target/debug/eth-rpc', - '--dev', - '--node-rpc-url=ws://localhost:9944', - '-l=rpc-metrics=debug,eth-rpc=debug', - ], - { - stdout: Bun.file('/tmp/eth-rpc.out.log'), - stderr: Bun.file('/tmp/eth-rpc.err.log'), - cwd: polkadotSdkPath, - } - ) - await waitForHealth('http://localhost:8545').catch() - return proc - })() - ) -} diff --git a/substrate/frame/revive/rpc/examples/westend_local_network.toml b/substrate/frame/revive/rpc/examples/westend_local_network.toml index 28295db76133..76561be814ec 100644 --- a/substrate/frame/revive/rpc/examples/westend_local_network.toml +++ b/substrate/frame/revive/rpc/examples/westend_local_network.toml @@ -29,13 +29,9 @@ name = "asset-hub-westend-collator1" rpc_port = 9011 ws_port = 9944 command = "{{POLKADOT_PARACHAIN_BINARY}}" -args = [ - "-lparachain=debug,runtime::revive=debug", -] +args = ["-lparachain=debug,runtime::revive=debug"] [[parachains.collators]] name = "asset-hub-westend-collator2" command = "{{POLKADOT_PARACHAIN_BINARY}}" -args = [ - "-lparachain=debug,runtime::revive=debug", -] +args = ["-lparachain=debug,runtime::revive=debug"] diff --git a/substrate/frame/revive/rpc/migrations/20241205165418_create_transaction_hashes.sql b/substrate/frame/revive/rpc/migrations/20241205165418_create_transaction_hashes.sql new file mode 100644 index 000000000000..43405bea9d04 --- /dev/null +++ b/substrate/frame/revive/rpc/migrations/20241205165418_create_transaction_hashes.sql @@ -0,0 +1,15 @@ +-- Create DB: +-- DATABASE_URL="..." cargo sqlx database create +-- +-- Run migration: +-- DATABASE_URL="..." cargo sqlx migrate run +-- +-- Update compile time artifacts: +-- DATABASE_URL="..." cargo sqlx prepare +CREATE TABLE transaction_hashes ( + transaction_hash CHAR(64) NOT NULL PRIMARY KEY, + transaction_index INTEGER NOT NULL, + block_hash CHAR(64) NOT NULL +); + +CREATE INDEX idx_block_hash ON transaction_hashes (block_hash); diff --git a/substrate/frame/revive/rpc/src/block_info_provider.rs b/substrate/frame/revive/rpc/src/block_info_provider.rs new file mode 100644 index 000000000000..0e91869cddaa --- /dev/null +++ b/substrate/frame/revive/rpc/src/block_info_provider.rs @@ -0,0 +1,250 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + client::{SubstrateBlock, SubstrateBlockNumber}, + subxt_client::SrcChainConfig, + ClientError, +}; +use jsonrpsee::core::async_trait; +use sp_core::H256; +use std::{ + collections::{HashMap, VecDeque}, + sync::Arc, +}; +use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient}; +use tokio::sync::RwLock; + +/// BlockInfoProvider cache and retrieves information about blocks. +#[async_trait] +pub trait BlockInfoProvider: Send + Sync { + /// Cache a new block and return the pruned block hash. + async fn cache_block(&self, block: SubstrateBlock) -> Option; + + /// Return the latest ingested block. + async fn latest_block(&self) -> Option>; + + /// Get block by block_number. + async fn block_by_number( + &self, + block_number: SubstrateBlockNumber, + ) -> Result>, ClientError>; + + /// Get block by block hash. + async fn block_by_hash(&self, hash: &H256) -> Result>, ClientError>; +} + +/// Provides information about blocks. +#[derive(Clone)] +pub struct BlockInfoProviderImpl { + /// The shared in memory cache. + cache: Arc>>, + + /// The rpc client, used to fetch blocks not in the cache. + rpc: LegacyRpcMethods, + + /// The api client, used to fetch blocks not in the cache. + api: OnlineClient, +} + +impl BlockInfoProviderImpl { + pub fn new( + cache_size: usize, + api: OnlineClient, + rpc: LegacyRpcMethods, + ) -> Self { + Self { api, rpc, cache: Arc::new(RwLock::new(BlockCache::new(cache_size))) } + } + + async fn cache(&self) -> tokio::sync::RwLockReadGuard<'_, BlockCache> { + self.cache.read().await + } +} + +#[async_trait] +impl BlockInfoProvider for BlockInfoProviderImpl { + async fn cache_block(&self, block: SubstrateBlock) -> Option { + let mut cache = self.cache.write().await; + cache.insert(block) + } + + async fn latest_block(&self) -> Option> { + let cache = self.cache().await; + cache.buffer.back().cloned() + } + + async fn block_by_number( + &self, + block_number: SubstrateBlockNumber, + ) -> Result>, ClientError> { + let cache = self.cache().await; + if let Some(block) = cache.blocks_by_number.get(&block_number).cloned() { + return Ok(Some(block)); + } + + let Some(hash) = self.rpc.chain_get_block_hash(Some(block_number.into())).await? else { + return Ok(None); + }; + + self.block_by_hash(&hash).await + } + + async fn block_by_hash(&self, hash: &H256) -> Result>, ClientError> { + let cache = self.cache().await; + if let Some(block) = cache.blocks_by_hash.get(hash).cloned() { + return Ok(Some(block)); + } + + match self.api.blocks().at(*hash).await { + Ok(block) => Ok(Some(Arc::new(block))), + Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => Ok(None), + Err(err) => Err(err.into()), + } + } +} + +/// The cache maintains a buffer of the last N blocks, +struct BlockCache { + /// The maximum buffer's size. + max_cache_size: usize, + + /// A double-ended queue of the last N blocks. + /// The most recent block is at the back of the queue, and the oldest block is at the front. + buffer: VecDeque>, + + /// A map of blocks by block number. + blocks_by_number: HashMap>, + + /// A map of blocks by block hash. + blocks_by_hash: HashMap>, +} + +/// Provides information about a block, +/// This is an abstratction on top of [`SubstrateBlock`] used to test the [`BlockCache`]. +/// Can be removed once https://github.com/paritytech/subxt/issues/1883 is fixed. +trait BlockInfo { + /// Returns the block hash. + fn hash(&self) -> H256; + /// Returns the block number. + fn number(&self) -> SubstrateBlockNumber; +} + +impl BlockInfo for SubstrateBlock { + fn hash(&self) -> H256 { + SubstrateBlock::hash(self) + } + fn number(&self) -> u32 { + SubstrateBlock::number(self) + } +} + +impl BlockCache { + /// Create a new cache with the given maximum buffer size. + pub fn new(max_cache_size: usize) -> Self { + Self { + max_cache_size, + buffer: Default::default(), + blocks_by_number: Default::default(), + blocks_by_hash: Default::default(), + } + } + + /// Insert an entry into the cache, and prune the oldest entry if the cache is full. + pub fn insert(&mut self, block: B) -> Option { + let mut pruned_block_hash = None; + if self.buffer.len() >= self.max_cache_size { + if let Some(block) = self.buffer.pop_front() { + let hash = block.hash(); + self.blocks_by_hash.remove(&hash); + self.blocks_by_number.remove(&block.number()); + pruned_block_hash = Some(hash); + } + } + + let block = Arc::new(block); + self.buffer.push_back(block.clone()); + self.blocks_by_number.insert(block.number(), block.clone()); + self.blocks_by_hash.insert(block.hash(), block); + pruned_block_hash + } +} + +#[cfg(test)] +pub mod test { + use super::*; + + struct MockBlock { + block_number: SubstrateBlockNumber, + block_hash: H256, + } + + impl BlockInfo for MockBlock { + fn hash(&self) -> H256 { + self.block_hash + } + + fn number(&self) -> u32 { + self.block_number + } + } + + #[test] + fn cache_insert_works() { + let mut cache = BlockCache::::new(2); + + let pruned = cache.insert(MockBlock { block_number: 1, block_hash: H256::from([1; 32]) }); + assert_eq!(pruned, None); + + let pruned = cache.insert(MockBlock { block_number: 2, block_hash: H256::from([2; 32]) }); + assert_eq!(pruned, None); + + let pruned = cache.insert(MockBlock { block_number: 3, block_hash: H256::from([3; 32]) }); + assert_eq!(pruned, Some(H256::from([1; 32]))); + + assert_eq!(cache.buffer.len(), 2); + assert_eq!(cache.blocks_by_number.len(), 2); + assert_eq!(cache.blocks_by_hash.len(), 2); + } + + /// A Noop BlockInfoProvider used to test [`db::DBReceiptProvider`]. + pub struct MockBlockInfoProvider; + + #[async_trait] + impl BlockInfoProvider for MockBlockInfoProvider { + async fn cache_block(&self, _block: SubstrateBlock) -> Option { + None + } + + async fn latest_block(&self) -> Option> { + None + } + + async fn block_by_number( + &self, + _block_number: SubstrateBlockNumber, + ) -> Result>, ClientError> { + Ok(None) + } + + async fn block_by_hash( + &self, + _hash: &H256, + ) -> Result>, ClientError> { + Ok(None) + } + } +} diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index c0f81fcafd77..d63d596ab7a8 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -16,7 +16,9 @@ // limitations under the License. //! The Ethereum JSON-RPC server. use crate::{ - client::Client, EthRpcServer, EthRpcServerImpl, SystemHealthRpcServer, + client::{connect, Client}, + BlockInfoProvider, BlockInfoProviderImpl, CacheReceiptProvider, DBReceiptProvider, + EthRpcServer, EthRpcServerImpl, ReceiptProvider, SystemHealthRpcServer, SystemHealthRpcServerImpl, }; use clap::Parser; @@ -27,6 +29,7 @@ use sc_service::{ config::{PrometheusConfig, RpcConfiguration}, start_rpc_servers, TaskManager, }; +use std::sync::Arc; // Default port if --prometheus-port is not specified const DEFAULT_PROMETHEUS_PORT: u16 = 9616; @@ -42,6 +45,21 @@ pub struct CliCommand { #[clap(long, default_value = "ws://127.0.0.1:9944")] pub node_rpc_url: String, + /// The maximum number of blocks to cache in memory. + #[clap(long, default_value = "256")] + pub cache_size: usize, + + /// The database used to store Ethereum transaction hashes. + /// This is only useful if the node needs to act as an archive node and respond to Ethereum RPC + /// queries for transactions that are not in the in memory cache. + #[clap(long)] + pub database_url: Option, + + /// If true, we will only read from the database and not write to it. + /// Only useful if `--database-url` is specified. + #[clap(long, default_value = "true")] + pub database_read_only: bool, + #[allow(missing_docs)] #[clap(flatten)] pub shared_params: SharedParams, @@ -78,7 +96,16 @@ fn init_logger(params: &SharedParams) -> anyhow::Result<()> { /// Start the JSON-RPC server using the given command line arguments. pub fn run(cmd: CliCommand) -> anyhow::Result<()> { - let CliCommand { rpc_params, prometheus_params, node_rpc_url, shared_params, .. } = cmd; + let CliCommand { + rpc_params, + prometheus_params, + node_rpc_url, + cache_size, + database_url, + database_read_only, + shared_params, + .. + } = cmd; #[cfg(not(test))] init_logger(&shared_params)?; @@ -110,19 +137,42 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let tokio_runtime = sc_cli::build_runtime()?; let tokio_handle = tokio_runtime.handle(); - let signals = tokio_runtime.block_on(async { Signals::capture() })?; let mut task_manager = TaskManager::new(tokio_handle.clone(), prometheus_registry)?; let essential_spawn_handle = task_manager.spawn_essential_handle(); let gen_rpc_module = || { let signals = tokio_runtime.block_on(async { Signals::capture() })?; - let fut = Client::from_url(&node_rpc_url, &essential_spawn_handle).fuse(); + let fut = async { + let (api, rpc_client, rpc) = connect(&node_rpc_url).await?; + let block_provider: Arc = + Arc::new(BlockInfoProviderImpl::new(cache_size, api.clone(), rpc.clone())); + let receipt_provider: Arc = + if let Some(database_url) = database_url.as_ref() { + Arc::new(( + CacheReceiptProvider::default(), + DBReceiptProvider::new( + database_url, + database_read_only, + block_provider.clone(), + ) + .await?, + )) + } else { + Arc::new(CacheReceiptProvider::default()) + }; + + let client = + Client::new(api, rpc_client, rpc, block_provider, receipt_provider).await?; + client.subscribe_and_cache_blocks(&essential_spawn_handle); + Ok::<_, crate::ClientError>(client) + } + .fuse(); pin_mut!(fut); match tokio_handle.block_on(signals.try_until_signal(fut)) { Ok(Ok(client)) => rpc_module(is_dev, client), Ok(Err(err)) => { - log::error!("Error connecting to the node at {node_rpc_url}: {err}"); + log::error!("Error initializing: {err:?}"); Err(sc_service::Error::Application(err.into())) }, Err(_) => Err(sc_service::Error::Application("Client connection interrupted".into())), @@ -142,6 +192,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { start_rpc_servers(&rpc_config, prometheus_registry, tokio_handle, gen_rpc_module, None)?; task_manager.keep_alive(rpc_server_handle); + let signals = tokio_runtime.block_on(async { Signals::capture() })?; tokio_runtime.block_on(signals.run_until_signal(task_manager.future().fuse()))?; Ok(()) } diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index de97844eccbb..cd0effe7faf2 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -17,30 +17,23 @@ //! The client connects to the source substrate chain //! and is used by the rpc server to query and send transactions to the substrate chain. use crate::{ + extract_receipts_from_block, runtime::gas_from_fee, subxt_client::{ - revive::{calls::types::EthTransact, events::ContractEmitted}, - runtime_types::pallet_revive::storage::ContractInfo, + revive::calls::types::EthTransact, runtime_types::pallet_revive::storage::ContractInfo, }, - LOG_TARGET, + BlockInfoProvider, ReceiptProvider, TransactionInfo, LOG_TARGET, }; -use futures::{stream, StreamExt}; use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned}; use pallet_revive::{ - create1, evm::{ - Block, BlockNumberOrTag, BlockNumberOrTagOrHash, Bytes256, GenericTransaction, Log, - ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256, + Block, BlockNumberOrTag, BlockNumberOrTagOrHash, GenericTransaction, ReceiptInfo, + SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256, }, EthTransactError, EthTransactInfo, }; -use sp_core::keccak_256; use sp_weights::Weight; -use std::{ - collections::{HashMap, VecDeque}, - sync::Arc, - time::Duration, -}; +use std::{ops::ControlFlow, sync::Arc, time::Duration}; use subxt::{ backend::{ legacy::{rpc_methods::SystemHealth, LegacyRpcMethods}, @@ -54,11 +47,10 @@ use subxt::{ storage::Storage, Config, OnlineClient, }; -use subxt_client::transaction_payment::events::TransactionFeePaid; use thiserror::Error; -use tokio::sync::{watch::Sender, RwLock}; +use tokio::{sync::RwLock, try_join}; -use crate::subxt_client::{self, system::events::ExtrinsicSuccess, SrcChainConfig}; +use crate::subxt_client::{self, SrcChainConfig}; /// The substrate block type. pub type SubstrateBlock = subxt::blocks::Block>; @@ -75,29 +67,6 @@ pub type Shared = Arc>; /// The runtime balance type. pub type Balance = u128; -/// The cache maintains a buffer of the last N blocks, -#[derive(Default)] -struct BlockCache { - /// A double-ended queue of the last N blocks. - /// The most recent block is at the back of the queue, and the oldest block is at the front. - buffer: VecDeque>, - - /// A map of blocks by block number. - blocks_by_number: HashMap>, - - /// A map of blocks by block hash. - blocks_by_hash: HashMap>, - - /// A map of receipts by hash. - receipts_by_hash: HashMap, - - /// A map of Signed transaction by hash. - signed_tx_by_hash: HashMap, - - /// A map of receipt hashes by block hash. - tx_hashes_by_block_and_index: HashMap>, -} - /// Unwrap the original `jsonrpsee::core::client::Error::Call` error. fn unwrap_call_err(err: &subxt::error::RpcError) -> Option { use subxt::backend::rpc::reconnecting_rpc_client; @@ -167,6 +136,9 @@ pub enum ClientError { /// A [`RpcError`] wrapper error. #[error(transparent)] RpcError(#[from] RpcError), + /// A [`sqlx::Error`] wrapper error. + #[error(transparent)] + SqlxError(#[from] sqlx::Error), /// A [`codec::Error`] wrapper error. #[error(transparent)] CodecError(#[from] codec::Error), @@ -179,9 +151,18 @@ pub enum ClientError { /// The block hash was not found. #[error("hash not found")] BlockNotFound, + + #[error("No Ethereum extrinsic found")] + EthExtrinsicNotFound, /// The transaction fee could not be found #[error("transactionFeePaid event not found")] TxFeeNotFound, + /// Failed to decode a raw payload into a signed transaction. + #[error("Failed to decode a raw payload into a signed transaction")] + TxDecodingFailed, + /// Failed to recover eth address. + #[error("failed to recover eth address")] + RecoverEthAddressFailed, /// The cache is empty. #[error("cache is empty")] CacheEmpty, @@ -214,163 +195,18 @@ impl From for ErrorObjectOwned { } } -/// The number of recent blocks maintained by the cache. -/// For each block in the cache, we also store the EVM transaction receipts. -pub const CACHE_SIZE: usize = 256; - -impl BlockCache { - fn latest_block(&self) -> Option<&Arc> { - self.buffer.back() - } - - /// Insert an entry into the cache, and prune the oldest entry if the cache is full. - fn insert(&mut self, block: SubstrateBlock) { - if self.buffer.len() >= N { - if let Some(block) = self.buffer.pop_front() { - log::trace!(target: LOG_TARGET, "Pruning block: {}", block.number()); - let hash = block.hash(); - self.blocks_by_hash.remove(&hash); - self.blocks_by_number.remove(&block.number()); - if let Some(entries) = self.tx_hashes_by_block_and_index.remove(&hash) { - for hash in entries.values() { - self.receipts_by_hash.remove(hash); - } - } - } - } - - let block = Arc::new(block); - self.buffer.push_back(block.clone()); - self.blocks_by_number.insert(block.number(), block.clone()); - self.blocks_by_hash.insert(block.hash(), block); - } -} - /// A client connect to a node and maintains a cache of the last `CACHE_SIZE` blocks. #[derive(Clone)] pub struct Client { - /// The inner state of the client. - inner: Arc, - /// A watch channel to signal cache updates. - pub updates: tokio::sync::watch::Receiver<()>, -} - -/// The inner state of the client. -struct ClientInner { api: OnlineClient, rpc_client: ReconnectingRpcClient, rpc: LegacyRpcMethods, - cache: Shared>, + receipt_provider: Arc, + block_provider: Arc, chain_id: u64, max_block_weight: Weight, } -impl ClientInner { - /// Create a new client instance connecting to the substrate node at the given URL. - async fn from_url(url: &str) -> Result { - let rpc_client = ReconnectingRpcClient::builder() - .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) - .build(url.to_string()) - .await?; - - let api = OnlineClient::::from_rpc_client(rpc_client.clone()).await?; - let cache = Arc::new(RwLock::new(BlockCache::::default())); - - let rpc = LegacyRpcMethods::::new(RpcClient::new(rpc_client.clone())); - - let (chain_id, max_block_weight) = - tokio::try_join!(chain_id(&api), max_block_weight(&api))?; - - Ok(Self { api, rpc_client, rpc, cache, chain_id, max_block_weight }) - } - - /// Get the receipt infos from the extrinsics in a block. - async fn receipt_infos( - &self, - block: &SubstrateBlock, - ) -> Result, ClientError> { - // Get extrinsics from the block - let extrinsics = block.extrinsics().await?; - - // Filter extrinsics from pallet_revive - let extrinsics = extrinsics.iter().flat_map(|ext| { - let call = ext.as_extrinsic::().ok()??; - let transaction_hash = H256(keccak_256(&call.payload)); - let signed_tx = TransactionSigned::decode(&call.payload).ok()?; - let from = signed_tx.recover_eth_address().ok()?; - let tx_info = GenericTransaction::from_signed(signed_tx.clone(), Some(from)); - let contract_address = if tx_info.to.is_none() { - Some(create1(&from, tx_info.nonce.unwrap_or_default().try_into().ok()?)) - } else { - None - }; - - Some((from, signed_tx, tx_info, transaction_hash, contract_address, ext)) - }); - - // Map each extrinsic to a receipt - stream::iter(extrinsics) - .map(|(from, signed_tx, tx_info, transaction_hash, contract_address, ext)| async move { - let events = ext.events().await?; - let tx_fees = - events.find_first::()?.ok_or(ClientError::TxFeeNotFound)?; - - let gas_price = tx_info.gas_price.unwrap_or_default(); - let gas_used = (tx_fees.tip.saturating_add(tx_fees.actual_fee)) - .checked_div(gas_price.as_u128()) - .unwrap_or_default(); - - let success = events.has::()?; - let transaction_index = ext.index(); - let block_hash = block.hash(); - let block_number = block.number().into(); - - // get logs from ContractEmitted event - let logs = events.iter() - .filter_map(|event_details| { - let event_details = event_details.ok()?; - let event = event_details.as_event::().ok()??; - - Some(Log { - address: event.contract, - topics: event.topics, - data: Some(event.data.into()), - block_number: Some(block_number), - transaction_hash, - transaction_index: Some(transaction_index.into()), - block_hash: Some(block_hash), - log_index: Some(event_details.index().into()), - ..Default::default() - }) - }).collect(); - - - log::debug!(target: LOG_TARGET, "Adding receipt for tx hash: {transaction_hash:?} - block: {block_number:?}"); - let receipt = ReceiptInfo::new( - block_hash, - block_number, - contract_address, - from, - logs, - tx_info.to, - gas_price, - gas_used.into(), - success, - transaction_hash, - transaction_index.into(), - tx_info.r#type.unwrap_or_default() - ); - - Ok::<_, ClientError>((receipt.transaction_hash, (signed_tx, receipt))) - }) - .buffer_unordered(10) - .collect::>>() - .await - .into_iter() - .collect::, _>>() - } -} - /// Fetch the chain ID from the substrate chain. async fn chain_id(api: &OnlineClient) -> Result { let query = subxt_client::constants().revive().chain_id(); @@ -395,23 +231,181 @@ async fn extract_block_timestamp(block: &SubstrateBlock) -> Option { Some(ext.value.now / 1000) } +/// Connect to a node at the given URL, and return the underlying API, RPC client, and legacy RPC +/// clients. +pub async fn connect( + node_rpc_url: &str, +) -> Result< + (OnlineClient, ReconnectingRpcClient, LegacyRpcMethods), + ClientError, +> { + log::info!(target: LOG_TARGET, "Connecting to node at: {node_rpc_url} ..."); + let rpc_client = ReconnectingRpcClient::builder() + .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) + .build(node_rpc_url.to_string()) + .await?; + log::info!(target: LOG_TARGET, "Connected to node at: {node_rpc_url}"); + + let api = OnlineClient::::from_rpc_client(rpc_client.clone()).await?; + let rpc = LegacyRpcMethods::::new(RpcClient::new(rpc_client.clone())); + Ok((api, rpc_client, rpc)) +} + impl Client { /// Create a new client instance. - /// The client will subscribe to new blocks and maintain a cache of [`CACHE_SIZE`] blocks. - pub async fn from_url( - url: &str, - spawn_handle: &sc_service::SpawnEssentialTaskHandle, + pub async fn new( + api: OnlineClient, + rpc_client: ReconnectingRpcClient, + rpc: LegacyRpcMethods, + block_provider: Arc, + receipt_provider: Arc, ) -> Result { - log::info!(target: LOG_TARGET, "Connecting to node at: {url} ..."); - let inner: Arc = Arc::new(ClientInner::from_url(url).await?); - log::info!(target: LOG_TARGET, "Connected to node at: {url}"); + let (chain_id, max_block_weight) = + tokio::try_join!(chain_id(&api), max_block_weight(&api))?; - let (tx, mut updates) = tokio::sync::watch::channel(()); + Ok(Self { + api, + rpc_client, + rpc, + receipt_provider, + block_provider, + chain_id, + max_block_weight, + }) + } - spawn_handle.spawn("subscribe-blocks", None, Self::subscribe_blocks(inner.clone(), tx)); + /// Subscribe to past blocks executing the callback for each block. + /// The subscription continues iterating past blocks until the closure returns + /// `ControlFlow::Break`. Blocks are iterated starting from the latest block and moving + /// backward. + #[allow(dead_code)] + async fn subscribe_past_blocks(&self, callback: F) -> Result<(), ClientError> + where + F: Fn(SubstrateBlock) -> Fut + Send + Sync, + Fut: std::future::Future, ClientError>> + Send, + { + log::info!(target: LOG_TARGET, "Subscribing to past blocks"); + let mut block = self.api.blocks().at_latest().await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to fetch latest block: {err:?}"); + })?; + + loop { + let block_number = block.number(); + log::debug!(target: LOG_TARGET, "Processing block {block_number}"); + + let parent_hash = block.header().parent_hash; + let control_flow = callback(block).await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to process block {block_number}: {err:?}"); + })?; + + match control_flow { + ControlFlow::Continue(_) => { + if block_number == 0 { + log::info!(target: LOG_TARGET, "All past blocks processed"); + return Ok(()); + } + block = self.api.blocks().at(parent_hash).await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to fetch block at {parent_hash:?}: {err:?}"); + })?; + }, + ControlFlow::Break(_) => { + log::info!(target: LOG_TARGET, "Stopping past block subscription at {block_number}"); + return Ok(()); + }, + } + } + } + + /// Subscribe to new best blocks, and execute the async closure with + /// the extracted block and ethereum transactions + async fn subscribe_new_blocks(&self, callback: F) -> Result<(), ClientError> + where + F: Fn(SubstrateBlock) -> Fut + Send + Sync, + Fut: std::future::Future> + Send, + { + log::info!(target: LOG_TARGET, "Subscribing to new blocks"); + let mut block_stream = match self.api.blocks().subscribe_best().await { + Ok(s) => s, + Err(err) => { + log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); + return Err(err.into()); + }, + }; + + while let Some(block) = block_stream.next().await { + let block = match block { + Ok(block) => block, + Err(err) => { + if err.is_disconnected_will_reconnect() { + log::warn!( + target: LOG_TARGET, + "The RPC connection was lost and we may have missed a few blocks" + ); + continue; + } + + log::error!(target: LOG_TARGET, "Failed to fetch block: {err:?}"); + return Err(err.into()); + }, + }; + + log::debug!(target: LOG_TARGET, "Pushing block: {}", block.number()); + callback(block).await?; + } - updates.changed().await.expect("tx is not dropped"); - Ok(Self { inner, updates }) + log::info!(target: LOG_TARGET, "Block subscription ended"); + Ok(()) + } + + /// Start the block subscription, and populate the block cache. + pub fn subscribe_and_cache_blocks(&self, spawn_handle: &sc_service::SpawnEssentialTaskHandle) { + let client = self.clone(); + spawn_handle.spawn("subscribe-blocks", None, async move { + let res = client + .subscribe_new_blocks(|block| async { + let receipts = extract_receipts_from_block(&block).await?; + + client.receipt_provider.insert(&block.hash(), &receipts).await; + if let Some(pruned) = client.block_provider.cache_block(block).await { + client.receipt_provider.remove(&pruned).await; + } + + Ok(()) + }) + .await; + + if let Err(err) = res { + log::error!(target: LOG_TARGET, "Block subscription error: {err:?}"); + } + }); + } + + /// Start the block subscription, and populate the block cache. + pub async fn subscribe_and_cache_receipts( + &self, + oldest_block: Option, + ) -> Result<(), ClientError> { + let new_blocks_fut = self.subscribe_new_blocks(|block| async move { + let receipts = extract_receipts_from_block(&block).await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to extract receipts from block: {err:?}"); + })?; + self.receipt_provider.insert(&block.hash(), &receipts).await; + Ok(()) + }); + + let Some(oldest_block) = oldest_block else { return new_blocks_fut.await }; + + let old_blocks_fut = self.subscribe_past_blocks(|block| async move { + let receipts = extract_receipts_from_block(&block).await?; + self.receipt_provider.insert(&block.hash(), &receipts).await; + if block.number() == oldest_block { + Ok(ControlFlow::Break(())) + } else { + Ok(ControlFlow::Continue(())) + } + }); + + try_join!(new_blocks_fut, old_blocks_fut).map(|_| ()) } /// Expose the storage API. @@ -425,14 +419,14 @@ impl Client { (*block_number).try_into().map_err(|_| ClientError::ConversionFailed)?; let hash = self.get_block_hash(n).await?.ok_or(ClientError::BlockNotFound)?; - Ok(self.inner.api.storage().at(hash)) + Ok(self.api.storage().at(hash)) }, - BlockNumberOrTagOrHash::H256(hash) => Ok(self.inner.api.storage().at(*hash)), + BlockNumberOrTagOrHash::H256(hash) => Ok(self.api.storage().at(*hash)), BlockNumberOrTagOrHash::BlockTag(_) => { if let Some(block) = self.latest_block().await { - return Ok(self.inner.api.storage().at(block.hash())); + return Ok(self.api.storage().at(block.hash())); } - let storage = self.inner.api.storage().at_latest().await?; + let storage = self.api.storage().at_latest().await?; Ok(storage) }, } @@ -452,90 +446,24 @@ impl Client { (*block_number).try_into().map_err(|_| ClientError::ConversionFailed)?; let hash = self.get_block_hash(n).await?.ok_or(ClientError::BlockNotFound)?; - Ok(self.inner.api.runtime_api().at(hash)) + Ok(self.api.runtime_api().at(hash)) }, - BlockNumberOrTagOrHash::H256(hash) => Ok(self.inner.api.runtime_api().at(*hash)), + BlockNumberOrTagOrHash::H256(hash) => Ok(self.api.runtime_api().at(*hash)), BlockNumberOrTagOrHash::BlockTag(_) => { if let Some(block) = self.latest_block().await { - return Ok(self.inner.api.runtime_api().at(block.hash())); + return Ok(self.api.runtime_api().at(block.hash())); } - let api = self.inner.api.runtime_api().at_latest().await?; + let api = self.api.runtime_api().at_latest().await?; Ok(api) }, } } - /// Subscribe to new blocks and update the cache. - async fn subscribe_blocks(inner: Arc, tx: Sender<()>) { - log::info!(target: LOG_TARGET, "Subscribing to new blocks"); - let mut block_stream = match inner.as_ref().api.blocks().subscribe_best().await { - Ok(s) => s, - Err(err) => { - log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); - return; - }, - }; - - while let Some(block) = block_stream.next().await { - let block = match block { - Ok(block) => block, - Err(err) => { - if err.is_disconnected_will_reconnect() { - log::warn!( - target: LOG_TARGET, - "The RPC connection was lost and we may have missed a few blocks" - ); - continue; - } - - log::error!(target: LOG_TARGET, "Failed to fetch block: {err:?}"); - return; - }, - }; - - log::trace!(target: LOG_TARGET, "Pushing block: {}", block.number()); - let mut cache = inner.cache.write().await; - - let receipts = inner - .receipt_infos(&block) - .await - .inspect_err(|err| { - log::error!(target: LOG_TARGET, "Failed to get receipts: {err:?}"); - }) - .unwrap_or_default(); - - if !receipts.is_empty() { - let values = receipts - .iter() - .map(|(hash, (_, receipt))| (receipt.transaction_index, *hash)) - .collect::>(); - - cache.tx_hashes_by_block_and_index.insert(block.hash(), values); - - cache - .receipts_by_hash - .extend(receipts.iter().map(|(hash, (_, receipt))| (*hash, receipt.clone()))); - - cache.signed_tx_by_hash.extend( - receipts.iter().map(|(hash, (signed_tx, _))| (*hash, signed_tx.clone())), - ) - } - - cache.insert(block); - tx.send_replace(()); - } - - log::info!(target: LOG_TARGET, "Block subscription ended"); - } -} - -impl Client { /// Get the most recent block stored in the cache. pub async fn latest_block(&self) -> Option> { - let cache = self.inner.cache.read().await; - let block = cache.latest_block()?; - Some(block.clone()) + let block = self.block_provider.latest_block().await?; + Some(block) } /// Expose the transaction API. @@ -543,23 +471,22 @@ impl Client { &self, call: subxt::tx::DefaultPayload, ) -> Result { - let ext = self.inner.api.tx().create_unsigned(&call).map_err(ClientError::from)?; + let ext = self.api.tx().create_unsigned(&call).map_err(ClientError::from)?; let hash = ext.submit().await?; Ok(hash) } /// Get an EVM transaction receipt by hash. pub async fn receipt(&self, tx_hash: &H256) -> Option { - let cache = self.inner.cache.read().await; - cache.receipts_by_hash.get(tx_hash).cloned() + self.receipt_provider.receipt_by_hash(tx_hash).await } /// Get the syncing status of the chain. pub async fn syncing(&self) -> Result { - let health = self.inner.rpc.system_health().await?; + let health = self.rpc.system_health().await?; let status = if health.is_syncing { - let client = RpcClient::new(self.inner.rpc_client.clone()); + let client = RpcClient::new(self.rpc_client.clone()); let sync_state: sc_rpc::system::SyncState = client.request("system_syncState", Default::default()).await?; @@ -582,27 +509,23 @@ impl Client { block_hash: &H256, transaction_index: &U256, ) -> Option { - let cache = self.inner.cache.read().await; - let receipt_hash = - cache.tx_hashes_by_block_and_index.get(block_hash)?.get(transaction_index)?; - let receipt = cache.receipts_by_hash.get(receipt_hash)?; - Some(receipt.clone()) + self.receipt_provider + .receipt_by_block_hash_and_index(block_hash, transaction_index) + .await } pub async fn signed_tx_by_hash(&self, tx_hash: &H256) -> Option { - let cache = self.inner.cache.read().await; - cache.signed_tx_by_hash.get(tx_hash).cloned() + self.receipt_provider.signed_tx_by_hash(tx_hash).await } /// Get receipts count per block. pub async fn receipts_count_per_block(&self, block_hash: &SubstrateBlockHash) -> Option { - let cache = self.inner.cache.read().await; - cache.tx_hashes_by_block_and_index.get(block_hash).map(|v| v.len()) + self.receipt_provider.receipts_count_per_block(block_hash).await } /// Get the system health. pub async fn system_health(&self) -> Result { - let health = self.inner.rpc.system_health().await?; + let health = self.rpc.system_health().await?; Ok(health) } @@ -697,8 +620,8 @@ impl Client { /// Get the block number of the latest block. pub async fn block_number(&self) -> Result { - let cache = self.inner.cache.read().await; - let latest_block = cache.buffer.back().ok_or(ClientError::CacheEmpty)?; + let latest_block = + self.block_provider.latest_block().await.ok_or(ClientError::CacheEmpty)?; Ok(latest_block.number()) } @@ -707,13 +630,8 @@ impl Client { &self, block_number: SubstrateBlockNumber, ) -> Result, ClientError> { - let cache = self.inner.cache.read().await; - if let Some(block) = cache.blocks_by_number.get(&block_number) { - return Ok(Some(block.hash())); - } - - let hash = self.inner.rpc.chain_get_block_hash(Some(block_number.into())).await?; - Ok(hash) + let maybe_block = self.block_provider.block_by_number(block_number).await?; + Ok(maybe_block.map(|block| block.hash())) } /// Get a block for the specified hash or number. @@ -727,8 +645,8 @@ impl Client { self.block_by_number(n).await }, BlockNumberOrTag::BlockTag(_) => { - let cache = self.inner.cache.read().await; - Ok(cache.buffer.back().cloned()) + let block = self.block_provider.latest_block().await; + Ok(block) }, } } @@ -738,16 +656,7 @@ impl Client { &self, hash: &SubstrateBlockHash, ) -> Result>, ClientError> { - let cache = self.inner.cache.read().await; - if let Some(block) = cache.blocks_by_hash.get(hash) { - return Ok(Some(block.clone())); - } - - match self.inner.api.blocks().at(*hash).await { - Ok(block) => Ok(Some(Arc::new(block))), - Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => Ok(None), - Err(err) => Err(err.into()), - } + self.block_provider.block_by_hash(hash).await } /// Get a block by number @@ -755,21 +664,16 @@ impl Client { &self, block_number: SubstrateBlockNumber, ) -> Result>, ClientError> { - let cache = self.inner.cache.read().await; - if let Some(block) = cache.blocks_by_number.get(&block_number) { - return Ok(Some(block.clone())); - } - - let Some(hash) = self.get_block_hash(block_number).await? else { - return Ok(None); - }; - - self.block_by_hash(&hash).await + self.block_provider.block_by_number(block_number).await } /// Get the EVM block for the given hash. - pub async fn evm_block(&self, block: Arc) -> Result { - let runtime_api = self.inner.api.runtime_api().at(block.hash()); + pub async fn evm_block( + &self, + block: Arc, + hydrated_transactions: bool, + ) -> Result { + let runtime_api = self.api.runtime_api().at(block.hash()); let max_fee = Self::weight_to_fee(&runtime_api, self.max_block_weight()).await?; let gas_limit = gas_from_fee(max_fee); @@ -781,6 +685,23 @@ impl Client { let state_root = header.state_root.0.into(); let extrinsics_root = header.extrinsics_root.0.into(); + let receipts = extract_receipts_from_block(&block).await?; + let gas_used = + receipts.iter().fold(U256::zero(), |acc, (_, receipt)| acc + receipt.gas_used); + let transactions = if hydrated_transactions { + receipts + .into_iter() + .map(|(signed_tx, receipt)| TransactionInfo::new(receipt, signed_tx)) + .collect::>() + .into() + } else { + receipts + .into_iter() + .map(|(_, receipt)| receipt.transaction_hash) + .collect::>() + .into() + }; + Ok(Block { hash: block.hash(), parent_hash, @@ -789,9 +710,11 @@ impl Client { number: header.number.into(), timestamp: timestamp.into(), difficulty: Some(0u32.into()), + base_fee_per_gas: Some(crate::GAS_PRICE.into()), gas_limit, - logs_bloom: Bytes256([0u8; 256]), + gas_used, receipts_root: extrinsics_root, + transactions, ..Default::default() }) } @@ -811,11 +734,11 @@ impl Client { /// Get the chain ID. pub fn chain_id(&self) -> u64 { - self.inner.chain_id + self.chain_id } /// Get the Max Block Weight. pub fn max_block_weight(&self) -> Weight { - self.inner.max_block_weight + self.max_block_weight } } diff --git a/substrate/frame/revive/rpc/src/eth-indexer.rs b/substrate/frame/revive/rpc/src/eth-indexer.rs new file mode 100644 index 000000000000..3e7f6b6fa91b --- /dev/null +++ b/substrate/frame/revive/rpc/src/eth-indexer.rs @@ -0,0 +1,88 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//! The Ethereum JSON-RPC server. +use clap::Parser; +use pallet_revive_eth_rpc::{ + client::{connect, Client, SubstrateBlockNumber}, + BlockInfoProvider, BlockInfoProviderImpl, DBReceiptProvider, ReceiptProvider, +}; +use sc_cli::SharedParams; +use std::sync::Arc; + +// Parsed command instructions from the command line +#[derive(Parser, Debug)] +#[clap(author, about, version)] +pub struct CliCommand { + /// The node url to connect to + #[clap(long, default_value = "ws://127.0.0.1:9944")] + pub node_rpc_url: String, + + /// Specifies the block number to start indexing from, going backwards from the current block. + /// If not provided, only new blocks will be indexed + #[clap(long)] + pub oldest_block: Option, + + /// The database used to store Ethereum transaction hashes. + #[clap(long)] + pub database_url: String, + + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, +} + +/// Initialize the logger +#[cfg(not(test))] +fn init_logger(params: &SharedParams) -> anyhow::Result<()> { + let mut logger = sc_cli::LoggerBuilder::new(params.log_filters().join(",")); + logger + .with_log_reloading(params.enable_log_reloading) + .with_detailed_output(params.detailed_log_output); + + if let Some(tracing_targets) = ¶ms.tracing_targets { + let tracing_receiver = params.tracing_receiver.into(); + logger.with_profiling(tracing_receiver, tracing_targets); + } + + if params.disable_log_color { + logger.with_colors(false); + } + + logger.init()?; + Ok(()) +} + +#[tokio::main] +pub async fn main() -> anyhow::Result<()> { + let CliCommand { + node_rpc_url, database_url, shared_params: _shared_params, oldest_block, .. + } = CliCommand::parse(); + + #[cfg(not(test))] + init_logger(&_shared_params)?; + + let (api, rpc_client, rpc) = connect(&node_rpc_url).await?; + let block_provider: Arc = + Arc::new(BlockInfoProviderImpl::new(0, api.clone(), rpc.clone())); + let receipt_provider: Arc = + Arc::new(DBReceiptProvider::new(&database_url, false, block_provider.clone()).await?); + + let client = Client::new(api, rpc_client, rpc, block_provider, receipt_provider).await?; + client.subscribe_and_cache_receipts(oldest_block).await?; + + Ok(()) +} diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 230f2f8b7ef9..5e1341e2a29a 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -24,6 +24,7 @@ use jsonrpsee::{ types::{ErrorCode, ErrorObjectOwned}, }; use pallet_revive::evm::*; +use sp_arithmetic::Permill; use sp_core::{keccak_256, H160, H256, U256}; use thiserror::Error; @@ -35,6 +36,12 @@ pub mod subxt_client; #[cfg(test)] mod tests; +mod block_info_provider; +pub use block_info_provider::*; + +mod receipt_provider; +pub use receipt_provider::*; + mod rpc_health; pub use rpc_health::*; @@ -121,7 +128,12 @@ impl EthRpcServer for EthRpcServerImpl { transaction_hash: H256, ) -> RpcResult> { let receipt = self.client.receipt(&transaction_hash).await; - log::debug!(target: LOG_TARGET, "transaction_receipt for {transaction_hash:?}: {}", receipt.is_some()); + log::debug!( + target: LOG_TARGET, + "transaction_receipt for {transaction_hash:?}: received: {received} - success: {success:?}", + received = receipt.is_some(), + success = receipt.as_ref().map(|r| r.status == Some(U256::one())) + ); Ok(receipt) } @@ -197,12 +209,12 @@ impl EthRpcServer for EthRpcServerImpl { async fn get_block_by_hash( &self, block_hash: H256, - _hydrated_transactions: bool, + hydrated_transactions: bool, ) -> RpcResult> { let Some(block) = self.client.block_by_hash(&block_hash).await? else { return Ok(None); }; - let block = self.client.evm_block(block).await?; + let block = self.client.evm_block(block, hydrated_transactions).await?; Ok(Some(block)) } @@ -220,6 +232,11 @@ impl EthRpcServer for EthRpcServerImpl { Ok(U256::from(GAS_PRICE)) } + async fn max_priority_fee_per_gas(&self) -> RpcResult { + // TODO: Provide better estimation + Ok(U256::from(Permill::from_percent(20).mul_ceil(GAS_PRICE))) + } + async fn get_code(&self, address: H160, block: BlockNumberOrTagOrHash) -> RpcResult { let code = self.client.get_contract_code(&address, block).await?; Ok(code.into()) @@ -232,12 +249,12 @@ impl EthRpcServer for EthRpcServerImpl { async fn get_block_by_number( &self, block: BlockNumberOrTag, - _hydrated_transactions: bool, + hydrated_transactions: bool, ) -> RpcResult> { let Some(block) = self.client.block_by_number_or_tag(&block).await? else { return Ok(None); }; - let block = self.client.evm_block(block).await?; + let block = self.client.evm_block(block, hydrated_transactions).await?; Ok(Some(block)) } diff --git a/substrate/frame/revive/rpc/src/receipt_provider.rs b/substrate/frame/revive/rpc/src/receipt_provider.rs new file mode 100644 index 000000000000..5c102b3d3d41 --- /dev/null +++ b/substrate/frame/revive/rpc/src/receipt_provider.rs @@ -0,0 +1,240 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + client::SubstrateBlock, + subxt_client::{ + revive::{calls::types::EthTransact, events::ContractEmitted}, + system::events::ExtrinsicSuccess, + transaction_payment::events::TransactionFeePaid, + SrcChainConfig, + }, + ClientError, LOG_TARGET, +}; +use futures::{stream, StreamExt}; +use jsonrpsee::core::async_trait; +use pallet_revive::{ + create1, + evm::{GenericTransaction, Log, ReceiptInfo, TransactionSigned, H256, U256}, +}; +use sp_core::keccak_256; +use tokio::join; + +mod cache; +pub use cache::CacheReceiptProvider; + +mod db; +pub use db::DBReceiptProvider; + +/// Provide means to store and retrieve receipts. +#[async_trait] +pub trait ReceiptProvider: Send + Sync { + /// Insert receipts into the provider. + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]); + + /// Remove receipts with the given block hash. + async fn remove(&self, block_hash: &H256); + + /// Get the receipt for the given block hash and transaction index. + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option; + + /// Get the number of receipts per block. + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option; + + /// Get the receipt for the given transaction hash. + async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option; + + /// Get the signed transaction for the given transaction hash. + async fn signed_tx_by_hash(&self, transaction_hash: &H256) -> Option; +} + +#[async_trait] +impl ReceiptProvider for (Main, Fallback) { + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + join!(self.0.insert(block_hash, receipts), self.1.insert(block_hash, receipts)); + } + + async fn remove(&self, block_hash: &H256) { + join!(self.0.remove(block_hash), self.1.remove(block_hash)); + } + + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option { + if let Some(receipt) = + self.0.receipt_by_block_hash_and_index(block_hash, transaction_index).await + { + return Some(receipt); + } + + self.1.receipt_by_block_hash_and_index(block_hash, transaction_index).await + } + + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option { + if let Some(count) = self.0.receipts_count_per_block(block_hash).await { + return Some(count); + } + self.1.receipts_count_per_block(block_hash).await + } + + async fn receipt_by_hash(&self, hash: &H256) -> Option { + if let Some(receipt) = self.0.receipt_by_hash(hash).await { + return Some(receipt); + } + self.1.receipt_by_hash(hash).await + } + + async fn signed_tx_by_hash(&self, hash: &H256) -> Option { + if let Some(tx) = self.0.signed_tx_by_hash(hash).await { + return Some(tx); + } + self.1.signed_tx_by_hash(hash).await + } +} + +/// Extract a [`TransactionSigned`] and a [`ReceiptInfo`] and from an extrinsic. +pub async fn extract_receipt_from_extrinsic( + block: &SubstrateBlock, + ext: subxt::blocks::ExtrinsicDetails>, + call: EthTransact, +) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { + let transaction_index = ext.index(); + let block_number = U256::from(block.number()); + let block_hash = block.hash(); + let events = ext.events().await?; + + let success = events.has::().inspect_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to lookup for ExtrinsicSuccess event in block {block_number}: {err:?}") + })?; + let tx_fees = events + .find_first::()? + .ok_or(ClientError::TxFeeNotFound) + .inspect_err( + |err| log::debug!(target: LOG_TARGET, "TransactionFeePaid not found in events for block {block_number}\n{err:?}") + )?; + let transaction_hash = H256(keccak_256(&call.payload)); + + let signed_tx = + TransactionSigned::decode(&call.payload).map_err(|_| ClientError::TxDecodingFailed)?; + let from = signed_tx.recover_eth_address().map_err(|_| { + log::error!(target: LOG_TARGET, "Failed to recover eth address from signed tx"); + ClientError::RecoverEthAddressFailed + })?; + + let tx_info = GenericTransaction::from_signed(signed_tx.clone(), Some(from)); + let gas_price = tx_info.gas_price.unwrap_or_default(); + let gas_used = (tx_fees.tip.saturating_add(tx_fees.actual_fee)) + .checked_div(gas_price.as_u128()) + .unwrap_or_default(); + + // get logs from ContractEmitted event + let logs = events + .iter() + .filter_map(|event_details| { + let event_details = event_details.ok()?; + let event = event_details.as_event::().ok()??; + + Some(Log { + address: event.contract, + topics: event.topics, + data: Some(event.data.into()), + block_number: Some(block_number), + transaction_hash, + transaction_index: Some(transaction_index.into()), + block_hash: Some(block_hash), + log_index: Some(event_details.index().into()), + ..Default::default() + }) + }) + .collect(); + + let contract_address = if tx_info.to.is_none() { + Some(create1( + &from, + tx_info + .nonce + .unwrap_or_default() + .try_into() + .map_err(|_| ClientError::ConversionFailed)?, + )) + } else { + None + }; + + log::debug!(target: LOG_TARGET, "Adding receipt for tx hash: {transaction_hash:?} - block: {block_number:?}"); + let receipt = ReceiptInfo::new( + block_hash, + block_number, + contract_address, + from, + logs, + tx_info.to, + gas_price, + gas_used.into(), + success, + transaction_hash, + transaction_index.into(), + tx_info.r#type.unwrap_or_default(), + ); + Ok((signed_tx, receipt)) +} + +/// Extract receipts from block. +pub async fn extract_receipts_from_block( + block: &SubstrateBlock, +) -> Result, ClientError> { + // Filter extrinsics from pallet_revive + let extrinsics = block.extrinsics().await.inspect_err(|err| { + log::debug!(target: LOG_TARGET, "Error fetching for #{:?} extrinsics: {err:?}", block.number()); + })?; + + let extrinsics = extrinsics.iter().flat_map(|ext| { + let call = ext.as_extrinsic::().ok()??; + Some((ext, call)) + }); + + stream::iter(extrinsics) + .map(|(ext, call)| async move { extract_receipt_from_extrinsic(block, ext, call).await }) + .buffer_unordered(10) + .collect::>>() + .await + .into_iter() + .collect::, _>>() +} + +/// Extract receipt from transaction +pub async fn extract_receipts_from_transaction( + block: &SubstrateBlock, + transaction_index: usize, +) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { + let extrinsics = block.extrinsics().await?; + let ext = extrinsics + .iter() + .nth(transaction_index) + .ok_or(ClientError::EthExtrinsicNotFound)?; + + let call = ext + .as_extrinsic::()? + .ok_or_else(|| ClientError::EthExtrinsicNotFound)?; + extract_receipt_from_extrinsic(block, ext, call).await +} diff --git a/substrate/frame/revive/rpc/src/receipt_provider/cache.rs b/substrate/frame/revive/rpc/src/receipt_provider/cache.rs new file mode 100644 index 000000000000..39124929ec07 --- /dev/null +++ b/substrate/frame/revive/rpc/src/receipt_provider/cache.rs @@ -0,0 +1,148 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use super::ReceiptProvider; +use jsonrpsee::core::async_trait; +use pallet_revive::evm::{ReceiptInfo, TransactionSigned, H256, U256}; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::RwLock; + +/// A `[ReceiptProvider]` that caches receipts in memory. +#[derive(Clone, Default)] +pub struct CacheReceiptProvider { + cache: Arc>, +} + +impl CacheReceiptProvider { + /// Get a read access on the shared cache. + async fn cache(&self) -> tokio::sync::RwLockReadGuard<'_, ReceiptCache> { + self.cache.read().await + } +} + +#[async_trait] +impl ReceiptProvider for CacheReceiptProvider { + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + let mut cache = self.cache.write().await; + cache.insert(block_hash, receipts); + } + + async fn remove(&self, block_hash: &H256) { + let mut cache = self.cache.write().await; + cache.remove(block_hash); + } + + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option { + let cache = self.cache().await; + let receipt_hash = cache + .transaction_hashes_by_block_and_index + .get(block_hash)? + .get(transaction_index)?; + let receipt = cache.receipts_by_hash.get(receipt_hash)?; + Some(receipt.clone()) + } + + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option { + let cache = self.cache().await; + cache.transaction_hashes_by_block_and_index.get(block_hash).map(|v| v.len()) + } + + async fn receipt_by_hash(&self, hash: &H256) -> Option { + let cache = self.cache().await; + cache.receipts_by_hash.get(hash).cloned() + } + + async fn signed_tx_by_hash(&self, hash: &H256) -> Option { + let cache = self.cache().await; + cache.signed_tx_by_hash.get(hash).cloned() + } +} + +#[derive(Default)] +struct ReceiptCache { + /// A map of receipts by transaction hash. + receipts_by_hash: HashMap, + + /// A map of Signed transaction by transaction hash. + signed_tx_by_hash: HashMap, + + /// A map of receipt hashes by block hash. + transaction_hashes_by_block_and_index: HashMap>, +} + +impl ReceiptCache { + /// Insert new receipts into the cache. + pub fn insert(&mut self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + if !receipts.is_empty() { + let values = receipts + .iter() + .map(|(_, receipt)| (receipt.transaction_index, receipt.transaction_hash)) + .collect::>(); + + self.transaction_hashes_by_block_and_index.insert(*block_hash, values); + + self.receipts_by_hash.extend( + receipts.iter().map(|(_, receipt)| (receipt.transaction_hash, receipt.clone())), + ); + + self.signed_tx_by_hash.extend( + receipts + .iter() + .map(|(signed_tx, receipt)| (receipt.transaction_hash, signed_tx.clone())), + ) + } + } + + /// Remove entry from the cache. + pub fn remove(&mut self, hash: &H256) { + if let Some(entries) = self.transaction_hashes_by_block_and_index.remove(hash) { + for hash in entries.values() { + self.receipts_by_hash.remove(hash); + self.signed_tx_by_hash.remove(hash); + } + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn cache_insert_and_remove_works() { + let mut cache = ReceiptCache::default(); + + for i in 1u8..=3 { + let hash = H256::from([i; 32]); + cache.insert( + &hash, + &[( + TransactionSigned::default(), + ReceiptInfo { transaction_hash: hash, ..Default::default() }, + )], + ); + } + + cache.remove(&H256::from([1u8; 32])); + assert_eq!(cache.transaction_hashes_by_block_and_index.len(), 2); + assert_eq!(cache.receipts_by_hash.len(), 2); + assert_eq!(cache.signed_tx_by_hash.len(), 2); + } +} diff --git a/substrate/frame/revive/rpc/src/receipt_provider/db.rs b/substrate/frame/revive/rpc/src/receipt_provider/db.rs new file mode 100644 index 000000000000..63917d6193ea --- /dev/null +++ b/substrate/frame/revive/rpc/src/receipt_provider/db.rs @@ -0,0 +1,216 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate::BlockInfoProvider; +use jsonrpsee::core::async_trait; +use pallet_revive::evm::{ReceiptInfo, TransactionSigned}; +use sp_core::{H256, U256}; +use sqlx::{query, SqlitePool}; +use std::sync::Arc; + +/// A `[ReceiptProvider]` that stores receipts in a SQLite database. +#[derive(Clone)] +pub struct DBReceiptProvider { + /// The database pool. + pool: SqlitePool, + /// The block provider used to fetch blocks, and reconstruct receipts. + block_provider: Arc, + /// weather or not we should write to the DB. + read_only: bool, +} + +impl DBReceiptProvider { + /// Create a new `DBReceiptProvider` with the given database URL and block provider. + pub async fn new( + database_url: &str, + read_only: bool, + block_provider: Arc, + ) -> Result { + let pool = SqlitePool::connect(database_url).await?; + Ok(Self { pool, block_provider, read_only }) + } + + async fn fetch_row(&self, transaction_hash: &H256) -> Option<(H256, usize)> { + let transaction_hash = hex::encode(transaction_hash); + let result = query!( + r#" + SELECT block_hash, transaction_index + FROM transaction_hashes + WHERE transaction_hash = $1 + "#, + transaction_hash + ) + .fetch_optional(&self.pool) + .await + .ok()??; + + let block_hash = result.block_hash.parse::().ok()?; + let transaction_index = result.transaction_index.try_into().ok()?; + Some((block_hash, transaction_index)) + } +} + +#[async_trait] +impl ReceiptProvider for DBReceiptProvider { + async fn insert(&self, block_hash: &H256, receipts: &[(TransactionSigned, ReceiptInfo)]) { + if self.read_only { + return + } + + let block_hash_str = hex::encode(block_hash); + for (_, receipt) in receipts { + let transaction_hash = hex::encode(receipt.transaction_hash); + let transaction_index = receipt.transaction_index.as_u32() as i32; + + let result = query!( + r#" + INSERT INTO transaction_hashes (transaction_hash, block_hash, transaction_index) + VALUES ($1, $2, $3) + + ON CONFLICT(transaction_hash) DO UPDATE SET + block_hash = EXCLUDED.block_hash, + transaction_index = EXCLUDED.transaction_index + "#, + transaction_hash, + block_hash_str, + transaction_index + ) + .execute(&self.pool) + .await; + + if let Err(err) = result { + log::error!( + "Error inserting transaction for block hash {block_hash:?}: {:?}", + err + ); + } + } + } + + async fn remove(&self, _block_hash: &H256) {} + + async fn receipts_count_per_block(&self, block_hash: &H256) -> Option { + let block_hash = hex::encode(block_hash); + let row = query!( + r#" + SELECT COUNT(*) as count + FROM transaction_hashes + WHERE block_hash = $1 + "#, + block_hash + ) + .fetch_one(&self.pool) + .await + .ok()?; + + let count = row.count as usize; + Some(count) + } + + async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: &U256, + ) -> Option { + let block = self.block_provider.block_by_hash(block_hash).await.ok()??; + let transaction_index: usize = transaction_index.as_usize(); // TODO: check for overflow + let (_, receipt) = + extract_receipts_from_transaction(&block, transaction_index).await.ok()?; + Some(receipt) + } + + async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option { + let (block_hash, transaction_index) = self.fetch_row(transaction_hash).await?; + + let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; + let (_, receipt) = + extract_receipts_from_transaction(&block, transaction_index).await.ok()?; + Some(receipt) + } + + async fn signed_tx_by_hash(&self, transaction_hash: &H256) -> Option { + let transaction_hash = hex::encode(transaction_hash); + let result = query!( + r#" + SELECT block_hash, transaction_index + FROM transaction_hashes + WHERE transaction_hash = $1 + "#, + transaction_hash + ) + .fetch_optional(&self.pool) + .await + .ok()??; + + let block_hash = result.block_hash.parse::().ok()?; + let transaction_index = result.transaction_index.try_into().ok()?; + + let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; + let (signed_tx, _) = + extract_receipts_from_transaction(&block, transaction_index).await.ok()?; + Some(signed_tx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::MockBlockInfoProvider; + use pallet_revive::evm::{ReceiptInfo, TransactionSigned}; + use sp_core::H256; + use sqlx::SqlitePool; + + async fn setup_sqlite_provider(pool: SqlitePool) -> DBReceiptProvider { + DBReceiptProvider { + pool, + block_provider: Arc::new(MockBlockInfoProvider {}), + read_only: false, + } + } + + #[sqlx::test] + async fn test_insert(pool: SqlitePool) { + let provider = setup_sqlite_provider(pool).await; + let block_hash = H256::default(); + let receipts = vec![(TransactionSigned::default(), ReceiptInfo::default())]; + + provider.insert(&block_hash, &receipts).await; + let row = provider.fetch_row(&receipts[0].1.transaction_hash).await; + assert_eq!(row, Some((block_hash, 0))); + } + + #[sqlx::test] + async fn test_receipts_count_per_block(pool: SqlitePool) { + let provider = setup_sqlite_provider(pool).await; + let block_hash = H256::default(); + let receipts = vec![ + ( + TransactionSigned::default(), + ReceiptInfo { transaction_hash: H256::from([0u8; 32]), ..Default::default() }, + ), + ( + TransactionSigned::default(), + ReceiptInfo { transaction_hash: H256::from([1u8; 32]), ..Default::default() }, + ), + ]; + + provider.insert(&block_hash, &receipts).await; + let count = provider.receipts_count_per_block(&block_hash).await; + assert_eq!(count, Some(2)); + } +} diff --git a/substrate/frame/revive/rpc/src/rpc_health.rs b/substrate/frame/revive/rpc/src/rpc_health.rs index f94d4b82a80f..35c5a588f284 100644 --- a/substrate/frame/revive/rpc/src/rpc_health.rs +++ b/substrate/frame/revive/rpc/src/rpc_health.rs @@ -25,6 +25,10 @@ pub trait SystemHealthRpc { /// Proxy the substrate chain system_health RPC call. #[method(name = "system_health")] async fn system_health(&self) -> RpcResult; + + ///Returns the number of peers currently connected to the client. + #[method(name = "net_peerCount")] + async fn net_peer_count(&self) -> RpcResult; } pub struct SystemHealthRpcServerImpl { @@ -47,4 +51,9 @@ impl SystemHealthRpcServer for SystemHealthRpcServerImpl { should_have_peers: health.should_have_peers, }) } + + async fn net_peer_count(&self) -> RpcResult { + let health = self.client.system_health().await?; + Ok((health.peers as u64).into()) + } } diff --git a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs index ad34dbfdfb49..da60360d9e61 100644 --- a/substrate/frame/revive/rpc/src/rpc_methods_gen.rs +++ b/substrate/frame/revive/rpc/src/rpc_methods_gen.rs @@ -142,6 +142,10 @@ pub trait EthRpc { transaction_hash: H256, ) -> RpcResult>; + /// Returns the current maxPriorityFeePerGas per gas in wei. + #[method(name = "eth_maxPriorityFeePerGas")] + async fn max_priority_fee_per_gas(&self) -> RpcResult; + /// Submits a raw transaction. For EIP-4844 transactions, the raw form must be the network form. /// This means it includes the blobs, KZG commitments, and KZG proofs. #[method(name = "eth_sendRawTransaction")] diff --git a/substrate/frame/revive/src/evm/api/rpc_types.rs b/substrate/frame/revive/src/evm/api/rpc_types.rs index ed046cb4da44..b4b2c6ffcf17 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types.rs @@ -192,7 +192,11 @@ impl GenericTransaction { value: Some(tx.value), to: Some(tx.to), gas: Some(tx.gas), - gas_price: Some(tx.max_fee_per_blob_gas), + gas_price: Some( + U256::from(crate::GAS_PRICE) + .saturating_add(tx.max_priority_fee_per_gas) + .max(tx.max_fee_per_blob_gas), + ), access_list: Some(tx.access_list), blob_versioned_hashes: tx.blob_versioned_hashes, max_fee_per_blob_gas: Some(tx.max_fee_per_blob_gas), @@ -209,7 +213,11 @@ impl GenericTransaction { value: Some(tx.value), to: tx.to, gas: Some(tx.gas), - gas_price: Some(tx.gas_price), + gas_price: Some( + U256::from(crate::GAS_PRICE) + .saturating_add(tx.max_priority_fee_per_gas) + .max(tx.max_fee_per_gas), + ), access_list: Some(tx.access_list), max_fee_per_gas: Some(tx.max_fee_per_gas), max_priority_fee_per_gas: Some(tx.max_priority_fee_per_gas), diff --git a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs index 1d65fdefdde6..5d31613ca314 100644 --- a/substrate/frame/revive/src/evm/api/rpc_types_gen.rs +++ b/substrate/frame/revive/src/evm/api/rpc_types_gen.rs @@ -87,7 +87,7 @@ pub struct Block { /// Total difficulty #[serde(rename = "totalDifficulty", skip_serializing_if = "Option::is_none")] pub total_difficulty: Option, - pub transactions: H256OrTransactionInfo, + pub transactions: HashesOrTransactionInfos, /// Transactions root #[serde(rename = "transactionsRoot")] pub transactions_root: H256, @@ -357,15 +357,15 @@ pub enum BlockTag { Debug, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, From, TryInto, Eq, PartialEq, )] #[serde(untagged)] -pub enum H256OrTransactionInfo { +pub enum HashesOrTransactionInfos { /// Transaction hashes - H256s(Vec), + Hashes(Vec), /// Full transactions TransactionInfos(Vec), } -impl Default for H256OrTransactionInfo { +impl Default for HashesOrTransactionInfos { fn default() -> Self { - H256OrTransactionInfo::H256s(Default::default()) + HashesOrTransactionInfos::Hashes(Default::default()) } } diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index b24de61314f9..3bd4bde5679f 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -193,8 +193,9 @@ where &HoldReason::CodeUploadDepositReserve.into(), &self.code_info.owner, deposit, - ) .map_err(|err| { log::debug!(target: LOG_TARGET, "failed to store code for owner: {:?}: {err:?}", self.code_info.owner); - >::StorageDepositNotEnoughFunds + ) .map_err(|err| { + log::debug!(target: LOG_TARGET, "failed to hold store code deposit {deposit:?} for owner: {:?}: {err:?}", self.code_info.owner); + >::StorageDepositNotEnoughFunds })?; } From 6878ba1f399b628cf456ad3abfe72f2553422e1f Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Tue, 14 Jan 2025 16:52:49 +0200 Subject: [PATCH 256/340] Retry approval on availability failure if the check is still needed (#6807) Recovering the POV can fail in situation where the node just restart and the DHT topology wasn't fully discovered yet, so the current node can't connect to most of its Peers. This is bad because for gossiping the assignment you need to be connected to just a few peers, so because we can't approve the candidate and other nodes will see this as a no show. This becomes bad in the scenario where you've got a lot of nodes restarting at the same time, so you end up having a lot of no-shows in the network that are never covered, in that case it makes sense for nodes to actually retry approving the candidate at a later data in time and retry several times if the block containing the candidate wasn't approved. ## TODO - [x] Add a subsystem test. --------- Signed-off-by: Alexandru Gheorghe --- polkadot/node/core/approval-voting/src/lib.rs | 137 +++++++++++++++- .../node/core/approval-voting/src/tests.rs | 146 ++++++++++++++++++ .../subsystem-bench/src/lib/approval/mod.rs | 2 + prdoc/pr_6807.prdoc | 19 +++ 4 files changed, 297 insertions(+), 7 deletions(-) create mode 100644 prdoc/pr_6807.prdoc diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 7cea22d1a6a7..27361df37310 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -21,6 +21,7 @@ //! of others. It uses this information to determine when candidates and blocks have //! been sufficiently approved to finalize. +use futures_timer::Delay; use polkadot_node_primitives::{ approval::{ v1::{BlockApprovalMeta, DelayTranche}, @@ -122,6 +123,9 @@ const APPROVAL_CHECKING_TIMEOUT: Duration = Duration::from_secs(120); const WAIT_FOR_SIGS_TIMEOUT: Duration = Duration::from_millis(500); const APPROVAL_CACHE_SIZE: u32 = 1024; +/// The maximum number of times we retry to approve a block if is still needed. +const MAX_APPROVAL_RETRIES: u32 = 16; + const APPROVAL_DELAY: Tick = 2; pub(crate) const LOG_TARGET: &str = "parachain::approval-voting"; @@ -165,6 +169,10 @@ pub struct ApprovalVotingSubsystem { metrics: Metrics, clock: Arc, spawner: Arc, + /// The maximum time we retry to approve a block if it is still needed and PoV fetch failed. + max_approval_retries: u32, + /// The backoff before we retry the approval. + retry_backoff: Duration, } #[derive(Clone)] @@ -493,6 +501,8 @@ impl ApprovalVotingSubsystem { metrics, Arc::new(SystemClock {}), spawner, + MAX_APPROVAL_RETRIES, + APPROVAL_CHECKING_TIMEOUT / 2, ) } @@ -505,6 +515,8 @@ impl ApprovalVotingSubsystem { metrics: Metrics, clock: Arc, spawner: Arc, + max_approval_retries: u32, + retry_backoff: Duration, ) -> Self { ApprovalVotingSubsystem { keystore, @@ -515,6 +527,8 @@ impl ApprovalVotingSubsystem { metrics, clock, spawner, + max_approval_retries, + retry_backoff, } } @@ -706,18 +720,53 @@ enum ApprovalOutcome { TimedOut, } +#[derive(Clone)] +struct RetryApprovalInfo { + candidate: CandidateReceipt, + backing_group: GroupIndex, + executor_params: ExecutorParams, + core_index: Option, + session_index: SessionIndex, + attempts_remaining: u32, + backoff: Duration, +} + struct ApprovalState { validator_index: ValidatorIndex, candidate_hash: CandidateHash, approval_outcome: ApprovalOutcome, + retry_info: Option, } impl ApprovalState { fn approved(validator_index: ValidatorIndex, candidate_hash: CandidateHash) -> Self { - Self { validator_index, candidate_hash, approval_outcome: ApprovalOutcome::Approved } + Self { + validator_index, + candidate_hash, + approval_outcome: ApprovalOutcome::Approved, + retry_info: None, + } } fn failed(validator_index: ValidatorIndex, candidate_hash: CandidateHash) -> Self { - Self { validator_index, candidate_hash, approval_outcome: ApprovalOutcome::Failed } + Self { + validator_index, + candidate_hash, + approval_outcome: ApprovalOutcome::Failed, + retry_info: None, + } + } + + fn failed_with_retry( + validator_index: ValidatorIndex, + candidate_hash: CandidateHash, + retry_info: Option, + ) -> Self { + Self { + validator_index, + candidate_hash, + approval_outcome: ApprovalOutcome::Failed, + retry_info, + } } } @@ -757,6 +806,7 @@ impl CurrentlyCheckingSet { candidate_hash, validator_index, approval_outcome: ApprovalOutcome::TimedOut, + retry_info: None, }, Some(approval_state) => approval_state, } @@ -1271,18 +1321,19 @@ where validator_index, candidate_hash, approval_outcome, + retry_info, } ) = approval_state; if matches!(approval_outcome, ApprovalOutcome::Approved) { let mut approvals: Vec = relay_block_hashes - .into_iter() + .iter() .map(|block_hash| Action::IssueApproval( candidate_hash, ApprovalVoteRequest { validator_index, - block_hash, + block_hash: *block_hash, }, ) ) @@ -1290,6 +1341,43 @@ where actions.append(&mut approvals); } + if let Some(retry_info) = retry_info { + for block_hash in relay_block_hashes { + if overlayed_db.load_block_entry(&block_hash).map(|block_info| block_info.is_some()).unwrap_or(false) { + let sender = to_other_subsystems.clone(); + let spawn_handle = subsystem.spawner.clone(); + let metrics = subsystem.metrics.clone(); + let retry_info = retry_info.clone(); + let executor_params = retry_info.executor_params.clone(); + let candidate = retry_info.candidate.clone(); + + currently_checking_set + .insert_relay_block_hash( + candidate_hash, + validator_index, + block_hash, + async move { + launch_approval( + sender, + spawn_handle, + metrics, + retry_info.session_index, + candidate, + validator_index, + block_hash, + retry_info.backing_group, + executor_params, + retry_info.core_index, + retry_info, + ) + .await + }, + ) + .await?; + } + } + } + actions }, (block_hash, validator_index) = delayed_approvals_timers.select_next_some() => { @@ -1340,6 +1428,8 @@ where &mut approvals_cache, &mut subsystem.mode, actions, + subsystem.max_approval_retries, + subsystem.retry_backoff, ) .await? { @@ -1389,6 +1479,8 @@ pub async fn start_approval_worker< metrics, clock, spawner, + MAX_APPROVAL_RETRIES, + APPROVAL_CHECKING_TIMEOUT / 2, ); let backend = DbBackend::new(db.clone(), approval_voting.db_config); let spawner = approval_voting.spawner.clone(); @@ -1456,6 +1548,8 @@ async fn handle_actions< approvals_cache: &mut LruMap, mode: &mut Mode, actions: Vec, + max_approval_retries: u32, + retry_backoff: Duration, ) -> SubsystemResult { let mut conclude = false; let mut actions_iter = actions.into_iter(); @@ -1542,6 +1636,16 @@ async fn handle_actions< let sender = sender.clone(); let spawn_handle = spawn_handle.clone(); + let retry = RetryApprovalInfo { + candidate: candidate.clone(), + backing_group, + executor_params: executor_params.clone(), + core_index, + session_index: session, + attempts_remaining: max_approval_retries, + backoff: retry_backoff, + }; + currently_checking_set .insert_relay_block_hash( candidate_hash, @@ -1559,6 +1663,7 @@ async fn handle_actions< backing_group, executor_params, core_index, + retry, ) .await }, @@ -3329,6 +3434,7 @@ async fn launch_approval< backing_group: GroupIndex, executor_params: ExecutorParams, core_index: Option, + retry: RetryApprovalInfo, ) -> SubsystemResult> { let (a_tx, a_rx) = oneshot::channel(); let (code_tx, code_rx) = oneshot::channel(); @@ -3360,6 +3466,7 @@ async fn launch_approval< let candidate_hash = candidate.hash(); let para_id = candidate.descriptor.para_id(); + let mut next_retry = None; gum::trace!(target: LOG_TARGET, ?candidate_hash, ?para_id, "Recovering data."); let timer = metrics.time_recover_and_approve(); @@ -3388,7 +3495,6 @@ async fn launch_approval< let background = async move { // Force the move of the timer into the background task. let _timer = timer; - let available_data = match a_rx.await { Err(_) => return ApprovalState::failed(validator_index, candidate_hash), Ok(Ok(a)) => a, @@ -3399,10 +3505,27 @@ async fn launch_approval< target: LOG_TARGET, ?para_id, ?candidate_hash, + attempts_remaining = retry.attempts_remaining, "Data unavailable for candidate {:?}", (candidate_hash, candidate.descriptor.para_id()), ); - // do nothing. we'll just be a no-show and that'll cause others to rise up. + // Availability could fail if we did not discover much of the network, so + // let's back off and order the subsystem to retry at a later point if the + // approval is still needed, because no-show wasn't covered yet. + if retry.attempts_remaining > 0 { + Delay::new(retry.backoff).await; + next_retry = Some(RetryApprovalInfo { + candidate, + backing_group, + executor_params, + core_index, + session_index, + attempts_remaining: retry.attempts_remaining - 1, + backoff: retry.backoff, + }); + } else { + next_retry = None; + } metrics_guard.take().on_approval_unavailable(); }, &RecoveryError::ChannelClosed => { @@ -3433,7 +3556,7 @@ async fn launch_approval< metrics_guard.take().on_approval_invalid(); }, } - return ApprovalState::failed(validator_index, candidate_hash) + return ApprovalState::failed_with_retry(validator_index, candidate_hash, next_retry) }, }; diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index be569a1de3ec..b72993fe1a94 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -78,6 +78,9 @@ const SLOT_DURATION_MILLIS: u64 = 5000; const TIMEOUT: Duration = Duration::from_millis(2000); +const NUM_APPROVAL_RETRIES: u32 = 3; +const RETRY_BACKOFF: Duration = Duration::from_millis(300); + #[derive(Clone)] struct TestSyncOracle { flag: Arc, @@ -573,6 +576,8 @@ fn test_harness>( Metrics::default(), clock.clone(), Arc::new(SpawnGlue(pool)), + NUM_APPROVAL_RETRIES, + RETRY_BACKOFF, ), assignment_criteria, backend, @@ -3202,6 +3207,20 @@ async fn recover_available_data(virtual_overseer: &mut VirtualOverseer) { ); } +async fn recover_available_data_failure(virtual_overseer: &mut VirtualOverseer) { + let available_data = RecoveryError::Unavailable; + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::AvailabilityRecovery( + AvailabilityRecoveryMessage::RecoverAvailableData(_, _, _, _, tx) + ) => { + tx.send(Err(available_data)).unwrap(); + }, + "overseer did not receive recover available data message", + ); +} + struct TriggersAssignmentConfig { our_assigned_tranche: DelayTranche, assign_validator_tranche: F1, @@ -4791,6 +4810,133 @@ fn subsystem_relaunches_approval_work_on_restart() { }); } +/// Test that we retry the approval of candidate on availability failure, up to max retries. +#[test] +fn subsystem_relaunches_approval_work_on_availability_failure() { + let assignment_criteria = Box::new(MockAssignmentCriteria( + || { + let mut assignments = HashMap::new(); + + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFModuloCompact { + core_bitfield: vec![CoreIndex(0), CoreIndex(2)].try_into().unwrap(), + }), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + let _ = assignments.insert( + CoreIndex(1), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFDelay { + core_index: CoreIndex(1), + }), + tranche: 0, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + assignments + }, + |_| Ok(0), + )); + let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build(); + let store = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + + setup_overseer_with_blocks_with_two_assignments_triggered( + &mut virtual_overseer, + store, + &clock, + sync_oracle_handle, + ) + .await; + + // We have two candidates for one we are going to fail the availability for up to + // max_retries and for the other we are going to succeed on the last retry, so we should + // see the approval being distributed. + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _ + )) => { + } + ); + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data_failure(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive { + exec_kind, + response_sender, + .. + }) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + .unwrap(); + } + ); + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); +} + // Test that cached approvals, which are candidates that we approved but we didn't issue // the signature yet because we want to coalesce it with more candidate are sent after restart. #[test] diff --git a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs index 1b20960a3f8a..5f1689cb226b 100644 --- a/polkadot/node/subsystem-bench/src/lib/approval/mod.rs +++ b/polkadot/node/subsystem-bench/src/lib/approval/mod.rs @@ -891,6 +891,8 @@ fn build_overseer( state.approval_voting_parallel_metrics.approval_voting_metrics(), Arc::new(system_clock.clone()), Arc::new(SpawnGlue(spawn_task_handle.clone())), + 1, + Duration::from_secs(1), ); let approval_distribution = ApprovalDistribution::new_with_clock( diff --git a/prdoc/pr_6807.prdoc b/prdoc/pr_6807.prdoc new file mode 100644 index 000000000000..b9564dfb2fe2 --- /dev/null +++ b/prdoc/pr_6807.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Retry approval on availability failure if the check is still needed + +doc: + - audience: Node Dev + description: | + Recovering the POV can fail in situation where the node just restart and the DHT topology + wasn't fully discovered yet, so the current node can't connect to most of its Peers. + This is bad because for gossiping the assignment you need to be connected to just a few + peers, so because we can't approve the candidate other nodes will see this as a no show. + Fix it by retrying to approve a candidate for a fixed number of atttempts if the block is + still needed. + + +crates: + - name: polkadot-node-core-approval-voting + bump: minor From d38bb9533b70abb7eff4e8770177d7840899ca86 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Tue, 14 Jan 2025 19:10:27 +0200 Subject: [PATCH 257/340] approval-voting: Fix sending of assignments after restart (#6973) There is a problem on restart where nodes will not trigger their needed assignment if they were offline while the time of the assignment passed. That happens because after restart we will hit this condition https://github.com/paritytech/polkadot-sdk/blob/4e805ca05067f6ed970f33f9be51483185b0cc0b/polkadot/node/core/approval-voting/src/lib.rs#L2495 and considered will be `tick_now` which is already higher than the tick of our assignment. The fix is to schedule a wakeup for untriggered assignments at restart and let the logic of processing an wakeup decide if it needs to trigger the assignment or not. One thing that we need to be careful here is to make sure we don't schedule the wake up immediately after restart because, the node would still be behind with all the assignments that should have received and might make it wrongfully decide it needs to trigger its assignment, so I added a `RESTART_WAKEUP_DELAY: Tick = 12` which should be more than enough for the node to catch up. --------- Signed-off-by: Alexandru Gheorghe Co-authored-by: ordian Co-authored-by: Andrei Eres --- polkadot/node/core/approval-voting/src/lib.rs | 25 +- .../node/core/approval-voting/src/tests.rs | 246 ++++++++++++++++++ prdoc/pr_6973.prdoc | 16 ++ 3 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_6973.prdoc diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index 27361df37310..b4c2a6afee0e 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -132,6 +132,16 @@ pub(crate) const LOG_TARGET: &str = "parachain::approval-voting"; // The max number of ticks we delay sending the approval after we are ready to issue the approval const MAX_APPROVAL_COALESCE_WAIT_TICKS: Tick = 12; +// If the node restarted and the tranche has passed without the assignment +// being trigger, we won't trigger the assignment at restart because we don't have +// an wakeup schedule for it. +// The solution, is to always schedule a wake up after the restart and let the +// process_wakeup to decide if the assignment needs to be triggered. +// We need to have a delay after restart to give time to the node to catch up with +// messages and not trigger its assignment unnecessarily, because it hasn't seen +// the assignments from the other validators. +const RESTART_WAKEUP_DELAY: Tick = 12; + /// Configuration for the approval voting subsystem #[derive(Debug, Clone)] pub struct Config { @@ -1837,7 +1847,20 @@ async fn distribution_messages_for_activation { match approval_entry.local_statements() { - (None, None) | (None, Some(_)) => {}, // second is impossible case. + (None, None) => + if approval_entry + .our_assignment() + .map(|assignment| !assignment.triggered()) + .unwrap_or(false) + { + actions.push(Action::ScheduleWakeup { + block_hash, + block_number: block_entry.block_number(), + candidate_hash: *candidate_hash, + tick: state.clock.tick_now() + RESTART_WAKEUP_DELAY, + }) + }, + (None, Some(_)) => {}, // second is impossible case. (Some(assignment), None) => { let claimed_core_indices = get_core_indices_on_startup(&assignment.cert().kind, *core_index); diff --git a/polkadot/node/core/approval-voting/src/tests.rs b/polkadot/node/core/approval-voting/src/tests.rs index b72993fe1a94..9fe716833b88 100644 --- a/polkadot/node/core/approval-voting/src/tests.rs +++ b/polkadot/node/core/approval-voting/src/tests.rs @@ -5380,6 +5380,252 @@ fn subsystem_sends_assignment_approval_in_correct_order_on_approval_restart() { }); } +// Test that if the subsystem missed the triggering of some tranches because it was not running +// it launches the missed assignements on restart. +#[test] +fn subsystem_launches_missed_assignments_on_restart() { + let test_tranche = 20; + let assignment_criteria = Box::new(MockAssignmentCriteria( + move || { + let mut assignments = HashMap::new(); + let _ = assignments.insert( + CoreIndex(0), + approval_db::v2::OurAssignment { + cert: garbage_assignment_cert_v2(AssignmentCertKindV2::RelayVRFDelay { + core_index: CoreIndex(0), + }), + tranche: test_tranche, + validator_index: ValidatorIndex(0), + triggered: false, + } + .into(), + ); + + assignments + }, + |_| Ok(0), + )); + let config = HarnessConfigBuilder::default().assignment_criteria(assignment_criteria).build(); + let store = config.backend(); + let store_clone = config.backend(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + let fork_block_hash = Hash::repeat_byte(0x02); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + let candidate_hash = candidate_receipt.hash(); + let slot = Slot::from(1); + let (chain_builder, _session_info) = build_chain_with_two_blocks_with_one_candidate_each( + block_hash, + fork_block_hash, + slot, + sync_oracle_handle, + candidate_receipt, + ) + .await; + chain_builder.build(&mut virtual_overseer).await; + + assert!(!clock.inner.lock().current_wakeup_is(1)); + clock.inner.lock().wakeup_all(1); + + assert!(clock.inner.lock().current_wakeup_is(slot_to_tick(slot) + test_tranche as u64)); + clock.inner.lock().wakeup_all(slot_to_tick(slot)); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + clock.inner.lock().wakeup_all(slot_to_tick(slot + 2)); + + assert_eq!(clock.inner.lock().wakeups.len(), 0); + + futures_timer::Delay::new(Duration::from_millis(200)).await; + + let candidate_entry = store.load_candidate_entry(&candidate_hash).unwrap().unwrap(); + let our_assignment = + candidate_entry.approval_entry(&block_hash).unwrap().our_assignment().unwrap(); + assert!(!our_assignment.triggered()); + + // Assignment is not triggered because its tranches has not been reached. + virtual_overseer + }); + + // Restart a new approval voting subsystem with the same database and major syncing true until + // the last leaf. + let config = HarnessConfigBuilder::default().backend(store_clone).major_syncing(true).build(); + + test_harness(config, |test_harness| async move { + let TestHarness { mut virtual_overseer, clock, sync_oracle_handle } = test_harness; + let slot = Slot::from(1); + // 1. Set the clock to the to a tick past the tranche where the assignment should be + // triggered. + clock.inner.lock().set_tick(slot_to_tick(slot) + 2 * test_tranche as u64); + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ChainApi(ChainApiMessage::FinalizedBlockNumber(rx)) => { + rx.send(Ok(0)).unwrap(); + } + ); + + let block_hash = Hash::repeat_byte(0x01); + let fork_block_hash = Hash::repeat_byte(0x02); + let candidate_commitments = CandidateCommitments::default(); + let mut candidate_receipt = dummy_candidate_receipt_v2(block_hash); + candidate_receipt.commitments_hash = candidate_commitments.hash(); + let (chain_builder, session_info) = build_chain_with_two_blocks_with_one_candidate_each( + block_hash, + fork_block_hash, + slot, + sync_oracle_handle, + candidate_receipt, + ) + .await; + + chain_builder.build(&mut virtual_overseer).await; + + futures_timer::Delay::new(Duration::from_millis(2000)).await; + + // On major syncing ending Approval voting should send all the necessary messages for a + // candidate to be approved. + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks( + _, + )) => { + } + ); + + clock + .inner + .lock() + .wakeup_all(slot_to_tick(slot) + 2 * test_tranche as u64 + RESTART_WAKEUP_DELAY - 1); + + // Subsystem should not send any messages because the assignment is not triggered yet. + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + // Set the clock to the tick where the assignment should be triggered. + clock + .inner + .lock() + .wakeup_all(slot_to_tick(slot) + 2 * test_tranche as u64 + RESTART_WAKEUP_DELAY); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + _, + RuntimeApiRequest::SessionInfo(_, si_tx), + ) + ) => { + si_tx.send(Ok(Some(session_info.clone()))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request( + _, + RuntimeApiRequest::SessionExecutorParams(_, si_tx), + ) + ) => { + // Make sure all SessionExecutorParams calls are not made for the leaf (but for its relay parent) + si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), ) + ) => { + si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + // Guarantees the approval work has been relaunched. + recover_available_data(&mut virtual_overseer).await; + fetch_validation_code(&mut virtual_overseer).await; + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::CandidateValidation(CandidateValidationMessage::ValidateFromExhaustive { + exec_kind, + response_sender, + .. + }) if exec_kind == PvfExecKind::Approval => { + response_sender.send(Ok(ValidationResult::Valid(Default::default(), Default::default()))) + .unwrap(); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + clock + .inner + .lock() + .wakeup_all(slot_to_tick(slot) + 2 * test_tranche as u64 + RESTART_WAKEUP_DELAY); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeAssignment( + _, + _, + )) => { + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request(_, RuntimeApiRequest::ApprovalVotingParams(_, sender))) => { + let _ = sender.send(Ok(ApprovalVotingParams { + max_approval_coalesce_count: 1, + })); + } + ); + + assert_matches!( + overseer_recv(&mut virtual_overseer).await, + AllMessages::ApprovalDistribution(ApprovalDistributionMessage::DistributeApproval(_)) + ); + + // Assert that there are no more messages being sent by the subsystem + assert!(overseer_recv(&mut virtual_overseer).timeout(TIMEOUT / 2).await.is_none()); + + virtual_overseer + }); +} + // Test we correctly update the timer when we mark the beginning of gathering assignments. #[test] fn test_gathering_assignments_statements() { diff --git a/prdoc/pr_6973.prdoc b/prdoc/pr_6973.prdoc new file mode 100644 index 000000000000..416789b9171a --- /dev/null +++ b/prdoc/pr_6973.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: approval-voting fix sending of assignments after restart + +doc: + - audience: Node Dev + description: | + There is a problem on restart where nodes will not trigger their needed assignment if + they were offline and the time of the assignment passed, so after restart always + schedule a wakeup so that nodes a have the opportunity of triggering their assignments + if they are still needed. + +crates: + - name: polkadot-node-core-approval-voting + bump: minor From ba36b2d2293d72d087072254e6371d9089f192b7 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Tue, 14 Jan 2025 18:56:30 +0100 Subject: [PATCH 258/340] CI: Only format umbrella crate during umbrella check (#7139) The umbrella crate quick-check was always failing whenever there was something misformated in the whole codebase. This leads to an error that indicates that a new crate was added, even when it was not. After this PR we only apply `cargo fmt` to the newly generated umbrella crate `polkadot-sdk`. This results in this check being independent from the fmt job which should check the entire codebase. --- .github/workflows/checks-quick.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks-quick.yml b/.github/workflows/checks-quick.yml index 4c26b85a6303..1a8813833def 100644 --- a/.github/workflows/checks-quick.yml +++ b/.github/workflows/checks-quick.yml @@ -138,7 +138,7 @@ jobs: # Fixes "detected dubious ownership" error in the ci git config --global --add safe.directory '*' python3 scripts/generate-umbrella.py --sdk . --version 0.1.0 - cargo +nightly fmt --all + cargo +nightly fmt -p polkadot-sdk if [ -n "$(git status --porcelain)" ]; then cat < Date: Tue, 14 Jan 2025 20:57:05 +0100 Subject: [PATCH 259/340] xcm: convert properly assets in xcmpayment apis (#7134) Port #6459 changes to relays as well, which were probably forgotten in that PR. Thanks! --------- Co-authored-by: Francisco Aguirre Co-authored-by: command-bot <> --- polkadot/runtime/rococo/src/lib.rs | 3 ++- polkadot/runtime/westend/src/lib.rs | 3 ++- prdoc/pr_7134.prdoc | 11 +++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_7134.prdoc diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index e5d703700fee..b3f2a0033278 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1885,7 +1885,8 @@ sp_api::impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::TokenLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 9d77a5e5eea1..58d2bdcb7c7d 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -2445,7 +2445,8 @@ sp_api::impl_runtime_apis! { } fn query_weight_to_asset_fee(weight: Weight, asset: VersionedAssetId) -> Result { - match asset.try_as::() { + let latest_asset_id: Result = asset.clone().try_into(); + match latest_asset_id { Ok(asset_id) if asset_id.0 == xcm_config::TokenLocation::get() => { // for native token Ok(WeightToFee::weight_to_fee(&weight)) diff --git a/prdoc/pr_7134.prdoc b/prdoc/pr_7134.prdoc new file mode 100644 index 000000000000..095d4757f438 --- /dev/null +++ b/prdoc/pr_7134.prdoc @@ -0,0 +1,11 @@ +title: 'xcm: convert properly assets in xcmpayment apis' +doc: +- audience: Runtime User + description: |- + Port #6459 changes to relays as well, which were probably forgotten in that PR. + Thanks! +crates: +- name: rococo-runtime + bump: patch +- name: westend-runtime + bump: patch From 5f391db8af50a79db83acfe37f73c7202177d71c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 14 Jan 2025 21:22:52 +0100 Subject: [PATCH 260/340] PRDOC: Document `validate: false` (#7117) --- docs/contributor/prdoc.md | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/contributor/prdoc.md b/docs/contributor/prdoc.md index 1f6252425e69..b3f7a7e94f0c 100644 --- a/docs/contributor/prdoc.md +++ b/docs/contributor/prdoc.md @@ -81,9 +81,6 @@ picked if no other applies. The `None` option is equivalent to the `R0-silent` l level. Experimental and private APIs are exempt from bumping and can be broken at any time. Please read the [Crate Section](../RELEASE.md) of the RELEASE doc about them. -> **Note**: There is currently no CI in place to sanity check this information, but should be added -> soon. - ### Example For example when you modified two crates and record the changes: @@ -106,3 +103,21 @@ you do not need to bump a crate that had a SemVer breaking change only from re-e crate with a breaking change. `minor` an `patch` bumps do not need to be inherited, since `cargo` will automatically update them to the latest compatible version. + +### Overwrite CI check + +The `check-semver` CI check is doing sanity checks based on the provided `PRDoc` and the mentioned +crate version bumps. The tooling is not perfect and it may recommends incorrect bumps of the version. +The CI check can be forced to accept the provided version bump. This can be done like: + +```yaml +crates: + - name: frame-example + bump: major + validate: false + - name: frame-example-pallet + bump: minor +``` + +By putting `validate: false` for `frame-example`, the version bump is ignored by the tooling. For +`frame-example-pallet` the version bump is still validated by the CI check. From d5539aa63edc8068eff9c4cbb78214c3a5ab66b2 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Tue, 14 Jan 2025 23:47:19 +0100 Subject: [PATCH 261/340] Parachains: Use relay chain slot for velocity measurement (#6825) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #3967 ## Changes We now use relay chain slots to measure velocity on chain. Previously we were storing the current parachain slot. Then in `on_state_proof` of the `ConsensusHook` we were checking how many blocks were athored in the current parachain slot. This works well when the parachain slot time and relay chain slot time is the same. With elastic scaling, we can have parachain slot times lower than that of the relay chain. In these cases we want to measure velocity in relation to the relay chain. This PR adjusts that. ## Migration This PR includes a migration. Storage item `SlotInfo` of pallet `aura-ext` is renamed to `RelaySlotInfo` to better reflect its new content. A migration has been added that just kills the old storage item. `RelaySlotInfo` will be `None` initially but its value will be adjusted after one new relay chain slot arrives. --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- Cargo.lock | 5 + .../consensus/aura/src/collators/lookahead.rs | 1 + .../consensus/aura/src/collators/mod.rs | 30 +- .../slot_based/block_builder_task.rs | 11 +- cumulus/client/parachain-inherent/src/mock.rs | 11 +- cumulus/pallets/aura-ext/Cargo.toml | 8 +- .../pallets/aura-ext/src/consensus_hook.rs | 42 ++- cumulus/pallets/aura-ext/src/lib.rs | 26 +- cumulus/pallets/aura-ext/src/migration.rs | 74 ++++ cumulus/pallets/aura-ext/src/test.rs | 338 ++++++++++++++++++ .../parachain-system/src/consensus_hook.rs | 4 +- cumulus/pallets/parachain-system/src/lib.rs | 4 +- .../assets/asset-hub-rococo/src/lib.rs | 1 + .../assets/asset-hub-westend/src/lib.rs | 2 +- .../bridge-hubs/bridge-hub-rococo/src/lib.rs | 1 + .../bridge-hubs/bridge-hub-westend/src/lib.rs | 1 + .../collectives-westend/src/lib.rs | 1 + .../contracts/contracts-rococo/src/lib.rs | 1 + .../coretime/coretime-rococo/src/lib.rs | 1 + .../coretime/coretime-westend/src/lib.rs | 1 + .../runtimes/people/people-rococo/src/lib.rs | 1 + .../runtimes/people/people-westend/src/lib.rs | 1 + cumulus/primitives/aura/src/lib.rs | 6 +- cumulus/xcm/xcm-emulator/src/lib.rs | 1 + prdoc/pr_6825.prdoc | 50 +++ 25 files changed, 560 insertions(+), 62 deletions(-) create mode 100644 cumulus/pallets/aura-ext/src/migration.rs create mode 100644 cumulus/pallets/aura-ext/src/test.rs create mode 100644 prdoc/pr_6825.prdoc diff --git a/Cargo.lock b/Cargo.lock index 3eab84d5ed16..7725db743c41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4874,6 +4874,8 @@ name = "cumulus-pallet-aura-ext" version = "0.7.0" dependencies = [ "cumulus-pallet-parachain-system 0.7.0", + "cumulus-primitives-core 0.7.0", + "cumulus-test-relay-sproof-builder 0.7.0", "frame-support 28.0.0", "frame-system 28.0.0", "pallet-aura 27.0.0", @@ -4882,7 +4884,10 @@ dependencies = [ "scale-info", "sp-application-crypto 30.0.0", "sp-consensus-aura 0.32.0", + "sp-core 28.0.0", + "sp-io 30.0.0", "sp-runtime 31.0.1", + "sp-version 29.0.0", ] [[package]] diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 2dbcf5eb58e9..7723de5a576a 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -336,6 +336,7 @@ where ); Some(super::can_build_upon::<_, _, P>( slot_now, + relay_slot, timestamp, block_hash, included_block, diff --git a/cumulus/client/consensus/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index 89070607fbab..031fa963ba6a 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -34,7 +34,7 @@ use polkadot_primitives::{ ValidationCodeHash, }; use sc_consensus_aura::{standalone as aura_internal, AuraApi}; -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_core::Pair; use sp_keystore::KeystorePtr; use sp_timestamp::Timestamp; @@ -160,7 +160,8 @@ async fn cores_scheduled_for_para( // Checks if we own the slot at the given block and whether there // is space in the unincluded segment. async fn can_build_upon( - slot: Slot, + para_slot: Slot, + relay_slot: Slot, timestamp: Timestamp, parent_hash: Block::Hash, included_block: Block::Hash, @@ -169,25 +170,28 @@ async fn can_build_upon( ) -> Option> where Client: ProvideRuntimeApi, - Client::Api: AuraApi + AuraUnincludedSegmentApi, + Client::Api: AuraApi + AuraUnincludedSegmentApi + ApiExt, P: Pair, P::Public: Codec, P::Signature: Codec, { let runtime_api = client.runtime_api(); let authorities = runtime_api.authorities(parent_hash).ok()?; - let author_pub = aura_internal::claim_slot::

(slot, &authorities, keystore).await?; + let author_pub = aura_internal::claim_slot::

(para_slot, &authorities, keystore).await?; - // Here we lean on the property that building on an empty unincluded segment must always - // be legal. Skipping the runtime API query here allows us to seamlessly run this - // collator against chains which have not yet upgraded their runtime. - if parent_hash != included_block && - !runtime_api.can_build_upon(parent_hash, included_block, slot).ok()? - { - return None - } + let Ok(Some(api_version)) = + runtime_api.api_version::>(parent_hash) + else { + return (parent_hash == included_block) + .then(|| SlotClaim::unchecked::

(author_pub, para_slot, timestamp)); + }; + + let slot = if api_version > 1 { relay_slot } else { para_slot }; - Some(SlotClaim::unchecked::

(author_pub, slot, timestamp)) + runtime_api + .can_build_upon(parent_hash, included_block, slot) + .ok()? + .then(|| SlotClaim::unchecked::

(author_pub, para_slot, timestamp)) } /// Use [`cumulus_client_consensus_common::find_potential_parents`] to find parachain blocks that diff --git a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs index 41751f1db530..48287555dea6 100644 --- a/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs +++ b/cumulus/client/consensus/aura/src/collators/slot_based/block_builder_task.rs @@ -23,7 +23,7 @@ use cumulus_primitives_aura::AuraUnincludedSegmentApi; use cumulus_primitives_core::{GetCoreSelectorApi, PersistedValidationData}; use cumulus_relay_chain_interface::RelayChainInterface; -use polkadot_primitives::Id as ParaId; +use polkadot_primitives::{Block as RelayBlock, Id as ParaId}; use futures::prelude::*; use sc_client_api::{backend::AuxStore, BlockBackend, BlockOf, UsageProvider}; @@ -302,8 +302,17 @@ where // on-chain data. collator.collator_service().check_block_status(parent_hash, &parent_header); + let Ok(relay_slot) = + sc_consensus_babe::find_pre_digest::(relay_parent_header) + .map(|babe_pre_digest| babe_pre_digest.slot()) + else { + tracing::error!(target: crate::LOG_TARGET, "Relay chain does not contain babe slot. This should never happen."); + continue; + }; + let slot_claim = match crate::collators::can_build_upon::<_, _, P>( para_slot.slot, + relay_slot, para_slot.timestamp, parent_hash, included_block, diff --git a/cumulus/client/parachain-inherent/src/mock.rs b/cumulus/client/parachain-inherent/src/mock.rs index e08aca932564..8dbc6ace0f06 100644 --- a/cumulus/client/parachain-inherent/src/mock.rs +++ b/cumulus/client/parachain-inherent/src/mock.rs @@ -17,8 +17,9 @@ use crate::{ParachainInherentData, INHERENT_IDENTIFIER}; use codec::Decode; use cumulus_primitives_core::{ - relay_chain, relay_chain::UpgradeGoAhead, InboundDownwardMessage, InboundHrmpMessage, ParaId, - PersistedValidationData, + relay_chain, + relay_chain::{Slot, UpgradeGoAhead}, + InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData, }; use cumulus_primitives_parachain_inherent::MessageQueueChain; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; @@ -28,9 +29,6 @@ use sp_inherents::{InherentData, InherentDataProvider}; use sp_runtime::traits::Block; use std::collections::BTreeMap; -/// Relay chain slot duration, in milliseconds. -pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; - /// Inherent data provider that supplies mocked validation data. /// /// This is useful when running a node that is not actually backed by any relay chain. @@ -175,8 +173,7 @@ impl> InherentDataProvider // Calculate the mocked relay block based on the current para block let relay_parent_number = self.relay_offset + self.relay_blocks_per_para_block * self.current_para_block; - sproof_builder.current_slot = - ((relay_parent_number / RELAY_CHAIN_SLOT_DURATION_MILLIS) as u64).into(); + sproof_builder.current_slot = Slot::from(relay_parent_number as u64); sproof_builder.upgrade_go_ahead = self.upgrade_go_ahead; // Process the downward messages and set up the correct head diff --git a/cumulus/pallets/aura-ext/Cargo.toml b/cumulus/pallets/aura-ext/Cargo.toml index fcda79f1d5c1..82638de71aa1 100644 --- a/cumulus/pallets/aura-ext/Cargo.toml +++ b/cumulus/pallets/aura-ext/Cargo.toml @@ -28,9 +28,15 @@ sp-runtime = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } [dev-dependencies] - # Cumulus cumulus-pallet-parachain-system = { workspace = true, default-features = true } +cumulus-primitives-core = { workspace = true, default-features = true } +cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } + +# Substrate +sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/cumulus/pallets/aura-ext/src/consensus_hook.rs b/cumulus/pallets/aura-ext/src/consensus_hook.rs index c1a8568bdd83..56966aa0c8f8 100644 --- a/cumulus/pallets/aura-ext/src/consensus_hook.rs +++ b/cumulus/pallets/aura-ext/src/consensus_hook.rs @@ -18,7 +18,6 @@ //! block velocity. //! //! The velocity `V` refers to the rate of block processing by the relay chain. - use super::{pallet, Aura}; use core::{marker::PhantomData, num::NonZeroU32}; use cumulus_pallet_parachain_system::{ @@ -54,8 +53,23 @@ where let velocity = V.max(1); let relay_chain_slot = state_proof.read_slot().expect("failed to read relay chain slot"); - let (slot, authored) = - pallet::SlotInfo::::get().expect("slot info is inserted on block initialization"); + let (relay_chain_slot, authored_in_relay) = match pallet::RelaySlotInfo::::get() { + Some((slot, authored)) if slot == relay_chain_slot => (slot, authored), + Some((slot, _)) if slot < relay_chain_slot => (relay_chain_slot, 0), + Some((slot, _)) => { + panic!("Slot moved backwards: stored_slot={slot:?}, relay_chain_slot={relay_chain_slot:?}") + }, + None => (relay_chain_slot, 0), + }; + + // We need to allow one additional block to be built to fill the unincluded segment. + if authored_in_relay > velocity { + panic!("authored blocks limit is reached for the slot: relay_chain_slot={relay_chain_slot:?}, authored={authored_in_relay:?}, velocity={velocity:?}"); + } + + pallet::RelaySlotInfo::::put((relay_chain_slot, authored_in_relay + 1)); + + let para_slot = pallet_aura::CurrentSlot::::get(); // Convert relay chain timestamp. let relay_chain_timestamp = @@ -67,19 +81,16 @@ where // Check that we are not too far in the future. Since we expect `V` parachain blocks // during the relay chain slot, we can allow for `V` parachain slots into the future. - if *slot > *para_slot_from_relay + u64::from(velocity) { + if *para_slot > *para_slot_from_relay + u64::from(velocity) { panic!( - "Parachain slot is too far in the future: parachain_slot: {:?}, derived_from_relay_slot: {:?} velocity: {:?}", - slot, + "Parachain slot is too far in the future: parachain_slot={:?}, derived_from_relay_slot={:?} velocity={:?}, relay_chain_slot={:?}", + para_slot, para_slot_from_relay, - velocity + velocity, + relay_chain_slot ); } - // We need to allow authoring multiple blocks in the same slot. - if slot != para_slot_from_relay && authored > velocity { - panic!("authored blocks limit is reached for the slot") - } let weight = T::DbWeight::get().reads(1); ( @@ -110,7 +121,7 @@ impl< /// is more recent than the included block itself. pub fn can_build_upon(included_hash: T::Hash, new_slot: Slot) -> bool { let velocity = V.max(1); - let (last_slot, authored_so_far) = match pallet::SlotInfo::::get() { + let (last_slot, authored_so_far) = match pallet::RelaySlotInfo::::get() { None => return true, Some(x) => x, }; @@ -123,11 +134,8 @@ impl< return false } - // TODO: This logic needs to be adjusted. - // It checks that we have not authored more than `V + 1` blocks in the slot. - // As a slot however, we take the parachain slot here. Velocity should - // be measured in relation to the relay chain slot. - // https://github.com/paritytech/polkadot-sdk/issues/3967 + // Check that we have not authored more than `V + 1` parachain blocks in the current relay + // chain slot. if last_slot == new_slot { authored_so_far < velocity + 1 } else { diff --git a/cumulus/pallets/aura-ext/src/lib.rs b/cumulus/pallets/aura-ext/src/lib.rs index dc854eb82018..19c2634ca708 100644 --- a/cumulus/pallets/aura-ext/src/lib.rs +++ b/cumulus/pallets/aura-ext/src/lib.rs @@ -40,6 +40,9 @@ use sp_consensus_aura::{digests::CompatibleDigestItem, Slot}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; pub mod consensus_hook; +pub mod migration; +mod test; + pub use consensus_hook::FixedVelocityConsensusHook; type Aura = pallet_aura::Pallet; @@ -57,6 +60,7 @@ pub mod pallet { pub trait Config: pallet_aura::Config + frame_system::Config {} #[pallet::pallet] + #[pallet::storage_version(migration::STORAGE_VERSION)] pub struct Pallet(_); #[pallet::hooks] @@ -70,20 +74,7 @@ pub mod pallet { // Fetch the authorities once to get them into the storage proof of the PoV. Authorities::::get(); - let new_slot = pallet_aura::CurrentSlot::::get(); - - let (new_slot, authored) = match SlotInfo::::get() { - Some((slot, authored)) if slot == new_slot => (slot, authored + 1), - Some((slot, _)) if slot < new_slot => (new_slot, 1), - Some(..) => { - panic!("slot moved backwards") - }, - None => (new_slot, 1), - }; - - SlotInfo::::put((new_slot, authored)); - - T::DbWeight::get().reads_writes(4, 2) + T::DbWeight::get().reads_writes(1, 0) } } @@ -99,11 +90,12 @@ pub mod pallet { ValueQuery, >; - /// Current slot paired with a number of authored blocks. + /// Current relay chain slot paired with a number of authored blocks. /// - /// Updated on each block initialization. + /// This is updated in [`FixedVelocityConsensusHook::on_state_proof`] with the current relay + /// chain slot as provided by the relay chain state proof. #[pallet::storage] - pub(crate) type SlotInfo = StorageValue<_, (Slot, u32), OptionQuery>; + pub(crate) type RelaySlotInfo = StorageValue<_, (Slot, u32), OptionQuery>; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] diff --git a/cumulus/pallets/aura-ext/src/migration.rs b/cumulus/pallets/aura-ext/src/migration.rs new file mode 100644 index 000000000000..b580c19fc733 --- /dev/null +++ b/cumulus/pallets/aura-ext/src/migration.rs @@ -0,0 +1,74 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . +extern crate alloc; + +use crate::{Config, Pallet}; +#[cfg(feature = "try-runtime")] +use alloc::vec::Vec; +use frame_support::{migrations::VersionedMigration, pallet_prelude::StorageVersion}; + +/// The in-code storage version. +pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + +mod v0 { + use super::*; + use frame_support::{pallet_prelude::OptionQuery, storage_alias}; + use sp_consensus_aura::Slot; + + /// Current slot paired with a number of authored blocks. + /// + /// Updated on each block initialization. + #[storage_alias] + pub(super) type SlotInfo = StorageValue, (Slot, u32), OptionQuery>; +} +mod v1 { + use super::*; + use frame_support::{pallet_prelude::*, traits::UncheckedOnRuntimeUpgrade}; + + pub struct UncheckedMigrationToV1(PhantomData); + + impl UncheckedOnRuntimeUpgrade for UncheckedMigrationToV1 { + fn on_runtime_upgrade() -> Weight { + let mut weight: Weight = Weight::zero(); + weight += migrate::(); + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + Ok(Vec::new()) + } + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + ensure!(!v0::SlotInfo::::exists(), "SlotInfo should not exist"); + Ok(()) + } + } + + pub fn migrate() -> Weight { + v0::SlotInfo::::kill(); + T::DbWeight::get().writes(1) + } +} + +/// Migrate `V0` to `V1`. +pub type MigrateV0ToV1 = VersionedMigration< + 0, + 1, + v1::UncheckedMigrationToV1, + Pallet, + ::DbWeight, +>; diff --git a/cumulus/pallets/aura-ext/src/test.rs b/cumulus/pallets/aura-ext/src/test.rs new file mode 100644 index 000000000000..b0099381e682 --- /dev/null +++ b/cumulus/pallets/aura-ext/src/test.rs @@ -0,0 +1,338 @@ +// Copyright Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +#![cfg(test)] +extern crate alloc; + +use super::*; + +use core::num::NonZeroU32; +use cumulus_pallet_parachain_system::{ + consensus_hook::ExpectParentIncluded, AnyRelayNumber, DefaultCoreSelector, ParachainSetCode, +}; +use cumulus_primitives_core::ParaId; +use frame_support::{ + derive_impl, + pallet_prelude::ConstU32, + parameter_types, + traits::{ConstBool, ConstU64, EnqueueWithOrigin}, +}; +use sp_io::TestExternalities; +use sp_version::RuntimeVersion; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + ParachainSystem: cumulus_pallet_parachain_system, + Aura: pallet_aura, + AuraExt: crate, + } +); + +parameter_types! { + pub Version: RuntimeVersion = RuntimeVersion { + spec_name: "test".into(), + impl_name: "system-test".into(), + authoring_version: 1, + spec_version: 1, + impl_version: 1, + apis: sp_version::create_apis_vec!([]), + transaction_version: 1, + system_version: 1, + }; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type Version = Version; + type OnSetCode = ParachainSetCode; + type RuntimeEvent = (); +} + +impl crate::Config for Test {} + +impl pallet_aura::Config for Test { + type AuthorityId = sp_consensus_aura::sr25519::AuthorityId; + type MaxAuthorities = ConstU32<100_000>; + type DisabledValidators = (); + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = ConstU64<6000>; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = (); + type WeightInfo = (); +} + +impl cumulus_pallet_parachain_system::Config for Test { + type WeightInfo = (); + type RuntimeEvent = (); + type OnSystemEvent = (); + type SelfParaId = (); + type OutboundXcmpMessageSource = (); + // Ignore all DMP messages by enqueueing them into `()`: + type DmpQueue = EnqueueWithOrigin<(), sp_core::ConstU8<0>>; + type ReservedDmpWeight = (); + type XcmpMessageHandler = (); + type ReservedXcmpWeight = (); + type CheckAssociatedRelayNumber = AnyRelayNumber; + type ConsensusHook = ExpectParentIncluded; + type SelectCore = DefaultCoreSelector; +} + +#[cfg(test)] +mod test { + use crate::test::*; + use cumulus_pallet_parachain_system::{ + Ancestor, ConsensusHook, RelayChainStateProof, UsedBandwidth, + }; + use sp_core::H256; + + fn set_ancestors() { + let mut ancestors = Vec::new(); + for i in 0..3 { + let mut ancestor = Ancestor::new_unchecked(UsedBandwidth::default(), None); + ancestor.replace_para_head_hash(H256::repeat_byte(i + 1)); + ancestors.push(ancestor); + } + cumulus_pallet_parachain_system::UnincludedSegment::::put(ancestors); + } + + pub fn new_test_ext(para_slot: u64) -> sp_io::TestExternalities { + let mut ext = TestExternalities::new_empty(); + ext.execute_with(|| { + set_ancestors(); + // Set initial parachain slot + pallet_aura::CurrentSlot::::put(Slot::from(para_slot)); + }); + ext + } + + fn set_relay_slot(slot: u64, authored: u32) { + RelaySlotInfo::::put((Slot::from(slot), authored)) + } + + fn relay_chain_state_proof(relay_slot: u64) -> RelayChainStateProof { + let mut builder = cumulus_test_relay_sproof_builder::RelayStateSproofBuilder::default(); + builder.current_slot = relay_slot.into(); + + let (hash, state_proof) = builder.into_state_root_and_proof(); + + RelayChainStateProof::new(ParaId::from(200), hash, state_proof) + .expect("Should be able to construct state proof.") + } + + fn assert_slot_info(expected_slot: u64, expected_authored: u32) { + let (slot, authored) = pallet::RelaySlotInfo::::get().unwrap(); + assert_eq!(slot, Slot::from(expected_slot), "Slot stored in RelaySlotInfo is incorrect."); + assert_eq!( + authored, expected_authored, + "Number of authored blocks stored in RelaySlotInfo is incorrect." + ); + } + + #[test] + fn test_velocity() { + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + let (_, capacity) = Hook::on_state_proof(&state_proof); + assert_eq!(capacity, NonZeroU32::new(1).unwrap().into()); + assert_slot_info(10, 1); + + let (_, capacity) = Hook::on_state_proof(&state_proof); + assert_eq!(capacity, NonZeroU32::new(1).unwrap().into()); + assert_slot_info(10, 2); + }); + } + + #[test] + #[should_panic(expected = "authored blocks limit is reached for the slot")] + fn test_exceeding_velocity_limit() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + for authored in 0..=VELOCITY + 1 { + Hook::on_state_proof(&state_proof); + assert_slot_info(10, authored + 1); + } + }); + } + + #[test] + fn test_para_slot_calculated_from_slot_duration() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(6).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + + let para_slot = Slot::from(7); + pallet_aura::CurrentSlot::::put(para_slot); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + fn test_velocity_at_least_one() { + // Even though this is 0, one block should always be allowed. + const VELOCITY: u32 = 0; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(6).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + #[should_panic( + expected = "Parachain slot is too far in the future: parachain_slot=Slot(8), derived_from_relay_slot=Slot(5) velocity=2" + )] + fn test_para_slot_calculated_from_slot_duration_2() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(8).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + let (_, _) = Hook::on_state_proof(&state_proof); + }); + } + + #[test] + fn test_velocity_resets_on_new_relay_slot() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + for authored in 0..=VELOCITY { + Hook::on_state_proof(&state_proof); + assert_slot_info(10, authored + 1); + } + + let state_proof = relay_chain_state_proof(11); + for authored in 0..=VELOCITY { + Hook::on_state_proof(&state_proof); + assert_slot_info(11, authored + 1); + } + }); + } + + #[test] + #[should_panic( + expected = "Slot moved backwards: stored_slot=Slot(10), relay_chain_slot=Slot(9)" + )] + fn test_backward_relay_slot_not_tolerated() { + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + assert_slot_info(10, 1); + + let state_proof = relay_chain_state_proof(9); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + #[should_panic( + expected = "Parachain slot is too far in the future: parachain_slot=Slot(13), derived_from_relay_slot=Slot(10) velocity=2" + )] + fn test_future_parachain_slot_errors() { + type Hook = FixedVelocityConsensusHook; + + new_test_ext(13).execute_with(|| { + let state_proof = relay_chain_state_proof(10); + Hook::on_state_proof(&state_proof); + }); + } + + #[test] + fn test_can_build_upon_true_when_empty() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let hash = H256::repeat_byte(0x1); + assert!(Hook::can_build_upon(hash, Slot::from(1))); + }); + } + + #[test] + fn test_can_build_upon_respects_velocity() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let hash = H256::repeat_byte(0x1); + let relay_slot = Slot::from(10); + + set_relay_slot(10, VELOCITY - 1); + assert!(Hook::can_build_upon(hash, relay_slot)); + + set_relay_slot(10, VELOCITY); + assert!(Hook::can_build_upon(hash, relay_slot)); + + set_relay_slot(10, VELOCITY + 1); + // Velocity too high + assert!(!Hook::can_build_upon(hash, relay_slot)); + }); + } + + #[test] + fn test_can_build_upon_slot_can_not_decrease() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let hash = H256::repeat_byte(0x1); + + set_relay_slot(10, VELOCITY); + // Slot moves backwards + assert!(!Hook::can_build_upon(hash, Slot::from(9))); + }); + } + + #[test] + fn test_can_build_upon_unincluded_segment_size() { + const VELOCITY: u32 = 2; + type Hook = FixedVelocityConsensusHook; + + new_test_ext(1).execute_with(|| { + let relay_slot = Slot::from(10); + + set_relay_slot(10, VELOCITY); + // Size after included is two, we can not build + let hash = H256::repeat_byte(0x1); + assert!(!Hook::can_build_upon(hash, relay_slot)); + + // Size after included is one, we can build + let hash = H256::repeat_byte(0x2); + assert!(Hook::can_build_upon(hash, relay_slot)); + }); + } +} diff --git a/cumulus/pallets/parachain-system/src/consensus_hook.rs b/cumulus/pallets/parachain-system/src/consensus_hook.rs index 3062396a4e78..6d65bdc77186 100644 --- a/cumulus/pallets/parachain-system/src/consensus_hook.rs +++ b/cumulus/pallets/parachain-system/src/consensus_hook.rs @@ -22,7 +22,7 @@ use core::num::NonZeroU32; use frame_support::weights::Weight; /// The possible capacity of the unincluded segment. -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub struct UnincludedSegmentCapacity(UnincludedSegmentCapacityInner); impl UnincludedSegmentCapacity { @@ -41,7 +41,7 @@ impl UnincludedSegmentCapacity { } } -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq)] pub(crate) enum UnincludedSegmentCapacityInner { ExpectParentIncluded, Value(NonZeroU32), diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 0fa759357f65..6857b08e66b7 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -80,8 +80,7 @@ pub mod relay_state_snapshot; pub mod validate_block; use unincluded_segment::{ - Ancestor, HrmpChannelUpdate, HrmpWatermarkUpdate, OutboundBandwidthLimits, SegmentTracker, - UsedBandwidth, + HrmpChannelUpdate, HrmpWatermarkUpdate, OutboundBandwidthLimits, SegmentTracker, }; pub use consensus_hook::{ConsensusHook, ExpectParentIncluded}; @@ -109,6 +108,7 @@ pub use consensus_hook::{ConsensusHook, ExpectParentIncluded}; /// ``` pub use cumulus_pallet_parachain_system_proc_macro::register_validate_block; pub use relay_state_snapshot::{MessagingStateSnapshot, RelayChainStateProof}; +pub use unincluded_segment::{Ancestor, UsedBandwidth}; pub use pallet::*; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index 1db152e39fd9..db9a8201ebbe 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -1050,6 +1050,7 @@ pub type Migrations = ( >, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); parameter_types! { diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 71cfdc58cceb..cfc150ce5d6f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -341,7 +341,6 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< xcm::v5::Location, AccountId, >; - /// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. pub type NativeAndAssets = fungible::UnionOf< Balances, @@ -1129,6 +1128,7 @@ pub type Migrations = ( >, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Asset Hub Westend has some undecodable storage, delete it. diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs index 35af034310d9..67bc06a9321e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs @@ -184,6 +184,7 @@ pub type Migrations = ( pallet_bridge_relayers::migration::v1::MigrationToV1, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); parameter_types! { diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index 2c2e01b4d21d..3824a4e9a7cb 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -171,6 +171,7 @@ pub type Migrations = ( bridge_to_ethereum_config::migrations::MigrationForXcmV5, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); parameter_types! { diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index e9adc4d1eae7..5eafc2960cc8 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -770,6 +770,7 @@ type Migrations = ( pallet_core_fellowship::migration::MigrateV0ToV1, // unreleased pallet_core_fellowship::migration::MigrateV0ToV1, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Executive: handles dispatch to the various modules. diff --git a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs index 3348a635df01..eaaaf0a9a9a7 100644 --- a/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/contracts/contracts-rococo/src/lib.rs @@ -118,6 +118,7 @@ pub type Migrations = ( cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); type EventRecord = frame_system::EventRecord< diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs index e9171c79afae..622a40e1d8dc 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/lib.rs @@ -129,6 +129,7 @@ pub type Migrations = ( pallet_broker::migration::MigrateV3ToV4, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Executive: handles dispatch to the various modules. diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs index 975856b3b6ff..7312c9c1639d 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/lib.rs @@ -129,6 +129,7 @@ pub type Migrations = ( pallet_broker::migration::MigrateV3ToV4, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Executive: handles dispatch to the various modules. diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs index ffdd86c500e5..cb0282b17a6c 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/lib.rs @@ -116,6 +116,7 @@ pub type Migrations = ( cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Executive: handles dispatch to the various modules. diff --git a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs index ee6b0db55b91..050256dd4f6a 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/lib.rs @@ -115,6 +115,7 @@ pub type Migrations = ( pallet_collator_selection::migration::v2::MigrationToV2, // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, ); /// Executive: handles dispatch to the various modules. diff --git a/cumulus/primitives/aura/src/lib.rs b/cumulus/primitives/aura/src/lib.rs index aeeee5f8bafa..4e7d7dc3e79d 100644 --- a/cumulus/primitives/aura/src/lib.rs +++ b/cumulus/primitives/aura/src/lib.rs @@ -34,10 +34,14 @@ sp_api::decl_runtime_apis! { /// When the unincluded segment is short, Aura chains will allow authors to create multiple /// blocks per slot in order to build a backlog. When it is saturated, this API will limit /// the amount of blocks that can be created. + /// + /// Changes: + /// - Version 2: Update to `can_build_upon` to take a relay chain `Slot` instead of a parachain `Slot`. + #[api_version(2)] pub trait AuraUnincludedSegmentApi { /// Whether it is legal to extend the chain, assuming the given block is the most /// recently included one as-of the relay parent that will be built against, and - /// the given slot. + /// the given relay chain slot. /// /// This should be consistent with the logic the runtime uses when validating blocks to /// avoid issues. diff --git a/cumulus/xcm/xcm-emulator/src/lib.rs b/cumulus/xcm/xcm-emulator/src/lib.rs index ff14b747973c..d9b1e7fd9d04 100644 --- a/cumulus/xcm/xcm-emulator/src/lib.rs +++ b/cumulus/xcm/xcm-emulator/src/lib.rs @@ -1118,6 +1118,7 @@ macro_rules! decl_test_networks { ) -> $crate::ParachainInherentData { let mut sproof = $crate::RelayStateSproofBuilder::default(); sproof.para_id = para_id.into(); + sproof.current_slot = $crate::polkadot_primitives::Slot::from(relay_parent_number as u64); // egress channel let e_index = sproof.hrmp_egress_channel_index.get_or_insert_with(Vec::new); diff --git a/prdoc/pr_6825.prdoc b/prdoc/pr_6825.prdoc new file mode 100644 index 000000000000..d57b2b573a10 --- /dev/null +++ b/prdoc/pr_6825.prdoc @@ -0,0 +1,50 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Use relay chain slot for velocity measurement on parachains + +doc: + - audience: Runtime Dev + description: | + The AuraExt pallets `ConsensusHook` is performing checks based on a parachains velocity. It was previously + checking how many blocks where produced in a given parachain slot. This only works well when the parachain + and relay chain slot length is the same. After this PR, we are checking against the relay chain slot. + + **🚨 Action Required:** A migration of name `cumulus_pallet_aura_ext::migration::MigrateV0ToV1` is included + that cleans up a renamed storage item. Parachain must add it to their runtimes. More information is available in + the [reference docs](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/frame_runtime_upgrades_and_migrations/index.html#single-block-migrations). + +crates: + - name: cumulus-pallet-parachain-system + bump: minor + - name: cumulus-pallet-aura-ext + bump: major + - name: cumulus-primitives-aura + bump: major + - name: cumulus-client-parachain-inherent + bump: minor + - name: cumulus-client-consensus-aura + bump: minor + - name: xcm-emulator + bump: minor + - name: asset-hub-rococo-runtime + bump: minor + - name: asset-hub-westend-runtime + bump: minor + - name: bridge-hub-rococo-runtime + bump: minor + - name: bridge-hub-westend-runtime + bump: minor + - name: collectives-westend-runtime + bump: minor + - name: coretime-rococo-runtime + bump: minor + - name: coretime-westend-runtime + bump: minor + - name: people-rococo-runtime + bump: minor + - name: people-westend-runtime + bump: minor + - name: contracts-rococo-runtime + bump: minor + From 0d660a420fbc11a90cde5aa4e43ce2027b502162 Mon Sep 17 00:00:00 2001 From: Alexandru Gheorghe <49718502+alexggh@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:13:23 +0200 Subject: [PATCH 262/340] approval-voting: Make importing of duplicate assignment idempotent (#6971) Normally, approval-voting wouldn't receive duplicate assignments because approval-distribution makes sure of it, however in the situation where we restart we might receive the same assignment again and since approval-voting already persisted it we will end up inserting it twice in `ApprovalEntry.tranches.assignments` because that's an array. Fix this by making sure duplicate assignments are a noop if the validator already had an assignment imported at the same tranche. --------- Signed-off-by: Alexandru Gheorghe Co-authored-by: ordian --- .../approval-voting/src/approval_checking.rs | 78 ++++++++++++------- polkadot/node/core/approval-voting/src/lib.rs | 11 ++- .../approval-voting/src/persisted_entries.rs | 14 +++- prdoc/pr_6971.prdoc | 16 ++++ 4 files changed, 86 insertions(+), 33 deletions(-) create mode 100644 prdoc/pr_6971.prdoc diff --git a/polkadot/node/core/approval-voting/src/approval_checking.rs b/polkadot/node/core/approval-voting/src/approval_checking.rs index 3b7262a46826..c7f38619ea1f 100644 --- a/polkadot/node/core/approval-voting/src/approval_checking.rs +++ b/polkadot/node/core/approval-voting/src/approval_checking.rs @@ -712,13 +712,13 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1, false); - approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + 2); + approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + 2, false); let approvals = bitvec![u8, BitOrderLsb0; 1; 5]; @@ -757,8 +757,10 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, true); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, false); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, true); let approvals = bitvec![u8, BitOrderLsb0; 0; 10]; @@ -798,10 +800,10 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, false); let mut approvals = bitvec![u8, BitOrderLsb0; 0; 10]; approvals.set(0, true); @@ -844,11 +846,11 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick, false); let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators]; approvals.set(0, true); @@ -913,14 +915,24 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1, false); - approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + no_show_duration + 2); - approval_entry.import_assignment(2, ValidatorIndex(5), block_tick + no_show_duration + 2); + approval_entry.import_assignment( + 2, + ValidatorIndex(4), + block_tick + no_show_duration + 2, + false, + ); + approval_entry.import_assignment( + 2, + ValidatorIndex(5), + block_tick + no_show_duration + 2, + false, + ); let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators]; approvals.set(0, true); @@ -1007,14 +1019,24 @@ mod tests { } .into(); - approval_entry.import_assignment(0, ValidatorIndex(0), block_tick); - approval_entry.import_assignment(0, ValidatorIndex(1), block_tick); + approval_entry.import_assignment(0, ValidatorIndex(0), block_tick, false); + approval_entry.import_assignment(0, ValidatorIndex(1), block_tick, false); - approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1); - approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1); + approval_entry.import_assignment(1, ValidatorIndex(2), block_tick + 1, false); + approval_entry.import_assignment(1, ValidatorIndex(3), block_tick + 1, false); - approval_entry.import_assignment(2, ValidatorIndex(4), block_tick + no_show_duration + 2); - approval_entry.import_assignment(2, ValidatorIndex(5), block_tick + no_show_duration + 2); + approval_entry.import_assignment( + 2, + ValidatorIndex(4), + block_tick + no_show_duration + 2, + false, + ); + approval_entry.import_assignment( + 2, + ValidatorIndex(5), + block_tick + no_show_duration + 2, + false, + ); let mut approvals = bitvec![u8, BitOrderLsb0; 0; n_validators]; approvals.set(0, true); @@ -1066,7 +1088,7 @@ mod tests { }, ); - approval_entry.import_assignment(3, ValidatorIndex(6), block_tick); + approval_entry.import_assignment(3, ValidatorIndex(6), block_tick, false); approvals.set(6, true); let tranche_now = no_show_duration as DelayTranche + 3; @@ -1176,7 +1198,7 @@ mod tests { // Populate the requested tranches. The assignments aren't inspected in // this test. for &t in &test_tranche { - approval_entry.import_assignment(t, ValidatorIndex(0), 0) + approval_entry.import_assignment(t, ValidatorIndex(0), 0, false); } let filled_tranches = filled_tranche_iterator(approval_entry.tranches()); diff --git a/polkadot/node/core/approval-voting/src/lib.rs b/polkadot/node/core/approval-voting/src/lib.rs index b4c2a6afee0e..2deca5a1aba8 100644 --- a/polkadot/node/core/approval-voting/src/lib.rs +++ b/polkadot/node/core/approval-voting/src/lib.rs @@ -2808,8 +2808,15 @@ where Vec::new(), )), }; - is_duplicate &= approval_entry.is_assigned(assignment.validator); - approval_entry.import_assignment(tranche, assignment.validator, tick_now); + + let is_duplicate_for_candidate = approval_entry.is_assigned(assignment.validator); + is_duplicate &= is_duplicate_for_candidate; + approval_entry.import_assignment( + tranche, + assignment.validator, + tick_now, + is_duplicate_for_candidate, + ); // We've imported a new assignment, so we need to schedule a wake-up for when that might // no-show. diff --git a/polkadot/node/core/approval-voting/src/persisted_entries.rs b/polkadot/node/core/approval-voting/src/persisted_entries.rs index a5d42d9fd6e6..14c678913dc3 100644 --- a/polkadot/node/core/approval-voting/src/persisted_entries.rs +++ b/polkadot/node/core/approval-voting/src/persisted_entries.rs @@ -172,7 +172,7 @@ impl ApprovalEntry { }); our.map(|a| { - self.import_assignment(a.tranche(), a.validator_index(), tick_now); + self.import_assignment(a.tranche(), a.validator_index(), tick_now, false); (a.cert().clone(), a.validator_index(), a.tranche()) }) @@ -197,6 +197,7 @@ impl ApprovalEntry { tranche: DelayTranche, validator_index: ValidatorIndex, tick_now: Tick, + is_duplicate: bool, ) { // linear search probably faster than binary. not many tranches typically. let idx = match self.tranches.iter().position(|t| t.tranche >= tranche) { @@ -213,8 +214,15 @@ impl ApprovalEntry { self.tranches.len() - 1 }, }; - - self.tranches[idx].assignments.push((validator_index, tick_now)); + // At restart we might have duplicate assignments because approval-distribution is not + // persistent across restarts, so avoid adding duplicates. + // We already know if we have seen an assignment from this validator and since this + // function is on the hot path we can avoid iterating through tranches by using + // !is_duplicate to determine if it is already present in the vector and does not need + // adding. + if !is_duplicate { + self.tranches[idx].assignments.push((validator_index, tick_now)); + } self.assigned_validators.set(validator_index.0 as _, true); } diff --git a/prdoc/pr_6971.prdoc b/prdoc/pr_6971.prdoc new file mode 100644 index 000000000000..4790d773fee4 --- /dev/null +++ b/prdoc/pr_6971.prdoc @@ -0,0 +1,16 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Make importing of duplicate assignment idempotent + +doc: + - audience: Node Dev + description: | + Normally, approval-voting wouldn't receive duplicate assignments because approval-distribution makes + sure of it, however in the situation where we restart we might receive the same assignment again and + since approval-voting already persisted it we will end up inserting it twice in ApprovalEntry.tranches.assignments + because that's an array. Fix this by inserting only assignments that are not duplicate. + +crates: + - name: polkadot-node-core-approval-voting + bump: minor From f798111afc15f464a772cd7ed37910cc6208b713 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Wed, 15 Jan 2025 11:08:49 +0100 Subject: [PATCH 263/340] Fix reversed error message in DispatchInfo (#7170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix error message in `DispatchInfo` where post-dispatch and pre-dispatch weight was reversed. --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- prdoc/pr_7170.prdoc | 8 ++++++++ substrate/frame/support/src/dispatch.rs | 6 ++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_7170.prdoc diff --git a/prdoc/pr_7170.prdoc b/prdoc/pr_7170.prdoc new file mode 100644 index 000000000000..fae908f7407d --- /dev/null +++ b/prdoc/pr_7170.prdoc @@ -0,0 +1,8 @@ +title: Fix reversed error message in DispatchInfo +doc: +- audience: Runtime Dev + description: "Fix error message in `DispatchInfo` where post-dispatch and pre-dispatch\ + \ weight was reversed.\r\n" +crates: +- name: frame-support + bump: patch diff --git a/substrate/frame/support/src/dispatch.rs b/substrate/frame/support/src/dispatch.rs index 990996830030..14bc2667def1 100644 --- a/substrate/frame/support/src/dispatch.rs +++ b/substrate/frame/support/src/dispatch.rs @@ -315,10 +315,8 @@ impl PostDispatchInfo { "Post dispatch weight is greater than pre dispatch weight. \ Pre dispatch weight may underestimating the actual weight. \ Greater post dispatch weight components are ignored. - Pre dispatch weight: {:?}, - Post dispatch weight: {:?}", - actual_weight, - info_total_weight, + Pre dispatch weight: {info_total_weight:?}, + Post dispatch weight: {actual_weight:?}", ); } actual_weight.min(info.total_weight()) From b72e76fba819e8029df27d127c57e3d6f532f1b8 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 15 Jan 2025 18:57:37 +0800 Subject: [PATCH 264/340] Add "run to block" tools (#7109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce `frame_system::Pallet::run_to_block`, `frame_system::Pallet::run_to_block_with`, and `frame_system::RunToBlockHooks` to establish a generic `run_to_block` mechanism for mock tests, minimizing redundant implementations across various pallets. Closes #299. --- Polkadot address: 156HGo9setPcU2qhFMVWLkcmtCEGySLwNqa3DaEiYSWtte4Y --------- Signed-off-by: Xavier Lau Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> Co-authored-by: Guillaume Thiolliere Co-authored-by: Guillaume Thiolliere --- cumulus/pallets/dmp-queue/src/tests.rs | 20 +- .../runtime/common/src/assigned_slots/mod.rs | 89 ++--- polkadot/runtime/common/src/auctions/mock.rs | 15 +- polkadot/runtime/common/src/auctions/tests.rs | 134 +++---- polkadot/runtime/common/src/crowdloan/mod.rs | 57 +-- .../runtime/common/src/integration_tests.rs | 16 +- .../common/src/paras_registrar/mock.rs | 40 +- polkadot/runtime/common/src/slots/mod.rs | 66 ++-- prdoc/pr_7109.prdoc | 11 + .../multi-block-migrations/src/mock.rs | 21 +- substrate/frame/fast-unstake/src/mock.rs | 29 +- substrate/frame/identity/src/tests.rs | 22 +- substrate/frame/lottery/src/mock.rs | 18 +- substrate/frame/lottery/src/tests.rs | 25 +- substrate/frame/migrations/src/mock.rs | 31 +- substrate/frame/nis/src/mock.rs | 14 +- substrate/frame/nis/src/tests.rs | 90 ++--- substrate/frame/nomination-pools/src/mock.rs | 13 +- substrate/frame/recovery/src/mock.rs | 16 +- substrate/frame/recovery/src/tests.rs | 22 +- substrate/frame/root-offences/src/mock.rs | 18 +- substrate/frame/scheduler/src/mock.rs | 10 +- substrate/frame/scheduler/src/tests.rs | 360 +++++++++--------- substrate/frame/society/src/mock.rs | 23 +- substrate/frame/society/src/tests.rs | 12 +- substrate/frame/src/lib.rs | 2 +- substrate/frame/staking/src/mock.rs | 47 +-- .../frame/state-trie-migration/src/lib.rs | 15 +- substrate/frame/system/src/lib.rs | 111 ++++++ .../frame/transaction-storage/src/mock.rs | 25 +- 30 files changed, 650 insertions(+), 722 deletions(-) create mode 100644 prdoc/pr_7109.prdoc diff --git a/cumulus/pallets/dmp-queue/src/tests.rs b/cumulus/pallets/dmp-queue/src/tests.rs index 70d542ea2ed2..368a1c0b4364 100644 --- a/cumulus/pallets/dmp-queue/src/tests.rs +++ b/cumulus/pallets/dmp-queue/src/tests.rs @@ -21,11 +21,7 @@ use super::{migration::*, mock::*}; use crate::*; -use frame_support::{ - pallet_prelude::*, - traits::{OnFinalize, OnIdle, OnInitialize}, - StorageNoopGuard, -}; +use frame_support::{pallet_prelude::*, traits::OnIdle, StorageNoopGuard}; #[test] fn migration_works() { @@ -183,14 +179,12 @@ fn migration_too_long_ignored() { } fn run_to_block(n: u64) { - assert!(n > System::block_number(), "Cannot go back in time"); - - while System::block_number() < n { - AllPalletsWithSystem::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - AllPalletsWithSystem::on_initialize(System::block_number()); - AllPalletsWithSystem::on_idle(System::block_number(), Weight::MAX); - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + AllPalletsWithSystem::on_idle(bn, Weight::MAX); + }), + ); } fn assert_only_event(e: Event) { diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index 65942c127b1c..dea29f53cad4 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -788,39 +788,14 @@ mod tests { t.into() } - fn run_to_block(n: BlockNumber) { - while System::block_number() < n { - let mut block = System::block_number(); - // on_finalize hooks - AssignedSlots::on_finalize(block); - Slots::on_finalize(block); - Parachains::on_finalize(block); - ParasShared::on_finalize(block); - Configuration::on_finalize(block); - Balances::on_finalize(block); - System::on_finalize(block); - // Set next block - System::set_block_number(block + 1); - block = System::block_number(); - // on_initialize hooks - System::on_initialize(block); - Balances::on_initialize(block); - Configuration::on_initialize(block); - ParasShared::on_initialize(block); - Parachains::on_initialize(block); - Slots::on_initialize(block); - AssignedSlots::on_initialize(block); - } - } - #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_eq!(AssignedSlots::current_lease_period_index(), 0); assert_eq!(Slots::deposit_held(1.into(), &1), 0); - run_to_block(3); + System::run_to_block::(3); assert_eq!(AssignedSlots::current_lease_period_index(), 1); }); } @@ -828,7 +803,7 @@ mod tests { #[test] fn assign_perm_slot_fails_for_unknown_para() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::assign_perm_parachain_slot( @@ -843,7 +818,7 @@ mod tests { #[test] fn assign_perm_slot_fails_for_invalid_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::assign_perm_parachain_slot( @@ -858,7 +833,7 @@ mod tests { #[test] fn assign_perm_slot_fails_when_not_parathread() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -881,7 +856,7 @@ mod tests { #[test] fn assign_perm_slot_fails_when_existing_lease() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -920,7 +895,7 @@ mod tests { #[test] fn assign_perm_slot_fails_when_max_perm_slots_exceeded() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -967,7 +942,7 @@ mod tests { fn assign_perm_slot_succeeds_for_parathread() { new_test_ext().execute_with(|| { let mut block = 1; - run_to_block(block); + System::run_to_block::(block); assert_ok!(TestRegistrar::::register( 1, ParaId::from(1_u32), @@ -1000,7 +975,7 @@ mod tests { assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 2), true); block += 1; - run_to_block(block); + System::run_to_block::(block); } // Para lease ended, downgraded back to parathread (on-demand parachain) @@ -1012,7 +987,7 @@ mod tests { #[test] fn assign_temp_slot_fails_for_unknown_para() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::assign_temp_parachain_slot( @@ -1028,7 +1003,7 @@ mod tests { #[test] fn assign_temp_slot_fails_for_invalid_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::assign_temp_parachain_slot( @@ -1044,7 +1019,7 @@ mod tests { #[test] fn assign_temp_slot_fails_when_not_parathread() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -1068,7 +1043,7 @@ mod tests { #[test] fn assign_temp_slot_fails_when_existing_lease() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -1109,7 +1084,7 @@ mod tests { #[test] fn assign_temp_slot_fails_when_max_temp_slots_exceeded() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); // Register 6 paras & a temp slot for each for n in 0..=5 { @@ -1151,7 +1126,7 @@ mod tests { fn assign_temp_slot_succeeds_for_single_parathread() { new_test_ext().execute_with(|| { let mut block = 1; - run_to_block(block); + System::run_to_block::(block); assert_ok!(TestRegistrar::::register( 1, ParaId::from(1_u32), @@ -1195,7 +1170,7 @@ mod tests { assert_eq!(Slots::already_leased(ParaId::from(1_u32), 0, 1), true); block += 1; - run_to_block(block); + System::run_to_block::(block); } // Block 6 @@ -1210,7 +1185,7 @@ mod tests { // Block 12 // Para should get a turn after TemporarySlotLeasePeriodLength * LeasePeriod blocks - run_to_block(12); + System::run_to_block::(12); println!("block #{}", block); println!("lease period #{}", AssignedSlots::current_lease_period_index()); println!("lease {:?}", slots::Leases::::get(ParaId::from(1_u32))); @@ -1225,7 +1200,7 @@ mod tests { fn assign_temp_slot_succeeds_for_multiple_parathreads() { new_test_ext().execute_with(|| { // Block 1, Period 0 - run_to_block(1); + System::run_to_block::(1); // Register 6 paras & a temp slot for each // (3 slots in current lease period, 3 in the next one) @@ -1251,7 +1226,7 @@ mod tests { // Block 1-5, Period 0-1 for n in 1..=5 { if n > 1 { - run_to_block(n); + System::run_to_block::(n); } assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), false); @@ -1264,7 +1239,7 @@ mod tests { // Block 6-11, Period 2-3 for n in 6..=11 { - run_to_block(n); + System::run_to_block::(n); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); @@ -1276,7 +1251,7 @@ mod tests { // Block 12-17, Period 4-5 for n in 12..=17 { - run_to_block(n); + System::run_to_block::(n); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); @@ -1288,7 +1263,7 @@ mod tests { // Block 18-23, Period 6-7 for n in 18..=23 { - run_to_block(n); + System::run_to_block::(n); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), true); @@ -1300,7 +1275,7 @@ mod tests { // Block 24-29, Period 8-9 for n in 24..=29 { - run_to_block(n); + System::run_to_block::(n); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), true); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); @@ -1312,7 +1287,7 @@ mod tests { // Block 30-35, Period 10-11 for n in 30..=35 { - run_to_block(n); + System::run_to_block::(n); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(0)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(1_u32)), false); assert_eq!(TestRegistrar::::is_parachain(ParaId::from(2_u32)), false); @@ -1327,7 +1302,7 @@ mod tests { #[test] fn unassign_slot_fails_for_unknown_para() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::unassign_parachain_slot(RuntimeOrigin::root(), ParaId::from(1_u32),), @@ -1339,7 +1314,7 @@ mod tests { #[test] fn unassign_slot_fails_for_invalid_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::assign_perm_parachain_slot( @@ -1354,7 +1329,7 @@ mod tests { #[test] fn unassign_perm_slot_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -1386,7 +1361,7 @@ mod tests { #[test] fn unassign_temp_slot_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -1419,7 +1394,7 @@ mod tests { #[test] fn set_max_permanent_slots_fails_for_no_root_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::set_max_permanent_slots(RuntimeOrigin::signed(1), 5), @@ -1430,7 +1405,7 @@ mod tests { #[test] fn set_max_permanent_slots_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_eq!(MaxPermanentSlots::::get(), 2); assert_ok!(AssignedSlots::set_max_permanent_slots(RuntimeOrigin::root(), 10),); @@ -1441,7 +1416,7 @@ mod tests { #[test] fn set_max_temporary_slots_fails_for_no_root_origin() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!( AssignedSlots::set_max_temporary_slots(RuntimeOrigin::signed(1), 5), @@ -1452,7 +1427,7 @@ mod tests { #[test] fn set_max_temporary_slots_succeeds() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_eq!(MaxTemporarySlots::::get(), 6); assert_ok!(AssignedSlots::set_max_temporary_slots(RuntimeOrigin::root(), 12),); diff --git a/polkadot/runtime/common/src/auctions/mock.rs b/polkadot/runtime/common/src/auctions/mock.rs index 9fe19e579cfa..e0365d363ca2 100644 --- a/polkadot/runtime/common/src/auctions/mock.rs +++ b/polkadot/runtime/common/src/auctions/mock.rs @@ -20,8 +20,7 @@ use super::*; use crate::{auctions, mock::TestRegistrar}; use frame_support::{ - assert_ok, derive_impl, ord_parameter_types, parameter_types, - traits::{EitherOfDiverse, OnFinalize, OnInitialize}, + assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::EitherOfDiverse, }; use frame_system::{EnsureRoot, EnsureSignedBy}; use pallet_balances; @@ -244,15 +243,3 @@ pub fn new_test_ext() -> sp_io::TestExternalities { }); ext } - -pub fn run_to_block(n: BlockNumber) { - while System::block_number() < n { - Auctions::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Auctions::on_initialize(System::block_number()); - } -} diff --git a/polkadot/runtime/common/src/auctions/tests.rs b/polkadot/runtime/common/src/auctions/tests.rs index 07574eeb295d..26e2ac47df84 100644 --- a/polkadot/runtime/common/src/auctions/tests.rs +++ b/polkadot/runtime/common/src/auctions/tests.rs @@ -36,7 +36,7 @@ fn basic_setup_works() { AuctionStatus::::NotStarted ); - run_to_block(10); + System::run_to_block::(10); assert_eq!(AuctionCounter::::get(), 0); assert_eq!(TestLeaser::deposit_held(0u32.into(), &1), 0); @@ -50,7 +50,7 @@ fn basic_setup_works() { #[test] fn can_start_auction() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!(Auctions::new_auction(RuntimeOrigin::signed(1), 5, 1), BadOrigin); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); @@ -66,7 +66,7 @@ fn can_start_auction() { #[test] fn bidding_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); @@ -82,7 +82,7 @@ fn bidding_works() { #[test] fn under_bidding_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); @@ -96,7 +96,7 @@ fn under_bidding_works() { #[test] fn over_bidding_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 5)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 1, 4, 6)); @@ -115,7 +115,7 @@ fn over_bidding_works() { #[test] fn auction_proceeds_correctly() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); @@ -125,49 +125,49 @@ fn auction_proceeds_correctly() { AuctionStatus::::StartingPeriod ); - run_to_block(2); + System::run_to_block::(2); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(3); + System::run_to_block::(3); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(4); + System::run_to_block::(4); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(5); + System::run_to_block::(5); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(6); + System::run_to_block::(6); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0) ); - run_to_block(7); + System::run_to_block::(7); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0) ); - run_to_block(8); + System::run_to_block::(8); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0) ); - run_to_block(9); + System::run_to_block::(9); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::NotStarted @@ -178,12 +178,12 @@ fn auction_proceeds_correctly() { #[test] fn can_win_auction() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); assert_eq!(Balances::reserved_balance(1), 1); assert_eq!(Balances::free_balance(1), 9); - run_to_block(9); + System::run_to_block::(9); assert_eq!( leases(), @@ -201,7 +201,7 @@ fn can_win_auction() { #[test] fn can_win_auction_with_late_randomness() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); assert_eq!(Balances::reserved_balance(1), 1); @@ -210,7 +210,7 @@ fn can_win_auction_with_late_randomness() { Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(8); + System::run_to_block::(8); // Auction has not yet ended. assert_eq!(leases(), vec![]); assert_eq!( @@ -222,7 +222,7 @@ fn can_win_auction_with_late_randomness() { set_last_random(H256::zero(), 8); // Auction definitely ended now, but we don't know exactly when in the last 3 blocks yet // since no randomness available yet. - run_to_block(9); + System::run_to_block::(9); // Auction has now ended... But auction winner still not yet decided, so no leases yet. assert_eq!( Auctions::auction_status(System::block_number()), @@ -233,7 +233,7 @@ fn can_win_auction_with_late_randomness() { // Random seed now updated to a value known at block 9, when the auction ended. This // means that the winner can now be chosen. set_last_random(H256::zero(), 9); - run_to_block(10); + System::run_to_block::(10); // Auction ended and winner selected assert_eq!( Auctions::auction_status(System::block_number()), @@ -255,10 +255,10 @@ fn can_win_auction_with_late_randomness() { #[test] fn can_win_incomplete_auction() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 4, 4, 5)); - run_to_block(9); + System::run_to_block::(9); assert_eq!(leases(), vec![((0.into(), 4), LeaseData { leaser: 1, amount: 5 }),]); assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); @@ -268,13 +268,13 @@ fn can_win_incomplete_auction() { #[test] fn should_choose_best_combination() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(2), 0.into(), 1, 2, 3, 4)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), 0.into(), 1, 4, 4, 2)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 1, 1, 4, 2)); - run_to_block(9); + System::run_to_block::(9); assert_eq!( leases(), @@ -295,7 +295,7 @@ fn should_choose_best_combination() { #[test] fn gap_bid_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); // User 1 will make a bid for period 1 and 4 for the same Para 0 @@ -314,7 +314,7 @@ fn gap_bid_works() { assert_eq!(Balances::reserved_balance(3), 3); // End the auction. - run_to_block(9); + System::run_to_block::(9); assert_eq!( leases(), @@ -334,11 +334,11 @@ fn gap_bid_works() { #[test] fn deposit_credit_should_work() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); assert_eq!(Balances::reserved_balance(1), 5); - run_to_block(10); + System::run_to_block::(10); assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); @@ -347,7 +347,7 @@ fn deposit_credit_should_work() { assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 2, 2, 2, 6)); // Only 1 reserved since we have a deposit credit of 5. assert_eq!(Balances::reserved_balance(1), 1); - run_to_block(20); + System::run_to_block::(20); assert_eq!( leases(), @@ -363,11 +363,11 @@ fn deposit_credit_should_work() { #[test] fn deposit_credit_on_alt_para_should_not_count() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 1, 5)); assert_eq!(Balances::reserved_balance(1), 5); - run_to_block(10); + System::run_to_block::(10); assert_eq!(leases(), vec![((0.into(), 1), LeaseData { leaser: 1, amount: 5 }),]); assert_eq!(TestLeaser::deposit_held(0.into(), &1), 5); @@ -376,7 +376,7 @@ fn deposit_credit_on_alt_para_should_not_count() { assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 1.into(), 2, 2, 2, 6)); // 6 reserved since we are bidding on a new para; only works because we don't assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(20); + System::run_to_block::(20); assert_eq!( leases(), @@ -393,12 +393,12 @@ fn deposit_credit_on_alt_para_should_not_count() { #[test] fn multiple_bids_work_pre_ending() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); for i in 1..6u64 { - run_to_block(i as _); + System::run_to_block::(i as _); assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); for j in 1..6 { assert_eq!(Balances::reserved_balance(j), if j == i { j } else { 0 }); @@ -406,7 +406,7 @@ fn multiple_bids_work_pre_ending() { } } - run_to_block(9); + System::run_to_block::(9); assert_eq!( leases(), vec![ @@ -422,12 +422,12 @@ fn multiple_bids_work_pre_ending() { #[test] fn multiple_bids_work_post_ending() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 0, 1)); for i in 1..6u64 { - run_to_block(((i - 1) / 2 + 1) as _); + System::run_to_block::(((i - 1) / 2 + 1) as _); assert_ok!(Auctions::bid(RuntimeOrigin::signed(i), 0.into(), 1, 1, 4, i)); for j in 1..6 { assert_eq!(Balances::reserved_balance(j), if j <= i { j } else { 0 }); @@ -438,7 +438,7 @@ fn multiple_bids_work_post_ending() { assert_eq!(ReservedAmounts::::get((i, ParaId::from(0))).unwrap(), i); } - run_to_block(5); + System::run_to_block::(5); assert_eq!( leases(), (1..=4) @@ -501,7 +501,7 @@ fn calculate_winners_works() { #[test] fn lower_bids_are_correctly_refunded() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 1, 1)); let para_1 = ParaId::from(1_u32); let para_2 = ParaId::from(2_u32); @@ -527,7 +527,7 @@ fn initialize_winners_in_ending_period_works() { new_test_ext().execute_with(|| { let ed: u64 = ::ExistentialDeposit::get(); assert_eq!(ed, 1); - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 1)); let para_1 = ParaId::from(1_u32); let para_2 = ParaId::from(2_u32); @@ -546,20 +546,20 @@ fn initialize_winners_in_ending_period_works() { winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); assert_eq!(Winning::::get(0), Some(winning)); - run_to_block(9); + System::run_to_block::(9); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(10); + System::run_to_block::(10); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0) ); assert_eq!(Winning::::get(0), Some(winning)); - run_to_block(11); + System::run_to_block::(11); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0) @@ -567,7 +567,7 @@ fn initialize_winners_in_ending_period_works() { assert_eq!(Winning::::get(1), Some(winning)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 3, 4, 29)); - run_to_block(12); + System::run_to_block::(12); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0) @@ -580,7 +580,7 @@ fn initialize_winners_in_ending_period_works() { #[test] fn handle_bid_requires_registered_para() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_noop!( Auctions::bid(RuntimeOrigin::signed(1), 1337.into(), 1, 1, 4, 1), @@ -599,12 +599,12 @@ fn handle_bid_requires_registered_para() { #[test] fn handle_bid_checks_existing_lease_periods() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 2, 3, 1)); assert_eq!(Balances::reserved_balance(1), 1); assert_eq!(Balances::free_balance(1), 9); - run_to_block(9); + System::run_to_block::(9); assert_eq!( leases(), @@ -644,7 +644,7 @@ fn less_winning_samples_work() { EndingPeriod::set(30); SampleLength::set(10); - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); let para_1 = ParaId::from(1_u32); let para_2 = ParaId::from(2_u32); @@ -663,13 +663,13 @@ fn less_winning_samples_work() { winning[SlotRange::TwoThree as u8 as usize] = Some((2, para_2, 19)); assert_eq!(Winning::::get(0), Some(winning)); - run_to_block(9); + System::run_to_block::(9); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(10); + System::run_to_block::(10); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0) @@ -681,19 +681,19 @@ fn less_winning_samples_work() { winning[SlotRange::ThreeThree as u8 as usize] = Some((3, para_3, 29)); assert_eq!(Winning::::get(0), Some(winning)); - run_to_block(20); + System::run_to_block::(20); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0) ); assert_eq!(Winning::::get(1), Some(winning)); - run_to_block(25); + System::run_to_block::(25); // Overbid mid sample assert_ok!(Auctions::bid(RuntimeOrigin::signed(3), para_3, 1, 13, 14, 29)); winning[SlotRange::TwoThree as u8 as usize] = Some((3, para_3, 29)); assert_eq!(Winning::::get(1), Some(winning)); - run_to_block(30); + System::run_to_block::(30); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0) @@ -701,7 +701,7 @@ fn less_winning_samples_work() { assert_eq!(Winning::::get(2), Some(winning)); set_last_random(H256::from([254; 32]), 40); - run_to_block(40); + System::run_to_block::(40); // Auction ended and winner selected assert_eq!( Auctions::auction_status(System::block_number()), @@ -729,71 +729,71 @@ fn auction_status_works() { AuctionStatus::::NotStarted ); - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 9, 11)); - run_to_block(9); + System::run_to_block::(9); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::StartingPeriod ); - run_to_block(10); + System::run_to_block::(10); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 0) ); - run_to_block(11); + System::run_to_block::(11); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 1) ); - run_to_block(19); + System::run_to_block::(19); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(0, 9) ); - run_to_block(20); + System::run_to_block::(20); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 0) ); - run_to_block(25); + System::run_to_block::(25); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(1, 5) ); - run_to_block(30); + System::run_to_block::(30); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 0) ); - run_to_block(39); + System::run_to_block::(39); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::EndingPeriod(2, 9) ); - run_to_block(40); + System::run_to_block::(40); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::VrfDelay(0) ); - run_to_block(44); + System::run_to_block::(44); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::VrfDelay(4) ); set_last_random(dummy_hash(), 45); - run_to_block(45); + System::run_to_block::(45); assert_eq!( Auctions::auction_status(System::block_number()), AuctionStatus::::NotStarted @@ -804,7 +804,7 @@ fn auction_status_works() { #[test] fn can_cancel_auction() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Auctions::new_auction(RuntimeOrigin::signed(6), 5, 1)); assert_ok!(Auctions::bid(RuntimeOrigin::signed(1), 0.into(), 1, 1, 4, 1)); assert_eq!(Balances::reserved_balance(1), 1); diff --git a/polkadot/runtime/common/src/crowdloan/mod.rs b/polkadot/runtime/common/src/crowdloan/mod.rs index 8cf288197e3d..f8b3169407e8 100644 --- a/polkadot/runtime/common/src/crowdloan/mod.rs +++ b/polkadot/runtime/common/src/crowdloan/mod.rs @@ -858,10 +858,7 @@ mod crypto { mod tests { use super::*; - use frame_support::{ - assert_noop, assert_ok, derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, - }; + use frame_support::{assert_noop, assert_ok, derive_impl, parameter_types}; use polkadot_primitives::Id as ParaId; use sp_core::H256; use std::{cell::RefCell, collections::BTreeMap, sync::Arc}; @@ -1111,18 +1108,6 @@ mod tests { unreachable!() } - fn run_to_block(n: u64) { - while System::block_number() < n { - Crowdloan::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Crowdloan::on_initialize(System::block_number()); - } - } - fn last_event() -> RuntimeEvent { System::events().pop().expect("RuntimeEvent expected").event } @@ -1426,7 +1411,7 @@ mod tests { ); // Move past end date - run_to_block(10); + System::run_to_block::(10); // Cannot contribute to ended fund assert_noop!( @@ -1451,7 +1436,7 @@ mod tests { // crowdloan that has starting period 1. let para_3 = new_para(); assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para_3, 1000, 1, 4, 40, None)); - run_to_block(40); + System::run_to_block::(40); let now = System::block_number(); assert_eq!(TestAuctioneer::lease_period_index(now).unwrap().0, 2); assert_noop!( @@ -1483,12 +1468,12 @@ mod tests { None )); - run_to_block(8); + System::run_to_block::(8); // Can def contribute when auction is running. assert!(TestAuctioneer::auction_status(System::block_number()).is_ending().is_some()); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None)); - run_to_block(10); + System::run_to_block::(10); // Can't contribute when auction is in the VRF delay period. assert!(TestAuctioneer::auction_status(System::block_number()).is_vrf()); assert_noop!( @@ -1496,7 +1481,7 @@ mod tests { Error::::VrfDelayInProgress ); - run_to_block(15); + System::run_to_block::(15); // Its fine to contribute when no auction is running. assert!(!TestAuctioneer::auction_status(System::block_number()).is_in_progress()); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None)); @@ -1526,15 +1511,15 @@ mod tests { let bidder = Crowdloan::fund_account_id(index); // Fund crowdloan - run_to_block(1); + System::run_to_block::(1); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); - run_to_block(3); + System::run_to_block::(3); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 150, None)); - run_to_block(5); + System::run_to_block::(5); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(4), para, 200, None)); - run_to_block(8); + System::run_to_block::(8); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 250, None)); - run_to_block(10); + System::run_to_block::(10); assert_eq!( bids(), @@ -1561,7 +1546,7 @@ mod tests { assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None)); - run_to_block(10); + System::run_to_block::(10); let account_id = Crowdloan::fund_account_id(index); // para has no reserved funds, indicating it did not win the auction. assert_eq!(Balances::reserved_balance(&account_id), 0); @@ -1591,7 +1576,7 @@ mod tests { assert_ok!(Crowdloan::create(RuntimeOrigin::signed(1), para, 1000, 1, 1, 9, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); - run_to_block(10); + System::run_to_block::(10); let account_id = Crowdloan::fund_account_id(index); // user sends the crowdloan funds trying to make an accounting error @@ -1636,7 +1621,7 @@ mod tests { ); // Move to the end of the crowdloan - run_to_block(10); + System::run_to_block::(10); assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para)); // Funds are returned @@ -1671,7 +1656,7 @@ mod tests { assert_eq!(Balances::free_balance(account_id), 21000); // Move to the end of the crowdloan - run_to_block(10); + System::run_to_block::(10); assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(1337), para)); assert_eq!( last_event(), @@ -1705,7 +1690,7 @@ mod tests { assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None)); - run_to_block(10); + System::run_to_block::(10); // All funds are refunded assert_ok!(Crowdloan::refund(RuntimeOrigin::signed(2), para)); @@ -1730,7 +1715,7 @@ mod tests { assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para, 100, None)); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(3), para, 50, None)); - run_to_block(10); + System::run_to_block::(10); // We test the historic case where crowdloan accounts only have one provider: { @@ -1770,7 +1755,7 @@ mod tests { Error::::NotReadyToDissolve ); - run_to_block(10); + System::run_to_block::(10); set_winner(para, 1, true); // Can't dissolve when it won. assert_noop!( @@ -1815,13 +1800,13 @@ mod tests { // simulate the reserving of para's funds. this actually happens in the Slots pallet. assert_ok!(Balances::reserve(&account_id, 149)); - run_to_block(19); + System::run_to_block::(19); assert_noop!( Crowdloan::withdraw(RuntimeOrigin::signed(2), 2, para), Error::::BidOrLeaseActive ); - run_to_block(20); + System::run_to_block::(20); // simulate the unreserving of para's funds, now that the lease expired. this actually // happens in the Slots pallet. Balances::unreserve(&account_id, 150); @@ -1949,7 +1934,7 @@ mod tests { Error::::NoContributions ); assert_ok!(Crowdloan::contribute(RuntimeOrigin::signed(2), para_1, 100, None)); - run_to_block(6); + System::run_to_block::(6); assert_ok!(Crowdloan::poke(RuntimeOrigin::signed(1), para_1)); assert_eq!(crowdloan::NewRaise::::get(), vec![para_1]); assert_noop!( diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index 8a76a138305e..bb4ad8b75065 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -28,7 +28,7 @@ use alloc::sync::Arc; use codec::Encode; use frame_support::{ assert_noop, assert_ok, derive_impl, parameter_types, - traits::{ConstU32, Currency, OnFinalize, OnInitialize}, + traits::{ConstU32, Currency}, weights::Weight, PalletId, }; @@ -377,14 +377,12 @@ fn add_blocks(n: u32) { } fn run_to_block(n: u32) { - assert!(System::block_number() < n); - while System::block_number() < n { - let block_number = System::block_number(); - AllPalletsWithSystem::on_finalize(block_number); - System::set_block_number(block_number + 1); - maybe_new_session(block_number + 1); - AllPalletsWithSystem::on_initialize(block_number + 1); - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().before_initialize(|bn| { + maybe_new_session(bn); + }), + ); } fn run_to_session(n: u32) { diff --git a/polkadot/runtime/common/src/paras_registrar/mock.rs b/polkadot/runtime/common/src/paras_registrar/mock.rs index 1627fd70365d..07b8fbca5189 100644 --- a/polkadot/runtime/common/src/paras_registrar/mock.rs +++ b/polkadot/runtime/common/src/paras_registrar/mock.rs @@ -20,10 +20,7 @@ use super::*; use crate::paras_registrar; use alloc::collections::btree_map::BTreeMap; -use frame_support::{ - derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, -}; +use frame_support::{derive_impl, parameter_types}; use frame_system::limits; use polkadot_primitives::{Balance, BlockNumber, MAX_CODE_SIZE}; use polkadot_runtime_parachains::{configuration, origin, shared}; @@ -205,26 +202,21 @@ pub const VALIDATORS: &[Sr25519Keyring] = &[ pub fn run_to_block(n: BlockNumber) { // NOTE that this function only simulates modules of interest. Depending on new pallet may // require adding it here. - assert!(System::block_number() < n); - while System::block_number() < n { - let b = System::block_number(); - - if System::block_number() > 1 { - System::on_finalize(System::block_number()); - } - // Session change every 3 blocks. - if (b + 1) % BLOCKS_PER_SESSION == 0 { - let session_index = shared::CurrentSessionIndex::::get() + 1; - let validators_pub_keys = VALIDATORS.iter().map(|v| v.public().into()).collect(); - - shared::Pallet::::set_session_index(session_index); - shared::Pallet::::set_active_validators_ascending(validators_pub_keys); - - Parachains::test_on_new_session(); - } - System::set_block_number(b + 1); - System::on_initialize(System::block_number()); - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().before_finalize(|bn| { + // Session change every 3 blocks. + if (bn + 1) % BLOCKS_PER_SESSION == 0 { + let session_index = shared::CurrentSessionIndex::::get() + 1; + let validators_pub_keys = VALIDATORS.iter().map(|v| v.public().into()).collect(); + + shared::Pallet::::set_session_index(session_index); + shared::Pallet::::set_active_validators_ascending(validators_pub_keys); + + Parachains::test_on_new_session(); + } + }), + ); } pub fn run_to_session(n: BlockNumber) { diff --git a/polkadot/runtime/common/src/slots/mod.rs b/polkadot/runtime/common/src/slots/mod.rs index 333f14c6608a..59a1f1870b2d 100644 --- a/polkadot/runtime/common/src/slots/mod.rs +++ b/polkadot/runtime/common/src/slots/mod.rs @@ -584,28 +584,16 @@ mod tests { t.into() } - fn run_to_block(n: BlockNumber) { - while System::block_number() < n { - Slots::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Slots::on_initialize(System::block_number()); - } - } - #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_eq!(Slots::lease_period_length(), (10, 0)); let now = System::block_number(); assert_eq!(Slots::lease_period_index(now).unwrap().0, 0); assert_eq!(Slots::deposit_held(1.into(), &1), 0); - run_to_block(10); + System::run_to_block::(10); let now = System::block_number(); assert_eq!(Slots::lease_period_index(now).unwrap().0, 1); }); @@ -614,7 +602,7 @@ mod tests { #[test] fn lease_lifecycle_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -627,11 +615,11 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &1), 1); assert_eq!(Balances::reserved_balance(1), 1); - run_to_block(19); + System::run_to_block::(19); assert_eq!(Slots::deposit_held(1.into(), &1), 1); assert_eq!(Balances::reserved_balance(1), 1); - run_to_block(20); + System::run_to_block::(20); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -645,7 +633,7 @@ mod tests { #[test] fn lease_interrupted_lifecycle_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -657,19 +645,19 @@ mod tests { assert_ok!(Slots::lease_out(1.into(), &1, 6, 1, 1)); assert_ok!(Slots::lease_out(1.into(), &1, 4, 3, 1)); - run_to_block(19); + System::run_to_block::(19); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(20); + System::run_to_block::(20); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(39); + System::run_to_block::(39); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(40); + System::run_to_block::(40); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -688,7 +676,7 @@ mod tests { #[test] fn lease_relayed_lifecycle_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -704,25 +692,25 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(19); + System::run_to_block::(19); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(20); + System::run_to_block::(20); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(29); + System::run_to_block::(29); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Slots::deposit_held(1.into(), &2), 4); assert_eq!(Balances::reserved_balance(2), 4); - run_to_block(30); + System::run_to_block::(30); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); assert_eq!(Slots::deposit_held(1.into(), &2), 0); @@ -738,7 +726,7 @@ mod tests { #[test] fn lease_deposit_increase_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -755,11 +743,11 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(29); + System::run_to_block::(29); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(30); + System::run_to_block::(30); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -773,7 +761,7 @@ mod tests { #[test] fn lease_deposit_decrease_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -790,19 +778,19 @@ mod tests { assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(19); + System::run_to_block::(19); assert_eq!(Slots::deposit_held(1.into(), &1), 6); assert_eq!(Balances::reserved_balance(1), 6); - run_to_block(20); + System::run_to_block::(20); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(29); + System::run_to_block::(29); assert_eq!(Slots::deposit_held(1.into(), &1), 4); assert_eq!(Balances::reserved_balance(1), 4); - run_to_block(30); + System::run_to_block::(30); assert_eq!(Slots::deposit_held(1.into(), &1), 0); assert_eq!(Balances::reserved_balance(1), 0); @@ -816,7 +804,7 @@ mod tests { #[test] fn clear_all_leases_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -852,7 +840,7 @@ mod tests { #[test] fn lease_out_current_lease_period() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, @@ -867,7 +855,7 @@ mod tests { dummy_validation_code() )); - run_to_block(20); + System::run_to_block::(20); let now = System::block_number(); assert_eq!(Slots::lease_period_index(now).unwrap().0, 2); // Can't lease from the past @@ -884,7 +872,7 @@ mod tests { #[test] fn trigger_onboard_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(TestRegistrar::::register( 1, ParaId::from(1_u32), diff --git a/prdoc/pr_7109.prdoc b/prdoc/pr_7109.prdoc new file mode 100644 index 000000000000..e54ef3295135 --- /dev/null +++ b/prdoc/pr_7109.prdoc @@ -0,0 +1,11 @@ +title: Add "run to block" tools +doc: +- audience: Runtime Dev + description: |- + Introduce `frame_system::Pallet::run_to_block`, `frame_system::Pallet::run_to_block_with`, and `frame_system::RunToBlockHooks` to establish a generic `run_to_block` mechanism for mock tests, minimizing redundant implementations across various pallets. + + Closes #299. + +crates: +- name: frame-system + bump: minor diff --git a/substrate/frame/examples/multi-block-migrations/src/mock.rs b/substrate/frame/examples/multi-block-migrations/src/mock.rs index b2a946e1c505..64940db080c4 100644 --- a/substrate/frame/examples/multi-block-migrations/src/mock.rs +++ b/substrate/frame/examples/multi-block-migrations/src/mock.rs @@ -25,10 +25,7 @@ //! using the [`Migrations`] type. use frame_support::{ - construct_runtime, derive_impl, - migrations::MultiStepMigrator, - pallet_prelude::Weight, - traits::{OnFinalize, OnInitialize}, + construct_runtime, derive_impl, migrations::MultiStepMigrator, pallet_prelude::Weight, }; type Block = frame_system::mocking::MockBlock; @@ -81,13 +78,11 @@ pub fn new_test_ext() -> sp_io::TestExternalities { #[allow(dead_code)] pub fn run_to_block(n: u64) { - assert!(System::block_number() < n); - while System::block_number() < n { - let b = System::block_number(); - AllPalletsWithSystem::on_finalize(b); - // Done by Executive: - ::MultiBlockMigrator::step(); - System::set_block_number(b + 1); - AllPalletsWithSystem::on_initialize(b + 1); - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().after_initialize(|_| { + // Done by Executive: + ::MultiBlockMigrator::step(); + }), + ); } diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index 757052e230a1..f044fc610187 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -266,22 +266,19 @@ impl ExtBuilder { } pub(crate) fn run_to_block(n: u64, on_idle: bool) { - let current_block = System::block_number(); - assert!(n > current_block); - while System::block_number() < n { - Balances::on_finalize(System::block_number()); - Staking::on_finalize(System::block_number()); - FastUnstake::on_finalize(System::block_number()); - - System::set_block_number(System::block_number() + 1); - - Balances::on_initialize(System::block_number()); - Staking::on_initialize(System::block_number()); - FastUnstake::on_initialize(System::block_number()); - if on_idle { - FastUnstake::on_idle(System::block_number(), BlockWeights::get().max_block); - } - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default() + .before_finalize(|_| { + // Satisfy the timestamp pallet. + Timestamp::set_timestamp(0); + }) + .after_initialize(|bn| { + if on_idle { + FastUnstake::on_idle(bn, BlockWeights::get().max_block); + } + }), + ); } pub(crate) fn next_block(on_idle: bool) { diff --git a/substrate/frame/identity/src/tests.rs b/substrate/frame/identity/src/tests.rs index 7bf5b2a72760..01bc312723aa 100644 --- a/substrate/frame/identity/src/tests.rs +++ b/substrate/frame/identity/src/tests.rs @@ -26,7 +26,7 @@ use crate::{ use codec::{Decode, Encode}; use frame_support::{ assert_err, assert_noop, assert_ok, derive_impl, parameter_types, - traits::{ConstU32, ConstU64, Get, OnFinalize, OnInitialize}, + traits::{ConstU32, ConstU64, Get}, BoundedVec, }; use frame_system::EnsureRoot; @@ -114,18 +114,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ext } -fn run_to_block(n: u64) { - while System::block_number() < n { - Identity::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Identity::on_initialize(System::block_number()); - } -} - fn account(id: u8) -> AccountIdOf { [id; 32].into() } @@ -1714,7 +1702,7 @@ fn unaccepted_usernames_through_grant_should_expire() { Some((who.clone(), expiration, Provider::Allocation)) ); - run_to_block(now + expiration - 1); + System::run_to_block::(now + expiration - 1); // Cannot be removed assert_noop!( @@ -1722,7 +1710,7 @@ fn unaccepted_usernames_through_grant_should_expire() { Error::::NotExpired ); - run_to_block(now + expiration); + System::run_to_block::(now + expiration); // Anyone can remove assert_ok!(Identity::remove_expired_approval( @@ -1782,7 +1770,7 @@ fn unaccepted_usernames_through_deposit_should_expire() { Some((who.clone(), expiration, Provider::AuthorityDeposit(username_deposit))) ); - run_to_block(now + expiration - 1); + System::run_to_block::(now + expiration - 1); // Cannot be removed assert_noop!( @@ -1790,7 +1778,7 @@ fn unaccepted_usernames_through_deposit_should_expire() { Error::::NotExpired ); - run_to_block(now + expiration); + System::run_to_block::(now + expiration); // Anyone can remove assert_eq!( diff --git a/substrate/frame/lottery/src/mock.rs b/substrate/frame/lottery/src/mock.rs index d2c442e2ac6e..b771ed0849f6 100644 --- a/substrate/frame/lottery/src/mock.rs +++ b/substrate/frame/lottery/src/mock.rs @@ -20,10 +20,7 @@ use super::*; use crate as pallet_lottery; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstU32, OnFinalize, OnInitialize}, -}; +use frame_support::{derive_impl, parameter_types, traits::ConstU32}; use frame_support_test::TestRandomness; use frame_system::EnsureRoot; use sp_runtime::{BuildStorage, Perbill}; @@ -83,16 +80,3 @@ pub fn new_test_ext() -> sp_io::TestExternalities { .unwrap(); t.into() } - -/// Run until a particular block. -pub fn run_to_block(n: u64) { - while System::block_number() < n { - if System::block_number() > 1 { - Lottery::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - } - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Lottery::on_initialize(System::block_number()); - } -} diff --git a/substrate/frame/lottery/src/tests.rs b/substrate/frame/lottery/src/tests.rs index ae3a6c858f24..119be5df4925 100644 --- a/substrate/frame/lottery/src/tests.rs +++ b/substrate/frame/lottery/src/tests.rs @@ -17,12 +17,11 @@ //! Tests for the module. -use super::*; -use frame_support::{assert_noop, assert_ok, assert_storage_noop}; -use mock::{ - new_test_ext, run_to_block, Balances, BalancesCall, Lottery, RuntimeCall, RuntimeOrigin, - SystemCall, Test, +use crate::{ + mock::{Lottery, *}, + *, }; +use frame_support::{assert_noop, assert_ok, assert_storage_noop}; use sp_runtime::{traits::BadOrigin, TokenError}; #[test] @@ -74,13 +73,13 @@ fn basic_end_to_end_works() { assert_eq!(TicketsCount::::get(), 4); // Go to end - run_to_block(20); + System::run_to_block::(20); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(5), call.clone())); // Ticket isn't bought assert_eq!(TicketsCount::::get(), 4); // Go to payout - run_to_block(25); + System::run_to_block::(25); // User 1 wins assert_eq!(Balances::free_balance(&1), 70 + 40); // Lottery is reset and restarted @@ -115,11 +114,11 @@ fn stop_repeat_works() { // Lottery still exists. assert!(crate::Lottery::::get().is_some()); // End and pick a winner. - run_to_block(length + delay); + System::run_to_block::(length + delay); // Lottery stays dead and does not repeat. assert!(crate::Lottery::::get().is_none()); - run_to_block(length + delay + 1); + System::run_to_block::(length + delay + 1); assert!(crate::Lottery::::get().is_none()); }); } @@ -281,7 +280,7 @@ fn buy_ticket_works() { assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 20, 5, false)); // Go to start, buy ticket for transfer - run_to_block(5); + System::run_to_block::(5); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call)); assert_eq!(TicketsCount::::get(), 1); @@ -300,12 +299,12 @@ fn buy_ticket_works() { assert_eq!(TicketsCount::::get(), 2); // Go to end, can't buy tickets anymore - run_to_block(20); + System::run_to_block::(20); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), call.clone())); assert_eq!(TicketsCount::::get(), 2); // Go to payout, can't buy tickets when there is no lottery open - run_to_block(25); + System::run_to_block::(25); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), call.clone())); assert_eq!(TicketsCount::::get(), 0); assert_eq!(LotteryIndex::::get(), 1); @@ -409,7 +408,7 @@ fn no_participants_works() { assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 10, length, delay, false)); // End the lottery, no one wins. - run_to_block(length + delay); + System::run_to_block::(length + delay); }); } diff --git a/substrate/frame/migrations/src/mock.rs b/substrate/frame/migrations/src/mock.rs index 48ff175f8137..ea86899cad83 100644 --- a/substrate/frame/migrations/src/mock.rs +++ b/substrate/frame/migrations/src/mock.rs @@ -21,12 +21,7 @@ use crate::{mock_helpers::*, Event, Historic}; -use frame_support::{ - derive_impl, - migrations::*, - traits::{OnFinalize, OnInitialize}, - weights::Weight, -}; +use frame_support::{derive_impl, migrations::*, weights::Weight}; use frame_system::EventRecord; use sp_core::H256; @@ -113,18 +108,18 @@ pub fn test_closure(f: impl FnOnce() -> R) -> R { ext.execute_with(f) } -pub fn run_to_block(n: u32) { - while System::block_number() < n as u64 { - log::debug!("Block {}", System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Migrations::on_initialize(System::block_number()); - // Executive calls this: - ::step(); - - Migrations::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - } +pub fn run_to_block(n: u64) { + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default() + .before_initialize(|bn| { + log::debug!("Block {bn}"); + }) + .after_initialize(|_| { + // Executive calls this: + ::step(); + }), + ); } /// Returns the historic migrations, sorted by their identifier. diff --git a/substrate/frame/nis/src/mock.rs b/substrate/frame/nis/src/mock.rs index 2b008f8ec2a4..08e69ef0de05 100644 --- a/substrate/frame/nis/src/mock.rs +++ b/substrate/frame/nis/src/mock.rs @@ -21,7 +21,7 @@ use crate::{self as pallet_nis, Perquintill, WithMaximumOf}; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{fungible::Inspect, ConstU32, ConstU64, OnFinalize, OnInitialize, StorageMapShim}, + traits::{fungible::Inspect, ConstU32, ConstU64, StorageMapShim}, weights::Weight, PalletId, }; @@ -145,15 +145,3 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pub fn new_test_ext_empty() -> sp_io::TestExternalities { frame_system::GenesisConfig::::default().build_storage().unwrap().into() } - -pub fn run_to_block(n: u64) { - while System::block_number() < n { - Nis::on_finalize(System::block_number()); - Balances::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Balances::on_initialize(System::block_number()); - Nis::on_initialize(System::block_number()); - } -} diff --git a/substrate/frame/nis/src/tests.rs b/substrate/frame/nis/src/tests.rs index a17aaf421827..10c39a0d48ed 100644 --- a/substrate/frame/nis/src/tests.rs +++ b/substrate/frame/nis/src/tests.rs @@ -55,7 +55,7 @@ fn enlarge(amount: Balance, max_bids: u32) { #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); for q in 0..3 { assert!(Queues::::get(q).is_empty()); @@ -76,7 +76,7 @@ fn basic_setup_works() { #[test] fn place_bid_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!(Nis::place_bid(signed(1), 1, 2), Error::::AmountTooSmall); assert_noop!(Nis::place_bid(signed(1), 101, 2), FundsUnavailable); assert_noop!(Nis::place_bid(signed(1), 10, 4), Error::::DurationTooBig); @@ -90,7 +90,7 @@ fn place_bid_works() { #[test] fn place_bid_queuing_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 20, 2)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(1), 5, 2)); @@ -116,7 +116,7 @@ fn place_bid_queuing_works() { #[test] fn place_bid_fails_when_queue_full() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(2), 10, 2)); assert_ok!(Nis::place_bid(signed(3), 10, 2)); @@ -128,7 +128,7 @@ fn place_bid_fails_when_queue_full() { #[test] fn multiple_place_bids_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); @@ -154,7 +154,7 @@ fn multiple_place_bids_works() { #[test] fn retract_single_item_queue_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::retract_bid(signed(1), 10, 1)); @@ -169,7 +169,7 @@ fn retract_single_item_queue_works() { #[test] fn retract_with_other_and_duplicate_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_ok!(Nis::place_bid(signed(1), 10, 2)); @@ -190,7 +190,7 @@ fn retract_with_other_and_duplicate_works() { #[test] fn retract_non_existent_item_fails() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_noop!(Nis::retract_bid(signed(1), 10, 1), Error::::UnknownBid); assert_ok!(Nis::place_bid(signed(1), 10, 1)); assert_noop!(Nis::retract_bid(signed(1), 20, 1), Error::::UnknownBid); @@ -202,7 +202,7 @@ fn retract_non_existent_item_fails() { #[test] fn basic_enlarge_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(2), 40, 2)); enlarge(40, 2); @@ -240,7 +240,7 @@ fn basic_enlarge_works() { #[test] fn enlarge_respects_bids_limit() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(2), 40, 2)); assert_ok!(Nis::place_bid(signed(3), 40, 2)); @@ -285,7 +285,7 @@ fn enlarge_respects_bids_limit() { #[test] fn enlarge_respects_amount_limit_and_will_split() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 80, 1)); enlarge(40, 2); @@ -317,7 +317,7 @@ fn enlarge_respects_amount_limit_and_will_split() { #[test] fn basic_thaw_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_eq!(Nis::issuance().effective, 400); assert_eq!(Balances::free_balance(1), 60); @@ -330,9 +330,9 @@ fn basic_thaw_works() { assert_eq!(Balances::reserved_balance(1), 40); assert_eq!(holdings(), 40); - run_to_block(3); + System::run_to_block::(3); assert_noop!(Nis::thaw_private(signed(1), 0, None), Error::::NotExpired); - run_to_block(4); + System::run_to_block::(4); assert_noop!(Nis::thaw_private(signed(1), 1, None), Error::::UnknownReceipt); assert_noop!(Nis::thaw_private(signed(2), 0, None), Error::::NotOwner); @@ -359,12 +359,12 @@ fn basic_thaw_works() { #[test] fn partial_thaw_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 80, 1)); enlarge(80, 1); assert_eq!(holdings(), 80); - run_to_block(4); + System::run_to_block::(4); let prop = Perquintill::from_rational(4_100_000, 21_000_000u64); assert_noop!(Nis::thaw_private(signed(1), 0, Some(prop)), Error::::MakesDust); let prop = Perquintill::from_rational(1_050_000, 21_000_000u64); @@ -402,10 +402,10 @@ fn partial_thaw_works() { #[test] fn thaw_respects_transfers() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); enlarge(40, 1); - run_to_block(4); + System::run_to_block::(4); assert_eq!(Nis::owner(&0), Some(1)); assert_eq!(Balances::reserved_balance(&1), 40); @@ -428,10 +428,10 @@ fn thaw_respects_transfers() { #[test] fn communify_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); enlarge(40, 1); - run_to_block(4); + System::run_to_block::(4); assert_eq!(Nis::owner(&0), Some(1)); assert_eq!(Balances::reserved_balance(&1), 40); @@ -479,10 +479,10 @@ fn communify_works() { #[test] fn privatize_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); enlarge(40, 1); - run_to_block(4); + System::run_to_block::(4); assert_noop!(Nis::privatize(signed(2), 0), Error::::AlreadyPrivate); assert_ok!(Nis::communify(signed(1), 0)); @@ -503,11 +503,11 @@ fn privatize_works() { #[test] fn privatize_and_thaw_with_another_receipt_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(2), 40, 1)); enlarge(80, 2); - run_to_block(4); + System::run_to_block::(4); assert_ok!(Nis::communify(signed(1), 0)); assert_ok!(Nis::communify(signed(2), 1)); @@ -535,7 +535,7 @@ fn privatize_and_thaw_with_another_receipt_works() { #[test] fn communal_thaw_when_issuance_higher_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); @@ -552,7 +552,7 @@ fn communal_thaw_when_issuance_higher_works() { assert_ok!(Balances::mint_into(&3, 50)); assert_ok!(Balances::mint_into(&4, 50)); - run_to_block(4); + System::run_to_block::(4); // Unfunded initially... assert_noop!(Nis::thaw_communal(signed(1), 0), Error::::Unfunded); @@ -581,7 +581,7 @@ fn communal_thaw_when_issuance_higher_works() { #[test] fn private_thaw_when_issuance_higher_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); @@ -591,7 +591,7 @@ fn private_thaw_when_issuance_higher_works() { assert_ok!(Balances::mint_into(&3, 50)); assert_ok!(Balances::mint_into(&4, 50)); - run_to_block(4); + System::run_to_block::(4); // Unfunded initially... assert_noop!(Nis::thaw_private(signed(1), 0, None), Error::::Unfunded); @@ -609,7 +609,7 @@ fn private_thaw_when_issuance_higher_works() { #[test] fn thaw_with_ignored_issuance_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); // Give account zero some balance. assert_ok!(Balances::mint_into(&0, 200)); @@ -622,7 +622,7 @@ fn thaw_with_ignored_issuance_works() { assert_ok!(Balances::transfer_allow_death(signed(0), 3, 50)); assert_ok!(Balances::transfer_allow_death(signed(0), 4, 50)); - run_to_block(4); + System::run_to_block::(4); // Unfunded initially... assert_noop!(Nis::thaw_private(signed(1), 0, None), Error::::Unfunded); // ...so we fund... @@ -640,7 +640,7 @@ fn thaw_with_ignored_issuance_works() { #[test] fn thaw_when_issuance_lower_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); @@ -650,7 +650,7 @@ fn thaw_when_issuance_lower_works() { assert_ok!(Balances::burn_from(&3, 25, Expendable, Exact, Force)); assert_ok!(Balances::burn_from(&4, 25, Expendable, Exact, Force)); - run_to_block(4); + System::run_to_block::(4); assert_ok!(Nis::thaw_private(signed(1), 0, None)); assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1)); @@ -662,7 +662,7 @@ fn thaw_when_issuance_lower_works() { #[test] fn multiple_thaws_works() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(1), 60, 1)); @@ -675,11 +675,11 @@ fn multiple_thaws_works() { assert_ok!(Balances::mint_into(&4, 100)); assert_ok!(Nis::fund_deficit(signed(1))); - run_to_block(4); + System::run_to_block::(4); assert_ok!(Nis::thaw_private(signed(1), 0, None)); assert_ok!(Nis::thaw_private(signed(1), 1, None)); assert_noop!(Nis::thaw_private(signed(2), 2, None), Error::::Throttled); - run_to_block(5); + System::run_to_block::(5); assert_ok!(Nis::thaw_private(signed(2), 2, None)); assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1)); @@ -693,7 +693,7 @@ fn multiple_thaws_works() { #[test] fn multiple_thaws_works_in_alternative_thaw_order() { new_test_ext().execute_with(|| { - run_to_block(1); + System::run_to_block::(1); assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(1), 60, 1)); @@ -706,12 +706,12 @@ fn multiple_thaws_works_in_alternative_thaw_order() { assert_ok!(Balances::mint_into(&4, 100)); assert_ok!(Nis::fund_deficit(signed(1))); - run_to_block(4); + System::run_to_block::(4); assert_ok!(Nis::thaw_private(signed(2), 2, None)); assert_noop!(Nis::thaw_private(signed(1), 1, None), Error::::Throttled); assert_ok!(Nis::thaw_private(signed(1), 0, None)); - run_to_block(5); + System::run_to_block::(5); assert_ok!(Nis::thaw_private(signed(1), 1, None)); assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1)); @@ -725,7 +725,7 @@ fn multiple_thaws_works_in_alternative_thaw_order() { #[test] fn enlargement_to_target_works() { new_test_ext().execute_with(|| { - run_to_block(2); + System::run_to_block::(2); let w = <() as WeightInfo>::process_queues() + <() as WeightInfo>::process_queue() + (<() as WeightInfo>::process_bid() * 2); @@ -737,7 +737,7 @@ fn enlargement_to_target_works() { assert_ok!(Nis::place_bid(signed(3), 40, 3)); Target::set(Perquintill::from_percent(40)); - run_to_block(3); + System::run_to_block::(3); assert_eq!(Queues::::get(1), vec![Bid { amount: 40, who: 1 },]); assert_eq!( Queues::::get(2), @@ -749,7 +749,7 @@ fn enlargement_to_target_works() { ); assert_eq!(QueueTotals::::get(), vec![(1, 40), (2, 80), (2, 80)]); - run_to_block(4); + System::run_to_block::(4); // Two new items should have been issued to 2 & 3 for 40 each & duration of 3. assert_eq!( Receipts::::get(0).unwrap(), @@ -778,7 +778,7 @@ fn enlargement_to_target_works() { } ); - run_to_block(5); + System::run_to_block::(5); // No change assert_eq!( Summary::::get(), @@ -791,7 +791,7 @@ fn enlargement_to_target_works() { } ); - run_to_block(6); + System::run_to_block::(6); // Two new items should have been issued to 1 & 2 for 40 each & duration of 2. assert_eq!( Receipts::::get(2).unwrap(), @@ -820,7 +820,7 @@ fn enlargement_to_target_works() { } ); - run_to_block(8); + System::run_to_block::(8); // No change now. assert_eq!( Summary::::get(), @@ -835,7 +835,7 @@ fn enlargement_to_target_works() { // Set target a bit higher to use up the remaining bid. Target::set(Perquintill::from_percent(60)); - run_to_block(10); + System::run_to_block::(10); // One new item should have been issued to 1 for 40 each & duration of 2. assert_eq!( diff --git a/substrate/frame/nomination-pools/src/mock.rs b/substrate/frame/nomination-pools/src/mock.rs index cc942039760c..f544e79ec481 100644 --- a/substrate/frame/nomination-pools/src/mock.rs +++ b/substrate/frame/nomination-pools/src/mock.rs @@ -435,18 +435,7 @@ parameter_types! { /// Helper to run a specified amount of blocks. pub fn run_blocks(n: u64) { let current_block = System::block_number(); - run_to_block(n + current_block); -} - -/// Helper to run to a specific block. -pub fn run_to_block(n: u64) { - let current_block = System::block_number(); - assert!(n > current_block); - while System::block_number() < n { - Pools::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - Pools::on_initialize(System::block_number()); - } + System::run_to_block::(n + current_block); } /// All events of this pallet. diff --git a/substrate/frame/recovery/src/mock.rs b/substrate/frame/recovery/src/mock.rs index 3930db82d6c7..86f13b0da4f7 100644 --- a/substrate/frame/recovery/src/mock.rs +++ b/substrate/frame/recovery/src/mock.rs @@ -20,10 +20,7 @@ use super::*; use crate as recovery; -use frame_support::{ - derive_impl, parameter_types, - traits::{OnFinalize, OnInitialize}, -}; +use frame_support::{derive_impl, parameter_types}; use sp_runtime::BuildStorage; type Block = frame_system::mocking::MockBlock; @@ -86,14 +83,3 @@ pub fn new_test_ext() -> sp_io::TestExternalities { .unwrap(); t.into() } - -/// Run until a particular block. -pub fn run_to_block(n: u64) { - while System::block_number() < n { - if System::block_number() > 1 { - System::on_finalize(System::block_number()); - } - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - } -} diff --git a/substrate/frame/recovery/src/tests.rs b/substrate/frame/recovery/src/tests.rs index 93df07015852..97085df2ae78 100644 --- a/substrate/frame/recovery/src/tests.rs +++ b/substrate/frame/recovery/src/tests.rs @@ -17,12 +17,8 @@ //! Tests for the module. -use super::*; +use crate::{mock::*, *}; use frame_support::{assert_noop, assert_ok, traits::Currency}; -use mock::{ - new_test_ext, run_to_block, Balances, BalancesCall, MaxFriends, Recovery, RecoveryCall, - RuntimeCall, RuntimeOrigin, Test, -}; use sp_runtime::{bounded_vec, traits::BadOrigin}; #[test] @@ -70,7 +66,7 @@ fn recovery_life_cycle_works() { delay_period )); // Some time has passed, and the user lost their keys! - run_to_block(10); + System::run_to_block::(10); // Using account 1, the user begins the recovery process to recover the lost account assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Off chain, the user contacts their friends and asks them to vouch for the recovery @@ -84,7 +80,7 @@ fn recovery_life_cycle_works() { Error::::DelayPeriod ); // We need to wait at least the delay_period number of blocks before we can recover - run_to_block(20); + System::run_to_block::(20); assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(1), 5)); // Account 1 can use account 5 to close the active recovery process, claiming the deposited // funds used to initiate the recovery process into account 5. @@ -128,7 +124,7 @@ fn malicious_recovery_fails() { delay_period )); // Some time has passed, and account 1 wants to try and attack this account! - run_to_block(10); + System::run_to_block::(10); // Using account 1, the malicious user begins the recovery process on account 5 assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5)); // Off chain, the user **tricks** their friends and asks them to vouch for the recovery @@ -144,7 +140,7 @@ fn malicious_recovery_fails() { Error::::DelayPeriod ); // Account 1 needs to wait... - run_to_block(19); + System::run_to_block::(19); // One more block to wait! assert_noop!( Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), @@ -158,7 +154,7 @@ fn malicious_recovery_fails() { // Thanks for the free money! assert_eq!(Balances::total_balance(&5), 110); // The recovery process has been closed, so account 1 can't make the claim - run_to_block(20); + System::run_to_block::(20); assert_noop!( Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), Error::::NotStarted @@ -397,7 +393,7 @@ fn claim_recovery_handles_basic_errors() { Recovery::claim_recovery(RuntimeOrigin::signed(1), 5), Error::::DelayPeriod ); - run_to_block(11); + System::run_to_block::(11); // Cannot claim an account which has not passed the threshold number of votes assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1)); assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1)); @@ -427,7 +423,7 @@ fn claim_recovery_works() { assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1)); assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 1)); - run_to_block(11); + System::run_to_block::(11); // Account can be recovered. assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(1), 5)); @@ -439,7 +435,7 @@ fn claim_recovery_works() { assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 4)); assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 4)); - run_to_block(21); + System::run_to_block::(21); // Account is re-recovered. assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(4), 5)); diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index a27fb36f64a6..7a96b8eade4e 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -25,7 +25,7 @@ use frame_election_provider_support::{ }; use frame_support::{ derive_impl, parameter_types, - traits::{ConstU32, ConstU64, Hooks, OneSessionHandler}, + traits::{ConstU32, ConstU64, OneSessionHandler}, }; use pallet_staking::StakerStatus; use sp_runtime::{curve::PiecewiseLinear, testing::UintAuthorityId, traits::Zero, BuildStorage}; @@ -283,16 +283,12 @@ pub(crate) fn start_session(session_index: SessionIndex) { /// a block import/propose process where we first initialize the block, then execute some stuff (not /// in the function), and then finalize the block. pub(crate) fn run_to_block(n: BlockNumber) { - Staking::on_finalize(System::block_number()); - for b in (System::block_number() + 1)..=n { - System::set_block_number(b); - Session::on_initialize(b); - >::on_initialize(b); - Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); - if b != n { - Staking::on_finalize(System::block_number()); - } - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + Timestamp::set_timestamp(bn * BLOCK_TIME + INIT_TIMESTAMP); + }), + ); } pub(crate) fn active_era() -> EraIndex { diff --git a/substrate/frame/scheduler/src/mock.rs b/substrate/frame/scheduler/src/mock.rs index 8d36ca1c42e3..43a964bcf149 100644 --- a/substrate/frame/scheduler/src/mock.rs +++ b/substrate/frame/scheduler/src/mock.rs @@ -22,7 +22,7 @@ use super::*; use crate as scheduler; use frame_support::{ derive_impl, ord_parameter_types, parameter_types, - traits::{ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize}, + traits::{ConstU32, Contains, EitherOfDiverse, EqualPrivilegeOnly}, }; use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_runtime::{BuildStorage, Perbill}; @@ -236,14 +236,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t.into() } -pub fn run_to_block(n: u64) { - while System::block_number() < n { - Scheduler::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - Scheduler::on_initialize(System::block_number()); - } -} - pub fn root() -> OriginCaller { system::RawOrigin::Root.into() } diff --git a/substrate/frame/scheduler/src/tests.rs b/substrate/frame/scheduler/src/tests.rs index 3023a370a4b6..755223934108 100644 --- a/substrate/frame/scheduler/src/tests.rs +++ b/substrate/frame/scheduler/src/tests.rs @@ -20,7 +20,7 @@ use super::*; use crate::mock::{ logger::{self, Threshold}, - new_test_ext, root, run_to_block, LoggerCall, RuntimeCall, Scheduler, Test, *, + new_test_ext, root, LoggerCall, RuntimeCall, Scheduler, Test, *, }; use frame_support::{ assert_err, assert_noop, assert_ok, @@ -52,14 +52,14 @@ fn basic_scheduling_works() { )); // `log` runtime call should not have executed yet - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::(4); // `log` runtime call should have executed at block 4 assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -87,17 +87,17 @@ fn scheduling_with_preimages_works() { assert!(Preimage::is_requested(&hash)); // `log` runtime call should not have executed yet - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::(4); // preimage should not have been removed when executed by the scheduler assert!(!Preimage::len(&hash).is_some()); assert!(!Preimage::is_requested(&hash)); // `log` runtime call should have executed at block 4 assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -105,7 +105,7 @@ fn scheduling_with_preimages_works() { #[test] fn schedule_after_works() { new_test_ext().execute_with(|| { - run_to_block(2); + System::run_to_block::(2); let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!::BaseCallFilter::contains(&call)); @@ -117,11 +117,11 @@ fn schedule_after_works() { root(), Preimage::bound(call).unwrap() )); - run_to_block(5); + System::run_to_block::(5); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -129,7 +129,7 @@ fn schedule_after_works() { #[test] fn schedule_after_zero_works() { new_test_ext().execute_with(|| { - run_to_block(2); + System::run_to_block::(2); let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!::BaseCallFilter::contains(&call)); @@ -141,9 +141,9 @@ fn schedule_after_zero_works() { Preimage::bound(call).unwrap() )); // Will trigger on the next block. - run_to_block(3); + System::run_to_block::(3); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -163,19 +163,19 @@ fn periodic_scheduling_works() { })) .unwrap() )); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); + System::run_to_block::(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(7); + System::run_to_block::(7); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); + System::run_to_block::(9); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); }); } @@ -201,37 +201,37 @@ fn retry_scheduling_works() { // retry 10 times every 3 blocks assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 3)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert!(Agenda::::get(4)[0].is_some()); // task should be retried in block 7 - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); assert!(Agenda::::get(7)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert!(Agenda::::get(7)[0].is_some()); assert!(logger::log().is_empty()); // task still fails, should be retried in block 10 - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::(8); assert!(Agenda::::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::(9); assert!(logger::log().is_empty()); assert_eq!(Retries::::iter().count(), 1); // finally it should succeed - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::::iter().count(), 0); - run_to_block(11); + System::run_to_block::(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -262,37 +262,37 @@ fn named_retry_scheduling_works() { // retry 10 times every 3 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 3)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert!(Agenda::::get(4)[0].is_some()); // task should be retried in block 7 - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); assert!(Agenda::::get(7)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert!(Agenda::::get(7)[0].is_some()); assert!(logger::log().is_empty()); // task still fails, should be retried in block 10 - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::(8); assert!(Agenda::::get(10)[0].is_some()); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::(9); assert!(logger::log().is_empty()); assert_eq!(Retries::::iter().count(), 1); // finally it should succeed - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::::iter().count(), 0); - run_to_block(11); + System::run_to_block::(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -333,11 +333,11 @@ fn retry_scheduling_multiple_tasks_works() { // task 42 will be retried 10 times every 3 blocks assert_ok!(Scheduler::set_retry(root().into(), (4, 1), 10, 3)); assert_eq!(Retries::::iter().count(), 2); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::::get(4).len(), 2); // both tasks fail - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); // 20 is rescheduled for next block assert_eq!(Agenda::::get(5).len(), 1); @@ -345,41 +345,41 @@ fn retry_scheduling_multiple_tasks_works() { assert_eq!(Agenda::::get(7).len(), 1); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(5); + System::run_to_block::(5); // 20 rescheduled for next block assert_eq!(Agenda::::get(6).len(), 1); assert_eq!(Agenda::::get(7).len(), 1); assert_eq!(Retries::::iter().count(), 2); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(6); + System::run_to_block::(6); // rescheduled for next block together with 42 assert_eq!(Agenda::::get(7).len(), 2); assert_eq!(Retries::::iter().count(), 2); assert!(logger::log().is_empty()); // both tasks will fail, for 20 it was the last retry so it's dropped - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(8).is_empty()); // 42 is rescheduled for block 10 assert_eq!(Agenda::::get(10).len(), 1); assert_eq!(Retries::::iter().count(), 1); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::(8); assert_eq!(Agenda::::get(10).len(), 1); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::(9); assert!(logger::log().is_empty()); assert_eq!(Retries::::iter().count(), 1); // 42 runs successfully - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::::iter().count(), 0); - run_to_block(11); + System::run_to_block::(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -422,11 +422,11 @@ fn retry_scheduling_multiple_named_tasks_works() { // task 42 will be retried 10 times every 3 block assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 3)); assert_eq!(Retries::::iter().count(), 2); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::::get(4).len(), 2); // both tasks fail - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); // 42 is rescheduled for block 7 assert_eq!(Agenda::::get(7).len(), 1); @@ -434,41 +434,41 @@ fn retry_scheduling_multiple_named_tasks_works() { assert_eq!(Agenda::::get(5).len(), 1); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(5); + System::run_to_block::(5); // 20 rescheduled for next block assert_eq!(Agenda::::get(6).len(), 1); assert_eq!(Agenda::::get(7).len(), 1); assert_eq!(Retries::::iter().count(), 2); assert!(logger::log().is_empty()); // 20 still fails - run_to_block(6); + System::run_to_block::(6); // 20 rescheduled for next block together with 42 assert_eq!(Agenda::::get(7).len(), 2); assert_eq!(Retries::::iter().count(), 2); assert!(logger::log().is_empty()); // both tasks will fail, for 20 it was the last retry so it's dropped - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(8).is_empty()); // 42 is rescheduled for block 10 assert_eq!(Agenda::::get(10).len(), 1); assert_eq!(Retries::::iter().count(), 1); assert!(logger::log().is_empty()); - run_to_block(8); + System::run_to_block::(8); assert_eq!(Agenda::::get(10).len(), 1); assert!(logger::log().is_empty()); - run_to_block(9); + System::run_to_block::(9); assert!(logger::log().is_empty()); assert_eq!(Retries::::iter().count(), 1); // 42 runs successfully - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!(Retries::::iter().count(), 0); - run_to_block(11); + System::run_to_block::(11); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(12); + System::run_to_block::(12); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -495,33 +495,33 @@ fn retry_scheduling_with_period_works() { // 42 will be retried 10 times every 2 blocks assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 2)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert!(Agenda::::get(4)[0].is_some()); // 42 runs successfully once, it will run again at block 7 - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); assert!(Agenda::::get(7)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // nothing changed - run_to_block(6); + System::run_to_block::(6); assert!(Agenda::::get(7)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 runs successfully again, it will run again at block 10 - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(10)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); + System::run_to_block::(9); assert!(Agenda::::get(10)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 has 10 retries left out of a total of 10 assert_eq!(Retries::::get((10, 0)).unwrap().remaining, 10); // 42 will fail because we're outside the set threshold (block number in `4..8`), so it // should be retried in 2 blocks (at block 12) - run_to_block(10); + System::run_to_block::(10); // should be queued for the normal period of 3 blocks assert!(Agenda::::get(13)[0].is_some()); // should also be queued to be retried in 2 blocks @@ -532,7 +532,7 @@ fn retry_scheduling_with_period_works() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail again - run_to_block(12); + System::run_to_block::(12); // should still be queued for the normal period assert!(Agenda::::get(13)[0].is_some()); // should be queued to be retried in 2 blocks @@ -543,7 +543,7 @@ fn retry_scheduling_with_period_works() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail for the regular periodic run - run_to_block(13); + System::run_to_block::(13); // should still be queued for the normal period assert!(Agenda::::get(16)[0].is_some()); // should still be queued to be retried next block @@ -560,7 +560,7 @@ fn retry_scheduling_with_period_works() { // change the threshold to allow the task to succeed Threshold::::put((14, 100)); // first retry should now succeed - run_to_block(14); + System::run_to_block::(14); assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::::get(16)[0].is_some()); @@ -569,7 +569,7 @@ fn retry_scheduling_with_period_works() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); // second retry should also succeed - run_to_block(15); + System::run_to_block::(15); assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::::get(16)[0].is_some()); assert!(Agenda::::get(17).is_empty()); @@ -580,7 +580,7 @@ fn retry_scheduling_with_period_works() { vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)] ); // normal periodic run on block 16 will succeed - run_to_block(16); + System::run_to_block::(16); // next periodic run at block 19 assert!(Agenda::::get(19)[0].is_some()); assert!(Agenda::::get(18).is_empty()); @@ -598,7 +598,7 @@ fn retry_scheduling_with_period_works() { ] ); // final periodic run on block 19 will succeed - run_to_block(19); + System::run_to_block::(19); // next periodic run at block 19 assert_eq!(Agenda::::iter().count(), 0); assert_eq!(Retries::::iter().count(), 0); @@ -639,33 +639,33 @@ fn named_retry_scheduling_with_period_works() { // 42 will be retried 10 times every 2 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 10, 2)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert!(Agenda::::get(4)[0].is_some()); // 42 runs successfully once, it will run again at block 7 - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); assert!(Agenda::::get(7)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // nothing changed - run_to_block(6); + System::run_to_block::(6); assert!(Agenda::::get(7)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 runs successfully again, it will run again at block 10 - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(7).is_empty()); assert!(Agenda::::get(10)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(9); + System::run_to_block::(9); assert!(Agenda::::get(10)[0].is_some()); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 has 10 retries left out of a total of 10 assert_eq!(Retries::::get((10, 0)).unwrap().remaining, 10); // 42 will fail because we're outside the set threshold (block number in `4..8`), so it // should be retried in 2 blocks (at block 12) - run_to_block(10); + System::run_to_block::(10); // should be queued for the normal period of 3 blocks assert!(Agenda::::get(13)[0].is_some()); // should also be queued to be retried in 2 blocks @@ -677,7 +677,7 @@ fn named_retry_scheduling_with_period_works() { assert_eq!(Lookup::::get([42u8; 32]).unwrap(), (13, 0)); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail again - run_to_block(12); + System::run_to_block::(12); // should still be queued for the normal period assert!(Agenda::::get(13)[0].is_some()); // should be queued to be retried in 2 blocks @@ -688,7 +688,7 @@ fn named_retry_scheduling_with_period_works() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // 42 will fail for the regular periodic run - run_to_block(13); + System::run_to_block::(13); // should still be queued for the normal period assert!(Agenda::::get(16)[0].is_some()); // should still be queued to be retried next block @@ -706,7 +706,7 @@ fn named_retry_scheduling_with_period_works() { // change the threshold to allow the task to succeed Threshold::::put((14, 100)); // first retry should now succeed - run_to_block(14); + System::run_to_block::(14); assert!(Agenda::::get(15)[0].as_ref().unwrap().maybe_periodic.is_none()); assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::::get(16)[0].is_some()); @@ -715,7 +715,7 @@ fn named_retry_scheduling_with_period_works() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); // second retry should also succeed - run_to_block(15); + System::run_to_block::(15); assert_eq!(Agenda::::get(16).iter().filter(|entry| entry.is_some()).count(), 1); assert!(Agenda::::get(16)[0].is_some()); assert!(Agenda::::get(17).is_empty()); @@ -727,7 +727,7 @@ fn named_retry_scheduling_with_period_works() { vec![(root(), 42u32), (root(), 42u32), (root(), 42u32), (root(), 42u32)] ); // normal periodic run on block 16 will succeed - run_to_block(16); + System::run_to_block::(16); // next periodic run at block 19 assert!(Agenda::::get(19)[0].is_some()); assert!(Agenda::::get(18).is_empty()); @@ -746,7 +746,7 @@ fn named_retry_scheduling_with_period_works() { ] ); // final periodic run on block 19 will succeed - run_to_block(19); + System::run_to_block::(19); // next periodic run at block 19 assert_eq!(Agenda::::iter().count(), 0); assert_eq!(Retries::::iter().count(), 0); @@ -786,12 +786,12 @@ fn retry_scheduling_expires() { // task 42 will be retried 3 times every block assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 3, 1)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); // task 42 is scheduled for next block assert!(Agenda::::get(4)[0].is_some()); // task fails because we're past block 3 - run_to_block(4); + System::run_to_block::(4); // task is scheduled for next block assert!(Agenda::::get(4).is_empty()); assert!(Agenda::::get(5)[0].is_some()); @@ -799,7 +799,7 @@ fn retry_scheduling_expires() { assert_eq!(Retries::::get((5, 0)).unwrap().remaining, 2); assert!(logger::log().is_empty()); // task fails again - run_to_block(5); + System::run_to_block::(5); // task is scheduled for next block assert!(Agenda::::get(5).is_empty()); assert!(Agenda::::get(6)[0].is_some()); @@ -807,7 +807,7 @@ fn retry_scheduling_expires() { assert_eq!(Retries::::get((6, 0)).unwrap().remaining, 1); assert!(logger::log().is_empty()); // task fails again - run_to_block(6); + System::run_to_block::(6); // task is scheduled for next block assert!(Agenda::::get(6).is_empty()); assert!(Agenda::::get(7)[0].is_some()); @@ -815,7 +815,7 @@ fn retry_scheduling_expires() { assert_eq!(Retries::::get((7, 0)).unwrap().remaining, 0); assert!(logger::log().is_empty()); // task fails again - run_to_block(7); + System::run_to_block::(7); // task ran out of retries so it gets dropped assert_eq!(Agenda::::iter().count(), 0); assert_eq!(Retries::::iter().count(), 0); @@ -949,17 +949,17 @@ fn retry_periodic_full_cycle() { // 42 will be retried 2 times every block assert_ok!(Scheduler::set_retry_named(root().into(), [42u8; 32], 2, 1)); assert_eq!(Retries::::iter().count(), 1); - run_to_block(9); + System::run_to_block::(9); assert!(logger::log().is_empty()); assert!(Agenda::::get(10)[0].is_some()); // 42 runs successfully once, it will run again at block 110 - run_to_block(10); + System::run_to_block::(10); assert!(Agenda::::get(10).is_empty()); assert!(Agenda::::get(110)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // nothing changed - run_to_block(109); + System::run_to_block::(109); assert!(Agenda::::get(110)[0].is_some()); // original task still has 2 remaining retries assert_eq!(Retries::::get((110, 0)).unwrap().remaining, 2); @@ -968,7 +968,7 @@ fn retry_periodic_full_cycle() { Threshold::::put((1, 2)); // 42 will fail because we're outside the set threshold (block number in `1..2`), so it // should be retried next block (at block 111) - run_to_block(110); + System::run_to_block::(110); // should be queued for the normal period of 100 blocks assert!(Agenda::::get(210)[0].is_some()); // should also be queued to be retried next block @@ -980,7 +980,7 @@ fn retry_periodic_full_cycle() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 retry will fail again - run_to_block(111); + System::run_to_block::(111); // should still be queued for the normal period assert!(Agenda::::get(210)[0].is_some()); // should be queued to be retried next block @@ -991,20 +991,20 @@ fn retry_periodic_full_cycle() { assert_eq!(Retries::::iter().count(), 2); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 42 retry will fail again - run_to_block(112); + System::run_to_block::(112); // should still be queued for the normal period assert!(Agenda::::get(210)[0].is_some()); // 42 retry clone ran out of retries, must have been evicted assert_eq!(Agenda::::iter().count(), 1); // advance - run_to_block(209); + System::run_to_block::(209); // should still be queued for the normal period assert!(Agenda::::get(210)[0].is_some()); // 42 retry clone ran out of retries, must have been evicted assert_eq!(Agenda::::iter().count(), 1); // 42 should fail again and should spawn another retry clone - run_to_block(210); + System::run_to_block::(210); // should be queued for the normal period of 100 blocks assert!(Agenda::::get(310)[0].is_some()); // should also be queued to be retried next block @@ -1018,7 +1018,7 @@ fn retry_periodic_full_cycle() { // make 42 run successfully again Threshold::::put((1, 1000)); // 42 retry clone should now succeed - run_to_block(211); + System::run_to_block::(211); // should be queued for the normal period of 100 blocks assert!(Agenda::::get(310)[0].is_some()); // retry was successful, retry task should have been discarded @@ -1029,7 +1029,7 @@ fn retry_periodic_full_cycle() { assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); // fast forward to the last periodic run of 42 - run_to_block(310); + System::run_to_block::(310); // 42 was successful, the period ended as this was the 4th scheduled periodic run so 42 must // have been discarded assert_eq!(Agenda::::iter().count(), 0); @@ -1057,7 +1057,7 @@ fn reschedule_works() { (4, 0) ); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Scheduler::do_reschedule((4, 0), DispatchTime::At(6)).unwrap(), (6, 0)); @@ -1067,13 +1067,13 @@ fn reschedule_works() { Error::::RescheduleNoChange ); - run_to_block(4); + System::run_to_block::(4); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -1097,7 +1097,7 @@ fn reschedule_named_works() { (4, 0) ); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)).unwrap(), (6, 0)); @@ -1107,13 +1107,13 @@ fn reschedule_named_works() { Error::::RescheduleNoChange ); - run_to_block(4); + System::run_to_block::(4); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -1137,16 +1137,16 @@ fn reschedule_named_periodic_works() { (4, 0) ); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(5)).unwrap(), (5, 0)); assert_eq!(Scheduler::do_reschedule_named([1u8; 32], DispatchTime::At(6)).unwrap(), (6, 0)); - run_to_block(5); + System::run_to_block::(5); assert!(logger::log().is_empty()); - run_to_block(6); + System::run_to_block::(6); assert_eq!(logger::log(), vec![(root(), 42u32)]); assert_eq!( @@ -1154,16 +1154,16 @@ fn reschedule_named_periodic_works() { (10, 0) ); - run_to_block(9); + System::run_to_block::(9); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(10); + System::run_to_block::(10); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32)]); - run_to_block(13); + System::run_to_block::(13); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 42u32), (root(), 42u32)]); }); } @@ -1197,11 +1197,11 @@ fn cancel_named_scheduling_works_with_normal_cancel() { .unwrap(), ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); assert_ok!(Scheduler::do_cancel(None, i)); - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); }); } @@ -1251,13 +1251,13 @@ fn cancel_named_periodic_scheduling_works() { .unwrap(), ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(6); + System::run_to_block::(6); assert_ok!(Scheduler::do_cancel_named(None, [1u8; 32])); - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); }); } @@ -1283,9 +1283,9 @@ fn scheduler_respects_weight_limits() { Preimage::bound(call).unwrap(), )); // 69 and 42 do not fit together - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); - run_to_block(5); + System::run_to_block::(5); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 69u32)]); }); } @@ -1316,26 +1316,26 @@ fn retry_respects_weight_limits() { // set a retry config for 20 for 10 retries every block assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); // 20 should fail and be retried later - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(5)[0].is_some()); assert!(Agenda::::get(8)[0].is_some()); assert_eq!(Retries::::iter().count(), 1); assert!(logger::log().is_empty()); // 20 still fails but is scheduled next block together with 42 - run_to_block(7); + System::run_to_block::(7); assert_eq!(Agenda::::get(8).len(), 2); assert_eq!(Retries::::iter().count(), 1); assert!(logger::log().is_empty()); // 20 and 42 do not fit together // 42 is executed as it was first in the queue // 20 is still on the 8th block's agenda - run_to_block(8); + System::run_to_block::(8); assert!(Agenda::::get(8)[0].is_none()); assert!(Agenda::::get(8)[1].is_some()); assert_eq!(Retries::::iter().count(), 1); assert_eq!(logger::log(), vec![(root(), 42u32)]); // 20 is executed and the schedule is cleared - run_to_block(9); + System::run_to_block::(9); assert_eq!(Agenda::::iter().count(), 0); assert_eq!(Retries::::iter().count(), 0); assert_eq!(logger::log(), vec![(root(), 42u32), (root(), 20u32)]); @@ -1386,7 +1386,7 @@ fn try_schedule_retry_respects_weight_limits() { // set a retry config for 20 for 10 retries every block assert_ok!(Scheduler::set_retry(root().into(), (4, 0), 10, 1)); // 20 should fail and, because of insufficient weight, it should not be scheduled again - run_to_block(4); + System::run_to_block::(4); // nothing else should be scheduled assert_eq!(Agenda::::iter().count(), 0); assert_eq!(Retries::::iter().count(), 0); @@ -1415,7 +1415,7 @@ fn scheduler_does_not_delete_permanently_overweight_call() { Preimage::bound(call).unwrap(), )); // Never executes. - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![]); // Assert the `PermanentlyOverweight` event. @@ -1445,7 +1445,7 @@ fn scheduler_handles_periodic_failure() { bound.clone(), )); // Executes 5 times till block 20. - run_to_block(20); + System::run_to_block::(20); assert_eq!(logger::log().len(), 5); // Block 28 will already be full. @@ -1460,7 +1460,7 @@ fn scheduler_handles_periodic_failure() { } // Going to block 24 will emit a `PeriodicFailed` event. - run_to_block(24); + System::run_to_block::(24); assert_eq!(logger::log().len(), 6); assert_eq!( @@ -1498,7 +1498,7 @@ fn scheduler_handles_periodic_unavailable_preimage() { assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), call.encode())); // Executes 1 times till block 4. - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log().len(), 1); // As the public api doesn't support to remove a noted preimage, we need to first unnote it @@ -1508,7 +1508,7 @@ fn scheduler_handles_periodic_unavailable_preimage() { Preimage::request(&hash); // Does not ever execute again. - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log().len(), 1); // The preimage is not requested anymore. @@ -1536,7 +1536,7 @@ fn scheduler_respects_priority_ordering() { root(), Preimage::bound(call).unwrap(), )); - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 69u32), (root(), 42u32)]); }); } @@ -1571,10 +1571,10 @@ fn scheduler_respects_priority_ordering_with_soft_deadlines() { )); // 2600 does not fit with 69 or 42, but has higher priority, so will go through - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 2600u32)]); // 69 and 42 fit together - run_to_block(5); + System::run_to_block::(5); assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]); }); } @@ -1701,14 +1701,14 @@ fn root_calls_works() { Scheduler::schedule_named(RuntimeOrigin::root(), [1u8; 32], 4, None, 127, call,) ); assert_ok!(Scheduler::schedule(RuntimeOrigin::root(), 4, None, 127, call2)); - run_to_block(3); + System::run_to_block::(3); // Scheduled calls are in the agenda. assert_eq!(Agenda::::get(4).len(), 2); assert!(logger::log().is_empty()); assert_ok!(Scheduler::cancel_named(RuntimeOrigin::root(), [1u8; 32])); assert_ok!(Scheduler::cancel(RuntimeOrigin::root(), 4, 1)); // Scheduled calls are made NONE, so should not effect state - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); }); } @@ -1716,7 +1716,7 @@ fn root_calls_works() { #[test] fn fails_to_schedule_task_in_the_past() { new_test_ext().execute_with(|| { - run_to_block(3); + System::run_to_block::(3); let call1 = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 69, @@ -1768,14 +1768,14 @@ fn should_use_origin() { call, )); assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2,)); - run_to_block(3); + System::run_to_block::(3); // Scheduled calls are in the agenda. assert_eq!(Agenda::::get(4).len(), 2); assert!(logger::log().is_empty()); assert_ok!(Scheduler::cancel_named(system::RawOrigin::Signed(1).into(), [1u8; 32])); assert_ok!(Scheduler::cancel(system::RawOrigin::Signed(1).into(), 4, 1)); // Scheduled calls are made NONE, so should not effect state - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); }); } @@ -1829,7 +1829,7 @@ fn should_check_origin_for_cancel() { call, )); assert_ok!(Scheduler::schedule(system::RawOrigin::Signed(1).into(), 4, None, 127, call2,)); - run_to_block(3); + System::run_to_block::(3); // Scheduled calls are in the agenda. assert_eq!(Agenda::::get(4).len(), 2); assert!(logger::log().is_empty()); @@ -1840,7 +1840,7 @@ fn should_check_origin_for_cancel() { assert_noop!(Scheduler::cancel(system::RawOrigin::Signed(2).into(), 4, 1), BadOrigin); assert_noop!(Scheduler::cancel_named(system::RawOrigin::Root.into(), [1u8; 32]), BadOrigin); assert_noop!(Scheduler::cancel(system::RawOrigin::Root.into(), 4, 1), BadOrigin); - run_to_block(5); + System::run_to_block::(5); assert_eq!( logger::log(), vec![ @@ -1888,17 +1888,17 @@ fn cancel_removes_retry_entry() { // task 42 will be retried 10 times every 3 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1)); assert_eq!(Retries::::iter().count(), 2); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::::get(4).len(), 2); // both tasks fail - run_to_block(4); + System::run_to_block::(4); assert!(Agenda::::get(4).is_empty()); // 42 and 20 are rescheduled for next block assert_eq!(Agenda::::get(5).len(), 2); assert!(logger::log().is_empty()); // 42 and 20 still fail - run_to_block(5); + System::run_to_block::(5); // 42 and 20 rescheduled for next block assert_eq!(Agenda::::get(6).len(), 2); assert_eq!(Retries::::iter().count(), 2); @@ -1909,7 +1909,7 @@ fn cancel_removes_retry_entry() { assert!(Scheduler::cancel(root().into(), 6, 0).is_ok()); // 20 is removed, 42 still fails - run_to_block(6); + System::run_to_block::(6); // 42 rescheduled for next block assert_eq!(Agenda::::get(7).len(), 1); // 20's retry entry is removed @@ -1920,7 +1920,7 @@ fn cancel_removes_retry_entry() { assert!(Scheduler::cancel(root().into(), 7, 0).is_ok()); // both tasks are canceled, everything is removed now - run_to_block(7); + System::run_to_block::(7); assert!(Agenda::::get(8).is_empty()); assert_eq!(Retries::::iter().count(), 0); }); @@ -1963,7 +1963,7 @@ fn cancel_retries_works() { // task 42 will be retried 10 times every 3 blocks assert_ok!(Scheduler::set_retry_named(root().into(), [1u8; 32], 10, 1)); assert_eq!(Retries::::iter().count(), 2); - run_to_block(3); + System::run_to_block::(3); assert!(logger::log().is_empty()); assert_eq!(Agenda::::get(4).len(), 2); // cancel the retry config for 20 @@ -1972,7 +1972,7 @@ fn cancel_retries_works() { // cancel the retry config for 42 assert_ok!(Scheduler::cancel_retry_named(root().into(), [1u8; 32])); assert_eq!(Retries::::iter().count(), 0); - run_to_block(4); + System::run_to_block::(4); // both tasks failed and there are no more retries, so they are evicted assert_eq!(Agenda::::get(4).len(), 0); assert_eq!(Retries::::iter().count(), 0); @@ -2287,7 +2287,7 @@ fn postponed_named_task_cannot_be_rescheduled() { assert!(Lookup::::contains_key(name)); // Run to a very large block. - run_to_block(10); + System::run_to_block::(10); // It was not executed. assert!(logger::log().is_empty()); @@ -2321,7 +2321,7 @@ fn postponed_named_task_cannot_be_rescheduled() { // Finally add the preimage. assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(0), call.encode())); - run_to_block(1000); + System::run_to_block::(1000); // It did not execute. assert!(logger::log().is_empty()); assert!(!Preimage::is_requested(&hash)); @@ -2357,14 +2357,14 @@ fn scheduler_v3_anon_basic_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); // Executes in block 4. - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); // ... but not again. - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -2389,7 +2389,7 @@ fn scheduler_v3_anon_cancel_works() { // Cancel the call. assert_ok!(>::cancel(address)); // It did not get executed. - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); // Cannot cancel again. assert_err!(>::cancel(address), DispatchError::Unavailable); @@ -2413,7 +2413,7 @@ fn scheduler_v3_anon_reschedule_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2430,9 +2430,9 @@ fn scheduler_v3_anon_reschedule_works() { // Re-schedule to block 5. assert_ok!(>::reschedule(address, DispatchTime::At(5))); // Scheduled for block 5. - run_to_block(4); + System::run_to_block::(4); assert!(logger::log().is_empty()); - run_to_block(5); + System::run_to_block::(5); // Does execute in block 5. assert_eq!(logger::log(), vec![(root(), 42)]); // Cannot re-schedule executed task. @@ -2461,14 +2461,14 @@ fn scheduler_v3_anon_next_schedule_time_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); // Scheduled for block 4. assert_eq!(>::next_dispatch_time(address), Ok(4)); // Block 4 executes it. - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42)]); // It has no dispatch time anymore. @@ -2498,7 +2498,7 @@ fn scheduler_v3_anon_reschedule_and_next_schedule_time_work() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2512,10 +2512,10 @@ fn scheduler_v3_anon_reschedule_and_next_schedule_time_work() { assert_eq!(>::next_dispatch_time(address), Ok(5)); // Block 4 does nothing. - run_to_block(4); + System::run_to_block::(4); assert!(logger::log().is_empty()); // Block 5 executes it. - run_to_block(5); + System::run_to_block::(5); assert_eq!(logger::log(), vec![(root(), 42)]); }); } @@ -2548,7 +2548,7 @@ fn scheduler_v3_anon_schedule_agenda_overflows() { DispatchError::Exhausted ); - run_to_block(4); + System::run_to_block::(4); // All scheduled calls are executed. assert_eq!(logger::log().len() as u32, max); }); @@ -2597,7 +2597,7 @@ fn scheduler_v3_anon_cancel_and_schedule_fills_holes() { assert_eq!(i, index); } - run_to_block(4); + System::run_to_block::(4); // Maximum number of calls are executed. assert_eq!(logger::log().len() as u32, max); }); @@ -2643,7 +2643,7 @@ fn scheduler_v3_anon_reschedule_fills_holes() { assert_eq!(new, want); } - run_to_block(4); + System::run_to_block::(4); // Maximum number of calls are executed. assert_eq!(logger::log().len() as u32, max); }); @@ -2670,14 +2670,14 @@ fn scheduler_v3_named_basic_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); // Executes in block 4. - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42u32)]); // ... but not again. - run_to_block(100); + System::run_to_block::(100); assert_eq!(logger::log(), vec![(root(), 42u32)]); }); } @@ -2705,7 +2705,7 @@ fn scheduler_v3_named_cancel_named_works() { // Cancel the call by name. assert_ok!(>::cancel_named(name)); // It did not get executed. - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); // Cannot cancel again. assert_noop!(>::cancel_named(name), DispatchError::Unavailable); @@ -2735,7 +2735,7 @@ fn scheduler_v3_named_cancel_without_name_works() { // Cancel the call by address. assert_ok!(>::cancel(address)); // It did not get executed. - run_to_block(100); + System::run_to_block::(100); assert!(logger::log().is_empty()); // Cannot cancel again. assert_err!(>::cancel(address), DispatchError::Unavailable); @@ -2762,7 +2762,7 @@ fn scheduler_v3_named_reschedule_named_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2784,9 +2784,9 @@ fn scheduler_v3_named_reschedule_named_works() { // Re-schedule to block 5. assert_ok!(>::reschedule_named(name, DispatchTime::At(5))); // Scheduled for block 5. - run_to_block(4); + System::run_to_block::(4); assert!(logger::log().is_empty()); - run_to_block(5); + System::run_to_block::(5); // Does execute in block 5. assert_eq!(logger::log(), vec![(root(), 42)]); // Cannot re-schedule executed task. @@ -2822,7 +2822,7 @@ fn scheduler_v3_named_next_schedule_time_works() { ) .unwrap(); - run_to_block(3); + System::run_to_block::(3); // Did not execute till block 3. assert!(logger::log().is_empty()); @@ -2831,7 +2831,7 @@ fn scheduler_v3_named_next_schedule_time_works() { // Also works by address. assert_eq!(>::next_dispatch_time(address), Ok(4)); // Block 4 executes it. - run_to_block(4); + System::run_to_block::(4); assert_eq!(logger::log(), vec![(root(), 42)]); // It has no dispatch time anymore. @@ -3025,7 +3025,7 @@ fn unavailable_call_is_detected() { assert!(Preimage::is_requested(&hash)); // Executes in block 4. - run_to_block(4); + System::run_to_block::(4); assert_eq!( System::events().last().unwrap().event, diff --git a/substrate/frame/society/src/mock.rs b/substrate/frame/society/src/mock.rs index 3c27c08a1061..8cb5dc823753 100644 --- a/substrate/frame/society/src/mock.rs +++ b/substrate/frame/society/src/mock.rs @@ -138,18 +138,6 @@ impl EnvBuilder { } } -/// Run until a particular block. -pub fn run_to_block(n: u64) { - while System::block_number() < n { - if System::block_number() > 1 { - System::on_finalize(System::block_number()); - } - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - Society::on_initialize(System::block_number()); - } -} - /// Creates a bid struct using input parameters. pub fn bid( who: AccountId, @@ -173,12 +161,12 @@ pub fn candidacy( pub fn next_challenge() { let challenge_period: u64 = ::ChallengePeriod::get(); let now = System::block_number(); - run_to_block(now + challenge_period - now % challenge_period); + System::run_to_block::(now + challenge_period - now % challenge_period); } pub fn next_voting() { if let Period::Voting { more, .. } = Society::period() { - run_to_block(System::block_number() + more); + System::run_to_block::(System::block_number() + more); } } @@ -235,8 +223,11 @@ pub fn conclude_intake(allow_resignation: bool, judge_intake: Option) { pub fn next_intake() { let claim_period: u64 = ::ClaimPeriod::get(); match Society::period() { - Period::Voting { more, .. } => run_to_block(System::block_number() + more + claim_period), - Period::Claim { more, .. } => run_to_block(System::block_number() + more), + Period::Voting { more, .. } => System::run_to_block::( + System::block_number() + more + claim_period, + ), + Period::Claim { more, .. } => + System::run_to_block::(System::block_number() + more), } } diff --git a/substrate/frame/society/src/tests.rs b/substrate/frame/society/src/tests.rs index 2a13f99855b5..22832f18b6fe 100644 --- a/substrate/frame/society/src/tests.rs +++ b/substrate/frame/society/src/tests.rs @@ -272,7 +272,7 @@ fn bidding_works() { // 40, now a member, can vote for 50 assert_ok!(Society::vote(Origin::signed(40), 50, true)); conclude_intake(true, None); - run_to_block(12); + System::run_to_block::(12); // 50 is now a member assert_eq!(members(), vec![10, 30, 40, 50]); // Pot is increased by 1000, and 500 is paid out. Total payout so far is 1200. @@ -282,7 +282,7 @@ fn bidding_works() { assert_eq!(candidacies(), vec![]); assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around // Next period - run_to_block(16); + System::run_to_block::(16); // Same members assert_eq!(members(), vec![10, 30, 40, 50]); // Pot is increased by 1000 again @@ -294,7 +294,7 @@ fn bidding_works() { // Candidate 60 is voted in. assert_ok!(Society::vote(Origin::signed(50), 60, true)); conclude_intake(true, None); - run_to_block(20); + System::run_to_block::(20); // 60 joins as a member assert_eq!(members(), vec![10, 30, 40, 50, 60]); // Pay them @@ -368,7 +368,7 @@ fn rejecting_skeptic_on_approved_is_punished() { } conclude_intake(true, None); assert_eq!(Members::::get(10).unwrap().strikes, 0); - run_to_block(12); + System::run_to_block::(12); assert_eq!(members(), vec![10, 20, 30, 40]); assert_eq!(Members::::get(skeptic).unwrap().strikes, 1); }); @@ -418,7 +418,7 @@ fn slash_payout_works() { Payouts::::get(20), PayoutRecord { paid: 0, payouts: vec![(8, 500)].try_into().unwrap() } ); - run_to_block(8); + System::run_to_block::(8); // payout should be here, but 500 less assert_ok!(Society::payout(RuntimeOrigin::signed(20))); assert_eq!(Balances::free_balance(20), 550); @@ -1315,7 +1315,7 @@ fn drop_candidate_works() { assert_ok!(Society::vote(Origin::signed(10), 40, false)); assert_ok!(Society::vote(Origin::signed(20), 40, false)); assert_ok!(Society::vote(Origin::signed(30), 40, false)); - run_to_block(12); + System::run_to_block::(12); assert_ok!(Society::drop_candidate(Origin::signed(50), 40)); // 40 candidacy has gone. assert_eq!(candidates(), vec![]); diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index f79a52bc6c5b..e3e58fc01b5f 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -325,7 +325,7 @@ pub mod testing_prelude { assert_storage_noop, hypothetically, storage_alias, }; - pub use frame_system::{self, mocking::*}; + pub use frame_system::{self, mocking::*, RunToBlockHooks}; #[deprecated(note = "Use `frame::testing_prelude::TestState` instead.")] pub use sp_io::TestExternalities; diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index df8cb38e8b37..769b84826b41 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -25,7 +25,7 @@ use frame_election_provider_support::{ use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::{ - ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Hooks, Imbalance, LockableCurrency, + ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Imbalance, LockableCurrency, OnUnbalanced, OneSessionHandler, WithdrawReasons, }, weights::constants::RocksDbWeight, @@ -155,7 +155,7 @@ impl pallet_session::historical::Config for Test { } impl pallet_authorship::Config for Test { type FindAuthor = Author11; - type EventHandler = Pallet; + type EventHandler = (); } impl pallet_timestamp::Config for Test { @@ -544,13 +544,10 @@ impl ExtBuilder { let mut ext = sp_io::TestExternalities::from(storage); if self.initialize_first_session { - // We consider all test to start after timestamp is initialized This must be ensured by - // having `timestamp::on_initialize` called before `staking::on_initialize`. Also, if - // session length is 1, then it is already triggered. ext.execute_with(|| { - System::set_block_number(1); - Session::on_initialize(1); - >::on_initialize(1); + run_to_block(1); + + // Force reset the timestamp to the initial timestamp for easy testing. Timestamp::set_timestamp(INIT_TIMESTAMP); }); } @@ -618,33 +615,31 @@ pub(crate) fn bond_virtual_nominator( /// a block import/propose process where we first initialize the block, then execute some stuff (not /// in the function), and then finalize the block. pub(crate) fn run_to_block(n: BlockNumber) { - Staking::on_finalize(System::block_number()); - for b in (System::block_number() + 1)..=n { - System::set_block_number(b); - Session::on_initialize(b); - >::on_initialize(b); - Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); - if b != n { - Staking::on_finalize(System::block_number()); - } - } + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + Timestamp::set_timestamp(bn * BLOCK_TIME + INIT_TIMESTAMP); + }), + ); } /// Progresses from the current block number (whatever that may be) to the `P * session_index + 1`. -pub(crate) fn start_session(session_index: SessionIndex) { +pub(crate) fn start_session(end_session_idx: SessionIndex) { + let period = Period::get(); let end: u64 = if Offset::get().is_zero() { - (session_index as u64) * Period::get() + (end_session_idx as u64) * period } else { - Offset::get() + (session_index.saturating_sub(1) as u64) * Period::get() + Offset::get() + (end_session_idx.saturating_sub(1) as u64) * period }; + run_to_block(end); + + let curr_session_idx = Session::current_index(); + // session must have progressed properly. assert_eq!( - Session::current_index(), - session_index, - "current session index = {}, expected = {}", - Session::current_index(), - session_index, + curr_session_idx, end_session_idx, + "current session index = {curr_session_idx}, expected = {end_session_idx}", ); } diff --git a/substrate/frame/state-trie-migration/src/lib.rs b/substrate/frame/state-trie-migration/src/lib.rs index 61323b70b33d..1dc1a3928f2b 100644 --- a/substrate/frame/state-trie-migration/src/lib.rs +++ b/substrate/frame/state-trie-migration/src/lib.rs @@ -1309,16 +1309,17 @@ mod mock { pub(crate) fn run_to_block(n: u32) -> (H256, Weight) { let mut root = Default::default(); let mut weight_sum = Weight::zero(); + log::trace!(target: LOG_TARGET, "running from {:?} to {:?}", System::block_number(), n); - while System::block_number() < n { - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - weight_sum += StateTrieMigration::on_initialize(System::block_number()); + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().after_initialize(|bn| { + weight_sum += StateTrieMigration::on_initialize(bn); + root = *System::finalize().state_root(); + }), + ); - root = *System::finalize().state_root(); - System::on_finalize(System::block_number()); - } (root, weight_sum) } } diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index 894e1898ed15..f2bb5e290c94 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -1974,6 +1974,51 @@ impl Pallet { .collect::<_>() } + /// Simulate the execution of a block sequence up to a specified height, injecting the + /// provided hooks at each block. + /// + /// `on_finalize` is always called before `on_initialize` with the current block number. + /// `on_initalize` is always called with the next block number. + /// + /// These hooks allows custom logic to be executed at each block at specific location. + /// For example, you might use one of them to set a timestamp for each block. + #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + pub fn run_to_block_with( + n: BlockNumberFor, + mut hooks: RunToBlockHooks, + ) where + AllPalletsWithSystem: frame_support::traits::OnInitialize> + + frame_support::traits::OnFinalize>, + { + let mut bn = Self::block_number(); + + while bn < n { + // Skip block 0. + if !bn.is_zero() { + (hooks.before_finalize)(bn); + AllPalletsWithSystem::on_finalize(bn); + (hooks.after_finalize)(bn); + } + + bn += One::one(); + + Self::set_block_number(bn); + (hooks.before_initialize)(bn); + AllPalletsWithSystem::on_initialize(bn); + (hooks.after_initialize)(bn); + } + } + + /// Simulate the execution of a block sequence up to a specified height. + #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + pub fn run_to_block(n: BlockNumberFor) + where + AllPalletsWithSystem: frame_support::traits::OnInitialize> + + frame_support::traits::OnFinalize>, + { + Self::run_to_block_with::(n, Default::default()); + } + /// Set the block number to something in particular. Can be used as an alternative to /// `initialize` for tests that don't need to bother with the other environment entries. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] @@ -2347,6 +2392,72 @@ impl Lookup for ChainContext { } } +/// Hooks for the [`Pallet::run_to_block_with`] function. +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +pub struct RunToBlockHooks<'a, T> +where + T: 'a + Config, +{ + before_initialize: Box)>, + after_initialize: Box)>, + before_finalize: Box)>, + after_finalize: Box)>, +} + +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +impl<'a, T> RunToBlockHooks<'a, T> +where + T: 'a + Config, +{ + /// Set the hook function logic before the initialization of the block. + pub fn before_initialize(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor), + { + self.before_initialize = Box::new(f); + self + } + /// Set the hook function logic after the initialization of the block. + pub fn after_initialize(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor), + { + self.after_initialize = Box::new(f); + self + } + /// Set the hook function logic before the finalization of the block. + pub fn before_finalize(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor), + { + self.before_finalize = Box::new(f); + self + } + /// Set the hook function logic after the finalization of the block. + pub fn after_finalize(mut self, f: F) -> Self + where + F: 'a + FnMut(BlockNumberFor), + { + self.after_finalize = Box::new(f); + self + } +} + +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] +impl<'a, T> Default for RunToBlockHooks<'a, T> +where + T: Config, +{ + fn default() -> Self { + Self { + before_initialize: Box::new(|_| {}), + after_initialize: Box::new(|_| {}), + before_finalize: Box::new(|_| {}), + after_finalize: Box::new(|_| {}), + } + } +} + /// Prelude to be used alongside pallet macro, for ease of use. pub mod pallet_prelude { pub use crate::{ensure_none, ensure_root, ensure_signed, ensure_signed_or_root}; diff --git a/substrate/frame/transaction-storage/src/mock.rs b/substrate/frame/transaction-storage/src/mock.rs index 73174b73dbac..84a77043d577 100644 --- a/substrate/frame/transaction-storage/src/mock.rs +++ b/substrate/frame/transaction-storage/src/mock.rs @@ -21,10 +21,7 @@ use crate::{ self as pallet_transaction_storage, TransactionStorageProof, DEFAULT_MAX_BLOCK_TRANSACTIONS, DEFAULT_MAX_TRANSACTION_SIZE, }; -use frame_support::{ - derive_impl, - traits::{ConstU32, OnFinalize, OnInitialize}, -}; +use frame_support::{derive_impl, traits::ConstU32}; use sp_runtime::{traits::IdentityLookup, BuildStorage}; pub type Block = frame_system::mocking::MockBlock; @@ -80,15 +77,13 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t.into() } -pub fn run_to_block(n: u64, f: impl Fn() -> Option) { - while System::block_number() < n { - if let Some(proof) = f() { - TransactionStorage::check_proof(RuntimeOrigin::none(), proof).unwrap(); - } - TransactionStorage::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - TransactionStorage::on_initialize(System::block_number()); - } +pub fn run_to_block(n: u64, f: impl Fn() -> Option + 'static) { + System::run_to_block_with::( + n, + frame_system::RunToBlockHooks::default().before_finalize(|_| { + if let Some(proof) = f() { + TransactionStorage::check_proof(RuntimeOrigin::none(), proof).unwrap(); + } + }), + ); } From ef064a357c97c2635f05295aac1698a91fa2f4fd Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:04:37 +0200 Subject: [PATCH 265/340] req-resp/litep2p: Reject inbound requests from banned peers (#7158) This PR rejects inbound requests from banned peers (reputation is below the banned threshold). This mirrors the request-response implementation from the libp2p side. I won't expect this to get triggered too often, but we'll monitor this metric. While at it, have registered a new inbound failure metric to have visibility into this. Discovered during the investigation of: https://github.com/paritytech/polkadot-sdk/issues/7076#issuecomment-2589613046 cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile --- prdoc/pr_7158.prdoc | 12 +++++++++ .../src/litep2p/shim/request_response/mod.rs | 25 ++++++++++++++----- 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 prdoc/pr_7158.prdoc diff --git a/prdoc/pr_7158.prdoc b/prdoc/pr_7158.prdoc new file mode 100644 index 000000000000..e113a7fdcd1c --- /dev/null +++ b/prdoc/pr_7158.prdoc @@ -0,0 +1,12 @@ +title: Reject litep2p inbound requests from banned peers + +doc: + - audience: Node Dev + description: | + This PR rejects inbound requests from banned peers (reputation is below the banned threshold). + This mirrors the request-response implementation from the libp2p side. + While at it, have registered a new inbound failure metric to have visibility into this. + +crates: +- name: sc-network + bump: patch diff --git a/substrate/client/network/src/litep2p/shim/request_response/mod.rs b/substrate/client/network/src/litep2p/shim/request_response/mod.rs index 146f2e4add97..690d5a31e6ad 100644 --- a/substrate/client/network/src/litep2p/shim/request_response/mod.rs +++ b/substrate/client/network/src/litep2p/shim/request_response/mod.rs @@ -273,6 +273,13 @@ impl RequestResponseProtocol { request_id: RequestId, request: Vec, ) { + log::trace!( + target: LOG_TARGET, + "{}: request received from {peer:?} ({fallback:?} {request_id:?}), request size {:?}", + self.protocol, + request.len(), + ); + let Some(inbound_queue) = &self.inbound_queue else { log::trace!( target: LOG_TARGET, @@ -284,12 +291,18 @@ impl RequestResponseProtocol { return; }; - log::trace!( - target: LOG_TARGET, - "{}: request received from {peer:?} ({fallback:?} {request_id:?}), request size {:?}", - self.protocol, - request.len(), - ); + if self.peerstore_handle.is_banned(&peer.into()) { + log::trace!( + target: LOG_TARGET, + "{}: rejecting inbound request from banned {peer:?} ({request_id:?})", + self.protocol, + ); + + self.handle.reject_request(request_id); + self.metrics.register_inbound_request_failure("banned-peer"); + return; + } + let (tx, rx) = oneshot::channel(); match inbound_queue.try_send(IncomingRequest { From 88f898e74423ab32806f44c77c925b0081efa2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20M=C3=BCller?= Date: Wed, 15 Jan 2025 14:14:00 +0100 Subject: [PATCH 266/340] [pallet-revive] Fix `caller_is_root` return value (#7086) Closes https://github.com/paritytech/polkadot-sdk/issues/6767. The return type of the host function `caller_is_root` was denoted as `u32` in `pallet_revive_uapi`. This PR fixes the return type to `bool`. As a drive-by, the PR re-exports `pallet_revive::exec::Origin` to extend what can be tested externally. --------- Co-authored-by: Cyrill Leutwiler --- prdoc/pr_7086.prdoc | 11 +++++++++++ substrate/frame/revive/src/exec.rs | 2 +- substrate/frame/revive/src/gas.rs | 2 +- substrate/frame/revive/src/lib.rs | 4 ++-- substrate/frame/revive/uapi/src/host.rs | 2 +- substrate/frame/revive/uapi/src/host/riscv64.rs | 5 +++-- 6 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 prdoc/pr_7086.prdoc diff --git a/prdoc/pr_7086.prdoc b/prdoc/pr_7086.prdoc new file mode 100644 index 000000000000..55fed9bca3e6 --- /dev/null +++ b/prdoc/pr_7086.prdoc @@ -0,0 +1,11 @@ +title: '[pallet-revive] Fix `caller_is_root` return value' +doc: +- audience: Runtime Dev + description: The return type of the host function `caller_is_root` was denoted as `u32` + in `pallet_revive_uapi`. This PR fixes the return type to `bool`. As a drive-by, the + PR re-exports `pallet_revive::exec::Origin` to extend what can be tested externally. +crates: +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: major diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index a6a259149768..478e96dc994d 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -325,7 +325,7 @@ pub trait Ext: sealing::Sealed { /// Returns `Err(InvalidImmutableAccess)` if called from a constructor. fn get_immutable_data(&mut self) -> Result; - /// Set the the immutable data of the current contract. + /// Set the immutable data of the current contract. /// /// Returns `Err(InvalidImmutableAccess)` if not called from a constructor. /// diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs index 9aad84e69201..5c30a0a51009 100644 --- a/substrate/frame/revive/src/gas.rs +++ b/substrate/frame/revive/src/gas.rs @@ -89,7 +89,7 @@ pub struct RefTimeLeft(u64); /// Resource that needs to be synced to the executor. /// -/// Wrapped to make sure that the resource will be synced back the the executor. +/// Wrapped to make sure that the resource will be synced back to the executor. #[must_use] pub struct Syncable(polkavm::Gas); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 04bce264a188..bdb4b92edd9e 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -45,7 +45,7 @@ use crate::{ runtime::{gas_from_fee, GAS_PRICE}, GasEncoder, GenericTransaction, }, - exec::{AccountIdOf, ExecError, Executable, Ext, Key, Origin, Stack as ExecStack}, + exec::{AccountIdOf, ExecError, Executable, Ext, Key, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, wasm::{CodeInfo, RuntimeCosts, WasmBlob}, @@ -84,7 +84,7 @@ use sp_runtime::{ pub use crate::{ address::{create1, create2, AccountId32Mapper, AddressMapper}, debug::Tracing, - exec::MomentOf, + exec::{MomentOf, Origin}, pallet::*, }; pub use primitives::*; diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index eced4843b552..d90c0f45205d 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -488,7 +488,7 @@ pub trait HostFn: private::Sealed { /// A return value of `true` indicates that this contract is being called by a root origin, /// and `false` indicates that the caller is a signed origin. #[unstable_hostfn] - fn caller_is_root() -> u32; + fn caller_is_root() -> bool; /// Clear the value at the given key in the contract storage. /// diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 6fdda86892d5..c83be942a970 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -501,8 +501,9 @@ impl HostFn for HostFnImpl { } #[unstable_hostfn] - fn caller_is_root() -> u32 { - unsafe { sys::caller_is_root() }.into_u32() + fn caller_is_root() -> bool { + let ret_val = unsafe { sys::caller_is_root() }; + ret_val.into_bool() } #[unstable_hostfn] From cb0d8544dc8828c7b5e7f6a5fc20ce8c6ef9bbb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandre=20R=2E=20Bald=C3=A9?= Date: Wed, 15 Jan 2025 13:14:54 +0000 Subject: [PATCH 267/340] Remove 0 as a special case in gas/storage meters (#6890) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #6846 . --------- Signed-off-by: xermicus Co-authored-by: command-bot <> Co-authored-by: Alexander Theißen Co-authored-by: xermicus --- .../people-westend/src/tests/governance.rs | 2 +- prdoc/pr_6890.prdoc | 19 ++++ .../frame/revive/fixtures/contracts/call.rs | 8 +- .../contracts/call_diverging_out_len.rs | 12 +-- .../fixtures/contracts/call_return_code.rs | 8 +- .../contracts/call_runtime_and_call.rs | 8 +- .../contracts/call_with_flags_and_value.rs | 8 +- .../fixtures/contracts/call_with_limit.rs | 4 +- .../fixtures/contracts/caller_contract.rs | 48 +++++----- .../contracts/chain_extension_temp_storage.rs | 8 +- .../fixtures/contracts/create1_with_value.rs | 12 ++- .../contracts/create_storage_and_call.rs | 8 +- .../create_storage_and_instantiate.rs | 6 +- .../create_transient_storage_and_call.rs | 8 +- .../fixtures/contracts/delegate_call.rs | 10 +- .../contracts/delegate_call_deposit_limit.rs | 10 +- .../contracts/delegate_call_simple.rs | 10 +- .../contracts/destroy_and_transfer.rs | 18 ++-- .../frame/revive/fixtures/contracts/drain.rs | 2 +- .../contracts/instantiate_return_code.rs | 7 +- .../contracts/locking_delegate_dependency.rs | 10 +- .../frame/revive/fixtures/contracts/origin.rs | 6 +- .../fixtures/contracts/read_only_call.rs | 8 +- .../revive/fixtures/contracts/recurse.rs | 8 +- .../fixtures/contracts/return_data_api.rs | 24 +++-- .../fixtures/contracts/self_destruct.rs | 8 +- .../contracts/transfer_return_code.rs | 2 +- substrate/frame/revive/fixtures/src/lib.rs | 2 +- .../rpc/examples/js/pvm/FlipperCaller.polkavm | Bin 4532 -> 4584 bytes .../rpc/examples/js/pvm/PiggyBank.polkavm | Bin 5062 -> 5088 bytes .../frame/revive/src/benchmarking/mod.rs | 14 +-- substrate/frame/revive/src/exec.rs | 60 +++++------- substrate/frame/revive/src/gas.rs | 74 +++++++++++---- substrate/frame/revive/src/primitives.rs | 2 +- substrate/frame/revive/src/storage/meter.rs | 89 ++++++++++-------- substrate/frame/revive/src/tests.rs | 72 +++++++------- substrate/frame/revive/src/wasm/runtime.rs | 6 +- substrate/frame/revive/uapi/src/host.rs | 6 +- .../frame/revive/uapi/src/host/riscv64.rs | 12 +-- 39 files changed, 355 insertions(+), 264 deletions(-) create mode 100644 prdoc/pr_6890.prdoc diff --git a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs index ea438f80552e..3b1779e40b60 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/people/people-westend/src/tests/governance.rs @@ -396,7 +396,7 @@ fn relay_commands_add_remove_username_authority() { ); }); - // Now, remove the username authority with another priviledged XCM call. + // Now, remove the username authority with another privileged XCM call. Westend::execute_with(|| { type Runtime = ::Runtime; type RuntimeCall = ::RuntimeCall; diff --git a/prdoc/pr_6890.prdoc b/prdoc/pr_6890.prdoc new file mode 100644 index 000000000000..b22a339035d8 --- /dev/null +++ b/prdoc/pr_6890.prdoc @@ -0,0 +1,19 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Alter semantic meaning of 0 in metering limits of EVM contract calls + +doc: + - audience: [ Runtime Dev, Runtime User ] + description: | + A limit of 0, for gas meters and storage meters, no longer has the meaning of unlimited metering. + +crates: + - name: pallet-revive + bump: patch + - name: pallet-revive-fixtures + bump: patch + - name: pallet-revive-uapi + bump: patch + - name: pallet-revive-eth-rpc + bump: patch diff --git a/substrate/frame/revive/fixtures/contracts/call.rs b/substrate/frame/revive/fixtures/contracts/call.rs index ee51548879d9..7c4c0882c6b8 100644 --- a/substrate/frame/revive/fixtures/contracts/call.rs +++ b/substrate/frame/revive/fixtures/contracts/call.rs @@ -38,10 +38,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs index 129adde2cec9..9a8fe5f5f6cc 100644 --- a/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs +++ b/substrate/frame/revive/fixtures/contracts/call_diverging_out_len.rs @@ -42,9 +42,9 @@ fn assert_call(callee_address: &[u8; 20], expected_output: [u8; api::call( uapi::CallFlags::ALLOW_REENTRY, callee_address, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0u8; 32], &[], Some(output_buf_capped), @@ -67,9 +67,9 @@ fn assert_instantiate(expected_output: [u8; BUF_SIZE]) { api::instantiate( &code_hash, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], &[0; 32], None, diff --git a/substrate/frame/revive/fixtures/contracts/call_return_code.rs b/substrate/frame/revive/fixtures/contracts/call_return_code.rs index 2d13b9f70956..19b3ae3fdb26 100644 --- a/substrate/frame/revive/fixtures/contracts/call_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/call_return_code.rs @@ -42,10 +42,10 @@ pub extern "C" fn call() { let err_code = match api::call( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - value, // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + value, // Value transferred to the contract. input, None, ) { diff --git a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs index 8c8aee962849..78b275459f0e 100644 --- a/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/call_runtime_and_call.rs @@ -42,10 +42,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs index 330393e706e9..155a4b41bd95 100644 --- a/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs +++ b/substrate/frame/revive/fixtures/contracts/call_with_flags_and_value.rs @@ -40,10 +40,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::from_bits(flags).unwrap(), callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &u256_bytes(value), // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &u256_bytes(value), // Value transferred to the contract. forwarded_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs index 6ab892a6b7ae..af5c301a353c 100644 --- a/substrate/frame/revive/fixtures/contracts/call_with_limit.rs +++ b/substrate/frame/revive/fixtures/contracts/call_with_limit.rs @@ -43,8 +43,8 @@ pub extern "C" fn call() { callee_addr, ref_time, proof_size, - None, // No deposit limit. - &[0u8; 32], // value transferred to the contract. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // value transferred to the contract. forwarded_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/caller_contract.rs b/substrate/frame/revive/fixtures/contracts/caller_contract.rs index edad43fae251..d042dc2c22a2 100644 --- a/substrate/frame/revive/fixtures/contracts/caller_contract.rs +++ b/substrate/frame/revive/fixtures/contracts/caller_contract.rs @@ -42,9 +42,9 @@ pub extern "C" fn call() { // Fail to deploy the contract since it returns a non-zero exit status. let res = api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &reverted_input, None, @@ -56,9 +56,9 @@ pub extern "C" fn call() { // Fail to deploy the contract due to insufficient ref_time weight. let res = api::instantiate( code_hash, - 1u64, // too little ref_time weight - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + 1u64, // too little ref_time weight + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &input, None, @@ -70,9 +70,9 @@ pub extern "C" fn call() { // Fail to deploy the contract due to insufficient proof_size weight. let res = api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 1u64, // Too little proof_size weight - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + 1u64, // Too little proof_size weight + &[u8::MAX; 32], // No deposit limit. &value, &input, None, @@ -86,9 +86,9 @@ pub extern "C" fn call() { api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &input, Some(&mut callee), @@ -101,9 +101,9 @@ pub extern "C" fn call() { let res = api::call( uapi::CallFlags::empty(), &callee, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &reverted_input, None, @@ -114,9 +114,9 @@ pub extern "C" fn call() { let res = api::call( uapi::CallFlags::empty(), &callee, - 1u64, // Too little ref_time weight. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + 1u64, // Too little ref_time weight. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &input, None, @@ -127,9 +127,9 @@ pub extern "C" fn call() { let res = api::call( uapi::CallFlags::empty(), &callee, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 1u64, // too little proof_size weight - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + 1u64, // too little proof_size weight + &[u8::MAX; 32], // No deposit limit. &value, &input, None, @@ -141,9 +141,9 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), &callee, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &value, &input, Some(&mut &mut output[..]), diff --git a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs index 22d6c5b548d8..9b76b9d39ee9 100644 --- a/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs +++ b/substrate/frame/revive/fixtures/contracts/chain_extension_temp_storage.rs @@ -54,10 +54,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/create1_with_value.rs b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs index c6adab828860..3554f8f620a2 100644 --- a/substrate/frame/revive/fixtures/contracts/create1_with_value.rs +++ b/substrate/frame/revive/fixtures/contracts/create1_with_value.rs @@ -34,6 +34,16 @@ pub extern "C" fn call() { api::value_transferred(&mut value); // Deploy the contract with no salt (equivalent to create1). - let ret = api::instantiate(code_hash, 0u64, 0u64, None, &value, &[], None, None, None); + let ret = api::instantiate( + code_hash, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], + &value, + &[], + None, + None, + None + ); assert!(ret.is_ok()); } diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs index a12c36af856a..5bb11e27903e 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_call.rs @@ -43,10 +43,10 @@ pub extern "C" fn call() { let ret = api::call( uapi::CallFlags::empty(), callee, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - Some(deposit_limit), - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all resources. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all resources. + deposit_limit, + &[0u8; 32], // Value transferred to the contract. input, None, ); diff --git a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs index ecc0fc79e6fd..f627bc8ba6c4 100644 --- a/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs +++ b/substrate/frame/revive/fixtures/contracts/create_storage_and_instantiate.rs @@ -41,9 +41,9 @@ pub extern "C" fn call() { let ret = api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - Some(deposit_limit), + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + deposit_limit, &value, input, Some(&mut address), diff --git a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs index cf12fed27563..660db84028db 100644 --- a/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs +++ b/substrate/frame/revive/fixtures/contracts/create_transient_storage_and_call.rs @@ -49,10 +49,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), callee, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call.rs b/substrate/frame/revive/fixtures/contracts/delegate_call.rs index 3cf74acf1321..0dedd5f704cb 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call.rs @@ -46,7 +46,15 @@ pub extern "C" fn call() { assert!(value[0] == 2u8); let input = [0u8; 0]; - api::delegate_call(uapi::CallFlags::empty(), address, ref_time, proof_size, None, &input, None).unwrap(); + api::delegate_call( + uapi::CallFlags::empty(), + address, + ref_time, + proof_size, + &[u8::MAX; 32], + &input, + None + ).unwrap(); api::get_storage(StorageFlags::empty(), &key, value).unwrap(); assert!(value[0] == 1u8); diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs index 0f157f5a18ac..0c503aa93c56 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_deposit_limit.rs @@ -34,7 +34,15 @@ pub extern "C" fn call() { ); let input = [0u8; 0]; - let ret = api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, Some(&u256_bytes(deposit_limit)), &input, None); + let ret = api::delegate_call( + uapi::CallFlags::empty(), + address, + u64::MAX, + u64::MAX, + &u256_bytes(deposit_limit), + &input, + None + ); if let Err(code) = ret { api::return_value(uapi::ReturnFlags::REVERT, &(code as u32).to_le_bytes()); diff --git a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs index a8501dad4692..b7bdb792c76c 100644 --- a/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs +++ b/substrate/frame/revive/fixtures/contracts/delegate_call_simple.rs @@ -32,5 +32,13 @@ pub extern "C" fn call() { // Delegate call into passed address. let input = [0u8; 0]; - api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, None, &input, None).unwrap(); + api::delegate_call( + uapi::CallFlags::empty(), + address, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], + &input, + None + ).unwrap(); } diff --git a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs index 8342f4acf952..c2c7da528ba7 100644 --- a/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs +++ b/substrate/frame/revive/fixtures/contracts/destroy_and_transfer.rs @@ -35,9 +35,9 @@ pub extern "C" fn deploy() { api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &VALUE, &input, Some(&mut address), @@ -62,9 +62,9 @@ pub extern "C" fn call() { let res = api::call( uapi::CallFlags::empty(), &callee_addr, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &VALUE, &[0u8; 1], None, @@ -75,9 +75,9 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), &callee_addr, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, // How much proof_size weight to devote for the execution. 0 = all. - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &VALUE, &[0u8; 0], None, diff --git a/substrate/frame/revive/fixtures/contracts/drain.rs b/substrate/frame/revive/fixtures/contracts/drain.rs index 6e3e708a6b3d..53fb213143c4 100644 --- a/substrate/frame/revive/fixtures/contracts/drain.rs +++ b/substrate/frame/revive/fixtures/contracts/drain.rs @@ -41,7 +41,7 @@ pub extern "C" fn call() { &[0u8; 20], 0, 0, - None, + &[u8::MAX; 32], &u256_bytes(balance), &[], None, diff --git a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs index 9764859c619b..f7cbd75be5aa 100644 --- a/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/instantiate_return_code.rs @@ -33,10 +33,9 @@ pub extern "C" fn call() { let err_code = match api::instantiate( code_hash, - 0u64, // How much ref_time weight to devote for the execution. 0 = all. - 0u64, /* How much proof_size weight to devote for the execution. 0 = - * all. */ - None, // No deposit limit. + u64::MAX, // How much ref_time weight to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size weight to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. &u256_bytes(10_000u64), // Value to transfer. input, None, diff --git a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs index 3d7702c6537a..6be5d5c72f9a 100644 --- a/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs +++ b/substrate/frame/revive/fixtures/contracts/locking_delegate_dependency.rs @@ -52,7 +52,15 @@ fn load_input(delegate_call: bool) { } if delegate_call { - api::delegate_call(uapi::CallFlags::empty(), address, 0, 0, None, &[], None).unwrap(); + api::delegate_call( + uapi::CallFlags::empty(), + address, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], + &[], + None + ).unwrap(); } } diff --git a/substrate/frame/revive/fixtures/contracts/origin.rs b/substrate/frame/revive/fixtures/contracts/origin.rs index 8e9afd8e8052..151ca3da77cd 100644 --- a/substrate/frame/revive/fixtures/contracts/origin.rs +++ b/substrate/frame/revive/fixtures/contracts/origin.rs @@ -49,9 +49,9 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], &[], Some(&mut &mut buf[..]), diff --git a/substrate/frame/revive/fixtures/contracts/read_only_call.rs b/substrate/frame/revive/fixtures/contracts/read_only_call.rs index ea74d56867f5..0a87ecbb9b14 100644 --- a/substrate/frame/revive/fixtures/contracts/read_only_call.rs +++ b/substrate/frame/revive/fixtures/contracts/read_only_call.rs @@ -39,10 +39,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::READ_ONLY, callee_addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. callee_input, None, ) diff --git a/substrate/frame/revive/fixtures/contracts/recurse.rs b/substrate/frame/revive/fixtures/contracts/recurse.rs index 2e70d67d8c73..ead565c01459 100644 --- a/substrate/frame/revive/fixtures/contracts/recurse.rs +++ b/substrate/frame/revive/fixtures/contracts/recurse.rs @@ -43,10 +43,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much deposit_limit to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value transferred to the contract. + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all resources. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all resources. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. &(calls_left - 1).to_le_bytes(), None, ) diff --git a/substrate/frame/revive/fixtures/contracts/return_data_api.rs b/substrate/frame/revive/fixtures/contracts/return_data_api.rs index 1d483373cffd..1407e5323ea1 100644 --- a/substrate/frame/revive/fixtures/contracts/return_data_api.rs +++ b/substrate/frame/revive/fixtures/contracts/return_data_api.rs @@ -80,8 +80,16 @@ fn assert_return_data_size_of(expected: u64) { /// Assert the return data to be reset after a balance transfer. fn assert_balance_transfer_does_reset() { - api::call(uapi::CallFlags::empty(), &[0u8; 20], 0, 0, None, &u256_bytes(128), &[], None) - .unwrap(); + api::call( + uapi::CallFlags::empty(), + &[0u8; 20], + u64::MAX, + u64::MAX, + &[u8::MAX; 32], + &u256_bytes(128), + &[], + None + ).unwrap(); assert_return_data_size_of(0); } @@ -111,9 +119,9 @@ pub extern "C" fn call() { let mut instantiate = |exit_flag| { api::instantiate( code_hash, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], &construct_input(exit_flag), Some(&mut address_buf), @@ -125,9 +133,9 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::empty(), address_buf, - 0u64, - 0u64, - None, + u64::MAX, + u64::MAX, + &[u8::MAX; 32], &[0; 32], &construct_input(exit_flag), None, diff --git a/substrate/frame/revive/fixtures/contracts/self_destruct.rs b/substrate/frame/revive/fixtures/contracts/self_destruct.rs index 2f37706634bd..053e545deb19 100644 --- a/substrate/frame/revive/fixtures/contracts/self_destruct.rs +++ b/substrate/frame/revive/fixtures/contracts/self_destruct.rs @@ -42,10 +42,10 @@ pub extern "C" fn call() { api::call( uapi::CallFlags::ALLOW_REENTRY, &addr, - 0u64, // How much ref_time to devote for the execution. 0 = all. - 0u64, // How much proof_size to devote for the execution. 0 = all. - None, // No deposit limit. - &[0u8; 32], // Value to transfer. + u64::MAX, // How much ref_time to devote for the execution. u64 = all. + u64::MAX, // How much proof_size to devote for the execution. u64 = all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value to transfer. &[0u8; 0], None, ) diff --git a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs index 09d45d0a8411..053f97feda4a 100644 --- a/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs +++ b/substrate/frame/revive/fixtures/contracts/transfer_return_code.rs @@ -33,7 +33,7 @@ pub extern "C" fn call() { &[0u8; 20], 0, 0, - None, + &[u8::MAX; 32], &u256_bytes(100u64), &[], None, diff --git a/substrate/frame/revive/fixtures/src/lib.rs b/substrate/frame/revive/fixtures/src/lib.rs index 38171edf1152..7685253d1ea2 100644 --- a/substrate/frame/revive/fixtures/src/lib.rs +++ b/substrate/frame/revive/fixtures/src/lib.rs @@ -22,7 +22,7 @@ extern crate alloc; // generated file that tells us where to find the fixtures include!(concat!(env!("OUT_DIR"), "/fixture_location.rs")); -/// Load a given wasm module and returns a wasm binary contents along with it's hash. +/// Load a given wasm module and returns a wasm binary contents along with its hash. #[cfg(feature = "std")] pub fn compile_module(fixture_name: &str) -> anyhow::Result<(Vec, sp_core::H256)> { let out_dir: std::path::PathBuf = FIXTURE_DIR.into(); diff --git a/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/FlipperCaller.polkavm index 585fbb392a314c15a35e7e529106738bde3be02a..38a1098fe3a767aa0af74764bf7247e59f6110b7 100644 GIT binary patch delta 1279 zcmY+CT}&KR6vy}8S>|h5m|e@38e(^sVY0h1Ep^(6SQDhH>}!P9i#ocF8;1We z1&cCkvXmJzsF9js>R6z3I3zPIG8J`_;T2Ml#D|l+*Vkt?i@5 ztCMe2THB{bUsz&LrtQSs>Z>1^N4Waj-Ek8*CVpF;+&yM4{tY!X(1&!Z#7oxDXw^P}Z0q$vizVG>x-;7qDqe!i}q7 zm%$dm=CRCrExkz<#HCvrQwyZwO;;*`%b}E1!eK?Kr8X2va2M8HJk&-?3RATspl2E) zrAUqLag5p+(LI!@H&G+{JT~?Hlw}B}(LzO2Qj+qTMhXHY2((jB6^ch0<`(8zbZ~pP zNPcCW@V!P`hT2uT^QlWuG#O&wM~{&@uATnvZ;h$hAfMB{ z4&4TN1n}5}cNf-OJlKWDEWjgxhaxn($1&`}16vaYcm(kH zKX?r6*kyf;y%cCMIlY&3 z_}VIF{?eE$7bkoPw9Q%D(r+1D(U|cytG|bQM#by;*YY{&BPgpvYa@i7TXXgLQgiRI zWRdP7S^timbAM{gPi2>t3(L$suuq?b$d_c=-*M<@8E`gXkCg9&P3?l5XWb~k!f5^v zi|@c&ZhAk<5ua2gUSs$L5x0Misix!wacG&pGmQ^}-MvDcC|Z8jM7LF=By0F&T{p zf=b5ecg~3MMEon;o)X`g61CImOk7cd#pGT`P@Gv&fuvwmam1~sAOstNMcWpWd>~Q~ zqOG=_D%f_tdq)f;lCz2}Dw%YrGbJjb<_Ja5wArD}54w_fgc|Pd?%#Adj+1H?l|)<# zHbl0v-JFcGEjn|Lc(mvgQeyE`QA$Jp*)x*jr0?culug+wF~T3@BL(q+_*(^`byyU~ MKxn~9Ll-^&0#ACHtN;K2 delta 1201 zcmY+CT}&KR6vy}8ncX|HA26&fC8pA0xJ-5l*6L^^*2Z-+Ei-FLW=5dI57KJu43x+Y zMt8T3p(sh0Ua-Hyiz1{>|>!WCTkm+%(FY#BP8PQ0eS^uTv0efs9##K&|iLXj{5)@MBxuYW`*?g7YQ||-kirLfusPhu3EG$;H-f3XJ*%#53}-|O9b3(fXB@+|HP=ZP z!V^zXjk!t(mbOJY)Ap>ReS8|_9Fd`3C}WG{WT?si^S>tg^TM~GdLT^R+VoF+xNM?N z35Yxx?Z1`FtPf5zgk%RTr%57`*|qSGiIz(q880g6F0=!eY3DccQ>3}$of18K9qp~s zJ!sksRP(CWP1_0jOKqVg;09U(f~=Ex?{um}hclcNEiO^YWwYLw8HSL0fNpk2Z2w%_Rs7()9MQZ8KX z%X2j$Pu3IfuzM?|c>vjDj>1@#|6mWFV!8HZksuw(wTEH~f zc=#He!ci*EZbX1)XCS|%wk2mYoVpP?WxS;NrBpZ6PTl%`X^}H|;Yl#mC Wh2pkok^_;#NP;SAo~e&x*7rX_jeaBm diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm index 3f96fdfc21d8d8fc5bf68a34f0c9f7b055afa2a4..d0082db90e5e398832e4a32a9ec86dce83d16dd5 100644 GIT binary patch delta 1052 zcmY*WZA@EL7{2Gw_S}z_-8R4wr?xkz7m8*QJKg?h8rF;+;@s^O6m*LyGwlzeYnDOL zOo#y~Rt-HbnCv4NHkbfe6h#-~4>NTxL}MhzD1Ic?s0qd;$P$NhVR$Yg!{j{Y&3VrA zydURWtS!}gtcUhXQmNosZhy+v`FK~?$T831Cy#VJ-Rt3B8+hcoLsGZ&taL^y!@F=Z zK8XAAW%ru9lN8BOIiqmpBjpzLpn9*?p{;5Ud&9m*eUrY{^5ycEw^isn^#l4b{rIef z9>d|_YDQncnaX*bshY$2$`XFYUpibBC9$X+wDiE>YLcwLS7p1=EO=ct>SCGQl)H4| zV{pSV+2w6_XH#uGJr8boS#Ee+sfBs619iYVE*f~rlWdyt{XDjZ-Ep;GM(;|k)sx8M)5*ViU!OCTW-gP43bib1y=MnyO+ z--kNkd-;Q#iYx2GwQhF>{+(n8U$Mz=L;|l?4WBBvqkg!e%%V}4R)gp$e5T$R+IuBQ z-o4(FwxR?@6{;%E%%aAOz@)%9+^kVF4l%9Lv-oF{JaQdglkb2b&4;F7Oe>)|==K`u zbvWTYjbymT*N)zR5#Q&#OP7dP)|z2*>vpW&}~=i z>aYFx;df2LX!-r<5d2hr7R|sb+fJbhsMk-SEL_xIz{^YUfWO-F%~F!Ezu--^2FK1p z&VQ@^<5HAZdgFH@AWM&b3FrJ5`Wj__C?2PV@kGsVE;m8PCTOxi!&4?>#$Zlnte&x= zF-9XSM_D+`iZsqgZB7|?uw^ydEjhn`i=Oiq*ec)^278{y8-yzo=lkL`%=n9a6K06h zu~9nAb!In5{+Cj?*DFyL;?t&5pi|8o30ru8@i?cUO$#4L+~{asHop(7 z^xrD%frv=7StiLE2PmSEa6!mK!p#f8RkDD=ZZIqmv Iuuw?)2l-oAiU0rr delta 1068 zcmY*XZ)h839KPS%OYTpa^e)h5|8Qx1b8REIvCCKmDKXsWy-&LYj{V!UA`ZEN1Ilf0!pWHNO@r< z1<#>yY$dB)K-uPLlx>+n<>omwq*nV{bRnjT!Ia`ZzLFNI^oNFBaEdnao22g>tnSjE zAAa>3-Nx_S9CzjIo?LEkfPc(Iv>rsX6obhSqXj++G5RTw{ja*y-P7J*ugvpSck=a3 zkABJ1?R?Zz;@5_8fp)mQgY)#3YZNxpvitMGOGp=VF{QM)|49pnk*O8I)XsodwquaB zD?qeqsF~UYs1a=fYGu2Z(X)`ZQ(1eCNf+4TWhmQ4wtfqP_8E|~c8@HACHzs3wS?c? zMBBiMDhwh6hsuHKP_EjSC4mjJ%(uHZ)PDKjdQaa<2M3Bsmuk8vs0*?O?*1HHqc3{8V2+;iRN-ej z+!%scI^B2*w$h|`KYUF;@h9!a&{H+nb=1s zw%%%w+`j9)W1dd=_P{IjrtchlL&uv=!Xxwv2ZtH%yT&RL%EO`i^f)}JJ)r3@9G;YV*_2hn!zDA} z;AWDH7chx*5P!JUsf<|+(PHXL)Eac~ij*JW!W!!WR_j^b7) Pdfaq^l8Pstc%1tmWiL>e diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index e67c39ec0899..1796348ff321 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -1648,8 +1648,8 @@ mod benchmarks { memory.as_mut_slice(), CallFlags::CLONE_INPUT.bits(), // flags 0, // callee_ptr - 0, // ref_time_limit - 0, // proof_size_limit + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit callee_len, // deposit_ptr callee_len + deposit_len, // value_ptr 0, // input_data_ptr @@ -1688,8 +1688,8 @@ mod benchmarks { memory.as_mut_slice(), 0, // flags 0, // address_ptr - 0, // ref_time_limit - 0, // proof_size_limit + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit address_len, // deposit_ptr 0, // input_data_ptr 0, // input_data_len @@ -1715,7 +1715,7 @@ mod benchmarks { let value_bytes = Into::::into(value).encode(); let value_len = value_bytes.len() as u32; - let deposit: BalanceOf = 0u32.into(); + let deposit: BalanceOf = BalanceOf::::max_value(); let deposit_bytes = Into::::into(deposit).encode(); let deposit_len = deposit_bytes.len() as u32; @@ -1750,8 +1750,8 @@ mod benchmarks { result = runtime.bench_instantiate( memory.as_mut_slice(), 0, // code_hash_ptr - 0, // ref_time_limit - 0, // proof_size_limit + u64::MAX, // ref_time_limit + u64::MAX, // proof_size_limit offset(hash_len), // deposit_ptr offset(deposit_len), // value_ptr offset(value_len), // input_data_ptr diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 478e96dc994d..c069216d6cc7 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -53,7 +53,7 @@ use sp_core::{ }; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; use sp_runtime::{ - traits::{BadOrigin, Convert, Dispatchable, Saturating, Zero}, + traits::{BadOrigin, Bounded, Convert, Dispatchable, Saturating, Zero}, DispatchError, SaturatedConversion, }; @@ -885,9 +885,9 @@ where args, value, gas_meter, - Weight::zero(), + Weight::max_value(), storage_meter, - BalanceOf::::zero(), + BalanceOf::::max_value(), false, true, )? @@ -1117,25 +1117,15 @@ where return Ok(output); } - // Storage limit is normally enforced as late as possible (when the last frame returns) - // so that the ordering of storage accesses does not matter. - // (However, if a special limit was set for a sub-call, it should be enforced right - // after the sub-call returned. See below for this case of enforcement). - if self.frames.is_empty() { - let frame = &mut self.first_frame; - frame.contract_info.load(&frame.account_id); - let contract = frame.contract_info.as_contract(); - frame.nested_storage.enforce_limit(contract)?; - } - let frame = self.top_frame_mut(); - // If a special limit was set for the sub-call, we enforce it here. - // The sub-call will be rolled back in case the limit is exhausted. + // The storage deposit is only charged at the end of every call stack. + // To make sure that no sub call uses more than it is allowed to, + // the limit is manually enforced here. let contract = frame.contract_info.as_contract(); frame .nested_storage - .enforce_subcall_limit(contract) + .enforce_limit(contract) .map_err(|e| ExecError { error: e, origin: ErrorOrigin::Callee })?; let account_id = T::AddressMapper::to_address(&frame.account_id); @@ -1463,7 +1453,7 @@ where FrameArgs::Call { dest: dest.clone(), cached_info, delegated_call: None }, value, gas_limit, - deposit_limit.try_into().map_err(|_| Error::::BalanceConversionFailed)?, + deposit_limit.saturated_into::>(), // Enable read-only access if requested; cannot disable it if already set. read_only || self.is_read_only(), )? { @@ -1519,7 +1509,7 @@ where }, value, gas_limit, - deposit_limit.try_into().map_err(|_| Error::::BalanceConversionFailed)?, + deposit_limit.saturated_into::>(), self.is_read_only(), )?; self.run(executable.expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE), input_data) @@ -1549,7 +1539,7 @@ where }, value.try_into().map_err(|_| Error::::BalanceConversionFailed)?, gas_limit, - deposit_limit.try_into().map_err(|_| Error::::BalanceConversionFailed)?, + deposit_limit.saturated_into::>(), self.is_read_only(), )?; let address = T::AddressMapper::to_address(&self.top_frame().account_id); @@ -3098,8 +3088,8 @@ mod tests { let (address, output) = ctx .ext .instantiate( - Weight::zero(), - U256::zero(), + Weight::MAX, + U256::MAX, dummy_ch, ::Currency::minimum_balance().into(), vec![], @@ -3802,8 +3792,8 @@ mod tests { let succ_fail_code = MockLoader::insert(Constructor, move |ctx, _| { ctx.ext .instantiate( - Weight::zero(), - U256::zero(), + Weight::MAX, + U256::MAX, fail_code, ctx.ext.minimum_balance() * 100, vec![], @@ -3819,8 +3809,8 @@ mod tests { let addr = ctx .ext .instantiate( - Weight::zero(), - U256::zero(), + Weight::MAX, + U256::MAX, success_code, ctx.ext.minimum_balance() * 100, vec![], @@ -4597,7 +4587,7 @@ mod tests { // Successful instantiation should set the output let address = ctx .ext - .instantiate(Weight::zero(), U256::zero(), ok_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, ok_ch, value, vec![], None) .unwrap(); assert_eq!( ctx.ext.last_frame_output(), @@ -4606,15 +4596,7 @@ mod tests { // Balance transfers should reset the output ctx.ext - .call( - Weight::zero(), - U256::zero(), - &address, - U256::from(1), - vec![], - true, - false, - ) + .call(Weight::MAX, U256::MAX, &address, U256::from(1), vec![], true, false) .unwrap(); assert_eq!(ctx.ext.last_frame_output(), &Default::default()); @@ -4827,7 +4809,7 @@ mod tests { // Constructors can not access the immutable data ctx.ext - .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, dummy_ch, value, vec![], None) .unwrap(); exec_success() @@ -4944,7 +4926,7 @@ mod tests { move |ctx, _| { let value = ::Currency::minimum_balance().into(); ctx.ext - .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, dummy_ch, value, vec![], None) .unwrap(); exec_success() @@ -4989,7 +4971,7 @@ mod tests { move |ctx, _| { let value = ::Currency::minimum_balance().into(); ctx.ext - .instantiate(Weight::zero(), U256::zero(), dummy_ch, value, vec![], None) + .instantiate(Weight::MAX, U256::MAX, dummy_ch, value, vec![], None) .unwrap(); exec_success() diff --git a/substrate/frame/revive/src/gas.rs b/substrate/frame/revive/src/gas.rs index 5c30a0a51009..e8338db12192 100644 --- a/substrate/frame/revive/src/gas.rs +++ b/substrate/frame/revive/src/gas.rs @@ -22,7 +22,7 @@ use frame_support::{ weights::Weight, DefaultNoBound, }; -use sp_runtime::{traits::Zero, DispatchError}; +use sp_runtime::DispatchError; #[cfg(test)] use std::{any::Any, fmt::Debug}; @@ -168,25 +168,19 @@ impl GasMeter { } } - /// Create a new gas meter by removing gas from the current meter. + /// Create a new gas meter by removing *all* the gas from the current meter. /// - /// # Note - /// - /// Passing `0` as amount is interpreted as "all remaining gas". + /// This should only be used by the primordial frame in a sequence of calls - every subsequent + /// frame should use [`nested`](Self::nested). + pub fn nested_take_all(&mut self) -> Self { + let gas_left = self.gas_left; + self.gas_left -= gas_left; + GasMeter::new(gas_left) + } + + /// Create a new gas meter for a nested call by removing gas from the current meter. pub fn nested(&mut self, amount: Weight) -> Self { - let amount = Weight::from_parts( - if amount.ref_time().is_zero() { - self.gas_left().ref_time() - } else { - amount.ref_time() - }, - if amount.proof_size().is_zero() { - self.gas_left().proof_size() - } else { - amount.proof_size() - }, - ) - .min(self.gas_left); + let amount = amount.min(self.gas_left); self.gas_left -= amount; GasMeter::new(amount) } @@ -392,6 +386,50 @@ mod tests { assert!(gas_meter.charge(SimpleToken(1)).is_err()); } + /// Previously, passing a `Weight` of 0 to `nested` would consume all of the meter's current + /// gas. + /// + /// Now, a `Weight` of 0 means no gas for the nested call. + #[test] + fn nested_zero_gas_requested() { + let test_weight = 50000.into(); + let mut gas_meter = GasMeter::::new(test_weight); + let gas_for_nested_call = gas_meter.nested(0.into()); + + assert_eq!(gas_meter.gas_left(), 50000.into()); + assert_eq!(gas_for_nested_call.gas_left(), 0.into()) + } + + #[test] + fn nested_some_gas_requested() { + let test_weight = 50000.into(); + let mut gas_meter = GasMeter::::new(test_weight); + let gas_for_nested_call = gas_meter.nested(10000.into()); + + assert_eq!(gas_meter.gas_left(), 40000.into()); + assert_eq!(gas_for_nested_call.gas_left(), 10000.into()) + } + + #[test] + fn nested_all_gas_requested() { + let test_weight = Weight::from_parts(50000, 50000); + let mut gas_meter = GasMeter::::new(test_weight); + let gas_for_nested_call = gas_meter.nested(test_weight); + + assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0)); + assert_eq!(gas_for_nested_call.gas_left(), 50_000.into()) + } + + #[test] + fn nested_excess_gas_requested() { + let test_weight = Weight::from_parts(50000, 50000); + let mut gas_meter = GasMeter::::new(test_weight); + let gas_for_nested_call = gas_meter.nested(test_weight + 10000.into()); + + assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0)); + assert_eq!(gas_for_nested_call.gas_left(), 50_000.into()) + } + // Make sure that the gas meter does not charge in case of overcharge #[test] fn overcharge_does_not_charge() { diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index a7127f812b4b..452d2c8a3067 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -72,7 +72,7 @@ pub struct ContractResult { /// /// # Note /// - /// This can only different from [`Self::gas_consumed`] when weight pre charging + /// This can only be different from [`Self::gas_consumed`] when weight pre charging /// is used. Currently, only `seal_call_runtime` makes use of pre charging. /// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging /// when a non-zero `gas_limit` argument is supplied. diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index 6eddf048be98..4febcb0c4066 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -101,12 +101,8 @@ pub struct Root; /// State parameter that constitutes a meter that is in its nested state. /// Its value indicates whether the nested meter has its own limit. -#[derive(DefaultNoBound, RuntimeDebugNoBound)] -pub enum Nested { - #[default] - DerivedLimit, - OwnLimit, -} +#[derive(Default, Debug)] +pub struct Nested; impl State for Root {} impl State for Nested {} @@ -125,10 +121,8 @@ pub struct RawMeter { /// We only have one charge per contract hence the size of this vector is /// limited by the maximum call depth. charges: Vec>, - /// We store the nested state to determine if it has a special limit for sub-call. - nested: S, /// Type parameter only used in impls. - _phantom: PhantomData, + _phantom: PhantomData<(E, S)>, } /// This type is used to describe a storage change when charging from the meter. @@ -281,21 +275,14 @@ where S: State + Default + Debug, { /// Create a new child that has its `limit`. - /// Passing `0` as the limit is interpreted as to take whatever is remaining from its parent. /// /// This is called whenever a new subcall is initiated in order to track the storage /// usage for this sub call separately. This is necessary because we want to exchange balance /// with the current contract we are interacting with. pub fn nested(&self, limit: BalanceOf) -> RawMeter { debug_assert!(matches!(self.contract_state(), ContractState::Alive)); - // If a special limit is specified higher than it is available, - // we want to enforce the lesser limit to the nested meter, to fail in the sub-call. - let limit = self.available().min(limit); - if limit.is_zero() { - RawMeter { limit: self.available(), ..Default::default() } - } else { - RawMeter { limit, nested: Nested::OwnLimit, ..Default::default() } - } + + RawMeter { limit: self.available().min(limit), ..Default::default() } } /// Absorb a child that was spawned to handle a sub call. @@ -477,13 +464,6 @@ impl> RawMeter { /// [`Self::charge`] does not enforce the storage limit since we want to do this check as late /// as possible to allow later refunds to offset earlier charges. - /// - /// # Note - /// - /// We normally need to call this **once** for every call stack and not for every cross contract - /// call. However, if a dedicated limit is specified for a sub-call, this needs to be called - /// once the sub-call has returned. For this, the [`Self::enforce_subcall_limit`] wrapper is - /// used. pub fn enforce_limit( &mut self, info: Option<&mut ContractInfo>, @@ -502,18 +482,6 @@ impl> RawMeter { } Ok(()) } - - /// This is a wrapper around [`Self::enforce_limit`] to use on the exit from a sub-call to - /// enforce its special limit if needed. - pub fn enforce_subcall_limit( - &mut self, - info: Option<&mut ContractInfo>, - ) -> Result<(), DispatchError> { - match self.nested { - Nested::OwnLimit => self.enforce_limit(info), - Nested::DerivedLimit => Ok(()), - } - } } impl Ext for ReservingExt { @@ -724,6 +692,49 @@ mod tests { ) } + /// Previously, passing a limit of 0 meant unlimited storage for a nested call. + /// + /// Now, a limit of 0 means the subcall will not be able to use any storage. + #[test] + fn nested_zero_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(BalanceOf::::zero()); + assert_eq!(nested0.available(), 0); + } + + #[test] + fn nested_some_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(500); + assert_eq!(nested0.available(), 500); + } + + #[test] + fn nested_all_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(1_000); + assert_eq!(nested0.available(), 1_000); + } + + #[test] + fn nested_over_limit_requested() { + clear_ext(); + + let meter = TestMeter::new(&Origin::from_account_id(ALICE), 1_000, 0).unwrap(); + assert_eq!(meter.available(), 1_000); + let nested0 = meter.nested(2_000); + assert_eq!(nested0.available(), 1_000); + } + #[test] fn empty_charge_works() { clear_ext(); @@ -879,7 +890,7 @@ mod tests { let mut meter = TestMeter::new(&test_case.origin, 1_000, 0).unwrap(); assert_eq!(meter.available(), 1_000); - let mut nested0 = meter.nested(BalanceOf::::zero()); + let mut nested0 = meter.nested(BalanceOf::::max_value()); nested0.charge(&Diff { bytes_added: 5, bytes_removed: 1, @@ -895,7 +906,7 @@ mod tests { items_deposit: 20, immutable_data_len: 0, }); - let mut nested1 = nested0.nested(BalanceOf::::zero()); + let mut nested1 = nested0.nested(BalanceOf::::max_value()); nested1.charge(&Diff { items_removed: 5, ..Default::default() }); nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); nested1.terminate(&nested1_info, CHARLIE); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 664578bf7672..cf02d17a4d03 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -1149,7 +1149,7 @@ fn delegate_call() { assert_ok!(builder::call(caller_addr) .value(1337) - .data((callee_addr, 0u64, 0u64).encode()) + .data((callee_addr, u64::MAX, u64::MAX).encode()) .build()); }); } @@ -2261,12 +2261,12 @@ fn gas_estimation_for_subcalls() { // Run the test for all of those weight limits for the subcall let weights = [ - Weight::zero(), + Weight::MAX, GAS_LIMIT, GAS_LIMIT * 2, GAS_LIMIT / 5, - Weight::from_parts(0, GAS_LIMIT.proof_size()), - Weight::from_parts(GAS_LIMIT.ref_time(), 0), + Weight::from_parts(u64::MAX, GAS_LIMIT.proof_size()), + Weight::from_parts(GAS_LIMIT.ref_time(), u64::MAX), ]; // This call is passed to the sub call in order to create a large `required_weight` @@ -3453,13 +3453,13 @@ fn deposit_limit_in_nested_calls() { // We do not remove any storage but add a storage item of 12 bytes in the caller // contract. This would cost 12 + 2 = 14 Balance. - // The nested call doesn't get a special limit, which is set by passing 0 to it. + // The nested call doesn't get a special limit, which is set by passing `u64::MAX` to it. // This should fail as the specified parent's limit is less than the cost: 13 < // 14. assert_err_ignore_postinfo!( builder::call(addr_caller) .storage_deposit_limit(13) - .data((100u32, &addr_callee, U256::from(0u64)).encode()) + .data((100u32, &addr_callee, U256::MAX).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3467,13 +3467,13 @@ fn deposit_limit_in_nested_calls() { // Now we specify the parent's limit high enough to cover the caller's storage // additions. However, we use a single byte more in the callee, hence the storage // deposit should be 15 Balance. - // The nested call doesn't get a special limit, which is set by passing 0 to it. + // The nested call doesn't get a special limit, which is set by passing `u64::MAX` to it. // This should fail as the specified parent's limit is less than the cost: 14 // < 15. assert_err_ignore_postinfo!( builder::call(addr_caller) .storage_deposit_limit(14) - .data((101u32, &addr_callee, U256::from(0u64)).encode()) + .data((101u32, &addr_callee, &U256::MAX).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3495,7 +3495,7 @@ fn deposit_limit_in_nested_calls() { assert_err_ignore_postinfo!( builder::call(addr_caller) .storage_deposit_limit(0) - .data((87u32, &addr_callee, U256::from(0u64)).encode()) + .data((87u32, &addr_callee, &U256::MAX.to_little_endian()).encode()) .build(), >::StorageDepositLimitExhausted, ); @@ -3551,28 +3551,24 @@ fn deposit_limit_in_nested_instantiate() { // // Provided the limit is set to be 1 Balance less, // this call should fail on the return from the caller contract. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 1) - .data((0u32, &code_hash_callee, U256::from(0u64)).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(callee_info_len + 2 + ED + 1)) + .data((0u32, &code_hash_callee, &U256::MAX.to_little_endian()).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on instantiation should be rolled back. assert_eq!(::Currency::free_balance(&BOB), 1_000_000); // Now we give enough limit for the instantiation itself, but require for 1 more storage // byte in the constructor. Hence +1 Balance to the limit is needed. This should fail on // the return from constructor. - assert_err_ignore_postinfo!( - builder::call(addr_caller) - .origin(RuntimeOrigin::signed(BOB)) - .storage_deposit_limit(callee_info_len + 2 + ED + 2) - .data((1u32, &code_hash_callee, U256::from(0u64)).encode()) - .build(), - >::StorageDepositLimitExhausted, - ); + let ret = builder::bare_call(addr_caller) + .origin(RuntimeOrigin::signed(BOB)) + .storage_deposit_limit(DepositLimit::Balance(callee_info_len + 2 + ED + 2)) + .data((1u32, &code_hash_callee, U256::from(0u64)).encode()) + .build_and_unwrap_result(); + assert_return_code!(ret, RuntimeReturnCode::OutOfResources); // The charges made on the instantiation should be rolled back. assert_eq!(::Currency::free_balance(&BOB), 1_000_000); @@ -4856,20 +4852,18 @@ fn skip_transfer_works() { ); // fails when calling from a contract when gas is specified. - assert_err!( - Pallet::::bare_eth_transact( - GenericTransaction { - from: Some(BOB_ADDR), - to: Some(caller_addr), - input: Some((0u32, &addr).encode().into()), - gas: Some(1u32.into()), - ..Default::default() - }, - Weight::MAX, - |_| 0u32 - ), - EthTransactError::Message(format!("insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)")) - ); + assert!(Pallet::::bare_eth_transact( + GenericTransaction { + from: Some(BOB_ADDR), + to: Some(caller_addr), + input: Some((0u32, &addr).encode().into()), + gas: Some(1u32.into()), + ..Default::default() + }, + Weight::MAX, + |_| 0u32 + ) + .is_err(),); // works when no gas is specified. assert_ok!(Pallet::::bare_eth_transact( diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 52f79f2eb55a..8529c7d9e73b 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -1004,8 +1004,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { self.charge_gas(call_type.cost())?; let callee = memory.read_h160(callee_ptr)?; - let deposit_limit = - if deposit_ptr == SENTINEL { U256::zero() } else { memory.read_u256(deposit_ptr)? }; + let deposit_limit = memory.read_u256(deposit_ptr)?; let input_data = if flags.contains(CallFlags::CLONE_INPUT) { let input = self.input_data.as_ref().ok_or(Error::::InputForwarded)?; @@ -1091,8 +1090,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { salt_ptr: u32, ) -> Result { self.charge_gas(RuntimeCosts::Instantiate { input_data_len })?; - let deposit_limit: U256 = - if deposit_ptr == SENTINEL { U256::zero() } else { memory.read_u256(deposit_ptr)? }; + let deposit_limit: U256 = memory.read_u256(deposit_ptr)?; let value = memory.read_u256(value_ptr)?; let code_hash = memory.read_h256(code_hash_ptr)?; let input_data = memory.read(input_data_ptr, input_data_len)?; diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index d90c0f45205d..ba0a63b15c37 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -113,7 +113,7 @@ pub trait HostFn: private::Sealed { callee: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit: Option<&[u8; 32]>, + deposit: &[u8; 32], value: &[u8; 32], input_data: &[u8], output: Option<&mut &mut [u8]>, @@ -202,7 +202,7 @@ pub trait HostFn: private::Sealed { address: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], input_data: &[u8], output: Option<&mut &mut [u8]>, ) -> Result; @@ -318,7 +318,7 @@ pub trait HostFn: private::Sealed { code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, - deposit: Option<&[u8; 32]>, + deposit: &[u8; 32], value: &[u8; 32], input: &[u8], address: Option<&mut [u8; 20]>, diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index c83be942a970..8c40bc9f48ea 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -168,7 +168,7 @@ impl HostFn for HostFnImpl { code_hash: &[u8; 32], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], value: &[u8; 32], input: &[u8], mut address: Option<&mut [u8; 20]>, @@ -180,7 +180,7 @@ impl HostFn for HostFnImpl { None => crate::SENTINEL as _, }; let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); - let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + let deposit_limit_ptr = deposit_limit.as_ptr(); let salt_ptr = ptr_or_sentinel(&salt); #[repr(C)] #[allow(dead_code)] @@ -225,13 +225,13 @@ impl HostFn for HostFnImpl { callee: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], value: &[u8; 32], input: &[u8], mut output: Option<&mut &mut [u8]>, ) -> Result { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); - let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + let deposit_limit_ptr = deposit_limit.as_ptr(); #[repr(C)] #[allow(dead_code)] struct Args { @@ -273,12 +273,12 @@ impl HostFn for HostFnImpl { address: &[u8; 20], ref_time_limit: u64, proof_size_limit: u64, - deposit_limit: Option<&[u8; 32]>, + deposit_limit: &[u8; 32], input: &[u8], mut output: Option<&mut &mut [u8]>, ) -> Result { let (output_ptr, mut output_len) = ptr_len_or_sentinel(&mut output); - let deposit_limit_ptr = ptr_or_sentinel(&deposit_limit); + let deposit_limit_ptr = deposit_limit.as_ptr(); #[repr(C)] #[allow(dead_code)] struct Args { From d822e07d51dda41982291dc6582a8c4a34821e94 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Wed, 15 Jan 2025 14:48:38 +0100 Subject: [PATCH 268/340] [pallet-revive] Bump asset-hub westend spec version (#7176) Bump asset-hub westend spec version --------- Co-authored-by: command-bot <> --- .../assets/asset-hub-westend/src/lib.rs | 2 +- prdoc/pr_7176.prdoc | 9 +++++++++ .../frame/revive/rpc/revive_chain.metadata | Bin 661594 -> 661585 bytes 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 prdoc/pr_7176.prdoc diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index cfc150ce5d6f..7844b0d885ec 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -125,7 +125,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_017_003, + spec_version: 1_017_004, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, diff --git a/prdoc/pr_7176.prdoc b/prdoc/pr_7176.prdoc new file mode 100644 index 000000000000..b78f42014afe --- /dev/null +++ b/prdoc/pr_7176.prdoc @@ -0,0 +1,9 @@ +title: '[pallet-revive] Bump asset-hub westend spec version' +doc: +- audience: Runtime Dev + description: Bump asset-hub westend spec version +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index 402e8c2d22b21471929e9c61acd2cc968af614cf..a03c95b4944f663225642b1678ef66aaccec3fb5 100644 GIT binary patch delta 92 zcmcb$LF3{EjSX``7+ojN4N+-68q$6=gmL@P5T;BYM&9ZA3z{(Be4WXq_(SEW&&bnAZ7t#Rv=~rVs;?r*sgM!)65kB;58&I delta 100 zcmcb(LF3j2jSX``7(*w|4N+mvNGxtX5Ym1igmL?U5T-02M#<^g3zWLjq^ vbi_5UxU?u$p(G=*1W2SRB(_UmW&&bnAZ7t#Rv=~rVs;?r*e-pU)65kB*&QY! From 77c78e1561bbe5ee0ecf414312bae82396ae6d11 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:50:42 +0200 Subject: [PATCH 269/340] litep2p: Provide partial results to speedup GetRecord queries (#7099) This PR provides the partial results of the `GetRecord` kademlia query. This significantly improves the authority discovery records, from ~37 minutes to ~2/3 minutes. In contrast, libp2p discovers authority records in around ~10 minutes. The authority discovery was slow because litep2p provided the records only after the Kademlia query was completed. A normal Kademlia query completes in around 40 seconds to a few minutes. In this PR, partial records are provided as soon as they are discovered from the network. ### Testing Done Started a node in Kusama with `--validator` and litep2p backend. The node discovered 996/1000 authority records in ~ 1 minute 45 seconds. ![Screenshot 2025-01-09 at 12 26 08](https://github.com/user-attachments/assets/b618bf7c-2bba-43a0-a021-4047e854c075) ### Before & After In this image, on the left side is libp2p, in the middle litep2p without this PR, on the right litep2p with this PR ![Screenshot 2025-01-07 at 17 57 56](https://github.com/user-attachments/assets/a8d467f7-8dc7-461c-bcff-163b94d01ae8) Closes: https://github.com/paritytech/polkadot-sdk/issues/7077 cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile --- Cargo.lock | 4 +- Cargo.toml | 2 +- prdoc/pr_7099.prdoc | 16 ++++ .../client/network/src/litep2p/discovery.rs | 33 +++++-- substrate/client/network/src/litep2p/mod.rs | 87 ++++++++----------- 5 files changed, 79 insertions(+), 63 deletions(-) create mode 100644 prdoc/pr_7099.prdoc diff --git a/Cargo.lock b/Cargo.lock index 7725db743c41..0d71a770d38b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10446,9 +10446,9 @@ dependencies = [ [[package]] name = "litep2p" -version = "0.8.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b0fef34af8847e816003bf7fdeac5ea50b9a7a88441ac927a6166b5e812ab79" +checksum = "6ca6ee50a125dc4fc4e9a3ae3640010796d1d07bc517a0ac715fdf0b24a0b6ac" dependencies = [ "async-trait", "bs58", diff --git a/Cargo.toml b/Cargo.toml index c30a9949e85e..eb99b80e16fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -850,7 +850,7 @@ linked-hash-map = { version = "0.5.4" } linked_hash_set = { version = "0.1.4" } linregress = { version = "0.5.1" } lite-json = { version = "0.2.0", default-features = false } -litep2p = { version = "0.8.4", features = ["websocket"] } +litep2p = { version = "0.9.0", features = ["websocket"] } log = { version = "0.4.22", default-features = false } macro_magic = { version = "0.5.1" } maplit = { version = "1.0.2" } diff --git a/prdoc/pr_7099.prdoc b/prdoc/pr_7099.prdoc new file mode 100644 index 000000000000..58d809f3c090 --- /dev/null +++ b/prdoc/pr_7099.prdoc @@ -0,0 +1,16 @@ +title: Provide partial results to speedup GetRecord queries + +doc: + - audience: Node Dev + description: | + This PR provides the partial results of the GetRecord kademlia query. + + This significantly improves the authority discovery records, from ~37 minutes to ~2/3 minutes. + In contrast, libp2p discovers authority records in around ~10 minutes. + + The authority discovery was slow because litep2p provided the records only after the Kademlia query was completed. A normal Kademlia query completes in around 40 seconds to a few minutes. + In this PR, partial records are provided as soon as they are discovered from the network. + +crates: + - name: sc-network + bump: patch diff --git a/substrate/client/network/src/litep2p/discovery.rs b/substrate/client/network/src/litep2p/discovery.rs index b55df374f60e..eb571804f30e 100644 --- a/substrate/client/network/src/litep2p/discovery.rs +++ b/substrate/client/network/src/litep2p/discovery.rs @@ -33,8 +33,8 @@ use litep2p::{ identify::{Config as IdentifyConfig, IdentifyEvent}, kademlia::{ Config as KademliaConfig, ConfigBuilder as KademliaConfigBuilder, ContentProvider, - IncomingRecordValidationMode, KademliaEvent, KademliaHandle, QueryId, Quorum, - Record, RecordKey, RecordsType, + IncomingRecordValidationMode, KademliaEvent, KademliaHandle, PeerRecord, QueryId, + Quorum, Record, RecordKey, }, ping::{Config as PingConfig, PingEvent}, }, @@ -129,13 +129,19 @@ pub enum DiscoveryEvent { address: Multiaddr, }, - /// Record was found from the DHT. + /// `GetRecord` query succeeded. GetRecordSuccess { /// Query ID. query_id: QueryId, + }, - /// Records. - records: RecordsType, + /// Record was found from the DHT. + GetRecordPartialResult { + /// Query ID. + query_id: QueryId, + + /// Record. + record: PeerRecord, }, /// Record was successfully stored on the DHT. @@ -573,13 +579,24 @@ impl Stream for Discovery { peers: peers.into_iter().collect(), })) }, - Poll::Ready(Some(KademliaEvent::GetRecordSuccess { query_id, records })) => { + Poll::Ready(Some(KademliaEvent::GetRecordSuccess { query_id })) => { log::trace!( target: LOG_TARGET, - "`GET_RECORD` succeeded for {query_id:?}: {records:?}", + "`GET_RECORD` succeeded for {query_id:?}", ); - return Poll::Ready(Some(DiscoveryEvent::GetRecordSuccess { query_id, records })); + return Poll::Ready(Some(DiscoveryEvent::GetRecordSuccess { query_id })); + }, + Poll::Ready(Some(KademliaEvent::GetRecordPartialResult { query_id, record })) => { + log::trace!( + target: LOG_TARGET, + "`GET_RECORD` intermediary succeeded for {query_id:?}: {record:?}", + ); + + return Poll::Ready(Some(DiscoveryEvent::GetRecordPartialResult { + query_id, + record, + })); }, Poll::Ready(Some(KademliaEvent::PutRecordSuccess { query_id, key: _ })) => return Poll::Ready(Some(DiscoveryEvent::PutRecordSuccess { query_id })), diff --git a/substrate/client/network/src/litep2p/mod.rs b/substrate/client/network/src/litep2p/mod.rs index 52b2970525df..fc4cce476283 100644 --- a/substrate/client/network/src/litep2p/mod.rs +++ b/substrate/client/network/src/litep2p/mod.rs @@ -58,7 +58,7 @@ use litep2p::{ protocol::{ libp2p::{ bitswap::Config as BitswapConfig, - kademlia::{QueryId, Record, RecordsType}, + kademlia::{QueryId, Record}, }, request_response::ConfigBuilder as RequestResponseConfigBuilder, }, @@ -836,23 +836,45 @@ impl NetworkBackend for Litep2pNetworkBac self.peerstore_handle.add_known_peer(peer.into()); } } - Some(DiscoveryEvent::GetRecordSuccess { query_id, records }) => { + Some(DiscoveryEvent::GetRecordPartialResult { query_id, record }) => { + if !self.pending_queries.contains_key(&query_id) { + log::error!( + target: LOG_TARGET, + "Missing/invalid pending query for `GET_VALUE` partial result: {query_id:?}" + ); + + continue + } + + let peer_id: sc_network_types::PeerId = record.peer.into(); + let record = PeerRecord { + record: P2PRecord { + key: record.record.key.to_vec().into(), + value: record.record.value, + publisher: record.record.publisher.map(|peer_id| { + let peer_id: sc_network_types::PeerId = peer_id.into(); + peer_id.into() + }), + expires: record.record.expires, + }, + peer: Some(peer_id.into()), + }; + + self.event_streams.send( + Event::Dht( + DhtEvent::ValueFound( + record.into() + ) + ) + ); + } + Some(DiscoveryEvent::GetRecordSuccess { query_id }) => { match self.pending_queries.remove(&query_id) { Some(KadQuery::GetValue(key, started)) => { log::trace!( target: LOG_TARGET, - "`GET_VALUE` for {:?} ({query_id:?}) succeeded", - key, + "`GET_VALUE` for {key:?} ({query_id:?}) succeeded", ); - for record in litep2p_to_libp2p_peer_record(records) { - self.event_streams.send( - Event::Dht( - DhtEvent::ValueFound( - record.into() - ) - ) - ); - } if let Some(ref metrics) = self.metrics { metrics @@ -1165,42 +1187,3 @@ impl NetworkBackend for Litep2pNetworkBac } } } - -// Glue code to convert from a litep2p records type to a libp2p2 PeerRecord. -fn litep2p_to_libp2p_peer_record(records: RecordsType) -> Vec { - match records { - litep2p::protocol::libp2p::kademlia::RecordsType::LocalStore(record) => { - vec![PeerRecord { - record: P2PRecord { - key: record.key.to_vec().into(), - value: record.value, - publisher: record.publisher.map(|peer_id| { - let peer_id: sc_network_types::PeerId = peer_id.into(); - peer_id.into() - }), - expires: record.expires, - }, - peer: None, - }] - }, - litep2p::protocol::libp2p::kademlia::RecordsType::Network(records) => records - .into_iter() - .map(|record| { - let peer_id: sc_network_types::PeerId = record.peer.into(); - - PeerRecord { - record: P2PRecord { - key: record.record.key.to_vec().into(), - value: record.record.value, - publisher: record.record.publisher.map(|peer_id| { - let peer_id: sc_network_types::PeerId = peer_id.into(); - peer_id.into() - }), - expires: record.record.expires, - }, - peer: Some(peer_id.into()), - } - }) - .collect::>(), - } -} From ece32e38a1a37aa354d51b16c07a42c66f23976e Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Wed, 15 Jan 2025 18:37:59 +0100 Subject: [PATCH 270/340] [pallet-revive] Remove debug buffer (#7163) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the `debug_buffer` feature --------- Co-authored-by: command-bot <> Co-authored-by: Cyrill Leutwiler Co-authored-by: Alexander Theißen --- .../assets/asset-hub-westend/src/lib.rs | 15 +- prdoc/pr_7163.prdoc | 13 + substrate/bin/node/runtime/src/lib.rs | 10 +- substrate/frame/revive/README.md | 23 -- .../contracts/debug_message_invalid_utf8.rs | 33 --- .../debug_message_logging_disabled.rs | 33 --- .../fixtures/contracts/debug_message_works.rs | 33 --- substrate/frame/revive/proc-macro/src/lib.rs | 7 +- .../revive/src/benchmarking/call_builder.rs | 15 +- .../frame/revive/src/benchmarking/mod.rs | 28 --- substrate/frame/revive/src/exec.rs | 228 +----------------- substrate/frame/revive/src/lib.rs | 67 +---- substrate/frame/revive/src/limits.rs | 5 - substrate/frame/revive/src/primitives.rs | 53 +--- .../frame/revive/src/test_utils/builder.rs | 17 +- substrate/frame/revive/src/tests.rs | 146 +---------- .../frame/revive/src/tests/test_debug.rs | 4 - substrate/frame/revive/src/wasm/mod.rs | 2 +- substrate/frame/revive/src/wasm/runtime.rs | 34 +-- substrate/frame/revive/src/weights.rs | 21 -- substrate/frame/revive/uapi/src/host.rs | 20 -- .../frame/revive/uapi/src/host/riscv64.rs | 7 - substrate/frame/revive/uapi/src/lib.rs | 7 +- 23 files changed, 54 insertions(+), 767 deletions(-) create mode 100644 prdoc/pr_7163.prdoc delete mode 100644 substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs delete mode 100644 substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs delete mode 100644 substrate/frame/revive/fixtures/contracts/debug_message_works.rs diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 7844b0d885ec..5966dd01f18f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -952,11 +952,6 @@ parameter_types! { pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); } -type EventRecord = frame_system::EventRecord< - ::RuntimeEvent, - ::Hash, ->; - impl pallet_revive::Config for Runtime { type Time = Timestamp; type Currency = Balances; @@ -2073,7 +2068,7 @@ impl_runtime_apis! { } } - impl pallet_revive::ReviveApi for Runtime + impl pallet_revive::ReviveApi for Runtime { fn balance(address: H160) -> U256 { Revive::evm_balance(&address) @@ -2108,7 +2103,7 @@ impl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> pallet_revive::ContractResult { + ) -> pallet_revive::ContractResult { let blockweights= ::BlockWeights::get(); Revive::bare_call( RuntimeOrigin::signed(origin), @@ -2117,8 +2112,6 @@ impl_runtime_apis! { gas_limit.unwrap_or(blockweights.max_block), pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -2130,7 +2123,7 @@ impl_runtime_apis! { code: pallet_revive::Code, data: Vec, salt: Option<[u8; 32]>, - ) -> pallet_revive::ContractResult + ) -> pallet_revive::ContractResult { let blockweights= ::BlockWeights::get(); Revive::bare_instantiate( @@ -2141,8 +2134,6 @@ impl_runtime_apis! { code, data, salt, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } diff --git a/prdoc/pr_7163.prdoc b/prdoc/pr_7163.prdoc new file mode 100644 index 000000000000..669c480b835b --- /dev/null +++ b/prdoc/pr_7163.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] Remove debug buffer' +doc: +- audience: Runtime Dev + description: Remove the `debug_buffer` feature +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive + bump: major +- name: pallet-revive-proc-macro + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index e11a009c1c3f..97728f12f5f9 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -3212,7 +3212,7 @@ impl_runtime_apis! { } } - impl pallet_revive::ReviveApi for Runtime + impl pallet_revive::ReviveApi for Runtime { fn balance(address: H160) -> U256 { Revive::evm_balance(&address) @@ -3247,7 +3247,7 @@ impl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> pallet_revive::ContractResult { + ) -> pallet_revive::ContractResult { Revive::bare_call( RuntimeOrigin::signed(origin), dest, @@ -3255,8 +3255,6 @@ impl_runtime_apis! { gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block), pallet_revive::DepositLimit::Balance(storage_deposit_limit.unwrap_or(u128::MAX)), input_data, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } @@ -3268,7 +3266,7 @@ impl_runtime_apis! { code: pallet_revive::Code, data: Vec, salt: Option<[u8; 32]>, - ) -> pallet_revive::ContractResult + ) -> pallet_revive::ContractResult { Revive::bare_instantiate( RuntimeOrigin::signed(origin), @@ -3278,8 +3276,6 @@ impl_runtime_apis! { code, data, salt, - pallet_revive::DebugInfo::UnsafeDebug, - pallet_revive::CollectEvents::UnsafeCollect, ) } diff --git a/substrate/frame/revive/README.md b/substrate/frame/revive/README.md index 575920dfaac7..7538f77d10bc 100644 --- a/substrate/frame/revive/README.md +++ b/substrate/frame/revive/README.md @@ -49,29 +49,6 @@ This module executes PolkaVM smart contracts. These can potentially be written i RISC-V. For now, the only officially supported languages are Solidity (via [`revive`](https://github.com/xermicus/revive)) and Rust (check the `fixtures` directory for Rust examples). -## Debugging - -Contracts can emit messages to the client when called as RPC through the -[`debug_message`](https://paritytech.github.io/substrate/master/pallet_revive/trait.SyscallDocs.html#tymethod.debug_message) -API. - -Those messages are gathered into an internal buffer and sent to the RPC client. It is up to the individual client if -and how those messages are presented to the user. - -This buffer is also printed as a debug message. In order to see these messages on the node console the log level for the -`runtime::revive` target needs to be raised to at least the `debug` level. However, those messages are easy to -overlook because of the noise generated by block production. A good starting point for observing them on the console is -using this command line in the root directory of the Substrate repository: - -```bash -cargo run --release -- --dev -lerror,runtime::revive=debug -``` - -This raises the log level of `runtime::revive` to `debug` and all other targets to `error` in order to prevent them -from spamming the console. - -`--dev`: Use a dev chain spec `--tmp`: Use temporary storage for chain data (the chain state is deleted on exit) - ## Host function tracing For contract authors, it can be a helpful debugging tool to see which host functions are called, with which arguments, diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs b/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs deleted file mode 100644 index 6c850a9ec663..000000000000 --- a/substrate/frame/revive/fixtures/contracts/debug_message_invalid_utf8.rs +++ /dev/null @@ -1,33 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Emit a debug message with an invalid utf-8 code. -#![no_std] -#![no_main] - -extern crate common; -use uapi::{HostFn, HostFnImpl as api}; - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() {} - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn call() { - api::debug_message(b"\xFC").unwrap(); -} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs b/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs deleted file mode 100644 index 0ce2b6b5628d..000000000000 --- a/substrate/frame/revive/fixtures/contracts/debug_message_logging_disabled.rs +++ /dev/null @@ -1,33 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Emit a "Hello World!" debug message but assume that logging is disabled. -#![no_std] -#![no_main] - -extern crate common; -use uapi::{HostFn, HostFnImpl as api, ReturnErrorCode}; - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() {} - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn call() { - assert_eq!(api::debug_message(b"Hello World!"), Err(ReturnErrorCode::LoggingDisabled)); -} diff --git a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs b/substrate/frame/revive/fixtures/contracts/debug_message_works.rs deleted file mode 100644 index 3a2509509d8f..000000000000 --- a/substrate/frame/revive/fixtures/contracts/debug_message_works.rs +++ /dev/null @@ -1,33 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Emit a "Hello World!" debug message. -#![no_std] -#![no_main] - -extern crate common; -use uapi::{HostFn, HostFnImpl as api}; - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn deploy() {} - -#[no_mangle] -#[polkavm_derive::polkavm_export] -pub extern "C" fn call() { - api::debug_message(b"Hello World!").unwrap(); -} diff --git a/substrate/frame/revive/proc-macro/src/lib.rs b/substrate/frame/revive/proc-macro/src/lib.rs index b09bdef14632..6e38063d20a6 100644 --- a/substrate/frame/revive/proc-macro/src/lib.rs +++ b/substrate/frame/revive/proc-macro/src/lib.rs @@ -510,12 +510,7 @@ fn expand_functions(def: &EnvDef) -> TokenStream2 { quote! { // wrap body in closure to make sure the tracing is always executed let result = (|| #body)(); - if ::log::log_enabled!(target: "runtime::revive::strace", ::log::Level::Trace) { - use core::fmt::Write; - let mut msg = alloc::string::String::default(); - let _ = core::write!(&mut msg, #trace_fmt_str, #( #trace_fmt_args, )* result); - self.ext().append_debug_buffer(&msg); - } + ::log::trace!(target: "runtime::revive::strace", #trace_fmt_str, #( #trace_fmt_args, )* result); result } }; diff --git a/substrate/frame/revive/src/benchmarking/call_builder.rs b/substrate/frame/revive/src/benchmarking/call_builder.rs index 1177d47aadc3..077e18ff5f0b 100644 --- a/substrate/frame/revive/src/benchmarking/call_builder.rs +++ b/substrate/frame/revive/src/benchmarking/call_builder.rs @@ -22,7 +22,7 @@ use crate::{ storage::meter::Meter, transient_storage::MeterEntry, wasm::{PreparedCall, Runtime}, - BalanceOf, Config, DebugBuffer, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight, + BalanceOf, Config, Error, GasMeter, MomentOf, Origin, WasmBlob, Weight, }; use alloc::{vec, vec::Vec}; use frame_benchmarking::benchmarking; @@ -38,7 +38,6 @@ pub struct CallSetup { gas_meter: GasMeter, storage_meter: Meter, value: BalanceOf, - debug_message: Option, data: Vec, transient_storage_size: u32, } @@ -91,7 +90,6 @@ where gas_meter: GasMeter::new(Weight::MAX), storage_meter, value: 0u32.into(), - debug_message: None, data: vec![], transient_storage_size: 0, } @@ -122,16 +120,6 @@ where self.transient_storage_size = size; } - /// Set the debug message. - pub fn enable_debug_message(&mut self) { - self.debug_message = Some(Default::default()); - } - - /// Get the debug message. - pub fn debug_message(&self) -> Option { - self.debug_message.clone() - } - /// Get the call's input data. pub fn data(&self) -> Vec { self.data.clone() @@ -150,7 +138,6 @@ where &mut self.gas_meter, &mut self.storage_meter, self.value, - self.debug_message.as_mut(), ); if self.transient_storage_size > 0 { Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap(); diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 1796348ff321..e23554f21ba8 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -107,8 +107,6 @@ where Code::Upload(module.code), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); let address = outcome.result?.addr; @@ -1047,32 +1045,6 @@ mod benchmarks { ); } - // Benchmark debug_message call - // Whereas this function is used in RPC mode only, it still should be secured - // against an excessive use. - // - // i: size of input in bytes up to maximum allowed contract memory or maximum allowed debug - // buffer size, whichever is less. - #[benchmark] - fn seal_debug_message( - i: Linear<0, { (limits::code::BLOB_BYTES).min(limits::DEBUG_BUFFER_BYTES) }>, - ) { - let mut setup = CallSetup::::default(); - setup.enable_debug_message(); - let (mut ext, _) = setup.ext(); - let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, vec![]); - // Fill memory with printable ASCII bytes. - let mut memory = (0..i).zip((32..127).cycle()).map(|i| i.1).collect::>(); - - let result; - #[block] - { - result = runtime.bench_debug_message(memory.as_mut_slice(), 0, i); - } - assert_ok!(result); - assert_eq!(setup.debug_message().unwrap().len() as u32, i); - } - #[benchmark(skip_meta, pov_mode = Measured)] fn get_storage_empty() -> Result<(), BenchmarkError> { let max_key_len = limits::STORAGE_KEY_BYTES; diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index c069216d6cc7..e20c5dd7786e 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -24,8 +24,8 @@ use crate::{ runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, storage::{self, meter::Diff, WriteOutcome}, transient_storage::TransientStorage, - BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, DebugBuffer, Error, - Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, LOG_TARGET, + BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, Error, Event, + ImmutableData, ImmutableDataOf, Pallet as Contracts, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData, mem}; @@ -378,19 +378,6 @@ pub trait Ext: sealing::Sealed { /// Charges `diff` from the meter. fn charge_storage(&mut self, diff: &Diff); - /// Append a string to the debug buffer. - /// - /// It is added as-is without any additional new line. - /// - /// This is a no-op if debug message recording is disabled which is always the case - /// when the code is executing on-chain. - /// - /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. - fn append_debug_buffer(&mut self, msg: &str) -> bool; - - /// Returns `true` if debug message recording is enabled. Otherwise `false` is returned. - fn debug_buffer_enabled(&self) -> bool; - /// Call some dispatchable and return the result. fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo; @@ -555,11 +542,6 @@ pub struct Stack<'a, T: Config, E> { frames: BoundedVec, ConstU32<{ limits::CALL_STACK_DEPTH }>>, /// Statically guarantee that each call stack has at least one frame. first_frame: Frame, - /// A text buffer used to output human readable information. - /// - /// All the bytes added to this field should be valid UTF-8. The buffer has no defined - /// structure and is intended to be shown to users as-is for debugging purposes. - debug_message: Option<&'a mut DebugBuffer>, /// Transient storage used to store data, which is kept for the duration of a transaction. transient_storage: TransientStorage, /// Whether or not actual transfer of funds should be performed. @@ -765,11 +747,6 @@ where { /// Create and run a new call stack by calling into `dest`. /// - /// # Note - /// - /// `debug_message` should only ever be set to `Some` when executing as an RPC because - /// it adds allocations and could be abused to drive the runtime into an OOM panic. - /// /// # Return Value /// /// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)> @@ -781,7 +758,6 @@ where value: U256, input_data: Vec, skip_transfer: bool, - debug_message: Option<&'a mut DebugBuffer>, ) -> ExecResult { let dest = T::AddressMapper::to_account_id(&dest); if let Some((mut stack, executable)) = Self::new( @@ -791,7 +767,6 @@ where storage_meter, value, skip_transfer, - debug_message, )? { stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) } else { @@ -801,11 +776,6 @@ where /// Create and run a new call stack by instantiating a new contract. /// - /// # Note - /// - /// `debug_message` should only ever be set to `Some` when executing as an RPC because - /// it adds allocations and could be abused to drive the runtime into an OOM panic. - /// /// # Return Value /// /// Result<(NewContractAccountId, ExecReturnValue), ExecError)> @@ -818,7 +788,6 @@ where input_data: Vec, salt: Option<&[u8; 32]>, skip_transfer: bool, - debug_message: Option<&'a mut DebugBuffer>, ) -> Result<(H160, ExecReturnValue), ExecError> { let (mut stack, executable) = Self::new( FrameArgs::Instantiate { @@ -832,7 +801,6 @@ where storage_meter, value, skip_transfer, - debug_message, )? .expect(FRAME_ALWAYS_EXISTS_ON_INSTANTIATE); let address = T::AddressMapper::to_address(&stack.top_frame().account_id); @@ -848,7 +816,6 @@ where gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, value: BalanceOf, - debug_message: Option<&'a mut DebugBuffer>, ) -> (Self, E) { Self::new( FrameArgs::Call { @@ -861,7 +828,6 @@ where storage_meter, value.into(), false, - debug_message, ) .unwrap() .unwrap() @@ -878,7 +844,6 @@ where storage_meter: &'a mut storage::meter::Meter, value: U256, skip_transfer: bool, - debug_message: Option<&'a mut DebugBuffer>, ) -> Result, ExecError> { origin.ensure_mapped()?; let Some((first_frame, executable)) = Self::new_frame( @@ -903,7 +868,6 @@ where block_number: >::block_number(), first_frame, frames: Default::default(), - debug_message, transient_storage: TransientStorage::new(limits::TRANSIENT_STORAGE_BYTES), skip_transfer, _phantom: Default::default(), @@ -1250,13 +1214,6 @@ where } } } else { - if let Some((msg, false)) = self.debug_message.as_ref().map(|m| (m, m.is_empty())) { - log::debug!( - target: LOG_TARGET, - "Execution finished with debug buffer: {}", - core::str::from_utf8(msg).unwrap_or(""), - ); - } self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_gas)); if !persist { return; @@ -1759,28 +1716,6 @@ where self.top_frame_mut().nested_storage.charge(diff) } - fn debug_buffer_enabled(&self) -> bool { - self.debug_message.is_some() - } - - fn append_debug_buffer(&mut self, msg: &str) -> bool { - if let Some(buffer) = &mut self.debug_message { - buffer - .try_extend(&mut msg.bytes()) - .map_err(|_| { - log::debug!( - target: LOG_TARGET, - "Debug buffer (of {} bytes) exhausted!", - limits::DEBUG_BUFFER_BYTES, - ) - }) - .ok(); - true - } else { - false - } - } - fn call_runtime(&self, call: ::RuntimeCall) -> DispatchResultWithPostInfo { let mut origin: T::RuntimeOrigin = RawOrigin::Signed(self.account_id().clone()).into(); origin.add_filter(T::CallFilter::contains); @@ -2103,7 +2038,6 @@ mod tests { value.into(), vec![], false, - None, ), Ok(_) ); @@ -2196,7 +2130,6 @@ mod tests { value.into(), vec![], false, - None, ) .unwrap(); @@ -2237,7 +2170,6 @@ mod tests { value.into(), vec![], false, - None, )); assert_eq!(get_balance(&ALICE), 100 - value); @@ -2274,7 +2206,6 @@ mod tests { U256::zero(), vec![], false, - None, ), ExecError { error: Error::::CodeNotFound.into(), @@ -2292,7 +2223,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -2321,7 +2251,6 @@ mod tests { 55u64.into(), vec![], false, - None, ) .unwrap(); @@ -2371,7 +2300,6 @@ mod tests { U256::zero(), vec![], false, - None, ); let output = result.unwrap(); @@ -2401,7 +2329,6 @@ mod tests { U256::zero(), vec![], false, - None, ); let output = result.unwrap(); @@ -2431,7 +2358,6 @@ mod tests { U256::zero(), vec![1, 2, 3, 4], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2468,7 +2394,6 @@ mod tests { vec![1, 2, 3, 4], Some(&[0; 32]), false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2523,7 +2448,6 @@ mod tests { value.into(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2588,7 +2512,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2654,7 +2577,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2687,7 +2609,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2725,7 +2646,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2752,7 +2672,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2797,7 +2716,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2824,7 +2742,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2851,7 +2768,6 @@ mod tests { 1u64.into(), vec![0], false, - None, ); assert_matches!(result, Err(_)); }); @@ -2896,7 +2812,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -2942,7 +2857,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); @@ -2969,7 +2883,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ), Err(_) ); @@ -3005,7 +2918,6 @@ mod tests { vec![], Some(&[0 ;32]), false, - None, ), Ok((address, ref output)) if output.data == vec![80, 65, 83, 83] => address ); @@ -3060,7 +2972,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ), Ok((address, ref output)) if output.data == vec![70, 65, 73, 76] => address ); @@ -3125,7 +3036,6 @@ mod tests { (min_balance * 10).into(), vec![], false, - None, ), Ok(_) ); @@ -3206,7 +3116,6 @@ mod tests { U256::zero(), vec![], false, - None, ), Ok(_) ); @@ -3250,7 +3159,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ), Err(Error::::TerminatedInConstructor.into()) ); @@ -3315,7 +3223,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -3378,7 +3285,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ); assert_matches!(result, Ok(_)); }); @@ -3425,113 +3331,11 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap(); }); } - #[test] - fn printing_works() { - let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); - exec_success() - }); - - let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let min_balance = ::Currency::minimum_balance(); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - vec![], - false, - Some(&mut debug_buffer), - ) - .unwrap(); - }); - - assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); - } - - #[test] - fn printing_works_on_fail() { - let code_hash = MockLoader::insert(Call, |ctx, _| { - ctx.ext.append_debug_buffer("This is a test"); - ctx.ext.append_debug_buffer("More text"); - exec_trapped() - }); - - let mut debug_buffer = DebugBuffer::try_from(Vec::new()).unwrap(); - - ExtBuilder::default().build().execute_with(|| { - let min_balance = ::Currency::minimum_balance(); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - let result = MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - vec![], - false, - Some(&mut debug_buffer), - ); - assert!(result.is_err()); - }); - - assert_eq!(&String::from_utf8(debug_buffer.to_vec()).unwrap(), "This is a testMore text"); - } - - #[test] - fn debug_buffer_is_limited() { - let code_hash = MockLoader::insert(Call, move |ctx, _| { - ctx.ext.append_debug_buffer("overflowing bytes"); - exec_success() - }); - - // Pre-fill the buffer almost up to its limit, leaving not enough space to the message - let debug_buf_before = DebugBuffer::try_from(vec![0u8; DebugBuffer::bound() - 5]).unwrap(); - let mut debug_buf_after = debug_buf_before.clone(); - - ExtBuilder::default().build().execute_with(|| { - let min_balance = ::Currency::minimum_balance(); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - set_balance(&ALICE, min_balance * 10); - place_contract(&BOB, code_hash); - let origin = Origin::from_account_id(ALICE); - let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); - MockStack::run_call( - origin, - BOB_ADDR, - &mut gas_meter, - &mut storage_meter, - U256::zero(), - vec![], - false, - Some(&mut debug_buf_after), - ) - .unwrap(); - assert_eq!(debug_buf_before, debug_buf_after); - }); - } - #[test] fn call_reentry_direct_recursion() { // call the contract passed as input with disabled reentry @@ -3559,7 +3363,6 @@ mod tests { U256::zero(), CHARLIE_ADDR.as_bytes().to_vec(), false, - None, )); // Calling into oneself fails @@ -3572,7 +3375,6 @@ mod tests { U256::zero(), BOB_ADDR.as_bytes().to_vec(), false, - None, ) .map_err(|e| e.error), >::ReentranceDenied, @@ -3623,7 +3425,6 @@ mod tests { U256::zero(), vec![0], false, - None, ) .map_err(|e| e.error), >::ReentranceDenied, @@ -3658,7 +3459,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap(); @@ -3743,7 +3543,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap(); @@ -3870,7 +3669,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, ) .ok(); assert_eq!(System::account_nonce(&ALICE), 0); @@ -3884,7 +3682,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, )); assert_eq!(System::account_nonce(&ALICE), 1); @@ -3897,7 +3694,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, )); assert_eq!(System::account_nonce(&ALICE), 2); @@ -3910,7 +3706,6 @@ mod tests { vec![], Some(&[0; 32]), false, - None, )); assert_eq!(System::account_nonce(&ALICE), 3); }); @@ -3979,7 +3774,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4091,7 +3885,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4131,7 +3924,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4171,7 +3963,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4225,7 +4016,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4282,7 +4072,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4358,7 +4147,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4429,7 +4217,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4468,7 +4255,6 @@ mod tests { U256::zero(), vec![], false, - None, )); }); } @@ -4531,7 +4317,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4565,7 +4350,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4641,7 +4425,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4710,7 +4493,6 @@ mod tests { U256::zero(), vec![0], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4782,7 +4564,6 @@ mod tests { U256::zero(), vec![], false, - None, ); assert_matches!(result, Ok(_)); }); @@ -4834,7 +4615,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4904,7 +4684,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4951,7 +4730,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -4996,7 +4774,6 @@ mod tests { U256::zero(), vec![], false, - None, ) .unwrap() }); @@ -5052,7 +4829,6 @@ mod tests { U256::zero(), vec![0], false, - None, ), Ok(_) ); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index bdb4b92edd9e..403598ae136e 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -71,7 +71,7 @@ use frame_support::{ use frame_system::{ ensure_signed, pallet_prelude::{BlockNumberFor, OriginFor}, - EventRecord, Pallet as System, + Pallet as System, }; use pallet_transaction_payment::OnChargeTransaction; use scale_info::TypeInfo; @@ -98,9 +98,6 @@ type BalanceOf = <::Currency as Inspect<::AccountId>>::Balance; type OnChargeTransactionBalanceOf = <::OnChargeTransaction as OnChargeTransaction>::Balance; type CodeVec = BoundedVec>; -type EventRecordOf = - EventRecord<::RuntimeEvent, ::Hash>; -type DebugBuffer = BoundedVec>; type ImmutableData = BoundedVec>; /// Used as a sentinel value when reading and writing contract memory. @@ -258,9 +255,9 @@ pub mod pallet { #[pallet::no_default_bounds] type InstantiateOrigin: EnsureOrigin; - /// For most production chains, it's recommended to use the `()` implementation of this - /// trait. This implementation offers additional logging when the log target - /// "runtime::revive" is set to trace. + /// Debugging utilities for contracts. + /// For production chains, it's recommended to use the `()` implementation of this + /// trait. #[pallet::no_default_bounds] type Debug: Debugger; @@ -810,9 +807,8 @@ pub mod pallet { gas_limit, DepositLimit::Balance(storage_deposit_limit), data, - DebugInfo::Skip, - CollectEvents::Skip, ); + if let Ok(return_value) = &output.result { if return_value.did_revert() { output.result = Err(>::ContractReverted.into()); @@ -848,8 +844,6 @@ pub mod pallet { Code::Existing(code_hash), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); if let Ok(retval) = &output.result { if retval.result.did_revert() { @@ -914,8 +908,6 @@ pub mod pallet { Code::Upload(code), data, salt, - DebugInfo::Skip, - CollectEvents::Skip, ); if let Ok(retval) = &output.result { if retval.result.did_revert() { @@ -1085,16 +1077,10 @@ where gas_limit: Weight, storage_deposit_limit: DepositLimit>, data: Vec, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf> { + ) -> ContractResult> { let mut gas_meter = GasMeter::new(gas_limit); let mut storage_deposit = Default::default(); - let mut debug_message = if matches!(debug, DebugInfo::UnsafeDebug) { - Some(DebugBuffer::default()) - } else { - None - }; + let try_call = || { let origin = Origin::from_runtime_origin(origin)?; let mut storage_meter = match storage_deposit_limit { @@ -1109,7 +1095,6 @@ where Self::convert_native_to_evm(value), data, storage_deposit_limit.is_unchecked(), - debug_message.as_mut(), )?; storage_deposit = storage_meter .try_into_deposit(&origin, storage_deposit_limit.is_unchecked()) @@ -1119,18 +1104,11 @@ where Ok(result) }; let result = Self::run_guarded(try_call); - let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { - Some(System::::read_events_no_consensus().map(|e| *e).collect()) - } else { - None - }; ContractResult { result: result.map_err(|r| r.error), gas_consumed: gas_meter.gas_consumed(), gas_required: gas_meter.gas_required(), storage_deposit, - debug_message: debug_message.unwrap_or_default().to_vec(), - events, } } @@ -1138,8 +1116,7 @@ where /// /// Identical to [`Self::instantiate`] or [`Self::instantiate_with_code`] but tailored towards /// being called by other code within the runtime as opposed to from an extrinsic. It returns - /// more information and allows the enablement of features that are not suitable for an - /// extrinsic (debugging, event collection). + /// more information to the caller useful to estimate the cost of the operation. pub fn bare_instantiate( origin: OriginFor, value: BalanceOf, @@ -1148,14 +1125,9 @@ where code: Code, data: Vec, salt: Option<[u8; 32]>, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf> { + ) -> ContractResult> { let mut gas_meter = GasMeter::new(gas_limit); let mut storage_deposit = Default::default(); - let mut debug_message = - if debug == DebugInfo::UnsafeDebug { Some(DebugBuffer::default()) } else { None }; - let unchecked_deposit_limit = storage_deposit_limit.is_unchecked(); let mut storage_deposit_limit = match storage_deposit_limit { DepositLimit::Balance(limit) => limit, @@ -1195,7 +1167,6 @@ where data, salt.as_ref(), unchecked_deposit_limit, - debug_message.as_mut(), ); storage_deposit = storage_meter .try_into_deposit(&instantiate_origin, unchecked_deposit_limit)? @@ -1203,11 +1174,6 @@ where result }; let output = Self::run_guarded(try_instantiate); - let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { - Some(System::::read_events_no_consensus().map(|e| *e).collect()) - } else { - None - }; ContractResult { result: output .map(|(addr, result)| InstantiateReturnValue { result, addr }) @@ -1215,8 +1181,6 @@ where gas_consumed: gas_meter.gas_consumed(), gas_required: gas_meter.gas_required(), storage_deposit, - debug_message: debug_message.unwrap_or_default().to_vec(), - events, } } @@ -1273,8 +1237,6 @@ where }; let input = tx.input.clone().unwrap_or_default().0; - let debug = DebugInfo::Skip; - let collect_events = CollectEvents::Skip; let extract_error = |err| { if err == Error::::TransferFailed.into() || @@ -1305,8 +1267,6 @@ where gas_limit, storage_deposit_limit, input.clone(), - debug, - collect_events, ); let data = match result.result { @@ -1363,8 +1323,6 @@ where Code::Upload(code.to_vec()), data.to_vec(), None, - debug, - collect_events, ); let returned_data = match result.result { @@ -1535,12 +1493,11 @@ environmental!(executing_contract: bool); sp_api::decl_runtime_apis! { /// The API used to dry-run contract interactions. #[api_version(1)] - pub trait ReviveApi where + pub trait ReviveApi where AccountId: Codec, Balance: Codec, Nonce: Codec, BlockNumber: Codec, - EventRecord: Codec, { /// Returns the free balance of the given `[H160]` address, using EVM decimals. fn balance(address: H160) -> U256; @@ -1558,7 +1515,7 @@ sp_api::decl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> ContractResult; + ) -> ContractResult; /// Instantiate a new contract. /// @@ -1571,7 +1528,7 @@ sp_api::decl_runtime_apis! { code: Code, data: Vec, salt: Option<[u8; 32]>, - ) -> ContractResult; + ) -> ContractResult; /// Perform an Ethereum call. diff --git a/substrate/frame/revive/src/limits.rs b/substrate/frame/revive/src/limits.rs index 3b55106c67d8..f101abf0ea7e 100644 --- a/substrate/frame/revive/src/limits.rs +++ b/substrate/frame/revive/src/limits.rs @@ -57,11 +57,6 @@ pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024; /// The maximum allowable length in bytes for (transient) storage keys. pub const STORAGE_KEY_BYTES: u32 = 128; -/// The maximum size of the debug buffer contracts can write messages to. -/// -/// The buffer will always be disabled for on-chain execution. -pub const DEBUG_BUFFER_BYTES: u32 = 2 * 1024 * 1024; - /// The page size in which PolkaVM should allocate memory chunks. pub const PAGE_SIZE: u32 = 4 * 1024; diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 452d2c8a3067..9c149c7cc389 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -63,7 +63,7 @@ impl From for DepositLimit { /// `ContractsApi` version. Therefore when SCALE decoding a `ContractResult` its trailing data /// should be ignored to avoid any potential compatibility issues. #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct ContractResult { +pub struct ContractResult { /// How much weight was consumed during execution. pub gas_consumed: Weight, /// How much weight is required as gas limit in order to execute this call. @@ -84,26 +84,8 @@ pub struct ContractResult { /// is `Err`. This is because on error all storage changes are rolled back including the /// payment of the deposit. pub storage_deposit: StorageDeposit, - /// An optional debug message. This message is only filled when explicitly requested - /// by the code that calls into the contract. Otherwise it is empty. - /// - /// The contained bytes are valid UTF-8. This is not declared as `String` because - /// this type is not allowed within the runtime. - /// - /// Clients should not make any assumptions about the format of the buffer. - /// They should just display it as-is. It is **not** only a collection of log lines - /// provided by a contract but a formatted buffer with different sections. - /// - /// # Note - /// - /// The debug message is never generated during on-chain execution. It is reserved for - /// RPC calls. - pub debug_message: Vec, /// The execution result of the wasm code. pub result: Result, - /// The events that were emitted during execution. It is an option as event collection is - /// optional. - pub events: Option>, } /// The result of the execution of a `eth_transact` call. @@ -284,36 +266,3 @@ where } } } - -/// Determines whether events should be collected during execution. -#[derive( - Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo, -)] -pub enum CollectEvents { - /// Collect events. - /// - /// # Note - /// - /// Events should only be collected when called off-chain, as this would otherwise - /// collect all the Events emitted in the block so far and put them into the PoV. - /// - /// **Never** use this mode for on-chain execution. - UnsafeCollect, - /// Skip event collection. - Skip, -} - -/// Determines whether debug messages will be collected. -#[derive( - Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo, -)] -pub enum DebugInfo { - /// Collect debug messages. - /// # Note - /// - /// This should only ever be set to `UnsafeDebug` when executing as an RPC because - /// it adds allocations and could be abused to drive the runtime into an OOM panic. - UnsafeDebug, - /// Skip collection of debug messages. - Skip, -} diff --git a/substrate/frame/revive/src/test_utils/builder.rs b/substrate/frame/revive/src/test_utils/builder.rs index 8ba5e7384070..7fbb5b676439 100644 --- a/substrate/frame/revive/src/test_utils/builder.rs +++ b/substrate/frame/revive/src/test_utils/builder.rs @@ -17,9 +17,8 @@ use super::{deposit_limit, GAS_LIMIT}; use crate::{ - address::AddressMapper, AccountIdOf, BalanceOf, Code, CollectEvents, Config, ContractResult, - DebugInfo, DepositLimit, EventRecordOf, ExecReturnValue, InstantiateReturnValue, OriginFor, - Pallet, Weight, + address::AddressMapper, AccountIdOf, BalanceOf, Code, Config, ContractResult, DepositLimit, + ExecReturnValue, InstantiateReturnValue, OriginFor, Pallet, Weight, }; use frame_support::pallet_prelude::DispatchResultWithPostInfo; use paste::paste; @@ -138,9 +137,7 @@ builder!( code: Code, data: Vec, salt: Option<[u8; 32]>, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf>; + ) -> ContractResult>; /// Build the instantiate call and unwrap the result. pub fn build_and_unwrap_result(self) -> InstantiateReturnValue { @@ -164,8 +161,6 @@ builder!( code, data: vec![], salt: Some([0; 32]), - debug: DebugInfo::UnsafeDebug, - collect_events: CollectEvents::Skip, } } ); @@ -201,9 +196,7 @@ builder!( gas_limit: Weight, storage_deposit_limit: DepositLimit>, data: Vec, - debug: DebugInfo, - collect_events: CollectEvents, - ) -> ContractResult, EventRecordOf>; + ) -> ContractResult>; /// Build the call and unwrap the result. pub fn build_and_unwrap_result(self) -> ExecReturnValue { @@ -219,8 +212,6 @@ builder!( gas_limit: GAS_LIMIT, storage_deposit_limit: DepositLimit::Balance(deposit_limit::()), data: vec![], - debug: DebugInfo::UnsafeDebug, - collect_events: CollectEvents::Skip, } } ); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index cf02d17a4d03..e2b30cf07c8d 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -38,9 +38,9 @@ use crate::{ tests::test_utils::{get_contract, get_contract_checked}, wasm::Memory, weights::WeightInfo, - AccountId32Mapper, BalanceOf, Code, CodeInfoOf, CollectEvents, Config, ContractInfo, - ContractInfoOf, DebugInfo, DeletionQueueCounter, DepositLimit, Error, EthTransactError, - HoldReason, Origin, Pallet, PristineCode, H160, + AccountId32Mapper, BalanceOf, Code, CodeInfoOf, Config, ContractInfo, ContractInfoOf, + DeletionQueueCounter, DepositLimit, Error, EthTransactError, HoldReason, Origin, Pallet, + PristineCode, H160, }; use crate::test_utils::builder::Contract; @@ -2184,58 +2184,6 @@ fn refcounter() { }); } -#[test] -fn debug_message_works() { - let (wasm, _code_hash) = compile_module("debug_message_works").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); - - assert_matches!(result.result, Ok(_)); - assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!"); - }); -} - -#[test] -fn debug_message_logging_disabled() { - let (wasm, _code_hash) = compile_module("debug_message_logging_disabled").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - // the dispatchables always run without debugging - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr, - 0, - GAS_LIMIT, - deposit_limit::(), - vec![] - )); - }); -} - -#[test] -fn debug_message_invalid_utf8() { - let (wasm, _code_hash) = compile_module("debug_message_invalid_utf8").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = ::Currency::set_balance(&ALICE, 1_000_000); - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(30_000) - .build_and_unwrap_contract(); - let result = builder::bare_call(addr).debug(DebugInfo::UnsafeDebug).build(); - assert_ok!(result.result); - assert!(result.debug_message.is_empty()); - }); -} - #[test] fn gas_estimation_for_subcalls() { let (caller_code, _caller_hash) = compile_module("call_with_limit").unwrap(); @@ -2451,79 +2399,6 @@ fn ecdsa_recover() { }) } -#[test] -fn bare_instantiate_returns_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let result = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .collect_events(CollectEvents::UnsafeCollect) - .build(); - - let events = result.events.unwrap(); - assert!(!events.is_empty()); - assert_eq!(events, System::events()); - }); -} - -#[test] -fn bare_instantiate_does_not_return_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let result = builder::bare_instantiate(Code::Upload(wasm)).value(min_balance * 100).build(); - - let events = result.events; - assert!(!System::events().is_empty()); - assert!(events.is_none()); - }); -} - -#[test] -fn bare_call_returns_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .build_and_unwrap_contract(); - - let result = builder::bare_call(addr).collect_events(CollectEvents::UnsafeCollect).build(); - - let events = result.events.unwrap(); - assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); - assert!(!events.is_empty()); - assert_eq!(events, System::events()); - }); -} - -#[test] -fn bare_call_does_not_return_events() { - let (wasm, _code_hash) = compile_module("transfer_return_code").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let min_balance = Contracts::min_balance(); - let _ = ::Currency::set_balance(&ALICE, 1000 * min_balance); - - let Contract { addr, .. } = builder::bare_instantiate(Code::Upload(wasm)) - .value(min_balance * 100) - .build_and_unwrap_contract(); - - let result = builder::bare_call(addr).build(); - - let events = result.events; - assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); - assert!(!System::events().is_empty()); - assert!(events.is_none()); - }); -} - #[test] fn sr25519_verify() { let (wasm, _code_hash) = compile_module("sr25519_verify").unwrap(); @@ -3327,14 +3202,11 @@ fn set_code_hash() { // First call sets new code_hash and returns 1 let result = builder::bare_call(contract_addr) .data(new_code_hash.as_ref().to_vec()) - .debug(DebugInfo::UnsafeDebug) .build_and_unwrap_result(); assert_return_code!(result, 1); // Second calls new contract code that returns 2 - let result = builder::bare_call(contract_addr) - .debug(DebugInfo::UnsafeDebug) - .build_and_unwrap_result(); + let result = builder::bare_call(contract_addr).build_and_unwrap_result(); assert_return_code!(result, 2); // Checking for the last event only @@ -4810,7 +4682,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, ), EthTransactError::Message(format!( "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" @@ -4825,7 +4697,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, )); let Contract { addr, .. } = @@ -4844,7 +4716,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, ), EthTransactError::Message(format!( "insufficient funds for gas * price + value: address {BOB_ADDR:?} have 0 (supplied gas 1)" @@ -4869,7 +4741,7 @@ fn skip_transfer_works() { assert_ok!(Pallet::::bare_eth_transact( GenericTransaction { from: Some(BOB_ADDR), to: Some(addr), ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, )); // works when calling from a contract when no gas is specified. @@ -4881,7 +4753,7 @@ fn skip_transfer_works() { ..Default::default() }, Weight::MAX, - |_| 0u32 + |_| 0u32, )); }); } diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs index c9e19e52ace1..b1fdb2d47441 100644 --- a/substrate/frame/revive/src/tests/test_debug.rs +++ b/substrate/frame/revive/src/tests/test_debug.rs @@ -119,8 +119,6 @@ fn debugging_works() { Code::Upload(wasm), vec![], Some([0u8; 32]), - DebugInfo::Skip, - CollectEvents::Skip, ) .result .unwrap() @@ -204,8 +202,6 @@ fn call_interception_works() { vec![], // some salt to ensure that the address of this contract is unique among all tests Some([0x41; 32]), - DebugInfo::Skip, - CollectEvents::Skip, ) .result .unwrap() diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index 3bd4bde5679f..b45d7026ba91 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -288,7 +288,7 @@ impl WasmBlob { } let engine = polkavm::Engine::new(&config).expect( "on-chain (no_std) use of interpreter is hard coded. - interpreter is available on all plattforms; qed", + interpreter is available on all platforms; qed", ); let mut module_config = polkavm::ModuleConfig::new(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 8529c7d9e73b..1ff6a80840a9 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -339,8 +339,6 @@ pub enum RuntimeCosts { Terminate(u32), /// Weight of calling `seal_deposit_event` with the given number of topics and event size. DepositEvent { num_topic: u32, len: u32 }, - /// Weight of calling `seal_debug_message` per byte of passed message. - DebugMessage(u32), /// Weight of calling `seal_set_storage` for the given storage item sizes. SetStorage { old_bytes: u32, new_bytes: u32 }, /// Weight of calling `seal_clear_storage` per cleared byte. @@ -489,7 +487,6 @@ impl Token for RuntimeCosts { WeightToFee => T::WeightInfo::seal_weight_to_fee(), Terminate(locked_dependencies) => T::WeightInfo::seal_terminate(locked_dependencies), DepositEvent { num_topic, len } => T::WeightInfo::seal_deposit_event(num_topic, len), - DebugMessage(len) => T::WeightInfo::seal_debug_message(len), SetStorage { new_bytes, old_bytes } => { cost_storage!(write, seal_set_storage, new_bytes, old_bytes) }, @@ -669,10 +666,7 @@ impl<'a, E: Ext, M: ?Sized + Memory> Runtime<'a, E, M> { match result { Ok(_) => Ok(ReturnErrorCode::Success), Err(e) => { - if self.ext.debug_buffer_enabled() { - self.ext.append_debug_buffer("call failed with: "); - self.ext.append_debug_buffer(e.into()); - }; + log::debug!(target: LOG_TARGET, "call failed with: {e:?}"); Ok(ErrorReturnCode::get()) }, } @@ -1832,27 +1826,6 @@ pub mod env { self.contains_storage(memory, flags, key_ptr, key_len) } - /// Emit a custom debug message. - /// See [`pallet_revive_uapi::HostFn::debug_message`]. - fn debug_message( - &mut self, - memory: &mut M, - str_ptr: u32, - str_len: u32, - ) -> Result { - let str_len = str_len.min(limits::DEBUG_BUFFER_BYTES); - self.charge_gas(RuntimeCosts::DebugMessage(str_len))?; - if self.ext.append_debug_buffer("") { - let data = memory.read(str_ptr, str_len)?; - if let Some(msg) = core::str::from_utf8(&data).ok() { - self.ext.append_debug_buffer(msg); - } - Ok(ReturnErrorCode::Success) - } else { - Ok(ReturnErrorCode::LoggingDisabled) - } - } - /// Recovers the ECDSA public key from the given message hash and signature. /// See [`pallet_revive_uapi::HostFn::ecdsa_recover`]. fn ecdsa_recover( @@ -2162,10 +2135,7 @@ pub mod env { Ok(ReturnErrorCode::Success) }, Err(e) => { - if self.ext.append_debug_buffer("") { - self.ext.append_debug_buffer("seal0::xcm_send failed with: "); - self.ext.append_debug_buffer(e.into()); - }; + log::debug!(target: LOG_TARGET, "seal0::xcm_send failed with: {e:?}"); Ok(ReturnErrorCode::XcmSendFailed) }, } diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index e35ba5ca0766..06495d5d21aa 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -96,7 +96,6 @@ pub trait WeightInfo { fn seal_return(n: u32, ) -> Weight; fn seal_terminate(n: u32, ) -> Weight; fn seal_deposit_event(t: u32, n: u32, ) -> Weight; - fn seal_debug_message(i: u32, ) -> Weight; fn get_storage_empty() -> Weight; fn get_storage_full() -> Weight; fn set_storage_empty() -> Weight; @@ -643,16 +642,6 @@ impl WeightInfo for SubstrateWeight { // Standard Error: 34 .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) } - /// The range of component `i` is `[0, 262144]`. - fn seal_debug_message(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 340_000 picoseconds. - Weight::from_parts(306_527, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(728, 0).saturating_mul(i.into())) - } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_empty() -> Weight { @@ -1539,16 +1528,6 @@ impl WeightInfo for () { // Standard Error: 34 .saturating_add(Weight::from_parts(774, 0).saturating_mul(n.into())) } - /// The range of component `i` is `[0, 262144]`. - fn seal_debug_message(i: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 340_000 picoseconds. - Weight::from_parts(306_527, 0) - // Standard Error: 1 - .saturating_add(Weight::from_parts(728, 0).saturating_mul(i.into())) - } /// Storage: `Skipped::Metadata` (r:0 w:0) /// Proof: `Skipped::Metadata` (`max_values`: None, `max_size`: None, mode: `Measured`) fn get_storage_empty() -> Weight { diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index ba0a63b15c37..b82393826ddf 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -515,26 +515,6 @@ pub trait HostFn: private::Sealed { #[unstable_hostfn] fn contains_storage(flags: StorageFlags, key: &[u8]) -> Option; - /// Emit a custom debug message. - /// - /// No newlines are added to the supplied message. - /// Specifying invalid UTF-8 just drops the message with no trap. - /// - /// This is a no-op if debug message recording is disabled which is always the case - /// when the code is executing on-chain. The message is interpreted as UTF-8 and - /// appended to the debug buffer which is then supplied to the calling RPC client. - /// - /// # Note - /// - /// Even though no action is taken when debug message recording is disabled there is still - /// a non trivial overhead (and weight cost) associated with calling this function. Contract - /// languages should remove calls to this function (either at runtime or compile time) when - /// not being executed as an RPC. For example, they could allow users to disable logging - /// through compile time flags (cargo features) for on-chain deployment. Additionally, the - /// return value of this function can be cached in order to prevent further calls at runtime. - #[unstable_hostfn] - fn debug_message(str: &[u8]) -> Result; - /// Recovers the ECDSA public key from the given message hash and signature. /// /// Writes the public key into the given output buffer. diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 8c40bc9f48ea..0023b8aa721d 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -109,7 +109,6 @@ mod sys { out_ptr: *mut u8, out_len_ptr: *mut u32, ) -> ReturnCode; - pub fn debug_message(str_ptr: *const u8, str_len: u32) -> ReturnCode; pub fn call_runtime(call_ptr: *const u8, call_len: u32) -> ReturnCode; pub fn ecdsa_recover( signature_ptr: *const u8, @@ -519,12 +518,6 @@ impl HostFn for HostFnImpl { ret_code.into() } - #[unstable_hostfn] - fn debug_message(str: &[u8]) -> Result { - let ret_code = unsafe { sys::debug_message(str.as_ptr(), str.len() as u32) }; - ret_code.into() - } - #[unstable_hostfn] fn ecdsa_recover( signature: &[u8; 65], diff --git a/substrate/frame/revive/uapi/src/lib.rs b/substrate/frame/revive/uapi/src/lib.rs index ef1798b4bf61..867f35633987 100644 --- a/substrate/frame/revive/uapi/src/lib.rs +++ b/substrate/frame/revive/uapi/src/lib.rs @@ -86,9 +86,8 @@ define_error_codes! { /// Transfer failed for other not further specified reason. Most probably /// reserved or locked balance of the sender that was preventing the transfer. TransferFailed = 4, - /// The call to `debug_message` had no effect because debug message - /// recording was disabled. - LoggingDisabled = 5, + /// The subcall ran out of weight or storage deposit. + OutOfResources = 5, /// The call dispatched by `call_runtime` was executed but returned an error. CallRuntimeFailed = 6, /// ECDSA public key recovery failed. Most probably wrong recovery id or signature. @@ -99,8 +98,6 @@ define_error_codes! { XcmExecutionFailed = 9, /// The `xcm_send` call failed. XcmSendFailed = 10, - /// The subcall ran out of weight or storage deposit. - OutOfResources = 11, } /// The raw return code returned by the host side. From 5be65872188a4ac1bf76333af3958b65f2a9629e Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Wed, 15 Jan 2025 20:23:54 +0100 Subject: [PATCH 271/340] [pallet-revive] Remove revive events (#7164) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove all pallet::events except for the `ContractEmitted` event that is emitted by contracts --------- Co-authored-by: command-bot <> Co-authored-by: Alexander Theißen --- prdoc/pr_7164.prdoc | 8 + substrate/frame/revive/src/exec.rs | 103 +------ substrate/frame/revive/src/lib.rs | 72 ----- substrate/frame/revive/src/storage/meter.rs | 16 +- substrate/frame/revive/src/tests.rs | 323 +------------------- substrate/frame/revive/src/wasm/mod.rs | 18 +- 6 files changed, 40 insertions(+), 500 deletions(-) create mode 100644 prdoc/pr_7164.prdoc diff --git a/prdoc/pr_7164.prdoc b/prdoc/pr_7164.prdoc new file mode 100644 index 000000000000..cb0410a9de79 --- /dev/null +++ b/prdoc/pr_7164.prdoc @@ -0,0 +1,8 @@ +title: '[pallet-revive] Remove revive events' +doc: +- audience: Runtime Dev + description: Remove all pallet::events except for the `ContractEmitted` event that + is emitted by contracts +crates: +- name: pallet-revive + bump: major diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index e20c5dd7786e..1c6ca435aefb 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -1092,34 +1092,11 @@ where .enforce_limit(contract) .map_err(|e| ExecError { error: e, origin: ErrorOrigin::Callee })?; - let account_id = T::AddressMapper::to_address(&frame.account_id); - match (entry_point, delegated_code_hash) { - (ExportedFunction::Constructor, _) => { - // It is not allowed to terminate a contract inside its constructor. - if matches!(frame.contract_info, CachedContract::Terminated) { - return Err(Error::::TerminatedInConstructor.into()); - } - - let caller = T::AddressMapper::to_address(self.caller().account_id()?); - // Deposit an instantiation event. - Contracts::::deposit_event(Event::Instantiated { - deployer: caller, - contract: account_id, - }); - }, - (ExportedFunction::Call, Some(code_hash)) => { - Contracts::::deposit_event(Event::DelegateCalled { - contract: account_id, - code_hash, - }); - }, - (ExportedFunction::Call, None) => { - let caller = self.caller(); - Contracts::::deposit_event(Event::Called { - caller: caller.clone(), - contract: account_id, - }); - }, + // It is not allowed to terminate a contract inside its constructor. + if entry_point == ExportedFunction::Constructor && + matches!(frame.contract_info, CachedContract::Terminated) + { + return Err(Error::::TerminatedInConstructor.into()); } Ok(output) @@ -1526,10 +1503,6 @@ where .charge_deposit(frame.account_id.clone(), StorageDeposit::Refund(*deposit)); } - Contracts::::deposit_event(Event::Terminated { - contract: account_address, - beneficiary: *beneficiary, - }); Ok(()) } @@ -1782,11 +1755,6 @@ where Self::increment_refcount(hash)?; Self::decrement_refcount(prev_hash); - Contracts::::deposit_event(Event::ContractCodeUpdated { - contract: T::AddressMapper::to_address(&frame.account_id), - new_code_hash: hash, - old_code_hash: prev_hash, - }); Ok(()) } @@ -2933,13 +2901,6 @@ mod tests { ContractInfo::::load_code_hash(&instantiated_contract_id).unwrap(), dummy_ch ); - assert_eq!( - &events(), - &[Event::Instantiated { - deployer: ALICE_ADDR, - contract: instantiated_contract_address - }] - ); }); } @@ -3055,19 +3016,6 @@ mod tests { ContractInfo::::load_code_hash(&instantiated_contract_id).unwrap(), dummy_ch ); - assert_eq!( - &events(), - &[ - Event::Instantiated { - deployer: BOB_ADDR, - contract: instantiated_contract_address - }, - Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR - }, - ] - ); }); } @@ -3119,13 +3067,6 @@ mod tests { ), Ok(_) ); - - // The contract wasn't instantiated so we don't expect to see an instantiation - // event here. - assert_eq!( - &events(), - &[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB_ADDR },] - ); }); } @@ -3465,24 +3406,14 @@ mod tests { let remark_hash = ::Hashing::hash(b"Hello World"); assert_eq!( System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::System(frame_system::Event::Remarked { - sender: BOB_FALLBACK, - hash: remark_hash - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR, - }), - topics: vec![], - }, - ] + vec![EventRecord { + phase: Phase::Initialization, + event: MetaEvent::System(frame_system::Event::Remarked { + sender: BOB_FALLBACK, + hash: remark_hash + }), + topics: vec![], + },] ); }); } @@ -3571,14 +3502,6 @@ mod tests { },), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: MetaEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: BOB_ADDR, - }), - topics: vec![], - }, ] ); }); diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 403598ae136e..a9f2842c35f6 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -379,25 +379,6 @@ pub mod pallet { #[pallet::event] pub enum Event { - /// Contract deployed by address at the specified address. - Instantiated { deployer: H160, contract: H160 }, - - /// Contract has been removed. - /// - /// # Note - /// - /// The only way for a contract to be removed and emitting this event is by calling - /// `seal_terminate`. - Terminated { - /// The contract that was terminated. - contract: H160, - /// The account that received the contracts remaining balance - beneficiary: H160, - }, - - /// Code with the specified hash has been stored. - CodeStored { code_hash: H256, deposit_held: BalanceOf, uploader: H160 }, - /// A custom event emitted by the contract. ContractEmitted { /// The contract that emitted the event. @@ -409,54 +390,6 @@ pub mod pallet { /// Number of topics is capped by [`limits::NUM_EVENT_TOPICS`]. topics: Vec, }, - - /// A code with the specified hash was removed. - CodeRemoved { code_hash: H256, deposit_released: BalanceOf, remover: H160 }, - - /// A contract's code was updated. - ContractCodeUpdated { - /// The contract that has been updated. - contract: H160, - /// New code hash that was set for the contract. - new_code_hash: H256, - /// Previous code hash of the contract. - old_code_hash: H256, - }, - - /// A contract was called either by a plain account or another contract. - /// - /// # Note - /// - /// Please keep in mind that like all events this is only emitted for successful - /// calls. This is because on failure all storage changes including events are - /// rolled back. - Called { - /// The caller of the `contract`. - caller: Origin, - /// The contract that was called. - contract: H160, - }, - - /// A contract delegate called a code hash. - /// - /// # Note - /// - /// Please keep in mind that like all events this is only emitted for successful - /// calls. This is because on failure all storage changes including events are - /// rolled back. - DelegateCalled { - /// The contract that performed the delegate call and hence in whose context - /// the `code_hash` is executed. - contract: H160, - /// The code hash that was delegate called. - code_hash: H256, - }, - - /// Some funds have been transferred and held as storage deposit. - StorageDepositTransferredAndHeld { from: H160, to: H160, amount: BalanceOf }, - - /// Some storage deposit funds have been transferred and released. - StorageDepositTransferredAndReleased { from: H160, to: H160, amount: BalanceOf }, } #[pallet::error] @@ -985,11 +918,6 @@ pub mod pallet { }; >>::increment_refcount(code_hash)?; >>::decrement_refcount(contract.code_hash); - Self::deposit_event(Event::ContractCodeUpdated { - contract: dest, - new_code_hash: code_hash, - old_code_hash: contract.code_hash, - }); contract.code_hash = code_hash; Ok(()) }) diff --git a/substrate/frame/revive/src/storage/meter.rs b/substrate/frame/revive/src/storage/meter.rs index 4febcb0c4066..cd390c86f63a 100644 --- a/substrate/frame/revive/src/storage/meter.rs +++ b/substrate/frame/revive/src/storage/meter.rs @@ -18,8 +18,8 @@ //! This module contains functions to meter the storage deposit. use crate::{ - address::AddressMapper, storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, - Event, HoldReason, Inspect, Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, + storage::ContractInfo, AccountIdOf, BalanceOf, CodeInfo, Config, Error, HoldReason, Inspect, + Origin, Pallet, StorageDeposit as Deposit, System, LOG_TARGET, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData}; @@ -516,12 +516,6 @@ impl Ext for ReservingExt { Preservation::Preserve, Fortitude::Polite, )?; - - Pallet::::deposit_event(Event::StorageDepositTransferredAndHeld { - from: T::AddressMapper::to_address(origin), - to: T::AddressMapper::to_address(contract), - amount: *amount, - }); }, Deposit::Refund(amount) => { let transferred = T::Currency::transfer_on_hold( @@ -534,12 +528,6 @@ impl Ext for ReservingExt { Fortitude::Polite, )?; - Pallet::::deposit_event(Event::StorageDepositTransferredAndReleased { - from: T::AddressMapper::to_address(contract), - to: T::AddressMapper::to_address(origin), - amount: transferred, - }); - if transferred < *amount { // This should never happen, if it does it means that there is a bug in the // runtime logic. In the rare case this happens we try to refund as much as we diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index e2b30cf07c8d..35940f544d00 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -713,25 +713,6 @@ fn instantiate_and_call_and_deposit_event() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -1078,14 +1059,6 @@ fn deploy_and_call_other_contract() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: caller_addr, - contract: callee_addr, - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { @@ -1095,33 +1068,6 @@ fn deploy_and_call_other_contract() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(caller_account.clone()), - contract: callee_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: caller_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: callee_addr, - amount: test_utils::contract_info_storage_deposit(&callee_addr), - } - ), - topics: vec![], - }, ] ); }); @@ -1373,8 +1319,6 @@ fn self_destruct_works() { // Check that the BOB contract has been instantiated. let _ = get_contract(&contract.addr); - let info_deposit = test_utils::contract_info_storage_deposit(&contract.addr); - // Drop all previous events initialize_block(2); @@ -1404,33 +1348,6 @@ fn self_destruct_works() { pretty_assertions::assert_eq!( System::events(), vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Terminated { - contract: contract.addr, - beneficiary: DJANGO_ADDR, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract.addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndReleased { - from: contract.addr, - to: ALICE_ADDR, - amount: info_deposit, - } - ), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::KilledAccount { @@ -2512,23 +2429,9 @@ fn upload_code_works() { initialize_block(2); assert!(!PristineCode::::contains_key(&code_hash)); - assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); - - assert_eq!( - System::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - },] - ); + expected_deposit(ensure_stored(code_hash)); }); } @@ -2586,32 +2489,8 @@ fn remove_code_works() { assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); - + expected_deposit(ensure_stored(code_hash)); assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeRemoved { - code_hash, - deposit_released: deposit_expected, - remover: ALICE_ADDR - }), - topics: vec![], - }, - ] - ); }); } @@ -2627,25 +2506,12 @@ fn remove_code_wrong_origin() { assert_ok!(Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, 1_000,)); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); + expected_deposit(ensure_stored(code_hash)); assert_noop!( Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), sp_runtime::traits::BadOrigin, ); - - assert_eq!( - System::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - },] - ); }); } @@ -2704,7 +2570,7 @@ fn instantiate_with_zero_balance_works() { builder::bare_instantiate(Code::Upload(wasm)).build_and_unwrap_contract(); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); + expected_deposit(ensure_stored(code_hash)); // Make sure the account exists even though no free balance was send assert_eq!(::Currency::free_balance(&account_id), min_balance); @@ -2716,15 +2582,6 @@ fn instantiate_with_zero_balance_works() { assert_eq!( System::events(), vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { @@ -2749,25 +2606,6 @@ fn instantiate_with_zero_balance_works() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -2790,7 +2628,7 @@ fn instantiate_with_below_existential_deposit_works() { .build_and_unwrap_contract(); // Ensure the contract was stored and get expected deposit amount to be reserved. - let deposit_expected = expected_deposit(ensure_stored(code_hash)); + expected_deposit(ensure_stored(code_hash)); // Make sure the account exists even though not enough free balance was send assert_eq!(::Currency::free_balance(&account_id), min_balance + value); assert_eq!( @@ -2801,15 +2639,6 @@ fn instantiate_with_below_existential_deposit_works() { assert_eq!( System::events(), vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { - code_hash, - deposit_held: deposit_expected, - uploader: ALICE_ADDR - }), - topics: vec![], - }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { @@ -2843,25 +2672,6 @@ fn instantiate_with_below_existential_deposit_works() { }), topics: vec![], }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Instantiated { - deployer: ALICE_ADDR, - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: test_utils::contract_info_storage_deposit(&addr), - } - ), - topics: vec![], - }, ] ); }); @@ -2903,74 +2713,15 @@ fn storage_deposit_works() { assert_eq!( System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: account_id.clone(), - amount: 42, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: charged0, - } - ), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndHeld { - from: ALICE_ADDR, - to: addr, - amount: charged1, - } - ), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts( - pallet_revive::Event::StorageDepositTransferredAndReleased { - from: addr, - to: ALICE_ADDR, - amount: refunded0, - } - ), - topics: vec![], - }, - ] + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: account_id.clone(), + amount: 42, + }), + topics: vec![], + },] ); }); } @@ -3063,18 +2814,6 @@ fn set_code_extrinsic() { assert_eq!(get_contract(&addr).code_hash, new_code_hash); assert_refcount!(&code_hash, 0); assert_refcount!(&new_code_hash, 1); - assert_eq!( - System::events(), - vec![EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(pallet_revive::Event::ContractCodeUpdated { - contract: addr, - new_code_hash, - old_code_hash: code_hash, - }), - topics: vec![], - },] - ); }); } @@ -3180,7 +2919,7 @@ fn contract_reverted() { #[test] fn set_code_hash() { - let (wasm, code_hash) = compile_module("set_code_hash").unwrap(); + let (wasm, _) = compile_module("set_code_hash").unwrap(); let (new_wasm, new_code_hash) = compile_module("new_set_code_hash_contract").unwrap(); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { @@ -3208,38 +2947,6 @@ fn set_code_hash() { // Second calls new contract code that returns 2 let result = builder::bare_call(contract_addr).build_and_unwrap_result(); assert_return_code!(result, 2); - - // Checking for the last event only - assert_eq!( - &System::events(), - &[ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::ContractCodeUpdated { - contract: contract_addr, - new_code_hash, - old_code_hash: code_hash, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract_addr, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: Origin::from_account_id(ALICE), - contract: contract_addr, - }), - topics: vec![], - }, - ], - ); }); } diff --git a/substrate/frame/revive/src/wasm/mod.rs b/substrate/frame/revive/src/wasm/mod.rs index b45d7026ba91..527cf1630954 100644 --- a/substrate/frame/revive/src/wasm/mod.rs +++ b/substrate/frame/revive/src/wasm/mod.rs @@ -29,14 +29,13 @@ pub use crate::wasm::runtime::{ReturnData, TrapReason}; pub use crate::wasm::runtime::{Memory, Runtime, RuntimeCosts}; use crate::{ - address::AddressMapper, exec::{ExecResult, Executable, ExportedFunction, Ext}, gas::{GasMeter, Token}, limits, storage::meter::Diff, weights::WeightInfo, - AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, Event, ExecError, - HoldReason, Pallet, PristineCode, Weight, LOG_TARGET, + AccountIdOf, BadOrigin, BalanceOf, CodeInfoOf, CodeVec, Config, Error, ExecError, HoldReason, + PristineCode, Weight, LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode, MaxEncodedLen}; @@ -157,16 +156,9 @@ where code_info.deposit, BestEffort, ); - let deposit_released = code_info.deposit; - let remover = T::AddressMapper::to_address(&code_info.owner); *existing = None; >::remove(&code_hash); - >::deposit_event(Event::CodeRemoved { - code_hash, - deposit_released, - remover, - }); Ok(()) } else { Err(>::CodeNotFound.into()) @@ -202,12 +194,6 @@ where self.code_info.refcount = 0; >::insert(code_hash, &self.code); *stored_code_info = Some(self.code_info.clone()); - let uploader = T::AddressMapper::to_address(&self.code_info.owner); - >::deposit_event(Event::CodeStored { - code_hash, - deposit_held: deposit, - uploader, - }); Ok(deposit) }, } From 412aca6c48a01f11318228f4d8a79fec544a22bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20M=C3=BCller?= Date: Wed, 15 Jan 2025 22:58:51 +0100 Subject: [PATCH 272/340] [pallet-revive] Add host function `to_account_id` (#7091) Closes https://github.com/paritytech/polkadot-sdk/issues/6891. cc @athei @xermicus @pgherveou --- prdoc/pr_7091.prdoc | 12 ++++++ .../fixtures/contracts/to_account_id.rs | 40 ++++++++++++++++++ .../frame/revive/src/benchmarking/mod.rs | 32 +++++++++++++++ substrate/frame/revive/src/exec.rs | 41 +++++++++++++++++++ substrate/frame/revive/src/tests.rs | 37 +++++++++++++++++ substrate/frame/revive/src/wasm/runtime.rs | 24 +++++++++++ substrate/frame/revive/src/weights.rs | 21 ++++++++++ substrate/frame/revive/uapi/src/host.rs | 12 ++++++ .../frame/revive/uapi/src/host/riscv64.rs | 6 +++ 9 files changed, 225 insertions(+) create mode 100644 prdoc/pr_7091.prdoc create mode 100644 substrate/frame/revive/fixtures/contracts/to_account_id.rs diff --git a/prdoc/pr_7091.prdoc b/prdoc/pr_7091.prdoc new file mode 100644 index 000000000000..badea4e82fdb --- /dev/null +++ b/prdoc/pr_7091.prdoc @@ -0,0 +1,12 @@ +title: '[pallet-revive] Add new host function `to_account_id`' +doc: +- audience: Runtime Dev + description: A new host function `to_account_id` is added. It allows retrieving + the account id for a `H160` address. +crates: +- name: pallet-revive-fixtures + bump: minor +- name: pallet-revive + bump: minor +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/fixtures/contracts/to_account_id.rs b/substrate/frame/revive/fixtures/contracts/to_account_id.rs new file mode 100644 index 000000000000..c2a8fce3ec99 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/to_account_id.rs @@ -0,0 +1,40 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!( + address: &[u8; 20], + expected_account_id: &[u8; 32], + ); + + let mut account_id = [0u8; 32]; + api::to_account_id(address, &mut account_id); + + assert!(&account_id == expected_account_id); +} diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index e23554f21ba8..18d7bb0afc31 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -556,6 +556,38 @@ mod benchmarks { assert_eq!(result.unwrap(), 1); } + #[benchmark(pov_mode = Measured)] + fn seal_to_account_id() { + // use a mapped address for the benchmark, to ensure that we bench the worst + // case (and not the fallback case). + let address = { + let caller = account("seal_to_account_id", 0, 0); + T::Currency::set_balance(&caller, caller_funding::()); + T::AddressMapper::map(&caller).unwrap(); + T::AddressMapper::to_address(&caller) + }; + + let len = ::max_encoded_len(); + build_runtime!(runtime, memory: [vec![0u8; len], address.0, ]); + + let result; + #[block] + { + result = runtime.bench_to_account_id(memory.as_mut_slice(), len as u32, 0); + } + + assert_ok!(result); + assert_ne!( + memory.as_slice()[20..32], + [0xEE; 12], + "fallback suffix found where none should be" + ); + assert_eq!( + T::AccountId::decode(&mut memory.as_slice()), + Ok(runtime.ext().to_account_id(&address)) + ); + } + #[benchmark(pov_mode = Measured)] fn seal_code_hash() { let contract = Contract::::with_index(1, WasmModule::dummy(), vec![]).unwrap(); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index 1c6ca435aefb..f696f75a4a13 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -293,6 +293,9 @@ pub trait Ext: sealing::Sealed { /// Check if a contract lives at the specified `address`. fn is_contract(&self, address: &H160) -> bool; + /// Returns the account id for the given `address`. + fn to_account_id(&self, address: &H160) -> AccountIdOf; + /// Returns the code hash of the contract for the given `address`. /// If not a contract but account exists then `keccak_256([])` is returned, otherwise `zero`. fn code_hash(&self, address: &H160) -> H256; @@ -1572,6 +1575,10 @@ where ContractInfoOf::::contains_key(&address) } + fn to_account_id(&self, address: &H160) -> T::AccountId { + T::AddressMapper::to_account_id(address) + } + fn code_hash(&self, address: &H160) -> H256 { >::get(&address) .map(|contract| contract.code_hash) @@ -2582,6 +2589,40 @@ mod tests { }); } + #[test] + fn to_account_id_returns_proper_values() { + let bob_code_hash = MockLoader::insert(Call, |ctx, _| { + let alice_account_id = ::AddressMapper::to_account_id(&ALICE_ADDR); + assert_eq!(ctx.ext.to_account_id(&ALICE_ADDR), alice_account_id); + + const UNMAPPED_ADDR: H160 = H160([99u8; 20]); + let mut unmapped_fallback_account_id = [0xEE; 32]; + unmapped_fallback_account_id[..20].copy_from_slice(UNMAPPED_ADDR.as_bytes()); + assert_eq!( + ctx.ext.to_account_id(&UNMAPPED_ADDR), + AccountId32::new(unmapped_fallback_account_id) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + place_contract(&BOB, bob_code_hash); + let origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&origin, 0, 0).unwrap(); + let result = MockStack::run_call( + origin, + BOB_ADDR, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + U256::zero(), + vec![0], + false, + ); + assert_matches!(result, Ok(_)); + }); + } + #[test] fn code_hash_returns_proper_values() { let bob_code_hash = MockLoader::insert(Call, |ctx, _| { diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 35940f544d00..8398bc2cb66f 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -4239,6 +4239,43 @@ fn origin_api_works() { }); } +#[test] +fn to_account_id_works() { + let (code_hash_code, _) = compile_module("to_account_id").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + let _ = ::Currency::set_balance(&EVE, 1_000_000); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code_hash_code)).build_and_unwrap_contract(); + + // mapped account + >::map_account(RuntimeOrigin::signed(EVE)).unwrap(); + let expected_mapped_account_id = &::AddressMapper::to_account_id(&EVE_ADDR); + assert_ne!( + expected_mapped_account_id.encode()[20..32], + [0xEE; 12], + "fallback suffix found where none should be" + ); + assert_ok!(builder::call(addr) + .data((EVE_ADDR, expected_mapped_account_id).encode()) + .build()); + + // fallback for unmapped accounts + let expected_fallback_account_id = + &::AddressMapper::to_account_id(&BOB_ADDR); + assert_eq!( + expected_fallback_account_id.encode()[20..32], + [0xEE; 12], + "no fallback suffix found where one should be" + ); + assert_ok!(builder::call(addr) + .data((BOB_ADDR, expected_fallback_account_id).encode()) + .build()); + }); +} + #[test] fn code_hash_works() { let (code_hash_code, self_code_hash) = compile_module("code_hash").unwrap(); diff --git a/substrate/frame/revive/src/wasm/runtime.rs b/substrate/frame/revive/src/wasm/runtime.rs index 1ff6a80840a9..4fbcfe1b47f5 100644 --- a/substrate/frame/revive/src/wasm/runtime.rs +++ b/substrate/frame/revive/src/wasm/runtime.rs @@ -293,6 +293,8 @@ pub enum RuntimeCosts { CallDataSize, /// Weight of calling `seal_return_data_size`. ReturnDataSize, + /// Weight of calling `seal_to_account_id`. + ToAccountId, /// Weight of calling `seal_origin`. Origin, /// Weight of calling `seal_is_contract`. @@ -466,6 +468,7 @@ impl Token for RuntimeCosts { Caller => T::WeightInfo::seal_caller(), Origin => T::WeightInfo::seal_origin(), IsContract => T::WeightInfo::seal_is_contract(), + ToAccountId => T::WeightInfo::seal_to_account_id(), CodeHash => T::WeightInfo::seal_code_hash(), CodeSize => T::WeightInfo::seal_code_size(), OwnCodeHash => T::WeightInfo::seal_own_code_hash(), @@ -2140,4 +2143,25 @@ pub mod env { }, } } + + /// Retrieves the account id for a specified contract address. + /// + /// See [`pallet_revive_uapi::HostFn::to_account_id`]. + fn to_account_id( + &mut self, + memory: &mut M, + addr_ptr: u32, + out_ptr: u32, + ) -> Result<(), TrapReason> { + self.charge_gas(RuntimeCosts::ToAccountId)?; + let address = memory.read_h160(addr_ptr)?; + let account_id = self.ext.to_account_id(&address); + Ok(self.write_fixed_sandbox_output( + memory, + out_ptr, + &account_id.encode(), + false, + already_charged, + )?) + } } diff --git a/substrate/frame/revive/src/weights.rs b/substrate/frame/revive/src/weights.rs index 06495d5d21aa..52153d74ca75 100644 --- a/substrate/frame/revive/src/weights.rs +++ b/substrate/frame/revive/src/weights.rs @@ -67,6 +67,7 @@ pub trait WeightInfo { fn seal_caller() -> Weight; fn seal_origin() -> Weight; fn seal_is_contract() -> Weight; + fn seal_to_account_id() -> Weight; fn seal_code_hash() -> Weight; fn seal_own_code_hash() -> Weight; fn seal_code_size() -> Weight; @@ -377,6 +378,16 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(10_336_000, 3771) .saturating_add(T::DbWeight::get().reads(1_u64)) } + /// Storage: `Revive::AddressSuffix` (r:1 w:0) + /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) + fn seal_to_account_id() -> Weight { + // Proof Size summary in bytes: + // Measured: `212` + // Estimated: `3677` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 3677) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) fn seal_code_hash() -> Weight { @@ -1263,6 +1274,16 @@ impl WeightInfo for () { Weight::from_parts(10_336_000, 3771) .saturating_add(RocksDbWeight::get().reads(1_u64)) } + /// Storage: `Revive::AddressSuffix` (r:1 w:0) + /// Proof: `Revive::AddressSuffix` (`max_values`: None, `max_size`: Some(32), added: 2507, mode: `Measured`) + fn seal_to_account_id() -> Weight { + // Proof Size summary in bytes: + // Measured: `212` + // Estimated: `3677` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(4_000_000, 3677) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } /// Storage: `Revive::ContractInfoOf` (r:1 w:0) /// Proof: `Revive::ContractInfoOf` (`max_values`: None, `max_size`: Some(1779), added: 4254, mode: `Measured`) fn seal_code_hash() -> Weight { diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index b82393826ddf..3e5cf0eb0c24 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -144,6 +144,18 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the origin's address. fn origin(output: &mut [u8; 20]); + /// Retrieve the account id for a specified address. + /// + /// # Parameters + /// + /// - `addr`: A `H160` address. + /// - `output`: A reference to the output data buffer to write the account id. + /// + /// # Note + /// + /// If no mapping exists for `addr`, the fallback account id will be returned. + fn to_account_id(addr: &[u8; 20], output: &mut [u8]); + /// Retrieve the code hash for a specified contract address. /// /// # Parameters diff --git a/substrate/frame/revive/uapi/src/host/riscv64.rs b/substrate/frame/revive/uapi/src/host/riscv64.rs index 0023b8aa721d..3726564e26eb 100644 --- a/substrate/frame/revive/uapi/src/host/riscv64.rs +++ b/substrate/frame/revive/uapi/src/host/riscv64.rs @@ -69,6 +69,7 @@ mod sys { pub fn caller(out_ptr: *mut u8); pub fn origin(out_ptr: *mut u8); pub fn is_contract(account_ptr: *const u8) -> ReturnCode; + pub fn to_account_id(address_ptr: *const u8, out_ptr: *mut u8); pub fn code_hash(address_ptr: *const u8, out_ptr: *mut u8); pub fn code_size(address_ptr: *const u8) -> u64; pub fn own_code_hash(out_ptr: *mut u8); @@ -456,6 +457,11 @@ impl HostFn for HostFnImpl { unsafe { sys::ref_time_left() } } + #[unstable_hostfn] + fn to_account_id(address: &[u8; 20], output: &mut [u8]) { + unsafe { sys::to_account_id(address.as_ptr(), output.as_mut_ptr()) } + } + #[unstable_hostfn] fn block_hash(block_number_ptr: &[u8; 32], output: &mut [u8; 32]) { unsafe { sys::block_hash(block_number_ptr.as_ptr(), output.as_mut_ptr()) }; From be2404cccd9923c41e2f16bfe655f19574f1ae0e Mon Sep 17 00:00:00 2001 From: liamaharon Date: Thu, 16 Jan 2025 10:26:59 +0400 Subject: [PATCH 273/340] Implement `pallet-asset-rewards` (#3926) Closes #3149 ## Description This PR introduces `pallet-asset-rewards`, which allows accounts to be rewarded for freezing `fungible` tokens. The motivation for creating this pallet is to allow incentivising LPs. See the pallet docs for more info about the pallet. ## Runtime changes The pallet has been added to - `asset-hub-rococo` - `asset-hub-westend` The `NativeAndAssets` `fungibles` Union did not contain `PoolAssets`, so it has been renamed `NativeAndNonPoolAssets` A new `fungibles` Union `NativeAndAllAssets` was created to encompass all assets and the native token. ## TODO - [x] Emulation tests - [x] Fill in Freeze logic (blocked https://github.com/paritytech/polkadot-sdk/issues/3342) and re-run benchmarks --------- Co-authored-by: command-bot <> Co-authored-by: Oliver Tale-Yazdi Co-authored-by: muharem Co-authored-by: Guillaume Thiolliere --- Cargo.lock | 27 + Cargo.toml | 2 + .../assets/asset-hub-rococo/Cargo.toml | 1 + .../emulated/common/src/lib.rs | 2 + .../tests/assets/asset-hub-rococo/Cargo.toml | 1 + .../tests/assets/asset-hub-rococo/src/lib.rs | 3 +- .../assets/asset-hub-rococo/src/tests/mod.rs | 1 + .../asset-hub-rococo/src/tests/reward_pool.rs | 114 ++ .../tests/assets/asset-hub-westend/Cargo.toml | 1 + .../tests/assets/asset-hub-westend/src/lib.rs | 8 +- .../assets/asset-hub-westend/src/tests/mod.rs | 1 + .../src/tests/reward_pool.rs | 113 ++ .../assets/asset-hub-rococo/Cargo.toml | 5 + .../assets/asset-hub-rococo/src/lib.rs | 142 +- .../asset-hub-rococo/src/weights/mod.rs | 1 + .../src/weights/pallet_asset_rewards.rs | 217 +++ .../assets/asset-hub-rococo/src/xcm_config.rs | 10 +- .../assets/asset-hub-westend/Cargo.toml | 5 + .../assets/asset-hub-westend/src/lib.rs | 144 +- .../asset-hub-westend/src/weights/mod.rs | 1 + .../src/weights/pallet_asset_rewards.rs | 217 +++ .../asset-hub-westend/src/xcm_config.rs | 7 +- .../runtimes/assets/common/src/lib.rs | 7 +- polkadot/runtime/rococo/src/xcm_config.rs | 20 +- polkadot/runtime/westend/src/xcm_config.rs | 5 +- prdoc/pr_3926.prdoc | 30 + substrate/bin/node/runtime/src/lib.rs | 77 +- substrate/frame/asset-rewards/Cargo.toml | 71 + .../frame/asset-rewards/src/benchmarking.rs | 355 ++++ substrate/frame/asset-rewards/src/lib.rs | 905 ++++++++++ substrate/frame/asset-rewards/src/mock.rs | 221 +++ substrate/frame/asset-rewards/src/tests.rs | 1457 +++++++++++++++++ substrate/frame/asset-rewards/src/weights.rs | 368 +++++ substrate/frame/support/src/traits.rs | 5 +- substrate/frame/support/src/traits/storage.rs | 12 + umbrella/Cargo.toml | 10 +- umbrella/src/lib.rs | 4 + 37 files changed, 4517 insertions(+), 53 deletions(-) create mode 100644 cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs create mode 100644 cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs create mode 100644 prdoc/pr_3926.prdoc create mode 100644 substrate/frame/asset-rewards/Cargo.toml create mode 100644 substrate/frame/asset-rewards/src/benchmarking.rs create mode 100644 substrate/frame/asset-rewards/src/lib.rs create mode 100644 substrate/frame/asset-rewards/src/mock.rs create mode 100644 substrate/frame/asset-rewards/src/tests.rs create mode 100644 substrate/frame/asset-rewards/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 0d71a770d38b..6eba7e651096 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -910,6 +910,7 @@ dependencies = [ "cumulus-primitives-core 0.7.0", "emulated-integration-tests-common", "frame-support 28.0.0", + "pallet-asset-rewards", "parachains-common 7.0.0", "rococo-emulated-chain", "sp-core 28.0.0", @@ -928,6 +929,7 @@ dependencies = [ "emulated-integration-tests-common", "frame-support 28.0.0", "pallet-asset-conversion 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-message-queue 31.0.0", @@ -978,6 +980,7 @@ dependencies = [ "pallet-asset-conversion 10.0.0", "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", "pallet-aura 27.0.0", @@ -1063,6 +1066,7 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "pallet-asset-conversion 10.0.0", + "pallet-asset-rewards", "pallet-asset-tx-payment 28.0.0", "pallet-assets 29.1.0", "pallet-balances 28.0.0", @@ -1114,6 +1118,7 @@ dependencies = [ "pallet-asset-conversion 10.0.0", "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", + "pallet-asset-rewards", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", "pallet-aura 27.0.0", @@ -12036,6 +12041,27 @@ dependencies = [ "sp-runtime 39.0.2", ] +[[package]] +name = "pallet-asset-rewards" +version = "0.1.0" +dependencies = [ + "frame-benchmarking 28.0.0", + "frame-support 28.0.0", + "frame-system 28.0.0", + "pallet-assets 29.1.0", + "pallet-assets-freezer 0.1.0", + "pallet-balances 28.0.0", + "parity-scale-codec", + "primitive-types 0.13.1", + "scale-info", + "sp-api 26.0.0", + "sp-arithmetic 23.0.0", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-std 14.0.0", +] + [[package]] name = "pallet-asset-tx-payment" version = "28.0.0" @@ -18715,6 +18741,7 @@ dependencies = [ "pallet-asset-conversion-ops 0.1.0", "pallet-asset-conversion-tx-payment 10.0.0", "pallet-asset-rate 7.0.0", + "pallet-asset-rewards", "pallet-asset-tx-payment 28.0.0", "pallet-assets 29.1.0", "pallet-assets-freezer 0.1.0", diff --git a/Cargo.toml b/Cargo.toml index eb99b80e16fa..509775fe99e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,6 +315,7 @@ members = [ "substrate/frame/asset-conversion", "substrate/frame/asset-conversion/ops", "substrate/frame/asset-rate", + "substrate/frame/asset-rewards", "substrate/frame/assets", "substrate/frame/assets-freezer", "substrate/frame/atomic-swap", @@ -893,6 +894,7 @@ pallet-asset-conversion = { path = "substrate/frame/asset-conversion", default-f pallet-asset-conversion-ops = { path = "substrate/frame/asset-conversion/ops", default-features = false } pallet-asset-conversion-tx-payment = { path = "substrate/frame/transaction-payment/asset-conversion-tx-payment", default-features = false } pallet-asset-rate = { path = "substrate/frame/asset-rate", default-features = false } +pallet-asset-rewards = { path = "substrate/frame/asset-rewards", default-features = false } pallet-asset-tx-payment = { path = "substrate/frame/transaction-payment/asset-tx-payment", default-features = false } pallet-assets = { path = "substrate/frame/assets", default-features = false } pallet-assets-freezer = { path = "substrate/frame/assets-freezer", default-features = false } diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml index a164a8197f72..c6a8baeff3b3 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/Cargo.toml @@ -14,6 +14,7 @@ workspace = true # Substrate frame-support = { workspace = true } +pallet-asset-rewards = { workspace = true } sp-core = { workspace = true } sp-keyring = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index e2757f8b9a35..f5466a63f1f5 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -58,6 +58,8 @@ pub const USDT_ID: u32 = 1984; pub const PENPAL_A_ID: u32 = 2000; pub const PENPAL_B_ID: u32 = 2001; +pub const ASSET_HUB_ROCOCO_ID: u32 = 1000; +pub const ASSET_HUB_WESTEND_ID: u32 = 1000; pub const ASSETS_PALLET_ID: u8 = 50; parameter_types! { diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml index 9e8b8f2a52d7..b53edb39c73b 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/Cargo.toml @@ -17,6 +17,7 @@ codec = { workspace = true } # Substrate frame-support = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-assets = { workspace = true } pallet-balances = { workspace = true } pallet-message-queue = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs index f3a1b3f5bfa2..513ca278a319 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/lib.rs @@ -76,10 +76,11 @@ mod imports { genesis::ED as ROCOCO_ED, rococo_runtime::{ governance as rococo_governance, + governance::pallet_custom_origins::Origin::Treasurer, xcm_config::{ UniversalLocation as RococoUniversalLocation, XcmConfig as RococoXcmConfig, }, - OriginCaller as RococoOriginCaller, + Dmp, OriginCaller as RococoOriginCaller, }, RococoRelayPallet as RococoPallet, }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs index 88fa379c4072..75714acb07cd 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/mod.rs @@ -16,6 +16,7 @@ mod claim_assets; mod hybrid_transfers; mod reserve_transfer; +mod reward_pool; mod send; mod set_xcm_versions; mod swap; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs new file mode 100644 index 000000000000..2f3ee536a7b9 --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-rococo/src/tests/reward_pool.rs @@ -0,0 +1,114 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::imports::*; +use codec::Encode; +use frame_support::{assert_ok, sp_runtime::traits::Dispatchable, traits::schedule::DispatchTime}; +use xcm_executor::traits::ConvertLocation; + +#[test] +fn treasury_creates_asset_reward_pool() { + AssetHubRococo::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + let treasurer = + Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]); + let treasurer_account = + ahr_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap(); + + assert_ok!(Balances::force_set_balance( + ::RuntimeOrigin::root(), + treasurer_account.clone().into(), + ASSET_HUB_ROCOCO_ED * 100_000, + )); + + let events = AssetHubRococo::events(); + match events.iter().last() { + Some(RuntimeEvent::Balances(pallet_balances::Event::BalanceSet { who, .. })) => + assert_eq!(*who, treasurer_account), + _ => panic!("Expected Balances::BalanceSet event"), + } + }); + + Rococo::execute_with(|| { + type AssetHubRococoRuntimeCall = ::RuntimeCall; + type AssetHubRococoRuntime = ::Runtime; + type RococoRuntimeCall = ::RuntimeCall; + type RococoRuntime = ::Runtime; + type RococoRuntimeEvent = ::RuntimeEvent; + type RococoRuntimeOrigin = ::RuntimeOrigin; + + Dmp::make_parachain_reachable(AssetHubRococo::para_id()); + + let staked_asset_id = bx!(RelayLocation::get()); + let reward_asset_id = bx!(RelayLocation::get()); + + let reward_rate_per_block = 1_000_000_000; + let lifetime = 1_000_000_000; + let admin = None; + + let create_pool_call = + RococoRuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::V4( + xcm::v4::Junction::Parachain(AssetHubRococo::para_id().into()).into() + )), + message: bx!(VersionedXcm::V5(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::SovereignAccount, + fallback_max_weight: None, + call: AssetHubRococoRuntimeCall::AssetRewards( + pallet_asset_rewards::Call::::create_pool { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + expiry: DispatchTime::After(lifetime), + admin + } + ) + .encode() + .into(), + } + ]))), + }); + + let treasury_origin: RococoRuntimeOrigin = Treasurer.into(); + assert_ok!(create_pool_call.dispatch(treasury_origin)); + + assert_expected_events!( + Rococo, + vec![ + RococoRuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + AssetHubRococo::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeEvent = ::RuntimeEvent; + + assert_eq!(1, pallet_asset_rewards::Pools::::iter().count()); + + let events = AssetHubRococo::events(); + match events.iter().last() { + Some(RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { + success: true, + .. + })) => (), + _ => panic!("Expected MessageQueue::Processed event"), + } + }); +} diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml index 5cd00c239e60..ef68a53c3b18 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/Cargo.toml @@ -19,6 +19,7 @@ frame-metadata-hash-extension = { workspace = true, default-features = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-asset-conversion = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-asset-tx-payment = { workspace = true } pallet-assets = { workspace = true } pallet-balances = { workspace = true } diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs index 36630e2d2221..68dc87250f76 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/lib.rs @@ -79,8 +79,12 @@ mod imports { }, westend_emulated_chain::{ genesis::ED as WESTEND_ED, - westend_runtime::xcm_config::{ - UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig, + westend_runtime::{ + governance::pallet_custom_origins::Origin::Treasurer, + xcm_config::{ + UniversalLocation as WestendUniversalLocation, XcmConfig as WestendXcmConfig, + }, + Dmp, }, WestendRelayPallet as WestendPallet, }, diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs index 0dfe7a85f4c2..576c44fc542f 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/mod.rs @@ -17,6 +17,7 @@ mod claim_assets; mod fellowship_treasury; mod hybrid_transfers; mod reserve_transfer; +mod reward_pool; mod send; mod set_asset_claimer; mod set_xcm_versions; diff --git a/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs new file mode 100644 index 000000000000..4df51abcaceb --- /dev/null +++ b/cumulus/parachains/integration-tests/emulated/tests/assets/asset-hub-westend/src/tests/reward_pool.rs @@ -0,0 +1,113 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::imports::*; +use codec::Encode; +use frame_support::{assert_ok, sp_runtime::traits::Dispatchable, traits::schedule::DispatchTime}; +use xcm_executor::traits::ConvertLocation; + +#[test] +fn treasury_creates_asset_reward_pool() { + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type Balances = ::Balances; + + let treasurer = + Location::new(1, [Plurality { id: BodyId::Treasury, part: BodyPart::Voice }]); + let treasurer_account = + ahw_xcm_config::LocationToAccountId::convert_location(&treasurer).unwrap(); + + assert_ok!(Balances::force_set_balance( + ::RuntimeOrigin::root(), + treasurer_account.clone().into(), + ASSET_HUB_WESTEND_ED * 100_000, + )); + + let events = AssetHubWestend::events(); + match events.iter().last() { + Some(RuntimeEvent::Balances(pallet_balances::Event::BalanceSet { who, .. })) => + assert_eq!(*who, treasurer_account), + _ => panic!("Expected Balances::BalanceSet event"), + } + }); + Westend::execute_with(|| { + type AssetHubWestendRuntimeCall = ::RuntimeCall; + type AssetHubWestendRuntime = ::Runtime; + type WestendRuntimeCall = ::RuntimeCall; + type WestendRuntime = ::Runtime; + type WestendRuntimeEvent = ::RuntimeEvent; + type WestendRuntimeOrigin = ::RuntimeOrigin; + + Dmp::make_parachain_reachable(AssetHubWestend::para_id()); + + let staked_asset_id = bx!(RelayLocation::get()); + let reward_asset_id = bx!(RelayLocation::get()); + + let reward_rate_per_block = 1_000_000_000; + let lifetime = 1_000_000_000; + let admin = None; + + let create_pool_call = + WestendRuntimeCall::XcmPallet(pallet_xcm::Call::::send { + dest: bx!(VersionedLocation::V4( + xcm::v4::Junction::Parachain(AssetHubWestend::para_id().into()).into() + )), + message: bx!(VersionedXcm::V5(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + Transact { + origin_kind: OriginKind::SovereignAccount, + fallback_max_weight: None, + call: AssetHubWestendRuntimeCall::AssetRewards( + pallet_asset_rewards::Call::::create_pool { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + expiry: DispatchTime::After(lifetime), + admin + } + ) + .encode() + .into(), + } + ]))), + }); + + let treasury_origin: WestendRuntimeOrigin = Treasurer.into(); + assert_ok!(create_pool_call.dispatch(treasury_origin)); + + assert_expected_events!( + Westend, + vec![ + WestendRuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type Runtime = ::Runtime; + type RuntimeEvent = ::RuntimeEvent; + + assert_eq!(1, pallet_asset_rewards::Pools::::iter().count()); + + let events = AssetHubWestend::events(); + match events.iter().last() { + Some(RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { + success: true, + .. + })) => (), + _ => panic!("Expected MessageQueue::Processed event"), + } + }); +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index abe59a8439a8..d612dd03c247 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -30,6 +30,7 @@ frame-try-runtime = { optional = true, workspace = true } pallet-asset-conversion = { workspace = true } pallet-asset-conversion-ops = { workspace = true } pallet-asset-conversion-tx-payment = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-assets = { workspace = true } pallet-assets-freezer = { workspace = true } pallet-aura = { workspace = true } @@ -61,6 +62,7 @@ sp-storage = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } sp-weights = { workspace = true } + # num-traits feature needed for dex integer sq root: primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } @@ -123,6 +125,7 @@ runtime-benchmarks = [ "pallet-asset-conversion-ops/runtime-benchmarks", "pallet-asset-conversion-tx-payment/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", + "pallet-asset-rewards/runtime-benchmarks", "pallet-assets-freezer/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", @@ -162,6 +165,7 @@ try-runtime = [ "pallet-asset-conversion-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", + "pallet-asset-rewards/try-runtime", "pallet-assets-freezer/try-runtime", "pallet-assets/try-runtime", "pallet-aura/try-runtime", @@ -212,6 +216,7 @@ std = [ "pallet-asset-conversion-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", + "pallet-asset-rewards/std", "pallet-assets-freezer/std", "pallet-assets/std", "pallet-aura/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs index db9a8201ebbe..43b7bf0ba118 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs @@ -35,7 +35,7 @@ use assets_common::{ foreign_creators::ForeignCreators, local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, matching::{FromNetwork, FromSiblingParachain}, - AssetIdForTrustBackedAssetsConvert, + AssetIdForPoolAssets, AssetIdForPoolAssetsConvert, AssetIdForTrustBackedAssetsConvert, }; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{AggregateMessageOrigin, ClaimQueueOffset, CoreSelector}; @@ -61,9 +61,9 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, ord_parameter_types, parameter_types, traits::{ - fungible, fungibles, tokens::imbalance::ResolveAssetTo, AsEnsureOriginWithArg, ConstBool, - ConstU128, ConstU32, ConstU64, ConstU8, EitherOfDiverse, Equals, InstanceFilter, - TransformOrigin, + fungible, fungible::HoldConsideration, fungibles, tokens::imbalance::ResolveAssetTo, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, + ConstantStoragePrice, EitherOfDiverse, Equals, InstanceFilter, TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, BoundedVec, PalletId, @@ -84,8 +84,8 @@ use sp_runtime::{Perbill, RuntimeDebug}; use testnet_parachains_constants::rococo::{consensus::*, currency::*, fee::WeightToFee, time::*}; use xcm_config::{ ForeignAssetsConvertedConcreteId, GovernanceLocation, LocationToAccountId, - PoolAssetsConvertedConcreteId, TokenLocation, TrustBackedAssetsConvertedConcreteId, - TrustBackedAssetsPalletLocation, + PoolAssetsConvertedConcreteId, PoolAssetsPalletLocation, TokenLocation, + TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocation, }; #[cfg(test)] @@ -111,6 +111,9 @@ use xcm_runtime_apis::{ fees::Error as XcmPaymentApiError, }; +#[cfg(feature = "runtime-benchmarks")] +use frame_support::traits::PalletInfoAccess; + use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; impl_opaque_keys! { @@ -217,8 +220,8 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); - type MaxFreezes = ConstU32<0>; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; type DoneSlashHandler = (); } @@ -302,7 +305,7 @@ impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = Balance; type RemoveItemsLimit = ConstU32<1000>; - type AssetId = u32; + type AssetId = AssetIdForPoolAssets; type AssetIdParameter = u32; type Currency = Balances; type CreateOrigin = @@ -343,8 +346,21 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< AccountId, >; -/// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. -pub type NativeAndAssets = fungible::UnionOf< +/// Union fungibles implementation for `AssetsFreezer` and `ForeignAssetsFreezer`. +pub type LocalAndForeignAssetsFreezer = fungibles::UnionOf< + AssetsFreezer, + ForeignAssetsFreezer, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`LocalAndForeignAssets`] and [`Balances`]. +pub type NativeAndNonPoolAssets = fungible::UnionOf< Balances, LocalAndForeignAssets, TargetFromLeft, @@ -352,6 +368,45 @@ pub type NativeAndAssets = fungible::UnionOf< AccountId, >; +/// Union fungibles implementation for [`LocalAndForeignAssetsFreezer`] and [`Balances`]. +pub type NativeAndNonPoolAssetsFreezer = fungible::UnionOf< + Balances, + LocalAndForeignAssetsFreezer, + TargetFromLeft, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssets`] and [`NativeAndNonPoolAssets`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssets = fungibles::UnionOf< + PoolAssets, + NativeAndNonPoolAssets, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssetsFreezer`] and [`NativeAndNonPoolAssetsFreezer`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssetsFreezer = fungibles::UnionOf< + PoolAssetsFreezer, + NativeAndNonPoolAssetsFreezer, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< AssetConversionPalletId, (xcm::v5::Location, xcm::v5::Location), @@ -362,7 +417,7 @@ impl pallet_asset_conversion::Config for Runtime { type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; type AssetKind = xcm::v5::Location; - type Assets = NativeAndAssets; + type Assets = NativeAndNonPoolAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< TokenLocation, @@ -823,9 +878,9 @@ impl pallet_asset_conversion_tx_payment::Config for Runtime { type AssetId = xcm::v5::Location; type OnChargeAssetTransaction = SwapAssetAdapter< TokenLocation, - NativeAndAssets, + NativeAndNonPoolAssets, AssetConversion, - ResolveAssetTo, + ResolveAssetTo, >; type WeightInfo = weights::pallet_asset_conversion_tx_payment::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -953,6 +1008,55 @@ impl pallet_xcm_bridge_hub_router::Config for Runtim type FeeAsset = xcm_config::bridging::XcmBridgeHubRouterFeeAssetId; } +#[cfg(feature = "runtime-benchmarks")] +pub struct PalletAssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper + for PalletAssetRewardsBenchmarkHelper +{ + fn staked_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(100)], + ) + } + fn reward_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(101)], + ) + } +} + +parameter_types! { + pub const AssetRewardsPalletId: PalletId = PalletId(*b"py/astrd"); + pub const RewardsPoolCreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = AssetRewardsPalletId; + type Balance = Balance; + type Assets = NativeAndAllAssets; + type AssetsFreezer = NativeAndAllAssetsFreezer; + type AssetId = xcm::v5::Location; + type CreatePoolOrigin = EnsureSigned; + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + AccountId, + Balances, + RewardsPoolCreationHoldReason, + ConstantStoragePrice, + >; + type WeightInfo = weights::pallet_asset_rewards::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = PalletAssetRewardsBenchmarkHelper; +} + // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( pub enum Runtime @@ -998,10 +1102,13 @@ construct_runtime!( NftFractionalization: pallet_nft_fractionalization = 54, PoolAssets: pallet_assets:: = 55, AssetConversion: pallet_asset_conversion = 56, + AssetsFreezer: pallet_assets_freezer:: = 57, ForeignAssetsFreezer: pallet_assets_freezer:: = 58, PoolAssetsFreezer: pallet_assets_freezer:: = 59, + AssetRewards: pallet_asset_rewards = 60, + // TODO: the pallet instance should be removed once all pools have migrated // to the new account IDs. AssetConversionMigration: pallet_asset_conversion_ops = 200, @@ -1193,6 +1300,7 @@ mod benches { [pallet_assets, Foreign] [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetTxPayment] [pallet_balances, Balances] [pallet_message_queue, MessageQueue] @@ -1503,6 +1611,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { fn core_selector() -> (CoreSelector, ClaimQueueOffset) { ParachainSystem::core_selector() diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs index ae78a56d8b3c..6893766ac72d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/mod.rs @@ -24,6 +24,7 @@ pub mod frame_system_extensions; pub mod pallet_asset_conversion; pub mod pallet_asset_conversion_ops; pub mod pallet_asset_conversion_tx_payment; +pub mod pallet_asset_rewards; pub mod pallet_assets_foreign; pub mod pallet_assets_local; pub mod pallet_assets_pool; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs new file mode 100644 index 000000000000..218c93c51035 --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_asset_rewards.rs @@ -0,0 +1,217 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_rewards +// --chain=asset-hub-rococo-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_rewards`. +pub struct WeightInfo(PhantomData); +impl pallet_asset_rewards::WeightInfo for WeightInfo { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `358` + // Estimated: `6360` + // Minimum execution time: 65_882_000 picoseconds. + Weight::from_parts(67_073_000, 0) + .saturating_add(Weight::from_parts(0, 6360)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `872` + // Estimated: `4809` + // Minimum execution time: 56_950_000 picoseconds. + Weight::from_parts(58_088_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `872` + // Estimated: `4809` + // Minimum execution time: 59_509_000 picoseconds. + Weight::from_parts(61_064_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1072` + // Estimated: `6208` + // Minimum execution time: 80_685_000 picoseconds. + Weight::from_parts(83_505_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_032_000 picoseconds. + Weight::from_parts(17_628_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 15_290_000 picoseconds. + Weight::from_parts(16_212_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_721_000 picoseconds. + Weight::from_parts(18_603_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `747` + // Estimated: `6208` + // Minimum execution time: 67_754_000 picoseconds. + Weight::from_parts(69_428_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:0 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1105` + // Estimated: `6208` + // Minimum execution time: 127_524_000 picoseconds. + Weight::from_parts(130_238_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(10)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs index 08b2f520c4b9..0c6ff5e4bfdd 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs @@ -76,6 +76,10 @@ parameter_types! { pub TrustBackedAssetsPalletLocation: Location = PalletInstance(TrustBackedAssetsPalletIndex::get()).into(); pub TrustBackedAssetsPalletIndex: u8 = ::index() as u8; + pub TrustBackedAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(::index() as u8).into(); + pub PoolAssetsPalletLocationV3: xcm::v3::Location = + xcm::v3::Junction::PalletInstance(::index() as u8).into(); pub ForeignAssetsPalletLocation: Location = PalletInstance(::index() as u8).into(); pub PoolAssetsPalletLocation: Location = @@ -336,7 +340,7 @@ pub type TrustedTeleporters = ( /// asset and the asset required for fee payment. pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, @@ -387,7 +391,7 @@ impl xcm_executor::Config for XcmConfig { TokenLocation, crate::AssetConversion, WeightToFee, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation< TrustBackedAssetsPalletLocation, @@ -396,7 +400,7 @@ impl xcm_executor::Config for XcmConfig { >, ForeignAssetsConvertedConcreteId, ), - ResolveAssetTo, + ResolveAssetTo, AccountId, >, // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml index cb10ae9a4800..65ef63a7fb35 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml @@ -30,6 +30,7 @@ frame-try-runtime = { optional = true, workspace = true } pallet-asset-conversion = { workspace = true } pallet-asset-conversion-ops = { workspace = true } pallet-asset-conversion-tx-payment = { workspace = true } +pallet-asset-rewards = { workspace = true } pallet-assets = { workspace = true } pallet-assets-freezer = { workspace = true } pallet-aura = { workspace = true } @@ -62,6 +63,7 @@ sp-std = { workspace = true } sp-storage = { workspace = true } sp-transaction-pool = { workspace = true } sp-version = { workspace = true } + # num-traits feature needed for dex integer sq root: primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true } @@ -125,6 +127,7 @@ runtime-benchmarks = [ "pallet-asset-conversion-ops/runtime-benchmarks", "pallet-asset-conversion-tx-payment/runtime-benchmarks", "pallet-asset-conversion/runtime-benchmarks", + "pallet-asset-rewards/runtime-benchmarks", "pallet-assets-freezer/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", @@ -166,6 +169,7 @@ try-runtime = [ "pallet-asset-conversion-ops/try-runtime", "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-conversion/try-runtime", + "pallet-asset-rewards/try-runtime", "pallet-assets-freezer/try-runtime", "pallet-assets/try-runtime", "pallet-aura/try-runtime", @@ -218,6 +222,7 @@ std = [ "pallet-asset-conversion-ops/std", "pallet-asset-conversion-tx-payment/std", "pallet-asset-conversion/std", + "pallet-asset-rewards/std", "pallet-assets-freezer/std", "pallet-assets/std", "pallet-aura/std", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 5966dd01f18f..3ef5e87f24c4 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -33,7 +33,7 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use assets_common::{ local_and_foreign_assets::{LocalFromLeft, TargetFromLeft}, - AssetIdForTrustBackedAssetsConvert, + AssetIdForPoolAssets, AssetIdForPoolAssetsConvert, AssetIdForTrustBackedAssetsConvert, }; use codec::{Decode, Encode, MaxEncodedLen}; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; @@ -44,10 +44,12 @@ use frame_support::{ genesis_builder_helper::{build_state, get_preset}, ord_parameter_types, parameter_types, traits::{ - fungible, fungibles, + fungible, + fungible::HoldConsideration, + fungibles, tokens::{imbalance::ResolveAssetTo, nonfungibles_v2::Inspect}, - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, Equals, - InstanceFilter, Nothing, TransformOrigin, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, + ConstantStoragePrice, Equals, InstanceFilter, Nothing, TransformOrigin, }, weights::{ConstantMultiplier, Weight, WeightToFee as _}, BoundedVec, PalletId, @@ -81,8 +83,8 @@ use testnet_parachains_constants::westend::{ }; use xcm_config::{ ForeignAssetsConvertedConcreteId, LocationToAccountId, PoolAssetsConvertedConcreteId, - TrustBackedAssetsConvertedConcreteId, TrustBackedAssetsPalletLocation, WestendLocation, - XcmOriginToTransactDispatchOrigin, + PoolAssetsPalletLocation, TrustBackedAssetsConvertedConcreteId, + TrustBackedAssetsPalletLocation, WestendLocation, XcmOriginToTransactDispatchOrigin, }; #[cfg(any(feature = "std", test))] @@ -93,11 +95,15 @@ use assets_common::{ matching::{FromNetwork, FromSiblingParachain}, }; use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate}; +use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; use xcm::{ latest::prelude::AssetId, prelude::{VersionedAsset, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm}, }; +#[cfg(feature = "runtime-benchmarks")] +use frame_support::traits::PalletInfoAccess; + #[cfg(feature = "runtime-benchmarks")] use xcm::latest::prelude::{ Asset, Assets as XcmAssets, Fungible, Here, InteriorLocation, Junction, Junction::*, Location, @@ -109,8 +115,6 @@ use xcm_runtime_apis::{ fees::Error as XcmPaymentApiError, }; -use weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}; - impl_opaque_keys! { pub struct SessionKeys { pub aura: Aura, @@ -218,8 +222,8 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; - type FreezeIdentifier = (); - type MaxFreezes = ConstU32<0>; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; type DoneSlashHandler = (); } @@ -341,8 +345,22 @@ pub type LocalAndForeignAssets = fungibles::UnionOf< xcm::v5::Location, AccountId, >; + +/// Union fungibles implementation for `AssetsFreezer` and `ForeignAssetsFreezer`. +pub type LocalAndForeignAssetsFreezer = fungibles::UnionOf< + AssetsFreezer, + ForeignAssetsFreezer, + LocalFromLeft< + AssetIdForTrustBackedAssetsConvert, + AssetIdForTrustBackedAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + /// Union fungibles implementation for [`LocalAndForeignAssets`] and `Balances`. -pub type NativeAndAssets = fungible::UnionOf< +pub type NativeAndNonPoolAssets = fungible::UnionOf< Balances, LocalAndForeignAssets, TargetFromLeft, @@ -350,6 +368,45 @@ pub type NativeAndAssets = fungible::UnionOf< AccountId, >; +/// Union fungibles implementation for [`LocalAndForeignAssetsFreezer`] and [`Balances`]. +pub type NativeAndNonPoolAssetsFreezer = fungible::UnionOf< + Balances, + LocalAndForeignAssetsFreezer, + TargetFromLeft, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssets`] and [`NativeAndNonPoolAssets`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssets = fungibles::UnionOf< + PoolAssets, + NativeAndNonPoolAssets, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + +/// Union fungibles implementation for [`PoolAssetsFreezer`] and [`NativeAndNonPoolAssetsFreezer`]. +/// +/// NOTE: Should be kept updated to include ALL balances and assets in the runtime. +pub type NativeAndAllAssetsFreezer = fungibles::UnionOf< + PoolAssetsFreezer, + NativeAndNonPoolAssetsFreezer, + LocalFromLeft< + AssetIdForPoolAssetsConvert, + AssetIdForPoolAssets, + xcm::v5::Location, + >, + xcm::v5::Location, + AccountId, +>; + pub type PoolIdToAccountId = pallet_asset_conversion::AccountIdConverter< AssetConversionPalletId, (xcm::v5::Location, xcm::v5::Location), @@ -360,7 +417,7 @@ impl pallet_asset_conversion::Config for Runtime { type Balance = Balance; type HigherPrecisionBalance = sp_core::U256; type AssetKind = xcm::v5::Location; - type Assets = NativeAndAssets; + type Assets = NativeAndNonPoolAssets; type PoolId = (Self::AssetKind, Self::AssetKind); type PoolLocator = pallet_asset_conversion::WithFirstAsset< WestendLocation, @@ -388,6 +445,55 @@ impl pallet_asset_conversion::Config for Runtime { >; } +#[cfg(feature = "runtime-benchmarks")] +pub struct PalletAssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper + for PalletAssetRewardsBenchmarkHelper +{ + fn staked_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(100)], + ) + } + fn reward_asset() -> Location { + Location::new( + 0, + [PalletInstance(::index() as u8), GeneralIndex(101)], + ) + } +} + +parameter_types! { + pub const AssetRewardsPalletId: PalletId = PalletId(*b"py/astrd"); + pub const RewardsPoolCreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = AssetRewardsPalletId; + type Balance = Balance; + type Assets = NativeAndAllAssets; + type AssetsFreezer = NativeAndAllAssetsFreezer; + type AssetId = xcm::v5::Location; + type CreatePoolOrigin = EnsureSigned; + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + AccountId, + Balances, + RewardsPoolCreationHoldReason, + ConstantStoragePrice, + >; + type WeightInfo = weights::pallet_asset_rewards::WeightInfo; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = PalletAssetRewardsBenchmarkHelper; +} + impl pallet_asset_conversion_ops::Config for Runtime { type RuntimeEvent = RuntimeEvent; type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed< @@ -816,9 +922,9 @@ impl pallet_asset_conversion_tx_payment::Config for Runtime { type AssetId = xcm::v5::Location; type OnChargeAssetTransaction = SwapAssetAdapter< WestendLocation, - NativeAndAssets, + NativeAndNonPoolAssets, AssetConversion, - ResolveAssetTo, + ResolveAssetTo, >; type WeightInfo = weights::pallet_asset_conversion_tx_payment::WeightInfo; #[cfg(feature = "runtime-benchmarks")] @@ -1035,11 +1141,14 @@ construct_runtime!( NftFractionalization: pallet_nft_fractionalization = 54, PoolAssets: pallet_assets:: = 55, AssetConversion: pallet_asset_conversion = 56, + AssetsFreezer: pallet_assets_freezer:: = 57, ForeignAssetsFreezer: pallet_assets_freezer:: = 58, PoolAssetsFreezer: pallet_assets_freezer:: = 59, Revive: pallet_revive = 60, + AssetRewards: pallet_asset_rewards = 61, + StateTrieMigration: pallet_state_trie_migration = 70, // TODO: the pallet instance should be removed once all pools have migrated @@ -1317,6 +1426,7 @@ mod benches { [pallet_assets, Foreign] [pallet_assets, Pool] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetTxPayment] [pallet_balances, Balances] [pallet_message_queue, MessageQueue] @@ -1674,6 +1784,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + impl cumulus_primitives_core::GetCoreSelectorApi for Runtime { fn core_selector() -> (CoreSelector, ClaimQueueOffset) { ParachainSystem::core_selector() diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs index 442b58635f48..d653838ad80e 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/mod.rs @@ -23,6 +23,7 @@ pub mod frame_system_extensions; pub mod pallet_asset_conversion; pub mod pallet_asset_conversion_ops; pub mod pallet_asset_conversion_tx_payment; +pub mod pallet_asset_rewards; pub mod pallet_assets_foreign; pub mod pallet_assets_local; pub mod pallet_assets_pool; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs new file mode 100644 index 000000000000..3bbc289fec7b --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_asset_rewards.rs @@ -0,0 +1,217 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +//! Autogenerated weights for `pallet_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 + +// Executed Command: +// target/production/polkadot-parachain +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_rewards +// --chain=asset-hub-westend-dev +// --header=./cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_asset_rewards`. +pub struct WeightInfo(PhantomData); +impl pallet_asset_rewards::WeightInfo for WeightInfo { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(157), added: 2632, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `392` + // Estimated: `6360` + // Minimum execution time: 60_734_000 picoseconds. + Weight::from_parts(61_828_000, 0) + .saturating_add(Weight::from_parts(0, 6360)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(5)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `906` + // Estimated: `4809` + // Minimum execution time: 56_014_000 picoseconds. + Weight::from_parts(58_487_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `906` + // Estimated: `4809` + // Minimum execution time: 59_071_000 picoseconds. + Weight::from_parts(60_631_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1106` + // Estimated: `6208` + // Minimum execution time: 80_585_000 picoseconds. + Weight::from_parts(82_186_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_083_000 picoseconds. + Weight::from_parts(17_816_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 15_269_000 picoseconds. + Weight::from_parts(15_881_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `318` + // Estimated: `4809` + // Minimum execution time: 17_482_000 picoseconds. + Weight::from_parts(18_124_000, 0) + .saturating_add(Weight::from_parts(0, 4809)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:0) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `781` + // Estimated: `6208` + // Minimum execution time: 66_644_000 picoseconds. + Weight::from_parts(67_950_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(1344), added: 3819, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(157), added: 2632, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:0 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(87), added: 2562, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1139` + // Estimated: `6208` + // Minimum execution time: 124_136_000 picoseconds. + Weight::from_parts(128_642_000, 0) + .saturating_add(Weight::from_parts(0, 6208)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(10)) + } +} diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index b4e938f1f8b5..1ea2ce5136ab 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -65,6 +65,7 @@ use xcm_executor::XcmExecutor; parameter_types! { pub const RootLocation: Location = Location::here(); pub const WestendLocation: Location = Location::parent(); + pub const GovernanceLocation: Location = Location::parent(); pub const RelayNetwork: Option = Some(NetworkId::ByGenesis(WESTEND_GENESIS_HASH)); pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); pub UniversalLocation: InteriorLocation = @@ -359,7 +360,7 @@ pub type TrustedTeleporters = ( /// asset and the asset required for fee payment. pub type PoolAssetsExchanger = SingleAssetExchangeAdapter< crate::AssetConversion, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation, ForeignAssetsConvertedConcreteId, @@ -409,7 +410,7 @@ impl xcm_executor::Config for XcmConfig { WestendLocation, crate::AssetConversion, WeightToFee, - crate::NativeAndAssets, + crate::NativeAndNonPoolAssets, ( TrustBackedAssetsAsLocation< TrustBackedAssetsPalletLocation, @@ -418,7 +419,7 @@ impl xcm_executor::Config for XcmConfig { >, ForeignAssetsConvertedConcreteId, ), - ResolveAssetTo, + ResolveAssetTo, AccountId, >, // This trader allows to pay with `is_sufficient=true` "Trust Backed" assets from dedicated diff --git a/cumulus/parachains/runtimes/assets/common/src/lib.rs b/cumulus/parachains/runtimes/assets/common/src/lib.rs index 25c2df6b68d1..50b1b63146bc 100644 --- a/cumulus/parachains/runtimes/assets/common/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/common/src/lib.rs @@ -123,10 +123,11 @@ pub type ForeignAssetsConvertedConcreteId< BalanceConverter, >; -type AssetIdForPoolAssets = u32; +pub type AssetIdForPoolAssets = u32; + /// `Location` vs `AssetIdForPoolAssets` converter for `PoolAssets`. -pub type AssetIdForPoolAssetsConvert = - AsPrefixedGeneralIndex; +pub type AssetIdForPoolAssetsConvert = + AsPrefixedGeneralIndex; /// [`MatchedConvertedConcreteId`] converter dedicated for `PoolAssets` pub type PoolAssetsConvertedConcreteId = MatchedConvertedConcreteId< diff --git a/polkadot/runtime/rococo/src/xcm_config.rs b/polkadot/runtime/rococo/src/xcm_config.rs index bb77ec0000e5..10c3f6c0cbfc 100644 --- a/polkadot/runtime/rococo/src/xcm_config.rs +++ b/polkadot/runtime/rococo/src/xcm_config.rs @@ -18,7 +18,8 @@ use super::{ parachains_origin, AccountId, AllPalletsWithSystem, Balances, Dmp, Fellows, ParaId, Runtime, - RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, Treasury, WeightToFee, XcmPallet, + RuntimeCall, RuntimeEvent, RuntimeOrigin, TransactionByteFee, Treasurer, Treasury, WeightToFee, + XcmPallet, }; use crate::governance::StakingAdmin; @@ -228,11 +229,14 @@ impl xcm_executor::Config for XcmConfig { } parameter_types! { + /// Collective pluralistic body. pub const CollectiveBodyId: BodyId = BodyId::Unit; - // StakingAdmin pluralistic body. + /// StakingAdmin pluralistic body. pub const StakingAdminBodyId: BodyId = BodyId::Defense; - // Fellows pluralistic body. + /// Fellows pluralistic body. pub const FellowsBodyId: BodyId = BodyId::Technical; + /// Treasury pluralistic body. + pub const TreasuryBodyId: BodyId = BodyId::Treasury; } /// Type to convert an `Origin` type value into a `Location` value which represents an interior @@ -249,6 +253,9 @@ pub type StakingAdminToPlurality = /// Type to convert the Fellows origin to a Plurality `Location` value. pub type FellowsToPlurality = OriginToPluralityVoice; +/// Type to convert the Treasury origin to a Plurality `Location` value. +pub type TreasurerToPlurality = OriginToPluralityVoice; + /// Type to convert a pallet `Origin` type value into a `Location` value which represents an /// interior location of this chain for a destination chain. pub type LocalPalletOriginToLocation = ( @@ -256,13 +263,18 @@ pub type LocalPalletOriginToLocation = ( StakingAdminToPlurality, // Fellows origin to be used in XCM as a corresponding Plurality `Location` value. FellowsToPlurality, + // Treasurer origin to be used in XCM as a corresponding Plurality `Location` value. + TreasurerToPlurality, ); impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; // Note that this configuration of `SendXcmOrigin` is different from the one present in // production. - type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin< + RuntimeOrigin, + (LocalPalletOriginToLocation, LocalOriginToLocation), + >; type XcmRouter = XcmRouter; // Anyone can execute XCM messages locally. type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; diff --git a/polkadot/runtime/westend/src/xcm_config.rs b/polkadot/runtime/westend/src/xcm_config.rs index 3f6a7304c8a9..4235edf82b24 100644 --- a/polkadot/runtime/westend/src/xcm_config.rs +++ b/polkadot/runtime/westend/src/xcm_config.rs @@ -280,7 +280,10 @@ impl pallet_xcm::Config for Runtime { type RuntimeEvent = RuntimeEvent; // Note that this configuration of `SendXcmOrigin` is different from the one present in // production. - type SendXcmOrigin = xcm_builder::EnsureXcmOrigin; + type SendXcmOrigin = xcm_builder::EnsureXcmOrigin< + RuntimeOrigin, + (LocalPalletOriginToLocation, LocalOriginToLocation), + >; type XcmRouter = XcmRouter; // Anyone can execute XCM messages locally. type ExecuteXcmOrigin = xcm_builder::EnsureXcmOrigin; diff --git a/prdoc/pr_3926.prdoc b/prdoc/pr_3926.prdoc new file mode 100644 index 000000000000..7f352f7a45fb --- /dev/null +++ b/prdoc/pr_3926.prdoc @@ -0,0 +1,30 @@ +title: Introduce pallet-asset-rewards + +doc: + - audience: Runtime Dev + description: | + Introduce pallet-asset-rewards, which allows accounts to be rewarded for freezing fungible + tokens. The motivation for creating this pallet is to allow incentivising LPs. + See the pallet docs for more info about the pallet. + +crates: + - name: pallet-asset-rewards + bump: major + - name: polkadot-sdk + bump: minor + - name: kitchensink-runtime + bump: major + - name: asset-hub-rococo-runtime + bump: major + - name: asset-hub-westend-runtime + bump: major + - name: assets-common + bump: minor + - name: rococo-runtime + bump: minor + - name: westend-runtime + bump: patch + - name: frame-support + bump: minor + - name: emulated-integration-tests-common + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 97728f12f5f9..de377a55bc88 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -56,10 +56,10 @@ use frame_support::{ imbalance::ResolveAssetTo, nonfungibles_v2::Inspect, pay::PayAssetFromAccount, GetSalary, PayFromAccount, }, - AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, Contains, - Currency, EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth, - InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, - OnUnbalanced, VariantCountOf, WithdrawReasons, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, + ConstantStoragePrice, Contains, Currency, EitherOfDiverse, EnsureOriginWithArg, + EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter, KeyOwnerProofSystem, + LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced, VariantCountOf, WithdrawReasons, }, weights::{ constants::{ @@ -511,7 +511,8 @@ impl pallet_glutton::Config for Runtime { } parameter_types! { - pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); + pub const PreimageHoldReason: RuntimeHoldReason = + RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); } impl pallet_preimage::Config for Runtime { @@ -618,6 +619,12 @@ impl pallet_transaction_payment::Config for Runtime { type WeightInfo = pallet_transaction_payment::weights::SubstrateWeight; } +pub type AssetsFreezerInstance = pallet_assets_freezer::Instance1; +impl pallet_assets_freezer::Config for Runtime { + type RuntimeFreezeReason = RuntimeFreezeReason; + type RuntimeEvent = RuntimeEvent; +} + impl pallet_asset_conversion_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type AssetId = NativeOrWithId; @@ -1858,6 +1865,53 @@ impl pallet_asset_conversion::Config for Runtime { type BenchmarkHelper = (); } +pub type NativeAndAssetsFreezer = + UnionOf, AccountId>; + +/// Benchmark Helper +#[cfg(feature = "runtime-benchmarks")] +pub struct AssetRewardsBenchmarkHelper; + +#[cfg(feature = "runtime-benchmarks")] +impl pallet_asset_rewards::benchmarking::BenchmarkHelper> + for AssetRewardsBenchmarkHelper +{ + fn staked_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(100) + } + fn reward_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(101) + } +} + +parameter_types! { + pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd"); + pub const CreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::AssetRewards(pallet_asset_rewards::HoldReason::PoolCreation); + // 1 item, 135 bytes into the storage on pool creation. + pub const StakePoolCreationDeposit: Balance = deposit(1, 135); +} + +impl pallet_asset_rewards::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeFreezeReason = RuntimeFreezeReason; + type AssetId = NativeOrWithId; + type Balance = Balance; + type Assets = NativeAndAssets; + type PalletId = StakingRewardsPalletId; + type CreatePoolOrigin = EnsureSigned; + type WeightInfo = (); + type AssetsFreezer = NativeAndAssetsFreezer; + type Consideration = HoldConsideration< + AccountId, + Balances, + CreationHoldReason, + ConstantStoragePrice, + >; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = AssetRewardsBenchmarkHelper; +} + impl pallet_asset_conversion_ops::Config for Runtime { type RuntimeEvent = RuntimeEvent; type PriorAccountIdConverter = pallet_asset_conversion::AccountIdConverterNoSeed<( @@ -2636,6 +2690,12 @@ mod runtime { #[runtime::pallet_index(81)] pub type VerifySignature = pallet_verify_signature::Pallet; + + #[runtime::pallet_index(83)] + pub type AssetRewards = pallet_asset_rewards::Pallet; + + #[runtime::pallet_index(84)] + pub type AssetsFreezer = pallet_assets_freezer::Pallet; } impl TryFrom for pallet_revive::Call { @@ -2846,6 +2906,7 @@ mod benches { [pallet_example_tasks, TasksExample] [pallet_democracy, Democracy] [pallet_asset_conversion, AssetConversion] + [pallet_asset_rewards, AssetRewards] [pallet_asset_conversion_tx_payment, AssetConversionTxPayment] [pallet_transaction_payment, TransactionPayment] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] @@ -3553,6 +3614,12 @@ impl_runtime_apis! { } } + impl pallet_asset_rewards::AssetRewards for Runtime { + fn pool_creation_cost() -> Balance { + StakePoolCreationDeposit::get() + } + } + #[cfg(feature = "try-runtime")] impl frame_try_runtime::TryRuntime for Runtime { fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { diff --git a/substrate/frame/asset-rewards/Cargo.toml b/substrate/frame/asset-rewards/Cargo.toml new file mode 100644 index 000000000000..a03fa17cf0dc --- /dev/null +++ b/substrate/frame/asset-rewards/Cargo.toml @@ -0,0 +1,71 @@ +[package] +name = "pallet-asset-rewards" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "FRAME asset rewards pallet" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +frame-benchmarking = { workspace = true, optional = true } +frame-support = { workspace = true, features = ["experimental"] } +frame-system = { workspace = true } +scale-info = { workspace = true, features = ["derive"] } +sp-api = { workspace = true } +sp-arithmetic = { workspace = true } +sp-core = { workspace = true } +sp-io = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +[dev-dependencies] +pallet-assets = { workspace = true } +pallet-assets-freezer = { workspace = true } +pallet-balances = { workspace = true } +primitive-types = { workspace = true, features = ["codec", "num-traits", "scale-info"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "pallet-assets-freezer/std", + "pallet-assets/std", + "pallet-balances/std", + "primitive-types/std", + "scale-info/std", + "sp-api/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-assets-freezer/runtime-benchmarks", + "pallet-assets/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-assets-freezer/try-runtime", + "pallet-assets/try-runtime", + "pallet-balances/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/asset-rewards/src/benchmarking.rs b/substrate/frame/asset-rewards/src/benchmarking.rs new file mode 100644 index 000000000000..5605804dd20e --- /dev/null +++ b/substrate/frame/asset-rewards/src/benchmarking.rs @@ -0,0 +1,355 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Asset Rewards pallet benchmarking. + +use super::*; +use crate::Pallet as AssetRewards; +use frame_benchmarking::{v2::*, whitelisted_caller, BenchmarkError}; +use frame_support::{ + assert_ok, + traits::{ + fungibles::{Create, Inspect, Mutate}, + Consideration, EnsureOrigin, Footprint, + }, +}; +use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System, RawOrigin}; +use sp_runtime::{traits::One, Saturating}; +use sp_std::prelude::*; + +/// Benchmark Helper +pub trait BenchmarkHelper { + /// Returns the staked asset id. + /// + /// If the asset does not exist, it will be created by the benchmark. + fn staked_asset() -> AssetId; + /// Returns the reward asset id. + /// + /// If the asset does not exist, it will be created by the benchmark. + fn reward_asset() -> AssetId; +} + +fn pool_expire() -> DispatchTime> { + DispatchTime::At(BlockNumberFor::::from(100u32)) +} + +fn create_reward_pool() -> Result +where + T::Assets: Create + Mutate, +{ + let caller_origin = + T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap(); + + let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor)>(); + T::Consideration::ensure_successful(&caller, footprint); + + let staked_asset = T::BenchmarkHelper::staked_asset(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + + let min_staked_balance = + T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(staked_asset.clone()) { + assert_ok!(T::Assets::create( + staked_asset.clone(), + caller.clone(), + true, + min_staked_balance + )); + } + let min_reward_balance = + T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(reward_asset.clone()) { + assert_ok!(T::Assets::create( + reward_asset.clone(), + caller.clone(), + true, + min_reward_balance + )); + } + + assert_ok!(AssetRewards::::create_pool( + caller_origin.clone(), + Box::new(staked_asset), + Box::new(reward_asset), + // reward rate per block + min_reward_balance, + pool_expire::(), + Some(caller), + )); + + Ok(caller_origin) +} + +fn mint_into(caller: &T::AccountId, asset: &T::AssetId) -> T::Balance +where + T::Assets: Mutate, +{ + let min_balance = T::Assets::minimum_balance(asset.clone()); + assert_ok!(T::Assets::mint_into( + asset.clone(), + &caller, + min_balance.saturating_mul(10u32.into()) + )); + min_balance +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + System::::assert_last_event(generic_event.into()); +} + +#[benchmarks(where T::Assets: Create + Mutate)] +mod benchmarks { + use super::*; + + #[benchmark] + fn create_pool() -> Result<(), BenchmarkError> { + let caller_origin = + T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap(); + + let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor)>(); + T::Consideration::ensure_successful(&caller, footprint); + + let staked_asset = T::BenchmarkHelper::staked_asset(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + + let min_balance = T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(staked_asset.clone()) { + assert_ok!(T::Assets::create(staked_asset.clone(), caller.clone(), true, min_balance)); + } + let min_balance = T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one()); + if !T::Assets::asset_exists(reward_asset.clone()) { + assert_ok!(T::Assets::create(reward_asset.clone(), caller.clone(), true, min_balance)); + } + + #[extrinsic_call] + _( + caller_origin as T::RuntimeOrigin, + Box::new(staked_asset.clone()), + Box::new(reward_asset.clone()), + min_balance, + pool_expire::(), + Some(caller.clone()), + ); + + assert_last_event::( + Event::PoolCreated { + creator: caller.clone(), + admin: caller, + staked_asset_id: staked_asset, + reward_asset_id: reward_asset, + reward_rate_per_block: min_balance, + expiry_block: pool_expire::().evaluate(System::::block_number()), + pool_id: 0, + } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn stake() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + // stake first to get worth case benchmark. + assert_ok!(AssetRewards::::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + min_balance + )); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, min_balance); + + assert_last_event::(Event::Staked { staker, pool_id: 0, amount: min_balance }.into()); + + Ok(()) + } + + #[benchmark] + fn unstake() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + min_balance, + )); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, min_balance, None); + + assert_last_event::( + Event::Unstaked { caller: staker.clone(), staker, pool_id: 0, amount: min_balance } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn harvest_rewards() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + + let pool_acc = AssetRewards::::pool_account_id(&0u32); + let min_reward_balance = mint_into::(&pool_acc, &T::BenchmarkHelper::reward_asset()); + + let staker = whitelisted_caller(); + let _ = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + assert_ok!(AssetRewards::::stake( + RawOrigin::Signed(staker.clone()).into(), + 0, + T::Balance::one(), + )); + + System::::set_block_number(System::::block_number() + BlockNumberFor::::one()); + + #[extrinsic_call] + _(RawOrigin::Signed(staker.clone()), 0, None); + + assert_last_event::( + Event::RewardsHarvested { + caller: staker.clone(), + staker, + pool_id: 0, + amount: min_reward_balance, + } + .into(), + ); + + Ok(()) + } + + #[benchmark] + fn set_pool_reward_rate_per_block() -> Result<(), BenchmarkError> { + let caller_origin = create_reward_pool::()?; + + // stake first to get worth case benchmark. + { + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::::stake(RawOrigin::Signed(staker).into(), 0, min_balance)); + } + + let new_reward_rate_per_block = + T::Assets::minimum_balance(T::BenchmarkHelper::reward_asset()).max(T::Balance::one()) + + T::Balance::one(); + + #[extrinsic_call] + _(caller_origin as T::RuntimeOrigin, 0, new_reward_rate_per_block); + + assert_last_event::( + Event::PoolRewardRateModified { pool_id: 0, new_reward_rate_per_block }.into(), + ); + Ok(()) + } + + #[benchmark] + fn set_pool_admin() -> Result<(), BenchmarkError> { + let caller_origin = create_reward_pool::()?; + let new_admin: T::AccountId = whitelisted_caller(); + + #[extrinsic_call] + _(caller_origin as T::RuntimeOrigin, 0, new_admin.clone()); + + assert_last_event::(Event::PoolAdminModified { pool_id: 0, new_admin }.into()); + + Ok(()) + } + + #[benchmark] + fn set_pool_expiry_block() -> Result<(), BenchmarkError> { + let create_origin = create_reward_pool::()?; + + // stake first to get worth case benchmark. + { + let staker: T::AccountId = whitelisted_caller(); + let min_balance = mint_into::(&staker, &T::BenchmarkHelper::staked_asset()); + + assert_ok!(AssetRewards::::stake(RawOrigin::Signed(staker).into(), 0, min_balance)); + } + + let new_expiry_block = + pool_expire::().evaluate(System::::block_number()) + BlockNumberFor::::one(); + + #[extrinsic_call] + _(create_origin as T::RuntimeOrigin, 0, DispatchTime::At(new_expiry_block)); + + assert_last_event::( + Event::PoolExpiryBlockModified { pool_id: 0, new_expiry_block }.into(), + ); + + Ok(()) + } + + #[benchmark] + fn deposit_reward_tokens() -> Result<(), BenchmarkError> { + create_reward_pool::()?; + let caller = whitelisted_caller(); + + let reward_asset = T::BenchmarkHelper::reward_asset(); + let pool_acc = AssetRewards::::pool_account_id(&0u32); + let min_balance = mint_into::(&caller, &reward_asset); + + let balance_before = T::Assets::balance(reward_asset.clone(), &pool_acc); + + #[extrinsic_call] + _(RawOrigin::Signed(caller), 0, min_balance); + + let balance_after = T::Assets::balance(reward_asset, &pool_acc); + + assert_eq!(balance_after, balance_before + min_balance); + + Ok(()) + } + + #[benchmark] + fn cleanup_pool() -> Result<(), BenchmarkError> { + let create_origin = create_reward_pool::()?; + let caller = T::CreatePoolOrigin::ensure_origin(create_origin.clone()).unwrap(); + + // deposit rewards tokens to get worth case benchmark. + { + let caller = whitelisted_caller(); + let reward_asset = T::BenchmarkHelper::reward_asset(); + let min_balance = mint_into::(&caller, &reward_asset); + assert_ok!(AssetRewards::::deposit_reward_tokens( + RawOrigin::Signed(caller).into(), + 0, + min_balance + )); + } + + #[extrinsic_call] + _(RawOrigin::Signed(caller), 0); + + assert_last_event::(Event::PoolCleanedUp { pool_id: 0 }.into()); + + Ok(()) + } + + impl_benchmark_test_suite!(AssetRewards, crate::mock::new_test_ext(), crate::mock::MockRuntime); +} diff --git a/substrate/frame/asset-rewards/src/lib.rs b/substrate/frame/asset-rewards/src/lib.rs new file mode 100644 index 000000000000..4ce73e9febf9 --- /dev/null +++ b/substrate/frame/asset-rewards/src/lib.rs @@ -0,0 +1,905 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # FRAME Staking Rewards Pallet +//! +//! Allows accounts to be rewarded for holding `fungible` asset/s, for example LP tokens. +//! +//! ## Overview +//! +//! Initiate an incentive program for a fungible asset by creating a new pool. +//! +//! During pool creation, a 'staking asset', 'reward asset', 'reward rate per block', 'expiry +//! block', and 'admin' are specified. +//! +//! Once created, holders of the 'staking asset' can 'stake' them in a corresponding pool, which +//! creates a Freeze on the asset. +//! +//! Once staked, rewards denominated in 'reward asset' begin accumulating to the staker, +//! proportional to their share of the total staked tokens in the pool. +//! +//! Reward assets pending distribution are held in an account unique to each pool. +//! +//! Care should be taken by the pool operator to keep pool accounts adequately funded with the +//! reward asset. +//! +//! The pool admin may increase reward rate per block, increase expiry block, and change admin. +//! +//! ## Disambiguation +//! +//! While this pallet shares some terminology with the `staking-pool` and similar native staking +//! related pallets, it is distinct and is entirely unrelated to native staking. +//! +//! ## Permissioning +//! +//! Currently, pool creation and management restricted to a configured Origin. +//! +//! Future iterations of this pallet may allow permissionless creation and management of pools. +//! +//! Note: The permissioned origin must return an AccountId. This can be achieved for any Origin by +//! wrapping it with `EnsureSuccess`. +//! +//! ## Implementation Notes +//! +//! Internal logic functions such as `update_pool_and_staker_rewards` were deliberately written +//! without side-effects. +//! +//! Storage interaction such as reads and writes are instead all performed in the top level +//! pallet Call method, which while slightly more verbose, makes it easier to understand the +//! code and reason about how storage reads and writes occur in the pallet. +//! +//! ## Rewards Algorithm +//! +//! The rewards algorithm is based on the Synthetix [StakingRewards.sol](https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol) +//! smart contract. +//! +//! Rewards are calculated JIT (just-in-time), and all operations are O(1) making the approach +//! scalable to many pools and stakers. +//! +//! ### Resources +//! +//! - [This video series](https://www.youtube.com/watch?v=6ZO5aYg1GI8), which walks through the math +//! of the algorithm. +//! - [This dev.to article](https://dev.to/heymarkkop/understanding-sushiswaps-masterchef-staking-rewards-1m6f), +//! which explains the algorithm of the SushiSwap MasterChef staking. While not identical to the +//! Synthetix approach, they are quite similar. +#![deny(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +use codec::{Codec, Decode, Encode, MaxEncodedLen}; +use frame_support::{ + traits::{ + fungibles::{Inspect, Mutate}, + schedule::DispatchTime, + tokens::Balance, + }, + PalletId, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use scale_info::TypeInfo; +use sp_core::Get; +use sp_runtime::{ + traits::{MaybeDisplay, Zero}, + DispatchError, +}; +use sp_std::boxed::Box; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +mod weights; + +pub use weights::WeightInfo; + +/// Unique id type for each pool. +pub type PoolId = u32; + +/// Multiplier to maintain precision when calculating rewards. +pub(crate) const PRECISION_SCALING_FACTOR: u16 = 4096; + +/// Convenience alias for `PoolInfo`. +pub type PoolInfoFor = PoolInfo< + ::AccountId, + ::AssetId, + ::Balance, + BlockNumberFor, +>; + +/// The state of a staker in a pool. +#[derive(Debug, Default, Clone, Decode, Encode, MaxEncodedLen, TypeInfo)] +pub struct PoolStakerInfo { + /// Amount of tokens staked. + amount: Balance, + /// Accumulated, unpaid rewards. + rewards: Balance, + /// Reward per token value at the time of the staker's last interaction with the contract. + reward_per_token_paid: Balance, +} + +/// The state and configuration of an incentive pool. +#[derive(Debug, Clone, Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub struct PoolInfo { + /// The asset staked in this pool. + staked_asset_id: AssetId, + /// The asset distributed as rewards by this pool. + reward_asset_id: AssetId, + /// The amount of tokens rewarded per block. + reward_rate_per_block: Balance, + /// The block the pool will cease distributing rewards. + expiry_block: BlockNumber, + /// The account authorized to manage this pool. + admin: AccountId, + /// The total amount of tokens staked in this pool. + total_tokens_staked: Balance, + /// Total rewards accumulated per token, up to the `last_update_block`. + reward_per_token_stored: Balance, + /// Last block number the pool was updated. + last_update_block: BlockNumber, + /// The account that holds the pool's rewards. + account: AccountId, +} + +sp_api::decl_runtime_apis! { + /// The runtime API for the asset rewards pallet. + pub trait AssetRewards { + /// Get the cost of creating a pool. + /// + /// This is especially useful when the cost is dynamic. + fn pool_creation_cost() -> Cost; + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + pallet_prelude::*, + traits::{ + fungibles::MutateFreeze, + tokens::{AssetId, Fortitude, Preservation}, + Consideration, Footprint, + }, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::{ + traits::{ + AccountIdConversion, BadOrigin, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureMul, + EnsureSub, EnsureSubAssign, + }, + DispatchResult, + }; + + #[pallet::pallet] + pub struct Pallet(_); + + /// A reason for the pallet placing a hold on funds. + #[pallet::composite_enum] + pub enum FreezeReason { + /// Funds are staked in the pallet. + #[codec(index = 0)] + Staked, + } + + /// A reason for the pallet placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// Cost associated with storing pool information on-chain. + #[codec(index = 0)] + PoolCreation, + } + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The pallet's unique identifier, used to derive the pool's account ID. + /// + /// The account ID is derived once during pool creation and stored in the storage. + #[pallet::constant] + type PalletId: Get; + + /// Identifier for each type of asset. + type AssetId: AssetId + Member + Parameter; + + /// The type in which the assets are measured. + type Balance: Balance + TypeInfo; + + /// The origin with permission to create pools. + /// + /// The Origin must return an AccountId. + type CreatePoolOrigin: EnsureOrigin; + + /// Registry of assets that can be configured to either stake for rewards, or be offered as + /// rewards for staking. + type Assets: Inspect + + Mutate; + + /// Freezer for the Assets. + type AssetsFreezer: MutateFreeze< + Self::AccountId, + Id = Self::RuntimeFreezeReason, + AssetId = Self::AssetId, + Balance = Self::Balance, + >; + + /// The overarching freeze reason. + type RuntimeFreezeReason: From; + + /// Means for associating a cost with the on-chain storage of pool information, which + /// is incurred by the pool creator. + /// + /// The passed `Footprint` specifically accounts for the storage footprint of the pool's + /// information itself, excluding any potential storage footprint related to the stakers. + type Consideration: Consideration; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Helper for benchmarking. + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: benchmarking::BenchmarkHelper; + } + + /// State of pool stakers. + #[pallet::storage] + pub type PoolStakers = StorageDoubleMap< + _, + Blake2_128Concat, + PoolId, + Blake2_128Concat, + T::AccountId, + PoolStakerInfo, + >; + + /// State and configuration of each staking pool. + #[pallet::storage] + pub type Pools = StorageMap<_, Blake2_128Concat, PoolId, PoolInfoFor>; + + /// The cost associated with storing pool information on-chain which was incurred by the pool + /// creator. + /// + /// This cost may be [`None`], as determined by [`Config::Consideration`]. + #[pallet::storage] + pub type PoolCost = + StorageMap<_, Blake2_128Concat, PoolId, (T::AccountId, T::Consideration)>; + + /// Stores the [`PoolId`] to use for the next pool. + /// + /// Incremented when a new pool is created. + #[pallet::storage] + pub type NextPoolId = StorageValue<_, PoolId, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// An account staked some tokens in a pool. + Staked { + /// The account that staked assets. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The staked asset amount. + amount: T::Balance, + }, + /// An account unstaked some tokens from a pool. + Unstaked { + /// The account that signed transaction. + caller: T::AccountId, + /// The account that unstaked assets. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The unstaked asset amount. + amount: T::Balance, + }, + /// An account harvested some rewards. + RewardsHarvested { + /// The account that signed transaction. + caller: T::AccountId, + /// The staker whos rewards were harvested. + staker: T::AccountId, + /// The pool. + pool_id: PoolId, + /// The amount of harvested tokens. + amount: T::Balance, + }, + /// A new reward pool was created. + PoolCreated { + /// The account that created the pool. + creator: T::AccountId, + /// The unique ID for the new pool. + pool_id: PoolId, + /// The staking asset. + staked_asset_id: T::AssetId, + /// The reward asset. + reward_asset_id: T::AssetId, + /// The initial reward rate per block. + reward_rate_per_block: T::Balance, + /// The block the pool will cease to accumulate rewards. + expiry_block: BlockNumberFor, + /// The account allowed to modify the pool. + admin: T::AccountId, + }, + /// A pool reward rate was modified by the admin. + PoolRewardRateModified { + /// The modified pool. + pool_id: PoolId, + /// The new reward rate per block. + new_reward_rate_per_block: T::Balance, + }, + /// A pool admin was modified. + PoolAdminModified { + /// The modified pool. + pool_id: PoolId, + /// The new admin. + new_admin: T::AccountId, + }, + /// A pool expiry block was modified by the admin. + PoolExpiryBlockModified { + /// The modified pool. + pool_id: PoolId, + /// The new expiry block. + new_expiry_block: BlockNumberFor, + }, + /// A pool information was cleared after it's completion. + PoolCleanedUp { + /// The cleared pool. + pool_id: PoolId, + }, + } + + #[pallet::error] + pub enum Error { + /// The staker does not have enough tokens to perform the operation. + NotEnoughTokens, + /// An operation was attempted on a non-existent pool. + NonExistentPool, + /// An operation was attempted for a non-existent staker. + NonExistentStaker, + /// An operation was attempted with a non-existent asset. + NonExistentAsset, + /// There was an error converting a block number. + BlockNumberConversionError, + /// The expiry block must be in the future. + ExpiryBlockMustBeInTheFuture, + /// Insufficient funds to create the freeze. + InsufficientFunds, + /// The expiry block can be only extended. + ExpiryCut, + /// The reward rate per block can be only increased. + RewardRateCut, + /// The pool still has staked tokens or rewards. + NonEmptyPool, + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + // The AccountId is at least 16 bytes to contain the unique PalletId. + let pool_id: PoolId = 1; + assert!( + >::try_into_sub_account( + &T::PalletId::get(), pool_id, + ) + .is_some() + ); + } + } + + /// Pallet's callable functions. + #[pallet::call(weight(::WeightInfo))] + impl Pallet { + /// Create a new reward pool. + /// + /// Parameters: + /// - `origin`: must be `Config::CreatePoolOrigin`; + /// - `staked_asset_id`: the asset to be staked in the pool; + /// - `reward_asset_id`: the asset to be distributed as rewards; + /// - `reward_rate_per_block`: the amount of reward tokens distributed per block; + /// - `expiry`: the block number at which the pool will cease to accumulate rewards. The + /// [`DispatchTime::After`] variant evaluated at the execution time. + /// - `admin`: the account allowed to extend the pool expiration, increase the rewards rate + /// and receive the unutilized reward tokens back after the pool completion. If `None`, + /// the caller is set as an admin. + #[pallet::call_index(0)] + pub fn create_pool( + origin: OriginFor, + staked_asset_id: Box, + reward_asset_id: Box, + reward_rate_per_block: T::Balance, + expiry: DispatchTime>, + admin: Option, + ) -> DispatchResult { + // Check the origin. + let creator = T::CreatePoolOrigin::ensure_origin(origin)?; + + // Ensure the assets exist. + ensure!( + T::Assets::asset_exists(*staked_asset_id.clone()), + Error::::NonExistentAsset + ); + ensure!( + T::Assets::asset_exists(*reward_asset_id.clone()), + Error::::NonExistentAsset + ); + + // Check the expiry block. + let expiry_block = expiry.evaluate(frame_system::Pallet::::block_number()); + ensure!( + expiry_block > frame_system::Pallet::::block_number(), + Error::::ExpiryBlockMustBeInTheFuture + ); + + let pool_id = NextPoolId::::get(); + + let footprint = Self::pool_creation_footprint(); + let cost = T::Consideration::new(&creator, footprint)?; + PoolCost::::insert(pool_id, (creator.clone(), cost)); + + let admin = admin.unwrap_or(creator.clone()); + + // Create the pool. + let pool = PoolInfoFor:: { + staked_asset_id: *staked_asset_id.clone(), + reward_asset_id: *reward_asset_id.clone(), + reward_rate_per_block, + total_tokens_staked: 0u32.into(), + reward_per_token_stored: 0u32.into(), + last_update_block: 0u32.into(), + expiry_block, + admin: admin.clone(), + account: Self::pool_account_id(&pool_id), + }; + + // Insert it into storage. + Pools::::insert(pool_id, pool); + + NextPoolId::::put(pool_id.ensure_add(1)?); + + // Emit created event. + Self::deposit_event(Event::PoolCreated { + creator, + pool_id, + staked_asset_id: *staked_asset_id, + reward_asset_id: *reward_asset_id, + reward_rate_per_block, + expiry_block, + admin, + }); + + Ok(()) + } + + /// Stake additional tokens in a pool. + /// + /// A freeze is placed on the staked tokens. + #[pallet::call_index(1)] + pub fn stake(origin: OriginFor, pool_id: PoolId, amount: T::Balance) -> DispatchResult { + let staker = ensure_signed(origin)?; + + // Always start by updating staker and pool rewards. + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + let staker_info = PoolStakers::::get(pool_id, &staker).unwrap_or_default(); + let (mut pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + T::AssetsFreezer::increase_frozen( + pool_info.staked_asset_id.clone(), + &FreezeReason::Staked.into(), + &staker, + amount, + )?; + + // Update Pools. + pool_info.total_tokens_staked.ensure_add_assign(amount)?; + + Pools::::insert(pool_id, pool_info); + + // Update PoolStakers. + staker_info.amount.ensure_add_assign(amount)?; + PoolStakers::::insert(pool_id, &staker, staker_info); + + // Emit event. + Self::deposit_event(Event::Staked { staker, pool_id, amount }); + + Ok(()) + } + + /// Unstake tokens from a pool. + /// + /// Removes the freeze on the staked tokens. + /// + /// Parameters: + /// - origin: must be the `staker` if the pool is still active. Otherwise, any account. + /// - pool_id: the pool to unstake from. + /// - amount: the amount of tokens to unstake. + /// - staker: the account to unstake from. If `None`, the caller is used. + #[pallet::call_index(2)] + pub fn unstake( + origin: OriginFor, + pool_id: PoolId, + amount: T::Balance, + staker: Option, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let staker = staker.unwrap_or(caller.clone()); + + // Always start by updating the pool rewards. + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + let now = frame_system::Pallet::::block_number(); + ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin); + + let staker_info = PoolStakers::::get(pool_id, &staker).unwrap_or_default(); + let (mut pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + // Check the staker has enough staked tokens. + ensure!(staker_info.amount >= amount, Error::::NotEnoughTokens); + + // Unfreeze staker assets. + T::AssetsFreezer::decrease_frozen( + pool_info.staked_asset_id.clone(), + &FreezeReason::Staked.into(), + &staker, + amount, + )?; + + // Update Pools. + pool_info.total_tokens_staked.ensure_sub_assign(amount)?; + Pools::::insert(pool_id, pool_info); + + // Update PoolStakers. + staker_info.amount.ensure_sub_assign(amount)?; + + if staker_info.amount.is_zero() && staker_info.rewards.is_zero() { + PoolStakers::::remove(&pool_id, &staker); + } else { + PoolStakers::::insert(&pool_id, &staker, staker_info); + } + + // Emit event. + Self::deposit_event(Event::Unstaked { caller, staker, pool_id, amount }); + + Ok(()) + } + + /// Harvest unclaimed pool rewards. + /// + /// Parameters: + /// - origin: must be the `staker` if the pool is still active. Otherwise, any account. + /// - pool_id: the pool to harvest from. + /// - staker: the account for which to harvest rewards. If `None`, the caller is used. + #[pallet::call_index(3)] + pub fn harvest_rewards( + origin: OriginFor, + pool_id: PoolId, + staker: Option, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let staker = staker.unwrap_or(caller.clone()); + + // Always start by updating the pool and staker rewards. + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + let now = frame_system::Pallet::::block_number(); + ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin); + + let staker_info = + PoolStakers::::get(pool_id, &staker).ok_or(Error::::NonExistentStaker)?; + let (pool_info, mut staker_info) = + Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?; + + // Transfer unclaimed rewards from the pool to the staker. + T::Assets::transfer( + pool_info.reward_asset_id, + &pool_info.account, + &staker, + staker_info.rewards, + // Could kill the account, but only if the pool was already almost empty. + Preservation::Expendable, + )?; + + // Emit event. + Self::deposit_event(Event::RewardsHarvested { + caller, + staker: staker.clone(), + pool_id, + amount: staker_info.rewards, + }); + + // Reset staker rewards. + staker_info.rewards = 0u32.into(); + + if staker_info.amount.is_zero() { + PoolStakers::::remove(&pool_id, &staker); + } else { + PoolStakers::::insert(&pool_id, &staker, staker_info); + } + + Ok(()) + } + + /// Modify a pool reward rate. + /// + /// Currently the reward rate can only be increased. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(4)] + pub fn set_pool_reward_rate_per_block( + origin: OriginFor, + pool_id: PoolId, + new_reward_rate_per_block: T::Balance, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + ensure!( + new_reward_rate_per_block > pool_info.reward_rate_per_block, + Error::::RewardRateCut + ); + + // Always start by updating the pool rewards. + let rewards_per_token = Self::reward_per_token(&pool_info)?; + let mut pool_info = Self::update_pool_rewards(&pool_info, rewards_per_token)?; + + pool_info.reward_rate_per_block = new_reward_rate_per_block; + Pools::::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block, + }); + + Ok(()) + } + + /// Modify a pool admin. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(5)] + pub fn set_pool_admin( + origin: OriginFor, + pool_id: PoolId, + new_admin: T::AccountId, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let mut pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + + pool_info.admin = new_admin.clone(); + Pools::::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolAdminModified { pool_id, new_admin }); + + Ok(()) + } + + /// Set when the pool should expire. + /// + /// Currently the expiry block can only be extended. + /// + /// Only the pool admin may perform this operation. + #[pallet::call_index(6)] + pub fn set_pool_expiry_block( + origin: OriginFor, + pool_id: PoolId, + new_expiry: DispatchTime>, + ) -> DispatchResult { + let caller = T::CreatePoolOrigin::ensure_origin(origin.clone()) + .or_else(|_| ensure_signed(origin))?; + + let new_expiry = new_expiry.evaluate(frame_system::Pallet::::block_number()); + ensure!( + new_expiry > frame_system::Pallet::::block_number(), + Error::::ExpiryBlockMustBeInTheFuture + ); + + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == caller, BadOrigin); + ensure!(new_expiry > pool_info.expiry_block, Error::::ExpiryCut); + + // Always start by updating the pool rewards. + let reward_per_token = Self::reward_per_token(&pool_info)?; + let mut pool_info = Self::update_pool_rewards(&pool_info, reward_per_token)?; + + pool_info.expiry_block = new_expiry; + Pools::::insert(pool_id, pool_info); + + Self::deposit_event(Event::PoolExpiryBlockModified { + pool_id, + new_expiry_block: new_expiry, + }); + + Ok(()) + } + + /// Convenience method to deposit reward tokens into a pool. + /// + /// This method is not strictly necessary (tokens could be transferred directly to the + /// pool pot address), but is provided for convenience so manual derivation of the + /// account id is not required. + #[pallet::call_index(7)] + pub fn deposit_reward_tokens( + origin: OriginFor, + pool_id: PoolId, + amount: T::Balance, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + T::Assets::transfer( + pool_info.reward_asset_id, + &caller, + &pool_info.account, + amount, + Preservation::Preserve, + )?; + Ok(()) + } + + /// Cleanup a pool. + /// + /// Origin must be the pool admin. + /// + /// Cleanup storage, release any associated storage cost and return the remaining reward + /// tokens to the admin. + #[pallet::call_index(8)] + pub fn cleanup_pool(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + + let pool_info = Pools::::get(pool_id).ok_or(Error::::NonExistentPool)?; + ensure!(pool_info.admin == who, BadOrigin); + + let stakers = PoolStakers::::iter_key_prefix(pool_id).next(); + ensure!(stakers.is_none(), Error::::NonEmptyPool); + + let pool_balance = T::Assets::reducible_balance( + pool_info.reward_asset_id.clone(), + &pool_info.account, + Preservation::Expendable, + Fortitude::Polite, + ); + T::Assets::transfer( + pool_info.reward_asset_id, + &pool_info.account, + &pool_info.admin, + pool_balance, + Preservation::Expendable, + )?; + + if let Some((who, cost)) = PoolCost::::take(pool_id) { + T::Consideration::drop(cost, &who)?; + } + + Pools::::remove(pool_id); + + Self::deposit_event(Event::PoolCleanedUp { pool_id }); + + Ok(()) + } + } + + impl Pallet { + /// The pool creation footprint. + /// + /// The footprint specifically accounts for the storage footprint of the pool's information + /// itself, excluding any potential storage footprint related to the stakers. + pub fn pool_creation_footprint() -> Footprint { + Footprint::from_mel::<(PoolId, PoolInfoFor)>() + } + + /// Derive a pool account ID from the pool's ID. + pub fn pool_account_id(id: &PoolId) -> T::AccountId { + T::PalletId::get().into_sub_account_truncating(id) + } + + /// Computes update pool and staker reward state. + /// + /// Should be called prior to any operation involving a staker. + /// + /// Returns the updated pool and staker info. + /// + /// NOTE: this function has no side-effects. Side-effects such as storage modifications are + /// the responsibility of the caller. + pub fn update_pool_and_staker_rewards( + pool_info: &PoolInfoFor, + staker_info: &PoolStakerInfo, + ) -> Result<(PoolInfoFor, PoolStakerInfo), DispatchError> { + let reward_per_token = Self::reward_per_token(&pool_info)?; + let pool_info = Self::update_pool_rewards(pool_info, reward_per_token)?; + + let mut new_staker_info = staker_info.clone(); + new_staker_info.rewards = Self::derive_rewards(&staker_info, &reward_per_token)?; + new_staker_info.reward_per_token_paid = pool_info.reward_per_token_stored; + return Ok((pool_info, new_staker_info)); + } + + /// Computes update pool reward state. + /// + /// Should be called every time the pool is adjusted, and a staker is not involved. + /// + /// Returns the updated pool and staker info. + /// + /// NOTE: this function has no side-effects. Side-effects such as storage modifications are + /// the responsibility of the caller. + pub fn update_pool_rewards( + pool_info: &PoolInfoFor, + reward_per_token: T::Balance, + ) -> Result, DispatchError> { + let mut new_pool_info = pool_info.clone(); + new_pool_info.last_update_block = frame_system::Pallet::::block_number(); + new_pool_info.reward_per_token_stored = reward_per_token; + + Ok(new_pool_info) + } + + /// Derives the current reward per token for this pool. + fn reward_per_token(pool_info: &PoolInfoFor) -> Result { + if pool_info.total_tokens_staked.is_zero() { + return Ok(pool_info.reward_per_token_stored) + } + + let rewardable_blocks_elapsed: u32 = + match Self::last_block_reward_applicable(pool_info.expiry_block) + .ensure_sub(pool_info.last_update_block)? + .try_into() + { + Ok(b) => b, + Err(_) => return Err(Error::::BlockNumberConversionError.into()), + }; + + Ok(pool_info.reward_per_token_stored.ensure_add( + pool_info + .reward_rate_per_block + .ensure_mul(rewardable_blocks_elapsed.into())? + .ensure_mul(PRECISION_SCALING_FACTOR.into())? + .ensure_div(pool_info.total_tokens_staked)?, + )?) + } + + /// Derives the amount of rewards earned by a staker. + /// + /// This is a helper function for `update_pool_rewards` and should not be called directly. + fn derive_rewards( + staker_info: &PoolStakerInfo, + reward_per_token: &T::Balance, + ) -> Result { + Ok(staker_info + .amount + .ensure_mul(reward_per_token.ensure_sub(staker_info.reward_per_token_paid)?)? + .ensure_div(PRECISION_SCALING_FACTOR.into())? + .ensure_add(staker_info.rewards)?) + } + + fn last_block_reward_applicable(pool_expiry_block: BlockNumberFor) -> BlockNumberFor { + let now = frame_system::Pallet::::block_number(); + if now < pool_expiry_block { + now + } else { + pool_expiry_block + } + } + } +} diff --git a/substrate/frame/asset-rewards/src/mock.rs b/substrate/frame/asset-rewards/src/mock.rs new file mode 100644 index 000000000000..87c8a8a0dea0 --- /dev/null +++ b/substrate/frame/asset-rewards/src/mock.rs @@ -0,0 +1,221 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test environment for Asset Rewards pallet. + +use super::*; +use crate as pallet_asset_rewards; +use core::default::Default; +use frame_support::{ + construct_runtime, derive_impl, + instances::Instance1, + parameter_types, + traits::{ + tokens::fungible::{HoldConsideration, NativeFromLeft, NativeOrWithId, UnionOf}, + AsEnsureOriginWithArg, ConstU128, ConstU32, EnsureOrigin, LinearStoragePrice, + }, + PalletId, +}; +use frame_system::EnsureSigned; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; + +#[cfg(feature = "runtime-benchmarks")] +use self::benchmarking::BenchmarkHelper; + +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum MockRuntime + { + System: frame_system, + Balances: pallet_balances, + Assets: pallet_assets::, + AssetsFreezer: pallet_assets_freezer::, + StakingRewards: pallet_asset_rewards, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for MockRuntime { + type AccountId = u128; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +impl pallet_balances::Config for MockRuntime { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<100>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = ConstU32<50>; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type DoneSlashHandler = (); +} + +impl pallet_assets::Config for MockRuntime { + type RuntimeEvent = RuntimeEvent; + type Balance = u128; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type StringLimit = ConstU32<50>; + type Freezer = AssetsFreezer; + type Extra = (); + type WeightInfo = (); + type CallbackHandle = (); + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +parameter_types! { + pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd"); + pub const Native: NativeOrWithId = NativeOrWithId::Native; + pub const PermissionedAccountId: u128 = 0; +} + +/// Give Root Origin permission to create pools. +pub struct MockPermissionedOrigin; +impl EnsureOrigin for MockPermissionedOrigin { + type Success = ::AccountId; + + fn try_origin(origin: RuntimeOrigin) -> Result { + match origin.clone().into() { + Ok(frame_system::RawOrigin::Root) => Ok(PermissionedAccountId::get()), + _ => Err(origin), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(RuntimeOrigin::root()) + } +} + +/// Allow Freezes for the `Assets` pallet +impl pallet_assets_freezer::Config for MockRuntime { + type RuntimeFreezeReason = RuntimeFreezeReason; + type RuntimeEvent = RuntimeEvent; +} + +pub type NativeAndAssets = UnionOf, u128>; + +pub type NativeAndAssetsFreezer = + UnionOf, u128>; + +#[cfg(feature = "runtime-benchmarks")] +pub struct AssetRewardsBenchmarkHelper; +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper> for AssetRewardsBenchmarkHelper { + fn staked_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(101) + } + fn reward_asset() -> NativeOrWithId { + NativeOrWithId::::WithId(102) + } +} + +parameter_types! { + pub const CreationHoldReason: RuntimeHoldReason = + RuntimeHoldReason::StakingRewards(pallet_asset_rewards::HoldReason::PoolCreation); +} + +impl Config for MockRuntime { + type RuntimeEvent = RuntimeEvent; + type AssetId = NativeOrWithId; + type Balance = ::Balance; + type Assets = NativeAndAssets; + type AssetsFreezer = NativeAndAssetsFreezer; + type PalletId = StakingRewardsPalletId; + type CreatePoolOrigin = MockPermissionedOrigin; + type WeightInfo = (); + type RuntimeFreezeReason = RuntimeFreezeReason; + type Consideration = HoldConsideration< + u128, + Balances, + CreationHoldReason, + LinearStoragePrice, ConstU128<0>, u128>, + >; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = AssetRewardsBenchmarkHelper; +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_assets::GenesisConfig:: { + // Genesis assets: id, owner, is_sufficient, min_balance + // pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance)>, + assets: vec![(1, 1, true, 1), (10, 1, true, 1), (20, 1, true, 1)], + // Genesis metadata: id, name, symbol, decimals + // pub metadata: Vec<(T::AssetId, Vec, Vec, u8)>, + metadata: vec![ + (1, b"test".to_vec(), b"TST".to_vec(), 18), + (10, b"test10".to_vec(), b"T10".to_vec(), 18), + (20, b"test20".to_vec(), b"T20".to_vec(), 18), + ], + // Genesis accounts: id, account_id, balance + // pub accounts: Vec<(T::AssetId, T::AccountId, T::Balance)>, + accounts: vec![ + (1, 1, 10000), + (1, 2, 20000), + (1, 3, 30000), + (1, 4, 40000), + (1, 10, 40000), + (1, 20, 40000), + ], + next_asset_id: None, + } + .assimilate_storage(&mut t) + .unwrap(); + + let pool_zero_account_id = 31086825966906540362769395565; + pallet_balances::GenesisConfig:: { + balances: vec![ + (0, 10000), + (1, 10000), + (2, 20000), + (3, 30000), + (4, 40000), + (10, 40000), + (20, 40000), + (pool_zero_account_id, 100_000), // Top up the default pool account id + ], + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/substrate/frame/asset-rewards/src/tests.rs b/substrate/frame/asset-rewards/src/tests.rs new file mode 100644 index 000000000000..399d6a54c939 --- /dev/null +++ b/substrate/frame/asset-rewards/src/tests.rs @@ -0,0 +1,1457 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{mock::*, *}; +use frame_support::{ + assert_err, assert_noop, assert_ok, hypothetically, + traits::{ + fungible, + fungible::NativeOrWithId, + fungibles, + tokens::{Fortitude, Preservation}, + }, +}; +use sp_runtime::{traits::BadOrigin, ArithmeticError, TokenError}; + +const DEFAULT_STAKED_ASSET_ID: NativeOrWithId = NativeOrWithId::::WithId(1); +const DEFAULT_REWARD_ASSET_ID: NativeOrWithId = NativeOrWithId::::Native; +const DEFAULT_REWARD_RATE_PER_BLOCK: u128 = 100; +const DEFAULT_EXPIRE_AFTER: u64 = 200; +const DEFAULT_ADMIN: u128 = 1; + +/// Creates a basic pool with values: +/// - Staking asset: 1 +/// - Reward asset: Native +/// - Reward rate per block: 100 +/// - Lifetime: 100 +/// - Admin: 1 +/// +/// Useful to reduce boilerplate in tests when it's not important to customise or reuse pool +/// params. +pub fn create_default_pool() { + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID.clone()), + Box::new(DEFAULT_REWARD_ASSET_ID.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(DEFAULT_ADMIN) + )); +} + +/// The same as [`create_default_pool`], but with the admin parameter set to the creator. +pub fn create_default_pool_permissioned_admin() { + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID.clone()), + Box::new(DEFAULT_REWARD_ASSET_ID.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()), + )); +} + +fn assert_hypothetically_earned( + staker: u128, + expected_earned: u128, + pool_id: u32, + reward_asset_id: NativeOrWithId, +) { + hypothetically!({ + // Get the pre-harvest balance. + let balance_before: ::Balance = + <::Assets>::balance(reward_asset_id.clone(), &staker); + + // Harvest the rewards. + assert_ok!(StakingRewards::harvest_rewards(RuntimeOrigin::signed(staker), pool_id, None),); + + // Sanity check: staker rewards are reset to 0 if some `amount` is still staked, otherwise + // the storage item removed. + if let Some(staker_pool) = PoolStakers::::get(pool_id, staker) { + assert!(staker_pool.rewards == 0); + assert!(staker_pool.amount > 0); + } + + // Check that the staker has earned the expected amount. + let balance_after = + <::Assets>::balance(reward_asset_id.clone(), &staker); + assert_eq!(balance_after - balance_before, expected_earned); + }); +} + +fn events() -> Vec> { + let result = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let mock::RuntimeEvent::StakingRewards(inner) = e { + Some(inner) + } else { + None + } + }) + .collect(); + + System::reset_events(); + + result +} + +fn pools() -> Vec<(u32, PoolInfo, u128, u64>)> { + Pools::::iter().collect() +} + +mod create_pool { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + // Create a pool with default values, and no admin override so [`PermissionedAccountId`] + // is admin. + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID), + Box::new(DEFAULT_REWARD_ASSET_ID), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::::get(), 1); + assert_eq!( + pools(), + vec![( + 0, + PoolInfo { + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + )] + ); + + // Create another pool with explicit admin and other overrides. + let admin = 2; + let staked_asset_id = NativeOrWithId::::WithId(10); + let reward_asset_id = NativeOrWithId::::WithId(20); + let reward_rate_per_block = 250; + let expiry_block = 500; + let expected_expiry_block = expiry_block + 10; + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(expiry_block), + Some(admin) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 1, + staked_asset_id: staked_asset_id.clone(), + reward_asset_id: reward_asset_id.clone(), + reward_rate_per_block, + admin, + expiry_block: expected_expiry_block, + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::::get(), 2); + assert_eq!( + pools(), + vec![ + ( + 0, + PoolInfo { + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + admin: PermissionedAccountId::get(), + expiry_block: DEFAULT_EXPIRE_AFTER + 10, + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + ), + ( + 1, + PoolInfo { + staked_asset_id, + reward_asset_id, + reward_rate_per_block, + admin, + total_tokens_staked: 0, + expiry_block: expected_expiry_block, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&1), + } + ) + ] + ); + }); + } + + #[test] + fn success_same_assets() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + // Create a pool with the same staking and reward asset. + let asset = NativeOrWithId::::Native; + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(asset.clone()), + Box::new(asset.clone()), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + Some(PermissionedAccountId::get()) + )); + + // Event is emitted. + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: asset.clone(), + reward_asset_id: asset.clone(), + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + // State is updated correctly. + assert_eq!(NextPoolId::::get(), 1); + assert_eq!( + pools(), + vec![( + 0, + PoolInfo { + staked_asset_id: asset.clone(), + reward_asset_id: asset, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + total_tokens_staked: 0, + reward_per_token_stored: 0, + last_update_block: 0, + account: StakingRewards::pool_account_id(&0), + } + )] + ); + }) + } + + #[test] + fn fails_for_non_existent_asset() { + new_test_ext().execute_with(|| { + let valid_asset = NativeOrWithId::::WithId(1); + let invalid_asset = NativeOrWithId::::WithId(200); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(valid_asset.clone()), + Box::new(invalid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::::NonExistentAsset + ); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(invalid_asset.clone()), + Box::new(valid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::::NonExistentAsset + ); + + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(invalid_asset.clone()), + Box::new(invalid_asset.clone()), + 10, + DispatchTime::After(10u64), + None + ), + Error::::NonExistentAsset + ); + }) + } + + #[test] + fn fails_for_not_permissioned() { + new_test_ext().execute_with(|| { + let user = 100; + let staked_asset_id = NativeOrWithId::::Native; + let reward_asset_id = NativeOrWithId::::WithId(1); + let reward_rate_per_block = 100; + let expiry_block = 100u64; + assert_err!( + StakingRewards::create_pool( + RuntimeOrigin::signed(user), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(expiry_block), + None + ), + BadOrigin + ); + }); + } + + #[test] + fn create_pool_with_caller_admin() { + new_test_ext().execute_with(|| { + assert_eq!(NextPoolId::::get(), 0); + + System::set_block_number(10); + let expected_expiry_block = DEFAULT_EXPIRE_AFTER + 10; + + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(DEFAULT_STAKED_ASSET_ID), + Box::new(DEFAULT_REWARD_ASSET_ID), + DEFAULT_REWARD_RATE_PER_BLOCK, + DispatchTime::After(DEFAULT_EXPIRE_AFTER), + None, + )); + + assert_eq!( + events(), + [Event::::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id: 0, + staked_asset_id: DEFAULT_STAKED_ASSET_ID, + reward_asset_id: DEFAULT_REWARD_ASSET_ID, + reward_rate_per_block: DEFAULT_REWARD_RATE_PER_BLOCK, + expiry_block: expected_expiry_block, + admin: PermissionedAccountId::get(), + }] + ); + + assert_eq!(Pools::::get(0).unwrap().admin, PermissionedAccountId::get()); + }); + } +} + +mod stake { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + let initial_balance = >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ); + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::::get(pool_id, user).unwrap().amount, 1000); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Staked { staker: user, amount: 1000, pool_id: 0 } + ); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 1000); + + // Check user's frozen balance is updated + assert_eq!( + >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ), + // - extra 1 for ed + initial_balance - 1000 - 1 + ); + + // User stakes more tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 500)); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Staked { staker: user, amount: 500, pool_id: 0 } + ); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::::get(pool_id, user).unwrap().amount, 1000 + 500); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 1000 + 500); + + assert_eq!( + >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ), + // - extra 1 for ed + initial_balance - 1500 - 1 + ); + + // Event is emitted. + assert_eq!(events(), []); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let user = 1; + assert_err!( + StakingRewards::stake(RuntimeOrigin::signed(user), 999, 1000), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_balance() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + let initial_balance = >::reducible_balance( + 1, + &user, + Preservation::Expendable, + Fortitude::Force, + ); + assert_err!( + StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, initial_balance + 1), + TokenError::FundsUnavailable, + ); + }) + } +} + +mod unstake { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // User unstakes tokens + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500, None)); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Unstaked { + caller: user, + staker: user, + amount: 500, + pool_id: 0 + } + ); + + // Check that the user's staked amount is updated + assert_eq!(PoolStakers::::get(pool_id, user).unwrap().amount, 500); + + // Check that the pool's total tokens staked is updated + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 500); + + // User unstakes remaining tokens + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 500, None)); + + // Check that the storage items is removed since stake amount and rewards are zero. + assert!(PoolStakers::::get(pool_id, user).is_none()); + + // Check that the pool's total tokens staked is zero + assert_eq!(Pools::::get(pool_id).unwrap().total_tokens_staked, 0); + }); + } + + #[test] + fn unstake_for_other() { + new_test_ext().execute_with(|| { + let staker = 1; + let caller = 2; + let pool_id = 0; + let init_block = System::block_number(); + + create_default_pool(); + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + // Fails to unstake for other since pool is still active + assert_noop!( + StakingRewards::unstake(RuntimeOrigin::signed(caller), pool_id, 500, Some(staker)), + BadOrigin, + ); + + System::set_block_number(init_block + DEFAULT_EXPIRE_AFTER + 1); + + assert_ok!(StakingRewards::unstake( + RuntimeOrigin::signed(caller), + pool_id, + 500, + Some(staker) + )); + + // Event is emitted. + assert_eq!( + *events().last().unwrap(), + Event::::Unstaked { caller, staker, amount: 500, pool_id: 0 } + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let user = 1; + let non_existent_pool_id = 999; + + // User tries to unstake tokens from a non-existent pool + assert_err!( + StakingRewards::unstake( + RuntimeOrigin::signed(user), + non_existent_pool_id, + 500, + None + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_staked_amount() { + new_test_ext().execute_with(|| { + let user = 1; + create_default_pool(); + let pool_id = 0; + + // User stakes tokens + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(user), pool_id, 1000)); + + // User tries to unstake more tokens than they have staked + assert_err!( + StakingRewards::unstake(RuntimeOrigin::signed(user), pool_id, 1500, None), + Error::::NotEnoughTokens + ); + }); + } +} + +mod harvest_rewards { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let staker = 1; + let pool_id = 0; + let reward_asset_id = NativeOrWithId::::Native; + create_default_pool(); + + // Stake + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + // Harvest + System::set_block_number(20); + let balance_before: ::Balance = + <::Assets>::balance(reward_asset_id.clone(), &staker); + assert_ok!(StakingRewards::harvest_rewards( + RuntimeOrigin::signed(staker), + pool_id, + None + )); + let balance_after = + <::Assets>::balance(reward_asset_id.clone(), &staker); + + // Assert + assert_eq!( + balance_after - balance_before, + 10 * Pools::::get(pool_id).unwrap().reward_rate_per_block + ); + assert_eq!( + *events().last().unwrap(), + Event::::RewardsHarvested { + caller: staker, + staker, + pool_id, + amount: 10 * Pools::::get(pool_id).unwrap().reward_rate_per_block + } + ); + }); + } + + #[test] + fn harvest_for_other() { + new_test_ext().execute_with(|| { + let caller = 2; + let staker = 1; + let pool_id = 0; + let init_block = System::block_number(); + + create_default_pool(); + + // Stake + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + + System::set_block_number(20); + + // Fails to harvest for staker since pool is still active + assert_noop!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(caller), + pool_id, + Some(staker) + ), + BadOrigin + ); + + System::set_block_number(init_block + DEFAULT_EXPIRE_AFTER + 1); + + // Harvest for staker + assert_ok!(StakingRewards::harvest_rewards( + RuntimeOrigin::signed(caller), + pool_id, + Some(staker), + )); + + assert!(matches!( + events().last().unwrap(), + Event::::RewardsHarvested { + caller, + staker, + pool_id, + .. + } if caller == caller && staker == staker && pool_id == pool_id + )); + }); + } + + #[test] + fn fails_for_non_existent_staker() { + new_test_ext().execute_with(|| { + let non_existent_staker = 999; + + create_default_pool(); + assert_err!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(non_existent_staker), + 0, + None + ), + Error::::NonExistentStaker + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let staker = 1; + let non_existent_pool_id = 999; + + assert_err!( + StakingRewards::harvest_rewards( + RuntimeOrigin::signed(staker), + non_existent_pool_id, + None, + ), + Error::::NonExistentPool + ); + }); + } +} + +mod set_pool_admin { + use super::*; + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let admin = 1; + let new_admin = 2; + let pool_id = 0; + create_default_pool(); + + // Modify the pool admin + assert_ok!(StakingRewards::set_pool_admin( + RuntimeOrigin::signed(admin), + pool_id, + new_admin, + )); + + // Check state + assert_eq!( + *events().last().unwrap(), + Event::::PoolAdminModified { pool_id, new_admin } + ); + assert_eq!(Pools::::get(pool_id).unwrap().admin, new_admin); + }); + } + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let new_admin = 2; + let pool_id = 0; + create_default_pool_permissioned_admin(); + + // Modify the pool admin + assert_ok!(StakingRewards::set_pool_admin(RuntimeOrigin::root(), pool_id, new_admin)); + + // Check state + assert_eq!( + *events().last().unwrap(), + Event::::PoolAdminModified { pool_id, new_admin } + ); + assert_eq!(Pools::::get(pool_id).unwrap().admin, new_admin); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let new_admin = 2; + let non_existent_pool_id = 999; + + assert_err!( + StakingRewards::set_pool_admin( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + new_admin + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let new_admin = 2; + let non_admin = 3; + let pool_id = 0; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_admin( + RuntimeOrigin::signed(non_admin), + pool_id, + new_admin + ), + BadOrigin + ); + }); + } +} + +mod set_pool_expiry_block { + use super::*; + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_expiry_block = System::block_number() + DEFAULT_EXPIRE_AFTER + 1u64; + create_default_pool_permissioned_admin(); + + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::root(), + pool_id, + DispatchTime::At(new_expiry_block), + )); + + // Check state + assert_eq!(Pools::::get(pool_id).unwrap().expiry_block, new_expiry_block); + assert_eq!( + *events().last().unwrap(), + Event::::PoolExpiryBlockModified { pool_id, new_expiry_block } + ); + }); + } + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + let new_expiry_block = System::block_number() + DEFAULT_EXPIRE_AFTER + 1u64; + create_default_pool(); + + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(new_expiry_block) + )); + + // Check state + assert_eq!(Pools::::get(pool_id).unwrap().expiry_block, new_expiry_block); + assert_eq!( + *events().last().unwrap(), + Event::::PoolExpiryBlockModified { pool_id, new_expiry_block } + ); + }); + } + + #[test] + fn extends_reward_accumulation() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker = 2; + let pool_id = 0; + let new_expiry_block = 300u64; + System::set_block_number(10); + create_default_pool(); + + // Regular reward accumulation + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + System::set_block_number(20); + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 10, + pool_id, + NativeOrWithId::::Native, + ); + + // Expiry was block 210, so earned 200 at block 250 + System::set_block_number(250); + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 200, + pool_id, + NativeOrWithId::::Native, + ); + + // Extend expiry 50 more blocks + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(new_expiry_block) + )); + System::set_block_number(350); + + // Staker has been in pool with rewards active for 250 blocks total + assert_hypothetically_earned( + staker, + DEFAULT_REWARD_RATE_PER_BLOCK * 250, + pool_id, + NativeOrWithId::::Native, + ); + }); + } + + #[test] + fn fails_to_cutback_expiration() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + create_default_pool(); + + assert_noop!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::After(30) + ), + Error::::ExpiryCut + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let non_existent_pool_id = 999; + let new_expiry_block = 200u64; + + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + DispatchTime::After(new_expiry_block) + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let non_admin = 2; + let pool_id = 0; + let new_expiry_block = 200u64; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(non_admin), + pool_id, + DispatchTime::After(new_expiry_block) + ), + BadOrigin + ); + }); + } + + #[test] + fn fails_for_expiry_block_in_the_past() { + new_test_ext().execute_with(|| { + let admin = 1; + let pool_id = 0; + create_default_pool(); + System::set_block_number(50); + assert_err!( + StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(40u64) + ), + Error::::ExpiryBlockMustBeInTheFuture + ); + }); + } +} + +mod set_pool_reward_rate_per_block { + use super::*; + + #[test] + fn success_signed_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool(); + + // Pool Admin can modify + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(DEFAULT_ADMIN), + pool_id, + new_reward_rate + )); + + // Check state + assert_eq!( + Pools::::get(pool_id).unwrap().reward_rate_per_block, + new_reward_rate + ); + + // Check event + assert_eq!( + *events().last().unwrap(), + Event::::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block: new_reward_rate + } + ); + }); + } + + #[test] + fn success_permissioned_admin() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool_permissioned_admin(); + + // Root can modify + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::root(), + pool_id, + new_reward_rate + )); + + // Check state + assert_eq!( + Pools::::get(pool_id).unwrap().reward_rate_per_block, + new_reward_rate + ); + + // Check event + assert_eq!( + *events().last().unwrap(), + Event::::PoolRewardRateModified { + pool_id, + new_reward_rate_per_block: new_reward_rate + } + ); + }); + } + + #[test] + fn staker_rewards_are_affected_correctly() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker = 2; + let pool_id = 0; + let new_reward_rate = 150; + create_default_pool(); + + // Stake some tokens, and accumulate 10 blocks of rewards at the default pool rate (100) + System::set_block_number(10); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 1000)); + System::set_block_number(20); + + // Increase the reward rate + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + pool_id, + new_reward_rate + )); + + // Accumulate 10 blocks of rewards at the new rate + System::set_block_number(30); + + // Check that rewards are calculated correctly with the updated rate + assert_hypothetically_earned( + staker, + 10 * 100 + 10 * new_reward_rate, + pool_id, + NativeOrWithId::::Native, + ); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + let admin = 1; + let non_existent_pool_id = 999; + let new_reward_rate = 200; + + assert_err!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + non_existent_pool_id, + new_reward_rate + ), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_non_admin() { + new_test_ext().execute_with(|| { + let non_admin = 2; + let pool_id = 0; + let new_reward_rate = 200; + create_default_pool(); + + assert_err!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(non_admin), + pool_id, + new_reward_rate + ), + BadOrigin + ); + }); + } + + #[test] + fn fails_to_decrease() { + new_test_ext().execute_with(|| { + create_default_pool_permissioned_admin(); + + assert_noop!( + StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::root(), + 0, + DEFAULT_REWARD_RATE_PER_BLOCK - 1 + ), + Error::::RewardRateCut + ); + }); + } +} + +mod deposit_reward_tokens { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let depositor = 1; + let pool_id = 0; + let amount = 1000; + let reward_asset_id = NativeOrWithId::::Native; + create_default_pool(); + let pool_account_id = StakingRewards::pool_account_id(&pool_id); + + let depositor_balance_before = + <::Assets>::balance(reward_asset_id.clone(), &depositor); + let pool_balance_before = <::Assets>::balance( + reward_asset_id.clone(), + &pool_account_id, + ); + assert_ok!(StakingRewards::deposit_reward_tokens( + RuntimeOrigin::signed(depositor), + pool_id, + amount + )); + let depositor_balance_after = + <::Assets>::balance(reward_asset_id.clone(), &depositor); + let pool_balance_after = + <::Assets>::balance(reward_asset_id, &pool_account_id); + + assert_eq!(pool_balance_after - pool_balance_before, amount); + assert_eq!(depositor_balance_before - depositor_balance_after, amount); + }); + } + + #[test] + fn fails_for_non_existent_pool() { + new_test_ext().execute_with(|| { + assert_err!( + StakingRewards::deposit_reward_tokens(RuntimeOrigin::signed(1), 999, 100), + Error::::NonExistentPool + ); + }); + } + + #[test] + fn fails_for_insufficient_balance() { + new_test_ext().execute_with(|| { + create_default_pool(); + assert_err!( + StakingRewards::deposit_reward_tokens(RuntimeOrigin::signed(1), 0, 100_000_000), + ArithmeticError::Underflow + ); + }); + } +} + +mod cleanup_pool { + use super::*; + + #[test] + fn success() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let admin = DEFAULT_ADMIN; + let admin_balance_before = >::balance(&admin); + + create_default_pool(); + assert!(Pools::::get(pool_id).is_some()); + + assert_ok!(StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id)); + + assert_eq!( + >::balance(&admin), + // `100_000` initial pool account balance from Genesis config + admin_balance_before + 100_000, + ); + assert_eq!(Pools::::get(pool_id), None); + assert_eq!(PoolStakers::::iter_prefix_values(pool_id).count(), 0); + assert_eq!(PoolCost::::get(pool_id), None); + }); + } + + #[test] + fn success_only_when_pool_empty() { + new_test_ext().execute_with(|| { + let pool_id = 0; + let staker = 20; + let admin = DEFAULT_ADMIN; + + create_default_pool(); + + // stake to prevent pool cleanup + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker), pool_id, 100)); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id), + Error::::NonEmptyPool + ); + + // unstake partially + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker), pool_id, 50, None)); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id), + Error::::NonEmptyPool + ); + + // unstake all + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker), pool_id, 50, None)); + + assert_ok!(StakingRewards::cleanup_pool(RuntimeOrigin::signed(admin), pool_id),); + + assert_eq!(Pools::::get(pool_id), None); + assert_eq!(PoolStakers::::iter_prefix_values(pool_id).count(), 0); + assert_eq!(PoolCost::::get(pool_id), None); + }); + } + + #[test] + fn fails_on_wrong_origin() { + new_test_ext().execute_with(|| { + let caller = 888; + let pool_id = 0; + create_default_pool(); + + assert_noop!( + StakingRewards::cleanup_pool(RuntimeOrigin::signed(caller), pool_id), + BadOrigin + ); + }); + } +} + +/// This integration test +/// 1. Considers 2 stakers each staking and unstaking at different intervals, asserts their +/// claimable rewards are adjusted as expected, and that harvesting works. +/// 2. Checks that rewards are correctly halted after the pool's expiry block, and resume when the +/// pool is extended. +/// 3. Checks that reward rates adjustment works correctly. +/// +/// Note: There are occasionally off by 1 errors due to rounding. In practice this is +/// insignificant. +#[test] +fn integration() { + new_test_ext().execute_with(|| { + let admin = 1; + let staker1 = 10u128; + let staker2 = 20; + let staked_asset_id = NativeOrWithId::::WithId(1); + let reward_asset_id = NativeOrWithId::::Native; + let reward_rate_per_block = 100; + let lifetime = 24u64.into(); + System::set_block_number(1); + assert_ok!(StakingRewards::create_pool( + RuntimeOrigin::root(), + Box::new(staked_asset_id.clone()), + Box::new(reward_asset_id.clone()), + reward_rate_per_block, + DispatchTime::After(lifetime), + Some(admin) + )); + let pool_id = 0; + + // Block 7: Staker 1 stakes 100 tokens. + System::set_block_number(7); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker1), pool_id, 100)); + // At this point + // - Staker 1 has earned 0 tokens. + // - Staker 1 is earning 100 tokens per block. + + // Check that Staker 1 has earned 0 tokens. + assert_hypothetically_earned(staker1, 0, pool_id, reward_asset_id.clone()); + + // Block 9: Staker 2 stakes 100 tokens. + System::set_block_number(9); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker2), pool_id, 100)); + // At this point + // - Staker 1 has earned 200 (100*2) tokens. + // - Staker 2 has earned 0 tokens. + // - Staker 1 is earning 50 tokens per block. + // - Staker 2 is earning 50 tokens per block. + + // Check that Staker 1 has earned 200 tokens and Staker 2 has earned 0 tokens. + assert_hypothetically_earned(staker1, 200, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 0, pool_id, reward_asset_id.clone()); + + // Block 12: Staker 1 stakes an additional 100 tokens. + System::set_block_number(12); + assert_ok!(StakingRewards::stake(RuntimeOrigin::signed(staker1), pool_id, 100)); + // At this point + // - Staker 1 has earned 350 (200 + (50 * 3)) tokens. + // - Staker 2 has earned 150 (50 * 3) tokens. + // - Staker 1 is earning 66.66 tokens per block. + // - Staker 2 is earning 33.33 tokens per block. + + // Check that Staker 1 has earned 350 tokens and Staker 2 has earned 150 tokens. + assert_hypothetically_earned(staker1, 350, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 150, pool_id, reward_asset_id.clone()); + + // Block 22: Staker 1 unstakes 100 tokens. + System::set_block_number(22); + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker1), pool_id, 100, None)); + // - Staker 1 has earned 1016 (350 + 66.66 * 10) tokens. + // - Staker 2 has earned 483 (150 + 33.33 * 10) tokens. + // - Staker 1 is earning 50 tokens per block. + // - Staker 2 is earning 50 tokens per block. + assert_hypothetically_earned(staker1, 1016, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 483, pool_id, reward_asset_id.clone()); + + // Block 23: Staker 1 unstakes 100 tokens. + System::set_block_number(23); + assert_ok!(StakingRewards::unstake(RuntimeOrigin::signed(staker1), pool_id, 100, None)); + // - Staker 1 has earned 1065 (1015 + 50) tokens. + // - Staker 2 has earned 533 (483 + 50) tokens. + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 100 tokens per block. + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 533, pool_id, reward_asset_id.clone()); + + // Block 50: Stakers should only have earned 2 blocks worth of tokens (expiry is 25). + System::set_block_number(50); + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 733 (533 + 2 * 100) tokens. + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 0 tokens per block. + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 733, pool_id, reward_asset_id.clone()); + + // Block 51: Extend the pool expiry block to 60. + System::set_block_number(51); + // - Staker 1 is earning 0 tokens per block. + // - Staker 2 is earning 100 tokens per block. + assert_ok!(StakingRewards::set_pool_expiry_block( + RuntimeOrigin::signed(admin), + pool_id, + DispatchTime::At(60u64), + )); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 733, pool_id, reward_asset_id.clone()); + + // Block 53: Check rewards are resumed. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 933 (733 + 2 * 100) tokens. + // - Staker 2 is earning 100 tokens per block. + System::set_block_number(53); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 933, pool_id, reward_asset_id.clone()); + + // Block 55: Increase the block reward. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 1133 (933 + 2 * 100) tokens. + // - Staker 2 is earning 50 tokens per block. + System::set_block_number(55); + assert_ok!(StakingRewards::set_pool_reward_rate_per_block( + RuntimeOrigin::signed(admin), + pool_id, + 150 + )); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 1133, pool_id, reward_asset_id.clone()); + + // Block 57: Staker2 harvests their rewards. + System::set_block_number(57); + // - Staker 2 has earned 1433 (1133 + 2 * 150) tokens. + assert_hypothetically_earned(staker2, 1433, pool_id, reward_asset_id.clone()); + // Get the pre-harvest balance. + let balance_before: ::Balance = + <::Assets>::balance(reward_asset_id.clone(), &staker2); + assert_ok!(StakingRewards::harvest_rewards(RuntimeOrigin::signed(staker2), pool_id, None)); + let balance_after = + <::Assets>::balance(reward_asset_id.clone(), &staker2); + assert_eq!(balance_after - balance_before, 1433u128); + + // Block 60: Check rewards were adjusted correctly. + // - Staker 1 has earned 1065 tokens. + // - Staker 2 has earned 450 (3 * 150) tokens. + System::set_block_number(60); + assert_hypothetically_earned(staker1, 1066, pool_id, reward_asset_id.clone()); + assert_hypothetically_earned(staker2, 450, pool_id, reward_asset_id.clone()); + + // Finally, check events. + assert_eq!( + events(), + [ + Event::PoolCreated { + creator: PermissionedAccountId::get(), + pool_id, + staked_asset_id, + reward_asset_id, + reward_rate_per_block: 100, + expiry_block: 25, + admin, + }, + Event::Staked { staker: staker1, pool_id, amount: 100 }, + Event::Staked { staker: staker2, pool_id, amount: 100 }, + Event::Staked { staker: staker1, pool_id, amount: 100 }, + Event::Unstaked { caller: staker1, staker: staker1, pool_id, amount: 100 }, + Event::Unstaked { caller: staker1, staker: staker1, pool_id, amount: 100 }, + Event::PoolExpiryBlockModified { pool_id, new_expiry_block: 60 }, + Event::PoolRewardRateModified { pool_id, new_reward_rate_per_block: 150 }, + Event::RewardsHarvested { caller: staker2, staker: staker2, pool_id, amount: 1433 } + ] + ); + }); +} diff --git a/substrate/frame/asset-rewards/src/weights.rs b/substrate/frame/asset-rewards/src/weights.rs new file mode 100644 index 000000000000..c9e2d0fd251a --- /dev/null +++ b/substrate/frame/asset-rewards/src/weights.rs @@ -0,0 +1,368 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_asset_rewards` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-ys-ssygq-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate-node +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_asset_rewards +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/asset-rewards/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_asset_rewards`. +pub trait WeightInfo { + fn create_pool() -> Weight; + fn stake() -> Weight; + fn unstake() -> Weight; + fn harvest_rewards() -> Weight; + fn set_pool_reward_rate_per_block() -> Weight; + fn set_pool_admin() -> Weight; + fn set_pool_expiry_block() -> Weight; + fn deposit_reward_tokens() -> Weight; + fn cleanup_pool() -> Weight; +} + +/// Weights for `pallet_asset_rewards` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `6360` + // Minimum execution time: 62_655_000 picoseconds. + Weight::from_parts(63_723_000, 6360) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 54_463_000 picoseconds. + Weight::from_parts(55_974_000, 3615) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 55_749_000 picoseconds. + Weight::from_parts(57_652_000, 3615) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1021` + // Estimated: `6208` + // Minimum execution time: 69_372_000 picoseconds. + Weight::from_parts(70_278_000, 6208) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_284_000 picoseconds. + Weight::from_parts(19_791_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 17_388_000 picoseconds. + Weight::from_parts(18_390_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_780_000 picoseconds. + Weight::from_parts(20_676_000, 3615) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `840` + // Estimated: `6208` + // Minimum execution time: 57_746_000 picoseconds. + Weight::from_parts(59_669_000, 6208) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1236` + // Estimated: `6208` + // Minimum execution time: 110_443_000 picoseconds. + Weight::from_parts(113_149_000, 6208) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Assets::Asset` (r:2 w:0) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::NextPoolId` (r:1 w:1) + /// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:0 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::Pools` (r:0 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `6360` + // Minimum execution time: 62_655_000 picoseconds. + Weight::from_parts(63_723_000, 6360) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn stake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 54_463_000 picoseconds. + Weight::from_parts(55_974_000, 3615) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::Freezes` (r:1 w:1) + /// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:0) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1) + /// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`) + fn unstake() -> Weight { + // Proof Size summary in bytes: + // Measured: `935` + // Estimated: `3615` + // Minimum execution time: 55_749_000 picoseconds. + Weight::from_parts(57_652_000, 3615) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:1) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn harvest_rewards() -> Weight { + // Proof Size summary in bytes: + // Measured: `1021` + // Estimated: `6208` + // Minimum execution time: 69_372_000 picoseconds. + Weight::from_parts(70_278_000, 6208) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_reward_rate_per_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_284_000 picoseconds. + Weight::from_parts(19_791_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_admin() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 17_388_000 picoseconds. + Weight::from_parts(18_390_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + fn set_pool_expiry_block() -> Weight { + // Proof Size summary in bytes: + // Measured: `347` + // Estimated: `3615` + // Minimum execution time: 19_780_000 picoseconds. + Weight::from_parts(20_676_000, 3615) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:0) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn deposit_reward_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `840` + // Estimated: `6208` + // Minimum execution time: 57_746_000 picoseconds. + Weight::from_parts(59_669_000, 6208) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: `AssetRewards::Pools` (r:1 w:1) + /// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolStakers` (r:1 w:0) + /// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `AssetRewards::PoolCost` (r:1 w:1) + /// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(373), added: 2848, mode: `MaxEncodedLen`) + fn cleanup_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `1236` + // Estimated: `6208` + // Minimum execution time: 110_443_000 picoseconds. + Weight::from_parts(113_149_000, 6208) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + } +} diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 728426cc84c7..4a83c809a6a5 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -96,8 +96,9 @@ mod storage; #[cfg(feature = "experimental")] pub use storage::MaybeConsideration; pub use storage::{ - Consideration, Footprint, Incrementable, Instance, LinearStoragePrice, PartialStorageInfoTrait, - StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey, WhitelistedStorageKeys, + Consideration, ConstantStoragePrice, Footprint, Incrementable, Instance, LinearStoragePrice, + PartialStorageInfoTrait, StorageInfo, StorageInfoTrait, StorageInstance, TrackedStorageKey, + WhitelistedStorageKeys, }; mod dispatch; diff --git a/substrate/frame/support/src/traits/storage.rs b/substrate/frame/support/src/traits/storage.rs index 2b8e43707389..676b73e03d3c 100644 --- a/substrate/frame/support/src/traits/storage.rs +++ b/substrate/frame/support/src/traits/storage.rs @@ -200,6 +200,18 @@ where } } +/// Constant `Price` regardless of the given [`Footprint`]. +pub struct ConstantStoragePrice(PhantomData<(Price, Balance)>); +impl Convert for ConstantStoragePrice +where + Price: Get, + Balance: From + sp_runtime::Saturating, +{ + fn convert(_: Footprint) -> Balance { + Price::get() + } +} + /// Some sort of cost taken from account temporarily in order to offset the cost to the chain of /// holding some data [`Footprint`] in state. /// diff --git a/umbrella/Cargo.toml b/umbrella/Cargo.toml index 17a7c02e8259..fc0b2d5a140e 100644 --- a/umbrella/Cargo.toml +++ b/umbrella/Cargo.toml @@ -57,6 +57,7 @@ std = [ "pallet-asset-conversion-tx-payment?/std", "pallet-asset-conversion?/std", "pallet-asset-rate?/std", + "pallet-asset-rewards?/std", "pallet-asset-tx-payment?/std", "pallet-assets-freezer?/std", "pallet-assets?/std", @@ -256,6 +257,7 @@ runtime-benchmarks = [ "pallet-asset-conversion-tx-payment?/runtime-benchmarks", "pallet-asset-conversion?/runtime-benchmarks", "pallet-asset-rate?/runtime-benchmarks", + "pallet-asset-rewards?/runtime-benchmarks", "pallet-asset-tx-payment?/runtime-benchmarks", "pallet-assets-freezer?/runtime-benchmarks", "pallet-assets?/runtime-benchmarks", @@ -386,6 +388,7 @@ try-runtime = [ "pallet-asset-conversion-tx-payment?/try-runtime", "pallet-asset-conversion?/try-runtime", "pallet-asset-rate?/try-runtime", + "pallet-asset-rewards?/try-runtime", "pallet-asset-tx-payment?/try-runtime", "pallet-assets-freezer?/try-runtime", "pallet-assets?/try-runtime", @@ -543,7 +546,7 @@ with-tracing = [ "sp-tracing?/with-tracing", "sp-tracing?/with-tracing", ] -runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] +runtime-full = ["assets-common", "binary-merkle-tree", "bp-header-chain", "bp-messages", "bp-parachains", "bp-polkadot", "bp-polkadot-core", "bp-relayers", "bp-runtime", "bp-test-utils", "bp-xcm-bridge-hub", "bp-xcm-bridge-hub-router", "bridge-hub-common", "bridge-runtime-common", "cumulus-pallet-aura-ext", "cumulus-pallet-dmp-queue", "cumulus-pallet-parachain-system", "cumulus-pallet-parachain-system-proc-macro", "cumulus-pallet-session-benchmarking", "cumulus-pallet-solo-to-para", "cumulus-pallet-weight-reclaim", "cumulus-pallet-xcm", "cumulus-pallet-xcmp-queue", "cumulus-ping", "cumulus-primitives-aura", "cumulus-primitives-core", "cumulus-primitives-parachain-inherent", "cumulus-primitives-proof-size-hostfunction", "cumulus-primitives-storage-weight-reclaim", "cumulus-primitives-timestamp", "cumulus-primitives-utility", "frame-benchmarking", "frame-benchmarking-pallet-pov", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-executive", "frame-metadata-hash-extension", "frame-support", "frame-support-procedural", "frame-support-procedural-tools-derive", "frame-system", "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", "pallet-alliance", "pallet-asset-conversion", "pallet-asset-conversion-ops", "pallet-asset-conversion-tx-payment", "pallet-asset-rate", "pallet-asset-rewards", "pallet-asset-tx-payment", "pallet-assets", "pallet-assets-freezer", "pallet-atomic-swap", "pallet-aura", "pallet-authority-discovery", "pallet-authorship", "pallet-babe", "pallet-bags-list", "pallet-balances", "pallet-beefy", "pallet-beefy-mmr", "pallet-bounties", "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", "pallet-bridge-relayers", "pallet-broker", "pallet-child-bounties", "pallet-collator-selection", "pallet-collective", "pallet-collective-content", "pallet-contracts", "pallet-contracts-proc-macro", "pallet-contracts-uapi", "pallet-conviction-voting", "pallet-core-fellowship", "pallet-delegated-staking", "pallet-democracy", "pallet-dev-mode", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-migrations", "pallet-mixnet", "pallet-mmr", "pallet-multisig", "pallet-nft-fractionalization", "pallet-nfts", "pallet-nfts-runtime-api", "pallet-nis", "pallet-node-authorization", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-paged-list", "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-revive", "pallet-revive-proc-macro", "pallet-revive-uapi", "pallet-root-offences", "pallet-root-testing", "pallet-safe-mode", "pallet-salary", "pallet-scheduler", "pallet-scored-pool", "pallet-session", "pallet-session-benchmarking", "pallet-skip-feeless-payment", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", "pallet-staking-reward-fn", "pallet-staking-runtime-api", "pallet-state-trie-migration", "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", "pallet-transaction-payment", "pallet-transaction-payment-rpc-runtime-api", "pallet-transaction-storage", "pallet-treasury", "pallet-tx-pause", "pallet-uniques", "pallet-utility", "pallet-verify-signature", "pallet-vesting", "pallet-whitelist", "pallet-xcm", "pallet-xcm-benchmarks", "pallet-xcm-bridge-hub", "pallet-xcm-bridge-hub-router", "parachains-common", "polkadot-core-primitives", "polkadot-parachain-primitives", "polkadot-primitives", "polkadot-runtime-common", "polkadot-runtime-metrics", "polkadot-runtime-parachains", "polkadot-sdk-frame", "sc-chain-spec-derive", "sc-tracing-proc-macro", "slot-range-helper", "snowbridge-beacon-primitives", "snowbridge-core", "snowbridge-ethereum", "snowbridge-outbound-queue-merkle-tree", "snowbridge-outbound-queue-runtime-api", "snowbridge-pallet-ethereum-client", "snowbridge-pallet-ethereum-client-fixtures", "snowbridge-pallet-inbound-queue", "snowbridge-pallet-inbound-queue-fixtures", "snowbridge-pallet-outbound-queue", "snowbridge-pallet-system", "snowbridge-router-primitives", "snowbridge-runtime-common", "snowbridge-system-runtime-api", "sp-api", "sp-api-proc-macro", "sp-application-crypto", "sp-arithmetic", "sp-authority-discovery", "sp-block-builder", "sp-consensus-aura", "sp-consensus-babe", "sp-consensus-beefy", "sp-consensus-grandpa", "sp-consensus-pow", "sp-consensus-slots", "sp-core", "sp-crypto-ec-utils", "sp-crypto-hashing", "sp-crypto-hashing-proc-macro", "sp-debug-derive", "sp-externalities", "sp-genesis-builder", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-metadata-ir", "sp-mixnet", "sp-mmr-primitives", "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-runtime-interface", "sp-runtime-interface-proc-macro", "sp-session", "sp-staking", "sp-state-machine", "sp-statement-store", "sp-std", "sp-storage", "sp-timestamp", "sp-tracing", "sp-transaction-pool", "sp-transaction-storage-proof", "sp-trie", "sp-version", "sp-version-proc-macro", "sp-wasm-interface", "sp-weights", "staging-parachain-info", "staging-xcm", "staging-xcm-builder", "staging-xcm-executor", "substrate-bip39", "testnet-parachains-constants", "tracing-gum-proc-macro", "xcm-procedural", "xcm-runtime-apis"] runtime = [ "frame-benchmarking", "frame-benchmarking-pallet-pov", @@ -870,6 +873,11 @@ default-features = false optional = true path = "../substrate/frame/asset-rate" +[dependencies.pallet-asset-rewards] +default-features = false +optional = true +path = "../substrate/frame/asset-rewards" + [dependencies.pallet-asset-tx-payment] default-features = false optional = true diff --git a/umbrella/src/lib.rs b/umbrella/src/lib.rs index 3504f081f295..a132f16a2c33 100644 --- a/umbrella/src/lib.rs +++ b/umbrella/src/lib.rs @@ -312,6 +312,10 @@ pub use pallet_asset_conversion_tx_payment; #[cfg(feature = "pallet-asset-rate")] pub use pallet_asset_rate; +/// FRAME asset rewards pallet. +#[cfg(feature = "pallet-asset-rewards")] +pub use pallet_asset_rewards; + /// pallet to manage transaction payments in assets. #[cfg(feature = "pallet-asset-tx-payment")] pub use pallet_asset_tx_payment; From 64abc745d9a7e7d6bea471e7bd2e895c503199c2 Mon Sep 17 00:00:00 2001 From: Giuseppe Re Date: Thu, 16 Jan 2025 15:00:59 +0100 Subject: [PATCH 274/340] Update `parity-publish` to v0.10.4 (#7193) The changes from v0.10.3 are only related to dependencies version. This should fix some failing CIs. This PR also updates the Rust cache version in CI. --- .github/workflows/check-semver.yml | 2 +- .github/workflows/publish-check-compile.yml | 6 ++++-- .github/workflows/publish-check-crates.yml | 4 ++-- .github/workflows/publish-claim-crates.yml | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/check-semver.yml b/.github/workflows/check-semver.yml index 0da3e54ef60b..43c70d6abc78 100644 --- a/.github/workflows/check-semver.yml +++ b/.github/workflows/check-semver.yml @@ -81,7 +81,7 @@ jobs: - name: install parity-publish if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} # Set the target dir to cache the build. - run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.3 --locked -q + run: CARGO_TARGET_DIR=./target/ cargo install parity-publish@0.10.4 --locked -q - name: check semver if: ${{ !contains(github.event.pull_request.labels.*.name, 'R0-silent') }} diff --git a/.github/workflows/publish-check-compile.yml b/.github/workflows/publish-check-compile.yml index ce1b2cb231d0..f20909106a82 100644 --- a/.github/workflows/publish-check-compile.yml +++ b/.github/workflows/publish-check-compile.yml @@ -26,12 +26,14 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Rust Cache - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 + uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 with: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.10.3 --locked -q + run: | + rustup override set 1.82.0 + cargo install parity-publish@0.10.4 --locked -q - name: parity-publish update plan run: parity-publish --color always plan --skip-check --prdoc prdoc/ diff --git a/.github/workflows/publish-check-crates.yml b/.github/workflows/publish-check-crates.yml index 3150cb9dd405..c1b13243ba19 100644 --- a/.github/workflows/publish-check-crates.yml +++ b/.github/workflows/publish-check-crates.yml @@ -19,12 +19,12 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Rust Cache - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 + uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 with: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.10.3 --locked -q + run: cargo install parity-publish@0.10.4 --locked -q - name: parity-publish check run: parity-publish --color always check --allow-unpublished diff --git a/.github/workflows/publish-claim-crates.yml b/.github/workflows/publish-claim-crates.yml index a6efc8a5599e..804baf9ff06c 100644 --- a/.github/workflows/publish-claim-crates.yml +++ b/.github/workflows/publish-claim-crates.yml @@ -13,12 +13,12 @@ jobs: - uses: actions/checkout@6d193bf28034eafb982f37bd894289fe649468fc # v4.1.7 - name: Rust Cache - uses: Swatinem/rust-cache@82a92a6e8fbeee089604da2575dc567ae9ddeaab # v2.7.5 + uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 with: cache-on-failure: true - name: install parity-publish - run: cargo install parity-publish@0.10.3 --locked -q + run: cargo install parity-publish@0.10.4 --locked -q - name: parity-publish claim env: From f7baa84f48aa72b96e8c9a9ec8a1934431de6709 Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Thu, 16 Jan 2025 21:12:41 +0600 Subject: [PATCH 275/340] [FRAME] `pallet_asset_tx_payment`: replace `AssetId` bound from `Copy` to `Clone` (#7194) closes https://github.com/paritytech/polkadot-sdk/issues/6911 --- prdoc/pr_7194.prdoc | 15 +++++++++++++++ .../asset-tx-payment/src/lib.rs | 6 +++--- .../asset-tx-payment/src/payment.rs | 15 +++++++++------ 3 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 prdoc/pr_7194.prdoc diff --git a/prdoc/pr_7194.prdoc b/prdoc/pr_7194.prdoc new file mode 100644 index 000000000000..3a9db46ceae9 --- /dev/null +++ b/prdoc/pr_7194.prdoc @@ -0,0 +1,15 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: '[FRAME] `pallet_asset_tx_payment`: replace `AssetId` bound from `Copy` to `Clone`' + +doc: + - audience: Runtime Dev + description: | + `OnChargeAssetTransaction`'s associated type `AssetId` is bounded by `Copy` which makes it impossible + to use `staging_xcm::v4::Location` as `AssetId`. This PR bounds `AssetId` to `Clone` instead, which is + more lenient. + +crates: + - name: pallet-asset-tx-payment + bump: minor diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs index dd752989c366..4a96cbcacb58 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs +++ b/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs @@ -202,7 +202,7 @@ where debug_assert!(self.tip <= fee, "tip should be included in the computed fee"); if fee.is_zero() { Ok((fee, InitialPayment::Nothing)) - } else if let Some(asset_id) = self.asset_id { + } else if let Some(asset_id) = self.asset_id.clone() { T::OnChargeAssetTransaction::withdraw_fee( who, call, @@ -233,7 +233,7 @@ where debug_assert!(self.tip <= fee, "tip should be included in the computed fee"); if fee.is_zero() { Ok(()) - } else if let Some(asset_id) = self.asset_id { + } else if let Some(asset_id) = self.asset_id.clone() { T::OnChargeAssetTransaction::can_withdraw_fee( who, call, @@ -358,7 +358,7 @@ where tip, who, initial_payment, - asset_id: self.asset_id, + asset_id: self.asset_id.clone(), weight: self.weight(call), }) }, diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs index 2074b1476f45..7b7ae855bf8f 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs +++ b/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs @@ -40,7 +40,7 @@ pub trait OnChargeAssetTransaction { /// The underlying integer type in which fees are calculated. type Balance: Balance; /// The type used to identify the assets used for transaction payment. - type AssetId: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo; + type AssetId: FullCodec + Clone + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo; /// The type used to store the intermediate values between pre- and post-dispatch. type LiquidityInfo; @@ -112,7 +112,7 @@ where T: Config, CON: ConversionToAssetBalance, AssetIdOf, AssetBalanceOf>, HC: HandleCredit, - AssetIdOf: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo, + AssetIdOf: FullCodec + Clone + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo, { type Balance = BalanceOf; type AssetId = AssetIdOf; @@ -133,11 +133,14 @@ where // less than one (e.g. 0.5) but gets rounded down by integer division we introduce a minimum // fee. let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() }; - let converted_fee = CON::to_asset_balance(fee, asset_id) + let converted_fee = CON::to_asset_balance(fee, asset_id.clone()) .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))? .max(min_converted_fee); - let can_withdraw = - >::can_withdraw(asset_id, who, converted_fee); + let can_withdraw = >::can_withdraw( + asset_id.clone(), + who, + converted_fee, + ); if can_withdraw != WithdrawConsequence::Success { return Err(InvalidTransaction::Payment.into()) } @@ -167,7 +170,7 @@ where // less than one (e.g. 0.5) but gets rounded down by integer division we introduce a minimum // fee. let min_converted_fee = if fee.is_zero() { Zero::zero() } else { One::one() }; - let converted_fee = CON::to_asset_balance(fee, asset_id) + let converted_fee = CON::to_asset_balance(fee, asset_id.clone()) .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))? .max(min_converted_fee); let can_withdraw = From 77ad8abb4a3aada3362fc4d5780db1844cc2e15d Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:09:24 -0300 Subject: [PATCH 276/340] Migrate substrate zombienet test poc (#7178) Zombienet substrate tests PoC (using native provider). cc: @emamihe @alvicsam --- .github/workflows/build-publish-images.yml | 47 +++--- .../zombienet-reusable-preflight.yml | 145 ++++++++++++++++++ .github/workflows/zombienet_substrate.yml | 45 ++++++ .github/zombienet-env | 9 ++ 4 files changed, 223 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/zombienet-reusable-preflight.yml create mode 100644 .github/workflows/zombienet_substrate.yml create mode 100644 .github/zombienet-env diff --git a/.github/workflows/build-publish-images.yml b/.github/workflows/build-publish-images.yml index 874b5d37469c..deb3b3df5ff2 100644 --- a/.github/workflows/build-publish-images.yml +++ b/.github/workflows/build-publish-images.yml @@ -53,7 +53,7 @@ jobs: - name: pack artifacts run: | mkdir -p ./artifacts - VERSION="${{ needs.preflight.outputs.SOURCE_REF_NAME }}" # will be tag or branch name + VERSION="${{ needs.preflight.outputs.SOURCE_REF_SLUG }}" # will be tag or branch name mv ./target/testnet/polkadot ./artifacts/. mv ./target/testnet/polkadot-prepare-worker ./artifacts/. mv ./target/testnet/polkadot-execute-worker ./artifacts/. @@ -62,7 +62,7 @@ jobs: sha256sum polkadot | tee polkadot.sha256 shasum -c polkadot.sha256 cd ../ - EXTRATAG="${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}" + EXTRATAG="${{ needs.preflight.outputs.SOURCE_REF_SLUG }}-${COMMIT_SHA}" echo "Polkadot version = ${VERSION} (EXTRATAG = ${EXTRATAG})" echo -n ${VERSION} > ./artifacts/VERSION echo -n ${EXTRATAG} > ./artifacts/EXTRATAG @@ -77,7 +77,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -103,7 +103,7 @@ jobs: mkdir -p ./artifacts mv ./target/release/polkadot-parachain ./artifacts/. echo "___The VERSION is either a tag name or the curent branch if triggered not by a tag___" - echo ${{ needs.preflight.outputs.SOURCE_REF_NAME }} | tee ./artifacts/VERSION + echo ${{ needs.preflight.outputs.SOURCE_REF_SLUG }} | tee ./artifacts/VERSION - name: tar run: tar -cvf artifacts.tar artifacts @@ -111,7 +111,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -147,7 +147,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -172,8 +172,8 @@ jobs: mkdir -p ./artifacts mv ./target/testnet/adder-collator ./artifacts/. mv ./target/testnet/undying-collator ./artifacts/. - echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}" > ./artifacts/VERSION - echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG + echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}" > ./artifacts/VERSION + echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG echo "adder-collator version = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))" echo "undying-collator version = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))" cp -r ./docker/* ./artifacts @@ -184,7 +184,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -209,8 +209,8 @@ jobs: mv ./target/testnet/malus ./artifacts/. mv ./target/testnet/polkadot-execute-worker ./artifacts/. mv ./target/testnet/polkadot-prepare-worker ./artifacts/. - echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}" > ./artifacts/VERSION - echo -n "${{ needs.preflight.outputs.SOURCE_REF_NAME }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG + echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}" > ./artifacts/VERSION + echo -n "${{ needs.preflight.outputs.SOURCE_REF_SLUG }}-${COMMIT_SHA}" > ./artifacts/EXTRATAG echo "polkadot-test-malus = $(cat ./artifacts/VERSION) (EXTRATAG = $(cat ./artifacts/EXTRATAG))" cp -r ./docker/* ./artifacts @@ -220,7 +220,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -246,6 +246,7 @@ jobs: WASM_BUILD_NO_COLOR=1 forklift cargo build --locked --release -p staging-node-cli ls -la target/release/ - name: pack artifacts + shell: bash run: | mv target/release/substrate-node ./artifacts/substrate/substrate echo -n "Substrate version = " @@ -264,7 +265,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -294,7 +295,7 @@ jobs: - name: upload artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: ${{ github.job }}-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} path: artifacts.tar retention-days: 1 @@ -313,7 +314,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-test-parachain-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-test-parachain-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar @@ -337,7 +338,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar @@ -361,7 +362,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-test-collators-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-test-collators-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar @@ -385,7 +386,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-malus-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-malus-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar @@ -409,7 +410,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-substrate-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-linux-substrate-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar @@ -441,7 +442,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-linux-stable-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: | tar -xvf artifacts.tar @@ -449,7 +450,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: | tar -xvf artifacts.tar @@ -457,7 +458,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: prepare-bridges-zombienet-artifacts-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: prepare-bridges-zombienet-artifacts-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: | tar -xvf artifacts.tar @@ -482,7 +483,7 @@ jobs: - uses: actions/download-artifact@v4.1.8 with: - name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + name: build-linux-stable-cumulus-${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - name: tar run: tar -xvf artifacts.tar diff --git a/.github/workflows/zombienet-reusable-preflight.yml b/.github/workflows/zombienet-reusable-preflight.yml new file mode 100644 index 000000000000..8e938567d811 --- /dev/null +++ b/.github/workflows/zombienet-reusable-preflight.yml @@ -0,0 +1,145 @@ +# Reusable workflow to set various useful variables +# and to perform checks and generate conditions for other workflows. +# Currently it checks if any Rust (build-related) file is changed +# and if the current (caller) workflow file is changed. +# Example: +# +# jobs: +# preflight: +# uses: ./.github/workflows/reusable-preflight.yml +# some-job: +# needs: changes +# if: ${{ needs.preflight.outputs.changes_rust }} +# ....... + +name: Zombienet Preflight + +on: + workflow_call: + # Map the workflow outputs to job outputs + outputs: + changes_substrate: + value: ${{ jobs.preflight.outputs.changes_substrate }} + + ZOMBIENET_IMAGE: + value: ${{ jobs.preflight.outputs.ZOMBIENET_IMAGE }} + description: "ZOMBIENET CI image" + + ZOMBIENET_RUNNER: + value: ${{ jobs.preflight.outputs.ZOMBIENET_RUNNER }} + description: | + Main runner for zombienet tests. + + DOCKER_IMAGES_VERSION: + value: ${{ jobs.preflight.outputs.DOCKER_IMAGES_VERSION }} + description: | + Version for temp docker images. + + # Global vars (from global preflight) + SOURCE_REF_SLUG: + value: ${{ jobs.global_preflight.outputs.SOURCE_REF_SLUG }} + + # Zombie vars + PUSHGATEWAY_URL: + value: ${{ jobs.preflight.outputs.PUSHGATEWAY_URL }} + description: "Gateway (url) to push metrics related to test." + DEBUG: + value: ${{ jobs.preflight.outputs.DEBUG }} + description: "Debug value to zombienet v1 tests." + ZOMBIE_PROVIDER: + value: ${{ jobs.preflight.outputs.ZOMBIE_PROVIDER }} + description: "Provider to use in zombienet-sdk tests." + RUST_LOG: + value: ${{ jobs.preflight.outputs.RUST_LOG }} + description: "Log value to use in zombinet-sdk tests." + RUN_IN_CI: + value: ${{ jobs.preflight.outputs.RUN_IN_CI }} + description: "Internal flag to make zombienet aware of the env." + + KUBERNETES_CPU_REQUEST: + value: ${{ jobs.preflight.outputs.KUBERNETES_CPU_REQUEST }} + description: "Base cpu (request) for pod runner." + + KUBERNETES_MEMORY_REQUEST: + value: ${{ jobs.preflight.outputs.KUBERNETES_MEMORY_REQUEST }} + description: "Base memory (request) for pod runner." + +jobs: + global_preflight: + uses: ./.github/workflows/reusable-preflight.yml + + # + # + # + preflight: + runs-on: ubuntu-latest + outputs: + changes_substrate: ${{ steps.set_changes.outputs.substrate_any_changed || steps.set_changes.outputs.currentWorkflow_any_changed }} + + ZOMBIENET_IMAGE: ${{ steps.set_vars.outputs.ZOMBIENET_IMAGE }} + ZOMBIENET_RUNNER: ${{ steps.set_vars.outputs.ZOMBIENET_RUNNER }} + + DOCKER_IMAGES_VERSION: ${{ steps.set_images_version.outputs.ZOMBIENET_RUNNER }} + + # common vars + PUSHGATEWAY_URL: ${{ steps.set_vars.outputs.PUSHGATEWAY_URL }} + DEBUG: ${{ steps.set_vars.outputs.DEBUG }} + ZOMBIE_PROVIDER: ${{ steps.set_vars.outputs.ZOMBIE_PROVIDER }} + RUST_LOG: ${{ steps.set_vars.outputs.RUST_LOG }} + RUN_IN_CI: ${{ steps.set_vars.outputs.RUN_IN_CI }} + KUBERNETES_CPU_REQUEST: ${{ steps.set_vars.outputs.KUBERNETES_CPU_REQUEST }} + KUBERNETES_MEMORY_REQUEST: ${{ steps.set_vars.outputs.KUBERNETES_MEMORY_REQUEST }} + + steps: + + - uses: actions/checkout@v4 + + # + # Set changes + # + - name: Current file + id: current_file + shell: bash + run: | + echo "currentWorkflowFile=$(echo ${{ github.workflow_ref }} | sed -nE "s/.*(\.github\/workflows\/[a-zA-Z0-9_-]*\.y[a]?ml)@refs.*/\1/p")" >> $GITHUB_OUTPUT + echo "currentActionDir=$(echo ${{ github.action_path }} | sed -nE "s/.*(\.github\/actions\/[a-zA-Z0-9_-]*)/\1/p")" >> $GITHUB_OUTPUT + + - name: Set changes + id: set_changes + uses: tj-actions/changed-files@v45 + with: + files_yaml: | + substrate: + - 'substrate/**/*' + currentWorkflow: + - '${{ steps.current_file.outputs.currentWorkflowFile }}' + - '.github/workflows/zombienet-reusable-preflight.yml' + - '.github/zombienet-env' + + + # + # Set environment vars (including runner/image) + # + - name: Set vars + id: set_vars + shell: bash + run: cat .github/env >> $GITHUB_OUTPUT + + + # + # + # + - name: Set docker images version + id: set_images_version + shell: bash + run: | + export BRANCH_NAME=${{ github.head_ref || github.ref_name }} + export DOCKER_IMAGES_VERSION=${BRANCH_NAME/\//-} + if [[ ${{ github.event_name }} == "merge_group" ]]; then export DOCKER_IMAGES_VERSION="${GITHUB_SHA::8}"; fi + echo "DOCKER_IMAGES_VERSION=${DOCKER_IMAGES_VERSION}" >> $GITHUB_OUTPUT + + - name: log + shell: bash + run: | + echo "workflow file: ${{ steps.current_file.outputs.currentWorkflowFile }}" + echo "Modified: ${{ steps.set_changes.outputs.modified_keys }}" \ No newline at end of file diff --git a/.github/workflows/zombienet_substrate.yml b/.github/workflows/zombienet_substrate.yml new file mode 100644 index 000000000000..823679d67d5c --- /dev/null +++ b/.github/workflows/zombienet_substrate.yml @@ -0,0 +1,45 @@ +name: Zombienet Substrate + +on: + workflow_run: + workflows: [Build and push images] + types: [completed] + merge_group: + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + preflight: + uses: ./.github/workflows/zombienet-reusable-preflight.yml + + zombienet-substrate-0000-block-building: + needs: [preflight] + # only run if we have changes in ./substrate directory and the build workflow already finish with success status. + if: ${{ needs.preflight.outputs.changes_substrate && github.event.workflow_run.conclusion == 'success' }} + runs-on: ${{ needs.preflight.outputs.ZOMBIENET_RUNNER }} + timeout-minutes: 60 + container: + image: ${{ needs.preflight.outputs.ZOMBIENET_IMAGE }} + env: + FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 + LOCAL_DIR: "./substrate/zombienet" + steps: + - name: Checkout + uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4.1.8 + with: + name: build-linux-substrate-${{ needs.preflight.outputs.SOURCE_REF_NAME }} + + - name: script + run: | + DEBUG=${{ needs.preflight.outputs.DEBUG }} zombie -p native ${LOCAL_DIR}/0000-block-building/block-building.zndsl + + - name: upload logs + uses: actions/upload-artifact@v4 + with: + name: zombienet-logs-scale-net + path: | + /tmp/zombie*/logs/* diff --git a/.github/zombienet-env b/.github/zombienet-env new file mode 100644 index 000000000000..e6da1a49c4bb --- /dev/null +++ b/.github/zombienet-env @@ -0,0 +1,9 @@ + ZOMBIENET_IMAGE="docker.io/paritytech/zombienet:v1.3.116" + ZOMBIE_RUNNER="zombienet-arc-runner" + PUSHGATEWAY_URL="http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" + DEBUG="zombie,zombie::network-node,zombie::kube::client::logs" + ZOMBIE_PROVIDER="k8s" + RUST_LOG="info,zombienet_orchestrator=debug" + RUN_IN_CI="1" + KUBERNETES_CPU_REQUEST="512m" + KUBERNETES_MEMORY_REQUEST="1Gi" From e056586b96f2bf8d2f0abe44bd91184d2dfd8bf3 Mon Sep 17 00:00:00 2001 From: chloefeal <188809157+chloefeal@users.noreply.github.com> Date: Fri, 17 Jan 2025 03:33:59 +0800 Subject: [PATCH 277/340] chore: fix typos (#6999) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✄ ----------------------------------------------------------------------------- Thank you for your Pull Request! 🙏 Please make sure it follows the contribution guidelines outlined in [this document](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and fill out the sections below. Once you're ready to submit your PR for review, please delete this section and leave only the text under the "Description" heading. # Description Hello, I fix some typos in logs and comments. Thank you very much. ## Integration *In depth notes about how this PR should be integrated by downstream projects. This part is mandatory, and should be reviewed by reviewers, if the PR does NOT have the `R0-Silent` label. In case of a `R0-Silent`, it can be ignored.* ## Review Notes *In depth notes about the **implementation** details of your PR. This should be the main guide for reviewers to understand your approach and effectively review it. If too long, use [`

`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)*. *Imagine that someone who is depending on the old code wants to integrate your new code and the only information that they get is this section. It helps to include example usage and default value here, with a `diff` code-block to show possibly integration.* *Include your leftover TODOs, if any, here.* # Checklist * [ ] My PR includes a detailed description as outlined in the "Description" and its two subsections above. * [ ] My PR follows the [labeling requirements]( https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md#Process ) of this project (at minimum one label for `T` required) * External contributors: ask maintainers to put the right label on your PR. * [ ] I have made corresponding changes to the documentation (if applicable) * [ ] I have added tests that prove my fix is effective or that my feature works (if applicable) You can remove the "Checklist" section once all have been checked. Thank you for your contribution! ✄ ----------------------------------------------------------------------------- Signed-off-by: chloefeal <188809157+chloefeal@users.noreply.github.com> --- bridges/relays/lib-substrate-relay/src/error.rs | 2 +- bridges/relays/messages/src/message_lane_loop.rs | 2 +- bridges/snowbridge/primitives/core/src/location.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bridges/relays/lib-substrate-relay/src/error.rs b/bridges/relays/lib-substrate-relay/src/error.rs index 2ebd9130f391..3a62f30838c7 100644 --- a/bridges/relays/lib-substrate-relay/src/error.rs +++ b/bridges/relays/lib-substrate-relay/src/error.rs @@ -47,7 +47,7 @@ pub enum Error { #[error("Failed to guess initial {0} GRANDPA authorities set id: checked all possible ids in range [0; {1}]")] GuessInitialAuthorities(&'static str, HeaderNumber), /// Failed to retrieve GRANDPA authorities at the given header from the source chain. - #[error("Failed to retrive {0} GRANDPA authorities set at header {1}: {2:?}")] + #[error("Failed to retrieve {0} GRANDPA authorities set at header {1}: {2:?}")] RetrieveAuthorities(&'static str, Hash, client::Error), /// Failed to decode GRANDPA authorities at the given header of the source chain. #[error("Failed to decode {0} GRANDPA authorities set at header {1}: {2:?}")] diff --git a/bridges/relays/messages/src/message_lane_loop.rs b/bridges/relays/messages/src/message_lane_loop.rs index 36de637f04c4..cdc94b9fae49 100644 --- a/bridges/relays/messages/src/message_lane_loop.rs +++ b/bridges/relays/messages/src/message_lane_loop.rs @@ -1041,7 +1041,7 @@ pub(crate) mod tests { #[test] fn message_lane_loop_is_able_to_recover_from_unsuccessful_transaction() { // with this configuration, both source and target clients will mine their transactions, but - // their corresponding nonce won't be udpated => reconnect will happen + // their corresponding nonce won't be updated => reconnect will happen let (exit_sender, exit_receiver) = unbounded(); let result = run_loop_test( Arc::new(Mutex::new(TestClientData { diff --git a/bridges/snowbridge/primitives/core/src/location.rs b/bridges/snowbridge/primitives/core/src/location.rs index f49a245c4126..eb5ac66d46db 100644 --- a/bridges/snowbridge/primitives/core/src/location.rs +++ b/bridges/snowbridge/primitives/core/src/location.rs @@ -206,7 +206,7 @@ mod tests { for token in token_locations { assert!( TokenIdOf::convert_location(&token).is_some(), - "Valid token = {token:?} yeilds no TokenId." + "Valid token = {token:?} yields no TokenId." ); } @@ -220,7 +220,7 @@ mod tests { for token in non_token_locations { assert!( TokenIdOf::convert_location(&token).is_none(), - "Invalid token = {token:?} yeilds a TokenId." + "Invalid token = {token:?} yields a TokenId." ); } } From f5673cf260ba08fbc04667b1fcd0e635d87e6451 Mon Sep 17 00:00:00 2001 From: Ankan <10196091+Ank4n@users.noreply.github.com> Date: Thu, 16 Jan 2025 20:32:53 +0100 Subject: [PATCH 278/340] [Staking] Currency <> Fungible migration (#5501) Migrate staking currency from `traits::LockableCurrency` to `traits::fungible::holds`. Resolves part of https://github.com/paritytech/polkadot-sdk/issues/226. ## Changes ### Nomination Pool TransferStake is now incompatible with fungible migration as old pools were not meant to have additional ED. Since they are anyways deprecated, removed its usage from all test runtimes. ### Staking - Config: `Currency` becomes of type `Fungible` while `OldCurrency` is the `LockableCurrency` used before. - Lazy migration of accounts. Any ledger update will create a new hold with no extra reads/writes. A permissionless extrinsic `migrate_currency()` releases the old `lock` along with some housekeeping. - Staking now requires ED to be left free. It also adds no consumer to staking accounts. - If hold cannot be applied to all stake, the un-holdable part is force withdrawn from the ledger. ### Delegated Staking The pallet does not add provider for agents anymore. ## Migration stats ### Polkadot Total accounts that can be migrated: 59564 Accounts failing to migrate: 0 Accounts with stake force withdrawn greater than ED: 59 Total force withdrawal: 29591.26 DOT ### Kusama Total accounts that can be migrated: 26311 Accounts failing to migrate: 0 Accounts with stake force withdrawn greater than ED: 48 Total force withdrawal: 1036.05 KSM [Full logs here](https://hackmd.io/@ak0n/BklDuFra0). ## Note about locks (freeze) vs holds With locks or freezes, staking could use total balance of an account. But with holds, the account needs to be left with at least Existential Deposit in free balance. This would also affect nomination pools which till now has been able to stake all funds contributed to it. An alternate version of this PR is https://github.com/paritytech/polkadot-sdk/pull/5658 where staking pallet does not add any provider, but means pools and delegated-staking pallet has to provide for these accounts and makes the end to end logic (of provider and consumer ref) lot less intuitive and prone to bug. This PR now introduces requirement for stakers to maintain ED in their free balance. This helps with removing the bug prone incrementing and decrementing of consumers and providers. ## TODO - [x] Test: Vesting + governance locked funds can be staked. - [ ] can `Call::restore_ledger` be removed? @gpestana - [x] Ensure unclaimed withdrawals is not affected by no provider for pool accounts. - [x] Investigate kusama accounts with balance between 0 and ED. - [x] Permissionless call to release lock. - [x] Migration of consumer (dec) and provider (inc) for direct stakers. - [x] force unstake if hold cannot be applied to all stake. - [x] Fix try state checks (it thinks nothing is staked for unmigrated ledgers). - [x] Bench `migrate_currency`. - [x] Virtual Staker migration test. - [x] Ensure total issuance is upto date when minting rewards. ## Followup - https://github.com/paritytech/polkadot-sdk/issues/5742 --------- Co-authored-by: command-bot <> --- Cargo.lock | 24 +- Cargo.toml | 1 - polkadot/runtime/test-runtime/src/lib.rs | 2 + polkadot/runtime/westend/src/lib.rs | 2 + polkadot/runtime/westend/src/tests.rs | 99 +- .../westend/src/weights/pallet_staking.rs | 386 ++++---- prdoc/pr_5501.prdoc | 47 + substrate/bin/node/runtime/src/lib.rs | 33 +- substrate/bin/node/testing/src/genesis.rs | 4 +- substrate/frame/babe/src/mock.rs | 1 + substrate/frame/beefy/src/mock.rs | 1 + substrate/frame/delegated-staking/src/lib.rs | 2 +- substrate/frame/delegated-staking/src/mock.rs | 1 + .../frame/delegated-staking/src/tests.rs | 11 +- .../frame/delegated-staking/src/types.rs | 6 - .../test-staking-e2e/Cargo.toml | 2 + .../test-staking-e2e/src/lib.rs | 32 +- .../test-staking-e2e/src/mock.rs | 39 +- substrate/frame/fast-unstake/src/mock.rs | 6 +- substrate/frame/fast-unstake/src/tests.rs | 16 +- substrate/frame/grandpa/src/mock.rs | 1 + .../benchmarking/src/inner.rs | 75 +- .../nomination-pools/benchmarking/src/mock.rs | 1 + .../frame/nomination-pools/src/adapter.rs | 6 +- substrate/frame/nomination-pools/src/mock.rs | 202 +++- substrate/frame/nomination-pools/src/tests.rs | 326 ++----- .../test-delegate-stake/src/lib.rs | 22 +- .../test-delegate-stake/src/mock.rs | 4 + .../test-transfer-stake/Cargo.toml | 39 - .../test-transfer-stake/src/lib.rs | 912 ------------------ .../test-transfer-stake/src/mock.rs | 231 ----- .../frame/offences/benchmarking/src/inner.rs | 10 +- .../frame/offences/benchmarking/src/mock.rs | 1 + substrate/frame/root-offences/src/mock.rs | 9 +- .../frame/session/benchmarking/src/mock.rs | 1 + substrate/frame/staking/Cargo.toml | 1 + substrate/frame/staking/src/asset.rs | 88 +- substrate/frame/staking/src/benchmarking.rs | 23 +- substrate/frame/staking/src/ledger.rs | 10 +- substrate/frame/staking/src/lib.rs | 12 +- substrate/frame/staking/src/mock.rs | 47 +- substrate/frame/staking/src/pallet/impls.rs | 121 ++- substrate/frame/staking/src/pallet/mod.rs | 80 +- substrate/frame/staking/src/testing_utils.rs | 18 + substrate/frame/staking/src/tests.rs | 854 +++++++++++----- substrate/frame/staking/src/weights.rs | 755 ++++++++------- substrate/primitives/staking/src/lib.rs | 2 +- 47 files changed, 2100 insertions(+), 2466 deletions(-) create mode 100644 prdoc/pr_5501.prdoc delete mode 100644 substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml delete mode 100644 substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs delete mode 100644 substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs diff --git a/Cargo.lock b/Cargo.lock index 6eba7e651096..42ed88fb0d06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13369,6 +13369,7 @@ dependencies = [ "log", "pallet-bags-list 27.0.0", "pallet-balances 28.0.0", + "pallet-delegated-staking 1.0.0", "pallet-election-provider-multi-phase 27.0.0", "pallet-nomination-pools 25.0.0", "pallet-session 28.0.0", @@ -14459,29 +14460,6 @@ dependencies = [ "sp-tracing 16.0.0", ] -[[package]] -name = "pallet-nomination-pools-test-transfer-stake" -version = "1.0.0" -dependencies = [ - "frame-election-provider-support 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", - "log", - "pallet-bags-list 27.0.0", - "pallet-balances 28.0.0", - "pallet-nomination-pools 25.0.0", - "pallet-staking 28.0.0", - "pallet-staking-reward-curve", - "pallet-timestamp 27.0.0", - "parity-scale-codec", - "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", - "sp-staking 26.0.0", - "sp-tracing 16.0.0", -] - [[package]] name = "pallet-offences" version = "27.0.0" diff --git a/Cargo.toml b/Cargo.toml index 509775fe99e4..e17f08148b16 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -389,7 +389,6 @@ members = [ "substrate/frame/nomination-pools/fuzzer", "substrate/frame/nomination-pools/runtime-api", "substrate/frame/nomination-pools/test-delegate-stake", - "substrate/frame/nomination-pools/test-transfer-stake", "substrate/frame/offences", "substrate/frame/offences/benchmarking", "substrate/frame/paged-list", diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index 4f9ba8d8508c..cdf6fa92da2f 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -366,11 +366,13 @@ impl onchain::Config for OnChainSeqPhragmen { const MAX_QUOTA_NOMINATIONS: u32 = 16; impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = Timestamp; type CurrencyToVote = polkadot_runtime_common::CurrencyToVote; type RewardRemainder = (); + type RuntimeHoldReason = RuntimeHoldReason; type RuntimeEvent = RuntimeEvent; type Slash = (); type Reward = (); diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 58d2bdcb7c7d..8a5771fe7cc0 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -728,8 +728,10 @@ parameter_types! { } impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = Balance; + type RuntimeHoldReason = RuntimeHoldReason; type UnixTime = Timestamp; type CurrencyToVote = CurrencyToVote; type RewardRemainder = (); diff --git a/polkadot/runtime/westend/src/tests.rs b/polkadot/runtime/westend/src/tests.rs index fcdaf7ff2de6..65b81cc00f06 100644 --- a/polkadot/runtime/westend/src/tests.rs +++ b/polkadot/runtime/westend/src/tests.rs @@ -155,25 +155,27 @@ mod remote_tests { let transport: Transport = var("WS").unwrap_or("ws://127.0.0.1:9900".to_string()).into(); let maybe_state_snapshot: Option = var("SNAP").map(|s| s.into()).ok(); + let online_config = OnlineConfig { + transport, + state_snapshot: maybe_state_snapshot.clone(), + child_trie: false, + pallets: vec![ + "Staking".into(), + "System".into(), + "Balances".into(), + "NominationPools".into(), + "DelegatedStaking".into(), + ], + ..Default::default() + }; let mut ext = Builder::::default() .mode(if let Some(state_snapshot) = maybe_state_snapshot { Mode::OfflineOrElseOnline( OfflineConfig { state_snapshot: state_snapshot.clone() }, - OnlineConfig { - transport, - state_snapshot: Some(state_snapshot), - pallets: vec![ - "staking".into(), - "system".into(), - "balances".into(), - "nomination-pools".into(), - "delegated-staking".into(), - ], - ..Default::default() - }, + online_config, ) } else { - Mode::Online(OnlineConfig { transport, ..Default::default() }) + Mode::Online(online_config) }) .build() .await @@ -241,6 +243,77 @@ mod remote_tests { ); }); } + + #[tokio::test] + async fn staking_curr_fun_migrate() { + // Intended to be run only manually. + if var("RUN_MIGRATION_TESTS").is_err() { + return; + } + sp_tracing::try_init_simple(); + + let transport: Transport = var("WS").unwrap_or("ws://127.0.0.1:9944".to_string()).into(); + let maybe_state_snapshot: Option = var("SNAP").map(|s| s.into()).ok(); + let online_config = OnlineConfig { + transport, + state_snapshot: maybe_state_snapshot.clone(), + child_trie: false, + pallets: vec!["Staking".into(), "System".into(), "Balances".into()], + ..Default::default() + }; + let mut ext = Builder::::default() + .mode(if let Some(state_snapshot) = maybe_state_snapshot { + Mode::OfflineOrElseOnline( + OfflineConfig { state_snapshot: state_snapshot.clone() }, + online_config, + ) + } else { + Mode::Online(online_config) + }) + .build() + .await + .unwrap(); + ext.execute_with(|| { + // create an account with some balance + let alice = AccountId::from([1u8; 32]); + use frame_support::traits::Currency; + let _ = Balances::deposit_creating(&alice, 100_000 * UNITS); + + let mut success = 0; + let mut err = 0; + let mut force_withdraw_acc = 0; + // iterate over all pools + pallet_staking::Ledger::::iter().for_each(|(ctrl, ledger)| { + match pallet_staking::Pallet::::migrate_currency( + RuntimeOrigin::signed(alice.clone()).into(), + ledger.stash.clone(), + ) { + Ok(_) => { + let updated_ledger = + pallet_staking::Ledger::::get(&ctrl).expect("ledger exists"); + let force_withdraw = ledger.total - updated_ledger.total; + if force_withdraw > 0 { + force_withdraw_acc += force_withdraw; + log::info!(target: "remote_test", "Force withdraw from stash {:?}: value {:?}", ledger.stash, force_withdraw); + } + success += 1; + }, + Err(e) => { + log::error!(target: "remote_test", "Error migrating {:?}: {:?}", ledger.stash, e); + err += 1; + }, + } + }); + + log::info!( + target: "remote_test", + "Migration stats: success: {}, err: {}, total force withdrawn stake: {}", + success, + err, + force_withdraw_acc + ); + }); + } } #[test] diff --git a/polkadot/runtime/westend/src/weights/pallet_staking.rs b/polkadot/runtime/westend/src/weights/pallet_staking.rs index 393fa0b37176..f1e7f5ba1576 100644 --- a/polkadot/runtime/westend/src/weights/pallet_staking.rs +++ b/polkadot/runtime/westend/src/weights/pallet_staking.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_staking` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-03-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-09-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-h2rr8wx7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-obbyq9g6-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -52,19 +52,19 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn bond() -> Weight { // Proof Size summary in bytes: - // Measured: `1009` - // Estimated: `4764` - // Minimum execution time: 40_585_000 picoseconds. - Weight::from_parts(41_800_000, 0) - .saturating_add(Weight::from_parts(0, 4764)) + // Measured: `1035` + // Estimated: `4556` + // Minimum execution time: 70_147_000 picoseconds. + Weight::from_parts(71_795_000, 0) + .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -72,20 +72,20 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn bond_extra() -> Weight { // Proof Size summary in bytes: - // Measured: `1921` + // Measured: `1947` // Estimated: `8877` - // Minimum execution time: 81_809_000 picoseconds. - Weight::from_parts(84_387_000, 0) + // Minimum execution time: 125_203_000 picoseconds. + Weight::from_parts(128_088_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(7)) @@ -100,23 +100,23 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `2128` + // Measured: `2051` // Estimated: `8877` - // Minimum execution time: 89_419_000 picoseconds. - Weight::from_parts(91_237_000, 0) + // Minimum execution time: 101_991_000 picoseconds. + Weight::from_parts(104_567_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(12)) - .saturating_add(T::DbWeight::get().writes(7)) + .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) @@ -124,23 +124,25 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0) /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Storage: `DelegatedStaking::Agents` (r:1 w:0) + /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1223` - // Estimated: `4764` - // Minimum execution time: 45_152_000 picoseconds. - Weight::from_parts(46_460_819, 0) - .saturating_add(Weight::from_parts(0, 4764)) - // Standard Error: 972 - .saturating_add(Weight::from_parts(55_473, 0).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(6)) + // Measured: `1253` + // Estimated: `4556` + // Minimum execution time: 76_450_000 picoseconds. + Weight::from_parts(78_836_594, 0) + .saturating_add(Weight::from_parts(0, 4556)) + // Standard Error: 1_529 + .saturating_add(Weight::from_parts(66_662, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Staking::Ledger` (r:1 w:1) @@ -151,10 +153,10 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -174,15 +176,15 @@ impl pallet_staking::WeightInfo for WeightInfo { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2127 + s * (4 ±0)` + // Measured: `2153 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 82_762_000 picoseconds. - Weight::from_parts(91_035_077, 0) + // Minimum execution time: 121_962_000 picoseconds. + Weight::from_parts(131_000_151, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 3_771 - .saturating_add(Weight::from_parts(1_217_871, 0).saturating_mul(s.into())) + // Standard Error: 3_846 + .saturating_add(Weight::from_parts(1_277_843, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(11)) + .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -210,10 +212,10 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn validate() -> Weight { // Proof Size summary in bytes: - // Measured: `1301` + // Measured: `1334` // Estimated: `4556` - // Minimum execution time: 50_555_000 picoseconds. - Weight::from_parts(52_052_000, 0) + // Minimum execution time: 66_450_000 picoseconds. + Weight::from_parts(68_302_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(5)) @@ -227,13 +229,13 @@ impl pallet_staking::WeightInfo for WeightInfo { /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1778 + k * (572 ±0)` + // Measured: `1811 + k * (572 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 35_037_000 picoseconds. - Weight::from_parts(35_081_878, 0) + // Minimum execution time: 43_875_000 picoseconds. + Weight::from_parts(47_332_240, 0) .saturating_add(Weight::from_parts(0, 4556)) - // Standard Error: 5_473 - .saturating_add(Weight::from_parts(6_667_924, 0).saturating_mul(k.into())) + // Standard Error: 6_530 + .saturating_add(Weight::from_parts(7_398_001, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -264,13 +266,13 @@ impl pallet_staking::WeightInfo for WeightInfo { /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1797 + n * (102 ±0)` + // Measured: `1830 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 62_098_000 picoseconds. - Weight::from_parts(60_154_061, 0) + // Minimum execution time: 80_640_000 picoseconds. + Weight::from_parts(78_801_092, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 19_257 - .saturating_add(Weight::from_parts(3_839_855, 0).saturating_mul(n.into())) + // Standard Error: 22_249 + .saturating_add(Weight::from_parts(4_996_344, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6)) @@ -294,10 +296,10 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `1747` + // Measured: `1780` // Estimated: `6248` - // Minimum execution time: 54_993_000 picoseconds. - Weight::from_parts(56_698_000, 0) + // Minimum execution time: 71_494_000 picoseconds. + Weight::from_parts(73_487_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(9)) .saturating_add(T::DbWeight::get().writes(6)) @@ -310,10 +312,10 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn set_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `865` + // Measured: `898` // Estimated: `4556` - // Minimum execution time: 18_100_000 picoseconds. - Weight::from_parts(18_547_000, 0) + // Minimum execution time: 24_310_000 picoseconds. + Weight::from_parts(24_676_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -326,10 +328,10 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `932` + // Measured: `965` // Estimated: `4556` - // Minimum execution time: 23_428_000 picoseconds. - Weight::from_parts(24_080_000, 0) + // Minimum execution time: 31_348_000 picoseconds. + Weight::from_parts(32_384_000, 0) .saturating_add(Weight::from_parts(0, 4556)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -340,10 +342,10 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) fn set_controller() -> Weight { // Proof Size summary in bytes: - // Measured: `865` + // Measured: `898` // Estimated: `8122` - // Minimum execution time: 21_159_000 picoseconds. - Weight::from_parts(21_706_000, 0) + // Minimum execution time: 27_537_000 picoseconds. + Weight::from_parts(28_714_000, 0) .saturating_add(Weight::from_parts(0, 8122)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) @@ -354,8 +356,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_910_000 picoseconds. - Weight::from_parts(2_003_000, 0) + // Minimum execution time: 2_362_000 picoseconds. + Weight::from_parts(2_518_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -365,8 +367,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_076_000 picoseconds. - Weight::from_parts(7_349_000, 0) + // Minimum execution time: 7_752_000 picoseconds. + Weight::from_parts(8_105_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -376,8 +378,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_067_000 picoseconds. - Weight::from_parts(7_389_000, 0) + // Minimum execution time: 7_868_000 picoseconds. + Weight::from_parts(8_175_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -387,8 +389,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_148_000 picoseconds. - Weight::from_parts(7_446_000, 0) + // Minimum execution time: 7_945_000 picoseconds. + Weight::from_parts(8_203_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -399,11 +401,11 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_025_000 picoseconds. - Weight::from_parts(2_229_953, 0) + // Minimum execution time: 2_458_000 picoseconds. + Weight::from_parts(2_815_664, 0) .saturating_add(Weight::from_parts(0, 0)) // Standard Error: 67 - .saturating_add(Weight::from_parts(11_785, 0).saturating_mul(v.into())) + .saturating_add(Weight::from_parts(12_287, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Staking::Ledger` (r:1502 w:1502) @@ -415,13 +417,13 @@ impl pallet_staking::WeightInfo for WeightInfo { /// The range of component `i` is `[0, 751]`. fn deprecate_controller_batch(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `680 + i * (227 ±0)` + // Measured: `713 + i * (227 ±0)` // Estimated: `990 + i * (7132 ±0)` - // Minimum execution time: 4_321_000 picoseconds. - Weight::from_parts(4_407_000, 0) + // Minimum execution time: 4_976_000 picoseconds. + Weight::from_parts(5_102_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 37_239 - .saturating_add(Weight::from_parts(21_300_598, 0).saturating_mul(i.into())) + // Standard Error: 36_458 + .saturating_add(Weight::from_parts(25_359_275, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 7132).saturating_mul(i.into())) @@ -432,10 +434,10 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) @@ -457,15 +459,15 @@ impl pallet_staking::WeightInfo for WeightInfo { /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2127 + s * (4 ±0)` + // Measured: `2153 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 78_908_000 picoseconds. - Weight::from_parts(84_886_373, 0) + // Minimum execution time: 116_776_000 picoseconds. + Weight::from_parts(125_460_389, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 3_376 - .saturating_add(Weight::from_parts(1_217_850, 0).saturating_mul(s.into())) + // Standard Error: 3_095 + .saturating_add(Weight::from_parts(1_300_502, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(12)) + .saturating_add(T::DbWeight::get().writes(13)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -474,13 +476,13 @@ impl pallet_staking::WeightInfo for WeightInfo { /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `66639` - // Estimated: `70104` - // Minimum execution time: 136_389_000 picoseconds. - Weight::from_parts(1_207_241_524, 0) - .saturating_add(Weight::from_parts(0, 70104)) - // Standard Error: 77_138 - .saturating_add(Weight::from_parts(6_443_948, 0).saturating_mul(s.into())) + // Measured: `66672` + // Estimated: `70137` + // Minimum execution time: 135_135_000 picoseconds. + Weight::from_parts(937_565_332, 0) + .saturating_add(Weight::from_parts(0, 70137)) + // Standard Error: 57_675 + .saturating_add(Weight::from_parts(4_828_080, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -498,12 +500,10 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorReward` (r:1 w:0) /// Proof: `Staking::ErasValidatorReward` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:65 w:65) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:65 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:65 w:65) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:65 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:65 w:65) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasStakersPaged` (r:1 w:0) /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::ErasRewardPoints` (r:1 w:0) @@ -512,30 +512,32 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:65 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:65 w:65) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 64]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `8249 + n * (396 ±0)` - // Estimated: `10779 + n * (3774 ±0)` - // Minimum execution time: 130_222_000 picoseconds. - Weight::from_parts(167_236_150, 0) - .saturating_add(Weight::from_parts(0, 10779)) - // Standard Error: 34_051 - .saturating_add(Weight::from_parts(39_899_917, 0).saturating_mul(n.into())) + // Measured: `8275 + n * (389 ±0)` + // Estimated: `10805 + n * (3566 ±0)` + // Minimum execution time: 180_144_000 picoseconds. + Weight::from_parts(237_134_733, 0) + .saturating_add(Weight::from_parts(0, 10805)) + // Standard Error: 52_498 + .saturating_add(Weight::from_parts(73_633_326, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) @@ -543,26 +545,26 @@ impl pallet_staking::WeightInfo for WeightInfo { /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1922 + l * (5 ±0)` + // Measured: `1845 + l * (5 ±0)` // Estimated: `8877` - // Minimum execution time: 79_136_000 picoseconds. - Weight::from_parts(82_129_497, 0) + // Minimum execution time: 89_307_000 picoseconds. + Weight::from_parts(92_902_634, 0) .saturating_add(Weight::from_parts(0, 8877)) - // Standard Error: 3_867 - .saturating_add(Weight::from_parts(75_156, 0).saturating_mul(l.into())) + // Standard Error: 4_446 + .saturating_add(Weight::from_parts(73_546, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(7)) + .saturating_add(T::DbWeight::get().writes(6)) } + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -582,15 +584,15 @@ impl pallet_staking::WeightInfo for WeightInfo { /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2127 + s * (4 ±0)` + // Measured: `2153 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 89_375_000 picoseconds. - Weight::from_parts(91_224_907, 0) + // Minimum execution time: 130_544_000 picoseconds. + Weight::from_parts(133_260_598, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 3_424 - .saturating_add(Weight::from_parts(1_219_542, 0).saturating_mul(s.into())) + // Standard Error: 3_545 + .saturating_add(Weight::from_parts(1_313_348, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(12)) - .saturating_add(T::DbWeight::get().writes(11)) + .saturating_add(T::DbWeight::get().writes(12)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -633,14 +635,14 @@ impl pallet_staking::WeightInfo for WeightInfo { fn new_era(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + n * (716 ±0) + v * (3594 ±0)` - // Estimated: `456136 + n * (3566 ±4) + v * (3566 ±0)` - // Minimum execution time: 520_905_000 picoseconds. - Weight::from_parts(523_771_000, 0) + // Estimated: `456136 + n * (3566 ±4) + v * (3566 ±40)` + // Minimum execution time: 654_756_000 picoseconds. + Weight::from_parts(658_861_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 2_142_714 - .saturating_add(Weight::from_parts(68_631_588, 0).saturating_mul(v.into())) - // Standard Error: 213_509 - .saturating_add(Weight::from_parts(19_343_025, 0).saturating_mul(n.into())) + // Standard Error: 2_078_102 + .saturating_add(Weight::from_parts(67_775_668, 0).saturating_mul(v.into())) + // Standard Error: 207_071 + .saturating_add(Weight::from_parts(22_624_711, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(184)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -669,15 +671,15 @@ impl pallet_staking::WeightInfo for WeightInfo { /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `3108 + n * (907 ±0) + v * (391 ±0)` + // Measured: `3141 + n * (907 ±0) + v * (391 ±0)` // Estimated: `456136 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 36_848_619_000 picoseconds. - Weight::from_parts(37_362_442_000, 0) + // Minimum execution time: 42_790_195_000 picoseconds. + Weight::from_parts(42_954_437_000, 0) .saturating_add(Weight::from_parts(0, 456136)) - // Standard Error: 415_031 - .saturating_add(Weight::from_parts(5_204_987, 0).saturating_mul(v.into())) - // Standard Error: 415_031 - .saturating_add(Weight::from_parts(4_132_636, 0).saturating_mul(n.into())) + // Standard Error: 478_107 + .saturating_add(Weight::from_parts(6_744_044, 0).saturating_mul(v.into())) + // Standard Error: 478_107 + .saturating_add(Weight::from_parts(4_837_739, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(179)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -692,13 +694,13 @@ impl pallet_staking::WeightInfo for WeightInfo { /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `946 + v * (50 ±0)` + // Measured: `979 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_512_817_000 picoseconds. - Weight::from_parts(119_401_374, 0) + // Minimum execution time: 2_851_801_000 picoseconds. + Weight::from_parts(4_477_533, 0) .saturating_add(Weight::from_parts(0, 3510)) - // Standard Error: 8_463 - .saturating_add(Weight::from_parts(4_860_364, 0).saturating_mul(v.into())) + // Standard Error: 8_644 + .saturating_add(Weight::from_parts(5_811_682, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -721,8 +723,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_686_000 picoseconds. - Weight::from_parts(3_881_000, 0) + // Minimum execution time: 4_250_000 picoseconds. + Weight::from_parts(4_472_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -744,8 +746,8 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_143_000 picoseconds. - Weight::from_parts(3_424_000, 0) + // Minimum execution time: 3_986_000 picoseconds. + Weight::from_parts(4_144_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(7)) } @@ -773,10 +775,10 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill_other() -> Weight { // Proof Size summary in bytes: - // Measured: `1870` + // Measured: `1903` // Estimated: `6248` - // Minimum execution time: 66_946_000 picoseconds. - Weight::from_parts(69_382_000, 0) + // Minimum execution time: 87_291_000 picoseconds. + Weight::from_parts(89_344_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(6)) @@ -787,10 +789,10 @@ impl pallet_staking::WeightInfo for WeightInfo { /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) fn force_apply_min_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `658` + // Measured: `691` // Estimated: `3510` - // Minimum execution time: 11_278_000 picoseconds. - Weight::from_parts(11_603_000, 0) + // Minimum execution time: 16_113_000 picoseconds. + Weight::from_parts(16_593_000, 0) .saturating_add(Weight::from_parts(0, 3510)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -801,29 +803,53 @@ impl pallet_staking::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_963_000 picoseconds. - Weight::from_parts(2_077_000, 0) + // Minimum execution time: 2_433_000 picoseconds. + Weight::from_parts(2_561_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:0) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + fn restore_ledger() -> Weight { + // Proof Size summary in bytes: + // Measured: `1040` + // Estimated: `4764` + // Minimum execution time: 50_167_000 picoseconds. + Weight::from_parts(51_108_000, 0) + .saturating_add(Weight::from_parts(0, 4764)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) + } + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(103), added: 2578, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - fn restore_ledger() -> Weight { + fn migrate_currency() -> Weight { // Proof Size summary in bytes: - // Measured: `1014` + // Measured: `1209` // Estimated: `4764` - // Minimum execution time: 40_258_000 picoseconds. - Weight::from_parts(41_210_000, 0) + // Minimum execution time: 91_790_000 picoseconds. + Weight::from_parts(92_991_000, 0) .saturating_add(Weight::from_parts(0, 4764)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } } diff --git a/prdoc/pr_5501.prdoc b/prdoc/pr_5501.prdoc new file mode 100644 index 000000000000..f2a5aa9a4667 --- /dev/null +++ b/prdoc/pr_5501.prdoc @@ -0,0 +1,47 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Currency to Fungible migration for pallet-staking + +doc: + - audience: Runtime User + description: | + Lazy migration of staking balance from `Currency::locks` to `Fungible::holds`. New extrinsic + `staking::migrate_currency` removes the old lock along with other housekeeping. Additionally, any ledger mutation + creates hold if it does not exist. + + The pallet-staking configuration item `Currency` is updated to use `fungible::hold::Mutate` type while still + requiring `LockableCurrency` type to be passed as `OldCurrency` for migration purposes. + + +crates: + - name: westend-runtime + bump: major + - name: kitchensink-runtime + bump: minor + - name: pallet-delegated-staking + bump: patch + - name: pallet-nomination-pools + bump: minor + - name: pallet-nomination-pools-runtime-api + bump: patch + - name: sp-staking + bump: patch + - name: pallet-beefy + bump: patch + - name: pallet-fast-unstake + bump: patch + - name: pallet-staking + bump: major + - name: pallet-grandpa + bump: patch + - name: pallet-babe + bump: patch + - name: pallet-nomination-pools-benchmarking + bump: patch + - name: pallet-session-benchmarking + bump: patch + - name: pallet-root-offences + bump: patch + - name: pallet-offences-benchmarking + bump: patch diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index de377a55bc88..117d306e3060 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -53,7 +53,9 @@ use frame_support::{ Balanced, Credit, HoldConsideration, ItemOf, NativeFromLeft, NativeOrWithId, UnionOf, }, tokens::{ - imbalance::ResolveAssetTo, nonfungibles_v2::Inspect, pay::PayAssetFromAccount, + imbalance::{ResolveAssetTo, ResolveTo}, + nonfungibles_v2::Inspect, + pay::PayAssetFromAccount, GetSalary, PayFromAccount, }, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64, @@ -719,13 +721,15 @@ impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { } impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = Timestamp; type CurrencyToVote = sp_staking::currency_to_vote::U128CurrencyToVote; - type RewardRemainder = Treasury; + type RewardRemainder = ResolveTo; type RuntimeEvent = RuntimeEvent; - type Slash = Treasury; // send the slashed funds to the treasury. + type RuntimeHoldReason = RuntimeHoldReason; + type Slash = ResolveTo; // send the slashed funds to the treasury. type Reward = (); // rewards are minted from the void type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; @@ -748,7 +752,7 @@ impl pallet_staking::Config for Runtime { type MaxUnlockingChunks = ConstU32<32>; type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type HistoryDepth = HistoryDepth; - type EventListeners = NominationPools; + type EventListeners = (NominationPools, DelegatedStaking); type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy; @@ -932,6 +936,21 @@ impl pallet_bags_list::Config for Runtime { type WeightInfo = pallet_bags_list::weights::SubstrateWeight; } +parameter_types! { + pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk"); + pub const SlashRewardFraction: Perbill = Perbill::from_percent(1); +} + +impl pallet_delegated_staking::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = DelegatedStakingPalletId; + type Currency = Balances; + type OnSlash = (); + type SlashRewardFraction = SlashRewardFraction; + type RuntimeHoldReason = RuntimeHoldReason; + type CoreStaking = Staking; +} + parameter_types! { pub const PostUnbondPoolsWindow: u32 = 4; pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/nopls"); @@ -960,7 +979,8 @@ impl pallet_nomination_pools::Config for Runtime { type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type StakeAdapter = pallet_nomination_pools::adapter::TransferStake; + type StakeAdapter = + pallet_nomination_pools::adapter::DelegateStake; type PostUnbondingPoolsWindow = PostUnbondPoolsWindow; type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; @@ -2691,6 +2711,9 @@ mod runtime { #[runtime::pallet_index(81)] pub type VerifySignature = pallet_verify_signature::Pallet; + #[runtime::pallet_index(82)] + pub type DelegatedStaking = pallet_delegated_staking::Pallet; + #[runtime::pallet_index(83)] pub type AssetRewards = pallet_asset_rewards::Pallet; diff --git a/substrate/bin/node/testing/src/genesis.rs b/substrate/bin/node/testing/src/genesis.rs index 7f5364744c66..0394f6cd7394 100644 --- a/substrate/bin/node/testing/src/genesis.rs +++ b/substrate/bin/node/testing/src/genesis.rs @@ -38,9 +38,9 @@ pub fn config_endowed(extra_endowed: Vec) -> RuntimeGenesisConfig { (alice(), 111 * DOLLARS), (bob(), 100 * DOLLARS), (charlie(), 100_000_000 * DOLLARS), - (dave(), 111 * DOLLARS), + (dave(), 112 * DOLLARS), (eve(), 101 * DOLLARS), - (ferdie(), 100 * DOLLARS), + (ferdie(), 101 * DOLLARS), ]; endowed.extend(extra_endowed.into_iter().map(|endowed| (endowed, 100 * DOLLARS))); diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index 23857470adc4..8d00509e800b 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -157,6 +157,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; diff --git a/substrate/frame/beefy/src/mock.rs b/substrate/frame/beefy/src/mock.rs index 7ae41c609180..38e0cc4cfc26 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -235,6 +235,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { type RuntimeEvent = RuntimeEvent; + type OldCurrency = Balances; type Currency = Balances; type AdminOrigin = frame_system::EnsureRoot; type SessionInterface = Self; diff --git a/substrate/frame/delegated-staking/src/lib.rs b/substrate/frame/delegated-staking/src/lib.rs index 1d181eb29cab..0dacfe9c5579 100644 --- a/substrate/frame/delegated-staking/src/lib.rs +++ b/substrate/frame/delegated-staking/src/lib.rs @@ -520,7 +520,7 @@ impl Pallet { let stake = T::CoreStaking::stake(who)?; // release funds from core staking. - T::CoreStaking::migrate_to_virtual_staker(who); + T::CoreStaking::migrate_to_virtual_staker(who)?; // transfer just released staked amount plus any free amount. let amount_to_transfer = diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs index 811d5739f4e9..875279864f7a 100644 --- a/substrate/frame/delegated-staking/src/mock.rs +++ b/substrate/frame/delegated-staking/src/mock.rs @@ -102,6 +102,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type UnixTime = pallet_timestamp::Pallet; type AdminOrigin = frame_system::EnsureRoot; diff --git a/substrate/frame/delegated-staking/src/tests.rs b/substrate/frame/delegated-staking/src/tests.rs index b7b82a43771e..c764e2741a2a 100644 --- a/substrate/frame/delegated-staking/src/tests.rs +++ b/substrate/frame/delegated-staking/src/tests.rs @@ -671,12 +671,14 @@ mod staking_integration { )); assert_ok!(Staking::nominate(RuntimeOrigin::signed(agent), vec![GENESIS_VALIDATOR],)); let init_stake = Staking::stake(&agent).unwrap(); + // no extra provider added. + assert_eq!(System::providers(&agent), 1); // scenario: 200 is a pool account, and the stake comes from its 4 delegators (300..304) // in equal parts. lets try to migrate this nominator into delegate based stake. // all balance currently is in 200 - assert_eq!(pallet_staking::asset::stakeable_balance::(&agent), agent_amount); + assert_eq!(pallet_staking::asset::total_balance::(&agent), agent_amount); // to migrate, nominator needs to set an account as a proxy delegator where staked funds // will be moved and delegated back to this old nominator account. This should be funded @@ -685,8 +687,9 @@ mod staking_integration { DelegatedStaking::generate_proxy_delegator(Agent::from(agent)).get(); assert_ok!(DelegatedStaking::migrate_to_agent(RawOrigin::Signed(agent).into(), 201)); - // after migration, funds are moved to proxy delegator, still a provider exists. - assert_eq!(System::providers(&agent), 1); + // after migration, no provider left since free balance is 0 and staking pallet released + // all funds. + assert_eq!(System::providers(&agent), 0); assert_eq!(Balances::free_balance(agent), 0); // proxy delegator has one provider as well with no free balance. assert_eq!(System::providers(&proxy_delegator), 1); @@ -798,8 +801,6 @@ mod staking_integration { RawOrigin::Signed(agent).into(), reward_acc )); - // becoming an agent adds another provider. - assert_eq!(System::providers(&agent), 2); // delegate to this account fund(&delegator, 1000); diff --git a/substrate/frame/delegated-staking/src/types.rs b/substrate/frame/delegated-staking/src/types.rs index a78aa3f55906..14f49466f0e2 100644 --- a/substrate/frame/delegated-staking/src/types.rs +++ b/substrate/frame/delegated-staking/src/types.rs @@ -131,10 +131,6 @@ impl AgentLedger { /// /// Increments provider count if this is a new agent. pub(crate) fn update(self, key: &T::AccountId) { - if !>::contains_key(key) { - // This is a new agent. Provide for this account. - frame_system::Pallet::::inc_providers(key); - } >::insert(key, self) } @@ -142,8 +138,6 @@ impl AgentLedger { pub(crate) fn remove(key: &T::AccountId) { debug_assert!(>::contains_key(key), "Agent should exist in storage"); >::remove(key); - // Remove provider reference. - let _ = frame_system::Pallet::::dec_providers(key).defensive(); } /// Effective total balance of the `Agent`. diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml index 7a48ae868a5a..f11f9c04dbf4 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml @@ -34,6 +34,7 @@ frame-system = { workspace = true, default-features = true } pallet-bags-list = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +pallet-delegated-staking = { workspace = true, default-features = true } pallet-election-provider-multi-phase = { workspace = true, default-features = true } pallet-nomination-pools = { workspace = true, default-features = true } pallet-session = { workspace = true, default-features = true } @@ -47,6 +48,7 @@ try-runtime = [ "frame-system/try-runtime", "pallet-bags-list/try-runtime", "pallet-balances/try-runtime", + "pallet-delegated-staking/try-runtime", "pallet-election-provider-multi-phase/try-runtime", "pallet-nomination-pools/try-runtime", "pallet-session/try-runtime", diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs index 26a6345e145f..b1029e89fe85 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs @@ -327,8 +327,8 @@ fn automatic_unbonding_pools() { assert_eq!(::MaxUnbonding::get(), 1); // init state of pool members. - let init_stakeable_balance_2 = pallet_staking::asset::stakeable_balance::(&2); - let init_stakeable_balance_3 = pallet_staking::asset::stakeable_balance::(&3); + let init_free_balance_2 = Balances::free_balance(2); + let init_free_balance_3 = Balances::free_balance(3); let pool_bonded_account = Pools::generate_bonded_account(1); @@ -378,7 +378,7 @@ fn automatic_unbonding_pools() { System::reset_events(); let staked_before_withdraw_pool = staked_amount_for(pool_bonded_account); - assert_eq!(pallet_staking::asset::stakeable_balance::(&pool_bonded_account), 26); + assert_eq!(delegated_balance_for(pool_bonded_account), 5 + 10 + 10); // now unbonding 3 will work, although the pool's ledger still has the unlocking chunks // filled up. @@ -390,13 +390,13 @@ fn automatic_unbonding_pools() { [ // auto-withdraw happened as expected to release 2's unbonding funds, but the funds // were not transferred to 2 and stay in the pool's transferrable balance instead. - pallet_staking::Event::Withdrawn { stash: 7939698191839293293, amount: 10 }, - pallet_staking::Event::Unbonded { stash: 7939698191839293293, amount: 10 } + pallet_staking::Event::Withdrawn { stash: pool_bonded_account, amount: 10 }, + pallet_staking::Event::Unbonded { stash: pool_bonded_account, amount: 10 } ] ); // balance of the pool remains the same, it hasn't withdraw explicitly from the pool yet. - assert_eq!(pallet_staking::asset::stakeable_balance::(&pool_bonded_account), 26); + assert_eq!(delegated_balance_for(pool_bonded_account), 25); // but the locked amount in the pool's account decreases due to the auto-withdraw: assert_eq!(staked_before_withdraw_pool - 10, staked_amount_for(pool_bonded_account)); @@ -405,12 +405,12 @@ fn automatic_unbonding_pools() { // however, note that the withdrawing from the pool still works for 2, the funds are taken // from the pool's non staked balance. - assert_eq!(pallet_staking::asset::stakeable_balance::(&pool_bonded_account), 26); - assert_eq!(pallet_staking::asset::staked::(&pool_bonded_account), 15); + assert_eq!(delegated_balance_for(pool_bonded_account), 25); + assert_eq!(staked_amount_for(pool_bonded_account), 15); assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(2), 2, 10)); - assert_eq!(pallet_staking::asset::stakeable_balance::(&pool_bonded_account), 16); + assert_eq!(delegated_balance_for(pool_bonded_account), 15); - assert_eq!(pallet_staking::asset::stakeable_balance::(&2), 20); + assert_eq!(Balances::free_balance(2), 20); assert_eq!(TotalValueLocked::::get(), 15); // 3 cannot withdraw yet. @@ -429,15 +429,9 @@ fn automatic_unbonding_pools() { assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(3), 3, 10)); // final conditions are the expected. - assert_eq!(pallet_staking::asset::stakeable_balance::(&pool_bonded_account), 6); // 5 init bonded + ED - assert_eq!( - pallet_staking::asset::stakeable_balance::(&2), - init_stakeable_balance_2 - ); - assert_eq!( - pallet_staking::asset::stakeable_balance::(&3), - init_stakeable_balance_3 - ); + assert_eq!(delegated_balance_for(pool_bonded_account), 5); // 5 init bonded + assert_eq!(Balances::free_balance(2), init_free_balance_2); + assert_eq!(Balances::free_balance(3), init_free_balance_3); assert_eq!(TotalValueLocked::::get(), init_tvl); }); diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index eaab848c1694..bcb25f8287b3 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -21,6 +21,7 @@ use frame_support::{ assert_ok, parameter_types, traits, traits::{Hooks, UnfilteredDispatchable, VariantCountOf}, weights::constants, + PalletId, }; use frame_system::EnsureRoot; use sp_core::{ConstU32, Get}; @@ -36,7 +37,7 @@ use sp_runtime::{ }; use sp_staking::{ offence::{OffenceDetails, OnOffenceHandler}, - EraIndex, SessionIndex, + Agent, DelegationInterface, EraIndex, SessionIndex, StakingInterface, }; use std::collections::BTreeMap; @@ -68,6 +69,7 @@ frame_support::construct_runtime!( System: frame_system, ElectionProviderMultiPhase: pallet_election_provider_multi_phase, Staking: pallet_staking, + DelegatedStaking: pallet_delegated_staking, Pools: pallet_nomination_pools, Balances: pallet_balances, BagsList: pallet_bags_list, @@ -77,7 +79,7 @@ frame_support::construct_runtime!( } ); -pub(crate) type AccountId = u64; +pub(crate) type AccountId = u128; pub(crate) type AccountIndex = u32; pub(crate) type BlockNumber = u32; pub(crate) type Balance = u64; @@ -87,8 +89,10 @@ pub(crate) type Moment = u32; #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { + type AccountId = AccountId; type Block = Block; type AccountData = pallet_balances::AccountData; + type Lookup = sp_runtime::traits::IdentityLookup; } const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); @@ -265,7 +269,8 @@ impl pallet_nomination_pools::Config for Runtime { type RewardCounter = sp_runtime::FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type StakeAdapter = pallet_nomination_pools::adapter::TransferStake; + type StakeAdapter = + pallet_nomination_pools::adapter::DelegateStake; type PostUnbondingPoolsWindow = ConstU32<2>; type PalletId = PoolsPalletId; type MaxMetadataLen = ConstU32<256>; @@ -274,6 +279,21 @@ impl pallet_nomination_pools::Config for Runtime { type AdminOrigin = frame_system::EnsureRoot; } +parameter_types! { + pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk"); + pub const SlashRewardFraction: Perbill = Perbill::from_percent(1); +} + +impl pallet_delegated_staking::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type PalletId = DelegatedStakingPalletId; + type Currency = Balances; + type OnSlash = (); + type SlashRewardFraction = SlashRewardFraction; + type RuntimeHoldReason = RuntimeHoldReason; + type CoreStaking = Staking; +} + parameter_types! { pub static MaxUnlockingChunks: u32 = 32; } @@ -285,6 +305,7 @@ pub(crate) const SLASHING_DISABLING_FACTOR: usize = 3; #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = Timestamp; @@ -302,7 +323,7 @@ impl pallet_staking::Config for Runtime { type NominationsQuota = pallet_staking::FixedNominationsQuota; type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = MaxUnlockingChunks; - type EventListeners = Pools; + type EventListeners = (Pools, DelegatedStaking); type WeightInfo = pallet_staking::weights::SubstrateWeight; type DisablingStrategy = pallet_staking::UpToLimitWithReEnablingDisablingStrategy; @@ -502,7 +523,7 @@ impl Default for BalancesExtBuilder { (100, 100), (200, 100), // stashes - (11, 1000), + (11, 1100), (21, 2000), (31, 3000), (41, 4000), @@ -581,7 +602,7 @@ impl ExtBuilder { // set the keys for the first session. keys: stakers .into_iter() - .map(|(id, ..)| (id, id, SessionKeys { other: (id as u64).into() })) + .map(|(id, ..)| (id, id, SessionKeys { other: (id as AccountId as u64).into() })) .collect(), ..Default::default() } @@ -926,7 +947,11 @@ pub(crate) fn set_minimum_election_score( } pub(crate) fn staked_amount_for(account_id: AccountId) -> Balance { - pallet_staking::asset::staked::(&account_id) + Staking::total_stake(&account_id).expect("account must be staker") +} + +pub(crate) fn delegated_balance_for(account_id: AccountId) -> Balance { + DelegatedStaking::agent_balance(Agent::from(account_id)).unwrap_or_default() } pub(crate) fn staking_events() -> Vec> { diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index f044fc610187..cf4f5f49240e 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -105,6 +105,7 @@ impl frame_election_provider_support::ElectionProvider for MockElection { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type UnixTime = pallet_timestamp::Pallet; type AdminOrigin = frame_system::EnsureRoot; @@ -223,8 +224,9 @@ impl ExtBuilder { .clone() .into_iter() .map(|(stash, _, balance)| (stash, balance * 2)) - .chain(validators_range.clone().map(|x| (x, 7 + 100))) - .chain(nominators_range.clone().map(|x| (x, 7 + 100))) + // give stakers enough balance for stake, ed and fast unstake deposit. + .chain(validators_range.clone().map(|x| (x, 7 + 1 + 100))) + .chain(nominators_range.clone().map(|x| (x, 7 + 1 + 100))) .collect::>(), } .assimilate_storage(&mut storage); diff --git a/substrate/frame/fast-unstake/src/tests.rs b/substrate/frame/fast-unstake/src/tests.rs index 7c11f381ca10..0fddb88e02b7 100644 --- a/substrate/frame/fast-unstake/src/tests.rs +++ b/substrate/frame/fast-unstake/src/tests.rs @@ -19,7 +19,15 @@ use super::*; use crate::{mock::*, types::*, Event}; -use frame_support::{pallet_prelude::*, testing_prelude::*, traits::Currency}; +use frame_support::{ + pallet_prelude::*, + testing_prelude::*, + traits::{ + fungible::Inspect, + tokens::{Fortitude::Polite, Preservation::Expendable}, + Currency, + }, +}; use pallet_staking::{CurrentEra, RewardDestination}; use sp_runtime::traits::BadOrigin; @@ -146,7 +154,7 @@ fn deregister_works() { // Controller then changes mind and deregisters. assert_ok!(FastUnstake::deregister(RuntimeOrigin::signed(1))); - assert_eq!(::Currency::reserved_balance(&1) - pre_reserved, 0); + assert_eq!(::Currency::reserved_balance(&1), pre_reserved); // Ensure stash no longer exists in the queue. assert_eq!(Queue::::get(1), None); @@ -297,7 +305,7 @@ mod on_idle { ); assert_eq!(Queue::::count(), 3); - assert_eq!(::Currency::reserved_balance(&1) - pre_reserved, 0); + assert_eq!(::Currency::reserved_balance(&1), pre_reserved); assert_eq!( fast_unstake_events_since_last_call(), @@ -793,6 +801,8 @@ mod on_idle { RuntimeOrigin::signed(VALIDATOR_PREFIX), vec![VALIDATOR_PREFIX] )); + + assert_eq!(Balances::reducible_balance(&VALIDATOR_PREFIX, Expendable, Polite), 7); assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(VALIDATOR_PREFIX))); // but they indeed are exposed! diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index 87369c23948c..0a85d9ffd2b0 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -161,6 +161,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = ::Balance; type SessionsPerEra = SessionsPerEra; diff --git a/substrate/frame/nomination-pools/benchmarking/src/inner.rs b/substrate/frame/nomination-pools/benchmarking/src/inner.rs index 7ddb78cca3f9..20c5eafbcfc5 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/inner.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/inner.rs @@ -132,6 +132,10 @@ fn migrate_to_transfer_stake(pool_id: PoolId) { .expect("member should have enough balance to transfer"); }); + // Pool needs to have ED balance free to stake so give it some. + // Note: we didn't require ED until pallet-staking migrated from locks to holds. + let _ = CurrencyOf::::mint_into(&pool_acc, CurrencyOf::::minimum_balance()); + pallet_staking::Pallet::::migrate_to_direct_staker(&pool_acc); } @@ -141,14 +145,6 @@ fn vote_to_balance( vote.try_into().map_err(|_| "could not convert u64 to Balance") } -/// `assertion` should strictly be true if the adapter is using `Delegate` strategy and strictly -/// false if the adapter is not using `Delegate` strategy. -fn assert_if_delegate(assertion: bool) { - let legacy_adapter_used = T::StakeAdapter::strategy_type() != StakeStrategyType::Delegate; - // one and only one of the two should be true. - assert!(assertion ^ legacy_adapter_used); -} - #[allow(unused)] struct ListScenario { /// Stash/Controller that is expected to be moved. @@ -981,9 +977,6 @@ mod benchmarks { #[benchmark] fn apply_slash() { - // Note: With older `TransferStake` strategy, slashing is greedy and apply_slash should - // always fail. - // We want to fill member's unbonding pools. So let's bond with big enough amount. let deposit_amount = Pools::::depositor_min_bond() * T::MaxUnbonding::get().into() * 4u32.into(); @@ -993,7 +986,7 @@ mod benchmarks { // verify user balance in the pool. assert_eq!(PoolMembers::::get(&depositor).unwrap().total_balance(), deposit_amount); // verify delegated balance. - assert_if_delegate::( + assert!( T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) == Some(deposit_amount), ); @@ -1017,7 +1010,7 @@ mod benchmarks { deposit_amount / 2u32.into() ); // verify delegated balance are not yet slashed. - assert_if_delegate::( + assert!( T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) == Some(deposit_amount), ); @@ -1041,13 +1034,11 @@ mod benchmarks { #[block] { - assert_if_delegate::( - Pools::::apply_slash( - RuntimeOrigin::Signed(slash_reporter.clone()).into(), - depositor_lookup.clone(), - ) - .is_ok(), - ); + assert!(Pools::::apply_slash( + RuntimeOrigin::Signed(slash_reporter.clone()).into(), + depositor_lookup.clone(), + ) + .is_ok(),); } // verify balances are correct and slash applied. @@ -1055,7 +1046,7 @@ mod benchmarks { PoolMembers::::get(&depositor).unwrap().total_balance(), deposit_amount / 2u32.into() ); - assert_if_delegate::( + assert!( T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) == Some(deposit_amount / 2u32.into()), ); @@ -1126,18 +1117,16 @@ mod benchmarks { let _ = migrate_to_transfer_stake::(1); #[block] { - assert_if_delegate::( - Pools::::migrate_pool_to_delegate_stake( - RuntimeOrigin::Signed(depositor.clone()).into(), - 1u32.into(), - ) - .is_ok(), - ); + assert!(Pools::::migrate_pool_to_delegate_stake( + RuntimeOrigin::Signed(depositor.clone()).into(), + 1u32.into(), + ) + .is_ok(),); } - // this queries agent balance if `DelegateStake` strategy. + // this queries agent balance. assert_eq!( T::StakeAdapter::total_balance(Pool::from(pool_account.clone())), - Some(deposit_amount) + Some(deposit_amount + CurrencyOf::::minimum_balance()) ); } @@ -1152,13 +1141,11 @@ mod benchmarks { let _ = migrate_to_transfer_stake::(1); // Now migrate pool to delegate stake keeping delegators unmigrated. - assert_if_delegate::( - Pools::::migrate_pool_to_delegate_stake( - RuntimeOrigin::Signed(depositor.clone()).into(), - 1u32.into(), - ) - .is_ok(), - ); + assert!(Pools::::migrate_pool_to_delegate_stake( + RuntimeOrigin::Signed(depositor.clone()).into(), + 1u32.into(), + ) + .is_ok(),); // delegation does not exist. assert!( @@ -1171,16 +1158,14 @@ mod benchmarks { #[block] { - assert_if_delegate::( - Pools::::migrate_delegation( - RuntimeOrigin::Signed(depositor.clone()).into(), - depositor_lookup.clone(), - ) - .is_ok(), - ); + assert!(Pools::::migrate_delegation( + RuntimeOrigin::Signed(depositor.clone()).into(), + depositor_lookup.clone(), + ) + .is_ok(),); } // verify balances once more. - assert_if_delegate::( + assert!( T::StakeAdapter::member_delegation_balance(Member::from(depositor.clone())) == Some(deposit_amount), ); diff --git a/substrate/frame/nomination-pools/benchmarking/src/mock.rs b/substrate/frame/nomination-pools/benchmarking/src/mock.rs index 15d9e2c56031..7c09cf22ad51 100644 --- a/substrate/frame/nomination-pools/benchmarking/src/mock.rs +++ b/substrate/frame/nomination-pools/benchmarking/src/mock.rs @@ -78,6 +78,7 @@ parameter_types! { } #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = pallet_timestamp::Pallet; diff --git a/substrate/frame/nomination-pools/src/adapter.rs b/substrate/frame/nomination-pools/src/adapter.rs index f125919dabfa..f1c68af4ea6a 100644 --- a/substrate/frame/nomination-pools/src/adapter.rs +++ b/substrate/frame/nomination-pools/src/adapter.rs @@ -16,6 +16,7 @@ // limitations under the License. use crate::*; +use frame_support::traits::tokens::{Fortitude::Polite, Preservation::Expendable}; use sp_staking::{Agent, DelegationInterface, DelegationMigrator, Delegator}; /// Types of stake strategies. @@ -245,8 +246,10 @@ pub trait StakeStrategy { /// strategy in an existing runtime, storage migration is required. See /// [`migration::unversioned::DelegationStakeMigration`]. For new runtimes, it is highly recommended /// to use the [`DelegateStake`] strategy. +#[deprecated = "consider migrating to DelegateStake"] pub struct TransferStake(PhantomData<(T, Staking)>); +#[allow(deprecated)] impl, AccountId = T::AccountId>> StakeStrategy for TransferStake { @@ -262,7 +265,8 @@ impl, AccountId = T: pool_account: Pool, _: Member, ) -> BalanceOf { - T::Currency::balance(&pool_account.0).saturating_sub(Self::active_stake(pool_account)) + // free/liquid balance of the pool account. + T::Currency::reducible_balance(&pool_account.get(), Expendable, Polite) } fn total_balance(pool_account: Pool) -> Option> { diff --git a/substrate/frame/nomination-pools/src/mock.rs b/substrate/frame/nomination-pools/src/mock.rs index f544e79ec481..f4552389a267 100644 --- a/substrate/frame/nomination-pools/src/mock.rs +++ b/substrate/frame/nomination-pools/src/mock.rs @@ -23,8 +23,10 @@ use frame_support::{ PalletId, }; use frame_system::{EnsureSignedBy, RawOrigin}; -use sp_runtime::{BuildStorage, FixedU128}; -use sp_staking::{OnStakingUpdate, Stake}; +use sp_runtime::{BuildStorage, DispatchResult, FixedU128}; +use sp_staking::{ + Agent, DelegationInterface, DelegationMigrator, Delegator, OnStakingUpdate, Stake, +}; pub type BlockNumber = u64; pub type AccountId = u128; @@ -76,6 +78,7 @@ impl StakingMock { let bonded = BondedBalanceMap::get(); let pre_total = bonded.get(&acc).unwrap(); Self::set_bonded_balance(acc, pre_total - amount); + DelegateMock::on_slash(acc, amount); Pools::on_slash(&acc, pre_total - amount, &Default::default(), amount); } } @@ -112,8 +115,8 @@ impl sp_staking::StakingInterface for StakingMock { .ok_or(DispatchError::Other("NotStash")) } - fn is_virtual_staker(_who: &Self::AccountId) -> bool { - false + fn is_virtual_staker(who: &Self::AccountId) -> bool { + AgentBalanceMap::get().contains_key(who) } fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { @@ -162,7 +165,9 @@ impl sp_staking::StakingInterface for StakingMock { staker_map.retain(|(unlocking_at, _amount)| *unlocking_at > current_era); // if there was a withdrawal, notify the pallet. - Pools::on_withdraw(&who, unlocking_before.saturating_sub(unlocking(&staker_map))); + let withdraw_amount = unlocking_before.saturating_sub(unlocking(&staker_map)); + Pools::on_withdraw(&who, withdraw_amount); + DelegateMock::on_withdraw(who, withdraw_amount); UnbondingBalanceMap::set(&unbonding_map); Ok(UnbondingBalanceMap::get().get(&who).unwrap().is_empty() && @@ -239,6 +244,176 @@ impl sp_staking::StakingInterface for StakingMock { } } +parameter_types! { + // Map of agent to their (delegated balance, unclaimed withdrawal, pending slash). + pub storage AgentBalanceMap: BTreeMap = Default::default(); + pub storage DelegatorBalanceMap: BTreeMap = Default::default(); +} +pub struct DelegateMock; +impl DelegationInterface for DelegateMock { + type Balance = Balance; + type AccountId = AccountId; + fn agent_balance(agent: Agent) -> Option { + AgentBalanceMap::get() + .get(&agent.get()) + .copied() + .map(|(delegated, _, pending)| delegated - pending) + } + + fn agent_transferable_balance(agent: Agent) -> Option { + AgentBalanceMap::get() + .get(&agent.get()) + .copied() + .map(|(_, unclaimed_withdrawals, _)| unclaimed_withdrawals) + } + + fn delegator_balance(delegator: Delegator) -> Option { + DelegatorBalanceMap::get().get(&delegator.get()).copied() + } + + fn register_agent( + agent: Agent, + _reward_account: &Self::AccountId, + ) -> DispatchResult { + let mut agents = AgentBalanceMap::get(); + agents.insert(agent.get(), (0, 0, 0)); + AgentBalanceMap::set(&agents); + Ok(()) + } + + fn remove_agent(agent: Agent) -> DispatchResult { + let mut agents = AgentBalanceMap::get(); + let agent = agent.get(); + assert!(agents.contains_key(&agent)); + agents.remove(&agent); + AgentBalanceMap::set(&agents); + Ok(()) + } + + fn delegate( + delegator: Delegator, + agent: Agent, + amount: Self::Balance, + ) -> DispatchResult { + let delegator = delegator.get(); + let mut delegators = DelegatorBalanceMap::get(); + delegators.entry(delegator).and_modify(|b| *b += amount).or_insert(amount); + DelegatorBalanceMap::set(&delegators); + + let agent = agent.get(); + let mut agents = AgentBalanceMap::get(); + agents + .get_mut(&agent) + .map(|(d, _, _)| *d += amount) + .ok_or(DispatchError::Other("agent not registered"))?; + AgentBalanceMap::set(&agents); + + if BondedBalanceMap::get().contains_key(&agent) { + StakingMock::bond_extra(&agent, amount) + } else { + // reward account does not matter in this context. + StakingMock::bond(&agent, amount, &999) + } + } + + fn withdraw_delegation( + delegator: Delegator, + agent: Agent, + amount: Self::Balance, + _num_slashing_spans: u32, + ) -> DispatchResult { + let mut delegators = DelegatorBalanceMap::get(); + delegators.get_mut(&delegator.get()).map(|b| *b -= amount); + DelegatorBalanceMap::set(&delegators); + + let mut agents = AgentBalanceMap::get(); + agents.get_mut(&agent.get()).map(|(d, u, _)| { + *d -= amount; + *u -= amount; + }); + AgentBalanceMap::set(&agents); + + Ok(()) + } + + fn pending_slash(agent: Agent) -> Option { + AgentBalanceMap::get() + .get(&agent.get()) + .copied() + .map(|(_, _, pending_slash)| pending_slash) + } + + fn delegator_slash( + agent: Agent, + delegator: Delegator, + value: Self::Balance, + _maybe_reporter: Option, + ) -> DispatchResult { + let mut delegators = DelegatorBalanceMap::get(); + delegators.get_mut(&delegator.get()).map(|b| *b -= value); + DelegatorBalanceMap::set(&delegators); + + let mut agents = AgentBalanceMap::get(); + agents.get_mut(&agent.get()).map(|(_, _, p)| { + p.saturating_reduce(value); + }); + AgentBalanceMap::set(&agents); + + Ok(()) + } +} + +impl DelegateMock { + pub fn set_agent_balance(who: AccountId, delegated: Balance) { + let mut agents = AgentBalanceMap::get(); + agents.insert(who, (delegated, 0, 0)); + AgentBalanceMap::set(&agents); + } + + pub fn set_delegator_balance(who: AccountId, amount: Balance) { + let mut delegators = DelegatorBalanceMap::get(); + delegators.insert(who, amount); + DelegatorBalanceMap::set(&delegators); + } + + pub fn on_slash(agent: AccountId, amount: Balance) { + let mut agents = AgentBalanceMap::get(); + agents.get_mut(&agent).map(|(_, _, p)| *p += amount); + AgentBalanceMap::set(&agents); + } + + fn on_withdraw(agent: AccountId, amount: Balance) { + let mut agents = AgentBalanceMap::get(); + // if agent exists, add the amount to unclaimed withdrawals. + agents.get_mut(&agent).map(|(_, u, _)| *u += amount); + AgentBalanceMap::set(&agents); + } +} + +impl DelegationMigrator for DelegateMock { + type Balance = Balance; + type AccountId = AccountId; + fn migrate_nominator_to_agent( + _agent: Agent, + _reward_account: &Self::AccountId, + ) -> DispatchResult { + unimplemented!("not used in current unit tests") + } + + fn migrate_delegation( + _agent: Agent, + _delegator: Delegator, + _value: Self::Balance, + ) -> DispatchResult { + unimplemented!("not used in current unit tests") + } + + #[cfg(feature = "runtime-benchmarks")] + fn force_kill_agent(_agent: Agent) { + unimplemented!("not used in current unit tests") + } +} + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Runtime { type Nonce = u64; @@ -295,7 +470,7 @@ impl pools::Config for Runtime { type RewardCounter = RewardCounter; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; - type StakeAdapter = adapter::TransferStake; + type StakeAdapter = adapter::DelegateStake; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type PalletId = PoolsPalletId; type MaxMetadataLen = MaxMetadataLen; @@ -522,6 +697,21 @@ pub fn reward_imbalance(pool: PoolId) -> RewardImbalance { } } +pub fn set_pool_balance(who: AccountId, amount: Balance) { + StakingMock::set_bonded_balance(who, amount); + DelegateMock::set_agent_balance(who, amount); +} + +pub fn member_delegation(who: AccountId) -> Balance { + ::StakeAdapter::member_delegation_balance(Member::from(who)) + .expect("who must be a pool member") +} + +pub fn pool_balance(id: PoolId) -> Balance { + ::StakeAdapter::total_balance(Pool::from(Pools::generate_bonded_account(id))) + .expect("who must be a bonded pool account") +} + #[cfg(test)] mod test { use super::*; diff --git a/substrate/frame/nomination-pools/src/tests.rs b/substrate/frame/nomination-pools/src/tests.rs index 06261699a5b2..c46638d2f8f7 100644 --- a/substrate/frame/nomination-pools/src/tests.rs +++ b/substrate/frame/nomination-pools/src/tests.rs @@ -24,6 +24,7 @@ use sp_runtime::{ traits::{BadOrigin, Dispatchable}, FixedU128, }; +use sp_staking::{Agent, DelegationInterface}; macro_rules! unbonding_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ @@ -127,41 +128,41 @@ mod bonded_pool { }; // 1 points : 1 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); assert_eq!(bonded_pool.balance_to_point(10), 10); assert_eq!(bonded_pool.balance_to_point(0), 0); // 2 points : 1 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50); + set_pool_balance(bonded_pool.bonded_account(), 50); assert_eq!(bonded_pool.balance_to_point(10), 20); // 1 points : 2 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 50; assert_eq!(bonded_pool.balance_to_point(10), 5); // 100 points : 0 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + set_pool_balance(bonded_pool.bonded_account(), 0); bonded_pool.points = 100; assert_eq!(bonded_pool.balance_to_point(10), 100 * 10); // 0 points : 100 balance - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 0; assert_eq!(bonded_pool.balance_to_point(10), 10); // 10 points : 3 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30); + set_pool_balance(bonded_pool.bonded_account(), 30); bonded_pool.points = 100; assert_eq!(bonded_pool.balance_to_point(10), 33); // 2 points : 3 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300); + set_pool_balance(bonded_pool.bonded_account(), 300); bonded_pool.points = 200; assert_eq!(bonded_pool.balance_to_point(10), 6); // 4 points : 9 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900); + set_pool_balance(bonded_pool.bonded_account(), 900); bonded_pool.points = 400; assert_eq!(bonded_pool.balance_to_point(90), 40); }) @@ -182,7 +183,7 @@ mod bonded_pool { }, }; - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); assert_eq!(bonded_pool.points_to_balance(10), 10); assert_eq!(bonded_pool.points_to_balance(0), 0); @@ -191,27 +192,27 @@ mod bonded_pool { assert_eq!(bonded_pool.points_to_balance(10), 20); // 100 balance : 0 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 0; assert_eq!(bonded_pool.points_to_balance(10), 0); // 0 balance : 100 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + set_pool_balance(bonded_pool.bonded_account(), 0); bonded_pool.points = 100; assert_eq!(bonded_pool.points_to_balance(10), 0); // 10 balance : 3 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + set_pool_balance(bonded_pool.bonded_account(), 100); bonded_pool.points = 30; assert_eq!(bonded_pool.points_to_balance(10), 33); // 2 balance : 3 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200); + set_pool_balance(bonded_pool.bonded_account(), 200); bonded_pool.points = 300; assert_eq!(bonded_pool.points_to_balance(10), 6); // 4 balance : 9 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400); + set_pool_balance(bonded_pool.bonded_account(), 400); bonded_pool.points = 900; assert_eq!(bonded_pool.points_to_balance(90), 40); }) @@ -269,30 +270,21 @@ mod bonded_pool { <::MaxPointsToBalance as Get>::get().into(); // Simulate a 100% slashed pool - StakingMock::set_bonded_balance(pool.bonded_account(), 0); + set_pool_balance(pool.bonded_account(), 0); assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); // Simulate a slashed pool at `MaxPointsToBalance` + 1 slashed pool - StakingMock::set_bonded_balance( - pool.bonded_account(), - max_points_to_balance.saturating_add(1), - ); + set_pool_balance(pool.bonded_account(), max_points_to_balance.saturating_add(1)); assert_ok!(pool.ok_to_join()); // Simulate a slashed pool at `MaxPointsToBalance` - StakingMock::set_bonded_balance(pool.bonded_account(), max_points_to_balance); + set_pool_balance(pool.bonded_account(), max_points_to_balance); assert_noop!(pool.ok_to_join(), Error::::OverflowRisk); - StakingMock::set_bonded_balance( - pool.bonded_account(), - Balance::MAX / max_points_to_balance, - ); + set_pool_balance(pool.bonded_account(), Balance::MAX / max_points_to_balance); // and a sanity check - StakingMock::set_bonded_balance( - pool.bonded_account(), - Balance::MAX / max_points_to_balance - 1, - ); + set_pool_balance(pool.bonded_account(), Balance::MAX / max_points_to_balance - 1); assert_ok!(pool.ok_to_join()); }); } @@ -310,7 +302,7 @@ mod bonded_pool { state: PoolState::Open, }, }; - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), u128::MAX); + set_pool_balance(bonded_pool.bonded_account(), u128::MAX); // Max out the points and balance of the pool and make sure the conversion works as // expected and does not overflow. @@ -640,8 +632,6 @@ mod sub_pools { } mod join { - use sp_runtime::TokenError; - use super::*; #[test] @@ -728,7 +718,7 @@ mod join { ); // Force the pools bonded balance to 0, simulating a 100% slash - StakingMock::set_bonded_balance(Pools::generate_bonded_account(1), 0); + set_pool_balance(Pools::generate_bonded_account(1), 0); assert_noop!( Pools::join(RuntimeOrigin::signed(11), 420, 1), Error::::OverflowRisk @@ -754,29 +744,13 @@ mod join { let max_points_to_balance: u128 = <::MaxPointsToBalance as Get>::get().into(); - StakingMock::set_bonded_balance( - Pools::generate_bonded_account(123), - max_points_to_balance, - ); + set_pool_balance(Pools::generate_bonded_account(123), max_points_to_balance); assert_noop!( Pools::join(RuntimeOrigin::signed(11), 420, 123), Error::::OverflowRisk ); - StakingMock::set_bonded_balance( - Pools::generate_bonded_account(123), - Balance::MAX / max_points_to_balance, - ); - // Balance needs to be gt Balance::MAX / `MaxPointsToBalance` - assert_noop!( - Pools::join(RuntimeOrigin::signed(11), 5, 123), - TokenError::FundsUnavailable, - ); - - StakingMock::set_bonded_balance( - Pools::generate_bonded_account(1), - max_points_to_balance, - ); + set_pool_balance(Pools::generate_bonded_account(1), max_points_to_balance); // Cannot join a pool that isn't open unsafe_set_state(123, PoolState::Blocked); @@ -807,7 +781,7 @@ mod join { #[cfg_attr(not(debug_assertions), should_panic)] fn join_panics_when_reward_pool_not_found() { ExtBuilder::default().build_and_execute(|| { - StakingMock::set_bonded_balance(Pools::generate_bonded_account(123), 100); + set_pool_balance(Pools::generate_bonded_account(123), 100); BondedPool:: { id: 123, inner: BondedPoolInner { @@ -2321,8 +2295,8 @@ mod claim_payout { fn rewards_are_rounded_down_depositor_collects_them() { ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { // initial balance of 10. - - assert_eq!(Currency::free_balance(&10), 35); + let init_balance_10 = Currency::free_balance(&10); + assert_eq!(member_delegation(10), 10); assert_eq!( Currency::free_balance(&default_reward_account()), Currency::minimum_balance() @@ -2373,8 +2347,10 @@ mod claim_payout { ); assert!(!Metadata::::contains_key(1)); - // original ed + ed put into reward account + reward + bond + dust. - assert_eq!(Currency::free_balance(&10), 35 + 5 + 13 + 10 + 1); + // original ed + ed put into reward account + reward + dust. + assert_eq!(Currency::free_balance(&10), init_balance_10 + 5 + 13 + 1); + // delegation reduced from 10 to 0. + assert_eq!(member_delegation(10), 0); }) } @@ -2444,9 +2420,10 @@ mod claim_payout { let claimable_reward = 8 - ExistentialDeposit::get(); // NOTE: easier to read if we use 3, so let's use the number instead of variable. assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3"); + let init_balance = Currency::free_balance(&10); // given - assert_eq!(Currency::free_balance(&10), 35); + assert_eq!(member_delegation(10), 10); // when @@ -2455,7 +2432,10 @@ mod claim_payout { assert_ok!(Pools::claim_payout_other(RuntimeOrigin::signed(80), 10)); // then - assert_eq!(Currency::free_balance(&10), 36); + // delegated balance does not change. + assert_eq!(member_delegation(10), 10); + // reward of 1 is paid out to 10. + assert_eq!(Currency::free_balance(&10), init_balance + 1); assert_eq!(Currency::free_balance(&default_reward_account()), 7); }) } @@ -2818,6 +2798,8 @@ mod unbond { ExtBuilder::default() .add_members(vec![(40, 40), (550, 550)]) .build_and_execute(|| { + let init_balance_40 = Currency::free_balance(&40); + let init_balance_550 = Currency::free_balance(&550); let ed = Currency::minimum_balance(); // Given a slash from 600 -> 500 StakingMock::slash_by(1, 500); @@ -2864,7 +2846,9 @@ mod unbond { PoolMembers::::get(40).unwrap().unbonding_eras, member_unbonding_eras!(3 => 6) ); - assert_eq!(Currency::free_balance(&40), 40 + 40); // We claim rewards when unbonding + assert_eq!(member_delegation(40), 40); + // We claim rewards when unbonding + assert_eq!(Currency::free_balance(&40), init_balance_40 + 40); // When unsafe_set_state(1, PoolState::Destroying); @@ -2893,7 +2877,8 @@ mod unbond { PoolMembers::::get(550).unwrap().unbonding_eras, member_unbonding_eras!(3 => 92) ); - assert_eq!(Currency::free_balance(&550), 550 + 550); + assert_eq!(member_delegation(550), 550); + assert_eq!(Currency::free_balance(&550), init_balance_550 + 550); assert_eq!( pool_events_since_last_call(), vec![ @@ -2934,7 +2919,8 @@ mod unbond { ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); - assert_eq!(Currency::free_balance(&550), 550 + 550 + 92); + // 550 is removed from pool. + assert_eq!(member_delegation(550), 0); assert_eq!( pool_events_since_last_call(), vec![ @@ -3532,7 +3518,7 @@ mod pool_withdraw_unbonded { assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15)); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20)); - assert_eq!(Balances::free_balance(&default_bonded_account()), 20); + assert_eq!(pool_balance(1), 20); // When CurrentEra::set(StakingMock::current_era() + StakingMock::bonding_duration() + 1); @@ -3541,7 +3527,7 @@ mod pool_withdraw_unbonded { // Then their unbonding balance is no longer locked assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15)); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15)); - assert_eq!(Balances::free_balance(&default_bonded_account()), 20); + assert_eq!(pool_balance(1), 20); }); } #[test] @@ -3552,7 +3538,7 @@ mod pool_withdraw_unbonded { assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15)); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20)); - assert_eq!(Balances::free_balance(&default_bonded_account()), 20); + assert_eq!(pool_balance(1), 20); assert_eq!(TotalValueLocked::::get(), 20); // When @@ -3568,14 +3554,14 @@ mod pool_withdraw_unbonded { // Then their unbonding balance is no longer locked assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15)); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15)); - assert_eq!(Currency::free_balance(&default_bonded_account()), 20); + assert_eq!(pool_balance(1), 20); // The difference between TVL and member_balance is exactly the difference between - // `total_stake` and the `free_balance`. - // This relation is not guaranteed in the wild as arbitrary transfers towards - // `free_balance` can be made to the pool that are not accounted for. - let non_locked_balance = Balances::free_balance(&default_bonded_account()) - - StakingMock::total_stake(&default_bonded_account()).unwrap(); + // `pool balance` (sum of all balance delegated to pool) and the `staked balance`. + // This is the withdrawn funds from the pool stake that has not yet been claimed by the + // respective members. + let non_locked_balance = + pool_balance(1) - StakingMock::total_stake(&default_bonded_account()).unwrap(); assert_eq!(member_balance, TotalValueLocked::::get() + non_locked_balance); }); } @@ -3597,7 +3583,7 @@ mod withdraw_unbonded { assert_eq!(StakingMock::bonding_duration(), 3); assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(550), 550)); assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(40), 40)); - assert_eq!(Currency::free_balance(&default_bonded_account()), 600); + assert_eq!(pool_balance(1), 600); let mut current_era = 1; CurrentEra::set(current_era); @@ -3626,10 +3612,7 @@ mod withdraw_unbonded { .1 /= 2; UnbondingBalanceMap::set(&x); - Currency::set_balance( - &default_bonded_account(), - Currency::free_balance(&default_bonded_account()) / 2, // 300 - ); + set_pool_balance(1, pool_balance(1) / 2); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 10); StakingMock::slash_by(1, 5); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 5); @@ -3671,11 +3654,6 @@ mod withdraw_unbonded { Event::PoolSlashed { pool_id: 1, balance: 5 } ] ); - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Burned { who: default_bonded_account(), amount: 300 }] - ); - // When assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0)); @@ -3691,10 +3669,9 @@ mod withdraw_unbonded { Event::MemberRemoved { pool_id: 1, member: 550, released_balance: 0 } ] ); - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 }] - ); + + // member has 40 tokens in delegation, but only 20 can be withdrawan. + assert_eq!(member_delegation(40), 40); // When assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0)); @@ -3708,18 +3685,18 @@ mod withdraw_unbonded { assert_eq!( pool_events_since_last_call(), vec![ + // out of 40, 20 is withdrawn. Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 40 }, - Event::MemberRemoved { pool_id: 1, member: 40, released_balance: 0 } + // member is removed and the dangling delegation of 20 tokens left in their + // account is released. + Event::MemberRemoved { pool_id: 1, member: 40, released_balance: 20 } ] ); - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 }] - ); // now, finally, the depositor can take out its share. unsafe_set_state(1, PoolState::Destroying); assert_ok!(fully_unbond_permissioned(10)); + assert_eq!(member_delegation(10), 10); current_era += 3; CurrentEra::set(current_era); @@ -3731,7 +3708,9 @@ mod withdraw_unbonded { vec![ Event::Unbonded { member: 10, pool_id: 1, balance: 5, points: 5, era: 9 }, Event::Withdrawn { member: 10, pool_id: 1, balance: 5, points: 5 }, - Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, + // when member is removed, any leftover delegation is released. + Event::MemberRemoved { pool_id: 1, member: 10, released_balance: 5 }, + // when the last member leaves, the pool is destroyed. Event::Destroyed { pool_id: 1 } ] ); @@ -3739,7 +3718,6 @@ mod withdraw_unbonded { assert_eq!( balances_events_since_last_call(), vec![ - BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 }, BEvent::Thawed { who: default_reward_account(), amount: 5 }, BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 } ] @@ -3753,11 +3731,9 @@ mod withdraw_unbonded { .add_members(vec![(40, 40), (550, 550)]) .build_and_execute(|| { let _ = balances_events_since_last_call(); - // Given // current bond is 600, we slash it all to 300. StakingMock::slash_by(1, 300); - Currency::set_balance(&default_bonded_account(), 300); assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(300)); assert_ok!(fully_unbond_permissioned(40)); @@ -3787,10 +3763,6 @@ mod withdraw_unbonded { } ] ); - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Burned { who: default_bonded_account(), amount: 300 },] - ); CurrentEra::set(StakingMock::bonding_duration()); @@ -3798,10 +3770,6 @@ mod withdraw_unbonded { assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0)); // Then - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 },] - ); assert_eq!( pool_events_since_last_call(), vec![ @@ -3819,10 +3787,6 @@ mod withdraw_unbonded { assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0)); // Then - assert_eq!( - balances_events_since_last_call(), - vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 },] - ); assert_eq!( pool_events_since_last_call(), vec![ @@ -3852,9 +3816,11 @@ mod withdraw_unbonded { assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); // then - assert_eq!(Currency::free_balance(&10), 10 + 35); - assert_eq!(Currency::free_balance(&default_bonded_account()), 0); - + assert_eq!( + DelegateMock::agent_balance(Agent::from(default_bonded_account())), + None + ); + assert_eq!(StakingMock::stake(&default_bonded_account()).unwrap().total, 0); // in this test 10 also gets a fair share of the slash, because the slash was // applied to the bonded account. assert_eq!( @@ -3870,7 +3836,6 @@ mod withdraw_unbonded { assert_eq!( balances_events_since_last_call(), vec![ - BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 }, BEvent::Thawed { who: default_reward_account(), amount: 5 }, BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 } ] @@ -3878,35 +3843,6 @@ mod withdraw_unbonded { }); } - #[test] - fn withdraw_unbonded_handles_faulty_sub_pool_accounting() { - ExtBuilder::default().build_and_execute(|| { - // Given - assert_eq!(Currency::minimum_balance(), 5); - assert_eq!(Currency::free_balance(&10), 35); - assert_eq!(Currency::free_balance(&default_bonded_account()), 10); - unsafe_set_state(1, PoolState::Destroying); - assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(10), 10)); - - // Simulate a slash that is not accounted for in the sub pools. - Currency::set_balance(&default_bonded_account(), 5); - assert_eq!( - SubPoolsStorage::::get(1).unwrap().with_era, - //------------------------------balance decrease is not account for - unbonding_pools_with_era! { 3 => UnbondPool { points: 10, balance: 10 } } - ); - - CurrentEra::set(3); - - // When - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); - - // Then - assert_eq!(Currency::free_balance(&10), 10 + 35); - assert_eq!(Currency::free_balance(&default_bonded_account()), 0); - }); - } - #[test] fn withdraw_unbonded_errors_correctly() { ExtBuilder::default().with_check(0).build_and_execute(|| { @@ -3925,6 +3861,10 @@ mod withdraw_unbonded { let mut member = PoolMember { pool_id: 1, points: 10, ..Default::default() }; PoolMembers::::insert(11, member.clone()); + // set agent and delegator balance + DelegateMock::set_agent_balance(Pools::generate_bonded_account(1), 10); + DelegateMock::set_delegator_balance(11, 10); + // Simulate calling `unbond` member.unbonding_eras = member_unbonding_eras!(3 => 10); PoolMembers::::insert(11, member.clone()); @@ -4045,7 +3985,7 @@ mod withdraw_unbonded { } ); CurrentEra::set(StakingMock::bonding_duration()); - assert_eq!(Currency::free_balance(&100), 100); + assert_eq!(member_delegation(100), 100); // Cannot permissionlessly withdraw assert_noop!( @@ -4061,6 +4001,7 @@ mod withdraw_unbonded { assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default(),); assert_eq!(Currency::free_balance(&100), 100 + 100); + assert_eq!(member_delegation(100), 0); assert!(!PoolMembers::::contains_key(100)); assert_eq!( pool_events_since_last_call(), @@ -4662,10 +4603,6 @@ mod withdraw_unbonded { // move to era when unbonded funds can be withdrawn. CurrentEra::set(4); - - // increment consumer by 1 reproducing the erroneous consumer bug. - // refer https://github.com/paritytech/polkadot-sdk/issues/4440. - assert_ok!(frame_system::Pallet::::inc_consumers(&pool_one)); assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); assert_eq!( @@ -4712,7 +4649,7 @@ mod create { )); assert_eq!(TotalValueLocked::::get(), 10 + StakingMock::minimum_nominator_bond()); - assert_eq!(Currency::free_balance(&11), 0); + assert_eq!(member_delegation(11), StakingMock::minimum_nominator_bond()); assert_eq!( PoolMembers::::get(11).unwrap(), PoolMember { @@ -4851,7 +4788,7 @@ mod create { 789 )); - assert_eq!(Currency::free_balance(&11), 0); + assert_eq!(member_delegation(11), StakingMock::minimum_nominator_bond()); // delete the initial pool created, then pool_Id `1` will be free assert_noop!( @@ -5014,16 +4951,9 @@ mod set_state { // surpassed. Making this pool destroyable by anyone. StakingMock::slash_by(1, 10); - // in mock we are using transfer stake which implies slash is greedy. Extrinsic to - // apply pending slash should fail. - assert_noop!( - Pools::apply_slash(RuntimeOrigin::signed(11), 10), - Error::::NotSupported - ); - - // pending slash api should return zero as well. - assert_eq!(Pools::api_pool_pending_slash(1), 0); - assert_eq!(Pools::api_member_pending_slash(10), 0); + // pending slash is correct. + assert_eq!(Pools::api_pool_pending_slash(1), 10); + assert_eq!(Pools::api_member_pending_slash(10), 10); // When assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying)); @@ -5175,13 +5105,13 @@ mod bond_extra { // given assert_eq!(PoolMembers::::get(10).unwrap().points, 10); assert_eq!(BondedPools::::get(1).unwrap().points, 10); - assert_eq!(Currency::free_balance(&10), 100); + assert_eq!(member_delegation(10), 10); // when assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); // then - assert_eq!(Currency::free_balance(&10), 90); + assert_eq!(member_delegation(10), 10 + 10); assert_eq!(PoolMembers::::get(10).unwrap().points, 20); assert_eq!(BondedPools::::get(1).unwrap().points, 20); @@ -5198,7 +5128,7 @@ mod bond_extra { assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(20))); // then - assert_eq!(Currency::free_balance(&10), 70); + assert_eq!(member_delegation(10), 20 + 20); assert_eq!(PoolMembers::::get(10).unwrap().points, 40); assert_eq!(BondedPools::::get(1).unwrap().points, 40); @@ -5221,13 +5151,15 @@ mod bond_extra { // given assert_eq!(PoolMembers::::get(10).unwrap().points, 10); assert_eq!(BondedPools::::get(1).unwrap().points, 10); - assert_eq!(Currency::free_balance(&10), 35); + // 10 has delegated 10 tokens to the pool. + assert_eq!(member_delegation(10), 10); // when assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards)); // then - assert_eq!(Currency::free_balance(&10), 35); + // delegator balance is increased by the claimable reward. + assert_eq!(member_delegation(10), 10 + claimable_reward); assert_eq!(PoolMembers::::get(10).unwrap().points, 10 + claimable_reward); assert_eq!(BondedPools::::get(1).unwrap().points, 10 + claimable_reward); @@ -5264,8 +5196,8 @@ mod bond_extra { assert_eq!(PoolMembers::::get(20).unwrap().points, 20); assert_eq!(BondedPools::::get(1).unwrap().points, 30); - assert_eq!(Currency::free_balance(&10), 35); - assert_eq!(Currency::free_balance(&20), 20); + assert_eq!(member_delegation(10), 10); + assert_eq!(member_delegation(20), 20); assert_eq!(TotalValueLocked::::get(), 30); // when @@ -5273,7 +5205,7 @@ mod bond_extra { assert_eq!(Currency::free_balance(&default_reward_account()), 7); // then - assert_eq!(Currency::free_balance(&10), 35); + assert_eq!(member_delegation(10), 10 + 1); assert_eq!(TotalValueLocked::::get(), 31); // 10's share of the reward is 1/3, since they gave 10/30 of the total shares. @@ -5284,11 +5216,11 @@ mod bond_extra { assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::Rewards)); // then - assert_eq!(Currency::free_balance(&20), 20); assert_eq!(TotalValueLocked::::get(), 33); // 20's share of the rewards is the other 2/3 of the rewards, since they have 20/30 of // the shares + assert_eq!(member_delegation(20), 20 + 2); assert_eq!(PoolMembers::::get(20).unwrap().points, 20 + 2); assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 3); @@ -5320,8 +5252,8 @@ mod bond_extra { assert_eq!(PoolMembers::::get(10).unwrap().points, 10); assert_eq!(PoolMembers::::get(20).unwrap().points, 20); assert_eq!(BondedPools::::get(1).unwrap().points, 30); - assert_eq!(Currency::free_balance(&10), 35); - assert_eq!(Currency::free_balance(&20), 20); + assert_eq!(member_delegation(10), 10); + assert_eq!(member_delegation(20), 20); // Permissioned by default assert_noop!( @@ -5337,7 +5269,7 @@ mod bond_extra { assert_eq!(Currency::free_balance(&default_reward_account()), 7); // then - assert_eq!(Currency::free_balance(&10), 35); + assert_eq!(member_delegation(10), 10 + 1); assert_eq!(PoolMembers::::get(10).unwrap().points, 10 + 1); assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 1); @@ -5355,7 +5287,7 @@ mod bond_extra { )); // then - assert_eq!(Currency::free_balance(&20), 12); + assert_eq!(member_delegation(20), 20 + 10); assert_eq!(Currency::free_balance(&default_reward_account()), 5); assert_eq!(PoolMembers::::get(20).unwrap().points, 30); assert_eq!(BondedPools::::get(1).unwrap().points, 41); @@ -7487,63 +7419,3 @@ mod chill { }) } } - -// the test mock is using `TransferStake` and so `DelegateStake` is not tested here. Extrinsics -// meant for `DelegateStake` should be gated. -// -// `DelegateStake` tests are in `pallet-nomination-pools-test-delegate-stake`. Since we support both -// strategies currently, we keep these tests as it is but in future we may remove `TransferStake` -// completely. -mod delegate_stake { - use super::*; - #[test] - fn delegation_specific_calls_are_gated() { - ExtBuilder::default().with_check(0).build_and_execute(|| { - // Given - Currency::set_balance(&11, ExistentialDeposit::get() + 2); - assert!(!PoolMembers::::contains_key(11)); - - // When - assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1)); - - // Then - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true }, - ] - ); - - assert_eq!( - PoolMembers::::get(11).unwrap(), - PoolMember:: { pool_id: 1, points: 2, ..Default::default() } - ); - - // ensure pool 1 cannot be migrated. - assert!(!Pools::api_pool_needs_delegate_migration(1)); - assert_noop!( - Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1), - Error::::NotSupported - ); - - // members cannot be migrated either. - assert!(!Pools::api_member_needs_delegate_migration(10)); - assert_noop!( - Pools::migrate_delegation(RuntimeOrigin::signed(10), 11), - Error::::NotSupported - ); - - // Given - // The bonded balance is slashed in half - StakingMock::slash_by(1, 6); - - // since slash is greedy with `TransferStake`, `apply_slash` should not work either. - assert_noop!( - Pools::apply_slash(RuntimeOrigin::signed(10), 11), - Error::::NotSupported - ); - }); - } -} diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs index cc6335959ab7..54783332aa3e 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs @@ -21,7 +21,10 @@ mod mock; use frame_support::{ assert_noop, assert_ok, hypothetically, - traits::{fungible::InspectHold, Currency}, + traits::{ + fungible::{InspectHold, Mutate}, + Currency, + }, }; use mock::*; use pallet_nomination_pools::{ @@ -942,9 +945,13 @@ fn pool_slash_non_proportional_bonded_pool_and_chunks() { fn pool_migration_e2e() { new_test_ext().execute_with(|| { LegacyAdapter::set(true); - assert_eq!(Balances::minimum_balance(), 5); assert_eq!(CurrentEra::::get(), None); + // hack: mint ED to pool so that the deprecated `TransferStake` works correctly with + // staking. + assert_eq!(Balances::minimum_balance(), 5); + assert_ok!(Balances::mint_into(&POOL1_BONDED, 5)); + // create the pool with TransferStake strategy. assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); assert_eq!(LastPoolId::::get(), 1); @@ -1050,10 +1057,11 @@ fn pool_migration_e2e() { assert_eq!( delegated_staking_events_since_last_call(), + // delegated also contains the extra ED that we minted when pool was `TransferStake` . vec![DelegatedStakingEvent::Delegated { agent: POOL1_BONDED, delegator: proxy_delegator_1, - amount: 50 + 10 * 3 + amount: 50 + 10 * 3 + 5 }] ); @@ -1223,6 +1231,11 @@ fn disable_pool_operations_on_non_migrated() { assert_eq!(Balances::minimum_balance(), 5); assert_eq!(CurrentEra::::get(), None); + // hack: mint ED to pool so that the deprecated `TransferStake` works correctly with + // staking. + assert_eq!(Balances::minimum_balance(), 5); + assert_ok!(Balances::mint_into(&POOL1_BONDED, 5)); + // create the pool with TransferStake strategy. assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); assert_eq!(LastPoolId::::get(), 1); @@ -1331,11 +1344,12 @@ fn disable_pool_operations_on_non_migrated() { assert_ok!(Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1)); assert_eq!( delegated_staking_events_since_last_call(), + // delegated also contains the extra ED that we minted when pool was `TransferStake` . vec![DelegatedStakingEvent::Delegated { agent: POOL1_BONDED, delegator: DelegatedStaking::generate_proxy_delegator(Agent::from(POOL1_BONDED)) .get(), - amount: 50 + 10 + amount: 50 + 10 + 5 },] ); diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index d1bc4ef8ff28..d943ba6f5333 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -15,6 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Disable warnings for `TransferStake` being deprecated. +#![allow(deprecated)] + use frame_election_provider_support::VoteWeight; use frame_support::{ assert_ok, derive_impl, @@ -92,6 +95,7 @@ parameter_types! { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Runtime { + type OldCurrency = Balances; type Currency = Balances; type UnixTime = pallet_timestamp::Pallet; type AdminOrigin = frame_system::EnsureRoot; diff --git a/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml b/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml deleted file mode 100644 index 0b21d5f4e8cf..000000000000 --- a/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "pallet-nomination-pools-test-transfer-stake" -version = "1.0.0" -authors.workspace = true -edition.workspace = true -license = "Apache-2.0" -homepage.workspace = true -repository.workspace = true -description = "FRAME nomination pools pallet tests with the staking pallet" -publish = false - -[lints] -workspace = true - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dev-dependencies] -codec = { features = ["derive"], workspace = true, default-features = true } -scale-info = { features = ["derive"], workspace = true, default-features = true } - -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-staking = { workspace = true, default-features = true } - -frame-election-provider-support = { workspace = true, default-features = true } -frame-support = { workspace = true, default-features = true } -frame-system = { workspace = true, default-features = true } - -pallet-bags-list = { workspace = true, default-features = true } -pallet-balances = { workspace = true, default-features = true } -pallet-nomination-pools = { workspace = true, default-features = true } -pallet-staking = { workspace = true, default-features = true } -pallet-staking-reward-curve = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } - -log = { workspace = true, default-features = true } -sp-tracing = { workspace = true, default-features = true } diff --git a/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs deleted file mode 100644 index cc39cfee91c8..000000000000 --- a/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs +++ /dev/null @@ -1,912 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#![cfg(test)] - -mod mock; - -use frame_support::{assert_noop, assert_ok, traits::Currency}; -use mock::*; -use pallet_nomination_pools::{ - BondExtra, BondedPools, Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember, - PoolMembers, PoolState, -}; -use pallet_staking::{ - CurrentEra, Error as StakingError, Event as StakingEvent, Payee, RewardDestination, -}; -use sp_runtime::{bounded_btree_map, traits::Zero}; - -#[test] -fn pool_lifecycle_e2e() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::minimum_balance(), 5); - assert_eq!(CurrentEra::::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); - assert_eq!(LastPoolId::::get(), 1); - - // have the pool nominate. - assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3])); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Created { depositor: 10, pool_id: 1 }, - PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, - ] - ); - - // have two members join - assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); - assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, - ] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, - PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true }, - ] - ); - - // pool goes into destroying - assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying)); - - // depositor cannot unbond yet. - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(10), 10, 50), - PoolsError::::MinimumBondNotMet, - ); - - // now the members want to unbond. - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); - - assert_eq!(PoolMembers::::get(20).unwrap().unbonding_eras.len(), 1); - assert_eq!(PoolMembers::::get(20).unwrap().points, 0); - assert_eq!(PoolMembers::::get(21).unwrap().unbonding_eras.len(), 1); - assert_eq!(PoolMembers::::get(21).unwrap().points, 0); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, - ] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, - PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10, era: 3 }, - PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10, era: 3 }, - ] - ); - - // depositor cannot still unbond - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(10), 10, 50), - PoolsError::::MinimumBondNotMet, - ); - - for e in 1..BondingDuration::get() { - CurrentEra::::set(Some(e)); - assert_noop!( - Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0), - PoolsError::::CannotWithdrawAny - ); - } - - // members are now unlocked. - CurrentEra::::set(Some(BondingDuration::get())); - - // depositor cannot still unbond - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(10), 10, 50), - PoolsError::::MinimumBondNotMet, - ); - - // but members can now withdraw. - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0)); - assert!(PoolMembers::::get(20).is_none()); - assert!(PoolMembers::::get(21).is_none()); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 20, released_balance: 0 }, - PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 21, released_balance: 0 }, - ] - ); - - // as soon as all members have left, the depositor can try to unbond, but since the - // min-nominator intention is set, they must chill first. - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(10), 10, 50), - pallet_staking::Error::::InsufficientBond - ); - - assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 50)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Chilled { stash: POOL1_BONDED }, - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 50 }, - ] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50, era: 6 }] - ); - - // waiting another bonding duration: - CurrentEra::::set(Some(BondingDuration::get() * 2)); - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 1)); - - // pools is fully destroyed now. - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 50 },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, - PoolsEvent::Destroyed { pool_id: 1 } - ] - ); - }) -} - -#[test] -fn destroy_pool_with_erroneous_consumer() { - new_test_ext().execute_with(|| { - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); - assert_eq!(LastPoolId::::get(), 1); - - // expect consumers on pool account to be 2 (staking lock and an explicit inc by staking). - assert_eq!(frame_system::Pallet::::consumers(&POOL1_BONDED), 2); - - // increment consumer by 1 reproducing the erroneous consumer bug. - // refer https://github.com/paritytech/polkadot-sdk/issues/4440. - assert_ok!(frame_system::Pallet::::inc_consumers(&POOL1_BONDED)); - assert_eq!(frame_system::Pallet::::consumers(&POOL1_BONDED), 3); - - // have the pool nominate. - assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3])); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Created { depositor: 10, pool_id: 1 }, - PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, - ] - ); - - // pool goes into destroying - assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying)); - - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },] - ); - - // move to era 1 - CurrentEra::::set(Some(1)); - - // depositor need to chill before unbonding - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(10), 10, 50), - pallet_staking::Error::::InsufficientBond - ); - - assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 50)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Chilled { stash: POOL1_BONDED }, - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 50 }, - ] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 10, - pool_id: 1, - points: 50, - balance: 50, - era: 1 + 3 - }] - ); - - // waiting bonding duration: - CurrentEra::::set(Some(1 + 3)); - // this should work even with an extra consumer count on pool account. - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 1)); - - // pools is fully destroyed now. - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 50 },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, - PoolsEvent::Destroyed { pool_id: 1 } - ] - ); - }) -} - -#[test] -fn pool_chill_e2e() { - new_test_ext().execute_with(|| { - assert_eq!(Balances::minimum_balance(), 5); - assert_eq!(CurrentEra::::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10)); - assert_eq!(LastPoolId::::get(), 1); - - // have the pool nominate. - assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3])); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Created { depositor: 10, pool_id: 1 }, - PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, - ] - ); - - // have two members join - assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); - assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, - ] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, - PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true }, - ] - ); - - // in case depositor does not have more than `MinNominatorBond` staked, we can end up in - // situation where a member unbonding would cause pool balance to drop below - // `MinNominatorBond` and hence not allowed. This can happen if the `MinNominatorBond` is - // increased after the pool is created. - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - pallet_staking::ConfigOp::Set(55), // minimum nominator bond - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - )); - - // members can unbond as long as total stake of the pool is above min nominator bond - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10),); - assert_eq!(PoolMembers::::get(20).unwrap().unbonding_eras.len(), 1); - assert_eq!(PoolMembers::::get(20).unwrap().points, 0); - - // this member cannot unbond since it will cause `pool stake < MinNominatorBond` - assert_noop!( - Pools::unbond(RuntimeOrigin::signed(21), 21, 10), - StakingError::::InsufficientBond, - ); - - // members can call `chill` permissionlessly now - assert_ok!(Pools::chill(RuntimeOrigin::signed(20), 1)); - - // now another member can unbond. - assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); - assert_eq!(PoolMembers::::get(21).unwrap().unbonding_eras.len(), 1); - assert_eq!(PoolMembers::::get(21).unwrap().points, 0); - - // nominator can not resume nomination until depositor have enough stake - assert_noop!( - Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]), - PoolsError::::MinimumBondNotMet, - ); - - // other members joining pool does not affect the depositor's ability to resume nomination - assert_ok!(Pools::join(RuntimeOrigin::signed(22), 10, 1)); - - assert_noop!( - Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]), - PoolsError::::MinimumBondNotMet, - ); - - // depositor can bond extra stake - assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10))); - - // `chill` can not be called permissionlessly anymore - assert_noop!( - Pools::chill(RuntimeOrigin::signed(20), 1), - PoolsError::::NotNominator, - ); - - // now nominator can resume nomination - assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3])); - - // skip to make the unbonding period end. - CurrentEra::::set(Some(BondingDuration::get())); - - // members can now withdraw. - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, - StakingEvent::Chilled { stash: POOL1_BONDED }, - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // other member bonding - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // depositor bond extra - StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 }, - ] - ); - }) -} - -#[test] -fn pool_slash_e2e() { - new_test_ext().execute_with(|| { - ExistentialDeposit::set(1); - assert_eq!(Balances::minimum_balance(), 1); - assert_eq!(CurrentEra::::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); - assert_eq!(LastPoolId::::get(), 1); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Created { depositor: 10, pool_id: 1 }, - PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, - ] - ); - - assert_eq!( - Payee::::get(POOL1_BONDED), - Some(RewardDestination::Account(POOL1_REWARD)) - ); - - // have two members join - assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1)); - assert_ok!(Pools::join(RuntimeOrigin::signed(21), 20, 1)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 }, - StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 } - ] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, - PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 20, joined: true }, - ] - ); - - // now let's progress a bit. - CurrentEra::::set(Some(1)); - - // 20 / 80 of the total funds are unlocked, and safe from any further slash. - assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 } - ] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 }, - PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 4 } - ] - ); - - CurrentEra::::set(Some(2)); - - // note: depositor cannot fully unbond at this point. - // these funds will still get slashed. - assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, - StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }, - ] - ); - - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 5 }, - PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 5 }, - PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10, era: 5 }, - ] - ); - - // At this point, 20 are safe from slash, 30 are unlocking but vulnerable to slash, and - // another 30 are active and vulnerable to slash. Let's slash half of them. - pallet_staking::slashing::do_slash::( - &POOL1_BONDED, - 30, - &mut Default::default(), - &mut Default::default(), - 2, // slash era 2, affects chunks at era 5 onwards. - ); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - // 30 has been slashed to 15 (15 slash) - PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 15 }, - // 30 has been slashed to 15 (15 slash) - PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 } - ] - ); - - CurrentEra::::set(Some(3)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10)); - - assert_eq!( - PoolMembers::::get(21).unwrap(), - PoolMember { - pool_id: 1, - points: 0, - last_recorded_reward_counter: Zero::zero(), - // the 10 points unlocked just now correspond to 5 points in the unbond pool. - unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5) - } - ); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5, era: 6 }] - ); - - // now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free. - CurrentEra::::set(Some(6)); - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0)); - - assert_eq!( - pool_events_since_last_call(), - vec![ - // 20 had unbonded 10 safely, and 10 got slashed by half. - PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 20, released_balance: 0 }, - // 21 unbonded all of it after the slash - PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 21, released_balance: 0 } - ] - ); - assert_eq!( - staking_events_since_last_call(), - // a 10 (un-slashed) + 10/2 (slashed) balance from 10 has also been unlocked - vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 15 + 10 + 15 }] - ); - - // now, finally, we can unbond the depositor further than their current limit. - assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 20)); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, - PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 } - ] - ); - - CurrentEra::::set(Some(9)); - assert_eq!( - PoolMembers::::get(10).unwrap(), - PoolMember { - pool_id: 1, - points: 0, - last_recorded_reward_counter: Zero::zero(), - unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10) - } - ); - // withdraw the depositor, they should lose 12 balance in total due to slash. - assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 10 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 }, - PoolsEvent::MemberRemoved { pool_id: 1, member: 10, released_balance: 0 }, - PoolsEvent::Destroyed { pool_id: 1 } - ] - ); - }); -} - -#[test] -fn pool_slash_proportional() { - // a typical example where 3 pool members unbond in era 99, 100, and 101, and a slash that - // happened in era 100 should only affect the latter two. - new_test_ext().execute_with(|| { - ExistentialDeposit::set(1); - BondingDuration::set(28); - assert_eq!(Balances::minimum_balance(), 1); - assert_eq!(CurrentEra::::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); - assert_eq!(LastPoolId::::get(), 1); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Created { depositor: 10, pool_id: 1 }, - PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, - ] - ); - - // have two members join - let bond = 20; - assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); - assert_ok!(Pools::join(RuntimeOrigin::signed(21), bond, 1)); - assert_ok!(Pools::join(RuntimeOrigin::signed(22), bond, 1)); - - assert_eq!( - staking_events_since_last_call(), - vec![ - StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, - StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, - StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }, - ] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }, - PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: bond, joined: true }, - PoolsEvent::Bonded { member: 22, pool_id: 1, bonded: bond, joined: true }, - ] - ); - - // now let's progress a lot. - CurrentEra::::set(Some(99)); - - // and unbond - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 20, - pool_id: 1, - balance: bond, - points: bond, - era: 127 - }] - ); - - CurrentEra::::set(Some(100)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, bond)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 21, - pool_id: 1, - balance: bond, - points: bond, - era: 128 - }] - ); - - CurrentEra::::set(Some(101)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, bond)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 22, - pool_id: 1, - balance: bond, - points: bond, - era: 129 - }] - ); - - // Apply a slash that happened in era 100. This is typically applied with a delay. - // Of the total 100, 50 is slashed. - assert_eq!(BondedPools::::get(1).unwrap().points, 40); - pallet_staking::slashing::do_slash::( - &POOL1_BONDED, - 50, - &mut Default::default(), - &mut Default::default(), - 100, - ); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - // This era got slashed 12.5, which rounded up to 13. - PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 128, balance: 7 }, - // This era got slashed 12 instead of 12.5 because an earlier chunk got 0.5 more - // slashed, and 12 is all the remaining slash - PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 129, balance: 8 }, - // Bonded pool got slashed for 25, remaining 15 in it. - PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 } - ] - ); - }); -} - -#[test] -fn pool_slash_non_proportional_only_bonded_pool() { - // A typical example where a pool member unbonds in era 99, and they can get away with a slash - // that happened in era 100, as long as the pool has enough active bond to cover the slash. If - // everything else in the slashing/staking system works, this should always be the case. - // Nonetheless, `ledger.slash` has been written such that it will slash greedily from any chunk - // if it runs out of chunks that it thinks should be affected by the slash. - new_test_ext().execute_with(|| { - ExistentialDeposit::set(1); - BondingDuration::set(28); - assert_eq!(Balances::minimum_balance(), 1); - assert_eq!(CurrentEra::::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Created { depositor: 10, pool_id: 1 }, - PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, - ] - ); - - // have two members join - let bond = 20; - assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }] - ); - - // progress and unbond. - CurrentEra::::set(Some(99)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 20, - pool_id: 1, - balance: bond, - points: bond, - era: 127 - }] - ); - - // slash for 30. This will be deducted only from the bonded pool. - CurrentEra::::set(Some(100)); - assert_eq!(BondedPools::::get(1).unwrap().points, 40); - pallet_staking::slashing::do_slash::( - &POOL1_BONDED, - 30, - &mut Default::default(), - &mut Default::default(), - 100, - ); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::PoolSlashed { pool_id: 1, balance: 10 }] - ); - }); -} - -#[test] -fn pool_slash_non_proportional_bonded_pool_and_chunks() { - // An uncommon example where even though some funds are unlocked such that they should not be - // affected by a slash, we still slash out of them. This should not happen at all. If a - // nomination has unbonded, from the next era onwards, their exposure will drop, so if an era - // happens in that era, then their share of that slash should naturally be less, such that only - // their active ledger stake is enough to compensate it. - new_test_ext().execute_with(|| { - ExistentialDeposit::set(1); - BondingDuration::set(28); - assert_eq!(Balances::minimum_balance(), 1); - assert_eq!(CurrentEra::::get(), None); - - // create the pool, we know this has id 1. - assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - PoolsEvent::Created { depositor: 10, pool_id: 1 }, - PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, - ] - ); - - // have two members join - let bond = 20; - assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }] - ); - - // progress and unbond. - CurrentEra::::set(Some(99)); - assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond)); - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { - member: 20, - pool_id: 1, - balance: bond, - points: bond, - era: 127 - }] - ); - - // slash 50. This will be deducted only from the bonded pool and one of the unbonding pools. - CurrentEra::::set(Some(100)); - assert_eq!(BondedPools::::get(1).unwrap().points, 40); - pallet_staking::slashing::do_slash::( - &POOL1_BONDED, - 50, - &mut Default::default(), - &mut Default::default(), - 100, - ); - - assert_eq!( - staking_events_since_last_call(), - vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }] - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - // out of 20, 10 was taken. - PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 127, balance: 10 }, - // out of 40, all was taken. - PoolsEvent::PoolSlashed { pool_id: 1, balance: 0 } - ] - ); - }); -} diff --git a/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs deleted file mode 100644 index d913c5fe6948..000000000000 --- a/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs +++ /dev/null @@ -1,231 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use frame_election_provider_support::VoteWeight; -use frame_support::{ - assert_ok, derive_impl, - pallet_prelude::*, - parameter_types, - traits::{ConstU64, ConstU8, VariantCountOf}, - PalletId, -}; -use sp_runtime::{ - traits::{Convert, IdentityLookup}, - BuildStorage, FixedU128, Perbill, -}; - -type AccountId = u128; -type BlockNumber = u64; -type Balance = u128; - -pub(crate) type T = Runtime; - -pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128; -pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128; - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for Runtime { - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Block = Block; - type AccountData = pallet_balances::AccountData; -} - -impl pallet_timestamp::Config for Runtime { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = ConstU64<5>; - type WeightInfo = (); -} - -parameter_types! { - pub static ExistentialDeposit: Balance = 5; -} - -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl pallet_balances::Config for Runtime { - type Balance = Balance; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = System; - type FreezeIdentifier = RuntimeFreezeReason; - type MaxFreezes = VariantCountOf; - type RuntimeFreezeReason = RuntimeFreezeReason; -} - -pallet_staking_reward_curve::build! { - const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!( - min_inflation: 0_025_000, - max_inflation: 0_100_000, - ideal_stake: 0_500_000, - falloff: 0_050_000, - max_piece_count: 40, - test_precision: 0_005_000, - ); -} - -parameter_types! { - pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; - pub static BondingDuration: u32 = 3; -} - -#[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] -impl pallet_staking::Config for Runtime { - type Currency = Balances; - type UnixTime = pallet_timestamp::Pallet; - type AdminOrigin = frame_system::EnsureRoot; - type BondingDuration = BondingDuration; - type EraPayout = pallet_staking::ConvertCurve; - type ElectionProvider = - frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; - type GenesisElectionProvider = Self::ElectionProvider; - type VoterList = VoterList; - type TargetList = pallet_staking::UseValidatorsMap; - type EventListeners = Pools; - type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; -} - -parameter_types! { - pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; -} - -type VoterBagsListInstance = pallet_bags_list::Instance1; -impl pallet_bags_list::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - type BagThresholds = BagThresholds; - type ScoreProvider = Staking; - type Score = VoteWeight; -} - -pub struct BalanceToU256; -impl Convert for BalanceToU256 { - fn convert(n: Balance) -> sp_core::U256 { - n.into() - } -} - -pub struct U256ToBalance; -impl Convert for U256ToBalance { - fn convert(n: sp_core::U256) -> Balance { - n.try_into().unwrap() - } -} - -parameter_types! { - pub const PostUnbondingPoolsWindow: u32 = 10; - pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); -} - -impl pallet_nomination_pools::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); - type Currency = Balances; - type RuntimeFreezeReason = RuntimeFreezeReason; - type RewardCounter = FixedU128; - type BalanceToU256 = BalanceToU256; - type U256ToBalance = U256ToBalance; - type StakeAdapter = pallet_nomination_pools::adapter::TransferStake; - type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; - type MaxMetadataLen = ConstU32<256>; - type MaxUnbonding = ConstU32<8>; - type MaxPointsToBalance = ConstU8<10>; - type PalletId = PoolsPalletId; - type AdminOrigin = frame_system::EnsureRoot; -} - -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Runtime { - System: frame_system, - Timestamp: pallet_timestamp, - Balances: pallet_balances, - Staking: pallet_staking, - VoterList: pallet_bags_list::, - Pools: pallet_nomination_pools, - } -); - -pub fn new_test_ext() -> sp_io::TestExternalities { - sp_tracing::try_init_simple(); - let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let _ = pallet_nomination_pools::GenesisConfig:: { - min_join_bond: 2, - min_create_bond: 2, - max_pools: Some(3), - max_members_per_pool: Some(5), - max_members: Some(3 * 5), - global_max_commission: Some(Perbill::from_percent(90)), - } - .assimilate_storage(&mut storage) - .unwrap(); - - let _ = pallet_balances::GenesisConfig:: { - balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)], - } - .assimilate_storage(&mut storage) - .unwrap(); - - let mut ext = sp_io::TestExternalities::from(storage); - - ext.execute_with(|| { - // for events to be deposited. - frame_system::Pallet::::set_block_number(1); - - // set some limit for nominations. - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - pallet_staking::ConfigOp::Set(10), // minimum nominator bond - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - pallet_staking::ConfigOp::Noop, - )); - }); - - ext -} - -parameter_types! { - static ObservedEventsPools: usize = 0; - static ObservedEventsStaking: usize = 0; - static ObservedEventsBalances: usize = 0; -} - -pub(crate) fn pool_events_since_last_call() -> Vec> { - let events = System::events() - .into_iter() - .map(|r| r.event) - .filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None }) - .collect::>(); - let already_seen = ObservedEventsPools::get(); - ObservedEventsPools::set(events.len()); - events.into_iter().skip(already_seen).collect() -} - -pub(crate) fn staking_events_since_last_call() -> Vec> { - let events = System::events() - .into_iter() - .map(|r| r.event) - .filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None }) - .collect::>(); - let already_seen = ObservedEventsStaking::get(); - ObservedEventsStaking::set(events.len()); - events.into_iter().skip(already_seen).collect() -} diff --git a/substrate/frame/offences/benchmarking/src/inner.rs b/substrate/frame/offences/benchmarking/src/inner.rs index 75f3e9931e34..3d3cd470bc24 100644 --- a/substrate/frame/offences/benchmarking/src/inner.rs +++ b/substrate/frame/offences/benchmarking/src/inner.rs @@ -180,16 +180,12 @@ where ::RuntimeEvent: TryInto>, { // make sure that all slashes have been applied - // (n nominators + one validator) * (slashed + unlocked) + deposit to reporter + - // reporter account endowed + some funds rescinded from issuance. - assert_eq!( - System::::read_events_for_pallet::>().len(), - 2 * (offender_count + 1) + 3 - ); + // deposit to reporter + reporter account endowed. + assert_eq!(System::::read_events_for_pallet::>().len(), 2); // (n nominators + one validator) * slashed + Slash Reported assert_eq!( System::::read_events_for_pallet::>().len(), - 1 * (offender_count + 1) + 1 + 1 * (offender_count + 1) as usize + 1 ); // offence assert_eq!(System::::read_events_for_pallet::().len(), 1); diff --git a/substrate/frame/offences/benchmarking/src/mock.rs b/substrate/frame/offences/benchmarking/src/mock.rs index c5c178aa4443..3c81f2a664e3 100644 --- a/substrate/frame/offences/benchmarking/src/mock.rs +++ b/substrate/frame/offences/benchmarking/src/mock.rs @@ -125,6 +125,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index 7a96b8eade4e..3f14dc00b560 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -126,6 +126,7 @@ parameter_types! { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = Timestamp; @@ -206,10 +207,10 @@ impl ExtBuilder { (30, self.balance_factor * 50), (40, self.balance_factor * 50), // stashes - (11, self.balance_factor * 1000), - (21, self.balance_factor * 1000), - (31, self.balance_factor * 500), - (41, self.balance_factor * 1000), + (11, self.balance_factor * 1500), + (21, self.balance_factor * 1500), + (31, self.balance_factor * 1000), + (41, self.balance_factor * 2000), ], } .assimilate_storage(&mut storage) diff --git a/substrate/frame/session/benchmarking/src/mock.rs b/substrate/frame/session/benchmarking/src/mock.rs index 346cd04c0fa9..74201da3d2f3 100644 --- a/substrate/frame/session/benchmarking/src/mock.rs +++ b/substrate/frame/session/benchmarking/src/mock.rs @@ -133,6 +133,7 @@ impl onchain::Config for OnChainSeqPhragmen { #[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] impl pallet_staking::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml index 22176b6d720b..74b1c78e9cbe 100644 --- a/substrate/frame/staking/Cargo.toml +++ b/substrate/frame/staking/Cargo.toml @@ -41,6 +41,7 @@ rand_chacha = { optional = true, workspace = true } [dev-dependencies] frame-benchmarking = { workspace = true, default-features = true } frame-election-provider-support = { workspace = true, default-features = true } +frame-support = { features = ["experimental"], workspace = true, default-features = true } pallet-bags-list = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-staking-reward-curve = { workspace = true, default-features = true } diff --git a/substrate/frame/staking/src/asset.rs b/substrate/frame/staking/src/asset.rs index 23368b1f8fca..a1140d317c20 100644 --- a/substrate/frame/staking/src/asset.rs +++ b/substrate/frame/staking/src/asset.rs @@ -18,9 +18,15 @@ //! Contains all the interactions with [`Config::Currency`] to manipulate the underlying staking //! asset. -use frame_support::traits::{Currency, InspectLockableCurrency, LockableCurrency}; - -use crate::{BalanceOf, Config, NegativeImbalanceOf, PositiveImbalanceOf}; +use crate::{BalanceOf, Config, HoldReason, NegativeImbalanceOf, PositiveImbalanceOf}; +use frame_support::traits::{ + fungible::{ + hold::{Balanced as FunHoldBalanced, Inspect as FunHoldInspect, Mutate as FunHoldMutate}, + Balanced, Inspect as FunInspect, + }, + tokens::{Fortitude, Precision, Preservation}, +}; +use sp_runtime::{DispatchResult, Saturating}; /// Existential deposit for the chain. pub fn existential_deposit() -> BalanceOf { @@ -32,7 +38,7 @@ pub fn total_issuance() -> BalanceOf { T::Currency::total_issuance() } -/// Total balance of `who`. Includes both, free and reserved. +/// Total balance of `who`. Includes both free and staked. pub fn total_balance(who: &T::AccountId) -> BalanceOf { T::Currency::total_balance(who) } @@ -41,42 +47,65 @@ pub fn total_balance(who: &T::AccountId) -> BalanceOf { /// /// This includes balance free to stake along with any balance that is already staked. pub fn stakeable_balance(who: &T::AccountId) -> BalanceOf { - T::Currency::free_balance(who) + free_to_stake::(who).saturating_add(staked::(who)) } /// Balance of `who` that is currently at stake. /// -/// The staked amount is locked and cannot be transferred out of `who`s account. +/// The staked amount is on hold and cannot be transferred out of `who`s account. pub fn staked(who: &T::AccountId) -> BalanceOf { - T::Currency::balance_locked(crate::STAKING_ID, who) + T::Currency::balance_on_hold(&HoldReason::Staking.into(), who) +} + +/// Balance of who that can be staked additionally. +/// +/// Does not include the current stake. +pub fn free_to_stake(who: &T::AccountId) -> BalanceOf { + // since we want to be able to use frozen funds for staking, we force the reduction. + T::Currency::reducible_balance(who, Preservation::Preserve, Fortitude::Force) } /// Set balance that can be staked for `who`. /// -/// This includes any balance that is already staked. +/// If `Value` is lower than the current staked balance, the difference is unlocked. +/// +/// Should only be used with test. #[cfg(any(test, feature = "runtime-benchmarks"))] pub fn set_stakeable_balance(who: &T::AccountId, value: BalanceOf) { - T::Currency::make_free_balance_be(who, value); + use frame_support::traits::fungible::Mutate; + + // minimum free balance (non-staked) required to keep the account alive. + let ed = existential_deposit::(); + // currently on stake + let staked_balance = staked::(who); + + // if new value is greater than staked balance, mint some free balance. + if value > staked_balance { + let _ = T::Currency::set_balance(who, value - staked_balance + ed); + } else { + // else reduce the staked balance. + update_stake::(who, value).expect("can remove from what is staked"); + // burn all free, only leaving ED. + let _ = T::Currency::set_balance(who, ed); + } + + // ensure new stakeable balance same as desired `value`. + assert_eq!(stakeable_balance::(who), value); } /// Update `amount` at stake for `who`. /// /// Overwrites the existing stake amount. If passed amount is lower than the existing stake, the /// difference is unlocked. -pub fn update_stake(who: &T::AccountId, amount: BalanceOf) { - T::Currency::set_lock( - crate::STAKING_ID, - who, - amount, - frame_support::traits::WithdrawReasons::all(), - ); +pub fn update_stake(who: &T::AccountId, amount: BalanceOf) -> DispatchResult { + T::Currency::set_on_hold(&HoldReason::Staking.into(), who, amount) } -/// Kill the stake of `who`. +/// Release all staked amount to `who`. /// -/// All locked amount is unlocked. -pub fn kill_stake(who: &T::AccountId) { - T::Currency::remove_lock(crate::STAKING_ID, who); +/// Fails if there are consumers left on `who` that restricts it from being reaped. +pub fn kill_stake(who: &T::AccountId) -> DispatchResult { + T::Currency::release_all(&HoldReason::Staking.into(), who, Precision::BestEffort).map(|_| ()) } /// Slash the value from `who`. @@ -86,29 +115,32 @@ pub fn slash( who: &T::AccountId, value: BalanceOf, ) -> (NegativeImbalanceOf, BalanceOf) { - T::Currency::slash(who, value) + T::Currency::slash(&HoldReason::Staking.into(), who, value) } /// Mint `value` into an existing account `who`. /// /// This does not increase the total issuance. -pub fn mint_existing( +pub fn mint_into_existing( who: &T::AccountId, value: BalanceOf, ) -> Option> { - T::Currency::deposit_into_existing(who, value).ok() + // since the account already exists, we mint exact value even if value is below ED. + T::Currency::deposit(who, value, Precision::Exact).ok() } -/// Mint reward and create account for `who` if it does not exist. +/// Mint `value` and create account for `who` if it does not exist. /// -/// This does not increase the total issuance. +/// If value is below existential deposit, the account is not created. +/// +/// Note: This does not increase the total issuance. pub fn mint_creating(who: &T::AccountId, value: BalanceOf) -> PositiveImbalanceOf { - T::Currency::deposit_creating(who, value) + T::Currency::deposit(who, value, Precision::BestEffort).unwrap_or_default() } /// Deposit newly issued or slashed `value` into `who`. pub fn deposit_slashed(who: &T::AccountId, value: NegativeImbalanceOf) { - T::Currency::resolve_creating(who, value) + let _ = T::Currency::resolve(who, value); } /// Issue `value` increasing total issuance. @@ -121,5 +153,5 @@ pub fn issue(value: BalanceOf) -> NegativeImbalanceOf { /// Burn the amount from the total issuance. #[cfg(feature = "runtime-benchmarks")] pub fn burn(amount: BalanceOf) -> PositiveImbalanceOf { - T::Currency::burn(amount) + T::Currency::rescind(amount) } diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 79d8dd3fbc30..59d272168d68 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -257,7 +257,11 @@ mod benchmarks { .map(|l| l.active) .ok_or("ledger not created after")?; - let _ = asset::mint_existing::(&stash, max_additional).unwrap(); + let _ = asset::mint_into_existing::( + &stash, + max_additional + asset::existential_deposit::(), + ) + .unwrap(); whitelist_account!(stash); @@ -1133,6 +1137,23 @@ mod benchmarks { Ok(()) } + #[benchmark] + fn migrate_currency() -> Result<(), BenchmarkError> { + let (stash, _ctrl) = + create_stash_controller::(USER_SEED, 100, RewardDestination::Staked)?; + let stake = asset::staked::(&stash); + migrate_to_old_currency::(stash.clone()); + // no holds + assert!(asset::staked::(&stash).is_zero()); + whitelist_account!(stash); + + #[extrinsic_call] + _(RawOrigin::Signed(stash.clone()), stash.clone()); + + assert_eq!(asset::staked::(&stash), stake); + Ok(()) + } + impl_benchmark_test_suite!( Staking, crate::mock::ExtBuilder::default().has_stakers(true), diff --git a/substrate/frame/staking/src/ledger.rs b/substrate/frame/staking/src/ledger.rs index ac3be04cf607..1d66ebd27e9f 100644 --- a/substrate/frame/staking/src/ledger.rs +++ b/substrate/frame/staking/src/ledger.rs @@ -32,6 +32,7 @@ //! state consistency. use frame_support::{defensive, ensure, traits::Defensive}; +use sp_runtime::DispatchResult; use sp_staking::{StakingAccount, StakingInterface}; use crate::{ @@ -187,7 +188,8 @@ impl StakingLedger { // We skip locking virtual stakers. if !Pallet::::is_virtual_staker(&self.stash) { // for direct stakers, update lock on stash based on ledger. - asset::update_stake::(&self.stash, self.total); + asset::update_stake::(&self.stash, self.total) + .map_err(|_| Error::::NotEnoughFunds)?; } Ledger::::insert( @@ -250,7 +252,7 @@ impl StakingLedger { /// Clears all data related to a staking ledger and its bond in both [`Ledger`] and [`Bonded`] /// storage items and updates the stash staking lock. - pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error> { + pub(crate) fn kill(stash: &T::AccountId) -> DispatchResult { let controller = >::get(stash).ok_or(Error::::NotStash)?; >::get(&controller).ok_or(Error::::NotController).map(|ledger| { @@ -259,9 +261,9 @@ impl StakingLedger { >::remove(&stash); // kill virtual staker if it exists. - if >::take(&stash).is_none() { + if >::take(&ledger.stash).is_none() { // if not virtual staker, clear locks. - asset::kill_stake::(&ledger.stash); + asset::kill_stake::(&ledger.stash)?; } Ok(()) diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 6361663b2b1c..42230cb27b75 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -312,7 +312,8 @@ use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; use frame_support::{ defensive, defensive_assert, traits::{ - ConstU32, Currency, Defensive, DefensiveMax, DefensiveSaturating, Get, LockIdentifier, + tokens::fungible::{Credit, Debt}, + ConstU32, Defensive, DefensiveMax, DefensiveSaturating, Get, LockIdentifier, }, weights::Weight, BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, @@ -361,12 +362,9 @@ pub type RewardPoint = u32; /// The balance type of this pallet. pub type BalanceOf = ::CurrencyBalance; -type PositiveImbalanceOf = <::Currency as Currency< - ::AccountId, ->>::PositiveImbalance; -pub type NegativeImbalanceOf = <::Currency as Currency< - ::AccountId, ->>::NegativeImbalance; +type PositiveImbalanceOf = Debt<::AccountId, ::Currency>; +pub type NegativeImbalanceOf = + Credit<::AccountId, ::Currency>; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 769b84826b41..6346949576fa 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -25,8 +25,7 @@ use frame_election_provider_support::{ use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, traits::{ - ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Imbalance, LockableCurrency, - OnUnbalanced, OneSessionHandler, WithdrawReasons, + ConstU64, EitherOfDiverse, FindAuthor, Get, Imbalance, OnUnbalanced, OneSessionHandler, }, weights::constants::RocksDbWeight, }; @@ -264,6 +263,7 @@ pub(crate) const DISABLING_LIMIT_FACTOR: usize = 3; #[derive_impl(crate::config_preludes::TestDefaultConfig)] impl crate::pallet::pallet::Config for Test { + type OldCurrency = Balances; type Currency = Balances; type UnixTime = Timestamp; type RewardRemainder = RewardRemainderMock; @@ -432,6 +432,7 @@ impl ExtBuilder { fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let ed = ExistentialDeposit::get(); let _ = pallet_balances::GenesisConfig:: { balances: vec![ @@ -446,19 +447,23 @@ impl ExtBuilder { (40, self.balance_factor), (50, self.balance_factor), // stashes - (11, self.balance_factor * 1000), - (21, self.balance_factor * 2000), - (31, self.balance_factor * 2000), - (41, self.balance_factor * 2000), - (51, self.balance_factor * 2000), - (201, self.balance_factor * 2000), - (202, self.balance_factor * 2000), + // Note: Previously this pallet used locks and stakers could stake all their + // balance including ED. Now with holds, stakers are required to maintain + // (non-staked) ED in their accounts. Therefore, we drop an additional existential + // deposit to genesis stakers. + (11, self.balance_factor * 1000 + ed), + (21, self.balance_factor * 2000 + ed), + (31, self.balance_factor * 2000 + ed), + (41, self.balance_factor * 2000 + ed), + (51, self.balance_factor * 2000 + ed), + (201, self.balance_factor * 2000 + ed), + (202, self.balance_factor * 2000 + ed), // optional nominator - (100, self.balance_factor * 2000), - (101, self.balance_factor * 2000), + (100, self.balance_factor * 2000 + ed), + (101, self.balance_factor * 2000 + ed), // aux accounts (60, self.balance_factor), - (61, self.balance_factor * 2000), + (61, self.balance_factor * 2000 + ed), (70, self.balance_factor), (71, self.balance_factor * 2000), (80, self.balance_factor), @@ -575,7 +580,7 @@ pub(crate) fn current_era() -> EraIndex { } pub(crate) fn bond(who: AccountId, val: Balance) { - let _ = Balances::make_free_balance_be(&who, val); + let _ = asset::set_stakeable_balance::(&who, val); assert_ok!(Staking::bond(RuntimeOrigin::signed(who), val, RewardDestination::Stash)); } @@ -600,10 +605,6 @@ pub(crate) fn bond_virtual_nominator( val: Balance, target: Vec, ) { - // In a real scenario, `who` is a keyless account managed by another pallet which provides for - // it. - System::inc_providers(&who); - // Bond who virtually. assert_ok!(::virtual_bond(&who, val, &payee)); assert_ok!(Staking::nominate(RuntimeOrigin::signed(who), target)); @@ -809,7 +810,7 @@ pub(crate) fn bond_extra_no_checks(stash: &AccountId, amount: Balance) { let mut ledger = Ledger::::get(&controller).expect("ledger must exist to bond_extra"); let new_total = ledger.total + amount; - Balances::set_lock(crate::STAKING_ID, stash, new_total, WithdrawReasons::all()); + let _ = asset::update_stake::(stash, new_total); ledger.total = new_total; ledger.active = new_total; Ledger::::insert(controller, ledger); @@ -818,10 +819,10 @@ pub(crate) fn bond_extra_no_checks(stash: &AccountId, amount: Balance) { pub(crate) fn setup_double_bonded_ledgers() { let init_ledgers = Ledger::::iter().count(); - let _ = Balances::make_free_balance_be(&333, 2000); - let _ = Balances::make_free_balance_be(&444, 2000); - let _ = Balances::make_free_balance_be(&555, 2000); - let _ = Balances::make_free_balance_be(&777, 2000); + let _ = asset::set_stakeable_balance::(&333, 2000); + let _ = asset::set_stakeable_balance::(&444, 2000); + let _ = asset::set_stakeable_balance::(&555, 2000); + let _ = asset::set_stakeable_balance::(&777, 2000); assert_ok!(Staking::bond(RuntimeOrigin::signed(333), 10, RewardDestination::Staked)); assert_ok!(Staking::bond(RuntimeOrigin::signed(444), 20, RewardDestination::Staked)); @@ -923,5 +924,5 @@ pub(crate) fn staking_events_since_last_call() -> Vec> { } pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { - (Balances::free_balance(who), Balances::reserved_balance(who)) + (asset::stakeable_balance::(who), Balances::reserved_balance(who)) } diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 2ae925d03643..8c3ff23315a4 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -27,8 +27,8 @@ use frame_support::{ dispatch::WithPostDispatchInfo, pallet_prelude::*, traits::{ - Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance, Len, OnUnbalanced, - TryCollect, UnixTime, + Defensive, DefensiveSaturating, EstimateNextNewSession, Get, Imbalance, + InspectLockableCurrency, Len, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, }, weights::Weight, }; @@ -36,10 +36,9 @@ use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_session::historical; use sp_runtime::{ traits::{ - Bounded, CheckedAdd, CheckedSub, Convert, One, SaturatedConversion, Saturating, - StaticLookup, Zero, + Bounded, CheckedAdd, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero, }, - ArithmeticError, Perbill, Percent, + ArithmeticError, DispatchResult, Perbill, Percent, }; use sp_staking::{ currency_to_vote::CurrencyToVote, @@ -54,6 +53,7 @@ use crate::{ BalanceOf, EraInfo, EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure, LedgerIntegrityState, MaxNominationsOf, MaxWinnersOf, Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs, + STAKING_ID, }; use alloc::{boxed::Box, vec, vec::Vec}; @@ -96,10 +96,12 @@ impl Pallet { pub(crate) fn inspect_bond_state( stash: &T::AccountId, ) -> Result> { - let lock = asset::staked::(&stash); + // look at any old unmigrated lock as well. + let hold_or_lock = asset::staked::(&stash) + .max(T::OldCurrency::balance_locked(STAKING_ID, &stash).into()); let controller = >::get(stash).ok_or_else(|| { - if lock == Zero::zero() { + if hold_or_lock == Zero::zero() { Error::::NotStash } else { Error::::BadState @@ -111,7 +113,7 @@ impl Pallet { if ledger.stash != *stash { Ok(LedgerIntegrityState::Corrupted) } else { - if lock != ledger.total { + if hold_or_lock != ledger.total { Ok(LedgerIntegrityState::LockCorrupted) } else { Ok(LedgerIntegrityState::Ok) @@ -163,11 +165,7 @@ impl Pallet { additional } else { // additional amount or actual balance of stash whichever is lower. - additional.min( - asset::stakeable_balance::(stash) - .checked_sub(&ledger.total) - .ok_or(ArithmeticError::Overflow)?, - ) + additional.min(asset::free_to_stake::(stash)) }; ledger.total = ledger.total.checked_add(&extra).ok_or(ArithmeticError::Overflow)?; @@ -416,12 +414,12 @@ impl Pallet { let dest = Self::payee(StakingAccount::Stash(stash.clone()))?; let maybe_imbalance = match dest { - RewardDestination::Stash => asset::mint_existing::(stash, amount), + RewardDestination::Stash => asset::mint_into_existing::(stash, amount), RewardDestination::Staked => Self::ledger(Stash(stash.clone())) .and_then(|mut ledger| { ledger.active += amount; ledger.total += amount; - let r = asset::mint_existing::(stash, amount); + let r = asset::mint_into_existing::(stash, amount); let _ = ledger .update() @@ -799,8 +797,6 @@ impl Pallet { Self::do_remove_validator(&stash); Self::do_remove_nominator(&stash); - frame_system::Pallet::::dec_consumers(&stash); - Ok(()) } @@ -1163,6 +1159,81 @@ impl Pallet { ) -> Exposure> { EraInfo::::get_full_exposure(era, account) } + + pub(super) fn do_migrate_currency(stash: &T::AccountId) -> DispatchResult { + if Self::is_virtual_staker(stash) { + return Self::do_migrate_virtual_staker(stash); + } + + let ledger = Self::ledger(Stash(stash.clone()))?; + let staked: BalanceOf = T::OldCurrency::balance_locked(STAKING_ID, stash).into(); + ensure!(!staked.is_zero(), Error::::AlreadyMigrated); + ensure!(ledger.total == staked, Error::::BadState); + + // remove old staking lock + T::OldCurrency::remove_lock(STAKING_ID, &stash); + + // check if we can hold all stake. + let max_hold = asset::free_to_stake::(&stash); + let force_withdraw = if max_hold >= staked { + // this means we can hold all stake. yay! + asset::update_stake::(&stash, staked)?; + Zero::zero() + } else { + // if we are here, it means we cannot hold all user stake. We will do a force withdraw + // from ledger, but that's okay since anyways user do not have funds for it. + let force_withdraw = staked.saturating_sub(max_hold); + + // we ignore if active is 0. It implies the locked amount is not actively staked. The + // account can still get away from potential slash but we can't do much better here. + StakingLedger { + total: max_hold, + active: ledger.active.saturating_sub(force_withdraw), + // we are not changing the stash, so we can keep the stash. + ..ledger + } + .update()?; + force_withdraw + }; + + // Get rid of the extra consumer we used to have with OldCurrency. + frame_system::Pallet::::dec_consumers(&stash); + + Self::deposit_event(Event::::CurrencyMigrated { stash: stash.clone(), force_withdraw }); + Ok(()) + } + + fn do_migrate_virtual_staker(stash: &T::AccountId) -> DispatchResult { + // Funds for virtual stakers not managed/held by this pallet. We only need to clear + // the extra consumer we used to have with OldCurrency. + frame_system::Pallet::::dec_consumers(&stash); + + // The delegation system that manages the virtual staker needed to increment provider + // previously because of the consumer needed by this pallet. In reality, this stash + // is just a key for managing the ledger and the account does not need to hold any + // balance or exist. We decrement this provider. + let actual_providers = frame_system::Pallet::::providers(stash); + + let expected_providers = + // provider is expected to be 1 but someone can always transfer some free funds to + // these accounts, increasing the provider. + if asset::free_to_stake::(&stash) >= asset::existential_deposit::() { + 2 + } else { + 1 + }; + + // We should never have more than expected providers. + ensure!(actual_providers <= expected_providers, Error::::BadState); + + // if actual provider is less than expected, it is already migrated. + ensure!(actual_providers == expected_providers, Error::::AlreadyMigrated); + + // dec provider + let _ = frame_system::Pallet::::dec_providers(&stash)?; + + return Ok(()) + } } impl Pallet { @@ -1925,9 +1996,10 @@ impl StakingInterface for Pallet { } impl sp_staking::StakingUnchecked for Pallet { - fn migrate_to_virtual_staker(who: &Self::AccountId) { - asset::kill_stake::(who); + fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult { + asset::kill_stake::(who)?; VirtualStakers::::insert(who, ()); + Ok(()) } /// Virtually bonds `keyless_who` to `payee` with `value`. @@ -1945,9 +2017,6 @@ impl sp_staking::StakingUnchecked for Pallet { // check if payee not same as who. ensure!(keyless_who != payee, Error::::RewardDestinationRestricted); - // mark this pallet as consumer of `who`. - frame_system::Pallet::::inc_consumers(&keyless_who).map_err(|_| Error::::BadState)?; - // mark who as a virtual staker. VirtualStakers::::insert(keyless_who, ()); @@ -1959,11 +2028,13 @@ impl sp_staking::StakingUnchecked for Pallet { Ok(()) } + /// Only meant to be used in tests. #[cfg(feature = "runtime-benchmarks")] fn migrate_to_direct_staker(who: &Self::AccountId) { assert!(VirtualStakers::::contains_key(who)); let ledger = StakingLedger::::get(Stash(who.clone())).unwrap(); - asset::update_stake::(who, ledger.total); + let _ = asset::update_stake::(who, ledger.total) + .expect("funds must be transferred to stash"); VirtualStakers::::remove(who); } } @@ -2100,7 +2171,7 @@ impl Pallet { if VirtualStakers::::contains_key(stash.clone()) { ensure!( asset::staked::(&stash) == Zero::zero(), - "virtual stakers should not have any locked balance" + "virtual stakers should not have any staked balance" ); ensure!( >::get(stash.clone()).unwrap() == stash.clone(), @@ -2128,7 +2199,7 @@ impl Pallet { } else { ensure!( Self::inspect_bond_state(&stash) == Ok(LedgerIntegrityState::Ok), - "bond, ledger and/or staking lock inconsistent for a bonded stash." + "bond, ledger and/or staking hold inconsistent for a bonded stash." ); } diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index b3f8c18f704c..7d5da9ea0c49 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -25,8 +25,12 @@ use frame_election_provider_support::{ use frame_support::{ pallet_prelude::*, traits::{ + fungible::{ + hold::{Balanced as FunHoldBalanced, Mutate as FunHoldMutate}, + Mutate as FunMutate, + }, Defensive, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, - InspectLockableCurrency, LockableCurrency, OnUnbalanced, UnixTime, + InspectLockableCurrency, OnUnbalanced, UnixTime, }, weights::Weight, BoundedVec, @@ -89,13 +93,27 @@ pub mod pallet { #[pallet::config(with_default)] pub trait Config: frame_system::Config { + /// The old trait for staking balance. Deprecated and only used for migrating old ledgers. + #[pallet::no_default] + type OldCurrency: InspectLockableCurrency< + Self::AccountId, + Moment = BlockNumberFor, + Balance = Self::CurrencyBalance, + >; + /// The staking balance. #[pallet::no_default] - type Currency: LockableCurrency< + type Currency: FunHoldMutate< Self::AccountId, - Moment = BlockNumberFor, + Reason = Self::RuntimeHoldReason, Balance = Self::CurrencyBalance, - > + InspectLockableCurrency; + > + FunMutate + + FunHoldBalanced; + + /// Overarching hold reason. + #[pallet::no_default_bounds] + type RuntimeHoldReason: From; + /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to /// `From`. type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned @@ -106,6 +124,8 @@ pub mod pallet { + Default + From + TypeInfo + + Send + + Sync + MaxEncodedLen; /// Time used for computing era duration. /// @@ -309,6 +329,14 @@ pub mod pallet { type WeightInfo: WeightInfo; } + /// A reason for placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// Funds on stake by a nominator or a validator. + #[codec(index = 0)] + Staking, + } + /// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`]. pub mod config_preludes { use super::*; @@ -327,6 +355,8 @@ pub mod pallet { impl DefaultConfig for TestDefaultConfig { #[inject_runtime_type] type RuntimeEvent = (); + #[inject_runtime_type] + type RuntimeHoldReason = (); type CurrencyBalance = u128; type CurrencyToVote = (); type NominationsQuota = crate::FixedNominationsQuota<16>; @@ -765,7 +795,7 @@ pub mod pallet { status ); assert!( - asset::stakeable_balance::(stash) >= balance, + asset::free_to_stake::(stash) >= balance, "Stash does not have enough balance to bond." ); frame_support::assert_ok!(>::bond( @@ -858,6 +888,9 @@ pub mod pallet { ValidatorDisabled { stash: T::AccountId }, /// Validator has been re-enabled. ValidatorReenabled { stash: T::AccountId }, + /// Staking balance migrated from locks to holds, with any balance that could not be held + /// is force withdrawn. + CurrencyMigrated { stash: T::AccountId, force_withdraw: BalanceOf }, } #[pallet::error] @@ -929,6 +962,10 @@ pub mod pallet { NotEnoughFunds, /// Operation not allowed for virtual stakers. VirtualStakerNotAllowed, + /// Stash could not be reaped as other pallet might depend on it. + CannotReapStash, + /// The stake of this account is already migrated to `Fungible` holds. + AlreadyMigrated, } #[pallet::hooks] @@ -1172,10 +1209,7 @@ pub mod pallet { return Err(Error::::InsufficientBond.into()) } - // Would fail if account has no provider. - frame_system::Pallet::::inc_consumers(&stash)?; - - let stash_balance = asset::stakeable_balance::(&stash); + let stash_balance = asset::free_to_stake::(&stash); let value = value.min(stash_balance); Self::deposit_event(Event::::Bonded { stash: stash.clone(), amount: value }); let ledger = StakingLedger::::new(stash.clone(), value); @@ -2231,8 +2265,8 @@ pub mod pallet { let new_total = if let Some(total) = maybe_total { let new_total = total.min(stash_balance); - // enforce lock == ledger.amount. - asset::update_stake::(&stash, new_total); + // enforce hold == ledger.amount. + asset::update_stake::(&stash, new_total)?; new_total } else { current_lock @@ -2259,13 +2293,13 @@ pub mod pallet { // to enforce a new ledger.total and staking lock for this stash. let new_total = maybe_total.ok_or(Error::::CannotRestoreLedger)?.min(stash_balance); - asset::update_stake::(&stash, new_total); + asset::update_stake::(&stash, new_total)?; Ok((stash.clone(), new_total)) }, Err(Error::::BadState) => { // the stash and ledger do not exist but lock is lingering. - asset::kill_stake::(&stash); + asset::kill_stake::(&stash)?; ensure!( Self::inspect_bond_state(&stash) == Err(Error::::NotStash), Error::::BadState @@ -2291,6 +2325,26 @@ pub mod pallet { ); Ok(()) } + + /// Migrates permissionlessly a stash from locks to holds. + /// + /// This removes the old lock on the stake and creates a hold on it atomically. If all + /// stake cannot be held, the best effort is made to hold as much as possible. The remaining + /// stake is removed from the ledger. + /// + /// The fee is waived if the migration is successful. + #[pallet::call_index(30)] + #[pallet::weight(T::WeightInfo::migrate_currency())] + pub fn migrate_currency( + origin: OriginFor, + stash: T::AccountId, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + Self::do_migrate_currency(&stash)?; + + // Refund the transaction fee if successful. + Ok(Pays::No.into()) + } } } diff --git a/substrate/frame/staking/src/testing_utils.rs b/substrate/frame/staking/src/testing_utils.rs index 81337710aa90..dfd5422106c0 100644 --- a/substrate/frame/staking/src/testing_utils.rs +++ b/substrate/frame/staking/src/testing_utils.rs @@ -238,3 +238,21 @@ pub fn create_validators_with_nominators_for_era( pub fn current_era() -> EraIndex { CurrentEra::::get().unwrap_or(0) } + +pub fn migrate_to_old_currency(who: T::AccountId) { + use frame_support::traits::LockableCurrency; + let staked = asset::staked::(&who); + + // apply locks (this also adds a consumer). + T::OldCurrency::set_lock( + STAKING_ID, + &who, + staked, + frame_support::traits::WithdrawReasons::all(), + ); + // remove holds. + asset::kill_stake::(&who).expect("remove hold failed"); + + // replicate old behaviour of explicit increment of consumer. + frame_system::Pallet::::inc_consumers(&who).expect("increment consumer failed"); +} diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 6c2335e1aac8..908415143994 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -26,8 +26,12 @@ use frame_election_provider_support::{ use frame_support::{ assert_noop, assert_ok, assert_storage_noop, dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, + hypothetically, pallet_prelude::*, - traits::{Currency, Get, ReservableCurrency}, + traits::{ + fungible::Inspect, Currency, Get, InspectLockableCurrency, LockableCurrency, + ReservableCurrency, WithdrawReasons, + }, }; use mock::*; @@ -108,7 +112,7 @@ fn force_unstake_works() { // Cant transfer assert_noop!( Balances::transfer_allow_death(RuntimeOrigin::signed(11), 1, 10), - TokenError::Frozen, + TokenError::FundsUnavailable, ); // Force unstake requires root. assert_noop!(Staking::force_unstake(RuntimeOrigin::signed(11), 11, 2), BadOrigin); @@ -229,8 +233,7 @@ fn basic_setup_works() { assert_eq!(active_era(), 0); // Account 10 has `balance_factor` free balance - assert_eq!(asset::stakeable_balance::(&10), 1); - assert_eq!(asset::stakeable_balance::(&10), 1); + assert_eq!(Balances::balance(&10), 1); // New era is not being forced assert_eq!(ForceEra::::get(), Forcing::NotForcing); @@ -360,8 +363,16 @@ fn rewards_should_work() { remainder: maximum_payout - total_payout_0 } ); + + // make note of total issuance before rewards. + let total_issuance_0 = asset::total_issuance::(); + mock::make_all_reward_payment(0); + // total issuance should have increased + let total_issuance_1 = asset::total_issuance::(); + assert_eq!(total_issuance_1, total_issuance_0 + total_payout_0); + assert_eq_error_rate!( asset::total_balance::(&11), init_balance_11 + part_for_11 * total_payout_0 * 2 / 3, @@ -401,6 +412,7 @@ fn rewards_should_work() { ); mock::make_all_reward_payment(1); + assert_eq!(asset::total_issuance::(), total_issuance_1 + total_payout_1); assert_eq_error_rate!( asset::total_balance::(&11), init_balance_11 + part_for_11 * (total_payout_0 * 2 / 3 + total_payout_1), @@ -490,7 +502,7 @@ fn staking_should_work() { } ); // e.g. it cannot reserve more than 500 that it has free from the total 2000 - assert_noop!(Balances::reserve(&3, 501), BalancesError::::LiquidityRestrictions); + assert_noop!(Balances::reserve(&3, 501), DispatchError::ConsumerRemaining); assert_ok!(Balances::reserve(&3, 409)); }); } @@ -689,7 +701,7 @@ fn nominating_and_rewards_should_work() { ); // Nominator 3: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 21]'s reward. ==> // 2/9 + 3/11 - assert_eq!(asset::total_balance::(&3), initial_balance); + assert_eq!(asset::stakeable_balance::(&3), initial_balance); // 333 is the reward destination for 3. assert_eq_error_rate!( asset::total_balance::(&333), @@ -992,9 +1004,9 @@ fn cannot_transfer_staked_balance() { ExtBuilder::default().nominate(false).build_and_execute(|| { // Confirm account 11 is stashed assert_eq!(Staking::bonded(&11), Some(11)); - // Confirm account 11 has some free balance + // Confirm account 11 has some stakeable balance assert_eq!(asset::stakeable_balance::(&11), 1000); - // Confirm account 11 (via controller) is totally staked + // Confirm account 11 is totally staked assert_eq!(Staking::eras_stakers(active_era(), &11).total, 1000); // Confirm account 11 cannot transfer as a result assert_noop!( @@ -1021,11 +1033,12 @@ fn cannot_transfer_staked_balance_2() { assert_eq!(asset::stakeable_balance::(&21), 2000); // Confirm account 21 (via controller) is totally staked assert_eq!(Staking::eras_stakers(active_era(), &21).total, 1000); - // Confirm account 21 can transfer at most 1000 + // Confirm account 21 cannot transfer more than 1000 assert_noop!( Balances::transfer_allow_death(RuntimeOrigin::signed(21), 21, 1001), TokenError::Frozen, ); + // Confirm account 21 needs to leave at least ED in free balance to be able to transfer assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(21), 21, 1000)); }); } @@ -1036,17 +1049,61 @@ fn cannot_reserve_staked_balance() { ExtBuilder::default().build_and_execute(|| { // Confirm account 11 is stashed assert_eq!(Staking::bonded(&11), Some(11)); - // Confirm account 11 has some free balance - assert_eq!(asset::stakeable_balance::(&11), 1000); - // Confirm account 11 (via controller 10) is totally staked - assert_eq!(Staking::eras_stakers(active_era(), &11).own, 1000); + // Confirm account 11 is totally staked + assert_eq!(asset::staked::(&11), 1000); + // Confirm account 11 cannot reserve as a result - assert_noop!(Balances::reserve(&11, 1), BalancesError::::LiquidityRestrictions); + assert_noop!(Balances::reserve(&11, 2), BalancesError::::InsufficientBalance); + assert_noop!(Balances::reserve(&11, 1), DispatchError::ConsumerRemaining); // Give account 11 extra free balance - let _ = asset::set_stakeable_balance::(&11, 10000); + let _ = asset::set_stakeable_balance::(&11, 1000 + 1000); + assert_eq!(asset::free_to_stake::(&11), 1000); + // Confirm account 11 can now reserve balance - assert_ok!(Balances::reserve(&11, 1)); + assert_ok!(Balances::reserve(&11, 500)); + + // free to stake balance has reduced + assert_eq!(asset::free_to_stake::(&11), 500); + }); +} + +#[test] +fn locked_balance_can_be_staked() { + // Checks that a bonded account cannot reserve balance from free balance + ExtBuilder::default().build_and_execute(|| { + // Confirm account 11 is stashed + assert_eq!(Staking::bonded(&11), Some(11)); + assert_eq!(asset::staked::(&11), 1000); + assert_eq!(asset::free_to_stake::(&11), 0); + + // add some staking balance to 11 + let _ = asset::set_stakeable_balance::(&11, 1000 + 1000); + // free to stake is 1000 + assert_eq!(asset::free_to_stake::(&11), 1000); + + // lock some balance + Balances::set_lock(*b"somelock", &11, 500, WithdrawReasons::all()); + + // locked balance still available for staking + assert_eq!(asset::free_to_stake::(&11), 1000); + + // can stake free balance + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 500)); + assert_eq!(asset::staked::(&11), 1500); + + // Can stake the locked balance + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 500)); + assert_eq!(asset::staked::(&11), 2000); + // no balance left to stake + assert_eq!(asset::free_to_stake::(&11), 0); + + // this does not fail if someone tries to stake more than free balance but just stakes + // whatever is available. (not sure if that is the best way, but we keep it backward + // compatible) + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 10)); + // no extra balance staked. + assert_eq!(asset::staked::(&11), 2000); }); } @@ -1057,9 +1114,9 @@ fn reward_destination_works() { // Check that account 11 is a validator assert!(Session::validators().contains(&11)); // Check the balance of the validator account - assert_eq!(asset::stakeable_balance::(&10), 1); + assert_eq!(asset::total_balance::(&10), 1); // Check the balance of the stash account - assert_eq!(asset::stakeable_balance::(&11), 1000); + assert_eq!(asset::total_balance::(&11), 1001); // Check how much is at stake assert_eq!( Staking::ledger(11.into()).unwrap(), @@ -1294,12 +1351,12 @@ fn bond_extra_and_withdraw_unbonded_works() { // Give account 11 some large free balance greater than total let _ = asset::set_stakeable_balance::(&11, 1000000); + // ensure it has the correct balance. + assert_eq!(asset::stakeable_balance::(&11), 1000000); + // Initial config should be correct assert_eq!(active_era(), 0); - // check the balance of a validator accounts. - assert_eq!(asset::total_balance::(&11), 1000000); - // confirm that 10 is a normal validator and gets paid at the end of the era. mock::start_active_era(1); @@ -2077,7 +2134,7 @@ fn bond_with_no_staked_value() { ); // bonded with absolute minimum value possible. assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 5, RewardDestination::Account(1))); - assert_eq!(pallet_balances::Locks::::get(&1)[0].amount, 5); + assert_eq!(pallet_balances::Holds::::get(&1)[0].amount, 5); // unbonding even 1 will cause all to be unbonded. assert_ok!(Staking::unbond(RuntimeOrigin::signed(1), 1)); @@ -2098,14 +2155,14 @@ fn bond_with_no_staked_value() { // not yet removed. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); assert!(Staking::ledger(1.into()).is_ok()); - assert_eq!(pallet_balances::Locks::::get(&1)[0].amount, 5); + assert_eq!(pallet_balances::Holds::::get(&1)[0].amount, 5); mock::start_active_era(3); // poof. Account 1 is removed from the staking system. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); assert!(Staking::ledger(1.into()).is_err()); - assert_eq!(pallet_balances::Locks::::get(&1).len(), 0); + assert_eq!(pallet_balances::Holds::::get(&1).len(), 0); }); } @@ -2338,9 +2395,20 @@ fn reward_validator_slashing_validator_does_not_overflow() { EraInfo::::set_exposure(0, &11, exposure); ErasValidatorReward::::insert(0, stake); assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 0, 0)); - assert_eq!(asset::total_balance::(&11), stake * 2); + assert_eq!(asset::stakeable_balance::(&11), stake * 2); - // Set staker + // ensure ledger has `stake` and no more. + Ledger::::insert( + 11, + StakingLedgerInspect { + stash: 11, + total: stake, + active: stake, + unlocking: Default::default(), + legacy_claimed_rewards: bounded_vec![1], + }, + ); + // Set staker (unsafe, can reduce balance below actual stake) let _ = asset::set_stakeable_balance::(&11, stake); let _ = asset::set_stakeable_balance::(&2, stake); @@ -2366,8 +2434,8 @@ fn reward_validator_slashing_validator_does_not_overflow() { &[Perbill::from_percent(100)], ); - assert_eq!(asset::total_balance::(&11), stake - 1); - assert_eq!(asset::total_balance::(&2), 1); + assert_eq!(asset::stakeable_balance::(&11), stake - 1); + assert_eq!(asset::stakeable_balance::(&2), 1); }) } @@ -2627,8 +2695,8 @@ fn reporters_receive_their_slice() { // 50% * (10% * initial_balance / 2) let reward = (initial_balance / 20) / 2; let reward_each = reward / 2; // split into two pieces. - assert_eq!(asset::stakeable_balance::(&1), 10 + reward_each); - assert_eq!(asset::stakeable_balance::(&2), 20 + reward_each); + assert_eq!(asset::total_balance::(&1), 10 + reward_each); + assert_eq!(asset::total_balance::(&2), 20 + reward_each); }); } @@ -2653,7 +2721,7 @@ fn subsequent_reports_in_same_span_pay_out_less() { // F1 * (reward_proportion * slash - 0) // 50% * (10% * initial_balance * 20%) let reward = (initial_balance / 5) / 20; - assert_eq!(asset::stakeable_balance::(&1), 10 + reward); + assert_eq!(asset::total_balance::(&1), 10 + reward); on_offence_now( &[OffenceDetails { @@ -2668,7 +2736,7 @@ fn subsequent_reports_in_same_span_pay_out_less() { // F1 * (reward_proportion * slash - prior_payout) // 50% * (10% * (initial_balance / 2) - prior_payout) let reward = ((initial_balance / 20) - prior_payout) / 2; - assert_eq!(asset::stakeable_balance::(&1), 10 + prior_payout + reward); + assert_eq!(asset::total_balance::(&1), 10 + prior_payout + reward); }); } @@ -2812,8 +2880,9 @@ fn garbage_collection_after_slashing() { // validator and nominator slash in era are garbage-collected by era change, // so we don't test those here. - assert_eq!(asset::stakeable_balance::(&11), 2); - assert_eq!(asset::total_balance::(&11), 2); + assert_eq!(asset::stakeable_balance::(&11), 0); + // Non staked balance is not touched. + assert_eq!(asset::total_balance::(&11), ExistentialDeposit::get()); let slashing_spans = SlashingSpans::::get(&11).unwrap(); assert_eq!(slashing_spans.iter().count(), 2); @@ -6092,7 +6161,7 @@ fn nomination_quota_max_changes_decoding() { .add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) .add_staker(30, 330, 10, StakerStatus::Nominator(vec![1, 2, 3, 4])) .add_staker(50, 550, 10, StakerStatus::Nominator(vec![1, 2, 3, 4])) - .balance_factor(10) + .balance_factor(11) .build_and_execute(|| { // pre-condition. assert_eq!(MaxNominationsOf::::get(), 16); @@ -6208,240 +6277,248 @@ fn force_apply_min_commission_works() { #[test] fn proportional_slash_stop_slashing_if_remaining_zero() { - let c = |era, value| UnlockChunk:: { era, value }; + ExtBuilder::default().nominate(true).build_and_execute(|| { + let c = |era, value| UnlockChunk:: { era, value }; - // we have some chunks, but they are not affected. - let unlocking = bounded_vec![c(1, 10), c(2, 10)]; + // we have some chunks, but they are not affected. + let unlocking = bounded_vec![c(1, 10), c(2, 10)]; - // Given - let mut ledger = StakingLedger::::new(123, 20); - ledger.total = 40; - ledger.unlocking = unlocking; + // Given + let mut ledger = StakingLedger::::new(123, 20); + ledger.total = 40; + ledger.unlocking = unlocking; - assert_eq!(BondingDuration::get(), 3); + assert_eq!(BondingDuration::get(), 3); - // should not slash more than the amount requested, by accidentally slashing the first chunk. - assert_eq!(ledger.slash(18, 1, 0), 18); + // should not slash more than the amount requested, by accidentally slashing the first + // chunk. + assert_eq!(ledger.slash(18, 1, 0), 18); + }); } #[test] fn proportional_ledger_slash_works() { - let c = |era, value| UnlockChunk:: { era, value }; - // Given - let mut ledger = StakingLedger::::new(123, 10); - assert_eq!(BondingDuration::get(), 3); - - // When we slash a ledger with no unlocking chunks - assert_eq!(ledger.slash(5, 1, 0), 5); - // Then - assert_eq!(ledger.total, 5); - assert_eq!(ledger.active, 5); - assert_eq!(LedgerSlashPerEra::get().0, 5); - assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - - // When we slash a ledger with no unlocking chunks and the slash amount is greater then the - // total - assert_eq!(ledger.slash(11, 1, 0), 5); - // Then - assert_eq!(ledger.total, 0); - assert_eq!(ledger.active, 0); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - - // Given - ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)]; - ledger.total = 2 * 10; - ledger.active = 0; - // When all the chunks overlap with the slash eras - assert_eq!(ledger.slash(20, 0, 0), 20); - // Then - assert_eq!(ledger.unlocking, vec![]); - assert_eq!(ledger.total, 0); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)])); - - // Given - ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; - ledger.total = 4 * 100; - ledger.active = 0; - // When the first 2 chunks don't overlap with the affected range of unlock eras. - assert_eq!(ledger.slash(140, 0, 3), 140); - // Then - assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]); - assert_eq!(ledger.total, 4 * 100 - 140); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)])); - - // Given - ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; - ledger.total = 4 * 100; - ledger.active = 0; - // When the first 2 chunks don't overlap with the affected range of unlock eras. - assert_eq!(ledger.slash(15, 0, 3), 15); - // Then - assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 100 - 8), c(7, 100 - 7)]); - assert_eq!(ledger.total, 4 * 100 - 15); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 92), (7, 93)])); - - // Given - ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; - ledger.active = 500; - // 900 - ledger.total = 40 + 10 + 100 + 250 + 500; - // When we have a partial slash that touches all chunks - assert_eq!(ledger.slash(900 / 2, 0, 0), 450); - // Then - assert_eq!(ledger.active, 500 / 2); - assert_eq!(ledger.unlocking, vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]); - assert_eq!(ledger.total, 900 / 2); - assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); - assert_eq!( - LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)]) - ); + ExtBuilder::default().nominate(true).build_and_execute(|| { + let c = |era, value| UnlockChunk:: { era, value }; + // Given + let mut ledger = StakingLedger::::new(123, 10); + assert_eq!(BondingDuration::get(), 3); - // slash 1/4th with not chunk. - ledger.unlocking = bounded_vec![]; - ledger.active = 500; - ledger.total = 500; - // When we have a partial slash that touches all chunks - assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4); - // Then - assert_eq!(ledger.active, 3 * 500 / 4); - assert_eq!(ledger.unlocking, vec![]); - assert_eq!(ledger.total, ledger.active); - assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4); - assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - - // Given we have the same as above, - ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; - ledger.active = 500; - ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - assert_eq!(ledger.total, 900); - // When we have a higher min balance - assert_eq!( - ledger.slash( - 900 / 2, - 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to - * get swept */ - 0 - ), - 450 - ); - assert_eq!(ledger.active, 500 / 2); - // the last chunk was not slashed 50% like all the rest, because some other earlier chunks got - // dusted. - assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 150)]); - assert_eq!(ledger.total, 900 / 2); - assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); - assert_eq!( - LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 150)]) - ); + // When we slash a ledger with no unlocking chunks + assert_eq!(ledger.slash(5, 1, 0), 5); + // Then + assert_eq!(ledger.total, 5); + assert_eq!(ledger.active, 5); + assert_eq!(LedgerSlashPerEra::get().0, 5); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // When we slash a ledger with no unlocking chunks and the slash amount is greater then the + // total + assert_eq!(ledger.slash(11, 1, 0), 5); + // Then + assert_eq!(ledger.total, 0); + assert_eq!(ledger.active, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); - // Given - // slash order --------------------NA--------2----------0----------1---- - ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; - ledger.active = 500; - ledger.total = 40 + 10 + 100 + 250 + 500; // 900 - assert_eq!( - ledger.slash( - 500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2 - 0, - 3 /* slash era 6 first, so the affected parts are era 6, era 7 and - * ledge.active. This will cause the affected to go to zero, and then we will - * start slashing older chunks */ - ), - 500 + 250 + 10 + 100 / 2 - ); - // Then - assert_eq!(ledger.active, 0); - assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]); - assert_eq!(ledger.total, 90); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)])); - - // Given - // iteration order------------------NA---------2----------0----------1---- - ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; - ledger.active = 100; - ledger.total = 5 * 100; - // When - assert_eq!( - ledger.slash( - 351, // active + era 6 + era 7 + era 5 / 2 + 1 - 50, // min balance - everything slashed below 50 will get dusted - 3 /* slash era 3+3 first, so the affected parts are era 6, era 7 and - * ledge.active. This will cause the affected to go to zero, and then we will - * start slashing older chunks */ - ), - 400 - ); - // Then - assert_eq!(ledger.active, 0); - assert_eq!(ledger.unlocking, vec![c(4, 100)]); - assert_eq!(ledger.total, 100); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)])); - - // Tests for saturating arithmetic - - // Given - let slash = u64::MAX as Balance * 2; - // The value of the other parts of ledger that will get slashed - let value = slash - (10 * 4); - - ledger.active = 10; - ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)]; - ledger.total = value + 40; - // When - let slash_amount = ledger.slash(slash, 0, 0); - assert_eq_error_rate!(slash_amount, slash, 5); - // Then - assert_eq!(ledger.active, 0); // slash of 9 - assert_eq!(ledger.unlocking, vec![]); - assert_eq!(ledger.total, 0); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)])); - - // Given - use sp_runtime::PerThing as _; - let slash = u64::MAX as Balance * 2; - let value = u64::MAX as Balance * 2; - let unit = 100; - // slash * value that will saturate - assert!(slash.checked_mul(value).is_none()); - // but slash * unit won't. - assert!(slash.checked_mul(unit).is_some()); - ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)]; - //--------------------------------------note value^^^ - ledger.active = unit; - ledger.total = unit * 4 + value; - // When - assert_eq!(ledger.slash(slash, 0, 0), slash); - // Then - // The amount slashed out of `unit` - let affected_balance = value + unit * 4; - let ratio = - Perquintill::from_rational_with_rounding(slash, affected_balance, Rounding::Up).unwrap(); - // `unit` after the slash is applied - let unit_slashed = { - let unit_slash = ratio.mul_ceil(unit); - unit - unit_slash - }; - let value_slashed = { - let value_slash = ratio.mul_ceil(value); - value - value_slash - }; - assert_eq!(ledger.active, unit_slashed); - assert_eq!(ledger.unlocking, vec![c(5, value_slashed), c(7, 32)]); - assert_eq!(ledger.total, value_slashed + 32); - assert_eq!(LedgerSlashPerEra::get().0, 0); - assert_eq!( - LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 32)]) - ); + // Given + ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)]; + ledger.total = 2 * 10; + ledger.active = 0; + // When all the chunks overlap with the slash eras + assert_eq!(ledger.slash(20, 0, 0), 20); + // Then + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.total = 4 * 100; + ledger.active = 0; + // When the first 2 chunks don't overlap with the affected range of unlock eras. + assert_eq!(ledger.slash(140, 0, 3), 140); + // Then + assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]); + assert_eq!(ledger.total, 4 * 100 - 140); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.total = 4 * 100; + ledger.active = 0; + // When the first 2 chunks don't overlap with the affected range of unlock eras. + assert_eq!(ledger.slash(15, 0, 3), 15); + // Then + assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 100 - 8), c(7, 100 - 7)]); + assert_eq!(ledger.total, 4 * 100 - 15); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 92), (7, 93)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + // 900 + ledger.total = 40 + 10 + 100 + 250 + 500; + // When we have a partial slash that touches all chunks + assert_eq!(ledger.slash(900 / 2, 0, 0), 450); + // Then + assert_eq!(ledger.active, 500 / 2); + assert_eq!( + ledger.unlocking, + vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)] + ); + assert_eq!(ledger.total, 900 / 2); + assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)]) + ); + + // slash 1/4th with not chunk. + ledger.unlocking = bounded_vec![]; + ledger.active = 500; + ledger.total = 500; + // When we have a partial slash that touches all chunks + assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4); + // Then + assert_eq!(ledger.active, 3 * 500 / 4); + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, ledger.active); + assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // Given we have the same as above, + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + assert_eq!(ledger.total, 900); + // When we have a higher min balance + assert_eq!( + ledger.slash( + 900 / 2, + 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it + * to get swept */ + 0 + ), + 450 + ); + assert_eq!(ledger.active, 500 / 2); + // the last chunk was not slashed 50% like all the rest, because some other earlier chunks + // got dusted. + assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 150)]); + assert_eq!(ledger.total, 900 / 2); + assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 150)]) + ); + + // Given + // slash order --------------------NA--------2----------0----------1---- + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + assert_eq!( + ledger.slash( + 500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2 + 0, + 3 /* slash era 6 first, so the affected parts are era 6, era 7 and + * ledge.active. This will cause the affected to go to zero, and then we will + * start slashing older chunks */ + ), + 500 + 250 + 10 + 100 / 2 + ); + // Then + assert_eq!(ledger.active, 0); + assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]); + assert_eq!(ledger.total, 90); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)])); + + // Given + // iteration order------------------NA---------2----------0----------1---- + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.active = 100; + ledger.total = 5 * 100; + // When + assert_eq!( + ledger.slash( + 351, // active + era 6 + era 7 + era 5 / 2 + 1 + 50, // min balance - everything slashed below 50 will get dusted + 3 /* slash era 3+3 first, so the affected parts are era 6, era 7 and + * ledge.active. This will cause the affected to go to zero, and then we + * will start slashing older chunks */ + ), + 400 + ); + // Then + assert_eq!(ledger.active, 0); + assert_eq!(ledger.unlocking, vec![c(4, 100)]); + assert_eq!(ledger.total, 100); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)])); + + // Tests for saturating arithmetic + + // Given + let slash = u64::MAX as Balance * 2; + // The value of the other parts of ledger that will get slashed + let value = slash - (10 * 4); + + ledger.active = 10; + ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)]; + ledger.total = value + 40; + // When + let slash_amount = ledger.slash(slash, 0, 0); + assert_eq_error_rate!(slash_amount, slash, 5); + // Then + assert_eq!(ledger.active, 0); // slash of 9 + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)])); + + // Given + use sp_runtime::PerThing as _; + let slash = u64::MAX as Balance * 2; + let value = u64::MAX as Balance * 2; + let unit = 100; + // slash * value that will saturate + assert!(slash.checked_mul(value).is_none()); + // but slash * unit won't. + assert!(slash.checked_mul(unit).is_some()); + ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)]; + //--------------------------------------note value^^^ + ledger.active = unit; + ledger.total = unit * 4 + value; + // When + assert_eq!(ledger.slash(slash, 0, 0), slash); + // Then + // The amount slashed out of `unit` + let affected_balance = value + unit * 4; + let ratio = Perquintill::from_rational_with_rounding(slash, affected_balance, Rounding::Up) + .unwrap(); + // `unit` after the slash is applied + let unit_slashed = { + let unit_slash = ratio.mul_ceil(unit); + unit - unit_slash + }; + let value_slashed = { + let value_slash = ratio.mul_ceil(value); + value - value_slash + }; + assert_eq!(ledger.active, unit_slashed); + assert_eq!(ledger.unlocking, vec![c(5, value_slashed), c(7, 32)]); + assert_eq!(ledger.total, value_slashed + 32); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 32)]) + ); + }); } #[test] @@ -7126,7 +7203,7 @@ mod staking_unchecked { fn virtual_bond_does_not_lock() { ExtBuilder::default().build_and_execute(|| { mock::start_active_era(1); - assert_eq!(asset::stakeable_balance::(&10), 1); + assert_eq!(asset::total_balance::(&10), 1); // 10 can bond more than its balance amount since we do not require lock for virtual // bonding. assert_ok!(::virtual_bond(&10, 100, &15)); @@ -7265,7 +7342,7 @@ mod staking_unchecked { assert_eq!(asset::staked::(&200), 1000); // migrate them to virtual staker - ::migrate_to_virtual_staker(&200); + assert_ok!(::migrate_to_virtual_staker(&200)); // payee needs to be updated to a non-stash account. assert_ok!(::set_payee(&200, &201)); @@ -7292,7 +7369,7 @@ mod staking_unchecked { // 101 is a nominator for 11 assert_eq!(initial_exposure.others.first().unwrap().who, 101); // make 101 a virtual nominator - ::migrate_to_virtual_staker(&101); + assert_ok!(::migrate_to_virtual_staker(&101)); // set payee different to self. assert_ok!(::set_payee(&101, &102)); @@ -7367,7 +7444,7 @@ mod staking_unchecked { // 101 is a nominator for 11 assert_eq!(initial_exposure.others.first().unwrap().who, 101); // make 101 a virtual nominator - ::migrate_to_virtual_staker(&101); + assert_ok!(::migrate_to_virtual_staker(&101)); // set payee different to self. assert_ok!(::set_payee(&101, &102)); @@ -7423,7 +7500,7 @@ mod staking_unchecked { // 333 is corrupted assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Corrupted); // migrate to virtual staker. - ::migrate_to_virtual_staker(&333); + assert_ok!(::migrate_to_virtual_staker(&333)); // recover the ledger won't work for virtual staker assert_noop!( @@ -8034,8 +8111,7 @@ mod ledger_recovery { // side effects on 333 - ledger, bonded, payee, lock should be intact. assert_eq!(asset::staked::(&333), lock_333_before); // OK assert_eq!(Bonded::::get(&333), Some(444)); // OK - assert!(Payee::::get(&333).is_some()); // OK - + assert!(Payee::::get(&333).is_some()); // however, ledger associated with its controller was killed. assert!(Ledger::::get(&444).is_none()); // NOK @@ -9081,3 +9157,249 @@ mod getters { }); } } + +mod hold_migration { + use super::*; + use sp_staking::{Stake, StakingInterface}; + + #[test] + fn ledger_update_creates_hold() { + ExtBuilder::default().has_stakers(true).build_and_execute(|| { + // GIVEN alice who is a nominator with old currency + let alice = 300; + bond_nominator(alice, 1000, vec![11]); + assert_eq!(asset::staked::(&alice), 1000); + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0); + // migrate alice currency to legacy locks + testing_utils::migrate_to_old_currency::(alice); + // no more holds + assert_eq!(asset::staked::(&alice), 0); + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000); + assert_eq!( + ::stake(&alice), + Ok(Stake { total: 1000, active: 1000 }) + ); + + // any ledger mutation should create a hold + hypothetically!({ + // give some extra balance to alice. + let _ = asset::mint_into_existing::(&alice, 100); + + // WHEN new fund is bonded to ledger. + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(alice), 100)); + + // THEN new hold is created + assert_eq!(asset::staked::(&alice), 1000 + 100); + assert_eq!( + ::stake(&alice), + Ok(Stake { total: 1100, active: 1100 }) + ); + + // old locked balance is untouched + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000); + }); + + hypothetically!({ + // WHEN new fund is unbonded from ledger. + assert_ok!(Staking::unbond(RuntimeOrigin::signed(alice), 100)); + + // THEN hold is updated. + assert_eq!(asset::staked::(&alice), 1000); + assert_eq!( + ::stake(&alice), + Ok(Stake { total: 1000, active: 900 }) + ); + + // old locked balance is untouched + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000); + }); + + // WHEN alice currency is migrated. + assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), alice)); + + // THEN hold is updated. + assert_eq!(asset::staked::(&alice), 1000); + assert_eq!( + ::stake(&alice), + Ok(Stake { total: 1000, active: 1000 }) + ); + + // ensure cannot migrate again. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), alice), + Error::::AlreadyMigrated + ); + + // locked balance is removed + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0); + }); + } + + #[test] + fn migrate_removes_old_lock() { + ExtBuilder::default().has_stakers(true).build_and_execute(|| { + // GIVEN alice who is a nominator with old currency + let alice = 300; + bond_nominator(alice, 1000, vec![11]); + testing_utils::migrate_to_old_currency::(alice); + assert_eq!(asset::staked::(&alice), 0); + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 1000); + let pre_migrate_consumer = System::consumers(&alice); + System::reset_events(); + + // WHEN alice currency is migrated. + assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), alice)); + + // THEN + // the extra consumer from old code is removed. + assert_eq!(System::consumers(&alice), pre_migrate_consumer - 1); + // ensure no lock + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0); + // ensure stake and hold are same. + assert_eq!( + ::stake(&alice), + Ok(Stake { total: 1000, active: 1000 }) + ); + assert_eq!(asset::staked::(&alice), 1000); + // ensure events are emitted. + assert_eq!( + staking_events_since_last_call(), + vec![Event::CurrencyMigrated { stash: alice, force_withdraw: 0 }] + ); + + // ensure cannot migrate again. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), alice), + Error::::AlreadyMigrated + ); + }); + } + #[test] + fn cannot_hold_all_stake() { + // When there is not enough funds to hold all stake, part of the stake if force withdrawn. + // At end of the migration, the stake and hold should be same. + ExtBuilder::default().has_stakers(true).build_and_execute(|| { + // GIVEN alice who is a nominator with old currency. + let alice = 300; + let stake = 1000; + bond_nominator(alice, stake, vec![11]); + testing_utils::migrate_to_old_currency::(alice); + assert_eq!(asset::staked::(&alice), 0); + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), stake); + // ledger has 1000 staked. + assert_eq!( + ::stake(&alice), + Ok(Stake { total: stake, active: stake }) + ); + + // Get rid of the extra ED to emulate all their balance including ED is staked. + assert_ok!(Balances::transfer_allow_death( + RuntimeOrigin::signed(alice), + 10, + ExistentialDeposit::get() + )); + + let expected_force_withdraw = ExistentialDeposit::get(); + + // ledger mutation would fail in this case before migration because of failing hold. + assert_noop!( + Staking::unbond(RuntimeOrigin::signed(alice), 100), + Error::::NotEnoughFunds + ); + + // clear events + System::reset_events(); + + // WHEN alice currency is migrated. + assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), alice)); + + // THEN + let expected_hold = stake - expected_force_withdraw; + // ensure no lock + assert_eq!(Balances::balance_locked(STAKING_ID, &alice), 0); + // ensure stake and hold are same. + assert_eq!( + ::stake(&alice), + Ok(Stake { total: expected_hold, active: expected_hold }) + ); + assert_eq!(asset::staked::(&alice), expected_hold); + // ensure events are emitted. + assert_eq!( + staking_events_since_last_call(), + vec![Event::CurrencyMigrated { + stash: alice, + force_withdraw: expected_force_withdraw + }] + ); + + // ensure cannot migrate again. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), alice), + Error::::AlreadyMigrated + ); + + // unbond works after migration. + assert_ok!(Staking::unbond(RuntimeOrigin::signed(alice), 100)); + }); + } + + #[test] + fn virtual_staker_consumer_provider_dec() { + // Ensure virtual stakers consumer and provider count is decremented. + ExtBuilder::default().has_stakers(true).build_and_execute(|| { + // 200 virtual bonds + bond_virtual_nominator(200, 201, 500, vec![11, 21]); + + // previously the virtual nominator had a provider inc by the delegation system as + // well as a consumer by this pallet. + System::inc_providers(&200); + System::inc_consumers(&200).expect("has provider, can consume"); + + hypothetically!({ + // migrate 200 + assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), 200)); + + // ensure account does not exist in system anymore. + assert_eq!(System::consumers(&200), 0); + assert_eq!(System::providers(&200), 0); + assert!(!System::account_exists(&200)); + + // ensure cannot migrate again. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), 200), + Error::::AlreadyMigrated + ); + }); + + hypothetically!({ + // 200 has an erroneously extra provider + System::inc_providers(&200); + + // causes migration to fail. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), 200), + Error::::BadState + ); + }); + + // 200 is funded for more than ED by a random account. + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(999), 200, 10)); + + // it has an extra provider now. + assert_eq!(System::providers(&200), 2); + + // migrate 200 + assert_ok!(Staking::migrate_currency(RuntimeOrigin::signed(1), 200)); + + // 1 provider is left, consumers is 0. + assert_eq!(System::providers(&200), 1); + assert_eq!(System::consumers(&200), 0); + + // ensure cannot migrate again. + assert_noop!( + Staking::migrate_currency(RuntimeOrigin::signed(1), 200), + Error::::AlreadyMigrated + ); + }); + } +} diff --git a/substrate/frame/staking/src/weights.rs b/substrate/frame/staking/src/weights.rs index 56f561679cfc..02ccdacb01c4 100644 --- a/substrate/frame/staking/src/weights.rs +++ b/substrate/frame/staking/src/weights.rs @@ -18,27 +18,25 @@ //! Autogenerated weights for `pallet_staking` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-09-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-obbyq9g6-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// target/production/substrate-node // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_staking -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* // --wasm-execution=compiled // --heap-pages=4096 -// --output=./substrate/frame/staking/src/weights.rs +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_staking +// --chain=dev // --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/staking/src/weights.rs // --template=./substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] @@ -83,6 +81,7 @@ pub trait WeightInfo { fn force_apply_min_commission() -> Weight; fn set_min_commission() -> Weight; fn restore_ledger() -> Weight; + fn migrate_currency() -> Weight; } /// Weights for `pallet_staking` using the Substrate node and recommended hardware. @@ -92,18 +91,18 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn bond() -> Weight { // Proof Size summary in bytes: - // Measured: `1042` - // Estimated: `4764` - // Minimum execution time: 46_504_000 picoseconds. - Weight::from_parts(48_459_000, 4764) + // Measured: `1068` + // Estimated: `4556` + // Minimum execution time: 71_854_000 picoseconds. + Weight::from_parts(73_408_000, 4556) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -111,20 +110,20 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn bond_extra() -> Weight { // Proof Size summary in bytes: - // Measured: `1990` + // Measured: `2049` // Estimated: `8877` - // Minimum execution time: 90_475_000 picoseconds. - Weight::from_parts(93_619_000, 8877) + // Minimum execution time: 127_442_000 picoseconds. + Weight::from_parts(130_845_000, 8877) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -138,22 +137,22 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `2195` + // Measured: `2151` // Estimated: `8877` - // Minimum execution time: 99_335_000 picoseconds. - Weight::from_parts(101_440_000, 8877) + // Minimum execution time: 105_259_000 picoseconds. + Weight::from_parts(107_112_000, 8877) .saturating_add(T::DbWeight::get().reads(12_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) @@ -161,21 +160,21 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0) /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1297` - // Estimated: `4764` - // Minimum execution time: 50_067_000 picoseconds. - Weight::from_parts(52_396_327, 4764) - // Standard Error: 1_419 - .saturating_add(Weight::from_parts(51_406, 0).saturating_mul(s.into())) + // Measured: `1393` + // Estimated: `4556` + // Minimum execution time: 77_158_000 picoseconds. + Weight::from_parts(79_140_122, 4556) + // Standard Error: 1_688 + .saturating_add(Weight::from_parts(62_663, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -187,10 +186,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -210,14 +209,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 92_931_000 picoseconds. - Weight::from_parts(101_398_156, 6248) - // Standard Error: 4_180 - .saturating_add(Weight::from_parts(1_377_850, 0).saturating_mul(s.into())) + // Minimum execution time: 125_396_000 picoseconds. + Weight::from_parts(134_915_543, 6248) + // Standard Error: 3_660 + .saturating_add(Weight::from_parts(1_324_736, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13_u64)) - .saturating_add(T::DbWeight::get().writes(11_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -245,10 +244,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn validate() -> Weight { // Proof Size summary in bytes: - // Measured: `1372` + // Measured: `1438` // Estimated: `4556` - // Minimum execution time: 56_291_000 picoseconds. - Weight::from_parts(58_372_000, 4556) + // Minimum execution time: 68_826_000 picoseconds. + Weight::from_parts(71_261_000, 4556) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } @@ -261,12 +260,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1815 + k * (572 ±0)` + // Measured: `1848 + k * (572 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 36_218_000 picoseconds. - Weight::from_parts(38_811_308, 4556) - // Standard Error: 8_352 - .saturating_add(Weight::from_parts(6_527_398, 0).saturating_mul(k.into())) + // Minimum execution time: 46_082_000 picoseconds. + Weight::from_parts(49_541_374, 4556) + // Standard Error: 7_218 + .saturating_add(Weight::from_parts(7_281_079, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -297,12 +296,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1866 + n * (102 ±0)` + // Measured: `1932 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 68_607_000 picoseconds. - Weight::from_parts(66_831_185, 6248) - // Standard Error: 14_014 - .saturating_add(Weight::from_parts(4_031_635, 0).saturating_mul(n.into())) + // Minimum execution time: 83_854_000 picoseconds. + Weight::from_parts(81_387_241, 6248) + // Standard Error: 16_811 + .saturating_add(Weight::from_parts(4_900_554, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6_u64)) @@ -326,10 +325,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `1816` + // Measured: `1882` // Estimated: `6248` - // Minimum execution time: 60_088_000 picoseconds. - Weight::from_parts(62_471_000, 6248) + // Minimum execution time: 73_939_000 picoseconds. + Weight::from_parts(75_639_000, 6248) .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -341,10 +340,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn set_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `902` + // Measured: `935` // Estimated: `4556` - // Minimum execution time: 19_777_000 picoseconds. - Weight::from_parts(20_690_000, 4556) + // Minimum execution time: 24_592_000 picoseconds. + Weight::from_parts(25_092_000, 4556) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -356,10 +355,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `969` + // Measured: `1002` // Estimated: `4556` - // Minimum execution time: 23_705_000 picoseconds. - Weight::from_parts(24_409_000, 4556) + // Minimum execution time: 29_735_000 picoseconds. + Weight::from_parts(30_546_000, 4556) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -369,10 +368,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) fn set_controller() -> Weight { // Proof Size summary in bytes: - // Measured: `902` + // Measured: `935` // Estimated: `8122` - // Minimum execution time: 23_479_000 picoseconds. - Weight::from_parts(24_502_000, 8122) + // Minimum execution time: 28_728_000 picoseconds. + Weight::from_parts(29_709_000, 8122) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -382,8 +381,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_675_000 picoseconds. - Weight::from_parts(2_802_000, 0) + // Minimum execution time: 2_519_000 picoseconds. + Weight::from_parts(2_673_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -392,8 +391,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_067_000 picoseconds. - Weight::from_parts(7_413_000, 0) + // Minimum execution time: 8_050_000 picoseconds. + Weight::from_parts(8_268_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -402,8 +401,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_977_000 picoseconds. - Weight::from_parts(7_353_000, 0) + // Minimum execution time: 8_131_000 picoseconds. + Weight::from_parts(8_349_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -412,8 +411,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_071_000 picoseconds. - Weight::from_parts(7_463_000, 0) + // Minimum execution time: 8_104_000 picoseconds. + Weight::from_parts(8_317_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -423,10 +422,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_833_000 picoseconds. - Weight::from_parts(3_328_130, 0) - // Standard Error: 30 - .saturating_add(Weight::from_parts(10_058, 0).saturating_mul(v.into())) + // Minimum execution time: 2_669_000 picoseconds. + Weight::from_parts(3_013_436, 0) + // Standard Error: 31 + .saturating_add(Weight::from_parts(10_704, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:11800 w:11800) @@ -438,12 +437,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 5900]`. fn deprecate_controller_batch(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1746 + i * (229 ±0)` + // Measured: `1779 + i * (229 ±0)` // Estimated: `990 + i * (7132 ±0)` - // Minimum execution time: 5_300_000 picoseconds. - Weight::from_parts(5_437_000, 990) - // Standard Error: 66_261 - .saturating_add(Weight::from_parts(30_172_457, 0).saturating_mul(i.into())) + // Minimum execution time: 5_101_000 picoseconds. + Weight::from_parts(5_368_000, 990) + // Standard Error: 75_180 + .saturating_add(Weight::from_parts(33_781_643, 0).saturating_mul(i.into())) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 7132).saturating_mul(i.into())) @@ -454,10 +453,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) @@ -479,14 +478,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 87_677_000 picoseconds. - Weight::from_parts(96_386_462, 6248) - // Standard Error: 3_717 - .saturating_add(Weight::from_parts(1_370_585, 0).saturating_mul(s.into())) + // Minimum execution time: 119_955_000 picoseconds. + Weight::from_parts(128_392_032, 6248) + // Standard Error: 3_773 + .saturating_add(Weight::from_parts(1_302_488, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(13_u64)) - .saturating_add(T::DbWeight::get().writes(12_u64)) + .saturating_add(T::DbWeight::get().writes(13_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -495,12 +494,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `66672` - // Estimated: `70137` - // Minimum execution time: 105_086_000 picoseconds. - Weight::from_parts(1_167_895_222, 70137) - // Standard Error: 77_022 - .saturating_add(Weight::from_parts(6_487_305, 0).saturating_mul(s.into())) + // Measured: `66705` + // Estimated: `70170` + // Minimum execution time: 139_290_000 picoseconds. + Weight::from_parts(959_667_494, 70170) + // Standard Error: 56_271 + .saturating_add(Weight::from_parts(4_798_293, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -518,12 +517,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorReward` (r:1 w:0) /// Proof: `Staking::ErasValidatorReward` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:257 w:257) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:257 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:257 w:257) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:257 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:257 w:257) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasStakersPaged` (r:1 w:0) /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::ErasRewardPoints` (r:1 w:0) @@ -532,29 +529,31 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:257 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:257 w:257) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `33297 + n * (377 ±0)` - // Estimated: `30944 + n * (3774 ±3)` - // Minimum execution time: 154_210_000 picoseconds. - Weight::from_parts(192_836_012, 30944) - // Standard Error: 40_441 - .saturating_add(Weight::from_parts(47_646_642, 0).saturating_mul(n.into())) + // Measured: `33283 + n * (370 ±0)` + // Estimated: `30958 + n * (3566 ±0)` + // Minimum execution time: 193_068_000 picoseconds. + Weight::from_parts(252_762_568, 30958) + // Standard Error: 22_743 + .saturating_add(Weight::from_parts(81_185_306, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(14_u64)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4_u64)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) @@ -562,25 +561,25 @@ impl WeightInfo for SubstrateWeight { /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1991 + l * (7 ±0)` + // Measured: `1947 + l * (7 ±0)` // Estimated: `8877` - // Minimum execution time: 88_337_000 picoseconds. - Weight::from_parts(91_391_254, 8877) - // Standard Error: 4_485 - .saturating_add(Weight::from_parts(103_443, 0).saturating_mul(l.into())) + // Minimum execution time: 91_151_000 picoseconds. + Weight::from_parts(93_596_096, 8877) + // Standard Error: 5_313 + .saturating_add(Weight::from_parts(124_684, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(9_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -600,14 +599,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 98_014_000 picoseconds. - Weight::from_parts(102_537_670, 6248) - // Standard Error: 3_324 - .saturating_add(Weight::from_parts(1_353_142, 0).saturating_mul(s.into())) + // Minimum execution time: 133_214_000 picoseconds. + Weight::from_parts(137_290_527, 6248) + // Standard Error: 4_153 + .saturating_add(Weight::from_parts(1_291_007, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(12_u64)) - .saturating_add(T::DbWeight::get().writes(11_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -651,12 +650,12 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 608_575_000 picoseconds. - Weight::from_parts(613_663_000, 512390) - // Standard Error: 2_286_521 - .saturating_add(Weight::from_parts(72_108_001, 0).saturating_mul(v.into())) - // Standard Error: 227_839 - .saturating_add(Weight::from_parts(20_314_085, 0).saturating_mul(n.into())) + // Minimum execution time: 692_301_000 picoseconds. + Weight::from_parts(708_732_000, 512390) + // Standard Error: 2_117_299 + .saturating_add(Weight::from_parts(70_087_600, 0).saturating_mul(v.into())) + // Standard Error: 210_977 + .saturating_add(Weight::from_parts(22_953_405, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(206_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -685,14 +684,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `3175 + n * (911 ±0) + v * (395 ±0)` + // Measured: `3241 + n * (911 ±0) + v * (395 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 37_173_756_000 picoseconds. - Weight::from_parts(37_488_937_000, 512390) - // Standard Error: 467_413 - .saturating_add(Weight::from_parts(8_086_367, 0).saturating_mul(v.into())) - // Standard Error: 467_413 - .saturating_add(Weight::from_parts(3_108_193, 0).saturating_mul(n.into())) + // Minimum execution time: 43_708_472_000 picoseconds. + Weight::from_parts(44_048_436_000, 512390) + // Standard Error: 493_244 + .saturating_add(Weight::from_parts(6_697_278, 0).saturating_mul(v.into())) + // Standard Error: 493_244 + .saturating_add(Weight::from_parts(4_559_779, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(201_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -707,12 +706,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `979 + v * (50 ±0)` + // Measured: `1012 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_641_258_000 picoseconds. - Weight::from_parts(382_882_595, 3510) - // Standard Error: 11_991 - .saturating_add(Weight::from_parts(4_695_820, 0).saturating_mul(v.into())) + // Minimum execution time: 2_917_165_000 picoseconds. + Weight::from_parts(2_948_999_000, 3510) + // Standard Error: 33_372 + .saturating_add(Weight::from_parts(2_126_909, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -735,8 +734,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_753_000 picoseconds. - Weight::from_parts(6_529_000, 0) + // Minimum execution time: 4_748_000 picoseconds. + Weight::from_parts(5_052_000, 0) .saturating_add(T::DbWeight::get().writes(7_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -757,8 +756,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_212_000 picoseconds. - Weight::from_parts(5_451_000, 0) + // Minimum execution time: 4_316_000 picoseconds. + Weight::from_parts(4_526_000, 0) .saturating_add(T::DbWeight::get().writes(7_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -785,10 +784,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill_other() -> Weight { // Proof Size summary in bytes: - // Measured: `1939` + // Measured: `2005` // Estimated: `6248` - // Minimum execution time: 73_000_000 picoseconds. - Weight::from_parts(75_184_000, 6248) + // Minimum execution time: 87_374_000 picoseconds. + Weight::from_parts(89_848_000, 6248) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -798,10 +797,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) fn force_apply_min_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `691` + // Measured: `724` // Estimated: `3510` - // Minimum execution time: 13_056_000 picoseconds. - Weight::from_parts(13_517_000, 3510) + // Minimum execution time: 15_529_000 picoseconds. + Weight::from_parts(16_094_000, 3510) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -811,28 +810,51 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_201_000 picoseconds. - Weight::from_parts(3_442_000, 0) + // Minimum execution time: 2_533_000 picoseconds. + Weight::from_parts(2_817_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:0) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + fn restore_ledger() -> Weight { + // Proof Size summary in bytes: + // Measured: `1110` + // Estimated: `4764` + // Minimum execution time: 50_105_000 picoseconds. + Weight::from_parts(50_966_000, 4764) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - fn restore_ledger() -> Weight { + fn migrate_currency() -> Weight { // Proof Size summary in bytes: - // Measured: `1047` + // Measured: `1246` // Estimated: `4764` - // Minimum execution time: 44_671_000 picoseconds. - Weight::from_parts(45_611_000, 4764) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(4_u64)) + // Minimum execution time: 94_054_000 picoseconds. + Weight::from_parts(96_272_000, 4764) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } } @@ -842,18 +864,18 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn bond() -> Weight { // Proof Size summary in bytes: - // Measured: `1042` - // Estimated: `4764` - // Minimum execution time: 46_504_000 picoseconds. - Weight::from_parts(48_459_000, 4764) + // Measured: `1068` + // Estimated: `4556` + // Minimum execution time: 71_854_000 picoseconds. + Weight::from_parts(73_408_000, 4556) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -861,20 +883,20 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn bond_extra() -> Weight { // Proof Size summary in bytes: - // Measured: `1990` + // Measured: `2049` // Estimated: `8877` - // Minimum execution time: 90_475_000 picoseconds. - Weight::from_parts(93_619_000, 8877) + // Minimum execution time: 127_442_000 picoseconds. + Weight::from_parts(130_845_000, 8877) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -888,22 +910,22 @@ impl WeightInfo for () { /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `2195` + // Measured: `2151` // Estimated: `8877` - // Minimum execution time: 99_335_000 picoseconds. - Weight::from_parts(101_440_000, 8877) + // Minimum execution time: 105_259_000 picoseconds. + Weight::from_parts(107_112_000, 8877) .saturating_add(RocksDbWeight::get().reads(12_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) @@ -911,21 +933,21 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0) /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1297` - // Estimated: `4764` - // Minimum execution time: 50_067_000 picoseconds. - Weight::from_parts(52_396_327, 4764) - // Standard Error: 1_419 - .saturating_add(Weight::from_parts(51_406, 0).saturating_mul(s.into())) + // Measured: `1393` + // Estimated: `4556` + // Minimum execution time: 77_158_000 picoseconds. + Weight::from_parts(79_140_122, 4556) + // Standard Error: 1_688 + .saturating_add(Weight::from_parts(62_663, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -937,10 +959,10 @@ impl WeightInfo for () { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -960,14 +982,14 @@ impl WeightInfo for () { /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 92_931_000 picoseconds. - Weight::from_parts(101_398_156, 6248) - // Standard Error: 4_180 - .saturating_add(Weight::from_parts(1_377_850, 0).saturating_mul(s.into())) + // Minimum execution time: 125_396_000 picoseconds. + Weight::from_parts(134_915_543, 6248) + // Standard Error: 3_660 + .saturating_add(Weight::from_parts(1_324_736, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(13_u64)) - .saturating_add(RocksDbWeight::get().writes(11_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -995,10 +1017,10 @@ impl WeightInfo for () { /// Proof: `Staking::CounterForValidators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn validate() -> Weight { // Proof Size summary in bytes: - // Measured: `1372` + // Measured: `1438` // Estimated: `4556` - // Minimum execution time: 56_291_000 picoseconds. - Weight::from_parts(58_372_000, 4556) + // Minimum execution time: 68_826_000 picoseconds. + Weight::from_parts(71_261_000, 4556) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } @@ -1011,12 +1033,12 @@ impl WeightInfo for () { /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1815 + k * (572 ±0)` + // Measured: `1848 + k * (572 ±0)` // Estimated: `4556 + k * (3033 ±0)` - // Minimum execution time: 36_218_000 picoseconds. - Weight::from_parts(38_811_308, 4556) - // Standard Error: 8_352 - .saturating_add(Weight::from_parts(6_527_398, 0).saturating_mul(k.into())) + // Minimum execution time: 46_082_000 picoseconds. + Weight::from_parts(49_541_374, 4556) + // Standard Error: 7_218 + .saturating_add(Weight::from_parts(7_281_079, 0).saturating_mul(k.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -1047,12 +1069,12 @@ impl WeightInfo for () { /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1866 + n * (102 ±0)` + // Measured: `1932 + n * (102 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 68_607_000 picoseconds. - Weight::from_parts(66_831_185, 6248) - // Standard Error: 14_014 - .saturating_add(Weight::from_parts(4_031_635, 0).saturating_mul(n.into())) + // Minimum execution time: 83_854_000 picoseconds. + Weight::from_parts(81_387_241, 6248) + // Standard Error: 16_811 + .saturating_add(Weight::from_parts(4_900_554, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(6_u64)) @@ -1076,10 +1098,10 @@ impl WeightInfo for () { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill() -> Weight { // Proof Size summary in bytes: - // Measured: `1816` + // Measured: `1882` // Estimated: `6248` - // Minimum execution time: 60_088_000 picoseconds. - Weight::from_parts(62_471_000, 6248) + // Minimum execution time: 73_939_000 picoseconds. + Weight::from_parts(75_639_000, 6248) .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1091,10 +1113,10 @@ impl WeightInfo for () { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn set_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `902` + // Measured: `935` // Estimated: `4556` - // Minimum execution time: 19_777_000 picoseconds. - Weight::from_parts(20_690_000, 4556) + // Minimum execution time: 24_592_000 picoseconds. + Weight::from_parts(25_092_000, 4556) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1106,10 +1128,10 @@ impl WeightInfo for () { /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn update_payee() -> Weight { // Proof Size summary in bytes: - // Measured: `969` + // Measured: `1002` // Estimated: `4556` - // Minimum execution time: 23_705_000 picoseconds. - Weight::from_parts(24_409_000, 4556) + // Minimum execution time: 29_735_000 picoseconds. + Weight::from_parts(30_546_000, 4556) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1119,10 +1141,10 @@ impl WeightInfo for () { /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) fn set_controller() -> Weight { // Proof Size summary in bytes: - // Measured: `902` + // Measured: `935` // Estimated: `8122` - // Minimum execution time: 23_479_000 picoseconds. - Weight::from_parts(24_502_000, 8122) + // Minimum execution time: 28_728_000 picoseconds. + Weight::from_parts(29_709_000, 8122) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1132,8 +1154,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_675_000 picoseconds. - Weight::from_parts(2_802_000, 0) + // Minimum execution time: 2_519_000 picoseconds. + Weight::from_parts(2_673_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1142,8 +1164,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_067_000 picoseconds. - Weight::from_parts(7_413_000, 0) + // Minimum execution time: 8_050_000 picoseconds. + Weight::from_parts(8_268_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1152,8 +1174,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_977_000 picoseconds. - Weight::from_parts(7_353_000, 0) + // Minimum execution time: 8_131_000 picoseconds. + Weight::from_parts(8_349_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1162,8 +1184,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_071_000 picoseconds. - Weight::from_parts(7_463_000, 0) + // Minimum execution time: 8_104_000 picoseconds. + Weight::from_parts(8_317_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Invulnerables` (r:0 w:1) @@ -1173,10 +1195,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_833_000 picoseconds. - Weight::from_parts(3_328_130, 0) - // Standard Error: 30 - .saturating_add(Weight::from_parts(10_058, 0).saturating_mul(v.into())) + // Minimum execution time: 2_669_000 picoseconds. + Weight::from_parts(3_013_436, 0) + // Standard Error: 31 + .saturating_add(Weight::from_parts(10_704, 0).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:11800 w:11800) @@ -1188,12 +1210,12 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 5900]`. fn deprecate_controller_batch(i: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1746 + i * (229 ±0)` + // Measured: `1779 + i * (229 ±0)` // Estimated: `990 + i * (7132 ±0)` - // Minimum execution time: 5_300_000 picoseconds. - Weight::from_parts(5_437_000, 990) - // Standard Error: 66_261 - .saturating_add(Weight::from_parts(30_172_457, 0).saturating_mul(i.into())) + // Minimum execution time: 5_101_000 picoseconds. + Weight::from_parts(5_368_000, 990) + // Standard Error: 75_180 + .saturating_add(Weight::from_parts(33_781_643, 0).saturating_mul(i.into())) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(i.into()))) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(i.into()))) .saturating_add(Weight::from_parts(0, 7132).saturating_mul(i.into())) @@ -1204,10 +1226,10 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) @@ -1229,14 +1251,14 @@ impl WeightInfo for () { /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 87_677_000 picoseconds. - Weight::from_parts(96_386_462, 6248) - // Standard Error: 3_717 - .saturating_add(Weight::from_parts(1_370_585, 0).saturating_mul(s.into())) + // Minimum execution time: 119_955_000 picoseconds. + Weight::from_parts(128_392_032, 6248) + // Standard Error: 3_773 + .saturating_add(Weight::from_parts(1_302_488, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(13_u64)) - .saturating_add(RocksDbWeight::get().writes(12_u64)) + .saturating_add(RocksDbWeight::get().writes(13_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -1245,12 +1267,12 @@ impl WeightInfo for () { /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `66672` - // Estimated: `70137` - // Minimum execution time: 105_086_000 picoseconds. - Weight::from_parts(1_167_895_222, 70137) - // Standard Error: 77_022 - .saturating_add(Weight::from_parts(6_487_305, 0).saturating_mul(s.into())) + // Measured: `66705` + // Estimated: `70170` + // Minimum execution time: 139_290_000 picoseconds. + Weight::from_parts(959_667_494, 70170) + // Standard Error: 56_271 + .saturating_add(Weight::from_parts(4_798_293, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1268,12 +1290,10 @@ impl WeightInfo for () { /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorReward` (r:1 w:0) /// Proof: `Staking::ErasValidatorReward` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:257 w:257) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:257 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:257 w:257) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:257 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:257 w:257) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasStakersPaged` (r:1 w:0) /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: None, mode: `Measured`) /// Storage: `Staking::ErasRewardPoints` (r:1 w:0) @@ -1282,29 +1302,31 @@ impl WeightInfo for () { /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:257 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:257 w:257) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `33297 + n * (377 ±0)` - // Estimated: `30944 + n * (3774 ±3)` - // Minimum execution time: 154_210_000 picoseconds. - Weight::from_parts(192_836_012, 30944) - // Standard Error: 40_441 - .saturating_add(Weight::from_parts(47_646_642, 0).saturating_mul(n.into())) + // Measured: `33283 + n * (370 ±0)` + // Estimated: `30958 + n * (3566 ±0)` + // Minimum execution time: 193_068_000 picoseconds. + Weight::from_parts(252_762_568, 30958) + // Standard Error: 22_743 + .saturating_add(Weight::from_parts(81_185_306, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(14_u64)) .saturating_add(RocksDbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(4_u64)) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) - .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListBags` (r:2 w:2) @@ -1312,25 +1334,25 @@ impl WeightInfo for () { /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `1991 + l * (7 ±0)` + // Measured: `1947 + l * (7 ±0)` // Estimated: `8877` - // Minimum execution time: 88_337_000 picoseconds. - Weight::from_parts(91_391_254, 8877) - // Standard Error: 4_485 - .saturating_add(Weight::from_parts(103_443, 0).saturating_mul(l.into())) + // Minimum execution time: 91_151_000 picoseconds. + Weight::from_parts(93_596_096, 8877) + // Standard Error: 5_313 + .saturating_add(Weight::from_parts(124_684, 0).saturating_mul(l.into())) .saturating_add(RocksDbWeight::get().reads(9_u64)) - .saturating_add(RocksDbWeight::get().writes(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } + /// Storage: `Staking::VirtualStakers` (r:1 w:1) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashingSpans` (r:1 w:1) /// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `Balances::Freezes` (r:1 w:0) - /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -1350,14 +1372,14 @@ impl WeightInfo for () { /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `2196 + s * (4 ±0)` + // Measured: `2255 + s * (4 ±0)` // Estimated: `6248 + s * (4 ±0)` - // Minimum execution time: 98_014_000 picoseconds. - Weight::from_parts(102_537_670, 6248) - // Standard Error: 3_324 - .saturating_add(Weight::from_parts(1_353_142, 0).saturating_mul(s.into())) + // Minimum execution time: 133_214_000 picoseconds. + Weight::from_parts(137_290_527, 6248) + // Standard Error: 4_153 + .saturating_add(Weight::from_parts(1_291_007, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(12_u64)) - .saturating_add(RocksDbWeight::get().writes(11_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } @@ -1401,12 +1423,12 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 608_575_000 picoseconds. - Weight::from_parts(613_663_000, 512390) - // Standard Error: 2_286_521 - .saturating_add(Weight::from_parts(72_108_001, 0).saturating_mul(v.into())) - // Standard Error: 227_839 - .saturating_add(Weight::from_parts(20_314_085, 0).saturating_mul(n.into())) + // Minimum execution time: 692_301_000 picoseconds. + Weight::from_parts(708_732_000, 512390) + // Standard Error: 2_117_299 + .saturating_add(Weight::from_parts(70_087_600, 0).saturating_mul(v.into())) + // Standard Error: 210_977 + .saturating_add(Weight::from_parts(22_953_405, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(206_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -1435,14 +1457,14 @@ impl WeightInfo for () { /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `3175 + n * (911 ±0) + v * (395 ±0)` + // Measured: `3241 + n * (911 ±0) + v * (395 ±0)` // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` - // Minimum execution time: 37_173_756_000 picoseconds. - Weight::from_parts(37_488_937_000, 512390) - // Standard Error: 467_413 - .saturating_add(Weight::from_parts(8_086_367, 0).saturating_mul(v.into())) - // Standard Error: 467_413 - .saturating_add(Weight::from_parts(3_108_193, 0).saturating_mul(n.into())) + // Minimum execution time: 43_708_472_000 picoseconds. + Weight::from_parts(44_048_436_000, 512390) + // Standard Error: 493_244 + .saturating_add(Weight::from_parts(6_697_278, 0).saturating_mul(v.into())) + // Standard Error: 493_244 + .saturating_add(Weight::from_parts(4_559_779, 0).saturating_mul(n.into())) .saturating_add(RocksDbWeight::get().reads(201_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) @@ -1457,12 +1479,12 @@ impl WeightInfo for () { /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `979 + v * (50 ±0)` + // Measured: `1012 + v * (50 ±0)` // Estimated: `3510 + v * (2520 ±0)` - // Minimum execution time: 2_641_258_000 picoseconds. - Weight::from_parts(382_882_595, 3510) - // Standard Error: 11_991 - .saturating_add(Weight::from_parts(4_695_820, 0).saturating_mul(v.into())) + // Minimum execution time: 2_917_165_000 picoseconds. + Weight::from_parts(2_948_999_000, 3510) + // Standard Error: 33_372 + .saturating_add(Weight::from_parts(2_126_909, 0).saturating_mul(v.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) @@ -1485,8 +1507,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_753_000 picoseconds. - Weight::from_parts(6_529_000, 0) + // Minimum execution time: 4_748_000 picoseconds. + Weight::from_parts(5_052_000, 0) .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// Storage: `Staking::MinCommission` (r:0 w:1) @@ -1507,8 +1529,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_212_000 picoseconds. - Weight::from_parts(5_451_000, 0) + // Minimum execution time: 4_316_000 picoseconds. + Weight::from_parts(4_526_000, 0) .saturating_add(RocksDbWeight::get().writes(7_u64)) } /// Storage: `Staking::Bonded` (r:1 w:0) @@ -1535,10 +1557,10 @@ impl WeightInfo for () { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn chill_other() -> Weight { // Proof Size summary in bytes: - // Measured: `1939` + // Measured: `2005` // Estimated: `6248` - // Minimum execution time: 73_000_000 picoseconds. - Weight::from_parts(75_184_000, 6248) + // Minimum execution time: 87_374_000 picoseconds. + Weight::from_parts(89_848_000, 6248) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1548,10 +1570,10 @@ impl WeightInfo for () { /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) fn force_apply_min_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `691` + // Measured: `724` // Estimated: `3510` - // Minimum execution time: 13_056_000 picoseconds. - Weight::from_parts(13_517_000, 3510) + // Minimum execution time: 15_529_000 picoseconds. + Weight::from_parts(16_094_000, 3510) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1561,27 +1583,50 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_201_000 picoseconds. - Weight::from_parts(3_442_000, 0) + // Minimum execution time: 2_533_000 picoseconds. + Weight::from_parts(2_817_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } - /// Storage: `Balances::Locks` (r:1 w:1) - /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:1 w:1) + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:0) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:0) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + fn restore_ledger() -> Weight { + // Proof Size summary in bytes: + // Measured: `1110` + // Estimated: `4764` + // Minimum execution time: 50_105_000 picoseconds. + Weight::from_parts(50_966_000, 4764) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Staking::VirtualStakers` (r:1 w:0) + /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) + /// Storage: `Staking::Bonded` (r:1 w:0) + /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) + /// Storage: `Staking::Ledger` (r:1 w:0) + /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) + /// Storage: `Balances::Holds` (r:1 w:1) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(355), added: 2830, mode: `MaxEncodedLen`) /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`) - fn restore_ledger() -> Weight { + fn migrate_currency() -> Weight { // Proof Size summary in bytes: - // Measured: `1047` + // Measured: `1246` // Estimated: `4764` - // Minimum execution time: 44_671_000 picoseconds. - Weight::from_parts(45_611_000, 4764) - .saturating_add(RocksDbWeight::get().reads(5_u64)) - .saturating_add(RocksDbWeight::get().writes(4_u64)) + // Minimum execution time: 94_054_000 picoseconds. + Weight::from_parts(96_272_000, 4764) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } } \ No newline at end of file diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 17010a8907fc..8e23c6800a9d 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -325,7 +325,7 @@ pub trait StakingUnchecked: StakingInterface { /// Migrate an existing staker to a virtual staker. /// /// It would release all funds held by the implementation pallet. - fn migrate_to_virtual_staker(who: &Self::AccountId); + fn migrate_to_virtual_staker(who: &Self::AccountId) -> DispatchResult; /// Book-keep a new bond for `keyless_who` without applying any locks (hence virtual). /// From 4b2febe18c6f2180a31a902433c00c30f8903ef7 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Fri, 17 Jan 2025 20:46:28 +0900 Subject: [PATCH 279/340] Make frame crate not use the feature experimental (#7177) We already use it for lots of pallet. Keeping it feature gated by experimental means we lose the information of which pallet was using experimental before the migration to frame crate usage. We can consider `polkadot-sdk-frame` crate unstable but let's not use the feature `experimental`. --------- Co-authored-by: command-bot <> --- .../packages/guides/first-pallet/Cargo.toml | 2 +- .../packages/guides/first-runtime/Cargo.toml | 2 +- polkadot/xcm/docs/Cargo.toml | 2 +- prdoc/pr_7177.prdoc | 20 +++++++++++++++++++ substrate/frame/atomic-swap/Cargo.toml | 2 +- .../frame/examples/frame-crate/Cargo.toml | 2 +- substrate/frame/mixnet/Cargo.toml | 2 +- substrate/frame/multisig/Cargo.toml | 2 +- substrate/frame/node-authorization/Cargo.toml | 2 +- substrate/frame/proxy/Cargo.toml | 2 +- substrate/frame/salary/Cargo.toml | 2 +- substrate/frame/src/lib.rs | 3 +-- .../support/test/stg_frame_crate/Cargo.toml | 2 +- 13 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 prdoc/pr_7177.prdoc diff --git a/docs/sdk/packages/guides/first-pallet/Cargo.toml b/docs/sdk/packages/guides/first-pallet/Cargo.toml index a1411580119d..e6325c31781a 100644 --- a/docs/sdk/packages/guides/first-pallet/Cargo.toml +++ b/docs/sdk/packages/guides/first-pallet/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } docify = { workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { workspace = true } [features] diff --git a/docs/sdk/packages/guides/first-runtime/Cargo.toml b/docs/sdk/packages/guides/first-runtime/Cargo.toml index 303d5c5e7f5f..8ed17dea1b71 100644 --- a/docs/sdk/packages/guides/first-runtime/Cargo.toml +++ b/docs/sdk/packages/guides/first-runtime/Cargo.toml @@ -18,7 +18,7 @@ scale-info = { workspace = true } serde_json = { workspace = true } # this is a frame-based runtime, thus importing `frame` with runtime feature enabled. -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } # pallets that we want to use pallet-balances = { workspace = true } diff --git a/polkadot/xcm/docs/Cargo.toml b/polkadot/xcm/docs/Cargo.toml index 6fa7ea9a23a9..c3bda50619c1 100644 --- a/polkadot/xcm/docs/Cargo.toml +++ b/polkadot/xcm/docs/Cargo.toml @@ -18,7 +18,7 @@ xcm-simulator = { workspace = true, default-features = true } # For building FRAME runtimes codec = { workspace = true, default-features = true } -frame = { features = ["experimental", "runtime"], workspace = true, default-features = true } +frame = { features = ["runtime"], workspace = true, default-features = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-runtime-parachains = { workspace = true, default-features = true } diff --git a/prdoc/pr_7177.prdoc b/prdoc/pr_7177.prdoc new file mode 100644 index 000000000000..9ab0be1f20a9 --- /dev/null +++ b/prdoc/pr_7177.prdoc @@ -0,0 +1,20 @@ +title: Make frame crate not experimental +doc: +- audience: Runtime Dev + description: |- + Frame crate may still be unstable, but it is no longer feature gated by the feature `experimental`. +crates: +- name: polkadot-sdk-frame + bump: minor +- name: pallet-salary + bump: patch +- name: pallet-multisig + bump: patch +- name: pallet-proxy + bump: patch +- name: pallet-atomic-swap + bump: patch +- name: pallet-mixnet + bump: patch +- name: pallet-node-authorization + bump: patch diff --git a/substrate/frame/atomic-swap/Cargo.toml b/substrate/frame/atomic-swap/Cargo.toml index 785bfee71b68..05a38ded91c5 100644 --- a/substrate/frame/atomic-swap/Cargo.toml +++ b/substrate/frame/atomic-swap/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } [dev-dependencies] diff --git a/substrate/frame/examples/frame-crate/Cargo.toml b/substrate/frame/examples/frame-crate/Cargo.toml index f174c6b9054b..46db1afc3464 100644 --- a/substrate/frame/examples/frame-crate/Cargo.toml +++ b/substrate/frame/examples/frame-crate/Cargo.toml @@ -19,7 +19,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame = { features = ["experimental", "runtime"], workspace = true } +frame = { features = ["runtime"], workspace = true } [features] diff --git a/substrate/frame/mixnet/Cargo.toml b/substrate/frame/mixnet/Cargo.toml index 0ae3b3938c60..33bf7146980d 100644 --- a/substrate/frame/mixnet/Cargo.toml +++ b/substrate/frame/mixnet/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive", "max-encoded-len"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = ["derive"], workspace = true } diff --git a/substrate/frame/multisig/Cargo.toml b/substrate/frame/multisig/Cargo.toml index 0d175617c9c2..e18e14f2626b 100644 --- a/substrate/frame/multisig/Cargo.toml +++ b/substrate/frame/multisig/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } # third party diff --git a/substrate/frame/node-authorization/Cargo.toml b/substrate/frame/node-authorization/Cargo.toml index 7e55ad178091..86a78e6e3615 100644 --- a/substrate/frame/node-authorization/Cargo.toml +++ b/substrate/frame/node-authorization/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } diff --git a/substrate/frame/proxy/Cargo.toml b/substrate/frame/proxy/Cargo.toml index a36b2c1cb9c3..3f2565abac88 100644 --- a/substrate/frame/proxy/Cargo.toml +++ b/substrate/frame/proxy/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["max-encoded-len"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } scale-info = { features = ["derive"], workspace = true } [dev-dependencies] diff --git a/substrate/frame/salary/Cargo.toml b/substrate/frame/salary/Cargo.toml index 626993a0547b..84c55b110c8c 100644 --- a/substrate/frame/salary/Cargo.toml +++ b/substrate/frame/salary/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame = { workspace = true, features = ["experimental", "runtime"] } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } pallet-ranked-collective = { optional = true, workspace = true } scale-info = { features = ["derive"], workspace = true } diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index e3e58fc01b5f..18c7bd123944 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -106,7 +106,7 @@ //! [dependencies] //! codec = { features = ["max-encoded-len"], workspace = true } //! scale-info = { features = ["derive"], workspace = true } -//! frame = { workspace = true, features = ["experimental", "runtime"] } +//! frame = { workspace = true, features = ["runtime"] } //! //! [features] //! default = ["std"] @@ -150,7 +150,6 @@ //! * `runtime::apis` should expose all common runtime APIs that all FRAME-based runtimes need. #![cfg_attr(not(feature = "std"), no_std)] -#![cfg(feature = "experimental")] #[doc(no_inline)] pub use frame_support::pallet; diff --git a/substrate/frame/support/test/stg_frame_crate/Cargo.toml b/substrate/frame/support/test/stg_frame_crate/Cargo.toml index f627d29cd563..157361dbd5d6 100644 --- a/substrate/frame/support/test/stg_frame_crate/Cargo.toml +++ b/substrate/frame/support/test/stg_frame_crate/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame = { features = ["experimental", "runtime"], workspace = true } +frame = { features = ["runtime"], workspace = true } scale-info = { features = ["derive"], workspace = true } [features] From d62a90c8c729acd98c7e9a5cab9803b8b211ffc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Fri, 17 Jan 2025 15:36:28 +0100 Subject: [PATCH 280/340] pallet_revive: Bump PolkaVM (#7203) Update to PolkaVM `0.19`. This version renumbers the opcodes in order to be in-line with the grey paper. Hopefully, for the last time. This means that it breaks existing contracts. --------- Signed-off-by: xermicus Co-authored-by: command-bot <> Co-authored-by: xermicus --- Cargo.lock | 91 +++++++++++++++++- prdoc/pr_7203.prdoc | 13 +++ substrate/frame/revive/Cargo.toml | 2 +- substrate/frame/revive/fixtures/Cargo.toml | 2 +- .../frame/revive/fixtures/build/_Cargo.toml | 2 +- .../revive/rpc/examples/js/pvm/Errors.polkavm | Bin 7274 -> 7274 bytes .../rpc/examples/js/pvm/EventExample.polkavm | Bin 2615 -> 2615 bytes .../rpc/examples/js/pvm/Flipper.polkavm | Bin 1738 -> 1738 bytes .../rpc/examples/js/pvm/FlipperCaller.polkavm | Bin 4584 -> 4584 bytes .../rpc/examples/js/pvm/PiggyBank.polkavm | Bin 5088 -> 5088 bytes substrate/frame/revive/uapi/Cargo.toml | 2 +- 11 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 prdoc/pr_7203.prdoc diff --git a/Cargo.lock b/Cargo.lock index 42ed88fb0d06..23271617e927 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14857,7 +14857,7 @@ dependencies = [ "pallet-utility 28.0.0", "parity-scale-codec", "paste", - "polkavm 0.18.0", + "polkavm 0.19.0", "pretty_assertions", "rlp 0.6.1", "scale-info", @@ -14946,7 +14946,7 @@ name = "pallet-revive-fixtures" version = "0.1.0" dependencies = [ "anyhow", - "polkavm-linker 0.18.0", + "polkavm-linker 0.19.0", "sp-core 28.0.0", "sp-io 30.0.0", "toml 0.8.19", @@ -15061,7 +15061,7 @@ dependencies = [ "pallet-revive-proc-macro 0.1.0", "parity-scale-codec", "paste", - "polkavm-derive 0.18.0", + "polkavm-derive 0.19.0", "scale-info", ] @@ -19933,6 +19933,19 @@ dependencies = [ "polkavm-linux-raw 0.18.0", ] +[[package]] +name = "polkavm" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8379bb48ff026aa8ae0645ea45f27920bfd21c82b2e82ed914224bb233d59f83" +dependencies = [ + "libc", + "log", + "polkavm-assembler 0.19.0", + "polkavm-common 0.19.0", + "polkavm-linux-raw 0.19.0", +] + [[package]] name = "polkavm-assembler" version = "0.9.0" @@ -19960,6 +19973,15 @@ dependencies = [ "log", ] +[[package]] +name = "polkavm-assembler" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57513b596cf0bafb052dab48e9c168f473c35f7522e17f70cc9f96603012d9b7" +dependencies = [ + "log", +] + [[package]] name = "polkavm-common" version = "0.9.0" @@ -19989,6 +20011,16 @@ dependencies = [ "polkavm-assembler 0.18.0", ] +[[package]] +name = "polkavm-common" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a972bd305ba8cbf0de79951d6d49d2abfad47c277596be5a2c6a0924a163abbd" +dependencies = [ + "log", + "polkavm-assembler 0.19.0", +] + [[package]] name = "polkavm-derive" version = "0.9.1" @@ -20016,6 +20048,15 @@ dependencies = [ "polkavm-derive-impl-macro 0.18.0", ] +[[package]] +name = "polkavm-derive" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d866972a7532d82d05c26b4516563660dd6676d7ab9e64e681d8ef0e29255c" +dependencies = [ + "polkavm-derive-impl-macro 0.19.0", +] + [[package]] name = "polkavm-derive-impl" version = "0.9.0" @@ -20052,6 +20093,18 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "polkavm-derive-impl" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cffca9d51b21153395a192b65698457687bc51daa41026629895542ccaa65c2" +dependencies = [ + "polkavm-common 0.19.0", + "proc-macro2 1.0.86", + "quote 1.0.37", + "syn 2.0.87", +] + [[package]] name = "polkavm-derive-impl-macro" version = "0.9.0" @@ -20082,6 +20135,16 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0dc0cf2e8f4d30874131eccfa36bdabd4a52cfb79c15f8630508abaf06a2a6" +dependencies = [ + "polkavm-derive-impl 0.19.0", + "syn 2.0.87", +] + [[package]] name = "polkavm-linker" version = "0.9.2" @@ -20128,6 +20191,22 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "polkavm-linker" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caec2308f1328b5a667da45322c04fad7ff97ad8b36817d18c7635ea4dd6c6f4" +dependencies = [ + "dirs", + "gimli 0.31.1", + "hashbrown 0.14.5", + "log", + "object 0.36.1", + "polkavm-common 0.19.0", + "regalloc2 0.9.3", + "rustc-demangle", +] + [[package]] name = "polkavm-linux-raw" version = "0.9.0" @@ -20146,6 +20225,12 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23eff02c070c70f31878a3d915e88a914ecf3e153741e2fb572dde28cce20fde" +[[package]] +name = "polkavm-linux-raw" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136ae072ab6fa38e584a06d12b1b216cff19f54d5cd202a8f8c5ec2e92e7e4bb" + [[package]] name = "polling" version = "2.8.0" diff --git a/prdoc/pr_7203.prdoc b/prdoc/pr_7203.prdoc new file mode 100644 index 000000000000..96a3d19472e9 --- /dev/null +++ b/prdoc/pr_7203.prdoc @@ -0,0 +1,13 @@ +title: 'pallet_revive: Bump PolkaVM' +doc: +- audience: Runtime Dev + description: Update to PolkaVM `0.19`. This version renumbers the opcodes in order + to be in-line with the grey paper. Hopefully, for the last time. This means that + it breaks existing contracts. +crates: +- name: pallet-revive + bump: patch +- name: pallet-revive-fixtures + bump: patch +- name: pallet-revive-uapi + bump: patch diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 1284f5ee8947..49a27cfdaab2 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -25,7 +25,7 @@ hex = { workspace = true } impl-trait-for-tuples = { workspace = true } log = { workspace = true } paste = { workspace = true } -polkavm = { version = "0.18.0", default-features = false } +polkavm = { version = "0.19.0", default-features = false } rlp = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { features = [ diff --git a/substrate/frame/revive/fixtures/Cargo.toml b/substrate/frame/revive/fixtures/Cargo.toml index e17bc88a3847..a6f25cc26f3c 100644 --- a/substrate/frame/revive/fixtures/Cargo.toml +++ b/substrate/frame/revive/fixtures/Cargo.toml @@ -21,7 +21,7 @@ sp-io = { workspace = true, default-features = true, optional = true } [build-dependencies] anyhow = { workspace = true, default-features = true } -polkavm-linker = { version = "0.18.0" } +polkavm-linker = { version = "0.19.0" } toml = { workspace = true } [features] diff --git a/substrate/frame/revive/fixtures/build/_Cargo.toml b/substrate/frame/revive/fixtures/build/_Cargo.toml index bfb9aaedd6f5..483d9775b12a 100644 --- a/substrate/frame/revive/fixtures/build/_Cargo.toml +++ b/substrate/frame/revive/fixtures/build/_Cargo.toml @@ -14,7 +14,7 @@ edition = "2021" [dependencies] uapi = { package = 'pallet-revive-uapi', path = "", features = ["unstable-hostfn"], default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } -polkavm-derive = { version = "0.18.0" } +polkavm-derive = { version = "0.19.0" } [profile.release] opt-level = 3 diff --git a/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/Errors.polkavm index 77de4ff3b1b3fe1f378ae31bbba24ddb38cc6300..48de6e0aa0c6cc1604008ba4e65c237dd557675b 100644 GIT binary patch literal 7274 zcmc&&4R9OPo!^yY?bAxOSBmUi*^VPCn?wnBOo-Cfx#l=GqdZGF%4k*7y0lzkV&nBk zqF_6Co!xfK%BiIkQd>B9Z4R0VY5kRBxPh6NgzJRDHFC_%af28jej;gYB{|J$h0%&Az z6SOZutAZAVb|198b*-(t>+3q|cJGZH=vw*L8mp^c`PbfbsG+f;V|RN;tgWuGp}nE5 zb$44s$HBJ!?J(N3Z{NX=y8BxjvKx_+MY@OTS`Rkt?r5vq-~K>DTU$eYp3`LbEM#9} zqqV-_Kx?ea`ozRnOvaY`sD~((8m27tR(coxUAmhNn%Yd`rguz#ZT`Kv%JN6c<9XYx z>re{4i-v65*|*t~_Fvd59D5yy9d9|P{5$ghGv8k@TCh-n3cp=w=bqst=R3|4*J0Q8 zRl8Ru+~e-|+;0}~#aH=_!W}|=*AEYI0Vc|REx=L~ed@@zw1MKiYkE3oYrX;lY=GH9 zZ3FO3&nWJ{WuUKD0*se3rF&swmH|sTU<*(jOdWGlAFJE~trpGGYVjO>tcWsQcT3k{ zSY`EhW(MtyqOVbZGZl`OjFFp3; zO9!F9Mc0}19kCZKz$@j9r9123Hn#5I&WJ;$e*UXP3_*0{}rl6 zZfeh6v_8U>&0Ln4f3t(8&~b%a3FKT4Qogsk_1hPpK4O-te>(F0!}%y${eypb>aiz* zkbqUgul<|%zd&_|IzrjK)a z%L4WKX-03M=+`F^=(H{)`gNFW7U+mkHm&z94a_m`+J!RNaiajOR~Ud_6MH!9 zDa4)v?8(O-2lm*phs7S)1Yr+@Jyz_=!yXIvn2%G~W9q^l`ejO&7bv}Dp33~t?nald zUcE|`K?inbj%4pXFgh@aZAd~Qo8x+pil4V+*`B8&oX-U;+j1<+^Avb7%&+lcz8v$b zF<*xHQp}fN-h+7o^E~E@F<*pvH|AGi-i3K5<~ht4V!i_r{zFn%`2bRd>MXP3?gK00!A^5O_OdOoqyba;rqc;1$oU@j`QsO>2xyxyS0>-(T3 zpmozZMy@mI`YvKd$M^+^T-Qwy+X6!yR$CAqSL1RwxS6W~Mr{QPynmiz&3ZHVi;<;} z#`SbkSF`p&e8B>hdBLW`%FG#7ZxQIsBnq#e1RkD+HVW-HeXMl0=j?3#KdY01dWKV9 zKBC@EHQ6dA+(WLM?#)d{Skzj0>Jr&0}~%U zXT3F?JFQDNTlT?`RiIoEZc{rP)@=QZPHLO=CQjSx4BjlvJT|I<0C zM=1$?Kkr6f>h{(TBxY~k^)BFx^ipqHKIx@4PyMgNoP*(Oy%c!MFU(7AU1V{^OKn*X zEUsQ;@p>=ymt1`t_MHI3@66S*KJ7jvr{O zFrL>0zTfL_yG4_zH!qvSy=Jh703^#>>;5D$wdQPoB?w`G3kxWW?hdIB1{f07BHWk% z4k*5C4v@Uq<^SrUw;}1pb&h2*AO1_(-JEIp?6*uy=2-b3f0USurkpue>7yZiBBGzF zjU9?5W0SF;gAaV-;)wldJh9>2;ZFKuoXLziE>1(*@l@U-`+su+b{I0+0e}{XFm`1AM`EVwoG%2ADUvLIvHJLIaCbq@x0p=Fz%S20dktC|+PNFXi$f%D zykWeEkCQlYE*B>tK9KyflTa^7F<1^<7eNFAQze;Stv*(zk4GT$>&=`-nK4^z2+~E8 z_q|{n9P+|*6#a&ZcLI(?iy|EU_4^W&p)}a@XcvJ=hvBq>l;j#Llw)Bv7RsWv~!VPT%Q+5zXdGb{RtMo$cXdv zE1%4ZYo7mu#7w|235vHEaRPR)qLLy5Vod?WpnQlyj)x%TK+m=*Dk(J}t0uKZHsUC% zg@#lZo=p&aXc;p#kh0bj)*{?D*8)Q&S00weQ>#p1DX z2nd~e3rDM`Yt_k!dM1=fF`2ta6>2~&nap~YK}LP9D#|1;$2Vl1%K}tn_jbckotLwi zK?0jTO^QhR^Qa;rJ%i-T&Ah!-?#L89lPjF{<{9eZ5vZZ(pq88^k80CAg>6yh*B|A) z2XUzJ)7~uGv%vOur~V7_ADASfo~3@)3rVw=6n8A?p_Y$u|L{{FdGaHD&pf3u9t{ba ztwdu>2RjKUgG1clxB$*KGstLYwPq{R*mBLjW-x)k0S}IIgJ%WE;xnUY5FE2&(vVZL zaT@E=?5qAijb`)*Src5b^1QXSu;wh0L^l(WxiRnvK?2&2G{h=1-D5VbNHd zW=EQX)foHWID(T0U*!fnh0J*-a|j0WG`mG}STzK11`v5a0J8}plO|WuP`+k&XpRES zR!CTzL}YeI$V`#f5V38s+Y)EZ15c1kgC^`EOJbb^s$|~|P?4~@=X>u-Ovk00BnkI_ z2PmSKvXOd=|62=g=J>TjRI+bB26~D726`pNe~CyS-nj=3AcBi<^(C-@6NZ;9Q&R+0 zuGAC{1R6~#QDwiTl&Z2hqd`GBSrt8xPV@T%Mm9a7~<=xnNNK*y@`D0FsJJ`SBjm0yD{UzMMQ zu0WO3&=sokIp|b)%b_V?k~7emRJfL=m{rg-O|g)`s400Wyc^IIE3sOwlOfgQlJP!p z`|l)X-G|HZ?9;^i4;B{VJs08RKm1l=-dzB_2U+WKCCE#c{euZkZ}JkcW?wDo@@9~= z-V}kqdQ%A0S#Pdd)M(IOqO*{+))JaV1zs!ZO>&lI&oYhPbO7{FZ|tKoT{gWrL4nfi zE#1_j;)4oja}mACpt5w6Il9pt-SHJVz4=)fgPmTZKv(saG)!@N>p5!C06>?=yIPE1X8AH<$^I>saz;DhBqr03XEaDav|Rs z_9_<~#;~AVup7gia)DK*kTHamDVve3RHk?%xmlSiHj;j2s>n!sl_|H86qKn|Mv_ye zTuPcV5=coqjmk5Kr{&2&TL?=OeJM+m~7|rJ6w=!HUIO zjD~jh_awxzlVW%PId@)jcq(WM6O2=#p^dk70jSB1~DEXQ!l!X-n&6A6M(_%2}gnyKvb_5W*jrFOB-ur3Ds zD4*me;$K;j3i|p(B1r8-_Sd(3`yPVw@)wA*ltjGY><_FFiE_Yq;2Og03fLqQI_7qn GsQ&}g)MbzW literal 7274 zcmc&&4R9OPo!^yY?bAwjR*LLh*^VPCn?wnBOdO@HbIoyXMtPQU9HUiD>(X+GiH+AE z*<{xada^0wD^4vrShWR5ZF6v$kk(&ZhZ{T-lW>P9Ic&$w95(|)=GuaXIeKon_RO_M zO-RW7pX85(OEZue292KH>b?Ja@Be=P?~Q!puNbDel*xWrj(Mqy$uL!xt{)&eQUHz4 zZHM+HXjRan(C&qHsIIm3Kz&_%-GM_#j>gyiwH~RfU;EeA)X~t`(0-t;{fJT5*wEHc z*LuKcXg_9XZ7|w&`0%mzy8BuivK!HnWx5CIT8}jxXgBJ#w)-0lqoF>}X&Urd=)Qy7 zto02?TaU!8PfUEpG`RC#<^iUY8D=c(E_Ofr9kz=Nnhevp=^fLbntx-ivi#2Sc;0U7 zW|T(nq9NNJ{%!uW{S|w;h-WFg+(DOqA(Ya@f=Hu*6Y!-+b$-FB+-soimh-cYY;~uuQ`qHCMzH|)w zfB4nomIGF$+9N4npX8P5-jc`VSx2<`CopcxUjYoUH$n`tH-_vL2WgbpSnTVf`lr7H?In%7fCg*0)46rhw_#m$d0mEXD~9|p0L>J%NodbPdj{GlG+4u(&#f!o z1nY{I*UiJ+&!Jspj3Y+K|2os)ZGiqYMnBdB{i{r!*Jud&XBcl&n>XY?&(x?*ZMloq zM}*Rgmlf_`?Vu@aoX{(Qoa;f#_f)rj>t|2*nw9Dwk34idA4RLb|4&an`Xmriuxi9j zKd{Fet3XH=o+a=AItG0NdH}jH1U&8G zLlxthSuS%HWe7?oyqPmxU(B1elec)ib&=8GFuh4)<0BwN%CO#y*off$myZ1LW6_00Cl&=PuESyh7W1*_z@iK(_xZ8- z!p&Lyca&o>m}OA{!Ebr>Lxp=8hAE2@n?o_jVMD-lOfyOY=2oq(Bw(-8I*S9A2efvI zDBvurfaxIswO#9^s2&DbVc6LYz#ai)HGtL|aI|R;Qg|BxUl`@<0b!s*4VuAeZC0%_ zPiveFxd@k?yJK5!5~X&^A-&5Ia3>%S>?7#1^$Z#f7&Fvf(ZL&=SzP*c>C* znRI=en$Zby2_n~Z6V$fA(1!IE#Kt@0Y8SYfs{u}L1q-}yk>Smx8T`e_N=OqrV$9X7 zJy2h;#AKeg5m=cy%aay~%}k;2mMP%jS!kosPO>LT<~z>K*Z-qFCFy4c{duIHiJy(D zC$q=cy#bE5((oN%{Qh_UKm(+I{tpWGrWHaPjde8SDb*iAm?;39pFcYB;d9no!#QSM z!P#;cj;sRZiU`}=;IL-vXLM4#tv7K}f8vELML6v9c2fULozy7&u-d0{Qhkhq9?H9s zm%6?61BE-3cfAYvBE8g`mQQ-A9kc(da2XiB)=Po6{KCA{u4NY2ywuJuz~Y8w7O(eG zf6Ubz;OoKRtcTtO%?S=j0NmGsn<{|ud~j2aHgHPEz$t+j%Q~exaQr}Hh4H*5@cmxD zaf>EVZ(cQtd(2=D0Z5j2)%{-KYR%dFN|M5o5SCCF-4*K80~`%&5#h^!0Tf?02WVdG z@_%*N+tBplI>)k{5C5_3Zo#yA_FJYEbFBQ2-znVBOgVF`BBLQP5h2giR&-P(E2b)b z3O?|O=@EPP!w+rEbjH}}hq=s{V|otKj;HdL+5fXsu)~n)ChYI&0*V+M3G9E(|KP1T zlkIEVdm)j|B6r7D*tqOwM_1(m#bt17a5M{%Okj`pctcWYNRmR5J0uAq$q|xJNCKc` zDvZ6a|Bu4Wu{mD|9#f`S{&My4+2D>t&bORQ9|6C75!!3eW}szm7%w_#-gv`!5g(^< zB9n^~5FcoM8KcxIQVf;>*M$(lz*K4GS4+mK$an-Yzn;t)lo_*C3_-d`^SO@fhGpwJ0LsU%anyuQLYh*&U}a5g48%NK0ilsa(S+Hcrk_k&Jt$#4e zYIx{gIKReUYr8e9^K($&Em~Z;%5;DJ`%zA`F)IMwd1X=Ie!q&(r^}4b-z4VT^$cwI z^yeRPZY3{vT)k1miCp?`g?s)rVm8F3nbm+SN5=r@7IyFS6<7a4JWdgYUOan0m! z6>b8CX;8eyh!e1TwN6@OK&&Z%7?ck&$ngNg9O!vN>!hUyWYx6R$VMEk)51b349_Nr zKCFtl8c12|DQgko8=HWk;wv$A0hAp)1T9$gW+>>O{?HFygu-r~(OVZFwS>GA3dj|U zsD1A@3ir-s0>dY^R-NC9NGn+UBlh!i{3{8bRxgvo{P|%->Y;`Tp^Aqq#vvfYNQ=Pg zXKMASh<-MdNpqPyX%(7+S~8jSEI;J*=c=My@^XA@*10S}MRrdY92L8q#S9YI$uqQw zWIvB864EnB&fMJFE9H*Lu;+7yGikoaO!qbhB2VgcKWoGDA43ux!9fqU8u&tx4O(8lv zBxPplYlw>76}LTf&fNP1y);Nss>rFs|O9@*42K- zAceYm)F4H=nt(r|u699(bae_ko30K*$Ls1Sbaq`m37tb%UxO}RSD%HhKv!p=Tc@iT z=yZ6?VGuA$cvnSCI$X;jW*zj*AQl=J4U(tBy8(k(snr@Wj#iT^#(V$mzgD=-AFjr; zPZRGySXz$vLPSu1`zwWecM0?!WKGmckQY(?eF=dyd8t^lua=0q17uB_BJh_qg+QH2 zbJem&gZ@&Tg`~BX(lioytwfsCEX~1H8q#zW^pG_6Gnu%JG$$BPdeYLxEGs^!a5fhq zO@mC9ZYoDNnxi|pMn{^Tg)!LaH3oE*w9LSiKw2}*vH^fDk9)x+sF7#spiHK83pu*^ z6}kx%Eu{O-!A?**ciO4X15Ks{H)Z+)PptxW+74@J4kS!=I#Lsr+8mdf*rCl?Qxkq| zE-y9V)#faz2}zqXrzQk#&Xk%!+8nE0NDWtN7uKbQcW4(1Qp0}jLVjx4t6gxUh9&KS zJvA(77kF(JrG}6;YfB|7wOKKh+@Z}DrILPawlJ0SYP0TCQqpGEr;>s;>(XX~R03%; z&QxWkHnTBRxkH;NOI7-{nGLB*uQpSfs+6>ul2oOj%@k`dOQ}kvw@R!w;{m&f2+h_7 zg}d@W4D=~r14D$^u$tNO@ZK=MwEAybpa$By1v;=It!#n$QMYW{F1w?l%|G((>JEm| zr{y3jxAga)mfw)Kg-`Vdf88H>x<7hbeYDu=+-3`-@*wgCk+R84Kgo2>rf}HD4+u^t z8!*NoY`u6_Gtrw?`ur{QRy&zDRjO;KL`0#TNdiZ`77fi*H!4){KH4PkZ#Z1Q^OZ{02v^M8Oo BH1Gfb diff --git a/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/EventExample.polkavm index 6dbc5ca8b108c1ad04cc248b735b2d7d4f43f2a4..cea22e46adcad0dc9bf6375e676dd4923c624c89 100644 GIT binary patch delta 1583 zcmZuxO>7%g5PrK!`qt}EHmx1INof|u&jukdF{N%KkO8T(sZ`l1h#-1!B}=<@E9}C- z&v}s?zl~%ksYE2(@@rbD5g>WvL{%jYDfH3<5=*%t@WE6Z0;;$n_0R(}Y0T_~3JzUq zcUEs^zL{@k-fa3deb=8BN=q|J7*EM>g=K_04YHQPtRP#HSi8feP78M`cnY5k<2e+T5{kUMo5XRZD`sI1(b`qfUjom4 z!Xy1)3iOAI#aM;?H4$7}z)yHmIyl?|w_5cY(T0;XYQKpk} z;r&2=Y%jqB?T1M`7M4OtG;3y`CYZI2zM~~-5VvG|?9_xZ@(S2}$qW+LmQh@*!aIk2 z*hvQ-FRgGGgH=ctY!CN=E$K}{vLefn__&2WIiHS?-$3#4>xgnN!%^4r$S*rde5%K? zgtcPKin6r?YftB9-^nfIF6G*tuW{$L)FBeDs-5pJ6u|Atksi(D>G=dbH*K~6hw3Ss zH4=(?5zvGlFl5=3UHhW{>`A6ivrM8>^!o5tuh(;OWf`Me#4MAqH|v`xq$K`ym!6AK zBVx8x7c7=gp7~=ti9gzPkc=42_=&(+wDuc5Q(GSxDKW$766kZjUSUSGUPGBDJt9omnXReF8jF!E>Ca^Tn4!XE?#bdi_9(X z@UU!*%mm#K0XM|38^Z5~@VOxlx*-m5C&G}6Jmw-Fagm>Lkq?(#=usDX#3!x(udE9| zB46h$6RmT;CXwQyq}1MFx|$wX@TB>ZsplZ~B!P4&joy4UNcbrLM&cTZvIEe4SSTfzIWTOg-3@Bb2c))bS zX&w=~%t;FpfbC*0cz!O*-J}_Uxc-OEzS6L-NUw z_;o**>nrueq&#|O+k7GA_A;MPUOo*O*xT-;uEJ`g7d6(mmLAbdP3ER>}ny{OQNL>F?c+<0!1 vE=K4=h*q?CP%UR7W+g0%ESs16_g2Eha}amsLD_&6zqiWV?eNt>&$Gfmk)k}n delta 1583 zcmZuxPly|36#pi>+ix;i>uj5B(%sq}#XngQ&1|eq7nG<_5(_2SDz4Z=3W1rVWyn{- zc@qn1^^k0)J1zv=XcxWx6%VTvZ$b|}Xm`8u&8$Ta9hiB; z_ul)x_j~VsZ!NqQzVMZXFf>{mqb1>e^ycvbxgZ2ap8C^$0OoJFws@4E|&}@iHIt5Od2rpC&D5>5H^03n2~i-9a}Gs zCuxcVm;whqc{Dm)H54znz;W*@7s;Y7g?WO{+Q+y5T&^F>J>XM$7@x|+`RoS;bp5U9 zaArHfUETY6I+f%TBw(~Y8j5p9>*~<{8dJe9*)}^VCr-S8c9%hgYd|vMMzNEcdd=Gf5A@#naSOC8#`v$C3VJCC!#Jtf#9HtalSBI<`2_d`Vd5Me*WZa>5>%Zo7PBTxCr`+VfbeB`|~A9~VY&IOP(a#DMd-xEzw&epp>r}=bqPxIa(i)A>KOqn5wepF z>%7!k7{X<1#(LGpdhzURh$n9cW=BO5n58lN1SMQ3W{jN^S)FGKaz~z)XIV4F780x; p&&DLZlrri`J^h?R&b4D}H;G`P72l<(ynXZS{J!t)R^L|N z;-}2Q4C7=>zsz-n@YDH3qe9*C$&%9=KMMt&lM$4l2x-sq;R^(wlX*^JNNbeg#jo86 z^83F?MrEYrC2*Yi{jVe&m66lZi2IRb$(zP!3esj5Nfv2-v<3RoGAE}+)D%%BqEryq z(Fs;lrdnaDYN{Gl^=5@OS4gu=#>iDpW)Y`prsH;o2xT$XnMI(%DG%v5LBmMP&^}I* zC;&=5c(1?r?T-u@;6&NpkaldiXag?hA&!_H#D0IQE!le{BX5v28pw~dP|3Tloga|5 z&7nXFY2#+#u_mXOYsEO#F)n&fw86q20X>X>9!`+b;L=6sS~Pe`6UKxv(!xjyBiYs3 zCp0-DE#^nJ!KHn5gx$I;UAvIvp*AOa02Gx2yXUJ!K-(nA(C1+IGrid>h z5ubwvc9koTS;t3cHL}q98Y|0m^H2)KKoA_+&E}6}g#+Q_K&6Xz3kxGJj61@xg^~Ra zwCrzW$(`GpFTZ6-xH})Il&VgsT2j^1IhiXrS81~r$}PjRROVvM_0ZCC4EAI6BXS)$ zEG-zD!Mkw&9qJf!4_r|eZRBa*n-AU`m$rF@?tJ9L_SX!#QMAo}a^rv*y&W&Sb3Mbm zQ?f6?za`rVqMPg#Q!A=URgI~-S(0ruw~0#*YByJjIJJhvspqh&upS}*c&oJ8ntUNQ eOu5eF8kMW=hHHBtdIpYmU71)$5(@@Yk8AVx&koH8y&k=Z*6(=dAEmz^iuiYr} z`@c#?Wx@-xIL-Y2P_i+ZI3h;*?CeznxAZezO>A;6iHJgnMhJWTt_EZ zO_^$msk*5eR5imI4VOq*CFA5WCo_oC0@HE3NQAPO>&zg~;FQO7oTYK36=@$QITQh< z9=zAz`)<2P1~^f6Fr*zDc5J}KJjM~zquB3HwI%z2Wa2H7#v9}(TF48oYG()JO>-!c zN7{rLd8WxJ=2|IEb&Q?hxi(nYBcO*7(8CGx8eH0Ot|X%uG+|5%qacjDFmhe3eL`hV zTFg&wflK@92)j*p+O;b=9&2-=M?g_I(A$Mg6_1229++_4J-^Lj61@xg;Dwsw9;>6 zxt-gZuf7*aygMKHysA&CMou*gndwY5T%lnjmRW>pc{G~}*JAUFDcFzGPsnxPu(V)o z2JgZ7_o!ox3$7@OE-QK~n2TPYkhXb=?tJ9L*0&Z#m?g+n?C^*9Pw46haUc329}tNsl+>)ghYh6mnZgB%EZZcji>5E zUjTy`!h6s@f)M|!blaCOyhqX@tRTLUhYxrD?Lnp18&o2+eZ08H^<+w|;SDOCN3?wb z(cmm9wO>Nxt@V+h7L02_T??vOFzgs%v4vxX&dfMVs4N+-mPvI6SMxZAC(%AEA)Xq2 zAsh}7MVCluBZ|inGyB}UQcfwQly}S$&7vB`)@Z1OApXZGD`j1HWV@gu{(v|Pk3bxp z6rbZC+{KJuCzbWqhXNwU!TX4;><-}&(WRC**Q0o6t~kSgHp5Q#vdaa;Dp_QWS`(8q z<+3taxH?1H@CN)4{FYn{=PagUCq^A}m?or9U?YUZMrO*{d>bpsZc1SZsp?{u zPyi>byNtB%DHsJ9Syb9lAD^y|yhzJ3btPu>P?s>HkGjrTr}%TB^J$jsWiu+9jkCEh zo7XLB<*Toe>RiB@26yFxQhhz(PJ6Cnq0Q%q(l)& z>+@g>M&R>Lw?3x{fEf@2T!;V_P9cQ3P#}b4u#{S}A5keS(DqXT4Vpr!Ju8eCzuEA` z3{Auz@z@%Yg}OkKx??D?UgWu)v7d|fUwFHA?uSCO|98)S<2vSI{mv^lk1qu$`j|nO#fYNfQuXQw}1+6>IY27-Ej0%r1cQ2mCsG;AJCuiOP zj=~V!CPN_Lv_#D?OA~6U43N`9IE#GIcD}zF#htKq82AIxS}A6#R)HmSYnmnFn)z53 z(YZ3Bvr~x96dx1f^^YN3a)u@1%!LR5b(NLlY&uLq&rm_aek%cGK?;mauoL0xc>?-oC)Mij z?^UtmYv2S}is8d+&P>h(pDM^!m!4{c?Vef#oW_h{s6&ub>cu>U9wUST84>5e+VdWO zaT6qa1I9HNb)@)FApjPlf>U@=NgM#hmCQQw!;(2&+W%MHOC>L}4$S={ivREt8+ZDV&dbniKtHzHZfk9JBxnzGWZVLQ679}Q?xI}1AXaCI3wlfQ zw#wO>9pUW(;geHrtz9qRS8LAVM4zECs2JHTT9U=CBXb(1fVSxSiHm_kjvdtc7 zu?Ih8tK08*D9F_cxfMkoTWRYoL|onF#Xafug{2wnw_Csd{b=>*RsjuLe~;qw>Q>tP zH$6JJ>ZJUNsp`^Kdtv#pRgc|-T5(D7#lTSUXWNMErH2_$=euC$h^tG_yz+h&|7j&x zytr-0|4V!jR*XEq0$cg|JF#+?{?x}|$D`g?(4W`NZtE`toyFH54bexU{x>Mtu-;$jT`@Sw189XgGk)uWG3Z|~YE)8ayG)_be@ABDmDng@ zr`QXO$?W@tO-Q9Tx@vDUS6;=nqm9dpjg_xwNNrXeKVFAxYh@VKhMOvGbLB+KlHFXh z8<&66SW#hiW9`+Z+8JEg!&iPu;L=<9Uely88NX_f>a%$A%GGpptohrlgVavprI$$U zrNDANP&!rEmPluE_Z+mCb3uWxL>$^8p;0t-JUpsvHoJ;}m2g1Nhizg z40p93__lOj6=3`_4V1((cLxB)v;xJfBNEG-}bKVi+4McgFRceclUJI7xA8f zjvWKrt*&^F)w8u@Ao)N~M_g!l)+yNEI2Oiit&~<-b zkMoAza$e~}U41)yItHw+2X}1kv89*WC_owcN z_+#-n zucmuL>3B#iXK37@i5N|4v^P|o5QN4< zUaEWhb#FrVcIK)VU{bfKSIc^}q*n_%-@VI>%em@P|J_a)v)7kCl-`w2bec&i=w1x+ zVhK&i4-Hq*tej8zXi_SU^EBy$SM|CHqSuWAae>Cy zo8#$y=>*J!wlW_SmMuHK@`bYzo?oQBO>|15(=j>|qO(R|S75mKqEMW12PQGfXx3MJ z*`1%XM~n(@pG(8Y^@db6h<9H-2baUmv%>($@%aX!im(x31%@D8IwgugVZ9BPH;n#2 zU+zx@tctS=0v0%(brvN^WUNcBV(1G~qMYgJ=S**c%Xx-4hqC2auO`sc;0n6&W>5r9 zH5_=T;Q+%10k>6v-uVqR^Pt5{#vF9oWp05pdyRD5#{>nDWm555n8?OMubgs z>kNnn2{B*Cz@Qm7zLtUXtPMa2m@X|pVu?y-B1EZRCNv8F8y9e0^U0YAzXl_R00-hH zkfI4Kkfli@FiCr3dgBl=XC{a_Jx0u_>_yD4I*j+;V`)5=rZyTj7x2A<>NhicH5s$8wgURCy}a+NAyuF93FT%pR6Dqp6`D^+=gDwnHr znJSB_>{eyy;Z?a*l}l9FrOMo9Rc52A%sfrZgro{Su@s9INaZPFS_@2$IsxXaTYjIt zl1MISGvm{2fASz}CicUlCm|;w$00>FD7X+@hNOJ>d4!h%wa>Y&TfnWG(drghpI#?( zi+Y`89|ZeAxJ9kb$H_V9GZP9xZL*u|xy4+ZaPSA0ix-igZ{cXdW{%V9S~(Z6L;C{S z+c?AzCo)m~b5<0X&DFjUPQ>~cdfiJ%LI)w!#EkDJcKV5*zzHTC$4NHhMFQOfBNLiz zGLt8fx9cA)r3s^5UVB@8`dbBExdY$kYS(Hz%?C+i{lY0b-88;)D$28^m<~k%_Cpeo z&TkWlofiTzX;5U;35iZ(-3EPuteqQoi+0#eK|dWqp{Hk^v^_apg!6Nc9)I?^*nRGdx28@rO7t?=4aMI&m-t zrS_JOb;n8Tj~|4uPeM*WjzfxobBWcaA7Y|?*anO?GoVYm;i15G-hdn{Q#4NM%7PYmN0!pC-=ad`u!Hg`G}gZd@0 zyCk#oecEkpbX#G;+9XB>yjERPUK|%GrN3LuzTl>-V0qS2n55 zjbq>V?l&KVErM1>IcS)P*n`7sF?jd5A+^qZ65-!HZR35u1Xwmo-G?WCJpCQ<4d+FL zou@IpgZQ@7UqW2F7GvFGKLz4;O>zNo6ax<8EW#)>F&sRNfEuN~sL*gaO_6HMgkebG z5C4h%53f7#*`+0eNJBZ}L@ly+khD34M>dtEwgB~FyGG~c}AFj#(i|yJ%1a2 zXE5e%2m|kGfzN0XGq3dAH%P~61-l+`Rs#iADogIZFgw9%pmVV zXG4nq3SF@!#sx*(JdCE19pn1?uv30gT`f-$Zku>rlJgbOqSOX;MsYVPRZ*?>Isf&U)@(MrS4kF>kujyUphTxz1b0P2yS-hD zes5mW{Ih#Wp)wLKD77<6?MQn8S0h<1TxeIG%4mi5Xh8{Ql{d$XyKpqZSNr|7H7!Do zOR8*nRt<$hg+lw22b4W6t*y~QI9iz2YV|P2iH2KSl~(@^x<0E%3tEle-=Nnv=!nz(VV7gk1Vvmu@N`8N3jsI_n`ihE?R=>ZZ61sW|dzFU9 zhWTvvfb!GdwUhrAFK=)?}21$1_sPkfMxXYR3y<1QGcU DwkV1b diff --git a/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm b/substrate/frame/revive/rpc/examples/js/pvm/PiggyBank.polkavm index d0082db90e5e398832e4a32a9ec86dce83d16dd5..78455fcdd7c64a3a1f5e93b6d62cd03b46eb5953 100644 GIT binary patch literal 5088 zcmcIneQX3fRfi9wp*A|&mq_Bk5#2plN60j2-O zPLL0S+z;{)NH54BNC~6@vcIdh*Ooo$u7R#S`?E)eR^NKFUFp@g{+{7(`>nfA>mJzC zKalO~(z^S)59}G}dANH|Z})+LzV3meeTP>y{rkJUyT7}ucTat?A5`=_{P59%u6@1T z&JKHKS?TAydXIMR8R+Xe+<&0Eudh4pZehj_yX?cqjy9&dkMw4T8q2>3Fk@f(9QAq1 zpq``}m^+zW%tOow^H=whdp~=c?MIJrW$p*uM*boGL!P~!Yo1RvZEgN+^HlSLYhGRR z)wN#XcH!f~QQ?b1dg%DD5Mh)2Mk-yZF;)RStp0ZgHQw@yAs}OY{wN(`BPcE~XYXYZ#qbUEy{tenA9VYvhJ>4q z3ob4$H2#4eZ}Be~LX9%kzepKv7B#+pk;=C$rrRQHEQ0)T0UcOMuuszw1X{K#;>;N zYTFUDEu*&WrR#^@bYgng(4=wkDj0LqJVobbaz-w*H^MDNSl&%C(n!HeiqgcOpY+&A zs2wJyc8q}(K}INgTwctdTuh%ZOv#uKh!!#?5YeK>L~b&d8KG=N9zW>vs2vwz$1@;P zAg8FeF)KHm%bfbREfE$J07yJ@J;ClGS~xd2LV@=pD8lhHeHZdlOae6n6jTE^kq1oc z8=8K4Ey2nWmSm!^l3T`l&GXlitcx1Wgd;2+;Ubg(p8()%05AgpGXO9H05dlM-1;c! zdd(f-0N@A$j;x|=WKwt8Gpj&5C>uR-2mjqt!?)ZyZEpne3_YrS;WInG+5FoVe?TR6 ze)jTHUwx?|xwH6}lRtdiy}}CRCM=d&p}ajSKJ6x2YIN`uqATuJJ9I|vI0BLZ*}KfL zo0Qprr)~u}0}fe+JVgo;IGGd#LyM6DGBkx0V0-8*jftJlzzu4ykfn(?HM)McYLZKhXthHCK_C#@8y54=FQ%V0E=b06f^i-h z7jw_$GL*xWdQ|5$Kh3*FwJ;6W0_VaIf+PTgpKpYBfDtYd0#NPy3~O;1qK2l!5HqwR zFerwWfgxpRdza(XyY7ty`zTXS&~1(IqBBaz2$*z%UV;>2Kc?)*DSAoixJ0AzOpG=% zKY|4>+S3-jG_IWj6KeE0C^fWSgZ{XoEz(NI@8I<%m>z-gFOXR;(L7L$M|6_?vrWt- z`};<DrD8-)4? zF&h`!_rvy|Q{ZEAC~DcPgd#;UO122h%AVA#683qpkH!lQ|jEXE&OYetp(wPD=Xh26Ix-XWPw_NUzGeC77kCUKPz!Sg*F3 zrI21-Zq1eND*Y@ors{^BAiE7{SR3b@+xFh$T^T* zfp+!ex1Y6iPtv7|R@%_6cl zW^r6?inpdYOLx`stTogCK+IiWYsVpFrD|pFiq6%3#Lk6Lt$|-C@N@UuN$konm*5VA zLn6@mY^wY-!8R_DOk9;DRT5MQspw;h@koSqIW3RB9~S)ndch4jlC&2xVJvd=sx>6bT{=LG$7yE%jO zSy`_N4wprT%WV#q*E?MHI$U1oaJkjta*J*uhs$dnF0XO8-0X0<$>Flc;WF=VnbTiz zsLVQ4Zgi;ZHg&&lxlCQstp-yUbc-B z*$&e3iVL?t1xv(hjtgUCN+HuJJNWyux$G0!8DP(NVRNm3pj8|n`fzQCg`OH3KI<%N zUF3{IF}v(sL{`BwcaAd-Mr-HT1(UVSMYK?05Z}m&0E0nC^_f{oy!TUx}30#^RXnlH6RtuFXF5Ld~-x91JG{>QW z!=0RbrRx$FMJ%>qaXl8jSX_t2RxGw)QNZF_EUv*~GZve$=)oe7MGlJyi!2r!vFOI4 z3yTd{WUxqM)&_QHvkmOfCL7qHjW)1DJ{#Df4K}bt?KZHw3}}!HbQKbq?By1hmX_?_ z4CG*}Hp#({g35HI1ZX9=fj6O6Lh?$o2jM?xh5e%$sGU(G6N?Ms#lI%lB+SEYqTh3F zGn}CqsMQcPJPX|?#ekEZE^K~1wEfK$@7^&3yL@l1L{E%N$H`0z@_sQpk)4JF1-c~j;2L!k=X`8iPblQ-%nSOpHto3XqJ%Nw!m!}10! zw_{nxvV>(3%WYU*k7X~G*I~I8%Pm+Iu)G$_Yp~pm6!8_O;% zH=Lrd%nV_f{vkAL3ON*;Kko6Ow{EyCTswv!ZYxt@Y~_N{6T=)zpbvmu(cGY&zo0>h z3-5pC)dU-;8yf=jvAEff#Z6e;h(#Y3H(;?Hi!v7fH+1l#U$59_H)(bzu9%QvDkf$V z=!w@I+kz{$MZI9#mthjHT(%9U_y4o)ySK1y>8fK}r(;_xF5IoZl3@P?lVDq_?zeYg zS{lNr8{()J0;vZgsS85s0K}3%;_1^3JD%XH8)jVn5K@OBrn+8>}qNMGgc=h6S?=Rttdi9-)SL{2mAavC!ULOf{zP?hGcn)emwIlSKm+g|Z zV*v&_E_D9vW$LMXklP;gCY7yE1@6pu7K_D79&O1}t>wy@$>7#fJ+p4pj|2 zS}dLkj-Lr8-ZvgpcQMSeW^drWzzL-@U;lOj)Mq*?ghz0D=c7eo2Wr{U*$I30wU<^-I&tG@1UppqbYMKVO)Uo1ty|U8 z!On8U(Yvg>egc@5++B1|SXmE7Jbu#}Z$Kr%&M;WCt+R|fZO0rt)3K!-9IpgRWkF5u z+U8ihq7gPPUtmjlrh_{XoZhvpZ+E#BoLUZUxv{03SIRdIbC#V^86{Z?!lH8UG!EG2 zKL{&Yw}GP7V|L_&ba30&6R-;O-I#~D^=fNCU7b-|W96j1Y3jYlT>1$xoZoP;(^yD=^ta0*cd`uBnDy|5+D1Ze6-nf z=cJ(sDh+Lso_llez3=lr@ALaTAC7(RE{d94LD_#aOZ_@Zm8qzs|FwE+#sSg@;sv=5 z(52O<$1Cj?B0omEs*=fnPR9jEmww({|HJ9ExA8bo4y>)l=wp(xQU0Qq3 zw(gz>yV|t&uJ&EqdO99x-`3f_tEa2IXJ41Tr0KKo*6!~1w$5$U$!<{5@xTN7dfIk$ zw%a?bnFXc$+dB8PZ|mu5)4O-IcXhR=oDIyd?ywFY-dCGy-`n}1SzG*rj~V{a{nUP{ zk9vx#VQyfyF!wS2%m>bS=T7!0+l>sa$i2v|=I`Tgb!~TDbbY^Wef_=lPv5-a#}ala7&&Xee`_*BiNPZJNW1{XTrp44=A_wYrniT=`7p~nNm|;Ub(5TQAmb$+))8vU zD5bUxgXBQ^DSAkr>peV|I%tea#vy@d0pk!NTEsYHj+yCx%2MR=f-aZZavF9#3o;IJ zg!%%r%w98nH!L>2As$PrqvB~ zzq=S`D_#yzz0Kg0Y%mBd56#zFr3c5aY zhB*K@jDRCcC>!bIP1ej3&^F3ON6f~5t5ov~r>SiZBc7o%+7Iv9{PX%ZUVf2^Z@%~Z z(+A$JNo>x&bNHplor|ncuE1h}70MU0;s;KmB{P}tijJ65Z6S=>vKJ%`vVDPNS17Xp zPhJgh1{|^gd6Hx$a5BjXh887RWM~S>!uG&pweii*!VO+^BsSmmQ1f+va(orbp8_h| zmivF|Ff|J=2hR{~)B=`h!!SgNmV-ed@D`47hjoNYnxcwf;+aPma#BmFW;8iT0v-uB}f;~afBN3DI3Z%=5si_`UrKgVU zj8<;8LjCNiI!FprT~wtDtVkLi!AhPx%dt~8qw;BVHd~jd3pz+TdU6n*Bz%Qf(QtAA zkv)Nvr(p&(Dkr)ks zyqTtKu2iGi)VwtB$Y?^DZbOjc? zY)wzo^F!JZFyU2t2$UMy>!3eoXmhmE@)vl08>ahVdYFYalQZZ5xrQ$ zq@x$&tQ$&Dj2u_Uc#=#+$z&|l7s{4Tq4ES5It=y5DmZ7j0}uwID1qLh%nZ~&h}oFX zyc4#+LV=ITo=C}JB@`)=Rzf*oR_L%kBVmsVdwA^Ou!qH-TI_LRj{|#Zu!q4O8rK!Z zBKqvw!dOtBT~inf=(DQ}V_tpMQy7!<*;R!xL7#0d97g(#tWOJtd_|H?uLkAmZeL2@AdUq%GwD&gF}Px*Irs+ZZIaaOu+V4mn`P)2$rq^ z=0+^$MlI$lp~GZ%GBl~rN`(^<{ruX(iJ*RdP2og9Kfk(g!mFS66i!I``BjAzf_}ca zFp2b8S)UPXE{ison`|zxw7KlIxxB*Wa-+@V27MaYTwZQ-d6~`SdYj92HkVyCmwB7Z zoPN%xGHX-0)~2$vK)m|2qd+8mx~4z`eVQp`kWFsrN4=?QVnSg0pKg|sS}*BJ5o8po*iXN-F1~)9HmWYU}j+OY@RPa21^42FPs~5 zC0t3zRDi1l_;X{9satqRFXu*}&E4cM(qOw_?39chU}_cLYY`aN`zg_og^OU}60mRy zf@OPR#`8(zOw2f~0A!?7Do+E)BV@N1bS@;UnT3R9oQfvU^`B-mOD$BYm~h=Q|A@0* z&>Vvb4tKKil};oqidbyI;z}&KvA6<@jaY2JqJYKaSX_q1dMwsq(S=1GiyRga7FjIT zV$q332Nr9v$Y7DitOcyO)&kaCV*zWfwtzJ~7O>_j3s|$+0+z^t21!F#A%V$mZf<^l z-ulfzc1J5?9Q-I4oEXdlT5+!D6R4Gtyb|ng_zzlPZzc`3Gh(EpF(EkjL7Yv%JlrNS zY2RjVnqr_iL1kj)e~8HPDHhrxyWM%?W5C`#WIGh9zzz3$X3_7xluI7q93qVL{-6UA(>-X#HrhD)AiDeri+T&+l3# zYtw5m&@rL)_wQ0q5BRx_es@Ay|FrLhf!16uH!^_M4N#56kz-^2_4$!wBmVVju{BZL zk|>TJEBf=f;Sv9YveD<;px%*)E528TV?P^Cd@Yq|ZfMxRHEcp0Juoml*5BXnUq8{J zs=h~axnuq(j``!EC;aLbhFQ?;_TAw-sI(4Lza0ei>DH0rh+i%GeZFFGYq8jR$L>VY zdRL2zJvKC`>c-+AY|FR8!@sfh(VVaeHLPoGg*`i(^NS}PynHl*9jbcN_k@3gn)9pc z*Q-bUt;G>r?}F~?31C`cYtBAlaXlFE_!Vp1KJ^iQYY;5j&|1W;mSYBwwX7@ppBVAy zi-MZivca}?Q6p?#xWKyNWD9rDKe1&&-_~LyIJM|scX>;3Kq+26%wBd(Wt2qT4~vTa zqu6Jee>bdX+yIJ}j@dNer~Mn&AB0t)@A5p%tyCL*>e7tb7%e8OWeZb2@NzDvfZ1c4 P{Oa%kDsENRt)u<}4o5!@ diff --git a/substrate/frame/revive/uapi/Cargo.toml b/substrate/frame/revive/uapi/Cargo.toml index 7241d667fcdc..cf006941cfd0 100644 --- a/substrate/frame/revive/uapi/Cargo.toml +++ b/substrate/frame/revive/uapi/Cargo.toml @@ -22,7 +22,7 @@ paste = { workspace = true } scale-info = { features = ["derive"], optional = true, workspace = true } [target.'cfg(target_arch = "riscv64")'.dependencies] -polkavm-derive = { version = "0.18.0" } +polkavm-derive = { version = "0.19.0" } [package.metadata.docs.rs] features = ["unstable-hostfn"] From c2531dc12dedfb345c16200229038ef8d04972cc Mon Sep 17 00:00:00 2001 From: Yuri Volkov <0@mcornholio.ru> Date: Fri, 17 Jan 2025 18:00:04 +0100 Subject: [PATCH 281/340] review-bot upgrade (#7214) Upgrading PAPI in review-bot: https://github.com/paritytech/review-bot/issues/140 --- .github/workflows/review-bot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/review-bot.yml b/.github/workflows/review-bot.yml index 3dd5b1114813..27c6162a0fc2 100644 --- a/.github/workflows/review-bot.yml +++ b/.github/workflows/review-bot.yml @@ -29,7 +29,7 @@ jobs: with: artifact-name: pr_number - name: "Evaluates PR reviews and assigns reviewers" - uses: paritytech/review-bot@v2.6.0 + uses: paritytech/review-bot@v2.7.0 with: repo-token: ${{ steps.app_token.outputs.token }} team-token: ${{ steps.app_token.outputs.token }} From 0047c4cb15d3361454c4042f08bc69f28bdada8f Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Fri, 17 Jan 2025 17:48:11 +0000 Subject: [PATCH 282/340] enable-deprecation-warning for old command bot (#7221) Deprecation warning for old command bot --- .github/workflows/command-inform.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/command-inform.yml b/.github/workflows/command-inform.yml index 973463953193..3431eadf7060 100644 --- a/.github/workflows/command-inform.yml +++ b/.github/workflows/command-inform.yml @@ -8,7 +8,7 @@ jobs: comment: runs-on: ubuntu-latest # Temporary disable the bot until the new command bot works properly - if: github.event.issue.pull_request && startsWith(github.event.comment.body, 'bot ') && false # disabled for now, until tested + if: github.event.issue.pull_request && startsWith(github.event.comment.body, 'bot ') steps: - name: Inform that the new command exist uses: actions/github-script@v7 @@ -18,5 +18,5 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: 'We have migrated the command bot to GHA

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

Please, see the new usage instructions here or here. Soon the old commands will be disabled.' + }) From f90a785c1689f7a64bcb161490b4393dd0b65d65 Mon Sep 17 00:00:00 2001 From: Santi Balaguer Date: Fri, 17 Jan 2025 18:50:03 +0100 Subject: [PATCH 283/340] added new proxy ParaRegistration to Westend (#6995) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a new Proxy type to Westend Runtime called ParaRegistration. This is related to: https://github.com/polkadot-fellows/runtimes/pull/520. This new proxy allows: 1. Reserve paraID 2. Register Parachain 3. Leverage Utilites pallet 4. Remove proxy. --------- Co-authored-by: command-bot <> Co-authored-by: Dónal Murray --- polkadot/runtime/westend/src/lib.rs | 10 ++++++++++ prdoc/pr_6995.prdoc | 14 ++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 prdoc/pr_6995.prdoc diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 8a5771fe7cc0..a9ba0778fe0e 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -1087,6 +1087,7 @@ pub enum ProxyType { CancelProxy, Auction, NominationPools, + ParaRegistration, } impl Default for ProxyType { fn default() -> Self { @@ -1183,6 +1184,15 @@ impl InstanceFilter for ProxyType { RuntimeCall::Registrar(..) | RuntimeCall::Slots(..) ), + ProxyType::ParaRegistration => matches!( + c, + RuntimeCall::Registrar(paras_registrar::Call::reserve { .. }) | + RuntimeCall::Registrar(paras_registrar::Call::register { .. }) | + RuntimeCall::Utility(pallet_utility::Call::batch { .. }) | + RuntimeCall::Utility(pallet_utility::Call::batch_all { .. }) | + RuntimeCall::Utility(pallet_utility::Call::force_batch { .. }) | + RuntimeCall::Proxy(pallet_proxy::Call::remove_proxy { .. }) + ), } } fn is_superset(&self, o: &Self) -> bool { diff --git a/prdoc/pr_6995.prdoc b/prdoc/pr_6995.prdoc new file mode 100644 index 000000000000..ffdb4738a6fd --- /dev/null +++ b/prdoc/pr_6995.prdoc @@ -0,0 +1,14 @@ +title: added new proxy ParaRegistration to Westend +doc: +- audience: Runtime User + description: |- + This adds a new Proxy type to Westend Runtime called ParaRegistration. This is related to: https://github.com/polkadot-fellows/runtimes/pull/520. + + This new proxy allows: + 1. Reserve paraID + 2. Register Parachain + 3. Leverage Utilites pallet + 4. Remove proxy. +crates: +- name: westend-runtime + bump: major From 7702fdd1bd869e518bf176ccf0268f83f8927f9b Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Fri, 17 Jan 2025 19:21:38 +0100 Subject: [PATCH 284/340] [pallet-revive] Add tracing support (1/3) (#7166) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add foundation for supporting call traces in pallet_revive Follow up: - PR #7167 Add changes to eth-rpc to introduce debug endpoint that will use pallet-revive tracing features - PR #6727 Add new RPC to the client and implement tracing runtime API that can capture traces on previous blocks --------- Co-authored-by: Alexander Theißen --- Cargo.lock | 1 + .../assets/asset-hub-westend/src/lib.rs | 1 - substrate/bin/node/runtime/src/lib.rs | 1 - substrate/frame/revive/Cargo.toml | 2 + .../frame/revive/fixtures/build/_Cargo.toml | 1 + .../revive/fixtures/contracts/tracing.rs | 75 ++++++ .../fixtures/contracts/tracing_callee.rs | 45 ++++ substrate/frame/revive/rpc/src/client.rs | 46 +--- .../frame/revive/src/benchmarking/mod.rs | 2 +- substrate/frame/revive/src/debug.rs | 109 -------- substrate/frame/revive/src/evm.rs | 45 ++++ substrate/frame/revive/src/evm/api.rs | 5 + substrate/frame/revive/src/evm/api/byte.rs | 74 +----- .../revive/src/evm/api/debug_rpc_types.rs | 219 ++++++++++++++++ .../frame/revive/src/evm/api/hex_serde.rs | 84 +++++++ substrate/frame/revive/src/evm/runtime.rs | 14 +- substrate/frame/revive/src/evm/tracing.rs | 134 ++++++++++ substrate/frame/revive/src/exec.rs | 76 ++++-- substrate/frame/revive/src/lib.rs | 12 +- substrate/frame/revive/src/tests.rs | 158 +++++++++++- .../frame/revive/src/tests/test_debug.rs | 235 ------------------ substrate/frame/revive/src/tracing.rs | 64 +++++ 22 files changed, 912 insertions(+), 491 deletions(-) create mode 100644 substrate/frame/revive/fixtures/contracts/tracing.rs create mode 100644 substrate/frame/revive/fixtures/contracts/tracing_callee.rs delete mode 100644 substrate/frame/revive/src/debug.rs create mode 100644 substrate/frame/revive/src/evm/api/debug_rpc_types.rs create mode 100644 substrate/frame/revive/src/evm/api/hex_serde.rs create mode 100644 substrate/frame/revive/src/evm/tracing.rs delete mode 100644 substrate/frame/revive/src/tests/test_debug.rs create mode 100644 substrate/frame/revive/src/tracing.rs diff --git a/Cargo.lock b/Cargo.lock index 23271617e927..da4e85511919 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14839,6 +14839,7 @@ dependencies = [ "assert_matches", "derive_more 0.99.17", "environmental", + "ethabi-decode 2.0.0", "ethereum-types 0.15.1", "frame-benchmarking 28.0.0", "frame-support 28.0.0", diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 3ef5e87f24c4..41f29fe2c56a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1077,7 +1077,6 @@ impl pallet_revive::Config for Runtime { type InstantiateOrigin = EnsureSigned; type RuntimeHoldReason = RuntimeHoldReason; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); type Xcm = pallet_xcm::Pallet; type ChainId = ConstU64<420_420_421>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 117d306e3060..26f4dacf9a1e 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1491,7 +1491,6 @@ impl pallet_revive::Config for Runtime { type InstantiateOrigin = EnsureSigned; type RuntimeHoldReason = RuntimeHoldReason; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = (); type Xcm = (); type ChainId = ConstU64<420_420_420>; type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. diff --git a/substrate/frame/revive/Cargo.toml b/substrate/frame/revive/Cargo.toml index 49a27cfdaab2..0959cc50638b 100644 --- a/substrate/frame/revive/Cargo.toml +++ b/substrate/frame/revive/Cargo.toml @@ -20,6 +20,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { features = ["derive", "max-encoded-len"], workspace = true } derive_more = { workspace = true } environmental = { workspace = true } +ethabi = { workspace = true } ethereum-types = { workspace = true, features = ["codec", "rlp", "serialize"] } hex = { workspace = true } impl-trait-for-tuples = { workspace = true } @@ -75,6 +76,7 @@ default = ["std"] std = [ "codec/std", "environmental/std", + "ethabi/std", "ethereum-types/std", "frame-benchmarking?/std", "frame-support/std", diff --git a/substrate/frame/revive/fixtures/build/_Cargo.toml b/substrate/frame/revive/fixtures/build/_Cargo.toml index 483d9775b12a..1a0a635420ad 100644 --- a/substrate/frame/revive/fixtures/build/_Cargo.toml +++ b/substrate/frame/revive/fixtures/build/_Cargo.toml @@ -14,6 +14,7 @@ edition = "2021" [dependencies] uapi = { package = 'pallet-revive-uapi', path = "", features = ["unstable-hostfn"], default-features = false } common = { package = 'pallet-revive-fixtures-common', path = "" } +hex-literal = { version = "0.4.1", default-features = false } polkavm-derive = { version = "0.19.0" } [profile.release] diff --git a/substrate/frame/revive/fixtures/contracts/tracing.rs b/substrate/frame/revive/fixtures/contracts/tracing.rs new file mode 100644 index 000000000000..9cbef3bbc843 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/tracing.rs @@ -0,0 +1,75 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! This fixture calls itself as many times as passed as argument. + +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(calls_left: u32, callee_addr: &[u8; 20],); + if calls_left == 0 { + return + } + + let next_input = (calls_left - 1).to_le_bytes(); + api::deposit_event(&[], b"before"); + + // Call the callee, ignore revert. + let _ = api::call( + uapi::CallFlags::empty(), + callee_addr, + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. + &next_input, + None, + ); + + api::deposit_event(&[], b"after"); + + // own address + let mut addr = [0u8; 20]; + api::address(&mut addr); + let mut input = [0u8; 24]; + + input[..4].copy_from_slice(&next_input); + input[4..24].copy_from_slice(&callee_addr[..20]); + + // recurse + api::call( + uapi::CallFlags::ALLOW_REENTRY, + &addr, + u64::MAX, // How much ref_time to devote for the execution. u64::MAX = use all. + u64::MAX, // How much proof_size to devote for the execution. u64::MAX = use all. + &[u8::MAX; 32], // No deposit limit. + &[0u8; 32], // Value transferred to the contract. + &input, + None, + ) + .unwrap(); +} diff --git a/substrate/frame/revive/fixtures/contracts/tracing_callee.rs b/substrate/frame/revive/fixtures/contracts/tracing_callee.rs new file mode 100644 index 000000000000..d44771e417f9 --- /dev/null +++ b/substrate/frame/revive/fixtures/contracts/tracing_callee.rs @@ -0,0 +1,45 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#![no_std] +#![no_main] + +use common::input; +use uapi::{HostFn, HostFnImpl as api}; + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn deploy() {} + +#[no_mangle] +#[polkavm_derive::polkavm_export] +pub extern "C" fn call() { + input!(id: u32, ); + + match id { + // Revert with message "This function always fails" + 2 => { + let data = hex_literal::hex!( + "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000" + ); + api::return_value(uapi::ReturnFlags::REVERT, &data) + }, + 1 => { + panic!("booum"); + }, + _ => api::return_value(uapi::ReturnFlags::empty(), &id.to_le_bytes()), + }; +} diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index cd0effe7faf2..c61c5871f76a 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -27,8 +27,9 @@ use crate::{ use jsonrpsee::types::{error::CALL_EXECUTION_FAILED_CODE, ErrorObjectOwned}; use pallet_revive::{ evm::{ - Block, BlockNumberOrTag, BlockNumberOrTagOrHash, GenericTransaction, ReceiptInfo, - SyncingProgress, SyncingStatus, TransactionSigned, H160, H256, U256, + extract_revert_message, Block, BlockNumberOrTag, BlockNumberOrTagOrHash, + GenericTransaction, ReceiptInfo, SyncingProgress, SyncingStatus, TransactionSigned, H160, + H256, U256, }, EthTransactError, EthTransactInfo, }; @@ -83,47 +84,6 @@ fn unwrap_call_err(err: &subxt::error::RpcError) -> Option { } } -/// Extract the revert message from a revert("msg") solidity statement. -fn extract_revert_message(exec_data: &[u8]) -> Option { - let error_selector = exec_data.get(0..4)?; - - match error_selector { - // assert(false) - [0x4E, 0x48, 0x7B, 0x71] => { - let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?; - - // See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require - let msg = match panic_code { - 0x00 => "generic panic", - 0x01 => "assert(false)", - 0x11 => "arithmetic underflow or overflow", - 0x12 => "division or modulo by zero", - 0x21 => "enum overflow", - 0x22 => "invalid encoded storage byte array accessed", - 0x31 => "out-of-bounds array access; popping on an empty array", - 0x32 => "out-of-bounds access of an array or bytesN", - 0x41 => "out of memory", - 0x51 => "uninitialized function", - code => return Some(format!("execution reverted: unknown panic code: {code:#x}")), - }; - - Some(format!("execution reverted: {msg}")) - }, - // revert(string) - [0x08, 0xC3, 0x79, 0xA0] => { - let decoded = ethabi::decode(&[ethabi::ParamType::String], &exec_data[4..]).ok()?; - if let Some(ethabi::Token::String(msg)) = decoded.first() { - return Some(format!("execution reverted: {msg}")) - } - Some("execution reverted".to_string()) - }, - _ => { - log::debug!(target: LOG_TARGET, "Unknown revert function selector: {error_selector:?}"); - Some("execution reverted".to_string()) - }, - } -} - /// The error type for the client. #[derive(Error, Debug)] pub enum ClientError { diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 18d7bb0afc31..16bdd6d1a18a 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -772,7 +772,7 @@ mod benchmarks { let mut setup = CallSetup::::default(); let input = setup.data(); let (mut ext, _) = setup.ext(); - ext.override_export(crate::debug::ExportedFunction::Constructor); + ext.override_export(crate::exec::ExportedFunction::Constructor); let mut runtime = crate::wasm::Runtime::<_, [u8]>::new(&mut ext, input); diff --git a/substrate/frame/revive/src/debug.rs b/substrate/frame/revive/src/debug.rs deleted file mode 100644 index d1fc0823e03d..000000000000 --- a/substrate/frame/revive/src/debug.rs +++ /dev/null @@ -1,109 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub use crate::{ - exec::{ExecResult, ExportedFunction}, - primitives::ExecReturnValue, -}; -use crate::{Config, LOG_TARGET}; -use sp_core::H160; - -/// Umbrella trait for all interfaces that serves for debugging. -pub trait Debugger: Tracing + CallInterceptor {} - -impl Debugger for V where V: Tracing + CallInterceptor {} - -/// Defines methods to capture contract calls, enabling external observers to -/// measure, trace, and react to contract interactions. -pub trait Tracing { - /// The type of [`CallSpan`] that is created by this trait. - type CallSpan: CallSpan; - - /// Creates a new call span to encompass the upcoming contract execution. - /// - /// This method should be invoked just before the execution of a contract and - /// marks the beginning of a traceable span of execution. - /// - /// # Arguments - /// - /// * `contract_address` - The address of the contract that is about to be executed. - /// * `entry_point` - Describes whether the call is the constructor or a regular call. - /// * `input_data` - The raw input data of the call. - fn new_call_span( - contract_address: &H160, - entry_point: ExportedFunction, - input_data: &[u8], - ) -> Self::CallSpan; -} - -/// Defines a span of execution for a contract call. -pub trait CallSpan { - /// Called just after the execution of a contract. - /// - /// # Arguments - /// - /// * `output` - The raw output of the call. - fn after_call(self, output: &ExecReturnValue); -} - -impl Tracing for () { - type CallSpan = (); - - fn new_call_span(contract_address: &H160, entry_point: ExportedFunction, input_data: &[u8]) { - log::trace!(target: LOG_TARGET, "call {entry_point:?} address: {contract_address:?}, input_data: {input_data:?}") - } -} - -impl CallSpan for () { - fn after_call(self, output: &ExecReturnValue) { - log::trace!(target: LOG_TARGET, "call result {output:?}") - } -} - -/// Provides an interface for intercepting contract calls. -pub trait CallInterceptor { - /// Allows to intercept contract calls and decide whether they should be executed or not. - /// If the call is intercepted, the mocked result of the call is returned. - /// - /// # Arguments - /// - /// * `contract_address` - The address of the contract that is about to be executed. - /// * `entry_point` - Describes whether the call is the constructor or a regular call. - /// * `input_data` - The raw input data of the call. - /// - /// # Expected behavior - /// - /// This method should return: - /// * `Some(ExecResult)` - if the call should be intercepted and the mocked result of the call - /// is returned. - /// * `None` - otherwise, i.e. the call should be executed normally. - fn intercept_call( - contract_address: &H160, - entry_point: ExportedFunction, - input_data: &[u8], - ) -> Option; -} - -impl CallInterceptor for () { - fn intercept_call( - _contract_address: &H160, - _entry_point: ExportedFunction, - _input_data: &[u8], - ) -> Option { - None - } -} diff --git a/substrate/frame/revive/src/evm.rs b/substrate/frame/revive/src/evm.rs index c8c967fbe091..33660a36aa6e 100644 --- a/substrate/frame/revive/src/evm.rs +++ b/substrate/frame/revive/src/evm.rs @@ -19,6 +19,51 @@ mod api; pub use api::*; +mod tracing; +pub use tracing::*; mod gas_encoder; pub use gas_encoder::*; pub mod runtime; + +use crate::alloc::{format, string::*}; + +/// Extract the revert message from a revert("msg") solidity statement. +pub fn extract_revert_message(exec_data: &[u8]) -> Option { + let error_selector = exec_data.get(0..4)?; + + match error_selector { + // assert(false) + [0x4E, 0x48, 0x7B, 0x71] => { + let panic_code: u32 = U256::from_big_endian(exec_data.get(4..36)?).try_into().ok()?; + + // See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require + let msg = match panic_code { + 0x00 => "generic panic", + 0x01 => "assert(false)", + 0x11 => "arithmetic underflow or overflow", + 0x12 => "division or modulo by zero", + 0x21 => "enum overflow", + 0x22 => "invalid encoded storage byte array accessed", + 0x31 => "out-of-bounds array access; popping on an empty array", + 0x32 => "out-of-bounds access of an array or bytesN", + 0x41 => "out of memory", + 0x51 => "uninitialized function", + code => return Some(format!("execution reverted: unknown panic code: {code:#x}")), + }; + + Some(format!("execution reverted: {msg}")) + }, + // revert(string) + [0x08, 0xC3, 0x79, 0xA0] => { + let decoded = ethabi::decode(&[ethabi::ParamKind::String], &exec_data[4..]).ok()?; + if let Some(ethabi::Token::String(msg)) = decoded.first() { + return Some(format!("execution reverted: {}", String::from_utf8_lossy(msg))) + } + Some("execution reverted".to_string()) + }, + _ => { + log::debug!(target: crate::LOG_TARGET, "Unknown revert function selector: {error_selector:?}"); + Some("execution reverted".to_string()) + }, + } +} diff --git a/substrate/frame/revive/src/evm/api.rs b/substrate/frame/revive/src/evm/api.rs index fe18c8735bed..7a34fdc83f9a 100644 --- a/substrate/frame/revive/src/evm/api.rs +++ b/substrate/frame/revive/src/evm/api.rs @@ -16,6 +16,8 @@ // limitations under the License. //! JSON-RPC methods and types, for Ethereum. +mod hex_serde; + mod byte; pub use byte::*; @@ -25,6 +27,9 @@ pub use rlp; mod type_id; pub use type_id::*; +mod debug_rpc_types; +pub use debug_rpc_types::*; + mod rpc_types; mod rpc_types_gen; pub use rpc_types_gen::*; diff --git a/substrate/frame/revive/src/evm/api/byte.rs b/substrate/frame/revive/src/evm/api/byte.rs index c2d64f8e5e42..f11966d0072c 100644 --- a/substrate/frame/revive/src/evm/api/byte.rs +++ b/substrate/frame/revive/src/evm/api/byte.rs @@ -15,79 +15,16 @@ // See the License for the specific language governing permissions and // limitations under the License. //! Define Byte wrapper types for encoding and decoding hex strings +use super::hex_serde::HexCodec; use alloc::{vec, vec::Vec}; use codec::{Decode, Encode}; use core::{ fmt::{Debug, Display, Formatter, Result as FmtResult}, str::FromStr, }; -use hex_serde::HexCodec; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; -mod hex_serde { - #[cfg(not(feature = "std"))] - use alloc::{format, string::String, vec::Vec}; - use serde::{Deserialize, Deserializer, Serializer}; - - pub trait HexCodec: Sized { - type Error; - fn to_hex(&self) -> String; - fn from_hex(s: String) -> Result; - } - - impl HexCodec for u8 { - type Error = core::num::ParseIntError; - fn to_hex(&self) -> String { - format!("0x{:x}", self) - } - fn from_hex(s: String) -> Result { - u8::from_str_radix(s.trim_start_matches("0x"), 16) - } - } - - impl HexCodec for [u8; T] { - type Error = hex::FromHexError; - fn to_hex(&self) -> String { - format!("0x{}", hex::encode(self)) - } - fn from_hex(s: String) -> Result { - let data = hex::decode(s.trim_start_matches("0x"))?; - data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength) - } - } - - impl HexCodec for Vec { - type Error = hex::FromHexError; - fn to_hex(&self) -> String { - format!("0x{}", hex::encode(self)) - } - fn from_hex(s: String) -> Result { - hex::decode(s.trim_start_matches("0x")) - } - } - - pub fn serialize(value: &T, serializer: S) -> Result - where - S: Serializer, - T: HexCodec, - { - let s = value.to_hex(); - serializer.serialize_str(&s) - } - - pub fn deserialize<'de, D, T>(deserializer: D) -> Result - where - D: Deserializer<'de>, - T: HexCodec, - ::Error: core::fmt::Debug, - { - let s = String::deserialize(deserializer)?; - let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; - Ok(value) - } -} - impl FromStr for Bytes { type Err = hex::FromHexError; fn from_str(s: &str) -> Result { @@ -100,7 +37,7 @@ macro_rules! impl_hex { ($type:ident, $inner:ty, $default:expr) => { #[derive(Encode, Decode, Eq, PartialEq, TypeInfo, Clone, Serialize, Deserialize)] #[doc = concat!("`", stringify!($inner), "`", " wrapper type for encoding and decoding hex strings")] - pub struct $type(#[serde(with = "hex_serde")] pub $inner); + pub struct $type(#[serde(with = "crate::evm::api::hex_serde")] pub $inner); impl Default for $type { fn default() -> Self { @@ -131,6 +68,13 @@ macro_rules! impl_hex { }; } +impl Bytes { + /// See `Vec::is_empty` + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + impl_hex!(Byte, u8, 0u8); impl_hex!(Bytes, Vec, vec![]); impl_hex!(Bytes8, [u8; 8], [0u8; 8]); diff --git a/substrate/frame/revive/src/evm/api/debug_rpc_types.rs b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs new file mode 100644 index 000000000000..0857a59fbf3b --- /dev/null +++ b/substrate/frame/revive/src/evm/api/debug_rpc_types.rs @@ -0,0 +1,219 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::evm::{Bytes, CallTracer}; +use alloc::{fmt, string::String, vec::Vec}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use serde::{ + de::{self, MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; +use sp_core::{H160, H256, U256}; + +/// Tracer configuration used to trace calls. +#[derive(TypeInfo, Debug, Clone, Encode, Decode, Serialize, PartialEq)] +#[serde(tag = "tracer", content = "tracerConfig")] +pub enum TracerConfig { + /// A tracer that captures call traces. + #[serde(rename = "callTracer")] + CallTracer { + /// Whether or not to capture logs. + #[serde(rename = "withLog")] + with_logs: bool, + }, +} + +impl TracerConfig { + /// Build the tracer associated to this config. + pub fn build(self, gas_mapper: G) -> CallTracer { + match self { + Self::CallTracer { with_logs } => CallTracer::new(with_logs, gas_mapper), + } + } +} + +/// Custom deserializer to support the following JSON format: +/// +/// ```json +/// { "tracer": "callTracer", "tracerConfig": { "withLogs": false } } +/// ``` +/// +/// ```json +/// { "tracer": "callTracer" } +/// ``` +impl<'de> Deserialize<'de> for TracerConfig { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct TracerConfigVisitor; + + impl<'de> Visitor<'de> for TracerConfigVisitor { + type Value = TracerConfig; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a map with tracer and optional tracerConfig") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut tracer_type: Option = None; + let mut with_logs = None; + + while let Some(key) = map.next_key::()? { + match key.as_str() { + "tracer" => { + tracer_type = map.next_value()?; + }, + "tracerConfig" => { + #[derive(Deserialize)] + struct CallTracerConfig { + #[serde(rename = "withLogs")] + with_logs: Option, + } + let inner: CallTracerConfig = map.next_value()?; + with_logs = inner.with_logs; + }, + _ => {}, + } + } + + match tracer_type.as_deref() { + Some("callTracer") => + Ok(TracerConfig::CallTracer { with_logs: with_logs.unwrap_or(true) }), + _ => Err(de::Error::custom("Unsupported or missing tracer type")), + } + } + } + + deserializer.deserialize_map(TracerConfigVisitor) + } +} + +#[test] +fn test_tracer_config_serialization() { + let tracers = vec![ + (r#"{"tracer": "callTracer"}"#, TracerConfig::CallTracer { with_logs: true }), + ( + r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": true }}"#, + TracerConfig::CallTracer { with_logs: true }, + ), + ( + r#"{"tracer": "callTracer", "tracerConfig": { "withLogs": false }}"#, + TracerConfig::CallTracer { with_logs: false }, + ), + ]; + + for (json_data, expected) in tracers { + let result: TracerConfig = + serde_json::from_str(json_data).expect("Deserialization should succeed"); + assert_eq!(result, expected); + } +} + +impl Default for TracerConfig { + fn default() -> Self { + TracerConfig::CallTracer { with_logs: false } + } +} + +/// The type of call that was executed. +#[derive( + Default, TypeInfo, Encode, Decode, Serialize, Deserialize, Eq, PartialEq, Clone, Debug, +)] +#[serde(rename_all = "UPPERCASE")] +pub enum CallType { + /// A regular call. + #[default] + Call, + /// A read-only call. + StaticCall, + /// A delegate call. + DelegateCall, +} + +/// A smart contract execution call trace. +#[derive( + TypeInfo, Default, Encode, Decode, Serialize, Deserialize, Clone, Debug, Eq, PartialEq, +)] +pub struct CallTrace { + /// Address of the sender. + pub from: H160, + /// Address of the receiver. + pub to: H160, + /// Call input data. + pub input: Vec, + /// Amount of value transferred. + #[serde(skip_serializing_if = "U256::is_zero")] + pub value: U256, + /// Type of call. + #[serde(rename = "type")] + pub call_type: CallType, + /// Amount of gas provided for the call. + pub gas: Gas, + /// Amount of gas used. + #[serde(rename = "gasUsed")] + pub gas_used: Gas, + /// Return data. + #[serde(flatten, skip_serializing_if = "Bytes::is_empty")] + pub output: Bytes, + /// The error message if the call failed. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// The revert reason, if the call reverted. + #[serde(rename = "revertReason")] + pub revert_reason: Option, + /// List of sub-calls. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub calls: Vec>, + /// List of logs emitted during the call. + #[serde(skip_serializing_if = "Vec::is_empty")] + pub logs: Vec, +} + +/// A log emitted during a call. +#[derive( + Debug, Default, Clone, Encode, Decode, TypeInfo, Serialize, Deserialize, Eq, PartialEq, +)] +pub struct CallLog { + /// The address of the contract that emitted the log. + pub address: H160, + /// The log's data. + #[serde(skip_serializing_if = "Bytes::is_empty")] + pub data: Bytes, + /// The topics used to index the log. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub topics: Vec, + /// Position of the log relative to subcalls within the same trace + /// See for details + #[serde(with = "super::hex_serde")] + pub position: u32, +} + +/// A transaction trace +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct TransactionTrace { + /// The transaction hash. + #[serde(rename = "txHash")] + pub tx_hash: H256, + /// The trace of the transaction. + #[serde(rename = "result")] + pub trace: CallTrace, +} diff --git a/substrate/frame/revive/src/evm/api/hex_serde.rs b/substrate/frame/revive/src/evm/api/hex_serde.rs new file mode 100644 index 000000000000..ba07b36fa4be --- /dev/null +++ b/substrate/frame/revive/src/evm/api/hex_serde.rs @@ -0,0 +1,84 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use alloc::{format, string::String, vec::Vec}; +use serde::{Deserialize, Deserializer, Serializer}; + +pub trait HexCodec: Sized { + type Error; + fn to_hex(&self) -> String; + fn from_hex(s: String) -> Result; +} + +macro_rules! impl_hex_codec { + ($($t:ty),*) => { + $( + impl HexCodec for $t { + type Error = core::num::ParseIntError; + fn to_hex(&self) -> String { + format!("0x{:x}", self) + } + fn from_hex(s: String) -> Result { + <$t>::from_str_radix(s.trim_start_matches("0x"), 16) + } + } + )* + }; +} + +impl_hex_codec!(u8, u32); + +impl HexCodec for [u8; T] { + type Error = hex::FromHexError; + fn to_hex(&self) -> String { + format!("0x{}", hex::encode(self)) + } + fn from_hex(s: String) -> Result { + let data = hex::decode(s.trim_start_matches("0x"))?; + data.try_into().map_err(|_| hex::FromHexError::InvalidStringLength) + } +} + +impl HexCodec for Vec { + type Error = hex::FromHexError; + fn to_hex(&self) -> String { + format!("0x{}", hex::encode(self)) + } + fn from_hex(s: String) -> Result { + hex::decode(s.trim_start_matches("0x")) + } +} + +pub fn serialize(value: &T, serializer: S) -> Result +where + S: Serializer, + T: HexCodec, +{ + let s = value.to_hex(); + serializer.serialize_str(&s) +} + +pub fn deserialize<'de, D, T>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: HexCodec, + ::Error: core::fmt::Debug, +{ + let s = String::deserialize(deserializer)?; + let value = T::from_hex(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e)))?; + Ok(value) +} diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index d4b344e20eb8..0e5fc3da545b 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -20,7 +20,7 @@ use crate::{ api::{GenericTransaction, TransactionSigned}, GasEncoder, }, - AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, LOG_TARGET, + AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, Weight, LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode}; @@ -72,6 +72,18 @@ where } } +/// Convert a `Weight` into a gas value, using the fixed `GAS_PRICE`. +/// and the `Config::WeightPrice` to compute the fee. +/// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer. +pub fn gas_from_weight(weight: Weight) -> U256 +where + BalanceOf: Into, +{ + use sp_runtime::traits::Convert; + let fee: BalanceOf = T::WeightPrice::convert(weight); + gas_from_fee(fee) +} + /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] diff --git a/substrate/frame/revive/src/evm/tracing.rs b/substrate/frame/revive/src/evm/tracing.rs new file mode 100644 index 000000000000..7466ec1de487 --- /dev/null +++ b/substrate/frame/revive/src/evm/tracing.rs @@ -0,0 +1,134 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use crate::{ + evm::{extract_revert_message, CallLog, CallTrace, CallType}, + primitives::ExecReturnValue, + tracing::Tracer, + DispatchError, Weight, +}; +use alloc::{format, string::ToString, vec::Vec}; +use sp_core::{H160, H256, U256}; + +/// A Tracer that reports logs and nested call traces transactions. +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct CallTracer { + /// Map Weight to Gas equivalent. + gas_mapper: GasMapper, + /// Store all in-progress CallTrace instances. + traces: Vec>, + /// Stack of indices to the current active traces. + current_stack: Vec, + /// whether or not to capture logs. + with_log: bool, +} + +impl CallTracer { + /// Create a new [`CallTracer`] instance. + pub fn new(with_log: bool, gas_mapper: GasMapper) -> Self { + Self { gas_mapper, traces: Vec::new(), current_stack: Vec::new(), with_log } + } + + /// Collect the traces and return them. + pub fn collect_traces(&mut self) -> Vec> { + core::mem::take(&mut self.traces) + } +} + +impl Gas> Tracer for CallTracer { + fn enter_child_span( + &mut self, + from: H160, + to: H160, + is_delegate_call: bool, + is_read_only: bool, + value: U256, + input: &[u8], + gas_left: Weight, + ) { + let call_type = if is_read_only { + CallType::StaticCall + } else if is_delegate_call { + CallType::DelegateCall + } else { + CallType::Call + }; + + self.traces.push(CallTrace { + from, + to, + value, + call_type, + input: input.to_vec(), + gas: (self.gas_mapper)(gas_left), + ..Default::default() + }); + + // Push the index onto the stack of the current active trace + self.current_stack.push(self.traces.len() - 1); + } + + fn log_event(&mut self, address: H160, topics: &[H256], data: &[u8]) { + if !self.with_log { + return; + } + + let current_index = self.current_stack.last().unwrap(); + let position = self.traces[*current_index].calls.len() as u32; + let log = + CallLog { address, topics: topics.to_vec(), data: data.to_vec().into(), position }; + + let current_index = *self.current_stack.last().unwrap(); + self.traces[current_index].logs.push(log); + } + + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_used: Weight) { + // Set the output of the current trace + let current_index = self.current_stack.pop().unwrap(); + let trace = &mut self.traces[current_index]; + trace.output = output.data.clone().into(); + trace.gas_used = (self.gas_mapper)(gas_used); + + if output.did_revert() { + trace.revert_reason = extract_revert_message(&output.data); + trace.error = Some("execution reverted".to_string()); + } + + // Move the current trace into its parent + if let Some(parent_index) = self.current_stack.last() { + let child_trace = self.traces.remove(current_index); + self.traces[*parent_index].calls.push(child_trace); + } + } + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_used: Weight) { + // Set the output of the current trace + let current_index = self.current_stack.pop().unwrap(); + let trace = &mut self.traces[current_index]; + trace.gas_used = (self.gas_mapper)(gas_used); + + trace.error = match error { + DispatchError::Module(sp_runtime::ModuleError { message, .. }) => + Some(message.unwrap_or_default().to_string()), + _ => Some(format!("{:?}", error)), + }; + + // Move the current trace into its parent + if let Some(parent_index) = self.current_stack.last() { + let child_trace = self.traces.remove(current_index); + self.traces[*parent_index].calls.push(child_trace); + } + } +} diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index f696f75a4a13..d2ef6c9c7ba6 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -17,12 +17,12 @@ use crate::{ address::{self, AddressMapper}, - debug::{CallInterceptor, CallSpan, Tracing}, gas::GasMeter, limits, primitives::{ExecReturnValue, StorageDeposit}, runtime_decl_for_revive_api::{Decode, Encode, RuntimeDebugNoBound, TypeInfo}, storage::{self, meter::Diff, WriteOutcome}, + tracing::if_tracing, transient_storage::TransientStorage, BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, Error, Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, @@ -773,7 +773,25 @@ where )? { stack.run(executable, input_data).map(|_| stack.first_frame.last_frame_output) } else { - Self::transfer_from_origin(&origin, &origin, &dest, value) + if_tracing(|t| { + let address = + origin.account_id().map(T::AddressMapper::to_address).unwrap_or_default(); + let dest = T::AddressMapper::to_address(&dest); + t.enter_child_span(address, dest, false, false, value, &input_data, Weight::zero()); + }); + + let result = Self::transfer_from_origin(&origin, &origin, &dest, value); + match result { + Ok(ref output) => { + if_tracing(|t| { + t.exit_child_span(&output, Weight::zero()); + }); + }, + Err(e) => { + if_tracing(|t| t.exit_child_span_with_error(e.error.into(), Weight::zero())); + }, + } + result } } @@ -1018,6 +1036,7 @@ where fn run(&mut self, executable: E, input_data: Vec) -> Result<(), ExecError> { let frame = self.top_frame(); let entry_point = frame.entry_point; + let is_delegate_call = frame.delegate.is_some(); let delegated_code_hash = if frame.delegate.is_some() { Some(*executable.code_hash()) } else { None }; @@ -1038,6 +1057,9 @@ where let do_transaction = || -> ExecResult { let caller = self.caller(); let frame = top_frame_mut!(self); + let read_only = frame.read_only; + let value_transferred = frame.value_transferred; + let account_id = &frame.account_id.clone(); // We need to charge the storage deposit before the initial transfer so that // it can create the account in case the initial transfer is < ed. @@ -1045,10 +1067,11 @@ where // Root origin can't be used to instantiate a contract, so it is safe to assume that // if we reached this point the origin has an associated account. let origin = &self.origin.account_id()?; + frame.nested_storage.charge_instantiate( origin, - &frame.account_id, - frame.contract_info.get(&frame.account_id), + &account_id, + frame.contract_info.get(&account_id), executable.code_info(), self.skip_transfer, )?; @@ -1069,15 +1092,34 @@ where )?; } - let contract_address = T::AddressMapper::to_address(&top_frame!(self).account_id); - - let call_span = T::Debug::new_call_span(&contract_address, entry_point, &input_data); + let contract_address = T::AddressMapper::to_address(account_id); + let maybe_caller_address = caller.account_id().map(T::AddressMapper::to_address); + + if_tracing(|tracer| { + tracer.enter_child_span( + maybe_caller_address.unwrap_or_default(), + contract_address, + is_delegate_call, + read_only, + value_transferred, + &input_data, + frame.nested_gas.gas_left(), + ); + }); - let output = T::Debug::intercept_call(&contract_address, entry_point, &input_data) - .unwrap_or_else(|| executable.execute(self, entry_point, input_data)) - .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?; + let output = executable.execute(self, entry_point, input_data).map_err(|e| { + if_tracing(|tracer| { + tracer.exit_child_span_with_error( + e.error, + top_frame_mut!(self).nested_gas.gas_consumed(), + ); + }); + ExecError { error: e.error, origin: ErrorOrigin::Callee } + })?; - call_span.after_call(&output); + if_tracing(|tracer| { + tracer.exit_child_span(&output, top_frame_mut!(self).nested_gas.gas_consumed()); + }); // Avoid useless work that would be reverted anyways. if output.did_revert() { @@ -1353,7 +1395,7 @@ where &mut self, gas_limit: Weight, deposit_limit: U256, - dest: &H160, + dest_addr: &H160, value: U256, input_data: Vec, allows_reentry: bool, @@ -1369,7 +1411,7 @@ where *self.last_frame_output_mut() = Default::default(); let try_call = || { - let dest = T::AddressMapper::to_account_id(dest); + let dest = T::AddressMapper::to_account_id(dest_addr); if !self.allows_reentry(&dest) { return Err(>::ReentranceDenied.into()); } @@ -1661,11 +1703,11 @@ where } fn deposit_event(&mut self, topics: Vec, data: Vec) { - Contracts::::deposit_event(Event::ContractEmitted { - contract: T::AddressMapper::to_address(self.account_id()), - data, - topics, + let contract = T::AddressMapper::to_address(self.account_id()); + if_tracing(|tracer| { + tracer.log_event(contract, &topics, &data); }); + Contracts::::deposit_event(Event::ContractEmitted { contract, data, topics }); } fn block_number(&self) -> U256 { diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index a9f2842c35f6..c36cb3f47cae 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -35,9 +35,9 @@ mod wasm; mod tests; pub mod chain_extension; -pub mod debug; pub mod evm; pub mod test_utils; +pub mod tracing; pub mod weights; use crate::{ @@ -83,7 +83,6 @@ use sp_runtime::{ pub use crate::{ address::{create1, create2, AccountId32Mapper, AddressMapper}, - debug::Tracing, exec::{MomentOf, Origin}, pallet::*, }; @@ -118,7 +117,6 @@ const LOG_TARGET: &str = "runtime::revive"; #[frame_support::pallet] pub mod pallet { use super::*; - use crate::debug::Debugger; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use sp_core::U256; @@ -255,12 +253,6 @@ pub mod pallet { #[pallet::no_default_bounds] type InstantiateOrigin: EnsureOrigin; - /// Debugging utilities for contracts. - /// For production chains, it's recommended to use the `()` implementation of this - /// trait. - #[pallet::no_default_bounds] - type Debug: Debugger; - /// A type that exposes XCM APIs, allowing contracts to interact with other parachains, and /// execute XCM programs. #[pallet::no_default_bounds] @@ -367,7 +359,6 @@ pub mod pallet { type InstantiateOrigin = EnsureSigned; type WeightInfo = (); type WeightPrice = Self; - type Debug = (); type Xcm = (); type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; @@ -1146,7 +1137,6 @@ where DepositLimit::Unchecked }; - // TODO remove once we have revisited how we encode the gas limit. if tx.nonce.is_none() { tx.nonce = Some(>::account_nonce(&origin).into()); } diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 8398bc2cb66f..90b9f053a03f 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -16,12 +16,8 @@ // limitations under the License. mod pallet_dummy; -mod test_debug; -use self::{ - test_debug::TestDebug, - test_utils::{ensure_stored, expected_deposit}, -}; +use self::test_utils::{ensure_stored, expected_deposit}; use crate::{ self as pallet_revive, address::{create1, create2, AddressMapper}, @@ -29,13 +25,14 @@ use crate::{ ChainExtension, Environment, Ext, RegisteredChainExtension, Result as ExtensionResult, RetVal, ReturnFlags, }, - evm::{runtime::GAS_PRICE, GenericTransaction}, + evm::{runtime::GAS_PRICE, CallTrace, CallTracer, CallType, GenericTransaction}, exec::Key, limits, primitives::CodeUploadReturnValue, storage::DeletionQueueManager, test_utils::*, tests::test_utils::{get_contract, get_contract_checked}, + tracing::trace, wasm::Memory, weights::WeightInfo, AccountId32Mapper, BalanceOf, Code, CodeInfoOf, Config, ContractInfo, ContractInfoOf, @@ -523,7 +520,6 @@ impl Config for Test { type UploadOrigin = EnsureAccount; type InstantiateOrigin = EnsureAccount; type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; - type Debug = TestDebug; type ChainId = ChainId; } @@ -4554,3 +4550,151 @@ fn unstable_interface_rejected() { assert_ok!(builder::bare_instantiate(Code::Upload(code)).build().result); }); } + +#[test] +fn tracing_works_for_transfers() { + ExtBuilder::default().build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 100_000_000); + let mut tracer = CallTracer::new(false, |_| U256::zero()); + trace(&mut tracer, || { + builder::bare_call(BOB_ADDR).value(10_000_000).build_and_unwrap_result(); + }); + assert_eq!( + tracer.collect_traces(), + vec![CallTrace { + from: ALICE_ADDR, + to: BOB_ADDR, + value: U256::from(10_000_000), + call_type: CallType::Call, + ..Default::default() + },] + ) + }); +} + +#[test] +fn tracing_works() { + use crate::evm::*; + use CallType::*; + let (code, _code_hash) = compile_module("tracing").unwrap(); + let (wasm_callee, _) = compile_module("tracing_callee").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = ::Currency::set_balance(&ALICE, 1_000_000); + + let Contract { addr: addr_callee, .. } = + builder::bare_instantiate(Code::Upload(wasm_callee)).build_and_unwrap_contract(); + + let Contract { addr, .. } = + builder::bare_instantiate(Code::Upload(code)).build_and_unwrap_contract(); + + let tracer_options = vec![ + ( false , vec![]), + ( + true , + vec![ + CallLog { + address: addr, + topics: Default::default(), + data: b"before".to_vec().into(), + position: 0, + }, + CallLog { + address: addr, + topics: Default::default(), + data: b"after".to_vec().into(), + position: 1, + }, + ], + ), + ]; + + // Verify that the first trace report the same weight reported by bare_call + let mut tracer = CallTracer::new(false, |w| w); + let gas_used = trace(&mut tracer, || { + builder::bare_call(addr).data((3u32, addr_callee).encode()).build().gas_consumed + }); + let traces = tracer.collect_traces(); + assert_eq!(&traces[0].gas_used, &gas_used); + + // Discarding gas usage, check that traces reported are correct + for (with_logs, logs) in tracer_options { + let mut tracer = CallTracer::new(with_logs, |_| U256::zero()); + trace(&mut tracer, || { + builder::bare_call(addr).data((3u32, addr_callee).encode()).build() + }); + + + assert_eq!( + tracer.collect_traces(), + vec![CallTrace { + from: ALICE_ADDR, + to: addr, + input: (3u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 2u32.encode(), + output: hex_literal::hex!( + "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a546869732066756e6374696f6e20616c77617973206661696c73000000000000" + ).to_vec().into(), + revert_reason: Some( + "execution reverted: This function always fails".to_string() + ), + error: Some("execution reverted".to_string()), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (2u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 1u32.encode(), + output: Default::default(), + error: Some("ContractTrapped".to_string()), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (1u32, addr_callee).encode(), + call_type: Call, + logs: logs.clone(), + calls: vec![ + CallTrace { + from: addr, + to: addr_callee, + input: 0u32.encode(), + output: 0u32.to_le_bytes().to_vec().into(), + call_type: Call, + ..Default::default() + }, + CallTrace { + from: addr, + to: addr, + input: (0u32, addr_callee).encode(), + call_type: Call, + ..Default::default() + }, + ], + ..Default::default() + }, + ], + ..Default::default() + }, + ], + ..Default::default() + },] + ); + } + }); +} diff --git a/substrate/frame/revive/src/tests/test_debug.rs b/substrate/frame/revive/src/tests/test_debug.rs deleted file mode 100644 index b1fdb2d47441..000000000000 --- a/substrate/frame/revive/src/tests/test_debug.rs +++ /dev/null @@ -1,235 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::*; - -use crate::{ - debug::{CallInterceptor, CallSpan, ExecResult, ExportedFunction, Tracing}, - primitives::ExecReturnValue, - test_utils::*, - DepositLimit, -}; -use frame_support::traits::Currency; -use pretty_assertions::assert_eq; -use sp_core::H160; -use std::cell::RefCell; - -#[derive(Clone, PartialEq, Eq, Debug)] -struct DebugFrame { - contract_address: sp_core::H160, - call: ExportedFunction, - input: Vec, - result: Option>, -} - -thread_local! { - static DEBUG_EXECUTION_TRACE: RefCell> = RefCell::new(Vec::new()); - static INTERCEPTED_ADDRESS: RefCell> = RefCell::new(None); -} - -pub struct TestDebug; -pub struct TestCallSpan { - contract_address: sp_core::H160, - call: ExportedFunction, - input: Vec, -} - -impl Tracing for TestDebug { - type CallSpan = TestCallSpan; - - fn new_call_span( - contract_address: &crate::H160, - entry_point: ExportedFunction, - input_data: &[u8], - ) -> TestCallSpan { - DEBUG_EXECUTION_TRACE.with(|d| { - d.borrow_mut().push(DebugFrame { - contract_address: *contract_address, - call: entry_point, - input: input_data.to_vec(), - result: None, - }) - }); - TestCallSpan { - contract_address: *contract_address, - call: entry_point, - input: input_data.to_vec(), - } - } -} - -impl CallInterceptor for TestDebug { - fn intercept_call( - contract_address: &sp_core::H160, - _entry_point: ExportedFunction, - _input_data: &[u8], - ) -> Option { - INTERCEPTED_ADDRESS.with(|i| { - if i.borrow().as_ref() == Some(contract_address) { - Some(Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![] })) - } else { - None - } - }) - } -} - -impl CallSpan for TestCallSpan { - fn after_call(self, output: &ExecReturnValue) { - DEBUG_EXECUTION_TRACE.with(|d| { - d.borrow_mut().push(DebugFrame { - contract_address: self.contract_address, - call: self.call, - input: self.input, - result: Some(output.data.clone()), - }) - }); - } -} - -#[test] -fn debugging_works() { - let (wasm_caller, _) = compile_module("call").unwrap(); - let (wasm_callee, _) = compile_module("store_call").unwrap(); - - fn current_stack() -> Vec { - DEBUG_EXECUTION_TRACE.with(|stack| stack.borrow().clone()) - } - - fn deploy(wasm: Vec) -> H160 { - Contracts::bare_instantiate( - RuntimeOrigin::signed(ALICE), - 0, - GAS_LIMIT, - DepositLimit::Balance(deposit_limit::()), - Code::Upload(wasm), - vec![], - Some([0u8; 32]), - ) - .result - .unwrap() - .addr - } - - fn constructor_frame(contract_address: &H160, after: bool) -> DebugFrame { - DebugFrame { - contract_address: *contract_address, - call: ExportedFunction::Constructor, - input: vec![], - result: if after { Some(vec![]) } else { None }, - } - } - - fn call_frame(contract_address: &H160, args: Vec, after: bool) -> DebugFrame { - DebugFrame { - contract_address: *contract_address, - call: ExportedFunction::Call, - input: args, - result: if after { Some(vec![]) } else { None }, - } - } - - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = Balances::deposit_creating(&ALICE, 1_000_000); - - assert_eq!(current_stack(), vec![]); - - let addr_caller = deploy(wasm_caller); - let addr_callee = deploy(wasm_callee); - - assert_eq!( - current_stack(), - vec![ - constructor_frame(&addr_caller, false), - constructor_frame(&addr_caller, true), - constructor_frame(&addr_callee, false), - constructor_frame(&addr_callee, true), - ] - ); - - let main_args = (100u32, &addr_callee.clone()).encode(); - let inner_args = (100u32).encode(); - - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr_caller, - 0, - GAS_LIMIT, - deposit_limit::(), - main_args.clone() - )); - - let stack_top = current_stack()[4..].to_vec(); - assert_eq!( - stack_top, - vec![ - call_frame(&addr_caller, main_args.clone(), false), - call_frame(&addr_callee, inner_args.clone(), false), - call_frame(&addr_callee, inner_args, true), - call_frame(&addr_caller, main_args, true), - ] - ); - }); -} - -#[test] -fn call_interception_works() { - let (wasm, _) = compile_module("dummy").unwrap(); - - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { - let _ = Balances::deposit_creating(&ALICE, 1_000_000); - - let account_id = Contracts::bare_instantiate( - RuntimeOrigin::signed(ALICE), - 0, - GAS_LIMIT, - deposit_limit::().into(), - Code::Upload(wasm), - vec![], - // some salt to ensure that the address of this contract is unique among all tests - Some([0x41; 32]), - ) - .result - .unwrap() - .addr; - - // no interception yet - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - account_id, - 0, - GAS_LIMIT, - deposit_limit::(), - vec![], - )); - - // intercept calls to this contract - INTERCEPTED_ADDRESS.with(|i| *i.borrow_mut() = Some(account_id)); - - assert_err_ignore_postinfo!( - Contracts::call( - RuntimeOrigin::signed(ALICE), - account_id, - 0, - GAS_LIMIT, - deposit_limit::(), - vec![], - ), - >::ContractReverted, - ); - }); -} diff --git a/substrate/frame/revive/src/tracing.rs b/substrate/frame/revive/src/tracing.rs new file mode 100644 index 000000000000..e9c05f8cb505 --- /dev/null +++ b/substrate/frame/revive/src/tracing.rs @@ -0,0 +1,64 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{primitives::ExecReturnValue, DispatchError, Weight}; +use environmental::environmental; +use sp_core::{H160, H256, U256}; + +environmental!(tracer: dyn Tracer + 'static); + +/// Trace the execution of the given closure. +/// +/// # Warning +/// +/// Only meant to be called from off-chain code as its additional resource usage is +/// not accounted for in the weights or memory envelope. +pub fn trace R>(tracer: &mut (dyn Tracer + 'static), f: F) -> R { + tracer::using_once(tracer, f) +} + +/// Run the closure when tracing is enabled. +/// +/// This is safe to be called from on-chain code as tracing will never be activated +/// there. Hence the closure is not executed in this case. +pub(crate) fn if_tracing(f: F) { + tracer::with(f); +} + +/// Defines methods to trace contract interactions. +pub trait Tracer { + /// Called before a contract call is executed + fn enter_child_span( + &mut self, + from: H160, + to: H160, + is_delegate_call: bool, + is_read_only: bool, + value: U256, + input: &[u8], + gas: Weight, + ); + + /// Record a log event + fn log_event(&mut self, event: H160, topics: &[H256], data: &[u8]); + + /// Called after a contract call is executed + fn exit_child_span(&mut self, output: &ExecReturnValue, gas_left: Weight); + + /// Called when a contract call terminates with an error + fn exit_child_span_with_error(&mut self, error: DispatchError, gas_left: Weight); +} From 06f5d486f552e2ead543024168035bcdbb29c027 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Mon, 20 Jan 2025 09:25:43 +0100 Subject: [PATCH 285/340] Collator: Fix `can_build_upon` by always allowing to build on included block (#7205) Follow-up to #6825, which introduced this bug. We use the `can_build_upon` method to ask the runtime if it is fine to build another block. The runtime checks this based on the [`ConsensusHook`](https://github.com/paritytech/polkadot-sdk/blob/c1b7c3025aa4423d4cf3e57309b60fb7602c2db6/cumulus/pallets/aura-ext/src/consensus_hook.rs#L110-L110) implementation, the most popular one being the `FixedConsensusHook`. In #6825 I removed a check that would always allow us to build when we are building on an included block. Turns out this check is still required when: 1. The [`UnincludedSegment` ](https://github.com/paritytech/polkadot-sdk/blob/c1b7c3025aa4423d4cf3e57309b60fb7602c2db6/cumulus/pallets/parachain-system/src/lib.rs#L758-L758) storage item in pallet-parachain-system is equal or larger than the unincluded segment. 2. We are calling the `can_build_upon` runtime API where the included block has progressed offchain to the current parent block (so last entry in the `UnincludedSegment` storage item). In this scenario the last entry in `UnincludedSegment` does not have a hash assigned yet (because it was not available in `on_finalize` of the previous block). So the unincluded segment will be reported at its maximum length which will forbid building another block. Ideally we would have a more elegant solution than to rely on the node-side here. But for now the check is reintroduced and a test is added to not break it again by accident. --------- Co-authored-by: command-bot <> Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> --- Cargo.lock | 3 + cumulus/client/consensus/aura/Cargo.toml | 5 + .../consensus/aura/src/collators/mod.rs | 132 +++++++++++++++++- prdoc/pr_7205.prdoc | 10 ++ 4 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 prdoc/pr_7205.prdoc diff --git a/Cargo.lock b/Cargo.lock index da4e85511919..c9a139f30744 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4640,6 +4640,8 @@ dependencies = [ "cumulus-primitives-aura 0.7.0", "cumulus-primitives-core 0.7.0", "cumulus-relay-chain-interface", + "cumulus-test-client", + "cumulus-test-relay-sproof-builder 0.7.0", "futures", "parity-scale-codec", "parking_lot 0.12.3", @@ -4664,6 +4666,7 @@ dependencies = [ "sp-consensus-aura 0.32.0", "sp-core 28.0.0", "sp-inherents 26.0.0", + "sp-keyring 31.0.0", "sp-keystore 0.34.0", "sp-runtime 31.0.1", "sp-state-machine 0.35.0", diff --git a/cumulus/client/consensus/aura/Cargo.toml b/cumulus/client/consensus/aura/Cargo.toml index 702230938645..8637133a5f5c 100644 --- a/cumulus/client/consensus/aura/Cargo.toml +++ b/cumulus/client/consensus/aura/Cargo.toml @@ -59,6 +59,11 @@ polkadot-node-subsystem-util = { workspace = true, default-features = true } polkadot-overseer = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } +[dev-dependencies] +cumulus-test-client = { workspace = true } +cumulus-test-relay-sproof-builder = { workspace = true } +sp-keyring = { workspace = true } + [features] # Allows collator to use full PoV size for block building full-pov-size = [] diff --git a/cumulus/client/consensus/aura/src/collators/mod.rs b/cumulus/client/consensus/aura/src/collators/mod.rs index 031fa963ba6a..66c6086eaf9e 100644 --- a/cumulus/client/consensus/aura/src/collators/mod.rs +++ b/cumulus/client/consensus/aura/src/collators/mod.rs @@ -179,12 +179,19 @@ where let authorities = runtime_api.authorities(parent_hash).ok()?; let author_pub = aura_internal::claim_slot::

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

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

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

+ +# Polkadot SDK's Parachain Template + +Polkadot SDK Logo +Polkadot SDK Logo + +> This is a template for creating a [parachain](https://wiki.polkadot.network/docs/learn-parachains) based on Polkadot SDK. +> +> This template is automatically updated after releases in the main [Polkadot SDK monorepo](https://github.com/paritytech/polkadot-sdk). + +
+ +## Table of Contents + +- [Intro](#intro) + +- [Template Structure](#template-structure) + +- [Getting Started](#getting-started) + +- [Starting a Development Chain](#starting-a-development-chain) + + - [Omni Node](#omni-node-prerequisites) + - [Zombienet setup with Omni Node](#zombienet-setup-with-omni-node) + - [Parachain Template Node](#parachain-template-node) + - [Connect with the Polkadot-JS Apps Front-End](#connect-with-the-polkadot-js-apps-front-end) + - [Takeaways](#takeaways) + +- [Runtime development](#runtime-development) +- [Contributing](#contributing) +- [Getting Help](#getting-help) + +## Intro + +- ⏫ This template provides a starting point to build a [parachain](https://wiki.polkadot.network/docs/learn-parachains). + +- ☁️ It is based on the + [Cumulus](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/cumulus/index.html) framework. + +- 🔧 Its runtime is configured with a single custom pallet as a starting point, and a handful of ready-made pallets + such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_balances/index.html). + +- 👉 Learn more about parachains [here](https://wiki.polkadot.network/docs/learn-parachains) + +## Template Structure + +A Polkadot SDK based project such as this one consists of: + +- 🧮 the [Runtime](./runtime/README.md) - the core logic of the parachain. +- 🎨 the [Pallets](./pallets/README.md) - from which the runtime is constructed. +- 💿 a [Node](./node/README.md) - the binary application, not part of the project default-members list and not compiled unless + building the project with `--workspace` flag, which builds all workspace members, and is an alternative to + [Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html). + +## Getting Started + +- 🦀 The template is using the Rust language. + +- 👉 Check the + [Rust installation instructions](https://www.rust-lang.org/tools/install) for your system. + +- 🛠️ Depending on your operating system and Rust version, there might be additional + packages required to compile this template - please take note of the Rust compiler output. + +Fetch parachain template code: + +```sh +git clone https://github.com/paritytech/polkadot-sdk-parachain-template.git parachain-template + +cd parachain-template +``` + +## Starting a Development Chain + +The parachain template relies on a hardcoded parachain id which is defined in the runtime code +and referenced throughout the contents of this file as `{{PARACHAIN_ID}}`. Please replace +any command or file referencing this placeholder with the value of the `PARACHAIN_ID` constant: + + + +### Omni Node Prerequisites + +[Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html) can +be used to run the parachain template's runtime. `polkadot-omni-node` binary crate usage is described at a high-level +[on crates.io](https://crates.io/crates/polkadot-omni-node). + +#### Install `polkadot-omni-node` + +Please see the installation section at [`crates.io/omni-node`](https://crates.io/crates/polkadot-omni-node). + +#### Build `parachain-template-runtime` + +```sh +cargo build --release +``` + +#### Install `staging-chain-spec-builder` + +Please see the installation section at [`crates.io/staging-chain-spec-builder`](https://crates.io/crates/staging-chain-spec-builder). + +#### Use `chain-spec-builder` to generate the `chain_spec.json` file + +```sh +chain-spec-builder create --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ + target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development +``` + +**Note**: the `relay-chain` and `para-id` flags are mandatory information required by +Omni Node, and for parachain template case the value for `para-id` must be set to `{{PARACHAIN_ID}}`, since this +is also the value injected through [ParachainInfo](https://docs.rs/staging-parachain-info/0.17.0/staging_parachain_info/) +pallet into the `parachain-template-runtime`'s storage. The `relay-chain` value is set in accordance +with the relay chain ID where this instantiation of parachain-template will connect to. + +#### Run Omni Node + +Start Omni Node with the generated chain spec. We'll start it in development mode (without a relay chain config), producing +and finalizing blocks based on manual seal, configured below to seal a block with each second. + +```bash +polkadot-omni-node --chain --dev --dev-block-time 1000 +``` + +However, such a setup is not close to what would run in production, and for that we need to setup a local +relay chain network that will help with the block finalization. In this guide we'll setup a local relay chain +as well. We'll not do it manually, by starting one node at a time, but we'll use [zombienet](https://paritytech.github.io/zombienet/intro.html). + +Follow through the next section for more details on how to do it. + +### Zombienet setup with Omni Node + +Assuming we continue from the last step of the previous section, we have a chain spec and we need to setup a relay chain. +We can install `zombienet` as described [here](https://paritytech.github.io/zombienet/install.html#installation), and +`zombienet-omni-node.toml` contains the network specification we want to start. + +#### Relay chain prerequisites + +Download the `polkadot` (and the accompanying `polkadot-prepare-worker` and `polkadot-execute-worker`) binaries from +[Polkadot SDK releases](https://github.com/paritytech/polkadot-sdk/releases). Then expose them on `PATH` like so: + +```sh +export PATH="$PATH:" +``` + +#### Update `zombienet-omni-node.toml` with a valid chain spec path + +```toml +# ... +[[parachains]] +id = {{PARACHAIN_ID}} +chain_spec_path = "" +# ... +``` + +#### Start the network + +```sh +zombienet --provider native spawn zombienet-omni-node.toml +``` + +### Parachain Template Node + +As mentioned in the `Template Structure` section, the `node` crate is optionally compiled and it is an alternative +to `Omni Node`. Similarly, it requires setting up a relay chain, and we'll use `zombienet` once more. + +#### Install the `parachain-template-node` + +```sh +cargo install --path node +``` + +#### Setup and start the network + +For setup, please consider the instructions for `zombienet` installation [here](https://paritytech.github.io/zombienet/install.html#installation) +and [relay chain prerequisites](#relay-chain-prerequisites). + +We're left just with starting the network: + +```sh +zombienet --provider native spawn zombienet.toml +``` + +### Connect with the Polkadot-JS Apps Front-End + +- 🌐 You can interact with your local node using the + hosted version of the Polkadot/Substrate Portal: + [relay chain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) + and [parachain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9988). + +- 🪐 A hosted version is also + available on [IPFS](https://dotapps.io/). + +- 🧑‍🔧 You can also find the source code and instructions for hosting your own instance in the + [`polkadot-js/apps`](https://github.com/polkadot-js/apps) repository. + +### Takeaways + +Development parachains: + +- 🔗 Connect to relay chains, and we showcased how to connect to a local one. +- 🧹 Do not persist the state. +- 💰 Are preconfigured with a genesis state that includes several prefunded development accounts. +- 🧑‍⚖️ Development accounts are used as validators, collators, and `sudo` accounts. + +## Runtime development + +We recommend using [`chopsticks`](https://github.com/AcalaNetwork/chopsticks) when the focus is more on the runtime +development and `OmniNode` is enough as is. + +### Install chopsticks + +To use `chopsticks`, please install the latest version according to the installation [guide](https://github.com/AcalaNetwork/chopsticks?tab=readme-ov-file#install). + +### Build a raw chain spec + +Build the `parachain-template-runtime` as mentioned before in this guide and use `chain-spec-builder` +again but this time by passing `--raw-storage` flag: + +```sh +chain-spec-builder create --raw-storage --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ + target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development +``` + +### Start `chopsticks` with the chain spec + +```sh +npx @acala-network/chopsticks@latest --chain-spec +``` + +### Alternatives + +`OmniNode` can be still used for runtime development if using the `--dev` flag, while `parachain-template-node` doesn't +support it at this moment. It can still be used to test a runtime in a full setup where it is started alongside a +relay chain network (see [Parachain Template node](#parachain-template-node) setup). + +## Contributing + +- 🔄 This template is automatically updated after releases in the main [Polkadot SDK monorepo](https://github.com/paritytech/polkadot-sdk). + +- ➡️ Any pull requests should be directed to this [source](https://github.com/paritytech/polkadot-sdk/tree/master/templates/parachain). + +- 😇 Please refer to the monorepo's + [contribution guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and + [Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CODE_OF_CONDUCT.md). + +## Getting Help + +- 🧑‍🏫 To learn about Polkadot in general, [Polkadot.network](https://polkadot.network/) website is a good starting point. + +- 🧑‍🔧 For technical introduction, [here](https://github.com/paritytech/polkadot-sdk#-documentation) are + the Polkadot SDK documentation resources. + +- 👥 Additionally, there are [GitHub issues](https://github.com/paritytech/polkadot-sdk/issues) and + [Substrate StackExchange](https://substrate.stackexchange.com/). diff --git a/templates/parachain/README.md b/templates/parachain/README.md index c1e333df9e9e..15e9f7fe61cf 100644 --- a/templates/parachain/README.md +++ b/templates/parachain/README.md @@ -36,10 +36,10 @@ - ⏫ This template provides a starting point to build a [parachain](https://wiki.polkadot.network/docs/learn-parachains). - ☁️ It is based on the -[Cumulus](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/cumulus/index.html) framework. + [Cumulus](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/polkadot_sdk/cumulus/index.html) framework. - 🔧 Its runtime is configured with a single custom pallet as a starting point, and a handful of ready-made pallets -such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_balances/index.html). + such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_balances/index.html). - 👉 Learn more about parachains [here](https://wiki.polkadot.network/docs/learn-parachains) @@ -50,18 +50,18 @@ A Polkadot SDK based project such as this one consists of: - 🧮 the [Runtime](./runtime/README.md) - the core logic of the parachain. - 🎨 the [Pallets](./pallets/README.md) - from which the runtime is constructed. - 💿 a [Node](./node/README.md) - the binary application, not part of the project default-members list and not compiled unless -building the project with `--workspace` flag, which builds all workspace members, and is an alternative to -[Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html). + building the project with `--workspace` flag, which builds all workspace members, and is an alternative to + [Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html). ## Getting Started - 🦀 The template is using the Rust language. - 👉 Check the -[Rust installation instructions](https://www.rust-lang.org/tools/install) for your system. + [Rust installation instructions](https://www.rust-lang.org/tools/install) for your system. - 🛠️ Depending on your operating system and Rust version, there might be additional -packages required to compile this template - please take note of the Rust compiler output. + packages required to compile this template - please take note of the Rust compiler output. Fetch parachain template code: @@ -73,6 +73,14 @@ cd parachain-template ## Starting a Development Chain +The parachain template relies on a hardcoded parachain id which is defined in the runtime code +and referenced throughout the contents of this file as `{{PARACHAIN_ID}}`. Please replace +any command or file referencing this placeholder with the value of the `PARACHAIN_ID` constant: + +```rust,ignore +pub const PARACHAIN_ID: u32 = 1000; +``` + ### Omni Node Prerequisites [Omni Node](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/omni_node/index.html) can @@ -96,12 +104,12 @@ Please see the installation section at [`crates.io/staging-chain-spec-builder`]( #### Use `chain-spec-builder` to generate the `chain_spec.json` file ```sh -chain-spec-builder create --relay-chain "rococo-local" --para-id 1000 --runtime \ +chain-spec-builder create --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development ``` **Note**: the `relay-chain` and `para-id` flags are mandatory information required by -Omni Node, and for parachain template case the value for `para-id` must be set to `1000`, since this +Omni Node, and for parachain template case the value for `para-id` must be set to `{{PARACHAIN_ID}}`, since this is also the value injected through [ParachainInfo](https://docs.rs/staging-parachain-info/0.17.0/staging_parachain_info/) pallet into the `parachain-template-runtime`'s storage. The `relay-chain` value is set in accordance with the relay chain ID where this instantiation of parachain-template will connect to. @@ -141,7 +149,7 @@ export PATH="$PATH:" ```toml # ... [[parachains]] -id = 1000 +id = {{PARACHAIN_ID}} chain_spec_path = "" # ... ``` @@ -177,15 +185,15 @@ zombienet --provider native spawn zombienet.toml ### Connect with the Polkadot-JS Apps Front-End - 🌐 You can interact with your local node using the -hosted version of the Polkadot/Substrate Portal: -[relay chain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) -and [parachain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9988). + hosted version of the Polkadot/Substrate Portal: + [relay chain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) + and [parachain](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9988). - 🪐 A hosted version is also -available on [IPFS](https://dotapps.io/). + available on [IPFS](https://dotapps.io/). - 🧑‍🔧 You can also find the source code and instructions for hosting your own instance in the -[`polkadot-js/apps`](https://github.com/polkadot-js/apps) repository. + [`polkadot-js/apps`](https://github.com/polkadot-js/apps) repository. ### Takeaways @@ -211,7 +219,7 @@ Build the `parachain-template-runtime` as mentioned before in this guide and use again but this time by passing `--raw-storage` flag: ```sh -chain-spec-builder create --raw-storage --relay-chain "rococo-local" --para-id 1000 --runtime \ +chain-spec-builder create --raw-storage --relay-chain "rococo-local" --para-id {{PARACHAIN_ID}} --runtime \ target/release/wbuild/parachain-template-runtime/parachain_template_runtime.wasm named-preset development ``` @@ -234,15 +242,15 @@ relay chain network (see [Parachain Template node](#parachain-template-node) set - ➡️ Any pull requests should be directed to this [source](https://github.com/paritytech/polkadot-sdk/tree/master/templates/parachain). - 😇 Please refer to the monorepo's -[contribution guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and -[Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CODE_OF_CONDUCT.md). + [contribution guidelines](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CONTRIBUTING.md) and + [Code of Conduct](https://github.com/paritytech/polkadot-sdk/blob/master/docs/contributor/CODE_OF_CONDUCT.md). ## Getting Help - 🧑‍🏫 To learn about Polkadot in general, [Polkadot.network](https://polkadot.network/) website is a good starting point. - 🧑‍🔧 For technical introduction, [here](https://github.com/paritytech/polkadot-sdk#-documentation) are -the Polkadot SDK documentation resources. + the Polkadot SDK documentation resources. - 👥 Additionally, there are [GitHub issues](https://github.com/paritytech/polkadot-sdk/issues) and -[Substrate StackExchange](https://substrate.stackexchange.com/). + [Substrate StackExchange](https://substrate.stackexchange.com/). diff --git a/templates/parachain/runtime/src/genesis_config_presets.rs b/templates/parachain/runtime/src/genesis_config_presets.rs index f1b24e437247..8cdadca5060a 100644 --- a/templates/parachain/runtime/src/genesis_config_presets.rs +++ b/templates/parachain/runtime/src/genesis_config_presets.rs @@ -17,6 +17,7 @@ use sp_keyring::Sr25519Keyring; /// The default XCM version to set in genesis config. const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; /// Parachain id used for genesis config presets of parachain template. +#[docify::export_content] pub const PARACHAIN_ID: u32 = 1000; /// Generate the session keys from individual elements. diff --git a/templates/parachain/src/lib.rs b/templates/parachain/src/lib.rs new file mode 100644 index 000000000000..d3c5b8ba3101 --- /dev/null +++ b/templates/parachain/src/lib.rs @@ -0,0 +1,22 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . + +// The parachain-template crate helps with keeping the README.md in sync +// with code sections across the components under the template: node, +// pallets & runtime, by using `docify`. + +#[cfg(feature = "generate-readme")] +docify::compile_markdown!("README.docify.md", "README.md"); From d5d9b1276a088a6bd7a8c2c698320dad3d0ee2c4 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Mon, 20 Jan 2025 12:02:59 +0100 Subject: [PATCH 287/340] Stabilize `ensure_execute_processes_have_correct_num_threads` test (#7253) Saw this test flake a few times, last time [here](https://github.com/paritytech/polkadot-sdk/actions/runs/12834432188/job/35791830215). We first fetch all processes in the test, then query `/proc//stat` for every one of them. When the file was not found, we would error. Now we tolerate not finding this file. Ran 200 times locally without error, before would fail a few times, probably depending on process fluctuation (which I expect to be high on CI runners). --- polkadot/node/core/pvf/tests/it/process.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/polkadot/node/core/pvf/tests/it/process.rs b/polkadot/node/core/pvf/tests/it/process.rs index 353367b394f3..29326365b5ba 100644 --- a/polkadot/node/core/pvf/tests/it/process.rs +++ b/polkadot/node/core/pvf/tests/it/process.rs @@ -77,7 +77,9 @@ fn find_process_by_sid_and_name( let mut found = None; for process in all_processes { - let stat = process.stat().expect("/proc existed above. Potential race occurred"); + let Ok(stat) = process.stat() else { + continue; + }; if stat.session != sid || !process.exe().unwrap().to_str().unwrap().contains(exe_name) { continue From ea27696aeed8e76cfb82492f6f3665948d766fe5 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Mon, 20 Jan 2025 12:47:29 +0100 Subject: [PATCH 288/340] [pallet-revive] eth-rpc error logging (#7251) Log error instead of failing with an error when block processing fails --------- Co-authored-by: command-bot <> --- .../runtimes/assets/asset-hub-westend/src/lib.rs | 2 +- prdoc/pr_7251.prdoc | 7 +++++++ substrate/frame/revive/rpc/src/client.rs | 4 +++- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_7251.prdoc diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 41f29fe2c56a..f56c4568f2d1 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -129,7 +129,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_017_004, + spec_version: 1_017_005, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, diff --git a/prdoc/pr_7251.prdoc b/prdoc/pr_7251.prdoc new file mode 100644 index 000000000000..98e371dc940f --- /dev/null +++ b/prdoc/pr_7251.prdoc @@ -0,0 +1,7 @@ +title: '[pallet-revive] eth-rpc error logging' +doc: +- audience: Runtime Dev + description: Log error instead of failing with an error when block processing fails +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index c61c5871f76a..a5a022f97228 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -310,7 +310,9 @@ impl Client { }; log::debug!(target: LOG_TARGET, "Pushing block: {}", block.number()); - callback(block).await?; + if let Err(err) = callback(block).await { + log::error!(target: LOG_TARGET, "Failed to process block: {err:?}"); + } } log::info!(target: LOG_TARGET, "Block subscription ended"); From 115ff4e98ecc301a3d380a2fc53ec2304647c69d Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 20 Jan 2025 13:48:25 +0100 Subject: [PATCH 289/340] Apply a few minor fixes found while addressing the fellows PR for weights. (#7098) This PR addresses a few minor issues found while working on the polkadot-fellows PR [https://github.com/polkadot-fellows/runtimes/pull/522](https://github.com/polkadot-fellows/runtimes/pull/522): - Incorrect generic type for `InboundLaneData` in `check_message_lane_weights`. - Renaming leftovers: `assigner_on_demand` -> `on_demand`. --- bridges/bin/runtime-common/src/integrity.rs | 11 +++++++---- bridges/modules/messages/src/lib.rs | 4 ++-- polkadot/runtime/rococo/src/lib.rs | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/bridges/bin/runtime-common/src/integrity.rs b/bridges/bin/runtime-common/src/integrity.rs index 535f1a26e5e8..61dbf09109ac 100644 --- a/bridges/bin/runtime-common/src/integrity.rs +++ b/bridges/bin/runtime-common/src/integrity.rs @@ -21,11 +21,11 @@ use bp_header_chain::ChainWithGrandpa; use bp_messages::{ChainWithMessages, InboundLaneData, MessageNonce}; -use bp_runtime::Chain; +use bp_runtime::{AccountIdOf, Chain}; use codec::Encode; use frame_support::{storage::generator::StorageValue, traits::Get, weights::Weight}; use frame_system::limits; -use pallet_bridge_messages::WeightInfoExt as _; +use pallet_bridge_messages::{ThisChainOf, WeightInfoExt as _}; // Re-export to avoid include all dependencies everywhere. #[doc(hidden)] @@ -364,8 +364,11 @@ pub fn check_message_lane_weights< ); // check that weights allow us to receive delivery confirmations - let max_incoming_inbound_lane_data_proof_size = - InboundLaneData::<()>::encoded_size_hint_u32(this_chain_max_unrewarded_relayers as _); + let max_incoming_inbound_lane_data_proof_size = InboundLaneData::< + AccountIdOf>, + >::encoded_size_hint_u32( + this_chain_max_unrewarded_relayers as _ + ); pallet_bridge_messages::ensure_able_to_receive_confirmation::>( C::max_extrinsic_size(), C::max_extrinsic_weight(), diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs index af14257db99c..61763186cb02 100644 --- a/bridges/modules/messages/src/lib.rs +++ b/bridges/modules/messages/src/lib.rs @@ -230,8 +230,8 @@ pub mod pallet { // why do we need to know the weight of this (`receive_messages_proof`) call? Because // we may want to return some funds for not-dispatching (or partially dispatching) some // messages to the call origin (relayer). And this is done by returning actual weight - // from the call. But we only know dispatch weight of every messages. So to refund - // relayer because we have not dispatched Message, we need to: + // from the call. But we only know dispatch weight of every message. So to refund + // relayer because we have not dispatched message, we need to: // // ActualWeight = DeclaredWeight - Message.DispatchWeight // diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index b3f2a0033278..c2c3d35ee5b4 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1822,7 +1822,7 @@ mod benches { [polkadot_runtime_parachains::initializer, Initializer] [polkadot_runtime_parachains::paras_inherent, ParaInherent] [polkadot_runtime_parachains::paras, Paras] - [polkadot_runtime_parachains::assigner_on_demand, OnDemandAssignmentProvider] + [polkadot_runtime_parachains::on_demand, OnDemandAssignmentProvider] // Substrate [pallet_balances, Balances] [pallet_balances, NisCounterpartBalances] From 569ce71e2c759b26601608f145d9b5efcb906919 Mon Sep 17 00:00:00 2001 From: Ron Date: Mon, 20 Jan 2025 22:16:57 +0800 Subject: [PATCH 290/340] Migrate pallet-mmr to umbrella crate (#7081) Part of https://github.com/paritytech/polkadot-sdk/issues/6504 --- Cargo.lock | 8 +- prdoc/pr_7081.prdoc | 14 +++ .../frame/merkle-mountain-range/Cargo.toml | 24 +----- .../merkle-mountain-range/src/benchmarking.rs | 10 ++- .../src/default_weights.rs | 9 +- .../frame/merkle-mountain-range/src/lib.rs | 85 +++++++++---------- .../merkle-mountain-range/src/mmr/mmr.rs | 45 +++++----- .../merkle-mountain-range/src/mmr/mod.rs | 5 +- .../merkle-mountain-range/src/mmr/storage.rs | 38 +++++---- .../frame/merkle-mountain-range/src/mock.rs | 18 ++-- .../frame/merkle-mountain-range/src/tests.rs | 23 ++--- 11 files changed, 137 insertions(+), 142 deletions(-) create mode 100644 prdoc/pr_7081.prdoc diff --git a/Cargo.lock b/Cargo.lock index 0907830c5e7b..50d36338cd2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14147,18 +14147,12 @@ dependencies = [ name = "pallet-mmr" version = "27.0.0" dependencies = [ - "array-bytes", - "frame-benchmarking 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", "itertools 0.11.0", "log", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", "sp-mmr-primitives 26.0.0", - "sp-runtime 31.0.1", "sp-tracing 16.0.0", ] diff --git a/prdoc/pr_7081.prdoc b/prdoc/pr_7081.prdoc new file mode 100644 index 000000000000..be1d8aa6ee01 --- /dev/null +++ b/prdoc/pr_7081.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: '[pallet-mmr] Migrate to using frame umbrella crate' + +doc: + - audience: Runtime Dev + description: This PR migrates the pallet-mmr to use the frame umbrella crate. This + is part of the ongoing effort to migrate all pallets to use the frame umbrella crate. + The effort is tracked [here](https://github.com/paritytech/polkadot-sdk/issues/6504). + +crates: + - name: pallet-mmr + bump: minor diff --git a/substrate/frame/merkle-mountain-range/Cargo.toml b/substrate/frame/merkle-mountain-range/Cargo.toml index 04f5ab64100d..ecbef01a9205 100644 --- a/substrate/frame/merkle-mountain-range/Cargo.toml +++ b/substrate/frame/merkle-mountain-range/Cargo.toml @@ -16,18 +16,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } sp-mmr-primitives = { workspace = true } -sp-runtime = { workspace = true } [dev-dependencies] -array-bytes = { workspace = true, default-features = true } itertools = { workspace = true } sp-tracing = { workspace = true, default-features = true } @@ -35,24 +29,14 @@ sp-tracing = { workspace = true, default-features = true } default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "scale-info/std", - "sp-core/std", - "sp-io/std", "sp-mmr-primitives/std", - "sp-runtime/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", + "frame/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", + "frame/try-runtime", ] diff --git a/substrate/frame/merkle-mountain-range/src/benchmarking.rs b/substrate/frame/merkle-mountain-range/src/benchmarking.rs index 07afd9529eb2..407f1f7ead60 100644 --- a/substrate/frame/merkle-mountain-range/src/benchmarking.rs +++ b/substrate/frame/merkle-mountain-range/src/benchmarking.rs @@ -20,8 +20,10 @@ #![cfg(feature = "runtime-benchmarks")] use crate::*; -use frame_benchmarking::v1::benchmarks_instance_pallet; -use frame_support::traits::OnInitialize; +use frame::{ + benchmarking::prelude::v1::benchmarks_instance_pallet, + deps::frame_support::traits::OnInitialize, +}; benchmarks_instance_pallet! { on_initialize { @@ -31,10 +33,10 @@ benchmarks_instance_pallet! { <>::BenchmarkHelper as BenchmarkHelper>::setup(); for leaf in 0..(leaves - 1) { - Pallet::::on_initialize((leaf as u32).into()); + as OnInitialize>>::on_initialize((leaf as u32).into()); } }: { - Pallet::::on_initialize((leaves as u32 - 1).into()); + as OnInitialize>>::on_initialize((leaves as u32 - 1).into()); } verify { assert_eq!(crate::NumberOfLeaves::::get(), leaves); } diff --git a/substrate/frame/merkle-mountain-range/src/default_weights.rs b/substrate/frame/merkle-mountain-range/src/default_weights.rs index b0ef0539018c..d1ed12edd062 100644 --- a/substrate/frame/merkle-mountain-range/src/default_weights.rs +++ b/substrate/frame/merkle-mountain-range/src/default_weights.rs @@ -18,16 +18,13 @@ //! Default weights for the MMR Pallet //! This file was not auto-generated. -use frame_support::weights::{ - constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_NANOS}, - Weight, -}; +use frame::{deps::frame_support::weights::constants::*, weights_prelude::*}; impl crate::WeightInfo for () { fn on_initialize(peaks: u32) -> Weight { let peaks = u64::from(peaks); // Reading the parent hash. - let leaf_weight = DbWeight::get().reads(1); + let leaf_weight = RocksDbWeight::get().reads(1); // Blake2 hash cost. let hash_weight = Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_NANOS, 0); // No-op hook. @@ -36,6 +33,6 @@ impl crate::WeightInfo for () { leaf_weight .saturating_add(hash_weight) .saturating_add(hook_weight) - .saturating_add(DbWeight::get().reads_writes(2 + peaks, 2 + peaks)) + .saturating_add(RocksDbWeight::get().reads_writes(2 + peaks, 2 + peaks)) } } diff --git a/substrate/frame/merkle-mountain-range/src/lib.rs b/substrate/frame/merkle-mountain-range/src/lib.rs index 7dfe95c83361..76d6c2a1ac76 100644 --- a/substrate/frame/merkle-mountain-range/src/lib.rs +++ b/substrate/frame/merkle-mountain-range/src/lib.rs @@ -59,20 +59,17 @@ extern crate alloc; use alloc::vec::Vec; -use frame_support::weights::Weight; -use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; use log; -use sp_mmr_primitives::utils; -use sp_runtime::{ - traits::{self, One, Saturating}, - SaturatedConversion, -}; -pub use pallet::*; +use frame::prelude::*; + pub use sp_mmr_primitives::{ - self as primitives, utils::NodesUtils, Error, LeafDataProvider, LeafIndex, NodeIndex, + self as primitives, utils, utils::NodesUtils, AncestryProof, Error, FullLeaf, LeafDataProvider, + LeafIndex, LeafProof, NodeIndex, OnNewRoot, }; +pub use pallet::*; + #[cfg(feature = "runtime-benchmarks")] mod benchmarking; mod default_weights; @@ -90,11 +87,11 @@ mod tests; /// crate-local wrapper over [frame_system::Pallet]. Since the current block hash /// is not available (since the block is not finished yet), /// we use the `parent_hash` here along with parent block number. -pub struct ParentNumberAndHash { - _phantom: core::marker::PhantomData, +pub struct ParentNumberAndHash { + _phantom: PhantomData, } -impl LeafDataProvider for ParentNumberAndHash { +impl LeafDataProvider for ParentNumberAndHash { type LeafData = (BlockNumberFor, ::Hash); fn leaf_data() -> Self::LeafData { @@ -111,13 +108,11 @@ pub trait BlockHashProvider { } /// Default implementation of BlockHashProvider using frame_system. -pub struct DefaultBlockHashProvider { +pub struct DefaultBlockHashProvider { _phantom: core::marker::PhantomData, } -impl BlockHashProvider, T::Hash> - for DefaultBlockHashProvider -{ +impl BlockHashProvider, T::Hash> for DefaultBlockHashProvider { fn block_hash(block_number: BlockNumberFor) -> T::Hash { frame_system::Pallet::::block_hash(block_number) } @@ -142,17 +137,16 @@ impl BenchmarkHelper for () { type ModuleMmr = mmr::Mmr>; /// Leaf data. -type LeafOf = <>::LeafData as primitives::LeafDataProvider>::LeafData; +type LeafOf = <>::LeafData as LeafDataProvider>::LeafData; /// Hashing used for the pallet. pub(crate) type HashingOf = >::Hashing; /// Hash type used for the pallet. -pub(crate) type HashOf = <>::Hashing as traits::Hash>::Output; +pub(crate) type HashOf = <>::Hashing as Hash>::Output; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; #[pallet::pallet] pub struct Pallet(PhantomData<(T, I)>); @@ -180,7 +174,7 @@ pub mod pallet { /// /// Then we create a tuple of these two hashes, SCALE-encode it (concatenate) and /// hash, to obtain a new MMR inner node - the new peak. - type Hashing: traits::Hash; + type Hashing: Hash; /// Data stored in the leaf nodes. /// @@ -198,7 +192,7 @@ pub mod pallet { /// two forks with identical line of ancestors compete to write the same offchain key, but /// that's fine as long as leaves only contain data coming from ancestors - conflicting /// writes are identical). - type LeafData: primitives::LeafDataProvider; + type LeafData: LeafDataProvider; /// A hook to act on the new MMR root. /// @@ -206,7 +200,7 @@ pub mod pallet { /// apart from having it in the storage. For instance you might output it in the header /// digest (see [`frame_system::Pallet::deposit_log`]) to make it available for Light /// Clients. Hook complexity should be `O(1)`. - type OnNewRoot: primitives::OnNewRoot>; + type OnNewRoot: OnNewRoot>; /// Block hash provider for a given block number. type BlockHashProvider: BlockHashProvider< @@ -248,9 +242,8 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { - use primitives::LeafDataProvider; let leaves = NumberOfLeaves::::get(); - let peaks_before = sp_mmr_primitives::utils::NodesUtils::new(leaves).number_of_peaks(); + let peaks_before = NodesUtils::new(leaves).number_of_peaks(); let data = T::LeafData::leaf_data(); // append new leaf to MMR @@ -268,12 +261,12 @@ pub mod pallet { return T::WeightInfo::on_initialize(peaks_before as u32) }, }; - >::on_new_root(&root); + >::on_new_root(&root); NumberOfLeaves::::put(leaves); RootHash::::put(root); - let peaks_after = sp_mmr_primitives::utils::NodesUtils::new(leaves).number_of_peaks(); + let peaks_after = NodesUtils::new(leaves).number_of_peaks(); T::WeightInfo::on_initialize(peaks_before.max(peaks_after) as u32) } @@ -290,28 +283,28 @@ pub mod pallet { pub fn verify_leaves_proof( root: H::Output, leaves: Vec>, - proof: primitives::LeafProof, -) -> Result<(), primitives::Error> + proof: LeafProof, +) -> Result<(), Error> where - H: traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { let is_valid = mmr::verify_leaves_proof::(root, leaves, proof)?; if is_valid { Ok(()) } else { - Err(primitives::Error::Verify.log_debug(("The proof is incorrect.", root))) + Err(Error::Verify.log_debug(("The proof is incorrect.", root))) } } /// Stateless ancestry proof verification. pub fn verify_ancestry_proof( root: H::Output, - ancestry_proof: primitives::AncestryProof, + ancestry_proof: AncestryProof, ) -> Result where - H: traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { mmr::verify_ancestry_proof::(root, ancestry_proof) .map_err(|_| Error::Verify.log_debug(("The ancestry proof is incorrect.", root))) @@ -383,7 +376,7 @@ impl, I: 'static> Pallet { pub fn generate_proof( block_numbers: Vec>, best_known_block_number: Option>, - ) -> Result<(Vec>, primitives::LeafProof>), primitives::Error> { + ) -> Result<(Vec>, LeafProof>), Error> { // check whether best_known_block_number provided, else use current best block let best_known_block_number = best_known_block_number.unwrap_or_else(|| >::block_number()); @@ -393,7 +386,7 @@ impl, I: 'static> Pallet { // we need to translate the block_numbers into leaf indices. let leaf_indices = block_numbers .iter() - .map(|block_num| -> Result { + .map(|block_num| -> Result { Self::block_num_to_leaf_index(*block_num) }) .collect::, _>>()?; @@ -410,14 +403,15 @@ impl, I: 'static> Pallet { /// or the proof is invalid. pub fn verify_leaves( leaves: Vec>, - proof: primitives::LeafProof>, - ) -> Result<(), primitives::Error> { + proof: LeafProof>, + ) -> Result<(), Error> { if proof.leaf_count > NumberOfLeaves::::get() || proof.leaf_count == 0 || proof.items.len().saturating_add(leaves.len()) as u64 > proof.leaf_count { - return Err(primitives::Error::Verify - .log_debug("The proof has incorrect number of leaves or proof items.")) + return Err( + Error::Verify.log_debug("The proof has incorrect number of leaves or proof items.") + ) } let mmr: ModuleMmr = mmr::Mmr::new(proof.leaf_count); @@ -425,14 +419,14 @@ impl, I: 'static> Pallet { if is_valid { Ok(()) } else { - Err(primitives::Error::Verify.log_debug("The proof is incorrect.")) + Err(Error::Verify.log_debug("The proof is incorrect.")) } } pub fn generate_ancestry_proof( prev_block_number: BlockNumberFor, best_known_block_number: Option>, - ) -> Result>, Error> { + ) -> Result>, Error> { // check whether best_known_block_number provided, else use current best block let best_known_block_number = best_known_block_number.unwrap_or_else(|| >::block_number()); @@ -445,8 +439,7 @@ impl, I: 'static> Pallet { } #[cfg(feature = "runtime-benchmarks")] - pub fn generate_mock_ancestry_proof() -> Result>, Error> - { + pub fn generate_mock_ancestry_proof() -> Result>, Error> { let leaf_count = Self::block_num_to_leaf_count(>::block_number())?; let mmr: ModuleMmr = mmr::Mmr::new(leaf_count); mmr.generate_mock_ancestry_proof() @@ -454,7 +447,7 @@ impl, I: 'static> Pallet { pub fn verify_ancestry_proof( root: HashOf, - ancestry_proof: primitives::AncestryProof>, + ancestry_proof: AncestryProof>, ) -> Result, Error> { verify_ancestry_proof::, LeafOf>(root, ancestry_proof) } diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs index f9a4580b9bb3..a9818ba47101 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs @@ -20,11 +20,14 @@ use crate::{ storage::{OffchainStorage, RuntimeStorage, Storage}, Hasher, Node, NodeOf, }, - primitives::{self, Error, NodeIndex}, + primitives::{ + mmr_lib, mmr_lib::MMRStoreReadOps, utils::NodesUtils, AncestryProof, Error, FullLeaf, + LeafIndex, LeafProof, NodeIndex, + }, Config, HashOf, HashingOf, }; use alloc::vec::Vec; -use sp_mmr_primitives::{mmr_lib, mmr_lib::MMRStoreReadOps, utils::NodesUtils, LeafIndex}; +use frame::prelude::*; /// Stateless verification of the proof for a batch of leaves. /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the @@ -33,11 +36,11 @@ use sp_mmr_primitives::{mmr_lib, mmr_lib::MMRStoreReadOps, utils::NodesUtils, Le pub fn verify_leaves_proof( root: H::Output, leaves: Vec>, - proof: primitives::LeafProof, + proof: LeafProof, ) -> Result where - H: sp_runtime::traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { let size = NodesUtils::new(proof.leaf_count).size(); @@ -62,11 +65,11 @@ where pub fn verify_ancestry_proof( root: H::Output, - ancestry_proof: primitives::AncestryProof, + ancestry_proof: AncestryProof, ) -> Result where - H: sp_runtime::traits::Hash, - L: primitives::FullLeaf, + H: Hash, + L: FullLeaf, { let mmr_size = NodesUtils::new(ancestry_proof.leaf_count).size(); @@ -104,7 +107,7 @@ pub struct Mmr where T: Config, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, Storage: MMRStoreReadOps> + mmr_lib::MMRStoreWriteOps>, { @@ -116,7 +119,7 @@ impl Mmr where T: Config, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, Storage: MMRStoreReadOps> + mmr_lib::MMRStoreWriteOps>, { @@ -133,7 +136,7 @@ where pub fn verify_leaves_proof( &self, leaves: Vec, - proof: primitives::LeafProof>, + proof: LeafProof>, ) -> Result { let p = mmr_lib::MerkleProof::, Hasher, L>>::new( self.mmr.mmr_size(), @@ -167,7 +170,7 @@ impl Mmr where T: Config, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { /// Push another item to the MMR. /// @@ -195,7 +198,7 @@ impl Mmr where T: Config, I: 'static, - L: primitives::FullLeaf + codec::Decode, + L: FullLeaf + codec::Decode, { /// Generate a proof for given leaf indices. /// @@ -204,7 +207,7 @@ where pub fn generate_proof( &self, leaf_indices: Vec, - ) -> Result<(Vec, primitives::LeafProof>), Error> { + ) -> Result<(Vec, LeafProof>), Error> { let positions = leaf_indices .iter() .map(|index| mmr_lib::leaf_index_to_pos(*index)) @@ -222,7 +225,7 @@ where self.mmr .gen_proof(positions) .map_err(|e| Error::GenerateProof.log_error(e)) - .map(|p| primitives::LeafProof { + .map(|p| LeafProof { leaf_indices, leaf_count, items: p.proof_items().iter().map(|x| x.hash()).collect(), @@ -233,14 +236,14 @@ where pub fn generate_ancestry_proof( &self, prev_leaf_count: LeafIndex, - ) -> Result>, Error> { + ) -> Result>, Error> { let prev_mmr_size = NodesUtils::new(prev_leaf_count).size(); let raw_ancestry_proof = self .mmr .gen_ancestry_proof(prev_mmr_size) .map_err(|e| Error::GenerateProof.log_error(e))?; - Ok(primitives::AncestryProof { + Ok(AncestryProof { prev_peaks: raw_ancestry_proof.prev_peaks.into_iter().map(|p| p.hash()).collect(), prev_leaf_count, leaf_count: self.leaves, @@ -258,12 +261,10 @@ where /// The generated proof contains all the leafs in the MMR, so this way we can generate a proof /// with exactly `leaf_count` items. #[cfg(feature = "runtime-benchmarks")] - pub fn generate_mock_ancestry_proof( - &self, - ) -> Result>, Error> { + pub fn generate_mock_ancestry_proof(&self) -> Result>, Error> { use crate::ModuleMmr; use alloc::vec; - use sp_mmr_primitives::mmr_lib::helper; + use mmr_lib::helper; let mmr: ModuleMmr = Mmr::new(self.leaves); let store = >::default(); @@ -289,7 +290,7 @@ where proof_items.push((leaf_pos, leaf)); } - Ok(sp_mmr_primitives::AncestryProof { + Ok(AncestryProof { prev_peaks, prev_leaf_count: self.leaves, leaf_count: self.leaves, diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs index 5b73f53506e9..85d00f8a65de 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs @@ -18,10 +18,9 @@ mod mmr; pub mod storage; -use sp_mmr_primitives::{mmr_lib, DataOrHash, FullLeaf}; -use sp_runtime::traits; - pub use self::mmr::{verify_ancestry_proof, verify_leaves_proof, Mmr}; +use crate::primitives::{mmr_lib, DataOrHash, FullLeaf}; +use frame::traits; /// Node type for runtime `T`. pub type NodeOf = Node<>::Hashing, L>; diff --git a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs index 02852388b417..c201c0ea846d 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/storage.rs @@ -17,18 +17,22 @@ //! An MMR storage implementation. -use alloc::{vec, vec::Vec}; -use codec::Encode; -use core::iter::Peekable; -use log::{debug, trace}; -use sp_core::offchain::StorageKind; -use sp_mmr_primitives::{mmr_lib, mmr_lib::helper, utils::NodesUtils}; - use crate::{ mmr::{Node, NodeOf}, - primitives::{self, NodeIndex}, + primitives::{mmr_lib, mmr_lib::helper, utils::NodesUtils, FullLeaf, NodeIndex}, BlockHashProvider, Config, Nodes, NumberOfLeaves, Pallet, }; +use alloc::{vec, vec::Vec}; +use codec::Encode; +use core::iter::Peekable; +use frame::{ + deps::{ + sp_core::offchain::StorageKind, + sp_io::{offchain, offchain_index}, + }, + prelude::*, +}; +use log::{debug, trace}; /// A marker type for runtime-specific storage implementation. /// @@ -48,20 +52,20 @@ pub struct OffchainStorage; impl OffchainStorage { fn get(key: &[u8]) -> Option> { - sp_io::offchain::local_storage_get(StorageKind::PERSISTENT, &key) + offchain::local_storage_get(StorageKind::PERSISTENT, &key) } #[cfg(not(feature = "runtime-benchmarks"))] fn set, I: 'static>(key: &[u8], value: &[u8]) { - sp_io::offchain_index::set(key, value); + offchain_index::set(key, value); } #[cfg(feature = "runtime-benchmarks")] fn set, I: 'static>(key: &[u8], value: &[u8]) { if crate::pallet::UseLocalStorage::::get() { - sp_io::offchain::local_storage_set(StorageKind::PERSISTENT, key, value); + offchain::local_storage_set(StorageKind::PERSISTENT, key, value); } else { - sp_io::offchain_index::set(key, value); + offchain_index::set(key, value); } } } @@ -82,7 +86,7 @@ impl mmr_lib::MMRStoreReadOps> for Storage, I: 'static, - L: primitives::FullLeaf + codec::Decode, + L: FullLeaf + Decode, { fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result>> { // Find out which leaf added node `pos` in the MMR. @@ -120,7 +124,7 @@ impl mmr_lib::MMRStoreWriteOps> for Storage, I: 'static, - L: primitives::FullLeaf + codec::Decode, + L: FullLeaf + Decode, { fn append(&mut self, _: NodeIndex, _: Vec>) -> mmr_lib::Result<()> { panic!("MMR must not be altered in the off-chain context.") @@ -131,7 +135,7 @@ impl mmr_lib::MMRStoreReadOps> for Storage, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result>> { Ok(Nodes::::get(pos).map(Node::Hash)) @@ -142,7 +146,7 @@ impl mmr_lib::MMRStoreWriteOps> for Storage, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { fn append(&mut self, pos: NodeIndex, elems: Vec>) -> mmr_lib::Result<()> { if elems.is_empty() { @@ -205,7 +209,7 @@ impl Storage where T: Config, I: 'static, - L: primitives::FullLeaf, + L: FullLeaf, { fn store_to_offchain( pos: NodeIndex, diff --git a/substrate/frame/merkle-mountain-range/src/mock.rs b/substrate/frame/merkle-mountain-range/src/mock.rs index 606719c6deba..4c234e0d94aa 100644 --- a/substrate/frame/merkle-mountain-range/src/mock.rs +++ b/substrate/frame/merkle-mountain-range/src/mock.rs @@ -18,14 +18,20 @@ use crate as pallet_mmr; use crate::*; +use crate::{ + frame_system::DefaultConfig, + primitives::{Compact, LeafDataProvider}, +}; use codec::{Decode, Encode}; -use frame_support::{derive_impl, parameter_types}; -use sp_mmr_primitives::{Compact, LeafDataProvider}; -use sp_runtime::traits::Keccak256; +use frame::{ + deps::frame_support::derive_impl, + prelude::{frame_system, frame_system::config_preludes::TestDefaultConfig}, + testing_prelude::*, +}; -type Block = frame_system::mocking::MockBlock; +type Block = MockBlock; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -33,7 +39,7 @@ frame_support::construct_runtime!( } ); -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +#[derive_impl(TestDefaultConfig)] impl frame_system::Config for Test { type Block = Block; } diff --git a/substrate/frame/merkle-mountain-range/src/tests.rs b/substrate/frame/merkle-mountain-range/src/tests.rs index 93e3d06eaa0a..ae0c58e91aba 100644 --- a/substrate/frame/merkle-mountain-range/src/tests.rs +++ b/substrate/frame/merkle-mountain-range/src/tests.rs @@ -17,19 +17,21 @@ use crate::{mock::*, *}; -use frame_support::traits::{Get, OnInitialize}; -use sp_core::{ - offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, - H256, +use crate::primitives::{mmr_lib::helper, utils, Compact, LeafProof}; + +use frame::{ + deps::sp_core::{ + offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, + H256, + }, + testing_prelude::*, }; -use sp_mmr_primitives::{mmr_lib::helper, utils, Compact, LeafProof}; -use sp_runtime::BuildStorage; -pub(crate) fn new_test_ext() -> sp_io::TestExternalities { +pub(crate) fn new_test_ext() -> TestState { frame_system::GenesisConfig::::default().build_storage().unwrap().into() } -fn register_offchain_ext(ext: &mut sp_io::TestExternalities) { +fn register_offchain_ext(ext: &mut TestState) { let (offchain, _offchain_state) = TestOffchainExt::with_offchain_db(ext.offchain_db()); ext.register_extension(OffchainDbExt::new(offchain.clone())); ext.register_extension(OffchainWorkerExt::new(offchain)); @@ -54,7 +56,7 @@ pub(crate) fn hex(s: &str) -> H256 { s.parse().unwrap() } -type BlockNumber = frame_system::pallet_prelude::BlockNumberFor; +type BlockNumber = BlockNumberFor; fn decode_node( v: Vec, @@ -517,7 +519,7 @@ fn should_verify() { } fn generate_and_verify_batch_proof( - ext: &mut sp_io::TestExternalities, + ext: &mut TestExternalities, block_numbers: &Vec, blocks_to_add: usize, ) { @@ -719,7 +721,6 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() { #[test] fn should_verify_canonicalized() { - use frame_support::traits::Hooks; sp_tracing::init_for_tests(); // How deep is our fork-aware storage (in terms of blocks/leaves, nodes will be more). From 711e6ff33373bc08b026446ce19b73920bfe068c Mon Sep 17 00:00:00 2001 From: runcomet Date: Mon, 20 Jan 2025 08:12:44 -0800 Subject: [PATCH 291/340] Migrate `pallet-assets-freezer` to umbrella crate (#6599) Part of https://github.com/paritytech/polkadot-sdk/issues/6504 ### Added modules - `utility`: Traits not tied to any direct operation in the runtime. polkadot address: 14SRqZTC1d8rfxL8W1tBTnfUBPU23ACFVPzp61FyGf4ftUFg --------- Co-authored-by: Giuseppe Re --- Cargo.lock | 7 +--- substrate/frame/assets-freezer/Cargo.toml | 23 ++--------- substrate/frame/assets-freezer/src/impls.rs | 12 ++---- substrate/frame/assets-freezer/src/lib.rs | 43 +++++++++++---------- substrate/frame/assets-freezer/src/mock.rs | 23 ++++------- substrate/frame/assets-freezer/src/tests.rs | 16 ++------ substrate/frame/src/lib.rs | 29 ++++++++++---- 7 files changed, 64 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50d36338cd2c..397d0c7fe823 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12175,17 +12175,12 @@ dependencies = [ name = "pallet-assets-freezer" version = "0.1.0" dependencies = [ - "frame-benchmarking 28.0.0", - "frame-support 28.0.0", - "frame-system 28.0.0", "log", "pallet-assets 29.1.0", "pallet-balances 28.0.0", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] diff --git a/substrate/frame/assets-freezer/Cargo.toml b/substrate/frame/assets-freezer/Cargo.toml index 3fffa4d0627f..d8c0ee6e442b 100644 --- a/substrate/frame/assets-freezer/Cargo.toml +++ b/substrate/frame/assets-freezer/Cargo.toml @@ -16,46 +16,31 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +frame = { workspace = true, features = ["runtime"] } log = { workspace = true } pallet-assets = { workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true } -sp-core = { workspace = true } -sp-io = { workspace = true } [features] default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", + "frame/std", "log/std", "pallet-assets/std", "pallet-balances/std", "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", + "frame/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", + "frame/try-runtime", "pallet-assets/try-runtime", "pallet-balances/try-runtime", - "sp-runtime/try-runtime", ] diff --git a/substrate/frame/assets-freezer/src/impls.rs b/substrate/frame/assets-freezer/src/impls.rs index cd383f1c3cd1..8c9f148e1e9a 100644 --- a/substrate/frame/assets-freezer/src/impls.rs +++ b/substrate/frame/assets-freezer/src/impls.rs @@ -16,13 +16,7 @@ // limitations under the License. use super::*; - -use frame_support::traits::{ - fungibles::{Inspect, InspectFreeze, MutateFreeze}, - tokens::{DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence}, -}; use pallet_assets::FrozenBalance; -use sp_runtime::traits::Zero; // Implements [`FrozenBalance`] from [`pallet-assets`], so it can understand how much of an // account balance is frozen, and is able to signal to this pallet when to clear the state of an @@ -115,7 +109,7 @@ impl, I: 'static> MutateFreeze for Pallet { id: &Self::Id, who: &T::AccountId, amount: Self::Balance, - ) -> sp_runtime::DispatchResult { + ) -> DispatchResult { if amount.is_zero() { return Self::thaw(asset, id, who); } @@ -135,7 +129,7 @@ impl, I: 'static> MutateFreeze for Pallet { id: &Self::Id, who: &T::AccountId, amount: Self::Balance, - ) -> sp_runtime::DispatchResult { + ) -> DispatchResult { if amount.is_zero() { return Ok(()); } @@ -150,7 +144,7 @@ impl, I: 'static> MutateFreeze for Pallet { Self::update_freezes(asset, who, freezes.as_bounded_slice()) } - fn thaw(asset: Self::AssetId, id: &Self::Id, who: &T::AccountId) -> sp_runtime::DispatchResult { + fn thaw(asset: Self::AssetId, id: &Self::Id, who: &T::AccountId) -> DispatchResult { let mut freezes = Freezes::::get(asset.clone(), who); freezes.retain(|f| &f.id != id); Self::update_freezes(asset, who, freezes.as_bounded_slice()) diff --git a/substrate/frame/assets-freezer/src/lib.rs b/substrate/frame/assets-freezer/src/lib.rs index b42d41ac1d92..5f718ed84820 100644 --- a/substrate/frame/assets-freezer/src/lib.rs +++ b/substrate/frame/assets-freezer/src/lib.rs @@ -18,10 +18,10 @@ //! # Assets Freezer Pallet //! //! A pallet capable of freezing fungibles from `pallet-assets`. This is an extension of -//! `pallet-assets`, wrapping [`fungibles::Inspect`](`frame_support::traits::fungibles::Inspect`). +//! `pallet-assets`, wrapping [`fungibles::Inspect`](`Inspect`). //! It implements both -//! [`fungibles::freeze::Inspect`](frame_support::traits::fungibles::freeze::Inspect) and -//! [`fungibles::freeze::Mutate`](frame_support::traits::fungibles::freeze::Mutate). The complexity +//! [`fungibles::freeze::Inspect`](InspectFreeze) and +//! [`fungibles::freeze::Mutate`](MutateFreeze). The complexity //! of the operations is `O(n)`. where `n` is the variant count of `RuntimeFreezeReason`. //! //! ## Pallet API @@ -35,26 +35,27 @@ //! //! - Pallet hooks allowing [`pallet-assets`] to know the frozen balance for an account on a given //! asset (see [`pallet_assets::FrozenBalance`]). -//! - An implementation of -//! [`fungibles::freeze::Inspect`](frame_support::traits::fungibles::freeze::Inspect) and -//! [`fungibles::freeze::Mutate`](frame_support::traits::fungibles::freeze::Mutate), allowing -//! other pallets to manage freezes for the `pallet-assets` assets. +//! - An implementation of [`fungibles::freeze::Inspect`](InspectFreeze) and +//! [`fungibles::freeze::Mutate`](MutateFreeze), allowing other pallets to manage freezes for the +//! `pallet-assets` assets. #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ - pallet_prelude::*, - traits::{tokens::IdAmount, VariantCount, VariantCountOf}, - BoundedVec, -}; -use frame_system::pallet_prelude::BlockNumberFor; -use sp_runtime::{ - traits::{Saturating, Zero}, - BoundedSlice, +use frame::{ + prelude::*, + traits::{ + fungibles::{Inspect, InspectFreeze, MutateFreeze}, + tokens::{ + DepositConsequence, Fortitude, IdAmount, Preservation, Provenance, WithdrawConsequence, + }, + }, }; pub use pallet::*; +#[cfg(feature = "try-runtime")] +use frame::try_runtime::TryRuntimeError; + #[cfg(test)] mod mock; #[cfg(test)] @@ -62,7 +63,7 @@ mod tests; mod impls; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; @@ -125,7 +126,7 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { #[cfg(feature = "try-runtime")] - fn try_state(_: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + fn try_state(_: BlockNumberFor) -> Result<(), TryRuntimeError> { Self::do_try_state() } } @@ -159,13 +160,13 @@ impl, I: 'static> Pallet { Ok(()) } - #[cfg(any(test, feature = "try-runtime"))] - fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + #[cfg(feature = "try-runtime")] + fn do_try_state() -> Result<(), TryRuntimeError> { for (asset, who, _) in FrozenBalances::::iter() { let max_frozen_amount = Freezes::::get(asset.clone(), who.clone()).iter().map(|l| l.amount).max(); - frame_support::ensure!( + ensure!( FrozenBalances::::get(asset, who) == max_frozen_amount, "The `FrozenAmount` is not equal to the maximum amount in `Freezes` for (`asset`, `who`)" ); diff --git a/substrate/frame/assets-freezer/src/mock.rs b/substrate/frame/assets-freezer/src/mock.rs index bc903a018f7b..ad08787aba27 100644 --- a/substrate/frame/assets-freezer/src/mock.rs +++ b/substrate/frame/assets-freezer/src/mock.rs @@ -20,23 +20,15 @@ use crate as pallet_assets_freezer; pub use crate::*; use codec::{Compact, Decode, Encode, MaxEncodedLen}; -use frame_support::{ - derive_impl, - traits::{AsEnsureOriginWithArg, ConstU64}, -}; +use frame::testing_prelude::*; use scale_info::TypeInfo; -use sp_core::{ConstU32, H256}; -use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, -}; pub type AccountId = u64; pub type Balance = u64; pub type AssetId = u32; type Block = frame_system::mocking::MockBlock; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test { System: frame_system, @@ -48,7 +40,7 @@ frame_support::construct_runtime!( #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; + type BaseCallFilter = Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); @@ -70,7 +62,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { @@ -132,7 +124,7 @@ impl Config for Test { type RuntimeEvent = RuntimeEvent; } -pub fn new_test_ext(execute: impl FnOnce()) -> sp_io::TestExternalities { +pub fn new_test_ext(execute: impl FnOnce()) -> TestExternalities { let t = RuntimeGenesisConfig { assets: pallet_assets::GenesisConfig { assets: vec![(1, 0, true, 1)], @@ -145,11 +137,12 @@ pub fn new_test_ext(execute: impl FnOnce()) -> sp_io::TestExternalities { } .build_storage() .unwrap(); - let mut ext: sp_io::TestExternalities = t.into(); + let mut ext: TestExternalities = t.into(); ext.execute_with(|| { System::set_block_number(1); execute(); - frame_support::assert_ok!(AssetsFreezer::do_try_state()); + #[cfg(feature = "try-runtime")] + assert_ok!(AssetsFreezer::do_try_state()); }); ext diff --git a/substrate/frame/assets-freezer/src/tests.rs b/substrate/frame/assets-freezer/src/tests.rs index 4f2dea79c705..b890dc98b574 100644 --- a/substrate/frame/assets-freezer/src/tests.rs +++ b/substrate/frame/assets-freezer/src/tests.rs @@ -17,22 +17,16 @@ //! Tests for pallet-assets-freezer. -use crate::mock::*; +use crate::mock::{self, *}; use codec::Compact; -use frame_support::{ - assert_ok, assert_storage_noop, - traits::{ - fungibles::{Inspect, InspectFreeze, MutateFreeze}, - tokens::{Fortitude, Preservation}, - }, -}; +use frame::testing_prelude::*; use pallet_assets::FrozenBalance; const WHO: AccountId = 1; -const ASSET_ID: AssetId = 1; +const ASSET_ID: mock::AssetId = 1; -fn test_set_freeze(id: DummyFreezeReason, amount: Balance) { +fn test_set_freeze(id: DummyFreezeReason, amount: mock::Balance) { let mut freezes = Freezes::::get(ASSET_ID, WHO); if let Some(i) = freezes.iter_mut().find(|l| l.id == id) { @@ -281,8 +275,6 @@ mod impl_mutate_freeze { } mod with_pallet_assets { - use frame_support::assert_noop; - use super::*; #[test] diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 18c7bd123944..1c4b2ed5b821 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -202,12 +202,10 @@ pub mod prelude { /// Dispatch types from `frame-support`, other fundamental traits #[doc(no_inline)] pub use frame_support::dispatch::{GetDispatchInfo, PostDispatchInfo}; - pub use frame_support::{ - defensive, defensive_assert, - traits::{ - Contains, EitherOf, EstimateNextSessionRotation, IsSubType, MapSuccess, NoOpPoll, - OnRuntimeUpgrade, OneSessionHandler, RankedMembers, RankedMembersSwapHandler, - }, + pub use frame_support::traits::{ + Contains, EitherOf, EstimateNextSessionRotation, Everything, IsSubType, MapSuccess, + NoOpPoll, OnRuntimeUpgrade, OneSessionHandler, RankedMembers, RankedMembersSwapHandler, + VariantCount, VariantCountOf, }; /// Pallet prelude of `frame-system`. @@ -225,6 +223,9 @@ pub mod prelude { /// All hashing related things pub use super::hashing::*; + /// All account related things. + pub use super::account::*; + /// All arithmetic types and traits used for safe math. pub use super::arithmetic::*; @@ -234,6 +235,10 @@ pub mod prelude { BlockNumberProvider, Bounded, Convert, DispatchInfoOf, Dispatchable, ReduceBy, ReplaceWithDefault, SaturatedConversion, Saturating, StaticLookup, TrailingZeroInput, }; + + /// Bounded storage related types. + pub use sp_runtime::{BoundedSlice, BoundedVec}; + /// Other error/result types for runtime #[doc(no_inline)] pub use sp_runtime::{ @@ -321,7 +326,7 @@ pub mod testing_prelude { /// Other helper macros from `frame_support` that help with asserting in tests. pub use frame_support::{ assert_err, assert_err_ignore_postinfo, assert_error_encoded_size, assert_noop, assert_ok, - assert_storage_noop, hypothetically, storage_alias, + assert_storage_noop, ensure, hypothetically, storage_alias, }; pub use frame_system::{self, mocking::*, RunToBlockHooks}; @@ -551,6 +556,16 @@ pub mod hashing { pub use sp_runtime::traits::{BlakeTwo256, Hash, Keccak256}; } +/// All account management related traits. +/// +/// This is already part of the [`prelude`]. +pub mod account { + pub use frame_support::traits::{ + AsEnsureOriginWithArg, ChangeMembers, EitherOfDiverse, InitializeMembers, + }; + pub use sp_runtime::traits::{IdentifyAccount, IdentityLookup}; +} + /// Access to all of the dependencies of this crate. In case the prelude re-exports are not enough, /// this module can be used. /// From 2c4ceccebe2c338029eef243645455d525a5a78b Mon Sep 17 00:00:00 2001 From: Benjamin Gallois Date: Mon, 20 Jan 2025 22:19:48 +0100 Subject: [PATCH 292/340] Fix `frame-benchmarking-cli` not buildable without rocksdb (#7263) ## Description The `frame-benchmarking-cli` crate has not been buildable without the `rocksdb` feature since version 1.17.0. **Error:** ```rust self.database()?.unwrap_or(Database::RocksDb), ^^^^^^^ variant or associated item not found in `Database` ``` This issue is also related to the `rocksdb` feature bleeding (#3793), where the `rocksdb` feature was always activated even when compiling this crate with `--no-default-features`. **Fix:** - Resolved the error by choosing `paritydb` as the default database when compiled without the `rocksdb` feature. - Fixed the issue where the `sc-cli` crate's `rocksdb` feature was always active, even compiling `frame-benchmarking-cli` with `--no-default-features`. ## Review Notes Fix the crate to be built without rocksdb, not intended to solve #3793. --------- Co-authored-by: command-bot <> --- polkadot/node/metrics/Cargo.toml | 2 +- prdoc/pr_7263.prdoc | 28 +++++++++++++++++++ .../benchmarking-cli/src/overhead/command.rs | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_7263.prdoc diff --git a/polkadot/node/metrics/Cargo.toml b/polkadot/node/metrics/Cargo.toml index 454337cb63f8..318deca4f243 100644 --- a/polkadot/node/metrics/Cargo.toml +++ b/polkadot/node/metrics/Cargo.toml @@ -18,7 +18,7 @@ gum = { workspace = true, default-features = true } metered = { features = ["futures_channel"], workspace = true } # Both `sc-service` and `sc-cli` are required by runtime metrics `logger_hook()`. -sc-cli = { workspace = true, default-features = true } +sc-cli = { workspace = true } sc-service = { workspace = true, default-features = true } bs58 = { features = ["alloc"], workspace = true, default-features = true } diff --git a/prdoc/pr_7263.prdoc b/prdoc/pr_7263.prdoc new file mode 100644 index 000000000000..892e80493955 --- /dev/null +++ b/prdoc/pr_7263.prdoc @@ -0,0 +1,28 @@ +title: Fix `frame-benchmarking-cli` not buildable without rocksdb +doc: +- audience: Runtime Dev + description: |- + ## Description + + The `frame-benchmarking-cli` crate has not been buildable without the `rocksdb` feature since version 1.17.0. + + **Error:** + ```rust + self.database()?.unwrap_or(Database::RocksDb), + ^^^^^^^ variant or associated item not found in `Database` + ``` + + This issue is also related to the `rocksdb` feature bleeding (#3793), where the `rocksdb` feature was always activated even when compiling this crate with `--no-default-features`. + + **Fix:** + - Resolved the error by choosing `paritydb` as the default database when compiled without the `rocksdb` feature. + - Fixed the issue where the `sc-cli` crate's `rocksdb` feature was always active, even compiling `frame-benchmarking-cli` with `--no-default-features`. + + ## Review Notes + + Fix the crate to be built without rocksdb, not intended to solve #3793. +crates: +- name: polkadot-node-metrics + bump: patch +- name: frame-benchmarking-cli + bump: patch diff --git a/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs b/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs index 8df8ee5464f7..847f8e16c0df 100644 --- a/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/overhead/command.rs @@ -482,7 +482,7 @@ impl OverheadCmd { let database_source = self.database_config( &base_path.path().to_path_buf(), self.database_cache_size()?.unwrap_or(1024), - self.database()?.unwrap_or(Database::RocksDb), + self.database()?.unwrap_or(Database::Auto), )?; let backend = new_db_backend(DatabaseSettings { From cbf3925e1fe1383b998cfb428038c46da1577501 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Mon, 20 Jan 2025 23:58:21 +0100 Subject: [PATCH 293/340] [eth-indexer] subscribe to finalize blocks instead of best blocks (#7260) For eth-indexer, it's probably safer to use `subscribe_finalized` and index these blocks into the DB rather than `subscribe_best` --------- Co-authored-by: command-bot <> --- prdoc/pr_7260.prdoc | 10 ++++++ substrate/frame/revive/rpc/src/client.rs | 45 +++++++++++++++--------- 2 files changed, 39 insertions(+), 16 deletions(-) create mode 100644 prdoc/pr_7260.prdoc diff --git a/prdoc/pr_7260.prdoc b/prdoc/pr_7260.prdoc new file mode 100644 index 000000000000..62f73120bc19 --- /dev/null +++ b/prdoc/pr_7260.prdoc @@ -0,0 +1,10 @@ +title: '[eth-indexer] subscribe to finalize blocks instead of best blocks' +doc: +- audience: Runtime Dev + description: 'For eth-indexer, it''s probably safer to use `subscribe_finalized` + and index these blocks into the DB rather than `subscribe_best` + + ' +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index a5a022f97228..7a72f8e26b0b 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -68,6 +68,14 @@ pub type Shared = Arc>; /// The runtime balance type. pub type Balance = u128; +/// The subscription type used to listen to new blocks. +pub enum SubscriptionType { + /// Subscribe to the best blocks. + BestBlocks, + /// Subscribe to the finalized blocks. + FinalizedBlocks, +} + /// Unwrap the original `jsonrpsee::core::client::Error::Call` error. fn unwrap_call_err(err: &subxt::error::RpcError) -> Option { use subxt::backend::rpc::reconnecting_rpc_client; @@ -278,19 +286,23 @@ impl Client { /// Subscribe to new best blocks, and execute the async closure with /// the extracted block and ethereum transactions - async fn subscribe_new_blocks(&self, callback: F) -> Result<(), ClientError> + async fn subscribe_new_blocks( + &self, + subscription_type: SubscriptionType, + callback: F, + ) -> Result<(), ClientError> where F: Fn(SubstrateBlock) -> Fut + Send + Sync, Fut: std::future::Future> + Send, { log::info!(target: LOG_TARGET, "Subscribing to new blocks"); - let mut block_stream = match self.api.blocks().subscribe_best().await { - Ok(s) => s, - Err(err) => { - log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); - return Err(err.into()); - }, - }; + let mut block_stream = match subscription_type { + SubscriptionType::BestBlocks => self.api.blocks().subscribe_best().await, + SubscriptionType::FinalizedBlocks => self.api.blocks().subscribe_finalized().await, + } + .inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); + })?; while let Some(block) = block_stream.next().await { let block = match block { @@ -324,7 +336,7 @@ impl Client { let client = self.clone(); spawn_handle.spawn("subscribe-blocks", None, async move { let res = client - .subscribe_new_blocks(|block| async { + .subscribe_new_blocks(SubscriptionType::BestBlocks, |block| async { let receipts = extract_receipts_from_block(&block).await?; client.receipt_provider.insert(&block.hash(), &receipts).await; @@ -347,13 +359,14 @@ impl Client { &self, oldest_block: Option, ) -> Result<(), ClientError> { - let new_blocks_fut = self.subscribe_new_blocks(|block| async move { - let receipts = extract_receipts_from_block(&block).await.inspect_err(|err| { - log::error!(target: LOG_TARGET, "Failed to extract receipts from block: {err:?}"); - })?; - self.receipt_provider.insert(&block.hash(), &receipts).await; - Ok(()) - }); + let new_blocks_fut = + self.subscribe_new_blocks(SubscriptionType::FinalizedBlocks, |block| async move { + let receipts = extract_receipts_from_block(&block).await.inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to extract receipts from block: {err:?}"); + })?; + self.receipt_provider.insert(&block.hash(), &receipts).await; + Ok(()) + }); let Some(oldest_block) = oldest_block else { return new_blocks_fut.await }; From 12ed0f4ffe4dcf3a8fe8928e3791141a110fad8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Molina=20Colmenero?= Date: Tue, 21 Jan 2025 10:49:09 +0100 Subject: [PATCH 294/340] Add an extra_constant to pallet-collator-selection (#7206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently `pallet-collator-selection` does not expose a way to query the assigned pot account derived from the `PotId` configuration item. Without it, it is not possible to transfer the existential deposit to it. This PR addresses this issue by exposing an extra constant. --------- Co-authored-by: Bastian Köcher --- cumulus/pallets/collator-selection/src/lib.rs | 13 +++++++++++++ prdoc/pr_7206.prdoc | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 prdoc/pr_7206.prdoc diff --git a/cumulus/pallets/collator-selection/src/lib.rs b/cumulus/pallets/collator-selection/src/lib.rs index 9d7e62af3c68..34c6ca8b36ea 100644 --- a/cumulus/pallets/collator-selection/src/lib.rs +++ b/cumulus/pallets/collator-selection/src/lib.rs @@ -150,22 +150,27 @@ pub mod pallet { type UpdateOrigin: EnsureOrigin; /// Account Identifier from which the internal Pot is generated. + #[pallet::constant] type PotId: Get; /// Maximum number of candidates that we should have. /// /// This does not take into account the invulnerables. + #[pallet::constant] type MaxCandidates: Get; /// Minimum number eligible collators. Should always be greater than zero. This includes /// Invulnerable collators. This ensures that there will always be one collator who can /// produce a block. + #[pallet::constant] type MinEligibleCollators: Get; /// Maximum number of invulnerables. + #[pallet::constant] type MaxInvulnerables: Get; // Will be kicked if block is not produced in threshold. + #[pallet::constant] type KickThreshold: Get>; /// A stable ID for a validator. @@ -183,6 +188,14 @@ pub mod pallet { type WeightInfo: WeightInfo; } + #[pallet::extra_constants] + impl Pallet { + /// Gets this pallet's derived pot account. + fn pot_account() -> T::AccountId { + Self::account_id() + } + } + /// Basic information about a collation candidate. #[derive( PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, diff --git a/prdoc/pr_7206.prdoc b/prdoc/pr_7206.prdoc new file mode 100644 index 000000000000..d605308ba54c --- /dev/null +++ b/prdoc/pr_7206.prdoc @@ -0,0 +1,13 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: "Add an extra_constant to pallet-collator-selection" + +doc: + - audience: Runtime Dev + description: | + - Allows to query collator-selection's pot account via extra constant. + +crates: + - name: pallet-collator-selection + bump: minor \ No newline at end of file From c0c0632c2efca435e973a1f6788e24235fe0e2a6 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Tue, 21 Jan 2025 16:11:50 +0200 Subject: [PATCH 295/340] Snowbridge - Copy Rococo integration tests to Westend (#7108) Copies all the integration tests from Rococo to Westend. Closes: https://github.com/paritytech/polkadot-sdk/issues/6389 --- .../bridges/bridge-hub-westend/src/lib.rs | 3 + .../bridges/bridge-hub-westend/src/lib.rs | 4 +- .../src/tests/snowbridge.rs | 647 +++++++++++++++++- 3 files changed, 646 insertions(+), 8 deletions(-) diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs index b548e3b7e64c..1b6f79651887 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/lib.rs @@ -18,6 +18,7 @@ pub mod genesis; pub use bridge_hub_westend_runtime::{ self, xcm_config::XcmConfig as BridgeHubWestendXcmConfig, ExistentialDeposit as BridgeHubWestendExistentialDeposit, + RuntimeOrigin as BridgeHubWestendRuntimeOrigin, }; // Substrate @@ -47,6 +48,8 @@ decl_test_parachains! { PolkadotXcm: bridge_hub_westend_runtime::PolkadotXcm, Balances: bridge_hub_westend_runtime::Balances, EthereumSystem: bridge_hub_westend_runtime::EthereumSystem, + EthereumInboundQueue: bridge_hub_westend_runtime::EthereumInboundQueue, + EthereumOutboundQueue: bridge_hub_westend_runtime::EthereumOutboundQueue, } }, } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs index 501ddb84d425..3d4d4f58e3b5 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/lib.rs @@ -51,9 +51,11 @@ mod imports { }, bridge_hub_westend_emulated_chain::{ genesis::ED as BRIDGE_HUB_WESTEND_ED, BridgeHubWestendExistentialDeposit, - BridgeHubWestendParaPallet as BridgeHubWestendPallet, BridgeHubWestendXcmConfig, + BridgeHubWestendParaPallet as BridgeHubWestendPallet, BridgeHubWestendRuntimeOrigin, + BridgeHubWestendXcmConfig, }, penpal_emulated_chain::{ + self, penpal_runtime::xcm_config::{ CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub, LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub, diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs index ffa60a4f52e7..15ca3a5cf1b8 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs @@ -12,15 +12,18 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use crate::imports::*; +use crate::{imports::*, tests::penpal_emulated_chain::penpal_runtime}; use asset_hub_westend_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; -use bridge_hub_westend_runtime::EthereumInboundQueue; +use bridge_hub_westend_runtime::{ + bridge_to_ethereum_config::EthereumGatewayAddress, EthereumBeaconClient, EthereumInboundQueue, +}; use codec::{Decode, Encode}; -use emulated_integration_tests_common::RESERVABLE_ASSET_ID; +use emulated_integration_tests_common::{PENPAL_B_ID, RESERVABLE_ASSET_ID}; use frame_support::pallet_prelude::TypeInfo; use hex_literal::hex; use rococo_westend_system_emulated_network::asset_hub_westend_emulated_chain::genesis::AssetHubWestendAssetOwner; -use snowbridge_core::{outbound::OperatingMode, AssetMetadata, TokenIdOf}; +use snowbridge_core::{inbound::InboundQueueFixture, AssetMetadata, TokenIdOf}; +use snowbridge_pallet_inbound_queue_fixtures::send_native_eth::make_send_native_eth_message; use snowbridge_router_primitives::inbound::{ Command, Destination, EthereumLocationsConverterFor, MessageV1, VersionedMessage, }; @@ -28,19 +31,20 @@ use sp_core::H256; use testnet_parachains_constants::westend::snowbridge::EthereumNetwork; use xcm_executor::traits::ConvertLocation; -const INITIAL_FUND: u128 = 5_000_000_000_000; pub const CHAIN_ID: u64 = 11155111; pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d"); +const INITIAL_FUND: u128 = 5_000_000_000_000; const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e"); const XCM_FEE: u128 = 100_000_000_000; +const INSUFFICIENT_XCM_FEE: u128 = 1000; const TOKEN_AMOUNT: u128 = 100_000_000_000; +const TREASURY_ACCOUNT: [u8; 32] = + hex!("6d6f646c70792f74727372790000000000000000000000000000000000000000"); #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum ControlCall { #[codec(index = 3)] CreateAgent, - #[codec(index = 4)] - CreateChannel { mode: OperatingMode }, } #[allow(clippy::large_enum_variant)] @@ -50,6 +54,75 @@ pub enum SnowbridgeControl { Control(ControlCall), } +pub fn send_inbound_message(fixture: InboundQueueFixture) -> DispatchResult { + EthereumBeaconClient::store_finalized_header( + fixture.finalized_header, + fixture.block_roots_root, + ) + .unwrap(); + EthereumInboundQueue::submit( + BridgeHubWestendRuntimeOrigin::signed(BridgeHubWestendSender::get()), + fixture.message, + ) +} + +/// Create an agent on Ethereum. An agent is a representation of an entity in the Polkadot +/// ecosystem (like a parachain) on Ethereum. +#[test] +#[ignore] +fn create_agent() { + let origin_para: u32 = 1001; + // Fund the origin parachain sovereign account so that it can pay execution fees. + BridgeHubWestend::fund_para_sovereign(origin_para.into(), INITIAL_FUND); + + let sudo_origin = ::RuntimeOrigin::root(); + let destination = Westend::child_location_of(BridgeHubWestend::para_id()).into(); + + let create_agent_call = SnowbridgeControl::Control(ControlCall::CreateAgent {}); + // Construct XCM to create an agent for para 1001 + let remote_xcm = VersionedXcm::from(Xcm(vec![ + UnpaidExecution { weight_limit: Unlimited, check_origin: None }, + DescendOrigin(Parachain(origin_para).into()), + Transact { + origin_kind: OriginKind::Xcm, + call: create_agent_call.encode().into(), + fallback_max_weight: None, + }, + ])); + + // Westend Global Consensus + // Send XCM message from Relay Chain to Bridge Hub source Parachain + Westend::execute_with(|| { + assert_ok!(::XcmPallet::send( + sudo_origin, + bx!(destination), + bx!(remote_xcm), + )); + + type RuntimeEvent = ::RuntimeEvent; + // Check that the Transact message was sent + assert_expected_events!( + Westend, + vec![ + RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. }) => {}, + ] + ); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that a message was sent to Ethereum to create the agent + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::EthereumSystem(snowbridge_pallet_system::Event::CreateAgent { + .. + }) => {}, + ] + ); + }); +} + /// Tests the registering of a token as an asset on AssetHub. #[test] fn register_weth_token_from_ethereum_to_asset_hub() { @@ -82,6 +155,566 @@ fn register_weth_token_from_ethereum_to_asset_hub() { }); } +/// Tests the registering of a token as an asset on AssetHub, and then subsequently sending +/// a token from Ethereum to AssetHub. +#[test] +fn send_weth_token_from_ethereum_to_asset_hub() { + let ethereum_network: NetworkId = EthereumNetwork::get().into(); + let origin_location = Location::new(2, ethereum_network); + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + // Fund ethereum sovereign on AssetHub + AssetHubWestend::fund_accounts(vec![ + (AssetHubWestendReceiver::get(), INITIAL_FUND), + (ethereum_sovereign, INITIAL_FUND), + ]); + + // Register the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); + + // Send the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: AssetHubWestendSender::get().into() }, + amount: 1_000_000, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +/// Tests sending a token to a 3rd party parachain, called PenPal. The token reserve is +/// still located on AssetHub. +#[test] +fn send_weth_from_ethereum_to_penpal() { + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + // Fund AssetHub sovereign account so it can pay execution fees for the asset transfer + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + + // Fund PenPal receiver (covering ED) + let native_id: Location = Parent.into(); + let receiver: AccountId = [ + 28, 189, 45, 67, 83, 10, 68, 112, 90, 208, 136, 175, 49, 62, 24, 248, 11, 83, 239, 22, 179, + 97, 119, 205, 75, 119, 184, 70, 242, 165, 240, 124, + ] + .into(); + PenpalB::mint_foreign_asset( + ::RuntimeOrigin::signed(PenpalAssetOwner::get()), + native_id, + receiver, + penpal_runtime::EXISTENTIAL_DEPOSIT, + ); + + PenpalB::execute_with(|| { + assert_ok!(::System::set_storage( + ::RuntimeOrigin::root(), + vec![( + PenpalCustomizableAssetFromSystemAssetHub::key().to_vec(), + Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]).encode(), + )], + )); + }); + + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + + // The Weth asset location, identified by the contract address on Ethereum + let weth_asset_location: Location = + (Parent, Parent, ethereum_network_v5, AccountKey20 { network: None, key: WETH }).into(); + + let origin_location = (Parent, Parent, ethereum_network_v5).into(); + + // Fund ethereum sovereign on AssetHub + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + AssetHubWestend::fund_accounts(vec![(ethereum_sovereign.clone(), INITIAL_FUND)]); + + // Create asset on the Penpal parachain. + PenpalB::execute_with(|| { + assert_ok!(::ForeignAssets::force_create( + ::RuntimeOrigin::root(), + weth_asset_location.clone(), + asset_hub_sovereign.into(), + false, + 1000, + )); + + assert!(::ForeignAssets::asset_exists(weth_asset_location)); + }); + + // Register the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert([0; 32].into(), message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},] + ); + }); + + // Send the token + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::ForeignAccountId32 { + para_id: PENPAL_B_ID, + id: PenpalBReceiver::get().into(), + fee: XCM_FEE, + }, + amount: 1_000_000, + fee: XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + assert_expected_events!( + BridgeHubWestend, + vec![RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + PenpalB::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the assets were issued on PenPal + assert_expected_events!( + PenpalB, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +/// Tests the full cycle of eth transfers: +/// - sending a token to AssetHub +/// - returning the token to Ethereum +#[test] +fn send_eth_asset_from_asset_hub_to_ethereum_and_back() { + let ethereum_network: NetworkId = EthereumNetwork::get().into(); + let origin_location = (Parent, Parent, ethereum_network).into(); + + use asset_hub_westend_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee; + let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id()); + let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location); + let ethereum_sovereign: AccountId = + EthereumLocationsConverterFor::::convert_location(&origin_location).unwrap(); + + AssetHubWestend::force_default_xcm_version(Some(XCM_VERSION)); + BridgeHubWestend::force_default_xcm_version(Some(XCM_VERSION)); + AssetHubWestend::force_xcm_version(origin_location.clone(), XCM_VERSION); + + BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]); + AssetHubWestend::fund_accounts(vec![ + (AssetHubWestendReceiver::get(), INITIAL_FUND), + (ethereum_sovereign.clone(), INITIAL_FUND), + ]); + + // Register ETH + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + origin_location.clone(), + ethereum_sovereign.into(), + true, + 1000, + )); + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::ForceCreated { .. }) => {}, + ] + ); + }); + const ETH_AMOUNT: u128 = 1_000_000_000_000_000_000; + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + // Set the gateway. This is needed because new fixtures use a different gateway address. + assert_ok!(::System::set_storage( + RuntimeOrigin::root(), + vec![( + EthereumGatewayAddress::key().to_vec(), + sp_core::H160(hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d")).encode(), + )], + )); + + // Construct SendToken message and sent to inbound queue + assert_ok!(send_inbound_message(make_send_native_eth_message())); + + // Check that the send token message was sent using xcm + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type RuntimeOrigin = ::RuntimeOrigin; + + let _issued_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { + asset_id: origin_location.clone(), + owner: AssetHubWestendReceiver::get().into(), + amount: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + assert_expected_events!( + AssetHubWestend, + vec![ + _issued_event => {}, + ] + ); + let assets = + vec![Asset { id: AssetId(origin_location.clone()), fun: Fungible(ETH_AMOUNT) }]; + let multi_assets = VersionedAssets::from(Assets::from(assets)); + + let destination = origin_location.clone().into(); + + let beneficiary = VersionedLocation::from(Location::new( + 0, + [AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }], + )); + + let free_balance_before = + ::Balances::free_balance( + AssetHubWestendReceiver::get(), + ); + // Send the Weth back to Ethereum + ::PolkadotXcm::limited_reserve_transfer_assets( + RuntimeOrigin::signed(AssetHubWestendReceiver::get()), + Box::new(destination), + Box::new(beneficiary), + Box::new(multi_assets), + 0, + Unlimited, + ) + .unwrap(); + + let _burned_event = RuntimeEvent::ForeignAssets(pallet_assets::Event::Burned { + asset_id: origin_location.clone(), + owner: AssetHubWestendReceiver::get().into(), + balance: ETH_AMOUNT, + }); + // Check that AssetHub has issued the foreign asset + let _destination = origin_location.clone(); + assert_expected_events!( + AssetHubWestend, + vec![ + _burned_event => {}, + RuntimeEvent::PolkadotXcm(pallet_xcm::Event::Sent { + destination: _destination, .. + }) => {}, + ] + ); + + let free_balance_after = ::Balances::free_balance( + AssetHubWestendReceiver::get(), + ); + // Assert at least DefaultBridgeHubEthereumBaseFee charged from the sender + let free_balance_diff = free_balance_before - free_balance_after; + assert!(free_balance_diff > DefaultBridgeHubEthereumBaseFee::get()); + }); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + // Check that the transfer token back to Ethereum message was queue in the Ethereum + // Outbound Queue + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageAccepted {..}) => {}, + RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued {..}) => {}, + ] + ); + + let events = BridgeHubWestend::events(); + // Check that the local fee was credited to the Snowbridge sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _ }) + if *who == TREASURY_ACCOUNT.into() + )), + "Snowbridge sovereign takes local fee." + ); + // Check that the remote fee was credited to the AssetHub sovereign account + assert!( + events.iter().any(|event| matches!( + event, + RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount: _ }) + if *who == assethub_sovereign + )), + "AssetHub sovereign takes remote fee." + ); + }); +} + +#[test] +fn register_weth_token_in_asset_hub_fail_for_insufficient_fee() { + BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND); + + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::RegisterToken { + token: WETH.into(), + // Insufficient fee which should trigger the trap + fee: INSUFFICIENT_XCM_FEE, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap(); + + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + +fn send_weth_from_ethereum_to_asset_hub_with_fee(account_id: [u8; 32], fee: u128) { + let ethereum_network_v5: NetworkId = EthereumNetwork::get().into(); + let weth_asset_location: Location = + Location::new(2, [ethereum_network_v5.into(), AccountKey20 { network: None, key: WETH }]); + // Fund asset hub sovereign on bridge hub + let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new( + 1, + [Parachain(AssetHubWestend::para_id().into())], + )); + BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]); + + // Register WETH + AssetHubWestend::execute_with(|| { + type RuntimeOrigin = ::RuntimeOrigin; + + assert_ok!(::ForeignAssets::force_create( + RuntimeOrigin::root(), + weth_asset_location.clone().try_into().unwrap(), + asset_hub_sovereign.into(), + false, + 1, + )); + + assert!(::ForeignAssets::asset_exists( + weth_asset_location.clone().try_into().unwrap(), + )); + }); + + // Send WETH to an existent account on asset hub + BridgeHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + type EthereumInboundQueue = + ::EthereumInboundQueue; + let message_id: H256 = [0; 32].into(); + let message = VersionedMessage::V1(MessageV1 { + chain_id: CHAIN_ID, + command: Command::SendToken { + token: WETH.into(), + destination: Destination::AccountId32 { id: account_id }, + amount: 1_000_000, + fee, + }, + }); + let (xcm, _) = EthereumInboundQueue::do_convert(message_id, message).unwrap(); + assert_ok!(EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into())); + + // Check that the message was sent + assert_expected_events!( + BridgeHubWestend, + vec![ + RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee(AssetHubWestendSender::get().into(), XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the token was received and issued as a foreign asset on AssetHub + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_insufficient_fee() { + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], INSUFFICIENT_XCM_FEE); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the message was not processed successfully due to insufficient fee + + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + +#[test] +fn send_weth_from_ethereum_to_non_existent_account_on_asset_hub_with_sufficient_fee_but_do_not_satisfy_ed( +) { + // On AH the xcm fee is 26_789_690 and the ED is 3_300_000 + send_weth_from_ethereum_to_asset_hub_with_fee([1; 32], 30_000_000); + + AssetHubWestend::execute_with(|| { + type RuntimeEvent = ::RuntimeEvent; + + // Check that the message was not processed successfully due to insufficient ED + assert_expected_events!( + AssetHubWestend, + vec![ + RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success:false, .. }) => {}, + ] + ); + }); +} + /// Tests the registering of a token as an asset on AssetHub, and then subsequently sending /// a token from Ethereum to AssetHub. #[test] From ebde96caf5bf24a626d7de247724a599f106284f Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Tue, 21 Jan 2025 17:33:47 +0100 Subject: [PATCH 296/340] Fix link-checker job (#7261) Link-checker job is constantly failing because of these two links. In the browser there is a redirect, apparently our lychee checker can't handle it. --- polkadot/node/gum/proc-macro/src/lib.rs | 2 +- substrate/frame/contracts/src/schedule.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/polkadot/node/gum/proc-macro/src/lib.rs b/polkadot/node/gum/proc-macro/src/lib.rs index e8b6b599172d..96ff4417a5a2 100644 --- a/polkadot/node/gum/proc-macro/src/lib.rs +++ b/polkadot/node/gum/proc-macro/src/lib.rs @@ -90,7 +90,7 @@ pub(crate) fn gum(item: proc_macro::TokenStream, level: Level) -> proc_macro::To .add_comment("Generated overseer code by `gum::warn!(..)`".to_owned()) // `dry=true` until rust-analyzer can selectively disable features so it's // not all red squiggles. Originally: `!cfg!(feature = "expand")` - // ISSUE: + // ISSUE: https://github.com/rust-lang/rust-analyzer/issues/11777 .dry(true) .verbose(false) .fmt(expander::Edition::_2021) diff --git a/substrate/frame/contracts/src/schedule.rs b/substrate/frame/contracts/src/schedule.rs index 80b8c54b1e1d..285184280fcb 100644 --- a/substrate/frame/contracts/src/schedule.rs +++ b/substrate/frame/contracts/src/schedule.rs @@ -114,7 +114,7 @@ impl Limits { #[scale_info(skip_type_params(T))] pub struct InstructionWeights { /// Base instruction `ref_time` Weight. - /// Should match to wasmi's `1` fuel (see ). + /// Should match to wasmi's `1` fuel (see ). pub base: u32, /// The type parameter is used in the default implementation. #[codec(skip)] From 9edaef09a69e39b0785f8339f93a3ed6a1f6e023 Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Tue, 21 Jan 2025 18:36:04 +0100 Subject: [PATCH 297/340] Migrate pallet-paged-list-fuzzer to umbrella crate (#6930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of #6504 --------- Co-authored-by: Bastian Köcher Co-authored-by: Giuseppe Re --- Cargo.lock | 3 +-- substrate/frame/paged-list/fuzzer/Cargo.toml | 11 ++++++++--- substrate/frame/paged-list/fuzzer/src/paged_list.rs | 7 +++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 397d0c7fe823..55cc1721bdde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14608,10 +14608,9 @@ name = "pallet-paged-list-fuzzer" version = "0.1.0" dependencies = [ "arbitrary", - "frame-support 28.0.0", "honggfuzz", "pallet-paged-list 0.6.0", - "sp-io 30.0.0", + "polkadot-sdk-frame 0.1.0", ] [[package]] diff --git a/substrate/frame/paged-list/fuzzer/Cargo.toml b/substrate/frame/paged-list/fuzzer/Cargo.toml index 7e6162df09ba..32535093b598 100644 --- a/substrate/frame/paged-list/fuzzer/Cargo.toml +++ b/substrate/frame/paged-list/fuzzer/Cargo.toml @@ -18,8 +18,13 @@ path = "src/paged_list.rs" [dependencies] arbitrary = { workspace = true } +frame = { workspace = true, features = ["runtime"] } honggfuzz = { workspace = true } - -frame-support = { features = ["std"], workspace = true } pallet-paged-list = { features = ["std"], workspace = true } -sp-io = { features = ["std"], workspace = true } + +[features] +default = ["std"] +std = [ + "frame/std", + "pallet-paged-list/std", +] diff --git a/substrate/frame/paged-list/fuzzer/src/paged_list.rs b/substrate/frame/paged-list/fuzzer/src/paged_list.rs index 43b797eee6bf..f0f914de1422 100644 --- a/substrate/frame/paged-list/fuzzer/src/paged_list.rs +++ b/substrate/frame/paged-list/fuzzer/src/paged_list.rs @@ -30,9 +30,12 @@ use arbitrary::Arbitrary; use honggfuzz::fuzz; -use frame_support::{storage::StorageList, StorageNoopGuard}; +use frame::{ + prelude::*, runtime::prelude::storage::storage_noop_guard::StorageNoopGuard, + testing_prelude::TestExternalities, +}; + use pallet_paged_list::mock::{PagedList as List, *}; -use sp_io::TestExternalities; type Meta = MetaOf; fn main() { From 2345eb9a5b5e2145ac1c04fd9cf1fcf12b7278b6 Mon Sep 17 00:00:00 2001 From: Javier Viola <363911+pepoviola@users.noreply.github.com> Date: Tue, 21 Jan 2025 18:24:05 -0300 Subject: [PATCH 298/340] Bump zombienet version to `v1.3.119` (#7283) This version include a fix that make test `zombienet-polkadot-malus-0001-dispute-valid` green again. Thx! --- .gitlab/pipeline/zombienet.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab/pipeline/zombienet.yml b/.gitlab/pipeline/zombienet.yml index 08bfed2e24ce..c48bca8af48b 100644 --- a/.gitlab/pipeline/zombienet.yml +++ b/.gitlab/pipeline/zombienet.yml @@ -1,7 +1,7 @@ .zombienet-refs: extends: .build-refs variables: - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.116" + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.119" PUSHGATEWAY_URL: "http://zombienet-prometheus-pushgateway.managed-monitoring:9091/metrics/job/zombie-metrics" DEBUG: "zombie,zombie::network-node,zombie::kube::client::logs" ZOMBIE_PROVIDER: "k8s" From 1bdb817f2b140b0c2573396146fd7bbfb936af10 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Wed, 22 Jan 2025 12:01:28 +0200 Subject: [PATCH 299/340] Enable BEEFY `report_fork_voting()` (#6856) Related to https://github.com/paritytech/polkadot-sdk/issues/4523 Follow-up for: https://github.com/paritytech/polkadot-sdk/pull/5188 Reopening https://github.com/paritytech/polkadot-sdk/pull/6732 as a new PR --------- Co-authored-by: command-bot <> --- Cargo.lock | 14 +++- .../rococo/src/weights/pallet_beefy_mmr.rs | 37 ++++++--- .../westend/src/weights/pallet_beefy_mmr.rs | 37 ++++++--- prdoc/pr_6856.prdoc | 28 +++++++ substrate/frame/beefy-mmr/src/benchmarking.rs | 18 +++++ substrate/frame/beefy-mmr/src/lib.rs | 16 ++++ substrate/frame/beefy-mmr/src/weights.rs | 81 +++++++++++++------ substrate/frame/beefy/src/equivocation.rs | 8 +- substrate/frame/beefy/src/lib.rs | 10 +-- substrate/frame/beefy/src/mock.rs | 9 +++ substrate/frame/beefy/src/tests.rs | 56 ++++++++++++- .../frame/merkle-mountain-range/src/lib.rs | 6 ++ .../merkle-mountain-range/src/mmr/mmr.rs | 18 ++++- .../merkle-mountain-range/src/mmr/mod.rs | 2 +- .../frame/merkle-mountain-range/src/tests.rs | 1 + .../primitives/consensus/beefy/src/lib.rs | 6 ++ .../merkle-mountain-range/Cargo.toml | 2 +- 17 files changed, 278 insertions(+), 71 deletions(-) create mode 100644 prdoc/pr_6856.prdoc diff --git a/Cargo.lock b/Cargo.lock index 55cc1721bdde..7e41b7e99374 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17263,6 +17263,16 @@ dependencies = [ "itertools 0.10.5", ] +[[package]] +name = "polkadot-ckb-merkle-mountain-range" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "221c71b432b38e494a0fdedb5f720e4cb974edf03a0af09e5b2238dbac7e6947" +dependencies = [ + "cfg-if", + "itertools 0.10.5", +] + [[package]] name = "polkadot-cli" version = "7.0.0" @@ -26958,7 +26968,7 @@ dependencies = [ "array-bytes", "log", "parity-scale-codec", - "polkadot-ckb-merkle-mountain-range", + "polkadot-ckb-merkle-mountain-range 0.8.1", "scale-info", "serde", "sp-api 26.0.0", @@ -26976,7 +26986,7 @@ checksum = "9a12dd76e368f1e48144a84b4735218b712f84b3f976970e2f25a29b30440e10" dependencies = [ "log", "parity-scale-codec", - "polkadot-ckb-merkle-mountain-range", + "polkadot-ckb-merkle-mountain-range 0.7.0", "scale-info", "serde", "sp-api 34.0.0", diff --git a/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs b/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs index 317c9149ec6c..54989c4f549c 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_beefy_mmr.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_beefy_mmr` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: @@ -48,14 +48,25 @@ use core::marker::PhantomData; /// Weight functions for `pallet_beefy_mmr`. pub struct WeightInfo(PhantomData); impl pallet_beefy_mmr::WeightInfo for WeightInfo { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 622_000 picoseconds. + Weight::from_parts(1_166_954, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_356, 0).saturating_mul(n.into())) + } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn extract_validation_context() -> Weight { // Proof Size summary in bytes: - // Measured: `92` + // Measured: `68` // Estimated: `3509` - // Minimum execution time: 7_116_000 picoseconds. - Weight::from_parts(7_343_000, 0) + // Minimum execution time: 6_272_000 picoseconds. + Weight::from_parts(6_452_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -63,10 +74,10 @@ impl pallet_beefy_mmr::WeightInfo for WeightInfo { /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn read_peak() -> Weight { // Proof Size summary in bytes: - // Measured: `234` + // Measured: `254` // Estimated: `3505` - // Minimum execution time: 5_652_000 picoseconds. - Weight::from_parts(5_963_000, 0) + // Minimum execution time: 6_576_000 picoseconds. + Weight::from_parts(6_760_000, 0) .saturating_add(Weight::from_parts(0, 3505)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -77,13 +88,13 @@ impl pallet_beefy_mmr::WeightInfo for WeightInfo { /// The range of component `n` is `[2, 512]`. fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `226` + // Measured: `246` // Estimated: `1517` - // Minimum execution time: 11_953_000 picoseconds. - Weight::from_parts(15_978_891, 0) + // Minimum execution time: 12_538_000 picoseconds. + Weight::from_parts(24_516_023, 0) .saturating_add(Weight::from_parts(0, 1517)) - // Standard Error: 1_780 - .saturating_add(Weight::from_parts(1_480_582, 0).saturating_mul(n.into())) + // Standard Error: 1_923 + .saturating_add(Weight::from_parts(1_426_781, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2)) } } diff --git a/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs b/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs index 5be207e3fcff..8de9f6ab53e6 100644 --- a/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs +++ b/polkadot/runtime/westend/src/weights/pallet_beefy_mmr.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_beefy_mmr` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2024-12-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-696hpswk-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -48,14 +48,25 @@ use core::marker::PhantomData; /// Weight functions for `pallet_beefy_mmr`. pub struct WeightInfo(PhantomData); impl pallet_beefy_mmr::WeightInfo for WeightInfo { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 628_000 picoseconds. + Weight::from_parts(1_200_102, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 63 + .saturating_add(Weight::from_parts(1_110, 0).saturating_mul(n.into())) + } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn extract_validation_context() -> Weight { // Proof Size summary in bytes: - // Measured: `92` + // Measured: `68` // Estimated: `3509` - // Minimum execution time: 7_850_000 picoseconds. - Weight::from_parts(8_169_000, 0) + // Minimum execution time: 9_862_000 picoseconds. + Weight::from_parts(10_329_000, 0) .saturating_add(Weight::from_parts(0, 3509)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -63,10 +74,10 @@ impl pallet_beefy_mmr::WeightInfo for WeightInfo { /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn read_peak() -> Weight { // Proof Size summary in bytes: - // Measured: `201` + // Measured: `221` // Estimated: `3505` - // Minimum execution time: 6_852_000 picoseconds. - Weight::from_parts(7_448_000, 0) + // Minimum execution time: 6_396_000 picoseconds. + Weight::from_parts(6_691_000, 0) .saturating_add(Weight::from_parts(0, 3505)) .saturating_add(T::DbWeight::get().reads(1)) } @@ -77,13 +88,13 @@ impl pallet_beefy_mmr::WeightInfo for WeightInfo { /// The range of component `n` is `[2, 512]`. fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `193` + // Measured: `213` // Estimated: `1517` - // Minimum execution time: 12_860_000 picoseconds. - Weight::from_parts(17_158_162, 0) + // Minimum execution time: 12_553_000 picoseconds. + Weight::from_parts(24_003_920, 0) .saturating_add(Weight::from_parts(0, 1517)) - // Standard Error: 1_732 - .saturating_add(Weight::from_parts(1_489_410, 0).saturating_mul(n.into())) + // Standard Error: 2_023 + .saturating_add(Weight::from_parts(1_390_986, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(2)) } } diff --git a/prdoc/pr_6856.prdoc b/prdoc/pr_6856.prdoc new file mode 100644 index 000000000000..480c3acea195 --- /dev/null +++ b/prdoc/pr_6856.prdoc @@ -0,0 +1,28 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Enable report_fork_voting() + +doc: + - audience: + - Runtime Dev + - Runtime User + description: | + This PR enables calling `report_fork_voting`. + In order to do this we needed to also check that the ancestry proof is optimal. + +crates: + - name: pallet-mmr + bump: minor + - name: sp-mmr-primitives + bump: minor + - name: sp-consensus-beefy + bump: minor + - name: rococo-runtime + bump: minor + - name: pallet-beefy + bump: minor + - name: pallet-beefy-mmr + bump: minor + - name: westend-runtime + bump: minor diff --git a/substrate/frame/beefy-mmr/src/benchmarking.rs b/substrate/frame/beefy-mmr/src/benchmarking.rs index fea6a2078f0f..4fddd1bccf11 100644 --- a/substrate/frame/beefy-mmr/src/benchmarking.rs +++ b/substrate/frame/beefy-mmr/src/benchmarking.rs @@ -49,6 +49,24 @@ fn init_block(block_num: u32) { mod benchmarks { use super::*; + /// Generate ancestry proofs with `n` leafs and benchmark the logic that checks + /// if the proof is optimal. + #[benchmark] + fn n_leafs_proof_is_optimal(n: Linear<2, 512>) { + pallet_mmr::UseLocalStorage::::set(true); + + for block_num in 1..=n { + init_block::(block_num); + } + let proof = Mmr::::generate_mock_ancestry_proof().unwrap(); + assert_eq!(proof.leaf_count, n as u64); + + #[block] + { + as AncestryHelper>>::is_proof_optimal(&proof); + }; + } + #[benchmark] fn extract_validation_context() { pallet_mmr::UseLocalStorage::::set(true); diff --git a/substrate/frame/beefy-mmr/src/lib.rs b/substrate/frame/beefy-mmr/src/lib.rs index ef99bc1e9cf1..c7fcdeff8799 100644 --- a/substrate/frame/beefy-mmr/src/lib.rs +++ b/substrate/frame/beefy-mmr/src/lib.rs @@ -210,6 +210,18 @@ where .ok() } + fn is_proof_optimal(proof: &Self::Proof) -> bool { + let is_proof_optimal = pallet_mmr::Pallet::::is_ancestry_proof_optimal(proof); + + // We don't check the proof size when running benchmarks, since we use mock proofs + // which would cause the test to fail. + if cfg!(feature = "runtime-benchmarks") { + return true + } + + is_proof_optimal + } + fn extract_validation_context(header: HeaderFor) -> Option { // Check if the provided header is canonical. let expected_hash = frame_system::Pallet::::block_hash(header.number()); @@ -292,6 +304,10 @@ impl AncestryHelperWeightInfo> for Pallet where T: pallet_mmr::Config, { + fn is_proof_optimal(proof: &>>::Proof) -> Weight { + ::WeightInfo::n_leafs_proof_is_optimal(proof.leaf_count.saturated_into()) + } + fn extract_validation_context() -> Weight { ::WeightInfo::extract_validation_context() } diff --git a/substrate/frame/beefy-mmr/src/weights.rs b/substrate/frame/beefy-mmr/src/weights.rs index dcfdb560ee94..5f7f7055311c 100644 --- a/substrate/frame/beefy-mmr/src/weights.rs +++ b/substrate/frame/beefy-mmr/src/weights.rs @@ -51,6 +51,7 @@ use core::marker::PhantomData; /// Weight functions needed for `pallet_beefy_mmr`. pub trait WeightInfo { + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight; fn extract_validation_context() -> Weight; fn read_peak() -> Weight; fn n_items_proof_is_non_canonical(n: u32, ) -> Weight; @@ -59,25 +60,38 @@ pub trait WeightInfo { /// Weights for `pallet_beefy_mmr` using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 622_000 picoseconds. + Weight::from_parts(1_166_954, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_356, 0).saturating_mul(n.into())) + } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn extract_validation_context() -> Weight { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3509` - // Minimum execution time: 6_687_000 picoseconds. - Weight::from_parts(6_939_000, 3509) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 6_272_000 picoseconds. + Weight::from_parts(6_452_000, 0) + .saturating_add(Weight::from_parts(0, 3509)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `Mmr::Nodes` (r:1 w:0) /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn read_peak() -> Weight { // Proof Size summary in bytes: - // Measured: `386` + // Measured: `254` // Estimated: `3505` - // Minimum execution time: 10_409_000 picoseconds. - Weight::from_parts(10_795_000, 3505) - .saturating_add(T::DbWeight::get().reads(1_u64)) + // Minimum execution time: 6_576_000 picoseconds. + Weight::from_parts(6_760_000, 0) + .saturating_add(Weight::from_parts(0, 3505)) + .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `Mmr::RootHash` (r:1 w:0) /// Proof: `Mmr::RootHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) @@ -86,37 +100,51 @@ impl WeightInfo for SubstrateWeight { /// The range of component `n` is `[2, 512]`. fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `378` + // Measured: `246` // Estimated: `1517` - // Minimum execution time: 15_459_000 picoseconds. - Weight::from_parts(21_963_366, 1517) - // Standard Error: 1_528 - .saturating_add(Weight::from_parts(984_907, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Minimum execution time: 12_538_000 picoseconds. + Weight::from_parts(24_516_023, 0) + .saturating_add(Weight::from_parts(0, 1517)) + // Standard Error: 1_923 + .saturating_add(Weight::from_parts(1_426_781, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2)) } } // For backwards compatibility and tests. impl WeightInfo for () { + /// The range of component `n` is `[2, 512]`. + fn n_leafs_proof_is_optimal(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 622_000 picoseconds. + Weight::from_parts(1_166_954, 0) + .saturating_add(Weight::from_parts(0, 0)) + // Standard Error: 65 + .saturating_add(Weight::from_parts(1_356, 0).saturating_mul(n.into())) + } /// Storage: `System::BlockHash` (r:1 w:0) /// Proof: `System::BlockHash` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn extract_validation_context() -> Weight { // Proof Size summary in bytes: // Measured: `68` // Estimated: `3509` - // Minimum execution time: 6_687_000 picoseconds. - Weight::from_parts(6_939_000, 3509) - .saturating_add(RocksDbWeight::get().reads(1_u64)) + // Minimum execution time: 6_272_000 picoseconds. + Weight::from_parts(6_452_000, 0) + .saturating_add(Weight::from_parts(0, 3509)) + .saturating_add(RocksDbWeight::get().reads(1)) } /// Storage: `Mmr::Nodes` (r:1 w:0) /// Proof: `Mmr::Nodes` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) fn read_peak() -> Weight { // Proof Size summary in bytes: - // Measured: `386` + // Measured: `254` // Estimated: `3505` - // Minimum execution time: 10_409_000 picoseconds. - Weight::from_parts(10_795_000, 3505) - .saturating_add(RocksDbWeight::get().reads(1_u64)) + // Minimum execution time: 6_576_000 picoseconds. + Weight::from_parts(6_760_000, 0) + .saturating_add(Weight::from_parts(0, 3505)) + .saturating_add(RocksDbWeight::get().reads(1)) } /// Storage: `Mmr::RootHash` (r:1 w:0) /// Proof: `Mmr::RootHash` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`) @@ -125,12 +153,13 @@ impl WeightInfo for () { /// The range of component `n` is `[2, 512]`. fn n_items_proof_is_non_canonical(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `378` + // Measured: `246` // Estimated: `1517` - // Minimum execution time: 15_459_000 picoseconds. - Weight::from_parts(21_963_366, 1517) - // Standard Error: 1_528 - .saturating_add(Weight::from_parts(984_907, 0).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + // Minimum execution time: 12_538_000 picoseconds. + Weight::from_parts(24_516_023, 0) + .saturating_add(Weight::from_parts(0, 1517)) + // Standard Error: 1_923 + .saturating_add(Weight::from_parts(1_426_781, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2)) } } diff --git a/substrate/frame/beefy/src/equivocation.rs b/substrate/frame/beefy/src/equivocation.rs index 3a49b9e169ce..294d64427ef8 100644 --- a/substrate/frame/beefy/src/equivocation.rs +++ b/substrate/frame/beefy/src/equivocation.rs @@ -207,11 +207,17 @@ impl EquivocationEvidenceFor { return Err(Error::::InvalidDoubleVotingProof); } - return Ok(()) + Ok(()) }, EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) => { let ForkVotingProof { vote, ancestry_proof, header } = equivocation_proof; + if !>>::is_proof_optimal( + &ancestry_proof, + ) { + return Err(Error::::InvalidForkVotingProof); + } + let maybe_validation_context = , >>::extract_validation_context(header); diff --git a/substrate/frame/beefy/src/lib.rs b/substrate/frame/beefy/src/lib.rs index cf690a9df339..e57fc0e21bc1 100644 --- a/substrate/frame/beefy/src/lib.rs +++ b/substrate/frame/beefy/src/lib.rs @@ -755,7 +755,8 @@ pub(crate) trait WeightInfoExt: WeightInfo { max_nominators_per_validator: u32, ancestry_proof: &>>::Proof, ) -> Weight { - let _weight = >>::extract_validation_context() + >>::is_proof_optimal(&ancestry_proof) + .saturating_add(>>::extract_validation_context()) .saturating_add( >>::is_non_canonical( ancestry_proof, @@ -765,12 +766,7 @@ pub(crate) trait WeightInfoExt: WeightInfo { 1, validator_count, max_nominators_per_validator, - )); - - // TODO: https://github.com/paritytech/polkadot-sdk/issues/4523 - return `_weight` here. - // We return `Weight::MAX` currently in order to disallow this extrinsic for the moment. - // We need to check that the proof is optimal. - Weight::MAX + )) } fn report_future_block_voting( diff --git a/substrate/frame/beefy/src/mock.rs b/substrate/frame/beefy/src/mock.rs index 38e0cc4cfc26..4b5f1d103b50 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -99,6 +99,7 @@ pub struct MockAncestryProofContext { #[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)] pub struct MockAncestryProof { + pub is_optimal: bool, pub is_non_canonical: bool, } @@ -128,6 +129,10 @@ impl AncestryHelper
for MockAncestryHelper { unimplemented!() } + fn is_proof_optimal(proof: &Self::Proof) -> bool { + proof.is_optimal + } + fn extract_validation_context(_header: Header) -> Option { AncestryProofContext::get() } @@ -142,6 +147,10 @@ impl AncestryHelper
for MockAncestryHelper { } impl AncestryHelperWeightInfo
for MockAncestryHelper { + fn is_proof_optimal(_proof: &>>::Proof) -> Weight { + unimplemented!() + } + fn extract_validation_context() -> Weight { unimplemented!() } diff --git a/substrate/frame/beefy/src/tests.rs b/substrate/frame/beefy/src/tests.rs index 89645d21f6ba..1bd0a72b25ec 100644 --- a/substrate/frame/beefy/src/tests.rs +++ b/substrate/frame/beefy/src/tests.rs @@ -799,7 +799,7 @@ fn report_fork_voting( let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); let equivocation_proof = generate_fork_voting_proof( (block_num, payload, set_id, &equivocation_keyring), - MockAncestryProof { is_non_canonical: true }, + MockAncestryProof { is_optimal: true, is_non_canonical: true }, System::finalize(), ); @@ -835,6 +835,54 @@ fn report_fork_voting_invalid_key_owner_proof() { report_equivocation_invalid_key_owner_proof(report_fork_voting); } +#[test] +fn report_fork_voting_non_optimal_equivocation_proof() { + let authorities = test_authorities(); + + let mut ext = ExtBuilder::default().add_authorities(authorities).build(); + + let mut era = 1; + let (block_num, set_id, equivocation_keyring, key_owner_proof) = ext.execute_with(|| { + start_era(era); + let block_num = System::block_number(); + + let validator_set = Beefy::validator_set().unwrap(); + let authorities = validator_set.validators(); + let set_id = validator_set.id(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index]; + let equivocation_keyring = BeefyKeyring::from_public(equivocation_key).unwrap(); + + // generate a key ownership proof at set id in era 1 + let key_owner_proof = Historical::prove((BEEFY_KEY_TYPE, &equivocation_key)).unwrap(); + + era += 1; + start_era(era); + (block_num, set_id, equivocation_keyring, key_owner_proof) + }); + ext.persist_offchain_overlay(); + + ext.execute_with(|| { + let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + + // Simulate non optimal equivocation proof. + let equivocation_proof = generate_fork_voting_proof( + (block_num + 1, payload.clone(), set_id, &equivocation_keyring), + MockAncestryProof { is_optimal: false, is_non_canonical: true }, + System::finalize(), + ); + assert_err!( + Beefy::report_fork_voting_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + key_owner_proof.clone(), + ), + Error::::InvalidForkVotingProof, + ); + }); +} + #[test] fn report_fork_voting_invalid_equivocation_proof() { let authorities = test_authorities(); @@ -869,7 +917,7 @@ fn report_fork_voting_invalid_equivocation_proof() { // vote signed with a key that isn't part of the authority set let equivocation_proof = generate_fork_voting_proof( (block_num, payload.clone(), set_id, &BeefyKeyring::Dave), - MockAncestryProof { is_non_canonical: true }, + MockAncestryProof { is_optimal: true, is_non_canonical: true }, System::finalize(), ); assert_err!( @@ -884,7 +932,7 @@ fn report_fork_voting_invalid_equivocation_proof() { // Simulate InvalidForkVotingProof error. let equivocation_proof = generate_fork_voting_proof( (block_num + 1, payload.clone(), set_id, &equivocation_keyring), - MockAncestryProof { is_non_canonical: false }, + MockAncestryProof { is_optimal: true, is_non_canonical: false }, System::finalize(), ); assert_err!( @@ -945,7 +993,7 @@ fn report_fork_voting_invalid_context() { // different payload than finalized let equivocation_proof = generate_fork_voting_proof( (block_num, payload, set_id, &equivocation_keyring), - MockAncestryProof { is_non_canonical: true }, + MockAncestryProof { is_optimal: true, is_non_canonical: true }, System::finalize(), ); diff --git a/substrate/frame/merkle-mountain-range/src/lib.rs b/substrate/frame/merkle-mountain-range/src/lib.rs index 76d6c2a1ac76..cc64dfcb7de8 100644 --- a/substrate/frame/merkle-mountain-range/src/lib.rs +++ b/substrate/frame/merkle-mountain-range/src/lib.rs @@ -445,6 +445,12 @@ impl, I: 'static> Pallet { mmr.generate_mock_ancestry_proof() } + pub fn is_ancestry_proof_optimal( + ancestry_proof: &primitives::AncestryProof>, + ) -> bool { + mmr::is_ancestry_proof_optimal::>(ancestry_proof) + } + pub fn verify_ancestry_proof( root: HashOf, ancestry_proof: AncestryProof>, diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs index a9818ba47101..69a08a8b2d6a 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mmr.rs @@ -63,6 +63,18 @@ where .map_err(|e| Error::Verify.log_debug(e)) } +pub fn is_ancestry_proof_optimal(ancestry_proof: &AncestryProof) -> bool +where + H: frame::traits::Hash, +{ + let prev_mmr_size = NodesUtils::new(ancestry_proof.prev_leaf_count).size(); + let mmr_size = NodesUtils::new(ancestry_proof.leaf_count).size(); + + let expected_proof_size = + mmr_lib::ancestry_proof::expected_ancestry_proof_size(prev_mmr_size, mmr_size); + ancestry_proof.items.len() == expected_proof_size +} + pub fn verify_ancestry_proof( root: H::Output, ancestry_proof: AncestryProof, @@ -83,9 +95,9 @@ where ); let raw_ancestry_proof = mmr_lib::AncestryProof::, Hasher> { + prev_mmr_size: mmr_lib::helper::leaf_index_to_mmr_size(ancestry_proof.prev_leaf_count - 1), prev_peaks: ancestry_proof.prev_peaks.into_iter().map(|hash| Node::Hash(hash)).collect(), - prev_size: mmr_lib::helper::leaf_index_to_mmr_size(ancestry_proof.prev_leaf_count - 1), - proof: prev_peaks_proof, + prev_peaks_proof, }; let prev_root = mmr_lib::ancestry_proof::bagging_peaks_hashes::, Hasher>( @@ -248,7 +260,7 @@ where prev_leaf_count, leaf_count: self.leaves, items: raw_ancestry_proof - .proof + .prev_peaks_proof .proof_items() .iter() .map(|(index, item)| (*index, item.hash())) diff --git a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs index 85d00f8a65de..d3232f23bce1 100644 --- a/substrate/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/substrate/frame/merkle-mountain-range/src/mmr/mod.rs @@ -18,7 +18,7 @@ mod mmr; pub mod storage; -pub use self::mmr::{verify_ancestry_proof, verify_leaves_proof, Mmr}; +pub use self::mmr::{is_ancestry_proof_optimal, verify_ancestry_proof, verify_leaves_proof, Mmr}; use crate::primitives::{mmr_lib, DataOrHash, FullLeaf}; use frame::traits; diff --git a/substrate/frame/merkle-mountain-range/src/tests.rs b/substrate/frame/merkle-mountain-range/src/tests.rs index ae0c58e91aba..03b08e51c32a 100644 --- a/substrate/frame/merkle-mountain-range/src/tests.rs +++ b/substrate/frame/merkle-mountain-range/src/tests.rs @@ -811,6 +811,7 @@ fn generating_and_verifying_ancestry_proofs_works_correctly() { for prev_block_number in 1usize..=500 { let proof = Pallet::::generate_ancestry_proof(prev_block_number as u64, None).unwrap(); + assert!(Pallet::::is_ancestry_proof_optimal(&proof)); assert_eq!( Pallet::::verify_ancestry_proof(root, proof), Ok(prev_roots[prev_block_number - 1]) diff --git a/substrate/primitives/consensus/beefy/src/lib.rs b/substrate/primitives/consensus/beefy/src/lib.rs index e977fb0ea25f..0f57cdfc8104 100644 --- a/substrate/primitives/consensus/beefy/src/lib.rs +++ b/substrate/primitives/consensus/beefy/src/lib.rs @@ -449,6 +449,9 @@ pub trait AncestryHelper { best_known_block_number: Option, ) -> Option; + /// Check if the proof is optimal. + fn is_proof_optimal(proof: &Self::Proof) -> bool; + /// Extract the validation context from the provided header. fn extract_validation_context(header: Header) -> Option; @@ -463,6 +466,9 @@ pub trait AncestryHelper { /// Weight information for the logic in `AncestryHelper`. pub trait AncestryHelperWeightInfo: AncestryHelper
{ + /// Weight info for the `AncestryHelper::is_proof_optimal()` method. + fn is_proof_optimal(proof: &>::Proof) -> Weight; + /// Weight info for the `AncestryHelper::extract_validation_context()` method. fn extract_validation_context() -> Weight; diff --git a/substrate/primitives/merkle-mountain-range/Cargo.toml b/substrate/primitives/merkle-mountain-range/Cargo.toml index 5f861ca7acf1..0d8a67da7cad 100644 --- a/substrate/primitives/merkle-mountain-range/Cargo.toml +++ b/substrate/primitives/merkle-mountain-range/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { workspace = true } log = { workspace = true } -mmr-lib = { package = "polkadot-ckb-merkle-mountain-range", version = "0.7.0", default-features = false } +mmr-lib = { package = "polkadot-ckb-merkle-mountain-range", version = "0.8.1", default-features = false } scale-info = { features = ["derive"], workspace = true } serde = { features = ["alloc", "derive"], optional = true, workspace = true } sp-api = { workspace = true } From 4eb9228840be0abef1c45cf8fa8bc44b5f95200a Mon Sep 17 00:00:00 2001 From: Stephane Gurgenidze <59443568+sw10pa@users.noreply.github.com> Date: Wed, 22 Jan 2025 15:00:50 +0400 Subject: [PATCH 300/340] collation-generation: resolve mismatch between descriptor and commitments core index (#7104) ## Issue [[#7107] Core Index Mismatch in Commitments and Descriptor](https://github.com/paritytech/polkadot-sdk/issues/7107) ## Description This PR resolves a bug where normal (non-malus) undying collators failed to generate and submit collations, resulting in the following error: `ERROR tokio-runtime-worker parachain::collation-generation: Failed to construct and distribute collation: V2 core index check failed: The core index in commitments doesn't match the one in descriptor.` More details about the issue and reproduction steps are described in the [related issue](https://github.com/paritytech/polkadot-sdk/issues/7107). ## Summary of Fix - When core selectors are provided in the UMP signals, core indexes will be chosen using them; - The fix ensures that functionality remains unchanged for parachains not using UMP signals; - Added checks to stop processing if the same core is selected repeatedly. ## TODO - [X] Implement the fix; - [x] Add tests; - [x] Add PRdoc. --- polkadot/node/collation-generation/src/lib.rs | 88 ++++++- .../node/collation-generation/src/tests.rs | 216 ++++++++++++++++-- prdoc/pr_7104.prdoc | 23 ++ 3 files changed, 299 insertions(+), 28 deletions(-) create mode 100644 prdoc/pr_7104.prdoc diff --git a/polkadot/node/collation-generation/src/lib.rs b/polkadot/node/collation-generation/src/lib.rs index b371017a8289..3c8a216f5f35 100644 --- a/polkadot/node/collation-generation/src/lib.rs +++ b/polkadot/node/collation-generation/src/lib.rs @@ -53,7 +53,7 @@ use polkadot_primitives::{ node_features::FeatureIndex, vstaging::{ transpose_claim_queue, CandidateDescriptorV2, CandidateReceiptV2 as CandidateReceipt, - CommittedCandidateReceiptV2, TransposedClaimQueue, + ClaimQueueOffset, CommittedCandidateReceiptV2, TransposedClaimQueue, }, CandidateCommitments, CandidateDescriptor, CollatorPair, CoreIndex, Hash, Id as ParaId, NodeFeatures, OccupiedCoreAssumption, PersistedValidationData, SessionIndex, @@ -61,7 +61,7 @@ use polkadot_primitives::{ }; use schnellru::{ByLength, LruMap}; use sp_core::crypto::Pair; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; mod error; @@ -276,13 +276,15 @@ impl CollationGenerationSubsystem { let claim_queue = ClaimQueueSnapshot::from(request_claim_queue(relay_parent, ctx.sender()).await.await??); - let cores_to_build_on = claim_queue - .iter_claims_at_depth(0) - .filter_map(|(core_idx, para_id)| (para_id == config.para_id).then_some(core_idx)) + let assigned_cores = claim_queue + .iter_all_claims() + .filter_map(|(core_idx, para_ids)| { + para_ids.iter().any(|¶_id| para_id == config.para_id).then_some(*core_idx) + }) .collect::>(); - // Nothing to do if no core assigned to us. - if cores_to_build_on.is_empty() { + // Nothing to do if no core is assigned to us at any depth. + if assigned_cores.is_empty() { return Ok(()) } @@ -342,9 +344,13 @@ impl CollationGenerationSubsystem { ctx.spawn( "chained-collation-builder", Box::pin(async move { - let transposed_claim_queue = transpose_claim_queue(claim_queue.0); + let transposed_claim_queue = transpose_claim_queue(claim_queue.0.clone()); - for core_index in cores_to_build_on { + // Track used core indexes not to submit collations on the same core. + let mut used_cores = HashSet::new(); + + for i in 0..assigned_cores.len() { + // Get the collation. let collator_fn = match task_config.collator.as_ref() { Some(x) => x, None => return, @@ -363,6 +369,68 @@ impl CollationGenerationSubsystem { }, }; + // Use the core_selector method from CandidateCommitments to extract + // CoreSelector and ClaimQueueOffset. + let mut commitments = CandidateCommitments::default(); + commitments.upward_messages = collation.upward_messages.clone(); + + let (cs_index, cq_offset) = match commitments.core_selector() { + // Use the CoreSelector's index if provided. + Ok(Some((sel, off))) => (sel.0 as usize, off), + // Fallback to the sequential index if no CoreSelector is provided. + Ok(None) => (i, ClaimQueueOffset(0)), + Err(err) => { + gum::debug!( + target: LOG_TARGET, + ?para_id, + "error processing UMP signals: {}", + err + ); + return + }, + }; + + // Identify the cores to build collations on using the given claim queue offset. + let cores_to_build_on = claim_queue + .iter_claims_at_depth(cq_offset.0 as usize) + .filter_map(|(core_idx, para_id)| { + (para_id == task_config.para_id).then_some(core_idx) + }) + .collect::>(); + + if cores_to_build_on.is_empty() { + gum::debug!( + target: LOG_TARGET, + ?para_id, + "no core is assigned to para at depth {}", + cq_offset.0, + ); + return + } + + let descriptor_core_index = + cores_to_build_on[cs_index % cores_to_build_on.len()]; + + // Ensure the core index has not been used before. + if used_cores.contains(&descriptor_core_index.0) { + gum::warn!( + target: LOG_TARGET, + ?para_id, + "parachain repeatedly selected the same core index: {}", + descriptor_core_index.0, + ); + return + } + + used_cores.insert(descriptor_core_index.0); + gum::trace!( + target: LOG_TARGET, + ?para_id, + "selected core index: {}", + descriptor_core_index.0, + ); + + // Distribute the collation. let parent_head = collation.head_data.clone(); if let Err(err) = construct_and_distribute_receipt( PreparedCollation { @@ -372,7 +440,7 @@ impl CollationGenerationSubsystem { validation_data: validation_data.clone(), validation_code_hash, n_validators, - core_index, + core_index: descriptor_core_index, session_index, }, task_config.key.clone(), diff --git a/polkadot/node/collation-generation/src/tests.rs b/polkadot/node/collation-generation/src/tests.rs index f81c14cdf8f9..dc1d7b3489c1 100644 --- a/polkadot/node/collation-generation/src/tests.rs +++ b/polkadot/node/collation-generation/src/tests.rs @@ -16,11 +16,10 @@ use super::*; use assert_matches::assert_matches; -use futures::{ - task::{Context as FuturesContext, Poll}, - Future, StreamExt, +use futures::{self, Future, StreamExt}; +use polkadot_node_primitives::{ + BlockData, Collation, CollationResult, CollatorFn, MaybeCompressedPoV, PoV, }; -use polkadot_node_primitives::{BlockData, Collation, CollationResult, MaybeCompressedPoV, PoV}; use polkadot_node_subsystem::{ messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest}, ActivatedLeaf, @@ -28,14 +27,16 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_test_helpers::TestSubsystemContextHandle; use polkadot_node_subsystem_util::TimeoutExt; use polkadot_primitives::{ - node_features, vstaging::CandidateDescriptorVersion, CollatorPair, PersistedValidationData, + node_features, + vstaging::{CandidateDescriptorVersion, CoreSelector, UMPSignal, UMP_SEPARATOR}, + CollatorPair, PersistedValidationData, }; use polkadot_primitives_test_helpers::dummy_head_data; use rstest::rstest; use sp_keyring::sr25519::Keyring as Sr25519Keyring; use std::{ collections::{BTreeMap, VecDeque}, - pin::Pin, + sync::Mutex, }; type VirtualOverseer = TestSubsystemContextHandle; @@ -79,17 +80,64 @@ fn test_collation() -> Collation { } } -struct TestCollator; +struct CoreSelectorData { + // The core selector index. + index: u8, + // The increment value for the core selector index. Normally 1, but can be set to 0 or another + // value for testing scenarios where a parachain repeatedly selects the same core index. + increment_index_by: u8, + // The claim queue offset. + cq_offset: u8, +} + +impl CoreSelectorData { + fn new(index: u8, increment_index_by: u8, cq_offset: u8) -> Self { + Self { index, increment_index_by, cq_offset } + } +} -impl Future for TestCollator { - type Output = Option; +struct State { + core_selector_data: Option, +} - fn poll(self: Pin<&mut Self>, _cx: &mut FuturesContext) -> Poll { - Poll::Ready(Some(CollationResult { collation: test_collation(), result_sender: None })) +impl State { + fn new(core_selector_data: Option) -> Self { + Self { core_selector_data } } } -impl Unpin for TestCollator {} +struct TestCollator { + state: Arc>, +} + +impl TestCollator { + fn new(core_selector_data: Option) -> Self { + Self { state: Arc::new(Mutex::new(State::new(core_selector_data))) } + } + + pub fn create_collation_function(&self) -> CollatorFn { + let state = Arc::clone(&self.state); + + Box::new(move |_relay_parent: Hash, _validation_data: &PersistedValidationData| { + let mut collation = test_collation(); + let mut state_guard = state.lock().unwrap(); + + if let Some(core_selector_data) = &mut state_guard.core_selector_data { + collation.upward_messages.force_push(UMP_SEPARATOR); + collation.upward_messages.force_push( + UMPSignal::SelectCore( + CoreSelector(core_selector_data.index), + ClaimQueueOffset(core_selector_data.cq_offset), + ) + .encode(), + ); + core_selector_data.index += core_selector_data.increment_index_by; + } + + async move { Some(CollationResult { collation, result_sender: None }) }.boxed() + }) + } +} const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(2000); @@ -101,10 +149,14 @@ async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages { .expect(&format!("{:?} is long enough to receive messages", TIMEOUT)) } -fn test_config>(para_id: Id) -> CollationGenerationConfig { +fn test_config>( + para_id: Id, + core_selector_data: Option, +) -> CollationGenerationConfig { + let test_collator = TestCollator::new(core_selector_data); CollationGenerationConfig { key: CollatorPair::generate().0, - collator: Some(Box::new(|_: Hash, _vd: &PersistedValidationData| TestCollator.boxed())), + collator: Some(test_collator.create_collation_function()), para_id: para_id.into(), } } @@ -219,7 +271,7 @@ fn distribute_collation_only_for_assigned_para_id_at_offset_0() { .collect::>(); test_harness(|mut virtual_overseer| async move { - helpers::initialize_collator(&mut virtual_overseer, para_id).await; + helpers::initialize_collator(&mut virtual_overseer, para_id, None).await; helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; helpers::handle_runtime_calls_on_new_head_activation( &mut virtual_overseer, @@ -259,7 +311,7 @@ fn distribute_collation_with_elastic_scaling(#[case] total_cores: u32) { .collect::>(); test_harness(|mut virtual_overseer| async move { - helpers::initialize_collator(&mut virtual_overseer, para_id).await; + helpers::initialize_collator(&mut virtual_overseer, para_id, None).await; helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; helpers::handle_runtime_calls_on_new_head_activation( &mut virtual_overseer, @@ -281,6 +333,127 @@ fn distribute_collation_with_elastic_scaling(#[case] total_cores: u32) { }); } +// Tests when submission core indexes need to be selected using the core selectors provided in the +// UMP signals. The core selector index is an increasing number that can start with a non-negative +// value (even greater than the core index), but the collation generation protocol uses the +// remainder to select the core. UMP signals may also contain a claim queue offset, based on which +// we need to select the assigned core indexes for the para from that offset in the claim queue. +#[rstest] +#[case(0, 0, 0, false)] +#[case(1, 0, 0, true)] +#[case(1, 5, 0, false)] +#[case(2, 0, 1, true)] +#[case(4, 2, 2, false)] +fn distribute_collation_with_core_selectors( + #[case] total_cores: u32, + // The core selector index that will be obtained from the first collation. + #[case] init_cs_index: u8, + // Claim queue offset where the assigned cores will be stored. + #[case] cq_offset: u8, + // Enables v2 receipts feature, affecting core selector and claim queue handling. + #[case] v2_receipts: bool, +) { + let activated_hash: Hash = [1; 32].into(); + let para_id = ParaId::from(5); + let other_para_id = ParaId::from(10); + let node_features = + if v2_receipts { node_features_with_v2_enabled() } else { NodeFeatures::EMPTY }; + + let claim_queue = (0..total_cores) + .into_iter() + .map(|idx| { + // Set all cores assigned to para_id 5 at the cq_offset depth. + let mut vec = VecDeque::from(vec![other_para_id; cq_offset as usize]); + vec.push_back(para_id); + (CoreIndex(idx), vec) + }) + .collect::>(); + + test_harness(|mut virtual_overseer| async move { + helpers::initialize_collator( + &mut virtual_overseer, + para_id, + Some(CoreSelectorData::new(init_cs_index, 1, cq_offset)), + ) + .await; + helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; + helpers::handle_runtime_calls_on_new_head_activation( + &mut virtual_overseer, + activated_hash, + claim_queue, + node_features, + ) + .await; + + let mut cores_assigned = (0..total_cores).collect::>(); + if total_cores > 1 && init_cs_index > 0 { + // We need to rotate the list of cores because the first core selector index was + // non-zero, which should change the sequence of submissions. However, collations should + // still be submitted on all cores. + cores_assigned.rotate_left((init_cs_index as u32 % total_cores) as usize); + } + helpers::handle_cores_processing_for_a_leaf( + &mut virtual_overseer, + activated_hash, + para_id, + cores_assigned, + ) + .await; + + virtual_overseer + }); +} + +// Tests the behavior when a parachain repeatedly selects the same core index. +// Ensures that the system handles this behavior correctly while maintaining expected functionality. +#[rstest] +#[case(3, 0, vec![0])] +#[case(3, 1, vec![0, 1, 2])] +#[case(3, 2, vec![0, 2, 1])] +#[case(3, 3, vec![0])] +#[case(3, 4, vec![0, 1, 2])] +fn distribute_collation_with_repeated_core_selector_index( + #[case] total_cores: u32, + #[case] increment_cs_index_by: u8, + #[case] expected_selected_cores: Vec, +) { + let activated_hash: Hash = [1; 32].into(); + let para_id = ParaId::from(5); + let node_features = node_features_with_v2_enabled(); + + let claim_queue = (0..total_cores) + .into_iter() + .map(|idx| (CoreIndex(idx), VecDeque::from([para_id]))) + .collect::>(); + + test_harness(|mut virtual_overseer| async move { + helpers::initialize_collator( + &mut virtual_overseer, + para_id, + Some(CoreSelectorData::new(0, increment_cs_index_by, 0)), + ) + .await; + helpers::activate_new_head(&mut virtual_overseer, activated_hash).await; + helpers::handle_runtime_calls_on_new_head_activation( + &mut virtual_overseer, + activated_hash, + claim_queue, + node_features, + ) + .await; + + helpers::handle_cores_processing_for_a_leaf( + &mut virtual_overseer, + activated_hash, + para_id, + expected_selected_cores, + ) + .await; + + virtual_overseer + }); +} + #[rstest] #[case(true)] #[case(false)] @@ -405,10 +578,17 @@ mod helpers { use std::collections::{BTreeMap, VecDeque}; // Sends `Initialize` with a collator config - pub async fn initialize_collator(virtual_overseer: &mut VirtualOverseer, para_id: ParaId) { + pub async fn initialize_collator( + virtual_overseer: &mut VirtualOverseer, + para_id: ParaId, + core_selector_data: Option, + ) { virtual_overseer .send(FromOrchestra::Communication { - msg: CollationGenerationMessage::Initialize(test_config(para_id)), + msg: CollationGenerationMessage::Initialize(test_config( + para_id, + core_selector_data, + )), }) .await; } diff --git a/prdoc/pr_7104.prdoc b/prdoc/pr_7104.prdoc new file mode 100644 index 000000000000..bd05e2b60e1f --- /dev/null +++ b/prdoc/pr_7104.prdoc @@ -0,0 +1,23 @@ +title: "collation-generation: resolve mismatch between descriptor and commitments core index" + +doc: + - audience: Node Dev + description: | + This PR resolves a bug where collators failed to generate and submit collations, + resulting in the following error: + + ``` + ERROR tokio-runtime-worker parachain::collation-generation: Failed to construct and + distribute collation: V2 core index check failed: The core index in commitments doesn't + match the one in descriptor. + ``` + + This issue affects only legacy and test collators that still use the collation function. + It is not a problem for lookahead or slot-based collators. + + This fix ensures the descriptor core index contains the value determined by the core + selector UMP signal when the parachain is using RFC103. + +crates: + - name: polkadot-node-collation-generation + bump: patch From 31039707b4d4a1639c38f7302e576b9973ab64df Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 22 Jan 2025 14:00:02 +0200 Subject: [PATCH 301/340] apply merge damage changes --- .../primitives/router/src/inbound/v1.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v1.rs b/bridges/snowbridge/primitives/router/src/inbound/v1.rs index b78c9ca78b43..f3dd191ac70f 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v1.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v1.rs @@ -273,7 +273,7 @@ where // Call create_asset on foreign assets pallet. Transact { origin_kind: OriginKind::Xcm, - fallback_max_weight: None, + fallback_max_weight: Some(Weight::from_parts(400_000_000, 8_000)), call: ( create_call_index, asset_id, @@ -352,7 +352,9 @@ where }])), // Perform a deposit reserve to send to destination chain. DepositReserveAsset { - assets: Definite(vec![dest_para_fee_asset.clone(), asset].into()), + // Send over assets and unspent fees, XCM delivery fee will be charged from + // here. + assets: Wild(AllCounted(2)), dest: Location::new(1, [Parachain(dest_para_id)]), xcm: vec![ // Buy execution on target. @@ -386,10 +388,14 @@ where // Convert ERC20 token address to a location that can be understood by Assets Hub. fn convert_token_address(network: NetworkId, token: H160) -> Location { - Location::new( - 2, - [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], - ) + if token == H160([0; 20]) { + Location::new(2, [GlobalConsensus(network)]) + } else { + Location::new( + 2, + [GlobalConsensus(network), AccountKey20 { network: None, key: token.into() }], + ) + } } /// Constructs an XCM message destined for AssetHub that withdraws assets from the sovereign From 350a6c4ccc4c2f376b9f5ed259daf3a56d5fed56 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Wed, 22 Jan 2025 14:02:01 +0200 Subject: [PATCH 302/340] Fix bridge tests image (#7292) Fix bridge tests image --- docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile index b1f4bffc772a..f9879fea2082 100644 --- a/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile +++ b/docker/dockerfiles/bridges_zombienet_tests_injected.Dockerfile @@ -48,8 +48,9 @@ RUN set -eux; \ cd /home/nonroot/bridges-polkadot-sdk/bridges/testing/framework/utils/generate_hex_encoded_call; \ npm install +# use the non-root user +USER node # check if executable works in this container -USER nonroot RUN /usr/local/bin/polkadot --version RUN /usr/local/bin/polkadot-parachain --version RUN /usr/local/bin/substrate-relay --version From 959c662c9d1773df5a2c82dd4aa6bebb1c5db5a2 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Wed, 22 Jan 2025 15:37:52 +0200 Subject: [PATCH 303/340] fixes --- Cargo.lock | 5 ++--- .../snowbridge/pallets/inbound-queue-v2/Cargo.toml | 6 ++---- .../pallets/inbound-queue-v2/src/envelope.rs | 7 +++---- bridges/snowbridge/primitives/router/Cargo.toml | 4 ++-- .../primitives/router/src/inbound/payload.rs | 11 +++++++---- .../snowbridge/primitives/router/src/inbound/v2.rs | 2 +- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73697d9fe5d7..0fdc9327e6ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25373,8 +25373,7 @@ dependencies = [ name = "snowbridge-pallet-inbound-queue-v2" version = "0.2.0" dependencies = [ - "alloy-primitives", - "alloy-sol-types", + "alloy-core", "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-system 28.0.0", @@ -25497,7 +25496,7 @@ dependencies = [ name = "snowbridge-router-primitives" version = "0.9.0" dependencies = [ - "alloy-sol-types", + "alloy-core", "frame-support 28.0.0", "frame-system 28.0.0", "hex-literal", diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml index ecebc677e997..be3f42d9bd75 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml @@ -20,8 +20,7 @@ codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } hex-literal = { optional = true, workspace = true, default-features = true } log = { workspace = true } -alloy-primitives = { features = ["rlp"], workspace = true } -alloy-sol-types = { workspace = true } +alloy-core = { workspace = true, features = ["sol-types"] } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } @@ -51,8 +50,7 @@ hex = { workspace = true, default-features = true } [features] default = ["std"] std = [ - "alloy-primitives/std", - "alloy-sol-types/std", + "alloy-core/std", "codec/std", "frame-benchmarking/std", "frame-support/std", diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs index 8c9b137c64ba..c9f224442812 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs @@ -5,8 +5,7 @@ use snowbridge_core::inbound::Log; use sp_core::{RuntimeDebug, H160}; use sp_std::prelude::*; -use alloy_primitives::B256; -use alloy_sol_types::{sol, SolEvent}; +use alloy_core::{primitives::B256, sol, sol_types::SolEvent}; sol! { event OutboundMessageAccepted(uint64 indexed nonce, uint128 fee, bytes payload); @@ -34,14 +33,14 @@ impl TryFrom<&Log> for Envelope { fn try_from(log: &Log) -> Result { let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); - let event = OutboundMessageAccepted::decode_log(topics, &log.data, true) + let event = OutboundMessageAccepted::decode_raw_log(topics, &log.data, true) .map_err(|_| EnvelopeDecodeError)?; Ok(Self { gateway: log.address, nonce: event.nonce, fee: event.fee, - payload: event.payload, + payload: event.payload.into(), }) } } diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index 010bb3b90e6e..9b55b0d76d03 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -15,7 +15,7 @@ workspace = true codec = { workspace = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -alloy-sol-types = { workspace = true } +alloy-core = { workspace = true, features = ["sol-types"] } frame-support = { workspace = true } frame-system = { workspace = true } @@ -37,7 +37,7 @@ hex-literal = { workspace = true, default-features = true } [features] default = ["std"] std = [ - "alloy-sol-types/std", + "alloy-core/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/bridges/snowbridge/primitives/router/src/inbound/payload.rs b/bridges/snowbridge/primitives/router/src/inbound/payload.rs index 3caa5641427f..99c505c144ed 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/payload.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/payload.rs @@ -1,10 +1,13 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2023 Snowfork + +extern crate alloc; + use crate::inbound::v2::{ Asset::{ForeignTokenERC20, NativeTokenERC20}, Message, }; -use alloy_sol_types::{sol, SolType}; +use alloy_core::{sol, sol_types::SolType}; use sp_core::{RuntimeDebug, H160, H256}; sol! { @@ -49,7 +52,7 @@ impl TryFrom<&[u8]> for Message { let decoded_payload = Payload::abi_decode(&encoded_payload, true).map_err(|_| PayloadDecodeError)?; - let mut substrate_assets = vec![]; + let mut substrate_assets = alloc::vec![]; for asset in decoded_payload.assets { match asset.kind { @@ -75,13 +78,13 @@ impl TryFrom<&[u8]> for Message { let mut claimer = None; if decoded_payload.claimer.len() > 0 { - claimer = Some(decoded_payload.claimer); + claimer = Some(decoded_payload.claimer.to_vec()); } Ok(Self { origin: H160::from(decoded_payload.origin.as_ref()), assets: substrate_assets, - xcm: decoded_payload.xcm, + xcm: decoded_payload.xcm.to_vec(), claimer, value: decoded_payload.value, execution_fee: decoded_payload.executionFee, diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index eb5cdcece065..d49b5834cabf 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -165,7 +165,7 @@ where if let Ok(claimer) = Junction::decode(&mut claimer.as_ref()) { let claimer_location: Location = Location::new(0, [claimer.into()]); refund_surplus_to = claimer_location.clone(); - instructions.push(SetAssetClaimer { location: claimer_location }); + instructions.push(SetHints { hints: vec![AssetClaimer {location: claimer_location }].try_into().unwrap() }); // TODO } } From 634a17b6f67c71e589f921b0ddd4c23bbed883f1 Mon Sep 17 00:00:00 2001 From: Mrisho Lukamba <69342343+MrishoLukamba@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:06:18 +0300 Subject: [PATCH 304/340] Unify Import verifier usage across parachain template and omninode (#7195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #7055 @skunert @bkchr --------- Co-authored-by: Bastian Köcher Co-authored-by: command-bot <> Co-authored-by: Sebastian Kunert --- .../aura/src/equivocation_import_queue.rs | 31 +++++++++- .../polkadot-omni-node/lib/src/nodes/aura.rs | 56 +++++++------------ prdoc/pr_7195.prdoc | 7 +++ 3 files changed, 56 insertions(+), 38 deletions(-) create mode 100644 prdoc/pr_7195.prdoc diff --git a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs index dbd9d5ba6a6f..a3bc90f53c25 100644 --- a/cumulus/client/consensus/aura/src/equivocation_import_queue.rs +++ b/cumulus/client/consensus/aura/src/equivocation_import_queue.rs @@ -68,7 +68,8 @@ impl NaiveEquivocationDefender { } } -struct Verifier { +/// A parachain block import verifier that checks for equivocation limits within each slot. +pub struct Verifier { client: Arc, create_inherent_data_providers: CIDP, defender: Mutex, @@ -76,6 +77,34 @@ struct Verifier { _phantom: std::marker::PhantomData (Block, P)>, } +impl Verifier +where + P: Pair, + P::Signature: Codec, + P::Public: Codec + Debug, + Block: BlockT, + Client: ProvideRuntimeApi + Send + Sync, + >::Api: BlockBuilderApi + AuraApi, + + CIDP: CreateInherentDataProviders, +{ + /// Creates a new Verifier instance for handling parachain block import verification in Aura + /// consensus. + pub fn new( + client: Arc, + inherent_data_provider: CIDP, + telemetry: Option, + ) -> Self { + Self { + client, + create_inherent_data_providers: inherent_data_provider, + defender: Mutex::new(NaiveEquivocationDefender::default()), + telemetry, + _phantom: std::marker::PhantomData, + } + } +} + #[async_trait::async_trait] impl VerifierT for Verifier where diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs index 816f76117a26..cd0e35d0d069 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs @@ -37,9 +37,12 @@ use cumulus_client_collator::service::{ use cumulus_client_consensus_aura::collators::slot_based::{ self as slot_based, Params as SlotBasedParams, }; -use cumulus_client_consensus_aura::collators::{ - lookahead::{self as aura, Params as AuraParams}, - slot_based::{SlotBasedBlockImport, SlotBasedBlockImportHandle}, +use cumulus_client_consensus_aura::{ + collators::{ + lookahead::{self as aura, Params as AuraParams}, + slot_based::{SlotBasedBlockImport, SlotBasedBlockImportHandle}, + }, + equivocation_import_queue::Verifier as EquivocationVerifier, }; use cumulus_client_consensus_proposer::{Proposer, ProposerInterface}; use cumulus_client_consensus_relay_chain::Verifier as RelayChainVerifier; @@ -118,49 +121,28 @@ where telemetry_handle: Option, task_manager: &TaskManager, ) -> sc_service::error::Result> { - let verifier_client = client.clone(); - - let aura_verifier = cumulus_client_consensus_aura::build_verifier::< - ::Pair, - _, - _, - _, - >(cumulus_client_consensus_aura::BuildVerifierParams { - client: verifier_client.clone(), - create_inherent_data_providers: move |parent_hash, _| { - let cidp_client = verifier_client.clone(); - async move { - let slot_duration = cumulus_client_consensus_aura::slot_duration_at( - &*cidp_client, - parent_hash, - )?; - let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); - - let slot = - sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( - *timestamp, - slot_duration, - ); - - Ok((slot, timestamp)) - } - }, - telemetry: telemetry_handle, - }); + let inherent_data_providers = + move |_, _| async move { Ok(sp_timestamp::InherentDataProvider::from_system_time()) }; + let registry = config.prometheus_registry(); + let spawner = task_manager.spawn_essential_handle(); let relay_chain_verifier = Box::new(RelayChainVerifier::new(client.clone(), |_, _| async { Ok(()) })); + let equivocation_aura_verifier = + EquivocationVerifier::<::Pair, _, _, _>::new( + client.clone(), + inherent_data_providers, + telemetry_handle, + ); + let verifier = Verifier { client, + aura_verifier: Box::new(equivocation_aura_verifier), relay_chain_verifier, - aura_verifier: Box::new(aura_verifier), - _phantom: PhantomData, + _phantom: Default::default(), }; - let registry = config.prometheus_registry(); - let spawner = task_manager.spawn_essential_handle(); - Ok(BasicQueue::new(verifier, Box::new(block_import), None, &spawner, registry)) } } diff --git a/prdoc/pr_7195.prdoc b/prdoc/pr_7195.prdoc new file mode 100644 index 000000000000..db4f877b156a --- /dev/null +++ b/prdoc/pr_7195.prdoc @@ -0,0 +1,7 @@ +title: Unify Import verifier usage across parachain template and omninode +doc: +- audience: Node Dev + description: |- + In polkadot-omni-node block import pipeline it uses default aura verifier without checking equivocation, + This Pr replaces the check with full verification with equivocation like in parachain template block import +crates: [] From fd64a1e7768ba6e8676cbbf25c4e821a901c0a7f Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Wed, 22 Jan 2025 18:51:59 +0200 Subject: [PATCH 305/340] net/libp2p: Enforce outbound request-response timeout limits (#7222) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR enforces that outbound requests are finished within the specified protocol timeout. The stable2412 version running libp2p 0.52.4 contains a bug which does not track request timeouts properly: - https://github.com/libp2p/rust-libp2p/pull/5429 The issue has been detected while submitting libp2p -> litep2p requests in kusama. This aims to check that pending outbound requests have not timedout. Although the issue has been fixed in libp2p, there might be other cases where this may happen. For example: - https://github.com/libp2p/rust-libp2p/pull/5417 For more context see: https://github.com/paritytech/polkadot-sdk/issues/7076#issuecomment-2596085096 1. Ideally, the force-timeout mechanism in this PR should never be triggered in production. However, origin/stable2412 occasionally encounters this issue. When this happens, 2 warnings may be generated: - one warning introduced by this PR wrt force timeout terminating the request - possible one warning when the libp2p decides (if at all) to provide the response back to substrate (as mentioned by @alexggh [here](https://github.com/paritytech/polkadot-sdk/pull/7222/files#diff-052aeaf79fef3d9a18c2cfd67006aa306b8d52e848509d9077a6a0f2eb856af7L769) and [here](https://github.com/paritytech/polkadot-sdk/pull/7222/files#diff-052aeaf79fef3d9a18c2cfd67006aa306b8d52e848509d9077a6a0f2eb856af7L842) 2. This implementation does not propagate to the substrate service the `RequestFinished { error: .. }`. That event is only used internally by substrate to increment metrics. However, we don't have the peer information available to propagate the event properly when we force-timeout the request. Considering this should most likely not happen in production (origin/master) and that we'll be able to extract information by warnings, I would say this is a good tradeoff for code simplicity: https://github.com/paritytech/polkadot-sdk/blob/06e3b5c6a7696048d65f1b8729f16b379a16f501/substrate/client/network/src/service.rs#L1543 ### Testing Added a new test to ensure the timeout is reached properly, even if libp2p does not produce a response in due time. I've also transitioned the tests to using `tokio::test` due to a limitation of [CI](https://github.com/paritytech/polkadot-sdk/actions/runs/12832055737/job/35784043867) ``` --- TRY 1 STDERR: sc-network request_responses::tests::max_response_size_exceeded --- thread 'request_responses::tests::max_response_size_exceeded' panicked at /usr/local/cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.40.0/src/time/interval.rs:139:26: there is no reactor running, must be called from the context of a Tokio 1.x runtime ``` cc @paritytech/networking --------- Signed-off-by: Alexandru Vasile Co-authored-by: Bastian Köcher --- prdoc/pr_7222.prdoc | 19 + .../client/network/src/request_responses.rs | 1020 ++++++++++------- 2 files changed, 612 insertions(+), 427 deletions(-) create mode 100644 prdoc/pr_7222.prdoc diff --git a/prdoc/pr_7222.prdoc b/prdoc/pr_7222.prdoc new file mode 100644 index 000000000000..40b89b0a1820 --- /dev/null +++ b/prdoc/pr_7222.prdoc @@ -0,0 +1,19 @@ +title: Enforce libp2p outbound request-response timeout limits + +doc: + - audience: Node Dev + description: | + This PR enforces that outbound requests are finished within the specified protocol timeout. + The stable2412 version running libp2p 0.52.4 contains a bug which does not track request timeouts properly + https://github.com/libp2p/rust-libp2p/pull/5429. + + The issue has been detected while submitting libp2p to litep2p requests in Kusama. + This aims to check that pending outbound requests have not timed out. + Although the issue has been fixed in libp2p, there might be other cases where this may happen. + For example, https://github.com/libp2p/rust-libp2p/pull/5417. + + For more context see https://github.com/paritytech/polkadot-sdk/issues/7076#issuecomment-2596085096. + +crates: +- name: sc-network + bump: patch diff --git a/substrate/client/network/src/request_responses.rs b/substrate/client/network/src/request_responses.rs index 5fe34c781378..e21773632ed7 100644 --- a/substrate/client/network/src/request_responses.rs +++ b/substrate/client/network/src/request_responses.rs @@ -64,6 +64,9 @@ use std::{ pub use libp2p::request_response::{Config, InboundRequestId, OutboundRequestId}; +/// Periodically check if requests are taking too long. +const PERIODIC_REQUEST_CHECK: Duration = Duration::from_secs(2); + /// Possible failures occurring in the context of sending an outbound request and receiving the /// response. #[derive(Debug, Clone, thiserror::Error)] @@ -251,8 +254,14 @@ pub struct OutgoingResponse { /// Information stored about a pending request. struct PendingRequest { + /// The time when the request was sent to the libp2p request-response protocol. started_at: Instant, - response_tx: oneshot::Sender, ProtocolName), RequestFailure>>, + /// The channel to send the response back to the caller. + /// + /// This is wrapped in an `Option` to allow for the channel to be taken out + /// on force-detected timeouts. + response_tx: Option, ProtocolName), RequestFailure>>>, + /// Fallback request to send if the primary request fails. fallback_request: Option<(Vec, ProtocolName)>, } @@ -336,16 +345,20 @@ impl From<(ProtocolName, RequestId)> for ProtocolRequestId } } +/// Details of a request-response protocol. +struct ProtocolDetails { + behaviour: Behaviour, + inbound_queue: Option>, + request_timeout: Duration, +} + /// Implementation of `NetworkBehaviour` that provides support for request-response protocols. pub struct RequestResponsesBehaviour { /// The multiple sub-protocols, by name. /// /// Contains the underlying libp2p request-response [`Behaviour`], plus an optional /// "response builder" used to build responses for incoming requests. - protocols: HashMap< - ProtocolName, - (Behaviour, Option>), - >, + protocols: HashMap, /// Pending requests, passed down to a request-response [`Behaviour`], awaiting a reply. pending_requests: HashMap, PendingRequest>, @@ -365,6 +378,14 @@ pub struct RequestResponsesBehaviour { /// Primarily used to get a reputation of a node. peer_store: Arc, + + /// Interval to check that the requests are not taking too long. + /// + /// We had issues in the past where libp2p did not produce a timeout event in due time. + /// + /// For more details, see: + /// - + periodic_request_check: tokio::time::Interval, } /// Generated by the response builder and waiting to be processed. @@ -393,7 +414,7 @@ impl RequestResponsesBehaviour { ProtocolSupport::Outbound }; - let rq_rp = Behaviour::with_codec( + let behaviour = Behaviour::with_codec( GenericCodec { max_request_size: protocol.max_request_size, max_response_size: protocol.max_response_size, @@ -405,7 +426,11 @@ impl RequestResponsesBehaviour { ); match protocols.entry(protocol.name) { - Entry::Vacant(e) => e.insert((rq_rp, protocol.inbound_queue)), + Entry::Vacant(e) => e.insert(ProtocolDetails { + behaviour, + inbound_queue: protocol.inbound_queue, + request_timeout: protocol.request_timeout, + }), Entry::Occupied(e) => return Err(RegisterError::DuplicateProtocol(e.key().clone())), }; } @@ -417,6 +442,7 @@ impl RequestResponsesBehaviour { pending_responses_arrival_time: Default::default(), send_feedback: Default::default(), peer_store, + periodic_request_check: tokio::time::interval(PERIODIC_REQUEST_CHECK), }) } @@ -437,9 +463,11 @@ impl RequestResponsesBehaviour { ) { log::trace!(target: "sub-libp2p", "send request to {target} ({protocol_name:?}), {} bytes", request.len()); - if let Some((protocol, _)) = self.protocols.get_mut(protocol_name.deref()) { + if let Some(ProtocolDetails { behaviour, .. }) = + self.protocols.get_mut(protocol_name.deref()) + { Self::send_request_inner( - protocol, + behaviour, &mut self.pending_requests, target, protocol_name, @@ -474,7 +502,7 @@ impl RequestResponsesBehaviour { (protocol_name.to_string().into(), request_id).into(), PendingRequest { started_at: Instant::now(), - response_tx: pending_response, + response_tx: Some(pending_response), fallback_request, }, ); @@ -521,18 +549,19 @@ impl NetworkBehaviour for RequestResponsesBehaviour { local_addr: &Multiaddr, remote_addr: &Multiaddr, ) -> Result, ConnectionDenied> { - let iter = self.protocols.iter_mut().filter_map(|(p, (r, _))| { - if let Ok(handler) = r.handle_established_inbound_connection( - connection_id, - peer, - local_addr, - remote_addr, - ) { - Some((p.to_string(), handler)) - } else { - None - } - }); + let iter = + self.protocols.iter_mut().filter_map(|(p, ProtocolDetails { behaviour, .. })| { + if let Ok(handler) = behaviour.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + ) { + Some((p.to_string(), handler)) + } else { + None + } + }); Ok(MultiHandler::try_from_iter(iter).expect( "Protocols are in a HashMap and there can be at most one handler per protocol name, \ @@ -548,19 +577,20 @@ impl NetworkBehaviour for RequestResponsesBehaviour { role_override: Endpoint, port_use: PortUse, ) -> Result, ConnectionDenied> { - let iter = self.protocols.iter_mut().filter_map(|(p, (r, _))| { - if let Ok(handler) = r.handle_established_outbound_connection( - connection_id, - peer, - addr, - role_override, - port_use, - ) { - Some((p.to_string(), handler)) - } else { - None - } - }); + let iter = + self.protocols.iter_mut().filter_map(|(p, ProtocolDetails { behaviour, .. })| { + if let Ok(handler) = behaviour.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + port_use, + ) { + Some((p.to_string(), handler)) + } else { + None + } + }); Ok(MultiHandler::try_from_iter(iter).expect( "Protocols are in a HashMap and there can be at most one handler per protocol name, \ @@ -569,8 +599,8 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } fn on_swarm_event(&mut self, event: FromSwarm) { - for (protocol, _) in self.protocols.values_mut() { - protocol.on_swarm_event(event); + for ProtocolDetails { behaviour, .. } in self.protocols.values_mut() { + behaviour.on_swarm_event(event); } } @@ -581,8 +611,8 @@ impl NetworkBehaviour for RequestResponsesBehaviour { event: THandlerOutEvent, ) { let p_name = event.0; - if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { - return proto.on_connection_handler_event(peer_id, connection_id, event.1) + if let Some(ProtocolDetails { behaviour, .. }) = self.protocols.get_mut(p_name.as_str()) { + return behaviour.on_connection_handler_event(peer_id, connection_id, event.1) } else { log::warn!( target: "sub-libp2p", @@ -594,6 +624,51 @@ impl NetworkBehaviour for RequestResponsesBehaviour { fn poll(&mut self, cx: &mut Context) -> Poll>> { 'poll_all: loop { + // Poll the periodic request check. + if self.periodic_request_check.poll_tick(cx).is_ready() { + self.pending_requests.retain(|id, req| { + let Some(ProtocolDetails { request_timeout, .. }) = + self.protocols.get(&id.protocol) + else { + log::warn!( + target: "sub-libp2p", + "Request {id:?} has no protocol registered.", + ); + + if let Some(response_tx) = req.response_tx.take() { + if response_tx.send(Err(RequestFailure::UnknownProtocol)).is_err() { + log::debug!( + target: "sub-libp2p", + "Request {id:?} has no protocol registered. At the same time local node is no longer interested in the result.", + ); + } + } + return false + }; + + let elapsed = req.started_at.elapsed(); + if elapsed > *request_timeout { + log::debug!( + target: "sub-libp2p", + "Request {id:?} force detected as timeout.", + ); + + if let Some(response_tx) = req.response_tx.take() { + if response_tx.send(Err(RequestFailure::Network(OutboundFailure::Timeout))).is_err() { + log::debug!( + target: "sub-libp2p", + "Request {id:?} force detected as timeout. At the same time local node is no longer interested in the result.", + ); + } + } + + false + } else { + true + } + }); + } + // Poll to see if any response is ready to be sent back. while let Poll::Ready(Some(outcome)) = self.pending_responses.poll_next_unpin(cx) { let RequestProcessingOutcome { @@ -610,10 +685,12 @@ impl NetworkBehaviour for RequestResponsesBehaviour { }; if let Ok(payload) = result { - if let Some((protocol, _)) = self.protocols.get_mut(&*protocol_name) { + if let Some(ProtocolDetails { behaviour, .. }) = + self.protocols.get_mut(&*protocol_name) + { log::trace!(target: "sub-libp2p", "send response to {peer} ({protocol_name:?}), {} bytes", payload.len()); - if protocol.send_response(inner_channel, Ok(payload)).is_err() { + if behaviour.send_response(inner_channel, Ok(payload)).is_err() { // Note: Failure is handled further below when receiving // `InboundFailure` event from request-response [`Behaviour`]. log::debug!( @@ -641,7 +718,8 @@ impl NetworkBehaviour for RequestResponsesBehaviour { let mut fallback_requests = vec![]; // Poll request-responses protocols. - for (protocol, (ref mut behaviour, ref mut resp_builder)) in &mut self.protocols { + for (protocol, ProtocolDetails { behaviour, inbound_queue, .. }) in &mut self.protocols + { 'poll_protocol: while let Poll::Ready(ev) = behaviour.poll(cx) { let ev = match ev { // Main events we are interested in. @@ -696,7 +774,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { // Submit the request to the "response builder" passed by the user at // initialization. - if let Some(resp_builder) = resp_builder { + if let Some(resp_builder) = inbound_queue { // If the response builder is too busy, silently drop `tx`. This // will be reported by the corresponding request-response // [`Behaviour`] through an `InboundFailure::Omission` event. @@ -744,7 +822,11 @@ impl NetworkBehaviour for RequestResponsesBehaviour { .pending_requests .remove(&(protocol.clone(), request_id).into()) { - Some(PendingRequest { started_at, response_tx, .. }) => { + Some(PendingRequest { + started_at, + response_tx: Some(response_tx), + .. + }) => { log::trace!( target: "sub-libp2p", "received response from {peer} ({protocol:?}), {} bytes", @@ -760,13 +842,13 @@ impl NetworkBehaviour for RequestResponsesBehaviour { .map_err(|_| RequestFailure::Obsolete); (started_at, delivered) }, - None => { - log::warn!( + _ => { + log::debug!( target: "sub-libp2p", - "Received `RequestResponseEvent::Message` with unexpected request id {:?}", + "Received `RequestResponseEvent::Message` with unexpected request id {:?} from {:?}", request_id, + peer, ); - debug_assert!(false); continue }, }; @@ -795,7 +877,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { { Some(PendingRequest { started_at, - response_tx, + response_tx: Some(response_tx), fallback_request, }) => { // Try using the fallback request if the protocol was not @@ -833,13 +915,14 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } started_at }, - None => { - log::warn!( + _ => { + log::debug!( target: "sub-libp2p", - "Received `RequestResponseEvent::Message` with unexpected request id {:?}", + "Received `RequestResponseEvent::OutboundFailure` with unexpected request id {:?} error {:?} from {:?}", request_id, + error, + peer ); - debug_assert!(false); continue }, }; @@ -904,7 +987,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { // Send out fallback requests. for (peer, protocol, request, pending_response) in fallback_requests.drain(..) { - if let Some((behaviour, _)) = self.protocols.get_mut(&protocol) { + if let Some(ProtocolDetails { behaviour, .. }) = self.protocols.get_mut(&protocol) { Self::send_request_inner( behaviour, &mut self.pending_requests, @@ -1073,7 +1156,7 @@ mod tests { use crate::mock::MockPeerStore; use assert_matches::assert_matches; - use futures::{channel::oneshot, executor::LocalPool, task::Spawn}; + use futures::channel::oneshot; use libp2p::{ core::{ transport::{MemoryTransport, Transport}, @@ -1086,10 +1169,10 @@ mod tests { }; use std::{iter, time::Duration}; - struct TokioExecutor(tokio::runtime::Runtime); + struct TokioExecutor; impl Executor for TokioExecutor { fn exec(&self, f: Pin + Send>>) { - let _ = self.0.spawn(f); + tokio::spawn(f); } } @@ -1106,13 +1189,11 @@ mod tests { let behaviour = RequestResponsesBehaviour::new(list, Arc::new(MockPeerStore {})).unwrap(); - let runtime = tokio::runtime::Runtime::new().unwrap(); - let mut swarm = Swarm::new( transport, behaviour, keypair.public().to_peer_id(), - SwarmConfig::with_executor(TokioExecutor(runtime)) + SwarmConfig::with_executor(TokioExecutor {}) // This is taken care of by notification protocols in non-test environment // It is very slow in test environment for some reason, hence larger timeout .with_idle_connection_timeout(Duration::from_secs(10)), @@ -1125,34 +1206,27 @@ mod tests { (swarm, listen_addr) } - #[test] - fn basic_request_response_works() { + #[tokio::test] + async fn basic_request_response_works() { let protocol_name = ProtocolName::from("/test/req-resp/1"); - let mut pool = LocalPool::new(); // Build swarms whose behaviour is [`RequestResponsesBehaviour`]. let mut swarms = (0..2) .map(|_| { let (tx, mut rx) = async_channel::bounded::(64); - pool.spawner() - .spawn_obj( - async move { - while let Some(rq) = rx.next().await { - let (fb_tx, fb_rx) = oneshot::channel(); - assert_eq!(rq.payload, b"this is a request"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok(b"this is a response".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: Some(fb_tx), - }); - fb_rx.await.unwrap(); - } - } - .boxed() - .into(), - ) - .unwrap(); + tokio::spawn(async move { + while let Some(rq) = rx.next().await { + let (fb_tx, fb_rx) = oneshot::channel(); + assert_eq!(rq.payload, b"this is a request"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this is a response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: Some(fb_tx), + }); + fb_rx.await.unwrap(); + } + }); let protocol_config = ProtocolConfig { name: protocol_name.clone(), @@ -1176,84 +1250,69 @@ mod tests { let (mut swarm, _) = swarms.remove(0); // Running `swarm[0]` in the background. - pool.spawner() - .spawn_obj({ - async move { - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { - result.unwrap(); - }, - _ => {}, - } - } - } - .boxed() - .into() - }) - .unwrap(); - - // Remove and run the remaining swarm. - let (mut swarm, _) = swarms.remove(0); - pool.run_until(async move { - let mut response_receiver = None; - + tokio::spawn(async move { loop { match swarm.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - let (sender, receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - &peer_id, - protocol_name.clone(), - b"this is a request".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); - assert!(response_receiver.is_none()); - response_receiver = Some(receiver); - }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { result.unwrap(); - break }, _ => {}, } } - - assert_eq!( - response_receiver.unwrap().await.unwrap().unwrap(), - (b"this is a response".to_vec(), protocol_name) - ); }); + + // Remove and run the remaining swarm. + let (mut swarm, _) = swarms.remove(0); + let mut response_receiver = None; + + loop { + match swarm.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender, receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + &peer_id, + protocol_name.clone(), + b"this is a request".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + assert!(response_receiver.is_none()); + response_receiver = Some(receiver); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, + } + } + + assert_eq!( + response_receiver.unwrap().await.unwrap().unwrap(), + (b"this is a response".to_vec(), protocol_name) + ); } - #[test] - fn max_response_size_exceeded() { + #[tokio::test] + async fn max_response_size_exceeded() { let protocol_name = ProtocolName::from("/test/req-resp/1"); - let mut pool = LocalPool::new(); // Build swarms whose behaviour is [`RequestResponsesBehaviour`]. let mut swarms = (0..2) .map(|_| { let (tx, mut rx) = async_channel::bounded::(64); - pool.spawner() - .spawn_obj( - async move { - while let Some(rq) = rx.next().await { - assert_eq!(rq.payload, b"this is a request"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok(b"this response exceeds the limit".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: None, - }); - } - } - .boxed() - .into(), - ) - .unwrap(); + tokio::spawn(async move { + while let Some(rq) = rx.next().await { + assert_eq!(rq.payload, b"this is a request"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this response exceeds the limit".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }); + } + }); let protocol_config = ProtocolConfig { name: protocol_name.clone(), @@ -1278,59 +1337,52 @@ mod tests { // Running `swarm[0]` in the background until a `InboundRequest` event happens, // which is a hint about the test having ended. let (mut swarm, _) = swarms.remove(0); - pool.spawner() - .spawn_obj({ - async move { - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { - assert!(result.is_ok()); - }, - SwarmEvent::ConnectionClosed { .. } => { - break; - }, - _ => {}, - } - } - } - .boxed() - .into() - }) - .unwrap(); - - // Remove and run the remaining swarm. - let (mut swarm, _) = swarms.remove(0); - pool.run_until(async move { - let mut response_receiver = None; - + tokio::spawn(async move { loop { match swarm.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - let (sender, receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - &peer_id, - protocol_name.clone(), - b"this is a request".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); - assert!(response_receiver.is_none()); - response_receiver = Some(receiver); + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { + assert!(result.is_ok()); }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - assert!(result.is_err()); - break + SwarmEvent::ConnectionClosed { .. } => { + break; }, _ => {}, } } + }); + + // Remove and run the remaining swarm. + let (mut swarm, _) = swarms.remove(0); + + let mut response_receiver = None; - match response_receiver.unwrap().await.unwrap().unwrap_err() { - RequestFailure::Network(OutboundFailure::Io(_)) => {}, - request_failure => panic!("Unexpected failure: {request_failure:?}"), + loop { + match swarm.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender, receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + &peer_id, + protocol_name.clone(), + b"this is a request".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + assert!(response_receiver.is_none()); + response_receiver = Some(receiver); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + assert!(result.is_err()); + break + }, + _ => {}, } - }); + } + + match response_receiver.unwrap().await.unwrap().unwrap_err() { + RequestFailure::Network(OutboundFailure::Io(_)) => {}, + request_failure => panic!("Unexpected failure: {request_failure:?}"), + } } /// A `RequestId` is a unique identifier among either all inbound or all outbound requests for @@ -1343,11 +1395,10 @@ mod tests { /// without a `RequestId` collision. /// /// See [`ProtocolRequestId`] for additional information. - #[test] - fn request_id_collision() { + #[tokio::test] + async fn request_id_collision() { let protocol_name_1 = ProtocolName::from("/test/req-resp-1/1"); let protocol_name_2 = ProtocolName::from("/test/req-resp-2/1"); - let mut pool = LocalPool::new(); let mut swarm_1 = { let protocol_configs = vec![ @@ -1405,114 +1456,100 @@ mod tests { swarm_1.dial(listen_add_2).unwrap(); // Run swarm 2 in the background, receiving two requests. - pool.spawner() - .spawn_obj( - async move { - loop { - match swarm_2.select_next_some().await { - SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { - result.unwrap(); - }, - _ => {}, - } - } + tokio::spawn(async move { + loop { + match swarm_2.select_next_some().await { + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { + result.unwrap(); + }, + _ => {}, } - .boxed() - .into(), - ) - .unwrap(); + } + }); // Handle both requests sent by swarm 1 to swarm 2 in the background. // // Make sure both requests overlap, by answering the first only after receiving the // second. - pool.spawner() - .spawn_obj( - async move { - let protocol_1_request = swarm_2_handler_1.next().await; - let protocol_2_request = swarm_2_handler_2.next().await; - - protocol_1_request - .unwrap() - .pending_response - .send(OutgoingResponse { - result: Ok(b"this is a response".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: None, - }) - .unwrap(); - protocol_2_request - .unwrap() - .pending_response - .send(OutgoingResponse { - result: Ok(b"this is a response".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: None, - }) - .unwrap(); - } - .boxed() - .into(), - ) - .unwrap(); + tokio::spawn(async move { + let protocol_1_request = swarm_2_handler_1.next().await; + let protocol_2_request = swarm_2_handler_2.next().await; + + protocol_1_request + .unwrap() + .pending_response + .send(OutgoingResponse { + result: Ok(b"this is a response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }) + .unwrap(); + protocol_2_request + .unwrap() + .pending_response + .send(OutgoingResponse { + result: Ok(b"this is a response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }) + .unwrap(); + }); // Have swarm 1 send two requests to swarm 2 and await responses. - pool.run_until(async move { - let mut response_receivers = None; - let mut num_responses = 0; - loop { - match swarm_1.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - let (sender_1, receiver_1) = oneshot::channel(); - let (sender_2, receiver_2) = oneshot::channel(); - swarm_1.behaviour_mut().send_request( - &peer_id, - protocol_name_1.clone(), - b"this is a request".to_vec(), - None, - sender_1, - IfDisconnected::ImmediateError, - ); - swarm_1.behaviour_mut().send_request( - &peer_id, - protocol_name_2.clone(), - b"this is a request".to_vec(), - None, - sender_2, - IfDisconnected::ImmediateError, - ); - assert!(response_receivers.is_none()); - response_receivers = Some((receiver_1, receiver_2)); - }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - num_responses += 1; - result.unwrap(); - if num_responses == 2 { - break - } - }, - _ => {}, - } + let mut response_receivers = None; + let mut num_responses = 0; + + loop { + match swarm_1.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender_1, receiver_1) = oneshot::channel(); + let (sender_2, receiver_2) = oneshot::channel(); + swarm_1.behaviour_mut().send_request( + &peer_id, + protocol_name_1.clone(), + b"this is a request".to_vec(), + None, + sender_1, + IfDisconnected::ImmediateError, + ); + swarm_1.behaviour_mut().send_request( + &peer_id, + protocol_name_2.clone(), + b"this is a request".to_vec(), + None, + sender_2, + IfDisconnected::ImmediateError, + ); + assert!(response_receivers.is_none()); + response_receivers = Some((receiver_1, receiver_2)); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + num_responses += 1; + result.unwrap(); + if num_responses == 2 { + break + } + }, + _ => {}, } - let (response_receiver_1, response_receiver_2) = response_receivers.unwrap(); - assert_eq!( - response_receiver_1.await.unwrap().unwrap(), - (b"this is a response".to_vec(), protocol_name_1) - ); - assert_eq!( - response_receiver_2.await.unwrap().unwrap(), - (b"this is a response".to_vec(), protocol_name_2) - ); - }); + } + let (response_receiver_1, response_receiver_2) = response_receivers.unwrap(); + assert_eq!( + response_receiver_1.await.unwrap().unwrap(), + (b"this is a response".to_vec(), protocol_name_1) + ); + assert_eq!( + response_receiver_2.await.unwrap().unwrap(), + (b"this is a response".to_vec(), protocol_name_2) + ); } - #[test] - fn request_fallback() { + #[tokio::test] + async fn request_fallback() { let protocol_name_1 = ProtocolName::from("/test/req-resp/2"); let protocol_name_1_fallback = ProtocolName::from("/test/req-resp/1"); let protocol_name_2 = ProtocolName::from("/test/another"); - let mut pool = LocalPool::new(); let protocol_config_1 = ProtocolConfig { name: protocol_name_1.clone(), @@ -1550,39 +1587,31 @@ mod tests { let mut protocol_config_2 = protocol_config_2.clone(); protocol_config_2.inbound_queue = Some(tx_2); - pool.spawner() - .spawn_obj( - async move { - for _ in 0..2 { - if let Some(rq) = rx_1.next().await { - let (fb_tx, fb_rx) = oneshot::channel(); - assert_eq!(rq.payload, b"request on protocol /test/req-resp/1"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok( - b"this is a response on protocol /test/req-resp/1".to_vec() - ), - reputation_changes: Vec::new(), - sent_feedback: Some(fb_tx), - }); - fb_rx.await.unwrap(); - } - } - - if let Some(rq) = rx_2.next().await { - let (fb_tx, fb_rx) = oneshot::channel(); - assert_eq!(rq.payload, b"request on protocol /test/other"); - let _ = rq.pending_response.send(super::OutgoingResponse { - result: Ok(b"this is a response on protocol /test/other".to_vec()), - reputation_changes: Vec::new(), - sent_feedback: Some(fb_tx), - }); - fb_rx.await.unwrap(); - } + tokio::spawn(async move { + for _ in 0..2 { + if let Some(rq) = rx_1.next().await { + let (fb_tx, fb_rx) = oneshot::channel(); + assert_eq!(rq.payload, b"request on protocol /test/req-resp/1"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this is a response on protocol /test/req-resp/1".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: Some(fb_tx), + }); + fb_rx.await.unwrap(); } - .boxed() - .into(), - ) - .unwrap(); + } + + if let Some(rq) = rx_2.next().await { + let (fb_tx, fb_rx) = oneshot::channel(); + assert_eq!(rq.payload, b"request on protocol /test/other"); + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"this is a response on protocol /test/other".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: Some(fb_tx), + }); + fb_rx.await.unwrap(); + } + }); build_swarm(vec![protocol_config_1_fallback, protocol_config_2].into_iter()) }; @@ -1603,132 +1632,269 @@ mod tests { } // Running `older_swarm`` in the background. - pool.spawner() - .spawn_obj({ - async move { - loop { - _ = older_swarm.0.select_next_some().await; - } - } - .boxed() - .into() - }) - .unwrap(); + tokio::spawn(async move { + loop { + _ = older_swarm.0.select_next_some().await; + } + }); // Run the newer swarm. Attempt to make requests on all protocols. let (mut swarm, _) = new_swarm; let mut older_peer_id = None; - pool.run_until(async move { - let mut response_receiver = None; - // Try the new protocol with a fallback. - loop { - match swarm.select_next_some().await { - SwarmEvent::ConnectionEstablished { peer_id, .. } => { - older_peer_id = Some(peer_id); - let (sender, receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - &peer_id, - protocol_name_1.clone(), - b"request on protocol /test/req-resp/2".to_vec(), - Some(( - b"request on protocol /test/req-resp/1".to_vec(), - protocol_config_1_fallback.name.clone(), - )), - sender, - IfDisconnected::ImmediateError, - ); - response_receiver = Some(receiver); - }, - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - result.unwrap(); - break - }, - _ => {}, - } + let mut response_receiver = None; + // Try the new protocol with a fallback. + loop { + match swarm.select_next_some().await { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + older_peer_id = Some(peer_id); + let (sender, receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + &peer_id, + protocol_name_1.clone(), + b"request on protocol /test/req-resp/2".to_vec(), + Some(( + b"request on protocol /test/req-resp/1".to_vec(), + protocol_config_1_fallback.name.clone(), + )), + sender, + IfDisconnected::ImmediateError, + ); + response_receiver = Some(receiver); + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, } - assert_eq!( - response_receiver.unwrap().await.unwrap().unwrap(), - ( - b"this is a response on protocol /test/req-resp/1".to_vec(), - protocol_name_1_fallback.clone() - ) - ); - // Try the old protocol with a useless fallback. - let (sender, response_receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - older_peer_id.as_ref().unwrap(), - protocol_name_1_fallback.clone(), - b"request on protocol /test/req-resp/1".to_vec(), - Some(( - b"dummy request, will fail if processed".to_vec(), - protocol_config_1_fallback.name.clone(), - )), - sender, - IfDisconnected::ImmediateError, - ); - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - result.unwrap(); - break - }, - _ => {}, - } + } + assert_eq!( + response_receiver.unwrap().await.unwrap().unwrap(), + ( + b"this is a response on protocol /test/req-resp/1".to_vec(), + protocol_name_1_fallback.clone() + ) + ); + // Try the old protocol with a useless fallback. + let (sender, response_receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + older_peer_id.as_ref().unwrap(), + protocol_name_1_fallback.clone(), + b"request on protocol /test/req-resp/1".to_vec(), + Some(( + b"dummy request, will fail if processed".to_vec(), + protocol_config_1_fallback.name.clone(), + )), + sender, + IfDisconnected::ImmediateError, + ); + loop { + match swarm.select_next_some().await { + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, } - assert_eq!( - response_receiver.await.unwrap().unwrap(), - ( - b"this is a response on protocol /test/req-resp/1".to_vec(), - protocol_name_1_fallback.clone() - ) - ); - // Try the new protocol with no fallback. Should fail. - let (sender, response_receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - older_peer_id.as_ref().unwrap(), - protocol_name_1.clone(), - b"request on protocol /test/req-resp-2".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); - loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - assert_matches!( - result.unwrap_err(), - RequestFailure::Network(OutboundFailure::UnsupportedProtocols) - ); - break - }, - _ => {}, - } + } + assert_eq!( + response_receiver.await.unwrap().unwrap(), + ( + b"this is a response on protocol /test/req-resp/1".to_vec(), + protocol_name_1_fallback.clone() + ) + ); + // Try the new protocol with no fallback. Should fail. + let (sender, response_receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + older_peer_id.as_ref().unwrap(), + protocol_name_1.clone(), + b"request on protocol /test/req-resp-2".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + loop { + match swarm.select_next_some().await { + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + assert_matches!( + result.unwrap_err(), + RequestFailure::Network(OutboundFailure::UnsupportedProtocols) + ); + break + }, + _ => {}, } - assert!(response_receiver.await.unwrap().is_err()); - // Try the other protocol with no fallback. - let (sender, response_receiver) = oneshot::channel(); - swarm.behaviour_mut().send_request( - older_peer_id.as_ref().unwrap(), - protocol_name_2.clone(), - b"request on protocol /test/other".to_vec(), - None, - sender, - IfDisconnected::ImmediateError, - ); + } + assert!(response_receiver.await.unwrap().is_err()); + // Try the other protocol with no fallback. + let (sender, response_receiver) = oneshot::channel(); + swarm.behaviour_mut().send_request( + older_peer_id.as_ref().unwrap(), + protocol_name_2.clone(), + b"request on protocol /test/other".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + loop { + match swarm.select_next_some().await { + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + result.unwrap(); + break + }, + _ => {}, + } + } + assert_eq!( + response_receiver.await.unwrap().unwrap(), + (b"this is a response on protocol /test/other".to_vec(), protocol_name_2.clone()) + ); + } + + /// This test ensures the `RequestResponsesBehaviour` propagates back the Request::Timeout error + /// even if the libp2p component hangs. + /// + /// For testing purposes, the communication happens on the `/test/req-resp/1` protocol. + /// + /// This is achieved by: + /// - Two swarms are connected, the first one is slow to respond and has the timeout set to 10 + /// seconds. The second swarm is configured with a timeout of 10 seconds in libp2p, however in + /// substrate this is set to 1 second. + /// + /// - The first swarm introduces a delay of 2 seconds before responding to the request. + /// + /// - The second swarm must enforce the 1 second timeout. + #[tokio::test] + async fn enforce_outbound_timeouts() { + const REQUEST_TIMEOUT: Duration = Duration::from_secs(10); + const REQUEST_TIMEOUT_SHORT: Duration = Duration::from_secs(1); + + // These swarms only speaks protocol_name. + let protocol_name = ProtocolName::from("/test/req-resp/1"); + + let protocol_config = ProtocolConfig { + name: protocol_name.clone(), + fallback_names: Vec::new(), + max_request_size: 1024, + max_response_size: 1024 * 1024, + request_timeout: REQUEST_TIMEOUT, // <-- important for the test + inbound_queue: None, + }; + + // Build swarms whose behaviour is [`RequestResponsesBehaviour`]. + let (mut first_swarm, _) = { + let (tx, mut rx) = async_channel::bounded::(64); + + tokio::spawn(async move { + if let Some(rq) = rx.next().await { + assert_eq!(rq.payload, b"this is a request"); + + // Sleep for more than `REQUEST_TIMEOUT_SHORT` and less than + // `REQUEST_TIMEOUT`. + tokio::time::sleep(REQUEST_TIMEOUT_SHORT * 2).await; + + // By the time the response is sent back, the second swarm + // received Timeout. + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"Second swarm already timedout".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }); + } + }); + + let mut protocol_config = protocol_config.clone(); + protocol_config.inbound_queue = Some(tx); + + build_swarm(iter::once(protocol_config)) + }; + + let (mut second_swarm, second_address) = { + let (tx, mut rx) = async_channel::bounded::(64); + + tokio::spawn(async move { + while let Some(rq) = rx.next().await { + let _ = rq.pending_response.send(super::OutgoingResponse { + result: Ok(b"This is the response".to_vec()), + reputation_changes: Vec::new(), + sent_feedback: None, + }); + } + }); + let mut protocol_config = protocol_config.clone(); + protocol_config.inbound_queue = Some(tx); + + build_swarm(iter::once(protocol_config.clone())) + }; + // Modify the second swarm to have a shorter timeout. + second_swarm + .behaviour_mut() + .protocols + .get_mut(&protocol_name) + .unwrap() + .request_timeout = REQUEST_TIMEOUT_SHORT; + + // Ask first swarm to dial the second swarm. + { + Swarm::dial(&mut first_swarm, second_address).unwrap(); + } + + // Running the first swarm in the background until a `InboundRequest` event happens, + // which is a hint about the test having ended. + tokio::spawn(async move { loop { - match swarm.select_next_some().await { - SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { - result.unwrap(); - break + let event = first_swarm.select_next_some().await; + match event { + SwarmEvent::Behaviour(Event::InboundRequest { result, .. }) => { + assert!(result.is_ok()); + break; + }, + SwarmEvent::ConnectionClosed { .. } => { + break; }, _ => {}, } } - assert_eq!( - response_receiver.await.unwrap().unwrap(), - (b"this is a response on protocol /test/other".to_vec(), protocol_name_2.clone()) - ); }); + + // Run the second swarm. + // - on connection established send the request to the first swarm + // - expect to receive a timeout + let mut response_receiver = None; + loop { + let event = second_swarm.select_next_some().await; + + match event { + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + let (sender, receiver) = oneshot::channel(); + second_swarm.behaviour_mut().send_request( + &peer_id, + protocol_name.clone(), + b"this is a request".to_vec(), + None, + sender, + IfDisconnected::ImmediateError, + ); + assert!(response_receiver.is_none()); + response_receiver = Some(receiver); + }, + SwarmEvent::ConnectionClosed { .. } => { + break; + }, + SwarmEvent::Behaviour(Event::RequestFinished { result, .. }) => { + assert!(result.is_err()); + break + }, + _ => {}, + } + } + + // Expect the timeout. + match response_receiver.unwrap().await.unwrap().unwrap_err() { + RequestFailure::Network(OutboundFailure::Timeout) => {}, + request_failure => panic!("Unexpected failure: {request_failure:?}"), + } } } From 89b022842c7ab922de5bf026cd45e43b9cd8c654 Mon Sep 17 00:00:00 2001 From: FereMouSiopi Date: Wed, 22 Jan 2025 10:08:59 -0800 Subject: [PATCH 306/340] Migrate `pallet-insecure-randomness-collective-flip` to umbrella crate (#6738) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of https://github.com/paritytech/polkadot-sdk/issues/6504 --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- Cargo.lock | 6 +--- .../Cargo.toml | 18 ++---------- .../src/lib.rs | 28 ++++++------------- 3 files changed, 13 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e41b7e99374..a10def370be7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13925,14 +13925,10 @@ dependencies = [ name = "pallet-insecure-randomness-collective-flip" version = "16.0.0" dependencies = [ - "frame-support 28.0.0", - "frame-system 28.0.0", "parity-scale-codec", + "polkadot-sdk-frame 0.1.0", "safe-mix", "scale-info", - "sp-core 28.0.0", - "sp-io 30.0.0", - "sp-runtime 31.0.1", ] [[package]] diff --git a/substrate/frame/insecure-randomness-collective-flip/Cargo.toml b/substrate/frame/insecure-randomness-collective-flip/Cargo.toml index 1682b52dfbf4..789f130423a4 100644 --- a/substrate/frame/insecure-randomness-collective-flip/Cargo.toml +++ b/substrate/frame/insecure-randomness-collective-flip/Cargo.toml @@ -17,30 +17,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -frame-support = { workspace = true } -frame-system = { workspace = true } +frame = { workspace = true, features = ["runtime"] } safe-mix = { workspace = true } scale-info = { features = ["derive"], workspace = true } -sp-runtime = { workspace = true } - -[dev-dependencies] -sp-core = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } [features] default = ["std"] std = [ "codec/std", - "frame-support/std", - "frame-system/std", + "frame/std", "safe-mix/std", "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", + "frame/try-runtime", ] diff --git a/substrate/frame/insecure-randomness-collective-flip/src/lib.rs b/substrate/frame/insecure-randomness-collective-flip/src/lib.rs index b605b4d08582..0e7e8001d5df 100644 --- a/substrate/frame/insecure-randomness-collective-flip/src/lib.rs +++ b/substrate/frame/insecure-randomness-collective-flip/src/lib.rs @@ -42,13 +42,11 @@ //! ### Example - Get random seed for the current block //! //! ``` -//! use frame_support::traits::Randomness; +//! use frame::{prelude::*, traits::Randomness}; //! -//! #[frame_support::pallet] +//! #[frame::pallet] //! pub mod pallet { //! use super::*; -//! use frame_support::pallet_prelude::*; -//! use frame_system::pallet_prelude::*; //! //! #[pallet::pallet] //! pub struct Pallet(_); @@ -73,9 +71,7 @@ use safe_mix::TripletMix; use codec::Encode; -use frame_support::{pallet_prelude::Weight, traits::Randomness}; -use frame_system::pallet_prelude::BlockNumberFor; -use sp_runtime::traits::{Hash, Saturating}; +use frame::{prelude::*, traits::Randomness}; const RANDOM_MATERIAL_LEN: u32 = 81; @@ -87,10 +83,9 @@ fn block_number_to_index(block_number: BlockNumberFor) -> usize { pub use pallet::*; -#[frame_support::pallet] +#[frame::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; #[pallet::pallet] pub struct Pallet(_); @@ -167,19 +162,14 @@ impl Randomness> for Pallet { mod tests { use super::*; use crate as pallet_insecure_randomness_collective_flip; - - use sp_core::H256; - use sp_runtime::{traits::Header as _, BuildStorage}; - - use frame_support::{ - derive_impl, parameter_types, - traits::{OnInitialize, Randomness}, + use frame::{ + testing_prelude::{frame_system::limits, *}, + traits::Header as _, }; - use frame_system::limits; type Block = frame_system::mocking::MockBlock; - frame_support::construct_runtime!( + construct_runtime!( pub enum Test { System: frame_system, @@ -199,7 +189,7 @@ mod tests { impl pallet_insecure_randomness_collective_flip::Config for Test {} - fn new_test_ext() -> sp_io::TestExternalities { + fn new_test_ext() -> TestExternalities { let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); t.into() } From 61e2a28cfd695c83ca20aea5d6a43c7273734794 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 23 Jan 2025 11:40:45 +0200 Subject: [PATCH 307/340] fix encoding --- .../pallets/inbound-queue-v2/src/envelope.rs | 100 +++++++++++++++--- .../pallets/inbound-queue-v2/src/lib.rs | 14 +-- .../pallets/inbound-queue-v2/src/test.rs | 5 +- 3 files changed, 97 insertions(+), 22 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs index c9f224442812..6cbbeb16ca47 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs @@ -7,8 +7,31 @@ use sp_std::prelude::*; use alloy_core::{primitives::B256, sol, sol_types::SolEvent}; +/** +struct AsNativeTokenERC20 { + address token_id; + uint128 value; + } + struct AsForeignTokenERC20 { + bytes32 token_id; + uint128 value; + } +**/ sol! { - event OutboundMessageAccepted(uint64 indexed nonce, uint128 fee, bytes payload); + struct EthereumAsset { + uint8 kind; + bytes data; + } + struct Payload { + address origin; + EthereumAsset[] assets; + bytes xcm; + bytes claimer; + uint128 value; + uint128 executionFee; + uint128 relayerFee; + } + event OutboundMessageAccepted(uint64 nonce, Payload payload); } /// An inbound message that has had its outer envelope decoded. @@ -18,10 +41,8 @@ pub struct Envelope { pub gateway: H160, /// A nonce for enforcing replay protection and ordering. pub nonce: u64, - /// Total fee paid in Ether on Ethereum, should cover all the cost - pub fee: u128, /// The inner payload generated from the source application. - pub payload: Vec, + pub payload: Payload, } #[derive(Copy, Clone, RuntimeDebug)] @@ -31,16 +52,71 @@ impl TryFrom<&Log> for Envelope { type Error = EnvelopeDecodeError; fn try_from(log: &Log) -> Result { + // Convert to B256 for Alloy decoding let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); - let event = OutboundMessageAccepted::decode_raw_log(topics, &log.data, true) - .map_err(|_| EnvelopeDecodeError)?; + // Decode the Solidity event from raw logs + let event = OutboundMessageAccepted::decode_raw_log(topics, &log.data, true).map_err( + |decode_err| { + println!("error is {decode_err}"); + log::error!( + target: "snowbridge-inbound-queue:v2", + "💫 decode error {:?}", + decode_err + ); + EnvelopeDecodeError + }, + )?; + + // event.nonce is a `u64` + // event.payload is already the typed `Payload` struct + Ok(Self { gateway: log.address, nonce: event.nonce, payload: event.payload }) + } +} + +impl core::fmt::Debug for Payload { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Payload") + .field("origin", &self.origin) + .field("assets", &self.assets) + .field("xcm", &self.xcm) + .field("claimer", &self.claimer) + .field("value", &self.value) + .field("executionFee", &self.executionFee) + .field("relayerFee", &self.relayerFee) + .finish() + } +} + +impl core::fmt::Debug for EthereumAsset { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("EthereumAsset") + .field("kind", &self.kind) + .field("data", &self.data) + .finish() + } +} + +#[cfg(test)] +mod tests { + use crate::{envelope::Log, Envelope}; + use frame_support::assert_ok; + use hex_literal::hex; + use sp_core::H160; + + #[test] + fn test_decode() { + let log = Log{ + address: hex!("b8ea8cb425d85536b158d661da1ef0895bb92f1d").into(), + topics: vec![hex!("b61699d45635baed7500944331ea827538a50dbfef79180f2079e9185da627aa").into()], + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000b8ea8cb425d85536b158d661da1ef0895bb92f1d00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000001dcd6500000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000059682f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cdeadbeef774667629726ec1fabebcec0d9139bd1c8f72a23deadbeef0000000000000000000000001dcd650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").to_vec(), + }; + + let result = Envelope::try_from(&log); + assert_ok!(result.clone()); + let envelope = result.unwrap(); - Ok(Self { - gateway: log.address, - nonce: event.nonce, - fee: event.fee, - payload: event.payload.into(), - }) + assert_eq!(H160::from(hex!("b8ea8cb425d85536b158d661da1ef0895bb92f1d")), envelope.gateway); + assert_eq!(hex!("B8EA8cB425d85536b158d661da1ef0895Bb92F1D"), envelope.payload.origin); } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 7320bf9188a6..612a346b8d1b 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -221,13 +221,13 @@ pub mod pallet { ensure!(!Nonce::::get(envelope.nonce.into()), Error::::InvalidNonce); // Decode payload into `MessageV2` - let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) - .map_err(|_| Error::::InvalidPayload)?; + //let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) + // .map_err(|_| Error::::InvalidPayload)?; - let origin_account_location = Self::account_to_location(who)?; + //let origin_account_location = Self::account_to_location(who)?; - let (xcm, _relayer_reward) = - Self::do_convert(message, origin_account_location.clone())?; + //let (xcm, _relayer_reward) = + // Self::do_convert(message, origin_account_location.clone())?; // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: // T::RewardLeger::deposit(who, relayer_reward.into())?; @@ -242,8 +242,8 @@ pub mod pallet { // Set nonce flag to true Nonce::::set(envelope.nonce.into()); - let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; - Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); + //let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; + //Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); Ok(()) } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index db321576917e..78d2df524e14 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -2,13 +2,12 @@ // SPDX-FileCopyrightText: 2023 Snowfork use super::*; +use crate::{mock::*, Error}; use frame_support::{assert_noop, assert_ok}; use snowbridge_core::inbound::Proof; -use sp_keyring::AccountKeyring as Keyring; +use sp_keyring::sr25519::Keyring; use sp_runtime::DispatchError; -use crate::{mock::*, Error}; - #[test] fn test_submit_with_invalid_gateway() { new_tester().execute_with(|| { From 5772b9dbde8f88718ec5c6409f444d6e5b4e4e03 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Thu, 23 Jan 2025 10:57:06 +0100 Subject: [PATCH 308/340] [pallet-revive] fee estimation fixes (#7281) - Fix the EVM fee cost estimation. The estimation shown in EVM wallet was using Native instead of EVM decimals - Remove the precise code length estimation in dry run call. Over-estimating is fine, since extra gas is refunded anyway. - Ensure that the estimated fee calculated from gas_price x gas use the encoded weight & deposit limit instead of the exact one calculated by the dry-run. Else we can end up with a fee that is lower than the actual fee paid by the user --------- Co-authored-by: command-bot <> --- .../assets/asset-hub-westend/src/lib.rs | 6 +- prdoc/pr_7281.prdoc | 13 ++ substrate/bin/node/runtime/src/lib.rs | 4 + .../rpc/examples/js/src/build-contracts.ts | 2 +- .../rpc/examples/js/src/geth-diff.test.ts | 162 +++++++++--------- .../frame/revive/rpc/examples/js/src/util.ts | 11 +- .../frame/revive/rpc/revive_chain.metadata | Bin 661585 -> 671115 bytes substrate/frame/revive/rpc/src/client.rs | 17 +- .../frame/revive/src/benchmarking/mod.rs | 6 +- substrate/frame/revive/src/evm/gas_encoder.rs | 11 ++ substrate/frame/revive/src/evm/runtime.rs | 64 +++---- substrate/frame/revive/src/exec.rs | 6 +- substrate/frame/revive/src/lib.rs | 129 ++++++++------ substrate/frame/revive/src/primitives.rs | 8 + 14 files changed, 249 insertions(+), 190 deletions(-) create mode 100644 prdoc/pr_7281.prdoc diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index f56c4568f2d1..ecbe1fb0e62a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -129,7 +129,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: alloc::borrow::Cow::Borrowed("westmint"), impl_name: alloc::borrow::Cow::Borrowed("westmint"), authoring_version: 1, - spec_version: 1_017_005, + spec_version: 1_017_006, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 16, @@ -2189,6 +2189,10 @@ impl_runtime_apis! { Revive::evm_balance(&address) } + fn block_gas_limit() -> U256 { + Revive::evm_block_gas_limit() + } + fn nonce(address: H160) -> Nonce { let account = ::AddressMapper::to_account_id(&address); System::account_nonce(account) diff --git a/prdoc/pr_7281.prdoc b/prdoc/pr_7281.prdoc new file mode 100644 index 000000000000..33e04c419bad --- /dev/null +++ b/prdoc/pr_7281.prdoc @@ -0,0 +1,13 @@ +title: '[pallet-revive] fix eth fee estimation' +doc: +- audience: Runtime Dev + description: |- + Fix EVM fee cost estimation. + The current estimation was shown in Native and not EVM decimal currency. +crates: +- name: asset-hub-westend-runtime + bump: minor +- name: pallet-revive-eth-rpc + bump: minor +- name: pallet-revive + bump: minor diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 26f4dacf9a1e..220929fdfd83 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -3301,6 +3301,10 @@ impl_runtime_apis! { Revive::evm_balance(&address) } + fn block_gas_limit() -> U256 { + Revive::evm_block_gas_limit() + } + fn nonce(address: H160) -> Nonce { let account = ::AddressMapper::to_account_id(&address); System::account_nonce(account) diff --git a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts index f26f275ec3d5..b162b8be0adf 100644 --- a/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts +++ b/substrate/frame/revive/rpc/examples/js/src/build-contracts.ts @@ -55,7 +55,7 @@ for (const file of input) { } console.log('Compiling with revive...') - const reviveOut = await compile(input, { bin: 'resolc' }) + const reviveOut = await compile(input) for (const contracts of Object.values(reviveOut.contracts)) { for (const [name, contract] of Object.entries(contracts)) { diff --git a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts index 86b8ec50bd63..2a4ff2edcdf5 100644 --- a/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts +++ b/substrate/frame/revive/rpc/examples/js/src/geth-diff.test.ts @@ -12,62 +12,64 @@ import { ErrorsAbi } from '../abi/Errors' import { FlipperCallerAbi } from '../abi/FlipperCaller' import { FlipperAbi } from '../abi/Flipper' import { Subprocess, spawn } from 'bun' +import { fail } from 'node:assert' const procs: Subprocess[] = [] -beforeAll(async () => { - if (!process.env.USE_LIVE_SERVERS) { - procs.push( - // Run geth on port 8546 - await (async () => { - killProcessOnPort(8546) - const proc = spawn( - 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( - ' ' - ), - { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } - ) +if (!process.env.USE_LIVE_SERVERS) { + procs.push( + // Run geth on port 8546 + await (async () => { + killProcessOnPort(8546) + console.log('Starting geth') + const proc = spawn( + 'geth --http --http.api web3,eth,debug,personal,net --http.port 8546 --dev --verbosity 0'.split( + ' ' + ), + { stdout: Bun.file('/tmp/geth.out.log'), stderr: Bun.file('/tmp/geth.err.log') } + ) - await waitForHealth('http://localhost:8546').catch() - return proc - })(), - //Run the substate node - (() => { - killProcessOnPort(9944) - return spawn( - [ - './target/debug/substrate-node', - '--dev', - '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', - ], - { - stdout: Bun.file('/tmp/kitchensink.out.log'), - stderr: Bun.file('/tmp/kitchensink.err.log'), - cwd: polkadotSdkPath, - } - ) - })(), - // Run eth-rpc on 8545 - await (async () => { - killProcessOnPort(8545) - const proc = spawn( - [ - './target/debug/eth-rpc', - '--dev', - '--node-rpc-url=ws://localhost:9944', - '-l=rpc-metrics=debug,eth-rpc=debug', - ], - { - stdout: Bun.file('/tmp/eth-rpc.out.log'), - stderr: Bun.file('/tmp/eth-rpc.err.log'), - cwd: polkadotSdkPath, - } - ) - await waitForHealth('http://localhost:8545').catch() - return proc - })() - ) - } -}) + await waitForHealth('http://localhost:8546').catch() + return proc + })(), + //Run the substate node + (() => { + killProcessOnPort(9944) + console.log('Starting substrate node') + return spawn( + [ + './target/debug/substrate-node', + '--dev', + '-l=error,evm=debug,sc_rpc_server=info,runtime::revive=debug', + ], + { + stdout: Bun.file('/tmp/kitchensink.out.log'), + stderr: Bun.file('/tmp/kitchensink.err.log'), + cwd: polkadotSdkPath, + } + ) + })(), + // Run eth-rpc on 8545 + await (async () => { + killProcessOnPort(8545) + console.log('Starting eth-rpc') + const proc = spawn( + [ + './target/debug/eth-rpc', + '--dev', + '--node-rpc-url=ws://localhost:9944', + '-l=rpc-metrics=debug,eth-rpc=debug', + ], + { + stdout: Bun.file('/tmp/eth-rpc.out.log'), + stderr: Bun.file('/tmp/eth-rpc.err.log'), + cwd: polkadotSdkPath, + } + ) + await waitForHealth('http://localhost:8545').catch() + return proc + })() + ) +} afterEach(() => { jsonRpcErrors.length = 0 @@ -88,7 +90,7 @@ for (const env of envs) { { const hash = await env.serverWallet.deployContract({ abi: ErrorsAbi, - bytecode: getByteCode('errors', env.evm), + bytecode: getByteCode('Errors', env.evm), }) const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) if (!deployReceipt.contractAddress) @@ -99,7 +101,7 @@ for (const env of envs) { { const hash = await env.serverWallet.deployContract({ abi: FlipperAbi, - bytecode: getByteCode('flipper', env.evm), + bytecode: getByteCode('Flipper', env.evm), }) const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) if (!deployReceipt.contractAddress) @@ -111,7 +113,7 @@ for (const env of envs) { const hash = await env.serverWallet.deployContract({ abi: FlipperCallerAbi, args: [flipperAddr], - bytecode: getByteCode('flipperCaller', env.evm), + bytecode: getByteCode('FlipperCaller', env.evm), }) const deployReceipt = await env.serverWallet.waitForTransactionReceipt({ hash }) if (!deployReceipt.contractAddress) @@ -121,13 +123,13 @@ for (const env of envs) { }) test('triggerAssertError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerAssertError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -139,13 +141,13 @@ for (const env of envs) { }) test('triggerRevertError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerRevertError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -157,13 +159,13 @@ for (const env of envs) { }) test('triggerDivisionByZero', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerDivisionByZero', }) + expect.assertions(3) } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -177,13 +179,13 @@ for (const env of envs) { }) test('triggerOutOfBoundsError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerOutOfBoundsError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -197,13 +199,13 @@ for (const env of envs) { }) test('triggerCustomError', async () => { - expect.assertions(3) try { await env.accountWallet.readContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'triggerCustomError', }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -215,15 +217,15 @@ for (const env of envs) { }) test('eth_call (not enough funds)', async () => { - expect.assertions(3) try { - await env.accountWallet.simulateContract({ + await env.emptyWallet.simulateContract({ address: errorsAddr, abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -233,12 +235,15 @@ for (const env of envs) { }) test('eth_call transfer (not enough funds)', async () => { - expect.assertions(3) + const value = parseEther('10') + const balance = await env.emptyWallet.getBalance(env.emptyWallet.account) + expect(balance, 'Balance should be less than 10').toBeLessThan(value) try { - await env.accountWallet.sendTransaction({ + await env.emptyWallet.sendTransaction({ to: '0x75E480dB528101a381Ce68544611C169Ad7EB342', - value: parseEther('10'), + value, }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -248,15 +253,15 @@ for (const env of envs) { }) test('eth_estimate (not enough funds)', async () => { - expect.assertions(3) try { - await env.accountWallet.estimateContractGas({ + await env.emptyWallet.estimateContractGas({ address: errorsAddr, abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -266,15 +271,15 @@ for (const env of envs) { }) test('eth_estimate call caller (not enough funds)', async () => { - expect.assertions(3) try { - await env.accountWallet.estimateContractGas({ + await env.emptyWallet.estimateContractGas({ address: errorsAddr, abi: ErrorsAbi, functionName: 'valueMatch', value: parseEther('10'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -284,7 +289,6 @@ for (const env of envs) { }) test('eth_estimate (revert)', async () => { - expect.assertions(3) try { await env.serverWallet.estimateContractGas({ address: errorsAddr, @@ -293,6 +297,7 @@ for (const env of envs) { value: parseEther('11'), args: [parseEther('10')], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(3) @@ -313,17 +318,16 @@ for (const env of envs) { }) test('eth_estimate (not enough funds to cover gas specified)', async () => { - expect.assertions(4) + let balance = await env.serverWallet.getBalance(env.emptyWallet.account) + expect(balance).toBe(0n) try { - let balance = await env.serverWallet.getBalance(env.accountWallet.account) - expect(balance).toBe(0n) - - await env.accountWallet.estimateContractGas({ + await env.emptyWallet.estimateContractGas({ address: errorsAddr, abi: ErrorsAbi, functionName: 'setState', args: [true], }) + fail('Expect call to fail') } catch (err) { const lastJsonRpcError = jsonRpcErrors.pop() expect(lastJsonRpcError?.code).toBe(-32000) @@ -333,7 +337,7 @@ for (const env of envs) { }) test('eth_estimate (no gas specified)', async () => { - let balance = await env.serverWallet.getBalance(env.accountWallet.account) + let balance = await env.serverWallet.getBalance(env.emptyWallet.account) expect(balance).toBe(0n) const data = encodeFunctionData({ @@ -342,12 +346,12 @@ for (const env of envs) { args: [true], }) - await env.accountWallet.request({ + await env.emptyWallet.request({ method: 'eth_estimateGas', params: [ { data, - from: env.accountWallet.account.address, + from: env.emptyWallet.account.address, to: errorsAddr, }, ], diff --git a/substrate/frame/revive/rpc/examples/js/src/util.ts b/substrate/frame/revive/rpc/examples/js/src/util.ts index bdc64eea1ef5..2991bdfe6367 100644 --- a/substrate/frame/revive/rpc/examples/js/src/util.ts +++ b/substrate/frame/revive/rpc/examples/js/src/util.ts @@ -85,7 +85,16 @@ export async function createEnv(name: 'geth' | 'kitchensink') { chain, }).extend(publicActions) - return { serverWallet, accountWallet, evm: name == 'geth' } + const emptyWallet = createWalletClient({ + account: privateKeyToAccount( + '0x4450c571bae82da0528ecf76fcf7079e12ecc46dc873c9cacb6db8b75ed22f41', + { nonceManager } + ), + transport, + chain, + }).extend(publicActions) + + return { serverWallet, emptyWallet, accountWallet, evm: name == 'geth' } } export function wait(ms: number) { diff --git a/substrate/frame/revive/rpc/revive_chain.metadata b/substrate/frame/revive/rpc/revive_chain.metadata index a03c95b4944f663225642b1678ef66aaccec3fb5..ff365892a265e1fd9b59f6811ea1c59642b65d91 100644 GIT binary patch delta 25763 zcmbWg4SZC^)jxjc?%g~0?n`z92_%rf28j@bB~ieD0SOQ!%1Z(PMTyCh-H?@JH`(0? zQ8Ca;3o2^d!ia)`6_r*Lo|axwQBb4OM_OY=#frdFQBhITih>sXf9Kx2$%f$b^#Aj- z=Dy9GnK^HB=A1J_$ByB5{xF<21Y$myGj%8Z+(L5V|FX;?*?*WUeW{WK@q&~BQW>vF zDI&i3=9KYbfU+xyk{<6&$q`#qR9izS}j3tct(qnorio zN4Y1Fb@4{`<>EHV+!iGx-tHbuw#SdV^QB#s{E_`sW6h3_P0c2|;}xm-;vUMLwxEFr zQ?p2W{F&4VO{KCDkZ>5O07r--QWb$wrTBsHGh_kt0V zrUvQ*i+! zN9@n!t@lA8jdSkav3P%PLGm?+E@M(Z`B(;|Vu&4Gv{WGf__B-f2r z)f(U8#m%BoL};nHgLh@x@rd57vRLg|*>GO`vLNFjnV}_?Bjn{K>w04r_L8QN=_dO_@i` z@p~2-A3P0u^?=_~8wodf>ccflqMlgTQx~rHM@M)#B#$rD(p5ZG=Zkr2!p-%5PbeJo zEDCt)0`>kG!KepK+iL=P4c<08w%2;WAKoeY;WaW(8~Ks7h>&?~pF+lI(`AyDcwp-( ziI^YQrcbQ58TSt*%5G4}3Xznv0SAkid}Cb0{;Dvd%nm11n1>avy^zBRt*Y z(Cnkg$WnT%dr>&#S6HfD%+Rh`MxHI|Gszr#RgH;CDef-p-#RGkJp-qKsHZUyX$VH6 z!EmTP5RKNiczhlZCt7ztYS!Htg?b!vQ}B#e*Hp)CqTW1c`QP#^Q4;Rc^S zkRYDNhv()RP!FRBV+f1t171%Bx`gief+5Jj?FQhk9veKs_Kp>;S@i+`;y}bJyuxeL z1_B)XQeW^UFiMcsKVi*gmt11K^4HhwBKTj#{}%i&Y1izs{?gW3mdxx4LCG*)x~E`F zl2jp(p(6|wR5gaf^=YI??~T98kglMAGbx%JURG2we!MPDrT&QmDKdoae=SoHxkzV% zDo9133IwcL#JM_y3{HU@5S~1s|vj z*L#H#Wxj|H$}kX#j(?t&zaXWzP6>xT||YJzgi5 zJ~U1sh>cOg6ExU@H*@*dsCdo*&r{JKjK(6t)Mq)2ksL+)45J19?$jFQ-je)U#zCC0-6$Ztp!4>D?K;)B0(Q|7P!e*-^_J1 z#6ZVaU|FE1IR@eLaMkLqZlif{Cw1zM3T-AMg9L?q+Jj z4UL#e(2Op()w9B(K=p_f9=!yYXW$GXgo%UG%opI*YRdG^Z8^+fUS5B zt&BKgRd_23$@JY3;ptq>h2ApxMmozp4_yjt2bR zqB>s$TEELMU|a0gblL;UmW*4c8->u@ntC6Y+R!jvqVoTSzyAzy8)Gd6 z=?wnpi4)8;o~3m`Oe?@nqK2t~k{SPe2pYVMn@yZt=9F-bhSm|*>WA_@|M#96TuQ5( z8TJQj`IM})fGMP0oV~(c!#av##_pDNUDQ38MD@^DOT|P0R zws0E&!)^c|uxJ=hx;F6^c8x|as`uVHl=nWK^J@AHR!3RS{uu(FXy-p}rlPAELm3;h znckXxIBzX!B5Re?R?LNlSy~t9o>n~3I^A5;%k@T$QQc7bJ#b?vQZV*^-sNl)H5L8< zOpahe6sioN)BD_|=2~gmQOu#RorADq10Kw-bzxnDo0ZSey1@`QE}@T1L(8lKvT)0Y zPm492;vs19Xw(;J@eDV`-7r|W`ib~L(b@nk=T<)^KInU$MZ%m*#u1&58Uf1Z_eTIC zZ-fWaqOM>g;hTbfNGlhBnxt+=!{CunGgP!c5DDI-vj(4H0S`Lan4N;KnT#$*0!__m z)bGu&anuJ;uThr=iLHFk+P787D4Jrt?HcGZ!>H(?KJ{Lj+*D2yHKMU_#J3o_GJ?en zpX0dI4ud0H6V#_v^E)~ZMQqG$;HQD@YkNO4u&cI-iy|u z;ylWZd>{>A8GEg>#RYWz>ry|~y4SjjR5I&6>lV>xeAiyuXT4tJYs1a+=|DFA73)S4 zWbeLWEzS>8DPv9`+FTz~qzckNr4rIiiK5J2A_)ytz<-XC2o_JxxF>?pz^1oj4bj9_ zwOey>`8~fhvyOJF*Nx%|848YzRRu$};Rfb<)tWR?;O zSTnSSx2%Jm;ws9HJtt-Enn6}cWR1B(S(Cg%F&>N+ig~ZEQ1ttUh*=gZl$oMs4OQ5_ zVrwR={M>rAxR$cL#nv1)<5g>lcIb0!nDlR>Wcd7WEHEcf6OQ-=k`{wa(6UCWI%(}g zENzMEYrnJ>kag_EFRiU)gLcJN)~O<{w|83C^XtHGtZjsB(zc(nK269Lt@1l-JFX1Z zyV=e&)~RFz`}T}=uWbX6CMlaY3!B*9@2#WyY{9d*l?wRr_Zh-Ajr?HUD~j7Gn^X;B z{^(ijJ!GfW_@niZB<`fF?KP=CTV#=MBfHo!i|iA3V|l-h4q!J(awp4G<$>%*tNa1k zr(G$_ABtojE3?W2*d$fHfV4BeDqly4FO5$FsR<4W!dF^#CZ^p773pv;Sgm_G3x+rfkR#_lf&A+ zKJqg%ImTuUkSDTdGUavTn1=fzCpoU&IZXaUAt$tGzD%r+WBTA#s#4)NTQNo+mUaSf z#FJFO4`?BD0}(pefk&+a8KB$UQr5giKNlVcaV!b6oMN40;He;-Oqj9-{eLGfO zO-`}a%j81&3?+w15i5SodI39nnLMAIVeZGRBbmQYo^#RJ9z!NduOSmfXLO>lqlNM~ z*{YCZNx){aug1z&nahP0i`d7lBiQTX$7wG9^{VnzV4x2o)QO1gF*?#;=c8n?7p|G zeebL9f2kndI;}b6$EqRWGx03qQ?U6Mj(9zD39=+oUR2&ZDfSRv%oKQv`EP*T~}K z!-w?GR2=79-Li6roK~5k+wJJOY4r&tpD$mHX`s|E-CDtwX~MkZo)q22;>$S9@=-6z z(ygPQ-%T>gU?-u{ zK5wSR$H>#9uUERaci)1GLzwaODU;jeSlT28#Z@jr$Y>yt2rcJmD|6>(}F)Th!UP#8VulZ#Hn|Y;tA=@`y9#vSZ2$*|9d|36x3gJ%H5nT(`u2(~` zV1Zc^jXRN^hd1(6G~rt_cJe*Tp6ZLWxoOzUgvW zcBw)t$^wyuTW<ocT)Si;N+rJIK%=TxL}v z_*L~mr?ZzzVbIKD&a32!dGopvuz(`~e+2!HSg8p3!CqS_xdorXT%$<3w&p7NHx?3L zgJ#JgF{m(~ou)JQI(g{u21T07?Zzf}yygdLco;-z(CaT3^YIqg$78czC7%tREz_cw zq@pdnfFvwek`30Cy{)&ED2HXrN>*7eXR+I6%l*kJwtluejI3oZ%$7%xHum{!d|6m7 zXS1PY^1{Ar6p}wbQacy3tPd+O7{6;+yiC3Z6?7Q1T%LzoxLyAq$3ovF1}$X5_?XA;`dcbNe9 zBmvyTR?L?(*oE`u87aFJk~IhG%y1|w2)p@*@wPp@on4Bs&*)Toy9r}|5{z~OM$R?z zLfd{`zFiRx80GtQnB`N|Zc=q9Z1xRue&2&img&1E8CdNpLH|MKzE&O}9#q(h%j9%6 z=rP#ErPs=i%L=o&8K%eh4k@J0lvr*N;obQ0(-CbkdM?<7tSRsx*97Y&;V|=E3zPjA zzZ9{%&rq3tf30jM$Cre?i#iv`L!~nc z*|?rb1KH^7WDgby98#Z4CkjP^XRfc7&l*_Sz+(UHb@D}`Rb_v@P9D^Hp2(!Cc7+;E z<1^sbBa`R$zgLjbiEvr6hDyXw0;(cr5AuxDxNE$z*NU%Ts}ycdnO512^`Ca)H#TI2z-{4zT({)H$WFonJu~Q|^-} zdxXC^qp%;>OGDY$3*{l=*(8v_Ou1FgWZM?XDXg_p?vr9w9iz?RL;^RhzaC`%W2Jm` ztE&FY5(Wq|!%RI#G}YvY?nMC~n^QfnEgwBEh9xk?;opoo25Bd3uG z>-}<>vsi@~XWRsrNF-XyD*f^${1w+r`6a4QVpe$bIk~T`lsAWuGmV$S%1uPiOG0$H zUU!GvEzIMk@wk9Z3dpa+A%)9u_VP1wsy4Y+rV{b7t8SD#qyWdP?=JA>=p}L=F<`d% z<6X2bd{lQWkw-`+D$)>U?jV`$;1ck80~=5eiEdKK2y;?w9y=P-NfWE9mve2+ybFq3 z5Ly_kmsi@C>s@jSZp;c7G|0DOLU^J0SAP?A?4cdsG2Gj)?;nVV{22t`-XI><=yS#bm#Bq(!bF;sJwV zmv)%!a4?A-I(Q|AdyFUu2RTGMAJX9*Rx^adNz4U>|Maa;I?}E3BDTh7BKzBY@+Ep5Tb@y8wJ_57%XC=J=R&i?xr~VqqN_)E=A*e)Hp!ECr$|?m(b9YvBa*NN=+Xa( zYi$C4AkXLwp^g2|7C8WCZdc=Q&)xM1tOqz{ACU*cZ+r9+c>td9GK=;9t^6cj>Gwx} zD-UCl$K*6N@KJdX*~Z2{3JY>OTl^?Yke%$FN9Bp|%l_q2plLVjyH)m*J#6w;eA~vp z#Kp>W^mgMm1GhVk+tIk)rQgcsSeU?S(+lV0c8_rj-6iZ}(Z}SA#daI!_kr4TkICLt zalehZE5tPRlLo);0Rv0ixXvC&j47|fmSnMUW*J9>gY4;NquNc3WXB}I^Dua1CN}kA#&ko_U9d9p8n{AdRaL*A=(HOk((T>D!Mh|^b*jCW-!>Hx zVfm-pS>v;i(Hozam#FD@CpzsyrqPDZdD&)x@q6UCVvhOn*dF;xlFPo_Bli@Jyq^xiooj~_E_*>Pp`?Jl&@PvgG0gp{oR>GIN597P?$+&e6^KrdtW}IZsQGXu|uqX|97a-otpatc^MIR+2ecf&1AnlEFXp{Soxv67OKE? zL@vQ)-VtboeQfy=dCY)zz#lO$#N&@hQ3&{9dc}e@O9HX|?2RMPCI^`PsC<>!Ztpoq zT)LlQRuS6m!hs(B-QT;vyeRtHkwl(@THZ1Fe$jqNhjQ4CsaOA+e#}J3@g#&CV|_l7 z+xi~o13hLJPUv+`qRvU9PN!MtR8pNz_V176+tgFMPN!Wsqy6%@Tq@en>UE)+iG#iS ziTpS@%vwLi(!=VAU&jV$FMo=ON`yhIoRqgqP6xSn{T1{=(dp2Roy5G3`LpwkJVi`* zXiZE36DoPni3CL zgn3#4r62LRwx5kYgt>N?jc&I4ct;d>z=0W62eI4K>%k-tK@MU!5gN3o?DQ1^+2cji z&!-%kjZZBOLr|6{L1|$>Iq5a(at^G;0Wqvfp|8O*#wDE!djeli-PN!=ym+JRR`q5tw-l`XL zzwRT#HXURKChqO}UB;8bPR-e$&XO>t7G%>j);N&vf-vd##cXVWe^C zfsi@jdiZPh7-eEvv;cC3@&nnvEIKf?-7&Pdu@M;qzWONH?+_ep=P5aT#86-2<$gH7 zx=KL(ni&u64w5z1$eduF!5BwLHmyVe6?pE)V3l0Rj$~7N`T<98)8qjADVz51-+>_s z{Ev9hA>hYl_jTcr(a+*Rv<{M>kL!g&w7+=F!Kwz)44A-Q52BagqJO$@&g;6} zuOB8IL^z=XIz&!7Ad7mLj=}Us%)*I|xtPyG2lY7xQPAxpr^m&nVQHh9pmg4p#rB8-}&u8_EsUi&o%~C#R8`= z&e`qWh`;fzs#~4f#&PsX5z<^*M7Kb2KQE$-loDPeUnphsCeVC{>aQlyDqKFEKz){S zbZhV=+K*LFqztp*>50^9nU{EZ#TE20S)gT4qEiVZ_Ske7;I|ahSt$7DVmbozlj(3=woIm1^mrP^o^GL;=e&Aw3Mi9YXBd;F;+4;6VC+;{W;Ebok58rdNC79Q zXPviz$LE#kD`Ye?oVjkLDrV#dOK2WSx0lc%u(m%fp%=mzt9NwxGN7!W+mMCp@zHAl|zY11exYT}fYr5%A)4+Rw4pNz#yGb`#XVT3+byliHlbGcC9* z;4d9JS|FZfRpHuNM0L-3-EzLySf#c(5H=5aRHgh1Unf-b*8=#~Q@MiGXp@Y-<%%yi($U*Jl z`E(t@+JbjfU$lycop4lUX?Oox{%;YgjDN^3mklvKo&@?h2Usp+CBO4}$jM1waw^wl zyh6~da4M<7DPAEc!??J8A^lv22HWR{$M=pJ+A5M1X7|$~e$^umH~4A6rPh?*`q`S& zbLr?dmyV^dR0K7JHPt|<|L&(lL}v>7$xo+=nJMhb0L|ym_&9*Y19Y;On!?@<(7|GQ z3fp#2oX>AM0p;tUBJPqXVsZ zDTEN_0-FdpzuZl*{6$XYY*l1|E%yb z0d3zjn!$91r#J8)E4(#H3U8Rf0{QHPTd9(gn-uYwnW86}X|u1QyDXOS6f;rmg%#A3 zK2JvoL|0f~-np?BDr=)tL|=-LLw47%ql!}(+=vQ&I zHJ#16hX%21dgdNldhyDXWWBvArI+4bgWUNK*r$KRx(eXj2 zOxfI;(mf|{Mf>6@S=eTj>9gHr(Vbmo*iRcFSv4DIA>GLfZ%<+OZUVWV-9Vp*k$2=l z+D}{i04*obV0fs2-s11rRU7H#{_QDb(v(CDIFvB(+l>;tH`3{>=3y8U8JlR>en2On z8?@};-}|#|o9NZ#pi$QT5a!=QEcYQgWayz3axD+~1^iRO5xAG4OC|?nJPZ{>Tn0d_ zcC3>0KCFC*ZWNChLrXty4(&wO&;+Eh>E&*In9fsA@X;Ml5l(9FJxr(CpwY5_M{f~N zrLd0i@`c*o-@!J3CB}IRDs1GF^fw}LvGS+T)AygGr$O?+ZKs8z74O<0`KtcSCpul) zAD^Nh^(jns{d~+NE!NT)hz{?LuJHXmu$XxU7u!tbi^d4)|7Z0P#XQ+;hQ9AeukU85 zYpofrMX@k)CA;_Nm6CIIwULUhWcse1Z?L?3mmAI9)(8<*>Dgnwz6+V>>tieTV|T}u z(vXxIj`04G{Nc00v0`KA3G~_%-07W2HZeDA{aos}=`#mManJ%m>O%-SSFp1OkA~+Q zX~w#zzO`rmxtWMnT^hpbB8WgH-^4N%YkD(mgkbgC#!FAqTN4&fd5PZ|^?iRiI~L^NUL zSlX}ENZAAQZfqm&fCoK}=JjwYan=&7(hA%o`MDd_=VBor1xyTtBZ4epzXvJ+l{yxX z`)#)D^-T8pxk3mCtyEgglU9MBNTgX$YUR7Skgpzy@U1)GAM8jdoEO6KuO2BvdVZsz zNFQ{)LC1Bz^yqwvTp%NpJ!!K6kF}Ux)sa#MHmL|3kXlvg$;2?~K-dsRL^3>J26Q)D z_ZD6gG&YA$^98ZVPq0Zlj5|)B#KuuG9nVOwlY=qZ@7$7aMtJ5VELx|^Qj=Ee$|r&Z0;ciACLTu;Gj72q#O^j07<42@{y z=0;cy<>35s3?QKrt|B4I zoQTkxuS08g3A&ys=8MGw4UJ$;E-~2184iuq)hg;331Cx`pcs6|H74M+{v05_gOOCo2h$t3JFutp>>n%Tf3;P?}~}=J;(Z71Eu#zOf8T>o~6c> z7oUN+Hohqk(dP@FnI36u&Ga@Vr{`hY-Q+-N2mq!v>+Th+krmk^$Bu8#OXW(!+>KhR z7iGR*%4w~#v>6-Hn`>)>HSnLu%miuZ3ci&Rn>=Hf^^6^X-l0arQ<`G}mG0-$VUJcY zpYb;Iy;ohEi$)uh70;^V^%Q9@uV*kY->-E8GCl)lYN)lRc}s02iB(YZpE zc5n!d;1H-@zMqgQMV(@aEs@xiXtsc80D{h~1+Je->@zb#j7cB+R0OFMY*$zj4$s8) z8Y4jhE??h;vN?fI`a@Rc90+-JfbiqBm_itFA{be6KM-zIY;qx?;+}Wt zRfy@l{|?QYztWYwDp~I8wJKSTYJ63Ka&x+N7~PHyMr%-3-(Yk*fSdb^))4lcHF2cxr@B}~eE_DoS10v>ymW0AfLlu1Ym|=nO2G(k*15MV(>t4X9aCbX*m5x2Mm{GeA?N`>&?ynBew>ATKF@=HTp z=0mZmz8HwN&FJ0@@6)mQ+k1>_rz?42J2~8*Bim_I{rG*l10L?C4`>d1=I^v1e|HZs zdpMYV3Hi7@nSger%<_2u^dGiDK{r0o(KcKmJ2YQgDy*F9f4L}`i z`e7OcFFb#k)?$Hl!H3juJWXSN`H)h}ItA#TgWRY?F4pG@c-{`P0Y_*NoI1;o(n0Li zPvCw$&dQI|2VBQpM4H1l^YbG(qNiBu$21$?azDYs^8_zm$owDET=xD^Iu0u$z5d@n zrZdg_9iCo$rgulr7#-you?&eAdbRz>=;b1Dv)hi+Y@-iW_WmbylcQG$Mp@m-jHkMR z**cT)R37ay=~JiURW(7D{)2kisZZf2zv~|~bCk0OjMQWpsl8yNnlPq(iIpq;h)yj? z?@|4Pj4oul%|4vacM%!8>oNfg#LYATZ1{-I6|>#!%a7=QfjMsCxgHr#mwQH6B7qDg z^(|QWLav+b`xuVKiN|S5A4Qr&^4tk?26mg9)gPzDVu71IeVh(dGa|YOj&ZY-c(e$4 zdgTG^!J~A5IMU6uqtwH%t3Rff@k)SPGipqa?&l?sKCjp4$C(ftKA{uD3Fr2$`2X%( zNuqDXCM3|JKl|)cY8TPl?>?nNyd^z4RGi$Q;$9spWjnusi=pZS;zBc7+X*`Pss(Or z|AWe#+=3%NaFB(krVeLtU=O>`P4Z1OJMWS)WAVw&H@F1cq{w_-Z0tqF7sp~>)W@m>kOb-SzCx14DcD?y*=o62?%9*{5?cYBeRKItwwQ(gMf+zZWPMc< zd_&e(nbjbt6$NX0bWIo0wTZ4Ji|AUTYirHA5Yc|@^p{YJZEVL^n06+AMH?{f;QlI1 zJ3o9yFUi}`14)~DBWaTXbCUs~z*EU@{{Z2_o}n6N8xtP5?w8{1ME3X@K=(u-`(LB_TyGO;1&+Jm8sFjs)mM0 zRbwRB5M=HD!Zg^yzWNvD#u9enH?-J$utzJalN(vxtBpf!-8Zzf?_tQ}Tx>gsY>^Sf8$pli4Cw2KG?jdFTm3YF95Xb$ez#Au-{4KqvwG%+PW%N{U zdYv+A^NV=KE#QZN7q%l6u}AZKEMR%VkV+a0-=c!XNosO4yr7{91_+70y=FmcD%!!l znrdV~+rOhO2Qnh(wqS|ZAPGK}`yHn70Gsn27-uWHjbFC2-+f1Mo)dfjJH(hOS@vlJ zZ+5Xor>PfqNZV;1FtNoa$?5F9)0kg!Y&eLlA6rY5y!g}DEHJYBnWy-t!kXTEhL(sW zwzv;sb@mKJIwBkUJ(_4>wckU3H?jM_rVTS2LiP%67*%~S+v{02K|VFYgsA3>}0VYAxl;#+wvpkPNy?| zg(HXk@FQH!*)07hI$F$g#&eJ$R-WkO$l|8M8I6}W!xTsPKumJZ5HobuN5 zNH@%a+P6Ffk>qMFn{tCl^0bgcsUyfMde^C3PR3|`QzLn0$p zsj`$SrR?TZC5QO6jj2i+Q39vuy>m4FQm6K0AEkg`()_TmvH((3n5JA!g4*qA%I__> z=4U7m5(I|c$i&y>+Jys@EJjx_jN@T0{ z-35xZ=u*4`I$Mr5%wjEn~iN1VhnIU!#PHrCDv!Zd{-|EaHuAp^|5@ zELT@*W2=;OKHM4A%6IrM)u%j8$SUoNMT(yXp2};LDF}eBt5x0rfKAN5SXqnG|6Hs@ zkT(>lQ^s1`)UE0YmSa^s+TJ>4HX++I_l?Ri2ic|l^CqP~A-gr(GUYD>I5^azltDFI zbhC0BP_gZ1Wj;Cdr(1w6>oHY=AG7lg)ytl^Rher!slLZSj@3plSC$jI)g}q<1;Bq@ zfNgo5WU#kyQ;v(OO?$3Yc|n0DW4EkUJYuSiZCb4?7t?JlYYoPetu0)u3?Xdnok}xQ z9NT;?^xaE$Dmh}VSu!uVBqPvN-`uTSfE0#|dz2eMfu;8-X`*GE?RMUq9PQ~gWuFXP z*mS>gnOuxcNw2e&cPMG>rTdkIV8Fo}lx*mw2^*AgpwrR~$}JXBuKjDHk}4v5+xoE5 zk60GiBv$aSvXlLFvvP-}GV$bmL|LQwILJ3Wb6MjfN;(_$h@!IR9|5s9v8vw!SwZbj zzg65K)Z1%Ym19u3+a813XtuG7UsSKeIUa)^SLRFB|q7tzU z`S$zNJ6L!-I>BCdtSK|m&4C1GqB>kXpa)Zg7V=#${koxUi5-;3OdR2qVf&t z)YA7V9}W?NrYu0dY?*_V~u38ecM?aIF?8L71%P!13f zVdh_z>%sTi{tEuZV`c|{&t#8wfXgb`Hyz5QmViCTE_w^X(!g5YQf87S?X|Z65-d9S zZ6yqr4YIbkl`Da{LvJgWSfENB?GHyllmsblr#Gl(lU6hZxQ} z_U8|kZ=quM9f6wJ#Lga37DJ)~N0pF>jMBINN115FjMev3jEsWNWV z30sF9$8V5qD_Khfa?n0RNGpqkm$eY%qGa)>U?56}WEBpv|M^sT1?O%&enL4RVS;G; zQn^q%YbOrvxi6LbL_Pt|?o@8%DVWc7D&xge2m7v584H>B{!i zyCT>aCHh}Q{|g74b#$gfd-+>sn1q?`^Y1a2Ee#Cf86;r~(Ya`8{H8nS@Z!>T$MG`UAr8^G~rRkg?pP9N`5W6)6SX}222WpJwc zky7aZZ$IfVRj8*A8u4jY_EEkcPORrGhA!oE%lhk2W#F}p^Q46@0vSTpm&-Rw6!$sApeO98{2o`mxrm5G$k2&i~ zwGfwIU#Z>%Lk5{v>Oh{nUl=)L|bbus9Ocb8%Yz2s_jI)S0X z%5qS!ZH77wg0_1GPzR;wnWdIs8m*nB4u^KTXO_AbZIsPcZzUz#f!S&>g_}WPXSz1? z2K8?gi<6vM^=fdV7OGWmuzs&6h_Pmhhr6bL>7_VkuqfZsz$ZYy2ZKGX=s18r%`dlrD z&Xa8EGW9rKSGK6X040vLsFzF2X<8ZEQAN|VOK(f<7^k5Yf3 z-i%r0u3xAFVTEW<{8GKf;>3AL9wRfe)F0FwzgDMOfXnhb(Dh<=@(y(hDb)(@RDB|j zQqmj^>OG=~8OY7Hd(}Syoz-pX<*=tWwt;~bF#COKyVR_L|+w8qltdt7U|&)3*Ia&4oT-e_f~kLk-$IjV+^m(HeGknp!M^dwyK6 z4uwT<0aHH^$+q?;&*_ zQkb>+ht*l!f&1KMbu&n?a0>*XgWbAC{Tt+=>=CsJig3px>Rsr{gx{(%FFN&6^(C@L zqg&N-sn3R#X~Iy%k+5Qc2NFYR*KXXV&ajX}n)amn2JiaqPpOL_f1f<1*1?J@ds-a@ z&?}x+X9M(`PpgY8ds6nXF+0>dU_$KOf$kh&uAM3pZ`sM6>h)lT(*IJIfTX+rOMM2E z4*fxW-+7p(CDCTyA5|oAon-!9oaovoyVPPuI)y`(*oYU@f!Uqd>@!1{K6gp5aasV@ za5QS}p+a|6=6^wb(CSnKY@?B01hUVti(XWxi)T~V;K!9Bw&O)LohORC_M$o!$sw9^ zuUbry9im~@-AXK}u<-7BN{MJUzN~&BwK`p8LeYF2gjU;*wqWtdN>>nGKcF~P7YIf?)#ghqnXA1*D{R?Pehw!mhHoH!J2#@<65-qxr`y*<_DLLrQEDf#he)yQ25#k%q%02yH>*LIpK@kTVL0-z9YQW3xP!XYj>`Nrz3x-?^bjwPwkPU(f~`sCj36AX zJATbGG; zj0d)h_#K%B_KYPu4q7pfF6w5&5xjk!YxH9kjW9EB0)@@I1RQka6($&fx?k}w7*%)* z0OZu)+T2YHy>mc^F5yN}L?Y(RpTK@zGSYSK3GSrlf$j>nlJSFgVeIOxmySiAlukUw zx-!vOUk8mN(RsO6pw~>kIVUJ=WEL#OVYeZVoL%tY1@vesfPrLNJ#6X0tC<*wBu^e5wtnR%|&Wm9kp*5ZJUl!|5MbhW^>da7P* zCjM0d?vbC4{p&v0N;2B0hocqwk-9XF3n=2=UQEDT)cy%fsX^!;iPZ17e4VQIFo z7xt@vz3S0J(dI4MTkvagAe(WRlURc{GJWad$ zxcZqWAMOe3Bw=$uQ!8)+;uD|2KCzbCrBcFXr`nLh`-R$sRdmxyoEmZH3)KOu;o~o0 zU+Av}xz8GdDs)GE^|3;PH1~F@rR>v_FsYnwcJY_$FT`}W_Vkx(HJR=yGKPxCQ&l~1 z8xykKQp__az|Rgf)P`Qbg90ACfH!NA@h=NVS>`FKr4$jx+^^MVllvqhi1^7D>Ke0C zspH&6kVWs*N`f#RdUc(p)ZNUo|Eb<0uGD*Wt5Qn&QH;_WZP35e+sMUDE}Wwpa<{pK zso2>VX{o|d^+*+sA-656IGUHm%mVA&0y}bA9i$!qh8uue*sSkhC%3WDr(yZ7)1E%9 z&VvETtUsu~vVdsX-9M-|iWxiIMQE%Fk$>cwVMABARww8LPS6or(U0nbCW$MJH!87V zFMv+s2Z(l5vb7NDfJ-T1h|SX4biZLg;6cc-RPw@sE15qO4`I&9f&h{JXP2t3VUy9O)nha9$5 z5)A*LskYl%drX?rbgn+D5*o0IPbfl$<95%LI51dOBYY~u^kd9K@D5X;`11xbAJiflk31FQ(WL6;MtQgZJ!EX#{cOUV~kWVpi z#PC43Wl#v|b`KwvOwPG)8j-GLwe6*BboXZw-pj3$?}ioFVL?!QsT>ju<@T z#W4TYC^GNG&arO_m>xr8UR*ip=eJ3&Qby=NA816H|rjv$PIhR3i5etQ5Ck=DuJO-QI)d#}R_YDmsAD(^RuwO3l z0_s5XFC;Fe&mt9!a6Wg9 zUUD%B|CzHYEA>(Z)`^5o?$JTPKtDDNAtkUZpZde3j}3lUpZBpLj#R+fj}4=Oup=i! z%gd^&m%2(*Sz4IV}}sEq!DEH^Mnc^DZa|74g&wkmUeGVCQm+Zc&o zR95Yr?<_2M&)1qhUqeC zY)hbzV4=J&)18DgDf9L85FvZu9Y!Bic2Ihg_u)g*2)j9b-PDMj)AN1%q$UQZkB~^% zEU10(9&DBOGYS9Hx53f@2K)B0NToy2{j%K5;CP`S0vgP8J84k@EVNrsTHsqhB;Lwk z!%D+2Xcn&5ln9* z9k4u@=3vntLIa>9nEI0w@M$pp6X}8#A#_Z_3Dk+X<<+jTQa$QKC#r*ePFblFb-})% zti-98Iz7tJsUU_xV(G$1ogQE7CA78+>kOgFl~B5pl5S;13_U_g4@AY&k37XBB_)Ag zF_T^;c|83$Cufx4$&?t(JsKkf6HMxbxaoAb{~2tN&oT-BkVS+w5$PN(n@&>=eT=-7 z4m+MP_=7!*hQQm?X#%+nebeb`Pca#GXVEQkAA{BzBmi!nL1W1!D40Pv%9j{q&ZLPT zZlUu>Tplpf{u^i7kF-(k`@M)By@d`X1diWA@4;fqOuCL3;K)q+sw_B|pEC@FN3-cj zl>A?_=~uD^E&2@>?5=ahhGnA-%uE@dERpEKveN2GM`88VCigqedGPhERJh|;jON!` zSFGqU*k_JO9q*pCGQo1SS(5cHO>-Mv;qE6_Mk}XprAdV7z&e{IxSv`T>h18(s>JJf zbgnrb><(9|_xM^n(cW0~~r*(O;eswarj_LI^ zH68>nW)_bm0oR5^cy-y;2RE4z>TWJs^M8CB;+|K!91h+}{S0>JdS{{gucc#AM6WKQ zc_M_P#OsI@0ilcO?IZ^F-%bM}r5LU^Pb*ueDq|c+CBq*{`!UE;0z9*rE+I*W9J$|W-iE-kApaTKR=qF*SH#IkZ{r9)NnYfVE-9Hm8$ z>axmO4~&%R!D`5-=}{$&qQNL8)EOElM=G8&p$_+N7%oV2Epl*G;EsQ17G3;U!p=`P~g6fqK4n#C~6Q6&8Oy( z78_=&m?wglTNlv zjp}Uhsfoj~TZzB&K_T5s%zM<=W+^yY+RtImBIMFtn_svBp-Fa5IP2+S2hmX#zS?+9 z1wG28b}wj$7xc0ZXb1GJqA9^Ac=%LDv7@xmNrEvBb63#`{LLYFBOczZsCx%Ze zt0^sX6{9bQ{Vuu)T|XA-Fm5%9;}pzTjeeyYs#nwD=(QhPO~;a6_+T}ira!~UF6f_) z5hrC09jZUe$z$&FpTeMe4UHz}G$ez@PLk54=mm}y&YZF$3>SSI{<(&ZkS}qt71Kx= zX~vY$X-HEo5~jr8HhX!`+!b4gn~o zG4fgNF8ygJd{K&?{hS96lELT~0!zziv`VYKj20U&t9BCcycf%bBm zGRh!+%Qu5i`6iKIM+vg4I+$DqkR$^{SI|U%efpfT5*I2d`jL5MWyMu`Nx-rSI#MSI zp(QpDUrFuoLj@fyECRq7A9!g?A9=CyPTiGay# z=~6jDsFs@yAFQSRx|nMa>u7={PM~mu*J3tPS(OA+*U>R*ld7`G2|`NHI!-etsWh=Y z#e-;gs(1bqeCD63bvtagOA}ORe9VCJ>uEgXtf%`(rh@e;dUR8x>u4AHB(f1@ne83e z_JedVCbbnCX(CC0$2ZbtnDPF)k){vI6C}~=Fl)w-Lno65Ih$ylF<+&|?UGag8#d8; zlS89!m+X=g_&szpaly8G=zL5mPv1l1ZYmM_=&1`Uoz5IbIoHo3l711fqx-7BZrYDr zBP9GooQR?pD{`GccadICj~MH<_P2ti2DnmBA0b=SA`}kYOQ-s8Q$cqM3IEjbGzzIv zKuRZOv?2GQ0yPOBuVA*8e;+lG4T|$V`aO5w`)h2(9<|?oA>p4&vr{J>&}z*hO=^ZI z+c5sOD8<`oA(2}>Od8wfW%^Mcrnjk`%=T1Sl8&kv@%fmBr(Fa~?H=i2j+VXyE#nV= zVm8>fou*)-_ThHw!UEgd0y+grTc~ER>0X0JE_ZsmMD3)v%3Z?!{LCP@cPG6Cjrwmp z=^QlUS0AEL@)-d=57Agy@(^7*uq{qLC7>ddrNns$-QzRpZf%72e@Q*+knyS4L#@h9 zryx#^+Iwy~HSnwkm@1!^&Uw1D3#m^)SH^?zYm|~SQhhA=P_-_dm7au1-^Nj7LioQeTJrD zfph}3UEp#DTivD?w^|{LE=jnW%oM&{06@@E%TPY>Da8psbf88{Q z6hN**2Sfi0^pC{hsqcD`Ucp5hyo9N{(=)>4IyNkN9NI&$$EUfBB<-rHq|Zu>UW9aA zkl99qf=ZA_lKLZ87$y8e%^epk)p$_f+lJ;=d!2fn+8hq4FJru}hn$yb6ls8xmuVXoG0Uu8YnH7A7)%US-)@SCO;Q z$FB(Z@_o!(p6#M@Ef$mNyCl0XNq#2iDv*Pf$PZ~E!xVE?H_as>u&0|QB!rlJ#}{F` zVSEvY2gery-*(fRG4mbqDVkp#Ec=v3_{Et>JQ`40O?4G!shICW#rrg6Sc1vJ#U#^p zaVDt4;r3uD2`e%_rD(7Lsb1I&AJ|kF`5CSeCcv7{X*4|a86AZ61E0|nk)d`@HA$He z_BoyFff$cb?}^Vb#mIt=&*^MqwnhoHF9*VUaLBnDAqWW^Ek0%li-BVoarL|({&5kl{{R@jLt$gU{Ei0W&PM)s=oJ;z9K~VoHBnIBV!+D>12>Flf=xbDyJ5A zYH*Pu0AB<`o{=RJi&_tdCyXpF%+E~5Wfm5y$*dZp5R?R0YAcff7-V9TiJzzADicdV zW7d!yHnH8N5L9W*R#5XJJgvoMwg@h8RO4kvOk+lGJk#6YXn7boD9 z8`S!v(OWe@`#TOIZPRFU;qq>$Rtr8THJYLSGn{3&g;|x?N3eK3#?ZrKm_PK7Vr^)M zTK(^<^_GzRX4Fbf(x#Q+*b3E^%lxupTw?F{z>s8={Q*z?mSh&|*K8h|SzeA2!BJdA zTFjCeMvrCzusxX><66w*`b&v~7ddg)*HztYCgIaPRSA<~jrNDhtN;x?IfdOat<`*E zrqXJ@G0}eqN7{x{)PCfnW(ogPm++2s%!Bk~3R?wZl5se*#;_o{!wlb~uwcxa>&CD# zSRm#4kP~VryZxOW(nX|B4MR8SGGolv26pFIwi?r899SHjAIl;Kb)z|HdDH@DwYELd z4UyvzYmWzh);QJ$hsGdHm7Ve#GfYWk0pyJ5?dnvPDW5efEvYQoB%d?Gms8lw`aZK~ zA*-}yvM3^7GQ%fRS(kj-49(NnGP&Ojrs*t9E#s%N$I!q#rn5Mm-eBRkL3=n41pQ1_ zY!eoB_7>SsmMj*t_sxz?dZs)!lP%TBeiqFZf&wfa-3_r^x2gc#jr?UcZVoS<%^os_ zASOA$B1KqmNgby|=dd;z1Mnwv(LCZ{{XDjuC#WFFQWE@Q9(s`!7@W(NVo{aL9GD`X z%4LTzMmEf6qp)~sKDwt23mkk}AF1FrNgP2#{dOT+i4k&f9!tddxjB#BgXwSPViu1D zS{Hz9_TSb-s>1{?(}aL|CaxMr=fxJNm|qOSKqORcpKvkN<81vf~{ zQk_M?!<>^kx!&SApK5ksC~45fEa7hcRybY6qM>X#n}e%?*Os#^^gH@OG~8{TH;W4y z9{s_tLN*gUSZ^Vk&VC?8Y^c1`!<^xbUQKWE(ey@*fIXE;O<0rhagT~F44R7A3`BtS zP&>y!#zLijWhRy zl=WFR=2bcj9J$J=Ri{-3>FgRi%fldM{R2&g080O7NH zoT4n-!QR)QZ%=-hO$?ZzF#@er%JA0gxHP)wVKz<9vclY-3_~DnH#IBYKg_nv`fTgf z)6&O*-K@{K_K1h4Liq()8AHdrcONFD_wL0+F%O>J%ch|d{(3JgzjoosaRRQZolpLm<>*ST zK8<~nZNTNz;U`%l`0rz5v}Ih45_5nx>u@{d-DlWGxMteh%mQV(9>sv?n%WTneEuAB z$qk-iMqcvT=vM3XSi2SJ%9p6L&CBkcUQKNDf!PTuEy#@v2U(hERN;48r6#rQL-a@L zD<$-KmP^oIX>j)*Vw2EmwI5=)1vOjAlxbxp<(OGw^5#wCG0oBdXBs}*DIF?gYs#l{}9lH1hN7H844vP#@)ty(kHRjr;8 zR7=t^L|`z%@k3~vwinoLxx+iH6W(cc`b_Ht^u55=h)#7zC#+JJ;(U=!H)4AA>@jw? z+-=oPNtIczu@MCGDOH_t`|*|I>|yzgRhjoXvk{c;vUZk6&U%#XoE5M+2rjj=Tjf5h zf+wo~8WeF!omBi~YrmD)3^rl&vjx~fY%#V3n{?R@*vRPNV%x zbQ;B{*>LnLTK)Ocml0~`!I_M@YF|L zWYM_CG4~={hWm_%FQRLTfOjrp))NEgFS1FdI2*C6M<4301vW&(;_t9Wf~WUG-=RV$ zX(Zsi?~r^7e2Yb3s*R+nOQ5Xv)s?Q&Dpw)y-lcfJCVtQ64#Fwxr8IO^sW#+oiVY7I zANw9xiW%_l?^(j4Oq;JUWY}&nh79b6#*l?@*Dri*)@PAygw^hQY)0hX>mJ!2;B4so zfyKg>OPEyTz@bYlHZ0dhX6BWyDJ@%9y0FqwT7?JMsDvu!92Iy7*nVKWXzN(7Kr=7= z53(g6w*H5WNXfSi3^(<9^wMcKs{*8^{m2fRgn#IP>-A~4zWUdH*!;nHHj`bD)hHmBXIN`~WaE=d2F$0z<~yMZ74N_qRY2E|Xks<6@h9Ao=(x;Ml4}QGs8cc2 zUu8+14~BZI<236$U2pk`jmKpgqPY`Yf6q^tS#AaPGmA^uHb4>$H%g+xgJ>rt|AOl? z*Uy+&JK@C77(*J>rXtVNjM>3>Q(%Q_}ClHJJ7D(W^Yt&z6wZ#{E3QcECpT^KnKxF&%eQ~m4gK*%Q=R)2!f#JLHK5~JicIUis#a&|b;a!VOt#z8^(mPzkb7+Kv&=)o zdTk{74m>=Y7(K2)l6nzPyJnLtov}f^9uE$$$?&Yohezd0Hn372h9@?@@{`YM;GvpK z;EgM|&dt{I@p7NmT&UiRxq_L?WiM2cfoI75J`|{`CBzQUk9Tkk(52(yzoCGxwcd#1 znL&9hY$iMa>L|C3GT4264YB*^4epOzGfj`)2$&~$FnmaP3bBBm@u{I9cD$~IE^%sY zwX+Iy%4p0faUJ6-ih*Ug+Y=gRzm|Ar78O-Gt5hKKN=H=;>|i`BBnCHJy?JLIUZla7 z{;tyUn(732i}B&;t-oPB1`8|aBYqFKvZ}8H7$QN`nY9R;Mr2xDCIBgFJZWG)N-*dh|gsAT#?6?z$ z3>z!Q*irQAn(v^%IVIrrGCn*ZX#ky+8|kEY=%lDPB5t;;!$!BOUKgmr7?V0ca1&I) zWgreMN=(3>SDk8x9n1zE1CJT`?Ysx0p2}wEx`He3439=6nfRFWI7~Aas8MaDI=9RL z$g_O4BFhK2M=P>CXkIq)g~{0iI%*b`b9Gd{7M0_fTaKqIT2vscG4o+^t{pc!0^myv zw?Lbjm&kcO4Kpmry?iLO@G)3?j7*dZuD!0f!!dxN&Knu(^bl1GJr53+Tmw3k+}NSS z)1kx;z3T4faVsB*x%byt$Q4@Kbc`znHa;7RXKg%gRLy|tk3*T%UY$Lzxvz?()`PYd z`fdExsdWSTB>VPBzM)V30N@5yFtv85H}Sz?UaSq;?6%r>+NG^nmABcYZIIx{({Po( z+>eh(^=tCunWGv9VA|qKcFPT9n}84E*@O3>5iLLyR&P?Qa#W!@?t!8~yew`10AvT$ zVYOVH_yHWn)tR?o9o0*1lAGQ7V{PXlup zAB2bVSkJ<-IKz-$E<6;502OddEgIp!!}v(cSu-iVy2ZW%a)bF`_fT^vF}o+5W1uXY zhaldq;e4EIvA7SL!{F0!9!DY`iQwsSlEppR5&-W+ax*NA;OlYw()~9}D0~;e!*K%k zNE~uKOo_xe*=BL?w?;r?B)&NcFGTX2a8u_hZxcM%*n41N6u(IyVfEf)hwV{33RC+- zQG5|DnBB?t2vrhhNRH-#a*owqVGjo-f?M6Y?RL4q>Tazk@s|YS*h9&ZeBtco8$hQ;Z^Tj0oCi3~^QT#Bf)ymO)sV<9$z=_cq z9hWIzkLLbFF{PmJP!%SO;rXbS?lF88DNw!~!=KY(`KR%GH^GyT;c57`La9&V;d;!6 zA5Z6ZVG)wSA0yk8moxZoJeE-APvv$Uu5t^rcn#UFbZ7A#J!YZ9Z{Nl`w(oj*ZHhO%jiniOR!yPe#KH<^^DT%77ij?z-hJ8)w{sjB99<2+wku$Gq* zUA`z#I@a;MGPXGH;t4vPLpYV4bv!`5r1A7d{vUj?cN2daXRO>(&x_RUf`gm+G#tQ% z&HOz?P!Go&_%?*j`2(*+2mbmW_;^E&*edRUiXl8&3Hu|TOOPdNw(wpvvLvUG2jRtW z#rX(7Nst%gcJX;=X$`yhM$DS^O?(mF=f0x}`GwnvU61kxyxRl44~m*f7rtPWnF}EvpSI&#dw&vQ}vonW)Ozi zf4-=r>B(Jn1Cvp_OH@IC!mk|3F??s)|cc2H! zhvW`EQ!X&V`#mCEdAWm!=!rx5-~=xvxZa!mF5i!0>U|egy-qps9yfE;%ojf9Um?4j zPVp6_`H@dhge}VSPxv|+x&G?k`70>sb=|1aX!l*8qQxBp{b%S3k3rgJ7??Yt;WJ*1 zx3PZujNeZ>q2Y6W32pQ49{xQ!rIdfczaa7%Bg{L^-<8i9A)=R`*7Y07Bmdxw@$B}t zfAGx&zXa*|C;x>JKjqV}c_(23CgffWQP~#&Z=T~*P@VPXQ7usf)!!nS6!`U9RGv(j zdx4MDWt(!K;R4z~E_{4}=a4)lwhyr*H}1N~%TN<@;G2v57UbEu@Aw!rRap50kHEwQfuj)u`6y!>XkuLfUANdHv~`(c)#4;nHL=75&i@$>L^$XHh*V0uRZQJI9C` z5|L|8l+1HnRYd)m|3 zBUT`uT{)sp747{CL=lSi+XW&TEy2D}G@^t}cZgbu$`gaI>9IS+AK*xyuo&vh$Yz+l zM5My6c_IJ<&Xpx13dSuGq3FiuE)pYjTg}^G!y+*b#oxL}{8@*=IPZ33K$8#V9Ui2Q zA(QvO#3iECaNs)Pe=b2}$5h0r=;#sWFtld;cf#E1MGR1Y3^@fK7Kl$skJ5an7;eCQ z!mdJ*f@iCc8H2a6!-_;aJEvj|AaG$3I-5I-L?3Qb&2x&|a5>cM6lqxe!ztFveP+0Q zg$Ps6UDvG;_BD1pB=iU-k`X`#ZE_QO*xvoY|pf+?&bg`TagX8 zery$^b&V`rd9hW@BdE0c7X_YAH^GRP#6MA8tZm{}Eb`mLP|O52wu#SW89&C_o`w4v z>y9923V`B8!Q4;B83SS6DZrQ%hQM`#JtOa2AWYZSrI z_^Plg-yah(YWN8}F7}~#l;db9ZSY^mQFg8H-0NZ~KAGCZ1IX50?E-Is*1+C3#2a{& zxcp6#s~=Qnogod?u9K?wN^xH8O2^w`whrUVpm)W41Qp}U_r!9v&8eMY6&{>E*C|Hh zZbI}oxJSmpz_FE6Yj+5ekOen(M7juyC>i0zt5}x^ixC0gC$q&RDR0DgLID%?$ zqDy>g>1O^uQak*iz%#clIR25i7YR@OSY-0_KGu(u)z6MzfM3iwi=xXNl@9z86mO%C zB1w##!>gOKrI`!XxXNcZoyEAY^InI=IT+yh=VG@(aEWxo(jE~f_u#o*2ZpI$IN2lS z<5>X2ej%pIXRXSrFGMD$HCCANF27$%J+11NMak_I%M5XS)_GF;B4?#*MXk@LBA6=X zO8PzW1i4Rt$r=xRU$Vg~E?c)unYPMVxMo4Qvk<$e%glbOJxyy{xZc}BZ1!ZWlu=i1y7zs7oG&Y=g@skfY9^e zMO6Be=f!k=rp^8&be$K0O5(TTVWLa1Wx<;l#QVBDTPig6iPgp=n_Z=sXLEwMD9-u2 zY`l7XiYw{znZEI#1I9T4S`!a;FXAgNz&StFAT9+O$(Pr6tI?h>RRMA3tVW>&z^7g@-Tqd;+5n zcX)ONvssT#pF*PNDdsZzQN66*%f2J2C%89?L%Cfi?5Xfv zpz&_>=EH-Gn~^&QgNz%=$P}Bo*jZ}NwM)}0YMhm|OIJ9ZON&q!T*Xz~T(iE~3y^1* zpd`W=sZ@m+(T6zT^DyHXOyu4OH{OMbTtPI@Vx zzc?$G;IMO&C zbG?p{#?|Ofk`s-8HEp%~Yp8eH8x@jd+(HJlJUq&HMD?IK$;PR;E3qTlI1P)BlZ}hf xAtt668}#U7UPv`=gwMwthmd10-(>6~a=TsmF3otSPVTTP3(}3LM0dh2{Vy%9#fAU? diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 7a72f8e26b0b..440972c7a681 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -18,7 +18,6 @@ //! and is used by the rpc server to query and send transactions to the substrate chain. use crate::{ extract_receipts_from_block, - runtime::gas_from_fee, subxt_client::{ revive::calls::types::EthTransact, runtime_types::pallet_revive::storage::ContractInfo, }, @@ -649,8 +648,7 @@ impl Client { hydrated_transactions: bool, ) -> Result { let runtime_api = self.api.runtime_api().at(block.hash()); - let max_fee = Self::weight_to_fee(&runtime_api, self.max_block_weight()).await?; - let gas_limit = gas_from_fee(max_fee); + let gas_limit = Self::block_gas_limit(&runtime_api).await?; let header = block.header(); let timestamp = extract_block_timestamp(&block).await.unwrap_or_default(); @@ -695,16 +693,13 @@ impl Client { } /// Convert a weight to a fee. - async fn weight_to_fee( + async fn block_gas_limit( runtime_api: &subxt::runtime_api::RuntimeApi>, - weight: Weight, - ) -> Result { - let payload = subxt_client::apis() - .transaction_payment_api() - .query_weight_to_fee(weight.into()); + ) -> Result { + let payload = subxt_client::apis().revive_api().block_gas_limit(); - let fee = runtime_api.call(payload).await?; - Ok(fee) + let gas_limit = runtime_api.call(payload).await?; + Ok(*gas_limit) } /// Get the chain ID. diff --git a/substrate/frame/revive/src/benchmarking/mod.rs b/substrate/frame/revive/src/benchmarking/mod.rs index 16bdd6d1a18a..a19ed28dd9b0 100644 --- a/substrate/frame/revive/src/benchmarking/mod.rs +++ b/substrate/frame/revive/src/benchmarking/mod.rs @@ -27,7 +27,7 @@ use crate::{ exec::{Key, MomentOf}, limits, storage::WriteOutcome, - Pallet as Contracts, *, + ConversionPrecision, Pallet as Contracts, *, }; use alloc::{vec, vec::Vec}; use codec::{Encode, MaxEncodedLen}; @@ -1771,7 +1771,9 @@ mod benchmarks { assert!(ContractInfoOf::::get(&addr).is_some()); assert_eq!( T::Currency::balance(&account_id), - Pallet::::min_balance() + Pallet::::convert_evm_to_native(value.into()).unwrap() + Pallet::::min_balance() + + Pallet::::convert_evm_to_native(value.into(), ConversionPrecision::Exact) + .unwrap() ); Ok(()) } diff --git a/substrate/frame/revive/src/evm/gas_encoder.rs b/substrate/frame/revive/src/evm/gas_encoder.rs index ffdf8b13c043..8853e77e958e 100644 --- a/substrate/frame/revive/src/evm/gas_encoder.rs +++ b/substrate/frame/revive/src/evm/gas_encoder.rs @@ -72,6 +72,12 @@ pub trait GasEncoder: private::Sealed { /// Decodes the weight and deposit from the encoded gas value. /// Returns `None` if the gas value is invalid fn decode(gas: U256) -> Option<(Weight, Balance)>; + + /// Returns the encoded values of the specified weight and deposit. + fn as_encoded_values(weight: Weight, deposit: Balance) -> (Weight, Balance) { + let encoded = Self::encode(U256::zero(), weight, deposit); + Self::decode(encoded).expect("encoded values should be decodable; qed") + } } impl GasEncoder for () @@ -148,6 +154,11 @@ mod test { assert!(decoded_deposit >= deposit); assert!(deposit * 2 >= decoded_deposit); + + assert_eq!( + (decoded_weight, decoded_deposit), + <() as GasEncoder>::as_encoded_values(weight, deposit) + ); } #[test] diff --git a/substrate/frame/revive/src/evm/runtime.rs b/substrate/frame/revive/src/evm/runtime.rs index 0e5fc3da545b..09bfbf380c61 100644 --- a/substrate/frame/revive/src/evm/runtime.rs +++ b/substrate/frame/revive/src/evm/runtime.rs @@ -20,7 +20,8 @@ use crate::{ api::{GenericTransaction, TransactionSigned}, GasEncoder, }, - AccountIdOf, AddressMapper, BalanceOf, Config, MomentOf, Weight, LOG_TARGET, + AccountIdOf, AddressMapper, BalanceOf, Config, ConversionPrecision, MomentOf, Pallet, + LOG_TARGET, }; use alloc::vec::Vec; use codec::{Decode, Encode}; @@ -34,8 +35,8 @@ use sp_core::{Get, H256, U256}; use sp_runtime::{ generic::{self, CheckedExtrinsic, ExtrinsicFormat}, traits::{ - self, AtLeast32BitUnsigned, Checkable, Dispatchable, ExtrinsicLike, ExtrinsicMetadata, - IdentifyAccount, Member, TransactionExtension, + self, Checkable, Dispatchable, ExtrinsicLike, ExtrinsicMetadata, IdentifyAccount, Member, + TransactionExtension, }, transaction_validity::{InvalidTransaction, TransactionValidityError}, OpaqueExtrinsic, RuntimeDebug, Saturating, @@ -56,34 +57,6 @@ type CallOf = ::RuntimeCall; /// - Not too low, enabling users to adjust the gas price to define a tip. pub const GAS_PRICE: u32 = 1_000u32; -/// Convert a `Balance` into a gas value, using the fixed `GAS_PRICE`. -/// The gas is calculated as `balance / GAS_PRICE`, rounded up to the nearest integer. -pub fn gas_from_fee(fee: Balance) -> U256 -where - u32: Into, - Balance: Into + AtLeast32BitUnsigned + Copy, -{ - let gas_price = GAS_PRICE.into(); - let remainder = fee % gas_price; - if remainder.is_zero() { - (fee / gas_price).into() - } else { - (fee.saturating_add(gas_price) / gas_price).into() - } -} - -/// Convert a `Weight` into a gas value, using the fixed `GAS_PRICE`. -/// and the `Config::WeightPrice` to compute the fee. -/// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer. -pub fn gas_from_weight(weight: Weight) -> U256 -where - BalanceOf: Into, -{ - use sp_runtime::traits::Convert; - let fee: BalanceOf = T::WeightPrice::convert(weight); - gas_from_fee(fee) -} - /// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned /// [`crate::Call::eth_transact`] extrinsic. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] @@ -346,11 +319,14 @@ pub trait EthExtra { return Err(InvalidTransaction::Call); } - let value = crate::Pallet::::convert_evm_to_native(value.unwrap_or_default()) - .map_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}"); - InvalidTransaction::Call - })?; + let value = crate::Pallet::::convert_evm_to_native( + value.unwrap_or_default(), + ConversionPrecision::Exact, + ) + .map_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to convert value to native: {err:?}"); + InvalidTransaction::Call + })?; let data = input.unwrap_or_default().0; @@ -393,17 +369,21 @@ pub trait EthExtra { let nonce = nonce.unwrap_or_default().try_into().map_err(|_| InvalidTransaction::Call)?; // Fees calculated with the fixed `GAS_PRICE` - // When we dry-run the transaction, we set the gas to `Fee / GAS_PRICE` + // When we dry-run the transaction, we set the gas to `fee / GAS_PRICE` let eth_fee_no_tip = U256::from(GAS_PRICE) .saturating_mul(gas) .try_into() .map_err(|_| InvalidTransaction::Call)?; - // Fees with the actual gas_price from the transaction. - let eth_fee: BalanceOf = U256::from(gas_price.unwrap_or_default()) - .saturating_mul(gas) - .try_into() - .map_err(|_| InvalidTransaction::Call)?; + // Fees calculated from the gas and gas_price of the transaction. + let eth_fee = Pallet::::convert_evm_to_native( + U256::from(gas_price.unwrap_or_default()).saturating_mul(gas), + ConversionPrecision::RoundUp, + ) + .map_err(|err| { + log::debug!(target: LOG_TARGET, "Failed to compute eth_fee: {err:?}"); + InvalidTransaction::Call + })?; let info = call.get_dispatch_info(); let function: CallOf = call.into(); diff --git a/substrate/frame/revive/src/exec.rs b/substrate/frame/revive/src/exec.rs index d2ef6c9c7ba6..14ab917c0d4f 100644 --- a/substrate/frame/revive/src/exec.rs +++ b/substrate/frame/revive/src/exec.rs @@ -24,8 +24,8 @@ use crate::{ storage::{self, meter::Diff, WriteOutcome}, tracing::if_tracing, transient_storage::TransientStorage, - BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, Error, Event, - ImmutableData, ImmutableDataOf, Pallet as Contracts, + BalanceOf, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, ConversionPrecision, + Error, Event, ImmutableData, ImmutableDataOf, Pallet as Contracts, }; use alloc::vec::Vec; use core::{fmt::Debug, marker::PhantomData, mem}; @@ -1273,7 +1273,7 @@ where to: &T::AccountId, value: U256, ) -> ExecResult { - let value = crate::Pallet::::convert_evm_to_native(value)?; + let value = crate::Pallet::::convert_evm_to_native(value, ConversionPrecision::Exact)?; if value.is_zero() { return Ok(Default::default()); } diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index c36cb3f47cae..7f4565a9f088 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -41,10 +41,7 @@ pub mod tracing; pub mod weights; use crate::{ - evm::{ - runtime::{gas_from_fee, GAS_PRICE}, - GasEncoder, GenericTransaction, - }, + evm::{runtime::GAS_PRICE, GasEncoder, GenericTransaction}, exec::{AccountIdOf, ExecError, Executable, Ext, Key, Stack as ExecStack}, gas::GasMeter, storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, @@ -1140,16 +1137,20 @@ where if tx.nonce.is_none() { tx.nonce = Some(>::account_nonce(&origin).into()); } + if tx.chain_id.is_none() { + tx.chain_id = Some(T::ChainId::get().into()); + } if tx.gas_price.is_none() { tx.gas_price = Some(GAS_PRICE.into()); } - if tx.chain_id.is_none() { - tx.chain_id = Some(T::ChainId::get().into()); + if tx.gas.is_none() { + tx.gas = Some(Self::evm_block_gas_limit()); } // Convert the value to the native balance type. let evm_value = tx.value.unwrap_or_default(); - let native_value = match Self::convert_evm_to_native(evm_value) { + let native_value = match Self::convert_evm_to_native(evm_value, ConversionPrecision::Exact) + { Ok(v) => v, Err(_) => return Err(EthTransactError::Message("Failed to convert value".into())), }; @@ -1206,12 +1207,16 @@ where data, eth_gas: Default::default(), }; - // Get the dispatch info of the call. + + let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values( + result.gas_required, + result.storage_deposit, + ); let dispatch_call: ::RuntimeCall = crate::Call::::call { dest, value: native_value, - gas_limit: result.gas_required, - storage_deposit_limit: result.storage_deposit, + gas_limit, + storage_deposit_limit, data: input.clone(), } .into(); @@ -1264,11 +1269,15 @@ where }; // Get the dispatch info of the call. + let (gas_limit, storage_deposit_limit) = T::EthGasEncoder::as_encoded_values( + result.gas_required, + result.storage_deposit, + ); let dispatch_call: ::RuntimeCall = crate::Call::::instantiate_with_code { value: native_value, - gas_limit: result.gas_required, - storage_deposit_limit: result.storage_deposit, + gas_limit, + storage_deposit_limit, code: code.to_vec(), data: data.to_vec(), salt: None, @@ -1278,38 +1287,26 @@ where }, }; - // The transaction fees depend on the extrinsic's length, which in turn is influenced by - // the encoded length of the gas limit specified in the transaction (tx.gas). - // We iteratively compute the fee by adjusting tx.gas until the fee stabilizes. - // with a maximum of 3 iterations to avoid an infinite loop. - for _ in 0..3 { - let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { - log::debug!(target: LOG_TARGET, "Failed to convert to unsigned"); - return Err(EthTransactError::Message("Invalid transaction".into())); - }; - - let eth_dispatch_call = - crate::Call::::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; - let encoded_len = utx_encoded_size(eth_dispatch_call); - let fee = pallet_transaction_payment::Pallet::::compute_fee( - encoded_len, - &dispatch_info, - 0u32.into(), - ) - .into(); - let eth_gas = gas_from_fee(fee); - let eth_gas = - T::EthGasEncoder::encode(eth_gas, result.gas_required, result.storage_deposit); - - if eth_gas == result.eth_gas { - log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); - break; - } - result.eth_gas = eth_gas; - tx.gas = Some(eth_gas.into()); - log::debug!(target: LOG_TARGET, "Adjusting Eth gas to: {eth_gas:?}"); - } + let Ok(unsigned_tx) = tx.clone().try_into_unsigned() else { + return Err(EthTransactError::Message("Invalid transaction".into())); + }; + let eth_dispatch_call = + crate::Call::::eth_transact { payload: unsigned_tx.dummy_signed_payload() }; + + let encoded_len = utx_encoded_size(eth_dispatch_call); + let fee = pallet_transaction_payment::Pallet::::compute_fee( + encoded_len, + &dispatch_info, + 0u32.into(), + ) + .into(); + let eth_gas = Self::evm_fee_to_gas(fee); + let eth_gas = + T::EthGasEncoder::encode(eth_gas, result.gas_required, result.storage_deposit); + + log::trace!(target: LOG_TARGET, "bare_eth_call: encoded_len: {encoded_len:?} eth_gas: {eth_gas:?}"); + result.eth_gas = eth_gas; Ok(result) } @@ -1319,6 +1316,29 @@ where Self::convert_native_to_evm(T::Currency::reducible_balance(&account, Preserve, Polite)) } + /// Convert an EVM fee into a gas value, using the fixed `GAS_PRICE`. + /// The gas is calculated as `fee / GAS_PRICE`, rounded up to the nearest integer. + pub fn evm_fee_to_gas(fee: BalanceOf) -> U256 { + let fee = Self::convert_native_to_evm(fee); + let gas_price = GAS_PRICE.into(); + let (quotient, remainder) = fee.div_mod(gas_price); + if remainder.is_zero() { + quotient + } else { + quotient + U256::one() + } + } + + pub fn evm_block_gas_limit() -> U256 { + let max_block_weight = T::BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap_or_else(|| T::BlockWeights::get().max_block); + + let fee = T::WeightPrice::convert(max_block_weight); + Self::evm_fee_to_gas(fee) + } + /// A generalized version of [`Self::upload_code`]. /// /// It is identical to [`Self::upload_code`] and only differs in the information it returns. @@ -1379,16 +1399,22 @@ where } /// Convert an EVM balance to a native balance. - fn convert_evm_to_native(value: U256) -> Result, Error> { + fn convert_evm_to_native( + value: U256, + precision: ConversionPrecision, + ) -> Result, Error> { if value.is_zero() { return Ok(Zero::zero()) } - let ratio = T::NativeToEthRatio::get().into(); - let res = value.checked_div(ratio).expect("divisor is non-zero; qed"); - if res.saturating_mul(ratio) == value { - res.try_into().map_err(|_| Error::::BalanceConversionFailed) - } else { - Err(Error::::DecimalPrecisionLoss) + + let (quotient, remainder) = value.div_mod(T::NativeToEthRatio::get().into()); + match (precision, remainder.is_zero()) { + (ConversionPrecision::Exact, false) => Err(Error::::DecimalPrecisionLoss), + (_, true) => quotient.try_into().map_err(|_| Error::::BalanceConversionFailed), + (_, false) => quotient + .saturating_add(U256::one()) + .try_into() + .map_err(|_| Error::::BalanceConversionFailed), } } } @@ -1417,6 +1443,9 @@ sp_api::decl_runtime_apis! { Nonce: Codec, BlockNumber: Codec, { + /// Returns the block gas limit. + fn block_gas_limit() -> U256; + /// Returns the free balance of the given `[H160]` address, using EVM decimals. fn balance(address: H160) -> U256; diff --git a/substrate/frame/revive/src/primitives.rs b/substrate/frame/revive/src/primitives.rs index 9c149c7cc389..e2900bd027b6 100644 --- a/substrate/frame/revive/src/primitives.rs +++ b/substrate/frame/revive/src/primitives.rs @@ -108,6 +108,14 @@ pub enum EthTransactError { Message(String), } +/// Precision used for converting between Native and EVM balances. +pub enum ConversionPrecision { + /// Exact conversion without any rounding. + Exact, + /// Conversion that rounds up to the nearest whole number. + RoundUp, +} + /// Result type of a `bare_code_upload` call. pub type CodeUploadResult = Result, DispatchError>; From fb2e414f44d4bfa2d47aba4a868fb3f8932f7930 Mon Sep 17 00:00:00 2001 From: Egor_P Date: Thu, 23 Jan 2025 11:27:39 +0100 Subject: [PATCH 309/340] [Release|CI/CD] Download only linux artefacts for deb package build (#7271) This PR contains a fix for the rc-build release pipeline. The problem was, that for the deb package build were all the artefacts downloaded and merged together, what could lead to the issue, that the polkadot linux binary was overwritten with the macos one. --- .github/workflows/release-reusable-rc-buid.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-reusable-rc-buid.yml b/.github/workflows/release-reusable-rc-buid.yml index 035b547603e1..b79f7fa61750 100644 --- a/.github/workflows/release-reusable-rc-buid.yml +++ b/.github/workflows/release-reusable-rc-buid.yml @@ -263,9 +263,24 @@ jobs: ref: ${{ inputs.release_tag }} fetch-depth: 0 - - name: Download artifacts + - name: Download polkadot_x86_64-unknown-linux-gnu artifacts uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: + name: polkadot_x86_64-unknown-linux-gnu + path: target/production + merge-multiple: true + + - name: Download polkadot-execute-worker_x86_64-unknown-linux-gnu artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: polkadot-execute-worker_x86_64-unknown-linux-gnu + path: target/production + merge-multiple: true + + - name: Download polkadot-prepare-worker_x86_64-unknown-linux-gnu artifacts + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 + with: + name: polkadot-prepare-worker_x86_64-unknown-linux-gnu path: target/production merge-multiple: true From 04847d515ef56da4d0801c9b89a4241dfa827b33 Mon Sep 17 00:00:00 2001 From: runcomet Date: Thu, 23 Jan 2025 02:38:15 -0800 Subject: [PATCH 310/340] Balances: Configurable Number of Genesis Accounts with Specified Balances for Benchmarking (#6267) # Derived Dev Accounts Resolves https://github.com/paritytech/polkadot-sdk/issues/6040 ## Description This update introduces support for creating an arbitrary number of developer accounts at the genesis block based on a specified derivation path. This functionality is gated by the runtime-benchmarks feature, ensuring it is only enabled during benchmarking scenarios. ### Key Features - Arbitrary Dev Accounts at Genesis: Developers can now specify any number of accounts to be generated at genesis using a hard derivation path. - Default Derivation Path: If no derivation path is provided (i.e., when `Option` is set to `Some` at genesis), the system will default to the path `//Sender//{}`. - No Impact on Total Token Issuance: Developer accounts are excluded from the total issuance of the token supply at genesis, ensuring they do not affect the overall balance or token distribution. polkadot address: 14SRqZTC1d8rfxL8W1tBTnfUBPU23ACFVPzp61FyGf4ftUFg --------- Co-authored-by: Sebastian Kunert --- bridges/modules/messages/src/tests/mock.rs | 9 +- .../pallets/collator-selection/src/mock.rs | 2 +- .../assets/asset-hub-rococo/src/genesis.rs | 1 + .../assets/asset-hub-westend/src/genesis.rs | 1 + .../bridges/bridge-hub-rococo/src/genesis.rs | 1 + .../bridges/bridge-hub-westend/src/genesis.rs | 1 + .../collectives-westend/src/genesis.rs | 1 + .../coretime/coretime-rococo/src/genesis.rs | 1 + .../coretime/coretime-westend/src/genesis.rs | 1 + .../people/people-rococo/src/genesis.rs | 1 + .../people/people-westend/src/genesis.rs | 1 + .../parachains/testing/penpal/src/genesis.rs | 1 + .../chains/relays/rococo/src/genesis.rs | 1 + .../chains/relays/westend/src/genesis.rs | 1 + .../parachains/runtimes/test-utils/src/lib.rs | 2 +- .../runtime/common/src/assigned_slots/mod.rs | 1 + polkadot/runtime/common/src/auctions/mock.rs | 1 + polkadot/runtime/common/src/crowdloan/mod.rs | 1 + .../common/src/paras_registrar/mock.rs | 1 + polkadot/runtime/common/src/slots/mod.rs | 1 + .../relay_token_transactor/network.rs | 9 +- polkadot/xcm/pallet-xcm/src/mock.rs | 2 +- .../single_asset_adapter/mock.rs | 1 + polkadot/xcm/xcm-builder/tests/mock/mod.rs | 2 +- polkadot/xcm/xcm-runtime-apis/tests/mock.rs | 4 +- polkadot/xcm/xcm-simulator/example/src/lib.rs | 2 + polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs | 3 +- prdoc/pr_6267.prdoc | 171 ++++++++++++++++++ .../cli/tests/res/default_genesis_config.json | 3 +- substrate/bin/node/testing/src/genesis.rs | 2 +- .../tests/expected/create_default.json | 3 +- .../tests/expected/create_parachain.json | 3 +- .../tests/expected/create_with_params.json | 3 +- .../tests/expected/doc/create_default.json | 3 +- .../tests/expected/doc/display_preset.json | 2 +- .../chain-spec/src/genesis_config_builder.rs | 2 +- substrate/frame/alliance/src/mock.rs | 1 + .../frame/asset-conversion/ops/src/mock.rs | 1 + substrate/frame/asset-conversion/src/mock.rs | 1 + substrate/frame/asset-rewards/src/mock.rs | 1 + substrate/frame/atomic-swap/src/tests.rs | 5 +- substrate/frame/babe/src/mock.rs | 2 +- substrate/frame/balances/Cargo.toml | 6 +- substrate/frame/balances/src/lib.rs | 64 ++++++- .../balances/src/tests/currency_tests.rs | 11 +- substrate/frame/balances/src/tests/mod.rs | 40 +++- substrate/frame/beefy/src/mock.rs | 2 +- substrate/frame/bounties/src/tests.rs | 15 +- substrate/frame/child-bounties/src/tests.rs | 1 + substrate/frame/collective/src/tests.rs | 5 +- .../frame/contracts/mock-network/src/lib.rs | 2 + substrate/frame/contracts/src/tests.rs | 2 +- .../frame/conviction-voting/src/tests.rs | 1 + substrate/frame/delegated-staking/src/mock.rs | 1 + substrate/frame/democracy/src/tests.rs | 1 + .../election-provider-multi-phase/src/mock.rs | 1 + .../test-staking-e2e/src/mock.rs | 1 + substrate/frame/elections-phragmen/src/lib.rs | 1 + substrate/frame/executive/src/tests.rs | 20 +- substrate/frame/fast-unstake/src/mock.rs | 1 + substrate/frame/grandpa/src/mock.rs | 2 +- substrate/frame/identity/src/tests.rs | 1 + substrate/frame/indices/src/mock.rs | 1 + substrate/frame/lottery/src/mock.rs | 1 + substrate/frame/multisig/src/tests.rs | 1 + substrate/frame/nis/src/mock.rs | 1 + .../test-delegate-stake/src/mock.rs | 1 + substrate/frame/preimage/src/mock.rs | 1 + substrate/frame/proxy/src/tests.rs | 1 + substrate/frame/recovery/src/mock.rs | 1 + substrate/frame/referenda/src/mock.rs | 2 +- .../frame/revive/mock-network/src/lib.rs | 2 + substrate/frame/revive/src/tests.rs | 2 +- substrate/frame/root-offences/src/mock.rs | 1 + substrate/frame/safe-mode/src/mock.rs | 1 + substrate/frame/scored-pool/src/mock.rs | 2 +- substrate/frame/society/src/mock.rs | 2 +- substrate/frame/staking/src/mock.rs | 1 + .../frame/state-trie-migration/src/lib.rs | 9 +- substrate/frame/statement/src/mock.rs | 1 + substrate/frame/tips/src/tests.rs | 6 +- .../asset-conversion-tx-payment/src/tests.rs | 1 + .../asset-tx-payment/src/tests.rs | 1 + .../frame/transaction-payment/src/tests.rs | 1 + .../frame/transaction-storage/src/mock.rs | 1 + substrate/frame/treasury/src/tests.rs | 11 +- substrate/frame/tx-pause/src/mock.rs | 1 + substrate/frame/utility/src/tests.rs | 1 + substrate/frame/vesting/src/mock.rs | 1 + .../test-utils/runtime/src/genesismap.rs | 5 +- substrate/test-utils/runtime/src/lib.rs | 2 +- 91 files changed, 434 insertions(+), 62 deletions(-) create mode 100644 prdoc/pr_6267.prdoc diff --git a/bridges/modules/messages/src/tests/mock.rs b/bridges/modules/messages/src/tests/mock.rs index 2935ebd69610..8eebdf3a5081 100644 --- a/bridges/modules/messages/src/tests/mock.rs +++ b/bridges/modules/messages/src/tests/mock.rs @@ -461,9 +461,12 @@ pub fn inbound_unrewarded_relayers_state(lane: TestLaneIdType) -> UnrewardedRela /// Return test externalities to use in tests. pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(ENDOWED_ACCOUNT, 1_000_000)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(ENDOWED_ACCOUNT, 1_000_000)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); sp_io::TestExternalities::new(t) } diff --git a/cumulus/pallets/collator-selection/src/mock.rs b/cumulus/pallets/collator-selection/src/mock.rs index d13f9e9d8c44..6a97525c4f57 100644 --- a/cumulus/pallets/collator-selection/src/mock.rs +++ b/cumulus/pallets/collator-selection/src/mock.rs @@ -188,7 +188,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { invulnerables, }; let session = pallet_session::GenesisConfig:: { keys, ..Default::default() }; - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); // collator selection must be initialized before session. diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs index 3ffb9a704b46..4a10a1e10c73 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-rococo/src/genesis.rs @@ -42,6 +42,7 @@ pub fn genesis() -> Storage { .cloned() .map(|k| (k, ED * 4096 * 4096)) .collect(), + ..Default::default() }, parachain_info: asset_hub_rococo_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs index ef7997322da7..0473686081e7 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/assets/asset-hub-westend/src/genesis.rs @@ -39,6 +39,7 @@ pub fn genesis() -> Storage { system: asset_hub_westend_runtime::SystemConfig::default(), balances: asset_hub_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: asset_hub_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs index 575017f88bb5..62b2e4eed9e7 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-rococo/src/genesis.rs @@ -33,6 +33,7 @@ pub fn genesis() -> Storage { system: bridge_hub_rococo_runtime::SystemConfig::default(), balances: bridge_hub_rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: bridge_hub_rococo_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs index eb4623084f85..5286110bcab9 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs @@ -33,6 +33,7 @@ pub fn genesis() -> Storage { system: bridge_hub_westend_runtime::SystemConfig::default(), balances: bridge_hub_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: bridge_hub_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs index d4ef184ea392..51e065a4ae55 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/collectives/collectives-westend/src/genesis.rs @@ -30,6 +30,7 @@ pub fn genesis() -> Storage { system: collectives_westend_runtime::SystemConfig::default(), balances: collectives_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: collectives_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs index e0f035c368e3..f2035c8654d0 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-rococo/src/genesis.rs @@ -30,6 +30,7 @@ pub fn genesis() -> Storage { system: coretime_rococo_runtime::SystemConfig::default(), balances: coretime_rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: coretime_rococo_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs index 239ad3760c11..29894222eff7 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/coretime/coretime-westend/src/genesis.rs @@ -30,6 +30,7 @@ pub fn genesis() -> Storage { system: coretime_westend_runtime::SystemConfig::default(), balances: coretime_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: coretime_westend_runtime::ParachainInfoConfig { parachain_id: PARA_ID.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/genesis.rs index 36a701d24c27..9772a64d23b3 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-rococo/src/genesis.rs @@ -31,6 +31,7 @@ pub fn genesis() -> Storage { system: people_rococo_runtime::SystemConfig::default(), balances: people_rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: people_rococo_runtime::ParachainInfoConfig { parachain_id: ParaId::from(PARA_ID), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/genesis.rs index 942ec1b31d2b..377babc59f65 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend/src/genesis.rs @@ -31,6 +31,7 @@ pub fn genesis() -> Storage { system: people_westend_runtime::SystemConfig::default(), balances: people_westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: people_westend_runtime::ParachainInfoConfig { parachain_id: ParaId::from(PARA_ID), diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs index 63510d233d2c..e514d0cb7477 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/testing/penpal/src/genesis.rs @@ -40,6 +40,7 @@ pub fn genesis(para_id: u32) -> Storage { system: penpal_runtime::SystemConfig::default(), balances: penpal_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ED * 4096)).collect(), + ..Default::default() }, parachain_info: penpal_runtime::ParachainInfoConfig { parachain_id: para_id.into(), diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs index 3d8b5b1a500f..db9fe19dbdd7 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/genesis.rs @@ -57,6 +57,7 @@ pub fn genesis() -> Storage { system: rococo_runtime::SystemConfig::default(), balances: rococo_runtime::BalancesConfig { balances: accounts::init_balances().iter().map(|k| (k.clone(), ENDOWMENT)).collect(), + ..Default::default() }, session: rococo_runtime::SessionConfig { keys: validators::initial_authorities() diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs index f8d43cf4648d..2f02ca5f1932 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/genesis.rs @@ -58,6 +58,7 @@ pub fn genesis() -> Storage { system: westend_runtime::SystemConfig::default(), balances: westend_runtime::BalancesConfig { balances: accounts::init_balances().iter().cloned().map(|k| (k, ENDOWMENT)).collect(), + ..Default::default() }, session: westend_runtime::SessionConfig { keys: validators::initial_authorities() diff --git a/cumulus/parachains/runtimes/test-utils/src/lib.rs b/cumulus/parachains/runtimes/test-utils/src/lib.rs index 5c33809ba67b..b46a68312aa1 100644 --- a/cumulus/parachains/runtimes/test-utils/src/lib.rs +++ b/cumulus/parachains/runtimes/test-utils/src/lib.rs @@ -230,7 +230,7 @@ impl ExtBuilder { .unwrap(); } - pallet_balances::GenesisConfig:: { balances: self.balances } + pallet_balances::GenesisConfig:: { balances: self.balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/runtime/common/src/assigned_slots/mod.rs b/polkadot/runtime/common/src/assigned_slots/mod.rs index dea29f53cad4..81e2986ab6b3 100644 --- a/polkadot/runtime/common/src/assigned_slots/mod.rs +++ b/polkadot/runtime/common/src/assigned_slots/mod.rs @@ -773,6 +773,7 @@ mod tests { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/runtime/common/src/auctions/mock.rs b/polkadot/runtime/common/src/auctions/mock.rs index e0365d363ca2..191608f8c878 100644 --- a/polkadot/runtime/common/src/auctions/mock.rs +++ b/polkadot/runtime/common/src/auctions/mock.rs @@ -210,6 +210,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/runtime/common/src/crowdloan/mod.rs b/polkadot/runtime/common/src/crowdloan/mod.rs index f8b3169407e8..1b40f248bfb1 100644 --- a/polkadot/runtime/common/src/crowdloan/mod.rs +++ b/polkadot/runtime/common/src/crowdloan/mod.rs @@ -1082,6 +1082,7 @@ mod tests { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 1000), (2, 2000), (3, 3000), (4, 4000)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/runtime/common/src/paras_registrar/mock.rs b/polkadot/runtime/common/src/paras_registrar/mock.rs index 07b8fbca5189..bb3728f0e12a 100644 --- a/polkadot/runtime/common/src/paras_registrar/mock.rs +++ b/polkadot/runtime/common/src/paras_registrar/mock.rs @@ -166,6 +166,7 @@ pub fn new_test_ext() -> TestExternalities { pallet_balances::GenesisConfig:: { balances: vec![(1, 10_000_000), (2, 10_000_000), (3, 10_000_000)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/runtime/common/src/slots/mod.rs b/polkadot/runtime/common/src/slots/mod.rs index 59a1f1870b2d..131a75f3d743 100644 --- a/polkadot/runtime/common/src/slots/mod.rs +++ b/polkadot/runtime/common/src/slots/mod.rs @@ -578,6 +578,7 @@ mod tests { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs index 46ac0e5df637..71c14f6b241b 100644 --- a/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs +++ b/polkadot/xcm/docs/src/cookbook/relay_token_transactor/network.rs @@ -78,9 +78,12 @@ pub fn relay_ext() -> TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(ALICE, INITIAL_BALANCE)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); let mut ext = TestExternalities::new(t); ext.execute_with(|| { diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index 8d0476b0e70d..58b4226ccf19 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -700,7 +700,7 @@ pub(crate) fn new_test_ext_with_balances_and_xcm_version( ) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs index e6fe8e45c265..55a924dbaa63 100644 --- a/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs +++ b/polkadot/xcm/xcm-builder/src/asset_exchange/single_asset_adapter/mock.rs @@ -339,6 +339,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { balances: vec![(0, INITIAL_BALANCE), (1, INITIAL_BALANCE), (2, INITIAL_BALANCE)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-builder/tests/mock/mod.rs b/polkadot/xcm/xcm-builder/tests/mock/mod.rs index 0468b0a5410c..c3e532845082 100644 --- a/polkadot/xcm/xcm-builder/tests/mock/mod.rs +++ b/polkadot/xcm/xcm-builder/tests/mock/mod.rs @@ -243,7 +243,7 @@ construct_runtime!( pub fn kusama_like_with_balances(balances: Vec<(AccountId, Balance)>) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs index 56a77094f177..18d9dce9245a 100644 --- a/polkadot/xcm/xcm-runtime-apis/tests/mock.rs +++ b/polkadot/xcm/xcm-runtime-apis/tests/mock.rs @@ -365,7 +365,7 @@ impl pallet_xcm::Config for TestRuntime { pub fn new_test_ext_with_balances(balances: Vec<(AccountId, Balance)>) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -381,7 +381,7 @@ pub fn new_test_ext_with_balances_and_assets( ) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-simulator/example/src/lib.rs b/polkadot/xcm/xcm-simulator/example/src/lib.rs index 6fb9a69770ea..8a05569831b5 100644 --- a/polkadot/xcm/xcm-simulator/example/src/lib.rs +++ b/polkadot/xcm/xcm-simulator/example/src/lib.rs @@ -101,6 +101,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { balances: vec![(ALICE, INITIAL_BALANCE), (parent_account_id(), INITIAL_BALANCE)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -125,6 +126,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { (child_account_id(1), INITIAL_BALANCE), (child_account_id(2), INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs index adf6cacd278b..8ea5e033f3ad 100644 --- a/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs +++ b/polkadot/xcm/xcm-simulator/fuzzer/src/fuzz.rs @@ -117,6 +117,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { balances: (0..6).map(|i| ([i; 32].into(), INITIAL_BALANCE)).collect(), + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -138,7 +139,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { balances.append(&mut (1..=3).map(|i| (para_account_id(i), INITIAL_BALANCE)).collect()); balances.append(&mut (0..6).map(|i| ([i; 32].into(), INITIAL_BALANCE)).collect()); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/prdoc/pr_6267.prdoc b/prdoc/pr_6267.prdoc new file mode 100644 index 000000000000..30ada4456259 --- /dev/null +++ b/prdoc/pr_6267.prdoc @@ -0,0 +1,171 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Allow configurable number of genesis accounts with specified balances for benchmarking. + +doc: + - audience: Runtime Dev + description: | + This pull request adds an additional field `dev_accounts` to the `GenesisConfig` + of the balances pallet, feature gated by `runtime-benchmarks`. + + Bringing about an abitrary number of derived dev accounts when building the genesis + state. Runtime developers should supply a derivation path that includes an index placeholder + (i.e. "//Sender/{}") to generate multiple accounts from the same root in a consistent + manner. + +crates: + - name: substrate-test-runtime + bump: minor + - name: pallet-vesting + bump: patch + - name: pallet-utility + bump: patch + - name: pallet-tx-pause + bump: patch + - name: pallet-treasury + bump: patch + - name: pallet-transaction-storage + bump: patch + - name: pallet-transaction-payment + bump: patch + - name: pallet-asset-tx-payment + bump: patch + - name: pallet-asset-conversion-tx-payment + bump: patch + - name: pallet-tips + bump: patch + - name: pallet-state-trie-migration + bump: patch + - name: pallet-staking + bump: patch + - name: pallet-society + bump: patch + - name: pallet-safe-mode + bump: patch + - name: pallet-scored-pool + bump: patch + - name: pallet-statement + bump: patch + - name: pallet-root-offences + bump: patch + - name: pallet-revive + bump: patch + - name: pallet-revive-mock-network + bump: patch + - name: pallet-referenda + bump: patch + - name: pallet-recovery + bump: patch + - name: pallet-proxy + bump: patch + - name: pallet-preimage + bump: patch + - name: pallet-nis + bump: patch + - name: pallet-nomination-pools-test-delegate-stake + bump: minor + - name: pallet-multisig + bump: patch + - name: pallet-lottery + bump: patch + - name: pallet-indices + bump: patch + - name: pallet-identity + bump: patch + - name: pallet-grandpa + bump: patch + - name: pallet-fast-unstake + bump: patch + - name: frame-executive + bump: patch + - name: pallet-elections-phragmen + bump: patch + - name: pallet-election-provider-e2e-test + bump: minor + - name: pallet-election-provider-multi-phase + bump: patch + - name: pallet-democracy + bump: patch + - name: pallet-delegated-staking + bump: patch + - name: pallet-conviction-voting + bump: patch + - name: pallet-contracts + bump: patch + - name: pallet-contracts-mock-network + bump: patch + - name: pallet-collective + bump: patch + - name: pallet-child-bounties + bump: patch + - name: pallet-bounties + bump: patch + - name: pallet-beefy + bump: patch + - name: pallet-balances + bump: major + - name: pallet-babe + bump: patch + - name: pallet-asset-conversion + bump: patch + - name: pallet-asset-conversion-ops + bump: patch + - name: pallet-asset-rewards + bump: patch + - name: pallet-atomic-swap + bump: patch + - name: pallet-alliance + bump: patch + - name: node-testing + bump: minor + - name: sc-chain-spec + bump: patch + - name: staging-chain-spec-builder + bump: patch + - name: xcm-simulator-fuzzer + bump: minor + - name: xcm-simulator-fuzzer + bump: minor + - name: xcm-simulator-example + bump: patch + - name: xcm-runtime-apis + bump: patch + - name: staging-xcm-builder + bump: patch + - name: pallet-xcm + bump: patch + - name: xcm-docs + bump: minor + - name: polkadot-runtime-common + bump: patch + - name: parachains-runtimes-test-utils + bump: patch + - name: westend-emulated-chain + bump: minor + - name: rococo-emulated-chain + bump: minor + - name: penpal-emulated-chain + bump: minor + - name: people-westend-emulated-chain + bump: minor + - name: people-rococo-emulated-chain + bump: minor + - name: coretime-westend-emulated-chain + bump: minor + - name: coretime-rococo-emulated-chain + bump: minor + - name: collectives-westend-emulated-chain + bump: minor + - name: bridge-hub-westend-emulated-chain + bump: minor + - name: bridge-hub-rococo-emulated-chain + bump: minor + - name: asset-hub-westend-emulated-chain + bump: minor + - name: asset-hub-rococo-emulated-chain + bump: minor + - name: pallet-collator-selection + bump: patch + - name: pallet-bridge-messages + bump: patch diff --git a/substrate/bin/node/cli/tests/res/default_genesis_config.json b/substrate/bin/node/cli/tests/res/default_genesis_config.json index a2e52837d882..8ad2428f7855 100644 --- a/substrate/bin/node/cli/tests/res/default_genesis_config.json +++ b/substrate/bin/node/cli/tests/res/default_genesis_config.json @@ -14,7 +14,8 @@ "indices": [] }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "broker": {}, "transactionPayment": { diff --git a/substrate/bin/node/testing/src/genesis.rs b/substrate/bin/node/testing/src/genesis.rs index 0394f6cd7394..624b00b4d6c2 100644 --- a/substrate/bin/node/testing/src/genesis.rs +++ b/substrate/bin/node/testing/src/genesis.rs @@ -47,7 +47,7 @@ pub fn config_endowed(extra_endowed: Vec) -> RuntimeGenesisConfig { RuntimeGenesisConfig { indices: IndicesConfig { indices: vec![] }, - balances: BalancesConfig { balances: endowed }, + balances: BalancesConfig { balances: endowed, ..Default::default() }, session: SessionConfig { keys: vec![ (alice(), dave(), session_keys_from_seed(Ed25519Keyring::Alice.into())), diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json b/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json index ac67aef93345..77891ac93ead 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/create_default.json @@ -25,7 +25,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json b/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json index 7106b4b50dc5..22b0ca6571b4 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/create_parachain.json @@ -27,7 +27,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json b/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json index 5aedd5b5c18b..641df669e188 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/create_with_params.json @@ -25,7 +25,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json index 203b6716cb26..e5957624ead2 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/create_default.json @@ -24,7 +24,8 @@ } }, "balances": { - "balances": [] + "balances": [], + "devAccounts": null }, "substrateTest": { "authorities": [] diff --git a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json index 6aa6799af771..6bbb475d35c7 100644 --- a/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json +++ b/substrate/bin/utils/chain-spec-builder/tests/expected/doc/display_preset.json @@ -1 +1 @@ -{"babe":{"authorities":[],"epochConfig":{"allowed_slots":"PrimaryAndSecondaryVRFSlots","c":[1,4]}},"balances":{"balances":[]},"substrateTest":{"authorities":[]},"system":{}} +{"babe":{"authorities":[],"epochConfig":{"allowed_slots":"PrimaryAndSecondaryVRFSlots","c":[1,4]}},"balances":{"balances":[], "devAccounts": null},"substrateTest":{"authorities":[]},"system":{}} diff --git a/substrate/client/chain-spec/src/genesis_config_builder.rs b/substrate/client/chain-spec/src/genesis_config_builder.rs index 5fe8f9dc053c..c7b5ae4bf168 100644 --- a/substrate/client/chain-spec/src/genesis_config_builder.rs +++ b/substrate/client/chain-spec/src/genesis_config_builder.rs @@ -196,7 +196,7 @@ mod tests { ::new(substrate_test_runtime::wasm_binary_unwrap()) .get_default_config() .unwrap(); - let expected = r#"{"babe": {"authorities": [], "epochConfig": {"allowed_slots": "PrimaryAndSecondaryVRFSlots", "c": [1, 4]}}, "balances": {"balances": []}, "substrateTest": {"authorities": []}, "system": {}}"#; + let expected = r#"{"babe": {"authorities": [], "epochConfig": {"allowed_slots": "PrimaryAndSecondaryVRFSlots", "c": [1, 4]}}, "balances": {"balances": [], "devAccounts": null}, "substrateTest": {"authorities": []}, "system": {}}"#; assert_eq!(from_str::(expected).unwrap(), config); } diff --git a/substrate/frame/alliance/src/mock.rs b/substrate/frame/alliance/src/mock.rs index 625cabf3457f..069c29a88d38 100644 --- a/substrate/frame/alliance/src/mock.rs +++ b/substrate/frame/alliance/src/mock.rs @@ -283,6 +283,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (8, 1000), (9, 1000), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/asset-conversion/ops/src/mock.rs b/substrate/frame/asset-conversion/ops/src/mock.rs index 5c05faa6aa88..576b266b39c1 100644 --- a/substrate/frame/asset-conversion/ops/src/mock.rs +++ b/substrate/frame/asset-conversion/ops/src/mock.rs @@ -135,6 +135,7 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { balances: vec![(1, 10000), (2, 20000), (3, 30000), (4, 40000)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/asset-conversion/src/mock.rs b/substrate/frame/asset-conversion/src/mock.rs index d8832d70488a..313d9f9857e4 100644 --- a/substrate/frame/asset-conversion/src/mock.rs +++ b/substrate/frame/asset-conversion/src/mock.rs @@ -162,6 +162,7 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { balances: vec![(1, 10000), (2, 20000), (3, 30000), (4, 40000)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/asset-rewards/src/mock.rs b/substrate/frame/asset-rewards/src/mock.rs index 87c8a8a0dea0..1e9b41104d4c 100644 --- a/substrate/frame/asset-rewards/src/mock.rs +++ b/substrate/frame/asset-rewards/src/mock.rs @@ -211,6 +211,7 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities { (20, 40000), (pool_zero_account_id, 100_000), // Top up the default pool account id ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/atomic-swap/src/tests.rs b/substrate/frame/atomic-swap/src/tests.rs index 6fcc5571a523..d6384fab343d 100644 --- a/substrate/frame/atomic-swap/src/tests.rs +++ b/substrate/frame/atomic-swap/src/tests.rs @@ -54,7 +54,10 @@ const B: u64 = 2; pub fn new_test_ext() -> TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let genesis = pallet_balances::GenesisConfig:: { balances: vec![(A, 100), (B, 200)] }; + let genesis = pallet_balances::GenesisConfig:: { + balances: vec![(A, 100), (B, 200)], + ..Default::default() + }; genesis.assimilate_storage(&mut t).unwrap(); t.into() } diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index 8d00509e800b..6f9f54cc7efc 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -314,7 +314,7 @@ pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::Tes let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/balances/Cargo.toml b/substrate/frame/balances/Cargo.toml index 03bc7fcb3fcc..4255ed414360 100644 --- a/substrate/frame/balances/Cargo.toml +++ b/substrate/frame/balances/Cargo.toml @@ -23,13 +23,15 @@ frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } +sp-core = { workspace = true } sp-runtime = { workspace = true } [dev-dependencies] -frame-support = { features = ["experimental"], workspace = true, default-features = true } +frame-support = { features = [ + "experimental", +], workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } paste = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } [features] diff --git a/substrate/frame/balances/src/lib.rs b/substrate/frame/balances/src/lib.rs index 9d7401452101..e994f05a77c5 100644 --- a/substrate/frame/balances/src/lib.rs +++ b/substrate/frame/balances/src/lib.rs @@ -152,7 +152,11 @@ pub mod weights; extern crate alloc; -use alloc::vec::Vec; +use alloc::{ + format, + string::{String, ToString}, + vec::Vec, +}; use codec::{Codec, MaxEncodedLen}; use core::{cmp, fmt::Debug, mem, result}; use frame_support::{ @@ -173,6 +177,7 @@ use frame_support::{ use frame_system as system; pub use impl_currency::{NegativeImbalance, PositiveImbalance}; use scale_info::TypeInfo; +use sp_core::{sr25519::Pair as SrPair, Pair}; use sp_runtime::{ traits::{ AtLeast32BitUnsigned, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Saturating, @@ -180,6 +185,7 @@ use sp_runtime::{ }, ArithmeticError, DispatchError, FixedPointOperand, Perbill, RuntimeDebug, TokenError, }; + pub use types::{ AccountData, AdjustmentDirection, BalanceLock, DustCleaner, ExtraFlags, Reasons, ReserveData, }; @@ -189,6 +195,9 @@ pub use pallet::*; const LOG_TARGET: &str = "runtime::balances"; +// Default derivation(hard) for development accounts. +const DEFAULT_ADDRESS_URI: &str = "//Sender//{}"; + type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] @@ -505,11 +514,18 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig, I: 'static = ()> { pub balances: Vec<(T::AccountId, T::Balance)>, + /// Derived development accounts(Optional): + /// - `u32`: The number of development accounts to generate. + /// - `T::Balance`: The initial balance assigned to each development account. + /// - `String`: An optional derivation(hard) string template. + /// - Must include `{}` as a placeholder for account indices. + /// - Defaults to `"//Sender//{}`" if `None`. + pub dev_accounts: Option<(u32, T::Balance, Option)>, } impl, I: 'static> Default for GenesisConfig { fn default() -> Self { - Self { balances: Default::default() } + Self { balances: Default::default(), dev_accounts: None } } } @@ -540,6 +556,15 @@ pub mod pallet { "duplicate balances in genesis." ); + // Generate additional dev accounts. + if let Some((num_accounts, balance, ref derivation)) = self.dev_accounts { + // Using the provided derivation string or default to `"//Sender//{}`". + Pallet::::derive_dev_account( + num_accounts, + balance, + derivation.as_deref().unwrap_or(DEFAULT_ADDRESS_URI), + ); + } for &(ref who, free) in self.balances.iter() { frame_system::Pallet::::inc_providers(who); assert!(T::AccountStore::insert(who, AccountData { free, ..Default::default() }) @@ -1248,5 +1273,40 @@ pub mod pallet { }); Ok(actual) } + + /// Generate dev account from derivation(hard) string. + pub fn derive_dev_account(num_accounts: u32, balance: T::Balance, derivation: &str) { + // Ensure that the number of accounts is not zero. + assert!(num_accounts > 0, "num_accounts must be greater than zero"); + + assert!( + balance >= >::ExistentialDeposit::get(), + "the balance of any account should always be at least the existential deposit.", + ); + + assert!( + derivation.contains("{}"), + "Invalid derivation, expected `{{}}` as part of the derivation" + ); + + for index in 0..num_accounts { + // Replace "{}" in the derivation string with the index. + let derivation_string = derivation.replace("{}", &index.to_string()); + + // Generate the key pair from the derivation string using sr25519. + let pair: SrPair = Pair::from_string(&derivation_string, None) + .expect(&format!("Failed to parse derivation string: {derivation_string}")); + + // Convert the public key to AccountId. + let who = T::AccountId::decode(&mut &pair.public().encode()[..]) + .expect(&format!("Failed to decode public key from pair: {:?}", pair.public())); + + // Set the balance for the generated account. + Self::mutate_account_handling_dust(&who, |account| { + account.free = balance; + }) + .expect(&format!("Failed to add account to keystore: {:?}", who)); + } + } } } diff --git a/substrate/frame/balances/src/tests/currency_tests.rs b/substrate/frame/balances/src/tests/currency_tests.rs index 5ad818e5bfa2..a6377c3ad72e 100644 --- a/substrate/frame/balances/src/tests/currency_tests.rs +++ b/substrate/frame/balances/src/tests/currency_tests.rs @@ -721,7 +721,7 @@ fn burn_must_work() { fn cannot_set_genesis_value_below_ed() { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = 11); let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let _ = crate::GenesisConfig:: { balances: vec![(1, 10)] } + let _ = crate::GenesisConfig:: { balances: vec![(1, 10)], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); } @@ -730,9 +730,12 @@ fn cannot_set_genesis_value_below_ed() { #[should_panic = "duplicate balances in genesis."] fn cannot_set_genesis_value_twice() { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - let _ = crate::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (1, 15)] } - .assimilate_storage(&mut t) - .unwrap(); + let _ = crate::GenesisConfig:: { + balances: vec![(1, 10), (2, 20), (1, 15)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); } #[test] diff --git a/substrate/frame/balances/src/tests/mod.rs b/substrate/frame/balances/src/tests/mod.rs index bf49ad9f0a1f..ceb8e8134f0a 100644 --- a/substrate/frame/balances/src/tests/mod.rs +++ b/substrate/frame/balances/src/tests/mod.rs @@ -19,7 +19,10 @@ #![cfg(test)] -use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet, TotalIssuance}; +use crate::{ + self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet, TotalIssuance, + DEFAULT_ADDRESS_URI, +}; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ assert_err, assert_noop, assert_ok, assert_storage_noop, derive_impl, @@ -34,7 +37,7 @@ use frame_support::{ use frame_system::{self as system, RawOrigin}; use pallet_transaction_payment::{ChargeTransactionPayment, FungibleAdapter, Multiplier}; use scale_info::TypeInfo; -use sp_core::hexdisplay::HexDisplay; +use sp_core::{hexdisplay::HexDisplay, sr25519::Pair as SrPair, Pair}; use sp_io; use sp_runtime::{ traits::{BadOrigin, Zero}, @@ -169,6 +172,11 @@ impl ExtBuilder { } else { vec![] }, + dev_accounts: Some(( + 1000, + self.existential_deposit, + Some(DEFAULT_ADDRESS_URI.to_string()), + )), } .assimilate_storage(&mut t) .unwrap(); @@ -281,7 +289,32 @@ pub fn info_from_weight(w: Weight) -> DispatchInfo { pub fn ensure_ti_valid() { let mut sum = 0; + // Fetch the dev accounts from Account Storage. + let dev_accounts = (1000, EXISTENTIAL_DEPOSIT, DEFAULT_ADDRESS_URI.to_string()); + let (num_accounts, _balance, ref derivation) = dev_accounts; + + // Generate the dev account public keys. + let dev_account_ids: Vec<_> = (0..num_accounts) + .map(|index| { + let derivation_string = derivation.replace("{}", &index.to_string()); + let pair: SrPair = + Pair::from_string(&derivation_string, None).expect("Invalid derivation string"); + ::AccountId::decode( + &mut &pair.public().encode()[..], + ) + .unwrap() + }) + .collect(); + + // Iterate over all account keys (i.e., the account IDs). for acc in frame_system::Account::::iter_keys() { + // Skip dev accounts by checking if the account is in the dev_account_ids list. + // This also proves dev_accounts exists in storage. + if dev_account_ids.contains(&acc) { + continue; + } + + // Check if we are using the system pallet or some other custom storage for accounts. if UseSystem::get() { let data = frame_system::Pallet::::account(acc); sum += data.data.total(); @@ -291,7 +324,8 @@ pub fn ensure_ti_valid() { } } - assert_eq!(TotalIssuance::::get(), sum, "Total Issuance wrong"); + // Ensure the total issuance matches the sum of the account balances + assert_eq!(TotalIssuance::::get(), sum, "Total Issuance is incorrect"); } #[test] diff --git a/substrate/frame/beefy/src/mock.rs b/substrate/frame/beefy/src/mock.rs index 4b5f1d103b50..fc731e3bc50e 100644 --- a/substrate/frame/beefy/src/mock.rs +++ b/substrate/frame/beefy/src/mock.rs @@ -282,7 +282,7 @@ impl ExtBuilder { let balances: Vec<_> = (0..self.authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/bounties/src/tests.rs b/substrate/frame/bounties/src/tests.rs index 447d0edb4122..c9f6c1319ed1 100644 --- a/substrate/frame/bounties/src/tests.rs +++ b/substrate/frame/bounties/src/tests.rs @@ -187,7 +187,10 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system: frame_system::GenesisConfig::default(), - balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, + balances: pallet_balances::GenesisConfig { + balances: vec![(0, 100), (1, 98), (2, 1)], + ..Default::default() + }, treasury: Default::default(), treasury_1: Default::default(), } @@ -338,9 +341,12 @@ fn treasury_account_doesnt_get_deleted() { #[allow(deprecated)] fn inexistent_account_works() { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(0, 100), (1, 99), (2, 1)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(0, 100), (1, 99), (2, 1)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); // Treasury genesis config is not build thus treasury account does not exist let mut t: sp_io::TestExternalities = t.into(); @@ -977,6 +983,7 @@ fn genesis_funding_works() { pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/child-bounties/src/tests.rs b/substrate/frame/child-bounties/src/tests.rs index 939983054f66..50c8adb453e5 100644 --- a/substrate/frame/child-bounties/src/tests.rs +++ b/substrate/frame/child-bounties/src/tests.rs @@ -148,6 +148,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized at ED. balances: vec![(account_id(0), 100), (account_id(1), 98), (account_id(2), 1)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/collective/src/tests.rs b/substrate/frame/collective/src/tests.rs index c4ed17821ae8..300d5ad3772a 100644 --- a/substrate/frame/collective/src/tests.rs +++ b/substrate/frame/collective/src/tests.rs @@ -203,7 +203,10 @@ impl ExtBuilder { let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system: frame_system::GenesisConfig::default(), // balances: pallet_balances::GenesisConfig::default(), - balances: pallet_balances::GenesisConfig { balances: vec![(1, 100), (2, 200)] }, + balances: pallet_balances::GenesisConfig { + balances: vec![(1, 100), (2, 200)], + ..Default::default() + }, collective: pallet_collective::GenesisConfig { members: self.collective_members, phantom: Default::default(), diff --git a/substrate/frame/contracts/mock-network/src/lib.rs b/substrate/frame/contracts/mock-network/src/lib.rs index cb9e22439b76..c918cd39ed91 100644 --- a/substrate/frame/contracts/mock-network/src/lib.rs +++ b/substrate/frame/contracts/mock-network/src/lib.rs @@ -99,6 +99,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { (relay_sovereign_account_id(), INITIAL_BALANCE), (BOB, INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -137,6 +138,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { (parachain_sovereign_account_id(1), INITIAL_BALANCE), (parachain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/contracts/src/tests.rs b/substrate/frame/contracts/src/tests.rs index b01d0aa4fa48..9bba55f82b4e 100644 --- a/substrate/frame/contracts/src/tests.rs +++ b/substrate/frame/contracts/src/tests.rs @@ -553,7 +553,7 @@ impl ExtBuilder { sp_tracing::try_init_simple(); self.set_associated_consts(); let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![] } + pallet_balances::GenesisConfig:: { balances: vec![], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); diff --git a/substrate/frame/conviction-voting/src/tests.rs b/substrate/frame/conviction-voting/src/tests.rs index dd9ee33ee183..b1b1fab5ae50 100644 --- a/substrate/frame/conviction-voting/src/tests.rs +++ b/substrate/frame/conviction-voting/src/tests.rs @@ -160,6 +160,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs index 875279864f7a..a4546e57dab5 100644 --- a/substrate/frame/delegated-staking/src/mock.rs +++ b/substrate/frame/delegated-staking/src/mock.rs @@ -189,6 +189,7 @@ impl ExtBuilder { (GENESIS_NOMINATOR_ONE, 1000), (GENESIS_NOMINATOR_TWO, 2000), ], + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/democracy/src/tests.rs b/substrate/frame/democracy/src/tests.rs index 10e5ee75611d..777744800684 100644 --- a/substrate/frame/democracy/src/tests.rs +++ b/substrate/frame/democracy/src/tests.rs @@ -169,6 +169,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/election-provider-multi-phase/src/mock.rs b/substrate/frame/election-provider-multi-phase/src/mock.rs index 2e5ac2527203..d0797e100fcd 100644 --- a/substrate/frame/election-provider-multi-phase/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/src/mock.rs @@ -600,6 +600,7 @@ impl ExtBuilder { (999, 100), (9999, 100), ], + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index bcb25f8287b3..3a6496436187 100644 --- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -567,6 +567,7 @@ impl ExtBuilder { let _ = pallet_balances::GenesisConfig:: { balances: self.balances_builder.balances.clone(), + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/elections-phragmen/src/lib.rs b/substrate/frame/elections-phragmen/src/lib.rs index fa1c48ee65ed..4a40d44e4077 100644 --- a/substrate/frame/elections-phragmen/src/lib.rs +++ b/substrate/frame/elections-phragmen/src/lib.rs @@ -1476,6 +1476,7 @@ mod tests { (5, 50 * self.balance_factor), (6, 60 * self.balance_factor), ], + ..Default::default() }, elections: elections_phragmen::GenesisConfig:: { members: self.genesis_members, diff --git a/substrate/frame/executive/src/tests.rs b/substrate/frame/executive/src/tests.rs index 882d875f3d80..dd12a85a1114 100644 --- a/substrate/frame/executive/src/tests.rs +++ b/substrate/frame/executive/src/tests.rs @@ -576,7 +576,7 @@ fn call_transfer(dest: u64, value: u64) -> RuntimeCall { #[test] fn balance_transfer_dispatch_works() { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(1, 211)] } + pallet_balances::GenesisConfig:: { balances: vec![(1, 211)], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let xt = UncheckedXt::new_signed(call_transfer(2, 69), 1, 1.into(), tx_ext(0, 0)); @@ -598,9 +598,12 @@ fn balance_transfer_dispatch_works() { fn new_test_ext(balance_factor: Balance) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(1, 111 * balance_factor)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 111 * balance_factor)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); let mut ext: sp_io::TestExternalities = t.into(); ext.execute_with(|| { SystemCallbacksCalled::set(0); @@ -610,9 +613,12 @@ fn new_test_ext(balance_factor: Balance) -> sp_io::TestExternalities { fn new_test_ext_v0(balance_factor: Balance) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(1, 111 * balance_factor)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 111 * balance_factor)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); (t, sp_runtime::StateVersion::V0).into() } diff --git a/substrate/frame/fast-unstake/src/mock.rs b/substrate/frame/fast-unstake/src/mock.rs index cf4f5f49240e..67f7ee21e617 100644 --- a/substrate/frame/fast-unstake/src/mock.rs +++ b/substrate/frame/fast-unstake/src/mock.rs @@ -228,6 +228,7 @@ impl ExtBuilder { .chain(validators_range.clone().map(|x| (x, 7 + 1 + 100))) .chain(nominators_range.clone().map(|x| (x, 7 + 1 + 100))) .collect::>(), + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index 0a85d9ffd2b0..cb754fb7955b 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -226,7 +226,7 @@ pub fn new_test_ext_raw_authorities(authorities: AuthorityList) -> sp_io::TestEx let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/identity/src/tests.rs b/substrate/frame/identity/src/tests.rs index 01bc312723aa..c4c02a2834ac 100644 --- a/substrate/frame/identity/src/tests.rs +++ b/substrate/frame/identity/src/tests.rs @@ -105,6 +105,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (account(20), 1000), (account(30), 1000), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/indices/src/mock.rs b/substrate/frame/indices/src/mock.rs index 72bbc6dab4a4..80d0a88881f9 100644 --- a/substrate/frame/indices/src/mock.rs +++ b/substrate/frame/indices/src/mock.rs @@ -59,6 +59,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/lottery/src/mock.rs b/substrate/frame/lottery/src/mock.rs index b771ed0849f6..ea3f69b6cfc5 100644 --- a/substrate/frame/lottery/src/mock.rs +++ b/substrate/frame/lottery/src/mock.rs @@ -75,6 +75,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/multisig/src/tests.rs b/substrate/frame/multisig/src/tests.rs index 4065ce73f905..8a389314256b 100644 --- a/substrate/frame/multisig/src/tests.rs +++ b/substrate/frame/multisig/src/tests.rs @@ -75,6 +75,7 @@ pub fn new_test_ext() -> TestState { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/nis/src/mock.rs b/substrate/frame/nis/src/mock.rs index 08e69ef0de05..82b9f55b919b 100644 --- a/substrate/frame/nis/src/mock.rs +++ b/substrate/frame/nis/src/mock.rs @@ -133,6 +133,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index d943ba6f5333..7aa8019b9c42 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -314,6 +314,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let _ = pallet_balances::GenesisConfig:: { balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)], + ..Default::default() } .assimilate_storage(&mut storage) .unwrap(); diff --git a/substrate/frame/preimage/src/mock.rs b/substrate/frame/preimage/src/mock.rs index 9c72d09cae14..dec590c6a197 100644 --- a/substrate/frame/preimage/src/mock.rs +++ b/substrate/frame/preimage/src/mock.rs @@ -81,6 +81,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let balances = pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], + ..Default::default() }; balances.assimilate_storage(&mut t).unwrap(); t.into() diff --git a/substrate/frame/proxy/src/tests.rs b/substrate/frame/proxy/src/tests.rs index afc668188e6c..14389b03ac7e 100644 --- a/substrate/frame/proxy/src/tests.rs +++ b/substrate/frame/proxy/src/tests.rs @@ -133,6 +133,7 @@ pub fn new_test_ext() -> TestState { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 3)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/recovery/src/mock.rs b/substrate/frame/recovery/src/mock.rs index 86f13b0da4f7..446d507a414c 100644 --- a/substrate/frame/recovery/src/mock.rs +++ b/substrate/frame/recovery/src/mock.rs @@ -78,6 +78,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/referenda/src/mock.rs b/substrate/frame/referenda/src/mock.rs index c96a50af8658..5d36ce137d46 100644 --- a/substrate/frame/referenda/src/mock.rs +++ b/substrate/frame/referenda/src/mock.rs @@ -219,7 +219,7 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)]; - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); diff --git a/substrate/frame/revive/mock-network/src/lib.rs b/substrate/frame/revive/mock-network/src/lib.rs index adfd0016b4dd..b8c9bc13aa79 100644 --- a/substrate/frame/revive/mock-network/src/lib.rs +++ b/substrate/frame/revive/mock-network/src/lib.rs @@ -99,6 +99,7 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities { (relay_sovereign_account_id(), INITIAL_BALANCE), (BOB, INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -137,6 +138,7 @@ pub fn relay_ext() -> sp_io::TestExternalities { (parachain_sovereign_account_id(1), INITIAL_BALANCE), (parachain_account_sovereign_account_id(1, ALICE), INITIAL_BALANCE), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 90b9f053a03f..d8b60e38da5e 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -566,7 +566,7 @@ impl ExtBuilder { sp_tracing::try_init_simple(); self.set_associated_consts(); let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![] } + pallet_balances::GenesisConfig:: { balances: vec![], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); diff --git a/substrate/frame/root-offences/src/mock.rs b/substrate/frame/root-offences/src/mock.rs index 3f14dc00b560..9b319cabb09e 100644 --- a/substrate/frame/root-offences/src/mock.rs +++ b/substrate/frame/root-offences/src/mock.rs @@ -212,6 +212,7 @@ impl ExtBuilder { (31, self.balance_factor * 1000), (41, self.balance_factor * 2000), ], + ..Default::default() } .assimilate_storage(&mut storage) .unwrap(); diff --git a/substrate/frame/safe-mode/src/mock.rs b/substrate/frame/safe-mode/src/mock.rs index aaf3456272fa..2980f86abc28 100644 --- a/substrate/frame/safe-mode/src/mock.rs +++ b/substrate/frame/safe-mode/src/mock.rs @@ -233,6 +233,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { // The 0 account is NOT a special origin, the rest may be. balances: vec![(0, BAL_ACC0), (1, BAL_ACC1), (2, 5678), (3, 5678), (4, 5678)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/scored-pool/src/mock.rs b/substrate/frame/scored-pool/src/mock.rs index 7708c06e56bd..5eb9df528924 100644 --- a/substrate/frame/scored-pool/src/mock.rs +++ b/substrate/frame/scored-pool/src/mock.rs @@ -109,7 +109,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { balances.push((40, 500_000)); balances.push((99, 1)); - pallet_balances::GenesisConfig:: { balances } + pallet_balances::GenesisConfig:: { balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); pallet_scored_pool::GenesisConfig:: { diff --git a/substrate/frame/society/src/mock.rs b/substrate/frame/society/src/mock.rs index 8cb5dc823753..63fc5059279b 100644 --- a/substrate/frame/society/src/mock.rs +++ b/substrate/frame/society/src/mock.rs @@ -115,7 +115,7 @@ impl EnvBuilder { pub fn execute R>(mut self, f: F) -> R { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); self.balances.push((Society::account_id(), self.balance.max(self.pot))); - pallet_balances::GenesisConfig:: { balances: self.balances } + pallet_balances::GenesisConfig:: { balances: self.balances, ..Default::default() } .assimilate_storage(&mut t) .unwrap(); pallet_society::GenesisConfig:: { pot: self.pot } diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 6346949576fa..41fb3a31d52e 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -471,6 +471,7 @@ impl ExtBuilder { // This allows us to have a total_payout different from 0. (999, 1_000_000_000_000), ], + ..Default::default() } .assimilate_storage(&mut storage); diff --git a/substrate/frame/state-trie-migration/src/lib.rs b/substrate/frame/state-trie-migration/src/lib.rs index 1dc1a3928f2b..6e475b7067e1 100644 --- a/substrate/frame/state-trie-migration/src/lib.rs +++ b/substrate/frame/state-trie-migration/src/lib.rs @@ -1297,9 +1297,12 @@ mod mock { frame_system::GenesisConfig::::default() .assimilate_storage(&mut custom_storage) .unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(1, 1000)] } - .assimilate_storage(&mut custom_storage) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 1000)], + ..Default::default() + } + .assimilate_storage(&mut custom_storage) + .unwrap(); } sp_tracing::try_init_simple(); diff --git a/substrate/frame/statement/src/mock.rs b/substrate/frame/statement/src/mock.rs index 34afd332c083..db9d19dbbab7 100644 --- a/substrate/frame/statement/src/mock.rs +++ b/substrate/frame/statement/src/mock.rs @@ -82,6 +82,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { 500000, ), ], + ..Default::default() }; balances.assimilate_storage(&mut t).unwrap(); t.into() diff --git a/substrate/frame/tips/src/tests.rs b/substrate/frame/tips/src/tests.rs index 530efb708e41..b769ea5b3e75 100644 --- a/substrate/frame/tips/src/tests.rs +++ b/substrate/frame/tips/src/tests.rs @@ -180,7 +180,10 @@ impl Config for Test { pub fn new_test_ext() -> sp_io::TestExternalities { let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system: frame_system::GenesisConfig::default(), - balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, + balances: pallet_balances::GenesisConfig { + balances: vec![(0, 100), (1, 98), (2, 1)], + ..Default::default() + }, treasury: Default::default(), treasury_1: Default::default(), } @@ -583,6 +586,7 @@ fn genesis_funding_works() { pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs index 6ce4652fd42f..76d46aa16471 100644 --- a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs @@ -86,6 +86,7 @@ impl ExtBuilder { } else { vec![] }, + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs index 6de2e8e7da55..2aa5d8ec7bee 100644 --- a/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -81,6 +81,7 @@ impl ExtBuilder { } else { vec![] }, + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/transaction-payment/src/tests.rs b/substrate/frame/transaction-payment/src/tests.rs index bde1bf64728e..8349df306675 100644 --- a/substrate/frame/transaction-payment/src/tests.rs +++ b/substrate/frame/transaction-payment/src/tests.rs @@ -99,6 +99,7 @@ impl ExtBuilder { } else { vec![] }, + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/transaction-storage/src/mock.rs b/substrate/frame/transaction-storage/src/mock.rs index 84a77043d577..25f44b953bfb 100644 --- a/substrate/frame/transaction-storage/src/mock.rs +++ b/substrate/frame/transaction-storage/src/mock.rs @@ -65,6 +65,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { system: Default::default(), balances: pallet_balances::GenesisConfig:: { balances: vec![(1, 1000000000), (2, 100), (3, 100), (4, 100)], + ..Default::default() }, transaction_storage: pallet_transaction_storage::GenesisConfig:: { storage_period: 10, diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs index e9efb7c0956f..2c2ceac58624 100644 --- a/substrate/frame/treasury/src/tests.rs +++ b/substrate/frame/treasury/src/tests.rs @@ -221,6 +221,7 @@ impl ExtBuilder { pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized at ED. balances: vec![(0, 100), (1, 98), (2, 1)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); @@ -406,9 +407,12 @@ fn treasury_account_doesnt_get_deleted() { #[test] fn inexistent_account_works() { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![(0, 100), (1, 99), (2, 1)] } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(0, 100), (1, 99), (2, 1)], + ..Default::default() + } + .assimilate_storage(&mut t) + .unwrap(); // Treasury genesis config is not build thus treasury account does not exist let mut t: sp_io::TestExternalities = t.into(); @@ -445,6 +449,7 @@ fn genesis_funding_works() { pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized with 100. balances: vec![(0, 100), (Treasury::account_id(), initial_funding)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/tx-pause/src/mock.rs b/substrate/frame/tx-pause/src/mock.rs index fd9b3b552ccd..d543f447ca7a 100644 --- a/substrate/frame/tx-pause/src/mock.rs +++ b/substrate/frame/tx-pause/src/mock.rs @@ -157,6 +157,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { // The 0 account is NOT a special origin. The rest may be: balances: vec![(0, 1234), (1, 5678), (2, 5678), (3, 5678), (4, 5678)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/utility/src/tests.rs b/substrate/frame/utility/src/tests.rs index 274a90d77cf0..d075ec1ff82e 100644 --- a/substrate/frame/utility/src/tests.rs +++ b/substrate/frame/utility/src/tests.rs @@ -237,6 +237,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/frame/vesting/src/mock.rs b/substrate/frame/vesting/src/mock.rs index f0954a5b989c..8fae9bbf7497 100644 --- a/substrate/frame/vesting/src/mock.rs +++ b/substrate/frame/vesting/src/mock.rs @@ -94,6 +94,7 @@ impl ExtBuilder { (12, 10 * self.existential_deposit), (13, 9999 * self.existential_deposit), ], + ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/substrate/test-utils/runtime/src/genesismap.rs b/substrate/test-utils/runtime/src/genesismap.rs index 5c0c146d45a5..e9a0e4815a2b 100644 --- a/substrate/test-utils/runtime/src/genesismap.rs +++ b/substrate/test-utils/runtime/src/genesismap.rs @@ -130,7 +130,10 @@ impl GenesisStorageBuilder { authorities: authorities_sr25519.clone(), ..Default::default() }, - balances: pallet_balances::GenesisConfig { balances: self.balances.clone() }, + balances: pallet_balances::GenesisConfig { + balances: self.balances.clone(), + ..Default::default() + }, } } diff --git a/substrate/test-utils/runtime/src/lib.rs b/substrate/test-utils/runtime/src/lib.rs index 4d24354f99a7..7c092f285166 100644 --- a/substrate/test-utils/runtime/src/lib.rs +++ b/substrate/test-utils/runtime/src/lib.rs @@ -1329,7 +1329,7 @@ mod tests { .expect("default config is there"); let json = String::from_utf8(r.into()).expect("returned value is json. qed."); - let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":{"c":[1,4],"allowed_slots":"PrimaryAndSecondaryVRFSlots"}},"substrateTest":{"authorities":[]},"balances":{"balances":[]}}"#; + let expected = r#"{"system":{},"babe":{"authorities":[],"epochConfig":{"c":[1,4],"allowed_slots":"PrimaryAndSecondaryVRFSlots"}},"substrateTest":{"authorities":[]},"balances":{"balances":[],"devAccounts":null}}"#; assert_eq!(expected.to_string(), json); } From 1d7ffa036066f51a52c197e07e898097afdcac96 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 23 Jan 2025 12:54:57 +0200 Subject: [PATCH 311/340] complete abi encoded payload --- .../pallets/inbound-queue-v2/src/envelope.rs | 72 +++++++++++++++---- .../pallets/inbound-queue-v2/src/lib.rs | 16 ++--- 2 files changed, 65 insertions(+), 23 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs index 6cbbeb16ca47..6b902078c344 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs @@ -2,13 +2,19 @@ // SPDX-FileCopyrightText: 2023 Snowfork use snowbridge_core::inbound::Log; -use sp_core::{RuntimeDebug, H160}; +use alloy_core::{ + primitives::B256, + sol, + sol_types::{SolEvent, SolType}, +}; +use snowbridge_router_primitives::inbound::v2::{ + Asset::{ForeignTokenERC20, NativeTokenERC20}, + Message as MessageV2, +}; +use sp_core::{RuntimeDebug, H160, H256}; use sp_std::prelude::*; - -use alloy_core::{primitives::B256, sol, sol_types::SolEvent}; - -/** -struct AsNativeTokenERC20 { +sol! { + struct AsNativeTokenERC20 { address token_id; uint128 value; } @@ -16,8 +22,6 @@ struct AsNativeTokenERC20 { bytes32 token_id; uint128 value; } -**/ -sol! { struct EthereumAsset { uint8 kind; bytes data; @@ -42,7 +46,7 @@ pub struct Envelope { /// A nonce for enforcing replay protection and ordering. pub nonce: u64, /// The inner payload generated from the source application. - pub payload: Payload, + pub message: MessageV2, } #[derive(Copy, Clone, RuntimeDebug)] @@ -55,6 +59,8 @@ impl TryFrom<&Log> for Envelope { // Convert to B256 for Alloy decoding let topics: Vec = log.topics.iter().map(|x| B256::from_slice(x.as_ref())).collect(); + let mut substrate_assets = alloc::vec![]; + // Decode the Solidity event from raw logs let event = OutboundMessageAccepted::decode_raw_log(topics, &log.data, true).map_err( |decode_err| { @@ -68,9 +74,46 @@ impl TryFrom<&Log> for Envelope { }, )?; - // event.nonce is a `u64` - // event.payload is already the typed `Payload` struct - Ok(Self { gateway: log.address, nonce: event.nonce, payload: event.payload }) + let payload = event.payload; + + for asset in payload.assets { + match asset.kind { + 0 => { + let native_data = AsNativeTokenERC20::abi_decode(&asset.data, true) + .map_err(|_| EnvelopeDecodeError)?; + substrate_assets.push(NativeTokenERC20 { + token_id: H160::from(native_data.token_id.as_ref()), + value: native_data.value, + }); + }, + 1 => { + let foreign_data = AsForeignTokenERC20::abi_decode(&asset.data, true) + .map_err(|_| EnvelopeDecodeError)?; + substrate_assets.push(ForeignTokenERC20 { + token_id: H256::from(foreign_data.token_id.as_ref()), + value: foreign_data.value, + }); + }, + _ => return Err(EnvelopeDecodeError), + } + } + + let mut claimer = None; + if payload.claimer.len() > 0 { + claimer = Some(payload.claimer.to_vec()); + } + + let message = MessageV2 { + origin: H160::from(payload.origin.as_ref()), + assets: substrate_assets, + xcm: payload.xcm.to_vec(), + claimer, + value: payload.value, + execution_fee: payload.executionFee, + relayer_fee: payload.relayerFee, + }; + + Ok(Self { gateway: log.address, nonce: event.nonce, message }) } } @@ -117,6 +160,9 @@ mod tests { let envelope = result.unwrap(); assert_eq!(H160::from(hex!("b8ea8cb425d85536b158d661da1ef0895bb92f1d")), envelope.gateway); - assert_eq!(hex!("B8EA8cB425d85536b158d661da1ef0895Bb92F1D"), envelope.payload.origin); + assert_eq!( + H160::from(hex!("B8EA8cB425d85536b158d661da1ef0895Bb92F1D")), + envelope.message.origin + ); } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 612a346b8d1b..3a35f14b04f2 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -39,7 +39,7 @@ mod mock; #[cfg(test)] mod test; -use codec::{Decode, DecodeAll, Encode}; +use codec::{Decode, Encode}; use envelope::Envelope; use frame_support::{ traits::{ @@ -220,14 +220,10 @@ pub mod pallet { // Verify the message has not been processed ensure!(!Nonce::::get(envelope.nonce.into()), Error::::InvalidNonce); - // Decode payload into `MessageV2` - //let message = MessageV2::decode_all(&mut envelope.payload.as_ref()) - // .map_err(|_| Error::::InvalidPayload)?; + let origin_account_location = Self::account_to_location(who)?; - //let origin_account_location = Self::account_to_location(who)?; - - //let (xcm, _relayer_reward) = - // Self::do_convert(message, origin_account_location.clone())?; + let (xcm, _relayer_reward) = + Self::do_convert(envelope.message, origin_account_location.clone())?; // Todo: Deposit fee(in Ether) to RewardLeger which should cover all of: // T::RewardLeger::deposit(who, relayer_reward.into())?; @@ -242,8 +238,8 @@ pub mod pallet { // Set nonce flag to true Nonce::::set(envelope.nonce.into()); - //let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; - //Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); + let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; + Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); Ok(()) } From 66bd26d35c21ad260120129776c86870ff1dd220 Mon Sep 17 00:00:00 2001 From: "Alisher A. Khassanov" Date: Thu, 23 Jan 2025 16:01:55 +0500 Subject: [PATCH 312/340] Add `offchain_localStorageClear` RPC method (#7266) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description Closes https://github.com/paritytech/polkadot-sdk/issues/7265. ## Integration Requires changes in `https://github.com/polkadot-js/api/packages/{rpc-augment,types-support,types}` to be visible in Polkadot\Substrate Portal and in other libraries where we should explicitly state RPC methods. Accompany PR to `polkadot-js/api`: https://github.com/polkadot-js/api/pull/6070. ## Review Notes Please put the right label on my PR. --------- Co-authored-by: command-bot <> Co-authored-by: Bastian Köcher --- prdoc/pr_7266.prdoc | 13 +++++++++++++ substrate/client/offchain/src/api.rs | 8 +++++++- substrate/client/rpc-api/src/offchain/mod.rs | 4 ++++ substrate/client/rpc/src/offchain/mod.rs | 17 +++++++++++++++++ substrate/client/rpc/src/offchain/tests.rs | 13 ++++++++++++- 5 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 prdoc/pr_7266.prdoc diff --git a/prdoc/pr_7266.prdoc b/prdoc/pr_7266.prdoc new file mode 100644 index 000000000000..4fa7ddb7b41a --- /dev/null +++ b/prdoc/pr_7266.prdoc @@ -0,0 +1,13 @@ +title: Add `offchain_localStorageClear` RPC method +doc: +- audience: Node Operator + description: |- + Adds RPC method `offchain_localStorageClear` to clear the offchain local storage. +crates: +- name: sc-offchain + bump: minor +- name: sc-rpc-api + bump: minor + validate: false +- name: sc-rpc + bump: minor diff --git a/substrate/client/offchain/src/api.rs b/substrate/client/offchain/src/api.rs index a5981f14c093..7d5c07deca4f 100644 --- a/substrate/client/offchain/src/api.rs +++ b/substrate/client/offchain/src/api.rs @@ -375,7 +375,7 @@ mod tests { } #[test] - fn should_set_and_get_local_storage() { + fn should_set_get_and_clear_local_storage() { // given let kind = StorageKind::PERSISTENT; let mut api = offchain_db(); @@ -387,6 +387,12 @@ mod tests { // then assert_eq!(api.local_storage_get(kind, key), Some(b"value".to_vec())); + + // when + api.local_storage_clear(kind, key); + + // then + assert_eq!(api.local_storage_get(kind, key), None); } #[test] diff --git a/substrate/client/rpc-api/src/offchain/mod.rs b/substrate/client/rpc-api/src/offchain/mod.rs index 4dd5b066d49f..606d441231b0 100644 --- a/substrate/client/rpc-api/src/offchain/mod.rs +++ b/substrate/client/rpc-api/src/offchain/mod.rs @@ -31,6 +31,10 @@ pub trait OffchainApi { #[method(name = "offchain_localStorageSet", with_extensions)] fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<(), Error>; + /// Clear offchain local storage under given key and prefix. + #[method(name = "offchain_localStorageClear", with_extensions)] + fn clear_local_storage(&self, kind: StorageKind, key: Bytes) -> Result<(), Error>; + /// Get offchain local storage under given key and prefix. #[method(name = "offchain_localStorageGet", with_extensions)] fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result, Error>; diff --git a/substrate/client/rpc/src/offchain/mod.rs b/substrate/client/rpc/src/offchain/mod.rs index af6bc1ba58c8..f5b1b35be106 100644 --- a/substrate/client/rpc/src/offchain/mod.rs +++ b/substrate/client/rpc/src/offchain/mod.rs @@ -66,6 +66,23 @@ impl OffchainApiServer for Offchain { Ok(()) } + fn clear_local_storage( + &self, + ext: &Extensions, + kind: StorageKind, + key: Bytes, + ) -> Result<(), Error> { + check_if_safe(ext)?; + + let prefix = match kind { + StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX, + StorageKind::LOCAL => return Err(Error::UnavailableStorageKind), + }; + self.storage.write().remove(prefix, &key); + + Ok(()) + } + fn get_local_storage( &self, ext: &Extensions, diff --git a/substrate/client/rpc/src/offchain/tests.rs b/substrate/client/rpc/src/offchain/tests.rs index 41f22c2dc964..6b8225a7b5eb 100644 --- a/substrate/client/rpc/src/offchain/tests.rs +++ b/substrate/client/rpc/src/offchain/tests.rs @@ -35,9 +35,14 @@ fn local_storage_should_work() { Ok(()) ); assert_matches!( - offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), + offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key.clone()), Ok(Some(ref v)) if *v == value ); + assert_matches!( + offchain.clear_local_storage(&ext, StorageKind::PERSISTENT, key.clone()), + Ok(()) + ); + assert_matches!(offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), Ok(None)); } #[test] @@ -55,6 +60,12 @@ fn offchain_calls_considered_unsafe() { assert_eq!(e.to_string(), "RPC call is unsafe to be called externally") } ); + assert_matches!( + offchain.clear_local_storage(&ext, StorageKind::PERSISTENT, key.clone()), + Err(Error::UnsafeRpcCalled(e)) => { + assert_eq!(e.to_string(), "RPC call is unsafe to be called externally") + } + ); assert_matches!( offchain.get_local_storage(&ext, StorageKind::PERSISTENT, key), Err(Error::UnsafeRpcCalled(e)) => { From 1d3fb328cbe9166ed5d124ae3d7f144ee2ef6eb4 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 23 Jan 2025 13:39:42 +0200 Subject: [PATCH 313/340] fix tests --- .../pallets/inbound-queue-v2/src/mock.rs | 21 ++--- .../pallets/inbound-queue-v2/src/test.rs | 35 ++++++- .../primitives/router/src/inbound/mod.rs | 1 - .../primitives/router/src/inbound/payload.rs | 94 ------------------- 4 files changed, 41 insertions(+), 110 deletions(-) delete mode 100644 bridges/snowbridge/primitives/router/src/inbound/payload.rs diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs index e603ab8e270a..88b15f847813 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/mock.rs @@ -98,7 +98,7 @@ impl Verifier for MockVerifier { } } -const GATEWAY_ADDRESS: [u8; 20] = hex!["eda338e4dc46038493b885327842fd3e301cab39"]; +const GATEWAY_ADDRESS: [u8; 20] = hex!["b8ea8cb425d85536b158d661da1ef0895bb92f1d"]; #[cfg(feature = "runtime-benchmarks")] impl BenchmarkHelper for Test { @@ -149,6 +149,7 @@ parameter_types! { pub UniversalLocation: InteriorLocation = [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), Parachain(1002)].into(); pub AssetHubFromEthereum: Location = Location::new(1,[GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)),Parachain(1000)]); + pub const InitialFund: u128 = 1_000_000_000_000; } impl inbound_queue_v2::Config for Test { @@ -191,16 +192,12 @@ pub fn new_tester() -> sp_io::TestExternalities { pub fn mock_event_log() -> Log { Log { // gateway address - address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), + address: hex!("b8ea8cb425d85536b158d661da1ef0895bb92f1d").into(), topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - // channel id - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), - // message id - hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + hex!("b61699d45635baed7500944331ea827538a50dbfef79180f2079e9185da627aa").into(), ], // Nonce + Payload - data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d00e40b54020000000000000000000000000000000000000000000000000000000000").into(), + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000b8ea8cb425d85536b158d661da1ef0895bb92f1d00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000001dcd6500000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000059682f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cdeadbeef774667629726ec1fabebcec0d9139bd1c8f72a23deadbeef0000000000000000000000001dcd650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into(), } } @@ -209,14 +206,10 @@ pub fn mock_event_log_invalid_gateway() -> Log { // gateway address address: H160::zero(), topics: vec![ - hex!("7153f9357c8ea496bba60bf82e67143e27b64462b49041f8e689e1b05728f84f").into(), - // channel id - hex!("c173fac324158e77fb5840738a1a541f633cbec8884c6a601c567d2b376a0539").into(), - // message id - hex!("5f7060e971b0dc81e63f0aa41831091847d97c1a4693ac450cc128c7214e65e0").into(), + hex!("b61699d45635baed7500944331ea827538a50dbfef79180f2079e9185da627aa").into(), ], // Nonce + Payload - data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000001e000f000000000000000087d1f7fdfee7f651fabc8bfcb6e086c278b77a7d0000").into(), + data: hex!("00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000b8ea8cb425d85536b158d661da1ef0895bb92f1d00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000001dcd6500000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000059682f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cdeadbeef774667629726ec1fabebcec0d9139bd1c8f72a23deadbeef0000000000000000000000001dcd650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into(), } } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 78d2df524e14..4089f609485c 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -4,7 +4,7 @@ use super::*; use crate::{mock::*, Error}; use frame_support::{assert_noop, assert_ok}; -use snowbridge_core::inbound::Proof; +use snowbridge_core::{inbound::Proof, sibling_sovereign_account}; use sp_keyring::sr25519::Keyring; use sp_runtime::DispatchError; @@ -29,6 +29,39 @@ fn test_submit_with_invalid_gateway() { }); } +#[test] +fn test_submit_happy_path() { + new_tester().execute_with(|| { + let relayer: AccountId = Keyring::Bob.into(); + + let origin = RuntimeOrigin::signed(relayer.clone()); + + // Submit message + let message = Message { + event_log: mock_event_log(), + proof: Proof { + receipt_proof: Default::default(), + execution_proof: mock_execution_proof(), + }, + }; + + let initial_fund = InitialFund::get(); + assert_eq!(Balances::balance(&relayer), 0); + + assert_ok!(InboundQueue::submit(origin.clone(), message.clone())); + + let events = frame_system::Pallet::::events(); + assert!( + events.iter().any(|event| matches!( + event.event, + RuntimeEvent::InboundQueue(Event::MessageReceived { nonce, ..}) + if nonce == 1 + )), + "no event emit." + ); + }); +} + #[test] fn test_set_operating_mode() { new_tester().execute_with(|| { diff --git a/bridges/snowbridge/primitives/router/src/inbound/mod.rs b/bridges/snowbridge/primitives/router/src/inbound/mod.rs index fc0a32163b49..b494bc5b0e64 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/mod.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/mod.rs @@ -2,7 +2,6 @@ // SPDX-FileCopyrightText: 2023 Snowfork // SPDX-FileCopyrightText: 2021-2022 Parity Technologies (UK) Ltd. -pub mod payload; pub mod v1; pub mod v2; use codec::Encode; diff --git a/bridges/snowbridge/primitives/router/src/inbound/payload.rs b/bridges/snowbridge/primitives/router/src/inbound/payload.rs deleted file mode 100644 index 99c505c144ed..000000000000 --- a/bridges/snowbridge/primitives/router/src/inbound/payload.rs +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2023 Snowfork - -extern crate alloc; - -use crate::inbound::v2::{ - Asset::{ForeignTokenERC20, NativeTokenERC20}, - Message, -}; -use alloy_core::{sol, sol_types::SolType}; -use sp_core::{RuntimeDebug, H160, H256}; - -sol! { - struct AsNativeTokenERC20 { - address token_id; - uint128 value; - } -} - -sol! { - struct AsForeignTokenERC20 { - bytes32 token_id; - uint128 value; - } -} - -sol! { - struct EthereumAsset { - uint8 kind; - bytes data; - } -} - -sol! { - struct Payload { - address origin; - EthereumAsset[] assets; - bytes xcm; - bytes claimer; - uint128 value; - uint128 executionFee; - uint128 relayerFee; - } -} - -#[derive(Copy, Clone, RuntimeDebug)] -pub struct PayloadDecodeError; -impl TryFrom<&[u8]> for Message { - type Error = PayloadDecodeError; - - fn try_from(encoded_payload: &[u8]) -> Result { - let decoded_payload = - Payload::abi_decode(&encoded_payload, true).map_err(|_| PayloadDecodeError)?; - - let mut substrate_assets = alloc::vec![]; - - for asset in decoded_payload.assets { - match asset.kind { - 0 => { - let native_data = AsNativeTokenERC20::abi_decode(&asset.data, true) - .map_err(|_| PayloadDecodeError)?; - substrate_assets.push(NativeTokenERC20 { - token_id: H160::from(native_data.token_id.as_ref()), - value: native_data.value, - }); - }, - 1 => { - let foreign_data = AsForeignTokenERC20::abi_decode(&asset.data, true) - .map_err(|_| PayloadDecodeError)?; - substrate_assets.push(ForeignTokenERC20 { - token_id: H256::from(foreign_data.token_id.as_ref()), - value: foreign_data.value, - }); - }, - _ => return Err(PayloadDecodeError), - } - } - - let mut claimer = None; - if decoded_payload.claimer.len() > 0 { - claimer = Some(decoded_payload.claimer.to_vec()); - } - - Ok(Self { - origin: H160::from(decoded_payload.origin.as_ref()), - assets: substrate_assets, - xcm: decoded_payload.xcm.to_vec(), - claimer, - value: decoded_payload.value, - execution_fee: decoded_payload.executionFee, - relayer_fee: decoded_payload.relayerFee, - }) - } -} From 085da479dee8e09ad3de83dbc59b304bd36b4ebe Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Thu, 23 Jan 2025 12:55:14 +0100 Subject: [PATCH 314/340] Bridges small nits/improvements (#7307) This PR contains small fixes identified during work on the larger PR: https://github.com/paritytech/polkadot-sdk/issues/6906. --------- Co-authored-by: command-bot <> --- Cargo.lock | 3 --- bridges/bin/runtime-common/Cargo.toml | 2 -- bridges/bin/runtime-common/src/integrity.rs | 9 +++++---- .../integration-tests/emulated/common/Cargo.toml | 1 - .../bridge-hubs/bridge-hub-rococo/Cargo.toml | 2 -- .../src/bridge_to_bulletin_config.rs | 1 + .../src/bridge_to_westend_config.rs | 1 + .../src/bridge_to_rococo_config.rs | 1 + polkadot/xcm/xcm-builder/src/barriers.rs | 2 +- prdoc/pr_7307.prdoc | 16 ++++++++++++++++ 10 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 prdoc/pr_7307.prdoc diff --git a/Cargo.lock b/Cargo.lock index a10def370be7..5cc898714d31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2671,7 +2671,6 @@ version = "0.5.0" dependencies = [ "bp-asset-hub-rococo", "bp-asset-hub-westend", - "bp-bridge-hub-polkadot", "bp-bridge-hub-rococo", "bp-bridge-hub-westend", "bp-header-chain 0.7.0", @@ -3018,7 +3017,6 @@ dependencies = [ "bp-relayers 0.7.0", "bp-runtime 0.7.0", "bp-test-utils 0.7.0", - "bp-xcm-bridge-hub 0.2.0", "frame-support 28.0.0", "frame-system 28.0.0", "log", @@ -6436,7 +6434,6 @@ dependencies = [ "asset-test-utils 7.0.0", "bp-messages 0.7.0", "bp-xcm-bridge-hub 0.2.0", - "bridge-runtime-common 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-core 0.7.0", diff --git a/bridges/bin/runtime-common/Cargo.toml b/bridges/bin/runtime-common/Cargo.toml index 49cd086fd3eb..b5ec37a24a8d 100644 --- a/bridges/bin/runtime-common/Cargo.toml +++ b/bridges/bin/runtime-common/Cargo.toml @@ -24,7 +24,6 @@ bp-parachains = { workspace = true } bp-polkadot-core = { workspace = true } bp-relayers = { workspace = true } bp-runtime = { workspace = true } -bp-xcm-bridge-hub = { workspace = true } pallet-bridge-grandpa = { workspace = true } pallet-bridge-messages = { workspace = true } pallet-bridge-parachains = { workspace = true } @@ -63,7 +62,6 @@ std = [ "bp-relayers/std", "bp-runtime/std", "bp-test-utils/std", - "bp-xcm-bridge-hub/std", "codec/std", "frame-support/std", "frame-system/std", diff --git a/bridges/bin/runtime-common/src/integrity.rs b/bridges/bin/runtime-common/src/integrity.rs index 61dbf09109ac..0fc377090cfe 100644 --- a/bridges/bin/runtime-common/src/integrity.rs +++ b/bridges/bin/runtime-common/src/integrity.rs @@ -30,7 +30,6 @@ use pallet_bridge_messages::{ThisChainOf, WeightInfoExt as _}; // Re-export to avoid include all dependencies everywhere. #[doc(hidden)] pub mod __private { - pub use bp_xcm_bridge_hub; pub use static_assertions; } @@ -66,9 +65,9 @@ macro_rules! assert_bridge_messages_pallet_types( with_bridged_chain_messages_instance: $i:path, this_chain: $this:path, bridged_chain: $bridged:path, + expected_payload_type: $payload:path, ) => { { - use $crate::integrity::__private::bp_xcm_bridge_hub::XcmAsPlainPayload; use $crate::integrity::__private::static_assertions::assert_type_eq_all; use bp_messages::ChainWithMessages; use bp_runtime::Chain; @@ -81,8 +80,8 @@ macro_rules! assert_bridge_messages_pallet_types( assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::ThisChain, $this); assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::BridgedChain, $bridged); - assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::OutboundPayload, XcmAsPlainPayload); - assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::InboundPayload, XcmAsPlainPayload); + assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::OutboundPayload, $payload); + assert_type_eq_all!(<$r as BridgeMessagesConfig<$i>>::InboundPayload, $payload); } } ); @@ -97,6 +96,7 @@ macro_rules! assert_complete_bridge_types( with_bridged_chain_messages_instance: $mi:path, this_chain: $this:path, bridged_chain: $bridged:path, + expected_payload_type: $payload:path, ) => { $crate::assert_chain_types!(runtime: $r, this_chain: $this); $crate::assert_bridge_messages_pallet_types!( @@ -104,6 +104,7 @@ macro_rules! assert_complete_bridge_types( with_bridged_chain_messages_instance: $mi, this_chain: $this, bridged_chain: $bridged, + expected_payload_type: $payload, ); } ); diff --git a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml index e921deb9c628..4bd45ef1a87c 100644 --- a/cumulus/parachains/integration-tests/emulated/common/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/common/Cargo.toml @@ -46,6 +46,5 @@ xcm-emulator = { workspace = true, default-features = true } # Bridges bp-messages = { workspace = true, default-features = true } bp-xcm-bridge-hub = { workspace = true, default-features = true } -bridge-runtime-common = { workspace = true, default-features = true } pallet-bridge-messages = { workspace = true, default-features = true } pallet-xcm-bridge-hub = { workspace = true, default-features = true } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml index 3dba65ae99f1..b3d48adfedc5 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml @@ -86,7 +86,6 @@ testnet-parachains-constants = { features = ["rococo"], workspace = true } # Bridges bp-asset-hub-rococo = { workspace = true } bp-asset-hub-westend = { workspace = true } -bp-bridge-hub-polkadot = { workspace = true } bp-bridge-hub-rococo = { workspace = true } bp-bridge-hub-westend = { workspace = true } bp-header-chain = { workspace = true } @@ -132,7 +131,6 @@ default = ["std"] std = [ "bp-asset-hub-rococo/std", "bp-asset-hub-westend/std", - "bp-bridge-hub-polkadot/std", "bp-bridge-hub-rococo/std", "bp-bridge-hub-westend/std", "bp-header-chain/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs index 1e733503f43b..1f58e9c2f2ba 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_bulletin_config.rs @@ -203,6 +203,7 @@ mod tests { with_bridged_chain_messages_instance: WithRococoBulletinMessagesInstance, this_chain: bp_bridge_hub_rococo::BridgeHubRococo, bridged_chain: bp_polkadot_bulletin::PolkadotBulletin, + expected_payload_type: XcmAsPlainPayload, ); // we can't use `assert_complete_bridge_constants` here, because there's a trick with diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs index a14101eb454b..d394af73e747 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/bridge_to_westend_config.rs @@ -295,6 +295,7 @@ mod tests { with_bridged_chain_messages_instance: WithBridgeHubWestendMessagesInstance, this_chain: bp_bridge_hub_rococo::BridgeHubRococo, bridged_chain: bp_bridge_hub_westend::BridgeHubWestend, + expected_payload_type: XcmAsPlainPayload, ); assert_complete_with_parachain_bridge_constants::< diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs index 24e5482b7b09..a5fb33cf504d 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_rococo_config.rs @@ -323,6 +323,7 @@ mod tests { with_bridged_chain_messages_instance: WithBridgeHubRococoMessagesInstance, this_chain: bp_bridge_hub_westend::BridgeHubWestend, bridged_chain: bp_bridge_hub_rococo::BridgeHubRococo, + expected_payload_type: XcmAsPlainPayload, ); assert_complete_with_parachain_bridge_constants::< diff --git a/polkadot/xcm/xcm-builder/src/barriers.rs b/polkadot/xcm/xcm-builder/src/barriers.rs index adba9a3ef79f..27153a3f441d 100644 --- a/polkadot/xcm/xcm-builder/src/barriers.rs +++ b/polkadot/xcm/xcm-builder/src/barriers.rs @@ -490,7 +490,7 @@ impl ShouldExecute for DenyReserveTransferToRelayChain { if matches!(origin, Location { parents: 1, interior: Here }) => { log::warn!( - target: "xcm::barrier", + target: "xcm::barriers", "Unexpected ReserveAssetDeposited from the Relay Chain", ); Ok(ControlFlow::Continue(())) diff --git a/prdoc/pr_7307.prdoc b/prdoc/pr_7307.prdoc new file mode 100644 index 000000000000..b27aace0bd13 --- /dev/null +++ b/prdoc/pr_7307.prdoc @@ -0,0 +1,16 @@ +title: Bridges small nits/improvements +doc: +- audience: Runtime Dev + description: | + This PR introduces a new `expected_payload_type` parameter to the Bridges `assert_complete_bridge_types` macro. +crates: +- name: bridge-runtime-common + bump: patch +- name: bridge-hub-rococo-runtime + bump: patch +- name: bridge-hub-westend-runtime + bump: patch +- name: staging-xcm-builder + bump: patch +- name: emulated-integration-tests-common + bump: patch From 09dfe47ab2e83a07d861e0ad391bb841d88235ca Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Thu, 23 Jan 2025 14:04:16 +0200 Subject: [PATCH 315/340] print register token values --- .../pallets/inbound-queue-v2/src/envelope.rs | 1 - .../bridge-hub-westend/src/tests/snowbridge_v2.rs | 10 +++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs index 6b902078c344..a9f251d94eaf 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/envelope.rs @@ -64,7 +64,6 @@ impl TryFrom<&Log> for Envelope { // Decode the Solidity event from raw logs let event = OutboundMessageAccepted::decode_raw_log(topics, &log.data, true).map_err( |decode_err| { - println!("error is {decode_err}"); log::error!( target: "snowbridge-inbound-queue:v2", "💫 decode error {:?}", diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index 6fb1f0354bce..b3010445c030 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -119,10 +119,18 @@ fn register_token_v2() { let versioned_message_xcm = VersionedXcm::V5(xcm); let origin = EthereumGatewayAddress::get(); + let encoded_xcm = versioned_message_xcm.encode(); + + let hex_string = hex::encode(encoded_xcm.clone()); + let eth_asset_value_encoded = eth_asset_value.encode(); + let eth_asset_value_hex = hex::encode(eth_asset_value_encoded); + println!("register token hex: {:x?}", hex_string); + println!("eth value hex: {:x?}", eth_asset_value_hex); + let message = Message { origin, assets: vec![], - xcm: versioned_message_xcm.encode(), + xcm: encoded_xcm, claimer: Some(claimer_bytes), // Used to pay the asset creation deposit. value: 9_000_000_000_000u128, From cfc5b6f59a1fa46aa55144bff5eb7fca14e27e2b Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Thu, 23 Jan 2025 15:00:31 +0200 Subject: [PATCH 316/340] bump lookahead to 3 for testnet genesis (#7252) This is the right value after https://github.com/paritytech/polkadot-sdk/pull/4880, which corresponds to an allowedAncestryLen of 2 (which is the default) WIll fix https://github.com/paritytech/polkadot-sdk/issues/7105 --- polkadot/runtime/rococo/src/genesis_config_presets.rs | 2 +- polkadot/runtime/westend/src/genesis_config_presets.rs | 2 +- .../tests/elastic_scaling/doesnt_break_parachains.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/polkadot/runtime/rococo/src/genesis_config_presets.rs b/polkadot/runtime/rococo/src/genesis_config_presets.rs index a96a509b0e4d..83bd1fbbc8fa 100644 --- a/polkadot/runtime/rococo/src/genesis_config_presets.rs +++ b/polkadot/runtime/rococo/src/genesis_config_presets.rs @@ -134,7 +134,7 @@ fn default_parachains_host_configuration( 1u8 << (FeatureIndex::CandidateReceiptV2 as usize), ), scheduler_params: SchedulerParams { - lookahead: 2, + lookahead: 3, group_rotation_frequency: 20, paras_availability_period: 4, ..Default::default() diff --git a/polkadot/runtime/westend/src/genesis_config_presets.rs b/polkadot/runtime/westend/src/genesis_config_presets.rs index ea5aff554e8c..729df20b3c65 100644 --- a/polkadot/runtime/westend/src/genesis_config_presets.rs +++ b/polkadot/runtime/westend/src/genesis_config_presets.rs @@ -137,7 +137,7 @@ fn default_parachains_host_configuration( 1u8 << (FeatureIndex::CandidateReceiptV2 as usize), ), scheduler_params: SchedulerParams { - lookahead: 2, + lookahead: 3, group_rotation_frequency: 20, paras_availability_period: 4, ..Default::default() diff --git a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs index f83400d2b22a..e65029d7095c 100644 --- a/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs +++ b/polkadot/zombienet-sdk-tests/tests/elastic_scaling/doesnt_break_parachains.rs @@ -120,8 +120,8 @@ async fn doesnt_break_parachains_test() -> Result<(), anyhow::Error> { assert_eq!( cq, [ - (CoreIndex(0), [para_id, para_id].into_iter().collect()), - (CoreIndex(1), [para_id, para_id].into_iter().collect()), + (CoreIndex(0), std::iter::repeat(para_id).take(3).collect()), + (CoreIndex(1), std::iter::repeat(para_id).take(3).collect()), ] .into_iter() .collect() From 6091330ae6d799bcf34d366acda7aff91c609ab1 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 23 Jan 2025 13:30:26 +0000 Subject: [PATCH 317/340] Refactor command bot and drop rejecting non paritytech members (#7231) Aims to - close #7049 - close https://github.com/paritytech/opstooling/issues/449 - close https://github.com/paritytech/opstooling/issues/463 What's changed: - removed is paritytech member check as required prerequisite to run a command - run the cmd.py script taking it from master, if someone who run this is not a member of paritytech, and from current branch, if is a member. That keeps the developer experience & easy testing if paritytech members are contributing to cmd.py - isolate the cmd job from being able to access GH App token or PR token- currently the fmt/bench/prdoc are being run with limited permissions scope, just to generate output, which then is uploaded to artifacts, and then the other job which doesn't run any files from repo, does a commit/push more securely --- .github/workflows/cmd.yml | 428 +++++++++++++++++++++----------------- 1 file changed, 240 insertions(+), 188 deletions(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 42b2eab3b9e4..50e71f2699d5 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -5,7 +5,7 @@ on: types: [created] permissions: # allow the action to comment on the PR - contents: write + contents: read issues: write pull-requests: write actions: read @@ -55,38 +55,9 @@ jobs: return 'false'; - reject-non-members: - needs: is-org-member - if: ${{ startsWith(github.event.comment.body, '/cmd') && needs.is-org-member.outputs.member != 'true' }} - runs-on: ubuntu-latest - steps: - - name: Add reaction to rejected comment - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - github.rest.reactions.createForIssueComment({ - comment_id: ${{ github.event.comment.id }}, - owner: context.repo.owner, - repo: context.repo.repo, - content: 'confused' - }) - - - name: Comment PR (Rejected) - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `Sorry, only members of the organization ${{ github.event.repository.owner.login }} members can run commands.` - }) acknowledge: - needs: is-org-member - if: ${{ startsWith(github.event.comment.body, '/cmd') && needs.is-org-member.outputs.member == 'true' }} + if: ${{ startsWith(github.event.comment.body, '/cmd') }} runs-on: ubuntu-latest steps: - name: Add reaction to triggered comment @@ -102,12 +73,11 @@ jobs: }) clean: - needs: is-org-member runs-on: ubuntu-latest steps: - name: Clean previous comments - if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--clean') && needs.is-org-member.outputs.member == 'true' }} uses: actions/github-script@v7 + if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--clean') }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -139,25 +109,72 @@ jobs: } } }) - help: - needs: [clean, is-org-member] - if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--help') && needs.is-org-member.outputs.member == 'true' }} + + get-pr-info: + if: ${{ startsWith(github.event.comment.body, '/cmd') }} runs-on: ubuntu-latest + outputs: + CMD: ${{ steps.get-comment.outputs.group2 }} + pr-branch: ${{ steps.get-pr.outputs.pr_branch }} + repo: ${{ steps.get-pr.outputs.repo }} steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Get command uses: actions-ecosystem/action-regex-match@v2 - id: get-pr-comment + id: get-comment with: text: ${{ github.event.comment.body }} regex: "^(\\/cmd )([-\\/\\s\\w.=:]+)$" # see explanation in docs/contributor/commands-readme.md#examples + + # Get PR branch name, because the issue_comment event does not contain the PR branch name + - name: Check if the issue is a PR + id: check-pr + run: | + if [ -n "${{ github.event.issue.pull_request.url }}" ]; then + echo "This is a pull request comment" + else + echo "This is not a pull request comment" + exit 1 + fi + + - name: Get PR Branch Name and Repo + if: steps.check-pr.outcome == 'success' + id: get-pr + uses: actions/github-script@v7 + with: + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + }); + const prBranch = pr.data.head.ref; + const repo = pr.data.head.repo.full_name; + console.log(prBranch, repo) + core.setOutput('pr_branch', prBranch); + core.setOutput('repo', repo); + + - name: Use PR Branch Name and Repo + env: + PR_BRANCH: ${{ steps.get-pr.outputs.pr_branch }} + REPO: ${{ steps.get-pr.outputs.repo }} + CMD: ${{ steps.get-comment.outputs.group2 }} + run: | + echo "The PR branch is $PR_BRANCH" + echo "The repository is $REPO" + echo "The CMD is $CMD" + + help: + needs: [clean, get-pr-info] + if: ${{ startsWith(github.event.comment.body, '/cmd') && contains(github.event.comment.body, '--help') }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 - name: Save output of help id: help env: - CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command + CMD: ${{ needs.get-pr-info.outputs.CMD }} # to avoid "" around the command run: | python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt echo 'help<> $GITHUB_OUTPUT @@ -209,9 +226,11 @@ jobs: }) set-image: - needs: [clean, is-org-member] - if: ${{ startsWith(github.event.comment.body, '/cmd') && !contains(github.event.comment.body, '--help') && needs.is-org-member.outputs.member == 'true' }} + needs: [clean, get-pr-info] + if: ${{ startsWith(github.event.comment.body, '/cmd') && !contains(github.event.comment.body, '--help') }} runs-on: ubuntu-latest + env: + CMD: ${{ needs.get-pr-info.outputs.CMD }} outputs: IMAGE: ${{ steps.set-image.outputs.IMAGE }} RUNNER: ${{ steps.set-image.outputs.RUNNER }} @@ -221,7 +240,7 @@ jobs: - id: set-image run: | - BODY=$(echo "${{ github.event.comment.body }}" | xargs) + BODY=$(echo "$CMD" | xargs) # remove whitespace IMAGE_OVERRIDE=$(echo $BODY | grep -oe 'docker.io/paritytech/ci-unified:.*\s' | xargs) cat .github/env >> $GITHUB_OUTPUT @@ -243,87 +262,17 @@ jobs: echo "RUNNER=${{ steps.set-image.outputs.RUNNER }}" echo "IMAGE=${{ steps.set-image.outputs.IMAGE }}" - # Get PR branch name, because the issue_comment event does not contain the PR branch name - get-pr-branch: - needs: [set-image] + before-cmd: + needs: [set-image, get-pr-info] runs-on: ubuntu-latest - outputs: - pr-branch: ${{ steps.get-pr.outputs.pr_branch }} - repo: ${{ steps.get-pr.outputs.repo }} - steps: - - name: Check if the issue is a PR - id: check-pr - run: | - if [ -n "${{ github.event.issue.pull_request.url }}" ]; then - echo "This is a pull request comment" - else - echo "This is not a pull request comment" - exit 1 - fi - - - name: Get PR Branch Name and Repo - if: steps.check-pr.outcome == 'success' - id: get-pr - uses: actions/github-script@v7 - with: - script: | - const pr = await github.rest.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - }); - const prBranch = pr.data.head.ref; - const repo = pr.data.head.repo.full_name; - console.log(prBranch, repo) - core.setOutput('pr_branch', prBranch); - core.setOutput('repo', repo); - - - name: Use PR Branch Name and Repo - run: | - echo "The PR branch is ${{ steps.get-pr.outputs.pr_branch }}" - echo "The repository is ${{ steps.get-pr.outputs.repo }}" - - cmd: - needs: [set-image, get-pr-branch] env: JOB_NAME: "cmd" - runs-on: ${{ needs.set-image.outputs.RUNNER }} - container: - image: ${{ needs.set-image.outputs.IMAGE }} - timeout-minutes: 1440 # 24 hours per runtime + CMD: ${{ needs.get-pr-info.outputs.CMD }} + PR_BRANCH: ${{ needs.get-pr-info.outputs.pr-branch }} + outputs: + job_url: ${{ steps.build-link.outputs.job_url }} + run_url: ${{ steps.build-link.outputs.run_url }} steps: - - name: Generate token - uses: actions/create-github-app-token@v1 - id: generate_token - with: - app-id: ${{ secrets.CMD_BOT_APP_ID }} - private-key: ${{ secrets.CMD_BOT_APP_KEY }} - - - name: Checkout - uses: actions/checkout@v4 - with: - token: ${{ steps.generate_token.outputs.token }} - repository: ${{ needs.get-pr-branch.outputs.repo }} - ref: ${{ needs.get-pr-branch.outputs.pr-branch }} - - - name: Get command - uses: actions-ecosystem/action-regex-match@v2 - id: get-pr-comment - with: - text: ${{ github.event.comment.body }} - regex: "^(\\/cmd )([-\\/\\s\\w.=:]+)$" # see explanation in docs/contributor/commands-readme.md#examples - - # In order to run prdoc without specifying the PR number, we need to add the PR number as an argument automatically - - name: Prepare PR Number argument - id: pr-arg - run: | - CMD="${{ steps.get-pr-comment.outputs.group2 }}" - if echo "$CMD" | grep -q "prdoc" && ! echo "$CMD" | grep -qE "\-\-pr[[:space:]=][0-9]+"; then - echo "arg=--pr ${{ github.event.issue.number }}" >> $GITHUB_OUTPUT - else - echo "arg=" >> $GITHUB_OUTPUT - fi - - name: Build workflow link if: ${{ !contains(github.event.comment.body, '--quiet') }} id: build-link @@ -346,40 +295,90 @@ jobs: - name: Comment PR (Start) # No need to comment on prdoc start or if --quiet - if: ${{ !contains(github.event.comment.body, '--quiet') && !contains(github.event.comment.body, 'prdoc') }} + if: ${{ !contains(github.event.comment.body, '--quiet') && !startsWith(needs.get-pr-info.outputs.CMD, 'prdoc') && !startsWith(needs.get-pr-info.outputs.CMD, 'fmt')}} uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | let job_url = ${{ steps.build-link.outputs.job_url }} - + let cmd = process.env.CMD; github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has started 🚀 [See logs here](${job_url})` + body: `Command "${cmd}" has started 🚀 [See logs here](${job_url})` }) + + cmd: + needs: [before-cmd, set-image, get-pr-info, is-org-member] + env: + CMD: ${{ needs.get-pr-info.outputs.CMD }} + PR_BRANCH: ${{ needs.get-pr-info.outputs.pr-branch }} + runs-on: ${{ needs.set-image.outputs.RUNNER }} + container: + image: ${{ needs.set-image.outputs.IMAGE }} + timeout-minutes: 1440 # 24 hours per runtime + # lowerdown permissions to separate permissions context for executable parts by contributors + permissions: + contents: read + pull-requests: none + actions: none + issues: none + outputs: + cmd_output: ${{ steps.cmd.outputs.cmd_output }} + subweight: ${{ steps.subweight.outputs.result }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + repository: ${{ needs.get-pr-info.outputs.repo }} + ref: ${{ needs.get-pr-info.outputs.pr-branch }} + + # In order to run prdoc without specifying the PR number, we need to add the PR number as an argument automatically + - name: Prepare PR Number argument + id: pr-arg + run: | + CMD="${{ needs.get-pr-info.outputs.CMD }}" + if echo "$CMD" | grep -q "prdoc" && ! echo "$CMD" | grep -qE "\-\-pr[[:space:]=][0-9]+"; then + echo "arg=--pr ${{ github.event.issue.number }}" >> $GITHUB_OUTPUT + else + echo "arg=" >> $GITHUB_OUTPUT + fi - name: Install dependencies for bench - if: startsWith(steps.get-pr-comment.outputs.group2, 'bench') + if: startsWith(needs.get-pr-info.outputs.CMD, 'bench') run: | - cargo install subweight --locked cargo install --path substrate/utils/frame/omni-bencher --locked - name: Run cmd id: cmd env: - CMD: ${{ steps.get-pr-comment.outputs.group2 }} # to avoid "" around the command PR_ARG: ${{ steps.pr-arg.outputs.arg }} + IS_ORG_MEMBER: ${{ needs.is-org-member.outputs.member }} run: | echo "Running command: '$CMD $PR_ARG' on '${{ needs.set-image.outputs.RUNNER }}' runner, container: '${{ needs.set-image.outputs.IMAGE }}'" echo "RUST_NIGHTLY_VERSION: $RUST_NIGHTLY_VERSION" - # Fixes "detected dubious ownership" error in the ci - git config --global --add safe.directory '*' - git remote -v - cat /proc/cpuinfo - python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt - python3 .github/scripts/cmd/cmd.py $CMD $PR_ARG + echo "IS_ORG_MEMBER: $IS_ORG_MEMBER" + + git config --global --add safe.directory $GITHUB_WORKSPACE + git config user.name "cmd[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + + # if the user is not an org member, we need to use the bot's path from master to avoid unwanted modifications + if [ "$IS_ORG_MEMBER" = "true" ]; then + # safe to run commands from current branch + BOT_PATH=.github + else + # going to run commands from master + TMP_DIR=/tmp/polkadot-sdk + git clone --depth 1 --branch master https://github.com/paritytech/polkadot-sdk $TMP_DIR + BOT_PATH=$TMP_DIR/.github + fi + + # install deps and run a command from master + python3 -m pip install -r $BOT_PATH/scripts/generate-prdoc.requirements.txt + python3 $BOT_PATH/scripts/cmd/cmd.py $CMD $PR_ARG git status git diff @@ -393,6 +392,11 @@ jobs: echo 'EOF' >> $GITHUB_OUTPUT fi + git add -A + git diff HEAD > /tmp/cmd/command_diff.patch -U0 + git commit -m "tmp cmd: $CMD" || true + # without push, as we're saving the diff to an artifact and subweight will compare the local branch with the remote branch + - name: Upload command output if: ${{ always() }} uses: actions/upload-artifact@v4 @@ -400,38 +404,100 @@ jobs: name: command-output path: /tmp/cmd/command_output.log - # Generate token for commit, as the earlier token expires after 1 hour, while cmd can take longer - - name: Generate token for commit - uses: actions/create-github-app-token@v1 - id: generate_token_commit + - name: Upload command diff + uses: actions/upload-artifact@v4 + with: + name: command-diff + path: /tmp/cmd/command_diff.patch + + - name: Install subweight for bench + if: startsWith(needs.get-pr-info.outputs.CMD, 'bench') + run: cargo install subweight + + - name: Run Subweight for bench + id: subweight + if: startsWith(needs.get-pr-info.outputs.CMD, 'bench') + shell: bash + run: | + git fetch + git remote -v + echo $(git log -n 2 --oneline) + + result=$(subweight compare commits \ + --path-pattern "./**/weights/**/*.rs,./**/weights.rs" \ + --method asymptotic \ + --format markdown \ + --no-color \ + --change added changed \ + --ignore-errors \ + refs/remotes/origin/master $PR_BRANCH) + + # Save the multiline result to the output + { + echo "result<> $GITHUB_OUTPUT + + after-cmd: + needs: [cmd, get-pr-info, before-cmd] + env: + CMD: ${{ needs.get-pr-info.outputs.CMD }} + PR_BRANCH: ${{ needs.get-pr-info.outputs.pr-branch }} + runs-on: ubuntu-latest + steps: + # needs to be able to trigger CI, as default token does not retrigger + - uses: actions/create-github-app-token@v1 + id: generate_token with: app-id: ${{ secrets.CMD_BOT_APP_ID }} private-key: ${{ secrets.CMD_BOT_APP_KEY }} - - name: Commit changes + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ steps.generate_token.outputs.token }} + repository: ${{ needs.get-pr-info.outputs.repo }} + ref: ${{ needs.get-pr-info.outputs.pr-branch }} + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + name: command-diff + path: command-diff + + - name: Apply & Commit changes run: | + ls -lsa . + + git config --global --add safe.directory $GITHUB_WORKSPACE + git config user.name "cmd[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --global pull.rebase false + + echo "Applying $file" + git apply "command-diff/command_diff.patch" --unidiff-zero --allow-empty + + rm -rf command-diff + + git status + if [ -n "$(git status --porcelain)" ]; then - git config --global user.name command-bot - git config --global user.email "<>" - git config --global pull.rebase false - # Push the results to the target branch - git remote add \ - github \ - "https://x-access-token:${{ steps.generate_token_commit.outputs.token }}@github.com/${{ needs.get-pr-branch.outputs.repo }}.git" || : + git remote -v push_changes() { - git push github "HEAD:${{ needs.get-pr-branch.outputs.pr-branch }}" + git push origin "HEAD:$PR_BRANCH" } git add . git restore --staged Cargo.lock # ignore changes in Cargo.lock - git commit -m "Update from ${{ github.actor }} running command '${{ steps.get-pr-comment.outputs.group2 }}'" || true + git commit -m "Update from ${{ github.actor }} running command '$CMD'" || true # Attempt to push changes if ! push_changes; then echo "Push failed, trying to rebase..." - git pull --rebase github "${{ needs.get-pr-branch.outputs.pr-branch }}" + git pull --rebase origin $PR_BRANCH # After successful rebase, try pushing again push_changes fi @@ -439,41 +505,20 @@ jobs: echo "Nothing to commit"; fi - - name: Run Subweight - id: subweight - if: startsWith(steps.get-pr-comment.outputs.group2, 'bench') - shell: bash - run: | - git fetch - result=$(subweight compare commits \ - --path-pattern "./**/weights/**/*.rs,./**/weights.rs" \ - --method asymptotic \ - --format markdown \ - --no-color \ - --change added changed \ - --ignore-errors \ - refs/remotes/origin/master refs/heads/${{ needs.get-pr-branch.outputs.pr-branch }}) - - # Save the multiline result to the output - { - echo "result<> $GITHUB_OUTPUT - - name: Comment PR (End) # No need to comment on prdoc success or --quiet - if: ${{ !failure() && !contains(github.event.comment.body, '--quiet') && !contains(github.event.comment.body, 'prdoc') }} + if: ${{ needs.cmd.result == 'success' && !contains(github.event.comment.body, '--quiet') && !startsWith(needs.get-pr-info.outputs.CMD, 'prdoc') && !startsWith(needs.get-pr-info.outputs.CMD, 'fmt') }} uses: actions/github-script@v7 env: - SUBWEIGHT: "${{ steps.subweight.outputs.result }}" - CMD_OUTPUT: "${{ steps.cmd.outputs.cmd_output }}" + SUBWEIGHT: "${{ needs.cmd.outputs.subweight }}" + CMD_OUTPUT: "${{ needs.cmd.outputs.cmd_output }}" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - let runUrl = ${{ steps.build-link.outputs.run_url }} - let subweight = process.env.SUBWEIGHT; - let cmdOutput = process.env.CMD_OUTPUT; + let runUrl = ${{ needs.before-cmd.outputs.run_url }} + let subweight = process.env.SUBWEIGHT || ''; + let cmdOutput = process.env.CMD_OUTPUT || ''; + let cmd = process.env.CMD; console.log(cmdOutput); let subweightCollapsed = subweight.trim() !== '' @@ -488,34 +533,41 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has finished ✅ [See logs here](${runUrl})${subweightCollapsed}${cmdOutputCollapsed}` + body: `Command "${cmd}" has finished ✅ [See logs here](${runUrl})${subweightCollapsed}${cmdOutputCollapsed}` }) + finish: + needs: [get-pr-info, before-cmd, after-cmd, cmd] + if: ${{ always() }} + runs-on: ubuntu-latest + env: + CMD_OUTPUT: "${{ needs.cmd.outputs.cmd_output }}" + CMD: ${{ needs.get-pr-info.outputs.CMD }} + steps: - name: Comment PR (Failure) - if: ${{ failure() && !contains(github.event.comment.body, '--quiet') }} + if: ${{ needs.cmd.result == 'failure' || needs.after-cmd.result == 'failure' }} uses: actions/github-script@v7 - env: - CMD_OUTPUT: "${{ steps.cmd.outputs.cmd_output }}" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - let jobUrl = ${{ steps.build-link.outputs.job_url }} + let jobUrl = ${{ needs.before-cmd.outputs.job_url }} let cmdOutput = process.env.CMD_OUTPUT; - - let cmdOutputCollapsed = cmdOutput.trim() !== '' - ? `
\n\nCommand output:\n\n${cmdOutput}\n\n
` - : ''; + let cmd = process.env.CMD; + let cmdOutputCollapsed = ''; + if (cmdOutput && cmdOutput.trim() !== '') { + cmdOutputCollapsed = `
\n\nCommand output:\n\n${cmdOutput}\n\n
` + } github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - body: `Command "${{ steps.get-pr-comment.outputs.group2 }}" has failed ❌! [See logs here](${jobUrl})${cmdOutputCollapsed}` + body: `Command "${cmd}" has failed ❌! [See logs here](${jobUrl})${cmdOutputCollapsed}` }) - name: Add 😕 reaction on failure + if: ${{ needs.cmd.result == 'failure' || needs.after-cmd.result == 'failure' }} uses: actions/github-script@v7 - if: ${{ failure() }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -527,8 +579,8 @@ jobs: }) - name: Add 👍 reaction on success + if: ${{ needs.cmd.result == 'success' && needs.after-cmd.result == 'success' }} uses: actions/github-script@v7 - if: ${{ !failure() }} with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | From 3a7f3c0af63b1a7566ca29c59fa4ac274bd911f1 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Thu, 23 Jan 2025 16:08:32 +0000 Subject: [PATCH 318/340] Fix setting the image properly (#7315) Fixed condition which sets weights/large images --- .github/workflows/cmd.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 50e71f2699d5..3d4779064a44 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -250,9 +250,9 @@ jobs: echo "IMAGE=$IMAGE" >> $GITHUB_OUTPUT fi - if [[ $BODY == "/cmd bench"* ]]; then + if [[ $BODY == "bench"* ]]; then echo "RUNNER=parity-weights" >> $GITHUB_OUTPUT - elif [[ $BODY == "/cmd update-ui"* ]]; then + elif [[ $BODY == "update-ui"* ]]; then echo "RUNNER=parity-large" >> $GITHUB_OUTPUT else echo "RUNNER=ubuntu-latest" >> $GITHUB_OUTPUT From e9393a9afc3b33cc2d01b7820a8f186434196758 Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:53:27 +0200 Subject: [PATCH 319/340] Deprecate ParaBackingState API (#6867) Currently the `para_backing_state` API is used only by the prospective parachains subsystems and returns 2 things: the constraints for parachain blocks and the candidates pending availability. This PR deprecates `para_backing_state` and introduces a new `backing_constraints` API that can be used together with `candidates_pending_availability` to get the same information provided by `para_backing_state`. TODO: - [x] PRDoc --------- Signed-off-by: Andrei Sandu Co-authored-by: command-bot <> --- .../src/blockchain_rpc_client.rs | 12 +- .../src/rpc_client.rs | 13 +- .../emulated/chains/relays/rococo/src/lib.rs | 2 +- .../emulated/chains/relays/westend/src/lib.rs | 2 +- .../src/fragment_chain/mod.rs | 25 +- .../src/fragment_chain/tests.rs | 1 + .../core/prospective-parachains/src/lib.rs | 89 ++++- .../core/prospective-parachains/src/tests.rs | 369 +++++++++++++++--- polkadot/node/core/runtime-api/src/cache.rs | 24 +- polkadot/node/core/runtime-api/src/lib.rs | 13 + polkadot/node/core/runtime-api/src/tests.rs | 12 +- polkadot/node/subsystem-types/src/messages.rs | 10 +- .../subsystem-types/src/runtime_client.rs | 23 +- .../src/inclusion_emulator/mod.rs | 139 +++++-- polkadot/node/subsystem-util/src/lib.rs | 7 +- polkadot/primitives/src/runtime_api.rs | 10 +- .../primitives/src/vstaging/async_backing.rs | 40 +- polkadot/primitives/src/vstaging/mod.rs | 9 +- .../node/backing/prospective-parachains.md | 3 + .../parachains/src/runtime_api_impl/v11.rs | 19 +- .../src/runtime_api_impl/vstaging.rs | 30 ++ polkadot/runtime/rococo/src/lib.rs | 15 +- polkadot/runtime/test-runtime/src/lib.rs | 1 + polkadot/runtime/westend/src/lib.rs | 15 +- prdoc/pr_6867.prdoc | 30 ++ 25 files changed, 758 insertions(+), 155 deletions(-) create mode 100644 prdoc/pr_6867.prdoc diff --git a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs index 1086e3a52ec0..862cf6af9795 100644 --- a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs +++ b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs @@ -26,7 +26,9 @@ use futures::{Stream, StreamExt}; use polkadot_core_primitives::{Block, BlockNumber, Hash, Header}; use polkadot_overseer::{ChainApiBackend, RuntimeApiSubsystemClient}; use polkadot_primitives::{ - async_backing::AsyncBackingParams, slashing, vstaging::async_backing::BackingState, + async_backing::AsyncBackingParams, + slashing, + vstaging::async_backing::{BackingState, Constraints}, ApprovalVotingParams, CoreIndex, NodeFeatures, }; use sc_authority_discovery::{AuthorityDiscovery, Error as AuthorityDiscoveryError}; @@ -454,6 +456,14 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { .parachain_host_candidates_pending_availability(at, para_id) .await?) } + + async fn backing_constraints( + &self, + at: Hash, + para_id: ParaId, + ) -> Result, ApiError> { + Ok(self.rpc_client.parachain_host_backing_constraints(at, para_id).await?) + } } #[async_trait::async_trait] diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index d7785d92c73a..0467b7085ca0 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -35,8 +35,8 @@ use cumulus_primitives_core::{ async_backing::AsyncBackingParams, slashing, vstaging::{ - async_backing::BackingState, CandidateEvent, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, + async_backing::{BackingState, Constraints}, + CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, @@ -720,6 +720,15 @@ impl RelayChainRpcClient { .await } + pub async fn parachain_host_backing_constraints( + &self, + at: RelayHash, + para_id: ParaId, + ) -> Result, RelayChainError> { + self.call_remote_runtime_function("ParachainHost_backing_constraints", at, Some(para_id)) + .await + } + fn send_register_message_to_worker( &self, message: RpcDispatcherMessage, diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs index bd637a5f7965..240c0931ae5a 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs @@ -25,7 +25,7 @@ use emulated_integration_tests_common::{ // Rococo declaration decl_test_relay_chains! { - #[api_version(11)] + #[api_version(12)] pub struct Rococo { genesis = genesis::genesis(), on_init = (), diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs index ce9fafcd5bda..729bb3ad63d1 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs @@ -25,7 +25,7 @@ use emulated_integration_tests_common::{ // Westend declaration decl_test_relay_chains! { - #[api_version(11)] + #[api_version(12)] pub struct Westend { genesis = genesis::genesis(), on_init = (), diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs index ded0a3ab73b2..72a76537160d 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs @@ -132,8 +132,8 @@ use std::{ use super::LOG_TARGET; use polkadot_node_subsystem::messages::Ancestors; use polkadot_node_subsystem_util::inclusion_emulator::{ - self, ConstraintModifications, Constraints, Fragment, HypotheticalOrConcreteCandidate, - ProspectiveCandidate, RelayChainBlockInfo, + self, validate_commitments, ConstraintModifications, Constraints, Fragment, + HypotheticalOrConcreteCandidate, ProspectiveCandidate, RelayChainBlockInfo, }; use polkadot_primitives::{ vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, BlockNumber, @@ -1052,7 +1052,7 @@ impl FragmentChain { // Try seeing if the parent candidate is in the current chain or if it is the latest // included candidate. If so, get the constraints the candidate must satisfy. - let (constraints, maybe_min_relay_parent_number) = + let (is_unconnected, constraints, maybe_min_relay_parent_number) = if let Some(parent_candidate) = self.best_chain.by_output_head.get(&parent_head_hash) { let Some(parent_candidate) = self.best_chain.chain.iter().find(|c| &c.candidate_hash == parent_candidate) @@ -1062,6 +1062,7 @@ impl FragmentChain { }; ( + false, self.scope .base_constraints .apply_modifications(&parent_candidate.cumulative_modifications) @@ -1070,11 +1071,10 @@ impl FragmentChain { ) } else if self.scope.base_constraints.required_parent.hash() == parent_head_hash { // It builds on the latest included candidate. - (self.scope.base_constraints.clone(), None) + (false, self.scope.base_constraints.clone(), None) } else { - // If the parent is not yet part of the chain, there's nothing else we can check for - // now. - return Ok(()) + // The parent is not yet part of the chain + (true, self.scope.base_constraints.clone(), None) }; // Check for cycles or invalid tree transitions. @@ -1088,6 +1088,17 @@ impl FragmentChain { candidate.persisted_validation_data(), candidate.validation_code_hash(), ) { + if is_unconnected { + // If the parent is not yet part of the chain, we can check the commitments only + // if we have the full candidate. + return validate_commitments( + &self.scope.base_constraints, + &relay_parent, + commitments, + &validation_code_hash, + ) + .map_err(Error::CheckAgainstConstraints) + } Fragment::check_against_constraints( &relay_parent, &constraints, diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs index 624dd74132c1..9e7e570bd16f 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs @@ -34,6 +34,7 @@ fn make_constraints( min_relay_parent_number, max_pov_size: 1_000_000, max_code_size: 1_000_000, + max_head_data_size: 20480, ump_remaining: 10, ump_remaining_bytes: 1_000, max_ump_num_per_candidate: 10, diff --git a/polkadot/node/core/prospective-parachains/src/lib.rs b/polkadot/node/core/prospective-parachains/src/lib.rs index 92aea8509f8c..7416c97f3cd0 100644 --- a/polkadot/node/core/prospective-parachains/src/lib.rs +++ b/polkadot/node/core/prospective-parachains/src/lib.rs @@ -45,15 +45,13 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::{ backing_implicit_view::{BlockInfoProspectiveParachains as BlockInfo, View as ImplicitView}, inclusion_emulator::{Constraints, RelayChainBlockInfo}, + request_backing_constraints, request_candidates_pending_availability, request_session_index_for_child, runtime::{fetch_claim_queue, prospective_parachains_mode, ProspectiveParachainsMode}, }; use polkadot_primitives::{ - vstaging::{ - async_backing::CandidatePendingAvailability, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - }, - BlockNumber, CandidateHash, Hash, HeadData, Header, Id as ParaId, PersistedValidationData, + vstaging::{CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState}, + BlockNumber, CandidateHash, Hash, Header, Id as ParaId, PersistedValidationData, }; use crate::{ @@ -257,8 +255,9 @@ async fn handle_active_leaves_update( let mut fragment_chains = HashMap::new(); for para in scheduled_paras { // Find constraints and pending availability candidates. - let backing_state = fetch_backing_state(ctx, hash, para).await?; - let Some((constraints, pending_availability)) = backing_state else { + let Some((constraints, pending_availability)) = + fetch_backing_constraints_and_candidates(ctx, hash, para).await? + else { // This indicates a runtime conflict of some kind. gum::debug!( target: LOG_TARGET, @@ -273,7 +272,7 @@ async fn handle_active_leaves_update( let pending_availability = preprocess_candidates_pending_availability( ctx, &mut temp_header_cache, - constraints.required_parent.clone(), + &constraints, pending_availability, ) .await?; @@ -445,22 +444,23 @@ struct ImportablePendingAvailability { async fn preprocess_candidates_pending_availability( ctx: &mut Context, cache: &mut HashMap, - required_parent: HeadData, - pending_availability: Vec, + constraints: &Constraints, + pending_availability: Vec, ) -> JfyiErrorResult> { - let mut required_parent = required_parent; + let mut required_parent = constraints.required_parent.clone(); let mut importable = Vec::new(); let expected_count = pending_availability.len(); for (i, pending) in pending_availability.into_iter().enumerate() { + let candidate_hash = pending.hash(); let Some(relay_parent) = fetch_block_info(ctx, cache, pending.descriptor.relay_parent()).await? else { let para_id = pending.descriptor.para_id(); gum::debug!( target: LOG_TARGET, - ?pending.candidate_hash, + ?candidate_hash, ?para_id, index = ?i, ?expected_count, @@ -478,12 +478,12 @@ async fn preprocess_candidates_pending_availability( }, persisted_validation_data: PersistedValidationData { parent_head: required_parent, - max_pov_size: pending.max_pov_size, + max_pov_size: constraints.max_pov_size as _, relay_parent_number: relay_parent.number, relay_parent_storage_root: relay_parent.storage_root, }, compact: fragment_chain::PendingAvailability { - candidate_hash: pending.candidate_hash, + candidate_hash, relay_parent: relay_parent.into(), }, }); @@ -883,7 +883,7 @@ async fn fetch_backing_state( ctx: &mut Context, relay_parent: Hash, para_id: ParaId, -) -> JfyiErrorResult)>> { +) -> JfyiErrorResult)>> { let (tx, rx) = oneshot::channel(); ctx.send_message(RuntimeApiMessage::Request( relay_parent, @@ -891,10 +891,63 @@ async fn fetch_backing_state( )) .await; - Ok(rx + Ok(rx.await.map_err(JfyiError::RuntimeApiRequestCanceled)??.map(|s| { + ( + From::from(s.constraints), + s.pending_availability + .into_iter() + .map(|c| CommittedCandidateReceipt { + descriptor: c.descriptor, + commitments: c.commitments, + }) + .collect(), + ) + })) +} + +#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] +async fn fetch_backing_constraints_and_candidates( + ctx: &mut Context, + relay_parent: Hash, + para_id: ParaId, +) -> JfyiErrorResult)>> { + match fetch_backing_constraints_and_candidates_inner(ctx, relay_parent, para_id).await { + Err(error) => { + gum::debug!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + ?error, + "Failed to get constraints and candidates pending availability." + ); + + // Fallback to backing state. + fetch_backing_state(ctx, relay_parent, para_id).await + }, + Ok(maybe_constraints_and_candidatest) => Ok(maybe_constraints_and_candidatest), + } +} + +#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] +async fn fetch_backing_constraints_and_candidates_inner( + ctx: &mut Context, + relay_parent: Hash, + para_id: ParaId, +) -> JfyiErrorResult)>> { + let maybe_constraints = request_backing_constraints(relay_parent, para_id, ctx.sender()) + .await .await - .map_err(JfyiError::RuntimeApiRequestCanceled)?? - .map(|s| (From::from(s.constraints), s.pending_availability))) + .map_err(JfyiError::RuntimeApiRequestCanceled)??; + + let Some(constraints) = maybe_constraints else { return Ok(None) }; + + let pending_availability = + request_candidates_pending_availability(relay_parent, para_id, ctx.sender()) + .await + .await + .map_err(JfyiError::RuntimeApiRequestCanceled)??; + + Ok(Some((From::from(constraints), pending_availability))) } #[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] diff --git a/polkadot/node/core/prospective-parachains/src/tests.rs b/polkadot/node/core/prospective-parachains/src/tests.rs index 3f1eaa4e41ed..5d1ef2f2f51c 100644 --- a/polkadot/node/core/prospective-parachains/src/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/tests.rs @@ -27,8 +27,8 @@ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::{ async_backing::{AsyncBackingParams, Constraints, InboundHrmpLimitations}, vstaging::{ - async_backing::BackingState, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, - MutateDescriptorV2, + async_backing::{BackingState, CandidatePendingAvailability, Constraints as ConstraintsV2}, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, MutateDescriptorV2, }, CoreIndex, HeadData, Header, PersistedValidationData, ScheduledCore, ValidationCodeHash, }; @@ -44,7 +44,7 @@ const ALLOWED_ANCESTRY_LEN: u32 = 3; const ASYNC_BACKING_PARAMETERS: AsyncBackingParams = AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: ALLOWED_ANCESTRY_LEN }; -const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError = +const RUNTIME_API_NOT_SUPPORTED: RuntimeApiError = RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" }; const MAX_POV_SIZE: u32 = 1_000_000; @@ -76,6 +76,31 @@ fn dummy_constraints( } } +fn dummy_constraints_v2( + min_relay_parent_number: BlockNumber, + valid_watermarks: Vec, + required_parent: HeadData, + validation_code_hash: ValidationCodeHash, +) -> ConstraintsV2 { + ConstraintsV2 { + min_relay_parent_number, + max_pov_size: MAX_POV_SIZE, + max_head_data_size: 20480, + max_code_size: 1_000_000, + ump_remaining: 10, + ump_remaining_bytes: 1_000, + max_ump_num_per_candidate: 10, + dmp_remaining_messages: vec![], + hrmp_inbound: InboundHrmpLimitations { valid_watermarks }, + hrmp_channels_out: vec![], + max_hrmp_num_per_candidate: 0, + required_parent, + validation_code_hash, + upgrade_restriction: None, + future_validation_code: None, + } +} + struct TestState { claim_queue: BTreeMap>, runtime_api_version: u32, @@ -364,47 +389,93 @@ async fn handle_leaf_activation( let paras: HashSet<_> = test_state.claim_queue.values().flatten().collect(); - for _ in 0..paras.len() { + // We expect two messages per parachain block. + for _ in 0..paras.len() * 2 { let message = virtual_overseer.recv().await; - // Get the para we are working with since the order is not deterministic. - let para_id = match &message { + let para_id = match message { + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::ParaBackingState(p_id, tx), + )) if parent == *hash => { + let PerParaData { min_relay_parent, head_data, pending_availability } = + leaf.para_data(p_id); + + let constraints = dummy_constraints( + *min_relay_parent, + vec![*number], + head_data.clone(), + test_state.validation_code_hash, + ); + + tx.send(Ok(Some(BackingState { + constraints, + pending_availability: pending_availability.clone(), + }))) + .unwrap(); + Some(p_id) + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::BackingConstraints(p_id, tx), + )) if parent == *hash && + test_state.runtime_api_version >= + RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT => + { + let PerParaData { min_relay_parent, head_data, pending_availability: _ } = + leaf.para_data(p_id); + let constraints = dummy_constraints_v2( + *min_relay_parent, + vec![*number], + head_data.clone(), + test_state.validation_code_hash, + ); + + tx.send(Ok(Some(constraints))).unwrap(); + None + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::BackingConstraints(_p_id, tx), + )) if parent == *hash && + test_state.runtime_api_version < + RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT => + { + tx.send(Err(RUNTIME_API_NOT_SUPPORTED)).unwrap(); + None + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::ParaBackingState(p_id, _), - )) => *p_id, + parent, + RuntimeApiRequest::CandidatesPendingAvailability(p_id, tx), + )) if parent == *hash => { + tx.send(Ok(leaf + .para_data(p_id) + .pending_availability + .clone() + .into_iter() + .map(|c| CommittedCandidateReceipt { + descriptor: c.descriptor, + commitments: c.commitments, + }) + .collect())) + .unwrap(); + Some(p_id) + }, _ => panic!("received unexpected message {:?}", message), }; - let PerParaData { min_relay_parent, head_data, pending_availability } = - leaf.para_data(para_id); - let constraints = dummy_constraints( - *min_relay_parent, - vec![*number], - head_data.clone(), - test_state.validation_code_hash, - ); - let backing_state = - BackingState { constraints, pending_availability: pending_availability.clone() }; - - assert_matches!( - message, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::ParaBackingState(p_id, tx)) - ) if parent == *hash && p_id == para_id => { - tx.send(Ok(Some(backing_state))).unwrap(); - } - ); - - for pending in pending_availability { - if !used_relay_parents.contains(&pending.descriptor.relay_parent()) { - send_block_header( - virtual_overseer, - pending.descriptor.relay_parent(), - pending.relay_parent_number, - ) - .await; - - used_relay_parents.insert(pending.descriptor.relay_parent()); + if let Some(para_id) = para_id { + for pending in leaf.para_data(para_id).pending_availability.clone() { + if !used_relay_parents.contains(&pending.descriptor.relay_parent()) { + send_block_header( + virtual_overseer, + pending.descriptor.relay_parent(), + pending.relay_parent_number, + ) + .await; + + used_relay_parents.insert(pending.descriptor.relay_parent()); + } } } } @@ -416,7 +487,9 @@ async fn handle_leaf_activation( msg: ProspectiveParachainsMessage::GetMinimumRelayParents(*hash, tx), }) .await; + let mut resp = rx.await.unwrap(); + resp.sort(); let mrp_response: Vec<(ParaId, BlockNumber)> = para_data .iter() @@ -597,7 +670,7 @@ fn should_do_no_work_if_async_backing_disabled_for_leaf() { AllMessages::RuntimeApi( RuntimeApiMessage::Request(parent, RuntimeApiRequest::AsyncBackingParams(tx)) ) if parent == hash => { - tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); + tx.send(Err(RUNTIME_API_NOT_SUPPORTED)).unwrap(); } ); } @@ -616,9 +689,12 @@ fn should_do_no_work_if_async_backing_disabled_for_leaf() { // - One for leaf B on parachain 1 // - One for leaf C on parachain 2 // Also tests a claim queue size larger than 1. -#[test] -fn introduce_candidates_basic() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidates_basic(#[case] runtime_api_version: u32) { let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let chain_a = ParaId::from(1); let chain_b = ParaId::from(2); @@ -786,9 +862,129 @@ fn introduce_candidates_basic() { assert_eq!(view.active_leaves.len(), 3); } -#[test] -fn introduce_candidate_multiple_times() { - let test_state = TestState::default(); +// Check if candidates are not backed if they fail constraint checks +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidates_error(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + + let view = test_harness(|mut virtual_overseer| async move { + // Leaf A + let leaf_a = TestLeaf { + number: 100, + hash: Default::default(), + para_data: vec![ + (1.into(), PerParaData::new(98, HeadData(vec![1, 2, 3]))), + (2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))), + ], + }; + + // Activate leaves. + activate_leaf_with_params( + &mut virtual_overseer, + &leaf_a, + &test_state, + AsyncBackingParams { allowed_ancestry_len: 3, max_candidate_depth: 1 }, + ) + .await; + + // Candidate A. + let (candidate_a, pvd_a) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1, 2, 3]), + HeadData(vec![1]), + test_state.validation_code_hash, + ); + + // Candidate B. + let (candidate_b, pvd_b) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1]), + HeadData(vec![1; 20480]), + test_state.validation_code_hash, + ); + + // Candidate C commits to oversized head data. + let (candidate_c, pvd_c) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1; 20480]), + HeadData(vec![0; 20485]), + test_state.validation_code_hash, + ); + + // Get hypothetical membership of candidates before adding candidate A. + // Candidate A can be added directly, candidates B and C are potential candidates. + for (candidate, pvd) in + [(candidate_a.clone(), pvd_a.clone()), (candidate_b.clone(), pvd_b.clone())] + { + get_hypothetical_membership( + &mut virtual_overseer, + candidate.hash(), + candidate, + pvd, + vec![leaf_a.hash], + ) + .await; + } + + // Fails constraints check + get_hypothetical_membership( + &mut virtual_overseer, + candidate_c.hash(), + candidate_c.clone(), + pvd_c.clone(), + Vec::new(), + ) + .await; + + // Add candidates + introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone()) + .await; + introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone()) + .await; + // Fails constraints check + introduce_seconded_candidate_failed( + &mut virtual_overseer, + candidate_c.clone(), + pvd_c.clone(), + ) + .await; + + back_candidate(&mut virtual_overseer, &candidate_a, candidate_a.hash()).await; + back_candidate(&mut virtual_overseer, &candidate_b, candidate_b.hash()).await; + // This one will not be backed. + back_candidate(&mut virtual_overseer, &candidate_c, candidate_c.hash()).await; + + // Expect only A and B to be backable + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + Ancestors::default(), + 5, + vec![(candidate_a.hash(), leaf_a.hash), (candidate_b.hash(), leaf_a.hash)], + ) + .await; + virtual_overseer + }); + + assert_eq!(view.active_leaves.len(), 1); +} + +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidate_multiple_times(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1172,9 +1368,12 @@ fn introduce_candidate_parent_leaving_view() { } // Introduce a candidate to multiple forks, see how the membership is returned. -#[test] -fn introduce_candidate_on_multiple_forks() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidate_on_multiple_forks(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf B let leaf_b = TestLeaf { @@ -1241,11 +1440,14 @@ fn introduce_candidate_on_multiple_forks() { assert_eq!(view.active_leaves.len(), 2); } -#[test] -fn unconnected_candidates_become_connected() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn unconnected_candidates_become_connected(#[case] runtime_api_version: u32) { // This doesn't test all the complicated cases with many unconnected candidates, as it's more // extensively tested in the `fragment_chain::tests` module. - let test_state = TestState::default(); + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1483,9 +1685,14 @@ fn check_backable_query_single_candidate() { } // Backs some candidates and tests `GetBackableCandidates` when requesting a multiple candidates. -#[test] -fn check_backable_query_multiple_candidates() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn check_backable_query_multiple_candidates(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1755,9 +1962,13 @@ fn check_backable_query_multiple_candidates() { } // Test hypothetical membership query. -#[test] -fn check_hypothetical_membership_query() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn check_hypothetical_membership_query(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + let view = test_harness(|mut virtual_overseer| async move { // Leaf B let leaf_b = TestLeaf { @@ -1894,6 +2105,17 @@ fn check_hypothetical_membership_query() { ); introduce_seconded_candidate_failed(&mut virtual_overseer, candidate_d, pvd_d).await; + // Candidate E has invalid head data. + let (candidate_e, pvd_e) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![2]), + HeadData(vec![0; 20481]), + test_state.validation_code_hash, + ); + introduce_seconded_candidate_failed(&mut virtual_overseer, candidate_e, pvd_e).await; + // Add candidate B and back it. introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone()) .await; @@ -1921,9 +2143,14 @@ fn check_hypothetical_membership_query() { assert_eq!(view.active_leaves.len(), 2); } -#[test] -fn check_pvd_query() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn check_pvd_query(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -2061,6 +2288,7 @@ fn check_pvd_query() { // This test is parametrised with the runtime api version. For versions that don't support the claim // queue API, we check that av-cores are used. #[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] #[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] #[case(8)] fn correctly_updates_leaves(#[case] runtime_api_version: u32) { @@ -2098,6 +2326,7 @@ fn correctly_updates_leaves(#[case] runtime_api_version: u32) { // Activate leaves. activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; + activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await; // Try activating a duplicate leaf. @@ -2161,10 +2390,15 @@ fn correctly_updates_leaves(#[case] runtime_api_version: u32) { assert_eq!(view.active_leaves.len(), 0); } -#[test] -fn handle_active_leaves_update_gets_candidates_from_parent() { - let para_id = ParaId::from(1); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn handle_active_leaves_update_gets_candidates_from_parent(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + let para_id = ParaId::from(1); test_state.claim_queue = test_state .claim_queue .into_iter() @@ -2477,9 +2711,14 @@ fn handle_active_leaves_update_bounded_implicit_view() { ); } -#[test] -fn persists_pending_availability_candidate() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn persists_pending_availability_candidate(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let para_id = ParaId::from(1); test_state.claim_queue = test_state .claim_queue diff --git a/polkadot/node/core/runtime-api/src/cache.rs b/polkadot/node/core/runtime-api/src/cache.rs index 7246010711e4..8a885ea9cc92 100644 --- a/polkadot/node/core/runtime-api/src/cache.rs +++ b/polkadot/node/core/runtime-api/src/cache.rs @@ -20,10 +20,10 @@ use schnellru::{ByLength, LruMap}; use sp_consensus_babe::Epoch; use polkadot_primitives::{ - async_backing, slashing, vstaging, + async_backing, slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, @@ -75,6 +75,7 @@ pub(crate) struct RequestResultCache { node_features: LruMap, approval_voting_params: LruMap, claim_queue: LruMap>>, + backing_constraints: LruMap<(Hash, ParaId), Option>, } impl Default for RequestResultCache { @@ -112,6 +113,7 @@ impl Default for RequestResultCache { async_backing_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), node_features: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), claim_queue: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), + backing_constraints: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), } } } @@ -559,6 +561,21 @@ impl RequestResultCache { ) { self.claim_queue.insert(relay_parent, value); } + + pub(crate) fn backing_constraints( + &mut self, + key: (Hash, ParaId), + ) -> Option<&Option> { + self.backing_constraints.get(&key).map(|v| &*v) + } + + pub(crate) fn cache_backing_constraints( + &mut self, + key: (Hash, ParaId), + value: Option, + ) { + self.backing_constraints.insert(key, value); + } } pub(crate) enum RequestResult { @@ -610,4 +627,5 @@ pub(crate) enum RequestResult { NodeFeatures(SessionIndex, NodeFeatures), ClaimQueue(Hash, BTreeMap>), CandidatesPendingAvailability(Hash, ParaId, Vec), + BackingConstraints(Hash, ParaId, Option), } diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs index c8b1d61e7be7..4889822b46a9 100644 --- a/polkadot/node/core/runtime-api/src/lib.rs +++ b/polkadot/node/core/runtime-api/src/lib.rs @@ -183,6 +183,9 @@ where ClaimQueue(relay_parent, sender) => { self.requests_cache.cache_claim_queue(relay_parent, sender); }, + BackingConstraints(relay_parent, para_id, constraints) => self + .requests_cache + .cache_backing_constraints((relay_parent, para_id), constraints), } } @@ -340,6 +343,8 @@ where }, Request::ClaimQueue(sender) => query!(claim_queue(), sender).map(|sender| Request::ClaimQueue(sender)), + Request::BackingConstraints(para, sender) => query!(backing_constraints(para), sender) + .map(|sender| Request::BackingConstraints(para, sender)), } } @@ -652,5 +657,13 @@ where ver = Request::CLAIM_QUEUE_RUNTIME_REQUIREMENT, sender ), + Request::BackingConstraints(para, sender) => { + query!( + BackingConstraints, + backing_constraints(para), + ver = Request::CONSTRAINTS_RUNTIME_REQUIREMENT, + sender + ) + }, } } diff --git a/polkadot/node/core/runtime-api/src/tests.rs b/polkadot/node/core/runtime-api/src/tests.rs index d4fa07323886..56c608769578 100644 --- a/polkadot/node/core/runtime-api/src/tests.rs +++ b/polkadot/node/core/runtime-api/src/tests.rs @@ -22,8 +22,8 @@ use polkadot_node_subsystem_test_helpers::make_subsystem_context; use polkadot_primitives::{ async_backing, slashing, vstaging, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Id as ParaId, @@ -307,6 +307,14 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient { ) -> Result>, ApiError> { todo!("Not required for tests") } + + async fn backing_constraints( + &self, + _at: Hash, + _para_id: ParaId, + ) -> Result, ApiError> { + todo!("Not required for tests") + } } #[test] diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index b541f9519219..8a3b91b3ec74 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -42,9 +42,9 @@ use polkadot_node_primitives::{ ValidationResult, }; use polkadot_primitives::{ - async_backing, slashing, vstaging, + async_backing, slashing, vstaging::{ - BackedCandidate, CandidateReceiptV2 as CandidateReceipt, + self, async_backing::Constraints, BackedCandidate, CandidateReceiptV2 as CandidateReceipt, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, @@ -772,6 +772,9 @@ pub enum RuntimeApiRequest { /// Get the candidates pending availability for a particular parachain /// `V11` CandidatesPendingAvailability(ParaId, RuntimeApiSender>), + /// Get the backing constraints for a particular parachain. + /// `V12` + BackingConstraints(ParaId, RuntimeApiSender>), } impl RuntimeApiRequest { @@ -812,6 +815,9 @@ impl RuntimeApiRequest { /// `candidates_pending_availability` pub const CANDIDATES_PENDING_AVAILABILITY_RUNTIME_REQUIREMENT: u32 = 11; + + /// `backing_constraints` + pub const CONSTRAINTS_RUNTIME_REQUIREMENT: u32 = 12; } /// A message to the Runtime API subsystem. diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs index 4b96009f44bf..018b52bedcd2 100644 --- a/polkadot/node/subsystem-types/src/runtime_client.rs +++ b/polkadot/node/subsystem-types/src/runtime_client.rs @@ -18,10 +18,10 @@ use async_trait::async_trait; use polkadot_primitives::{ async_backing, runtime_api::ParachainHost, - slashing, vstaging, + slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, Block, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Header, Id, InboundDownwardMessage, @@ -347,6 +347,15 @@ pub trait RuntimeApiSubsystemClient { at: Hash, para_id: Id, ) -> Result>, ApiError>; + + // == v12 == + /// Get the constraints on the actions that can be taken by a new parachain + /// block. + async fn backing_constraints( + &self, + at: Hash, + para_id: Id, + ) -> Result, ApiError>; } /// Default implementation of [`RuntimeApiSubsystemClient`] using the client. @@ -624,6 +633,14 @@ where async fn claim_queue(&self, at: Hash) -> Result>, ApiError> { self.client.runtime_api().claim_queue(at) } + + async fn backing_constraints( + &self, + at: Hash, + para_id: Id, + ) -> Result, ApiError> { + self.client.runtime_api().backing_constraints(at, para_id) + } } impl HeaderBackend for DefaultSubsystemClient diff --git a/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs b/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs index 48d3f27b1fa6..8a620db4ab0c 100644 --- a/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs +++ b/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs @@ -82,9 +82,10 @@ /// in practice at most once every few weeks. use polkadot_node_subsystem::messages::HypotheticalCandidate; use polkadot_primitives::{ - async_backing::Constraints as PrimitiveConstraints, vstaging::skip_ump_signals, BlockNumber, - CandidateCommitments, CandidateHash, Hash, HeadData, Id as ParaId, PersistedValidationData, - UpgradeRestriction, ValidationCodeHash, + async_backing::Constraints as OldPrimitiveConstraints, + vstaging::{async_backing::Constraints as PrimitiveConstraints, skip_ump_signals}, + BlockNumber, CandidateCommitments, CandidateHash, Hash, HeadData, Id as ParaId, + PersistedValidationData, UpgradeRestriction, ValidationCodeHash, }; use std::{collections::HashMap, sync::Arc}; @@ -115,6 +116,8 @@ pub struct Constraints { pub max_pov_size: usize, /// The maximum new validation code size allowed, in bytes. pub max_code_size: usize, + /// The maximum head-data size, in bytes. + pub max_head_data_size: usize, /// The amount of UMP messages remaining. pub ump_remaining: usize, /// The amount of UMP bytes remaining. @@ -146,6 +149,44 @@ impl From for Constraints { min_relay_parent_number: c.min_relay_parent_number, max_pov_size: c.max_pov_size as _, max_code_size: c.max_code_size as _, + max_head_data_size: c.max_head_data_size as _, + ump_remaining: c.ump_remaining as _, + ump_remaining_bytes: c.ump_remaining_bytes as _, + max_ump_num_per_candidate: c.max_ump_num_per_candidate as _, + dmp_remaining_messages: c.dmp_remaining_messages, + hrmp_inbound: InboundHrmpLimitations { + valid_watermarks: c.hrmp_inbound.valid_watermarks, + }, + hrmp_channels_out: c + .hrmp_channels_out + .into_iter() + .map(|(para_id, limits)| { + ( + para_id, + OutboundHrmpChannelLimitations { + bytes_remaining: limits.bytes_remaining as _, + messages_remaining: limits.messages_remaining as _, + }, + ) + }) + .collect(), + max_hrmp_num_per_candidate: c.max_hrmp_num_per_candidate as _, + required_parent: c.required_parent, + validation_code_hash: c.validation_code_hash, + upgrade_restriction: c.upgrade_restriction, + future_validation_code: c.future_validation_code, + } + } +} + +impl From for Constraints { + fn from(c: OldPrimitiveConstraints) -> Self { + Constraints { + min_relay_parent_number: c.min_relay_parent_number, + max_pov_size: c.max_pov_size as _, + max_code_size: c.max_code_size as _, + // Equal to Polkadot/Kusama config. + max_head_data_size: 20480, ump_remaining: c.ump_remaining as _, ump_remaining_bytes: c.ump_remaining_bytes as _, max_ump_num_per_candidate: c.max_ump_num_per_candidate as _, @@ -520,6 +561,10 @@ pub enum FragmentValidityError { /// /// Max allowed, new. CodeSizeTooLarge(usize, usize), + /// Head data size too big. + /// + /// Max allowed, new. + HeadDataTooLarge(usize, usize), /// Relay parent too old. /// /// Min allowed, current. @@ -686,28 +731,13 @@ impl Fragment { } } -fn validate_against_constraints( +/// Validates if the candidate commitments are obeying the constraints. +pub fn validate_commitments( constraints: &Constraints, relay_parent: &RelayChainBlockInfo, commitments: &CandidateCommitments, - persisted_validation_data: &PersistedValidationData, validation_code_hash: &ValidationCodeHash, - modifications: &ConstraintModifications, ) -> Result<(), FragmentValidityError> { - let expected_pvd = PersistedValidationData { - parent_head: constraints.required_parent.clone(), - relay_parent_number: relay_parent.number, - relay_parent_storage_root: relay_parent.storage_root, - max_pov_size: constraints.max_pov_size as u32, - }; - - if expected_pvd != *persisted_validation_data { - return Err(FragmentValidityError::PersistedValidationDataMismatch( - expected_pvd, - persisted_validation_data.clone(), - )) - } - if constraints.validation_code_hash != *validation_code_hash { return Err(FragmentValidityError::ValidationCodeMismatch( constraints.validation_code_hash, @@ -715,6 +745,13 @@ fn validate_against_constraints( )) } + if commitments.head_data.0.len() > constraints.max_head_data_size { + return Err(FragmentValidityError::HeadDataTooLarge( + constraints.max_head_data_size, + commitments.head_data.0.len(), + )) + } + if relay_parent.number < constraints.min_relay_parent_number { return Err(FragmentValidityError::RelayParentTooOld( constraints.min_relay_parent_number, @@ -740,6 +777,39 @@ fn validate_against_constraints( )) } + if commitments.horizontal_messages.len() > constraints.max_hrmp_num_per_candidate { + return Err(FragmentValidityError::HrmpMessagesPerCandidateOverflow { + messages_allowed: constraints.max_hrmp_num_per_candidate, + messages_submitted: commitments.horizontal_messages.len(), + }) + } + + Ok(()) +} + +fn validate_against_constraints( + constraints: &Constraints, + relay_parent: &RelayChainBlockInfo, + commitments: &CandidateCommitments, + persisted_validation_data: &PersistedValidationData, + validation_code_hash: &ValidationCodeHash, + modifications: &ConstraintModifications, +) -> Result<(), FragmentValidityError> { + validate_commitments(constraints, relay_parent, commitments, validation_code_hash)?; + + let expected_pvd = PersistedValidationData { + parent_head: constraints.required_parent.clone(), + relay_parent_number: relay_parent.number, + relay_parent_storage_root: relay_parent.storage_root, + max_pov_size: constraints.max_pov_size as u32, + }; + + if expected_pvd != *persisted_validation_data { + return Err(FragmentValidityError::PersistedValidationDataMismatch( + expected_pvd, + persisted_validation_data.clone(), + )) + } if modifications.dmp_messages_processed == 0 { if constraints .dmp_remaining_messages @@ -750,20 +820,12 @@ fn validate_against_constraints( } } - if commitments.horizontal_messages.len() > constraints.max_hrmp_num_per_candidate { - return Err(FragmentValidityError::HrmpMessagesPerCandidateOverflow { - messages_allowed: constraints.max_hrmp_num_per_candidate, - messages_submitted: commitments.horizontal_messages.len(), - }) - } - if modifications.ump_messages_sent > constraints.max_ump_num_per_candidate { return Err(FragmentValidityError::UmpMessagesPerCandidateOverflow { messages_allowed: constraints.max_ump_num_per_candidate, messages_submitted: commitments.upward_messages.len(), }) } - constraints .check_modifications(&modifications) .map_err(FragmentValidityError::OutputsInvalid) @@ -971,6 +1033,7 @@ mod tests { validation_code_hash: ValidationCode(vec![4, 5, 6]).hash(), upgrade_restriction: None, future_validation_code: None, + max_head_data_size: 1024, } } @@ -1478,4 +1541,24 @@ mod tests { Err(FragmentValidityError::HrmpMessagesDescendingOrDuplicate(1)), ); } + + #[test] + fn head_data_size_too_large() { + let relay_parent = RelayChainBlockInfo { + number: 6, + hash: Hash::repeat_byte(0xcc), + storage_root: Hash::repeat_byte(0xff), + }; + + let constraints = make_constraints(); + let mut candidate = make_candidate(&constraints, &relay_parent); + + let head_data_size = constraints.max_head_data_size; + candidate.commitments.head_data = vec![0; head_data_size + 1].into(); + + assert_eq!( + Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())), + Err(FragmentValidityError::HeadDataTooLarge(head_data_size, head_data_size + 1)), + ); + } } diff --git a/polkadot/node/subsystem-util/src/lib.rs b/polkadot/node/subsystem-util/src/lib.rs index 3bed18558941..6b069ee86113 100644 --- a/polkadot/node/subsystem-util/src/lib.rs +++ b/polkadot/node/subsystem-util/src/lib.rs @@ -43,8 +43,9 @@ use futures::channel::{mpsc, oneshot}; use polkadot_primitives::{ slashing, vstaging::{ - async_backing::BackingState, CandidateEvent, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, + async_backing::{BackingState, Constraints}, + CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, + ScrapedOnChainVotes, }, AsyncBackingParams, AuthorityDiscoveryId, CandidateHash, CoreIndex, EncodeAs, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption, @@ -313,6 +314,8 @@ specialize_requests! { fn request_async_backing_params() -> AsyncBackingParams; AsyncBackingParams; fn request_claim_queue() -> BTreeMap>; ClaimQueue; fn request_para_backing_state(para_id: ParaId) -> Option; ParaBackingState; + fn request_backing_constraints(para_id: ParaId) -> Option; BackingConstraints; + } /// Requests executor parameters from the runtime effective at given relay-parent. First obtains diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs index 3c90c050baed..df1dfbac4001 100644 --- a/polkadot/primitives/src/runtime_api.rs +++ b/polkadot/primitives/src/runtime_api.rs @@ -116,8 +116,8 @@ use crate::{ slashing, vstaging::{ - self, CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AsyncBackingParams, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, NodeFeatures, @@ -297,5 +297,11 @@ sp_api::decl_runtime_apis! { /// Elastic scaling support #[api_version(11)] fn candidates_pending_availability(para_id: ppp::Id) -> Vec>; + + /***** Added in v12 *****/ + /// Returns the constraints on the actions that can be taken by a new parachain + /// block. + #[api_version(12)] + fn backing_constraints(para_id: ppp::Id) -> Option; } } diff --git a/polkadot/primitives/src/vstaging/async_backing.rs b/polkadot/primitives/src/vstaging/async_backing.rs index 8706214b5a01..ce9954538056 100644 --- a/polkadot/primitives/src/vstaging/async_backing.rs +++ b/polkadot/primitives/src/vstaging/async_backing.rs @@ -50,12 +50,50 @@ impl From> } } +/// Constraints on the actions that can be taken by a new parachain +/// block. These limitations are implicitly associated with some particular +/// parachain, which should be apparent from usage. +#[derive(RuntimeDebug, Clone, PartialEq, Encode, Decode, TypeInfo)] +pub struct Constraints { + /// The minimum relay-parent number accepted under these constraints. + pub min_relay_parent_number: N, + /// The maximum Proof-of-Validity size allowed, in bytes. + pub max_pov_size: u32, + /// The maximum new validation code size allowed, in bytes. + pub max_code_size: u32, + /// The maximum head-data size, in bytes. + pub max_head_data_size: u32, + /// The amount of UMP messages remaining. + pub ump_remaining: u32, + /// The amount of UMP bytes remaining. + pub ump_remaining_bytes: u32, + /// The maximum number of UMP messages allowed per candidate. + pub max_ump_num_per_candidate: u32, + /// Remaining DMP queue. Only includes sent-at block numbers. + pub dmp_remaining_messages: Vec, + /// The limitations of all registered inbound HRMP channels. + pub hrmp_inbound: InboundHrmpLimitations, + /// The limitations of all registered outbound HRMP channels. + pub hrmp_channels_out: Vec<(Id, OutboundHrmpChannelLimitations)>, + /// The maximum number of HRMP messages allowed per candidate. + pub max_hrmp_num_per_candidate: u32, + /// The required parent head-data of the parachain. + pub required_parent: HeadData, + /// The expected validation-code-hash of this parachain. + pub validation_code_hash: ValidationCodeHash, + /// The code upgrade restriction signal as-of this parachain. + pub upgrade_restriction: Option, + /// The future validation code hash, if any, and at what relay-parent + /// number the upgrade would be minimally applied. + pub future_validation_code: Option<(N, ValidationCodeHash)>, +} + /// The per-parachain state of the backing system, including /// state-machine constraints and candidates pending availability. #[derive(RuntimeDebug, Clone, PartialEq, Encode, Decode, TypeInfo)] pub struct BackingState { /// The state-machine constraints of the parachain. - pub constraints: Constraints, + pub constraints: crate::async_backing::Constraints, /// The candidates pending availability. These should be ordered, i.e. they should form /// a sub-chain, where the first candidate builds on top of the required parent of the /// constraints and each subsequent builds on top of the previous head-data. diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index c52f3539c3e5..5da4595af658 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -19,10 +19,11 @@ use crate::{ValidatorIndex, ValidityAttestation}; // Put any primitives used by staging APIs functions here use super::{ - async_backing::Constraints, BlakeTwo256, BlockNumber, CandidateCommitments, - CandidateDescriptor, CandidateHash, CollatorId, CollatorSignature, CoreIndex, GroupIndex, Hash, - HashT, HeadData, Header, Id, Id as ParaId, MultiDisputeStatementSet, ScheduledCore, - UncheckedSignedAvailabilityBitfields, ValidationCodeHash, + async_backing::{InboundHrmpLimitations, OutboundHrmpChannelLimitations}, + BlakeTwo256, BlockNumber, CandidateCommitments, CandidateDescriptor, CandidateHash, CollatorId, + CollatorSignature, CoreIndex, GroupIndex, Hash, HashT, HeadData, Header, Id, Id as ParaId, + MultiDisputeStatementSet, ScheduledCore, UncheckedSignedAvailabilityBitfields, + UpgradeRestriction, ValidationCodeHash, }; use alloc::{ collections::{BTreeMap, BTreeSet, VecDeque}, diff --git a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md index 61278621cf56..0f210a078640 100644 --- a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md +++ b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md @@ -126,6 +126,9 @@ prospective validation data. This is unlikely to change. - `RuntimeApiRequest::ParaBackingState` - Gets the backing state of the given para (the constraints of the para and candidates pending availability). +- `RuntimeApiRequest::BackingConstraints` + - Gets the constraints on the actions that can be taken by a new parachain + block. - `RuntimeApiRequest::AvailabilityCores` - Gets information on all availability cores. - `ChainApiMessage::Ancestors` diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs index e9327bc7641a..3f2cb5771098 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs @@ -401,10 +401,10 @@ pub fn minimum_backing_votes() -> u32 { configuration::ActiveConfig::::get().minimum_backing_votes } -/// Implementation for `ParaBackingState` function from the runtime API -pub fn backing_state( +// Helper function that returns the backing constraints given a parachain id. +pub(crate) fn backing_constraints( para_id: ParaId, -) -> Option>> { +) -> Option>> { let config = configuration::ActiveConfig::::get(); // Async backing is only expected to be enabled with a tracker capacity of 1. // Subsequent configuration update gets applied on new session, which always @@ -458,7 +458,7 @@ pub fn backing_state( }) .collect(); - let constraints = Constraints { + Some(Constraints { min_relay_parent_number, max_pov_size: config.max_pov_size, max_code_size: config.max_code_size, @@ -473,7 +473,16 @@ pub fn backing_state( validation_code_hash, upgrade_restriction, future_validation_code, - }; + }) +} + +/// Implementation for `ParaBackingState` function from the runtime API +#[deprecated(note = "`backing_state` will be removed. Use `backing_constraints` and + `candidates_pending_availability` instead.")] +pub fn backing_state( + para_id: ParaId, +) -> Option>> { + let constraints = backing_constraints::(para_id)?; let pending_availability = { crate::inclusion::PendingAvailability::::get(¶_id) diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs index d01b543630c3..52a9a9e12288 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs @@ -15,3 +15,33 @@ // along with Polkadot. If not, see . //! Put implementations of functions from staging APIs here. + +use crate::{configuration, initializer}; +use frame_system::pallet_prelude::*; +use polkadot_primitives::{vstaging::async_backing::Constraints, Id as ParaId}; + +/// Implementation for `constraints` function from the runtime API +pub fn backing_constraints( + para_id: ParaId, +) -> Option>> { + let config = configuration::ActiveConfig::::get(); + let constraints_v11 = super::v11::backing_constraints::(para_id)?; + + Some(Constraints { + min_relay_parent_number: constraints_v11.min_relay_parent_number, + max_pov_size: constraints_v11.max_pov_size, + max_code_size: constraints_v11.max_code_size, + max_head_data_size: config.max_head_data_size, + ump_remaining: constraints_v11.ump_remaining, + ump_remaining_bytes: constraints_v11.ump_remaining_bytes, + max_ump_num_per_candidate: constraints_v11.max_ump_num_per_candidate, + dmp_remaining_messages: constraints_v11.dmp_remaining_messages, + hrmp_inbound: constraints_v11.hrmp_inbound, + hrmp_channels_out: constraints_v11.hrmp_channels_out, + max_hrmp_num_per_candidate: constraints_v11.max_hrmp_num_per_candidate, + required_parent: constraints_v11.required_parent, + validation_code_hash: constraints_v11.validation_code_hash, + upgrade_restriction: constraints_v11.upgrade_restriction, + future_validation_code: constraints_v11.future_validation_code, + }) +} diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index c2c3d35ee5b4..f165091beda4 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -49,8 +49,8 @@ use pallet_nis::WithMaximumOf; use polkadot_primitives::{ slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, AccountId, AccountIndex, ApprovalVotingParams, Balance, BlockNumber, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, @@ -78,7 +78,9 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, - runtime_api_impl::v11 as parachains_runtime_api_impl, + runtime_api_impl::{ + v11 as parachains_runtime_api_impl, vstaging as parachains_runtime_vstaging_api_impl, + }, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -1984,7 +1986,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(11)] + #[api_version(12)] impl polkadot_primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { parachains_runtime_api_impl::validators::() @@ -2122,6 +2124,7 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option { + #[allow(deprecated)] parachains_runtime_api_impl::backing_state::(para_id) } @@ -2148,6 +2151,10 @@ sp_api::impl_runtime_apis! { fn candidates_pending_availability(para_id: ParaId) -> Vec> { parachains_runtime_api_impl::candidates_pending_availability::(para_id) } + + fn backing_constraints(para_id: ParaId) -> Option { + parachains_runtime_vstaging_api_impl::backing_constraints::(para_id) + } } #[api_version(5)] diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index cdf6fa92da2f..4126193388ca 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -1067,6 +1067,7 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option { + #[allow(deprecated)] runtime_impl::backing_state::(para_id) } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index a9ba0778fe0e..935b62c23388 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -52,8 +52,8 @@ use pallet_transaction_payment::{FeeDetails, FungibleAdapter, RuntimeDispatchInf use polkadot_primitives::{ slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, AccountId, AccountIndex, ApprovalVotingParams, Balance, BlockNumber, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, @@ -84,7 +84,9 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, reward_points as parachains_reward_points, - runtime_api_impl::v11 as parachains_runtime_api_impl, + runtime_api_impl::{ + v11 as parachains_runtime_api_impl, vstaging as parachains_runtime_vstaging_api_impl, + }, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -2010,7 +2012,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(11)] + #[api_version(12)] impl polkadot_primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { parachains_runtime_api_impl::validators::() @@ -2148,6 +2150,7 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option { + #[allow(deprecated)] parachains_runtime_api_impl::backing_state::(para_id) } @@ -2174,6 +2177,10 @@ sp_api::impl_runtime_apis! { fn candidates_pending_availability(para_id: ParaId) -> Vec> { parachains_runtime_api_impl::candidates_pending_availability::(para_id) } + + fn backing_constraints(para_id: ParaId) -> Option { + parachains_runtime_vstaging_api_impl::backing_constraints::(para_id) + } } #[api_version(5)] diff --git a/prdoc/pr_6867.prdoc b/prdoc/pr_6867.prdoc new file mode 100644 index 000000000000..afa35533d463 --- /dev/null +++ b/prdoc/pr_6867.prdoc @@ -0,0 +1,30 @@ +title: Deprecate ParaBackingState API +doc: +- audience: [ Runtime Dev, Node Dev ] + description: |- + Deprecates the `para_backing_state` API. Introduces and new `backing_constraints` API that can be used + together with existing `candidates_pending_availability` to retrieve the same information provided by + `para_backing_state`. + +crates: +- name: polkadot-primitives + bump: minor +- name: polkadot-runtime-parachains + bump: minor +- name: rococo-runtime + bump: minor +- name: westend-runtime + bump: minor +- name: cumulus-relay-chain-rpc-interface + bump: minor +- name: polkadot-node-core-prospective-parachains + bump: patch +- name: polkadot-node-core-runtime-api + bump: minor +- name: polkadot-node-subsystem-types + bump: major +- name: polkadot-node-subsystem-util + bump: major +- name: cumulus-relay-chain-minimal-node + bump: minor + From f845a9f42614120c98582f598d45d6d831455305 Mon Sep 17 00:00:00 2001 From: Maksym H <1177472+mordamax@users.noreply.github.com> Date: Fri, 24 Jan 2025 09:36:16 +0000 Subject: [PATCH 320/340] bench all weekly - and fix for pallet_multisig lib (#6789) Closes #6196 Closes #7204 Example of PR: https://github.com/paritytech/polkadot-sdk/pull/6816 Every sunday 01:00 AM it's going to start to benchmark (with /cmd bench) all runtimes and all pallets Then diff total will be pushed to a branch and PR open,. I assume review-bot is going assign required reviewers per changed files I afraid each weeks will be too much to review & merge, but we can adjust later Bonus: fix for pallet_multisig lib and substrate/.maintain/frame-weight-template.hbs , which didn't let to compile new weights --------- Signed-off-by: Oliver Tale-Yazdi Co-authored-by: command-bot <> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Oliver Tale-Yazdi --- .github/workflows/bench-all-runtimes.yml | 165 ++++++++++++++++ .../src/weights/pallet_multisig.rs | 109 +++++----- .../src/weights/pallet_multisig.rs | 134 +++++++------ .../src/weights/pallet_multisig.rs | 134 +++++++------ .../src/weights/pallet_multisig.rs | 111 ++++++----- .../src/weights/pallet_multisig.rs | 126 ++++++------ .../src/weights/pallet_multisig.rs | 99 +++++----- .../src/weights/pallet_multisig.rs | 97 ++++----- .../src/weights/pallet_multisig.rs | 150 +++++++------- .../src/weights/pallet_multisig.rs | 150 +++++++------- .../rococo/src/weights/pallet_multisig.rs | 89 ++++----- .../westend/src/weights/pallet_multisig.rs | 125 ++++++------ substrate/.maintain/frame-weight-template.hbs | 3 +- substrate/frame/multisig/src/benchmarking.rs | 22 +-- substrate/frame/multisig/src/weights.rs | 186 +++++++++--------- 15 files changed, 949 insertions(+), 751 deletions(-) create mode 100644 .github/workflows/bench-all-runtimes.yml diff --git a/.github/workflows/bench-all-runtimes.yml b/.github/workflows/bench-all-runtimes.yml new file mode 100644 index 000000000000..a24a7095d980 --- /dev/null +++ b/.github/workflows/bench-all-runtimes.yml @@ -0,0 +1,165 @@ +name: Bench all runtimes + +on: + # schedule: + # - cron: '0 1 * * 0' # weekly on Sunday night 01:00 UTC + workflow_dispatch: + # pull_request: + +permissions: # allow the action to create a PR + contents: write + issues: write + pull-requests: write + actions: read + +jobs: + preflight: + uses: ./.github/workflows/reusable-preflight.yml + + runtime-matrix: + runs-on: ubuntu-latest + needs: [preflight] + timeout-minutes: 30 + outputs: + runtime: ${{ steps.runtime.outputs.runtime }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + name: Extract runtimes from matrix + steps: + - uses: actions/checkout@v4 + - id: runtime + run: | + RUNTIMES=$(jq '[.[] | select(.package != null)]' .github/workflows/runtimes-matrix.json) + + RUNTIMES=$(echo $RUNTIMES | jq -c .) + echo "runtime=$RUNTIMES" + echo "runtime=$RUNTIMES" >> $GITHUB_OUTPUT + + run-frame-omni-bencher: + needs: [preflight, runtime-matrix] + runs-on: ${{ needs.preflight.outputs.RUNNER_WEIGHTS }} + # 24 hours per runtime. + # Max it takes 14hr for westend to recalculate, but due to limited runners, + # sometimes it can take longer. + timeout-minutes: 1440 + strategy: + fail-fast: false # keep running other workflows even if one fails, to see the logs of all possible failures + matrix: + runtime: ${{ fromJSON(needs.runtime-matrix.outputs.runtime) }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + env: + PACKAGE_NAME: ${{ matrix.runtime.package }} + FLAGS: ${{ matrix.runtime.bench_flags }} + RUST_LOG: "frame_omni_bencher=info,polkadot_sdk_frame=info" + steps: + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: master + + - name: script + id: required + run: | + # Fixes "detected dubious ownership" error in the ci + git config --global --add safe.directory $GITHUB_WORKSPACE + git remote -v + python3 -m pip install -r .github/scripts/generate-prdoc.requirements.txt + python3 .github/scripts/cmd/cmd.py bench --runtime ${{ matrix.runtime.name }} + git add . + git status + + if [ -f /tmp/cmd/command_output.log ]; then + CMD_OUTPUT=$(cat /tmp/cmd/command_output.log) + # export to summary to display in the PR + echo "$CMD_OUTPUT" >> $GITHUB_STEP_SUMMARY + # should be multiline, otherwise it captures the first line only + echo 'cmd_output<> $GITHUB_OUTPUT + echo "$CMD_OUTPUT" >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT + fi + + # Create patch that includes both modifications and new files + git add -A + git diff --staged > diff-${{ matrix.runtime.name }}.patch -U0 + git reset + + - name: Upload diff + uses: actions/upload-artifact@v4 + with: + name: diff-${{ matrix.runtime.name }} + path: diff-${{ matrix.runtime.name }}.patch + + apply-diff-commit: + runs-on: ubuntu-latest + needs: [run-frame-omni-bencher] + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: master + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: patches + + - name: Install subweight + run: cargo install subweight + + # needs to be able to trigger CI + - uses: actions/create-github-app-token@v1 + id: generate_token + with: + app-id: ${{ secrets.CMD_BOT_APP_ID }} + private-key: ${{ secrets.CMD_BOT_APP_KEY }} + + - name: Apply diff and create PR + env: + GH_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + DATE=$(date +'%Y-%m-%d-%s') + BRANCH="update-weights-weekly-$DATE" + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git switch -c "$BRANCH" + + for file in patches/diff-*/diff-*.patch; do + if [ -f "$file" ] && [ -s "$file" ]; then + echo "Applying $file" + git apply "$file" --unidiff-zero --allow-empty || echo "Failed to apply $file" + else + echo "Skipping empty or non-existent patch file: $file" + fi + done + rm -rf patches + + git add . + git commit -m "Update all weights weekly for $DATE" + git push --set-upstream origin "$BRANCH" + + PR_TITLE="Auto-update of all weights for $DATE" + gh pr create \ + --title "$PR_TITLE" \ + --head "$BRANCH" \ + --base "master" \ + --reviewer paritytech/ci \ + --reviewer paritytech/release-engineering \ + --draft \ + --label "R0-silent" \ + --body "$PR_TITLE" + + subweight compare commits \ + --path-pattern "./**/weights/**/*.rs,./**/weights.rs" \ + --method asymptotic \ + --format markdown \ + --no-color \ + --change added changed \ + --ignore-errors \ + --threshold 2 \ + origin/master $BRANCH \ No newline at end of file diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs index cf9c523f6571..1192478c90ac 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=asset-hub-rococo-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_714_000 picoseconds. - Weight::from_parts(14_440_231, 0) + // Minimum execution time: 16_059_000 picoseconds. + Weight::from_parts(17_033_878, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 5 - .saturating_add(Weight::from_parts(598, 0).saturating_mul(z.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(489, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -67,15 +67,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `262 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_768_000 picoseconds. - Weight::from_parts(33_662_218, 0) + // Minimum execution time: 46_128_000 picoseconds. + Weight::from_parts(33_704_180, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_633 - .saturating_add(Weight::from_parts(128_927, 0).saturating_mul(s.into())) - // Standard Error: 16 - .saturating_add(Weight::from_parts(1_543, 0).saturating_mul(z.into())) + // Standard Error: 1_456 + .saturating_add(Weight::from_parts(147_148, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_037, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -85,15 +85,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 29_745_000 picoseconds. - Weight::from_parts(20_559_891, 0) + // Minimum execution time: 32_218_000 picoseconds. + Weight::from_parts(21_320_145, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 914 - .saturating_add(Weight::from_parts(103_601, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_504, 0).saturating_mul(z.into())) + // Standard Error: 1_922 + .saturating_add(Weight::from_parts(131_349, 0).saturating_mul(s.into())) + // Standard Error: 18 + .saturating_add(Weight::from_parts(1_829, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -105,60 +105,63 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `385 + s * (33 ±0)` + // Measured: `418 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 51_506_000 picoseconds. - Weight::from_parts(36_510_777, 0) + // Minimum execution time: 53_641_000 picoseconds. + Weight::from_parts(32_057_363, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 2_183 - .saturating_add(Weight::from_parts(183_764, 0).saturating_mul(s.into())) - // Standard Error: 21 - .saturating_add(Weight::from_parts(1_653, 0).saturating_mul(z.into())) + // Standard Error: 2_897 + .saturating_add(Weight::from_parts(254_035, 0).saturating_mul(s.into())) + // Standard Error: 28 + .saturating_add(Weight::from_parts(2_432, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 31_072_000 picoseconds. - Weight::from_parts(32_408_621, 0) + // Minimum execution time: 30_302_000 picoseconds. + Weight::from_parts(33_367_363, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 913 - .saturating_add(Weight::from_parts(121_410, 0).saturating_mul(s.into())) + // Standard Error: 1_389 + .saturating_add(Weight::from_parts(150_845, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 18_301_000 picoseconds. - Weight::from_parts(18_223_547, 0) + // Minimum execution time: 17_008_000 picoseconds. + Weight::from_parts(18_452_875, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 747 - .saturating_add(Weight::from_parts(114_584, 0).saturating_mul(s.into())) + // Standard Error: 949 + .saturating_add(Weight::from_parts(130_051, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `482 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_107_000 picoseconds. - Weight::from_parts(33_674_827, 0) + // Minimum execution time: 30_645_000 picoseconds. + Weight::from_parts(33_864_517, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_220 - .saturating_add(Weight::from_parts(122_011, 0).saturating_mul(s.into())) + // Standard Error: 1_511 + .saturating_add(Weight::from_parts(138_628, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs index 27687e10751b..737ee0f54df0 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_multisig.rs @@ -1,42 +1,43 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("asset-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=asset-hub-westend-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/assets/asset-hub-westend/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 14_098_000 picoseconds. - Weight::from_parts(14_915_657, 0) + // Minimum execution time: 16_032_000 picoseconds. + Weight::from_parts(16_636_014, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 6 - .saturating_add(Weight::from_parts(454, 0).saturating_mul(z.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(632, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -66,15 +67,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `262 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_573_000 picoseconds. - Weight::from_parts(32_633_219, 0) + // Minimum execution time: 47_519_000 picoseconds. + Weight::from_parts(33_881_382, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_256 - .saturating_add(Weight::from_parts(131_767, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_512, 0).saturating_mul(z.into())) + // Standard Error: 1_770 + .saturating_add(Weight::from_parts(159_560, 0).saturating_mul(s.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(2_031, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -84,15 +85,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 30_035_000 picoseconds. - Weight::from_parts(20_179_371, 0) + // Minimum execution time: 31_369_000 picoseconds. + Weight::from_parts(18_862_672, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 827 - .saturating_add(Weight::from_parts(110_520, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_419, 0).saturating_mul(z.into())) + // Standard Error: 1_519 + .saturating_add(Weight::from_parts(141_546, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_057, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -104,60 +105,63 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `385 + s * (33 ±0)` + // Measured: `418 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 50_444_000 picoseconds. - Weight::from_parts(36_060_265, 0) + // Minimum execution time: 55_421_000 picoseconds. + Weight::from_parts(33_628_199, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_604 - .saturating_add(Weight::from_parts(187_796, 0).saturating_mul(s.into())) - // Standard Error: 15 - .saturating_add(Weight::from_parts(1_506, 0).saturating_mul(z.into())) + // Standard Error: 2_430 + .saturating_add(Weight::from_parts(247_959, 0).saturating_mul(s.into())) + // Standard Error: 23 + .saturating_add(Weight::from_parts(2_339, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `295 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 30_298_000 picoseconds. - Weight::from_parts(31_284_628, 0) + // Minimum execution time: 30_380_000 picoseconds. + Weight::from_parts(32_147_463, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 924 - .saturating_add(Weight::from_parts(132_724, 0).saturating_mul(s.into())) + // Standard Error: 1_530 + .saturating_add(Weight::from_parts(156_234, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 17_486_000 picoseconds. - Weight::from_parts(18_518_530, 0) + // Minimum execution time: 17_016_000 picoseconds. + Weight::from_parts(17_777_791, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_274 - .saturating_add(Weight::from_parts(103_767, 0).saturating_mul(s.into())) + // Standard Error: 1_216 + .saturating_add(Weight::from_parts(137_967, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `482 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 31_236_000 picoseconds. - Weight::from_parts(32_663_816, 0) + // Minimum execution time: 31_594_000 picoseconds. + Weight::from_parts(31_850_574, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_445 - .saturating_add(Weight::from_parts(131_060, 0).saturating_mul(s.into())) + // Standard Error: 2_031 + .saturating_add(Weight::from_parts(159_513, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs index 832380d3876b..4ee6f6725409 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/pallet_multisig.rs @@ -1,42 +1,43 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=bridge-hub-rococo-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_958_000 picoseconds. - Weight::from_parts(14_501_711, 0) + // Minimum execution time: 16_890_000 picoseconds. + Weight::from_parts(17_493_920, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(626, 0).saturating_mul(z.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(559, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -66,15 +67,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `191 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_067_000 picoseconds. - Weight::from_parts(33_432_998, 0) + // Minimum execution time: 46_099_000 picoseconds. + Weight::from_parts(34_431_293, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_250 - .saturating_add(Weight::from_parts(131_851, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_459, 0).saturating_mul(z.into())) + // Standard Error: 2_489 + .saturating_add(Weight::from_parts(151_886, 0).saturating_mul(s.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(1_900, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -84,15 +85,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `210` // Estimated: `6811` - // Minimum execution time: 29_373_000 picoseconds. - Weight::from_parts(19_409_201, 0) + // Minimum execution time: 31_133_000 picoseconds. + Weight::from_parts(19_877_758, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 725 - .saturating_add(Weight::from_parts(110_824, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_502, 0).saturating_mul(z.into())) + // Standard Error: 1_220 + .saturating_add(Weight::from_parts(132_155, 0).saturating_mul(s.into())) + // Standard Error: 11 + .saturating_add(Weight::from_parts(1_916, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -104,60 +105,63 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `388 + s * (33 ±0)` + // Measured: `316 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 49_724_000 picoseconds. - Weight::from_parts(34_153_321, 0) + // Minimum execution time: 58_414_000 picoseconds. + Weight::from_parts(32_980_753, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_376 - .saturating_add(Weight::from_parts(174_634, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_753, 0).saturating_mul(z.into())) + // Standard Error: 3_838 + .saturating_add(Weight::from_parts(302_359, 0).saturating_mul(s.into())) + // Standard Error: 37 + .saturating_add(Weight::from_parts(2_629, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `191 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 31_081_000 picoseconds. - Weight::from_parts(31_552_702, 0) + // Minimum execution time: 29_917_000 picoseconds. + Weight::from_parts(33_459_806, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_066 - .saturating_add(Weight::from_parts(135_081, 0).saturating_mul(s.into())) + // Standard Error: 1_607 + .saturating_add(Weight::from_parts(150_128, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `210` // Estimated: `6811` - // Minimum execution time: 17_807_000 picoseconds. - Weight::from_parts(18_241_044, 0) + // Minimum execution time: 16_739_000 picoseconds. + Weight::from_parts(16_757_542, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 768 - .saturating_add(Weight::from_parts(112_957, 0).saturating_mul(s.into())) + // Standard Error: 909 + .saturating_add(Weight::from_parts(138_791, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `382 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_421_000 picoseconds. - Weight::from_parts(32_554_061, 0) + // Minimum execution time: 35_004_000 picoseconds. + Weight::from_parts(35_434_253, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_157 - .saturating_add(Weight::from_parts(141_221, 0).saturating_mul(s.into())) + // Standard Error: 1_130 + .saturating_add(Weight::from_parts(158_542, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs index 91840ae0c6d7..599bed182de4 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=bridge-hub-rococo-dev -// --wasm-execution=compiled -// --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* +// --chain=bridge-hub-westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_958_000 picoseconds. - Weight::from_parts(14_501_711, 0) + // Minimum execution time: 16_960_000 picoseconds. + Weight::from_parts(17_458_038, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(626, 0).saturating_mul(z.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(745, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -67,15 +67,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `296 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_067_000 picoseconds. - Weight::from_parts(33_432_998, 0) + // Minimum execution time: 49_023_000 picoseconds. + Weight::from_parts(36_653_713, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_250 - .saturating_add(Weight::from_parts(131_851, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_459, 0).saturating_mul(z.into())) + // Standard Error: 1_966 + .saturating_add(Weight::from_parts(144_768, 0).saturating_mul(s.into())) + // Standard Error: 19 + .saturating_add(Weight::from_parts(1_983, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -85,15 +85,15 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 29_373_000 picoseconds. - Weight::from_parts(19_409_201, 0) + // Minimum execution time: 32_233_000 picoseconds. + Weight::from_parts(20_563_994, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 725 - .saturating_add(Weight::from_parts(110_824, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_502, 0).saturating_mul(z.into())) + // Standard Error: 1_541 + .saturating_add(Weight::from_parts(137_834, 0).saturating_mul(s.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(2_004, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -105,60 +105,63 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `388 + s * (33 ±0)` + // Measured: `421 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 49_724_000 picoseconds. - Weight::from_parts(34_153_321, 0) + // Minimum execution time: 57_893_000 picoseconds. + Weight::from_parts(32_138_684, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_376 - .saturating_add(Weight::from_parts(174_634, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_753, 0).saturating_mul(z.into())) + // Standard Error: 3_096 + .saturating_add(Weight::from_parts(324_931, 0).saturating_mul(s.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(2_617, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `296 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 31_081_000 picoseconds. - Weight::from_parts(31_552_702, 0) + // Minimum execution time: 31_313_000 picoseconds. + Weight::from_parts(33_535_933, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_066 - .saturating_add(Weight::from_parts(135_081, 0).saturating_mul(s.into())) + // Standard Error: 1_649 + .saturating_add(Weight::from_parts(153_756, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `282` + // Measured: `315` // Estimated: `6811` - // Minimum execution time: 17_807_000 picoseconds. - Weight::from_parts(18_241_044, 0) + // Minimum execution time: 17_860_000 picoseconds. + Weight::from_parts(18_559_535, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 768 - .saturating_add(Weight::from_parts(112_957, 0).saturating_mul(s.into())) + // Standard Error: 1_036 + .saturating_add(Weight::from_parts(135_049, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `487 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_421_000 picoseconds. - Weight::from_parts(32_554_061, 0) + // Minimum execution time: 32_340_000 picoseconds. + Weight::from_parts(33_519_124, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_157 - .saturating_add(Weight::from_parts(141_221, 0).saturating_mul(s.into())) + // Standard Error: 1_932 + .saturating_add(Weight::from_parts(193_896, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs index a7827b720090..5c428bb5e5ea 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_multisig.rs @@ -1,42 +1,43 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-07-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-ynta1nyy-project-238-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-polkadot-dev")`, DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=collectives-polkadot-dev -// --wasm-execution=compiled -// --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares // --extrinsic=* +// --chain=collectives-westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./parachains/runtimes/collectives/collectives-polkadot/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_288_000 picoseconds. - Weight::from_parts(14_235_741, 0) + // Minimum execution time: 16_309_000 picoseconds. + Weight::from_parts(17_281_100, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 5 - .saturating_add(Weight::from_parts(500, 0).saturating_mul(z.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(549, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -68,13 +69,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `328 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 44_865_000 picoseconds. - Weight::from_parts(33_468_056, 0) + // Minimum execution time: 48_617_000 picoseconds. + Weight::from_parts(35_426_484, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_513 - .saturating_add(Weight::from_parts(130_544, 0).saturating_mul(s.into())) - // Standard Error: 14 - .saturating_add(Weight::from_parts(1_422, 0).saturating_mul(z.into())) + // Standard Error: 1_941 + .saturating_add(Weight::from_parts(164_183, 0).saturating_mul(s.into())) + // Standard Error: 19 + .saturating_add(Weight::from_parts(1_898, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -86,13 +87,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `348` // Estimated: `6811` - // Minimum execution time: 29_284_000 picoseconds. - Weight::from_parts(18_708_967, 0) + // Minimum execution time: 32_600_000 picoseconds. + Weight::from_parts(18_613_047, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 916 - .saturating_add(Weight::from_parts(119_202, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_447, 0).saturating_mul(z.into())) + // Standard Error: 1_498 + .saturating_add(Weight::from_parts(147_489, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_094, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -106,28 +107,29 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `451 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 49_462_000 picoseconds. - Weight::from_parts(34_470_286, 0) + // Minimum execution time: 55_580_000 picoseconds. + Weight::from_parts(32_757_473, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_738 - .saturating_add(Weight::from_parts(178_227, 0).saturating_mul(s.into())) - // Standard Error: 17 - .saturating_add(Weight::from_parts(1_644, 0).saturating_mul(z.into())) + // Standard Error: 3_265 + .saturating_add(Weight::from_parts(261_212, 0).saturating_mul(s.into())) + // Standard Error: 32 + .saturating_add(Weight::from_parts(2_407, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `329 + s * (2 ±0)` + // Measured: `328 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 30_749_000 picoseconds. - Weight::from_parts(31_841_438, 0) + // Minimum execution time: 31_137_000 picoseconds. + Weight::from_parts(32_271_159, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_033 - .saturating_add(Weight::from_parts(123_126, 0).saturating_mul(s.into())) + // Standard Error: 1_280 + .saturating_add(Weight::from_parts(163_156, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -138,11 +140,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `348` // Estimated: `6811` - // Minimum execution time: 17_436_000 picoseconds. - Weight::from_parts(18_036_002, 0) + // Minimum execution time: 17_763_000 picoseconds. + Weight::from_parts(18_235_437, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 829 - .saturating_add(Weight::from_parts(109_450, 0).saturating_mul(s.into())) + // Standard Error: 1_245 + .saturating_add(Weight::from_parts(138_553, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -151,13 +153,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `520 + s * (1 ±0)` + // Measured: `515 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 31_532_000 picoseconds. - Weight::from_parts(32_818_015, 0) + // Minimum execution time: 32_152_000 picoseconds. + Weight::from_parts(34_248_643, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 977 - .saturating_add(Weight::from_parts(123_121, 0).saturating_mul(s.into())) + // Standard Error: 1_943 + .saturating_add(Weight::from_parts(153_258, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs index 8e010d768f64..f3ab1b1cac8a 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2024-01-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-j8vvqcjr-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=coretime-rococo-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_905_000 picoseconds. - Weight::from_parts(13_544_225, 0) + // Minimum execution time: 16_150_000 picoseconds. + Weight::from_parts(17_417_293, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 2 - .saturating_add(Weight::from_parts(596, 0).saturating_mul(z.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(488, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -69,13 +69,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 38_729_000 picoseconds. - Weight::from_parts(27_942_442, 0) + // Minimum execution time: 47_027_000 picoseconds. + Weight::from_parts(33_446_171, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 648 - .saturating_add(Weight::from_parts(120_340, 0).saturating_mul(s.into())) - // Standard Error: 6 - .saturating_add(Weight::from_parts(1_578, 0).saturating_mul(z.into())) + // Standard Error: 1_434 + .saturating_add(Weight::from_parts(152_452, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_012, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 25_936_000 picoseconds. - Weight::from_parts(16_537_903, 0) + // Minimum execution time: 32_131_000 picoseconds. + Weight::from_parts(18_539_623, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 412 - .saturating_add(Weight::from_parts(105_835, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_534, 0).saturating_mul(z.into())) + // Standard Error: 1_460 + .saturating_add(Weight::from_parts(140_999, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_033, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,58 +107,61 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `385 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 45_291_000 picoseconds. - Weight::from_parts(31_294_385, 0) + // Minimum execution time: 53_701_000 picoseconds. + Weight::from_parts(32_431_551, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 816 - .saturating_add(Weight::from_parts(152_838, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_638, 0).saturating_mul(z.into())) + // Standard Error: 2_797 + .saturating_add(Weight::from_parts(255_676, 0).saturating_mul(s.into())) + // Standard Error: 27 + .saturating_add(Weight::from_parts(2_261, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 26_585_000 picoseconds. - Weight::from_parts(27_424_168, 0) + // Minimum execution time: 30_011_000 picoseconds. + Weight::from_parts(32_146_378, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 732 - .saturating_add(Weight::from_parts(123_460, 0).saturating_mul(s.into())) + // Standard Error: 1_455 + .saturating_add(Weight::from_parts(160_784, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 15_228_000 picoseconds. - Weight::from_parts(15_568_631, 0) + // Minimum execution time: 16_968_000 picoseconds. + Weight::from_parts(16_851_993, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 441 - .saturating_add(Weight::from_parts(107_463, 0).saturating_mul(s.into())) + // Standard Error: 793 + .saturating_add(Weight::from_parts(142_320, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `449 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 28_033_000 picoseconds. - Weight::from_parts(29_228_827, 0) + // Minimum execution time: 31_706_000 picoseconds. + Weight::from_parts(33_679_423, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 748 - .saturating_add(Weight::from_parts(117_495, 0).saturating_mul(s.into())) + // Standard Error: 1_154 + .saturating_add(Weight::from_parts(145_059, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs index 1aaf3f4a6fb9..044356f1e146 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/pallet_multisig.rs @@ -17,27 +17,27 @@ //! Autogenerated weights for `pallet_multisig` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("coretime-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet +// --extrinsic=* // --chain=coretime-westend-dev -// --wasm-execution=compiled // --pallet=pallet_multisig -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./cumulus/file_header.txt -// --output=./cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/ +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_938_000 picoseconds. - Weight::from_parts(13_021_007, 0) + // Minimum execution time: 16_090_000 picoseconds. + Weight::from_parts(16_926_991, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 4 - .saturating_add(Weight::from_parts(482, 0).saturating_mul(z.into())) + // Standard Error: 6 + .saturating_add(Weight::from_parts(500, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -69,13 +69,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 37_643_000 picoseconds. - Weight::from_parts(27_088_068, 0) + // Minimum execution time: 46_739_000 picoseconds. + Weight::from_parts(34_253_833, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 828 - .saturating_add(Weight::from_parts(123_693, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_456, 0).saturating_mul(z.into())) + // Standard Error: 1_258 + .saturating_add(Weight::from_parts(141_511, 0).saturating_mul(s.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(1_969, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 25_825_000 picoseconds. - Weight::from_parts(15_698_835, 0) + // Minimum execution time: 31_190_000 picoseconds. + Weight::from_parts(18_287_369, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 568 - .saturating_add(Weight::from_parts(111_928, 0).saturating_mul(s.into())) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_421, 0).saturating_mul(z.into())) + // Standard Error: 1_405 + .saturating_add(Weight::from_parts(143_414, 0).saturating_mul(s.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(2_047, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,58 +107,61 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `385 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 43_587_000 picoseconds. - Weight::from_parts(29_740_539, 0) + // Minimum execution time: 53_340_000 picoseconds. + Weight::from_parts(31_091_227, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 771 - .saturating_add(Weight::from_parts(154_861, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_557, 0).saturating_mul(z.into())) + // Standard Error: 3_346 + .saturating_add(Weight::from_parts(256_292, 0).saturating_mul(s.into())) + // Standard Error: 32 + .saturating_add(Weight::from_parts(2_518, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `263 + s * (2 ±0)` + // Measured: `262 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 24_966_000 picoseconds. - Weight::from_parts(25_879_458, 0) + // Minimum execution time: 30_024_000 picoseconds. + Weight::from_parts(32_926_280, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 777 - .saturating_add(Weight::from_parts(122_823, 0).saturating_mul(s.into())) + // Standard Error: 1_559 + .saturating_add(Weight::from_parts(151_433, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 14_450_000 picoseconds. - Weight::from_parts(14_607_858, 0) + // Minimum execution time: 16_853_000 picoseconds. + Weight::from_parts(17_314_743, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 471 - .saturating_add(Weight::from_parts(107_007, 0).saturating_mul(s.into())) + // Standard Error: 1_022 + .saturating_add(Weight::from_parts(139_694, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `454 + s * (1 ±0)` + // Measured: `449 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 26_861_000 picoseconds. - Weight::from_parts(27_846_825, 0) + // Minimum execution time: 31_102_000 picoseconds. + Weight::from_parts(32_212_096, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 714 - .saturating_add(Weight::from_parts(116_914, 0).saturating_mul(s.into())) + // Standard Error: 1_524 + .saturating_add(Weight::from_parts(151_963, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs index 73abb62b0482..82fcacf64aca 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_multisig.rs @@ -1,40 +1,43 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-kusama-dev"), DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=people-kusama-dev -// --execution=wasm -// --wasm-execution=compiled -// --pallet=pallet_multisig // --extrinsic=* +// --chain=people-rococo-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/people/people-kusama/src/weights/pallet_multisig.rs +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -52,110 +55,113 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_056_000 picoseconds. - Weight::from_parts(11_510_137, 0) + // Minimum execution time: 16_209_000 picoseconds. + Weight::from_parts(16_941_673, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 1 - .saturating_add(Weight::from_parts(528, 0).saturating_mul(z.into())) + // Standard Error: 10 + .saturating_add(Weight::from_parts(551, 0).saturating_mul(z.into())) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_105_000 picoseconds. - Weight::from_parts(34_947_072, 0) + // Minimum execution time: 47_880_000 picoseconds. + Weight::from_parts(35_747_073, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 499 - .saturating_add(Weight::from_parts(67_375, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_227, 0).saturating_mul(z.into())) + // Standard Error: 2_069 + .saturating_add(Weight::from_parts(147_421, 0).saturating_mul(s.into())) + // Standard Error: 20 + .saturating_add(Weight::from_parts(1_853, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 26_640_000 picoseconds. - Weight::from_parts(21_515_344, 0) + // Minimum execution time: 31_245_000 picoseconds. + Weight::from_parts(19_011_583, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 943 - .saturating_add(Weight::from_parts(58_769, 0).saturating_mul(s.into())) - // Standard Error: 9 - .saturating_add(Weight::from_parts(1_233, 0).saturating_mul(z.into())) + // Standard Error: 1_336 + .saturating_add(Weight::from_parts(136_422, 0).saturating_mul(s.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(2_013, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `388 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 45_875_000 picoseconds. - Weight::from_parts(38_052_994, 0) + // Minimum execution time: 52_116_000 picoseconds. + Weight::from_parts(33_912_565, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 507 - .saturating_add(Weight::from_parts(82_957, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_277, 0).saturating_mul(z.into())) + // Standard Error: 3_064 + .saturating_add(Weight::from_parts(258_562, 0).saturating_mul(s.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(2_206, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 32_359_000 picoseconds. - Weight::from_parts(33_845_761, 0) + // Minimum execution time: 31_142_000 picoseconds. + Weight::from_parts(32_417_223, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 623 - .saturating_add(Weight::from_parts(69_809, 0).saturating_mul(s.into())) + // Standard Error: 1_622 + .saturating_add(Weight::from_parts(163_533, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 18_791_000 picoseconds. - Weight::from_parts(20_017_375, 0) + // Minimum execution time: 17_183_000 picoseconds. + Weight::from_parts(18_181_089, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 466 - .saturating_add(Weight::from_parts(64_780, 0).saturating_mul(s.into())) + // Standard Error: 1_123 + .saturating_add(Weight::from_parts(134_567, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `454 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 33_132_000 picoseconds. - Weight::from_parts(34_485_734, 0) + // Minimum execution time: 32_006_000 picoseconds. + Weight::from_parts(33_910_335, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 601 - .saturating_add(Weight::from_parts(70_191, 0).saturating_mul(s.into())) + // Standard Error: 1_347 + .saturating_add(Weight::from_parts(138_258, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs index 70809dea2366..5857a140e05e 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_multisig.rs @@ -1,40 +1,43 @@ // Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 +// This file is part of Cumulus. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Cumulus is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Cumulus is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Cumulus. If not, see . //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-31, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bm4`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("people-polkadot-dev"), DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("people-westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./artifacts/polkadot-parachain +// target/production/polkadot-parachain // benchmark // pallet -// --chain=people-polkadot-dev -// --execution=wasm -// --wasm-execution=compiled -// --pallet=pallet_multisig // --extrinsic=* +// --chain=people-westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/people/people-westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --json -// --header=./file_header.txt -// --output=./cumulus/parachains/runtimes/people/people-polkadot/src/weights/pallet_multisig.rs +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -52,110 +55,113 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_337_000 picoseconds. - Weight::from_parts(11_960_522, 0) + // Minimum execution time: 15_664_000 picoseconds. + Weight::from_parts(16_483_544, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 9 - .saturating_add(Weight::from_parts(504, 0).saturating_mul(z.into())) + // Standard Error: 6 + .saturating_add(Weight::from_parts(527, 0).saturating_mul(z.into())) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_128_000 picoseconds. - Weight::from_parts(35_215_592, 0) + // Minimum execution time: 47_543_000 picoseconds. + Weight::from_parts(32_140_648, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 429 - .saturating_add(Weight::from_parts(65_959, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_230, 0).saturating_mul(z.into())) + // Standard Error: 2_184 + .saturating_add(Weight::from_parts(163_779, 0).saturating_mul(s.into())) + // Standard Error: 21 + .saturating_add(Weight::from_parts(2_192, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 26_878_000 picoseconds. - Weight::from_parts(21_448_577, 0) + // Minimum execution time: 31_080_000 picoseconds. + Weight::from_parts(19_282_980, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 354 - .saturating_add(Weight::from_parts(60_286, 0).saturating_mul(s.into())) - // Standard Error: 3 - .saturating_add(Weight::from_parts(1_236, 0).saturating_mul(z.into())) + // Standard Error: 1_261 + .saturating_add(Weight::from_parts(134_865, 0).saturating_mul(s.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(2_015, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `388 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 45_716_000 picoseconds. - Weight::from_parts(38_332_947, 0) + // Minimum execution time: 54_063_000 picoseconds. + Weight::from_parts(34_760_071, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 554 - .saturating_add(Weight::from_parts(81_026, 0).saturating_mul(s.into())) - // Standard Error: 5 - .saturating_add(Weight::from_parts(1_265, 0).saturating_mul(z.into())) + // Standard Error: 2_858 + .saturating_add(Weight::from_parts(242_502, 0).saturating_mul(s.into())) + // Standard Error: 28 + .saturating_add(Weight::from_parts(2_187, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `263 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 32_089_000 picoseconds. - Weight::from_parts(33_664_508, 0) + // Minimum execution time: 30_997_000 picoseconds. + Weight::from_parts(32_861_544, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 487 - .saturating_add(Weight::from_parts(67_443, 0).saturating_mul(s.into())) + // Standard Error: 1_172 + .saturating_add(Weight::from_parts(144_646, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `282` // Estimated: `6811` - // Minimum execution time: 18_631_000 picoseconds. - Weight::from_parts(19_909_964, 0) + // Minimum execution time: 17_110_000 picoseconds. + Weight::from_parts(16_883_743, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 434 - .saturating_add(Weight::from_parts(62_989, 0).saturating_mul(s.into())) + // Standard Error: 1_170 + .saturating_add(Weight::from_parts(141_623, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `454 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 32_486_000 picoseconds. - Weight::from_parts(34_303_784, 0) + // Minimum execution time: 31_575_000 picoseconds. + Weight::from_parts(33_599_222, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 585 - .saturating_add(Weight::from_parts(69_979, 0).saturating_mul(s.into())) + // Standard Error: 1_343 + .saturating_add(Weight::from_parts(148_578, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/polkadot/runtime/rococo/src/weights/pallet_multisig.rs b/polkadot/runtime/rococo/src/weights/pallet_multisig.rs index f1b81759ece6..d63c82daacde 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_multisig.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_multisig.rs @@ -17,27 +17,27 @@ //! Autogenerated weights for `pallet_multisig` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-02-29, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("rococo-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet +// --extrinsic=* // --chain=rococo-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/rococo/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --pallet=pallet_multisig -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./polkadot/file_header.txt -// --output=./polkadot/runtime/rococo/src/weights/ +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,11 +55,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_023_000 picoseconds. - Weight::from_parts(12_643_116, 0) + // Minimum execution time: 15_707_000 picoseconds. + Weight::from_parts(17_199_004, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 3 - .saturating_add(Weight::from_parts(582, 0).saturating_mul(z.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(639, 0).saturating_mul(z.into())) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) @@ -69,13 +69,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `229 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 39_339_000 picoseconds. - Weight::from_parts(27_243_033, 0) + // Minimum execution time: 47_949_000 picoseconds. + Weight::from_parts(33_500_294, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_319 - .saturating_add(Weight::from_parts(142_212, 0).saturating_mul(s.into())) - // Standard Error: 12 - .saturating_add(Weight::from_parts(1_592, 0).saturating_mul(z.into())) + // Standard Error: 1_775 + .saturating_add(Weight::from_parts(159_011, 0).saturating_mul(s.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(2_213, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -87,13 +87,13 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `248` // Estimated: `6811` - // Minimum execution time: 27_647_000 picoseconds. - Weight::from_parts(15_828_725, 0) + // Minimum execution time: 31_197_000 picoseconds. + Weight::from_parts(19_488_352, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 908 - .saturating_add(Weight::from_parts(130_880, 0).saturating_mul(s.into())) - // Standard Error: 8 - .saturating_add(Weight::from_parts(1_532, 0).saturating_mul(z.into())) + // Standard Error: 1_332 + .saturating_add(Weight::from_parts(138_347, 0).saturating_mul(s.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(2_122, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -107,28 +107,29 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `354 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 46_971_000 picoseconds. - Weight::from_parts(32_150_393, 0) + // Minimum execution time: 54_297_000 picoseconds. + Weight::from_parts(33_256_178, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_129 - .saturating_add(Weight::from_parts(154_796, 0).saturating_mul(s.into())) - // Standard Error: 11 - .saturating_add(Weight::from_parts(1_603, 0).saturating_mul(z.into())) + // Standard Error: 3_088 + .saturating_add(Weight::from_parts(256_364, 0).saturating_mul(s.into())) + // Standard Error: 30 + .saturating_add(Weight::from_parts(2_488, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `229 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 24_947_000 picoseconds. - Weight::from_parts(26_497_183, 0) + // Minimum execution time: 31_246_000 picoseconds. + Weight::from_parts(32_245_711, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_615 - .saturating_add(Weight::from_parts(147_071, 0).saturating_mul(s.into())) + // Standard Error: 1_704 + .saturating_add(Weight::from_parts(156_235, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -139,11 +140,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `248` // Estimated: `6811` - // Minimum execution time: 13_897_000 picoseconds. - Weight::from_parts(14_828_339, 0) + // Minimum execution time: 17_353_000 picoseconds. + Weight::from_parts(17_418_506, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_136 - .saturating_add(Weight::from_parts(133_925, 0).saturating_mul(s.into())) + // Standard Error: 1_126 + .saturating_add(Weight::from_parts(136_788, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -154,11 +155,11 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `420 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 28_984_000 picoseconds. - Weight::from_parts(29_853_232, 0) + // Minimum execution time: 32_603_000 picoseconds. + Weight::from_parts(33_456_399, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 650 - .saturating_add(Weight::from_parts(113_440, 0).saturating_mul(s.into())) + // Standard Error: 1_239 + .saturating_add(Weight::from_parts(146_249, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/polkadot/runtime/westend/src/weights/pallet_multisig.rs b/polkadot/runtime/westend/src/weights/pallet_multisig.rs index 616aea9c8e73..83521f3d1927 100644 --- a/polkadot/runtime/westend/src/weights/pallet_multisig.rs +++ b/polkadot/runtime/westend/src/weights/pallet_multisig.rs @@ -16,28 +16,28 @@ //! Autogenerated weights for `pallet_multisig` //! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-06-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner--ss9ysm1-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("westend-dev"), DB CACHE: 1024 +//! HOSTNAME: `e20fc9f125eb`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024 // Executed Command: -// ./target/production/polkadot +// target/production/polkadot // benchmark // pallet +// --extrinsic=* // --chain=westend-dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/polkadot/file_header.txt +// --output=./polkadot/runtime/westend/src/weights +// --wasm-execution=compiled // --steps=50 // --repeat=20 +// --heap-pages=4096 // --no-storage-info -// --no-median-slopes // --no-min-squares -// --pallet=pallet_multisig -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --header=./file_header.txt -// --output=./runtime/westend/src/weights/ +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,110 +55,111 @@ impl pallet_multisig::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 13_218_000 picoseconds. - Weight::from_parts(14_749_472, 0) + // Minimum execution time: 15_705_000 picoseconds. + Weight::from_parts(16_890_096, 0) .saturating_add(Weight::from_parts(0, 0)) - // Standard Error: 10 - .saturating_add(Weight::from_parts(507, 0).saturating_mul(z.into())) + // Standard Error: 13 + .saturating_add(Weight::from_parts(549, 0).saturating_mul(z.into())) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `309 + s * (2 ±0)` + // Measured: `267 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 45_891_000 picoseconds. - Weight::from_parts(33_546_627, 0) + // Minimum execution time: 54_293_000 picoseconds. + Weight::from_parts(39_710_880, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 2_347 - .saturating_add(Weight::from_parts(136_466, 0).saturating_mul(s.into())) - // Standard Error: 23 - .saturating_add(Weight::from_parts(1_595, 0).saturating_mul(z.into())) + // Standard Error: 1_591 + .saturating_add(Weight::from_parts(164_846, 0).saturating_mul(s.into())) + // Standard Error: 15 + .saturating_add(Weight::from_parts(1_993, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `286` // Estimated: `6811` - // Minimum execution time: 30_355_000 picoseconds. - Weight::from_parts(19_611_682, 0) + // Minimum execution time: 36_477_000 picoseconds. + Weight::from_parts(22_595_904, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_383 - .saturating_add(Weight::from_parts(123_652, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_488, 0).saturating_mul(z.into())) + // Standard Error: 1_526 + .saturating_add(Weight::from_parts(159_314, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_219, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `392 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 50_453_000 picoseconds. - Weight::from_parts(35_628_285, 0) + // Minimum execution time: 60_127_000 picoseconds. + Weight::from_parts(33_469_803, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 3_693 - .saturating_add(Weight::from_parts(203_453, 0).saturating_mul(s.into())) - // Standard Error: 36 - .saturating_add(Weight::from_parts(1_726, 0).saturating_mul(z.into())) + // Standard Error: 3_400 + .saturating_add(Weight::from_parts(309_634, 0).saturating_mul(s.into())) + // Standard Error: 33 + .saturating_add(Weight::from_parts(2_795, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. + /// The range of component `z` is `[0, 10000]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `314 + s * (2 ±0)` + // Measured: `267 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 32_500_000 picoseconds. - Weight::from_parts(33_231_806, 0) + // Minimum execution time: 36_697_000 picoseconds. + Weight::from_parts(38_746_125, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_511 - .saturating_add(Weight::from_parts(134_500, 0).saturating_mul(s.into())) + // Standard Error: 2_073 + .saturating_add(Weight::from_parts(159_426, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `286` // Estimated: `6811` - // Minimum execution time: 17_906_000 picoseconds. - Weight::from_parts(18_757_928, 0) + // Minimum execution time: 21_909_000 picoseconds. + Weight::from_parts(22_227_385, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_172 - .saturating_add(Weight::from_parts(113_535, 0).saturating_mul(s.into())) + // Standard Error: 1_063 + .saturating_add(Weight::from_parts(146_021, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: Multisig Multisigs (r:1 w:1) - /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: `Multisig::Multisigs` (r:1 w:1) + /// Proof: `Multisig::Multisigs` (`max_values`: None, `max_size`: Some(3346), added: 5821, mode: `MaxEncodedLen`) /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `458 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 33_018_000 picoseconds. - Weight::from_parts(34_186_533, 0) + // Minimum execution time: 36_637_000 picoseconds. + Weight::from_parts(36_457_379, 0) .saturating_add(Weight::from_parts(0, 6811)) - // Standard Error: 1_188 - .saturating_add(Weight::from_parts(128_449, 0).saturating_mul(s.into())) + // Standard Error: 1_709 + .saturating_add(Weight::from_parts(171_090, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/substrate/.maintain/frame-weight-template.hbs b/substrate/.maintain/frame-weight-template.hbs index ec9eee205cee..b174823b3840 100644 --- a/substrate/.maintain/frame-weight-template.hbs +++ b/substrate/.maintain/frame-weight-template.hbs @@ -17,8 +17,7 @@ #![allow(unused_imports)] #![allow(missing_docs)] -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use core::marker::PhantomData; +use frame::weights_prelude::*; /// Weight functions needed for `{{pallet}}`. pub trait WeightInfo { diff --git a/substrate/frame/multisig/src/benchmarking.rs b/substrate/frame/multisig/src/benchmarking.rs index ccaa1ceab66e..3f75d92fe0ed 100644 --- a/substrate/frame/multisig/src/benchmarking.rs +++ b/substrate/frame/multisig/src/benchmarking.rs @@ -194,14 +194,14 @@ mod benchmarks { Ok(()) } - /// `z`: Transaction Length, not a component /// `s`: Signatories, need at least 2 people #[benchmark] fn approve_as_multi_create( s: Linear<2, { T::MaxSignatories::get() }>, - z: Linear<0, 10_000>, ) -> Result<(), BenchmarkError> { - let (mut signatories, call) = setup_multi::(s, z)?; + // The call is neither in storage or an argument, so just use any: + let call_len = 10_000; + let (mut signatories, call) = setup_multi::(s, call_len)?; let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; let call_hash = call.using_encoded(blake2_256); @@ -225,14 +225,14 @@ mod benchmarks { Ok(()) } - /// `z`: Transaction Length, not a component /// `s`: Signatories, need at least 2 people #[benchmark] fn approve_as_multi_approve( s: Linear<2, { T::MaxSignatories::get() }>, - z: Linear<0, 10_000>, ) -> Result<(), BenchmarkError> { - let (mut signatories, call) = setup_multi::(s, z)?; + // The call is neither in storage or an argument, so just use any: + let call_len = 10_000; + let (mut signatories, call) = setup_multi::(s, call_len)?; let mut signatories2 = signatories.clone(); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; @@ -270,14 +270,12 @@ mod benchmarks { Ok(()) } - /// `z`: Transaction Length, not a component /// `s`: Signatories, need at least 2 people #[benchmark] - fn cancel_as_multi( - s: Linear<2, { T::MaxSignatories::get() }>, - z: Linear<0, 10_000>, - ) -> Result<(), BenchmarkError> { - let (mut signatories, call) = setup_multi::(s, z)?; + fn cancel_as_multi(s: Linear<2, { T::MaxSignatories::get() }>) -> Result<(), BenchmarkError> { + // The call is neither in storage or an argument, so just use any: + let call_len = 10_000; + let (mut signatories, call) = setup_multi::(s, call_len)?; let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; let call_hash = call.using_encoded(blake2_256); diff --git a/substrate/frame/multisig/src/weights.rs b/substrate/frame/multisig/src/weights.rs index 5c14922e0ef0..1c91734e6188 100644 --- a/substrate/frame/multisig/src/weights.rs +++ b/substrate/frame/multisig/src/weights.rs @@ -18,36 +18,36 @@ //! Autogenerated weights for `pallet_multisig` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `25968fd2c26d`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate-node +// target/production/substrate-node // benchmark // pallet +// --extrinsic=* // --chain=dev +// --pallet=pallet_multisig +// --header=/__w/polkadot-sdk/polkadot-sdk/substrate/HEADER-APACHE2 +// --output=/__w/polkadot-sdk/polkadot-sdk/substrate/frame/multisig/src/weights.rs +// --wasm-execution=compiled // --steps=50 // --repeat=20 -// --pallet=pallet_multisig +// --heap-pages=4096 +// --template=substrate/.maintain/frame-weight-template.hbs // --no-storage-info -// --no-median-slopes // --no-min-squares -// --extrinsic=* -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./substrate/frame/multisig/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] #![allow(missing_docs)] -// TODO update this in frame-weight-template.hbs use frame::weights_prelude::*; + /// Weight functions needed for `pallet_multisig`. pub trait WeightInfo { fn as_multi_threshold_1(z: u32, ) -> Weight; @@ -71,10 +71,10 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3997` - // Minimum execution time: 20_302_000 picoseconds. - Weight::from_parts(21_362_808, 3997) - // Standard Error: 4 - .saturating_add(Weight::from_parts(432, 0).saturating_mul(z.into())) + // Minimum execution time: 28_800_000 picoseconds. + Weight::from_parts(30_130_161, 3997) + // Standard Error: 18 + .saturating_add(Weight::from_parts(551, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(2_u64)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) @@ -83,14 +83,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `334 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(31_518_927, 6811) - // Standard Error: 754 - .saturating_add(Weight::from_parts(115_804, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_442, 0).saturating_mul(z.into())) + // Minimum execution time: 51_467_000 picoseconds. + Weight::from_parts(38_610_296, 6811) + // Standard Error: 1_796 + .saturating_add(Weight::from_parts(161_251, 0).saturating_mul(s.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(2_068, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -100,14 +100,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `353` // Estimated: `6811` - // Minimum execution time: 27_375_000 picoseconds. - Weight::from_parts(17_806_361, 6811) - // Standard Error: 501 - .saturating_add(Weight::from_parts(107_042, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_491, 0).saturating_mul(z.into())) + // Minimum execution time: 36_208_000 picoseconds. + Weight::from_parts(24_694_507, 6811) + // Standard Error: 1_430 + .saturating_add(Weight::from_parts(134_263, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_021, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -123,14 +123,14 @@ impl WeightInfo for SubstrateWeight { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `571 + s * (33 ±0)` + // Measured: `604 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 54_427_000 picoseconds. - Weight::from_parts(43_677_970, 6811) - // Standard Error: 1_342 - .saturating_add(Weight::from_parts(154_697, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_534, 0).saturating_mul(z.into())) + // Minimum execution time: 65_217_000 picoseconds. + Weight::from_parts(48_235_573, 6811) + // Standard Error: 2_841 + .saturating_add(Weight::from_parts(205_077, 0).saturating_mul(s.into())) + // Standard Error: 27 + .saturating_add(Weight::from_parts(2_298, 0).saturating_mul(z.into())) .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -139,12 +139,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `334 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 29_102_000 picoseconds. - Weight::from_parts(30_317_105, 6811) - // Standard Error: 903 - .saturating_add(Weight::from_parts(109_792, 0).saturating_mul(s.into())) + // Minimum execution time: 35_727_000 picoseconds. + Weight::from_parts(37_329_524, 6811) + // Standard Error: 1_814 + .saturating_add(Weight::from_parts(157_471, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -153,12 +153,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `353` // Estimated: `6811` - // Minimum execution time: 16_300_000 picoseconds. - Weight::from_parts(17_358_877, 6811) - // Standard Error: 522 - .saturating_add(Weight::from_parts(99_194, 0).saturating_mul(s.into())) + // Minimum execution time: 21_623_000 picoseconds. + Weight::from_parts(22_601_251, 6811) + // Standard Error: 963 + .saturating_add(Weight::from_parts(139_320, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -167,12 +167,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `492 + s * (1 ±0)` + // Measured: `525 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 30_147_000 picoseconds. - Weight::from_parts(32_003_421, 6811) - // Standard Error: 1_077 - .saturating_add(Weight::from_parts(108_567, 0).saturating_mul(s.into())) + // Minimum execution time: 36_801_000 picoseconds. + Weight::from_parts(37_578_412, 6811) + // Standard Error: 1_580 + .saturating_add(Weight::from_parts(159_580, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -189,10 +189,10 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `145` // Estimated: `3997` - // Minimum execution time: 20_302_000 picoseconds. - Weight::from_parts(21_362_808, 3997) - // Standard Error: 4 - .saturating_add(Weight::from_parts(432, 0).saturating_mul(z.into())) + // Minimum execution time: 28_800_000 picoseconds. + Weight::from_parts(30_130_161, 3997) + // Standard Error: 18 + .saturating_add(Weight::from_parts(551, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(2_u64)) } /// Storage: `Multisig::Multisigs` (r:1 w:1) @@ -201,14 +201,14 @@ impl WeightInfo for () { /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `334 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 41_140_000 picoseconds. - Weight::from_parts(31_518_927, 6811) - // Standard Error: 754 - .saturating_add(Weight::from_parts(115_804, 0).saturating_mul(s.into())) - // Standard Error: 7 - .saturating_add(Weight::from_parts(1_442, 0).saturating_mul(z.into())) + // Minimum execution time: 51_467_000 picoseconds. + Weight::from_parts(38_610_296, 6811) + // Standard Error: 1_796 + .saturating_add(Weight::from_parts(161_251, 0).saturating_mul(s.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(2_068, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -218,14 +218,14 @@ impl WeightInfo for () { /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `353` // Estimated: `6811` - // Minimum execution time: 27_375_000 picoseconds. - Weight::from_parts(17_806_361, 6811) - // Standard Error: 501 - .saturating_add(Weight::from_parts(107_042, 0).saturating_mul(s.into())) - // Standard Error: 4 - .saturating_add(Weight::from_parts(1_491, 0).saturating_mul(z.into())) + // Minimum execution time: 36_208_000 picoseconds. + Weight::from_parts(24_694_507, 6811) + // Standard Error: 1_430 + .saturating_add(Weight::from_parts(134_263, 0).saturating_mul(s.into())) + // Standard Error: 14 + .saturating_add(Weight::from_parts(2_021, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -241,14 +241,14 @@ impl WeightInfo for () { /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `571 + s * (33 ±0)` + // Measured: `604 + s * (33 ±0)` // Estimated: `6811` - // Minimum execution time: 54_427_000 picoseconds. - Weight::from_parts(43_677_970, 6811) - // Standard Error: 1_342 - .saturating_add(Weight::from_parts(154_697, 0).saturating_mul(s.into())) - // Standard Error: 13 - .saturating_add(Weight::from_parts(1_534, 0).saturating_mul(z.into())) + // Minimum execution time: 65_217_000 picoseconds. + Weight::from_parts(48_235_573, 6811) + // Standard Error: 2_841 + .saturating_add(Weight::from_parts(205_077, 0).saturating_mul(s.into())) + // Standard Error: 27 + .saturating_add(Weight::from_parts(2_298, 0).saturating_mul(z.into())) .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -257,12 +257,12 @@ impl WeightInfo for () { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `301 + s * (2 ±0)` + // Measured: `334 + s * (2 ±0)` // Estimated: `6811` - // Minimum execution time: 29_102_000 picoseconds. - Weight::from_parts(30_317_105, 6811) - // Standard Error: 903 - .saturating_add(Weight::from_parts(109_792, 0).saturating_mul(s.into())) + // Minimum execution time: 35_727_000 picoseconds. + Weight::from_parts(37_329_524, 6811) + // Standard Error: 1_814 + .saturating_add(Weight::from_parts(157_471, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -271,12 +271,12 @@ impl WeightInfo for () { /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `320` + // Measured: `353` // Estimated: `6811` - // Minimum execution time: 16_300_000 picoseconds. - Weight::from_parts(17_358_877, 6811) - // Standard Error: 522 - .saturating_add(Weight::from_parts(99_194, 0).saturating_mul(s.into())) + // Minimum execution time: 21_623_000 picoseconds. + Weight::from_parts(22_601_251, 6811) + // Standard Error: 963 + .saturating_add(Weight::from_parts(139_320, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -285,13 +285,13 @@ impl WeightInfo for () { /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `492 + s * (1 ±0)` + // Measured: `525 + s * (1 ±0)` // Estimated: `6811` - // Minimum execution time: 30_147_000 picoseconds. - Weight::from_parts(32_003_421, 6811) - // Standard Error: 1_077 - .saturating_add(Weight::from_parts(108_567, 0).saturating_mul(s.into())) + // Minimum execution time: 36_801_000 picoseconds. + Weight::from_parts(37_578_412, 6811) + // Standard Error: 1_580 + .saturating_add(Weight::from_parts(159_580, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } -} \ No newline at end of file +} From 23600076de203dad498d815ff4b7ed2968217c10 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Fri, 24 Jan 2025 13:32:19 +0100 Subject: [PATCH 321/340] Nits for collectives-westend XCM benchmarks setup (#7215) Closes: https://github.com/paritytech/polkadot-sdk/issues/2904 --------- Co-authored-by: command-bot <> --- Cargo.lock | 1 + .../collectives-westend/Cargo.toml | 3 + .../collectives-westend/src/lib.rs | 122 +++++++- .../src/weights/pallet_xcm.rs | 235 +++++++--------- .../xcm/pallet_xcm_benchmarks_fungible.rs | 128 ++++----- .../xcm/pallet_xcm_benchmarks_generic.rs | 262 +++++++++--------- 6 files changed, 419 insertions(+), 332 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5cc898714d31..df2c58b7f4c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3707,6 +3707,7 @@ dependencies = [ "pallet-treasury 27.0.0", "pallet-utility 28.0.0", "pallet-xcm 7.0.0", + "pallet-xcm-benchmarks 7.0.0", "parachains-common 7.0.0", "parachains-runtimes-test-utils 7.0.0", "parity-scale-codec", diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml index 2786321e48e2..f9cc54495aea 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/Cargo.toml @@ -65,6 +65,7 @@ sp-version = { workspace = true } # Polkadot pallet-xcm = { workspace = true } +pallet-xcm-benchmarks = { optional = true, workspace = true } polkadot-parachain-primitives = { workspace = true } polkadot-runtime-common = { workspace = true } westend-runtime-constants = { workspace = true } @@ -131,6 +132,7 @@ runtime-benchmarks = [ "pallet-transaction-payment/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", "pallet-utility/runtime-benchmarks", + "pallet-xcm-benchmarks/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "parachains-common/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", @@ -222,6 +224,7 @@ std = [ "pallet-transaction-payment/std", "pallet-treasury/std", "pallet-utility/std", + "pallet-xcm-benchmarks?/std", "pallet-xcm/std", "parachain-info/std", "parachains-common/std", diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs index 5eafc2960cc8..5e087832f0e8 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/lib.rs @@ -801,7 +801,6 @@ mod benches { [cumulus_pallet_xcmp_queue, XcmpQueue] [pallet_alliance, Alliance] [pallet_collective, AllianceMotion] - [pallet_xcm, PalletXcmExtrinsicsBenchmark::] [pallet_preimage, Preimage] [pallet_scheduler, Scheduler] [pallet_referenda, FellowshipReferenda] @@ -816,6 +815,11 @@ mod benches { [pallet_treasury, FellowshipTreasury] [pallet_asset_rate, AssetRate] [cumulus_pallet_weight_reclaim, WeightReclaim] + // XCM + [pallet_xcm, PalletXcmExtrinsicsBenchmark::] + // NOTE: Make sure you point to the individual modules below. + [pallet_xcm_benchmarks::fungible, XcmBalances] + [pallet_xcm_benchmarks::generic, XcmGeneric] ); } @@ -1065,6 +1069,12 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; use pallet_xcm::benchmarking::Pallet as PalletXcmExtrinsicsBenchmark; + // This is defined once again in dispatch_benchmark, because list_benchmarks! + // and add_benchmarks! are macros exported by define_benchmarks! macros and those types + // are referenced in that call. + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -1093,10 +1103,11 @@ impl_runtime_apis! { use cumulus_pallet_session_benchmarking::Pallet as SessionBench; impl cumulus_pallet_session_benchmarking::Config for Runtime {} + use xcm_config::WndLocation; parameter_types! { pub ExistentialDepositAsset: Option = Some(( - xcm_config::WndLocation::get(), + WndLocation::get(), ExistentialDeposit::get() ).into()); } @@ -1149,6 +1160,112 @@ impl_runtime_apis! { } } + impl pallet_xcm_benchmarks::Config for Runtime { + type XcmConfig = xcm_config::XcmConfig; + type AccountIdConverter = xcm_config::LocationToAccountId; + type DeliveryHelper = cumulus_primitives_utility::ToParentDeliveryHelper< + xcm_config::XcmConfig, + ExistentialDepositAsset, + xcm_config::PriceForParentDelivery, + >; + fn valid_destination() -> Result { + Ok(WndLocation::get()) + } + fn worst_case_holding(_depositable_count: u32) -> Assets { + // just concrete assets according to relay chain. + let assets: Vec = vec![ + Asset { + id: AssetId(WndLocation::get()), + fun: Fungible(1_000_000 * UNITS), + } + ]; + assets.into() + } + } + + parameter_types! { + pub const TrustedTeleporter: Option<(Location, Asset)> = Some(( + WndLocation::get(), + Asset { fun: Fungible(UNITS), id: AssetId(WndLocation::get()) }, + )); + pub const CheckedAccount: Option<(AccountId, xcm_builder::MintLocation)> = None; + pub const TrustedReserve: Option<(Location, Asset)> = None; + } + + impl pallet_xcm_benchmarks::fungible::Config for Runtime { + type TransactAsset = Balances; + + type CheckedAccount = CheckedAccount; + type TrustedTeleporter = TrustedTeleporter; + type TrustedReserve = TrustedReserve; + + fn get_asset() -> Asset { + Asset { + id: AssetId(WndLocation::get()), + fun: Fungible(UNITS), + } + } + } + + impl pallet_xcm_benchmarks::generic::Config for Runtime { + type TransactAsset = Balances; + type RuntimeCall = RuntimeCall; + + fn worst_case_response() -> (u64, Response) { + (0u64, Response::Version(Default::default())) + } + + fn worst_case_asset_exchange() -> Result<(Assets, Assets), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn universal_alias() -> Result<(Location, Junction), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn transact_origin_and_runtime_call() -> Result<(Location, RuntimeCall), BenchmarkError> { + Ok((WndLocation::get(), frame_system::Call::remark_with_event { remark: vec![] }.into())) + } + + fn subscribe_origin() -> Result { + Ok(WndLocation::get()) + } + + fn claimable_asset() -> Result<(Location, Location, Assets), BenchmarkError> { + let origin = WndLocation::get(); + let assets: Assets = (AssetId(WndLocation::get()), 1_000 * UNITS).into(); + let ticket = Location { parents: 0, interior: Here }; + Ok((origin, ticket, assets)) + } + + fn fee_asset() -> Result { + Ok(Asset { + id: AssetId(WndLocation::get()), + fun: Fungible(1_000_000 * UNITS), + }) + } + + fn unlockable_asset() -> Result<(Location, Location, Asset), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn export_message_origin_and_destination( + ) -> Result<(Location, NetworkId, InteriorLocation), BenchmarkError> { + Err(BenchmarkError::Skip) + } + + fn alias_origin() -> Result<(Location, Location), BenchmarkError> { + // Any location can alias to an internal location. + // Here parachain 1000 aliases to an internal account. + let origin = Location::new(1, [Parachain(1000)]); + let target = Location::new(1, [Parachain(1000), AccountId32 { id: [128u8; 32], network: None }]); + Ok((origin, target)) + } + } + + type XcmBalances = pallet_xcm_benchmarks::fungible::Pallet::; + type XcmGeneric = pallet_xcm_benchmarks::generic::Pallet::; + use frame_support::traits::WhitelistedStorageKeys; let whitelist: Vec = AllPalletsWithSystem::whitelisted_storage_keys(); @@ -1156,7 +1273,6 @@ impl_runtime_apis! { let params = (&config, &whitelist); add_benchmarks!(params, batches); - if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } Ok(batches) } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs index ccf88873c2cd..c0389cbcdb42 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/pallet_xcm.rs @@ -17,9 +17,9 @@ //! Autogenerated weights for `pallet_xcm` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-12-18, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `47a5bbdc8de3`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `17a605d70d1a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("collectives-westend-dev")`, DB CACHE: 1024 // Executed Command: @@ -56,23 +56,19 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn send() -> Weight { // Proof Size summary in bytes: - // Measured: `214` - // Estimated: `3679` - // Minimum execution time: 32_779_000 picoseconds. - Weight::from_parts(33_417_000, 0) - .saturating_add(Weight::from_parts(0, 3679)) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `111` + // Estimated: `3576` + // Minimum execution time: 26_877_000 picoseconds. + Weight::from_parts(27_778_000, 0) + .saturating_add(Weight::from_parts(0, 3576)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `ParachainInfo::ParachainId` (r:1 w:0) /// Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) @@ -82,10 +78,6 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) @@ -94,13 +86,13 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn teleport_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `214` - // Estimated: `3679` - // Minimum execution time: 116_031_000 picoseconds. - Weight::from_parts(118_863_000, 0) - .saturating_add(Weight::from_parts(0, 3679)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `111` + // Estimated: `3593` + // Minimum execution time: 109_606_000 picoseconds. + Weight::from_parts(120_756_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `Benchmark::Override` (r:0 w:0) /// Proof: `Benchmark::Override` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -120,10 +112,6 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) @@ -132,23 +120,23 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn transfer_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `214` - // Estimated: `3679` - // Minimum execution time: 116_267_000 picoseconds. - Weight::from_parts(119_519_000, 0) - .saturating_add(Weight::from_parts(0, 3679)) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `111` + // Estimated: `3593` + // Minimum execution time: 109_165_000 picoseconds. + Weight::from_parts(110_899_000, 0) + .saturating_add(Weight::from_parts(0, 3593)) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `PolkadotXcm::ShouldRecordXcm` (r:1 w:0) /// Proof: `PolkadotXcm::ShouldRecordXcm` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn execute() -> Weight { // Proof Size summary in bytes: - // Measured: `103` - // Estimated: `1588` - // Minimum execution time: 12_718_000 picoseconds. - Weight::from_parts(13_572_000, 0) - .saturating_add(Weight::from_parts(0, 1588)) + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 9_494_000 picoseconds. + Weight::from_parts(9_917_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) } /// Storage: `PolkadotXcm::SupportedVersion` (r:0 w:1) @@ -157,21 +145,18 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_568_000 picoseconds. - Weight::from_parts(7_913_000, 0) + // Minimum execution time: 7_515_000 picoseconds. + Weight::from_parts(7_771_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:0 w:1) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn force_default_xcm_version() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_225_000 picoseconds. - Weight::from_parts(2_473_000, 0) + // Minimum execution time: 2_430_000 picoseconds. + Weight::from_parts(2_536_000, 0) .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(1)) } /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -181,10 +166,6 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) @@ -193,13 +174,13 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_subscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `145` - // Estimated: `3610` - // Minimum execution time: 35_869_000 picoseconds. - Weight::from_parts(37_848_000, 0) - .saturating_add(Weight::from_parts(0, 3610)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(5)) + // Measured: `42` + // Estimated: `3507` + // Minimum execution time: 28_913_000 picoseconds. + Weight::from_parts(29_949_000, 0) + .saturating_add(Weight::from_parts(0, 3507)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(4)) } /// Storage: `PolkadotXcm::VersionNotifiers` (r:1 w:1) /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -207,10 +188,6 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) @@ -219,13 +196,13 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn force_unsubscribe_version_notify() -> Weight { // Proof Size summary in bytes: - // Measured: `363` - // Estimated: `3828` - // Minimum execution time: 38_649_000 picoseconds. - Weight::from_parts(39_842_000, 0) - .saturating_add(Weight::from_parts(0, 3828)) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `136` + // Estimated: `3601` + // Minimum execution time: 30_496_000 picoseconds. + Weight::from_parts(31_828_000, 0) + .saturating_add(Weight::from_parts(0, 3601)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `PolkadotXcm::XcmExecutionSuspended` (r:0 w:1) /// Proof: `PolkadotXcm::XcmExecutionSuspended` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -233,8 +210,8 @@ impl pallet_xcm::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_223_000 picoseconds. - Weight::from_parts(2_483_000, 0) + // Minimum execution time: 2_435_000 picoseconds. + Weight::from_parts(2_635_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -242,11 +219,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_supported_version() -> Weight { // Proof Size summary in bytes: - // Measured: `159` - // Estimated: `15999` - // Minimum execution time: 24_164_000 picoseconds. - Weight::from_parts(24_972_000, 0) - .saturating_add(Weight::from_parts(0, 15999)) + // Measured: `22` + // Estimated: `15862` + // Minimum execution time: 21_713_000 picoseconds. + Weight::from_parts(22_209_000, 0) + .saturating_add(Weight::from_parts(0, 15862)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -254,11 +231,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::VersionNotifiers` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notifiers() -> Weight { // Proof Size summary in bytes: - // Measured: `163` - // Estimated: `16003` - // Minimum execution time: 24_604_000 picoseconds. - Weight::from_parts(25_047_000, 0) - .saturating_add(Weight::from_parts(0, 16003)) + // Measured: `26` + // Estimated: `15866` + // Minimum execution time: 22_035_000 picoseconds. + Weight::from_parts(22_675_000, 0) + .saturating_add(Weight::from_parts(0, 15866)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -266,11 +243,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn already_notified_target() -> Weight { // Proof Size summary in bytes: - // Measured: `173` - // Estimated: `18488` - // Minimum execution time: 28_088_000 picoseconds. - Weight::from_parts(28_431_000, 0) - .saturating_add(Weight::from_parts(0, 18488)) + // Measured: `36` + // Estimated: `18351` + // Minimum execution time: 24_882_000 picoseconds. + Weight::from_parts(25_172_000, 0) + .saturating_add(Weight::from_parts(0, 18351)) .saturating_add(T::DbWeight::get().reads(7)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:2 w:1) @@ -279,44 +256,40 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn notify_current_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `212` - // Estimated: `6152` - // Minimum execution time: 33_814_000 picoseconds. - Weight::from_parts(34_741_000, 0) - .saturating_add(Weight::from_parts(0, 6152)) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `75` + // Estimated: `6015` + // Minimum execution time: 28_244_000 picoseconds. + Weight::from_parts(28_873_000, 0) + .saturating_add(Weight::from_parts(0, 6015)) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:5 w:0) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn notify_target_migration_fail() -> Weight { // Proof Size summary in bytes: - // Measured: `176` - // Estimated: `13541` - // Minimum execution time: 18_242_000 picoseconds. - Weight::from_parts(18_636_000, 0) - .saturating_add(Weight::from_parts(0, 13541)) + // Measured: `39` + // Estimated: `13404` + // Minimum execution time: 17_457_000 picoseconds. + Weight::from_parts(18_023_000, 0) + .saturating_add(Weight::from_parts(0, 13404)) .saturating_add(T::DbWeight::get().reads(5)) } /// Storage: `PolkadotXcm::VersionNotifyTargets` (r:6 w:2) /// Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) fn migrate_version_notify_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `170` - // Estimated: `16010` - // Minimum execution time: 24_249_000 picoseconds. - Weight::from_parts(24_768_000, 0) - .saturating_add(Weight::from_parts(0, 16010)) + // Measured: `33` + // Estimated: `15873` + // Minimum execution time: 22_283_000 picoseconds. + Weight::from_parts(22_783_000, 0) + .saturating_add(Weight::from_parts(0, 15873)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -326,23 +299,19 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) /// Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - /// Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - /// Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - /// Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) /// Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) /// Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) /// Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn migrate_and_notify_old_targets() -> Weight { // Proof Size summary in bytes: - // Measured: `212` - // Estimated: `16052` - // Minimum execution time: 47_602_000 picoseconds. - Weight::from_parts(48_378_000, 0) - .saturating_add(Weight::from_parts(0, 16052)) - .saturating_add(T::DbWeight::get().reads(12)) - .saturating_add(T::DbWeight::get().writes(4)) + // Measured: `75` + // Estimated: `15915` + // Minimum execution time: 41_244_000 picoseconds. + Weight::from_parts(42_264_000, 0) + .saturating_add(Weight::from_parts(0, 15915)) + .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().writes(3)) } /// Storage: `PolkadotXcm::QueryCounter` (r:1 w:1) /// Proof: `PolkadotXcm::QueryCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) @@ -350,11 +319,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn new_query() -> Weight { // Proof Size summary in bytes: - // Measured: `103` - // Estimated: `1588` - // Minimum execution time: 5_566_000 picoseconds. - Weight::from_parts(5_768_000, 0) - .saturating_add(Weight::from_parts(0, 1588)) + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 2_678_000 picoseconds. + Weight::from_parts(2_892_000, 0) + .saturating_add(Weight::from_parts(0, 1485)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -362,11 +331,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) fn take_response() -> Weight { // Proof Size summary in bytes: - // Measured: `7740` - // Estimated: `11205` - // Minimum execution time: 30_821_000 picoseconds. - Weight::from_parts(31_250_000, 0) - .saturating_add(Weight::from_parts(0, 11205)) + // Measured: `7576` + // Estimated: `11041` + // Minimum execution time: 26_677_000 picoseconds. + Weight::from_parts(27_470_000, 0) + .saturating_add(Weight::from_parts(0, 11041)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -376,11 +345,11 @@ impl pallet_xcm::WeightInfo for WeightInfo { /// Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) fn claim_assets() -> Weight { // Proof Size summary in bytes: - // Measured: `160` - // Estimated: `3625` - // Minimum execution time: 43_463_000 picoseconds. - Weight::from_parts(44_960_000, 0) - .saturating_add(Weight::from_parts(0, 3625)) + // Measured: `23` + // Estimated: `3488` + // Minimum execution time: 40_143_000 picoseconds. + Weight::from_parts(41_712_000, 0) + .saturating_add(Weight::from_parts(0, 3488)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs index 00826cbb8d79..f6a140f3157f 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_fungible.rs @@ -17,26 +17,28 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::fungible` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-10-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-augrssgt-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `17a605d70d1a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("collectives-westend-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=collectives-westend-dev +// --pallet=pallet_xcm_benchmarks::fungible +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm_benchmarks::fungible -// --chain=collectives-westend-dev -// --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/ +// --template=cumulus/templates/xcm-bench-template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,8 +56,8 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `101` // Estimated: `3593` - // Minimum execution time: 30_401_000 picoseconds. - Weight::from_parts(30_813_000, 3593) + // Minimum execution time: 32_692_000 picoseconds. + Weight::from_parts(33_469_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -63,33 +65,31 @@ impl WeightInfo { // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) pub fn transfer_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `153` + // Measured: `101` // Estimated: `6196` - // Minimum execution time: 43_150_000 picoseconds. - Weight::from_parts(43_919_000, 6196) + // Minimum execution time: 42_464_000 picoseconds. + Weight::from_parts(43_897_000, 6196) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } - // Storage: `System::Account` (r:2 w:2) + // Storage: `System::Account` (r:3 w:3) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn transfer_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `223` - // Estimated: `6196` - // Minimum execution time: 67_808_000 picoseconds. - Weight::from_parts(69_114_000, 6196) + // Measured: `212` + // Estimated: `8799` + // Minimum execution time: 105_472_000 picoseconds. + Weight::from_parts(115_465_000, 8799) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(4)) } @@ -104,51 +104,49 @@ impl WeightInfo { } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_reserve_withdraw() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 29_312_000 picoseconds. - Weight::from_parts(30_347_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 72_377_000 picoseconds. + Weight::from_parts(76_456_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } pub fn receive_teleported_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_283_000 picoseconds. - Weight::from_parts(2_448_000, 0) + // Minimum execution time: 2_556_000 picoseconds. + Weight::from_parts(2_960_000, 0) } // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) pub fn deposit_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `52` + // Measured: `0` // Estimated: `3593` - // Minimum execution time: 23_556_000 picoseconds. - Weight::from_parts(24_419_000, 3593) + // Minimum execution time: 24_560_000 picoseconds. + Weight::from_parts(24_926_000, 3593) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `System::Account` (r:1 w:1) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) @@ -157,54 +155,50 @@ impl WeightInfo { // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn deposit_reserve_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `122` + // Measured: `111` // Estimated: `3593` - // Minimum execution time: 58_342_000 picoseconds. - Weight::from_parts(59_598_000, 3593) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) + // Minimum execution time: 57_780_000 picoseconds. + Weight::from_parts(59_561_000, 3593) + .saturating_add(T::DbWeight::get().reads(6)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_teleport() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 28_285_000 picoseconds. - Weight::from_parts(29_016_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `111` + // Estimated: `3576` + // Minimum execution time: 37_041_000 picoseconds. + Weight::from_parts(38_101_000, 3576) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(1)) } - // Storage: `System::Account` (r:1 w:1) + // Storage: `System::Account` (r:2 w:2) // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn initiate_transfer() -> Weight { // Proof Size summary in bytes: - // Measured: `122` - // Estimated: `3593` - // Minimum execution time: 65_211_000 picoseconds. - Weight::from_parts(67_200_000, 3593) + // Measured: `111` + // Estimated: `6196` + // Minimum execution time: 87_635_000 picoseconds. + Weight::from_parts(89_712_000, 6196) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(3)) } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs index ae94edc3d731..8e732546437a 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/pallet_xcm_benchmarks_generic.rs @@ -17,26 +17,28 @@ //! Autogenerated weights for `pallet_xcm_benchmarks::generic` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-08-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-svzsllib-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `96ae15bb1012`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: Compiled, CHAIN: Some("collectives-westend-dev"), DB CACHE: 1024 // Executed Command: // target/production/polkadot-parachain // benchmark // pallet -// --steps=50 -// --repeat=20 // --extrinsic=* +// --chain=collectives-westend-dev +// --pallet=pallet_xcm_benchmarks::generic +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm // --wasm-execution=compiled +// --steps=50 +// --repeat=20 // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json -// --pallet=pallet_xcm_benchmarks::generic -// --chain=collectives-westend-dev -// --header=./cumulus/file_header.txt -// --template=./cumulus/templates/xcm-bench-template.hbs -// --output=./cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/xcm/ +// --template=cumulus/templates/xcm-bench-template.hbs +// --no-storage-info +// --no-min-squares +// --no-median-slopes #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,127 +52,145 @@ pub struct WeightInfo(PhantomData); impl WeightInfo { // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_holding() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 29_015_000 picoseconds. - Weight::from_parts(30_359_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 72_839_000 picoseconds. + Weight::from_parts(74_957_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } pub fn buy_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 572_000 picoseconds. - Weight::from_parts(637_000, 0) + // Minimum execution time: 592_000 picoseconds. + Weight::from_parts(646_000, 0) } + // Storage: `System::Account` (r:1 w:1) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) pub fn pay_fees() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 3_630_000 picoseconds. + Weight::from_parts(3_843_000, 3593) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + pub fn asset_claimer() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_550_000 picoseconds. - Weight::from_parts(1_604_000, 0) + // Minimum execution time: 660_000 picoseconds. + Weight::from_parts(712_000, 0) } // Storage: `PolkadotXcm::Queries` (r:1 w:0) // Proof: `PolkadotXcm::Queries` (`max_values`: None, `max_size`: None, mode: `Measured`) pub fn query_response() -> Weight { // Proof Size summary in bytes: - // Measured: `32` - // Estimated: `3497` - // Minimum execution time: 7_354_000 picoseconds. - Weight::from_parts(7_808_000, 3497) + // Measured: `0` + // Estimated: `3465` + // Minimum execution time: 5_996_000 picoseconds. + Weight::from_parts(6_277_000, 3465) .saturating_add(T::DbWeight::get().reads(1)) } pub fn transact() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 6_716_000 picoseconds. - Weight::from_parts(7_067_000, 0) + // Minimum execution time: 7_427_000 picoseconds. + Weight::from_parts(7_817_000, 0) } pub fn refund_surplus() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 1_280_000 picoseconds. - Weight::from_parts(1_355_000, 0) + // Minimum execution time: 1_245_000 picoseconds. + Weight::from_parts(1_373_000, 0) } pub fn set_error_handler() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 587_000 picoseconds. - Weight::from_parts(645_000, 0) + // Minimum execution time: 589_000 picoseconds. + Weight::from_parts(647_000, 0) } pub fn set_appendix() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 629_000 picoseconds. - Weight::from_parts(662_000, 0) + // Minimum execution time: 593_000 picoseconds. + Weight::from_parts(653_000, 0) } pub fn clear_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 590_000 picoseconds. - Weight::from_parts(639_000, 0) + // Minimum execution time: 599_000 picoseconds. + Weight::from_parts(652_000, 0) } pub fn descend_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 651_000 picoseconds. - Weight::from_parts(688_000, 0) + // Minimum execution time: 620_000 picoseconds. + Weight::from_parts(670_000, 0) + } + pub fn execute_with_origin() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 682_000 picoseconds. + Weight::from_parts(747_000, 0) } pub fn clear_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 601_000 picoseconds. - Weight::from_parts(630_000, 0) + // Minimum execution time: 596_000 picoseconds. + Weight::from_parts(650_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_error() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 25_650_000 picoseconds. - Weight::from_parts(26_440_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 68_183_000 picoseconds. + Weight::from_parts(70_042_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } // Storage: `PolkadotXcm::AssetTraps` (r:1 w:1) // Proof: `PolkadotXcm::AssetTraps` (`max_values`: None, `max_size`: None, mode: `Measured`) pub fn claim_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `90` - // Estimated: `3555` - // Minimum execution time: 10_492_000 picoseconds. - Weight::from_parts(10_875_000, 3555) + // Measured: `23` + // Estimated: `3488` + // Minimum execution time: 9_661_000 picoseconds. + Weight::from_parts(9_943_000, 3488) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -178,29 +198,27 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 597_000 picoseconds. - Weight::from_parts(647_000, 0) + // Minimum execution time: 580_000 picoseconds. + Weight::from_parts(652_000, 0) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:1 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn subscribe_version() -> Weight { // Proof Size summary in bytes: - // Measured: `38` - // Estimated: `3503` - // Minimum execution time: 23_732_000 picoseconds. - Weight::from_parts(24_290_000, 3503) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) + // Measured: `42` + // Estimated: `3507` + // Minimum execution time: 24_197_000 picoseconds. + Weight::from_parts(25_199_000, 3507) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: `PolkadotXcm::VersionNotifyTargets` (r:0 w:1) // Proof: `PolkadotXcm::VersionNotifyTargets` (`max_values`: None, `max_size`: None, mode: `Measured`) @@ -208,148 +226,134 @@ impl WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_446_000 picoseconds. - Weight::from_parts(2_613_000, 0) + // Minimum execution time: 2_720_000 picoseconds. + Weight::from_parts(2_881_000, 0) .saturating_add(T::DbWeight::get().writes(1)) } pub fn burn_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 960_000 picoseconds. - Weight::from_parts(1_045_000, 0) + // Minimum execution time: 950_000 picoseconds. + Weight::from_parts(1_076_000, 0) } pub fn expect_asset() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 703_000 picoseconds. - Weight::from_parts(739_000, 0) + // Minimum execution time: 742_000 picoseconds. + Weight::from_parts(785_000, 0) } pub fn expect_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 616_000 picoseconds. - Weight::from_parts(651_000, 0) + // Minimum execution time: 598_000 picoseconds. + Weight::from_parts(671_000, 0) } pub fn expect_error() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 621_000 picoseconds. - Weight::from_parts(660_000, 0) + // Minimum execution time: 571_000 picoseconds. + Weight::from_parts(635_000, 0) } pub fn expect_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 794_000 picoseconds. - Weight::from_parts(831_000, 0) + // Minimum execution time: 766_000 picoseconds. + Weight::from_parts(835_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn query_pallet() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 29_527_000 picoseconds. - Weight::from_parts(30_614_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 76_301_000 picoseconds. + Weight::from_parts(79_269_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } pub fn expect_pallet() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_189_000 picoseconds. - Weight::from_parts(3_296_000, 0) + // Minimum execution time: 5_452_000 picoseconds. + Weight::from_parts(5_721_000, 0) } // Storage: `ParachainInfo::ParachainId` (r:1 w:0) // Proof: `ParachainInfo::ParachainId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + // Storage: `ParachainSystem::UpwardDeliveryFeeFactor` (r:1 w:0) + // Proof: `ParachainSystem::UpwardDeliveryFeeFactor` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `PolkadotXcm::SupportedVersion` (r:1 w:0) // Proof: `PolkadotXcm::SupportedVersion` (`max_values`: None, `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::VersionDiscoveryQueue` (r:1 w:1) - // Proof: `PolkadotXcm::VersionDiscoveryQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) - // Storage: `PolkadotXcm::SafeXcmVersion` (r:1 w:0) - // Proof: `PolkadotXcm::SafeXcmVersion` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + // Storage: `System::Account` (r:2 w:2) + // Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) // Storage: `ParachainSystem::HostConfiguration` (r:1 w:0) // Proof: `ParachainSystem::HostConfiguration` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) // Storage: `ParachainSystem::PendingUpwardMessages` (r:1 w:1) // Proof: `ParachainSystem::PendingUpwardMessages` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) pub fn report_transact_status() -> Weight { // Proof Size summary in bytes: - // Measured: `70` - // Estimated: `3535` - // Minimum execution time: 25_965_000 picoseconds. - Weight::from_parts(26_468_000, 3535) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(2)) + // Measured: `212` + // Estimated: `6196` + // Minimum execution time: 68_763_000 picoseconds. + Weight::from_parts(71_142_000, 6196) + .saturating_add(T::DbWeight::get().reads(7)) + .saturating_add(T::DbWeight::get().writes(3)) } pub fn clear_transact_status() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 618_000 picoseconds. - Weight::from_parts(659_000, 0) + // Minimum execution time: 630_000 picoseconds. + Weight::from_parts(676_000, 0) } pub fn set_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 593_000 picoseconds. - Weight::from_parts(618_000, 0) + // Minimum execution time: 570_000 picoseconds. + Weight::from_parts(622_000, 0) } pub fn clear_topic() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 603_000 picoseconds. - Weight::from_parts(634_000, 0) - } - pub fn alias_origin() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_000_000 picoseconds. - Weight::from_parts(2_000_000, 0) + // Minimum execution time: 549_000 picoseconds. + Weight::from_parts(603_000, 0) } pub fn set_fees_mode() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 568_000 picoseconds. - Weight::from_parts(629_000, 0) + // Minimum execution time: 578_000 picoseconds. + Weight::from_parts(626_000, 0) } pub fn unpaid_execution() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 598_000 picoseconds. - Weight::from_parts(655_000, 0) - } - pub fn asset_claimer() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 707_000 picoseconds. - Weight::from_parts(749_000, 0) + // Minimum execution time: 594_000 picoseconds. + Weight::from_parts(639_000, 0) } - pub fn execute_with_origin() -> Weight { + pub fn alias_origin() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 713_000 picoseconds. - Weight::from_parts(776_000, 0) + // Minimum execution time: 637_000 picoseconds. + Weight::from_parts(676_000, 0) } } From a2c63e8d8a512eca28ed24c3c58ea7609c28b9ee Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Fri, 24 Jan 2025 15:29:25 +0200 Subject: [PATCH 322/340] fix(cmd bench-omni): build omni-bencher with production profile (#7299) # Description This PR builds frame-omni-bencher with `production` profile when calling `/cmd bench-omni` to compute benchmarks for pallets. Fix proposed by @bkchr , thanks! Closes #6797. ## Integration N/A ## Review Notes More info on #6797, and related to how the fix was tested: https://github.com/paritytech/polkadot-sdk/issues/6797#issuecomment-2611903102. --------- Signed-off-by: Iulian Barbu Co-authored-by: command-bot <> --- .github/workflows/cmd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cmd.yml b/.github/workflows/cmd.yml index 3d4779064a44..247fc34f1b18 100644 --- a/.github/workflows/cmd.yml +++ b/.github/workflows/cmd.yml @@ -348,7 +348,7 @@ jobs: - name: Install dependencies for bench if: startsWith(needs.get-pr-info.outputs.CMD, 'bench') run: | - cargo install --path substrate/utils/frame/omni-bencher --locked + cargo install --path substrate/utils/frame/omni-bencher --locked --profile production - name: Run cmd id: cmd From 7710483541ce273df892c77a6e300aaa2efa1dca Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Fri, 24 Jan 2025 16:05:36 +0100 Subject: [PATCH 323/340] Bridges: emulated tests small nits/improvements (#7322) This PR includes minor fixes identified during work on the larger PR: [https://github.com/paritytech/polkadot-sdk/issues/6906](https://github.com/paritytech/polkadot-sdk/issues/6906). Specifically, this PR removes the use of `open_bridge_between_asset_hub_rococo_and_asset_hub_westend`, which is no longer relevant for BridgeHubs, as bridges are now created with genesis settings. This function was used in the generic `test_dry_run_transfer_across_pk_bridge` macro, which could cause compilation issues when used in other contexts (e.g. fellows repo). --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../emulated/common/src/macros.rs | 3 +- .../src/tests/asset_transfers.rs | 3 -- .../bridge-hub-rococo/src/tests/mod.rs | 43 ------------------- .../bridge-hub-rococo/src/tests/send_xcm.rs | 3 -- .../src/tests/asset_transfers.rs | 3 -- .../bridge-hub-westend/src/tests/mod.rs | 43 ------------------- .../bridge-hub-westend/src/tests/send_xcm.rs | 3 -- prdoc/pr_7322.prdoc | 8 ++++ 8 files changed, 9 insertions(+), 100 deletions(-) create mode 100644 prdoc/pr_7322.prdoc diff --git a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs index cd2b41e5198f..983ac626177e 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/macros.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/macros.rs @@ -644,9 +644,8 @@ macro_rules! test_dry_run_transfer_across_pk_bridge { let transfer_amount = 10_000_000_000_000u128; let initial_balance = transfer_amount * 10; - // Bridge setup. + // AssetHub setup. $sender_asset_hub::force_xcm_version($destination, XCM_VERSION); - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); <$sender_asset_hub as TestExt>::execute_with(|| { type Runtime = <$sender_asset_hub as Chain>::Runtime; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs index a2a61660afff..d1fe94962f18 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/asset_transfers.rs @@ -25,9 +25,6 @@ fn send_assets_over_bridge(send_fn: F) { AssetHubRococo::force_xcm_version(asset_hub_westend_location(), XCM_VERSION); BridgeHubRococo::force_xcm_version(bridge_hub_westend_location(), XCM_VERSION); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // send message over bridge send_fn(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs index 8aff87755961..265002897ac5 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/mod.rs @@ -51,9 +51,6 @@ pub(crate) fn bridged_roc_at_ah_westend() -> Location { } // WND and wWND -pub(crate) fn wnd_at_ah_westend() -> Location { - Parent.into() -} pub(crate) fn bridged_wnd_at_ah_rococo() -> Location { Location::new(2, [GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH))]) } @@ -240,43 +237,3 @@ pub(crate) fn assert_bridge_hub_westend_message_received() { ); }) } - -pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { - use testnet_parachains_constants::{ - rococo::currency::UNITS as ROC, westend::currency::UNITS as WND, - }; - - // open AHR -> AHW - BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), ROC * 5); - AssetHubRococo::open_bridge( - AssetHubRococo::sibling_location_of(BridgeHubRococo::para_id()), - [ - GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), - Parachain(AssetHubWestend::para_id().into()), - ] - .into(), - Some(( - (roc_at_ah_rococo(), ROC * 1).into(), - BridgeHubRococo::sovereign_account_id_of(BridgeHubRococo::sibling_location_of( - AssetHubRococo::para_id(), - )), - )), - ); - - // open AHW -> AHR - BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), WND * 5); - AssetHubWestend::open_bridge( - AssetHubWestend::sibling_location_of(BridgeHubWestend::para_id()), - [ - GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), - Parachain(AssetHubRococo::para_id().into()), - ] - .into(), - Some(( - (wnd_at_ah_westend(), WND * 1).into(), - BridgeHubWestend::sovereign_account_id_of(BridgeHubWestend::sibling_location_of( - AssetHubWestend::para_id(), - )), - )), - ); -} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs index cfcb581238e6..799af0378697 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/send_xcm.rs @@ -74,9 +74,6 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { // fund sender AssetHubRococo::fund_accounts(vec![(AssetHubRococoSender::get().into(), amount * 10)]); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // Initially set only default version on all runtimes let newer_xcm_version = xcm::prelude::XCM_VERSION; let older_xcm_version = newer_xcm_version - 1; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs index cc90c10b54bc..a73c1280b406 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/asset_transfers.rs @@ -26,9 +26,6 @@ fn send_assets_over_bridge(send_fn: F) { AssetHubWestend::force_xcm_version(asset_hub_rococo_location(), XCM_VERSION); BridgeHubWestend::force_xcm_version(bridge_hub_rococo_location(), XCM_VERSION); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // send message over bridge send_fn(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs index 6c1cdb98e8b2..676b2862e667 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs @@ -52,9 +52,6 @@ pub(crate) fn bridged_wnd_at_ah_rococo() -> Location { } // ROC and wROC -pub(crate) fn roc_at_ah_rococo() -> Location { - Parent.into() -} pub(crate) fn bridged_roc_at_ah_westend() -> Location { Location::new(2, [GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH))]) } @@ -250,43 +247,3 @@ pub(crate) fn assert_bridge_hub_rococo_message_received() { ); }) } - -pub(crate) fn open_bridge_between_asset_hub_rococo_and_asset_hub_westend() { - use testnet_parachains_constants::{ - rococo::currency::UNITS as ROC, westend::currency::UNITS as WND, - }; - - // open AHR -> AHW - BridgeHubRococo::fund_para_sovereign(AssetHubRococo::para_id(), ROC * 5); - AssetHubRococo::open_bridge( - AssetHubRococo::sibling_location_of(BridgeHubRococo::para_id()), - [ - GlobalConsensus(ByGenesis(WESTEND_GENESIS_HASH)), - Parachain(AssetHubWestend::para_id().into()), - ] - .into(), - Some(( - (roc_at_ah_rococo(), ROC * 1).into(), - BridgeHubRococo::sovereign_account_id_of(BridgeHubRococo::sibling_location_of( - AssetHubRococo::para_id(), - )), - )), - ); - - // open AHW -> AHR - BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id(), WND * 5); - AssetHubWestend::open_bridge( - AssetHubWestend::sibling_location_of(BridgeHubWestend::para_id()), - [ - GlobalConsensus(ByGenesis(ROCOCO_GENESIS_HASH)), - Parachain(AssetHubRococo::para_id().into()), - ] - .into(), - Some(( - (wnd_at_ah_westend(), WND * 1).into(), - BridgeHubWestend::sovereign_account_id_of(BridgeHubWestend::sibling_location_of( - AssetHubWestend::para_id(), - )), - )), - ); -} diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs index 60f8af2242f9..e655f06a0f01 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/send_xcm.rs @@ -74,9 +74,6 @@ fn send_xcm_through_opened_lane_with_different_xcm_version_on_hops_works() { // fund sender AssetHubWestend::fund_accounts(vec![(AssetHubWestendSender::get().into(), amount * 10)]); - // open bridge - open_bridge_between_asset_hub_rococo_and_asset_hub_westend(); - // Initially set only default version on all runtimes let newer_xcm_version = xcm::prelude::XCM_VERSION; let older_xcm_version = newer_xcm_version - 1; diff --git a/prdoc/pr_7322.prdoc b/prdoc/pr_7322.prdoc new file mode 100644 index 000000000000..72c566f7a814 --- /dev/null +++ b/prdoc/pr_7322.prdoc @@ -0,0 +1,8 @@ +title: 'Bridges: emulated tests small nits/improvements' +doc: +- audience: Runtime Dev + description: |- + This PR removes the use of `open_bridge_between_asset_hub_rococo_and_asset_hub_westend`. This function was used in the generic `test_dry_run_transfer_across_pk_bridge` macro, which could cause compilation issues when used in other contexts (e.g. fellows repo). +crates: +- name: emulated-integration-tests-common + bump: patch From ccd6337f1bfef8ff9da9020fefc25db5a6508da7 Mon Sep 17 00:00:00 2001 From: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Date: Fri, 24 Jan 2025 18:29:17 +0200 Subject: [PATCH 324/340] sync-templates: enable syncing from stable release patches (#7227) # Description We're unable to sync templates repos with what's in polkadot-sdk/templates for stable2412 because the tag which references the release (`polkadot-stable2412`) is missing the Plan.toml file, which is needed by PSVM, ran when syncing, to update the templates dependencies versions in Cargo.tomls. This PR adds a workflow `patch` input, to enable the workflow to use PSVM with a tag corresponding to a patch stable release (e.g. `polkadot-stable2412-1`), which will contain the `Plan.toml` file. ## Integration This enables the templates repos update with the contents of latest stable2412 release, in terms of polkadot-sdk/templates, which is relevant for getting-started docs. ## Review Notes This PR adds a `patch` input for the `misc-sync-templates.yml` workflow, which if set will be used with `psvm` accordingly to update templates repos' dependencies versions based on upcomming patch stable2412-1, which contains the `Plan.toml`. The workflow will be ran manually after stable2412-1 is out and this work is tracked under #6329 . Signed-off-by: Iulian Barbu --- .github/workflows/misc-sync-templates.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/misc-sync-templates.yml b/.github/workflows/misc-sync-templates.yml index ac66e697562b..ce01f010aa71 100644 --- a/.github/workflows/misc-sync-templates.yml +++ b/.github/workflows/misc-sync-templates.yml @@ -25,6 +25,10 @@ on: description: Enable runner debug logging required: false default: false + patch: + description: 'Patch number of the stable release we want to sync with' + required: false + default: "" jobs: sync-templates: @@ -139,7 +143,14 @@ jobs: rm -f "${{ env.template-path }}/src/lib.rs" - name: Run psvm on monorepo workspace dependencies - run: psvm -o -v ${{ github.event.inputs.stable_release_branch }} -p ./Cargo.toml + run: | + patch_input="${{ github.event.inputs.patch }}" + if [[ -n "$patch_input" ]]; then + patch="-$patch_input" + else + patch="" + fi + psvm -o -v "${{ github.event.inputs.stable_release_branch }}$patch" -p ./Cargo.toml working-directory: polkadot-sdk/ - name: Copy over required workspace dependencies run: | From 223bd28896cfa7ece1068c70da9f433a08da5554 Mon Sep 17 00:00:00 2001 From: PG Herveou Date: Fri, 24 Jan 2025 17:34:15 +0100 Subject: [PATCH 325/340] [pallet-revive] eth-rpc minor fixes (#7325) - Add option to specify database_url using DATABASE_URL environment variable - Add a eth-rpc-tester rust bin that can be used to test deployment before releasing eth-rpc - make evm_block non fallible so that it can return an Ok response for older blocks when the runtime API is not available - update cargo.lock to integrate changes from https://github.com/paritytech/subxt/pull/1904 --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- Cargo.lock | 12 +- prdoc/pr_7325.prdoc | 11 ++ substrate/frame/revive/rpc/Cargo.toml | 28 ++-- substrate/frame/revive/rpc/examples/README.md | 2 +- substrate/frame/revive/rpc/src/cli.rs | 6 +- substrate/frame/revive/rpc/src/client.rs | 11 +- substrate/frame/revive/rpc/src/eth-indexer.rs | 2 +- .../frame/revive/rpc/src/eth-rpc-tester.rs | 157 ++++++++++++++++++ substrate/frame/revive/rpc/src/example.rs | 2 - substrate/frame/revive/rpc/src/lib.rs | 4 +- 10 files changed, 198 insertions(+), 37 deletions(-) create mode 100644 prdoc/pr_7325.prdoc create mode 100644 substrate/frame/revive/rpc/src/eth-rpc-tester.rs diff --git a/Cargo.lock b/Cargo.lock index df2c58b7f4c1..e4bd817300f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8735,7 +8735,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.4.9", "tokio", "tower-service", "tracing", @@ -16456,7 +16456,7 @@ checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" dependencies = [ "bitcoin_hashes 0.13.0", "rand", - "rand_core 0.6.4", + "rand_core 0.5.1", "serde", "unicode-normalization", ] @@ -20721,7 +20721,7 @@ checksum = "f8650aabb6c35b860610e9cff5dc1af886c9e25073b7b1712a68972af4281302" dependencies = [ "bytes", "heck 0.5.0", - "itertools 0.13.0", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -20767,7 +20767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf0c195eebb4af52c752bec4f52f645da98b6e92077a04110c7f349477ae5ac" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.12.1", "proc-macro2 1.0.86", "quote 1.0.37", "syn 2.0.87", @@ -29085,9 +29085,9 @@ dependencies = [ [[package]] name = "subxt" -version = "0.38.0" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c53029d133e4e0cb7933f1fe06f2c68804b956de9bb8fa930ffca44e9e5e4230" +checksum = "1c17d7ec2359d33133b63c97e28c8b7cd3f0a5bc6ce567ae3aef9d9e85be3433" dependencies = [ "async-trait", "derive-where", diff --git a/prdoc/pr_7325.prdoc b/prdoc/pr_7325.prdoc new file mode 100644 index 000000000000..788f01cb3247 --- /dev/null +++ b/prdoc/pr_7325.prdoc @@ -0,0 +1,11 @@ +title: '[pallet-revive] eth-rpc minor fixes' +doc: +- audience: Runtime Dev + description: |- + - Add option to specify database_url from an environment variable + - Add a test-deployment.rs rust script that can be used to test deployment and call of a contract before releasing eth-rpc + - Make evm_block non fallible so that it can return an Ok response for older blocks when the runtime API is not available + - Update subxt version to integrate changes from https://github.com/paritytech/subxt/pull/1904 +crates: +- name: pallet-revive-eth-rpc + bump: minor diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 9d822f5ff8e2..014231f7f3e5 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -17,34 +17,33 @@ path = "src/main.rs" name = "eth-indexer" path = "src/eth-indexer.rs" +[[bin]] +name = "eth-rpc-tester" +path = "src/eth-rpc-tester.rs" + [[example]] name = "deploy" path = "examples/rust/deploy.rs" -required-features = ["example"] [[example]] name = "transfer" path = "examples/rust/transfer.rs" -required-features = ["example"] [[example]] name = "rpc-playground" path = "examples/rust/rpc-playground.rs" -required-features = ["example"] [[example]] name = "extrinsic" path = "examples/rust/extrinsic.rs" -required-features = ["example"] [[example]] name = "remark-extrinsic" path = "examples/rust/remark-extrinsic.rs" -required-features = ["example"] [dependencies] anyhow = { workspace = true } -clap = { workspace = true, features = ["derive"] } +clap = { workspace = true, features = ["derive", "env"] } codec = { workspace = true, features = ["derive"] } ethabi = { version = "18.0.0" } futures = { workspace = true, features = ["thread-pool"] } @@ -52,8 +51,9 @@ hex = { workspace = true } jsonrpsee = { workspace = true, features = ["full"] } log = { workspace = true } pallet-revive = { workspace = true, default-features = true } +pallet-revive-fixtures = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } -rlp = { workspace = true, optional = true } +rlp = { workspace = true } sc-cli = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } @@ -62,24 +62,18 @@ sp-arithmetic = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true } sp-weights = { workspace = true, default-features = true } -sqlx = { version = "0.8.2", features = [ - "macros", - "runtime-tokio", - "sqlite", +sqlx = { version = "0.8.2", features = ["macros", "runtime-tokio", "sqlite"] } +subxt = { workspace = true, default-features = true, features = [ + "reconnecting-rpc-client", ] } -subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"] } -subxt-signer = { workspace = true, optional = true, features = [ +subxt-signer = { workspace = true, features = [ "unstable-eth", ] } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } -[features] -example = ["rlp", "subxt-signer"] - [dev-dependencies] env_logger = { workspace = true } -pallet-revive-fixtures = { workspace = true, default-features = true } static_init = { workspace = true } substrate-cli-test-utils = { workspace = true } subxt-signer = { workspace = true, features = ["unstable-eth"] } diff --git a/substrate/frame/revive/rpc/examples/README.md b/substrate/frame/revive/rpc/examples/README.md index b9a2756b381d..1079c254b9c2 100644 --- a/substrate/frame/revive/rpc/examples/README.md +++ b/substrate/frame/revive/rpc/examples/README.md @@ -42,7 +42,7 @@ RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc -- --dev Run one of the examples from the `examples` directory to send a transaction to the node: ```bash -RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc --features example --example deploy +RUST_LOG="info,eth-rpc=debug" cargo run -p pallet-revive-eth-rpc --example deploy ``` ## JS examples diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index d63d596ab7a8..b6c57d2c3b0b 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -19,7 +19,7 @@ use crate::{ client::{connect, Client}, BlockInfoProvider, BlockInfoProviderImpl, CacheReceiptProvider, DBReceiptProvider, EthRpcServer, EthRpcServerImpl, ReceiptProvider, SystemHealthRpcServer, - SystemHealthRpcServerImpl, + SystemHealthRpcServerImpl, LOG_TARGET, }; use clap::Parser; use futures::{pin_mut, FutureExt}; @@ -52,7 +52,7 @@ pub struct CliCommand { /// The database used to store Ethereum transaction hashes. /// This is only useful if the node needs to act as an archive node and respond to Ethereum RPC /// queries for transactions that are not in the in memory cache. - #[clap(long)] + #[clap(long, env = "DATABASE_URL")] pub database_url: Option, /// If true, we will only read from the database and not write to it. @@ -148,6 +148,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { Arc::new(BlockInfoProviderImpl::new(cache_size, api.clone(), rpc.clone())); let receipt_provider: Arc = if let Some(database_url) = database_url.as_ref() { + log::info!(target: LOG_TARGET, "🔗 Connecting to provided database"); Arc::new(( CacheReceiptProvider::default(), DBReceiptProvider::new( @@ -158,6 +159,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { .await?, )) } else { + log::info!(target: LOG_TARGET, "🔌 No database provided, using in-memory cache"); Arc::new(CacheReceiptProvider::default()) }; diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 440972c7a681..47e439f06851 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -646,9 +646,9 @@ impl Client { &self, block: Arc, hydrated_transactions: bool, - ) -> Result { + ) -> Block { let runtime_api = self.api.runtime_api().at(block.hash()); - let gas_limit = Self::block_gas_limit(&runtime_api).await?; + let gas_limit = Self::block_gas_limit(&runtime_api).await.unwrap_or_default(); let header = block.header(); let timestamp = extract_block_timestamp(&block).await.unwrap_or_default(); @@ -658,7 +658,7 @@ impl Client { let state_root = header.state_root.0.into(); let extrinsics_root = header.extrinsics_root.0.into(); - let receipts = extract_receipts_from_block(&block).await?; + let receipts = extract_receipts_from_block(&block).await.unwrap_or_default(); let gas_used = receipts.iter().fold(U256::zero(), |acc, (_, receipt)| acc + receipt.gas_used); let transactions = if hydrated_transactions { @@ -675,7 +675,7 @@ impl Client { .into() }; - Ok(Block { + Block { hash: block.hash(), parent_hash, state_root, @@ -689,7 +689,7 @@ impl Client { receipts_root: extrinsics_root, transactions, ..Default::default() - }) + } } /// Convert a weight to a fee. @@ -697,7 +697,6 @@ impl Client { runtime_api: &subxt::runtime_api::RuntimeApi>, ) -> Result { let payload = subxt_client::apis().revive_api().block_gas_limit(); - let gas_limit = runtime_api.call(payload).await?; Ok(*gas_limit) } diff --git a/substrate/frame/revive/rpc/src/eth-indexer.rs b/substrate/frame/revive/rpc/src/eth-indexer.rs index 3e7f6b6fa91b..894143be0a52 100644 --- a/substrate/frame/revive/rpc/src/eth-indexer.rs +++ b/substrate/frame/revive/rpc/src/eth-indexer.rs @@ -37,7 +37,7 @@ pub struct CliCommand { pub oldest_block: Option, /// The database used to store Ethereum transaction hashes. - #[clap(long)] + #[clap(long, env = "DATABASE_URL")] pub database_url: String, #[allow(missing_docs)] diff --git a/substrate/frame/revive/rpc/src/eth-rpc-tester.rs b/substrate/frame/revive/rpc/src/eth-rpc-tester.rs new file mode 100644 index 000000000000..0ddad6874dfd --- /dev/null +++ b/substrate/frame/revive/rpc/src/eth-rpc-tester.rs @@ -0,0 +1,157 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use clap::Parser; +use jsonrpsee::http_client::HttpClientBuilder; +use pallet_revive::evm::{Account, BlockTag, ReceiptInfo}; +use pallet_revive_eth_rpc::{ + example::{wait_for_receipt, TransactionBuilder}, + EthRpcClient, +}; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + process::{Child, ChildStderr, Command}, + signal::unix::{signal, SignalKind}, +}; + +const DOCKER_CONTAINER_NAME: &str = "eth-rpc-test"; + +#[derive(Parser, Debug)] +#[clap(author, about, version)] +pub struct CliCommand { + /// The parity docker image e.g eth-rpc:master-fb2e414f + #[clap(long, default_value = "eth-rpc:master-fb2e414f")] + docker_image: String, + + /// The docker binary + /// Either docker or podman + #[clap(long, default_value = "docker")] + docker_bin: String, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let CliCommand { docker_bin, docker_image, .. } = CliCommand::parse(); + + let mut docker_process = start_docker(&docker_bin, &docker_image)?; + let stderr = docker_process.stderr.take().unwrap(); + + tokio::select! { + result = docker_process.wait() => { + println!("docker failed: {result:?}"); + } + _ = interrupt() => { + kill_docker().await?; + } + _ = test_eth_rpc(stderr) => { + kill_docker().await?; + } + } + + Ok(()) +} + +async fn interrupt() { + let mut sigint = signal(SignalKind::interrupt()).expect("failed to listen for SIGINT"); + let mut sigterm = signal(SignalKind::terminate()).expect("failed to listen for SIGTERM"); + + tokio::select! { + _ = sigint.recv() => {}, + _ = sigterm.recv() => {}, + } +} + +fn start_docker(docker_bin: &str, docker_image: &str) -> anyhow::Result { + let docker_process = Command::new(docker_bin) + .args([ + "run", + "--name", + DOCKER_CONTAINER_NAME, + "--rm", + "-p", + "8545:8545", + &format!("docker.io/paritypr/{docker_image}"), + "--node-rpc-url", + "wss://westend-asset-hub-rpc.polkadot.io", + "--rpc-cors", + "all", + "--unsafe-rpc-external", + "--log=sc_rpc_server:info", + ]) + .stderr(std::process::Stdio::piped()) + .kill_on_drop(true) + .spawn()?; + + Ok(docker_process) +} + +async fn kill_docker() -> anyhow::Result<()> { + Command::new("docker").args(["kill", DOCKER_CONTAINER_NAME]).output().await?; + Ok(()) +} + +async fn test_eth_rpc(stderr: ChildStderr) -> anyhow::Result<()> { + let mut reader = BufReader::new(stderr).lines(); + while let Some(line) = reader.next_line().await? { + println!("{line}"); + if line.contains("Running JSON-RPC server") { + break; + } + } + + let account = Account::default(); + let data = vec![]; + let (bytes, _) = pallet_revive_fixtures::compile_module("dummy")?; + let input = bytes.into_iter().chain(data).collect::>(); + + println!("Account:"); + println!("- address: {:?}", account.address()); + let client = HttpClientBuilder::default().build("http://localhost:8545")?; + + let nonce = client.get_transaction_count(account.address(), BlockTag::Latest.into()).await?; + let balance = client.get_balance(account.address(), BlockTag::Latest.into()).await?; + println!("- nonce: {nonce:?}"); + println!("- balance: {balance:?}"); + + println!("\n\n=== Deploying dummy contract ===\n\n"); + let hash = TransactionBuilder::default().input(input).send(&client).await?; + + println!("Hash: {hash:?}"); + println!("Waiting for receipt..."); + let ReceiptInfo { block_number, gas_used, contract_address, .. } = + wait_for_receipt(&client, hash).await?; + + let contract_address = contract_address.unwrap(); + println!("\nReceipt:"); + println!("Block explorer: https://westend-asset-hub-eth-explorer.parity.io/{hash:?}"); + println!("- Block number: {block_number}"); + println!("- Gas used: {gas_used}"); + println!("- Address: {contract_address:?}"); + + println!("\n\n=== Calling dummy contract ===\n\n"); + let hash = TransactionBuilder::default().to(contract_address).send(&client).await?; + + println!("Hash: {hash:?}"); + println!("Waiting for receipt..."); + + let ReceiptInfo { block_number, gas_used, to, .. } = wait_for_receipt(&client, hash).await?; + println!("\nReceipt:"); + println!("Block explorer: https://westend-asset-hub-eth-explorer.parity.io/{hash:?}"); + println!("- Block number: {block_number}"); + println!("- Gas used: {gas_used}"); + println!("- To: {to:?}"); + Ok(()) +} diff --git a/substrate/frame/revive/rpc/src/example.rs b/substrate/frame/revive/rpc/src/example.rs index 3b9a33296ef4..aad5b4fbc344 100644 --- a/substrate/frame/revive/rpc/src/example.rs +++ b/substrate/frame/revive/rpc/src/example.rs @@ -15,8 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. //! Example utilities -#![cfg(any(feature = "example", test))] - use crate::{EthRpcClient, ReceiptInfo}; use anyhow::Context; use pallet_revive::evm::{ diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 5e1341e2a29a..fcf93fa6c0d2 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -214,7 +214,7 @@ impl EthRpcServer for EthRpcServerImpl { let Some(block) = self.client.block_by_hash(&block_hash).await? else { return Ok(None); }; - let block = self.client.evm_block(block, hydrated_transactions).await?; + let block = self.client.evm_block(block, hydrated_transactions).await; Ok(Some(block)) } @@ -254,7 +254,7 @@ impl EthRpcServer for EthRpcServerImpl { let Some(block) = self.client.block_by_number_or_tag(&block).await? else { return Ok(None); }; - let block = self.client.evm_block(block, hydrated_transactions).await?; + let block = self.client.evm_block(block, hydrated_transactions).await; Ok(Some(block)) } From dcbea60cc7a280f37986f2f815ec3fcff4758be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Fri, 24 Jan 2025 19:20:09 +0100 Subject: [PATCH 326/340] revive: Fix compilation of `uapi` crate when `unstable-hostfn` is not set (#7318) This regression was introduced with some of the recent PRs. Regression fixed and test added. --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/build-misc.yml | 28 +++++++++++++++++++++++++ prdoc/pr_7318.prdoc | 8 +++++++ substrate/frame/revive/uapi/src/host.rs | 26 +++++++++++------------ 3 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 prdoc/pr_7318.prdoc diff --git a/.github/workflows/build-misc.yml b/.github/workflows/build-misc.yml index 335c26282027..e1ef29f305d0 100644 --- a/.github/workflows/build-misc.yml +++ b/.github/workflows/build-misc.yml @@ -46,6 +46,34 @@ jobs: app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} app-key: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_KEY }} + # As part of our test fixtures we build the revive-uapi crate always with the `unstable-hostfn` feature. + # To make sure that it won't break for users downstream which are not setting this feature + # It doesn't need to produce working code so we just use a similar enough RISC-V target + check-revive-stable-uapi-polkavm: + timeout-minutes: 30 + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} + container: + image: ${{ needs.preflight.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Check Rust + run: | + rustup show + rustup +nightly show + + - name: Build + id: required + run: forklift cargo +nightly check -p pallet-revive-uapi --no-default-features --target riscv64imac-unknown-none-elf -Zbuild-std=core + - name: Stop all workflows if failed + if: ${{ failure() && steps.required.conclusion == 'failure' && !github.event.pull_request.head.repo.fork }} + uses: ./.github/actions/workflow-stopper + with: + app-id: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_ID }} + app-key: ${{ secrets.WORKFLOW_STOPPER_RUNNER_APP_KEY }} + build-subkey: timeout-minutes: 20 needs: [preflight] diff --git a/prdoc/pr_7318.prdoc b/prdoc/pr_7318.prdoc new file mode 100644 index 000000000000..ec41b648a9c2 --- /dev/null +++ b/prdoc/pr_7318.prdoc @@ -0,0 +1,8 @@ +title: 'revive: Fix compilation of `uapi` crate when `unstable-hostfn` is not set' +doc: +- audience: Runtime Dev + description: This regression was introduced with some of the recent PRs. Regression + fixed and test added. +crates: +- name: pallet-revive-uapi + bump: minor diff --git a/substrate/frame/revive/uapi/src/host.rs b/substrate/frame/revive/uapi/src/host.rs index 3e5cf0eb0c24..130cbf97ad50 100644 --- a/substrate/frame/revive/uapi/src/host.rs +++ b/substrate/frame/revive/uapi/src/host.rs @@ -144,18 +144,6 @@ pub trait HostFn: private::Sealed { /// - `output`: A reference to the output data buffer to write the origin's address. fn origin(output: &mut [u8; 20]); - /// Retrieve the account id for a specified address. - /// - /// # Parameters - /// - /// - `addr`: A `H160` address. - /// - `output`: A reference to the output data buffer to write the account id. - /// - /// # Note - /// - /// If no mapping exists for `addr`, the fallback account id will be returned. - fn to_account_id(addr: &[u8; 20], output: &mut [u8]); - /// Retrieve the code hash for a specified contract address. /// /// # Parameters @@ -415,9 +403,21 @@ pub trait HostFn: private::Sealed { /// # Parameters /// /// - `output`: A reference to the output data buffer to write the block number. - #[unstable_hostfn] fn block_number(output: &mut [u8; 32]); + /// Retrieve the account id for a specified address. + /// + /// # Parameters + /// + /// - `addr`: A `H160` address. + /// - `output`: A reference to the output data buffer to write the account id. + /// + /// # Note + /// + /// If no mapping exists for `addr`, the fallback account id will be returned. + #[unstable_hostfn] + fn to_account_id(addr: &[u8; 20], output: &mut [u8]); + /// Stores the block hash of the given block number into the supplied buffer. /// /// # Parameters From a31d26dc30d90ca3b228d07fda8d3f94da6aa155 Mon Sep 17 00:00:00 2001 From: Andrei Eres Date: Fri, 24 Jan 2025 20:44:31 +0100 Subject: [PATCH 327/340] Fix the link to the chain snapshots (#7330) The link to Polkachu is not working --- substrate/utils/frame/benchmarking-cli/src/storage/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/substrate/utils/frame/benchmarking-cli/src/storage/README.md b/substrate/utils/frame/benchmarking-cli/src/storage/README.md index 95c83d2edbc5..955b52a248c6 100644 --- a/substrate/utils/frame/benchmarking-cli/src/storage/README.md +++ b/substrate/utils/frame/benchmarking-cli/src/storage/README.md @@ -13,7 +13,7 @@ Running the command on Substrate itself is not verify meaningful, since the gene used. The output for the Polkadot client with a recent chain snapshot will give you a better impression. A recent snapshot can -be downloaded from [Polkachu]. +be downloaded from [Polkadot Snapshots]. Then run (remove the `--db=paritydb` if you have a RocksDB snapshot): ```sh cargo run --profile=production -- benchmark storage --dev --state-version=0 --db=paritydb --weight-path runtime/polkadot/constants/src/weights @@ -106,6 +106,6 @@ write: 71_347 * constants::WEIGHT_REF_TIME_PER_NANOS, License: Apache-2.0 -[Polkachu]: https://polkachu.com/snapshots +[Polkadot Snapshots]: https://snapshots.polkadot.io [paritydb_weights.rs]: https://github.com/paritytech/polkadot/blob/c254e5975711a6497af256f6831e9a6c752d28f5/runtime/polkadot/constants/src/weights/paritydb_weights.rs#L60 From 682f8cd22f5bcb76d1b98820b62be49d11deae10 Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Sat, 25 Jan 2025 12:04:45 +0900 Subject: [PATCH 328/340] `set_validation_data` register weight manually, do not use refund when the pre dispatch is zero. (#7327) Related https://github.com/paritytech/polkadot-sdk/issues/6772 For an extrinsic, in the post dispatch info, the actual weight is only used to reclaim unused weight. If the actual weight is more than the pre dispatch weight, then the extrinsic is using the minimum, e.g., the weight used registered in pre dispatch. In parachain-system pallet one call is `set_validation_data`. This call is returning an actual weight, but the pre-dispatch weight is 0. This PR fix the disregard of actual weight of `set_validation_data` by registering it manually. --- cumulus/pallets/parachain-system/src/lib.rs | 14 ++++++++++---- prdoc/pr_7327.prdoc | 11 +++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 prdoc/pr_7327.prdoc diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 6857b08e66b7..fa754ea29ccf 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -45,7 +45,7 @@ use cumulus_primitives_core::{ use cumulus_primitives_parachain_inherent::{MessageQueueChain, ParachainInherentData}; use frame_support::{ defensive, - dispatch::{DispatchResult, Pays, PostDispatchInfo}, + dispatch::DispatchResult, ensure, inherent::{InherentData, InherentIdentifier, ProvideInherent}, traits::{Get, HandleMessage}, @@ -567,11 +567,12 @@ pub mod pallet { /// if the appropriate time has come. #[pallet::call_index(0)] #[pallet::weight((0, DispatchClass::Mandatory))] - // TODO: This weight should be corrected. + // TODO: This weight should be corrected. Currently the weight is registered manually in the + // call with `register_extra_weight_unchecked`. pub fn set_validation_data( origin: OriginFor, data: ParachainInherentData, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { ensure_none(origin)?; assert!( !>::exists(), @@ -692,7 +693,12 @@ pub mod pallet { vfp.relay_parent_number, )); - Ok(PostDispatchInfo { actual_weight: Some(total_weight), pays_fee: Pays::No }) + frame_system::Pallet::::register_extra_weight_unchecked( + total_weight, + DispatchClass::Mandatory, + ); + + Ok(()) } #[pallet::call_index(1)] diff --git a/prdoc/pr_7327.prdoc b/prdoc/pr_7327.prdoc new file mode 100644 index 000000000000..bb2d7a671af3 --- /dev/null +++ b/prdoc/pr_7327.prdoc @@ -0,0 +1,11 @@ +title: Correctly register the weight n `set_validation_data` in `cumulus-pallet-parachain-system` + +doc: + - audience: Runtime Dev + description: | + The actual weight of the call was register as a refund, but the pre-dispatch weight is 0, + and we can't refund from 0. Now the actual weight is registered manually instead of ignored. + +crates: + - name: cumulus-pallet-parachain-system + bump: patch From 17ae06272b366cbeb9429e01a3bf70d30a885a6f Mon Sep 17 00:00:00 2001 From: Guillaume Thiolliere Date: Sun, 26 Jan 2025 06:32:45 +0900 Subject: [PATCH 329/340] Improve debugging by using `#[track_caller]` in system `assert_last_event` and `assert_has_event` (#7142) Without track caller the error message of the assert points to the `assert_last_event` function, which is not useful. ``` thread 'tests::set_metadata_works' panicked at /home/gui/Developpement/polkadot-sdk/substrate/frame/system/src/lib.rs:2034:9: assertion `left == right` failed: expected event RuntimeEvent::Referenda(Event::MetadataSet { index: 0, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) is not equal to the last event RuntimeEvent::Referenda(Event::MetadataSet { index: 1, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) left: RuntimeEvent::Referenda(Event::MetadataSet { index: 1, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) right: RuntimeEvent::Referenda(Event::MetadataSet { index: 0, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) ``` With the track caller the error message points to the caller, showing the source of the error: ``` thread 'tests::set_metadata_works' panicked at substrate/frame/referenda/src/tests.rs:639:9: assertion `left == right` failed: expected event RuntimeEvent::Referenda(Event::MetadataSet { index: 0, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) is not equal to the last event RuntimeEvent::Referenda(Event::MetadataSet { index: 1, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) left: RuntimeEvent::Referenda(Event::MetadataSet { index: 1, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) right: RuntimeEvent::Referenda(Event::MetadataSet { index: 0, hash: 0xbb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aa }) ``` I also improved the error message to include a warning when checking events on block number zero. --- substrate/frame/system/src/lib.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index f2bb5e290c94..8980c6d6c8f4 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -2062,11 +2062,18 @@ impl Pallet { /// /// NOTE: Events not registered at the genesis block and quietly omitted. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + #[track_caller] pub fn assert_has_event(event: T::RuntimeEvent) { + let warn = if Self::block_number().is_zero() { + "WARNING: block number is zero, and events are not registered at block number zero.\n" + } else { + "" + }; + let events = Self::events(); assert!( events.iter().any(|record| record.event == event), - "expected event {event:?} not found in events {events:?}", + "{warn}expected event {event:?} not found in events {events:?}", ); } @@ -2074,11 +2081,22 @@ impl Pallet { /// /// NOTE: Events not registered at the genesis block and quietly omitted. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] + #[track_caller] pub fn assert_last_event(event: T::RuntimeEvent) { - let last_event = Self::events().last().expect("events expected").event.clone(); + let warn = if Self::block_number().is_zero() { + "WARNING: block number is zero, and events are not registered at block number zero.\n" + } else { + "" + }; + + let last_event = Self::events() + .last() + .expect(&alloc::format!("{warn}events expected")) + .event + .clone(); assert_eq!( last_event, event, - "expected event {event:?} is not equal to the last event {last_event:?}", + "{warn}expected event {event:?} is not equal to the last event {last_event:?}", ); } From c95e49c4c9848c42d5cbfd261de0d22eec9c2bf6 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Sun, 26 Jan 2025 22:18:43 +0100 Subject: [PATCH 330/340] Removed unused dependencies (partial progress) (#7329) Part of: https://github.com/paritytech/polkadot-sdk/issues/6906 --- Cargo.lock | 121 ------------------ Cargo.toml | 3 - bridges/relays/utils/Cargo.toml | 1 - .../pallets/ethereum-client/Cargo.toml | 1 - .../pallets/outbound-queue/Cargo.toml | 1 - bridges/snowbridge/pallets/system/Cargo.toml | 1 - .../snowbridge/primitives/ethereum/Cargo.toml | 1 - cumulus/client/network/Cargo.toml | 2 - cumulus/client/pov-recovery/Cargo.toml | 2 - .../Cargo.toml | 3 - .../assets/asset-hub-rococo/Cargo.toml | 2 - .../bridge-hubs/test-utils/Cargo.toml | 2 - cumulus/polkadot-parachain/Cargo.toml | 3 - cumulus/test/runtime/Cargo.toml | 2 - cumulus/test/service/Cargo.toml | 5 - cumulus/xcm/xcm-emulator/Cargo.toml | 1 - polkadot/node/metrics/Cargo.toml | 2 - polkadot/node/test/service/Cargo.toml | 1 - .../test-parachains/adder/collator/Cargo.toml | 1 - .../undying/collator/Cargo.toml | 1 - polkadot/runtime/parachains/Cargo.toml | 4 - polkadot/runtime/rococo/Cargo.toml | 10 -- polkadot/runtime/test-runtime/Cargo.toml | 1 - polkadot/runtime/westend/Cargo.toml | 8 -- polkadot/xcm/xcm-builder/Cargo.toml | 1 - substrate/bin/node/cli/Cargo.toml | 1 - substrate/client/api/Cargo.toml | 1 - substrate/client/db/Cargo.toml | 1 - substrate/client/network/common/Cargo.toml | 6 - substrate/client/telemetry/Cargo.toml | 1 - substrate/frame/contracts/Cargo.toml | 6 - .../frame/contracts/mock-network/Cargo.toml | 10 -- substrate/frame/conviction-voting/Cargo.toml | 4 - substrate/frame/delegated-staking/Cargo.toml | 1 - substrate/frame/fast-unstake/Cargo.toml | 1 - substrate/frame/glutton/Cargo.toml | 6 - substrate/frame/indices/Cargo.toml | 3 - substrate/frame/nfts/runtime-api/Cargo.toml | 3 +- substrate/frame/offences/Cargo.toml | 4 - substrate/frame/paged-list/Cargo.toml | 3 - substrate/frame/verify-signature/Cargo.toml | 15 --- substrate/test-utils/Cargo.toml | 8 -- substrate/test-utils/client/Cargo.toml | 1 - 43 files changed, 1 insertion(+), 254 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4bd817300f0..64c68d81d42b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -998,7 +998,6 @@ dependencies = [ "pallet-multisig 28.0.0", "pallet-nft-fractionalization 10.0.0", "pallet-nfts 22.0.0", - "pallet-nfts-runtime-api 14.0.0", "pallet-proxy 28.0.0", "pallet-session 28.0.0", "pallet-timestamp 27.0.0", @@ -2780,7 +2779,6 @@ dependencies = [ "bp-runtime 0.7.0", "bp-test-utils 0.7.0", "bp-xcm-bridge-hub 0.2.0", - "bridge-runtime-common 0.7.0", "cumulus-pallet-parachain-system 0.7.0", "cumulus-pallet-xcmp-queue 0.7.0", "frame-support 28.0.0", @@ -3893,16 +3891,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "const-hex" version = "1.14.0" @@ -4770,7 +4758,6 @@ dependencies = [ "polkadot-parachain-primitives 6.0.0", "polkadot-primitives 7.0.0", "polkadot-test-client", - "portpicker", "rstest", "sc-cli", "sc-client-api", @@ -4783,7 +4770,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "sp-version 29.0.0", - "substrate-test-utils", "tokio", "tracing", "url", @@ -4827,7 +4813,6 @@ dependencies = [ "polkadot-node-subsystem", "polkadot-overseer", "polkadot-primitives 7.0.0", - "portpicker", "rand", "rstest", "sc-cli", @@ -4841,7 +4826,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-tracing 16.0.0", "sp-version 29.0.0", - "substrate-test-utils", "tokio", "tracing", ] @@ -5471,7 +5455,6 @@ dependencies = [ "async-trait", "cumulus-primitives-core 0.7.0", "cumulus-relay-chain-interface", - "cumulus-test-service", "futures", "futures-timer", "polkadot-cli", @@ -5664,7 +5647,6 @@ dependencies = [ "pallet-aura 27.0.0", "pallet-authorship 28.0.0", "pallet-balances 28.0.0", - "pallet-collator-selection 9.0.0", "pallet-glutton 14.0.0", "pallet-message-queue 31.0.0", "pallet-session 28.0.0", @@ -5703,7 +5685,6 @@ dependencies = [ "cumulus-client-consensus-aura", "cumulus-client-consensus-common", "cumulus-client-consensus-proposer", - "cumulus-client-consensus-relay-chain", "cumulus-client-parachain-inherent", "cumulus-client-pov-recovery", "cumulus-client-service", @@ -5722,7 +5703,6 @@ dependencies = [ "jsonrpsee", "pallet-timestamp 27.0.0", "pallet-transaction-payment 28.0.0", - "parachains-common 7.0.0", "parity-scale-codec", "polkadot-cli", "polkadot-node-subsystem", @@ -5730,7 +5710,6 @@ dependencies = [ "polkadot-primitives 7.0.0", "polkadot-service", "polkadot-test-service", - "portpicker", "prometheus", "rand", "sc-basic-authorship", @@ -5766,7 +5745,6 @@ dependencies = [ "sp-timestamp 26.0.0", "sp-tracing 16.0.0", "substrate-test-client", - "substrate-test-utils", "tempfile", "tokio", "tracing", @@ -13004,13 +12982,11 @@ dependencies = [ "frame-system 28.0.0", "impl-trait-for-tuples", "log", - "pallet-assets 29.1.0", "pallet-balances 28.0.0", "pallet-contracts-fixtures", "pallet-contracts-proc-macro 18.0.0", "pallet-contracts-uapi 5.0.0", "pallet-insecure-randomness-collective-flip 16.0.0", - "pallet-message-queue 31.0.0", "pallet-proxy 28.0.0", "pallet-timestamp 27.0.0", "pallet-utility 28.0.0", @@ -13085,7 +13061,6 @@ dependencies = [ name = "pallet-contracts-mock-network" version = "3.0.0" dependencies = [ - "assert_matches", "frame-support 28.0.0", "frame-system 28.0.0", "pallet-assets 29.1.0", @@ -13094,17 +13069,13 @@ dependencies = [ "pallet-contracts-fixtures", "pallet-contracts-proc-macro 18.0.0", "pallet-contracts-uapi 5.0.0", - "pallet-insecure-randomness-collective-flip 16.0.0", "pallet-message-queue 31.0.0", - "pallet-proxy 28.0.0", "pallet-timestamp 27.0.0", - "pallet-utility 28.0.0", "pallet-xcm 7.0.0", "parity-scale-codec", "polkadot-parachain-primitives 6.0.0", "polkadot-primitives 7.0.0", "polkadot-runtime-parachains 7.0.0", - "pretty_assertions", "scale-info", "sp-api 26.0.0", "sp-core 28.0.0", @@ -13206,7 +13177,6 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "pallet-balances 28.0.0", - "pallet-scheduler 29.0.0", "parity-scale-codec", "scale-info", "serde", @@ -13301,7 +13271,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-staking 26.0.0", "sp-tracing 16.0.0", - "substrate-test-utils", ] [[package]] @@ -13701,7 +13670,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-staking 26.0.0", "sp-tracing 16.0.0", - "substrate-test-utils", ] [[package]] @@ -13732,7 +13700,6 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "log", - "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", "sp-core 28.0.0", @@ -13898,7 +13865,6 @@ dependencies = [ "scale-info", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", ] @@ -14265,7 +14231,6 @@ dependencies = [ name = "pallet-nfts-runtime-api" version = "14.0.0" dependencies = [ - "pallet-nfts 22.0.0", "parity-scale-codec", "sp-api 26.0.0", ] @@ -14486,7 +14451,6 @@ dependencies = [ "frame-support 28.0.0", "frame-system 28.0.0", "log", - "pallet-balances 28.0.0", "parity-scale-codec", "scale-info", "serde", @@ -14568,7 +14532,6 @@ name = "pallet-paged-list" version = "0.6.0" dependencies = [ "docify", - "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-system 28.0.0", "parity-scale-codec", @@ -16012,10 +15975,6 @@ dependencies = [ "frame-benchmarking 28.0.0", "frame-support 28.0.0", "frame-system 28.0.0", - "pallet-balances 28.0.0", - "pallet-collective 28.0.0", - "pallet-root-testing 4.0.0", - "pallet-timestamp 27.0.0", "parity-scale-codec", "scale-info", "sp-core 28.0.0", @@ -17072,12 +17031,6 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" -[[package]] -name = "platforms" -version = "3.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4c7666f2019727f9e8e14bf14456e99c707d780922869f1ba473eee101fa49" - [[package]] name = "plotters" version = "0.3.5" @@ -17955,7 +17908,6 @@ dependencies = [ name = "polkadot-node-metrics" version = "7.0.0" dependencies = [ - "assert_cmd", "bs58", "futures", "futures-timer", @@ -17973,7 +17925,6 @@ dependencies = [ "sc-tracing", "sp-keyring 31.0.0", "substrate-prometheus-endpoint", - "substrate-test-utils", "tempfile", "tokio", "tracing-gum", @@ -18254,7 +18205,6 @@ dependencies = [ "bridge-hub-westend-runtime", "collectives-westend-runtime", "color-eyre", - "contracts-rococo-runtime", "coretime-rococo-runtime", "coretime-westend-runtime", "cumulus-primitives-core 0.7.0", @@ -18594,7 +18544,6 @@ dependencies = [ "pallet-session 28.0.0", "pallet-staking 28.0.0", "pallet-timestamp 27.0.0", - "pallet-vesting 28.0.0", "parity-scale-codec", "polkadot-core-primitives 7.0.0", "polkadot-parachain-primitives 6.0.0", @@ -19846,7 +19795,6 @@ dependencies = [ "staging-xcm-executor 7.0.0", "substrate-wasm-builder 17.0.0", "test-runtime-constants", - "tiny-keccak", ] [[package]] @@ -19894,7 +19842,6 @@ dependencies = [ "sp-runtime 31.0.1", "sp-state-machine 0.35.0", "substrate-test-client", - "substrate-test-utils", "tempfile", "test-runtime-constants", "tokio", @@ -21307,7 +21254,6 @@ dependencies = [ "async-trait", "backoff", "bp-runtime 0.7.0", - "console", "futures", "isahc", "jsonpath_lib", @@ -21632,14 +21578,12 @@ dependencies = [ "pallet-beefy-mmr 28.0.0", "pallet-bounties 27.0.0", "pallet-child-bounties 27.0.0", - "pallet-collective 28.0.0", "pallet-conviction-voting 28.0.0", "pallet-democracy 28.0.0", "pallet-elections-phragmen 29.0.0", "pallet-grandpa 28.0.0", "pallet-identity 29.0.0", "pallet-indices 28.0.0", - "pallet-membership 28.0.0", "pallet-message-queue 31.0.0", "pallet-migrations 1.0.0", "pallet-mmr 27.0.0", @@ -21676,7 +21620,6 @@ dependencies = [ "polkadot-runtime-parachains 7.0.0", "rococo-runtime-constants 7.0.0", "scale-info", - "separator", "serde", "serde_derive", "serde_json", @@ -21708,7 +21651,6 @@ dependencies = [ "staging-xcm-executor 7.0.0", "static_assertions", "substrate-wasm-builder 17.0.0", - "tiny-keccak", "tokio", "xcm-runtime-apis 0.1.0", ] @@ -22463,7 +22405,6 @@ dependencies = [ "sp-state-machine 0.35.0", "sp-statement-store 10.0.0", "sp-storage 19.0.0", - "sp-test-primitives", "sp-trie 29.0.0", "substrate-prometheus-endpoint", "substrate-test-runtime", @@ -22486,7 +22427,6 @@ dependencies = [ "parity-db", "parity-scale-codec", "parking_lot 0.12.3", - "quickcheck", "rand", "sc-client-api", "sc-state-db", @@ -23223,16 +23163,10 @@ dependencies = [ name = "sc-network-common" version = "0.33.0" dependencies = [ - "async-trait", "bitflags 1.3.2", "futures", - "libp2p-identity", "parity-scale-codec", "prost-build", - "sc-consensus", - "sc-network-types", - "sp-consensus", - "sp-consensus-grandpa 13.0.0", "sp-runtime 31.0.1", "tempfile", ] @@ -23808,7 +23742,6 @@ dependencies = [ "parking_lot 0.12.3", "pin-project", "rand", - "sc-network", "sc-utils", "serde", "serde_json", @@ -24151,12 +24084,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -24353,12 +24280,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" -[[package]] -name = "separator" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" - [[package]] name = "serde" version = "1.0.214" @@ -25087,7 +25008,6 @@ dependencies = [ "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", - "wasm-bindgen-test", ] [[package]] @@ -25200,7 +25120,6 @@ dependencies = [ "snowbridge-pallet-ethereum-client-fixtures 0.9.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", "static_assertions", @@ -25353,7 +25272,6 @@ dependencies = [ "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", ] @@ -25400,7 +25318,6 @@ dependencies = [ "snowbridge-pallet-outbound-queue 0.2.0", "sp-core 28.0.0", "sp-io 30.0.0", - "sp-keyring 31.0.0", "sp-runtime 31.0.1", "sp-std 14.0.0", "staging-xcm 7.0.0", @@ -28275,7 +28192,6 @@ dependencies = [ "node-rpc", "node-testing", "parity-scale-codec", - "platforms", "polkadot-sdk 0.1.0", "pretty_assertions", "rand", @@ -28390,7 +28306,6 @@ dependencies = [ name = "staging-xcm-builder" version = "7.0.0" dependencies = [ - "assert_matches", "frame-support 28.0.0", "frame-system 28.0.0", "impl-trait-for-tuples", @@ -28880,7 +28795,6 @@ dependencies = [ "sc-client-db", "sc-consensus", "sc-executor 0.32.0", - "sc-offchain", "sc-service", "serde", "serde_json", @@ -28982,12 +28896,6 @@ dependencies = [ [[package]] name = "substrate-test-utils" version = "4.0.0-dev" -dependencies = [ - "futures", - "sc-service", - "tokio", - "trybuild", -] [[package]] name = "substrate-wasm-builder" @@ -29603,7 +29511,6 @@ dependencies = [ "sc-service", "sp-core 28.0.0", "sp-keyring 31.0.0", - "substrate-test-utils", "test-parachain-adder", "tokio", ] @@ -29650,7 +29557,6 @@ dependencies = [ "sc-service", "sp-core 28.0.0", "sp-keyring 31.0.0", - "substrate-test-utils", "test-parachain-undying", "tokio", ] @@ -30843,30 +30749,6 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" -[[package]] -name = "wasm-bindgen-test" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e6e302a7ea94f83a6d09e78e7dc7d9ca7b186bc2829c24a22d0753efd680671" -dependencies = [ - "console_error_panic_hook", - "js-sys", - "scoped-tls", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test-macro", -] - -[[package]] -name = "wasm-bindgen-test-macro" -version = "0.3.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecb993dd8c836930ed130e020e77d9b2e65dd0fbab1b67c790b0f5d80b11a575" -dependencies = [ - "proc-macro2 1.0.86", - "quote 1.0.37", -] - [[package]] name = "wasm-encoder" version = "0.31.1" @@ -31380,10 +31262,8 @@ dependencies = [ "pallet-balances 28.0.0", "pallet-beefy 28.0.0", "pallet-beefy-mmr 28.0.0", - "pallet-collective 28.0.0", "pallet-conviction-voting 28.0.0", "pallet-delegated-staking 1.0.0", - "pallet-democracy 28.0.0", "pallet-election-provider-multi-phase 27.0.0", "pallet-election-provider-support-benchmarking 27.0.0", "pallet-elections-phragmen 29.0.0", @@ -31969,7 +31849,6 @@ version = "0.5.0" dependencies = [ "array-bytes", "cumulus-pallet-parachain-system 0.7.0", - "cumulus-pallet-xcmp-queue 0.7.0", "cumulus-primitives-core 0.7.0", "cumulus-primitives-parachain-inherent 0.7.0", "cumulus-test-relay-sproof-builder 0.7.0", diff --git a/Cargo.toml b/Cargo.toml index 18c1dd2c68d2..7a906e7c0d64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1036,7 +1036,6 @@ people-rococo-runtime = { path = "cumulus/parachains/runtimes/people/people-roco people-westend-emulated-chain = { path = "cumulus/parachains/integration-tests/emulated/chains/parachains/people/people-westend" } people-westend-runtime = { path = "cumulus/parachains/runtimes/people/people-westend" } pin-project = { version = "1.1.3" } -platforms = { version = "3.4" } polkadot-approval-distribution = { path = "polkadot/node/network/approval-distribution", default-features = false } polkadot-availability-bitfield-distribution = { path = "polkadot/node/network/bitfield-distribution", default-features = false } polkadot-availability-distribution = { path = "polkadot/node/network/availability-distribution", default-features = false } @@ -1209,7 +1208,6 @@ schnorrkel = { version = "0.11.4", default-features = false } seccompiler = { version = "0.4.0" } secp256k1 = { version = "0.28.0", default-features = false } secrecy = { version = "0.8.0", default-features = false } -separator = { version = "0.4.1" } serde = { version = "1.0.214", default-features = false } serde-big-array = { version = "0.3.2" } serde_derive = { version = "1.0.117" } @@ -1372,7 +1370,6 @@ void = { version = "1.0.2" } w3f-bls = { version = "0.1.3", default-features = false } wait-timeout = { version = "0.2" } walkdir = { version = "2.5.0" } -wasm-bindgen-test = { version = "0.3.19" } wasm-instrument = { version = "0.4", default-features = false } wasm-opt = { version = "0.116" } wasm-timer = { version = "0.2.5" } diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/utils/Cargo.toml index 8592ca780eaa..6d28789daaec 100644 --- a/bridges/relays/utils/Cargo.toml +++ b/bridges/relays/utils/Cargo.toml @@ -15,7 +15,6 @@ anyhow = { workspace = true, default-features = true } async-std = { workspace = true } async-trait = { workspace = true } backoff = { workspace = true } -console = { workspace = true } futures = { workspace = true } isahc = { workspace = true } jsonpath_lib = { workspace = true } diff --git a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml index ebd8a1c6ed11..87b4c66d7753 100644 --- a/bridges/snowbridge/pallets/ethereum-client/Cargo.toml +++ b/bridges/snowbridge/pallets/ethereum-client/Cargo.toml @@ -45,7 +45,6 @@ serde = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } snowbridge-pallet-ethereum-client-fixtures = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml index f4910e6e6457..e343d4c684ab 100644 --- a/bridges/snowbridge/pallets/outbound-queue/Cargo.toml +++ b/bridges/snowbridge/pallets/outbound-queue/Cargo.toml @@ -36,7 +36,6 @@ snowbridge-outbound-queue-merkle-tree = { workspace = true } [dev-dependencies] pallet-message-queue = { workspace = true } -sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/pallets/system/Cargo.toml b/bridges/snowbridge/pallets/system/Cargo.toml index 3544925956b4..c695b1034f69 100644 --- a/bridges/snowbridge/pallets/system/Cargo.toml +++ b/bridges/snowbridge/pallets/system/Cargo.toml @@ -41,7 +41,6 @@ pallet-balances = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } snowbridge-pallet-outbound-queue = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] diff --git a/bridges/snowbridge/primitives/ethereum/Cargo.toml b/bridges/snowbridge/primitives/ethereum/Cargo.toml index 44ea2d0d222b..5c249354a53a 100644 --- a/bridges/snowbridge/primitives/ethereum/Cargo.toml +++ b/bridges/snowbridge/primitives/ethereum/Cargo.toml @@ -31,7 +31,6 @@ ethabi = { workspace = true } [dev-dependencies] rand = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } -wasm-bindgen-test = { workspace = true } [features] default = ["std"] diff --git a/cumulus/client/network/Cargo.toml b/cumulus/client/network/Cargo.toml index 11025f8f62e6..3fb7eac591aa 100644 --- a/cumulus/client/network/Cargo.toml +++ b/cumulus/client/network/Cargo.toml @@ -39,7 +39,6 @@ polkadot-primitives = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } [dev-dependencies] -portpicker = { workspace = true } rstest = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } url = { workspace = true } @@ -51,7 +50,6 @@ sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-keystore = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } # Polkadot polkadot-test-client = { workspace = true } diff --git a/cumulus/client/pov-recovery/Cargo.toml b/cumulus/client/pov-recovery/Cargo.toml index 7e7da7244a86..7c85318bdde3 100644 --- a/cumulus/client/pov-recovery/Cargo.toml +++ b/cumulus/client/pov-recovery/Cargo.toml @@ -41,7 +41,6 @@ cumulus-relay-chain-interface = { workspace = true, default-features = true } [dev-dependencies] assert_matches = { workspace = true } cumulus-test-client = { workspace = true } -portpicker = { workspace = true } rstest = { workspace = true } sc-utils = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } @@ -54,4 +53,3 @@ cumulus-test-service = { workspace = true } # Substrate sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } diff --git a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml index 2a590bbca562..1307ec76de85 100644 --- a/cumulus/client/relay-chain-inprocess-interface/Cargo.toml +++ b/cumulus/client/relay-chain-inprocess-interface/Cargo.toml @@ -45,6 +45,3 @@ sp-keyring = { workspace = true, default-features = true } metered = { features = ["futures_channel"], workspace = true } polkadot-primitives = { workspace = true, default-features = true } polkadot-test-client = { workspace = true } - -# Cumulus -cumulus-test-service = { workspace = true } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml index d612dd03c247..3da8aa9b6cfe 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/Cargo.toml @@ -40,7 +40,6 @@ pallet-message-queue = { workspace = true } pallet-multisig = { workspace = true } pallet-nft-fractionalization = { workspace = true } pallet-nfts = { workspace = true } -pallet-nfts-runtime-api = { workspace = true } pallet-proxy = { workspace = true } pallet-session = { workspace = true } pallet-timestamp = { workspace = true } @@ -226,7 +225,6 @@ std = [ "pallet-message-queue/std", "pallet-multisig/std", "pallet-nft-fractionalization/std", - "pallet-nfts-runtime-api/std", "pallet-nfts/std", "pallet-proxy/std", "pallet-session/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml index ace23e71c4d1..132e42deea4a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/test-utils/Cargo.toml @@ -50,7 +50,6 @@ bp-relayers = { workspace = true } bp-runtime = { workspace = true } bp-test-utils = { workspace = true } bp-xcm-bridge-hub = { workspace = true } -bridge-runtime-common = { workspace = true } pallet-bridge-grandpa = { workspace = true } pallet-bridge-messages = { features = ["test-helpers"], workspace = true } pallet-bridge-parachains = { workspace = true } @@ -69,7 +68,6 @@ std = [ "bp-runtime/std", "bp-test-utils/std", "bp-xcm-bridge-hub/std", - "bridge-runtime-common/std", "codec/std", "cumulus-pallet-parachain-system/std", "cumulus-pallet-xcmp-queue/std", diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml index 9130f60ceb38..6b578779997c 100644 --- a/cumulus/polkadot-parachain/Cargo.toml +++ b/cumulus/polkadot-parachain/Cargo.toml @@ -29,7 +29,6 @@ asset-hub-westend-runtime = { workspace = true } bridge-hub-rococo-runtime = { workspace = true, default-features = true } bridge-hub-westend-runtime = { workspace = true, default-features = true } collectives-westend-runtime = { workspace = true } -contracts-rococo-runtime = { workspace = true } coretime-rococo-runtime = { workspace = true } coretime-westend-runtime = { workspace = true } glutton-westend-runtime = { workspace = true } @@ -70,7 +69,6 @@ runtime-benchmarks = [ "bridge-hub-rococo-runtime/runtime-benchmarks", "bridge-hub-westend-runtime/runtime-benchmarks", "collectives-westend-runtime/runtime-benchmarks", - "contracts-rococo-runtime/runtime-benchmarks", "coretime-rococo-runtime/runtime-benchmarks", "coretime-westend-runtime/runtime-benchmarks", "glutton-westend-runtime/runtime-benchmarks", @@ -88,7 +86,6 @@ try-runtime = [ "bridge-hub-rococo-runtime/try-runtime", "bridge-hub-westend-runtime/try-runtime", "collectives-westend-runtime/try-runtime", - "contracts-rococo-runtime/try-runtime", "coretime-rococo-runtime/try-runtime", "coretime-westend-runtime/try-runtime", "glutton-westend-runtime/try-runtime", diff --git a/cumulus/test/runtime/Cargo.toml b/cumulus/test/runtime/Cargo.toml index 4cc4f483c028..71509f82bfe1 100644 --- a/cumulus/test/runtime/Cargo.toml +++ b/cumulus/test/runtime/Cargo.toml @@ -47,7 +47,6 @@ cumulus-pallet-parachain-system = { workspace = true } cumulus-pallet-weight-reclaim = { workspace = true } cumulus-primitives-aura = { workspace = true } cumulus-primitives-core = { workspace = true } -pallet-collator-selection = { workspace = true } parachain-info = { workspace = true } [build-dependencies] @@ -69,7 +68,6 @@ std = [ "pallet-aura/std", "pallet-authorship/std", "pallet-balances/std", - "pallet-collator-selection/std", "pallet-glutton/std", "pallet-message-queue/std", "pallet-session/std", diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml index 794007532621..407c657bd14e 100644 --- a/cumulus/test/service/Cargo.toml +++ b/cumulus/test/service/Cargo.toml @@ -76,7 +76,6 @@ cumulus-client-collator = { workspace = true, default-features = true } cumulus-client-consensus-aura = { workspace = true, default-features = true } cumulus-client-consensus-common = { workspace = true, default-features = true } cumulus-client-consensus-proposer = { workspace = true, default-features = true } -cumulus-client-consensus-relay-chain = { workspace = true, default-features = true } cumulus-client-parachain-inherent = { workspace = true, default-features = true } cumulus-client-pov-recovery = { workspace = true, default-features = true } cumulus-client-service = { workspace = true, default-features = true } @@ -89,12 +88,10 @@ cumulus-relay-chain-minimal-node = { workspace = true, default-features = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } cumulus-test-runtime = { workspace = true } pallet-timestamp = { workspace = true, default-features = true } -parachains-common = { workspace = true, default-features = true } [dev-dependencies] cumulus-test-client = { workspace = true } futures = { workspace = true } -portpicker = { workspace = true } sp-authority-discovery = { workspace = true, default-features = true } # Polkadot dependencies @@ -102,7 +99,6 @@ polkadot-test-service = { workspace = true } # Substrate dependencies sc-cli = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } [features] runtime-benchmarks = [ @@ -113,7 +109,6 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks", - "parachains-common/runtime-benchmarks", "polkadot-cli/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "polkadot-service/runtime-benchmarks", diff --git a/cumulus/xcm/xcm-emulator/Cargo.toml b/cumulus/xcm/xcm-emulator/Cargo.toml index ae8cb79bb55e..b6fbbe3e4ce4 100644 --- a/cumulus/xcm/xcm-emulator/Cargo.toml +++ b/cumulus/xcm/xcm-emulator/Cargo.toml @@ -33,7 +33,6 @@ sp-tracing = { workspace = true, default-features = true } # Cumulus cumulus-pallet-parachain-system = { workspace = true, default-features = true } -cumulus-pallet-xcmp-queue = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-primitives-parachain-inherent = { workspace = true, default-features = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } diff --git a/polkadot/node/metrics/Cargo.toml b/polkadot/node/metrics/Cargo.toml index 318deca4f243..8d15391b11c2 100644 --- a/polkadot/node/metrics/Cargo.toml +++ b/polkadot/node/metrics/Cargo.toml @@ -29,7 +29,6 @@ prometheus-endpoint = { workspace = true, default-features = true } sc-tracing = { workspace = true, default-features = true } [dev-dependencies] -assert_cmd = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } hyper-util = { features = ["client-legacy", "tokio"], workspace = true } @@ -37,7 +36,6 @@ polkadot-test-service = { features = ["runtime-metrics"], workspace = true } prometheus-parse = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } tempfile = { workspace = true } tokio = { workspace = true, default-features = true } diff --git a/polkadot/node/test/service/Cargo.toml b/polkadot/node/test/service/Cargo.toml index 54db2a0ac942..96bbdd2e7bde 100644 --- a/polkadot/node/test/service/Cargo.toml +++ b/polkadot/node/test/service/Cargo.toml @@ -62,7 +62,6 @@ substrate-test-client = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true } -substrate-test-utils = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } [features] diff --git a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml index 20305dc07c3a..301a0d10ba85 100644 --- a/polkadot/parachain/test-parachains/adder/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/adder/collator/Cargo.toml @@ -39,6 +39,5 @@ polkadot-test-service = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml index b964b4dc49ce..f4e6d4e58542 100644 --- a/polkadot/parachain/test-parachains/undying/collator/Cargo.toml +++ b/polkadot/parachain/test-parachains/undying/collator/Cargo.toml @@ -39,6 +39,5 @@ polkadot-test-service = { workspace = true } sc-service = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 7c00995d2291..6c87f7773c23 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -47,7 +47,6 @@ pallet-mmr = { workspace = true, optional = true } pallet-session = { workspace = true } pallet-staking = { workspace = true } pallet-timestamp = { workspace = true } -pallet-vesting = { workspace = true } polkadot-primitives = { workspace = true } xcm = { workspace = true } @@ -96,7 +95,6 @@ std = [ "pallet-session/std", "pallet-staking/std", "pallet-timestamp/std", - "pallet-vesting/std", "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", "polkadot-primitives/std", @@ -131,7 +129,6 @@ runtime-benchmarks = [ "pallet-mmr/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", - "pallet-vesting/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "sp-application-crypto", @@ -156,7 +153,6 @@ try-runtime = [ "pallet-session/try-runtime", "pallet-staking/try-runtime", "pallet-timestamp/try-runtime", - "pallet-vesting/try-runtime", "sp-runtime/try-runtime", ] runtime-metrics = [ diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml index e7f463566e3a..67c7cacc296b 100644 --- a/polkadot/runtime/rococo/Cargo.toml +++ b/polkadot/runtime/rococo/Cargo.toml @@ -59,14 +59,12 @@ pallet-beefy = { workspace = true } pallet-beefy-mmr = { workspace = true } pallet-bounties = { workspace = true } pallet-child-bounties = { workspace = true } -pallet-collective = { workspace = true } pallet-conviction-voting = { workspace = true } pallet-democracy = { workspace = true } pallet-elections-phragmen = { workspace = true } pallet-grandpa = { workspace = true } pallet-identity = { workspace = true } pallet-indices = { workspace = true } -pallet-membership = { workspace = true } pallet-message-queue = { workspace = true } pallet-migrations = { workspace = true } pallet-mmr = { workspace = true } @@ -115,12 +113,10 @@ xcm-runtime-apis = { workspace = true } [dev-dependencies] remote-externalities = { workspace = true, default-features = true } -separator = { workspace = true } serde_json = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true } sp-trie = { workspace = true, default-features = true } -tiny-keccak = { features = ["keccak"], workspace = true } tokio = { features = ["macros"], workspace = true, default-features = true } [build-dependencies] @@ -151,14 +147,12 @@ std = [ "pallet-beefy/std", "pallet-bounties/std", "pallet-child-bounties/std", - "pallet-collective/std", "pallet-conviction-voting/std", "pallet-democracy/std", "pallet-elections-phragmen/std", "pallet-grandpa/std", "pallet-identity/std", "pallet-indices/std", - "pallet-membership/std", "pallet-message-queue/std", "pallet-migrations/std", "pallet-mmr/std", @@ -234,14 +228,12 @@ runtime-benchmarks = [ "pallet-beefy-mmr/runtime-benchmarks", "pallet-bounties/runtime-benchmarks", "pallet-child-bounties/runtime-benchmarks", - "pallet-collective/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", "pallet-identity/runtime-benchmarks", "pallet-indices/runtime-benchmarks", - "pallet-membership/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-migrations/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", @@ -294,14 +286,12 @@ try-runtime = [ "pallet-beefy/try-runtime", "pallet-bounties/try-runtime", "pallet-child-bounties/try-runtime", - "pallet-collective/try-runtime", "pallet-conviction-voting/try-runtime", "pallet-democracy/try-runtime", "pallet-elections-phragmen/try-runtime", "pallet-grandpa/try-runtime", "pallet-identity/try-runtime", "pallet-indices/try-runtime", - "pallet-membership/try-runtime", "pallet-message-queue/try-runtime", "pallet-migrations/try-runtime", "pallet-mmr/try-runtime", diff --git a/polkadot/runtime/test-runtime/Cargo.toml b/polkadot/runtime/test-runtime/Cargo.toml index f35bb53ac904..cd5507decd5d 100644 --- a/polkadot/runtime/test-runtime/Cargo.toml +++ b/polkadot/runtime/test-runtime/Cargo.toml @@ -68,7 +68,6 @@ hex-literal = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } -tiny-keccak = { features = ["keccak"], workspace = true } [build-dependencies] substrate-wasm-builder = { workspace = true, default-features = true } diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index e945e64e7fc0..3317484419a9 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -60,10 +60,8 @@ pallet-bags-list = { workspace = true } pallet-balances = { workspace = true } pallet-beefy = { workspace = true } pallet-beefy-mmr = { workspace = true } -pallet-collective = { workspace = true } pallet-conviction-voting = { workspace = true } pallet-delegated-staking = { workspace = true } -pallet-democracy = { workspace = true } pallet-election-provider-multi-phase = { workspace = true } pallet-elections-phragmen = { workspace = true } pallet-fast-unstake = { workspace = true } @@ -159,10 +157,8 @@ std = [ "pallet-balances/std", "pallet-beefy-mmr/std", "pallet-beefy/std", - "pallet-collective/std", "pallet-conviction-voting/std", "pallet-delegated-staking/std", - "pallet-democracy/std", "pallet-election-provider-multi-phase/std", "pallet-election-provider-support-benchmarking?/std", "pallet-elections-phragmen/std", @@ -250,10 +246,8 @@ runtime-benchmarks = [ "pallet-bags-list/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-beefy-mmr/runtime-benchmarks", - "pallet-collective/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", "pallet-delegated-staking/runtime-benchmarks", - "pallet-democracy/runtime-benchmarks", "pallet-election-provider-multi-phase/runtime-benchmarks", "pallet-election-provider-support-benchmarking/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", @@ -315,10 +309,8 @@ try-runtime = [ "pallet-balances/try-runtime", "pallet-beefy-mmr/try-runtime", "pallet-beefy/try-runtime", - "pallet-collective/try-runtime", "pallet-conviction-voting/try-runtime", "pallet-delegated-staking/try-runtime", - "pallet-democracy/try-runtime", "pallet-election-provider-multi-phase/try-runtime", "pallet-elections-phragmen/try-runtime", "pallet-fast-unstake/try-runtime", diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml index f75c984c068e..5169f586d723 100644 --- a/polkadot/xcm/xcm-builder/Cargo.toml +++ b/polkadot/xcm/xcm-builder/Cargo.toml @@ -31,7 +31,6 @@ xcm-executor = { workspace = true } polkadot-parachain-primitives = { workspace = true } [dev-dependencies] -assert_matches = { workspace = true } pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-salary = { workspace = true, default-features = true } diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml index 9e063ee3cde0..7b355074823c 100644 --- a/substrate/bin/node/cli/Cargo.toml +++ b/substrate/bin/node/cli/Cargo.toml @@ -144,7 +144,6 @@ assert_cmd = { workspace = true } criterion = { features = ["async_tokio"], workspace = true, default-features = true } futures = { workspace = true } nix = { features = ["signal"], workspace = true } -platforms = { workspace = true } pretty_assertions.workspace = true regex = { workspace = true } scale-info = { features = ["derive", "serde"], workspace = true, default-features = true } diff --git a/substrate/client/api/Cargo.toml b/substrate/client/api/Cargo.toml index fe961b4690fc..dede50fc01e8 100644 --- a/substrate/client/api/Cargo.toml +++ b/substrate/client/api/Cargo.toml @@ -41,6 +41,5 @@ sp-storage = { workspace = true, default-features = true } sp-trie = { workspace = true, default-features = true } [dev-dependencies] -sp-test-primitives = { workspace = true } substrate-test-runtime = { workspace = true } thiserror = { workspace = true } diff --git a/substrate/client/db/Cargo.toml b/substrate/client/db/Cargo.toml index 7e02558e007c..9268ccf8a064 100644 --- a/substrate/client/db/Cargo.toml +++ b/substrate/client/db/Cargo.toml @@ -43,7 +43,6 @@ array-bytes = { workspace = true, default-features = true } criterion = { workspace = true, default-features = true } kitchensink-runtime = { workspace = true } kvdb-rocksdb = { workspace = true } -quickcheck = { workspace = true } rand = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-runtime-client = { workspace = true } diff --git a/substrate/client/network/common/Cargo.toml b/substrate/client/network/common/Cargo.toml index cd1bc1cfe8eb..30407423da29 100644 --- a/substrate/client/network/common/Cargo.toml +++ b/substrate/client/network/common/Cargo.toml @@ -19,17 +19,11 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = { workspace = true } [dependencies] -async-trait = { workspace = true } bitflags = { workspace = true } codec = { features = [ "derive", ], workspace = true, default-features = true } futures = { workspace = true } -libp2p-identity = { features = ["peerid"], workspace = true } -sc-consensus = { workspace = true, default-features = true } -sc-network-types = { workspace = true, default-features = true } -sp-consensus = { workspace = true, default-features = true } -sp-consensus-grandpa = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } [dev-dependencies] diff --git a/substrate/client/telemetry/Cargo.toml b/substrate/client/telemetry/Cargo.toml index 4a41a6b6deca..1ebff618e0ce 100644 --- a/substrate/client/telemetry/Cargo.toml +++ b/substrate/client/telemetry/Cargo.toml @@ -24,7 +24,6 @@ log = { workspace = true, default-features = true } parking_lot = { workspace = true, default-features = true } pin-project = { workspace = true } rand = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } sc-utils = { workspace = true, default-features = true } serde = { features = ["derive"], workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } diff --git a/substrate/frame/contracts/Cargo.toml b/substrate/frame/contracts/Cargo.toml index 5784e6dd1553..88404803fe0f 100644 --- a/substrate/frame/contracts/Cargo.toml +++ b/substrate/frame/contracts/Cargo.toml @@ -65,10 +65,8 @@ wat = { workspace = true } xcm-builder = { workspace = true, default-features = true } # Substrate Dependencies -pallet-assets = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } pallet-insecure-randomness-collective-flip = { workspace = true, default-features = true } -pallet-message-queue = { workspace = true, default-features = true } pallet-proxy = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } pallet-utility = { workspace = true, default-features = true } @@ -106,9 +104,7 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-assets/runtime-benchmarks", "pallet-balances/runtime-benchmarks", - "pallet-message-queue/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-utility/runtime-benchmarks", @@ -122,10 +118,8 @@ runtime-benchmarks = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-assets/try-runtime", "pallet-balances/try-runtime", "pallet-insecure-randomness-collective-flip/try-runtime", - "pallet-message-queue/try-runtime", "pallet-proxy/try-runtime", "pallet-timestamp/try-runtime", "pallet-utility/try-runtime", diff --git a/substrate/frame/contracts/mock-network/Cargo.toml b/substrate/frame/contracts/mock-network/Cargo.toml index a7423b33abc1..84aa95694b50 100644 --- a/substrate/frame/contracts/mock-network/Cargo.toml +++ b/substrate/frame/contracts/mock-network/Cargo.toml @@ -21,11 +21,8 @@ pallet-balances = { workspace = true, default-features = true } pallet-contracts = { workspace = true, default-features = true } pallet-contracts-proc-macro = { workspace = true, default-features = true } pallet-contracts-uapi = { workspace = true } -pallet-insecure-randomness-collective-flip = { workspace = true, default-features = true } pallet-message-queue = { workspace = true, default-features = true } -pallet-proxy = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } -pallet-utility = { workspace = true, default-features = true } pallet-xcm = { workspace = true } polkadot-parachain-primitives = { workspace = true, default-features = true } polkadot-primitives = { workspace = true, default-features = true } @@ -43,9 +40,7 @@ xcm-executor = { workspace = true } xcm-simulator = { workspace = true, default-features = true } [dev-dependencies] -assert_matches = { workspace = true } pallet-contracts-fixtures = { workspace = true } -pretty_assertions = { workspace = true } [features] default = ["std"] @@ -55,10 +50,7 @@ std = [ "frame-system/std", "pallet-balances/std", "pallet-contracts/std", - "pallet-insecure-randomness-collective-flip/std", - "pallet-proxy/std", "pallet-timestamp/std", - "pallet-utility/std", "pallet-xcm/std", "scale-info/std", "sp-api/std", @@ -77,9 +69,7 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-contracts/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", - "pallet-proxy/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", - "pallet-utility/runtime-benchmarks", "pallet-xcm/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", diff --git a/substrate/frame/conviction-voting/Cargo.toml b/substrate/frame/conviction-voting/Cargo.toml index 2d23f493ea01..e2d483609769 100644 --- a/substrate/frame/conviction-voting/Cargo.toml +++ b/substrate/frame/conviction-voting/Cargo.toml @@ -31,7 +31,6 @@ sp-runtime = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, default-features = true } -pallet-scheduler = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } [features] @@ -42,7 +41,6 @@ std = [ "frame-support/std", "frame-system/std", "pallet-balances/std", - "pallet-scheduler/std", "scale-info/std", "serde", "sp-core/std", @@ -54,13 +52,11 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", - "pallet-scheduler/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-balances/try-runtime", - "pallet-scheduler/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/frame/delegated-staking/Cargo.toml b/substrate/frame/delegated-staking/Cargo.toml index 576276dced52..3a2498fb9912 100644 --- a/substrate/frame/delegated-staking/Cargo.toml +++ b/substrate/frame/delegated-staking/Cargo.toml @@ -31,7 +31,6 @@ pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/fast-unstake/Cargo.toml b/substrate/frame/fast-unstake/Cargo.toml index 98a9655074e7..209406dc3f99 100644 --- a/substrate/frame/fast-unstake/Cargo.toml +++ b/substrate/frame/fast-unstake/Cargo.toml @@ -38,7 +38,6 @@ pallet-staking-reward-curve = { workspace = true, default-features = true } pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true } sp-tracing = { workspace = true, default-features = true } -substrate-test-utils = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/glutton/Cargo.toml b/substrate/frame/glutton/Cargo.toml index 317a9ea8b760..7f7b24c12117 100644 --- a/substrate/frame/glutton/Cargo.toml +++ b/substrate/frame/glutton/Cargo.toml @@ -28,9 +28,6 @@ sp-inherents = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } -[dev-dependencies] -pallet-balances = { workspace = true, default-features = true } - [features] default = ["std"] std = [ @@ -40,7 +37,6 @@ std = [ "frame-support/std", "frame-system/std", "log/std", - "pallet-balances/std", "scale-info/std", "sp-core/std", "sp-inherents/std", @@ -50,7 +46,6 @@ std = [ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-balances/try-runtime", "sp-runtime/try-runtime", ] @@ -58,6 +53,5 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] diff --git a/substrate/frame/indices/Cargo.toml b/substrate/frame/indices/Cargo.toml index a0030b5b0edf..fdc1753e44fc 100644 --- a/substrate/frame/indices/Cargo.toml +++ b/substrate/frame/indices/Cargo.toml @@ -23,7 +23,6 @@ frame-system = { workspace = true } scale-info = { features = ["derive"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } -sp-keyring = { optional = true, workspace = true } sp-runtime = { workspace = true } [dev-dependencies] @@ -40,8 +39,6 @@ std = [ "scale-info/std", "sp-core/std", "sp-io/std", - "sp-keyring", - "sp-keyring?/std", "sp-runtime/std", ] runtime-benchmarks = [ diff --git a/substrate/frame/nfts/runtime-api/Cargo.toml b/substrate/frame/nfts/runtime-api/Cargo.toml index 4d004875468d..36f85fbf6112 100644 --- a/substrate/frame/nfts/runtime-api/Cargo.toml +++ b/substrate/frame/nfts/runtime-api/Cargo.toml @@ -17,9 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { features = ["derive"], workspace = true } -pallet-nfts = { workspace = true } sp-api = { workspace = true } [features] default = ["std"] -std = ["codec/std", "pallet-nfts/std", "sp-api/std"] +std = ["codec/std", "sp-api/std"] diff --git a/substrate/frame/offences/Cargo.toml b/substrate/frame/offences/Cargo.toml index 4dd9d7f10c9f..221a4918a511 100644 --- a/substrate/frame/offences/Cargo.toml +++ b/substrate/frame/offences/Cargo.toml @@ -20,7 +20,6 @@ codec = { features = ["derive"], workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } -pallet-balances = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, workspace = true, default-features = true } sp-runtime = { workspace = true } @@ -37,7 +36,6 @@ std = [ "frame-support/std", "frame-system/std", "log/std", - "pallet-balances/std", "scale-info/std", "serde", "sp-core/std", @@ -48,13 +46,11 @@ std = [ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-balances/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/frame/paged-list/Cargo.toml b/substrate/frame/paged-list/Cargo.toml index da029bdd7423..07755c351e28 100644 --- a/substrate/frame/paged-list/Cargo.toml +++ b/substrate/frame/paged-list/Cargo.toml @@ -19,7 +19,6 @@ codec = { features = ["derive"], workspace = true } docify = { workspace = true } scale-info = { features = ["derive"], workspace = true } -frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } @@ -33,7 +32,6 @@ default = ["std"] std = [ "codec/std", - "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "scale-info/std", @@ -44,7 +42,6 @@ std = [ ] runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", diff --git a/substrate/frame/verify-signature/Cargo.toml b/substrate/frame/verify-signature/Cargo.toml index 37cc6c0b3065..453424bbec7a 100644 --- a/substrate/frame/verify-signature/Cargo.toml +++ b/substrate/frame/verify-signature/Cargo.toml @@ -27,10 +27,6 @@ sp-runtime = { workspace = true } sp-weights = { features = ["serde"], workspace = true } [dev-dependencies] -pallet-balances = { workspace = true, default-features = true } -pallet-collective = { workspace = true, default-features = true } -pallet-root-testing = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } [features] @@ -40,10 +36,6 @@ std = [ "frame-benchmarking?/std", "frame-support/std", "frame-system/std", - "pallet-balances/std", - "pallet-collective/std", - "pallet-root-testing/std", - "pallet-timestamp/std", "scale-info/std", "sp-core/std", "sp-io/std", @@ -54,17 +46,10 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-collective/runtime-benchmarks", - "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", - "pallet-balances/try-runtime", - "pallet-collective/try-runtime", - "pallet-root-testing/try-runtime", - "pallet-timestamp/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/test-utils/Cargo.toml b/substrate/test-utils/Cargo.toml index 87c9cb731e3a..75eab46cb217 100644 --- a/substrate/test-utils/Cargo.toml +++ b/substrate/test-utils/Cargo.toml @@ -14,11 +14,3 @@ workspace = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -futures = { workspace = true } -tokio = { features = ["macros", "time"], workspace = true, default-features = true } - -[dev-dependencies] -sc-service = { workspace = true, default-features = true } -trybuild = { features = ["diff"], workspace = true } diff --git a/substrate/test-utils/client/Cargo.toml b/substrate/test-utils/client/Cargo.toml index e7ab4c8c8367..b0709f4e244d 100644 --- a/substrate/test-utils/client/Cargo.toml +++ b/substrate/test-utils/client/Cargo.toml @@ -26,7 +26,6 @@ sc-client-db = { features = [ ], workspace = true } sc-consensus = { workspace = true, default-features = true } sc-executor = { workspace = true, default-features = true } -sc-offchain = { workspace = true, default-features = true } sc-service = { workspace = true } serde = { workspace = true, default-features = true } serde_json = { workspace = true, default-features = true } From ee30ec723ee22e247014217e48513a2e7690c953 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Mon, 27 Jan 2025 14:29:49 +0200 Subject: [PATCH 331/340] [sync] Let new subscribers know about already connected peers (backward-compatible) (#7344) Revert https://github.com/paritytech/polkadot-sdk/pull/7011 and replace it with a backward-compatible solution suitable for backporting to a release branch. ### Review notes It's easier to review this PR per commit: the first commit is just a revert, so it's enough to review only the second one, which is almost a one-liner. --------- Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- prdoc/pr_7344.prdoc | 14 +++++++++++ substrate/client/network-gossip/src/bridge.rs | 10 ++++---- substrate/client/network-gossip/src/lib.rs | 13 ++++------- substrate/client/network/statement/src/lib.rs | 23 +++++-------------- substrate/client/network/sync/src/engine.rs | 6 +++-- substrate/client/network/sync/src/types.rs | 4 ---- .../client/network/transactions/src/lib.rs | 23 +++++-------------- 7 files changed, 39 insertions(+), 54 deletions(-) create mode 100644 prdoc/pr_7344.prdoc diff --git a/prdoc/pr_7344.prdoc b/prdoc/pr_7344.prdoc new file mode 100644 index 000000000000..a3ffa32f125c --- /dev/null +++ b/prdoc/pr_7344.prdoc @@ -0,0 +1,14 @@ +title: '[sync] Let new subscribers know about already connected peers (backward-compatible)' +doc: +- audience: Node Dev + description: Revert https://github.com/paritytech/polkadot-sdk/pull/7011 and replace + it with a backward-compatible solution suitable for backporting to a release branch. +crates: +- name: sc-network-gossip + bump: patch +- name: sc-network-statement + bump: patch +- name: sc-network-sync + bump: patch +- name: sc-network-transactions + bump: patch diff --git a/substrate/client/network-gossip/src/bridge.rs b/substrate/client/network-gossip/src/bridge.rs index bff258a9a011..2daf1e49ee4b 100644 --- a/substrate/client/network-gossip/src/bridge.rs +++ b/substrate/client/network-gossip/src/bridge.rs @@ -254,12 +254,10 @@ impl Future for GossipEngine { match sync_event_stream { Poll::Ready(Some(event)) => match event { - SyncEvent::InitialPeers(peer_ids) => - this.network.add_set_reserved(peer_ids, this.protocol.clone()), - SyncEvent::PeerConnected(peer_id) => - this.network.add_set_reserved(vec![peer_id], this.protocol.clone()), - SyncEvent::PeerDisconnected(peer_id) => - this.network.remove_set_reserved(peer_id, this.protocol.clone()), + SyncEvent::PeerConnected(remote) => + this.network.add_set_reserved(remote, this.protocol.clone()), + SyncEvent::PeerDisconnected(remote) => + this.network.remove_set_reserved(remote, this.protocol.clone()), }, // The sync event stream closed. Do the same for [`GossipValidator`]. Poll::Ready(None) => { diff --git a/substrate/client/network-gossip/src/lib.rs b/substrate/client/network-gossip/src/lib.rs index 2ec573bf9e3e..20d9922200c2 100644 --- a/substrate/client/network-gossip/src/lib.rs +++ b/substrate/client/network-gossip/src/lib.rs @@ -82,18 +82,15 @@ mod validator; /// Abstraction over a network. pub trait Network: NetworkPeers + NetworkEventStream { - fn add_set_reserved(&self, peer_ids: Vec, protocol: ProtocolName) { - let addrs = peer_ids - .into_iter() - .map(|peer_id| Multiaddr::empty().with(Protocol::P2p(peer_id.into()))) - .collect(); - let result = self.add_peers_to_reserved_set(protocol, addrs); + fn add_set_reserved(&self, who: PeerId, protocol: ProtocolName) { + let addr = Multiaddr::empty().with(Protocol::P2p(*who.as_ref())); + let result = self.add_peers_to_reserved_set(protocol, iter::once(addr).collect()); if let Err(err) = result { log::error!(target: "gossip", "add_set_reserved failed: {}", err); } } - fn remove_set_reserved(&self, peer_id: PeerId, protocol: ProtocolName) { - let result = self.remove_peers_from_reserved_set(protocol, iter::once(peer_id).collect()); + fn remove_set_reserved(&self, who: PeerId, protocol: ProtocolName) { + let result = self.remove_peers_from_reserved_set(protocol, iter::once(who).collect()); if let Err(err) = result { log::error!(target: "gossip", "remove_set_reserved failed: {}", err); } diff --git a/substrate/client/network/statement/src/lib.rs b/substrate/client/network/statement/src/lib.rs index 586a15cadd68..df93788696e3 100644 --- a/substrate/client/network/statement/src/lib.rs +++ b/substrate/client/network/statement/src/lib.rs @@ -33,8 +33,7 @@ use futures::{channel::oneshot, prelude::*, stream::FuturesUnordered, FutureExt} use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; use sc_network::{ config::{NonReservedPeerMode, SetConfig}, - error, - multiaddr::{Multiaddr, Protocol}, + error, multiaddr, peer_store::PeerStoreProvider, service::{ traits::{NotificationEvent, NotificationService, ValidationResult}, @@ -297,19 +296,9 @@ where fn handle_sync_event(&mut self, event: SyncEvent) { match event { - SyncEvent::InitialPeers(peer_ids) => { - let addrs = peer_ids - .into_iter() - .map(|peer_id| Multiaddr::empty().with(Protocol::P2p(peer_id.into()))) - .collect(); - let result = - self.network.add_peers_to_reserved_set(self.protocol_name.clone(), addrs); - if let Err(err) = result { - log::error!(target: LOG_TARGET, "Add reserved peers failed: {}", err); - } - }, - SyncEvent::PeerConnected(peer_id) => { - let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into())); + SyncEvent::PeerConnected(remote) => { + let addr = iter::once(multiaddr::Protocol::P2p(remote.into())) + .collect::(); let result = self.network.add_peers_to_reserved_set( self.protocol_name.clone(), iter::once(addr).collect(), @@ -318,10 +307,10 @@ where log::error!(target: LOG_TARGET, "Add reserved peer failed: {}", err); } }, - SyncEvent::PeerDisconnected(peer_id) => { + SyncEvent::PeerDisconnected(remote) => { let result = self.network.remove_peers_from_reserved_set( self.protocol_name.clone(), - iter::once(peer_id).collect(), + iter::once(remote).collect(), ); if let Err(err) = result { log::error!(target: LOG_TARGET, "Failed to remove reserved peer: {err}"); diff --git a/substrate/client/network/sync/src/engine.rs b/substrate/client/network/sync/src/engine.rs index 4003361525e1..3b9c5f38329b 100644 --- a/substrate/client/network/sync/src/engine.rs +++ b/substrate/client/network/sync/src/engine.rs @@ -657,8 +657,10 @@ where self.strategy.set_sync_fork_request(peers, &hash, number); }, ToServiceCommand::EventStream(tx) => { - let _ = tx - .unbounded_send(SyncEvent::InitialPeers(self.peers.keys().cloned().collect())); + // Let a new subscriber know about already connected peers. + for peer_id in self.peers.keys() { + let _ = tx.unbounded_send(SyncEvent::PeerConnected(*peer_id)); + } self.event_streams.push(tx); }, ToServiceCommand::RequestJustification(hash, number) => diff --git a/substrate/client/network/sync/src/types.rs b/substrate/client/network/sync/src/types.rs index a72a2f7c1ffe..5745a34378df 100644 --- a/substrate/client/network/sync/src/types.rs +++ b/substrate/client/network/sync/src/types.rs @@ -127,10 +127,6 @@ where /// Syncing-related events that other protocols can subscribe to. pub enum SyncEvent { - /// All connected peers that the syncing implementation is tracking. - /// Always sent as the first message to the stream. - InitialPeers(Vec), - /// Peer that the syncing implementation is tracking connected. PeerConnected(PeerId), diff --git a/substrate/client/network/transactions/src/lib.rs b/substrate/client/network/transactions/src/lib.rs index 49f429a04ee2..44fa702ef6d4 100644 --- a/substrate/client/network/transactions/src/lib.rs +++ b/substrate/client/network/transactions/src/lib.rs @@ -35,8 +35,7 @@ use log::{debug, trace, warn}; use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; use sc_network::{ config::{NonReservedPeerMode, ProtocolId, SetConfig}, - error, - multiaddr::{Multiaddr, Protocol}, + error, multiaddr, peer_store::PeerStoreProvider, service::{ traits::{NotificationEvent, NotificationService, ValidationResult}, @@ -378,19 +377,9 @@ where fn handle_sync_event(&mut self, event: SyncEvent) { match event { - SyncEvent::InitialPeers(peer_ids) => { - let addrs = peer_ids - .into_iter() - .map(|peer_id| Multiaddr::empty().with(Protocol::P2p(peer_id.into()))) - .collect(); - let result = - self.network.add_peers_to_reserved_set(self.protocol_name.clone(), addrs); - if let Err(err) = result { - log::error!(target: LOG_TARGET, "Add reserved peers failed: {}", err); - } - }, - SyncEvent::PeerConnected(peer_id) => { - let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into())); + SyncEvent::PeerConnected(remote) => { + let addr = iter::once(multiaddr::Protocol::P2p(remote.into())) + .collect::(); let result = self.network.add_peers_to_reserved_set( self.protocol_name.clone(), iter::once(addr).collect(), @@ -399,10 +388,10 @@ where log::error!(target: LOG_TARGET, "Add reserved peer failed: {}", err); } }, - SyncEvent::PeerDisconnected(peer_id) => { + SyncEvent::PeerDisconnected(remote) => { let result = self.network.remove_peers_from_reserved_set( self.protocol_name.clone(), - iter::once(peer_id).collect(), + iter::once(remote).collect(), ); if let Err(err) = result { log::error!(target: LOG_TARGET, "Remove reserved peer failed: {}", err); From b2004ed89dd58e7f30be7312d32fcaaf98d6384f Mon Sep 17 00:00:00 2001 From: dharjeezy Date: Mon, 27 Jan 2025 14:01:03 +0100 Subject: [PATCH 332/340] `Arc` definition in `TransactionPool` (#7042) closes #5978 --------- Co-authored-by: command-bot <> Co-authored-by: Michal Kucharczyk <1728078+michalkucharczyk@users.noreply.github.com> --- prdoc/pr_7042.prdoc | 9 +++++++++ substrate/client/network/transactions/src/config.rs | 10 +++++----- substrate/client/network/transactions/src/lib.rs | 2 +- substrate/client/service/src/lib.rs | 12 ++++++------ 4 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 prdoc/pr_7042.prdoc diff --git a/prdoc/pr_7042.prdoc b/prdoc/pr_7042.prdoc new file mode 100644 index 000000000000..00fb34c6af49 --- /dev/null +++ b/prdoc/pr_7042.prdoc @@ -0,0 +1,9 @@ +title: `networking::TransactionPool` should accept `Arc` +doc: +- audience: Node Dev + description: The `sc_network_transactions::config::TransactionPool` trait now returns an `Arc` for transactions. +crates: +- name: sc-network-transactions + bump: minor +- name: sc-service + bump: minor \ No newline at end of file diff --git a/substrate/client/network/transactions/src/config.rs b/substrate/client/network/transactions/src/config.rs index 239b76b51485..42a335d7041a 100644 --- a/substrate/client/network/transactions/src/config.rs +++ b/substrate/client/network/transactions/src/config.rs @@ -22,7 +22,7 @@ use futures::prelude::*; use sc_network::MAX_RESPONSE_SIZE; use sc_network_common::ExHashT; use sp_runtime::traits::Block as BlockT; -use std::{collections::HashMap, future::Future, pin::Pin, time}; +use std::{collections::HashMap, future::Future, pin::Pin, sync::Arc, time}; /// Interval at which we propagate transactions; pub(crate) const PROPAGATE_TIMEOUT: time::Duration = time::Duration::from_millis(2900); @@ -57,7 +57,7 @@ pub type TransactionImportFuture = Pin: Send + Sync { /// Get transactions from the pool that are ready to be propagated. - fn transactions(&self) -> Vec<(H, B::Extrinsic)>; + fn transactions(&self) -> Vec<(H, Arc)>; /// Get hash of transaction. fn hash_of(&self, transaction: &B::Extrinsic) -> H; /// Import a transaction into the pool. @@ -67,7 +67,7 @@ pub trait TransactionPool: Send + Sync { /// Notify the pool about transactions broadcast. fn on_broadcasted(&self, propagations: HashMap>); /// Get transaction by hash. - fn transaction(&self, hash: &H) -> Option; + fn transaction(&self, hash: &H) -> Option>; } /// Dummy implementation of the [`TransactionPool`] trait for a transaction pool that is always @@ -79,7 +79,7 @@ pub trait TransactionPool: Send + Sync { pub struct EmptyTransactionPool; impl TransactionPool for EmptyTransactionPool { - fn transactions(&self) -> Vec<(H, B::Extrinsic)> { + fn transactions(&self) -> Vec<(H, Arc)> { Vec::new() } @@ -93,7 +93,7 @@ impl TransactionPool for EmptyTransaction fn on_broadcasted(&self, _: HashMap>) {} - fn transaction(&self, _h: &H) -> Option { + fn transaction(&self, _h: &H) -> Option> { None } } diff --git a/substrate/client/network/transactions/src/lib.rs b/substrate/client/network/transactions/src/lib.rs index 44fa702ef6d4..a6a955207945 100644 --- a/substrate/client/network/transactions/src/lib.rs +++ b/substrate/client/network/transactions/src/lib.rs @@ -469,7 +469,7 @@ where fn do_propagate_transactions( &mut self, - transactions: &[(H, B::Extrinsic)], + transactions: &[(H, Arc)], ) -> HashMap> { let mut propagated_to = HashMap::<_, Vec<_>>::new(); let mut propagated_transactions = 0; diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index 2a3144a33e1a..322726a1eff4 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -474,7 +474,7 @@ impl TransactionPoolAdapter { /// Get transactions for propagation. /// /// Function extracted to simplify the test and prevent creating `ServiceFactory`. -fn transactions_to_propagate(pool: &Pool) -> Vec<(H, B::Extrinsic)> +fn transactions_to_propagate(pool: &Pool) -> Vec<(H, Arc)> where Pool: TransactionPool, B: BlockT, @@ -485,7 +485,7 @@ where .filter(|t| t.is_propagable()) .map(|t| { let hash = t.hash().clone(); - let ex: B::Extrinsic = (**t.data()).clone(); + let ex = t.data().clone(); (hash, ex) }) .collect() @@ -506,7 +506,7 @@ where H: std::hash::Hash + Eq + sp_runtime::traits::Member + sp_runtime::traits::MaybeSerialize, E: 'static + IntoPoolError + From, { - fn transactions(&self) -> Vec<(H, B::Extrinsic)> { + fn transactions(&self) -> Vec<(H, Arc)> { transactions_to_propagate(&*self.pool) } @@ -559,10 +559,10 @@ where self.pool.on_broadcasted(propagations) } - fn transaction(&self, hash: &H) -> Option { + fn transaction(&self, hash: &H) -> Option> { self.pool.ready_transaction(hash).and_then( // Only propagable transactions should be resolved for network service. - |tx| if tx.is_propagable() { Some((**tx.data()).clone()) } else { None }, + |tx| tx.is_propagable().then(|| tx.data().clone()), ) } } @@ -614,6 +614,6 @@ mod tests { // then assert_eq!(transactions.len(), 1); - assert!(TransferData::try_from(&transactions[0].1).is_ok()); + assert!(TransferData::try_from(&*transactions[0].1).is_ok()); } } From d85147d013e112feae5000816932d0543aee95da Mon Sep 17 00:00:00 2001 From: christopher k <30932534+EleisonC@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:01:49 +0300 Subject: [PATCH 333/340] Add development chain-spec file for minimal/parachain templates for Omni Node compatibility (#6529) # Description This PR adds development chain specs for the minimal and parachain templates. [#6334](https://github.com/paritytech/polkadot-sdk/issues/6334) ## Integration This PR adds development chain specs for the minimal and para chain template runtimes, ensuring synchronization with runtime code. It updates zombienet-omni-node.toml, zombinet.toml files to include valid chain spec paths, simplifying configuration for zombienet in the parachain and minimal template. ## Review Notes 1. Overview of Changes: - Added development chain specs for use in the minimal and parachain template. - Updated zombienet-omni-node.toml and zombinet.toml files in the minimal and parachain templates to include paths to the new dev chain specs. 2. Integration Guidance: **NB: Follow the templates' READMEs from the polkadot-SDK master branch. Please build the binaries and runtimes based on the polkadot-SDK master branch.** - Ensure you have set up your runtimes `parachain-template-runtime` and `minimal-template-runtime` - Ensure you have installed the nodes required ie `parachain-template-node` and `minimal-template-node` - Set up [Zombinet](https://paritytech.github.io/zombienet/intro.html) - For running the parachains, you will need to install the polkadot `cargo install --path polkadot` remember from the polkadot-SDK master branch. - Inside the template folders minimal or parachain, run the command to start with `Zombienet with Omni Node`, `Zombienet with minimal-template-node` or `Zombienet with parachain-template-node` *Include your leftover TODOs, if any, here.* * [ ] Test the syncing of chain specs with runtime's code. --------- Signed-off-by: EleisonC Co-authored-by: Iulian Barbu <14218860+iulianbarbu@users.noreply.github.com> Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> --- .github/workflows/misc-sync-templates.yml | 65 +++++++++++ templates/minimal/README.md | 11 +- templates/minimal/dev_chain_spec.json | 85 +++++++++++++++ templates/minimal/zombienet-omni-node.toml | 4 +- templates/parachain/README.docify.md | 9 +- templates/parachain/README.md | 9 +- templates/parachain/dev_chain_spec.json | 108 +++++++++++++++++++ templates/parachain/zombienet-omni-node.toml | 2 +- 8 files changed, 284 insertions(+), 9 deletions(-) create mode 100644 templates/minimal/dev_chain_spec.json create mode 100644 templates/parachain/dev_chain_spec.json diff --git a/.github/workflows/misc-sync-templates.yml b/.github/workflows/misc-sync-templates.yml index ce01f010aa71..26f89ed1cc97 100644 --- a/.github/workflows/misc-sync-templates.yml +++ b/.github/workflows/misc-sync-templates.yml @@ -31,7 +31,66 @@ on: default: "" jobs: + prepare-chain-spec-artifacts: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - template: minimal + package_name: 'minimal-template-runtime' + runtime_path: './templates/minimal/runtime' + runtime_wasm_path: minimal-template-runtime/minimal_template_runtime.compact.compressed.wasm + relay_chain: 'dev' + - template: parachain + package_name: 'parachain-template-runtime' + runtime_path: './templates/parachain/runtime' + runtime_wasm_path: parachain-template-runtime/parachain_template_runtime.compact.compressed.wasm + relay_chain: 'rococo-local' + steps: + - uses: actions/checkout@v4 + with: + ref: "${{ github.event.inputs.stable_release_branch }}" + + - name: Setup build environment + run: | + sudo apt-get update + sudo apt-get install -y protobuf-compiler + cargo install --git https://github.com/chevdor/srtool-cli --locked + cargo install --path substrate/bin/utils/chain-spec-builder --locked + srtool pull + + - name: Build runtime and generate chain spec + run: | + # Prepare directories + sudo mkdir -p ${{ matrix.template.runtime_path }}/target + sudo chmod -R 777 ${{ matrix.template.runtime_path }}/target + + # Build runtime + srtool build --package ${{ matrix.template.package_name }} --runtime-dir ${{ matrix.template.runtime_path }} --root + + # Generate chain spec + # Note that para-id is set to 1000 for both minimal/parachain templates. + # `parachain-runtime` is hardcoded to use this parachain id. + # `minimal` template isn't using it, but when started with Omni Node, this para id is required (any number can do it, so setting it to 1000 for convenience). + chain-spec-builder -c dev_chain_spec.json create \ + --relay-chain "${{ matrix.template.relay_chain }}" \ + --para-id 1000 \ + --runtime "${{ matrix.template.runtime_path }}/target/srtool/release/wbuild/${{ matrix.template.runtime_wasm_path }}" \ + named-preset development + + - name: Prepare upload directory + run: | + mkdir -p artifacts-${{ matrix.template }} + cp dev_chain_spec.json artifacts-${{ matrix.template }}/dev_chain_spec.json + + - name: Upload template directory + uses: actions/upload-artifact@v4 + with: + name: artifacts-${{ matrix.template }} + path: artifacts-${{ matrix.template }}/dev_chain_spec.json + sync-templates: + needs: prepare-chain-spec-artifacts runs-on: ubuntu-latest environment: master strategy: @@ -52,6 +111,12 @@ jobs: with: path: polkadot-sdk ref: "${{ github.event.inputs.stable_release_branch }}" + - name: Download template artifacts + uses: actions/download-artifact@v4 + with: + name: artifacts-${{ matrix.template }} + path: templates/${{ matrix.template }}/ + if: matrix.template != 'solochain' - name: Generate a token for the template repository id: app_token uses: actions/create-github-app-token@v1.9.3 diff --git a/templates/minimal/README.md b/templates/minimal/README.md index 22f396c243ef..4cf3fd2a44bb 100644 --- a/templates/minimal/README.md +++ b/templates/minimal/README.md @@ -42,6 +42,7 @@ such as a [Balances pallet](https://paritytech.github.io/polkadot-sdk/master/pal - 👤 The template has no consensus configured - it is best for experimenting with a single node network. + ## Template Structure A Polkadot SDK based project such as this one consists of: @@ -61,7 +62,7 @@ compiled unless building the entire workspace). - 🛠️ Depending on your operating system and Rust version, there might be additional packages required to compile this template - please take note of the Rust compiler output. -Fetch minimal template code: +Fetch minimal template code. ```sh git clone https://github.com/paritytech/polkadot-sdk-minimal-template.git minimal-template @@ -147,11 +148,13 @@ docker run --rm polkadot-sdk-minimal-template We can install `zombienet` as described [here](https://paritytech.github.io/zombienet/install.html#installation), and `zombienet-omni-node.toml` contains the network specification we want to start. + #### Update `zombienet-omni-node.toml` with a valid chain spec path -Before starting the network with zombienet we must update the network specification -with a valid chain spec path. If we need to generate one, we can look up at the previous -section for chain spec creation [here](#use-chain-spec-builder-to-generate-the-chain_specjson-file). +To simplify the process of starting the minimal template with ZombieNet and Omni Node, we've included a +pre-configured development chain spec (dev_chain_spec.json) in the minimal template. The zombienet-omni-node.toml +file in this template points to it, but you can update it to a new path for the chain spec generated on your machine. +To generate a chain spec refer to [staging-chain-spec-builder](https://crates.io/crates/staging-chain-spec-builder) Then make the changes in the network specification like so: diff --git a/templates/minimal/dev_chain_spec.json b/templates/minimal/dev_chain_spec.json new file mode 100644 index 000000000000..91d703c6fd5b --- /dev/null +++ b/templates/minimal/dev_chain_spec.json @@ -0,0 +1,85 @@ +{ + "bootNodes": [], + "chainType": "Live", + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "code": "0x52bc537646db8e0528b52ffd0058bc49057eb84632144d10784894742c8bde903e277d4efa9ca4a5f0e2c7b95fe173920d2da637ed48cf23dbde08721d93d1ee93927f0102b631505ddb8ea0b7b14774f612445415d300fa8db44608216493bdf7965b064c159913b313096873c0d17b6e346bb3af5a029e75557bc411556f2ba2ca5e9ad86212d0f07087ae3d5d198eaeed1243c2d34ca1707fc049eb76aaa5caaceb92189407aafbc3c594d60975bb7b688f583fb47b6c09d87695bbc6769f302219c8da27fe84dd441facc8c231b5d69a74ad3d6e2d9240ed9309ac29cddf13d175b79bb5737b8918ef3dab86bbefa39046d142880dbc14169b2d78a6a5f93f2e206435a7f7325d5bba6e1a9888023fdb164ce213c96adba2f975d3d0fcd27be662eef0c51170700433e2f0f1b1817787736ddcf4793fe0420726c8c1126ca26821c4c65d0a8bcd8ba205dbc44b59808d3b9c521660532aa309f712132e86efda683e7ca98c26e2212dd5b8187ea4a5329a8077b47417c38754a6eecf8f7bdd36160f27a082187008830fccb08537fffa41b6363d3060630937b88113de18421d6cd6e6bdcaf47d7c6cdc163c43849beb62a6f40ac1f37a1a3f1f6c1146bff4fe6c21a58f7022d3d3efed5688efb09baa759c3be4e907e5684775a6c7579eaeeeed136d12294e0e6df2f8fa682db53b9cf10fc962614a472a86847f112767e5e878d864b130a5e12bf78bb4c9cad1b08902a2fc34a46258c153c4179e50c4179e406b15eddee67e310c14f185275450bfc32986153c2b870df5d57155aab4183e4056b0832ea8f0105482b086cfa362e568317c8af8c2137ca202a2fc489fc5c294ca042b870d457d560e9b7dbdfa7d3701bb45d4fd49c14fefb416c38707198480051b7c7c6c965ac123a9cffed1ea1cad54b46b42640632f0e00f519660697e0df3700fe43886b55164fb8302321af6fe4861a35734467b8f4aba304e045691b586ec3704218cb4350e2c76eb806cb107f4f47bb4dd6ab3486bd966eb9ed6844002c3e97b8b677d6f87737404bec3699aa6f6699a265adf197e9ab1450869757f144705304218638c31c2181fdbe1c469f44a021492382f9cb57bc694e8699cdd577edbac31c172f1551e3e4e1cf978374747288ebcfbb6bbe2746fd3e2186fc568b9ab4ddf81dd6ebd69a6c6f3db1f7c23d2b6d12acf140aece9356cc1abcde9703274b3ba4b8a03cf9f627478b0da9b15febdcdd191f6f84871e0dbb72d9e4e748074fc0e6c7ecb10775d3ca3333514ee788933fd495adba7b7e6378c08b7346337fbe3e3e3d34c2bfc44a1c08eb7e0e5663bcff8c59278e7d668758714e7316c3c45ca18bd3f2780a36ba8df05d0fb738237bc432b4eb74c6acba4fa9a1fa7bbe499dad34ce50f086f410889b8d7309cadad76fd783b9cf36149b486af61c933a322ade121958d732501521d9ee6f6284ef684054076f56b2bf460737f309e3f625a84efeee21b3c8e8b973cc31dafaee11aee69adad61ad03322b24058da7444fa39249efef56e8003a3cfdfedebead1fed80b56c6defb32e9e191d7155aab43bce0ef707a054eddeae6820edda15d6f28cfb103277fe737fb7de6cddb0e7dc6bb8f562ee35bcc472df5fd35a98ba3fed7e82309cbb3bc9d138194713a58c8a44bb43aac56dcaa8da1d09b67127e8a2dd7b9595ff24cfe0f0db9769328e71fc47abec9ab5fbb26899f4fe98d45eb3febb5a95dd5afb8f1673d0349bfdb55811fea3f2e219befbc2388ab33fcc2c05abf7278b7592ed4f0acce8e6d35f98da7c9a6fd1b46be703321cf7edda7cb8f727056474eb76c68e349f5e5a9b4fbb5b8b1179df09e5f5fecd9bacf76ed69e76df3d627453f57bdd2ff4bb13997e5427d4fbba55babaf36bdc5cbe7696b2f6d6cb07d8374ce7edbe984ed68ebe341fce021cfd1e69560d1f1f9bfdb3428f67e2f758f4deda3d0274cbeab90e9abb422e5aadeb70c75bb4725b6fbf66e5e9ab35a83ee169ebefd5778fa00e5a21205aadbb63a7e6ae108cd6ebee35ced299aa54a9f2877ebf66ed29d2fabd86adb95d4d9c47a1b48ebfe18e77d89177a6b5a7f9dbd6dbacaddb1d56241e746b8e6ebac624aeef0a69af71357c4d9d56a5e541738425319da2b535f56ad6d6d5ad386beb78ec0f43f21ebf1891eb356c2d08b2fdc9e20dfd1e6fadc5d7624960b7687d6d3986221b74106dd8cdeb78cb5a0c446b4f837e4d28b2af5bb3b6b6be7bc445ebf59c237a578875eb35ce9ad6745f0c44755c63b3c6bb2f76e43dae1a3e26689eb1be7f3c537ddfb0245a95bee8ae906a519d56a5adc35b743708e5aa769f15023fcd1a7ba8164d60ddd16411fabfd3b3e0f17821fc2c7c1cf401b8678182769717f1753459f8a0a595bb1fcf22885bdf4b4793854c163deee37b174793050f5ae2f150e83e440fcdf37889072d8960eee3251ebf1e7a168e96a47c464bd927f792a3254951d05e9207e2a263df678e268b8a96aa33dff2414bf4931027e23cddc74b7c1fb404e1335aaa9ebde4e3d6b378ff414b2d2d5fa125778a96a84feba5798b969c7bc9fae82c59f68b96207c454b8e9680bc7ae69ebd743da325c8f2127ccc877829fb10b4d4d2f21db4b4e3d3742068a90588ef98767c8897a60f414b40500e889728188a02120b2db17cc78e8f5876eca017a2aa0e0490aab22a6aeef0c143e4b289a98a8793d60e189af062e943146239a7cc65d5b4c382170f1f3ce40c89604551cecaa61da22be3415d3e44134bae4213ee7070870f278908406671685a178068888a0822587c588e8a8979891f1343f1e0414fcae449570002f052f50004604e53e8f43157f5184822bd344f228224433bb2d307c087e801b8005ee27101c4c4bc643d6688980be0250a124184354f4f823b4e6222882022002f4d0f002d114184c85d00430c01e4a5eb40a8d3c700712042f010c0635ef2f1181209001f829648176288c7bc545d8897320a731616f6717a228000b9104000f1d1810871202f890e449e88077929f4204de6e5027829740134980b2180077989c78578493e86f4f242ff12f39797e65fda4bd65f3ec468742180a02520b404e4427c04c4472f5d1fb597e08738e9a5eca40600225e9a4e042d018088535a02c08100e2a497dce94bd483d052000270185a1ad1d2e82c2c7fa1a51f3f3ea2a597b3b07cf4d28e8f68e9c7e83f5eba68cdc6b4c35c00425c2b07424b3f68e9c781fcba4e5faa4e69694588d3972c4ac5c6b4af5c002f55b48a8d6917002d0580a2a0c5581f8296aaea2db4d4726b885b2f51d4098b962aeb2fb454fda27ebd3429131bd3fef2d20e6a6463da49b444c48e038096282dd10f310f434b42fc9496b60d51f4c046151bd4c105526ca6164ed04516f620071a64a1059b2ce66b7c50b931ed93964ecf83eec6b40741d7c5ec8100e2e32fb43472f90f5aea510211dcd0c61b3cf001169460e3a3a7062608430b9e00063dbc81031b1f2f89a8cbc6b4fba02517ba2ea6085a49bd2f8568b631ed3da874317b1e2f39ca6463da79d0d20e8a82669385e82db4143a0b45818c4d16ee2bb424023d444b3d3b10c30f62eca00e3bc0610e366ec540071174d0821ad4f1043dd864e11e7a29a34336a63d444b20ba2e6600b40ee97d69a2321bd38e511717b32f3155b231ed172d591405249b2ce22b5a42e20869c4210876e0021d3fb0a1280a6e9345104ec043119a10062df6e0069b2cf8d34b929236a67da2a548d7c508a155a6f72548bf31ed8f662e66bfef713c312ca4ca26c9f6270a1dbd3f587cd135d4fcf542ef0f1664b4155a98e599655295cd5fa6598ee1efad866111a5a7c7f374f81aa764125fcec724feb3e9db1c6f261a5fc30d83b7da155b330679269eff78469e2fa7b96dc5b92d676b0bb678794bced60e93346e0dcb196fc5b9d3746eb28e67ccc876a417931aa4b5a7a18c72d6d7d57dc7f5f4c588c0d7b0652d96c444ebebe9d4e5ac6ff9386bad3532a1a256d1f2cb24f91a474d9d52bfcb595f7a9adb71be7e3aaedf1723f2e863929bb23d2eac6d2dc8f6ed5b590b4517935c98d40e7b7f4cd0876e6152fbfa7869afb5d6dadf61736f8ddf1cc3f72efbd1d111f877486b6bfea33badf92e899e66ba4cda065b5382b53cf3e06be4c729fbf10cfcde82b3f59bdb44dee11cbdb77eb71a4684dfe1dfbc8690c5c3bb378c88a36f6ec3b97cbdb94d4464055398b4cdff41840b9f15b2ed3022fcc5225d8cc86392c38c6c7b0dc7f91aceed375bf7b8d6e6b0160059fbdeda20b2c558ac85a96ff7c733fcfdf24cfb9ea7b533cb98e47e31c97d31229249ee354cb3dad2aeca7677e1997777ab8567de5f7806dedd6ad79bdb8bc14309a29ff4c86e97dde8e808bf9ddfe84e6b48df6c6dedccb6b799567e7b0d2f8c73ad29c1046fe82a7b9f31cdb3d94b9ed99f2be8e8fdc533adf72e3cb3bd5f98a538ef15a2a7350458db816c7fae60c35a28b2fdb9228dde76e1994a6a3e9fe468dcf92f4cd36cf819cfec8f4f1acd6fe11926c2c5152b64bb86dd8c528616b72903f6c5337bbe4caf103cada771c381b5bd30356bbed5eedaebd5dc9ad526ff607c499ea9adf70821b2fd6249382a2dbeda7c7ded114e1a1dad3335949590e5ae365d6350e2154743b9407b8493e6d33a0ce876fe62489e8f4fb7d770c36e329f6ebf78669bbfd84df3e985618a93f50ad1c38db360b9f6ea9c3b8ea39249fb9ed61c6059bb22222b90c2a4ed8711a9e17db16eb2f6bd15da963c537f5c43c8f8eeb5fd31696fb52999b47fc66fdf2f8684e963121fa751c9a4fd3269efe63289e932a9bd617cab5d6eb6e6b9dda664d29e31bee5da32e831a9bd866bb8866b98e7c5a4f61e6ead8b2b07456c7183eadac105c645e5b2c18583eb06961dae2b2e1fab8ac5076b0fd717561fac3a587ab85a706521dfb856706971b1e0c2c2fac3b5854507ab0ecb0f540d281a5033a0bca0824001812a83a242d980c2017503ea0bea0c8a08d4102832a817505328165042a0cca07e40f980ea0135062506c5036a075418940ea81c50605052282ca82c28145051641c241db20fb20ea907690749078907ea0a3907b907c907e9072907f907b905c905a9054904d9862c8234821c82cc825c82c4823c431e41f640064102418a21c76828903c6842fe8027027207f20b5983690e931aa41753179e19130d9e101e10a6379c1ea62f4c69906080714c3e8862886f4437a216a628530e22179e1a261d4034241c2637b83d383c4c319864e0e8307901e5806f806e8863805198bce0fac052380accc26486298ec70687856785c90d788257c6f30114038e017b0075e0f20073007920c9783f7865806abc38c82e5e1b8f8d778528834886490c08c7b4c6540548063886298c4bcab4034a05540a60196217ae294e0b4f0c2f0c0f0c970a1c17220ca2176f088f8c870604e321e1254122e11521c6e19d5124c6409621d1707c78519c1fde15b18b9e58067804c70637c7c503b786f885290bb7854b06ee0df7057806e785690cd7854905ce0d8804f70777059705578763e37de18ae2c200992061c038b8c6b872c061c035700c5806110c318e6b06d316ae8a150e092f081318cf079a61a20254c21404c986a98de805de62fa99cc902f786b805ac02c261e5c2790549c14de1c4e0d39830908118e498be9043009d3192e144c6f885b7051786860185c5c4433c028310cf20d52064c06ff807dc054f805dc02ee82a7b00b580b3e01e740c6e01243b68051c029602cd804ecd34ed0ae683e4da6a0c004768c58a0020db0e91a0608510012093802010ba09931c2000520804c1141868821c194e007909711c3c16abd0d168162b7e4c9131d4842c5134b982449623517459325499254b18d7db204044f3449328a59d89d254f9638a122c9ce920ff88858d8253b4fec3c4912458e1421e8c013299e2c69b224891ff10a4c763ed064490e1bbb648907924cb1936487c907a45822829868857d9223c5133b4ba248b2648907888855d8293cd0448a242370b224043a49a4a0a2779a2c01c1089e60e2910abb048a26533449f2441229a8e82550349902023b4d42300514503490b846145340b1248a20e214f6c90896e024799264c9ce134e963459f281244f46b0e4489329a478f264c9046694c28a20c91449a4a0a29d2cc1713285059a48f10413283ce0012433466197ec3c118514394f4049b224044b42108126a258f2810f3cd18400a21afb04078a254ca080c23ed969b2c4c912264da4787c4213293c308a4e5828a478d2440a273b552c4932821c2aa8782289932551e44811021b1c28a6c8b179893ffbe4476cc23e91e42532614190e4899d27a2786267c912a2b804124891f384143b52349162474c6371a09822a7034996ec84008a25552c59894a58275334e0dab624a7034fec3409810792ec40b1c489145134912249121f3ee20aa62951e0628bde1e6b7f7c7cbaf5381f91e3c0716854b4e9adc0951598ad6430cb569677e3c2c7eb769f73cebd4751ee2d8649f9ae8bafeb3dea516d6237bd1727e95e6cfce2732e3a574927a3731bafe8a2dc8d6f0de09cdb7def718471638ccfc1189f8bb072f12dc3085b8b710be0228c0f937141cb4fc6ddeb72ceed7b8f1d01648a587e6e3742f738eef21bc0dbddc8c33917e3bef7282af2d8ddb7ceedb638f79c5cf7a27cbb6eaddde5c7bb3b39d7844c6ea5dce85e74cead73948b1445518fa2a8dde81c3bb791da6db9ae0bc6785dbbd15d4fb6d62294419e48241245e822bc9c94b1b96d020840cc9246448c441bddbee8f6edbaf69e8b6e638cebe03ae75e8b2e42aa45182386ed6edcf81ee4756e59de7b2fc6e8dc42ec391eceb15be76204c59d5e73cf4dd1bde93de6f7765f7bcfc9f6f8b15c1777dfe36ddbe266ce39c7bcbbece4c618e3eec618dd2ee5761fbb5d7e8b619894eebdd71af6f839e72045b9f6dee59c7bce39775d975bf7dee3f7dc63e79a8f186504c01010b6441863848930bef836beb71be31342b294987cefb99de48f1f58931c61f44161842d13c6173fb5c7cc8f992f7e8d995f8c172842e7e22ebb37b908375e39b8c2702bcb237032050896802016c08910d61660c993274b9c50018229a048e264099429a058e224c9134fa28062491202e0cc1ca0005440b1e4098d8b07a470b2044a120410e000072840144b924cb143c5089c40214403c12e019638f140122753ec3c01c51439394fbc2c0181143b4d42e084b27860270440963049b2d3440a27542481e28914207809228a254e9644e1648a24330728c012299cec04c0c9133b4f88e8f1c407105b8029a670f203078a273a9093338593008c60094e929d2a96340162c7ce920ff8083dd9792245154b5ea625409329a4d881e289274d9e70b29364470a1080600a12902d401552786089132a288b932796e44031ea114593255450f14492251f78c2c9143b4f5e8c980152c51422d80162e60005080d29000192146009ce13391d60f2e40926af729640599293a48a2740100494264bdccb1220499329a478e2640994241f982227c9079a48f1810f4c919364a78a253ea03459e246720b5e1e9e9e9e9ec84ab68787797878781a0f0f0f0f0f0f4fe429f5b8520f8fa394f4b490929eb63d3d3c8e95f0b49ea684a7f1f0b092e5e9e971a51ea9a4a7f5b41e9e1ea7647978b8472ae1693c4d494fe3e1e9e1e1e1694a9687a7a7e7f54c4ab6272ae159253dad072ad965253dadc729d99e1e56b23d3d3d3d4f494feb694ab6a72931c2f9b4dd6d2078f2c40654ca6de2a5dc86a23c12c2c34fd159f023ebf164b2945501e9c83a846f8d8e2c487ba8346344dc1b849227b27cc37ab738741e276cb20a60e769e115ec2b2b3e8ec52fe604f6957935f9bd13252b6c8668826db2bf6ec28a1ebf6e45f61e5fac891ebf6809f40c7e3127b2f730821bed6e0435da7d2f8835e1e3a25f133b685ae1e3a0430c033f8e3dce267c5c444ba0b750b7e0759897403f8ed1928f5fa0b9362ed38aeb3f0e33adf8711f6f995694aefb38356b4fd77d22db1f239cd1169c4daa86a7e64dd5105e6fb965350c03a0b73c34ad003d74d12c812e7a364ba0675f0c033e5a662974985fb3a7ad57b7a60b2e868192e8d84ba1bb7cb1267cfc072d65c744d38a1fb782de8af9128f15417ce52d2f65173d344b7fe9077d897ede655a512a5dc75eda71112db9bcc4e32c5f1b986945a9843d2bbd1c88b30dc49a005d74883991dd072df5d884de63969a4d88c2d904085e44adc82eba355d0eabc0f51fd40aeca04f58a44df8b815d7435f9925ec20ca63458fc33871512c7efc7a88f25436a26393a7b2c9768f80b4cab4185240bf284f65733d5e3443cf26f61a87cd6653818c96ae63540bd0cb881427079e7abce8356cd12ad3d42b5a999a4d5cc3e2c745b4d46c86a05694462f0179e9e5401ce6724ebf26163f0ef31eb3e4f21eb7e20aa8f462614a5c01958e677cd1ab856e1ec8f6070a687475e9f7f77e87c2184fe8c20861d8a18b204711ce38c2174690e308721c818c228451042967d471c61b67ac7106196d5d179ca3891ea13abe75d5d31cbd724c73745c393a2a20cacf3b43193e1d3f756c0b74c139829fe608d29b8ea75af66237457ce1093e1776a380283f1db1300574185a6a36252128167a89521e8cb6bc34a994db84284f10bf7eca13841652186093458f4de8a1b7bc86db7441b70331a5dc467420f365f2606ff988f2646fc932ca835129b7c97efdc72cf5d8f8d06140e3d46d45d4fde9c1946ecdc6654ab94d49965e92721b1ed0af8bde639678501e10c522d4f2d20ecab3f2162ae536d859a894db8828cf0a95721bd02f8a4588ae8ba9fbd30329dde8ce5b2d74a320c8b0d0158a6146e76c1bfd1e2a4d6112b88e3d73341895821d44b5c028160cb001ada3f9bbc424805df4ccd188a814d033aa058896118ffd7dc294e8e908290e057a6c426c03fae3c964a9f9f86455eccf081e742d6e037a7ca5a2231d41da44d65e95b4a3c1a815d8afbfcb9680461f310cdc07fb758735711d444ba05f20ecfad1233983a36d08b2776b9f64fb33461d1dcf9289cb09258f076dbc76c2d1e11a9e348467eaaed1f0109e0998864234bc1324daf04aaac333713451caa828124c036de06578667fc430a3e18de019d8f04178069e6435e7ee68742bc4ad8bdf9d4e7418d0bc42dc77626f6b189ed16955ba1d4e27fb9d7824894e10ddbe538701dd56886b7437c83aac08c492686fd803669d662d41c797acb08247f238775e8a56771c496b2c12cf374cab15edbe1d696deddc6b9867fb79668e1ec0770d3be2deee8eb8f0cc74f8176b9964eff1ade3dcf669ee0f0fc6e83861673cb391d6e6878e7fd820fb566583ece3037a9ad0ecb341f6351c6ff80a7690251eac9582ffd9d38d30f17820e5f5f4c34b59b3aeef29beee1b3d5d09cfd4213d7dfa10979ea6d74c461c4d9432249d1e05d3389be94eb8d1d335c033fbc23373faa2d1a0276e179ed99f1d78c16ddd663ce33ebd8567dea769e97027b0f603d9bbdb9f1d48e92a1b7e1b0fa9b2ebef68a294f1dc1f756fa9a3dd2f9e61626d0732be737179914cf36c5cb7737779d923b6dd81ec11edce49c05a2a06d9be9dffd212d0f8ebb2ded7ac8fb430d2e8d6da17a6cd8431450751deab4bc39f67aa4cc3c3cbc837fc90ac61100d0fc3346c03ef9283311afe256be1197708e1f78704560ebae8217a7fc078433b4a89ea1495690390af610c64d5a96ae264151dddb08f4fbbf3f8f83465c411788ad61d0de5750ef591916daa3aec9dae618bdf7a0befeac23adeebae2faad3aa606f6d591606bab29515111c2cfa4a96658c31b172ebaddc822ba115d04519081402dd724b6891e31f64c5d4f7d61bdd5ac718e3a9c94dcdc724790d64d3e3dd655a11f24198244f6292bcbc45723446708cbcac46b47c955d497d0192114c923583f2d5882eb88b9aadb97504630b1d3bde6aec9a03d59e1edd389e233e94a9759a4f5f6f5dcd117f54b51e55f4013d7d51314ec0001becfb03461bbd3f6068a1298b569ea65ec3748fa02e2f1d4de8f299a36936f22e8e4674f9b7045497b744b3d954b38ae00a5d81f103c69406c3c75dd5acd547d645c0468bc00a4dcd27adc5c82e9a23ebcd866d427364d108c8d0b56836a1df721813a25bf0a2e6eb86116142742b5ed474bd98044412581b906862b155eac09e4d2c9a4d888e2aaa13aad2624831c2063b16a13fd868d16cb047913e3e3e208ac554c506bbf52e6a722ff644c3e612da1bb644b684ebe912f640866e7843d335d0e8207a7fa878d154a4f4ecfdc1c11ada0abdaea486876909d8e64e71dcf9f58eb70f5d5f1abec625dc99e2604bb06b9c78f8d2f00dff78667fa8f8a15d5a02b8dbe46ed8fbbad7ace125cff0e1af9680bde13bea5abbb704f0126e0999de15d25c195e4469f8857632b043cfe65b219dd6b0d1da1a76e49d699d81178d1569770dbfd0ed6fe2f0eb939e6e87d7d92a55aa4c690727941d5de4cd875fe836a1ece865d2a38eeef09137f3dfddc4613aba6987e73f3a3ad220ddb1da8b2e7a87dbd1ca87d2dabdc6cd767336ef58bdef818dad108330dabd8a3f0dbf3cd36675eea2ddab0a897bf5862931d110645d9f8f4fef5455c5328c21a95e5be3b457b2e228edee309ddaced69f5b06e9b42a1ddfb053c7ce6dc0464110500c2584eca8f37d1cc3463150a3ddab8f8ec5e08c7677d875fdc24097658d8000f19b10102042cd8c11866118364720898d4e7d34a25880b01b4046a3d16834816411c8791c08193c03e4f0564b102357c728880b63b91ecf54383f5e5da6eb8fff78f51a7ed811f7ead65a6ee53dcd403e01c1dee3d82d6c8ec8e099d1e1278cc8755d2faf6120d774727301f12bf40b087a852ecc1a8dfe32fa85843bfe6a281afd8db0bb1cbbf5bac9e663d6d63fbe189298c67e4c1f3e5c7a4ce80593e0794c67072689662df5caaca56cd61268d65237e805cf345747c337670776c36a58454737ee38f1edeeefd3173312dfc9e019ecf016866167ecafe136416fa3de9f26ac21066ec4400e18d8010672c0c00debcaa4dce6d75fbd3a2dfdba1a193cb30d8f61af618a3ae8d441a0c35b2010086455d515baae67d574725355d6e5abea55655d6d563a0b68d0b72b5068eab406818e218969101d1dc1de8ed1460693e0ff59bd86abf8e927a53eff9cf26dca567d96112f5b831891fd61c11add6e62bad1980693e0195b82ea8a561174756b79013eb7dae4e9f8366b15ddfed9aec56dace099ff7d1c1c6ee8c2c7e634caf201e4b5b8cd2f697d9f6b131f5fe117f2a2b551afa6bb356b154d7db125484ddd54d5748f69f00c7558e1170d4f4d6e8349b05e0d9b190d4790c1731bcd0cd8ce68d8d2f0d9bf0ddfc838fcc50bf8e1c39a98ff1dd6c45fa23f9d871427ba9e11939a602d08cea8a8f4fb8fef654bc074248c76ff312bf7e8a02f86c4fdc705846c7499ae53a6657ab111019a09d08bfd0042162fd30d43f2e3951b6bdd686dcdb7162b8280de1522df26466b0f3fce861589b3a7db1c1d34eb8ff312073d88208208c20511c42d770511c46b38cb800002885bedca2610409c3122408040d7755d5d74d15ede2e2051fce8fd6982196dfdb87cc82711a9a43ddd662dd576d0e3acedd841ee0d57845bf40b85ae8730229dcd6d6b7435acfdc56a1804e0a311f890ec43f5f21e7f798f97979797eae54080bcbcbc86fff2f2225fe8e84810bf1ec47559a1500be3c7acad479f80f87520aeef1e5165fae57574be8515711fb57eb90cb93784aeebbaae09046248627a1cc87b1c48652091e50ac1292120402e204080cc5005e40e4312d340e8e888cb437779e8d6cb74f2fae5a1d772c533ae5028147a0d83bed812ad23157a888e8ef838e83eaebf4cd98d0acf5c3b803ce9e938ebf4c63b365b83ae6b1bc8d4697d1dbb5ac7bfcced76ebfa68fe788dfb31b77dccc90d26bdbbcc680693de7b4cb8c5f4462d55b396bac22947487dd1d1111e079d07957530e91d4477cc1164aeebf446bfcb3aa6375ae60832eec90d9e69f10c963982ac7534836740154ee977cbcdc1332251551d2b42eaea165f6e0e9ea9fe98798dea8c11a9ce6af04c7b9665d917abe1cc72eeaa2d8cceee3022d9dbdc162189e98cb21a4c7a6f542c8a6ad41f25c67c9b35dcbe20b0de459416d2fbf30237bae843d3f4fe74a1060cd4e899de1f186cf102355e30c60bbc6820bd3f2ea8a35d30870bd8686be59a666d96752d26692db57c9b35ebf639a5dc665e6dd2d23c4f65b37c00296f59d79c6bd3e6bb6cd67551d4e1e347eddb3d3d3a221f9f84a4d5bda1df5b6db496babda79b9b3bdced124312631d74eb200ab760d2b3fe6e75dde1b6245684d4d6172362b5f886bd45ba4c6a74871b748168a8ca8ec7a6931becf2372bc7be4e6ee445bfc92e8fd11abf429749d8315a5f8b68d5698d5dd2ba4c8acf406d62733b347516d0a06deb35dc96852189698b8e8e54a75e51370793dea9575ff00cf547a5dfabec43bf53b35161d2bbf5c3894cc709279cc81a9dbe3f4690e1c86f57f7065ee3c708b2f67749db4390f54ed6918a21e536253e83ca15c0f0b1e179733881064fb0b16204a51e1b989f40c6460b199b32dae1163ce3dec033adbfe8edeae8685ea3df990d21dc1c42f01abc00216a0ba3dfdb1894d5a02d0c5e00fd6b5ef47695fdfe7866142f8694dbcc6fbf372a3c338ab489ccc7c7c7c7669ebb2d60bab422dae1fcb141da21b644d6ee0f6b827ede614d7c9eb108ac8d10347331ed0d8bc0da502a5d4cfb6211589b49d7c5b457548070116b84f6233818d4fb33258c5e9fa9f7678a94de9f57c77f9020e3de1f16d0d1fbcce2e93ceb93729be99a265a9ace334d96bbdcdc5e426aabb9d7d6aeee70bb22a4761743be86792e460436814a93da95fa9da79c526e232f29e536f191d6b700494bf23cd3970fc0af0fcd732fe7da2c86c4359feab8692e1fa0ee0f0ba8f40e3753e1996d2431ed68a3c25467ea16074b89ec45d1c5a4c7a416e768761daded4d29cfe6ddcd26329bf75df7ba5f68772732eda84ea8c5787b7ebb189f3058ad4d1725b2fd6bec0241590961adaf3d627777cf670cc96bfb25e264e73287613937378a6e35789c7d6885f0eb52d00ae1778bdb99afe86512bfd18c499012d18fb630a9b55e187ead75613ddaf0b171e1d37cf956a49dcf6f9812ccad5d8c2971842f6f2d936c7f564047832edf2eab5d0e2372511cebd8ab59f9212a27e816688e6e9e8f4f87fedaf5860e9adca1d73068b6b6f83857bd2868853838bd610b45207a3129bef271e4abc35b0fabd4419812af1508f41ab61e9644bbbc05e5b75dcad7b0c38e549738a015e21e5a21cea7df0a714964cfe78a0ebdbeaeaf410f4d273a4b878f4fefac74f69dabb35fcec94eeccbc9f59dd8d9173b92a443e7063d9b3a4174e8d7d46140bb42a110dd0d029ad6bb78d6ead7adc592e0220b4711daa137eca681517baee810659edbd8acd76cb7e61ea77d5bb66f144d346352a4b421a8c53a584a64aff767056c502283bd3f2b9063578810d3ad9d56e8867115ba85495ca5b11532f1944c9adea6ef6c58342cb277f7c733dbd3b13d82087f9a7eed11d317665283b50fdddad9fbf3a346b7fc44a15b1e954c0acdd6496cebb42ad80a095d2b0474ecd7adead4e5a71fcd04593cacb2430ffdda23f8a1637bc476e83c61426bb0e00fa9db26b504d31ca5262b47b31554545d9da295a7e5172b42bdcac6c9a9baa24d2a49eb7b95cc7f8ff9353c9da7f5da631264be45e4c8fe34d35ad37c8bba168adef7a7f7678b337abf4e68a68f49709af5759c35e7ea77482f2661cb241d58f030e31979f32cbee07c0f02193ff25d98b46bbd2b9e7a8d6bb3c5055a6f42690d2794d611567f33529c25e2a375a75fbd1ade92a3adbbe6b646378b85296d1df69ba32dcaad0808cf24786b1baf036d2b02ba3773ae1671a829d355841302597b0b93aa399d9af20dd391fdb242f6922e7d543289671ebddc7c61b06ee083b0869f7c0dbfd95a3e7e3a6344e474c92c08a5bd437e14e7dd5d2ef1eee47cf36f7a3bfc93525ed2f71aa773353f8efcf8f11bff85b6c5cde956c392e04fb7f86ad8f436fdb851d2ef7437882c022f239d669d8ef32e6fbd3b2a18899296afe1dafa49ba1be4315604fe51f91ad73e457a31692bbf397954c606dbe06037ccdd6ad86111b6b16a4fb7876ee263bb0eaf57d7a957d56c9994152794d6505a5fd7e3dcb678dbf20335fa1512e74571e2a98c67ac4f56a8b5b518918da28c49d565fa7ac388345a7bbacdda1a279efa1ed17a9a5f21fb8c67647a6fcd8c492b7b3a124fd16785aac3bf5f6fd811f98aba30697fd19665d2c6378ad69ea67ecd36df5ee3ae59ab5315adafab5b6fd3716d7dbd3ea0a709bd1be4a2b59a3a5735b79ba3a68e6bf74d41e69eb57b0db3ceebc98ab3c2b7cb396a54a7f54405d056bbe414d2d37c61d21e7eb1f6a9413f58ed0b5365dadd6a0d2382e4455adff19269dd71bd4ca2dec233d5dd5d2acba2d53a756b9ad6dd339e99ee6e59b3b575bd618f49d4adb97d4dabf136076adbd35c26915c82bcbc64dd688d77adc5b94d4dc980b903bbd1162671a4b58a5965afdc2822c130c97dfa3269a2355ce376be595bebb42a55aa54e9dad3fb38775cef975e4ce22f0669c624fed5729ed9bbc3f04cbb3b8967b6dd0bcfbcdd83ec11ae7baa06b7b06eb27d4b66a9b14bb2fd61c194aebadd72cd7ad7ee11cda7b7c81aa6f37c7c160b4474386e1c131a120e4a8eeada16f0ee0dc7bd1dd7f08f04c3a41726b52c64ed3548c3d7f7e034b242f6f21dbe41f80edf71e0a30e60d2d630691f9b83a3eedd1ebe3ab7777b1cb754b6753463d25e4ce2aeeeceb9d6f6aff57ef7b9c0dac68ebe1fef15fec13947c381ad3566fa073290810c6428438f5fb3c78eeb228bc79c7ecd9689669f44bf567e9dc7ac2ecd83078f95ecbaa65fe7d1728baf6bb67cb1eb35dc5299f475c68af0f8947dba084a10bd7251461fd0d32b74941d67e52ccfa665926856262dba446f115d1f89cee3138f8b8e442b67f9cab5a30c2b2b3ccac083c78e6b07ad2b578f1ea3977ef0782b038f1e3d78f05869e1c1e3a2ff68c9321e5fe1f1e359c6e32bffb1c2a3c75b6eb5f4c0187b404f0fee315bef982de731b3f798d5a57bdc613b8ec3e3d977f078761e3dbee32db4470f8c1ad91ef5380ecb79bc07bd90908d6e58cee32c54741e54f46592e83be832a985569736c2e7c78f16beb88d8eb71ae8d6144da2d787bd8a2cd1bc5ab7fc9a2d173969fde31a4286337de5d3459fe68ffbb8888a563e5597beae6b7a365df4ebc78f1f5ff9342b939ece5811243c9e5df41d5f616161a1a31df48607cde8ae9095af5011ad4c5af4c9e58b216179f61aae2bff41af21642b34c7a5a78f567e5d44273a5aa1372ccfcef24c34edb4ee71ab5dd99dce9305f44e6361f9257a0d5f3d6e39992efa6247dc7bd09a51160aa5b5a8e5f5dd60df15525b2a73d1f135cec7aca2ef5d6615ddb01823c8e265e040693c2f2f8d6819d6cba8d6a1acbc8d2183dfde153aba79affcbd72d1775a8f6e562ebf72e9a4f54e6b96165a7bbae5a2959616f55859b948d483d6ac45e7f11d7399c483d6ac579ecd952fd38cce8307adb2579e7d34976344af712bb33269f9caad159c160aa535cbb76b8e4bb35c748b2f1696ef386337f22cb758e6ca2d8b312496bb5a66eb95595d5af4c592e05e790db34c6e165a5bb37ce5d9ac2e373cde42778564e7d1f21ac732b9b359b3577f5811ee95b3eca037bb42446fa12e2b44f41a66992b5f915fa138d65bcd688e4b5b5f7ac47a6b35ee9a6a65324737fbd313c581d2ba4928d3db77260aa535cebbf56237bfa2db7d462f31a0ca8896d11e290eb7b183ad1458d61b35484cd9f59eac71c05f483046c8183144a60d8c484b262798ec7a2f67d234c13069fa90ec8294c4a42908932618264df49d6a91d4a5be66f7606c43561df06098045d5a5c5e8ce8e97232c2a4a60409860d591dd2adb556c50e1349cd2af3d22e4f3ef2f4b61c4ac5aee1000dec5001d34aa29009c224e886acba34ec96bdb5f3cb46efe56e4b75e9168549f04d0a93e06b7869f36112a42f6352cb41d66ead9c227c8e9b04d52a7a64e459d72a7c7c7ca4f4d2ed428f6c2b34b016060e34d7d5e09055179786b48e1240648be0571fdd5ebac1740b52d10ab3f69e1e52afcb08134d383135c0246e7dcdba53683e9cdb4a30895f5d9af92e2e2e2e57d6625996bc6559cf7ad57459217c6a1e66926610263db8fd22fc6b4847ef3dfddad11726f1e5c8daab8bd5200bacf7da1baa4b4ec353d1b0ee34ac55b806834c2609f92858116a62d21abbd68620e166906e33e3ec7cc95575c933d3ab5f4c63bdaa60148ee12301e8820fbad032c7a5293a3af25afeb5a423f89efe1bb21f1f1f9944153e538291c130a99d89a3a9714b874bdd39e0ceb17134ef1c43907082499c86acbd0e698e5a180283041248205191687e153224fa5dca2cab2eef95245342630e681803668814a4747fb46e1b3deb90768263f85266998bcb4f22759369996e6f45c8e08f573d6392555514f5c58e94c008701003958639433afe75936da31fa59afbc2b21c7d7109d2ef56c3889098d42c1a630d5fd8a5d31e9322ad39303ddd7a0d5b158949ef90d61dd8f271a2d4e18469f886305ef214d109c2a47719263d7eaf2d04c1ac90f78c49ef62d2db210d6b500796d3c01ec1d204cf88424af08c75de7107903276b4ca60b49242b4be2ba97907ad2e5d49d30e1f9ee1717e8bd2f21de737293c43adcbac38a412479359715471844075349f7a153c033abfc433d8ad2f6773f0d0809215e2809a7df2629291acb2b866bd7518d1c3a3c52d2c978ed588662355d2606723988de657239aef44a504134df08c2561663517b3687d338924e3b27158ec66d0461bb5f958a1f9b4f4693e6db4e1a6342a71d2d146c8266e73f41cdda8b829bd42f4c4d6aee03324f41a84f48e221a234caa58668d8ba9be3287b898ea4c1c8d882ec74cafce84a3c9a80638a6fa45ab39ba7a5d38bafa0ecf30c9e119ecd5a9e019d0ab57c133d4ab8366102655c7a60c93aa5fd38875315535af21643c04632705eb6e5a8fb3c29c267b75a95947501cb2ba70347f72eefc2898847278a63a3f348d6012ff9a4198c4777388a361101f9b128c8c7a9551f2a255c6aaa85965fdaa0103629705ea6e5257f56a72ee7cc90d0fedc7a9d1528d8e6f6b64d3e5ebf6a1a59bd7103235a6d6d6a0a3c7ddecf138ebb23183bd0691e9588de8588734121d635d353abeee1a1d5f492fafe7f1fa225d0cbfb674fccaeb6b9696d7ab775cc6d1bcd01a8ee1fba0cba416872878a6eeb8fe711f671f3ece57c2332ee733e11959fd381ad234a4867acdfa9a128c4cde099eb9ced700cf5895a3717131fce9a3b9b342dc7f4c2a7ccc9cc984491ae018d783d6ada3dd9707dd319d60927bcb6c826532b1329530c224f76c3a8049eea159c32477d0c4e69bd2c5d150954b73cd5a4eb3caaefb46e72c1b3f3812c1e25b0bab38ad4b30b2e9d4ebd5f16e4a303279ead2d15832cb5c5ca64aea2a5d4c95a1173f42fa98d4f68e29662d584b6bd67bb93f26f13447d61ecf3891496e64fc7a5158775e37286c6b5de4b8f44d00bae0032fe4b874a4a3238f0a93c87b7b73d79bed72501445518fa7601873f1c3537e24d3b07c9a265a77da191d23732185b9e0293f2c85b9e029146fc152786679ca0f7341512c85ca7e2a1a61188f8afb82bd681d2977c1a4e92c85b7a03c8549d37ff094185faf38dd55d931d20afa54b16fbc689946f47816eb71c7b53c3f0b6575c7b5f518a275e7b53c9ce635840c44775e4fd4e11f46775c4fd4996efbeb89e250d3db797dfdedb86edfd71dd7cd0ce6e2fa7473519c76d813b4286c6f3aa495a774642e5a11315627bd1c978e9f66298a7edbfe6685b971e9ca243671e94771a622d3a92bda1d2352dd611712327831441290721bd15bee5a2e8e992e9a2b61e0251d8de84950d71463e5708ab1729699710cbc352f26419fae1ebf524dc9a478186a658a5ecdf66956e1e3e393513388684a17e3a46cb2ca2adbf114e602c4221ed0d1effdd9421b1c1f998654bd668dd64647bfb616001953e9d734435c8c7331f135bb3efd4d7fd3f4f816c7346144a61af730222dbe9d21363aacc733f0bbfddee8e099e98fca1491588dc8bbb17aa2431ccdca0a55e262dcddf72f0e69f775342b745d0cf68abaa5a3d169be8fd1241f2ec4d144f4fe70018ddec24ffbe8fdd142186db90b46793d51f6717de03a5a4fb4d98149efdbe26809d89e68a383490f7eaaaf772aa4d3eb9657d935eb89d6d011a2d7eba04f97b4b6b05c7ee5eae92dd5a7af3c1fbba8569fb0afa361a1d8abcc28466b4e4bcb63f31a4216a2392d7d59c7ae2164d7ab75c692c0897f7d511c8b5e392d1dbfaf392d0dbff4c795842c1e7b85c7686d72c07e6f676809b84ebd613790e2c4c3bea8ca61486047bac34d474f6f6768454c2e4c723aad4a5367ac88eb76a659b9fcf2b43225c74c9fa6bff04ca4eeaea7d7b01247b372b7329763dcbce67be1998956978e771705a7fc62455eb76ac2cc1726b975b4ca2c73e9f6ead2eedd265a4b3dfd4d0aff261ded0cad88f7f7094e91eab886908e1087e4602d1019e5b6d7d14caf59bcd8614498d69e52194d4c8f135d17e3de2c1e61807a7c13f293fca34d50ef14bd8b71af8978494b998b718f745d0cbc04d6c6c5c5b857fde2ebdb55366b33bd12e1d738d2dac059652399ef86365f00eda28bcb0ce2cb38763fd6c25499261d0de520849069ed2995011d74566902b0ed111c01240ec29baa610dcb7531ef70be334684a77963b550c3f384b762db235c2f91277cea16e5e262deab99b998376144e06535baa93d38687eec413375f28eefe9b816755951069fa73a75a40c6a1b17d9fe68a18b764526509da25650af4e81b5a168197c3831099c80013615bd8b7927b998770b43e2ceb432217f0206d850a75edfef9593b30c778bb11b515f3ce12376236a778bba78663d4dae4e837357c8e54095020da905ba9ac56d19c4af610d74cd40bbc75208d6b61564eddb81acbd8d2f9adfd254907119bd3f5718423395de1f2bb8a049bd3f5578c2cd0e167474ed4923760d410ba4d01b6a2ac8dc137a7fa6e0d34d8dde9f28a4d1417a7fd490a36149545788a37594d07183d6e10175dd390319425d77c6c0014b0ebaeee82007d619baeea8e00cb0eb133014a117890092d046d72768b4d12b7d7cb0add26f16c3d2693ddd829fe693d653a338fcb63f6aa8d1951b9ea793b642e07764eb6c95c65e5518d35d21b06a736fcd1a0fa31a981a6e34f6ea16ac56484f7375876145e4f56a6267ec08f5d6e8ae90cab4ad90f6ead7ac6c4191ade3fee0d37b396beb9ea9b11c2c7959f9fb9a1d375a5fe3744add2e5b7b6bd779b9a24a43e1e93189a7d6745a1f3afe4d879275cef3b9a22b5f67eac315551a8a6c3e352bb4de28cfab3253be8e8c62822bda0994d8d4a14cfda4a7a977ae3a131d0dbfc33aae231dddb49f7667aa41e17eb79837089f72df362b35bda6de989969a5be6fd412a5961487bf9f68913dffe6f5a338ee8ee2ec79dfde221baca645c6772d8bec6d9d00f6cca4f652eb697cc413a2f45b62877ea76e2d08dea313dce805811afd8e39418e7eb7b0ef5b5c5e0e43e299f8f74751d7dd755d5e974ea92febdb56c8c2191d990e3f4d933555a0e9f216557d9baa681179ead475395bc7569dc2693d5515852179af280e7c7d7fd2d3f11243f24e5dc7897f9f688dbf28f59e86137b357bdaeae92b34e3acd61d357130eaa119254a9428a16bb14b80c61ddf6cb4d82540751d69b3a9716b63cdedda735d48c8b03b87dd588fb462b7e8c8fa748c8e2c2a1f67f5494e387209272749516bd6eb23eadbd6e31c51b408bc758b56164ef51a9158745708d56955a83bab63eb6db88e666daee8edbd8c114390e0996d9ed653b145876d7c2bd47cb2edc6ed4c581c47bbf35c22abdbc606b2d7a2aea1762d8e1af9d3ef6e2e9155690607b2d755faf4bbf4e92acde877f9c300f7de6c1384f1ee138430ce97c6460dbbc0bf47fac22448f73d66369adc235c7d69f7e6e45dbc8b771f22a3bb73f1ce5991d7d818dfe247efadb9476f0af170b6481f935652c1dac3bf3d227e0ff788d6463009fe092dc324f8c55cbd7caeae57439aeaf048ec115f21f21993e4e187c4c8c3308de4185947467c41d3c231f2f0a33da235b4a8f9c22479d7841a8e522ea1e547d35d738fa6e9fbf38346efcf8f12ba65b6a6e80b93e434e10a5921ad1d0fac8da2ec45aa6eec76f6704362ad93a5b07afc4e6b1629228f23afde9f9f331aae90c897dfc9afa6e44fd397d6e9d62d5e5a5befad59f5a95de2fad2665954e7494f63af22e0831faaf4936d638c2e52d018b568ddad557475e4a2b58aaebe38d4772965b9c65891aa7785d4b8774dd379ea8ae24ce73a4d97737b923b7bacf6806095b2760af4d8f0ad70cfc2d11330c086cf94875b104b80f788e80a2b75534131e4a2a12115434e79382b50c4179c50c566ffa01856f02c16a6d848eab35898524b0da97644b4d5160b536cf644f4c2b82c588b352aede812070278812a78551ce3a57c5549525772b657f37a9c53bc554d48e3b424c48a60afeea6fc35add73869ddda61d0ac6d879b97c853d41baddea6a4f52d935a91e8b6dbc44e5177b45a969b9343426a472bbc95bd86dd456ba9afca65cb0bc8688927f4d2f201dcb3bb3f0c09a9dbbe4debd7e467736d42b38ceb9acb240a2b12ab5b53bec65592dae659db1b8624a621bc8090352abd8d7b2e8e527a7adad9e16e77b3969c409177df798f670c09a92b8f9bb50a9d2090c025ae095568b704de82d7041d24a2211147a1483a4d78871d21a21d0d2d7cbf79379d20ba395a7b1a3ece5aea78c68e10d1d5c5ca7787fb1d76d811d8954fb7bb38ac7d128ed6d698918d523d4a19ee1635f787d9e26be3c094781812b7366cc1e5760fe861414b24324b07046a5ade614738d4d3ad064195678a919a9698919a249ab40ecfcd15c4037a70d0122bd243a57594b405812a0f1539dbade98a93bc2a49cdb5b1a8ab9a6b43cdb5b1aacad369f8a0e3336adee7e6dd5187e95411c50457f8b4135a1d63489e8e6b1c37d110b8f48e4b47f7e670da754a5ddde8481d41f323c33bc8dc647bf8da62ed16efbcd218a31f4b3e4fe789f25d1c4d94321ad3381bfe4f946617262d14457a5dbd3f697c21c327702e8d2edabd455e17b8007a7f94a0865e9073efbdc724f7f800dcfba30439dc7e81c803329cf7dcbbe5269479f32a4dc33b9169487542edde9bdb6e2e63449ccea1196ef800408d99f617d16d886e6f619a96042a32a02a48b0832329814a7b5f379a975003097234df85666dd5a93ff8f78721e1bbbf69fdb88090c974e55eb5cac1ca5577d72ff7aabab3668b5805e4ad90d40a2b5e0e3bc277b7e4d5b00a480c68d16ce4add0a2d9c4782ba213f2564827e4a7d7dc0cac33dabd14312484d1ee0ebe7a145df438b1908f8793a7b281df3d627aa43c954d7c6d481c1352a843389b0d35cbd061404e565d8be96558f7a96ce0df43df4642176d59b349d5d6af795381feb022ed2bd7ebc4c9b17e1dbbc38acc26d6b1afccdad3a2f650e8d9bcbe98128d86da4173646168c071043ba0c146c3f4fea031061a5ec0cb9a231086456cbaac39b240736461109bb023d8d198a377f848d5ee91622169a9c7a6a25a4c6f36109b6248992e6abda6212d45f8e954a43c103ebec4231f03af71342d14e651be6c77c8f6a7c6014676e963d2102619515515b62ba45e5d572e22d2fe9061b75e594e9d7a36e5ac59cb5badba75ea2c0d2bb242b3875ee344f4edbc065159abef3c4b56e78a88cc08a68c381a1e675ec319cd5c59556555542e269944d195e1195973de93f25d8c962f86014e4233c07bcbc5688952469509d23ba616b7294300dd32338eb9debc56584ec15ab9198b886c51475f3ce34444b6408349db4e943169f446f3478f16e17edf1f39d0d1ee17d3503e2d2e2f4c33844d3b4c1834f7f1f151824dbb0c1ddd6e04cf0cd1ed4378667fd2f045b72371854dbb03de24c2451437896c11c624b2c518d80d37112e28ab2063e9fd91838cde36beb0e2ade864e7a2a295f4aec392d019ddfce8da83039d2d43c35b3ca1bc83f2137c0d4ffc9dd853652793849f18c243ba1b84efb60cfde6a3f1ce41082b3b81f11dae4eabd2adb1226d8ab53cf3acf79ef55eac6215ab5855af9291ab73dc55eb57d14aab73f1138e3b0e9fabe6fc10e15f83e99b1734e62047bf473a3af2a8d05e6bada5a18734fc9055986b3ac7b6870ccaf105a4b09bea90d6eaeee1b8c73f8c081abce87ad13c1bfe39c3164d498a2f49ddc8e3b8cb89f32e59e573326058bbbbbbebdc6bfdd6b975ceed4a192fac85b16a8687ac35be24912cb2eb2508cfc40769d6d2da9fe3f61e04ed0ae113e1dd922efdb68ac86930698a11b6098669b0bfbb389aebcfe28324132ee62dc730ad3b8f925c13039956f83827a4b5b54f93d16845f01bc3b7c94968453035822201e71026bd33e168606a1c8d4b907e97f4bdd9219319d394a8d0e2369c846680f6f7cbb24ab4a7d5381dd7dca6b44336d1b771488e6c08431f7af6fe84e18d7e830e9797efa6c1a4c68f31224b60a267199ddab349fe70755955d324b34b1dd7b57588e2b48b2aa74177b89bbcc392c059b9e813e8a25aeeb8946559f51a57512c59463d14caa88e6b9ce92daf6196ec2d6f09b1bc62a175877ba21476597de532f4ea8d8aab40151517e3ce32b1acbc92215a5b68a83acb1b1596ea9a6895a2cb5ba1299a15b442076bbada1c321cd061d75d91077d876764a5a88cd69e9c2a1a68565f99a06373ba9cd81b86e46a11add5f489f20f93a8bb368567b0bbb72e1a9531c8a0d24213f68651aac65d13f5660693e0bbe8c645ef0f19a2f4db20ed8e4c8fb7dcd5e81064a2338df28f44c2013c63dd9ad79475c86ad6aecab7cf216bb5756d54c755a922a505c3b046d716a55d6d694c619ab571fb03861f8c93d08a685f8ca28c462b02ca66c691d7d3046bd604d3d485a3a2958246e47437512a97aeb48c4a19dae800f4fe94218c5dd0ad09210882402090dbe186d32104c183ac2164ef923909b76e5d07cd9ab5e3521518b6460b612090f5ac3d03bdc681acebb8ce5ec3a159791a07f4ebfcd3184669dd79aeb59a35836e1dab391bc242ad3ee9a9eda7db43a1764ea31b88ca6377bf2627a1156155f3495adb3bd426258cc10eadbdf81deec99aa631c8318634c6404617a0f7078e3a26ba9319d3d7bdc64dfb6d17e527d74b8bf4b49cdaa3d5f5f4b606ab515bffa4d18dd6bd5becc6f5f4bee9efe7e9b8ef378baf9c4667998b94d933ebf1d5899e4d3813ad6dc7a7f7b01d530b36619f5ace21d05b40a0b3cc155a4534a32110365dd344ab45775e57b4f650b4cae6966b85e513e098038e9f7e95d3e8ea1eade8268633c4b0051c663411bd3f70486931d4d106e8fd11831a6dadf5ba365a5fcf9a754f8967dde5198d96806a45b7cb365dbf89a3536a795ee3c635e434b636bab4721aed1e3122920893d685492d2cd3dd9729be7477342d54b6cc1e3acb79457416d1ad49abb5426b4ba345b4e65c9dd11005d1f3d7d1baf330eaa68ae6e45cd44471aa9deb8c8677e724b404481a3f5d0d696d7f9493d08a705446c4b10b161418697dbd0bd360a0c3b730b21aa4f941648c1872dd3a1f896bcac7a475e1981813e38fc5dc84264634ce7b9c46d0800e4faa0e1f846942872f342b87f092694487cf78c63accb8e9e0e530249e89c7ce0fc233f265ba58533425c7c4670fcd202426c583e639263649af9d52e662a683b54f46b1693db29facb33e3a9daaceb3bed9c5a5dfe3f3a7ad661f9a291dd74c47471a4e118aa38f568dc5887f33c608f9f17b74c438c1085f8c7731b638302231c217a38330ceda13237cf0bdc7538370b00ee734cde5b9fd69e9e89a436a2929396b9cced4b2f27484d374175fc3d3f418217c53de74d0f10c7c5e3009bae8e4936ea916676059d6626fefeddb5db8fbe07b6b077e67e0f75a1cb3d101dffeed3e334879bc054f5981f537db19422fac36ddd5dadc8ead4dadb5f73dcfdae0dc26a515d1ce1891a5ae3d4e7ee3c60c679bf2412e5cb0ed0c2e3dc9d78c7aa5babd6d6bb4be5bb49a7e78decdb6824534bf2106cc020c3bd836f82f4c7a4146e20f41a2c6013cc37f69716129cc454bc0767bab61291bf5e6d2d5fabbae9c6eafdecea42deb357c92f5277fc7537b758bb6576f159386a7dcdffb42e676d4cd4273cb1a0fb2fae6ffe7b7d104eb3cf3a650e77d83d4eff07df1ba78535e0c9e17cf8bc73330f49886fad6ab1ff3abab39a6bee6d6fee3861d7a9a6ea696ce4d93a33a53579e76678cc8c470c2c3bff384bccc7f2b99666dda2d8b9d7beec9f7ce33ad8a86e74607848fea4ced1cbda6e4657a1eee99381681a23bdc7b9c27778baba9af175fa4404a0a9a175e50121ea1096434337ae7314fe12e780b96c25cf0941e9e025ff375d5a08dfe421c72df7b538eae392def1dc2a92b086b9cced4aff2c84fb5d9a1eb0ecbefa9bf16472be2418cc84e5f24db55343a98247b7ab3d1c1248aea4c3da728e723125f12cf65ef0a65f08118ae46061186592c3d16a4b28c85a5aaaaec22161616f8ac86b6217561d7c4c130a6588e5dd73562a10fe8e9eb3acb555915954d11ad21ecba300bb3308b8585e52db7ac6358666556c6f2d0ab4b5b9675abc75940976f59b95844df711e2bd7cac4b9debe437489aa4b5b59265ab945a104d12c1886b1d0510bf616ec2c9895652bb4e6dc124dead90cd10afaf5809e1e61c7b96e1dbb6efdb29649555551b43d21fb811894602d8c55b53164f1d39fb5486675b8722f90d1edd61743725d17cb8e10863dbb2e1008849de5baae1dc75a7478da77ccbaf27dd576c56a21cbaaaaca42b22b84ca42d764a1b5c50b5f7475e96733dd0b5df4f41752b3ac1eea22c2e3a053d39a9589b6288aa7afafbcba3465511645cd1ebfaae8f2a0a9c3d37accdaf21dcdd270aab7ef98d710b216ead2d4595ec32017ec156551af595bd42d5a4718bda17e559619fa3557683b9f9afbea883b925d2130cb28a53e8d9edc804e1dfbe849757351745f7ddf9c6051df571485d2832cbeb563d20c4716ef3ebb4da266e540c63a865dd7454712c342c7ce7384047491f56bd651f66d267dddddd151466f4054442f2947217a5351948fd16acd8a1d6bc956ba0acdcaa443b75a5eb3b6342ef2665708955d97129ba25f33741747d376632a45b31b63bde58b2161d22069eda89ebdc655335e34335a9d4befc056e808abb2e1ad4f67c16e71b7cc4ac4368b942d15c3d808e630eb75dfa0194247d7450fdd25fb8a73d5af63bb424474447d541dbb75f96cee0ab919511412796b44b91b5921ee327d49eab2424034c7a5a9cb6397e1218e8eae577777b1768554777484557444611f5d77c76875ea7b49eb486217bdd915e26edd541475b9a37524e98dcc0ad91b59210ca57593c14c60adb54a322b04ab4bc3927067be44e2ce971876e3ce77c1a6ddeb494f5bbfee2c1a8220bae5ee92ba3c4fc6b007f4740b1db1b0586e77b4833ea0a7a9965ba75a6ebdc5927ccd7ad58b56269d73372b939677a067b332e9ec2bb778bc669db918787755cac8baebae8976bf9868f71d6f99d5a55b8ecdcaa431d129cb7acd2c5a993475ac6566c7e6ca33472339c69d07a4d8af63d82f8c5626edbe838ad1e3355c650bad590fba1c13a2358e4544ab4b876675203abab0eaf13a3677f8a2f035ce715713677a3b35735cb4b84d199394eef2cbccaf5b18b23064bcc4e4688e4b1f812b2cd760b56d37222f3304defddd858ea0759cbfbb6d4b47d3f7f0d61c4d1fc9f3484a49779ae899d61c977674f48ee3fef838eb287ed76899dec35947713a4e3cfc44e321a46cacb16bf4fef11dd39eaa3d1ef8c0073de8814fbb5bcd07ed0719ff669dd0ed6fe376c1075df841b7f87e30ba69210306ddfe2894e69386555bb393f69d37bad9bbbb7dcf5935daed2eac9aea2364dbdac8ea3b32b523cdd8168c93a4667ddd1a1c2e3460d615422d84f86a6c64f56a90c3de05ad584d94bc5adea32d577d69fe0bbd98d4e48cecfa9e31895b98c4fdfa7a6dada4dea3dbc233eff5ee0c1378b6dc7b7474f30e0f697dfd68f342d6ded36c01c1da9d79776752757467de73ecaa544900bf2ef72ee33869bd55aaf4ee1ec1afbbbbcb805ea705841042a18edd7541484ac8c818718044c119dce8e535b850830493f66a18b142d68c36b6d0c6e38106e42b857dbdd15395dfd3141ddd54a74e51af2a8961971865a2665eb6c4a8c42427498d3069ff9e03608491ca3089dfbb7b94d49af9b1a16ba630dc3d587b6cf0f53ab9c97dd7b975c3d26aa477881a3b7386f4ee56a16685b4c00721cc340013851c985ff261216b87300d0689214c8a628841f697f715b287e93df7a4b16a5c61d5904f893da2b442da3748154c6a3454b89826630412350e30a2036b615a2e9797f3ccd6203b0c2948d6e2f222dfbec3c850c9a626984603469460a28d9e98e098e97b984ac6883d027b9b645ccc647d9aecd0d3a73fac7391b6284c9a445f268e46caac66222b5e3dad010e33be202376e1059d2cfa9814a940f8c0c8e0eb45471b5f78c15ce38515622b63c22456120593f82029535dd8cc59217c1947d3cee098e9d3f48683acb62d7a7add397aaa57dba24d695d30c9e119ead3a9981a3bf509fb747d9a2e9ab36d3446047b4d5dd35bce346b333d889cdb0b335161b06af6082a56c80a1f14c2b247c372aa8a708d95d95a033ce300238e46c60d59a3463a9a28ac2fae4974f792752947bfa0310734c0e430290a26d99460b003782608939a4bcd10921572691d84675a98e442729b6173db62994ca20023abcd67e9f079438bd2a4b42d4a73b4752a76aa888249ce10279468a009268c2891f12405c290f688a9bdf71ea504939e1126bd75a3dfa73face4577f724a17cd0b9ea968144cfac2fa76f577234f3ecba57a6ec8102f8c28c1441315cb3f09cf24181726f1abf94272345470ccfb6b34c8eaf6a1df7bed2f8a11257826fe9d89262a2a76968e7e54e80450304357ab4657d56b395f4ff1bd779846257ee1b9bfb8e42093cfc5882eecee5e0a59bb64d28b8b156ae2d2383bfa28ba4777eb874f1a1d51c7716fa73e9a3e596fb72a3a3dce3d356993fb96b371bc5973da18adbda1c11a3db98977871f3db989745708df3ceae8ae907582139ce05a8b628d3edaf7fd6b7fe2e848fea3781c78f748e1dd21efbbfba36ea24b97f9fcf63edb4fe65e21cd6963d44773368e76148a93edda1bfe32b5e8dc1ef13ec40a818f89bcbf2033efafdd5be3b8e7ba3ae7dca4019825e4c0f47b6c96bc943c0dd8efce39b71204b342f654644ad879f07d9aa6697ad1593ed8dd5d07e1bb92a7b1031bcaf7e282868b6f31c6e8761b115a88d0daf0f63d7e97e4130c013eae52aaa27db142f64c5c84e04245ce1aab86038c0c09c29086512861d25e26c84f1026ede3c896616d798f562b3a36a42d6734fc7b6f40c81a844d4a8b32650b26ed1eb1cda779d1ba0861c10c59db37dc60258c4882114a189141d68290d0f0de7b87f385278e7e5490d0a8b45896913aa4dd1b13b276f7217206d6e866e7f5d22898b4d54d3bafab373aba697474c36ff11cb1b6263aba919f3ed1fa7abaa44e5ee70c6999634453efe99eee690b5ed7e1459d60d2ce9a6a06518249774d3069e183940926b966c41cdd3cb7c456a9f2d38eca3069fb6a2e0d66b4dbf17db6c3343cbe2f4926bcf2bd11a61932939d4492634644538774936385b0c1a4761eb3a9c1317130a97dc76c6fb898f696d9da7031ed249e0902fabef9b4284cd3f27ddb8267a6b42ebc30e268380ca6542c8b45a6e5c3e099317866ba388c6bba688e4bfb61990d0d6bb63486642c194b1c6fb8c1b28246d6861b4c5342c3d128695bc8d1a2c031071a74ac51453663931ccd902abbd9e09a32eb72baaee9ba56c2a0820ba69c21d319d96c6498e1e2826166b4315a18a229c1c85c184ca34493e26864168e33d68d4646bbaf94763fdd5e9b1746a4a3696fe01877d7ecd0ee1668c26e73bc81b7e0198b26b1fd05cf8ce4b733394de982bde099898a5507cf341f071addb88b562bdaaa42ce9d1068baa66287695ed809172408c3c0b8b6a80b93626333b6d53291e018e7de6690d5d687766f7de099ba3fecc351ecc0333567eb68eb73bcc1c27eddba2e0cfb6247b034da70435bafe12b67d968cba28d0a935a1ba385d1be6052c392685e4ce982cd10b188093acd0826359a1a17d38cfc4c1384635a0bd34867441019be74342d1c69ab412663c410240e430ac2332d3c23efeed2d2aefaebc5a88ae65c2d179394c4a433a9bdf9609c6c1c87b5339bfeda0ac537c8d17b0d304d0ec7c4ef99688267aeef9de099e700a651c2fb21f1d27a46ea35f62e4c63c4c5c4dad27b8983ccbd5405936e90d516a5e3a73f6cf57858d124b6f990b62e9814a918587c158ee6714cbc94d1d1ead2f475ac430d6ff8619ac4a6c13323feb66412e49fc133fc860634e3c93c0946f65e9f1ce2e8986344f323d37d13dcd275d7f0a2baae21cdbfb89ae8ce6b668249ac841126f187381a99f8241819df88a3418263f8510d92a3696a704c7cbb4156db171d5fb78e8e6f63f04c7ded0b9ea1124615f2917a7c8bc23335a7cdd1d7d5e0998b67a81a1d9bd5b02391083360a3af9c46a5afd73ef1970bd3ac4d3c4c6b63e24a05efbdf7de7bcdd16d79686664ed2f4cdabbc804e12d2c8470dd7b2dd9c5695cbd5530a25726480f5a1959fbbe29a6afa8a4a9156a84a19e441d5308cdd048830002831400303824148b4703f24819661f14000faeba544c1ac9b32087520819638821840003044060406486004d00be28462c28a2f6a0a610a761374be6040d6f85fe866efb38d9846c6f50a36c3692030ab77989c490c3d599539b2367acc9ee78376b35db38b11f3022bbcdd0d7371409efbbfcaa7d7d4aa3cbc64767915124db2f65f80c2bb40bea2112f58f71c45e2e805c403c15d3ba53e0302e8b98cc80089a97357470cc6359cf2b9e930a19a80380e685fd157e9b0c09ee32fcc33f1a0f73a7d4af0b54ceb009a198fbe0d7b56861c41cf61f65c766392069e1e56347be940fbf7191528e2624cf961e68560d4ac7e3cab8a0fc85a6699c85163b49863afb2d237e88c969a0526caf548b6e6598a88febded86d82eb69726906f4164d977e7397ca73095c66b059d3be2184ee95252ab7ca778b1cc98bee6a6dbf182c09821e54d08a520739d4b8d497612089ed27268ce59a7c4ea418c92521f41cf6e8defcd4bda5ee6a56fe3b3228275a53e6c912dd4dddc6cb68bbaa85f938d7efa586396f292b728524e5444b5fcedac8e5a053b054f421a9689cfcd19c0d9060bc4042de8229a1834f68b0b20d270dc2f696f3053c8419fe694159a5ec96f472babff7eae48d67e79bd6ab504b252a980657eeb701e75ac260129e422bfc5b5d795c61c3b47c84051684ff4d4a8cb8fc8870451645510ee0eafc80d78f9556ed2d934c415b443306799a6e9303cc1e1ce0f15188cf2d02d79c40c8e1641bac5472b63fb533a8c9391570530937f16acae7cdc17a997d642369ae3bcb7249136fcd8452d933caac278bd2161939a3fa4d8d34a4a02a0303b272d4a1e9f2b810639149e285566d19e53fd0d4b34bb83a010d2dc035aa3b94a6d7a4d42ae83c0f996297b2f44e76d8f6c69e4b24419256c156d03a1907b109bc6b3c2308340b25f0529b39a74bf68a6548b908ecfaebfbb7d5797eaa250c660903c1efd81b20f8dab7a641ef7ff007ee7a493e35b8849ea7fbdac1cc1e8805256422976286a5db42a09666700d75b3721f046b5669d0a29000aab575ab53e0d24756791bc920fe29cb1441b907cb92f278622194cac86679a20531233065d7be23b010b5fdc8ba63bef52f141d02c25b7cccad8191482b5f0622e583660188ead0dc16a2db4afdfd2954edba293ad83960e91f8cb6ba11d472b4c3f226cd7577d80e053d4fcc4070151733a5496c8d7b57c701665a2b415a6434e4761ebd9e61383c6e0142d8d1c2da054e04ebb0903b0bd95d402303d5129265f7b6669a462e24784c71db10f4d05847632a521f0ab10937ec2c543bc84d3c224836e5f2945566db65e5c035807848eed0c147fcbb2cb6c093f7b8a6f592f7c625aaac6e12db762a45483d5a5e711fecf58acc63074eaf798483e335a0e6243f2a075d23494b1e8e7a49af6e7836a17a98547bcef6ececce792a8d4d6574903199036bb7458960ea0c0ee465d582feae412c6632cfb2789bf44372fbcf7b8be77f0809b0d0ac9718e165ea3eb3cde224b7a127c63d604a3eb76533d07356409bf48dfe3512e439252b46fe3ba3564534163fba63187621855e992f6487c3e3e72f2e76183372e6a274671235c8e257b26091e6f7b1cfada501d999ba3fe4dc875fc39e8773311986e81b461ddf074667f824a808d9f5e55bfef178a84586bf636977d0cf0e02cb1224bcf014ab91d88c2304955a41cec2f470619f81828a585b6daeaf88288212ec1badbd517540205a735443d8c4c0b2ae3709c68ce193774ad68bad8686a39027238987a249871c8c8ebab66d63339f43ae325f6dad06e447ac0fb7fd1c6a5ff2cb39e08a35bd4d49db863bd2f00240cb9a5e0a9318e3c841f928265a7048e5b10b112ba49c92e2994b44991e1c6cdd7a5dafd3ed44ccd3ef49f4fe410563d50485ca001fbcdd313644af4d6701d5682f7aca318931f4cdb4df21240525b878aba868fb054a1982e095efbccca34474ff2c2f6c2d801a3ad3b87d3e1a7c3780332482e8ed10076b4093caf0f95a321257078cb2ba82162367df5c44dff36d123294a234c39d92d2e99bcad214e314e399dd67e1f847ef7684d59533bdf5ea63da4b7b7dbcf5613e2a39df6454e4acc7a145be830123fa57bd44301f8b1c24b39b54881fdb5ca0ee1a69ef850deb1600fb8684fecbe8c9f5b3b843fad4eb0152b4530baf98a56e9b18a1f67d8b2e888aa7ac03a392b5b6fdb9dd76125cd0cda3cf40d12fd21a3dbc6723b0e8c8adfc1e5b2bf4dd54e27a0052bfa80b546dc0074562bfab87367123de85ae43100a973b955b6c40ea2d8aa4ac21c3099b91b99497e7939e0756a0ba2b386e9962f5116819dadc26b9496cd2a444e2b51f8c6eee09ffffbaa457c408c5f46bf6782eaceeb7b40a5444531a9c40af7d2e3839e5f07d034a2e7a19fab0788819eab059726e0568971eaee84bb88a52bb678bc8106614e8f00979fbdafada57eb1bdca04f8811b863a60e027b7601731757bdb69a177bc026bd0f9ff5b0e7e80b3157822183103e41d5c036c110d5f8ff410138959cb060ba3d27cd541a25cc5277df2a752f231c173bf64388e7f24ef06a37fe5a16a9761e7a02b10fdfac435ba9b731c6293ac87b3e325e2c77d7d20b1a5a4392aefaf1d2e4b62afcf193281ea2b0aced940cc3c631e70064c1e3787dbc123b3284a7c2045767614832dc079cbc9f5649990394213d6e8563dbe98072834b15c545560335d15c778b0c10e53a95d612fbdeb953d438ef64a9eefa1581b368a1d9dc4ba4a59ad2c3c83032974c0b1221cf5e21c6ba2fcb6bc33bad49add125f3dde2cf35806d7ab8cf745d2c4ec6e8ffa1a04a653a77861561803e5717d2e61a84d2e7ecf615584f4ed91b5a5d3464b3b3fc8d97b955bd29152ce759c9dcad4f6e41ef0550fabdf0d743a3ba33a436d911fc8ce92d83c1fcd1bd2c155bf4556d83eaad1d46bf1172360e8b87b523d23d2aa4f1c630c0b778710f044df25b72ce10d39e2e46b3ed11583fbc3b08b341370c351ccb6c06381ee5885abd3635d765d2d3d4e3d53602e878c4dc13df8b89740f741f6cbf3931c430b43dc2501458de23406396dd26260678eb80cb3c0d4682a66e61b8da9e80512f7095da522fd66df587a8a00d9e293c9889646d66db31d8c089595c976d0b5d17285eb246733595c21d50a20ef19343aba5649c2d51fe8492e96c100da53290aec36eb23b2fa0e7e737e4871c40441cb5e92d190d6820d482a87b065b1e37692df7504e7735d0e95ebf56b4e4b706f144ba874b03036c3b74c3cb64e11887a7cebedb432e0f8ec50e35251d64769dacb5c20ce737dbdbaef867ae501afe450161ae070fa8b7d517537de560e8f12dce55879f572dbb8f8686dad51149b0d96e799ef7fd0d871bcb2374f0ec1be491c1ff7313f1f9e7a2892a006ea12aa4f5a26350bea1de6323fe0dbc2c9910f71931201ac9db4d56df67d88d61fbf56b47623ac7b9202126b51ae56236585ca0a894c49d0bbdb4d81c3b61144565a1455b3b6599aae3e85cdfab13abe786399667700f9b8ed9a85e8e581ed7a892aadfb4ea11682f4d77ea89c5dd36859f9eef514c9a5f8aa3d672a9fd5c3bcbf61512c714ce9ea82a6e77b4c47c7bf4c1f7e224019f274ebd0aeacb71de7ba8d587b110d711d476aba402dc0389a3e4131173dcc005f41abafc1f5ab4ced996b42d11e5a976c73f24d8976fd761f3c279a5b936ddffa46d954f0fb2bec88faeb292f9de3e73d3a90dd3959365a1a4d6ebbbca12ed674d0cf1f4dca4c15e8c230267557e7e47a0d0130cdc76215413e7e4d12b5e55d0893ab406d5e1b2f72894b223c9a3f45e9fde1ed2ea4af01400e19f24e571dce0545114a60901fa2a73a22c4b791894bfb0dc3ea767072f0cb5075a244e19bbb47f33059c9374b6e3e0ca0064a274b18ac4fb6146a3d418c6035aade5433b53d4fdb8224549646b1a0b6be44cf03ace5612ed4218b0d42a0e771588a6ebd5d0088476113b0da970c57ceb1510f6204c934cfeab38db4f725012589d434e0aca54473bf52f1887e145b4f00207459da412b5e359802622c74355f245d0d95f0f745718875ee35f9745b6879bc30bfe29276fa63bf76cf0e65fff01b4f177de957439b98c2ef8812b3cacfdf71503ba5dfaacdbc3b2dc95715ceb06064c67cf2f6eedcfa59b5a0be27bbb082d305dcd1212ae5fd952b8a929e0fb25e81e53b5db52d181669f40dcd966dac808b555dce81e26d5e3f654f4ccca819720ee56ccc030225c157b960e04600631ab269429decb3673ff253d4ef5a4f6fd9047d0ba5b7202d237b57d85142174df550602d646110c212d57e5ee87de13b0f9340a80bef77b0167aac9aae6db7588758248c5c54aa071acf74212dbb1e0a592cff58f12f6492599ba6873c0475197030d2513f80da0c9196e679cf9891e4765c37abf1dde8bc2fa3d8749653fa39342f29232cf4a5e7adac47e08a87079ca8a3db81d41e6fa0133d63ebca5ed945f00c75677e739ebae6e127b0e4385190ef009b1416f5e8686e09443c603418344659433c0625baac6b2b5024812c3895d9f8aabf6b829994c3d5248243e003cf3101f7c83238c9d82dfb3b56c8dfe0e6782bba1a639c0ef47b2a2477e4b7906ea9fdda1dfa16a7830360776dec2e0e7897d113124c531a6f103f329b2908d92f9cbc248ee91f788234a0247e170ac6baeb72089f2ef35294509b1fd620c757bd259fe0f5cfde5101799349f7b0b915b48e4baa169fb2f7b860898694a5acb13c7cddb2de4cdbd952c8973d97fb631b7177403147f0cfaf008ebd4721c8277113a79cd3161f49f36106c6538d0a4850c7dd487b2d7cbf29de0a05fe0721bb54bed2588223d76cb889e8f2087fa12278de7c33fcc9db14003f8b88735749a11eec0e364707ed5bb7400246e03f7540ffadd079052001845f50aa64e20d9389067d28fc1287a7e0d02f6a2a26c0dc9ac3459e87085ae62bd9f5516ea61fa963adedc84e19f3a7dc8aec6e22d2ec8655ca8c800a5bc8378fafab729b8f9a1fc2e28651c8ba75bb8b4cd8f5b7164e4d3425433b73c094888b4fd4c82b11386571b1732effe3985bb076c7a37acb70c51a91b6c40ef5e60bd1bb1746df0db59ddfb5980def8f49bdc8e115fc08a092357cb6dd58ad1fb596c264d446ec2978904d92caf4acaa223b2aa3c7f5fec2856a9d50b1ce7dbb18bcdf45b25f1d06be13d928b189320e315eaa2c29bdc859bc9d662f56e017d3368e4cb564efe4f907019e450d2a1d84fc5ea02d80af785221da2c558b5d6e141f62ea4f620a4086ed978502eb69516331cf65ea82db57ec934c1cf433682b01ed2f0562424c8dd033fbf686dd869e8cd952e919a97a76b554afa9b83f8458cfb35e0e0fed849a458c758e3530d0e6ef6a3e26ccd9c658e0dcbbb26e558e34ba07b926e43f921fd9036c20a54fa62d144bf74c235577e15b2b837fa12f1d3e2bce84a5d5b460c59edac42f895082d184e6c7dae0c2a009e4bffce30608a2c3a0727b7e1aa496cca9b965cde9bfe81fdc4729da3c1b7fe568a074d1e340178d3defd226da3c8dffa2b4cc7a101739494da9b448f8f9aed684f742ea8f155e4d9cd6993a2f65613e31f58ac60679a451438e7dfdff992ee05bcb235da53ac0cd0cd35af227e57116dc901428141e19515b88adb6f71f3e980144ed98f374475e734e91d3e7d0fc51e852b21d65fec478e492bec9f84482fb14bde37a4d0b3809317397a49ee483d781c3483d03bbb8c79830fbc468d15084dcfb15b640c217cfb2f5d52bc32f508229bf535c1f6b4733da196ca8455540c226e43f0d064c6832b3360d022c42537ab69ba8415732f57d1500386b42350546cd5848cbe0c0769ba517208fc690fa3e05fa4bd21664d7b487dc9711360af1eaffd289016290bae5461d622703aacebd57da603160ba36fc0c3213b4b659698fc1737421f4c5cff24a5fd2bcda83f230e894e57c1a4eef7b33f9401c4d8850c68ae6c7bfd20accaa85300bc6799cf1023c4c4d86cca4678eb9cd2a16595f4a07ffcf069926f57454629213d7ce4a5786ee355cac2c3ca25b9102c62aecd9fde9405d94d5502fc12fb56e14fb4c4002f97fdb1840137b5d77593389ccd5007a19617fe268b736aec480f83069c8c4f3eb55f258f7437a645362f16e41d970e4fc929775687ff02e8202cd26824cce56d398939df8886d4cab5e3f4f74e262ccb006af5d1ed55d6fa2baa4b53f521be3a336e72326e1f87fb16ba07a008b7d531cd970468d58f2690475cbd2a923e668a11ab1067027e52fab45a9065951d7013c8b661c09267adfc54f5679009969c5680bf01d4a9a6ca85ed40c0c5c38d9582d8e612d6562c0889f31cc2dd51f357bb90106a1888418caada8a99886b033fadd09a63aa976d0a067054c7427547bca61239482559ec64bb7496e47a4454302a97e3840453eb5c28d54ce84f309221152e2383e18be3ec6a8069c44a1d2abd9edd389b94ea94eabc785dd6c7f6653eeb6ef7553820d266abcc77c26cc4cb041fa4b72e36084b76f69862700b050deb657dd09d333aa7bcae81855f42b3bd145a1956bd026ef792af1365f2b15baba052119e1d26fde4a1d65aa2ee884d32794fe5d6625a10eaf3626676483c6081b49df0958e4b26133a3db874fcde69fc007512afb86c64e24946eb208e56f25a740208239aa58e16f013db500d44ef243f2173bf76e3f08517a795083457d4df03f96c6e8ae4900f7d4df78fe260ad07d437d92108487dd9bc449c8c79c36331658e3d6f9461ed07a3fce6cc0ab03e53dd4429473b943eb722374e690878340c31704f152f8d31f2a0f602637dec39816236304370e423f6d0411e2e0164f64e00a12fe748369d7d4dbee3f5d65d1c9bbd7cab55718f29808671a4ebd89c33148a5d03f7c4e2c1dafb088d8daa3d86ef68294f2830548d001b72dec9d07f8f7a208451dc2c71a7c7748805ac58902dbdfc7e1413d2382ceaa0f643fb5d8079d4a39005ea75b65b6e3301e0a06df132af3e3e95a56007e6239be1300b84277ff6f7a98ddd03346abffcadaafdfe1f66c0d5ab580c85ca50bf6cb50f904a067e4f4c808dec6a8d539fc25800ce111b331d37d55eeb2d44b5168a9b037380a50033be796d4985d10c46deac0bfdbd996947ff7996b180250f25eac3556792632f41f32b7a0654735be6b88e928e2189cdd5f458aa6949bd1ed06b0bfc7097bd12c5bc85b1beae6647a9dd96237125423326052c16e9677e2d6666a77fcc7bb65540d48307c1eb48bd83f4ab5cfcd0f98c8d3bf75d77ef047d25b83de010f4ce739f2329eed5d6eece6fac4e7828cd69a2486afddffbc6879f11c391b6b1fe654ee81e1f82e89214bf736248a34b957d1640d51b031981c45f8a7a7f756fae476d6bfb918f2d773f6fc8d24963ab637164bb8183079c3ca3079cc0aa7714ba401f4da2b3e2505358d8675874008a03868ae2550b5590d9238748c82cc8f4c97e4c60390bd1109a89023aa89a912b964fc60053d417adcee7518682345f0d520cdaf0bd12d73f367a31fc796bdc674a84af99be2f6b921e9739696539d5a03cbd13806d52f559e81b06447b004dfc8aa307c650549dc397e4db9d8db3706923a2ee6c21ee500ea222dc6c1cb691ad0b24f49748d21f2b8bb33b40f1f95918563f4ed69275e527735d356cdf6db7c5e13523826f2428e22ff2720edf902f3f4db944834b546f514217b74cacffded8c5e0321368ad4e65a3d3e92a92b4afe6a0e8591f88dd430b15569ee28f2597a1735829c0f4ebb97deea0730260763d5a7107641e5afefffefa484b56c50747b648c875685de1e40761c93c8810205635bba8e3e2250a1c54c610eeeb9a7bc0bc2a6efae701b474de72c2481ff3a0095f2461b0fe5927694252b8b51c4b1d85c5aa5a5a0d428bf6fa6911a873c0845cd45c49ed9bb1487009faf1c145172b6b941772bad782a969973a65866a9adf4b3cfce75e4e68ed30e66d079b121a201586b9563bb53afb2916aadd268b6636d4445b12b93213f6f75ad115d0f4991b1d9d42845da4530c66aec35a1096e5113982bd8f50f8b459c4645569fbae39f8fe8189b096fbe40036214336db5b7326251cc2c00aa5e5ea871ea65acc2a15680753280a4e2d737a8d61c6fc4598b35364c569eb62d7af4369310714ba5416fd5f29a0b953789efe8cb4baab9744e6f2d62fc6b96da6e9173842dc72af3d8452b91825061f87bd8be2536f321d0c0a7628a331381e85aa0ac9ae84920f1da55abb8f9cf33247f8779be37e97bd98fb6704ad788db189e6433b2f0ec40d395ea551b0db180562bf1d8a4ea45f22984ef0dda1feb43b14bb5c10e9643c1de9e082d719b9a04a9ce9f01d62c480dc8201fe908ce9759a08296b2c88afc2c993fe5a87924431be91bc10d5e28e3af08e47c3b4c4fc4129dfb88c3f337184f47e63a27a5bd71210ce8742eb8f4ef79077a89ea81ba0fa5e164dca552dcba5ed8bd00d47478d0377eaa5e21e493f72e0dd7916e1f2a0c97bcd7eb4fc47f71bffe1f586fb70b9711f2e37fdc70b6eea164fbf34a3fcb00d023a22e30e44331debe9a043509ab55cdf68a9e5be1def914fce98cf3757a8cc1c76e2afd4fd87817c5309a71fad6fd2e3d05760deef776a654f4bb7c6f2484e2a0eda2615c485d1de103bdeac81d9d3cc8cfdbb3e4e0cdac75c5a343cbcaae22b3e29990d00a7d836a10d4227da67d0fb2b3ce64f8d6d867a7b75b2c3ee030bac10aa1e1b7a0126458fc7a762807c29391372da839c65178b1ac87260499136029a1e0e6a487a5b7de1d704f19234c18d38bb3289b1061537e4be1616bea1a29bff736cb0891d6c4b0f9e051ef4b0641f30de07c52449fc90569ee5dffdb4876473ea4db27a74b83861d0ea589498d431b43258f999de05e4c8507441ba68858a12e98f787f9c1cd13adf444cc44bb3bb64d38be8f64d9235a29f209ea20a9cd48bf850ee167c9b865466dfb9d7626077c9a2c230e12d01b16aeafb7af3acc67b67791387bc53f8ac7048e73a28e0774b8d4a2cf1e226a393c60bdb9487e5e4280cfec435852937d5ad8cbf0f763defe1b29050dffec6372b6cf831d0d88c1ee884608fea9254cfed8694454fedadd7b66773d4616b3fadf6646edf3487ce2de0b2faed810c602941b03d7a14152aa0b509de63a4dc289222e9ec8f025f2a962e69c5701b8aaf3a1fecc16f7834f9602a3ef0485e667a158e028c5aac567126b197c4cffa961373fafc2944ab48c8fab98748b1c03bc078a44728129ff523719d7ac954c20c01f236efd19fb10ab0e2c79189f32240457b00096e18f7a17ce8a40419c0c9acc79cf021955f167c52ae0b5c49b653b4ae71ef3535901121402ee0b4fce0fc8ca73b1bb7c5b56762e87185b417b7bc0e53219831028aecf61570242ea7ed62d5119f6223dd81eaca5674b316b44ea9e91cac5a1ad4d4a51e973e2fc7dc00fda240674cd3d8da9bfd758ca6217895f074c200fd150286d7e162588e570aa812617a55490f0cbacca5fbbd6afb7892cce455fbe78654b34c2fa8a80bfdea8214385e351be8dcdcb85093b4833ad7a836fd9973bfaae5afce82d230c2d53b4e0929fcb739393a6f5c6ee43a691f120dcaf3976a04d3599952d53a98344081fe5fbca625f5f5f347bced397b1242a3b1b701c056d68d4e150220fb766b959f180df32359065757c5fdcfa58354e46d246dc48550598d513d149f88cb42f869d59370c6216e78f3c790b3715756381066d7af3f7cc3e5dcb808aa999575fae0ec122aac96241998227d07b565e69811c9cae263a1f1c5cfb1b71a78cadea4098455d7ce1c637e2bb876e20043cc4fe6cc802b891548b3c414cf16f3ccea62953a15ccef0552fe486d2f2cf3b0baaac5faa6b7c64c23fb41a59fe3906b87ef6019d2978d344e8853aca69af1059c24414c533fcfea3b0190ae626e46bb94298b9c369be68811a790475a712d02b3b920265ec85a99520661ebf25238828f103dd6dee50c494b1b6e9d6eca28e27e7a65d99d0427877ac8b9f8a5a005fe4d25d354448876f31d346375847cf2becf06498f15d07e686b7cec45ffc59b1e59837322bb23a1c623588e9c8ef819f408fb227972c3f98e4a18dc20511e9558c92d0591c118c1dcd846b838b6f28275cab004cf27eb419b52ddc25acfe26979dc167b7fddb2a659f5c66b51a7951ffdb17b8d77aa39e77a81d505cf53a2d11bfa928a6c4f9015c014a8f5c1d2eb123dec322e76f1cbaee3ec227bb42b40ed626fbbb475bbdea02f6c79335af09cd32b3ca318fba8acab168d2f1738f2f965fe68a1309b1e93fbe3348224b6a0bbc1ca6ca75c669386e1e0ae4b374bcd569d134c269847eeaffc355b641322cf333a6b3648bc39171b31c134dbddb4ef6469e49b31643a38b344f86fd37c90039df5576a6d1fd5e6a0c3b425f9311f4c6409195417d9eafdc8ae8a67405a887d5ce651bb8bc473b72337484c27bcc2e81a88d4207dd24c5eb0bd61e52fa437f214b23aa19bfa742389c5c03ff0d26c767b0a8f4563d589e298ddec5473492c79bd8c9d7e48a104dcd22b9026394ebfa20cfa0347897c30af67d2d60a45427ca4d55c98c92464a7fbc0bc4e4d33f83e4180a494a04ace19559549b398d02e8990293a644424d6c97c7ef6b12ab56138a4dc15ae618bb35da4a7383456d9f8329d12514f0e113cb44cfcea348c3dc8574dc7b248b31c87298885a850bd292dacfad67df25dbbf1290b3d02900ad5149c8225699a4083e972c60552088c0af0b569a3b82303023d6492570b8f49843b2edfbcbb55823875725d5bcf4ffa93ce225712f8801622d1976044cd27fd09375609f5fadfe4c409b37962af0d0750272858fbd430fbd7f6c47e8ddca6a8dc8c12b365d997896555f681747da62f3cd954c16ebc92bc5984c3f583597a313d4186171c6062ee7a24950da5de2cb2e788dda794f28bd978c93172a9086972ca6919f67bf37cdab7c7901f19e08736cc227c0b9a3c2113d8ec084884241d3b319b0c91aed67c7dd9a0404f48d5b50003ee882d1b2be381ea96a57860417d1fd7c8158d43ff5378d2826a4f4719433bd46b742ed7d9c30cf2649657a9e07865a668100380e19fe795ae6e05e20db67d658ec404d8efb3314e7a85e43355ab21963c0d16e1834e01429a28b8ef07e00e5124764ae4409dd4179f476e27123a501589ce480ffc00312ac68734f9661d03e5479933966bce2dcea73d77e14e2ac519513c630b770800e59eec6631c0fd07015937334254dbff9ef00a282aae2d03599c546d1551409fa6605a19c9a45c70a03b80b7390901e339a79bdfe622eb0e85bdcff4b2f74600dbe84e814dca7c0364bd2c0c82bd0268660239b6300a2253068ed10d376f23617caa26335aa5ea538d1be1f4d3c346517eae52fb4f5544975413d7f91e83a91193aa95267fdc10072dbdfece422fa8c5087f2f78c1fa4b637d2d26270c49fd1a44a09666a7c35051ff63bc4bfec6f60d6caa05df9aa65d8d2a2a4a7e425187751d9c7efbfbdc1d61891ee73b3872a5023ce7014f7f43283354cfc5b76d385e41b035e044c63e2cc9b0ae994d9a48bac691d95a2123c178f4efb5495fe317614e46aa5fa2de0dcc01acd4bca469913d7ff8d6dae4aff9d9b63a8481b5cda085089800e06619a4c775da4126d345ab50f3a115f7719e4103ec40fafea8445f17b9964909b8dd6e2c63b370b2f3d36a0e4430e81ca5508a99fa572adfd5d820f53df0e7078f3aeba494214f695cc36e7bf5b150e5ec193da042046309ad59fbb63d5087197faf29017e8d9b2147c6ba16bfbfcbcf25859258605cb693473fe35175802139c2b4e7501f645daa2346b989e2cff817f11c7ebe846b49b63ff01631dc983d44525dd9f243388029a3efc58d12b9f39a3b090d75e4232462a7187051353c5552e5d76063d39a8c5394178c7ef17c33d7400b0c34b4fd63115a318d8baba2d6fc2e08226f4e361ab16b3e48b11e18a66788621bfd383b38023ad68727b3a42928cd075fed55892ddb8c4b0dd1cc6ad2d90403a44eb338259fc5a152a0eb57872b97d1b2f44c2f39d64525f5223187e448957bdcbba9f5c9c16d7f3cee02707a95e54573bd0b184629335f1025417dc16e9adfea8386f1c20095a347866635b6b47150b41af8060a4d04c216057a00fec5c2e4d53dff94a4b1dab02ae8937783b1e3ce3e5528533b74e298b6dc07d1fa454200ddef5e7e50294a2cb120865e86dff16e7367a4d89543babbc71ae43cc667561cdd1363aed905b13abda7930d619718cba8f0908ab08779a65dae4c32cb5ed127bfd16bf29bdc8170eaea5528d37b383d176959232c329c5f64bb479d61be3816704cdfa97f9553dda0b7dfe6f1af6b3165d5b0b05d158ff406b1d5016a9f433bd6b4548004ee8b4a710eac699d580a1b6f1c2944a0c41bb4b6bc3b0ba9554e8b1f7a40c92771a49a1691d26aba67051bf0b2379d94a0477ecfcfb94398336af9b8fecd3d5247999b48350d6a41183f094ff59f7a3049511285f7bbfcfc08342bf03c7bb4f90b3655ba1bbc196ac355af1f71ea84972e988d1e884b9b3f5fcc47274e02259ef34861c9eace6cef57aab32f8fec68145abe4326136d272187bb8842f66c929bb5b6ef78a026247239adfcac8091cd60fb28d799343b56927c26a1bd2634919dac5b4364ffa421614c29d52a104012407ee5e883307c12123fcadc6f59853ff80ba68ca5ab8ed49d139b5d29e59c17629c22142c4728587cccdcd6279a2020977b1096794861d3fa3f3c204493a07a6b776378cd7f369e5e6446512be9cae7224cfe0e7c930d09693873edab5f9d2487afd25e9ff85cb068faabcb797be67f7df30969b1c16dcd5440c97984f72c26536ec099ece4361e8c4e89efb2bfac73308238add8c049a1b23810a2b0e00597f72a6b2b325bc280c7953799f179418a498bb95785f7f0961d51e082da618a072d86ebbe55e80ec31c7c1ed23375bcbe5fa298d3a9d9e18f6d95b151fa046c90df76453b8e287f908c4892b8837275102377f74ab56d16705b0bb450745115b739eac193d7fd2a2c0a042c54d02836501733c8662c1408660aa83bc3f4e2799044110a931db2c478b772516eb5126d17946f78cc74b03cd0252ba1dfc3ffc7f03aabb05a73c52059bcefa01dd77ab4d2f85ece22cca8be675b3ffe736cec4ca0e9d9e47b2cbea3e1b2878544ce8aa0fccfb131547bc98f874947c189f4615fb38c13ff66942f0cb1e94b684c4637ddeff7b2b11c7a2b756caae7ff9e4b347a089018c5620edbfacfcf6e00100b086a630b74edfdb07fd9317a0ae835e8740295520de2cabdcfe79126d13b14de6ed41fd5867c44f2234629e45ae4f14174f10d4205967c529f0b9cfd89482e33374e36b8b281e4bd76dc6720d9835c138516af682bb001f3060f5eed70d4ecf3dace026d437eaf71b2a2930308a3c9eb85c2a5d4e4634cbe4b10462dfe10fc0be7fa91c1126b9c21c60924a50445c6af50d50b285ddb1bcb9e631c9440c98f8a4d826d2890af93466234ced822546fa57d8c5a08af9a9058069fdab474b2040b3f32ffce6ba83234e2a727141b3fefb938baad8875b4d7085474495681eb7b00ecc2dc8d754904158933ca4fbda69375d89518e01867b32ac16d93aaa973df754afbe9e447e87d253423d626091e5cf1bb7168dfa5a07cc070165a03ce52c97c6bc3128747b24f326bea753eecb55a0f0a45062d10022e5246847d6a86c952049424aabf170b8cedc093395e07eed93671de2740e68b372ac35a63c95877290f7b8922db9ddc5204be9291d0be75ed35e6f961bec1987dfb902c5a5b91e1f1ff8d9c39394288d30990cd0c448f59e8b1c0e54959048f263541473c64ac2860be3ec04895d7c21418b9176c77d61d8cca2badf3dd62bdff66a59e06265eec45ef92b2af94ae71b4f1351c0d908bf626bb96d57f9c2a0c9d1e34ecf26aa344f163c8951db4113fa2339f5c0222677e7a8b98ae9998cacf922157144235544d7a445cec180b3ac156f356a7b65825141ebc799a5699c03d9206e83ca7560163f88807b336bfcd3c0048f7396c35b6f448cd7c730c735b595b49cf7981fe58a0121a62ea277a3e6ed70363586bf89605b155689582ddfd8e2dab05e742b3524411220410a2d620a3c378fd853c9a24258ea2ffee3bc7149e8683f779e3767751a91e514f9e60173ea2290a1bf855f50ab9a860e324f1f99a930790a114cecc0d058a84b79039e1d94c7da0e375f26ba03c4e2dd0212844413dd037d168fae7c3b2c1d2dbade33f889f47d1bd64be25a199173261f3a5ad1772168ddb16d9b7c400f49bf916822d6014093db6ce9c451b07d33a2d246bc248d1bfc25577706916b607703e5783d28cf51a8f36c8230a3a8b5248091aa40113543909b6042a1dd9a29ad1e5c223aee4380bc26959de1c974ced229da1fea6ea318f5847d33b9b79c87a0e89f503448125a518580df6a101e01e9d10f0e055f1119da1f1d3362957aa56b8a652311ad191085aec3e03ade7e2c7e8d54798280c061271206a3d5e5bc3bc05da8f912bce7e3fe0486c4962cbc11a9465918fce6c4615af8174513ec22df96fda54106d38ae7dc8c7f8003ca6c3d97573608835f1b3070c82b235cb202f533569368880ca6b436d829b81722801779edeb622c101f0ddd48df58b3a5abe685b1096e417c8b5a835068c4ef417b29ab122a069e76b110cdfebe0150b10be3e11ae6c9c83e4446a01e605fccd34e8a0070710b5fff3c6edb8a02df18f0fcb895fb118ef021b8a8cc29865df9e5dc77d397d6df3e0a1c39aa833aece9ec0f64b7d06672a946f6900554bce4f5033a62902c490b6b326700830ad04256e11515fad7f3045be7b9634278857b8c6894b3def989e6e192d8b779665d23ece511f0f6c08ba9b18676615d3ccdf522e42108bf60a6f5a40f6dd5df63b4608d7ec5f7b40e64eac6d80de60dc6bcf0d338b07fe88845fa7597c653717aba0379f9bf8edec9c7a0b120ce562b57a4ae8fc88481f22d1b4455d30bdf8328dd7bab2cb6dd6a53968ba3d4ecbd76fe41a33de94c5a880d7a71ab93b8694090494d7056298cfdf6e1b0cf73b650d7fc4effcd01b52944360f547612842bb907aba6e5eb09328fc6c9fabd033ba518fb18f1e1340e04e3d1f8305183d345f44b52aab0534ce85ec1d37401c827509892e1c08e87c219c73c750644afc00f19291211a41f6d1ff00aa869b872f90427311844ed23c533ceaa4509de78ef0d2ac952fba9a73941430c2ebc3f737df713236ca892696450deafbb26badba9490ae7c5d78910f11ce0c1f3c26f85a6c6fa9ecfc9cd86e2633b74044e2d664415dfeb11d82a9478a47446ef050d527ee64b4db60ac4ddeae918b6e15c814398b2f656750f6beb417ea04610af63c8229862f9fe542ea9fd5aa5b8d73f55766e71a1dfadcd3c3a910ea71f1bed12c96f25148b695fb27af31d86f57c8c63b9d67ad25c06a7738bc637e829e31da1c41885b1653502b66bedcaca0394b3f43d0c275a908a72591b698b5f1d62300a0de3879f2e766b03aef792fe9a0d20e56e98121e9e1033283ca3dc07630093d6f44e8fdea382b353763314d489a51c51edede03ada9a04ab6bbeba349c96e3ea07594c636c61c82d68be3020259b87449a71a34c8d2628682a69948b20d21005b89e0670b4cf04ea0c22be2628f8a28591c3b0b54a51481906951c8e907511aa7baccb66dd580664c2aeb7de9bb3fa90f9db32ea6e769afad32508ef54904197148cedaef4072edcc993a640ca090e88fbdb6a8a2d7cbd3b383ded1210b4a06b952156cad85540fc1a68ecb557e25044fc70f0de5ef41495dbfaf06ab5528bfec6613beb3aa4f099585e2acb2a279a2fd1c7fd37a42f6476319af0b78d1d0d3e03fd76d69b23ddee4356197af7884a21ebdd5f791e33715bf6d5d00ba0c83846b45c7dc22a2ae4c4b7dc4f8bb661a5b567d78a59a748ba30fc6f7bfc180bde1e4fe3a4e4bb4a0219a756fa78e651ec0036a6eabd5569eba93778723efcca652393cb997fc0f85f85d0d99a74c4ff182d685d81346a56ba7d86c6d9d34fb5a9677aec6bdbea1e5398ef5a08f55cbbe48bca76ced3139d998ad48decbe49dc0489aa7702138ca619e9acc3926a0f693f673ac45398d433ff884b6b0e9d2539f339fa59361c7623836a274be1a479dfdd704278da10384ff1f981cd172f63b9674a49635a548414382b2d0cf414d3a186648396b5951dfb4896407161743e0f8040d359457db4aadf5161b18530b283a73eab873d52248390fc6eab5ca42bdc47ec7b251c48ea146f5059dad73fd4e1a43af64f634f92df85032f218f319b46d2da7c96efbad476928d420b6c26b5a972d12424e082af9047b9498a11950c873af1f430c2bed9a26108e882d2dbc9e0ceb3e9e2263575a54508f90e8e51603546c2071eaf2c09895d11d0e2b01465976d5e5cc16eb0be4b286fb313c003712ec69776d93dbfea1b44f46a8bd26d4c1477f163564fc82a3460c6d411ad16571b02a979589746c04d6922d38718ecd9f1c95fb077b502f095d3e6e27eb63e77a58e33f427e9fd3175490e981918d851d724a979e6ccaf9d3fc77e1986747b5cb1f1f5bc7d2ea9ca60095ec006fbe19cdee029ab47d958588d0d36d4d4159123c3b5b195d439bb9bc89445e02503cbcee3045e37462f8e610cc833da61869c528588be08e2c55352e1292aec64f974509e567fa1dabfdab2757e4c62d6acf4a374c777dcfaeb3db15126c6557d97a81a5c7850a91925b2549ffeff42fd3eb6f2325828e740a27f1a62c688804d00cbaee7e73517f244f255e3eee3bac0fd6c4c20a0e4090963c7439e896e606cff0ae92407236598b18ac4c15e654d26157c89d4b07ae7bd7b494b2a3c31be29a000b56329a69b5d3804d1a30c3aad1de9ab6a3f782b62d43b1b9437f381e19f36d5f3c68de51d921e4d71f5f3f10106b5174d764b3d58ea831c62823ce021789b7126cc6dbe83c67e401bc8adc93465c04c56e6b1ff6a39f69c0bf4c32c8cc6c4738085b91b0b03d48bb4c22fed446cf0173062f995cfb317b4b463c444eb37e30873f13c5fab98e1551bd8641128f135c486d5921d7ab9591520389560bd90bf84bc32d4a6b3a09e141d3360294d2f8c6295c693bdf7c78f82ed89ceb778c074d70487a967ebe556a40a167fb2bcf3e9c9da2cc9cf814c0f744e0b7b3e81239762320d82a001b44c54e588f379e0044c1428e012bc8b15898c0b19a20d8d2ca775b12acd9cf2ae2afa39ff3b77778300cc76e5c554192eb5d87bdb409c46d3928e5f254d4a26ef6f95a6615481390abb24d002d6c4a16e71416015414413674f2a006ae54630a683b2f3c8d4d6e5e8a754df33a2950d391718f5247deead8d061a2a39024401445ec80167904ac06ba3260e11f21710d5e053bd4bbc66b49ab52fcdf1966dfc2f499f4ff5dff470fd861f5ab6848008272ad4c2468444897c86da323885cbb408142fa6d2520168137e204ed0784354ebac76c05831f58d840778295389a91fd80d0d11c5f5c90c62fd1e6f3c1d3d63ae260efbc4f88125a0c55013ec0aaf6f0f106585eb6860fcd7f576e83ea0f8d250860d338e0a374ebaa59f9d1a63b5f71d45d58ca7d134084dd4b2c97284b1bd8e030f177a3e398f3330a1b8494093906896a369bda2c5f74a6e9ba6857fba8d49a832fea66e3e57e7a329d1ef0dd9b6652431e4c86035f545eb1c98e2111033711c892e4c20011d45151d1be2f4628205d5eff325fc410f2fa8deac42a5d0714c6256cbc056aa410e0f71cb82c402d2196148049d2a16ffc2f1172c22782168397417fbeaef7a75e58c13460611b77308097b5dd77a111be9b0b1c8d0bea825608cec9448da5b55762f846ce5d263fca486119ebedbf75f90900a61559adb90ccc35d4fd6577458461697b1b27cf875b77d87a5456d6d81a843fe07fde8b629740cbb5ba505c36138d13d0dde063f44ed08086d766d4ac7816bc08f8442f201488b3a55744af53b94b659f9801b0f8c63523354d99ab219b324ff05361e1914c34af43462ad2e3525fe3e1736e4f26c2f0616bdb84eee49e1b244e540da5527716db9b346aef00f203d8d57d2ab85efdd0770c72787dd03a2199473c031d2f5c1b21b4ff91d36ed80835d46256e767d21fc36e905ed5d40e32cfacee03dab88ab4030082f37b38b69eb31ba1d44864d35448256bc72e92d0bedac1bc20c25fad01ec058963b914299a76029a45a1b1d5aa0064880841d981499bd9c032d709c35774288a5da4200a8a29db5c32971fba28c486b2d05068c760728259fa9741b6b110434185e689fb2dd94cf01c69a691d90e74239469c71db2f1a7844b336154fe00863ad5d9656f721613958fce88ebfa1ea4a8926799ab35449a2862b3dc4b812670f99e3a1369714f6972958a9f0e1529e021f61c3982e9c4e6e24d2389fd662138b10a0364e58ea30456bc76dcb397b43ff1368274200c69918641b302df36f2a03083fa8a4dcc1ca17cf5febfca8a80da7a0c69635a1c604fcaebdf50ab7be333a782b9c6a69b847c435c6fd8aa81a53f7a6aa037029cba1835033d349d5055bdbbe33e46967da93e4076007172b6f03e31e3d110a9483f88fc136ca5f44f070689b2f04806f026675303a4ad17599a023cffd16377b740c8d2ab8a82dcb444f301cbbbaee8013ac8a98c1e04dfc91891311934dd816535861c1ed26a98113cf84d01c6291d431a681f01560e2d76acd6964d9d9349d85b98abfb996660ac5358a9a1f6be9cc5d928d77e7d5cbaad3792a1bfeebe5f232b3343ad6d9c3600a0d76d9b7769fecea0e652f8c183881dfe448e0addd9fde9d549a13b6bdcd86c93cf3501f1a3d57d7c94974c859cfd197a9772ba5f49e3a8f608b6df0c5f5d9e3b2191a9530150f7a10f124ab8a2c360eefc78bf2cec6ccd238dde2f25b367ff41070380efe4bbb58cff29f70e2b3c0df803b1c76f852ec0405261d9977712e33d8934ee4e1855a3bfe9862c6457bcf025d22b641fafa9fab550122f878c2c103c261c13b298d26ae2eed621ee5a437c8c232148793ad07702f4dc08cd2b2ea525a4158c428e0a9f3df8b3f1ef0a8cad1bf0a3219186f997ef8b42794c2234513a03ecf9efb6281fe26d6adf1868d54996d495cb8577854fd7c63c38d34ad9ff3fcaf505f76743dd43023266a76b360604e486e4de7a0c5440e85024c143d8413a65e393a602a4dac8e68f9bb0ec0964fe1079b9424e1f076ceb97c667d4aea8b9f8b05c361b10bfec021515b4150157884061bc69d7d28f99d5c2406c3aca8241b2a37f7da967a7404352227313152c919258239e13bf3d6229b6e21830d82d4a3c1c012c4ca46ff15da98e1beb3a46bf155ad7dc6412c706a95019fb2fe245ecd7bf0241f10129f929b0817142da258f6e101aa35004792f04d13af55aa2f221c04ebea881df720a271d1aea752ab2d61041b61d767fc179cc650c9024d7ffb25d6a541744a7ca9b06fcefbe384a7d137d6178877b90951c616e627376afd3cf27876d61cd020aa68ff765222a6e0c1b97ec084b8bd0b54ebba66d81910d6646d5d4241a02e00d69d20c302f6b0d7889872b2c383da71f623f98a5f849844f96a1edb16b4a4951be6815a98e71488c185241493734d404a27cc22d0caaa8394e824e6b8e33945c959ff03d3951fefd347eec5174f046ce1fedecbed2293f288967e226f82742c2d65c3a1c781792ea9294cf2098b90dbcb6ecb9e72321809b26e5d1738c6cfc9917a1381bc19e25502304a6734be3b15d2f804669c0b3da5e822d3d243194f416f930436cd177404a6618b28de71a0f0845f3799ab3771a0ac1d0e059f7b499a846fbeb7a344c52706f10610885f5dac67984bba29182103daea6500155658569b462c415f2b5375aeef7ecb81c0741b6c0d7495e669ccc90cfb966274ee96ec3f4e1ac7fa082868f23cdd5cba09636d02cbe932d921fc48f37d2d042ae5f31620d94f8bb35bfd5aab0e4ffdd2d5f97f696fab38c23ec2acdc2f52dd8a8572e123c7b54059090ef2456eec4cd70f07002a4c35d4e5e7e8b2d10e37c0db4994dc40ecdda2782c3e3ccc44a7fb122a1d288770519a7eedac434464c34bd2b7da03f627101e37be01d529896de121e58d01b38114285bd3fb992c991e3240b9c0fd2c7c3622a14b13be4a83dfc8c6bdc7fe2345b441dce586aea867979691c2c87395144b96dde12035c582d93c4bf015800789602809a58e4c86e4be64adf8d28bf936a34af8e37a0c8d8a0a0ed6ad1b3ad5db9dd9ba562d9297258a2005b4848e883db955ae21e15431218337c2c2d2cb3c9e82c050aea844e303444cb5167e41faf3c0866e4bcea31cc56165bc9528aa3dde69a94436521bb9089955130a66a13da5610b48bc8970a4df578f695f1376ef275b0bb64858a05eeac6cee170d1a2a4162c87874c06bf0b81048941004de5f77164491cded9ea2d20e188f37d6cc506c0436fd4536ce339f003bb484f14c069fa4b70c01e7b03acd3f3ebb9e1663e1b6728fbb90c9af4543e606d365c61028f37f28a3c9b58dce068d2aa255c802a70298f005781dfb985179390f638c985761261da05b4787c62ea07ae8b230518870d874692b9a8f2af0b62e890172e66290cca34466d13052c6e108540aef9e60a6e359a519768841011919cf1d2ee755748622f24968c127131bc7edbe4cd64bc503f8d3ab22582d1c778bdd5358332918c0203625d6d5098b95f69305499b20037e143f0ddb3579619de739868cb4fb14ed880a880895f553b7ec34d3078986fa16b1edfced45a7c2d7c7a282432800591df9976b750a4b7924084f82f22bb9b3fd6a240e81fd20e4d64bc37cd0e5a4921db076154bea0c105c68a5243b4947da3c530bab1c7d2d6a9cf49f1c6dd41e9e9a8fee5e11e6329e5c817240151f5318da4bb7912a5ac3f29723c63e2c6c5a6c02293c4e680779636527c081d3ec5644b6727ce4003a423837d32913bd050d0409c0e5e8cd2bc5a1311f0a3d4cef4896728c9349897afafd1264659077271928b913538fe80ad3514c13f922753ef0acbb573653403809bae13adfe18576fd593877e42c5d53bc321179c216253105ee9008a82c39339a8d4dc16da7ebfa77fc81e1c137d08b4a68433944b30ed3073eadaf2c7cf644fb76804b723829aaa5349b2217fcd9f4d1e962922cb20dd21d786d3f52dca682c9462c482e27d4d288611eec05545dc61390321cc44c2bacc24c5226585526d1a478a95043c29698bf44baf7dd9200c2c60330bbdeaf016ce37a72fb0ca78dd6d48ab3edb426c276dc4bedd9555edf69262b686acb33ac1eb09270bbbcf28d2a00eca8c7d62a3153790537ce5cf5d33a16ef6fca54e47930dc21a69e86d0132af4591f3eccb3e704b5f45fe7c9468bf370847ef58f3087e6efc72fc2b65a31e04be5326e623ab7ca9b6ac375a18b9262605d99c1720b81b6694150cb8bac586ef7b26bc4a9c0ba0ad3dd3e1c5541f70a4ce8f9939dc8702042b8edb808fc0c4fa3a8152ecd83a4e571b068d2c7a2eb61def0f449d69596788866eba3d02b568d5400a1c98cdb9049e37d418c1b3b90313a9c3a115445982c785a1cb2ff93cf7de5d4d205621ab1516a019190be846932ba26704226c725135859247f773a2d4c73766b668ef20dbd614b8ec20a650dbd920bf398a2845e99a00cfa280ce12b6d8c302fe24c8c2ecd2404abed1c988ec29f9f70daaa14900a3ad7843a0098f2eb3948f71adac7fa9cf3bbf0a41700c3c3d817292b586d1f355ad66343d486d8765d2bf4acec02ec3dc1ed667b442f5ae78f9a8166ac9fd118c59a09abcf03007e3fb65ac33f87fe00cadfb8343441d48916b02d0c1e143e76004785043545d38b3f4622907aa0da0150b9cc874b358a3e131dc50024f8a78e9227bf583377e5170e9973f22e7fbcc8b41dd7737a5a2e08d2debeb5237d0ddea10584a66bb796ba509fe24d28430950f7a767ee78425504513e05ca344e2830a238ce6960d4c48664ea1c571840e75d084a66e5d1cb90303d0e99b46e01621e945805e0e9d6487ab97dd201fd478ba83d57052440fb14fdc5d893595ceb0d6a4900347e0a4cb0ae0279d3a8bdcd25bbfcdc1d546dd197fca41d1239d8c0b2411061f53887a7424392c0c00018f1c74b140f1e3658d297bbdd65fd35fb4a3f68abd19487162fc41dc2df5d50e0a564416f14baa10df3a0cfeca54c8bb75a740cd5594a63afab8c49351486d7ae57316927ac209791b08761e91f70b93e69faf8e4425dc33ab8092dff6a06d61e084dee51b59758d9235f12be5fdd66f183d3d164a6ab60f6ea5bb991eca7f650abe2eb2a7167e4b2bd1fc071568676ff9cd2d72348abe223d13e6ad851a61db7411030808230c807a18dd2e4d280dfc64a8d74c0d7440df0429773be06b4012668727afdb747171296de2ca8acf5295a1087e96d0d0cf9c6d20631d6ba17544a99b827cde06c4c0b99894e573e15320d5d5eba9cbbbd893a525f5d7c4475bb8b82ec8c09822dd9296dbe17d06f48094141b55073628d322a6cf548dfdb06921d624e4087dc1e367723b96a9d5e8ea87666424c8e7cbff352c107f4ef2cac62ff5c986f0562ea028a4def00aff5a8b7ec4a3756bbc2ecde4af5779be98b7bd4bb25bb9ae8cf413ed77bb4801d05ff92850380ebb193725b7d4c9517cd8e4176bb9ed7367f2b52389fef4020982347651536f36c43964dddbf4c2367c59fbf890c1d86349d9f1dae176ae7d9865f8017c39d30e22073c40b3bd4898f95c7a4d2ac87107a23cb0f0cfea879972569b9d0e381132cef66c4af14f59a67e9b440855bbd770e06561e5dcc0d5cf4e33557530de88d3acd009e38972549841a531389a181e9f1fb46d73bd3e9d77e5c4300994cfbc70187bc03be9dd626a6176ddfe5e2222226f2ad191bfdb2a923cd2966685e22cdc5292e47b7a0d098b23271518e0a8f89c3b782b007dfc1c3d59de02e376758297ec4c163c0bd222e03573bf807b65cf37307d9cf7a65debc0ec2365ca7ea371ff94363e03a588c0c14fbfe0ae8932d10f99c0f601f5b57b632e59c25f742750575d748ceb25a3f21e4d4cbbc13b277df543329f541e706c36cc44e2105dea6f9a225f87a098ead8e8320cf3bc65ad220d80e1cbb0d8f416ca2d0f7f702c1774117149fc2a92bd6b8a80f81c0058ce16fe0c2c78289e90dbe53b35e8b2f7435419ff3089abc65593aa6601cb5a01a04689bdb6b890d12239bc7dbc3c9bbe2b2499c6251b2367e756e548664c13d372db4b98a5584d30168ef6fdf363e19585e043fb780e85bb6d6fa9375409bc5884b5e097a84a8145b38fbbb096a0e96aef3ecda34f011c660d5313d492ba2b1fc8c6c1c7a1077aab15d749e99d318c97f386c933842c530bc395749220498220560845bc3d833a6206a21aa7816075290f2098c94262b8b072c13e0743a235b9e6e00e2d435d4e01a395962e8a8c534173660004434a78089a7643d5533ac9cf3a4e6ac22ea5264ba348843f888b7546594939418311e71f070fd7c3725902392574fb5d31cf7892c6f700a14f28384382e307833b0bf6dc64b7d748be4ea365f97abd5cd39197527ee29b232c20d2c0d25e82feebb95b685bde0cb138c01260fbdce09941a63b0d9d982996ace76a12662bde2bc965e34866830f5a4d7b636dbc8ffa50b5194123d9ae40ac2fe2d3b7918069f23cf07dde4cec4243c3992f08c86ee2b35eef2b5559f12b4d5ec95fa394778bea7ea54df16832763e78f8c7d81ffdd9780612eea0fd23b02c7e7c4fa16eea15d10a98c113589ec84d40116ccac14c055b44b259d7ee46f9a32741c92427c825a3dff8a4dc30e2432fbb32b6a8b9b01b1e8725fefe20e229b375788d7a14ccdcbb505b2eb3b24804e78ff7dad2aaf67c7efcaefcfdf75cf101b3bdd203837a2bd0db2c19ed3ed0fa0896198c21005021a176d40922e4f3359564dab9d9b6b457c8c7ec0dbfd2dd9968940006700cdce109fbe004dab39043988fde3cc2692c3d0e23a3421a4dc885ab0729235adc9f18c1c9426af70f1243ee0721c9e625682061eb2f63e98ff658941097b6cd171ae6d01ba420c07ae0f1ac0c3cba788dce7c1674fe6a46562c17bdcc50ba11642ea9e16f21b748b85faf9a332c76c823f19e7a4b11ad53dda3c3960f7cd3222fa28a97560882a6be65dc4b577fe813f98761e91a542951601c7cb79bdd6ee9285aeb744134286741f17ca52fe270832ec2804f1873ec91086a3abe1906ae0336594350e610a95b36980781205395accc282852a896848353f3b1a4d2354f9732c023b5103a44c202e9205c8548a88abbd6c89484ade915c82fb0a3b05e4195255e1ba5d654094ef31706567a9736263270d754b5569650340c1ab10e3d894f81ce083222a7d19024d9fa2431942af65923f5b3a61cd68d364a303d7a659ab807df3920fab8742ac9224ee825b832be0aea177c6b080323b8f96206f2c2ade520ecff1d2a5bcf25e2a4ca00ee1dcc6e707770de2ba437a105ce01e19d81f476e0d5084966665f6063e7d18f426dec8a908e066b8437d833e8feccc18f9afbb945bb58eee3e2be515f07ae9dc4c7816d4de6a5dc372ebe6f003db09248d9c6bbda5e9d208c8bef7c1521ef7f67bf3464e44cfa72c6d8ce2e9a4cb091fdfddad741ba6ea735fde9fa9a2eb40d11b2ceb61d537953e28b7ee42f5b691c9486d139b2e361bb8a20d4da1029320c2c7b735622e197527f9f8a2df1f319f4a6f461168b00e3af5f3d5c258e89d47941495da0dc66d6e4ef0399fe1df544909ec3b597e01458879353557bdef923cbead4891cc3ad7f4a416536cc89909374cf8dca298a665c229397742d6915c6fb6c8660d11bdfae16d65a23616aab1f3c27ea74813a23724b0b5893fdf72aebebf67e2ba7d4b2e0b3f3ccf4639a76451153606fb0b2e83c4d2b1a4f42c60706b31aa0884c013019cc13b4a405cec6b68668f2800ab27a82031c716089a43eacd18f1e3902a2b5aa6f29cbd93eec43c1a432b2796fcdd0072e42052b4d2b61b2eee4763a44dfd433b01011854f94476b1dcca10d38ba54ec14adc77bb155796a9e0fb936cda0cd1e9cc58fb7f9ad859260036b1298f6990e6ad36b31f2d7890f1e40ae8173dd1513bd1e92cf897866cdc9ef61190acc7c2694e6255617520dbea15312569b8dc12deb9d881c8b18d6222fa11dcc49fb67a68cc62f2918bb2e572ed4058eff18338c8f433d6bd506c8e6834f8c2209a5116c462ddd91c86618a6d349ef65abe0e692b64dc3498c0e57b220d065f6194217f7b701ff7e3d06f394e89597a2e1ae9cef5cb9dcc7a4ce1e5aacd940dcbcb2f20f63ac238f8e0638b48efcb1b00472164bc86819cfe3f313967a180dfd1fa21209471b9d3156f50ac6f3179d37f02751f851964745690ce5c19a4031a3c9206d7331ea4dcb5c6d3e14444be5d128f8ef5e2a47652512dfbf6b84b427eb3b507f1fd637c8a67cc91cc8601cd636d2b5d274e1d312d20795551886d782d07a831ba30e6de44873c9586e2af45d09144d987beaf8c1f1672d9667767da1d4f5a4d2d006044dcacad0d9301824aa6922053bd68691c9de52aade5429ea2b7386a8512d04966488c2596adf2ce262e3ffba800a520d54cd0202bb75cec50a9b4e09c7ff6cc76cfbd4ffd60056ad0a6877ed3f02cc4d35a27c4fa4a664020ee35f02b855fa5fb2b9dadf018ee3cd8744a8748b9af849cf9efc43ab80f105b573a7db607b3b5c8ebaee02d9ac78c16fdc2603365007c6988cc0cb54e826dc09b0ce4767d972162c71eba288bca6305d9c1622c9d30f7d4dcbbdb6c14771fb088ff0f8deb263f683b46c95b113791594517f960346e7707184615246a19d091b33e4a2a4190a21211c222b1fd4bf29c9230766857ed6c9f2ad933d9d68ac3863b5cb6f9a2e8a670c8b7dd7a41c420ec67947d292b4a5bf83619214d1d3da2b5d0c1afcb93f4b634aa42697b76c9f212547fc9cbaacc80dd4f427ef5237c8136ca77cb239e0e0551ae5394fcf916468b51119a4faeb4fcba777b6a06a08c4dd9890885c6336547b290099098d77ee6e687cb11c7e9c6cf27af73cf841d9cf6aced5c09375a06885de401ddc7e26dd9d84b0137de29a780a5f6860c0b4a6eb0e8cbf6a8f63e39afa721cd79128f56d13181a6f9b1d745f1740b5c1a0cd0185cc3945ca67548be7054881cf11e09ac2c1613c63c7ddf74077ac1a9e2f33b5550590376f11a412dd2abb573a8f685f3481db3226652a2e6357e775a0e1c3729552a120cbb509d23e37dff70d219006890dc96f888dc99ab86ca6e9388b312d3d1da998443ee890e3b76449a974a8796634fdf0effa751ffa5d5286a739e58b70c93e0ff70158e30165096043a7dfc91a492b090e1eedef333b3c8445c433c7126e11da1087d8e647d6f86f27382b97bd2c6e720fa738a83203e9f55f366fcf16a66488bf1ffc28ad0a2f6fae6d99133dedf84df23021494d61d98baead2ce6f7272d84775b4a57f060feb0f9e22f9046c843f81396a7f26af4ba366f006b16016704e58bd14816b877fd01d2432c5b69b20d0680560a65902c690b9bbbb1135c5280d5ac338b42943fc90639311d1ce1de215098b235e672cc3c9fc061f1d83bb1bb47d14a54765850dd65d4b55133fe44815f413435c9ba4df82c74deebf33a14347a4f75557bab78622e5b4f7db44cd0ebd74088cbcf7a3a7e5809f61acf57ef1c3db6ba821f71916bec182a14f4984d8679eeb332e0c8947e94f87b53e1b0aad24cd22fb44d1c7705d87945771c2bb7b83b1e45bf2cf9b1be2afea8cd468b4d77f4eae5a03ea8b77273810c28b7a026143febecf3918274d1747d83041f9870b807affcf2973f6991c6c0ae36fc096a851834401872a7e3886a1aad8cad581dec602906eb0e47fd692c46bd5681179e6a8c8b8ad961d5ba71206237ae0d68e5900f8d1c607c32a16417baf8ce2cdc8f0502e9e12c3c3c6cf5629483a8dcd24cd94f3e01a1a8c84a5c7808f3e661221620cab499102cc85625e4ecadd32724542848d29a8d68717a169169db269c301c84ce4b585e0b6f33db009658664c8819fed14167cf3a7efd01358bd8b9d08c5c9879d5f5cc9c7d3c392a3850ce88790f26a039cfdc05485b37b2978e49092e847bdb24a9ff05b6578962696789f75f8ccc17dda55342b5e2f709db2ec09387f8ab1a7639cc4355e452fcee0066d16853680319dfbddf011c0aabc34934b45bb9f354eba5573bb85dd808511acb8653aec5acc7952ce8f2a2590b88bd147d11d5e32c3a392be544acde48bdbd132018364c89461aad7be6ec9f7ee14bf9ff18ffe1324c4c8454f7de56d3d76d5f088ac2dc14b0410f9a02f5670a5b93327bc6165bc4fc256e08903232541215f90d26994b405b3a2f646d731137c0f3a76def4e4144e784bfe052165a48e067d9a9f59504c3125b679fdb33ad803ba0e230793d24f25aba2348ac407a2f76ff7b46662b0f6a5826531524045fe7fcab449a302f2cc092d40dec112d918387f51cdadf39ceeea1615ba3863896882c489c9106971c5813378922d0bff67ca8d1c1fcf93e78d580789f6de15d66089613236def1e8cd1d477e15f3bf99c16abaeb0f4005d6773a74ee88fe61f2cdb2bc632d44562e96fcd7af498c3f135908ac039bfa2061040c82f0a74191d4c03d0b84e24accb56233562504f83b82d08de658417c96619b1dc9c13c467788db4f8f5bfae0d91e20f90590a1bcca437ae113a06f0b717193d508f8ffa7035adc31149114952a19b6afa7ac690f5cfadd4c91e25aad3370d4ae30c336e11d0216cd8ea103ce23e51fe1f7cd5b4a169c702c5c45693fd2a1ce7bfc2b5d7763b2ee0129f36d3e8c49451bc04f92748022989d3d86ad1fc04174a95fff8ed2dda5fb9e526634c135593bbc0f3ef1707076e6780bc079fe036dcd9bfea3f577d548bc3990c5cb194e9b57071b090e76bd62ef90c61442c08bcedd834000aeab09580afe23c5e53d7780276d5a0c760474af107546538b35a80a9276b6eef71c4140301bc1e883c29b3d3f9fba37603f342ce1e7e4f804d28d51d6c75535019ec175355be0b7f0e2dd1d1aebb7f74522fca17c2c7e200e779413a36a9422b567f8bf9d8d4e2c90ab7a7f3e8f9633a4fcab477d180bdda3f41337bcc6519ec92352a09b9d52a3fbc032ca1d464e76001a2857c5daefa766efbd9e3ebc5e6344c08ce81a8ced0b5746bc9593ba0405adace5ec54ec510ea368c38bf2f673c4655856c2a1c5fd2cc6a61b228e31a92450695cdae85a281cc448585f4e2dbd98ed582e606eb25df9acf57eefeb500037eda579c24608f6f4acf6744e679cf36604f90d0937541d706e262333011c8b0ddf671ba1fe693a649b53a2978a794b4353e7de9840d83a7d13b2cc543cc38a11a50fd75d55d0c119fb7d7790d37a99f333b2e40de6eab10e092c2fa119c1ea4ac5efbaa8bdb17f40569bd877d320a50d014fd0a095e1b940f857b09ea15002d605708ff2d4b7b0609c9352a2eb72bd66ff459c3df08c1135f8b9f604ccc15956c60cc054496441c8e725de84da221af08733753480cfbc08d13c42db674d2314be78e0d1435ffb68044453f1300868d620ab3cc0a494fd735f7e8d4c7fb3cf74e23dd5f8ffe6e47553c6562555f28dea8c6215ce01167f78e73a1c821316e60ab03d79b64b10583713b7b3776435062253c82ea6458cd2cfd2ec5b76b6dc1fc4beb1d699d751febf7abaf7ae17ae8bf7814f741b44b8ac3e8949487bd746dc958087b055b228bc5eecdbad8f76ab3cd70d72f942269d165b131395e41781a90606024c3b65c9db264aa883e4ad64d7474f3c98c7a32f6b0a8db2833391548daaecd31919adcef3d7e433fecaafc6a8c1aba144325fdf1b2dcfa94868c6f3e5c454133f6c009c4b4609c967d6941aef575737d1b0070c0df3050831eab558053c3c856e3c2b054116b62ab8c44049968af5bc47b63994279e4e704736bf1d6d9d9a196192e106f35991c0ba74c0c55ed48d955e07d9165e062f1529cc6d5b48d381441d09f67035560db4353bea60ec641861985af1d2077c2b21efb9b0d6a4d2ae5d886726d44713b99e709b565c9879d911f0f751e2761e402caf9c4d1a96f395a58d9e235a9f03cc8d812e59b6686adc58b81cc787538ce80fd1fa59d6b3204fce5136dc0fa2c39ac4d56493e33e61a9861e33d911125e8c6d146efdcfb0e17b512e873e4feb8ae9a8e40639063d1f00eefcfd5027bc4815b688bf888183ff986cc857ab40e57c2ad9a0178310408b62751509fb921e340bc4f625382affc8ae74258c30a0461990d8a60128905191e867b0599e2176f1791275bafb721e5980a12ccfc27688246601b51089d527fe7f75f0db3a64c8890b048c5ec40cae6ac8589458a6c5e6c34e8a7e9b7e691ac281fae4ca114884ba0202ffa205fe3590e29f4af83ced73068486c363d39e37defcc68f45bf70efb0df33c030b78f9d3306b27b71650178db31216d10eed01828f48b93bfc82f3cb48ddb5056b6c17c8a49f9c83131b3a4752df7b1cc13110d66b19e8d9d0ed9a23b4da5f46c21998b939f5c0d449f04028794e75c79139d3ee10768e44f4ae3bec42090fd0a87e03aaef1d390062626e1535719a856d42a771835b3095488098026a40173a33f93d6081f5f126f9457a1a37530cd96093d0ed921f7c20ef759e5220c8d9d841771fc69ae42fc1d49f2e79cc5f69f53f9b5bef017ef7d56b0cc31a6ff991a08ebc8911b2ac5ef68200f3b63b71138dd8c6ee1d5dde975a9e8002bebf52d4adc7e9852e5803f71de6a952a9f5ba3e813d7ef782c8f246b68cba9d490459ee5ee190f225e01e586e3427d51e9b835091cb968ce45a0c5aed59f5823ba6534294f3aab622a3aef6f6b00ebf0e945625abea830149a911cc836fdec2b1dba1782d256e830565998d4aaa1918a4dc00feee0d5c7e50882b9c56b5995f46cba0c89b860b6a4fa3a164001bc0f131c0d6efb0ecc8d9301c5024a408e11c0065f2a1035cc1ed189b0818ea6b786718573e0ccd973804cae0cb2e5384fbfa382db85faea9f30c7aa6a11e2d6929427f6c1e9e67b18ba92fa5206b8c96e7186cdebabf352999bc18d1bd87c85ba5e8ebbc96e7b074356a997d281dbd9ed67987cadba5682b91ddc7c02cb57abf3f25937c94d2e9864b5fa523eba99dd7e0ac3d6515f05f07030bbfd0c8baf55cfca013783b30877286cbe5a5dad980ba3349bebf3e0a2420f03b382f0389deb8d2b69336b9390d8d45d1e0533308c5b2fe84871cd2dec27e5fae831800a54e0fa1b1989af59f5ec9f330012d8c7942e2e33c4ddb325fb534ec32dc19d18f8b0b6b8455540131361e2778106706841650a82a5e0d4b4f99c17664888d5a1d8b99c3122b8b38417ec1084f0874d339000a1c59bc6817d853d73a146a46012a0ebb64a622aa3750f0e1a1a80f523b0526cae0e69e15d91ca1782df74958a71bd7bf3aa5786a7f56ed8b981d4a6dfd8c7d895f95447ec1660b53044c7556218a8b7a808431e0dcd0adc8b59f21e6dff5814595e05cb9c963d38dce6f0db04a2634d991536a68d15cc3ee64b6577c41f5c95d27cad63bf943ee37f9520dc539cded8d66353ff216b0ff6a1c6fdf8034f5bba855ae43bd264c0cbe4871d834c032dccd050cd19add082056b492242d51835c0da02acfe86f8af3a4c89d041d9b011d957a09261ebc0e8356d7702db3e844d3a576842417001bede3cae6cd49d3b558cc5be3f3da95f7634dfd61038e0331e1d7e46b8c5e220403abb8819eb2b39c378dfefd727e2896996e5de9c6a378cdd7fc695d676b8b5f404164fd754946484cedb21e36e73fa701bb74fa47726840670235602c5be5d3969607593142cc792d17bef419b64f573ff645b3cfb14319d2542af6908b371f958d0cff0661de4f9f7295630cc8344cacea6a229db5560c875cea6cca6e3d3bf94cac1f30239cf0c59b9eebe5a04d230377eb87cf0d1c9e7d699d39a1045935508ca3611fd3712cd2b1cf4447164cd0c07d22c3f26e65868f04506a862f49e1d3c03dcbb83f53411cf1f1ea890f50def4167a0c64b8d6802f740a8616ebd1a6ff9150ff102895065e7afc6e8068592b11019dcf7525a03d44f55ad29c655a61d9288c3ded98d98f032d6484b97e6466914ee9f5bcbfd77ed062a744a5c7930a77c1969c303d51504784fc7d9bc21f5258c7435e5ee906a5d2add33cbef7c0d5a9ff1e8425db34a08e67154ffe4f27cdc2022b25f724a6ee1b8df3fa11e3dccb82d284e1828a717bb9ad9c53e7c134516b81a7fb58f69e2357f3a07175f92b1213ed8a8389f5eda2751e2a317c503b858f4ca1777a843a862c0e3fcf82d456381bdab1601ae8d4330c5da85dab67f3c446ca7b8cd853ee1ea1b63663c78dea1d7a7f99f55e211eb633451c12169c4685a782b0aec087ea52158c036f0f88421b1a03f0636341a41b26dba174554703ef42f885edac08d51b6ed5e60c87a895facbc922110817533f0025ed83eb8d06c6680c4590b852ca2ea89354d7010f00ff51413f35a30cda38bc6eaa107173965727a1500e4c88a0e16ea1388adde6bd70a8348d5b03a0f4e3b406ad7c85ef9a99bb7dc36b06984a5c550762130607b69e27b78b51a10aea73d2bc6da1b07bff659cc10407730446b7a059cde033aab0fc258200eda4a0749824964ba0b049c4a5a44b1909f2333bc848f774dde597243edfed8a79320f582a469ee220d0d1d74a49ce8a4fe9880e497dbe33503d6afec195eea38550ab6ecdcab4d94ea4854354bf6265b6e29a54c5206880cd90bef0b679204e1c1245a1fbaf2fc90230a172b5188f64c171899ca49d1074cf9814b0d4f6cb8e01c7104ee284e62c862ca03e70a0c6b39eef53a727524ebc8d6112e1a6c3428d1b064612cd925fba45a242a7e787139f1bae999a22f9ba541b8c107322cc441b304172b306c32504055191b94f002831c2137dc75d3d679c6df755d87b18e29ab83caeab0b23aaeac8e2cab636bf6560717d1db1d3618b78ea33ce02156b0bbe546b7c07cb6349cac1d443c59c3039b2666846410812d304b4d5c69eab2a447171f5de24451e5244a19191342f094c903c40d0db63056a6bed8f034f990a60549439a20d1d4022d2a406cd55b6badcd51a9a8de726f3f29c795765d145c4d9336a52579468d1163a06c69e9e1c6083060e89d31543fa0d85cf5502750e58a112a3dac9c6842660c4e82c22686a516bab0a8387d3fc8275953e24c8182a50c92315fbc383180288a60a9f2c389ca9c4bf1ebc56449d32413289809549645c151a2e02b79c30e516ea420910316145509424ca1a1a2618c951a68521078d2a829226b8a520d628600d2c6cc1c344d00d18ae28b14cb8369c614c54c1049941942a5881f4af507108c45085492a517a282d8b9f6f5e26a6a9a747a8a32e2ba5d8e48071ea54b04d7540d9381c49344a98917a628994941105d9044c9e189993979640c4a40f9de7bbfebb17befebf5058afd02d5346973ee59ad4e63b90b15bd31279e17d6f05ea92f585e525db8dc4a91642edf7befbd2ec37bc358aed98a01aad2d4610edffba240c828b54d783401d284c895e18ebb61e88d0b4eac0450518ae082c70d1dac149a94a27cc9bab2868734368627ce9ba85c9b1bc40c779cfd6b5f2f26db6552ba4c4b9789e93235f9df3bc3dd8be3e1890d38f86032d5e489681d0d9d3b3c1471553565054c9430f6c57987b921a28ce9a29c49111ee18a3284122a608c102d9c19864013840f5074f0ec911a589da96262eb2a061984904b58f1e21424093a504ac8a96ea97bafeba65e4ab500f5d6caa923550897b7da3d354d46e15bd3b04f354dfe28732944971d1d519c70b821c8d45b28a6bc8522cb8c2e4d7ce9d251e60d113998ebba1f368c7f10e90fe0ecc024082f33a891e1cb6dce129cc40a12048839505b71a044212f00c58a5697114ae059c2cb104fb0e0009e2194b4c83083d21214b8880e5ade1ce126c9941e5d6ca84a7c5ec490e64d191f63947e284d224c4046a06041c7ed081eec4c11ad971e7bd0382111c4152e45e2c08a74016d2962c647aac81658112e748de99202c48b17d12291e187ada81e3ab2eca0a5eed84a7ad244151d6630628ad2122d60a8de8ec048d690198c648121b3651aedb1fa61c5fe0882f61112312801e7a908229a442bf556881be41b2b64ee10c1014b9e293b29a29662549e5e50565eee382167071d98a498116d78c7883c4bdcb8c981ca667da0f2a26efeeabad70b8b53d761f0c3ef16875fcf6aec01373c30d05b17d337bf0419ba747842c6894d8c681dd31aec0628334b58a5412287684359ecabc76acc2b109e0433fefade4f9743c46e73479b3ad9b59f424569f231c6af419a23436cd0414d1b26a275c05b29195e782b15a5c82bc9062d2f50b8100488520c148916cf7a537fee769e5bd780a86b5f0de6d68d42e85a6e9a744b930e033fd00365d3300d87f952394ed66e6179eb990555fbf064cf3bbbe79c73ce39bb29f54abd3e0d852250e7b4d7daea42d866f230be56fa22042d112206186f6b28848c85b4f3829222ce349146089658b96c2951ba6265cc535703def8e8dec400b290c98c28d19b1c5d977be7154708278444984f2fbd3b491ea55527c9728649ef514a9b6a659a73c9b60d5b698b34f2a067330eae960b65744de7636d03a8709119020d569d21a056963560885ced1005430b0c4ccbca478b8a8f953ade287d1cfbdefbb214863c4222a23449679452a732cf7616acd9d697ad20c5948624296d4e244a937e020ae83b5101e7dd4b980c19d366133919f22672610894b6c24cba5db1fcbcbcc501d293d624185ea083e60c95325049d4be6addf0b56a29f9aa42ac2e7e2003e507132b2725628d517068d661ece52d4e93f75edee2d0f0e35017385d2f6f70887c8b97373862be9f648c2c5b54af5eafbeb668d48d248ffdf4d5e914ada960fd466b2a5cb7a2359b87abc9569d46d1275a73a2b5a67aa335294e517df5a6a2b5db39e6bcbbdc6c2b5ab321be6e1ddf300a75dbfd446b72ea8ad6a658bdb9a82817adc925275a5b80104f534f51b436896043f564456d4cd5463b8bd6a47845959aaad25771ecad7a35bbcae0611963b628ddfba991c20c20c73ee37a901f3981b98471cecd1be7dc8c9a4f41e1a4e26a1b2c9c53389c565b5759269430a367d99344af3a2cbcd1349d6a88844314286d6670324da5a51a29d42001c83927e13acc4190ef047b8d19383e7f3990cfc0f1e39e5ff7b2dbecd66b504fa10609422474a28fd740f20933a54dcf8010093e1ee441a10a228e241104264a9b0e047613a54dcfa050fbf524d1a36005a4c8a3731ff7268c849777b0199efbb884cdf866fc84ad4469d37d4221eab0d07b89d2a67b60089436fd052251daf40e14a221120e6594369d0b6717b4a05087484494363d495cc12789f6ed6aef1af2889fd63a6f9fb739d5b5adb36aa5ab6e46515abb578209284ca7699b4f5349c2aa8b25505abfbc3dcbbc1738450e447af92b54a1732e9c617046284402e7d871a882e79c28456f7eaf238a843bc78e6385ce397f852b74ce8138a29030e14c19c2267ace398e15c2153a57618517e75c37d11af6f676a235aea13403acae28ad912615a5753bf56cfab44d254a6ba41b94d6a01009b80b674224705d4844698de494d64d603b515a3bfdf411f7d31502034613fc306a179960e84fa8ded74523e7d89bc69087c10f3bc99ccba7b56bf2f6e109a0800b4d30f4373cc1daf01bea0e2237b0a2a2750d64d714adb5d662af32acd6bacc6f44b67efd86ea9e14df8fac1149794196d58fe845073990b8b95085490e54b1228008d12488205578287a9307ce0ea0fa82a832e00d9aef0d9b14a112476e48e2a94c112a78c42417cc702141226a86214f8834ccd3f6cd8656bcbcb971e2c7a19a8337675ba123433f527783840f0d690c7df558be99d66ef8a2dd8cf92675184ff2f8e2450746dc6cf931cb9b063337597e9cfdac5d89787dabf55a6b12d4125170edb1d2f4430b6d2aaa0e15a9344d8478110657555b2b1d420f1ead1cc27cd10a225600007913e6cb99a52c59625dc0d73756bedaaf6f82f80a136bb5d6d637505f6b97af57acd556c7f57b797323c54d127b65830d36e80da8329009c99a51d326aedc009a863cca8646d9e4d1dd9005768a43234a9b9688178e1eb4531ee5cde82ae0a9160c4fe94deb85a73ea401754c4316d3665f346d624ac371682e11c7872d4fc8c9a106a1383fdc6e9853a505220a186ae9c6c7e797b736796edf274fca183705961dd4dca8947864890e40338318a920608430234a9775d1062f1d89d6e46d060c6a446b52c4a274a9012aa389ef913ce6db0b181d30e5faf2d6c6ce8ff256cb6de6d41bca271913c124e696c4151e27b2ec5519aa396de4984942745fdeda68fd38ab489ca8e3c412efc902647f7cc194b52c53c8da8b1ea5957df1d38310baa8dd9b86f1c5cfe9b219ab9ebbc8c5579711c91ac69c729e74d67064f1d369289df8f9f24b131fd84c79238410714469c4d4115771b22461078ae8e2dbdfa8590ce12599bebcb5c9fa7176c3e3c9210a820ca43bb9dd9b5346cf907a567d0334ba68723e637e09213001051b37ba880b6584483dab23b4073d738271887b280fe5c8e3f7b5ba2763d17dfb5e97b02bc72f562b96366b839767ea19b22e5fd36b10fd0c53f8f9d9306421116693996faf41f41da2a0b6a030645b2fbd8ef11cbb06beb71dcfee0edb27feeee538ffb1ddc6fc4aa1b42902b719a20ee0e5ad8d933647a857a0dcb3a1efa64053e84ac7917b2674c30ffa2fbdb523c9c8f3ed33bae6627b0950df1e821fc4761a6ebebfdd4617c95bd6926fbfd145b62be72c01df4e3483b4f8f6cefaf163077f0483927c39ab1e3ae15c46b9b0021d01b1ea196dcaea512555c6b45e190b28ba662173c2b975a396d11a171a511b52afb5cef9ea7dad1ec81badc96ad5fa7abfd65a431632fa8288c25eded82861bb32598d13a96585e1d336421749a217120559de96c87aa097b725aa9eb2e1e129750f471a433fff45571e898888a64f974179fad45de230775970060e511cb2fcfa17bcbcb199fafaf2c686cb8f43f409f60b620c72dce5eaadb7fb0aebb24142dd9318638c31eeba5707ce7fe1aeebbe9e593b2fec883a073ba28e31e793c338c43db318fbc4d886a38d8a6fd775afce5eec95c31887f3895ee7c07abdd6bef7de6a6f535ab1f74cf67cd130420e557823645cb34f8cbdd6a0da05e15a7307c93ef8cbf5b301323e5fe149a227291015c21c11c5e107fd7846d784b67a7c7513ba26245647e1e9abdb905ae8d4f91af4554b9d255f314789b4d021525dc232f0f98a10c7e72a422119400e73204f014af106819ec77c60164c014af163778a419f25ea594581d2aac3c01b9456dda7fa0f5802a5d53a83d2aaad445f12d73e5c9e1ca23e1dd329faccb3c4b5035be308861d810b559c89698d07e73c7a6071c7dc234699de238b31979f4b99657bf228fd06ada9a0b5cfa94f265a6ba2b5205a4be1403e4ea5497dda421c3db350b6824e7cbcc11a47f3a967d467d853017e60cc558053f4260c09a642b6a0fe812c863078ea2b2603a87b5747ee49a7d38dae11a0c90921a71e8e08da6284699a20b71801f2d451a0351e51d63cf589446b1d8cb983f6b183a731db95db1b7684c403960252ccabab40b6a0cda3878b722e6094513cf5154c06fc3c17be845ee107fd2fec37ba36735cf950bb40a43e97b89efa24928328a59e50f214e8a9549d2deaf3e985bbce250c49851b355601dea08eaf8eec04e61f386de054ea19f5093a51c222cc63a0521661e10d4afbf91b7a32ec5ee752b8ce8520c8b718dc1c99eb0a4831e62748fa8ac423c861eecd648c9f18f879bd21d31f673324a50991f0b90a5711aa100b25a555972228450bc24029a698621010e8790af0c78390606e61483c5848b2d9fcc781602e6135ac80143f17bd0f9c4b94461d06fa803fe0b4511a756f2a3df501a508133d300976847483d2689c3c4346cfa8c593a79784dbe7c99c5758986470450e941636800d4fc21aea4c915227e84624050d564d7a5459d3990bb454d1ca818b962b587ce020db16244e645b66c8d2218a2d34500de980da8590ba687a83b96756b5f0ca66b3e590522d80e1a76bd0b59838a5c874d59cc4e9465c2b8aa454d5d64f1f4146a5cd23ca18fb299bf8a9c5071a7efebc7ad9f939abe797522d50f25216c9af3f23e2c896942d3ee871d2803c3b740c69b2e40c197106a115039494223a94ca6434699220c224cb134ac4e997852c6b2530154162899c35554dc4e932594b011b198650355165a94b9ceeb296d4031163c600f9e2c4081e71fa4cd66ecc60a489234174a8d2244e31cad634f962d4558273adde4e711e655fbb2819e3549543111f5dc41ab35c59be0d3239902f3d6350abf238bffdab2845e9d98766d6dab0c6fcae0ffb640bea144bf65a76cfea33a6e06f5e0d1bf8b1a300bf0c6bd8c8dfd3c5f4fe6a6b57a78e53984ffb935d97525e16de218d92a54b9fb48d2787c669adedaa5cb44e6f6169f5ac6265f5ac621f3babc3e1ad6e611fcc201ff10a1f65d76338e6d855a870ec2b402969f8ba0a3006a600c3ced6d186e55ed5dab09fdd66afdee5ae5bd359bdc67ad35a6b6dedac356b3a8b32c9eca3ccaeb9c2ba5a9375b526cb5e615df59acea24cbda6af7acd0d4aa34ebd5738e60c4326cb524ea6c9446979f40ffcbb6c39cf96e3bc837df072ce6f00b9e792fae7927e55b27fe71c5098c351f6138ef9b34fd875f706c518875426cb5962ce3bdce1ce47d9e3ce311766d9827e6005629a92ce883aabd7ccdb4f071de1494f7af2030253381098c2f6fe524a4cd37b140b0b0beb07ebe7e7e76762e58945e4eb8733666011f9f119583f33666011f9f999f12382d35a68f506782935e6e989e07a00bc940a93e63dfa59ae5a876eb5d69cd145359c583f6ea3af8f3fe10dc789158eedd8a5aca5f0317b75d981526275ddfd6c504f014a4993e1751976e8321c659a0ad26f98fb3867bd7e8727c0c2fae338e4b82e3ce1c7abfbf88489f0139e60dd7ef5f97c2c57dd271c27d6df7aaf848570c366b3e9f038ce1bfbabcf9e738e73093be2fcded88f347efe0d4d107b2e3cc17a97af8f17cc3f41274a2e0639f54c01204fe1d227c43de338b7e018e39c04792940197bdbb90f7841fc13b40ee417f471ee031f0f02794481790f17a37c6e4481401f10f681392663e158585858583e0d74c5eabfc754513f7b2945b5f4322a01eb5c388a601d3601ebdccb31f6ea2725d397571808d8bdf004ee7bebd8bebce6d45fe027e585c127e5050502f613da9d862f8e0bdd56f0d682221f315b4ff4524accedb5bebe9498a522fcf4b73bffd52d9f6b2b58bae8e86afd7c9a9e69cd67740804be689af50b8e80d433eb52639a9e012fa5a8b25e46cfacd3ee6e2a2bc7ddee17cceffdb93ff7fef4cf8d00ece7fa48f4edd5e9edb6c1a9f02ff7248cfa055fe15841f0fc7ad3eb344cd28b4e7f5d7fd170acef8129707bbabea91b3577eb22cf279d3e369739a937bf397d42cda8ae01f9f41b86329e64eb23d1db8aed3768ff822c5e7651dfbfe0d0537054c153eff5514adb3b9808f4bdfad7e7d3bd35e7bdbaf3e67cc45cfbf4e6c7d5ae01f99d1ca5dd3e230bec0d250c04ea81d3e9e73e3e6120485a734ab36e69e8e333faf8e87302f61ff77c8479d87f3ad8f50fe4b014d5d47bef3996afef719ee7795d37cedb7beecdaf0b611666dfb3f631f8a4e4515ecdaead5907235df7c6157c181c55f0d8bbfbd73d70ab95bada5642d65fef54803fa735224ab35eff7ab7be77b16656d3ae57d804bef79c88d68c9a76ddcaa9bf5e38b2e0fb3ed95d780276ce5ff37bbdbc709c38c45e57da3e230b1e3ff6f9646572208fb07c68bf66c164168bd9e8b6d96c361bfd7d43482ecc45c910c925b7e09955a1f484f9ea728b15c1eff4227b1a32291a1f8dcfdefa36ea5b08c86f975ae6a45d6dd42168c41660240c1b1a31216c541b55527d6b7c3822a9c0d0980120cc1a281a331bd66d369b0d4b8560f17ea61f65208000b010c20000b20b56a888a50802ca3f3eb0cf7b7598bbb6d29eb245bb13ea46dd2d6b3494923641f92bcca4ee4e82ba639cf3287bba822787bc71baf5fb59cfb65a1bc67a36258fdf57675173ac5a6bbfd63c9e9435ece3756e042bfeeb7dc2c5f686e3f5399d0be7f5e9f78439441ee904b10dfad3a716bdba6ef23897be12d51908f39d609741451ce25841949456eb57eb5793d4c945012b7230e48a2b941e30330bcf2c7a557f74178e35e4b15e9733ce2587e3ab4bdacbdb5fe1681d7fdc57ffa8e5acbddfdf5b433a43ad75566b6dad561656ab09625fa9d687a5c2848900fd32c6f96b92478c732c16b31f932da8db2362f8cb0b3012664d2c968397bf5a290d1bf258eb37a35c78c3bc2657fcdd2b93f5d05eaf2c7fd567a730df528cc7a4cffed12579c438ccde313b99f6972f77ebbd39d0f8de5a6b82ef6b2d73c14abfcfc72c6b63e7f5863cd6a7345fa7b2977de198bd1b96f0d75fde3011fc2f3756f9f80b6b067bf6ecb1618544e81ba2c086f3a99bc9d44f29a41497a6b72e27539728dae54a37047e7ae1be70bd565a47ab4e9d3a5d4ff090391153354c069e01e6a813f7ded72bcf5673407217d65f0a8688172a2c201982a78d1416f126118499365d412481c41df186b258d79251c7a6c88830955de7030bf6c105fb0823237297e0faf9f9fa9252385471d5fbaadcad5d281056556a959abdfb892c6cf4c456a6b13144f7ef7dbd9074dd1a78dc1a80dc1a88dc1a90dc1a96dc4b645f1673d6da98b52f7054b66a15dbbdb49db81f276bef8bdb9a46779a32d0b2858bd224bef7bb41cc2fb97b5a61b2d27433ee38cc71615e50aca05c41c982b205852b8a2d8a928581bb1777c30b73d6da97a53170659c202933448e932333a8a1a185166c3b64014283133259baf4bcf16287c814135d3e39729454d97952e68d8f7e72f524eb75efbdf7f5a4c6a27889dd2a89a5a3494c58c88262073743aa0c5902cc9310d22d0b0e4ed010e7a6e312b9f96cf101828b7e52b26b0e9a1684a009f3820d5709f0b1e4c70d32d8a85c9154963e0764b1e1ad7d7d16881a458e26505ea25893438a1261921c20141245cc953a46e6f870dc71f5d6ebc255d36416decaef0297cc29f7e389fb418562ee8b963b45dd5c2728272c4e5c9cc2385539a571c272d2dae18c10300c8104092e51a24de2ed142004309187cc115a6fdc50055da178b8a0b857a0ae6091dd9d7207c88e2917c2c85044bc5b2c10d1049a2d3a808c41224b8fa02c4cb7ca7cefbdaf98e79cdbfe3a0ba92c3a2aec2adfdbd599678ad2a4c5dcbd79b8fe5ef71d7f7d0806f1ba064457cf8dd2e45b8ba198b2093153a68851f3e3ce9734533dc069f232664a0f23cc4005b1302871c970040d9a24d04885e92184822d3c04a1468b942953ba224cf5da48f2e329dd7024285b48af5502bd6ce9369cb3ebeeee6eae7ec9574a29a5a46dd00050d2a477bb3ff5592ba54d6ded9e738663fb77fb77067f4277b7d79efd28a594b2b250e78f5d9367b7ec5e3e1e1e59bdbb7b8a734f1e7f9e0299e2808e5eb0f54c3eed5e06ca18f2e79eaf501cf7d28bf7d3f1f43cf3fc741f9a11bdc1414e51c9aa128a982e4739caad9f2e47c935278fe97309ce919f9348110b7b76f46067c7ad05719c4c1d38e1830d15266e7af8411c67139e1fa7d3d3cf39a7cf9b113fe738c749f5735a5dfd9c33eb671c363fed0413248c12a8199814218e93cb050b67ae14015385e68838b68debc7565a6a22ddb485c4cf5ef2938910354a4b4059f962471cdbc9070a2a34ecc1b2840f32dcf9b1a36e0d801fbfc72eb51ee32d2e8f3d533de660382fe0472e0e149c3459017fe124f9eb167674a7de90a96f9c3019ad31140f400b87efc693eaf1ec62b8fa49c3543597eefa1ef1f491f2f8eeabef76baa465e4f5232572d463c818abadaa224dc49132c9b14db520070a2855cc88236db2f32375fafe9162f9a6372b8e94cb776fd5f9760d885e746d8aed4847783fcaaa14dee0b005278a282822e228ada4a6f8f0660d19294ec688a3bc7ae27f945ba3ac334aae6f1b8f6f9f4d557c7e9c4bbe7d9c4edf2ec7f9b4c18f33aa3e97714e7d4faa59f5ed73cb851f679d6f67735543d417246a38238e934b3e91840b134ad8a97a228e6dabf3632be51f7be9bb892c8963337db738f6926fef9b94ef1f9bcb07c0861ec420c14417132ae2d853df4838c5a0f4b485440f9d1fdbea1b878f053c645942e6082c4d4a22c0525a224798237876883de7e99b427dff48a3beff5c8eb1ff5c8e2c58fc38f49f4b3755fef3fa7976a3e63f277af8cf0928fef3fe39ebf7f3eaf1ecebbf60df8c03f99e63d891d732991b21ff02ffe51dece845e5c8949a267f89a9c9e9f6d4b4365fbef3ce39d8518745ec1676843387f59c5fd811876551db8e6e3dc0db2c1ede625934b3d96c39b66657356d76159b382f9e7a2be55156f5d3653fafbebf09f332f3c4f3a7a482f1f527be922fbf3e411938365481b2c388963138a4c1e3c30d6b6a3023478f0f2017648c6e82c8531792c954e4c9d183142b2626551c122e2955b458b909b385e5f323d324766080f1ac91794f00c08eea0cfaf1c345d06479eab2ac270815f302239228e2a9af903fce306111e3664c901f1660a40cd61e3750ec88214e53d06de108ada75ea9abb0f7f5cad465d4bd033a968e24f1c306a629626da1079e3c3ae881018f48e1c819e2596b53780c765427137164310e5511a594a3e4f2ed729461bed57c7b6dc750be5d7635e6db9dc9b7cf96b0f3ed3098e0e1db8d58f85663f5ed41b0a3a6c293e65579cc22a07a46eaa9e7d1041adc534aa9931e236e4b77dad8e08814852b46302d998941c91a91be798a278aa734043b256fa08b466935a3d61fd8111d639fe5a8c1f7037e7c51cbb90937df2eebe17b4d13df6c9ebe9bd0f2ed3eb0a386214b32f203172f5a63aa6e98a2b1c38a1b1ccaece0030c5a7aa0dc6f48962161a483a70efb210b8b6fff78dcca631679558b12499efa2bc89938dd05c254806fc7b0a30693394995c609c53113ba68be6fa06b21f4d114356172b8ea51c74a46f1e4871e4f6f828099e2c4cd136be562c8a34c362382f1f296e64ef8d4ad3cf3429e95960493235a24bbf3ccf9760fbae623f6fc5acb3450e82219434a2579fcbe58fda2043c45a2054f29be2f4c0f61042429ac331fe3fb6a006b3ce9812ca58e48f352ea88a61f3d257eba062fa58e78f2ae7ebae41c7b0d6559f9eac5f5389659bca67e3a92acbdb8a4069facbda660e41b91c44e5bef35f56c3a75889770e551853d4f5dc7d350e8ba74faf1a5c5f5d35f5a5db4f5aad3452ffa7d37356dba6d93a98fb8e93da7d4ebae34beafdecbab703ad0de2c16d046c9c91e35dd7b4dd11a4e2269d39f50f5f0d3abfcf4f175f5af295a932295d5bf70936cb155821da460f97003112cf818418c1d305c323ca174459c3ef1748fc90bd345e3acd563eada5473e53575d11227efc983f2b04cf76e5df4f3d33d2e5d24a5aca27eba37d545b09fee5175ed13a7d79e4d3c7e4e9f4eadf4d440a284dc864cc5f0d37da888fcf4cfaac5fc749fabda357d66592a3fddc7a7cf2d0eeba77f6e7efae4bae1a7fb846d7e3abe217bf62eabdbc24d5394369dc3ea222e8bdbe2b2b82c2e8bcbe2b6382e6ce3b2f0d26c9a4dd3693ecd284b85c888e8769dee938d8aaa5d5d34da256bb34ad6666d4a4b4cef3f5a9b7512e3cdaf41274248f265b44df40991a0b2147dbc044a9358a2143f1fe515093efe854248300943f209853a4becada6fa40225dc4446bd8a99b64d965236e72aa9fcdf44d4e4f51b889d2e69bec3fdaa89f6ea3eccd4e4db75431a060636b888c6182be45a06f4237700891314c40436245606e094d1b0fac6f5062bd0d88121025003839d32307228aa0719271b8e060e4872e20748a282d11599ade3cba48683221c14dd3711376c24fd3719477e535b8c593435ead7bc0531f815eca2f2e248dd2aba73ec66a488d3425b7537947e34fd68f8c41245b50a79e656d0349a30e00000405fde8f3faf1f5a34f8c1ff9f5637d594317f689ec3fba78ea48334aa06e028542f513cbda7529abdd5083a6b5db50362d455107d6afcb9ea2b4760bf68dd266df4430640d6329c205cd1e2f647810db8d644d0262a4eca012d3f5c31db1674432467ffbec32c9e3ecdb673246fd76fb52cacc969f338a25d710d7196d0a444a29a574ec2f658e1cd6b4a086853b398010a5cf642dc8698f0b576f60a0b2224af76020cc9f445bdf1dd53e6befa8c6d2f93b77d10c85aa8593db29953ce6ef2c63acf87622ea3c38effaf3bc1d86918c51bdfd650cebed00901cf418e942c6981dd5b76f37e2db47a16ff73e10bb07be9c82d5b9e904bb047da869d23d5083a649e7c01d386c36117b07eea051d844ec5ea66d55d4b5ceb1c5c20c956ff7ae7d62fbac26a9da5f38fdcf4b29334cdf1ec2d4ea2221fc5f5b697df755577db3f9f69cdb65edde3eebaeea2229932d660f2cbadc40b64d9c608b9c1b35285b4c1c217892f802856460249a24debe6be8b2c5f4b6217713543cd83e49ecc00e834232300e19a509c91092f172cfbd2ec4a0900c21191d0e5988714829d02217f64db28dc93c3d2de1a74bd04221e9b4d1338a42cfe853cfe4fbf59147d992096412991c95d007b49f4e1e329cf43814925fa91351e740e91ce6309e36ec4fc75f9dec3e238af50c4ef61f65329f113dd93912cf9933a5499f4718031d01b9f54c7ed2cff71f3391a44997de4b2d7286fc0cc7bcf523d15b3181c7083077ec3cf990544874e0b2b3a3871da888524ad9840f524a9ad8d2f3c4264a0f41d62e8095468b920e3d5cc022522d9e27a767d4357ee476a19e35b08320bfc3af4a96bdd4f243d7736ed435aadf1f99bac702f419068f60801fc82a58c3067e15d205751bb27f0a820d334d93a9cb1fe554082374d1d7b317ac7a269fd61f5b864cf11246893265a0642862092171608873844b0c4154901e4a78c0535564861e940429a28d0f385944ea9268ce39bd5b822f7a7695e4e9560af7acf11737516af6b01163a445932f3a66e09a214f1d5183a7eea1144315858e0e30f418424712142932559e90e96265844853bc942a93f5764b962a43f5d45f8c444f892e8fec4f441d067523595b41c6ca0816c6bcd1618f483d0459c3c0871f8e5c41e2e34a0c22b541485a030a82adb8c0e7277f414e4127310702599822502c680538c50f4ce11954e117705e012906b9515f00c27c4517048305cd2e05ebd861419dd779decb7b75dd8ad78aeee5c4c7613e1ee4d37521081229c88ddaf301a5c821c5dc8b813f17ac80147fdceb18f8e329c0cf5580d981e6530a58500528c5d8e7d9a88380603e3ff99322eecf8d87612956585a53613583d680bc3a0a37680de6d5c7ab97406b3fee25841be859b5ba44ae93154e4127d9695eb1443d0f0cf21f30e61fb8c27dc0ec40e01461a00a2a605c97653e5ffd3519502905d234fca09ff657023455a72120d15aad3e5960165f89beb2902d2abd58b6a895f309034a0169855bc7806c511b2693f8ea19980ca88e43dcf98c0bc4ea2854b7511d0750f5a934976a87b14b1812901b750704e2ea5dfb48f415834e56f8d7a0093dab2b1c062a6571453883d2ea0cd9a252ea983abeed2bfc0449144c01293b17ce0072987b2b40a133f9462d6112b86ed454df556df51d0a8960bd6d2884939eba85e333ea44d49baadbaaa957d087684db28d7b78f3a7e4e0657be9ed4177e920d7885c7c72660059231a22808c515d7a01aaac654b01b002b245fb042d2043d2ba4d17ac34df443064174b2fd01110aa9ec99f21a36735742224639454200ece55d0719d0b8548c02a708ec32852e490ee91a4429a5182093632ad5140d2da8f902df1eda3b7f9f67126b6681ce18d9e5d2f9efc40965c9ee87a223dc38e044f256a257569e712c5ce35e5713a3d76393990d95edea0388df3c9fa38a31e63bf40d628d30a198366894d0e2816ec30ba28e62abc2673910df063065d96e4c2386799cc2320b111198d4c8d74e27165f2187b084cc8211e7b97456b358aa461b72d9811e15d64403ceeb2a63cf62ecbeaf1d5e390cb6e3df60eb990f8b1d2a192524961d35b7442b6c03e3607b58d74813d667bf9938aa6a158f48a52512b5a5599a857d305ac022daa086b65a2cef97a3539a85b46ea7075b8baba257acbc24039cb7edc27ebb1c3bc6beaa220c7de2dd19ae5a223b65cb4466f3485b7e57aec1d97ae61d9611e77552c6e96e9d645edb866d16e49d76a5703798a70c4afc1d0108b1f6b168b1fbb1aa6ab9aead6535cf211e5d133ec40a852cfb073d1e8efee6eeb144c6185ef99c2a4b69ee12e5a6b117b908c41b5e4c901bdcd9e745a3a9d6a9131aa1bd9e23639a859d3050e0a2b57cfb0d32572cdc2519dd54f94861d038523ce391cb3ec271c65ee4cb4566d92861de39c65fe9409cbe4a0b3a68b2c3246bb01ed2d8ad66a1249c38e1d67d19ae5d234ec42cca4bc8aac87c751f5c9638f7a6cb11efb178e9ee6b17be128ab7aecaf70cc36cc63efc2acc763be06f733ac59ed46c6a870648bacc941d59a2eb063e7bce4eb23bd3d5ea2b72e1a2717972e1afbeab163a7535d34be6ef8ab2ec2de585dc485dc553f3d762e8bd6844850e12b7c45a8c20a57e14bb4262577b5c571611b569231e64bbc24f192c44b321c5be9f1d263f75480382e701574a8f00b42211256f8057e41a8c205bec2a58843855f106618928a19450863af36ec55097b5dc25e99b827ee898be26edc1447c5597136cec629714b1c13d7c439515daa6b75afb2b6b8640c1d79bc4d8fc76a7bec35ab6e552e6bb34a76e93639d15a92885d0588fd46d17ba3b52a62bf5332c67cecad246b938ad2b0e3a5cc84c776a24bb9869d55b205762e1983730cab59b205beca7aec930ae32c6baac72e877078c2ab09d633db8d2e1aa747670d3586cb16dd3e2ea037901c80d245e7a6b00f7e3ecb16edf549ee6ebac1c525b96892367d830648292b431e67d6d7e9ee06f2b979b4cfac79ebc4d23539d3f40cd3930be6ea121196e59ca9546e975dbef7de6afdda7aefad469a640cd9bda474d24b6f489d4a6046f134ec1fd9a36102932c13b23ea7b76ea09b8cf9861af80c5a23b282854083fa055358e1670a73848943083eedc47593e4ea1b94d6d3c6249365a7ef0e7ad6307ad673ce39c10e8e68c83dd5d44dddd44d30a877adf7ea3077c31a82e48c319e21b9ac16c961c2bcbb4c9633c623f46c3ed1709c717d3fcda7f9e44e79945725b8cb64394f25fa6294571d3497a69fee51af7e74567d466944335aab61db28addb65325b1592c7f964fbf6f934adba883ac6395b9f31494804cb8211c7d370265b4ca7eaf681aa3972e4c891c2abbdddeb4b11e4d287805cfa0b295b50971e1028e5acbbdbab40120d4f39e8c8e0254a0b94101b8485e3f7f56d3c1038bf41f9383e855390c6f741b34abfb072e5e92388e50ce612e6d3c60f58e50ce60d130176c307947206f30913a1fe07f37cce0ebf3a2c6f642efca49e3cefd3ecdacba52cebe6533ee49f7c812634ad9d86a04e3692d8e839a7d69d8e328931e644f0e7dc4b81eee912a16d9f02ddb3a7c5f5b003deec50d07fbddeb046e7a0747107f0431bf8eab5c226506d3650ceae13e0af6b0026827deb163681fa9d7b54ce4aab4f0af4bbd0658bcb85f4b657a75b1c0d97567b47ce895ef44c8c19244c5c4974e92023769221884842a9861ae81c3181ccc97acd99e3c321f161ebd112cf862aeda16f1aa0f000068e1922a4a2b869028b8d9a234c55e248f4fd038b0172a0c9f1c39b2a2e203ef002d410383801451a17a60f2d18bc94baa3e44e0d5368f40c7bce199e306532e4fa8df9dbdbb3d5551652f728e370cf3e6bc30f06be8dd92392b4e953f258bfbdfdf3bc5fd873eb9e67633f59e44fd2e6a8c14f8aa9d38825fda458070d5e4ae941f3f98503f450c5e18bd5ed538e9992244c1041d8b1dae14297150deae14c97265bd00c792376983f0e7f3116b497527a94be4e0e0578297567ebbd97527774f82dc5f723db8310b0f2f8c594440e9cc5a4ec7b2e19be180b172fa5ba5af8a097527698f87188fa70fbbac804b23f617abb7c19e5f43497986e4c9bec2b8c0225506f9f3eca293d45be6819634588942444ea1256010ce810256a071528305d620d43a0b449695d44ea8c143b3ff4181147188ff5260715971d803cd51167931e215aa04491c549bc000826be2c1102c38f3de2e84a3fce5aa061050b93168ce1d1638cd8e128a97a7c1122cf0f2d678820421c65160b3fcaada922926ca599b3670e10e2386d3c7e9c4ab28d921c6a728c29428625c47132edf87136fd802a13064b8d141d2e88e37cd2e14190354e58c8dcf9a1469c510c71d245081613737e10678e9f1e02adc9aa1dded8808411b63954e2f412688d045cb28c6822868927a8c4e937682d290725d40ca16487eb053de2947194c85069810b149a1d4479a76b8917bec0b941cc1471dedea857e8a46e2ec98d2a03a4aec96e6235f6ee965a4086e090068c6f67219319f50e3f1ec89b918ce9a5e463f172fc0a8d28ee9e3620be9bce96ef2b76638a44083a4abe7d48031cb6e30e5920c95c07be207216b26e26f2286f1e8c603404e3ce9a6f4aef687db7ac8a422fa5ee80f9fb52ea0e16dcdd158f92c9d225e7c9f911458fd3ac61d9c224d616e4717ef5922c3121aef011611b080f53e20a1dd4208310913a8ed9435c417a11691845687ad2cbf265a1cb95b8820b3c4958714a554ce20a442c302d612c6b3640e044ddea0f34e20650486cb48663f2e8c1495c81ee10bf66c074e9a3cb5ee697d36d0eb197524f604d9f524ad94e316cce4fb668c760eec852ea09ae6f9731a4ec287dac67d30d950d8564581cd8ad27892f4f123f4f12619e2424e33ae75c8883eb3c49f43c494c12e3e4e91f983d5076258ff97df6248f4e04d63779240a0134d2217b4e6372d034246d3ac6f3496ad170cc52eb41305d4c1fe7130d1b37704c1e44736912994c5d24a5ca44fda43ffb496275fdf4192599d0f3d34360027d1054f7aae44ce66da040a36713c8d528c968fdf40a6460527549f586b5825e6a915247d61e13fb70ebddfde8a34b7749938ef3d37094a978b925861cdce50d8c8cf3be73da5a2f28872897375fb44a496bad94d24a29a5b47ec913574eca22596badf2daa8b55689ddca7034e1b6c9d3a9eca573b023e992564cbb7e7d147398d2a4f74929e594eff5f98c499f81a278b33faf7eafef38d0035f1db45f530e731f75fcd52e9272465dd2efe26aa7e2ee3ed1bd6c9e9757bdef931fecc3b43625edbaecfe3a4d8108dd572963ccf7beabe4640d7b182ba23efe7cb7bc76f2f47c3e29637c30eff5f948d9e2de5026d1b03d30de8ba6a973a54c7ef5c095614bdc64cf88963a42c664d84ba93a3cb6f22b0f11adc9a613c7eb2a7191a8a6495c25ffbc94a273e5850e159d294fe24c27c98fb2db04cbea0ff65e297e3a6cc45facc3748a74218da1ef0e42f5d1e101d42d3979795e7849e915c7e35e60eaaa84ee10273cf55921531c51959ffa619c6188ba38cf394d525022182f5c594c4444e7b81080fe30ce40d2344d623d10ad0a3bc232a22e0e06dfee2f6008b10951a25826e7eadb59d86a1e36e5204c419a1a7684ad5ee090e70fe32cb32fa5e444fde8415fe64099c3c4c91ad1734ebd7a9768a867b3e91d4496bd94aad2d266b22bc95225c44ae65e4a55f9002203e16902ce54fe9888ca9e19aa7cc1adf3641cd554b2cf4b292372789a23df9752462c912fe789d264075e505699c8d35f744004a38be69696ac2fa5b6b27e94d929de387ffa7bf49bd4a7535983a394b2c3f436faab6c21c7eeba7675ef82b58698d2e87bb2526bbbdbd2a111a8144bd74c2339ca94442403000000b315000030140e878482d178a026fa3e14800b7dba4a5a441688c3c120866114c518630c218600400830c420c3d854b1002e16191c79da25a86b585dd58671ea9a60ddc7b77b2d358353cea6cb8d194c125d979a33c7be2f77d819580da3c1462e8207ef881cec82d023fc7173b9c89b26d682c1712a6e8e36f178917c768946d7edc34bf8dbb7e66b2f7b13ef34128f9219e7c7af4662dd55662561eb219e72a8c61561d9128694f88749fe1722cc10ca9074b0e024992ae50cc71392720265dae389c580711a435ed4a1d2eae83d5c421a5ffdd9175da1c0f246dae2d41a1af9a27669653fb01c9a83763945373765a953467c9498e6c58a9517408253b5eccd271be08843ec776f72f788366f6c15a691a48d77e61c5c6212891e8c1c8c05c71336a43c9c08b5fe28cc5f25235cbb9ed90ab2173e0819aa7652b6d3da5dc41eac192b1067fb2419983b5f88bd94997da2ce547395613142a3c2b23cf2bfaad07a662f6c726928400e89e5af6a9aecd91599fd9d41f01e027925e67ba9692a03a1cdb3178582dafd4c2a9e783e132fe4ad56d5afba5035ab0f744a4f60b14f27d210133d36fef4b45c0d1c8a9ec48c468eff2379acc1c031a0045593f54dbda0dfe09eef95297c0dd44c7d9dc756a40603d62a9128a869a5847a9f3bdec18fc970685c55f1514d05d46b11de1c90f5705cb329a6ea2d05b85358e3aec6bb97d2c6a64f2724c3654cb462e820c35707af3a41788e3e77025192d15ac075d9dc89bd8d9f0358e8cbaccb2fe5aedb3c61b8d8f64a21be0d6d63fbbd9699dafa943c895ceb008b495d74b425a4117792d92d91dcdc07fb99ee822050449a6dcc0b128623302037b864acd810ccd81f33496de45e08b679f11fa681f0a65fe78a4641286df7e32c2c0ad2db6145643b4f37b99c52a1241312479565d019adda3682cbc1d6d2e31fc29a2a48015afc03f3cda540a4bcf9c7b32d5d02fc1603917f9d8588d48901590175bc71b7fd7d64a82a432fc9d1fef10b32f5760d280dfca781f851a2c34ceb4bfc3ecc6732513a4d32085c5e0931ba7cb0fa4f6ff63943ae16177450dd7f57dbe3f128a6de6afda6ad5c5b20a7e9dd03da1719ac0c63f8a6b1b877e48d6c07caa24d4983f03c700c0436bd940952db262bc298ec412a893f3e8760740315b4f312ac7f94c07402598f53df3a3d039842d1ab9de3b639ba5084351c6ce2067563ade4d41f1f09af3f181ff68936ded1b3edbfc1b7b4d9821b98a1db26dc650e0e3f36fa3d2e9f8943beea6d5f83cb2d84ec7974c543ff786431b256fbe6c08a6353c8b25641790dda35fbdee117559072bef43c1b6a6a351a9dfa9a8a0c411b9b643c274e9591a096669aa52e1236a459959f20eaa2fd97fe48fc2752c63199de7c9d28a6620be841b02dad5428c531e0e845c11b654b1aa94673f005344848f5f7e686a529251d4d6537244329dee0ec6309a76524570b8221c8ae1a80f7c610bb0fc94bcf79028116d3a57495ff30f1becc7136d6fd950be298b967d33f81d96dbe01a9c3588b678a0a0ca88c209eb6eb5845f3744116918e214155361ea7f6ca819dcbbd0020126ce39e5560dac6324e5bf4e4570e5f6752c947653f7a9ec006f7911a622f3961cf8a6cefb14cdbf028df765ce0d7347cc480322e5d45d3da315c370c39763ca9f4c391a516a8a71eb7aa52c0e1a4b1e53103642f246cbac49ca9233bd8eb5fd29d3c705e7e350d06a6f6bfc384eac8c9151d218c7c1a605a362df13787c3d2e5a7d551d645fb996985c0d8be4b20c7988aff42bc8e86b4eaebe8502ac024d275623fde3c2fdb2035e1dcca726f43ace01e07d8b0585c7a2e814ca4b62c43a5d94b4b62e56aa20db213af71be453909e9e0417ef7040d513cf0ff0778174050a3d43c843b15b23f10f5aa9cd067c0117d086be399d979dea6bd6fa2cbf74505985c2eebb8510d64169451220f74cba86a902f521c2278beb36239bf011b63f416bae8250ad2fe67ba4f3cb2e06e1484640bf54082cb178c26164ff61d2b1f271337da9e64157e66c1925d3c0b37155487616c392b657d5a49975c73560b5700bbab9a6238da85bbdbcf27cbd41113fc8716b99651d5bb00bf604c3bffe59788af6067476c2a9463cedd0acd361801388e2337f2b87a008c9a4de842181059c47d3d0e478ef4a96ac95a38e844571730c5c8bedeab5df56144006162461f9cf7227c9883183026e456d5de395cc8a1e4a48b18e07d5b6dc42ba6b1c61b34785d7ecfd36f70bc6eac6bc1f1de38894a9d05499419ae84b56a0b449f4c9d75f924b7a5a787279192dfb104de7225cecce680f9501d05bef0469f63efba3a8758acadc4ee1a4dc0dafbd3e047dfa5579ac7bed299aa3383f141aa2bb980e78414c2b23271cfec2df27396efd72d4d33e8e857e69c9411fa6748887730a26a4c3f49b0371c8f4ae4c4fd9d3a70e01d43e1f15279ecae5665a9918a84aab2b72bcf778025bbeef412b6f08ddd0a8673df805d016f57e4e7a84745c8d8961b644fad3626b1e7b720231b984ef47689e8e9b00da5809bf266afe4842271a35613b131dc44cdb8d906b545dadb5feda5212de77b3b34ce0e051e2dcb0dedde45cf5039fb58baf4ce8a56cd22d25227335baa83b4a3ab65b131b3f1f2b73f9df4549a1d8e56095a419b9094072627e7fa9b644b3a191f0007e91aef3b10110fac2f068411446dbdb0a6888cd098fdb260af6f1a29066dc3e5e4109e687c110c200a771210b4dc798fd1033bc729163b7ba2c8b673b7a6579e0064f4fa33099067437ca986b21b8834b605e17be7eee86b757c7c81b88ed7704ff10741303f6be21f75e3f8677a7d79165334ba6b28161927a41cdcf3a0c43141faee87042d020f43537fc77b95c820783d713c4749d8f824c8a4a690ad72a8e5ebef26306268929ab10687e4d23e917e42c9121012acbcd1f3ca72ce4d7702a379b3b7e758e1a7fb75050a2448a655e5c6b795867cf39850a50625c82646f6be936552278c99ba2e0dbcf08301fc6e75998c505a3cfa50c40d70c833c3b3bf78450735df32e32b105a0ec7f1022bbb8439ed2a1b235b70b77dc9b48105cfe91d374cf29e91305e02022a237383429bc150bcdb0b711f1741f866bf333091c1d2e58bfdbf79a5a475656fc569c96c4372a1399caa92ae449996475622ae63146c8463155be12ca2c753b46b18d6cc965c45dbc7136de516d50e35126491aeacd3825764b80671af65f55ac311792d3a214746ba565263bbdcea01816bd9c3ca1a259793c472e5c475e16e0b374b1fdf914760ad6611599b6377730be4de5ee91615d7e49c582ff8642d37161b870a16a8631d5981a5b61431d5f2384e057e6e1fb42a3e1d9e6d80d68dc8f21df8a9eaa42bab3aaf5d6030f4b8bfe5c59e3dc11abbaf50c0bd8d7325d093659bca093a13a35a722c9f1504c3aeebde0bf30f67a53728bbc57ff8cabacba9e3b81b87bbc0e7ced16ba295fa0b74b48601a9498f8759f14276475dbde7ca5b5945c91cbe48e916b512dfbedf2acc10ab7a707d1468a81beb1693a55108c7d93268feb1592d54962e0d598a9c49501a3d55242db5d8159c03254d68bf2b867609f282b60118b8997af654a96465a2d8d156dead00cf30e29e61a38ac47d19688bcd1d08ca06a765fb7ff1e1ef78eed2e21fc875421e6f35ae12e2d8186d207f8127902843911585540a250a90055623674efe9d37e32c908f09bb080be9208c0c7cd45fad2b742c9c396cdf47bcd9b05a603a17ef7cc34505db0bfa98a668fedad0ce02dd3bb181c73ca55daa4e8378de63fff566817b955c21614c920c3603877fc8a811f114d44a78f4ff741d7b8c42f5ccd3cb4dbeb9069d88cd140ed2a15ac7aaa83b87db7a01671e508135667d16a902cb1420d9bea6a5673125c241fa85c8aea45017839b7b55098fa18796511cd89cbb95b30f6c74efae2af2d5420255edc88167bebbbddce0a2d58cb50d1433708c2e1421f0d9e22bf3161d0a97598b7510e696eb5257185fc0258f6b7b0a90d7fe2787c74ea4af5c83eba13839f258d8f2679311234037cb8c6fe8c42212289d16dcec738615228162838570570670fd17e41247bcab36b35aa17aed6a6e2faa93b7ea42f2f78dcacd553a13f31db43535aac7d51e822dec27f4958492289b5241338685d33e6237652574b4d551cfe991f0549df5246550a625067364fce202fc5661e6f67115e29534a256fce0471be6f59ec16519cd7eded3d4146599a42cb6f71c22351e07dec84d20906fb8127270b224e993dd1af6340252ffa42aba7525b6a19160d1670e73f5def6ecbe14c906bc050fd48b5e849937504ec8d4978ab381f2defe5f0b5bc6bdade2e8a6a9d24e8028b12d61f89d28b686113f451b462ab3b6d5cbc402a2d360271dfef3ded6a06a795714832bdd2b82f0ae6b415aca75a2cba510c5f50523175ebe821c3f08ee6d3478f46906776e0c84aefd4dc6740b0b3e3b56ccf022ffd7e4ba845276cda9fa3f7a8b7ef3ae0e1ec1c5b14dd1401e3b7ada8da48cea5910a81b4e5bf119359490c5afd118d4f4ae8fcf10aa5ea2661620dafdd804454417ccbce9847a4d7082bd64f226b585234ad45cc4d97b4eca1498e9c3f53293af903893697b1ee4a74f5740b212b201d15dace2988f4798a755db10c5d192e8b5f5fcbe6b2c29761417b89da9d53ec52319fa2acd39022100c827ee6faf61f27243cb1da675bfeabf26aea9bae26af1445649443775ee9d871b2ba3ff8bafb6dd67699d97234741452c060de6ccbc720d0ee0aac80c420e98b2368b5c394e9804c3e40489919d869e2beb68943d4c7df846f4e6b8a769bc9c6e9b564bc2cd7e58a9d10b4723422a926c2554248bd7a415c4437692cf06199df5a2468a6c4f8d0bf94f33939d303fbd27d6b6cbf1fe25fbc79496b5ef13a94bef4149bd00e4fc3c9a7c54013da7c991bd283cd10c7ce917895824aa9d1a1c83fa93b97affb4ed08045e53a53d8332080a74664fb93c68da0c2d921b1f5819f53fccc9116ed7ebcc0b55775fa4059dcf81ace4a84a37df65d83cbf591b0a48608f86897d2592c1b99436fd371850f313415405ca7906c145d44e877af5abfea2593ea368695f4fac20475947445d77cc067c550d7dc2e03b8d1f45bcc8e469e4644f2eb9872c2b6abbe102978642b9bb4ebfaed4aa61f6613590b11540bf15508dc11462ab6d52353cea0747e47c71cd3d00edacdff494134d4562d3e7f7a9d8f09f91143393bb59019590e79e988dae0269220b46a172b6f012388f13f6d755e77920d286327581016513fd5c6d16b0bdfc246f9f8b38f6e5096807e414e661b25b84afb245f84b1ce0b94db3bfbc5614e6d17be369b72e2f27229e6a48b7a405b73b2754f81c1ebb53c25131e9653855170aed075c5e54e1285d0f89e40da04c33c28af2dbfcdcaf44c37ccc5909d8cd1733432fd48b83488f47c6e64f9d33047f25c71a00181f7d551728eac7b1e921852013c078f444f30ab4a53144f3623dfd9ddd1d278306f621c73dbc2b326f837bf239dec6cc738cb1394ec186201e355fa01dbaa38521d955dcd7228221ec72ab56265bafc40f26a10d83c952138e7d808fdb5f745a09ec74db495e146a3fc84b0058d7b40061236e64b405988bf749e0dbc562f6e3e7194a1a5d8d94095f952c080ca01ea139c1be3becb4c71b7fd458cbac18c4cb99d1e04d6bdf8b0dcc4f9bf8fcdcef144cafe913b4e62b8d57d4586246757c6d31630b8ac0752468d8fbfed0831b892ee22bf06d058a14bf53949563fb1bca9eed958d1892203a221a28eeb615ab64e60841764931bf408434797cf29d330ce670aa92af2ef395fe026b8c3023445233b95563caa53982780276de19f839ae12ae55c3516e21ad6bfb53cbe6e433716fa4efc9a12954c521496f141dbf2062915fe2ca8552e5152274d808872deba6146060c38802cd01b4d0582c127ae2251b0ef085bb2010a03b76fd33e90d3674833076b550d0e3ca1622ec5c148bb1fa9528dda86d2d59f6dabfbe7084ac04b37c217daa20b9d8409db3135de76af678466b572aa3982f137d03a84fe706c33c2875285343c3dac1a27bb42b3cf3050e9871460f3599b99d7f4c2c7834b199fb5b9d5380f5cfd3a2591071abc3234f4c5999a2bdad1f2628f2d85f64dc3665b8ee67680c80c1ecd03f90ccb0735803a241f1acf567bb174f533d8a8503f3d23b3f9f02cd4425b21b3484387575c096bb06294e4af645b6034758e2925baad8029a844444e49d6fcad82f75062f99b25f6cb2a1f75bdd596bf43a0d95dae06c12701cd0ba2eff97319fa72f3dad5d1db8b8b70769529b207d3528c6662b778d0dac50b7d80832acb79869c911b25cd29c7294e49e559afafd65dcc7348b6e674b67229ea64416baf415b6f2f32858bf6ac02c967ec1579956623cc0ea6c7c82fd3e4a84fe13fc05d4ba87933fbe73bfe240f545136ec71415c95817f3d62fcffdbece1b3e91d64b8f064951eb7e4f6fb377fd1843f1003c48dd82cc9b4a9591c345edcd019d320a90af0e2f6da40acec7436da10275786a2baa786e3c6a5515ad83791729d0f87f80a7e964ba3912def2c715b41cd3706c64967c5183b62921b7531d0ce2118e89c11e8584a96aaec9827cd1638a2002033374a243b5131daedc7005b56e7065120b7c39223a162e01916b8328b79d89d824465f996db6dc30d7edc369b35ef16f3ec8c3fd69da69b77ce6eb0464849b6601224b0b336434a6980be5fa28453c3f677f6f1eff1bb7f9c0d889ddc62d9a199fe396300de1a274ee8386f6a1550c8616c1705d75351224a46f9096b962c7b9fc24e8268962602b7f4be81fc6e226edb74398da6f611ca9038bce2c5627a3d0a1a8812cc2033691673e4af98fd61f67ca73231409d230a034e2cceb5aee1114e5fe6eb23b12e369c62527090f7f2c0ecb57af7210784ecc4d054a379a8b3133c66d858e8315192f691c3ec8e8e89aaaadee4850ead64c0c2f54fc10214a44b0f99fe2ba34ab82f9a868f12e35b796fe4b2c54f132cb0529427376a865cc02f760027ba9c23e610981ec1881ea3e4588b6aa8fbcdc6e13799e159b0673b2e18fcf21dce5c5d6c78b2aaaa69aa5173c03779f242be80fee1346558c7a5dcdeb04677582c09e6aa609dcc17c8adfc30270f3798180669ed87b96b2f8d389afa5fea87fbade489b92867692e6df596a78bd421d469e695111ac958f6e6c3fed8ac13dbe3701e7c360d18e51be8b51dd7cf4bbbaf243f405be11b471c8fb453f607d58b08984f7be450ecc7c230a6be3603e29185db980fbfc425acc0276882166a2ff84249b8e751edae625f683bc683d412a9ed1a317db38b7621ae72e4dfa8928fec5c299a096da2ee8594d37f3da4692fba627cea66be7e676cc7ce18c6b15fab1d1f22b7d5eae0d2920e6d988af7493aa953d1e1950be7d1b9bffe0a58f39afe8a2544643aa3eb8a3424d046c7cd2cd722c68757442c48bc6df225cb4a73bdd5872e90d7c08f4bd3724cbfee2ab0ab81219046024daefe553e37b7e2647ac3d5fcc7eac60468ca5af90044c3c02ca249e4e9ce3b58e5121ca17eb6cd070364ee3ddbda0d54a1073436a5f7f8d200f900357058b29ea156b08915d321af9d8d07550d675a113bc63b9e10e7fa35708ed0ce6c480bfd4f9f88f2cae3e488c8d323973b3c5be9a587da8a35d339a6bf44ec6a0ef4d03a37afb0e53d393cc3605f99163916f80dd32e84293e0f74aa064161dbb48d04b5ea8c80b70407ea5e71d6cee557ad0cb18289c0cedcddda9e7e43c050a92fab0d8d93decb46206504534477dc9b48201d8f51ac1e02954321296ebbdf21835f4c42f263b43f1cdf5de625b8685820d5a2f3c24cf4a3837547770945ddf07bb154015aa36f13c03bac69d8ad52d8142813b3e85ad80c7a7aa3d144fd6a08d20a884c79828424d7b263b0d6055ee5b2737cfef2e16d56dba7848046292bbc474096b5a386bc7961c1907b5f407a4b9e2071c082b34fbee0b53861be9eaf0bf83e6adb4816367ae0f1ea3b22593f5113810eb2ee845ea182d28bb4ff8c047d55945f6a56ba3dd6a3044fa870f084277866eae7b82262d47d0f3ae1711353ba2cd82837437fe93e0b034c6a07fea06b9fda119ec5c9ad7cc46e3add145312ff514206a02b420f687b54f6ffabde11671a3ceaa2ad745f9540a1abc5d753a84b8a72deffb5fe765fb0c346e8398d29bb7125b09527a04bb08a18c834207f37db05ce08dd06eb8e4f6fc6fd31948233c9191c72deda98b35b3bf3de674bc772d1aa5a566fdcc014a12a22d6146063ebd374f44339c10f0dd9a0aa1658df0e2a3c635335c4104428e393206a6be706ad00fe4b41fa992409e3e072f6d4d7ac6e2b0367a325b537fa56d9672acab94d77ff103060c9c30a888e0b6aa4aedc704b1387f15575b9e15785f20cdc4498630b887bfc28c9627a91ea5b46a6a586b2b0ebef90652f5db340737d9e386e7951971b83e1dd4fcbcfde9bb5fbc86abc8ccc74653008c8e180af8833abb2c9f50a4c9da8fbeb08dc95916742d31a04457c91ec7327ea10ce75ab091bac4b8df5e1cebd01851be1ef17f55bfc3a22704b4d723016be25263b0672c37fb3963f4b36cd8d8c12174daf1e47c67abdd2f4a973d895485bed0a49b4588c9a58e79f2ea839bf130f121272a5be7bd8a35cd7284f97ea66e17b476bb6e397952dd1fed25a769bae7997ee0427a1385464d94febc34214dfbd4f7373b69c2fa5d2b4aeb5501c4a571a60bfd18b2cfe9e0a4097e973bc499be69b0f7ec5331e41ca777cd50721f59ce7e980c4c4630052fced4700e4ad6f14661ff981aea8ca9b3fdcb6e0e7b0a3d5aacc3a74d2d6942818f0c29c2574804c4e091a6d3cbda4668cd2b6fe9e164f3515695616093da48680200c65eaac5c09afe3cb7c67f333ba300537daeb623af1582ea1e961f47552c64b500aaa22c03738c9f19a8a1f3c2ea19e8261705859c3b52cb914bbdf36595465008f116b82c8ce2ac76c50e89a0ee96e67343fd791e8e1192bba4edf78ad0023d40454c48a2d1cfe60bc0b8e564a498a9d72a57367ea993c18a4550d89986e0fde5f3a32c5244d7ea1684f87d819c1879c1e3ea934bab5e756ea4773659f63f7892570f29709c2463357230126e87231886f557d7dc5dba7a025f80129eedf63f2c074ba95f1f109a631fe3c5f650f95a15811586434eb54a4e87e4632474b9c3d2c59c5cfea62fb480797bc8a90c6ece557f9ed31eb179a70509569d89ddd2113bc994b50d667b8d5b92ef84d6c579c98717fe50ee54cebab267e6643037eb8b446479b49328964a10bbda7ac7cde40f1132b50ccbe9ddaa2660afb584efd12961426f729f2386a602884a13750a5c6a3f3ee0416810c26d238f6d04c8f3fca9bf93d234ec71564a6e3d40cd85ab82374753d7765cda0fb3cf15c21670af20c9bdd3c5431c34ebaf92ec70c449429609ea1e2f1772373286f282392d358b9b3e5abb62e670bb87087d729dbc697d9ff54b3b560d46e24a1771ca3dba1d321bc5a137ce877b5e4821431115e51061ce9f54eda0ff5b78a126006223f7425c714de9469e2e2af0e3221473097af2f23cee45f6bf7a0079fca44a4903b3cb111e5d4d7e4e2771db397e827a0e529f118ab65d0d793157832404d86606eaca3abd0a236e8af72a723483442d9d86858fd519156900af331fc4fe5397bdaefe2171f256178593be23420a322374711ca71a365606e8cc0b45a23a1254f9ac7e3c5ed0264d18d1cdba3d28409e2611ab8b4b30ae345a3492c89a77e36cfd2ca5ec6121a8fc8d8f66f146a4d8f4594befc801dad5d709348a5cf55361f5a51cb2664755d51e3b20afa7667f7ccec7190c744b511a18134de313373171823c1e225309057f0415998ec01cd32b7cca47e925cbaa907091e6835735b01f103b62adae7346d5d472ade2ca4c5c4899a5e2aea04e756e23f2a55e3ff1e4b11e694d34496ec60c258bf9fc9c7384644cb808d13c0d92288c09a97240675d64ced2de9690aa2d27a789c7d4f0763073378a86b21aeec669214c5f1491752c6bc1bc62781c20d6c6ea8c3b8ad68d6e4901f170bb02a77dc7edb5f7c423f97ba7fc8987124ae6d331203e94028cac919a6a932a0868490e9a559f85c0549d36303a8e32febebaa7c01d935453fbef709a271b90527947e77d9cd6f25a9690d58bfd244c5b68a006a01e4745c5bfb4fb97ed77b9f50722a5b00690ce803bc01c0c764b10ac82875f39d2a8ec6a5af5fdbda5c52d3457d8b1144736b0b44c3921d3f695c037c5dc908384a66055a97a99c642b4ce79b3c92708a4bb5833b61331691e4599d5c87d8a5f16d957a5f6501ff9b98b3567950cf1208dbd0dda3d999f44293f7292e3b28aad13185544a4403b2d13e60a213b18bf3a5eb9babbf2cdd9167939db9388db27a33e04197a9c1be6d58942c3e74720e0b5080fafea913ea86c4faca7aca8ed8f71ad20f5559489402c336ba8eff8a2d60e4262180219bc869b6e0d274be2407a3808b8c49ec4667360b7cc293aa00542789fc43aee8a26de1728bb1827aacafea8e8009492794e8e16ac0fee541671850af71baf5f25f82165e8b2f04f2ce1616c0f4876f5b95709a86810336cd72438263d6f7f6f2cb13f7a252d10291bb6a2f7a1dd3f812972d81511646f57b4c08923b23a49ec7d7dfbb745635108233c3fc07f0b6a2cfa4b0803c344169efe9c76a0d0e9332fca5245dac4a6d7ad20ccd268a42dfee4a6ff44d025bb2451675fbf5afc413063b08503bf5517ddddb3516260b52769b5f84d8ead7c52d662b1dbf95bd15fbd937e232876728b54ad0b22f8e3b3196bc0d1bf2185aef786902d149b9e3f408d416e092303ede98b2f895e26118792fb059618d50c566b77012a11ddf43143f9be4e5315c7f3f0083957fa0202576f1c7a830c0d05569f3f92dc5e7c761f017877bc4bef1573af2964640e9183458eba5dfae03b4e2a4c2714ee4c3809ede09787dc40c02acc41e870e88171224d615d09770029face40895cb06d0f8fbacb58f61a467a1892f4ecf63694f2911e25ab32aaa318f3d00bc42d2071347099509d56a46e7f34944c88f49c4f12744c20ce78f4a55b79879e46c7ee07c84ad2982209cd3b748648214259709ff38e1cba4d12dd3c144fa76e74174daf06f74a04829499aa31d561595283a46a4bb92163d87cf35387d1c682de76fa0f09c665cdda212e0a4e76013fd4d83108a8ee7e48cf460c421f6068aa477951cabe06a45caab1db07218734467dd6908bf2acee961ab2e85ce1ac7e70df556c1c5f671b874331ee23c4184f94853709a0f9645536e3ae3bb73b49071cef19845c2e53b65ec98a0699cd5f43e56e2973c90879100168a4aedccdd1aa426c787826951d7e2495d7d11a914e2ddb1a2878507f3f580f74d67154a7f0e2db3dc0ba78762d74f035f6875f41d8365839522901614f03fb27a4a074fe73cef197b19697106adc1396286ea2b84b308ab647c5d3a5ebfdcd77171b08350c7950421f2fbbbbb81e6584837630a42ee7bcdadee3dd24361532692227d7798be6ad0f7dbdb57b984ce31c6a1a24447d8547bad7a82c27aadca0e6953aca683b2679e471e03b1985971790acd478da453339e656a01379158374ca1c965087e01359340742cdee310ef099a399698f2fc7c36a1b4e955905084e271fa4f74a05e4e4fae387ce52c447b3d70cd76b035a692ba99e1fc2befc88c5708721da67904607e87f049f6014220d9996516b94b0dd69b1e531d39e9c3c607b9a0fd69d6f933180fa798da3b0ab5f0f120f834377e1da614e179de5fc51d6e2d6780e076030cd5b76e872da643f6aabdf5d50fb89925d24bf9096ab7bee9d465265088a20ca3ceed310b7d6b907014832b74c47e9ed34f6c17a24206529ad015b057da21a27437a294e40e30f2df6abf950840e1b2c225ae6b1d8005e7d74ac26ed01d949b8fb1be500d032fc13d2be6211b98615f2459c3873c258b5412603dd9c53e6a925dd37e239f2289d97226dd7d49d31a7550167ce11a0b9a4f54994673f19add7766fedbe5d7df55ac861b3ee9054005beb98412d6fa73779e410d093b0ab5f69312195de079797b4d3d9487ff75d03ba68db0c0e72398fb89d165af22f657afd4cb987448a12a5e5d991da427b54c4867b207cbcac0720f6cb1f3e1a6076a814025041a8d694fd64f6909c28db66d1c108dc2d3d74b03ea827a1331606f7d0bd9ca01a3639d4d91b43fe7bc82f0085e3aee0edf79d5ac5b9847ddf60e1d8dc88022e5849df696bd3391deb29dd01c597825b8d117029b4051e91d93163adcab4d141dbc8fcc439864fae48116d5c24861e275ce7a888c8c281a573667dcefef4c87dd4225f6c2ec18c261e49a0bb747db1883e6d04d8e34f85ff561e9ab275dab445e59ed7df68ed37d28aebb248a208bc66711fbda57873ba1c1a58c5a0378a4d27a4485d397016b1aa7912fb4a993bbd55db6c347029d7238a697e96369c7f50afa993516df4896ca7f8bfda410fde8efad4d5f74d014055e14c2a73c5e1098210a03745005456df5c93736b0e35a4fae34fb1c52e42aca704c08b8ad504cf86539924aadcba9a45d903a398aa8180e68e0aaa0a0d4cb467813b623887c94b17d6c92a2c9795fc910a1aeb56beebe3f62a17d169741633a0249e85e5b3253be8a616636f3e1b0662d795802d11b83325b9e96d66f696e8e59f8cf020ac959b5fbc28cfded11d56ca441a3aa0b074c6433e77fbe1b204a21adeed122f71737701d29bdb5538052067a13c1fb24405ea197b8523ad9f56401c9e0e7c89e38a8fb0434e4c1cf1979ed8f8edaced33373731f54ab19412d30a04ac3cc11e0712948194de196ad5792b138c3c3438b9ba8e52a1d6247c1bcc2897be21a9689c038c6919f890f8b259c7b40618a00672f556a0b58094f451020273a0158701a9a8c0af5254c403a5be5e20c482d4b30f843a6407ca486c3a56489a962335666d1ec059e0ad9e4b9bda454fb425689fd26e639799a546323a7780179f0658b48d6bc9abdc40a3e316251dfbea60e8ebc05662bb379dce7d6f64faf5b8e0231da9b552a4d22f9192a22b5802dc48465cf178468dbf70c454bdd3b0eaa219db8f5dfe4ea696ba92553dbe69d12e0bb7d275b6ddb57cba1c492b51a32dde88996cbda43973bd3772ecfbc964cde5ec1ddc4f4600b7e76649976f43cbbf6d3f0b584517827e21f472a40a6fa65f11a9172f44f98d9852dfc0c6914204842d5fd4b40db5920ffe22191f22f1ccbf2508272fb7a2206b3e21427a0f8c872abaf074352de7cac008944b400bb4cb1b114bd83c4db10c7bb74c7471fb5c098d4134ca77006dc086f82769c31193a7e365a3770501d283412c8360cd125b3dbf6f92acbde8ef0063d9854fe55b4f9e1dfaf2adf9608c09f005c11ec805a8005f5517340e725b19a210284c18bb3bac614c7a43d85b938be80f96690de91ba1e835b1a073cd353db0404309941fb3f8082e059161294da253742d509066fb6b433d3f5fb0ef81479a655e840a2be09ec8f8e63e2a37e146ee36e19de82ca0d2de443d82e758f0781409b17147092d24a2d117dee43ddd096a4d6b6992c3f0b781f009a4e24e901182a47152ecd965a08108bf1504208c9e007381455bc920cb6f4ff87bde66bce32c63e9b932377ea0b3d885d91247e28456e28c56adc5e99b92d8467d6d35984705627a64c74703d4410dba93158202e2931961549990204102c25764b274cc9984c90b94000f9299859a142879a040da862f5c5f29d73959e2f6d0114e991790f09bc3cfc58cae0dab6353225f9dc48351921c644a0af186c093c88169662d094d49e18cca07a4e762e8adbf0acae24147fce9623c3a1338872b45e5fb37faa4e8adcb2c11fb8a0c3e093488925229d5b891a0314e270520a373345d98b91e393baa69eb9791101169376decb85c62211b888be6dae879a55f68f8ff03938a6fc18bdb4103a6ffefb1150bcbfe5d9d260d45323a2c68ec19b9d24c8f6d42b9b5676c343e9b67edf360c33d6a4ce6e0d90838eb3d981f62b7c76b9231015fff63da03188f02d7a7d17dde978f3ecb2ffdeaf5fb7111fc7266490ce1a9bf73b0df1dc4c5dd9617bd47cc5e8e440c80df6b3d0c4eb65524bb57b0dfd5b7a611c871db0b129a8023e1e9cb0409d490fa3207190755d7c14877d91cbf15fc2e569754e713479c4ac7f847400483d46475b6f2ac42961dd3a8c68ca1d0c1a976a7e7bbcb3c1d23e7a26e43960664b65bdcc6b22020dce617c9ebd96423f06543336c4632c705a4f3219042f221f435a58c12d10c50d2cf20d1adad2e060ac6e7661ae88d0267aee0665234c7ed20338b0232127cd698f2ac0faecfa982ea58ccd36e6276c0281649c9ea3b6034532ff849f474c0e0e163e484cf1169df9793fb15e4423ceca744972087cc654b2da675eb3e7cbe65305229130f5e9a80e3071a1ef329e0f28746fc22e1fe17853f84c68199cb83b80b0c8621249ce43bb67d886e371d307f5c0a6efa6f71319e249b1fc14024818aadb2c93725d97a5dd2def6cbd97db0f156c1b0f2293122603ff01612ccf5363803648f32f582bdc7d0649d7055c4ca512912acf59b682a50b495bd8c92b70aa18689753383dd076ed0689dec9c93fd3e5ed98036596682baa3245057c5930a0a22431895d68ca267b2216b0611601b66db8c442aff101ae8f94116ee5a2bfed1536677a236572dce950b3a34369f1ab7e0edb42eacd2bb9ca2fd7bd76943a4d533631735238103d9c908b99e07d02e6c12d7eda48529a60e90bed0d4468ee43e776d12e605beef7da9521d95f59d6a2b1a17567f13d952dbd589fa7e154d99e52e8ebc97d32f86b42b73abba2de9991e69f413fc79b2d924e69fa422373ef11652826f877bc1063300a202fe298d8d2b0f6f6d8502bb76ef3e4ca360384949c32cf4daabb78eed386a6012845a7cc61cbe437f327332d410d761746721a4e19b99b76d46bdf424696867dbe5b41605a9e731ce361169368cd120a67066d2030446aced4df4c8bf124864b276632a74be6c41a5d911239562804107db447a839a6cc6edf372ba806db99d76c48138546c10ced86cad58d84fc6e2a6fe44aef0dfbc76043b3b42f09177fc2be3493298a833b2ee1ee393b06374652648e819f048a7e6317e287109b39cd300b16fdb58b0e2c3184757c1957a4eb2cc343b9921baa97977969b76e5963dd6a857ba760496ab8988d2cde467ad9c8861e67ac0a7d54818bce3afa8415401a83b028a415260cc9c0ab04da6f4c65134a1df725ed0781824b8b743279145cf31134a130d52fbb9bdfadf92da1530d41dc1796a82d3cb56a78ad18ed0dbffce80b13bcfa409ae644ea8a5b3ca36b8dd547b594385e3e43549ba91ee97df26add4837329f50be470a6a8f77d005399ea7419353dddd42f3b822cbb24ee4161702b8b61823914d3f9504c4d8297e16351a4cb806246ad2cf2f802e16248ad300f12852848021b63ca4a9f23d03a343fe54fc3b5564836cf5aa9d92dd2dd90f4c7c776b6cd147f3994346d28a9140e29fe1c41481e6176d71325804ef4c2bb82ef1b3356a4d1b4a5e4824e71f53adc726447721f7313dd921c47faff7219ba86b315ac23f0e905ac6799ad3a2b6fce2182a6cc7a0efb20ddd502963c7966c11ec3490a3f173a3362d6aa21b5f92c422b6c4c44eb61d4b7a0fc3ee721203829a2ea13bf82d9e148d273088f30257335138f261431a320910b031226331e08ddb9ab5d11134c3c02830b0c3c3a182ffec75380cbe6c853c79368b4e6b8b513495468c9aa1849e317bc85260633b00f07da3bf02fdbd21a4c0c88817c5d889e99f5bd0e8f571e3fac22e7a0a5a6b3dc8d5861d8047b8b02953d0c8d980a89c8409861d7e5dc7b7ea0e50345d07df521401bdbc128786466ea44664aa570c089ca2595b27eb0a3b9665c1635161a220b06a599f30555f04263534a2f980d86a430902aa1db4ad19e5d2925b208202f8967f188b62daa204c6ec656a69bed229262f7131aeb879ad6b2aa6cc2ae0558bc262da4660f9afb7cb0ec71cd9665669e0284cb3bc340d40dcae24e3c832fa40de7c5cc7b22492056e757c092cebc7df2e21d9b89cc8223b398128711dc9b4600ed28e21e6ac04f07d196cdb03543be25964292cd5982ef2a31e25ccb0e5a3f8beb23264251ee9696017bf62a17e7fd302f26a908cf830455f1a12db77df8fbe451bbe339c1f6917c53552e554fa60ef794eafd97616480b9c8824c22e573b0370c71e2a1d9f6fa6c900c49a10c70d78ebc6620338c75f6390ea42e33b541b4e41565ad342b0a84a4283008f1ce17e51b29909208f46e0dfacac20c5dd29b9e014861df3754b9073d8e10751b98c149c2e1c492b0862e07f9c213bba449b2074408cf38e3df0c2800bf31265c058f4788ebbfe178567d52d19590a4b428687d379395292d18f1c0689217895d902f54292c627d99d24edb823eb8bf392ff8fd856dadc93f27a9febd1e8965fb37c7484167093a2467b2fb237d8cb65b684d3200dfad92930568fcbb9c0442838ee10a2484fe643fa506ec5a0df25fb108dc232a63ce977c7d1d1cac1cd0d390bdd283bd4ec1445830b659cc4e218fd82523a8b4481961b1c937eb21ddf571ba01a140ee6309fd44510abda59243273a33e1403a45d9a168f218fd1e2ff45e4aa8708a8dfbdadfd610172fe5696d9982dee9ca065e21a3b7fcae61329ab32ca6368c1edb0a5c2d447a4a640955c51878c00fd851ab5ad0818a460cb38f3506c7bf8e945f48d52869f22d3462313936b7bf95ac3bef97d13472b50133e606cd0487f9114468dcbde21ae899f7719263472d2718453695c7048bd3fdf302f1a9a839ef41a1fe60c68a35418064f5045bd5588a0f1633ed9310b95cc07a443fe081de2c10236897658bb21a8cd090a28068e20091bc11c546be9ba32e0c5b2105320a703092895e34fc9b1d0ba08d224c80178e9c0a0670f158c4bc83077d00fa04ac2c4180b9cc5d1e3ff21d4b3d0ade2f3e5410e3a040a5aa9acc09cc180597547c5c4f8f6ddb97a5c92483d04cf47ca22bb59b8f8a6eed5982f6283cc94bad8e80d9fcc6f49fe28bbde30565783a4c36b00dc6b0f177181045434cb12b08dfdd96968fa1f88529225397252c9671e5a2a8a4151bce89e6d5dd85b46144ea10f6fd3004a408263a5896322202daf52ff0c318883083d05246087afa1c07c6d8462a4bc8d9990c6efe7bded7a4b8d7eaa0a10236444b199bcb0fd2543493583452f8215af683a92332b01bc80c212c90208621a6dab063fb8d9a840f8a6af820378db1be5be012773114920c870c6fc30709a251f83644ce3911193e83e48dd567837e163baadcb54dd29179e7fbbc4535f5890d96c1850cc24fad18ef42af5743f7c4574c9018595f4c7da64468d8f37b3dad46e7faa931bd042911f4fcebd9c5a07289473619f655b74c6e41a496c8e25c432ecac8959543b1674a3aef60e46c7f4f48534a945533a4be8853ef1e109c4577ac636e6dc2d04f29da465da6c532e107d8d95a5821130c7d5808c6c356e41ad29714ca88148cbe74faf4e34984fe231caeb5a601f22f2307cc989285f77838f3954afff3955b3eec6b1ff07ac8b516c82b44b3f423ebbee5732c4b2da76feb1a19f84cfc4089a37e5a4b9c3df76ddf12608b33c54833e65144565a9ede0d2596dcdff3ce68055b8afeb556c80de06afd812effde790e1164d5f7b21eff16ee4ffd3fe04c5ab796e258414594f1ce8d35c559fb14229c29687b9bdadab68c34320719c84432134692bc088f03b9724a4d0837ffb40a736d85c493f0d08a6d1d096f9ca869608afc8e0e14e5d1b527b9d248c4d5e3f688c4c27168823bb1aae975a4bb82371ad9c52ee5794666bdf5042ec07c010aaef6f1d23d28fea816aeca4ed442f40751da4461b4622ee8c01b57c10c498aedb123e948911cee5cfb2a279871abf1c7a32aec94ed1180acfcaab845f710fd20da31ecb2f344848156934a329b97643f89f0dc249bb7bf01142d2ef0e5e3b656d8ef37436793e1c0bc867db18d5c280953a064423544666e8430ebb8ec305d038bd4f70f8175503d3c40c2d0eca064572dcfd0c69813d4eb247ee98e9310b740f2a096e110bd0eb7c97988c542cb2267200647b8357f7b995429bf568a2754633ed89e89b0be79693ba0cf4592a859a591be8d772449b90d9cf8dc1874945bb6ec64d7bfc74ea3930b7bae21221d79b92dc76f93f20170c146fa1af4e80e196f3270cd3fa362b014f87153c1fa55e551bd89fc106bdd24d9ab5052edb0444133d80e665746237cf7ce7d74262a6e1482234848db995403b800a5722fecb71dc853b44a20458d014d024fb08b9eedce97b9425355085b077caabc906e4a90228cdb83dd40753b896e11e1362a172543bcc6b313ab43ec345194edf0b6f78f23728f4b0bf676d81e4e0128f7a26639381f711dcd766cf4638405de656d71ae8c53d062d1b742e3a892671dc84559436d0dc4846b68fb05b6e4480b6b0f8b77a3a610dc277895c07af168b9cc20a10eb49099b2040d5b63bb39f163e0574b945382331eddb36428bd22ee9f481c2c6ada3b436969838c58dcde1a0547fcbf89b9fab554dba5f8e3c4137b5d360cba9b4027afb068021441fc8951c85e6d21502a29fc3eb51e84ac0305c3fa025f9c43fa240a6d9752640c63e6f6e8333772438d3e360ddb3cab69304b6d495b2c09b3035639b5aa96988b74de3c59916c29bb26ddc09f53490ae0f6dc13012e671729672ba151bcd41c3e044b196b20c06e1f5c569302ea6e371165515f9f940ff6cd3bd2e69aa18a2abf1fb2c29e82c6bfdcc2b41ee471e6db3f901e6af0ef3fe1fe4fb38ec29a9f096b572e9b27b7a623240fd7b63cd91a4f12ca73c17b1a21248a7a7815b12d24838995111c309470908541a1f6b016c8c093f6da88ed76bbe597d341b8f6df19388d815668907d17a4b6793df243c3e4b257a3deb511d563f973467da01ac3fd766423d22857611c779537a559a6d46ae9384199d6d9ec0c5a79ea8e3a8268012c9fc77e1172a925d9dde545356813e1e8910784e0fecb6960c304a3cc77203246dbdf3b9959c791d2851624bd55ac646b447b7fbc4ffcc20c0dbaccef335febc470f7c4c504cd72296b3159b385b516d4a4a9f364744534185cd6687c0fb343a6297eb9cce3021c1388e8f1907c8e527a7d512a21d19872fa8882daa24fb0de2a3ab2637f7ccdddc8208e87372119a3e20f734ac04e93df55484ac9e8fba0537a567fab64a9ab107bcc73b816f60e6d1050ebc9a184c40751f14a8f81e664ff9c65795bcd73ab4fd8d2dbf2f3d7589e8e7585b4b6263a3310dcbac6641afbfc6fd17fb111d0ba5d4e29d331d1351b3c35850047333a5a962c3bc7024b034eee3e3f8a0141f0c2dee8cfb29d9dad11f11c9295dae4f70f759291a0c7fe31794aec28467361c5d6298652181cf1fc6dbe24434bbf14b80be519a3e916d48c81fb6b6ea07daf73fc27c61c41a78a377d96bbe0f2edcf36c59ce645b773d0ae41dbc5dd8d3bc0a3454ccd52619ecc5e115d9e911efba3db3ed29d818c29cbd3ebe9e5fa71a5fa7e31057684ed8826defe0972cae0dbaa15154600b843dc7ae952baa47d5369a954497ce5d1508ac78d886989d755b80e508333a338cc9ac14a3e0947376b7bcc70b3cf3aa90a5690e47c1f3b8da5ce66f6b6165a46b39c56a1e42ae166fcef77cd5811f5dfa8ad9ef1439be3cb720e0014f907a563f1450dfddcae8220d50e5d029da66ecb806952901279c7c231a1a710fc1eee39bed407d174760f740b5bd933333a2a26d4bcdac51674c8db4d104d8ce6a95bd587d38611f2ed484a96110a64846f42efb166c70560dca6cc520fbc2fa344c78daf025267ad8cdf9bc4eaa65459456fd9aab190c54d92a0c8532fa59875cb8cd037e0a70b2bd6b98271a09cc26a032c876f51e514af3f56812c7c11deafa8d96eeb0b5d58828baf62d978fe738ff93b2db764bae38d8f40f29b68a73c5460f8d000c830130626914f02982a7624e68252ef4a1a5e55f301eba0ed40dacb998e301baff9aa6215444ae0e079390bf60eed444dcfccfccac66c881b2feec9869a4c09045a1ee8bf168f0b158cc50f071f7a9df037577924185037bac5298424a039f0696c367e2524c3a4535b7caa48e6f68974877e1b62a77888f96317675f51b76c48c4a2c9c94940b21ea0c662c5697cdbe90d5ad726f77a030cf418aa89c251142934ad7ddef49afa3f21f1e583aa5e4311c4df89e38175731fc94ef01e87c30fd700a8e464e77f2e30683d7b0e3fdf7c128fd16375584e5b2963e8abc61bfe8105e8b94ef53aafce1678a503188c8fe416de3aa7d2880d4300dd5569601f4f8050bfc232eb5e126bd31c24964d1ed932b53115e9933720a58f842e95463a22403d4eba2fc6cd30a829808420e37b19b91c706c963cfbc5d3c53430a83673ff3bb69a6a6412fdc68b2b340bd77db581f928b26b6c403764447ac49f88bb6bcf008d6eb369a06f140eb162057383a76b6b48c585bf85bd0a5eea57869bc2cd4a587fc925162539e8f03bbfea0c389c4dd550acb7fda39a766d7a4224110c8f48bc602e187f6e6db39be1cec889e7370c1eb166f0e04a07137cee0a8f0401aa3b0a39f5fab7ede3e847c8e61348b79ea01f4e07bca9a7cf4b97694fb05fcdc44ce3e944bf4fc7371d4881cf69ae3bec6006963dcda2fdb1803c9609c9ab44baca4917ad0ec301817220731da38a8512d94f28962b7a6a1f3195011ea9020cdeccb05c075c95b79516b07ac4323e9057fbb94f562d3d3e8a2c575fbf4189f5c88b4bab47ecc9445345271edaab7cf31093bbd9ca9f2fde84f5f47e300d6b5a10dce57eb86eb17a3547b2b915a566ec55d4658ab9dd7745d46c3415a654ae2e1a161045c6b81e57563d4cdda13a31d067a57d020da7c908c7524aeed363a07b9ebcabda3cc50ce0db18b7056d40a6fd5b14df0fbd465ae3b1ba98de50f81ab9312336589fb31d1f789593c293466a71ae28c848f3d1a451ea5fb83cf06a2c2b6a7bc7a38b7f582070c1c3c7b32cc5e98f17a0cf17f65c3645e9005627020de06e97b9a742781cc49b6dec14338e82598b5b0d79f8a407d39cc7ab6ba8d928aa673bd8f3c7a2f9dafaa87b1c34907ef12832ed1d298f2b62f87ca0969efd3c8043a20bbd64b295a60783d6260439148e6685a4b9e7c45fdbecb5117a2ae9b409e5782cc6f2239b0377b485647c3121b1a267a5b207d6885722812489c9ef7fc4c69803f9254355b183b1af50c57b9559ce9f9741552a059da689e2be743e3861485cfabd014ca5221eb995e7836b0737d87c4f43dfc44063dd6d5cb4e16c6f1d101bc0fe1c333b665110c24d340e548c483778ad265a64eb5adc0937691c1030ef3836371c956f027016d51e157c676da1c8d9f18dc75471862bfe69ef743924ea372307d27f40fa55df236dcb61b0f4bafd7471d5a267e2bde4995a8d83e3c1100fb903f9d492b33f0f7a2c686dc91ddc8f5b6bdc4fc0e353a351a6cda0374ea031c8ee0df7e97f6ec9436879b8eff9b5af8fea550655b5cfe119e470f1fdec99ee87dc4af5c42d0bf6cb03e299dbb560dcf5e8ce7664daec52151f0c29cf000aee88b8527739eee6a29be5369d233e89531f9feb1b0e14e13ca970d9907f4d01aa175ed050cc35083434d619f6f7d25807228eac8f4db66a55ae350876bc48b8a2cdd8cad0d46c5163123b323132e82c7ddddf6cf56571e242d2494ac4b46c86bc2338891ba3c4808bf4265b6410d2c44e2bbcecb9805dde36fbda45c2624ae2b116fd2d2fea63d6d8d60d6fe229b466268e273a863983f6c8538cf190894f1dc40be9e18ace1bed7bebecc09d8ee50f8409a6140f03ac08a9363052d966bb45e6cab8101cd713a37c0b154887a6262cfd567f3034798b12564cfb3fe936b13792985eb651264a1f18f7315da70a3f41981d32ac6bfee215ca1b18470c57140661133e0186a578848b7ab8309f1d89d9ac2354301a19c510237d0854d315538327569a7265640ab72ba7492ec5e3ffc897a9492cb3fbaebf635324e6cffbd8d8dec93d2e1a621e4bd99aa10e699e9951ce5c090a1c47a8560a3aad7a44e7257e950c37fc2220ec52201e97952771d13d07edf157844a91cb56a8324e12a0321adfc128cea1755274284ebac1da9c98ebcc77ec73b5c791d2f342385343298cd8c4bca037698ade5540b363af18ab833271dd54e8033cc5441386ebc6f6a5fb54b96e4dfbad25a1502d0998c11dfa88ab0ca9657013c1f32c55718fc5ff8362d69714f7a532d3d7b0b44a135725e858295c055a1a7afecdc38301ffe9ab50e9025c924391182d3000b3c4386f5b75754b03b36eab17d22fc64cf014327fdf05d9ea1ffaa8b41c24ae83e935948f0d12b8d57ac0cbcf13a1cfe0629039196e9e641f8158903f7b5a2eb6b587d2f4b874db09c22a949b7dda88a78e667b3952161d2143e286efe69e2fa5ef450c58a92da116bca7b8d94e005605f1450506480ac3f7f4dbfa57dd2f5d9c3684fba78921d82765424fe1f339c7addf9c1f5240f5570b421e4b978b2f7bc8ae452747438028d9877880e210897fbbef3820b47f6fe25521f00fbead6b27c1475fd17f4015033f3bcfd22629a9620fbbc78678d498309c4201a803bfe9c4f323a82cb40e7007fca20282a0f1557a5414bc365b8ad8128c1a14feca5d89b0898be49cff6a102c86edac060705284239def46f98edaf391ae5211c298f29bde360fa36fc2e03f9d698c325169f9ed8f86270d6dcc44d7c1fe60cc9f19a99e8d31f2d0ef81af56ce2c50ee11ede5a89f1c78786c07522a6d87aa8f3707a60fc5fdda171f7e89a20979443ac449cde866ad518aa55c4ff1c619ade3a22a6939b08dd51dd228f45e6be56881720390b58b82405d384ff4fe66366cd1a1df8703e0b6b7168e1a32400c431a71d68fe47fbba968cd06e4e383b41b7207895e1d14c900af23452a880905f7ffa518a7e9f383b115ce6f756aaf1c5da93aa517807c5f93b0cb3601a21e10cc23873bcb61fcbe5cc415329ed8f7f25c59c536da075437492a83614d3b7a1acae97671401a11342c685825a7e0fcf521a931fe91d55bbe0af1ddfc8e588be93eb8771b343472a99f665832b1dea154509d95554e2fe6ee443d5e2589583a31f1c92c398295b9cbb7a94c3fb012832faa7b08eaa40a900d1524c243e323af07c82028279cea96814338943c6691c3b09e86aa0f1fb587be24d2a085c2c94fd8839f82b85d0ce4dffe1569e0f7acf18e7267478fe5c59f489a1af364fb7a2d10a647e28a8e6fd1298240d4aed6aa548cd8855a3e9f03dd7ae5760d8ca4c2763ea680435c145acdda8202ebec79f39a27c350aa557b970cb7ceedbb1472340aee53f1f27ce2e5859a24901b09341f7b91034b9a1389c96021fc2a3e30beb3edbff5dc1087b0050344cf43976829389dbf2e4f6663cd5c16f234a6a7547a266f2932919e2d9f6a10e278f204284228388cae631c93ff295aa6c52450185e2d1259473b6444edac497c627d831ba8a9c29aee662950db1031567222102e8dd7e82a47cdc77a23c96d1581e76c17d59d66a3a7a1a067a60829cc35be55d10fa06d5ae10daf44a4d8f0722bc43ef2ffd31a0176bbc370471e6ace9b00512501b3b34c8fb9ba44b289351cc7f07b891054cc8f493d186e097b85b1a7e0538dff95bf68f3abede879925a01c5ddcc58e388fbceac5df40233dfb071ac787ab834da978f284d7275328bcc10a32e41132800b138930f0d4359f83f1c645d195590bfdcec87313182713b2ddc5242f5abd5845ec0ccc422037c901f8a3239f73f2ddcbb7e847480640030d998153434b1fb7c7cc2f788e99104fdbf0e3b1d49daf1640fdfd351cae53404619d30cdeb7587bc55bccd00fc5f661e295aa08b1ba09882368c1edd88fa4138a4bdfac195d734f20d2d388090e9938846823426140cbdc588f75b049ab4982c3b5734810475d874d3a4390bee0a571223b5cecbbf0b1ebe95a5544462d90f1a4c13e7799c7dca2eef2f69ea299a4f47c8f1a53b77dc07e385655d2f16102cd5f2a2411a1a30dd1c298974e48af674a2523b054a41d36069ee36807b0288dd7b231a14687859c73abba28bd50aff3e54cf921807802ecfd40f3cbeb45785eae46dc02e381d3397c26173a07cb4788ac141050d189f793a2d143368a3b9d7299a4a72111d2c4197e28a1473d2f386700d17ab26356c552396b2281dae6379717d26e2de117d169b0f07699d595aee9b303200429f10c15ffc9ddeab9dbbe15d037ee9d06767d10af8a640173ae58599d2a6bf9d53d00e68adaba897a1c886435e722412af1b92e736e75977e60369dd39c6023a2edfd7cc28f8a918a27ad2202d10d7579c2447877a89b7f4ac7d08433f916eaee26f8a30c289fc6162db19fbe7bb94dfe9eeaff6137a24c940175cb451308a855a2d4e1d1b0b0bc7861f8c890062f3695c40eb717bf586b96a8d1378c92a76f52346f006d2ea44e4b40a9651467386dc032b8162db9fe38d685b36b821f97c16df6ca1d98cdc6921727a3077d7c8bd2b619b4bd3e673f491ad15c4e2153aacb4651b9610d930e5dd52db377b9ecd76d232e964ca1118d8f2b763af10eb4ed265ada09b546421de8fd0ef1d8b5195730352608ea5e610b37cf980e984243b53f11222930d02271c770d794c40371464e0bb29aa7d9d6c504c26aec42f0ecc04bc6b270dd1a0c106c57f1c644bf11c94d5972d977002c7391194dacc90b0bda4ab855d396a5d2f23ece741f615ea4cd7e0cc3c04b10158e1f33d2c32cf8938ff4418ed43d0cb971ee95b89a0092dbc1c93417ab208986a1d013ed1c5fef46516f4d2be3db4afd5edb07230b0c82ac345e2cb5a1f55770180975cc278cf11f22396d28d933444476b8caa2a4f1bbce68d6486691380f3190f1e0ea7d7041da4eea571e76900afe537f3946dea6c2996386bbf830f6b6d37a0728824860802ad07f4d5993820df48275e714284b85225091f5132f290030aded2b6c8071ad206b07b10ed41c427c88b64d8341a4e7ad03a0ad107699ee4566b34e28c5a0795d63c44dd24107901b9986f3ffe3c011b84320f8eb44a0f4ba67a28ea2708ccd08e55009ed8934bade6918659074661db5fb386db59dc9553db8fee320f4f68065bb5c99bd200362ff5a66d4fc8500aa641d6067c74db62afd90203c7022df49dc166ff6a8802c4f56bc435d106599e4d0c704e31ad08c9030477c82f60413512634dccc382732ff636410ca12ac4f2fdb2c7347bae8bac7040cc1540021410b3b3fc93f066493def2eae11a130b3c73e1f67e2eaa41148ca209cfc2aa2030885f5d8987dc67520d03e9f04d98b6fa43f0b47d5d25b79c3d69f58936763010c9c4d51d5508aa4b8f1eedeea24dab773ccbba9c73a27c380e158a4bad7d17b012569f24edc30d518b9d88e3213008110ff3f8252c721f1e6b0365d9ebd4e46445f27c2c4fe334214f41aa2a6d4e541e05d5121b1fd8705810d7dff1947c47c34e6c0065b883a483a99a219209a7f6fa1c7f4ad6d1858482530604e381ec31e2fbcc9fe1dbb8c18359b09374ec0486df81650b6786faf94869d4cf4ff88cf79510ffad92988710ef03a996639be1b5174744ef8b4bb81058dd6aea57bcafb65a5b174b72a63462c55144217fdac4a4d9b955faa1243c9cf98144e18f25764a4fcdfc2b7d64e65fcb24ab9627b82c2b4c945927e1918067684b2c0cf12486dd33c44d58fb882deebf9c0e7406ddbb0b0cf3250b78a3e56ca999c6ac2991e7d004f6e8649fd44b8ddfdc40093e32264cefa4097bc01661cfb44527e107b5018a049087cd06bc26467880472766c2c042d8351284e7024e07e99d812655b4a2c2cbc4941eada0de8fd46e1a675b19b0d92b29a59e2316139437c4263926cc87efed007b324e95a5d5a6e71c21f0ce8c7a27201a1c52ec4582d663821055ded918d1323d5aaa121e56f578ddf48d80d9664841a0f9da85cfd093698831d98912f05649188b935aa63d61ebd97c84795df7815818aff1623eb29db200d80343a77aef981fd9ee220ddc0769e808b1971fe7b3e2888388d9b38754edb9c43b3834c67aed0e42176aa5301317ad83a8d04bf5a11ca474ad0e5a9e7d478951e8e2f250056553dbf882c58ec2687304a4e711826b2e9083bd5931118532d63b61f5a3ff07b1d2f10b0552ccaec5353b86ac7dccb804e2ddeb05cc2b046c5799a6ecd1a1efe04a3f5ce639c713d29b64878c6c4c664c34ffa3480b3614032ecca94cf282e60504f6a25421b05d79005665d560edf1a7163e94448698e7c01fb7c4cb4b6209fc6da9d549bf7ef5910b9845ec91a79a83b4d942d018a9772bb01fb7320ed0b6f054ef38649d46f9b154cf384816a0911bc04361fd93a23a7fcf9e2e8686ac8c90c2f945a202cb98513eaabfd4cfe0ca410c0e6d2bfbc09d08b94351938fd191f5669bcddfedc494c34d5a30ec9667364c87f6d2203472973083b6c2f608c5880ae2f4c699e1e03cabdb924ec07dab979a83779b33057dfed0754d59b9a578952a6a414b5da39bed482ddf51137a6bca5b2212d61fb2793ae2928e3784f6a29290e1764d3b4b4b9952c4ad497a4410da23f152efbc7a11a43912bcab67cbca638a3fc2910e8d662a97fda6aac3a65c00071a34a387cbe89b780a429c5c4cdac3eb184791a591e730aadc2793469270c1fbf1385df47f12f922fbbcc79302563befb6f22a11a53e703c2bd4b1c028b1159d7026d0e23175ca8eefef8c8d12f2d91c79591caffbcac8bdb52c9acfcd4c650cf30a20550b54d797b39b06ca90e570fd90b3339ebfd8f937613fbd656fc80edbf50fa0941e3bbe9b6261b88257e8be6e12f78ab2e6d3afbd27e1013227a957b81d4d205ce1dadbaab6d685cfd8635e40621c839d59742838540fe44e04aab3a7bc5820103b4a07821d3da163aaca739be6ea1caa39a4b1b37ca99c5e8fbe3cef083fdfb8ed637d61160c18e971a50a070b40697246251c4aabf0ae3401b6dc9ee5491946eb7250feb96ffba389c9c7ca4ff6de7b4bb9a59449a6520bbd0b1d0c2c1eadfa5aa5dc3029632c43f3510c6e313310e610bee03803d6a1deddc72ff481c6f7eac3f8e51bf3e959ef19e5a95a4fc53da2631e0f9f1a352db62db69c3972623135a35c2834623c67ec1a6c1c5b7643e4e4c17a6ab9af5dcfb4bfd91ae3d8b6e62627a58c9376742aa594524a29a36dd5a4ddb41cd6cd8c354dd3e2269d689f148ee7a88ef81441c25bafa69428977d8b526e685bc6fec81ff3a47432cf283742b9a18d9357885478d51e9df8a6e1240c702b9691458efc48b12a6c1abedb45036fa78c6c3580916848c9550c6e188b7e6e71c3f8e54ba4f9f9c522ee41f3b5e8869c54f4edd12729292929c9a7816c0dec9c38389ea3e2c14d8bb21623e24ad79e5f31a8f1cc0c46af3f4e39abcf3a278b2a353183d100921098908441a65685ab671598d60678d51d6594b141cbad132cc940ca17232d3d8001db2fb798086d71058a2633b8f861c12d5ac325c5144d423100db9d5bfcc61a4c48e183144654ca00dbade51fa96fe7e2a701eed15e8b6e7777533636ce692ab79d5fc5a3db7796befdda8eadada539337df4545062bf3f7afcbcc509d2474f059a5762bb2134bf799ab7e3ad605f7e3c5ac52c31674c76cc1148f62a324ae233f7a06eb9794894b2daccebc73de61136048f8f6c615f1c1a9ab1a800b763110f11e4970c425590b4aadd2798db52b21424880f1f3c787cf3120ad4865b255a691d229a94797ec560e643787e3571f23caebef48b1b89f088d01ccfa4c10d99cb08d3f5573651e9428a2b404af34998970e045482d42b497d74c9300c750a674e399b7eb2e395b29fd838875f00faedd9b9cb3fa4f77ac1f5fc7a41e8f905cfaf178a1ed53a537acb2f94eff54f28573ed73c6cb225d71d59c6ee8edcf6dbe4a4f61be566f61bc651fb5be53096cedca362bf659ca5bf695cb6719afc8de3b6feade3b84e883c715d2784bb137777d318369be3db9cb5d65a67cacb66390b6779f678f427f0e439e78c93552a166b6767ce1326fd98c98c14680be3bfa878e9f1157ba701ac0484ab0fbd67a73591b3fcace2a56f927bcc4f3e9319e56bebeeee38666666e6acbb39728fee6e66a6cccc3aedce977b7477333333339f3a8ee6d451eb610df9f340dce87a19638c316a5a8c59966532289cdfea30bd922e398e31c618352dc62ccbb29e160aea8edc4bd14977eceee8e46552ddd2282871768ea7e57ababb3bcb6ca5cccc6c264c648af3e542fafa31f7a0f18b4b3808f36114f3523af56298c814975a25bfb8615c7ae9282861e106fc2506db08377a11b8a155b1cb2fca6f4981901279268610172003e91b93707f4ee9cc927b2875d1d6d0f2ecf60c2c8cd5b1613b5c6e2be1469fad8aded1968f9f9562e543fbcd59ce34c97286ca87567af69ce5cc9447e180b90b1ba3679b675f9665599665dbb66d2d8802250543dd60d862c0b87cbb6dbf4dc8c8305f69a460866f7795ec72fa764b43f7d65b6fbdf5e6e1e086eb52255951dbe4b6f3dac7c92837daaf7e3dbf2f647db751831cdfced7a68b4d05cd6f84f46b5f0af1cc73aec4bee6fc9c1643902ec5b854f3b0c72ae65d96394f733d99a6716e374dd334cd7ae7a5e0c26bcefc9b671e0f0f2747b5f3a55a25dbe3b461e39c701e3d17a456cdb6d2aa49b1a6de581fb5ea4a6f19f08ffea60ddc634620f29b3f3184e952492a2d3135c916cf2e33ddda7b532977d5cb9fd6915e7dd66ffec895936e4da15e4d9fa1fd268f9c9fb0cbfc740e9234b8f3a74c13956957ff345007f510125292d21253530c8a414391c8c8ca11339980020bd13577767888f81c41c2c17025d1cf331339dd5ed962024e4fb9544916ff8847cf02d0c24872f486183194384aa20b143882c098dc5046d211409cb181992b6c8c3db23cb161882d3f4cb1a20527be58a38833323c31268733310ce0394b1c61c4917406534a87f8e01eac5661d643ead537ac39ea189d3c54f329e621a5f3e90934873ae68d6118466fb51e56bfd54355cc4fa86d2957ddbaad9f4dc2fdfcbc3eb171cea65d23ac5c3571234e0a4b9781ccebe7c28d1110ce809947f0870333ff996093ea3358307202c0d8159960f6cd10030d58bdc17681dafa3127a057b0718e74f9b520d97a294fc92d468ef2648474ee8c34284d90ad1b23b305eaa06a876767c32a101b94d3e756dd7a58f53abdc18d7ad89762d07a60bc7255bd2b577245bf192248a5840852fa3528bd063927624cbae7169beac6886c815d7ed219b4eda57c8dc9c60d551fd9630f6184c1ce7aeb3198e21f8ce21eb251dc434a1d1c02f334873518bb213433e812699d3065edbdfe2955c6c376d359e52ae7ec0932c8882e2f2d7022e6b7c84930e0208223e8e83746e17296388c7ee3142e678963e837cb4dd597aa1d07c66d97445c768e763df3fab48ac974acfbb8297dce5a6b8a8bdde41a6e55b424d02eee9170f9e572f2d914dbf3cbc5c4739633669e1d7b7eb98afe3ebf5c421f37c0b763cb492be3c04a6d9608bf6027023b7107881b46a298d43a61aa4beb84ac4844444484adb8476465b2078dae60ab53f8875c710f2edba1db8ce8a02616e4aae58344b678344d61125a92f2ade4e44faa03a456a9ee3a21349f12ba91cc0ee63b181266e22617d7bc138992b02f462397a949e7d00d77be91ec4849bf6b6dc5381b4a3dd938476563e48614550462a523484c6847a1b9fd8f742b47523b92f610b49bd08e42bb926e7152fb92d8a40848c627dfbea964ebb25849aa5447e4aa55ed3c581197bd49750cfb5a988e79fd5aa05e3deadd14a96ebd7e3745ea12ead57a374530b7be64daef56cca3def4527235d45de1d83619e646df62b79d734e4a7b6c37247aac56f118d2afe21ecc6d958b1ed763876418ad272967d713a98c4e65d3aebbbb5117abd5462f8b9eb66d713b854c9e47abd819d543bb21fd9cb72a6e76767f967b30378df234fbfb0b57f8f8d5279b0a9cebd9bb4830ad92ce9f546a558d72c3965d6497972ebbb44ebbb5f7a652eebff507464691ff26074a9367778447aee68e5ccd5662f9e5ce4827c99574955c49e9d2884aae8c6c996729fb0dfd84f4d3e912a85b1e2405492139d42d02c82259455a9158de70bd747924919a4eee223033d7a475f6ecb35d91ccedb7c4aa5ea67c587235b72f255713f370e7a944b79fdb0df36299a75ebb7e7a71e8a917897e7a6104aa1fd2538f937e7accecf66302eef0c895f49d949452ca1db99212e8a54b20192487a44b222997689a6c9c134ad757df3206b2827dcc19736bb9625fb5e152a51baf1e87f887f5ea510806a2c3eb075e9d3948188df8ea71087e40079e625d951cc37ae0294f35c1f12600f0947bf0dd14f1c07778608decb86967d003dff1dd14d9e101f0007c4b767c2c5737f2336207139041acfa8e6c793f20001e5e10be54cada7b430fc017e64af92373f68c8158e7d3096b1d8ee9e8939f4e7eca3e15945c7e7d759e1842f56d723b3cdb0ca8bec33f684a6870c7c730b8212755a5f7699d9357322187d9ac67c40ed7b1c33bf07e52e08eaf9564f5d4ad1ebb0a3478f3edc855f5d9c6bdf11d31a8babd3abef0a66ebc063ff0706a003bf032d07accf43a3c4925e93be8ffa418b7b31ef65bca8b810597cb05a61c005e0cf54c1c2e30e5385e0cf10c98eacf00c0208ec720e6ba619bf9dadad1bc95f2ea714ab73cf07af4d5db4cebf4a78292d46f7e4373696a686c6c9ca73b85aec7c679bac6b9ebc9e13c8d6135281d8e6118d635aec34bc1055463b15abe3697da5dbeba8dd7535a555b553d8757e3b5945655bf5ed3783dd4aaea27af8daa7f50bdf3e251c4d2aaea3b352ec955f532a9a6cbf4b5c6a0dfbce803f7a8ae554f797148aeaa7be04522b9aa1d7c1149ae6a0ce21ed5ad1779e010aacb195cccf9a94b174ddf2c648b199c20bbea7e03c1922a9099d8494bd25bc97e38bcdc5c662ee7916c692ebd9164cb7647ac11edb372457d4bd96e48447ae9dcf4527204c2ddcc11a4cb9636e4394d2d028065aa6001a50769d2a179cee2069697ee236333f551bce1a5f751eb74770f0e3f1d1f3cb6047dea96faa59ea2afa0269e52cff9f9a232c3cf29bbfc3c459e35b6249e986d8f8961adb558bc9a98a19fb8b7c3b8d937978b7de4d12b9c1c223c37e5a9e797135ea260e2c3d487b70dd6dbdf9800576b7afb4d4c9090e20d971560b63c41050ce188207488c28d2eacb802642acf9ef21cb8f1c5110d5b10017d019e40030d9ea801092e690c815cf44c9af8700516196640c5880db08418826ae0c50bc478e20910896747c26e023b0aec2cb047174720f618c41e879809102c2002074b34f8f2431590896cb9610b971a42481105e478c4b3c723d92ac2c518415c6962e6062740f688245bdc022422212b6228e20c2840f698d43a9105cf1e9b2299d62933956e7b18cd3c7b14d33a22d8f9133c86894c71e967e692474e8c7ae6592fc94effd0af9bb4cac5c60ddb05446bf2d3a3520413975aa79f1deb7a66cf2cb43466161f3dd4c67c6b6234305a98222330d4ac68471a9296a42925c9a16f8d48cc875a9026a435f90e3527df9a14cd95317d674ddf5999a46cca7756f49d55c986be3dfbc980beb320373219dcf8e86146c377d684054bc69a9156e97bc9327d3799692ed24c97f963add8a3f963919820f3a135fa765ba58bacd0d645768ad645b6c8061d59d7677d647f2c501553c97c8735ac4adfce61659a4258bebfd06af9fea650e552ad7c7b1d92ad692457d1e9175aa2ef696424b3a83fc82c82f8ae4232a93a91554a9810138391c1ca5457fd59c2929630256c0943c2b854c1f21d6241dfce213644830f312223acc9b7634ebe43caf4ed1cd2a62f3ea46530d7ec3297c07c3bb5d2ce213d0a29124d6a30a8130d0614dfb44a2fd129bf54f4ed1c5229424d72d504d466cc7c489b7cffc895fc994df2a7cc5c9a6184b80875f90e6738ad84f308a9680a157df4704ef90ea7ebdb399c3f4a1f4ea01924a51889e563bb4c6ae7502a7d289724d3770d1c42f45066f1b1862dbeb938c1125d0e8572ca772889bec1501a492b422d45488848ba88e40f11946e3358c260118385cc529252145305cb77f476c955901d2212ea550bb573189b1eccff180c847208d1435ac6b7c730fc836dd4d2ed914bb492d3ab98538545b4248c4d94be7d846e1d6937a1ad902634ab7da79da7bb533349b6dad5ab38e71b5d7eba08dda2e0740d200941925692f4717edd6405376c57bbb6b8a1ac5adc700211c7534a29a594022166522a37944b1f7d9ab961377df4f946f499831bca9f8f615e3ee0a54e1154fcf4b63db25671c328f4d12b15d1e5d1ade286391f5d52d1f4b3e92ba3bbbb092e5f65e96310e9db1968f44f7459cbb2628c80aafcfcfcf8056aa0f813b6d0cbb0811aa88358d15a1ba7aff03d548138038d18c43fb08af4ac1359155dce17c3732f66e586318dbbc5c9cfb34f3141b66a2c4a11912d77d972ca29fde917aef0d82757f7a31637f66c6eec437fac06d52a6ed5b44fa400a0a37b76ca3fa4538a514ae79c33c6b845195db60ca29973d239e794585e5e1afb686a119fe7cdf32b0a2711755b27a4de38a956c5e9821ba23e2ab15528e48dce52d227a244923b6cd359c696b2878be23a90f726c8d6f34a3a164d5cbc94d2bd0ff988877b483fe2aca6e706a356b18752ba1efb88548fc713e1883e21f988e9c354f5028b1bf2118f0844e49c33cc3ecc9e316f3ebb5ca5aa951bb2e80b9766ae7d679d7557848fc0ec63c9d576138170621fcf6f3309f7413c15e6274961d572415ac5b562cce21eecdc8f7de10aaf52fd19db9ccedf09f30a291be784f188fd516822c53dcedcc8ec953681399ddaabdc4bd53ca7b21408f64341560f21f0ddca9cb9a5795800cddaec5e9b4af515771e615de23667967b80672d494c2f5bd42db511e85e9bc48d1eda782584c0463379c688ba21e7ac28f4ad2569893b52416de2527007c7c465e21f66c8a8c6cbe8c6cb7ee1655bcc977eba5e4ac7bce9a21f4fea33ca0d29100da2412f2705e21e46ad9245ad9293887b48cc8b4810fb96b8004e975c49fff949329469ba2a4041ca144acac4928c2c63a675624d92705c19e737e3942dda2bb76d587a7ed540f4edade7570d569ebb9e117058393500bd7f7b10fe21bf882d96cb2f1c983e04617b0f010e4aed13cbf5d691cee27c78e939a92a2f1d87bb281736d6cecf1e7efa4e90d691de38fcc839e79c627ef251e46263f7ec951b4ecb1e59f2375b5580cb1e77549a58ee18af1baa7c4710c4d8b3f14b2872082e5e4e887eb209c5cd6848e074741c67636363c3794d8eec723442aec713ae4725f46fe6ddeeae17daf8bd9a739e6599127a592a1e3b9a8d8d0d1ff1fce6e1ce6bd743d6250104ce9c89c3adbd57b3094f994d9629a19ff96b9e59ab695ba6f9fd42247f95502534e37c05fa27b073ce79e52a57392e73cf9cf3b80a5d257589128184fef0119706ad619b4fd46cf7e524e823099993808966e33aba13c7e5c891c3c66b74b8cd69b3e9bc14e26f276449509f755977369e6ddb969d9cbde60b51d9cbc9cfcb896bf3b865da75ed0b3de840f342d56b9e79e191cf790de73594e68173346e3d474dcd091bea37cd0b51af695f887acd372fdc7c0510dea635cdb34f7b0ea575ce79f70b69be24a8939f4ea72f4c82faee1301ca371270515ce73e2e8a6b7fab3e2439a15392d01a92b03eb619e58611088a941bc62020168f185d881d71e062e89503d22f60bb61e7befe0d7fcc3d5e4fb43cbf9e243d0a01af275f9e04d5299e5f4f7e3eb4569e5f389479fafc7ad2c413a2ff807dd6f20319f9ac4508266a8468a012638c3f31e9670003684d708965e510375ef12ac40f63d20e2d29f8f61e6ee54411f8479c1e32183ebc7cccc9e2db69d38d4a71293285905300d6c1684e4be6508a61b27ad5b98b35e4f043256d79925ebff8137f62c78e1ddb65524c1aca868897a688cf90fe11b847f449e9bc348dd9ca4a2acec266ea6c9a0cedb432c6e831bbf45adbb8ee74ea368f076b049f2228a8545d673da050dc3c78b07cec04c141a57238724c5cb9cd393b4c467ef9cd6f5ad9556b2b0f140ae3c1665e83967ebb9dadeaf881c7ab6f21f1b16a6dc5a6bd01775129968ac70ed749296dad5689e53a693b730ec8c61693f254d7b8fcd9375da57a22254b9652f250390e0a73ee2375f993ca39c7d8388e07cbc74e1014777176cc3297e50af380c6260704aebb3b4e8ec3a670e7e6e25c92ba288151790eabce59d983cba9e81eafda5797155acd373ff2b7a1bfcad99d8871c6192de7be54aba0534a19bd09482a51b838f3120c1a525c5d06aecbe5a282b90b54a247ef66ba31b121f1e7c4a4006111d33224edec44273b4e80a4c4a028c4b31384087b6f4c2e5bd14947274e82b84d312c3ae11f34df3b3bfc43c76bde19d1dc6ecf5d0f3389626688889f2a7ce841e802331071468c3153aeac006cdf34efc608779a97793797890c4fca88e20a23a42c5e2124519484153050c24b10d8484370b811c7105f80be80ed3edc62e22a82083398a832c59630603bf7dcf5f8f074acd656b9b39325c16271dc45a57076583e78689ed541f6ed18e01f536e177221177ef00d985f68f986ac2f4c71d19476b3583070179562a978ec741c3d47c5839b16e5fddd7336eb230fd5c7abfaa69742a43cb12c51a5e2c1f2b173c4a7089293c7128d93e7a383342b9e717d64ab05ce4f193839e7ed91e63a4f6f3efc83bff37624ed1d6f5c85a9ce63c955b7e39d3c2272d57daa266ee8be63ba713f969be4ca3ad5a656acebb18e65b6da8cceb96933b375720f1fad352a9807cbc7ceb55c8a312a8da03e3d237e1ca42aa6d5072c5935bd9ce8499ce768ef951baa54d6ce34b65407e1118188a7705491e663e692254bfeb162f4ed38aa0c2c89a0f5ece30f5c7ed5e0fa3035c1d74f996fc751717725305d83043294c2c84224a58a20985c896387a12ca0b8224b134f718bd540438728ccf8b0030d4d22d490a4c4501b668c81860998286209a1a12186b802f61846414d4a8051450e6800db637f9c71462c95b25796542a6bb98b4ab1543c763a8ed26a6de5ba13d2793e9c776404224db77f01aa08843904e9ed070801c8ea43d5cfae07f3d90de9df304e7a91a39452c6cfacd536b79b66ede41e9635824ab5a4b57da139765191519501589e7772b2639f624abafdcd8ba0f4fa19dcbc0eea6ab99dabb459662ff7e0b8a78851ca18633f7f2304516c1760024ea00ff864bdd597808f2e97b8d5638c9e72d9ca7c8bae010ca171034667117d741fd1837891e3a38b105d4da844e7197fd21cd4ea11aca911a4641693e4391eb43c4816311f9d47842c603eba4fb396364105499b741688ce92ab1deb799d29b98a114876edb2456f213acb158b7ea4ddcedae96ec75c2599b81dda26d407b3938eb91dbd5ba6b1a9d0aeaf45660b9853cb448ed9c9b0efa6c8f4ac3a8b47b622587dcbbc16680630c79cca5abf52afccf5c608c739839ba6d55a6bad95d65a3967df2457690b98d3efc6415565b1aaef54e7a94ea4baec7c30979d0ff5e662574211b98473e94c6e7c18a41e83374630e99837d95cf3ce87b3952557b556af6e6b6c72443a960d896f04f508d45d65d8386763f689a5646b7e3746b6162676f9877d0cbbcf71df0ddb7b53aeea3e972b0cbbc19d5f0bd3378fdb77e393b956647eb662d55972a5b21553c92b579863d8746e923d9bd898ddb25ff6143b3f567db40af3f676bc4d72566298b3f8c7f698efc856bb4df57763a4b600daef862d86c465c9d68d912cfb6e6e0af3cb9d8ffd78e40adb912b8cd52aacbf163a647197816eb7ed3536691576e3c3922bcc89c815e6d149fc6915b6c5afbdfa8db70ac3306761be83390fe644b008a43e7f738d4b5d3a506c8a064639caad76f6b07b0e51df1f120f70d355499aa75f4833bf90e6c31b2d0bdc43ba4ab64ec02b197f5e785987da2590152472553d1350902bd9df11b9926e742350ecea51492102fd11a955d287f0472dad923502394fcf23373e9108890622908c4b358cedc608d682f56e8c44d07a04298b40777c50ee7aa8730a42e297c02073773e986fb239ca533be60c4eeae398c71eb296975256efa60406ad777e63e4a6482fb9a1ce60e6fdb5d05e3ba76004b98bde791838639c25b4d70a52239b77be39833cbd75decdf41b2311ec3c829d778ea93081f8445a25bd478456c5bf9891ce39e7be1638ef9c41acdbe1bc9be9ac4d8571759e2ea279d24b6050a58123ad9220771968bf61d0ca95949e92eed255b282be5499cca6495067695d5a2f2557944a671a9d524fc996f494d24da2ac9d7b839b797b782b607db26f9336b8def9a46e705bc8dc7a04d23fcfad83350dbf4d2e03738dcb3711eb96686ebf9b2abb22d6b56f89741b825651ebddd4cfe7ca1575955c5167908eb949a81bb76997423c7369f8add2d03f3f5bc7d280557baa3fd5b8d29b646346b44b1c9ccf1143885ee54a07476474648e16e4e86e6eb140668a0d6088428d2ed600a36bdcba71bd91832b2c9801063080f1fa42f4fa62c5e538bc8a6e2d4ee3f052cfd91e442186e572b98eb8d24115621a8909542c7d6cc1cc711c6e72c122064b182a92a840fae83c767c2ce6d20b8fbc7c4dc1f47cc6f30b8b99df68b0288ebc74f62e163754edc816834c5c2e978b59eec05eb8ec72c5dd0d69e7b0a13b9da5f46b3dba2a3a2bfa4e741ee91caaacb35c7912555db264c912fbc3c4e8b1238f398f5c426712d577d21b344748286e64fe711f63c1eda4b7a81750c9810b0c37a44106c8592401440e2f2f397cf9028caff710d0b8ea63a7628c593696ad9b21a860024f32607281311c0104b25b2b7ef8a2245b4b2c1420864017f8d5051954c0ae61802ea09000fbd3b265063f5d60f002498b38c4508a40440071c4aa421303335c88706a00d96f16e51b5b85819d350aeeee86e1466ff9eaeeeef8fd85e14f11b9cd089a29ac88f9769ad8c59ee8b20930b3e3a2b8edfcb2b285a49452ee684f2600e1c3255109e998981b9f5f39343db78a6b95f42c2e87d66ab9482cb5b05b742f51f60a0eed4720edfa373e9c0e45ca18364c8d1bdabfdcaca718a5b829d54eeb44c7f11c1e1ebf666ee672dbdafe72a3e7787e59f9c2c505e3e586975f56b678dac50d1df0d10a16df3dbd6c518c9ae797152b36d850609e5309b3945180919f6d1510b7bd82b9d185c824760bb1714ec864f813d27f9f38fae8231491ddd383c38fac61d9eed33af123cd91135e524d08818413354e54f9e845625f2a3e7a5bf6389be33ccd31d205f77af41d688c8172460c42445534b1e202a59f25ba7c01466771eb041cb4f0e1066198015305187d875b1a18830651194414892106185d3626c48dbf3db9fed1eb1437745ef100acecc18067c79962ae4ce2860ef8d8bdc316725c591e60a8cba57c87c5a170aa17eea8d520e3fa00e3ce1994b6c0e20637d65a8bc69d33f8f072ad0d57b829c0f0a69f36b583c44b6fad4dfdf42e3f3f7b9612a98830a44c92a1c4f232a8572d3ba8e8a574f2528a1a1fca201d602885baf4aaa574961e7619251a0f1bcc52d1930f3b0c0d1836131217a42e4d8d05f3b0b5f49493874dd445397cd8461b187615a20e22ea212bfd63a5816424534605663e8c666ac0b05d3f61827a2583c060d182840a3e8c481d8061e462825cb59465a48c4e22100f6624c8dde448afa4949209263ee4250b861ca67bd51eee643cceb42628e5d2fc968a58c153ea07e87a28996ec5a6293dc5af9145133ea03144c41a5b300c769872461958b4d10232b8227410f3ad0eaa5f94f26107751359e50730b62fc11b4d426b8841841458980b28d01023882d3394e1014bc4865c2ed70b559b32a44161e3d2e98888d7cf2ee0f43be5889f44c0f0d30de0338021f714e5050641419eb2162edcc50bca61bc0f59cc10484f7d52b7673cf5d40f4d4fdd81d8f2d4553b3cf59d209478ea3c41e0e0a913197ac153f7116288a77e4488369e3a92156cf1d44d18c288a7beea7a6853bf752b5b263861c5171b663003284980f6470d41c4e0850a80b090016dbcf2f607a6b7aeea7a6c0d60c8a1429c90c7d71bc0d0c7d7205f598b071f32971f9a7cf559dd3af9ea29157c75ef61065f5de54395afceaae1abf3e8f0f52594c6572f40d75331ac879fc79c005d0fe6676297a229da4f23a6065bb626d860fb3c6a1d269a43336802d97c5118df2e9b5ae701df2e91dae551d196a1d661299ecba09203248e86bcb0e20688250a173f5af490c64f0d60cd1725c1fd51fc9397174e80d0210a1e76004bf0d28516519e5451640436e617537acca7c57c005d0fe673cea22a7e7a4eadb5d65a6bad2e80aea7ca90f574e7e953b7d46f4c41911045403cf50f090989314fe34fe35ed74383440dba0420c104ebab7bd753ed85c053249078ea20743d9488480a0efee4270f40d773aa2f444143075f519b5c2ed70bb44a0913058c2f635e5e4c41c24b03c2e0610c144be400694cb4325952b6bcf5214a0a158f617ea5d0f09803a0ebc152d3edbc428aeba7e3743dd343ee09580486393ea6cd818da77ea14001c58a7f4159234a114ffda6eba193916460c60b4a4c0cd5e00d2033e0808d34b458630730a0c8418dafbea3eba9fe23c677def9075d4f07870aba0c7152a435aa60050af8eaa9aea7524a29750fba1e6a02d311afe77c72de41d7c3c17144124cec5046107050c91287164cac9469a38aa62eb28ca9e109592390cbe58a816660df16e282c68006416990d587ace508293d141187115f88889eba8ae88da7bec3821e9e3a0f0bd078ea3aba1e8ad4b5d68af43509a97a17c313153429630666c400560234b1c60c636658b2c1126075e2abfb1a5f9da708325f8b10f3d5515d4f5d2282c4c3d06f3e37b7e97ab6333b40a16122c4133e80f102cc144cb06048146ad01064c38f4b8858251ec93a77b872b2e1c76583866c9c605bebf08b871cbe7d3a691d1ad9622f4da61823c5c9116d6c01db67906cb57e9021c90d239a583103b6cfa1d6894db44e51eb184918c0f669656251922d3e0ac2f29c739ea3ebe1ec7c66b752d3f5d0cd79072e7ef3dbf56c1683820b95329478838a9f2f3e66bc1086061a7478f2068831f1d80e583ce6345d0fb6e3adf30e42bcf553d763ad16d5e6439caf3e80d5af0e5f3d1546071c7cf5aeeba99efd743bfd4e4f4d1d6ef8e95cd7335da55a628dd7ba1e4d8922ec1266c4860d13218ade503223e88c1131806de40b2d63baf0e10c3784e212958fe85c8206356c668720cad8d246161bb6d031e3a7c950123ee420c9684ad687a825acfc5ce2e7a76b5dcf4cd110e7290b40ea3789a79e1a63c4e5a9675d0ff59a80ea56004ac4f098d7ae07b34d7ad5de2409283ef39939d6f564666ed8414d8ef8aeed4dd4de46ed6d45b6a2eb483652eb706926177c7b8b699deedbbba9bdcbb85a87937e9cb40e050a6a2964a575f8958411df2eb1b4cefd6692c313549688c286112f68029bc78c09628c2621b480c108b05d26b54e54e2dba592042397a4646a2966fec816ebcb5b9fd56df55b63125f5520f401f1a775f825b6719873d8a4d4e3fbf4ada34b6a3a21fd4a58c87219f3ec96dbee53e4886c6d2e7ff358f0945cae8940da95a3da6c2f7d43719867b1dce8e21fda071d65a155d2ebdfbcf82357d2b12ebab887f4f8e46abfa455d25968d5a5403764324bf8478fc75c49ebdc97ce02b683c8088c3446a55eeb0db49a1293a241ea6627c8ce183bd8d8b1c68ecbdca9eba198a7cab0e9d356c7343199c4e98464181bb371381b01ccebc6496ee34e2107b0aebbd8c98e7aa79009115fa7639403aae1e262bfad13a56739dad2512460328e8de6e05be8284c32a55e8839877da8efc62ada6bd886c93085141a860106b7af054d73bb650d50c90348d7e16a8e71b11b822d99ee82066a8eea2640df7aec4ae0ae88e61a8d82696eb9986d4180fd429fcf342ff479ebf2a232dbf5448a518f9a8db2713c658fe770f93df43b63a45e8ae334efa61ddc642784c710c25b8420e0319f18e6b51bc27a5bb18f4e0bc346a3dce9d229943ba3752cda8b02bac3c5dc8742b94442ebd337daf1acb556e766d75329cd6e4840647a750930094a00c2d7ef048b592fdc3c89f6d6b7da4da0fee6b51bc26434c1039711988c267878c1d7adbae63cb292a0bdfdb48ff38ca751fd1db9af39743ab135e0a6bd872a399eb2fb0093261eeab702eb75ce39b121ac578f9d0498b03c40c85aa4207a25f231ffc2aef2d3ab3704131a70b9810c68200d8950bd3b0930f1008b50db89c7be13302701f695000429a4730e5dd24cbaa550c79376be6ddbe6ddf56c9dc7b58a6e6eebcdf685914ab9276c3ea1dc13ac87d36bb87d6194de9218e219904ae72e68a0ec09d03f797725f0007ec0858c2678e042461341a0a4512894bbb9f4f6c86d5cb83976eabed0e7b9ef041bd6eb39548a77f2a2e8ed269d2d3dd228e9042c095f670fcf963e3d7ac38d5d04343a84f5b5ca48e5fc6e1ab4e319fdbaa452de56719dde7146d9915023758ac3a5d8c7a16877fb168770abfae364790a2ff01875ae5591e270a38e5cdab67872a3571ee355f08e9bfa9d5d8f6c76fa8e6d94a3727624509f4e71b8d52790f97c0dfba78d08f8b0c62108d8f2758caf9f9350a334639b7e23e589499eee689d0a61ee91d9cc0ba733f99e9877bf1c39257a94878b39679d629e20406007c8020ef0801f07733cc0c6010c04001c02e6ec02e096cb15d6393b0e001888e6ec20b04e45b9f593c7d83c47474742996f586739cea31cc66df1372a7fd34e96db4ecea753cfc9378eb31ee72e5b48fce47cf2a80f377ae71991c3ad97e21f616c7a65c338daf5e4787e8151c46f93abb9ce791d4d8d2391adf82357985b7b7f8cc8e19bbc3244da2ab68cdbbc79050ca18f39bc085ecf881c6ee3fd386863f3b1ccf1c524b932d26a2724be1139fab72df32216dce394f2f0eaf896e0c058c17327a4b9cda371acfb68bcc845896bbde3366f56e185ebb913c242d7715efcaedb3edac3b53ebb14e2739105dce6c5a2566144dca306372679188984f4db2cd73a0a65bfe892361e83a87ecee3cf11dacfbc7ed411378c2e5713ba7131a95598a3bcd8a55798db78dc0c0823982654369fc1c11cce93c288a8514717c41030af5ee4927159ff16bf981423532a7531cc7b098e4831a91980b98d48adc27cc39c2ad172d3b8cd9b545c7e29353dbfc090f2fc9cc6c5ae67fb2813d7ba8d3f8d05f7c02212f7c09c2281611e7f9c388adb52fc23c53d22977934e8f20b0cd28771cc63ce6f08e12f5ee3386b238659cfea66bdcc3347c56dfb4213bcf6d5ecbb4d5c7b5006e734c7dbc1c7683daec27a7b53e6d445c995b78ef5ee4961ba5c515e89e6f1bb9b80079e398fb41fcee73bd817b25cc54b4929b8eddc128a7f44d7ff804a824a82b216854a92049504d5d26e3649924f82a2417d679323439db2cc2687bda713ea2f4d1214eab7930d8f24417da625b1a72fb4dd1726b9495034a8af41322937b94929ef26a5f44d06d1d236e7715adcddd75a9b04f52b8010e455619067577d108b3abda1846624589bb9e6d1cead671ee699b7d7524acd25739f04983c96655f5813669e7d219257423fded0bf73ce0b913ca70365b3b9a6792192d7dc7aa1bfa571ceb22cdb5cebdc7a9c0e94cdc9e6c03a8fe60b6bbc9df3fc737861cdadf19450fb79a1ffe69ba5f1f09e429e974efd33e7692991f4e62c3be73e9a4f9e3e0e91bcb4dcf785a994b51ee69ba78492501df3eb9d70ef17669eb523b14cdc64e5263789c4f3dd6cdef9c9a5fb77b37d44b8935898f2f67bfa6e30bfe1dc7a76c359d7fc06db36977673e99a6b5e78a3494d4a2e0a9a4f0945c284855c86390929b573639df3fe6e36b72efde475dfcde637986b2e6fb09bcd6a5f9882ff0a3b9e044c32cfbcf026735e7a3ebac93e22fcbcc4ec3558bcbe48f1e2cb0b552ef686136c88716d1a5aca48e26661b871312533ae4d5aba16298b9b556174b128ba898b35a144916c72ed14335dfc40832e52b044172fbce1c699ab3dbfbc8c2153c61a5e6650c6adcf2f2f4a5e92d85e2f477c9872d1d84d3bd92b44b6b5d150e705071bad891fce8beb9429e1dac870568932da18343589a14c061b964492edd2020f2813aefa44379530c2724823263a844d2f713471d011593b4c1cf58a6d7c75719dee1241bd625be61583241b197859ab7165053959eecdf3eb0a0d2fb835cf2f29da0873eff34b0a325a70699e5f5248d7e59e5f52bc008e6bf3fc92228d97142d838b7a7e49a103180324d7b7cf570c5ef87e5d91c10b066686b62f36dbd581b2c9216bfa368d7f2a65ed17fbd4715b6b595b59897c8dd129592fdb763bcbd595dcad036593a3e6d29c3a6ed3b20e4e2b283666d63ed28fd1931f5598af843ed72a7bc3cee4f9c3aa8f5cb1b78f6ecf7b29a5fc543a9efe6d1959d2d3fc8dab65d25d55b0694fdcd834eb69d9fa33538f1400e14fa0ec126b617245273639fa44125baf354ccec0d862cb8cc63b62658bc6ad0f919bfec86d956b2b6dd454d44ab3ad891a0bcfce4eb6351116ad8b342c5bb6d1266a2c5a5b6923adb1d4b06f908447c02d1a6f9a6f09e38097b87163fbd6755d4fe69d3d71430df40d342586d0de457064e16904ecd36cd31a684a6d29750a9d426903f5941eeaa09e42bf1377ea7a363f7134fdd338fc90ce6d5a2dd34eb6e6d8a75ac646331d74f0d3b7f8fdaca732edc4f64fbb6c3ba1b99cd56868686e13531b72890c4ba55a2213860cb744a689894ca79db225324d4c6468ae46c3d1743dd3694e35dc4eb72212924f6f9a731e1ee0391a1969e1c1f2b16220118c8a7f9c8cb4788a55d5e3905ae7b20ac5a503a3e384cf747ce65bec325a4f1349b62e98ce9552499d83f9ee004de6db3d7d1c464de38cb454e940e336232d4756b4645f0d57d3f5645ed30de9b79e8323ca49a2d2d86cd31a482c8a21b4b3d3502f6315e69ad7b10af32d06b54e0dab740875107e17011d1c0aad6ad73c25ad6aef92c420d9aae15854dd23c2fee3c0a98b80f7d4371ad991323dd92f720fccb31c9c96d9aa754d801bb468d1e5719eb568e1c46f3449f2ca1d1984474606b2825d212504f30f7885c32d1ef363958c1fed726353a48de1f8304e28377ef4f9717372b93040e3431bbd7011e6a34f2b3a54098303339050238b1184e1c318176c79614c1a710bd00396a84992f28b1b9da373120c5e4fa9a76647ed9c13063bfc9ca91b2ed878e90d002eaef8f6489f98b3861cd14a0b46f779f291bbb0122f45308c4d64f0718b31c61859d1c1307679713185d6aefef9d824c63863d2189ff9ccfca88fdeda2eb3534c0bc05a9376f8eab5eba9292828a50e78ba85d2d32d7ca0ce61a654925dc821a40cf261909749a0f45bc44b4f7131f3d23d8b2e2f5d75c4cb971659bca08b8f60cca23593f2a389e590058e9fd3adc521eef0f3a7571b454a31af205e483f1d4b01cdf9110b30e4421c2c9840c04fb7582cb980053f9d42b9e14c7d7455eb4c9f36cc1c1e3f7d04612d5cb6802177f9e9d30670f2f490c9f0f890cde80060462752662c72c1740e239616547981d3afab5851fcf41d1e9f2d56fc74242d68e3a7a3e0fae92c68b9c165450a7eaa00daf21a226a1d7e5961e6e7155f7e7ab422c4cfe8c59dd3cec9d91748f9f6d6890f60e2a78bf0d3914c4f828212fe313f8ba40617a66da8d40bf7b549ce2a6433020000000003150030301410088542b1509808c3f40314000e839e506a4a190ad424c9711442c61843880104008008888cd0cc560000106300b479d21305646feb36a226480bd58950b0b93287eb086ea7bc5e5392f202ab88e9a9066bc6dc5c46f8e735cdac8d156924cb73370ed34c50faa4d1a3407226f941a47d0c4fd9b2ef3a633a3f04a52dd00ee7c8af34aa95cd98b6b960d8c18a1ebd06ac77cfba8eed1899a4979d98496749b66b952d0e60de04ce18951821f29effb67f59d70617b3abea6c91be4907ae7966cab403307f735c389912ccb0a45a623f82edeb7863d3ca1ca987ac33aea6bae33b71235dab1be4358a5be977fab7582e68836a1ce0f0d5277e67f165563fa8a8db7f7f63d8ce61bd3e161e2b6f27ba6a992c5b5aeb69f30a815a60c704810a23ad63a10c712a1c4ef99403639d7140a775320e45f53995860ecfb0cc96d1303c02e24ab1c56716f74bf70b1b305fdda771d2fed5009b013026795187111dd1f5ea7274a47ec3aeb06946c39d86cd58ccd8f06c909920e23821cd52c85ef61de4d88b6eb73f54316331004d646da1686984c64f54ce1ecd8a9d2c9e8a13eb86d2f0c60079e70ff30feb050c55f0ea46b6625dcefdb83c060ce57ede3606313b23573190ed3264933119eabea3b99f82b2dac5a8e82ea834a3ff428c8e82e6cd367c0d24625183d7a1b4e503b42d4220ccdd299a702e7d8334cd14dd0298055934e32ac50d7a2abf8a6a005dfd48056e2c04f8135933a2a3e13203a2dd80e867ea4867965108c494d4fe00c891937ba2be8d5dc4b5e69a8cacd8f422ea6d12c894459a04182a810754611d9d51eefaf8fd045181f130f60d43215ccd18cd3bcb5234537c7b643705d127df3d07afaf498edfeea2f2371abeca91c3120e1ef59f2b3591f084721febe2acefc755513ac2871957beead9109badae7663402e7ff498106ef4ae2d2f18044b8017b4c59166a7d4fb69b10fb8ee3583e3449041d29e7eeb7aafd7c8afc0ac01190843fd2e5fa392988d75a94facdf83fe3a04ee14b11ef4e420afb56a724a4d5a8202b1a62673037b3d65c2294826f289f171eefe520935b913b1f84492982f42c521243fcc1629af52bd25d1dbae2d0be73952ddf9acccd5a7b67807b13a30a38dfe020a475ffe49a007ac0b6b1adc7f5549195448eb5465596d7f2acad10f908f98b264830d8f21498dd58b093b504f6882f4c4b77bfd0de53a9ef8a4350dd7d16c7f3138a0eb79235c984fb7ab6bb9cf560707704c3b4cd3d2c9dfe335c42fe84e82d9634ba842598895268b2c89e0a6be08027e83af7a660c10c12bee12388e04830dd347d96fcd49e6b49d8b993c8ccaad81da0e50f8e976b80376cc4c43ac307e5fa145b884ac868bdcfa4485cfd725acf933818b64193b1d50924688dc6eb1ea7f3c04c4aa0aa97bf49de9bd404b330818a0876b2a682e4fa26d70eda4f0d3d5dc20fab9300fd584e54ee043fd5a64a58cfdb9649dc423e6266fd49f693341fe591d41a14bed4edca95d32fca263c3d34a5e4f8a91c73779f8949bbd73c084a4fba25ae9f5a09d520b5166567450352c789c53f297823b81c5d01c75a30b0777d9cadcb25ddcd58ed4ad8cde13c687901ab836aa43a4e2650e28afa7077abf02c00d55a5987b00811d44b6d7aa3c2deae7d5fbf04b24a904ec8a6cd034708caa9400fe37c8a566d504fcb380c66a17a67029610264fc0dcec04e7d55240698922359d447188d1f6d57cb002b7fa5a0999ae256c997eda039fcab11e9883290d43f794cbb55d823fbf4062bcbbb015bd646ea369741d4f5ac9e3aca9533e70b7c580010b811413c27e97a4af79ddf66448d92fff37c621d738e83300bc85c909f999bf2b53577e1f25f5e882f88f7bf543861fe0dfd25a40ad1fc158671d53f7fc36a9f2746815cad33c172930e22e38300c07a36ba1d6882491f8d86dbee57529ad22ff8da437969c895a3e34dfcf47349e4ee624a2bb87c311d214f0a4c62e63a85a68da83d9edf8308a3bad2207398d00115b9aadd6179f9418006bb88b046f488eda0d307767b3c361836014d7e7e11a40c7f6bfe37e856cb73bfe66e5e21829de711eb9ec8d4ab43a85cd000c3206825a8c919ff94b572824246413a2d26ad4a06c02b8333b133ea7ec8141603e5ae39a0c839ccc70fdec0c63591f0cc827569f46faadb5de648917a86b7305054fcdd3862689a6c4472db4b8c15d5e71025ac1fa8d8bd81f9260a63da9e4c505136d5898eb68828cc0c4e726911dddbfb2f87ca73e6198ce0fd28d15064771736e65f5bd752617846b38226ff312d50ef4c63efc2f0ce9c309a1bcd9f250e4f3670ade80f1715b16eb89f49fbb67c1ab8fb0bfecf48d0cfd6763c4a69f151d5d8f510e06e2e4ff1d52bc54ef71b4e61dfda51cdc56ed35c613ec596fb92279319e3b12bc8bb2ab7f787b1bd0716224e54d697a20c4213ab57d8c69ae3bb6693968cdbb874ffa1dae6d5a7124719e184dda75d072e557fd4af14b55411deacd663b669e7ba2f133b22b7d4a140e4cbf77da20b33ffd71a5002578efaa2a95ab6b2d9aa5329335fc4dcdf178a9a466d1ed5515c84f3d7c41ec13b913e47d35cf21b38e781fc75e38ca3101c99de95ef5694e2f6b2b7f5f074a65828708723b26300a3d490fed17d91e855deb76b5f90dcc06d7b45377cd1480d9ddbc772b0b37319072157ca197079b5f795a7a7d330908bf074b6a90f3ccca65c619264a620d092b7a60aff7b249c4800828bbab8ad84090060d898740df46b460393c322bc4d7e22c80eb78989cd6a09a5d0c86b29e19e190f9003af29fd571fc6c183666fbe65ef055c374156b109443aa5834f898cc1bf4c0e1cd41933bc2274a8bb9c82f365c81d7b9d84b1128c880f80abddaf192d77dfba564ca9c144d9923fb72745e20e0803bc4a20a246b094731913320c890930bb4d5a20c48e96e510cc5ab71fb45a006569ec1ba7fd78ab85880e47110d27b771553981d4b12c08ff17858567543bf2a053249aed68454fcac4278892955fb647edb429b2b2b932151ddfda736a5186083ae76ea0d596ae165d161e0e86a2a0161a6f6434a4ec465856578a3ed062e4da813c72adc48756dd44a1553d647b31575e46cc28d8edef41b475085202721fd44d9fe047057261ec2c71dc9f7a7602f3c07c342eb99075113d874669204d099054d04f8c8dfd40a45690693761d87fcdb462ec5047b43a446578be8d47a1f96a784aa82073e6d5080752e1f28d3099f74c0a4deeede0201e095dc7435f32526932387a0ca120da6b7a96f0f25dc8e5853e12ced046eb8402d1d7378309d80866b5f230e3b7276cc5ec9861aee6436db26958085178b29963343dddc61a99d1da183396d3b698f1e370bb421c5034c6389561cc9d8c10b03ac1067326bdcea3e311206128fb3527463a0993936f55a0d772e093a40edba6322d524df9b9952f1003b75ec6cba7983c9b34083968ffa2e9024a5b14b74643caa00d541bf37642427881cd96547b4b7c03fdb186d35b750a08780cc80e7d4fb89e0811f3b9771a4594c28b157920fa561112ea69c61dc71e799d2c230f930c6a241ed8d8058585bb17a4eb9473fde37de629db806744750cc8dd18c40cfd558f91e40f4dddffd5d22c24cac13636f4b0ea33f5d3cd0866b9ad9d4cc2bc2a54a9a20a172ef91bd35775f4755ffff7b979124d743330d6f14140058f61c4053c7b3c29520bd39f854bc58f5d35c682dcd9c63f60bd00f891ca4e027dba917034b749dfd036d4c8c4d61b84fda2981e931bf241ef744468fd009abaf7ad86595969e50df64289e1162e1ea86b4bb8bf5372278094ef9aea16f9c772e59fd891afeafe4b978f491811e5379a71fbe8b2fdf53e6d71fb70f57dd84724c705efff117b44cbecfbfd39fc3456a25e0aa0f9ec908c1f868030137cd2d547db3eac2f0c2b254cdd1a0864ccf1986860b0e42a40f570902ad2077ea5385550aaa7e3501d90be63259f602ec1703e08656f4b944d856ee62bed34dd404f12dfc3f382fca8434025437b6dd730f0747625cf0f57f60041e65f065928661ff7141c3fde1221d88ccc23278f09c1f43b432536e557d27b6fe8ecb292e8d10c471f10c9f547c917415cc0854ca5fd9b44525a4e453c9ec105927c8da9fe027a41c6e340ac0874e0959051096504552b7bb5b2e4aabace7841f2de925e83f8cf8d7a4325d0e071465a31c61a36d6551e43c4bb75439b5d7226ff5f35619060de50fc378f12ba4ef606cecc116db33e6b21e60d1afb70474e6ce6264ff2297485445317adf4c045c19d45f9ee9b327db8401d31882444878b0975e6e8903083347145b073130703d052e0ae66094c88b44156a99331ad8828cb9dd86fa97a17a3a9eb634fd2596ee362ea35f534900be16127d834caf63b4b7625db3726ce8d7fe2e6e1b345c6b87298220af7fdc2089eff5dea9f550fb62785e921ffc94776d9c48c0d4b8311d2329c9c8e54634b545fc5b503f9f219e8dab1acf1f6c806b2ccc77540a89d23d6db2cb5a1b83d11d4c68669bfafc0521eb0fde58281f3847b52465296575d5567035680514afa68058dfb0f8097384f4c025c6abd6aaa1453e1a5d90a2c70f224bd5e102cc77bb8929725ab34f982cb51972ecd1d5fede98033c41081e4e0f676f68a000d7a08598c62db715b8dad12f9c3b68a57a403097eaed5d3936206682dc3074ecbab0261f8154c61edc521b6606e8f954ba786f5f420e916fa50eac7d7f50ea61c6564d3151728ebaac4351c27ca05de48bf19f527d158e8be93e198e29d175c7ddd5253a6e999d4b9badc8145082c0104fac11f71e9f6d6f37077cbf952b4692a3e9f066d7b65e86053d295de13ffb421305b0d976cd9729566198d79ce0c2b63e9d1ca5765ba935840b7ffe4a78ecb8766ab290814c9e2255191f9f589f41a36a3bf278a479d8788919dd9a18c795beedeb474b3aa557f178977db85e9453443e4755e5d126806f3a83305ba4241f7adcfc09c694ee0ce6195e3b8642e21068af18bbe1193cb7575e9eccedba3534f6fbbfc5eaf45f5a677060525fa9d8757cc02b0560eae1832b18c923425c817a6c3d7a99d633f17c887ee1cc758272662980d75acbf8def7813d460bbb4e83bc83781f72ffea18344d4db6ba1382d0a23e76d11d3cf2834055d3da230ae3255ed41e6dd15c2f6359cf97c87858fd0773624bbd350ebd08e0a1fa98d768855d570eec59d6d4c2368d1d119b2b4349490652e9a9fb6650d491326055d8fd695c6354a2b0e4926febae0561cbc9da04781a65f30c7c8481718bff93b3860765e7b60143ad26b562427286dc55a29b6c3831b3050539e0089ac7b470a0d189526e254ae0a7ca8d511cbb2d866009e714566f8bc568dde4c1d4805eff8772818e66e5725b3bf12932fef53ad7d6287f59be60ae6b91e34d94084c75a7c66c7868181f1de4d6c2d028f6131b7bd3722e4bfc26c6eb2eb6193dedb1cb4394adaf8168ffc37fe07a6cad32ab869604194ac159981ab59058a0623eb64dc6f1629bb5e36b7d8adb719879aefd0632bbd46150a4e7dc5da2a6b664d9a0a2e126b582d23e756d2c797bd1793fe1e1d3bf59ec5f2c8b253fadcb9756734cb3bd168611355fdce3984718f2ae8c1a5ed3affcd9c8aa381f73e6908f9f60f07d1883561b8fb76e9ce4122513c08cc81a0a8fc362e3cd811e4ead372fd4248c916e1dc3e8336aba896800c3e8ed1f0fe31fcd0999e2ec41b5344703ea9ea45e3db58731c6103aa161aec306ac8c6d8e0d8cbd922728ee86b1c14b1ad51485dcdb767703a49771371c148a07c2bfa82a30c1651d42379b717731f7c2483e6046ca1b8fd2cba6995eda42ceb1279872667ccc9005a2ec896138dabd5e92931813ed8159e5b36ea7871cf097ab51f9620f769b284076c53d232c6bb15db18d95dd184763e98b8905ec0ccd0df662276152bc97a7f8243148214ded7017b8fe2d2a2006ee6a7360494fa397da44cff6e809b6021478786ed379857cf0c0d2595d15b27c250b6b4f37583a0406c3ffeb32441851b29aef58a94f458423929ce255218846660c546ca64a6c9faae911be1b4533581691c0762d78a109bff2b0f31f219a2899219d03969fdd69e9dc5879fe1d053c9045b01b2641202ab53b1504e7127fab1a7aa9559559ea0165ee8023e21c72160ad7a33e766d79cc4da5ccbb9ff0206487d2980301012efde4a4dda188c5b7611a70c60241f76a8989262a5d4875798166448018493451c629d289126150338373e590a3d4a198e4ac950805d86bdb75643bc4a8594bf5d6563d22ce704a5b9548408c002a6f8a460a79129ad15ecdd58b22d1ddcf27b953a9972d1e5c967c88eb3377a3b196c08854791f4b75bf5f557330db01065126c6a57e5a31cd180c10451a3237dfd318a693ade1a44d94f9c323253ef2f582cdf3f20beff808ddaef8839e612ee6ce22c2a6630ea444fce47a63cad5dab5128ff1781b48f1870deb1f01b88de2597f3cb2e2173e4df0bbeb41d76a65f28fdc2956ada5e3520577cbd153d368b3edc3b61116009f6a26f806fa9cd1b6e8cd150a2a998333e3d307b7ee6494e648102c8e28cdf1cca3626e61d0e51c27055de42136ff03a3cca78f665f787be45772e2c0183217621563e3df352babdc29790845b89c097cab9824182e0df8b8bcd2ae14ea3d32f6a40931e3c1fa31cc0048a393c8962e5ba05825867d6c042425e91908c1a1feac3a1c578884bc9726f146f3948cdb97f536bbaaec141dd14302391e74df31beec85d9ad0829074d7a53b9ded2724bd054086ffb750b5dadb223ee062e2da91c5dafa0691879c3e85eb744b118379ae8183398990fbabbf40a71afe190aee81f4a24cdee9840678e5f22e40bfd239c5f9373c45cfea99e5ca4f7be4824b2a1f4c61186550eaec3b10639967ae2a72fd87f7eaad3c37cb49b9a085355eb546bc3a6c5060b3bec0284158cc0974378aa44956c24585a58933b985a6695bbab8d9764fb1d7a1b21158f36456d48442520213ee45f171741232ce3bea5e16ca635572e1db117f4c4bd43f432401157258173485f632204fd9be5645d202b29131aacd527ebbb948843b7695d10d7e025b7b42824e253e91b18310d71d9f4df920c706c09c54320980272d34f9d7ef44198ebc8a62abe7f0c3e8eafe7f02910b4878c2b574f8fab59772f47d2b82c3f6502babefcf9c3bcdfe2e33a04187bc0cd52b3c84cee7b397884b567d6f6d2a1e08d59149b7bff0e70c14f44b72cd86fb7a7d3ac3a9f7937ee421198be36fb142f307d9b34886947d436c8570e8d42c6d710d09c78bbc09231914f4a2cf33706c718a36b649fa199e53cadd47592b78255c6df8084c6ec06bb5b3740eb276ef10f217c30b8cf46d7753e5f3edf08284e2f03e9fd5acf46962dfdc9ac20d35fa75f0a6755f16eed332b28b09a4a605921964bfbdc036b5cfb3d2fb0aded97786fd82acf92c8da3d5f178da89025aaeec1658e71888cac0de9c0c000b5c870b3c1e8e82bc6da131d2c1981a6b7c71be781d49c8d8addc85b93cc4270aa49fdc82dfb99bc0692990b5400041586facc91f109a91256b6e05dc8c25b6a07547fb2c5b6e02046cbf5a55f0846b1a8c19ec8449311838f3609bc0ab66272fb58a1c9c88533df2f4f03cb69db65cf72f663f73e55d8b2521091a218c4710500742b2aa8d3c0c660fd70587f43f44d4b4a0fc82e195e2083711009230c425028ac11f035262226036b90779335fb75432dd70dc1b023627a74da37c4ead1d6035d86a5243d67c449d4a8089e0af839e68820172ea2fff75fe689856ed004ab92a3220f6c1329d9f6c4ebf6a3aae028a0e68a60b3db31abdfb113c309fc08f0eb4055bee4a1223646964613edf782e40d41e021aa1fe18949c937303e177acfb8caf65c675eadf4ca9a7e03ac4827e4f3dc13182b8ff2edf29ac5607d6b321581c0026943a469ea1928fa0fab0414edb206890e7e2e26a0c2042e429b25ea5f2823b3a050e495538319acda8054ca015be6153c8ea03d90c1eafd6134bc402245d5a8f77958f6f0723d35ec18e04f37b8e13010ca6e6c703fbdaadb554aea3fc1d2b0d7156907000fd5c26052bc8d3b8e27675e7056ae985336cd92187e2b6f2b7546820195d38bdeb875a7ef12048d2f202da4ceca533dce48824918939359201a12bf443edd82bebd73a018f22573406447a487ba700aea969f30081251f16d64715810cbe4e91d1f15477087babb4e1d9f89bb6572795d48752c2e0a60b479474c150d184f6669806e9f2cb806b5b72ab18d008007a43dca7180a1a14a6e9870650b39ddf129f0b103bf0f46020e707c9bc44d2e3d68216da180fc6e417b5d990097bf470fca2110c954f6da4d2ae40b9bc4d1fa47d7116becb9d807a02796e9d8a299e266836752889f2d8fa5aca7a5d64e5c1830028f6d59cc54b5d912bb7dbf8a220a2832ba9e1aa59d8c7e3af7b58602834c0bea1723dfc3db63228bbad8e25269aa30a9779eb6127c33800770ede9dc4ee1325eccf1c420a069a2e97e4485cfab4635d628e8826b2ae3e970a7504c20687ce44a102877623880a16e78de89bceff54a0ec87dd8315c4f3c23805e2d3f3f0f47dc39a881bfd72d6364336d20f41471c135d812a3b09353ed70ed304d9b393e45634859571e6dbdd0248204612a8a709e234424a1e973bd7aea8a9e14579cf640d0f35bca68e8453ca83fa0d69fd245b832a8d3e291620a94a8f0d6a9f663f4bad40e9c0f4c146b9494d3d1deabaf17b6a19f6b78225ee1611bf4a363b8831dab7987f089170596d18bcc6603e8aa0e9c78d74cd17dc214f68607efd9a76e7f88001ff1cc166db34d5d00903dc1d7e7df99a52191631f8ddc2b9385e236d5d9fae8ed171119610830379a4e178217020178d8e4cf05f984f3cb727bd5f71419b798c3bf864256789224e3552cc85db02e4640504cedcaa046836b9d0993655fd870f2449b3f7270295d47e91feb5f4509c732bc761f7889d32983258be6a358f2cd8400df6d126c99fbcf3a7d7716b4663bc8e9ac2134e97d4f4f40b32018f8a0244bc59f123c43b83497feedbc89214aab9c232a14c99879da26ec0dde5ff600215d225500aefa7a1d813dd51f7cc6d0b2b724354efdb33f8b313393c69bf844efb8b0beee600a9935d2e9a4b996af6440ff1fe672cbadcab4f81611f39d5e8b92674e6591b97f48d6909e027e3d50cf1de353901dc752b03ae7382df6cfbd0354ae82492f045eda534c7fd6207ea242079f53aad686428b1d012ddafd87ec32a8fb4fbdb3d17375980ff7be85273481e2e598e74c7f55b90c7f2c50a220ff69f4beedae95b8661afb8d8fc200b968b4708345682490fd6b0d4cfc3629c1e45debf6c864e4c19a79b78a868d9f75331a677a551189a6fc7264ea37cc4b39724c8c5120db1b0619cabb872cc1aae41e8e83171a181e56af421fe0764573bad96c4c0a606327ac6429572f4e091249eab3822623d7f0cbc6a04556165b1c7711d07bc0be37072d43929bd0b70554c17c95582d1fec3ae94f72a0d01a1963905ca67f669f6112c58555891fd33d297f410c6d0037c9d68dfdfa2f9bf7c2f0e4a3ead0bd8a508db0922ce766aa73dad1c7ade8d38ce0d839bca1f7e137fcd05437d9b4f08dc3301774576c7f0c3e109667d31bcea4462e7f85bfce2dc529845ea73023de30e948bf03cf2cea413cb6fb116d07459bc158fc65023b24f9383aa3b22c46781aa5c22ea5ddd0ef5ac3f6d043e0a502c49bc725956d709040852165ff20da38ccfa4439ec45aa360a0a9ae02eb75615ed91b6c7368841e784f6e68e184af019d2a7d02a4396bf6e87bd4aa47f4209d00aad00de3bca2a1194162616f4ebbd34706f64a8b06e572e29f0d9a34fd84dba96731d309f090f25fa9bae6b89f43068cf1302817635c361ccb322d5e6e0adfb9a58b3fb866806a8e5ee6e19b386236b4fd6bbfc3c2c50abcd05d6c4a1c4c27575a325a398757d3be087a469d1dcfe309549d6b2553ba3605b400a5f65dfea660a20319d91678e0272f3da692edecd0e690c8ebd3bc270374fdce985c27b972203a4e78b87503e9e8712e9b9557ec38bbc5af5d3503e730967e31ab406267845eb10e041fbbc938dc18f0c8e3ecb9467237937d8ba6f7269c4e1049779a3cd30e105adb8ef28b00746ef4119515754a2fc39deddbdb3bd98393020388b04fdd96c4f2f714303156dc0b0589314f0da408fe72cc00948ab3743285a4cda74fc2e5ac8581065b27c643d7f3e0c13fc32c6ee448ca29d9141a234e8fdedcdbe686d0001585ded9d65f884cfac1dac629ba99b9a3584e59c7c61019caaec2054093b14b0e2ee757774beafdbeea508b7e8e2f105a7eeda19c922e97c29e018d34df7c5d0a31478519cea07a1fda8d96e0384c826b04b8c35367b4441dce2dbb6d64f043aaabcab56ef5b529041fe3f5714a9e4901259f4a5ab3f2ca85fe0008ddd0e80352f050f4f998f20266fc938671f101c98bef8308b0de627c551bc8852326ebe3533135de646267222e310a0217deb2460af94fffdeead3e7f39df439ad2ecddcfaa0134f13b968fbd10754c5fd99e5ef239ef64a7d7de64b614ec055f069126794c81128296ea19fa49d836b2d013375be141cfabba664495d82ab2274bd3109e9934d4d2a49ce27275ffa72ce2585161c97f0bff5912f06cc0055aa50222abef76f365d2ff14ea99541ccf95bd14825d5a36846c4d060e09bbe2471fd72181445750a226145af6a90c903739346c31185d30da496cd98df115742089b741fdc83f05b590afb1dba9d3484e317c2707f2566b602a094d3f87f11f6ea2e2c1bf241e42a1ce5ba614d63d44e91bb0ed791cc36f86834f2de864f4ca316504dd8b32556ac3c3bcf4dd0939960d2fb1f3a30d61f268d58e1e0f0b4965f09b604e9d3e54091d10172f512f1bca0352f61174a771f0d32a4308840add5ba2184f085cd783b1991800d0c15897cc56362d1d06861d7cac643a292c296c80ad89847d55a2e5413786b3885c69a556b99613f81ceb0d6f8b42b2b8bb5e63cab0678bb496acc57159e37836c0a8a2b08ca04210650fbb308e97687c4d648ca9c457a5f971316a43b54018689c14d8408bcebb099917c60a1b9d34d4991eccee3d33ec9b7f72069bd2393b92e7068f58649f1118727e78136d97c11efa3338947b24f2e8f2af890fb437060c1091610e84762ba7abc7cf590f4ea891225f86e1caaa5c4499a00f9e611fbe97282b9624a545a3e3267efd6df725911d51881ab58d050155752750810507d59f3c4c8cd7ae2c0c9ae4908f49506e5bdfbb3643d642c52ccd66b64e03a2db21861ba9d2805cc6341eb43208f02dd225a4d02fd40e016823d98438ecd51b60d5cc7d343cb270e321650adbbe62d1481164bab79455a0c226df084534c679fea77f9820d2b0051f7743d6642ba29b83d40ccd4a61225829881165cf492e881b049c3bd682ddbe443ab9f4967d848bf0b495d843714f55b00d96886a11edb0921c0170c75e960519d22a2673210cfd622f544c7c03648f598b2210f954d21b73601b4b20b4011b678874e187a086ac9a170c83591909fa09609f694d2aa64f0fb397b863cb4731a7cc9e3a1525b52b23266814aa5eb9ced6b7806644d0f38dc34563a421edc51487200f964ec15c9208c7a9a44459d0e83d0882ae53e77abd2995a9bf9939f81ca8ccc3d6be0b80fe36decdc1b37b533d6009ab32a1c9d1b19a050221e4145871a195ca67be8584f09fe62b504d82a30addce1f87f0d02e709f2a04505690874cc115603b2f536d1738b91f8cba32463afc6aef2799b9a0b68d538d15fec432627bd1f451b4c5d65483124a889a67e603e4c9a4e0c60f78b74b2cc1d39cbbfc5c672491bea3ab3ffb014dd4f42519cf8632882b69f4b138b00d541da1afe2627c8061acde25ceae7d1e066137804f39985da5566c5ca2f0e566cf8f1324b4c8d4ed275fef2d3c6ce35384e84a812fb334f3acf9b02120d56654bd9e3ff4dcc58320468f9067a057416c9c0b91f51b9951feee063f3c48be125c12abec6b08cb21298d7db22a4957a98512866facb448f425b0a19a40db8a5a646ba00e4a6c1c1c16a404fc0716fc80aed20e10cd5e1426d18d2cb23b6f403512007ba8b22a47a91d385d65de15bcfcbbf0279800ec9757d41a03e4e0194440771a2a63e8acda76b4affb089ee570acd7c6e95addb0bf4d5889266997a7bdafa336b80fc564ade55d2a64775cbda9b918693c9ae02a9d1e3ad1d7899d6b4f17808e44784ccee6916398b4aaa4a4043e259b71fccf2eac41e34b903041cef7629035d770026e4c29293b9a70131a2a893f5c03497b56530f4b944445b80fbceadabb3b0ce32bf9621eac3d873a2a64945112262352e02f5fc95f489b9db60053fd1f1a70d84e21cce468ca9706041773eafff08860084a118de995d5e0ad2a45288a5180ae34d9941375c6605821dc7b34084a9768b27b2888a392a0e95367f9cd2113cb791ec4b28642c5f48afbf9758ad585ae61932ea56764810bef8b8cc4bd6459ad4f0c8eeb6afaf851bc451266e2550ec5982004fc4d108f6363975165338e5d18565989319f226967821533b559a7dc4936d7b075301e4c5d80e360b1869aeb700dbcf37ee7476133ed50eeb5883d22dfd9e22ebc60062c84dce82499abb56c4e46f14882600d7d8f8116c3409454366858b1f6630fc8d23b495fedc85f797ee70bdbe8b237761728028da46ac2a9102187de9d4a51bbcf8d1ea60bbf3d8127ab28b1ef7fb5882551449ec5dcbf6eb7c75457ebd6b65fae1c6e06bc7c85406cc33e3bd472186fa77735d5503c04a572bf33f100cb2813073980e170cea54b801b4873b043c849917765218bea30440607adca2ebae00fad3129be32f9af69ae697b94a15650f9e7b3765a623f3d9d7abe1d6ab5d3e44ce83dd83a10678d8b0c9060f0dbb6a1507a7b58f01dbbfee2461fca44f4471e27c12cf95286afaec00345524458dcaa33099e48a107cd93dcfbf5261b881bd399df3e2b6de2500b761c8b24733316812fe6fe7945e2b600225f22ff24c6ef437298ab589883e4950f379950d6b2dc70866a2bf3ae40660b468080a645e2e2a65c605ee19cf9516c9758d00fe478ea50f57149efd0bc24ec54c9c52c232f80dd88d8b69f588a7d7874205ae78085ff56681bf2efcc7972a201f16556eca6ae06f595f94d02fa10d8ada4a9f081385cfb41add4a260b84606873fd39462ed5ed108a82e085924cd3f42876a1328a82aa387d4ed0439db9d0207e5c1365d09211200760391098684216728d221791a35a50792186228df9d4472964cc718a9d02c96c600a5c51da8ff246d3743f891f570f307509bdd2a08d51016b37fc0bf63df60b8e8145cbbacff40c85c72e1a5d8e3b48954b836ec3c65a225bcecef5ca4ed986c45ae0969a09d8ba12a50591d983da83519386188563ec22b8cf4b99e03b750ade78c1e5b42675fd09afa4551cda09427375bf3a462140b0c5a1c525d06942f9d824efc44a459115d9f47ff3cb2bc2390cc19b702bcdb9eb91598e4581701e491d7f94cc00a7f8de7c0925e4f16ad5c924fcd10e497cdb1d8a7af9ae07b0022132d4b5e6eccc1a59a32a844a9555bd1bc76eeea0b349079894b8f4c07a08f4a9715fffff61fc526667464413f4131e9e094e231b97204bbbc82a311506704a0afe753ab56de9bbe7d77639c0045ba0b5a4a48f4619bc39052306bbc3e4e0cc014dcaa26522eaba899ad0b46ef5a852f21dff2188b586702d1aa4684655d9f1919249d8390f63d172808ef92d565842d39877fdd5442f0eb35b0479cced0b3b343c52e2df86589daa6c92ab33d35febd396b7d046ab082473fff93269fa6c070a2ee06c056e7f9d82f1915a14d681e7dc1f025054053cab5c4bf56c05cafe91abf7843ffef43b65f10861b2c29a4da6858f759c6f99fd5b704104bcaf0a870314979e25768e8d0c3bbf8cf97616d68042455c94a03ea18f8d3327931f14df149f6818169fd8fc3b4b23928e3899ff4a935a927fce912b57debafe8a0d64f92d6d4c56279af894e7b32b19c3c62b16a8228b05dae25ac4a4592fa0620913013722e673628fecf0608f3777a74e8f10bfa1e1ebc31bb8c15be6370435779a94e5b86973f0f25dc9b1f687b04c30863c5fa08b3058eeaba3a3dd2ac77a992db010536183a83856f50a3b22f5a83a58cbae24b524353c9bce15eddee8c21018ec8ef688cc82f18e26004ee88a99e07a4f18da802cabc1af13bd945ea09de8e5dc6b920f477efca148c827e3889d2d57acb3f3a50b2fead245b53913b632ac0bfa6fb010b1b1a883cf45da579557d2b001ee344d8705054b070a8204b0e4dc6efbf7fa10961f386ee30150afb8c443aeb36078452664b06007b7c2745c96c97f1ff75851c4556c1431d61c77e096548a93f139f0ea84ba73f0c620da068a5b57ca97d960ca18470a495693ff68b29c6a3e01094a14b36273ecf6afa4930e7cb4545b62db1f6064d748fea058077f998dac8fa11eb0d730a85576ecfaf47a7f158963c6fca963f66c4df292b9906592944d7852d5e6bc8594ecd5a3fb0837c327b2827040f5fa32e46d00a1234b45147ca1fac91596217b1291ba66f5311e415b2b8d7d1459c6aff550340609c8f4f0f5ea907d7ec24373747ed8b342adb281d77e79b900f878c300f81e05f68ffd594ed01d81babd29504ff2052a457cebdc95f0e1c0c960ad150ecff0b456ec21a22bc646721e4a5aaa811f4a40f30a38fff8aed90bf4aec62a841513c7f23fbb7b533520df43c1491ea1ea15d807cec4b2d4aff75a930cf4026dc588bfce8a7a3bdd5ff9cfa16b8759ff79936a86cacf277e6cdd9cea4133381fb4d50faac3a5af1a5489b41d2d6a2c8d2d19dfcb4717fcab1e384a459b8ab0417c54d1d1e4456bf0d39d12fd6f6addfeeb53071adeb899060088a0ae41f85b76e84c8748d06582eedd5401f6a332ac8b6b9d763e80a01dba7ca07c470addd0351554420e1d42450d9f24a774abd0e369ca75e86403166ce1362d73aec8236209434fd0a7332fc9b75df88a2518e8ca0e4315e93d57a7b43b132665f714df9f5316334bb11be9be710d53b2737c5a64bd8080aea37d7ca5d801cb4d4df6823a60a3e719b08943205bdbbb9484c84bf5a05e4a8dcaa848bf828a790370e1fc729bbcae29b6a86b96ab3a1e38e2407a83dbae1b513892d219a69ee3888e7bfebb174835b39de1ab17558e2402a39d552ab1cd46b1f626b93388a1f2e6642c9e995d7e16e3745148a121c7aa9d7675a5cc4a1315dd4f93780e777ee44df7ab8528a7bbdad16564ceec1b1d08c97327c6d4fdc60df6173189dcfca4426c7c72e79babbb6e09cb47f6e51ca305c881c9e7361e8aa46545ca698384de867c85377f4ec6b8797070cb029b65480d5e153b89bda750755f6b0e32e963c0c04d23d3f14d6c4a26ce1df38bb3c382f5cb27d0243a5b4a274f5a612a10e9a0c39171e489b2fee0dbd23e386ac3c2a8525bde525d5d37c0d62e63d776e30bb148aebe8de8047682f2e9e32da139811e9a4ce9d4e910522644c1d32165a7a3de1967983469e2e81a50e0d793b2299063d7c1924df4918ffe6c337036c1a17614644abc02319110154b462da0c8b390486a1d83e8b182b45bc8593cc0269e9e2a04426de4124c501361ae9a8e3068a365cd13d5350505eade833b291b88a3c5461be141161d1547d446af5f01212ec83bc89c737b961f2aa7d4090c4a46ad3623d4a68d0665caddb96a58053c48b5b06a722917b1f120dc902110c2edecd1873bbbf82560c155d72d12275ddfdb97ada1ed5d3c5cf5c09f8682c5979b54892cc00964a1bd34914764ce1885032c83fd2748a51f66335a006940f21f14f74853da4ef616df2bc23bae8a284340c535db282b8695db06ad01f0c57c727bc9e92a9aee7759637f8771b85996a471b424a7abef517d34b33454714b96d2c25429a22305538b6187c4e215cef6ea67210b53c383e4996b48a5e7949dd92a9da513e602500b31c47d9c1b1cd3b2ef8f10bdfe525c670978cbdf6ca1e4cccc65c1b618fd18c9893039bd490e26400218b9e137c66fd1a2ce5b19aed7f20be622b9fd7858f04bdabe399e32480725e45789fca40f498a1233f8fd51d620297270879f9fb4ba4f4b01a647ce764ac768bb3cd4b36a8702fa1b2447eec5c0e92959b829ba69610e0100065c93e240306b605bfa6aca3ee20b446935405b81009c3caf42fde9e5b6a58890e272bd9757418d7a9775a4babb2dd172b5f8548d63c666bbfa837efc3455315ee2f637e3cd0af9cc2c2761fb55086023d4c559e1853f9e218f9d530474c5d924a9090f24c9e574ae8f365ff765a9751896fa859625ad9881c1c9c0bd804b187b0694cda94589e4d43c1c6c733bb84e6e48e37aacf4e3cc9d0e3582e34399e333e0e50417d3032430c269221e3a9dec21edbb91f636ac6d96b268cc8418f3beaa4846e165af5e82921d9bad682bf64b0694c7c781d591e850e7e47e9189136ccb7f2636023ccc072aa75550ca1b195cb9560a8f4ae7507f1dded3228f16f149fa2159e9acb74dff27eea5c54ad5848d2866e695e0b34be2df034c49fdf31af012760395342ed79c1374d4efb8a12ee263bce6b9ccccd4d089cdf03ad009a3e15fee4ddaf96ea9bbf676e859928cf8cf33aab931820f28176f4377663440648ab4ed87d06f5055ea85d02be31a67db79864dcecf97b99048dc21ac055a3267484952f0dcdf90b83e2d92e6e3585073f32dd60471021e04d04d28710f538c37f70bc72c079821de1cbc7c2ef6f2ef5cda777e5ce316d4fbda93a6ba05557a7492c6448c6da484b34e9fcce9cb2c0ebefa71855f0ec307e40256beb91ede38658e4a9761d2e4ede7781e4a62f488525764b77e121b699fb71c4e01cb8be3942922ac327f8d1523300113f84438e81668696a84b96432798d0a384702e71150b2b45a5a5c9042a1d52d3f910ec6d2055e0a599d79b4400f4e5cd6f0cb94c68cd607e0e1451dc3e01eb6748858573e1055d1a849e05a2cf67b52e238e2d075731a0e5a798b7f50c1b0e133f33f638e0df2e14657b084197a097b4eabc3d5e000fbbeb885858de5841f886718e200f155494273ae5af01feb3867d6a317c89e4a944ce6a97b6f2c9fb59656b413fd440fbc1a165a8e30e9f96c1c7219685d54d25d273e7387645de931170aae7b84621a19635f08676cdaa55c13dcc3931df46b6d7318980a430248d60061bc4b92230a4582afa4e7f2c2cfa906bd91ad3c8a6b815eb221408b8871345b70d281fbd3e34703f56cd2ba140772735725ab2a0356450002ce651a18d7a9b34a01c7b6c034e7639179920db695df84160fb43520a80a931dd6a64906b03bdc58a69e323b50ea4444aad5c0109ab509710f98039796abc1805039d62312f39bb6ec0ad28fbde39ab1d8844327fb668a90ba70f364419fc65d8ed05d1f9c982ad057de4c83019e381fcb54b5086b008a6d6866665ebcc920610afee51cc4958550507ee96119f529ba821b894c58170e126001c154c9246c21e4741ba0557edf2e402bb925438c4c8bad3a6236170ded47248032c28b2d5879899f05cf797b26a3e1c7555f52e7272c2479deafacb0bd82c7dbac3005c6b6cf84c0f9212584c323fa766d1451193b37fa39c6132b691100b27a51f6e9d13b246a8807069e13c42aba7d1bec405ad4cded96eb735c88864e43296954965eb77f95f71a597c8bab65eff1aa6b8abf411e89d6b196c267d80bb06c64cd94854a08c1ee538d36d6aae9e632227a45cf7955675c8182b200a0f84d8d6e780d6d8584986adc376f12d0934cb0ad007eaf452e32c165ddbf1225ebada125be7a69669e546001f2f614b76c402aa8f7550cdbc7ac90bbb0cd5c339b4130f744757ccd5a6d0d6e284be5141b33327ee0c97fa47fd5410f4e8b528e7c5c633e9c97d731663b243eea36cdd497ccd82b880f37a3499a0d688d909566bbbc0109d23268f2a190f3fd7f84f65ea76c9a2c208a9b8f0d993cb868fd1fad5d47c59cdc5577610d9f702379ec14beb8b9daac6183e2eabf428c0319c88557c8511326feece9e557cd6f70fda9cf2e24d74617dd92118316f00e4094144e91257564cd30d197d2ea94488bb2a1b99aa10d418d769995970a5da5eb9b27ade6e2dd11bde504c529c35a190ed37f88789340d7a7347f53218502e46ed5750847788506b900e8f5bdbe27b8d06ac2c919f457e800bf06caafcde56bf294c40f27fd863581042a6c9363be869e005160b62b4b9bb802b68435e33e87a3295a82f360137c7303123afe644fe413ab008b2e01b245a8084aff0b184e05c373280bb8b6e759fd8e09f4ebfd8ad3b8828c87bc1f1f438650bf4c0895b64627817ffe93943b1cbd823db3d44649e7109491da4d2a29fdb02f84ea22b9066f4f8b8f91f2420ca651c73ede0fc6f49f7f46ac6cfff73be2b3725e021a3846b9b11c92402ba0224a17148a87d5806546548cfb4c59bf56f77c4bcdb06fcb4ba09a7c3d30cf54c706ab95f636a3e10beca58dcf1262b7447fc89d51615275a058c4a8be8db9ccc27753950534058cea65061ac2b4d34b123a6b2efd3b06daa2cf98bacc619e53e45858215f1b2fa218b9443b870ad9f2253cce667bd314adeb6e758d45c1e376375fde1b845b447f504f36d57de962973b6e5256a512d8aa927e0d22c120629327bb8237195565a3c12ceb69509fa35543e589f34ff20004a1e863dbab4f0e811e877161085a5d1e34885f98491b47c0b610f4808623e8da352d92f8726a864443798d9aace10fac11f636e40c84726ca785a8ffc4e1463f05c24c7ca16c5d61cc6f405b14b33d462516c748dc399ab6945fdbea7a3b48f1d024be801767f374c4704f8520b36a9ca5040aa81abe1f92287b7acf105bcfe2297960b0cd4694f1d71b615d65d19f4a7570463d3cc476c58b55068b95018ed1a8299e2ed9cec832dacdc2927c6ce9da8649e95b48d93bd21ff8a42c22061099f6a43cd20a89eaf9a0397f98a944756dce13ece6cb24e90c6123419a7d88a310165755cd7d4bd882d90f6f989a5f38acbc3eee2851b5163debe28b1bd17e212ebf371b6c7ddad206dfcae93259a992f1f994f3c9fe42a8a3ac14469172871ffdbaac8d6e25c47804bf3176fd73ea2b20908bd517f6c78f087847a3844a09d878d72f64fa425012d6a1efe6d5964bd248a2ba684f886883d3c51eace41409ca6e857f2ddfec1809c688a6833fc6cc9ececc112907488ae50c4bdba2e32c39e012ce49d329521d2927cdda6f97714fbb3ac41a45734f8ca0b6be18b2bda1765ae43b4a64715ee4d52aef2ee8611c2521d8cd8e4036dcdcecb571a4c0e8a5c67c6c64ba5a52b89fbfa23736e5083189c7894c3bbf26a9a342851297dc35e4365f0031a18eee8ef2f9e6b80068b6e59192c62b64ed2508c4f24c20234a58d7b2e719adce80aa8a86bacd2fc2c6805bc99e6922452fcc45ce4c65a82f0d151c0525f7697815dfe2afa2e6eac4f6389b7aa6bab20ee4c44472521b756c711342ea6facd6bcadf64c5ea40453c94d16e6752175055237359284fad3ba44458ea3b4865deb3406a930676f48228a670833a2c42d28bd8f132aafc675384721643acc950e75afcdbe9306f6a66f4e9e9620e947b62309830165a9d9ec3b68c121cc92c6e5d7ee12f818296707491957f2c914f4ff27ded392f7974021a97196e23c13bdf6c635b88d32e68447ede8ccae401c765493041cf4791817ef3ff6ea2a7575c8538a3338135d260877d1915ac3a61f031b0e682dca88dcf9ac73775f8a93c9c9d8e5de8545519a93210331aca202c00cc2d1f8d778479769b0dc549a7bd36a5ad8d4ec3d541c168e235b69d50762a6bfc3384699948aca6e8b0a0c91e2cc95e72750224231ab0e71e622ade3e0184c43e47da7607249718a4ef866d96cab94760b37e926ba535da66d8c2c14529c248b6e360f4c9e6631529c1f29c4cc76b43637d44a3541cc4410b0f6f33329012b84d1922fadf2f03340cc143a0a9a8176867f7d792c0e067ec399a918612e34bb973d2f55033e7186476b2da716e758030925892105bd18091103e56d264e1b87fa21cdd4cb749202c29666b82f2fd77061905fa70325b466f597cd4c6e4a768050b3a82aa9d9a1ca630408b0e0ebe78b0b0c010e88d9cf401800db5b641130f913089f45b2acdc3d4e5c213714870ced78388a03ad985cc3f84990faefa6a69a9b79a650c3a01575cc16c2d9fa0b946232a690e99581a453e5927240f3e164abfd01965f9f48ba3fc3105979754d04a67f936455dd3935c751754c8f60d3a94a12f46adcf619b0711e44d52c714ccd6a0f8262f655ac0400c4e2f555cb56a9db83d8dc09b164fece6daa2308bf446b2cff6c94e50ec9c0180b7e8fdd6147d088e1c768380ba14b7d023793c266005de59d247b52418142bac1f98c49204b5ede8f21a50c77c0d8c1a25164b742ced31b992ea5f172a9940097caf82d954498a4534df2321f46a8c8fd67f6db457039940116f519d8d29684d1e9450c88f6f42bcdca438ad68ea913f2fc38a48568ef45272e143269eb97d40b327543bd90c3fdb1b91f0fd2089b06234aaf1e9dcaf7fa51b391adc0646edce285cdc7e2567518b73a274fe289938df719d9fcd178639930d015dcda5f55a90a957fbb2cb82e69553bd20620d196c64c70b6476526308984a2db7f3e8650d47048734421628e807c9411d20653c941b1b4e49b90b28d8ba93ada699d6c419f8cc0039f57421c428d0631b8e0b4bc3c4d58e4f8a1297a602bab95873b6a17819cb483bded9ad2f5b4c6136457d803fd51ea1560be0e023fa6b64c9cc1f4867dc01effbe14be6e8b65e011cf6bbe9d42556d27ec9a7fcfe8c083f53e92fe2146f54e0342b6673384a31ee8638f3058af90656a88e1bc94b978f4a00723ad734871edde1161155505d45cb2a095f14830defe11f35bc4bc1706025007ef546695028ce2d73db8088a3f9fb4f512ae075734264c3d51b8a8480f9e4c7c44f0460ab7b5815d2d9606109fc6bc7551da40d045c4c6af7bf21662322bbef745cd156a425aaa4dfd4c25299e117309f0116231f581b933b945d18fde03958867eb0b2355e9350e88fc40464f582e294f2869297791886cff0c6c0fead39bfed9cbe8379b3ae348e497d6ee475944635f0657d692b43b9f3fd97ef0f4f9abbf6a74511f2c997ec0984791f6223963164453d9e619406db3245ec7e660bb300385695783d136e1c432b9c344720871381daaa85352ff8e3d52bbc97518c2573029b4d54a99ced64d7714df2ab87df5e33755fdba239e864ee59d103eb81d00bf61f96df23c6f8a8bac6ac8c28d0576481b53d5a795828f54e99723621c9a1d71864c8d15460b3fc898eeea8f5bdfcfffbcd4369800775e524ebafc35f826d1c1cd94452df55d2b3c9af6791cbe410980c70e388777af4232e7c5286aa6d3155d5726da4e66d58e85c07ffe61a6b721e631b2d36f70cbfea7cbd2f430272bfcf0dea4ad2ad96ae2d01e791cd87d4ecf6ec0eb204d49022afba367a8bc9b4c0161ba298da7546f7ab9aa3a8315ffd2b7069c1bdbdcffe36c30f1d04bbf1c1d38195f9a88a9ff187ed8082def8446c37ec7b4a6ceb38147c031e050c7ebde4f73bb584d4d709f46ac4f9c86e536320dd0eb1bfb96868699bec0902ee0e58c4963fd0ff9e8a21f5180e73142c0672867b0cf6792052da0e69db83c30cfe4e56f0f78aff3603d584f65680c80004a04b20de344b5f3d50455888196c96f3558150174bedb17a7da28337560564dd83bcb51d73e525fa29da87fde64e6b775b70f3c1d95e88dd05e32c2636f851d1533d0c6d2c638ee1d667753bb6bbff1d8164ccb6a7f64919326d09e5761da9fd4620e9ab672659fa3d4ae6ebfa7e18e4c27b171271a612728a621691b671eed4883181ed8d0ab7a3e70e6e205829e277e3dd672b6c09b21a2226eb447a529571d53f023bb96efac814bad9b852a8c0f482a9b9c6ac1a218a6979e5c65e6a758983e1673422394053fcbe6ad84bd81e3a14e65b4df8f426e41cfd82f35b364aa243ffa224ddc631d63503c12c223200e3e76b9a85944337dfe593a3a3534ee62f4c55dd70d78d2e81d477292b756aaf3c7f9b8fd1cd57297ec7f8e60d180a821c7089a0ff91e7cd8c089b67878351daf0d069be49bf903ecaca641d20aae69c81f4a821782f939b252629395220342eaad7d2f7f1c4d75db8cf026485c12e6037319997fff62c702df35b569610b7f630085376c58708731348499c07c9e008c9da89d26f29b4aa9e6b2396ba069b2186ace6eea527f2194ec91a163e9cdaf6efc6e1d44d2d730d58cfbfe148bbc979c9a60f596ee935abde4ccafeac02a05a899f07bf96cf89948a1f61eaad80b9a7a52d9ab922080e53e41ae74cea6c8578199e92e55452ac2078019eaf2813d18cf4b085b7f0f5038493256add75f8149e0eeeda1a96901fc26493ad25474baa3f0a9e3c0f15dbee31ab8e130c4e1f24640f20e3b97e62157293117f912ce9493a634a71d9e36c212e809ad666a3dc4a6f997c93aae50a78c6d7278ec511bb8cf314ee13b43ac0c73b2bffb8b87b5b316996065ed230be4adc6488526cdacd9f79a55edd85e0783b72cc7fd45022afa2037dcf8f2a9013d5e20d65b74ee44c31c05c1b523daba8038fa1a0af67b1e52a9b4373f7aca89235ccfc25f838e1b221b2c6c657e27d3786fac26f4297a539e6937b88881295cc0b3f7beae9a04438d1b7184589ee105d622f92710a58670a2a6c3690bb85482ea10b7cc14d641294d755b5c51d9aa42b566d31ef3e0d947afe4ba7d6fe656e1811edd6cf6a7d662b3ecae24f2b6c535f2f96870601d1fee1ec24bba1f80c87de0c850838a17b0dbcc6a5136395b2e31ac405ed21fe7950328fd48718424b0dd74e832fd20a7273c5f70baa7c55c9210ce4ce8dc2fc9f5b7c4ecfba9ef0a27b336c559261efc391eb1a571cb756cd914f3c2d4551b40da13d2c1777f51374f3facc41d78da29998896fbf400a86043e1523c0b261feece91a8e474de9126298de7a3278381a0161fb4f26c77f98aeede27f63b11aa0ea6269ee89621f8462d635b28c317b3e4e068bb4feb0a6fdf067706871aee15422803045ea54513c402023902ef375ec33408be23c89e56db0075a18678fbadaaa8527a1f5a227a2651c2df5ce50a2889c361c8ffd6ae273b8227294ca0bb6051770defba687dd0b12ee1bd98f6a22f464e59c68bbfbb62a9d4eb3da95157096c32b0d4275ce8f39940a0038267ec7dbfe343926d2b5ed1b8f314c623b376c0f84d53547f6ada255d6e69481e582cd71ca85da5c8e19532d7d5fd59df6d6350ed65ab5557056e171419572da4d3d334d7136ba7c3e437245e92030f652507537360eb39c6cbbceab51c923207c313bef0d4b2f74dde3d11f719e0882ab559faf83c392519e817b51ab20c6c1bd4c54f866e63d133e5bb9cfb84ce02eaf42fa0433c04449142d4d6d3c2f19b848a87bac2998981ec3089ae2947e0d00c5b3bd0e368719b1f8575b30fcf1107064ea5d827e4267f4a8a7078e0fd907d12def9cc850df4020aa6ecc1bc35fde5e99971aa02fbd384d9fdc3948a4563175ad839aee194f42e9c30c49a96f38f1b68216cae913e9d4830b06f5d2c2158031a50e0769337a0e876b772401fe85ac3421dd0f96494dc97c4342791cefdf280091732b40fa42eca1fbe60e45f5eaa00db4d1d55d4e1091aeab6f324b2b91a9522be91c5c355981001bbc5a5bd5e301d7bc49b3c8b407b0025c494a717e6e633dfac3b410828a5a2d843f0d50ea16698c61260e081c7a34df4319f184b90c11a330e2c843bb6eee9ca76ac5593795989e47a67e5dd858c0b93a2cc7014d1d17c7e394d13675194d299574bcdc72ea499fc147b083df6049451a82657575c299ecb073f41643bd73681b11836e888a24e428297b360c85b69999bc90801ba053d3c1f34e5908d22887d3c350496af3a18b8d975dc4a66ea72d0a1c07caf89200b27805286e07001369c0b9d8b4f708ab2ecd725b803b31ef7c749839802222c1b7b7499f4360033219454f087fa9a526bf037361412b358a6bd9e07fe29bcc29478b1d66be9bf5dcaf76e9899bae27724c90b8a16d6b88e3a625ba8b73f4f610c774f9b21a9e5a6640130739f49f21c894a10fd75fd0322a20c669331c322f4d582c93de7bf75cfa7dbf8a72d2f5436c322090521477f0f646898c47a3b801d4b7eff413a5b4c2f224bb478a0cf3aa3947ea21798174a136f291fa0bc5b7003fee60d7e12e11c1abd37283d10de6dc47103deeba9d2a89018fa9d5b694323adb2b7378ff59022f05b51f729f216ae55fb35782a4562bb99e99a2c4a36a543af23c8a36952933432379b19b9268665b79b8403193115740b1e2318b74450856e740bc08ee7c8f656dfd44696aed3aedaf716b2cf5d77c2e197a622e11815b2d03cc2996e094b796b81363e97a780960654c9d5266b21437e62e7ba8175a18003a9d10d511c65bcbae53c7b40e539706494c2f4930060353f78967c27261129d302b507a70e2c56285e88538271b70527e84a807cf9a9562f1e267f23d54d1938cc12d14c10375ec67a34b809d3f638bb1e156a1bbcc0a9c1f05673d3274b37b52d9a2ab13492365adb9109c27fd4b8bca8d2238cae0dc8c6f4baf8fa0fbcc701ddd4eb03c961063314b26410649b2db820a526563b38be6c5822c50b31126bce4a1ebe005c1e71110f62a09ea877d63115588bb715af61dcba5599f13df81986a1cec0abef5008986b9a86f97610f6875721b4f2b6a5b7b9e0d614ad60db2f4ec2aae4c312c0f8225163279b61f84f7a8cd40415f71916eae3abb8a1ff0ed64b2f866f3125f1c0ed738988c6dd2875addc1e1bb99b79c1bf6590dc6f367a438c84b7bf8f7e4cfae617c892d36369c6f924091879ef5ee387a28ba7f86b322c2293361edb85448e57042b8429b90349e1b4c532086c46663e1be1c3db1f2d1e2bb47ad65c7cfc6ee2928a052cf1d1acce38ba3a294c708d0fd4c79f2c4d4aedf5f2e0449417112431a2840338a533c21d1f2862b3d02857ceb9f24a9d861225bf2a8c4d55b8e392b980ddc19b8c38bc78e52b87e0f85cd6aded3e969f75cd1631b4c8f4729fab9744d48b4d4602dacddd316452d7b8a3cbd97861c8a64f2f3df09583984d6cba3d8b661c1fb86c606d98d82373bab9747c521b2d3c35d5e617211328486b214682664ad196f7793a1d1f0371905b3b907fa9c7202df1c88b9693939289930373d0ffa610cb036e8338a92fc6deb14e4f6d559e9a5d9f30e23a3e42e345263a85096521cef7219c989e31b2480e7b9941b28d4562a99a2dfb39af6bfcd1daa8831f11878d2d9e0b570b74120714c73f670d1854caa4b33ad355b55bd7ea996dcd110347a10e6105e7609046a94dd3a51635c266cb53454d2ca5ea5bf52581deeb38a4d6bf9c40635d2052bba9ba4af1a4bd178f7e4c11ed46c8246863cb84f17c0662ad9578bdf6f0d8ce7e37fb118a27b9ac96e9762584e60ec5dae132b36e7568a1f458d6de9341a715b6a8f0a14825c42b615b678714a0c5e1e61292d3945e6be3097dbd6d0513b458b522fb66d075f02d4a67a83230359333703e209ceea3c780f02ce411a7085fd1eb4dddb4c3b364330d425b2d7cdbb4a3f414171293c3e3ad7fdbb7f3bc273fa873eced09eb216d5edcd24ca716fd75035e88832bcc77c5d1cd21981684c90c417c5fe707a249202184403cd6a9ac275329d687bd0e6798b8782c0c340a8440c30a82e6987cefd689778ebd955f6cfc1f12b10748b5394f8c185a24286afc023250ebc0405299652ae4ed9c0cb4ef34b60b01ee304c42f8c9c910c461d47ae512431aeede86d5a5b769e371833e348f58f539397803a60f7b35837fc923e8163c2d55af9d0989d3a06b92a54240cf97d445075491b775a327a7417ca4b233844792cb985c48c6232a9b7cbbbc6f79d89a78605fc850dc88c84bc1b356708a3ce940b7b67bd133c10e31edbe0afb33911ddcc43d7f4f932f2662741a7eb9fb40775150cc402737607fd695656443595cba6ae2d51840571cd553685d033dc49a63bb19a690f6b22698d4a98e4ba716ed35f3acad58b889f8969412d004d487894b776313cd0caca1826ba1afa24f50d0dc7c7f1a84ad0a2cc5fc7b961c9dd5645fc11c13b3c819937417987fbacdc5bb5f0bda544e348fc981f39b289f7a4cb814dacce76791b55d547c4891477d128b8171d9865daba0bde6b52a3901987a85bebe97e06f7b14a564db638411dacc4490f68037829443b6c499b34a358d99ca150481d118d06cc3bda9eca909449c86468df0009e92f9ee4e49c0200797940f01d96ef032d6e49ede00d116f7976728381992fbefc5ea915b0d6b0ba509c390f69390b742e14b2b9b35aa2a4af027938b924bf99ac17ac7752651d7528f62a05d264fc4490a902675b18a2190c46b48e300f88ad98c10015e6c03ac4f3a6f1b66c1c55ea021cec8c3a46ac5e129d857deb62f2783cae6e6e282eafffbd6e79793070ff9ad8332dd3157a8332b96b3e1f6db4f66c1d82fd4f7b1cc694f683b222f3a0ea5c036f7c0c239222798df00c1e871d21087ff698119240eb6021582fe947cb73c99e60579ac2dc036e2cc21754803eef50bd1ae1ef10da70b2afbbf5ed4209990ea68c214c44bc4c2132e2a585cd85f7279716488177fc5ce09422fa0590713f88234f44b1afa505e17a6997bb5acafd5d148fe93dc30627462a7b3a044192b240f59a1e4e8437ffe0fc2fca62934ce8754acff9352532667e8d43090be840e9b59a10098b7417e379c796a14df471aa57fe8914f37bde975f60fc221930ebce4a6965c532dd7fb47a4f81a671f39aa9cfa94b63f7e3676bb5f2b0e58df13c41f576c8fc7cecf4ef3af8e8f5f84ef27ff3257dbd8198051995955735242dc4b6818cd42a3b243d8f82c0a4cd8b16dc82c91c9a3707aa1490e133d0b41c6017f86af686962f04903cc2abdaf589776996f94e8a16cda28720f4b2dda50a41899e15596b9584f85f5d05c5ad010b16a4b4fc4429e7e9b98d2be3b9d33b571a56df05b91509a01d22f31baa9f8cead38d93c8eca8414bf1553df074eb59565c0e1408b17572294e1240fc286134a7ddfedfbb1e521bb22d8a8efec137d3bfb060b4e9681a32920fb1454acb98d97b07a8e936c73c131251e805e2c693e383fc6365afa437f5675e5d7e6d592480c9dc40fd8c48df3042dadc91c4db6b74b28215eb104f4feee577515cbb64aad2597b89d853bbffc1c547d50e5ce7dc9d54abc78f74a27d5073715fefb085cf241343edb25de690cc3d40764b9e4dfc7dfea89a64746f01338eb813ba037ddc9e9ed4df7301bd3085f391ef897e6ef256592bd3853f883dbfd4f274b5b3e1c678508a96444fb890899944434a9ccb409ac681a6573f020710a39cfee3ef809befaf6ce542766faf2ca0664b452319bca00393d650aa5c94ca3ba1daa37983ba222a619a299083633c811aa5ef919dc743848f2bc065e4f7630ac17600828476eee010e5391ccd0537a0e22cb9c09630c0cb5804c597843dcd8c3c6bec69d6618c4d882d28d031274f5803cea4cd8b6968c9da1b3852875b462c35a11a30957352413a3f9f845895bcf34b7035a2d97d47d8e64a6a3caf36097809605352de65658941a59be29739acaea130b65f653c58a94c23e6e78188f532cc3218720a34ac5683389759fc402629eb0f4513f48caf6cc6e0161af0ae39649af65504edec70d05a80b1407e22ca812b0ad53a556154710cab80d56f06ee518d7b584356089ea8598a5de919999ebc41f37d85e59f7f51f61da21165d6eb5913bcf69ee4265ea39727aa1187b02ee85a1e2340d393105f0bf4a8a8939650fa04aa786ea99d0c29ab65902bc8d76e454091c1b00692507c340af76ac09a3fa18e15e9dc2f09df95eacc66f51408ede7c51707dccade0e2e05081abfa7f03fabd7b50d0d3c06d196be72a3dc4cd0d4298dc28558658f9a814ac937697a38e7ea06e8f295192b109864b3b20749002f83adbd039432129ff0ca6e68660d2233840f26e54628eead398704db95beb8f7352318a72e4d552ef5c091f44476255501ba820651550ce54aeecba0a03065c105be408f128fe68b82640195aea9c840feecafc573e18e64c95a927df91cd99ea705414952054a1f038b8155948f3bedb0d7ba59762c289a63326bd01912c43f2269e7b74d05c5e795a42948e120ccd12c8403488f9b5fb953c0466bf0f043faf49ee8f411eb821fb0dcd92e308e8b2556f19c0084ed0a638aa80699a300b52950f53d54192d3ae32d831211fba3bbd8b61569429a678b83685ce69b498d85add6232e6baf778134520090a03d7f90de8265e31493e80c2e53711a1f68a8b6354b393da613f2c0e2ee5e7c0ef0630c699408ffe14b95ee222ecb8d036a3dd8d96a5e2609eb0030a5907025107fe50a56a1db4670ea4750ffccadb28f6eff7cc5c3121ba61f97b10d270fde0503bdd6dfcb83b1e1ed9032aa12e1989da03cf56f680ac47caaa105368a8241dbe1992c455c137a8f0b44aba150894b2fa9d32b97e855b0319cb5e49c07b750b04bd1dd355089b80bc032d63926e72dba6a590cb5df165eb64eda20a6818dcc655a024c681e8fd1b6f207cd71ebbe5504e1201d076e14e346b55214b05b0d9870759e22ac6cb8a87d869bb46a36cd913fb851937411954ba0be859a61f228c08e29254c0004d10f9f4f2af8d8c94546c6fa0e136b40afdf34dabd058b4a30592418d661536b08d0db3fe45ba5b82df30ee95bdbf901e0d1b5bc4aa6032226f12c262125a60bcfdf4bce2acad71fb996b1216577dd1a7028e049707bb085ff9fedb147f356287290c7eac2b563e8c8d46a98d20d646e691af01b5d8aa6f6c2314e9dab2eab14fd18bcf536a114a332ee59f1e345c6825f5eae2d71f4f97beea6b76ad06962db7ec339591202be8f97bed327857f3ad21ee25c77c22d9577e1150844a89249a131b687c2a389a308dae42991469a882cb0f0cc472cb969e5207d09585770ceca86c41ebd00318a928a1b27ecf70c3b2a00868d96463035bb431b7b306e37226a5fa485abbdab43056205d7da3a10ced95f12c2c63eb4e5c94051012c5a8539ea7c1e10553fb2ecaf1fa70d4e5f0c36918eddc1317dfc6a50173df226e1ceff93f85396bb434134b6d8efea336166957faf62948fb71185077ce5834dc1e80040cff2f6eaf723520ae0e959fbcd4a9426b845f10f309408739b76d8af3364c656fa021de301d4d2f9f7ef7247670f6fdd052c3578ba20d03cee9232a8625125df831cf73c0d8917c7c1bb88a4bb106b2f8c69f33d97edf593aa9bd987aa82c74f1e1a0c29acde05b6512d179d1ed00969ec9cbe1485118d2f385e8366fd154233f2c41826906e6e1da734e104c61c881519081c7866559d403400d8dddd455c6ba01672fc052ec39060d3da76ca31e799c5f73b8663befd58566021fae1b686d859e6ad92c5faff0e0720642555f9fc05b2a88013325e99ebd8ac54517c9e510a62fb64427cac1a0400b2fb5a50a33bf3e6852e539757102fc05189bd997969abe48653867a21a203852c8b93cef826f85f64e93fe3da936dc8100156524df49d0c1730f3685e8cdc1e898b812f30c57dac4f473f6ffb83dafe9dfa70b4175037f889f624618aea102c42a7d3325726a5328813ca55cf7cdaacd3b02f182b8fee70b05021334226013038f35ab01a21e0d2ee709b55f4d087b641f68c6acb0e469c23584a8045269d728e8b6e5412ae1c409d8a036c12a6548d6c69744d1d9931a95dd5e188e02541ed7b9d3636528529ea3a1974bc3fa786b9853ce7a0f3fdc0a4ad956308723710a9acc389b6452264593594a02165aa0ef7835465cf2a61502017c93dc3b330b2ee15363f80281852eb1f428173c6d8616213e396d385470dfc0198d956c2712a73335b4bc88eb50eeb848ad15c430197aeec6ba2101a47633b615d245bc1970dbd5942e22cf9b60376d4d67b2d5ccc417e3950fae363a2d3a1813ccb84ab20183ae45830194c52f8a6c54406f4c0a47a455d30b9f72d40bdefc7437dab4198c253e15181342c0326b28572c675da1e73e6929da21288e767c2e44be2e4f5c5c8f80fdad9a0e354fb4e96962d88b9a859138e690fd3ec6d49ee8263391a064017c927a7db8ab8c9b8e6abeaa14c52ef270f0cbc777da6c38722f0a88aa151b5e22cbe70783289f91b1a428e544f054b6d23a2d69dd0b216d884432ffbeb49170df7b1869e7c1bb5effdd4d871093efb02cf048877402b6ca9ac8f51d7ca2efe4f6cd823b42b1b7073967f620fae9ee035410e3c37dfe472bfad0d1445db43595b76ec43346618bd1453dfa3d2063d0f647bbf9f869d79bf74956c5c9d96d00e571b20be9213599cb85dc2ab453f4a0f5ae416775630105026424bfc88806098126abfd518cf0ab1901b0c06925497b7fc7ce6753f28f5d5545a535b1b724286b13531b23347bb069f5feaad2fb4309a66e2a3a07f3f194d00fd98be183e563b75f0e0a0265054446941946a49f4fabb656d666aef8d8006e4d94920d8a24dc4f79aa14b73ce78ce79cafa123b9a30edbad94fd980a08a515720e4c716abc359a441d5627898b76139e1348e1e4991c4a5e1cd9526d1ca1399c4d637f325668712d5c33b1e81f1fa1a54872ff4fc4147b1a8e845437baba36815c1ea496edad776402f5219a295858307294ecc478b3dcb64bb8794318b991e89b90d9042779da3a1e0bb836a0a6d42bad71e534a3c2928426dd7d6f5ad718bb4431b938b7663e2e8984e7b5bc340030c7006a38b0fcdea219a67e25497e32dd8ef30310e55c843f1703df76d69b4f0d3c1cd7789191a7369ae1fedd88e7d034009af172433d05dbce1cb9c55a000ad1188379f43bad33b15b674115e45feb5b790d8644cb65777fe80b292aa0f64a97d244bbe61c1b0de72050a54329cf4e05e60364248408b774841834cf739dbc04d5031a8047168bdd43a4370edd8171c969e403cd024a1478e550525391fb2028802b180b7e765587c8a7b0287177ba8b2ad80dcbcec8e0774d1637940e38715dfd2abb6d71d326ac7b1d95341b2cbec17d807f12527a9ffda456ae1b93e47be1290baeef2b3840259a104c690fec1d064ee4027b0cd95e2d5bd9429f966c3d8b26279154f14eb1036c87b776a289c8396cc1029eb3a5ebe9363492525c70ba8992fb3c1d57c6477ffa03dbf9ec84c69b0c0f363d6363e610d4d39fc6c2100d85b110bcb61c8c2952640360b4b92d9e960fe536342b3c0b958502197e779bd6edc0a07f50fb6547d36a932fca4efd8244326368077d65d8b0a64e8cfa36bf5126970a4cf0c8c924e98572617bdb25932de74cc43a359e3158f4939b6ed21bf2e78dab4113d4fecf5d2383841a2cb35c59d30f37c58dfd9a1e7af5761e7aa7f2dfb235fc11b5f8865af9ba034571f33c1b9d6b3e2d2d99447751c112b8bcdefaf314d71327d3558c30802049f767ca5d0c5cb1027949e5f13cd7edbaa4e558fcef9afbe3aa43efacdaaa6b295b4eb3d973ecff25b080334ffec52aa8d97a2530b5f7fddd6c08de4be36d9ccaf86c5f4643a19003faedabfd7843cd7daf34d1f402d3da8304700e5fb062ac0ff4741dee9e0eec990c307bb44eeb172a84cca725156e88b30ae91b506c58e0d0afae1982eb4d5122afe272e78423a83d47a28b652df91ad875a90bb2bee1771ed3f23bf462ef3fc4146895cc883255bba586d266d2d8584be9f8e6ca54f4996f4df1c822897be02c8989594fd611415d22ab94af9ad18f5ce9f11b3c3bf1f52ddd8270be9143faae00f2d0deb909d838d339df20cad108a0790acdb26f5c70682ba841a0c7185cbf002fd5da4530e8c9a746fc0ac47e0127a64e9059fdb47a5c5aa9d55283a3cd8fdcf38b7c89b113d01be70cd13e7f113ce9d1dae25bd174aa98ff561e19a0e258537bbcf5ed48301d61baef10333abda8e3bc8f8691b45c91f12bb310304e4c892e4d8369c0c142b9fc874b47131f26b5997cf952bde86f5e2868ba0400e056d1cdd6e692fc533b6148d1915ca9de008ab97ec9a068ace97dfd081a0942d6f50bc639e54db8c1bb79f736322cfbe6c44902b74a36930545403fd74a883b7ac7d695858c09f015568f11c3d2248c44f91ea59be1ec6d4e1b009efba1c4498de0990801756e70638fc9942318680e0ca8fd3b66bdd8f0bd9c2fe3ab3ed6c4d34bad0be40137370bf02d0f85901ed687ae106da2afb619dd8f75930cfb1b9138ba4e4bbc5eb7cc73da6892e8d481c406f0dce25889296a1154af3c86ad656f5ad534b9d20dc727642e85667877b55ae7687eafcc72a929dd09d662f040a47cf3b95ea0782e17c51e08897a4799d46fc3c7047ab3df47750754153969e8e266095b0bb1c159655a95d40d6bd68ff955dec31012d76583c6d4e5d4126901ac11045af976945830923a4a074a33f2acb4c9a6f5b6c4921558af31e232c31d436bdabe82cd3f0a4ec0ddbcbe9beb697013d554701f33c031087167c43156775b8e4da66d3db73d70955300aad8b00fb1e088326de5b7bd3721724b29534a29280911091e09aabac3be5c17e7fc48f341a263f062dea76a1673f215424e7240cf06668ee23e3978b53a3630038b2354d9c202325aacc4195e9c784119461411841b4024146389979458480644f182b8aa903b604c8431484c431fbb070548653e4739fbe9743a9d86ecdb98fb79ff981da2a83073bbe423a9ee8f351d2a5d5dba94d2fde27d1e90ca94ddacc57eefbdecbd2d6ab852a8e17fdeb7bfad9b1a73f006731d6ee1c7ccfc38c893c10f98c4fc7bdd5aa2a238f4bbf7ada529bad2670ec257e70fa2ab1389b08497319e81605e8d40a7cd9e147d07f49dfa69c9c9af5e91b698f298d230156969e8db4fdae387ca0ffd55f92e3ebca18a172eaf19e6ca75f101f0b322ef33c736a21f42f39963ac83c79a8ac75e0e1e83498fc5ec068fd27870f0618d611863d8621816310cc3b00b85990e88c9638eb424c5a3364cce7877729ed1ee724dbd3b799c811a8f93b73b00021298e13a4d07a656a618fd4308a163de417f501dfa0b72f2f620b03b69303c0d022794b29fba0e011010f79daaaa02611bb550d5002619204396ba8ce0336ae46b9ce72e870143e0e1d99f9caab446134aaa991e6ab0060db20833141404060a8008a2c265872e2d0642d345c6c5cc4982b87e3084115c65082d668062f0040927b428a3e84808403f30f102841228b0ec0004dfa52fcffee454b1ab7c65e3156909084b45c801d358c454242e8a1e890b222e8652412de98aed906c35914f2f231f83fcf03f8e84d779afb9cb9461a189ec6b9932adcb757ff89bb8d0ba78a59e04074c7b07cad32c30046508cf07f969a096f4a7bd7031d72d74b90949d701ca109e574540dc1d40ff5a6feb6ce81e097fc5b0abacd308567be114fd6d445e78ab2bb82fc0675e665638d35c11feeb11b99c880ab0ff1d79e1fdfadb5ee0a05f8ec8e53ff2577140e8cbbe508af05fded0396d7e3b609743c72e6f88ba7c986586f2a79a2995a8ede672c9c1154cf54bdf0d561b4e9579b954f10a3a0eafb44d7140879d3f74d58d3270c9544fa918e5d7a6d29e13a7521cea282ddfa9f0d0f57472d475a55057dca463ae272eca2b42254c71544ac714aa74f5e56581a6f393afa603f3d306975c7c49ef9ded81b2a2473078b8f81884e8daace940a96ea84ea793a6e3e4a793bebeec3169b6d581aa107d66db2a74157d87a2ea35d754539a277f9ae4209230457c4ac7f86cc631ac47b7bc9a19e5ecf1e4d8d5af485a8e3e6a5712449ff9d5af4949f4f057c6add25eb94e27eff6cac94f27ce4707fcdad66d0f5e8f7c72a80f639bfed58d6532d324709d4e1c0d7b459b681082424df96ec990198d0a30dd98f58ee699b6a74d7f6df0a4c862e82f2a36c511579d838979e79283464ccc59932e939a20f90bf59ad4e4863fb9bc300c730cc3300cc374602e37b8e4240405a56d4243de9a711dbca59db6939cd9b485bd6c6b92b9a6f98cc665dcaa59d1593b714b3457bcf21d989cc1e6f039f9ce85d3693f3296058b8e299f30943fb951ca13e6acf1c0a45c7e4be44cc597b5f634d5744877b882ce4e38bc624d47eb8881e697b6e14099d23bc736cd4fdbe5518aa7618e61dbc9df0db010f3999f1c636d022d2cc250963e263fe356bc8a7eda70bc4f9b74d4b6d2d2f4faa72b5b8d87e39ca04c95bb4a7543625bd1772a4829e58bd811734e8650357995aa32f725ebe87b2f83ef71433b4e50ed0127fcf5af07d536ebbcd5bfcbb78cf94eb978eeebb5067d351dd7e2f00ac75da57aef82ee4d6677e36f703839bc62b92d099fcc2ef58cc3ef317cd76bd8a9a5cb057d5d572f0bd1d551abf34eba365e5d3700af0478f599ddd16bdb25b35da53ddd9eb626984bcc55db4669963e1c60305e9a0e94a603d374f46ad66f667de37505b5627d6d3360196bebd86eebe468761be4647db71978b551240e3067ae00500b5ba5796f66371a9ac51c105e4121a6be74b99c9174c69162d4894b498ee786bd4155c84157bc83768b8416227e78a83479787543c7f3ecdb5aa27cb7523cab58667077af2d1f7d852e5fb8d773e818dccbf7df15172e0655db1b36480693a68b97b05b5c3b5310f68bda034cd882553754704baa049e736b581fab216b5adbc1fde54f7bf4d7a594ac6209616f3293184ae371820f9fb4079ce010a6f057b70f250f55034218a3632d25d78d202707453540da32f4bac509ed15698b930cfa30314c8b318b61da35b1abbbae4dd3361b7e6125687e617e6d26a41cd3fcaa717f1b1b1b9b1a1b134c30c135bfb60ec85f29c75cab71ad4673cc35bf5cdb3a20af39b675401eabf14e7313b81bcd6d1ce3b41a27c16b2e0c7b50d4d8406163b3753f30c3fcaac1ae19ed72f513b6eee27ee067da8c0d28f8911b0e4ae650ba8cc6311a282e1a9a6bb31180000210000000e0c68d9b1b145038e10495ca04136cd828a184548a04126c6c6a6ae6a4a19999d1b42cc33014ea742aa104aea6c6bbab47e534f04b701368b82ebe8dcfb86a53c531c375f12f4f6d7fb99a50c2a68a23c575ef6d5c55a7c66d6cfcf20bc8773f30c80f7c1b5c917d12fcaaa9f1d66c780dd779cde529aef3bfba1a4e27f780cc777f46dbf41d080a355bbc276c78b6d928e10495d770a9d46759365397d7788d5f279c70829094d7a46cae948dd7b88d5f5f63c209a95409ef89d47ba2041b365426a4de133636b80ee86d78cd66534209291b1450d8945935a9140a426c04b1912ac1860d1bdbf4d66cf8e4bad4dbb051e329ae4ba55242504d485b9656082d9a5e91b478f29d0b18cd3e6f517a45da72f4067845d282caaf52609d159801d2d97924e370beba21c34ce85d77e3c6c302d387da50db7e165fb220f33d8f39f6b6b527dd86d732f6c775d869d3bc3ba09741962bb2edfb3eaae735ae43053d74242d90609cadc65c07f42cbda5afa68a636f9e4b3f71485a2cf9e79ab1a6396ac35cdb5ee87914c67da0a7933ba43ff7e9d574a01ce33a7eacd31ceb50fe23b3ad938e6d97abf60411d2f328ee033d8f72144704e53ff25571b49f5cb6772b177ad1fd9382ded027afba69b2432824fb1b2ad6e11484300710c29da92d3eb6f600131e72dd083e72cf1b461bad6f0bc94f66121295d0aab7f1f4a91a0f1d7b4f53b323ce4c3782879c5f11591f773f77acfbe97baa0998e946f0bc5d549b5180c1751ce79c15208cb088f832b8452033d0d0308857dd1fb70d5a2166350632c7b82350a4671a05b435e3be741b502decda4e2fb9ce87b6b0d52450f3cfa176bd60f3cf00589451467f7ae299c709f10a955d3b03b2fdbd5596effbf01afb74771930e4ad93ab1196ca8093abce40c3eeeec69b18170ba316cad8c95aeb3dd661e09d19c3fa0f19c668fa7520347084cacd40c3eeeeee6a2bdb0d8241d01d5a24ea0b442fb8ed2fa0af501d3c6b9527a24ec32537081414e4ab9c2b939ca4fbe908fdfc704367159955d3d643d53b3fcc62ff4126aa56aaf7021acd1e0badd0b3bf5943b3d87586768cec0fa98adef989f3696eb92dba4fefa878a50414e4d33bdabaa2ea9d1fde5c8a9bbc5205acc74d6e44f086d6565555c90355a1abaaaa425555857a3a22e88a2ea1943edaaddaadddddaaaa8a011105a638dd2e0b34af72a5642b41632407f1d036be7d87b4e77d6f12df5bc4f7aae0d787b815da6d837628531f5ef17cfbd31592aac39c0386e9a86cc6b59705fee15bfa6a3a88488f8ed95c3736dc0e7e9b9a3d5aeaacd5be4538f89e3a3448644c8932c5a6e6badc877d4c8e86ebe60ca7715ce7ab5feec475abef5429470f5e754fc829c92620bc228c115eab2984a7595ae62337437cb8520ae2f90d6a143b8a33f223c5ec548cced4c980fcb00e8f11d541f9ee0cac03395436e397c6659b90998da759288e065eb1f7e02e8e5b0efeeb9c345a8ce703fba87d7acc39678f1ced3e397aecfaf4c87256e0e996bacb50e21f17a723aeebbadac83c713e381878d5e30891e3227cbd234f93c0f3f702fcf7972fb7b30e0eeb4414ea0625d8bae2a82084509799795d99873e740821ec26b1e366c1317adce17e5d3af43fbe1ef3e3ebbd6bb517e03eed191d11ca7e2de4e974da54e7e3fe81b65a7253653df9ae0703b37a470f5ec5227039bd139e5f5783d11c9763e4b89ca352d6735796b278afa9ace7fa3c6e529fae74f5ba1fae9a1fb92146b28fef2337e4c8c3b70553397ccb6ff72dbf0b3691db0d293965ad6f8029ce748875732ffb88a1fd473b90f619da85b4d7f0d3ddcbdddd3ca74ae5be5a754e8e0fefd1ac4e323bbf02b994e3b4cfdee966028361ca174689f3300a1d84cea303399672a694157d5a81fc3a0f84cee3e33f66763c3c57f88de95658e115a90c2654898c7cac136560963bc47cc0d0351b9b6763dc3e0af3ec34d4f0e4d97f9ee862228159d2cba35816092e9e1d1e315f91c004fd116697031fd7e4f2d95cc6412f465230f8048faaaae6ac5c9fe8eed656a59794a598f931f39583939a99aed0420e4f96ba314611475af3e0134f68115ce0258d1da400892108b13551850f48c06c31451a2b84226264de272f80315830328872658c22f6b94fc41823c91534d448c153c3076c0b267d5468c831be90085003aa810228a50a069f1e181a62a8311130060e6588f1c5698b7792f38f52eae504d35cee628ceff52a5fc1a99965df65057845128308317a78020d662632880046d312137b5404a6e28bbd50dd0d1198994f582089b1c3cce19959a65250314f317060a42f64be5b3d30b0cc2e2b50f12a3539788339e76a31b157a42e44ff78f5aea44bd02b52d39227c02b52d393efc13acb8457ab380b638af96dd0751b62c33f7f43f69fc375e51f66b746eba46fac91d11a298e751fdeb7d401dd69818186b2f4d767d09e7defb1ad1fd468dac18c6f81514659fa9d8af19a88f8f7cf33ed511991e389f9e73247df609d9fb7ba51d4a58abc670ff2dca78623b37529e1d7ed638df6a88b96df45b2f257084ab748b4f86b1382d2593c6f3fef21bca1ba826697939363858c75c0f822bf391f2366d7c2afb790e36a6a45c9c8c0de86bded05ec6dd8db5ec0de86bded0584dd673c2ec256737296d9871237d459d7c781f19cfded1ee13db80a5fab2a9f66fe31c3207c30dbd753d8dd1e3ac2c971f73566fbc5a9aaaa5ac1ce52e2a870b4e7fdf3550abc71ddaa8567e408cf0a2b64cc7a3e31e6325ebd15cc4e3ece8a59cfe512d39bd5fde476dada5127d4e9ad78f57672dcb4f585702bf0f0ce93cc7c44034c972e954aafa4a07a1e1e9e6765b6773ccf17a757f499596f8ac92c250fa64f0bbc7acea35778fea95431f480c1077b4b4160ea53ccf8cf53fe1c877558677bebf75d3db8ffecace9d8521b0655f505e13591d712b3e3f937c5ecb2f899e2781ea1985df6ea06d09e22f3d99fb39405f6e72bed81fe3c851cde91d17bf3c9c1bdcc331d0faf5e0b5b8e1ecc7a0e37f858cc76e6619d1c30dc70de91dc6396fa63874c4c7febd96a3af6c62b921731f87d766666d6301eeb3ca4d9a996025ce9a14c5a1ec98b2b332c0bd0d93db02cc41374f5b72c48578fbb9339e6ddcc7636d63b00702b293bf9267cb77a13769ce03ba0261fe84204649ebf2b620366e6dd5d4ec04c07e4e464e5174b0f7ea700f343f40a18581ee5fc3c9ac9436d8786e9b243bfa0f2eb5c202184237c6103e5f244b3502ba2501ef440b3d8420846f86d0d02403510c202b858400a66ba17729084f2c00a15ac5885f975ae0b0128b12a03051458514384005ea055826ef89f2bf517d781208b9439335d0d52d40ca123845f1ed8975f8fda0e09842b5ea8994e044a3c2e22ec8842fc6a3ca0113b7a8a03a2925f07c81ffcd63fa4bca49497bc2e29afeb92525e72ad98e9de010dc5afb3e6009aa35f97da0b3dbf8e9de0b6aac2c9e5d6aac2c957bd03957572bee142097f62edaf2050065eeda33820d2cb841c11ff935ffeaaf138c00fd1326bfc76dc9f5c3a767abc659e4891f828171ec5ade2b8fc65d001402fe8d16f6b2ff04b0e880cbcfac12b9e418cd909e11eace32387575b0a2b30279e6901b6ab36453c5f91bc186a4c8d07066a44a0c91263bc2412d313d52b1213954fc9243ec5ab45622a7a5d15d4c53c35ae9031f288ab97ae9bfe16ead77505514f7d90cb23508c58f44eb9ce86e79a6011e5ba75475e3b220de1093776e423d769ea31a05610b5509e7ad59e142e6f9694337bbbbb45d74ec8aa8190dd0a65fecbd9defcf7fa79ea5d3142f8ccf822e8a333ffb6d1fc82e85b46efb6dbb66dee8697b9cead52cdb9dcccfc63d41e227af445f8872c99672245e443ff89cf1f37fe1e7ebe901ba265ca3ce3c01d4c1d489eac0afadd7e3f8d31c608e1855df1f278c50821112e26f60a185c68f4d590ef41850a55dff30861e40197a3c0de2fc71acc2efb55f67d5631e7cccbbb459061a85ee7bb92cb54bf505552b68af90b966926c4eca6770fb7ecb1859bfcdefa7593dfdabcca53ec0af985485385a47e1f845048ea95016333ece870f9bd77cd8710420cc21cbc8adc7b2fee3bc2022e65458559ba861566e9773c4865b87c271fa98c131d3e0cb10eaa464712238c9958797d1d489cb08e5af9f2e4f5db7dbab71dfb3a7c10e2950e1f8458e7a67dbf393e4222032686116b7041785dd745c41284ef4108e1d3d7c42b7dfae5bb1ff8c583906989bf78909bc0d8852be5a3bc72dd4fc318211398724d685ca9489593d9a956f8e73ebd9365abc77537febd48e5a3ebdb899a8e48e5a147a7f107a73fdd7230ebc51eac93b94f0ecce1c9318dbbe9a1af390f7d8dbb99f1209a5f3ec35d2e1de8e3d665ddce6cddfbe81a966d408f6d42b247497949b33d66610cb160ff79efbdf79eabde1182fea1fea150f04979bdf8e07b6f677a1598e274d0e89f630fcc62bdab3deaa3389e63dda330b24786528bce7513afded16a8a47513931fa57d4ac57f41dd42c2ea6fe7a3759090c5fa13b3ccd7a7e94c59521fc53ee727214ca15c3a2cb2b4657d7b80fcc0b93e130d559a977de52b39e3371137f6130ac83f2e72c86c744df23d68962e5b94c21051c9c6ea37060fe418e978a30a1f7c662989b361ed3ace7a88dc1f0eaf969e32fccb4c45dcf31189d9785c8b1d232a13d90632c14c7f3c74b50e6b2d2b2c037fcfba13866b39ec72666bb2e0b3641af3e9b64eec36f4aefb052679c4ff9e74fe967bc7b57fe266adb4d8c71cb7cbf49c61db161e6b5d27306f3967e288ee7aae799bf30282ce3364ab39e87793b0522f1ef229aecacc4ab072167612d526614d6d19b8c955ee9396b79ce4bcfb9094a4e3dc9a4aa6613ae31b9058e8757eb9dc554ef56602e829966c426725e6f6436c65ce9ea54f96a0b754c5bda827ba47d9d683f1e7b346a96fa4f95afe0c6d32cdeb4b59bb694a7f0913a5f5187634c7f755775ff2872aad49d95961b08987260aeeec014a7bbb05d67309e4f5aaad034a72ccca05fa2f99e32df51b3d8a59c53a5824b580766e1d503b32cb0431e7407565138a43d8f83478aa308ba0365a02cf69c7f4d2cd60b430209d0e83575ebe2bc9853a6f44e5968d4ac87c5dc29900bc885f6a87e5050efa8360add6977555050178386aa10e9a08bae70f2ecb189b90e9d680f0c94051633b78bb947cf0e8dae98dd4e99a23dda438a83b983dc55aae864c628cf45e40fb9ca6c73fd6476ed8407eadb4871b491ee539596f661f21273e195432ff386375b6262cc7379590b3b8361676d594bb398dfd1d1d3f2fc8e6edaf7890f9418d3da273c80819356dc6e9af3a13ea0a2c503440bebad676e62e8d6e5fb4d2eee880db1530dd16276dae4c5ec74494bef28162ac0086a31f683163b8f5916f89566a74d4014077b511cac12e23bec3bec3b550cdd8abedb901dd8b7eb68122f6ea7348bdd8bf67cb7539a96853d5a15d8d92f206617cb3c7b6a893625ba826a9ec0f4574031a69023c47cc5815a474c25cc510bd55262386128cf3ce3805a1105bcb4ce5f58475f4e1a4cd469f32a66a78abdf1407a82ccabc643f5ccc4abe67809a231fd797c7af8e827e60ac17c5407fa935c2724f597b78095313b9ee7e17c32262e8f575c35efe891e44b8c339c9c6134af0b42082fcc48caebbaae6e9af1c50c268757ae244aaf56ca80f2aae9809c518e115391cc78f2dd0e0d098919b3a3ad339afe0d9db184a488b52c8aa9ae1cfcb7cdc79ec643dbc1bafa88f2ec57e381ca8aa672ddd55d7ea5b665c2ab1e621de6d609af56723798b1c9bc7c30d9e393c95c0b3598585c626ad7dc400859b1207a826487c68ca05f5f2a3268adaf15d65124348cf0eb7b65b158f9f5d5022f1e4242030cd1182abfc4c49e4785674cdfdd290b6394a9494a9abe877298447ee66f0f8a635da66145a77784a0c0df4d83cad098313baf3506ccaf2f13d6d9b5a168fe0ed16076cbed97c3ad065ea90d450fb1e8d712290e0cbbe236f14a957761a05ae2378ad5eeee122d125e010dfda2106730b589683a3a99fc3731b43f7f7dc4553267a5f2e159bd9b18b64703b5786e76681533985eab98a1f4ec3716781707a459ad1b0bf40920f7a3592d759da9a962069667ee87bf86f8ea8f6b35776381534bfabbfc41ae87b41f973750ebc47590367d0d469a15b45751b3b4d534346bd79ba88bdaa89d7414e60b07f6ad22863ec20ec4d3dc8dbab6dab5a553a5f2554eb3568c7eddfebabbbbbbbbbbbbbb5b1e59574ec6c01d51871c50eb39f6de902dc3dddddddddddddddd2da7ca8d5280690b1661baaaae15e525457f05313bf973648516c9b6e4949053655e6666666666e69172aa7c05492960f23ffcec399d1379eb7f9be4e75e61b3d40d32620ed3d755175897eb735d85c599dde554ed00531c76ff65397547b5b0c1981e80f08312455180924012a3458a184dcac0c1b7882d93c1ec52377ee0bf95f6f02ff117cc4decc3189f54cde28db57387eed3ae6a54fc81713b6276da8387dd2d71d0a2469076aaaa6fdfbb349ee249d5a154bf9e3e9452218c2eb7e7d7061b2af9c1f4aa60f1a51392f9b091df1899628c4d30c218e18f2e6064628afcc35196e2550c6697eab2f752a96cfe605a1fd6eb3a752914ca7db84bfd721dcadfe5aade610ea652170b6697752ebca76bcc1db04e64c1ec5ef6e21193bd475f377cdd31baee53c2dea6aa1ae3c0c4845446962160dd567c8a575784af39436185a6220d61c977d93b698ff2e007aa6184a6eeeeee6e1962529e0347840d681c11041920c8d001870d93c587237c49238d2d5e5421573aab8f0e6406d659b5a2a1845495ee68ebbd552fd0db4e0265e018638c31c618a3037230a2228d15b4a00c325a1b88419517a0618496222eadc8f4d1cac70883c4e8610483a4179411e2075d50e9e18928987cb10416672c11a480fce2a5394a05135a4bf1e2a52e74c08b255198be28c3840468055d960883c5920e4d4eb60b2830ca2cc97cf9afdcabe252b915638ea0c419517c09a306100e375029430652948c386387a0c4755d314028c59c1ac30fd8fa01ba0cbc239d06de51fe813da04b15021efa2a073a0febf8b00ef41cdc6b010fc5da1bc330ae25d7c98beb268429de9937fc493fede1ff95dcd990edeb29ff3835e2116176ef65bf6e20eb409fc3ec24aeeb5e2a8e75047c6f629efc0fff102c63dc7849785a79c5852de18e1e026e14d834d9d7a888995d30c9c0dd9e131d61bc21c6e81162dd375c8133952dc44cf0faaf3d02fa1d0cd3d783ae7e3ded775d9777af566017bf0e75ec6d99d4f178d5ab8f57da33d17dd8a16ecf444e9f8b70f91c436dd96b9477ef51cd350a750a8a635d915eab7ce46eda81beb9ae7b7b7bb57be671373174ebe4fb4d6495eb07c53c866e61dc111b5a9863d70f735b6644afcee5bb4c834e38e90e8f6916fbf39b07c420aedfed51a3fc4da1c5775ca45180335f2d8639c486a06f8eb25714073b17f54eeb40595d545454b49445c76390bedba3a3a7c31e35eb0aaf160bb3d8b938f9cd62b403fe1e0d053d6cc9ad09e6d9b64bcdda2aca62d741505f31a4e58767c7b86d3053715c91351e7af4b03bf243f6e8b9b51a8ff743768d878eea7e906d78107244e43b0221944fcaab3bcaa908dacae99d76e9cad363cefb48f9b12d2865aeef172f382d73ce0c30269bc382c86473fc864ae2c0e0d38355be4a0aa2073f5f938290e2bb1c35aae0a420be4812c2054941007134037618fde2e46cb10e1fda504c23bd73727839f409e80e4b59d04f781fda73824ac1cc213c643df44eb378e839ba23792207957b685c35340b9e361a80340bfa89fbd12ce8402d201fcd821796e98a83c8a638d637d92cd469c36e62c026c6298a53c9d92c5695aed132843345c2dbde3a75f8546210c285efbdc7b9005423548e3080cf1b4265b15fced7ec33a0c61823848f45185ff3c2196d7292d28a2b5b9292484125099531267c4d4a02c597c9af49499cb8543a6796d3f12461179292c02029c9d067af4948c8bc1214d80bc2061d65ecd357ef753cdaa9d2c6e03bd5a29d769a04fb7427cfeef3e4939438f11c245b3057af4941a4f136af494180f11d8e1106bd20e2091153d8ca6bd210639e791d689708e6ea9d776f9d10c95bb741446386c06c50d39825a5232d5796943c67c593d3f17c438f2e3c6612942953460b7f60f5cd0559e72956698d76ca4ed9283b65a3ac9475b2466bb44ce40cd0a36c48f04e45c2a6cd9ad122d74121e6cd03b4eff66fa2037de47e55a7c6f9348b0bd317314bc50a962c4c9e48795c98be888154ac60c9c2e48914c985e98b9813152b58b230792225c31c9b3333bb5df9b7053979f61dff89eba0b79492f3dea9712985acfe8580e6a3d76cde3b5c98be88592a56b064e12f4b983c91d264be087d41f245c9e3c2f4450ca462054b9618c612264fa45c64c2100a0349184a2417a62f624e54ac60c982f2b284c9132918192f425e9078519275e1d285a9cb972e62b4cca795b4d064333333ed331fb90ed2300965c0f09116252d4a5ab42865e12b7cc4476c05cc3655e91f2b0f14ff701de6b388b9df2d0425934f3af4cc31f8a4ef93ae9fc94ccacda4bfc032fbf9cc9f76d2951682cffc6933d29fe488cc484765fe8af0cfcc705271c8d3cb11d06852e33a239fbd203f6b7fd416a4fd3d4fedd0c89de9a70d8759d2313ebabc69467fdeed329281b1ce7b88613929e0f80bf29c3fcb122c4d0fc7e4601643e83eb63bc18e1fbce2ad391978f5727777975b23bfbebbcc33dd09fe713c3e76f8e0d5ae472a783708c2ece419eb641caf9ec65be01d1e9acfd336dbdcb45934dbcca6cdd2b656a50dba548cf28ee87088b99f79f732e7a1d646498e3776425429eb268a879e25c741637e157a0b7c3974082184101e81619d77714254e9710b66dd0863c9057b123694323434a4830e3af4d0430f489020a1d1276143d891b7b9ecb0c30e3c44f878881036ecc8438c909f60f0bd6667f8de763005b3083fd884cc5f1e23731912f1b47517274ff28ad12fae63324599f6a0bccb32ef321aef32cc6d9c667b9f6df2515bf6368e72cc668b8eb241d9d4d4d4cc39b56dc66936f9d9963d6a9bafa94ee6ec3328c730c7b42d3a86f5895bbf76acc36b834252dffe382a813db8edd1b9617408bbf93584dcce0d1f723773ccdd03ec3d9f8650da20a907a29753caae5d7292875a09cac100537af4ab25d6c913732ff07327fdc4c95dd7f68e2513ac01394d442f0b2184f02d61526633c6089b71a08ac8be4ed8673f272d04f234026944f2803d14b8c06838c88b5bc6c47c987cd90ce673fd18dfebf8de7b32b663efc177043e6882183509bc87106edcc1de412e083b76b46139beb7ba7a0eff71e4a8f05d2fc068de75f973ecdade0c263b3bc68c2396e0691e3311095585e73faa7f2e8db426810775f5fc2481f7523ec9c16702b8479efdedd041341145e00f6c2545e004cc00a2882459084d416600442c31264c109c8c49028c0c9ac6162d787085135c7860869671a2c8092114f1200a17325465509f5936a0aa1ab8118421149103297c5832c6c98c1978616a22093292c69001e88828863842891884499201096058c1410f5c04799981180418230544528c41920415404f888e80628a951438c1e58b06948ca4187cd1c3971e78589a61e98b279c20cad2258c3034a0ba011732c028ac818415445de800060fad1b4330d18352103c00518617193628537441d484161cdcb05d8abc60290b1914e080430facb006a8d0dd1c24a5c05701ec9005efbb0b51172274a5df5dbfbe4decb17577155252cab36738dbd3f72159ad5d47a6dccc78c5ef090f42cf7ea3b96cbb9183d9654966a75a2325b33c7b96b34c94f3ab1573d102ef6cd2d006f1d2b3ccc199b089d9e5e4ace0384b66ec3329e079b71ae2202bb3631e3a3d0ff50b621d21eec6c7f3ed4f7ff1a05f92a06ec7ec98260309ef20d46058a8caece45f55cc05c32b299b7521a1da9c83e79a82eb40e6d971f4e8c6c773f537836f3e78c5ae0b66881c177054579efdc66664c82d87c5d2ff75b7bb0a3a10b1a6a3b3959d738ef3deb614274a41419620f0d00fd160675947935060f4eccce472c5e95e94674fa13b875b61eb9e0d5de186a3ce4cd809d28c4eccee0dbda1ded93494c57e03110f45cfac02a31f93343b99bd28118b922544444818296162a36c231253e5ab647dbbf8dd4f7fbb90ec9b4b3d21cc8e8998a8777688b928cb6176efdf66b4051382f272babbfb12622ec7b302afd8230bae2db393ee9b24148505b3731daf39b5647639392b1cb940e32e84103ef9638415c0de59303183b88b56eab6094ab1b32d4dca01c9b37796e78ad3c1a3cec22cf62ba96930dff5c3ec78ccb37311af9895f08a994871f07bfeb6a959dc4acdf2617652e9d983e0d197143b27e1a0e5d9a702d8b37f00f61dce73cf9f4141413eb480be3bc78925ea322543377c412204e529125e11b102d8a30eb3cb7a87be7ca7629d1e7ad5e907b3bddb21fd5def23302128b06bef6d7d688778b53263761bd48d0449d20d529e7d855883eb6476a9d8c3153b10e3d9533a3cfbe90161761bb44d7486d084d2decd3fbcda5555fd3172641e5926a5e75d0821ecc7751b64e4c80fafd8999e99b689775c660ba6070ee220fea12929a8685b11caec5688cce330d939e8fdc695c09eb7d650c22c5d3299a89e04a630a8842df7643deb14d10c000000a314000020100c8744429160302854c67d14800c8ba0406e4e988ad3288b510c21638c21841820000002003233a45515386bdbd7e022303e8890492f8dd7465338dd39777c286a90474699043cd69a5b2da4addd02a635e971a33c292862fd4a2689d5f743290a89316def58e00fee32058058fe7b598e7119c68c175cc461d2e5591e308694b29ce59e4fb8d1f18bae33a1232d95bfae3c7433ac552d2a0e9ecc12b3c0dd03b00be3a0ce8293c8f1a19cd7a448953aeb0bf9786a2662dd30d4a287c875b26959a0d6b6a58ba3c5a9c6617021d9aa1a8c2361028de9cafb4b8126d184fb5d6606e85a7fca16ebb68dc57aeec05c7846ce7c70d9d0febc2acacc2749d1e1471cea42768ff5b5def5f65b093f4e0bf4aff6b91bd9f713ba9882157f726444682c1ca9d097bf8817042ea34dda67d2758cb15b502d5d0457d3d783cc31e48e8bd1b02274743159e2430592d40c6159beb1286de1379030c0ea3921a7d69ffce4914b0b09689c055b300aec3e214675b44d4bfd8c12152ff15090637b042750d16b6015d68c0f6447a7b38cac9e6f0966049d1af179596b94bcfc30dfdfdcf7953d82211ea693682e29297a37c3a22b68f7a647abc89515015dbc4a32dfcad66ede57767ba1001a9142a0c5c404a65630f57bb08bed56f77d4a08f13265fa3f5c8e8622877e0c110cf48ef91802cfc1b09bfc381ec1ebf9726ea7115643635ff7a8ec00c107e54cd5e906d2072836c502b791cfc72b670bc22fd28e2acbe993049c82f137aedda7f355f9ee748823e636df28c41ddbadf8d512cbd038c13bedace8fd132388a0ee9a04e055166d08f3f611dda1a11e0d3630ab00d12f7a78526e21a9cdef52e9ebb25d321230a57b26c44482f9012156e3d101faaf9415d4990b3f535e85a0d2ddc1b09f3c73024bbd69bc1a298fee7807fea9b8591c56d5628d98cf1114fdb644021a25838dbd66283a86e563931da0b97a47c2c8f99f5e4d198da3fcd90e1e34b21c7eff45ef92cccc946aa5913cffe1948e07e76c72f29a746bdbbb1f8759a86a312242f6d6825410e7082242064124331a1dc32d1061a1c2d436b4885a1b640f016eb04e0008378b445938a26f1c82520d9f46fd269277550e42fb27b627d5babeef2e9f724999c08cd23ea5d62b1497d2c3117759d6b55ed7dd1ea1a53189d929c6d28592b3478712e4527bc349b8005b138f6036e52338f6b42433b7ef8b479f8084414f8b3e32ae394c58fb9a680b986414e50d2e47342a55fd22162d003d892c258b854af31fe626815b622c2aff4b61f744b8bbda024430f663080ebf627ef36c9ae4f2274ae81e00c1c972b6b2bf8110691937a015cec2db140a981c16ab050145f07ff530e41ec162c53c1bc5c21a9427d14a9ccedc24aeee9c37a7efa0fc44d1dc181c96e4151c89407691e09608e8982f174711db1215eef07d532a8b0e589927220d2a0ae07fa604608b4c89d10490109bb0c2c8d6a9c77dd1a0a2faced0dc466d1862f7bb292a72a2f5fb05c5d07aca85cf5cf1c9ac42a1cc93dc17784879636bbcbb1a6a4dbc5f796d503ec6e532a511fc4f6b30be20e654701e8a0f875969f06877a1591222760ecc187c622f6987847617ec52cf66e2723a26191e84cacb2f640ee2e8d9d44789da0036c724bf3670fe743a66888288c8e3c3eaab66cb5e0904ac3b7de8a2d232ebea2a6221ccd5b18a30d3d2eabd3662e6adef3bfafb844cae7cb35e53bb626affc5e03e69e9adfc698447ef8603a79189e3eda4956465c6ffa176ac8c026ccffb4f467e530d70ca86cfb8caa091c2dfae5a655a55f9a971b43596c0a4cad004342543953d5cdac8792afb16a599440cdbf2a805e4c9922c4f062e9c360d36f5066f9d725eecfc5354f5949cea96087079da12a8339008e72b0871268345ea4715280f16936f5554d5a2425916f85075c1788c30b2df8eb41a72b649d92d13dc5c020c45781eef8dfdc928525791ab1bf84c050e5f064abb9ed9d9e5cc0c71b8eb21d4dcfbddb31c5fd76caaf5c4efb4370839d5400f87c7b40129fa65ae4d7a895fc6e265672d06615fbe2d3aa833e95718e98c09d57be742b78984817e902eb791931dd6d2fe1adb8c225d4a524196f9de776c125b286966e58db429ffc2e841f70725ebef85ac6e4f16b28c3bbcd2a1751fbc443902a8db2d98d3ba1d2bb76ecc3cb1813ed57a33d0ae2666c4e05ff28b28516f54714e439c4eb0de0c6fd155c6b3821273baed633c7a2a728ab94e579a590492a37464241a04719565d87c85f6b81433a72b507f42fb95a85fc717e607afae301249a9afdeee7bfcbd4acc0579c66e60dd3cccb1ebe80e77c3630d5b2400eb18569da7bdd8c32a21dc0cf0230021a8f81b7da3f075aa498ab448581386fcf9f13761ed9c620eafa790304aca23888a05ac7dceefb3984aa5e8a459663fa1fe3e5f5fb34116350e656cca7ee5172d0a27f1bfa530de3ee690125e63f3202db2093a97104ad6a689306c217a28a3c85757f68f8c516c562dc2d2c291e706121c8ca3f16dc1b288b7def84e5e7e99ebe68589a922070f0a02772ad9f4f6fd58649e0602e859231bc653605c5a9b3584156a349be3a8499cef5b5d82c390ce859fab5e86f2dfe31e9f88568fcfbee5d0d375162aa9afea48e329ba5eaa6e5731d9cef6d68ad86b5186a15c5e5f6329652f1056d7932ef82ad668c82738d964378e6e1d95ccdc25610eef1d7714799e91712974f6d00d06d79d4966f8253bf71b8d3378335707f6d793f42f86a975bc19622af62e7d06d961c08f4a85cc30a6f56fcd6ca44a9281e36809a2e20235a7bc486035dfa1c464d08ee7183266f29fc5dfdf758c9f8b240b2934a2b3aab782bd16ffc6570b5755ce9a64d8f4064f4927fa3696ae6be51d988f70d725a38ef696cde40147e18aad6b015fac82f80de3687ba13b87460a13107b2d4bb7d7ed8ca3cbab52b22fd7a46574cb408b2be04ded44eaf53cc4468c9923a495b284291af4639f1c4aa156ed29e083cfca36eecae130a1b310000e57261d4ec6c7a24da2018a2e75d379473cfe941e2fcaa450d0584493b46c581d19a0416af979ffdea31a587252e777c1ea05c70493505568b27cdf96c3e5fc11c0cdb3cac002d65529335cf2cc376b6a2ce3bab418ecb392c1e67e1c2694210db2a20936986efc1a9455c2c514fa668eb44b2499d1b898e074f223c92e199b07e918eb60050ad9d18896451fbe2fb9e66ed302de0863b13911430d443a2018b0f779678a2d3f7e474f5d2fbb8a9361bbb72da648c2aa2f8f8ef84d13ac2dee718549cd1d66f8e340f099a8f115adf07100ab8b791aa50c28103b7e8864aa0293aa6d435ff0873ed7a8abfef81c4dac1a903b8c380e90d0c70d5d22ca2eef9bce21027b73b8c0dc707dd532cc9886018d7dd1b89d819df90b2ed99b53e61541fba1659adaba5d11abe4203610d7fa3d2515399849866c4198ac5c30755f7cb730b7d890d58e1c2aa1c6e00b59955d4ab23acf6256ab67e168318ca419dac6aec51cfbc0b609b02cec6c8d6ee49b4636a173257890ea8d559ac5001a23c09e76eb7bed42f7007a9cf4072c2343cf9463c7023069ddacd0110c59ffcf1c4498a4dc4ac96c2c9961f274fca34f77e45f1c2dfff3ecfe0c2f7562a0ab9231071fd07e5b31bc0393c47fe27622737502a9a2180b64abdd49f2aeb3d0ae75c791b72367a62aa90864a74aa2496ed6c2fd995197a774b8b701b9600f71a99ef00c02174fdf3690f01ff7a13b5f9a47e743de20e2cbb65334734bba215803cdf8e6d301bb6d08c4992e32976882fedaad383ee978778ff0689c2b3a8434479c8256e16e38ad7438770060e75f587dcf787ca21fbbe0a0fc1c00b8cb1303843c3102b01a516c77fcc3f247000440b61f7ca4441640ae6b57dfd48361f19a72039f5914cf13ddbc60dd46fdfa855e91da4b791a2ce8e5873dff361e5c3086c96166d4ee4577f32200efbf55b3db2f7a6502b62eda2ce33770855fece094f03a36009fb088b1e4eedbb438a1652ef5c5a3b486840c4bffdd1ebac362fe628db181a5bf767f4a11210da0fb4f6cdf835f399f14804e41febac07e5008444d2f28e0c4aecf7e6bbc41741b4259b8766f42e8feb186b1f39b8f278aabce3604fe4be18bacf63279709fec83aadad45d4008260ba194e987b7b18daa983e8180c06e4626f6a36422563a8d14f30ea89fc3865b30ca69906a1682c3bb62354471ee2d0db8d51a76d62eec87331cd83cdaacf24a33fe1202d4db6c48e92ef6be01cce34ab3ca217641f935b34e476a82a036f5b74bf43e47ccef9e895a8b7fb45a695ae5da6908d3c4cff292910c189b7d044bc6f3e7de0ab851b7c0fc836b785e699bf5c2fa1c2d6247477ceef1deab8ebff9d01240cc3b2cd7df499d7bc277d7f3fd302518cb996ca1c2704f9ec2a98b5768fe68ae7f45af268ad07a5d4d31495e421eb5d3248e52f5c9e8a8e58a57c973de46fe311f2bdf4b89f60f8f1e14d176873e4b216b0e8fdebee35d1af4dbeb3c55d39adf4c9dd1433bc4680e4d5d626819b6c2133b93d102065d389c71b756be21f260c2fd9c9d6375c8ca1cc36a1924421bebddf2d46d4cbd5c686e4c28587d72ea666b7083f3dd350f51c20c937652635926c4fd5eda10008e061cafdf850e2fa2d3dc6f16644eb28d62df312b083ed30be0da1f819d499861516ddf38de11459160015035d33d465c0865d4109088addfdc3038da414fdd1802c34005e4e671d2ed23b90a42c949b93389ed8ecf16a48fc42d30371329599febec15374d76f298cf600d5ba3acc9a52aef5599fa2906b5e4fc5e624ca4a951ef4b33e683756575d05d143a4f6d888182b222efc0dcfc76cb31a588bab4b4239b049028b27057c9878b8bc16361268479036a91081503312eda4b95614b1066e8ae42c91eb4c79139de33999f0e00cfedfab36e384326416bead6c6a37ec00dc1680342b792ef47f993e8251d2bf727e5c351dbed3fb9ed587476bb1675da2ab9c0743c690f1f602613b2699ded61b8645712db4f7b02a09f6fd7b8f1452f2468de03a46773469e7d2c6e3d0320ae8381e084fc76236d926d8f0d80e632d1f7c0d54eb9380c05521ce188c5a93c85061412ee74c81bb785a7f987b09757405b031313bc64a06eac8424c46ec3d1d0e11c6bb90d42eb3c5459b909ea36011b78b2b008822fb0999d375f0a87b682282ddfb96e2db53f2010b9b5a245d7f43f4be1cff60e0d9848d6a9b5ff6555ae55517454a911d6e1cc0bebc9ebea267c8723310b1fca9a7ef0e31a1597c661a2d25c2f7ea3c4ab32aeb57c0e39c60c1c2483a73a7e09a878887d8adc925002a35518c68db33ea8cfea1d715d40748b8ca41209e8191684c338f7a5403b88d5502076d691307f82910a666a5055936c81c2f74d92f1b4e60f3951fe5a2f2c043281c6202ec8352f238d5162274ff38e63e12a818cd5fd3a909cabc2c6978f966eb885ae435166c84261f44fbecb83eedb2b3ec02a19dc766bc9a726cb77a859b36d11ecc2162ca184f4eadfe6d18570451ef601f6ccd852f833657f458700d683dbb4594167127c75f7eda948bea78e346750b17f64f2e378ff6114da845f181ec8af084ab3fbdea4879d5bc90f40dd0b4f416ac071cbf0c98b8465dfe07958d5627e46c7c7145124b7e74da47447403dca68e9190838599a3046027ee5b50af22e9ce6427b7905a49b640f87021fd5adcb42babafd44f6282de6385fc6bb0333e592da11056bfbc44e58e192d40b132f4ced019f216181cb0ac897e35f55483a5af4d2e751882a57012447b217c6bd1cba043cb78ec29372706bde851bc5e0bdc940d7fd7014ac22adad8be15da27c9c7a5cc7da1c710512060aa9fec66ce63a2ad6d2b6895bb1ef1ac075539930feaa6f748aa7cee7513f32b434dae332b6b6868e64f1410b2630586e65e909e141089f0e9e79730e85f6581126c20de961379efbedcefc1cd7290eeb16c979262b56b8632215d7413010d0e700900677fa4d8889d60fe2c6bb227e1e9a0ea4eb31c29ff4d2861202434108295f075f0849fe0474548df6680b215469b63a4c58ec4d0327dc4f08e17bc97a4554949a8913decc8bfafdabcc990ee9325586c3e19b419c388dba9e97c811c384a794566005807398717259bc17c86616d2291c1f56f45099ebb46266acd72c9733dc3e195a3986d9631b89f2844bcd30f184182f98d5b77baa1c64843ff308b57642f28656e535b1869529684ef7027944bcd0183307130b1ca0b3568ada3fe46182fb702451893889fc7a46dcef212375d6b20c1e91bb4e14cef7c6c7d331b4cf2674fe452a7457d7d19c2305c25cc706e2d2c3558d42fac7f4a97238944c447591fb0324223fcb0a0d40ea4ca56eb27370e32507a7ba8cd2dec4faa067e4279e9215a0469ef05f1b0ece2d4af4fbc249e8d9fd64f08d18a25f94dc91d70233bcb5ab8932b35fe1060e9d7180df8cdfd84c40430b3f69875a25aa26269fd0b61296c4e825221814411035579e9cd78f3725e31a35ffb600eac38d7f2a2b4f1e05794e2860981a5c00a8d8922e92244598dd99144c2e373ce0273bdf1e24a0ec6a1ae4dd1271e24d87bbffcd3aad29bc0c7c252ee1e20a73a50c86fec87d8f280664f518a62e31980962a383ccc8541d631ad0024ade40eec455630d3fafd9712adc03acc15d6938df72a9f956278cdd8f30f25ebc748f16c83c53201fa1bc8c0d55ba2e6b35a930c34cdab8adcdfc9bc037d96b441da0d738f144b219fe9e2ce7e7bc4e057431d0d1f39196f14c7b0980042e4241323e3e64f108a3f27bb2428263a03f7603a56917213a85c58056f5ba5cf7273b3f6920a395617573014e7c053a61471594914b971943fb67fe8a47f81bd43096e6d4c1a49f7d6fd625165afaba67bb1b0abd02ceda86c3499f0db5d83a9c4e79cd1dd7794691e6b10ccbbe2bcfc326adc9d23205a0e936b77497111f119cc602c21a12656779f196a6c00ff4fe4965f97925ebe76f72a7fa332e28ac6b4bfd09c8247a5e6584a6ffdf3ef0b05659b61aae6dad8b87c35d409b1468c03fc9adc2b8890628d6e19ea0296af24b7a5992dd4b1766de847e2b0c87156f5dc44301889ecd7e530acbf5d9140d7f778c675a421f54771db2b21037b04de9ec8312fa1c743ae2a417802c55834d0080dcd70a3ecd45ddcb77dc8e90b027117315599d00984293b32b58d9b84e438f313f96710dff1e70da3e8020899d7e347d3fa2b5967b5051c69a678674dd2c2f0d0d58a54c5d4c9f690a8b7504ba0d44a7ca4ae8a411bc49561308ab71775c2ff00e17355d28f8284699a75ed4429614cb53e20c11bb8a9d5e2d8c7032fbb4de0ab923becb44a487db99e4bd1dbb89d52669be9057748c680033daeee4c3c113130a8588b509ffa5cd77aa3d9779432f1e9e34c1503208faa118f4665aea8b25521be89922989f41b13f8b50621617382b6f1983e3d0943d315ae0be13c567b06a00461cd20b6f6100275b71d2ab0f0b37bd936dd2145875b4e62b31aa582eea2eacf22e4b1aa7ac7a1746690626ec5b97970a53c253e0970a08ed7fd1eb7b6694423442c8c2b3f64965a99d3264ce90a64cbd4a7a55e326cda21b167c7276cb090242eee7c7da6ed01dee9c86493f4649a68c0fec64bfdebd28bb810b203115c13b08951574d5876a0174d9b6a77260c5d767849fad2b1df459612fc0fcb13a2fb203c3efa565cc856415d7c0b2baa6ace4bc23c82b733a379e52444e5e749712645790454603b4e94e32cdc711321138bd1e20a358b7efff266ac7c19ef4914225cea8b5f98519c9487fb38095dc55d071c94ed41db20c6892c790ee1d5cd552f31f144a11526c293b6bccc5ceb90d160da86e96b2388cf442c8daf577c6085fdb08d819d61a74c44dfc17af633d6baa627756d8f7c94ec6d4e39099e95fcdf68302ef2e65e14a2437d375ab4c6279677fd6ca7dd2d1ddd1d9599659131cc0a9ca68d49909bba1e3898c4934a9d620c4a519b5769d1937b58a5eddd66f745de2da1d72d5b41ed44aee62fe64394aef5cca370cae8d5a4dac437937f51109d3521190f78e47116e85f3cc0ccb5436416140a7d7d18c0c24363e4b5ee449fecefdc34279ad87afc8442768fbb6c8e9e47eddfa2387653b44f04489c11165d3ad67bfd22f881ef39ff0566316e27deb09415426034c38d1466814734784a3ac73f9bbfb46bd10acfd0b5ae598198f55702f7d1586c910283bafe369216302abc38242fd4abf36ec352f8642315059404f19976008571b4940725e65c09b74aaf7c9c6300bb5aff74a85b47b2d7f81bf67dad69aa28b72a1316c833ddd446d06d233945b60be453b2d5aa7880040e88a503ba1cc563c1b1e07a6fe8e6098e7e92c0fcd4d87f8a4e3e9ca7ade28d4416582efaa9dfc4e11d1e91363ebf854c11b8d21a4fda5ed4a06636773ff294524e55d1101e550438595284783f4e457a04644a136d5182026f836221120372eca289f3a70e6b3d21c10564e6c4171606c7c73f151d562f12223e03f940f6955e1d182fae7afcd28485d8e16ebd029b6106639517a9f5433674ba532bd4bc210ae74336762934c573b1168eb55b4ea9007a8bb4c4c7b0df0fa422a7dc4a70d9c6604cef5bc4d120c05e160f88f61aa12e38f86e655f877f4375e51d74f4ae264c9cd98821115bde7aa8162ef531915c2589e9751dc7dffb3d55bdfaebf2ff7ff4c1f17d586939cced7bf6c6c57cf1c61d1aeb5b880e8c3c5997a858296758db42c3d7662647e75ff2b00c349cb52a45036db50286577a65200a98817f3ef30c5480b3478059e8d9d653d9048cb4a3efab22e543e6dd5def362305f4af855712ab90e60d775d712320bd09711bdc1c0e91a704ba7f8b489e9396741696e8e077b4bc1057e5be8fe27eec5c75b62d07e2ba547864f8c73737d8f9e2fe37c0566b0bd6dd6ad94e5cbc28d66125a236f5b8c3ce6f80704f5db297700a45284114223f89e8d0b4556047b4b563f3ea6971d552e67f0542f3bf4432a820688acd136cc74fdde006c7f9587192834f616cff3728d8e65a1c9e4fe3d6d907b62a37ecb31337f9cd6008da5a40232fadb5d9ed2ce986e5f85c7580933a64c7253417fff7d124396295950d063e9d648c0fc7a9aad455100cd5f0db658800c9a1e1105c897fddb151c8efd290da98cb1c7f740b173d670d608118fe46981e97e80588b75e5608cf279e8f2b8081c6c400112ffc5b436412ebec6a64e6149406bbc81161e3fb92fce0c66d003fa9e55d84147a42da6a04fdf438a10228740769f48463e5b4ba1dbd14c5b0e719ccb0b1fd5cbf8adc71f72905e357c4913240b67e3465576ab5ecb644517157bc358166bc1bca7ff4d4f65cffc3086ff6be36d858485fa1e174954e503a964a750cdeda906c7d06f9de9ce75d9a03c6bef454d9f70fead4643150c5798c5103c1ca95be9ff51d1556e90f6cba5f9c203436d8f11cb92bd900dc759c6f445f52fc02dad4c45b1da7a6d98ead76e8079034a0df96f38ef708778c8ed9cc5e631e7e0eb3886b9cafeb4aa8226d33ad881c4adfcd222fad851043c041166deea5d489b297f1237f771e48862c8527be04d220369e67df0605f469cc9c264b3073b2b9f9d7ca63a587f2ca98ed942e3565813a07ec6ef27ec43cbab662f0f2784e154ecaa01ee2bdd75f5667522ed5ed45de555780508530f116b27c055e12a41f4206ee09fa96785171095f5242b1f7f3f96296963ea21dd94b4f222d3de588011772ba0d7f1de3de0e34ed2d3557c84786982290cf9725f6bb72bc9ef6bd134d968a7d32ea08cc5131f0579c43d4db6ac18c46eede6a438ce83ddf5ce7f39ef175f22c00624f2fe3f6a1c6b37d7c7ea4ae244ca3207aecc4eae9fab534785c7096bd062a422b5a466a835d1d6eec4d6968302d02807abda1f7c2e5ac20946e94fb33ad43c99d61040b56632bb1e97d36363c3f21dd852a876e182db97d13593879e08cc71efe3a86345ff84643e0b7faa056077adb67a7e1543282e0f16c5aa581780924b20d5ddcef0810aaa6080525124112b8415ad9ded6a49913e4c02285363f807ac865c82203d34d897ce29d272cd046169f0866a50040f463a56ba816f59d1b3758591e6c4d25a64224ea62bba3fa68de2da5b8011f297fed0329c457178ed074813f5ffc3a0850431a444939524c7a09e15aad9bfdadb6631288cbe5dded0960244a4d252963cbb221a473584a6d65c709da123821988d278d9dcceb3f6413930043e3c93f419e2c708977c6990046ca3e3592c4390d4c58ff6aa43fddeb74cc52d71f171c6da0b9a3857958281b4a75076146ed676241582ff367e408de3ab4a45ee69e7bd2fe1c9bf5060f0e7e198cc5bd6cce21ea65fbc7f0d5d03a8ca132c477bd490bc3614a6007697f1d73103b013756cf7f2265c4c4c750b266077a3ce03609049dd05068101169036359407e11ab2dc6ab7d01fe3ced5d901eaae8279e200ead9b4d5038c94f304610961f1fb8648cc66429d4dbfa20a8d2f01d067469e8a5376c1fb59cc3597227c87d2fbb47f068906d812ce5f116d0178105e07fb2f34b5a38700bb19bf741525443b2cec7850711aebf21e8ad7622f8618b1e83c84bc1890bb6408fcf043b44d1c8ed7081339bbfdd558d06b35fb21932003c69e83469ab24cdc4976d4a7ce25adc1648a7b93995063d43c5d082cfff90fad850510dccecfc7d79daae3249a6d694aee499418545675a6f73dd90f0a6fc81ce5ef4cbb89a12b2cc19ef471352780154b27a4893da59f053eb6d4414f331535449cdb6f72751d66828c9adfed1c0cb39cae5aef8aa702de2e83ed4187153d33d755cf518dc6f9d7f4b93f4a6bf0f7919770a131598b2a60515f021411e48ef2414cfc248759a95672a7d19e460a3920121d5575d240344c0c0dcf17d3259892bdb9d45053a23dc040010a2836fc55fd063949df35ae64053ff6b597af4247b46311f2170d1b44757cb0c13f7cac7284ae11ad6d1a6fb54d639525aaba3effa79422e00ba92266d08d95caa7c17b5eea4a21b55d795f8e227a80919e0eec9d1c09e51752929577cca9dfe85a7f156d6f264a571d9747e9e4ca6cdb0cba73c3fb9fa4d9542a499fe89e179512deb2ba1e062426a312bb4f288fc8a1581254ed0a69a094d1e7fee11d45a9f5144ecbc4c6aca1270e31b51298726248ed76d203bd40d6da5aa07b92ac83c23fd281a384bf780bf9bd894a76179db8952507c1e72eca07cce3b57a11411114b739dcc8c23fd4a32451890b9175684764b12313d95fd2f8d08a8c16b3729020ba84fac87ea4989e4226d8c378e18f789d4c50889e54a68351d54583a965e1029e2b84a7f41da97d7ab036f7393ec86309842c58de504de6e461b48a18569115327fe1e4ca1ca2f1528d8978e5084c213ffafc0b34002825c56b45ad74b684ed4f5918015aa29516e8b2f94add3652ab56380a777ba9bb7649663395538152f24d6ae349b89260bb9bc6e65bf38fcd270ba3a868d5554390b334920367a7a022a0ac9e49669e71660a4605ff74ed2e0ce6a31e2754ca2cd36030c8dc90333413021cd145508440e97108f0c266d32b1e67e56aca8db5e3abb1f28549ce04b1a54197a13be329f72aa978d184a6b4ac55ce44afa702d2138b4ea642cdce951656c0c12a3b027ac197bad6f3981684e0807fcce98d21dddb73cb53b76b72db17a3696a04221756ec7df675adf4eaab4753b93c09f19c5f045b6153a8b4f75c35551528605012b3f0e4903360102cd39a6487d70d46a5cb502697bff4b6544b16b321c572e7871aba395549883cebdc39b285360b4e9088fe89fcd434c823b8f806b79fb8c79fb6185b1f06b15101fc6c9acff875b52ace784fcc41cc68706478410afde420e673c5d5dae30d3324ce62e36321852f61bb0ddfabd3a716f36a0274eaa50d2d4e341b73aee2ff0ed59ede17e11f535043d037b3a269707d8b9e425e44e81da0099fa58f6bc616b50eec47fdfc375c5aed5c981a5ff23d036613381199c6c3612f2153ba64b42c5ede7ca70e4d66d36d3de91d420d6ecc51d7905349ede560249284839c795494cc98b445a7b7f79bd069a49cbaa65a7b79f98ed10779b810d6e4ca1d462f010065d7198dff22e64f2f892675521a7f07d97f0e8d552e59b76e3fbc7d852462ed73e8dfcebae7494316532a6be8b67cb72f45e7579c6311bcd7cb049406946fc394cb8009ff9d2a44df1185956551b9beba5e672678b26f7d026fc6cb113bfed602df6b733160e1197744687a16fff564538e15fc9dae205e1dd686f7ed49fbf3ab50e01e135c9450c160470ba3adaef74a73aa6022f437611734a0f2c1201bd6404d8b61218ad2e72e2977081dd61132a7c38b527abdf145f64592d24b9f0c3f979ca4295596a8b8ad8290b8f3f08d738e2bd6e7ec79fba64e56607dbb72c30986af7026e86ed2e87cc7edd81550dc29812bdc9e521c1cbd0656523b1ab155132d7c0204da278f4d4bf029b89f2c8cdb0da9755ec475aa5ccead3eaeff3e2b758a857a0abaee84b76a5f383105527405c57169f125bb3f5f73cab59140c42bad766b379715c42cb712f6b98f313dce6dcea4dbfa11abbf204dd2811b79ef95440af3187cf7434b5e0056720436f30f4464996eb097317a851433df63c898f97b31a4147e173b8c4ca864748fdca38ee297fd327627aed8b7fb251b0cfe52d39349cd8f3e6e75cd41fbaa7b2621ceaaa947410cbd2dc8842e8977901309db0fca79f71089f9315dea8c6fb9a43a5500b0a88beb59cc9800390d04266c4068a295eef85e0b8ff58f1ba42f05d0fc123f4d6c463d08e044244ce3f561e536106ccbf758d94bc2fa4018f1d9afa1fc5f0330231e96657bdc8b27aad44088190faa4194085d4a383db79e956130f215cd244a42e70d667271e19a336853176be2adfc3e39081e5977ee6f08e6d1d988be8f2077f657e36ecd2d4941f8054146c2a9bd6bc3948bbc590f1a3b0315bf455467b10f5d9ef22ea65115835135d98ac718fd2c83a6c3ecad6ad4721c3c91ea17313b6e444ae1fb7e897219395936237b318bb498cbfe75be539a453adc517d2f23ed0d66e450c41ca32f3312d46c236dc62c535dea64eb7f84bcae909c5d88d7a9e6bb454aed9a537e7757d09e71d2bb0a95b7beda3f4c2e131b0441cdf7d41632abd67d54afc26e69da03262eb8e3f861aa5b5f7f62a24ca7cfe8743455fe998202efc1d0f898fe66ff10242282e9a2ae846c494e0a3d99a5db6d988283d6c6c6315e11e6e85d13f6ac2d9fc6baaf22b6c01348fc2e52d163d3cc2c14e1bff20b321f5d94553c4a19817f544ad9a6146feac29af2c466223619a898fa4fd4ae3988fc612dff13e5a1c8086a9d7139b844ee094b428ef9f01831b8183ad9a9f440f3d345eadfdce474e14f537ec0c68ce1d6b1a067158bf966aa2f15f4f6465a957b23d6d70a7a06c86c7518962eacd19d4f82a99947d925e6837bc5ec22260b7accd4259ab1eb8bf488f3a9f7c11a8d39e934e61084a705c25620f9fb38f8d82967fe818cb0bde97cf63321c4070d88a2d66e32cbe0b82b3aa84ebc273e36b4fadbf826a99c0d730eac001efd4dfccff21f8096d325780a3e5d9d7bb58edd86a9adb904150f4aa3c7550872ab27a5d54d5ef6473e80ccaf026495b77c21e1f31c5e125247bfd63c3d8e49c4c706883ffeb69a1837ed08e8c8282bf412f64fa9bd16178010f06e4c2ab898835f96461094aa6fa9bbe71655db140f9326ebb7f5138df5c04989892965dd3a680f57013702dc1c7c4f155703fcb385189584187e94d27cfb4d1d362363387a72678f0d552afd7f476f0a1ef6d877a8056c5013f1a058307a39bfe0ccee4d3a5b965f2e54185275a4d87996680545de9e26ab8fb2fa4136d0a29bb3b8503b0783bf4283a46063bc5eeb000d8b2d1e064b2ac0551f49acf953b902de97fb77ad41861a198c86b702385b51c41d389d215df390b8e06f90846a8385f944960397ed92b8ae7a71dd3ec9628223adc192ca773e77a0e0604920322b7d5590dcc5cc3d9c82fdcaa4c9153a0e78164230544c3e04b84676c03908d2830aac886631ba9fd2cc82144d2bccfa29f969938b39b57c4abd6c368a97eaa50894338ccb266fb39e8d4e69a835a737b0c48cc95d9ac066113dc3b8b12757799ead896d7e79c435ea42e74ff9008195ffb8ee777b4cd5fdb0af37274df841010d8e2d70c8a485ed3ed5c3dbe328f7e1e935a51879c08a4962ff2e9423be38267aa94ceebe9c7a0bff1ff6c10456fded4d805d7f55a1a4f15f751511115bfc0546506c65936bb334f88ba37a0692fc49e80256872857365ffb4051b710da83d555823ab42bbf5209c74d6bdbcdfade98abafd2daa9fb3a704351c690396095cd4bca3238129c5d57bbdc236d20c5cb60067e2f1313261bd7981e82bca34a153afe483b07934d56c2ad966881ffe53a4a25a06c0c552289184072ae3d76d5629c75295d3b79d3940fd995da744b746438c4c8d9272a94bad23de136d4f88bac0e445c37485e4691d09eb0aded29c0798a0f099a03bdc1bde2739869e26774e923f8533e232b5cda52aa072b1777a5870543a450833cc74fdee0c12d1f0604c267db6d1f81901a68beb6cb53ce812be5659f5f60dfbe3e0d0db3e8a79a498ed51bd7e6e230a5de3fd71346d4eefabc6805e88511d246e539478fd9a1dcf9f1d01fc0066c3ac1173383bc0661299d643cd309250b565fab29c70b16797d5fd07acc4648d6593711abca7b4942d2cba23c806b655cab6f5971e499a317ceb2dec0969afc9b2208e275e0d751bea9b03dbbd2cd0b1a486b6ea5d0ce79ade13340cf8c08f715f0eabb0219c8c00c7c0400157d2a0e586d9537b01e42fc81d622419da1730377c8dd3aed7a1c55989a97108d444bd1eaf1d55d6d7e9dcf7e84153385574b34bdb14f0ef2956b188f1ce519db8106460a5701ac4ae5cdd869798daf60658dbee306a6ed9e1e6cf0623b73c8fcc44fe93a5406823d8e4c6e8388d4ca507d308b7c4051c67a0b66ec9c018d978782c8c8bb03bea8c37bfad71b43eee6f46e2ff199a6a9f59d7f6cc67538da4ce54efd9f842f581028a32aca0d5a92069876aa547987fc969205ebcb2fd6465e398bafd6f97001ea0e932339f9685bd8b6ea06b9d2b4070a8bd1eed00fa37e878a90b1b011df73646e6ac804c1472212349e5fab4baa27067db10108f399f14d7bfc1a5b78fbf21ca7326e8cb8262f96fd66247cd1dc8d2187fddff347a9343b48ecb816daa1efc1082bd72cf0b612a86bf3390e7002e99fdb5ca90ea35478643dff09428899b36a709b44bfaf44254035e5e8088159ccb203342c1a5072b3e44b7619e46902d6ada5e35dc1a9c164167cb4510398f0d21de0e42d6c0dedc42e31165fa6dfffaaab04c19aeaad16ca267aed6c31bd69a9bf0e3b9fedef832cf045fcdb0e574297defa156a6d93fb170f1c42a0fcb12ac157b62770a457b5ab7c876371192c41e06a6115a9fe01364f4a32123e4f4c300bc3a99aaf90251995b8ea3f1e98df9b989f2ae708f404964991ead62596795d0cde8f793e84d1d1867018ae537dfe23f7c7440af91a04d482ba78b0ce35d4c7802d4a5c55b7ea71289340d37074be72b7cc6b6c38bc3cd82c5230593162237345ac1688943e9e74b7e58ce374bdcd87c9df901ead87030e1cd7ce24d5f42e6a8fb8d8c2b8fabdd767110c92ff4e6c73bb7062264fcdd114666c45b3a94f3ff5541384c82a1a5a183f062bc8cd4b03b1d4393ee3c184d5c71c79663db9ac1f96eb852ab4ca02b3450395d5a3142b21e3dc1a105901c29d4159a2e02e28e926956d09af58fe48b40e47950edb1cd4d622157216702de537a4e85767bdd188789d592ffc9e5b8df2799af981c51b7de02b5f7c44195edb52aa723523daa137b43b36a297c5045bad0a7339a7b6201d2fbee0d8d36af303017659114c516393c872386158d94c9aa95092cfcb52540a9fee7862a3edda271eb101f58db5f8680c44e763409680ece9d745ee631deef0cc87b772aadd4219036cd7cb76539215d3d102feced002e1ad52fa8eeb19668450f4bc778f66344258e983d6a774c8513faadae61ced474618f69c2469035766ffafc61814ea80c7abab622d943fa9e0df6f5f413540665d37b463241730fb211084eda7c58e41de45e1658fa017a509b73dc780d242ac523375b39a74e16e37036e85008b85330028994637066b3a176d86391d972320eb841b796d8e7473e43178b5c78b6c8668b306e6f54d25f77a692c3525b16db8e053d8c72b1368896e8e6eb125a9e47fd17ffe60fd2772b83a6eb1f99f055ef14e190c7f5797cc5500fb686187495ad7d5233ae5d9beb38d3d27373ec6ce2b3bb231ce590931b352e8e2f10155a1d236fc0c04fb83d5905319dcb31c85056d02313785325b7957f08e7299a47aca286862e7d8dc5de9c4846dc79e7c001c377850198d57b7e60e44401d0671d385b642458ff9f4964a47fbabf6e4603650005e6bec9cdb0908123a760288d0102dc26c8effa0c7bc8c4caeb1595aa50f0be71771afb2678cf3de40e4b05e1345764551136b004bcd60a6b4992945ed0737d9444f9e11966f800a459b8a643b49dcc78679858ef7fd38ceebd5ae97e402c79cd707f7c1ea49e0c9a7c25c0150a0b0558fab25801249aeffc56e6fc451da6e5979763398493cea8c5b9b409503612e061ff37a9482012fe76a3a99ba81bf0084463ff264aafaa16d320a4c3c786d198f20e26b77eaa92cbdcdbbd162693d993ba276376fb8bc11818ce188adb6bdebc6ef4467040d386b8904a95db358cdd4894da7f182a661e2e71098ea4089e4cfb93364ce2bbe0b32ce9c8f5e056d51e0d9aba0e6f1f451e6d92bde8955599357e87462bd9b745b1f90aefcf8b2b03c2e59baabfa02b1567e5837a330ab64b517feae80e5ddc66301b19386b51983546276e4496fccf7431cdf56bebd70740cb12b0d689e581af71af56689c031707f4a25f45a8098b7952b81a8a15c445e5d34d29a91b1016b70134dc885817aff7182feebb308a8bc4f6dc73027ca794380236cbc565f3ac007591607a106153a3d091d9929b667c2bf2426f8efe8d0d1057c061f1fba85b7214b4596509885931e14fe6ad4794b3e3b8756401eb68c0c24d62479bd48e287c6ad2ec1e7b15c142d30cc7dc9137b74db7de0741c0916fb987acae665382c640facc4e8555025c428fe9005c6d4140e341e2f8c03ce1bce0b0c3010be1806a6848724a64d2c725880610a438a506f4e402777498e659bf82d258ebb398b4014829a69f4fcf00ea91bd986400708680dfda84d300b463b202c544bca365ae65c4ee5e0a3663a7c8336dd9d938826963b435837cc07b70253ba9849623ca12295b9c48c4846b1752fde896fa106e250091a60d96e3224a6f117c6570517f8d9cff57eb787b2d80d02bb2b02fc5fb89c045630a14e2adba4c19603208a9cc5fbd55bc2a3bd4e0d220ae7d0957dc2d3cb9f81007f4f6c05fc1642c6318fbb2c6ec32f63d254077c90c787a166122e858005bb023a0c0c49738d466b60c7f750378518d54ac31868f5c53f608e079546ee7847ab15769ead850475ee3de71f6e11110578d7f2086f443842d905f08da2ed9aea466966b81e620d9e59b0e935656b4797e177c9f3112c9733acb71ce63b493281565aa24c4770bc35d469be053edebe5082dbcb251ecb23a4a5aa667fd0edabe8e5ac1bd1a9d6ef2173c60cda5bb21ef0c4cac31c210531e9ba65da39e27177657a26c16ee133a2778b8146935524f65fd1b8e3f81032c27ee5b34fc18f218a96af5c955373063f0b851e52f346d82c5ef4356968f507da8d2c0e7a7b18ce7bbb9f592543266f4866f0bff0dea5009d7122b57c75ce380bba0aa533193eae66113dc82722fe6027e950c800e87ec29ccda4f8fa619cab9f5beebfcb2259214d31a937c7f8a4551f3499729725ba02664bc36f77a09d8b4cba41c12d3d78b83ca155a373f5a3e311eaef4f80c1aa4b538c3fd75f2de9529cd2fb87c64c78cf2f336ada7bc8b0a2fe20df71d9afe59ccef3e34ecda5be51f34409680b3e3568bc471278abf60abb06678591d50261166c3575bbf80d5a430d8b3bf57c44603bb1615ec1ff08c9fd44ee404851e203e91fe246eb103a081558fcba690184a7a49282bea1ce7eead2cc1c08c6f1a85ff7a78b30c190b854ff931b0dff1d197f413fc034a8dd55973b13a806580402afee5977cbd45f50ccd47f7b940ebc1d139af6b60b80a308c98baa57c43dcce809a48cf294ab520887659d7464f774a2a2633d3a9b34742c2dc3773469d228f71f3637eaccb80df32744ecafd50915f89e29cc01f98cb3eaac1238c7469281fd16b7017091dc3e749f29878d3ae47940bcd64067401826f37d341d472e89899a6678eca978e66878f6b7fa7e231aa1229b7c0201900e8c6d5bdcc6dc5f2e3ca2a7b3cc34f05cee679ffccacff6637e63f6f58fb0b28d6619cb0ae0d7f4e61a83cca0176913c956f381ecd59cd11be4607170218e28b9cccee7b0d6be24803a8e0fe5b92b3449dd924b701c20c1d741397170e1e97a5857eea5adba1c7a272c5f36644f67cf85d405361afc866fb2939d1c988d780a0b11ebac962be37536d8fb38962954cc8cbbd989f86b12a9766b29e2fbe1673c06df94de47ac60db744e954ed3d0469bd3c6eff84b76b93ee09472f66ec571845e3a86079d10ab240c4d9e3180dd166c03eb930b7fde73508dcce1eadf1a3df8e673808419edb145fad67f50fadd1522576a52edd6ce2b34850a38fde1822ee4526dff5d5f0ca007ea15d078ceb46c94f3e365eda167300fdd00f1f5a8f9e08b67c960df6d754a82174d584740dd9a12d7fe36322d2838fcfdd16a9fb305adc0ab10046b969b137fc69d20d89f4c3b93c3030b92f16e3f98a7024438b014841daf5bff752c9f6134f853ee349f1c84635e41d6aa51aee9ae852be62383bf3169d595a0edcf6cb5d7ab2e8a48a1da81c4b9a60b22b019988348fda0923b0a050d40e5aba17ae1b814025569ad39cd0d6036c6b623195704d0b9f1ae1a624bb97e2431a0e54e4d9478c2524499bf5ce6236f97558bed6202981870d7d14c59b22d82c3809d6e4548691d89b4199500648966c45838fad9b975f59fc896aabaf667f274522079801929519b30900f1858c1b3d1df4206c2c8f9674c94d4cddaf87374abb0b3bc3d979e3c0413d26aa86eabef6e9f360ad490116b451bdc7a8087cacceaaac0725f720bc687cd1ce5bb634d6160a900cde1f417d975c120a8631944c9b218bafe7b992c15dd422c344a6caab95996b0ec09530f97348001239b8db4879f12732786aa7640d8735e6e720f84061c80cbb0350e864f7127e9d5670512f93c684e3063299aa2ce7e495bc9968a94abdb1ce1feb74015788b0d546bdce3cfe367e881f10571325755cd023075f6c8ac08bb0a46cbe1f4b682590c8bac8bbf8e2bd091a5234b62d758dafa06aac2246d47f5cdcd9cc11ef20dc1b57b95c0352f82befc9305f3e37d74a47fe27ceec26746eec5dcf0353fc4da81cbc3532e39e7304fc36405bd52122d7d6890e3f591a093f9421870ae5ef1e1472a7b80365fad45e08544f76d62651aaa67f342923b982a101c1fea76f33ce037e24797a3b50876ed048799986917996b12d298624269e6c450dcba5b0d3103c61c704ad8ca9a7318257e3075ba636c57875d04e31539ec1ab00798a887fed9cc2fd0035291ac9bfdd855f189f77739b1fa8c300380e80a4298b64bacb6d6241def941311aacd4e6d018d804c6f9faadcb21a1607922d49afbe83b6bfe12a54bea624ea105381d6479e2b38ac0fe9dbe2a7ac319a2ad3aec1d81a9cf6b611f849fd0ef6e8ffcac40281f86fc88e8bd5f17081ae645b654fb469c40dfde53364f3f882dfa8bd587afe9b8d704cf75b6850003047795cac34d7d7acc3c5ac4de0fd1259a8193af817ee7ceb792a3f03336ead94953026b9313333bc5fb82626d1dd2f009417c484fa4d065ecc4b1b5f8974ba7a788ee37c932fa1da8d664c4517a74642e87651d6c05e88375147dcf3638afefb44d4b241fd840727b89f3e05014d6b8de2c8b20730f1e5be74dee805d979fecb87f4a9f1e2171246ba9c3a34d54a26c63d06cd02df19d2e6dd28e1eebecd939c69183c8e5bbc92038316f650732f7953252c57548ac7b8afe65ef8200c4b306ae51ea111651afc656f54765851861436152851dbd3485413e6cf2c7c81ccb59d1087febf254f2f336fa397675e358869e605c09b1bc49f43d8c48153e1cdc24deafda954bb0bcddfc67d870e9e38784e2e77e137e924bcc73b370d0229a7f08ca3b7c95e999be41c3b733e593c1b9d2b256cd6653580bf521737d496118084c5cb1252d6cc42a057dfa2a05507654b015d1ba623af2e578ec6434c295526c19538060de1827dcda1b9de99a6e6d54ae852a45d5de63d866124f61cdaea7c28ac30e891fffbb952997d090136d7288a576e6bbd50fa283ffde42befb2b247af84dbfc8dc95a812b7c25fae0a5d2d73ad74cefc9e757c9606ca82e5aa56fcf2d6060f04be161845ea753977a60f0155df90675f09c55a675d4f3903cfd0cbdb1bbbb5c9702852ac9991644ecfba683bc20d61129dea880e4dc79b87a9c6ab86e54ed419590a3d1a67256d5679313c74145653928970a538a4564fb4614d1e857e5a8d38392e9abb673ace0604b348fb7a0d164674bdebfc6ac988fa81ace46088e6ff817e4f8a66c6c996ae0e272e8cb09cf565f283f64ee394ab50d7d177069e3b17a3f44d476b2ffbea9ee12f5add92cff66eb3bdc9e323a5bc66644c0ac4b973affe466d93bde3058bf960b60a83cad433d8957a402a25c34c52509e38742824e2e63ca2ee4a87cfe12da9bd9e129dd432fba8d8c9b474fa4ed2efdb9b1abd22a2710408ee3f90e7de55f65abd5a85afae5e3b2a9256b6da14515c7be912977df8453d5d967effde60ce3d38fad24552971be11ce9149704b68b489b27d3f518d802e3f2a1b3842e42c062d0d19dc253be441399154b3dd5d94241b5ccab7083e58c1b26c30415bfa39d02de2c2dc86c3ed17d96c707d18e41fdd89a5d5c0f6cfe77d4a392c200995e24912af2e9f80a4bde952f8d9491b0ca986d2a13a9e0dbe255b7f2402de19e3fc351ebc531af60d3e377faa8736cebbf3ea4a26defa86935c46908fb95dd855c11a9f2f3f1a9c73db30198345e8816166ea307a0a50fdac6d1c66e125445fef544e9ce191f357465db83008c78f6ccfd7944ee62cf655f67adb0005519ca50c6a05c0f97071227047bed7caeb64607c95de4c8963c32c2319dee978867ab59ee5fc2c029c45e330cdbe23679072527b00ffa93ed339666dffcd2b28f8554262df45f55414952e1ff7c702da6c118363bb2ebe7e4529db6b97c2c5ea048cbbe491f3faceb0450b70dc310387a4b565ab8e82f471035574728dbe1e6bd197ef6f4e1b13c1f7c9b44be82acebcbca6bf979e8134f75c35360769d05ef9cb6e2860f31da8008c895e09203c5f66e5f71b1458d7a2884ee5a288bf6739a257bfa6b083bafa45e99f0b2a635f80032c47f5e8d788aa7cdb7d26a74eb0616a6c58d93d9b79b4a518280ded2982ec96e70d91e726f3293a936f8a4863e58adb897592f81da9e9cf8e00b8c1f6067be49c313ba8e53ad3c826f13bb2ff204b53d6d8e80d68bc6c6f515c351c12eb7c0ca0a1e6072d24d8ec2b8114decac83ef8a0cdea134164ac23807cd0f19ad91057b711a86f83b11050dd6e83b10d5eba13dd1f5c12f64b86296b8b1743176e916b1e2b2d8ce6eacb03f29a69dc0d47e1f9ca4c5ddac2507d87e8d9c031b847d08efbb1c12285dc3105cc80f6f7e6fd62dcf225a752350793a71ebc23eeed365c4798d4b3444cc6055c23180c06bcc138655b671d91417a37143fe91b0b0cd2c594cf165ab63efca1de48aea60d0ab0308af4e4c51d0f2544cf0f3ffbee55ea9678859a1b39b45afaa2fb6a8147d444f2f4005324a453314c44c69106cf22ae815dd8029a867601990e27ac7c1d5e13c99ac5a690bd3341b674765f22dc7da8b545de196e5417a6ad441c7fe66be518adc5be1ca0d1d1fcd75b227db00c5a26120d3506fc7ffd7af7cfe60bcaa760bbd890fb9a2740312fa5042cb94f40844b9dc2edb567bd881c804d51f7b051c411e69b48222ee7573ab023e6477536621e8aa8f4a324b9c753c3eec8c49a82e9436519ad948df3aadd54b81c2d9c7a378f21d18fb194cac621d033b0548f29b5eea50557c1a55c8f30fd10648cc966217617c51e3ef2eec141c8ab426782510e8673906e1ad1c1cf50a85e8c0e1bc45311c8d0008f70e06be895d974727abc84714b31c394d162181967b89438684d1fbde6becdfddddeac8d08ab05299c1fe64fc8f364a146d71693cb044188c445095ed4ac3ddd14d788b17e30edff6255b97b74ca2a67234e484490b4785814cd01cadf4354e279556a0678fd0fad08cf45a11de0f53fa48a6bb565fd774a889931c16209a885cb44c488fc8dbdf986523bfd359f1758fd7c1f20f8e73116ea870feee773de2334219211948d409cb7da220ff8b2bb943605762a23563eb740ac3af0323b44082a7fd260d781a276d767c04b6285480b6bfdc50af89fb8083d96d35750576105e45f8713502e5991cc6d2109525e1c3705659d79d1e20f468df16921fe7d9a791958567c2ccdbf8d06e9400d6e7a626fe5fcca3c953b2b2e242f2d6df3042a8f38ebafb63282ba268806035641b2006f8491536e735925ac1edd9fff4e06ac20a406bfda508f6193b7b7c73b285e98ecc3e749191800d986a2edeaf7740866a3a4c925edb4a50ff0ec4712cc4e8b69e903d1aa82dd64552336b9500d97932250e38cea1cf62b2c6a5b0fc165ff874495213139ed687c9d61d1a5456dbf00aad7500da0641b48696b257381dd6bc4127c14a952b1bcd4ecf1b40151b7305c17248152b4b7821e769dd7a4adfa5d45109b81b8fbfd6dce8fd0757017fb4bb19072bd4c9c56c75c04831c8c1d4f63178edf07d1c68055bffb5e1424499dcdcc462bd6fadca12ee59a6f4465c2c73601215822111900860c15fa86392c60c1d552214376a7103f541b3623ccf51163e5b54a582357758a8d76ddda414bbc9d5ca0295d3125f21dc68c8540739295c653c4b72e520163d873b5e17a5af440794b8ae65ab8a9f4dc28dd545eca3f80ba1255dcbb7b69fa1f8b12d0bfe50ecff015c7bdade175a2379a17d901e58d249368573cf93ed59e655a069fcac516511b51da0151b65c3136bbf7bc415497f708436606652d7839d8c5704b5fcc9a0cd8c192f034babbfae8f040baf5f5d38cc7601c4108657cbd2639e284b27c68af1e2a520676403d0c95cf8643387206dc0fb6232f277ad49404211b4bce6d162929b29b8c78ec5e484c4dcf1bd4b12649b0e82656b179a2e2ab5533e86c3f7affc98028b9f50cece74dc459c2df4636426be4ba14b12cf43c5c9340be281a468cec734093f16cfcc0aa7fa11c63b0a687dede0a5560a2ec3ddd1c1282c2c4b090eb8fad66d7f97c400ea812c9f5596df70236bb37e5de9a8fd08cfd6a19504df40482105f0fa03d12e788fef6261a58c6f2f00d292b35c8c7b4c6158a0957ee152c81532b79799c633b34f4a4970904ae1d238907596367d9e456fc2b4b754da48395e1ca5e052352212724433e55c717fab41210940b1f52b8b1bd251cbfd1b5fab1130f6900f98e09787e7bb4dce965981d5ee667d63da8126105db1031a594e27eeef2d763961fe2a11d49fc6677cf5a8fb5877daf9127cd7a66a2dce341780c96ce651578a4ca9fadb0b042c312861772e0ffb8dc2532a6172966b606ba4f0c2ff7c58e283dc89ec106eb76f979fa7b1bfb8f617e5b8415f19360447534f787d5dc28dbb87262e86085ac1dd9a0eca086897fd7bb1d5be390c364c2545f1fc2d6ff91f664c51020177360324872c66725dbbe617d3e0f525957fbd8de886d88f193012dd98788c44fd8cebfdcfe455952e3b6791fb06ea26913eecf2abf622900f1d7df3167bfd9fd32c1400ccdfff32e274863100b9770d91f9468a5070b9e6b97f3175aa85b73c360d1bd3eb645cd97fc0a7a2dbb53a9bd7170a75073a49fb79118ca73dc3d16bdffd6f8bade6e298192e67441ed8624946702d9f0a9a93336a4304ea4c16cc7e4f6973b02a5b6c6d490e14a52035e9dfb925fcc0111ff2e05e9d45e06fd141209731745868c38f084904eace6d717284d82489624075081ddb9c9472cc98c672586b946e772b3ae08ca7320506f915a6b2ec7fd4aee78525d85639e76be8be055eee668b65814b898e2552b2a6c268fd29bc223d83ac1336f73e2132dfd20bac044fd6a72cfdfb671a027b9edde8d5620d6c78016344241118f8e5cd7840219eea97764ec4ac92aad01bfd445133ec766b63a731486c4cdfd08471b17828f385a49af3160778da3b12bb58282c1b73b47ba71c671b7181833ce3bd15924e58d15e0cc7c954a3b7e38f423b817cefa5e3c6b4627a3a689467c1712965186dcd4c0f841f01f04a502a75513ebbc5e1c00ed3c13dfdaa3571bae56bdfba18a4413b5d8768d07c64b986be1c73fc2900ecede06345b80480735e23e7d1c4b48afff7424aa071bb728d76362aab8d1db36f88847718e4e0788290d5aafb90c6e22bcace03d5e794cb229e61764700bc36176e9234ba10425581c8859a8283e07330b58964130e16ae3fc006af2f189bad1f02998cf87e5820c4cac6704335909be5b5f68783957c715c8cf52ed7dbb1165fc71c994f2846ef2f0ed281f89ddf43ece9bf8384458940ef42ddc30478766d32d4500b5cba7657a9ca2818015cb6e9565b92fb95cf94a39abeab30b46cb625a08f8c16261d141a68bb6ee20733561f17affef6d2e2d14338ad7a68a3d4da06f8e689bac851a8be09e1076bd711a44210e14f9a84790705819e9168d6b2ca7dc7d8e946f5e2f2d500160b92c32bda45ec36f1d34065dc23425578bc96397140f79ac813a6f2cac83ccd155900193f8c5169c8f4aa34f498bc0599590616686124bf422129d7e22ed52501505017d2741860dd33c6b8779a5fd779fc0edf0240be9481d3cf1487329ba808169d30583cdabe6b5fe175f9a81789221c1ad756b61bab063b29db01512aa08afe9a66d599dea55a60aa6e82e5863db19f32c524bd2a323ca57213412d91bc7a2d12585c32a8540c9914cb547e258461162c2882137ef5dc3688aeefca12c960fca6f051fe09413fd290748be2663681c813fc1031c6cd003b4346a61107828c099aead9298988c9405de8f65b1e8a0475ca712797065c0ba4398e0c5a2a58a4925f55da39dadb498b0b23d77b479aba53ad379bb4dc40126a53d74cba2b3fe0dead7e4a07da7e1f8f42aa02a1a6cca1d66a50558e619491e14ae60ff943218ad4416549e1ca05167eec8cc893191eec11cb778fa6bc25052143ce31ab12c69991f9194d562d64c71b05316d632244f7ce17175e3bdfa4bf53c395e7738eea5decdacef31b480ac49edcd5b220942d95e3593043e2ed811cb0520eaccb0612ed80d8d5f558fd2444f6e34ce0eed617adbd90f34fbaef45a88912a474b3615187af9a31b48deb623436b7becf3ea40b1321a7245d4982e04f0dcdce166d285bf922a68f0e88781abfa2683cd569d331828e1efcaac498d552def5f08e88fd637557f8d2973367005dc81c498facc35b913fd63d8042704224a11c7fe7847015540c1f52e271d3251b3c4817e3249a18079b69088d7f9160196ef2b1f51d008cb58e7a44021712216d249b92157d978ebfc2fce024a4b0c72032bb96a8a6f8ce64d1c17f84a261370bea253263ec9df5b97e46416ad12d60c55d4aa6c3c5d2249c4d01cf515993303b60372b25095ad48981bb280106adde12224e7e070837698c3068a9791899320b12543b9b0a439b361b9ee5d8dba695c49976421b80b75909afa92c65bdb6662e84775912653af51ac22e3258935c31a6951c04001944f8bdfcd08408f4fa0bcec7897ac1727a7a88323178441d822aba1b3d79013a5d8f01e0001121320228afacca286a1d8d05c178efe215aa1697f9202063793258bdbebc2209fda4f08f0138b4c19e67916fd36c4831517f30c7a8bc5a960504c3afc9c353276cc8d021a00680d2aebbd94475efe7bdefa4f828ab0c832f378967b232d7c2609b7623d625f4fe6e8283eb042b44b9b015a33790c45d35670e18a54330b3e939e4eec0a920614b7d609ac162de937662ff08a980d6bb2438f94f1cafe1aae14f7302a9bc2b28ea1848cdf04e81c0a0f4a66a902f0c4822d200aa4dd622346a0f56b55e503193599843cd8fbae2c93f90dc8a6a247713d2221a8d86cdb61685f5484e15c82e7c8bd34492040f129b7d505469031a44da7b1d9afda2b7c041f8ee57af129da08c02082b386bf94f02ec0b801eeeb938a0e7a748205634587a913444b31fd6f28fcbf0d4de2195cbcb1b2f9759bc2b58858ce6560b226e3246a1323aa99362259e886d5834e1900a360dec6b380d43eb5b9c30a5fdf5d2753cf712001e4e17669ba86ea165b1e58c1de584eb35f4552d77cd16d34c1ccdd62bd00db1f76ce426dbd5949adebaac777995e56fa996f14815a1cb068312e3e656839ac8c54217156cfab0e4288dab66e16cbe43321c10e3f49435cba39745b160bc34eb08f5e7353a8a352f2964e7ff31a45a55486fb37a5eb7afb9fe0dd526e3b619612b5911d9f655274b8d30545246160c79a6c502283e4ad2be6db63401e51022c52015b128d79f80937c8f2684d54801e44efbde4bd564d91ed9485d796f46bbee29fa60e226c717411e6240586dcc51a99119b632e74948a5b2a60c8de875cce6b8de5ced9f19a12284edad2a86514374da7fd6d7470d01f3e6717f727c7dcb1af8faa8f794cc4ba10ab6eac7fec2957acdf72fc082658ad5b0008fbeb724ede7796d602759585479fe84c1327e39be23be2a0c2a8395a88c2dfa1294a4469b2013da4ee7959e95955520b0bfd1e47c30a938c0b0b7931d50d7142b0486c13b68591ee824dd61b46594c42cb8640ea2e2adbc3a02b7e2c12260d7c8f1a10a40209d8262fc32e57ebb6109c08609739f732454170fe9daf52b5c2305e4f33b0faff9986e2d559fb831e13a6ba5a365be630c101e0444bafb8b8398c5312c86f26ce3b21ba72e814893f62be5b84343cee40e650913a869ba7583fbc4106155424767908e8cb788ad4b9fbe68c506ccca07e3c6600881155364b629b36f5faa3138a72fc672ae8247fe338b7f25443cf8cb98a88901588892b54a44e574d44060b220945738a12e03c44194d65560e6e4c54d71601b50a2d5f3d44e5cb982acea8a2702c04b467ee2f39f120d226cad11925d3d808555662dfe7c105d219a5d820eb896ec4da9033ca6e3aa3f6606335c44e132b7711928a8c646e45ae3214944e1cc82713a1d9a09ba531bafa3f8907d4bc56c137f780ec5f8adee807115a556ec6160a9e52ac36a6c5b59c403f7db41b277943d63260b28ec184a2f523beddc80263e225eba89802681b09a93904621f39efd6827f24026cb327362458e126dc78f2ea7b3e07b58beb9e5d64522d63009b1df72953e045657593e0429fcad29dad76b9c9139a668a5a74f30e2cdbd89913bac76936600c09128b01c4d2054c54e13932b7d7f00752ef62902ac3334ad36884b443effd1e07bff83ac72d4620dc28ff3fa08c6297c54d528b9222d9904acecff2225f56ac1d567dccda611d3e0f421f382ea4ade400fd15e5b2ac000b3339b36c39caf12d19e4736fc80d3a9259987812f6e0f999fd650aca09635b606cf4c6538679a31f28bf5afccc05c61e27a7df75246876781fd75295fee6652c078862e39714ddf23a22f898f63639ba56f8381f58335e300a2c8e64550cdc097608e736b42ad8740b0d4dbca4f931159c6b84a005d12491da628707159a45033a62755cb1da8830112c0076eefcb120aab07b0277c369edf027c06832fd855cf7bbc93894ab4336bcf543b30d9ace4ce4ca7a6b7a25ce98a8d427570e6c1da35223b77eaeb7eb4029fccefac6e71d41a00eefbc8ea53375ef097d549c0c8ce9fe4782a92667aaad41dc301b39363c5802824edf7a64e57728e555fd633e9edf5a20f1e19a0a997a76d4fc32b09c44a9dbc1eadb725ca6a61cef6db5043f19c3f2b33a800d5b964895cca8e8373847c460631b39ee719c5287e422e467fde3e120d7e0e5c0e68bb73f9a282558754e91f18398aaabd141456020626e4fc03b5b0a5cba7966951863c6c2f543874b256ef3be435525935d72dec4564babd93ea267e7a302f923818134b34b8962bebca1935bac15b296e366d5e30ae2a979c9c16791e61780c3571529fe4bf4643604b6c7e4cbad04484f0141a3d3c052881eac67c6b95a3290024d89b27cc39b1319ed1d1d36a69409997d56e460257417cfce87b4bd7b12fb11dce2a79f31e615c9a37cc3aaa48f867e5815ec93a345e6d0a7a4ee42e77f8ba6b1f78c1cf539dcfe41ed6a113eba460a782ec9cff07982f80cc795501cbce1061b59c167bd95d1ea8f523663f3b6005f205faf38d7651fcedac398ffa9f94a8837eba1092c1b06c552af3671921ec600c83b8360505c068913124b5ab5856fb3bec685cb7e45baef951ada24ca657ff945892b7ce6357e441aba810ae76413309fc1d5b340c3cf310da05b7e963e89c32b4669a1162e6b33e586e6b1a85d14ac6dbb5f60626bebed6bec98ad59966ca700fa2e9a2ea5b5aa1d9c12d634db99fb3a486cf396a5c2a65fe5135d3bc11a3690164346581d89146af13a120bbea4c390f44fd65bc4b2ffd5d474da46d093c41630ca80997ade9e1b4b4a567f603246d86d19e280437420fec5b3ad67c210443aef7df168c82cd0709e08ba2d157a5a0bb549ebede55b138dd6aeadaa6686bd7c986066051d7444a595369a8c077389c9d181121f673a361ef243c6bcb090ddc62b63399388db05551ad3046ac90b7ef69d6dfec2b38a682ceefbc6253741d1ba2fa3bb5f97b9c3a1b9d7c2c6ff1104ffe0c2c72b73221a3dc1346ff90e604b7bca13d7ca7b6b7bd5e7bda30e87ea23a7241f6eaeff7f3707488cda70ed3b05de7206619bc301ecfb64df655fcea1bb5f17fd52c93ce89ec17dbb4b8187f1cbd420e935ffc25bda57d48f697b2da6152223566955ed9527eafa3176da807361060e278aa661990b1fa5c7dccd6d652135ef206604ffa3ae346faabe7d29b8ecedc947198aa81dda3e3503b25fcce3a5c9d8198829765f6e583a10f6a8773c935b35db8a65be9d135f7ec9356d1c44f548d5bd2a1ddab9acd836a0ceba0c98b80a49b904287f35e12e50a7afa364118343b9da614e7f1b46095b2b97c0e54cb483f3c6910d48181bc2173b6715a2ac2a900cad2ea7b09e1d80485de4c496ec5f057c1c324253840ae6949b252b947fc8654bd68f49e415271c2605711380da7984ca6f5b8ecbd095e91f56ac5d354aa0d81b569b37b41cd96f6d085cfb63295c03a905093db1428d6cd28293b4d21655e8d97c8cb146a70ddb14b17857ab1d1f480bb13c6d0ea3fd352bf2536ab6379ba6a8cfb07148f0cc06fbdb66d30aea016d4821465710588a8efb648f54f19424b458e22e9adacac77ecd8b12ce5add521c563721094a170b4523a86871260321da3c65ed43b59e817a326c35b286c13e8988845f53ee71a84058849dcdf90218d3ed80e8454cc6d2fef60ba7b9fe6023ae8c28a82601bd0f7927961f6e49d3a3bcd87b9641d9b6bf80adf7ee254ce7b114d57ac20d34d67bd02fe0ef730cd24e6ec12b128299f88b65658165ea82831176d0418150d1d22746b4c7daf0d42505a98c9aca23aedac4ab0d42e54aa75e77a537df65f7a2bba3edc3587b47df52d4b47cc71202f83dbb982154aef7ed3199a1938cb06a22b07e267a1c92b1347e06444762cf53348107558b1c936171f875e2e32522513d4a9256fc8b67369f94a6d3844500cf5feac4860750e428d537e3d2d1c51b370816ddb2ce0c4864a30f0210ff29745d1ef01842e45b7295e8648e9d9d75ecdf2a88bb5c5820ac1b7b6571b8754408e55a55e0a07469e1d8e189de034b2f7174d704e9cf5f6f8f436fb03dea91143c8c621a83984bd199251814861b90628b7ea510b828031818a7e9e3c7ee9a93353c705cf92ddb1823d749ab016db1e17fe05fe22c2d28ed892eb61a8b3680bf0f8ce110f60f4e63b68d2f78c5a9dccac90f32924a775e62e28b26535dca6dde958273bba0d610bb7ba0909c922d38d7691ae9f2fffdcee54f7829c6d4153f4c191c1f0332201b27f63ca8f31aa7f6bede044aa14c3fd4406e53fe1ac48d15d981e779ae3ab4e5ba39eb05014bc49c37b2bc4b65a01ede5622a587e2c6787fbbe86960d110d7fde548a29117c02289d9bebe43edb5edf672e0a1f4f480a2e9258d748034f416c1e86f3e26d02d41d0cc10078b09625f03110b1a251ac7814571f361b74272bfe8eb9fad1fb25296a59306772f53e1d18ed3a63111897d15c3457078a906a01785742c35aeb3a520bcd5a83fa92b5e2f6706f3aa210eed138f74efeab01e5cb458b81c912b80f6dc88bbb782a0840fb631b9528742818dc49caa2d0bceb3e56e4cf6067b4446d5664798934e91f4e5a34fadf77e629599fcd48e202bd167409f19e35e24f0c5f0edcbb2bf210391ec3aa3bbd765dc939c09bdec50a97e1843d44b6257d81cc6cd4cc552373b821be11b9e091380c5fa41fe5360419843431a26e603b02daadbe3684943a7fdb70a9b0cbc38f403a06b692316cd51c8cef37fefb3917255ea0ee5721405432a099d0fb9e93eae489327225389640ab263845bea2ae85c2c1f2ebc0ae150b7a80c709afbd0a44747fb914a72bf06662f36639edba581a476c481852f710459a6ccc5feacf77cc16ab3c0d3790b6f45f7813f1a34319412e46985f2f424597700a1c2899302d01a9e0ecb1bf2623d8c2de9f38af030f9250e173d80ca5ce83cd158d658d10ee7cbe33808bf12b0718764a024302925f2e8f91a630f9352514140bae355118bc4815f3b7c1281ef210aecee053d6a8b6b9c1d36243cb2354104227cfab6f2e8e2e01571ec87e73e0795483cbca6bb57294e13dba5c2dc8701a61f5d9c5f32e6eb29f8aabceb1667e4f93db1334750f00762148424d90d5b0e91e859fdce1826abcd2db2e1597e26216b228a537c680d06b47a4590caa479f20e3592b6bf010aa851b3208eae09a0a9199cea0dd4664157a315e83a670ec6dcfe0335fa41da2c859c671669b3bad5faada86aed3a5139225267399a7bc54c270e51ea0153cd9c7764af930749f10f27b31107763952af12444fce6e71c0b6979b085060080fc43571e4a1cecaab0a3a7e3455592b08434192ecc8228d1f1195eeaf2043a507d079b5fa70c52964b882d82fee5898cdb33a361f074b6349e38eaf9ae1d6cf1142188f2de58076cd80c73b7d58d6f8479749d382dc8826cd327006d02147e370de0b77fa142e81d870c8feeb7246efd771d6dd81515dcd7f74afb4605c4287cb3d4f4214e2fe76939c35f7788f30b6342107be02798fe0286ab49c537fd1a5e8d171a4f945d722a37296fa8bdc228cc869fa17ba8a3c1ac7945fe82c32520ea97fc955c46839a7fea2dba4e5ec536a58e77107e31fee701bcb51d3ca2ecebfc9b6255c3e6444c4cf96747e7489f0b8d0161713bf919bf837c6ad28f1d7bb8f5a67149c8110e58efdc103b197daf275847a2a785bd08eafc5b4daf40343898366d69caa0c09c66eabd44019df2d70a86627e9924b65764c27d57845a97a8fcafe90c7c47dc0a7f774f31d55f59e77190f1746a184905a6a5658bdc701207c3e76343ceb37891a00e8d5cfe171d82e8885f0b79e78661b27299a59f8fe310ea7a4745da7f478ff16657143911dfea2de9324793ec38da7cabd274a743eec51d9e9ef156bc7d3443071794fe5633c45a88ff2de7325cffdcadb77b452678e278fa44704a6a3b1bc5fa4f878146ac000b483be6702cdc07709ac1c769a7e40f23cb61f9dff372a15dabf7d6dfc0a68fdcffeb54ae80f002963d4c7bc2014dc2ebeeb505c745b220eef651fb36508ef21863821672f52217122179392c21adfaaaf6b7804795888a52c0d7662c4502b66086846098652def19882b62f8c7789e4595121f189739ba602c83e9395d0afbcaac9ae6344abdb81bdfbc2ab8fce4fed6b211b0c0b5b20748e014b47c41ddb0cb8761c18f3e70e1081672ffbec2eabc5870b0e082b947d764a2405d6746e5c5f597f9333fad96717c608526f123f1cca0262dcee70d680abe85f95a2b46bccfcdf5cc05e1a18b5bf220f51ec6412d6833949972f3ae256b768f3c62e490118c9045333263c242a54c6fa424adb7be100e2315af087dfe2fab18f513d852c5c80e18afaaab3b3020d59a303eedd0a5c0288190563efb4a422b8fc4192e09d1888541b441ee388430724e8463073f6e05416dba9571f3500c9d64ab3bf66d7f51f83a11a0ab7de6627c69ce49db22cacac750919b85557d6ad4582a23249e5f3643015ed987bf4774bca8fe6d3e10aa7d013745818cc80f82670d63379fc5bf331d5c4f526f70ab2fb1d4d5384e36b574b7393ff5330bfd062b372a216d15af4e15fdb61aa36b67d19601a762038e73009e6c2feaef1055d3646aeaac2a127ae1ed1ccfb4fceec83713fe8d509621d37b2e832d8bf07c6cb19528f72fdece0c6372e88bfbe4afc023b69a140f798807c4e09cc0c1458d99e3c9500c7b5637bb95ed66746d13b4a13b78b65edc1a5da7beec6e3acc98bf863183f5d988ccad98f67032b59be00130e6a8f250233e672344d2ded64dd681e711ea54b85e93b2703c96461c14ad1005c9dbb18b1eb4aba3e721b196b6e5068f5e915b0f4d4844138c6d587f3daae68312dd973db062cd840d5df25c936689bee51906cb93d0dce5048f7c294e2342d401a555c0f766fd25fcfc0ae9c0d06f3a05cf277ebd274159b6a07a2031eb3b93a5868b79ae6ed2204a66031b75561ee7911fcd54b1b5ca33bb4026a6004039d07e6b8960400f011ba655a797c7420e665f16b18068b5d87a8d96547365878b167505e4ea6c2f60307f77e70641b9fad8691eca3d84a5030d85325b8b69c530b6f645a9d2d1606fbd11d82b42d46ac8988fc34c8e98327790725f6cc157ac551a043ce78208261d2b4d65a8ef7b4181a433690bbeeb0a9e414e024453f85406d8337eb1508d176804a45e4f4b17a56cafa9d067b2ecf7832e7a2890e0ad384b3450962b18e1353a91c837f4ff90328c61bf3b278ef290fc67aca8a810d5d1c792811bae2854903573ecb9ead49c831cc362441049a5e6ba95720d23a4a078d66554806ef59e26a20e33fa78239fb961c1bcf5106fbd513392a27e6319974bd8f08673ec0443bc05088c820064957a0ea2a108ae92a7b52d1d4341e8ae5f2101e2cc462b34412b2f7de5b4a29654a5206ef0a410a5b0a524cf1924a19e50456b007fbdc281b96bf5bad60091695e301a1ce0dbd3b18af9582ae41868ff08fc87ae2cc1362a0408251375060b9e818dba11e0a1ca080f25d7d16a3069ba9974cb50143ca9311f6942f8e3892860043065f6ad0012b5aa9810e31a8810a7290062b32d1c788e4c4103e4656a4619ae0e23b1c6642052427ca2039f1e57de9f2dd13d329f8b81d0f1f4db4e2e3dc1e1f2674233531037a86ed7cf86013909a28e2e93fa42688f86ed56f40a80535a3a786082d76e8f9e109a8bd37a6044b0800ecd1fe5c8b66ec3c51e5882c8060a28b33ac159e636c8175151221c8c61696c94b1e045f03aacaab2e397ce66a777794eeeef6876d8ec9ecaa861e725ded34f89b3317e1c669fe3676cc376ee859c33c9325e69247321f573cb964fd4385a43a64094156e698c3072463c19bba623b545dedf460dc7b39d973c13e2420aafcea1fd20f29f8ec79c03e2420b2fc43faa1cbebfc43fae1cb738f27023fd0000df04a7862ccc008fc208500cf9f8d89315ed7550d7995ebea57975ca75d9c068545fa8186cf58a3dceca19f17a6c1f934eaf51b19134714455184293868fc600bebed80d539f0b4449928ac28b31db0a40f71905b5132ec659e29e0624409b189ea397cd58fe99f9bdee804ec41f18d88da6de73c6666d8c5162946909028f30f090ec2cc1d91e88284c417e642062e6dbcc7850c5806135c9828838465078e84c4130f91d8f290ddf9f956e8167aea05d97b527ae7bc1970619ff315b6e61fd2113db8ff905a0083fa8e20e3530f21e4242222a21872c01b585f3ca0bde8a4feb63d4477f7ca28a05f37a4d24663be3db6d728be3d95e58a6f771fbe7d058330be7d0706dfee1383207c7b0a51bedd48956f4f22c5432a22e9db9750f9f617ae88628416538278242d678ef092250b9439d4fc63218a72c072062906a31f581d241ac2d2c316371c21850561706175300a84e33b3845084a7c89d5c129be00f33de6fb850b7fd41853041bb670f1441a5643207c03f1ddbe812496d5ddc2161bd62082123eb01186d58fb505f562a1e7b0a2478f1e77ca640fa78b667eb6a51c64a5fa87ca08146aa43efec25d3a677ae56655c33ed6e32e49d3f9cb741633a3195e02568110da964d842061165653987e45cf7c889555ce4775a40af5d4bb6364d5397d84fa7585b02317f562dbb9a7d3cea5308e72fe742ece5ef7512e35c4a27405167a52e416f3ae13b04822db813203b0104262b14de6d4ec76768cc2d87081c50958e060b3033d018b19e80ef4842b885077a02af8a490f2955115dfed24f11549fd832bbcd8ae1003001d20f0c08c36b6c040891b58ec061a2f443c89018d317050567b2913767a4ee0fe2119b5e0a97c87d346c69e317cced079b2fade8561d77bfcde711e7e16b52a26e52b44d1220aebb9edf149c1c8c459edf4304f06c9ac10df75b93f1ecfb2cce516242bb2b9e6fe7836bf4cf7c2f92d08d4b61fcdb7feb85d9e71f0336e870bd2c4045e55efe155f55755b829c82058ec76b215f95fd205290fbc679472b6ac32c68eb11f6468c60c1a1c62ea86dd97fbbf7ec61d99f9548afc318836c4289a90a845b851e07dece05f57f5d3c57895688fa37a4f07ba4a08fd1ffe78e19066b69c7a3af04529a5bcb566cb9ac32c1761c5a367c0c206cef55eb529a3d4111c1026b13d3193e5c50dce378f6f7f3ff81e82d54454f926a28def1750f18d85876faff179eba1865c4601aa0e57244308655c30368cd606431081e4021f5c0852baaee297ec3dafcfedf3d4f3e793f594f147a2404267789bcc9f08b7983d9650368cecc40de20c1367ced49854f79f159f9990b89cb940d6ef4d202cf48efbe8cf549f59ae2083ccf299b97eee2033333333430db2fb700ef996237c5172d7de576c8e99991932333333df54852fde0a5110a2207c37055110055110055115f2336354b20724be077178f5babbbb6baabb3b6a61e1db8272a14d50262817da04658272a14d5026bebcf7e5eb63d7e37c6149b13492c01e54ab5add8248af5c254d2189176e2f1c4c825d5248f2c4c879554abf6767d5396f05eff7783678d8e5e570f17e848073f88df1e2d93b0463562de52a6e3fb71946ecb3aad1262c139672b934db3fb44c4bf1ea99e9f20ceb45b72558d8fef21836e5f428a416f6791cfdde7bef598df9458e02d51a5fe8037d3db38e12eb34aec4f070604228e29c576528dac119964c9198ec552bc5772979542f1c221652d196cc6afce31d1565b9e43fa4221bc2541bcd0427d651b6665376a791844883c5222e9cf3a6f001428bb72035f5c4523445b0f41f92106f7ca7656173fc431202cc03c17ef94ed350111082528c6d3efe4312c206990368698810024208a2610cdd907bf6d7022e86c83229d6775e0e7bcf97a32e417abc1ecf6e64852423c8eaabcdd227a573c246ba7420324ab1804c113a5cd031242e532071f17244c506882f58c09c5982868885814449d51fd2952a56bc14d1c5173fd80229e90b761b861756b4f07164258bcb8261e50af8b4b082c51192952abe5bc536e01556b89e708519e90a1b4857aef8fa0fe90a15ed907b3694530924259a434a220e9a84994735978324cc7894945262d9d196946994529a6944605c34edbaae4bdb92c021092b9bd6485b84f0a852c603d3298cd54aa552c99ebacc2fa78b8a1aad91b624f1281bad660b525353b30d81ffdc06a5f543da6205698b11260d8dec3deee6f7861a3a9149d0cc777bb7741f2653df5b45d853beb7f0aa8de05567793a479a25893aa7e3c726236f9381672094493f9044789144e460091f6880a2418a09a2879e628248be8048be604e08a44993e661908cc17465ca43375d99b284bb5bb162454c10c5e16a50580a04058202814352fbf01043984396f4cfc1e0745df4d5b39efef15818c6fe4a808fd40395cfa2d6d33f5416a83ec4f2e73e29185921c903023d4a0a12d96766043f9eea881616308f6ef2c752194139ea0a4196f5215645790e47790ecf502647b9c951350e59356ef20c6e44aa2339fce439fc74554820ebe49075f21c5a6cf507dfe7cc574781796abe7a0a347c75159847f5d58d8cf9da53be7af59e52bdab44f95a5d09ff6056b5c207455f3d04ccc34152c0415647e9c1576f1c9847005f4d9e831b62e5b88ca673aaaba4c3476241eb83d8c43551e7541733c4cae03684be7dc3da78e7466c2091897bc1c66daa9bee083aa79a92744e8dd79f4e74eee944b735a9d3551d31b9757b854cf7f9744e75fb3af5d031f7a95ead1593aad97dbcca9274ad0c80ebd60bd494b1310f0e2c617091420d334f887ce083106bcc90c31a3198410ed88da11a72a0e10d2e55ec5085053de001e68a35d2a0f2858d2bb88d36de083460e0450a1b7af0c11b475c20430fa62053e6881a98797001eac6ff21f120c363ff90a8a0f90e4707d99cf276ec19739a33107b4c4db1508b9db55ab31229da51a8a0fe6ec8cc32f6bb95ccf4569eaa91a977757a4629adb5522965aa73e69cdd1da31d638a796b7c7f7140ca15f07eacd0393d7d095da17f40345e0e7408bfacf15007f0f6abd5deaeba503faac01275b11dfcd2031a2258421fc11e6dc5d369ef302abe93454fbe5962ffe6ed2ed08a0704dede2283376629ea1f718997b305f6686fc9c43792b5e9242aba3879f8ee2eb08408c6ebd1dede5b3c20518ca7d3de5f3c9d1663bb566bb37c3b19d0dbc7835f600939af07f496de33489900bfc4d592fee129345405b646397bf87e3acc6a9f1715feaaf41a3915bc46e6cd2819708a5d41e89c05859251a66e5a0754bd372886de0d5bf26861617d3a70e537d46f523b4f07facd152f1df563d80e156b804e6db037505ec6d455fdb4e4efee55e7402ae97509bf39d5f4f6a1cfa4949076aa1f7e1502350596ffb5aa6c4e6088d09f0779fe3a219590e7d1819ec7f83eccefad1ef45e227b105ed7e5d3218410c20b5e375e8dadc8b2cc6ad9834e53f887c2bfe98fa3f276cc02ce03f173e622b732cc6987f38d9aae39bd9d3f1582ed78f0d8d97c528f3edd044a7de39e3f1dea26ce720ea5f4c2c7416898b7d769810bf3d4cf572d0c3dc40cb364eed7095b679797380debf9f627fb49c4ae9965ae790a6b0f8a9cc59bab572cadd5da7a1f4334eab322a883baa7c63071702a8f5e451ffa88735df18a31c618239c1ccae2ac78cc297de8e58edcd9611e99edc81de61902a57c46b8a1772939857a667a37686c75769ce9113a140ba9402a900aa4428579824c0885776eb47ac93cf40b5efdf5aa88c49f1e7fde15af5eae98673a672be65931cf90c90d79bf81e93eccad30e79297d16350b4686c57bf68b61cd50f4d3f3468ba7b5eb34fec0f4300028717a2b9dc84bcd0ed7ca7c4c7c7232b6c60dbb0c622e646b2b771d10c16cd74438f61905b618525cc63e4c806ba77e69969add6a652b44bd1548a7647253d2fb3d4141b79c5fc8385bee5522263d8d1637cc8b503f5ad430d610e59fbc7ce7e447d4c79bcdd8c5e32954c5b69731ffed2a6719a45a56e5ec68e1c6b09721c381d8ef6d3a27ea6dea9c7cb5d9dbe95b6d20dead7367f69d26cda45659c3f1d3a84d48d104a218e1f51a3d8ce1d277503a7a4d2499a3469babc0c923f7768a8324cf769d71e9016a06724fc2479da09e977e408afa411d84920e92caf0f0abc823ebcfa79406e7e9c37ebccbd277accb9e8db5544d8a7d778b764c70e54f2f974a833c76ee3bc1569b7cedce6373e1dbea7d35b9166975b11ccb31a0da7661332f4a61f76aab27ef2f75de3365cc7a988605ee398d75c957515919397fce4252f7906b51b9f0edd28ca7b0a40f98933b9e5840cbde9e27025ce7240da4d615eb91e5e65b71b7aec9a70537a40201398a65b55b6740bf0e926a4371586aa6067a74fe9e57dd1d9a47ad02c92e4a900984080c7f4f9f77874ae3d1de8702b3d9de705e6261f2b1e9eba61cbdae381d5b1dbfd38dc8a54c79c624ecf74d1cccb7981a8476f4cfa6be7544ff58e84e95d9c3d3b9863985fd18d1bddf0e7c3b38a2cfa68923665e69cd3a7c733cc037d07f3f6fabefa3353f4b21a0e7a4fc9641de32018688583556012afa4f487e5c0dc80259cbc7778a7c3690ea73c9ee2504a294539a514db81aa8e79a7034529a59452ccab77392eaa2b1747b05a3d32b046335986f97cb18c241a1a020c68befdc532593c532b566b3cc33c350eab77f10c74d8f506317c5f3c2fd0c70ef364177e81c35af99593df5ccfd7f9cdbda39f5c67829f59b350f558a6c6217cd75c2bf55886c633df05713fd0bcb622d985cdde26af25afb78b66be9d07ce4d8dd55fbd134ed31503e6cb17495e6a67424fe748ef7c7c9dde984fc74a37887f367329c232b0c7ac0edd72a69ed4773dccf5744f4f6a6ad9e46c1c665efb47b535b7b3d8ed525c579fd5b80d3333f3c6b7b3f10b74f9e6d855e35dfce91a57af0bc3a2998f9fd19dabafcbe305ddfab0a9b4d570a6d2567371f2ada9b461527ec764525cae27580c7fe1a42ed3b1eb7627b0e1a04737600fe817d799e02fb7a6d25633db8adf9029d3547eba6d1c7ebaa9a1fcf4d2107efa76e6a7d798e0a31bb0c71c41ff8055ba95f5ce5332fe74123425a6773b0fb11a54c63111af3086c239d38df8e9a7dbad88702b3fa9fcf492641c7efa762516dde8ba9daf3df628d5e5cb55e2e3b1cbc7675e2f76812ea75eeb75ebede2cfebf217dd8866ae1addb081393314bdf6d1897ef5af45e5cb533769d80ff5e9d2e3f438247abd40b30807d3f0ab2961a36bdceb1c392717ea40f54aeaf3568e9b0a2a2d2a55becad7d60e51bec503996fc0bf160f5b6689524ae190ea5abc1c6e81694032cc7339f5de82d02acf7186b2e1763a677ac6f5f06aa67835dd6b8c31be18a95fd02f1a63918c524a23a544aac78da35c6a52280652ae73f299532ebb5d1ca239f518219aa1333054875ba098ec96f16926b4ba0f5f9783d4218499438765984773e89977b0cc43186f107f855ee1e5e80f6ca7fdd42c8a79665635a75c67b983417fe6bbe854bb2e28a6c2329749fb30dd8a649b47cfbc3bf955e3d7ed9ebdb7abb941dc9dee95451bbfbc0b8a1be4e6528782eacdc5f761eaedd31a77e1165e994c5bb55e4db56e9b577b3b083522986f5773936797d6e261ca675f2199af9f55cdc4512fd12e88fdc2bcfa9539f5ecd24c5cf5124c0396e0b53ae4c23c2468516024685104a9eec318a7d5aba1b22f9a76bb13609c7cf95ec47431f2e504266ef3124c03f630c1633e93b66bc64b92cea14ec604300de7af545382832d5e5147713e9d435de3e2db70afa94f98062ca1de0e05efd704aa67f7e2ba8a610eb7c020900c76d5231d4d64ed90d580ed9521acb1ded884df0501fcf4f60ebe0b02f89640d61c90746d7a877530bc4a02fdd3fba7efbc0492ce60b67cbd5d74e95a14567a77bb27e688efa2774d01e93db05d3b36bdb7145ebc4df8a343cc1b07dfc121f0e577f1edcfdb0d6d299c5efa5421b95898c3673558983712ba467c3550819c390831ff6d481e00a50afa6f4312d330220debdf86a4871833b8fe6d481a4c1b3b60ff3624042842228bec0c521936d0fe6d480090c30ba808b1f110507cb04515a57f1b121d61927842c6f46f4302d540820d2c3ea449c3c2bc39fb6f437242230c96223c11d2221d731f6eeef493ebba0991f8d3a5ab84c86702d3a88c3c812c8c490532c7bc497699bc64cab8ad84952ece68739f2e0119655b86b989cb7cdb7c724c824c2642808c4cfe5e890b6f945debd57d5abedf3cc35826c72e11d6447a052c4724baf04698676f43724b44186bbbd80aa54b84b1ac679708931c11057c67b4b992212b6f846dbe79c695dc722647624d9e5da2378655721f9eddf51b4581000b051d64783aef3b2e01051d885e0f88714298cf30c43a39d152cab2cc88e63e5c847a0349ec124916c6196143ffb621404698bf5782823722922c4c4792b753f2cd854a177b2fc9fa636dde59d3e96a91ee9d0b36afe46d81c5064f1da89dba9096d739f60a2db944fe90a820fa0c690a343f6f1091fe76176a5e492433836f5939264634279a97b620469ecc30c4a241cb8c5b1108fc9cd04ce7b09b3838a573b873322f253d7be90ac1a44ccc119624a608164df10e1609cd30c4d222ddb5246cdf16ded1337969be7b437820176c5e890098182309212d473637b9e90af176cdf86395c47cc744db85c22ccc1f2bbbaf7380fa020d19fa761750afe4022e98e1db1fd21a5f3609b497c0cb7725e0e2fb094703b3b2ab6acf1c4a955c931ffe760b3c56c6c494e6dba11b45f1c1c23ce3b04bc5f33a5419f52583dec130743d74785978636e0743abf11d3f3ae4da2347a487183348d3093db3e04a869230f37d5970ee8420bc46348bdd09229012a187ad294f7cd01bb324f635127a0e8329298b0c8858f43285af934230c64c90a43c3043dd78538091828d7fdb1019a628537760fa8724851852e4208b4c0a2f2dc4163e93f1e93c87dd49220e24afbdf50179f1e93094285aafb1ef548ee199a1f8123393a625c0ca081465bdab4efa24d24450d0c98f0e2449d02e91024f1d8707bd360c51ad3bad1becf0d7bfd60db43411b23a80561759fef4afd5450fffb621150d3a9d527a83123fbdddbc4a709efa4dea8d2ef893522d9e525acf401d3da5ee8f7aad67bca359656cc6a93edbbf56d2126ffad74a22429e014605638a242a3688f2efd4aa819937bff2aabd846f1fefc7fb6121f0ed19c717b36ee6a9bdc56b16b6f3f13e4c50e1018144df90630f50837de3d3810c9ff6b2a8b63eae1ca5e428a5acd563adb2ba740833c83c902133330c82fcd5b9c9d095ca45e5c242af15d25abdbbea75e3c725a07c10ca18bb2594af6e516b2da1328147b08aa7cec305d9c3c9e4cdbb425aa0cffb3aa76385517a0f0969810e03c6920c253bfca979962de3e412df1b5ea5b4c83cd11d75c3636311e0c5e1d52a084e848f4b9d93f78a85edde918f659e19637f5fe7155b148e75e6691f3c82f8f015dc7c9e0ef7f08a1dc7ab16d684e83e2caae38deae776ab2e1616d1f9792918ecdd8aa288f987ac535876e880e8dd1e39d8108cecf943f134b1afb585e8db6109d09fd62f3a37793001fd74a037793001fc74a007d56fc2650adc32e5e94ccdc629d2dbbba1efe44fae89cfbb30ff27d724c9bba07d3b8c0f0e791a8dd78eb9e281ed98a99b98caa645596d8b5e8a3b3e78c53ba87f95df3bcf8fd0ccbb50f3d2dfedda630dc3c6efeea207614944df2e8f883ec621ef07b4c836e48129226fbcdb4fa321ea5e17a2ef27889e49a321fabe40b61ed8831dc95e29c82cf34ccfde53ee89496a2161464bdda43e9aa845cd128494fe6497b66d58493355dbe3a4d509a646abcc33ab183ab41c5b107aab15368746dde4383a34d38a475c31062f0861a510c219a13c52ea3a90d54842573df2aa86612750bc5d04aadb4fb436568dbaac343b693e2c5074ea9d8f19ebadbce20ae6eb7d4f27bb472f7fa4669fc7a734e35e8c5e4df01d7517fa8d4ea0b911ed3e19223a2a9a12058a0c3110b19e4079608e60c10d57a4f1c61360589a4311f2000fe0888308454853851896e64230a4a9e20c1ec0110711d2b066b82b88d272c10aa2b45cc0d27c86219676dfcbc9a84f8eeba8d7237b39e6d865f2c203c55ac49fba0f23b1e58179db901b5dde2e8d91655e618cf70259641e20e92f46248a9e5d2231e5d99b9b3ba1398122ecd3250704d961f51686be5eb8d5dbcd14eacf4bdda7af6f2026ce847a0b434f857eba0f0f4d00fa24d29d02fc76224db4dc0834ce0334227abfaa3d7eff6be3557d3130c49afe5a3558e35fab06639efac0ae9ebc3468e6a5425e1fae31b0b09f937f4270a89bb3a851b47b53f9f6d50e6c04114f072c6068748dfa7bd5e5d55f46027bb36b11f3edd1db598b996fc788be7d7a3b4b2c5eb0f8f04fca8979bd1d567dce5ae7acb3ce8741ace86b5c73861a97e605d2bc26d6b46b9aa6695ee33e9c3d2d6e3ff51aed701a69dc8ca2386bc785016369a17e056125d853af4d8b5c981f31e531cfbca963dc7529f7833d360401d7dbb8845da0ccb12ea3b03b98f9dc7e32c73c3b6d3fedd36bb8396be664ed629b05dc09208e08430d2ab02862bd97436f87393bb6fdd407c2bcfdda86601be618c64587dcdb90e8708bce9dddee7ae99b67282ebe60ccc9a5d32fbee4da0bce7cc927968d03cae1dbacf1c975d1338f5b11ea9a5f9b90e8d52ba72272f2e8278f57a5c3515ef28cb7a8c323d074a0936fb5cee8efafb71529f9bc2a1d57c8d05b57fda0fce4283ff969c8d05bdf38e9912b79763be926dfd13bb81c5c6ac575435f737bb88be270b81d5e5da0d2769d5724c02081a1a18fb793db10ea926af272a17702c335f4bc614d9840e0352af4d54d9a93af97bf0c86afd8bd86bede4e6af4fae015e4b68e62af7fad228afe421d7cca8188a47fad21aa7cf778d01a22cb67a6e7fc63ebceb30e647678a7f20698a733210523ad21889e3d042b8cc004145e133f3695f2b77ade57b68ae0529db09bc6ab777dc88a887253c6668c121738ff5a3130e211878f2c5a3058e2841f9050864d97284a4668453a6481f2864d18ad8b11a020418896119416963224d42f2cf4d40e1d395036352394e414358c2a468d2d2c54a294123eeaa5cca84718a78411a749d094e82804f540d0519fbd0744e7abfb70163d767d3b18a2c64ea6cf306f0d106055af909b11b6ae302123bd443e6fd71d35cacd792b0a4eea03db550440f761aa83f78424e0a13f7f3ca2f4c975432ffbca16861cbaf46e97724aee7d4f2963ec66869b10d85748029e086f5c0723f4068a2e04327b15d9f3223042bcf119e790ca81af40797edc8b3ec4cb17deeb85a2a543e1d02a2ad34f9ee8d57835af58f92ec5d622bbc976e0d6bab245fbb9e14a96df4050c59397264d9a0c3ae41e2ca9c15b186dca226a52c8e8c46c2eb41a514b2db8341d3021a2c0561043789414218820ff88bcb10d6422af4ad501edec75c551bc20e07966e21a2cdcc108cb34b45a30e56b4de93d1d213804991a91a19a0c79d19f1402e385134e87504a19f98d27c01cf1f17a66fab1d05f2b0830ff8424e0e79432c66ee6788524e089cc4d48029e2f13a1ef7ab6845d844c051b5f7c2d20acd07fad20be680581f428d80a82cba378c544ad2088f8e627dfdd0a82cae3704e0bf12ea871ee8a082f720c6334fe71c339d429bda1f56a94a27845d3d82ef5347593848660044b825ea0443c6859f9e25f0b882dbf807f2d208e3ef20f947c6f346fe41ce9f2c61c6c472223cc73640558027ce94942c03c90875d3a8c0284111f9dc304617d78f556d267eaba6e576bbc9db5a9d4bc47f2762b66c89021a77e681ce1c32220d88659ba14b511349a914eb720f3aa88b44f1fd23eeff4f1b17b4eb138f42f88b97e0f877f54e1950a8eb8700ed35a83ed60111f75619ee83c1d17f804f34c2e13e2005fe81c3e595ec1a7c31e75b05dc3882fd620a2995826c201b79079ad1e8640a60cdc42868c1a37291ef45aad5e1cb9f792bb6fa7640bea7784e39199c0e7f72e18c384617a91eb1739cad8cd31cb20c7662d882791faee8567ef6caada60eb7f85444e9434ac4e198298d5192cd4b4db05c5d5d35cbb38676c87533b47d36e97b2d235cde116ef2d8836f9043e218ab3daa35b3ea13d7aea26abd3e9a642d26e720bb49beed097ae0a09bbc92dc06ef2a1d7b8cebe962d79cc37cec4bdced13c636c7a1614ffdaf2d3c298710442f6703a1b67f8e7468e7c68106c7d22b85c731f8cd58c1d87e37b7e5dae7906294ff88010e0e9b447ef4d939059dbf9b18a2b5e3510a6300e2043b0ddea1be7e9b457ed76ecd44b5ce51cf6cd7dfa0a8a9fd1abb3a01851175fccf5d959c4152b9d7d151f10f874a0f38f239006eb83420a2a2409c10894304fa724f57e6405e6e9ae167f72b1e260bb77c4734395a282121c79856055f041c1486c29031fc83cf5968fabb700bf7ad65f9eba8a4874dfe1b4cb531f9ed0e1dd45b7698fdcc559efa2d72d880e8f5c0e6f4e3339e6f1b263db10ea28af1cca6b38200c73eb254ea5c3df97fc3af9c5a97478c9dfa776786a8763bee3aa74b88a48c94f9b0e8f1c6a04afae831332f425cfc17599574771361c508d53af351c50f5d3b5bcaa3d25a0eca6ae09bc92f5def02a722d90f003c57ce4ded3a9f1bec7a3fadc8a50efe83e2d447b3ab2f474a437e78357d2a5c328d21f8e44f12c834990f6c9c4039811f938c87510c62a48181202be57ebbac2d6d8b47070f4d0377f5b91ebe630699ae635395c3b6d9a8d3f376d9b437f36db96656cdab82ef34d336d3b4532df5c85ba4554d92df29ecea5993cf3cc847135b73b6d2d1c6c69e1200b36746dbe4307ca2faef3bf4a2dbccd4fde596cebac5747655b4d069439f41c1cf4ab5e8edd6ec96335288b5d548d3fbf6cfcba3ceb29954a289f156a7314ca33579534238f393fdeaeab509777491ef352e976aaedfe5cce362a2235eee46bdc4d8e71a70b9f67455c88ccdab5c57c66aea32557ab44833a24eb5a3367efce72976faa222737d552e68fd3b86ec96baad2fd395dd37d4fa76aa6776daf5bf21ad7f5d5aa89eb96bcc9b3f77eec7839d2df8f6ecd6b97e4db977cf5ccbab6dd6ec9b756b993679cc977bc1ff2e5b4cb2539dc8773dcae3aaadea048eda3b8ea355755331b2e88bf2f7f03cda0f874baac7f5961bbae5e56d8be5d10fcdb75dda999573fb9a9bb5e15e69a57cf7cabbef39b67aebaee8f3f9ddeecbb7cf3da98b7a65d5556abe6ed15f3cb5f0d8a964d0f7dbae58c07b8092992f98575f63bd5f4c92e1566c48b5c5e337fbfe4a9c530e84bf8aab2fb73dd771f757855f3fec40b5ff5e9cf0c197e5faeeadd1fcdb7cbe3c6bda7835d5575d535affba3aad255b555f5fe2459d21ee7f5793acfe535f274de916a3af5ccaf2ea5a2f707735655d5745575eaed5755ef8f6ade9ff774da55d3773ad2dba9e6fd71f913efbb762883d5cc85c585c585c585c54e26e5bac2c635cec64f7404ea278e044fe5f0d3c9ea50e5b83ff69e9c9e4ea713d72df993f326a488759453afd1e135394e39ae4ac7fdb1d4514bbec6e9ed963cf593fbf489db810a8a7f72558d6b4eef09e5d651235c575c57a05040238c80b2b149ed088ad715355cb744a55af25d507c94d77856c30105c547456bad0d8abe044b02c3b0a8a427a9e769c63cf887f5d59b9c52b71c0fcea9f7f1aa9aaedfcab5d6d8b7b2c6dd253baaf5763ebed66a6d2ae5d7e58565795d927d8064be25d7c93b6b602f8f3eb9c5403b5d900f69c43c46a718c5b0cb24c943a759c5710ca310c26ee753bcba280b3e1e483abd9eb01aafa2c7962263e19179a0e99e79fd0c6a357a7d6b7cccae9247a67273660fcd5732dfc128d7179b719a12765e578bf33e161e998f57c943f37d2bb3131714a9542c80b5f1fa99a95a3f797bca6b5b1bb78e61369a635cfdb711f9f1dadb84d85c253dbfb9e6189759cf4e5cd7f327ef7cfcc957cc63e327f7fe91638777a93f79ed1f23f8c95fffd0e127e781f9c947e05ee7a4b8d73bb8d7281f8fb90e2e07e79d83b90db7e295f513a7e4253de6f6865798bd295e616ee278f00af312b7c3b1d0f35a7651bcc23807f3197d3c76a94313c7277548d7b3b3fa76fb9b5bceb9d24df58fedd6cea13eaf3be94e64c886a59e0694f77f2d34c69c4ea7d3c99ff519bdd36c972a5da8d641935b1b454a157b9252e514454a15d8a548f627ae837f3ad99a9ee25754d238f306f8d74ac30b1a5ffc08ff5a62a4f8ac356944ccd2447011fc78a19528b084f714563939f458ba91a23c7635ae79677d8b6ee21effb07193a32e8fccdde5d16db8c7268dd3de9e384d096bbd3b7985f6029d6e07dfe456f34ea3251ca0145882e90295dc7ae9024d976fe105e92d55f958ba500aec11a38c34baf4254a521348fa65b5b736ed761362aa907bd6724636d19ab8c7ab68c3556ba14dac55a4549152454a152955a444cba72a2360d837e6df61d8e3c18f3d9dbe52603b1edfce63c7478f0929cb0e8f8ec884c0df61db73643a72d8e76fa26c6ade7befbdf7b8e832953a59d5bcf5ed77f5c2c1a69159fb6266b250b4707b2a8bf08df89660803027dbec8b3ea42f1e2ce4c7498eaf917c5bd4e0a81d799545eebddbc669d781edb4ac35f8d8a1bfa31abaa8e06502f986afd1f48e4659179910f8d829bbec8b991822feb5ce18f309f8d73ae3cc370bac45a56e9887cb43ae09fc6b74f9510d5d3e839a51b550b454c766d2e3895eedd67e71aa76f8927190515bcaa685a2055e1a9c55b901ec3c629af817b33392f871a682182e12a7d6e8a3d654aa8f5cb63e32285d6e1368a77436a5153e2e6d4386e6aba720395409f3c4c3bfa8d8ce3e84337808c36c79c884e395038453761bd98b1042080d60a13fc84456029b3dd52ac7445020bfbd72dc24a87e50eaef75f1f613e1b310b6fdf40468b66d937590417f7ec6d1bf5698a3ef4cffc224fddb7e26bcd03e0864cf46678e5f0bd1044fffea2581c07e78e34644c8106f42a2b3f304d8dbe746a4037fb9ccf4d1d9e91624f20dd414bb18bd259d52d22965bcb6f95a8185344479e970e96026d3ba9e7f9569d6d12b54224c0ca615dabc3746b5b61059bc6a78648461412b0c1051de1b8afad70af3447691c57e4450dc80451262bc11052b26f1d153644433c210000e1cec6089379a10830c2158b106af59c147b565469616182eb0230a29a820b92b3fb460082a7ee0031747acdef27db49381325318218311645c9185d5570db1fb88ddb2a7d88c193ca7bcfe6b99f1c477383b6e5c3440f99e151c6024be5b7d0ac2231eee402c021043186fb8008b1727aa18bac1063031c881941a640083b5b4c01051e1a839580307d3e10fb9946f0a117d735d19533c24faedc74ae6e11ffec3592a3c205dc61175540a8a964e2be2dff6539f7cbcad5619443cd12a838be3c48be891f1c5185dbeb4c620d31a03cd992a2d32c6b4cac8a1550614385a6140c1d202e34c2b8c2bad30a888d1fad70ac388ef6e5e8631c5dfb8741ce92be93c3c324f94db0dafa4bf60a5bc64b09db53b4dbcf72630f4aaee8901af504ae950144e905183278e580194385c20860e19e59046d1186068b0022c171b454873e5cb974ae6226a8151c667ff5a6024a501a3e8eb3f325aff5a6444f9ee756991f1ce45cf2c0483f1c473dd86d4fa784c576125b7e93eb0afe50598ef5e1777017ed73f9dda1e8ee5edf1c2cb47e7871b11f8f1eeb061a7b777abe7f7def3312107d67f4e8eded1bbbbfb49d67e415e75511fc1a2f6eaf4763db7db59790abb9d850d7bba9e9dd50a9ae99c4ea5ee108b81bc6f20e890c889becab7901678bbcad369e7299d939414bde9b393400489a00b4122964f10fda962874441f4e7050ac297c94bf3ec2ed0e7db0dd57000fb588f256f13b13a080f3d0ae13905e641018c1edeaacb0342795000c387b77adf3d2efe08fb82f0efd98012042f69788043872eacf69efed1eed33f5e1443cdc82cb76c7de9d212e38b34a816b4beac41e34591b13645ffb5bce8c18b28378c8172cc9831eccfbb15e64e38275a8e602677c29b43cca31fc13cb2637edd239af71582aef9e625bed6760e753759e0b1a0f00e3fb9909623d6775817d2724487b343e8991f819e650efdba47526eba42264f5d235748cb9193c32bb4d339d44fbee39eae1014e22be49d435dc77d9d1339acc46d57488baa02d1313f122f17ba47dab52b04a7740e855748cb11eaecd811ccf90a41a23b4548cb11e87c8584b41c6187576873be42257f9d53bd66c7c61f2b873f1644f963e5e084b41839b90de72473cc6b38275a8e641716750e75cc9d68459caebd1d2c7aea1a177b9d0869b1c06349ec3a91fe581ddcf2d44d5c3bc63d960685fd03f6b5ba40f3d9e3c725239591e91678ace948204bb24c43cdf408332a189c1e253d65b0d04306945dc928021d76bf377768b1ab18aa69b4d5c51301a512a8244d55b5458e32453323000040004314000028140c0744e28050342416e6e50314000b94a0446a489988c32cc751903208196308210680000800ccd0482b003e6198211cc71749f0ef63aacb2da0a52367bd48667896351781ab0237eea66ce75f2237adfe1844347e9c71c8481993d92f76a471eadc39f11f4f9409c3a8a64c8c6b65e7509b3511596dbffe2ede8c40b4883bbcd3440085908fc3d1a067c3260e039346df1ade95aaf0256bef702024eecf2ada2d6088647f7ba5b351a3eacc095d07ef27b1f0c872214190022c15e94dc711cc18afed05cbd444dfb9976be15dd0104d1f13f3e55a20b81a71543c552e168996b0aaae7b908c90e430181da5930bd716190bfd4710a1761001eab7b63e24f1397161e6a21c3b1c66304c0453122a4eb59dd45b054a99712ba430b7bcd72f12f342581a5cd1f658a8da127de9a396ffa16655400e4d405de91356d692df908deaf03eb3d46f0f97700f537c1e7ebb49fc2ebcdbb50f1060097354e873cedac64ad5ddb324c5b791539d8f7b1682dc5e50375a7c67119452149e15d2e6af0383c618ce5c245f2636907d7061b98d4449aeb66a92b7bbe0a88560202dae977a6d74b187917b47c5372f35b299d581efbec967c1314b07f23cd7c2c4c67e530c5188f79423fc5d26466d44ec42df11a40ea8929e526a06da7d81f65ea692cb2460aab8c596ef215ae8484af8c92e506c9d58131bf7f5f491e341f3271844fd8346b834bcfeee39143d15875fb5a0a765ea5bf9affa743822d69628867a9193535ab8f9e2e0a08aba57fa0ccca94f0be49816a294e7669e05843e1d2d3975cabda72ca70fb14d43eb37f4a410a1b4af2dfbd249f15718dc310883ff2c5833a518f841da1bec9ca33537c7238c60ab2a9513069542179b79255f6690fed842241b08e598a3d583030b91fa54173d25bab0c6590806472d7889cc0d5b55c5b0ee06333c2a8af6d4823ee16ae100cc89790a880ef703906ac112373e66a552806aa1839a034d6b218ead6de6720d0bfe892fb3f6e7af765a0b2ad7b49b850e639093e72c44ffd530101b5344ae0517b96470972a2e5c0b947dc9808a80856c082cd8e7eb0a03168abb58eab04fd01320281615a89b458568e5afea21ab6b40a7fd88c1c8c5b900887f58ee1a0aab0c759f8acaa02797cc38e2cd9d969ad4fc1e84b13517d6d096aa13ae39e269278e0c238b907bdfb2b4a24152ada34fd1ddbccd79854ea32979e0cf91454de7b05d731e698bb97b0883c10e4bb003a5d54beae365e414ec808d4eb6efbbaf954a602e0a364a4b684c1fe2ba393130ffe8b51a5c3da245d2475a5f5cc3786158e164702edb6991766681f7ff97e942362552f512c4c4777c48946ab3e51c27352613d3fa407148d467faf5d51c639a45eb617fa26184b055a999ec95f5a80444fa0427fd298b8e86063d8f7290c3b64dc52e4fe105f888947423f02d73cdfeb0e853f66b9103beb067af7eb737c5afeb90ae4499b64b80b2c7cef5847ed27281ad1c0d4f3d0fa141a407ffa07f66d42b562019e04fdf27629e4d47f5687cbb61bc2856af50f0c2d530f3706596084a3835f51a48d36fb8f4652325ddf37250e91f63dc199bcaae96a45f331b0e437f77df7cbe2290d4664f1be4e74802580e4347aba8d350df210c493f7be3991fa72d41b387f1af65d3150b1ffa3362a1e5302e1d7ea1ace4c340fdf78b5f0ee93060350e1996e9a17b25408ce1874fb565def3301c65c5492882b5d48e6977de87e32330040e5b2cf33b273105154d86e3b97b57762f1cf8e73b524c6985d34df7a22d303bc5de3ed1c04353416d76c16c680fc54bab26594493ae005a58b426764b51e51f1c31ef034b9232242a92d0ab6283aa4fa5858938fce22b6f92597ea9b81a88439905bdbbebe26c3b8007453d83807656d857fbc84f774d911883b0b6c9aecdc710cd464da2615c0f642234505e9848218c643fbfff6aea68bfc379d85b2b5854294c07619ba95d2563db06dcb29b9ed8d9844130775e670741b03efe7857bbefc7b5204cab35775e417e1c40f492861b409b67f9fc94ddcb2023810ec10cde1f3acb8080be011897dcbc4ce109d5dfdfcb05dc8dc66406653c4c8658803d3493046883e39d9c702479da8a4281a4005c3ff912001bf6a23da60b00705be1bacb76fdafb5c5d83f525b509d0a280d21f79420a91b8bd6f03a497322063d011e9d1a45db4096bbc36e3364d7a606fa9542caf7c099f636f72274ae783818611883927a980d52c6188710d1d2e1d7916b12ed3ae9b7c3c3917f0306711729c59cfdc9a8080b51b8cadf50b4562e44b541f0f16c5e5669000921f0ab994942b3014311f263a6d099d865b83077c377e40f59de96b96c357dbaab49c75aadecb5555add677ce7847b89859d23e52adeb4f596518279b1cfe5c8e41a0fc5acc23ed9448c72e5605358778361ca595b7dfe93090d786e8311444913fa660e737b958d99e0d67d95ec923b7a766097b7ff1caa68160b6f1ac09aaaca53712e2b76b9dd36ff2e654a42ce25f9ff5ebfa0816d44611f55beaf977fc57f549c1f1027733511e2864189ee192806a1c9411be803a1f3111c20aff73c50491aa3cc6f41f107a9b9622fc1a6df7d75d8fd437f51e20e2e803ef049627b79406fb70560532803a28ff97f177fb7c1335f8998bf8603652f31fc10cadd7cce0a0894eb98e4cdd45ab29ca8d1e45a5a0670e4796878b5943562709f526be05066d8cfba4e04346e5da989b74d47e7adab024a4014b6c98b68e3d8002319913dca7b56b81aa4f786b2bb6a30e44f94426efd96b7a7d9e5c67a6dcd30157d57273e014c6874efa5fd9b182567ec506d1eea7bd91e8f38748e37a3e44548e5132a318fc4de0a019bf484f969cdc4ba2da5517456daecbfc20733fe9beb094e9bb9c77f006d32bb3f01b84aaedf709c748664821bc9275a88b02eea30300a59160de09a035e961e468e5b6493f57bb61e11ec06031124833a81a8f89409fad71a1b399a52db117ecaee68cba5a4575efee445108b5815f2f4370339b6c0279709b8aedc790c75d2ebc2b5ce483e2c7e30463f39a58c32ecd0cf5f256539c935435230d54f26e64b027ee494deb4a355366e055c798bc9911198d77ec10d241a61a2033cd30884d94687308fde5bc077617e6be8dbe8ea171d3e2825aa23fe3f138798d3aa1a5c374aa9ee330cc9007f684d76e3be467faa9ee256a19778449c490e4f04f9a8bfe408f384d38057f375bb3d2e937ec951088c358fb960f6100efdeee78d25a8de8a0c0d7ec911a57fc2013d83d09aba8bb322bb0a5c45875a0a82b1daa0bb5ea84ef99acfb39baf93ae407fc43c8544095c5536c6faf810edcc9491a368fe696df6b655a888b9a2fceacce03d3b5e29f38cd1a501725c1f1ebb254782fc26d15894cbd15716e107e27e8c7ed7ddb0938124b0f9851fb6f6117e17b8528dd6f61bc0616242702ddc728e235fe446340082c475fa5ece8bbc45df4e4bcce3d0bdff290c29b1eb7ab88032705292f69587240c036c725cf4f53758d84ad1404caf5ebe1d7c761bb15a4886c0b3cca7dc11de39f16cce0d547f0baf30d79e50cc8e8de793c1bda97b5443e0dc00021d8c393b9ee409adffa4554258ef8d960d47670fd8b13d3bc18ebc8c0fbf4cc7aa9266463c6706f9403b93e24f222fd7c8d20cf31e28cf2db1ec4064d744f298711899fe0f8fc379738316cb03708533ccfe687756c1a923b14324fd41308ca6d762aa65f418722d02ce9d7cd399a8a99b7705fb0039d973b0c9f9b9d00950dacd3fee7a2a21eaa04a3272641b959cb4a83dfd2d1d98e7f38bdf0d3b268e0b35500555b972f61be89d399bd6d97b27bbb412af63d52c386bfaf873b6905a1cf7f1c2fc74edc14fd9d374702c032798914891a69ee47a8617ec5ff26044735872ee2069a38ab50ab66bf2754ecf3e6af18f8c0235716e963ada1eea046f9653a2b90e3d5f7b238ac91137ef54665964dc587b922fc4fd0f51dad910837249b338bfe21f1184871d58eaad87a9636668382f44eec07b5fcb52ceae219d2d2e22be07e9fed704c3229f548922d45e4e27933b2e9af7e39e0e8399f952bbfea3e06b39f91c1e2324409d8f0f4ffa0c5af8ac72afe90a71cb5e94630d57d5e60241f0f26c499ae432aa654241804cb6bb00194d74615c27dc26063b295b06937e2c35688b127a59f789c2ea878e33519d9a28b4b92b0f42d3b70763ebab2369f19f4b5a72cccf02ccaf8c21fa28b441619b0c343603361bd96958294886e834fad4df1f0e5c7ad4396b4c2edb221a6bfd80e1e42ff94b573ad83ff34f22545fd68971271a7fc3ccca10c2d51d4493763005fc6a88517e59b9751094aa771e885e21b2b8774e0bc54514eafe34ed21c4292151681dedf5d37151a477f38b6a5681af67c3258c9dbcf9d2670565906bb85544c466d1491df1fd40631563cc42441efeafab047c27230444abab38c63a8ac5cfeda9de6a9efb0ef3b55a093ff1b603daee9d2f0be5d621260151ce3dbb1580095f0540bed93f2e228233a1601bfae968bbb23392df8771c106d897417e184000cfb4b3338bba8f82f07dc4afb02d8e0d97e30243c637ac1c65a44d651eb121a13c188b088632bde05696250bac9ed49076cc148c6ed453ddae3935e90ad3fc205cc0a88cf74fcda8a8a2c926b81ee9e8c1e438c1df872835110b2c5e2714906502b1a6839be068d143c35a1eba0a49b157161e5c24232c1c8e395acb6d2fa4650c23031910b20da8afe43920764c64a93de531bf787a4f4eb8010170cc0576d1bffb02d7eaf9ba34eb95d54cdfd5746a0f84c6587412002e6e2d8492ac57150c13ee07cdc234cc59c3cd8aa1c0168e348a9a52c9a9e708069007c5a5a8e579716612670c2cf68b699c6758964c8a75f8f2a47c27173c1dc4665654af2072b37d2cd341462382b5bb225ae7e0db6d93c8ff4a6dd426a0f7ac9bf5bd4cd7d24eb6aa654b4c8545e16a69d1ff3ff01ea437807f640221a791f73df4f8b1a13ad3b20818dc301c47c939eab13ec85822ff0670900db945ea8385c4597311f3bd6067bd17ac29cd78a1c4f86f610468001c780703125714dc13c7d9a529cc03c875f8292deed1fb1c417574c431ed1cacde80d28aa69950f633c05a381c652e7b17fac1b935e04029f9bad0ca8e059d30712e34784185e0463d7ad6ad607d42892939588c1b0d81d0b95c39f6ab04670fe3175299d18c6692570a79695d719456b5d80583ca4755f1906a1b0f09784db320c59c8faacdfd1463b21393c5c0b579673f2ea6a389f9072443867253ddbe16579aeaa60a2f95dc9c36866db09335d4b473df5b0dcd6e77e8983efca2b81a0b73cc0bbd8e7a476a7b80c5c5c88bf7fdf3220d499ee76279996879abf018e1af76d34b2e4fa365e02e635758b78e93ae272466d49903c3747c0b35f5d6c1d53f79605ed9b2bd2758ce91725bdd599f22257261ca7a9a43f17f5d2c993c927cfc1487fcf08e4f8d57d1ca5cd4f4bd658a0454817058a069c85f3f7610bf0da382e202baeba5a8746b9856149efa5045ee0f8b02df86fe68c06c6a95638a4987d80f90473155aa5874e07554c927cd0c224821af7385bfdb3ba358814c80d9a12829ef7c1ca5441986965b74888f58a6b8c7c10f0d77c0753e1904ac761880ef4baf9b59b3e06390ae3bad2d721c7be3704e930c838b4474ba48cc3e6ca760a5f469bb413b7ba57080725ea601982716137a10cf28e61c7a64e2b9db4815b041541ae8a0ed3880ace590eb9c7475e4b35fd75cea89aea23596e2980c6171f7a037fd76415d3e1ae01a3c66c238f1e4a2b07349d15995abe49bbc034dd7f979cffcb0ab60435ac0b6484a164f0dbb165ca11a2d7c180f83961be0eaba4b2670d91479558f5fb12dbce77e11a25f8844ccea88889020d71da68ffe0a5f427b7449d68b3cbe434ac82f642195b4fef02f800a511fc8cf88f8d0d64a6b960111034352227e4b358aeba4ce25c2795e4abae0846a0fff8fddfc52c50edb2a137e8089bd045e62f1e120362c0909d7e48815832213aac2fd13750a35878e8f3e195ab1dc8c13c91a2d2457dda3f2aa6b39ae9e5c1283f819a548b0d827eef4c71da959bb447392e948eec02f31fecb2f7ab761b23c6a93c3d847717752b62262c40524d2c83ddba3106e34e26efcb80359c9c5ded6961d01b01727a3b69151ab649c924bfa1a68154b6ee4e18aea747d98d0694fe053e09b614c7a6ec22d0536b0164eeed785271d98ab6523386e9d2af5d0abd72db9f426b18f6fa745113be4b0d9035e3eae7f60a7d56f37c1d7e8a8852b219d30bea3c33fb637206a78ee79b74591573924afab890239581d736802a34bd2515be1ef03ab4a119a1acbf8644c08a75b0cb96da031378a684cf188a9a8564244733b311bc1d11c3a6454580e327cf3a143562fe6ba45fb89c3c38beb949b0d8395114c96fb690f409b179146d1209b2cfa299b602ecc9639dcf45cb2ea2e23a2a304cf03f5a41f568c9f7e96d984599959a8b2a287aea9e18336a38ca3675b70316e7afb29a174b12ca969ca23a28814e0553056601f2041d013e63d405d475df52f0cc2c0708d04ad7b29ca5068f978cb54d83679a977d4ec33d50a7d5e171e80497ff25456d81533f329fadc44b99d2474027812dfb5a2af0398e1b0cea6b9891240e77e2f172d9a597ecc4d048860885e810d0c21b7dea34d1455dd00b93f421d2c13fc039242613402b296124996ebf55f02ae6a41c0420832e35cc72822d244006c2f12b5ed3e06a60017c2db09520c65a10045a71b11dba83f57680613c20896e878fe9df9bcc43ec3ab843010b73882f12a9f3c1c511cb449b1a528c6aad07acd1f656a502cf67639120aa0ce40e7d8d24c12a49cc753ea8b110dcf2f3e5ff20241685bf4b334d33a467899e6088d0b8578117b765b7dab73b9c8d6c944d1dba0d781a736874f18e4ec61b836cfdc3707680489effcb0c0e9b36ed03c6c2b9236ddcc9fc53a38a1225c0a816e671489b440100398a4b252a535292d40a7928f51eb8e509940f5b5093a588f8474ad49c930b9e56322069943b0156d27cb5efc0e1cbc2b5fb601f39214cd80b4012d7fa244ac9dd172f0e709b3185a94e6c1ec733ccb5df541a2fa3ecbe1e3a75e9f68894a4483167b89aa64271ecfd1a4677cc83f86c460ba5d0d3073da13b4041fd46602a2dbd1a76e9e7dd033941b0fcd20f3c8a8d033b8b7e0d08f77b6c67f7b713d05da88062a3ddc55295b3e79d0073e01b6ebe7bd593e22d30c6e6d7111a0c7de2b99df0d2ff8ab91ad550b45bd3350836136b5821f992e2c89f88181063f30f431ef57607a79eb22b7e29755a07da33d738d45e683fd48a9d656d0eb61aa21842f8a0b96e0b5ba73ca8a9efb13db64b96dc0f627af9ca1e7092fcd28b7ab856a8c0a3a715d741c03e23bd10f221592ebad0b3ca6e82b67a5c0447f4a16be0992de122a2952e407577261ec857494c499c2476e9ae069ce91986437e749941889975d3f6eddaa89dd2e162721cc704f39a64042642acd6415c7e39ee0aa8b0f87fe45f8d4d05e3cfeb0289529766b4b1974fbd6ce4f6fde01989b5824d15d1dcd9e1066b660d0d3af03eb2eaf84550b3126b3c100f8ae3ec2e9604ce52508e3663983d5c38e01fd54b75842337b2514753e88df1403b5b1a7a23365082543a690f923be736512a1573da2c46eb879b59050742a97ccb26592a19f793f2531fc2e3a5392b1e935219be72d760ea47e22184cedacad9ddb01d5ac111c86241faac15cbd12044a92fb5a260b381e28aa3dacb9b72c172f5167995694e082e645906f18fc8abae0d87ca80671126343d66aaa313db818dc0f8b29dfe221d52375a3b22c409a8416dcaa584aaf32b940304c320bb9178cd1e63fc56c97f7a554cfaa21efd58e381830c95ade87e3940a5ca3051480cd27e2d5789e99d1be550f52f9d419a9f49f2d862e34a97f24bc93f5369c51a95502de56ec4428511bbd8623564b25a55be1c38fa576fe2ea59523c567c94b945b234fcccb1ea64e6f7930ff9e5bb7925380f5b82e8c154446a54db21dce0da9f5824d4d2a351413c2e6392b88f5f2d3acb2d92ce0aa4cf5d62ee05fe15af23c17a453bfbf8e22a0bce848a03ced5226a8e1099df54c209fd0626f94c64838643ef3922c42cb590a7ea3d2a182977f08e0b092db1be1ade9a168bf0bc8541bcd2c9ba9e5725a9747f091a0b96ca6edbe11ee14801c096ed036e57cb49f8b15b408d19e95dbf04f5e5bd48b1a69c1379115fc55ab88e8be3d57725bc036b833b4dccf12f723b5995b0adebc05dc4ec11b01c4d69afd39b686b1e1a221728776606ef26962dadb6b83195b225a1a30811cc133e06dedfbf4359c8ffa8b32a8d9d1c8d258113288db1815fac65877e450efc87da1e7485be12228a4d8836be6650f6c07eb13269e15afcd3757205d626686ca4775cf3771bd835613e2f983d9abf4c6c7520933c0740fcf42471ed0b7a417ab0b2b4c22b8083bd14b96173a959c9386da76ef46ef2df743049967c6e385403daba07856db7b051a32e411d50fb5aeefd8b6ccdb4303ccbc081dc057994d3af3464a402217cdfb1c976c58f64bd70dd7f5c3e2c8ea3e422ad72120e196b31b01654000a7eabfb0e5c57d5be6dc3e51e9e59fe8c3683d95c3ded3f515876338bc12f47b86bcfc3650cbc0c61998df2e51fc5a97de7b90652ada81c7fc5c8c8225472f7f3ff1be8b28512657f0c082747a9957e8e5f4409eafe74de815f5096521b5f6df551bf13d2e05930925db62ee5d35f04382b230e1e593a7360244defd33e1bdf9400d90c1dca3053a13f3f18bbf16dc8b8cda7de25666ba595c2e70822ca2e4d1e6837c6ffd893098961697bb2c9d70309b2e453f2a78f77f6549024834e39c191b96a04bd9f97c84fab70c8a0622d23fdb441c0581ec2a125b945c82c2abf7db5873460033a73ef803f11f28a0baeab80a9659f627c345799f0abfa87967c79faa84c7fbc60b128438128a0a293757fd1d9049c17bd853260e584be2ccc860079d7325084eab095b1b0106713d8dc204df74db40bc255636f72fbffd1263cc2301024de9f9b81d7f8b3004a54e6caf24fc90c6f8e105ae23d17248ee3475affc17b8e02c4169a4eff180ac7fca0ccbb29a73efae5942e59d9933b12da56530c66a562ad5a669bb7657d494229ca0a6c16689599fbeb06f6ce6cd8501f29a392c16668346ab7b66cc0666cfdda3d43087322fdbb1211916f292d3f3edf7b2d05db95ec70502060128d78acab44417601d5fba8008c224524535bdf6cb2233215737673c3e6d76bca0a799cdcf82454cedf1ad52dd390b6867565ee7d6b3f8acc46abf10d33fd446f52a0706f7d1fdbbdbb650c4cd6a8d48076d98a51825ed74788ba10bdeda2b703f23dbbfad6ac017ada6741088b23dfa12ea4f1e730748572017b37569e76bdd3386ab2a228c94a2c3601f1b667bbd9e90aa51333e5a7882c076013878f58532d061c195d7279babf4459cb0a68e09c9717c5484ea63cbc9b06dd4d2597f2f5ca3450787593bffaa6051f534374bbf709cebebcb3a55daa3aea5a72184d4d4ddeaa4a319e39320092a33fa927171dc99f194db63230c244dfe11749744fc2032f337c3ecc89e5a83d4b0c240215332cf82b273534f0bdd667e1cd5b8aa6cd474cd50309ca30693e1419d851b751038dadf931a85bb23b2e29b8f08041b6cd0268bebc6539cea9cfc75643d703ca24efbd8c46c8d10d5061e7cd453aa6f3ca46d3f0f4bfe7def5623505e88edab4e7476a4509cee2c2e7438ac0b7cbc28c1e91ee54f8e7590eb93483164e238b18d891b3cf4fd9ab5b1a99d4fe774203a800cb564359a6b30bc0becb63b05bd9e648d8bddd9d13106c1957239d2a687d839876d7f777b7263cfdcffd753a2ab7bc96732fac3b8c3c498ce7ef7d516c0a78d14e4c9d3452a93e1f433d7443c6a65b34c27a7992f64f7df486a8ad80b73679fcde8482c4323e014574bea6f4ad3ec6d9551c6ff56dc0445eb8b3686b6600fb9f499dee36c11eeaadf27942a44a11e78c4e91a9034b0bd47e3229204f7287512f4842f0579de191063593e93fc9632d9f7642ca6fd3aa25760dc3ba0bf47c6eb20801b5aab46ba3d81aad0c5039658b95673baa9348d1da2665f970aa00d049483899bfec9527a160113e66b83951eb10f8b133742c975006927baf0c99ca88198df05dc0ff41d057b3e7ff01960aa919d808ce1f5bd91d77e89162c5afaed87770c8ee95b4403ef5b9565197a3762b5a27c501d427972c2949aa73758b6c41b93fa80789e0f8686c65eefa2ff5df6b9abfe77397cafbefb5012068535e7f86d68378494279ba8181eb52a77e26d60ab3856b745f117a048b2ec24c225cd95c98eb754cf1a8e91cf273c1ae87bc6e7f04d20979033143d24ab957506870bf66e689afc1528634ed86ff86720bc1f00732492be4a3cf3acb46096e329d8090fee2f2a3dc656a00a4b82ddb8ab5f3d82a67002e244616411db34b4c53c93a43e19ac36bb246e6efb38609896312ee3b5b2844df968d36f8611a5ecb7b8560e6e105531de274843d2a8d5c368b60a32fc06039740f671d528936da10dd14b0a764d3b62245aef8df82b5ed19f55bd05be02dd3b5e026bac1d33bfb966fe1ce4076be155af8ec6e161b33c8f618be2e2ce60f1bc582bb166dfa7bb6cc3342a48b59a3f582cfe67477afe5a6df829617a24295e183f8935e71d5f1133032204e2a94900e2f55f1230741654afa33d18eeb19d9cd46df07325eb5bbc39a978ea321aafeac938704a950c4c49b2b88ea70525c735dfbdd07c76487c56a3ac211889090ca647a6f09da410ce10e617b2c896818031bc874b707258e9cb0775e51bcf55f31d168ac8028d7bc4299061dd36ae71db27e2afd31ed1e42363174d0a91034eb9741393b0dfcf4d57d2f93121ba0c4f4968583e10a3127af48f39554883506b8550058c1daa9420c15225f5be68c551abf5303b02af921b5ec7ad4af306dbecd5745233b852c04b28fd7c82ad5c48611ec28fd8cecdd13a17c2a51638a806e8dfcff7ece9008ff7bcfca4ca922829786e7a750ff641d157322f4aadf7cfd50a21201b82cbb214223f5f780d494612d9595df944844ccebca218934e94bc18c7f23715b1e44457da2ce6ad8b5cead45f64d456317b43072751330ccd0425224a50d0455829c105d1359eb054b366f49063b1f842e2bd56b7616e536e82ffd3183a3111bc49a1927d902e36cae4f129603d0f5aafe41187a5f80e231a17ab16f1b4bd46c910e527ac0c829da3c92b4241052b9d20c378ada0830c77943d7a2a023f2d0dfa3ffe41a3099e2f90379ee2dbb66cd4deefd7b2f4be7b09852c29014c0b54c20bd4b0ffd90b79bdeb8d3ebbeae199824b29c108d8d48a246a0a8da2a415887d52111ecc2177752549fcfd84096076c4e119323eebc8f69b1c228e83a7b588cd84b322c9f0a28ee2dc8790b2c9280c807cf59ff0941e0d948b02b94391fd7971304acc3ae0f3cc3a3cf8df104841fe9d1c8b59354d43ef41915cc22990d9af490c09e6da7acafff1c4918be6447bd664f9447e2c259cb1afaecd456cf36e69dfd65b88e613c8ef1eaa182249ee80aa2a9f1e72c33ad2038c4dc18b73ad3b06624587ee45feb95287bafa0680be83d334fe866429fdb130dae5d5c1c389093af6c17cf5f81254c647c766e12b6712e4b0b96718fea240b9b8638493ff0ccc19345d30c613f39a61841fa1671e953fac561ee4a28ecd055abfadae51bb9f33a40896560e33673446b7d700ae6247757323393e3e1f0217e3f07c0f18246c77358ab4010b009c560f528f8133ac2a8be97615b77625e79b8ae621f4ec3e85bc7120efb313115dae5c4df308f01400c87e70ef30a9fcf0e0b028aab4fcd8e6edb4ed509d59e7bc24ebe4463c8e069e041d21f51e72b72343bc41b859c7742365c137ee819ea9fe821ed81c2b0558a7121aebe5ce7007d3cf654d79ba743d89ed9433eda61b81933c178d9eaf1d0327287bd73068768cd878c8ee39e1bb30fad83ec4253aed6acc53ac3f61bc819ba1f1936fa2e2663cf4ab6715eca41a9d622652cc9be754bc6604a1697ed1a695946486d5d91c6369b15e80669a7747860a80160b8d6ebd3ce0ca9cf08382599d82e618276a01e2513adae94551016b3382949f3ed4509df7d1fbf1e780cbf47a02da881792532bbbc72e25c138c6b1a04811de93e1aa054423bac198e9494df0555b6c6e2b88141a87cad3454c2bb7f20216796a7db97535d64009794a6d9be24fcc92454c8b5bf836cbba3d216f40e0d942ccabb72068cf9c82590fbf4a15978726d767e1e57a018bf5e18c09f129b8a1a1feea4f0bf9f9be4e1169de1945255039f4a35e2dc645211954f81571fc54422092b16ff146dac0a5a12092b0ce15aba0502b905f51f1fff5cd2cd76663c43152089ec0bd694e354921e5d057b76c944bad4c28a1f296a80ace0008aeee8ede80876093f0f9a45bb908067df1e9fabbf71912e5c344ff246d7151e79874f97034110d8f9c07d1d98a80009c8af7e764e86b7c417baa7d8f4b664698900fa5e6a8812e23d0d934e6273462c7ab44668ec1cf46f08438fba44c52e7c667ced905bd040765d4b0b7aead72a27dcd0501b74721d653c1106a5077cf6ea0f32830a1068a4088cd0a17f987b18c2d9c188b971f18c4ed68726598899907467c1caeb858d822eea60051a99ad905375c1ef2680ec8c5ce0aaea219ee5afa5fc5447ea30a299c4662dd9417788bb1f0460ca0294fac9d7c4edaffe6e62213fb529269700355a2682e65a9eb526c306f61c61927236508fbb23f0ad346eff0d5a711be3444fbcae325b6008c425b58c36ce392bbe00d142889d2e8d652c350ba01583760bc70890a55edc52a2ebf8c60e1bfb05387dd77042f1114594b628a9bc29cc829cd527970ef65871917094a1df8c46c870a7cb1c4561c98e91c5a5a68c18fb6873b38a45896d792189498e0ef9c5148255b3c545951f33736206043e6d92727f555a8ae651a26cebb5f4402562521555f3aeb7469be47142d010342bf252404244dba87d46a8c7f984524e7b34c153f40402d559c93736dbce1ee5e7cb58ff67fb7a7de86d790d234c544300b1bd076fd1662a6afa580cf9c83a6472f7a27a36d5e64f2cfc83ce778c88309998c835f7ca8757e7e57c0036c1240753c44c0734a7868413d9de2dbd4aeb0721f8ebc14fa291ea2549265014aadeeb491a321cbf05d8c3c31804149396b52d77d621e24c89a8b1c629d827ba1aaf4bd9696f6fe595edf15ffae016c19f986d6d55022fd1c96c1bccd31a0e48b96f3a51b93dcef737128088713092ac15a05d78d77de34dc9501edfe3a53e7801a039717c42f8cb5b9d40173252a633c31c34240e7fb6ea8c7865d73811a1e03fd3b1d67def1ab73f9212afacddae40076db20301fe1d049ed021c097648622183645b806265052cad83ce319365c1b84e6f6754fee4c90af3b0db3c175c70a05b1b1b6a68541801e1418d671a5de8ece387a1df895b2aed7d1a26df352d9473ceab3e281f80a8f228257c0e47b28c68951b31e9406eb44368f7c4198fa94c6920603aa6c12908e1074a3bedcaf6c492f641cc00604c86f0c37749281d5c063a8fa55dca889316b6d242ffc8a0bdc6f677e719d2133fecd78031ebef3b262d08caffd3c862bd902da0d04f88a608c492a8a54a5ea30cced680600a881f0921972ab78d3a5280ee123ca93ac380653dca50b48462a8cb36b224c4736d64ab751f6a8eb270a45ee639a91c5992b08b25bd320cd00a467b5e181de5388a988f720a1acf503ebc28f7543fb109fcf9a298b015880f8f76534838a5fdb0a72405975135d1076bf76f74d4803cc2ec906c591db67e26c2474ab7ca3839185f1e6574cec37463269c86800cb0011e213a2316f897371dd1c42fe98ecd7fc7f02c8cbd0144e7ac3ac387f504177e107ad1a1518499235ec17fa11c342710b3b1dc7f0e98b90cb146928b71a5ddd463c071d3d846f7ff92a50c294182dae3ce6016ffa4c4c3c9c313ec5efeab07f226d4709a21c8b46c030d3f46d7eb5dcab4b92c90b8a39180be3522ce19ff4335463b77e5a8a2448f2a46dce4449834f8094f36a4a2d169e70d87a4a6b2d695e882ff9272985e0603da02ed8276b68240a658d842c9effa5c66e5c351e3aee31edb1a6f8cb7fc3fc90af3c5367f127dd1cd154843203eb07e3aeaffd5a38d328fa9572bb79cdde3abde985a5ad65833f866c7e84d7a41d8e3e411f8c9478829f967e1b20da9ccf2ad905a57451555058709aa6496238ec6a0ca949f35f2a00a1eb3fc70680cad6335ae5065ce75e52484bbc5ac2d6e17a88bb8e8f027ea9323a5a2c507d7a857abe0f74054c47b686a2aac7915cad4ccb8a2a6b8e271b81a0a38547b032e9e0f6153b157d347bddee3c7d18b7c725878f4f54aecd11eccf02fb64ee3149f129ee713ce6413d7540ffbac231fa7afd111ad5337830578437dacafc7d13b6b2e0c584398ad14214b2063a5d87af1ad13f371b33e6495ef7bc02463a301c37340ac8683b344eae72ba3a7becee0df7ede8aa96dcbf2d3290f225885dedd85835446f826bedcf2b6460867d5c3bc22e11771cac23dc06b323e9082e7f643af854d6f4370bab2eaf23086e3e37a62fea3806e87375e0ab3aecd47f3067d461149f332d6440826562d83061f93999707966e5024b5e6b11c0c9ccb6a81ebf14a15f8bad8b9f495c944467b438663ac08f1090888429f1ce64dcc07b6fbcbf8af70e318e0837b8277247fe2bb006cbc2f7b237b70c2322ad430a1a9b3c50760c55a3bb156dd7fee2f495a622ef4358367f53ba9f0a3edf56883fbf684cc4049d28fc08a5db03a267c606f1f21493a078806be711420920200282a86cb899e4ce12414f07d4a23a6a25f80544171c3b7dbe325d3222e8f8ccb0ab8b8e4d8af04d0fe167bc049158ea53a194ce2a6e64219616feedccd95d71d4fb82669bce47e12ff8ba0aa8864af592a684aa57faab423e0ca91c4cd684e3a381b8d9b8622093ce4bb27ba9fc8d6464562a8382ded7c7caa720c4c30838cd65b7639871d1eb8d4d9977126d7a924b34b4b2096db638c30178b929c78e0ba349586d50d004d0b86c7d25f0d0768dab15a3a614adfe4704ec3a762413931eb347ba65fbd3c4c4377460c5ff5c21ec7795164f502ee858794e4d93111aa733fde9b37da4226bd500391a76a1b4a9ce7b8180beb7033aa635ed3bed5c7327dde492e46f25c59330ff9fa47dcad7af68b71df6556cbe38ac5396ad7bd651fc647ad7534a31c7ce89e044b1a8f6ea073fdb508e4f3d78355e66e1eb7a49daba7ef4b69ea911c3b0417565488a29a470ca03c20a44d41ac82d84513dba5add2666714ace0804d2a1cacf087186bc0380631105ff21090defd16195b9768cbf6862aa0ebaecb4823e05e0b8be277ac23b2ea1f02b814ba32adb4ba09b12a7cbc5c2f57857c62a8b528265635420757582ffb129954076f65b96a3c07f84f833eb8c7688f216b5b248c88e9f1f23c93f1a9d797463a76dd5a19a0c54358d0dd5d42d55045cfef49b9e7541f0f99cd19693f699146a1f50c385aa5c68e3807dc498879f625d0580bbd089574fc41abf713b878f8fc5b4b5a07f10e1602958b7471e5881263121e20a620dee21188151bf34baea87c138fa1d262c281e006e7d12dd7c93e2f0cffc5533af3016acd01fb792decaea29c854b7d13e0673ce0f842bbe640d0b83648db92b53ffeabe16a963e2fccbbdf25bc42364b718cd0c6efd22fa117116655b31ed6d56490b086d0352ef9c000fb3a0fc21c88a15c06ea00bcd3b99310307f585430ac76168a23f8a1db581e7e2a00d67b616cc1680ce9f94c4f94472fd8d0f21c8138a1b5e9ef43db2274f1f80c7d4af5d0b50f7a45953ea318b7663b1c375519472f279be9e45ec0d20bef1a319527350333d18f2e11bf360650cb9fce02ad1ea92c245c69136e7e83feec3d275eb413efc06d81e5d1eff0cba1c6740232766c6bc55a2aeaf507426d5767b1403831e9c7ee6c8e0381a47a9db06f5102f17a8e847483649020459e1c9d468cb06966910d656201ff3bb9276b84ff29da05068afffb546441b46794f8d86634ae33e21306c92328e099954cffac705c3ce37d8aa758a5db359da4d63c31bd0db0c5f0fc3f048f7684b71c7ff0dc73b5c14e1cc57a449b87dc0c510667f1716150dfb3576e1c749326ab354ff02b41993bee0bd8761fbe903dcb0b1a711f9ca86a4cbe42cab2f28aee47c3d0cb112a15cd8d666a494bdc3cf6651d6ef5c4be2e81e9c14c704a42334639674405e91a0db8d4a980b0a68ab6c3cee03ac0f94f925997dfe61d81fbcda0056ccd10eb7c4b9a64bd5f2d7af6f9222e4721992dc2337a3e2fec8da99d39753c0a3a3223cf3e425dea32391c3a9d47737184d452e19e7809b11cebf889ffd0b5c5f9e25f2eb309f7f35ad1da9bfca2cce314485269e095361f8e9e00106b2dac0a5441068a562253b8029d34970a0cd5d8ea88cc383ecaa060d57d889ed36b9358a2c43be91d98e703dbfed4e74f8d80c19b60cdcf2ea536c335f7a4cfa3a20120d0c09391b4a15f83a2811c6cd485ac962b4600a7297b9757276065d2760ed1ec9c8ae9e1e4998ce93bf5103d73291c51de5220cd8be6aa04abf6d99a3a10733abc7daa9fa015d752faf328d26b55ea80b60d4f85a5cd25d53f7509954bb6fa1d97fcdd9646811973254e31c47ebcadc4a18f16ef1839583210830ab3079737e86fd4eaa9e899a3c71f2674c9e6fa9c2622c96a3c17c41e250e0d160f04032eae3b02be5674aec881fa2484aa2d030c5c6dc1304314bab78ef8fc83d0432e9951873b561b6c4488724ee318e7ec4643d07e069bee6fb3d5f23d0483eb8a952899aa6ba788cb6ed906d19f71fa5162473e7bcfce5d3e77522eb81e1945e81ac5b9299b87530a4722a87901b9438fbdb4778e0456f96a1bc7f8a44ab07fe17a186489161736cb7506d165c269494328e8e1aca7fd115c521bd2ce8c5933f6d176792e077125334bb7cfedeacf1e6eaef1f52158cc64d9c0e7f6e9da351217c65ee7c1e1d973051fb2fe5910ec5cb7a6ab6d2001c11bddc5b8314d129d088355fa5223aaac87e68b095e9205ec7399bd465cb0c255247ad0a0ca3f265c66decfc9b75a287f794d522a6203e3b1d3d6afb32fd9a17cdf45d3251211f43ef028a97b23f92e1e2e1cc4dec84b5d488d729c964799a4a0ba17a86d64af2a8c297e2b4987e4db652632672c26ba3d0125a94991210431aa40b349c7e0f075fed053650c9fea0e2c74b2498cf289c2d51194bb4e47904988e41c8a291ea8710dc8d86759a1c9996af2b63cd3b85b4836932eaa4abd4a9a337b5811b0a759516ca7d708120987370e547c8a6cc09fe600e6a6650a5985c619b6c5fc0741f2eca9b82cee7114da9f32bd3e7fc2f8167742814710e52f79b1fa74df1928136c9628396bb5859ede42764a7091a55dcf31025391f0afa20c7276db794a0149c82df79710532f03f441dcacd8cafdb6531db2a1f45e375e779d5a1c4713f363b631c70c6c9e883bffa3e18c4f936169f7578417a205a04f6356cef7987e49e942e67f9bd1820e6bab4a8e83b5d8578a061af2e38b2ffa0520cf26da1e06c0cf053918a0b91c8fb15749fbe57893ae664dec11f80f94f28e02f6c9b983564a07ab959f0638ad9533033a53979d2afdfaa427c2793d10e33f7074d47f44f210ba19231b5c86f07e363181b6a792d60104f9afee2b2f3444143d94495b10e6382fb1c6636244cdb6f44ae35db7ef58486eefa9218208b159c6c95ca31742c511c435b12c836a4591bcbd9ef434d7d058a9f1b38e74efcdf8f5d0778e6acbdd206d953c66dad10697701d3f5b5dff8a151c90f33121f0b630b1a86aa927f2b54edf90fbe2592cf9339723d0b8b66834e53c69f675054c30855b87657b9283bb5ddcdce15216f492a6e6e20156559c4e4fc12ff0b61119759647ee18fa227c969e407a14847a5380341141998e62d21136f113f0fda85e15a8232ba13a484c5cfb149d1f4212771394458516e440ae992f4be1ad193317c6c157327b7873ca133ccee9cd2b4d9440e88684c176481c1dec3dc9a3edae93e4fe7484e985f3ec89287c0faa37918f79011d5a893977ff0484e61ca9fb64edbcce07bed87d548353b849fd2a4534a80fb1e3ee41dadbf1e33261bd50861bc0933451112ec653178626cad4f4b18f43bd255962e35f00b108f633930954d1915461711dc50ccfe5d1d320042ae16ade4fb924862cb39f7bf63c2b16f37d73e8d7c699ae5b0ac42cc8e59b662c5048d4db7c635bf327ed7dd3969114d17af81b6a90508fa246df5723941ef4c84dcb821a7993a0a8e12756166d4ce52723e9592dc438b337a3e26d0b2a48acf46f7027700e8f81d464f84bf696d9de733589c8639171f47c6569fe1eee15768bccd44ff3dd373f6182cc5e2c938adffadaf02c4f59d078cbd71682394c02cb38a055519b2989c852810341fce7adca776850f3a5eb74d700173e40e9ae90c476cee1f494141765ecfd13598608c6b2641f885ac4c0c35884ea210a4cfa62360a6b67d1e792253e8db223121008786ad98b4a39a19edaad94cf586144f669e1942cb8ab0676051b9fe034b8d8fdb335c4384cfc5a12e148ba247164a5e7a0c90961a8615d1918064d51806b8589c686b4f1b801835e382ac23fec34c4ff560716c299a0ea88ee43718f6e91197c48cad7e5268ada3f64d1b0d9a5bf20368ed52e25f9d88680a3490222c3fe8e8cba48a813405aa6be169495552dd160e435a15b3f93f86fbd3c4542546c0bd7d8db2f980df60ec86c696091c0943e31c8b96502428d6145394e3f1692cf766e05c3461f17d2e7c2b2b17d441b0f5af15044a74019cdae9a98560ffb7babf1f5d2809b8f3baca86b917e901f6b45424df96775da5888badb91ac2514fe51485e1442eaa7e1df4b830bde4135d1e4ff0450436981731b50669fbcfb0c5399c826cc298110808396759ac6ae6fc2c5b6b67539998ade9b0062fead5f8c28bc6c9910fe26280417619fab0bcf13712868c7e57bc7336f447a84a4a073c7e917790887a52d76f60602b3dcdb20b22eee4b784357809ad73d61848b9f05d20150a7a9d18f40e9ed200f80d0c4c2fc73e6dac74f233955f981b356ad030410a284bfd9a586638353f8a4762e4615e40ad8e9b74c8b5bbf32ab1159127a2ee227589c897747e30bea4f3e83f6203d02e3e8b181e82293f7e78d5b9d9e7f317a1cc25f925bba36931aea82563dd5c7148d9f0ebb1efee147b800a3aeed42baeee4064f555ff77f0c294da13d0fe42212ce1a5d4f0af2c86302b814a0e7270717716b7671b24cd95ec3951369267a8e6303bea8230959106af6b0bf05d659273261f8b978edc50c05183a5afec7f43b697c2936c16a9d0fc88e5cfc1baf455f68615a9caac4c9ceb05c5c7117b449ed0ceeb6acb77fddcd7226fc163144cc94fc2ab89a518e4ad087c1a0a15fdd6a1d3252508f95e2cc8061691d8865c7af744239e713dc446a15da842b084548d29013481ed41d8cfe22d58c083c11089c39f30727e8e282b3818b2c1888834743fcc69214846039fead93f9306f6f2044abb882f87f5af8eac8c0942f5c28260d1e445f566d8aecbd42db4b1bf775b696ae64571f5723b973c73df3b6a9d9f69485d0ee03f1ae06051d4618179151af5dc25eb96b78e89ed2db3298e8b065b2ef79bb7d1f5edcb8c9e389c79deac594f033c01e1e8594acda4f59f842d77578c47edb010c54ca5a9559f0976b547a5614d2cbbbc35b8f4590d2bc13d05f7d5c30503222c5684423656cffe96effa52e899624a1243111f3113998bd57069eca51d7f44ef90c98f2804e2022750c830f2f2cc6849a870e3a67d1607703c56a6ea0354c12ad4e8aa2b900404fb7f284bdc8c7e3f65f303f75fbecd60a896cdb54d218984e131418e946048b577b8dc32e68503e755c50db88231c2caa421c3673f7818fcf6dda66a8b378ce8731a7ae064f5d8a33a6306569e6f0b008c77c9ee4173dadfd732637119b78193295341818dbb700d98a1f6ff7a0184cecb2b1953abbf7b19db28805aed471e0a9281b8b08850a6f13de03e938a1a809e16ddc49a6e9a18ba4f68ed83cd317a1e79a6579383830a4678e9a5edaecac32cf5a99a88f14411628afe240187dd656055d0e3d70d29ce95e4a66ee997e22640c6df9fa57d741273049c6af87298fa44885167528b4e0fe94eb2ead33d9c8615637c4f39142ca3681eaa80de9f19349feabf16d10a1d442014b03677ebf4f743a2edb212a81ee4aac9623e74272235283a855d111c22e5a618dac847f9efd4bc81ef764dbaa763f6a21ffa5436b617989c1c28fd0c8fcce679382360f440094e24497e98fa456ed54523d4f9897b5d04c22049f640764897f4850e2f5f8dabdd3cf7f41b39382b7fd798fa96daa4d1d4bc05df5c5e783241849da45b30126e41ba37dbb129d556958ffa86dbf7419140b4aa423f5948280bc2d8089b2844b24d4baa26b99eedd18d7e8b5221188b9cb7405fe8694f6329d930fd73c359338830dc814fe501f236e564799e9eac32960e7a0b15b882f55a9e4e5be44e670cb01ed23393da09ad0bce5f4be74d61e087e0a524fd32f89d4a85a4cf555826dd5ac5a02c20cf9001ae3f740fcb8e08ba59bb173b45bef40554345688fe038c373722aac1cc2d6abe457a14116118654375346bfd22964a0f610d855cb6eaa413394175e3fb53eb965583a1bc49e71a817b174127c5ba0be62eb7a2ea8cdf8fcb1b388b3fb4982fb3958177c63358bec88042d365990fded193fd34059c268f68f9056579ad1c437cc2c9889344ae3e9edc80505109acc59905234fbedcfc987d222a62d9613ae6d862c568718f7fc6400fabc4f886dcd121da142e24ac74ce1d11c938a69e89a85464bf3bb12c8cc84bff874506dd4cb6fea853a18fc65b53cb230a0127864a525473c5d0588c6d910c48c7c5fbdca92d02fbf043da5f2deaff8daffc098ebe5814d8e04a794abcb35d8b30b5c03c63630a5f1baa21a26b35e14c12254c25413bcf303d84afc3c8996336c3077ef2eef269c7e75c22f36176f0c307a6c24816e1a88e6783f2014f11055af738f028d904b60466e3e75433ab5f7e1413536c5bf9bd73305fb998ca8cfa661f78c76dbbea254ee21dc70394cf76cce36b38307b3eba6ca3184334d36d771c0ed26da877bd4fbab17f3c843c3c8282ec4b65653e69495ddd509a964aefd6b853b340a6ab8d2ce2fb4ae8348f6a82864d97dd4a45a848e546309726c8bee7188005d2606a089a13be31174864a92642971d354a9f2eef69363c3117dc61d2fa87f1f6f35f21924cbdbd39ad368a414419544ffa634e920523d1f53763e72ce69b69d7233950ae10e98175d678d95d9b2ed2d5ef8bb3af8d7303ee547e8ec56e4d02ecab1357851db40cd4f45e247814f5b556dd028a375a96aaf69f927f3fbd4ee181e35ad45033c81531a1bb0129b3ba7fc6094f606e2947a8fb6e760fd3eb907f2693e4fc1d4bb78673af1e3fe7cc34c87ce95b0b0cd06241c65b8e5cc9a9009f04428f3ae29291a0e4a4dcc594c36767c2dead46088ce850231c7458f17e79e730edceb46d7088488b2a16dac06c79af39106be9581279473cb3247f7c0e4006f7991b2a1bd9e20122b523cd7959473b93fe84f992cd54b012229f0d87e261cf1b860ac4bbb6286d434292fcc33f186b5bba2112861e81e1aad0beff20b80b93c971088e3723b56c99adc2e0b081cdb58c97a633cd97cb01ee9ff09a71e259d067dca7d094b35edd3225256d87a34dc32816dcdc274fe926fb5df7503824adfc6842bddf65846b917075f937c9be26038788f59ea10bb258f646530df4a1d7d0b3f8531c6e34fc7be6e0081197b80d91b144bd82100e7a3c5036799f5511acb0e1698cc3195027893d408e20152ab14790fc8a86ad1dcd882996da01a4e823abfba9928c96c86be7c9c976276a92672a6bcc4d6a4781199565ccd45c12f1c0601d30d046c4488f85fd04b1eda024815227b27c2795bfd9bd122444a7e6e6edfc35a16ee7f68e5f09d4da07351d4d9473233f2706b5460e25faaa8982ec4c4958ffcea4d6422073685507600091cef6576e8bb363d9609393ba7413b2bc30ef9d8eefb5d38b8c5b41ed7f76252e8de1a53d2065d6a253a5931cdf0acb81e1d629365f0ccfe32cfcab3b0abc73dfc84dc9c87ce4d6e474206557df1ca700e936a5fb41eb30a97e7d90c10468452ba4bdd0635381b4ce10677a87bb04851e0bc62d656857c5fef29e0bec53169ad2af153a17cb85cc0409dd64694f4fee395dca9cf83670da80bb5256c76a70f792faa54a614b77cc33a5137a96dcae458289e97b4903a0a584cdc6f75188653e5679d4606b90fc0c26700b9a72d4257357d6154f54eae4d6a513bc29711c43182e65ae1e4787667a66668187aaed3aafc20990e5f4b0a0d194a6cc670a308c44cad1df587e738ebdb2201b89cbd6f3d73db58545c76afdf9c92e349112aa6f3f07cb7f2ee4d99d4b40b25226327e3b6ac7856497ef2ecb93ec5b105cbb4855ada03b71629488f4d1e0373ea701cbe1423f4b594b423407345fbfc72a9e7be9e3ebc9a6f0676d713116ed087af167a8ec7c75baa39926b2b629960538187abf4d5d428b66afe96802587e9cb828351a6cdc5accacf2fb405cba3a1f1bf7627c338e446130d20f70d51bf4348ebb8c241e05f0a2bf02644b58aa75b2f4e18d1fa42689259361c00a756e474db96656de37a1a27131a64a2d3243b2e7401d8bcb6f9d929531deb357ddcb82da368be1558a89f09ea16cc020d80525865f78c5118a73c89b5775738d943f4f65b610c1d997e79a2e416d5009464c94e9bece5fa69b6a908b8959943bb6add1c68b8f10ea0a5b6ad04b0db8059fef42bb80239b44a41ded60b6fb481c062acc10f9365cb64afb81b21b7b570be6f44f4bd6af62953cbe04158e7f54d4771d3f387fe875643843aa3965b9ed6692c52def11f8ba3f03d8b11a9e59dcb92aeb436b2fa43d689b60883aa9c8dd32e6501893ffc361ea39190be59200a3e2cfc22538847afdc843316c448d749b92e76cabc1ca14a6972d5d0d3a15172aa3e38fe9cbc206ffc563da393a450533c136786050d7b231df4aa86538e14b06e41a832799273a7849aff92be14ca81f223a70cab15f475d3bd11286e1bd23ae71740430df489c7286e1832e089965cd5419c2261b220a0cdfaf553710a72be162b9b62384542d55816db2f48b93f7187ec6f0b2dcc4ba2d87ec3bb3a7116ba5686de5b59bdf08bf4ed8a4c92b708713896bd4432f3b7e38d3b1ff7c288b7dc4d56581fd0bec25a5f0f003a7c4c7145f516590c58bf83a64c2badc5dfc436c4ba3192870c4ce67033983d7977be311b2f59e0fc95c6bb31f5612bde868633164ae2eef71c09d071419ec6c36fe2069baaf0629bcca61e8a21e078307bc05e7cba69ef55c1e5e6fa44e8f1aafe0b6ac6e0f884b21b9f21b8516f64ca23b9a2b7579f35f0af889795c31b7e97a9a53412dff0af1305bc96a7919742a9ba6e9a3ae2ad07f075b3185d481a4fb214a33776b791ae4384b6bbeb1b85051371cf72d1a731e75401dca0d5759e054ebefa7c4fc65714f3b8822b85542b02c3cf36db553dcea8326803c5e325e397986016f0b6097b90acc21621acf9ca4d4c461ef762250ba04e1bf00ae79ea29601adb96f858580cbd05918775a2b9501443ecf976d1a59133aded8caa4087dc99c23fde5b60f2687b0264ca35bb903e777f549ea738396b8d34393ad5ca6e19c893c9e29a94fbcb703ef67a8d51b6f0354da655b7e9cb20be88ecd9afe6899cbfdeb9d46acef672b204894cca961c8d8133e7664e17d636176b7c02598e087a4608ab4321638b2141f1befcc98cc359893fdba65fa9703139027db6e3306ed37d39df27f1ec89c116b03cf238e4f6f0e08762fd89100dba8366df4b8363311306cf9011697131ae480a9947cc133d09da2e14cb3d0d8c18265d51912ff79048bcb318b0a3404b7a60ed0f70c3d5dd132e7ad7529c7069b81c60134c484411c476dc68d65fc7b22f9a65ac8149ab6e203712fc64ab5e7fb66d554d3581de61fc045ab9c96f8abbd6a62a8ef5fd50a3782650be070a1ed123da48130b11b79e8f3823b121045afba208a6ba39e7eb8e70bd703eec2f7d3b9582f2e30f2c4e42109071e0a8045cdba647519f7429d5b911782867b075eab59143d668030fc016351fb58739c8c8f584fff62781808f2b85e077a29d3af8baecb487ff6d6dfba5ea7a57e0e044833c8975cc447b067066c7ec280e8c1bb2923e41111415e3c640a90510684d604fd61a5259b3abbc11f7a107d7459b690cb145fa0015c7849410769f0bdd8f63856d584ee558e2703db1622ed4473c2176df06a5385360fa8ba4f5f8f14d179f2d1b607e2fe998c7e1030c13bb7dde0e15ed5d8acac1825f0886cfb4040c8c15a6181542b5ee1555d04c0053c3b54847cfa890741a097896a3e1473c770427474742be3c06f5790ef2662e4c67d9b8eee3da0d0ff2fddaee21b7bbdbd1572f54c5f41f8778081f11e5c245f973f69ddbd5db77eda60cc7706c62cf6652cca27823f6ff28677da88381b3240ab3396ff277e3d0ac29868c1cb5cdfa6377666ffdcad3cbeab0d8e5e02dca0d48572f0570052de73452ddba69448d103088e5f478337b4f442a091f2c048364f0f5796175baaa51f11357f33b6c4ed9b834ad918a518592e368d806426f4d0580ce250be92675acebc95906a81539bc844d6f6e4f8d0893a4cf0c6333a85e789820fe8999974ae53fb61d53ea5093f79889a837b3d0d981bfe517279fc675bc88161d3474976dafe4029d8f6325005a18f3b4390158152219016e51aa545923f5259935424910385312be18f12c96f886b5da77000438312bbb6ddf3712863ce12f3d0d8a83689e0b250a5b659900a61c70971fdee6849809aa85254de3e048371906b1a433a4e7aac209db43825a4ce6aca935efbaa132d2f63af4e6e1879e952da19fd469a6f2015aa7134f52fd3796581ea2ca50b0cba8f002a37ca1f44dbaf1884367433d3a8f15267d76cc67eb024a747ea13ae17c7fe48c3f9d1e277cc24029627c837c4a25349b469304daa67d5db102623ba5709c584f01b18746c0d8a636b9afa4c0629242a45d5b263549a6451bd3be7d8f60e61087f2e391b681b220a0b99e4f343e25550992a052b78ed9d4d1d1fb763e89736a686d0f4f08e3cde722f10156fc97812376dac253c85c617e594a41d18e85903f9ca0929e03ccc621ee0aa7237ce099c9c2eed4efe63e03e14499edc66db7dd17693f8b8a518d0b9b4a0603fb9b3088717f2b0e6cddb17f21ce39a68407cd3ef3273319990f0d28f336e7842f1d09d624fe40eaf26c807a1b03f724500d46cbf2e26ab7e790fc5abc3003647b5cc39052aeb6e0ea9a0336d878468144bf409246d0520e8dcab7f4c49851d103f28cd7fded94b47a0d1405920b0f621ba6635699abb83f96c423da9698b11b11a8e22d60c3c1c5083c821a6ebcab42aad63dab961184a14bb907885180c4aae055aae2f321cfc179b7b161bb993d89066f2683ae9c3f8bfd65d9bd4f1dfd6a19c2e6e01410561683159b87c3cbf4bb93bfac99328353b562641fff8415054e02451d60ae7cff04f86b5ecdb52af1d649498d2b82693bbd6429e66100dddfee790d07b93e88d6878e57454687b909fa079eb089820ebec2f94e06b7bc818cb98c4e29f36d0060f8b05ac583cfb0482f4fbd5315a674ec96fa164f32fa84305fafd8bf8caa6e0a18da7e9c387154700b94b2bf16288ae4c1199acdb6e14da2b4ff6804440a235d957beff6fd45143ae1079fb2a84ac91f6198c0d8d294bf145b88762984a81ade2b346f3c01d060e15ac39e5ccbd50694a4f12eec56230d1cf72512d98086051760c421ca572a8cc81029145d94d260183ff66075a6bcfe198e2d248f5156af95c47ec14810e5362b03a728ccd430c836f20aa6690ab2b9ec6d5252e6ab414e3431675463f49e928b78f0860d5b85796cc6853dd77cf4fda302d96907b04857e1843eca7d46a90dbb1fd05f773acd46c1452a129c588b3f1265ee4eddd9254bcec19c278fffb28a2092cf8869e4029e930ad47bb6c09333f007bac377456562d8c7a8b4d521f7aac37e0489f08e34f3330b1de3846d97121d613539649163c11e81bf146814c45c050b14193984f54694c8d0e9ebcfc1f8e9e04306fdd4aaf591ed79544e11c054529b22f51c526ca5430cf37a7670c1b42f45e302b9efad70a23a1501156b51d4cc322f2bab0a1a36d8410362cc9f4d4797cce69d8501fe3504a56cf3196b40edc9de2c7f82b2de69f326e9dd7518c381fe5adbf57607c3b47f0e66d66f208b4015edd4360531e140470b74aae00fc948dce5eff6dc72979a8a1bcd290d37920dee1211a0a1e63af58f79f7e881da8a0344c41292d051d134867c5676489a1f16513bc8255f6b8a538061a38256693180d319c19810b9156e1741b53a6406a288af899384f22b1714c80699962c0315e75064511a9c133aad954c3108ac4a7bd3e5a6907ac3937d0291bfd3c689cc50f3f138d27dae5b2004b2c892f06696b293c306a2eaf534430a24fc4b1252e0cbe4d52738b780a6390d7ce59877f7cd6b3ef484ae78a0020f94c90f37e91ac3fcdfb7f932ae6b12ece3fd994295040f93035d9c053d8fe5cd99aa97c411233311fa93e57180ed839cbea1933957db9540505a6f1bc50adb44275fcd0b406c837e7c6c12f35abb8639342d7adbf6654dcf7bd66804fac7ffb2f1f2cf6b9f4daa82d0c2d64348a974d6679170cd338ec761e2407abce01c2229880fa6e39f0d6ebef8d98b2bd879b3fd26b2a9de8f931c1219d9d516a1b9772e4c6e7d1430990e3c3c3a65111e52aee5a49160f3bd52d79ffb3582d107190c957577e454cb252a1055d2a088ab446ca0636e0c31ab541337429df3918acd558e3009832967ac5617e8d4fab35ba0e88371c3fb67f3afd107c193ff1bb5481edeef57a4babbe691e1441f31e8fd85b08c31e538130b17b590bc2aaed3039730e726eddecd6519f9111efad6314f245920ad33d2e3714b0424cb24da3553142d3e0a4f19fa02e51a6f9b3b514ded773af37472ae8c070834a21b8cf0dd1bddca04578580949e843e512ad8b5a0d74ad3afa663abee0609ef168600e46674704973ead788fd41f7d9a23180dfdb4e5d612af4c3b992032446fbd1c0a47c0248982359e42cdda38340fa755c19e0d7a62d57b18ecd9509d6c5bf51ce89b5d69197f0dc39cb8b9eb3ee6328ba19d596525f92964c1e1f9adeb0b5f6b3482001c7c88ced8b407f84930ec649ae8f1f622b367294cbbfdde2caf327a981a0c80b7a7554e5dbcb38c735d55e101dcef2e4e067c84358a333437fc6aa2159ec056ace22fa0d25b9649ad567db1ff33c49815d0ac30e4f90093546eb3c68b30cca2a4824f3cdcc99863217e29f25b082dc694c6745aefaee021c1cfe9e7458d586bf90612f0826229028d80cdc321e0bbf0259cb14c1b1994473ec0c65c064e5107806257492a64a9c3c74eb56b1cc76f439a079cce6392b6d80dba6197438851234121e48b20aa5230cb61e3fb7e6f3f75c0a440497a7c61ab0958cc35131935ff1a9817c3728a6f0652bca438e83d47046f88d82b1990ec2b00b77775cfac6ed27fed040948196b8d1fbe14f35cc816508929b07a9386885e670b2328203896a4e833bb9b7290cbb01c51e8ee4fecd466c534081d39c37c4461696e19f7e9be627e60f7fb30c7679387ce845f107593c18b3e08a390f5440292e19df193ff9d768ba26d8f33bb0ac8e84a1b56f7f01037d8cf4a2ba25dd4d94fb9b8163305b611969322c56b39a079607b493ed06a3391a6eb2a28d6d87454217d39cc04fa067f6de63e8a4d2c28881bf216c9503c9221afe6bcaa2e42552e18e95310e4191aa2f417c84d804da9ec54a987f585ad7e01183e8bca24c01a021db7208b8827a0c399bb73a6fe0f1f47b78925ee4905efdfe2514b02306a75335049e5087a2839ee8e167eed71cdb8aae22728e628719677e891ad4d5fb0a9dac0616c88a76194540251a22fef54f0af5a118dd28233f9f2307e663b6cd75395d34e35f1f2f0ac461fd72a44ad7303e2386b5a793938f564a734648849a0942723f873ea979bffb9a8accfc15b5fddb1ee2b20756640bcabc31c91f1580a6f1b9b3a24dacbe3f43bbf0a71343d7801bc105153d1f22bc277f87d591d394ba98de6ea5dcea9f6b809d5e90c1478256b7d108081d9858bc431a5805cff2013e15a820925d585747587e955fb3bbca7940022ea8f77323b5b8b4bb19f08c9febcc4e4dc54620da11d8f7fcfd25e4a938089855eaa9a7ba9dd808fa808824377e7eaab420968009e44a0bb5b993b093043d19bd68dc016f57429a7379850e293938cca5e007cd161e10e4543bf40d5478ce50e62b50bc9b905e7743e39f560bf7a85ae82efc2557d4bf78cc140dc3d20ba48fd5f05de006b907bb6f125aa92881b591684a6e2e62014cba497fe1ad95e68925bd073a88bcb6a7d88e8c9435bd39ea541ec63e1b4d092fb610cb8ca6461eb6038d06d5d07f22fdc2607c99c8d58580c3d13a82a6d8591762010c8b9092f2bc57d0672de75beee6c4fde28b421426dc5f2a8b72216f516d8c3a0b32e1a822067b5a94d733a5cc54e0075bc5ab95f39160a109f53e529f5ace605255ab0d7c468ff6b7b350e61775d757dfe9050d73ef508b27af7c390ee2f6ab7a98dfdf6ef756791b08235d8e535b986a7f13284bd5f241314e1a3ced7f3e91949acbb8387a4463a86133cdcc173d25812415d6884e27f02cc0b99f6a310ad9d2f899f280a48aeabd9d5b5eeba2a1bf11a4aedab497c511c6d2028dfc5e7ab31312abb6edd41787e3493d117e7eba0bc403356d74069cc9d48db649c598a2a0c4b14570b5ec7a457307f8b0f15352209dad9fabe75e3350745ca868f82d5ed0ecfd7ff9bd9e7df9a2c2ce9ac18c7179178efb84fcce5acf02d9e65a8568654aaa61904f420b078401975628a48048507bb4c872d799a9a9e4e13d8a513aff7f1ccf8daacc5d84e57ad57a22c947e41240fd0b87f6ed5c282cd973e895e29e5641fdb645c4214bb821db20645a076ef65bf0e5a04d1bfe39c21cbd667ea3abf60f44c32299df98557b52ad27bcaa2782e91143b9278ff6096d85ec0e135dfaaa7d795693e55dd5648c937b77085a869b64685264c5835f6006d5dc2846eb546bba071658fdab43012bf5466322877ca68d84858bf04796ee10ac1037dab9a62da441c4a1c96ad120a42356230f9d75c58fef8853189d71c96ae5d46290de8d1c8b4d32782332616881bd8e41b5f7c19383c3ec12d7a609a7a2ab0f71b264b513467f4155f63f1c0d00bca37643e6fb69fdb6a1912abc021a07a59486746cf5341423b1a7be2fdc5f2177ce640124852f6add88193502a058c3f69b86aa1dcfdbb16a5c497c521dd25c5c492f50c5d1c0222e3927ba577961fbace8c91d650baaa95de1788d2dc106efae452b7e6e7ad58fde86e72ab81860167ac786dfb763863c60530285956adb3046c42ba88abbb34a367c11f46394c811f890c07d90ae3ea89dade1c84046bafe6dcd5092676daf5a033923f03601d0f8075d70d8156544693e50dfe04c7c223fdd18d6025b38c6a64be7007b3a1ee1783603e7ceee60f7679fbff4c9f47b9b2e428e7e50f4d0b1d3636e0ff8b3b848d53e3f6ce7482960b7364efd863d92bdc81e77b0034d7e6da01a45df9f3fa8787632f80b1c7e2086664f43cfc29321136f5f4bbd9f674ff85bf03281eac4a1e9f34d8c98f0d381424c161df2b63a911d966c66b51417c25ee2eb5483fba769b65604b1d5dfbe1a8a8c5f68f6bf1efd506e3145d22826ad2ed85a7c30e8dfc2f72f1a9e32c5f16238805fc9caccf3402e649f0f80d41ff56831b2e9012503271b120ad70978c96ff3aed7b07e0e1a8c4982b2d904af856732552be3d8b23c5b8d18d294abdddfbbb616a1cf0cf5d67e03ff1ab1421abb5251f62b1908d4c03cd808910f95d35d1d4d2d67117584f558c69bfb15b2c101164eb3086e3a9dc80042837ce0b20100c1184f0efc72b30a47225961d5bbd98bf0119a68ec1c927bf97c7aca8bb9fc82f2791a12ddda09e88539ff14e56810ed247c7612703ffa1abfcb5efd200fb6c9fffabe80b2d181c551088bc4101765716c1f4c3f764542a837357a581f2faa157e5cf271ce8c05ad82b26b7aa4e9753aa99fe0ef749ac9459f389824337073791320114a049e9c47be855b05d804aca66ba40af4d8cc53844b2186d6732a1a2cb4f2cc2af2ef41abcd3684be5e892815b09f607fbd69fa5caf236959cae8040e92d34d50897354599521a1b97ad4f878b8f2c11d2bf8a70a363848bbb46b8af38c22d203ec25dc5fa3cda81185354c277712eefad867d32629abec318845965a53ed51e0a891a76b525bb41dbc517a8b12f693f1f753a948cce09c3f031e0ca98441d6e9237adb49f920b4c7af736927664a2917bb53e64acbe0323c94bbc4841956f17142fdafde85294a617a4f43189ca37744b977d8099946fa961df772c241c65d76e1f1d98bc28fca810becb19268e6f98aa093de29751622ed488dd0e5d9eb2e3c7992288f198f3bd35eb1c375a232f6e1c9dfb107c44f384e6ba978b69627b096cf7edb43709d3f3d730822f86133db83e9bb10ab602a15ac089dbb3b461b16f3c5b91843890d18d893974e7312299c12490386ba27b4d2a02e53509240ed724f582243b675878a073eda224a63f429f346ab172868188dc108fc3dfcbc40f7ed5275737ea9436fa349cc5ce58f807eaa738831960ed20daf33faae5fbc8b63d8f1fa07d42988ff2d3e1066751d0f0fc8cf6ecc7294036c62dfb36fb7fc6f1c012f60e62e3f4df6d19b51d7c6fce2b1edd0953da5d1faaf4fb357818e829556cb7785f902ac640819c6b584e0dd32582db87816c4daf858cbd44300aa318f6b6f99c623f845c0d9b80f034d6b206efcf70a69a7e9b407c9279e0a1da86813e1fa530a605290899d66b1018d56b264d3c0c63367bdb8e30b4e5996aee67f858757fd9be0afcbfe6acf735066df3da5c231fea75900ca350c85440872b4a46a155f21f1523c7813d556e65c84541aaae60bfd33efb5863ed8e75bf5f1c0aef44b6c95da2f939b3a62b058ba5bccd74ed4b15159f5518b2da793333d6e3de2195d459c5d376c39cc24212277c5bcf166d56bfc9f50e03d92f6563085ca906f2745fe49f66e2ec3ef9a67eb6b41fd4c77a15066b57f88acfd63876c691d69bc2ea4c96722ed394852abf7753ca6c010a19259c74fa0b688230c210c1671b77715f3e09990851ac1b014d30a88376461752b39514e78d497599818c2af39af324bbbf32f26153c26134bf906576b7311f82054560bb24aeed7a159fe542d8412b0f4da1f3ad4de02f1463ced26b81fe33354c56dd6b33c66b8e5fd52d80ccc8e5940165b1f65caf5299538c4ad7eec699f4e44e80af42ea66fcaa8c69e13adb3aacfd2fd96ef20e7178b1a3e4662eea170cb87a34ebbbd54dff7c9264b67c87c5ab27743ba53b1022206d5c35750dc046cf3c250b09fcbb6936821ad7e1465e0ba443c100768a2400ba677724b2906ee2b11a7ee06a13384a216cbc9b5783d286b6549f0afad4e7f8f0b315443cb345c2bb0134a4a04dd275cba0c52eb6295c3f07710be59e7ec2b4ac37725bc32159c7639183ff39ec182085617147918809f6727fd5b6811929bc870db8c9491afa9cc0c6826a2458c7aed9ace74a19f2c07b5d78998b179be02c0953f9e50dcda1aa1fcfb74cdb85a5f44a0f31c33076788c36c86dbeca5966282998a61f732699da4c463ba0b56bde307ac2565700ca9624444316d448594e4a0b4a080de0437c5753eeed607cc855074c21885ce62751a8da5cd0066c9d29e023e07b888a6647b7219242779729d398a6142962dd274b89ca7610e47d4efbee32b9a985248cd9f723c912c422d25d38498743fd5d7deed7ded4693d7ab7f7b61a1c169b614d975400340932e806110e55daafbd514fd77e7a7916c6dce8e6ea768544804174edfd1c44c7b5bb37085f8ae5748e87b93aea521d7d9952008fb7d1d5444f6902be0ab938b12480a0ee5f7ede11b22a794e5d543b8c5bafae6ca418bedb00af8235797f387aed144b04c4124e992943708771d080de2a76d8018250939a0a60051da2a9ec317e83b220cd9afc14ae40a86a166acd04d21f74a3de6a3574c386cd03e0933a9e68594847190041d075db4ce052bfc03adff4178bd759d96e3977d43b3096e805dd77606ff4b95d52e679d27a16ee00adae62e8170ac494ee30606d2c10c0f2dec5cf432bb6c3478235186b15887627e1c35f50a3aab58273a978f157e464d41166a01ff83181a01b3930449b0005edcc90d46c8500e5f0c306debf6d1fea5981b13d048ea297048e7b7082b83657386686a6d3de52e07d70b7c240b212e7a39022680614d807e1ae57bbc343aaa6ef4408105cdb423cc97ff745a763ca3c99f5518bc3462f602deb001f3ac514d7f8e20872d21e7e090c23b07cfae2ece19b0f9a8c5e915f4a7709647033ce2c3e0b1e11bb66dedfcd21818f795b8de4949dba5ce79923c84c5c01e1d4e99ae0f0965c586619d230d23747039028b775d2668db972ac95f8ae0a24053e94d521f5ea166f1e8ed77a9c490c0f5d008a5cd9b3bb84eb5acbf151c7eeb212fd385f65b097166628d04c1c60e49ae0a74693df2e63e182cde6a93b4f2059d807608e5f999d4a74e73d31f8ded10196fc1c1f7c83a085adccd0de03f9e2299bfe8ab8b620ecbc5d694f3659679c0ce5377d761c7e726d83cc240b6fe97f0020b1c11b4408da965e1cfb4656f5675e907ea456dfb023f03c5cab2f7582a5a0d09330404e70f3e48484b9e1cb749b9b955af41e24f01c06ce01c789d5eef99b1c1623000e3375dd9c9f5ece3ecb56c82b4fa11dbeeeb4a07f8db2eba3f1f38ad421035a3495bd6dad6b652847d94a85b6ae8df6fa488c136a2b47f850d12c35c44efb2a7d12350fcd7e06d7bce8b3ae353309cf70d31101f4a876adcd4a3c08013091926f85c388bb0094b042457900f118312eb23410609c10e0c845802d48cb1fe2bc1e812b65078317286919e02a576bc14bb853668db09480bee99bc89c4cc81f7f2b47a7e89f0f638d978debfb88d04f3cf619ba08dc4b0ff2e187cc36620198b9256d9742b0208843b604666391b0f3fa319ce6d522f42555475dfdf786578bd88c92cdd03fbccbdfe3f35420034f42b4d90fed9ce202f131af7410e12a8fda60af2860686a1b037a51c3e3849b07b797cd42c44a2cbee983a281e824fd7f2125a4e4c7d3d3cf5bf2f8a4b95c20209a71a64025906d169a3600f4c8df80bccaca4bd96e8a8c62b14c685dc51f4f007e8a674ef67805604e8ec0beb36d43b482b4ab4898407690bb5fc11f0774b72d11548361b769183b29b90b035aa75e1ade321eac87378f12d0ef035c5a026a83429248101085de741ff76805f00040135c905350a22bfbff3fdf67e53f3f50eb4b2ea3b091efc8c10cb9a669fab98b6c9b818f086cb09fb0b3446fd73216a3d6604244f6de7bef2da59429bc081d099f08c337564d94e82d124f3be78dc470a13215a659c59cbbc1f2fb355ae2f4360ac2e45692d0169a62eb9c56364d2a9974cbcbab80403128bf2dc72888d4dad335dd9c73bb4b31878a39e7dc8e51cce19873ce5fbc2c6777276ee397b393132b81b112d8d4e57dabd465d9bd21cbd0bd808f874aab9885abdeef56566db7b2b2ea7a5815e7be81cab49f524a0d021bc1405508947233bcea77a887b977e54f79156740767261de1c3f4ff37149bbb7dcf34f09536fca39e79cf90fcb6b027e3dfc8fe2efa297d16ba7bc52e94d8a0769cc5881e146401246921a0f7ee83f2c749d011febfe6954766f9ee61f1cabcd5f453b9381b28906f9385a77b2950e7d7651ada344db44b1a087a260d01f513f9f2683e814744d9bcc97674e479498012ae57b9037346ce0ec49a3c54f11a8a94342bc84e901442e88dcd7192183f073ec30b11405140d42e1a21286d0af94df58062289a15328cf983c297dda9cf274b1e3e5080f0ef85cfab505b629f3b6a3501244727c164982fa530904205b3a983427502302eea21aa075d1ecc62a2f8a07ea4f91525400e8a328b4ca07b9881334f3791178e4650c06452d452d401545ff2e4ef4087d9203ed71c3e70941c9750c45aff28c3085032881e0faf62892d2610a183649fec09192255f1a126d42245a051521040ed0f5055327276591dede1f3d892ab94fe84e97224c0a197ac4d7c1924991f4a065c044d3445f28344c149abe1a495bb8d065896633bf882a51344fa1f077bea099929cb78326fa4c720089ce89fe1a3d089a95746876fb3e7a08edb804279a1f092345af2645f3222f2eb4c9525c9e436d60de180293c7ceadfde1e7824bd10d96f8c021060a7d487b510ea25ed0428cfe925476b222da00fdb82f994d18f4f5e47c64a8a411a28cf4efa1e456649125477a28e159b406be91019411142c521ae561f113bde0010a439fee4a56f421e4e80b4284e5501408dd008431fc4cf4e55c100dc142bb248a22896280e245f31e23f4f934880e008d1394c7901d9dfcc7033851bc426f26f488ee1b18a029021d8267cf10337712ddbd210cc232c90d0c5eee13127d137d6a14cd3872922868d7cfbd9e235cd0dfcb547edf7ebbb2756f566d67cc134f5321502ae65d8a4bed4781eab20498c4e743044867ea20d2212cf200ea86163b53889cc900d012278ea220a0ab5c22443a57ca5a1c41bfefe9408a56d882aa31d296fb7c803e203c71fae2b16caae6bdb371f57a6b4c1045392467e73770ee1f97a0cb6398780b077bb5e9a12acc7bbfb749db7e98baaba4bdedcadd2443b75123d9597431ada28b6eb1091d8c6dd4d08ce8a8c7288f4cca3c46a83cc6c35899c708c7303151a5eaaf73a6a6be06d7ad0aa90e48e1aeaaaa5099f5e6f1af8b6d1936e2f4d7395350c68e53a1f2c6ebb676825ed004eb046bac76cefbba6e1937a0b1ba83ef9b1e633eb0328f39f29ea66544592fe7dd80b3ebf6b23df7758536c2d025b2dab57218c7f036c25c0fb34e9fc1181f77c19be5b2dadb745ddbb58b69755d5be2dbc5dfcdb4514caf89101b5eadbffb5ea153fc3a0c5dfcb930233211d7f430ffda271a387f69892754b71fc54ad8c5baed3074b1ed2ecc5a293d4ca1876560e5c789a9b9498ea237e4ae0a3392fdd32d0787e0e20cf1cb8d60286e75de53db568a65599665d9766d3534231931e6d77f4c19eec3741fb662e0f5d7d75fd77df8eb8fb5aa6d5015dbac53573bdcdbe01aa43aa04d59e1ca0aa94cb46ca73a00aab3c2b5433db1ac50090765d8885735f88fe5d4572761f693f5e861ee4cfd67a787b9bb59d5ad661ac1b693d25eaf5d953d8f66dbac97f3b85098f1a0ec9bfc0556724348943f43766eb631c77141b277a78bcb6e8ec32392786f3d5ee50cace46e7bf1eefc4def71372dd47a5c882eeabc3604b835b3fb5ea28b4a3c3ddc5dc87ed3458d441735223ddc3df3d8f37beefedbbdc5032b5fcf6d4f55d7755db5d65a6b61951eae420dd56d880cf9d19387d7065666bc7f6a5266bcb50b1e7007a5c2eccdf162b675b390f7eeb66a7f55b7ddad9c34d56c2a84dfb07b0b0bf8387fe65cdfbd55cf6ab85fe9224f4b5321f08a28a5e6cc25c5da5397b3fd2daeaadad7becad8f1ee69c7781566b761091a2c18586915eb2b0f6acfdcd5fa0dbae757d5fe9a585b3b5015a641b76b0d79d7bcb3ed0a02dcf6b5ea63b815025b37b0128eb5e64a96555355edcf735f5f9f5ebcb78c35de3d4d378be8f1f0f21e6f90dc08757286dcac949a2eb6d0bf673ddb78a17fcf82b29c36e69713e7a7d03f4e1797c438c6dba5faf76ce8c32a188f6ddf69e8e284feddc536a67844398b2e02fbf7cc65f974710ca71ed5bf9fa8236e63a6abd2567109379805e961ee172c0433ddde90f7bf04bea7faf71c2557d1c5d5155675a9a092aabf6e50497ca5de5e1594f154c134620c0b739c1ee65798e12804f3980947a90e1f8f461b3a45f63141a8845f2825a4a1876a0ccc514e3dcae98b1e3a093397a0ae42d761b6f7dc897ebe4ea83c0a951f0797d04309415d450f7516bd4177d8aaad8353ec6dacb2ba722504d5182aa8c6ef5470f97a710cb6b7addee3c56e87b1ddce421b5d27b73f3f6caa28a1bb5d842e4ee86e2fb18d16badb536ce385eef6067411aabbbabb4a18d895400a5dd92b74e590aa2b897155571e63b743052f042d047393bd410bd9427411aa1fab84ca23914aa8240e2b0895c34e41a8ec40a050098c122a6129a19295ea4a388e928aeabfa384caa83e15cc721e4bf709c1bc440f759710cc48a482bf1e151cc650c11ceb376d64a137e86ea6879aa9a94705d9184a0f757f62fb5350c9c241e5e33905956f480924c64a2d8415e6257a58054916f246a59f08bd1f84db38ddc6ad5935a1a317d4348973bfa0a6c9076d9a360d5c1ba78dd3c6690bb6705a382d9cc75373bc6d1d38f7b60e5c001d5809ffbe5832302f808b89588db1f6d0d2617f30b0aa5b49d18173ea23dd696f7f61fcd7056edb56eb586b3d4cf9dad7d78717f30978cc6fb0597f71230737460f79efc4384d5341294ea475298fdba3c1450f79163de4ffff99cb979753420f391dacac8a3990773b447ef292dc44e8a55dd3b269e3b469da353d3bde8f8342e3df2990296ff7d27f617e6362a49d8fe037763ee0fdf76deafe84632d6c6add35e77ca3793fe71b31b43086db3f56395f59beba7cb76bde5de9b2c2189b73cef7d64a13b9ebce27d83d7daedd461a7c1936e25428c3860c609c85dad5c1186a4f53b11d6315da88b932a8d4b9a3793fe7bc956cdf69dbdfc456f2e67302ddd59ef2a00c601c4328c346dc761d94018c55e17397c831bbe35628c346bc0a63f4acaa42193b8ef1fde3d76acafcfa3ffc0bfed8fb06f5deefab5ffdaf2aa44aa0ed6e0c2aaeb086ee545a618ddd43e0d0790caa7bc6b17f6c0db6f10edd3d77adbb664ac1761e6cabc0ed99e537dce08e1063bbb7c136b73cb6fb8e1063bcefdf8b63f7dc7fc1b65f77eb636e5f836dd0ddb3b00dc7ee6ddfd1c75c2174f756d8e6d6c7defe4c0b6ce8c6b1fb8a14bab741b7ec96c7d8ee06db7684186bbb5b1e73fbab836fe74c5409f0fef615bb6761056e5f55fcd82baca057d0760d077fb36acf63bf2db08cef8b55610c2a3574a75283c32974cf4c5409f0aefb8afd636f664aa1b59ae3fd81d1c26ea30d1b16e8bebb05b9f3ee761b6d6e7d2c5da1f6b45b20b4b1a3b742b73e6681da59e12ab440cd3d15eefedd02352fb90e7e1638ecfd89817567b751f7d7cd8db7b971e18e311ddc9177f036372d3242e4be830bb568190cfcb878b092fa88a697f8e6bbb32ea4d8c9101c9bce6160377701a53448e4e36e5548143ea31e74d8b91e79f6fe5d5925c43fd7834c8f2f6adeef41139da8f6feff2fcab3ca2d44b991248a95bb499449b17257d163ca9d65bbad3117ae5a8f168427566a4451b152dbe1e5edc5bb6b3a55ada2e2c7c90719b48982684ca99bd8c54a5d254eacd45cf4173ab1528f59c7941a4e2ede587679f890c1216737274d11e1a18995596f4f43759bf62188f3315e62658e8b77967323a76e6fbd0c65122bb35bbc95d90797fdb6f6ca776324ae53be9ef773fc415914cfb9edcd5da023deddea4a78ccd03cc7e32e6e8a7f8ec7a0385703ad4801457448a092be74b133a66fbcf9900344c8ca8f4350c6b4a23937bb391e94549eae39ff3811fe418195bf38f79f14708e8720ad3f53d8447237a82c5192244e0a902845e686b031034987063cf034a25f12b8775213beb63c0dbcd43d2233b61e8ad74474f19aa8cee96c043d447566de289233b6b26afc733ce6f07879fa8b77908df9f3977a29ce5f8af7a7113fdc08d5550dfee2f7b79a66bd3f6dda9d5df3affdd3201cbffb55331389391e6462cd44a280fcc3efef37a5ec33154048d0d3a550f14611577d9eae81141e8e4fb782458a067451aa8ae8058c7f8ec79378c73fc78349ac04c6828cbc7578fc70f1f06873db587ad53c6a15257213097aa37972cc7125cdbd38e0de265e0c5da973d22242e266b023c80b66c78e57cca52e793d16d12174650f123a774a1b37ca99b70d98f7822c3ea0e848a247c71037900e371748df6b44521a46ee0586f2f4690185f04279f316e7ee11e3fee8a0ed23a5d0122f45e4da3078dddaa2af103d48772e062310fac3a60e519c570335ee9d0fdc362e6f172a21704026033a397cb86272e478ed546a0739ba842848a011f0b81c80e0480e4d19b7afcb9bc8ca3ba5499323717000a9844307df5e194873146930c84d8f9b47c78dc4e6d561a64df4e5111657ca13178b062fa1a890974a94470b89cf6514298edd9f42ef103e47eab8716e5e1f68de35615c365a9e4479db94fc2162e90277c81c14ea1e99d44a6ad40a12e18e9f3276a4c0797ba4f93106ba658429853678f5bcb961fa7ae0e8c3f132a1e42ec1eb73c8092037cf9d778e9c406b0c9169f73c70ab5401c3a41dc1884b84ef87ab86afadcdb3b4c1a3172ed18b45d0983c7473dc2f7321c071f1e2bab9f25a71d23242e2eeb814e44d7269477be9d29abc408bf008bd4af6b88de8ceb4e173c6f502e67d02b75982c02d14574ec94ba4c8cbc15e0f97ca5e8ed691a5d60fa457cb22974b21974f9fb7889d0ce2b493d6bc7465de2d5e166169f1a0b887945829f2bab1d4c3cd6229472b82a5770e92eb64d106855e11fab83cecb47d71ded6bc7eca1cf2d2ea6179df407107299954e4c553a9870b572987abc4526b07a9fdb3a8834283fab81fd879f7c441b2a64899378b17571116770e0a25256da4222d214a3d5e1028e53064a9bd83d467915ba7d01ba60f0f3bae993866d6bc5fcac8f1e20ac1f27a00e58a123b45de38bd1e7ef472b8462cb95b20bd4b16bd1814baeb53c88e5d1c286beacae4f0f29ec1e2a6815249c9db4191966f520f2d937244b1e452820467517ba8d01eccf6010166ed5892e3269973d19c791189a9c205909517fa2416f446d25bac64bb9c241facd0aaeb2e080b8aeeff2ad59e73cef907ebc92c9bf70fce6c865916867f3fae2ad338a79bbfc17a98fb050fffb8f0a78752b0aaaa733ceae24fd3746bdd97f62b022b7f3f0ca4f6c419895d6fb4ca9a5b75201e891bb28e64a5b55c242c14d8addf0fe0748aab07f3272e93481ac44aabf651a420c4a32e3efb04a68a5f2e2f2f4f51ac043a7af3fe30e5d1314d885f2e8f8c223a202b2622cfcbdab2eb3a85893502af793d5e152806ccd3a1a6404a3f5e05040219fd36a441bf2aa0cb42baf3833417af2d10d88790cac44ae26b97a6ebbaaeac2b07e6f1cb411263a5896943e2e0fd417a735f483d5a770ccc14bfdc23be970cecc62ff728efdd01a77781601516db756f2ae2e3c460bde689c3302b04bad1476660f8d197560a665df6519578851f4189d75eb5aeeb706ba6323f4471bf1cc90a7eb4245655a22af748c887b1f00f8a8f33fa4f59e2d40f98aaaa3accaaaac84eac0a30a6aa42e5aa762f554d215555555508a77c6b20f12755e508d213586de12a965d5d0fe0347e39478dda468762570447825e49626efc728ef0b88995433abd497c1191454aee388fd4f2491def18451def78d7811485c02ae213f5de81502486558426e69ca71c08c72fe76883154fba1d65c0eaad76ae724742524717ac299e5eac24d225a2938ab3d44fbd5184de903bbb55390c3bedb94b1df5307b1003e2287ee1c06bfc72788a62a554d51591c50b21cea313fb948aef7ef400480c0f4d148ce7e52705efdf784e62e5b08a568155786f4c3fa058bfeb93f0de496addbb42516daaeff2fc58a0b02f82d5f8e5eeea008dc2bbb8a7bb2d4f7754d8bb27e9dd12f52e83f56e08976be42850ac24fe04bd71fcbde049b012e650f1cb3502a1919a58198a96086708d0a8479cbd3de40632b204d132e50d98bc3d1276126031dc5220490ebbbdab1f7418b408b9b1e7c1d0ded3990586dcc531f0fcfc5892d437c383bd33707e10a8b3e407cb1adf549b246ce0fcfff3c9b19466840a74f6870a2302fdffd57f4ea5d8e6d30947e971a275f5e1282b709413388a02386aed5df43366dfd087bd27cdd09c4bb4ee44eb4fb4dea14613d944c228eb48b4feac41064721101522aa031c85e5c2457fb1442b434b736ee81f3cf461375b26fb070f7d89f622491641565616047b76ff3d0c868203437981a1a07c0f737bd8f32c99d6bbebe209e3055ab3f7e75932cd451246d913a9f621f8e1c3f387a1cc602811ffdd89f66423ebdd89d697c9b49e84f184a1faff339bc901f41a643d698636019a59673efb734d2df97c22b7044f3e9ccdac6793e94c4b43aa39b5cc924d240ca7d6b3c9746a25cbd0d29c271e7e3af634e493f9ecc85e23ed59966c369965fdc95c7667926956b6643beb4ba613ad895c53639675b3a419da04cce61a5633e964839facfeff7b98beb14ca6d140aa690888674b34144814046029335a1631ebcc7b399f4de49a99d740d440d641f40888109cb191c1c14ecc0dc31acf35b5640b275ad6191b59100feae257b5f5f9f30ff9d726d92c7dcaeb3be574426eca2c1737fe7ee31756cdb229df39ff94ce7afd39011e22ffda217d27357fce394dbdf4ab3fe7bc75899cd7ac73deea0074d679d5130490b39ad7d799296b6b551dba3d73adb990cc73d66e8ea473fb69e67a277356ea9c35ab80a6d259ab5ce7453a6755e704f254ce5bb36bd2bd36e509394b6077de20ebacea9cf56e9135775b2ffdcb20d89c5aed0f79d59a6f6d57ebbcc1a99829e05b2f899d423f7f2d25f33de4a9cefbd30c32a1fcfa6118fefc99679d1b655673c859a75aef9cbbb25666f34f6bad5500640d5a739d893beba5c066c84c5015f4919c6a0a99e79d5fa7d0ddcdfc359afadd45feb13fb826bf181bbdb39af99a99f246a2351bb6f37167ad79d7bf7698d39c5722dfc240ccda55591d96b5eeb3d5dc6abe27c842cd96f3c6caedab33bb662da62fe80bdac2fefdd929ab5a67260e6fae2968adb39af9596eadf29bea9ce62663ce39672a6d627d5b9dc9d8dfaa730299cd79ebcc73896c4183e92afdea55a779679df5ab579e77ba7336e69d87b9cdbf9c73877c41bb7bcd2ccf3ae7b09cd5095a2a739d539ec1f49bb7be90f7d69a6b9ec1b4ce1558ae73ce61d98276d79d39cb73cec69e811c580f9b8034efdc660ecc6d1bf6424075210bc0b3d6aa5e931996407c4be4acb3ce3963fdfa6bffc08d8ce740a3ef0e384144e740d0901d0348421d283048d0af08f279154eea245520e9bc009cc06d2080201381049c28f15220bdc44e38b900a904b28a08e604a805866cc2621cc464650cbab938285f2abae8e3a0d8f93ecb85a50b582eaae82b415398579dc88180265777185cf41f863e05f54da2227817e990ef3cc7ff7e16c3ff4ff1ff153cfc3e9366cc32b4251b536e12fee70372320703ff7902abff2ce3f76697c6d0cc0c5963c330a9c674b670a2656d1892b1395b98256394256194653d97c89c2ba519808a15260a3021a245012654b468200e40ff00a26f182e936959676c6433d2d626f87f6207d19d6c4bb467fbffb6875937c40ea23f9748b52c3367336bc97c816c9a69d578917c229b4fa75659d28ceb9976a6a5c6b64c554c6ae1e76e4ede92911fa89f369314801b3e4a52ba9b92a38033aeac6f459387313b0f589050b4d2c2e68c4b862e32967732d0e344c780e55a0c4029d84049d0e2a1670f93e6193b3c20409265236b14893064d853ba6e508003810b1f39ee87b54327929d004f435a4e128e9776200ddaac0c406420023b51084a797ca81c61b43c323243a3c490ffbf11965a0c818e2c775101763c62a6f99ff1f5f6ef22359fb7b0fe2fb4417f3389fda3e8ff2bddffbbd11d92c0f934d2a07f2446a0ef3426f67947937f0df0ff46f4ff66ffeffe7f0ec0dad79065cee459d2ac8c0c09763416bfbcbab8b688ad910cffed0ff5f9ff49b6e633e94c439e399b59664c35a286c1b63a88de6cb29571e861cda4d38904033b8219c18a605f605e605d605c605b604430b0e3f1683c168f5f47af63d791ebb875241ec18c47a3d158347e19bd8c5d462ee3969168042b1e8bc662b1f855f42a7615b98a5b456211ecebf865fc2a7e7d7d797d757d717d6d7d11bfc0bc8e5e46afa2d797979757971797d79617d10bacebd865ec2a767d757975757571756d7511bbc0b88e5c46ae22d717971757171717d71617910b6cebb865dc2a6e7d6d796d756d716d6d6d11b7c08847a29158247e11bd885d442ee216914844fbffb58739dc3f0da2b399a5c6b6d47aaa9d259bccb2f6bf818779024f8696d6649221ffffc2ff5bf8ffaaffa7faff0aff6fe29fbf71fdf31d49b634e404dd45b339c2455f22fb92d95d6c18ae219b33966c6161cf34366752002eba59322d0d59f6ff148053ff3fe15feaffa3fe1fea1ffeff76aff046fe3ff0e16da38739cd966c4cb27ec6ec4f3666673e5b3cd1d2d69cffeff4ff4dfffbfec1437736b3b8b28c595b58cee4b3c6938d590379e2fffb7fcef2ef7f8efbff071ed6d07f67336b2beb2b8b8875fc7292699d6d11895f5b4832322e2e33e3d659118b8996e54c3e91cdb22c1a49b3255a8de6d389f622d9d462be6822cb9afef58dff577b58eb7736b39e6a674fe6b29ba199753564b2f974f6174bb41a69cda4d992ad27613cfb320963466f329bcd645f22bb5972d9647b269bcf65522d99b6a686d69f68c8e6b3bf58a23d9f48b526f3ac23ffbffdffccfefffadf64369b23fe3ffd7f0aff2fe3e1d7fb4f4ed06206b2f9f4f0fbf9ff19ccb4b366720266aff1442b43ae21cb9e7c8ef2ffcc87ffc43b9b595a359ac826195a9a5659d3a9458696468696a6c5b67c8145836d899686b59cc15cc658b26179155b148f34b088595bc7ac2d2de7120d09a32c8dc97c6aadb13d9d6c4d2d32ada41a1386d6ff333dfc58ff2fe1e107fe9b25d3d09067dd997cd620ebfd8cd9974cb427b2ef7ffeff3f1f01efe1610d81be9b2dd998671dabbb88a0d8d5afb820709184f1ec4bb49e444beb4f279b5a0f7b2ed15ad0403343365b349fce166acce61993d3a6da67a0fc07949f5039f4bc468ac253a069fa5b33f08972275b7cea17058b8a01949f6827b855c01cd0d601fec0bef03ed51f4b823daaeeee0074d33850760c548a45b4356e0e1ebd81a0291cad8007fa7b0acf405b60935e3ba45ae99b9b8497c2111cdc07d429e814dc584f5a2cf0a90f474a007b5c3d6ca7ed818d02dd01d8d2525d350b74bf6a057ef56eedcc7ffcca259112b03aa056505d55f993fa49b5c076c2eac0be8443adb0b9e048dd40075072b5de62000b3cece7a43ef1bcb13600788adf94aa570656aa15452aa129abef4ef96fefadf7bad9ddba557caf0aac504e29dca7555df35eb917d7e299a5b0a9a22d86140bb4294bf449e554ac081c3c100e618bfe7d7c87f6e8a943f7c60ddb0b1543848c180bf1c1c85cde35d2d2003bb12b040f2080a26f4e5d08188210911c11fb2481f2d0698346cc162a5a59198cb03d83389b911ce1f9b9533707e6860512b078a0010450144002c385a90970656e6ce9c0820a5a58a8622e3134050224430ddac73749120b08a6263ce569c3260d1a31613c0802e482161238c00002284cd099c3060433484af4e143a7cd1c9a3353a2f4d091c3115e9e3b68cec075698326cd4c8c7862c213c150a0377365c87cb132e447d2ac85042c2060c0c4880e611b9e98e8cc79b38192216d4933090c304162448717c1109d3970d89421f3c5032b4480b4456080890f1dc28cfcfb44300408ce1b367365c8786085c810203fda08b588804508adac3d4b270e706afa5325c4478f0618203ef9d1a3060d2d6ddfe6d397524bba128b855fd1563495cca5b7383167bd589b80dd2760d055996aadb02964209fca13da2828a80d73a75f13cc54e147e1ad729fda955557adaa69baf3ab6dee0071a06bbbb6bc892daeedb6f05b1145216487aacb1ef9cf29f39fda82cfd84cfb89ebc99104aa36e9b5e5574c168e1d40dd057e66fcaa29aabda8520578563da8817e3821e22987e62e3fb1611274041c1ce8a2390d7bc22016d5bcc2c65acb4110290177a0aa8a21459b04a7311dbb80280f288596819fd44f6a002424acb14e5712d4c3b32dc00511786d37531a835fe023caf226d54276ea8a1e7ad272c0d3c6ea1d69d26f2af6b482f103921070c111950e5d126e069505e7b082e1f8c0490138ae04990013db98ca4e6020077dd9ce94e460e272ca0b54ed0e2b8562d45dc154976dda4613056c0b2e0949507735c1a4822d41358284db96f8a930f3f001748a5f155d352c8a4975df114209a6405db6c405b21ed074c369561350ddb483ca01c4d8cbd66a604ba8ee4a8149446bc551b4535255192a4ce0c01270800032445d366bed62ea329a48b1fef85a210ddbc5f6c73574b1b55d5dae951ec08f6239a5dac17402b165cb36d8880e40d69dd01aa30040f17ab1436ec5add42b500e4d5c444a407aa1b5e2554e5bdb020e689656de671b8bef9402a10445ce30975b3aa08be3b2e54a140c6e5440c319e32c59e64202160f34b080022a529c30a044d8b16b986100013a6733d777677728048801841528ac4d409043654a022342845990f07b553938325aaa508902c549930d14186ad0ca529264858abebd3cb207ee1aa169c17c221204ed0c440777ad324488b1dda3c74a9530b0e294d4ebb26b159598302c926608a030c0048911615e5b1ba09e9a983807402ee00125e8cbcc960eaedc606001631128c5ebd0415a00c48762bbaa1eec20d3e58991223d2860c2010741104001a283865fb6b42b693d8ed71b6944fc901e940aca7c741e8da7bdf3abd375341d0da73dd85b740caecc5619036b21ad62a2a0a7b4842cf5149542e9a70cf326fda6ae6e5956af6baa79de3913f0ffee5780836e3e8d0c89e2041cdc4232d3ce5ad44033d352635b12fb07101df94c4b9a259f4e75c4f05301fcfff3e18ec03b9b59cb66f2f944ae69359fce082070375cb9c36105bd02f7caa5a0f7b07ed529e8bdf7feff1b347cd761c40ea23793309e4ca257f7d09d4fb41768cc65763e8c81c3ffbf40237bbef56737ac3f91dd2c19a30c6d895c4b2e99285cf42513edb986405696053616b8bcd932f94476a7052166cb246c4182131dc6f3c53299d6cf983dd9649e3d9bc8a7b3275f2c914dd8c2f0d3c8904f36350b4cdfc3cc9264bd861932d94c4306a0c96c3647cc60369bcc313906fceb3b14ffba904ed345dd0528ffc97eb26811cb4cd9ddb261abd83d78e85bc3eea177023ac072c50ab183e812fe733c4c01c93b9b596c4eb4e68b13dc699111344b328166ec11b1237140a10f912f85a8ac78d87ceabaa63658e2634cecb5e380bc58a2258188be20575b0a9aa021b19de50f96d5ee011571800403a0424747d55ecb32ec7bc83ae46d90da0a1c82955872b2e4b7c16f775002471121d190ad0d99ef4e87e10e9ec240dbd5de567b60017217466c49853cbda8cc04274e50892202b6d3749694d8d813858a080f8674982a7beaa86051c88b113f5a09a12660fc1cc1400cabd35124131e80444150452039fa4c4e9fa3c40e0b1a52e4f409b73d48538f84312138bd25c60f1a152b129d99f3454f90c42649130a12262cb2e43d294ad478f501f33140943c08100926663468d2a2d092dc26030e5f58543ad2c4cb5bcee2e64bc59c1b23117cf09189ec806007110894cca969cb33beac30e4a4c61fe00c1b99c5a39606055f2a88a1e399c79ea841d120c9dd0c03d2b2063eb9915985b51f8acc32139100a05d4da13ad4c0ff23b9082c501a936121c98ad793a30d0ba40296649f7f335c00258f3edc68a922e29f302a7bb255276749838777638280860c53498423bc5eed4e08c72858124161fcab086201d71c0a14b158fb3fc61d9943c19b558297ff0bd1106034f4dedab6ffad85c6a2b92c64bf3e4488eaa4c07d739688edf579a92366aa0142149257797d1af88093d33253b8c9b1ebb3004e04d68cc853854586faca2ef100f328c7c369f2d367e28a959506684e2276eafab20cb180e53810d04195d327a54108426ce828b891458e8f12085c065020b9b515068e2f10062c9e1e1a270931bef0c97d2062ad48902a4ae864e1b3a267a5051db767056f51f8824c096486c91811c60459c2f70c446986d5da060bccbcf13d6004850e1f43c86450c0075f8837707d2ebcd21055b4f10dc083ac459a66c3801e36f85c3060b454b9d2071df3bc740789905e11406b1c6548bb74070a96b306985af0916476894c9f2c6956159049696371a94991184ce69419e08d91159774ccfd11a1a1821d10c89cb814a3cd4d5da010d884e820e2528a2ec71796387c7501e2e1d2d79d978b648a431611bc2e5135d941078423688ee45d5dfa3c4f1d7106c011d6c66a6f04214614318f25902050b517470dfc560c80a5058c527b1ee0f000552382307318f8ed19e9408e8921623454a8f5f13d0a02910ba864aa99505a164346194a040000000002f316003028100a88048224cb812053f61e14000868c834503626114d0581280a82200642100661000060000680100660280830a6d20b1e40e5463e7904fd5aa929c8d05be7c07b02bff9833264ab8c1cd7afcc95bda7434395c3c0fbf27ef18012a03bcf4efa285f0d604cf11f982b8deacd2797c8a76a9fff81929a17b267d3c1d4da7b60dca9f9d72b3710234a2bfb11b471985dda5bde1c9dd68da73297f21284e05febe56570fef59dcba3708fb0fdf967b325f33b711a586bd9b46adde6ad074fd30c3187d42f2d166edff823e6ab1b177b8b1c768add890736cfdd4de4a6fe8c587a163faa9e44f27b829a62ade8532f104b81e9695b871e4bcfcdb77ea6940f06aca8b5281783e2c76695ee600141f3dd0ee323e18019beaadf52907e2446fc557526061f1e603517fe2c1ebb87debf91d7456150a5ab312d0d0d17d16fd81fd8c5713a3f390ef60900b036a47b8b27305a7f77012fee99a601df3d261ba7bfb1d72cf30e85789ace4df4d370fb73bcb54bdda0582c5d973f635a5bf6055a9125bd99f55277d368e67b0d6e83bb877e20fb68c15b430d5a2fb56259a7d627101000a7326b175df7ff63585bc6151a91e975f315e78a65eea2164fa37bbfb1d72be9167d91f45cf88fd8d14dd765bff1f1ca0ddc174b915222de0f4ebed414ecfb133aa05940deaa4c435b9169464c61983df519f06e34c16e1f00751a11ea17b0e5f3747bf4f57bf43ce347f7822c06d217d7fd1ba78cefcff1dc341744cbe885d376d527ef5fb739b9b1b2c35eadf478f88cf6e48997ed0a39bc63d99fa18147914dd9f4f45cde32c1de8e9fffe036d344d7d8946ff18962afe7dfd274f82b51eba352335bcaaa9b73e48b0d1cf93b690c48985f73bfac56f2efe6a4fa721ccdb31106684e6ef1510b7d0c358bf14abc41f48f4ba8ea07ffb02ad2afd5187e4221ff1bdafa755d6cf36128f0af7f98c04b5fead6ffc87cfa4fce4b557c0427d9996c4bcfb853cfb53f5337e315a9cf3189e253e9ccc434da3961eb288ebfb6596a861c7f598ab252f5bafacb528e785fe206b19ef48c2ec3c6424bc396d5e4b0d6c44de0c863fdcacddd9e88c802f2b29922d5f79fb877bf47bd0efe853cf03acc5968cabdeebfd0bbfe8b7a28d0ff41c1fb59605a4f14fed8af804ba15e375e51bd16886d2c1a3d3b39770d12130de7903fdaa53cb87bc4f0c4ab9aebfd3fff9b59673620e539cac60f55f97df849bf6ea3e961e620c59bf761841a7f2f7608b940543d0eae2d0e2e1d3b7896e3b67bacf22658ea01ad336bb8183548112b08b2b08952fb7f604834cfddc3cd9ffdf804f96f3145f613c94dd4e769dadcb2fce29003d00ff4a2e5630326b3f3daf56fe0c907006debd5c26d74b5be45629645f56fb9bcdb8be92147939ececb9ba7555239ae54d2718c72e8412ddee1d5882a8e0387a98772d43d7b5621778c5f2fd56364c555ca3d1bc17dff3e6d0d3b2c25f99a1f135e9166f75d24f5e2aaba58a36b5f47b197687e7bcd53a0dc47eda862d0df517ed3eaaad1daea3af942539e054fae318580ba839047e0a9fc3b6736f001d48bf78c69c217439fca48d7ad3cff0fdb76a023ffadb98f331a789bb404eca2a2597e91e4c1564d666d151d3cd45cb1f05542260f2a6c2e8de5952c1cd326547f7820d4760616a9a14e86cd75f5d042d6e19a40ac996257982b997b119ccdedca99bc4b08b35c31be75868355d837668703e6c6162dc15d38ce4dcdc28fa67df33aeb61a751fc9eb4dc10ab6c2fb28138dada0dbe760006bf68a5b8fb4215eb719db1f08b02d0cf86df1ad71a081a5d36b78400570bee3ab77ab09fed3f5c8733d05906db7dcd491e21d322a26772e220205f57c9be48de399469db95b0a614770f8b27839772b781eb7fa0b52e107d9c6c170ea762f6c131bb0bdaa407d527e0ae11165a31db23be78493b69f81361d7f6358099c8f34df6f41dcd8e37ffaa68a5542c04b6966a0584c2faafacb4be5d23cba3dcce35793ebdbfde383dea08fa7f1005a80f4fb66f89aaad57c1e2055983e17d0052f1a74d42331c0278ed5deffae83ffc7081dde3de650330ea44dcd7bb3e1a891589fc5841c030abc5262bed31aa097e3c71908a17d8fc36e854fdfb2dc6880d02f8f8e20bda08c33bf0fc16725025e98a06b868be42505182b3736ec5eba8048e62914b8af39d1fbd3b33da2a8aafc613a6a4ed75fd5aa9b0691b7cde30f0cc6769f23773825e538e9644a8d7fdc33bc39733ea59db21dc42a447f6e8e8155ead21d11af7bf8fd75579c2dec914818e725a8990c18e8d53eae8dd6298cd11772cfb57f22e8c570f36c38e8cea84ed1fc89dfa173f4c90e75ac4fd27a55702d9ec8fae57c654a5a61b78401d10dae10dcae313b3944d7eb0c389b81632d23591c223b3a5139e295f9a404f6530d8cb4f8582c1e338ab7f1b938baf8e8ed51ea8da8b64e791195ff154219f2b91f33f636c63b90c41541faa3c00c710af82bdb1be78153a4111183d8bfcdd7e982736499fbba52f883b6a39aa392812a1b71c21b4577a87a1625c15effe790ea3d659be1e4bb51a0b037564fcb56317828b693502dd807c0ec204694c6c5c43a9fd326d0d43283fd9032d255e60474bbdc50cee79cb64a5bdfba359e29310d57e6751d5f0ea93edf9677f346589e608e9d119b0745d3b0743a4d999c40dab630a1aeb046d0fb45bd2c44ea3e51c4d761000b77d85f4366a9711e3b85748f33267a5faf550958fe3562a55d717296081f20e79275a99cfbb8652c8661330314177b0662b791d48a277686a0e0c9c0b8966f83f489c08658e5aaf3838d8ffbed4ca5916020d8e4dad905ef3070aa07e06b1666316d693b8322c4af3502a3cc8c1f89ae9802fe52e0ef96c184cfc6310d30ac980e378f497c524941c84fcd0ffbf6625f439e812a541767cae3399305b883b6790b70b2b89308a1192eb071cbace6984c60b8f140c4b7a452cc99ff596b8a74bd498b57a8ddee460f515181bddfb37094764440f0d419a6481f16024fd6fe6ebbb8295e4473e6f467f4c01ee083e8ea49e817ae2e974a524fbec74014325bd28041624c84b980241de11c17c8658ed7da66fd5254101c2beabbccfef05e6c39955bdc25f75b8a64c0da3f87257987f2b44430df19e1cdf158e5ba09628aa44de0990a77e2d571a705e5153e16a7c7988cfbf40b884e0f574d4f95fd43003408cbc0ad35166a1654ddc110b430d430a7d60e2c7d3f4a676be101b4f1e7104343e5fb0b3c30c6176fd1d8204eedfc94678c06ae54c9702664d5086dc38870ce7f1d71f5c323a464411e001b43d85ec22f4ae980928eb5f368ca7a0d4766db81a463cd2e2577666129f7d10c4f1391d21f4a6f5e74146a046723f17299122f910788e6070cbe80d56d60e7e2cb446cfc101b2ef321d631616c159e6b0aa945dda677d5c7d28cff86b39c3948f5cac3db0111be7512ea6f43a46f7ebad44c7283fe9c226229cc6cac8fafce1abb10feca8cc9be3454b0585cc9f3f3d8fdad83b98c3317358db36f038056d660c60c848a06fbbc0162fb97e796723ffa6ec4cc50a01eed0de6170325513ddf8094c5a89f5c8f41604eab37924361f1061117ba95287ac1b8b47f0f83e680d6eb490c1577a52949f969ea97079a65b69b1d01ce6c356e90754f2163e272cdc11326b0aa54b04738d9f9f67cef6da196ddf6eb02c3fa175cf6ab942a076682c1da4e91d47b49627bcd4cf0075f0ce019ff0a85771011e16c0c6ac8f8a460f2757a1349714a0b77b9383fc884027eaa9201fdef8b403b867830cfa252517f86265bb90ea838d077bcb12f0f1be36411d1542f08fb4b443af35c9c5ea25d00dd49df0e49b296d42a6a7e8671c7533065f49fa23f83ccb7139d2eb009a50680f3307984cfa9910d5d06a6f10e86720fec894a2b62c5cbaf0a22b199e1b57389f57513896c78a75955ebf886413753308fd176db0521eef100d396ac7e40aca1a492df60229c6744b792ce8c13f93e912e178e45328c78f5ce789614b68ec4aa6497b410fc4df31e50294e510813d6a6d00fc3a808c97fb254c811e3e007b08ce9a6502d567552ff01902f6d08410df18be80586183946780cb1dcc21aad9081da4f1824774eb135d544e88779150137490c7238c51cd8fc41cd556b0e344bbad067a5d531714b4d91ec15db142b86f810c79d52abc8b0a5be8419c1fba029fb66b83fa9cf8c70c3227e15f1ee669f871802cd079f41b079c73400402ab8fb80eaab0463c0185ceb43b41991b61fb0b42db5fa355b5f5d7166c1cc1a78f2f2f4b4134f73007b6adf657048b470f510f8298ef0fc31c62787b128e9c4b24b88a866a2e000d8f1084959107d1b8accc33ee016c45f0367800365aff07fa6758816c5eb2be2233577bc1167e27dfe7cf8970ba576dd94fa17c1a5c48e825350bed1e18cc4881c10ce0982f901c7af88fa7bc422c9e726570eb931a7b749238d51c9a8d7e905dd0d5b2623ec17e66472ab1829356abedc74c15f44476a792e0b83c3186992b907e4d2ab6dcca9b7648fe2f64c31293f820e455f85deeb84562a18ec32ab3a0a536233959f78e0f564abe07113e49c9ee23e21936993fb434b84adc7d4d67dcad40f34f241d0eb1a4fd97abf0f84e4892d56417e20a49c1f733e46dd3332bebfe3875eba7b385d75f4fec7aea4854faeefda09df9d0f64be3d131fdf77782601cc6d4cbf1e89df211baa73157cd091d793a81bbbf5a658cb578bc84c9073016add5dae1c713de09145b7fe0214f929ca11abbb24dce50af18dcfae5591b643185d1b17b523b26d73a76ccb7ac13d53199e80823579d8e264196f7a84426b686da26e8b5d0bf8c090750f8082c55edc9519b0dc7f4c466919b4ad973cb62ee172484a3733cc44dc01ca60382052c8ec87631ce2f455ddf80825cd18f6157f8ce0d945c57559e10ad4c70dc718b5a07483b92319f2bc764f0b961b098db88ba7477565a1e5e54f33c02f91984ee4c0452f238e7d222324bbe542a295b689e0e9d27a09d53d840d362d97c2c02514bb5ba7471023ce7135dcc6d332c8c9a9a1c31b55db0254e1f4839458096c3a15e8080318c87e32881a9b2b729b6f8463c9e022d9298c6d61b8fd310de019dec9886d07742c039c7652f1aee2eb87df813f6f8d17089d46193e68e85966b381280e06104414bc9d408c5c57047ae877801e0743372e76fd9ffb766b22067fff981c4cbc3e9a1c0d57accca511585ce90916167deba91da9c2377ed690fce05e30664b25768db5801b8f5a8b137bfc66af87cc44a20bd59f927b0f634730267230b8e9cc8f0fb1b189e0d73e24f52ea233246732bf105492d2e2f5b5a222bb12725d123170061db7b6e36295336b49ff8ba57e621fc648e1f118724270c3bf871ed83f37a0e6cc22d0dcf769100746745a101b5ea79852fe0baeb2a5a30c849403f732d7457329bf2a136f2bc739b7a11bfb32530f582e1dcba272f7af230b51864adb7e5fe1b217fee815f54f4be605e933ce08011e2a9918ab4222dc2c555be5d54f06f1a27a383c54957a6f4631c29d52753d78ef86da17a4f42f5a5077fee85f501e1d7405f818b3bd740bbee1385fad824b2bb02a7c46047ae826b331ee13e05f8300e61f5dcb69df1cf74d808401c2a3e0d730917e07420e615ad42fc3d6387f08736f967abfba90c7a5186f98795b15c1558012fd7034708f54f0084b3f498406b52c687242e856c90f585c955f49dc09c3c21d9555f15940afb985620a29b2e449f56bfd6a80afccff28110fa3dcf1cea372c388d1a03863318398efd9d39821c3d7211dd67eb093986dd71509ae420088d03667420dc85a6c3d62a21f907b17baf86cc5130cfc3d7ba0c94251a4ce601cab58543fbac8da80090ff546f83c8061db5a8222f4c54d6eb2ac5e49e1f7fe24a07df23b1b47fe24d30cc8b3ed08ece303c2034cf4e5c51e10ea86e5454106bd977060699a0442efbdb282d84c945578296a9481ff5aa9d521f0ae3b28ef2a31d272ea44f7216621c941c95572010ece7914b9dea9d8f4d3592947c2614e18545fe3e90b7c40a1509b562619cc8b00e47b87c16a4d408239d9483678cb79d48b88509d3c224126ed2ad5921a6db66d82d4d65b9c97506abd8db8b328fd39b039b6c3c616ad49c6fe427f11e02475e7a82181da3123a666c8dbf77516661b687d867d8c7411e0330fd11055b4fa278f95e60ea6417ef9fb964c85c3634a224f4531a0a455a59eaacd3fc23f9887feaae8ddf4adbe9088c905aabf170c5339ef1636bbcf6e1c68693fecf1356d743432e8da0135685aeab8185b4910d48413c603ed581f6117b53da9a1dad168b48b65d6b0a44d27d9959f16a357c9a300b04cd6c0165aeb884c352f2fb27c706423dd57fa75ec3a637c9dacf206afe1a5da71a124bfd1aa7b378b105ccb792faa85ba744fd53c43cde9f0010c4a559fd152b2ec0e816029c86d0020bda715212f9031feb9c3e4a201155af8e4dc27860130089786a0ccd909474f8171639d2232e10d1c3f00a815d3e1b4a096b29d8eed74b86494e75a6ff85606709cf95ecb08ec1c2f619f0db576c8f3e3b25efb7410a6907e273fa62bf6d8aabbd7deae3b2fffee18fb6940d0d3fa93e04d684e3210eac286d35520eef47997732e1be70b5591a613e903e4a9797477bd04566deb1c35aaba62fa983759e7ac5b54c984c5e5d288245b0353aaf60ab634576c98f77edaae382370b64bf079b5cb9e2da6f88239f4edc60ca26405c29d9b594c7d249bfab7cc3b53fa183f1920238718302b0a3477e6c12d9153f6b6e6c317a010603a3d5d1991040ac1832b2d1908e0ad74032b38860d76db59fb8218634171b190ba35c8eaa5b10b519520b93121a9879e21a820772abbf20bb3803f8a3fe36c4e24dc5b81883c189606702e6fb372890e1a2a4ab115c970b441c68b9975cf9e0f50c12d3865f21254c1a0a2cd7ebefa955232c1112cf829ec88f803cc955d0dced5a9066e7bb872295eb0918fcf37c7a9ce7ddc04591b57726dbea0ae8a3032acc095798166c300036d3137708f2e839ea73d7da8a6d8dff768b6db82c2215a49d7e01699ba0b63bf8852715293ee8d67130b64b636d8d802f2a09b4df27dacdb4074c0ba15f72f130cca65e23158f7a37d356f6b59ebde652190ad334f7544843e2b17c89839383f3aca3477fb9e5f6cfb497df4056a42b7f40d3b443c6dfd2e98da940cf3bdb3ce2c4a34938b1301f741bfbd29adcbf740386e8250bc72dbfc2ccc1176ba6b27b929a70d1f0691107e44fbb436497deb6e36a46e35d434e63e866d3c57979916165e99d515c299de0471673c5b135cd89117a57dc34707b4e310875903a5461eeb7e9ff3bdb00029b017c62c84cb3cab469b2eb8c35b263adae9add798df1c7e0eefaca41c04003669c5c71447d80ae78269ba13405f3126181131538e6024097cace3a1480b8f2e9c9926b5b82a594655d7ecfaf4cd3c2c7b2f2bffce09b226c9a77ff849ed257edc3a6f0c2c167c8454cd5afe2d906515ff2cf10784b0faa8260a9e7cd01b964842a189786620c15a58bd5f9c1fe77e02859a90ad44a773bff8a333d80ba1cffcc1fd07f058e127955ed2fb04b4e76f11ba7e7a1fc2146889105601aa7ffff37fb5ffcb87ff77ffcdf802f796f08584bef5641b7d471d68050a2558d4c05582905974b97abce6ff94d8c763520732c148a5d5a22a896a91dae996cb114502ed7e3c3671d3094e267c80d32b6aadb68284bcdea37e38fe02ef559155c25379841532947e1fedcfe102d4b9296f9f57fc52fc91fc1a334f6556094266716674abe91ecb5705324544ae1169ac8da8c1f0aff42b104b40af8e9ffec2fe85fe06ff02db9b721784a13af8261a9e86630962ad626fe175ea5a9544151ba364a3feb7ffe497f72bff01fe43788c980617b49a04406196262325c20e9d85b2598960028c9da4e7f92962512ac82a9a460c3ff51a60845b20a1c4b952a609d0a8c4b466d03368989ddffa6bffd3f9a48b6b47f8fe05d32d95b7f8373a96ca6d0283d4e15c44adda80879dad0bdc2b224205b08b04472e6e6e652f124d538915cd355e95ff5dffe97bf21ff0666e95e9df831ff44fd179ea5f255f96dfcd3f42328958ad6f8979a29c1a935e23f215592ac2131f5b9446f375fff066c693a0ca2ade452d8fe709910f8c6be350e3de8767f59da5069f52f6c94ba6ad7effeeffedc9ff707fe6fd0977a6a8b1f9d3f02bfc4aaedff16f8961cabfbd79869620e950140495ee5bca906e9a555a6c2a5cddae5aeb72df878f8017e49ab0c0159b2585bfd0deed298da84ae521faac05cca5bfed7992e2ed4afe0d7fa95ff44fd080ea5db3afd71d3f465463f02bc84b6ce3fe61fd57ffc8f68329082291c4b2956f05b499b02844fa26c03e2c596180377c9519de92fc04a935a088c12ce8afd75fceb7e74ffb99ffd3fff8f301948b1152c94bc5205a592abd5a63b33feb969607b91f8e67c6709d025f595f67f034f895bdbf617bca58fedf7371096c6c310402506548150f27a33184a156b937e049752a50af2af81bb44145550965cacf6bffec6bff447fcc7fd079a12c4580f7f0cd4a53336ff8bbfd8bf8049231163070ca57a45c8d44c880a88cbd2123211d1002f4963cc81bd52fa86c02f81d4067f88e692b135ee87dadfc0ac242a1e981c50a706fdd43f27eb8aaf401ae5d284b5799381c69ad5a44f79a5a6c43dd83f7b5dcad9b1317acf8064f27cdacc7a033557ce7e7fd007d63a4a7bccbc205a7181c7ad3ffa3e27c1db5b212e0fcdffb2a4f8dcb3d0516ef1782f410a98cab37866cdd0e66414c2286025669a6c4627a9308ac9053c8b0bdb44b4eb145be66506fb30174d0bd099c11f741342921a0233843499d9b8f729273c86afecbc1c111e864d1c9658ae0ae492172a210cf15a80814629ad39a832e092758af3bb1c4ab1f33b7ff54d0e0893c4d804bdf9a5fab5246c01f863fe4f3828818b437fc1a4646cc3bffe974da38a8d7e04b5d2cc0ac96490c461aa8112898cba42ef846b3ba67011d9eb9d7ccd41f1e9940e4e2c3c05a34dea701e556fd06c8b99899f14f18a9e3fa89a6b7de77afbd31c55b03c24fb4bc2d9aab9baa5e8704699591a1d91890bad709c9373d6c5f546a0fa860f540a67c4fef9e1ed147a8cbb427b75e8a09e0ad98ec5fd1506bf5680b3381c400ae38e73d41414d1cef5d1f70ee1510f908b71cf54d9486fb25dcb27cb8e5d52fbd5574a29d6aec890a7b3339d913a34f320bc3d2c3800dafecf19858cf1ea249143af596a7e8604f619a234b3b05e606210cf9064b928cfd259c1649d0d347bb406761802f4ec6d9fcf2048f566429dcfe0d286f613e84c7448f3536895e64f0353413d663b0cfede59067f350c8556e3f4c7941f181a6b598cd424e86e9e2bcda12389934188b28663f1dc09b7e14144d13fe10e0425c9fcd58070ddb0121caa5e124f0dfa89c9642bcbe95781e96ec27fd84df06d803912ac8f108759281f040483cb7e1c6e9a0c4c2c7238e5a592de74606bad4d0866b304a0c495fc44a1126572b7dd4f9c7c1f8eba836bc64c43b58cf9d0b1069e518c9f6afb02016521669eb697f00e1baba2ed0e8ca4ecc6ea20ce420bf52f3a015053c35c1a93234e16363bc4281f19e1e643e428f3073e24e956f1a037b4bc925d6560f68b9e62974eb4c82c2eb6129e2f1950eac5db4bdbe495c90a7127941b902702a8d3233c62fd05fa949ea8a212d766d00f6c8c5d0a80b2a7634e3cbe7c2a5699b369b6bca47cb981c1ea109bc74c373517f9cc96501676749727a9c803cc2b205df12fd4eb9c4571b6044da46f8f41c6a87af54ff09e92d40dd58bdf05451510899191ffb08bdcabe69c037098cb8029330bf01fba289107ec399d16bff93df91a845c07bdee4afea1f4a4c6824d45e4dac4ee6d8f9c861a6b58c285a65df6a6a60ab831fb038c159261e1aed74cacdd8d11ba0aa525fefec23e5c183b4d5da5d58616d780a57a07a4eb7c3d68b77382f20534441dfba1a053a48e8188ef0f014bb93267d99a6db85f037ac20b00e606556d3ddc91cb46940202be7ee95aa1074d5d82eefae772b3487a53ee66fea994cbdca159708448f785733e21e556c9af3873f12b7ef0cb5c395e6687f0c39b9c7c9018540253632c9316aa5195d8f6ea1404859cfa4c3a54de77c3ae43c50589f8e1a75b8d0b0a2a76166ef5b00e193b6206c678ecc96f4d50bc934cd6e3c9c5a1dd6608b02b70c5b7a74d3560b898964a61c07dec87afc901acc5d716821f5c1c4bdb7daf045eeb53fea647966f9e34d03677294c89cb66e43da655d9ec201c91de31244ab0b6f80ad5f0a628ab5c5892528d1dadc2e93eb6a05c60f092174806a99f79ea9e6a7d25f909da0a5e09f3d056866fade13210712d0a3791862476f1e31069c82e00334ab2c4ae8aaecaef805e1ac09c047d55ace993acff78802942343f1f4a371af17adb1fa8111736b140429635a209e36725f6545154f14adece348b6d89f324fa18c9ff6194bc5fd883adb90ed16eddc51e2d6f9d9e2d03b1ede59de1645740630222d60bf07697444251f033fcda4bed65e9eb12a86c55b53ba05e8972f9cd0a4748ec41c9b830b6032ce9d80618e064683f40214f8eb53b99c4e0085823205a3d54885d317e6084e4fe0bdf01e2ecc8119fb92658d415410dcc4d86df79db6c7952382c0483a3b7a0a8e3383cd2dcb67d49ced2ed2669958c8dd392560a843de143ee3858599a2ea489ab502f1cb9662a8a8f8242d687745c9a79ee08d634aa188ee6ba5db4c7ba0a54cbff2a7dd3aa98d9d5c4162c6e042b7185108de657f5b78687b24bd5e7f6bdbc04d3cd1b929520b0554602b0c4f30de2f3c6f9a3c1f0ced814ad2a0f0b1a4e59afcd8af306121b876e95107310a3923521539100295424b45bca4497fc03ddeda861f4237e06c054ae5df8e7bbfebf09b298e0333bbc3a21b58d017051d85b94c5d1ed2317b7db447c24c0756ddb07e52956b9566e7968ee5456e5d0d678d08f855c4d1d9de77cfc2ddf582f852b1d533fed4694cc17eca1995859c0cae1d9ebaf429263fa87b33a4111538227daab0eb709fd632f11c5ef5a918a6ca130f5fe5b591dcc0d5e6ea9847f447cabcfd51181d0ff176835c65a22f93508d0a1f77d8b932c0df3fe84b5d0a1dce5f00aea9f1dfa0af58c3a0d10018fd130becf90ce90ba6ef6b8dc9a239001d25aeb8f988b9cff8df943fac33e8b2dafad85846eb5e0b5c9f4593e1f92e91de80b300c3847c24939a72e84b78b9e0b893fc601436503b54764f26588fb4b4016fcb92e4028db45045c049b54080cba3d8f4126f224eeeddff971eaadd34f7c6f492b4084ad657044193a244d53f0e3a377ea9bb3333e6afb6c8f1031a57d4c080ceeb2d1d6e2fa892b0120a2ebdd1a4a445c065dee56c1254192b7fc2a90fcd21e87441e5618f1d4fbba2768ccbd3ed6bb26ad0cd8287be3061d106d795665e6e31a1978a3e09c8a5e6e5f97e5248339c249d6aedfef5e8eb7ce966cb854afef3aeb52d5db7f61292e49dcb6da7abebb136a4adc7d1d2a073a0a60caaca1b2c46d77819a4a218cc383327e9a941be74b1549f2e4325566becd97baa79f6373a5e97fc92f9ce803be585498543a20ad91261d575ce07f610fb8fc6a1d6b1f420707185293c1c1fbcab3d986915c17dba2c5c2c1fa40a6c300430c081103744880dae1ac16db9b2b0831b4a834d22ae1278cca28b18773a984ebd44707294c26142934f79e848fcf14b7e87a86edc62aaa8ab3dcd9f2776c3d70cd7f2efee38abc58836bb199a24f94b627190c174a3d33e6c8e64ff00d5f453ee740a16dd6958f1eaf10ccc60bc51a72c54657b27d3aa5ab1e4fcc4404034520ad16264885c7426d07245479eae6734f3fdb58a9d526f87f0b68b746c82a83c5126363a374b3402cdb262edf82df5142383e191d2e5bd552dc4916557de42453e98019984452a1a9a403316bfc6c668b5ddb1d491406bd5251cd7749ac4d73f3446cd6c7c52060d506ed47dc3f8cbd601915ae71149d8151813f4c5881e31863d1c1221ad6c61d834864e67d8204376de9f58d05e18aa0a83ae003cb1e98aa2e269147701c1e83474d82f44f8086980f3e1bc14a2c7a303d9883969b63a1cb816bb4031275ede16ca664d324c33334ff3bc81ccb9c52f4862ec18209754235abd65fc4876ef2b7660c630457f14146da6b380ff1ac3a3856c2f1ff12d8cc175229a7e5d3ea65b70e7de1beb444542181096c020c1239428e249b0413a02189817249a5312f44b8ee69fdcb20c051e069e88ccf7973ab96abf66bebb9b1c434027d624a06bbf203d0b3777a574dfa0b73273095b8f6f9986118703efe4b573d5a80ff48cf46b1427b9bd1f23e4aa4cf9bab38d16a8111800d24ac7edac924b8f4b0c31c88b3648888060839f9858632ac75ee611523d1bf646a0c9240e8c8712b3883550bdc7ab2753f369d8469c117e711538244c3310068dc10f9a6fae166520ed9409e2d01e41fd6736541cb9fa89fb09cef7a20b56f21bf0c4abb740107b215d474f73303b7be652bb02677c587018d450e3f4aa40d40cb0c4a82467257a9c09791d0d302ff7809ea2492ef6f31e8257686510778340ae208e0b16daa12632507fdb07b3dada891aff44d43299ee5d4d03d9201781b90cf060310c833ccc702d1052cdf903e11ab0ecdf851321224d949c8f140578e37f8d51e39dd92b939d03892243a1bb8591506787b94ec682855967000cad0d952dde9d8021970c28a12db8ebe683edef9da10f9decb36ddd8f62a0a3b026fb45da4916759cfaa414980154be6a84772f41374deedf948222f979447d7a3c2bcab6827fc081c184f781f8c6b9a9b93d34044f6c83950823aaf55ab78212d13163f5a2ed7400dcfda7db7699a0f1e728682c9ac200768b4d6a4774eaeb97fe535bcea3d5490756c4ce6c4c00e9283c876a7b320f5400e6c00a95f324bc35ec06db65faf4f13c5fd42d0e37a281aa7e7e041e4ae13d3d97fda10ed17e26c7549e944191a8aca1cc714d922264b9e6f3ed83ed74f008a5f0c962cc9d13d0ac40bdec4f15ace0c0bca71ed2b84728ba4c7c620548436267e8921b0b086d76fc56a68f4da9896c4047e6a62f6d0310db81f2c51a16f1bb4b1c0de1450ff12a667db3f912886d2352665f0485fbc2c78c24d403e033ab1a77ed42eace7a1143bdeaaee8304757ee865669922df69a344bcda3e10a5c2f0f83f13909ff82f9dd546b5acbc027d8118cdb96dc52f4d1918aec806e914c163f3e1b6c2b5e0d75afddd0183a0322dca3da50bc3fabf8319663255e57b3d3a86fcfc1ac6c03f4b4aba8f8300219aabb86cee2820170f327293ad01886e150506740e92a207388edffbb7a0712ec7018946ecd6b2b83458e1e65929a0886c37204b5c04703b804e98eea31bc32bd904472d54a6c5bf18210c52477be2a210ba657c2be744bd14caf6813b7707a03fa1a2e3ba27abce9b4503820c64301b9c87415a6709190017681d06f8bf4c906e864b27aeee416a59b43789e84f1cc49f7cc474e201d61da29a0d38132de04732a2d7c7bcdcfc8975ca2826cf18a5e8cabe2bfe5b1af31e7dd5b2bb216f67eccde786518a66b8e6a482326c40dee2b207b147fb74713a72600e7bbc3b86ad07ced301c8ce2e94f2a8e6f3b893581fc89c6dccf33eb2eb9ff642dec8ed5698df8ceded69f0584a75fa3677ee5652024e320bcdc4db24350b21030ad6dc74da9dfd28d660a83480ccbe68d6c020b2d238c65a89cf52742e0f0a08e2543d02c039b4ecc4d487b125425be220e27e958bcc8a2874d5c0d7dff41ad1f14c4afc624571d20266214cfd052e9588bb1e411038b7da250ac55f4e5aed0af1a082be7de357b99dd2ce85ae7a776d93d84c521206919529934cc052777b83ee7f4f7b657777ef3db2e180f357f181e8c0402d05f21caf24f31e71be98fbdc9f1b8cbfe6178e5b9e8fff62f985dd1706ba73c8f9ccc2213c873c5edcecc6b01b037961ecc2300ba03b873e1e9c63d78d45f8e720f007771e2f2c84dbb7cce7f2f993fd8fc303297afeb2eec7c0b55027bf7010f6e1675d1e07c3bc708431fc2607e5f7bf5f867b3d8c833091502707671e73209087410fc7c010068cd6f5a1eccac03618ee852fdcb97ffffbbf8f63391b94c31706064378f66561be6c1117ade37edb7f6132390cf641359f16f7798cc57d1edef541b545371679807df0b7ed641ef7db76f397611f94151de0f3f1319f8ff72fd807e5302cd479de8e03491cb41c0459fb3d8ed7361feb5a5a223e5a13f082c6284d55e3d026ad45209fe59f3b2312830884ce1d3275eed097ce1ddad25a7b10002dcc90ac7386469d2b044a6b7d8ed02ab4266084560bf9d1b9425e74aed09bce15da3a4788059d23e448e708cdd1394258748e504fe708fdd0394230744e03503aa741071a8e7f9138b1177665d7cd25f301c46ac50680b4fe9a600330add57c3e3e16f4ff05b206b6db9df928534b5b83ecd61c61693ed6d4763b30b698f7c6de60f8725f38c3efd85a3094b9dbdd97cd95e1579ee186ba50e69fe5be50e66f61bd3976d9f0e5e11b7acef6e2e01c6cbbde609ffc7f1bc781213c876f99388ee41b4ff36570dfbf65bc6bd0214c8dd66d199418fcc1a08b0ec13038128261b015829910820dcaa3750836a88a0ec106a1691d820d221182090a41eb10ec4d08b613e413948660827408768118adf505bc102c67c10a16fc09c18ce89c05432c08b5d621582013b40ec13e0864279099d6baf8b76d909d83ecfff1980343f8057b61570e025f37f45cac82371580015482d6fabf652309a4c7ff3866b87fa6637dbf99ecf385c57d1e61656ff099fc7d40375e83d7e637088f55c5fdc9a2b5e66115f0cf5a88e667d41f135aff826f58a3c62ff8fa81b3aae127d5646b9f17d8b13e7f661856b332fb44d17dc8b4f605bbef2c03005d767fa628b023bb3f19149ca1e084fc83b2ec8633078633e2a00fb9cf17d424299302b2cc674eb6c647448e4f95d67a8f05fc7b285aebdc9eadd95ef775c3eeb2c166ab8fe1fe65ac5931f1a0a12da6718881262606e8d0c0135d14128f14f07187090739a8708042256a0d6b038b017accf861458960519532ad8500e61a914874e066c66134d1e95db1258d15395a0b04d94de0954212002f0cf979c1a88760a38570096924ee21e8ad908c12085ce5beeab8e89a80521d23ed560223300252408b1318e090f4a060d18c00283005ae1ac8608b0f2b84b7cf0c4f9bcc12f5d1a91e52720430aa082835193c164da0b0207407146f938b2b25348dba44a2930339dc080228e1c20938af265c809c74d2c20391a900c4a001a4f0700584b536436404d9a17c50c82466064d84ab2a6dd4f058c8729403c31c274bbea91d23407a307cb08183a94e24d614d924568746033cb80604f948aba2db1397250949054d3eb54901079f371e89de381e1128a8711845c2c6ae9365511120d906b5b12f4027ce28ad558ae00330696a2091a9d46197c6039bd7577a00c31f53094ceac2ae714324538888050b1a503c4824e3cf056d133e7c18d0da7062a8f2f2060d200c6b2856ac60135998fa657c88168a38d9a822c48137674220715cc992c944154e8666f74818470f181d4a521093030e01129c01836528819363497bc1c59335a60c11d0652c22b230d4f1a2637eff0f08fbbedbf7679e34adf5240dfba0ec84cdaf8da62a411820219199952156bca6344a130106405a21f8090f4a65114ac0fc20eeb75539a12299c4c1bdb90567395e0284df5f9b12b7c75684c810214180aca9a5fd403beb61c663878e1c3856b231b13019ba2faf5331607471bd20c0c5560b162b4e852a52a0386142ab048911224e9da23a22c1227806c6c55dfe6eeeecfedc1808060606b6e563f84d96395ad80c9ff5668ef60bca61a929ab69ad43f05c2c35bfccc53c17bb334be233276bebb0fc8171f37c243ef21e698fb0478e1c617124c3005a430b0da2d67aa77375dec8e4d689bafdf5c2b292ddf7f1979368ad95e81c9d4a757668ff9784336438fe4cf8f785f2ecb261ebf66439d6cd7deb4e1df9d5bf87fb17ec83329d9e96cb0f3c60c4a883458a38d05a274a0091d61a83d687f40f8e7168ad3768c090d63e1fffa9c1f05afcdb362856838a0244541d502d9213468e752e0639635aff706ba0afba56ecd065bacbff1ec338d680f8ef5fb7cb3fadcca7a989f5f0a90989670efc69decf7f9661b3590e92f9ff06fb77855347ebf9fd6e1180a0b5d6fd82ef0f79114cd15a97adf97c7c2c8236ad232b0b7f6f4df7c232877b3de6f177837571af8f075fd70c8ef7d0b9b3fc63b8f5eb8affc26d9c87adefcb305908f65fd717fee07986c542d70de3e0fcd9e2f84f0ec2716a305918c34af82fe42110066b93dd387cdfe2c29987b01cc23efe6eff0dd611eafcff71b07ff97579e1cbbb171c7f26218c389f8fff18465b16aef091b61609cfc274ac8569eb5ad89a96235cc5c23535203d42ddfb8170b8c667790ec2610bbfdcebcbe1fbd5e2deb05027cfee1b7479bc202f2ee6f1fcc2ddbb32db0bfb388f17ca5f6e5bf87b1eff1fdc43d9150b753c1e2cfb78102612f278bcd04918f29993855ea1178d9fdb5303f21e9f8dd708c33034200cc3ef73813fb8c32f8fc70b435d0bf3646c531db70c9f2f08ecf53fc7cd6ecc81327e70d7b1c1deecb2c990012603e7b9c70bff570b7541d88db7e1ebf61f03bf6fe1173e93d53852d3456bad7bf12204f3eece4b6e7f1caa5992e68534711a56eb100c9e355587064669ad759f7d7bf38a2eae36d849d8c50506a6d3855e06846108f7deb8e6f8cbaf0b0656a575c68406c8b4d6bd6eb89321a381e08d83ec4edef97c7c37bfdfed8c9032219cf171a64c6b6d464cce4c93288d50c8e18882b70c5cd05a6378c3260234107e0aa15a6baa02c218f101460b9a3f5a6b2a2e9d5474c500d088a3b5ae325b0bc0c4e50517515a6babac2f8b3c9159e00f23ad750606c68ca2fa00ccd40dad75d5a550083479810c09a8d61a0033bc00caf40e29a1f4f56546879e83401e10064ed0ff8567ef7ae1d831800003bd187ed9e6377cfb6fe815feb84c254db6fe08756e9bccd7e697bf60b00fca4dcaac91e1e332e419f641b599fb80bfbf7681055aeb403d74328f2787330f43ffba6d08cf21129eacc633e2baf17e0c8fe9d4c299f778baefe15ff8dd5efe43ddda9b5d6018dadafb7d335cd00586fde092df5bdbe9782d1cfe1cba1ffcc125f16fe33fb73c9d777d3c48a8fbbe30eee5aefc737f31dcc61f28f31e0c9479bc597e5dcec643f9bff1770be1f08df9f7c130307c61d7c3bd196e8e7bb3fbf6e7b70c2d2c9475b1ecc6bc2d43c203038b3fcf881bffe012790e79bcdeef3a5312ed6dcc15facf16ff5ff87b72d8eb3313f707cf72b0ff98c72e07865d619c0f0672c96e0cfcc1bd8bab854399c70bfdeff5a01cb66e0c875e3cef865eaffb35fbdc201bfb087565bdf9f7e6b62cd4f1bccb0bbdfbfd1bf7721ef49ae5e0eff5a1853a2f9987311cb6af3c07813c9fef7bff65ce7ead3dc2d27084bc2cd4b9401ef2c47863b20e9ec3b898bbf1997b1d8e5027bbf2ffc17d0ec22e304c880f9d0be4619bcce7b6854339eef3d0b940ae16eac842dd07c6c508df78acf3ffcb619ccfc7e1306ce158d75d1f1b04ae6661637fe6fd857b5a18f2d2760ef2421e7e7776e13e77dd42ff651808f7c3b8dff6bfec44e7ae98d1b92b5e74eeca139dbb22ebdc959fce5de13a770588ce5d29d3b92b3574eeca97d63a805c860e1ac4871158595412d0392a51748e4aac73545a9da32244e7a898e91c1553e7a870e91c15113a47c54ae7a678d2b92995ae387400a1250593ce49f940e7a46ca0735202e99c9407744eca023a27458bce4971d3392944744e4a013a2745d43afe71dc0c3ef3bfd7f94f9bdd9f6bf6d9b82cff5f0cc7f1f7053ddc436bfd466b8dc18dd69a0a02ad35551bad35151632adb50f18f4585e6021a1b59bbbf24278c5030cb4d632fcc657eee4aeb0d19ac70bbdefbbadecfefcff3ef8ccfb601dfebf40decf048e71cc5f96c3808c8dbdc1be2f16c7f95e1f2e77e32f10d9bf411c56805656d890a10a0a5a8e756caf1bcf729cef0d721f5076c91e70604d862a3a3450d943c5926c83461cfbfbfb834bbe1cf2d1070cce2187e35f84cfbe4f46e67db08eb897f780e135fec9d4d85e97fbf703e1d6b72ccd4cec8ad61a86ce4d79604a161d82b1b08f0dcf7ca8ed08c92933b41cebe20ff4fdde2efe3b369dff4b425d5b0cbf85f0fcc2b292109ec3f8cb5a6b35693490ff1e98d51c45d467e2ca330f2ef9e167e5d07fad75036834ec836a695438a20c817d506bbd0019adb506fd99b8f2ae85e118d8fe78eeb1b90a8cd1bb188ee30fcf3b607097b63310adb518306afdbbeddaf87f0303dbf9df7f10fffd1b84c5daf817a2b526a1b5e66aa14768ddeadc133ebf6de7e31d0ea3bd3c39b52e755bb7bf7c7c07c79fc9ee855fb8f9e784a7f39e87fe87be3027a9930e2fff8a7f9346b92688b4d6211cff35a1a0c999d0461882ad50b496d16484d640d6c0c0e41776e5f7cf701fac27731d6060dd8dfffbd980725b877d6271b3ebe3730cf7805b6bebbf37a6b5aeaaa252d35a43915df9bf3f47a89f68cd3231d3baf6c761c87f41368cf37d37577e7ffbc9c6da64bea6f6d3789cf9efe1b1a05a1c042f9030f8ac4de66d325ff321f770675fb087ff387c3e3e06c7bfc80b9f8d91bd7087d7cae035d9ff7fd63f50edfff859ff40b5b70c0ed03aa673703f9dcc7b6e211f5bf3f9f8189f3959972c7f9f8f39db8fe3cff5f9ccc9421ec6f077e399c7935daf637bddaef1b7719f58d7af37bb31cce6e35c616f4dc7d3c183756e87659987b2ace3f1d87eccc6c6c3df5b434b4d35359d1bbe610bff36fe8373db3908b33c7c83df36d90d435dcc7dfdfb306e8e3f108e81210b1ca929cb2c52534d2df47fe62f9b2b7f1f44f7f5babc9743084ba693f9cc7d4fe63afc7f83dffb60ae5fd823d4c1f19f2ef44199b321984c40d89bddb71f9fe5f0bff0ffc1720b7fb18533fcc2c162a1ee3371e5afd70d3f5bcc7d21e74c5c390c02a3b5af1b7ed97d0b5f980de3eff6dff0c770fbe3df9bdf6028bb210f5b37e4fd1facc60b756465dde5610cbf6e186c7b32d711eae459867de1677b331b9b1bcfe16767f80de117eebff0fcc2fdfdbd39e4f1fc9783a1978d351a318ca3adc33e312e5ef88171b3fce33fffde6f7bf8ba3fd97d79afff5fc8f6badf8de750fe6c3c86e7d0d7bfe61e9beb0b651fcadd9d65d7e70ba301cffdbb85bc2b8c63bffc63e08fb9c290b3bd2e10c87b843a1e4f2dd4f92fcbbfc331702483e107c6cd3cde1312eac230e640af50e715ffdefcd9e0399c83c0d0e7e35dafcb15ea66f80d5b37ece20abfece338421defc7f30bf7106edf70e6ff2d18af85baaed90de1597e01e43f2e39d4cdb2eb0b7936d668c83c99fb78bc189ed5c2005ab789ff4bc2193d99f7be1c3ca3327d2a7c58be5c5ba9b64f5aeb0074aead84b64a6d87da0c15d24038ec72c11582c908bb5c84a1978b100c08875e2e42ae3004f392432446be877bdb98360342b02eae998740389edfbf8be7d7056b43734a0429914017ba72e8f5baedf7614fd7c3f0ed9f856dc0a4f9b403fb5252448731dcc26f68e58c25bf6b93f9bc83fdcee773db76bbefe1be9bbf3c7f598e756c6f93d5a71d7e61404163a883a7aace0ebf6ad4f86cfe5bd139360d71f1fc7e1940fe6b831876f3fbdd601f9465200dd807d5641ba1971a8d18c6e163b9cfc7c7c0c036ec5eb8efe4ef77eff7deeded5c20df016530b0d6f7e51ea6d3807b6f2e077afddf86e1cf73a03bf35a38f4e205e7ce7961d8ffedcec7dd39febfddedfce7ffb7bbf7fbfe6f6dd0f81f47d9ff6dd0d75f17e8ceff07f700035b7b835fde894be2573818ffe21df8f0fcd6391ec92bb133b413b32b20b783a1d3f1e62fefe2eff32fef857ff7c2bcb08dcf606040bc4bc69383bc7f962b0cfefc1b873bfcea927d9c377fb5b5d7078f2be4756d2e1fbcf06b2c6cd14227c341de83fb6187fb317fe3e1170b75f2fcd9c2d9dded42d90dc3dd7fe133243c0fbbf147fb057feeebf60003e3198963c397f7e630fe9f05fef82c7f3ebf7865def36ac31f9685baf803c3df5fb80d767bddf0b3bbffe5dd0f8e5b784e5229091f2df3b71c3a97a4974444b249d715c3ae1bef7d56767f39241868fd06911880840b18377bffba41b698872fdcbf0ccfb31cbbed1151476419f68573100e5fb6d7f5225be7202bc3bd235974a9ce1dd1a1ebf2f07c8484d68dd2e58cf0d15ad725ffdf863a36dde70afd37b280ceff37de9b8df0b4d619e10a7965bcf0f7d68a3c0afd8fe159fef1ec72853f3806ca41b94814add9ef734578fe1711350fe1f97543ffe7d7fd1179a4b5eb7fdc432f1e2f8c63c3b7352260b4d6f0ecbf1bfce30abd9ad6fa86ce11d1a1f373e7dffffebab2877f4434681f7fe3b197dd78967f1cc6cdee90425a760bc386a8d16243805ae7dfdf63737b210ccff20f32842be8e11086e7708886e3cf44081fad753c1bde07eba0d1fa985feec63c88f7c13d2159b4d6fa2d9cdfff8be34019428668ade3e160f27fdc0ae1fafa31302ee66e77dbff7eb12027f06438de8317fa2ffbffe3ef19d9fcfedf0bbb36fe8fdbdddb7f67f97b30b0b55c100f40fa0081cb0191b1f6a907fd8fdf38c8f2bcbe8f75f9ebb6f1dfffc9f28fbde7e1ee8d83ec2ee421ccf6ba6daeef0512ca6ef8c64116187ce3a0d81aa2b5596bade3bf1e07612f99c3ad6b29d438508b5303a296aa9dd075bd71b0d7c3d6e7b2ae2cc3c037d8e77b5d37de0b67f89dbf2ffc42b84271fb7cfcb7e17773f91bdc7be3855fba1e21d8970e48daa6b43f5ab7e6c347daef31dc86716cc881a4bda599691d6f86e19817ca6e0b8731f705d97efc42e77ec0f9b1f6830a2d4e0e8d4a0e4d0d6d4c6bfd43e77ca4e0c38c8fb1b313c274ee4cce0fc6335708fcbf2dfcf3cb631e64eb75b7f7167af1ef0b671ecec0df7b731717efa197cc7bb27f83adf705e19e1c86bcf0ffd7fdc57ffe9e7f1f0cb3615808f6a59bdfef02ddfec178b3fc3d2ffcd2e9747dfea197ec25c73feee5712c07fe18f8df5f1884e3bcdcb690c36b61fcd9d0ab8bf783f1cc0b6537e47d37d7c777c0ffdb785ca11cc3a157e8e57f5b877d61dccb67ceff4f27efbe8fe5bb109e43398cddf9fde1efcd6737ce32f4f0d463019d877bf3ffb5f0fde69e4e37f7603ccb21d8fcf2b73008c785ffe7ce2e54b58d2331a2a6b55e75ae87969927333866484c9d33e3d2ba18e6f13eebc5439987df873eeeceef7f837d9f0d65577e117f37f7f71ff3fe8f71f0645e6fe6dfef76f16f79f0cb86ffc76de8dde012d717ff96c70a3cf4e8748e0716adf51ab6d0f178e17773431ecafeff3794e3f89b8fcf301c87712c1bc6f95e388ebf856cefcce59087af1bfefef2a02cbbbfe7c3d2e41796f98fe3efb6eefc7e5fd6d661b3f73381e35666e33e1c3c9ee156998dfbdaffe376cd9779777e83f08fc3edebca3d99f776f157a3b5ccdfc0783be0b4e66f613b7468dd93ddd76d77846cafff32efedd05a973dd25aeb1ff7b0323e40fee3b03230fafd4020ef85af8f325e9ccf176c65f9f71efcbef00dd35ab7d0b932516badc3742ceadc07fcf1f770973ddbcbe6febedcd917fc8561391d607cfc679f9bd3217e1d39f82c97e391d6bdb0cbf6ba71d0872e742ec71badb5fff3ecfed81c5572dcf86ff367c35e9ff9af853f8e4d9a2d0e0243b6d7fd6c0c0c7f7f7ffcdb1f8770fc997cbdb21bfeb8009dc3a146ebf8cc7df8675786bf5ef8fddeed6e502d8763d621e1b5c97c8d6724f4df15d3391c24d6495aeb8284311cf27c3ededd5537771ecf703f84e710878d1ff4f120719f4779064732d826f3b5b53699cfdfc3dd46dce711f7db72eb9ad65a17ceeeefaf5aabd6f178615cfcdd7e18c7867e4cd7167fbdf06fe33db4d63e748ecc4b8eecc60b5a6bdd8ff3fd99c7c1de7908bf85706e2bc7fd36362807c3109ec338df8fc32f248401036c5df3710603860ece6d7f0ce43e1f27a1736366726342c6a8726284b4d6693a271604a27362a1d6f9ff06fb7cdce70bd305fdef75398ce19017e70302b9ebc338fe0bf690f73d3c367fb5d7072f945fc021dbebfaf7033f99cecbbcc7f6f25e7f8371507ebf170f7d99ce77c5c5fc7559aef0b3f19ffb63205a2e6cd40f8e5b9fcfe511b6e8f76737fc3f580ecb920b7b91bbd1496b1d44e76e8cb961e3465a40a51986d342f9c76e70c98fd774bc1d2ffc329050f7e560e8bf7fffef07f71fffffe372f8bafcf7e3f0fff81bf60ff4017f1ceec7701b7acdbfe57218f7f2dfc6e799f7641e82e5382b3b4b0b13a28bffcc7dfebb3e367c790fd7421d8f17c273f8f2f0337163d886ce15b080d63a20ffbd507e31bffff7c2b1ebf6fe30cec7febe2c87b15f88fcd938306ce1b00060ae8013391b8d56f40f94f98ffbeb7ecdf01bf2d0ffd60ddf60ecf3b96fa11c9c796f0eff0c9fe978dfc39d2787712f0fd95e17f6e11c84e7afe68361b627731d60606b3d99f7becc7bc0757ebfdbb7b10684673e73323e73fffb0bbf722cc7ff17945fb8cf6f101e6b7f9a3cbfdfed6edb06651eafbfbff01918d89a8dad354a5df397c3389f1c845db71fff170e5b97cbfbe0f0f7d66c8081ad791f9cab8180d6baf8cb41fe0bc3df5b8b7335d0b4d6f93f730f0c86efff6c28f3de5aae06570dadf3c050e66fbfb796a361c1efdc070cdb60780e6739a824ec02039bbfcfc7d1b890c87c07fba06c240ca381800e7f6f8ebf7fbf1adc7b13b246a3f5010cb6c1b0d9db7f6df87c7c0c0ccc069bdbbef05828f3708d060d3858cce7e33d374305ad350f9d9bf14617d7bafd03e5109ec3f8cbc110f641b537cfff2cbfae97cfc77b0b876dfdf77233ccb46e86e1b4dc8c122b740e6c140c18baffc2feeb0d869f0d67f87dd760c0d0b9c1bf1f8771bef9fd2fffc51f0d8c841c5817ad8bfb6d6fffe56117efedbf3caed09785cd5f9edf1fc7c1aeb526750e0c057ba1b556d339f503ad75bb9decf6e630867f6f4dc7c3f7bbb96c21ffc1711bbe2e9b1bc321cfff97cd55e3f97cbcf350fe71bf2d847d500e7f6f0ee53ff3eeceefefcdc1b0f7c71c78167ff6177ce720700efa0003ebc95c07d92c7f36d7eb88bfe7bf678346ff41641b699046ff41908c2412710c67a4412370dfbfbeac87a57cb8cea5642e90effeccdddc18b8db8dba52277eb1c13ef4d38f8333ff6dc8736be385bf270743ff7d2c7f7ff859fff2d0e7c61f08f743d90dc3afb8dfa6b5aea1f50c3030b3b60efbc46cf80f04baf12c83fd64fe4683ca470eddf27f493823fc6c08cfe12ccfbe206fbf0fbf8f75e5f7abc9b14e9edd9feb03b311cf72e8d5dd99cff20b1a6ff0d8fff177ee1cf883c7c0c0faf7701fc9a01cc7d1fe6f77721c07bed9883f9a2ccbdcd777f107c2f15f5b877d6cece3333cefe0f8cf060d38fe13f2775fe6ac8d910cd280e33ff85dfb9952e0f62dfb9ec76c402069b3cccdca98b83efeba38d6381f1018f665de43fbfd598e83dbb76c7ebfdb1a7fb4ff83d5fe0b9fadbdc16365239dff6ef1977fdaff318e2ebb3f3030f9fd6df00c062607673eabfdfe721f17fb3f777e83714fe6bd37982b57e4ca0142301e2f9c79f8673ecba1ff333018c7f0189c18c371bcc6c5f0ccfdcb7bf878fc813e7cfc17c367b2cc7d6364bd3e73a0ec7a398ea3917774f099fc8990595a6b1f070303dbe9c88d0f6839d6f9b80c193d060283b28cffe3712b830dca327263015aa7d3bd98bfcf6be63f660b67f82cd4c9a18f735fb0fb780f7bb3fb16b2bdee1bec6b631e7e21605c7e793c837540f71badb59b9eb1436839d6f93fcbffddc57f76773f386e77fd036519fdbb89a324d05af3744e9cb5d6febf6cae2cf7668e26c7ba53b7b76efc82dbd7fdfa3ede651995f8db7f01a0e3648397570d1b35c22e38ef72cded37034b8b7dcced370bc330dc604008e7dddd67f37fd7d6793c8766273298d0193a845083461883ede95adf970188e155a886ce7945d15a7f0fbf6108f63ddcc3ef6379aff31fbfc1d0fff8cfef0fc17cfc8dff8c43eb00d268ada1e85c8c4f5a8e7540fe632343ebaa118462d0d0a1426b7d06a34a0c0da18dd0bbbfb6ee0697c810b5ae22d2f5498e757fe66cae1bd7004c9adf20dcc31abbb9fdbe9df7c101c898651fd6a8d163b90a0f91b5b2b130b4b0b1904608e7ddafb58ea2735c80b4875f19ffc7332b43fe3e1fdc6d84eff765a8e2dafa05dfe80301145a51a9c390492203401598421d4432c4901421aae4459707c659892a253c60d2f625040435a22ca980852c3642b874c40a05e990ab624a17391543cd985be3d25343d64846b380d6818845262f5a21a50112817de2f302016da44aa8a31d21349170e4882df2a58015155951bce4ccc8f1a92447ce199d2d222cc20407d1272d86b42e60633267871d11f6190d3203b4dc3c64b1597101100845781479a094d5183a183cba52d1006b8a4c070d1d544a9d790b6455c9c4df002d0536c0d70d0f64f0022d9081cceaa4c16b03318e84b07d20a23556c2c0c255160f846878c3929316cfa1f3a5041a044e784801cacd16a739687b9aec78f2859d22628904454c4091650b04a4c4190bc47accab15bcd469b1c303362abe2c9031870b8ca8137d9c600280cccaa26d295189ac01f20412e102194e4ad0a0902e9852630e9c25483e164bc9931f3ba40ca02a4d6d65297227050a25a72eeda47c8961ae50404d2418794a8dc88268020938e8c0028ca6387846f033a81496078d467de14063936c8296237914387d0a1515147902c76513e6bc11b3d6620b1889123f6b93d2f4e5d39613c6dca67ce002881c55722b1032412600842d679228ca194af1c883b3351944a72540e024b0c1254d6f48cc093e1afb32c6e2822b20631af6a8e8030290a84a6707530f5bd855d56f672646850244da3f9580a0c0c1cc99121d0b30c152620d9126e2794b01aeb3c4939a3817d28440c76615046960a44b17232e7ae8d84c392f535457af813a0e38d00485a3213e5294a8146488d88d529818a5f17a818b054fc884c00387c30cdbaef2068a10114e3462e5be24d056c7ca05e709a31c0202393222a605256dc5c486830a74fc00b1644c21069aacf10dca69539b9fc18d9805a1bc68d9910ba5b64321ac82c648458f81241cd66830c122c5294cda833c54fe0c43c43273232b0fa632557404484c4e83d65410cb4a7485a40a93451a8b130cfc287aeba28ab3c144210f44e12939bcf023c50394245384371160635e5f8e34ea5cbe1a19c9c2e00ea9ab4226a0029cc0f960c3e5cad2db222fbc13b216dd21175490004d1c1f8b02e519412542d0072ee0170485da81823c40db25870acb74ab42844c151210ba1cd9c067451036142c8480718142c3a28083141c0b4c211c469cc9d1617971c7440a69742c4824946860b509c2254dda0a228c48c0e5eccb133311050842d6437b4cf81466cfba038a18a769c4244a6d02ec3e5910dc2959738a880b2a408cc08402189e171a09264f9a4e1920c32c4517074820e9b48401198d787438348ac71c12c0297197fe14192190086c800e7c092004c401a60e4d813a5aa7309d593065cb8f920a1d40446812f2048132ef253674ccd2a91219e0c0518075c8cd9c14d0d2c29cd1b3400800807cf124355251811139282642d49c1c06b2ad1e4de2982d50d605c3cc1a06540935bf0c9638560802240573a3e01f5242441192ead19a43e86409830112e81256e65a395f48e80c2201854d671c4550438bd2aa5149c7984bbe34996eb490c21b6e662b84a91e1944400b92e947a2315e4ed0b244257708c888870cc7276c09592c44879e538b921627acb42216f4f480a1717193254816141d7ec22419902804e382ccc6841fd6d5a8660c0765321cf5b861a8cb478b2d4113554a1624bc31e34287030f6f9ea419f324c2eac8a609c21a34b83ce24081294c8a405d9dd9a186412632dd011034c41cead4e182135f6314ccd8c085510356befa5012818922115bce889131c3010f98502185f090600bc0d2e0646cc80b536234522032849a63e29f88c40260e40b0d953c17f0e1c0824e181a23b24ca04db69e5489a2e269d8c4e58d88f5a6839488629b199528d3ef4aa39000145cdc842a51c8c76e85260e585a12a48ca3116c4cc020849a02a0b0b0c1093d2fe00c0081b509900ba704a276a74184a8340d0a8590224cc607b72f73800b7400c565cf037302244a176d46c5a008a8b4a148232f2a6082208e70eaf4c56541a74c6f9d2b40e7003152b87005a723ac94e3c0251e3ec6b4d04146a250240dd494305a4c8046a3003853d4a0203cb09446e9f2e323830e2f9cf26306cf4e0505ee0a5a2c03b3d700dd1f452a85a9127c05186d2a7180cb980af6b4c0c3b440881e79641289a71268bc1dd2806b4a012267a32b94a03b5c4e30440aee409309243e9428f50105349b28590a419019950e7254300bb1814cc08813a06cb26306d86bf4499003b935a1f880d86ad52c6842e404380bcc4ca075e60f113e6035d212253028cb8b26d5226b8798430331a524398aa0270207b28529571a0848c3287748048e15e11d365f5c3063e2460d4454ceae00d5a04a5402861261c264213255c37ce4412c658643b8284450ec0a78c8c70061e30032756868ddc821e1c084a6381ec0a5d8a0890b4c5e2c20caf4821311692884c023b1e6854e280085a96145810dae24a920020f0e4da5b25c22f366a371403891408a2d7c96c832c27e81642929c8c9222204122fa9158eb84804eab340e5e5d30fab6c8e0b31839a74e2f0a20a061c7050610b0a313a6cb0a4d19189d0660d180938d2de46f4b8e851cd5c5861fad1a3909e0e2485c1ea23001242401467f6cc36c06278281343c142907d848715338482525d223811b31a310ac9a63947ce26812054ca0f095f2a02f135e204214e385cfae0c166e34a180429059d2e7452e072d494f8b109cd97107c5f4870712010d7590b3c39e63e232028fb14836053c2a7e0d1892f3e245c8e950054f0a2a2ed83005d4408f1a834c60da1a1039f4b2558c4bf3e745401a2bc6119531d20842284092f15e84011024be4e533094d905eca89e07068c8053e191bb47c8a9121066f82127f28c5b9c08cbb34298228648de224fa14824c8920250c429c7678d228560b0e2f7270235c0d02e2f3036f211a25c9070d155a46a826000008930c9eacc9b1421812bc312b8b815118c858740129199084cd2971414e84ac044e342676734a0046b001a2536abd80c28f4f2e243965c095970b24194ec22c322e24bd90016534940e812ba5b04194ab758f252b811608f5d3600e013dbe68684db009b4088453465894d518a191a527b223112610b9b92a93a527009f27cea6df157755a791091e425c21494365d01f0a38611173c28c0b88320af4b00b306244e8cba4367d4438c026f88118044ccc09067df2883d85b04e48113569cc4d9910046000e2a2a4817aa205b7cb1895c39b2e26dec678181b091aa1ebd36812211a571a35d9c1592268ed61712914e7529a9ee6aa5409202f384073020a39330eb005d2f812c48402d2c18921034600eaeaa010931228b0c04132c901321fb058a5fc78505dc8b18391a73b4f4ec4b9e949c16240c29751f1d50f279b0c11e202840288d8c5d27230744c88db524102a0507c1528826d8067b4ee303972259a90c78546242e2541a1829dac2285cadc1409224a8608204ab87449900a643ad48099a0630dd345021521ac7931f0f985e924382269e0c46482124e7a8042c313c2463e4913c2034f85c284f0368a5cea81c52794294c70c65cfcc27268db125a8d3709883a956285a21f5ba5568912949478e18609814c2970e6c926f584102360c912c250a22de48825e290092e6ce5f2f87c935e1b1ab85690b4c29f0a71d2ea9323c41827e2001ed6c268c0a3252997c519b94d2a65c1862593c2d47cd151e7c695090f193cf165d6ac7087aa47013f22da4492271a3600fbe262916286ce214b5c3a0bd356b3a650dc871107225cc09b427d55e5059e3c6a8c147af3800855885c89a24f646863c21aa9c10091d6f80ca11ed05a44db9e4f63471a5862401e34001040d3fa0a050d882872c2e38017c0dcc030dfd0fa84048d582051088e842f3c65d4f40905874125353c3d1574e5c05308490ba856894ca20eada947d4f42e50838e3c10280e05a1f8142c2b9cd981a265e1028c3c1c780043c49a3b29a6b059c0a418c1674c886bc25b2d8a14850416294571a1a5095cd992188f09f5981203cb974d8bcc707ad4c40aee51a61b9a040424b0c061e80c6f8e15ad41f0d1a3078057049e0a3068ade20014b268b0b39566488dd6256c1051d5742941d2075174885da9854c8e563481a375ee8526192ab0a48859b42e0169c9255355a834ddd021e6acb067b42a29d03ac7058617571518315a6bc18dc541429327d43aebab8ad0083b5888a2739f1465e8014218ad4d685d6a64090534adb3eaa080d3d3e586be1a728987400dad4d5cf0633b2ba4b588528895476ab4be1a1487568aa24fb48522a0d45ac41f0c5280d11a4b3c83e98646a125868dd62374b86dd21a4bcd2ed428127dad4738aaa23508ac455a9350a235882c2a6e84d05a6badb5d65a6bad754e6badb5d65a6badb5d65871447419e49313488021a47894551658330248083256c8e1c8d3a13a6803546d2ad800450cb519a08824e2f4c40c0056a67cd035c19a110200b747a1045f4212e425654e3c88300429c3075eb8288d259c40eb6126841f279af8d0a1e50e1e11a71ce89e5779f0af12dd3920c8870f9e761aa448f1670d062a4484a1f814278c072588dad30682b60dcc17263af8be466f592e05db7cc965f82368c9154bc967d8a0c78b212c714a3098a4b064f92153238003d8140c2841080656c6de369980e1a44319d04f1d2d12e6e881400c104bb916bcbc989329d803c8811902594ee8a193a5ee3b6578586a125151d2c3a9050925b4b03c41718acf88322d26c4012f4712a59c183a8c0279e0e100355d6e1aa4304180b6101444c2021362b435901fbc41e08683062536fde8e24181068076dc3034060c1a077c727ba440d1da89f3ace7a41493510a19a20694de58578b314b55689a3c0ab4224a09334a626a2af84244a2421682074f255052a16604d6a64f39b4aeb64480c082f3801010187868eb40012fe08e090e38d4090e047b48e8c1ca90810b7bba20f1db46a631683e85a52059de98b481674035bca8017bc009881e6e593be4b8f96f58c011b7b505010724420f184023a4830240908f46fcbe164d49d1a5410ad99c0cbc3cd21ca0d01147c6060b521bb40d4c301397434761d483c706994680f1f040293782842c60c19a0c951a009406c58c7556e34a82139e7c32a463010d7e19b07a5c8099627cf8000ee224d1ae0986ad1155747aa8d0e381212e5e286eec3102c0d78941442ab139a0c3cf09a2b6c060da1ac05db540c2eb86060304e73e6e91be1470828308b80650345160187550420804480f2478382122830c5c98c100ae74227aa03032d3c195971966d030770b8f29125730096b7a16a80da6007035e857a95403598742d0f2c7c45b83c595206519911a0098b1e1819c18575b99cc0442559ae3488316520220e36126eb552168940a4320f5d8238797652591a265c80d9e3da28c0f6b5c90893353640216b90a342c4a7b82c2c560cf13395424ee8010e2042e18d8503c808189298910aa2422ecd0e23a2183289806083596448f09174c3066528e881833c02f5c14ec30b1f190c2420127044c4a90990429683ab3c654282a489bac125c468d9428d5594365032711985e6745547862a100328ecc68b904080588296ffc13885044345590e98253c7c2e8b5b7a03949a46087d351b3844a1f3c15eca97102191515328e30d1d271c0a5087874fe00bd310f8070520611da6f089d1242f09de2e248b09040a20bf059e8447a402d8b8d14e2f26c4a31dff051034e24047af4949cd521c18205080d6ff6952d851858c4c5967cc15941466cc0a8c6a62f261754894b6000b80d22c54101e84a223685b4041319518b1a954220089602f30d241d8b26194f5ab0742509934623685ca1cd7fdc4a2e24a1c0cc0b164a7041c121eb01043a55210276f6a840a990c6131601275c91448e7b3060a9a8d0742c94083b6f362170ead4d7c3444a98950c6b299c38e2018b09a09c2a8005a4150864cac14977d5c72a94041f4e610912cae3820c1352402282ab8a0a291238e5aaa96c541736e956302101043232a5a7c6de68d850888925b03b573edc4c428184345bca00c800c82c91af93e5000220b48580c52709870e9f1a0d2892890e6e3b34f020021d5823226129a44ff891d364c8d31fb2b2441cce368999a4c5a865adf8a3a40dc8911b672cd8f409718302362d2531ac648081078dc7e1d12b83a6e982dc1423648b4876b06a70f8b062c7080ca4106c409d32a183232210f56054c21e42e6871287e840d904009e1350788e075c5050959653d1e30328ab2fa0149d41a4019904d6cc215e846a2790c99ec6a89c1a55034e4b0725c44dbd4d63c094f0813fc582592339ab0a272ca8b0e8136aabec49012a54ea202408083a2602a1f2403ae778d234ca17c0040a902823880599c5872a4494ac61018b000fcc48610199e703a7043e9cc18e990bd1003b40a4219a9614cea8f892ceb2948192e58c44062e2f071e7b51401a0b18812829615304208113475c4080fa40001c3db023b216b03050e40582094c708b000ec69d9f89cb89116e56319a772346ad91892410ef8b04146a55ce2e39e920ae7748901e19a97a046b6dbc73c6019b4e23402172a70695169144945000123653784000899288a20b09262c3ae1c01793030b11c08009f2c2d6a7139b305c6552a51010ea905691130919115857562276844600e2a951e9d3a0155e90a4c0983113a2288084a8d20044bc5648810656133031574c3ee00a3b6810f2a7c488cfe58007d06441d232727dd8f08ba302890d4f4b06c0c0848f4e4f24c04a244096a6d20b9c017e9457572ac890c1c3021773ec4481044347da055941e0165c4861474689136b3ecc91d89668c9e1408a824c47c02875436a6957543d7271b034092183d403871a40f201881d6a168568e41a293163d400a6ac842a2381a20c835607ab3b94b6b070f5e4456c26c996993597638a8f181340dac0d323376bb41a8dd0210411d6120b0f80a901c74b075a7897326725603f66f2f4c8d3236bb4d289021402b1517108ce94357c05af089417ed9bb63a5b4050da81c00321e40921fd02a7a66bacc704241ca1383d80b00402079e208548312bb65474b844058105222d4082e7c5e4465cd70f44cf0323b05359d27ca06102518f0705974888615d31144889061e706011684a92374852a491e16a226385c392a813ccb2903663a9d0405c0f345cdad62ae1718106920a5a46d8b932620d064f386090a084020068c0361467dca0d182a3cb519e34233869f040972b585236090a1e8a44c02212de14fe10e0e883112c581540d85864c7ca891d75dac4030a9de0e4429d471eaa8400414762647de10e1816d6a41e9a0400218026535a443800f29481083262d6f00e0941c44ba941e16c8e12b71335e20cf131f5158889130162b0189201882b087c294034100219395e5cf409f5668097885c002e16b89200203277ea74490002115126c468f064ea4d2aa754035a2ba8d862277845c3ca985d570b5f8a298b1e2530019db22c2e10b430810abe1c04b4b0e281cf0344246979024381b79363080a72dab43611302d0782962c82272f32630e055221ca8d96ccdac0738d22d803c8001b753ef1d0a058a065460a488735177222793062029c4f6327073401c28b3c64ba68d0414a3e6483040e427461b1c1923580e00d46d04515783823a8449d184e3c6cf0da00510a0060704298b114720318a0e3d2c088290b468c9909618092b7103948a42410958229208a10522031b06695c8cac1a9d08342051db688b041d38d8aa828b2a6c00b07dae67cf9026eb844c501148805e6498f34686116d7a4cdc04114376f249e84c93a13254705194950b24c093b584bd1c0c9001b3d645e3861b453c22602002200c1c252d8e6ce13383a0e18c0a64d040e6c19d18981378a508820229259112e3e4274f0a1c82445ad0a13903360282dd1b180f4010b969d9c072328022225121708fe804028023d238e6469883406c3d20f1d181f07550c0b14a4168f9c07a118c0c4a4c4e221005230b00af8f472aa121745918ff7410f0317fa885064c296362ec88c6095234e80292c804570828aa639676f2948642584f020018f21b52b4640443f0048e266832f8dbebc68483e1091a3c3a98f63821f80484464d9a5609989478b0ab0b140cfe41525d08d1348c298b9629bf2028b0a1bb0a4456f59aefa18b026eb02f64e4f93070e8c3018a3444497961613db0f924161075e0805fb6060060f8f913b95a555668529a4684420a6cf086827fc64d988c46f2c71600f170f489550e681169cb4bc802105c64b87366fa1515e0fe075295746cb4c0b430869fa35ccd888a281c6871081b8d8284fa446b07140958770030f1025505013d803830f112fa218026f3a4c729433a9bc90c6040f3a4a842e330268d8e02684d82517d84af00010959587ead08e01d04aa835007683129f0066492baa6c9c00f3808a37560a7530020201d2ae4a104be4409e44200ca9a199428750061ef0d8862cb8f24393f0f3a60a0945e2f6e840618a86e806d38d149cc24070629370a1e5c2c9211a1d880006449a19371d3450678428e44d93c12a8616891762086890a148082ffc76e0204425d40829368813c5024acc8c2c671ec579158860892087ca521e165e6863b201f0a04482c40805845419614b3f1c4ce9a32545231150a030c0044129805dc049500b3746db654c1bac9f4cca91a760a420c80a3fe188495846685834aa470d4d50d8e2c0e9da84e283124e1c02e0e41dc077610a88c9c3bad8d1e2ca1409612f0c3922298ba9139f0009390b22e2c9192c03de19abadc6035e26cc59d1e9448ab1115a3bae4664186080f4c52325ed5598123c01df1d747a21ae31129ac4a264897a4049072d9388bcd06754a3b4024468227840952f72d6ec18b0a9824d8d4a10b2e9aa0b2e8674288fa71e4bde7e5cf9d1085496dce80b181b6cba2a2502c185a64c6673b4162122e7b31d798850ed8039254a8f7282a0a8e1b2e3c9ce1e351c584113a393a6102aa8c8324e8271049396aa1718dc328da02543939202c4cc2ba4e824662703316acae4907c62c3f6c7108904d404a06340934bbc1041df21aa382e24e1f3029196d611147025c2102a2548440b67bb03856a54d86aa442545ca9f10c9f41a72ad06d3224c31307102dc8b151c01a1598406934e98713402b1022414a480f455b8044542012d366d2d712522ad0392bc67e1d60b64014285a83e3e00dc41444e5065b2f3250e3e88315620e49d940a243097b61cede23c00086ba4899e23d5234d96119f0e8c9d6a11f33663827a4d48018c04fdb016c678d4e6165c075e8ec0f86111dc0c001bc8045970a1cb9c5a658b401802283f95107506d323c1db0f601501c0034c032e306a04184a552a148ac6969b485843323c4c6746c70c20ca038892bc7eceb0c1514581f68bde08020391cb08061c44eb572002b06066026652222878a88202f66a8b0c12162023d7ab2769f4650d9214731bfe5d109df5914d963c20941270a30c5a8d41c2f5715000a0527810a0890fae1e6d498085de244e87e71234a76dcde20b83a15a2910c3226fa6850d485cc1931595f14781289839f26576bab943d225c10c20171157cd09103882e2b8220965c348ad5ec3cfa4a408b088c381320e43050c7ccc326341e52519550088ec899c0a3d29c2f5a7eb05c243060c298086464bd48f2e8910a6d6dfae03921c58d1f104cb4969c5f3e8838be74ed48607315c12322b92b824ab84fbc4ac449f2b4f2d8f0220df1264e3368d21e158fd24a688406d19d0c25187dc99107842d4d1cb03d60b616302947ae45992e1d0080e646d01ad48993839359082f289f00c05105841237461951a0c4254a25d03426645880800d3b04ced800354946a8b0824593061b32ba880a56f9a095258806049ef9d32760121fad08622dc031655348a849f37a021c43c00a35453110f53143428a42ed9e063af4812378238885402588490251e3c62888d00a563610202e120aca9021121dc01420c781724383cb23b40c3cd19891e2431e1f6843de2b0e7438f0a2dc433bb00c2dba130625dbc04d8f1ba7b4d8a8f030a209d9241632c82af52344c0c6083b206084c24890acd9cd4893363d3d5ecaa041bb8245242b51cf10a92f4d7c495c5895b6260205233704bdf016c7d5448e7a55994a381167cca9e0678c100ab284f510b352e3c015a7533b160052238807a1b816958991b5227e20143694749193250519839130204431714a77c90b00321cc591f12183f86d0ba52b284ece1e0daa91698352ae24b62d1320caa274848d971506e039f983415ad8cf433121843c34dec9f141132354db4121400d5c898892e4141d38323b238e3786c28d4ad691d02d10034072601b08e1a376e9832d2d3c5c62e0289cd6ca9a9c00414f0401bc7275481dc9c04e98798ea71059a8b664f02784245178449dd87305c92d0387ea84b7185a58a1d4ac40c2a5463e30d021a6f43e0895e301a94a190f3000a02245e9d081142b5a219228c214286d03a1f747980360844cbc126e5428c920e080b8324cbd100114d98d52f1c156a3163e898ece241154420051140b202e91c970a4518a2b17f8517db4a9808116484cfa4368050e1070c5300270e08b2e8c8f8aac18a44079f82281973c5abac4a4181100e237a3c91941094565441044110aa1c510123ab6182153e38d4171c2214881075eba1445e4a490d54bd77647ced4b8728852b146c919d183b728fd94b1a30229af16aa8c98c0118a90021f15a72828445170aa0b140f74b9d4d344a8ea875b4ecc20395eac2b8f3c5c0026040ea514c141181013da8ce0f282b5241488384a5427a47410878d831329dc21e3211e3224c8059d0d0e116117608da063ca0de00d3d5311d6b6442594108186810aacca8000604528ab910617f4dd8995ea57c5041c1c647c2499c12df9835ec5092fc508ec68e3a482960f877264b1119155028a2f269a0c98ecb4a955404212071366ba57c10a838e3427ae70cd497395e82a2c858a1123589ad3977800c947a52dcc42e04330299291217fce742822f4c0ba492b54a0dc18b414749db8242b0a0e10c2e620915182248d0c1235145c96d446056920eb4329b215dcd428dd5c92b536b1e05bb5d45e506077d0414a0793be8ce040194090dcec641225166e3a680123ee840cba8a02211040032448a71f19b48522d1cb11a58d9f12136080230702024e0d52483285084f118cdc95204e713439b244438801576f214831208408b24d901112453af4230202081eac8154c284055ee43010f6e3890e3b3c78e8d8e243056b4702211674d06652a41092a4d092430208347460b747540280f3c6501f468be4782061458bc39a17070c8d497ba10b4b1547572920e079e992c0cc4f0b6efc9025b41d1a8178f1c290481fe0504dde80a097502a9461042817e05c023c5290e4c1861b5e84f409008527263b3ee09302853084102dba73c21b2d587a3abc18a80002889f3f95aae0524b9011ac8f2d6e321ec5802187841507aa3871508091f003e187033394901456890489188345c6c1961710de0814048905f1ca9ca510d1886a0eaa8402ad17723ec4a302ad697382cb1412565636de1d32207c4c80c2160f578eb60f351ebc33af29df152320139489302be500578a74d0960a021081a3530b49262ad5e090820b0d56f8fdb2487ad4c60446573e025ebc09393304031fd4c2c696c72a8b908c3b40bea8bca19c84e97441d81a0c265217d0c081bbb4438210444170278c00eb95151ab12d5cbcd0a9b38156219d0beba12cb22a097ce9e22246feac6102d667840dad316df68c62e2e1530a710c3d32d44333400e0d96302e989505211327c90b2723b05c213028e1ad8f0a23726a2860de4142a48f0425586801c21b372319331706a1c863a76b97531b7d2d3d280d8041412405309e4a480aa18387a94e82d25c29b7ecc066979ab41245929882004f0eb60bbe42e052356b60536187850ad07825c2eec4a9cd1974b5210a080e183853a0f1c190874e1f9895e104011812826e6040e710083f691018da59f0d1ca14292c0a15c9c923382fd00cd863e7c903612084082af3c68b1b2543c2f448e3c007011190f8c273034b821f649e26a8e4468aa54f6a29d430372209260560f8053a608dc8e9619190678307333e9c447a4269caa4381e178c38b3a64318b116487ea420d3c5386ae00e0a2a867c4cc0484ed9a6ad4183c900579060c5483a603416478da65da615910eb985111e42e1c0f181025a56a0941c1550040ca9138c2420f348cb9f18ae1547893e6c13c5b2aad380a620596656a80041a5ae27178c89a862e6ca9a1866a83801c12c408ab02481132e04f5cadca408fa1df25470a3d100a3aec89491f2c5e9aa0494179f58d864a5909fac52504688f3637622803aa164e3068c93892694203902d4674f233a3d29877e08f0c0958ec326021a899120d317d50f155810049015e7c592d845a594864f589bd6918f2c5a8e5260e04f1d1d42b04090e6c182035a28c969204a8d9e130e38f31052a12c1d72b208314a4f16127a9faa70529f4c5c79c7e2689d963a9d18f36ac304cec7a227c0070ae4d81e8c4045e082261f97b13152326c891082c90839e864952853868170920bfa34471dbbefe131aa395aeb9ece4d5af3554142d8a475088c42b81382f1625e38cb1ce8e6f918246c07f96921e874dc414970645d767bfc8f43e72071d2394822e81c24453a074990ce419aa3739014d0394858740e529cce41fae91c2456e72005d13948623a0749d43948a1d69ac3002342e4ac3068f8e520858abf8ceece309cd6c2708cd6eb33ffc96e0fef73d37a32d7f13d7c2607e1fff2cf83ac27731d3feef308232b9bf9fcdd784d6b7d6a86e1b41776fd5f7ddc5477e71fa6b52e85a1ed05193e637bddd7e5bf9f36e6820fcfc5b4d69ad2d85e576bdd82cc674ea6b52695c95115ed5d8ef63f0e9d2c035dd707fcf1ffc6e379e02fc886dc0b560b7572c803bab3dc7b9bd115239c11c2086384ffb73baeb0460874e7ddfc7d33ba80eeccd57dbcef6a84e1fc7d3264845eb31c8c793c5ea803c6b1a15717d09d43f8e50abf0225a936b08e238ba228996e919ba5474f34ab4aee932ccf91ec632f59d63cb36ed2fff46459b634fd57d3d2ccea7dc95b94ede3e7bfb72603ebd9a6e7274bd58b6629f6922cb37e6e922657495e9e6cb949b82db7d6726c95e974313cf3f8cf6efce732787ecb74baf8cfee0e9e5f57a7ebdf8d14f759911dd515aa0556d26ccf4e927fe422dbf69424b3a2bd6fb2554bb635d9b4e4620fddeff7ede077a76eb3efe26637e6c0749f15d9e1a052606dd5fdf9297eb1e5fd34fba61a439e676a6e72ef739fe216b789a25b15b9e763dfeab849f44c45332a312b79929d1cd95fa667e7248a26b08aa2f766bad9b32d477593650fdd3d3908dcb5f982acbab596a32cf6d1f26f36b615646b6c8bc7d6d896ffef9b0f9d2efe6d31ffb53fed7bf84ca7d3e97429ba4f736f2a16a8c2c4b31c8c773b2d2a0456d62c4d5444bb577f0fdda9da0f8e71e8743a5d3ccb678f0acc7aa2a2ca55ef49df59cef2b187ee72e7a0fab2a65cfdac598ede6cd3961cd51ebac5f1177cbdac694ba6a44ff7b953966c4b4f8ebfe07bc0da7bd9cb2ff6d24c47966cd31eba77b23bee2e68aa92688a8eaa68a226ea3bda7ad3abe889aae93f4df58c8acb2aee94dc645aa2632ab6a2bc837db9cd78964faa2deb57c76f725164cdb2e4eadf2d8f4acbaaaa7d9b3c3549d6ab2c6fc51ebadffe4bc6bfd763fed5e2596d6f349ee52c1b1d4bd444cb3435c5524d51b24cc9b6f7563dcb72b3e589a25458d6f454cd2f8eec3f55d2dce4eea17b2475505d5955f193ed17fb3fc5d48bdf7ce1df0fee3b1fffb9ea68525959558e76be51311d515544e3e2f9fde62cbb66aa4a55651dd3cd9ee6e72c2b7eb434bf0f185c13a9a8aca6ea7dc93d5a6e53ed232bf6d06dce3d18cf6a7ba7f3afa59ab296ede61e155b9e9abb2449d4a592b29a6adaf929f27293661ff7f96720f7619ea3e97472fcd3f3acc88e8d2acacac7d2447f674db51c53332d832c94b5f43cede5ee9d9764b945b43c5df6c99aa27f9fbd97e488a2a64fc9cd9d6cf43c4f323df798aaa4ba5135454b72e469ca96e3d9dbb33c83bbf9d7a2654dd6fe53536d47f6dc25db9e9f1c49e0cf2559035671b3aadac74dfaf4147f2af6d0bdf3057fe266b2feb4dc9bb33df56737cffe7be81e53b1841b4936572c3d7fc1978c5b47f273d5a3ad499afda725db43f70f8e5b559ee3eff6c23d8d6799845bd9cd77da9eaac837aba6bcc3ed5bd6dd97a3e97470fbba3bf4acc8ae7d5b4f3eb22c6ffb4ebfaab6ad3abaad9da7a939963e354572dcff9f637025cb71ec7b1ccdb125c5b2935b6b39b6e07c19dbcbd3743a9d4e06c21b3d2bb2f3f37a9a5e4cd94ff674e4deab7fbc21af9ff333ed294b9a69a976cf9e64f1ea51534d5bb2fc6749fabf13c9b0256bcb79dbcd746c499ea2deff9dcff77b095c4f136db798f6b2f3ce8a674a9e67457647e0b535779a72b573f6a769e75ba7f3f97e17cfb3223b22ef3a9e623ba2e2e665ba37ffe3cebfe568fed634fdac28a669e9bf68fad1eca1dbbf4138dec595447c2bbacbbed56f72ce966459feb3ac6d25dbd37b944ccdf34cbd3802e19dba83b21bb399664a56b1f7cdfe3eeef69725e7ec4e7557de4f7f926c3ab6a3c88a9ef7d07dee7896c9dedac9f1b79ca7a968969bdda7ba67b0ad65fa59b434bbfad39ff24d9266bb8aa7c8a6ea66fbe8d5dd8e690fdd3bf9af5c25f7c85bdffff8cdb3e43d749b5ede447b8a726f8ee4e85ff7ed0dc26fbac1fa3a9265fb4b1155392b9ebc3dd975f75135f91749738be5ef650fdd71eccecdf53b72dc9bed75db54beeedef9c937bb3d3ba69c2d7be8f6c1aedbcec7f0ee8577e3a896266f65b7e977f93b47d57efa13eda17b47ee73dcad9f8fe8b9d393a37c9769eb77f09cd248b28a651759d57b56453bf7a908cfa68a641d4bb3fc623ba65b4cd9f434fbf73b78dea17bc691f577b19f5f3dcd5615cf34557df26e6ffffedfef0179cc66ee71a36846563f8ae2e9c516557b2fcd8ff6d03d770ef7c722eb886e7e8a2c274b9ea2aa2f7be89ee53fc7704fde99bb8cc8caa2a9297675fb92fd7c77df43b7b8df60e0ce17fca9678afe82afd89095f74d7a923577cb8a9e2dd9b24cc86a9aa65872df79797ef5fcec9823c88a8eaa6896688bf2322d555eae6de148db1a730059395a9aaa699aacfa7dfb4f4fea585b51b29b6c79b662dfdbb39e6fb5f514dbddcd54255bef3d67d57896c5356d25d1decdb22c7de7e6473b0cb33a7eace9475bcf9ea2fab98972cf8ee68ab69ee926791fcbf3a72247cfdd43f78edc9d387cacdef49d93e328a26299eeadee31ec6ce56d27f7a89a9e654996abbe876e798e3970e7ffac16d06315db7624c796e5fd2cd3ed3990d499ad9b6f5364c7913dfbd8f78883c72ab6beb39e7fd524c5dfa2640fdd3e1eb3fdf975779e03c71dff599d7f2d99ee5877cbd553544b34fdbc9facd943f71b0165abf87dead9b19f6ddb4d546de530f3acc86e888e3565dbeec9f3b7ec6749bf7d0fdd33dcdfb105d9ef7767cc7ba38cc6bcd74bb51cabf8c79faa65297ad26c7bf7e9b8c91d8e3535f729fe7f8add647df7bf3c83bb2fc71c58ee725d3b4fcd91976cdb49957f76eca17b8e6196b720f3ed5755ef51ae8e2de7ec2e1b1b53b13cbf67d1b2ffcfa6e73e136337da9262efe5f9bd5a9261ed38b6e74f4b4ffe161dd5cee3466f3b2a96a698fe1445c5b68035f566cbaaaaefadd9db917b1babb9c9544cd19d8aa9696e8eaa698d75fcbc24cf36dde5464771a71b8cc69a6ed597663a72f41c45f1e41e069bb1a2e5a98ee91f4bbf49d43449ef61b0951c4fafa21d35ff56d9946f7154d5f56c4bd5a7bb3c4fd5644ff4f7d0ed03fedcdd1cfcede07997a2e9ba4753f5e2e79b25cb14f5ec0c743dcf5fb22d798ee2389e6237c771c6b9a2e7389628db55b364cb72ab6f30ba4f7355773fbf79b224d95356e5fcfd6e7f07ffe018b847b0b23ccf5dfaf1fb93dd636f7be806c2b1df65a4f93d69a662dbc5b245c533b8c47d833bd93dcfc6f52cd514fdadc95954345b91f7d03d077f7b4db9c8f2563547767b73ab680f9ec92d6e31ed236f55f197e8986ed6d777e3de2163ddfd2c45b6ddea38962a7bb63d74a762e65991dda85b4b72fcbd14bd79ee361d51afd3f9743af53c2bb263bfc89d92244aaa648aaa7b8fa76aee5e965d2453ef51b2e45cfd4c5c39d86e8963d007fcb9a3f90bbee4fc6bdfbc56ae9a68598e7b979e93accaf6d00dcfafbbdbfb4c4d998db17ed224d5f13cdbd1dce426cd1eba65f9a73f86c7621e841dcb7d9e15d9a130e0badcb8567f9eea897e913ccfd4fc6ad94377fcdfd7dd8dfb9c7f2d79be58c7b6454f12ddbdfc686f5333dd280163da76f46479175bb22c4d93640bb29f8c6c73718e6269aa238a9ae3689aa3b8cff26fad24a976b23c4befd38faa9df7d0fd5f78f706c76ce9588b53f46d9b8e9c6c4d53257fda43f71b1cb3ed360f166b67f9b953d6b72cd943f748ae583de9fd275bf6e4e3e937da7be8def997efbe5bfcf7886eade5d80adb1adb5adb5a33dbea3fc81b4f538984e5a992ea4ed32d8adb5453751c47ddcfcde7d3ad25efbd9b28d9fefd53762c7be8dea11b35cf8aec7ea8f8c1f0cee7e3dd4e80142bff6a574fb6f52a29b6e8e7bd2eee1d8a95445193544f7e7ece4fd4ab3d74c7b3bcb596634ba79b7fad4ef763b8cb7c4b1c83bbd43c2bb24372623d511565f71ed3b1fca7d8b23d746fade5d852d3e974baad35b3adddebee8070ecc7329e816626d66e922aaab27ff49cddea667be89e7fed4efdb31b0b31e75f4bfe18eee559919da835962031228588ac10284060c1b87a91933f788031408a0e2538c81b04d010c0dec1fd131950009002c3698e5661155509dde1cf86352950fd64fea6439142ff1f7f316c60538883cec04b9430fd03cdc7590f331e2bd998d88d026cd4a031034c4dd1d32cc9117da4b5a68241358a83cea04b9630fd67377ef3fcc7ece8b603db01e5d5cc7ac0d5daa42b040bbd4230ae35ed3f88ec02c36417c87b7390fb803f976c03505e7b83c734e80c0060903f1b8f3f8f57d3344dd334cbb22ccbb22ccb9224499224499224c7711cc7711cc771efbdf7de7b6f711447711447711447711447711447555555555555554dd3344dd3344d53144551144551143dcff33ccff33c4fd3344dd3344dd32ccbb22ccbb22c4b922449922449921cc7711cc7711cc7bdf7de7b6f51144551144551dcaaaaaaaaaaaaaa9aa6699aa6699aa6288aa2288aa2287a9ee7799ee7799ea6699aa6699aa6599665599665599624499224499224398ee3388ee3388e7befbdf7de5bdc6a8a9e66498ebbc7af4c13b902c340fbf857fb315a9ceff5f13ddce3bbf11a977d5fd55699eb587dbcc366656a71b31b04087cf62bfb337fcb62cfc55ed8f55c6c2c4d6eebb04fac37c7ae580bc3319ae762dfc767b51f07e7200e9f8f8fc5cdeeee3447f86d6ddeb1bd542d95a7ea5a53b15a53712ade77e335246b9132b51dd94ae663ec6cdda5f9484b5b77ab1910321f3d7643c27a70d9f71fcb6060ea36cdd1ff99b371201c03cddc3edb0bfb6cb0f135b4f5fd6df05a1ab4f1fb73191becf365ff75d9de75ecaa2c355b0a72ac334d5d466b3dea2b728b1cebcaed16bb5fe50e128e8bae461257e399ab5de54a5cebe2a2d25ac7f9c21fef28d39a4a07558e3732aac8b12e8eddc9320776de7ecd2f7fdd9f1b77d9f53f99bf85b00eada970c83e3ef3b60d966d845e34f8cc3d500ec203c87bfc1faf89e9d6abaf425aeb745a537951c1e8a2e2baf27acb1898b4be8ac1a6a95e501140e5826aab85f6651f18b716f77980818de4150c09ae60d8e9d2e9745ca1d61a88d6555aaf6968a9a9a676d5856607947f700907faf071d545438e75b3cba6e3dec9f107fa7e2f1d77f08a6b0220fec3b938766060fec3b96d18c32ddf0165acab1776b41ceb601785d654274c687dc6cfdad7fd6a5fb78a8ed6faeb2b0216cd33fc429163800a1a7266128a5b214c1bb400d305450cecc73b05521f2803c0307109c317304f1cad917062511ce9091b383e76dc3062e9c96f120f383a01ab27e8251a5974f0cc13ab13a5d432a2ec38e1404ca1235d5dcc9c2cf096c1ce8a1a1b9cf4306d16689199e144069a9c160ca8514d480d10848fbc02a126186c88a3c120186913301ab4b4295a6ba10911376238486061a1c996150e1a60926167c0a7360e775c48cd0ce84346894a8c03701860c52a5395961a8c0c4053d4c9042323c900ad4ceb0a51449230c164058309af1c5098dc41cf262d7c9f89015cb6f0d23930131c02cc88f225a40b932b352fb45be400e34228096d7863d2230e02306966842954232e1ec04cc61b353b8e8625105066724a5c0026c4101d61810a8e511313b823091b1c9a04b2e08123b681638b00ac6b4d053e70e3098556e404917a335576a3d4d4c091b74260650699f0c4ce5b026c8833804602de76566b5f2e5001be712972e322e34c11371520c560d3c549965ba00b282961c2fdb86961c3e4a9d0990cb735b51443d4d8e8e2168a3d996366aa51909355294b21360015cca3c8d4d99b060b825120f81cc2fcd8823b1cf880125a1d0ec12c1f93a0c88168cc2374b8330a3ad5e73839a470ea608106e620230e80cc4c48cc61c1259358281462e680c20905844070456e34848438716918e43595266041685e24fb180084215e93229f96c44546dc12c9556707c84d761ce2443b0ce8d4c28e1393790d6911b38296980d14c09a31163e88bf4cc566d280525b22ca942011250e0861c9a0b11481892312967899a04f290488b42d191246c60e0b1164498b372e2ed093ae8028c8b099524c09400aba08c81a04670d5825ec447626cb18100d5862d09f28858e40812f9d5120d14f0422d88e9375c916560d9809ab414f062a62e0d504188cb6336e70ce92184f7f9c78bd9230ac70d202a7f4b639a3898c0b52a0bc4b76a4517a81adcc3b034e0af22bfef266587147d016411d7e1d007f67d644f8094342a65f1b411c43623e3afdc61541c0ccc82d7ca5aa5a8019c507f511d2c017064c1f0652421df890e44132c84b8729be17839e9c1c6a986d2a541d4a027b186d15b4650102658f6d7edad4de1658812b88d8a6645327aad4ca011b94e041a1fc421fd0a0040aa65007910cb2923225215c3a62858274702bad71e9a9216be4c209290d90080c943b6b22e1c8115bc4bdc1a7921c396775dc0d17b03199b3e34e0f04362b2e0002a1486f8d8a065853643a7a6d3db39306af0dc438ea5165f140888637106c883ca400e566ab139b024b242862020a2d6c7c052f755aecf880cdab8f134c00905d695970810c272568d256104bc9931f3ba4da03949cbab493f2a5159205d1041270d0a165e141a3515f38d4f89e545450e4091cff7c4afcac4d6ad3a792a34a6e054226fcb3471e9cadc9213e8939c147635fc63c098044553a3b9cbe8e0244da3f9582f8b835449a8807ce93f99c9729aaab97a643aa146488d80d53fd013c7038ccb08d1df82581b63a562ee86050d2564c6c147500c63728a74d6dd845ac1916294e61d21fb03f0fa632557404ece62e14f240149eb203d7006cccebcb91461c814226a0029cc0f1232b7002047de0027e61102f05d3ad0a1132553c403c2c677274585edce1a5b509c2254dda0a3c142008590fed3961a76912a5360176a07678466042010ccf8bdd932e0e9040d229dee9004e89bbf4c7c82e09251d404468120225490419e0c051808528c90c00902f9ea446496e241140cd2f83258e85e4832842523d5a41246a5a395f48e8608044498d4a3ac65cfa21298520d187808c78c8714736a0a4c5092bcdc891315074f80993d4e4081fd58ce1a04c3c12a3871116ac8e6c9a20ac61e482333bd430c844c64897af310a666ce86224881116454c88c40260e4ab81227bb24ca04db69e14993216e95164041b133008a1a600229688d021024764ec02221ecc218f863830041ea2b60dc1d0238f4c224141c8a22e42ca5412e2a3081183b41024831d620e0dc4980a8240102358415c203255c37ce4010828e0211f03440140fc00b9928642083c126c80fc90a482083c38360159cb14028997d40a47d624289be342cc20276b4ed672f4b8e851cd5cac61f511000921600ab5492c04d94778ba9a1b1f6a7207351b74bad04981cf51d3b095e6018ab434696e5a99a67f1862f2434ca11fbcdf0f1846a8260000880168a1d02c40dbb2430382b6a28c8f4e3e2610e1438a0f331f6796cee474387bab7126e6cf06e8e1a8079b1e6f8f34540fabd8c5d27204cc14a108b6019e3161b680593793c183140f0c40f00013018f22613cb638d8f169479f1d5676fc30e12183279e811d6598e4b23b47ca9a94e12833d311429c0e08b88e58938e1a6d74049083518e0672f448c841e23085a3108e0446e0d8e1e8e2b4aeb05ab06aa95ad7cc35e402c38bab0a01324e647ac8a290f1201b1b612cce98db58d85807b146626cc47e62a8585558a2443f8c8ca32a611d6b5198ae4a941ba394dc1894e5c6971b3788dcd82ae0933ea283711b7c6736d87edf59ae3fe94a7a91b640dbd16ac0e8281aa8791a4ddfd0a20e754e6bfd417bd003e80e9a83dea005d01a74003a83064063d056ba4a53695dd5ab12c086d6555654a7a85aa09a43d54653a9b91a21cab1cef31f577da8e25055e50295a82b114db42ff88af022d6b4d6bc2b11a51621e24a84060df6817173d6a5abac488cb21069adbb42302f2faeabac08b2d4bcbfa00fee61f85f37d87f2cf3df0fee2e8f67b8df0b391b1cc741f9bfb0afab7ddd2f1931c2180ecff85938c36c5ce19fddd76d43f9cf70704918f7dbe27e5b178e2d8ac1235fa2288ac1235ca10fe6b17082c48b07c60bfb57c2c2e11b7360f87d6eef2dc4e163353345ebae976b17f35edfc9aeaef57d0f19b4b078575794a8b4a84a684d45426baa115422b4d6e5191475a1d70beb0cfa2ad784a7a193d61a89befa70eafb728fdfc71feeff82ef07f2dfdb7f67f70d5e7d80d3e1d5872373e731eff5ab0f3478e1fcba70a8b5e685f167fbf3eb5e7df870f521038f17c7e398b3bd5c6baa9cd6541fb4a6f2a0d603cc958739d65a7bd09ad55703a420c7badecc8132e266392dfcb17d39eccd1c4805001898d65418ae3a8cdaa3437410d3ba459c63cf0a00065d71e872b561d495000b5c0960a51349e4d97066c2589334b811f6cb7b804a0ec4a0197190333df8156585213dd07a915434900115408400c8a28303350b0cc0138985123c3a38b70fc32f000e6ea4025d068c0a41c34d92033e596df10910e308dc0a192de4442b986c1039f3fef980851d9e5697422534d15853eac89793164ec0d0a30533c28d107c4a9df93240a65821e703690348d1b9044a3d420333f54dc08da2323b62a0d5bac2aa800701321a62ac6001a3b52e306a64a53eba8ecc701e0441c2e6018e007e7c8ed4a3134800c38be0ea525abc620d3b81892e2322524d541ea864e084662ac22024ab4f1db216d2d86c284afdc8490a38e55cb12e16150a33612c803726d4a553493e99584443ca0d37ae84531a05143e3874484bc40d91aded43c8d3678048173834d818708548052b9d0aa501c56984116aac953a3374c2001534e4cce4893856542ef8d15d9a4514c2b4410b305c24fca9627b614c201430b0c07ebc5320f5f161c8cc950ee7d48e2c286098b884e10b18286f46390240844f36c08835124e2c8a219deac39383e10ea7338eb0e0f8d871c3c8086578a64f56176471128762120f383a81aa2f561c4aa43933c0182f232ed1c8a28366da9cd13082591347701e8138514a2d23aace1e3b01683940060b051029c4143ad2d5c58c48890744f9b63a915041e22d839d153504a834025a069f5064a954024498360bb4c8ccd0ea1109556187079998a84093d38201350a86882e155288a0c2118c120304e123afa041962b3f5c989e40a1b41662431c0d06c1487d915e08f72cd09a8de534686953b446ea531d51cb2bae13f02ce1460c07092c2cf4c054621093237132010a61858306986448f2c09e1c8ac68840a1d306b9360e775c48794c402a430170e002a100821619252a310e58417901fe49079ccac422cb2a5395961a1d78e170408a1d8e4e753acb29ea64829189f448801a7740882ba3858356a675852822490808ca98820257a8375859563098f0caf1c40a8c2ebd406aa2287873859e4d5af83e0aa44658e60329ae6682cb165e3ae7adc053a192abc4972a1522049811e54b08974329a883d472b4316376a5e685768b1cb89111ee7ef0556952e3444968c31b13b32d311479801222802f9d08306966842954634690026c20ca0100a802ac01cc64bc5123be4076ea033b74e0173861090494999492c09a05d8988de0238022234c88213ac2029598a639670e688a7d3ab09a98c01d49d87a1408894f50c015440d8a04b2e08123b6998d043ad1c20591b6c811118075ada9b0074cd9b753c84c881206619d5068454e10a91b607b2c50d324e7a72a45d98d52530346ca88eac2c6e6a509013424c0ca0c32e1491d3a3b07ac98d0c284f9090836c4194023012e4af8682008951660bc0aabb52f17a80067189140814f1d308ab2868422372e32ce103124820bce02379218d0020129069b2e4e42a811e10a217da54c47968a0b282961c2fb3050ea81250c008337589460c3e4a9d099ce92a7e2258b000cc8b3106a2986a8b1c165139a6be32dedfcb22427f6648e99a9466a3296ef159641015eb4ac4a590ab1c1272b56dabc734e0ddf4e0845a6cede3458a0a4831f2e8424f9a5202007c1e710e6d7fa89e4e607d69d4f973a6939f00125b43a1b3480d1e6ac8a0f4d1fc8c9f231098a1c6846077ef6c456b28a4941ae3adc19059dea1e6464b082a22f6677b04ce490c2a983051a705282461f4c128438a392c58803203313123e8438a68a17b736e75570c924160a81a03256e4ea7c94841302265038a1801008ac34c0a84bcfd6011ece1411434888139786018a072c18a0a8c18945d555a50958109a17fd36aa6bc7d4da26aa130c00c210af4561b326545053a935085c1196c44546dc129d691285c0153fc6670debec00b9c96e439b2b3b370d4c70260137143b0ce8d4c286c30658a6c3defae20368c46b488b9815b24851618e0345a808d1c01456a000d68cb1e081992e217a2c55c949b351988acda401958680a4405a5c34954c4d30c2942011250e90e4c6d2a9352b7868fd2420c65204268e2c99a0a74c8f3c34b250604a31419f520810690b014450d6095e9e149841228c8c1d160248042294220811e282ec05106f5c5ca0275daddd1984094b0e8a458654c8b09952cc262c533cd080a2928c3c6e25ba08c81a04472d8d9910323c314b81050c42849dc8ce6411cb24a2ac814340413bab9c1690454e544529e0c030c107f4ccac4a0cfa13a5d091276408c0808d59962ca78310671448f41371488e0a2b3d02c011f4c94dcec9ba640b8b8616095c246b80f42cb8a2c56ad093818a980f542419aa6211566ec8320106a3ed8c5bac888400ca4d65d79a9c25319efe3889f1a5839e3448ae8059716602c30a272d704a3c726431f5c286286659d6194d645c900205f6fd1630c446c35873b5238dd20b6c655c7c1232e7354102e961024e0af22bf6e280960b4b6519a4f04482881577046d11c4410f9d2a7a02588489cba6ab3c5ecc191cc018704183244da41f34fa9c5813e1270c099902580b8d4104464a0b83444410c790988f4e2a06356f8d22518d71b15604013323b310638527012c1d54646b28aa5a8019c507555355fc980092193557469425dcd0805406217c49e0260326963231c202d387819450074d4019a14049213d568228f22019e4a5c3143ec5cb880803a030c3d388414f4e0e354c2d0eb4904082830b435c40541d4a027b18063c7a7de0807fbc02a4502242292819e0ec2cb0f3c4118929656f480081b2c7363f5012113985c584ea832b20de1658812b888c324b4220f25088ca8d910a17175c81e09319571e4f20764032030a8d4427aad4ca0102f48131e4031ca2c905038040a1fc0a3adddc6373ff5b1feb3136b6b58536923a1d109dce14b7864818f0ac9f4cff264bef4d3f96aa6fd5d2080609d69eeeae7a9393db73d4f3f6ceca8eaad95bb3ab2269a29e7f3b2b175b5345555644b7475591acb39a24ba45b64c3bdf18e8ac68ab9aecff2ad9fd9cd5b37fdc247baaa448a26337e5aced1f7def6639eeced93425e3aca94f3939b2e63751d47f726380b38a9efb3fb2e8c9cd92fd698f60fd7efb13e52d47792759b2bf59c7de55f1e4bbdde52ff9c9ba59cfb28b65aaaabdfc26df2287601545934d49d26f6faa9d6cd5366b4b6ed177ffdbf497e9e9d507561535c7affa32254d7fb2aceac0aafeb214cbb2fcece8476eaa6c56df7615f5beecede937dbd335abff662b96e448a6a957779aaa59d3b4aba9faf92ef95677798eb21e0c69d6b273efbf799aead8c78da60dacfde4dd54c5536c4dd697628a663d51f6937f9f6dcb8aa569a26756b5657dd9c9961545f2a3dfccaafacd7ef224d53465d5b29f0cac63e94d9f96242fcf322baaa29f6dd3d6ab5f4c51b35c60354d5e8e6967cd52dc2ce949322b499a252a9a62ffe3564d5681959726574fd29b3b2d3dc98e634e9f961d3d4fced134010c62d0ffd5f47791dda379b6fedc2a190c0998fb2ccb8daae8586e74244f31dd23e7bbdda778ee911549df43771acf400c61565eeecff2d69f6aa98a258ac02a92e67876542dc716fdad39825949d314c5913c4fb1257979ee2f6b6a96ad88fe727bdfc9f2dc3e4e6e1f18bcac5f44cdb29fe3c99a665a96690fdd9b07860356de7bca9a7d9364c98e5c357be816f73862e8b29efcf3942cf9388a6a1c0c5cd67fb6a4c9372beed67b534c770418b6ac1c3577caa6a44fc7b6554f5410062d2b3ba2a5e7646992e8e87befe2d8074396b5b724c9b69e244971b7be732e18b0aca82fcdf63c4d13fd6dda92a5badb59305c59d9f42c376bfab28ba4f79fdd82c1cada8eec2fbf699adb4ccdcd4b9d6e87a1ca6aa25bf43c153ddb76b2939fcada9e622b72516c4d51355374eca11b9ddd9febdbf566e0f77018a6ac232bb23b554f93f5643fd1543c8241ca4a9efc73fe3b577b3af2b66388b26ed41ccbf214c57144f766d5532bdfe5264d12fd7ca7a4799a5afdc9fe9665fb49aa65efe32fb58e9d54d1b46df9de7eb7a47f61fd9d9fa769fa9da6233996dc8595144fb1aba838aaede66da9b6b09628bbc9f4fb749729675352eb78aa66db7e5efefe51d54c47ad66fbcb315559b19f9b1d7d296ae57ff3f4ab261751f4aba268c5c2bad5f37fdec96df6141dd1b487ee50abd87f5afefe559645d97f9ee6c880d50aab58ee5464d9fff9d9c55e923fe62cb9bd58a9b072f1f42c6b6e3555b98876aea6d3e974e7560559a5b0a6e466d3ee53f6abe6d98a1bd49a9e5d353bebb9f9cf8d967e6c4b44db1adb127d6c8d6d89675b635b628fadb12dd16c6b6c4be4b18566b625ee3e569fd6b19b24a9ee9faae3f8b958b659a1b0b6bb97bd93ac28b65e157bd94381bc3ec6fd06772f4c6e57253dada7efdb54b71f459f929cab27ac6949aabe54cfb697a2d8b2a709682fd5f2fc9d569e8edbfca57a9a1fedac58f6d0dd9bddb732eef3f0f10e9ba15bd561c569eb925895b0b2e54f37abfe7f9aacfa3b7b5664375b6d5a51313d3d79b229ffeda98add4a134f4db59fbded28ebdbb4ec94ac32a9aa98aaa86aaaedd87bcb7be896e1338a15a69555c52f9e7d44cb76a3a719cfc04b9ee5bbf47d73b245c5d3f7d0bd83bd48c25a72d19bff9ba267d59d8aa3a596f354ede8e9d9ef53b3eca17b07fbbdc57134c06a84d554595615fbe9493e9625db7be876d3153dd5713c2bb2abca62556945777bfa3f7a94fda7efe7d943779cef85c9bdb566b6b5a354a6657ab2275a8e7e8f5ded8fac44e06a49eeb1fbcdcf74cba3c1ad1f0765d8d7b75834b8b54bc73876378edfc7f2f605a2d339b29ab4b6e948b66c99966869ee4d8ae558ee1e5621a4e51e51933449f12c45521d774996ed57d52feed49fa3d89ea3222b492bdb72df8a249b8e5b635b6b66261dab48eb177b3f59cf8a228aa25efd5690569eb69eb3bea7bc5551f3a71c583d5a3d29b2bb6dc97e8ea2e72c3f0b59395ab9e94fd5b362ca92bb6cf9b9f76e9b1a58355adbfe49b36c4fb4ecbfffd28f1b9015082bcbd9dd7651ed67aad3e9743addb8777b8fb2b81b597db0f6f1f776b3e627c9df6e51d4b135b69563dd1adb221b4333db3ac0ca83b57754153d3fcff1a7fe1ccd9ecc756c8d6dad996de9743add6e9fdd45ad18ad28f9d59644b7d9c77293e269feac3a58d59ff252f4dd8b5c144f75fe6ce89e7f0e9dce6a116ba56835799b9a1dede58976d5b7eaaad37dafd37d1f7f2b0eceae9222cad98f8a238ab23eefee5e183d8bbfe75f47d9eb031dad12ad243a9ee5b9f9fe65bb4db5a76745764bac10ada4d89e696fcfd297ac6f7f3e1fdfb1eab8b596632b4cac93d5a1b56c7d697a93ffddbf798a630fdd2f2ceeadb51c5b664912b2da60e56dff9b1c3f2ba2234a9a640fddaab80dad28fa5335e5bf64c7cef67fa2b12ab47adf53f28fedf8fbe6ea1e7ba8ca8015a1758fe8f6db73f44c4df134450dd616fde73fcf2ffe8d8ae569a219ac66299ead49967e9fa24fd1eff3f1b1148c15066b5bfe7d8e6d6755135551ae0e5ad9913c77caa6bc343b17fb2e8d95a095eda6798ea78a8ea2f855de7be88ecf0bd64da6a3398a67ab6edecfbf47726765c18aaa253f5bce7a15f527e959797eb9390eb10ab4aa9f971c3d4596dce2fe63db43cf6deed38755052bbb49b435d1dfbd48f6d69b3d74a76c5680d2b11ccbb44cd53daa63298ae2399a6cf7bf44c99d6eb5547b0fdd62acfeacbd244554654d933dbf68a2bb38d89f4cacfcace537557f7e52e4223fd13df6d02d9770567d56b62479e77d8fddb7aac851f3881505ebd8926929aaa427cfb43d47b1876ef81d47372b3eeb58a62dfb53534cd951dda6e9f718b4dab39e663fd1cf77f9bd47b9df3d74cb13ac6c2b9a6ddfe436515564fdeea17bf7f23efaacf4ac24aba6e72ffbb97dcbee3ef6d02d9a4aacf2ac67cb9a27cbaaa6c94b13ed1fcffa5b953dfba89aa87ab2db9b12ac1c4db738a2ad3a8e9d4dc57d67253dda5954444f3565d15f8a3d74efe2e299cf9c6c6ccb97e9b6d6726cf99d2fc72a3b5676d6dfd1f31c4f922c77ab8ee7efa1fb85d3d7c7b8c7b6c4750bcd6c6bb4aab3921d1d773b6e2e9a5b6b39743ab90d867be446c08aceca8ee4f7e629b2bbab6257c51eba7768a95acd59cfb634539ffa737b922cfdd943f7eec7c0719f7b87959c95454d5ffadd8a6567d3dfd3386bdf2647d14df6ef555f6ed30aceca8a6d2fbf58b2a5f94b6e8e61ac2258bbf959152545526c3fd93fd94337dbbb3b379b5fc57dc6eacd4af651dce2f9d154257f6aba59c9ae8afe6447b4a75bfd650bc1cacf92ffb6ab68bad57ff6df663ddb944d5bd18fe4f7fda70faca4a9f24db6ada9fe534ccfeec0fad9d12c3b9a7eb36fb52c399b758f244a966727d553ece356d7acde64d37244db2ea6bf77f454b3b2ea3f5bde4d53454b56fd669ab5fda37aa2e5de3c4549ce36b09a62cabf39b61ddd3d4dcf12cd6ab69eb7a4c8cd9da2e347c9336babf6ddd5fec93f9a59bd17f946377bb6ec989ae41eb362602d5594abe56fd3eec956657f997534c59444d1516d45d31c4bbec0facbd68f69cac93355c71325b3aae7998eec48f2912545b154605579a9aae399fa323d4773ab63d6f644798a7eb5ed6cdbc56d8a59cf726451d54c4dae7ab45413583b5a9aea296e93edbbff745b8539d57414c952dce23e49b52c55d5dc27e97929a6ddb442603db999b6e23f496f8e6a473d98356d55f3e4a5d9a6dc8fa8687e594551f4e43fcb543dbb7992630fdd7e47ee1c565ed63365cbf64445732c3d7b76f380b56d4791a368898ea73fc7d677594974a32247376fc7f254cfce65ddbf73344d4f161549b6fd7ccbca9a5d4549f654457224bda96a594d544c7949a6dbaba968965956969b27d9556e92a61596753cf738b2e9d89aa3597295ecdf0d87d5959545515265cbdd72346579db5a5959c9113d49b41d4571244d936dadaaac693fcb342559758fe3e8c7d68aca4a9edb9fe4c939df5d1cc7d66aca8aaaaafa4b4ff63f76be776b2565257fba59914dc73e8efcfb6f1565ddaaff652fd594732fb2ec3fb5a2e53fd5d2443f57b739f26f6afda249965d3c49b46cd38ffe52eb6e5394dd277a7e762ccbf3bfb09e6a6baabf93acf95d58452ea6a9dab63e657fcaf76f611d47f68b672f7dc9d97db69ed4caeef297e4fe7e735144593f6a4dd594e5e9587216edffab5ed49a72b4dc2dcaaabc455913f52cac9d64dbcf9624b94d91f3d3875a4fefc5cdb22cef2df945d3afb08a24ff9bece4e94d72efd2abb096a3688a263aa63b4d3d1f7d0a6b6b8e65d97b17cf91e4eae841ade4d9fb1ffdf9b7ffb4929b64db71abed2fdbb4fc28aceaf6e7688aace8b7dab29ed6ee5b72a727efe716f9f627acbde49e7756e4fc8fdbb326acaac98a7f5447734c4d92e59d56758be4398ebb253bc9a226a79565f9374bdfaa693a92e7d84b58513235f789929df7d4ff74d38a724ffe53f5a48ab6ac886a5a3bca77bbb7498a697a7293cdb4faef7d697296b35d2d4b16d39a96223bfa7db22c2ba69c5f5ac994a72adb7dd9fa93145512569625cf8d76d2f45bec246a69dde6dfdbf3f16cd391fde308eb6659b4b77cdc63caff28f24a2bc9b67ba79b4455f6ff9f525a55ff5112dd5b3d4792775484b53c51f3ff511ccfb6355973d22afe6deef1a3235a8adb976a08ebf7bd2ccb2f9e9f3cc5534d25ad2849b6a928a66cfbcf2eaa68a49545c75fb6eaf669cb479485b4fe93a7a6da729125dbdfaa8fd67eb69d45fde9515454d9d4d1ea47d2ff163dcd12154bb36cb48a2aab9edb97235b929e144f10d64ea6e679b67dfb9434cffec18aa26d7a969f35d196eda907ab4fcfdec59ea623db7a9265b4aa5b1dcdbfcb53fca2efbc83d5143de7edf764599eaca82e5acd3f9e9f2d55cfeeadb2a8a968ed68bbbb5892e76996694a72b0b6bcf5e5e6ff9f23ca96bb4cb472d29bbf8f9c24b7da961dd1aaa2df2cd9b654d5f4f7943db4f2d1ff8eb61c45473155cf0d567473bebda8aaa4888edc34b4aa6da9fa7134cf56fd5d25c9422b17fd39fe969f2ae75ffc845675b7234fcb123d3fbbbb066b6b6e93a3a4e7e4d89edb67b0faf17b951ddbed51f3f426066be9bbaafe6e8e26b95b4f8e83d66d7abf59727bb597db6405ad26f763c9c9ce4f9645cbf48275b3e2489a626b8e64273d6ac17a8afeefcdb98ab22537cf406bebcb7324d9b324396baa66056b4f5172b7dddc9df42a0b683d5bf3e4eae7aac98a7c977f56939fa8f9f9497e544545d2cfca51f3a3a54f51b124ff38f659559493685a927fe4256ff7a6603d5bb46d5154543d2f3bd9f2594996454995fca65a9adfec7b5611e5a8ba4b932dc97d8e7c82f53c7fcbf6b33c3f897e3dab4749729bbe54b76f4d36cf4aa6692bb269db374a96688a6735bf4aa626bac7f6fc1b3d255839df5c3dd3d634bfe8db3b2b596e56e5e5d99ae6eea4696715c52eaaad78967e34d193acb37235f5678ba263bbd32f6e91ceeacd164559b41cdbd3343d2fd9605f4a9e15d991557350b16ddbf4645b92b7aad8a6bed6ec5572ccf2ab6417bf674fcfb2c13e9257c5194bd21453921cd17d8a279a8e232a7af1f3df4b516579fa7f0fdd1e85cdd8d86033207ca6d3b1c1be2a38eb78a2e449961d6549b2157f1bc16ab224cb7e4efaf224d5d2ec556fd6966ff67bb114b7bacbd3a36e56b25551d524d1b6f49c145bb30a827393e856d17d8a292a8efb24535525c77644fff6e9d8fe2f92556d56dec9b6e527db53921dbdd9cd2f550face7efbeaba4cab6e4df9c25d533550eaca9e8b9dfa81f5b3135d12da661aad8ac9c64c7f2ec22697e9e8ea6a8a66acd7a6e92fb12f5a8599ee3f7a7899e15d9f5aad4ac6a177fdbaa5c1dd9d33cb9aad3a53b2f5569d6d11c4db5ef5d8a5beda548f6d02d8f635a5503eb68aa9f444b16f5e348b664efa13b1dc7aad0ac2adb3ddbbbc8fbd8fe911d7bea3ec7336be95374b3264a96242bb29e34b3fab6445b16fd67dbb6e63f4506d6fe39a98eedefa958728fb665d6f4937f8f5de5e93ebda8f21e8a03dd64550bacade8cb71dc6a59927f645514dd5245666d772bb264dbee8d9266c95b34ae4a81b5353767b9dfdbf4ad697a33dded71cc6a8a9c14f9888ae9b93749a262d6afb69e97bb1ccbf114d99474a02a81751477f949ee4bf51cbdca8ea73aa62accda4f93fbde929be5dbe4e569a50a81f5b3dcec9e6c53b1b3676fd7641ff0e79a54aac0acbba77da7ea49aa6d59b6a8ffafcbe71f9b233916a9fab272932dd1adaa1e355bce72b63c4fb1cacbfa79bad5126dfdd93947fbd94337dcef8dea4e5775c0ba4bb5fd6aab96a6c8db3d963a5d97dd9f4e27eeddeeb2fb2bcf39555d56b5dc251f4d9477ce7a7217c3bbf9b3953b45735471f9a36fb978b622f79b2dbb8fe138fe06546dd9673aa22a3996a2989ae2988e625ca56515d9111d39d9a2bd9be327cd2cabd945ae7aceaa6c9b96e65615a8c2b29a276992dd93e5488e7f2c7f0fddc0b8598ee3683a9d4e97ee765a7565445375a7e8b84d154dd3722c4b334dbf58b2bf1dc7afaa245b325065651dfbd8fa928b9c1ccdb44dcb35b3ad37784ca73bcf8aec76555556d4a32c99fabe4f533cf7d9aba8ac62c9f24d6e738be5264b54ac9ab27ab2f423dafefe772b8eecc7b135d696cd7d812a292b99723165ff173757cdbf3bcdc2c47c98e53867b873935551d6eecbb11cd5d424c7921c59d4c73c98c3c7753a9d2e0ad5a9b5ede3e9d5d6e427fb5bd44c7be88e63774bbe99a667fc67b7a432b58a9f8fa867f92fd96ea26c2ab635b615b6b566b6f5babc37a6d3e9743217b550955a53555545bfcddf9e665755f585d5ecdc54c5ad8ae868a6e427a95c585194a7be143baab2a3ef24d9c2499ee726c73d8ae27992a3989225d98e2a99b6a378aaa4498a24a9553cff57dbb1733f6ed3b37dd46a8ae4df28fbbbdaf2cf7256444595a47a92a829aa656aa2e82e4b12dd2d6ab6a8ca53763cf939f6509d4ea74bcf78069250a85858f71ecd8ff6f4f38d8ee6cead2d3d2551855ad36ef62e7a94abe8e979d9f6d0edd6b66bf9f7edca1d84c71ceca1c31ccfaa44542bac9f655bf47f32ddbd87ee1dec5315d67164fd2ecff49365df23bb7be83e5baa14d632ddde54f9e6e736c5d2c7ff2c9b7b86fbf30b8fb1bdae3a7e0fbfe5a702b5a2ad9992a6df2a67c754ece827cef9687a3255d56fa2b0f2ce76be59b69323674595eca13bf5b4a2ac2fd3d63cb73755efd97ec2ea5bf26fd6b7229ab2ede84913d65165d3adb2267a768f969dedb492dd24cff3243bef2df9d994d3caaa68f9772f479635b9d845201c93e9744038f6aa2a85aa8495255b143dd1adb65bdcdbf74debd959cffba992be6cd3df514debc8fa3efaf37bb41cc9d1936b6664a655fd25cab2e7f84d74a3dbec98d65114c7f68be3ee7d64c56fbe09505d5a4d6fee4db2dbab5d3dd54ec2ba4f91fd5ff4e8eea96a8eded25a9ea369b223b97fe9dbf68fb0fa163d53d3644bb29fe74ffb9ad9964e07e43f54fc425569e5e7ee623b8aff9fe9e7bda5b4b268ff27599a7c1cdb54654558cf9144d5d477d2ef1265cddd43b73a6915c9ce6ed17bee55b6fd240f611579d94db58f67dbc7d11cc71eba7b7de67197773ebedb2495a4b51dd5967ff4ff74eca1bb728e6179dc54915691f3b4f4a72992bef772f49056f4b71df568ca963f35477f3e5abd9a8ae97992fc77b5b3afcb7fbb72dba072b4aaa5587ad21c7dba559697a7badb62a81aad247a8eaa69a29c65bd49aabc87eef32c800a847514d5cffb99724fb63e2dbd4e37770efc75a1fa606577398ee647fff85b53344dd383d524bb289ebe8b7eb39c2453d364b47272444db3edbc3c51b2a3a7699a1dac6caaeed66c5b2e8a6c3fd16e9ae6a2f56ccbb13cbf17cd7e8e1cdde21b2a45ebc97bf9599445db9235b937cd720e1507ab39aa62cadbdefa123dff1ecb32d19aaa2d599a2aaa8ea6a9f28f9665896835bb789e24bacfb68fe9c972f3d05aa22c2b8e6a3ab6699992bf353758bdf8c7932c53defff7a32a9aa6a17514d94e92ac888aa82f517397855695b77c14ff589afd8b26df654968f55d55cdde72d42cfdf855558375e4a479fa8f9aa7df3dfdbf2c335851d69ba2789ee9e6a77a6e5e9618acbd6c7949a266dac791b7a9690e5ad54d96a928a6fbffcf498ef6d0dd9761b8d747312a41e22f7f698eadfa5b53645bf4822da2e8998e7b9ba54896a838a6a427476ea62cfa4d3e9a68dff18e7cb9ebff6fef19767381a6112a0bd6b41d45d6ec65e79e3cd9f3075a49cfd15d9e2acbcfcfd95304c6ada9bbf40c5505ebefdb3ccd6eaa66daaae56906d1e94c71ef360a68155bb54c517554b917d5162dbdefbb38743af96443f56755bfea51d314d5d22cdb3445fbc0b8359d2e6ed6e9c4bddb71f3289aa1f2b3fedd6e96444dd32ccbb41cff8643d567e57e1cd3af923d4dcb722cd914022a0a5692ed22da8a22fac5b1ff72e4b39aa53f45f68b7def6d9ee9ebf6769f892b8b6ba8f6acfda76d5a7a76a79bb39dede214aa09d6933d473e72f4ff132dc98df6d04d8a0750e9595b3445cb51f5e466c57e8aedb983a46354795672b325ca5b96454532dda9a8d3a56745766e547856b64dd5321dbfda5b11f5adf7706ffe3d5c60586a874a82b5f56dda92ead8459feefd4560dc5a581eaa3beb49a666da9227eabf1fc5967b33b8b5b6c59ac12df33c2bb2f35476d6cf5552355bf344d93225c9141e3be950d559c9b67fb11ddbf2ec69a97e332e9edfb5a48392dbdc657a8e2839a6aaa8a6272fd1ed5b1655fdc8921f7d61974ca73bb7dbdebb7d6e37f202aa39abca3dcad19645d3f26f74a768695b62dad6d896f8630bcd6c6bd5e9743a15f5017fee49d500959c9555fd3f457393a7bacdadfe386b6996e968b263cb929be42a9cf5e42389aa9e731645c72f8a3d74cfad1b7b3382b57fdf493eb62adac914e5bc87ee1f1cfbafdb5a33dbdae58e80eacd5a6e3625c9ee4bb3f466f9bf9b758f6a8b9e2a8b8e9bb3238a42b0eeb3fbdfc5d4b7bb8b632aaa56a8daac248992e9e6e617fb886ef23fb09a7c3c4bb145cdaf96ea59f683631cfd03d5743a719fbb7f37520e9503eb1f496ea6243aaafb34c9330d4da78b61a06ac6331009159bb5f46c4b727fb29fed7ea7ed9ab5a7db973c4d47d5a75d24bf9a755447d64c53f2643bab8e684fb38a7d3ccf5145cb334dcb9adca844ee6986438128c961140641c4503215490040013312003030241e8f074452c1a438673014000365a6708e4017c9439128876114c44010c420640001c00040083186ab5a0d05361e53843f9c10aee8bf507ca0caf01f49a5117dd4f12df92c5be91f602c8c582d998327c61fb9b79c3b9eb8fcc97426d0eace2b6c4b0c6e8f31d7bfbe6c3b080003f3c26eccf22662378b7d1dec57071fe1c879f4dc7bf38931bddc46ce2baa43acca2397114c47ffee4e950879eac623aa58349b6eb24c2951f99284bcef1a318b01a8ad417d2bb5f9347e16625e8c40452c1685321d83196456ee94180e58674d5aabd45bb7a2956867ad4c6dad2aabd7aca764c5d78a5899098f6eaccbcf7c3473a7edeb77d08e96c1729207632fc5c56bf2465e677d7a2bb8b0467eab7a62efba8ddc835e636b3e0d62d9a00c8d3a8c1ad488d922987cfa0943a96176158fa84f93cd27f8e671178a9ae3ff5036fb88df04460057985a851ed36cd34195982afc04f1306e51311e5a9542bd5661392b4787342bcf4a199da67daac0a8ac894c2d5a26d0df2d9572636bcaf0c6ac01de201595fba8160a1933795c2a10c518aa2d5ad2badad132aa4c6ec262548c8c64b0b302edddf77ced782babfa2e08d9888316e248e2ec9a6ee73ca2f53f2ea9c182a1e53e467a11fe209e851b8e97cd38c9608d91589ba16035f76e43675605c91468e2c29427fcf1700da2c32225d148ae96b19ebb747447ec2725309a2cad99da4e0c95b1a811b60cc555f189c135db6077ca6ba9a108821587af2838ea30065fc88c34b9896bfc4fc5b3c39483a8c26cbbfb461b967ec9010e494046d9e0b21151f578a992d05ddf026165182b8c0db0fdac9a5716f76505416b949e698532ee0fe56b57ba8d36151cd04a563d269a73eccde91d0831f86f2ac87b4b57d48518c2f021bd96ee814cbfc4ae8673525cf1d48302373d7aa79543a548c2b2de03b08f2d1bcc8b9a383659803612f590e09849972edebf867b1c143493246d615530fac1595de0a6d05b6fbb746dee7fced73e9c127102163a13ae9df3af9dabb32cb691b5a37773a68fe9a17b7001057d33752c7871d489d1d2c4477f9885c64cf78ee121b83c313d32b69115d8395c881bb42ff90952187e5a1095f95da0b2a11e2291d10ff84bf953604c3a303f6aa1e93300efe653ab3459372d9314c099139ae5b15d28a2b85767d3ab3cc47a92a502ce5e85156f4287ec1e3fe47fc339e31a06aa30b493016361c837b20ca5f3187637276cca8979941c713dd9c98a4e818a60184055c4df3bd4ab3c05abf8d2f5ee081c110d0395ab3c03793a97ac2f1d9bd344db48dda469cd7fb1eecb360e96585b42b658bd41f9fb2285a6c4bee2ebcfd915366d2bab6067802375715ced3abc9c8a1a1a54a4d714e07b063e3d3c0f6dd055820b434300b3a5c5f542cd937b91bb14a6894303f3e103fe51f34f2c9357173c84d471d0a78973aa183783a303a2a639249bd21099f70934d4ff7bdddf310825ab86c362971f32cc39644657888c142376c213dd0c88e3de4b6988ca4627062bb7b3a85a055435e59c011da66c8ce9e96826dffc7c2cdfc7864eadfb33e60de0f613b461415bf25653744642f59de1e747fd35cdbcf4b7ad040d2c9d6ccc2f5ef9a66098c5fc13b9ee04d812d8a7be58c72f9019431635e4f407683f2bf46c31ea5d22f172400055cf28b24543ce7dd14faec29a00fb4973497d5a2568a5de69490e8ef7fbe58b9378d44c29a85ecfefaf9234b4fb87f834ddf60a8b24dccad038e773b6f1231e1ac519d7146312d3112548ca7407d88c2326584c263a30876ccb444b031ee54ee721c346f2463dc217aa5c85fa92ea1bc76a4c97fbc110adc7f10db6be04003595e90e8d363c223fb47cfae3cf972a57171fb1c3a717d0802a1b5a99635e3aa48ad3566ae62c1b8e7de00362ff7d9cc3497ae5d8a7bde383fb6b7894c02ae8c878fbbbaa302b92a540512b165d4a1eb84aced63a9998293aee77ae7ac0b581901085351d9ce4e22d7d76bca8ed6990aadad1e1452da0852a246ee4ac2092d2f009203d07685b457e0f902419f84f03c17702d1d206751844e60e16f328002f04f0c5f1df2140430b19e7f1513ac3ad9be2268e84437c1b01d8df5b361bc5d59b7511e2eed772454728e59cc10169f02d882383e5593fbe6b30770c648a4244adc064761b6f4b88bdccb6ebd2996151191d74b2f30a0419643c3f00390113031698a67f8fe1186266ce87d1ca2980b8ad02cd82514b72af2356db35ab853ce916e0aacd254b954565ab2f97bff4707f4add083466b16b7aa92d070cdd5a95a3da058a86371d9cd945248010aa00dbc5a353b960c669aaa21081b153e0cd40570dd1fbd410c7fe3a0b33a4a01d91cba86cd100fa9273a51302f5bce5b602a78a234bc879c019a32338cc1dd1d7a683a110a4b8f1d80f877b7a5120124b5c1bcb6839314785700479c56043d3e9c427c2f638b9e4b5ddb2fe11c738575ec255fd0e79893062df3bdf3441ed339aae44d2fcabf561fdc647e83f7edcb8526d420f24419f2d66daa6aa01884f6affd91b4fdd83b02c5035dd95095a9b82f4075191b86e267de4f0461b88d2a37be567c53903b7e362928d90b1d6d8f02eb06619e4308f693dea4285dc98174e9004b234b24faf89cb408f0cc53ae55a2294c700c1b1bc63220bf7a6d3a99ccc0b370bcd742616d52e5a82786fb11741303e919c1a8d48f088020f9b836a873537c3969655076a36f2886dd1cd624b34c4a5a9ac2a8ab291f1d2d6bdc665d2890d382ded57eeaff726b3c658bc18662f1bac9a537f6313d19befc86f22909c9ff73958ae6989636167a17b94f8cb38fe4d782264ec5429f46304f5e41bcb41121a79ca165d6182a79e2471d97af22591f7e0222a4e1d6d0ee2ee59eca2c41f264e8e62f99578c1efc46c7bf1d470af457eaefaeda2931f9a9ea80db199f8508c0e75c31c92f41fb54c8514271175639b200336cb14650e0329161b86157654ea18429c7f8d289871eaf89fa18c0f70363675c4fca4dcf0e0694df42d09c1a637444104520f842584a3e975d09b1003463595ce3c4139fac1744d445ba6286934d890201488b3a77f7ea26315ae7bb907d460ba3a73b7f28d27ad56e1be974340dd4d7133372a78f544f72a1cf6b2078c3695c87c55b9ee0739d4e8be851d8c454359599157dce1fcf71aedad158365356c9d1507c5ddcf7fd768dc5a3558ec864ab0e2aeb87df0628dae5bb807cbc1507756d88a4b9eff5aa3fb168660ac1bea66c55171580fafaf2eb1ff7b10d94648b2e54aa1a6dce9540fd4b0cc86f32890968352204bcf7ec9340302d0f3332c7a540b6c3b40d2baa1a32c32fc4998a4aa5bb5b61261045bd4b6d458a3d192e019f1415527eedb876d09943708a7d4500dcf0655167f2d89d5a860cecd74012378caf1295f4108fc8ee90c5e238400a8ec041eca78ad587d677c85a9a78b624703fdb38de6accc1a5f2280f29168d841ca79bc4c1e6bfc8429eb4f4e34b08a71fcfda1071c83857952c8e108623c997e2c1466d3d37866166cae002aa6e6a4820b75d5b58ddb90467824f0e3186ad63966dc7b8ab77bf42807a7347068eea1c9a5a000174bc8f6e70fd370ad7f2b138d46f36449d19568783ca308f89305e21105e9f8225164c20422a89b0f05e14407a112fff8aa1cb00667f9f668614a91002288ffa6470a3def110216be3968655f7bd59bd2e62ced49b657f56c9964f2a6931e53c4445e228352229766ac9b2d48318c43404551651844eff83143b7890a8c63c53e2c29f3d82c7407619c1817d80140539e4396136734983429a1b2af7c89d2eae70a6aca260dd26790ba4030f6f38382c3361c58063ea0c050e393c8997587d4903d00919822b96400b4197e72639c8415ec18c3efc5d8c546e8ce5bda5bddc7d2897cc1791376c1eff23ed8ceeb01194844b70ba76e8c1238a96575f7136191c14ef4263b2c46a0793db2dee93527b7f1823d44d187510a5cb3d368acbf51231e265efc753788b8ad77893768ab5e751312a423f1e752922704a27f0777c13d4298d31d632cb933a399007fcfbc6569cf2c79eb640c8d9f9448fb5a6de382d58949727b20bc5e14acc71816f49063762ba12e6631f1d37f00351f0910db6e71d5233fe33670b52276c0d2cb56828017b7b0eba072db75177a138c6605afa378845280f46bd2ad28527ba9a481a9461a1c0440a7a1049c3202a8e84ffa0b5e5018d3537a10d3a3ad2db2050b25f91322ce5a5d712c6275c13aed7ea0780b62fd2e1ebfba9cad0a314c409deab2723d3185ed256720ad4cc7b3d6b9b251750061151362b310bcd56b0fa9894b9871f0ce6bb21b5abc8fa037dca83c1ad1deec20068d394557caa8b904fee8124c0448bb63aebdb2e34b001e61458c8227e501902989024d83dab078b2bfb2e88f9c78086f14f4b4ab33a8d6e9261906c9ed811eaaa41988fe94b51373313a06376e93a8abfa1ee4365bf6110611c68db867c7ceb90f30ce5341d2f92f9e81c6e5350cd45505119d230234f4077373b73fa0d99c12c03208b036b4676c66ea5f07195fb7bb2b02f684e088b404c0961b7ccb00ed3b9675cc13e9c24a57028d36fe576e668f72e2a59f12afabd8cbbe9e3c4b2d6ba7fcb4f1433f3dd2bf6f29fa7a693cda540dc2baafdf759c6c0c6ab7806991fa4f476efc6e7299d48432c5201cd6244090767f6c01a456eaf5d17d3c2a93b27b632e6bbce55419cbb8023e72ea9cdd2a7ac75b4eff0ee5c33c66031a3f20fee7c55571cc95c05b397b35028e091969998c9bbb3e33c570ef66039b13f2b8d0c77cffbf4e0ae8ad2532d29d95cff21b2be801d75af7523a1eb8f4410bf5f540e826f5c97969082f25b04d3131441b2ff0d280b06b8999ea3bee40943df725b4b2fa7ab326cbb6c467d2625bc0bacbd80c306c5a5f13843ea2c616aa45070de0a5c22d064b15f5cf1efaa46f6a4669d0a0a74314411f81085e2b43da132b423419fa2ee860f40aeac9e30295522df9a6c61ab8d25d58b4a63934e765a4bf974302d2b7387234f6c327af37603a336b980e7a6a87adfcaf7b71726f0c49e94394d21d220a2d77919437a1ec845a0e407f421b40e93b9832a84d6d4f0dfcac4db1bc2b320efb8635a3246212efe67dc9a12a62d9bef0f77a53bbbfb5ffbdb981f8cd295460ac84b89cfe1f321417700aa64e40518bd9a5a98f55b5ac83be17d2d676c89b84034262dfab2e8ed6626265acc5101ba6ede9d779744ea4f8b61f5ec9173b9000a702c029ef3738b8bf44a6262d47fb42a46a67e44c215ef044e80f1fb0f3e942dbb49a03ff64d2f74dfcfd5619c7070c8ba90d327482b394820b0e6f03e46a62b344f122254420809febb64adb6006a9d6247c98e3d096b7e3f0a19b79d1b200e4bd80f8b2995f22136999325b6469243244615bfc39cd18790137227c31d90e8fc06043a6a3ae4dbb9201407d5262ec9c864a540facf57cbc480daf17abbf0cf04fb2ab2d8e6fd31137d7eb223f5adc6a0b7d84cacaf2e0bc64e5e8019436f8f78c42a56d30d68f12bbd45162c5f90e48cded5c3d33c73003980621c9ae1326af022c7a6e8332d2cd93211a76978f5724d6cc5e573e87b61c19389b192409998ade5c11ab24e58a0d9bd061f33873717d68f91e5ac4a192bd760e3de4a8ebba59a26e51ff83eca43f6a72f4b8e6801239571030087c434ec917484c9eeae843359da8a033d5f4aa8aeed5e86725fdb3454fd7065230de65480dcbeb42f7014350561446e8f606189e3e1922913defed7fa4fde50a29b0c610a699ed3105f1970e5f092f0721cc638f5e6e2af75dc41c2dd501510178f8ee1a90e7b7e9f8e9bd35b17dd48be7c5eb162c24ea191223c5d0e697665f589c9ffbfde7fed1eefccc844c7be6d268e03e6497918d1cdd6cce1b2b113778e274088f5be5fa2f290fc8f5c3180833ca8aba364b8362289af1daf5176799123989517c513c6cf0891d8a111a0c52b86401210ef8b81b9337de1ae7ea87914c16c13c40bce9e17836beb21f8d74bce2e18acd0cc8abb9e91c9c214e57cb64a8b354b82244365c3428da9584823edfab43f5cf1f53d2530804bd2a2800d841a086aeb5682f2f1a2ce505fb0f12de66ad724b6f1fc7b4f28829716a1f22881ed6a44d4e829218128053f163acfda1d8fb6d3e1a6d1906bac4ba96ae709e937d17929dee24c3a03e94cb42716a0451b77d7ff1111ca50d711f639cd1148545e988cd67a595e0d304e5a8a29d502e763dcd1ac4d546e7eafbe927bcde2dfa6cf2bbdbd1db927561a9ac2ecaa628259c596c1465c0c3c3e4031fdf00108698eceff5ebacf6056a1425573438c03ef6dad24518a342b1cd8edd45c386c267922be47ca06877efc17fdc11790aa51ca5b34404fc608f52ce6f377f98c0764babd9f2582ea7b18c84352d1226f33c15877685a0f21bed1f60dc733817855e6dbc49b4a196048d4c73b9a4c295d7a62706e8d243df46448b045ba0e32709b83e5999420e97a6be52be47a819c52a972d057e6e00c2b8d1c9fb8fb32f590dde697b1cd44ca334e2f6341606ae9803a945bd02cde6c147c3ada9ad0da72517e7b664303931c009b6342e47e5cfb4d9134e3e9e15f96ea5d053826943c55b008aa20abe8d4660280c2ce66bc0a3548f0f1df65aa5f37e1de79f2817664ddc08f650916887a2e0bda033e2ce6b804e0dc993c7bd2138122a52aa284bb950420a6843e04c01f927308c896c7d44869597c44d590db9e5004e1f435f26567c53d26f44673e9497ca285d460f270d745cd7a5f948ccd35c17544539c127d52c742c700c4c486222cbf966729c3c20b4c4324a723964e304c549b42987e07b5f360555d8b220ec749bf2297912d737fdb5c405d34eace74e27588acc6958acd737188f7e0688da3dd0e0ac4e83c24f9cee5f5e67d06245d5d06d6581bb6cda985496c19e5a58c90a5779d23d5cfb15b83e4dd3574e0c85416063f40ff2e24ed3e3cedca316aed1a2aaf00564d0e6dd7a4fb92bc88cb2ca10fd08ee15c75bd56b1e1c2bdcdbe7142a083fa101f67e34f8746d04f55f6302324b2bf2fbfd4c412e503db925d431c8153fc8f866003230ee41337e0b1a0d8c4aeb46070ee699fff7642beb02663685946ba8d7733c81c48650e1b82bf36845d0cb449be3eb4ac28c7e2cce811d29ce3e4f7f85914684f8aeff8e3619fd0092e4c7678487632c1d7293a3819ba284c850f5567229ac24213b93844b0a1c10306d16de1238a04be1fae5dd4f11ba60a6e7c6f483b988651d83eaaae716425f1f0008d48421dbd5417c886b30e513baccbfe186f533a2aa0e6f4e95c6908976590606ec46a6795d0d2d95ef135389a850b52138cb98136f672471d332ea07d88cb402cb6363c88af4c8c56d1d4910a379f692acba651e0f7173c6509327de9afe1a301b910156a2f02640dbe4ad206e8788e28cc2a6394b8227933e0a7e779fbd252be41861b79d56d5b5ac203a6e4c75fe13680b390c1ad2d1ae099a74b4bb6fa5a35d695046603ada0d0d1d6d9dd4ab819a8e76773a1ded5483d2ef4f479b52f01f4ad4d146278c72951a1d144f813ff6a1ed0d863fa73adafd0daba31dc9504abb3ada5de64a59aca3e51d32c6c40094e419efc262eba8eefac57554b79f761dd509a81ad51caeb5ea375e2e1960479b43a14f1476b4e5a38a295608838f1ded1acfd1d1ae0a293bdaedcbec68b700e475b46db97b087f14b4f9d3febff082f178835dd9eb808eef49a87480e7dd0cd33effd0ce3fe1eb7f2f165602529fada025bf1716fd970ec773c82f9cf20d80b57b5e79d7c3de1be5dcc25f5f93c157a0977d80072d72e72caa43636f64f0710012856b25a7360ff47f23d4566e98614a75bbe15257547690860363675e6103a5b92b903f3e8041e4723addaae29e59990c3f3dc362233f9536b53101ff980ea088b8fa09548f11660d5fa17cfbd8177942fe61c9bde60fccff35b28a3720ff0f18755fc770f5a1d3ef02ec951d8c6b3981f535c33c5b3d8ff8608fb0f825f0eb0b523d4f907dac72a9fb83ff6745bec215fc9f1386eed7b02e7f68f65f0075bd27d1eb01c9b705d79a7f70ff57c8aa5f80fe3f61d2711ddbed934ecf6b109776f23d1e217c59e3a8fd03ef6f9d4cd53d88df739a5d6d9710b44484db47db2c6aa321db08d8e2bc96de1a3cd5d0a1f6eb8407d33aefc05d1f216a7f02e4f11f36659ee3fbbb24a7e13f8e57f3cc7bfc4239e78ca7ef1bf09efef9b49d8fe0f79f4d390ff8ffddc86cf213ffc52ce6fe3f509d39e1d7f3169c673ffe6dce21fff1655fce139cffd34dc3491246f140f67caa3bc3682462e22ff63fb411abf800d8d74b514719f3d6b333ad47e77b5c3f0d4a511afc1cab756906027f46d797773867c841dec5172b980fc0293f78a8d0fa95c13aae2d11845ef6a1f9dd041769b939a3958911946341edbf1be638c6030a880e668b41674da9f441b67b8acd5958c267351de03da716446547c36210fb24ab1c2fb29378ee377be43ed86efa1cdc21d88bb6d32e8f9785d3095bf8eaae9a2f42febb1302bcdb2e005ff26fccfb2391b7bd07ca9e51b94e80e7016d1174d0c40ebb1aaa37f1a1d92a5fcd3d0babb0c9667dbbc9d28b300e27fc9f5010cd0787bd6506d834a84275a2f7e85f2c0f077b1f77e8f70ca48a007a84499a78f75d65c0ee8c5de8234bfbad7ee6df6ab9300d210e3f5540ee841f2362a8dedc24740b6cde18e84ed5f95d78edbaf54f3be571e23f97a9d4227838d5cfe0eacbfd938337921b1d851791f582f9c303135064926ab7115ccd2670117aafb92657dab0a42e267d5ead7f7dcf2c86a4e4643357b804d4306d0a82d390f3aa7164c7c8cb03ceb0c176098abc2bce4201b1d3523b3d4dadb270dd1e4da1a64081ef94d08d824f64a5b4a13715ec2059d6d4f15132229cbbd6fe4ece02413c093d2ae9b6c708f25c3544b48193830793fdfc8eb33b2d1f7d07d03cd91acbd253c1b571491251d4dc23b02359910747c3ac42bac2872c88db81faa3ce5132b20dafab73488a4a55c41c1c0bd9583c498e013c5483d6c89b2e6894500f6b28002244f889f6b5328dacdddaf90116c79c7e2b28e6b9ae55b08e7790e61e6e4c92d28eb720f21c79979f8c811fb97c771114dceabd23dbbe7fe07d0a0a4f985b70d9f706bb379223afbd61bbefd77f6b45ca65dd6d4c95bb26b30a077cf535e6b73ef3ec471fddc3032cb93bd2a1e01679047a73c4ab9e39a8f1d975e9437cb6f140dbe31d2d3a914ff4b319fd820c38537d2ca01d3f6fc0450684d283823c2bb6883297911d9d50ce3856b3695b0db2744755b2837a231c670b2a3ebad94315cba46aee89e6723e81d370026e45e2266c2bd5823ed3b8ec602ee62bdf3a0e29cfd948b6c339a4cf381033189ac5dc123c003936fa40f49052e370a15f1310adffc484d56b8ac2fbb2a2515459747a82ba68fd353263f017e971780a04045584d02d44da03a9455a7f3549be215d327a4d9c9cde0a050c95c20a99525ca91407a912122b4ba895cf72058f5e812d58da2896ae92a557b34ca06839285bb0d02d30854bb972e9a4d22547bcec54bd1c600183471a86349431cd49997ed0325d6a664439f3ad67101134d08aa64cd21459d344889a7150354f750d5661c34ad9b49136a5b44db5b81958ddbc903780fa0698c06953381d9638891a679ec8f9ac72d09439589d5350e8b4c10f171fc48e6ab5c3267740d43b6482a730c55348f284d23c2b44cf47d58365b20781f02941f974903eddda6747fc1caa7ed0963f34f44f0301544f01b592400b34d0b10802860a82c8a0be3aa85d2114a0846648a1335a081e31045743d5caa132d1432514d18624baac89a089222e55d45216f5d6456dc268a7323a2a8d506a23007154591d552a8ffaf5d1d08d866320632816058f22d07daacabc0d55f3413f1473fddc96988b3c2789ce673ffa1d36216698b0d5f4fbb5157a1d061d9bcee327393314f73ec827e710c80cfe1966d8d381dae5b308c8d5a3a6ebe98319d1f057e1c1f88212073e7691b31b00af0a5031785c90dd81d56faf3346dc822871c7cb8a8f4a83eccdb5996f41dea84624ee6ae3787dff63bcda18e610b9b603c3597dfb1fabd9f2b384f20e9a7ad7d5fff834af7bfc8af4ea6d41777ad7f4d2cef4ab759e870a1497534d182a7123bf8e3a36e0bf3bc8fcc9dd43eff34214619e7aa558219903bc96d52d5729d24dc21dc6a066053dc768fa67fa467fa33afc16c142e95b07ccffa590cd84498a98e128974e9b4339e1c8ad0815f3f6d2282be9646f123863190ebb68303cd251c8a718bdf0dd6ac19587b93d6560de13d4c9e60586f03afe182dbe2d00965d9ae8e9e5b5758aabb22afa83ef386e2963ff13410c7b9225740cc5705c9f878c231ad374d7724e71c7fa34cff01dc2bcd19114549101dcdd8c7cfe8cf5638c19e62525b197f12762b702788cbc2e815905b2688080178449153bbc536b1a807a60d86150d4ba0461a7f337abde5203303a78381866186e3ccec8a0a3a323386c080361b0fdfd1fd24216fcfd17fca4a7871f01d0b615c65f3d0b2dc751d630f9cc7edcd3578ef3f117c11c1ed94a4846256dbd430582b3c587ec70b071a16c8e9aaf4464a490f0ed4cfe3170342357bf10c0e83f4ba94222453f2e91e4cafc04c5ba486840d3c0370629380f85a37cd35bd414c2b5d9e1662869ae7bd752d818d4e0790757e03a6758ab4d8f6d8f08381b389bea035925ffac7909b49606c8cc8e3734fddcd8ce8efa30c3c33b36e2583e5f1bd0b09161d8b4b624aa0743a2b92582886ba80366430394460f6718ea1a16c36bec8702e21f324f6f12985b890b05c57d81bbf33721bf19dc0f40c6509d15812db5f9f00443efc470259b1b4e07c81caa6b7560c1e690be949276d27b800cfee1bba8b544bd349afb713fa7f925c14f62a477b0797485a3b15d05608aae24b069d6d3cef44409a8022c4de630697744cf0f2d6251ea10d71b5c4386708b81b28c167615c5a04e989e06aa76e30d2bee6070e7bceffa2469e8673b1579b64ebf26700620b8fa199c2e4bb8c710481e64d8af019422aa6fd632922a44764c34b4bea9262e106b747e5864d06f97802e26176aa232fa11861820201e80b7145e249d20e4647ffc5772f9571a6716186aeb6af175e576d3eec68ba0532dddea9d01d5e4a6e0df624efa1e2c550de4c9d117e5885a569c985d74e47a9c47bf49985a2e504ec408e6437e73f1c379653d6124251ad8c675ea4a2a0122b292c89f101fe0428665fb2d644985b65d0d9da5710bdb17b9636b192455c5f31766d73f2b660c2558727e0e8ac4df160d437709b4ebffe670ff9154651cdbf8f027fa916f720b15ee70af4f048258b3d0000744404ac919def86da3e9ff10615c10383b960037488fdec3d31c70b33cd57a174ae3058fdb591ddd3b77d77d31a1a317149a3a532e988835d1c82feaaebb70135bc534c6a4cc98d029cf65ded5a416f1fc54d06328615204df8702c725534927cc45f97904e187adab9d191a22d5f9082a7df99d77bff435fcb3741b4c11837b1139a78a4884797f171c6def3caa6ce0bde13c76f3c3fcbbab869383565a3a8e5cac8071bd769561c812109dbf301d2c49c3dad06dfd575c7485d82f04f4ed675df049dd43d7b7c40b6851989e96b89ab1bbf193cbe536dd490b275933b0c1df0d3894096ab0dcab671976899103f3c10430b0312f7b12201a71b95dd113fde0d9b42ff93ac88a9f3fe1e468963ac7b57106902c0cdfb6865180eab430896c04c23396bd0dd7568342cdf1c075d232cf6f8a3b2d272815dea999e458064fd5bcdfdeb431cb733871f6950fb94ee8927def3471789c8599138f52141d314cc5011f637eed76fc36958d64094bbce42d63246f2d1b198082536d9f4870e008666167b6859692f24d2333ca61ccf8b9b5ec6bba54308b9362d673b6983621ae6fb0b6e36f9508eb8800dbaca93caf4ec044ce6b05bbfb980f1245e3df66d979925f1510acad238bd64cae88c401087894075a0f9ad541fb8e3a9974769f785120ce681d40957d03d37d7e1ffa52ba4514915f3c7801496803da7dea268463dafd5af986b9b63046aaaa21d0e8f28686ccab2c3094fab813b24d944562a38fba49722cd1748eea5212038e1405d9b4fa3f7af059079b6712f10573b4773012db5feb34c55924d9fe4504105ae0b40a039e260fe7014734058fb10e8af8e36b01e916fce9687ea93b39bd7738d02e0965f36f59e488fac31ac2e70cf5a71be9c471374adf830dd69edfe805444305bc12751aabc66bb5a63bdfc54ec81742c1ab5193e1890467c3793eaccb98694bd68500e2cd3a0958d6df54135d18f967b4451c3c976bfabecca57ac2c7a6413a387913777960c455ffe0874fdaf15681f13cc3f712a1118685a32dcee5bfc8ee63a04147d02df80b58739b57bce7afab4fa7ce5079e3266c8a960826785d3c4495a05c7c8d26193c85648a458ca0b4a6c8228f4e16b90697ad475524f5fe3a89be3f56bc5c33ee9fa5eaace52843173e81ac5de625a4ef331fe6b36952266a172505c67de697e9ae734b5f4cdca15e42b8db23a586d12c51b544419d3587a59327631e32ad491c327863090e7776d2e7cc2c5fbbdb3821452af833292739e14d642311b27e285772e99e78f2686382fcf20426b855bb8214035d0a187d11ef83f10728540e54999b9b735d01e3eae0a4b8b2ea7808ee05d8e16e8b76d61c19eeb41a9e67a64df508fc33e24fc6b0de9fc419d116b9fd41925ea5be15aaad30001cce701d78abff05906b81ae4757704ea5ae12ef325b0390573b62f0910755be074c2f438e1fb1176d32b5833c96f25c0372ef27ab28ee410d022b423d04e946c4270feff850e4d9c711b44e7e7f46a49dc2bb1fdd655aa56ca0ed56189352ac2901879e4ec7b384343714d43b6519e2f8e776c1ee4746fda8ffa3216b2cb9f5de70246f45c3aee32f6ebfd0dea473d88bb4c1d70ff52e5628bf76d1e0d9aeadeda462dfd00d96a4f1e8cefe701361ff908189cde14cdef7da7a9ec798a25d5c8d07fb366e71fdda7334eb7dfe8edddc1bd95309c41694dc7d6517aff3b7dd41ecb5fcbe0ebfcd9153142f0621c8dc4feef8386588dcb5e7470d81771d8e6a03c8a4006017692b5e3c5b06650325c933aab45b0836825d61abe89010e64b843e223796df599f5ecf50c2abb5f566cce081ec22b38b41b0dfcaa758b818be45d69d47ddb5e23a05f0989f4c23a020fe6b49d56c81dce13462e74e52d211dc23b9b5f64bcc1d4c5f743b581e1fb03fe0cb1907cb4dfa3f390d92e0b3453011c53d70a61bc989a73d4cab38dfdc612f3343dc04ce44fe811cf28787db2eecf5d19cbfb62853b9f0201d1dd07f32c9af0aeca7faa916b3001c05e5af2dee5f14f3167f4a0132ebe233d18f111334aef0faa5ac755b36a81ff9108aef84da05c27da2b91169d89187e171948817c309f170ff38e3ed8f59d76491217a3126e4408243574cebb113068907bfb0c84155f911965bb2978a0162f9941a0fbe7c067ce0ff5e896285d3f827d026267388762616777865df1eee0ca4401befc2a47a07e8b387a2db68c2ce987ea3443c8b39cd4e3b46308b67e9e8d927947009ab692380d5defc0b4863646bc4f234f2b96add45d0663bb3b09099be9d18f4916b783e9007e83b883ca3b43f00af2f4107fcc1e97b9bbeb196c22b73b15bd6e4ce7d11983eddd9424b3a928083c0a125e2dc9001f49b30a1c50c0ac6f92b5bb18a9bff39a49c63b7a52380545a5db1ea2079c28151282a4f8cac27b76ee2fbee16b8a9ea1939668743c466504219e839817e4edaaea4938fde9494388dac996111f17e0f56b47a28c98cfa861423d27597c488fcfc3608eb27f9dbba7e9705f0093ea0c96a28085dee742cab2841006554fda115591fb3dd7751f55f4aff4f09d6d14b153f7731926e59ca451a691cafb253b3745b598ee8ba2386cf5cac35a4aa0a3102d60e9c503c48141562f0cfafce9afbf8c605c67b45c8646f1cea985b47e28a4103e2cb40ae7e5316e4035662cfd049e4db8aee076d045bc3cbaee863d37f8095a0c8faa436e3eaa8b635e96fd26f5ab01eb3b96c2093ad2112c3ce97b6fa19a5a8fa375d16b0661440c42aed5df3dc927450ec6ebd4bd5fe8ea0bada96646cb964ddf0a38815b789942325bf07416a64dc56bc88877d0791dc9cde02076194fa2f59cf1057899a4aa26feefa5b90dd47362e8c303529546866ff8253f280d903eea37a460f4af954ca36ebc1feb592e8fbed1915a39fe70123fa44709eaf9e497c97525173718a0a95528c2f775c5a12c6c76e01a157f6b8b327919d0546b20b649b53340e07ee126fcc12466b82ae0d0c0c65535929d14dfd5ff8ccc2b2aea012d58fbf50a6b9d96817025c24db0246d4e303add08236b1a5b4ed21fb9f6d59fc6819c62ddb87f656f991b09a964d583612a9083d79afb50a1eba84dde5e9451cb91d3937eae71b07138a920666348375faec46a4bb038c08c481b2c01ae6bcdbf6b01120ba6edff876b9db471b79549a8376d2f1424fb0b86df8ca7bf80798b95149d9b353c4d11d99a5005469fc4dcefa4e0f27c95616f4af59ec29ca12859da1d6aa4e24f27f9a207e9966a5c5a9c60a0f87d184c8c433414562472256dfa8867d19b2778cad20d4452274961493bdcd32a8ca6682dd027254fc84bdb259402910e46e1eaccbc537d62424a20691804d0c4a7d892c7f5501045ac82772291a0e43875c7a9a07dffc0a2737476733b7d2ed946d4f69372dccb208de506e198a8684742cee0084cde1b8c04c0ebb3016ded9210d5e600d27286d45a08e04167ba822266c2f2146add01d86896015b736e9b38e77cf37f02ebcbeb9187980b953d6101cc37b8a3066ab117b0891d93b9738d972ed120901fce6b87fa7a5ea221f96fd7209c8958ea42c7316842467500382df7d663f9cae2bfce4f48d728f909f6b3a75a2313d0631abfd0c5ad36cabe6bf2646bbc8626046c4cb66bafbb2247207fc2e39e9903c35394120fae136f9c2f1e7306f49d4f7300559c4f7b3d50a172209a13231a78b2c54ac09f30188dffe7120832542bd8afff6f9a028e6f18927125b201dd144e7e70fac6953d0b32aa57d7623d5b315b35ade567f6d0e86dfc366d0a98319581c364f6d37a28d04d77ff39b700c92de32e87e8a014986f66ebf36e112b4a552a26be400ef0bc4942b9e9b629753fb8a9e45d1f11e6902a2bb103ae63762446c3caa02add8f3197d40b7867516863e122fe83ac22c9cd4a456335341e10ca141d09fbae085a6b3a74aeae6a1d22d159e38b48370d0299503d7805f666138078945bb20493a1beb80b778a3cf529d66aca5d98e48b34cda3a6541499aa42b737c4340dc46784cda2b01baece72485c260bf6e1b2dc1590912daeccb1a9e8cd35b738dfc79606bea1d4cfcb9f2380a9ade4c954ec307eee91b9492d68dfeaac45a38b59c8e620ab7a88c620181f534b14711bb96734ba88f89f8eb775fc0ae4d0e8615e7efd076e86b8362a981bbb9b870cced818c5d6835bcbcd274d8cdaa70c9631efdc98c4a9eadbe995b09359dae68645ab6b37ff9311ce49cfbd0c756981e8538375c66e3b3c1b388722ae4ad83f97faef4fc52fd27fdcb219be875667a244635f9ea8ea9e2070c21accd558b2a0b00e1fe2e98c1416a612af5500357536f4124aed864131b56e16d7ccd384d26f5485091bffb693aaf78eacfe9cac7411b6eec64a4c0ff18d21102719709047f5c41037c7fccb7dbd528029f832a0d5bf5cdb6135277e21c1ef225f6d484e102b2608faec7af94ada74ab7bd3e3011c9b18cfdaed2ce55ff9c4db5d224a54f6e7079e78b077d8d9b07d9e5c095a9d347ad795fb667a37200f8d8d2a0fc2263cb97019d6fc19d6152fa4b68f3b3a645080ad7bdd9970f75c47571617e8c6d19a9d1383dc0880a663e2a8ddcc73c9d9da1440df3eaad34ca9b099ba180ecd6b6ef59080ad072e1a97cad3804c11eeca2868b2602612651fed1ac81752363ec2e0098732efce178110159124ff885aba694d81ec688124bdb71d3facc4b6997190c75e9c7bf09e3cf2cd576f3042ab0f60b3f2bdf18a2b4fb336fd0aabb3eeb8c8a88ae6ca1de8ed7bbfb5748f95ff64959b073faf27e51d737ad88bdb1b1f47fb1eb4d9ef1028c65bb07d4f6782115a1fd23a93d9eb4993cf4ddd955fb6a7378cc95081a6d9eea503ca48b022a23a5ccd5bee245ba031835376ed1dd5f54bb46828c18a50e15138240d5018f51e3e72b950bf614451a03f10eb5e95f06214db2541bcd3f0fe8695d6bca618fe7cf90e076395f5c84dd578a60356c80d4ae4d1660af45a41a9b3a12e256048ea8b2021987002c51149d12dcfaaeae761c203356c50b92bff3dd509daf8b450617f8990c63f79c3de3689d0876989a3750f94d86314055fa6fd31c98229e79d47cca2bc521dc642d738172fc820aa7d252957e4e54e48f86c539b415e6d1d140af99b56e7366829c4bff56903b742ddda107ff8f3d2251de4452a0a75f0dc3792a1ae3bea92d6a14802d944b92e120912d21b3d0cfaba12eda0eb60add2f1a92a3318abd9e1baea334943f436d60b511e4ce0d4383f6baceb3312f2902406a429d5389b197fecd3e7f36bfbe4069ac993163fa4d3e095c0fe7456de6a47a21ab4a528eafe982e48861536cefbf5537ae66bb73d991a904921a9a132163678e5cfbdd3b22d81fe7a2ad2e59e95722b371008fd386aa5946f823cbda125da6b80bf61795503b3e4a1fbea0a55fd4201ef42e0f6a38a3c1e779695f844c7d38cff78ec933a27ec17a13623fad8693c5e3d92ee0b23b35366e1cccbd5ce6bb25f13a39924333aefdc15118dcb6be833b9ed315d8496c946ec621de82fb13d354d5690b239feb072b47d573085c3b660f186dbc6e1decf6c21e9f2be46664d0a4e142457b39335f13c250c1771a000af92147b0b11bc7d07881a0881662994dd8a4495006968abb624df6321eb3cb854d022d9acfa27961b4498a5d89152f244a600a5d60d1071763d4c12b9aa452bba23ee589633a703a912c92ee03b29cafe6f504f8bb2b90bd138286006b0b0e262d87be82db5f5efffba9c2d24545935f429586ee048cf7ca2ae0730a6e13ce56d4ee5f722709c76aa75a00fb472313b0050f287c8b28c0cbbad0306beed8471e7a8e23e62f7195213e90bd18b5c127571c189c55456513606b3237149124a4cf91719a0859fd5a1d6751580e03d1899ede85c027f4f0761ff9c8f97119e5613c1548f005d3406e80118eadcf5154541290f3e9892db6c56df0d58a673cb7003a3fc5f65d1d5a562afc7ae612fc5b0b8e37225206ba564a498541f7b3c0f974255e3754a7de423fded7220517b88db0621597b3216b05d74afae4116d1ce00c4e0f42996191feab770259d5bdc81c29884cf96e7f4aa8d3fc51effe796a26010acd0f2ece258aedc1293a7e5bffd7098a220f9c7c2252f6145a7fbbd0b829f4eb54774e322d88f44dc23fceb3f111a231d7e3e68e497fe7525fd7415e93ac79d3a7be89a3550d0955272a577c37196cbf5bb93f34b866d0a122608e325be9c0fad12610a75e88f6197a7016e59604211222b64f84efa0419169c5a51ff0bb7688f379845b009f32613f9e170028d6e14e32f596f23a6274186d0c06084a82e68035ef7a18682e893b027e79172bfdfe4384857226e3df3caa83e24897879a5033085b69ee410ee238b260e827dedd52777a76910b8d0ffa300729ab74a6bb185cfa446fd65fef82001dc688f803ac02d6de04077c504e14423d6010ef13270a8662d83828e383ff50819c025c0f4ff41ad3ea0a502b0132787a1d9e1448b5784e215bed4f794c8086b4cc59fed6c57108c85f4f82ec38c1ee40cd27c4838f9465d7c7238a121acb7c60cf0c36429efba650eb51eb114de4f1cd1bd3be223cf2a02eb9d339e5b342f00d65ddbdda0937e969ca097f3408ce2778c0b7c507522e791d79274cf8f5371c107814dc87284e67c684067833c8279e56f0ade412f60eddd8cbd59c752d7eb48beefae5a145791fb1f03766b62f089118e39faf76b964b43c3fc02dca6b27b1e0feb3673752ff06e0a7c884f9fd7b5c9f46028be1624ebbfedc53f3d22f08ab426b8de16fcf18d43286e79b3787a6b05bc9cfd4e0ea15682777f95c4430157b9570caac6456a3e3036c8034508032860e372fe592bf4e3538a30f05f9f9cf2baddce0e724007b0ff56682a0643eb3accbdaab8595d7c80867d142a155b46ddb15e8f90b43c7bf70c00d87e19114f823e7ce2311340f7836d559515098012f17e70884e1d0027df7783345291bfffca50b89e205f7fb3186c6fbf2bc6909d8c87f39678ce69e0708ea79f3cd889639740769ab22e01e14f77fe8854bcacd17c18ed1c81af6fa847a4706608f0c91f79beabed33ebaa1c8c1e7a38e2f0c8c7ff54def00e9468022515749f69d5d413259b7e3d61ee8c6f8ea65e9cfeb4fb3bd9c8639cc7cfc1ba9eb6af43a4a1033d276491c80502680a7d2fe2468611d6cce664c06893a50f926b20e21d2ed7f996aa5c15ed7c4594d499cc7e52c9cb6d2c8e08eb9cd15fedc1b8e7bec8c7e75c7a910488f8decbc0ab66e429da40d723dfc432b7bede2b2849da64ccc2ad4a2f9a2c15cb971b9924d41de3be2de463ee54122fb7d863047d5388b4c1678582b64ec9a754fd9c94f003aae95b1ee40df9c187515b8fddc2ee79f7ccac1f8cffd9ac452dc988d3549173f38fe583f9058c2c27e9dc763f6e315d441cb2b490fe30c8f7e99a8b57a47e81b44ea10fc85384993d1a0ee688582e924e0823fd7b6c4c4f755e92681f7d899cb698725a98d3e5bfeb24315cc8d54a64be230ef9064d44ba7d708b492d852c6be6d625696606a6716b080c81a20a198ad31c81a3cf1a4e0fc85f235e51b51b6d005072b56ef98eb15c9f648a3ea743f49568231b4773beb1cbbbf411e6cbeedcec347dd785d07db9aa817c982eec722eb2c1869f9576591a64782f9461c76cec636b63ada0549bcf8eee2396b7c89783e5c94430a265cb36dab025c2fd2b976c58ac5a660da34615db4df9090f21098d7ad251ed901d166980c950de4b61f42f5d2131fdd658704adc4233ca1439c6da48cf7e169adf009bb17b0ee72010456ab3f5670406ad88682bcf2f2ae30d3397522c380fe15a7979e238761d98715b5cb31c3ce254be2256fda2b27a99dcf75dc2e5d20c221b810a6ebd1201b509b153f3bd9ec928a4c4638ca67527b7288eb3a14a231890ba1b62f33ee17947e622cec2061ff6060bcfd60a232b3c66d662b81537db32fe408f078fbbbec7a1fc0d1d1a93e9dc381e380442389220014007c78f2dd7d257598ac810b9027c2b0d85061f086262c41fa712b7009ae1bc9967b0539138e237b31dfabf5fd2092883d45c5f7565c7160b13a533ca90edc61bbcf5d9810c59ccc8e93d62160eafa1a754e46d21dbf269f311e311e29eb56bc4087bada2f897658a30e3cbfc3617a0462a85eee0a00c4cbf3586a5465bc7f9ae2ee86fa060ebe6354515ea8420cd036aa2c10efcc9cd24a4396b27f861afaeee03271a1fdee2f1053eafc441fc09454a480cb01b5704f4e81da7080f209cfc85751a30203e33fa85a8f86d4500fb99ee51e5be6919a0d3b7c74b5e7551b8bd3d1d2e8938604e6a826f2acae255acf2a2c82cd0a7ba1e1a90ab1e40770b2c6aad4d8d5bdaf604292fa648add4f1a17a572d7a7abd5cb0f3419d6deb79cb0e7e3584fe7d50c40629bf2c486dafc75f2ad14be40920bea3d4c545882311a74f818b817736234e61041353944b1199177f7d3974269ba3a7bc12cb1b1f02f13133399154d1e2f4bd721cf27615e3b6ee7baf58fa6d8c2d42f4846e5aa41997b8086b865d4e188481e78211d27e488e0dc3f1942116ce7023c144e0ea59231759032960bc58e36df29c8b96190f226b644ce1b91b8ee9efe082991363ece1950d19158888654777800ff8926a09b23bc2ab50d58c23f22de5219567682fc6e49852fd563fe4b89f8c1eabea79ecba36aa32669014f76b4f57bd12a0c6c5da62bc68cfbbee2a8186fb24e7aa94ef6f61e782ede6f5f3dbd7c4dcf7a29d73de5ddf9ab4bdd23e72eeb8ec1863404f912944831dab19c1c76fb6e7c7e09f9efa2b432641f5333d10e4c766ef7266e1cc793a13b380e5684e389031949e98bbb9a47baed582c2b1530c5093e54c4e956565fd1b532bdbba4b3f5627b8ae19dd51a350d5f823d9600c97e87ddb20d89cf858e2219b0411691260499d632d753a0a7594f2719fceb73940ab0417cd92a241a9be3e9741fe25d2533be3321fe80c5283fde322dec46996784f692dcad9e420b66ed5f0aed9618704fe7d45eeef9f16ec30c5c345cce1bb3db74f2ba176697e4ff91713ed0d0ce7083ae1e6072fd1b9d562eafb6f32c8a1c8fdc11c5cf5856415274d9d8c5962547c59fb1011080324739a63915fc7fde00c91fd966451c31e984854de507865fd62134cd5ba22f62fddce95c88177d0d6e7ec5c1aa5dc9835d2ee8c375458a4e8ea73eaf1ec808f6768f86774db527826cba9bfaf5d50ba1ecf82facc06ebbfbf805451e423db3dc70d40aa2cd0f533bb9279a354dd16d0e8bdd0d17608e91a18d12da9da8ad054276b0793aeac6efbf75a63189678ffe7daa2e20f227a796ffd8f0b2c96701dfa5b479c106f5545d3a25c7c5996bcbb3e0f8a8266ca4304ed382d90ba6219424644869bdf825f6c61449bee05510179882da60a3e3d8003ebc809a36c31aa010e7288d85e8f17266072a91c867a46573b33e4818f17c7b70c850de6fb1bf630744836eb5238be89b2d68b01b29e54f04aa897b26585dc1ba96b21e1fd7798cf0114ce8a5ba75d86833160e659ff902bec0644224af1c5eac946eeaca7c860622e8d26973dfaa1e7d38b1c31737708373412485bd49dcd7a70924dd5deb3b9e07862618b5e01802fc1a1d4dbc5ec4a9d4789cbd94e21660cc1ce91cbdc3b9df1205c4e86f3542bd45de58f43e7c29073f0c64065aee3c6f600e4df6c0ea19c3751be07aa3ba6ae09fd3b502e01aad2229542cea8889ae185ab55021047b3db06d23645890f2f86decf6778ebe75c00749652b758d56d5119b9a590edb0efabd91e724eff8672dfefd2da391f8449f60a00bc7f7d0f86637ac8309cef7e92ef48bc616a793dbdeec256638edfcda78cf4022c01b1308d29683fb730b4bc123ea0c1795edae7caadfefea9adaf557017dec2ef0b9d21aa2be868f1c0b9b6564f1d783b09a50a5d69b17271f0620e5f0b86da19290ac65cf3ada99096e338c0df008323990b40124a91f97df45386f942567897172d1464f4d35fb46575d1eea64b5b7674d77fc7ea9667b2a47b71451888f6d649fd052781a091139e71a7bb67992551afe56535e9c90b8522e2031295e4f06329731af0a453ffd8ed36162dffc4f007500f176a46abea4f0f775cbcf3c23adc7b172a822a97adc359de64ca83b72969e91dd955f34ae11157511a6333cf07e8e42ef5144da9b9dea7849b21182bf2b091146f3a757accaf2f8222b31347484e6e93e12b23f95b0f7b2c0be55e43462b80cc88a74d0c761dcd539c08c2bc10ba7d6929628f1a992e73bc9cda25efd63d1f545482679f4a19417698a2c07aa8e9f975118424e5b0926301937412e4a8e9c7fb42fd86d8e6b662b213819297706b384107036ba9dfd5552c2474f0cb955330c35700b4ef26a4c50308949f1ea559d61174edf21f0b0718adb5c8fa4980308cb7b980af1f6c49b26272ddfca8e7408fde209d8c6e89293f05473973d861e0dc7cb379645e252fc9bfc75f3c4374c9e209760f207585d4b8adde19bc8d3ab04a605a3d57a272cf1ca00fd4b3c3bef89c2a5b386906f60e57f4b5338c4fa1941ee635093d2082059b3076ed61c4095ed1201eb6d7b6ec2d1c8c800c7183139cc4d6acc94f4f81c143a2ce67b4c8ddee12bedc18711ad6d2c1daafc971424bf1d661d9aaa2a581dd15ee807c25000291e0336f1301216e45d9ba104f5d67940455ab0f9a155158d21b080d4e00dc065154d0cb1aaebccfba66295ee38dda5df719a2c57e93b8918e4e06ae81cd15844adc2bcea50c56e1aac9735d47c52c8471557ac121c339213f1e46886d9fc70fb89637d68167fc5c291f55e3d95e8f524840a9a42d092867162b4e43a8a8c7eb3a34d4563c60bbee32ad3d8f949271cfd566cd05ce67e4a603d27713572d31b3b569fcc7da62c43d87a442a1458b5a3351c2940a7b0132936a2ec98635ad525a7c21994944cf6ebcbcdb11c8b22ba7a9779b9e8da0aaaf710dc6e833f5392ee03454ab6a793552d72a29c2f337ce268104f477e3f7ce2d00554f50062e710d8a17825734ab8a36826a9189b286df9ecdcbb2859ae94556ccbc46b326188ae7a06d90a0c43391f05bfa46acb0b048e18a38b9b8fe8ac82a64ef20d6261311f5e371ba9dc3d3af7c123e7723829ad777c46cdff17710ff97a73bcc0c5aef2d712771efb8f6fce1aa14f012231d2ce3c1d776871acb64151a8e13e4e3c7177565177b93589be8ccf52c923b05df9e14a54bed6e649d23ad816b1eb7628aaef4ad14f9b9d3ef33b65d4d74116f9bffd8b2eb84163bf520922e129b1d65a6a9174a5fe908b7210473bc33df71be0673f0f6735b7420851617e86d52de9fdcd456fa77bf0832b6a700f6d1e8680bea783cd1ce5b9b6c6a996cdfd7bca918ff09b18dc2d8b2613a082a5643e1fbd6163e5b3b9ab40a791505e9a48adb8867056ef467ed812b3fe9ef67692ee91f3fccd7273a62a6d87c8b96ed706383520c146b9e5f9881174c00c2d38ff88d2bef392f355c94471ec50dfe28c434de859327a9ea4c83461930eda775534a2da0aa6c8f362fdc1fa5b2f787a094b439f7308aa0b3d66339cad12b8d5850b4d67effe3f864ad81b2f7a4662520764f167eed725bf69307154505a9b2c7cff9e3ed94522ad51f2582f0cda523355448bd8fb0f47f30dd0df6fe4034e77690ce83b275f7ef72cd9863c829ac4a68e91078d42d7cbf45e91379f315a9de42e899d76408a023d22913c93655acf698a6a57bf7e842548eb0e7d19fa75eb064d2908ca6ab8bde14b2a64bd748585a0852e52d6e5caff02d557a5b8f11296f52067f6671191c293649b25e74483394cb8dd7b560e1ecb50bc926a462b57b5a9b69f970cb7227f7b480b2657ebd9f922bfccea68218f09744ffced9dc5d6e02797b5c3a6d8b6f304550eeb37cdd00baf23d6ed1067cc3fb13967c27eba383a87d55ac6af9e56f3ef5e313e5f3403e257cd94223882d7e645f17739909a40283e51a0dd51c1ea93fca96bdb84081abec4d7bbbd72f03614b5a71781ed876029ab07a6d74974498776ab9ef8e325f7f91433ebefa6bae7605ba1f8dbc7c14f248797245d7baaa40ca8bbdf8e25d4921e814abb2e19304c80cfdb3d35ccf69e863a0a46f4c64578600c1bea2349e20923de9165cf2d372d8eab6676bba3d2454ba3a3e2b782fb8c1e5bd6cca5c7e231e68c13d379c9b18d3b6987c9522bceb35dac42c81788e4387b1bff77444809c275f4db900aeb297d4a8b16732ea4f5629e44f7cdbcea50e77dfe5fe1282778196c4b62a0cb11ace66de7f1eb1308189d91a7fba54ac33240e27ea0d87b70354f9bdecb9d39face85c11faeebfe306c3d6d86b79c6e1ebe3e3f42e1b04981a9664979164fcdd022909dcf696acf35b31c4519198f4e7917c15986b6d927da77d49145a2dd288e827f88d30fec824d422e4e9488d3d4c02661b19ac192fc1583508a232ee9923be68d2aa2ed081f21c6dc1f781b39bfe10abe650bfe53cf1563f43d770db0b4b3079a40d251b2b60b1561f9144a1ee82f63dc22976312db6806ae78b23b9f5e45031f2a0535520ed61001f6d72e3f56a4609f6b0600e37988858ca60baf5d04de8c92b046808ace57f629167d7e92dda82c1119c34a8b0f646248efa42addc86f2a2fc3950dc57d1ffaae4c5d528902158c9b877a4440f4aeee4d1910848847de10401d2b8303c20340cde53a6f516e98e5b742fe1d704b2fcc382100cc3fc9c5036cccc621d5428677c7670de632596d84a69399d0f23f46029b5bce7a4b6b756cd7d1a2487e1fea5152ff257018c7ebf54d9ee47bb71353fe88f54a29cc0c02259a0c6c7f9772b8db16af897ac4cdc4e7b611d0d63c880e1157887160700c2b04ea818664d9c9a359d8618d1066b26cee358057be26635419fb037ae62bb3bbae7efba0e9b62e8c63c2c0643e65b59c4219767eacb4bf33dae0e7bdc279430c62d4f0566d77b393b52ca1db7914d8d3b37263ae8fef221f5da662b17d89d06ffbc882e50d6cb4d9a0f35cf614c63e2a87b33739729dc514ae967783d37f5c29e2ff814308d094fd378bafdf900708b8d6f9701b6bb85f55517af1fb68fb9203b0cfd1473a6206f3b0b2a5cb2a2ef0a3ef616f66b772847f35124e474aeea78b720511fc97309cf97a05f9fdd1fc8086e57d747ed1f023f7b18c9c6138b701add369628746a109e42e765ccc1b9207c4e664ea5f9c39dbc5fedee8e12f10b0ef800e31cbc38f3bfaee211c1f1f34a1e54b37684102c5a6022ebc045f0c3330b026c301000000000000000070f1add9b77183989da494eca42925408394524a29259960310034d77e471b10349e10e9c40aa70c6b0c1f0c8d3541b69ad0208349dfa9add3ee34e2511a63308a5df709717297652f051a62308ba9ba133a8fb24f2a1a61309f4ee9d7e45c0f553118ccd529e9d264e28f591e4bb62e697cc134fb245526c493c9f1545e308531b326ca93f283d3a3c7491d34ba607af7e495334105d9f1a1c105d3e9b86e7237df82c9c9f1644b28a5828945430b26f5a871a91ef6ee940573ca397d9c681e972e8f05f3376934f46287e91c5dc1e47359b9c7c37cf3b582792dcf478a3dd94bd72a98346133bc77b54f788e0a0697db278fa92ff9b9a660ca964c3e6154a5640bcb460b405034a4609e91b3f29e4451309f944f175c2f4f9a0e0553df8a296b0b4dd61a9f607e3dedd959779bbc9e130ca3f4c47bed523cb626982a6556f27495e3932f1aa0c10493acb49de76769da231a4b3086e75b9813e20927751a4a30a9e9aeaf858a574f9246128c79e99c384facb4a29a20c138e364924a61a74fd2ff08065d9a3423d64345d3311a46307b999ccae6647fb118e677f2793aa1164f9c1231cc647f722693d28b762695255a00198649e7937aaeadcbd467b2c23079268f7a3f1de749f36098e4b2099583ce911ded92ad0282fd020830ae9c46b442a8dce6fe8b4f07df50353d7672f185a972d58608d9d01f7d2f0c4e1095467430d7277c78612685a5285f1ef3efe65d98e4c6eb899e4a79e75517e6dd9799f1732798281766959793f2e44ae72fe2c238b7a2de3341bc2ebd5b989d9c33b93b6fa93e1db3854e50a1945e7eeb662d00a985b92b657c1627b4306893b217e3893d6a3f0e6e16a507882c4062b1134713442fc7f5e84ab67ef03ab841c6292b45c6d1713d78fc38ed031058185b54d7d68dbd87920ece0ff4c131818d316cfce007193acc0e14d818c346da8183043fe8f165a8c0c61836be8c1ce70536c6b0f18327818d316cf4f862328e8e3b2b80bcc270c1d533a97abe556d3380b8c2e04df09c74b474f2d9a515a64e132a3f8827c9c78e05105618ffe2494fd2b99c4cfecf2a36d318b32f4d2d6b11d527ff93d49d4a418d6c630c1e931da7071055189c6049bbf3da1694c006482accf2a4fca430d304d327ba716508105498535f8474e2f8896c924e6126b9e774b2b549eb51640a930a6a52bbce4f3d132f85419fecee1a5a7edc440a93501ed749b7f67ec64b001985e14fe54f2a9e83f44f021185b15367824ecfea70a29350982fd5e3c34d3c5708d57c3c0828cc6482cb67dfc7bf3cdd27cc44f179628db2f849c74848d210403c61b04d2779aab860aad4096395b6fb89c88c561227ccee49692f916a429ad0260c6b9d82d60c2727711e4d18bf456cff5975d2e45c268c9d5d3f2c4d6891fbc1845974ce8ea2e63b3c9914328e0988007209d36e7b9235624bd49cd8e900b184396c7ae794dc94021b8054c2a493ee4cce04d3fa7f11258c7eed57d2643c09f3656cff7b75d2419b92307d34cb297e5f90bf311249116a497c5a1cd38771fa6041eb9e600637120824cc048fdf7e176384ae8f30a587133aea879f6fe808e3577db69edf9df8ca8d2c328e096c03208d2894eb8855d414195102b2085398269efa48b37bf74114618a6d7289d5487dd23e49844194d40ecb793394183b008208736bc899d5a628d936c821cc6eb6fb045df23df73408104318dfc996e542866c4b9e3a8014c23c6be27ae5b3cc7c9010dbd7aba59453de92d6cd7db8db384b15cbf4255bbd3ddec725c8208c7219263fa6a229a509578ff7711700118429592e77d27d8cb3f50361f212f39d17ac54680908a33e99fcf269d27d3fb505903f982b3cb756d60bd274740c1bc607103f183c799347074dfaa074d2460b4a604307884709120d207d3086cee9a04e7d5477e68331d763e535db4a4b0eb20763284d0af2bd09ebc19c449eccf07c4263a44c00c98349dc6d67f6f37be3c68339d63529c4e2d92713dfc1942be5beda7527857e763067cfc1ccc99ff66a4465d4c14ccc0c55a53fe598b3d1c17c9e4d9899d3b5ce379b003207d3ed87735289ef4e42a86477e560d0fd334b3a9f6772284bb67477f02c7802481cccf76492e851d58fff0d1fa7c42afdfbb8b2d1021070808c63825c103824d39c9c84e9d9e5ba07206f281037d4016903f2ca4c6962c52ef14948387df80966706307081bcc332a9cdca39dfcb9e335186e9bbc953bf22ec906a206b4f6c5f7a4476557b29d03240d206830085519f1f9c4381d4f903398ad2e45e6cc7cb72a1118b3108098c1e8840b2a67b5139b9ca22a903298b7c9d1ba4922ce629f8090c138f2a9aa83d23f221609c8180cc2532679e627bb64ba92ad1dbc0f3198d308b13ca33cb64754b2b5e3d78084c1f41594b4d204a5d79d976cede0918307ba010693cebdd4d8aeb4bf720c902f342b7263b992975b69eae99c820a3ae5d393f1831f3774f04fc6e200f182e943eb7f774a397b0148178c4f6e97ffa45268513a38d8c12307ab00840b66e29b677230b34c4eb3b760ae2aa9223e28e5a3c4662e00d182416772fa3bf529a527df593056d2fb149f42bc2d8a3a41138060c19c536fe877abf7ee5bb27505a3e55bca3649c5f8692400b182d975cf49f3e4f9d3ba4ab67cf05702902a1409154cb133b9b44cd59feec68e837c4cc12017d7928b26bae8abca46002205b35ad21dbe9f459354ba648b6b0565e8b0f18115d8800248140c5af4663599f8ef51ab92adfa64060205a38a25a9a1968293eb04f2047367b5519aa479b576910188138cbbb2b7dabfe77b3190269889e99deffde365153513ccb9a5647e9aba151986016409c651eb27c99b85fbfd1b0c204a30aa76aceacfe9149b648224c194e4d65d49eb79bd27fb06102418fff4276522dbc692264730d7c5d0316647e87c2aea06102398624eacd59d5dfc275a0cb38e1384a97b79c95889610a79a29fb8ebe96b2fc330d5ede512316a9af0248561b02e4d4b7d6d1e1707c36cf9cf5cb49d4e4194c03055551ecb2777edeeacfe8531448fca648fbd78f12fd92aa37f80c65801090f7abc0f03c30c5f9809a327a4953c4dd092cf2231a317668234119e5b4ef85c7e7263070a5c80021b637c8084a40c318317a64e5ac73271f5892df2714aaa1e8920593063170627599fa6952a1dd3a4646b478f32bc5f304317eb09d39694c6a70bddcd61462eccd9f57977d5093a97c3770b33703199ecf983145139f792ad1fe8c9f0b143042424242476258037ccb885c94b35a97744a52ef1ff383fca3081eee7701c38d851c6845d30c316a6b2266852d507155fb47c98510b5a98728e0beff4514fcec4c38c5918af65f45fbcf49d54b2309ac9788f1f550a6261f6d1bbea23fe2d952702113c0f70fc3838f8139090f82021f901525898e293f579fc3df3925f6190b91bff5bf1f163eb0a8395f5e67b274fe0b7ad68ecdb5d2e8779685bdda76be78f16ff1c74a85498c10a33314c7e90e939eb21afc27ce1e420374f9e9592b161862a8c153ae69e94b29256a930553d31746f6b6e57c8460b4070e3060909090909c98d6ac50c5418aeda82acaf0fd1e2e41426b591317b499beee4f5196698c2e84ef03cafe404273bd91ec38c52985dc7ebc27acb697fb003073858404232c1d1e35f5086bea0078fc98d1ce81309490144318314e67c82dcbb888f61395ab275e6c3078684440c3346614ed993691b7db195491285f97b2ed738397d101763166684c2ecfba1e6f24ae9c9def95ef0ed748fdb0b333e615627dc292775be7ca76ae120830364e810018e1b64f8b8f68441fafee79a4ef5a559270aa184e7fcc96238612fd9ea81e346d572c21c6b37efdc3fb8fea9644b6d13463595c48a134e9354122ad9f281836db4000424243e74e0e3fcd0818fd302eb13cce006116668c2742a96cd93946e52524ac38c4c9857a459f44ab7956529590f086106260c4a7e7ab18f159f4f26b4987109a488586a51aff2799223c719232d13332c61b82b155d51b9640b7b7056208c1995286306254c7bafe86e4afebbd481a3c407090f1eede051f0d10e14ecf880fa9831093edb74a86e62079f37478e83f8471926d8f181641f64f050c10c491845ddc9a153e95f4b4191b05390a7e5e4b92a3d06332061eef0eb393ee58da5f01a663cc2a0c39a8aa254ab9778673822cd92ed7aefca1536c566467f6c92c5f5bbdc6046238c22cc2c8cb0fa9f4b419bc108c325a19e2e7aeb724abd0748483e95198b306592f0f417545a30f9600109498ff741424242728219dce8314311e6ec5af523f5fad4134b84b952bb982a7dd6600622ac4f65b56651fd3b842947bb9b7e0b0f3559c9161947c78d1f387ce0e041f7e0f103870f1c2f589b610883b036213a4d6aa798e5ce2884c1d7e2499644e9bd77e0409d4108838a5ff6de70bbf9b88c41a4bdc49426978f5410e651eaf19f4c26d5980eddc0c18eabfc643923103d9801881fccf8438e197e30977636416e5b699f6e461fcce460f719fa9fa0bd590766f021cbe4b4132a6e49873471ccd88322f684ddbcf8292f93197a48ab93d4ad4634b1732501d979fc91f979f154468eef41c6b2881a4c414b3e9cbc2959172fd9bad183dfa1d50191349874892af5e99eeca4f496fc3829c0921f870cfcc10f431134989e245ec3342d276da792ad1e7fc52c720683bd8e77eab2d3ddd11044cc60d452ed57ede37b4154065389cc9ac907af4e960819b89272d5272c591d3d787c40470f1e93de31982ec74c91a7ba4e4a411131b456692e9b25b9d86dd1e4f2a7790775f249ba1b8884c198f541fe2fbdd89303834997f24c1016ade509639321f205d36892ce9a62623cfbf68251d4e7638eca4d50da2e98da2e133da54aa2d2850ba6f37c59b2655e3794b4cad882a9d31343c93515dd467c3910d182a9c9a49cf4a58f28ff30070f1e9e3944b260fe14f47ddead2609ed460f1e3776f4783e8108164caace6e9c14db73e9125307225730f6490fcd26ca6ef60112921205112b98ae89574d9e51bad2edc97b800722553007fd79e475975c7974e0e8f183abc70f1f2410a182a95ccb9ea476369a1cc231c1e10307978f1c387e948101912998ecb7a47e366d4ddc908a48c124223fac9ea072493776f0c0f138e8c1636257d6e8797049240aa673b29a302d35d77743c1a05410623bba966c74e3073d7e3014449e60105d7f55224c5f6dee04f35f3cab2cfa548c9136c194827d1edd442786bb26c20483f58d3bb1e3473a493986c8124cdb1f679c686fdbda2bc1946d2a3ef1f6f249b94e8249db7fbc52a66fd2c98920a19456595bb4b562d76cf97867bc3f696409d38a1cc19cc48a6b05d3d2d1f42246308d909f7a4d675bfb5e0c3349df747b5b3e9b9413c378ca4497388b114fb1611845996e3b99a8a1e1496118ee444bc4b32bba5c42826126c5be27a8b8f15141280418a6ebbcdc571e47e964fdc2f869bfb227dd31efd417c6921bf5cddcfc2484bd309daa2708f11e3ef6897961dab514cca2c8932fa74276614efe95752cc5105dec4ed2eb14952705427261def1b7ec9969613b5cb2d5757cf8c0f163c7d1c13ff8f1e37b90a165fca0c79d6006373610820b63b8e7e9f3bb54f9df905b70b1bed36e3bede56d8b37e6a47ab5854b6b610aa1ed65d343fb6aa585d67276179e1e2bda53d57aa768e56bc82c4c22d3b36979d2e7266f9a16021292bd513e32441606b9a0c9eaad7d253cae8d8541554f6c7d3d58aff53db00c326061de7873f560eacea458b2b55606193b44e0a34719639090a04ffd0a6545478d5f3a93a58f105754b1ec53dbeee45a6de3ca095ab55ebd53ae1516c20ae39ffb2879528884846f94adc2e0966fc55abc9c5c5955186e9de8aea7679af1242415e69195e4f678d272390a861054984a6efb789a9c90a2e715424e6126b555c51ab3dc5f9e348541ec63ec63cb74cc14520a33514468b3d8ee52151b238414988d6ab668e5aa8eed4cddcfea16d493924a71c8280cf7a4b8a52e3b8f685bb29584105118e589df1a9e3429c447570921a130a84c0c1d3ce85d7652261d8510509849f1835b3015b65fc61e8684e4f4f8904f540af1c4579ed4e2e77afeb48474c2a49eab3653b493478913c6b827aafd49ddcadd6fc2a4b5a1cc83f6423461caf99d2b564a39ed551fd8818263448464c2a0c3c39a10cf1f634f0c440826cc49ddc9e7535e415c5dea12e60f71523b51448592d5e8106209834e276852dadb154d562b616ebda07395d0e4fc9ad5208412e6caee217413e4843653c9d64e216412c851bf5df34e522a56b2d583d323d5102209e3c977f0d893a5ba5bc956614248244c761d1e3e4af7894dd8c618f6881048189e4c7282588bfc1e613891259ab4f53bc2ecf21fd37487d907f10e23a411a6739195940ec25e8430c2d85bbade92baf527935f84593f29599f52543971fbff809a220c967f4d7cec574fa726c268d5613c67a96df920c21cf22dc9afbb1cc2fc9ecdcde74a2c44133584e172104ae333697c3da34148214c39c5d3e424bbecac574218efae8429199572c93c6410e64fb9c572b6ca0eb328224410a670a32ea51e7dd25ab34098b2dd563ece0795e40710468f26c4eadf8fdcbc2d86903f184e8df6b1707a3f18de49caa277e92f25bd3e7021840f5908d9036321440fa61c476a933249cd7930a9585e9749eae59d38976c7921040f7f3839faa69e7c884b36ef606a62d9cffe958dc76807f3ea65cb9f9e9cd904ab0a21753058de6e3595948a6b69081d4c4ebed46499cbe4042173307726e7aae09509aa7a2ac3478920440e66923d497ef47af80e6e1d42e2100207834a2a3541c7940b0e216f30868ae6042dea74684fdfa80244881b4c67418bd0d74eb214d236189407215d2f09fd2793668341289d27d49eec93cb3598ce42e628a5b34a8f6a7ee428039f10a206f3cd5693df2f8ad2416930e7a9af0c57bd98734a5a820841035bd1b5aede2a77c5aa7b26937bfea46b7a8219dcf8117206636832c9928518b91ceb159090a01e1c6206e37e95dec88535153fa40c069dee449c4a41e8f7a4103298f4ea9c5879daf967cd40c818f89c7226093b4f27373931e8a44cb40b6ba74eae671012067390e2fea97a5561dc103098da62e71c3ba4e5930d18215f30134b3d59de2cc5c9db7bc120b249417a6c92ba14a72e989d64329c942fecc732594042c2cf830ae1822953d3a367a78c11fd168ca6e348efcb54bdf6b4602ed32d2ae54543b2600a3a54b4acbc5a9f3f3a405f32462542b060b0acd19dfa6359ce5f4d845cc1646f63b17ee5eaa45608a982e1fcfe6c54bcb24f7688102a9846f5938ab7d1e420b639844cc124642b852d71e1ea49a721440a86279eeea0e27dd2c447f11012057307f53442134dfcc3bf2a9b8440c1245b4d45c83427956ac288902798fab28715996a424d8a978810271873bbbcb53c9490e926ab3f8434c1d4d10942e7fcba9cf7434208138c5a3ac890cf387175b131c6183638807d085982372a8b9097523221f6018f0e9090a4f7418812702db12cb1309a726ea96405293a4b2dc75b1f58810ddb4448120ca6df4bb489d8934a1324988976a59e4f0e224b4f2147305dcc284dce97b53ba71023187c4c5b7c828c69dbb11806ed6b225e848c18a6d104974f6acacb3a350c639b92a5a2631346fb424318c6d324b724bec2bb973c18a6aa4e4f34d5b0936603c3a4214ce6f9458dd1f51706f57e266fb52f8cf296e213eb777d3ded8541a80d2fb5781ed493e485a9479a7a7c9457b9e02ecc66a2ee3df7a67a902e8cd72de79ec358a959381726f3e4f9dd3d783cd56d4f30831b3b68e0c2b8276efd4eb08fab148d5b184e56d4cb59d5c9227feee091630c1292dfc123075a5b1847765ba6d8ec681dd1a885413f395e58ce492fc642831666cb2d9a5495639efec7e3a0c76981d3988599609a3c6b52ddbb453872f8485b1fa0210b2fc9cbe93341ce637e3462c1899ccdcca54a4bde96a4010b83b87688cc274a061aaf303d99d47add39676be9b8c2f8c41dd924717ab709762bcce4718f27df89ce2469b2c23ca746a9707f25b3c530d0588539ffe2c8b4a05785e9bdb3bcfaeb933e8b68a4c29c2b588a75917591312acc418cbe779fd2dbef4f61eaaef4ccf190b35f328571bfa45956ab9674520ab3c810573abc79b238290cb2e67bce6647dd671426d99da5cd459e26499128ccd5a14ed2153f74527b280cf3963d8fc5a9742a28cc95a69c742262f72efc0953eccfc90952c4fd42ea0993d615d3a2745ff85276c2b8dd8427673533f9f03861d2f9d9b68997740a966dc2a477fb4a6587f5f5451346f7f94af11562742e65c2244c48bf6e6278de91d7083430613af9546ff29a65d0b884252a716301342831091a928804046840e2110fa0e1881b11a0d10846dca0008d452822110ca081881b0da0718805d03044036814e200340891001a8340000d41d4a01188ea09c2f3c2e69678c2340061cef126cee8a8fdc1709a2cd292fecc7272ca0fe6fc356b3376a6917174dc0b68f4c1a09ddeb6be546e27135f3e98462b9fd619d33d279392ad626fa0b10793d4f6f0c4511ab2a387a2910773b252b225f27a2c8f98061a7848e3333d39f1cadc42d0b883d93d85ce3629f7c59d255b3f3839520ef6a19da06107a39886de77f83ef9f992ad54038d3a989468dd09edde23322fd9d235d0a08341a87dd27d55aeceb92ed9ba44038d39a493688eb01466591e3c4a6ed818253f70c0c0478e1c1cb031860d1b63d818c3868d316cd86841096c182c9534e4607e93a9a7da33898359c384abafc7e160269937b1c42fa4bd7ddeb0d68cc8b658dac68a5bed7be714c693e54ce740c30de626a875588aff639f6c83e9d356ce53f5d9c3e768b0c1e844db53ffb7b74ef0d660329db57982b65141466a30eda755cc782f11761acc972fcb92ba8706537c9213ffac63a8858d8cdb2bd0388341ebe6e86951972316fc781f26061a663093728a53323c645955051a6530ad5912356c30f58e88d24dbae4d95983312f9b4e41ad0633319fd47649cf9eb4d360521d4efc83c8a5cf6830c81b79619aa053c7f60c66733d799faac369926630a97c26978a8965307f36b13a5c93a36c886430cf795fd6cefeef87633013379ea0ef89180cb6fbbef949cf851f0653936259de86275513184c269d60f3e4d199f8415f307a10236d4bc80ba6b8d6905e3968c289ba600efbaf3ebb9c3331c405e3a8f3d1232f7b50415b307a48d96e8234ed848f164c4a6aa513eb2a15ea2c98c99968f6a52a46648d05d35c6fc5318fa6595fc118a34498e50f321eb682e1cb9cf0c4b0dc1d73158c3aafe56a3115cca67da4090b6dc1894ec134be4dcee9f94e45550ac6b7115561deadbc8c82e172e5a8fb974c7a775030eae52667856c8f91dd130c16544cdfafce0926cba5c46686267faa5c134c2a4e294f376f72376182d147de935a34d933b927c012cc3bba545c8ff9711301946096b9a02ba29a286a2240128c6bf1f3a1542c27c809800483722fe52674ccf79900473068ae58d27572ea94096004e3c907bda5ccd4655c0c73d0bbeacbe498215b6298740c9317a43669f71c86e1443dd68f10a7ea3461184f39995827cb60986eefb4e8933239f5070cd35d677210264296a57e618aa52d478f71ad4a992fcc04153d6cdf7e8eb9582f8c224c98324b73e293092f0cb716aff267b20ba3cf6c9bccb0f015d285595d7d8493827261be2c169aa0aeb24d8e0b432b7e169ffb166672f4592b9b6d61ae1cfe5a39cb5a98c9413edbc5da63678b16c690fb77e2a4b33007a195823539ba8f280be3c5bf6c1beac40559c5c2a04b597e15a1c9d74e60617c11ab6dc2084dfc15667527a83539cdbac55d610e959ffbeb694cdc0ac39fce113a0815646b5698c74ccaf599133bceab30cf38d94a98d00ca9c294d28b3419b9105953613c15ea579ba0499e4685a93b9758d19e73268ba7308fb23c2ae9fa96ec4c610aba3c569cda26eb560aa3766b5dfc6ca83c29ccf6734fcccf0d1dab511847e97ad8cbbb6f52ca88284c41757b34d59ff259ca48288c16b2730a427cac68662628cce6de39a8b327d9a77c667ec24cccd2b538625bdf2d9888114f98f37fb8263a5994e93cb23e8c74c26499e439bd33c177ffc57a015e61f1e0f630c209e397e729e1f974b01aad726413a626eae9544243a8d864c263b243048418d18461bdc4af6967b2e878a222463261b44c0c1bf144d13022463061f2b0f57c5aea4b182cb8d545ffacced78e58c29c6a4be99adf327d6ba4120639d22f6baddfa59c8be07694c138420953f65a3f3209b3a98d07a5e74b8f76333022094d075961f33339ef91c84c43ce4be52d9c5acbe5743a46a8caf1cd532390309d4a6d2145a794e6a547187e7427d90bf788234cf1929e68a57c72aee848238c622657edc9fe3a278d30c228d6644b159b9473feecc8228ce9c1e694b7d8fd058d28c2a43c4c29fdd604cb151d4984d14ec6cffcee49c1671e4184a95493d35875955c267f08d3c7139b143fed549769c410e6f350b6a5561b7a21cc39bda8a0779d20ca9d10c27826bbd7afc9599496066126cc255d1a5af2ab6e41182e857137277fda883a1208736782980df9d1d37d228311409c296fe9dd4c2ea5ec1427c52a51faa2936487f80f8e09d1393f2969c40f861ff55ae1c2f5c62a288cf4c14cf8d0e749a45ccff57c307faed8d31d915e251cd983699efcc19c54e99dbb2bd9d283a9e3cc87f1916a9f5ec9165e1eccd9b6ab542727255b3fb8819582113c9847debfb43e93946656b2a543ef60106ee9f2d493ea31c1b6834986565fc8d7533deb60cc9db54c124ac8caa12b4507535f68d2653b9d428f58b2d573309332e1c967df27db49a564eb0c478f1f2807638d762d296fb560240e86ef0ed6ba1d4f66ec3ee0600cdbcb4dde9e078f1ef88191371854ca9cce139f7ce1623018718359c4af597ace78ac26dc063341a89cf36ac283fc2036989b9c72ca534d1cadb0acc1a03e888e3db99bf2e44ab670e8d0c103b10b46d4608a6e2fb29fe245dbb51a4983c92efe833db9c3823dcee74009821134183e353b589c4b9a24e5108c9c4108236650148c9461840c236360d6b6626ab89dd5775b0a553a767ef266b89846c4709fbc15b15cf115c14818cc417e59762cd10a4a5eb2c523180183f9d2629a967ea2fc9af60553b60edad3743ec4a5255ba54a7c9c12138c78c1a4c9b6dbf9c29ed5872cc8910307094848ba70e300235c30295b391de4789f0acb1626c5cb9ed32d590e8c68412df574d1708f8f1bb9ac7be2ab96f94816cc5af245debd9ba8961ec182c944c9eabe353946c847ae60de0a15fdf24d255b3e383a9a042356305d29f11fe34f960d6dc9fab8f1c3078f921baf86831d387080031e3996078e1b64f8c813cce08619a98241877082080b2afdfe3d18a182f18356097dd9a4654b19998249eae776d593edc9b33826132c117ce0032437ac0b2352305ab26d6295767b8bad1346a290089d573ec90f1a144cea828a9f9d44479c37f204c3c878522755a22ecd1c718229057d554f2e193a8ece4813d40eabb3ca1ddf61a76f6485911d6919618229f6857593ad9cf553234b30bbe5bedced152efe52c688128c23fa6df14f990af3a88f9124189ea0b5cb091e6aedbb1124987348fbb6e0a9938ad9c811cca56e5d6ff51e4c87478c606e1597d3769fdaec122986d93fafeb534f7be98810c35cd16adfabde4b8e900f3e042424c9075f8244866150960956e724d3237213867954fc85caa79bf89548304c154334c9c2bff3e80a11448061dc79d7badc9d9ab7f68b12f185298f3ff16ee4b346e588f4c2acd64e76825cbd8e7bbc305cfd5f9349aa626da422bbf081882ecc9626dae6a154aaaa447291db8959b6c865d75ced2cc2d33a447061d2398f7eeeaa9cac22b7308f10e2895ea6f5ee49f1c187a0c7f78047096c616eedd1a5a216d43cb15a189edc72af5d56729468610eeba432671e1f72b4b330a51d2768d1848fdbbb6f94882c4c4a54136409f924d10fc7c2182af5d8ec8e1a6fc29c3e7cb0054460614a999c899a99fd7165dd1bd503915798744d4ddc9ad05dd1bbc2b0eef23ada752bcc5967ed82f82ac20a5365725221cf53bab4a2c82a520b96292a96b32c65dd9fe8e7669d4e08e5231155183d558abd8c4f62f353b2c5031ce93cd06cd5980a839bbe5db1fb59ed302a4c3d2a45c6ac6897d44e61d6acf40d9dd7fca445c414061db67c3f257bdddd4b61ae74cbb29eddcf4f2385c15b2dbc8ffabd744d4661b4fdd2b14b7fa6a78f28cc9d72d2395a3cd3d5c9501c76a529727229a7c51295eb93a6d4ae9f1428cc9d391af2d42a8793aa2f887cc278e2b14eca41fc53d09290f08d3a4f985f437df22d39aaa3df097375ac5f102785f7707102b335ef72ab7039f3e73ac6a4c8cbcc9b30a9ebd9fb92253e054dc8d081838484070e114d5c260e1345227209e39a05f9fcd70465991c4b189b78fa2e869c5135b512a630d22a961a35426a8512a61296fed209bafaf103870ec39330694b6ae3b33e69de3bfe442461ca615b7459f49dbd924824cc04fdd1fe891aca9f4c46200209e3c9b5931e4daf28b98b3c82add39ab1b65699addaea4f995571334d529d8a38c26c1e3f6baaffa4d14f6efc60c7798148234c622bd49334711e4e2fc208636982dcb0209fd5275b8439de9d1cbef2d54d452ba20883ca3e273ff1e48292221f7c49f5104984b153144d4ebefb41c5ae64ab5806441061ca97fe9236e1bdb75f9f5f0109493adf83840487c821ccff64a2e9d34d2a0fdae1f1e306193874e0a30da1c5baf3fa9051abb6b251214a9c74d727c754b235e17163070f1c2b788423070bda062285309cb8b4d19976a32384295c26aae9e8af27b5bf83878f11bcc8204cb1ef4d8912322b4118948ee8f58cab11627b1d1c1da552241086cd5aee70b520ef76d173d0f93aefd5ecbf0284a9e24941554e1764fb3203913f18ec9ea42ea5d845fc60d47631f92453271e63681f4c840f26b207935f3ab9c9e904c140440f2b10c98339e7fad2fad149497e13113c98473c298e13744a496dbb83593cf54b8afed167a21eefa3c7490109c927113b982cafe67f08a533e9553688d4c1bc5fa53dc9454b5f4e7af0f8800e78f028811a44e8601cd1179f933461f22d14fc0e14e4096670a30522733069a2e9ee79ea723085d2fb9ce4756a92f2c70e1c3c52e081921f386020120783a52065e4c935417317818341073b3d2a9c5744de60dc8a22264567df27fb6e30c869ffedeeecaaff44da6070353516f4dee90f8d0d29fb20162b46a8eefdc78f43061044d6608a0313c052812e15402b06813a0995f67f0940a09014939403d00f1f65f40009c00fbc023502e0a3c78f60c0070e324e8d1f0020000130400b00e0c38793cc10c0f97112094800ce8f9378f4200901c0001430d2e36fe8009d1f3e6edc30000002808010a3890a2e6b16d5c9a479802395813f76e0c0408d4084d40004193afe60fa68ed972f9a05970c1dc80fe634c249792d75cea4acf5c1a4ec89a6674e1ef0d0a1e375acd6e08369439a9def8cd249aae375648d3d1c8e1f3cc091cab87163a4861e8cb35a7ff7e2a5f2492a59b62ac34ac7f7b8c1031ce946ea52418d3cfc800c8487911a773027b572795234b5718e3274dc056ad861a4461d7ebc0f1daf83460d3a98c69f4c7832713c07777b94f8c83107934a2da2c4cb93f41c8e1f394a7c9c1b373a7fecc0713e76e0f8b135e470387ee42843c78d1b2235e26018ad8ceffdbc4f52b21a709838b289dbef961a6f30778cdffd190f423c29df1b55841a6e30895813f2bd435a2aada350a30d46ad13f2e4a0fcce643b35d860ea3f7de288275ab6fb7a0d69ba7b5251a347ae8672d5659d55dacad89235a57e44e86f870635d26094cfe4347b9a24d487d20bd0604cff1816f7f2ff934f673085373337714eee93570d339842ac89a6f239c982eb653068828bee1dcd3af7680d3298abab72b217b313bed51883f1744e77a95454a5e4d50b6a88018fa246e549a3b1f7a81186529cdb55f40a76d1bac3cdc9d504bd4fe29ff0a406180c7afa53708232f9e95f2ed4f882b12c3f5eec9a93aeb43276f0a78c1d98e205e3760a35db31d652b31a5d30ba7dae20f497935c672e98843639889986121f69c996aec00835b660eaebeab85eb916d6cb2264cbb65c230b6632298f562b594145786a60c11cf652eece3a9d72d27dd4b88241278b93a6c3c4c3044ca861055326bdc57ce2565e12f601a146154c29a9b94c0e55499c675430759011ed3ab6b36941428d2998e34fd9a5a56e6ff1310835a460b47df2ce8d9347c95321428d28982b973c39dab3146a40c114f52c9d99d5e84bd71c6a3cc154397da9d1ebc4e850c309c670729375e64e5fd651efa146134c993457a73c9f30c1f4f9ad9af0969390d4a1c6124ca5efc9724d98519d82420d2518b4849993b4a5f00a261212ab1a49305fd6b69445ba3b70b80e1ec84a65500309e5b79f080da1c6118ca62642563a530d23a871d6ea31dee159a925f4b357109fc94ece4f7214a100c530f68cf020d28393fed2c4306727cce55bdf6ed86841096c8c41421284020cc3d8e19c7c9fc9e97bb4957ef8b841868f3c53006198e236f9cd4935aa43ce050886b9637a0acf7d0b000ce337d13d7bccbc00bf3028276596872d27a5130be00b7350651523eef39d30f5c2143f7dffe73d93c9a6f1c21ce32e4bbfa8e650805d9853ce1746978689eebc2e0c1bbacf5c9852f49f115ff117d652005c1874ea2443e54aa19a74dec2a0cad4750c15ab6486b630857672ac54717255c5c628402d8c26477bca7a8a7e926861263fd9d3ceb79c93cd340b730a6df966be76d4230ba35a9a76b2c6dfcf680588056f17e3ad6dc32b6c6ba5ea27a7578e25536a016061509924c2cdc91df7fe2b4c59663d67c7a7adef0a637c2a79293e9e8ea25618a47dce27f8796e86586192e5bd4d3e7d9e2e5f85995c41e9c993dd8f8f2a4c99b47527e7c396dba9307b5d56ceda57ee352a4c332a8626e65fa5bfa7406c9b7c2ae86a0a535898111dc454aa6829cce13ec72feb2c97901406f1eb7815c7bea4381985492855a3743d69adc94d4461784b2a9b9d6e120a734a7ab3db64b2c8cb4d4061d2f3eb049d1fc47b36f984719466b686d22c5d4d3c6110a2d35c2686c5cb4fbe13a63ca1c474554ba5fe3961182d117d6dda844198d69ecbf9d27790264c1bfab9ec7274f578264c413e418b9eae1c9f63c2b4667275e204fd417b0993c5d2de9e3217ebb58439550a5d9e64894ebaaf84312cc8ab38c1f3c9ee2961b20eb21f463ab1fff293307be98c52afcb2937918451e382c9f14ad1528a84d9ec094e8e16d6dc4d9030a578961c0b5234b1f408835a909b5ddbae8b3bc2e0a755ef924e234c1eeef2c80e23cb3d8c30933d87fd6815f54bbd08d368c9502adda408834e499ba86fb99f3925c22c23d6e266bb677f13220c4e72d14b9d3c84999897639c304d0c2b0d617ef2a58abe3072ffa542182c883db9c5c9dab14b843089103d55fb64b2782a0dc2a05475896af2677b4b128429abf43d69a2abf69340185493549a5ec87a9e00616ef12727bd2713d7467f30fdaf8fa54b7a2a457e307b93e4b2a9e75fd1fb607213d254e90e1f0c9ab4a36a72c3743f7b30ff293562af5ef4e5e8c13ca28496b11444479c0793d0b6266e8407f39e50a7562a554cbe8339c5d48ddc68b5bd76305fa5c86d49cb048fd6c1d8493c44ef988598743039f1efe594e5f3f01c4caa57611a1bdf15948371c684de12ffe184e26010eb356b7f1f840a0e06d9ee6b5f3a256db93718d4925e7f92d2f784dd601097733c1db43698edae9d9c4405b1c1a06add572ba8e659680d062db7922e4ed4607e52aed0de7a6d9a7c1acc379a307b26341875cbfcfa5a7b3ee46730953c157488bcee9cbb198c22ae1d9cf04d8ccdbd0ca6f0f05a51e33c93632783298b26c94c53edf7b18fc1b8bd9b9d9f5c9633591783e9842693b39aea61303ab94f4b7b26abe9d0c1607c758b6dd67ee236ff82495acc8be7eed795b917cca934419e26676cd6e55d30ba09511baa2ce5cf840b26717232b52ae515ff16cc2499af67b15254f0b5600aab5921e26be14e5930b65ab2b32f2768324158308c99ed5d1cf17e4157307fe9ca5dba154c3aa9e44e7ced2696a70ac66a152543849349a3a682e1e6c9d157bddf727a0a061517da39a8ab8d560a867bd3cf21b2b3f48d82419d1eddf952f98ea060366d2746e8e8134cf9ed69bfff4d987a9c602655b4a4f15d134c4167d8f513b4a713134c5d77eb95e5464d5b82e19b94c5e47abc5229259873ac676772932ec5249894ed25753a99133d24983249e52d0b2aeaae728e601ef3544f2e62339c14c008a6d22707fd9e2f8639f4ccdcdac891ef13c3ec99ecfb95acf3b7340cd356ac1d6fedd44d9030cc7655aa5b7430a542c13059a8509e491e3c38716098c9229621d3f244efbf308baaa43229fb3439677d6150523b7fbc1766f27607ddd17f63535e18543431c7c962fac5db85d1840cdb914e982f952e4ceaede5ce4939d99f0b53ddef5b9ca57161aa7732b9852929cfc96abe538a932d4c32942693c9ebc42d33d5c2bc4ebc247fbf82b2112db0ffae66614a293869b7e262998e2cccd7a4d036a74939b41d0b73967ad063173f5f121626dd4127bd1ce45798892e9adc1ee40a536f49ff0bc26ee65698ca934719d3fe996c312bccbe4d9e1df9c4f3ca641586cdd11e2d8e9600aa30d5c8de92e61f7e3a4b805498c64b45b75a2ef4bd044085e9747e137634e162de4b805364a265a96253183febc8fb2c4a59d8a53049bb7fabebfc1b2e294cdd39da58f2ac548fc26069a46abcebcd95a2308b1abda749ef7f2686c29ceec95d9f8969b20585499678c9b4fefcec13a69cd63f9f269e8fe53c6194f99c49dadb76c2f8e4b1168f1a270ce3224fa5890fa3acdd84c957ce4f9914d5a43561f610594e9296b3c39f0973b0ee74827d34bd13264c428fdcaccceda0a34b184bbce7d6ac2a95895bc2203627dc09563acd953037b1091a5bf952b69430e5eef49e5f6d9d4dc2ec253c2e7d0eb5eb48c2f89ddace833a12e65ef7552d592161d01d6adaa2d44567f511468bb26fe5c4babca93ac21c324293edaf520e3a6d84e964ff49e1bfbcc9396584b13a950a1dd43cdca78b30e91e6d7ba13a4f90a922cc2a4a4d9e6c3a29516922ccf1d3fe4be9cbaa4a1161d4f268ba3247e8b8e821bce42bc283aca8218ca67cdde747b55b9342987ca496878737fd3e214cf597ef4a8ddd8d3f085338fd653eeacd3b4810b59b081dbbe4098441ae53d0a5a53ac293803087987acb0915d772fec1e0273feb9977e9b7e907b3da134e7df8272d66f6c19c3f7a2594e55c9a60f2c14cecbc14af54ecbc61eec1f86b4df4f839e6f7a51e4c9a619d82d8cb0b5fe6c17cb2156c5645c67989073349f6e28b977730ce6d094f2ac413ae4b3b189ba084f86a29a5b4967530c50f39b91273f2694907f37a132e5798d06ff939986ed63f68fbeeb8931ccc6e4ad493135d3e131407635c972608bd155c84e060162bf11ca296d5446f30ed5e6a954a5f7a416e308ca753f2b4c5f34c4e1bccda6d3dda276ee2138cbca8744fa5425124108602a160200c0605d16f3300a3130820203c228cc6a20171ae91f30014000449382c4c362a22321a16160b0682812818088582a140180c060402a150181c12cbadac531b88692f60b7412702b5d3b3bf651b6948df1285a274497839b667a41517e85a268f2eef1fed614a13cec94c4608e29de38646d06b7a99e63bb41ab68d2dc0bda9d386838b05b68e9eaadbec4e94a67678042f1a6bbe1fab19daf0705c4a5b66e542c49e6da351e0f0f9c44796acba0cedca67a54200778689b86f9834e93d1bc285c133e99b729db4c73775b8213c9de859931e75d42e157097682e869b0818d9f9911fc52dfe22fc3b8a47183d30b0b8f8768e5c1f39d143c8c39bcae0032108f3c524d0eedbd0dd08547b9dd68de8cd82452237b02e1a4d0a70f03ae0636a3b5a7504cea3cc7a2feea0b87f366b862ad83997a94e24a3d32423a989a44220195f49dcba198d98695dce1c1d282a0f820ff283a24875e859af9ce5f2530b4d485420eeb1442d18c46aadc06d9b492568f221f9c8379960eedbb3369c00f7696b756650d4e9011773b103a9a5d48791dff0d0544f9b4b223c0e906cb245fa71cf8b63407c186838c609d2a6a14cb0b7228b1349dde4d8841ba2f1999aedb09a2ec05c382845ab63a8357a17ab3bc8feb52103986ecd34ed3e2d9d52c2cf0f374d10f187c1efa859f7d440d178c690779ebe5382f11601b8dd04529b227b43d640a90975aa1e62b7a20302019d5b25318bf7238d228b6e8030694917c0a4261f246358edd1de06e68660138aa1fe10cbb2d56f9fbca7d4d0a865e74a35956c54d8a715ea44db7b557a7a88fba059561cd3bbd3e97614d645492170f5b657e5fdafeb908989e063e64e52212d82fe8edeb6f815134a1fafc388fe6f5bc9bc52a24204ea99c9a892bbf4fce725230ab0e5e6f9cfbfb68d7b187fd328b18924fdf80ab9f28f2e1fbbfddc094b2fe2f04a40f2cadb1c2b1d23c5cec30ec0c1b115ec0d18d1139478384411359ebcf96429be95d26a3ded7e56e679f30ee1340442dd609c14d83454102861607a1645708e5fe73a2a980da26391373e940614070496d6d874fc1f08fc36e8fd8d5e4f443e045c0a1c6a7767cf058d11fcadf2ad09910f806ce2a95fe5087e6292b8c765cc05ee73b93ef1c51f30ef62aa1c75e46df55f68a5bcdaa9f1f2b1a5f5a8a8bf96fbbad88bc3eefa27fe6a9dff8ef3f79743f3fb9eb9d615b05958607eef30d6ab3b9419f821ef1030dd5b9d20d79224fb7dd4fb7f03cefda3a8f0f38260991a8b7e06e89a91a9217c4fefc8e93b7c4ed225bed027bc66160a27a6505edb81b1c1a4caa9d5ba36a94c92420aa5ae544b99aeca13e0c338df71ba8ccff521bf63538520c85df5bacecede91bf0578d87dd2ebe2b5fd68ff612f618f3b2014620704518d3a68407051c64aa3a42f4129a717e47a3d29c7b6ec03e0a7ff2347f228bd49611071a9f62d4de3f7fde47f112c7d06b01c516622b585507963d3ef71051639359853a39f4291a51722993fce68f4444890bb6ecf944cc5541949131b471075e09ef3d137cd488d94b88a5e2527776ceed3a9e38ddecdeaca101dffe3e056bc9217d4e20446dce0e88a184cad1d97d068070d797a7f154c3d22e0b7a50a4b69d9ddffe099b920c7edd6b94217f8e23a331b01b25fc03abac2c7dbd60b541ec19f611af5c1ad866bdd0b01f9eed125be48ef63559505e445d95c0f838b7f42788aa4cb216e5e7d989c4412fe2030d2ac626e8ac56c4f9acfdaf08dbd4235f38801df8617d27acd28e7d480e0bf549ca774b9492e9514c4950813a889a1a2beb4e710dde2d3dbe842d5983cf39e296a8b85c844962cb0b80421bd533a91d3ea144f8da993ba9268c58202a69939b0501ed09bcbc52d1d26c2ea911ba1d119a9452ca440e594fdc933cdd2642e99b8a813d9280237fe747014cfe405bd4eceeab148660680d16fdfb975a52ff8c5790ceb02ecb71162279e4b1fbf1c34b90587e4d06e7dbe64a8f5aea53d28c776634e4f07f95c5fa8ebfb04dd5ab391da26ba4b7124ef9289255a571f7d3c29c620ce95edda50321912459a248dc4f93cbd2acb746a55a48d2a3a8ff7653507425f9b6e9175b6fa54efa1d745c011e892ee6e9902d85e740cd62922f35635d222f0cbeb9984504a97ad5a93477bb3d6a04075fbde78d63e035cff9cbc65682353af07e59c5a90cc9f2992c301f95ddcbb8a9c154f994ad77ad5a3d3be659e041a3e2452c2039f36247a986fa70f129bf8aa8f6f7f5074a6bb4f81357db5546966967806996e635d01a76b7b9e44c0236fc59fda817a8992d220025884073515b01fc7446fcf39bc1b16d9a83bcbc6ddfd93326dfe161f87216719252f92e1e39b5d5a4de5c5a9e633b5b169dd5331e9618e6f4b860af1cf5f858d477b22a2fc3e1ee29dbf28b683c198eba57b622f86b8b81236afc6c01076dc94790e976b8d738d5c357972a5c16cdfe2b872c1bfb6ca1ca1cc3c6396049b82f103826b375542151ee1653d446a4e1814e951792e8c201f53ada3efe3d4e0ed8bae72859a1cca887c9a0a1b9f964e6b8d238ea38ed0e1d516d44483c962d03ca8f51b9370f3ae1c96366944daecca4a60e18d2742d06f8393b6f1a0f5bc429a7c06559380cc255b8e6155d5c4575ad488034f482505c4824c00b0727ff784cca767de700fce9beb21fc0d8ce55e9cd54b7fdc89d9418e411dc38ed82d9978847361be06dfa54e687ab5e0750eccb605f86d295dc98006e4a8a4d381ebd53e33b0e9c0321d0112bb27640582df48ce8b7bdc8ed49dfef20d8f8671b0343b00fb5db2ffb33d225ab98fdde6737d6167501759932d5386fff1e45537a8312c26bbd7ce2d4b62eaf994aa20b22b60cbd5099a1a34c38476d6bdfdd716e91144b864e6ea11627e00188f729cef13221ec79da10eae186bf855df01ef98a4cd579dd05891c3d1da70947e516f3bb3e58e324df17a63bc394f34851c504979598dbb9c00135cfa21d6cda989aacb3f03d418bdfe68212c0e27001f00f2fa4be8939d660c196f04594b9b77e34ee91f913f548c23ce308b427e2f9ec09373623dae22ba3b751b0e13e83d616b220972d77b03bd48732db081a574517cdda2131da1cdcf1d20be542417bf97a5279991dac421fb0791a3559c03dc3eec98ce4123eb6027340870de5e6838f6534d19f506075ac6f4d5ef532a28dbec4184478364d654d378e3b11e971adf9352e1495c3eb474a4f8810abe84e68a7789311f1013f5b9e232df1d083a8ab7fb3078a0971ebebe4e1fb83004a5882313e5b0d13c8782020ba851579ca80e04a7a171e88a033d5dc3e403c986392845486052047b08fbb36b860752d05587e1858fe70592b8f0983ef243322e8bffcd5ac2b34d1365158b966f477195dcb4ea4cc3f17e77bd76a8c03c796931e298c5b344e1ce0ab69eb414e049fd9b33acc028ff92e69d2e847c8503dbed96fbe104bfefc03975bb5edda2b91b57d96ce50344606104f9586bbdf4f0a4b77080da42e52c00e3f9add22cebdb52dadb06773cba51a0fcc2e72ec9b082e14bdec64a63939e93bc096ac606e218d675b69a767a9c8dd659fa736f000eb7e2d7deb6b75fb7616ad1af51f3be9472e17ac9509c7caffe2db262e45bd2806296da52ca5a6ac73cda821a3c4da4b5678737c38736ba7c863c28940b222e6dfdddc8331b465110ee9e10244dbea2d85c5a803a80057a8d1880e8575380aa0fb39331e30e7d430545308fa7390e83b25d171149b9725d2fd32d36be7acb8b9e04362734820eafc0a286ac0254f41af34048f9939b6dde97d82f93b5689769fb8c626482960dc9df5f93ffcce5033282c3422914ca1187e3f770687cef4619a54f12db44da5d729fa4e2ece8e08e274e4643bb35e9a8df0fea820a38eccbaee801f3198eae8cc27bb7cdc97f6178e88fda76a45064eecb8b490d46c9c5d527bddf74ea3240e166042606bc5007aeebf252e3f7d75a36f30f56887decfe10d594b3c4a8740a1f2039e9ca9e39f2c7a7fb8b015ff304dc105e4bdea6a2db7fbcb94d92cdf9446310914f00bb813911741823042d10643d457ff2b337781e6f8eed1029b7a8697b7c099196927aa00dada49ab74fba7013fd1cc3a76ee5365c9347c7a9fa7c22f71ec43ace22cbf38e9766148864b3f271c3ed702466928692696bd0ec3f8180140e899563b1f1a0721d0d56230c2a92370d369a9d98a3c872fb4b3c6fbe358125fe753dcacdaa40be357f92ce278dc29c4397bfdf3bb04e2a223e25f0f135b0af948f12add35631d7418cbf99ddbf3dfb59a107f9a9c7a14a71b7fa43dcee32d777027853b97caafa9245293ca4356b6c049b61f76d991f3de29ce63c687b7f60188542d1d30e9e0656ea6beaa88dbd253232b5c2340a578cdc727c9ff2e78857036c19da0049941aca95ac24638f891e320c041e106a42dbe56e4c083079423f8a1ee52178062d7175d7a68ae036967219c1918bba3ae76e1b29d314208af05555b808a46406ae9127307e749b177874fd25bb20ac1a0bf7ab66b326d015dbbb3a221607f679ef4bf94dac16de1bbc33c306a6ec4ec817dd595276fa2f45511b88143a5df063d6fe0e0574bae545f02a3ae17336c1c4e072add3082223ad3a37b6cd0da1553eb1ccaff0d18ec8f27619e100984f4efec4d397874e2878d5117e5b07a00571f7b8de02072e691de11955a162c48a686cc3568b4ea03b0b900ed6cd8f1ad45de9dda8ee04b4e5a0b5aa5aff31d81c750a2cdc8cc12dd06c3b722b3188781daf07a663cbc38800051f6d15bdf408df7b909b5afb8e51026191f8a118aa6104621d9a10c0abfc0dbd44109dfeb842c8f8433aa1afb5757aa9bf3560ba57699022e26b2a8e24f51998146f2b0bdbec5fe85fd74901602eb6935540578a09d490afe2d8a6846ecda80616ef1451ac3c8f8699476f55936b7362b25918c471cc16364d1925cdd52e812172820728e262d4d3fa674ea1801dda6d3ac7991443693d08be66ef0a064ab7bdc039323be4b94971e86f5b1840b7eb85c1b1f344c8faec8675096deb89ce31796782695211e3ebd0c1ec1f3bba319d4d1c345420f62c3d5dc23d9283ef17159c9a903f40543b915bde9dcbce0a95609080895c866e3ab30b4b96b67217fcde48b727cb205515e504d29b1d6b027bca65601e1e13e2148667a23b3bcca6d68d5f0bf7536877899a231b2d09d5e4ad9d6ae2a80dcca4cda1d4ecaadb6c0f4714bbcf8e5a108f5170ab8caad0594a6845446899d0c5c2df02e8f47c52e63f6ad835497c1cb217d7e1517c406b096ab9971b9a7380db854a80f94f0b39f2bf02a0e7b243c6fc133e053d171c2f5f0409a9941d4595db4c630a947a0b333753e55c045f0b41f825c7908eab22dc2a9171388001e3202362d315084fb25e9194158180741be0443e5c8a83d0c9496ddcb10b3c49bbd4a5ff0433a2a417a795a1bef4aa487f4cc4c9529335ad75b334756fb0b9d2fa12eaf8973bd9151f7436749d855466250420f0942fd1b0a4d271c42e4a27b4df01e3e9a2eb2e5b7b8153361d94861ffa63ed16504dc0e5358cd38652b59b13e0cca1ccddb2dcd58d07947696db6186c47c63f3aa4eb141dce7699f34906dbf0d6f04ecf75a830d3462943514370f1550e1d75140156cd8e272169321ad41afacc08b127942598587621df4a740c9975c8069aafd2da068ee8a3e80a7f0f164d9ef4b82273489fe9ad393713e4c5551bf770e8aa6e464720b83b63c8ae2a1c0a16d04e6859224d11b8e1731f2b0a8dba3528ea002315074004348f18d751346b7ad3d8b6ce4c777e31271efb46c57f4100c4d4e28b0bd4ecbeb84a80e2eee0e308c74d9684a8ad07e0d629cc9cd6da510a18239c27297104a586c16942957307f4b40c067583a4559f0d5731d0fd948346e39dc2a05b7a30eda77b03ea9619f0125094f295ca4b680efa01da16f01fac656ce83a027364846f0aca1ba32cca2a0ea03695a5d7213ebee518624527a1310e48cb30dd027733072ae79fbf88d5645130ebf4f8e8d30c6df35fcab95a2fcd5be26a9e3ffb29898680a8fc4adaa810e68adb65d48005a1d0c9d27d2b69ddc1e4cd704360042ef8b27046594b3250ca90a5074164b15f937714a9803823eae6148605ec98d95efc57d0e62dc87138809798c6c03bff37e44ac567f2ae30e27487be6d242d035623224edcb5f7d8ebbd4ec407dc92dd38d208c14bbf89fb252ad940c5edf08590dd2b3ab84c90870f11e4c314565facaac2dfb03abc4e5cb1bf20fc5ff3fc32841bbcba1bfe0b0ef3767d96f59013b9adba3c5e79af66498024316d6d6a5c58d7033e91d306abdf2bfeececf65f3b3c3112010dde4eab461473033187872c322da5fff9956d517bac1320b0f5d1ef1cdb996c31b71dcfa5082eacc8c6817d67657a827ad92a693fe7ec7c0a35dbb3c8ed9329211fe0753a037c1432cf6470a13ba44046f8609453b4425a2eb307b9a812601392f6adc6eaad7c1efccc0669406334b644c4958a987ec38a6acd178869e86746c8da24fb2fa0923ba46f2e7d37d8a7cee6503ae287832557a67e3ab3fe82e994f4c6f6a6cdbe15614a23271559fca73d5f9d908f2bab798f72ca1437fa2322d51e57e0c13d4ccb62ad9ba605f21c7b353ffeae87401129eb3a7fc60c2c015f7820480228b30f445af2bd321bf6886b7b8697803fd41fc433e0d7aa0df1801a4f936302a7c87fe3f311f4d4644038c2e02957b548eed4943c0c8c9e9fcf1260a0a174b14a9e2b1c37b83838c81b6f86401d1b12542ac2f662cc9f424d017933195c9aa836b8e9291abc4ddf998bb3b99403d9da8be6f08d846e16d6ef1e5c73154f627806f9040e9853eeff83ec602ae55bcc36627f8e2b17a6548c66d8dc4651aa5611bbbac0eb613b29ba18473106b787a76c154dcfbf9740f72566484d3a4782579520483af3dc0e2ec7df31c27c57c18d00a912393d31ca2b2bf28b9463ff780c4295920740eeff3dd258a104f886683f26770a2360fd855728d00cc0245780a78404c9944ceca5767484144af80b23893cd429d6468ce5d6ccd087464f83c862737e5a746e9ca6887ce779acbb1209f997da020d0de105c7c9852267c3ed1f576191ae3f20da0214cb06253833015567a22e0815c1370c1dc3832b4f1e77f1491c4b74bbabf85d051a8fcb39d1500cdcf6e6bc45ed3ee770971c66d8a854b00794f26550c9411e54c3840a063747504bff3cfc66b0c175c3809a2124f043c13c82b882adced5ecd7b56353a26e1c2540560cc4e1e01ccf9e0c8934e5e00a6ec13d6c9cb08c16705f4250724659587d106f289c6fa3ecc90e20310800b6323b9589d7e1d2b3f230c9e6ca02d019928b3c21e13f38541b87f2e608230a0e4c161c24048d0368e387c4d71fdc4409a2dca82494049a6f7c3d757f4494109dc99fcf94c6dd2ab2ccff61533f31136da70853ad0e1b9715e57fad703f31fe930e1c5cd0cbca0baf4ad53e4ef1ba00db088b7d1d1e39c469253adeb29ba459227418b068819eb96904a240e90f7b3a54a2b0a398109cb676b6370ab86bc4197400407a02a2e5a26452e5aeef08296481afaa0a2fccf306e5a9d88267352726ec7d38a438ef75b50234719b29f83277d1fd31dc778cd381a481a6bd198c3b2c3a5378b094d0bcb96a121680b55ca83cc89a1236ab8777220071be7f85b3b0ec83ea1e5334d1017fcc818b1f6411c85552d53fd3b915611bd7359b14466980b72f80e769cf6f9e9ada0bf5a949a8a3fb441c1aec3637e80dcb5834657cf1b5c5e6ca263e31121429e94f1d2566045bff99f87f5d64c4641737057d8061fc65109615be0e9913b80923ae766324c5c73d743f8788ae852105d0a9168ca47b1d6a28b7d9fdc87ecf2c84a98bffc6c75be7057151cdaf46f887ec85d30ba302fc5dc1deac9ebdb34d493303530422fa3af3750350f326f9ba76fde21ab798393796533fef51c3dcc2c7ffab00d941e1fd13f54a8806925a86e2b303abb94094defbd96adcffe01b6f5d9cf15396454b6216ea5b3832bf99703f9dd3ca0127964d772a7b83efbcb727df6e53e873780de8930f15c9f5d3900dfae38918345a121180794ae3a142e26b3ebb3a781858f551f1d76c6ffdefcc8e4ec797df6a85c7f5b84939af957eda68190da8a4a0be756e25950babd27b8b8cde606496f99c3b58cc1d820abc7c03574e6c981c6e7c798a0e755c8674004f113a401599e7d40b803c9a236d484643a262511ef46172ad7621a7b5c731a2cd9662a3ea7840d29a0dd3310424faa60199e15fc4c2199e6e9d0bf42bf35e640234ad2574934b1a39b75e09440d4a04788ae6e6b939fe17997060e904765ccde109d35152a54b1278cd0c174618edebbfc689a3b259a9e746d768ddf44f7ab9ec7a40b3af72ca5c5acef3508f7acfd19bd5aa5772a83b02ea45f22c7e52fc6e896ddcfcacc509a7bd1cf03c405cc1310071cd1fa8e0f58a7001cf3aabf0aa44017787f7ec1deab6396dce01392c96d82f47730c31d1b1a5e6f30e81890374ea814ad05d4cf331164d489826673570b205827c4588fee606480af3180d9b9a62e77008031d7f655cf502f4a17fbf81b41ad29d1adc38c95b970f4d44150f87b7b457acccacfc7879e23002fdea69e8b50449ace8f4cd17fe074c31062498396e862df39d14675c30d76f606472899665aaa20f0834f8f81c6886496fdb2aba6b240693921242b4c09243a02400aab791b9c4f7ecf8500f5ed2ce0db05ea5626d256a9e4b520edbf6519b21165cbc71fb4d364f5051c1f81f9b5b11ed298c6f17e4fb77bbadcd3dd2dddeee8e68d76eb387f7bb92b37d1d7b17f160c17072b9ebd1c141701c1d2f4b370673170a820f9aee32d73836de0610c25beaecf5e7ecb599a2d95a59ca5a2f650a8878b91b90fec89f2db4866069b846c5703e31a4c2c051b89a5b1d6832515f4a60a5a2780f5a641a488febb27e64faafd0ce686c779e8100132858a5420814598934a5cee22319b3d882a4ae879c7f0ab4436e072257b7d0b8bd1e3dd7323b5ef3211004d438824194d42227f9166641424140cf961086929c9ab72baba077b376d867835d88016c7fef11e0b026a6f6e0467bf237758043041095bad4cb98c3e09e1342b8601766994bb78d6ddf3f6164a5f6e89069827234e35abb7cfd8bae5210285fcd7e0479c8497968768a10313f3e80f08e765bc9d5e9d8a4be6e9ed5b0302dd676054b16bf6c8c7a14585b2d26ecd4f6a538118a3640da763829db730789c1d84eaff934d7987a3495c1d5e76460ba277226d0e3f6a529f45702d652378fa4de18e0e1a108f917be35f82409745d64d07766a115e801804e4a473220ad167c7eaae7286682b5df619c8848600e1b118fc8b9985dc9f5b983357398918b02330104d50f4168b1844c7a977f181857faf41def0f6ce046813ba1d438147959f72b1caa09fa1a5cf2a3c8b2ae38a845013f8ecff559629f1f58d78497cc13f7e4a4528cba4630731941a8c753ed663c113b2062cfe06c4e255fc20e6f2c6aafa3a87d6dd9c0c921f7023de467308366d322c1bc61336152c0830c22d54434ca76ac0571a89813dce737b69200b27078fa6080c7b5c29b379b9bea59bdad6ac79921012138b9ed38fa86c28a3acc1fb6dfbd3ab811e0357e6c67ed3576f8e948baaf83346a28ba29635f950941746f01dcb2a8d6e56cfa43f504aee8ecc545063160d4df8391406c50320cce01144fe4488ee33c86a31265a8705828fa18be1d5b82529336d90cfe408564f97a8816c70994884b52f5c4213931a6b6e754b543fc2d75054c7b8ad06b6912618e16eb61cfb2cbff56f731111c1dfc23a1be3c0d0b68cd28150e250a16713605ac13f5b10900c83ae1b497c3fc5ef555170026ae46a7d11688a246d6104bb87c0e965c542c421a0564c4df5245cfa4d01f9e8577e0a766a7962a26d3cc964252123dbcfc39846db13108a53fe65ed90fd882588169d79aae6ed9321adc9b9e2755e42096a237f86b2f150b5155a61d4d344e95b9e1b20f63ef2ad253db97943f1b81ca94cd6734417ec78cf9891714b867930f1c75ca229fbcb0b6d1bf2716d234999fb53d3748802e33ef4f166eb21c1181cf9b97e65081256bfe75b2146e41cfdeeb2d911f2a12018c14be73ed7dbfb571d7da6c15abae250da485c0b7e972d4700e3e016a63f0303127081fc465dedc61df735484b363aa7db74afc126858a441cd69d8a8d40b754525ed2c7d73c48254d7f1e08886d180f450d00acad96145274cec0b12c79bf7973f7710696d508b0a571063fba17b040bb5cb338264865bd12d899762e8135a7aa58b25483dc0bfb2baa9b1b83826131d352a8816bd49c00fe16db7d00c47c90ab4ca827c909b1c446286807bd2aef918344e770b433b06e71fd677dde02f6bf332765b64e649aae9294595165e60f4a0d7b297259f9094359a0e7454d9d93f4d141f685ed01374b7c79ee51df549b8204d843f4c40e4247b22da922aba2294318164eca67268b5660ec6ba00bfcbc79ef13b25179da7b4ac5bf3d60ecef9052f6148db2bf043d5e75ac3322759264fef4f182da176bd14b027ee908398f218ee0f2d2f18d0c464ad2e1935f048d4035818ab111b16c52ebfd648814932e271cac8a0102bedb1a12d5715b122bd6c12e94b78768debaa5c3342099535bf8556c265b89942cd19532b0e311822bd68acf33f0ee2af84dc5712411ae9efc8df36af4f2583ce404556769f1b2d894b36e13921c47619d4661fd91b6f18af8706318d1b048753e36ba127c71fb056a14d639a142661b77d3d270d5f1b1146b0576d6ba3ac2549b1c568a11f1a2d5b70436575660b5a81ce41e4a9c7b76458be1068eee86e76ff652d2e964434f004a001df769fd68c7669953b32d7502f18187c6fa6dc9fb9f5c668c2bf0dc913a6fd9dccb1fcc3a7c0976af4ac937a89f66a3cfba8c7cc39c633c2cba5ad5b7ce0efe7459f7d253411471c9e9c5e9e2ec981011badafb892451f4fad043a654d24047645a41c36cda479bd8f86a95ca8376ccad332a8772fa66e0ea60a543da649f241354708359e88adf9fc91159c8402bf32307317e6c35bcf05f74bfacbfb761e86ada983c36add1c0d31499992843787755dc55923565fc44cef91543f6a5fdc3c1db3c58e40a049e6f0514daeae60e9c98b9cf8ea732ca4db03dfb7c3b06238bac2a876613bbc4c0f7423e3166fd768cae92c10de2744a72560eb65e0ce68e130713be98d57cbd95b8f4e013825882e4c450c5659cb10bcfe382c6282a8c3bd7841903d5a3915643e0115a013b8acbe6c6dc5d4372d9d31f2f120634bddda39f6afdb716a90359ae78ec7a022ec31a5f04e2ed0573893202978ff39aa3f42b18cc79af1b98b90b1411a439454027740ba4039cb42ab4a729e0034f02a1e06a040714da1c037013b31183d93f8b3c4e59a7d2a245c82be4a9b7acc5eae798a7095cfb7140e4c590f14da3460719c35c0b4473ea659f452a17aa367812c8a7b068eaa0a4d80601b7eec2d1a3022e5a6c4de39c5b349d7cf0a19159eb03cc4150b41628c106809b4ceceeb9a8b115ce31e1e12fc5aa78907777e302a7c6408ffd8bd384ea29d2f39e00b68538a9ed3bd1b23b8f1fe073727a71881f13461125fb1969a2146fc3aefd0972ea87f8739536ccb1bdc06b94c9ea03f05e5fb5071c51ae8f6326362a2e19c602ba12811e9f220dfa4af617a073feb0c9507f6f50b420a73e04ecb4c59fae1ddaf8826d1bd6888a7cd59875e9ed5fd32eab98fd957f19fd0b4c0789afc055ffbcd3427358e5c6e3fbf22f03845359ab595df6cbb120ad8cf5d2810018630372e9b23663f043839e0bb99f4db4f14c9ea19568770d060d0ce16181374b163713a948929ffcef126dd34a5463c74ddb04868ace3eac2c331802b930aede61ec40345b4da63138eab74c19409eed18ffb702c9a064141c0e080751666a93cae337e3db34bdaf866f1ef8701a59e96250f2822d18e14e8a7e55c71c6aa3cc15e34a76c09e6ed920748694437320191213b70ee5ca14bb0478238faa7b9a23cbdd8dd856652ca03fb010b1c89223e7fa60da461364be6840eb6d44b0b82c9720d7d6fe8914566ac4c0d56ef4b2247e9a02629685e3e0011b67ce843ad319f878a9092c070e9dca2e665ff8578c3fdc96657ec7890cdd7cc36be3fefc1aec83fb1cffb18fd9600692707e1f0a087ab00b4eee3d12a87173006cf080db669b9c8d77f9220c045d88774ee45a6441515afc1213c6a6ec071fc544f781458f405309ed48776a06ec19fbb54369054ce8b449fe588be56f93d3d4d9b87bc8842fd88c8727fdb945f81887d7ebe891647721c6a20ebf0c6b309484bae5c6ea57bc90a0016291c71788132b5529937386def22bbe3ba6b886c1c8c33040079e01f9519f16b9376fbcdacb7e1b307bf60bacc6fa74bf5d7957c8c6e556470b351e823e00e3b2e4fe7ac584b45f24fa61ea7ca0aeb9979f4ccaa05e7a327aaa99e4b1ccceb29b595320bdcbb97f357b4f0e45b4a4c592d9da3d04e67160bf8a74dcdd973ffbb14f006e6903da37a0ceb89afe85d22bc0432d64c66cf861391dfb41e51ecaf875a86395b5012b26fddd3a371891617137610398c3894a75d7582898e65ef5094230d04b35b6a843f0912f7979", + "patch": { + "balances": { + "balances": [ + [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + 1000 + ], + [ + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + 1000 + ], + [ + "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y", + 1000 + ], + [ + "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy", + 1000 + ], + [ + "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw", + 1000 + ], + [ + "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", + 1000 + ], + [ + "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY", + 1000 + ], + [ + "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc", + 1000 + ], + [ + "5Ck5SLSHYac6WFt5UZRSsdJjwmpSZq85fd5TRNAdZQVzEAPT", + 1000 + ], + [ + "5HKPmK9GYtE1PSLsS1qiYU9xQ9Si1NcEhdeCq9sw5bqu4ns8", + 1000 + ], + [ + "5FCfAonRZgTFrTd9HREEyeJjDpT397KMzizE6T3DvebLFE7n", + 1000 + ], + [ + "5CRmqmsiNFExV6VbdmPJViVxrWmkaXXvBrSX8oqBT8R9vmWk", + 1000 + ], + [ + "5Fxune7f71ZbpP2FoY3mhYcmM596Erhv1gRue4nsPwkxMR4n", + 1000 + ], + [ + "5CUjxa4wVKMj3FqKdqAUf7zcEMr4MYAjXeWmUf44B41neLmJ", + 1000 + ] + ] + }, + "sudo": { + "key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + } + } + } + }, + "id": "custom", + "name": "Custom", + "para_id": 1000, + "properties": { + "tokenDecimals": 12, + "tokenSymbol": "UNIT" + }, + "protocolId": null, + "relay_chain": "dev", + "telemetryEndpoints": null +} \ No newline at end of file diff --git a/templates/minimal/zombienet-omni-node.toml b/templates/minimal/zombienet-omni-node.toml index acd5b121c674..55539fd20862 100644 --- a/templates/minimal/zombienet-omni-node.toml +++ b/templates/minimal/zombienet-omni-node.toml @@ -1,8 +1,8 @@ [relaychain] default_command = "polkadot-omni-node" chain = "dev" -chain_spec_path = "" -default_args = ["--dev"] +chain_spec_path = "./dev_chain_spec.json" +default_args = ["--dev-block-time 3000"] [[relaychain.nodes]] name = "alice" diff --git a/templates/parachain/README.docify.md b/templates/parachain/README.docify.md index 47385e0bbf19..0d6071ddd951 100644 --- a/templates/parachain/README.docify.md +++ b/templates/parachain/README.docify.md @@ -144,10 +144,17 @@ export PATH="$PATH:" #### Update `zombienet-omni-node.toml` with a valid chain spec path +To simplify the process of using the parachain-template with zombienet and Omni Node, we've added a pre-configured +development chain spec (dev_chain_spec.json) to the parachain template. The zombienet-omni-node.toml file of this +template points to it, but you can update it to an updated chain spec generated on your machine. To generate a +chain spec refer to [staging-chain-spec-builder](https://crates.io/crates/staging-chain-spec-builder) + +Then make the changes in the network specification like so: + ```toml # ... [[parachains]] -id = {{PARACHAIN_ID}} +id = "" chain_spec_path = "" # ... ``` diff --git a/templates/parachain/README.md b/templates/parachain/README.md index 15e9f7fe61cf..818fbcb693d1 100644 --- a/templates/parachain/README.md +++ b/templates/parachain/README.md @@ -146,10 +146,17 @@ export PATH="$PATH:" #### Update `zombienet-omni-node.toml` with a valid chain spec path +To simplify the process of using the parachain-template with zombienet and Omni Node, we've added a pre-configured +development chain spec (dev_chain_spec.json) to the parachain template. The zombienet-omni-node.toml file of this +template points to it, but you can update it to an updated chain spec generated on your machine. To generate a +chain spec refer to [staging-chain-spec-builder](https://crates.io/crates/staging-chain-spec-builder) + +Then make the changes in the network specification like so: + ```toml # ... [[parachains]] -id = {{PARACHAIN_ID}} +id = "" chain_spec_path = "" # ... ``` diff --git a/templates/parachain/dev_chain_spec.json b/templates/parachain/dev_chain_spec.json new file mode 100644 index 000000000000..0650d0cbdcdb --- /dev/null +++ b/templates/parachain/dev_chain_spec.json @@ -0,0 +1,108 @@ +{ + "bootNodes": [], + "chainType": "Live", + "codeSubstitutes": {}, + "genesis": { + "runtimeGenesis": { + "code": "0x52bc537646db8e0528b52ffd0058acbb05de71072a1750106868947468aa038157644bd8ada3180b1f7c6900a17c97643f148b47a13c9cccc36f5c256c578e62213a2ec291fee88cdbbd6c0497005907dd7edab3978cf6e001fe2322ad114208d964efbde596016d1a7e15e515a7f77eba7ef3184d3b564f31deaaa7574f3a6e5d7bd77ec2aed5538cf929def41d7ed2be433dc9c83c473de538457d473dc9ec780e2ac777f889fa0ef5b4a37a3b7eaa72ea15a5987a8a798e1cbf89c991a35e07cbfa0e1d96755915cd513303f3348a2b6be6cd2b4724d188f1ac8121f1a494f6348bca71456ca6666652124cb4aaea5d1a950306d3662aac0686e2c91689c61c2fe6a87953c7877631895e0700cc0e968e4e4ccdf52a93e927bec954cdccd497b4f912e6c3c74fd67df8a014457abd09b36e8aa5d24ff4259dd224e5d05eefa306e63e0e809f660e0093e9a7eba61d4c07c04f55d4d1b9e8eb4b31c74baca3a3e3e327ea3eea494707e61d003beca0e327ec3aaad79b765c870e3300b8e9a79a9b4aa51edfa19e4ad761879b7eb2aec34f5acd794c0cd7bc5e47878eebb063c76fae4387ebf809e63ae675cec34fa4f3e080e01c003f910e0097731d00701e7e9ab90e3fcd9b4a3838f538a6e3fc448fe37eba8ef31d6e6eaec38e7ad2514f3aaec36f76fce627ec37eea7f81d5efa497bc9f5d0f989ba4e3df5d079ada71edfb1e3a59fdeeb4fd579a8271f3e9e534f37f574f39898e3d4938dcd6fea09e73131bff929c76feac9e6e6363f61b5674dee3907800e188eeba8279b7ab2b98e63d8eb4fd66b3de1d0e1f5a7ab3ab126771c07c04f567d624dee00a8271ff50a67babe433d59d665ea49e6d70ebf7eaaaa11573d59d771eac93a561dfb895622d6e48ef3538e1ac49adc4bf5a493e33deaa9d653fd0ef439f5a4c35feb69c9f0a40d657c81c11a2980e243a360c51428b8010d2fa0220a3e54d0f7d4d4b926775a4faf9fa9bb26771e759f69bf4347cd71eae986e636f53424841e9461cb1935b8c11384e0533394851458408111aed08633b6f0a9f909a6d2acc9bda69e68ea3e530f752bf5fe44aada9adc73a8f399f6333fbdbac49adc67ea2947bdc2f9500173997a223da65e01c4878a771cf504333aa99e866870050e5768e0061b664883cfdb2c78e9c10c5090851b4668830f15efa49fb4fac39adc49f534aafb4c01a8db0fbd3f5115c89adcb34af34cfb13d723d6e48ed5d355af28f950216fd5d301c010e30c3a60630a5e70e053d52bee43058f22acc1076360a10a3794c1870a3ef5d3aca535b953f524eb3ed38fba01e9fd29d6afc91d56ed99f6fba12764e21f5f98879c63c0b523d0b6a80a2f7a8b9ea0d11ba9f95b85dea2276af445e2d2369bbfccb36ce2ef2f973d21a3a9cb33f5f84dd2c925fea4904b7c0168d4b75916a1aa7c0fbb2cfe729874cd59fcf5dcf28877e4f99077e6f993a2db97a4db93babea2939fbf2675fdb259e5f6f0a4f297a4410ea888d6f29c05b12d2bc62517eb36d471ca4937d8db7bd06bea9b0989efe1ebda8c085537d8d4ab4fbabde725ddb66d934b38b13dd1f3cba5f99e5751a053c34fbae13445b725850d815ec36f260456c8a547974beed341135ccb67d0f6ee6b5d405807837189864beeb1b7e8c9172dc325f7adc9842c97701c74ce3977f8e8de9de3bbc711c2cf86f5c6483c7cac9b6b3eac41aef98fc850735d2ead8bce09e15ade81f19be44b3a1bf24efcfe8ad435a4db42e023bd81770d7fb94c08c3efc20c81862af190fa0043938f7f7799905721dd8e741983741966083470c1a56dbecd106c20da1fdb2f13c2df4cd6cd84402ebd2c8815e2dec392c28e741b52d743cfb92ed7fea0b9efaf05a26db6efe18db936677bf3f7cb3bee7ba6d70e40d3b8f48e71e97d3321934bef3d4c006d93e977771ade817fbf6478071e8777e2df2f8741babd597c0ff73053273cbac9d06cf7d9aede18e1bbf35d0d721d2ba4aeaf1d80b6bdcd75e3bbf7f0e6bce79c109e70d1dbecbdc63cd067a7c0d27b8c775cef6978677bbf395b7fdeabc3907306b87648dba2299e5ceb88b42d92828ddea6e19dadd47c7ee9f1bcf37198c7f9f035369a2fc33b3c041ba2d81fdb3dfce8932c18efecf99bc3ce4a18514019d2f8dbec778d77b6f7fbfb1fcf932ceff0efa5c7f324cb06fb1dd62aee9325f6bb0cf3b0cffbe49d2d2242a3df31de79fdbebcb37f87bce3febe39affe00e9d541c80d395ee2dade9c4d6bfee5fedc37acf9db7beecffd671de5a28cb1c93b9bebf7fd6e101fb4fd6644964bfb577bf862cc51d8d80661e2bc3410d54e7090ae8739fa3a7322312eed041b6d10268ea88114d0eefccd8e4022a276ef619715d188da1de39d6dfe66451c516f0ed71fad578721760c85ebb96fefbdef373bc2ff79757269bf5cdaf7f090730cb8ae5d9821d050854bdb904bfbcd842c97f63dbc38970634f72d2222eafd45da9ebcb3f1ddf7363ec0d0f8ef9b3be4d2fe727472692f008defbedfec0857c825fe8fab934bfbe5d2fed1e512d7e592bbcbf897c31e75cd74dbd1c9a53d67fcebb9e511e4927b0ff7700ff730538c4bee43ecdc141f2f705fa0c20a2c1f577cdcc0070e7cb4c007161f547cd8c007163eaef001c587153eaaf021c507153e4ce0a3043ea2f878e2e3043e48e0630a1f44193095c1f486e90c262f263498cc6052c34406931a4c6e98d8604a83690da6361c0e4c6c98d630713191618282a98b690ba6334c523089c1c40553164c5330a1618a82a90c93134c4d308d6162820909a6239894603282a908a624988a4c5a4c58309961da62da81890aa62b987460fa812907a62a98bc3049516aa30446e907a52194805012a32484920f4a442841a1548552144a572849a18485d21925344a59283da164855251c909252da5314a53289551a242a909252594cc2831a11446694b29083db694b628e5a0a445e94ac9052515f4d042c74a0f30e8a4a1a3c60e6ce8a441e70b3a5c7452a033039d2c3a5ce89c4027063db6e891860e174a547464a0f34407073d9ee8a8a0c71b3a527a84a1071b3a5de821061d283dd4a0a3049d2d3a51d059828e161d27e82041470a3d9ca043450f37e858d1c3043a52f40082ce143a28e861a5470e7ac8a054a5470c4a2b28a540a78a1e5df46081ce1374c8d081820e137492a073049d26e894b1c31a3ba861072f3bbc6187364a524a534a2cd8c18d1dd6507a410905a52c766043a9053ba4a1471a74bad0d1420f327aeca0c71a3a43d0f1410f23e80041a7073a42e8c1861e41d0d9828e183a60f4c0418f317ab4d1234b0f1de8044107063a68e884a153d4a3a8c7107688c30e6ff4c842670c1d12e87ca143041d2ce878a173831e44d0c14227073a45d0f981ce153dc4d031428f23e86421c78d9c3572d8c8f192a3869c34e47c21a70c396ae42421e7083964e42021078b9c16e4b02067053928c89192a3859c14e440c939410e969c2a392ec8997228e4709133839c1ae4a820e78a1c13e46441add02de80ea8175407340734091409940caa058501ad42b3a02fa02ea054680b280be80f680fa80fe81794075406340694097409540b55023d02c542b9a033a046a045a045740c4a043a042a0615020d0205020d8382414f40b1a051a8144a027ac5d9f81aa8148fc3bfdc0d77e36f502bce064a44a3a053bc0dfae460f812ce846b39962be137380e7ee549380f8e84dbe05d1c069fc165f02acfc275f1177c0b0783ed52be824fe111061e62e0b1051e5ce0c1041e61dc3ce1260a3765dc3461c7971b29f020da11871d6ff0b0c18e37f0c0526382992976c021e607365dd860b1d9c2060736576c642003071c41b0c962d3021b1dd868617303386507155005335b5c1b5719aa2e465f8839818c16150e2e335c6ae8c802e78c1d5e70ce8063061c2ae0a001c70b4e156ee53ac3e5e539c0b142056a54f0183ccb2bc30b03ce131c2970a0e05c8163050e163855e09800a704375b8cd2b8de70b961b486eb8d1a146862a059c3b586111b68de7073e5a6063737b041824d126cc8b05182cd116ccab069820d510d1c627240a282b9c0a60b365cb0d1420d0d6c7e10d385cc1b18093d5c4042416506520a749c81ad60a60995171b29551aaa33602ca8d080b5408715b0292429334cb031414d1162a6d44841d2414d09b434c4a8a0064a8c944b0d346cc4b880b4039217338d992755969a2968d240fa4253a3e6494d153bd240ca81e685182a3129a8b9c10e3668bcdcb0a0868a1d6aa821aab9a2260a8d1b6aaed464a9c152e3458d0b2618646a7093c5cd0a6a56a0ad2166053bdc5023e55a63871b3762d4d8a006063bda40d3069a366e865033a54607352da0d92293031b16d8d4808609344b8869814c1a64bacc9c4087183a863073c54c17332d98d162a6ca0c0e666430d383191eccec6026cb8c0e66bc98c9c18c95192e665e3073650606333498a1325383991bccc46006cbcc1639ce90438d1c66c85186992c666630e382992f666c20b3860c1b326ab881c20c949b2d332598f1c18d143753cc5861d3868d1a6cd6b06163a60a1c2a70a6b001834d1a3358d840b1a1a2c60c356ccc98c0a5c0a120a709333fc8a1450e18d8d020c70974def81a325fc8f4204608314190f102270a3558cc4441c3861a2b64c070c304191e686190f1828c169c163365ecb0c28e2c9058109365e483982e3161a041834d0e766021a60b3161d49020860b8d0d1a1c342f5a170d0cda19b43168696865d0ded0bea0b9413383b686d6060d0d3130d8c1869819c468e1d6a809438d186ad6509346cd1b6abcd490a1860b5a117894e1c63063859928e048c38e2ad8b80087186a7080c30a374ec031851d61d80106d8c50e2ae0e8010e1bc0e0408b030c1660aa80b1028c1530558021018c09604a0033049813686d6864d0d880014373e3460b4d0d30593429c8cc60071a68de80e982a4829119485e483db0f102e907a434b82fa42ca42ef8046c022e0147611230167c0543612bb80aa6829ff0142c0547c1442e0e0e0eee0df706e7061e63701aa4292331d07ca93901098b511a466cd0a881260e2335466a188161d4069a354665b881c1680c232fa42b646e20138398178cd040b26204071215a32f242837536e5e10c3829b2aa337485590a220118de2b0630da333e4b401c260140556c20e34465f8c92f03366c0e02d9c841928ecf802a6021b18e8b8820d159b2c3026604bc0b4c8c952b951bda172035665860c3365b0e9418c17551bb02c482b90c9727d1985e1a605571c2e36466f18b9e16a6386614486cb8d8b0d3159d0b871b50123c2b0b871c10d951d6be048810718d8932b0d3bdaa849c1c88d0b0d171c645c30f2c2f5852a0c9517302bae2e3c964002838712ae2ec8bc31fac1a8078f02298b1d62d891c68e2e231ecc483113849c17ec88028c22e70d58458e17bc363c37d8d82047971c2d90d181ab62bd5113858d159b2a6f8b8e169519151572745031a1ca41a5831b21d87031f3859925b014645620d3821929cc7061a60b395acca451a96193029a028a82192e3628c86923e74a5586981dbccbc76033a58a42b5a5820246254706d80b9e466584e986d986d9c6688dd9250605a336261b530c323290b1810c0ee61a325bc85c91e1428606da1b465d68e0105345060b8907325664bab089a2668d1a37d4a06163458d1a3653d49cc18604355ab0795283869a376ac06053450d1b6ad45093869a38d84851f3851a376abed494a1a68d9a36d46ca1660c3657d474a1c60b355c6ca2d86051d3c5f4c514c5c482f625e6063361c400616a617e317b607a438f2aa4168ca610f36464059215d20b64884665e4c080bf2013850e2c8ca4f0d0808308a326c8b0c066059715d7183359187121071b72682013a507514c16e00a74aed0230e31698ca0f0283154d8a8c066063367c84c993163460b3353a8c8786ec84851ad51b1015b503a410c16b02b395bc8908014831e5f7a64a1b483ab08980d7af48007144653d8c400c710302cd7153c9e50a9e19181a3b0630c334e98a9c2d130e385992bf4a042678d992dd010a1470b4a55cc60a1c71370ec600737906640d282b40549062418f4f801a90a8e27603dd08922668a1e5ef4e0410f2a3da0307aa29385ce0e669e7012cc50c1fa82cd0b66d0284da1a3031d229d2a324f7a487115fd0a9b13e8c4a1c4831e2fe8b1860b4a8f2fd00c81e482111839d67035f48043b586aa0dcb0b3a6ae8e106dc01fc026281368042803180348037f0210564c1b1802680258051a0146f0d8f8dc786b7c66be3757963804f3ec553e399e181e1a5f1bc90f30636039781c9c058e02bf018380b3964602e2c060e0357e155b0193c90c050602af0147824e1569c0a2683ab3836b835700fd80bde01eb80bbe02c9c031e5c9c1918081c06f38087131c1b3cb4300c38082c857dc05a3832b82e3cba70282e0b0e0b6e0b0e0d1682b381b3e2b6702470519628712a40e2441317f080048a8800041ea080230e18a20146882812c410210c58804f27806707882005702f2801c087c9b272b95e27b345c52f4913243680c40924922c8104c9e59e124b92204162fdf8a17531d26eb04d9278008925486e341b6c50922649983881242849076a342c9b240889a0264d9868926449122236da95249034c1812545942869620141492451a2a489050040ab01108d064a942091841225482c808b25823ab0248992196c92241c40d2441092a0253ac04412109834196c13254c20119444092449927040478bc136c181254c20090193241f000a0112445cb3b2499258d2c41224482061c2894e92c492261c10b4e4034d249144ebd0b650a289249228c143d3629b8420c90f92264d4290c4c892269868d2244904a806830501134e3493243f4c9aa0c011aa55d924414828c18412249c2049f281241f7840114a24e94007902802005a16dbe42789244b24e1826d12b4240993244b2c61e21a95254c70e0466bc126c14493254c30097a220992102871c20924903049a28412263ee0f39344134a7c7034166c131b6d058b04121c6dca7a000912414828814450922160341588800925483011c4c41226726829d89f249a50b2012449823e9044922792e0d050b04c9a5800a649d9254d38f101244a2861e2031b40b20489249030e144072d49e2810e248980124994f88092249630b144024830f940eb6827d826499808628249134b047d008918cd04bba40926829060f20125900435a1a40912269c6824987ca073ac4ba2640348042df90007900425918409134a2c610209929a3ad5a0ac38810d557a87ae2d22226a37f46aa415032b06ce0947411c381c0e1c6e06c70c0e37338343c6f7aaf79e7c0fc6482db552be286516474f071d59e4f738c237619c713492353570882ac66a5d15abb82f4a19af78c55d1e6fce18ad68c518ab5d1821001edc18a17bf2c569cdb915dc2897b7aade7b55ac36ee5efbe29311422937325f96acde5b19256fdcc7114227217c33ceb952c278ed2edcb72f6e55c9b7d735a7b4acf72c4bca4aca4abe2729f8284bca48cd27234319253679e19b1624cd595df03db9f1bd8d7137462bbe387763dc061881d1c57531ca171df638ca7dd045c71ce3d2e878d1c118df684679cdddb86bbdf7de7b1762475c2877376edca54f2e8cee392961ac62dce91c49ee1659de48ad06dfc6ec8e95d130b9fb56ca988d0b0363bdf7e6b4a6355d5555525a18c7aa0aa2aaac0763bd175dc59615a394d07a2b1f9cb02656cc1cdd6426226d6c6c6ca27bd1bde71c84efc9f7a494529268dcdbb9fb6074704aea3dde7590079eaf46ae83724238e19c12cab970230f6f61deab647ccf91de93ee41685996dc186384bb9079e7db392b78e1c0f9a0744fbe2921e9c9f7e67b32c6dd3724becbc1402865f5a0940f3e3892f24129e5830fc607a5e48d713f1a45e89cdb7555dc8d52c6eb8a2bdf1b3d2c0a99918f012528618c3ee27737aee427e5c605bc08af17a384524ec9f2bdc703c2b911be95efbd7d715f7cfcf6bdbd9e842fe2ec24ed257d64b55bc98652bab70ba165cd39e79413c2eb82d705e1b3acf7a69c70c239a584f34d4b5670c6a86341f8668cf0c11877df655da427574ab92b5f26b3ecbdc71c23748e39ee4219df7bf0c1dddd18472feeaeec910b97c7db179f93efc927a58cbccb8ff7da477af15911c65dea8da8f876219c1cb99a0badc9d34929f9addc1df168659edcf1de933ade7b239a916904001f23687afc56eeee3e2abe958f92f2c5dd7d31be1737ee7b72658c711fbbf79e943ce77cefc9dd0865b4e68472e3c27d3b63946b4db8313e29a5dc95b2a480f7deabe4ce28e56309df8310caf9ac673d381f7cf04db812f29c8f6384735f5c5e8efbde7b706e8c31c6f8762bf8762194f0bdb7fbaeeb9af34929f9f125df7bd255154b693df99e65bd7d524a28e5d3e4be5db82b9f7c1b17eec6dd5df87657be75d1c10917ce39777777caea494bbe77c19df0aa26b44893a22829e7d35e94d082d2c1ddf8a0f55e7c6f77df7b12ce39ada7f3de23c928233b860ca384f13928a3847277a58450caf7deb3de84ef714e8c71c604f4e8e1d8314c74314604c88d314ab91ba33cc084735e534af976a9b903103b902e9e30ca18638c2e0631c00f393c2e3a989de8a28c05883e448a25845042082d2819422863b446d1bd17e32e7c927a52cae836fa78818f2a4f6639044c9af040120fc801e8b002084282499225493ac064876c0790a44993244c9cf0401349206192c44913492461820489264a24910489007e760a3000279248d284a772800926499c203180000a5080012891044913414e848049123a788089a0251f6052653810f4011d49964012b48409264e2049a209131ec02180124998245182491348760a3080244c3009f2c10489209d1a6af39304121b50a2a4091f03b0b9d971539303875ad20413414920d16409124c82900431e1010f3451caa1dec42c49e2841348e0f8b0a3e389264010b463a70003c8f1c30004806400497e9050b201241d58c2c4124d9058e2d80a208993244a903c81840778d074a00925483ab084890e74a0092535ec811d9b840907903069220889249a50a204099c98cb3c914489254c2059c204124b24c1010e345109a04409133f4004d04407c80e20a889267a500289244c4a2148f28324e889244b78c8212849077402b0026812d484892792f8f8b1037882090e2461e2049018264824519284124a9858220924414a3e80040792288124e8892426274b923c6d05806449134c346192c449121130d10409079a688289134892740009264d04354112f4441202385992e499b22d784f7cc490131a12b28e58212121292434343485844e436f8786acec882137e448470c392121f88e187a43424242427c84901312127a51484848e83a0dc1d3d03c62c80d39a12177c40e0d09090d0dc9235668a83a42c809b9237648486868e8bd2384dc9090d0103c62c8b9235648888f58212121a1a178c490131a9a47ac90d0d0103c42c8ed1075c40ec92386dc503c62f7f1113b343434048fd8a1213e62c80db92386dcd0d0d03b6287dc110e8850dcee3a0f34416203a45f11bb487408ee9c093144b4eacf6e915ca6c5d5f757514bd7f25594bba2904bf34e34eaf2ef405c0ff33c7069bec4a5f9f9abf4787c60d3fcdc7ce8f96df656ea26251fb8346f8516e7371f86f0b08aba66d77b25082d5bfe72fcdc1b6d437d53e431bde13ba11ac8113576d716bde1df58ae6fac2ac45063f58a150af0c9be4557c2e82dba0284aeaeba0975f51eae1ba4fafc7c3ca4cf6b8fc7f9ccd33c1e98cfdf01617dfe82a1cec7a21b089cd0d6152c3570a36bd0e56116ddacdf5c0741190d822674459bb8bea21d86de5c773eec43a237577dc024bd0ae743faaf971101f32b621585ed322144c0fc9258455f6f06011808accf08864eb15fdcc8aed1299c0fa9de581588f4a5af40f1c127fb14a438f854e17cb23f99444444a33a05f5c527fb05b18a726f8684cbe810dc5d368436c46bea330e458e67b89afa7e1d10ae1d10fc6d7431fd7977ed853498e3b7a8065c7a8b6ad085e6cfba29c19abafc80d64114852de56bb0a5065dd4a04a0da218a1a4fccaaf9459116e285dcb5343c4a6a8a0811b5d7b8b6860060dc8681ebd4534e08206539af61671b1465f24d85ba9e3731c10ebdeebcf3b7ffbe385d21b4ec76f728877ae3fd910fcfa473ee2747cc7a21a5cd1340e086e47b95d06bfef9bd6f19377f8f19803628bf05f7daedf1d103cc41b0248effe70cf0b291dadd00f06444d9b7f91805c475737e73223f05c372db8e86c88fbeb68857687f487bf35196af778a0fdf2e54b158ed4498e1ef2e6472bb4a34e72f47209d65783d8c85d3f3efca33f5c6f8ab8c7f3377763c4c51a74350cac7410f7ab1bdf89ebf79e47fb51da1c74f57e283a7642165ef4bb258b3a7e79c751eb6ca5df2debc8bb75973580aa1fd07a8344441d64591603e1ec88f5cdf58fbb352d96d2ef2f03dadcf93a7ccb2320f7a5e55df6ead9794b7455ac38ce18f955e7d73ccb6eb2d8d2efd66f9e6551d4ef2fc3b063d908bbae1b1d3b5e84a46307c9797193655996d19bd1cc6e5efde6a68ad1c166e8b8b9b9b9b9a13a34a9e333d7d105efe878fc25c3e3e6b971c303cb6230c83bd68fcdad03e9cde636b7dec33033f26efddaebe1f850b38e533ab2e7f0ec57466fbae09d9bc75399100cc370dec33a30caa408b6e318e9d88e8a91b0ecbab939cecdb123dcf25847989bc39bec34cf7ec176d3d5d0cdb5cd373b62eacc86d6d4d0e44023152ec5cfd0c7069760e8766a1c743b69743b8de8766a17a9f08e7b6e74bc7b6cb0191d33abde1479ff91777f87a7be591012be0bdec91e7f655976cefe1e76747477d35ba4842db2e09285972c885ee0e5055c2e4c83729f63bf75ebf5740c735df0ce767c96bd87abeaa3571f8d1e7f8d46a3d165591809c3ae59944911cbba3e6f59b7ac0b73d4024a408fbe6d8d4e815c8f46cf8e987a546f8c6477cfaaeb824bf13fb5dec34ffcf5b456a29fd27947a7b34eb3c84fe76226648ba698d1ae88a95d956270299eb321aab6ea0682b67e2d2780e897a3422defe8f644bb9fba57711f2884e87fa2874619a610f9bc3e592ec07c15f7f967dde0e9fac8cb6fb1cac4eae6aa5bf4fda2db135d7db3214a5dbd8865d1772906ef548f5bacd2f115e52d5c8a1bd6d179d1d151d1e2798bf322ba2f3aca74bcf66fc7bb2e1e8f7102fe08b322e8ff9715f19fea5fe963fd916fe8864b5fb8d66151645969789befa7038232e245bfdbd08dfbe6a36f76e4ddc6070fb49b03e98d026920bdd98d009a05d09bd9f0409307d22e3b62f38d3b73edeae69a7f6d36c400bdfb63ded1ac6e437c495d3644d2a176f4e623bad99c87f8e83c78f0e0f1c783c7af87f1e0f11ed6b41d3b76fc72984677ec386742768c461886612eb0827387e988c2a6b728095dfab2c16ae689c83aeb503bba9d36f7d125dddcb38f9e1a988c519c0d2491b0933221add1edeb0673993bcee53207c4cb20e249f30d0be7391ce739e0e0e0e05838d7a10307e73d7c1c1c9c89536f8cf038761e187691488e0b1bbab9be39b5e3d87760df0db201699c6f37e75fd990f71bd7389fa4a70609c3300ca33a6276c494c3753c87ebd858878cc148510a49870e4c870e1d9464e9f8cb8e985a47bd314273d2694efa854399c0c63909ca60f20b8c442291dec3a39615e9a47a63a4e6a3d760c7a1b39d15dec172e86832d4926ed419f019753dc2b06d1d14c835f60c732d8f43b7dd2fec37d4e63dcf866ed750ca0c2ec1d350e90597e073a0310a75c676b2e876ea2d4a31526aacde1899f9e83375bac125f851cdf17aa3ce68f8e90675860c7554346eca0cde71f28b18eaa868aea517bc33daa29486bf5e17de8181b1ac67434a6d75e11deb9099b558e74c88752683779cd3344dfb663dac5def619be3a2b5bf4c887647b7618e985aab4c0697e09d95abaa5c75585da177b487ddd76171dd0552fa476f11152e2f80d23cbd452ed8f2822dbdd35bf4022a54b650f9a2a970d13a7a8ba83ce916b0d18233fac281517473d7856d36eb76ea794737addd2985721f8a395a4ff44296cf7201e6fc7561147eba0bc3aaeaf1f237eedb437d63645e9ec8acdb53a3e1dde6ea766af7a1768f0671bbcfec88e9fae8d7473546e112bc0e7ff516c47dcd6c48a9af6f26e472f22ebbe392ab41dc236c5449db6cf98c3229927dbe088e675f2645e6615e44fb7c5637791c75b9943dab1b6c98ba01b9ce3eebb65c92d7468e66749b448112d0a3edeb3ddcd7951d31f5556f8c58af6ed5d7854bf0d5ad2abc531d5a69f86dbed1f0157556b8047fd930d1809830d15ca5be54b49ff9ededa9c15a6c1c15cd1d7e56f70f681da4b5ac57a0dce7c45f5871420c887c84609722bcc0083e5084e034e493732b80f85401c4278b7b8cc23b4f0dde715da5b7b7e7a5594bc373193abc2e3ab0164e800e9be3a2e11d96ca6454c70527a03a2abdbdcd8687bc73237f05ca7de8b7e19d15deb991b5088d9edb2580d2b81edc23b5591edc633684d6ef302ba29efe65459c9eb307ac8f0e557b2677973d607d6a9dcfe468dd6772b7acf4ea20048774700f5e48fb9b2e3cea2d5a8117bd44546fd10aa6f416ade0c96d9c0b34ee2d9af246efb58ba933dd2094fb501845d51375218ada96a2d4977bdf82b8df9052bf5f99ef61a69b09894938a34bfd4e0dcf745228f799d884721f7959379880594ff342d4970bc0f08d66ba9f747d363bf29a5f811e45970bb06dd1942e3a889badf0cef61153bfeaac7005a2dab141054f36a0c18d30189764dee3d985140af4817fb408cd077ef77d5b2bf43b1320fd2a10a9afc067d2c2e51ca5d980b677bf96c2ceb091131ca4acb10db2bbbbe7f3deed570893a5cbccc5f5741a3efe674fda1ffc6deb687f30fcc5eecc51f47289efaaa6039d9b72c1daf1d279d1fc7937c49dcf775903985d038cf0e7af7554685b94022b3dfabcc32e87bd4c08567fae67b7e8c627d54947bf46f4a60824226ad261bf5ed247949bf41e1e51d717ff073b09ab91bacbd6c18c2ac625f98dff336f7d9bdfc75f30dbaa8fb2067cb346a3f7f0053322eef357c45c36e77c0fbfcc88f5f933da1fefa4fdf1881aee8f87010d1245d1a46fb037d8a3932813a0f54244d441385a7b10d6dab1c7244836c6047b906ced9b1941d2a4738fae51201e4d3a468114e030128954777918d10b624c37ebd8afcd8830f621b5497759111783a1289a5499e97646378cba5f74ffe3be3de94618aa6a5c92b5761c39d9c6b5010df616a5c0051bd0626f510aa6ecfed021cbb20cfbf52ccbb2ec9b93bde1baaeebbaaeebfae65c6c545555cd53afaaaaaabe399519a85f4ba76897b116295a864bfca5b3fd41319d5ca2eea82f75337033d0e0df21ef6c53cf3688103e451ddb20d437870ac3b527fd52c21b2da30438b40cac934b24ea9ac83690fb92ed0f12b63f46cf8efdb25e7d9eb27131d0e4e3369b74d2b10dc2273ddb20dba433cd2189e18a7fac6f976c23a0ba9c966c1acd503861b5f5aa6e423dbfd990eadbec1f25565b758935eb06bf4de643c8fc1ea6cef4820e722932ff1262648b9aebd6d3fcabc2d6c1c06f516f9114337a5f84e60ab91429bac196745382357cac33b8e2a3c63bb308fcc558a4102aa1f1259f864bbb17c4e4abf73c476568e205a913d7913a712da3754865fd9197b06e410d37ace3af34fafa6beeeba6c83e414a5f8f0dbbf455d9f510e3b9147fade37da3753dc407a912ccc99f8a02694b46aa84e62ec3258b52afe8bccb8066e3ec8ffdac5b619d5ce21d58b1472116ae2211c6d8c370be8721753d2f4f9d332193d2685774e2e023c3fa03ff3e87807f93427a48dd3d1ece393f2b7ccf03c29a214b860cf98e0f492bf328f5cb6544f8d42fc65c46dd5136458e68f8bacbc31c123f65a5e846fd077efe827f4ef091237abe8737d770d65d1e6036241ed6f99ee74eedc6774c609dd24532bce8b2f7cb658f43d8e7da86da9d54445e3aec11bb85bdba6551195a49eac4b513d7187649b72f5eb70c47ae7e7f488ad51ff94ae39deb14c9f5b599908d301a97ac0369ec2e13e2ea36d48e6eae7fe4ab6f10d714fdfed86bbc03a4f717d5b8b4b32923f2d545b2b0bb0c8979abd270698f5599e5d2baaa6e435d1da38edebde76174b35e5975836dfdbaa38f37d7d8372186c6e85d1eb0ba591408b3e8b67b15057afdbe59b4a731106cea92748b779ff4c65520d75405405f0e9bf4475314874bfbf8cddc2917e1e0be391b907ebf9c83ff1128ebf6969f5cb7a0d7cba5ea32bc63fd9dc6baaeba5daf7e51f43af5f7eba2ae2fec2e835caa7ed16d8c5e8ed7bd91dba6e872a944c3030e8ed6ae6ef2cf3949b72b3a15408362bb2ac32596757b826eb3776e8429e570e99dfab9f47ade524837d740eecb972f5f5ad2a0d77b472797b6625ce26f16abc6253e26f39c12ef6c3f1cde79bff3b0415e0f592e46b934a0ed65b48b8c7552685b34a54a5bed7e3d77416c3788236a1d682e038244719f200488e572a1c29868546958d83a205c17347acf8e677df69603025e98b5418258bdae876df6f2486702c2ada18dde867a63347a739fd911a1e6ea84bb034c5cbb5732967661d8cc9cc0d1b6452aa8d240a4767f276d908543bf4b07c4f6dbb837d9ef5426e4039a3bc5a7b22292c8fd5aca39e7e6730eb2734e9646eed4936b770847d4af6e8ea8373ba235fca34e60c3f7ddb74bb71fddfdb1775b5c51ca11da4fbc4e6f71fb57a4db49aeec87360c77dfadb23ea9e2878f3c94181f7778178577641adec6070fb44d49c703e94da6b705c303d6e789bc8bc2257823b4cd450143c39ff6da33b551058c2dc0f0820b11918fbcfb25e9fa384a047160821948218321f83ca9e2878f3b94181f479f54c13ed4a124c0c75d522809f0396529c2fd44c433c1bb2da72c455097f5d4f34cf0543d652942ded5d39f095e5620fb4cdbec6d8b8adcb7ddb26bb0620957cc80851b74410a6f781f3ad0d6676805650c410b2d8a70060fd6f0599ffd06a4881311f9b828bca3d3f04e0a33cce2b2c033e5904246e3f41649c1d2419800196a78f78bc47f5911ab81de7b146a18d3e85781a8cb6f42bdbdbb537589ac3f4aea92cb6fb06ea77e8f541e1ed927486959afcc78287f946c1a2d1f97ec13a474fcc60d655db269745c828027451deb1528847ab082117ab08211eaf644bf3bbadfcc023d58c108dba8e123bd0285d0a6e1537d81de972f7d854847096830c50aef6045075a88a0139b465f21eac10a4620920878523489f609522c29360d9faa126d1a3efb0d6bf85d20767bd8b6c8045c3ac8f515a2992dec608a311011f96c85426856a23dacdb7b7573a25f11305dd8420de2f0648898e6f7b0100f459636b8de3767db2dcdbf1e75bd4525f84143aa6e434d5d0f7b450ce407ded934de79e76b8f679e7f00206634df07de71fd32211ad0e2e179670302e407e639111111910fbf8779eec30f827766f31bb0415ef3714a5046f3657278c775f5e54203b92fbd1dd12f1bb268f4fbf530a8056251080d7f454c664280d0f017854930967518bd9336c8abbb3f5c45a23d4b6f47974bbb0e46568c4bb1d270c995e08b8655864bcef56ee9d54188872443225ceba2d0b6a8045c74ec2d8a42462f0e2b0ec23aebe63c105c43f61262bfa418a3acaeb7b8f8ed04b4cd84186a08abfbe5e810d7d351d743cee900048ed4f717d30dde3dd21b23113e521445b9531445d50d9ee3292a9d8cb16eefb0fe9020ca18a59452ca28e5a57ba43faefa18e30848ac4879d7ee396bc050ffec7ee3bbd51c11ae27bfcdc74bfa332fffe88d91fa33ffbefd3049df1dbd58ca5f525e0f73d48362bb5f90dad9e4f9ee30de0971ebea36cfd5496cea3d7c45cc51ea91727c747b9ff5279e4f49f9d6703948b77878476f8cb8cbcbfa13efbe7d316502a4a3e58362f365847818c63b40543be196e72fefcc1fea70d6cd9d3a5fe31dd77c97096199e6acc81611111135d72d9eaa4e62cb5f117bd49da9fc6644e0d9b9babdc7fa03393a9612c54a6f1109cce88dd4f000e82d22411af0f192f42d97dc726983cd97741b6aaed326c65f314621ef3d1ca9ebcb6136778f94c28c88ebf81e9ebc7333c4757cacb37f7c8c112b9050b383f54733c2fea0610dfb22c1e8de6194e7df701dc27f7ff22efee7c94fdee19658f7700f0f39e7b45c3b01ed2245f1023660c8d5c92578f88b440448a8e1e1ddd735ac1bb896afed8517c63b3746de972ffdfe13c4dd012756bfbb285a473f17c5b5bcf3ee030ced9d0fdfe12f485dbb0cbef71e76bdd97b0f27b9de975e9bb36d51bf93a0ca7b0fc801fa3d5efd7e001c1244e907bfcddef870f2ce0f7f7e994763d3e3c3bacddeb47e5f08b35c82875c72dfb43ec4dc36db39f7222cb8f0389f3de632fe6643f8b04e8c77788b98390957b0b1ee04da1699604b3ba2fee66c8ea8f9178fc3961ea1fdbc6f6f8e887b8b4c5046bb76e7cc8823eaad9b23eaf76b3321f04b9dc0de435a44ebfda3db50bfef06b9296235fcb656687826401a562052efb7fdd2db3bbfe7d1656ca9cb427377bd5c80bdcb80deef9b0169fd2a0e97dc7bd811b114cc6878c9b3641011f9ec892ec83bf2fb297a7fed0619fdba86b08fe8ee0fac6ed781b8e5afba715f77c7e826d498ebd1d644a8afc35bdf0d527db43f4675bbfe9ebda2bb3fb2ba617fdb8e02515fbe7c8943c363741b1ae21abe872fba6dd11f589db8962fc22dff3223f05cb7a1e66f5f777473edfeb221f2a35ff4a6486f9208f6dd1fee3daf87310ae4bef4fc88de6444a85775735ddda29b6beb97a49b6bf9ec303b022fbf9910ec3d7cadc342dba22bd26878f96b2fc6362392fdaa1becebb193d9a38faacb8ac096bfaecd46751bead131ea6436f68b6eaeafef06c1ea865d4990defd71fd7acfbbe845df371b55a0d719dde4df3733022f970c2229fa222def5cdf43deb1be771911f7a5b1bafb63bb2a90fbd2d7e3afbacb43f52c77a2fd114fd14d0e554e3eb9fe78a820fd5fea54cc5cfe079f8a9a8f1e80772aae70a7790fdfc743454d3de1f8b339153c7e7d3f1f0f1540a8c8e135dfd33c1e2a66ea69e624d26b604ea29ff969a69e60725ef3d3ccb1934ec5aba739afd59376eafdf4ea69d62bdc4ff33b0ef3ec7bedf15061d59375e65f35f5544fe9709d33f59a9ff835f514e3b57ab2aefd54f3eb54c0dbd4938ccc71d4d37b554fd5e9f513fd550315ddb0aeae51c8a6ead75d36847af5cbade337c2dec318bd343ad9e46e514d86a2388fd622100bd7c3fbbba4db504b7808f45ae80d659c9de10b5dda476fd117c4e88bb4f0bab05b14420c62188659e7209656851083c7ac28ef143b2c6e61241289e44624128659cfb2ba5cc2ac03bd265da87fb05fd7b2acd6cd310cc9b96ac1bb91d36a1037a96e4d86b6e532a2cba5ac5ae75f565dad6eee4d6289052f44716ee383b8e525e51b5d30a30b64f4007a8bba2045d6b7f26b8483b272bfcd91ba7cbd35664386a07b757b2defa8f0142951dad520eead46f00e94cd49a9402ffb7eb3986519a5f131469c357b863ddec607183fb26eae3734784897d7aa1cd520ee0b0d1256835e5f751bb2aa59a70c9631e5822e107139a32f12e44728da69699dde222e5ca434017a8bb880465f7bc1de5cdde0d24deba1933bb537c29ddad78ffe009d9afaf6f26156e435a4eae61a7ebfb9ba7c9909a18450df4ca3e112cc3779fe765d3b7518ba6ca2b2635fdeb13eba46974d5bd023d1e5d2a8cebac5ab6e56bcac3fafbe334ffe71978f97b1625cc2feea88e30e28ea9377a6ab2c59b722f0f2f2ce0879aabac8f33023423dc2530e8b4fea030c6de3c32dde825cb2ea16f42aba5c8a7cce8850ff99959275d6c31bd1c35cd7726f0c2786247d3f5d5394bb6bea18801991f907b7aba6b6a9bb28bc539dfacb84506234acfb1c2e49798cf2c02579ce84505586d2388a734d08215c08a957d5e8148418a4a81105435130108632320f198e6064240527cf5314ab1b2472690d27f55965888ad0949eee1856a5380a56f3eecedd49e19d577d7352da7d7353da61c05594bba03b452b633fd0369910c0cb639a9cd829ba69fdf8f4c4a4d21b3c4551231c1407843b7527a5ab1ea6b22358ddd845e90d7ec3f10ae655fdc8c5d49f79ead5371c7594bdaaea06ab6f5684fa84a9233827e5a0b81ea8bf8cc87ce50eab115ca22e8d5983d162ae8d60707c145361ace380d17e552e4ac3a64ea2aaaa5edfa86f41afb153dfeaaa3fd6e7ab6fd7af0ad3e83c896e63e18098f390775e5bf318e01dfe1ccd5f6ef4cdac096444d9b8a813192df12082d831b60d10364a4ef020028d8e4ec2c2f530ba45b710b4259bfa88fee00690201f4260610e0b07046cea93c27ed4ddb65e9afab6689c4bd42faa844bd4b19ca62e1d160e8859b711f4f20e0f318fd5caaab36e8b46539675231c16948b6255b33a2c5c0f94ac3f4066ac70e149635d6d218d2d88d1f35cfae1cddf80f4ee7bd478e7a9ba1599a74edd6547e6a9f88afa05a90f3034ea2f1e6644aa5ff5873afcabe2ff788878263e9067e297686898fa9bdfac58e7dfe3392332ff5355971dc17a9e88eb7c20d5f9aad42d1a9a0a4855b7779e548d3d0ce9e31291a005290da4b7080d2eada561a361850b6574017a8bb830a51d978e553b771a1a588577dca5944e1e6644f8d6b7270b2015de9942a4168b422ebdba177507f2789c142748cf59a1ae8ba755b7454ab6313b6954379aabbeeab870d7c5efbdb7d77bf273ca77d51f9e90050e88d1a524bd27df7ba4bff7e47bef91e69f7cefd54dbef7de2b6481a4ce8a7c4f4e2832bea454654ae4dab47e2ccf5886b810b2d27c28058a96a76a95e607fba240d49737e6835578e7ba93771bacd2ee1bb4d2ee8e9d73ceb9b773ceb9731acc0384b9f00e75c7aec696d461f942db9c952d04fd6878475e12db8c88a32ee9a6f5e3d3135b5073c1455e5b433d1d6c79288577a63820dcb9c22aaf02bdde5c5b47a8ba412abdbdbf6f93a9f43bd6f3707e7e9b6ff2e5a7bb1a3c409e297ee3347a7e79879afc37e733f53c53fc3c5bcc7f5991d853bec329ae07f9e72cba69bd392b9b33a3e3ab500a972490c7b33d8f47bb80587f45db1649acd2ee32224f107d1fcf11dae371626ef3d54deb4ddb68b86e34bd691d7f695a36d22ccbb2b2ecd64833523dab97f64ba3f2189dd516d8e8eb070d5269d8178569d43a56551b7b69cbb22c6bbb6559d637bbaccb0718daf68aacea9b2baa2a2cb149a3ac8a4545b129fabcf8c28a08c4687952182d3ff2a2e5332c2d5f69343b0659e07ac85ed12d045dfd925bb2caa5e5dd29fa8ac8280a4119676cd1d2f2d7c48e783c4e8a138fc75979a6f8a871217da3197dbd3c53f66dc296dbf3a2a50f30b4cd59690a9ba2cf0a97e46176046beb4ecae3795cb0c9cae371446b928f56dd46d077af8b96d7a450697952dd68a4b4fca86eda3e07a5e5b36aa5e32f0cc328e4927cdf984bf337eed25ce52fd85775fe1604dbe2d2d2c6c7185a50ecabfe54a73ebf05c5b6e6d4b48da6e77779764d3c0475ab6e41b1ab3a7f317c3cda46d0d55d562476752805b2c001b1421ca452d1398b5c4d55c802d7837498d361c3b5391b900d321f4ff5628c91eb3674ca125f7cd72902d16d107ec091176311ab630fcf7d26f848e13913c2142d723952c7338dbfa4db20af5748137ef5aba27926788b6acf04b586a73221f1d3ba29b20d6dd17c6983e6cae42d3fd472af0a83220b5fc87a65244b1577a06dd11965f41b1201eb5585a2ba7509ac4f55b3f023cd206085027cac5a7a26f82b3bf2ce752362de0a05f854af6ebd49b3bc5f9c1581698c69bccc8ac0f4fb55614cb5213717c845bafb037ba34d021daf11e6f83d8c81de2ed00ff2dcc1b5ce0bcdba7648736c05335c0e34ced25b6406199aadf4164981095dea2d8a821b8a044de1a5b72131646f1f4041127a492e07da33426fd113c26847466f51196c340fbd456554711911cb095c1ae8082f5a3490d0a8b7202f6c81d45b10962d6264d05bd00c647079a1b720127821f6d6e40a3ee83d0280206ce9ad49185b7a2711d1977ef3950bc835f52b9ea24d5c53aefef0dd1635e18ddeb8e3993271fb233e6836d07ee9ec969571ddfd112d47f717dde4a32c232b234a67b77e456b7f0c355b7f5936641ebba3d82d9a9d3323d59ddbb8bafde16e1da31b5f4e6603bd3810f57ed2cdf510e5580dd7fcdcf80b9b1fbbbabde7019dda7d3a77e70e8413c59776c214e41253ce01b9375a1e5277a2b5124814456f7c20ea8d28beb493d9fc8a6e51fefdbaab4cb18db9f281e61329a268264e645777427593a1aeea72c9c1f3064479e9f820067a2deb4d1157d4ef5c3927dcf017f3f2c0afde976e15e59899eb567defaa53cffac3df5375c89e5f0436ac3fefd59f3defdd9d1cc3e5ae68ebdc90e3035c0e8bc687bfa2b9241460b3e38532e02ce00703e237c080f8cd38b0803e420878b8102d03621d638afdf2e58df1ea3e530d34ec0160efb8e47e622137e49ebb428c86b7a4e8a2e12b2ce0e512a2b4fca56509551afecabe97a1c1794e8977e4e16155617f18f6896140a7c6ae6f5fa4ebe7c608f5788aa25ed56c888bb246d4e7afcafa7665d521f3d52bec93ba96ce7af5e39ab2ac2a3b026fd59ff80dbec950cbcfec087c85fd471e9eaa9b3c56ab0f75a4d92d3ad4d75063242ae976fd55f4273bac4ea24f9e3c79427a15bb0270dcf2cea78a5d016caf65753e3d6f7d2ebabd0d613e88a0657f2f2b725dd62dfb556fae53cfeacd55e725b54e4dea670ef12635ab7a5592de547548fcf5ab36f6ebc7fa268f5c75f74705725faabfaba5ebedb88f677da2e8ed3d101f7e3800ef6c0bb9218baf375c1ad0dc977f91dc16dab6731811177be977e612024d364c6f54bf3797f6c280e5b25bfe8406617a232d979e59d4f08f26a16dd30b1168b0b749d4f0efd38b59a480072175548cf2ef548c51529cf8a4657a9826c2cb8ac3a55817c20799cbb8dce7dc20ef425cb53ff62e23e2e09b6e6e90b7e1f4bb7bf34ffec9bfff30e5fb7bf2ef5d526a21da28efe46fe05d73dfc02ff2913a59e15cc2c5871b84dde10671d5b9ba40ee0bc7fdb1707fec14d73e1e6e10f97ddc20ae7de052bc1b1a0897e2377b1bb605d61bd691c77afc0136c8f7c77cfc0fa6f91ce6996c9adb8d0f66f0c8b0693efe6683b88e574571b834ffd8d0c371fedd4d25b4d1f337d40d457d8b94e0a5b748096bb40c755d551c2e4d8ac6fdb13f5cbf1a5c1b61b40fb1daf1dbc743ec8e5ccb646bb42ebff4a24386ccff4cacb748096a74dc1f923fbf946fd1c9a7a8ef7b78eb46fdfac55bb75f8c5dd4ea573b04f6adeeba2a5093a1cebe81e00d70f8d24d760b9629587456afbaed6e4fb46504abdb136d7d7faaefd6ea7a8eb32156effee87910a3a833d556fda1ce1b457dd26d6a2e1cba1cccc12581211f3e14ef54bc6a85027cf82cc4ee2b0008757a6bd771ab8a58f1cadc2f74ac57269d8f74023d58a1085f7cf6305e8142689f20c56756a27d8294edd4b1eaa7d397db2748f1d9ebf4e63c285c3b7256fad5c5c2fa15b19115318ba5fc9cb7ac596a6b52778b62979492bf2c1aaba43b633624bbf547e7317abde7cdeb57108fe8e682b879beaaeeaa754767dde072c90d916fdbd1ec55f557b7eb7a947a474afdea167f69efe187d5edd498f5b4e50468f52444fa69b900efdadf6176a4d46eefe8758cf235ba3e249a05c3e872a9ca8648eb179def79d63c64ba05b1cb8e983a461f3cd09c955e6705636982a1a68282b8dd1fdd4e4c9cccbf07c14bce8e947a13eab83d01c4e34894420952f436147f458c8a2fea7414f2aa9359291aff32233afd2a69237c91b703e2d1eed56da8e325dd4e2dcf99119dde9edcf8ef711fc42f3312db226af717866b4fe4d5cd7516c4f62e0ffbc4fa932cef5745b788f9626cb9640d80d991b73e7cc56521864ed0f30890f5e2809e9e24de5d17479b9094207a7a06d1436449030915c1780831b445cf2143561ae808c7cc3c1a6d42562675d4c4ac59d1f5b92acca2eb53d1f5b9ac4da88fe08596d72a7aa222f0afbe0ce88927524441d44ceaf6383b02815eff3caa7e80a683685abebbf7e30e74eaeddd18d942d0fcedd806100d68fb08dd66ee172fc5b8b4e7a2018d279fa93355f9348fe74916c7c798e7f9f0952046330d97f6586fd111cc00c256bcf78e5046bfcbbc0a1c00bd4547d0a297f41e841072e9412ec011aabcb5829023b41f081ffcf5a8135a046e3c1d23849c0979408f3fc8b20435a898d0006264c7fd3db42bb5bb0cf3b822840164d48222443142180e7ed5681e028de6bfa2379aff487473d6abc37878981de1bf430a81b4f56e39eb45eb597fd8b177cbfabba893d904e6a198158a4b622f333231974d605aa00ae7330f4515ce47ca43218d9887621a314f41f7aa5c65f4fb49664562f4fb8bb72e610e7349a798978f54c8f289df0d425d5621cb477e73471e1150aac7489d4f45b30029c05da159af827a96eb44964f3c3ce9db455df475d125565fc768116b74980d71c7817da33f4aae63cffeb22174c9f5ec38e836d430ee24d2358a7db306b84a721fd19b2b1b838d31a28c9146e7f4168d5134861737a32c93d9cd35a2375716332a3392bd88a883d888d5efb24e31eb69c8c7aa5550773e31a357a05058bdded3b19e643cf54a56a1182f7f129a9715ca7de4a1dca7aa4233c6c753f25df1371714d2e6497b22d69beb31deb97a73c53a64a53913f2ee629c42926717aecdb9987421237cb409641792611cd971e0a87926bf9911d971506c7e6fc4090a9f1d8a601f0d761150e470ec5068cfe19b1591c3b17a1a5d8bdfcc08ed39bc87d5e877370434fa7db198155173986334fb884251f3d16366019b6797b4889ac3d4d3e832d5af883de7a791cdb37aaa3936a2eb4343a1c06ef31c0a85cd6b2e43a13861af7945b7a1de1c14da160da18cbe225d6215b13a46ec32bf2e9759607499932814a3930e434fa3c35ca3a7d1b56f66811a197a223de7181deaebd62f0a8b9b59e004f3ec27d269be591135b7a927ed190c85c2e650d443417f128282c7715ce627ed3027d1d37fb2a93fd5d3d350284e27ecd94f390e534f343fcd3ce6eb9343a1389db26b279cef38fbc4ac88d1611e3323b4d7d4d3900fe939d093f321d5488b18c5c35428b4c3fca2b0974d00bb4d8522fbe854266b113587023be938e829fba80a4191c3738cc0ea1436c74eaa42960fcc332a64f968bb4162dd80f41528a36355c8f2c12e0f4349d768f69e9751e73301ad9eb067b58ad1b3c8faa324beba3ccc7bf8aa1b90ae6ed58d2bbae4b9296c0e534fce67870ac5e9e6271d3fe17cc7733e29758c4e61f39ce7404f34cfe1972c8195de27489125b0d2f2b1a110bee88d42bb29b42d628297de681a1e7ed3e099c0650965f410c418429416e30d31ca20c217437883086f10810c31c410c38a185284e045085c8440465f1816e90d558d542defda6aaa4b6f1a5497969b464b043c29825ec842d4f254cbbe4658a437f114bd89b5c843a30c53885abeead99b15e9c10a4620c2b222087852d4f2095246cfa927e773d2a166a75a85b22af313ad50ee43aa423c8efd558847155014e043c5900fe9a4cbbc871dbd41bbefa050ee03731d14870a6597f94d15d22ea3695528ab50eea31dbb0d3d0df9d40029a07fb6753d6c5be4059676ce878642b9cf699e7e82721fa1d1b1c33c077a9aa942a33a0549e6a71c5508c7652a94fb648fa950ee035385705428f7191dab5390ea3ed3b6455e58695783e046a15df580f684dee2152f5ac96ee90f59e92a8300f6ecdae3c92a94eca35a4556a75080cf681fcfe1670681ec30d71e0f4c8532ba56ab18d52cf2d9e1a9ac01432d63fd91c0900f897d468742da3c392222ed892dba891fbd8afb8c2ebf39d1b2dec45a84e6be1dd18f27ab5064c70e3f1d10ae5e6616385176ec2f2b02fba89e46c74619663334591b835b374583bfd649d1b6c88b282dcf9308cc8823609634601bf174f0e4757c0948c7d512448c2722a7638f8e37a2b443c71f613d7e89c7f3248b55a34fcc01191def03efc48ee78177e24b977befafcab73fde3ef95dca044801cdfbe33d48f6bacee11d20f7a5dd2365b20f92469000f168f7a5400a68b73f9eabbb3ceccb86c48c88bbcb84a0e83682963f410185d0147aefbc55ddde7f66dde410792ec27583a2dfb765dd5cbff71e66ea7edea13742f09fcb8cbcbb3f2334bc433d1e075edeb5a4eb4ed12dca22464b1a5be39d95757370681987e561efbe2c0f7b29c4d018cd44cbc3be8765118e825fe4e9866b5d151afc6acf69ea411001b354814dfdf1736e5a6f6f4a7edb339afa11bcb3fdd0d4a9ff40d314f51e2a88c7f324cbacd495609ee743dd882e4d1d03bcf396f2c2e5a0296e9a2e9a5a8d77dea9cbf00e3c45ad9767c5b5ee8906ffb6a88b2abdcd8e5fc75a1751f8d1d0e04ce6813eeff0f5e3bfe36c90ed771d1bc4fd3d085c4bbbd0f6ee7c1c0784e3efd37abf696d648bc3419476ce7d73dcce0db8d8c00cf88da6637c3c90a943c7ffa075ac1d9fc33cec134f83c58d8ec7d16478e73dc6f82d12c19546efd05b84458c7e5503ac5715880bc07c0f0769d62b8b3a289a556f8a301151bf0b111175e58391f8aa6e39da096c25d56f82d8aeaaf5d841ddc317df7510e03ec6fe1aab40ee4b76d7d77565234cc38103064b170c731c9aa67146048e5f10c7af8883846374186d34228d7ebd2138c9541497f5e51decf37b6cd2e944ab3e2dd7ec3a081a95e192751068d5e7b353dfb4ac01d7376bc0557b5c00ac0370c93a457fe092f5eb87c7c3003659b77e68eb87b6be1903b864d1b4f56dc968ebd7c320efdcbc63e1a2b708cb959e9fbc73f32a9010db41b87eed7ac239e7af65b7d95f6715c87d19dd35866123d25537a1be3e800d72ddb2fec7e37cac971e4fccad03714060b77ec550e78361c732a503d05b74258d36e30a1910c3e886fd26bb703c86de6430f426838181191131bf2476c5fca2b02b468bc101536f3012490b144b1a2e51929d12b4ed4d513fef6c323cf00ef59c920c751c29a59414109ea2ba24a0111f199a0c84103a2ef009da26d35006f283d7608c31c226382b681bd6525e5e93a1c1b9dc4b1f605077ee55986fca577f1e9fb27ec5aa386c9a385c9a308a39e79c734eaeaa0ac8962b461a1a24685b4ece83ec1c33f3813837b8b4f185196d70c1861b7df1b27b232920e0daa2f1785a92a082b6cd7ef32111df7382b6c9748cc7a1d178071e0a7786b6bde60de2fe736571df1cc74697bee92d62a38cde6c32415b2794a14a6fb357c8b504cd7d896cbb76750a9afb0ae07a18971e14b0fe66ae091aece1236b44514395be489bc3cec60718da4d11f82dd69b22f1f5ca95592511e8a2e0043374e90d6a4e8c6b3906da16b12106b3f1850cb42d5ae38d866ccc40dba2358a7a8d301a16c16b5cdacb6ceb311f44d0e2619d5c7a6c826b73b648d4ef970f17687c771e98e749162cfb3bcd658d1e7b7f3546af909e915ed49b63a39fd67323f2ce4e9db30a9f18eb04fb2b2ae38c34bcbc37a04cb1d24518646c41a30b6483449f6499f04f0cde817ffc99865f10e36c08eab06eb51f16b119b3392b9809c1ea6e0fdb65848d4031ff3e37334219795ff33ac44699911746dd6438d38ab42706ef5cf585c1253544697e1ad4687ecc8a68314688b9ccaa40ce48ac4f0c5e76551ccd5714c8f1ee1b6d50f4acaf687fbcd347c62bc2fe78672adaf6bee8f7febe7861f08e189108e3f9dd20bbf4bd1f5c3b00cdb5bb30e75c334481f6367877be0f688664b0062edcb0862f5851042fc028aec0862b98210c2dbac023450b3229b8d081a2284a0a28a44745f186a6b2a61e95f516a50107d28bab372c0a2f5714695404e82df2e2861731c480d45be4e50c2019ce0f349c8bafc9c46845ebd6377315886acbda660fcdec930981c77e5dd7e2854a933ea22e1332faf53221977522d92d5859da377b151deaa791ee28e93d8f44e34714c3e816f4e094f272517a1e10d54fa8ab49b7a514f7ead61f75b7de2b38efe87b9f936ebf37dfb32a10d5b392af287509abc6a51f08498e5a8308da666d179d6d51ac2baaf519bca081e8cb177d456cbf704f51a6f9fcacdbcc693ed43dec054afbd05b8406297d51981c62d69b2235cfe1351f6ac99910995f4ee6f866947c3828e932745455cf81ced0e51266d1f8186a553c1af4a9aaaafac55845b7a0d707e012bc0f5c82323ed036996df7927678add4f0380eff18ba8d43866e432d23c35c6f88b8f3877ad20dceae28ba9d964b33c7505334883b078e4fba2d1a8de315dd9e681cef793214e312ac649806e7bc1373f81cdeb170d4ebbaae3b47b5bb36290f43e51f1522ab11a7dbb361e6a41a06e6f5758aba3f0af3cd845c9990ec3defd2641a9e444794c8b6bbc625868ebe86120e1999751de5b6e8ec8a627d5384fa7ea8a518da1432354d6e73c6615c5c7b5d14c5204d5b7f7f0f02fb861528c1085108020a78b0059fea9b41a00238c812c614607cd184377c2c9aaee87c26f949b5679243a88a09bd216687e58a2cf4f811ba2dc65f247704cd09378c7ce5226dfc6d067a5fde28c2443fd85dae086422cf2468b6db1ee083681ef38f40b725f8d87004cd45226c499d70f35b38da85ef8b0b1e6aa9a265b8b4157289e3e7fee0ef6d1c0db42d72634b6f58f3ab0db254741296fca0e407222cf96179c741e924f6cfa33ecfd5af49b131fede90a713d7ee5588f109aedd207cc2428a2074f185286198c2e7a500063a2863074668c2143e3bbd27c2881f5e108f67faecf7025b78d0acc9f1f4cc4a079a1c1c9ae3e4f470c931804b058003290b7556253f3445331d17b0447105175fc8421184f0a40554c8a8c1159420062014a0095a162a0ecd15ebf33d4cf378acda23af276f30420ac800842eaef8e28a1bc880069114a440c387f2618a2874a6c0a243dd94fcd0f30cd8a21e1e1a023495465358535278690ac687a2661dc2862ca0c002327421862e3ed4e7e319fa410d8a70250b3710010c67f0a12a0e4d457d00721c9af978727808a287e6a599de223350a137209329225c313cd892960788db13b19167e6b041b1e1354ca6084461344c6fd117327ad5d0349a1d2092fac0a52a8cea4655253fb4bc0fae08da06a481544ce39d01b43c0def6c11a45042cbe3f00ea9e5cf3b5b8428ccd0f239ccf37ce479601ee823bf35468c1c7ca1680b94275d7ce4e7e34940165a0085229ca0094bb0828fac5a1134ac447364348a7189864deeeeedcaa0fd0084799c8f23723eee383781b60dcd60a575e82d52030ebde5501c2324b2446680831e08410b21bb4160ef5d7c26990ce18e17223713128fb33f98470f1c6f30c50d48700315dc208be8de102e669ee87056004c9b7568be8e2dc006c168ce7000de89f1f2e71247aa637b6086949b864bfcf800cddfb09d6dcf20e2000ce09ded870630ffd0cc4260c9624d3b8389857bb0a685fc043eb1d2efd7328f93f24cefdbfce100381b647e7fb341aaef6b1608eb7b9a0d727defaa304f0f9bde31363d8d67eb61dba1978b7eaeea2c8ff3a2637918ca8b81c984605c7a0ce88981714ee6f02028a785a933608b7aa81a84d3e2b4382d4e8bd342513339b43315dea9c23bf231973339b41c6810378e071171e0f8e5b01edec1f177066c86831473970981b910d235da73368492b90cceb71f6ebe09c0e69ba961cd37daf035ef7074f8b8417e78e980e0f11d3abec3e1693608cd61ddb4226dc3b02a33c4fe78e7411de07a78df4189e0d2bb0eba0335824def34f18f47c7e3d48dc74ddd686c686a2a374d3d627fbc8f6803f6c7bbf302171b84298f33b4cd4929cab90ecfe1fb78ea67be8f873ec77b9e48ea5a0e15c20653e440b7a90315c2062458d3f60cad54081ba8604ddb39e846d3940a61832cd6b45d58ed9c092159b23993d0080c143264a0e4000d2b06ec8f87c5e06098da7384264c210b900134082e916a8feb82f60e8b30a68231957e672a3358d3e260f6c08a0d84c043bbbe1863ac6a598455a6e3488e4e8d7ebdd15f26044a39da884508588e80a50958a600e5ec7d7411eeee3322b0a9eaa8c8c843c32cac69bf30cba0ad41c3230496d2eec4431eba877b38527eb2dbd379793cebb33eef6dc15f74f707758bbaed817a45dffed81fd49d97c74362288f27ae89fafb6e4c44dd4bbfafcb9a3a947ef209971ed5d1088f765428133765b66b41bfbb156c0fef0c0fab93e2a2540785071a1a1146f0ce110d28827736e70506b6c7453f216cf085356d19e61adafb3e1e216cf086356d6f0e4bbf477ec25676f05d706dcee68a1afe7ae2c43c4344effca4fa00435b37ee88de84a24971547a082f537a8bd410a3b720de9c9ab6d1fca5d26e40e01034b84156882929959617566f82d8c64e641bfbfbd2307a9dab6ff6e4a3936a8cf13bd68d660c5681b6fdd0f0f03f3cf8e72a079510bf321b44a7e3371f7a7f6c3be808e2446bf91ccb339f295253730b84fc9ed9a2da6b78b73d703ba6dc8f72cfc793634dd0079d1f0dbf7d87863a1afe7a42dc1ff7c5ee923a99fd3edf9c1790c7b332cabae568f822741d8fb8caec0ff82dc15714dbb9704bf08fee96e0a6a49f8635c3e60a74ea577fe05264e2c3fe88dffd5180edc1b9baa50a1b847b2f64ef7679280ad904ff983c99af62fa50da0a209ea24f9e54719f5d026c393abe0a203ebb02d872e878982d40fc059b1d1ab48da6636512349b4990267764b814b5fd11ef28ccf610cf311dbfafe2407b3ff3d3e2da9c0d16f18ef59cefaf1c6afde22fb48db5b475f7caaaa8e51cdd6cde92185c1f2cd0ae79654228ecda20dcd14bac2a07ef68754889b857905ab884addf380a7c858edfe52e7484505eac7583521ed48246af844bf14b70295e864bd7a516e621624df5712b336ad5c23b6554f48c86f678d612cf654cdec23bd6e3d90c3e8377de671a9a1ada376683ca9814a16d3dc7a94c08bd454b6e1e5ea39beb3a6b0e97eaaf875d9863ec8d767fd45bb4c25cc65ab80c2e68d7e5b7edc25ab25aafbfe7a0db0e46e392abc2a54869bd92f37e6f10f1ce9b53d36868be6d976dbbe4d40d08e5814b8ee693e693e652664278fcda2d9dd171575b9e20a0b43b74c77e9709e1f1201c10d6e385d820d5e38dec4e7dccf9367976f8a605c1f15d1e6a4df3385ee9dbd2bc45777b98873c7a7e871c7a3e87d473e39e2e9b13328443de7056b0d4ee99a03c9a9206a14c086556e786063fe976dd7af54b5253871d74a8f55b6155555575e21ad66ddefd5cd2a29f5c85adc8508bea40390c2ec5ef40590c364548e78e237d1259ef8df7867b6f70291e4ee9f846c74328bc53cf93f3783805528155a015c805c402bb805e54080542815178474a55372598bb13d8f340a49e35876fdbe508e9b33a717ded310f7ae2c43bf07979a648dfa587775cddacf9431fe4badeaa3e60a0f530a5cf8d0a854dee0a1a759cebf83bc7f8a0277887e6f1a7270e0acee39d141f30d0b61f3afec75dfbcf73834bf141ae1f1b5785c52b71023e761b47d67b98720e652fd814b1748e6facc5119db8145fd127b814ffbc48ea6507dd5cefb0ea12f3ffcd6dbe04f30c111111114121222292c227de09def9e38376a7d633343df3dd996f26c4e63d7c0aa34c0aa554189cd58e7817c433c54f58b7a969b3f2a8100a97e271aee31b84d2f117d0fc12bc53931da1f9bccc8cd07c09dea979bc122736c876fccc8cd000557583f9b503e737d4a6862ebc92f319ba6939e8e69e74bc0cddb018bad1c4e3a04ca8de568b46835c93ea01b8141f441c552d99164c4b4e8ddfabbeb869eeed3eebf6523b201b1059e29d2d71c91d16f1e01da25c9bb3b197965fd7fb2874e2f1cc5ce6d8f7f1e4f8553343dd9ade73c0f4bb0cdd356d55bfe7638a76ed2547df2f84d1b814e1d9ca0661cacd64f0ced372f95881e6983a31a833e5bb217c776ec050337461f0cee3182c8ff64cf23436f80e5a9680d0f2ee69f2736ada3e203213c2bf98ba6e1cbf1e763deac470d13111133111136de9113d1e6c4def32501a87a5e45099167ce7183706c7b56f3cf43b323a8eeac4754c8d54b8f4fec3a72054c3c29c143240e991a384508cb953d4e3e2b870e1726de93d1ecd75f1d2a57e8e4b76511411eedd1faf6e4a72fa06c835df09ec7720526f407a74579d383e75ec5127854b7209dee1bab95a0497de596ea16da5898da813834bf2401e8f2b6293bc3c8386467b674a64fb55178616341fb8f40e844b0fe670632b64848165836cf3c85c9ebbf082bfe03078470c2ee21d35786703c265308fa3c297672dbc333a4d1a1a1a2d1d15335a5e8612f14cf2d488feb05bfa81cae339c09adecdf0627358968c7e98f3814bd4663eac681be4a246179b0b238cc7f324cb1695c14b57719f2c3f5a9ebb601ee723cf5678678bd4e0e29ad8e32a180bef54977fef3313c2af62605d848fd7e1385b91672e78677749773880b4ab18c85c3629366160be4564a0d2f22e0c34c4f052c43bdb6e2183773627a5b5f0cee6a6b095c7e3c433c96327693c8577328d5e9139896eb337b612e47a549590229d783c734deff2fb36ed34f2473c1e998a818a42221e55e5d9cb8871c870fddacb61bb9a36b701bcb3cd20ae1351841118601e9a67ba7e7d99268d921a7d01e96bd7ad40dba2314ce955a3b7a80b969e3d0f3748bd79c5e7386e94a39b22b2dd21552f8c4bd777c2c86f519729bd1da0af5f3f00ef5c6700eff0af07817d9b94169a79ac6e73ce6f9aab1b4d4f9abebed0369abece1911d7fbdda96d34db7966bbadeab65a82b8de00de79bf4e04efc05f3d7c9d86fa7519de99bfbe39173ba9c6b53f68d46111942ebd4555141555b1a59dd0254aaf13c2c0a501d0eb843078e9ebe1e4f41e5b9e5daa25111151143ef057cc84c04a6d571bed5cc56870769908d420a4e2da9ced796996424b185d1d3ae10b58ba3a9caf68bef48ab6bca2c7e3d26013cbba957a7b45b16eefed15d138359a5fbda2e65f1083991078ae81b6c166347a486cc7854b6e68548dd8ecadbee150df647836ac6fde1d9767e2bb1ed6b8bc330d5537ed2dfce5c6b041809c17782ebc43b9ae0ef92e0dd765836c73857189aa33c2e7452ae17259d0aa6b5614750a1312a9ea0e48c3be1648dba22e4fbafafb8218e41deaeb8870dce8f94da6292f3dbf613c4b3cd33c4545531490259e46c1cc51d42b99b92a5244d5e79d087ae2c43c3f3cd37cd1132d3d774bcf6f6e4a1fc1a5798a2ac1a5798e15035c9a5f08537d9399ff81fa55a7b6f2072c23f2ae7e5198ab7eeddb2e4b3ce979254ef00e1472053b0f8ea20e448679969f53e29d88d559d5d9f39b33eb8fb322db8052728114d1fe6017e17b0f3ee886b4bec1be30ecb22a3831ed979366689132caf7de838f0ddff9eafb7876a128a65172b9f4de63f898bfb0f1205c5e47064dd27507c0364af9227c6f578361808eb9896be805c207e183900c65805a3be62156a3352e31f3ab0bdf7bccec1c848f9d73ecb8bc4975d19232713d3fefe61fdd221214f5f528f547a1c486509fbb1007ca23c059f1a5a42ea5ac7642c806694b13d220b24db4fbe3c908218cd00dfc625c09537d837ceb70323cf50b5e105e867722df511f60682ec60b89d59ccb0c1ff365139c93526a5c7a6e9d73f03133919cedbb314a186a93e9278888647c80a1f177001a478e183d23c69510c208b17e0fbea88518296a2119ad7129aea4a08c10c208d57832422825453d1c1c33a0e47e11bef7b8259e5f85b082e9f7625c18e3e156ecbd0b7931beb7709f4cefb1dddd7dcb6a705c7250cab91366b9b4955ae3123c8452c6287abf59741242b8418a92f03d0fd23cb80630bcd1d782410d314ce96b5d09b42d12c393bed69940dba230b001618c736243a6dc0ba68cde7867a8e3658661311312376e8c77285969306a52aa6251e392a37a5ea4325c72bdc9f04c9ebc33041766e7e4928b3026e17a62613f1ec82622f2b0e3163f9d11b40d12d92d51b0a9bac5cfbb9f78c218efbe7cc964bd29c2b13ae186fc842066222222a2203682737cc7fcde61bf7a6304fe1dd6cdb9e8989d730e3e0add019aef200de22e0203858c1934bca44a0e000f1bd61b21b6f9eba47345088a2722220a8ace0d61b6921f1ac2bf2e82838b25a840c90f1deb8d11d8ee10d625502110dedd3d0cd2342e693d41f51c7251b834445da8f9b15aab3aeb9755d52346b8730e97e0a9ca43a43414874bf0929e4bf04c4b42d0a6c63c2727888888887caab88fc3c211c01d1e3bd5e8805eb35053db100b415b78485538345d44c116bad802942c40e98b342fe918f210a498c3c054390546d6cd3ab5382c0be60e0a4c8e53168efae31ef31e7651fe32223f328fb9d446d5cce3130c0c8cf59e6705e5701898ea38602ad0eb1ff999f7700e309f99c901c7ad8a43c2bcd24ea2913945a391bee5a839642ea91c2c98cf54eb389e832e9770583586c6d04d9309030ced477bec6d65aa5661de80228688c0c0114ae0b80cd54ea2d6294a8aa91b8eeaea8977acead5477f7702866e07f10ee9ef4f3822de71ee8914f7c441e11d47fa48a38e54ab3b3aca2eabaa8b7626e82df24295be18cb285c20fde45d1840240378c75527e5407ce01d6a7b82e09dec19a40e081b65c43dd20d0ced6dae37d7c3e71daa6e433439251e7827bb16dce86dbbf4bb13bcb3b92a41ccb33eef4bf0ce1669c14bbf2be11db759551d14d7037c0f6fce082e419849b84d190feb564579aa14ebccc1233158e9f716a1e1c5f2a38b02b1d165dd82de93429c33224a29448ad1f36026c419e1b088512ec83bb0de0807c508dec1c245e14cc83b725523b8b44dafc07c44af40b90fcc2fea17495ec18daee92d3a038dbe1e16adc02a8f0a4f89d549e1d27b8fb1ba285c7a16f6f81eabbf3d150f13f991f4ec9adb468f7fd407185a55835e67eef214f50186b6d522550ad64c5697d437ac52f2f22a52d59ff7d859e5ea2f3b12fbc9ead1fa90abaa1bd6526a14e6b2e75d0f939846379aae70bca294bc46717cb32157cdde38240543b567f4fa15a7b8a8142ad44a84a11c2539c614a2a1190d0248431460304820140b0724125514861f14800db0ba4a521d8ad32007520a21648c3144404000000440442421016d747caabf79b6e2f9215acdcec28f42b7b6b49587506530ba256ed8377e4f34254995fe253a84176ab659bb5169865ffe52cb7877633c3387cfd24d618da3cb7736473bdd004496f45eb6288a99e169e94d2325192b52352483481ada88a4fe87e8e14678352ffbf7215663587fa24acecacf854a63507655b7adabf8567e4c3ea388f32d70f26cf18c0ecd7de17628b175326de0ba356818ab7e4fe075ea483a53b655580b5fbf6da94d5ba86cf74251aff5a4f915c3dea156a29af9a426d41d4d7647495a909a763fb8d0a5a911772ec561f6b0d1b035846f314d52f6db0a16ada0633d070cee899eec892afae1c383d677741967d0135a38faa78de37490c56de708261d23f68edbdef61d310e27acbee8f3726f95049f7ec2d9141fb2f627df95bf1043853ea337771e6b087d3960e8acbdb4620aebdafd23c39d508294cf97acb0416818b680db66530fd290cc4e98e81066a8363716895b5ca03de82edc880e3ea0ce6b78617b9f567ce40689763505b0321664646111ceb8a57107f04f4303563981a789c8ae5217f25d6fe8f5bc352dd6774cedd0c9588b471fa8ac4ca6453c9848f057fbd0221caaf612094f30438c698f70dc6563fcaf87249dce84310baafc46b883694ac0bc310e9e0746d70613106b0a097905c8b0f5d5fa30d9ab42ee76055d2d6fa1829572d9850a54965db6f4efd5d983ff485b312ac04cf0710048305e1f0131574ed2fdef8c41b83c42dfb4924da07e38642be92ad34171c394e5eef045d87e19448b7e95db33f0f17b283f9deec9ddf019d1b5c8434286737060e05262abc698492d855e012367b43a0733dbc4817168e7a19b84ce0a58c66d248f6d743c782a4454f1487d18b0d87584f0caafded4a73e88a0ae8d225dc086180a21b7647d2d8aa0cb6a686423cac2fbd93d7b105b4221c30e1cad5b2152b0b77b46c36b4c4f1c927524425b0d1e13dd31f372560b9f7ffad726584b86eb3da780a1ccce3cdafc94b7723745c1d597ffb28ce5c85622a79c2db5697609f4a3db0cac4dd86cddce0bad803017197591bb81ce8e34fc521bf41412f80cfb8161e0c69f55d713a80701bfe90761472e68beb2688dc10e0e5cf5324389e1440f04ccc545abcfd153299ee833e658bc73c1b9a3ed367c78db0d058b74f0f258b8ece5a72c7ade62bbd3805a94b251ea846729edef10e53075149389093c15d465bf258b2ee7f7434047e90072fc17fe49120b561dc3caf0718b2f81f2cacfdf9c9b7269841b51a3c85021036a2ee7bc22fda6f19fc6de01ce4adef956df0c3fbd918e80e08843a6b3fcc644b528122ac3b2d7c2b46e39e4e51f7b7d725091c8db9f5a1405248ec358355b9f1b3480c1dc598efff8f81ad104173a9f63d680cee535d517e353f46f4c44ff96ca6bac81e48c9da4629ef853e31a69728964d319160e0ee38bd82d93c64082ebafd093571dfe1d93924d57527439dc5e8beec76454b851fd7eff260659a4d4c44372e3cca720e54cc15bf33adbdb050ec29937d0c2f695c31c9e9e5dd131d65121f94aaaeded14f004badb3687736b30dd2645624bde0da5d5cce3e163ac0abaa07403e74f37084670adb9d8c7523942d69adb10429111cd0bca35d30e6e8cca9b874ab7d5053e1bd0ceaa3b85d04860f71ac34c0a0b9d8d1a8f501e7ca49d50572c956d5a44bd6c4a5ce13e486aa7de8f9d966b79ff57709f1d52c79a016727f1d4745e883b03780e99a82c8e41fa38267817e36dcff788a104624dd238e876aee9e38aa53ca5dbc2ceee93dabee80484de40c92acaf806d50712ebfc3a2a0bec5849cbc7e65d5aeeaed448718a5e9d0e105dc0336eaa5a8acd0c19f72071e70434a54b15a047154c34ae522fe79206e66d957918dd830345093ce9f090d76cc1b90b6942fda32b80048e4e34f9661588552a72fb0a51c69f6d821a5645e17b443031647621145505a2bb07b4c9b3e17d58e261fa1238d149703b424d35a853e6df7124ee499477099ae5bad6f6aa2895a82830f2bf6e52bd838eb6c189f7c97e462042265eb7b0119443a928425c57612a3b7f8923a2f81aafde1fe1440a5a8f2bdaf3aaa803c39a03aa326256e2b852a6c2005760b46e0ba901c5035865b81e229c4e218ccfcd0424c472464d814fe6c66065b3ff79e2c24ec4dcb4e4da62c5cda5adbca41313c4866ceb1238239824725fa60a0eeca742d249e2b14c44973aa2ee6a36033f861a39bca65be695e7d7cd175d9b39ae774479d3b8521175b13277258df14d915e9146534631599231d6ac258f1433ab5b032da296dcff98ea1eeb3b2dd24953cb967764111f005df04e5874aea718f24536791620c388658db57b1486a0d5bdd9820122e9c292a9e081a34b916d0f22ea1a3189a581709506e3a4a1e0302a961ca29b5cba362a6498f3f0efd8539643dc175463a8512dd898bef3f4342f166cd763b1efecf7e2b33ff0302c2690fc75ab9c1e3b9f9af9e6e532bb03a6e30e0cd76f01ea6dbecf6e9687c5ba321d8bd06ff0db672450355c100b76fec956136d1b81e4337a79ea6dd205c6586f037d93ef42058a259f617e574703f2f19ce2237562c941d11c0243929f685c455a54a83f3a15f87cc4064fdd6702b54696dde0048579c8c89c6a699bfca4d064994c4317bdd2592c5e0a2b7ccab28611964a599e108b65c2f4b671fc7c3938badf049d0d37f160dabac2c10ad05575664fa9b714b2b17ae2cb9e7226edff4a930e7d000fd01cd140307abd64f49c75dde82e02558b27a7f41c7fce6a806504831011077ebe36930f730adafe7283fe387fab1617d6a35d5534920840ed7e237ebd188eef3fb2a4268ee754d81a81eed2b7123872f896d730f293fac77dd3fa1c6848bb4172f62bac5db3a522a3ca7b8db767810817bc1b0537a8cbe3dc7256e6063540a08ba0920b44cc2943f13922e35025fba821fc3ecab8959d59aa0301c8c75501f23e12380c4a2b85d100e06f231cfefbac44a8c7695cfac7a1955337493c8d7dcb8f073d6f80a9a00d07e3b0c0fd405a3e0ef8f462a9beebb19f11f6b41dade4e911cc2e4e61b11d871229b799d5165198d087924e0c02b739e3f189c1f49a8c0afe0459af4af2231b50611647a3af73ed16e0487c8569bfcc7eb1fa6413f98839aa6d74b732291c79124a45292b3c90e2b8af2fd3b32739c44db9460f74a0ecc7fab74b0fd58cc2e4a8178d965bfd787123ea4dfa88365d52a8200d2a4bb807e80b232ceb73f3a88505bade0d7f203f7d10e942138d1f184f1b789f5e4bf03cf74c7b494677eac2499d1629ed8b30d1f33135e856a1fc75b9e57b6aa77120d1da3536eafd39defd3a3f3486a607d29253198e2ab25b04d4365f94175d6b1bf7269ac9aeb2a7297e760c5e25da94dc91a9bf1978d3311c7af88d61f8fc183270f0ea1b68d0bd8703a0b3c66a7f73ce608944bfa3de7ddf94cdd2afc314028b819a99d2b407a12cb4e7132ebee2b913330d3f6724ac2139cd61963b4279137c076e692e2f486d8080511383dc95be3d03173eed32faa8d08783c032b0668edec92a76153a1068c41474ec91e5e0843cafca5572559c90885882912ab84a2f12a9f4f2993a8ebeb3db5cdaa153e6286c4925e5ba7388f1db14c224db8851454686dc73ac1bad240efc72810e5052481fc1c4db6e3072b6d5f746a7b527783fec0c9ade24c03254f2776debbac45d57012ae8ff80567943cf1320010afb823bdbdfe66bb77418e05d3a9f9a93eab866cb7fcb3aedc9b614bcbfa84f5132a468b4098fa74ed8a5459d940186768dd993f91c1d0e1b3242bdff0f6c319b0dcc4a0efaaf36d0c1a7212c1276fd210b7d578e8836709e94f21dc08373878797766dbb0fe0555c7a503c3953aea54c0e5cf878292d801b9d6c0fdc6b03f277b47f9d95992c859cc5bea1815b32f6c80bfe246e816f7214ed3ea2a635328eb09688c1270e432b9d0873451a77598617a789c96ce10d8d92facb49ed8a6a87bd5256be734b7fa8d8b6f5ada1dae4575330041a4b2170205e1104dee902b3c2c8ce0baf83d6f180759b1eb6a1cef58b44c37b86f8e4e46529d58978059f15d5df0d7c2670b112f708edff0958511f6579ad3051ea49c3f80ff9ea0e8a017e7710a443ad3c3db2b4c51969b1b0e26913a114d3e5caa4ee0c86a7866f2c673a0d5cd795a3b080f2429810b04b302ddabe49b83b80e48e8e06e04fd7005083454f664aea3af06ba4466bfa8cc1741518ad0492b2203d3cfb6d0de3588903ef3a9dfaecaab5258ed7df1b573fca615083ec7842e3f1ef1a0e69a6d3e812a9535e839e118428c6799307faebfac95af6e89fc9d1f28b682e81eb05d63bfdb2f26b395a6dd7ad1f6526ca31cae2a857f85e159b0d1f2eca7a585347accb9153eb5377794a214a6a2270a886a32abd80aad67eaa3270aaf569aaccf48fb5959bf24323e09926b11ded0743f27546bb2a63b2fcf72a52f440ef4163e18347d6254cf8851d56c8f70a542ae48e8d6f0a701e8ac25d21c9481000b426be6de4a11f7f2ffa8650a68a5d8846ce02feff051f8b47788423ac0f254aa0187eee792e9d691cb33d0fd8f45bffd2a7fd8257ef2340af7643c818c00e55c0c1146fe9fc97fb7bea7143e9c12d6b2a685d3186b3600ef9aa21562d380b742bb66598d02701529160aea8df49d641d310b6ed3836b004478e33c136fd2e770e5d63ece13705693a5900b4a87ad50054b617f55ba65101152e58661f69ebc6633d1a0eb282875c8cd66602ae132d70da3cb7b0a82309bc66c26cd7c10f133ae7b10a02b2afc3dcd9d8a04d56025dcd6ba988a215932996b420aa76e4d247923ae78d91ff9e38e182ebab0bd1c880dd476c3a781ad91d6449b9ad98190f3861e0227007384fcac11742008121d7d5a4d741799b6dc1cd92bccd18dc6c1e72ef36dfe3f25db6a929accdc9119ff4ffedd8f7c57b624ccd9f7cf0c88433daf5882b929ae46f709b8aab9043afd320e153ff07ff197cb02508525a203fc5e4f68ea0433ee8edda62c5e24e0b7d57b8eb7d9c36efbf779e94399a0c7a9feacfd76d41dfa6f1b9db11af99b4b055c6c53a0ef969e5c18414dbf33364902d84b91b5e59e1f091e4091e9a3f1d03fae2f5562ee4f22512afb6a90767b951aaa12bfc85bd477ed5ec416b46720a40590d49b8581123d16a079eb0c1c85dc0900c56fb99e20226daabea63d79c8a2896632da5adde5a81688db995ea54b0548e458ea3c346b3441498ba0a414e291a4ced41c7001490dc9d7d1aaed39631714640884dbfca240364a212312573c6e42ff02a70c8a576147023efaa2c0cb8df68a75990c2d1c8af846b594dd94509d1fbfced6aaf2f21352f6ace16d3b182deb5ae1f96d1d216d517317b2f6dec7ef4ad8233fa9a670187d8a82c2c927d6a184c4a2c2caa7f8cee9642e58c352eb1bb1dcb3e4a5d6edec821c0fe69a31cc840f143bd3f09747297141ce9985fd70e120dd68069c093e92900c0e62be2e2c48209dca3a511b9e5f29aec0c2985b850c500973b6f66e6ae6eff8c1ca6ddfc21e9ea642af66840bca6b358026b91f9f37fc1a6c577f98003372065fd5298fc0af614e09dbe20d824306defe5af2e96afdb670fc5f28502af7ee72d463cad8b8af80ceeb9cfbab30f6cc12330ed9cbdf5890e1678f4af499bdeeda98b0813f4e7e6260bab85215f3ddd8c07fd48c285bb6896e361e00dd2979c7a704eb3e1b6cf8f29e89c487603a86786b0e4e2cef7e7d085db587a2a0ad8fa216c2041fa7c714fddc066c8c2cd8d062eac3687538475cb147f65c9e8ad05f195ee8c177e0ffa7bd2d1d2729b2dd7f8e28a0cded5e8676031a83fadaa790a35ace81d8daf8ba79bc574eb0d3f8bcc813c3aabd88e284fb3196287a2ae76a3858cb38171a61956bfa4d211d80a8454d5151be86f0c3b183298075a6b77ee038abb380c9897c31c464eb4035c217a031bab6c967611f359c686c6567a43c1e3c692331b87246449d06efaf08da3c857b350a7085f31e4c45070f6dda1e54aa172d6cfbc2f0dcc8b158e9aa270b2420248f9c391224df8537ea8e665330895dc6afeac6786292a5bc714ce4e25c21900e64f8a363b800e6b99dfba4a68d32c516ac085b6809509d78764d002a20a77194aee48901a5a3ca41c64c041c90f13ec8d5e137808d106d2a9bf437a67f3c6b08f2130fdf87092be0f4cb01f83b88c2bb681f0bd9b2970175c863c1f707b659ef90b2d040ab9fbdb802f6047aba87361be1b3892eabc4888a226a35a31c166d119de2c54344cc77bc037401c6efb4d86fedeb26c7134674c66f372e33bcc1bcf18f66cb128527d0130f4b994e5f905e642355949c06f7660376bf96b3f1158e8b7459b900ced0eed29c7af221c10c9afdc1981b20ad1193f767eca175f42c79a7bc676f048e275921dbd022350c014fc07bee7609a26986adff8a8389f7c563e6ba29bd7987cd81ed931696fc38804951fd4f5d9c7195152f8d36289061d24b16ff0f2b8ef3dac153661db9ef117265a2f681ccc54c12595a2a56a7eecfc8b355029cf080cc8d81691946c421492055705150c2c52aaa74c03da0f97d9a927715f03099ea84a2b1051f628b88be5642ae1d5880b82787ac1ad98f03aaa5e685e2b001b3566a34b711de255ad45966100818ce26fca564f6dc88430331e218784479817e10040ba8350dc60f2060856bf97af57cd872ae70e3c8083f8406d28c05ef8ce0c16ca9b72d75eeddf86ae31b6a0b3ce60c1a0dd7bd520a585df6e28fe1a9d65d8d80a2b519a37cad0ebbdf96e79e28daeeaa57154cfa69915d54e2895aa276c72ebf6b772f3b90a20bb1e91481d0872e812f0e7ae56ac0a58ec70cceb1780cd381e8992a7d9ba2812baf784393cc95c5023cb7d2cead801d99bec0ea1b34b708ced4e72f2b88b6cc1caa601148a7efd1313c0b84e7c4310c0f15f08fe8084f21df64299ba7534299b93dbd1613b5be4c001a66bbb809781b6dab07b51d501f4f1bf262f0a2a523c48a51a7f0d251bb87a9c72e35052de4bfa0a8c5df04ee528d55555e1f98fac82a69ffbd7f01868cbce60b7ac63f0f877d53338c4aa46b5f07e3301569e214ecc8e79fa1342f99cf450d478106c6cb3a4d839a1ef304b89bcf578ceb3ab078eee2e60e943ddbf1f9b0667d348fd913cf8770577cc87060849719758d641dfa0fa50a6cde99221f7418c64eb809aa3f41ee122c79d598859a80056d48f46419c6bfdb3fee45abcd2cbb955b0b569ab972839856a6f3491c6bc08bf6a7c39aec0250599eebe57e6ebd5c5b7833c85aa29cb9b805d14cbc14ef13de5f229f156134b3b05a1b3e8c5855e89aa078bc9d6eb3fd84e1f9ad05e6a30ef4c944c21f0b9e60e47b80d7f1aa8d7cee5b8291a3ae13471863a9b47999dd94945b1bfb1f6061608ad6881883afb24c12f52dba75bbec8ed4fc783c558118671de5ba53d85eeefcd0a34064550ebdc6764fb2e77b54cffdfcc9245455505b18fbd4bde15103abdb6887bd9731c5c138b5ee8f08956a7e39c90d219f632cb87e683540cb036b9bd95c25f26bb22e24a1f090d52ce666ba789377d35822f3df53ec57deb4a8895f71251f6e754702c838aa442e3cf2447872cea0f265432b0b83ad7704325968b88c8b0765dbfd78e23d40f66c4d2980823dab1b6dafdffa64bba038ba39ecfa26d870909cf8742fc3c352124ad89812f6a91719b3c5b74d48805c6854d903b415fec0f59fd27266a739a43002a67bf342cabbd80e251cb556d4091d86bc57e432ef76af8216bbe3aa5489ce97617118a4e9bf7919efe0a3a9e554fa4fc183c233aba217247d1c7ef3bc79ea29c9abab93b74e65fe5bba5d07eae9d1d94138ebc917f6eac3b9b9239f710736aa43fc7a5d44416317d127a4e8259ec8f48b6b794cba869914c49118c46b7194be24ab09aba7db0a49bebac458eb97aca986ea92e42aa58216b9ad37bac9884b6b16f871231a326dcb558f49c54519133e168111154404407aad4d18ec939bd6dc552a3dd960f9b02a6293031de867e0a310114ddc51c9bea05f7eb7b7357b78e6e75b780607bd51788449c5143ee2a5370427b4ecdee9ce4056d9640571c59584e83fde48f8b3cddfa152be925eb03b988bf4462c79a06bfaec20fad06f206c2a815a93c81c02e6518d62f9118fd2abf295011ba6d909d85a08be14920db7bdc85faaf84f25ee3915835c5f97622078e11591c27e298b20aadf2cf6afd086feed330a0b02da13bc9d48b8fb416cafd2341cc4999258cd8ad2aec66313c0237232b2a4448483d7095ce1064b3022588a48c06e7acba529e52c76d29d1fc012544b9dbeff0729f48752da7e9a30cd6606b1784957bfa94f9199e4e380a4c8f230a570d1b33d5e0ee034b6d782fcea90751210eb0d28a60375434cb063e973195542ad7c9e6babeab19477a55333e1298c889fe4578349215ee661f7b4a2a616077016ee26c81b9fe06c184650d2caa5d9dc2cb701192fc39e7eb9d3ba9ff13e883fa8509d37c744b4395cc7189d73b2f015d10fa19918a4c9b29d78052d1ab891ea98d0278c1fafa27f19d726b1f64f3e9ca9f977ded883989265aace1319d72906348521a2dd78368e4da957a4993ac78e6e764ae7419dc713def91a79cc08922beff70d19b7a053bbe8dc231ae895ae304750151ffdd94c853983921819250fef069a993513d4ddb2d0ea999e9f3a970c1a029fbb14a9a3bc12c2b9f9d8e0c94e6be4a8651253793b75c93f8667ebe4f6589fe14d051f771034e297fa430fe81138ea5830c7ed1969936e1fd65b5f74d3aa1c650d27d370e2a0641e10688622e6e8f32e7d49357f258aa25723904f6c0e236eb6b23fe12879db2260d03819607e39f4df4cb2f0990079e3d0daf3e08c85d0b0eec5cfa0058544fb2117f12db6a16fab49f8a7ee528414f5bc18931df77814e4968fb8518b7b8501616e75c399ee32f33ae1b0603d1bd9d3a4aa23febd7bf2112b0f196bb8b68a3c06c8b0c0e08a6831c3f22beee3b873c46a711115383aac79a73bb18d03e782d13d493389c778093334f1641a2e553a7f45d636c83a494d36a31886ddf35429a9a01c80fea6a1f8ef3ac442b5280afd83f4b975e25f93ad61c109b0a73c25ad7193c40ea660ac542afb734f55653e85cd75b388bbb88a70beee1b1feecd11acc0192bab54fb9917ab6d9b61ced26b2edad32112fbaf405adbb75a96c90b70795c785aff3ba66ad8f4f9e3ab1e41bc282e31d502eda0e1c834078c41221b68a68ccb64df72ea6a2233b8e44ea0d5e28afc19edbf0d1934b7de0b4229510b638f835b7c39149e34c87222e63060ee2de62f02a59db3ce646d4451bbaff1824e5d76ccb204e081dda1aa9e279291efae4049a831d4ed5c42556482ea2391bff6fd40b1ef891a99a5a52ecdeede36c94804fb19b604a58a601eab8840741b8bf60702dc53f12ad23a1f9ea813f3091ec187c50b501a0592ba52afbc789927f7f7f4d10ba8608c1548d81dc710c9bc22c5096cb3350cdf288320a7b291918a7ff97c7de4b6f50ae77ee7bfd25014a7c811289b13f9fe5820596dc8a839d5285ed8ca7d6a97f2d08ca54a482f7b6498b7eba5fda872e907928bed4daa5840037a7e94bdb7f379fecc4483a54ea28d28f60811fbdcaf82aabda01eb6ded9de4da4345c66424be00104304756db9db375fa21cbfd3582143c8d3401cda58acca6d557cf89131eb3b977644ca1fc89b2ed931f392b96684f1e06803ae45fc4984697684fcf55b0c5bccebef93ba637c7992d814693d88c80014dbefcb993bd30db1d9a53d3ff510dbefc6ed974ecb4de260a3a2a418b38c0d55b695194e10b5e49e2ffd448c800c5499023b92551fa240974b5ea0bdbd2e907d5b2679aa90358f97ebf2c61a7f92b14897f819bd2abc7a423e3d782fe6d8ebe2a02cc6ac60edc2c7522f8d0e41e35e18fa6ec681879f2f13c5d17ef44e755f98641aeb0d760338a9727040257af1d79e716dbe00b28cd70775843e80743b8ba460c8e063f6736012d7c1bf8a2d00832b9ab93b63cb6bc54e93e886897a31d7b07d5bad71234c83fdc31db2d30bdbba7271a2e9ae3c72fc6c5b057641c8d8caedf7fd04ffc72d347efe566501f86bd1cfb329d2f2f019d92034ec9dbf3392f44b1484516a48e7081b486fbd9f83048a58432124a32ad0eac08d2e4461df8934dcb84610c13da6de5473624fd9334eb42d0cd32b5ba9348e6343b79b02dfcad82d2c94767a391d64bcf8df092e26b4594493840d2121cd6199655bba79a5a16895398961228a735a15a06f2f3d5f150bb38a6d34fc69953bdcf44c296e2ec401ab068aed556cd47d2b5214f52727e45a31d0e964b09a3b00c987473f255a186ac10c66cad87bca8ecd6f2e2066b8ccae9533f28589351010469365e2d3cd366603fb2c4d99288d589329891664b0f62740cbab97dba811cd86a98df3703da6f1f37dd474074d4c27f68d9b781a298bfb4268483f040418641d566f59d03034c0678e18218eb748e212d7cb6543376dc9d41dd9c7021f0e807e12ea9509dbc87162b6472fd302d7f1f90bb2dfc6aa8f7d5b9f181828f762cce6f15cc7288cd88d1e168dbd5b316421b617297110686fa46322ce6453d628aba0916e347cc738727f54a0cf1935e25e0eaa2fadd59b6ac2689bac70fc1229a28875e5905bd3ce96d805a47651287a00cd66e970a6c5e4da8d9276ab720aced82b8ab6dedea8090dff8fd2be46d606caec4f7aba992ed0e8459155536a0a5d862a34ebb0caf6f6283f8a9c0317423c351d1415981ca66f664f4da5a2a6815c1938210739b38abf34ed6521ea5c6567fa33680ef7866ee87e64a9ae52841bad283914f94064715a4afe9b0886bfa27405698f94fbb934c6a80b908922c54b05c9d0d05820e6e862f9e70da5f648c08156b06a431f480acb2a140e16c78f00dc12d4686cb07a9e4863da6ebb6c4ff7270ab5bf9c8ec538511f4343f1d50796fd68286ea5055b8980f6d15d777f872b4bcd8aadbb0baf6170fbf6cb017d6f01ffc8ce582a928d7d7f24810eae35f2c9f1e54fa283e636f8c21a4f2b854ac0d245b1a152ccbc3fba7c104bd6582f0ee96ac511ff10fc69533151928c58c1d44ac1b7114ee28a5ee65a438eaf9f435c896336f82d20c6eb170e591c66775f993b96155396aec3bc0610780eda1e5db073119647cd39b62256be6eaf94ac91d5f68e4e106ebf77751c4b45d93d5682f8e3d1bcbe2f26e5dda2030ccfbea49c9ffaff99c5af808834763bf9cc958810f988a81481a3bfd2f022aacd1b4526871eeaf714f88b6ddd4a5952f6402dc9a8490ba87afbfe5c319e07637e1db95ec45620afc073dd77a2b288f54024799e7f1036d32c6ed8500e4b583bfeab6565beb421439802c79ec5cfec2a86efe0015c8f72e56c3dd14fde520227589e6b3384b6d8ffcdf7f543847889c8f4ac26e806afb2b9739e70fd07dbc72a9040f50bbfd08bae3c21967d4727db515fad5a6991c1e75297fa9a373bd9d62d7e57894b91778d623e2616e7c8f2c54dc896f28cf4a07987b18afe17816ec840ccb2f323c449e095eee9537bb83ae0c5beb37408ef7322fffccf223e3b26c593a6c2c7932d2d4a5fef5249133c2481db05010f8f65f08f2422023360888c444543a5e0558cf85e302399faf1bc2d6192b6e57fcbdbf8b4aac1e38a24e45408c7bd819bc20079bc85bacbaa00c333a40b547a754c24ad89523bf564f282522077b1ae46161363d6f7b00c84f7e78e12abcefbe61c9b2b081f4b121da328deca77c326da33e0aae8c77a8c8e638f58ac334a8400d94f5086e09b5e151adef77e8ecd2404842dc5728df970f2b93341a73d432f730c7e4d1375829ef0f93e2c556d9db5a0c9d7f88cb24cfa76fbb82c62ea9d95226a34e80567bdf8c84fbb74b3a571a00dd93935080750e1c1070db53e918bb678c5eff33c0793148c7f484a01cc39bdeb46dde6442b1eb399189da609c6e1cf11316d06a3b8791ab24d330b355c453606608a30642626c163939199342aaf56dbe5123ea60a4185e5de4862a6a311a9378eb328f4b39b2124ff95c3c8958ae2f6cdb858d4f970b72886b988ad691cb6aa5b7fe9cf3ac09ec80d6e7cfa5159aca51f89663bff25c499e9d05ddcf5938556650bcede37d926ab38bfd4c4829bd28a8216f37a73148581160b951559ef7d169d999e05e13b1a33ad1004350c67a1fc3fd20f7416f743e452b85469646416913d85111180c1148e2ea5e8e28bb8a93802cd5c0a72ea0a76eda5201d3ebca6fed442cb6a5302f04dd698c458812bae919fe5ab2bea519723b903aa933d30b33f2f89271b5e94cd57aae85d9cc489486837981966c9efcc94754d17f2eb2a33ddb9ce8cf87ba2cb2c4342fff9ec2f7c17cd16a79272920e26f1e6bb5e73fe94edb018ca94404a0055d828789d512edc62930e5a97dda2da7663634a4383e598af308e03286d988174d4a541d26e8d8b1937b2cdcb5f8b10b20999b12880bc52e54f0e56b0a66b2405e748b73cb1fb9fe608496389351ade22639d6dc0bdb9ab59ca3dee8a979e290c404add05e9d8b86987ab032ffcc7dc5678453348287842e6405977e5c164a04674612d32877190e61c94dd7fdfd123c688d82de784e82c719825b2df724a7803bdc820bc2474fbb46d433ea06faa959eac8a18a95f4403f1e51c9d4ebee7790483a743d22c6bfac155128faa57077ac5955206d370ae072e3c2b770783b3539a6a8a1e439f4087cb225ace65b0e26605ec0cca66c63642b75a6a913362447d2d17ea44a1753b6d94f01819fbecba019474f364b710cdfc9fd75d31caab1918d3074b907e44312214b280eb4e0a0667cf4f385c7db38dab8b5daeddf0e01842c0ded4fb023bc22ce54b8e93d0b28621b26189aa326347a6bbb9860e3e467395c889266252b7df0c93a3ce4e60840b17c3c867e107e65ceaf070f3db76211528b41a46e0e8be26796f930856ee5650909c7bd0e671125e3ceae4dbc917a092d89dd9268e20212f52b7ff060527a1e6cef6e0acc331222ac151414434e99dc5e09976aedc404810e0f24c948c149612a6403b594ae8bcd2918645a9a70142a15a7d36647897e7055b32c42b97d698c6b6d521ebc42037e3c69a1162d4ff1ae4af29b9f34c0440ab179be766a7d44aaa13c3b306f8358a61b1b769dadfec950e3357271bea6f99e79e4b774ee77fa28ca03b9bfd83d8dcc523b88e03ccf08169fb8a965d0b7b35318e9d9b47bbac4d390b7020247294904b8549996b29e9ac267b97269906103bed083caebafab7ccbb954f508942ed5129091b242cd140d7ddc507e34085c34afda3d03aaf8aae893c0f9eb766ec176b1f8494d9ae120a6903f6dcf1b7401306e788133c4a5512225c3ca87b7568109c7b37cfdea4aeb94c38cabbe0ec56aa2f2573855e441a5c803da683ca772688ed73b839f1adae706dc3ec4f3b10594020afb078b965d53d4890007d66a0945e13b675a0e26c2c5a0cd18abc6ea598105cd2f6f870e0ff48063b40c26475dab7d6c6533396f5c10259dbd28d6b7ef8be3a2e98d0c3f09dd8d2872f5454fa40860b6fb9b0e1850d69577d95b75952f0e964a5c99a2b2cd91e3f83803f8f34c9ab9dd0de6e689829727f008c5a6e0447fcdd70fd5c0acf297e923a4f318467c346a74d6f4ed47b00d690a246e52d7c74625aa68d3b8d0893c8adfc57bb8f075239be2800857970cdf45d23301aef3b28bc7d315557ee42ea6d4d3ce5f122c25643382908f14325b85bcd66eb0d00ceb28681efdfdc5cb7f851b88906d8ddf16d550f26b4765da1ca7951dbeed96ce48ea295e6cf041f175e11f592056580c053e02133dfc4c6d77359c32b80098e37958fd23c348a819b280e44d2ea9332cea8ee188af16f6f5a296e723ac9a086adb44275ba17675bf35378290b811d6670109356d238c7b271702c3617c00339e9d5c6dfc4817705fdd1751841b3d796be907ffc96e7b3a4c8b9babbc20e3f45691cfe86ea486b4d1b504de1bb19542e68287e0639ec7658a7238248a25a87f7b8b0b5988af217d443bb83ac4919d594d3d310681b104b768efcdf2f6505c5f5ec4abac010256e8e43a852b877516f374501606efa80264da82c9954dd89e661a6ca9f2e8fd195530ec24b73a4f6dda6c833adbc014b6e946cb60556b903ad7b74305d10a70c4c1994b89ed95d89ebfbea79e208d0412d9ec768b5f25c58f7f57a4f616aeb63b64f7a7407cf605d03339a20cf38a7d5f61608ed8533100f4f44ae4293bef1f4b1fbd6e2b48848aff3e8b2e4961414882c5749b175bd72c1b929c939695e1b62fe6a679afff877174ea6a85742510905ebd6c6273d4c86a3163cc50195df9056d3d37474e3f41a0a0caece713aa73186184381c8746d451c9982cb8c0394a05a5eb410dff4bebd49c700ac05651515f9cf1ac44f02197f18224008738a53b2a0842f5d5289443d220e5b98800eaafdf34ce380dad1b680a1384571449cf40a4f038ec8e0313a50b04d8114b46d74bc54fbed36af3c5362d53e883571783452cc46c8faf88d8eb693194a430876b8d0bd7ce22545d143f54eb204dfb97d174f23cd2ad9b4f368d2474e7e51e3241c584733052911b9d0b4cdc6c2e10f64550f3afa5d3647fe7cd397f98f012e99b54bcdf91f1c31a50145451ffb2984f0523279307ad3a93ee8de8deda792e37892dc066d64e1a8ea682b1a010aa66e6d63c49808210f6802fe9933cda692e26578739aee53b73428a0c136ccbbe01ad1c4da52e46dabcbe609a41325c9dc608780a20d49ffd684a2cef8373eeebbf78c2a9a1251e31d63d8f1a19b180f31443069610878b13dfda6f87df763bb8edbd9e7b21e30338a6aa5a6661c5e43c7a93e9c36c81276a0a37f88109e91838743352f2a6715aefbb057f8355ce7effc4da113bff6f84c2a1dc624070550fe02508ad927e683c0f7cf04114d5338b0484adedfb3079ecc208fbe9e65ab7c88f6c91a221330011feb79d34f49126bdd9340585a236014a4a56b1e17e4eb6a51634a5d7de4c068e86fded6a38feffee4f17275983cd4c0cc33890ecbe76cbf68e13abcd1c2e426d312c93613289573099ec3226a9838212d0703bcceeb253f7e380d6ca98ada34be507166b4f66e2ea11a2051c20b2d8f13d6c1e96655d547892ddf6ff27d485099d14b2b3ca4ba762fd67defcca2af236c9c7e5c38f3deb2bd889ff77016c27945aa8fa487b6cf5e09e5c42890794137cbc33e9101c77311b12aae6bc98c145bf7b5ee9607ada67715c95371f2a1b0415f9626c6a19fa4c7a14281d4a98db50b6c1c5bb4501c286b3e896d103e0ab6abd8288e38b2956449484a7eaf8158cd29f6af42f46aedf5c7468109c9f498069cc7e3fa40be4fa8002690976d5678d3a0e49d6f518ff8643edb135001b870fcc9409f84c2bae23c1550008225448244ea51d6ed0c70c72e9804fbc3b140f227a98dd5ecbb37899b1209da0583b3d0798413bdb0a8e65bc2e500d1773ba4deb6f9bdcfb80cce86ec820964570d52b0d3f5415bc6f945cce4a7976b80492ec03d956140ce4c99b23c45e84967ec4dace7b76012dc81b68c590da455edf2e3c298ea688d43184499f443025992f85eadc8df9159a9af10489f8902d340a443c735c16e5823bed962ed7c5f496da9ecfe9a85919246fb7c11e352e4b4b9cf88c1f56bcc662b8c7159761d8d8c1b8c9dbf085686d26c65a0a3840d8d925329654a3d1d1f6c6907ee45ba8d85b0f1c573612c5b007ab460fd27070a5843a60554cb86fbb3ff2cecfe9dd826a396383413dd8fce51a571dfa5ba74695689afe418ff0415334eacfcc43134de5f3ff0b7008257678ca71c21de251180bf41b13f856b5c32b8e4cc1b65342e258233a878a982c31dd7f607f492b6d03edb54d99a80566c288248c0905ec100fdfedf36e7ca26bbac5e5035ff97a6c16b6f14939fd29db65f8dc97788a65cec9429c00e57c33bce7e645faf802c363ed88a5c95b9ef548057e5dccb4071d971a258610bc6947a2a273bb7189aa6e067a66a6acb374fb2fbab0d0b7386691f842c4005532ed2df61aa4b5e3d11e16e262f6b6dd9927447eea0b0ff9b001fe2dfc391233efea1edd4741ffbf80813cd607bb7bf69c2148f44079892266a179c30484b050417536c6f50b381eaeaeb52f6b1477e20f01d7dae0a6927df8baa454b54167f77c2637ec278a6accd3add36c152b4a9f69f6043815c7e9ef88e434077664707b9995b90f11bf12a8a1c48150300bc045b3d1e8b54ca5ed56fd412cc8845b4261d2407c6106fa47453d5c5f0f234584d391661586d6a32eeab54862a6c268752e8b81f44543e43017e7f163c8916cf8240ad5a8ada5f08bd435675e235224c90953e64c7a82f3467797d4a168b4a2de3f8f637d3f86f8b38b77efad5381c6a8b1c1c77c244b1b496c63eebbbf145bc90f4f86cce5fc8da5c3ff65aedd2f187223d43b0114a648c878c1bf08308ece8f696117dfcb14de4463465d52aa9000628a8efb590aa8936d7eaa44d58f51b585774d38a6ee1bd6e5c068635bdbf4d185bdd440f1e08f960e77d130b1ce6624b1b66ea7159fa79912c80cc931c623cc8d2b724ac68caf075facda6eda848ae684cda6567ac42b085124881f92d2ed11a89abf2e7973934e40777c877ee82dfa719d02526cdfc4685eb88e9043d8fdede75a226195edc27aaf379180ec7ac12d9d75428d5a70a8a5c7329eb6027f744b039e6bad55e980f9cad2dc76b2e2a0bd3846c6c0750410aae226ce0ff22769e1d4ea928fc2f90b1a0e459ebf446c95c8a26c98279c19fdd50e8c92435c758ded934dc378b1e6c5db26cd666cb5444ba07a2502aa514559ea65dc3f0e3111c15001bbf2e363caa358ca6cf33e9bf3f15774c4332da04aa4735e99cf9284393686881436b334e0ae09720f88cf1c4833e9d3ecfee81febb15c6f327ddef864128ecef05a064f91f66caa336cfd30a6aec5c55e5e637159ac02d95dd64ad344c1c7d581a4c6972d060b25745ee65babc350f0b21e0f126c6f61b881700d669739e51388ba194fe1293c534e7bdc902f72407336e77357b4d58fa974eba5d232e4c9e48777026108620826486104d58a0ddc2378fd7356bf28ff38106c79657c30473f754972324d5e663f0567cf4e0b5033590c9808c84385964978169f129482138931360fe46d62adb8b54e94eb08b89cfa73f4f0a123753e048fa9ac26220a39c45eade882232d5e56b165bf4af7738642fea88d5fe9a39dc846515246e85b1870eb1f28000c100e600129e970b9a045dcd31c0944ab40a7d3277e8a76160854af1e839d860d9d60b61bb2584cd39c4a0632c5c590731004e4d4ace91d29ae7dc3d959e1411d2057c6dc8facd52b30c85ec3e35d95afadb043418dbaa680c0260f35a186a102c76152b2dc4776d99bbcd984c08b773c91976438fc383db4a64dbfc982d8e858b16986fcc1fa7c5fd1170ff01c65a12f3d92accb14d93f71e22932e1d61300040611af60de2126c765ee132f92865d15c68318762fc1d0a4ad0429602ceb4bc1859e31ed3f7cbd5ceaaf1c3da24f190653d1dc486026b5dd1d946cc85eb81928083eee8481bb66bf144043b161338f6ed10664bb56bd9e9fc03ee2710f7c505b7191b88f8560bfef221f53c39f9379d8071495d6d238046dca083f5bdf50d76a2bc784370f30e6d0b25a5783b15880ba49b610c61e1cea994baf31e58dd71c6dcffb3f7576f87b07e5017a833caf383507edd48a0ee75da01b0fa22628697d34ded6a03aa28a55f55399914cb42dc5aac4cf5e3326196a021acf6775d5507d1f92154fd9a1bceb1d8bc7fbc2366973f775fa729c9173bbdff7c8d14a082a40dcea71738c161dc7612fe6b0469662981caf9883c702cc8693512d52e5df18fcbee04d05887db0269e4e3818ae53c5281482ef2da5a13d560b798e48052b1a34e4f223a6002c06d5d6b43a8a03c17ef2192711aabae0eef76f658485fdac14124ca500d0c60b13bcc3ddc12b529d1602c25dd358ecce9232ac6b1f5c5405004110ec8412067b37ebf3b9b9695c727164789a413c80f7b09d9f8d07557506368fdfa433323e4de8c569130471ed5aaae819279740102c39342977752fc33455af01a76ec0dd3de4a0871156a95aa08e9c1aa0925331958426e4de214b7e52ca105adcb342b970f4f051b10b46fe85d16b04ef0020c7cb714202570fbeb913b478f7dd1b30a5bcc8129eb44f12b3a9d93a00449ec7e345c7b304cc2dfae2ffa91421695da4b55a78b020366530a1fdc654b70bf22b2f078f9ca14ab7fcb4fb18e3bd208dbf87c5fca81bbc1c53366be8228e14682fb53e09d49d17d5cbd0b026d42167b8283e1e2f64f2ae7a2c4a06ab50661b4a05014a682a6860e5bc85abb1a0f7d4e7cb05efc6b25545fb640b49cd5610b0a448b44a7239ba723234b1cf0cdd1d4a5cc90f33ed4c290942565e01e87b78945d42b08681269e65316a66fc9bf95a6d9b4651b584c86eb3f2017167fe21e3aa56e542149088c01be219958e0aeab39936557f42ff6b37063f0e069f2ca1366aaf10c15c34bf807cc53b09487c2a26d41b1c706895bcd0a108684622e8afd0e9e7a1e8a9590c0eecdaa152a944bf8555888f505abb6095982a4cf54f94346fe4609b8657d705e168ea71df4ad348d038da04941ea11823f286450880da7cfe2975aef13db45ec65892f137459dc47e3af57aa39909808d1816c0a034f8bbfbc74ec36a60141333f8ac2dd044783d86f910409c4e6f53a1ce39d0fec4c47a7537a0b3c5453479589cacc5ef7c1202f01294471307f83047649872d4560bf8dcccacf206e39e9c0c39501f3a09b3284efc9b91cc6e21a0269c8fbcda36d5b640fd4587839076d0c87cb19f8b11bc14b1aaf6de407c14ffe9ed43c6386f8c571c877fc1dabc4ed2f69fdc0137779ea93c70ef7b4c9f778689a36bec6de3800bf6e1ffe9a82cf70a392ef28e6f49825dea02646f886031e0161a392acdb231fd5b634c1bcb37159a434cdc6d73a8a9e045ab746037d7bb350c15100cff802cc7a99cb3b6233416c7005169969207f578e4c8e832304278a8c58635828af5de6bc22a2bbe19f048c6bbea0c68741ae9f6e0ca4028419a2fff6338102192f69df02e9ee9727e54c3db8fbebc45500d136697a48a596a829c0d1db0d8e83b3244bf01758dc082d69de8f16f40d36a26479698acce0a8b2a09284067a40447706c3152a2153c01c3ee38b63ddf9c7cde007a19a95c4727db0e9015a08b264f9e22a3c5b3d92a05185303207f034c3ae43dc2f4fa0f00d4174eb2de51c1d4b72f7c0bdde7e6b817b7bbb0901f3a4f1ebc0708c09a68a81e74135c336000a00ef41dac857947accd1ceb25144addd5d25c68585cf28199842caccfbd0cf16b3438f6fe2268b9626d00f8f33f61d30777c5b23e772a4afdb54ee923a192d626ec6bb44168efd609f7e8e538307ca0c54b0ed3547e05b1fd72a37829cff49b3518441ba9f690640ee2af846cd07c705797220619f3fefcd58dd9ac3bb9ef6e79f14e639403d4beb48cd596bf9fe3a9fdb571695d2d76af619e28b2f07d8bd59a218ff8300cab447fdb83d48b092a95803598a3ab3765dbf02a306d775ccdc185fddbd994b015e8bdd69821cc86dad344a2082a307c1a1ec07caa1b9c543829ca501b9e18ce818d940d4e132d0092d4aa19a00c441f8c3270eb607125199ccab15d0258b0f78cc68dd14922bd297f07ac4f66bce0629502db1f2fdc77d144e18f70169a8441be6e3cf4bee1f34a4b0a84da5a8b3d5df2e11876f248068211d350faf991a7cb6ea55d7080164980e782a5fa5e3aa540509e6255a674ffd434480dd572d7a595932454262221cb6ad66e10343c3214dcede051410a3bf5c5e9feb05369e3459dc0c240334faeedc625297a9f1c3e72478dc548a8227881f9ae3913415497746f64958e3597ae2c4c69c0728ac9220501da99a9ea93b383dcdb5856f98092b07044b1c33c35974229b2d6efc9054501f3c1875683e5694df301da6d093766f5a0955586801c3fb783d69e604584f06231ab067e9e43e843df22dd1166649e1b482a35700cc70ce433befd522fbdd37b270de386376e867d646ef9f93541bd55c177f5513dcef023bf87623e67cb8a13e6e4011b4fd2d46c1e9367951007a9eefcb1b1f714b0d60c1cd032053cbb5c7844b0e7341c8ff8953fdbcea625b0f3c8bc33cf23d9a565a328b1c89549c0e01cb177e95445cb2f0739615033de43432e122d565c365f5bc6d827a524415acd63c88ae9ab96ed1e6d87cc0a87066851763b0a0a64c474566a75780bef4ea1b02419cfbb0871be1ebdd85c4cc8d3779756ec888e5c70b590db8bbc2655abeac642a91346b5d01ebd941a14c29a922d4985022ade5a465eea0929a74e1b9c3952181920e59588e21d9fa26b7afe39dab0e8e16a8c4d7e03e28b5afa63bc5f8b8da964256d54c17318c546ca4d49c7ee5dcee8a803dd8ed5ec1a897ed1e1b1d3e81eb92d12095944ff8f937a91295f941daaea84e68ccbab3c41c0dcd2e92dd0a527dd86105def3cc426a0b68b3ebb08df68eae13d21da512b019df63ce573c61353ea029860358bb8b477069b35a0acf66bd7900ca65b4a6ade12c83cad700fe9b09e14ce7aef49fd4b25dc6ea1a31cf4df72c9443e9c6297ae40adb1cfbae2b39eede0248691161ae141ff84b44a874cf358906cf440069667d16e4bdf90f1837fc57cad32e03eff4fec2a5bc7d638597015df8cd9839014976f4bda5512732607b077546b57ab243762323397d47a16f80f8ebf029b54355e7b506d39b46e69c5c4375f21a5c23a866f35d890c51200ced73a51756fc5be881337ef401f973748e3ee5b802a4a2b3ea0203ef6cb8728ac3646a8b43aed98020ee92c0998e65895f07500a0d2e2b54a6605fe1cab1a8f5a70124d92959b04e0f1c80405181a593e3db51de6d262cfd1003c2ae9c8b08b1a3034b064262f31bb35e15e97b2883e935daca1ae04c83bc44a9765d86d46e2bc3196c1256da9cc7f83226e3dd1a1b95bdf7c0bd3326c726c8f0ff1dd94a465e4659fbcce65ae649a08eda0d48b2d10f77087c13a309b7a257e59d000fcc4e715e9a448bb4d8a42629c7a642e557bc90b271044dac22c6a2b4a14a412756a5f2fed2856831f3214b997b45a807cb6dd085b80d55471b27524832a693bc01aa85e3aa47b19a4804e06800d94d199bcd5c7466222a92e2de3a92c7ec2d11b9931092a629f1ce5c28d5ac1725983ad812082d3cc4671b78f1d057c34322dcd838dd7edf921d478e6f6b72dedb3c416077f04a4f3a02d05921d2750d993880c845a6dab14304bf43bc5c0348fb775ae3045b7cbb70ca6460e0fd5eecbae7f2232d5dd7e79d1e45e4498076f3f36ad1c349f9a54a2e8a899349e7f867e60a5e0f458e012a9474fa6542d7389355b3afb66a34753b206b30bfa7c63366046c623ae00f6b66ca81a3977768f1fb425216435e7def1b4638494a9a0344b7948d0deacd055cd0cb1cbb26cdf79323e1ef4d58f089fd3dd0526c10f0f80fe19695d4f5c4015d3225ed9b22720ad062ef7044534a91e8cf55f8ecdd8af25d2678c88345cd9b49373522db8c26b9040db829bdcdfd67892a12af18f52f844bfbaa258884c9e108e34e71a13681b19904fcc52f3f9123b4c766f20ceb77338352d54b8d038553b8306a02eeb8aa3e47eab8a42127d6ad3ce2ffccb841b43db956a8e247d5bb6465cb2d8da71cbddd66d01dd4ad9ebef4dbbbc2afd5c320115afe7c8e1ecef978b672aacbfb5f41418071cf414989452c704cc262e9dc30132530b8d1f9d724f4a7542e0c7ab84a336c0886f820d85b86766e68bf8d3c9396c31a56a5e98dba0010a20c90c5133c3cca92eab080141b6342032656a91a079a3ed6ea8206ef27958116cc33ca276fe225f02009489c715c0f5df3f495b716338b0683629617b89dc1ebbfde41d19a3beaadb7728b69cd77de8ba2f44acac0826692911624648214404b6dc06840ff044a307c75de4c2ad6a7786e882d827c06c958a18a56c4d7f1d973e89bb25e46fea3a5b131c7934418ffd1c372503938045e27713f47bd4bf990f8590f59c1b5c387b3630c78705e60447c97de09d615c9e83e16ae77ac53d8cea36ace7db40b881e98c8d3a6df6441602cc693ebb27bb6f788a58b76ae42ae5a3e29d0ed409d4e477681074727ec5bba666222002b72bec7c3bda6fe73d59a92d3cff3706a26e61030d64bd3af7501420301a3cec2e7a5dfc9e3280f209d447386a4b65c603971d0f34fb4d2e71935b83cf82f7db9a48302e1ae7098f57948e90d018645e1bb1300df90aaf2c845624e9164f2fa7b26bbe8d5c7395f629650539572dd083cc776f94aea3dd7c590a88af53c289383c80ce7aa92d16d15ae860de8d2fe87e48260727a0846b97e143c6e9a50aa864934d090d2fc0a0f09ce68fba8b3d45e36155b12f9dea92161f1180afa9d6bd1f51346bb009172e58c10bf57a8a64caa5304a2c0aa6a58f184185a81afd53e6a56dbc585f422def85ce8b9b96fb52bc1514b21200ee70e96ee6c4a0d0cd32bd858c8f694549e76ed57e3baebb9805eebf70160ec8812820a6f1498be3d3839effcb380b840759ece77264c7e8fd6dca7541659a5c017bb9e77d00ac375efc8895af4030d325218d40c52a7211481b61aecd36e6ae6416a32f174cbdd27a844305577cd95869e8e0d4538967e259e355b0e6da27689763128f14e690e48da6861b59ade51d8ed46201f944a2be550587d455393f7345eec3ce624ed970c197bbe9469c864b4972667842b89679072a896ae03d9310b17050817b211b36f841c5cc08f129b7a847f12aa55aa78da3c4b55e9c01bf81b8b1fab4788ea46f7cd23176c90a576919d055ea5bb4e20bd0ea00f727f6a044cfe92c366ac4ef037d931fc67abbe570a0ab8f861b050d9021b86842bd8d122c9278f8a0359015b2afdf8dffba66e1996347dc3825ecf6cc24e74688ae44f75fbad06060c26d34820882b8de4dd9c764a08f4029ad14d20ea5500e0b1126c3db9811157f06c52265ece1a066047744142c8ad1c73a185e4d51c7b7bdfcec8aed96f938d07a5b813ebd694a46c8de32d096a458e8873d1bc177069f0eb6d84b1bcfe6f4e90f7296c4b200ebb6c41b7d836b25807006aee4a3bb4d6c616bc10d724556ac413ced8fbce49d7a45d5220e3a181b1839c4785c99eefbd165881952c2d0d62b3281c2805e858e6320b7105ce0ec9defb31780b2ba43c240fab868ad2120697006754e407d3e0d6b76b0a9a0ae176f5521c30e8c151ba7d73ed31579c480395d7cad2dc4dec534124384cd6bce02069ddbd73e6e68ee6849440c4db29213ebb1c37dc4048ab1a0e85072842be06ccdaa411b07ee68905ce7420dedaa97b9d4deaec425243899ffdaccdc16d7953461d498039c42bbb7feac88f984ba391148a3addce08141020d8f82522a03f8b08c035ea1c36d1e60b79af9869bec180a2a536a98ae86b252b45eccc829ac0860013d58e9183569468f57913ef3415809e12868bdcff4adc8ec02fc524062f47e1805768a61cd93a52cf22b7796b8696a990d474c4708848a2ece07da5a5c62b32390b93c078f249c24a232e55025718f3832f20cc0349b31476479e7862e943600c8f872bbbd2172ada8d64ec4014b9ac8eb47c1a0437827b98d124faa549feb2a9ca020a6e3c1ee34680629c52e063b849abc20c22fd19a49455ba51524a35d0e399e93234cdc74f6b0f1915db745e327b392fb7d548c53454ef9858400d1575d7d1634f1043f77032dec99fb48787758096e17b7843028d1b945c6fbf0c002cb68abd5acf4dacc7c319247f1ba3dc443b86825ef168e9cdbe8875cbb3589d9e564d18b78ef6c06256d512a829dda7d217d6ca2c817ecae6f69b29fd403ef17a478dd511c9dcb91d08f42431c8db31b9b7ff273dff6099165403787e3ea46f7f27bba07006c2ed582f8732aacde030e8375e5e034d79874b61acc97bc573617434af8bc252af07f161866b91882b6e8a04252e6cefd1858242e62bcd598dcae0b8ab3982dfe1432bf506cb544979b4924dc049b4eea7d1226d76850925e6d6c7752a53aa7c4ef65d0ee9aec1a7afb38acbc9c8f51d453e504ca7734f70d3633f222bbaaedf0476cef43e562864852e6f885cb4e2bbcb24c334cec13c4d2e13b7dcdfca00835d1c3680fb1f2ee689df6c89894051bda614a55d872fb9749e231053d608098467375141a3e60426098c7a8df994b5ae88a8a7dc99df9abdec059f18d6c151849719a0adc07c3119d199ba51223106e681efdd4c48884987d63fb7d9024da8f2fc2166a960aaaed7536db49b78982ebf0de63f05016696e5ec9c703cdbe06658395172063c3c7df3956aee5dd466935714f4457b8c10749c6651b45331787a1c6ceb3729c7536083d447258770c4f2a1c271bed82e4599747f5c81debcb8ca8880903a22ce282c5650f297841465603f853ea73e91a8167b005d1d4c6b4eb71538aea30d1190e41335ba858dad315c678a3cab54c3247fb84b7fc40c6a85bffa684c02ec60a7176c03eaf12df7478b2ea7a8d0adc6355950f15cacbbba5f359f43eaea0899270ee589fcca3efa0eb324fdf0ce95f0da8dcd06769463735650463c143698ceab9732d70fb85f6751b9c586d6e5861460fb7da43b0f47ccd3386df07f473410a5319dcc52f5f1817db43cfa00901514b492c287d311ce10180f53dce6cd9b677070b51c817f539cc8aefdd800f01dc6b4d7db3e4f4fa0d9f848b44e73b16c0dd2bac24732281d4fd7ea99a0a1e331461a827a05eb602b6b96e617ce8011afd3bb3dc942b1888b1b1e79fccbad4ed7714b730b03076ee3ad295fb6c7390cfc4d7041681545e03104141a219deb04ceb65c583f891db56776063521cb33f5d2f89ddc36a4a3367d5602e5d8b150de0f4839e8ec1dd7afd27a2162acf8e40eb89bdbdf523cf0ff3a00c9e5edf39125bb466121ca67ec905593852ae6b79fcb02fa20fe44ecd9f8eb010be8c1157b06e07eb9dc736590c2fccec0af8afe8f7c6bc6d818ed002843b8145a246047732b04415a318906800083c452e9ef2975fc8c3b38f7d79e19d063b7fbeda2dd129bf857d5f3136f1f943bfd3ac95baf4f24a17a451fa711ac94a568cccf8c247fdc964af19c4023f7c8a498dd84aa51d4b9e5e3e19e328d6e6ec5a407265966eb1b3223bfcac91e32f101da601907d9b34191317883b3188e543bf0239ba57cf9cb609b71768de95f2085398b7d8f628e07e68c8772c1f78a6400d30a2303982f51c0c6d7927a461d3192882256d75faeb4f3ad6cb2ba6a7d87b4d5145ab532836543ee0463e41a3a7929a5c80d37a03662f5dedc1509f6bf0a6f440beaec1bbea0f62df8ddec8d07df4df8e651af723b8a15c9bca8a1fb31c6a93c3767cfd751c6c9d67e1911772068ecc980745a71b2acf0c0fef0613327576bcb7cb7cd61a33cc9c0f6f984136363fd8d67c681cf80da6823d0761c6f2353b73ccc6287be7017d982bdb6d0e176c69338bb111cdd920ef91dda30eea172f44836898f9f214ed5269382b3e86141cbf45096d830ae31efffbe5ba48318bddd8556e89613cf91bf9362a3177583217bc19fa1c8147db4a5d91d086c803f033aacea4ed0ea4ffa7f4927b32b0578228284f24b1709e2e0ce009582b71d8324ef671f6d7d27bcdcd7c325ddc61f4fea0d19eeeb67283c7cfea86f9d51fa04030e04c2f399b36d3ec4ae7df48c1a979a6734fcdb11719d3f304bb7cc0549d377bac76e01fb254c6b5b864d3a95066fb4faa8c064b26766b4a0ca3ef44eec47b4d29a169e5c8fe8f69ea7398d4db00a945bd61da929387244ffe1a62ba28fbcd9ef4b507eb0493c1e9e5693e0578cf0bba287776cdc64b8934bf7b01710801ebb4bdce811fcca629375bb9034536a06008a4e5f2bbd861b33a75185239aadf71bf9a3c29676c08d844a6b57271724fb3e4e0a649e2493028264907b10db7bd5fc1684b9bc2542bfe5ba20cdd541bff21663e40fb4820c6053b2734a818c09052e7b0b7b2f37f2eaf5e5f82d8744de3a7c3a7125a526ade45b16b4f2dafe026e11f1cdc4de1275a905319683d71248cea2b8a61c4925cf7700ed0bb93f6d33fac4958d7606d4b1dd8a0142f3de46532eaaac2914177640863d436865177ead0401882680ee5b5c125ab55a080deb91f139164b4cca3230f3870efaa82dac42d546de35239402317b066a01eecdddd2ab475b484e2e70a8d2cc7aeef4915fad82a4a8b4c936989b200bb6cc2142bf6783fad5a78b86072c2cccbf395400fc782ce255489cb7244c868f8b6370278a8d70d14e2de3ed341ec2ed26cb866a486d9e69c0e9c90c56d23a7b0f4b349b6e1b3048ca0546c6988c721c244a8dd042165e47c2a253c3fec10fe2844ce1802028b4bcd95dd8610e65d0dad9c436d332a6318c317e82586565a397f0ba4b789ac11edb475862bbf5bb6917c9c96b04164d3d6292c330837a834a6e2f7b57d8771858475b21f775f023bfd536a26463a61a87498d20e50e3eed2a0067797508a4f54549a87f3be837f0a1057df718c62a6ca61561e39dabca8d7c3a5d792c7834b4a212366fd7d5be5ea3c3aa22190534f0250d713aa3fed7050155df8e8aac6beeb9b467e6508bdd6d86a58972f7ca6c84f10fd7c768f988de46d71b651a3a2269df917d81490df14a3da2c85f38d29d6f090c9576ec5185bb13b9054befed9fe75ec15dc1ca92b7b6fde2f518e82dc15684e12882beb8e21ae586a0d580089b2767166696879cfb41ea6ee5398df1b85fb1e019657c8c96279488d3eff0246e928fa51d41140b4e5671ffa5b717e094ccb9493077e8436980ef410a963433a89a519556477fe33f044c62588e8b35ed4b551ffc2cd395883665015cac01f214618f4f222e88dd58e6a6621f9bf8b4a83b69cd769ba7578005fd04295212c4699a41ca3320216ef2fe42df4d9a2f3e0f5db8f38d3b6ee6ae0d41ee8189b0c1f77bb3f1a470a629909c90b3e4aacb50bc364f1d5a5931286bf29e5dacf442e428cf6548acc0260c2b08eef9ef9bcdadc2937b1868f113be22e5ba03bd590a03c6fb39339a6bc3083002a59b49744442d922abbe90beffb304d2f067039c09819f412c860638401696fb5304179e6b0862ad49446ce8e3eb152c3a0ec5fa527a4d958d0828ebd6b07f7eed2ea258eba43e0caa3b65a8300e34a1c5b262c8cf9912f957267cd5cb4a8533db128ed4ae4b8079d9e5811640517718f900df7dab14232fac0cfc0e13409664fdb5632d6f1fc0920024dfb00d1100531a72eaecc558287fedc8b7ebe7b5814c3160232974a4b0b693ac2e50f97d961d603b80d21c105a99dbe8787800274b58ac13ed633746820d7e71f0012926e118fcdc00f38f3e3de40b2cc745295a329813c0570d750cd38a8bfbc948abb4ae600ee29fc45780fa98f65bf22f68a4b71883d92a31cf7d7daca5e87fd90db8eab790cdaf5656cfb7b3a2986e9bf70f5c7dc5baa382b6ff916bb096e38594bd5ad36e98cd375baa3282cb3a0b92fd1fb28cb7a7b8363c88dc5ab28549a1d4a5be0d266dd2ce572268c15af13124dabb5ea606773be0188a9f2e5618fdbc151177ce6683d0d1b721f183140832bffffad1885f690aa20e46ece3466cb3f320bee8dc4039015e40a7cbae7dc6319f7974efb604d2bb2e050c346e55743d0aaf074141507a74df037d8e9d3d4542f401669c15ae5fac77a8bb96ea0b17852e11aaff88c1de5cc4d8e4cd48ef350069183f189b7b277232c881a7f80f39745d9da32857cf50df53fd736b86aa76a12e4c85a591a41ea4896ba4441df56c715d910c0e80d4e2a1bbba16dd53788eea52bc31a9ce086f1b300b0a8d119370437151530b2e6d6b3318d9f17834b38013165258b98b707b8a94999701adac3a27280a9eab39d3ddbb271a43825013e209c68c59aa5eb3b83ee510b2e9667ea86bfff5ac36a7bc1ad57a1eb37d783377c0496fbe63c4c64243b9cbcd48890b308b2ad60ce7ae29a79b79d60674eb583a19d291c6ef09e3e2de24d585aae84421ccd46a91c7e30ee02a900941f5b3aeb421019ebf1da8c5078c0d80c0d80f8a708408ba062b0d5a465635b4b961ca9eabdad8c58b51c080ad8b5f0c27b4bb7fe85319565bb6c6e5bbd6b2aec39e610dc035c208e129b0d35425dea1e6d61ce9d52ad66266e951fdcadf876f38f1830c67a1ea60665d307469d87ed90876772787b894208730d8b056369dd65d948d29d7a28a61f6abb35ca556c2ca845532660a134cbc825107c732b72162d309f029abe5938d40411ef9ac45aa68b1590691ccc0136909830dd4140091c927fd72534cce04f448c1b5cf216fd4188344832dee3b5fba0520eb22ec4380f018055733ab1f71e294c9b49764cee7d637d45173060491484543b23a831f0ded02f52cd10e1aa38e97749166d6b0b522657678d88d16b6617881663d8d1dc5d48f79c4864811da3396505991f10fd480a44cad4d1c1f1b4c817954af87262cdfbc0046f1d4539f05c3ad1ae9b6b9672eec20393e4b6a82895f7d8ac4f61e0f6ad04e438861afe115b0029c50e0b31282bcb455972686635972b37979d946dbc9fa4e6a5436f78ee64f6de93589fc703dfe90a06b72c0cd5eab9b20c43bda164706db613482ddb64a5607f93a128921d54873dd167f4a876c29c308ffe9879714fc7a913adf082c253be2d538648062c8d16eb97ab09e475c8ebfece837f251c35bb493a5fdb3a157869e0ec0e6f09f301f15fa4c8d29ac71177df9f91da6289c40f4aad75b5f6e40ed41d9de80ba3a1c85554ec7c6fe494b361885938d95e17e7ba30e28ff8625f9c5ddf882be7e282669250ecdd9fa7c9e72e7fd419510c895b12968d60507e130daec06a3423c277249af5f80429c8ac2bdaba2e2956e99c59115c6372bfc1fae5c0f6e1896ac0cf4b1b373a0788b680421418341d76986761e221083e2fec2dc10e9cce25f215837940578ed00dbeda0dd09823cb9d76ba6d2d24025caffb880eb06693f3c48f7d6ee978c701dd24d1601c183d58962fc588bea7f00b57e828ea4a74f4a90a88b23d2f9c0d6cd0f04111c0c6c316fc698db72a6ef005e2182945c0bfe3414427e03960bd985222f39dd44a8cb3bb31a87e5031c5c4828f5134b4274fcde13ec128e1458f2038927b322948dbac38d76f84d5d77584a150c10388722a78fc7f90e04f1dc4693664ee1f46b403a7c988d1980a6960ee939613a0f1be330435336c9ff093669a2353d69c0b2c8b2fca074f804b0f6dacc8bc403b750ff17b524183fe534d1d6c2aad06a9f5d8776b8b2f1fbfa65d3f491d6246078c85fb74076e72cd4db78ca9f1958bc8074a1e6d6f146f3ae9f88652bd77aadd7af11aedbc7028480927aaa8a237c4dd76fd6501536605927b945ecc4ffb5554ecf167d5a8f58724c8f98c5da6fe6985e3a6e7cb449e99075cfbd6f9c8f2cae047ce3b20efdb3744ae93d891c0be7168365b7ef4fec4bfd8c26654ae7c79b370e9edfe19383c4bc5ed4a41c9ae3a9f92de6f895eabf217cc5eae830bc0c67d606d8fcb5268b8b3b81b4a025d2516e55b52c6f30db25432a2eea21a8729c055375e7537d47f069dd81393719f256b80cda8405a325974bf803c3e5efc522fac037ab6d5c32399006772a40ccece3954a0add0feb620665336d5d5358a979987445b81dfdb3f2c82b3903165d0bb7a2b54f99dd445618be3e5c04cd98527483f930f92a70299afb94c10d66c2d015e1e66807294637cf0e83ac84abd15a53c86e3e03265d1d2e47439932dd645618be32dc048d3dc5ec467261f3ba702bca7de119577e3e0cb2265c46639a323b5fa01c0e90af818b8a3e173dcd30f50ff2d15ce406a69ea72c7c2ea5448b9ab9ee0c2d79ef02d2504d2095ba94080baf4946bfaeab2525aada13fd4a589584a5eb162e5eca02d53bf1d67773893640636491bce42c10a540c59fbe109a5767b9158897b240ad879c05305f09d9868b82aa36d3e953de496cd3c2a2658fab4c7a3818189e0a6c968e96d1e91f4154a645a2d3881dfb9e13012d5220c56a3158253c1dc175e77d0d5ce5b5d959606fb291d09295448b1ec48779bf0de38275f31577c624dffaec620387ac8f3f18d5b967d45becce68a010a223a3d0994cdeb240543de1c856b81753f91e61b78c08b35f854518ce7a40bce7c683362e6165d0851da4cda8ab97fec566f7853f883f683e05bf1825b657863205e2f41bb92fecf57907f37c1cc2387d692a5d1f8e8f33f90bf0eb243b2ab96713b9bcc0da6e22135c8b53e1ae2d2780a3dfab836362476df5d263dd062bdff0780216985a464c8b49127925bae0c9bbc485473944f23ffd915111914df5498b6c679c40ea96ccb7286d78da14a6d1a92d6f233d975e3d90be0d8ea04666ce746819d5866807543ef3adefa80b8379bfd353132914b89cfa73ad0e244e7b3438312d32f8bf926ecd27ba299efc97bd5baca0e0d7f0d387c044277aba218a02eef1791ee1faa3651dbd2648ef0319bbe0857277a09c5870bfcb9c3c75c932358ac02e1a576d40f7829a6dd7dc228e36c1c0967726455465c2028752fffe9515ec33822e4cd6ba99de0ccb0868e432a6eb2f4ac11b59dc3b21933c6ee72d3ac19c2721652595d1cfaea995c1db53211aaf491bbe0e049c15175103e4608f24403bc404336edcee67e61e4be8ff76a5031f379e72c64e02358f5d3c8387639815c7a5d0a5eba6d8c3445068dc195a36d05cf02c282822275e31b4b58ec981ec01e373d1a7aa8741b5e1a383b1e50c8a0990d30ad6a07f98e80d80db060931c97f327b7c9cc3d89e2db529c0244bf4940b869716b9d2ecfa4aab88862c7b0784f86a411ea6a01b381aa6fab28d9b24510224191c91980fff9f0a8a8f55d772100cdd8118f0d8ad58ac6edcb6fc4b6055f87a87f0994d47301595e1962074e18e4fd788334df417e9284c5c75fb6c395d9f465aae6e525272e2c0fb2f2d6dc5aab352bc89a2620061195561d7b018bc4748600a5dc268eb5673555240427d2ea4f7a4b656521d2785065aa7adcbe55bfe65c5f7a763a0dd65f1d81421c8ad571ee4c07a9d5c128b60aeef69a6c21f25f33dea1bc0c3bfa59cb33e8bdbe33db1173caee0e4f7a38ff9ad70759e30367eb4859ccc590e264e70adead1cddfe1a868fe053c5e30e8fb5201d8d093fd00b29eac1f2e43ab1401e1b1c47ac80c1699254f3adad9bf7aad6c7f7e10056fc0731c75a90fc30efa0e69276707b5690e5c138eb1ff42dc8d444533768c22ef03125054e53437513e46fcec70443dbea7809da404339474f6e7e3740c6ad8a98cf9fc6447543df771e2bd98a684abf93a7dfe91635ca8c430d313b9eaeddd234c407d77a806b3c562a10d83c51d8638b35acef24e91944894716ece6302859b1153e23483c2899b137ddee4e48d010986987914ab8b45988299c9684e96281445afc93687ec06958af154bcb3e1788b91005cb2e0f850a12e7eb704ba4476a1a04abc82a00f685098456f168fcf0f7b3281c7ee1d8134d3f198575aa83d3c122ea641c9b917440c384801636046d9379fe07f4ce9fe23cc80e21465b3950a39ffa19e7cbbbf81e8a597e0b909ffa18ed87327eb6d790023c403649a1ea0e4242c3930b7bf4923c4840a72f7aa69f23ebe3c0a928bf949b1208d4cde3c9e9360d31aa937118aafa5cfd18c7d23ee19e2422274fae23eb3ca388a57422bec448b810a48d730024716ca221554f3262870135984a1d7dd2ae35bb8e868c2860fca38fc917b342938951398a41ad9d9d0518166db87c789c84ad46923b49f621db24e02666add6da0216c17961bd10cb817873a78a8dc3a673f18f3d28a60219ffdd44c994cf24083d0c6261b535cd3244fa6a322505e24e05695c535b7f1d2d2ae7a518d930a6772858f9f54096bce3d91b65008e87ec05bf9e3587aace5fd441786c4cf03ebdd0122f8f95cf38a5eed00be2e193319f52f0bddc9c464dd0e391fbb8e65189b371969b9c0cf69f5a442477d0377e10392c002bd4116ce81a557492bd4475527e51aefd58c246713536601a27dc3020c7465433b33e19c32ad54fbc939d7286f0d67f665649f8f0f6827ea2ec74b9e725bcd57b166fd623793c87cda8c40f6b77c9eea008da8080447b163c4071a1ed1ac5f6a18bc0d20b2c5d237216d080c65a439342005566365837a56e1dea4cc27c5da22e49acba20ae184647ee53d0dfe4976a0ac80dd81acb552fc2b566badf49e98add5ea7bc65fcf993f7704db9e5e311f20915966c34952a33cc5497f2bc2f46f70c899624bf365d1ed03858d5c94ccdc45b33ae9faf75bd707926237809581322b7ddf2ab122ef67106ec9a7f4b31128cdb3662dbc7dd44b52da30f62657a04aac136f93829fe699645aaecdc40d30af06670d7d887b862be268ca022f3df01741501deef93df3c6f5e4f72ee22efaa0c110fe550fbacc9eb4ca9eff6c52048abeb7bc5c3054324a3a8a9c6a866752ee2b87d2e5a56f9aa09996d60fe472df3bf78a981c104106d055bab645e32a7c6990030339c3868cac0f117f8f0f99c2bb01f04f02828fc5bae5a781ad6c7204cb28ff6fadb5dd1a2184904dc8de5bee1d6e0b400c5d0bd8bbaeba66cf60990f0784cd1c1ee306ce2870377054c1166a0fecaf5e9c2b7d7bd787c3f41bb881e7ed89ef9905849738da55354e8591c86614deea9b0b3b56eba5ccbe19d19140305c730df892d8bb2bc8573779cc8b4211852ae59b68871d056291880079ed36d16ccf70b1b61c022cba30557cb61ba80115269c68c1028ad1ceea5f3b60085baa484194273ec8c14eab5a2a2fdebcb28145b826277fb86b60b52b3490c0b0a8075b312c4223c9d534896dcb1bee0c348156d89a0a9bb92dde02365bdc378d67164ec02b709f83f0c8082b539c24f922e405a2b21c810b3661587404237833c518638c32ceda4cacfc9c39362a9967c1f32d9a2f5d302c3a43a80342a08da6c1195e80e69569c50913224112496a219a92044977e9a4a32b509c0811895b225211919224427e78eca8a1e9a32b509c4ca149640b52119154229378e982e5280a9436d2425cb654298a4bfa533244480f1ecd84ad6122767fe2beb9061c2fc7a59d58f8ad5bc2396789855b4a596235ee3b8390a0f8648bdfb4b7d7c2d86bc5b2c455cec43c17d08a28c4e78831ecdc9b09cb5d3484b08d8d512716fb16b76e296789c543b17689b59ba7c4f95a71013956b172cf8d730bc76fd7056863477ff15f7c8eb8000851c81e57b8de74f3aa7637666f256cbfb51b24e20caf5cc13e2d4c7f79ac162ca6b798de3a2f4ca25736d8caa663595de97c03c3db348e4ecfe4586badc531df2f61c67833be60f9d32b1737c7681ccd48cff46b173bbfac8868468c6852703f5324185e23a231313131313131313131e9191313274553b044c9e2040a57b3608947fd458d48c39ed81c8f302e1a297df94a5f5efa622252fad2334d4b5f4c41a624a6a168c52414642232bd139eb0f3c62bfd59e92fbe67d11136d32b589aaef4d7c4fef44da65784f44d365df1d13779fa54c1f1fd1fa62b7d73852621997269096262051b63484a96fa6549159842e37ec605a6d0f44a7ffddaa53f9ffe70e8959ee963a9b139ab22dae5c8a7bf964c0fb4d1cfd80f0a17a0011fd0465fa75b893ebd82a321c4821bcea8e015dca71d0bd71205431874d105677a450b11bcc11cad8b74e322952af91396b0794ed930d212341d9d94e892707ffb76f20a4769a39cb2af29a92ceab70a37bdf993932ac113633895502438d7aa65858ae59545b2ca1236c72db8bb6ea9fd757f716b4e3ebf060e6c0ce1270f5f2584284829bba40b88213382906c8655f03c8723870baa9eb7c9b9d137d94d6972cf3478721c9e1c57a27f7ff19dd7f23e2df3f20a03a960673178248b30c29011829292bc206d2608af5871c28448109216a229499074493aba02c58910912d4845444a9208f9e1b1a386660c8a24aa7074058a932934896c412a22924a64122f5db01c4581d2465a88cb962a457149543244480f1ed0063cac8136a08d6f474bb1b25bc2f3cc609afee0c41047204a3b76586cdb120ba7922f5eb2609112450e49236370b15285492fe9a02f5ab24c911287927c72b4b65e62e39d1146485570801300e10511119e0821070600e930041d9200a38c140451a4e2bed9c00a678b57567a5c9295cf6f1c2f0eba48d1650a234bccf8b2937f03e70ff7111c8c8aa0b1c31726453bfd2a7118e08225d2188211906e10849d05e00c88b10313297af001d1183bdd3571ca065bf0063940c2880d9268418427845041061940e0a045288914c4b01516d9e0108bb1a05d7b5f42ad65768ea5c3be748859dd21f6362fffe0eda6cd7459dde5b1b7b495381d296fc6bc2cb3bcf907fb486a2a79f907972a8eea723b0fecaf1ef3342f7358eb2466af79a5abbcd9334d54d76979b19f2e51b75e77b37cbfe471f8e461a6cbc2b494f4b06bde4f63ecd24e793d97c4c1e13b4e5c28afe226c26688bd9fb126c2ca9bb19b65d7a3bc895b32fb766cf57c2c39ee74d3ed4bd7ec65d5b3b263d7cedde4b13256e5b8cbb3340cb234f9567fb2bb3ead7aee98f6ec72d32e6bd38eb1b6cbec37b3b07ee66076e1e5b0c6d184e363b6389a5a4bda3638c46eb15b4187f62c670e6796848faf919747bbf0b2341a1cdf8294c6dfd02e4f56eba9ccb09576f8b8c5e7d82e2bbb3c955e968eec956e87f807cbcbd2ae431eacedf264b7c6cb82f76a8197553f0fb140facc4a151eeeda0d5c73593947c279169587f5d6dce80f9e452f4f8efee079f407e5cd352ddc9775651adc5f61df542cecf268e7b0377659f5f418e7b1aeb3eab1cbcf6b97552f0febba3caceb2dbcc2f072fe66d675795afdc1af700bc3539e792107a4c52ac6c69a81e762cdc073f1cc0b6f3ff610563ed3eb03b160797d6ab0bc34c80a246314c1c6ba4e2f0ec21466ad2d84959a09ddb16082095aec56b018484cc2f5402212ae990d1b97ebb56fdd454b749a387aedd2cbb0fcd46e8e9d0e7a0760d8b5409f9b0507e07a813800d76f7693516b394b3334a6e1c2f5986c81de692f2fae303182afb35ab8a894b93efbb63517bb17aec71cd3c0350d9c613aaed357cf84af3bfbab2c98f0bc404c40588c5436d8ca7d84af6fb1f4cd3be589afe7d376f2b8d4392ff59317f1867941222ee104296ccfe9a89f8efde495f015cc31d44fa7735ee630972be6583097c7ded3b173257c6beee49570d89daed3251cf593977ff0e9ddada0c35e75ec3147c13c577c82b9ac98cb63551775b19b7f30f6d375e4c9ab71d8e95065d44bb8a7b350e78eddd365a12e8feaf6aadb1309df2267ff191a807f22ce40908855aa731c09df82603f9d849bed555d1a223ea5aed33cdd6fee232b2f9f0e5fc3cba7ac49076eb8133d34c41a643e84759dd8ee44de4e43c4a56fa8f70fa8cbe23946fd18f5d37f2266f1a05e8f7a0d12f14fc429d53da10e1b87f598989b2746dd9ee5c19eb1374f7c3aec1b7b14eaa7ef947f54477d4379d987c33daa4b03ce3e1c4efdf4adb954ea30ef8ea73ef52de5a1be6d3a5499c3a7a31aa7626ebdccf1a81e73617f8dad97edb5cf6e85c6a8a7bcc6b083395d75b9fe4ed7e994873aaa1e757bb6c793972d7c0bdb63d4911c96571862160f2cc2d8ed8934608fffc12e0d11f79ce736e0f820d6873880759d88471a943717583e08dbe78146387efea22dc4275c3ce102c7f3340d583c5f9627387e06116df0831f3da4c93cb07cb4aa95cc5b1f4ddf945a5f8f92f7160c9d27f318b05cc9c880a56ae5034babf2c93ccefec0b2723f58ce083f4ae5235017a5103a08f7eaff6a6a6e148ac2e6283405c90a16dcda6b8d566a4fe1b25a592be51ba971e2949ee94bf93964afd3710a51dfa0dea7f763152112a7c6dbbe1fa344297d13f37e14ea9bcb48e3f488481a270639d3efbb412f356e86bd40f1428451cf42701e82e3e9a56fd2fbc1a74379ad7dcacb4038f50de362bcd2619732994c670162d3351d0847af243d9397e5edb7e64a9eeab063411e06973c200ca3b28ff63a32e5d98bb3f6723d244e0e9ce9d79c63fae91275daa98092cf399d0af62754614f5fc1744f270cb17ccec1fddaa73e9d4e5cf6e1bace769dc99a4c5d3ce7994a2593e912964c9cc98461eea5ee264fbee4759d0731972dd63c25b8a1e05641e2c49dbecacb153c2fd4e90e82e9d216dbb6f50ff6fa53dfa00e0f6f34f52224c151077714771813c77147c19c3b6d9c973afcd63df6748f87a9ced46d5b9b3a2f6fef3853d7d2d19da5ba3a58dbd501fbcb38d3b76f26cd43dd7c2a8210ce2b9cb9535484208c5de6eb4d03ca3a1562ba9b1b77573df3f2ca07d6b20a6733d480bb9f9e6b56e3ddeb551e901a22ea308ff162cff67818ef04292ceafa7030be66d72e5471dd37edaa504f3dbbb906abbed1944a25556bc755dd55aad20a0655577a77962afbc0dabb7498bdb49dd55d9ed69162b5803a0830ea10b7e69d6eb6d787c3f15b0bb2d341b9639ce96e80c33d186b069ed2b37767cdc053e2de5d1eee66176a87ef216c76eddb8b7c6a342ffb402c38bb34481cdf41d8eb11ffe02ec25e97f770c44eb194c506fb666a8f8f18d64120ce9ef3568d76d7b66d6701e2ed4058f3f2b11683f6ae875da4cf3c201cab164ce9a37cb2c1aed0d2a055f18acd5856e196524a89c1141e58ab3fec50e6eb0fa3e90f6bf964d92a4fdc72d20b33c916581cb3d51f5da9563236187bc572b8f0b5a4159b73708c715e213f3e60cdbc5c83e157b36230fadc1fd00a0e187230fccf8f168e41861a9afea80b5491c837e8c520207805c357085544faa38f423006f9c425120d1bbfc18a5d74ca8eb5948560568e5b9ce13d818d8707aa114584cca271384e6661f38adecc5a800a10857e26a185230dd66ea6361e08e7648ecbb183478fcb836166e802dc2ef4d7cf2bdcfdd56ab5dabe7d6ee7b6b9ada08d7ee6dd6b7dfdf537e9219e17fdc4f4b2e681f0c4f2cac82b6ca43d6c120bae7d30fd41b3105983606bf2c4dd5fc7ec422e5fdf878c1d57f19a114b8f0647cf76cfca69da6bdf34ce764da35e24d233fd13b878c205ae9acf0a6797d5c2c4f513d7cba207c2ef0f0b1b0fbb7a1fc2fa90383a72a69fc485de9c1b3dfa6b231baf2d36a44bcb2ad6ba71e3c60ddc974dec8d9e53b6ac19320508603869e5607a912e2dab5896e5f4e8997eadd66689720496be8448b50fa4808c2e9660ea93733424955c5a56b1494f90c2c22af8f45209bb7dc171beb5db4ccb6ae821872bbcf8c02a78566c03d00ba637163512137776e5e0597ad4cebddbce95e6fc36a7366776ceb39b767f26ccc13df34eae481ad9605fcaac68e6772dc7751c9088e975b34f0b6fdad7df3cdd9a7a2d4c8f29bd5e6bc01448a4e96fbea6bfd97346484bad39614e176909da26870d4ef7b6691bdcef21b7daf2340f76d88fbb66b5b51ce2a83ab1310857088fb018ad6c3999a6b339fad39ef274e48c7694972367b4fb9038a7ab7d87c4b11786ab5d8d8bf69fbef1e1d337ddb5ffe89bd2b50be99becda4b1e4d7fda3bafc6b3e94ffb2667b413a4b0f008cbfa9cae650bb61c18600aa90f5f2617fa663b0c8ecfb6bcb9e633dd7c180c268888ea30d95bda0da9c62071ec86a421994a5e703fbb90be29bd3fa46fbab7e512e3f1e84fc21003b4814307cecaf597c31e69d28b4dced0652469bbf4b65f5e6ee515be724a572c26e539a9bd6ff26c3c9a9ccefb1036bb66b35ab369875570b74a2d633094e3922489c462cd850b1697c8211864975ec669af583f12a148bbbc6cb7cb0f91b90f61ebb35789b3dd96b2a5c4a4ac38d7e05c715fc861decfccbab780b0c6a557e39797e1157c7d469a1a7cd9e02b27dfc0d7956115d6f5fcc93c1fc75ca8e7aa7ae6b0c5325787842bafc8d037f947e2124ec29b0412de8fa16f56f5fd4d6a4512cbc1320bceaaa8f338ef43d80cb3f4e81beedd989ce912cbfb8136e44bf07e90e0f9783efa83a167e46bdc0cbd60197361bc1eaa94b7c3851cfdc95b93a703bde9d595c4b1724666dacd1667c13ef00a9d972ba1af28e0af7c7d5eb79ac0f6337769fe99f82b00c2ea042e9ed06095655996cd1eeaba1eeaa2da38a9639a147cf511a13ed2432dd4477a286b232dd437b0878afa4896b55066e54599cc2552492789f8ba8d44a88ddc1eea0f7b093d94b97b6197da7573e9d8755d4f4d6bbaea32dd4c4bf767ced9dd1f89b1be2cf4c44f8cdd9e8c8bd247b863e76e4f3cc518bd97c679e9cd3d84af3e1251b82eedeb6ffaacf075cc870c784634726b85b30f3c0f24e27837b0c2f3f660cf0ead7ca78276d9e41401a1ef9caebaec5650713d535b94ab12e774799b1d6b823af59aa09ef26ccfd06f1ea75d47795a2b4379a76b5ebc8eb434d7c9ab7246d65aadad3dd4474a7d7ab2c5c8872ad7afc6f9e40cd7e9d8307693f0c4bd44629fd8e7c490be884957a7c295f42f92662fc1ae2f72240e0a75639033bfaebc9ae94af087a1910f5d7ec0820780a1d10f508c7c90324404de2447894c8cdd0e925f1a63377ae96f22ddd82522dd98d41f7d8e49d84f677ab189e3ebcda6d333f7922af57a1487f251c1fa7ef753d68e7517f64caeb7bbd94785eb3bd3f551616e7bb7bdb3779f98bb3d1be7a3ca3e2a4c0f6ff71cb7503c1fbf880be04e319769f4ba3409638f5f441430f8fe647c772b488961e3a05eeb515eed19ecd19b327d735d791983c4415d5294077b4662eedbe6c19e2b6819f5ea94b56229a5bcd3bb3a92cea42f2810892935b549081b44c1c249a36ddb9faa3ff9d933af6a0756464e3ae79c387a398e5f5cc518e361152e4d7e68e207650871c616062022230729507491044507105e93b616057c81850aa22843a8c2041224c38995a020d060280850ec747ba1c5511845c0e0a8084ac230c10c92d03285952bb6ecc01c0c25191ac268684887631cb3638735de791368707ca5ade6fae6463e67c7be651ed759f6d91f775cdfd95ef2d9cdf59a40c51e439c27a4375b2c57f27d83757adec048563119c92aa6234de4e09c83e15563acc1af5e20994952d92707d363d7a6bf786b381eb0106cf4b6fa5b1d0901de70bf1e60751e7c8051181af190055bb1417833e76fcef0f1664ed9328d3416d0dd13246298461a38857e869f2650b1af0e6819250e4d6794179d737e65349443b6334f736dabbf99be3b0801e7160ab609250ed01354e0400854f4c0871d8481831e5e90eac18b19201bb448c208084f6450451173de264058614406972dacd0d2c311a71ea2b0229130b64829438a19536c6edc15f4208ba1297c2082085d1c31c2a9872230540728d882031758b2e0410a3248d28509bcd8e20724768088886ca1011713d8e0cb961b1c99c1912f6ae4d07c363760150086463b6881613034da610a9cbf224ac61746bc18192132bb7910e27840422f02786882041eaa441e8448f1f024eae0150b0f2cd87838aa3a3880147d8b80293b2cd941079c3f4c89ce417f919383831a83725a61e000890894747122a984a4289840019431bcb8c206305c11860c5a904608d08842c60598e0106505383041d00bb254a0d5d262c2a256c300d2791d422e7248638a20453d9b9b43971ccee82cae19b30c8e463be4d032adbe812b0c8d8afc00a730342aa205d39e30f811c30b308a66b0e58b0f7e52e0c30ac4388285cb0a6a00037d81133e5c09010f64f000c2e009237cb8820b1f6ad0050db6f4d00315419092f403282b94f1841256b440083082d8815c54e088a5aa017170c02a26d420f71196972f38220b1b8848820766f0b0937b09169cdbc91738779496a2831838f794ba939ba80a96ef2412e78671451130ba443186103bf2dea0785eca4507a2920e59ac90e63c800e46b074982247a70530906e7842045bae08c20c84b8081d2100402938420e51b8a0044690a8418e0e04e3892d704002285104310219085191123a5a012101060f5dd8f0054b0dc4d891f7468e0e10910f529018e2614a0f44d8915f12276a60851734f8418a1db9859012935013a81bfdc59c1a9a9cc689413a5d0c70a639aa37d8600bc379159bce4b29e57c81c530342a62a5c815970b39a49445aac0524a9ba21dd5d6c0d0a84893223ee05c8354a4480872d86294c3d19178f44c9411c38223870b7d13b3d8bcc231fab4e2bcd15fd4f17060612786463aa0b163cea98319784e3ce775c18acdd00af66c7a263ec78e2ae3bcf2e66aa48311fab6bf7aebc2d0480721e05c8334872e4af194b229150a02534a841b7b98426c61acdedc562c9c5fa00049891b987012c5931dfa2a7126f00222b8702246098696ecd0dbbe59fd93f435b7b3b0795a235c915e82b0f31e40a874839407b8a1cbe906692a62dac088881148b924f100e7e845c3397ee9a026d24690269e770221c84a0e403803ca1435d8c9314a0c708e53b2e01c8bb0c439560143eee478050b968f4e248e0a38fcb04596242c402c212550145fcc48810d4e54a182c64e865eb4c03906b900e7288425ced108169ce310921f12e707891da2dc00891e8824697c154dd1022124509818ed4018e00cad40911962c1810ace304bb793a1961c89e301a41e90184561e2c350af404a898309b094382c71edcc9d15c4a0982e8c30737062900b376cc1270c8d6ed88206cb1d0805e71b386bd9cc6b7303ca1d224fb0bccc610a30beafb0d986c6a6631109181a11b90277181a114101ce1f963b5c6d445b58660a10b6f8b4f03571bc8282be3c750ca198670605d1d010a210670cc2406e10ec3f9507042dd0cb2b25f882250b40f8e2e46d1a1a6311bb6e7ca16508e0c4d9878b462e4a2fd803ec3127a53a72420abd6824c6791867cfd95e14ea41299df013c2f9c5b66db0fbd03ca45bbee04d7231c6fc33f135b1538c520f52f8ebd1bbee1c8abfbc203c8885f493f4178160d8ab97fec0044babe508eb8e7aad94d2d9b1d0a348a742affa9397b1c1d8b3ca9abcd23b8fdbbc1c8d703dfad14813d96cd24ab19fde7067a865762a4c1baf22208406e86283f726607b06f6070b6003d99ffd51a197f9ac3773b862385ae670ec9b7f02b7ef2f1b908023c14dceee4f63f89e0c1ae1922829225424264942a7d8e173421b990a6787312976f92222c1f8454c9a5e4cca6a0caa60a320b0e0cd540452aa51af87e67c0d953612085fe1b3cf36125198b45301de16c20e7560c77aa8bf4931ec51856bd2490222a1294453685205f6ba4adbb6148cc93ef372cd0ecc959ebfaf52cf2b9c7119d7a95e6f351893d6b36da66dd3346dd301a18dcc9a5237abaecf2a7eab11537ae6edc05b96ad32bb71f9f4fa92c6a934edf1302a2de5c5671c972a3dbbddb56756cbce71a594673ab7d2744068c3da95dde4119b0e5394404ee124157b1dfb3ca18c8058924aa568666d2aa5699afd29954ad1db6ce25087cfb89e2e76967ac771dccdacd47d0010e6b8a7386dd3ac77bad9747d569b111043464008718f4fddf4ad71eaaa6f5ee7e51db8b3db56034e7ddbb6adc653b984e252271487f27ab893b7c2db09f5edd260024ebde35297a5eaaeea9eeab69b7d608b3a1d7e3b79d9ad678a3f8759dd7bb86fefb86fe7364dde2c97d8eb32071f72ca4654b3bdeb38ae765c677af7f6583a4a3f6de7bcccb287d807e6e4eb65d9cb53baa7bb9d335d1e2dbbfdeee64e65519ac9cb3eb0e99bead9467a59df411b99e55e6be79dce79a6af244e8433f234dbae3aec74f8c0a5bac168b7d74f9ebd59ae304c87baaceed853bfba6f2aaf5655ee0ebbced1c96e7bcd732e8bfbe9a6afec519712421ba7cbcaced2de7d7bbdf520b46182d046775696e9a8df58997c0e6843be0673f5aea08dd2f559e538ca5edfbde6b2b86b975f6d10dad02e2fabd32e2bab677197efae566f66d58ebb3c1b8f96dd15b4512f4f0e68a3a60bc1065fe5ab0ef97eb69d7cbf4a0f426e0620bc9dbbdc2e074ca8d9ebdbebae7b0010565d562ab5c973efb60de63e000867aa6fcf54dfaeda6a8599cbdccd3eb0cfa5977de05abaf5b20f6c8ffa16f36cb195331486cb590e2839ee3bb03ccc555e5e61d53b2ffbc0dde9b06341f56ccb3eb2772ad4ad543de60140b73bf7ae3b07739bd4b84eab6eb6352e77d3e5cddcf559e154ea74f30a9bbc2c75942e8beb4e2fee1dd7833d3ec3acadf5b07b622985ad979752d83ecc595e9f156e81a2ba86b1d55fcfa1979f5fd1ed3d5a84f0b2b05b2d636167d537abd60a31ecfaacb0bcacf91ef9be7e7999751d5ac135997561efb94e8fdd13aaa014d66068a52f9016e7123ae890430e52283e9142908b275c34b18a4da8a00081e3555080c8117790f89fc9e2819797f00699d80756c1124248bd9686332c81b0d0934e60587e17dee885d52c92b10561a9c599d2274c92099b395c92dde4e8766958a69add91cb9ccce5541577e3cce1e66a36557d333184373ea6bc2c9ef92ba3b0f140787b82524a4be0054228693e176a6a72e0a83a1459d856f01605becad7b28136609423288ee61230d4e74b31f7789fc59be9731f086797c5a33d7b965dd36ad7bd7655c77b555d970b58c5eacd31715cd7756b66d3c8392fe7fd2e4cfbb61a0375715e89fd8cc988c9431d29eb849e183930cc49027a3e3918429885cef45495526a616831699dabb3091b4f5bad1b3957172d847968a38188609527b04ac7c00598c2101a21fd459c1f7226d6d8dcd0c191a3041b6ca9685632ef1b088b60dffa68669d70be55b304c378601bc530add6d8c014380ceb30ae5a8cc3d80ab460ddc7fa10d71f585fec1a426b3ec487b0f41206d885fcf4cdb13b7fcc0f61a54e7689b0d963ddf767fae8af6390a1bfb652aaa91ae7f9401bfd1aec587fc19e7f7cf8fce81b6e49764c3bf683e10e494c8271d7071ee1eeddc41987c11dec34314a4dc1065b3a30851fd0866a55eaeccd4c65daad7811c3d037387258d19161330277cac1dbeb162c33050860b47cfa93c187adc1d13734351267a523b78869fa46d5dfea93d06e29cf870c66d81c836052d0189148148a468670c1db7ffc0891c1874f4e8f1860e0b123870bb631215be76556494008211216caac5ea0b275ae6c6a68b8d84c1b58e11ec83aeb92bfe4b79fce102f2b7b8f6461c7eef6f84dbbf097079f7937566904f58148d3cb3ed10996598c60401bf088238e20b2b1ce82f37066c76638eb7a0fbdfc75e9e5b30bb10ba511d2086944740291e21897f9301c2f292f006883ce2172c2f929e594526230b48ab8620c2782ce4b292576d15a6a411bf03fec103f936219c3306c5e727b023384104ad56338113f14534a655653aeebbaa48450fa607db03e9870cec9d5cec1884c1a43844425d006bc8f150fab1f3e5558815570e4c83922430cfdc1d7d014d1c82a6c6c9a5573b24080e7bc30bd2a29987ececb1e6ca4340a4522fd0dc5a0fee06f4c129198b4a0c24698054bbb6093c4c6851c5d6ca4f9ac9873ce534fa606243c97081197a8b62d079e979e5462e3e573f0ac436c2c9e9f89e195e166192f8b275e16cf65f5cd135f1ddeb0cbc25e6f90897d7270f5b1c1d981301006c21be5b853eef6e80f7a3afdc16b5ee6b9502f7950fa49ef8e88b7186d3c16cf84dd0b308d34b0945c943b888225cce1ed4fe3c41c7e488529f8689c1c7246a20e9f03070cdddf969b13ce6a15b7401bf1d58bf162959e41ea2f1ec68b59e44cbcca8b477226feeb1b1a228da33a7c34d2374311097d33e933b62d5583eb99b49366c261dcf559c5a29417a744a21c9bb229a42c5852a829f6084be30c992271628846b644225ab84c49b222a4bf78e14fe2e4f41138cc6eaf18c7611c876282c50f984f0e26c57a114a94d5aaeba2442791c9290a759ec4714964d2382ef48c90c4a9d1220562895084724c92a3bf2871e2183d23b79247b1ac32461be99bedb2007165d5436c2b3684045bb279e99b18244b2c1ec9dd0c02bcbd3ff9c90f21f18fbed964ba87a469b56a95126f77d55fbc70e75d78537937bc9c21d81cbf60f91cbf745013e9d237d9077ac1dbb9f4cd181b77799deedc378eebbac3ae858ee8480cbc5da7391f78056fdb8d4bfa8bd149641295f41793c42124fdc57794539faab0e92f5e47ce441c1a2883e1558d53a50d4d8daaafdb45b035363937faa67f5dd84abbb96a3e1cae59bd5f7fefaf8514055ff030340e0cbd83478f1c9c9a9ee91d3d731deb0ee157d7e1b9eb496c865c7c489c5aad95135f135f2cd0122f0bac1f3ff191081d5253e7e50b84f48d8f0d8ec7d13987081ac795b8aa993e31ec34d3fd99b8eee8af73f47324ce8d9ee95f55ae7f122722692fd74fdf5cf7e1f3a36fb6254ca29308a56fb24fe482b9d773bf1e93f4cd4582af2886b882399fb86462cd9bf83ab76a1cb873c528b568ce39e79c331681c3d087cb028a8dcf3418d2e739e79cd4cb016d401c3a5a64569209482985f273b6e59a48ee60e3bc19a3973d724e7a3f6883876cbc4c7ff0f491d2793adf43e7ade90fd2c820c34b78fa2c25bc84ef9131cacffe0e69ed1b7921ce524a19638e18f6fc01ca4c3f630f8d3176454305aa739eaa0676d4b1d331af84d33d61d8392faf30c7711886612be888b9fde92898a3b054ccb517421ba8a3eee9ae522b8c9d2e96ba7daa86548154412a55e366ab524915a0542a14cad640a17e3aab464cccb5d5aa6056ab98ab2e8436ec6354d0c6e9aabb82366ab62244444444924a8d735e0d18eea72dc6c3ce79aa9370ecda63a773a8738ff1f20ac7c4c4a06c0a8b5171dc75da07e6dedd0a31c7ecb19f683001a37e4a71a9679bba2c7b1f0084519765df837aea363531eca7733f79d9073e1df3b20f8cad0e3b1da7abee0952d8d3f55961eeacd3638e5deeb24e972775d453477130920a0af5ecb3c2a9c7482aa9189d8e81e1606e46a5b81a354858ad581f5242cc2595981a3703e11a3f7979854f8fb90a9f54313fa96eb6f8747b986c71cc515ecc61e394707bd838b5040ff60ceaeac8182ffba871d537554f8d55d7dd03806a740d2f62d5633cfb1a5e5ee11a971dcc7b626e0f1373fb98d738cc55b7468deee6809855e33da9c7bcc63da10acbe2493de6a90bfb8bb9a7c35cd89feae6155ed9f8082aa104557345db9645e7c6ac3a382ef3b56c6a6c726a8e54b65e66b5b564fac3b04f48512a038d2dc3e7dcd7756954e6a8b0a346cec84f4a79b9da6846bd69e9d4e8f46495333ba09c8971879ce9a141e2261dc09e61d8f1e011838276301eb06807bb39e40c91098a26b57309173c41e38631ce48e3074f1c81e71349c0009dc048680c29b64ce922cbce54616864821be00d432313c4c0057d9986cd15f7148993452b590765595a0829bbe21439d3df62f0f1233a8951603777629033ad1dbbe6c51dccdba15dbb20c88eddaf7a3d2e4b021808b663b76607ee6c5105b9a3ca99d70fa9e1d6901df38638218158060ab208dad98e7d480db786ec20a841c38ef592762271b086d209c8b2231ec99976d25f7c0c8a4272a6770891337d96046a763f9604b0ecd6c899de7139d34bbc762267fab976141fb60a5388bd03bf994a38410adb4e700c02a4c7e9f53daea3ae4293eeb0831d762c1661c7e294180575e391970eea6eb4526fcc9284c4c5470c3dfab307dad9e6bb2b644974327fc420ebc52451682808902012381dbb04ae776701e226f5417a0ce1aefdf41e434cdf7e7db3a7f6f441e4810491004b0299f6ec70a7f4203b4ad7ca99780ab024503aeaa80b82eda5c39d21403b2c249133f127af97c89978ebb17636e94500ee10c95efa163b09f4d87a703d4cb785e44c7ce9b2e4bbfb1e9233f19b9743ee9033f199c7927747c5ee7779d463f5dd512367e281766a2801c9bebe4c1d5a5328829106dc81429145ce225f9ad690f352deb00107453a4ca1c49323951646b058c28a1ea680a20c28cc882282d3b4a9996440d1050745187242231881d18327380c0931831d7ca48c2394b0924391d08ebcf66b411103e716942d3665a1d1c2591e0a0e4a3e2d3cafb2d7e7756d1aca165c94cfd9c1661b9b1b47b0250c8da0ac00670b8b28864650aac0f9c371f60462eb4b9e31e9e79c2f10713122881f9e0c71b143801e6c11060f5ab498a28b31ae542c93ce89c9a41e3099810e6c388108766692171011850e9840b2818a9d290686820a1456e0d9edcc49299d5048c173b200cf6b674e3a390c8da09c000a12948a235734c1c4e419f2ba949248765d997a59916cf29a457cf0c491eb091ea27c95572606d16b53535b404e424065882836d8c11747809a48a20a108e94a00649a2f8004a116c31346ac28c8b8aeb00473a11a261b32db2c1bc1b5fecc4d0a80922d8d8268230af1d6c0543a6d5c418387f2470a28b133a28aa615310c7065ed08625cfc836385e2563ebec410d9c5755c4da0fcae078d98418d0054b4411060ac8c0028b1fecc01958b101d20faaf08208507a20d392181a35c1e5cb065b2cf0d891c5f6b71879f4b7a33f187c86f40df61efdcd1864f0f1e34748df6097477ff377477f2ee068526fab67e66bb76c0b0c9b393ce7b7bebe5dd761875d17ec1a0619b175a1a563b0c1d69c5a7c901a1c2f10138ecf40b02c9eb8a406b81fa406f7a561da901308964e79bdd6983b48ed7e8d4062a489ef470ec3b9ff13f194509ad0a23691c50c0086464d34698208f916ee96a7c8bae087d8fe58d75da1f1159fa37164517f070f3370bf470c52705f8614ecf47d7c801b1a3d61821be3f44eff82f1bb512371883891630f61e0be4cb2181a3dd901e71bf8852208dd26587f25b17b9d5e28034a7f320923268898500707133a9ca8ceaa992042e74ad2c06a181a393103eb481c797fb071c172909980a19193229c72e2123ab255ce6b25c3246b19ec14bb3a72265e3a1902cf3971c0229dfee29c4e4680e785e79cf3caace49da97182c310dcacb1e92f263184c497262e88d826d7d4f41739d9a48a8c4116075fabbf263f682206cead279757ab7761f39a57371246ae26b0d65a6bad59a6655ec45acd32ae3f4ae3d5a9209b762ac8d7caddda1fadb5d29b7f66bdb24ccb328d5e5b9c58ad57bd31ca75cc9bd7e7ecebaa4d54b18b5e1737ebfb83efa8910003f6c26c18b075aaece39ca899a1ea9c3608b65439bbbde2a86eab67faddaf557261b30d2cd2c19123a73f1b4dd334086d640e67d44f2a4824db7d7b4e3d7b76eb658b6d2a762ba0aebde93af274e7cfc4a55bb3f66d6e55d3146073b59d1de6fd1c1227e66dd4e40a6e6dd3b458b30b6bfaa6ce396394a93aec28d0542202e6556fa222e244aee10186f17680551caa2b16769e5e28030bd7371908cad30b6518e90f6279b2fdb192e066cdbb42e37968748228586636ac7004b013df3ac2f19f0a76a2d1123b08e0061a296105c7df88c731bd178a40447a2f88a1c57b410ca48ea7f10b4510ea0fe22bcbb0290c8d9864c1474ab6ebdb15e487c3401c80e30f1c038670eb5800c2e2290167a02380c01c60fa8dd64b5a8fd1eb34d6ffb930d641b04a8f35a5f44248d32b4898033cbd79af4b4929fccf85e97307a1d73c8540621a78ba00e3d09c739bf3d22eedd22e4d9bd183f53a26df52e219f1d4e475acf6f4a516e51958cc1b10c91b10e5c0e616f696b20b4b995869213b8f767ab3f61ef98bcad9237f95a08aaa71e64e9f9b82b39a6535e3a9ef91ef91e7e4abd733b97a795c578304841042284303a584524a08614d92ad0636762a74a7c20c43866665556fd1396909421bfd1ac536a9cc610a12d39b7d5ab8e2c0568973d30228bc30e3065e7248b243c14862496acb0b7e10820d8e8ac460477a31cf98df7a42f843215713f577358d58ab71bacfaf240ef7b9f54b3be4cc843dd337ffcc8cd273374729f41ebd391ef37a4a44a11f9b3e7a4d25a2d0d7e6dea05e4e7ff33b244eab67e669f0cc15572c4da5a9482cb5af1e30c11e864645040d91046f261aa33411d69d6e317bb395ae62b18addacbd6a1a8655fb6aba3db1897a503f1d2b71b287586bdb0a96b256d3b29b4c16bbea3a9db257a94ca96ba99b75da51afdae312a9953af9544aab267bd535694f3d2e91dab567c76eaea7d76f26efe4e5d251df2e4eca600bdb533ac519a2ea4bffe99b6ab992a71de595de79d8abd79d6ed6b06359f638d437dda592be894b9c4059c2c4749b0ad63d7637d3911c966544464640c113d2605206344de576517f31bed26ddba89c81cdf6aabfae2d6c8d3f97556e9b1111494446430835959e1251a08f51faa318467934ce457354ec12bb6ebdf0a2a255e2082ff8040c8d8eb8224bdf3c4a4bb4542a9524dcb6535aa22fcd6dbe624de51bf792972d963d4448d7e5eda6ee6620dc954adb6dbc2d5d4796b65267afd3a6d239eea78b70d7ddfc33658cd9e2bedbbb084d9d29e6198070e95cbcc914df44b77697e7bca61251d8346fde8c234685114ae6f59fc61886196184045b301623122cc12ed090287d33055e1d89c92bce9bad74951c715113494cc326f6d94538cb09adbdacb5275851eded4b38410a79ea8e851eec521c986376621d0ca6eab0ee98ea6d2a5d552a3d65afa9d4615cdeeecfcc541caa6b0b4830a4844b5fd7ac4d84b3bc8ad0823f0c8d8a9092c548491423254b8c8a18c3cb1668488d517a4a9d5542dc4470361196b556550aabb0ca4b1cd5adf6a6a7dea8d353a76f5eada89b23113eddecc3616b2add2eddfc33bb8b492efb7019767bb4994529985ebea9c405d4db835df4e6f8799b4a4441de9a535f36883c1a67ee089145882a497cc036954c121daa8c41ab0080a1911063e089a191105c70aed781d4225e34d09b9f8fa97df65ed1cb10d84c43536393c3fd06e755b8ea996be67ab6755bb1c1beb9bae7bc5876737bf978ce7e1f83b8bfe0ce80484c6990775eae9b6c1bb0c13d4476e2b75f9e0d4ee9f49f76d3a90c0eea16e7744aed65a50d36cbe07ec75defdea7e99beaad54fd5dd74f5eed99ebf6268fa6e4bd67ae4f1b4ca1576bb8ee206cdb062fd9b3e74cbaaeeb925daecbebf2225244614e3aafeb535e179d74ced958a43fd84e3d0cf3b08b1e620c266148b16318f6eba2f473687af2f2178533497f545e52d6da55c22c5e452449a224094c4aeaf20512fc2249a88df4d0111410f9018c5fac3056331c1f61dcae86fdf44d9f410d05dc29b8e1e886ad2fd35f92cd325fe3e89bbe4c0bf591a8d35b5ee1facde2678fdb27c7f9e078edf13ef0b65da7bf7dd6bfb178eddb8dd71e351f983ef3f20a4f29561e06c600b71581fd7f4cadd866df48381b273bcc1c9eef6caa629788046312d69714dbe990947db3cf87672665045cf026b99652ca9ef4537ab39bf661ddb6799d96724e2018f7bea1f43d93f4d7a792f36a07a696a8d40bb7d5911c758a6864000000a314002028140c88c542b17048a84d7e14800b9bb0485c409609a32489511032c610638c21001862c088c01041db00d2ce0db5aab155e92e0f0cd6f8d23335ee6b69d5bd553daa0d0008fc2f5ca24db56abe6804e036f44b99abee4f39365145bcefb86defc23b24edbbb357874bb506da41306b49afb34c1ece1f9dc9e16f6871870edb00bd1111ff56cf55e383c6dfac49eb2b6cb541ec77dba89c5b8bda6763f33b9480706191310eb137d41a097afbced082d914197d01a38da76e4833917f0b15348e8c66eadd3b13639a1db0de347ac074c5b5b3451acd5d8b9ee623ae0ede1038c3d229648c0835cf5fc04628fa0e668bf2d883e257060281a0537ab82bc64a86bb9cce58e1068f161de23e1bf7040d8a2442d5bc8e45ab2d90bb4b824a33253e63b8d43be4b8034e280bad54d826f6d6251a914fef4eff0479f86246f196daa64e521d5337c567064fabdcd16222deb249a6dc5f405585bec4ca7daf8cc4b81f63e1461f23ba41c7f64851ec988968aa7276ea17b24f326a02b2195c7121d42a1309e48b9ddf43977e26ea04987ddc942157954e16ee68fc9d01ba5a9a93f6b30c058c1eb6f46d68a774318630b5984819955002c6bbae05c045426634569b94371c4d8b94cf840110af23f5949379aed832fab5f15a9a3f42c66792bb3f5c0c0b7ec5359761b74d3e3c5f23f97f008717f06df2813ef7bf92cf17f6b6ca94eb6fd0024a231a5ec1232c22ad3f5b7994a539d4080f137d892fe1157cb026be48af7fb1c2709f7f995ee5dbaa43f41f85d2beb817deaa61df6da351aab23b090037fef154b1f1c307d57fb3027c54dac5b470f48da3116ac0a1b8c3ce037d5bcca89fa335f2adc01074603373c6c465b4a4750ae9e0d57e65d5962131a2b6e448dacd682551df840bd91f0393e547d4bf971e22dfffad2ca5c8b98f0e96feda664dad3a03333b9813e01f20d39006b310aa72f2217eb088e09d8e4e35d44a9f7fe1057457447b9607147bcca5d7492e7568a8bcee48bd2016efd87e784c562e131f1e5b26b064b352506a0ed95d296a70c1b6e5e1544736703df1215d699c13d5c2505b662c81a0b144eca40b831eab3dd1db831c713fb894ffa65c764b486a4e5733652235a843fed13d49d7e357501dfe22471e002e42971afea815f8a880254e0c24b12b3c73060902172fe305e35c3c21f07803148b4be8cf84ca255fd76331859c75bf340bd601b7187d524430471eee0a8ce793cf01d926ec5da21c00397303c85d1d3c3ce98d9f24d083cb1425ee4ad050a0c4ecf61785d83c3f77ec38e5b3037eca8da95075f556af40934e8370b8e71d8c12cb9926249d6c5621e1c74d8c6818e33de9c14aa1ad6779371b2704a630e7e16645fc0f7480ac8e1c98a43ee090fc756421e201136a7a294be90fc314ae45916f9e31e31a9235338dcf50f5b7a594540d0278c8348a0401dcedd00f316e4e846af93999e551c707868534300fef41b0d0dd52b54e975b4066e29e9e08ca69c4c683ae989c5747a05078423d4f2796a7348a968459022d0a7f2798a62c5763b9d5b724f6b1c0e75a002fc0232634d0cf0306630142113750808eff39e5537d2a50ad72e3514046c12385fccfc61a05bf6a89faf2565eec891993bdccf6cff993200d587baaa0a7d05e36060e65c286a77d248aa95ec69d1cea1039b800def51e198f68a9d0ed9fded2668bfbf2e6d7d9646b60c4c338c92f9c211aa4bf700924f02f03aea0d88ebed02f15e85bfbe694faa60fc02ca9dc26654b1ebaf2fa113ac638b6081726236f8b289957d3bb138cba221edb6f4f7e712797bf83bf4cfe2fd20d40b9d35e4cc632f3e88d8af93e5a43d4d053d3668536daabc1c624e0b5aa0e0ce1d8a2bf3628ee8a52b070491144e1d3739064bc372d7f908a248ec970eeacaa8e1d11a3fc5febd0883d0eb9b88096140fc7925b0af015167b0d79b38981907a497d2e269a881282aa48de90e75f79010d2d6f088b09f0417655545a5ab4bb8f256dc65c733e5fd0bac8cedc86a7d162f3f6e69bc034f7b8065c4421194bf9a664d6623dda844ade3c45665dab34d68bcc75cc3a7a1773d87e41bef7faa0f0ca163c7bbf9ddaf1f45480e728a09e44827d3865a78b8734ef8592383cf7876a7012eb2512b2c069a9b1a98702aae0cc4d2b10331080f1eba6fb3728e904b0a86e33da06957a5584b4e5f9b0ae4d3d4c00ae210b55b0e64fb7b78d44091cd18a8f055e1bda4ecfa6cfe47506409402c9907cc4192c92902c71d095c16a93e658c91e63d5c66e3c9c8bb028172ddac509bc13e377c8082c9236412578ae1b66f59a1af99f3e501cc793044514ab574fe2bd3121ed62b526df0474b90ffa489692dae934429e9cc8ac998ec46c57f1adbb3b9346766c378665026e4b2016a5740c1ed8da0889976610b1f341c5a04cf8af04be98a10e703ca7b591ff92e8f783016f08ed00a452a272878dbf038a490355b7836051d9d0c2e55aa1ac0ab63222a0e446c3cdfb1e0cd6104675bdce01018688484c3c1a49d34c2a6a441a497f94a3a30060a5f187aeb8488fd6ae638b6c135cb26c2442e963a42d840df0ddbe1a0e9c2f1e7c7c651d71c7fcd283fda16ad1c68988787503b7dff0cf31d2b34add687c15a4e28f8810bd81ad49af059927af51ed1041721d2f64e83e7e50435bded2adb5d88c4cdb0efcf34d77302a69f1cd7e6de14d65c5b3320177c5fc43e87b5918afddf7e6d8dcad83f7d1898e8d536b9c3831f0ddf1341e511058001959fe3660f02a31a473437d9efa75bb3740d0e49037e21ad0aa1aaf56c33220411ad4e608c315dc208f6acbf7e244f114aef5f6c4818a21054da146331c93a261c7089ebac90e881be8ad9260e055acfd22635057abeac062b5a52aba76df04ff0122ab59b358b3927cc5124d0c10f6d15fedd16ebfaa62f690aad93f0bdec9db139c7f6da21ae6342d05b11bb5a43231e316bf5c459b8b76d3cdf186ae6071c0dded54bc0f2eda50e3002c74a0e1b10986407d876c761431d8ababe722bbd79074afc94c18af377431dda60ee5dcae2ea54567acbc925a22af11bef69536d5c0520fffc772df80ff9d378c7f893a52f814cfb02128a9f988f7c6b6efcc1a64b5907fd1de2e4a3781f90e93a3a0bd3f9dc184edf7ef82e6fe9076bb447985bd37bfda0f7469b04021f5863d22b3222f83a85986644c029c5aaa533af14d738266b486802c77ce6f36b4f88ea183207a6e73f2d68ce651cfabf7195406dc8d42ea4c50872bc732357c900e6174cff6016acec61f7750cdb29b4dca5d48e5c8634c09d6486504de643aa98cf753523920f2052f4a3d612a235d2388d881b8539444e15f677d8473a06f942e08cd8afde6312e95a65344e1fac77dcf80b5bcf804d642189d8bc8a07d5d397eb093294c7d484c761e6e543db798c2ece5e4e12065034fed2f4de00e0d40545e379e88585175dde8cfe4efe54025a5fc86e2c1a5e958ca8f8921b5468151a419659e0de68f5c19510f1e521b781e77716131554f10ee642697f0af8eae0049c30dceb3b53c8b4075a522c0ef67b93b0d82fd6c64cb9c03146920123917b31c1b87a4807427b02c5b17c1746bacfb1046da0b15873e22be8647d950550d9c9cc33ddae3010f5dafbfd554737cfe5bea6f93323100d77233ed7fc36a093a37f6726e8ce5b73d7693bde4b7d1d05de6be7d1f5c88005c865921bd433499d18f8a621b60030cad06b5d61065274a6bda7bfed73dbd313b57140c4c2b6dcd904caa1731ac235ba885176e19e2cbaf9efc795e7e86221cfbdcc1456e1c9bf4abbf8610c2801ef9a481f3f4e365d5d5d9bad37f67c82ca08beb8579696a1d829d96bc6acb5cba1c5f522eb63bbe30019cdccee52650c079a2a5c14c568c369556f407e93f03381f3043b6a55f04ff44bf186641df16deeb075520dc0b5e55e7432fd7840a8a361e74b732487c9a6fad7a60b61e0fdb23bbb4417d02b8e69d09a8dce3b567a9b9b46c27b55cad41a3e15430a7d81e6904a8ddf37c13d2379e6f4ae947f388bc8f4a60627ea520dfacf8ea5a949f516674871d23b78a7f43f275819bdf91e68f32ccfffba34ccad8b3ceaaa17739e1da9746e49a10a81b954a310b3438d1ac3e1891fd01d4e0508537dd513245ef5337ced9f6db9e3fc58efbfb51c88373b216091d4273f67bcbe66733e4a20a47e4ea0de0998080ad7ab78ea151170b9966dc6930395895b3df0d67b3fd382b67e5cbe902326a55ac8a22f540eba6e38d2bf3ce023baf64247209bde24b0bb007c09862df00c7650f085b5c8d1b7f90fe00d862d1372dd282a3c64827f3fd69d744ac639359b983ae1c53d6ff2674321fdd1e16c546b35972a3bd782e8bc2d08227bec559d864b33003b674e5c6171c60a5dc2595cca5cc4dc7561ef68c2e34dba9665faf14ff780f668be0fc8ac5f37ead312b6c50d2ea3eab3f683e6c32535581fac42a6ca45027b00b249253f132bf022a70e3b485609c07c5d03c63c04e4e1b73371363313c5566482387f0fec15711264825e52b5c49e79086faa69f80712b2c9a2b99559260a183e734ddfccd34bab50a2e5a45a3968e611c3d181702894548792fd0189806429ac32829c968d04011a96991a7ce5ab5611e06c091f3ff63ed627757502ff05724d799503a86aa16db362e6362aeabb45a12e53aa1889405e932df3aed0666a772340a8543743f761d30b4535791610ce43d5920773a48b936753644db7ee019ee675e76494783b9980105c1699eef7bb56290bb6dca1a80430d70a4ffe1b802d02e578af0789b65fe09d521d15712b41a4be2a479ca7a6aa109dbd2f8fe41fa8b789345fb468822dda7de8ee8e682b93e7adbe51fdd4e194f13788238ee505bbba6f3426c81390a926c2a78e7ac9a4405a29f53e08f25a19930761c398af8b9f7dbde213fe1430965b6096af7ca93a05f1984ff7e149b80ac9104c6afe1500ea002f419b6ee825a3e002c5b6c68121cbe8d01afd1607d4cd55a59d82a1def194967819302a4bcb9462fa287115c3da0463ab8932403352d9a0722c1943e616d1c84ef9fe4e351b1ae94b3d1fde6d11b1b01516e828b74379602ab118e75610e99f90c719e2f7e4a2035cf38100f03cccf4877b91abba904cacd1358c925f78a2da571a4818996cbb01ccd8046e33e6db34650a053dd6c21e415a0d5ae34d312146cffe435ceb4e876aa32937a6492067ec099b1bd69f6d82607d844e9a17b67cf3d105635bcb496ded220bb07e66cf1165fe5b8dd5ca05666f224acecb9819821284e4e644af0185fef1a13a6d0dbbff40d3677605b880b06bf6b3ff59860df7199dad28eecdcd408bd6d88ee424114b344ff06da38b8933ea756cdcd579c9b9318f5d54c9cfd9bee3325039cd43c3630f765920bff43956d6e20cee36a778a53dc380c88cd0d124e53f232fc2efbc932eaacbda93662ddff843e8187131d49fd3bad9cd7d879aea5655d11e5888a082a951d8414bd2d165efb31aae18f72e7497e60e7b252ffbd83797f1c0591fb8bfc6373a511f5fbdaa6c3ce972c5e235a9572c81ca66b54f4b2a6f389bc6d9eda76b8cd0cc554d40f961ae8e6ce11c0daac92d5b911f2d08dcfdf4601ba119b51c38dd5b342e2ae5b12b9cca0e8d834ec6095003725e4201e88b42ed4574d5da3e2620fe7323a4c42e1a6d4f1d4da7f160446cc2647040e1dea45826ece31db83b5e856195905e5e694126e72dd7ab1d40ed04ab8292350a484795e6ee88668830251b4009de9521197276549c747317af27345e324abfafaaf562d752ae1866e216dc64f6d87b5bc56fd964466850587b2d10d28df54d1b643385c2abf584a103f71106304306c52ade5179226d4efc2c14dc24e4894fcd7268c41020d6e12f66744339b3605c4b01b71ad15cff82f4573217bfcccde031e256d231b5b924087b01f801bad7bd805588ec2d773ca31a45fed3b334c96f6fb97fbc770bd950c98f64ff87e8843aa12432896010e59a3d0da0a2a0400701dbaa5e195a2788a5a1ce62899fc8ab5ccec289783e3202f3500c4c361ead610bd81e04c4bf1db62a6632b871980c0e50789fb9800d5572132885a63a6d3244c7b01cc8c9da0eb10ed1ab00bdfe985904c09bc917899cc3811f4e308bfd571c701b0bfa2b337893f86633a9d62eec5425b9f012a3d220dc29bf1dad8d5ac5dbd000e1dd5e00168b96f40cdf434a8635bde8783e7d2ba2542e9d96cf8389d76fa49037f078756735b8f3c754e8aa689765a64c4de25f49d7b6a9af4f931ac2d81aaba9e46c8a6e6d492793a467122d157fef943fc197e70813195f97e6ce3aa5ec2a4dd2f32e0585e841944f6c0c22f262c9967297e804b903342f599882b18cddae8acfaed673b44bb78a83d8361204ede0a93d4631aa64e663f8033d3fa23a4657f3511d3e8e42d718116eeb35c1a9dd8dd080ad49fe97090f49995b0910aa7c360f30b27cce8bd3f1315640eba19549c584c50191d13bb17a69c61acc2757840d1c36903e0e417979c59181dde9c56ebd925c4be76b17645b78e97e49d0de9062eb58509074e133fef7950a34e8fbc88408c7927f936f22ff8cc07a300dcb680ac41da83a696d3d68057e331b87b1fa2710804fedb79790d4cf7859ad4377e9c514f99b94f3dd6378f6b56e216642fbb17e73166d3912b20342aec4bd0218cec1436db7a5468e966949457fd9183fef8162552f9a56ee00c6f5016c055f5eb51518a23a7142c4fe0391cbceff5722af3a58f55afa9753e51f5ce61bde31458877fe8a603eaf3ce31f03a93b7264a7f24f351c98dd53a8f072d7e6cd231e59073e7d08027f214c287910dbeb73d1facc4ca1e64b62412241296cde98e6242d0f4ba28845589528b424ff9a9ba3623c4e76fdef20e3059f3f41fe9c951d6248a35f7766c06f99403c8c16c82e0fd7ddb1acb2460a5cf79787fc21ccc6a243fadf34687ddea6ddfae9339223e0f0fc3e310473b5fb538e9d2e69b0dd263f5b2a56e45da52e658d0736c29736aed48a7a3d7eb21d0c742ccc9c176ad658c162fd06f30c0864eff5946d76d7cd54c336c5f579b6514709cb1e6311210cb58d4b823af231428ccad6907d37d7c8047c5a2407b241525259402bcce908e77782e44abc551a67fb0d9f08a9565731605dfa11f8845298107aa9dfe32e9e0051d8214ee0076b57718dd09b731bb383ccf7a9633ad47d941c1129c745abadbedaad04cd9e3ea8d7a6470bb5a73603354a604e9136fb4d8f087c15b8c1414394d66bbb1ec92facf6609eb5539a244e3ff1b57aed52e45721d9ad14f077a3e672c777f840435b5f0cc034ae4e8069789fec8d934d9a13ec808d588e663a10ff3b4a0efdac515a1436db1105163d22319cad8ada5187a53147a4dc2c6def433702b42c034757208211dd3de5bfc82ca2117c416a2a4e24ed284aa974c2d0cea89909c291e54ac1844ef8e552c52e27e2b04f2d395481860c1257fed87ba05c3ba88c89910f637bbfa53cc34ccda2dff4427956e9d746825dd93b087f75c6f9cc5f6ad383f39aa0fdc607d0ac76ee7dac82a628fa52df85807e3e3c7b9641f52fdc84ff13eec7d77fc640fb7f21172eeabfdc35fbd99a15effc6f9d88726f8a5165ae0ff128ad1b0b215484f9a542d6698e1a6b60b3bc70664e7f6c60eb8c5bbc042a06d2f1005b719768e0e72ddeb33d7d6f1c85e82b0b77b44b70fc4b75a54babb93ffe1a37420913fc669e3ba5f0c780f1743c399790f575bab863b5a66c98a43d7bfbd4c2e0a22bb24cb0214bc5e45c200e042b4b502574af270660eeb77dfe2dcc26435459ea96c983f04b60425a923774c26050516def34667efd6d5f70155ca6ac34b9746a021da8135923553fd67e1049e645572c77403a78311782ea14a5dc882fbb00285adc824e7c734d6e31065225b5a8e45dc5ba6f44b5324338f6f91bd498fe369b9de367b21593bf21c44d2b1f21ac30ed55886c88ff83a5eed5eb8549244e02b1204e66b96665010e7f8009336177ed002e79df513a539b3bb121e6edf15a502defbd4bd5f995e3a99cc6ae2c9f47f40434e39bff0717b47979698ddb8b6c8fcf35fb8f31d3c5a21aa3da621bceae901afa819f991e4deec5d9409a55d2ed9a8b2e29cf072eea9c4b7dc5c23eee1012078c0666905bcb37553d9330d98964c99a0073afa82a4af2dc5cdd951342ac264b53b42a26cc95d17ae446fb0fc919a7d38ab8339cca39d3494ba98825c289c4806e6bc1e7c94bff2ec6c5a1dbc2bd0f9d49befbe17eee8e013cb141f845a532ec23e537c8ebcdcc87be830989b9a18378bfbd15c290fc9c0a03013c1e5c886bbba29ca2d32a0b67e6d6f0420547295f5e545fe992d83d853d8801b891e88397f4558b026a5ea02aec45c4d81fca66cc50c84db5ac69ced8851351653e9af13bcecf473c94fad69bf1abb5080e7ab2d50c52c80f887835dade0d52848bcdb0392dd8ea682e3ca4293c116a3b2441a275b7b65a140592a49359cd17ababf15dbc9bd3771cd8955b423aec4702e6e02c5c49deb5373c24eb6a27f8f81adde6a29e6d885eacfba863318077287bdb04b0a850c6d07ec96b1bad29115ba8453676b1d116461845de66c7ce4b092fe275b4f48d34808948812f41a9b71df6493076c2475cc0dfae1e8de9454cc8464e1bac9be24b7536ea3b622c9c1fd3419eba134268f86e828eeba825861dc2b69a572f1437b6b8c4537bba5fd336be395ec9b42eb4da134bb88a657f181cde432b2d1391c420d9b1fb29336cf35a16d0f21f70fa199ccee3a5294f1cb3810682ffd9c3625fb6249743c8c48b18972aaca70b0a91a8975c474b03347d7792f63c1bf4a6d4fbf62e4b6349910c141bc8861e7e9ec91c6901d4640b45cbae4e95c7d090aa809243bb675dc6928ec1e226d2a921debe877a3804dffffa063d9e1f70c85ecf7ed95bade7b0fee958a3095c98aca7cd221f845399aef64207f9bb3e21b061502958004d953b018ce179bf6553f208302fe1562bbbb313db653c60fea7925317a0a7763e082755830c3a4f6d9b7326a392dd8df7ff70b948950d8cb966bf64e01f686e143dc59581ac0d5b12211b27983e08ce321155bed6282f459f9ec729f2404cd43a03059b790199f03b99f2699aa55572e7a6e30ab40e8efd4d06f23fbeed8919ab0041c2a67dff3b8f9b4bd2f4bb5c929dbc8b13afbba90d145eb2595ccd6805fd5bc63689e63d535d7da3c4b5913b5fc975f21fe2b04ee76cbd71ceb657da2732263a29f8230c4d388d8699dfa8a7ef07e4c5e6f98188b01a9b015a15074e9032be23eeac4a49abb0da786c8c634ae486fb16410d5fac740fc7fd8af207046cce167d67f080804a272147fc0250c1095516164546a254d34c4b05cab6cca1a8739e8b52c08e598e2c4543477aada006914fe71a5f61bced74905762bf8d7c09ca000f72911fef335dae66ac3a1e560cc0902f0338be2c62362f79964c13b5009b8fe870907a83dcb8c96bd1d07dc4399044ecb72ca34e12d6327a310fe1a7af34481811eac097e0758ca6c72d2f6c96b9f0a352ecb812f2756baa0e5969f028da2f6a5cff0ffed1e0446ffc974988e2be37ae0be9e94e018b58bb4450c2b43d1f7bfa306ad693221241632e97cbc831155068b9a8dffa8b27c3f4aeda1764821b1845568fd05c7132b083099781ec15a425af29366b714aade531800a58cda57d098526536871f5d1a2c8d35156af53f185573337d50de0d4540d5f86c58c000c7900cbb1619abd7c8f55cc51c60ae3b63d81b5a8a7fb1fea68d313eda207f6212e62c1b630e00c55dae23840cbd56cc9c7dded19dfc43ac540af83af0d77e7d397a06d9970e1990ebaf641f0cc5230c635c236c60fb53ef99636ca223495cd4aabc452e0f646f5390e5fba5eae50d4ac601f6826cb7bc890c7ec975106d41f6dd0cf3293897e7bd3297924247c587e0d626af4c5aaec79ab904c618ac22c6a379926a5580f20a9700aaf8a4646672a54a0d8e98b0608fec76726181531da34343b79d5c239657c61f706fd14928ca1921e663fc7f49729d5ca552f10b38e5a6cde2a93dee269c3c2eb0415079f3b9f8d74ce3b63a3ce312c429a9005c2a0a5eeaa6af2bb6dc22cd2013771e5d59f784c1e58fdaba6a08a5d3729ed3d3df11164026f3539b092813c808f58fae92427fc8207facd7fc74c214e9be411e85d952c9dcd4a590c3afa98f1693192c01bced73923f6397e339bb363a35249ad4ba953110ca2fd84fb552561320edf5cbcd0af89718d0b53024779510f8a97a40201de62a5e77ccfc3d740d8da941d83d50e302bc6ce48906193ecfacc72ae6eecf812a4c8a519b3e2465b8a9a4005e289c70b9222cafba0c07fb25d92177013951e4785869f9e5d09f9a62c6332d9ade0dcaefaf321f732f1a859e90ea52c759303a5e5d7d8e8992526f51e2a27f6d1b698c140457e07bf6a5d5f59bdd4dad759c454be0605a9c284757b128659273be6076e5be95424c8587e2c1fb5468fc57840e35e64cd4e26b0563074e1df4ce75b0bbff6dda037eb68055c4e7824539ad95a7a29365b0c95570879d6d674a75232769d67f9b85eaf7146021cba0a003e9f8f717ba3c139e46b1d78112b7bc38f2e2d2a116878c45bfd4fb8f8f08a04e8c95fee6a2cf3e7896716f71b53066167cbe35bec6b5997d3f9c73fb56f15884c5856e243e21ba428be66229cbae109c3d6f481d9c235364dc8acc5d3c4452c8aaab7ef51833d2364aa1c9a9091f2f369ffd50a8450beb79606f5648ec03f820890d66c5f134f8d31ca51e79452e11a2e7f13c027dd6c6c52d4b9f437ccc1174d5a5263d372bd1f7c0c9579f81253331c91a41afc0e01d07df4e6d6317ba7058328fc1aba14238d57528e644e51084e3a6c9c98d702052d0b0113cff1ad150446919ea02e635cbe7663c4530bed97c9a430ecf2f2c23dd3a78dbe98537f48d37c2be1d2c443db6685df049b1588d1ba86456aa796c61416739fed66cc35a8ea9170489402bd80c1335656564fdc2812e18d83534aba1e10aca2442a8e039b6d52e991bb8d3ee838fea127be6a3bc925a2c46cc8dc80e8bc7508359b9fe8360b59b3045bac727bf4466503d724c46c70b04a4dc786215320e120d718073009538a681844c0d7afbf108174f7c2ecd6d9d50fdd4717331d3cfe101864965471acfb2c94cc9b7e1c514337f5696ad8a40cb297ec9a7c9e8f01dee04d38aab6a3826cf314299471ef21ac81c1d8ed92625d08cd263487dfac694d4223954c088561e244510e0108fe933b5253d3f402d810908ed9808557c93320a51178750a7b15c9b00804e328583ff90416a10eaa6f719a04c11a159507fe1a8c497dbb29fe23ef3e46c1cb9a658fc337c0df32c084902600c1500925bfaf93b4f51701142856fdd564eadd11e4d1f141f9a44ef98911e4b0a6752b07adf718a197e6262f27536d1078ee247b8e68ae892379cd40b6ade53aa5784bcfac088f48c0f44ea874f1a7d8d40429156e63068a575a0ce6422a4490aacf08bb3b637bbb540da9d8c0b30682025d2e597e8569c8575eb0ed62cf4bbd4dbda8f11a44908be2fa554f65bc6674d913a9fb088b636f0b8dd7112b4608e453d89d0d08814b80a9f3a2b65cc4a1193a05564b6f6d6acbcb5776c7ea1db5b0935f8158c02c116fa461cfe97e3ce98145ee281c1ebc41c0353846482230c05879afcf65fb5fb7a83ceb31ebb4f801d0f855cd9812f3ebf6cee7c59a3a3165891b8871ced779cb247008dbf93f16213cae38a67f57ed3ad5c6f5eabf79bceea7de35abdfa065a448e0adc5c624bc0562fc171772a8620c1166718d9921d34229d3264e17a15324070d20a7f5f52ced0a50cb6389606678ef7ea0c0af542a0299d4270c63d8a9b88f76ade0c9ad30dcffc0e01d93c36abcaa679414a2990b2cc1d988b496d02c58c6b29341756d344bfea7bc5288dac64b14bfc83f52bc6fb75dc28bf5aee41ef8d4adecdb9649df82706c4cf3207550411034bf9835e3d68b6b29fb83666f782f3f38f50d259e717d13318ab5f64342af530f6abcd8888aa0e04ff5536dce8ea1ab5aea20bd1b01440c3e2fcbf73a4ebaeaf7ebb24447ea0bde724a175d51c27f81538b1f7eeb86e4a4031633c199cc93493c62931d3d4cd1cc968c252a29b94706c13e40495399f4d4e6a71f32ddfa03e8d9a10ec3219a76032f03677b0d9659b76a2a295830c0f487720b54ffc84c050f5d29d753c1adfcaddff8d84cc41b8a5a0f674966660a262b75482ecefb7e6deac2b8248a44d537077c1780acf34f94a11a0ce33a1aec02dc0a9c6c4265c2befb5d964f5526ad4ac745050278e9585f4b65b35987dc38556e8a85533139cac82d26ac3f344c90365ae48336168a0271fbea5f65c066401cdbb0d6a578df4a3e7c66e0ca93b282ebced72a253c2a5c3e965926f011a401c5a73feb23d10e957049cf7f8f7a28416bb91a10d08cd6b98b782d3c6e54c30d47dbc228c7a65b58f4db1a052463a65ba55d91e3c4488e84831794cafff056a184a15abb5045814de0167e8f25baa3b9e08aa6c2fe0f8fe51dec9c29f9a103417311efa474f95b2bb11529ab619d8affa06b23c3a7bfe40ca5710b1f28f70371608b54e46c001d1777510400f0660e7a86c9218747b907d0ecf31c18e9538140bbefc2b6cb80c7ca9eba361ec1799bb2343847c71fbc462b969d5e8601a6507c697a8c34504a674bfd8c3f2dbd7f4d2c25e63981118e5a9b5148cab4c0dba60ac527272451725796714f28ba29dd1e87494805039310179b5a6d28f2387c63ea876fe0f30a22bca682d2b38a61f4963314367c0933246fe0dd20b0ea2fa4980ddf7dff60224000394712827635052c6818aa377a90f94f176a72b99af0db2e42345eaea33a05383ba35e07a60a2ec9e8d42f53fae7d71295b3d304ea81b2ca6bd3555ad3e9a41041023b439ce1306832eeb516114821740433ffac2087587e441d1620e7304a8c98d16ec574b36a8ca9facbe005746fb15a20cf64db3b8baf99bfdfcc5e288f168c7440687b7d016c47f31b43a34acffb8b6cedb3fcacf3ee8f7daf9120da0110d38c9bd7b7949228f80f10faadf0b739db80b448c972b71c6c8c6ee70688a2271e103b59e7d26319becd1d4beb3d3fa4d53b2dbdc7e89df1aca6d6df804b61fe4600187511680842d2093ccfdc55f0d6dbf0306169b1a8e05a5494cf87b75a79a3c1eb4af3a20ef5b481ab899ed939dd981d2b634c5dca8b575b46e729964a36e44f6f2021d45dfd74f98e0d377c936d18e7d049f1034304b95ff2f4a3386bf8cb5caf1eb5407a289143cea9119cd910cb80b352143ac74e51010f32c43a9405be971207e8d5ea5d90a4def7c639164f6a08113782c9765ad45bd2529640ee440a0483e47bb7632e27fbadf54d647166185087f853a8e85ac59a89c162ab82d54592e54e84255ea85fa4456876f9b4eabbf4dc2d65aa7973de39c24758b35b6d2955655a2d8f009344608c6166d8db37150cbbfe6564678ad9d9167497c41eb65a5f505a7514d4976d9cddc2b84d1e2389a052e6714ba49e2911493d04faaf6c7e2f0fc6b8a21928b1e11e160ed61ba99fe6bb13222c4b775c9f6455ff56309483953c06fc773f24e342a4322754b2d8ba12587979048c5bb816e93cc14bbeb20e2d283cec318a228319a3dc377bccbe03a501d1ee7a262b101da978a0d429ec75fd28e8680ca4e072c6e2ca39c86fa2a2e56ee8679a83341fc3a57bb5fe3b792574f7d3acc85a58ad063cb6ddefacd40816e0692f38ab55ffa095034afa139fd9aac0185e901e18b4090cbd2cd11b78042930b8b87a7dbc6e15f19e086a6afaac4a8f972043e2bfc1ea938d4ef54e744e6a68b0e1021dcbded9ccf16d60023c0ec6339d8dc5b2465ad39ab64c98598640de5236d2fbad0a0a37590f40bec5289e8a6f0f9e3790b689e689cee33756e5a402fa922501b5dd50259a6a47b51c81460f8285bdd8f4eac4387c646a23fa7f001b5de99062dd06f91b40ce51a961c8d013f95cd7dbcc4df9b9386f3a34ac93a9bd541be1b5c6984cb03c93c6a89df540380abbf0295826eaa5d75578e324f56033c1ccfb42d55671b464d1894150b11d9049d84face17f1f95d9fd51da25c535a144f9d34ebccb8ba53f85c9f3e21026184fa6107c38afb059308e2bfb58d82ea1075bb130d4b9592c4a666da1e77c681785dee78820110590a4a7d28714af3e28687feee51d66430c91321586ba880b4de7a424046e7553b4d0a508654e0ab390432ac8ea61adf80498b1a75b590e47e9b91d519897addffe55fb848538e6b86981b17c5c0de8953828b27e3001051f5ec9876d3bf2445abd520dff466d46eeb53c51b715aba61d31b6f6952434d7d4a4615cd6dee56f51647a7a227d54984ff7a0307c1b5fe66ea05fb94c028b55355723d54bf88e6ab76fa451f1cac0cdb8157a8986adc86ba60ad9c980c4d3dd841448ab489ffdb857a13f5aba98f257c26a2834d6273c6c9dff4d672fdd447a66dbbe7f1f6875892046c552b1484a4b79086b26bc4b275182f7b4af0342fd7ad09c5382592f3a71c0fa587392d06c8b0ec706f59521c465bad305813ab4709ad1b50a76280d886dc42b2a1dbf25a3a96801d8f87094b29fcfa1cbc3fc29e064c8f297b6edf45c7771e81efa3ee5baa545e6ad2df4497940e161b8e84789e423d4f7b1383ca7972719664a242090113a3e655105f88c63b23c95442aadcabf125c37c308e65f50e6dd0983aa521bcfe520216947e040ead640cf4f74e1b36d43b92dd7d7c54d724bf6be03267bee39047a34d565d839ef7d1ca4e3d316f455b2fc37c314d98c4422499d9ae44b83a162bdec1cd74af6b57737278624234bf0b6fa401866d41cfdb2a04ac79013bc4c1543a42c2dc8f5d0b9750b2d9aa31b7bc2cd1b5b36bec5d83fc43a7d8180cca436c02239fce09828e26dc57840b8ac7f8b11374f10bf8e372aa47ff5b98808e4406ccd51962f50215ef69c4c9faeba627f466ee847dbafa206d5ba4be65dc3c4d5033680bc5033e8b5a8e50b4b6d4e14fa681b9f6bd904e249d1b976b68ca7d85ce1a9a28e79cdf540f2ade4572e76242a778aab564bfe4c2f6b46c5f96e01c78f8a9c0d1a5f2158f45e778aa0f777f6a5de4be39f4981848879e3972a92dc7630cfd1a77f81a272549fefbe0b37d0ef2e243887f81b7852a79e5e697683956c8e1a946c8efcc982144d0b773a436056a2400f751d0f90ca16677f75f3edc328543ba3fcc1b8f4fc6cbff0edbcc9076e9b8d76d6cc7cc9c3ee2b6d495963a53e840f37ec065e37b27c6ec5a6d7d9d3c1182d5aabea8118413c97527b1a535b67332b092f9b42873955da61e19aec2890f70714d4af3beb345793d58ce498305800cc72b80481098c4710e16e3f4c170aaf954792322a4f8dd9b806cbeae5c43d75c8d59463b4f305bb8e4ca48c78d4ae91b166344b5963cb885d67f694d86c4572c844f02495bf1f904c4cc8906ef90c7f5ad8e13746011314d3884787b9258a2b7219926e5e1183f99a315409246f9f02bcab85fe31db59555587ca2e98887ffea0c80a4727b0488321d0ffe6f44eed2aa4358b224172923821f93aa8842aa668740ae12389ee8476b971384b316e0dfe9016122d240c0dac34864485ad45c5b4a818770238a91b6b0ac873b20941aebc153a8fb2b165b49d08a7967efb6dca7dac2c5e06b27f2709013dbaf07518aa3d905d2d0ba239374c75f5a63aa1a62b110cc4b4c40e50481ab9984a6caef63d4b82ac55ddb6dd012271144cf2a1bbd1861821d429d54957433c0fd6485afc08f582be8fa36dc28daafc18be1ce94b1df06f924d0c66f30ce7864a94c2459d04ba35c3218148293de1e31cdbef3aa56c3fc084542dd0eda828c7fa3009c3a9cf38d7b0ab0f86409edceefb1e99ad5a30591af98efc1eae92be8b0bc9542c84a01995fdc11f9bdde35a3cea2edc3e01db9c2722011acba7aca22cec02038519f01f917a332a0d60a1f78741eba4093acd20c8ec80044a567b5203615ca9a4a762c7a0cfd3b30f0667bab213202c019ccccb8c1f065b3fedb4de92abfd2a9b8664e015000f79f0b6fb7676f857da0f58530d48403671cb2b2d39c9dfe79ff66ba07a9266a76b0f4de766fb8697883a2e5411a98492cb96834acb17104b497872053efe0d4775e1d240131d87fd5225a3e12447d111810607caea7e3e7a856f1148b1986cdf15748a216568fbc730f2ba21b4a6af6680a68c761f183aecde7b6e798c35e64d4b1f52e0e6b5f19c42593c81847787498ff53ee90e7f7148b5d395572cb42d0d6ae63968dddbda6d8c31937436a0022b292aa9050899b5a80efc33e23f5fd2011e96f28d136a0fa8ea5fcc934921ced468fc60c2c0f2c0866577b7c59757348052253cc7d0829fb511b9f89a7b5728f571d67bc37cd00379c6e55abbee81503c44e600db401e1fcc39338fa574a0bfd0f7fcc1304ad296a836ce571649d135c0d91f38b1befaa655ef0b57ded4e589e7672fc03abd75fcd51a2c2f4962ddc155f1c257fc2e9a55a05f561774e71b30d60eda3f0f5360d649e2e83fdeb6916347bb617eb4838a08d0de83a1400eed347f43ea2aa7ca58fd39099e83a6a122ee41794721241ed4ab1cc7b4c1cfc28cd469a0b8bac0577961da4eedc3167a6a5617b77c99cd499631f66f9f1fc6e0f4b76174a0c7533fad3dc58f71d67370318a11e0821a1ba0d51d17be575a9ed440fd4b395301bff43b044d5953a15e8a1f4c2eb8c2e2b9448c0bb7a6ca08170029b203062879c84bef8bdb43c76f554791462a807bb2b2aaa4f479c2cebd1b263785ddfaf760a7a4e4c003397492d41db5b9ff67dd36875f464d9766666659e2c98da089fa9ac3e1c4c0f3542b830c8d1826291f5e5f8a15b2ed95a574a798460e765b2def2654882ab6483221c5a1268976c1eab7d6d35936cfb555f357e26590fa83b22bf87d6d47a775764b90dac9a5e71b64dd10410f2abae58a17a3785690898f1e2231cab7b95d5a3a8b809de0c6c22e864b73bbefa1f9a0cd494268ed2e0baebb549dd31006b7827b1bf9f498128c4fd5632c8132e9e85d3b0f1bc3f69cb9f8e661a68b15b56d69ede8c1f3a290b515362a1654d44ed382905e24e74cb1bc3aa893d35c3d8ad3941199167ca857547d4943e2f85312a32af2a0064125b99b6c865e42c13b7e05756ca2c2d41f353dc95f3c82bd5713ea5d05d86639043d3471bdecb0a831ce534ad4b3d7364891035fc2533c5e4849db9c08877fe28686644df6517ae81a3e32501ff8761d1ed6ee5a5afa3688b3253fda82f048f04d89cff7da309358c2918cd23fa273d4d649e65bb9e65e601bc313e5fcc23063bd907544e0a8d94ae02df1cac9106ba62e1e95734988aada9f55de4b257eccd6106f10e1833eaa1778fe327e8913e994fa2d7dd1ea0e348241f2d205c38ae67dd8ffccd4d438d2c235fe84ece28b80964e7b35720131882e8d9126f8f036315c4cc0b00b4a00455074ca61f402f64cd3f6b65d55e2e353bc4c1f36f327e0b8f2373739d00024def7805e21aa4a0fc366a667b14502660c844a5b4facbbf06041524a074eaa784fdc0a19c736e5f5c0208c3793120df347e1f3e0537cceca74101383967a877c0eb5b5fb7d8d2a14d68d2e5fa66f62782b0b429d4d83e318cdcbd16a2cb75379a7d1444996c0e239ebe264fe03b851536bb788b5cf3381289de0c3aeef6a77dce82c08825610593d62c026056610a2b0183f82a5a658375cf1ca0229b30e750dae4d253648dac9a45f1840548deb594874580e81515552ec91f27a072dd6afa5959bd21b9dbfb15a744d3fcb8e91b1dd3129f1a1244e20a2417801bbc7ff777057319fb4f97b935b56fe7ed640e79008380a0ccc45eeda420333be3ddf5475d37373c09273fca51496f16822980553395028cf1038c19350dd3a634b97312dc34bb1a21232aefb076285c52851ef5cbfaa44c80a926d5372c59eb0ad05d4e9e87a118d22b9c85fc366da09d347820fb025dd10c1a1e7f15d53b415a39ebb95ed57a589a2ef6a7f3b65db7983083117cdc784efcdadc3e772d661f0b91ad7d29f44a1ed97220297aee38501c010299826a55738f2a809f6cd1dba3f58b82d3671d040fc55b9421d9fce1451cbe9e1f2dd35c58ad1137a3bab3514ad4548bd36cd6f62404fa5a607c5df2eaac2f2011a4b54ee841d14da68735acceaa4019a2ef0c79a361aaabd3a588c36e9ba1a91e436e124b5bba5c30a3d8f4e5bdb4c89c3f58b1c659504ecdd392aa85e384ccab4b24ecedaa81319d394bde15bbdaeaed8e970dd0e9291a54e55a3c3a1250f26e9859ae178822db16d66f06f371cce9fb529f059731297ccf055b9fd7e377f3315cdd817a5f6bd3011bf1d668db8afd300e27a214b44b00b0923f660d92d67e2010a69e8310e433b049901c8c5d4b1294139ea8864b230a5b8994dd783127b96696e36c8e59db9e408952475176c9bed567bd40c174ea5c71c120d31da67b7302497d4e04278c058672b75e287fd0ca3f14284201902cddecfe6a1fd60c395e21d3383466aa0bbf0db657bfd5e76576a3d1b76b3d6feca5a1e5201d44ce72dd028c0c5a1e65db16da90e47f9ac05c3e87f0e609197a42256b7004ad10b01526a8ffb03714c4fb35e2ab4ee8251d387224493a9e6ee45b5c5b52330202625f056df65401502bf309f16569f4d256f597b3483ecfa8060c63706e1a40a894064ac38f7f90383c54e37c7cec80b50618c862606bde7d5005f78803766b8369b7ba8b2a3d771f18e28e2568144dd3ac579952fe36ccc55391660dcd90aae7531f492bdb12fffb05d94b6731230256c8b705b7d078ac54201bebd34e9a2442dbe01417dadc43492c05200577f88eed43559771ac72bfcb360e02c67aca52f40e476c11867633a167b4374581606d424d9d6e3738f4ad56c1e2c1acf86e458dfa6b8e98a58567f565dc7a03b6b383261934e664f8b3c106a447ac46f59222165e86cbe885425da08db1567fde61e5a1fe4fde6739ab5b7263449eb2226de31222fdfb9292eed64e09685922eeb5d9e07b0f5455198e00fc73cb8b84f903605339aea2d3327f72f234b51b36d538a43965cf99e19bc6e6d5f77973f0d50b8dc9155977aab6185ea7744b37ec009c6b7444e6da92b3763fe7b380920b0fd76299dae3e56617cc5727cdcb6869d4d4014d6e2deca011a179dbcaf4b6f33bad37345e4db66dea30187a6f2c4f740c45a0cc54187f836f92647b10181392b910c292758f24125a770eef307d84c80c5884c31879e78467b3ab92e3c80f479881aa1fb56a8c0d941d4f271d174131bf88d8f20bca741d3807054c3c2f56ee0939b8bf7b37f5fc1adb5a16409bcae4b29cb3e6c07e84456b505893d10ba935f8c376289141fe6660b3061ed52788b9959ac9ff7cc3ae292319f65dcf8269a1638532ccaba9daa84cb3c1e4248bd602feaf513255d963883741806929baa9b35f0203137264d45cb4a7682537924d25c45f4d6f55d98a8e9617501c1541a3950be569685f0223cec8ea3773407efa5a8d9971016258bab401f2b06cc7b79cf06833c5ae510941662c3f8398fc6b8ba76ee1042ee4edaf383100781457d4ab2f70a9d8a43a1f3092f504d4e2836a1fe9c5fb6eb86c40650f037db7a783819adfb833fb6b7ee4d8ca6ca887f54582fa6c6382d25c957a065f199db63483690802e9f4e60b11ae8150f3e630f13b1b5375e32db4df24af4900e87f4dae13822da7f78e7a3a83696e7b868aaf2884c0216351069dfd4bef9617883487d468296ad555285a354df279fd5549b82832c1fec8b511be1ce90353122e7c3ca5995d5ced2419d06e5436bd43672967494cbfd3280e59f8cb36a7e8d4f73433c87efff10144b394484b316df19764916a1ccdc1c1ae3f059685c864f2f744c62dcb92b1e356f88b3730dbe292c7fb24fd3ec87b89d697727f06c9900ac982184271a9487efd16caa93237107d2bb849d1a5b91a031fc004499c5fa9cc8de898ec2503c000c36a80100c2ee280715fc754e47db9b3106c3900e0de1a017e239b01989c12a922d474aade8c1c70cea148d9587aff3757f552eb65b59a2d011d664a6760477787fec6f9f9aea11bc8bdf464a11833097f832c46888accd952213f1a6fd7e714cb591e673b5b5298fddf9ea0153c29f13935f285bb1fd46c4c317cf87d125e852c105b90116990c4eb2ea240f8b08dfd9482c6bb9292a2a69edc22fdbd6599d3a4c7e7fa3c1ff0a00c7910b4693d51326e61ec946be68315294dbeb0641a2d09b3605d1969508e209d232e6353282f21d20f63ce3b6d0918d333d74107808864965729e6e0108ea031220fe7e8ddfcded47ad1cf5bb1e3298daaea49d14c30505b6a12667a2067299bf9f9dc88385d13b93f57602c3ebb52e4fa39cf6e2cd8b2a8ed51636b55024c98dba3c6eb26ca1f8605f706488874b7edf91beb0c3684f4f3f8eb94a5c4627479580edcedf4570ebd3b15b5153d6e8703dd3674f5dc6041abf861cfa7fb41cf91671a586f17945b109912aa49bcbc46c1ed96305b2c34b6b8ea1938ff9563e781f47e4581fc58bb0ead8c0f25f033a3e58a71fc067776f69f48b915a37ad45c9db3e2c5b6ddd05f300bb9d0a733874b1b5177b2c30282888c94dac659b10c5891d2b801932c135f56054b3ece596cc70ebfcc2d50385652018cf285e8a8a06eca8b955172b260d44f115d1e7181b529b806c14c821cee2fed528f3aefe5dbf5368a2f9770d8634fdb92634ea4df6fad11ffbc7e993d5bdc5aef566367ef6e5509df1c68a7d4abddf25703aa1132fd8b52ebcf092fd2e22307539d86afceceed2185bde4cc192147227cdb31d72f662106c067d713b463ad848a4612da6ee52cb48b68fe764e56f3a78e0841ed76d1fda4235b4206ef5d043b7c5be328b2dd32ee990c321c1991a19f62eeed3cfc6033d1fb3583056b1dd299a9f3836e1f5a94022db283c641687e2c883695b6cf410c48a1ab01b9f43a3c35283004d23c403d1174b2b0b14273d2426dcaf98509a60cc579edcdca763fcc4351f468efd2af1f278cf66340463bff77e31a29ca941ceda44b92fbded3dbd64e13d0182e1746a3722d84f77d493c174fbe4f540e0742f1df8e15006842e640c2e193416e0f4c242a95d55fb73e6e34a8c1596a49af917fdec01b9f1fbb492069dd8db601bf01bf0c90093e4b8b172780ecacc7f9e1bc14c0c601ad92dfa420a7f8b1b93f138f5906ba86a996c3b02c40a50d369de3c1050677391921c49d8a1d672237ba4c9bb9a2f4303dd478f0b9b1c362b701b3dd3aa68553728b2d121142fdd1e0f97163860e9a11accfe590b43f5f0cc9e16d5624b58bac21804fc316f7d0dd78c06315e9e48bb3686e619df2709bf6b76b79e95a0b3fd604bafddb37ff942bd2b0b78c363cdc34b4904307a87234f81be56b748b03e8dce1042529353c98a65cea3a828a20cb4ada0facccceeea8875bd03d8808863b5c41b42731892ae313fc12ea99eff2cba1f0465e0dbe9fa54fbeeb8e30d69985882df3f16f753ae72dcf11fb475877b7ab30c6552b45562d4224875c22ddf68c113f9e2b74090766d5ac3e5c87124ffb5340817d417ed3e7872a8579fea415eecbe3edf6e6a512d00d635d333c0317a65cf0a60c903a2277c0046219b04f45381700e98b0b0834c4a20e2160b2b5432e3aa9aad003f32b3c6c4e2a469f5011382a90b53ab5349458a7baa8e2ad0dd7b4aeeb68361024ccaf8f1ddd8ce4c346125e826cc5e62ad554cc435b98adfcc444cbe48c229d3c56067410f9b5fd367fcf371c70e65b240d1584b2a81a5c00c140ae84cde4c061bb6890290f65b722c4526193a2cdfb6455c42acf0ba8f08634641bf0599f807d73120ac546fb81657b42083c77eba098e76fa3cb805b6c5a8b9feb4c36dd7809c53239e2235c1279c81a31816d632d496b4d7ff15a64aaff33665556a4ef37bb0f4631e0db0438e03654743162d1cbe0a28f87053e5c8350a5b5037dacfb10c11fd5681e4faf2177e2e42c7d0d5f0a3e37e70d908ae01a65371d313bb0329adf03e28634c0b95929bc186f318234c2e3cdc42a1f9e9bc1b22012882bb58036d3405ff20290ec696638b94131242c79ab0405cd07b60977cd294ac51bd522d574dba5106535308d4d21a8dcef5b06db8bd70ee49b21faec706cb4f5e1019784b306ecd7f6ce3d8b2de1fed3cf3482be3cc367753c255eace856f31a6fcd49a0d4f4dae42a2c991595a7bb5fc63e306a82dc9713adc6469eef85412e2c0e7c377b2f40f581728174337b69f723651a17c0098e71516a9afbbb97f00bb600b98da8d4cf1fd632da7e5ec404d7980c9864d6ac99be756643c6dc1b2e562909bb25311a4a1467a541c29207bb11911b2d1a7a7aba693dc4ba36c6c1e0aba705c3d702e2fab6ac9bb6fa85fadc63dd179d80b0a9f5f5065498815ced5838a148d7e582a83b03e5c84ba790708173441797e64d028e2dcf746be419133b90add304424ad611c246420c72d271dee61a71d96671398aa190a05ed48766b6fa33b1d690e4bf59c34dd4bb36ab53908e932086d360d94d2f89a464fd5abc1f292c9e90a5ff09452117501466f67f1b0a3b33e9a08df165ab8db33da24450054ae3a0a18d827533865694f5fcb7399eb4806368a7ec0b8b735779abf09865e56f8288af8f781e0235c2726226c7c5abdc90e847c74a54e59c967a65f4194c2c7aa7e68c4abf44080fd0d824c9454fc6cb592359a89b2c83433809277c56a01ef560bf64dc10336aeaede376bd5af40acd9b863d1d936a1c5558b8c5e9e35bc7011f0a64e2f9a16b28cb0c6b049c13cf6037046964216b249ac9d272faff3896a5b38a3664dd28be5de35c4683dc1a9d9a545dc926a5844fb11eb025dfc4b37ef768260268adec8fa95f1e4554daea3b0fbef727e061dcfce51ebf1f9ba745e2b5b4222bc36d6de6a09beab3f8826b8bc3db8b2844868acb1248252292081e5e4c1b7a956f945e55a40c44ba1440ef970722c6b50fa77a3099682a83482b0941338da02fb8daaee8f0d6442d41e5a0d7ce3c288ca418478264f8115e5e71fa6967064a879220a43806cff42e9512c1d65d6684e0cf07b5ed83b0be9c028253c35eb5cb1d60facfb6533c46e66ff31b91f952e28112b4efd99b964b3f4cda53857294ee88a8da29dbf8947e0bc881f68061aa8046284af26efbd5997876507b0785b9fe90bd02f263710cab6c245a8f170990a1379142c122f6aa1fc76508257e19c3b3fa85467051e6e3c5c63b42ad2cafc8457af34116f6d9f71225f4b14a1056922fa0569ea56c2b8e6254cf3cfdad854299961ad33d82f013ad179f2585f5dcf725eeb758fbca472dc2337c7fa3b05417f52acf57e6667b94f9fd3aed7b09656ae5ec1ad43af930c34e4ff25567d3ea6fe7fbff3142338ed443ff82c782e20d4a5de3e29512868d40e9ed76255cef5937c4b61270f6ad467d33be64d2e9ac9aa37fff655608d1f3a74b60a9357a9c28fc7d447d2628422b7e48ec8ab5a7aef917b5eca9abb52f48beb75f417ff19217e35b66b4e8a533376fc78e3ea7f0e353493e17d13f5aad7fa788fdbc42df850a10f8e85ee8558bec46754249526542f5e0a62d1418d319b67b94686e479701b57cc6e36bcf40e3487aed90b16e953503c8d340a0446a1cc85606054c82e8c8029e4cb832a0347319e0aabd168a6798872735c519c46a76d23153bb61f00d3b994404993010d636ba5f9e70e7e7dc6aba7f0095ad48253201658090fc7129fbc26cd581f2b9fcd9c92f74adf6e0acd6af7e9102760d4506237057f5c9b338daca81e094030de19d561c492989fbcfd873bdbc8fe4d749b0fd419ec4ccecba35fc51b4c411e095a1bca21f0c046ae4becf8e534ba2ab5623fdc34579daadaea5fb759ac84bc3ed26eabfe23cbf520e8a55860375b2b0e083aa16df2076891d2091cc8afb0939bed4957a5f502964fbe29207e8fb71e7b302f230c152e3692edeeaa8f9a54b9371d4a7e51329d0d47e064c1e42ad8eac309791af4af55030a4acf87672a00b9814afa19c0631af86a13cb30153fd118d309f2666c47d2466d5e61bd3cbdff416417583277361f7eaa1678d5b9d00c461be1db3541b18024f20192efbfb64289128a994968e030f73df5808185082e01f8636bafbd4fa3a17779c37f75d0d00f285245224bb3d0c8fae3527ab25c001da1219f6112d25ea33874f9d38340b2b4e04e26012c15c6b3cad144eaa5ace620703a7ca353523b1221659affcfd1c8ffd166721c49390f1fcf95995975bde33cf61f61be19d02b23c29bfef2cd583305baa2e3b0ce58b50f70d76d7155367e60a339112d31f2c13a013e2eeb73d03e2e9e5443117570d4c731dd5f9683d9420203d341dd03e4bfdbb3aec08aa1285b87076c781ac384e320d3073855587acfaff3d3a36ca86886ab085ed5ebfca6730427d945b140d54b59a635fdc0ccb55362f294c56d06e2ff362c5a8a61512d242bd41d081b3938ef96abfb6ca527a294070a2a71e38280306c9a6cc602ece4f68fc6f304222119ddfb8217891a30c0a855b1d81030e3cecaa9f79811691565f9953d2fe6efc8a65626ec5994cc1403c0ad6f6b51e7eaaed543fbee0135a04996074aa6ae6a6df419782f64b646228dc097e953bd8ab3ee19acb2d0505208095ba8913bf1aa765da839ac17b105252fdac531174bb6e9ec26f334fa8a50553a4025385ecaa6515edec8091bdb8198d0724240281e82ada8871533327d563b5e397340e485210a00d8dc57df84d09332b87d03ee24db295cf2dc6c5f0cb09cf26e6f453548042e80a1e85f77a8e48ce70c84d067daf5d9a459992e6b46a9402f245abb05f002be2b70fd44a79d5090d4c281512fc132cfed754724024c49af2579a5771766fd3d50e1d9a6fffe5157df7d33c88d1a99e9334db9441af298669cd46d116534d5f0e89c326b918ec32119e686b99aa880f63279bd813a1f134dd7ca4e32f716f1ad479e39de953da3f937c326b107cfad9af8293937dde593e73084450b7269fa5ea5a6142d50357417eff649ff7fe30d2fd0a8d4fcc4053a779f6e3bf3fb4a3b35ec20184969a03e10d513d4aa4505ecf6a5ad8f2f3eb51dea20a1d01be3583e9597b06094b22a5490f7cc15c959140bd18331c269e8ccce71bc25e0ac3cdc31783603afa69518876237ab8d5e333d122e36379f0c2e719682899d402e6ad93a13122d211b0a313467f72cae60c63415d4a0bfa87da4a57d201ef8e919d5dc7ffb03e2506819cfd5d9119600af0048b34e3f9974e084853813b52ab91d7b8582a1690fdfc522419a33b8b37cd4c4ed0d92627320108dd70a7f6c890961aea82d33a215f7c36472cc2fffda86ee15cf3255abc2f4306205290e999e6326dedc1a45855104ca5ab98a47a722979faf69a1cee86405e7ef7cab790a9e59a7ed7a2ed1421eb5e29b114a7b3a6a0b54104c10dc74e042da942b3db2dd16e244c4b48e45e08cec1981b4adc40254e10f79a7733ebe01d675ebfadc3ee035a1a48a3180c9908f13c0b690edecf659729528ab4699a1d2646385fce27cff922160ba1d8474b0f0193b9f079ccbd73d16890f83b43e4c9631a33ef26ed246da045fe6871f8a3df82c1a611e87a574d325c6f6a82eb7f675cd5daefef8b2722e1b4a973b1d61b77e389b455e3971cbec645d8508d2e83ba6e917b6a4b0ef926675796a5d43fc9b3c05ee60e181368d945871faf5c0d9226c34448919405ff55ce10da4c72328a363080be8179a85e48221cb323195dca7a3c943dd9d17b3437ad48c4a1fdc79c9cdc2c498f29ebd57d3794b7da66cfc453796fb7b8f766f6fc030fcd9a1fd88bfc756f424e63cb007c01ccba462cd9f77f4cc359e2d833720f586b689b8104e94d3f21be0fe70cc1837137c043f805cd7de17928dc9394b6cee51101fde896d3257d99260afb3cc3db770242122627117d4a72c004a5b001780a0ada5ee309de6c4f2d37b64000ac24ab9ecc8df49577b241482eb332031253c0473b0121e5326972c44616cdb5436f79802f3b0607ddcef6809e13e0084788bfe0ce38bf148b42601f65e60f68b5c542e311b2e78d04e7ddbd0147567762d0cad656431994573649c36c42f22adf9affff911918d484058147b1c0b3cc6b74fa4caa81291bf7de1543d9ff133d4177ff045e5cecc455a4ac29cb75279b92a2fb816f8ed52503948e994b8ecd90ce53efaea5702270d29ec0a3e60b40a49c89f3326c93de11ca53e00973541ecd35ef47c63dd5b567ecbb04a08cf343d959192d7e5d3b8b680758d51b92249373109e9de2f00e06b3073e37a225d4a428d002379f86abfd60d0007347267ec11583b6a0443cdad021a2453877bcd989da5d623930418917c11b56df9fc0560159dbec313804133ea812940d5117ba4228ee106b340396743f7da72b37501f93b78572fff2d600ddcc4aeb4ec821ac5536fe31253113f918801132387c0c7c2d19db59a53cd1061855c3825b4502e5d4185acefb2212fc8cbdee898c59c017c9dd3ee07843ad8d0400e509565c817f3b71c3fd57e2795983286a292ea75c27d4d4f8309e4abcc06c8f78dd640b775b205a0d78ace853c0a01f44cca6817db76203a0b8cd4c0248d5ccb6f63ade3576321b08a739e36fbcbd8e1a328bdd63e02ecf4308f3b89e34f082756bf27bdc91f7fd08ee1d3c791787212156cc4fb3b3da0f44dad632fbf29f8baab082593c7a9d9035887f04ef2eabd14507fa3367df365059ff70780a39083631d247ad713c025cd1ef2f4063a0a5bc48c7c8ee66ceb8808190dbc1963664d71b6378a3830b3a140c0213cc0e1356c02682091f6fbacf84c524d027bd2638cf555f9b5dafc74ad1c52b766aff7956cc7ccf7e6176967387b25b4b397728fb0e62578b72ee58f60ee5dcf9ccd35ad69d5a462bc6ca6d261271938fc05e8bac635bdc604edc374a0c23986ebf142defce6793d632ee54ee9d67ad1665df99fc9dcab9c359d5a28c3b473936ed18ddd8c04de2185acf98aa347b6cd7401186ec5fbe2d5556e08ac618052c1731780a8f95014955eecc29a4f77db61188105198611016554a075bdc57f2ed1c889c696e8e126fcf8a2b65957535657c8f6901bd9a71d59462278d1796baf412c73bc9d6a59f6c4892f5f1367585f7911b9198e585c330af30b610dd1b00c3c5a615e360196c42a03ffe377b4dea7594c778aadde76494b9151dee18b57c43f457982bfcec3af3c08b1a0bce6e8d3610a749685a67e05e07c60e88508acce7f7e62a32b705b2fbe7833cbac5bf6abcfca43cad87980c9d2895ee9be9ff9993b2d9d86ea570d916102a39255e69f6dcca7fae5d23e5b47bd93356fe323cd515c99fcb1177700db97254b3908e0febe1ee38c2870f77b772b8dee0c8905fac19cf9b98b048152cbd8d7653c7fb119dcd9fefbb4cc97dfcba79c348ca87059eeee317746a4f867dd3c49a1ebe217f0e186d89bed16176a644c82242460139133c98ce9df4209aad7f67898afbd599a2a2fa7201b2cc655cd0ce0bc31d6ba2b20c4cb1bcb16a22edf0ec87a4b998c136db72361775c6a1f0e90cdca853f996c5786802b52cd2355005c8464cda84f00ef7f9d3b94a29f1019d9e340a399d60b782fde28e7895f024e07c5d36fe46601295007a0127390d49a4696fd11bfed6d3d8907434785978401d9b91316edd8a3d8e9e681b338098b027838cf1324c457e8519d3614b0d1d6233b2c4df3fb189d2b12708617120e11b78ecad69b118f2c0f908ddfb3fee2fb21183652f324678ae9bd08721a3a8032409a6c1a0e24a62c70403862f6011579993faa7841c2e4aa27833d0cf58d113809a31d00a8a54a59c6c6cd274fe94aa143d2fb9bdee4d1aa86f56d4e91fc676ab2994afb5a24352c7f36726cc89446a548c944fe835c7b4138690a6a270d81f8a458c60c0c04fcd880f39ed33a50f5358639fac8d18d0ead7cf3320d1858f27a244626b5547bf52dd9092b260786dfc15578318d407b4c88019617dd5954585c51542900f02c6aac3727c62d176c55f0be2132d342dd1aab5efde42e18f21b3658363194daea9cd16d75f0ed09e3aeec5c20b5724c4d7940780ede321fbbb67196766f1f25c21d451772de219e2fedc66c9e8c4da42ab8e6659650648f0ab21d20e6f8952a2b01f5085db3d4cfe9b7abe7417f196f8ab281d8a2b0bc906e1786c09aec384c0ddc2dcf6ecc55b20a4bfa0d8994711d88efd0d215f69048d4b2ab8bda9869dcb90a1bf4d14fabadc68d66056e7d8bf2ae79309aa10455e0e5b1ff18671872d0a2a95c8a15333fd9003fcaf4fef5deec851bf767702e54c2ada59af2cc87506b323620284503f7046d270e7909aea324f9c497199e60fe76cdc021a9ebbe3ea593c41914f2079a1478eb70418b4facccdb282f3e75723aedf4a18416b1eb5567baab4d09cc09b2ae441a9260ec6ff87c551053aa3a7b05098ac306f1c7a07502114cb586292650bd2c6626a11df5b6e153958a78356951ce43f808b37e727a54cbb147da39297e6b894125262ec206a9ae2ca648562098bfe093dc6bad24fbfb9444c0cdeb7f1e7506fcdae321fb609e3d80bf1a15bbddfa0ee5fade06dd2d70f58229f38de99c6102077d750bda66e99c92d34658b4d6fa9095b6ca2164dd95253b76cca969adc42a65bd4d416eef65610431e2442c389902838642987985883102b4979896d165aba6cc80aaeeea6b5c02e7ae3d8b336b165edb2778d9d5d535bd8744bd949ef1e6bd6c1e4b5161d945db0de0772482388b419d21c263e6538a370db26e4abb06b2062c3f2e52ef480c4f595bddc771db53b8a9102757137cf1530657d126aa447ecb30981d92e70cad6598ad72225210e8c87618241686536266ed1a5056b5d20968af623713fb29b9152af0d0c6fb3b8368cf199eb6be6783606cc6b6c6a9f23d26e9480446f70d2ae88e8aa080db6e513ad1e151e646e81c2784f0c3adbd1ebd9a7b33627a35f4ffdfb56f1c94cf5f072e1b90147870914872d15c9b8c5a94c17203752cdcf0e8901c0932804963c00f378028639d33c59ebd146339be0b7af823cfdbb8d38df57c46b5e513478d896633305407e4c896a6cecfeedc3a25df19023b088c751b5fc9cd5bc45d835a8a77a4ed4cc9b34a229f046c432ea78ed1cc4db8499866ce3aa8f2099a62000d9f3a451558322076783d98a751e1b44ed03a0c8ce0a1730290dc4bc40bc323d5b22e06354f753d4edec4c406ee01486f172ed7a0ac48976057ebf0bda769ba36f7d73700b5a1abbcda086a8e51f7cc6d6b1b10e17620eabbbf792069fd8881f80ec3f2755cc29e0cfdee7262db854812765740b09c77756c9ebe10590ab5725e32cb7a9558c95d4bdb8d223fffda05c3a1d41baf99ee3c1322a2e15725d701cb0852ae53eaa07346d8e9c09f802265d149d114084620558d356c44684fdaea7560978d283031a0714061bfddddfa75f2f2cca77e5fda687f905d12d53979458179dec9032084663857cfc5d94ead50b07bd457403258c4eedbac802511b1d2c1d431ad0dbf65599c9f8043f6594a2b81fbf1cf3524cd2e6709a13f19c429333a42d25ff19abada451ad2e850f5cddd74108f12996b25ba545fc1cdb1f0b6e19b741f38ddb7ad27845af29b681324212ead56cd633f169f8f6a5b5704183a529bed3e1839daae99840fbcf98235a56a887cd9677714a7203bdc3147a6a83b271e6da8f7bc521972c70bc8f4bfe9b7de3d2a971219c700175d298b63e49829b919194ad5f6adc2b38d2dc98a318b9f4988fcc30440da14caf2326018a46af2ba117a4f7ad7278af77b109d61dfb474f67e29bc355cdd33e5147f0de82f5264cd0c4fc5327806ed781aaf0ecf9a16f66f07c5180be4b2d6e39927f58845ce3eff308a5cddcda4c285ba833af8288655b177ba0a24e0104fc79fcdade916b45bf11cc6be8513c09476941c63cfc42c22b0155530fbff00af36784a1116451dc1c9f11fe28592460aded951691b361b7aee5dbbcb31532f365362a8fb6a53fe5fed02088ccb647938fb5235d2573b3a0832067ee5f6ed036893fcc2c1ef440eef7a812829f2cc2da79015a1d23e407cbc6a9933442dcb72ede6a3bbca421742191350fe50d254524f084d274b1263f454cccfa670708d0e63da65d4ff3ecf954dba451496c94143ff4b1ed4009686b017e5b9d296876ca6bff62c6ff9d813b9cd1467aac1a574647b9166e224df09148f1a007938b5633c1d3504d4967de541077e1c4c629b7cfe6a27c8f1f3d315ecd426093a6d90f5571727c505f6cd71cae5cf6ecf73dc14c7e7664d81f5df884e88dc08816f461178dab5242fed90dfb551acc7885163d4c835ef7036c7770128bf31db345bc2dda1a7475cc47205d46b6ceb05df801ed84a6f99eb7dcd420c3c996fc19ac8b8c6022c099707d523f4e65810168d8bac3a88fc6fa2e8d8aac1aecbc307a9ad66d0aefd19f56fa1803b791c8ade7222329ae4f424eedd20433aa47a544d358256165475ba9f0dc24f00267d4b01616f557559cd286b4b3ad0c48ba555c160fd2d1a567bfc7cb44af3e99fb2dfb05faa777f2df3f3a09b9716f906a765d85482afc7adbc495b3a1bce65ac1a3e6e3c816555c602ceeda6fa5df9a803c764016cca082ecece7efc3b42d2a3218520b32e93882c6ff61504f559488c47f62891450ffe94500d89e701bc7427a4a92623e1c1524b8771cc1bfded191543dd5834cb4c4a8b7f1a8f9903d6699f7748deafff51b3443c0f3bfcf65fbf144b089f00c91dc16c666ee0c2eb5cb2c7fc6686182fffa37a558ab00618d4e2ac6471b3b93375f4509b8b7fee67e037cbeba78442eaf0e1995efffc244e656a3faaa808cc0139826357d193c0bac56f42a9cea8981729ada1b966668dc426b8aea33c150f9592179a2d0fc968ba75ae7bca49a5241c6615481dd5877316f7463fcb42470ab09c293150292b981923a6424b25ed561784689aad08026bba99eb159a91c498d391ec7eac41040b862d4ec4aaff7aefd4ee5a4d5267c46ddf5c7425e646c898987bd226b3ae46c80ad4f72af3524279ae4c4a82dff41eece7feae8fc5950e08833d50bd8591e2fffa80aaa21cc1dfcfef4895d42c2916708c3877dd47f6a02a3b131cedd41e57b29f17a99725697abeaf05d2feacb51b2f60f6d333fa41f052331812e9c2e9563ad410ba82c68a52a62629b8b7b7394c6e11867cbcd1d7c977f54dd9ecf6a5bea17575de48b585f59c418c88655d6bb9cc392a3f6bfe309ef9f1c0c0fba7364308ea502ca918e19807720f0c91a44736b125357526c50ee20c9553d5ad941641443684c281d58dfa5cfc4113ecae6dcd801816cc1306487f8a6dde755e8531bb1de3e42ba6e37dfeb1bf67c0198017aad051b0fe9a45ec06f31f9206c3ca14df322079f4e4b7dc744e27369c774ecf3b445236706aec11a1df8264e75ec1333934e955d459a0c1a013049f55d3e2a47fbe8025aa9e2712e263b880320f025f5c96debd2e09b7c48f91ffc738c9b9124cc504227763edcf5eb4618d31969e7bcf154249415b0d808a60fea442fbb1ac8f6546f0d2bc7156ae3a52477c1ed8bd09f852117738ef9a60af5c02cf4d99dc696be266120f4601cd7312a7da2e4ae073986bf98fa4042033d69faaba2042a002de1bcd02576f3c1984544d8110076632e005422c76c237078985cb7381114b9e8e5065ab19bbbcbea368ec713de90e180bd9c9d28c247c021c29b3df89575943993c7d8395d536dd29e88e7bfdb5a935ba44bea82f8d7042b559d434af043fb79c546744a119ca1ae1ae8947228ba055d24d03f8eb79a443545c52973b1543f27350f5c2aa2db0a60b481ec00c8e44fcdb18953a0e017014ffb476a1d10db0b0bf742767bd94300c345f3705bb1a292447ec037334e62fb39e9460a0f752ce43a80651c1a8f2b319880d439f72eca04094e70f6376ccb6e9af0a82304c7ea3bdc21b3211342e109cf7118bc90cf36e17be998da3b541697c3a63b6f423538d2474b78f43becc134bc4a82d8f904a07176ba614b6d49765bd9e9b013f4e53965a23712429024c8c493eeb4d3b2b611d24d31e96c7859d42558d6c88e2470fd1836722b84b92b945e34285638f3e243f1ec01ed2c6d273b49422143bf96f1296862bd7a8ee810ea099b9c79cfe2ff153451449190cefd00b8ef982532671ced6e32fde9ac5b4d61c23e30030019e3186ce997a4d29a55318e0b87d817a8b3d7e5576bbe9bbb59842d244fb31362f5a2b4dc6d2271960ba10e2a7c89831d47e70d565cbc1884a4c6ec2a9e0074929ab9ae4459c8663fb38ca05d3d652c6f63f9d01363841fb81f96a3f3f57d42141c5a2750c38df76f603a80df6c0350717f0ec89afe7c229b6e25a9c6b0f3c7adcc9a567450d6c1451d2cd1fcada7a2687c00a301a6c8b2ea069aab560dcc489320e54104c13e44761a8d8399fd39036efb97e37724f540c6ae92380c346eb9a28854ee49ce6c4c8071e20a4390bd8a962112a83a57cd5d21768025e9be9e3337b51bdf2104dd0c2282b2cbec8af543d4f88f37dfb38f50c322eea7e6872cd1b107c1f715634f3ddf082da87f6e514247bd618729b85803989bddaa14ed50931db0b0f4464b0b7f16d2f430f0cc381bce47e58abe3d667e640d82800c4cfa2346f809d14facdf017a7511555430d0ccab0340206d840932a159614e6ac9cb8d47604e6f685706e4cc84082bb1122b9468b6b0433d18df71353057c342ac414585252c340c15c7072388397dc8fe00c7ad4a3f04ef8451362252ca24534a52ca1401054f054705b5088dbee67cd7ba5cba17a4f7febe1a380041fddfe14967256a84e334faabbb09ddc5e2e85e8604a2c0cde808ae6dcc434d736ec172c48990ea12c9a7046d5841014c687454ce35a3a000d40089ae1a0742e68d6925914a0b694d0052c5f2251462c4c66de1802d9a95dc8e9c5d19d55a3f7bdfb9ebe70b8d0f2ad0be024cde7fe3ddd0b0839ca6bb2baabfa45987993912474021e4784844cebe5a6894fb725cb01d9ce3078639780790e7c856d31d5356b1f65c602048663c5024a8da871cbbb816853bf38a2e99cb2052c6202f658626d380876f86c4879b17a9dacbf9289d8af4a82fb5e7a8e892650e13736c8e073c5717e6dd5fa057eec3be33cbb22f2f1a9362d9b11034cbb29f65ba30cbfec8276c0005b68475cc696f6635f07a4c89f6e87859de9694c49378cbe3e2a2aca63df23af3c8ebcc39e77cef9673cee78d9d803d73ce79b1ff3e73ce2f2e2c6c685bce39d7698de475defbff95cf1b385f393cdab635927fbbae3c676700a65db0fed1806e39807ff0fbb98efbdbcefade3be79ccf7f7fb70deef9f7d973f4ce3d34f6cc393cfaec670cdf9f60991228318127b76547535677be31a08aed1c7b03912215d3f696fc987b7ce9a35d7bb7d2b262563d09a06df907f982de515543b446abc184b62a06009c49d50700ce6c5aa372a33d323c91d6b9a5d78ea4b7dcca4b799d5b16b07de57801bb726060d3f25e57966d5bce7bf7de75ffafb0b26cdb72debbf7aecb9be41770da430303edfb2b68b01d2b7be10220d02e00c1430574cbc19dddc88d723b77235b0210ef469add1170ee0860e8c8df176f77b37bcfeb1cb3db04d60217fcbc47ded9dd10fe7a07ef46c986da1d299940edce425effb9e5b8e0393ce0ce9b0e815cffdefb9f3d3ad0adc7faf141ff0d7763b71b9de55a5c49d96a60008d9dd485e9a86cab514003b11c0981c5c717f325ddcedf8d6e67b725d2edec7b956e67deb21c29af159cf3b66d59beba7f7c6d59bef7de1edce30379cede7bc77c6fb6e36347e05d3febb69ef7bd7dcb733a5f77c79e3dfaec7bf79ceffdc215fa1d5806a427eda101457b7c8d0f42b8e08177de34bcf113cfbfd5e0728c946ca8df39e610fefabffcc79ddb0d8f165c68e71c6ca8da06ee6c833f37802e81f3969de776beb0393cf0e796e303bbfe1cbb0df2290721ef2826241dc504444731152f42c07ed47f8e37452bfcf59f7ffeecb167cfe7cd76ac35a0f9bc59100396e61c16e812386fd9f6ef4f02ed0b567afffdd56e3b7becbdf78e014bffb8f3a61041056a9763b7c11dc26c6206314db037ccb997df0880de51cc28fde91dc5d4b2f91b36ec284ef44aa73229d39c8e3709f2cfe0f2f5cb2158b7eb35cc2cad54843b6a648f10e8b5c80637ed2e81d8a02bd37e83f32572e904e41cdc83bcce9b5622b6c14f9bc0aec15559a694d21e1a19daeadab26c4f4efda6bfa192417b6fefd9f315426af286839bad17a1ad16116979344f7a721798de51491d659b049544b49f5b7ad3a29024f217d9c301499996bf80f8a35d7a4781a8d21e1d76433e5c3bdbceb4eb67a07ca131cf7d1e2103ab01a88162eeb309ac860a321bdc730fee99620338c7c1cf21fb16845b8f42e2a3fb2ccf1fb028f1664db8c9d5fee00568caab2c199d931daca554c9e2d3533daa3342798d6036d48c405e2a6f0206f6f1b83def15792103ac88ecb1a55c44de7307ba03bd7274a0e3b66d8b9c13d775d8d9cec10a9b9bc4e7bd15f156e87db7e50d437c54399159bbad29e4043583f2c1d17dc8a2f820adc00632185b964b12ae25bbe68a32b557b95b89ea51e9289147b38b2394c505cf784974f44e8379b0b011394c45232c66eef06eef631ec39e05939069c84494bbf71e8a43fb0bdacb7ccb1d7a247fefbdf7de7fff36ec1ed0f0ecc12a8292bc0ee91df5d634dcafe7de1bbf2ab47ccb23d03afa39cda88991a8fd120c914a9ccbc7d25ced8d68ef8d4617719cf1811f4132bcd7d2f0beb76edb954ab84a7cee72c475357c2fb1e07b8f7b1711059d8f63369eddd9b5e3bf771eba1bf3e8de7f8d7ff9802546c01990effa09b545b562b304c1dabba97b1134047a7271022392112b3acd2ca6f503feaf17d3f262512f36f5626bdecfc00efc30a0da2acb9a4a0193a9bb089be7dd4c5b945bdd89e8cec750307c77488c4417c15a975a74ad78335277b5658d1e12f3ae68ce5b101ed99e58763cc692da73519d31269e479c5568ac849fb9598061af46756618e97c10d526a6fac0bbcc8a6b6b8bcd04540cd622b9837321eac979594158b54d8f7a6c274620170f2552e29a67550f26667aa27afa40b88e2b21483fde05724a133b4c23c7103e7f032b4f422b423c7f781e289243b2727291f2f23952e02904e3833504c3547734a1e6732a7367b4c23d887defbdf7d88826426167d18562c235d5fb0bf23a801d920bd9b755ede9bd473601b5abce2d24c98a490542a255b5a6c8eeaa6744e8a9e69942d7b2a307a44453e1537e4879fe8e4f43897be0be5ea43e9c4d9fdae360216b687aa4c4920b904950f5b9db79cef5f9d47dced24b8382f568157b8a526772bc3be87c588e402a135a4beaa4e0ca49f6b4e95ac2d9d0bbebbe5a406c70239750ce67ad25fdafebf6de7befdf65fadd06832bdc4ac9e2923bba5973a866171efa68b0925d3cce1ebf0c3cbaa4b619b6b1293a9cd704088427ae65c4c915ff20bd9c6fddde27c5de6ef46ef225e9deb79bf00399d45cb5ebc959f71ba93191b679c35222c236959a294949c2ea27b0b8edaabe1db562da5309d3a6c905049c6a81ab50c15b9b1e5a0a635cc9b5ffdea1dc0319938fb2488e7882d17c9509756528b1d6eac23ee0157d4016730cc2514e8dc1d54875f1d1dd7837b226d78464346681074678e44683f6242284a829d5489c0a8ab4eec4230df0d843039df2be530289d7ac795ab171805b12c88c4e1288504bab8316b92812506b2fd399173ebbba77bc3f2b313ca6f2ed2fde23d44864927c446871322518350061b654bcd2c13b8143d4e4e1600dd1aac998a91b1e0c24b48c72275653ad112a60f9c27b4f7b1876d2d5ce6a975927ccc6f1067bc2019f5016e639fdcf353727aeaaefa445450833c2d4c34bce90dadb51ee518e162da7a2f56651adecf2339cd8bd78a186702f3a57dadd23fbbe7115ed8c5dc6febe8945871a135a2479375f7552f3d465ab256484b46de629f0bb61d8e87c5f786e12e5019b367af09e8b0b9c2f789408ff5ff0d08313a2548211ebcc9272c7a4b9bfa7a109ff8cfe9634922e020d32cd2846e2c348a64e816c2d75bdd1b06cf8ef4bf7baf4fbbeefff70376c701b3f919cd4689aa73a680e705a6cef1b7ab34f8837d5aa80739b53c1fdfcf7d6d99e1b4546c6ebe21229890faea75afbe8960f5d89aab31b16b4e659598c6682bbec0e4e6267726c56554dbe95299bb00ff2626c888c5e276c867de6fb001b9f05fede43ccc4e8de7bef9f03e85ea8509b74ef5426dde1baf7b312babaad30a2dd5f7dc2b4778cf24185d49d43e3825fb86080c08484a4ed6b8aac25e50ce43d23c60049456498736d0592945338c8b6ea1c8a94d71fddd9a21f1837a2de1c53dfbafd9623545943217f3eebc90a4078ac5c9a797923f3057ca8391e9ec862d6845335239b8407175f31ac454afdf2fb1d2dd90ae70dc5df211bdcc45f64833fd513a9179b95e83468010e8ee6c829cd0455064f7bcf61d84c8b0deee8742dced36625bc7f61b787cf358ac05effa847217c5f59865a28e178b5c9248f227275ff82c68099edc01ca34430e14d788ef154f4dcbbfe0982527e3d0c3fdf97424ae1fab5d7ed50bdb72e13015fc767d48e8895a80b84324fd23a362f96aee7fbb78782f251b59594db4197c9b1260f49dda3d8893baff5842ed0b75b406c7023ebada5d7e8fdbbbfb7644b188594620b178cdb901e971f06369321a1665f51d194fff8136eefebce50ee027b4587a0a7d632e659a244b335a56375613f8d7167d313a84b57cd5f957ad745f486930201ce5aa088284522688723b92733af109c17a9d6074e9411e18712527adc8d262d4406d97737cd12b960c10adb4c954e0c444bdc24fb65f480bc2b91455a6822c5d1bd1393236d3369f23affbaae4bd79599c615d78de3e8bfda5134999d94912e6215eb00284b1bb2249229e9a678dfdf87dfa3c41d03d45a5f9c9449ef0a474b002e4434ab9c181b3f9a6a45c56e12a755da0496dbb8c1dcf5bcea3d0eb93ff960835b38fcf509713eda65bc2f861346050498573349533811aa33eac20df1a8b8535695426e295318cb1029824468359503e2031b5cc9bd889dd0438a0bf68ed55286145336e657a502e7332b28c84c0ced3b57a8a318922e69f38a488542085974088b0e84de67a9dcb88bb5a4889a702cba960c6e2a114fac9cfd0bff72a522b66b0c5351d855099d9bbadb4ce170d67edadec4a6bae03a5695ee730830759f47c8206b6455d9e0fe1072d01ace72abe1e5a8c4111c0ade1850591529e5fc68c18b367dd3e9eea5aabaeee9b0235640bdbb426ff24dbe9e8a7cdb60ef5f5eb794da06f7c7e0bdffb4a70e7b077aefbd07c631d04fa9b4f7403f537df7fbf55db7dff37d814012fca44dbf358bfed480160f4a43cda1544cadee526d69711b23a8e11a115344e274b76b3c09ef1b9596b4a0d21821af4b482728442c6a56a19a1b8124660603075561b8f5162cf8154a2dcb829a598598f5c7b3d4e5ca801a221c1c35be2ca1a719f8e1e9bd45959c6afe3e5d940deee95a4d17d7ed8adc3a05f07cdf7618665161833be67360b9913560d7c367f1bb1720fbae947bef719f3f141bdcce273d273d7ebd5ff67eddef2bfcae86bfad16bd30a1142fc474184f6d683f87c052fb79840c6c096b86972112e788b9122bb5afa7d2def93c9a1b50edc905c22e8734a11a108c0142ce8ad34fca12899a37a7b324c61b622556fc90420bd2003d3dee45df99080f9c144e1454ed63b04e982803319336a5c9c84cef0cd788ac1a2493aa0c09487728395a566355bdbb9ee9c2f090fbc4be8305430718b1961cad19a6b99125b38b1094549aacf2c062be28e258ee1a14be7734270a967808b4b020c2d189af68d143d994a96caf85a62e84f8f4333a501af14baa0ee8c904510e5569f23c7c25f9fbb235b1c13da5c5812dda02eb402892c9ae5b844f7495a5458ba85f4834732b65567221a2284be25fbdf730ac14b6c1fd2b94fcd34445dc00a89343b5b89fa73aceb8fa6e5db704725bc59214fa54526aaf17c2462eaae5e7a544db7365eebd5184b75852294d595ec6e41e9032d74572f5c64257fb24daff2e2389eb7d0c86932697ad2b201ab52431866e65e11b4116a604616183dded77d98d3d67aa30630ce0bd0c06883ad7acfa9270e3b5f365e49b7e548028ca0c84591b4bf345168161efd18a28bcec3dcb5e407d4009bbecba1ea58b097735e3edfd4f57d5d7c41dda7e64c840d17ed05b2a844a7bffe1bdf7decbde7bef63254f5d4b574e1d9652ed9f67667c7734342a48c6a607c6980f1e58b850a3127b356f255269eda9489bbc35903926bf231f930a3e01df753350fdffc40f36b8854362b12602f50c1f8e196d8bccb215cf8fa7d0cbbd77d7a2f3e706d402eac232f8ab71b373332a892901332757c4a6238487e62466d5f1ecdd24c90677d394d6af4da21d63473e303530291fd99d8f90850f930719cc4e9040499ea1b87c740e67c5de7beffdf7de81b0876121515814020991c2a5104ae8ec1b6a2cbc6f1c73998bc855e402e242722db9a0b89c163f1fddb7c058668bc60466afc4528a9d0e8e0818a0b21bfa89e811eacc2140d38b337a9a1229d5a971343549f5e4a400d317a346e4a0569461a03cb3fba3f84ff80092a73ee8d8e736899a5cfcad27a0deef7e0b3f5bb89e2eafc2de08c591188dd787d2a8d542f10513dd8a4b53cab0434ed87b988e25b2e3bbb7482568576b31445e3f00d45674ae080c94bb9133ca11c925e59ae4a632e638a3f7de5558e1c646cc0ae92d1573c30a316e428c3011e195c243222db8ed9e689e53bd9725a84e343c411e1c0b74eb87286feeef30102294d35d565446865e1e33a44beb8925470202098825597c2429d474f4fe9e38d9e03e79fe13a97c42d538719b3b4e4ebe1b777c3f58f87330296b39201f5a2b5fd4c6b8e2a223b6e7520da9885445382094fd6bae6bb766176757c28eef6ee44ee52ee626eed83f5e0121149b25963fbbdf37f99332cab7322900479a4ed1920bd4cd15dcef605395b5d467e60c51d256e9805dc47b0a96985ff417febf77a938a912527caec60bcbbf0b8bf65efe33fc72fcc4f38fbff7de7befd55ebc6f61d866c9063794f329e56043f5193eb0f7de7bef1df87bef1dfeee054a8be13d865f1b0a8261d9638309e3801a8ccd1315512766c4f7debb9ec66851c141e8e9e1b8b9643f7044f8e921c980d4bcbdb0d045551fa82a172925106af1e7c191eadbe589db4fb4902fcfa40d0ba3051bbb2010602504ed86c7a47ae246e33337b4a797a563c59237b3b6b1c322eed639b9bcbb42974a59622778754e22b81640039b684c12c54d59e6b743b44e088902416e5ae054c81406027dafd9849d8ddb257d1298af6732004e2b6332a695502901c6f2d70169e658b26362676f6e2dc8ab81d080d38e15647c96023a82c5fd8408ea00985d915864f7d2567b35b51535e285f86768e9dd8400468b570475b85261026090c0fc94e47c0082f94a9399cabb781649aa6e46f891ecdd7b4c8c61b99853d632b4178d12aafddedcd11273652a577b341de0903c00b61fbc2b3be2cc9b0df442d0c886296553b5a78e7959181311000f671632dd85613ffcf1306c4b6c83fb43e9de5b0ec33f3eb11f805fe94000e1af74f496ba497eb563eb94c9078586f7036089814045df9ad15cac64ac8aeb55805fa6ae67d4562da6016c6c77204b942a67ed977cc43ea486daac96b52f03eafd62e96b7c77beef76d2bb19d2e474679194ee9ad91df1a6c8885b0098253b3bb710cca30d205509c1fe7a946aa8dae39237498a788608277a02ff7edfe2ffaeff00571388f1c4254ca4ee657a523d36a6223fa5129808756a003f7ee306264255296c4a361fa6533b2421446fbaf672983f604aeb02e48a2940ffa03670645e6b4030e48b1e132a4d410a8343cf1386e1fef7fb42fd8f443125806fc19fa1d0bd7b12b7ab58f078bfa2b02b490e2b4db04c9755c35237a6f5527b24551c1d0a148989da8f8dc071cc2f6e742e7cc6d44c342ec7494422486caa9882314c522da37e2d4b05b8f3020bf91b893b9992867dbca89bd7cfd0d599b4e3f93b7f5fe8930d6e29d5ed7380d275b47e1921186dcd39cd841e4884cd2cc501b99125578443b82a0ec3bec6efc45ea6e2065f2298b6b450460081c590bdf150d1c2a4754a2a7ebb0a86642f28c903dad95787e10f5b537246e9d704594f5871c370c288c260be181ac01b0b9337a864ad5ab99832c0a0440808531800000370280a0461a2a872790014000827be7cb86c50242c221247626130240603c28030280c0ac25008424118462159cd718d077002ab040ac1a6f48320ab59a286a07912a6b7b05464f5c171e8e93bc0642f57e612ca5c844cd745589f23e7372782460f2fc6a3780e1baa04a4291a00ef42dbd768104989ced341f526d506d40fbd5d1405596b33d139942db3b76ddceca7be6de7987b325fee6280548595233329f5992f57bbd0215e9240551a9aaa2a597d2aabecf2dbd0d39301423a4d7b614d56e5ab3bec5ad82429727f64c17452587b69884248cc11491791e4e2d2de3d4c4f4a709ab9389613bd114d416c2f48d3f0701de87bb03bc73c836fdae21b9627222ea7eabd2e1f53928e7a6ff8227c2274eea47e8c177f95baaa959d9ccd3afbaaa6dbb8b0c710c3439794e146523d850934fd62e995085d5c9bb864f2662f9e641af54773dfb9680b16d38405250115cc65e57d13145f8fb80fd43bd83b251bbddd065bebee523fb8fd1b6001032028840d66a97913358848e5ce74cae6bb7f3d99dbf2378de705a06c07a920052e07542632a046602239702a1e67a5959f7fc052fc6224c588a696571a1395617b168faf2bd5a3d7c16bb572d49f585699f9ff91e1c922557b3111292cb5478133319aa640c362a75ee02d68a98d87455fb43c87524958ad20ba37936ce7d3e0d7e99d3c15bba8b940e40f36ce544df7e43bffd07fa8c0fc9f9ef24a8e3bfcdfc9f676fef65533a13c06f47aa531ff01fd70db318a64faa08997fc0b59cca28fa4cadb0a3707e8f0209198ce8853543b6d5eb2a6ea60493cef17cd921a4f7c1f874cdb97c867c699cbc702f16c92a012194917ed0c3a008acb506b68360fce31d79f0a9710fb36503ba6926a1fcdb4b1595e26045c17f1036c85dd291348deac24275339f171cf8db4c6d76e68ab9bb1823ebdc9502e1ad2ce47061982a9b9b6ac28b370137009973e7e70d7f8183bc8bde4bfbe235bd2e23f38882a2ecc3d50fad088a22a6cff1909ecc4c22891061480553037f42ee2ec6269aa3ea348b077a1455fcfcd9af7b54184a112723bd05807cb60b4a10e9a3b264f9bae9e47c72a7514fb39910724f3237e578bd6edf5487c0f799d84237e95dc3dddf59836fc35352db2048931b14ca24d130e4419001684a22475d1e9d91bff7df4e3f880a69cc6275c681a1551affe802bc32b22dd75bf04a101dfa8c00b000101c4701cd0f1c920324dab8946178974dcf80a2f66512a040c512b8bf3a95248d37306aeaa07507afbab792151bd3f110d2c0ca07eb8314f10ba832891ba4eb82ae8f9f065889c9c9c78010233eab60b30929e3a8924a66760ceac53aca74348e9ca400ce7d71244975085436024887338a7c221666839f9beeca01562702fc889d225cd1e281a1d326c188b74c7661ec22c31ba172522152591b842a955116875b3b2d50c63261cea7769c02f2c7adcbc04b96149500c1560362f66260dd2008fff171fe93275e285006a252d810b0a7a4dca9ab2e8c0351c2d8ad41166ac1a60fd4c816f9539ef57cfe3be38421d1695f10482b3031fe807b5019e5e17ddd879940d7c81bd18d4982447ae7e045d06b6263355be84c3d464811bf105c4f604c3db0b104c5a11dacb12ea496cbb2a31d386b5a2de9fc40548b878d94b9081bb97c4863352924391e03beae9670993094bdadc758b787bad2417d0f8332f02a3a7c4203e61471c3848d7c6c86989e4f94d41a2e92d616ffad665f54a649baf378520678be0fec4a8ed15524c6dd8c4aa3cdfcdba4105793ee8404e912b75a90df73474ee67afac7f4ffabc37e138c4837a85a4b0f31072b423ec054a44c954294c03a1a1218345d14623c756136991b538b0ecc76f8f9be08825a6cf7fe52da8097ae0aad77f0888e19687c825315283aee44c7de2d9bf05276f90a01e9f4fc535483165095fd93afa1754eb0c819d82de9764cdf03048bb4e1c570ef894c7deaba8215f415272f048210c6a287979b41f6a988c9c021eb84f5d67bd72412d883c5866ea59c2de59d02337de47b85ac1ee6e7be05a751c73a8032f4fece82b788fc319a985f5f8f3dd2ed3eaa4a5da712a2cb7c5ce628a337cd1302c26d3f8b39948df6a359a7289b8913c826948ba74526ac8b1d12391433b04d4b846484e8fe22a40f1131969c7c01e4cae857c88f080ed93a50e1dba4584b4b481c5142eccba9e351f96861e650ea98dbdc4d1ac953c5c720ce174f51d424d94d12482cf21a10c967ef47162ce4c86405750c9e8625dc9f6bef5ad964cd6c678b6e4dc20dd02f545f9b40d8250503aba8c5140b3ccaceec56e5246585e4c5d4bd80e8862b64563048e9c78ca5ec5e89c4fd8bfead778724de370a6223c51c109b4cf71447765c0ba50125fb11235e25099163b8d2e620ac980062dfd6e88c0a481fe14153168c9f061521313af6632c48f017b2ac83af61946400414c1a5bb7e75a03dd6d90840a1f745c841e2a968dcf0a6989e60c97ac45ca69baa12c5d104db48221e2231b4dd14e89b2055c52f6df087648336fdf3a586272463e956d4a8957a5328a77c85af52a721c7bc7b7f5dbb5be600250928ac03b91312dafd68c27d4bc3e236c0c5496720526e6e9c9c673571036cd0af60b6ef20abfb3072f2953634da706301b80752a2150735cb56a3770177e14ba3fe95f60d7c3d63015226bb51f8920728c6a9f0858b40bcb8224051a4009655887fac9be8d0bd8017928a979665681e2b284cb592ed9bf71b0a83cf4d190351e002345aaa5000821ea1f8c002e6d236dd644aff9b418b9a9a79c662cee8fa5ccfa2bc8f1892b0e1800f0d9cb7d901b89654a0d4c536e9696be04966478ac84123383ca80c459911f724885bf9bd64e90c0cfd8d33a998f67c6320c4e3f55636cea3d1035ad1f6b6137b411daac15149d3a137299791cc61482bdd52774e74fb995fa5f2fd1e8bc33a85215a8a3ea3fa01ef5740dc2625b5fd979179b0f453a790cd4ae4ee792d45c6aa3793eaaaee985f7404d5d4b1a3c36bc8409f03bd4803013bf253d1ae7ed98f6a65a12d59ff321497c732514a801c303122d2f807c3e1338e5ea0487e6efc3ff5a3272019be25a7cfe3440ec0869a00040da9bae32708f3fb456eef24e5695f3de93631a0a7c2b281b0a076b85a2294c65ed95b9e89642883d663316f6f375d2c8a5477471e9e088c710e2b82dd4cfb50630e23e46b20b4beb8327d9c0e82a5e2ffaa8de33bbb06f54fc571bb95cf2482b1711f4172f419897a0ff12a079f7a8ac9704c4146c5a573dc6b9b5f8448f9ed0710ac3787abdb58557b2d179d3da6648a25fd1016cf45c9bdb352e009d3e12fba4b19e2d9e97c0f569771221a33cf08884641aa89fd2cb4c7b1e8e493d54bfc3283b2893583ccc494492f3719a2d8561119d2bb8ca705e8567140583df111b4c2661e4a0a2c5b098a7f88cd58189ce0941ca6b4ba8ee9ac195d7555f0033d89bb01c1034a4f8b39319cfce4ca08ff59ffed411fbe40c41e128622a1269a45fc44039d1816c8ff49703fa886792250fd13fecea93f513939f10f46160ad9658a2744dd12ccdfc5eda4f485753db5a35d0af4f81fad33273b411e563c31ab130c7412564dedd7a55239bbb04c52849bb5e8d244bc0809920b59b4cfdd21d92835cc60c51817b6044cf6ab7a1cb3ee7f3f0309d344d544011c3a5c029472bc102ae93be9279d24341f6b8874a35911e540f54baaf1f086c73d526c5e11909ba10c1aefc21085ea56d609408ec3109dbdc30075b5d8004a9d568968e632af134266923ba6b86a7088bc46b7cb1038cae4306209d056e8a5919807ef0cecc9218e8b08ae484114d1c9ef66f2034cc48daba6cb43ad3204d9d4467d24078cb8dd5a7fbde1f4aa6cbc9b776acb4b5cbbb67746579527bf92f6a5b8c9e872dd476b393fdfbd484b1c3b7d5ab506d15c42aca03038888a83d8b5b6cd4790f76163f597e28ce516541572350552c886c2fdbf952ad6530bee5460ec23621eaf40b67d132f34768ae53274a20e9331f8029eff3204148946662157dd770223345e00d980bb1df560406b0988380c6f86acc9a51106c148139b23e712925c58f245247352105acf903f804eea7a241e583175e60069a8069be5355b88d7f6e9ebcd3990680a29dfd9ef25b20af0cef36a0ec0a101c3ac39e754b13c630f6ff9d2f5df6bf13c7befa2b25bed8d1af54f969a75c640887f4b1918fdd54a5812014edd7f08dd769463199fc67b634517c9f472eb982ce9dd80fc185e8bd6fc801349d122cd086281005425b717318b74c5e73fbc0ac7420c2b87b51767b5a267770af7bac2fd6fe448002a0a9d2b62447fcaade2cedf023a2640b004010a52be3cbecc8f8d8236be8a5f14167b4ab9df5236af1e4c2b11bef11b8a0ec4fb58f786b34a9193d88b1fe50c35bc7916524ef329c3b245791fb68e80f3fd1396de52ba8c099a4c0ef47cfca6b1c4f329b1f2a517c5745bb88e1a049330c0b75b78b0296a0e7f0827c4e71149c4b09ce1ce898b7ed6a118ae892044b210bd84735215c0a41113ade3b564ea1e25a9d034f7e0b4c344a822a2752e09f00ec14d2b56f50ab3d239c1c49300cd16aa515625d3ac3c041fa285391a552b20e4818bd2c1ff3cfe97c5a29c48a1753c8cf4fd16faa3b1f61e0d8aef2de4a288bf3d92a96d058cb4965543635eedda52cdb12d48dbbfc4a657f45ab3d5c54a4cd2f8b58c506f8cd0f03112cd78f78eef31b6da7b306a4b480019f3e55d35783a5db02d586b331e26333433c157c40e2f8feb0a9e4e2a4c4255b061401f187ba2e21bf06d95930a6aaac9af87944f6ad55f4428a27fac5725c4818e37404d10a69a073ade55e88c1631815aa716a11d5d953c14ae4d414db149ec6e02c4ae52eb69ca9b57ff491efab80efd6a670463df8dfc907825e28875852feeb7f02dd4541a3b0c7c47e694270c82162b03918c678784458af2625c5d7f0be44444b536f0f146e05f4c81e1309da2a401f6a363979fa3723cfd38fe2c53c748542a1c2150acbeeb9b08929b7d4a85aa73a6927828ed165cefe08dff04c029527a89b4ddbe77ceb1ea1fad12a15631821a23103814d920daf39f5c50dc8810cf468865d7f5a4bd638851a8bfd4ba0e5379669553f5cbb2e003add0fa382af02f97020b3d6327afe0057316e4a333a2f769541951945bdfa779055110dfd81ae6b7ac8bcbf362b059d1f6c63ee8cc78bf7b0da404f52fa5c7e8eb2e2f96e3d338af923f550e391368291c12c03420ec66f1f87961ceba49f6bd4f774fd2383249550ede0646bce7259967e519d1ac039b4a2776011d6c6d8429e34c4e12d64fd547d685031dc90f7d7e8a409b6530443af1c0fbd3a48994effdc60ba23e5abfc822ed9f58150ec61fd51d37a5879b050d32d4706de5e71e59382e034baf589cfa8415aca3daa16444cf08555ffaa0219530101014a59ed718546a113ddad490520541f895c6506b4293099d6142b026fbc02425b9766bec3fde56af7c25cc7a42b0ff478f2ddec51612798d9c79c64a278c8ac291cf0ae9f063609e4e03fbecf188c0f00d93fc588b86e4d04557c7f1e5ce8ee24e7ad4bd1ca5351390a4eec52d6788775b3b1921c85e2499e201f56fa8c42dcce83440fdd67328e9db0df20f2e48d06ad95eb78c32e7cee1b13332854d134687fb0e2f8e2a9eca8042810e0478493702b542102ec0a909f178c8df998721b903b746555dd6095880e91bf08526678fa92dbc3408be34e8ee1946cbaaa06445504a2389bc05c5d483904583e0a2d2a8013048248ce93510d4e78beaf335aa2a4a82482ae71625180118b55e401025d29499f24c8ba1cb653192082a34993c8923404f490d3e4c65b8e3c3d4b4248f2b329d43ab7dcb17363723d2f3ecefe383700926a660ca202009d3c123185726c7ec36a4b7c76460d99e85bd1f7fbfe4a1760823eef2a23504d49946aeaa83561f2e590d12be3bdd016ca8104d0610830c174bf399961726a67af4525a64a7c1c394e3c7e68051cc1a1516c965ff45e95f5e1aed1ba2ece648d18f2065f448da4707f7ef10d3a8bf8ac811d681a1363a7cf629398b12cc3e77b43b64d04643adc979eb568f32d5a338a6af0590e924f04b86af93bc49df479bdc243de556a69367796a55941135f5766ae7bb59e359abbfd57ef35953c4b014b4be4e19aa3604130312ffda319c2950b380ada7919f12a38caac2dcadd97562c7cf8f72d974ed9fc64437d5ae7706304861f06d21c115bf29cae7aabcccb6555a53a80176aab4e11a0e02f517d3a789921f541c7cea54162169aab62f09fb945de186a6badc2d6126d2a1a98e58fdd6f931bd7e8037d387aed6b9a9e05f2e12149ce0eff9a0bb7284192d5edb300ca09afeb8cc812f0caeb8266a0ca59a7f935523d4883f97644d93f3e87c275ab8d8d5fece6f7dbcc1b96da702a7226c0b939c828ffa9bbea4f158d99495be2933eff7522e22e7aa30851d3ddb7914eddf56d6bbf8dcbea3e51e917cf4f4d52dd4f70c09a5bcde96b67259ef909934bca3e60986c8df8f36f1354f47925bd09b6ed5b7219909648b83be82b3ce1ec2aaebe669192397ee7e7293e6515b8cfc5a45277c60baa5049b588646c03a9dfab6c1262d78b293c952a41923e38c1a3029cf78a9b7ac173d345f2c4e4141291af0d6dff00634ea4083ec1e4ba51c4a63d72fe1acd3a511f2320c860243eb3ef1faafc6de8601d83c85fb0c0d50271811ed65d5a15f6a0c8154417eea7923941553c05563ac2832379bdd3a1d0550b95ceda9638c8e2c22db18f428007192496b1fda989a92c2b2b4083e8ee6549a26612e8d992c6bd43075f40ee3632e40a0278112371789aa48b5855d51baa9e7d9a1451892b8316e138cb1429d7c200f61a5aa4ee24588360a65b209010b98c5147c45a8795dd3c73953815c2bf6a97763aa0d2aefe8eee57ce17aca0c7303e3b0cd753ba374f73bbed987819aaa69ca4837a4920cf28b7a3b0c740603940b2631ea70203f7420e5a7c2b623be0123213b3c822485c74c9ba816ccfd79ec9def1d29e4af5f453b1ba40edbb57c0aa5426308eb723a5c620529785c8b3bec9f64e75ed09c30d1673419316fc88c8cb96704954f350f49e13fa44f520b2ce9948103bfc27e085a58bfd40ab61791c03452d23fc40e658ac70bec5672f6597e8badbb43d85594ac6631f884e802557a9f4fa4328d6af066fa8bdea9b90aca240e384babc8d2387229f3889648563ec02d774008862cfd04a794a58c6485114b4b947bda19ba1cbc3ab8aad524198b42457e06e9122888d53cbf4b9a919e8f94949949b2bad340dea720c539734bbfa479538c3cb06be0bdedede8313b8cdfa09c6083393f7b457de2f5199d02a60b4306417d39ca20ab5402d5d6cfac743cba9359d8a2b50f0354f96b71c6a912fcd6d255696d91577d5f6d16ad2c63ca34ea55c00b827ac0a96a3c622556e4888ca2ea914e66b85ef4f107bdf6de614796c16fabe67c1d1137377b6447000b4f22323f8069afb2beaa5fa22000e44749d49cbe14cac81fd21b635c6a3723b4a43d70c43c7251c6037217fe36871078540f22e0ff1dd1d2fe9de06b963d76dfc9abf6c4e1986b5aab816b5c563479a7943d66d200f3c7b72369626455921c87bef0da838a486c1c1366182b7de826c1ba21febd555d57a954b66481365841d8d6bd6b5cbb05565a015ea31788aadf232128d4ae052d3fff0a7b969ad6a773cbb8075abf9721db3ec763bb4b333bfdfd5ab0005ef45e6f7e5b58ead0665b013ecd53d123cfdd70ac0079d069fa680d68f1ccc5fe12a4f0a565055b6a784f194add5ee5ba18fce29faca2b3330975a2dc9c6afdd3a89b8b83eb2c06ce4a4e4eb2dcf8583c684024625e2f58e28072e4dce83a99a17b21e7444d26962be49ecaee8213ef435f8ab4c12c7a8422059056da425e65153c99453add23cb64231ccfd702ba029c52f21a6354fd6e9a6edab6a91cf91a8bbc2ca88ff62e20658541af136f57d2b84a865a49e8b04154bb4fccf6e147ddc2f435ad4e11b8a0c15b8b450c7d32fc7183ba4abc4bc701aa0c601f758118eb782f988e99f61bb6710c67b5a2d79a001293ee1d3a165cd195899648d21a52ee664f776700dd594efd727955cdcc5d5f26107f32c84c0ec637dd0b68a0354468e97d8ffd81fd49cfbb855dd0a2197460e1d305a51fcb86421274613a39cba244abefa33f714a5babb5e2ceddc08d34d04e6f3212feae01dee386191a82a0177eae9af0b788edbfc30fc87839962472efc8047b18631e9408fbc9d16a204b4a0e74cc8c2b917574c89f00acaa2c89319e377f90bc9a77a74dc007914149c616135f82167ff3e2d29246b979dc6c38503db45aebb6894dd98247bdcadf0018214f6b32c2975a598a1d93ba0eaf2a7e107dc7beebf6f7a23b604e274d0d3eae5a270c2412777b0148634ef2aa4f25e3828642a69fd4f1f740e43a8f726d4c4e3defe2e48fbdc980d7176b340dfedd99a8aff85e81f55c03ec036ba2eec6e56013e710f3d5c45869a951180e14961b694d74e8140050c136c4f8d1fa9451dd738b34f414e14391a49ab4bcb1739f7d54874e02b7652d0e9369b1a1bb82e9ddb361e12dcbdac836481a18a52a4c4c74c25d07bfb1c96a594ac32c0a0350740c3b9d59c71acdd94d7b8f5a73dcf3285413ece62c3616d08261270bf2ceb0eafd413222d0589466dc202188116a1c6e3efc3b5b659d099a1b08d53937c1dd453a4095b29dfa0c738888fab34a075655a4bd4ac27f58a0eeea0a68de561e095d9b144cec2119f3c4c9c54e36a793fc68204226742b79906cc332f27a33661268067618419a69e1bf4ac8f61fb98bd7ecc538b420c6d3e80a09124b409e043453c1e61d689ef597ba10f2458e82d94aa5b4d756a58afbdefe44447a56b7d923e518626faae87129fe8f0c7c20980219d102272e68c204465c6004acc519dc43e490ff02c798aac99eeeef3756e2b86e68ad650a2da3505e963b6f0e5af053e1313bf8a7eb88cf65de2008f930b37859b3780aa298a2acd081fcb3b3e331d01017cdf44baff2d0c21ca7f343882f255a9ba00a1f79b326b4421b6c0497b6430d785903b1d4caa0b254a645a632d7eb8401fd50845491321521823797d5744cb36d8a3215cb63ce8772f976506ade642865528a6e364e924c4f16bce627f4fe801d3dd8c148c5a09d9e43efedac8b9ff77c8d5650acc858adc96b600b063131ea1e938aad0842e648240914d36d796437b65692aa9316be00ffec9111e3e1d0c45139466b646a6561eb23c11465b12c0080aea62efab44bd33dc03659ef00b09a9c5d15c39498d571c8a7de799b1a627d64621d5d62c4749e399032c11404a77f50d70098f4a975455a52cc64b6d95101393fcca01053f4e3ac1fa806667ceb0e48927faa434f6dc057b60812630fd7624b0db03c00d7da4d6757db0173cee40686aad78ce063a84c4b7d92962c8461fe01d5e17e36b4a3c8bb016a3489f64384e9616843c1e88ada6ee43b1a4c3ee83cb4f9e992e862676e340718e6ce9c9ee83d683e15ffa151d0ce8280adf79f6ff213346e4bc9302ee70dab899433622ca4b03a1d964004831d1a9af7f3420331503a7316825ba66c01d2bfda12a4bfb45ae10a1ad5bc323442a8aad4909ec05c60ceef0f854280efb8e5d8e314722a97314c013198d26bc84aa824a457697800be1d90a4eee51214871e68854a99285c1c8a99568c0784892585302eb79dc8e404399aca4c9d9180748c7e67eb3204634cfc905cf1ae21c3bd497a2866a6f495c1cb60b98a011b9705c9894961fffe45fd077aff4b9c31ae17cc2a183d61014ee5452155ac8c30e9c838555078d98f75b0922e0b5fa644bd308780386148f264a3996e9ef73f567bbbddf3729cbb1a564382ba0bb50736b3975a7ab1dea0621c24fa814f860748f0b3bda75aa7cc220e70e1dadbe2d72f6a1573d4606b665938f1626fd8e9e10a7ef2847cc4d78c73722f26684d1e2efcb9a82b23db84d45db97a7563b75bbf5180d52d842ba7dd6b7b24d2dfa5d9aadb00ec919ebe9c62b3dd5d7b4d7d308984e760ed04db4da496aa148d0fd4e4f3140a3ddd92bac0378d47a53576aba187e37c07cc6ed30ec84f82fc657ab0a62640c4a1d493f119062f0ea4801767123e4a3546fb83036b35ee0fa9b4576b7cc03c9da40199d9eed5d886b988834e3b72fb3ec951b39b221eeeb59f03a1349f623b79fb72245bb7796113413795dc4def1d2baf7b5e7b1cd1f7bdfe08c214b6d20ddcf9a5e6bbf0de3220fbc063f39cd593c2883932a4a4fe9d7bcf0b6ae0fce0f1727d858d167dbf76dc1e78c325f6d6f59a02a8a59acbf886f8ea1e063dfb12801b7ffd990032874ffd988321d4142412c082ffc6fbc360828d02a5baad661ac8d81b96023968d548dd72844921db9832d310febcdfaa06fe63c1b4e6e4d90d5303c6eaf1d58296512650586d5da901933f0422247e96c20a398bbe9eaf626d64953026b5b91656c753891ff689f6df94f41233def871d9ab4a1b44b8226bc9704c281ea38226add9dd6c8d2343e8bc3855f203f67938b28a4ff21ec626d7b46ec115be5ab82ba5bb643cd75efc95943594200a3e9c63bfe3e0a30e567e01ec0cbe2c8f5939b61cd378d19055d9dd310068e40f2ad5f07722abd730df05c7a55edd714ed726c9796131f3a7965f64e14d99e42a00ac5af0cde2dee3b7b858b0b311c18cebf65cf28409381444890c8ba2c763077769b6d05b6746a8d426c7106acacdd427cb979c50d8cacd573a8076fdce343fa870a59746fca009ce371a08acf9e23f74f6fae7bdcdcffb5c296aa8f97246f0b6980737ad197aefbd1cd02a128b1cd2758d12a6f12715af6809fe1d6729b03459d353d36d0b424d3966664dfe6499713c51062fcb41441ba79788078984b07d701f65759279cec72daa14f5a89524e9589616c78a0c12bf48e6cef517352b60a47fcffaab97592a4e0cdc66567c0f9b8d4cddeb9329c7e87000b920ff9cdb5e378d90bee561e8d85f7587824887c3111d3261a2687fa880e51c116fb071c779f4873b054206e2a11b0b40eb868ca6418db9f478d2b1d64b8f2a865bd0716607ec3ca959492f839835724634dc05ee0a8f54456b752146b0bde0710f23393813699ba9c87b5719a5a334954987c250725e188da06b4c8d4d747e2c49d72bbd8c21acdd720c4dce8f942b9aa71c3cc726658c0cc0960b7cd111f1acf74d9e73cb42ba1a46680f71613cfbe3a5670669caf58b76500293a02a9fc6c372010e7a78ad4e868da03106e113261f3c58d6fd3c784ff001206d347edb3aedd86118059f942591a9134a1bc7aedff5a5b6d2d525ff945adca446783ad27b5de86cc32f782f6bf6139b011759b0673ee1c319659cb007eb82e308d623273a1c17765734b0e7fdf9da1e1098c16cf72669568b007af6eb67a948af53b50b3ac233488bd95a524607f082a18b438450d334638c355efebcb6e700896aba514c9061b00e26e2cd331101b98f1fb62b9c3065d72b9a8cfe7648ac3154b6d0ee07903ce044f2985e84b05fc0411a39877e49955a74c25d1a5b9c1145e10fbd8081cc74e2cf804e34a29d5153441fcaa4d5c9dd8c6ffc9119daab21c9097c6b874392535970f2d77c389d705cc25fdc100bb794d09ddf4589e8d09fdb18cf062ef30a1cbcf8f26e41e309583957bc56a2f33ac0c3363aa648530e8e115a323c9129abf92cf35092a3ae98e7c55d77bea5efe17e80c56dce8f1325826a17d061bc70a03840c689596f845cd4507a818172dac05d7990e52fa2dd7efe888734ebd50d978bb9959aa9ff238dc1949fb8139ce3f47b3c175041a0c2800b0316082c005820902d60d820d1665ad92fdbc0e939d18b3424e250a4419106231c224460bde6e7e5d19f24c2738540c6b7ba188403c2ca6c4dd83c88a298bd8e7d840c17a8f7beccbba0dd7661ad19a9c9f3ca09f2ffcd76ad748be675a0ca428b6c01a70b5613eb2c9190f4c844654bf0d0ae02c2aa19c09621579b43cbb54220d2964077556b6bd452e0254da4b521a4c764a97211539b0ba83e7c687aa7cbb169a9b75f8ee36c95136e507e24a25e95153bf028192c75e7bfc68e341c8fa0e19c4a3209c3edd78a431423c6281cdd50aad8473cc5efc00ab60eefd11fd6d522cd407be8a7fbfbeca1c784a4f422308ddd6b9cea5353f76a752d2a7af6b7b82e24d57eb5f7daea8a4f6b0169a716c165665a7f17446111a8688d4218333139ba54abfde0a12854efbcb8d5d8e3636194207f9f65269ae366f30f63f6e52c98dfbf09a15bd41cba3a22ce98f91148219d809c742177d4edc9b17d4f219e7a9fcb3bb789a6112c1c5189433dd14361f5de8d2c2b02c802fdcd66d86e4c7d2ce1cc5cc94ca02efbd2c2e29b5c9a421cc2d44b81fb1086280b0710f536e10eccf02da123e22798e4fca2e940118219b3b35bcee8a77f4820c9c98b7475767c643e77edaeca48b024f182926b31d24a32bb78c377afdc7df208f222f42160e9268065d614c65f763e5f240ded74fe10879c757a25491360f62c8be46a4644326eb90ea4dae367ecb38b768c3707125db6e3774e4e30b003cbf46217b8f60a95021a4f9451e4d2771efdda4aa3b406814bbefc15d87cba527ca0d0fd57a4c09644ae07eeddd96a7939bfd0dde27a5018e0c78a8fd6cb5f5f2ca515eb7440a03b6162c31f0204401044b32515d40e7b1e6549f89f3710a0565fbf7d93d8dca95a1e21a0746d401ef80870d3aa9b8a0d5eee4ed5280976eaa204a6b1e91eb6d7bf0c17de5ef984df5950c400b23f2dc0d0e430aca1d163adccb66c64cd849821c72086bca72255c1412344083a0dbfd1d2d46fcb4fcb91e4a4f3088b69e24686e84046441b4e25360040a234ec596c2f7e302c635079c797044d31ef0e6b2ff9e7fc263798ffbec4a9dfc45d78fadc1924dbc62d431746a7ea2e6506154eb34874f63a8dbb3b7b6a6f8df3dc81eca85ca6df0816b305a3dd898374dcb856411f09f3501caff91b4181f9b1970d8e90d4587af450ad431c9dc6a64511a94d115b4f197a1509ef558feda023c5878ba548b7f56681022ce766169302914ce6beca6e87ffa3f623e7bc9bd014008198779d57c1f3d0cc99af7c594c173ab04003e9cc8f4741c9749838b9a6c9d38161385a1e3b405b6691cd6e45f474c494195d816cd401ea6663784be5d2227b17340072d144b9235fa8cc3769b4fae1930516731d9f5cd69739f78ade4f96bc1385979618bf7f7fb5dd36d72c4f1af9771f84549a0be81f4ea6da597b6b1a2e536ea000fabf4dee05ea4473585a686f6fe1279c02868530655034bb563a8a5dbcb7c1a988fc388efbbbaa70076039892bcf5acaee1470cb39354aa069b87c622644d9b9b7845eccd949a7f8abc421baf0c2eee38eb0e895b4198f58b652191a2c5362e1dcc53735334b31c119e55cc130b8ef64a8ee484dd1f66d8c13108f1c2946fa8201ae596b4e6d2fb772b65dc495ee3b87511ff41196ab322f2553f7b39d3354b45f1b337c8ddc01e6c7d017c366b61ef2c5a94801f500bf1461e1a33ed50348918c56146a530e1887115f05ed590c6b277922b3ca1bf701b6ebcb918ec4dd94d1691ee3bfb6b847dcd836764e46ac4f2dd3ea7010b273d7b82eb079db440de436cb9e89131f26700de0a95f6a7a9cd08be673910b578f9443b521d1f08c9e26b2e61c8ed1c10a23376db854d47b0049393032ef479e9b5a0187c0bfa1a86105e6f1fe09152ca006a8f06a8e369e620b18d4f0f8c8950e846351e42c7a9a850bc9403eba31f411473132a386250296a2bb145e9981162db3554c27e144913a89e0c564647703da70927a5b4052aa92368b16a3c974d9eaaa2d5a7a6ee3c94b274f4196b4e295068f3db976bda552b5106236969aae4abc54699e8234c210b87e23aa3e6076fdc128762de4cfd143d9f7f50499965c2455e75cb0d6c6460811a195ddddbdb96546085907530736ef1db0cbe69b550a462914a576b86684582af05c599551f90648877d51d086e60357e4630da5a1349496f7770a457ade7973f43681899ebfb525262cf199113223046b3e9a8fe6237556ba46ea68ab46eae8babaf7de5befbdb75e57cd0899d1b17546c86399345205f0017d4ae9cf772d0fee08657c9718dfc5c5b93c16cfae3e5f825bc5dbe202f330fef9aec5b9bcb81536f92946ac9530cfb04ccb3bdc5d129097cbb6b19ce51d359692d2179f38700b0f07d432f3a614d31bee74ba3c19f90fd3df3134ef70fd7289045afc8db50e5ad60abd87fd5aa803a2d8b5c460fbe2f264aeffad3de9b327e5fda2278ddc5a967748493dbb3c96d9f29c85afc77e478d657ce16d472dff6e14bfe01b8e3717a95330ed606defafcb63792e4f66be747171c13bce2d6afc2e4f06e7cd8376b136e942539fbeb83c16991df6484e48e734e7a2b5f6e9bbca57724ea22a7fda97b6beffc3a7ffdfa489ab9a789c02e664d773226f22ad5cb96a085e2f48189df59ff2dae3f78d06885def92d93b48a29b05fc94c3e7f4d5742339a75cc969d16ff264ac97b234a79c73ce529e724e08f8d329e536839473fa90ded403dabe65a574f90e8587912dd2feaeb2ef2bf9d6f39ed3f77bda899ca808a73ced3df9728e1168db01314420da97467a4f57adb6134d2a6df6a1fbd3f2213d61a430521829ad55d32895744a29b3af1e8bb4aef29354c9bba6e45d49796b5ade9ace7293fa534e0bdbe45f73ce3934e77c6f32e7bce1d2ba165b576af9f25f0a5bf2ef0ef3af268f655ad7aa809868f996122dff22d1f2ad0e316a39dffc19e4e3bc4d7236792c183f6badcd168ddc3ba4ab9a0c35792c128527431aeb7a04127dd22ee888943699a04be11f209f3ea5b73ad0adf2abbc5ffa0b5fdafbf75277c29dd880a20f2c57030bda310ada0972a2a01d1f30153f1fed404fcf8903be35dd693add1369b7b41c4eda5e7c45fedc2230839fe40721e1a311a05dd01150347487efd1e92ec3cdfbfa99cd6ccecc333afdf26cb768dfe2c3e9fe73bb79471cd66bdae17c98840792f9febeffdc0cf37677870ee44e78d07e40effd23f27edc060942f914084dc62ab56f52cf2d05eda69e5b0d0db7ad8375f1fd0921f4b716a53fd0e9dd6edfa91bd1fe8f927e52e86f3de88e6d1edef614629f706ed8bf0b57e46318f61b3b618528349afad95bbfafb71eb6326b6dfbb2305a838e60a22d6dbd53cbb2de3a6185282c6bbdf5b0cd97ac97d496d44e7554f48cea0cf3adbd03b653cfb7b6f9efdde6c87bd460757868a305aec897bff5092b30a1ed57a4e7ca4ff349db1bba64c0dafa1668e386afcc2bbb134fc6ca9b07fddca7cd34755f09bce8e34eb8914a9d50a5d0f33c88b5b6d6211d5f713b3d448c04257161680985010d0bb3b0c90295454e16a92c786644915810c1c20816415824c16289b376c69472069d9186944d6c71f14091525acb03575c9df7f5b0d65a5beb8d8daf38544e8ac7e7a7013740d0674e77ec099f277eb42f64513d828a25889000022f72e0c26c810b54522c3181123a6ef3852caa95d239e7a4b50c53b5144e5883e8f88adb6940909e69aaf6d2306ea5a5dfc289228490220b1ef424e1c481606288a808233200c28e0b8a0c1600e10914641c0142044324b1928012ec0041074a34218490284ec44d514c121d70432b691ee08afa03129dd6fc818c2dba3092b34385874005622c81448b2f8cb0c4075870e4c2e3072f7e50c52a8b204c00860f212b315c9220266a5084297484bcc00149592b4c1df98af37183a3b3d333c48ed1524e2aa59c953a08e8113968a102241823081414398104910b528b274582080209327c503424a5063e2ed4e027cad0040d846042044cbe38c27fe001126a0f114e8002e268c5044f14d7802790d86aa794d2d229a54c91506c51827a908a8288118cfc03621c618818633ce9e288063f704177500138014918614851061857c0c07ba4522b1a00418f0b50c0c5101524896122c6114c70850888accae800136208a102e7092a7cd0b037889b207082d0096227889e20880461a45a7ae315621d31c2d00b221254b5b60689e0a789124828f1418e1094908428ab11c412cc58800f74f0832b88a2e881112ae27c92f215c7e3d380273f1057fae45329a2a559a7141b0f46483d71c4882f58e063094d6a2060075186948105501323dc27dfbd53d844296fa0c447153655a0aac8a92255054f153eb3458a2950960834931259ed8f67854a1139be4492259650fa522ca105adb4be12d7880f5561a22a9d12ca902a542b162c914ae26805470882f2915346144f68d42526080f9c18a10847e420044af8b1b27d0005516ef083238860f1a02cd1e309310d80aaa41fae39a736dd0a4d92008c52d59eb53b48c115577fd02328cd9d08a206106124b5497518190265c51838c801102e4c2b80500208274bf801072e4c3298a4f4389994d21b271301f4e40cb105ea8a281f48428a8a13760045116284b1832b920845d1dc2f28807cc5b9000592aaa196549944738737307283a0a95529a594b5d2bfe22de18101040612309480c1040c15129794d7c88d9e73523ca325a99268bbc0527050bdf0c20b2fbcf0c20b1963319d380a8a8d204d232c3ae7bc778a1e3a05113a85114aaf1454d268c948e7153db85617afa8ba5bcb244965b2442b9594c0015c4122220029203a789205114f7a3003282b2862858c1b948162a28a16149971defb05115f71466a90e6c217494c0fe9634161183b338c21d0c94b9d400b58848422181581848f12434082912310f08811850c2456442f9024d145132e48010f96b0d97912c613328c23970684180a920221867070c50c5ba3b548e08aab2b0b939a984da0a68c42e839a7a9a55a1bc64d0d03a786a1639a940bd713c50b1b7011c58aa1315031f86203518a9c10095102052c78473069c011aaa9554b757eaa01037e8a36f829be352576204f267b9a36bf66b1b65c6f792889b18ddfa478e8f890a3fdddc66f304e0582e330164e8ec9f44afa39101b84d0e2c76ad2ee1d7eb96d0dfacdcd741dbdddde39ddc66f1ce7e28aba53fa5f0f9736b661ba0ec46faead46b771d16431dcd2a2650bbbf762f72dec56bb4d6cab6f1d4835d1fa37fcd24ba57ca81c8e2ebd6f1dc863b997351fcf433d9cb7a3e55f4f07ed405aa08d18e04afc9696a8758d49d6100391967f6305a2996a4c35a6ea3640507402792cd2155d0f05222a3482f2881ed12b7298774b3e575eefa1bd08aef8cb9aa77a45a521a5212aa9d210a812a2a405554995784a3eb93464523a5552a521a59e9216943eaa4329a574d2d20fa5d69778ac924f2d0d9973d27969f61b06c91d3fc9d736c9e32b72ce120f2df558943e233f2522da5f2555e2a9251f4a21b568c9c761e0bf59f221a26956d951f151d951e1711b5a69c8637999e6a440db514b4c1f918e0e43e933bad5dd4b3f7a530b2d4b2555e229f950abd4739f62d62ef568fa567e4f2dcddf4acbf7a7960ded567f8c967820d65beea8a4e2ca5ed6f52760ef9f2ffad6c394524a292df57869c893d98f3e95a657c547d39ba5941788a8aa56aa1de8e3d290c742e98653502e5011910eba3444ee0469f9a52197feae2a3dffa9a8f4fcadb2a3dfc74d7be8f95bee68f9daa6c244a534e4c9d0bc79d0a521a5214fa5e153e96b9f4ad79f34f6e6adb2a35552a521a59ed2109a3a61886ad472d2ee4d375e62eebd4f654422eb952d2df7adccf561cbe9df6462211e894772c42360a8f454b96c34b2ee7344204a29a51f9fa694d2087304d168042b94d8f63e46cb34d2c7ed31f1d3c4e1a55c0bf3f1f6727494f86942ef814225f4bdaf4860bcf1c89349793a83fe49d74d5700e83a6346aeb5d64a53288d5bb401aa40818268a574bb11c2b78487121f92e8f8392210fd8b44471a9f8e8f462449f47b1bd2efe7c32dc18de18a10f3fe029106a7801b5f7e4b50a0613d37a9e12635dd5ef43635dd6674dcb60ef7c618a312bda1f55a816244a2778d415e8a411afe690300695b5dffd5af6121d3c4a7ef84f07c30598292319014a18bedb6d0353eac1f6bfc18e33f0a811e0bad7fc3df93b62c26da770e66ab402f957f170e78bf421b36c095f9b59477d534cf30ab90b70a7aab64f0f7b351f94c633f7b5ba27f590c8b412a1bf6d7c9ba75ebb6f4987d0c2bbdc54a8fe578e4b1607fc34b76c3720a49cb3029b7af76cbe98705db84ab34fd14f781a64fb242d3d7a8d0f4b31a68fa98ca9695b678e4b1642588d2f4734ccbeaa50b780343b084318aa0e99774b001aafc5621659bf47643708b9a851f4de9ddfcc6b540df8236bc046e85fe74225b851768fa2979bf119a3e296fd30a34751468fa59de55c7234f06cb9b071d8f3c167ac5235ad4954664327247faf96041d7b0c0c2ae61415b0f33ce34f62bd4d6530b5a1b8fc818646b3c3257bcb825139eefcefb50f742ed6aef877b3f1e6ef18cb450291f8eea72fe7467a1b4c9184f3b7cda596badf5aac1b1d6875fd3e191e3ef9ecd43e1fa7e64f8444f20f5c47a6adf4afa42ef61fa748be7acff6cea43f9acf6dd5cef58a6915f7f6251be5d83e5accfe6b1c8778fdd5bef07c6186b8cd6596b75fad11da47e3f9e8c943a89036e50c7648608c82ab703cab6185f916fb797fa7ebc1fef47b6686a70a5f4c7bba153e58a1a64aac142cb95524214ccb153da2076834160101804090fa5653008a22acc8141ee8541200e0cf27bb16743c3568375efbc3085ef85a819175f2b077932347e9a7e9b3063524a4b739af20ed7a54c03ff3efd6d422ebdf530a5f3d2b8334a79fb2ac73bc1eb1d24bd832a1075218e65e19f7fefdb49595047c37795f51065c19c7bf1bd30c761f0bffb7e431d67a1af6edee13e649ff5b2057c04a1ed08388c051c666a6b410861aa3a44c19cebf592c19a10476ffffbd66fece985a3ef3f52dee1da6279874b57e15fbfb2cd104e8882dbadb43c9daeacdd7b350d7beddedff65e597b4bdbe6671b85383a6aabbe0783589665d94c73b77dcbd67aefc5fe38c320f2b2b8629795fa084aeac7876787fb190cf258ee65f154c11e4740d77c8b61908b4fd0468e538cfa04286ee4a7f93307132d1f0679321706792cd3a296854128c481411eaaf4046064a909acd543fbcb2089c46198d0a411694402d55a6772ec4c6a4667562981e08abf495e99e95ff85baeb4bf04924040fe33426684cc08b17a106126098dd4e5aa999c9994c97499cc0881efee743342667492e8fb7ebd50b3ab55d368e8541a75a38143a3de3b9373677826bc772667de0a67843c99faf3de79efbd77de7bef9df7ce7b2ffc7bb798fdbe5034233f2df115f9b52e5932ef9dd1a915fefc7b5f0f0f1e1f766672ea4c4a7bdef7c2ebd767527326b5a3dffb7b30f49ffbcb34724aa06dcd474b8d88f623f59546b4e6e330f7ca20addd3ac3a3f7b5905257cde4cca4f4be345e6774f4867fb1191d7dbfbeaf17f3bf97c97de81fe7fd991cdf1a142d5d5581b4fccb04da9029b8221f5e26f732b94ce6c451359529684343c115f955a624e69ef745631fc398cce85cd6efdac3ce0891f9666553012788e3277a6d8803c333bd09bfeb8caf96844d53762bf7f5702bd3ad906a701ae99df6251552969f5233295f59fea86ba621e54afa884958a6217d25651afa525799492418643e128944224588e36c7c4925c5c60db8029ffe7c54dbe130880aa2543008c4214d9ce82a1804e2c020245274dac68c87ffd0c68c5c035c814fe35fdeda9b901d5c7934b2832bf355724ef9d7c357604d21fdcdbb86ae81e2c05efb6c7bda55eb60783d6a4dc95b33c11a10d792968296d6cbcf5edbb07f3da00661f61976bdede5fdb315e2c02032461e295e8a810564f352364fc726c706c706657373e3b2e6edfe62aca9b5f4f1ddf83a77e58f1fcefa8f77f3e3e5d0a3478f1f742ca5e03cc6081fd67b47e400d632d3bc1cf3fb093e8e9b1f373f6e7edcfcb8f9617353c357a00c466e9e111bd7c6d756e387cd8f9b1f3736914e98d276bd8ea520c8081f413b382d2f11bf4b8bcb8b4b8bcb8b8bbc6f5122349af952efa82111441a00da050d7185b61c6b2687a1fb45d38729380c7d7a617fb78ae3c281b383a1d28cfd0dbfb6a7affa0ebbf5eff5371cbba8bdb8bc85c18df1c67befc5ef3b5cc719e38d7f217e79717181dec3f52d9703d2ae0bfb162ce6d2b4df31fafafd7242acd1e5c9501717cd237bf2c80549f40ada05b920a5adec72e9d2dd2556e11516c24258d36cb04adb56f69ef5d8630c0f697848bbcb7fd7d37e439b6bcb5ec6de7a7ff8b2773d93e933938685b090f6ec512af0ca6db00a17b90d561539d1105eb90d56e121bc5a218a99cdcf2889464aaf9477ad44d2e897b257c9f2c6728a956bdc11167a3235d394b0e6b5074914bd17ddcb4c4295c44f123cda45bb201ea3992d8f1e668b7540b6e5c9d8e7f3b9cf39ed7c1b63ed7eb1d6526bbdc57b8747ff3a5d4a781a8157d46db08ae222bcc22b0d6badfbfa9ad5ba6bdcd18e76d27b4aadd9b3d14e5f6573beb22776a4ebe32bd7b8234dfd06d6986a4c47ee48bf1b0de3c38785b01016f2d4f48267c563445b597c711ed08ea747aba05d108f0f6da40fa05d1012402e4f1e3dc4dce21ed473a3d16f735acef8625fb4cfe9e6572bef3863cd2e2e2e2e8f8587f5e2143a700816412227ab1abc72e244073a89cf47cbe0d08c8989297aa877e1f0bf1042279028ea401ab4a4f2ce61525c5567609709a627bd6f3ddc404d799f4cc8fbb1ef785b0e7eba104208054308872a2c827ea19327837f9a00e184d08487264c08a163e8e4061b6ae01024bad7217c473ca0f860a47de84208a11739ccfd09a1115ebd9ff1aef7724d0e4368dbad5c11066ca8a9975238048be8854418126958bf660cb15561ad313174ce091f0ec52b7f46de95e6ad95f236997eff8994f749f3ac5996452b2572e22b9b412794422716eca194564bf6489d4aedc0f3e363599f41278fc5a23f5656f500e9408489120a6dd49897e384a6f484381e039d40273a3938c692b8d66aad13225b9d3c96e943002051be92c929e0e6a021d618e5eb7832f3ed67bcb93b75e8c8a1e3b140183c9d4cb3ded3b5d6fa37c45a5f474dad3a94d6fc354e5666c1b5e0ac8d31c618638c315a1badba95aa7dd612d5f856ad9b9577c481bfbedd72d0f064ead3231e507ca09f83862743f3b6d56e38406df37e4feb7832b5861b1c063ec705ad3a1ecbcbd5d21654acab05bd871cecd3f0649e86a2777d8aad8d4f832bb256d4f164fc2b662991ae6fdfe64ddf66cc560558f46b7c6adf46a98d59070d31c68a7d96ddafbf53bee65a4999e63ee96b4adeef6112062d5b6b7544d9465fd3b12ffe6dbdcddba9acb538db74c41a9c022217babe8e9a5760e2e9d0a1e3c948a99bb464e0c4c20ed996bda6e3b1643a8890e5180db4bce8d081550cb816e85fdbebd0612da8c311e99831c4eec8c111e99a1baab5d6dee06cddd1b7c6a96ea8ceda6aed3b8185d55a5b6bb5d55a5b1d8410bae75e6db5d5566babb5d65ae79cb3d557e0ce3df76a6db5b65a5badadd65a6bad73b5d65aab852b70e7aaadb6da6a6db5d65aeb9c73ae5aebd5b9679d73ce556b9d73d53eeba2d76a6d0422f25aadb5cf3a596bf45aadf5671dac7543afd55aeb9c73ae566b2df47942f5e847138aac3f9d92f9c4ee1aa7e4c97c328be693978244288fab74ac6e164d2317843f92f2cd59349f4c2894e7ba3f0dd05ad9b5702dc47f47d8b1771f6e631939f571dd8da42527140945cba75aa699ef4f69369ff490d227146a59f7e5bb3f1b1e3e66d134b28edcaf539f4f9e8cff7b3f8f1ce6ee79a4e56fda43cb774aa49cf365b751c1b675d0f3b5d2b4a80f49e92c9a46731ecd6ce20945fb0e347d0938191a22e014603dcd7b3f3d5fba0c3ee794d3e8b1cca7efe7e634e63d7c8b398b687832783a2da4add2357e32afe7b1c489afcb29d138efabce8bce39e78432dfce2733cff9a44215d4026e0185a0e557d47d3b9fe0e8137be20b620cb47c0c55f349cf9379478f25fef7ece733e58d7e474f0696c0b1c88f26d052ef77a46312bd1d48cbf8f3c99399f3c97ce24e89a6714a28adb3683e99502a9d4f1e8b74d50944d80c3a584df5d9691da75a6b3201a93159939bbcc6813810b781b9a548474b81a8bd918ee340e4fb7529e5f5e9b0e5fabad5e01c435dc897638c40af078f13aed0e2df80f1a4e3c718438c10628055fb137548a643a2754c949818fd7a2cb15826b12bf7423a444face7e400d6310596abb16f9104923e44903e1288e2240f8a1a40217da4cf2dc2c29afeb53e76989b3179492963b494d99a5bdc9ff1a7162def16bdb5ed94c4a4a8e019e4c32c9d7416b5a8251f90777d1bffc5bf0163757a6653c71c5199cba2199f5631444e56e394efbd27df290e4e86a78ff3c639d076852b148595f67e97b43b6df9f182407cfaf182000f5abea49159a3d70b0c9051cb7f41460982b945c7d09a78ce7776cb846eb8a65933d7e0d7035ecf1b905a3a231540196594a1e7037ce60e48edfcd4e26406aa084aaf920bc4c505ba4a5af465a2bee3420a115ca00b34033502f74181082dbebf140a08a81405605c30210b203a3f28948e3e474e75d231c6eaa718a5121c0335ad9aa06b772802204993199ce24947a77ac2426147cb9a603f411b1f7da9b4f75bf3f952bf4bce2905174535cf46c7d7ee8a198507f8c7f78f9903cf46d3681e9f0bf514692b730eb3eb8ede3538aa62cdc1f6f82946b8c9879be5792cf1bda664b0431e4ba4cf979012fa5bef7a414aebda50709a87bef762378b5636a1489796bcdebade8a1bb6ae0dc7f535071a5f2860f431fa5746217bec51c832044e1acbf8af777ec2eebaae8cf38e3cb4dcecc5b1c629d136cf275502ae8508b816e0d7404166089c74cc31f8e9f1932a70d4dbeae6615dfe56bd60d8ee862d6cc3e174ddf0bb6b02313e96b18f8fe57dffbdc4320a31388d3d0ea7b18cfdc47e3e96ebfcf8aecde2a14dbae658250feb390f2c57c37a382f472707bcf2d39b32c6086de0871e6fae3f7005aec4bc797caf87889f9e8f9fa095b7e907fe8f864f65a0b9868530169281e6ef62f5d9c4cbe1c54f4fbecc3963bccee7c2826fef2708bf64727907783d64f9a09c1546596bc0e863c1f874709ef6d8c3701186596b2d5de98d3db5f335fc1ba321bd765956093e5b9fad1617a564997d29ef4b59cadb948a7ffe23d1ccfe0916fff6145c812a29a4a7597eef2995bc6b4ade9a26f458ae9f246a844673392d42a3b91e2963a167e43014c3e6cfec494fcb3bcb9e53df51704efbf06f38c0577fdeea3677c3f5851af1828562709897f1909fde9bb9e130b1a41da16d93f66611dad333672c54c3611e85375ee771980dbdc753be83851ecbc3d76d66acf3adcdd9649a0166253095d9c0fac418466368965195a799f6f2fb45d37959776b1af6900386e5ed80ee0c0cdb5167dab7681936a354fa1bfe3b466b9fe5fda2b57cb74a5649f959d25e65737eca30d2d3b7ee4577349a8b12a1d15c49aecfe130f45eccc2daa7644f37ecf258b2bfbe0787c9728c9feeb530dcf258b075dd7cff59f7a28286f4b074477bdab9c4586b29129408976b6d90115a0419b1c4e5b1bc161bb5dd33bb441e2a0f7f61c1181c7063c64f30c64f30bfb87ff867de2f1a5a4fe7fbf49f4f77788e79c7f8515b99c6e51db1b4f9c597daca3b5c5bd19ab2e565c2e8e25e669452c639217c971738658c10c609adc71a6fd67c2be3bfe113bbf578b3e60bd756da438ef0618c10de67d980b4a93c6953f9940daf56ab9550b612cab22c7b2b0d67e5386ef54e8bbd030ae550e5105a659f436895e550e5105a398e5096e5c884541ec264ef4928d1081639916ac861e25b65bfdfea7a6c67333e4cd9b56eede33beb567a92f00ee848ee3af72e6fa745dc36462369aebc5f0dfd27141f9318c6647e4258de6ff5841e8d2f690dc5c195f4ae797a660e9434cd1ad4295fa10d9587af5180f42a156ba5b7219152a7cae66aca864b556523fd4ca9b82437530abf5aad562b5214964bf9942ce5a7cb4a30ee18ada252e4fdbca118ad25be22448afc0c918afc908814f9192a422291e2c318659288040639d033f288bc9f6babb8ebfdc49977f6a4fad8e662f47c3fe3e5dda2e77c9ff2788b7ae39f5f4a216d2b30a1659e531e53791923659a8c94f22979b768eab257e4c96899267bd2cb8c945f119cb357e4b1d0f7f38ac42bceebdf86830f383c8db1183512a594faa0e9c461cb32193b10358d7c6bff86f7d8f3a1760ea391522e18b2d7de6139dbdcb555bc590f89d2bf0d099b77a1441de96fb8a5914a5bf515f9d43eb63d1c367bd286b90f68ec336dcbbeb4f978c185ff06aca614769c3f7f8afc142945f1de7b4fca1c97c4f17a414a08af17e47bcf860b6752befb9eb4d8f56eadb5b656ab56ecfa5d538afe7ba5efd7892fec3d567fbfaf36d3bcbffebeebde57a4e91372ef0136e4803652f243a50c61da9ac161247e09efe6668dc9423d83c3580fbf3e1476bd85af4de60a837ceb3eb51698e1d62c6a507914a140232f5a11f929ae6cdee15a1b4af95db76652f9f9a55afafa33ead3d89c5ba9f3676ca54d65736e65874cd99c5b9939ee1d92c648b5a7dac7b75ab4967d340e3591524ae9434d9a0ce55857f326dec48772a4f4a11c6f2265fc1c30d4327c4b730a701abe1cfb72e00a7c7bb10661b86f35e9a4b3f47380d63b689f9ab2bc350db3f65d0e103a28b5666832d5592b7de72acd9a6b0162928610fa9037794099a76cf15253f2c41d98721d98e301a41ca8246dd915b554342400200023160000300c0c868382c1581c8929cf0f14000f69aa48624a984844e228c961144521438c21c01040083186185246431400f0874ac4ac3d31fa2432aa44f7e28989b6add407d155eb1158c2a3aa7c978d4a4de709e9d03d7d53b8ea17aa024a5fda815d4b93089f19a9aa10fd83b7c90272df9b15dc7da961f418fa3f86d7f97eeae3de94718e3f61cc1e9b06a2c412f78fd341f0fca961899c42ced075fe623e8fae915388c16c87b759619217864a4e1f42144502687aeeaa057ceda9c54dae1b2b50b48ad0757a40812b48d590fb6a2fcac4b490b47df4ca6b2235b0af56aa7d35beb487c84f102b7097cc2a5e3d540d341f7c35756feb6ea3b1af0f970f479abd4477af56227320c22a5d1e9d0595888aa942362bd16767e6125b024b1868612ed400679746540e5769425d6d7c875ba3bb6042a26bb686df6b441b4ec6129e6ae49b4e848111704ef0c792a45cc2667eed74354c7d94d9607d66f0d87f9a83964d7900ee6d4e06e0a6fa3c3a0859f0643f0f2e5419169968800954427ecaebc39af4785226e2f5f2198fd88d631e2eef6fd478c4f285be266948da071e73f83910bd8e9d41d0873ed9cd86de70c4a0fde34c57604069ffd9e88426e6cbba5f480efe90dde356cbff71e0e862d08da2980e2650bbd0bc561f4aeac4475f8a44de4dbdc06a429ad51fd44ed3b9e5438768a752d4e777f768ed1f3181acd86d1d72ba07cd701a9073fb7b160d74652168ae305031038c3f89dfa42570b0bdd673c6a9b45018fe1dde9ddc2f0d4be07ea17020542bc292388d26c3e36acf8529e5471dc352e37d11e7c34115350920d15481596aa2a1496acc6189a9a5f6e4a2a24655123ba8342ca4c468bdbf93cdac99af67bf7e6864253dfba93e14bdbbbdc106d7b325bfaadbc30d0eea302edf02a0d052483673b76c73db86bd47568b110dd7931cf19792ea39cce8a8a40d0f334c7bca97bece081387622f4c16972b3cbe191be9b3ac07e4d838d70b3fa239a6a2ba1d50987d48813e4dd50f9bf7402a41851b36766a99317aa4ca4a6fff9af05bba52a44b09ed5dff005a76272aa55ed42cc13c6b0815d152866268aedad53163e14ab24aef002e8b80d45a0d89ff87fcad0a9d0688ec69953b387067eb73da759ee37227ad8aa3863b2b6dff9f53c75caab62a93e852bc7b557ea0982a8fff028d22a357081507bf0c58595ec6ac0c28dd53066fc917191955bc51b2fd0e224f172ce011d6eb978c296fce1733ff7f2986ac9014b5faf8d6c6e8108422e9b47ad09fa9f371da9f00035abbd6afe49a58be16c1666716130b787fff802524c4b843a1571659cac06b8031a9041802d3ddcb392d6b347114b750df52d6597e05becd7d0fe4241a4ee4d9e95aa10ebaeb33d392d39310b8bcd842ff0cef2d455427bd0672ca523dd9fa984e3699b39538c8904afc1c84e239d2713f4512399001046610373aec0633470cd9e8bcc19c64fb58e262133475c3684e30dd6b2c8adeb35a2f299fa665601f735fb7a8d646ab19c0515c2b25d00266ab0034f3b1c917d46c0098b57a260ac4348698d24f7a1aa03641c0b844fc23ce15e809c61d4af6960b5d7c84ddb6b19a6765a54021626685aa1b40c5e9996ccedd4b7009ed6241b4231cf2318fcc192d686059fa40b71321c821a7003ba7f9e960cfff694b100fa3d9854f9d62ef8cf661d581c0849e70f8dc991751a7ba404d057f844129d0bce78569da906f4dfcdbefaf67797c08e5ff3e7a7558b6a444269563497347a7898c9c7f0d756245b54d65239eec0ebb643b46adfbc904d9b53d842da38cafdae36a83ddf684439c8025c342be602bee5d14766f821250d641d7a9e06f81c8e8f3243f53e2ab5c5b42d2dc86bc608c5c4256ab0149b0c9f092cb7a8f587afb8e6c05d9a95e53881dad59bddf86dc59e48b1e11e371465a070d8ecd7ad2f5406867d1b1666c43e677f5e97bce7b0c7154afb22955eed301635f2e48725c20a16f334b592763796ec734f93af7d1613741122fba3ce5eb1e9505b5e00a5ee3ae6579b74b3b870c7c68d80536eeee9912e54ae834ed29f1090e6f898b29d7b1a8ccf08f3c4f9ee19aad96e800cb3661ede1ed0d163ccfae5dbee54f79075b737638b98bab7eeb7ae49e84fd1de29d10e337cbba87d3dfe39c83e6a8da51b17c22c2552bd162742d84e425c53461521237f3829db5cf63442daaa48442041ca6bbba65cc6f6750a5309e738fe6c8cac859d00e3c22236c5883335c617113aa73b86445b3c88a41dc754bd8ccdf9afb371ac501fa6ffeef23555162bc2084e03814cc4029003425443c0a2d6054b45a8b209b07a39bddde6edc791e5fa32d545da5d24d1ac4a3809816a7139081eb67e3ec8882e8ba563ad3380fe0507b0b93fa1c19472d5e008d2eb7cd292b5b2fac9e9088cb5ebcf4403bad87d236adbc30e539107fba127422b89da2878604f59e6864548552c10b34930ba1c2f91fb29d2d8b94dbaf1583ea93e5310614752d99711063e702b93661cf29742aab4ec3813ea465f16cf410f8ba5f43d6a41b1107fe996c7e7a87587a4e3c39a22cb87e035a73dafe28d8f72f0609684581f78cf81bb1f0cb722c0cc9f31502209b57a88172a1a653cf29f86f48f59ee9b236ed56112e933e69da4674f2e50840b836558eb837bb1d4c243d99dc05039e75c359a96dea58bce0a1859bc6435d193dcdbb72cca231b7e99001796499120725c634f78662d2e5877a0d16eba8124068a2c3688b145c49257d8c05c17d9f18c856ba6395a98ff3566eb071c407700543e80487424a854627bae3379de5c2db1a67caf2ece51a78b671c277fd3eb28dc5a240ebddab2cc055fa112824844a88341317880c9f568977fe8a4d3b5e20ac8bc85885c2005c558ca9607d303c9cbf04e984ab058e9afb15b58bcb81444a126875cd7e3da2c94393d94b3c1ee840fb87b15ee5521fd285f984fd5445e71a34580e41cf557a497cfd0f3d404760a170b8824644a71cf62b2dae804dcf455fe1fe82b06e4055d3401f253460f06f4742be1dd2859bc32f42fcc5b1297fb5e4a80fdaa279bd0d384a7a42ae06f04232386afbd5eec578cfe3da46e3a18e43b12e3b519b722853f8556826d57fe046dbca51f629eba11543dbccb8a9d3496a4113a3a2e9e0351697075c6b38f855c38b2f1235684a55194d76501a50326b22fb5b78e87a684aed2fc6132ab536358da8d49cb4b8a022b8959fbe84320d750e822b8d8af8f605cc9554dc68e4ac4f936e7382420be01de876a78445ce3f49cdb0ed9289a10dc25e8b980cfbb4298f260da09d5a40d261ae6a2c217dd4825122c28837a963af04173eacc5a9cbeb1650d26dd598a9144b6c86d23fd81d9cffc33a2377532d90595a487eb3124e2de00e9085d890c8d9e07db00df399e1d0be05abe4c1112229d04e6c07ce296d0a65609d1a1a732165c3cb9a75ba4c70029c6ad31014007825eaf209278b0aa0a538ead8e4e3b70ded0d3499aef2891e80ceada8c66eca44feea897f9344799d169887dd65a550aef501b2078bfc151b8af43b36079e6f5de76709e5001044839670968fdcd50a49be247a06a5fcf839b87b4f67594d669293109cc3f5f01612e6618cdf00baa4854bf3404eee896e09d889772d81aa35a749dff0a92d218aec78699ad4b896509a3793c041ce27ad3c54661b22cc8533d58456668be60012b4ec7b2414f703613c1b51ec6fdbf548f6ff1fa842295a73c0d3820c54282d1ec74451fb48ec3e60090db4d43aae9cfa64e17c406c8606385546e74eb685148c429c27b49de89b7d350321350d948b28671e64cb0491cd84c18b9f2d13d89818242021d4f875bc0963b83a4a298919e9adffb18b0f9f898f3f17b1229f9f7fe81fdb7aab1cf9edb3779476200af0406e08c58e3c4a7132792d2142d7130afb4d9749df900321b5f8ea6ffdd2b83d3711709be8510ae277f27c4bc27bf628c59b777940c557663e4af5e9a3b414f4386697bfad431c9afb515a1a6beb7349c8be135b95df02eb1d84486d190b78091d1761494ea7715d292d88369ed37137c252da673db279d979641bc1a1290c074b631ca357e5b017334ee7e8ff64d7fcc85242310cbf2542b7ee71ee9074ca422cacf11e134196a4648c2191f2b07894487c5bf37f87dcd4f9aa9b38be54bf71cf224efb52910dc199e5251d53631bebba16d2bc1a40cee2f1c2e2a32e74e98c65490ff74515120c9baf4f1e203e788d5f4a99e52329d19ab99b598a0e1849a1c3e9418e29caac3ed57c2a7b6a49fd538cb187c02f1fe120eefcab80af0c1c3995143a1d4c8adff2cd04800048c0a2c9c9424140c68f94157634060410701510ed25614c2b3e4808c875550efe1af5f7115805402e40d4ab54b46185a7d0160dbb350dc59592589105e916cb11998038ca84363d16f4beb494b93dd1110a5198aa079d2bcd67fad4df3ca70bdf5e2981cb902bcd638afe0872a49deddb2bfd4f2ca292a1d4ef882f509669c8da917eaabaea35bc5e75e2c2207284251b3a3062a804188d739a1b31262d69cc99214f7a2f50aa5a15a81df90ac98645e9aa42ce9b096e95c081d5b46c04893b7e111c874b81a55ee52bc27b85f8cc820c16de04afd331cd08e9efe2c8be1ea70907f259637e17d2482f2c2c1a6380c991d4d367e81a54f09c39cf00d8bf8196a0e434290272cc24744727c32d7d9cd694159a7ebfae647a14b4239f108cfd94c3227e8814628d7728694af763449610830e6dd770f9e247738aae1546a943136359d0665f24765fc7e37793a0e0ee2815f3d9820999c21c0a3955aa568f91e8289b04c7de40d08d069108eddb6985278e62ec2dc3eb1dc03c0d267a6b6f9504b1c2bff66a6e7d4a85e0091d84a04dabf448b1abd01aad529918a2344e33fdaeb44856c881fc4137f1454010e017f743e8ba42a88e8730f8ba9a4bc2939e94bc82ec047b632038902069872024d8d4b913c3763e86898ed0edb26dbb4ded177630cddf90686d9955e8ac009e51c55f90d0b94f6a85077294f5099d672e1ca5e9e0ed81f431540b7436800c09ab02d27732c2e52d46119b1949c9ddf4c9cca56d87895501c9d50f890d64f255df0e91a880e9dd592f463afccae222a35d4defcf50e863473c03c28eda7ad746b1fc98a2cf61863e491d812cc1707a4e06f2b40952c9ca93be2f0869382779faee843aab7628201c8f71d3aa1abc0166a4a846d0f0d23d03e239ad77fa4bd5cad6d10b31e84a0f146f890a8f931745669664e8a55ce3caa7e61a47701d95bef331e0657c7831251a117175e650f7ef1f572fe4c2a509bfba3015a7205cdb9532225bd43350ab3c1b5995e3dcd0c24764dad9b9019af96db03e7bd0486bac6b310a3d3b454803cad070a65796a5606ed0c38522188df6b8297057145cfc91e1a48d7cebbcef5aa800f720354569837f14ac042e54c0037bf381f1a2ad3447ed6c95ee2a24502f3a202d391a842c3b149b556fb981861d1fa322908bd256d4a91c69770823c77b59150d3157718a38454527c7deb923723643751ff2c5854148a48ac6de7eeedc2a36d450e890c4f9658a07657e9ae1e758d801e37eee84fc7199a080760bf0fcf7b1612754253ac76450f7f9403621042633a10d9d48f291419bc8a11f1b30a477e6b3713896a10ef08af1893273d79510e18e3c3b02b33a014a908e16eecd0a26b8323c834d206e57faeb6a419a0ac770cdbf3afc2dc1e9318c149d0b3e7b4d3b661df230bff5952dd35496905999fee6ec25b6dd056da8c31b9b6ab323381c6427c238b038bb9240e78fa1f08ebbbd5f1d1679a3f20f0437b654586972f15dbd17df02967dee07dc93a04bdd6e3c126044d88db8974d9d5d06a1f22e0b1402c438cb238ac97664af4b6d4597ccdd2c03b3704fd6ab97486a07f0c63600ed8f38a80d0d593634760d0ce4634a57ca1e9a816fa842c1009e3574cf73cb3727a3480c2ce0aa2f4385cfe2c30a4605056874708df12620b1fe2f53b3aaa5c12d9cbaa03654882d165dbafb651c3af30175f1c52170f37963f6d09710b9a22c0ed9aa47b03a49c947ea9aa52b481841f754e09bcd90f9f9ef7db0384430d6f9ee8d09dfa78d4695b0380498f47885b261c6155ba7f30547b4a787015129f163e31dc917f10944f1ef0ad10ae157ee9690f1d9327413ead41d29c2f18f990d16020eb3fc55a3b103ed7e951b6ae44e1d46602b2aa57b735a1ca22960a10f6140845df53e174cf817e8b65da1bd655ca039fba0a8eb49caf6091a149ed108e8e59ed718d7530c25136196fc2e27bea8085bf9f830159a54209db5b4797c067a591b2a1ad531635a804a1f805901edf5ab80d3f2104440c0c2fa4faa86a5cdd888fc6bea1074ce1337e43ad406655fc4173978e2f510748a29765212297d3bf3a52b100b1b135265428e32fb1d35cba9ecc8673949b07d473e6837c7e161260e30045e19c53c7d05beae09830bb61f1be7c18c7dd05c960d05cf4de9b059edb15454c77227fc96004f24429ca15f39856c3e3cdc20b811a706548e077c7507fe7010600a8c0c8fb897ef920f69adeefaa1f40f1248d0fc01ef5d38da2247f94086039280abd988883cae1f910498ebb09ef670cf88609e04f529c225295e277bbf3470f2089b9c1f2b23609c37e1cfe4cf94d80a76ec10870bfdc072152866da79eea12ed2d4c61add66a41b7ef8ed789dabac6dca0e614586ef464e30b067980c0a7304a890bea800d60d46409513cd19ee3fca54b9d7e054cf8cfaa6555f20093211f94209fa59662d3cd419825c4234446c0f94af1dbcda84eac0be51a27418d6964dddb28492f9a5ba063c41ca475d8971f6ee449610a45de476eee404e683831fd9f61a186e49daefd2a14d6d7e92beb54fd861b97b5d5a91bd49f203291d0225780592176013b54641459aa3ec8e0602591ce15ab66108e45fc8a9c47403742957b7c1111df85d477da64c3e0ba9e22e096e300ff5e12b1301219b055c9f99e8b7acf3106faa13f40e5fdaa10124846c438eaf2ebaac3c8b08f236eee7e04529625268cadd0d8634f649ef2a80130ab8effef06abe364b472300b0a250dad203e840f25217ab026c059432fcabb35dd90f3ce9d0a8a38bd8d54358958f8f12a540c18f6fd9e6c048998f950750ac173685b38c1cfcfbedf0452376736445961436fb51eab4b5ef3b1a61517abeefc00e1606ac5a47c1ef8046b1a1f933d599e980648b64233beb513122021e1c7606750b496988967d20916b2085e9b2e74f9c087770d077ea12c83a4704267c2785b1ec0c1b6291aa2ef01a860949bfe943927e1b48d1a4fc60a891c00f633885fe20ec75518d21928f877a9d0fa4a2845a641730accb0c1fb2f3e5615adb6c8171f3b9f155817106948c90071585e330e04e2409b28fcace4e40ff1c5d01464aee3058fe981e9d3ca835190e3fb63420ce67698bb39599f28ebef01530080fe712bc2d3ad5e59243745d6884b894305a31448da2a5baf520ac91710d23ecb88fbd0df179509ee42031a5c1117a1eeff44801c6e4da9567766a58481afef769c3a543acba3a92d77a4d4b463ed86a729acb82e98dd159e9dfdaecd2f1015bbd7448da4875a244b64efb6a69b396414d0fb3ffa9ac470f36a95419b2a89767e7396662fd3ae81bdb66bfc0584fe170e6b1421cf2a8bfc5eb3049fbf6866fb3ec12b3331f89c8b71ca31d7030c8ebd0f89ada968bd366d9f7e713bc5a81f490102c3357b966b8a535be9e0e63ffb3a5b86b333ed0df21e1f794cf8f4adc9f2d6394d59585acc396b3faa51a60adf7066d3e95bf1a9423cbd578e597093e8f33e53c12decf50f5696aed60afafd633487d07d48573cf6555a7206ff33ea9f8b2bbbd0bb596d4229712c75ad66015d83485e7d0ea83681318a8e522de2a634a2a1b8d0c4a09a8014dd3b1722a371c9f990cb6375ec19cf84f5e5cecdad19e3b16b20f0d400a1a10d7f5d9a0e8bafa48252fa52000835980992ace5421a0f798681eb57ace4d7adcdfda6404324b39ff65ee68a084db536af1ad85988be8d3f83b3daa0d7fd31bbe1067b9cc1deac1f40cc6eba62431596f9e3b6ca92c28c062559540250aece549fadb498b9be2529d9e2bb8f47a1ff090acb83b15588001b675d5fc2997b60d57388e043cd5bb739591c9e5dd1385c60f1df5ee900f721843de40057d3d36aa448423f79a3fe0ebd5e570a3b8665729e2bb3c50fae6633ce4c67b1208dafee0bdbf96ede4194d6fa216d11929210ba32e47b9af367986d01308559bc868b136a30fa5d094452cbc5bee7c7f9135efabd7b5c1abfbd34661569b2a191c7935db5306e738a053ab6a8ca8ac14374410748ee0d05bf2d7ad8f7bd6e367ee59d9ed11e46a74d9a1e2c364ff0bbc30b9308df8f45e079de1ae66e2847e8785834056656525a499be3d9b90b4f93b4c2ad9cb6c7bb1cde6b737cad2a8802d228ac043eb083964008351c3be56010d3e533bb994b192eb856cd99b1ad568f7854fc56d30105f07fc9442a57520bb822a13050cfd2c37009153db729ba797dbd007c268781bb1921299c6380763b943761735c167d3504fd32407c92639cadf39617e3c68732739e876ab865027c308713d3fc9810a3147d5a0e4ccfab73186984e16a2fa6fd3915172b87500dccc7c8795e80557d11c240193cdb4af4070c63f3fdeb6501627c48354b73a52ad59c4d4aa98bcb0b809f07866be02cba976f40a5ff723a074697366be0f14c2a27275b3379e11c9f84efb03ab9e0aa7565000e79e97b0e18fdf0687562f92b92b0b122824a0aa9a529770dcbab01c899e6f5e0a586a317346ae91172d96caaa294c92a271a5e983612e0342f89717b81998c6c95a526ed2bc6f8a97a5fcf70d9aa4c495dd3980a9ac7a73f09bcbcdfdc08b0135ce5b2a1b756805896244353fd0515a4749f8ef11f24608ab2d88611c43de8fa6a4aadcf0123ea5c87bf14930950441984a8650d905319520d0c96b73a339d701e0976e32b2c523bc1342a672c5838b39a0642a0ba845ca5472b30666d70278f5cea122f1e29962a812cb355429c1a1ca21f3a14a46b708053ca24a4f892af3a486d6dd3448cd1c6213a744475deeb771eee00666989f924e09e5f038133ca284c5e6dd1ee75e5a8236c6cca06b29eb306d12f21eda261e8b43f171ee89e41aeb2974fdf09c227c5ce2e10bf495ee8f7e093633ea1098303315e6aa6b9f46e4543a671b528c2341491ae7be3d3e2c6107710db87a8d73872380c7a6a065a00b91fca10146ab341288a60221f0639eb19cf6f285d391ad26ef757f47603d53a4556c97a138ce5d01138e7b0e9f3b728ad3fdbfd9dcb3b6283c5c6ecce61eab7ac2f4f8b92f72a7a3a5d902bf82f79b23061cfb166a01052bbc83f5ac4129790160fb7dacb54fd56bee0f661bbe3f7cf698c943c7909932ec0337e5afb92f40f5896f581554cfdfff16b0b506fcdc0f077fd6fb8544cf43595d1af1423921bca73d789a28bb412f71a4e62eca1dfde42d2178a6f0c535c680aad41f798bc245a61a2fc8a146e1b6191afada2e170dc1a87816bbe14b095b1594f44c2a07d2d4394b246339e28826bfc0f8c44afb5a4ae59fcbca6ff95740db79a2be3a11d133a910f33fcc14f5ec4bad14235bba147fb07c92f54c92729d857b5948ec5d941fcac2370f87cf24c18b188589a4e2cf6400f25010f94c469daca36e30b84cbaa4c08357336993edac47ef6db813bbf6a9a27bb23114014d4a5e1dc5be8acd9e70c72f053312061a11539295c84cae468c4fff4993407957c1c5dd8c985e8844543245353b6c3e2f69d1a62d7858939da8163672cc669b99edf633864d0c151d74acafa81e635b8ba0a236b2e1507dc10b5d288a478b71d0ecd0e6af161baa5dab51fa953087a69bf12d044721c9a592743ecf760868095d6d0c66772649e58d7da0597aff3473e86690c9ae2a60609e3dd5a50e87787936795e01c9d626f97640e7b25fa23393c32283691ad1fdc9e42acab1e29949baa01091f117d1e9b3e974005789f50d029b6a763d6499e46ac6cbde32caeca301017c29aebc4db3a3ab69249d1730a1e5016253722e988e25995dbeb3e03348e46af007939e3397adedb0389125114093e4e94b56903c454d608f532a6cd4c127491aeabd0aa3317e74941a3dcb05686fcd9e995dcc4af283c94ad333d1e2a198a91409cb329eec1677d58582bbeddef0a05bdbbcf6327318c9ba9280920faec7d0aa4cd6fd0d34e6a52fdebeccdcd7be75341f09b8cb3ffc0503f2da6512efe15a527233200bbf4c4a16b2ccd27586f2941fd160622f2271bd7ba42d8b2949261d539205c488137b0559085350094a7c0be03d732396155863403efcaf60d0c259abbfa5bd846024f952a8d197fdb530771ee7a262f1df0f442dc756272ee5226a71886ea21daf092b3d8c79e0d7c24c2024656612dd64b8e11e68a573fae57629aff21245c5d1a2c86558b1a807f6667d8708b146616589af850434a3ba521b767bbf8a5f5522614835b575c5d2dc332323bd852e79dd3ac0b1d756f106eaa8a655b46740de0e0742b1d0e80d0e0da0abc04685827772d0e081afdfdfb084c3f10b6f30d42871720bad978f7206b13987a7e1c3bd6ed889afa0d4d2b76e7acba34829ea8325a407ee70971df61853d85155defb09567f92b7da7fd011e8090866ae78732e94684f3a2b5d329b242f1eb3e0bbc1aa6b74201cd92616efca66ec38f7213dcb180cf5421889a89a53c9eb7dcb581c9c9e1fecc3af6d8fc980708a4b9e31b15cc83b06d7ec015d4cca1e4f388536af3ca3f80242a8808222b6b1364c5351b9ffce3660a9e0a3140d7fb996af161a0c5f602482798679b95fc900291894b461bf79c3339cc47f1c2d85b3d3845758bd8bfc68587c3feb38e069f64a7eb9dfa873bee2af8a5cf433561076994315189694662c2210cdbc58c69bc1cec07a47c9c002f4d9f06b7c9f8392aa00708d7d3babe58181d0e94cfae898b7cadb579e20ad539028f05aec67b870e3ff045ebabdda153ce45ec913fb283ffc5e750b73afbc2fb53c84ee22ef152e3f53a724b3f2457dc4bf921a61135a4b927f6592aa9949be0e64537d2bc2fd72ebbf72f8f76a13024bedb17cee19b8aa521df7f3d0638f9b0c2de5603056504801d29e77a4170416e5ebe351483ed612a569c462c14396bb42350a90eef0cda488cde23d3aa1b0835d87c20a5814ea4191dd0292314ebeab5e8521fc57f730e734eabe34a08b1cb9a89db9426e5d30d478bf6fdd6507d8b82ccdfcd7a0a64dbb81dafcd203f7caab54803ca121395a5157d438b757a55a592123f8ca3c14a242d49a29111598c744f9c1e22f7127d52e593ff23bea4d9303fc39344966fd8f4436998d4c7c31eaf281bf3a1106a209cb1ca47a4e6969b57b6df677530455c1c605b85f191695d9e88b7047797f06424a707dfb6cb548a6f01565511d256ba5c4d2d9ba60dbbea493dbc1cb9f2f726a06a7fdedd5c9cb7f2532e63fba894fa99b79602906d6c11ffe250803e18fee43495eaafa3caead902acd3eacaf676aa8dcbaa0b40895ebd34960452c4f6b5fdaffb845b7ca3771d81fee1a3968d8beebe52bda22b2ed05799b80bdd7ab46a6400da383a8715f0fe8d006c84debd50631f89c680559bdeaaebb69e65ccb14b746a9dc1e770fd4a895ba02d1d22fbb45c36c1b18e7d4e6ed24b4c717cd76dad518c11f715a1e9b0163d60e5d5e71263c83dbb9bd416a5cd8dbdf34bb38927c1d5f4f9a35006f907242396a5701b1812435ca0a167070d414d46e52a364cf8e45ee2594932f2d8e12df7a4b6ecb4f4d6a54510d95c16efda5177e7fd88b76879caf59292f3a1fb12fc37f91e32b4bc568d86ab4d06ead1e61392068295e8ea93acc1d843bc20f31351a6a104cb5dbec27037cac8f421081169f712176369ee6fa33261f955dbfade795d90a77336a80f38d521cdb2b064db92f196075994450b4922e798e0bdd128e93bda991038ab18d4fa3a36e0c59514f8816d1fbc707cb9f2c6b438d040c782a6502f60970310f87c70b5afba6478de770316e333c640b6db22150c329017d3296f2c3a9c51f673ee84e42e177b4230dbc7e031ba4a3c62a5a05def3c331d4f09816a987865f8f15fb13b8cd6bc4de9c0923b51e72508c69fd855eb6d17d156a90258b78b4a3d4d8d91909ff15e04330a4e2f48599b2b6f1342e99c325e507ea01a3a44abee972fac20d89a658baaa114f7363bc303cf70ff10764d578b7e48921334cae5512fbaef0c5715ebf0456d91c7555233948e8c95b640339aceec7da52c375eee583d98e62115796e319e3975c08d61a431c1cccff630da756a1d7420ae94f7abfec3156a3d3a41741aeb5e5a331c2d8d52887538d0c04a8e916db204432850c3f4046336ab40c7c2ff834dcb6c145c7f0b921987c24e9d548e0a6c6abf095e87ffbf1459910d2d22d618d2bb392e4af40ba969353c0c3bc69e3296a5436d6c8d4d296b88162ec37057e358608709bca266be4a4acd1aab2c642bf6d1a578e35060dcc6c9fb2c628deac9535ba4bcb1ac561bb89c92dfd53da293756fe3881134de351d4b562fd32b42d6342f7e696be6fd3b991ba2be2f26bb0dd87d11d2a4822e91ce1d9e555a1a371c9e9d2799d0bebbd09bf7905f8ea6d822e107127a95c4edddd141f0ea2fd7d144c464671872893b8699ad0620142c3d7f8dd7a7a48b734867f01adc266d86338a9ec75ab1224b3bf1da2e1d2845d69d2af88afc6451ea7c97780b692ebb8ca7c3e362e66be989712654739c0703031f82b562c93412e2aa0a5e570733427db2dfe7da97e56522b33bdac923378df6fde56c27a538241aac00ebbe4ccc82c5d72de14bd160ddc5f0b97f74b6282242ed7595f545f606951f103b9d10b57d22f1f30c13eee76220509fcc586d97ecd2074c23895b4280f8169a4f6b603f0cf4077bbf46ae32bca9f68c97acb29cd79fec2e9b96db05fd76d0e18cd300548d79b96e079d77a56a143407e23f53c7ceade8e7a97c092acffc735d53406c6f7980227ed530244146fdb62c4b8a2adf4e60f607148d932215359b297058784345a78e15e9382e36c575d34ece66dfe2dbd496abdb71333499f8447c2e0c2ed6e2530cd657ecf043607b45d4b82eeb5432e9ac9c24989b0aae7b3c25bec185720903cad38d98ca6371d3f66cbbac92a47bc3afa5e17f502792d9b1e0a27013c0ce1d9bb4efa6309bda817eceee869c232ad5b6fba02222b6da30fed9180139baa0ed7861e5ce8743a6a745bac850c1958d5d779895bedebb1121b4e7d0350fb629ac3c9f23a6a2b2fa8cfd64df89c99a0fb72b5dacd42fc748764a0eef95c7a50ac7123a18128d2774d0c1c05b148296025e07e49bb7202415472051d974f0406ae3d0f01d11abedf2eff83b983c35c254711ee18ffb90acf151977108486c8c28fb36d6735f546f344f7ea8539592769a0ed78e6d9932cb9caef5e39db31b612c69552de11208c16c1669cae5a8b4cedcae1b997d1d913b71bd6859a1ab6d18e8589951f3adae0cad7248ae29cea8d5e7bcc7285bb8093c7747952a40aaf4af7fe6653cabce8e04b5858b8599cd575ca7d1baf083596b11331ca0561a3d4ae89001b84a276d59b50c34a06634732e60af98f392227afbaf37f01b85531025914c93dbbcb9137ce3e33aeca47704dbbb7c46f9f07be0f401085113e562142cda5ba6ef72450cf466c4d4960490fb1161603b0ed2a999967665dcfc7483ee880accbc288fb7d37c4ccafab424cef1fe6156ec1cd7706c037477196ec0c848e2ad785bb9ddbe51322bde3a0d52b74545dc4415887665af92f1f1426572417e4b37fc601caceab6da19fab0421a286e09d7796ddb040b59104bb9a066c00b5424c698386308049c4e6573388e3dd9777ff2cde82fd1c0f5460dcb5e6110160ea643f6115b8d23b58f309e829f5eba3457f4d312dccba8317b55093174bba1c155400f57597cfbf931417e88b67217584eee2e3c3da7ded786ec1995c520212192b173d6b9c9d20495a437ff40d08ff6fa13435214245f4dcb4b947ea3ee5b5447011c3fc9e28fc32e5ed873b1af57e0749d3e342c8f25a2f11646f222e35db9bb00f9e63f248687e6d7b078d3419b8d01d662454b7300801f0383f16a6567ce95bf6d142cabfd37a21147e26165210535e2dd1b0a035f65de860c81d42e4b8585f1919c63b6597b21efc016c13769405d8a44b5dc43d9cb164fd5e45f2858102e7111ff5592fa56994374afe32dc14c4204bc4bfd85e652f608f517e325b1987215402f6173da254f60722275387c1b81b00afcb294cc379e61f7ac23cdc6202834b96c0f74e2bb68de364ecd5d6638009fca3d2be2fff9a83e119c1152f030b7d7fb18de13c57e4042a4330212022e45462eb0c70c4c048b2648a8832bdad438f183938436762146ced90f50187d60f6e404cc83e2dbdb56e08bdb0ee28c6449293a60e88a064367e74f1d34f8368ac01c64338637dd5a5270460fa82de5c72e7c22d30b1e2e0cb3e55747f8957827f165c4f752ee6d8fe9554ce434ecb85cddee0e26028922d75ac62e25c91d7dbbffa33440c855d7d0a0e51b7d0f08ec2a9ec5b74b18ca8859dfbd4a964cdbeb8932256febad170aa1e0305dac12b0f06169c2bcb5e2c0d0d45a4ec54f84f43fc655f534b9488bba3c392ef389c4a6ffb4882120a73eec29f9eb0d86cf050ddb0410ea0454972f93c001bc3099639c828872e7dec183b1d05a02122a2e912a70cf2711a1b4f8fbacd8dcd44babbc0b29d42e5a6e3db34d4bb565d998f4ada61d041a4555897685436bade62022717c8789457156f0eae101aa16047fbee4a96d754fa51381065f9870c1baf6d8195dc2f24c1b905d0899e5225b930a8c6ebc6f0f4dbb23263fafd0faae82b718981568f453448193675a7f73c5ef0ca92dda003af6001953e872db63b445fa2ac9ce0f83a05bc0c7b5ab1b0ac0c0447c9f159b4711a81562b26eda1953c725244dba4f816aaaa55e8729d28e0380e6f20912e1b020486e1565106b5d255641f98999279a9498fc716836a911fdb89f746efdaa20ac66e6a9ab0d988cbf515c0f32b18964378a7775c4945fb62778830866c14d37035ca561dcb46d88df58971bdee227696999aa53c5aa0c388d8e69e65122dee7a16e2d3779cf1b84e18b15f36d4d6ff9bc1add2269ed4f0ecbe75c4c0ac23b2486bff039094e6bcbf1af47ca611a4c5c88ca8a52f72c2a7d53ec7d7b4d8e318cfa263dae41798b18ad08de9e84420c172f3d49db7211f1a4c08a45f9f418b6e1f0e4ab33181c1262e2b5940079dbaa956ef57defcfb3348785a249783422edfcf41fd3608399203541737ee5812983c5df76d38c73db58c348b4a2b147473d4450dd76068d18c935432d3c19f1129bd4effe40ba79096a3fce861bcaa5b4bba22667ae18f9f00c66210c79c0bd34448c391923960533f023d3e897a3d50166748b9da5d88bc04e81382abfa5ae2b5ace780f6abda1cb0906be536f1766c8c29d2ec1c953976e012f2ef005142adb03606b54c7ea2f0ae7fb72f4c6189736481477b82acf70d4d8fc199449848c3f859e3a1676f48c7ba6c1b0cfc98e693b081057d274c51576ad948e845eeaf577b65bd76bc082755543ca21706c44b506a31d183d4be66c2c266eff0dccd520cdb01aa0c9755b820e710599b0694b693b185fb7645a5e8e54326eee41a61ef9119d75165674956d4f91c71e7cd5c9d7edbda39c197ad4cd0dfbe10e6087c6bf0d0625886989bcf990531c888184f853bc4447c8c120ec2048d9c2aa05d7ccf5f280922507c7cbac5a9d93b2cea6ea0d1a182442ec7f0e77d0cf80680d3375fc82b9c0b2148dabd0b9c75d58e71c825fa0bea457af15c588bb573e33fc55a52133ff4e008a32902e4b824b7e0ed1f137b84495f21652b42f6b4d568969fa04d6384906c570cfc314941639c45f497b85c6891ab34f5574db44695a77a9a3f4411278c1cb91cf4c59027c2890eac39fcd9c242166977802ca35b1004064369b58859b63a57346b4c412f35aea64b190707c3bb1a9e9010a0ebb73541887b48141390ce8f44f1a0afb93e8ea44bd4444e9eb36dabdf8f0cc60d69b0195892127dbd24926b1403b597e802ba5fe10932530bd48e3914195072b00835032c0d169881829a838ce425f1ab1676a2dca855288ef9fc108aa3aae7390a4825b158928ca342926d854aec36d35fc01a13b6c12e9d468d9e821ef14bc224c496b7a27b51716185b14e48d0ccf7caebceb97cdad372c560bd30c325e57a427495cd04e8b098c213eeeb4be4324101c6085b12a45e89bf63ec48c4c6151334579e24c0456c8f11b9678157bd99e4267f9e8dc0acf6c3dd0d45ced96e27a6ab00e19b3f3f00f760568c56a431461b1c6527c4cdd94eb2ab358cbea489772ee31bd2b2248e5e2cc131dc10bb6b9cc1a27cf8ed22e53d6e0e4c7161d846a289f088d1f7c3d61b2370d67369bd8b1f833e82e289800e9f44d68dad9f30598da05456519dd5494a99df866686790f6fc0c5c2be8ee100384b47747a922ea4ca49a7b651a9d88cbbcf3a17413b93abd31f1f3820885af2f2e0c18b23b887e068b048e4008a7f5c14a3910772df448dc27fcfb9c5be34701a9813bd789ec0d77cedd6f7a96b54f4750cb02d3889f96d08325333d101f65ad9ea3905e5e2435658b8496c2e5429ed25038b943f44d297f2016748259798f864f96144475763527517d6822d5738736caea26b593ab6ab0b5265b12aa44c4069d83fcc1a59bd308811e5fe9e5cde3016cdec8a8cd8728503a8cf9b14195d6490f0577548616f2523f4f3d81487112a3bd3c2adb060d4727fd36769193b785b9af37ddbd28b37cc6417d72295d7b06b5d7692789e6967702cf068e5eed4c6d28014f47d2134fde54cc3d1e4d63874a60a3741020e1559dede191f583ef160160384809f62f53b1460076545e099ae48af54d20153422ca3b47ffdddbea80b032466774d908980087823ddccc3397659d88c14e1e3574445bcb11170a9d0c71ef70a7865b69f760b5c0a7f99a31867cecbdea9c749721508ca0198d12d42968ead4a679a20c5c1c9eefb0857d9551baf416a9be2bab51c1c501c6350852e40903815e1b565627fb50bce6fde39f5d30cc9850cf2724ffafafb8e2a5727d2a534e3401dbd0eb094143f2687c0946770343d181f51a3693b4c606ccbbc7e5fd835e8bcfbe6d1f1e1531b4c63a4d7e5fbac5c29c22b554b88aebb12281be5af1ae940ad99a0a567389e14fe22e44aa884f40c86cc3fe58d9b881a2ad001ded252188cff2b1531a69c1aab0d0d994bc0ed6cc921633566e689e38094c7c1555174e83f48535599999acd1a4d59f27659a6c1a68023b748d084c194e12063f343f791a66fe9f0f557ae13d20ef082473e547b8f98d7b6f573477aead911a92304376b6b42eeae270c54e2d721fab49658fc032c1e40d746453e95055bb29458b2e6b81e7200087c3f06593d7ddc1e2ffb2962edbc5c62bbd4663eafb90808925caa4053e12e8fbe4189abc3eff68ec60a91ca78ba9768a9262eb38ccc0cd52e371f5d34c5e9510b188b719dbe393f5d9f89c3e0c86674132e932c5163f82d5386851609b110a75abcdc1723e54d4714cfdcf485368fc1c5670e003f2291555f4a2456cb96266b397a4809607c0e22b010b1dc151e003274edc9420c2d425701df096299038555583d16d2ce2153afac46d4d8c643825c7e78f17b073bd3a0ea263d9e0e4b3840c73fb935c469cc27d46b76fdb3dfb9e965eb270a2b3d7185ef188923f35c44c33031bfbab4d1d4a83421a9bd803efa1f9c0dfcd824f2b516c9422d266833cfb2ace70c48419b9163a97404cc0483e682dde0926367db7d4a8d7b2b830e11207f05af0bed3ed051e0c3d795d30ad4165b3034000259546a87f9b6d001cba3c24aeec14ba843ae61af19dcdba97659f96dd39725bb5ea7d317787d47237b818230e009cff6ee4a983c6766642ad54d5897ad7496c61e9f88d20d530494038075e2c607e144bb67b7dac93239bd552f64532d65ed67a411b162293e05cd1a6de66af78652a6ae240171e19a760a77427b7cc1ebd9f2fa2772b95773cbba239a149df7a2b3311fa9385788fbed448b46ca0a63d2bf102f138a5c7da7b7df4ec98e0f10f86536c78cf64be38e4af76945c71356720bfb135fb499fab5a1105bd3165eabb5cbdeb0db0babddd94c1ab8589bc5f716694d4cd98a43c5280688f11494de06d0728555fb22d7797c915b035fa4e4fb2c2ceca43449f137095e5822779be5dccc0cebc5dbd351cdc72b61296b58dca627a56a759b6c4f7757e27b8c3d5d1f7053e51fe15ccdd61441febe590d0cda2c26804f9076a672bc27917018865c8bb340f69bc0327694a23dc6bc9b9308ba2487f2ed24e3a36e5f60b78973c24e40d0f7f139fc3e71a4dde4f6ddb39e0509648569ad13445ff8ea049bc19939c5409d65044d8313c6149f0fcba45dff12d2541710cc2c5bd40d35269fb3d9381d67ac5689c83d3ac5211e8cd0ab5ba073c701663601e32fb7178d23c33e491645778a5b78573731d2511bc64a7939d49d713b218f5bf018bf04f27694e0a04152c8a42813ed429ea2e11d85caa0409900d95744d1c904dec55c51a32c1c7c32da3625bed1d6890b5326b517c49c091436be0b457b19315f0426045e623d7b6489744b90fe39967d7f918e9684983641632d0fd5e9851ba7e313c17c0a8ca25578a53ab6e4c541d93a7b0e4012dd91a69221492503bda864025432fd692be6801a52e1dec2191f7a0f9f833385724ae973680666bb8c5736518e79910ac97b0165da926000653a71b5a985cbac6267d3febd007abe005b1ad92b26a998b29bf1c35884175e3bc558ba5cad7b7c14968a49f034c123bf09f48f5675638b938bffa3dfd614eecd6a99e3b7a38d339c38ab279584ea9eaa83ba934e0dbee154f5f994c9cec6a09b4ba2788f1b810012857349c0f4fcab8cd3c486570ecf6f2acb3e787045a9e3ae6748a5e92356a45f81068471f8e4c0e5e8f9966c8cc38cf3a46449ad24e249cd5e93251b3e202e0e8c8a62bfa6230b2c70dd7c33f36a32aea21f8576064d4faf7144d7fc2941edfb62c98a4a88705e40d91a7395ec571718676850fff3d869053af15a07f831c38a3cc4a766c05f316a5a9b0923c99a9b227335b74f3200f19642831d56849ff040243d5f974e29728a4e824302b08f2231974db740c55b043475fdb88e6c09d35b0ac15a9448644611e60ac652152311926f827960de9f2435bd592ad401d0831c67de26d34e2e4ccca8b78382ae4d86a39fd59a943e295ebc06b237c0dd4efb4fea9f521f5d152b17ee8b845b398ccf4006010ec9501c773823a6f8c6485cc4a06fa0c2e4e746f23e35c462182757aa9183e0633d96877cae1a45fea7c82d42dc05cbfd9704b06396c4a593919598ddd0d3c91f9a497614819feaed586ae4218533cffc599b836f8d3ea74740cc7d0bddf9728308cc44b5870adf46e72d1f7bbb876f8cc5e85c7712a6138f3d621350960d1c3798987b02b678f738715b3b0f900c8d7e4dda1c3fc63c7e8dfbc62bf453bbb79b05a121426016f3099990af1ed3156c3ebf0115a604f4d614437e9272c51281c6d94c3d20cf361e01b1f65457c8678f2736f5ae3f09c4c6a116be2e3f859a83b930878554804920c5ecef1e901f911e41282d0817999ebff6f40353e86cd71b10379520cbd879e31792fe484ad1e811793879c114444edced5fb18ddd418083e35b3e7be117caa03c9caa94d3fdc7d711c22690bda5378f02a41b7a67664a4c74700d15617036db15452140e4adef59777d528bd0e3eced641102a3304457a94f59599397098b7343cea3332785b0bc06c564ca3f220183cb60f23861e8e48a55138608bedab581bc2c0db7d9608d64e5daff656cd1a421edac9463f061aec8fbe18f502bec3daf99b4ccdfbeae057434d332e4fe8ec8995fda6a31211cce9a1f6c8b725f2eac4052f58bf2ec51223a3eda21d81eecd28955d5c8fdf1e57a0a715b79a39b0b64ba54f08840bef98d1a3ef54895ff582f2e8fe2aaec46c48d5d07ea96095ce40716021d6a70a2a15cf660179bc14b55881c48a284464352a5dae6a870d30f3be8855cd8eddde476b31c851bc3adede66f7865f8904ad5275e2d4cc117b7eb439652d1d37a11fcc51afd595d8f0bd838171ee36562a3181f2db2d1e382833adf85616c8c29857b9fd755fc112e1000fb65f03ad08834bca0cfec985b9a52b52d48433f305d000f72416b204244ef7d4786030661edb9968f234b67a7e9da0ef8afbfc73dbc0524a31dcf001232097c47602bc0654d9644c2315dab791e540e2a02c0aed9cad2c36d5063f28f4235ac0140c39a1affa88919d62c80988eedc252a590f373e18f8c94873f116af8978db7817b80a7ddb6e0a904bfa14e918f4747bfa186918ff598688635a0998f2074c4bfee4539a104320aa0f9253f38e67d5d7d742df9fd30b631f7e459136bc71c3df45f9dbe4753147c808bf67c89ba2db66cc083c8036d6ee10f3b3440ea4fbafac336cfc90fe542033067b5bf7ea35923156bcc5f5dfeb0b665aa44c3618d09f0058bcd6235a7262fd6b1b1adcf459db1aba100abbeb2830628687af2d111878366f070c125a7da3667ad650c42a671d7fbc56bfaadf147a462655ba29ce06d861b9e25c120f32b457dd0f5cb47e1b1ed620fd4f5e30b43ba75768b2caecb0b5b4c5c5b545577605bc7a7cc61b840f5989aa589be8b8f59491ad6e24d2020140cbdcba2a40d81e02618d2bd69d77290c74e841c37af72dad97cad905f4d2efd4168e959cf2faa11d6b3995f0a2cc838b4e3dd0faf94ab63da853be4742e396570883b72b4b6d2f8591b61270d9a6c1993081ca8a1729e579148f9983cfdaba14d4b4905cdd5043a85cc991b670e6934f6388d71547e9070f65faec16e979399b8e9ba666a62d5b3817158f9f4a7532fdb2809fec24917b0660cef7b1ad10dc024b822067e348ab432a87acb1163495cc50c948fdd8b6ed7885a73c60982b5e6192dbe6924a919192275175480d528036ba18104d5ba78f8c0ce35f5a6102008d6ae8fff7869bf5d35fe57d9fa6d3558d3a1ca60ad1676a3a8d05bd8af5a0588598a2b1742de5777aa84356ad15b9a05786f42af460e09df4cf55662a825c6a9861cbf3235e861589bcb8e9b54085fb47c1d3ee4b68004a9712878aa2c1496bcd95fff776637789b72bce5c2f7612d2a852d4c0d25eca1464a61544e1d6121eb62f3fb84137a9292479ab0c51a3436795d183d18ff4185039627c5fae7049ea9502bd65ef5c935551034a2a9b12b0b701bd1c703381f56c9581bc6acd9a4c5e1ddfd59634d4e903910ea5fa84f8e47ac13313779f658630fef964539f2228ca5e5628ce48d206be1178a5539248587d0205e05526aa223c95a34344a057ede17989d77a0ec8003d593c2f9063c352495b5b932505b06e58301bd24baf66d90d06cd1a48e65cdf4e0498d1c1a9717759d990b6b3cbfdedf5e39ff95dec954c5b2aba5b2661ab78d7fcb854b25306ba63d64cacc847655785fe3f08d63fb6ecdf4f167d612485793647eb29c9310352de78a39b336d6986642ad2eac6a06c9d24dc1baa755cbecc747d6ff4bb50edcc757198046386e166a9b4a9824c2c50989d0fb3871da496ce4a05a4764913c89b17ca2ce2dbc8ecb9d6e9f707c12038bc275ffad198083a9126b0a00ddb5c470c896987e99da0cc5c018d3965858734b4cf9aa01a0afd8ced003c61f3446a35077b034c0443a38c4eedc646b240c8ec218433bd3f3ca9654f217c2cf60038d3f68e784363234db58cf17d1d8ee786626b2c1fb83a7d129a034021be89be0d7b9ac94e6df8e38cbcdf0b227f46e4a4f0d4c6efc8ca78169745b9634be48365b6212a0b07d42cafac151aaf97dff4992b1b6cb6ce0ca6dc2a9e82b0524d6777a9cc0b41e210daa141295843fc3b5752ea7e21eacee315a628b6835ad6cb3892fac351fe92db7af1c4368c890c519aea15666b8d327c6014f7b69a1727e86f2fa1de387408fb0659e2b1a8879cf9ea417f17a38ebf1900695b36707506172f9f43e18cd38194dc28b8325ab613a71b371fa84f5931f9fcb95b9a2f37b9bc0666bdde36ae4a67ead16179d9242821039959428b5faa36990b22a651ac5c2b24642d3e92310c2b04b4e06408b803b61ab98bb9e9584d9d5ff3b7ca5c514fb54f7422a2bd1d96fc2ff26714408b675803e8b8919eebce5f0a3ff2e5b91574919059ba27c9ae000a6068d07e108cedb48172f6e635b6ee3de2d2388a02ae42af23f5967ec6d7496ae6dd872efc8f6f1a523aaaf240df40596b37b8ccc4fc147a7885d9a21125e779e25865e53143486cb75777b8fa843a77bd7ac1b5de3ef2f7761e64eef7ad097b5c02a8ef51b890042a5db65969ef1dfffacc3048713006d2dd71b2321ac0c7d96404d39ba749937d1e1030dd5e94148a54984811bc3a4baa36867ae43c5f38240dbdb8fc3c807161b836c1686af53c8033043d0a4712007a45efd62a64ba6e7a2f0aa8d5b0d412b296500be469f34205dbe5303f37ca7878001582aac50456668da9b8dd633c43b4f9e99fec8691f580710259c54798a80a330c65a86b14a81194cc518a192de7bb965faf261051c54e298cee257d70185610d671ccabebd70c1a615cd9310b46af655986139b49a920f64c6766dc01c2164e1adddd299b01e77c858021603c541c7f8165191c3133434c638ffc666c189aaa3f939d776cf38f8fcbacc078a5e03dff4a4daa328093cc1879f9139deee3ef62364effad3c9606d7133520a97d312d54f4bf8558aa3f0c137a841e7d2d1bbac10dde52c4e766815c9e19238c0b2e0d484ea0a0eac76865083c6aa58c5b83191a00c9d8115b0c4601712781c78456f81462101e94aaaa9413b38586b80350d23ccac11327204b41ea3f60bf7f4fd044500cb2ad003addd683baa8dd1ef937339249b283329442208a03534d18eaf5c2ca3601ff74294fad1abeac2dc9e60576d4cf32861801fdece4b58fbccb26c95db45885f846a9bb8e3394ad935f5668b5026964c8355c2e5d6b88606d2f2566dccbe0888e6fac3f44a5640bbdf588f134808f43d8acb9f5173cb5d15d382d4dc2d0f819028df6069a5b81459d0e5a20c6ef98b3b26e674e31841f12373d847b0c9a0c4b8452e95a8a4cbaa6b817a6f105b378e7568ddc74a1cc0178b79db9b2553951b95ecdc38367fbf177e37e5d6e78ec3b3cf3045b5115600611427397fa9fa5e7080d934f9483b44c76e21c51c350a6775f325b52e08a93b760c1e2640b263cf3824ac983dc93e27c26bad64c75cbeb78cdf6d8fbeb57865b646c068332592f1b1fcd503676a6b87c0dce145ac51a0ca5ca9bcd536ff13c7e18c872c690f1ef719c8de024becac86b54c549b6c05df73770e93af51e841c878871ece67909d478e7bb177fba6726795267e36f969118b2cbb8b9c361f49adcc212a7425750beed94fbe20d8fcb54011852fda2ee0ddeea797f726b2880eb3a94a5ec82e1c588db82e9cdf26ff9d75c59a7cdc8124a003e9172141823fd8734f1bf29eae27a9cf05bb666cd084fc4443dac3ed3f108a7ebf5c831d46af9580ca1cd9617be7b70d5e2be823837d20f4b0fad347a6c2b322e88dc63966a52f83a2b34cf53027c47231408ae449c81ac805049e74a1cc889bb1e5e227f0c2a66df3226d9ee9eec0fb425231ddcdb8cb8c67378a9e5364582b92238033d537f8be14fd6d999d15d52904121ef0b270f3040305c545d77724ba0da628222f349f1b9eb058e69da5b04c5dbbd79a8c1656781980ec7aef16002f154130ac81dabd0095c5e4e7987d821ee69a66ec9bdef722b968c61fd0380ddc7bad6238da5f719705086deeebe9edf8dd417aedbdf03ec8e7da7b5517e1eb6ece21a268a35e012be1437f3f80aff63eb5345621f12b39a570aa2ffe532bf924dd9c162d1acfac4587c19d7f50cb3f40e63af540c5f7e215bd7c0a122df21544dbb6786dfe86f807f09cc49e9bfdbddc5a461294c158c68477314ff205bd93966e1b4b75f955d0629d951bf28852c34f1659a519e5cd66be8eef7a178f584c516d842a34033b0b3c0f6af8eaa0803fc279f109a0accd42f5530a3338a41eb2b8b0d10db9704d5d162d6c848eac378c3a8c2d7972c949fcb905c1de922d181884e7ca05f3956318cbf37293b4a736279e3db0523b236def50e5480d6327522003c3fd32f491ec78ba223688af186615a598009d35f045a010e8703d817d46e0788fc3969a62e85b168616e34d9b38337450a0c0069781c9ad77217098297f5533777365d21b6cc64d19b88a5d356d75b7dd94a42fcaf32065227d79cd3a75524406105a6abeb6e981517fa5a3ff5ff376817d6d4df409807a07685e124b8c3c2373ef4aa5c016f3add90f45faa2c7e9c50a584ff9fd25f1bd061867fb4392847ff392b20ef17e1e6f917a54d7417dd66f63a20534b43693bc987d16edebb4999c8827e6e3d3e3e662800c0dd1ccec5e9700ce89daf44a38c3b7b4f5cc93a8a64d5bf44377b9eb81155a4576b82c6b4bb4ccfcef534293ee7e2575ccc4538fb5d78db269b0c74b3ca5083686f52eb37b35d0d6a55051e9b291ad7d4d528233f19fe3fce3dc245bfbaaebe038e8bc8925cdab51ae32bfc058811d8e88a23505c11a386f387bb282b8dbd27ce14443acdd44defc4a2de7b623acdf5684804fc6b78c59a625f22bf86c4b5ca995a439778c270120ee7c986ac72a6af7db42c4b5660b5b8cf3679baa3d5e1ebfa8b49571e8942b56497b735b9ced804a5c2b4c2aeabab684451ea05f66e6977a858b27d08d9f249d69feb173f0cb66b677ef5d8edc619662e8a39e2c79b061db2f6b5bf8073c67931a1c84663ddec0ce2df7dcdc6d6b3b2cb4f499bab3908380530dfc3cbd2333aefb36fecafd810ee69e3eb253cbfa82e9cbb9a58541a4e3b6c15505c7c4a7c398703ba5ca832de448fa3f9df6607dcbe87a7ab72dc85f5c70649bdb5e65711da5303b54ad42363ff4fff1c038a9442fa7d589eb98a8c05f80c8fbd6f8c367d01946089bab8be28d53a95dede0dba5966c784bf875a6721c82e9acbfe9875ece6fd12ccfd72dffd6a70d138a8a4ffad7784dbd5a8010489c5e66377002cb7707ee988749a7ac06c43b060f5d6e78b5bd52931ecfbce18666ca00243dbfd3a1865125180bb3046e3e30b0232c34c59e0e8bd5f3644c6a1df92e4b245d8b3380d6c95693c61924a4747557059c2bb09993ee39085af0a2d281457eed59dcc25e2c264115c8eb0e9838b64b2148cfba90208e1696b3acc09c15974bfa390549b30f1a0776416bbfff0252cc59f00cebaf743c4ef7fc7cbb62a4594b6575d1b971402225660e11d9ceef40d9547d9db3f6703ed956836a11ea53c7817e3708cc42d89c85e75c584ec2e52c2c9c989e9c1d7892962f179c92b109286391f35218a27651f6405d41073d8455b8787dba021aa6d4a61ba5205c2a1b174f6bf67006a2dc50fbe3e2bf6dd97c35484f4decd989b89d0305bdd81603b72dda2842c2d4f4e5544ec4121c8f6e15a2cf15408b032a3b4ce79e07a223175a78c721f2ae846ba4bc0be8b04ee2392a92bda483febf6462133ac9c041119c145929bf427d0ea4f26a505f8bb9405779f9346b1e947a1576c9ab5d1d3ef4d0f6299aecdaef289304287d2f069430e0d4cc7366fa4d099e2b35363a0ec40a7603bb29c69f2e63967fd1955505d4a05713066d289750f48b4f00191b0566212c67c272163e27613917d64fcc84818383af709a1d398923ea5ad8ffc6117b6dfb5eea7cc88b97d4f9c028a1ffc1a39ecacf278dcafdc787d12ea0a0debd936116e6644dd3f90916b222f3dda78422f0e262dafc92085bc6c15a7027001d4abc3f40e45e189ed90fdb66a2eb26440cd7ca2e5102854c612765e5c65d3f1c8dad2eae05df4359eab2ad9c7559061afedc6b2a583c71e81c7121d35db9308f3fccde020eb356954ba1fd66d474c33b3f91a241f9fe8b063e9fb14722e725876575a5b64858247ca4904b0485de70a353fea58927b4ebfb1d9c0e682889fa09a5347efa764dafe8bb2df0c4e7c6e33a8d55e8bec73c94a55df658185dad643e3b72603be40c5b6271294388c51944234ce4de3ba9a8c71062f62f058dc5bae9d558b5c432d0e6f57ae29b1fcbd654e102d76eac85d624a1382289f0855346356b6da6f0997d2229de1b571196e30c39679677863c67ca3bb364821f7a5c6fb986215ab79773f6c6711c3a7b1fd8859d6de68dc11b13164a97ccc09716fb8a13b9385177de2a08a7f3f06d08f0ffbfe5a0cf3184b6dd3032b92c0bb592cb9846acb53fee1efe2c58316e683d183f0f7db1943c37d0ea873f926e026f49d009f0609d74fb5e79f5d4f5884409562cbe7df7d213df1f0a82c61c508317827c049ed4646458e9f824754859975d397793a87810a3025ebe9036191bbbb4bc7a1d7ee803ee232a7dfb2bdcc040b1f28d587172e0956e3276f530795d1f87c3b2b210ede153b08bdb1992a38e3895bae1a11cf45fc77f76ffe7c6d80b1fdff6d83defb08d6cab43e80d2b29b7f3099d23a4e1d41ddd1015b1883968bb3a13b49d8e124824aad776644f586e8cf5413251ecdc86f23a3abdb46f796d67ba0d4b01d6c2229a7c76c69032aecdb5382cafd712667e12ce2e3f40203b4bb8c353dfc9c5587e1de5d42b20457f77ecc9050792cc890a6f21b58a8cc15427994136674176d4248160fb72d4e962ad04f7b513329c94af432738d4b90576747d0854420c376575d28a15cadd6c1168a33840a39e782b49a8c4538c29485966a322f92e8e36f14eea66a560159b96b518f5b87fcab7b63ea7a6ee19ac93619036e816568749506b1cfa2d69c2d6645d493a7c4519741718e5eda686108542a1a03686714e2e4f2f8a51c4fc2242a2bcdcd33788ad219c21ce4c20caf2a588db147341e7a2de92f318bc340ec4c0e1220b9f6928d0bfe95ab6bb88683b9b2f93c51b02298afe44da31aa1e4584f1d0397d34ddf26e24220cc804f1ca142200aeab117beb51e05e7417eef7ba01dba6383c02ed7b67eab3c62e9ee8e27a061b687901fb538c4b465562bb739cd20a5882929d84f5164ba449eb587233a76f967d39b14bd5808d953f2d25aa1f995135645a0cab6eb15c68013537c0542c8fce5fc960aa58d855309d8715ffd7e536905915bcbf2a017d012f55564fb468e8e8619ebad23087647a7d8e2e9b6123175ce5ea9e0231f101fe7c481ae8e8f15840a3b7f708012280dcde123306d669aac59fd68fd4e2a4f44648551c165ed9ed3d58d760eea55f12e8ee07dfcb264252ef14231d7aad3221f44994c41fcca332834adaab9be18aebc12e14ec48348b7f1ba4443f4b574c90d70d73a183decba2c27ac3c01579713f4625aa83ba56924f9b97c1722c1586583c7ae28fa34a32e89f124fc5f134cfaf4fda9e8462608f6fc4eb1e1f71c9ff7c55f47a0297dc00494a47881fe8bb6ded07c26f2595b2ade29722c9d5b85066012781f784df62930a81574263dc926a142b6dea78c4b9090422fbf23f73e30382edbe6862ca7f1195bb526ccdfc4133e2d0fba29c46f32f201f636502ab0afcc78d55e9abb7ab0f9a81c6f40a689c4392d61e60dca27942f620d146fc3199c85b172123dfe34579b1f5406a76c5e4968b488ab7c491ccfac084adcc0d59d4aa2e69d20620da96e18a512ba0513c60d51723ce87920a48968f4bd9861f29bb848f577f7de2c4aa5b193e461f22851e82d7396185ab52846ac238caf87c3acef82a6dae2ef80887c0a79c1168395b6fb0cf7bb5d0a4e34ae46b800ba82dfe4a3d481d5b3377e53e72c0f09ab109c20255f0e38d5c8d71443133e0038a074584849f00078182d8816015a348f5f56187855da94eaf2f3a8e01d0df37dde9aad785a997eac834d00ad77c53ad1f53fce78229acebe3bf28f680b980b5a702a653ca72bdda1478047a46df459de8d06730c6d04bea118a8c81f57ae894e8b0eba1e14f9226964d4c8d2341bce15a7d9e2627ba760319996fd5cf7f97009006421fce0d77f842245b1534f429630456942a6f3865fe1674cc754092e5e31ddaca50fa6fc368918318712976634b3aaf032158156c0754638a81a878b8f680068c19a1251eb0bfe002d1412821a8bbf2e895889d130e412c7fbce3d9b2d875cc45c25c09ecaf829e92bfdef186d9a254c126217e1c243e31c8d3af747a027b08b51521d16b0ea530171c780e3de8957d3dd8d2161768f0f7b6aece144ed218b219933f14eea3bce11ef04044f607ba0c1fb5e99b1b4dc46de0d2bd31682db9787854dbe3cfe6934a5de42f2052b8ebe2c1d330f3cc240e8b60d478af3551747e94c76e5aadc7a5666201ea53cac9f6824ce0cd5d1740a9e550367b1352bc7989dbcb234b42c77f3c1613402dfbda761e89d079e5a65cf2a0bd6a50b351723f07a55e46d7414a7fdcafc1b8142d096050aae2370fca95a9d1c85cedcb4668e6904abf12443109d484248e64351d848c419c3ff676c6ab8f7ae66c76a6724fa215e90f25ff572a2497a0f2d22ebf320a72a94b135c6e69f03ead97580fc11b1cadb02e5a286704277035e7f8e425e1f4780ef9ec94812b5c0c2bfe62a30049b048a86c3f70ee57426587d42ca75ffb242b41d085571f30e06c10a3643eed1c73dc6482495e111518c36c92c20314465ac9a5f04299f2e15ebda7c45971fe6c1efb1ccd963d2e412f7b169431031a828e7f58301e554d0789d67b610c3afa497d8f4babf19324f8b1e9add4845844fde35aeb42a51befb565e8a612ef9d7f049d5a3800a89b1c31dc1773a20a9fe50a106c86788ce67ea2b3175179a96251e8fbdbdeddb6dc52ca94920cf607da07ba072670a569d0e09f42ad924da24ec9247c56b7fe9c4da314a0f411d3a8fa76d6a9918b3a65c4ead4286a3b3d9d1ab7964fa7468b736ba7464d88893aa5c162dbea56cb82754af20cc1e8d6ffa1c30e9eb3ba23ffc0eec842f5a787c36fffb6ead4c8a20d09c988669df260609d1ae5cb12e954db1cab63773ac5976fff66b3dd6cad4e499e24bc6efd0d472bd2a91dcda5f1748af4f5b557a769b66b710b61c820e4d3834318234562ac97835a0f068a1aa9222f4f1284dcf04a9e210cb9cca1a945e61a0440dc11c87da2e809a3278e5a4c420baddba04c420b4317068a568bf2c218292259b60be0049f4cc248915b8231c22355e47d62d6a2bc3a58faab09681dd55f028aae7215fd4d03c2b50e4d704c0a87940270e5bf743cf87afb015c005c6975f0dfc955a5b7ff2922052605e897668820711ef7d2cb61ed97c07794dd68f44b8fc7e728fb4b7cfde3b7e2345cd705d9dddd3957be7c4d7e954fe54ff9de5d17babbfbc4de1ec73b73a44ab376041010101010101010101010101010101010101010101010101010101010101010101010908752450b7cd2aad98dc4755e89e42b31496152b182c5a9858b139417308250824e825c04b5083a05b1085a11042346d08a201541a6a0144126412541313ea824e80b22092a0579410f0679415d1017440a02c32052d0166483c21b826c901674434a90169402802000e0e02a66b87dd2aad98dc4755e89e42b31496152b182c5a9858b130772391831be9311ba919939305ea09cb8682173fb4f0fbe70d60d85099d58ac5061a2b0db9f020c51da75e3ac142625dfcded27096f3861569db58ba4e4d5d9edef6e4871c12ccd35eb34d7ed27a500a045c8bafdcd3724160070385d0118f1cd9cbbe5783856dc2d47aaf4775cf8f8c72de7f67338b0b81e8e15d746775cf802b0e2cae7c46ee6c81618573e47da6c89765e0ede0bc531c38f24a342878b31c467add434aeb529f539a73bedae1e0e8f31c4c797ef6871f856923f73f4370bea6b2ed4766a86dc76c271a4ef5a85801315542f5cfc5358d787e0e2bf82c4ed52924708abeb5fc3ff0647da6453afd9db7b69caf32b8d406ed7cdd7be451e24789243fd39c36eca290427b97d5292aa752536d01ac6c914ce767591cfc9d8b5ae922eb4ab6fed07f9a66ce9dedf06f9fcb90ec481898f7b26e190856fb4f7eb149b3a453a29bd1cbadd93486f65cbf6422a81d61a7d7fc7538a2a50eedd7be164ec9eac6c09f28da7bbddd3139cd33c1d9e0366c7d1aeab9d57ea34bb95b850b6e8e5e0007049e149cc0c1792af770889e521c275b78e3d06d8dfea4bef01b38a84fdc0f7d919309f36f0ff454104d68a80c30e984c2e4c042599cf7dede5d03990e0fa5306ccdb010964b208ecfce9019f724aeaee72059c4ce9baa7e1079aba7f6aa6d144a70ff3ca146f1e4ff3c78fca70acb57eb729f30f47c2ff1203c60b9413172d4e2c56a830a53029f9484a5ec79136ab553abd7988bf8deef6b0fb9a8081747710f752fabf09b8919f7e0dbbaf45538b9372d7e21c51e69f28d371ce28fcbb1fc8a52d2d55c61cbc171c33fc804c0e0177add59f824d74fd91d29631bf9429faa3fcd9d279c6f9e8eebe006efef6f5ad54d9afac7d979fe4821c8ef5995f0bb93e7fed00db21be71de1f26b781e34866ab6688987de7047252c437fffb3effaa209530a7a615f1f1cb94744ecd7612d693b033094afb74aa36364d9c9cb0028ace0bf3e7c8b7049d98a6750d7c8993c98511279389523a27ca8b329da6134a9d9356ea4cc04862e86c776744f79c90f020dc9ce2c934e9a4359cf3bbcfc4c4a63951eb9c93de6927ad3673d239e990c9761fedba1c7c45d8eeeeeea642777777a748157f184473d21899734ead4b62958439279d94d2395f60b2dd673211156189192c82b8601e314289cf87d259f9eb9ebbfbaca593d2693a99996cf7cd5abb6e250b4f27a5744e16c83418a1c4a70469e5da92f1fb9ee4b94f019a8025a0f41f972311030948b9e7ef724ffa418a71ff6e5ad5540c8185217858b9b4aedc194e1ef480a4846fd2393e47b2d9f1c7adce85a366390fc7b0fb2e9c5e0e1bb85c386a4fdf865b6ab7cddacd6adf324d9c98e0fa28ede88bc07b3e00e9c712dc3a02e97a4620a410dc292501ea6b407492bb24a58af6365a1bc19d7f82836fedcb9504f3ceedd6aa691aad74c25ca739bbaa892799684f54f04ddb9d4e451861a83b3a09d128e8234a031a00917238174b7cd3886f3e07c69973e90d4b88e52ae1200a6148018b752729c78750842566b03042c24e8af8a6c90adf3867673fb07df6e7ffdd1a3d9f3f579be5e4799aa7254a2b30c237ce2b653236246352e6bd26e5944842101993311713dd18c425bd942df649e1a88d2187be369452c58af556e97e67385da60ad30a42768a5fd793524ae99c63f8a6eda628aa817362d0ad3bdf4519be9177ee94b17925ece6e03bc47e8cbbd8300813b84847cdb7d1736a5ae3742ba85b8d435d4a413ef78901ac5bb65bd3158325b8e019755d679a05b8137527d89d648bfda9d9709457129f930d57f58811e38ea7eb9d3a525f1bf22055fc4d66da9c5c86ebdf7da692150f0f6ad4b873668412df15582ccb167adb489ce685ade348da8fdfb41894de194e2fa2738237b930e23ba1984c2856b6d49fe1c96442d142cb339a407bd64c2794d9752a68e2d484153ecdf914048109eeacd67e950cf3a9e9a40993ed3e5a6b37bb95d92aad4a2c7042adddef737e379b29efa55ffbaa105d77381221737f68fab5d8d67e55133b26bed1da4f7495493389f49a3e189860beaad569801af480619a2e350dd13f6bd3e6027dd428ef23964d1c2253678728a7d26d16e1195eb10cb32addbabbdb656608e344ac8f62b19850c35c35670d9bb386cd59c3e6ac6173f603c6913c47785dffd6e9140be13aeeb3880eeb4c9d1d1d1d1d1d9d228c93e36acbdaecacc6475cd4ae8e8bd8a8c8e572bdd8c5ccec834f1381b8b9fe41f8bb8dfbac31a38dd0629b6935169b1973ea10d9d8d808912ab373ef05e68919cff87373c6e170dc9c519939a333dd96b5d959ed071ebebbbb59d335e9a6f373c7ae993a3eb3d2add2cd099c8e89aebfd6030e0f5da72304070727c786b5df773a8d2f2355fc857cda6cce5c651f27e56ab8fe9565327368a8c3931e6a19cfa15a599d721957b18b3fd7d09dd99ccda27944579de3cf443c6bab4d3a43593039569bd36a53d4e6ecce5ac58d73e8fa8f74c788348fba9fe9d4090feff9ae5a35e3c9961d96a6d1ef81339a46d3681afd94d5aa56a76cfce94da7705ac52e0ca2981ae5a336d4a99165aeff3871aeb73ad553d629bffe9368b5b1f5d61df906a62f3f109367be664fa7609d62229dca9943985dffc9bfbd7ded250a9822bef00c619c6069353ce193c95fd386d0d3218c13acd9cc0de304ab45799d6039d1f2962d4d5d5a8700885d5b094b158b44d197d66369b792ca2ae9d23056dc7ec92ae93296aeff385333bc21049914a587378440240920ef382f4b548b2bbfe4cadfa44abad42fb4608923977fca943f772d6ed74a15faeee5e0cbf38280db70a5ca514b73576e7da3c5de2eb34ffad3dd75c8168e912d32a44a4f40aab4ef67803c0098836cb941aaa8a4c8172a341529331c59dc7e014496d49f369a07d8c5c183bd1c3b525a6ccf87f6a1eff8370703a44aff0c41a9d2413e92337ca324ba6d6bf0c5b4d82fa3c57eb1c51e5e8e94169bbd1c1eea902afd1c7e14be51be7434aadfdaef3bf50e1e3152a5bf99e0034c4012a0bf7548950d0a1ff76ce301273b65358d82dbb32780257cfd1e0ee1720c59bd0bc77939b6a79582da3b3884f40d86b351547697c6efd422d83b9c4c19b7d9adcf49f9c2a1064b8bf56bd83032aa4495a8f6d457e5a9ae5aa4eebc785c35ac9a96b6d266b49546d362906fc26ebd1366f2826e6690fab4d65a8714467b5aac2631f8c6ad751967a753f4c577b3e99424e9748a43ca435d2dd6967f7ad784383c615278c21a6bac316b53cb29b2e13847da849c23d16dc639d2748ee4da6665449b904cd3348db518ac512496cbfaccee0c3be762b96e883ac59d733e9d1a351dee9c6b76ad98d5b2b921620dc63e301913b1a6318c1122a2a21d9d4e51676de7e2a0eea48fba14a7c5fa2f2731d352f024867e8a4f1106f199d47cfde3d6d2ea9fe0d82ee93b1cbf2a74ebd67295940eab462dd6273dab5195a571b686a569a4d748da0c068a96365b9bcd2664cbd98ac8b6d6a6733757e7dc4664dbd9786efdaae174ce5159e7dcec9c733045b45b6b6bf5b6da7ab69b149b1022255b4e896ce16d45fa1212e95380329a06e9b79dcdc5b3bd606d026aa0969302d4705835ad9866345385b423bbb25ae78ec4eb8e1a12b13b5a194ba3fd26d429cd76aeb5663b2762732c0e12ae5bdfee68b673dbb92664350d47d3882041a391421823429a462269304684846423755d1fa8cfabc57a42819212502347d52f79e942c1130a9894a09abc7481c182ef44834f3a6aa4b35b9fca1c552d11be71ba64b79fa5c66a3e109793981441a4085dc681b8d4b44ad2b4e338aade38aa3e3769987fdeb4bc356d5aec3afb0c6e359d92b3a6c56a27ab450bcec6d13492ab5155b5f9f86d2e991c195fb9ea2486fefc1906b1ed38aa3e0d7de5a8fa405c50776b39c772d5c81676d58a6d03ad508b312e3ce16c8cfbf189b94be626ccc5d3e52a76610de67ab1c76d96e3bccd7a9b9ddee68ab17858ae62f15c9116757e385be408a2cf92948634992674c4d0adafcdec4752fab827e1bec4592e8431e233bdf0849b3a27dcdcd199f3081ece07c6884f8b9299e3d8e7a70897ab589cc5dec9d3446e7d16e00c11a375ad006788a0a1e6392f3662ae2a117295c9d7f7a229e339ae922e3bf5dd097980e9627fb98ff41b96abac4bfdd992719574a9f2a26ef523382dd6adcf4333277259a766648b906cf9e1d814d229e9baf5bb61de3fad922e2313c9918fc63e2aea214f47d7b1e780eebcd2ec48beda95689d89a743a64c1a6c5b6d2bd3b6da685edbca648a693873262b9a44d3e8d6df642421ce83e94e85ed3498865362a2f968381acca48485b468b3ad369a8d25731959a26d664868b6c98ee61142eec84547e8dcb1656efd9207336fff26245434333ada6448663a35b2c48486649f25e2e95473af4e917a3aa5c14a8eb0b1359d6adbb236f6460345efa55558b6b9f53bbeb151a162f67abd2ee771a4cd6a9576de5682802440fd7a8571368107b803d4d1229dcf913c3c0101df1ea44a75e2d6e925182330a922efd4845c65ad260481ea62711c55dfc5173e13b1be85e183cfd4856f9cae17ab636eb1846fece79ed47d83f26a355aac4fea9ed4695d6843b6e1fb3b4ed774491590239e20831b34b0bca0832fb8d49f2fa9eaa1852c1c41260d454df871a93f615225bbe0844c05376cb0c10d8cb8d49f31a9aa4448c2093056ce112a70a9ffc355a2a35cb5e23d705a878ae27bb9cace66add9223bd3342f9121b6583b050345ebe5fcd3299eae1f21764dd7e4993d333687266cfeb85e1c499b3e536812ddfad5b23892463d98e648dae4481a0a6f6d63b3a24e8dd675eb8bf698dea753273ceacf58ab6656f02e2aec8f6c71c56e6dc1adaf02640167340dfe3d04a1d917c35c751213c4c7303e22e401f4c23c61f20266ceb5f6f11aebd40ca245992232a57daa863dfcd062f3d022102df68937913b0e2e09c5edef57ab4e603ab7bf7d8a70e9ef9f26aec97d91c1c6dda5dfabab6688902f32b0ae1031b8fd6ce4aa1922607061a058f520b3dead43e7f6efe814026e7f8a263ed27f8eea1d57b14cb6c33217a8197c63ef70517f0bb9aa5b8e92824cf509a458e4255bf8bb24d33352ec1fbbe7f60efb40ff077bbbe767e6d2f36bde0bd7cb2f65a44a7f09ec15281dd53b48f8f8f2b790a8866fec1d96b9fcbd235bea6d996ce9ef8ebde3c30f1fc79e0e074cadb39e0e796dfd260236d8454802f47360116e878dd3627f8a307ca470fc2e6be1c8adcb33a5d00b3f47b58cabb84817a9456e57d7e4229a56eb6c160c142d19ef9ade99230abf768af0c07e845e433e422e8e447b6255078e44e90f8e442747a224bad32a922f42d42e8fe7eb9e527fc34adfffb2e58572bbbfa4f4791deb6fd9ac55ecd24f025670456c54334fbf0e9dea19339d6af5f7b790fe1f9690cdedb1716ef7b3acce8a8cba6f6e3791db957d18c631fee9fa3e76e8e011d3a3ab6aba340f1388dc7e205866d4b071a329a54758d586275586d84c58d5232c8dabb56a5a853972e448eb75f9ed6be78402de97be1496c00bd9e5287ee9d2ef325c13e0049c4c313d8c972f52da42075ff76f4f410f30eb76a0b9ee380bd687014a9707a5cb0419c6f1a0062775f8a1e2b9184debe183995d7af7e9399e72ae78ce596ad8e812fd19e1caec28f5706c57478b9a0df2999eb3dd0e297e23752f355af4192b9d8f1edd0cefc5f33c1cdb5d2995484858be92121393131ea4e86c90afe4395367837cdf73bc42ae30fdf0f143071af650a1a2c68a15263558e860e1bddcd0b163855fca3283a5c6ca29e6e4bdf0287d0b1be46bf15c0b90e439eeab85a308aed6023479b1d1a2ff8dee6a610dce4567837c2c9e3b41e9ac121fc9a3782f36a48a3f27e4e645b7e385f7a283c5c3e86c908f3e57a3458bd7341b2dc21b313a1be44bf1313c1cdbdde1f5d1e7fe41300c5966dce0bdac68da0c96166f98329426b447b3a851fe37cc59348d6eb86174d89c2ee44346d44eb145fa292937a4fccb167615f8fe2994fe0d94ce09364dcfecb82a868a5daab0d36a71e2e2514ec01417a07883f7d2622765cecc150d5352aa6033690853b40353b4d3a2a473d29dcb511c009072438842a14010041f858a01e3050aeac4450b140a7542b1588152614a6152f291943c140a85ea0c503a8999e1f2e2eb1df2220cc2042ea28327311f7b202e30c0931818ffe25f8441b890a3fc613c1017e9bdf07098a394f88a5c3057f18efb0e6c47001e0ed4e59d165d85eb1b3d09233777e49d29d4291661f35f902c04522e2f42e9a87e1b4e024f200b7005a802348129401370acf1f9833ecc1b039c21626418cd05c119225e7c08ce10f122ec7e82a84b432b7e7d8d9a3e4d735a593a35cea2eb3050b45caac3e611ccc7e8c7655ec4f2f7d83649433ef3af4cb44d125d6d93644465b6490ac114eb3087b50ac6fb37d3a9b159d7632f7e62fc8b3fe1f1cf5706fe376b1681204bd3a8ffe08ca6513f06b8d234eabf0065340dd58bf777233f9a32ab8601ca46c946b9ffa96dae8f0cdbc1b1cb32abeb4492a70a34249d9d2ee2357cf5f281c57e849c3e95e177bad329928b875f46d74f706c97b471a1d8e244d98a88584aa7b43689235a43982222f569adb488481191221d6a81b8681a5b70a186545052cac25f5a2981a2a3fc3df01de5e1c862f2826f3c5d717b26d5d315593a94ef3e7c0e4c09515af49701ae9ca0f8e44f16db62937afe80bb5b9bdcddccdcccdccccccccdcccdccccccddccccccddcdccdcddccdcccdcccccccddcdccdcddccdcccdcccccccddcdccdcddccdcccdcccccccdccddcddccccddcdcccdccdccdb3bbbbbb5987bb99bbb9b999b9ebeeee6ee66e9edddddd2c3633e624b2e16ee66e6e6e66e6eeeeeeee018787aec3e16e764ab9296d6e6e66e69edddecdccccdcdddeddb5bb56d664eee621ab95911a233547474343b3198b4475b35ceb83e84375ba5999c162e3c60e1e31afebff430a3d3d9f4a934235e5656cfc2ba4df9e9f9f07d9c297ff87945d66691afe1b38a369f85b70a569380d7f9656d5e7b7d129fafc37f875f0d040099a50fcb243ae39b937bb481827622dca99f774576e6197c304a684231970497cfef27a383ea9e22ee3870f19ad52213483a5c60d1d3c56c4aebf0e2ee3dbeb135d052351fe58b8fe40d76513d7b36e79a07949fb9fe45c7e966915bd89e9c186ebefa35538b8f8eb7074fd77d871719e22b2ebff43a7562c2167729fec0baeffbb805e182856b679ba804a29a5eb6db8cbc5ae8635cc6f6e6e6e6e1ce5d67edfe9e47226448810214c9b6933a7cdbc31b29a3673ae562b9654915768b6ca63ce9d25e21b5b060787c8cab2868ae2d3268eab662c8663349b5d97e994bcaf4e5166592df6533363dd487819a9315263a4c6888d549144e6d0d0c8b2e9c347734876343427d1d1d111a9db66b3d96cc63e8dccc484ae6f3d736675fd67b2c5afca57aca396754af2200105573e2108b3a68687664d6ba8664ea2a1a121225f396b88ce7bbca7679df7386ce63dddec059586af1fc6895987b3cb71d7b2dd4b1dc23861e4377ee3377ee33740f01d2398b8feb448a738081667f2f074374fbf7a9aa7a7a7c7a7775c358be6911df28dddea56df586d5a6dcea156cb5501428ef22bbd9721256fa34fb8f08d5cf246ae6a5965a119b24fa37e8cf8467bb4d1746a2cf170ba0eb3479df21e7f81e11bfdf5a7152e0211f05eea47c07ba12f814e02de4b7d09782fd442e1e39640077a3950ecd9277cf4432f47e8e140edd0223fc7626226c015f0706c7705acc51a7cec13932afea71ea84eadb872484949c8299d7455f37c2e7f39cadfdad333c3706cd76747dfd8b9fc4581cee4e6eb0acc64dd15e84c76f0f597bce94dbe3b2b74ddc66c0d5a880968fa1cd55285bf71a40a733d7a8816e82ce0bd788fc304e0f160d8122f151930ef9908c380101d0bd232eb84af61a555bb4a9bfef2015c95c65392511a456b8df0d11f19c673fd5132dfd819c15cb5d2566b692dad4543ada5b57c989498849d156e49281dc5c4f5e732d0c94ea16c13be91617cfd87a398f9535a74fa2378922af45219577db66564a45495bcec84905d14a4aa24ec8c1ce56ff2e9e07a281d15e4fa5b267c636780ceba3e998ca5cdaacd8a9252417146878ae2d34a2b57790e0c76bd494f37dbf2b963e773fd4fb4871a7932de8c97c2c27bd13cc775649e3363861388d0225991ac4856345342aa3a97a3644bd31f4bc6edf6cc552ef3cee52a978244f9c76230d8eb35fa8b85c55ffe6ad8f6ba2577f45727fd7559dcb173f94baae8f8ebd52abc1ca84b437fb5d86391f8467f9d66509a86bf5dc267faf1749205e0d91d4f56902dbc922a353c11e8b323cb0f318a4f4cc337facb5f9e3ae9162f07ead603781d6edda7806e6498c9e4d336428b848f861d4b80d7b08eb9a85880e9c60b8e1c3a52989430a0015f0e243b4a5ec7916036eb8007f0d02a04e88c800462264001af400f0b5c0003ed23031ad800cb19a592927e2fad609d917746b43b72e75ab9b472a1a5954c5662d15aabd526a59ac9fb97569d1a3ba3965d8f95fca4f81293e9fd4b34b2854b2b15a614253c6b55095f137eb1ab51fedd51a78c7cec467f5defd4d8b9ae37ecfa7b32d5d5298ea753f3d503eb143bc1e68e3dd403f357a7248f136a6856acaea66b754aa2a4f04c17cf24e2a1b5d2229e229ea29e5205526afe020a380985a10b85278a68d728fa122559aa5a48140f93438b22552b27b1cf06f97018f2a1fc2046871fc22042966991b91d3c484f79b5c209823c6aabb5b55a2581cc027985aa810d1e28187ce3caead2ef416d7027dccb18204419b4f3e8caa52f607607ebb3d576744a7bea2b36786c33586adc08f2f5f0f143b6744f5f87ab78e5a8193205441640a68258912dde535ef90391a9d2d3a7246089fcc01328a5389ab48dc4759e57f2da82230f2cd30c9368caa619e6cc9b33771f815ccd6a5e25d1d9b9d6799e0e792f57eaace70079f972db56aab7e32225a074947783bde3bd78c8391c10edb2dc37b759ad724ec90fcfeec4b9f3e64e6e356adec93434d27d7afbdbe814e796331151962293e1b529378f18149f4f9f5bade27e4e16ba11329f89cce79c2934e797bc8e44fac933e773a00465306b3cadacbc66d498f3b98665fe0f40cccc1f3af818baf3f288e994f673b31a1a8c38e54e31653661070d46ee8e062334183d21d39deeec4e4ff8fcbbdf7fd60de17cc4660a5c0e3ff0ed1fd47571e58bae629c46396dc66122741429edd6d1d93162d3a9c8d67a09d9ecf41e3e365b5bd57d11cee53df72af5907ce9fd4d6cf491943c8e7be78ea252a328f6b0c6feb9dc34427888e4ac906a50a1c81de58b6980d8563aecc043059debdf03ddacbce291994a51e8572aace891997eae9bd29e99a1a91159c6b6c7123aad34ca5d3d5cd5323d53633bf4d41e3c74aa6978c4c468683ac70616d3a193fba6896c1e0ea793ed119dacb15add4dd334311917acd542faa7611dab3b158cd9d1289fe3a9078d0e3d04d1231b2a3afa2123a2d388481af1ccf5bad159eb8b85e0dcb0363ae9e41e1ebda3b9d60ac214dddce09c54a41557914259575e2482516cd73426a12074a2e9d5280f4f6acf8b9947537d57b18b0f8a109f984ca635d49a09b9fefc4367b38c8b6cb7e86c8d87ceae743615d9877d84bc56ad1f2119114e4b48519599a1a99379a44fcac8ad9a1522d77facc173fd45eab23bce70b98ceeb5954e9c21d46db26a0f3b085d7f20627cfcd061a3375c4f372676e514ddd4ca5c4198a29b1b1c1e675cc95346d728c7a1075f25f289dde7634b84205941b9a828be51f2300cc60c639f17d9fd8387abb8b9e88608e7faebd861994e9ef96b1e4cb3a5b3b2cc0c651577c343322eea9bc6e1a356915634dc8e1b3da32c8a59f9b8d9a175b84712f9c47ab4a572680947b78e2cd77188300e66ad8afa37fb74b3747a2fcd3c27ada3a8e6c60b2fc15554c3dd5c5453545364332fcbdb20134c11bd1789606696937faeffe7dbed48726c900f87218ff2836f3c8539de87fc945076386fe808bbf06b51860d1e1daa1584d65adbbacfa443f8a81aa060c02dff1e7e0022884ed1d7c21cf25679abf6d644e7d75a5f7ad576465f8a2a7c6de0f899a3b0068f73c80b7652252806c575c13bf8240f10d1d5aee4099a5dd9e2dfefc91669afe409925d131bc1b9f28576ae69c57bd6307465cfd1cc0057f6ac41e6f68eb5df773afd8b62cbacb094f15077e8aeb5f60b8481a2c52efcaecdac5c9b32a9839919e41dce91790fddd19728e2111cad7feae4b83701076772ad7ce4c811318a4fc651fdd67e5f3f7b39b6db386bf8f88e526624b3dd6e1a99163be522091fc36432a2d84cabd3b5e9f672b34345f1cdf7afcf3257318d8e4ea9a68be469c2cded1d3cb81f3a459f23516d4e9a1366d1bc30a1687af5888d33d7ca20cc111b9bcb913a318a1da494da0e3c689aa6699aa6693b049fa5a8f4ab994c7a12a21918000000200083160020280c0a854382e170180782103f14800b7faa585a38990b43410ea4200a82308831c820600800c4004690a9a1991ba474d9de1570df61447f2e8f712cc65563673ac58eead7a0865119d170a7a3dd2eec22d7b01ab0db64b8d33042d21c0fd2a5c43d08e4b52917edc5c4c71fa030e8e0906acab34d6695f1aa835c416267f17a6f75b613e124cb7c9d880dab02fb0f1bc69856be9125e6cf79e4f5928f69e27e8ea24af4f2fbd9922eb7bb7d059befdb4177add9a3e3c487501489cb447fe74de8e5112716e89f331f02af488bcb2735274b398b842b83aeaf9ff02de6432bc2ee6351584f6a81a85e44747ea4111af615ae15ca02474927de38a19b6bf3742d07c77a1a7d0a81976112545bf0738dbbefe512e4e162b239184ac07e115993167c841d84b0433dddb1a7d844d3497c3c4ec1110b2c2c391a720826e90d21f271d12dad2732f39e76180b2aafcb13a3c1e6ef0c045ffc0c46a090f2205e7147770bc2d99051ad28695a0f471e2805150b1f19501cdc4f05610b29d3cb8f366df59bcab1111936de977ecb4df2d59ceb9e86daefa090c87cfa284048882c33fc62efcc35b1e7e50ea832e93251b64dddcbd3c733444d68c48e24b8bd122cd11f85fc18ac37d0df9ff93eeee94fd77fe29e94b417a1c61f6cf05219d6c74ecea22d1ba56ccf7783f3340545f7291c472fec9b61df514220e2a4fdc3f2d39b1eac5f0132067c7d6c784a3f1aebf251a1e7241e0c461875ee0a9985bba0eb3f0d46dc2d21d919124e166cc0a4d90195dd07f7e747485ef002ef7c55c0fd6a4c2bfd95e0f1284b15cd4b6cb2049988e6796f39211d75d7eb100b2e0c203dde4c4206c6a57b7a7dba018c5e3f24bfbee2e57eff721dd54b25d2cefd000615adaaa787c131528bc8e26fcaa6041f8b2d10570bb6c62458be18510a15d2e12a0313cba68fe48e147ff68da8ab1622ecb0da0f9036f07f79aa2a0893a2d4f2ce3f904c6aaf6b8f928b5d79ed06fd2826f1fd43a8253641a59d8dbbc4a1f6666704e4b8cb326a752a015378a4c3ba2cd36d3eccd816bec20fee65666160335265d6f3b428eb6e439d7be303f68105fed085b56df4ca671120b8c23aecb8bb57bb8a09cd1b0305c72d9a8c273e2fc162bb57bfda60f208064e9dd2d8ed21853b0a3be41c844e83711b2a852496519c8b622b1d8d12264546112df9b19636a2cdedde10b2a041df5c5fdc7fa74ea513e73fedb186eee79fc1fefee30e61b06d167a2c9cba1a82debbaa06ce4c4e0fd730e002b107665c4db0c2469b54afdd35c8e900d4c62a19787c45ece87fa0a723e1dd704e99246c3d480d8ec30a99a0f5533b9e7674cda7262b66ef0f0795098dc89f4e224777e11df44ee7c456699ee90115b3f8c9fa132ef258f52ea95d003535a329ae633861cd22aea709ce6e8056d8ee1dbd17ef4a01cb15d8eb10528f8583b907b21f164098c59d555eb5bbd9ea5a074a7c5f0c6bce3656dc5afe433ecabeedadcc4e08e972bf1d3eb659983adb38cd72236abe2841adacc8f9d574ce5eee73e5df33b82fa52cd7cc3a7c05c1ab5830e84fcb303ecd48c9f6f9910906fc0acc0484c7f1392a6d7b900b9699e619244e7dbf0b202dcbf6597c8d89b7098b3bccb10045fa94918aacda27cffb89f81b9906cabd7297c502f306a88fe144cbfecfae1c13c8c7aff40447b1a9ed3134f4b24c0b01ea40d09922d357d0758660d6e70a4c262f24e754ce365932add1025bc66ac5bcfda6fff553e4b3325e62d89810b7b406b7fe424c6d46d21646c1b2e09a2a0962ae56feec1eca3abad3dfc35ed919850e486665d54d05f266af91fde8d01a29212e11aead84fcafa6e92d356510feedea2587829bb1331cd826ec07b530fed7da496ae7133582ca3fd6256aac8ac843eba55f6d046318d31e966e693e0fe70efa2bc36b76e09d078745d23cde7913d30e6c6d68759199cbc18489c9379d6346317dd84d34482558edfa98711fb3e9f692f6e8c2c11abd3365dfa06ba036062973e9f9d653e7063376a2d8bedb1b3d0b4d36a8af8c6de6c93d8c9b40bf6e9d334a9ed675afa6111c6119660371ec697a25fed859101c3311fc2bce4aea7f62db9f4420e687263be3fc47fc25b12cbd7c472b52e09ac60012506227971fd1f2a50dba18f0cdfbb516badfc4b589c1c1a9e19b3a2addbf7ce0f6a79788cbe0770fb3d5d2dcb78c9cd34f49493914e7d5bf21a1072f0e688f38ece56191bfeac5991061539fb526e5098affea0d245862b714054979b24bf88bec178ec039da5a9f543013363ddce2b9ff89c0b1887164b1730f5d3cd0e6cbf5528aedb7baafbe71f497000f883b22ce9a6dd2c8e0612fae3c099ea7280e9d5f88fd20da270f12daaae8e1f2fce8beb65fac9c7f31fe765cbfcf2d802befa3cf1b4f7beb3fbd62ba2e865cab2a68d1fab72eac8d3255207deb10dfd4b924dd3ed166f17a4027770d24597e9b87340beffcf88619240547b49621a25f5ab20acc009a999955ccc700574e753b2d0a99d12d6f0e84a42b063d8c7b4aa91f8d82088571963be4567d0254619a82b95c3b25244ca4bb61c40e58216b6f10bbe57b2c47c2523f60a0f52e1a0416b88e7a6f048500903ccf9f6a3c0d784b0739cdc08de25c8970d8347416effcd59872a27559fb05dd37e4026d563c07eb342839fe5c22900587a8ed2b7a95445dc1937639d631c5d7fcf94096b4b148c195462ee009e3266e801064d7cc5a9659844fab2da04760ec7cb6293713767fae3d2c17e638d0e08f1bbab40f3b075a4ee1120e5f549bbdde805c4d743671bb2a5b56b321a8e05f7c7dd818e1c8393dfa1623ed661e0b13d5e49aed50f9e6c809c004a61dbbee88a4aa48d2ca00351712387bfaddea8c1bf94f26020c4a87f9b887b0f23735c7c02c96d83ea7eeeb9b10579638a43913c0d0e09b4e8bd61defe80b67bd376d6a0b00350a844bf6bab2e8fe14dad532a1bd141e7cc963caefa0430106b8c8ac0fe5ee1fc195e27cbc757a6b927163267fffe1ca309d2c1bd7fdf9c7fa688ff81c1a60b38e57efdd509e41aec58504627aa963316ec8a49f72749d273407c1166692b99924104d316f1e1d51a9257ace9b7ba32fbc9d64e18f0dcfc0c2ed8c19acd1db07ba333af1f7e530aabf8118d987b62a06de94aea292d0f61d9fc7a4df9017b280919fad59f126bd4a03ae295df22539e04e31e912b42ca8a99696d2d9ae4678ed2224c00155d781edea4f4adf68ba86df5f2142b72d42e553e989121861c83adf10b9072d73c76688583ce64162e9a78c0c53185a8f0ac67fab2485d5b097dadcf1ca722ed354e9558d90be1d48d38036c20d08f8099a56e7d3e9fad788eb84431f5841da5f23f6381c31b2ddd7ea31d179d5958efcec6a2581ba35db2f9955770f37a678e763ca0be92098b1df277f0438e06c1479c4c69672aa02669990b58ba2145b88f638385773fee780689ea8c9b6717333ad72d04d360c2149c6387aa250091b256d60ad22d1392d1a00ba44d4af005c78310185953e1f523fb9120b009c37037e3818082d56e615b332329bb4223ad3755dd8cb208a9888611a1851adbaf4e448f6c45073cd81be1b8259a2ffa6064faa2ce1ca7c4037757191b308611c52733930188cc353394f3a18e08d7b4b631ef12b73ed6a7f707a82b8f80dad40f2898f8f8cc4b27390cd8ad49ca91550d6153953438dcbae53995adb51e8a35039c4e6e4844d325296f7753e2bc0ce1472ac7044130075abedc1e071792e3beb702e6f17ef51fbe8ce3bffbb52a8f0c8fc8e8c8cbd63f893e8254896b493f59f51b123c168b4cbc78e464688e6915b493c26f63dbfc724a0d0034c828ecb5b37896befa342d706d89ce567e44f673a3548305d7ffe0d8840abfd909f2cb20de02afff1fe4ff9643ae813966dfc15ef9621a763684ab625af9ca0af9f4e07e4938473d7673a29b2e1a5a13ca46124bc083631f833f8463da37e5f4ff3741085880d23b415fcfce63172b6613cfdd4af6f4d5cb76941be65e08e96f8218868a4bfe9840fcd363dd5c0e458823e6a5ea7c387ee8acbdbcf3c186d7e6c3987993fb6aa0ae890e44a4f3ef5eb7785f1996ead5dfe3a4a148df99bc46bfd9ad22961ae50f1c06cd56cf5666b4ffc417ec63064084934c511dc37f7cded26e694479d30aaacfacf3da970dc6f4e151ee6fdfda060bf53e0783f01bfa3ba26bcc6eb7a8a29a15cbd35b313445020fe570941709c348e23037c85b2c2896846260e44ec320f115f5936a0c2151eaab1035c8aecae92f4f6fa59761641786cc67b0dcb104bf53f0b4180be59194e215e5551dac4ea18d9a36a09be4a84200d38131ab956e80b240240572930e36bc822a22aef42149edd429a63f3758a05e8c1966fa5bfba31e4ffbf0d93fb14f62010101e56ab2d86bc9710983d16add2437b7f05560a5e110ef990424c9b625e65ea9fb11e0064dac8c5c6e4832a6d530d3ab72235603a81a2fae216b351f6aca0bc314ed15b25cf0b77dfb3c4fa253002844a0073940b9214ee8aef6569d5d67f5e0e816a0d90d802433f701ee2f9f67d78a1b1ca1c04c2495bb7e38ed99004f495c50ce0c7a1a2fac0b0030c5c333d426ab1340515a0f6f94485078b701d98abc357c835f925ea707e4335b9d2aae54b552f50aa50d5df101937103f46f445fef840ec1237f834d1ed2f8ce20672f02b901b203c52965f06fc7ca723289a8c31097c808d86d0b22f933369ce50ec7b3dd420fa58a828a0ba8d25e79b15c23eeca0aa0a68bafbc03986114e8252200897b0ef8dbca409f9ee9a129fe34b7f7f2462088300b1f720905aade543c7e066c8232527ea2027aa79aad7f5d06b28404b846d034ccd5ac33f78abaa7549fcc1f12b2c2076308ec03ba77a227a93ff6f514e4134002f93866942110f1c54108a1e6bcbaa16a81ac74019babb204046ded090a2038c9e7c36bd30309f00135bac462e5bb2a130e288116d3a41fccfb9bf896865bb0d84bf0595a3c049ac5d09bbffff08629f595b7d12a7c64557ffccc6d26e7253bb3e280938325d82ebf3f7c82d144a2d58bcf41a7392308198847600f71c44bc40fa6adc39ce866f19650c5d3440ea33fc8f383bff83787638679579b064e56aea8e54a200cd3b1dfe3ef6a532ee8f03504bde045e1103ea9617c6c13a6be76ab8f24c75524bd5e8485aa5f85fae3c951664fc5b2a9f9066acc631070ffcf2b564efd6a6e590ecf04a6510d1e66acdc85c0a0b32cc26ce26249bcfe563480abd27cabe87a35a22f0dd52e3d437f364dd81af24c165e6dd67d06ad94451362ff8a6c8927b2675980f4677b27be22a4b627372d9fe97b166af0cdd6d7af423f21cc149162d2d8f9867c6423754e2eb6fdcec52945c8c019656b9a643d0c7c1f5c238da84d9be7f86901a19982211138592c188b5424393c18c150e9b0ed0ceaaafddbf46fa78876e40dd2faefd4d1802dae09728cb1cda25367fb62c183052d099113357f412b22d414c8acefca8e0badb2ee879b1937e43f0e2b8b273d459d7e0331674303a282d6eac521b4eaa213d0981dbabc074638b0d09887942a5a6b8e8ed20666003ba9ec44a66bcac98f49f9e93c4cc9eb64792c19b77c28e5c2e2317151fe5cbe7c50d99af9d91e6a2b5676fdda05508ecf05a3f028ccaf3d8fc1f6736af83ad123e3da286dff38ab4670d22532fe568764b1cc9cb598b5c62615c6e4493cbec7b89c86d90eef557804ea74029dd236ee4376f4c4c73c1238c4549c9d2d53fb662ee4e01dab88bf9f8206f4ed91d28ef0ff6c9164730dfb2e842deb592a4d74d76e00a9ef4112ca77eb4ef096203cce58f74dda93aab9e5695a51ae8541cca69aabb5bf2d6276b8b6b2a8665854c0d26497497941121888f5a3ec8670b53abaa99c5fa4829c5446413cf1017815ac2e458d8912e64a47b5c79c40d989786d436962fecdecd06cb6b7c9faa956af806b8546f96f6b8369a457b2419463f88365b06e0426d68908dc1e01880306ef6640844e5394c7e201f14edd6e98d0cee04741d2b2a16d63db8a6823a2c6f0d82ec78d00931d8d489d670d12fead347b4e768653ee9bea2cd10b5a323e8cab0d20e5df77ce11cb835b96480269287f59e7e21b9be8d12247b6edb09dfb63d5f43b6ed82252a10cdb546572bcc2fd702334a3458cb4f1d2420e5444307a332a6475ca5bcf0b69d744cc504a53b917bd50e0e8875643b655e99f95e4e17bd3e4978788312aac4259e264fe39a250d310a15a5a660f32c398535d9462bf3e0489491cdeaff30767304b8be111d092b90b92e68e3ba9f708f33526dfc9fd13d48d9344d943034e61c08eb219995ad85d9dc39893e8786815123b45316dad57142de563f7e75a32c766ac7347aa51e8ba7f6013d83d19a0786675bcb85035da7a9e3ab53c349906265ae24d8bf61e9b25f1ea60279c6d5239540427bf6351e30761503581383d3ec3f8f5d351388131ddb7a49b6e3a780a14f64112c82f1caddddd2728e65d41bacd12d96b60867f73486f6fb6e6d2d1795809cc8dbb1d2326e5dad1e007a52a7f1cc1f577bfe72436c543205d3cca014588095b700ed825fbfdaef89fbadefdae6080d9440135ca9c976ace0546520eb44125c11ebc894cf2efb6a1f65f618bf6c35d7d1afb4286902acb48888a5e71d1b6749fb1c9357f4d854502dd2d21d10d79a4b8996ee3b5c9dc3bea7b439ed7984f0748b072d98ef1e5ee990d331139e40cae34c135672dd1c8742912375797537fe5e1cc7325a4d814d5afabad55d833c16d5c70ca6192e760712cb30fafe7603dbea8e44af3b83682bc3409e6f030793a166b9327bda8b10c76cb4029baee51b6c9e98d2272569204863505cc12d5fb6a77438e35ee1269b52305781f72f928c85a55ab061b11097aa3c86340c5f3ab54120916851cec0c6c32ecce00d68cb7869a99ce6b54097bc1aac75c2856bdd38e607caa90c2ee68152febfa30aabeff711eefcacb29004d32dd7f79e37a72b38a725ca9dda2a5843d231eb4bc1b1b1f1750df01b0260e38855f8f37aa6554bcc999f3014fc68e891a2d55de73a58abd9f78e0968eaf26c87e1bd836fbcc85ff97c08c5b2d5337349cb2eacdd47395faaca2332b92eb813e8a8a4622f5a364ed59f41c4109eb69696579da8dd7e476cf88c6cfb8f99a2327ba25dc4e0e9d0714f74087157e3d8ff361301ace9c4404fca902edb68d1a0e8e150576bf27a9bc5da0f39d189c30bcff03958aea9d0ced90bb00165f390316bd7686f383a059c82ddaaa1e2af2687aa215584fedac1a62b0f5e294e01f7e804928296c23d4a8d6175fb47d948d82a5e2f7d8aa137744795c344e0f399ea5e895929d21043acb861c39107203aeb1fd0f52781be5bf073da1bc94a78783460668c627f299412fd804e766be2d47dcf2f74ab68424239ba8e1974ddb3c0e0e8989e513385cb7b2d2ede1eddcb993f67150d805ec757085d69ef7b8acbf60949df01e220cd6821e8a6282e02f66a749bf03bc0b5600f3aab1dcf7c3d86c51228b1d3efa183dbe9597488cedeb69d56bbfc9a99e15c13371fd0302c100fb4f2cbd2ae655bd97645d59a886339657f5c3192ca051c77bffae51b9c8adddd51ca371532c688fb2b31795592fc6779b01385e49a6ad3874d955e9803bf2d9a4bb9988747e6d3b15c3a24e16eb726f8d3ea7c2343a07caa7ccef095baa2f2a898f134ced73a464363f2ac36490080f38b0bcac8431d900efab35fe52a5abd291fcbee9effd5163f97931ee3678d6b31de91eebd1ee53afa88ac4e02b9c1898de330b37f29c86c19d017a57f2a6a3a119dc55d145efcfad68ce0066aa5aa7d0759fc8099307b1ce057864d4e3e0b8a40c7d063aaf9c9b748286c2ce399b353abb02f41be5b803920132b037bc1c31bb50079f3eb0bd5338fb1862561ea087346ff802976b184b3343fb148175a6e126cd594ffe428c50bc3876f0d75fdf1083096a75497673b193d46a2b60ef5577e908632da8c07780b153d548dc51e4b8525d6e96bf0340823be3bb94435b4100db650494fa3e313a97cf2a459def232b858c23bcb9e0424aa5d41c417dac905e1840911ef981bf8e6f1bd99e2bbcbb707c67ebd94a8c9b86a720975e91484b05f1707cc27e39d726365c6f80c537efd90819a1906f56e6b7acd4ef40aedc48f2396ca87ba26ca807b62df071e21d93793bac83d660168d4e5662b36fd872749385a0fa155380c93781850e5b6d1ee6bc18466a1f770c36d1107b15793c4625805bffcf04224d822bbdd152451163081467c780076e8b1efd899ed16814a5430810099764e8d95d1ccb7c51df833c0fffba1f8623c7fec7b4155af770806c4640abf82fa0535ae1a57b1364ece314457dc7145ae458f1860493b37f87c1355611f989f0c0d3c1a45aac3e312c38d4ea1ded12a1aae6288d0b0f0b1c6137de03cd5312eacf0a5c5aea4326a6993ad5cd82e20c5d487950b8b6a82be580fd9bcbfb11b89532ad98726b8dc39c9819672e46bdf687dd4e7bf7b81a2d33c199e3aaba06966efa95d7d0ea39a3595dd506cd5e4a5725c4711318ecc60826155cbf87685471dac3ba09ece7cd45e84ee716613170df318115d579ca66db9a246e1a87c6ff171e7681a50454745614cd119c2d286598b426fdb7ebd7f6777911c1993c51882465add8aa611e9abe9c3b0a147fcde0a7c5172a53497ec4ed94d506bceb35c80c7cb2b4d4ac202929e22b20e382e2183e0ba8afed760b271d426383f92e85983167c383a3a28c2288b928fde684349080c2b05cc2b5a8003700f5f475abda23e8110955171fe984a7be1648932223f9c08dd5051427d4059fd5a12fb959f1a880551614fd3b142f5ca1bf3f4bb7e4e34b39c146036145da3c372e4c2d4cb6716840a3a5d39b61f4493de76d820f7abce007e568cee55f747aad19d62afd2e5a7e8d412b5e23fa673f3f4ef738e183b6003ac9d14c462f87ad56d0a4940af8f98d546a9871f2a383d38c2339afad2ebc353a9dfbef88f2e7253e3136e796b2f49f2e3d2e415310abdaae2c323aa5ea0a7605d6943fe50d3a41e1b0ab0ce13b591e80094eb2aff919275f160dfb38089b06f3cc103d4a1c4b54ca909281ac0732445af9a3eaaffe2798e46e7ac7ffd4825d18e5859130d38d54b1908f919ac28010c3fb5e5d0211fe2fd73480ede408601d00181b1d4539c0344ed4d14d214b861ed987990a6938ba32faec84cb378a5fe36cde9fbb1f0665151a2492f638b3375ad957ec0032f901e36a5baef216d9a576cd8354d5e463dfca514f294752a4e6ebb8691103b766ca6539a209c1acdc8d6e37e460e80d3e500a0525361afbfab2f2a8631f7671808db0f72b38129f084917392588b1c4ff96d059e466d41cf1040fcf18bceb07b1720b7f1f7dddcc2e7e29888ed8114ee58dc6933dfd303b71572a0b785e205a029088e470972c45809e0313cdadec12bc5e8687cf33194b119b989a171c7108aebafad0b3b4539affcaada68be4e134b412fa4efa518e846da07070eb81a0800a3c02a1cf24a482295dbb0b9f3f516b048c0d08e7f3e00633aef18951c7ab010168c11adf17d288cc09afd0afc06e100edc0184720ccd7c1287f268d2f04bad421b93e35bedc4da7933bf1ff9ee3ce486615ad14c614d4c2f9ed92cc9cabd3e2085799429013c51c2cfa28c09a4669d08753e0c3e58533fcb5de13564f9127736d1057fff27a3da83e2bd50542c30152cdb1022fa4c29e2ee7053535e8f3f25d541e68533d9ca98c1849762c0a511e69f4804c919c3206a34d5c0c731eb9fba8fb6c857389a3a2588c20083d1b144f242e035b121504d5a5bf3f1162acfa30c312c3bf7ca2efe531d889d20c99413400bc50038db2a4c235480a56b11cb0b490da72465580b9c8a9c89ef71d971a4db8c5456859d77cb17ac09e74c28c353b394caf32c781872f21420e460367653a9ee2d59aeaa646755fd7d5fb298474422c15b5aebe68300f383184bd8dcc3451cba5745d44b45e6ff8f61edf20dae6c984ab43a4c647c4c86fd58be093d04b4e92514bf07a528b8eb698b65062ee9c45a656c1f9453627e7b6e099b7fd214aebf36b465ebbe1e6045e4b47ea612f12036b1f0a01a4325b0d8991a0c6229612143fdfd7b979f7de90da23b890b8c140a6c9ad0c7d9ba9b7536b5de77e12b504ff598006032f15f1aec66aa56669be43996b606b8fc4cc7b421797ec9e7b71219589a5ae3ffbf6176572afa56d7a76909d4fec80ff882c5028edc4f8a6399ed57d6a788327aa7bf437d30b408d1ebfbe3885dc1ae62875cc349490a21af66f189b472907f052698ad9ac149384c641d397890cf256820df8a26318c22f92345c94e9de12e27c70c8a3fc6204f59440ae0fcc6f349e60d85540fa9d44514d49c334dd30304fa23a45fd1fa2e1f2c1c9b1e1a3e984ba8629b83671ceadaa8993fa5186298c025bca12012ae63453b8f7672cab759641c32eff7115f1cbb953b36d267402188d654823335deb89e45307aeec44f548d416771ffa5bf709c855a6872288e1836f657dc97f343eec61965ed4197610858528d489197e017d02a9c035f7c766ffb5e31a27734ae4b9e5a554aa62574e8c1a8581be640068f5921c1eb3e4690904902743d49451ce92dd8da0765e0857b099011bc1aa095d43b61d83f665d3835c408b44da3a2fc1b2d53514f43c84640e91e3bef92e13ab0b4e0e29d1e1b3b2b3e6044fde40a3d29a8f8959f3ab32a91b397386a821b162216229958c4aabe35f6d5e58c1936abacdb9fa4033e414c59b39af1e68369d3cce31a862e36134b5f90c80de8edef04f761675e543b102fec99ab52e79120619976e7e19cc9a5c949eddcdf9f5401ac814e56d73be1ee806993cce3d5805f36064def456fe805e4dac997ac321addac7af949efae0566f0ee3e430974dda9501f15ac5a647fc406f7b5b3348a39ca2b891c3d4027dab93d35c0655e253ad316cc8e6aad922c2b3c7e3f173a047f11f5bb75e704c5b4855b3d214c7dcc4cb57a3d2ddb5ebf44720021f17cdaae4181bc2d362cff0b5f64694ab85a1ac0190240014b00b106b90a576d607c7b485f4472b4d71cc4dfdfc352e9dc3e9af4d951c1fe589bc690e3d992cbf0f1d6abfb1bdea8163d44a4a9a9503318c211e7cd868d7b95397101c7dd34fbbb49adb0a87c0e1921803af9907a8b32aec70a19c7d7a7fcfaa650d78194fdcda5ffe5566c90c732f1cf78d71394624918e667cd73920eb0a4b6dea6b97c2f18896a288d67c7643ceb7d8ef3a7f1d50ccd27a71eb7b6b7e7e2f46ec50245b130f5ff89f1538408482f145bd7e89ed66b43448e3bc587cc1c167b9bab0d34412b5f0df79448eef0e2d281ba671083d3a3c13d01db626dcf323b4f46078ac406e6247d6a146946cd58f2079b1a73e5ef79538891ca7b63969385b67889d83d25dd95b9f4939fdd3197c1ae70ec63b5d67d067f9262ffb546cacd191cdd0b77cec384b2643df227698f52d735bd40a535b3278d89cc1f55bc0044486c83dd465115bf0b9a7a72bf0bd64b092629cd8a46ee5152fd48ad9e2013c5045e02d8ad05f0dca5f6c9130d32b32e859decd688fb0f0ca1eb3b3db4a96877b7770909a850845ad3d0bdc88ebfe229cd34af97f8087bff710d33c89826354567eab5649cc6c1ecf83eb4e2106dbfad11d0234eff57bc1a3fe745167ff0fbd3db8c22e45c5b91d4eee669909b12f82b404e97fa5b9571bc959e8298a9837ca7e15e1bccabdcbce59550800027c4f033f6ba451d1326342d546652f6ae312096604d2b9a59582b5df0d79522d32c432069fb80735b4b23ea2c91085d1f3f040dff690d307752e68bf2659dc6a81183db42510a8d54691ad078384da00b29fb4ae6b6085f43a883cc5d6edd251f5d7c11d3e98b71d215d1b404ba8c36290a4c140804791ba0e24577781287a501d217a0684026501084c1db28d00c79b0930ddb33f0c0548ca47d283910018612d52985b74a3f1289bc489e3b5c529c76979a883cf43c3519120ad4fc7d35610b23a5145712bd87f2d8380fa5cf6263a0d28c29ad7094bc1610c2bb2fffa21e1499c095c06fffa1e21cd59b259ebdb78c97509936020cc36994fe476f02c2cd4712ed9e6081ca10cefc8fa26da46607b820992302a5e58a681428bd00e4471f178710c5abb3cd603c253503bab581a18e3d16178ec3b7b26decff2247c803c69ffd1f235368555a76e4f8710f6988ed8c37864cb8bfd752fbcbaf3b8df9503c53f0893b3d4152d7d8b103b289d415c095bb9fc01a1c8c137313281d263479004e0135f515c1b809289a21bec1b7d5079a3435e8860d4d88f0cd532c398b80a2e6de853a070afb049cb1a1aa2cae3e1e7059368f949087dbf90d174d437fcfbdcb6a50015978ee774bf4ced6fddb8225fe72ce194ef1629a3b2792cb288fbf90ec6e50cf3b765099653a47856ae85035a625113585106a53747babc26e22ac3b9548dc7afbeae113b3820df6bae5f70626fd3886140fb3807d56d4eaa780c41e34918f4eafa38bbf90ac2a0caad588b286d5921f00f1ca0c938fd40e089b6a5b4b6d0ff28d014655480218dfbc226ee40307ca43b0ae414621498daa1c22a7cc5085861b4740eea47b8b8702093d2a46fd8a8c07824b47a49a2882ce588331d7eda9f2b315719d87db44e840fa1f3248c2605bb53e232b617d9fcb43b54f8742d4acc64fe5bf907b857ce0fcb6c36e4dfa0a9ba0610e2b8977ad0633ade99627a802d4721b7252d42dcca0c5eb360224e0dc98bc7d40222a7cc278782efa043817cc49984f1d784e0e78a154ddd8ece8005d2fd2ebabc71d84437d90efa10de51d47b5aff474a2bc5c6a905988c0c7d7a1a4be9b76b2d245224b41619c8585dd0f712178f42e5285e895edf9b3b18d4097c05f1e211b25243ccb9fa74978988104b1ec4c100c63ad0693ec43bbb83b467b0ff94f6c1043790aba29277a0f945b7e42094ecf874186be08a93d8306697a8f2b1a876876516ad1e6b9afc7e894a1f75725dbd8eb1c8a31a7623d38aa1569b90ef8a0735acc477690fdfaae59434125e9b5eff93e353f30e5fa088f1a31b748ca12c1cd81003a3de397d6843d158c5994093aa2a4d17b11ff54984c7fd4375ea733d1fe5a3b4b44e8afb502fe844df9b3119a89de761377a46e907b8d5a4fcb9c0e82554a3cc07cd123d00ed814f267eef374fe80584de15be54f4077c364383afd21f36b177b356a647cf61b2a8fb9e65e1bd7ac348492fc56e40fa9ca998f30837c187e180825972a46638604c86f50366b41dd7ac842146b675daded3f98fac904ad61ec20fdcf3b01aa5ac7bff2202c1683d50a3696a2b29f8ddc82ca7a46c9988ef406f0013b19bb5324c29eb877b5b2ac8491b93ef08e063cc88a1dc0c8d78ea364bf9fb77428c08fcac94072b1a7efd4d83edaf52bf5cbf69bc3edf93e5e0ed3bbb9bd8de5d5ec2b244f42b2693a2551a22fc255a7d307c710131f37765a4488d3959f5cafb22fea0d889b19fb5f3926e98c52eb1143a7578da95960f1d7a72491d564e04c10798870997b4eb756d3eb9b926382a9e1c77120c675f3bdd56b8db1eba3ba0636afbad9bf51263330cdc99e569d20304d0344a7f15b4e9c5213e0f114bc8d286bba88e8bd86585bd68701676ef85d6c3b02f574ad64709a9e2fe948776e5c44f66b1984e149eb9a7a3080a6996944b8e58249a4f8df9b315c573fbd55c3f6d6b0ff6369ec2cff2506dbe41cce5da29549bb83b84d350a8469acca25a166f5984ccc282b15e6fc8972d14480c832f4314c88af11e4c19216677ef09822741f68565b1c60dd1b7926f9f5cf758e12c05100a7fa2fd4762a58c11127703c32a4a9777027395bdedfb27f1796c74bcdbf9340775e2264ff7d98ef9e2c98e0c37bfad690ef8905a50a72880948da6a0b95a3e9fd7424c7b23eb76cead03720c9a01b677c9af600b7ad46d9f46e16de7e1eb8a367d54d0e75d57641965f2a985de56bf6a3678bbc92d5b92ea8a62f303a2543d9773342b7c9c13fcd0547e4033a8b5e5b0423d0f60244dcc92f81b82a6f1cddc046d5ca061de864da879f27d11ef53618ee7737fe75de535bf6242e70b0348611b920dc7105dbb2ce3fb719e6e5fcaf34e73d05804097eb1c3061d25cdbb83e878c8b261c9cee1e348f73e146cc041676f18c557563e418a9f2eced6523049c1d030ce73415d4bce06f49468c470a6c0a3adbd563d048dd306db839bedd658d26537d4601d85da680f5f7d046a1f6c21d14ffcd64fe22359a7072b692335194f72fcc6445aa7f2207ac5a4ee517c4166a3aa72fdfc8905b42e3f6836a2d9fe6d9d5d524ed53c36966d9fa3b291d5e645fd077102e47bb9e013f8d62929329271a20b80b6b00b43e781ff2697f59a717b99d67dc1082629e50db60da159a0e44b841e8489b5e04e338d7321d382027a41aae0fc355cfc1a78c0ad8ac95bb05aa9ef83ad56b2e012459f7a51da44ee618b46a22fc6ebd099a7274edd1614a75d07fad7900a1cd500c73d8abd02a4b9429f087515af3cb4cbc96da72487ae1378d312fb9cd881c08919383acc159eb43ae0f9b3b907fe5af648a41e19c2e46ce4b884712706e338ff77e2686dc32926e669ecad8ab2248b054e61c21f94bcfa206a90341ad40ed7a673f3d5527cbc44a00127b90522222441f4fda81123a64f894a0d866853516b80fd2f7f54732dde9dd6b3b7a71eeefd3ba5181ad2a73ea47243e059bbbb2b5592d1f080eb3def59feb2f966a9bfdd77bccc1127981f273e10117045854404818404ba59d7aa5aa3b81a203512edaca66361c8cd19ab07c5c422b9f54f7c227722e404fb4a33579023f6266a3d2b72b22cd91f4ee5dd9057f9b22f7eb2960bd1dde3deaae84fa8d2ef00c0b340b6420b2d0e3e717a088b64b91891697afdf48abb789d3d9940c50e7915bddd8759761626b83807a499b74b34052bc8b9e10e6adf98bdb5865f18752a0b81c8ba3fbc35c34235c8b819a51862540b4c3a1a1cbd8258efb286fd2ab41f8fb7651e9391cc66e4cd3d403be65d90e2cae7148466a068031c64df9e8ba2b80445838b572e95418583b44f5f3c14b2317637c07b54987ccc434d85e192e13d1026458b543f1001e722d72a79790aa8b641812e6499bbd7c57163a0d76d8e948168595d7883286bd107670ebc269d33b78abb346e47f7060e9d5aff00c8a02173868ae731134266ce96f8b6090fd17cf1f5e3b005e537c2958db52dd4de142cf0da4516aec8f5a5a6b10486017f17d55a5c107abae419b58a3f7f45fc54a4f581ed9aa3160bba3c40d29be65fe8357aeff9d2e98ff0425854229c8a9c07481f27e1b3dd73e9856f478e880d590abe5713abb87c96e475084559b0d695826b4658b5d004c35ba0bf2cb22a1ace1aace21c22f6d580827f17de74882084d80334b367f0900b84f752d25054ded964f572be384584f1a8213425845b5c57259acd732b082ab4e4f9d58e25a292e7260d54cdbc3264872276d405c765b991602402385e713a4bb7741e861321b4e3e86759589871e526bcd3b1f10230a273b8d033b20e88591e91f440d15b159a115e34275225b2d80873690c098f789bb0025c609e97d09a2cd0469b611eff9cded505a925da6191f54a6b4cbc41e709ebd883ca8ad01661438ae9f5224278df7ef01cd590db17bf381296cb0fe324956cd72da241f4ef2a88533224b98c4163fd6d98e05fd3418dc24a70be96df6720b09c03c0ae02fb2d514477dc0e46fc2830508eea27229ddfb357d0f997c5417b11c5e4d3d26f55cbeca5a6f7cbb420f08ea0b3fc2a7069dc62bc662c7d129a35d097dcfe7fe44020a732707da32f6a5c44badb4c85e293ba5b893c1928ee160ca6f08825d6dcfd538614e22610ef0fae8c872aabb8311fc7c842183294be61165cad509534bc0d5baa9484539b1aacc52ea538cc0ae06be601de7375ebdf8e5d910ca68703a485c3c90166a6d38c3927878c5bdb5715934050e6ea6771e0b82d778664619ff545a89abe98d32a0c966f20069e2c6c900a16e0c671138fba3641b164f9d3fac6654d7a27b89aec8d5fd2f0b79db9232b2f3e299c521cc072b86bdc28b4f61b9f0e5e71072fb94fed132f6c97c6c97bea1342d78e757360bff60a95ce8f43bd54746207a63281f8497d9450220de1269cc599a488c9ce309f88e20014144e71f1f7829c159a57b9aebb1022df3a7602905c47c21787f6db58a64b1c040e13a02230bdba825555bcbcb31011b5ebc4580103ee1da34208be217e4d3f02a93811769225ee035aaa7e69079d5f726688047293af3ab5412b702a09bc793747a4a78f2337362a3ee569493e59f40528a493b60d9b982b4b0f8ea2267083d07d3f42de3b46d1845b0ec741c562f03316cf1ca343f7241f9b3963829c6f6478339627c13c6fd9da0fc69ad74b4a5ba7246cb608047dc2c34b43f63f1fcf1a28b67e36fdf9141855046f5c2b1e6b348dc26df9893968ff2ec0e5b13ed2bd7860e4f910e2beff458c28ad7057736f493824b93d037fb7df65b801dc2d18d71bf783dd9a79f18677f9be45b467e7ae716a27acf4334c3f80049dd2ba45c4205bc1a882c0b71fa658886ae88627c7452d62a63371d491410176052ca4f3962f21eb35e4c95cfbc281739e0ecc248e53edc17530ca46732f8abd7aaba04cf68770da2d83fb4a262d4fc702d96b24aa5fcbd1dcbe24acb8eed4b34c225c5ab0c053bf9ba1d0f39d73174cdfbf0966a559f3780f73d769cfd0b205dd45336b9439898fdf653a163b192fc8f089443adae7b270a46e4414ee4a6cd76fc464dccc9eb094f0544f5ea283673d2e35d488897ae162247594364c8835351fa406e9f8c49334366adfd159f3b9723c153d36be9dec479e72b246804ea94f3a919a5b8de45a2d7e5faa2711ec1c69041b551d0b96c689b2573f53b649063032c2834b07f67d6da41e8690fa1969f8f3302cf58a588ee619567682ef62e8dc8148d16196a6cdb8ec175be2c6f68f9e6028eeadc1bf6e34ab480ded9b6912cc57290d2635c110477474f5b8b6e2eba6a1d675eecc559d352f49935b20bb98c607bf45d2f15a51344af6d0ba9ac70a3e690879e9e5eadf7c1b757a3013df9216f3cb3deeba7761495030087580a6af0cd4d58cfeae97d3a1731523a414d16d146da89a83cbbfb054b3ec7d36019eebc2b2ccf1f0a5ae2e5db601a076c064486c6857142866fdcdb002ef6d6570b2bccdd9f1ddcee28e8667b3c72238d7af625cd00bd99e551f4573efd099a4dc408299d5d5cb3cdb687465e5ab4a8db7625f305566291b561cafe8bf548d276e492dab7dccb676df3c528dc3e01099fb468d26de9d0f260982e1960f50809ea2a6a9e2d49093049044544bcd8f34a317218ed4559d596da76347f9580b62281f6424ef48409f82b8e4d2d187d91898f37e45f3e800fdc3fa261b98cc445687fb8182ac10516977c0616a550508ee4e4f90cf51fb12e4ebb181ee0c176169a5ce1c2fc82bea0eac1aa6ca52eb6456a180422c48c14c6123233cc75f188468d808e3ce37c3a4158728a6b7c91d736260b783ec835f0b2f0b3898674c2bbce07e28b83575656fd08618ec0850d7ab9d590cebf8936aa54954ce61356eac086d6e1b1d3aaa8445611ce1f092fdf1b6b8cc60ec41591180fd41dcf8c1c5003260744ba35c703e015c7fa7328aef2a558e8dddef63f32d163dbd3e36f1f490179d50e1e17a5fb2c164139b0772f0f23d865d33d349e382d2742d8acf5012e2c55273aefe480091947d6412e74bb32699b20fbe55b58a42eac552b4879f392cc5e68368ce69506abfd9535da147149f8fac265ca552c2ce64284741bb34a62865e05e4c76722b9b99f41b6611c3c1eab5e6dc2e91397b2f3a2874297e73095f2c4f2e322c3cdd67e8753e759fdbe6e67ff1e8a4a316f445e3c649f949368e4c4f51df5b40039beca186bd9c26fce9bda69de8360b54d4dccb4bbc5c0d996e84e96d6d901a85d362361dca986f7bbc2b0d3a18eb1075b5d4972dae7442711ce938a9b9821e1fb31d546c05185db7413a15de57c18c6ac763d7c0bd3d5e02aa7d5ee5f5ded52ee1ad39f028fc6bc4e99f578a5b6fc68497be0aace77c73fd88513627b732ddd9306e153ed72f6bd770b48daa2bd9a51d9b86b23c02a13788ade4c181e98bc58b0fed250bfe8c1cfdb8cb89213baf728131e80a010719f496f2cff56eb7d854c863579b82a7876d358e2cb7f5894b1d3d211f2ddb0ee4da6967eee351cf69176622bb6a3ec6ae8b453376f061a83b77bf6e61eb13848a2fbf3997f616f2016b7732862477ab8bb42745a55d46ae8dd2da4891725860b3a351b6264c372cc611a2bd06741b8866cebaa6bab9362091c0965d44f7ac6ee018521d15259cb42d02f40dce3faeaf61e56e7e301ac0e488e5da1eeeaa9113f8ae8625ab078c019757a9b709ba188d358b746ef96946b9dfb5734dfdc937933339b71acb4d0bde5c1852c4f6079a1f5280824ddffb3b68b7c415d7916297c3297fde98be4e794f6026982fbd4f43b3085a7062fbe87b1a709192b16156b79d61680d1dc278580f23fa39f161639a69cf1098ed8e551d7497780a47d0ced4d0ff4a82d6f792960d7d968b730a97f951c2c699e79a3822a8b00396f98348f25dc30394bc0e18df81e4ec03ae308ed10e17427514e81db5282f00d1bad556931ed49fbe5f12ccdc7134a45942924f8f1e591e43073f3a1aa1cf6b519dd6594cef282fee1efe563078f979a7e69e261216fe505aa04ad33632f03104e9a82b0a9c9b1a1f53ae967edf5a50c5bb288f0301dac5057f95b550c9f611253af2884aeeeee3fddd701fe94b22eeb1f5b78fb46c7c1276f9f344066a4332e9b767d3d3cbba596965dd2c432f53d263bdb969b37cc43416f3d267ad39235da6d284259379e99a4e8b55a334e6d246ee35cd1c771437416a25e0dff000149b6f036ce7710a467d45cb909d1285f7da885bf6d8b6ecc99c79338076d4bac17a642fc3346e4ee6618d2ab72f4a5c90b022d952c265492b49d664ef044e772b092c24af94bc926845c2b2d49f94e06645d2aa846bc956122c88cec96db1aaaa29aa2cacb2aa03581b4a46c58cc20c051bc4097e5e90b022f1274f68571510eb7ca43294aacb08a932e4eab32147e9ad174da248b494abe64f17ed536d078843571d639af21f2be19802db236b0fd24a7c5939c704fc6a5d89956829760c00ec540597463458c88e6403fe62dae55a0f89f35e75998950d770329ee9e955d6d84dce3a7b0da49eeb114c2d7aad0d75cb1a1ceee36bbed373665ddcd1bf90f3acfbc89c2e01e53a2cda3ac77dfc641e4cda9b06db5f3637886bf5d897d8684756750e37359e84c94207bedd90f7791ca0de90d5f8508d461490019889003b7d0fe1cf2ac44bb7d7e52dc1cafa41a56ff95dc515ff44f3cf4a5904b8fa14b37b357733e88408ceed0812ce2123e2605401e499e1a94a750bff13878968b0d2f13d892297f27f9095c7b28bea2f0337860b04a004559255200534561902763f2f2262768c54c3cb222dd37f3684f3d1968807454378989becb0aeaf058d6f0d98953ff91101d1d371219afbe7591e37fc3feb3772737f8890a4cf58434f0e9c90cd659c29bc44557becd6bec18a1ada1911dafbf38d84ce88cc73411411dce4a6f241e0b832573bc355c236c981071b537cbb81a759a672d0270e63b5336edf86a0569684f70cd739841cc8a0492f3038d6ab0de0c8fe2803a6a018caf9989eac6367b4410774977f6f03750831145684a4fb4353f5c0111906fdf4e1aa0b5dcf7ea306b82c6abd51cf2c7fa88bb074bf6af6484335ac411aea35a21f8f3627144efa881973d52f487fcd37707fd25e76e0263732e16601635ebac7663d44728c636f643728da752ce2bbd01ce26730add52ef319d0ddd0ac1faf40c58b7d181b1112655d09666eeb298fe7826ddcf6858209f094df576e1cdc65c8e753dab7c1709b8021eb6128fee01192c05b9a614cc58d5e97d4c9a54074bfb13c8b886914df2b8a4726d48b4be4a118a91bcda6fd9b9712d0d296781e33c6dd2feab5e0658e28694c05b29d58806f63e556c747cdf1015585a6659357e2c0c29816dd21310be3224a92a54e8f1a9c1b67331a7e26455df5437d73b5b7572bc3e47c0fc91bb2efb54e28501f6125f355de049f355e54831b38fa38cb7c8a3a7a395b51e2af462f9276d1b40018aff71bbc31bc47805e81740dc2b59e7e2ab41eb332f739e2886f4b67fc77c9051aded8b351922c15d7ecbd591841804b5478ba93d758ee0bd9de963080a72ec5ca257321d2d33408d75b41baf63630fb7082737967a48b774bf806dad95c24bd762cb7bbca587f068e9b1c1252de9cce998e64235099677e2bd3a866326cb447e87f3a04743dd50a38a723e776e98cc279d79378ed83f8421a1b5af28a37fa6732936441121daab432e9e873c512ce8a17bb3ad1b4e609d15278ee29d2503985d2266d75e8e1eb89dfc6c6edda3748004d87bad3515a961a3d00eb66f47aca00d25ca0a3c30a0461ae9a51d641c97b5c75cf1584e2019dc5667d35a0061cac3cce379ee1bd7676f17b2d9240790148fa5e0f0796e71f01a1f216aac2c19649806d5343a79d2724f4e56be1ba21024d7afdd196e15c18477aed807890b7e804e88f4c05042f0db6e12881d0d08cd1a2b31177d53242becefdbbd85a438135fb8e0823b33deb5bed7de6ae5baed5b5aaec78d4caeb36acd5e77f1b4f37078d83a55e947cdd4ee480d1c85d935347850cc2f8bd20a7bbd94a48e0cd4a4982710ba04483a17a5b0a9405ceebf70013fd36bb553a30e0106ce6d25822494860e5f86dc604fe2aeea5c6db5f0f0d29fe76a7c9a849f8eb2fde861c467c416664c153c1d931d0856b482544de2f13e7b7f7b468d7364883b882b927eab8afefb24399b88760fe0811a898659bdb667d0ec9fba1c4faeac64004e206bafc50f63af9de04941f30ee0952b02c7db8645e17289453c15eea83d11c345b5ef979c4b8dbb930a61217f8da563778d92dc7ec481953d4487221c0dc362b3fdfdc8f4751734237b1a2cd4b12453f526cdb23879f25407fd72df472760108ccd7c71865587318eabfb79ac3a26ab9e8a3e82e8349616510c76ab7208240b419e9326fa675c14268eca0f0a6f4c221d22a77a8cbc7d0caaa4b44f7656477d74222ea625c3a082e687ad7928be29c45be867194960d465813b9e6894f0a2424ad2ad42b8399a0795a3132794684975595cca27311a2ca223f8c90d90678a28f15a9adc41d31adec8a7d55cb7222679896670b34dfb728b1c7492c380b957d752be937f0acc0ef34c8ffe10219a3a0254c98a28a4ecd0d51c76c5a8af15023e096b28e9c41040fd5c423c81150dca1a9193462395431d0a53a7555da1929ab989e15e0ad2a00a4044887857e92991caa29603e5bbf373ea639785f7c6ea71a4a964556aea92239f2b190cc8976e7b6ed0da9fee3b7d3e8a1c50c4c9fb000d174a06e2780d3c5c73650eb959782308b1797214049d4adb5f4bb87469e89e904726d57ef38216e1a79ceb590723d5f8a86e35e93a9ff5f5b0880ded48025ae8238d2b3a2c6eb38711fc1ee1239c5172218fe2b22f0e5bcc7fffd6ddd5b29dc884c3ad9cc7dc08564baad90e3ef0b70d09f1131933248b7b5bc6250d88e52fe3a311cb54e4768841bc3b9f718cadb1c8111502dbd48dfc7c172cd81a12150b5f0d95d207a0b45e4bb1a739bef565a6a034888a113be3aa9aec2eeb1d5e0920c21af9faac4958ae8b9ac7f5958662055506cdecbefb5985eb2355aa96037155d939906b2c445c51e75383b631dab2e0ad11ef07162e4509a98e5c66464660b2359589ce8eaf9325b213833bccb62a7ef3f7f38018c81abcc32b79559ccdef1faad36687f061de1c88b2e8220d74550a4823c554a94c1f0e1412caf105ec3f427e8189dc159210df5c4e858108459657268135913681478a7c9e849c46c98d30a54bdb0d9f60cccedcc45842acda73fd1194b6370f60d854fcbbdc246990a6bc8a136b9374eee713531c8924c4e51785d38823850d1a6981e1029f567405bea3a335335c8141963b636520c8bc853a5e920c266b8d244e5ddfd9748bc7db614ce646870c3293daac2397298aaa00d80cd3fb283efd11d575d664a4210b9b039c069e72f2750c7f279bcbebc40dd2cd216fa93096f3eb8c22cbcd7c3df3f353bc1b6f913f079724b5a6a498b5a9fdf2d2005a23405bd5df37d13d60f80230fbb94a20d2f1d14af74c56d6b056b0c67f396330dfaf5bafb9be648d3f26f13e43b256c0401ab2d7803d493b64e1b0c485427fda14b04490bfa1b4e996d0ba315e4c6c6ac582d8132d18314cc003b75e5d4cf439d9540d7ed923aa5b2557bab59e40e8bef6a6da1deeca85be85ac75d1c5e1760d5a13f2d2f5ba783aaf404e8869bbbb0a612935b3ea2697a2b74d71451f3a0c4aa6ee1c8364b772915c1bf60c436ea6ab2b9426cda5d4fa2e3d352e5d39864ac8f242e941b388b403cfb46dada8d676005907fa0156de88ce576df42c30ab2089efba557f144de847ebf5a74514278e100cffb4d1caa9bf55a48b9e1b4efc238faddbf3480b7989ffd784d967c10f546d3772e8d7a03bb28cadba08b3a0e6c1018dd3b8c3130eb4a0235eaa2536b441b5bf8eec6a7c36bac00568899731f88bb44b7a7649ea6d0436100a373784c602148c12d9667e375965d5a72c00b708effaa8d63d128ab61858dd4c79f51947fbc1a5c4b9c833da336f23582df86e2619b05d18fc404e6008da8a5347c033322047395a894435c0666a502b0c0ceb0202ac4f76fda7f542cfc7b6c548616b5c0a245bc5189b6c872ee258bd6fe7df20a82f08a132d5410ac9a5b7978dd768660c036fe0dcda36b059b73006d1182e304487d0d6d3487555fc0615ab33ffc8a12100f9c80bf89f172ee3163f080e8394e09a58ded2714cef3b02ed4016b985f96da3724ad318623d157a50558097283006ec70d70e0a853ae3ec402490696247bb529e313b49225b545ee897e433d31f01d14cd819e231c83dbb7c4f422735803ec4e4ad5e710d14ce224fad5b3ee03f2d42544b88cb5b9da9fd7f59407b6b629e0b1aaa25a2668a9db37ac38aa64582cd1685eb2b8a8de489ef817693bb53000befa1b2e08a18ea2279e7bc892a5eb1f23db940e1d8dc953f70911052104ad7532a64ed5e37b2804b30b41c862baec4701ee08dcdadf0cf56900ddb8e5643cb85ba0dd953813cc5409918aad0bfd55f5b1c71431f54864ed8e8aa393ece31d2ecceea0a22c16d558a1a9ab7ac2b5f04494323c7e46ece543de737db1d642023b3795f2fc25dfb83aca776e9ecf792d1c963f3fe6f779349bd5c6dbe8ee7616efb1e61fd7de62cf48e4714d175610eb6d209b704a7f07d2d59430d3cf10da4ea7f000a49cd90c8bfd7382edf64a438d3b9cfc1e917b8296a434126df3df239eda905cd79fca0ce858d333be9f4c8602820d1dd9c0c9724215b097a2bb8ad8b4c0c89ddd2d208a700e66e1608bd0fb503c99efd5355f1b7f5593dbe57011bcb076a7383da8fd7682e55066789fcea24da3e24ea8b3e269ddaa8f5fae44da51c5f60910dc7e2a2e051a43657aa34e97dd58e127f5414e42b465a57d7335fdb374d33edd6d7052bd66a76b624be75d992ac702eb7dc27ba21988ea799041c009a6781ec39c6c87c46d83fe031172d44a441745428ab6c1365f553d2c8a6b25031be3564c6a38f17e933b7bdd85738b3bb262e05f724a177b65d1ab78a60bd6d64bb2eae3136cadeaec77b1732b999981cda0bc55210ca23cbc99c424a0f0622a69798acdad83e1c115ddda853977db1e6cba7a58f44a61084c3720ebb8ccdb433ef5219f6090cb82640455d8181dda32d1f4bac2281b352234884f81c3874f9fc2043e8957593a4114f19fbe5bfa47741055542abedbf501f5bf5957bd21283aa08774aba7b8a5b889b508c970fd57ceffac29a94dcc5ec8c41dc7accfaa6d55fb908d6fc754ef8404e82938928d28630030b6dbe6e3db882b39e28ba73e7b1130fb4c2b561a66ae9859496215e3a65ff92632595cdc6cef2e0630298826a666319765ceb3bef2dcd44f536f6564f6ada58bccefee0b1cb547c7ee2d0cfd4d88de4831dbc328acfd3b0f0268c6e277d6dfa3b150264a1bb2799d719f8675cb502c117ad1f5bd2b289aba5e98ce84aab22879edc2910a6bbb997b04fb90c5ebdee0f4394c171a494c24a46f5f0d1df2c8b325ea32beacb1843a37490a4249d11e4b4a700a5680a3295a91f48d875b3b34a2286e2a187058dbbd00000c3ccd148dae590affcc5ab1efd0106d591cda3192043874360884b5d99595067f22f8cf9b73f0465b38390b7b613741b92382109b8ab4aee72a8d22be12b0c7b197966fa1ab5f9e4388698133cba7028e08eadebf1b2c7aa125d8e7b4b3d3b531526e638d03888bb6737e7eccea941fa62b865b626950de6fa43c325170dcc0ee13a7cf20de95c88953b2e0b39fd75bafb8c2877c612d85348ddd12117c9d34ddbd4f71a5c7604be6a29f0b4a443af18b83bd38b35a582fe9f92360027e0686ed46b7f31dc5c5a6f78c0c1a341ba5219125d649ca044f29a73ce94e247a7abbe4c39d1a6dac4d82bb287299252a92f1db5c4312bb806aaad4dd7c7b3b52045637e8b8baed113f04f0fb2b395012c4a9f735a40f436e3c914ff4660205b0506406157f39e5f173a65b69fe1a690ec755c58f882376f722761af160d6cb2d26ffe0518f51baeea0f14696f81e688c8247f00cd469df0ec7150c0111b527086cf72ef9b6baee1ace4aac14f79b65b035dbeeb32b9d4caf0667dfbc537b02f9a8018996661ebb0273306675642d00dc541d3b32223a1ec9e59b0e4b6dcf00b60256d2231d9cd36e6dd2b3f6b7ba70900fa724be10d03c23ac5a3dbe88b41b07fd01c35d64c59d07977c22401e92112135ecaa62595cbe9f83966b85ea0d12fec05a0ac28f4d75e79a465dc1ee154a52b998a4ba27ffee8d8f8d9d4203327f90889dfb61d8ba85815174e6c47b1f05c4f3e2284562c8c6e41fa12c5a5146637d049b422610095708881f7836b57007235442d13a60d245d3f3a324f546aeb0e9b4924ae1f771f94f15dbc993a8bc2ac7f87572419fa2802ef9e321577e6ed1a71f1e3653be38cb7f4fdeb45622f6bf23ce5459ae7690dbdec78cc2037abfbe8b58305c554d07991ca4d7dc109b29a81b026717a1af3fe221e0929dfc22d6c097f4406a07a44470b661ae5cb9c7f17a94a64160c09201e74825ca2a1257cc0cf16ae92f5cfbc3bd889f264b0e99586cdb72ef2db79452a62403aa089008ae0858beec6c58397df69c7346165158822cdf6304c2bba9205d7e49a69ac5a2cda2436468a9542be8284904a8756c914d9bbd33376dca1a333736ad661dabc8cdb93b27464e5efe6c7c22ba3c9eba3d3f1de9b264acbc9a0cb9796e3b42490e5d90d0c932880f1f7e386ad3b12347912c67c8a8317363d39828a94c64ea114d9828a1b552264a98286122d44157098eb343e0f85cccfd8525c82ea55fb77173b7c085b3cff216b58f9ab7439e2a12f867acc5b8f4b4ca9851c3512db14c71dbcf7fd931f5124ff5e7f4ac3ccdd73c2c4f31a56199d2be5a65ea868f5d029eae564920a44f242839fe0f124a7e7e90e0f9f9f94922042404d42735e01eb2e58ea9a37bce1f8eea1e2b6f6f8b0b0c8c769ae2565a880f9b8f1ffa6d8c81d3bdaae9f653ff8a0e47f5d790e1a8c8e3fac9fd336e7c4b3f8f7ed00506464c67f5352709bbbb76845e2ee59cf2f582758ee6da8a8b8e6a99499949a9454dd43fc39b0d5c6c0945b1c699939393e577672325e62f77e150bafce200fb8f9ef434a37ca18d81719540f71833d7b7bbbd80512fd0dd7d3c00d95562a4ff56483305490a14383a76d0b9ec4647d927608dc3a12f57ab5b2ddaa238381487b65a60708a437168a5381b85a9e810174483280c4661348806d1a02314466114f69c4cedbaaeebbaaeeb3adabd64aabbebae93e0b04bd201754abaee264996dfed74b476607cf15ff1937f77d3dd7477958373d3dddcdcac562b7b7373d3852659a904aeb2afaeac565729d24a0be827b73b355661fdaaaf9d9d9dacbeeaabbe549cc061bf8482e8cedcc9ec4e0a942bef2d48e04eae0f8e3a61fcb24360773cea01f60f6fcb36015fd20acab5712403b00d3894974b49215d5227a33d0e107415990438e33ff91620905554c175a51cef6f5fddce8b3d128b5c3f1b78f6cbc7c17efd7639af243af1400755349b4c6b564d5de49b39bfed759993b73d51e65ce0e0e0d8d8ccf99cd6ed80c3e5703b593e57699db27acd44feed9d64ce5bbabe80fd0ed9c854bf246793699ccd8c9df4912f1752e0edf5025bb247069a3808fa0b0bf7e5031f1c943e1c949f831670c8d164f93f1c94349e02224f353106fda1527c596d2f99c2a5ed377083d962b6d78acab538202ec602c6c1b816d7a21c8c8371b030e0b0fd24a58b6bd1cab5a490dcdddd2caec7628cd27d2de48e49442372bf0262cac693bf0caec5951163c4dc37fc0ed8641a82c57b87c8addc07576e5881726693ad4e2a95aa025520ae5532e53eee99caa1c0351b18932bd05781b8fb853787154889a75cfce431468df8d28a89f193c310c2b3be9f9ca5c511ec433fdf41a928def413fd7672f78fa328f58b5d886e443732e52ff9bac92f3a99d84d29ec93c0dcbf7e3d811dbfb82f3a197f51da9ed7c96894725db91694273d89f4a44f79d2471a282b1ee551be20a609a098fc2362f428afb242c593304fbee4c9e50890cf8dbefff65d4913fd5665e7a183c8fe5d1b07e57bee5cfa020e259016e6ce8e112b3742d5acb96384b533678cc562ddf0f0f0b864ca5a8c4ba57f10f4c1a1a8d5e211f1885cd9acd9ac97a7d592a9596416693511b15c3df4b5b7af1c8a86d0bb7265bac17e1c65bde6ce2233e7f210a1353535422eb62ea7713528e096b518974a24ad85437f81d7660187fd729e8c051c4ea06cd6fa9a401348a6a41123a2d64bd4f281f108993c3574130911b136cd56232d262a23b552210965a262a262a2a2d73281437fbd3c11ad58bc70388196c8f27d8098ace24bccf30e75ac9b869822e5f00ea1e4f00e0d49203f49d257e7675304ee25e24b0acb93c8c5837b04e03212026fd9cbbdc65328df90ef0ac9f2ed1243581bc0cd86b2a16c287b65af4e664356b552f9c13fb098f6772553a36f3534e41351538ac84444d96cc6185fc39c89294544b98f07752a8fd9cf5ff1453e0c0c6795c0e16d4997747153cb382d63653f95644a5c6dbfe270f7574499929a0531319229e93682c5c4487d1c4c8c5426466afd7030e129e2a8ec5b27c6a8df5ec4c18e91e1a017c9ed4d7c284666afecd5fd886cf000603197ab87071623c0257da354f771b0df5fb97780765a3265d231ea2079ea1f7ddd2249980bd3c9dc9683f26f600206f3e57119e00e817b53358d8e2cdb2430eae664f95cea661b736dab95a56d5996f49110f8fe4806788753eeb7adebcaf695259863672300b955a2c09d4279f9922e791a0981b31fc900e76839bc95b41ad980630e6f2b0721585c7ac1c4dca12c7b5a2d9e76cdae74a50574f00ed99a9a16aa067644912cdb469b366b715963c383bb11b066e2c3c487890f931f4dcaa197c578bbf3c5801a2f2e4c36e0b662af562f07dd93f84f967f051cf6cba5cb41ef94ed1170d8afcc6fdc1b9d8c7d4aefeaae6ad72760edebd795a366efb491b913ab672f26954eaea8e36192aeb5025e0dd55c9b4db3a2d5a6d91650e59ed48057da5babe356a77d62b98d706529ea3891a8e3565ac04e4665fb4cd4ea64f9da67a276c77e3898c4ac8e8a8a113b4c62f47130895126314a3f1c4c802ccbb2b2fc110bf06d2259fe6805d8bfd96ea3225f1296a50bc7b54300657f3b04962ee96a9b953cc95f616c5f1e63b48f83a0845d97ae962e69773cd537f224638cfa53c904ba516557bd2dff6eebb6329454a37cfd42218dbebfd0ca900143ba52acc5f61bfa5e3dc404ec4032255ac9534489a8104126224ad472b00e3551c5182e53a46b736fe4a9e5a80b83488588ba3511947f716c502e2bcbafdf5dc517f98dd345a280af4e4ee3dc9df8227f2ad1e99c9c6dbee66a755777755717c78d5ecd1c046e74d48b1c9fcb717b986da54f9168cb610ab27f4db2bcaa9a268eead5cab3f901d70e816de25b085c1e2442f9073f41017341597eb4429ea2699a2047f96f9f861f848971a71f0f12187018617953e5e841831120cbb755f899b9e182c5349806d3603df4605d38d46cb253946fa178cdf9490e6f4b0257029d8c08091cf64beb39915a8fa35e3e9a0d8eb1718dc7469ee4935e83828cb7628e4ca17cb7f5a1e6ca3287de964cb58fdb25e0190ed67050c616d5588e1a4096af0d718d488a37f29ce479271960692304c8956442438eda3e1b31377114cdaac651b2e4607da056b2699f655956ab0339501018db15474514c941e7462a3ca30305c9d6e60818c2451fbe3ec9ebec0310164f63f162ee98bb039fc0ef3dfd1d4444903eca737d61c020fd8caedca4394935626a47ec89af95200421819444fa9a88832c1c075948cd422235e9636679faadb2b12590084d44251ba964d928fb982b8b87af0f40484fb7154ffa6cf8f861da82f44d0e124b3f8944fad9c990581a6612ddf0339cbbc16b9217b3253d2591fe844412fdbf89e491fe492c9ef7e2df73f19cb7f24d04fd26829fbe95a91df244df5a5809464a21915e4525e567b88aca93482ba42f0442a5a6584b229148241229e555be1088acf224924a4a4acacff04bf2547ec5233d8b97a272b5cc71decadfe099be3bee77c418359b5cc4182fbea7fd996e5b7c3d43b9b28eeea429974ed2ad52c5e4792eb97817a5d20b173894fe2bb17cff4aa9f42d5a985ab4f852a9f433dce4852ac82b1f3b99120b2fe6162c2dbc7e162c1fbb15962f79fd2ca55f5929954acfb2522ab158f94220f2ca974a2b2c58b06029792d7ec52b3d8bc7a285c9d372e7b9e756ddd07dcc256f7b16de8a6f669fe28d3eeca0ccf80b1b0699fe8927f242306f7f3d1bd1b4699f35651f8b97908c62102325c5f8616f19379ac6aea959b58ae65eada6664891563599734e210ed2b6a969725bbbdd5a106efb7b2fb7d909e320dd029eefdf6f5b5b01179d7a607b517fdb73f3672773b2a285e907627238933dd0bf3df5c299bcadf8f8e3a7997d0d8365130b561863fa583215e9b3f8beb07f851782d92587ae127d76fa88c9dd6ddb6249337998124f1f7a602b0cb915829a66c63240d3c3519387399bd0d04c4133450d17573e8471f121f8e2c3ff0f4bde079ef0e0993e27ba4a5aa88584e8909048d41d955017e2e307aa7556bbb5ac6ae089ca5300f8d9a1bbf25ce15093c3e3f0f35d488b0edff2f37fb82b2ad1a1c583918307e3e20848e1f05d04005ef4930a070fc8d01482c1687eba49b354b8590525396c25797edde99cca42e527c74285a39d4c9dc2e6440b02e4e4a40aad9ec286d26e3a85cd143699b3f7b2f809800f6172f810d4e1c3073f2c9d3ec401f866f101f04e1ee8e9e0e5e001c08bf2c4f259b7c374703e8b0ffcef0badf785f8bfb014be0b962f84c92be00ea73c1a0ab1e9861c799abfe20bed6739e1e723119c4ffa18510d73707eca142cfa95c5c98a9fe1dc8af939728e68a99b6971bb7b80df1be435cdbb422f4bbd2eb26b7a138bcfe4cd18212a683257afc92bfd0d5e8b3e12513182a49fdf309f3fadfd51a9f45cbd346b9aa63da5345441ceb429d48f372f04226f742a6902d009e420a54f299e410e528c7f86779d795af75c76b74dcbdcf5b25fe1b1f8ee6e9e2c7e967ebee7c4182d7ebeaf640a879f1f65aae5fb0b31f7a29b61e175e6feb6f5ae67df9515873b69cba51300b7ca1cfa59789e392fe6d2b3f0b8fe16fd256fa7c58b12072fca1390cca9b05051798e63c1a978e10f2a5e57df6902709f79dccf9095f3bce520f7dd598b543ef3b8af2a222d8b3ccfb1bb9e1dc7515a9ebfc2d3b2e833c90be511f2fc9117e28ce261efc453d988364cdc17b3df3c13fdee29cfa9699afd194e637268f3eb851720062c564d0d6799c031c6971e2e2a1509bb0b68c780a5941e639432c618a58c1f8e1f76b0893410707d9be39b7e80a3bf76ed18700903a46ba5c0560a0cc3c1f86e810bb8fc0b3b062c062cfd630e3190fd9b39f1457eefa066b37aaf4864d2d1d1c85e94c3ce44645f1015d0f8fea6cc952e9184ef5b2670fca4779ca210884c330ac220597e734e5a6bcdb2cc5aabc1c8b868608c9b8ce5e8c53af8aec8ced2dbe1a00e07bd98c3c118f3c37c129e21c3c1f8b809ccc9c8559cdd4ec65a292795524ae9cdafd2e5f4b066f202de368ebbb7eb4422516fa21008e959d67113752109b2c39472309f9ada9b45a9b777975ce6c479e518016a1da06ee5f9d3723bb385701b9535666ed45a8914c9d931326bada9eed51ee9b2565e2d64c2c853de642e79c6e419c4870f3f7e00a263478e9c3cbfc70c1935666ecc4c28cb84eefc5a9110121212126a125f629e3d293a3a3a3a3a2ed711495c2e1d97cbf53262c49fdb010646f67b9d55f61013332525e530a693b154e06e87ce86cd273b740e9a169c5a606d7bf1a5df8b2f768ad9d92091b6ce064a04fb51620844f6ce06f60f9ff4b075110835bfb72315cdce868d2ff1253e6723ad99dd44f4fbe9479baafa310fee33b32ccb6636b36cce8b67ac95f4907792f8c36db4815aa8f2701ba545b88d4e1e3d3230ad36df8a7ebe9d3f7fba58078495e09f7f630cd69cf8a4859ca6c6455ee791fc34bf67e55d5431357278e69c733ed8393bf22687305a55c8c9b387b050dbb7cd9cb30aac3c1fc80e1d3c7acccfc0644264da4ec69584091126448814b91145c124c624160026cc6edbb3fdb6f46cc729452d06ce2f886f7bdd5d27e1397b529189879afb2790ad836de0e88c317371739745d93a6abab8b91379cf3833511b6979cde320fde99d642ebb36b6935182765b07cfe7b6eb5dd7cce7a2c87e480a014fa62b59cde8cc649689a4a6a1a084bdd34d88def54e7ca1cf3205066235dc95ee73608509b2b1f383c30e6a75109ddc173e902034ec9d54cad39596141e5ac0e843194d1ca4299fd3e8c08c648018c69fb894505ab0e89bd97e2ed4432c43b8878341648038482310162a301099ea9144461f884c794f4f563f53f6ca326b670644764d500b35f1d58fab84bcc6594397ab4ee3363e24d3af3f5cae521e97abf372f57a2b5e8bdde86bdf41de031b6a42b342790aa3f447f651ac7d91e7e208b0ef369e123d7d67397dbfc139f13c91877b7ce84a101a4a29cfea4748a61fa98a7e8f0cf8e0e38726a124874d24531e376672e8d8415fd6e8feb6916e75123e997ebb566877b90c0d4336d33e538669123bf6c3818621fb35b336c3818621340ca93ca6796989bdb17340677fcebd1eb03cc58f5d047ab0f2d4ef273201884258086e02dfc0a68564ef1b647fbf418c11b3ffe0feb094c3411d4dc09eef192dc1ed96c8cc8931be3c733c79674e8cf122cb804fe040caf239f9f257628c98412260ffee6e24828296f42ce9692f4e941c798e10c9a42335a84784905266d6092027866c8a8fd621b1899ac015719d00422675b73d27762072c2e6a4e6c8e3841058d404853bc388f4444a8a15485150c959d1c40d8b4b2291482c4a59186a62882d3561d3a28915dc80d160122559b930962d471e98122eeecacaca8a8b1730235eacc8910756c4dff6603bc8a4518e3cb0d597061ceed7c97cdf87430b177c92b4744800e0642707ad081d547600f2706106a7164c02507a4200b7bb1c79b6f0ca2497db5b706512a883f72f5cb0ac986e685162b1e27227117ee5395faf97167a3267354a4731e8d6e95a73ea680694360e8d91488c39dfc0046c3f3aca458e2f753acb648e2ce2e0b45e087a12a7ceb79fbcc9624141b1b3166ad241a736e0581220cd669b66b3d94260d18f64805759c73ab6ca41a598773e30ed230f133879fe8adcb1419e2f5bd127cf2924cf1fad00877d23d4715bd7c9742c49938ebd749a3469224473e328cd662977a9c375bd35fdeb855d847a2cbed0cfd4335d6ee36e37d37533a26ee6a49bc1dd0c4a37e3b29a35d3661404ee1c5ac45dfed219a97013f91b07e7aa711c9c7f8a02ceac5d9d9880c38e2008f610761bdf712312c64b9bb98a01a53f94368575fb8c662012f5ab69d3ee9b390e4e1d3fd5ccd55c758a0f700d7d5c198dbfeb3acf03dddbe84ebaeec47970ec2060ade6699d4cd53c204aad794b7bed6367350d88978de832611ca57ddde3e07c244e43132405d72a88a697a0a94b8296042d095a32742353d6ce1c1d4494d46c628c7e956e7e0ce7903c47daf7f78eed52c94d9da963dfeeae5bd47dd28dbb51ba47dd29dd9aad160a1c7a6c2589037d57353b7546f7a4869235d22e9f9bd93844f0cfeffe4484bd95160fec643a3c5b182f2104777549cf929eeef1ccb1b871dea87873e44d9b126c84828297003a81c1746030586ca4baa5db2eff365d95984c8d627ef2934b19a9acb2653cb5015377a77dd22d6aabb98f8f7cf9f8d07c09933f6eb3ea42794a559e2fb259ed529346e47915c931af84b06c566f2743972469ea63a2b02549ba9708ea254996245992648992b9b216e352e91f9ce1e09caba19be7e740040ee50d1129e59208822e8eb231348fc95344c92095af6f3b991a9353def89288924088e0fc93af873ccde72810412044148f69e33974333ab89cb466230fc846cda26f477c99cfe9d0cd80b74fd70370a500ee74b9f4e5d601d48a4f504652d41f8e253ef125e6d12806dd27dfb6b376c572145723aab37ed3469b3f6b34d61c72d3cd151538fbecc3084be9384dd344ddd7aa5ec597f9d66b9a587ce99f2b99f298c7aac7aa27696b27abae64aa695422b5b31ad8b9e6bae991412edb42767050448f4f7e3e88824f96b4dc0a315996107721dbd26aad3896b496b45c47f0ac8928ff42046110513106676b8cb1a94c9c3c553c6ee4f91762f447947e37d32ea7e8d2935b357c334db394be8ea0fc55aeb415ad59699d0d9ab539e9eca1b99a3542e64d91b9d32cf72122736246f2fc6933874c9ca933dfc632595fb405dc6eca4fb932326d464286a8cc9b15af22149457a8904613c753a39f3f731ca53377662b858ee4ca66ca1d980c923733b2e7e593a78ce5f9ef312057e241618c1249e20bc7ccd46392c80d4e911c47c93ca58f4ff7b4ab5fed438582869a4c157d1e3d7894f8c9f37d302277644bf238cab569962ed1d9a889ea6c9d0d9a37255a3a96d64a97e82cd15962e4b48329fd95a3e8a78c324ae6e49d35d34682399cabb9ea66b2db2eb73bb94befadddcdba07382afee729bd932cca5dbe99cb73e5282e536fcbdc76a9a7652e6e9ae544261eb82f2c41a6f3257212e070913b09c14cadc5b8548a2d339bae911f63967596b18a00b500b9645adbda9d43c70e99d913a09a9ed80d1129a514e11d4f75ad7ef9898b9452ca550b89e17e4b02a7470678dce41c3a7670db4ab674cf90d04a6825b412b279d48b08ca075dbe244511453befc58a7fe46c37f3006ededb8968764f687fa0831e41179818a37fbe9dd99c764e6dce6d4e6ece3b6737a7887e333a68e38b897e84892f4e29ad74ce263de4e0e62a3c883146f7808023cf1676b2eb649f39f26c8107d16dc045206b312e953e068140313826db1951461fe3e8f31b79aa8f62ad3c711e5d274618ecc727b605d9bcb71f32e53d9e04480aaeeeb947ddae2fb7d5771defe906d2f12974748cdccc6639587de7ba91ceacc5b854fa07c19c6540f214638fe0fe214f11d55244500c11d540f7391b6354f7ac3c65cfb514f347f6b523987d188772fd9aefd7b0ccc2d86f872c672f330e7b2e72974599cb7ab6b73b3b59e63b99b5d6f600e9f9b9719c94d3a8ff63e8e513e3ec562dccbedfc854ffecec1869a01ae4fa1dd43639e53d30ff89b54eaea83b11755927736dd63f59eca7f6d0589c2db3369b02072707c6d149a5d3a699287bf74e32b7dd2ed9da344ac059f69ca471b066cfc5b9fd0ca7c9f5b928bdcef9018890835545e3296f12797864c88001c3c5a5a5c581b6cf1dc873251ee460f51bbfe19438d8400ed606b23186f655870fb77533dced7bbdbb5274e7c9a5f85694eda3d53c9b79b309cc7df6f57dd80f7dec70500a98fbec3d90bd0f478536033d1c6579642d5a38cc9e6edc6f36c7ce661a73ec70703e228c47e679de11636c7947d6c1235a9b638c41021064555431947f90597bab0a2267df3195807892d1b40f868315c6c1fa36ead881440d84fc865a223c4caabda6f95006249b1801f2e327d6d282841c658736b75efdb7afcd64df09235076a026b9ddc80ace8f1f8054493b992dbba17d38a618c2c3ac57e8d1a618a2bdd53ecb342df3704c316448e6b85bff040b388c3eb9be0e1e366f5db66df6677806f3cc7d56464cb970394c40ae53b8f69978e032cde10872f645975c7f25083128d3be0e715456a4c541edc30e56fb911c641171148ea3b4afc66c236f58f847abda860836abf54f3ac0a10301f9905c059a8e8cda11341d4d87a42941d3d172b4236849b8311cda766f816377b77166a2195fe2a805a4ebad83ce717246d44089294723743064440d5a4dc23e3ff41cdadcda107048ca8d8a5d0427d2820152d860bd51155e8894982ea44c6902f7936c60556e112752724670ff26a52606dc1f5e4f79c16c4e06524a29b3215ca6996bff50ba4f937c2d77999bed80cefd6e7240e79b65eecf8eae80439b8dd8414ffec1734b8eb30099ab5ee6ec45c9dc76db0b01cd3f503aa5746f2e6a3473f5b67773706873f438286013ccdfb2ed74cc6c3b1ddd9eccb6d3115b34e6a7ddf871c737c3c118cefc5d1f37df70307e34f1103f2032377ddc10cceca354332f9c9964b3c835b7452d4aef6ee6fe421248af60e0b28f2d5c7c97f26d3c1f642ac793fc2a043191e537117a33e28b19681c8c991984440d0547199a38187f86b518fb0caf86187018c3f8351c94330eca199731943420f92b41e030b63421e0306636ec9c1cada987f22987d97b20029106b7bd09b847992ad5f69949fb72703a628bc6f695e4c99a78982a55a639763c74cd3ed033f5341c3cbf30be72e61b115e9de9db2dc6d2622c1b63c51c4c51434c4e9a025304113d18820637c6e262ac2dc6d2622c1b63c5a610a33d9dd6fa72be9cddf1a56637251c16d849b2569554b115e096d87f54d597f3e5acf4cbac549dac955fd84c2882881e0cd134e862ac1b637131d616636931968db162358871ade262ac2dc6d2622c1b63c58ca06d2ad513f0c65d6bbd128ed12c03877ea85a655780a1b0699aaec9fedeaa2fb4aafecf4c4a69552a954af58298d2de89e062ac2dc6d2622c1b63c58488bdaa9786c0f94f1b20b2d556ac887cb837431890588bf13b98b2068edb6e67b58de336cd6671e8e56974c4774dc2a224509a0a300b1ff61af21229be70c4e252e91f849de07e9db2acbfd05f5045a6c20c6168244343a5d23f08c2c090688051b67e7d11707ff8282fce09237c9242ca32cf4624b8637cc1279d03e4cf17753b0091699e2a4772d8af1c873a0bb01d5ca092f5abb91081c894fd9a0afb619b218701c966ad86b12d95863e06b87fc5ed44dde536adeb3a9bd5aeeb3ada1da0567c92435da50cfe5cfb8aae27a89e678f315472fcd7368edbb47ea5349193a6c0f460081abc2056612dc6a5d23f08f67042a35f2324e828cfe9d1ae5e3d8f20ad99ca9a6508449e5fc7175a5132f87217f694b7e4c95ff3bca6e5df92feed2b07fd4d51ac4a527e5f05b2fcb27cce337b6318035300f0a46944f42073b7137d18ab1059fb5a954aff20080343b2b1f163d096fb5504778c11633fe24b13b61c5f2342eb02eeb84cea214fde43a67ec41f0efab7678a226cd50fdf0f2fe07e15cceec6bc65f0bd520bff114b1053dbfbfb4aa6b62fec21b60f9b06dc87fd82fb61ace25acb61bc954ada2afbab46c4893ad1e5364d643351158944222a9ad2fb0022cf9c45125809f78a6a90a46e66b57a23c7220526f9c8f307ced4fe4027101a83dc4b953006162664f8f1da5eabd7f612063f264f17eac6d3fc789a30313235239e66724470bec7560e7fdc5ceb37a7925a6bed7b49398cae95e872b93142c32c228a30f8c7974967167fba607a36dda767d3f3d8333a21069083d962cdeec7ac5afb853c5af2f672bbdfe228bb7dafd9afc54179238aaea341305161ad17e3c94a28609b4110c45e81a50b5bccc741f9da5a0fbac078ea69c88f915163e6468e1d5bebb5b93624db6b63fd680938dc76b69dcd48966d312c9886c2900323838783407548ad3ec42da43e0a9aaec137ed333f398a883f585acf1251eab0d6051cc6d60c1835fe613e47252326266623627b2e4a944cb33199a6652b6c8ea8411400f079954ab5e4881b20911d61031e95e6e1a082c6c321454c1e318323593a0f531544ae5f0e07e58c3c495314dbf633a7a8097312214eb7119c1f636c2a0a836410049999cf12931aba8e2ff29b7e1109e9b26944f92e570e39234f2a629848601183b533ee820c59e693052c6190594619a38cd2b3e44c3b7406415711e3cbf429ad8d34ac1769685ea4e1b5bd56afed3434cf7a38d2905a50b12c510511840a0eb22c086e7ad23377cfa4e7cceb08a22073cef9a456e627b87effb0257ba694d2d8eeeefdd13d53494495d59b881125c7f93353d55aebcc8c0928cbc2105d1f63a09fe4ab6aadb5c136e0f05b2248ff5be64790f2a02fbfc78ccbc571aa9a831d949eb5679967ed9f7937ba1d66a2c0e147e9a28e2ff4551eaa20cf971cf58e738a627ca1ffbda3e8373f2c53d12666440cb6305bb29ca67eff20b37bead8429651d86429dfc711594a9e285459aadae712593e8c4cd140892c7f0509ecdf49a2c8a18c2cf362042911f6a9b7e2f62bb58d8402ee6e97b22315c91701e7cfb9177b076b6352d10ea69c505461d249a12862440e671e92853092490d854d56b35993c3e9e508850bb6263de79c990582a2d65aadf644abb54ddb9eb8c9fed2c8cdd9b88df3a73b08ed133597bbf10528fb1342e470e61f588e4fb040724330f7721cc7ddee678921584ab7484851136686046a8750b91e6439903f32c8b22cd3b6219e9fd59451c63e1293f2c80f65820897637ce1bc7762efcc5ec20e0ecb673534c49305a08e0e769b9280fb3d20ba7de0b8c6827430766c24382eb6cb49ab12d211808538638c05f0e1e27777e3905346108cd2833031b13573e6edee9e14461960b8941e8489524a1c7286768d1bf1c5a56585645b36a0a98163e686127feb7f498ec22510494bda781d8863f096e7035050f4380086dc3fe37b1b883c487c724c92afa6695a67a36dc7ddd88da495c3c8c383e4c5dde8213122df66d9032ebea7011c799c70244bc89107894d8e488a90230f1255a639f23cb183dce5c883e426f7dff85de0c84e0e67f623af188fb88e6c75cbb62cd3ea8684ada6a18673af95dbecf7d0aaad590ce2346b27d5ecdb126e420c0cd3143064c8c0dc19c0b444d0767e1d0c7764ea9d0364ce3a98fb85f88b9647a6dfa0cdc2996d900cc341989ac45fca57cdf054abfe86671f39fa241cb3a711049da9ce0d1e3e7c48e71d3dfc7d60b510bc63ad8e1b5608a54ea8740c99ac62eb100900000000c315000028100c068442e1903c8e04c17c14800d7aa4546c4a1a8ab32807521c860c42c8186308010010101899198d0bc82e978dd3ebec69d3101ad964fb9531716932afd5854157dccda4c80679fd12dab4cb7bdb7484860b7d97731ac2a7b8c7dba63d34869b9991d6ee8497ae4beb9a72c019b7e578a04467d6951a8b43b6b599e9c9d713439d502099d6f70f09a943474dacb36afe77ce4e1fafcc2eb1398fc1e28259846db656e018a4d68739c15dd6e5faf889b4b5774c53eaa4b26059a01068a371dadb144cb5a6a6545c80ed7b7b4d4f07c7576eb5b69412c503a80377bc733832da14321c8f2f36e9364d49afa67ecbbe14fc876707a6824536b55974767367604b07231a87e2cf033818fbb62f1548b0254d8b348a9a39615570a640ebf3144e7d2af96230b04f86a80506a0217c13a3e1e2508ba127ce38974381768e75314b79970e59dd8dcce1c386342696b0504c473bf31ba2aa211fc7945cd7473e907fca60e0cb2a784ca8014bb68abf4281beebd3b892dfc100791dc6cc5f57ceb70c0b72829240d839b7302107e634410aabaf5348bbc63264881a6662ff065d0e1296f443b217ba9bf7a0d1e8cb486eb649894e10a51a91fb27ceecc4db66f2c218d6b8793b83601ac2af1fa043c83aaf40ef3d333759b1b329c68c31b7e3c9edc33ad4086ff1006eba911d48fe934803f8be0210a79e39034394240106beb863906756da2467a46908c8c418a0d94d5424e0ed2f693d252143e2d47b6c7bff24356c24fb637cf65078b595d8bdd1c85bc98ee838062103f66182db6b22b1a6eb3b9bfe8c62fabf9f936de35c16b60d145ab9c0e2446629147d3400159ee3577afcb47a22b9174a90e3eb014416aededb6635a5ebe3ff1dead0229db740075700d2876f00d722f03e60381c824aab4eb98ce26c7ffc6ff2744bf7628e069195a2187c0844909af1347bef0721348344b9fe73b961836ecf08b38731e6883ebfee6aa5a8367041144e0da5f1ae961341b5dc72a8342ea6613d04fbc328d9050e59cff571bfa9843da26c6f680d0a0a226c12542a6bef3a889c7088f7d5535b82abf4b686e1d01b382921159d99966b44217cf9c647a34d8870f145c10da5197deed6f9d026c2186e45247cfb939c85ba18d4cf904b73b1025c0d48c8741dde4aa6a74a8f596c93ff82deb906a9bd38e3831d4f52cdcc96343644ab952eb545d7ea29a4e7a2c3c07d7292af8ce9bec2a9e383b1960fe88e717d25c1f46899429f2615313c2b6064308788a28ff4cf02e5f9158024bb10a1ea55fa809bbb24b478a1e959bcd8fcb4ae78acf7b5c9c1419a70f890cb6dc43f97e273e91e9cef8a026d3f71977e03b43a04f8fbdf28347fd7af21c05005f1b0652310275f177823f3e3d028fd29e7715a961c991cd6d87ec37ae6e3921aa932b0df8956dd260b4c602253801dba869dfc8e53e8901cf2b2d433beabd247b31cfd1dac3927cdef1cc04ca96f5fa6923f76b1116c8fd8b8cfd448b601001d2bb76d371a6dbb7d2f1b48d142e03f7b8f046c1b774d1e96c30cfd1a25d3ccb23d9e3d4016224f4256135c7bc58971be85730b104dd6cccd7687701b921c299d8e199ebd303f857fb4bc1adde4d4ffeac79f603e57817fddc84a8f9f6e466b8936aed4bff624982620dfa106f2758fbb3e2da32e73072e06813355e684c32e8ddcfce173f11ad4c090df2379b98068d5fc1f0f248976bb8049434b40034a16d33c995300367c74366a5ebf96139aa53397e6c6bb201a8e3e190bcb112447b9cf5967eb743bb9316ab8f00a7fd48e6efa850b6300a23d065699018cc55eac5a1eb7b12b8723ee5428890d332361fd7fc0b8e51720e3e1d35b21e7700ecb2eea0794e6de6dd7f5e4cf8857c15a4c08e4d631fe4057d1a4652b5c90a57e5cbe691a27491f635b9050028865f9644b3a2e35620b7b86b3ac2f221465b2d5f719bbb89627051f3bb3c881608d1259dd797e8f31db925894688c92269de933ab90be36ad0c361d1b8b7d55fa55c0ff83dc3431b30c51b5505c27e73553650cf29d3e592d534162d1005e0bf0c2697c8fadfb9c9ee7a389040905be2a57c7a2ce0a172de4f7e0a04313f22824e8a3540a2caf22178e2a080f2042cad3c07b68755a1428a78c1ce9271126180fead89982ef31572f872c670a5fb58cc05a8d47b4826fd80adae49d6795b5478a7d5f516268be0de536ec66881d6a22f1ca883dfc5f7dd85cc10a5863714a74e3b34bdc543070468862cdad683232643a7e31ec18ef81664068f4847faa11eeb7301f48f5762fc2edd74a869c46d6e372589cecd82701af222c0e5306e39072e84f16ca924e3979b34a4e226fa14e8a989e08d742ebf02536d8de45610b5af551138b98df9b3df4c418fe7d7d0601375109bf73649e12960eadbada8eea4ce81dae9c6b1c1e17f2eeefe779f6e9c24d8ca8b369a20e6f73b6ae08255a201b754096e0c6cf734a10a42a2f6a382ddd2963502b8a247571082c55b279dc8dfa169abec08f28bdc29b60dbbf6e410cea70a9e771b3c7c98217f6b539608edc95f9f5d873437c33a828062a6f02be8fd28841f213b763dbc3753dbbfbaedc9da9935d890b90dc64796b26c35ee9c1b0562827094eede0713157da12b5e23e344a56f8ab964cb11ad154a55153d7a597cbc480850fe20cb36ed62933531fb6388997bac69286aade0b1121b449658c029ba3456eec3d574360362c11b26842fbc232260e15819d7b2cd87a3a59336986b77518d076d4cdc22645a2ef17bca6332f49169692383004db43b2eef6e902bde34dc28b7d3ca4fb7d7dc6dc5da8de51883c5bf72707f107c8a7d31fc30252a287a4032b5da83fe091ab4bffe6dca77f02cb7894777c12be46cbd812c4d7ff7308b8babd129309b869de4848255ebcf64d4d618f104ec2ec9692d9148b6347392d29ec033293c96bb2019ce868ec59791d8add8f4a90f6aeaf50932dd83a76d83952a4cfa7511cf96b94ac76339112f088120de0fc3946aab82df09ead20b58c91899e202115f503747b128f1f1d8af20d3ca951c2827c696728b8ca5b22a5ba733af48cd43445882c60f27d57ef481ba0db052d974065990007436ad2fc0fa664d10e43df38d8cc37f2e415fc16a59d11bf0f8a036ec857e495df04b77f77b5c1853b0dc7de5ca972c5518b4a96ad7f40715018eafa4d38e3ac524658e1f9bc15c1fce195136fad0804428b0aff152d0e076d130fac2aee4fb1c19b71dbc42811a4154b59752875447e9ccce7313a70563cedf5cc7bbc0509908bb1c192bb32dfe4c85a15b0e9685f314490608ac2f235e5c8a761a750714b98ec67efa23d719b4868ede02c3b4a7edcb61562658b51983f0d9bfa4c8554272a234f9834a48955647a857f234db5b0a5bb53380972abea0810c354141952767a5372709393cbfb44cb71dc4aa8d2d746398cdeaed7835c8d5bd3c59d06cbbb66078d20f5d39bdf6dcaeaba02bb2efbcfec9ca27ff374956e808b28440e0e4fe308f59ba111a084631a5241f224580059a900d040f26b8576213718289751cbbd602971435af45e6c5cc34cef2511dd675d85f60ad83010010622059ac9bb766794b5f62ee12ec44742c017ce4f1bf14fac1a2dcd1c6c8e2715d3793c6935997148fdd0ad96c2dfdd313aefc2ec5c3c5da67a40a85ae66163e6d20c7db416c432aa0223ee7512b3539f279ce1a874d7c378fc8312234a6e560fb63ca03c2ac1e219b99bc124e82801b91781c883637a4ab2fab978193ccee77bf8445496a06a1cd4778d5a26057be5b513bc397cf9d7927b61babdac9af7a2a48a73ed841f92ca3d8ed9a07976aece0d5a0c0febd09ef2650a7a1ee3ddc2cd893c7b9566b77562dc7b3d7d92158e169b08ce55e695e91ffab3fff24bea2af7425758f4bb43a2e164b196e05aa4540880a835bdfc946625f2a8513592d96b21af7e8be466d89d3fdf6125196e3c912e1b3ace0f3b426d961b0f657c9e24b3fdcb0eabafbdbc0e6d27ac576a66c1593081ce43f78028bfb62b42acccc1a5d40521436b9826125b20ed9b2a571351b3b59eb08f21c100e966eca397f76a1ddcff2296cd6b84e96ddaef8df11a2304d7c893362862cc0237cc897be120ad426f0e8e17ca6a653738187c67d10a5266d02d737786d80f0fd949703c799963ca68a113b589e334d71b06de49db17eeb86c1c9c0160e1ce89550ac5ae40201f269e890ca0b0ced40a4fe08e07475578e687ef102fa0f807e379708f2f8d373ae641418417af5a8e04fab3c3631775693cba69a807fe78414da593067113a649632e14ec49813d77e514a95a4bd2bc7fc4553d25cb7910fd8e5ab02c68703d898f7e819e470249a12e801b12bf7646bf34944e88533ba3a36e518b58d7d92e6bf9fd6ab6818acb3773ab5a6e1180844dd04286f82f5d54f14394e7f80e613181f4ac50e89a6e6bd7c926031c6d8373e5ed1cc88e975eff06fa519073da276979c663ed30200969a7422bad888e28696c818f068cad83a664a5168f429622e9cc427535e58ffec193f2cc139c1be013304190082392a97460f15c48fe61c448057f149991b0a8200b1523cca73b45fd7d5985948c74ddf10d4adc070b3a63f840c75270b8d6b5a7956022fe05027eb34f6e323c7ba17cc9316fc10d71a0ee1c7a471c9d5f89316ac4ecdb17bc53a6eb77b2a7b34b90658bddc06b0e06637f44227295bf5e7bbe90e31b8dcc440f3fabeadc6539331fe438a8fd98ec3aee6e6fd4a25ecc7e25694e2f7c9d5d81b334268e92a9082a145a579a4e9d1bc297a16b861bfdc84bf80661bc77d506eb4293cd53e8f5e6f2df006ccbc491eacc6a414d1741e465a70f7050f172282c7fe3996b1126d079c69c83c9b798f330b9d8780569a333559163e353cf0aa5452f8bc4c47e9c8cc84984661da0e8296711c2a58e60470cc954b3180abd0811c0be74b165869d46f37537828439dfaf38cd7b3d92f7f54484aa67a2fb1ad3fd4f75e275a1b8f10d7761a92c4577314f7f0dc41164d88501cc31572260dcc298447693011d6734f5562da72ad55eeda452be47b2bdb8f038223e6e96348be2b1a68a6cac113ee038a2fc12297de3ae90b17e4e51c2163c4dfd18a59c87a07f689efe939b51d15ad9d06575cfc49f3c1706aa4b0fe94dbd064d9ff9c1cb5efa1d1002a5d9f9e4ac48a8ea33a2f0a7023feea51683799d914e9f6903f7e954d0611b599a0331736c4d8d5b826aa887a5db73cf21dbc46a391d0ef1e3c963967088b3b44555046770894b914e35c6f124469786e5e50633837c883be6183b6858c48f8e2d739903cb0ba177b7e32314ccddb63d99cb5c1c966a100daac360ba6250d3be24e1f6a66ed0256c4f6d5213107cbd451b2ee631e577c41e0086c46cf6f60763a80c271245333843489293804f3f4710fe43712d0749cae8feecedab6e2f1ecdf57f06f79ee37d66b62d726d49e865561a8959de5ef6f9683f2bc5e0194d37ac47840fbc655750f0cb0237ddc54c532207590f182035bb7d70cbd901eab9d574ff024741640d136d5d70525d86a50ede904c84dc48742be396764eb4dd6f1e78c6d09465e9ef3c486a31e08008d4872b48697bcfa8dd74591cdd3abeaace15042e42b49b063ebe1c18337872e2f483e10159f5415e71981efd3c056ea1c040a74189584a40190b87c8254067f1ddc8e626456a15f64125c4814ec70fc76118f01a1f63d452c04040cf72f5588b62ccc45d99ddd1cf22ea768faeefc98b2eb3fdbfced6f560d854253ed31afe603ccda82fb9970e8b4a23b5cb2725508819dc047a4afde51014d3bcec82d07c6e2bb1657d0f42d02576d4b9f1586505a7dc13cee536f67b0a1297965e289af59589ee250e86d5c9cf0d41de1918e74bce98f321ce51dd2dc493ef9ba3c2539e8b7a44a111e6ca477229f82a1cbec6cbf9a4a67cdf1bdcf20bd87933f5e72066e5c03f7d7c21e4ae5a00f68625babd74bf83728354010816fc233da262854b78b2f22c3cb6ce9292bf34ab09bf272b9fff04d446eae9fc1d454e82272b9f59708ce4c6dbf1f5bfee9a4f36fa7c87ec37fe3135022ce413dcf835f5cb28f0e7b1ec16808c0b0fce7428964aacbc69f673888060ee91eea5809c7d16eff103e2c2805602c3c96adb4c1abfd1281c3c01abbdcccfd711e7a2c489a73dffe4110c02a4e3c1e962a8b15c6297c8cdf7fd5d3c988a27183e511a7730e878ff5c313ba8d8dffb270beb6a127bc005085a8430db57c539eddd240ae8058015ba3e4dd07dbb046415debce535b5017125c8d4d203146ea012687ff7c5af5e3de0cc3c493330a30d0cc448b09fe6df69ac10531fc3b260d81e13092325d40ea4fe7b8fff8c748f5087ed2fe6b99dbdd7609bd7c5968b80fdf7af6cf45bdd25171fcc66f5829d9fda98988af565e0bf4ab3159bd7a209e8c9bf7ecf8e6cbb25543b8bbb1acb0979f311cbacdd57adc1da714b6246568406a4f6d6781fa180455b30889119b8825f9d9fec4c5e9ac8c9ad1dce88e175e28e6d69eb811de15cd371f283b3325cff1f187e3e8437570d72bd367678babf132fd501d8ac14313e09638fee6124fe7c53c94ff777fefe555cb1ec8c50fe9d08066ecee3c6e232e1d71e458206d7ef9700cad3a862a76b955ffd69e33028bc85127dfb7972729ee43ecc1b1517f4066f20ff2213871f626f5960e7f15e0e30f9a6e89553227c682eabc833d4267b587ed03efc900f04f2cf0be3cf2bc0553f3ab11e3a5f3ca6be04130ad4783eb21901d48d0bde2df4a4c7f54c747ffa44cf327600b3d4e79738a067226d8023dcc5e71036cbb9a12765b05222c254d6d96c5da9dc37ff40499fd5cbda413ed242f039094ec9383b01ddd95a4c95a094e1cf8074e00fd8811fa01c5047d67d1e44b22af13d33183143b3acdf8778eadbe6167481a659b0918ede8773332fa55360a3b3f6e1db1cad5aac7d50e624a1873aeda601cf40ec7dacd7b271eb1d32fe6c3854b38295c51429f989366d4397461b14a0195b97496c35d5ad2420c39642bd4119c0eb2406d064400b5416036fd1a83bf430b310b7fbdd123ff0a5780b262145cead3e8692bd784284466280dc940039f6c6deaa411713b675311024439e252543cb687403f7bb41a24226d8f2e427bac76de14f8ddfd9bd3b47efe98ed49c0c0a3dc08f42fef829da8192b44d36dcc5b7c34cfc988776c554a065cbb1f7e29f06b0f07599b8c99a18b43c9e64a103841766b3839cab0f17657218e2927719730322d06eedcd85792f4909e969354f6c5c8320f2587da00b2b45e23711379d1de3c65ffe0286d91f69624a7ad5ab49620278b3a543e32ae6a7e9f6f16e281d44d1836e5731b3a94af982b729ae1fdc8aaf69d52add6939193a237f0d4dd1849879e533c4c183be3fe2360ba2af6314a92922d073162bce8a7d297ab552ae37855cd60cd2f4b7e088005e9ab7a3c30a0a3a68e369d2702a0f14034aa235fa35f4b73311fa881724d2b213b819dfed916248e8fa1bced1c74768ba9c26109e5f6c3a9198558197b227deb329d411bfbcae1ef799aa23ee62c9990eb74b50319eee0a42a241f22e18fd14c3563780e74f2ed50a63689b36aed88aa611af6618517d605a51e71763d24d9852e16841b9b4c1e6007a462cdaaaa9480c0a5c6d07aa1d26c1444a3eed325105c50735937ea094f9d3360154547481276e8a36516ab5482c94cf0c9115ce58b4e6d83e6a71fb2eaa028f22c6909dbdafc455a102f683c07b91a4491eb2f8b78f6b6c9876ddce23507a03b016cb5a98b4ff085f0ed3118c6c1f9311c86aefc34f4045171d951e2b0bc18b8fef634e1686098aab57b49f00971e84ea4437bb9ef2e6819f665dd8c0058780dcd806903380701ee0320f1d7dad440b119bc03caac06ef4c8baa26997811b4c1870a4978f706051bdd19717b5c53959b8c00188a89017d7d3b110f7fae58aef02172812260914722c60eee50a9e81430f42c4145cd2bac0cd3e4e9e88b080918051fcf0b9526ee1852517f2146c70f6d844ab3540bc7205c0e1f0241e31dab3a1e2fd3a043fc2d5527a4acb4c20c02c2ec1b67b06678da9c96ae591382c8f16a3d1d30a7d4cc4c303b44db5a86cfef2842e09b7316219c809c8dcdc3c06274542756a1e73ee62b5c5eaf38642c0db3f38cabd51427478340d3da10800c062ef02d53ea0e1f5e6781b81dab3bebc59ee2e6dd7cc74afb22a9efe298c9fb8852849c0f2ab20e178e94b1bb841ebffb825c6b2227b8989403d08b4d073f64149ff9edd73975cdfb10df6259af6f4b69bd7f31ae9a30c5b5fe68fd884d013dcf21e87577f330865874c629f6812c6602a7bf4920108693a6562354b61f5abc36cfc517484993745b4fc78ce4740296bfe29b3515c5bf698574d5874655f5b8bc84920ba94c3a989a01cbd8048833f1a626777eae5ee2f84748827818153dc20df453dd1c3258baef02266b7aacd9a51ca26cc3476205d7ccad8db626245df650c3dab4cec670aebdd8d79a777af6639c8b0281e4907e31e7197d8dc1737e7b0ee04370af7bda3f0f2897de9a7e3b02db48b4120d23ee0f8f904b01ed38625e20e2e0394cc12e7954aee1e07dec14e36f18bf01ce90adb48331bb7b1bfc13ddcf48cdf4475c920f607c6243f51bf4a0edd21a83f7bef030db572bee84c1168a6bef3ef5e4b9d034ac05923de59655813fdb4a10d56a248d34a79b4f72fcc171d7ac99e71ed5b175e8c060a75c7840773135dcb134288dd163807247e3f63444860d2b71e1c8a07795ebb1dec162080b35a684af592a7ca4026d97e6c8b948323d507c709de20096073821601d6a58660154cd6ee284362715e7df470e349c11155d45d13abaecca7ecebfd529a6c8564ab3830e4f689a85e27024bf0ad4f67b1efee7343bf2795f8c5d7701c7d1c03a6bc3fd78585419fb40769dbddadb1f0d7724779348c14f02315616619bb335dd90283c1761961ab0b5f5b894e78d58171302732af3f5180c69408ee4803c461138ec600c91e3ee592ea0d17f2c66ee2226ac664e98ec85bccb7b9067adbea997295d9287084b56132c7ca1ccaa92cb04e80501ea48ef49baeaf769e1b09a6c7d3cd87b727d18bf95bf35263d9613e7049ba8df43c558630e85d4abfbed48a95b65b92f1d8215b0481d01accde67d70fd95e3493a13344ce6452f2835ec6206794bde1e1b28c8172b5d3d0014d61164a4b48339e2a6ca35cf727f733c1d4d664b18a4e9ad8d758ddbd286c44cb6a85afb24315e26e8d9d9417294a90fcd88b5c71d65d542d3d7c0446d1652f4578599be692a9d6b96680328863290d9a19431047d259e90f359b335d825209da045943a151921a22de935bf822744ef191423d9585f8f534487757e1801ec71fb5d937fb64e657b1d1abf9c985a239f827f97bf247edb477f8b5449ba51b38c941def2acbafcd6a41056f8da246f9b12ec080d8101a90b8c23b4f0ed0466296a55be5b2099bf38d85df8ba09fc97e3a9470fb520a53c7a1aa4040e4a839420cfd32025b82a0d529a5c4f83946207f0be219a687ff5ee49fb199ab442d323565685ca4b850a68ad5675d3591de84389e67e77419b483527edbbec825d55c57bc6e37255c78a30b41ea40cb4a0512a963e088886eb92b4378617c8aa5ea9ff8d805f6be80c5e1025a927fbdf707dea81c24ba753bdc33fbfd568d94f7cc7d3e2014f666b495c539a8933fecac778b70695213ecaf8a33bcf99ae092c95a2a8919f5902aa4d8a53c14c308ebf11de12b38d3fb8343cfd4fc0a9c30d6679695d63ed45037d8ab0155fa16155c25a0e7f655f510130d6c9be5af70f16f06c0ddd0160d5d76ddc427ee88d1d69bb5bc1c7afe423ee34e7b6897add772a41994757282887e466990fe6c42bf12be16fecab7481e858edf275cbe5e81261e76896dc8940c88e406caecc91b96a1e7d59b22562488a8b54029f791dde0c38ee8989a29c42ec665783576dad0c66148482bd5983a130757f8b897d4919ac778a90b895dfc1e7e58866768881425046042cdd32e94d78c98d2f2a5325bc99f0c9ab971af6d1f7ca4344ece823e753e97ff311712868b579abc848af818a962244d6bb945625c8ea0eba29faa9b1ee73878d1814958fde7c404fecab64c62a362dd173883027a2315d58b454ce083a0a4d864b98310cd411ff088b59ef4c8c57812f8ecc9e9c08a655213d99c9aa1d3a4b1ef217ab588b13713c71c6b686109080c48cff50efc99ca0713e1a57a268271f0b90223e0d10b28f9df6aa495243029c46782fef1236180ef33581cb62a624a9a90eebf89e88168d3af7eda441f9ba459201c3f6006c6ac7497f4aa63dafd17c7a379f6a0c1c8e41745698a4a1cca4af40596050628e07cae5498cf73eabac0bfd3fef43768a38c0d5f287c5e655c33d0f892b0dbd094b832c94fc00092a5050c44b09bfb2932911ddfd1e4025e924b837d09af61d0a538d74c2f285cd7429f16ccc9648340ca340c99503c042d63950595e8b70281badcf7e7f2e8bfe447d0073e6038e3a19e411b90ba01b1c31409bdc12087c681271f98aeb64a050280947db45b1a67b766c144efbb1ba69344e30071e77e5b34c0a9f87ccee6d1b928af47444db1c3f33c97b2de91eafc1110c9bb88c3284d90d93675f0e57d9d14b6b4d726743be344de2be51b86c37d8eb2010c939dc26b9cef51c35e9f7600f6cc081a811a76f6d6d0ddf063540dd09576f93b04debd39b387a672cdd54ac93eaedacca67512af42c15fde466ff734e573b104f8f5f69b19646e2f12bb12781c3594069ae9b78ef51cf743cc82789b3a3517dc8fd0baf5129661b95347633b03c1a08a87640c6000a205139e246f603382f555438fc8e620150ad4d2e0bb0018520178f034639259085c37094c132304a05527b11744aa792c89882ab27829ffc410950e4fc617ebf48c912616e1e416aa1104fa64ab704144c2da89118f4085246b5684664704d37b646d237d061955de83c41953e39572660a4ff8c91a2df27ac45e612914a70dca3e0f2808441dde1dc184511121aa0b1e61bcfcbc17354b3474314863c839ca69e3277d9b77eeaa3d94cb02ce2b249cf6324cb6f0cb151e42bb2e15cdb95514aaf39576e97b43eb07f3d6356c04ed2497cdd89a66448648ddd0f9836401f5a6aa04302e977bd4e0b3990c292461cef08dcb54a9d58c365a0304cea3c6346d147eeb07c597217e4a0a149f284b3525dc92d3f00292eab7a9a69d8ca9e5eab139d19d9b2aec28ef0667508bb103c25e9df8d1c03a41f0824190bfd954be7b724893460a70bb324ce591cf5b1a5a455d825fc48a75e8456a217b491a241e67cd31c8238a1312f9029a9ea90b1a0346d0c002d40a5625f2cc5317685484092fbaaa5fb63c48c9c8bdf0a040e2ac545e50eda5270c41cd3294f98362d924175c6036552d524d0ef868b46f2b6603fb73f54966e2eaf12f385975da30d88b64f3bda1e60375e990b15ef14d6c9f077d365ceff1876d87c2a374832dddc17d4849eca4c76621bdf733f62bb14663a4fadf7f5bdd41eb6741ac9b4e013ad141df52f8eae27f5654d5896105d5dfd8e8649a5bf60dd7a8f3360630abd77fd1ec74ea36c92604b92de5c5e2afadc7137eaa9b5033b9fdbe295469ab6287cd73bfe197b9c076d5827896b05c900e37f3b326a676712c0ebbcfac011819bf48ad0a0c7a66c1f0dc7eb5c3f40c63428a26d87a2beba1b5845cc1074af76a7b495a2c23fa700eb4fe1d51484d2c6fd8d80ee788782e808b837e906853c655e6c9323a529122099c6d4cf65ee506928417352f9bb342c12342241984a66a61f27a104b368b28054c698054537600ca62e81b3e5564d2dc92d9f18fe7a4ee45331afb9b450ea8e84287f622ac3f7e13aee38385c812501259094c2932323b44d60ec2c856f5e4df5902bbdf22245788ae9b3d80f00602e160419343b0dca799b827be3640ae1f59b73b72123b498160e280ece6e23d0be831adc952fff14940cd604d5bb244195d923a861571445c586ebf1e281a139909721226f31e5b59604718de700b7fe81b4da4d60ade4a00a9609f673083bdb9d7a22f105b4da6d1d9a82c463d86fade641fb85930ac39eaa5699f4a984fb4d186b9c4a085091e3acb1d0556a527e8885971725940fe03a5858400ba49ca54376b641b1c4909df93c2276ccc97d468e0e1bd1f85e50a3f73b2347279fcc511a20142c31b832f5c336de22b2aa18b98667a950543ddf1ef6c84824f48ca71df44b31c4d7a2ab0bb9818d3a54c2d02f8c73f02b4024828abd7732356fdefeeb4325348051d6a426f9915e02ab762367a46745034f21b69df61784deb31a332922d12d69352a217ba4e52365e506d6a65f855c6258cfe3c0e992b297e33770b939c23bd7a1520c39ee36d6825634691a26b28aebb99f053ec967e5ae238b37ef6520ff84f1f2de07150527c23a23813e5dbbc1925cb0758418c8c9a771de95c248887645afad21f2f9244528000f3be3f6ef0483420c73ea2720f0fd3db563aa434d85ba602aee43214aea39d961bcb6f8bcdbc29320c866778fde045111b144221a4479762de4d389934924525d80a38b5f68d43a9fa98d36962a667b64a5a0a7d1ecad08d3510435cd6746e66022cd290ec295086b6e3d2f5ed571afce8f59581e79bc9e6fe51bcf1178cdf3c2b2b9eeaa53ff466e8e6b7f15ae0e8adaf0365a93a3b3fed6e9b2c3395494aaab45b544445c08c30b6c197852813654e6b451a8280f1c390796b9915e27af891b0fa89314eb8fbbd97ba20d0b07dcfecc259a7eba1a31eab395b053cfb705ffa0415785340b5acf7abfca780fa131bcf78bce8a34833e547646a2391ecaa7b94e7425978b9e4f80764eb321da6f54e492c2259ebe28109fbff5a842b219c62848f78d1d48ee61f8cb8d13ec87018598a78af58f90a7ed15a96bad78ab9c16c56b4ef985f72f65c4ceacc01864979f23e2037eafe2c68c0ce86d8064ada9b821a95fc006d6190bf093f7ae4ab6f2e0171437a80d80e647ee7481bc486817427cf6063616756121183482102b226e54d7dcb13f39918f413fb24700376beba2a02f80b9116bdbae3ec76d326928dd7096d23d596bbf711cb6225f55a2288cb861f352cbfd50d10a89c537f9b7c8c2005fe2881e12c40d22977293221a167ca85084b8213cc0d1414714cf1626a1b6f5eb68e5e0ee0d4c3c2736b8ec941fa7f76947dca8b14e8c24d7f5a1e8c2b025204913ca47b5048120b098713e2945b8e579f9fa92a159914024f764249165dbeb470ccb8e1a4232b4eccb30c24832ff88398d7a06678271206e8c73264c7d1037e00a095991b102d5aaadc8ba9d094213f75cbf1e77b26b3216f193a5ff1469e9560ccf00c909f68d0294d001f5860691421fdac689781680215d8cbbba8fbf2845dd4ca1689938af1b48267f208f757fc7f8755ed27466c7d40a61d7d3fccef639f617562c65bb91bba2312fff07c508366ecd0d6c925eb8378694815291562c86086ca0193ea5e5d5c4909151de980c629f59350ee8a182f6372a0d1890b7e4e2daddcd58cb058af8657162888d5359b45d87abd9b1043613ae4b9ead31b5b052906a6ee4a04d83994f4401f9ed4ba7bfafbfc3f9ab9357dfeb44bb16e2ee63f92b5191cbee5868f1cd471016c5896a0dbd5d700e801ff6a1c170e42feb15157c0461a4b768de28f1303b2fc42c9fae6a02c24907b63046400cf89f982b8e45df053a698a51fc8e7a01cd69b38c0812cef03414f3054c9626731bed1c4e4e60526ec864586ca9e4e01159ba97f1589bed1c3ca9f84791a020087fc4d377199ef25b04bf659a82331fe1faeedecef34f5348a0208f2678c409a42fd0742f533c30b308da218adeff360beda287346b832d6781008eef1d296d2ef1107f35671407211810044fe840e661c455293174a6c85558ed41428a2d1205e0b160df84ca260855acab7175121e367d79ecccf45d20b86a4a0ba5fc8ffd96fc1f947cd1a851ccad4cce87be612d093a2bec58b38f355f7381ffc4f2f10fac85f6af3665f5813185f8f4b52f8ec4cfac63b6fad1991efab4367bd703ebde08e207d8d9a1fcf5786b7e0278cbba368ecda46d3efa17c59ad563959f013d378119772d1358f79d75603ca334874354c4531258e72bd083aa477aaa2ce7d3373c8ecb266187f2b207892f849615c3d69919dcfbdc7774f1aea14c577f12ea62e34b10986c4ac7bad216674163923ad37016301ae34abb3ed0dc4d989158c113cd8eec709d19a239a4e0571bc45a905005958179711db7eb1dda8fd3cbc39d8fe02511d5e5369122079f6496cb1b49ce699acabb169cbb2b54d9bde528ece61fd84808bbeae75697e0bc607d565c22a81c8f572a6839c6c88b4bba558a7021cbee1b1143628096b5f1c7905ea73f98fd7dad54be6ff168bdfae65743001f724b25285f6ed59fd7565ad5196b9ee216e22d836be790228d34a42d5b58b134cea4914037e01f8476f559020dbb9d23c77fe175b70b1cee32edae574963752b6a813627a0ad74ed71067cec89fd6d0ab4c88ce7da501627426f4ad33447efc427d00bb9374c826202420e2056fd40ef8be65fc09c9829ee5c12bbcca8d065616293735ff8e596b86186f8a60daf77d82dbb608cf3eff0c1f6092bf7799aa8f2b33017373cfa47e111aee21a74c1f0ba1466233fffb18c2d0264356f88734663f8de233366402e30f50324120718941b010659d8bfaebd88d90467f7e811e6bbea564a71655e766b7095dea3fa1bdf326f9309521eba19b09cbcd95b84d82c05588257d23b2d375e70441929059958e6add109b8cc27e475e00041dae09e7b014173d11878d30a33b3058c6608a9f158ec1d797b9d3449579c20ed60ba7af10cb901c2ea602a984d3f817d9512d23477f97fefd099987ade931ed134896758221842045b52c3347bbd42b5be96018216a6ffbe9572d82c6fb22e8956328aa6cb730c74c40213503a08e51a253818a0c831090aa10eb01de0c40b3e1518fd4e60f36273515013e8e4422171426516c669bba4d214b49ab4c23a5903d08f7f02ab9ae07d78579a97212a45239c497d1416e907573505917586455d26812b91e09b38cdd609e11906f32086c3e3e94a510fddf2999a7277135e2d7d834b948bc545cde1d1f64beaf48c0c705f9fb53264223841603be44a8d2d9d8116706032b074cc0b363bf3c5d0e58165ca985d30a13858fa39b504817e9a446192ae645bd5756fdd6ef7b4124a704178d5f5adeb0a74eb70a581250dd89e924a3d29702c5b7e0614cebc585edb34969bd13c1c7af1d3ebf4d7cb7e056f2aa69556c45b98adf0dfb8e526ffe0992705d18aa875a528aebfd703ec9e2463c2f481af34eaa337a4ef2e66489280ace20497e20d4fbcca01914724a9d807bce0069e8cb91a6db497e16635c100f77e9da64cca404f09479b974ee305bdde247c0c16a4e9b7b5e79beff9cbdb96ed3cb08a4310fe5124ea5f5fd3535871582e0447ab3d0563fbe504303317f3f64bda3aa5a9f8c0b68580b181c6ca0c020c7e0a0dd766f19ac58fd1be79d67e69bc458941aef3be06a02e0ae8098e96cc345f1288b636d21e9127a15efe6cbe157d7d1b161f8074d1e68c05146b0c5983cb01605a3d66c13b861935e8d1ec2edbe619eb41274b594adbf305d41adb07f5f21d4beb4553bedc050c6722921be406f4e81602c8e84c7c2ad230f78bc4907aeb0636689cb7f7caf3b402b57f7113dc4a3b934c31e7552afe90d03aae19c21e5c0e791c64c7e96b808b13710e3265a1d1519b128f4af8d88256b6465cce5774fb0ae3029508c275a0e73b9ef121eddfc4f1c7de930681062f23f656502a23698784abc92327dd13a4f21228cf0c02234804b5b57f82ba1ceb6ce27ac42bd1971ec18d3ca7334b3c299a7c00384ddf3875fe1fd0acc5aed2c3f9bfffc5adcd71fc4c986a6ff8e005a0b9f88280cd92e0f995a068e5c1e8c8737d7033a59c57460d1bb78b4c909b66cee09bd22c760fb4ec715d2f83e53ce0d26e4894447f80e5f57f4ab7e88adb7fd34ebfc085df18c438accbced3ddc12281200cff2e09f14957570dafd883fbcfb55a880e99a319568f04650ec8bf7905b12f514c599d5762eac8b1e2b8db74d4147b9e8e04b9c8c8b2e2d43d1ee0608b00061426d5f08ef8bbf9bc3481dbaba4a6f4ba2ab6d6da4da50ddb87ee810a62e082d682425e2b700004a31ffb5af5875eb1a57bc08939e41b09de43b873bc0e0d5446c5eb94ff76f0ba69247178edc5f4d7038423a8a609edb99da8fc17f88d17dac7475872709cbb4eae320872c28ea3e2ebdab54ec41bce069874997b8d7d771d2cb4ef70696eed60623b19b72a8900dc2c0f5c8b20d0b17cd78affda7bc4d6bb16e78b87e643c6e382a3bd398f2fdc3dbf5b90fd7533e30bd4aa5a02532d27ae217976f005147e09b6b1eebac260e84f82aed26524327c6c617dc76951c5660c33d175bc11537d877893aa5506d9aa1b636f23642b1c5666dadd9f0bd9d97d04ec3764d75502b8bba240866614b32aefe399ca15643636c6c855013658981443ce12257dc8dc7d9eb7e6b8f59760c98e767361ae5f9a30b7a0e766338d02f6fc79aedd2ca701d7587f46b64ba36e4dee5b18cd050acc098f4b8f2817ab8e338ed2432fda9e8e19456664bc8d5bfe4c007ac00af784913923cd9f91b720de7c46e6fd9d2b5549c5ad08b9432930e8ec9d7bb228d45e7347af68f071ec7e4f673a2c53d6fb06a94eb6da71167dd6344d505f8791138fb3d01ded94c5a01da89d25c54687e485991a3ab3fd227dc709777b2e4431d88e8c02e7d67f4a679fb125a5520ec4abeae7603d370c4ab7777cb33221be62d876476651aaae35aa1c39526e5f279790abe152f854192104ef224af878547706e0361650c99923f75c9a6c1264022399220ca451367b8dfa26f4d3deb70de5f893959982aed99472f9c46009ca57f1d2349d5fb2a9e8069f887a7c266795280b9ad5c6e2302670c883954330f0f118b2c3e1c44b3de871e6a57ca23816551ee31b15f27a496e061f9603b102b173f25026c023241fd271ea24f5eaa0561ed2ec1f09ee42f67f4a7f4db12062e7afa2e7e10f37ad5a51340f0d89fe0e1ea50ea028806105688db4006a4ce3daa61b8a308f64f89986a3bf6d77a9aedfc34da200fc544c3caa23fadb8f9bc88aa9aac649ada78cf970bd8149d255c33011b9243670900b62cc36760f7370f91e702a1ce4515638ce24f9817e95e16ee5dcac43112120b7d00a13ac04b726f948211e281b9af0b440613210f0650d03f1d78e4de053b9800ed541d3658bc1b78609b78790196e19c5c6b5b5dc9c63a1801b92c439f2d3c36fdedb82986e74e9bec9531c3992a3203b38e8cfa356133c65948bd82cc6a3184838cc83833106aa7cf17e04a8d522fbe0642000d93b6a24f5555de3bef8cc162bf848a75d5df33108aa46609a3b691ed4bfce93020ee5bbc6600878122c3236871020dedb46b04f0b51754d39d89da74858fd0e741787072515a5cdd841d67fa6f33dfaf101868674b4efb40ab740a8480228406632cc70560aa9002aef479c3a2be9cbdd66c96b7569ac8a8d0268e62715c118bafbac4aa5ced5119000af698640cb0965966ee3c304a89b24d8fb9f902bf51ddad208ea140ee0a6d7657446c84884738858610d3230adbb4ebb97093b95fc61b32a4aeaf487f8a1017c1762f36962823f19d6a0011ae2f9fc8673a613982fb30a2d6cdf98fb85b3b6f30ecf15e085144e9b8e33dcf8fef93b100d1862ad6d6c6e8d634aea9b0ae97084bab5c7ead500db3d4462625449d079ad1f14594163c2804a6a4f66383c40e84c82371a3582d0ac0bce896ca7d43b457d1f3fc0a73e321aea5a10d935ae8443ac541f73c24a03d52b7024c04008a5c271fbc2a083d5f9fc8291749ab16f508a3f9c62f5eb23b35673b1b251c2a482673d6c958a05fdf093c43a931045559fc18905f5dbe7d84af378838133ec54c208f3c398b6e02288e64a5d0ff8df9b1e0c302bb6ea024cfa827f2f516508a9ff8b1529a8d21bd9a6bcea73cff14419065d8f04f97c0577d9ab5e7baeeb6e42890a4a213fd25ccca5eefb03b9c2e2ff82586b00815ff3eaa871c0f47564ac9ff11f8666400c4e4a43b188bf0b4d88dc26b3f3924cd983a5dfb2b26132e4cf552ac2414c4a7e910875f805b03d5d50076c5eeb1d607e2ed14b94f47ddb82410d08999264ceb8954183cfa34d68a8d78869fade22ddc8a2396a63851e141271bd11498c5200831d8a7eb314a300b89797ba2c366ddf344c99c41f8660313668f0f9e1f427b0137eb6e3268623c7155057d89c7a7512d900c6e4ab862533398166bec47a5853efe36151710290a24061a33c872829641a94ab900a41fd194b793a6b072a0fcf2f5418eba789cf28a9e4064254f6cfd0294ffc434550ae761de4cebca9f7df40d26596faa49e316575f0af20c6e32553b81accf68b036454a6653f010bf673a1e47531b8f360bc0f2f218033988e4441f3ab2facdff11711ba95d917085d8e3bd7d4ae0de37b0ac53a13313d17db7db3daf906dab40325a18cbf6d7bf2dd9aca2efcb7d11d24ec4c561fe30bb46701cc440071c0ef2f038b37229183da5a7ee8c84d2a0c8bc02da69d75423b9a238b8176a0b7cb923442d054abfbee6393c33308348178ab4678c524e76377391d0c4a83f39e14befb4d64374c01ab0011ff2da3ab171ecb6c0f2b8fdd14984171ffb92cc65751fa6058959a114bbcfb66a21e91201158a9197bba4dc36b3d83315cb29f49d123081fbee01327050b6c0f9a99d48c0b993103fdc5ff7cd00baa740bcc9cd795d40c8dac6b0b55b3ba7b2b49cd389f5ccaa6da0a6d6d9932d8d46ff89a96a0c102efe04af53b587bf489b4f48dd81de269d6a9caac1eb029631dee8e43839a241dadfe6871f26e446152498b28340850c674abe4a1aa5fa51b696db6e8cd8a1723fb0b6db9e3c212048a7356f512c5a48aa265d06b4937d16b94418f656eae20335e63b1b87535588d16de6ab8928c7b2c1dfba1f2766d6d03e15a96c12b094b8365dba322816032edd92018f8cf26f64c77bd87bd922bd2232ddb3fc68686cc63656ed476b3c251b1788f81ccf17f91806f3b206edb67d55add4513d41c4e1348c4208e39752dd8e6cb12c24bbd8843aa9366a6db39d97ac365eead15c9a1fb61dbfa6807525845ec4daf091c50b03374ccfd841f663f6e282f8b46114ca5e7689a441ace9c94937dad9f98a8678a0ad1617ab27755ae6cec6865c0d48235b9e235084b2c9c43c0fbe39a30c7b78e2bbd14389e91b6875f43ca189e43195a031dfd135046ec39564394383c9b700f0784713f85e1d7470de42644b251a4198d915ba8e37a5ef888f402742eb302be60309d8c2b99755a74cdb97f5fba021436caf39c08b1f846c50e41740ed6593118edc198bcaed263c2125d5e307203c93c9a32018422d86da5e4220b6da976e2aaa3a71b79146f6a2eacc0848c958b9a52e3d3fffc3dcfc0f9f3e64eb60292ee2ccb779049e50857c4a18c9f3b2cd46cfc9b77660f7069c5da1cd736e739aefdbe61c0e4a917065610c6e195e6c8aa9aa100b21497d4e2190bb48fa42246186c1bfadacf1d0173acf1fe034959daadc825d7e728202b0e923f7d2f9f0b8b9a95b98bf8eb4228fcd65e336447788a8e425f50db7dc0346103db520810f4cdbeba5de9e2c491f6e9482b5dd3e430784a3836d370ab48f364494bdcce920117cd87edfe798528b2bff2b114ce535008e23bf40dc10f8ebb34c6cb75106f0936e8359122248f0647e3826d55bae692cbf99ec0f0c8b6b86201444fa427ea6075c0b5615b6943dbb8cb46ea05840654d70143839d9f295d1e6b7538c706d4164c0d0c8c0d91386a7330c8416d4d4d0f440993fff016ab7847c74068582014dab4183e3b6521b2af531e7da9f60ac1bb529a8ada1c179981a61033476f03dd109375fc6e782824ffca44bfd246ac545b2d34c6cd36db0d84e4755a07c3d2639b76097ef52fbc82c09aaa6c03f8aa9b08c78d535bc0a92039d6cd1adea03a1cefc16cabb6307a96e7a76e8c8c233469c560fc4d340fd219e831b62091115367414d38adfc07c2e5d05c9a84319efea3814e8f9ddfc810986638dd1bba10739e002ee76a776522569d627a3eaf4d8f2d575b00428864bebc256663879ba56e7dcc6af054a2e71512053db97350087bac5322548421c143467500622301cd8a639eac48511d636ab65c6fe24aaf5255a9567296e1266b1873a39049063d96b861c4c2d19ae7cf147a575972a749929812d0b02a88cdee29c6d66555126d0ebedc6375b0b72d34c408bc587f36d254e1c754111d6a0b73da480c435356507482b76a15d45c9c3f2369aa32f76d53ed34c6499e3c4d23ca6c204a935dc73ec72fa50867a825bdccdf4673bf1a6ec351759fd2c356c2b8314c7ca849968f2c86d0c063a5c47fcc0f244db7a12ac93f3312bf01cf8f65dfef8b556494708714eed336a02b7de34773f2776cd20eda8a6de1c3356946a84280e0e3cda3e60d3714fc3cc06e8fd1f1bf522817041d4ee84c66a22bbad8b59c1a9fe08bd09d942fe76f6e294ad83d1009f51beae359bd7706a4c8f48308a0e07977dc6551d2788885a5fef8a1cb997ecff3498f2895773b5402bd3abec7e59cbc5ba6dfdb90510aa5b62800de3474733cbc155efdc6e7fe96c9bd6142c4a56c6bcd6c574a24a466d94bbbc434084c79a3fe092e879e4028f9efd1dc58b4df502c6daed5bb62b56d7792f054138fe784ce10966355a0d2771459fd1a18d84cb5ab1f196df173f7428a23eaa9c6be9e3845282724d5591ab0952356caba839cb71c634469969df50b8f8081c8e804767126fa954efd59f028eda67675ed6f9e6111e05676fac41765db4c76ad52b5b97c9531ecf16f5087d2540d9009445d2271d6a5ff5c3fc97293e05c9c848e24275c3fea1ee3cbeb0899cb040874ca2c94916f62a0ee76ca5c3fd19e610d9640ef5e679692d8d8e708a8ee605e71f2a214f1a16f3461ff49dba31bc911255ae903e49f7b9a63554dbb2ee9a73d32c1f724f44f01c9ec9bc5b11f44730c65d12ac22190c54a42e0f396889206b36585739c9b0b33344f824f2bece4425ad5f930eab12237084f1305118019ffa52e9221ce9570b91f12a56be2f03914ee4349914bb669a5b8dcc8a64d2dd9bce7a6dec926fca8ab482a8cbeb1bdf868e4a903247d5f3c0c88572e855f5b508fab3d8686c318aa8705781c262995b0404cd7a8e5b3707db6f98075bb36d0bb3f11c889be68deaee379e8c9688582a58116944751bd22a710690807dff7949680dddb6101bed5543a8899250c783f828d46bb182b8b42643e74bc1580a50cd70806cb52d6a6c2947149fd8021a65e3c5491778401185acc1d5ffc3e8268104ca421f3250a76cbfdbc1d73c74fc9d4a197d7c47a081cb7d14cbc8513728c3098526baf662c7d913665bf4074fb884b36e169695790196ca4fdc7d0a9dd42efb4a2a4c6363e605b888395e7f06dd107c66792d6971fae07defce8f7fee1deeb6a79f3936ca6c7d4452fbe98c9d38842bfac78e9c0c805fb20ab5b270801daf00dfc915841b08bf7c30cd1ed9376acaeeb5ce48c4d8d39828c85f68494ff183acbfd3a3b50297936adb562126a673b509ae37f4829cbb72ae1d089d18201548fc80ab486db49028cea709dd03e4b832abe29bc428d87d242897c23903ab8cd4f40829d518f172f6e29dc3cd7495bb2893e58b3121a18a1129ff601d778471c86cbf4471a09271e00b63611d6cacdb4c8a99b2f489eef5f6e13788d73c839ef66643f018735a7054e0421f4479dfa57246939582950dcec1c82428aaf0fb15efae6e52c274b217810cbc8ddee561917ec79af92e70e20cb7c23c361c5bfb33370ea179a70f0a1091b5a1a171ec63d4bb85d9cbbde6b775a3f4a1056ebc5881fc7ec58f68533359dd79600233fd1d006c36dd9abbedee48d3f418e109bd6526aa31e828318498fd039fe1389ab0b838c544c685f5095b729831284b57d6a022077673ee57ceb82d6c0d059b46807184844bdb9e2f335d5aee7164183bad5b3cc95b2020a749c195defa3620c4a963e038ffd0fa2f9c50759aa549d762c79540636d022638d984205ae3b54acc703650a72d8d5de806f6c43e370c5b73b01d2789d21909c0f958e88d637e2ce45014d9052d63eee1662e895a11aeef389d7a9a46437334501a15c34b0c9e0786ca9d7a76e8d17f671696eb53ec0c595d266fea97f93dbf8233be57ed9cfc667b9f95bb1a6065086f354cc583cd69a9e74101cf0065a9f3ca65085e958a1614e473e763dfb6c18419ae52f086262d0e3f9e725334e9df4ae1512b703dd0f33674f159b05ac7bc075f433fa6118e8138e992359fa548f20413fc6589c03cb3a192e2deb72ccd15139d3cfa00d45ba90af0a5e50dd320537dacf833a7c06d41e2427c4ef38f1cbabe01b42373054c7402ec92fb7d8b73cada33b3bb557bdc6f581ba0e7576f989a862601d023dfe91f48a07a63b8837c888366a8b9bd44f4b85f9bf87415e079bec57dc2dd4bb07a3c91878496b75d520e9e7111682f9b8a58fc7dad30057b77579a49ff02260fd7b2f02af115b4c3343efa01a0021ac01a4b5242032e955289f9386ba46f3068942319d3d3416819566182faf99978c0b0d9f2c0ccf1ad9176fac161f349177fe8e0c0fdbc1e26852cb178b65cb36e2f11d10c0e2772bf2459a14d353a7038744874d515387b1de335f840e4880646e8f18408bf4f6d8e65b1d2160d07f77e097c77832a6ae2baa4502a6276ff9d44b028c885bc52b58077987452572d51220815576c3cb8b6c144720bb0290229bb713f3e25cccf3f1e51a5e753d98b3049d30ce4ee447df112ab1f3a2df0820390e5090c10e30ee0f2c5498831cea9687a3448f72847a04dc594c66f36b61af454a98ffb76db7defe05d7bdb034a653fad10cb1b8db44288059db66087eab6696027c09c486981b7791308e09ac2410965a2d601aa0c59db444ed150037c2f8e3a7ecb50c9f5e64cd232d877d90c06ce22914c0e088ba53de113962a9fa276beca595cc5963bc1409b003d38e1047eaf3c7712641743c19de974c9d36353bb5856bd0dd2846652ed090a4c504aa92f1d89917240d9922141abc5e1100bdca5edfe5afb989bf43f089c2ced7399174270968b2f8719ffced5f72d6a3ea437d6f1b4a3746ca175b942bc8017db56162ed2a764c3cc9863faa76aaf083d4379b50c40b7d042b2b3e0b17ce9c197d466a3c0d74b3da99c12bc83346e98b7262e8fc901c32330b560137c2ff765012590c4d72e21d800ca320dae14b2c7d6f03d4ba56d218b8960ed19b90a05158acece35b15702b1197235780e29f79770b08c99d506827b31e857ddd2723cbf2eb385e0277144c0b11d1df10a789b634949d27b252bf1e444f13b1bce9d219bd5b378a5251cf39cd95a131495bd6688c3dab44ec6d8fe25f75d57f8eb7538b065e851b40eb2d583a88047ac78f918cc18f6b201405d0883b51a919dbe8dc83f5112c0b3ef98e7401b0ec95e9a3007a8b684a01b637a18c2961125a4189e08986911afa90b753bb66d25b5b6d939b4209e0122eab05e055fd521e4d87f25a8e3e671befe7bca31f415e4e49b03e054796fdd40b2f8eec695c76d4f102bcdf4617f0ff052d1dbe00bdb84281f391f7ee493fcc0c32526f58ba8c4bd9c817e0eff4763dab9140a9b2bc87a34417650a2b59e0b45b4054adf3451bfa281b2fd49272cb498c45e4e1fd62b5c2c52a62af33e0a5bb43b147482734058772184b37cfcdc3d7d374cd58a6c1aa980bb5e8ac04f103aef67e04465ff81cf908cc671c37c899bf27e33e93cc4508ca9744d2249c84a51ad7c880b146638089cd53a0ae810a797ca5d21a0f26add5664954261a11fd5b2c4b913a04374bc6c2a5c97520b965c88b0075b391942c6459d525e8f46641caed691d6d2e68b8a60ff2a005d01e74a967e16836397ec8416be52be05ce2a1485dbc1f3d87c91d1750824ab388430dfcee280546162a531dcd090e5e1f66177f92a017930c38508204cac0b210c12398e38a7254eec56e12e7c81d55e8c68e97381aabd755ec23c58e26776f9e11c2d69ca61f74580a4e5a35816a498f7770b1f12059db29c1bdb58b1ba02235e270bd80b522a5111ca8676ddfc3c2b83e39de8c329676e607f20080f4a4f951acc4374f4094fff67ac0d7090a03d9e1d4ec22b97249c31d5505119ae6de2fc32c50ddf9655e1ca33cc58603d55d22d9a1e140eac20ce8a688d70357a90bb4bed3fb2cb95380022eb5a788c2d2d58238c506c968aa5bf71542c9c37904a5612ad484ad97fa8bc274abf399cb662b896ac32e87923790a95e83c68643d7895186e6bf5af5945b5c56d08b939bfc84eb26c1f72fc484321a60b768867e5b5bbd07d5ba700578ed8d76925a6e261379f1186e5975b23fec22c426c46d9f7af90098274669f764aac24c589a61fba19385ee23314fea74695be54b03a0ea1f543146e332ca84b7e597f26472918c0b1339b4cecee30b8d029049cd7f350fb689b90855835f38e4d5a1fe2b301d2c313437e0bf6671c017b4882d85b28c6c8bbb119744b7f97f4299a5fdf6c65a5667af67509a7c7685657479cb8821dd47cff2780ae1af0633801e9a6d4f685ea73c65f67ea159bfc5760f8916f0c4585f11108968184a5440b7bdb22a68d6911c87e8814bdfbc492357e89a1e812f36d2b0af32285d9e50d098f85c3347bd521df75520fc244a3b9c530f23924a5ff49af4a120fc810f398267ed4da8c02ec72a6a4dc86581fb14b2108b8983e5532bd6690165a690d7d9c0b6bfd7a424523de6318ca660b865f185061d18101996d03005e6c03ef3defc61c9dedb17eb3ee04b9efe652038d24c76af80f5ec137e6d079cd94112410bf12a068a4d679abd50547369bd3114304d9659aee282081833d86416a0d605eb4e758b94e218a33e0ec171d03f25846cc264dc3482c6abe99d110d886344aec2b8aaeaeb9bb93170c7d17dd396d2ad9edae59652fb02be73d614b3c52edf348e7dfeb6507b217a6165d816308cab53598b1cd59f079fcd2bee937921f87a847e004243e16ae06c4a8ff1486288a2bb6fe3e78e7c26a0840673cf0aec36f76f0a7dc51154f1d964c22a9704f7471a46c895610e5c7f7d6b463e897bf7689574e31c43c401befa9a77f9b4ada1536ecc9d21ae1fe222cce40d935cab64b5d675f8da85c239ed44a20028b19453b0e3c6cc215a69901c55d4a7502c72250322e28b87c39bd8990595ec88bd692009a15ccd61d7450787033deeec2257693886eaa7aba578592342c22971cb0dc54d14e7b8131fa1951b00891ed4f303e57089d504fbe6d441d5cb45221c4e71ccc8c00aa87437aaf47c99e77b1dedc0558951edecef33e956ca5b34b8946616890cf0d1b830ff1d22e945459134b2a5a917f19ee32af13c51440e73e4303c381885159b061d46048d52a081ad35caa11dcd43408f5a375978ce11849b213779321b82aa21c1198f54d9e53f930f8f150c69e0b574dd7879513910a839f779034db19d3b0694c02a5a415225ecc51f4eb6a80c37015d83a9775d09ca624eaec0302294865b328fa7c248bee01aa8e3cd031ad6e239de07bb7a4f0353e159dd6fb057b7d3e418140cc1c87222df901d5f406e95e83fe427464e767f65b37d5c2faa15ce2904496e56cd1e045ca04475cb9e0abedb71370edd8d3a8599399d0352f60ea7732ae3da7eb3bb04613103d1f06c08947c0b221e70f18241f9a55df80a0648635daf2b2070af01ca9639551cfacd1193597b0b2c18833bd917cde21e506926f7f210630b81c8039b46b553ef995926142cf8d29c7052d4849559acd7bca2382e300e529b4d11323e6b121951c82dc75c01fa5d203cd59d020eefa39dfff71cf460112c67e6b12bf322ee59c4bd1bfa61d0a5e27e5229caa1a1adf802bb04984220ae63ef45da05995c13ddee6dc8cc3ec0e71a8d741a1edba7e5c2aeeffab5b59c3745feac87f9265605b2e3ada21ca4509e3b7ec2111018abc8e3502cf04a76475195504b03581b06223c96dfb13e7f63e1fcf5967dec001e23ffedc191dc056471dd4bbb42d3da14d5dc0860cf3e2ba2ff15ad0082c6fd0b5418f670c102cb5eff31e20b4b3f4572380e1087d78983aee0ee1e6f9c87444eadf4ef35bb59d1952bd4983b891c4932dd67088c62e681fdc791434e4244687472d9394db0a2d6b8e1d22380b42032a182b62a7cea9f900ef3141f45cc4cf1779550df13cf1e10ba44d282add31e68ac137580a7256c9a35c1bc15c64d8e1b8e748f04bfc2b92862437c8ae344af6e632bd402353aa5ae32da386f4bba7e3e714ee720b50a56b7dd7212258db43143d892d06748c57b85a5cb1acacb724a3ade1282aa50e2702c21b8e584b70755572017310c8309f8d56e63b88458704678cb8a20112357d856d3780d7e5d7e6c35ea2824d4a59c0f5171be5c10a60b9a07bbccc0f77500455b3b79d7524a740e7fc4be1188222674bb0ee06f270a156b098f91b152f65fd0841971a4db95470c06bb9fee368f6014638427059444ff66e5f9ec8dcb0bf4c240bdf6cd684f6d7e10b2ff99afe83fd88fdd3f2ec9778f0d141ea7333bd55097da34ad28e843d9304c8a39f20ed91afaf26339789ccdffe98356cf99c944ddf86152a7f53b5ea89cc438248431b05071e8b58815a3b4f554fa49bdf53e5405d9f9eaf8150422cf19ed598e8506b28f99f1301a5ed2370643ac1788d98b5fb1cd98253200370eb19e44498cf67aa44708de7b3d5093df6d0b09b0fc8ee487634b03b8081a0d409872fda4ecee170add929f14701af8c3075b9d98f62f563d49fd92a5f06baa04008e38659f7789632a18eac35b75c6811dea9f60583924accd42c3e7cd684965007fddd688697e020bc703c13f04641d2aba59d090a670456238646ef02cfa0cf2c53ad08a66d82c0c27c84c44ff8818e7d86b8366a0529267efa456eb3bf8fc08411d6a0ad101e4f0f13ea15439028203b8cf757846ec26ffa56b19dbb13fd62300dfe7bb3a079c52c2ccbc9094d5f806bdc66a4533446c48acf7fe43a5570929653ea63236216a72166146f1fb51a48915918adbbdade8139910b02c1224a30047969e6701a3255efba3bb78bf5789eb80daeb4c4369cfb38df3e23246afe4ed457956b29411af99ed79fee298017375cc2bcf52f5e73ca066c045d658e1e9400abea347ff15939d3b76405c0b47b1fd24e8abf5c377fa0d8bcac5309ac06bc69e63bb5670725db1f45dc3789452cec06fcbed6ac018b41f670b2a547201957903f476c4ab2bd75fddf5666dcb13d09332d9b5f0fa936e5b952c5d78c35aa48b63d22c1e2e07dfb3663d9b18484bb3c445bc01030397346ce4de56c16020d40ebd58e1e985300106d8529dc2975db51d3704c954db5be42c885e223aae04c2c095e9f9ac0b279f236cab3bc111b20d4cf576d16018fca17be356180306f40978bcd0e8111e1beef6c797a9ad62cd5168442bd5d7623c4bb4e51e02596d1073c223cdde3b7b08678d51b2722d68829b48c69edb8267a3aae0b1603e09d34def18c8299891629dfd406fac4864260397249288756ba98c718356392b8ecd8d1d79c78d55df497c3f92f502b13e56a98c47638a5708a0a6e8ced60a529999b10b3ad711fb31c8b1de3e86a0f1634634c088f22e708fa842af72a2fd71159f49bae0b08f2f9e1e11a1f1b8d048e465ebf16782825d566101853ab7a8b078bf99e324bebdbeceb3ab4bdbe03e0ccbc2a470981b844e3c6d7b19e96b95a1b0715aeff4acb4cbcb5d3c52788d56b216e71434b620902886921ed36a1f607d7341a704a423df39067910767e6ac11e4d30ab16e822da88674408eb57d2e18754af2cde68732012da85297d32cfd6ef6bbf6e88020b2dcebed28146d7835f036d0d1a573942de01be8642f5523535599bf49bd609a2da46623ba192452347f3fd8b1d4f6a7a7cf4bb64a0b4fa949436e98dfb2a523f258ac48e5b3d3d21c1f0860fba8658e54313dd5e36a6d1b823f99ec78df2fbe88f910287b215867b5d6b54f5d89c2e9d29f10f24bd1b0aacdc6d312f4404480a8582138dc818bde686c9533011fd89137143a95acc69d4aa325a50672f2370a8d2ce8275291966a85da7c06be223fcde18fea9dd1639904a55c27cb152ba0efd06376ae25b5b9925158c1ac11cec959f9c90af27a62d839adfcc9e9e2642108dd1ffbe4d9cd9b76463bffbce3a5867ee31c1e7bb422b7dd8ceb57df505d006bcd16074a335b00314dc7e1ac4a9b1fa487c1da41dfeae14fa3a15bd59c4c07cd1f7eed87432f1c16ccb453732a060a94c08fce7b28e915b06997dd5fefea2e78271fcde79b8395431c2dd5cd0dd3248c9336d54acaf0a4dd9a6a519d956033f91d25481bc69c9be5595bea58722811678900e01942c1d1366f3c00891bb727db9c92ba3128426e73a0a8ba0302dc84e12de107026629f0a24c840d950347768b1baea05188d7ed148d76e828d7c7559e2c45adc403e880c0a917667adb436489eb1fb1e39041eadb2296b2acf936c88e1f2bdace75aaa7d1b4cab8f49a7ff1abcc7627b9ae82b139b4e9048df5b8b7738814cdab7abc6c01cf4b524fe55860d95599462b85489ac2a0739da92bc898f440fe15394a6e8a008ef08fde3576a008221529067deaafb01ae791a514ceedd6cdb40d0db338137fda2afad735128ded8eac52d524072db41178e340d90464bbaf2a66fe92da60a9b869702c92c6a82d9955d00e96da2cd1d5133ad673f50aaa0557eab0333c08494126e38911a626cabf400add61f374936650f1a89448da71e9c054f4d2a4339800cdccc0a0a456718f826ab523fbfec7253c17135dd9802829440650e4db72469b51987bf4121f498b3c980843aadf888cfc024e324667fddf7c51d1282dafc737394bd77b58af68e609f8fb56b7ea324851fc3d27731c7d5a0b70c973bbabc4a0078c97e906d8852966b22929f3b814333d72e1f6b7285da91c014ae2fe066b253a6f72d384e18c86c399deb4aba02ad156e33defc68aa99e701da22d81fdec86079d981d8a00854844c47c20ae7c59473af8f0a4f3777186a442f9ce1bf7d75a9808cedc575dd8d26f93f1891e2a72321b8642e51db9bc0793aa35873dcb2b5773c32d3885af67f184670b5035bf71f49f99b3ea965f0a7a2b350c52ab5b824a6d069a64882618499d35373c05d85c96b4d07fb38ca697c2ee3787ee3d19e869e721719732538f3c297431e966c1d386da1e923b671b4719d545a71d40fac0f13db7e0491b590440bd50f76611ef65e0c305b35bdf2226f54ee95777c192adb11948eb2fc3f7dc58baa67fa7fa81bde54f854f4e3e7bb4276231392cb64ab3dec3fde8fe6f34958db71c3d44f8f9913da884f26d364f0d35289636173ef8d94086149ab9ef3b11a13763f4ea72b32a8e2d2992203029545ac0b64c055cb50af7b4cdee48799df44a56554af8b26c506a383eb686159a8ecea088bc835fe370ef2a76fed27d4709d9cb6265e3cbd6650b83376c6ef8f520b82eb9df1d4bc98d21119f2f5619e410036a04d1c94de359fa32c3804a6d38d258394985e9f890e3f19e80b7166a6b0c6958415fd464bb3e3974e75e00e6d749a908e8511d4b0a6c8681e73a5c1f480ed68c21449a7907ba8678af3a98a13481ff1c2c5cde98d817bb4c689f835a82353dfe7de0785b4c37b07d639f49557dd51400b51fe27017fc604ea897785a3a252dbab7aade5b2bcf872620d5c101c15426724b71da8b88bdf0d112d61f286d3c6ad291a782074a10d96d15b660fdafb856261c75902353d03e0782523c33c7de7437a80282e762711769b1a0949e67d1ce087346dd1442d23d935ca761c4529794b309cb08ee56285c9155f440fcdba800b2e6ad3a09a843a7631a71c233dbdef7581d5535546c9936fc9c7705edd2900920ceea8c205c58d62d9c159a19df84ebe9f361668e6153e221b8eede114e8e34a7c4fcaf5dbdab662a9591c723d27d170d7f7d6a1521cff960f02ef90dbc8353e28c8dc2fddbfda4802683af095f1a33175634ead07a4e5b308e5dd1cb54077c1c0f34b6b85c2f4235649c63cf1dac0fb42febda1357d5809504ada08b30db1cbb092d5e13f2163646090370531b1c87873dad2e0a36f375f9316e3ace139349a069c3d4d2ae4a0b19b9504b095555059ebb453cea7d7e72c9740ebb2f05a6b7a218a782bc2076e9c8fc4796aa247f6bd56c8a6b5451e994a1d1c45dba2aa8bf0c777e972bd423db0ae242914d2d2f728c787484514cde9f5bcc08b5861352cffa9764d4ba6908357f40ef0fced1d2110f24777d2d61404ef34d6ceac7950e40c1d47159d6e36f03c885f6e0c0a6a33e70605be11ba434a909e19433c7eab81b4ec224c6befce5772baa1a1d07459038d141e3d73950c4ccbad3e41c055628886ba87db21c8b0c445e3d620156517c27cd2fa520d4bba3b431b65b2104364fc299c2687eb9eaa259b5d10d2835c9068289210cdfcde07ae52d722e2b2425644171674aa64b95c6971b74b99c26eabbd5a9d57eb20ec6203aaf05e9d5c304e42930eae30516b4cb7298a9af56bd2ac10a5382f5eb3de15dfbc59e07d60e50db85bae3321586df798a4421bcfd23915018002a8822ae856ba231595ccb99e6d3880b336df07f6bf08110410c00a3e24227befbdf7de52ca2465d009d809ef09edf5a87d3a886faf4bb5d6d3549dc000f9fef66a556b372ae0941409e342d593b2bde66a2d03563a54b9411543133c94dd56df54a872d51ab64276c826d59a83f90e5e8a552a6dd4114a7c716295fee630e082386b0d6701544615215c19c3ca66c39940a53150752d957586ad0c6702b316d822359476161382a3caadb4325ba486f26b5a0d921108ced66b95255884ca1613e5e3a99cfed11d42254f9472faa4327c3a96cd52acb2e6a1440fb9b2877414912ba911233ec69456b663450b12328829ad6ce66c94a21dab8c16252126578a77ac32eae4e996548add5865b52c4cb752f4c62a6bc14f1494a7d205364852096b02501727f8ca347e965556c3c1085729cac8f8c0f97bd008a732fa27d50e460895a20f3e8170280e9d955e5639651308cb8c3bb0a3e02b0dda774a9c3b2618417777df6b8296dd708db5cf749857c72fd423e2dcc960920643c410f5e3071b45e0caa98413243278f1618a274aca06e2a7c71c37b5681873cb71849124aa94530b16265f705ce5708041d93ffcf4106acd044f78d8b24505ae258294d349a8351a49508e4c1165a946d754397df8e91ba8b520199c8851a2a38b2b4739bd03b576822d2851403142a4840ee5acfae92a984e85e60b3f9ddaa6d3a3e934693a5daab51903234a7e88411358b725e574da546b345e3084094582d03045938f72760f3fbd58c56088122c3c926ee57c1589d10104d4981d5bca89e5a7d32d1a060a6ecfb4eba77f4ddf53087ebe846ab35460a8f6b94dede3d43eb4ce3edc4b4638f4d6a9d59c2e006c9f7e6f7b74b2fefba74f923d657c4274ba7c42748c10c0671d2c9d2a9d2b1d261d271d289d289d239da3239da4239d25a6cf3ab6d7671da32e9da1efdc22b7c8ca2db6b8a8aabe738bdce209c7e716b7a916482d94bebd8550fbcc2d887ae8730b5b8b234f891c319e123963649f73ba72b472c2e4e044cf3955395639b99c2c2f4c4e94172687ea13ca697a12cab939f99cb3347ece61ca31ca41face385c394748cce71ca21c1b96d677c6c938b88c6395c371c281fa769ca3f6997192bac6679c259c266f081c21de103828f8671c239c219c2216572cb05868b108c3a2cbcbb1a0f2722caa74f8cce2464348484847995944b16062e1c4028ac5120b1b8b2316495d2c86bef34dbec9ca375b5c37543755df7ed3d43ef3cd13d1e79bdbcd140d374b68b8812183cf374c3748374a3745374ff54628df10752c4cac6b2a969b8a654dc5b6709f6356dfb1ab28aaef1c5bcab1a61c7bbac58c6248dfbe62aa7de61538a7cf2bacbe57e43ecf2bb0be57785ea1f59d57e498508e11c56cde8f1552de8f1dbe5744ad705a0155571cfd8aa4154b2b9a3ca315459ed10a232f49059797b442c84b5a410424cb67155b2ac25c617d671559c514e5faac0267a582c9e93babc82a8eb28a24154b365d2a86bedd26d73eb34d56b6d9b2e1ea6c86e86cb4c43edb5cd950d954d928d930d938d940d94471d9d8b86c8eb86c92b03edb10d914c1c2c0ba6c86605b302b580e96058b8251c1aa6037d812ac09f604338221c19460b66fafe18209c188ba2b354d74576a9e58f1b9264c0d568d56cdd4b7d750d5e06aac6a725d55d740554575354b4d5dcd1310309f6b926a946a8a6a8c6a906a8868b668b86a849068ae5478a6c1faa6d1a2b1a2b9d14cd1e06898689c68a06896be9dc6467344938484860792a79e697c7cd314cd74d10ccddb8cd64c98a5dc52d64cd5ccd5d3cced6966ea690697c4e799a76fcf3350334a334cdf7926cfd8f2ccd14c5298aeef8cadbe7d669ccb386b0b4761aa6fc74bed33e3a68c9ff0ade38191743c9e7ac649be3d63256c849164b464c27ccb7461214c846d9d4dc6aab3c9e43a9b4c1610273ecbe0beb34c1554d47796c9324b559f659a9e648a648cbef32bbfb8b28c900cd1ebea85f5edaf5bfbccafa9223ebf702fab265e3734f17a02fbfc827a31bd9c868acafc327a21bd94b8c82d2e928beb2574c267322bfc4c6a91559ec92bcf241689239fbe3d93b76f720aa995be992a12e9442691444fdac8a3318ce7b1eb9b1c1ab7be7db41a73df79cc1a62f46108207eac1aa346aa1ef3a8f4cd34368d4fe32d3712e5465b6e3cfaf17914faf63c0e6985f9ce6216ada63e8bb92ca8a8ef2c6671a9ca67b1e9492c128dbe3ddc6a9f39e4caa2904874738448dc1c497ca875f51d6285b7702ac48556b72874ba4521d43d0a93ee51b8748fc226219fc3239acf2152a50a876c66f92b82cd2a5518e66ad6374d8e0bac02afbe33f8f4ed3383b7253e835320ee32814a33b3fc317d5f26d0094cfabeb759fe6cdfb323eb036882f5e1846f70887e612a6d9ff9c3d2b2aa565639aca6c2aadaaa4f5bdf6deb9b4af2f96bfafe9c3ea46f57face5ffe88f267fb8eb4bebd30dfd9cb9e95cf5e2eebbb41d1762f297b4bd96bf29eaa18cf481573e43d242be4150979465765ee7265eeb2baad8ecb13f2886c15f76debac60f8dc4d7de78e0ac909090a29aa0957798ebea48a5bb24ddd906dea8a6c5367d409d5a69af5fd6dd5262e1b551575f5edf7a97de67b1bf2f94e5d1c149706289afc75eaba4ab48be9fb1275beb67b7493bac87675d11dea239b75b47564b9a03edbdcb7678bc544c554c57435756bba3ddd6e7d6591faca2af51553b647df5b9547e86b6bab455ab5e8a9575cfbccd58a1ef1b9e66a960cfa63c6a09e2b95ef1f56be2b9511d41b45e59af4ddb6ccb5e9fb49a979682bb54feab90a113d6d92fadc5c329fbb6b0a6b4aeb3bf7546e5c6eab9c55f3d0ce9d947ba9a9e86956f350cf3d443ea55da60c72c6a0def8aa7d2e35d6049aed948a3ed59ad3174e4c5d7bd14bdff4a8d648681eeab8a1efee5b2a48d5b76fa07968efd446d03ced1d681f41fb501edfae829ef69fdee1db43e86e8fbd70387f4749522fda1745511445712aa9c9e6ad8f2219244281b317e6a9d770611a279cbda7bf7598de5a82736fd9a7fe654d20fa71b5cfdc2185304ffdeba20e0a4d20eaa0ad7de60e448a78eaa0117510a97d6e5b419646069cbda9a7fe9a82b367f4d45f5270f6929e3acc049c3baea747bce7350006488ee8c8218416164acf71add1f83166080c3eb248b142aef4faca7b9ee7ed20260ae845e1dc553df59a283877b9a75e0304ceddd253aff9813af9ceed87635dd77553388cb478eb5e470675443877b6a72efe80f3e57aaac35fb71f6edcdfab83094e346ffdde1670beb9a7fe2202e75bf4d45f43e07c8f9ebac804676bf5d44527eae20c38dbada7b8e6a1d6715b2b458a2f6f5d015d134bfb1f3081eda9cfd9571504ce1fee29d6ff87fbacbefa571d578fd5315fa77804c1c15b32a8d670c15e5304f51a1b3853a5a75e53038b36700d9c5f3c755108e79fd7c97a30a90242805530ae5d7b66cd8069df9eb36e31066c63312adc7df6b16997764b40278c09a3dda6040f4c5d0cf2031f1571c20fa820453bb0d490204358ea28cc97133661322d6a60a9144cf8e900d0a9034498541d61ae559704873a501d8123d2a45bc23442ae93fbc44de2821320f08b298747277749874387c20e7709db1f160bfe92744a5829a30ab62b35584dd1a66e3f3d2d9565f5f359e16e5f65d235db56078a35ab3ef02a109ef65c16b6725fb78207c31111c63ddf27e509ca466dd4466d9d2482516da3425b6d9fe607b669c254c0dc3e37d357afdf6cf7a6f66d0d12b5566badadb5d61d39df9eb10d3979dadaee5a6b6bad15cfdabc1263d1b4366bef50a07aefed3cbf5e77efb5d6da596b74f25007bf99a6dedf476e6676982c687e7a4febd676adb57677f7137efbd52d8db169dfd8f56d44c58813fc3e70d2dfe0db270f0f7a02662de613f46f027d0ffa0f95e139e840f400d4e73ae8e504b20e8618cfa88ceab4bf4cfe37fbc609d454c6f74d9294850ba04fe9b4af7096c9beb2198d413d566b240b9ba7de736f885394713cb3cca53b6f3fa7ed73d659e7dc691a9d30dab3cc4ff726d0f5e9e49431d218edd3c51debed35b306eb9ca79e71beafd70763c0f9450368bc77ebd75166facc39760c38ff7c7b1f9b30667e6cc185f38c6ac004a23326d0cf84d1b4f660facc497bd13381ae7fb35cbfce99259f7af46bead9f58c630c8fb0299ee77db87dfa4a1436cac7141ec85c81e131e63d232c67e7b643cc67fbbd809b667d0c22419d42a885119ef9a3325ae8bba5f82af302a69f0fd9fb3a708a9f31cec9a187aa9ff999430fb79f2403584c7ddff7755bf8f560f0e8c446e150070b0516715260300b15ecfd940292c44502cffc9402e2c4e501db1f30e02f4915266110c22094ed8265be73c2dd679c43bd672ca4e03c6f21cc90fa81e5c59f5244827ca6dd2a5bfe79ec672af77d3177dcbcfd177bfb57e9e6bec99fd7663d5fa37bc4b5f4f9227dbd495d6f51150a5385baaebe7ab616eb84163edb325badaf389c55cd76ca2ed9a61d377cb64e3765b64f51cda3a4658bbe5aa3af35ccd7ae1c429fad901d5aeaaca52d272ba7dbd71a55a78854f85ca964ca5c715d9dba427555eaca54abad1a7139e2733d12cb5c91b2aa505625e2dae2e2b292f1dc5739acaacf8d3553e6ce9aa2aa0d6515a5f4b5979aa94bcbe76e5251e6765aeaa3251b0dd3a596b985be1261555d55a7523f4ef89e54d03c4b62a5361e559fa9d151085dd52a4cd574fa3c73b1ce3f9d53303c78658575ae7a4f68bd05e1ad834006d9147aaebad65a452dd449cc25a8cc50044c129225ac94585050fa62841427a2782993725ab35c85a82c521249c41348b185b40417244ea0c070392285832b858ec107864d7c09b3429a7bc1def39973de7ba2e8bdaad2731ce63d8f3941f59ecb20833c1c651edfc27cc0729441985154f30a4bf6796a453501c42a09a7b71e4bc2c9968fad2d6ffd670b8ab73e4309286f3d842584bc75129640e2ad5f8089db5bdf409814de7a07c228f1d681c820bbd534e800840e2d5a80c0a00c81721cb91184101b5498326c243e4c42860fbd00645098039cf23927bf78f003304faa185479d02de8df0f0f7a0c8c9607dd953ce834ad291ef49e1e1e8c4222f760540c9a3ce83f6410f87d9fc73e2cff798d0cfa70381c5b4fe3f334ee4306d150e11042c5b30049d118a5169ec071a3449244c506176e984044839635400e8e199298408523c75b27001964fd0332087415da2f535fa7bf57172a07181ba18e24afe26dd0783535d309fad580e910f33087f900c820d8270032e8abd171c3d7b8076450cdd7430679309e268aa7f11864100d088220088220087a00c820304ce143874106859f756c3d666d0e216ffd05196459209ae13bfc5ab325aed62ce5b2e552fbcc6bbb4457882bcc161256ed33e94f27bedd36b5db25a2aaefd90fe6699c8065c4564e09150b52ca1c24a28a505832c2e2a3ec9927bae1a97de697a76184c9d093922c4250594a1a4662a41cf18406a61c4094fd39fe8cf8cf3fff3e070019f4b9e79fe7d89b5cde23aaf29ebb20833c10044110044110741e3208ac3fd671559314d192b74e6ba2297ec66dfbf8cf780764d00c90139420057002949e079d033208fcac7ff404a9139cbcf50db2ac3c76ec3b64102e5244141b0a04dcb21e17a81ada63892050366040526346554982a5e805545ea8d147d4f86aed33ec3f2e43fe6ba1fc1c6bf9cf352083be98e79fe7d8a340de2bc2ea3d9f91415e0e3e98cabcf381b5fee5a8bc75ece3adbb0a6fbd67ca9629ec0fe81fe818cc7a3027e5417732089c9ac2b2e1655cc63320836480603111d9e811eb35c05ac35ca278d0ad0e19045a6badf51664905d5aea2afa97db97e79041af2429b836c0b1c483280b1392286c1426705d35c61001441b51c11b202e08d8b75fe9e1480621cf282988784ba4b48eb9a6de7a6c05289678ebb428a4defa4f14586f7d861434bc751c32c84ee59d077d8220e833cfa907710f824ec3a88b13548e9058b1e5284124425a00b245a4288712f4f2a03bd4833e830b8707b9983ce82cc820304744f447e478f2ea49bf21834816bc48213302ae314947536c4a924820e724d534a607ac19ba0bf526688d507a35965098a3e6e6b8a1862a37688101056304c91352a8a67081cb4746f12df3ed96abd65ac080098c90199848592afbc684a4a51f466c4f5b65fb256a9f2ee2dbafed1adda35ac3b7fd2a758d96b72c24fff2e15f1e23835eb9e7ad4f5f61051964a795279d3ce92ac820f2fbfe8bea91032acb0628a84c293fa717be506982030a546e94df96ff8c90e13fb721833e161f1aa123047d66d8839e731e2c52828ebb8c79d063541e8cf2927bd06bc820107ccf3fcfb1e7312faa8b13ef390d19e479076c3bfce876f4193268e421c5479f7c3ea6d44caa215d52b05449898a810782787a82698a2d3bbc740ef479b3d6c6646676a170c4082927a0e8b0e27304d150120c8a4481d3e2d59ecf301b8ff73cc743bce7980cf26250ac92d23aee71e36d8f281f2b045910047d82fe2283400dfee361f59f9364d017028fa217a75ef4910c1253d831c603c2d2c5f582d09118ac50428e8a4913285a378810813d7a84a447acd7f8ba6814d1a27a2265080c463a94edb6a9d66830e121e90956aee88955d96e9fdaa787f8767b8bb253555b4c1fba0d5d248342d0cb1429784122f2f1a3047b18c249ccd31152941495e0d583515cc23ce8211904facd7f524538fe73900cfabe0f0c390148d5922e31c89576fa30e4e488a5a7312ad4286d962c24bbf3de8e2cefd15a4a99c7bfb0216e96cbe3de0bc475716b08fdfd6a50f9ebb1217fdd6d30f1d76955fe4addc8f2576a6886bf5226e8f8eb1d1974812cf16a9a2424246423c48db632c3de56f528f38bb7fe010851315ec8fa87e3ade31edeba8be1f2d6694ddefacf14526f7d8691b751639ab0380ae8d024872670436031299730c2829621a220f988a2cc3d7f85f8fcf3f7af7f78007fc5d8e86ae2afdb4bd53ced608c7c00be2b827315faf62a44c4b7d7a1f621bfe71451409a60a2ea0542a294edd5566b3b433752c092a4a48820ca3f6ec639815684369c1c1b9b1970c645bf15e67151fb64bc03ef984039e374b1c518fac0d4c31e198c210f1fc3224c3ddc81a9873a3418c31c986e3026494a0a6d21d1061c8cf384cfb2304be741ae09e343e2609c62076378028d67b4201122cf18e27031862600600c875e8ce10d18636803072046c7e819674fcf8b94f62d9b3b33ccaab56f76a79aa75f7c4f657dfb9c9229c2d4f39dfa76d183714ea09e9f2e7ad0132300305e00c0054fcfcbd635cc0771eef8bcf7fa4fd37ad6df8761df730608322ffc88b081a7fe038200f075d9f8d3357c6b1038fff8e40f4050c007e04fa9ac29305cedb29685227e4e9f49a342644b8628334891a40051dcb001a4052521b094943ed10ae22483101b443f64b922c208090e4db05052b7e1c2fe193ff5188d18a85cb952b4439214a050010a297421e2e3851a924a1a420d4acd339fd6a0b302c65f10b61f9662e1ca52950b42744895d6c284f0fcebe3dac6396756bd24f4f4f4e01e0cca5460021c2d4cdb49a833aff3eed5c057b7b2ead77bbeafe7eb714f04757cb5afe6c4b36ceb0875040dbcb52554d98a65e15c4f2761d666e0dbcf079bb3678e33947580035e30f519ddd519ef798dc53acfab53b1730a634651d0ddac04a04c83ef7cc7920f904d5ae71b60e09222dcbf7ec907d8075dac93da0ace7ccabe730dbc0dba3ebbd96e6776cb2bd2ddfe019924987e00f61d98d5f75e175db4d85aab5c16096d67ed59b08a33dcb4be5ec29dc1c08fbad7930513ad887f67be785abb7b86234275ebb6fa0760dfcdbe17eb5bfa52de6d17b77f01193bf829c574c4020e2cde07ebee7ed9a0230b9019b040812b9bebdb635ebe7cfb4e09aeaeac1cc184124668f1f9ba4ba58ee0f2333fa55668c25aa9de186bebf5386320f4fc303da12f7e4a317d79fc536a852e566a2002be7a9ed9a11663f2d4d1aa9220f3244c18d54327707b8c0a835946660255c7b3e6c1e4a97e734343f3f9f535fc1c23fcfca23306480493306b1b3812328492125eaa5a28ab5f60d65260c41020b82e35d8bacaea1b98b50f4a485496313a6081a1acde8159738015196ee0ac743179a2649a34c89c39a6e3e9c4ce371dcf23bf838514153a44c00887a8252f902282abec27befb45952b5252305da94043d94d6c96bbe3da70020a861e2b0cd9a108d30d296e4d3c21d982c4891e702b3e9c03c3b1eeeeab44c910362c4aa64218f60afe6ef60b1669b851985cba47f08b88144a7c4c29c161c32c4171b4f46509889a9d1b8645b182c7265d1393bd61d891bd5ae2a163a9c6e58245272c180c22918506a72a090f4fb0dd6128c90db8d666749219c2150a98b719215c2559ea54c0323fa592a490a48884752f2411ba529864a2eb01632e9609bc826aca931b5a6032a185a4312d08b13db440845b0a899809147ecf647ec0b35812a6011305d7fc9442626571c0373fa5903cb15ef0f7530a49131d18f6530ac90ba20c194292830babf829a5c484146cf3534a89081f4a575ed06230ac8006cc084129876f6f8f39fd29a594f4323fa5946650d2e1330d87917477475e3721c591fe7660789c1f80d0f3e3cde90400ba8e9abc701be245a9ec2a3d8244e9110417658ab29ba07a224545e4bf9f5254403eef5ca12bc6181e61ddf341fbd78edb46a1b2aaf13e680f946285efeef9798f14d50ddd525f827c11fa6e87cc373588ede4c49cc594febcbb67971c90b4a4b6bcc0845349e3842328cc60849726627a74982d27babb819051c0bccd880dc04bf864ec211eba2bdf5dd9fdc95c89b5c79cbe3da7eac97777b7cc15af2727f6edf4ed8705289d140b3b7cc79c86f3538a8516befb29c542d2e71eb04a04b5f0f4e969e1299524f439b6f5117d25e89e6c08c74f093ad15786a125e243024fa92339a923565247705ff5cee9b43c5e804356f912224ba89cfedd70c1564eff74300284550b3e202a24952e9839fce06549d9ee1151ba00f642d9b3b109403d4a4a0b51958d20a6aa4cc1c50a890ab71ea7b2872eaaa5322747391dd32a8528a542932be58a970acd6644f0d567befabd2cb85fed78e9e401a908e08f2e7a054631f40b86b2a1d071e5e959796cf53c3ba3ced2d6af92230ce03f179b7c813314f4fd3adb82fb755dbd5fe7de8cd2af3b9ef18bed3995954004fbe0a4b1e082b3126e095200be15e12b47d92c65739ccda608fee699b7f55e1c08903e7a05c8d9e815982e9860124caecf67d6d3286994eda0281b127d940d8deec209936072bd09ea5b9fe59c1ee4eb5d974372016319ca6cb6230ce03d17c5ebddebd90ebb5b245fdf25afd7ea006f017894cd52ecc0f6a010a58c88b16145d156a07e6ebd9e65fab553b7a595799efd6674f2802900bfedfd6edaddf3beea0c05fd9ed74b5040bdfb79306693be3d419cb5b6842ee99cca4a208215c1fe472bc17a4f6036278f0346484195655084cfcea7f1e0f89117205a42829f2fc104f53da73d8d12144940c77ce7a06c08741c4b48f0f326a81e74b34c69d8f2fbc8116aac3882b22528e8b74e657646823a66cc37d8dde17d79b3912417609d2e199acd26d34e5bedd99cb4f67a45b05e07825d0d3f19fd115af0362445687700b9001adf63e75d951045a0dd5be97513c4fedbd276da5917af95a1a06d77aba5f6d296417cfd20809a2964364d380d5506f37a063a73fa5c6bafb576060b6233c620f3da228a388435445434618235c40d6ac6e0e962fbd319073434b980174f8203fc943d0d153c655736af80a1d88d94ce50063c6513ca8b1fd6d3973da5b8d65a679f7db1369d75d66e8a38f0f4f1353e09e8981cc2dba70963baec5d406974572d7102ddf772984821e9275048c122ea6237d209f4fddc21e5cb964b2ea093e2c717267e4e1ab5a40b1c309d402e70c07402d5084c2664b042c2cb8e187228a29c93874ea0768103a61fe902074c5d04ab052748c10f04bbf3c02bf3ca2df8f624647074c0f467546eea67d3a0f0ac015b2738cf2b26387f4d70f6279ca7166803269af9731bb090e92495da03dbfc5514b09db54b0465c03fa3e01cfb8ecda004e779f54b38fbc780f3d47a1a304d112d50ff84800126a9b8cc4fa9231cbe5b2cc17596298f973a7afa1e6767d520a9a153f3366474823877b27854848351b07aea0d9875663a4e25617a036a19343df5175d635152ffc0c753ffe92525f519619eba02da67e6e910acda136ffd746a7b52fb9a399b84d805eb38c156a706ecb5367d82478c98c2b483852998d802e207488d1a4cd830a225c7d8430b38ad15868a886881a388208c607de9b25152af798239d0b4e93550b8660a165f42766021180493a736f153cc4f137e7a8efd07b05716609200f2e506d2d2133cf434c58a2886aa2c25f4a852840922baae9e642009304b462061e2c2a5a46e65332acff42a9b9547c709d30f97c0d4bd3681e89281a80819c414c1a3a44e9b350ea098a4346132e58b1125f59e59a3e1036987282b1d4caa0c95d47f660d01434c7de1b1040814ab927acb3ad0b449bf7f4a0d41e1c13138531e3f6d0fb7717ab0d1f37e4a055bb650a1160c891d500e7a831dabc19d7532cfbf0c409db0859833e2902c5e373231bce2fb46f0be048b2bcff41a18acc68646c5cc0a1c93b979b12071c61cb145a80366f0b927eb665703bb533ba0bd3e5cbbe6aa7dce392d1dadb5d6f605422330754bfbe9d45aebb65a1be6acb5d656ef7bafb5d6ce6cadb5b6b3de6f67e0907853251e515b8c4b34617c01a81546adb5d617b5d65a01705d743c1e0f4fb4cfca7dcab5d65ac3e9f333e20307e3960d76e61c712ce9730dba2cbbe34d9b3306cbcab5d65a63be8469a34ca6ffb361ea4d9bb14df35ac35cadb5d6995a6badb4c719cc8c37559f4e0d4d0b584d4ead3016e01173aeb0b1d88a3959dca8c06161532bce38e79cb38686db4fc729c2f4a98b2d469c223c2330cbfb345a1979a869318e3dd064a033e3191c619f302c9699c9c6afd65a2f3e7acd8864630ff8264d18df8fb5ee8c9f915a6bade19c1c801dcc39e7ac609637cee9d5e9c4b403c0096ee1edab87444c7711abb5d69b6465d92f00c6cf08a65e6d13c69cd65a6b5f8c375598c6c023320eb5600a1e516bad3500303a4600be9e719273965780fbd9f4d547284cbdc62083a683593e6b9db347c68dadadee6259cff3d2d55bbf9eaff33c5c978436cd69cf123d5e6b3ddd536714c64fef993d2db8f4547b63f8eb7a2a0f0854b1285c673f58b43b76ded7d3536bdfd7f3755ec7c2d451c19e1a809f3e36fd9e5a9b31e3565f7606e3e713f8cedbdf598c07afc4eec5cf8f1ca1849cf666549e065c7dcf2adadb59085d30edbd8e1cbff671f10a88a180e6699f61ddce78fe6b9f18dd4b84dca7038bbd5d47b584c01284f7816167cb519dd2ca42709635880b1e5a912a3ee8d76129f26db9f7dec963bd27583f6befb5d6d66e66a3bacf5eb0f3ec178e24498a22498ae23892a4f81a479274ea16a460531144413f5882d768294827edfec882079d82e32cd31f45ea2138b39fedd2795d6ead94de5a29bd94dece47e781df7dea62638925a86cd9b2253605b7bd5dd96db95baadcba5b47d7cebbf68a37b77473cb9d5bc6cce02b2e685da1c27274c585225d7745c7962d5b8a3c81505dbdd6da6e6bbbefb5d6a3f7528f5edb82af75d22eaddd4e7bd6755d772d54adf676b95bec964ab3ac77bbc6e21501a9b488e88a685dda4c4619fc22c5908645e8840182d4751efc66d4f116992d2ff26adc7283001144d7751dad4eebf5aefdc187aa1eb010117505e7799e67c5fb3eef03c1cfb312e57994f2f03c4a7954f1bc2d54a83c8fd2293c4c9132b5830e525145a2509a250728b72738781ea53c3c8f521e9452cfa394c7bd0dc3fdd0736de7f511d40f363451410bba46b3b52912353479eabccf9fc2a76f30e7612bd3c4050fcd5a22f65a5ba9fa0906c76babb5d84cfa79a35688c4dabf0fe358cc9df62154e56cdf5968bbeb7d30fcb5b78383b5d353aafd4211bc9d18ca6295079cf7c9ebba8b2543d7b512b52a61aff5ee8ed1a2454bcc0ab69df7959f57e2f6b9f75a5ba99c546bbdafab58bed6e2b596aeb554186670e8345ea463f714a9e3f6099dfa778f9ad63a42a7a4033e0ffd9241e10c1b352d540a95c2a5ae5da36bebbc9bd98a6ce775d6cad4517f9ed76111a2f3ef731a2ff6586bb51e11cff36e84cebf199e7d582ad4b5ded7d54f8ba7a5ab5aba12c9027df4aee809f6dacefb3e2c2d5dd5d2743692ed6ef7592b77f612baa055150e9cea1a58d53ced388b94e4794ee3c55aedd75d711c91ee7e235cf732fd6ea769b09adf70857d9ddda4e6ba372f14c1cf7f6c96a825d4f2a380101a40c28cf0a707d442bb810999d82bc4ed79c1e341016e8c30f52878139b303a07a63438cd10ca308e610ce44bcb4b066b103661ea210c34355dd77530a881759ed779dfe77554603639d8a8e83a4a7ba858d17594f658b1e48645d769610a5f68a1d375945ad17141c95292164224987aa8c441072fe8804684c6820b00503b7b85a423a10a987a68a4c783aea3b4870702e83a4a7b088052da7594f618401d80006059afdc259a409f4f3074d16ef15cc4f829fed8f24d5fb65ca7f3325a8ae12c5f39af6b0a2e786835b80a762093046e7d7f5626e9b33770cb52b138a3d8cce4e95a3805c441cebc4ff53655af3ced93804933f299d1b470aad642a8b3a9eff0eaaa7d729825cc32a7ee94e5a2eece6381b774f25cf05300fe37792e4e40d77eef254590d55a7b8aaf9c1470f624ad3922255f61483b3a6961d89586e1485fb47b91a47761d0e8221d49190afa5fe48b2449ef42ef427246278d24bbf0e5e2d70376edee6b36270c921c6799866c0c0a3f7b3fd968b7c476c12d8157abb55de7d57ad6f348fa119181a1b59a40dd04ba417c087ef33e5da7dbb4d5547bd2d1526bed6cd69f3b8d85c54fff6ca541623dd5eb2c2746737240c879193226d0a44d63c04fb576bba95af36e93cf3ca547734ec9982d6dbdb9bf392a983abef9b84913a8d274de75dbf44b3f6f530845b4cd108a68ab4333d8cad0ceebf1edb9e6a7c3623353c62da2311aa87efe7d2153d3fa0b979ad64e43a56b53aab5502954a24a4d6b0f95be1de93b322aa232ee8f203d6cb62a73fbb6ffd1e976d66496eaec2e54e2f90cb9b01252d21d52e4a7df14a68c6b24b3546bf788ce96beef910c940c54fb64191b646c90a2e428e4f266e0d4d155ba4917e91a511937855be40eb9471346e3ee91cccdc2504306d9ef79229a409ddb269fe90b679f907efa35ba47200e417efafd3165805034465752891ac8adceebbcee0e7d06a99ec0a9a645415119209428200edff7eb0c1c71b71a211f4d5353531348e78fdc93323581ac37bba6559e699bfa06d0eb6ed75a29ed5a29ed5a69d74ae9a596de6ef201b65a4f9c5da9f5ba6d77eff5ee07745dbb59d7a6d1d12e8a482fd0a475b7872ad18d9b409dd3db402b10d70d468151ed93c1286094578dee6ef612c2815453601488ab35300a8c8a02a35e382eb7bab5b709446d13e85a28509eceec0dc3e8280dcf4e0ec0e08cbf6d932db25c9b0d615b1a1eedbdf45ae0ab6dd2f15dba4d4ef7a97deecba7dfa40934fa740faefc748ba342a295cfe0966fcf1e7c3b8b05e8bc3803731346fb8734cae884117ae8b94ce76799c68c6d9a97649232b2db5479a0eeed46dda9ca13e3ebec3e35ad3d1ca2e93d87c3a67b24ce9d8c6ddf2e16c1b406c00711118d150cefc54f59d680ecad0301e0a7536fd20e53c0d4c3234c7d862860eae1104cc70d2610772accba533b2f0d4221a111f69f3df8ab6190991e9a0c64df3219489b35109cdda99cc167d94fcf9d0ab3ee54d6c535eda60a4d8d8e0f22a20c6ea6d4c05ac06c727042202b7a6237626c0c039e55a8e4ead74e939a54a86600000000006316002020140c888442b1284be344ec3d1480106c904c625830154ba5410c8320c8286308018000000030c610221a9a6d0082e14f1c3dc6685fd8feaa6a432ba98cb12fbd7d19525207406ffef796b50050885743158cd09a1e02f6be2b97ddf9a3febf35ff6af957e74fbaffeafc51f347ba3f35fca9f327bd7f35fef747428529b6270c90212bb62c31055b3749af55ea4508fe0ca64170c74aeec8de9f22f73e9c6f00eefb77ffc79b7f6efd51f727ad7f35fca9f327dd1f75fea9f993cebf7afed4fa93ee8f3aff474d8aff6af163bf6e803122614d29780217890a1491e8987ee9cc392e4e3601db66d4022ce7a934c25d17a06d43bd3249461381161810e7f655d45e346db7547f0e763e55413a337405094041638de54bb426216541eb7c5316f0938ff7669ca59d5a36aca2443d96375717790ea904fbe4dabb61ead9e72212b2f84ec53a92b15ad14aaf538edfc2e71440de6235ad400b7a88f0fde8747a9dd88fceb6bce7b6495f72ea508e97449394fd5cfbe2f6185541f3250c5d0be64c489332079d268f75b3aa0767211b4b2dcc40d8358d300b5053fe3e0362c1ac4ffde97b125143fdc2e5deeda613de06ec177422bce176090ed0f71c2308d81e7d2228f4f0dcc43a1aae583d647575ae340ee4e236b8ec7c82eb0d9294202d421e85c4820dce999cd3ceb7cccfdc15b00b503b26ae3334a81bf3a2f5de8c35561adc86272e52443a2e6d4f5007f85cc2caac615b6fcd7a426d84fa25a4273f87e289cb5456de1b1813f65834d356f690335811d9f440f5a90a6e3dbe9407d196b91a3c6b2882781bfad2b71172317f70220e5b1a66530d37b10944716d4bb8aeae775fc62500d1ce513e37deb11aec126af1a501d7984270a4a46565ce6256330d7030ccb9410bb3d17d1e728386b10219cc3ba6bf7007e136d80989b1e233c2b3e09114a89f652bf17487665275b433a1750cf666fcfb84b692eabd4d44ae238b76be231f5a8c417f132c9f0e826ba802ba4c4e97de32ff07d4d8e0106746b4c93a676de8c73e8783ac0dd8500d28b4bfa77f2cb3305318607b3c0ebbb3ba599105ed5b57cc9a05f2aa13ba4149160ea1a0335c5c43cfda5f96cffd4a6c4af757dfc0c85ab9681f6921595ef8f011c8f454da438bb7d49d629627f77dbacd8ad8c8708f40e11042793428bcd7e8f26805a5c7f92de1f17a61239b8f94f1e65a7108efa477a725dc0fc13da5f9eab9296dc5c19a0e739cb7b72c8523f35c8eed2d624bcdfca44e741f0eade1489a9656b5dec1edb41bd6de12679c4d53ef2dd84a6d478751860a00995157bf3fa354f1958430d0f2b493ec7f335e72f43a34ef1ce0231e229b8e3537d14e821c613b51e86d458c779c561b9b97036aa0938e8da78ceed4aa96c16d66722177ed65f127469d2ca431f1b74c83a69c540593f4ca71249d485b3f10b9e25f04014282c90fd1b2c4f485a63aef7103e7b4b099c6e2758d450f529f50bf40604f2742473dcb59c29e56d95f03e9f032808b1e45e960097725e1d985c5f99c23956616f3762c9f899d3ae64ebf1bbd68fb1627959a1c1596523727d308df5b40898d2b9cced0a6245268319a50c449522e481228c38896c11e63bceefceb87bf94abd311e2aa4927413740e352732d37b0ea8176c9819d3084ef90954218c4d6b4b79775439f0f3b2b103a18356aaea89a5e6812c0f641e9781f6d983e88243ed66690460ba8daa3c32015aab759d5c94953082e95d4e895207bb2bf66b5efe074a6f63df66686750d430e9e1d71cb721074f553bf40e545521e4988fb76962c708237c627b31398c40fa52c53cc0ee45ce649f3a6078d274cc383d09bfccfcc4bc44f3573b35bc95bed5ed41acecb5b740319be08b40b5e5f68a00e661673e2a4c42c4a9df30667f4a960f3ebbbb6a5cffabdf7ef4c5f7da509a0843504b4922d624d79a637be73159c111be859f1908eb2be53868d2815e44b5dad80f9cf195f3bb2d648dd99d8c039b909cf59f57192075d92a85e565dbe66d83cb8a4a9bd1576df227673a83bcbea498e6702cd5510ff4d651ef61ca330bb63407e45d80038e334d47bd61da6cf0050df60fcf9ec305a78df1782f5bd31ddd385466f7b7e3745ccd810ae9646e24ee225c84f4c9c3d58f3c59ef225b25907fa52ea9826bd9757743f292ef575d13e4d55f98c439036163ef794de50f42f4585a8cf9d38e8a56aabb480c9735834a158f4075446a738c7dde0c36d6160b9cc78ca743c1011c4d59082721c5adaf8024173bb9cb2b494e5e8c633d2f793f1a3d5ce5c39efa7dcafee55d1e06c1f40ad16e07244b39053205786a30ee095fbf9f63d8f3c0804db8b9536d978e9e52cdf16bd10e05f4a6f83906c3e8caca49bda3eb396f824b73401249b15faea8e7391306c9ce70eb225689c1ddacf5cee092769c8809f8e6b9dbe0e3718c05c59d1a7f4340c823becd01f8afdc98f25282bc6c7f4999c36a15868604a1bc68a31b75a8381b61cec30e202656a300e2b4fcedaabda7b9bd8205d6f479056fcd500617c185f3e7eb1394d2086704e31e583f9d01589691a1d879aa71934c3173bebd12cb9dac7bfd8e2dd715ef98440dd0a69941a66c147ec88f9b04c7ba0d725bf38ed0b951cb7067a6b98bd97146e72da49f303031c2858f0e8b27bc7ce8d38fea06be86f7f836af88f7c35000c85428eca9cdfadc79549c4d63a97bac385c367e16bb6ba6a1675cd0725b681b01fa4c3229d981758ceefcccdd958b45247b2f67f4082c59b3db5ffec5ac41a240975e07c7e5fae60419dbb8c3068423474cd8929a0e8b2071f921136e0e31437a55ee6bcd98722b3b8fffafd7abc4989c8a914ec62d28ff453d2b6bcfd705065e5485e83ab8f4b38241b83ed50deaff477cf699342b593a07fe637fc50f3fa28d34a8f4185b9efcf4f32cf7b33d8f010fde6768f0808c1c1cb628d3f03724e3531d6510b685b7d08d0b89bb0c6cd0e5ce987dcff92fe071725d40c86b023b57f4a567a1da8d23b3ff33d2b8216998bd69c7ea79dd474a6b1252cbe8c4679e4af49604a5b36fd276068703a20908d7bd9c93dc0e529301f32bfff81684282e6b7a6c79c8b31001aba59a68ccff90affd0eafa7157c363f8195ccc7074fc4a4aafa59f2b32e40601424148328eb833561ca64c011b53e6ba95ca2fe8bd073d56ff7a1ae8f39a39db8fdebf22a3e79a0ad78be2fe40ef1e4b46d2fd6665d7d5946ef72bcd8e0fffb35704a695259134d30c74db819430797b3f24adb66585dd7c11cb28450e9141c5ab7ff8b37396a3a5c07700a4422e41593a41d13697da3d8faeb1743ece4648863e48ce00abf13ca402d6c6d88e210388276f1e35d9ab1fd547283429c472eba872d01dd4f1e70978c2f853a461199bd8f2acd3501bb793a2ad494a257090490feb12d7ee1ea144709a270c3bca9c7941d480a55cb1c88155556704f44fc96284545f4a92f898cfb8eb4d0f6907b34c0374db8d63bcf5e4fff2247f5449be51efa34339ac0e366a219f45248aa0dfdb07d70687bf74307c63c978d086f1451cf294c01c32409694a823d6d7d07c08e76f6542bdf49c3b7535d7f45086819fa044281753affc2dc34ca63908c4241c24f48f888f689239460a513e6c0441eb57e47d0777760644be3723addd20f6139a7cc61d86637189295a802b3e6603b36ec61fd20f8d389cced11a165156e22e60173c774a73fe83e9fff934ea448437c707db46a451423f54e632f2ffd94fbd121583b9340a4da74facb62ff96830c3f02340034a691d7467923c5c54ef0ca0c1c070730d01189cfc6606a4905009ee3122ddc1b9062af41c74ae1303f60000a6438a31241e97ab3f03d83270b828447d2b9d04f57cef4cffd00e0a5c888756be83c6b23b567418ddf8c7199ffa04712468c9a235d758b8743d48c75faced5db6d2e5559b7b9bd81a472f4f5b1c7cbbc61a297dabbd1b1a531503216d9805d0928ef41b1aeb11df9e44bed17298f2f90daef84135f76d6341ba5e26ed9d0e90a047927f9a674c89605b019df8bee0731cf4395bf01f88156789e7107d37dc2d8406f4e6ef3d21f7cf29e775dada50a83ffca16de59e7a7b80de5f224043b0ba12842c0e6623c5132895b734fe6182e4e816dfff2194f91683e6f25faa3dfd8e016bffa924eec45d80724cae055f62f511c4a9ef061d4e387e18e69700e5d654030c07eab72bfaab5e52a42fe137a8fbff5feaef01fd8c0aeb3277b4fac41fbd2a127497cd209fa17648bb312d0f1e193e99793044410efc70b707cc33ccaa533efcf7bdab6d232f0cc690c9bdcf2eb2c60e38dabc5069df303df498142bfe81de6153860f7739db361946cb2797b054c17ce19a450239b97a5c7ff9f720d2ec98d1eea3d41eff685b51385cdb1ce1e3729c8deb38068fb04b9eacf1745e4a99a1abf38d993edc55577db37e2ae77d4a6f8dfcbfd97ab14d31f88292049fef08fa6ae0f2a7f1e6b0e90ee56492f36920440403ceb6222fd18d0a354f56487f2e3863536c3e7670fe979a6ab4f701a5b6e87dbf7b2a66e363e88fad55c42656be7495f364d5eaf25c49870c82dcff95005287888d0c97b0decb455590820c7549a47f59f4f01593683d82d2325ba21d715b3f8f7160986e42800f7196ee932cf1af7f25b93611d5d7b9f1fea2977bdc78b711baa3a9feb90799e3907842752f49aaeff36352767d21a67ec7063207933bbf22373837c5405f63ccc8e052c3356e369f6b8c5c55df9d507787b25b0dd414d3bc6d57720e72d360c4014050189566561f1f074c794fb16491447cf1e71f98169b550d3ea71ab5b6a09b533b7e3c8dbb75c34dc601dcdf204a482c3a1ffb122c46e789e55e0800576beb112b44ce7bc6807c47059cc0c7693b34d38ed3bcf288058bcf7ad726595e9ed7af536c8b28cef624a0ba9ae341bd774b6a477f83f9b49040cde157eb58c7c9a75e3acbd3065009786cd08dfe23c2ba1ce4120db47e293586d24856abd1fb19a6592632c67735a3d158ff9ea9a90b2bfa1d1d95d899b8be4ab5ad9d8136704970be4841476d413e8789147d04361811ec4e9e6f9c0443003d909b2b101e5c252c4f5d3d1b714a183139959982da856922efd6f939e6b9b383a5e51f23be6d8625f427037986c3022934112c9158ecae28b775bf71e29252b611f5ce42cc8ecdcad3e3b003a60cf2f5f3c5e169b6fed316730dbb8d2d78f799b6d2bf781d7b5a7e6a9e077bf8dc000f63b7989ce9ccf06b6dc5e1be057b26f32e790091ae1e3746ed061f31e8cdfd8cb193fd86322a8e93e6b85e2fc4f898f9a9192d4e640128aac245462f93c67fabcf4681be448640a2346c3db55f3457b3720c7d0e557784deab6285d9abd5b3279eb73ea5e640042dc6d8e0edbddc26403cb0b6cef4346f69005e6a6068e39a7243a8dc9dfca4129399bde506ff7b49e04204a42d37bb0eb9a57ae37570495d55d98bf2720f75b3afe06f054e878554861871c9039195273fce4e8d5b8137086b5b30ab8ff9989bf144d9fc20c8ad592841798d68f23a25d37b1a1cb07e4186dbc489465aaac7c7339c2ba09495e5c545926bf906805266f6b07aa66b6371ec1429fe82954df0b9084f2a939c2a8d16849816d0814ada813cf92a1720e00c03843380e5b310500bc3c7915d272af98ce4f6c3693bac8d5800984fe88138f7e5a88c106f0c1ceed80a222020b330c0b94aab73f2d55c934a021abb56bd2597e4e21dd71b0444adb728f9624f3bd7592e1b126630201c03783e8b0c7578bceff1a25547827a1b0f3827b769819025a6d4973e8320702b04ac10a38f1e38c57bb6d5a5d41d38be8a53e7892a6ae7ec73413dee0089e252f2cbbc62d6c58f1524e0d99a34b9cea4811016d53c71b6e0013c3bcfc982bcd867ec6bebfd2223e9c4efbd601e24f72f56971522a5632274e4ed0a5f0796b9cd712f712f09dc2b34e34d41f3959dadf767893a995b8713aa695797d25448c0067bff2dd81fcedd21f1690a6dba8f6d13a98fa5163e666aac6f13a1104629f71fed95bfeabb9c8f6d5dc3259b8e105c95f1ed30b20cca07f094ce371879134e265c96ec215c35ef340ed40cfbae55a8252e6a9aab8ad8014bcdb13c9b9e60b809be787e24bcbcc78df2c749163220cf5f968f5c33d22e2c6bfbb3ec8a4deecb54d8fb9a5cd38ff16944c6814c325e85f423e35dd284e8b217bbe5b3275daf8f41afd983e8f75700dc85f19ca1488c633c10d23cc276410bd3b01e094c3e7eab988e5cabd211a4bb8e12500c959ca14bc5981f34e86c67131f592ece30b3cbc7d3d2a9c615c82b4a7d1b031add4aad75b9dad9487ba24412c446e8b6028690691663f1b15d4a1199355ce3defc84aaf75cafea8a75c4a867a158f719ff5fea91f45d1b5fdd90aaf19c41a1fa0805f8e7b58c80e184c50a760a6de0619f8ac2fa55088133e0dc0748dc2b42a8fbda62f961b7af0d7952c0db5a0f2759250b8b56771a5bb2b39f5c6ca18d1dcab6b2a4bc68e8de0dc066823b5bc180e2d1882bd2b591ef544379b82dc75651ba21f489d5555e7291152b84b904d5bfc576c6467ab5921c291bc2d64e449afc3c42601fedab61554fbc8117840d2897cdad36a2a2acd133cf7ac0b6dac064565262e4cbb8ddcb65e53dd5d0da51547f540e9fbd87f8a660cde31e7fb9a52f8e48fa0b3650fec3d69dbe9b35846e62536129ed27122828403e2df89dbcaa0004be535e79a4c756d5884d2adcc02d7a3cfe2b974c8e4e68847ff8a6734522c3156ec9184bc8af4a32b7d9fb3c13f47a1c7f029537b7cbeacd3173630cead6894f48b4cf9bc52269b1602837701d4d187ddb9b4a64ec36ae5f25acea4c775dcc2161d4ddd07110a0ebcb30d09d9614b92b6dcb50a982483245e67c0cc412503be55c32d409337e619332fb970b2d3230661a1ef9ac44c95ea2bf1a9b209ed231560b5e2ea38f170e49dc428e7c1d4f30d75152c4f12c45fd932ac436dae05c1f7cf48c43ec62748453f2af56c6ca42096bd9955b6d9f94f540916def8e8491ceba519b57adb21548000b14e44c9f17c16db1bff5717f9991ef8ae13d35e64088e76734544ba0112efcf842085a2a619a199153ddcb9f388d97d19cc579431fc6e8282136238b6de0cc196b0f887fd3dd0cb1e41ae29333bd2f42ff4ca31e0bd03f1f0025666124eb6b16551e286a361b286c8ab2b0095aa31684ddd99b11f021e40718b7d76794ad36c8cf746839e9a2d603527ec8d14d87cf29d65771633a8df24018d465d50430d08268d49e945d525726391e5eef3e3d83e4612bd8e8af9adf3a3786061e778485d9f9963b8414956a887668020e19901e5dc10ad3cf94522d4f2570a8c77bf0051665ec2dbe1f97c56144f114c2c628eacb14e049ecdd5ad4ce8949227cc05ad7a8909824390cf343729d1cf096c472b024ea916564226164ac92a1cbe59a7c337b4b26e260ef633684792909682117ad61a58a962cd6e57d5c03cbb1616dd924008cd7a76ce947a595c8572ae19680c46fae0173d7b305bb9da85202ad11ca9e0b71988c804984e5e0f5429d931cf4edb0a79775af5e91961a1d1867a1d6a6a3ca1e7b2c753122f4b4548866b4ad0272c59d22edc3fd1ffb07385acc30f87dc8094a9c860955cf2315604cd46cbb8d4e564bf6e13c7d765d861264214235773c26391e452ae3a647df11d80d669b0b682929ed5cbc73ad86f94f01a4d8a6f227ce4760e0def164a32a80cba8df68226514aa795621d1a9f689be0c096738883f8e2926d165842449d1b08841240405df8231ad94eff7b8c4992cb8b0441b019924ac05ee88bc2c99cb060c9096d28c05c977c16bec69cf6bb132e32b979aa8a5d04827e1d158991dfbf949ed319b99beffe613f5bb471df9905f6b270391959999abbafc5d554851074c9a1e9cb4465042bab3d18ae7309dc2842586e3a80914c99289f10ae7a0786ddd613ed085bda3286dc8519ce5c0039059d9dd3426a90d46f20651e69fc322223f5b375f09dd0fdc4457fb15ab80fefd3f39fcbd4adfc0f402b2627164e012245a02e7c33109eca167bf4d9a6e135e052f551907f899636c1f0604faa1914f2492690a0f7c884bfa4719e514ef6304de91d091d5847fa45ce7c15de2e8de70a805aafc1a4f5aeea4c73e3cd7a62eb97a1a305db51115399e91e81b66ab026697f659b749510f0eea59a0aeac1f95c5b5ef64a47db91ede4dde607243898ed0bd1c133623819506e3582f97ba9f20892a84dbced601895bd1572473ea6b19092a6fd961c652ae426b41ab1beb43d79b16f48c2ae89575a0ce0198f459316115fc384ef1ab7031a0cacf049c3964ccf18e70576a02c155f1b0e9e48e68f06a123757337435b0399308bd183d3d9d5ab44c986de95385008a5c014f1f268154427c3f472ec0394b9c0dbee3b7b8866d1bf31cb1712c4a67e829327b6b2466208b6d28ebf5d0db6a7f185b6a76c9a1c0a090b02f64f3d8d3d8e0fe4e316982ea87d6f3e82099eef11fadf82d82693b0559771c7e5b48d92e08b265f446963d4c542f18b044481fb7097407e51455c949e1692266eb559a6184708361cd195cb876018f19e2402cf032b4483fa2f3a9410404ab916c44a76c4fd700550b908509963a76e101c62c6f843d4db55ce9bd27de4def721fa5b57c5d6636ae5645318c51824178b5226b67ec5cad765f9ee10b6c9bf4a320d0d8c9eb623cc879b55c0206df6b8927fd13a44e68fa896b46d962818d1b37d8f0235ecbc2cb3a9a7cc249db1a8030c71ce0224273e7f1e44300c80f2e79cc90767f428ca84460158ed1ff49a316b0a5cce6e377bfb5e0092c79b5584b79bc0696f6b4384d0d09d1b9823b726900e8260cfbe18a45a0e100ae8ca9e77f7b494fb9ec0103e8b46976d801512aebe8b48156e31a2c43a8947ff3365ffa169671ae25af653bec370a3b22b4991f10f276320f1d12bcd12da0b85cdb9d68f4412b7f7ddb5a63781e7307baf9a8732d5902d57cf0744d91e77aad988622e5090d315b0f4c6849f264c66021b03216a2721b163f816c87d4fd38b030d383ae82752ca38a0c93a245b646841079ff726ee6f71d501c33dbf1a594974215fc2abd328bd9075f5881f6864b7829c346e8c1e66ff3c39a90f8e18c0b47b3d9a9f518134e8520ad8f7dc21c62aa15866ec6d0d0bb6644173d02ad14c6d6a3e3535bcdac5d8915591df05e25c98b74209684a60938ff360589cc6d99aa3811b7166e5306a2fbca7a7098a763041b12bfbdbae3ca57c307a6c7a332fa2beff4c2e580454abca8d79b0467295643960944904423491ff930262b039ac260c42d14900837be4d502b15b951c32f313ab9c03b7a8138450da372ff8c67a3c4e3445e982d4497badb8f93cb1b0d60064bfddf4e89d4bd45ba5f55af1e5d278326f89169dbb6c850f6f21c59bf2ad7409f20e6747042b2130c77a2e84ac91eafc53f13dc18bbaec19c80d5d57c8802afeede89ba65d4fdfd9b70322ef8049dcf4530ce4e905312a70f631e33f0223500b03bbaf958c03f14405c82ff7de7c4643b1df64703f08a0af00c15571ebc181ed7c0891835296d33d14bac15cf54349db014bdc351a625fa0769a061d4e6a25bf5642937372927fe396b0671d2bb45aec75a010324a7f8619e6a0221b2826fb9e87608e9f989057c412380c9e69f5d6daef1443657095355bd61a997b386528e50cdf24882c231e76eace1c2be742e3ebf2a0f16596a52387537e98298c59081cd1206699fc4f06ba835cf7dde45e3d891892c2b34b0b1185a3c85a83854ce8ce3a03ed20a5ce92e6e3275b3aad4d8270b36c2f480ecf76e508431831a92305cf4a957c6725aabeae0cea44ab64e9a27da53505c37abe5719d680f6370a9c25c423341c0b01848dd3b14bec007c78f6662b8c2917d13292aa7d3be2ecc3ab02056af9dc0cc8ba8081aad524a3f6abf8b09d4403bc10100836a36d31d12ec102246e517383f04fa13683d00e62054368b398523df432db798a77bc454054e94595d1630c98a1f512df12f515799089c43eeae705583812b7392a20971f7e75e962a4248c9edc80141675d54b117a9612ac6e80c8e460945d254eda346702906cadc1d07973799e0c26ec0d5c95a6c5ed76880543fe8b9a3498620b90615f971455286dff789abdffb18e591c1e8f09575084ce2843e487ccc9aaeecd6b0eba5c260237426866dc949763c6c0ee6019a95c2caa0540f7808c26f1a7648a104001a88f53ae6b20926f9a1de6e1282951b61f22881aca93cdcb5a05e920a28ff3a191025238ad645467bd42914f132527c0803306484837a1d3e27861c9c07ffc505c5cf0fa3cd0ece5d4454ebfb66c8eefca62072d6a14c81b747e5671820647b8a23e84fa9991591801d3b4fbd904f280712252df2562c6b885fd8001496d34c856ab9ccd83f6ac3b87dad7102832a6220639dcc9df479a54be2440d21cbe2c8788238d34cae05d0d5251af0d7b1b71369f10c5fd1d2b3772d64f067ac9027451e5d35772dda6c01225bad309459d1b58dde77f72f58408f1258882c510b945d7b45d1c6187153bfb6882261ac24b5b811d8ded5fc6d877b94d19d8ff9def19e4a1206d0611731b71d9d665ac4191491af858a1ed5a139e245bc9a96a8677149e994f8a4623be3ad26dcb196a2a85304aba819543949fee8795d5727effe6d028798668a4696080b1ffb7a0d9eaa9546acad9e46a46d891c4b4c2c6efe4ee0290e707402d2068afaf102b19681e9e1ff81df036bf699b8c40b9ad4d92b724008b894ff03252aa5f1871acda5b5dc3adbe63950ce0fcf5636c27ac231c0615c3b87095008bdc499a5b926d1388737e9e305d112746038b7099585411a574a97284b9112ee39f7cd9eff491ee4df2ad2c773fa3bbd842e444c50920869dca9d77d7cc6468e2d96d78d2a97b3fff787a7a8dfe9d48e79ab5119efe5d45c50869115e9f11e5c4d45422d9f796b3925bf1c8ffc110dc435074e5894d04bfa50e5beaa8e8fbf95aa6a07d98b5baf12eb1a6fb6877e307871dc33486fe1c3513bd56b56b420bb672fc048c05441249fe5bb440a8c8b7a64f8897bf22c6c31ace910d430480e1b2b67bb70835c063199a40159de847d086312150aa78fba0fdec086e2ac0dff459895286e26be897e23a02b6162ce5ffbd760408cc11957690c522d27679d5c890825b631477f5c833a4fd502a5958d63acc71901df165d5a58a69bcac027211908ce0fa261ca633b341695088147c0446cfcbe8162a55c8831591874417ca2f7aa3c95f3274cef87ea52dab31387f9047b278f95b0d722509baef3a426a54b07e79a0ae480887bd85278bd8a38f0e76fb3c07706dcfcafdf88d39926d09fd6816cec91db37e14b71c2d2bec5e4e51771bad56baae37cde54848fe0ed263610cfcc2cc0c486b7bf52ebe34571e3deb41a44dd4c1023d67c31b78d0c3194a36800de083eeda23978ae2d6a6cb7a4ffb6ea2ae2aeba6d9a57bc00087aaeb124627666be9886861e38757c08ea3bbbe9078b52599c00f51ded74e5144cd4f09fa9e23be7c5ef2d89d96cbe60991b03a54d4581c3220984fb6a63ba37df7d81cbfa043410562a4c158e458814252bc7e6d17c3eae8f54c9fa0754e4a81cecdf619df3a40e85922a66c354eeafb49525d5de3a4dfab4eedf3d1a2f105734d9ee156e2b680857ec7d7da0f4eb10727dc0cb75b2d1ef8d761705f41892b3cc136204820074968066222715df159b3de95cb62d0cda36c2edf9e765f8d4a13896b356ab65247e9fe6aa4c431203cb4b71929cd8b2fd0d76ab2a69668be46c0bdc4755a29fb8227342bb7e0c15f09fba813a7c5beda9f47440598555f2d217cce37472f415bcca887c47dfc271c6155ce9451c2ac611d4edcd877c6a9892d78f3a0a9bb3100dc1f3d044e41dd794c65d732ce62409f546586c258fc19ccae12b9e02fbbf5d079562131b54701db3d382ab5e6f567961dc242ef0c058172b2dfa048a1d70682c43b4298675ef815f6c6beaff584096de15961c26af824749345819c2dfae78794e191ccb5aee28ed9aec497a413b3131b7a77865d1ee6b529468bddd6f1b4a5469217303dfd0f07ea2abb041f5df9254b0e66b9d46ca3031df0a8d1ee78afb19ce805fe332c4905e566f4fd5c8a134ad4313930b122f3803c290c6d61b9d9bfe065e4fa31e0b9873ad43a0880dba52d81614ede93a882bea3e3e90f1480002230c2403c49d7ca45ee13891a08e9acb472330b24a7c5cb2bbaa8bb035c9ab46b8e05d5899df1acbc02a63d4e40021f17dadea5a7ef22980b06b0497cb3daa0940681f7cfc7e216a2eea5f3cc30866579a7deec11c968a85c5615cab781b0ceb17ac04e1800ae6a0d3c38c01277c26c770745aae18855b9bc6c56145971dd6b699ca03f8839dd026a0b8a765a43735cfceeb6e4c6c0a8ca894a309a1efc480ee1ac7a6ccb9469366e842462427a309c13beef9a1726ee7871b4d90b6dca060288f89fe7326e25404698255fe0c0ab0f41067bc04f97bf4edd96ddd6088c48f7579c768ef42164d5c2ef96e29f9d8e5f31451600daf52b234ba9621d0973273b345a993ef38bb2a7c8b827ffe9abfd7b60e3c431f1f336b36d8fa793632ccd092b01a42d3a9c67d0604f31fcb0fae68daf253a3fb82a3ed2961fcf23a7595ef33c3aee84042e7f16c7887ff388377a3bff1cd613e0a983b403003024cdbb050749eb9f674bca6d90b2a48e213a0cb8f3f9f9deaf67f306539b1668ed566529e43568d8827a70c6a66a9deafda840d51afae0be492fd63d8b85e1acbcbc36531ba093a1cbaaffa26835bd63f99a388085081733dfe94bf0239b664b48792827c801cdd93767d750e76ef1c7b16a37d1a579fc1fe4f492363d1a13c3028a1030e9af9840d9e0cb91de2f16f2cce695fb3f1421eab519ae04e0674f73d3a08545e3efb9ccc5df6b820d7a701949033b12c5369953e25b6b8a67d3903ec1f38a01d09629c4a7b603b6a9615470a64f21c7e1f337b28c81794146e7c95f8ea865a97db7b0ca14947b83e068162d9276ba7e2ed79654b7ad725bacfa9ef52e0d219dcd9351ac49b0670202221908dc1de98c88eedea10b32e005ad21a22791b7faab84f185e9d41851c26f99c53e9db579cde7e8c0b37905e18031a5bee85f8b1316efed90ffe98dd863116c962696611775f562df4a3a8494b35fe6d58450b2612045b415e4588da0376ebc6e7e7057f088f8b665fd6378b396ca1a5ad18a905e82aca38c57867ea0698d6767c34496c7cfae03018ee6edfa2c8cc82d13e9a5a468b69d1b4980d5ae14fa56233079bf633e19489e6ac0518c2bbbd7efc3ce6ed0bba2550f47d5a8253a7bacd295063857ccae6c6d0c6e7e80d60843f50af2883f5206bb42f42c2a2e3ec569b39a8ba0413c283e1362bbeaecee3ca503a79fe22d51193ecf202098423ddb13e91434f51364361224ba06bc2052f35150d7f8828db76889b71f511ba410d7cb13678c16a2b754502d3146886f436bc8e924f769b552b8e1ade689ac0c681badf9ff0cc1533cff769913fd4890dedc0eafe6855b7c021c2e0bceebc45e0600b465064d7e160e0ca584042b25b2020d51aaace74147b427f9e6417a8bf2a6d6a1ade9edda0d80742e7ded1b09475b4d089ff8dbc776526147dea330ac872baee2aacbb3833bc791dacf34a7e14a979b6b1a80dcfdc971b4c61a986c3876b1076cf5571527058d21623c82a63291f6738726cb6496cebd8f3aed88fd8d48403aabd74714e5d85d47361f99337cb4ca17e107e940d2b6d150b69cdf3f7c5755c985cd4199989aa854c25497a034f66213a571ab3a0be8daa0f6f5feab4a361bf9cc54419c751769b1fe3b7366606a3632f43d7118d361d47544859bf748646692ac7af44e90b742c35ca0b268ac91ee869498d2c9960830669d38587c220edb5b61f05c8c49060dbd0237b23993cb2ece19a6bcad14302de63760f6aced77ac7d1dd871ae9d9f07646205acc545e63a8a39896f3596db312b2081790acefccd8f0d7a521cf5ee6df3b2cec2fc040895c377edae9fbe49d0d9bd3cedd75572c2b3a230b93a4761058443962e162e0c433612ed8a1d4770ba2d7d38c8c33624bb4ff95de29530918258b666f4642b16f64c538d9bc85328c6bf28c3a9489c0504bbf5ae5662bf820263a5da0daa5e1fec169bc92692226e5d4c2305b8f1116bcc144103f36eb7e99b728400e2749bbce89186959f04acac0c6ef556cb198ccadaeb3070a4a14598fda017521ecba09c283c9e510cf04042169f0ec1ce2470d83924ff0204e13905598e001f57c8e79023ef08189e96f5d17311d9e916d343ff05352822eca808509e2176501980519193e141741ed7dc75b08954bfe5d92ee44badecdd27f4f74e5403162456d51b7d2b98db18083d42416f6317592cc4acbe0206832daca3467ab7fa648ad9808b64a12244ac37dad6867f3b9045b25e978a5bfaacc8c15b7cbaffa9b4d5580d50593d9ee6205dee458def86c90b4ebfc9e98440628266195ca5ce1a52ad7512d8ca492c19aa99beafbe03fda67beb3bff717fa799b4505b00db8c20ffc1c4ec8fa44c5b20d07e22fc51704b9fa1a580721e59686cdf8418793fd8b4d8faaa67c77da6cc6389a320ea21624326ece204d6bb206cfddf6aa6d7c42caba14981d1bbb567667a9fb2de6c0d5ed222365643e3073d25b941132c909a830b5897b99c5491eca4d48a8a3b9afccce4028439a81c221f28fd86ed82b2b10a30359e458d95f458eb7d3bad105379c1c95250a77982a8d862a68d8aa74f3abe35794bb24a162f728fec9420dabb02159be564c437b3b0974527cb647f60162d680e450784d23632858c3e39db5d53b05cbb43551c20d3463965c010d9d9f3f170f9307c0c4c71bb5aa8d9b2b1e8dbf6cfd3f317f194fd43273d9d0a74d3c661a06463465b551b17230c72382f986a164a27e6c180c89e7036c8194c9ef83da7c4583241d20c15e957b49e6e75aa85e7dce02850307884585acf52aea314c7669d4df92d1ddfe692161306405dc05acff63301619b9488a4bfa5592759650862cb6ac3f7d9488610b7c25ab2277011327f952287599c896e1277f5be06f99e4682bb40c38f23fcdd0c38f04d53bb2c98a94e334d05898449b76e0dc0cf661d73f20b2db5e074fbbad364ed28f431fdc6e436e2d4a2472c8a715ba3b53b3c9257c263be596e0322b74215188268ee578303c12b8200300bedf80e565c61a3a59c27e8a0555e87fbf5425bb542ad4ca080e65c88e413a89eaeb0624919cb9a5fbcf8d7c92bbe742332bdbe58f5f308951ec789e24fb300dbab8f18c165e15bcb41817761c38b4a1bcb29d79a80843ff713c4d690bac22e5f5fe71de095272336f0c2e5024f676ff75f3906ee835f74442a31537da726f185dd2fb82a7018b418892505b7001d4e20aea29d68f9d2999d6ffff7b381111fe324c7b0ff742c0797a0bb0f0b668ca28c0c2e5b835a2027931fef51546500dd06be076bfd1af4ea821f6c6052ac18673f771a83abc9781f0671cee9ad355aacb3de0b46bdde033ed89da4802b7ad37db6d71bd03fa69aae2b3605d8455de57da29a0414c494ef89c32937810c51b16ef3ab68793e87c9346ae55db2b0aa2f0573c40c3a0153c166246c7ac429624856b10c2fa87baeb097d3e0d1ebcc232b0237cadc1602283b5fc1973c2530d5e9299b45be48cdddc48263c9177893c95bd66b2f131b1106f15e3b50b649c041f54604c74e845fd953f0ea7ddc3686c5771174ed8464d633607236e5c90e6470ff6ae1f40ea405ea9545de41ec1a0a14033cfa139f1634bb0a5e560952077341c1a02b9a500beadab4898036798f10b7003a350bf279446e58ba8f5aa7d2d800c5ffdbf289104698e2eaa8b4f15d02858684dda77f802888eb23e5c4a44614a6873d0bdbbd520dc5f5260df106cb00164acc783eaddec3f7379f43cb8f990e4ae41f5fef51acaf9360db35a206825266fbf5b9c4e19c997228707574b05ab4b918d6864ad1ee1e5340285a9d84ad02e30d2fe186aea9f50f7194c7e287a15638e3cb5fde8084a2d9bcaee19c9bf0cd1f263ecc1b762ab70ab984cc2b5b03bebe7ad9c9b1df163b7efbf0de00ea022b04980ce5d1e357a8204f7bf36cff0fd08261aef4ec9352c2ca8dbb3708ccede0b32036ac9561dc9f485f5758276ffeccd8f1b5d7502488618e615d60b67a3b4095b8800323cc4dbb65e6eb350b3b90e0b6026cf082c581d063872757ec2363eddaedb6a968d1c8c1e1ab2d76a1ed22d39e660daf621215e049270a8215f6ff50b8af90f5bd23978206abbad685514c2bc0b4712ad417e80f050c08a67eafd50584b12f2be852664c0c9d76146f1dcd6431c7a1d9d9e83dd6184c64500c23b8c363a1e8d802f7b9be16442e16826ad8d32593883fee576fef1d6a382f21938f7edec2d5b34705af4afdcc9d046f2ecda76410b003a7be586f59570b9373365e38be0a457c5243c23038be85af100aebf1ab8afbfdd1577e0e2e9fe1a9cb2ad9ba1103cc4e000bb14a796e0de25605397a514e0ca1fd8fc3eac9d473fb6f095212d8b5b496460aa15e623d9ea3af738aaaeaf77ec91bdeb426b84823a8eeaf72e52ffcc5ba30cd1152cb7fa491e80084cb232d03cf9c31e006f89a2aea27994e9c2987612ff04afea93fb1e8a6cade0093b4ed121623211b106ca50cb9f6cf5a31d957c82dc09b06adb5c2021ab3435e24c74392f9d49b65a42bb1b0c9314c8106df6ce68b3db390ca5a723afc512f891f34ef06ca99f9237816fef1b1a055523a45ff2929f867b26b15763fc0c794e8a9f6161c5d5dfad52cc75fc517de8402340f9ca148143c7e23669149d584a817d22a06ca1614672c05b8aa1cbf7f880a08981ddd08396500b816ed308a224caf557d2dd4431628c64db1513876366133b54fdd297bdc6c8dee1e55e48c18d5e25a5650bfcf649386cb42aa238da0687d0a437844a374aabe72f12a3a120ff1c0e918839dfa8567fb161cea7afc68c78f86674a43efa402b349b3916af0371eb8fdd318439497131d4651833e4eb6290b5194c8ee2e8d85af0fd8bb449afb2fd0ad2ceb186531e3947fe4df9d4a948f76df112b695aca35e8c86fb48e8ff08ec6b86145ce493a70beeb87547d3a07b7ee2049b263112fc3b5358f1f2bb9730b692f6b56fa24558c11edd2f6edb7b3a097dd42ce6a63fc0102c951693775968348d33ca7046d673d1de5c054a5095912a00de18b6fc764d307be8023c955ac30b45786288d5d6c060976ab2f87cd625c3066e252774c94c1650459ad76b849402fbce0b26757086a71d53254ee89a01743008715ea0529bd273a54283993e3daa9e6d066ad38721b8a225804309ce60073ff7f1cfd914497eef4a8b6904f7c48bd5b5044283c3ec2b0ba0f959701dba383e47a62d14dae68b3540039fff6d74ea7b93a32a70131088ca3309ac3e313178bcf29f081f80f5d441c56768b187bea11c68ecd7fc9cab33072e4dd4b33226f428f2a39dd6dabb52e1d9b49a03d196d5552ed143faeb85d0d9b642f0a75ae890b31f2ba9cc17bd0ec47b1d3801a00b08832834bc871ed39e398ea4fc022e8e42f4f43a22c6230c9b0624785c8bbca546b67385d6e173b77ebd3b78164eee80a698030e2706ab4b295ba075e10562d071871d04a9882555a5bab5029a4d79a1eb4c9b3ff1a525802997d101bac39a3880f4567a25b898fd82664ecee9aec4240c29e883079e548f5d2f99e2f893ddf39170561b0eae18c8e5950f1c7fd0f2d266a19a990810bdb6779cd3cd95479033e331a5b8a5cf72633ffc0caf2dd3e0af0fb109a9e2139a654297fb0270198a05700c9d92e068d77644e7cb9f7c5ef9666f3889017a901e7200e83ff762e1e5782854331d4258528e8b92ef99a5a3db48f2350fb28763b258e370b5a461c042612030cd490f74ac05d74713a2579ff3cc868fdc4b90921718a9db2d9f92e02ea2c42e0fb9c10503ff35874e137e66efd502a7aee11a8a94e62f009164551bdd4987e675cb6985515b50fff33c96b713da97c80113ed6d01dd8cf1a463f4d4373604be60b574f19daf2a1f02346de2f04d0aa13729710da6d422e10f1aecbf808039612332d663ca242130a03297474ce2480a83c438e36234ce2950744ce4a0316a2b2f905c431d90cff1da48f8f6889debad7942354930103c9d0273574e9cf82e3da28162520dc877cbf6305697a2ce6dca420c1084bba2708b65b2e2e9a51703b9917ed6eab53d68ddca7d53d03bfea05c43daf3ff0603aedeacd5c03e4bab3ae6f0fd21101a9f106b81fa66a5f19b2431f7ff7ac436caec967b4be7ddf61ce7ce0c870f88a61e7cbae008dc3d1cd278612d562e76ea8918702475dba130edd7bdd1a893b6d21bc60ce27c8359fe3d647ac934453fdb33c97fad83d0716704df197c4418e85f4a48fafb6286e18b673a95045103b38e93d69f4d14a721f76b6ee5a0a41a1e87899bed465a89a7fdec0d58ed8755a4fb9e96dd13981b2895636d62245f971996ea16a4c13f73879d7d3f9e86050f81082b4f05308699cd236d50633e4f201862acf93dc9fbbcfbd3496284ea9e4560583802ab24b3cf04faa6e1f21dd06f8bceddce0359deec095ab8a33d27f8158fe809abcce2db3b0cf827ec03e27fb2c58523b6253fa27142b4d86cb7af9083000440010a50000500a20550d0bba9851e74e0f7172e760c252c8c6addfa855d61aa44f47a088300593eb2f05db4481336099ae56919c321e832292ab03bc27b32af09c1c2608cab6c020622c5d9c6878bc29fb449e2c8cd0ec55c922c0e4d84f4dc093c0e7ceb578425b091896337bc351395fdcfd54bdf69e34f18e4249732b3b4a303af459926b325dfc9411671e8706770b4ca1829026673ec906261f03fcba6409cfed0e33488adb0b0d752a8423b556afc8d056a437f2295019a22d121898ee96dc5239cc0cac8cafd90135f5bc5e18480fc1402160494bedf33ad93c497352bb8f70f70ade8e7e48c8b741ceef685ecae4ddd9c6582d4366c1078d84bb73e482ecf30bf0d1eb72363f8af67cc7028f49405bca1054d4a5d9b6167a05ca634c16315f24aa6bd14924e969f85030f820ba037f114ae28465ab3722cc8f715416e731deebcb358e0f44f86820cf391fefecfa4c0a05cf2fc0387a3f126305dc5d371836c20b0c3431631f35a4d9564f029d025cc121fe18925c8662d802e46c5e0bd98f6645e0c2e04af871cdce4a93724e0246a7c2f3f923ae3810f5171846c8071443d7bef7ac239d7cbd6e2b2ac4af1a55e5fdfdef1ddb71786963f0bb48fb20dcd3c551419b68701d192f7070884681bc3b649d97d3a5560bfa1907e966c8b06fc5bf001d31422f6856d0a39ed059c420f33732e21d4aa31a6eec76aa349267a8932e62909f4c3c93944065903af4e20d6c2368fdd64faeb56df35b8b3d08c8f974bb0d4d9702708094c7a58b1d9066902b109d0d0697e359861ee068be6849605fc154b7d08d036481826c6b70f34436b235bb8d5a7b3562697b75a2629eb0f90648ca91b861c3f397da87b8f754c21cd346fa3cd4de0945a05dc5fb828e0b0ada1eb6c7f5021878f5c5f5855d492dd5241aa98f599e20da0250c416709aca992ad11d8e75904aa22c152cbe25ea2f6ce93818cc2edc5c515039c9824d24afcc0ce97551595eb27f4fc42e5460370fb65bf1563d3ed7ad3ce341a71e400095489829dc5345ef9f13124c794860656209bccfdc9428ac4b76ed4d97a3a563db5d48c961ae4d8644b4343a80a914fc3a607da9735f07c84433800e0fb9aa6e24fab674ad35c1ba7baa2417591270f7e5b345dda844871729626c05edbdcda8bd3c1f711f4ff8a103d2a76a4b78f653e01688697e189c309ba340234bf0c51679710dfc93be009bbfd6e4f06aea76ebd54e3721d4732d164e97404bf970f1f44be0920e7b666c49cb7f8bef2c6edea06944e60271907a0ec4ee9ad3dce4a214d7fb4595b5c27758b77a3e2493ccb4dcd506bbc8835c7f37280187051b4ea20f39185109a00207d947e85e3891f1ed53d402c6c5dd7909c0e3fd63507dc0f53c198e0c0da7b7dc3da4bf752181d56470275f821f885a82295bbd1ec17f32b4a75d100fbb5827d3f6da59464081c9787817f906137aaeb50b06211cc7257c3d6a020edd4a07f6c530b8d17f7407664b1a8871cb73c909d1b64d2b603b68f448d19336ec7c83164c93d6063f0dca971b7656f0b4a9b785fb3ed41359a752d9bff73fb496dc67186c5e735bc124060a920fc28bea2268149ef7913c4cb2c71725d70c33245814519ab75d494009613782dba4ba6ab173b68f331e03f5802c5c596310e40bdcfad6dfa7bde95db693b727baa19a5f28ed0d8daf086fe0c300d12b1f7622cb197034eb1afdcd94e7654cbc5418085a37a36c6237a90c661e1748d0da4d969e8ef0fccbec1d5beee2863c24607e9b04b29332d628cd35bdc3336d798215b5da8aaac4d603fa8827d80297dfacc4ffb4c9b97b62e401ffc0e19c58900cf18486a63bda1f9dce61006b006c37b322fb01e576ca0264170c274886440eea92622e18f7bea86c8d85ea917f0f491795dac254980bcfc8f33384488032d079ddcec09d57559b28c2f31b1ba7d74484ab3fc147f9da7ce563f1632c9da51b180835e67b1bd1f02799dbcd5c0d87e66829abf556898182164f412dffc84f489f0463055387df448f8319222141a7e16306c85d78b92c8ef882cadd018186bb02136becaf3df040a21604e1d578b277f18ebb170a54959a99cf263a615ae85ba0fff3cfbf63a1301da18cba2cdfc594e23444369b07fb4fc8e0f3794346762a864694de037ecd3f6c7f8f2a69ac35f8143c6d0d3765ed08ab049ee841a329a8ec2d7e0baf8e6bf1e9cdfb53ea2436a00b061a0c7ea2058670d67be58e97a0f594bc88eabfc8afe54583756f28cf4ec315462ee22c15ae43a18c9e4ec25380eef76d7f568eb88fa221b782fdc6fa75519db34993da6b9b9a1a69b942e796b3db7bfea0ab0f5bc6838a181654dae2ea0c49bba8ea67efe2e6291fa513628f4cd49c4e809244716561e61a9428f1eb135c9e5d7f2371c7b8675a5da77cd7d08059f4adacecaa490ab2343a0985a709c6a1b97731842901f06baf287eeafd88091cbb8c6ab3d99227ab0674566036c79298ae3ef83693abe611c623808b71ac12698038645fcc9cb28f1ddfb951ff50c2dfed053e103289781952ae19b1a3630d3babee7abf558b8bc18a1dfe34609eff41d3e9c2dd86fd5d2f92b340b19e0e7aed497486d8a161dd1b31595187958f83f9db41cc32501714f7c343aa6dcdd94adfc08268ad136ef7caf6d44f8c86fc07bfb7cdc1bb2f6d793054729f6e301b18468825e023fa7d79248e2d60fd86f58b7dbe567a6bda928eded2d357811abc6cf56217eaa972dfc9c95e13734083d28fd9f73dba9db4c88ad91f7cfbf3c1938b5ebf154f39105126e23eaaa10d5ad5f80d301efeb11ffd306125bf52a460294f2a32edc4ce32c2aaf3c75a87e4cca610473ed9e9419ae25f7a726c972fbf80a70063d3f9b8e914d6a64d6c65c2c4c18b4aec1a556c814b61ba0fb57093c1b1923ec689123fe9d259129824e33348b309f000a2b36f7638e39b9ce7e175141320af9f7ae736fe43b68e278e31e5fec06f5f860e132436fee69f1c60ab62e0ac1dbd8b8c05957295515cb8ec3e9b146c31d86d7b111fc87572cfe3668e87ed8a49fc0f2055967f1433f1444997c271222f505e3ed0cc29206768a59c8bb0f0ec9ef8e8cf5371497078e5b27abac19ccc3982c733288c9d63d97bb210ce47e4e5aaed5062cb7cce3ce1ea1698aa15f1008ed2e7d33d2db70094e28522cd6757b41de6b639aec95dd60dd0e25189e0c8c040dbdf91cfc8f0c2fb88fe98f5f74447fc4f11abcf1182947569ffe4c4b3abd632253febe1df0bedca0410cf0d100bd2303801f81039cda17a073df550f4b5a00c1ccece921581d1e6ee4a8cfdea502bb70e134375bb882a0d8c258d0b10142b855c55ad7161f666be3621441f9c68d88ec448acfbc435ab3f55861bfc0e910c1c0fa590dcdab1f1ba544832be0f0b800c44a78fb79a201b2a5764be23c037dd107503975e620fb07b235058a2798d33715fddde8ee09c35674c174494e4816ac8629d84d16f5c831c7aec3b32257461615399c0031529fe15a9019ac50cf38a3501786158512dac140b6bdc380549fe8ee052845e4fa3738381631a1f7fc4c4316f72ba6a1c24c87958019ceee0cc135e7853802a391437308738db302400178c470668383453208daa665ba1abc8a16a42001ece487c0090358accdbf9719a82dfea45b374c10ef9f438b6fd4d9709f0d28c15f41694887d9495ea27907c256faff05824ac7c66902f741ccf39f122434bd90d4be705480573e4ba4da7fb0a3b5f9c0221950fe9ed5a91d04281b0685727f9fbc7b76f749411972c29b753c79595efe5b36b9f1bd65cf60cbfe234fe4d3dc9d3d2f4e9d152233ea2cf86e8d6a98fe759749e8dd9045282de0a3485c1468b6bd405dd94f542d54e048788858c4d510adbabe8cb1da83334ee250b5d7537c4b557c8dc02ec9c3e3d8f75849d239f19324eb63abad0da1aab1764d694540528a0c59c4aa7cc262b7ac796e28707d2fd7d6148ce85e6541896430af8031600aa6974f1c07826d3385411557061b9e2c163efec1687f7e8f6f99f8c04a5ca99533de5095999359cd3f41113d88a572392cb5a38ec442dc251e16edba7cb084a412f8c3e729326377f6bbaf91ab2b191dd8cdbea2c1c4b26e48350eedb59ad4cdd114bb00096feb74d668a8f12f0a24eaf6101b65f07a8fa7c16799397aa90cae8cbd9bbefa3aa5e2bb273f13107fde1d180fb7364d084faf9821b3ede287d9e93e3bcc9ebd352e8d6bb06c1f80846b32b2c292fe2365217fe535b02ecf91a22540619c0febe5b3431e0fdef4176963fcc662698519b752fa3c4e4e7000107af14341f5732e90732841a79ca20e59fdc9e2435cc5e2b0bc5aa410f8ee346e55f1ac6a026b43bb1fae5071d0f872a914ab50e0a1b26e8dc2de3c1cd86127fc8044dd591939d504edeb5b926030e7a34f01c4f537d0702ca31d7ddebec50c2176c2f057d713a816f8f613201418552da69c0852eaac2d05fd5f737b03a04fe5a9f423f06a4cbae97321ede04fd36d13de347de47defa91a6b897a4772b9dbbf498cbf72b719ff102449a6ee4cc4f461ab5fd857c287fe4d0ef4986e0811d3af0d593bb4b5a1481e9db8451858064febfdd3a05011013bc47975a9b4120e9014484aafd32374c8505ecf642bf23792113086cc07961fbc08c49100765524fca8458211a8feed279d23e81b23185c9831be0c088b02f42c4be9ac28b60ea529f345de1d42ed917c5d240ffe2876b15799877e3a7f860e84a0d11efd5cf62563236cff7f16c5c5a2f6bc86ce200a9affd61729edce933d79eb04f1d248308df145bacc14604516651f73fe833baa00d0b0ea6000b0ea90c2013980eebfeee17232ac2151c7d221f7f6da45ac793beb15634294b3f397db7b44b47350168bc9996aebdbcc2372146c85ac77cafe368d6fcb4d67ccca5e330c82f4360b83d66566fcc79310d4bdd7d9011df8b9cfff38b0af0729d9ed219168fe520e308c46acdff70b9ae82f9be0c04c0d34d61f1c13cd0ba281b4ced9233cda1c57dfd1704909e1eb5a204052422a04a648538404a882393116ed3ae6ae9c4d03dfcad944084d8525346756308f51b5b6eb48d0b42734dd00fec4f5a458be26d074f7296d3731241870c4e501ee85fb29aef3fbc0506756d0f8ff14349c53737003283a0b3d8505c7448dfb51b83b1c11d4ed526ceb8c7bb4242ebce4a787056f521021297da2456a2e08b162d54be1f6f2e24588bb093a04f4e39d1cd10edfb44fe795c7888a6c1b8d8507c30c9d3b993e4f9f49448e982c2176252061d89f1bf7a4097d4b37e74b563335536c6dba423a41283cbe98203eedd366fc94fc73eb1c4187040c0b210107346e771653edad21258034013d0c8d9ad696cd251cf5d7b62be4b14db4a63a494838097af121f8921a38c24612dc0c3edbc4a3093a6125221d86bf885ad21b538f309f49a373404067b750821fcdd854ee864a8534c9e412b8ee6ee0f6a996915857d8e56ae8a431df1696fe0a3e909257ddd2917317457c647a2a1d8806db267b5be2a584b613dddba1de42dc079cf26e561c15fef955e66f2de6509373c109a85253bcc4f0bc3a4895a2de2ade9266e6bc561838842435b4302c453f2069e183a514b308c68080380fe71b390108fb16c5f555dae9a04d4674f18b7b059db7f9ab71c2bf6ba3535d664800e7333c38c70bf97948ba8b1fa083d562fb18c04a9f984bfb904e76b6e96e918b552fda4ddc4382c0672cefe65bfc3f4114c8d178b6bb60ff3cc50ec211ebd94313eaac83a5156f7f5425771c303c51cf1e452f45dbb747d081d4534a8b09e2881d1a49c16c56183c0ad2cef504da25541df9989dd75f261923d4212096a3df578511951a917828a92a9a735c9c6b66241a3d1eebd534d64fcf87206db0561eb79cba2df36370485cd2017033c76caa2e82107b40fb9d9a7859438803559c780b577fe68b070921e72ef15710794038d9d116876d220b767e5216d9b545f2118dbad4046f604497ae13eb13110851093521371deb271e6dfa401d34c31f843f4fd4f6afe0101a2f00260d222c01c55f4cb6e5d6087a189d390d02144c040a965fb85b3092b24a075964007caf31037cd1c604c0615522267f941ba826da243bd58860b4574506c09431564077228062e4468b8001de11822556d7b4fbab61a9f9180f3046afec38400d957d380686887c7f2b09d95bee2df796322599021b0c0b0b7d0b37b8cd61871d76d2ed1525e2f6cb201879028a0f3e2823080cd0461a465924d18405559c01f60c4f1c4a73dc733873b9991bd25c6e09252ed704e4de06e1723b40e2725266b8dcb7bd91bbd1956eeceacabb75dc8a00b73eed726aa7524be87037ef6eaf75391b4a28e5a4a1449142a559559aa54414577bedb32e476b1dc0074bf880b50ca62290feec72a8ed4151968cdabc8f24c09d4547dc49d9a68b7c7c563e3e3e3130f9f8805207675c12acc48fc1625be9b6528704b554dec8288411d48331aa84b183181c06105e00c58c2c456ad8e0e533c545897d74d1eddf91c20e51a2307185952d3970161ba478c1107c08428c21198238b18e4f073db8fd335d4eaba183947f779c33c763bf0e7690e5f6e7b04fc8c4187ce088541cb194d3bcc344860c5768c982c393202643d0a1882652ca9081a3000542bca4b8b0e1ca882889db2ffb6fb49021447ada94026e06500004832c54d04312234049831134d220820836a001760cb7bbdb86f9325dce8c42ccd020017215372ca108e8ca97f2a915577e0a89225cf97f83299208e2b2511256a2d0e0cad799e1cab711d336fdc426e72bbac7743953e2c0e8f6c3589165373211ce4cb90f28424bcbcab74aaefc140dbab8f2572fb8b2065c5cf92f37bd6a2d56309a2676e5e4f68ea4680331aebcf26d97239d88010b4cfaabe39ca82ec7e5124624e574e8d8945f2373f8e9a4c4b425de72e3c7e79137d62e073332a286eb515cff4e7a9600f9350a704203b1e8e3add5fcb8af7372df2a6e5f48b52fb4f40b53ab6ed9ecb52e278b463680c175c7d678d9e9dd331ae1808ceb3d4451400184228ac8c11243bcaa902109a52d43c8c206a0f471c2870b8f9121820d5737d22352f0ddfe29a148638b8d9295831ddc7e974d6cb1e104ca6f81affcc221f68bf1d1e518000a16b73fc608197259e8568a4bbd151e9a18b1c1162d782901e5d778ab851dbec04225066bfc5c01e5dfb44daf71e59bd0ad0c94cf2e3fcbdde8333ff9811249ca15106f31c844b9888f182964a51bfde3bd507ef071bb094045651358362ae2ca65a322846e58b5b8f1795c362a82a82add78551fa7786ba231763bda9a98159f37beb95c2344a506f52a3e155ae263430dac711d05d7b5f48a6ffc1922ac2ae3f835fb5816cd2893a807fb44f1f9c85bf40766c58f015211373e12377eb829dd78e42d069192eea614c43d68113e20031a5ca9810f4fcc900215a230634a930bcaf84902a4347ead492a95b609a3943549b762174a356889507d52a3c4af45554adbb0d193a21bbf1eb54d77e357a46e71607ce91df4dad23f4ffac8bcf123110e45391cb1e0c6df9068524771e36f4a72cb8d71691e71e36fd9951b9f16e146ea831a373ef5c1dab7af6934e828bbd236d952c6942d654bd952b69431654dd4275ba24031280645a1f82412cd236a706a8a3021ecc92422925bda269c40d367fefc002599425130d9fe23001296c1edd9af800cd20f59e9c7f6f453b92877aeed53f512d84c3158843c246d93c45b54a88352f65334e83b89b7180c127a42147611d57e886efc49348be651fc89c402222f30e5c03e40d0452974910a061e6ec2817d8060085f492132a9a2cfebca6d8faf5c9f233f477e7030b9c24893275a8085108f2125081d6cb0e508192df8014202145531495442836e7c1a4485e893f894a82a8d008415f61ca65ad8d79f2af691b3da59ab9a9b7e1cd45d718f7e981a6c489b7b686758ea3954a69141c3f2aff8a9aa9f3986265fd0d0c50c67c48003907fc5ad17a1356a500ac306a41b80fcb54321defe38a6218346aa5fc88cab6de2a7e27ad564917ed8c7cd7dc910d70087fc5fd96bdf4f0105f8ec00f116f7f5b7ef87817dc8ef2fc10acc0004b8d847fca65fbdeddd939f790cd6c831a1cfde7b34cd62167fe6c9c0e3e303d2d73c19bc083e20fdcaee123fc66e750c4c4058b8e1f60711d23fa45b8cc44632f0b9fd30b40d8a8d6610e507731b95bd31bd622a5b6e3f0e09b78970fb2923b9fa81f407e917d224b40d7f8a7bf04723147c4600b70f183dbe6b64cf3d22fd78747ac0cd53ed50d7f780d5bdb07de4f7dc237e1883c5868ca4514fb5435f7b8daa76b6d77efb543bdbd7af1f8fdd3ed63eeaa976543b1a4fca59f4b3cef2f72f7cc1ef0a4da5548e1ddba5111bd2fb16c5a1bafab2a1bd3197b9196c7dd9907639a91ad830f5b2fcd545ba8b8b8b4b47b691baa8db5f18e4c68f1d730f7e96a7d004377ed44a8dd22b274ac3621fe46acf9818f4f6531bda48e6d1e9c33991da269b1b93b390bc49651ef5ca513f8fa8b40deafd2712a35047dd365d95f4cabf26e995c71a74fdc38ae4fad7a0b609e7d15d5285dac649dbc4e7c891c95bf38859fe94725f68531b8ebf30efefd08115da98aeffc6d4363d977e2a7a39da443dca2c6a9b7874825f27409686ed8f377659f7bc318b3ad8196fb27f349166d254aa4f6a13dd8f73748ecef1a98b04c440d22bbe3d37debfa16516d7606972e317da242ed0a062ca19663c4901232921a2c90c198a90c1112033b317355002082f988810031f905f875b16b86265891f228cd41005e4671af6256ae5f835a6a85855c72ec705ec51e0db1f017260735c3e3a22cb8def82fcecdd9bd9bc95e6f188e11efe981781be3727e4b829193e80dfb819cfcfa296587fbe3a2a48a6979436ec2007237840458b2aaa80e20228b6c0610c1bc6804113e8ff2d10dd60048819a051a5052f2802881ba000420ac4c801e835fdf2bbd977fc60e3a386583565020ca22ed6c012850e3d866002052f687a411a5a401e61cdc751200d9141840d469c40418f1325a640220653182551013acc65a32119dc1730acd1900b6e8d0c1a5e7383c32d1b5324513183146110b106e8afc3ad1c35b0010c92509282245800fa4f1d487feed574c7d1b1d6daa8133d135c27460e47c73fceadb061cded9ab6b929e1882fb5e04a2d68240a7c318ed3ca3793669529ecb4c2b10d3ba04adb4876c0cf83d9c79f4936f3234ec79f088444a7b5ef225be483869d5ebc976513fbb8712b588acb4d609f0bf1c759405e44e2c1a0d36fadb7184cfdaae6e6368eb31ad478b0f2934a5811488e20dd782bf758366ab2e563132db1cb51b9f8ee54d775ecabf7a8f82e3cd8e8baf3e376c4bdb8630a1bc6d78ddfad20ce7a794b88b3e2bbbbdc5bddc79718d6e393458e88b7184c695f8ce57252437a159ffa73e7aa44521e25c28385e1855ec5dfb458fe1c1b19969ff9398fbdd40d53d33b14fa4f29b0f3f98978ab7e7c18bcc57dfc14b77a66b8010b124c3c397a628903c8c0890c7830c50c7a90848f82d440a30841f800c8073e430801041a40c181d115d09f6bb01f527327e761547b1e9ea11deeebc7539ffb78b81330f504b9703cf344c0735bca5ed5dcfe22d0868522bd76f0c2064e4e3e986e9811438619dc8073a52939808430e208073f3cd86ee4d0c54b0e4b90aa0c523c199238c142c380c8006304328a9a5cc934a6309860c280324410a4019e8881850d1ac4608c8c6332a38a278610a0e460868b2cfa810a8e1f829cd0b0c5100526069f2170900310c2c7835714237810c3103cf0696ac14b2a088829377a58411326b499c80062c710b0243183eec5670c2152412022228c2bc2701245d351c4045195013061832968f0041339283142dde82eed6a9b4fa98722c4ec1045a887251b3d28d944e94d636e8cf159c61d4734bf73d49d97d0c8cc4532cbb0d827e931cb9c5bc82da2b0b73a5a6f610cc63ebd12cfcc1ca3099936a7c6918bd8863987c238ae436dd8893b9d4e9b36938a49cdc4e8508a69ba0447a351d7f183319ea66d229649ba4d8dc382e3388ee3384e6ada4c2a263513a3034b8c7b8cd33dc619e39c7f84b6b4d4d29f685b36e7dd0270696ed5b61a4c4fe66a1b86bdcb77fa5566f3189cf393dc03fb4931893d712ca39a84b255ae3ba1accb0b4c8c8c8d1ba96e842896de9052c7914335a363c708ef7d00a009800006b09ab7b20850001eab1e2b9af9093df3848979cb6c8617bddaca0570ab0cc0ad93931ecddd40181a00e0d6ecbb957ab76a7feb36c2ad75c7ad9c8e5bbb995b4faa5b51396eb5386e7549ddfa72e356181bb7c670d293b9d49381c8645637021999985b6dc0dc7ae3e5d694cbad38ecad3954a75b67ba5b7570b7eea8b78eb0ddfadaad1ebdf5cb6e0500762b4d0004c0697deb005803589de427b9c77c1360447440892d3cfc88a183153f48a366688a27538888904288a39e1d98a0d164461447b09481461644d89092a48b32c200620557506c965073c3c5ccf865a3254f6e4873a3742206f3e573f1a29288c48b305e3c59ee0ba9d5bef0b30ffbbe909b5c5a66e1a83b9dcc2f0ac5254ea84d5c128522129d244a5cda536b53a9ff06425dec4b92f296cb59fd36f5ab558c96f8a4868a4df7f0c65dbabb7fa194382edd2736673cfa386f641b19d9fde429c43bb168b9e18abb41182e57b898c1b9c7e89e4aa55231de94807383c395e04b9c1a61a5320d177977bcdd7991f49f1ffb76d96849c90dedbb0eaa1237915c18361f7bec5d40da06cb688a59fdd817669907640769be6b07088665980b08f6c5582e420a6f6e1f096f6ebc15bf9f8a9b523252141333938a8999f9ff9f49c5c4a466626664d4b04b3e37ac7919548ed13d468ee338771c137470743ae4c103cb151705589ac8010b5ae30fedb1ff91658f7d45b0efcf3e55e4e119daf954916f70749adbb4c77e477bece3b793fdf6f114e9c7788a0cc52fe29ff58032fb1dffcc19cc30a4f961de8dd774cb2054a6617ff681ba4edf433bc3fcec67d861c4865fa4e317999f4a3eb6f256363ffecefc8f67487e3cf1fb53c96790a77f7eaa1ed05f93e1fd57fe35fe374fda89f496466b74ae7cb9d42fa5d77037de52653bdb7f3c43d9b7539ffb789e67eb01e56bde4d4df46e3c127a2571b07ca7a6edd0e791f1d6dce0e8c8964e54a60927d2cd3cfb1a9b6f04923d0efba8377bd9e49f6519533ff63b8c581c6fa9e4733ade52c95aff87f6dc57c45ffb2a5bbaa7e3b1b3b00fa757d9f5e9c57e0cf8a7a28d0115fd1dffed775a7bd5fc528fa3e30232b953d16f05ffed31e0bf7d18e8ac662275cf1f2ad3b44ab5427dec77ea639f2a3ef63de0cef6aaf8f10cc5e7f976fab1c73e1efafd3d20f73382dcc5e73c0c8c30bcd02bf915199a5fc41ffb8a705e064e903fcacebac132fffc76fab3c7e9f8aac17161379cb49252828cfdce33b8d3af9a9f05a59452662fff06ea9e3554a691ef2fbd95d3f40ae3bf18106f31dfbc4c621f3e2e9662b24b4ab4e808a9e260a30df66671b28fad3e0faa7e3ba7e7b17ffa54f4bb6770f362d370b3bb797d516f57383dea774e8ffa7886b6e7917ffaeddb41bdfd78fc513fa1f40a53d15f81274f453fd73b0b7bec19ecbc1b6761cf791a0f7646c17ed362b1979deb253b11c43568b835a3e172dbf48f26db86d28065f494ddca022b9fcea2d954574e6722d5fce9ffe8d7be225f91a122f43d7555d9fcf7962afb542b70dffd8f5abf038bd4e73e55f631c8e09c9f9afff357f36be6f6ad3ad73bab4b015977bd66a0ef3f83d6a95cfe3de0a7929f943d15eb9f4cd270552ea7efbf43df3f9ea1ec79fce967df8ef6fdf1f46b8fd3aa8c7aaaece933a879efae94b3e6538f9da5f160793e187a35b5441fef2983ca3421063479be110ababc7e6e40973742c14e81bd027b7bfe18a32427977f70d9c807549c9bdc7a19b90161fe878233ef801fda35e06705c294f07303c27c0873f9654caa9fefc76426a26189f8cb07f213887df8b82d9dca0da755f8747a25a76742afe4ef98c2aad0365687722b0a394bfe0412e4adb9e42cf9f2e513f196ea258e971f81bc954272f997cff19fbae1f5f8cc255f62ba1288b3e4cd478204d22b1984855ed1d75cc8892e20ced23afadcb9625e8b11d2666066bef38626c0e0cbbffdadd73da0cb4fa00774f9787a409ea19ddee1090249961840978f87270824597cc07661020cce7caf9757bd4a6350078f1d0410c0955f6b134b6788f95cf992d2be324855c4cb511624577e04a213f3918ffd780a96bc65025dba2648b972068870e6cabe00f7902e1e06224b7e7b2f230f725440c85f5cf91c17103217576e9f8adb54dc77425fae3e906ec51894e40b914f44fe0bf2892557ba5c69e40328577efc911f819aabdbf6dcb9bac7e90a24d626367a4cfca32784c90ffdb160bf8bf7325203f6c7cefab1a07f409c85ba41367ee7c2fc6dfef6a1c037fb1c1b19367e54699fcbbd7f043dd373bd5e39fee55f385ef5b5867da0aecbb7673f0585f51bd6a856e87ee681743ff3a9e6cf3c83404e2e3ff33f5c7ec67e117f97ff617fe667be22fdf6197cf118849900832f997ff1603e8727f338bc98bfe1a13ee5d97895f7f20cf32f2a97cb4f804197ffe15682c0823540fb2aeaf17df11854790ce6f018c4e13198baaed76b06d4dbf8d74be65f30eff2af987fbdbcfda55e492f081167c98ff1a28fb3e4dbf02290b3e4bbc078321e3b0b1593d8f01894f1188cf11884f1187cf11874f118ecf101329966164e929fa31661e39fbcceab37d87819b33d5acb04c17f7b0655fea956c8befe0fecb7afc8576468677bec8bd4cf7ef354aefaaa1f5b74cf851472767a255760f0953de61fdd13c109f102e995fc0930185dafac39fae9a9a7f257cd4fb5427ded8bd01fdaa1bf7d11ed6b0bbdc23cebef2cf99b57e32caf5ef518a40c621e5367c921427a257fd372859982eabb57e319e5deff47f7fd15d1be7b8dc1bac3885d79ab46b5c2f6a7ff51bfbf22fdf54ff553c567b048fded53450619cc5e93c1fecd7e957d4df637d9e3bcea977d7f37ceca9e3b578db3ba1590f5afc850117feebb8f1fae308061a0bffb54d8e7dabe3e83d8772aec53adc0bdff4ef7fddcbb8c0cc8542bf873bfd3dfa5b81b2cf633f36a9c95fde6ad9c95bdc6838d47e24fafb24d8b8d40dd728c1a3797184b29dddda5e699bb9496c35ee02ec6ec857408777fba67422eab5490cae1679799536f77777fa6cee2c8ccfc91ddddddc39e6b8110a317d89c139b53d36e94588c5e7811e3528c63c4e8458c5e488f31c6e88584819cbfc598485343738566bb75e3b2186316bf4deb52d08077e589d29cba1cf9923e5bdad9735762501623c372945a979901c15697974853437385e60526d2d4ccc74e5d04e8f540bcddda6b1fcd0891a686e60a8d0c77eaba28dc9269bb92d3b41b67c468ccb70d4b9f71ca8c625ba836d5d036b971658af35dbe6b97ab9259dcca3efb6250311c13a3559a617509cdd863f5594425064bd1c0c1942c2ab01b17df141fd4964cd3106a160331e5e80a6b2f1b4d21e2a2294faa3793a29299da29476e98c26aa02989a2356e30f4c670d4d69086daf7c7293efd85da3784e676371d6d63b8646620695554d8123de2a696ac060994e428b565fc60d4c621609c849a9a1b339e348b2ffd496b6adc891a743447a92543898c25329864aa668c9be356a17d37674439438ad7788bf66d4209c51f1781c0a624dde5246d1ecd5b8e529b9a978d9a20bae1db15c88cddb513b5ca49a67d64c32134977ebf0d4d5fb1a9cb464a8870e36732d8c7422e615d9c68b22e48e04006b30ccb5d365202c40d94bc60b24089cf44c3d2cb463b3461517600e36246ec7085739a78e22cbe9a37239bb031ca1ba68ac2ff94d11a5aee0bdedd95bb9de8b7b277b7f46e2a3ceec13f3129f9363fc63fbf883133b32e337bcce27fff984a592be5e7de3e657777773333dbf0c2461d6c76da86b09763ec2fc648535f36a4ccfca06f6143d47525998b04c4b0d22bbefe9e621f1c81b8c695b69bc82e437163bd31c6b8aa312282841b6f6e8c5582f0951b9b7c7e807a8848060006103e39a0010e9484008631890090809830b5008a0f9600c31854c30da3109458e4831b398c610c6392d28d313211841b677c8ecf61fb6071c3fe6924498eb6b8b1975cd104032878301483a02760d842a71c2861c30f3d4061f4829f1b36518c1f724c305d4a8f8e2e7ddb4897661dcd9820e281924dc0c57820440e766454051580d1932bdd8aebdf14005288b8dd34225d1a35175c762a1f7a95dbb1bfd7ede7b8f443e8496ebf0b4d3074299da599b4dc76d7165c652b42153d3c4185900d53802127ddf6e287090ea81c0125090c99861bf212331d01436e7addfe18a4445c12c64824258c11a93f32e170a568054a646091820518c6a698283c2c61051a601c31030cdb48d8408da49380612fb9fd5d1472d350282ffe30893e1bd349a46322c459fd73ce8a719dd44a2d468649ba7552670182e492744bcaa259b15193b2684b4a5a168e6e9d4ccaa22d2969594e28ec8bcd7558104ad8076e9dbc75faa293c8e4f6e724d6aeeb8a8a62aaa8120fd02ce9a61555c1a460556451150ce908ab228baa201d55396d7fea50e85b899ee4a06e1a2614251ea0272614257362a3c34e2dd451b226ea27594741d99a6d28140aa893f410de40bd04e7e6e6a68190709d76a20dd44b3aa893f41294dd501caacbf147752edc4db76293b3fa9b7a73790bf59cd405b8ac44a385470d8b19d04756eca353d2b2fa6fa698c735b58de5554cdc22e3814b653a2ab193374deefb27d5dc87dd1a40d1afb6fb98db36ad2a69b96243d64d490bd3921617972e87be8ba5418a595caeb9a8e76d3203a2159477f2bc98acf16abee6757cd4362ebc92a16223bc1e01192e06f5aabfa8242ab97de42d1766cdc880a489452b11f0bce3e479ca30ed65db626d26ae0770c494a22a77e6f29422236e45a994ac77775bceb9d9a73a8f124ce024c60012d01b9f04a91bb1dbfd3878865bbcf2f8e9806263f36c2207cb5a38117b90851337bea466b0195a00857ca589131f8cb84208498ab200435eeab9610918392039820a0d5580213301b96b7103cb5d86163f5c1c777e8aca6e5299851957ca942a0b2c746431349140fdc901dcf39fbc99e6be17fd7e2316e4deb937624114106fa9fa619e41ef4f8501eeebff70f997ff511fe62bc27dfd17ee5365f5537533e84941841071e5788f813c294e7777eeddbdde789c764a3f9d5e79bbbbbb8e7b1077fbee48ee2d1cff291c32edefeeee98bbb7b3a045ba90e302e22c4c76981785648e0bf2bbaefbda79f55f3c0ef52fdee9a7e7caf1dca9b2cafdab3ef708c0498003aefbb8deb3fd7d4e77777777079aedeefeeeeeeede35dcc3b1576517e01ecef9a3dce9a6b9bbbbbbfbc79f4da3391ea7b51c1e107fadd647a19e437975f7eec69e3b55f6b9aa8beb85e3edd7e94da0b68cd77dca8b791c1ecc5bfb37a2cb5b170a652783af19ba8ff9d70be65f2e8ffaa59317c459fed67b89f1502e0cbabb7395b68c0ea4c4628c1be3538f6f3d5a6f459f48c1f82e2b65dcc855388995628c32fea0e8c61a638c31d6c42a5904b125e015639431fe608ab4b4d3285f2ab36c076096a971b3cfba9c2c85c923e69c0db8d34a0eeeb41243ceb449408e949ee45578105e0212e86fad3872dd0a2eae5f7183eb58005dc7a20649adee222d6a46682a1631a1d89d1fe34d931a980042142da0286a0128d140d283123b08e26809326ec0b294d1605daac8f23263bc79410a92a8c2053bcc200695216230a184d0952557f460030ee60d95815e6dc9c6ceab90e2f6ed47b9afbc27688c16aca1460caa7c714412520401032c4a20a1a4c90a9696a029d50e52335b9783c91b9c2cfdf6892c9e3022a75352c6a7c0501a52d4a0a8064440a3e70822847e20c20f4af8000d501ec132c604254bb9b931412707f320dc7086f2d0832fc6b8fd5a97d36f13c4c7c7c7a7084cddc6fa53fddfbfeaafe9bfe9ee57a1a94d7d4d474e010d8f908412580a152330e42a5698b5f0b8216fd171e31124d1c995f2398c51a07062898f228a2bff06c78564e5ca0f72e4ca27e2822b1f862882f091e2879f1e00212505499103a11caefc384518571a5171842c5203c8cee9b60a0db7bfd98c3582e82c538e5600559ab81d7180d1effd4574bc4f1d332f755c5ca77ff91fa8b7ffe3e55dbe22a77f797b7ab19f6a4a1d179020f128888ecf698969c791cc660937f5b59f1e092500917093c475925cdeb2a81c2fbca44a3d8e985496e4ad08e44953e94a59bf15cdebb85450a225cd243aced2bacb99bfe3ad777a7939fdcbef78f18acc3cea63bc22433b338ffa223aa8e71ac185f9a9b4ffd18f7ad53682f7daf1a7af918b4d27130c0108f2994cb329a949a0243409fdbf9d6a93135057e7a8eb3aee755c3b4082b43084c80b1d57bbabcf9d6b84c7696e842a9feb50405d9d2ea73e4ed7ed73edd8711281eba5fa1cafd21e88bfcb03e9477d3d79aaf9237c0fb8c37bf9d3efbcfce9e79f3e9ea1f9edd81fe1e39979fb3bbe07acec2e53c5d336e14caab5a56272ed0009d2c210224d0c49b9b26e7326edf078867666fef43b542bbcfc08bff3f223ecf87874b8fccb6fdf8efdd3c733a37d2bf8bbfc4e3feafb516f5d3c06636cbcccd7c8b97831aff26ca43c98c7e1dd7894778af9d3cb0b8a415406c2fc8d7fbd6cfc2be65ffe25f3af532ac92573c38b3e2f31369a6e780cbec0e8bc50f6b973bdbca7e3f2829c94ac1c2531d81e633e2f3d5ba7a254a0079c8f060e1fa71b7ddcf8de8dac1b3f46c08d5fd4ab2899939c642167790d8eb7b68fef12cafee6463e9a42310a798bc1f924358fa4a43aef35997f880b39de5d8b542fde3c8be6518c54728f1f304fe5cab257fdc0522ab44d38855a9c0529fe2a7e4debe397101fc7042437beab9da790b75c0537b677edaffadb4b7b4a0106e714f2bec6c371567cce7379919d3553ce8a4fa693a8f5c8ee9054a691edffc34c5e462898bd110b9e9e9f93f37b6fbd7e6ac0ee9ddb61c486aca49a9f7a06bb3ffd0ffba8ff71fa14ea2b32b4d37daaed553ba74f35eda79a9a0c3688102230b8663e2a79475f09862049503f402aa549424df6930709423c6a9e7410216fe1087118a44ce80374659589c1f156147a7284138485ec035245480b5a329c0c4a106759cd3ed176bcea3b0ff57c3aa1fef4aa13ea719ae6f0ece3f052d473cd3c773bbe578ec7f1aaed27c0a00af52fd5a3be470906e87cd8684858256df2e0b16d800807c4002388bcc1cae77b714cd0b971e5bba25cf94282b4402467e671bace78412af598d89f1e91cc7ed7fda95bb25f90204a9af628f09d2ea8b6cfa542b95eaf1b9fe35f361ec79d9eebe4811d144ed00f877bc84f4161e9afa0fa9907827a1d4fbdbeb173ed7894a7f28aa81ef594c70e2f888e198d074b428d92ca1bd21ee515f13f7d9d1cbda8cff13ba8cff1f10cd1a7df4eea717c3cfd2995777a94d77d0e0ff538bcd49f7ec7fff4dae7b7a23c06e7cbbbbcfd1bdecbdbf05c3ec6e35ec683f994877aee56407de6a95ca753f7a98cc1148336f8ca780c3eccbf5e2fffb27ffa97cbbf50df793d3ed213e22cf9280f86f813939cec0b17c4e0c963b0f318e482e898208bfc27d365009c00a687b68c17cc3da420b9ba834ca1324dc8cc02108951156a9e66f52b1e53051d1336bf36e517f3f8dad4fc5e7e1cfda8e4d3fe3d60f6fe30780bfb54f273bd8838cbbf07f4af7f33c433b4833d7d9ea19dedb3e719dad1be7e3c2b67f96ba98f67fb40ed439a3d835a6a5361207bed778666d60332483f95cb59f11b72c58b413e84295280844cd901d1ed6f81c88c3b41bc3555aa1ff3e9c7ce0af23fb00c9e9a70c1901a452cf1f10129cf906a85f9f477e65376d6fc76e6671f0ff595532506e787747e2ac9a04a7e374c4355dc2009201f1f4d06eb5ffc6a922adece050bfd9225343ae1b6b8a46d24db08b50d3b607be62823fd18638c31c6c8d8eb742b4e11e4e3e313c30e90202d74cb0e21f242d38d0f4386b12781202875fc45243149f4f16cca7e8c31b20ef52fa6d3288a1289184a8cd236164a2ff948d44b3a0605f1d66abe93bb4a5a25ad925649ab98a41feb25cdea0fe24e925649aba455fc4981b41bc84c9a5770ec1f22648635576a5c6d93fd0d093524f4aa378f05ea37e599dff71683d8b7023bb9e67292c949ad9c9113fe72466c38815aa585fd9929be572ce50c4dafb83dd91ecdcff48a312c62a9d076ec13ed15b353c1c44639b12ff54264c3989917246cfc1d221719f387cccccccccccccc93999927c7f8708c390d71d607cb1fd2fb27a83d58fe7e16bc4606a02e6731cb5a8acf0c90641da58c4f621173897ddc5cfff8847dc8d84454e24624ae7fc5b8f8445f8412774c61db5e7f8e6d30208c149fd9267bbe2173213eb6a9b6ef84be9bf6f149b762911251db44f18f45fef1c83f52e996f58f551c75fda3952a978be2526f5a963dfdc2140b415ae8955b1eecfc362d585166c3e9e4829aa7f959d4c905e6ab1635deea61c1500f6a082208295a8400ce1cee9ca73b312ce5534a170263ba1886310b415a681b7bb1d761ec530cb81867c1a4082d84388229cb1a20f635ded25952c119538696ec100688e170312c4d5c0c93282b2cd39694b2c7f031a5e41e184f6676f628bfb6c4157bc93df8993ffbb08f3ffa71517e1c726392e521313786f20a216e979f3b8d2a18b38c38b0e02039219a79e08374b907a53615a2503b6c606b60c2b0e19098eb9fcaffa657f13f957f5f8dd603eac60803a89143a12cb3e2a32e0494701284328640e28c1e70164c5039438d355aa06404c6222b807084085bcc70050dc0a88595293dd881164d820087e0b013b63df673c366bcdc179ee072dcad9cc7727bc7b6f70d7bc7380c7b8ea3dce374ec72388c3e9dd53ff7c7bc900beb0b1cb66dde10ee6a5f4c93f9740ac923474c20b524c8474c1c5de92383fae74a164e6a42fa237b83d48a488e711fa6acf5a366c9ff789a253f4c6da19979810da3932ad813284452da263ba2f2f26792d899211b6992e8e755e24fdb60f4312fde782402f18d9f53396a6a297dd4c4d769639eb5c21e0111028323b1d0362d0c21d23698ac9f5351d1a79f57f9dc8a16f6c1465f30dd055c36fa22899b44c94d28915c7eff78cbb7344b3e922ba53c7265ffc897a4368561277ce847497ee42d076a96f45633354b3e52cdd1aae85d285563579416d9a3b099e4e758c3ba94dff29b8a95b6a11f0adc5984da86a86de851aca265daf7044adb642fe9179744416a2589c50f258e0cb24d95460a1a7e1acf3e3a61e517502e77761b0ab34dce1747f0757f9c669a6903922f96b49217757ca9a152f96deb9f4d8bd63e5b7a155f4b1f0161c3ee2a61d452c54a5caab55a2b6c3f8e770af3fa17835e8ed8b09594986e5f59cad236de4dbda56dfc8b491a4954d2d14c6163f69bc7a3575a426c659bb0a18cdf7c2549da2626099291995737fe357c5d052fd2625023c364bbc76a6aa66d686d2e26354335ede96fdb566badf5e57ff2b3d7dcb5d973a7d38d9e328f0047d829a7f4aef3ce5affc2f93eb9cb72764f9fd991524a6f29a594f64af7397d4ecf30fe971cec0c8fcf65639e464aef30bc888272ef64c8b0ccffc5ebb291174ebca0722b7739b2a7bbfba594b25b625dced6af79f2a99783f18d41345358be1da5d4f94c2333468c9a102ad3841d74e757dbabc9513a3f95fa30c54cec57ebbd86baaf2ee4705f9f318c6bae29b536127de7f1856162bd1b6fac1d37bf9bdd447a75fbd5433a21bd9a5de7c5ee8e2f39d8ae7b759fab57d1a78f749f0ad568c95bb1a959f327b76127ac5bcd1fe1065d97ba5df75a97d335d5dae5f4771edf48d4fdec66f731b11fa6bcdb915f548a4abd9a567a357fa9697ed4b2d436fe2dcb3e3cdbacf99585a5b6c9b9e11ef359989f4307567e1891dcc9bd47244994042d8942f189176fe51ef3b8af2e9cf5629666cd7fe94e78ee2f2ee558c286b1e94e9f61fb709bb67cfd5abd785fba5a5fb8daf254bfbfebd8b3b7cacee3eb5d6b5bc671bf6d5bedb915d5dc449d3aee55f4eefb765f7882dbd5fad8737f531fe32ad675ddd63d4e775d4e47b9a73dda637ffa2c2a552f7ce1d63fc9efb955c29c40c897c4a751b21d24a383da3f7da48f5c7e172dfdd3af5ebd2efb949eed18cf5e615573febcdab7f6715fc85df955e7a2b7e644b6ec1be3944e837a25a5fc29a57b747e53251e0a7c651053281ed1e5b757d83042b9fc114aaf54fef3f6fb97c2bcfd6d11bba2320d0d1516f5f4dd562e35d3365b6d4eca8f49cd6cb507c3befbb0d7be8692dee9525dbeb971331b679f79976191baa326caebb9f4e5657e61f633cb64f6dd5729b3efb239e7ac53d37edbb6e9d59a657372cf5d9671da4fedabf638be693407cbcdf0689b2cfb5cbdb2c1b0294fbd555131a65242cfdc441bbe344a58ee5333ff341ca559966559f6fed9fb9fbebfe7c618e9474a6312a6c1fc08938c10ecf637a8af1bc7fde6f55c0c85e2be90fb8d63ecfb621fb66d5bf7dad671db771bb66d8f6118b6758d6118f6a94eef01eced3d616f2ff6a76d3b615bddb059466d29fdea237da485cabd727473d9a88ba323eec12a6cbf7d5cafbc5072d9a80ba21ba26e7c56e2b2d195a4cb5d8e8b7bf8174ecd49afa6f664fb4ddbb6a79e26140fe057a341eeef7c6170b265199daf79fed4cb947828c420a6b651a5f0c527441fb5442ddc833fce20fa9e78eca7682fa039cfdeda9c9d3146194f7f827be23ceed6934725f7d97773729fcdf4a9fb4fa7eef4383dbb9c537f29ccdb3d2a86d26d7bf9db7bd77d966d19d773390cf58dfa0ef5384ebd0df571afb6c8619a932eec41bd074e9ffde9b3473dfd9edb5ec885acc479e10b977bd4aaf3ea73c779cfe5bc21dcad9fd6241e802eb9d86f5ed813a4795189ec95c5d29019c3ca9f58cf9d5e949e49e766252ad3d05061e7f3571d94902e951e7c2e6fb94c4509110c7c6ee5b802cb547460ba9572d3635e512fdee9f5dc94b778ba4f751eed05605f88bdac1a67db862da5cee2d971afb8e7179ee0ce5ba74769aeb098fc13b0af7ce747776ea8923f7f088f9bf3a530aff6d5c9fdeca9a7cdaf737e2aee3d30df5eaee74a2fe4c2a874b51764f5b6e70eebd9b2975e08b3243c41d0cd1ec7b9eb24eec13c43aa7be32caee9154f0499f24b0e9647dde9157f6a4e4c856c88ca34d5da66f9db5ef9f7cabb9c19eee1afc3073bd31f93c586f6da54d4e142df706646870f36a43645fd77b081437b4e18b10f77dcb671fb74fb31669becfbe3426b001fc03ff31ec02bffffc254ec4ae01efe2e45b061090d70de41093367d890bbfe31336dc3d72a0d31abe8a2861bd6d028d1850c3f506316c1d2326230069686d5be4081c594a41296266144b014890bab3121afb09812492c96c42402ca5c62a913306c2604f6847d6101a603173560c1450cf3075b9cf1c31835d802cb164a5b24e980a2356ef45443c21631d06afba0d46de10298ad87246e8b9f932654b7703448680b0395f1a0a47151b11faa34e5c6848092a64c29079d84800f44d88b432088692a1101d34952c90532b1e606023f3c6916d3a8f473b210e801a8594c957c586b1833bcf841098d0baceab2d10f3160665817260c0d6b95260e16258513cb19cd1044832945d0122741404d6825d4dcc4b7728d80988244a56dc39c7af796896998177769dba8548ad22fec3e755ced4d6b9abdbb8eb318e96b6c4abff18edc9acf5f37d694a65229e76e1e5754667eb9f1c668bd5dca9eabe2efb9fc61d14619335793d0ab6ee95f02c97ce5c7772573e76d8fec9333daadf96c1a18733695ecfc1fe773278111ae07e6b3bf302fbf63317efe71e52c7e9162bec4a46b60d8c83518ab172934ab316d95a2dc7a748b8a57501675c5a7bb3b36b599a16c36b56a5157a65693b26c566c5c9994655b52dab27c5a9d4959b625a52dcb976d1385722fa36a946a75b3f37d6a752baa9249c9aacca22a19d2515665ceba69455532a4a3accaa4d99c5be59cab5be6f1713a6b21eac4868d2ea3146539baa150281cae43d939e589d68db32839b393455d9113fb0cc33aac73ed5dfb1ab5994926acc3b00ed3004afb6a4f1f77df56374e49cb151b72e3aa9216a6a559358ab254ab9b455d99d86f18966119f62edf67dd34262cc3b04cbe4ffac589c5ae07805499c67d6294eb4e5a0cd1a7864dfbe9e2e784d2b64d3b4176f351289b6d9a8b9182ac4b1756b460ed8b4158828a82bcb02a04e9b8e4f8bbbc4cc7b4e7b026c2a41082d05e7b3d50f4d2e5f8bffc510a88db592681ece9673fea0c030313e345413735354112a6cbc11e86cab48b9362510945526294a00f4a1161598c423f88e853195614a5a8a89fa3414441912988c8cacb6caf51e62c1605c50350ad0b1be3955e29f52a2e756440fc011fa0bfc9669f16dca3e7955fb4c23dfa5f906cb5e173e389fead3da744a8144c984a9b1432543312000000d315003020100c0684429158124792acfa14000f7eaa466a489bcad3280862944206196308210000004404666668120426d09f43f86782d7b44d60daeab13b80ca18915127540afcf56e82482041c68aa3e6e6872efb60d2b61739d90212cef6adc711817a6424644c0cebec134e90919980713adb40e1d19fb2545a973211625c1f9dd9d0bb7ca6e0b828a93320068884af376125d667a293eb751fefe71ce6d85f2036107d4689cafebe6a5c935361f898425b6c39b7228499220f26c9fa35416af3297252fb31707af324dd9b8c8c4361de54b89f9035033b0416b1a47a795a29bd6c3ae8d68274c4582c03de848845cfc982326109ef2e3fa85bcc2fbc67684d0dea272de20817f8286a409ab3d0557b93578c7839360086facc4c419eb0c870bddbd7e238600e1c5320797babb86a7ee763882b7335e545d4e2325c2278379d6cd769832010ce78dddbc6894c2a4aba1cef960f5a84bfb5e6089e1d4025aa32cc8728584374f33c7cb865f12a2f019df502f1d7c1aed3fe5a4aa12fc74b8c2264ce37d3393f5951317ee79376c8ec7c2d38e77aaf1149a7df8070ac3494e7b5d20615ec2d6025af92944102f2457751efcc91a3bbc902d8a813037143d7cb311a4b74901b58bf59c1712901ae4b90a0c1c13fbfe7cffb687ef1d22473b845963dfddc968676f075381174d0f79ef5baca08fec90870c4ce43424642b3f53a8fb5ea67740afc19af7a8a999eb14afff52b546fe1c59137b2350ba3aeb304d6c0b1bc38b026a5317d6b36bed6ee033edf6a6233e0460fa35bbebeaf31efeb5b08e774f3146ad9a5038a6227b209c02de10208f6e411501371adcfa8f1ae3955330aa5e4cd4b9b0dc4c64b3b779ac7070a1f19d202b73234d39b41521fe43e0d5b91ac19eefd6d69515aac747aec7e948bc1e0eeab72ad799aa70a31613cad3cb645419707c3e3cfbe7293a45bcfa47bcfbd621eabc9fa672cffbbf0a25f98f6953362cbc533db3309a5d5072a0afb8dd79164eae1fa5b415f3f86111cf79d5fd692c463bab6eb79e2b6705a974b3d65d77a373548bd75b316bd838cbd214679e58030b826b7c5bb75aace08b95a9c4d151bf35bd066717efb6f21e8177193549fe5f5f25ed13632a5585ba4dc64e12e0a3156cdb2032f2feb0e4867abfc817b1737d60db3f1c4d7390658a472dddd02cd5f18e66e36819aef8f510a5f8a15c335cf7b9ec0ad8ed043c2ae3657308d2133f9f44c74fe5db51c0758feeb04ee4d81d38656a24affd6343d191b4c3178d891e2b6b2eb246176a65cdf86b3c4954ac9de1d703f329ca2e6abf862edcda92aa478728d5f459bacd363dc1fe0042a9080b34ca62c5aa1b1a184341b2dcb35718e1a1b36b614a7c51e9b5aa82b876a0fd451e6b57525c61e05d71e719d6d766b0d0c72536ab60fb1cc65f5eb781658a2614d559789ebefc401198c0dadfba038ad1e397979f2c32fdae7a076f85bcc4fbb61a76f1a4e819576360b50780408daa4d49465b91aa0bd8a9e1b17353e4eb5d607c4c1c0bd3554cb21760d6f481affd752872e0250232b3e5960ada23534ea91944090412b07b448c9282606d24c9e768f5a5d9f712b038ffa911eea29c965fba8f59797e20380eae01c96bb9ac2fd720d287e888780007ffb8e0f9394587a2f530685bd3cabe8b2012f04872ecc79c1cf088fd1448bcefdb8ac8571f3c9eeb57971982e1ea7e0401e160819408f649d5caed43e1510527f11ea31978c966893b3c0ef1879636a9c87360879f31d997cc6583b011d20547cce6448ec85d0be6a0778090241cf74813f912a6d1b69d5d664328edf16b87c8d9db8e67942585647dfe29f80212515c54c7e0e0b747a4fc968f95e49f40729f79adbf1b5ecdbe6577e7fc6c5550ba33cdec37bfb66ab50bb7ea174e96f2ad0e1bb575b2ce44e0a6393b975ff2465d34729bf1a1f77cf71461feb3c328a0a12d567d65b842fc467efa607ae2d67ec219860d4829c0019da39a78bf594d9d087451bc4949df513e1bfd9594ffaa8cffc07b2f7ad0e8155cc46fca6d23897b29e6f6a6731a24f400b7f2dd73860be2cad675e56eaa5a68e8151b9a6b15c2c5bf6f4ab6dcb7408d1c337934ff25ba26c51132cb109a9b43027e262110cdad3ac37dce988bce3b3841a8b8b51186dbc7f57bbaecd333a556fe6a5ca3e88e6f228b44333d0104124fbf1b7c36422fb569968bec3266c43ddf9cc7557ac385af3f852e05e122740d7742227930c0cfad505d3290f9f6180a58aedd320aebe6e944197c5550d289920699dc4c3d1328400c616ba831febb66effa8d153b49589948e93e48aa9c46a5a4c57bf8ba63387288801cd806f17363aaf0f640760bd4e7ae89c43120194779249d38c437a44c22deb658667f91d85458c44062df9bff34444f53a9c8372db7e48ea869c4e087068e369f61572f6375957ca46f71ebc6b271e012891b49114f924c2f8a735621c8c99627931d274878a234047e33d6f9018983d7585cf8b2e68fdcab3d741a42f8ec7cb9ae792849899c85078b38e4eb8041ae6db61334c97a19d94fb6f63799c6c2bf0876d368c823cfb8bac652495d1ddc7397ed7f1ad90a9d897cc5e24cfd0139c59f12b8b8ce4178ae88cfd22a705f8342a47c5ae2167e577f2c5ad8b7663f968d9c66f9072fb7272247c4a8c9c5ff32c4fc2f730a65649705d19fc0e2bf697037d819ee8a8b7723e9c1cb8df22a7e0e20f14fb182746adf1b5feea1fabc845adf0be6a847a44b412b4c18c0d7c528e0225398b5580f1d858c735072193b790e87906f27763547a40a18dfc813c0b9c50a444ef7376f78587adf0d63fae78dc3466bee137b7c1b75fd244c51ede5cc558ef1f810a74ccc788521dc1257a0f628ea88695f57ad353d8a2f7a26656e1fb52a2f6dbc1006d09f65212a473cfc26a3d92d0951c2acf820597a611cb53f3f3f99e1b3a0419123012de1e463c45108d91a920647eb71921f146b85a7190eb00184a48c64d5327b6b179cbc0817a954bcb12e5a577a3ca0981dc42ec336851e0825c084b7b9862456f51f2c79bf27395c83f2c99314548bc261c16502ba73296327db34b0a58deb6d14ea37479d62c62c9c0b2b5c5e938b065bca5215044e4a24cdcad80af1364bad59497a928d7328f7a05d3b80784d57d9026f98e7282b461a464501dac660977f4fa970544d064f3409af7bb1cbba5c7d96028b0ba2082507f4c8fd19641b96e1a6e71681f80374a6144b1e38ef64024f96084f9a880e3bc7607568307ce6501a9db265768e49f9e3a62ace54659dcef894807fd19abab61f3e2a5db606af1c8e990d342eb0c3ffd66569720e2cb233a95ccf75332084d5e474669bd5182a56419def1a5fec1cfcdd2a79c5ec7d98ea8175430a8eb762f0e46f4b40aa07f3fc092fa11fd970395d0dea7dca0024ae6941d916383a60f76707ac373e1287b70649c1103e369814844075f15023322b01b292463edf040c33dcf38faa824c4536e75600a212b70d26e35330d97ff1db29694d635b60c706cc9ebc0f2586704f245de667d923b80c19fe4fb8a55c1505668c5d388d29a01653aaf876b9398fd94461cbaaf4b0b050c6c764c6ada7ae4ce524df38dc818e4e9582ff70e3d3d86a067c4586f61a9de5247b6cc55f6b18c9f0b3a5e540aafe83ab9b778462387a7702e201b8c6277a8f31f0814757420dc40ba39a8f78a250e6464e4d69ca70911f61cec9723214e2b49a4116269acf3bc9c94053874305907500566cb3901a24185244b65778ba1da7e7e4dc3ee0e2076c2b081285e37bbe4b1b6d87762bd7318ed05b63244e1b3003ef615797a6651b74772f3b43986683fc36ea16900e62b0247d0afbebc75525ed3314b2db1bec72618bb6e393b7481d0f973d13f55eacc7e283b4a5b863156117db4615fccaca67d09ffddc227acfb6e6cac9712593a05e7611aa209cb7bbb2685703ae7ca6311f4ef8c674db8c33c892fdb2200024d9004dad39d355e91411adb240678322ba9867490ea3525d1a7543e385bbcbe7cc95f69852b09166d2c8987b0e722764ad4296f6e0cb548c5e246def87d66dde3b5b5088f9c5c1d96d561f69e82a1693ec75398bf7275d8ac51b11944f9c8fa33bd1b250b8f8225f19a9cbbe4fb45f879a05edd75481fcd502b3e3b7df7e5eee560340fe0f88c869a04366f1fad7cf486d64ed90053f1013a9a3d40ceab94bc0115c61db8a6418ca09be6f177cde8e63dc946393433b4cc7fe997008a42bd29bcd4ad01f0c14416b6289a9c8b951b9b8545c002b55ab379843b296a799545d87fea458c8cc2b02cf6514b76820528a0765dd888d840799ed79dce443afb7ace6ba4df0eb902ec8cdce3775b6da4ab1466cb30d46b7f2a213ed6a9b76da50b7fd87a6e92e170328dab951da89e99ae51d961161082ff4aa87431fea98baa6b7c922d553141838125e1f069bacb71aebbcd684ba569c67690ea9bc3c719b32c8be5f2b313cfdd7a4be3ad3d843669e875b4a0135585209844fa0a645d62e0df4bc79e3c0e64a88c7ab1003b164b9138c67a4ff7f1a79d99745d3234ff3c352ee60b6f45f7f7932a6716343a0e4f25fd091eca0dee5058cdc10d464cb369add21318e441029baae99e76c353855fc44b70c9521a43622a25999a28593be69eeafae642a70700f085d04aa8f454a48ca17286ac16a131dd05dd265697b929a3ac65cf3755926596eb563e64e387fc0990f31258f601d1d049ae83325057e578a98713111d1561636ce87d261a2b09678c845e42bdb201da9cc122526eefe854a931005a1a052ae7b1e1527dbe67283b7f8b1c0ecfeea15633aca7b4cfd60f8bc65946e65c118a933da21f845aba8dbca7d850a13cfefac1a100ad3b150f3e29e3e664a64bdd53712fa02b7d7a0d191671dbb4a590ed6960b6933fc3308fbb772e8faaac3b28099a9910a2570cf3084b3a8deac8afbd0df6b9590d4f275f346be263540902a9b44b5c8c4a63ed0195ab72177c7ae261c081a77721185d69247d0417761e4326f262788b442071169c0d481619b176f02558792f7fa85c3f287d8826c1d226b6fef98e2e4cb08cfbd752b9b7269d40ab73f4ade0f0cb2a76509d6074437140fbcdf0c5e3da1ec231f67072cd2f22aa44e16e1f182b02a404e4d0f2216a7b08ad81bd225ec62e866d3d7736250c49815459d2878ae717a1c8b3686a3deffb646a5c31ce1de3aeae9affe781226d89d1cc042583ece3f4116d171907dc75b3686726bcaec53bb08d099b58708b80531a46322453bcf92051d22d25194bd34712de339151e72a551b23859ff0e2c698120e66ef13c1f3b5f90ff7e96e5fc1c2145722304b6479602ff7d6646e2991311193f0009e1b91fb0e802d2de0115bd7e06a2436d13d872977f61023dc900a559b0c5751bf6ba0959af06637e3ecad55f184e85cc7b3d66c5cf7b780204d3450e490017a11a721345a2e34d8df4967df7262d008f183c75aa6f58c633966f889bd1cc721d556dd0c9d3119e5f0f17013ae1bc87a4b46db7188f1adfbf7151033a3599925409bec8731ab181ddccb459800f3fa96c4fffa56a52b8efb548e0f8d4ed0b6ca6d72e09d4cb0cf04fdfa25294ecadf5e07e728973808b6692c843827beae78c842879741ce6e4a7df8a74c9c6f6787b18693305fa873b25edb1830b2c7bd0dba8cc064ae97d5222541fc0e3c89ce3446e40bfe7ec3914669caa9d5de53d8a215d06bec783dfe8341c7a77045ffd11cd729956ab4fa7c63ef2045abc11543bbc3504dfc220a440ae09a5adbb3f30b9afb0518bed63a95a2588893b2eca8c1e93d24d08caec2218d8a1cf9e784d2952c1fbfb72198c2487452ef396d13545469eed180854be85f29ca9b61b85da16be815b2ab378b336e17f06d45ccc29803409434c4caf13c2408407b5166e25bf7661758bd846512f57c66206d9a8e75865d5f743dc6f2c94059b7445f1086bbcf7949d3b6ef5d03b979435d753c87edaf03b5d56d0b22129115cc2050e95af93e6f9029a7681ae4fb313bf28fda3d5ad1e03f44927746722b575c06c05a67696abf14df0980167fe2c0054731e1ad95eeae75cf2dd179472243faf9022903f4f26e2075d3c0d2beac132d05999b02fbec1971ceb95018309cd467c0d28c54e372033b6fa0243107281d6e34a831e18306e302525a18fb71340cef46c2675e842b91012ea18824933a0ce14c796afe1fbcceab4896e94ee75e27ff5fc92c7fd2550d746356ed42e46d81f955f545998a0b786535053b35da52747e4006775b1b659581f540fcc72a68049381de319a35055e155225c72912fbbe39b76d7d20236a6612f73ec2d07714714f14a19589498ff2199402eb5d24c31050cec5b4399cae1a52cc497cb98b56620f2014578cae3e9749ae3e3e2099ba8067eaabb1d63b8fb580c0ad9e5e000c6886468e159e7f9ba16355a23bff86589a92214f182b78952369b3fac07812af6084c362c9f9bf4bf33fb916c99986fb6f5992c6df41d69d144c832897ac553763cc06f3cbbf60080221f468168fcd253b64883062c6b20c6c4bef616fa4872ae02447b5927e397ad8a5c0acaea2e4af4a5bdaede76ce9dd38e3512ff4209258a8e27e0e2313d38b0384a0badaddeb81a2df4c69ef9db1743d705222fa2b51e3d147f92bb172e2b657a8e7d7382d4f68d47aef51ca9e1b48c260cd762ded16558d2ed0c207d37c961616b9a22c3f3730d1073b63a9166110b56e812a7b11171a5199ac0dc4aa9134d39c57dfc0d1079e89046f0304b888d291d478ff044a2003e20afefa94a0dc4c6b4fd14c4e23f31fbec8494918a0115047293df8a1ab0aea798651122a07cf36ce6f93af22a215912757d7aae4ddd8b64b1fa504f106098a29d820ff7e8c5b7dfe8ab7a885cc3b574c3509ad098faa97807dbaa33e1746171541abe23bd961a924f4eb68e5100e0bade80949f9a347f2cf7ac50ca9a0ecf4fa8b5eb743c232c4b6e24811742a6025d440a372e453544b8c77113a44e2412cb6fdccf907a61c8ffcf7f2dfeb5ff50a275081aaa7f7fff4828742026aa7973fe955cf3825e4029c3f48bd266d01ec1702ac9b32eb2a0ee7e48de86f52ea81d8df5e3ebd75f4fe5fafd97eaa1374a317b8c1fd96c666fbf71bf65cee531a9a420acda3cd02022297d618cbd1812ffd358e90c0c97a74a77a7a3bc57707084525999c2bbda4e21c7522c5e27e554f295a42a76c26ee419169c1c4fd44c56578864cea29b69c30a55b155c91001dff21915abd78762de1e0bc0e3ee10ef7fe36f8602602e60d7222799d6ac9064394b308ac50cefdc39dc24ebef9a96b727c49fcb7290fe0b964fb9afeb3850970b888d84a4fcee0d2e1ed3a11aec59a3f30b090e3ca681674b03af94c5b14433d22194f2046dfaf6f661d4aac42179f76d9f42b593b34491a3c4d889d91597fd9b101f73a315cba5e8cc86d318d5e7f8988ba18fb78dc84a7fc225debd1b2d9869ff72a67ee70547c1608da53463c78345bd3597cacb9a1d636c23563468948c6396590ad2b69de7959e8045b3f9a3a773c0eb0ca825ff6e8b3a6994cc5f23cc4caa4fa01273897d48b284e32bc74589e19b9756c82f33f39449271a885f37a8cbd7c2195a94c0e55329bc9a0e9844c0ff82854b8d1f9ea4dd9920637ad3931aee40aef74a3372fe5fe87cfc6a0d36ab12e08d0b35ca14f786f04f4c0547cc907a1025f127ab7f5638d1dd8df348a38429d1cc59ac2883193267d7a8555690332bc8310668f14028bcd6594e0d26cae0fdfc88a177ce89dc96ffe52ea2af8e71bc462f3639ca641175adcc56d3e5686e0aaf8655339d50a446cdbffa590736727c6f9b0c3ca338c5d40cb5339686cb72a34865307d5b170e5f3f3c13e4743f9b695498706e0bac67f701174cb16ad5d6d10f3a73ebf1e3c4799bdf038b4788fd422b7858a382140df318768746f439c98fb77bd737e8919587464fe9ffc37572f79cf26351f8bdeefd08a5164b8cd32a70ac98fcba38093bac65f709325f2980886be98258a8b36c47ae45463fdaf56cd6118cba222026b4d3817a75763c92332649c7daaf6c7dc675c3d1dc2d2c959ea06230d96f7e279b59df98c2a7de87031a69a6f5e9a16e78eaead4802229cb9b40022f24958a77a7dc7965565d3c1b7f6f9545dc8ab7cab3f6afbb992dfad5783345c41181754f919d0e69c0cd913af771e707cecf252f9913c01d2ec37f182d9b3d78c87e5c978723739a39b73d96a11d52389be953b11ea2d55817e68e6d9d16adc7c10ab26416633f720a7e3d9aac17c4b624ffa8c499e62ced8627338b9ef38f65ce7f1d1ef942ee78bde651dc8cf49639c93876d53692982046d55a35d83fc74e097e3477372a9e310315114b956f578ff38c8b97c2905f2d3fe9b2e493f184fcd5b2b9264c8f7abac7dd54c4549a788e1c5e7c7a9a58d9ddee74137757afbb3b1b258eec320d074a2e27a27d125a4935a186fd975d7e8b248ee642700d70f33a0e862c63275ea0182a8e9469edd10c66326d44d5b81cff60c05ff121e1f5332774efda26dc51d50a959bcca0562657019b830102a7e04b967865993d7e63652c26196651e224fbd0c2a83563a862394919d3d99f410f2caf311f6f48401dccc1064f2b676a18f6234f3548ba9b31be4f31cdb2e8a2b3449ac8f3eea00f4618c2195cc725dac0096c8b7869a4e13bae8f28aaba90d222a15a448cd6fbfe11fa0598b8baf9b48d2b66f0a2f7ab9cea008da483b2e4fefa08ecf3599ead55ed7066fa4402d4de3de3aa5c4d734be1c2d6378eca35405e91da7a37e730c5ecf71277170439d8bcdd7b49e323c2d7555d17bd3b1e00986774b5bd848bf045fde6bba52ca1894830982eca663d07e8e8f0c1cbfab952961c93d5f1a4cf2fb9f003ee52f0996c52b769d0e993d286fe5ff832ed45c5dd404583902658072689fb26acf7656c902c16745ea9ba7679ed8486563260de40f1992da4bdcecf27e1d7606c896df40d6e440803cdb47aeb999778b98563303d894a0aeb977807b4ff5df0161f75648facfaa0ebd374d23aeeb719f7ef53299449a0125b17b3be763635efb356319464cdc6e88af266c6b9acf7fc6f03aac2c789141ad968e0567138ce38acec84ad50d95908fcbb474d7f839817edba83e26a910b2ee282a20a34a43f9964af805cdbb6fed08e6733ddb0593e8ab986afe728d257ab5f56fefa1abc2f48d3ad8c783307768235f89b924d9747d5775cba567166c1633bbe5ffd8acc2be8b7927dd887ec56cf6b757955e3bf0cabea940c955dcb24787dd702fbf45828f08d1b8dafd27cb89266b983333a3dcdebced61e217e06d3e1478efa17901884d96c9c235a73cb49cdfa9e9004d97c2548b59269914d98e748835900c7964f90f135c677dacf4eced24bd9f226f7fafc0226d1b60a4255ad39064dc67bacc5951814826dbe34ee3e86a6c6535e56a9d73fef30ea0ac2c3e8d0b15168a553003b82dc7cf5843044c5d5c678d1a85d5c67584443ff478d6f726fc64ac323be84fb7dcb21bc6d76dc28bded6446696c978e38d626ba8ca241920818c2d5d59c8ab4b56411c8575a7935161651eb5525bba0d59826c79cef1dd1985afa944abb212dfaaf4788e3de077c4e132e062dd2025dedf9435ecbfdc0725bca12f8daf340cfc9fe30d9d81fd5b52b3f33e8ca2dc2222dc9e0e1232d1bb5371adc8a896e4f0bb0d9455cb4922febdb61389cfcfbc52b4472e2616216c0734025e6e7baea8fec75a516cddf49328c506ba788e4c44abc2301865c78c281dd89bd813847d23fc70a094f0c9c579b6cb122b6fea1e89723ed60e8cebc339b90ca53f63eebdf87ac7ac228862a27aa75d69ac3ee53e7218431fa743d6122b6110f2de4f67d8b8f54c61deb1007f94e1d28e5e0e3306fd00d5afaa4a6f5a9e9c52a77698bd6d0d7eb6fc38af789e8ea38773949f68ca469788414a499d3fd2668b53d2261bf73195964a04d8442b3415e628e8a441d34e2441d03d9a84e816d9856a4c4600f3830e891d06649c7e3386f7d2ac6c9469c7cd56492d77180a9d310e856609ace6c2e600463fd11b60ba843919521639b0930815a6d9748f1165005af5c32e0af4800ceaf0b571d2a664016ac839def7eb3c82960c10afa0b9e2a2ed8379fec9a026e01bc64336c7b8128aaf334a8931c8c4d93d2c8356a8520f4f201659c369ff141333641c86215ecc21b4bc678950055d7b025833f050422c583e4f33ef3d1872cda5be3780204fdc6e74a8efc801e761a8c98645696b60e5da8c79d524bb71ecc6476c360b37986b6b8fdc450d3c9b685e961101d68f40654940e5391a43209a4d8ecfebc0e25d96fcfa611e8543da7bb2d63b1346b2a577ffd6d5b1696380069adc87d7740f7d098a4939e7962a0a71e31e60ed4ad4a9391e8218725938e3cda47a81d1568c8d01dfc37cd368a7a0665c6fdbab08bb979d8eab678a0f475910b5e6fd69ed5b31320d1ad66cd9feb444147cafcb9f64ce5d806fca3891b1246a32c80a7d26e690bf9a4cc9be36ba81059092b3b0350c90e739c0f96d43f22f68994d30500052d678ffc9f6055d1ffaeefd4d9f4001e376e111570c65357ff75ef2d4a987a2875ce1ac5e7e00966b7f18ea7e74716e9da94e268ead611e948280b1ca6197c062c76e23d03ee6cd9072e49bde6b1e284007590724bb2084639da2b5032fadf1ad0064a268c41ea3f6ad38a35961bf4af86fae7e08eae4775f2fb9a6048ff658a7dfb65a8498b799ecebdb6686682feca15ae1ab00844f4ff1dc71d06952ac15a8918265aa8f2b561d3d1464aa5cd45d331ad048acdd539273729bb8b435d6b52da02407c248fb5e8da428cb22c3b5bff66e4708a8557318760fdb93a8db9954209a3ec483bb744df35cba9bd7956e946e8725eedc2e06e9e45870f7ba7b7535801fa18441b9e071bbeb1e1c1a29a9a12030c4d87e026e07658a433c448da037db6e497d3fa1acf3ef42418859e40219f7f0d6987a540784c22d77e0d53c1b9a7a99c7a3f9cc259d86a38d14bf6bd0296bce1f10a12d048013ec36b5e3b97ceff34776335fc36bbd9e4cdcebde7a1cc131eed15d74b2d1a3ecf972062f46e6d1723b24c73d6a812e7db7b50133e2d1a8839e853148da5e993c166056fa46328673c39c760859dc86ed0661387cf506108ca71c3a84e95bf8642230ffd790ecaa580e18ec1577d7c91ad800467e91a16cd24e3120237e5141bd02c2e96c037a166d0144587fbb897ac16af733528df397976004e65c32237dbb4f615a75e608beae68e91ad0bfdd52f3cda3456f9bef0f11765b9e7d995504b7b0436907ccf69973f6d621e200e2620236d36f376924a51b8d7c69d2429cdfcddaf55590de6e83b42493e35a97748ba623ad3d83e56084188d4ee71d70953d49d313abadc40474d917d7e29c9cf7e3b3ff97e0e218e3f2883dbb22d359caf25bb17ca4df03b2c33aac2ba40a53e40c92710daed948fd32f9c0cb49ef467068740688b78fdf54637b5af5f6354809420204a102af1994f3167cdb94729a2593d3bacfc31f1b5f259aae5ebd30bf32e0bccf2de57afd0cffd388dd91495f74d514800bfd4cce79f7a72b03e0ce2805c1b0920d5c97ef93377b9576c1793fd09218e56dc05124041058eb82c33d24cedb541c5b90f61f7034f78dc0819df7066f31cdc2bd24047a5ea0298161c072b5057747388b838679da9794279e866c08f33a0730377a4950d8e03afd5c6afb69de31c191789552efabf64cb579deff8496800e58c73bfc590bd396c3ff59c91046f064a72a942d0998e89e298cbf3d0d6d525b5e2919408c59b6052292c7c491c55e5e4c3d94d971d3d1547f23003cfd9847920ad0a4f50ef877bb3df45b0ddca749078175139616702b5bbfc5dd031c7e66680e460cdb3a221aaad78e2438885540b39f8605dfae681bd18ead441b2ba6fa730b8b7f664fa5229a30579c6c327525a911cba0cba5bdb253085dd6852e22784fb39a9db3f38d19372af61deec61e53df6c0e5beb1208430fb65b949e96726ca38cdfadd5730e63597c02d1239f81b1ba5b010cc827382972a4e6411c1cb6244bfd89e53a6a608b9f38fb131eaba8f7c0a909610f952275e7f2c638e1c9bfbfe495030f76b7f1e49889b4fe9d8233efccc165d5b82187c644794650777e0e7025724e3cc51cfceb98826be03f10c8d886553babe3eea7b1168a529a609eceb94e7bca62adea5737276b1eb67e25b5df0b880b39597614a7881e4ee8776393ac388e56197ad10dfa819de51b06057f3386b3049db4242adf23a2b82b1dcd55ee96c5ab48c8c2d2094251307516c74092cf90d711813627b88043ddbb4a97d1566442556b15ccb46afee1550fcd0f2c470a38779b0dcf10d06f6ba76aa8c887376d8b4e17a2e662c1ef8be2014e1840e79badec2b453850c2c428c740dd4bc79ebab78f17b78e1e61c7b90e7a7dd4ff0b3978f65bad314cb35ba01bd30c87f4b35e9e350963d691ea208cde3a0851b7356b0b1e4ec3d1a6d5ca56e1cb53e7fccafdd4c118a761b4af53a388095f95b4994121b97ad38b0db7a7996f67e47c5e423bc6d78ca57b2d48b16a0c10d9c7e9346a2b2a77622dc7900ba199d67c7707002fd4339a3748cc1c8cb1d9911f1c33f8c66804e62a8d80e0340256f323087a3f82df1f7947e301ec43d321a68198d1fd089cbd782b06f93e4d34fc80f2455f984c17a507ff45def46520608c93f287b44947eb1193f088e60459c76ff76c63e95b97e559f07e28876cd13d4a709905151aaa6bddcfe088b8690a8ad2969899a8039a0b2aa93215c8a499d22c123d06ec81b4cee0d04bc2bf366ad81e6c8373085c4b427e064a2abce759c12ccf31921f4127b975382ff1b39dae2c046c0232c5728945d2b3aa2208f593b6fcbad56ed84cc37c3c25b7c8f7f377826067cff80b59e52556a995fa68ddd43b2cdfd94f978cfe8e02e4c58596328761ec5234cdc351b93b1f1b5417928b3220121e24ad6b9d3a6bbae56e42702027e9e3430401bdfe5c7a2e34c0c23aa6b093eb39b40a84b8d243ef85220e7ad6270241a57edad1f7906e900a9906515cfcd45e0bf0436c3125b9ab8f30948c3c61ac42b5f2990f270682c79f08ca1ccc6982096204ed641515ec75340e17644617d42fcf37538cbb9208369b6373ed1b4c42b7ca915f190ada5fb120c91c46189c8a89de414294833724ff6ae4bdc8e7d8fcdaafb4f8ffd7411b51b4308711839f68a561e8d381c516b5aeb081de61c4dd6b6bf9b3024d90ccabe1892d45b00322bc2cf4eeea6d3db67028119cae463c99fd1414c230342ea217318b828e20fca609c87054b0370c5f726b96026fff6bc17c3b68fab2a244f10a0c36a1e7ca2f14f29aa29d0f0c68db18219a2cf630ed36d64fa75d83caad296418837cd8a340abbe5d5c887ed50eccdb58db2f18fe14e9b336bed6e6f60f199c78eec83d7aa0cf8e212ad766ad364ec1039aa31aefb88dd7461350b9ffb54902e6a7e3e6ead8564184c8476436944d9ff316ce1f8c4ba08c1b1e077e70688895e577ce2f8fca95128091f168c9b4bcc8626def635e3060b4a9e3f51dfac990e0092d8cfe077a9cb77c02f3364743d2458740bd9df749bfb13d0d8a04a7f2e8e8cb834f01a27ad559b530842eee800f29c13c228dc61187931e2a846ec0feae68ac6b8c4002c184f72f339b72530a3dba02a7d602627947131db2bba0feef4b2e68c41568cdfa67d600912cfcc5406b530d97ad53662d8a6fc5943369e2411e686d4843946549d6dee2f040a1e1ae360ca3b8daebd6366ba32108abf861b704923aa0c0130ff8be04a4143ee24bd1cf6cbee9a2414d7c510b1e2021a125096d7e560c34a541de1da81dc10fc3657272d780f11f48e272d78b48f8e0318c68d9ac3034a0768542de4f6ed6970185c348ad238592f68dbb56557020417646d0a9b3a9611c838cb3a0d4fd91c28c65b8f874505f860bb351a37c6f53f7c20c5b263af5e4ea529d41cfb4f21c1afb98a2e3d0635fdf762b8c4e97cb2079c7938e37c664970da12a29c822c08fdc93781c0123e63de0f20bd78e65eaf1939700cf23333d9a0ede4114fa9965d78b2b86fb5dc96cc53a70aad28950146e43498ce13e38e1ae9bb215be2ce393e2d2122a5e7cf480f735770f13e94d5bc5dbb2bcd1ad8bf946727483e22292b6dcb7e2f4f1c34e38f76fe4bbd9ec1504e0b069f86fb4545ba12e3278dfbca6a9085ac8e5a719f175572e1b26374a536f6c175bb44702c624c16cc3b585bc653e5270cc47dcda5278b836f243d34cd63a70b373ca0384f11199d7051ce623e1008f88b58ce7b5a61422cec0b276aa0b8fd33d2af72d47664a19a8b7eb35e2ee5b7f0da07a837701c3b3db93a860711abae50cd0e6a2688517ec48f6381e0ac4afd8bbaee072e04f62b22b9685c07423ff2a2e8c3c9dd20c46e23be83a389410db52e0369cd6420ac8778379d185eab6c0046c423ef7306b7081d85008bf32438c5aae1ed40a61c2fcc6605cc7843a395bed6826b9a158097cd421fbbb682158ee5a721005cd9855f55e3622c3246ed4c50ea9527369960b5d2a835045f7eea16b3602606f5c6c0fe7383eca7b8e7a791e75997cb5594a817f2c839194d768184720a17a5038fb8cf8dafc69890553001bc21c816c6f750a701c436989637bbbbef8659f0432a4a553b53f246f208983209f31d9e38387c56fe600e9faf5370770411b4f12227820e4fe13528d579e8db8f2e73a9e288f17d704e21a770ca3131e044bb0f4e3e48aedc10a049e05d925705120c6118451e9ea7691056778c2929b874d3c603fce0ae4fd19b3069080864331d23625662192bcab00b016be0db9a341d8cbf72f475c14005330a356c8c94cd919aa541f6914836c3cf6d673f1fcb938e574d844ec0382e382989069989ca207a5bd65a693909d675352c3b0163f5b2f2a62ecf9a5e7851fab6c9cade703a11e31babfbc97090b339103967c2721c24d11934960ff5cef12ee41d00507f3b3d973e00e5e1c6b32757766ab76c302f0425f5d87dff2f94e6f018821ac9c49b8a7ad99093ee7915d39b25c9904d5ad9dad79ae44996e1d4458196ce8e23ec6588cf2084d111113f6d72b846b1fed2cb6040dd8ec25623ece54ee662687f13d9e6c161d2f217c71a77a3db8eb864bc3a34f96bb2838b34b167ab687cc4194d8f96784713ac9d179b997b1a80b82a243017e464c66ca74c73902ab07d96f3d23cb5367202371e4d59792dc4ea16550c89732f023b1041c094994a1ade25c4c9c2d45c03029f13933f89e7023b2008dbc5235f025be4de8c1425ac11349231b5080da81637e0548b8cca31e33ffdcf73ba3899919880427407ddb3842dcb335364af5d55031ea4d35983298c5c367af4014343b3fb88f10880942b4a9f2b5e5f45637d206c44f4eb1bec3ef0d6cc6a79cd9f46d8089705b2d7ebf83b3290219105df4f0c00beb217aeac5bf987ad8f43401070e5e701b0c03b20f7cc5ff35815c71f8860f175d7b23d3ce8cd1a49bdb5ff35787471e65e2a257fa8233052f256eb16d3f4958529f73bfa2ec0ec1678d4e0a160c95cf66663e34dd87a351ef8dbda3230f7b40791368916fe7737656d2dfec7ee9b0a108f5c06baf73b5d7061e9eaa556ab94fce0b2319c28d8bcfc3822ab024daf7377ef3aef9208ca988b54022edc8b609cc6bb69429296f6f2aaf88ec8bef7aad64a94b597b562b7473caec240d6de09b78e6ca02a86454094a14f24c6e00a655478cf83e86016579dd6050f8efaca2f43f0b8cf664a2725e831a26333a9ef47a4efe49a89a31a692a375157642cb3ab5feba23e875cfeebab38cc927a841b6e6151946912738619b83453aa93c1843b5fb8d337435a3acb2a3bc2f02b75258fd07fdfabba732e02aec3e85d7b8ba15b878429460d8ee5ac0ae66ae239e247e8d27afe377f143bb6558701800ee23178708c2adaef8afbb24fdf9aeb1503403b2c19232a8c65dee463561165fc810844affbef8c4fa94c1d046c8785842fb6e57791c71e44c29be29b768847aa76408d51855691c9fbfac6d5c9f50ba1ba30b00961bf3ea855081bba43b5a1c5d85ef8cb7ed927ea82bd8c464e696766c7a149a91a4093aad0b3a6761d0b11ba1633ca163a1d0b1f573d01aa8413bea492fce422b2e1bd1b10b7c99a1ccb595b750731e16c6b0acb4e469af3376ae62da10292d5a64e9cd82a6dd7159c4ac28322bc86f08b8a5b2fd9b871e948ab09c8d2cb197d79e90728685e6d6f28cc81abbaa5217dd8f86446351331ac92d9c43ac0e4cb00d0e2c468ef5dc368a12bac26a25b80f647cb6333ef9f6a78a559416144424ef910a2b548e15ae8f8591fb78be0d75b4e11ba8876c80111ace1f64a39c2acc8f562265b5bea914230b7cb9fcb4fd53a97330b6fe568a9219221d69d356103d7ba2eb762e864ad7b0d455aa5952095ac1d55a29c9aae380d079fe5c7e88b23431b16403807b4305d8e1a3194315ef0c1e6b518c23a56e8fb0ca7e818d07c66c87f193847049a9a757d4aaa1540ea1c33b52d96d48615452d34a5ee111e146744ffa2e8fe0ef6837c5897dbc77c641f11605487d864293b3c2e2cd88c21bb8a1d87433a7cf2258d0b88ee39c8884d5d1b152a810827d2636d3368735cdcc1120b3aa4256f8e37d17c97cd81c442aac16bc7e781ea56913fa10389624f48857bc577dd98dbc3ae852f0b3f31efbc91a94046ae29aa3e8066fef38ef416f40b46e0680d6f5987521db3a5791adeb8dd5b6a5ff924f02d1b8ee08122f825c0731d917cabf8413c382c629434b226dc062e16fd05278ca8c61af547c8234c9097d70ec189eb687256f1d97a24c7a97de100c49dc79d5efc90e63d92ac6db80805b17829ec4025c7b8158483056e3de64237fa01ad4e5acf01b4864fe89f821e9b7d495d58571d0b8963b0f0030cf138305dd0351d636ad2029a84384c9d236f72095a2e443ee960dba80e54926fecf30df22c21223d4de0746977363e845c8dc08007d08803c85ad1914629f40b0990813d1b1d240ec3d300af80324a600386a3f00b8032c1ef32f16a4cb5262700317802f10be361e57f4b732e6375b2dbda5599659908ab87b64d324f8303ad315b1ed2f15ddbd8e484d14d918a2713f0b9820d9928b3f6888d8494b85c7d335132d3ee23bc960afe7a166eda663442fb8e55697f3ba173b680f36214283468141ee3837d2a44bbaef15b9ab9ca98de7444ed28def5dcdb53723b5f5a6baacb6d4fdf08b31cd0906447f3393e21070a794d3e8a4c4e84f5d44ba08fb304da6cad487d77ea671ae0a331ba6a70b6f4b9d5762469df2ac504e7e20435a2cc5a6f5a401f234aa767c28ca9379a81e063ca3c75807062936248f77b89a90b34e021468641c74287cc1e160f84a87234eca8a30abe00068891bd3230302779fecb6545d9503aff958b571d49588c36ab3c22d0612f9d5065e8a2f2aff97266ed1aaae7e26b8314328210c08f152bd2ed46dddd81d36a3268d514a65511196a96bd087ab495c69d0a5636e606b5187f0f34a261795a5b68b4f07e89aded9674e3dfea02a56f74d303bf832d0b5f419342cb38b663896ea5605225483eb4a5d6b2a4ea1de9b13ce92e290efa8484ed3671923b8baab80324aba27822cc51450401e7224a4debb6734d2560138612fad93206dea5524ce65e366cb05b804a3b2954ebc9f0e7a2a5e3a188cca906343816e2ae3208650747063d67cc7b6cca1ecbffa89b64e949bb2469c254b19fd8322a397330cc3884c72527bad3c7ff5074f8fabb8b3b13e040957f2a3e8b91e906618b355740c04f7388d81192dc3f64c83388b5cab0445ea1be2ce9eeea7c9a660a74ad2f0a03b67bd563f7c42a55dff4d56719ab52853372eb39acb8c8ef4f7381fd81193cb1612e906d98c06d49f6c3c6ec50bd265d1fae305239bc1e4b14d9d0b396c9d362de989ddb8d5664f25e485a161eaddfc354bfede02c4a5326448f29fe558ae3318a1e73e921177b30329e7a05d12e894776cb2dbd3f0fa493fba2d6cdf0e220d9cc347ebb1040a5c2d76122a212f23c767e87458353ef11226c7c00cf3e708cbc8982459a82ec3fceefdfdfbd5c94bf8def0698ac2f0a8c5f6d043679ef49b0e8b9461b4c7af014bd97bc5588206a4f7aa02b092a927644018b8ccadfc6684a61c043d8eb49c84832f3239c29eb0f76c91bb7b78a51a05b503cec9b83c2368b3c936d630b46ce02581839d8cc644dcdc588ffee847ba83ecb188d3082156a9e7fee41f1d4a06ebe00482f4d9917c85a23788a9fb4ed043cb90f6e407c0a7b340e5d2542d7d47b2653b4cd514c51937a26ec5e1a143ec7e9fe277a872dc30d1ff2ca7c02dbe4029d8ace0eb090003f66f63fee42d6a46f073fbd309c8bf2621d013b9b4e023ef4740d8d724c2ebc6345ff25c07d341ae460e00d0d4241f4e292019cad1b54dc3a80c12882463bce7f35e8187e70e7ebc2601633e00489979af0e2875bec6159d276309cf3ca6ce3f0ca9511b09cfb009397939570b7e383a357a6a6e502ea46e27f14189e4ac94a64daa33cece81ac179d7790e87ad417d01e6b9e9ee725630b447daa4d2999e2a596d35348e3f112925604caa1648410d2f42fb01e8892fd688a5cd9e0399163079cfb7ffcfd6bf2fe085158542d51b937fe5d6406aaea3ef924ca4b88d53ac356a090fca184569bbfd7c460b084626a79c059a750e710f8c2c7ffe7cb196913200c595c25931f6b2ea4fce2800e05401b16ed76dafc6759e2f92ef68da0c9a94d900bba0a416453c2e1dac57d970b9ee5a2b60edb7e213bf2593f11e674c1c88328208c552283ca1c49fa37b6789075570a672c641e9fe1ee613c160253ce5c3a24820a331604e090cc5c2e297987c0e857279df9fbf93668c035ae0fb2e93e0843b170eaee80cd99c6e1f38a6793295b54ed7da5dc1b19076fb944388011e4c0dd7d3546b05efabd670854eef197446ce1aca893a0c288732c1b23e1aad5eb2bda0c92269759434f572838eb64bae1604c776714dd22eb0c109d72984d4a5196436f71a22845307a449567187db3c81cc7287e413c1734339e9c599dee7afa789acc1e72cc30d99a61bc27ead77c88e88535a484e92cef3b914c908831be4e147e6acadb05059eeb16d322c8ad6093a21b784a02f9222cd3efca2a41adb7e5cc0a21056dd22233c4300998dde146d89889d428e7ecaebd38da3e525def79d652af5a6539591f6a492f9f5e3c662eda36f8000c1ab0433b1fe112adeba8930255910b9a2f2bb808bdf54c159d6290956968cd3d798ebe60e437255d8d71d11b79e0411f8e91ee3b291ec12f569c271d2818b72e44607d63444b126bedf878325e27b029bd7281370fe5b163fd6e660e7569ffdbb4af61073d3d5db427ab568793097b174ecfb227f0850265a04937ed594e800e8d25fcc9c326d8e138cdda8bb3ba7e89acb62b622bd5af5204c315815177ebb36733f3b756be39ce4eca14c4c8b8c7d4984198f89fa38b95854fd40d8c93845b3205dde6ae405aab0571e3cf844bdacbfb92c7ccfda5d0cb1c87542eb0a3b380fcc545ac7bbd8e9b5d314cb656d7674ae09387d15906b42887394c71012fe1520313d6606d76f3e0ae46bc5b0d3bad91773121bf3c6636d869c93519e25f77bf9a420b3d411f9b58e78ac5371b1ee250ea0b670bc425f5af3e7cc58608c462f349f39df22714b4a61fe5447143fdf5f95392c95765022d2842eeb436dd302a93ec08352b4410ac85e6cfaaee872d85fa7c50374b9decb2cb8c54f396e993fca9caff5206944048fd67cb5de3125841a1c3a4579f8a066d43a38d4cf6b2606c23a64e47db242cd370b83a0837985b0e4a605329140d43583332c576bc69da61f98ed035e3408bb2fa8474b9b88638b0758671067c663361e42970b8559e792a0aed5362951633c9418345b99463a0ef34dc98051a7a53dcf5b5c8c763f9cfd03e0d09c0374485fd6135d750e8f2f380b2bb98f8143da3f2c0b697d75a75d1b138b00a85302377f598a7561a7c6c4c3b2cb7a17d3bfae8e9d6500d6f02168593e17568ec1e83e8264f3a2d5d6d20e6b4f63e8caf1e2e8a6acd479e2faf1a08ee429c5e1640f410a39abd3c9100e98bd93d8fc58087eb9e2c2bf717e815e97437b3113329675b364cbad4c681e65197678222d3b5822890da8b59ee7079cf603d8f268a90c54265154b736f3afc5e9962d5c97dab5c5398b1007171fa08fa4d686320ffbbc73d209a7db1ef06b9ba983881e13193fa410043fd7639b00bab74ef2b43f176dc0dd498e11e39c98c5ed80c9e8c98e040594d042a7dbd71c0eeae048bd594997fc9cee2f73de78655a6f63eb20a9d1ae5b0a6f83e8a8b0e7df036210052999c439b03f3c8aabbb9957f9f57d3299becd80a8a2eb6038fdbfd294e3a8b0c38ead6db6e9fb6003f9c1ab6d45411c8e6bf44e702380894e3945f7d114328dbb59c527b2ab6ce4bf0d33830645a0b29b81bfd4ab70aed8d803af0a8cd2124cea3975c60c658e4af54ea9aa68387e45e65e02fbdbb0373c4b1d3613079b8ae50980400259b833837374ab52dc33f3600d52df31cea90573953dfb8e2bcc06097f99ca7cd1d0ea92dd56149b447ca0e4d896e2204619538da9a32fb33e437a2eaddabd577ec75199913029ca09a30820469232e9587304f3fdefa0858e039fcad93f8be6d9e28b6f71cbdf7526683e4c0b930f0f3cb8007531a6302cd50805d1359169ae02165ff6d6d73fd55b63992f671b2eb339b87888360cf66d5d2825571180e17c9eae56cd0f0efb0c19958006332136c385bd6a4cbc02dd222407b8be44581808be4056a4c4de667f60c3818ba488f0c0704f12e1b05114ab4553dbb657bf93c3357dc514083b76396358990feb18e6c83d4e73d1f3351bb0df900297c586c9dfbdcb657b05c28c37bed27cdf986227a29145edfebcfd40a2459f1e0574783b22bd1beb8d7eac4aaab5e75a382c0069534e0f8f6574e09cf0b015ce87e6f1e2884586d0115f75954ae1dfdfd2e887507069534e97b524641fa4a05ba12f0211b69188ec9bcbc698e9efe09c2fff3d2c8e3793b87c1791211485cfae9c5cf89bc2dfdd3dfe216f67962b53b3e7c7f43fb4bedbd381874ffe6d787a628fbe11d68a0ecd25bd50982e1671672b029ae3821ff082d6189c887b0c372916970f1054f1b5cd6b9634c0708ba602b6eefa5f3d32c35ac0d7ca1d69e955667a75504f8f0b28a63bba2c8f5499a8837931d979428b97ecbcc12f034d404e540ac9366b0c46ab0c4aa06e4cfab1b4927428883b1e2743334c27b1d46e8151c6859ca8c06c3a19094a0884f5128b36146da06fd2971b30ba10def47adc59e0e15d6986c1ad3d9ebe4e878df5e638dc77dec7144c52b6ab50a25c0d7a479840bcd8163a6f1de965424eb04fc232bd51c63bfc844d516d40520c8e9a4f04e872f0eb84578b7c275992570218abda3e3f49471a0ba1ba6a7609bd923548c6e1b5d888e740360647b85524791e4e792b451a652a422ba80b959946c4d2006d36584653ad058d0266d46033962110fc3c0e1d110a7b8f5a3d609777640c5030c73e8ecce88a1dc1faf59a7e4c3329e904c0ed9321780d9b9b4a1d0d8e7ca8c454436780e336c8dd85393aebbbded118c17f416b45b6c4e4aed823bfd595f5c5cbf4bb420352d9153114e13e3a07a07d6e76591ab2670e09edc3b7d447b0f73c5004c7093a0418e79b2b5c3eb239faf6eef05395dcd509f4866f20b8ab109e3f5c9960f375a3d4d422431fb4c2818580a021393be12cdf226b5cd5fadd57c5d0c865b0ff5f0f305fd4867a914747533317bf31ac134fa1f893ae5833df804163a7011a964c2de62ea3757e4c9cad37211b9f2f50f1560be7ddfe051ccd0b9f07e190704e6dc6f7ca47061a5342ab55be52a7f1fe0793deaacd9ff5733df3b004e8069bf66514ca8b629857ab2b913a0fd509eb01f1e55af241f8cc608f9a1a9688580757b84047c9a640660b5b79245b99725682efeadcc129467db4fed86dbec17586230dc6be2e980b23e67535c200dc17e5057039bf2f086831ca2379aa2a03f63223e2fb552070968c5f466a1f13fc5d7364ccc10b761f7381175f1f8db44253a64ef4f01d8be59e334a1f6dd128d6e5d546ffe12c7096c9c0263d92087c76e43168c856924c69362803c2e650590834aeb11f445cf409f18af9334d0062c0d8b9b67d4cc798915dcbbd26a538b3f751c55d4ea2e0792bbef68a7062a85dc95dcb9426cff5d73b051dda331398e7261f990db382dfb573ef0141283ce98f8839b8e60a72d0e72edbaec04864c03395701eeaa6bfed779329c599fe6e1f36581dbbf206ea702d73e099061f74230b6b85ef886a9c3492c90a5854e35940c904dd7ba9bae836b4a9ec3d2d5801f55b8cf0ee26e9bc41ebdc0e36fd2d64059615ed9dbfebedc96c171db966443df170336842572244022e17544bd43cdc5849304f0e4a7c494055f2fe0244b9ffeaa1cdd1f87839b9b278bf046ff714300608efccc8ab70ccd6b00128a37672a9d3a02ef79cc5678732ccfc092cce8346a82ac053386ebf9052a4da7f4bd85f947b57bfe75a6cc92f5ec3dd4a015e4c203a3e2384b1233cac375b58dcb4580adfb373f8b7d5bdb4a902966a9cc2db5e5e08f378d3c16f3e6888af80d4ad30c800674d4800fd6b77c16fb6860181cd0aff61777afcf22eddacde64839a13f44e3b8f616f99f487ee0b97dd4a9c3ae606691c336a6776a26eefb446ec39cd6f94990282022e6020854b93a860215467f180e00526618b08b646ad3ae86f5eef6c245b25d18f796d954ca6cc2148041fe67c3976be69b4c97c69a86050c13d81e31620964f15866fda75f47540debf2e98e9e88d53b708f78e61855dab0d98c13407080e9eb0238c1a0188b0198af608030d1d783e102c39fd78fc3cd3d57418080d31bca7f0750da91b6fde4e1513fa78dedb9671ba46e27a1619f082b5192d9f53470b3c9d316067440ceddfe6245df8b1ba3c5eabcf273efea604a08fca7ca3cdf86562960270abb6a4f8863097145ca3a974091e2ad89ff9f0627805d1516650df6a86bbfc49400587becc12e4bb033d369360dca7b74c418c2d97a59c86d18f690b3e3c74b7283fa74609c723cd830118303a9e128db93db4ede96459fcc7f5af0cedefcb56a2a6835b7a8caf681dc0ed6a8a8253343b788038a8c6caf9fb540dbc360187324023f066ddb5d768fc8256d74911ccd7ef36b2c326295dc850a83ad3e7cb5a9d9fef42a5f335afc95d1c0d5344296075847a486d597f94c48ead2a373fbc959e4c0327b6a7233ef111385f6ba88f13d57e134f8d55609cacf88237f07037d494a5dab46480334cb95b8b46e2d93e5121e5c63700b7f10fd6e2ba11f84a0e3a504a94fb11a0b47fb989459fd3091da6d6270f545435bd588a020c41df45f37900727eacc1a1c12929d16f6f47eb2310a780aa5bdefa29f3c43d6446c9747ec93c39890b2a516a4c6deafc93a5135c5a7284988824d5924ae4e232a2c896e3ca83b22400dc36096cf93f28124c459fd155bbbd198cfcb1475242880e11c7ce510220ac8921a2fe8cae574d81f7d737c6460461d23b60606a603c02100d31262885602465713d30c5d2b6e2229df3c49d68137eec554febb964daaa2bb8d9b5395865ccab3a5157d2b2924eb917ed6c891aba03b9bc4ca0eaf8cb335e90171a0e83a4c250420326e14e677b9b7d2b265d10b06c9c5af4a29a6d4ca21ddaa1cec2ada31d455108eb31a67103c704a9cb5647932237cce6812657077e46baa0131ce84a15c0921854ece3703aabaee8674fd0d406822d4be103fc2c11f8443de7a0907bf0ed3fed64b6efc3dc00265c22e8c33ed0f94881b4fecc99989db6a8befb120fc4a65127e29a3938f19e0d249208927b54102b8e90e1c7635c6c5a0335768d33cc5b6f67c59416f675a85c5233d088b740d06b4a7c818db0dfeb00ddfa3dd440444d2074c62392d1a137d4923c576debf2d305806c4be3ddc96e3e4f9f29d3f56b04f26091373a63c4f814991bf72ab22837b800d086cc3fa208878cc228d74b92f9799cccc83ccbabe2384b5ab37991a6eb6624fc559bf1e09b5c10623a37a2907cc584a9c058c848b4408949a9b2e35c153943aab806941732ccf30b73b9c7031209dc90d9765454502345b042fbd796cb894df5d310009408e340612bd0f9dd353d32e7b5f841a49adf9be23210fd465020fec87609aafb4a3610e13f56075120ea17671cdd387ca2198f46798edfd9f240f97dc5b38185a3afbe3ac6530b354a217561f946b28c5e90fba532471e1b21b0f01b6cdbaf311867fe2b7c311028e31731209a1f029b3a7d96e9cc03009dba12f910cbb50447e5ebf24a58e42a3bfb429e828b0e88f50b365bb8f99ac42a397c0091a3ffe205461dbdd54770f58f611958b9f164b82ed81ee3290347541ada54daa8160603d0222c4a190c06f1ceff1f46e988a391e06064153b8d309877c80aced464401f3dd54cf98eedfb1e042aa18550d89817b7a6dfac29ae1d5b41342491cafaa5e4cee8375975da78e0ff3eda2942dbea7a595817cc080bdc160ead9fb9e017d978055f3df0fa0cd408276eaa9a25602d871a960cfec0278e0b1762d153b0cd903fa83775e0ec9f6594b9bd0b791efb8aef341d1cd14d61e17541e1bc41c06ec495c0b0df6e78b612fd6831da8b98acb0186398e67fce42e467ff9b4dd6d95ff75d0d26872f511e6ef4d280f3340ba19f9872673ba3e0f38f376a96dd5c343492d880d1fc42984e25de7db962fa0f9f50818868c6c1bcec7d9fc0c818796983333001f68aabde2ec53803219516766f6bbc70c4631f81640d59b9b373689cb857a7154bf35df011ea2cb826db70af3b9cf0adac9efa330d2fd7416dbbd8ac9529316bbb6f527aef45b2577b3699580d2b94c26568943f9245722fdccabb49976dfd0edb54089f68b306e38086b340232d1d2b365ffd2c3636b29142345d295e16ec71b386f182ca9a7e246a4df9259aec66e8f7a8c81b980e6eb6c951a791558c2c44ef44b567799723806638d32704bafeb5cd22bfee3c43c8ae8c8ee006d3a7acb82a92128d3bffa6e1c09d4c4a7bae69be23e7bbd9ec3696052f08c50134ae9085a9cef02a3260e35ff75b11395463938eb214907f7bdee08ac634d9f69c59f4c4e9c040db9035c26494e1fcfd9e17d6e838c2dcd71f3d9f1cb4fa05c09991830d8872c89e8eeda5fd4fe467cce586576c0f16d39f3b68cd1041121000bfe5d1636427096831bfe5e912136c8f1fca12c95a2a6bfe0e9d31f7901b3ce3fac9719b0576b23fbad62e57e1a4de00b4433c75e18cf8b4b3bbf2a852e5fa1b4b26c85e55074419957dc4bb910e4e79391ea4f564aecea844eac102b73e7a018e785ac46b86ddef2fd9fb7b5ec2f6adc3ab9aa69d661120731ed2322685f066abb68b01726783fa6f0a48b5c7596f0a1e97a2bdd08649557989c8dc62ef7a96fbcfb0fbfeb164b107b02195c61f1a40c4e8c90970e3e49c0731ea3d7436be925ca52cc2cee61993a8de47802ccdc94ddb8c0362c53e4811bf4353993cfba837adb087eaea01791e3168bdc8a83ef9da10fbe54cd79c8bcef8fdaab100b6505fe0e3ff5ea2995844d71126916dc9874d7432ac43e1d4c02dd701b34b2439c7705bbc2c8eda4d411eec608371290e9332a1a62bd1dac8ebe316786d55e2ef7d367afc570a8a72663fb953c91efeecbc71e7f6b27066aec764ce07dcc4ed69f8ad0b77c0ce8092ce2061b6b0d3d102555b63195f2a165b5e6d54fd63de5b8bb983ddf229cda74191c5b7ebc3a1b238301503549da8a85fb42f83015128fcd93308c51b25b182213417fa7d9457540b64d6582947372b888f3c10d85e7c82ece99816baa7c9835964b0488969c2d1f835e9bb30fea2a632ed7cd4f47b7cc99e57c71db45863b2be64dca04415ad001316d6890ed7d481d97dd4166050ac10e437df08293599333129e2100023879cf8711f0f281a006a123de2cd19f74e7f259ede8a195a0d98df05426bf8d241576e08c3eb50436b7bfe064106ed0b8ae0220161db4e48f007063cf2d5a64185cef0f7834ba534dd58e37ee3a9a0875b4b129d0587049fbfbe5af461003dfa8aea37296e84bd9504e6d2b79898e29e1e66245dee06fc8d5e088bec587a90f15de81e57263a1e8261fd47b08e14e97540a8ad22a2b96cda382f30fd64a93884c4a9a82264812c5a63cf4bee51c3dcac0d4c55fd1cd07b77c9d439f8b3a2436bd897c6211557f0db7f3fc53d220c03b58ed63fa20485c7cfa2f9e74b179f26eba650044a1b1c791487dcce60374179ad2481a756232972f9e45933358795eccb22dc8cc00a3a5594f80457f758f25519c129a662417c1467c5e01e0578b948ee0bf544266ee310f11c06ba0e55d22c05cbee693076482f70c7f32d2f600413e273c38ccd868f9277ce0202749ffc62a2e8350c6650ca6d18641494a595de52e0b56223d2616dcd37248f79c768bb2f9f8963d9bfd22cd026301806bd4063f2e21ec568d192f035e5ad258f58174a7b0f5136918f1789651bb918ef56b99b2b472990cf3c051da6fcca1b37cbc403d7d5170f4913168a706dd749c96e855270307ef93bf38c8e00557953de7f5c9082f0e657529559da4cc1853f8a9ab00e1b8d049394c877d284f310b39d1bdc417195deb510e57db77dcc61f4206f8d4915e1b2692039fba5b0676f9b1732ec9ab0e2559a73f288da948738c0e0386b5025f89a11326fa69dc45c012fc63094b40084c2dde9d3681fb85776e6b2405c1bb9dc4d86cdd46493ae874a01d478dd80b2a809047ed5bd95654fd0a7b42dfa281a1f3b4c2bbde33c65b36e32c7032ab24992f8749053669221fa34503c1f60cd73a39d3eea847e3a9e298a0bd9fd8b4eb843d948d5cb5a570d615dbde84136e01940623add1daad2c6d6ce6da8ae1228277a978533cc832e2ddd1a1e693032f414af302c8992a939acc77cc48b05ece47ec64312241bb1f92f5f5c4ac5e7a554b56803aea02c3dad6aaa472506d94b3ce4e7537394b4f8a8664f23533d380576c7ae55349d182a1c7fb65acb5ae0d32109abc5f838e5f985fedf89a481af046e5d13fcb7ec7c1588abd2dfe421a03eae024f0662fa0f387bd7050ecb526cee5ded0d03efaceb00951abefc8500275fbab1d33a664b68bc4d4cdfab6c509fb15f1d9dd9747588c25dea8b7782b06bb81035dfc2afe957b6d0d7ca6c9828bc49198f8f6cfbbd387baa54a1c4a715b638e8a2ac291ec6e51a3523b555ab1f22c119ae5daf959aa1c22f0cd96374ace96f2c68c4025b6cee2e73e2a485f2ecba036c177c4fd85026b37f0581c3fc5530eacb85044bc68a101dda543ea8a217f6aa394e6261ab4ae6c6ddae8b0984e3bfaeb3752e25f61a6639b8aa7ea03a91387aac300235228a406955d9a150f999957311c035f104d4d8d70308d123849a535e9823cbb915c2182662c85912d369003c55bfb92bf2285b869192969cf5c94427898f5374d4dc08109d01d340a38bbcd5cdd5237346558713651cd675d0a146005b9b6c2b9a9a1facb82d57bf0c7b26dede412397e04afb7d016350bda4884b190ae52cd031c2ca2812a26ea92fa00faaa140843d241a3ad2da28a97d9d63ba4a311a1d9507f1abf815632156647059e36b961a74dd061b6939705a209a9ba6944f09fec41d0f0ea45c09f344064e4f9e690400bbf8f01a8ec22b82600e3ea259c7b115c662e32e2a4387b9065b5cbc07291122e80333b17f40d882d7d84544bb657c5d5d6719fc6ed52d18ec999dfde093fa556b40586694ad26a0e35538afc597318b59108052a18a85fe9e9d527bc79eacd6a5a9320afae0d5f845f9fee2622e1fe2de442c53000c44db6958667407597b6a58aa8e9c0042e7192a0ae2e8529eddc91d222031b0f085403c81c200979a68522b272072240733819acc37c384a55096424b99c2377e3880e34edefa4f522a0b48887a2b6d25da808cf1a6222f5c72422f122864cae550118db920457554a9bf79410275762d391f50af291a58a5b76075cba9cca00cad66b7fbc0ac54bf2e641201884036e6bd9095f3077b5826d6a5fbfcd748e6b404f620814c04695a194964cb001b58cd4aee4c03208ef27d3da2c8e035db6a14cca2a3b6d9d02bd62eb20437456ce235f17514c756c830ba9d3efea6bec839749910e8c75672507d5d239a833db851733cad95c05c3b96a82ce55ce73d504f891d6ae7532025a24abddc26e7912b2fd99e13a6af0ceb4758044ac76b6292b34bf6be184126e7e30b342dee687f0c4ae6602dd4ac02587a835224b16efc0b3425266e7d030060c20f5ef61dda3838953d9956279338a3ce125bdfee0520f267f2e55650860e3274bbd667274af4e3ddc53c8b70c4516cb41b925ebf4d1954b928d3d28b1b08999f998ac3bb3fd3bbb132fe5763e5483a5d24ed190a0ac0a3a0374ee1cf43a19df28d235902a93c3bd879b2e051ffbe14e1a7c337375cf2b62039f2e1bd1a20e381c3409b7bbd42a1a503d2a9eadde26131ebaed1ce236fbbc7a36ed5c1cbff9ef8e422411d10e4ec7319aebb40f8c79c1938e3daa0457bddb0cb556214df5faa4edde1f220a78480fdd091e194e0037cdcd15b6c0ae82c7f804ceeac758cb52d218e205d62e03da87ae35331c9277e6f62bf002decdea345b2d4505b3dfb36b705dfd77e9e0bd6a637015ac23c3d8504ee8e0e08ba05ce1781617f4e43a90ffeee402092454bba5569d4d418e40e60fe82a1775588cc73396ab97d9819fb5b3fe991da7fabee74f385d22e0522917f79bba7a93237674928bc07788cdcc2507225c4d8c595776a8c76835b95e3d7d91d330c78fc0ad2cff04936ba69feefa06926b749288bd514313b82a621008608afc4a90d6d92802171383eb611ca01c427d4f05d23040b410a6d14484d9a0b180bcdcdf9ab33db0f9ad53dd4a399c2244b09e9b9d7e9604da35c026b051b835684ae5783d1a2e366c3b8408d52e34df02f9f54cc7338b8baf228fb15f9f7673bf0feb5437b86a624a0236df1988808bee0c2f9a92c6e10b251c40c0fe983e50f3ad951b858a8bfcbb080b165ba976c54c7321f6d23ade1ea346d5995bf61b8f67fbed16a66765c991c3ca63d8bf4648933b972bd19d7251dbf0909a2e2b6eea639f95fb7c94929a19409a37bd06214e5e98f5db3ddebd53e2317012d82408eaf0f3e37d336b0927de5b76650c3f8ca1bbea1ca672986f8389061ea6b589224588777023f60b94a047674036e092511865256660a6d264feeb13543d9b1c56c03703e1384896dac04e6e9ca567bab34e5d8bcfe7304ffd5bb9107622c1447ab61fbd636cd22a25f1285cfc1940414531358073f074b5425bbb0331f58a5b358fcb1e9be3a3b071163c88c1392d32e74acc2e48cf99e4b23ef95f240aae30853dad342bb994ac4f26b5a353c2d53b4e6cb937ad99bbe8a2a3af49280b552ae802233f909e9dc21b60bc4a0d4d5d95c698744deb16a057d4570ede719e4118341c6955ebbdd7671e3880ee8ec2a3f6df13f4f9c2e76c625b2a73e2c7c0a42b79a9bc1490e49cf3ad4a57460c2979435ac7f324837fd3d22fed4dc516198c82964066cb7892e91fb48a47663cb5a8838a804ea1a69c06d8e21e85a66ad417e161654cd4284df51799402d93ce85eed93876a7ef6a1de7363e5a2131d166075a17781ed37fdaa39c574be9546c527811a7529cae07028629dc15f1fc6808c2a874db3b30ea4903624488dcb322e9be488f0b7af611c49bbd5d092c95e90d3887d220630cdbc9a8b876124a09408d393dad975105eef1424c08efe6a0fdf6f96b949fb57622bd6d89a0dae9ddb628720af60602ad4c51ab765239dec220cdd68aadbe453b29870ea24700093649d94ea948c902e4ef94403570f82454ff920cae8183fe6f41ebefbcbd0f9ff59ab84d0b7efb4fb7fc750e9d0ea50b755112f54689ae7d4efdf0d73d7662683d27668648cbcf69abb1ae9bba613ba0788615a5a1dbdd815b283a2ff0b5021dc8c69c83b83a3c88f4ef82bbf1fd8b913e02501f5d1231285bfb52a469ba797ee2541cbc3fec49749a7ef8cfa012b1faf31b2298022684bbcc520e4fbff22a9f334e78d3f417088f54afd5a11071750c17768d6bbf06f48a39c8c8d7f0bcca7511fc4368cba0edbef986e815488a47ee48a34a218820a0eb49754a2a8c847201027ade8cca09e6ac833977e12fb365da71c9aee5b6b7ef8774d3a26962920ee9f10e7407d6809ef2b4f23d94b219e522d5f101dabdb828f44bc091c709df7b4fca31fd2a6204ccd0395043a6c26b6c8c23b387768750e58556098b0c8348209193c89289dc4ae9c2d7092984c043887c178c1463726ff5e498331b24d5d798d8a7d743d761ff779ab62488101b06c8435df40f49bbecff8b5ed18b32707c96b2244ac04aabbb3aa07b1d403148db25d8452b460b9eced86309da6aef4a3e1b0cc6309ca409e6a8566734c79c62b9b374f68d00f8d9e0392c09de2bf7b9eea5cd837e1504b55d9adfbb5b660e8c5c8be33a15ce4044d74c0047646960594fa0324a9d0a6d6d84d504096e6a7e2546eed411b26b7ee6a166a2333424c1bb19188fec3a5495167c5e8552825013cad3f63e9dbc316f7446b3da2d05e1dd4ed76f40012839b099ce6ec2a2e44bad616f847ea7b4a2ae1b91a7cc8c17abf9036133590f92312a1b65841226f2269d829275dea272ba7068e6ba4873787fa24c29ad62a262669ff38c278a225702d85b4333f77fb241348b223f50b674ee3369df76097c849f0875360949667bf896f24874fca3c7d8e6582462db1d7f8623f0c30b667996de808f7689e38fa3e16c9702e7d6673369b8697513c5db4b633d80b400abf3b7c779ac483dfa3d00663d70dbd0eb14d2b110ca0d3d76918407db4c2ad42226e518fdd75b11978a9ff119e247de2d29a4ff92fc8b02be5f7deef50d6481e7afbee590320385036a7ec6d1cec2f65a21ee55c0d00bd5bf9553577084040eced2220ca9cdbb654f45b99182ae192f9d2a629144933d1ee35384681e2196b93974ec4649b179b66375d4bd3ef59cc746e680ac902dc7abc736cbe74a57ed1456c9055785f8286f8ec4c63c2c2ef3609548669a4a6c591d3a5e583344326d4a62194ce860e3fbe3705d1a6625576890aec7828f1391539389580c72e379fb6b75ca4d24cf81ed1156cab379d4ca42dde94c0750499693d06a8e30a2372c4008c437cfb7402d9303b8caba4f1a9ef78ea8c2efe8f189e1f4485de5a9c4c5e72841c4cc41956243925ea0c53844b52e2783cff0319f8a72be5e9975a75f943a9e39e32042fd8e18964ed043e42b222e72c835c378fbc1857e82ee95996199dc113282daa0b85575444cc298074f4b4b7944be29532ffe0edaa1cd99732d2a132607d8603759ac0fb2da306d3472b9b8d6481411037bcfd1b857703df21357316235caa0e34937de6fed4ed6cabc58968dda949616750b7a9bd9b37fb60ee894444433a1de4ce761c75214c509e2e2ea044923b81bbbe2ce8a77959d071e9437753deeec0fd3baa54ecaf9d8fc18af97f60771a0d773b806d9585020ea7799110ab66be78247828f6f4b2309f57cf91ba25d5ee0e3b9db113b870a63a4b72cf3dc7f8a4b490b4fe2a648828bb934792fcab60bbb7fc40ea4c6eb319075898e011f274a408dd9ed13c404be47e5b1dc7bdc82a8806023dee1e32d8e2bd2048d4031caba0fc3084052056c0de83391566537290288776b2bbba2dfdbe7ce28a36bc95cbf8872339495210017d8462a7dc8ce2582f91b82ff2b7ce93e1c33984b8c93994a3bfe4103b35365f94e40a2f1c0d217e14f8b10b095264266c8cac31f229664b1bc3a0c67207b6d9157bb51f4cb05ba1deb669fda2bcf45c4dbb8344cc45c6d94d0a044d73ff7d5e2e9f69828ac8c750d61102d540a0ca07216e80050f8e9e07a5708c515938427008beded274f6d112a887a96ceda03a5d802c48c83cffa95a3589f39a4e468a63319b045cf52b4c2029e8e808805be9dd87ef62b6e6cf003d7916d9b075395c022be4092e3e2c57dba34c75f45dcc681b74b2f30e1093b1fe757f717346b0719cd6b525fe3c971d8214fc78be795d53e89a8614b3f0661d89b6717d72bb4ea9c0d8999a83c954c562f7a81755234f645d89f57c48a75238fc35f3ef756f2159d539dc8509befb62479469a291cd1c06b77e6f762e95ee246b5248fffb2333b290eab76cb32f5007ebff7b5ccb397c2ab74dcd778895828c2558c6cd7451939cf5d6698b3b6968cbd823a3f6ae53d821ed79a005a54878d288958457190ad4535a0ce977c4b77b130698a54a9804dca3956a508b08c1656563aa42759ab2b061a7b0e8d663e1eae7783689149e3667149dd6eaabb740e973bdc6400f14b62c17862310b62e130ce7eb378badb74ab0ef3b1c6d63b1a4060f6983c60a77b7dd5f0ff4cd90f39a7969324c9df7de26142b59d90928a22f7f47994a6cb0c797fee86acc8ef396e5cd648539c0ebd055d3e2be60a50a108274e8947ad4b69efdcda6d39dc2bdb88787a5736658e4998be3743719aaf1c8d4b62e75b412e840908d17bdcfa572ef2b55f327596176ba04828d593daab24aa81e58560540ab72facb2f2d71fc56b0d270e4150d99211f135972602b16d20fdbb7ea2c1d6664c3ce965ed60c637d13e493c4e50a30c4f023f1efe6465c7c264c5ade50b7be9917af99057f465a5d0325d0b408b5ecb7fc7227111716e913c6741e9619ead6356f6ef10041cc0025a0d8a6d5a20ceba9a0e09e4d9322243537309b1fd26c8a4ce481868833f384e1c9fb563eaac903650bdfadb530ef72eb34955e6bd0970904c4d504e760cc89175ce397447c543cbcf2025ffd8ebbaf330fdfbf86a4b76b72db79452269902100720079506f3e9cfd6aac309bbdbb012840c270727a78d263a24726ee4dcb04aac946e7c37be6a04cd0e9fcea763a10451030e1d1c6d38a927c0e1c2e14282db306a0369b586cbf33ccf73edb021c52263c7934a5872782c168b9543474b0a1d3c3a78d8e8e2b1a1e5ee80b10183414295e0799e67c30a181ce8cdcdcdcd83b485047a3838383860d82509bd1b376edc08775c1d1d1d9d1d3d637a3c97cbe5eab98072673cc982010030b849e2f2f0f0f060f0ba748a97cfcb878d25da844f2b036fc78e1d3b32d0803e51bae0820b2ed080077d82e427003f016083081b4c98a66a6cc49ec402d023003d2c9626ab306e981e403d80700b803600da20cb8ddc4d4058d940001b0860aacad416b2292a4f088003017040cd602420a070d001071ddc292816cb51075e8f1e3d7a743080a9267700de061b6cb0c10082a690dc204f766cc07cc07c58a8a9213665f820800f02383153e6068a00420410f2d0f0cc7c719d9881d27942684868a88d28335b7c66843073642629fb7b7e9518fa31f4c34c8c076ae65b3ffcf0e08707b4ae869bca60a11e7ce0c1079e04dbfba78caccc9397512ac3d4a48613ad2dd42838f28137343434f40108396140f03cf0c0030f4020c2f982c8036115020820702010e51ad40a8b1282f02aad5068a84829ab4de3288d24ebcd39a7dd9c4431a5d6badd5a1ab1b96ddbe56a4db5282f73b52d7287230442ae04dd52ee9771c0d179a5f2e8791cbd67c218bdec525a72374929e51167a92d72374921e5f1cb2da50229a62c5b82a902668bdcfa06b344e83c74f3e70aa77fbc41ecaf295b352935273b9f87d342ad166af9ebfdc81a42a4c24c9c19cdc922a64cadd56e62624d62d2d8a4c6a0d24939c4a593899252fa6c220335c95c91c339d5822633771537f810aba2062e4d2d88d2d405893943a7024626d22f12eb17715f24765f24aebe68862e20d3de2f12555f247a3c60f9e3fde95306e8558217910f241f268cb0c38d101ce81f2ef0d813b499ea8aa4bb1aad26b8e9cfa7ae24d8e51efef491cf0da161828d40ca23108c8ad2c988a32ca5fb99271b33b8dfb39ca9d274660b59c64d0d6bd5715f431790ba7dcdf922f1fb2211c717893b5f24eaf822f1bf480cbf48ecf922f1822f127bfac86fd9601a4ff5367401b1f7ed1789385f24def82251e78b44d7178939be48e4f92211fc2271c717894562ff05e05582e5f780df0e104f8143f0357de483e02bc1200f0894850e7084e9233f074894048311708126a800e30051d0014f50028f1fe8c2f4917f035c41867340698403cad84d157894b1d64deb466584ca747a488262446503ca2389e495008ff248264d1f259a1af028936856342b99ddef13cff24bba910228464220c94fae07caa50e944c4eb2e97303051ea52c3f49282e78944f59aa511bb2c2089958c1165f4ec921d984c9a9f6917fef88e50c472ca7b204237bc89753bee441a3cbe494c3422762d412e53b134772189027b9130fcac9b29b7e90ca7266f92ec5613e6144a2c87709460729352518fa12ccc0092cc3c8443ce6af2934a48c320c391506196011751c233e0f29a550686ef0c226e129de0d618e62b118139eeccf9aa98cd025344f9d0e35f75f65e17ea0c562311d3c2e5b0edd92329868e1aa2f1b932f9c168bc59470354ccc788ac5623a5c232a195c168bc56cd86ca024a8c562311a2c0e363d2cc562b11dea93cb440d948815154a26150e0af6fe2db773ce7054e103b33fc5212afb09629edb3baaf0e51a561929098ce419fa53afe5413bd971ecd0d140f1a6b46a26bba7ca65d9b8d0327afbe22a23d486b6c141de272b229aa3e263e76e9f7e1b5e198e9f8738b955c002fc2da329e3acf9d2b374f7176186583ae9b0bb1f41b9acd4f5d4b22f5072bf0a75d2f6463243227fe5cef1f439a4242b3752eee93fbdfa572a2d7daf9baddb5675d4301a4a36681c4db17fb315379db1c5d7840691dcff93963195fbf59a39e42e43cccd17dc797bf97686e3eb6b4d073b104404c782bfbeed3c6b48041e7fe496dffd9d33469273e4fe6fe7ade4ee2633aae49e0bb04a98c95bf881adffd1d5e778c05b688090c85f389eb00d47ef6be8b2f5a7b573e7a8df0023bcc426632ab71162973144ee576e32b8e4cefd2db8add39b24c8fd5fff055bfe6acc25e1cfe38fdc0a68d83643140e525596982245ec07d2b09f020ca82153bc50d222f60f19936013185a2db7fc05264b6ef92b870d68d6f80a94fe70d84d2de710fd81786192fd23b084e87f8215d9b3377941e25302659f3864ff0ea8857ac885e47edf99e151e671cbfe9ebb29aa8cbcb590cc46fe72ef1ff994062a29f5a2268882215bcaef5ff681be40e20ac94aa392526bad009aa882a8fd42b69493052f72e8e1c81945845421da254a78b1258a105fc480102d936cff05248d44c5aedc22ab965a6aadb5d4fe27a5c8006d0aa431928821cba2012fcea0418a12262db31bc4783451f0034d588bb365324caf30afd65a6fadb55e408c21b3a62192b0200ab17eb6267be4845bcc5ff73fa7a8099b5fdae7fec509c78b6fc2289b30aa33a2475369fadcbf5c385e5c138ef8c3e1f84da557a6b209ab52ed73ff5e8cbf2a759f729143f448f6a063b4105dd23f9dd184591dba63ba0fc56b892fe20b22df37b23de4fb464ff9d2f2fd8e696e51f98e1d131699852dcbd0d6fc7597b4901dd33ff7efd3233964c57c5eb0bd5f55b45c8f3c68f41a132d15c983c6f925d71ae64ef34b0b714ed49d68cc492adf9f5a68dfe4e241fef767148f2c5fb53495f27d15d384b91ef0bdceeb8430e8fcf7b4fbaaa5096b99d313540bc9dc2a5aab68ada285e38ce5fb812fe07818847c8f23743d40e7713c8eb0c517bec711e295095fd82b135a078ce2baf7abd47d1bbb6f8deedb23554c155319a98e5448aa249512f7f404458baa4971495c12a7c42d714c9c8c73ea41c2e3ade5fb552adfb776dbb6edd6aed4841589f73fb048e462b7c5fbdcd18451f13e87d44232df9fb18649a8e973ff1ae15e23dfbf2edec772a815cbd7265f7739e59452deef3c1f2e54ffdc102e12cc82cb2efae77eaf2494d75a488c07cdbfef4e13b62dd13ef7a1b82ac01b16df0695ef8f3cf772e1c6d43fb7072c7fb4b57c5f42cd7bf177bf7b6e98c2aa861d4887ae7b3af9d8850418ac1d3a25ab85a8985da1cdaed4ecefc821df691fb9d3b49da6edecd8c81ef29b06342496e52b00c8092d146c261536def33bdeb010d0bc608265a9ca0e5a44f9aa861535b98209256a5eb0300a42c6aa905ae9660509284db0e04c132a10330b90859631522cd8a2c4183eb967474d324dee590c64ccdc6bedf4c83d3b6a8a01aaa6da91b52b4ca6b2d0a4409972623ae529573c71a508318e80d18638a7c0400ac8f870454996981ae2d84fb33c36141b504f8cd1258a14349210c78e6a220349135998acc00a19a8104759258f91a4cc184633940005e22891a4702a2306d3121396a825e22897a2a8a2f6c509145618a5401ca5131419c9530e3f405529428c384a994bc19244c54809b4748186b8c1104dac5144175196483d915d4c91256240c388224824a110860b2ea260fa418d22445f22862b92d0020b2c6a62910f668489010c1ea6808a42f47761c22450c40d51f0a0c4e8c908518327b29035d1c31155c44417410f5bb2a480e908a327c4119e48b2f403134776a041f4975013d6627ef08246149a13a427b1566372490b7b1f1d819e7a6ca7b19f5c8b3c36548f3d843f964264ff274222fb9fe0442c097ee59ed95024dbe49e1d8979c276392380eb8ffd3fb9431e5fd9a71c29b1b2040a254acc9410a1c4892da22407329826f74cc9117b03eeda8821891af54892282449ba4832bbe9a96918314b02449d9a25e16196c4c81414d8abd52eb813835ec19c162a6648a6cc902451c1c0aadc33245066487c50dd3002490e4bcc9018cd6830b36a512eb0f7650b5e85a105c6598cc13798b042c310341a78a84e602ff78c861a86cc6820720499d90c66cc66f06236c314b3198698018824702bf76c8625d48c1964282355c9981d09838bd9112bea0eb32332228905c5dfb372cf8e0091bbdcb32348e4f1f5847db01ba5d31a4931c3d2a54b0c27f04274275ed353c0f2450b962846b12e3160914337f72c862bf2d8236341f772487ec084159e0e623ff61e58a2e85a24a5fcd2995e4a5d0524473318cd209120e168470591822e707b35cc39e79c38c8b6209e0750072a782e916422df549e7cce852cff5239aa9e3ed1ec1050945daa4f413ef7aa508637f4bc960c8f20ecdc80c77f2fc25de0913a9d49f08877bed942bd4ac09d923e7569d010c58c128c8a144862d7c351186210cf8aeb4888701283fc2801096c9045c4203d133d8cf261199a6862901e6b7c0165b4059218044806a4a3fb7917246830c9280d59c47ebc8194263191d513f6822452849218640e113d1f807cb1a35367a8e4f1e6334879acb1a633489c51caad30b5afbcdfbdf7cee6f1f2a0cf3edd8aa63248f9078f35b464fb3db5d6daef3fcaa7f4e5b7ecf741875868e16650089c020def076e96df020bf25d9d6dc023cd56c84582ff7d16388b5bc8fb14b8f70e04c9fef6a957d342abb760f78fec8054813c34d99f86267f568120e8c8fe3a74e4ce6567625905ca273207ba48f820fb7ff0417689e042f62c737dea5afaa4d57644a47c333b274460fe045060ddeb23ef702eae6127b88f77e63a4afd55eba4e0067a2e1b26a0e6fa35a45ecdc5dc8d7220965e76f7af947e5777e654818737c172650b2923ef26e3041921366fceb99adb6d22b3e44c1a5dc6641993c518a731518c9132268831388c1132a6158503757373b3bab93937bc1b1e956f08354ca0c6914b0d223b2e97cb95430797860e1e3152c4a000044130dc21e66847983061b884c9126616664a981ebedc4d618e80990223e6c592eafa7b8aa90e7df8cb831f3da4e051f0e59a84c3e823531f3f5aa8dfdf83160243040f3a7a3bde541ebf9d34ccc8634f1e29509672aec14692992ad227ad76bb9caaf39a8635718d4d8d8dccd3d25a49296d6eb2904138b55673d2dfe3ba48009aa10756f057bf132de982d4e67f4c749d338894c7248d9ace2ddf73cef956808872c44375ddd35150914e40b570a8c0e38ecb35c290675276956a54655f4d30320bb2bde4a7fe39024b2bbeb404041e2713261e344a27241806dc47fe4c92c14754f6dfd9e4cc482dbb72cf8c92e455ee9951ec4ac77281b9a3c81d0b4b4c4a0774ddee3a57708e18d4dddddbbddac029e19e2ff2fc895c7a4e69fff4fb38df9da31fa784ddc31674dddddde3af9ef27e920b02cb6e5aa9a0b56eaabb0aa1fef5ba596bc527dd54b4d6acb5d5de6ddb6ecda173d66aad956e97f33a8fd6d1e4dd562b006155f6395f95399adbe538aaf2b103b402054cc924450172485ac9ee47f48fff789754aa6ebbe2979696b8a5234c78c92ab2149289d7d3d3d3ebe9e539c1f43d0f6f0d86095eb7a22858cd79373fe290dcebaae2b6a7368cec3fce7c82ec21a9dd4638e15ab99cdc024af5249f4638e1f6d36d5ae771d7de2d77e7c0c9402b98ac40d8e3a9a5160dc380fbf8ff78b964c9fe1303af49e98a8470baa4f5a92a779ca7f2f08475dd5bbe15fac75fd5e1a8714eea2a8449ebaa2f796664c0fd33f7cc8816930915cc8c3ca192aa34234828c14b221851eafa622343f2f87571e3b12447e9ecc152aabb82bd78f21cd6b21a372b82451e7b6899efc663ddf03eecb5a6873f0f03cd59912954f40a1628f769dac582bf2e7893d928f0967b5664893c7ab5224e72354191a43c7a5e4b842248af9e9bdcb318a8649a7b260316426ca053544c09ca70439763065e2156c20afe2ef8cb58c3301430b1294dd49a6821fa4ba386b51930464ce161ca52942ea2cbf6f187ca9716f595d03fcec31537b084b084b084b084ccf67c6009610969b186b084acd6f07cf09cd85adebee3fa5e8cbfefff0504d4db9936623744b83e4a038a44d713b987388cc06f34615276e3b79dc7f15bf743fdc3e8c59c1fe597bcbd2ca33fc8f9ed7b422561a42fc7cbcdf13a5e87bf8e6f3147e80292e3059c43e7737c8e100617cee7f816754217109de7799ef005d7b4df13ecb6ff40e7e5dff0fbeec5387482e7b4dfdf75dd86d43f9bc772473d89d442acbcfd5c35c02dc83581c79611cded861bc6a30b6c5cb38032983af009cfe780469fc8f352b63d0e90c890edcc99229fe8fa0f2462f4125d5fe41389147951e7bfef6e805294348b7479b66b34fad14ec80a47cc03caa8e9b3bdaf244d09eeecb9f38dc7f1371e870bd3677b29b5b9b803ee1c113bd066498aca392027e61cc0f33705be61a1859b5bc0013bdb273bdfedbc049f186171e77580435c88c562e2ce751ccf3ac016bb20f078f38682a47171d8ce6f397eabe56ddb1e0794b95724b49e7b1666d5b070706ac00ec88cf3bd0a01e789bce67b15828dadf1acb5d36bbe033b1024db6f0fec80ccf6439af2d7f616f49abfb66f816ec55fdbdf6c6f037a949be162fcb53d0b74297f6db1b93d4d380225e50d296fef85e3d7dd6994b71ca0334d9fed77405f7228feda9edbde05ca32da677b1ca0949a3edbeb8064f2f61fd83d805186c9dbdf00b747616edb6fef1495f46d1b035ccc09256dfa6ccf420b389391b79751b2c7f65e12b6dfd9dfd658de82eaba150ca962ee79ee1fe1c1b085eaf713f58fd0c441ded130a0892726a1875a903d7cec2ab46cd15b80b4cf0c4d68410e31c0bfa36139f2c80c001b003aa07f80b48f7b69e2a2a59ba609fef25ab300349c0089611a3e7101d936f085fb3008d9fe86ae07707fb9308a4bbc215e99b08577fac8106ee713c680f6f1efe2db92dd4bf6f71f7fc1057fb598d815d2350cb8ab2023b5aae526febaefe4caa3597b7e5789347bcede98c827df13b9ad4dd817da9adb9abb5cfa9eba88a5feb92f9726cc8b9a3ef7fbfb267890fcfb2e785077a553be5296af6492adbc74d085a25f32a727281a14b2bc3a5a2dd1417749bfeedf3cde25aba37c1f020da33220728872591dad9056492ba5d5d28aa9852499dc5fba501918798cbee231dd8bf1f7fd4d5f17def169f01cbc8694a4c8d746c9d7639a308b841735615b140b3c262e3c262b3ca6268fa9098fc98b92caf7ab177563f9fef4a2ae51beef5ed41979b46666cfd92bf972a8853c268764e8425ac8c30aa54aa94b35a92a552736ca92d13ff736c0c52fb464b4908d924356caa5c48ccd8c9a180fa251aacfe6c7afb24aabf4e872b8e6c3cfa2f9aee64139b409abb73a4d583d52396dded184754cb58d88fbdcbf77c4aaa37cff4638decca3a76767278f1d2ddf59655a994e9369ca26154aa1f8ebbe94257fdda749a1bbbb7bfde9aee4afebc45ff7ebd192feb9df69c991e1b193754c75490b5930fd7324870262542ae794d4ceb7b44a51a9dd62699562ce392bb5f325b5b44a616b6aadb34e2a29a54ae4486b7f525aed967371f253500afdf22014fce520918fefc3d829836c1fe0ed6355d97e40c5b58ffd11529828dc9cdc7144b86f32d7032d6b3233535693327f65e1c0229f18a5032020ef016c2edb553435dca8eaa6cbf1746a725f0eb4f573b87c394234de0972a8411a69564b85d6ddaaa5393e268cfb97cf839f0eae71599b34aaf004d44873b4ad5a3a6538a7e64926ad3061b2c6a4f4fa5eceda84c97b3196d5a8ac89e06529d37192d1acea77d89b35d2da1a9b95cdaa041b6f4a59b10d2b09c729aa93d6274b5229a594934a59a5f53929adb56ef75e8ee34298abe9d53a82eab9154176126c092bcf6b9d9db762615b5363bfabb196c5aaac337c52da0110d00f1f3e689d3dff3c5eaf2af1ce67e536af70515448277f260cd95faab8bbd94ae75d75f3bd7b509a69cd2550ef17a56e4ba079ce1ffa41542c966c4a5d4e3668b59bad74aaeeaad20450d5caab7292604e5baba5dbeceee6f085cda97ad9ee9704ddc6a9ba7b278c856b6c6e5ebfbdbd6f53b3f96c3ff18a842d0120e0ee8d949245d3751dcdaa898e8ba294524af38ca29452a79452dad55ac35a6b9593524a69edda737ead9552da52d2369eb63b09208fe8acabafb5863e75f57ad98ddba07f7cb5f369f92e1404b048f268cb21d73ffe9e3bb90ae1eac81669c2544eede37faf5b242426785439ad0185cac922d1487981657b4dfff8e3fe719aef1197ada2d3afaa6b8b84649b384c65c5554e2aa76e73e96128b75d4ab72cb2566bad75d5b45625b72cb4729c9c77dbb68d02f67601439e2f29ad964a69c615d375cfc85aaa9252261d9aeb779df4ba3ac11696d652ba6ddb46bda3d36e9df47c415b7bd091aaa9beaa5473521db94a55a92bb58302164b1e69baa83d64df5622e8c85c480305857dc290bbebb2374a53c32021d43a55a5544a1356a5a494b27f4b65c1b2594bdc1049b74eb5a93a313a55b3584e1c3739ce26d70de479bd7deaabc60f54bf2a95181bb7596bc1194aca43a5f5b71548ee5e8e0b77ab53a539d170bc17639afc2c9d24762153122e95f45e2e3ebf0f831600336d522f135b953105d52368505df2976f37cb950944b85015ca16d42bba7dea920755277f1dd141b58aeec89c61ca2cd66522dbc7ffe2409bab7b031e2bad3a7950a5f9cbaf0d585ed130ae62c9fedcb67af2b5c0d2ccf0bed7b5741a7aeff63a72b8594941acb281e65ba83aa2d9ea97d64dfaee7f3dc83d6c972f7d723eaf2c31492927cde2288939e7a43569864b741e90b99fae40e9cf511ba1c13a513e1033b2fc089860766486209d2c1f05a32db27c155af0f9fa80bc2ab274b0b33400111111857d52ae44f8a66cb0732779348f761d81e9b79ed46f90851afe9022ccfcc34b028f5f96d800fe9244e08f25f0f8e30791f43941166af65005cf5f58a2720bcec35395627f903217de69e1cfddef14531eb13d9244dd823df7cc48cad6c278d6e1ef8dd0b2977b66a494c7d7531555add2c830e7534ae70455f0a145f71551bd26daad0203cbf7eeeede32e79cf46b6cbacf273df0dfff3b61fff7fff5afe5a7e5d1c18bcbd78a81528e33644cec5151ae3c378ccd5ece5a4e0b966f2ba7f226b859aba2327c1fd7b7de3555a2ca46c3f2b726b7d6edcad075cf4879bcbe4e3a32f52019f617587e8d65ba54441e71117986efb27fb66fbbfd66255dca13fc6c4345757777ea2edbdddd63fde33d675df2cf5331a5f4de6d924129a5747ea5b5d6ee6e7b4627afc0f2bb2a3585ea9f2e9ad2adc05c50d1d5eb12f542f565ce39bb7f4e0d6a06d667f3e151221fdec5dddddef7fcf5be16fdefe5dde6a9f12efdbabb1b87ce2a84fece717917e8f53e807874d43c0f6585a07779f4649c69c2ef1feae565b3855a0d32f85a74ce39674fffec645a35e8eefebc4bffc3def5ae7733956fe98ba71e5976777f9f9452f2e81f0d6a06d667037abd6cb6747713f54f7763a0e2887ee480c1f2c79b01903306cbbfa0ebc95f0bdb59c3af0281aadadf272cbf75dcd8e4685d8a03ebdc68e14b2f557dc1f2bb5a70fc5a76432304cb8f4747cdf374773f2b045f218d10ecc5660bf57ababb011035ce39e7c4c0a3b5e79c73bad082bffa392f32a6419152434dc9a05bd8660b96fdd395524aa9ada00a3e5af809a809cbff1fef71a9d704963fdf660b96ff401454c147ee9a25a8444cda4a6ad4a19911010000007316000020100a07042291204af24cd87514800e588e42684e2c184c2381400cc32086a11808c218880180210a310311536cca0400e0b4affa1b9e0b8cd6ec1b1b0846374e5fe934d1c37994e2261ba160cb11e32b2a6f8fe0243153915c5d7b8d894e7c407a978538aeae271dd75b581468e8c4851a96f8d2c150800c496a817632447c84e29a26c416f80cce650959df1cd15c4f8c2c455c1a142026f1dc7eb5276429bc453900b45c6586c83ae599e7289066810279a0beae5587c6708a7cde100ddb1db221ecdb82e91f466cd9717de937e96f9bdc51c1ff0b9ade91a17e4ca507e8db4669edac8d78b9bbd3b331658a8f22ae371b5ed8d52ea890b903549fdd109492df8d2575f5daad368e7b8f21ae93950399536c51f78aa27aa5b04a359658d5d3886ec8331d56836ecbab82e14dae613bb45031d21e422bd9c2a974522153425c817fad881023b45f68a5959334cf0b412d6608e8d2994d12fefcfcd338674ae517719b4cf1194b1a3dc9e41f0490cc20c35aa99d61c3b15d14ec3d54a8855b8be60e3c69ea6a83df88608e8cc7dc6ee51b429002d06554b38117e5f49fda9d999bb08da3e8540473d213b1aa482aff85305e5b6299e8384900ca171b7518cfbae500256b0fd2ab6be3ea3fbf0ee1bc39142e132eb60acdb569ba1a692614606ff213076514a61f10fdf03988c5f2537e14caf2d29d04d8ed6af7caccbc509830810b8d2bfb366d48facdc86c5bc7aeac4329d784dacfd8b2635c92981e4e57f41536d014cb720e141e743db83db882278ca1e14c2df2a1345b3c5ff881218bb0e63d56b2d4371195717e541acd69178a9c6005c3edaa92400d70751d94ca7a23ec5f40503cae16374086becbd0020457a9b1be1708d238c262ef612381916d369a4a8e08953916a5528d1c6023529b714aeb63a07dde36bfa11de6cf321ca4d053bef0a0f1e7b0b92eca465661acc846f16aa1c00a7562e7e4e4d24458d4c03db7c67738cba454a3efb39b4644500a74b99e8dd8a99e9b42a1e18fba70b31defc6d517322ae06336a894b1d0e0c39a1844d03108428d8e527d1e94bcedd8ceb3d191c9871d408a5b405688c7585a173a4d9efe804ac323a40dde33b8b7bff95bfe6d03aecfdbc386e6003f505129d38b53166a2dc56072be2cdf2d4b4122706c0e0379ca9eb2398c68c5d46d181041b4ece1c2ecf28ee0a467542d751b486f2686eea87703e62b84f1029b4e15b777af2d56508228ef601d3f7378d24c25c6f1e27cef81d39d09c468be33e98978c233c5fae01a5de773036c4d3436ff78670aa0d38d3493b73e0db0b3e5d05568b3a96c594a610ddb32e44713c8460bfc2e36ad4a34102cae09040875cff364697dc02072010c37754f15248a4378cff1dce59ccf02b04e1410dd0e87a18550ba8ac2c6d94b54c2a5c6adb764b4d862ffd9091b7d37a8781fddd1bcdab01cb9fc41fdf2821ab8b959308b5c7811f15c846bd8f2c975d41234b04950285b486c32511b9a122108020dda33e703cb48c1418210bb34ea51558eaa5b656ea152a6402b4fd8109dd01b42e0d110f644bf85ab101870c78f544f2f4303ba75bd9a3e903a90a1bf4c00b05d2545e6eb9742054d6be967a4bb29025401ff8bfb962dfad869e4e894d9092195fe5046d098efac06a462590de4437b8a83e3d089a2e794d57c88922b540836d547a609b0c92e2824d4a60207665f3ec1e265fdb613b514d0822f90734c1c864ec5f92fc693586bcd40b65ad3fe872200f45221b95e094e27f8bfb6258ec16cf65f20b80ffd99adb67ebd4acf7f7d412acd6e34736ac1e198c65329f69f4f10d7816cabc91aeb160e2babd5ff40435fe6d091121741674bbef7a02c4c49df5729e9415c3a3a0b4d66c127e81508d99c0da4bba1ef4fa574c29b277e39849459ce70b36c8dd2e9b8c8df5f233bdefc08056523805bda8a26d6b311944a87b6f56a6ae406e3bc3c76aec40071e21a04b279624fa57afea73606e4cf18fcff620400caf32a986a2285615b9f450fd21067d8872bf98291c4cfdc57778698743f06ed8e1a17901f7a5b4b8c4e8624c8a7088d908a8c8120fed8790bc7e9d6d8cc749ff79f71a0a78710e0b08d86bed01ccac0b3ed1ffe564bab4ea80110eab068a92cb40cf0fd9104eb034f57a29c8b55947d35015a704783bce0a3360195020c0d5876d4bf793ce3ba68334c7131e8d08ea56810bd59b566c73900f61b4908ff2b1f615ee2779ea790092a1abd88f72570fd5881e87f472fa579b803bb0014d83d42a19582b140b44f20c574397c0360652c9ab93cc7e6ff5a48616b2116918385197cb6a00611636444bbaa0dd9460efe0a394476fc267f717e968d300a80c44268e0569dc4de75f3120980b77f1a9093ac4ad083bd625b3b9b2015a9f2269c40e082cc753200252c35cd8ef25368b1e79752640051cffe028807600a6c4b98673e7786a447fbdfb25bb611d63b866009026ee5046486dbf2300acc4452ae79820675a6752816043e98f05d3dc3bda6ef6feab5113b3a351eecc7a3b52c24b67216fcde645523b0af3dc8c774e0947ad9a00ad308b6cc457d6e8528ff48c47f86d5b6bfca5804869c709e163d66ae89c9006f6450df077a1ee1170479200aac72250653042ecb829ed3cd05fa01ae28ede249b00e3faa90262058cf86996aa283bda1489570984492ce80058d1c52e7226936bf659891896d0644b7cfc070971046a51f0c000f8f3d8a479b1e0993bf3602b5a68ffbf0de3c4d1238544a78d0417b65f00f80007b1786cb3e03af5a18993b039c951f67918a5d8a455df99d9df40fc6819c0c7ab318d96a29b798fa44736a8518e641a43ae9da3ec02f72f4c1745ab70a460af115c29dd3cc4ff16e7ceac75f9f89af6d16fb9ffd001d415d65f9d953d86148666acf38b3743111debd5985d38fa088b241606b35c83610a6c5fb5d62bca1c65f6e47f1425eb91672aec85919ad1e648525019567f5daa610e1cdc8d9c84f6ec80518a1f6099861b30e643219576f8942c1ebccc33236e90a1190f8b3f9d119ff2e9e671fc29be973df160ea4b999ff64f37f1666516ba573f40438e1bdc77b2d3ce4068854d3c67218430c574e0cd23a6952691d1be9c793ce1b3ff60df013709ad881f1a7ae0f3beea5f38a160ebe62d893fb6da1cee2912dd4d74f8bfb40c2e168484fff340391460610cbca0467af823da1b3cd4acb408276ef056a1287c09e6b107b15c54fec46998593f6b23abf39798887d83d95340eb50b865ff45abc91b2d1d0940302d1646280323bf83dc896425472249a8188411f80336d5b6020398c02fc5bccdb1c3a852872f89d418981726888ae03608437ff2cf3ac3b4d109163d2b1d4171d643f12e8958d395b3fa6bbe9ee9eb7fb317312df0e01ee15e289c6916fe5139a6346e6c91a658a405689df75ee1e30ee48050378500022eaa78060f6000b2867843b063ecfdd7810ef77a1e9ccf4a61921a1da31cc3bc1cbdfe4cd45a087e873058227e8201303f2e59cb5e05cab7ed691e329006548c167a3e15b578ea057c3bc7b130552a268172530dbc1f1122fd9ead27f531b001e9005c2e2eae32634c7a811fce64c70401b4c10bb34dfac0637af43ba6f8d749986ccb0c3f23a39db4ead6e7ac48a4c616195f86dbf223d8bbc2571c1c52a450427509cf359d8440bd280f9ca1d56153a9cc56fb657b9880d37a1b89004e511848208cfebde5b07c2f3911fb272479f010bc27723a122628e117e0440cb019d4f82d730e41425d0e5e425a9457764efd6e81165c89033f1558ca48dc9c70df8fef95b286391473561fb6a9a21cb1a47757c15b2205586e2849b2c75eac28510eab1ef50d9e19f178beb2178fe0b5a55c3df20fac3295105f44742f0038686f933a18c0022b887ca6822b2b5afd7a0f333639a41f42375ff187c0344a2c861943940c6aa05b1f47f491d4d24b84dff4e0b885c6061a4dfef9a25a9b016523f6a2110a79694678a694936b0fb0e726531a13a7c767649f4821d50ae585613fc81121671df9044b13699a92bfb3ce46beddda4aee53444a7f95d6f945ce9306b792dc5630cf25eaebff9d190b7c9b26535158bc83613db5fe4842fa06fa2606713bc794fcf2b9b8504e70d089de6fd45e694e1e7b57547919b41e5ff5c955f9c8b77c1558609fc58919f3b2da95788739625a49f8f809c6a36d707ef014af719a927c87d14444c2951b157e0a405676dce1a6d37f00fbd7c402c3eee01efd05febe438d4d0bf5220a4abf622ca78ffd26c128d00e60b25916b5122b2fe9be3971ec5cba60bcd1eca6e4e2412de11a18e920089d0bb5b7944b9b457738ef385eb2e8c09560dd711355fbd3de40e967debbe34d8a4cc1a89e1db2076ed3d1a4de118b5e261cd70a9cca299f677055d11a0b4e606e54f88af0c33a3000058a6a4c51f70e88ec26387d71f6b8f776d7fb7d904fea8ace4f0f0c51e20767a62b1119e5bb95181456a048f8046c9da67e3a1e7e34f9f2130b74602e616b337a53db213ab68e7a7c9e068c188411dc76381453f4d498c9e04af9eb28c33a614137a783a192a0365583dc316f1c597e4b1b454448c2807634608f95cf40405ea742034bfa503f6f1c723bd2aeb4802d333b79fc8bbaad78d1854ae31d2f077963c744cc516a9c328357c10d37d0d29a24a86a5ce0733c92570491294609e27725f2ace6c0a1e5d7b317fddf1599f3cd810125a5d2244daa58b36ce58163687a58801da4ca77857193b5a5087528c4be135fe8d2f9e8d580f678426846aa9666e135b5e3ee6fbb32edb97fd5395141466eaf44831627ea6418ad50e62e26286dd3c81bd197df94d91a47b9a8f1ac49714ff1284001f7cfef3d9a6494fb28b598935e903af064121837e682368c621b40ddb66b456d493bf8e27660a1d2ce739d80b145544a0f4c85b9641bdb8642672e52f79c9d1b40408060d5cc17e8c767adfbd7e68703f6eac83bd75c71f62c45b3f082138e9a4c063224f5826fd0bfcf1f46e76fa51581ddc6af0bcac0b9acbb75a18e0ab806443c281956baeee5bd07c9a106865b2ac9e2285b11355562987187edfc39d02f2611ea9076ac270098f5d17c821d05738f58ece2722ff62ac52aa1aa21f2900056f3bf22c76805f0cf87d4ecc113afc7463b7e811f96f2a14e3c21c394cafed4279d337f2271d51837deee30751d3a7c0331870954fa34eaa4b0c85b537d8b35504d13e3d43fe02f3fd4803195d56abe700ca7526f97d6c0302449e2f566b847fa9d8c7e56640e851e2aa5526d8b63bdac3c7bd7ec97c5c44f93634172bcf1ac023531a9fa894b4f9552654d923de6c2b920f87fb2adeabde19429ef39bc4a7c69b3a701a52406a88a7bc4bc504803121949f03bb7d68c08558869f63619451f5cc2dce05dd184eee4087b7a0820c8194119eed224c0c6208638ca97f4f9d7d77c050914e33e60d189f5504f882528525e2f00cb9e21a0b2f676ef500b378d3232580421c9f1f3c4c2cda87667c98fa8b5aa50a19777e1682a8acafd343686ce1fe5dfa3e87ef4e687f03a51459ca25f4e6e48212df3712599396028610f9373ab396a1899c982102d5cc60bdc482abb2a89c4fa10f3cf7486b686d3a03ca1cbb160e7ccf1866fa2d1abbb5db0f0c32d8fca56d8f6433a2e622ea153892344cc95dc7fe043311375bc8ecf54eb0721aeeafe66d56c33f6ad3f38738bf7f7c8a0e59b4b27d06d4463831f86d113b48232ecb6e25592e224cc04787248eb1366b72ffb92ba2d33cf778900407238f23abfa8b47bc11877cccbe93d1da44b90b8e13acfed3671ad26df9e6d9cab7a9decdee6d1712dda068d8643b48f1f8af35e2197b7566c57b2aedb21c1b41c5fe80350693a5388175772de9be485fbdeaf923a80526fede2f41e9f1748369712b69342a5b87a3e9312b61b5c0ccd1535e371beb68e51fcfa6e40e1461bba34355bcbb06d02c8c6fa16eadcc1a6d30c0fa714d1f4612f4459880856cfc1f6783fedca6c530f0c395fdd8c0d07cfb6e6679cf2e6c26dd233dc590a1556c2a3a462bba35bd152a228a36605871016f251356a016dfd6758e55a3c63d1e458aa1d1110fbd338a607e331ccaad834b78d8b4da26f379b7d5dfac810cbb85c2e872153fe77ec2040e488dc62d873c3dc1aa21eb4e4ad9db7047633e65eea60944506c5414209eb6fd58c0f12c40c51713c34f2b0de9ec25c030444a912dd154fa0ecbf1ac529fbba8ed5f1b87965adc368abb5753092a8444ed49a8a4489b1fd4b31b9d42747c5aa4561d03fa26d18921163a25d1ad8ec06379af29c590885f2c4847c8994c05c51c388a1dc7350189bb714eef34eca5ae5556d2bca55836a64974791155fa4116c1ab0276aa600cb0f8161f2d0d2778182a9ff15d58579f602fc1c6fb8b48881b0fbe22124a5211087e9a90008bc8f984afeab6c5df52c5eb442b8061ab444807a1854bdb153bdf7bd1cc8e0056f5c4692a5e9e4a4c7d34247e18748d7f0d479131c5042506a1f948d2b2d4a9cf3fe0f63074ada28ad140d00366ca60f950615e97313007c310a7d97bba29da74fd5f0f72b83448c1554e3793f7ca843349ee866e44ce6db4641979fe5556a3c04d2bfaa39dd3261e3af3298b0ed06c48dbd575b835d0a0f3068d3fcef1c375e41b973adc2e361b386a81ac535378043621d01229ad6027ef5f72521eee7a180601276dea207c22d80e158265e4ea708a09a46509484b86b732525ffb052c09de2011c51e93509cb146243ade571a5d1b0abd37884fbc6ff24ad5d6f7936c6d1d46f0e8d702b54a8fc60ab73a25f9fed857176604d848a1c0db628fcde406dff73316aef530b9371c517105bf2f5b644d70f698b880a19209c37143411cce1563ccc97e04f50e61ff6905dd522ef074e17f336dff6c22945b97c1ee8929831b227a5d610dffafb8fb5fd7c0426816a4cbb0076368317bacc8503bde07b8f056e30698ef93a5b286f1c89f5c9dcfaf2a72f4417b3db4cdb38589aebe00c0115c98fba559a3a8ca668728c885cd1e50abd4496a08a2c4abfe405154c3caa2c7a5a7709ad61c08867c3b1c6f328f1f32c324bd65aeb080b796e7634f1a4565202f2d255758153628e75e55f2d0117ac89085e3ee8188c31082c7fc8c50580081c7c56676fff903bf57182278a78756a6663e5de3f1b3697b972291e5ec416fb3db098fc7a2b8f57293bbebde3edbfa6b65fe09df6ff25de9eef3f6addc24aae7ef31d555623dd7787310d6210a9a4fea13038888daf18c865889d2ba4ae646c2f6abc4bce98898cd841f3cf9ced800d23c3bc50318da60fea96621b6c4d895bfd81c6394f3801cc1260cfad9001896f4669f83a8e32f37b14f36e4df9e8216b6b091bbf50a01f9d003373064138c50142e088e0aede44f4973c2267a07b2ea3339931a1473400aa01e495edd962b50284f57845c17dd2fbc133e799263e380edf0a3e7aa17c1c67d4a69e67360a76a81d4839ee71796708cc62ae4ae02118e12cb0a02ea04c0101ecc91aa7fdf25238e607c1c26cb4aace3089bc40dc7ddafe4c232731947b794421c1baa23ac148adefb274de54a0cb9f13f1f1c036a486ebc1b5384f138bbc21b1ec965eb91ea0228bd48a0216e2634d364bb92e8e7da93b0d0e0ff787dd9fdea9712d44fe440ae36252a51573e8744a5fb75e9a01489c556ec3d18adeebcff7d8f904871562cabc539e17d36950c13d569eb4a4a60506ace67c350bafc98a325c823000a29dc440586dce17eec8aae1894c052ac4dbca42a62886d941d0f56e91046f3b2ece0e0ea459c669728ff082d2c861c60cc57e0679d21a4c3f206a4dfa0e8a440aa55b283a5a899b696331e461a22b123902428ad60375c58c075899930f1c95446a8e43544b06dac8b785bafa365a38937ae837f04537e5b453e10c8a5f007681902373493c0562b97c6b0bbab5456334d61eb0c0b4a63a2a9e1e116429ebfe1d1f741cc98323e7bb97035cf84033e0025a3b40f0171079565161550facd14f02f136e1f272412603d512df4ff17b2fc4d24d3a567d0007b0101790c1e493319ffe58025e227f47022320924575f288ca714dc86ed15674a461522d3fa623d3dc04b7438a960f6fb198b52908ed06aa218da844ae6109f4f4cb1b7232454a21664502d080c015c48cd4fb033ad8d3580d0e5cf7e8c33db0ef917f2ff65833f1f063f87f2ad6dd78e385c491d8613eb691b39fbe40095d4a6275ee39e01af9ba3728f94db8b7d89f6e62d2ebba2de90b1f407ade4ec82bdcb16b050bdb14b837796bd292948eee13814b7b0b97b02a20452c62f4d20b54e674e45f627833239cd3914a0b372c8282abccf94842694f5cafbf2306bb1b1931c1c5a1e2bead6423fa0fa906ef3a25a0a762e20ce50fc3fd2dfb44f56f4a5bc360bc888dc8f1694824c3860b5a7c94c3415d032a60a61ae16bd6a3d59329759f4614264fc00763521eb24388a1ed226596ff49da4b5ba2481383dbc745ddb9b23ad12b3db25d7e9ac2732a655b4224a14299314598d273790e13a4adb94928023ff69d31a58e3f3b651c81ef302a84e95458ba76ca51d2b2074ac02b85b9701e0d5d608608da65ea9780599c3affd47da20fe1e584a7a5930bb9b930f1ccc8636afab46b3a86eb8ea9ac732a41c9b4b6aa4d4ee26e7fa24c502379a0ac31597e32eed889c81a38cbf38b64f9e2fab77be2c69491ecb874f55f13448076733ff1be99d081432fcbdb923f8e8dffe8ea2341680699d3070a80a9ace4b5f75640f739f0cf35243e4637b0f468150331f1e7a981c1624a409b9ecc215683a6337c93b59919a4d313ed532dc6f969c5c512f10764c2897ebd48adbe51d2c472a0b8c2cce564055f42cef4bd0ca7c31f7b8cc89bff48cfc45428eea4db6c8aa6682dea93aaf7dfaafb83344a3613946e02348c0e52b94a67f15b56429426064971a57abb054122b8e22c4e1aa0c575571b57aeaa4c7f2496ac2d5aacb4fd117d16ee6dc0e9caf5c18ce46cf4d981df3e2d28cd00872cba388e38db0563414c0873a6b4acf12d14adc961b655ca4d5386d9fab2fc8b072886244d25dbc48d4289dc0b461af1838990f277e495a390adcd44fc9d368e9f9e74742822a16aa8a9ef0ffd799475fdb24657a0fa20206008bfcca3712142db83346f0f4d414818b451b5a0db6b684cfec91cdf9d14b902b8253fdd5017e7378819ac82e76fc8077ad8f4b83c38aea13634ba39a394dd794f616a98c41b3574dc1b7008283ad05cd6534df76cba43be3ef6bb9de5a40397fb2afd1969ae9989426d6e096ca9eb6e806f5177a7a7e9a1d3b89bf8733c5e6651b6ad208fe0dc795a56f566cd9efde076d2771a0830a422e4a8f08981393b1fafd221b1a9dcc8b8c6c77f7d4c3cd13a43935b9b2b41cc40855f705f27a7edcec763017efbd99f0319334d59a4ce7e1d0f65a1f67a2e358f62ba2c78b2e082b2c39c34c2cdf0a3cbb84c5de6fe7ca34b9f6f4fc547afa8f1a64c6e0ebf46a1457fb78e454be5d72125d2c951c69e552dd7e6df0ddd1fbfcda0860065c5bd9081e826922ce268a5bf03fa90fe107fb02d2babb183295626faca2b95801ce5a3a1118befacc436058dcf1736d492c665f0430bb1a3c5a0369ecc3d8e1482858431f8d917840cb66b131375e9a9e05293eef68d992854be5662d0bc76ca84593827cd27cd137d8e749f3b112d7b6b14a225c083b6af646645d15f79dc38298ad92260415bffc63a8289f1175783120b9ab33731ce8f149f16c31c58c440575fcf2298e80e085f400106d518df17a20b0e742f3ebcc7b010f499bf33c13137feabb92e701a9e658e29c1da44ae7f9deedbedd8b669c7b5efba8068555dc647cb8467ae6f403219c1a6c32d86fd90c10536f5967463c454da2ee48d51298b9bfcfea993b2c153d8c7b5c48d75a9a356f85c7d646f243a5d65cb13733c05e2382803fccd3ba4b53971740a60c53b25adfeb4042a6a9cb0db4f7dabc626cf77e13e7f07d60fb6bcbaa075d06df3bc486dedd4b024b63d5a30605d3500ff9335bb558da9b4edfd868c0de37f6f41dd3394068b543f03b92c956acc6997d0ab77bce6555b86ecd3f03ca2e10ab72d3a46cdb332e22747c163d07e474c1f4815f1254e1ebdf2346aa30f8d42f3b52e025532d217b2cf73331a62354842c9eed401d2bf8aa9dec8af26fd25249168ad330cf76a6887ab0aa9a45c2764b90751470f885e4baef9ee29cd3e08cfcdc5f4d598ad9688be43a0dbd24a5b1549661ce052ee401b382a8427eefafa72cd56cb44772af2bc764297c2ac1cd46db2717bdcc4c6b1769972f6baddac3e36257091624df6c1ec2c16c4e94459539d3113b60407fc7c695189120218a027a4a0d86aef1b26a0f220b44183e639810cc0d80ce7dd2be6fdf90a732e8039d107aa0ede552443061796b713290374047ab9aefb675908673e8ae7d74817cd4bf4b8a9cdba750348b41141b03b4ae0b77b1a6433974f1a6ba8ab625d43a1425b40cbb5d25da10ceaf8e236d5b8fc30caaa7bee4bd0374ea0c62fb17229043a14beaeb4dab90ff9cbf6a423e0d100e573d3a45baafc79dbac12592decd2c0b59396ccfd0eb87c31e6f1104e29a02f225cf50f1e9b887abbbbaf8f430b225480b5feed6a252f72adae03e7d758c29255f9b2f81f0c5636c17b300c3e8ebe53a780849893f07afe4ec8f31ede2c61dddec162571f97558450f3ea463f8a474840c62708868c6725a10224589c640ddd5d781c88b291e3bd642aa31fc4d1c3184b116e495c8eafffeba2ef6ed70b411996bc2f262dfeaae8f537713c66fec8ec8c0221ac26299f9f5632770c51599fe58a52afb4a98ffc64167e3e0efa836788aa8865b14a5e3e4054dc7d34b6dc5823091b2f792f3384011a6319aeb16897875f011ab69d234c32419c87036ed2f4d6195d0e22e6c238f43b9770af290abe1ec511182148aca2d5232abe59b06e71c16e856970ac2ded1a32ceb458c8f05e2ac2af518460dd7c4efd0c1efa57a2bb3779912bf40ef849e1821685b5549cda97667e7572fe8d33a1ad2f74a61398cb11ae118c972cbeed4504454dbe8070c1d7473f8e1aeed22e6108004f2cc00b8780269dfab42a5327d22e6e6fd5d4b28613eb8888127145ba05967c76187c986156454d7df1e2807525e7c60164aec76415948000d82483954ac12383b0de84ccca9ecf2e0eda2295d4feecd6f486dac7a0254ec30b09e1357b4b70d34c4833c4f903243f9e78621dcac6040a63c21b5320e80f54282a8c5a27392c5fad31f400bef11920846e1bbfbae6c611303ceb82970857c2d61587b2ad5ef0c4ce59713207f71cda500262ec790b915ed710ae3cb1af3639a33b713091fcd34154519c4461ef7d0945d3ebdaf465eb860d303a278843df7c956765a83955aef2a7be8236341b6a47a053782f3f5bb2d6d01edd148d0b3d2ff6db08394291fefa602664c9387c8f7355ad06d494ace2e30a4f101fe5f9a7a33d46c5a858316f96267c580267af09230f3f2464a4dd8cbf5b2889f36bb6a36073a46c3172c868606302d172a866cf344808bcf1396dcd4ad44d324227ac792b0a8e22648aae28c2c0f0f5a7e90302bb077ef05b4f89fcbd9bec664cd3ab69fcb1c84912bd4a514e8bb54f91f41f129cadbfdd5781d18d1e78b1e07019671dfc79be4207841d05aab6bd164cddbd23eae8a11a9755d9130718d452acf28b4347610abb97b6cd8655b16428dcf9bcf66395a36f3432c552fa457bd622d2247d3c0dc02de93b0539076d302ce8df3aa69b85ed0cb919fa74d59e6c303738435a1805ecf68082bca96eda155134cb08a50eb08cccbdae22102210c5dbd52e9359e8933fe15db9fc0a96b7fbd89e0c13419200a31a8315f160f6f2caf188593138d51a97a707a768827ff1436d043b18b9ac8112c334c61ae15268e0ccd2e5bb1726eb51adb1993e649ebb2f893d0dacebd03e4fb1dd3e3138719e872578fa24f3793bfa5ae7412a818058e7d89e56987af4ddf114778c5a603f828ffa2ed1f217c2c3335b4c82be4a9e18380923291552c7cbbcf62833a52c0da21c55dab3ac257e755000fe9ac82f83e9946a73433abd2b3770490804ea16debac5fc103ea8887e23b4a3e134057014d875bfbca1b7f1ad7ff9b52c1292678e8fa7b447c519c37e73a637de679804ab0249c1c51f752fd83b1f501321780c9305def4f5113c0258494f29a400c01f1c515d4b94385b6a7528ceafd7bd16d1f35f35e8310fab7884a0864df6ab3645344165e77eb711bb2171135bfc8e61c745a032dc22fa0f064b8f98312454677ffbc693dd10b8b5cf90f8b5eee8d305e72c789dc15bb701b08494e75d52236804fbf746ee071d6c3ff5775d43db994e1b915ee76ad5b257e35faa962fbdbe214a5b5461b846c932bc891efbfe01cc3e41c838912c7ada3852e787f758f3cbc8e9788375c3a8ed42f207a6d64359036c9f45d6dde291fed17136cb50197a533a9c2edbc3901898cd197f7cf59c8f729560eb2e53b4295601a00114b7297c0e4825f69c500859130d9d43164f9cb334497ee5d870e8c47d7a76405c71c384277d08a47c37737bf676c08dc76ef23966b24b7fa1fe3354db651b905bdac1957299c51a99344ab1324d33761d00e337e70c382d0d2319f5705d5c15f8aef9557ea2172799363b123bb780466aaa35eb1dcb2d832ee052fd411313dc413fdb4d2f2b205ed8489e29d0333f27854a1a4f654314a6c68eeaed51a35a9bbd6730c2b51938a202dea3c3d1c3237f0dfcdf65411afb0ebc9ecb295bf54f1752f9e5c215a2ae550e2b070f93ad87be9a0c92faf8455f4fe949873a6e039f5df62db535e699b2f4104e28638db3b61eb133deed8519ca368e8146414c586c28d85dae0e18e7e8437a61ae978d81d28ecbd21f0b2e28f6a5877d3b8b8f7ef25a08b86cb0e3402664aa79321f070ba39acca403ea36ee28cf696298308f9648f2f0786272c9e52445f2ab069c9f18aaf8df249393a446ae88fee71a636875d167ffcc13ff58eacb8bf8b648bdc25f446d467f0e147116fe0d63c5538230cff037785d11400068e862598fd3c0a1048ba674c92e8a2a9c8da6ef0593a76f53c05c087113b4735a9e5de5864ab930c77dd51adc18718ec8f400fb65bb11c176431122449fa1f5f8adb0864c4bcd0ce9f0b5f91c41ec3b2cd250b795501568be40269e38bc479b94e2a8efb5ab72ad61f7efdbccdd55ee4b463a573e72b1d1f9af3ab77f3054c95b472e4ebffd7c92555b528b35f302540107eb2384d26908d9f5063e101cdf7d5da6fcb5470961e41003287335f381644373cce10bd5d28d35fb938c98fd70c29e6d3100f3363ff66427f75b27f6959f55dc0dd401ad02e9553daac0048c4a3d1fd752c83b9a028a2f058a0192da2225e80c6a0020cb48f777b6cfd9577aeaa9e2d898c9358e06d2929ad28b1546cd0ca12074da1830684afd877f1a08a587a418456cd1fa1a9808406b284f612135acf7f42e3b01510251da919089d0a6d849c5d06ca51eaefd941bf509b48387f87761c955acba5cd302000631f9dda2f02cfdc108f4cf56f7a3688eda63923564db572bede5f6a5dedd016929b386b6caa3f6104ea3021532570b1a4c2bac78c54c51b3b6856a0c29abad375812c21bc5384e7687f26f57fa5527c04dfa04974d29c51d4b1d6005ba92d587338542dadbb74b1ccc0e752699fe2463d96408a1eb6848da734038a5496400293f031de1c7344e10e123199d0c41f3783a8ba5e152ae2d822553ff1cf54c77a35c3697af3b8c5264918511c2a77a75f9e7f72f3d17e274a1b53e1013aa5ce69ab40122a9ee0de9853bf91324d362f552f29014386c4cb385228eb86b238b98f1be9f5d3240817b64e817f2c3fcd6819b17d59b106775091124a4a20228a14190a8e2933d9c9238d01fe1a8e269422c2bf06a5ec928462c97cdd0a24eb49e1f40f0d803c29568871be7f27a5a1c504b9a233934b84972125be1952389bb7cf412c6bec7576fb49d2fd9af815df29c33da92204528c79f3db6644f24b2079f4deec57982a764acf1e1d4f23273dc5b6fcf567596141c4625388c164f4d3750aa0f0574c2f40dd37bbcba27c9d4c2eb0cf021eff7659f7d1091fc741c8e04f6b9975d416a2fee733466eb44ac531833fec085f165396208f40b569eecdc5d10b046eac7c79cda19629af1a008476782a4947cdcd19fc952988de1f4945bfc4d8e4a19d28b15265f09fcb652e0c25c6a2edb74a06c498ff238001552c9bdf2d69c3ffd6a90258549a9f8e940bdf5b836f22d73f54acdddeaf31a71da9e183888e98cc32f8e7363a0c41478fb86404b87fa4907b937803f99b1993d20cfe03b76b6bc677b6e5502e59b911ae078573f7213f866f65fdfc054343c21c6c319957fd7d085cd032f933cdb8f1a8bb826c8eb1093072fabbf226f43d28263f223551d43ab6f7f8e87e31ff876f4b0ce60f006c34f855fadd92efdda611a9fd8bec14de637b529f7279163827e59d9bf6dea24d78a37e7d9c74c5a2c26d4dc0d64e6109947112061d2920193255b449f9849a18d7b65bbacaa465865e707152fdc0b8a4a04c27cfd3313ecc01f2ff36586ef05070cc12f61fb3a31b1c7c6fa336e9bc18888fb0a8b6e263076cb3b806898f3962ce11ec51ff8f5c8c3af99cdc84548534cd7a1057ec5f42f2ebf63f3cc16bccde923a0814816cf0fa5045fb3428fd2abe2f49759ecd9ca828552cee1b38b8b7f0e2f9dd3813b1b6c0942bd6b454c3a22a2d116005a0ff46f63cc0c5ca52b9df872fa5dbf289d2dc5bf031b8ffa800c5d7945b0f64ccf798b062f7b2bfb669a13928fc29120dc0736184d28b5055199aa1284935cf818ed3cb1b559a73d857c644997ec0debc4fed523161be341ec8bedb43fcc0dc3fe7e2e304d35ec5e35b1d3c0676cd55b25984446fa30cc769798104848d68c6f67274c9cd659836e8c78a3be8b3858bdce09a47916c63c61bb0324e30cc8d0384f7a58b4c9fd4180a9b1cbf3466239a39f3bb475790d51a3bc818506ed6a5b1f0a4779b1608d4ebcce1c43ab25754f7a946a6bb28770e95f693d720ac9d222a8b7e17c1432d6ef8ca5c8f0d8e0e4847e9b6ed79e916f12147cb4e53791b4aab1af7668b7616c6610f88f4c98709c316007ce8770c4ddce5eee867cba5ccb98857e0fa4d8ce0feefc14cfb4407e1396e5cfa9b5907472f4092844b0eddf58d5449c274e46e0a902ee5de8311f79f30b2598e9bc943805509e4cfbb3b1098fb7e3c3db89d45d3efec47b2f952b5f50c84a30e2dbd56b605ad6afea38009f5b56bc3e435e70a09e6c56f050a3ebbdca0be85497a6945566235f6c481c5795728256a8d1adc8ec43f943e9c3fc169088c48c47251132aa3a64c091e16cc4e3728d382086a5cbdadca8c01646500209efaa17a7f9fc1a00fbbcd23946c978505e72be7a9b0b4a09422c12393c196a4110d3463aa893ada51060cf6c82625ee07915abe1c280b41e1cd01128b4d34665b6ff3f7f03d93db956818f14cbacc919f6f653907e8763a966c46eb1a124b9c248beb03c5af9b736e8bc5430d07324932b23980307415445c634394cfbe896cdf5f1128761f926c852e999122d18cace0072dc82d97d14ea5b9476bd3c273ad63a99f0a25d3fb1ca5335a8970104ada964bc40206ada5388d15c5a73f92b41952a45824079708fe0823932746b4041d2b34a8255441e3df897d1fae722c0bf0483f73d5185730df9fa388c650c04b4f110cd52d21648e32ea4a204351bc2ecd196e5d5e8ef682b79b6b3032f5579d4048be749d089fc5772e0de49a768148b156469bfe02255e05797728208cc79aacd4e8a982008a1b156f80645f26e0e04ced996667cd4659aee76caa317378812edc87ba6bb5342fc10a68ddf102f6fc562b964895f3e971be7ef8fbf42bd65a2316b10c9bb90a8b3dd011f6d9ebb1d91d3988d6981195bbc6b9afc88728ad2c01d6b9a1a0fa119ec417de788cafefe5ea945367b2a239dc4de2c855353e96dc191b5728e1c695638cf00a580029707c063a5ed79940a022e15631d78efb0077b41818980b02292d6472ad3c05fe636356f01ac337777ebe7e87256823047b2b63b1e1cb32172e03d6a87faba955188f7a38ed47be44346e1a3d30ad5f491965947e7d72f0bba059dbba1f93eb920220bdc2cfb6a3e93198e6a2c355345e0ff3330b192aff9e3e853811acb67ea3a8d096ea35e88076a5564a6ad9f5d6772307f4695f528214501d234484918a4e5b222ba15e57b29754f49c2e722c9a7a432a3e9fc985e9fe8b406249466e649feb93681a13de4edd70ab0492bb4e12d6e410cbc6566fbd5d2b2bdd23e883c21237fab4bac27fab8b9b568f31ff8b15c1de1592790c6d6643d3581fe6c81354d6d6cb497a9cdd90cb8eadb0b65abc5d701d0a811426c218db2afc411fec4a0b000436fb02b13b9529bf3adb97f6981b30fa6f38abe9ae4bb46d84acc476b2e83b46b91718ebf7d52cfbf30000e3fcfc73d246f0989bcbbd5b7d3256c167ca0a2b6f72663cdba58e39cb00fcf8899ac20bfd7f8445e229fb3add7eb381ce596c347c87aa4b97d1ed66fd5c96238f864d7019f4c20aeb7411ea5263b282645d58319815b96ad97863bf345213add37c2f9623a8097cde129c8ff4ab71d9fe1b8a0d06358bba266aa28d5a56c54d81a3549a0ef79a288021b8c0a287547b762b2ec72c2aae733643575cb96992c04e28f5c6e9800fde46c719b655832d6bedafb82da5602f5a834f65367c0f8279cca8fe897951c9308756129528f9a944114df7ab6be4bfc0b69865239c8fa332af53fb906a1fe268f404005c08e90122485c37835fd8afa7ae8772f26db17be81c3098818306197cc0e041e778bbf4d3fed8a2058468df07f22d640e688071003206468ae40641283d670f9ff349e8e489be351759435008cf2d9d7759b7e7f6972f113c9fb06a40e62981822fcf70935e059282a2cd5a469f31da1a08e110f43c48fceaffa91ac8beb37b27d82de80d0e27cc832c34170dd4c25cc82e436c0a2bc88f7015dba9164ec24900dc7fb28770e0912440de7dda8bf4708844005c7cde19249fb97356f366bf12b48718e0c01853dd99516af4655cdbc36c45c88ede4573948c3a8c20709f2192484395794b96470e63338fba2b1b14e1fccdd4ecd5c004fec8b404e109acc770ff7fc6a04283951f77d364538d087aa5c5b4e9031c7ba0243122fc528319671753e926ca0b1e254e6d0728d61ee26782c8fddfca19cd3b7d4b181b071f1e6214f0e2758fe3529e899af85b67595714590e217b12798833cc8df24e3f6b8760d080c0287f2f66327b6cc487cbe277014f410ee12eca2d67493f59599f28a35793de9b527ede0c3275c4e3600182e821dca594da2dc2ccc80bfe76ba27b2f32d256195c0a043c0af66905742ef42b132f8d1667ea1647f3392b6bf7d5d1b32648e38be652fd0fc25e7200714c817c40cc31848886375a79afbde3a79b572f8267c90dbd8594e27329d1f240eeaffd588b380a3de29a56657657bb011fc55b97801ea919de502b2e05898cb1b121bb85c04d89876bdac473c74f0590d3956a6f319ca82775ad88d69763d3ca87be68a916ead8a624eb5033f3301903ae4118795089d40840501e8fa29c01ccf4b3218055c3632000da8516e39f823c824f9935881a1240ad9ba4b6fce7509a0231fb26b032509d04342ed9fe73de813ebb2730e9f2182b3261300873dfd43412f584f0143963f8a73f407320d73299b5ceed9998965bfa3c8399a951878eed8f1880d7b3ad89a1c34f51858c00ee8c4f6a1f7308f575726e3d823c8ad1cd1e168028ac419b89497c34f280790f7923f3e0fbc5184b790f88b3fb646b87289206be980f5b8bb63d35142b6982c7fa20f7ab4e34195e011a0113453b919e17a6e180694798c4804e0fe93fc2c7d20161da05e0c11d41c882000d71f9c144f99197ab2906c7c9f1d264aa9aa924eb419b465ce8c73c5ecab043051ecea9dc147c7b2677b86f9f7f9623d86623a4cb36ea9f40556b6c62983a548eafe8d72d88942faaa90d469680e6480a53c3fc40a160e00936c7e214378b7b2c4c2e22e7410fe5296b09d05e0dde7c83186e85d86cf86561c23443dcf393bb7a74235561a856819df134ab8fa2b53943ab9f7db88bea0c5752ea1cfed69f11970a02d37fe40cef0eeca80ef4ca6f0906964bbbc841d9762c977851f7461fad7d9dbe12b6646244ace47e39f1a40f592db4b6f1ffc0cf656567c4bae2383600c754f0aea506d158c6629d616f37f267baf7680649a9c54a5663a26370c5257ed75d5032f9ca3a2c0b3c9d90cfca4bc74c2108de6d10c5d55da6e52f2b52d6e9f375c23b23567a87a03a951f58f9d696475922f1ec5329e07ad31a9d7a6ce6aaad7ca423036e74b13c517cff3608494401c0a48378a4fe7132eedf7ea6c838cf2dec7638c200d7f8bc3be4082c49819f47708b3b4072c7aa17522a30e1d5ac6f6a3605ddc1f09dd09f8806006a8d3cf8fbc50b73eb91d632eee4ebc2a28bd2e1265d58076530c805d0368ac07aa756db5a57212d5a54bf03a9d9a60cd644d6a984c9e89e7f8ed588288dc4717d05ab78f2f8fcb01cd8b55206447654a5e3ec1b72fd4dc8699d14375fe0da098a9622c0160efb78200ff98020bf38a3229b607fa4a97da6af58420cb0d102ec755f2be131bf6ea0ae2323cf654a0926a648802f5110ceb619f42c967b3ec53026f28b6c16f69282d4a8208a9aac607bc9cc008d479ff094716d1b1b824f4f83521dc588bda7e0affc3823fae1249fc747ee984b75d84872df7788600462f6f1710a746dfd48f103d7bcccea8e637dd253e0b651decedbf793403dcb7f445a9af7ee0873cc3f4a5f39d7c89e2fec0ee9ae0d2fee162abb22ac634fdd456826a1757e189022ac906d8d5a3de48b784f7330cb1a902a8436c471768b18ffea909ebffd92838552f7ebd505c9c4111786c43b5fe1f2dc8c1aa9dbd7d1adbb5c2419e3f10ce479d05d040791a16bae4915ff0e0cf7e68e28b51d7815189064efd0fe0a8a1e5f4295a50f2ca7d303288c6094931327423ec519d9a57dbf07b83540ad7a60141481110b460728d938ee308d9f6cd361ab83d7245e5efd20ab3eafe8be76c63ecff0722c2af78c6d404f97f74554da1bf21e661eaf74a567596a7f6edc3e2d79cb14672805864e4ae3513e9673d867ea673aae0306979e13b7cf91c3f27301fde3629441b9c62fa03e672976b52f12c47f6b83ca8a4006b8f9f18b991ed6590dfa0ff38a44adaed6c6394ce00d19c1b7badc8276d052c4328e318e1b8a2ba7016e16823ecb082f0d93ff8e5f37488605824403e5410d8e3ec5297de4658c2fa189752eabe7ba650e4bb9b46f1ba8c07091b11752ece618b870d70c3b33c26a142398380189e170e25c666502311c2fc5696a7ab27c09d10287eed50042f30f8f24752e3790233a2fb1af5663c717e616f97527f8840cffc4d32625fcedfaad2aecda0b8a032ce1bba2e27696d9998a18ea26c29ef2a3d5f6938164bc3090e683b34014f4f3fb53d0ec904a84726816b9900d00fccabb90968ab08d37044259b7f5b1a7cbfee85717183c059aadb4e1270184980d06a5bd5d1852dc74a16d642bb0bade57b01392a7959297edbc627797723755783db7f85966b2f838758bc2205f570072aa72429a955eabcada518d86e64a748698da11ae713f86ced659e69879e21b96ac1528e1ff59be4464d4d13a8ac42e4cb981595b8e54cf8783961603e6cc60e52f1557d46b78752b5bef8628c43a476ef0cf87930d63e33702e43e50d0c8eb55e9a79b033a03affc4a1e4059a6ab371770d87c533763d4b98bbdb0cf8d0046dbedd0fb4b31b06a11106ab752b4405e373604e54c1f87ad81054643ccee25d64f7fc75d08acf681dd77cb41ee79cbe8cdf988e43cf41ca17573f58ab4d1fdecbdb8ab7c8e212cec136f492efd1acb2bede8e263a450f16b30cc1ed69b9becb960443c963ea5343109fa0e069e24048f5751eaaae7437b6a31a7d1a4bfcd9a7756ffd23f58a11ccff4b8e603453593a925ff53c5a6a493537a16e24f050f662919ffbac08a0a7b8d5bf9821b4d89e420aa376f0ba1606c98bcb2a44c2229705782fc6c5bc8bf922de75ba9844c0108a057817e3a2bc1401d45a5c90a43002314c9bc5ecc98d6a7920ac92782fe645860f9f77e29368e1487f1c51728358d8c01e56c5b72998bd76f6f15679c99058faebfc8d87a7a7778481ca0162ca9a031a84e01b5ba4de5cbea0d922a9c5c39996d3032e78e2d062cc8887d4ecc673d79a5a3f949b07765248f45144cf1875a8038bf5d8453456bc4a4a627e98be5bded0309901de61df47d299a9e275b3cd86905973fee20edceaa5d3e0c3216d86a1e4c1c5cfd2dc184f01c27f08c7d30dce6a437ee3dc067cb7bda01ef4d01193694d440bc193da495875e10accb601baef6fa150bd96798441d90aecbd2fa32748c7561d471dd07afecd2e12136698d28bb7346222a9bc791f03f263ebf791c787e4660639b666f20aee95c6a2fcb1081eeb3bf41a77749a23ac99e387a694a2ea5492a8e5cebdfe8bd5a16d6b9e5dd04767558aeb03a8eb7a17f4a35df2f1413736dfcdb3f31f46144319699baf344541b55cc5e3d8d3b2a7cc37a486136b2aba208a0ef3e5763f6252d454c99d1746119a858b6e536cf13986fc08010c91116bf9ca6faec40430c7aa49ceabff90face9a7eaaf6447755bf1627dc653e3e82492c645bde8ff2b7373101f40883e83ec645eca85ae8e3c83fec20e480e1088cf0555454b5ea99973d3fab233966b7d7daf161004cd72941f3b632d6dac9d05a4e3c4aa6b99f3114451e3e67940076ab7b8688141b68702d2939b6ea77b0e7c6346d59dded5eb7e6bc9702212807f5022df654441ac271c583182d19aae92236adf236cb3b24c2483edf29af5369fab16afba542a948b4f9be86cded76a214d17cf8bfdbad06138d11050afdf48f1599ff86b236dfb5bc6ba12593161a9d2031a21a91b750e22aca860b4da756d9eca29c54b08e095cb69abd9b25f3183532bf4d7d6fc46cdd3630a2c5359d08ebbe67815a701f6904403addc41c8c790b4548c6ab32668ab40767bb1e300f6b8424484e2494ca9f209fc68227655e9584ccf429c1bcfd6d013c21bbabb812ce88eca0c064b7161bbe9c254050a7e56321f538d9f994864feeeac64de83fc8e78dd94b96fffc15f890539a980dc77048c9871162c39048b42f656b9fc642198f755226f0087f899a5ad91b1bbf6311b7bbfc88dc728f39a9e333c7c67bd4f5591d1c01675ef9ab482b967cc2121f049b4eaf04e7bafdf7f045576e420c02fc85d38aae04e5678bc3b7528f7054a8b66662e4ec436e21b9256d5b72b00c18b56afbdad805367864ae41b1ec74314b772b8956ca4bd840998ba7ee0ffd237063f04db9c958a2eeac486dd29f1c65fc021838a6052211ca37e482e41cc9b2bf8e2411cd54b87c51dd90d6cce9d1635181128645ef7aa3a99710bb577d59cb140a61bdcd92fefdc3b0ca24318083ba10184579a74856a6a0b9a5f98a773abb1f0822852d234a82efe02d346b7dd234d5c992dc1237af209e1cdb96cd3838f3cd69f17e43658c84a367bf809cb4bc1185cb0253b2553154e38f22eba617f576301ec41e5c6cad2d78717c6c4973eed6198362e2a58343b270a10b4dfa5e28012c5df7c6e067d6b2710823cf8e5e01146a02ea8851525c3050ba9a0e1ad4955564e70a756e9e8a0cb36ebd91c6b27508bf7811da268d8c8154e34d2dec3eb33862444c4207a350a10a2818c2dac136d42ebccbc4df994e2aae0c8bcf73dd614ded7badbec163c8c4629bdcddb6945b4a99920c0a0507051e05b91f0bb970707af480e1c81f5cd840421a48a981b00d0de4c335a78f1ed46314d137361ba5818e9628f31c2fba60d9f6346bb2a90406131f013246ea20f9ac64be22b01401f9a44297144a20c1d689a2288a08400273116171e850decbd7116fd96cb6caddfc0a9bcdc6ea06c1984e942ea5eb92309b48641e4962b3b55213e6ab6d42b6166aa1a7a4ea8496948718dd8bc8977da2842b6dc885d2fb47a87f847eb01713807e6c361bd0cf131add2126e380b232c7e4a505b7ff1b3cc4bf29eefd4609f3c585b0190e9359482c309084040a833dec61b01bb541120a540601441e2e7ab74b86a28b1ed3893374e8d32bc4b1172147d86c36182501a5b5ceb861a5998e5a4af944a65cd566c232a7ede4cef7320926da2a67f36c2919ca36a960c235e49e3697898b2a264c039e018f0043c1417dc444d6d078911201cb8045903b26312a1bdf32954c8d18586456346666e018f01319343c9858c9318fe251300c3804f8050c8441809d601744b003c590c96cf9cba31acc75b4e5e8680cc23fd7eb69c32d4c813f809b60d997306efc864c7830f192398ff10210a39c2fd4673b7b29ed464a4156f69ee7ba17e1c3142219cc757a0edd3b45998f1ce5a37c948ff2513eca3e1349da280a5ca20f91a2100e0912b91493524080d296cd62b158ac2142c4a724d8a1bbbbbb7d685dc7f120e7ab7beff59d609c2ef088b7fc3def99df83ea6ebd15898b478a5016acbb173e388c95cb5269970506a11946397f8acf62e52c9952faf3753f3febffe7c77e9485d1df2398afa64d25f3d54bdef2ff775abe2d2d3588bb64e7debf8de6cb1e79cbbf455b93dca193ce39fb36bf8012347b4e19fc5e8ee36afff831bf7f68f1ff4f8f3c79a37ffa490371c95404631eab78ab85788c7e637417a292632d040c1a064d2ba22d9bde42b216a250a040a79cd7a59cab6ce48b6164cd903dc3f1380377c9fdb119a34f218fe53a1de6e2455c5ae9128fd0174243d89c13ca0b3e1e0846daedb36fdf392957b949e9bd1c57b94929fd89c2fd2577ac1cb529d4915ab7955a0759b0cd5d69395fd46789b74ceeccef81742e99e682942f168b4a8ef160edd8217724f76d43d9abeffb58dfe704a5d57259fe8cf22de7abd5af564afc2077aef9f25a60b17226325bce1119631d18b9f0d63a29077663e72c3e91f9bad766bb19951091fb5d5b7210754833d582872eb97f105d54e5605aa9554699412ae3f7838fcbb7e50323f77f3ef07a10479a519435345f7c52523cece0fbe2f32266368aa8189509eecd99c5722da19229c1281f15c30899950eab992833397c5d8020e7cbde30cab7202b7f3db041060964cca861460639269f72e4a0e1e322f7cbef96fbbf2d3e1e7c5a7c597c5800e089147700001c1f0ca36a0a1c21f8aec8fd3eb41610959fd367c557c547c5d7946d6e3e30cabf91ff69f1f1fa13a5f869c1f97c68315b3edb8703afbbe283c2fba0f03a2729a55cf2754e366b6766603018ac1b225ff7fd863eb3c398b462944cf20672092cb79efd0de59cdd684060dd1f020b225f5d17fa1431f389225f10962ee8e339fe2c798b31b3a261c5a0098166265cc9d81064dcaebb342d77e44a66068def06aea4961fd19c06a3c9a8b00130b05a39cd8dbce6479ee43788c934806262481fea985ce6401ee4331fa234c3d4b07154838121f60d64c59031d2255ff5534f7f02912dff21948eb031e134e2a06f120c6fb421ec096fc49f8def7b94d75d8be238fcd9565b74578ea359acb5b67ecfef6aebf7acd5da098496762f49c1d14a29a534a431015570f5de7b6fed0671e6199d7745d63fa5d41b27b802516377bf4e5408fb21c58ff15aeac91fe30be63f9af8c171dd5dc26b77ebeda8e8ae8481b19672967294724eaff85fa9a51cadb304b54e4129e598ea1494d25a99b83a27d30c449f969b9c6db29c1259cb94382951c289ff360b035ecde7e8773d4a5797524a69ad934e6fe4402a41d478c1a1ee2d3804f43cdd574a695db90ec77d0e72cb4abbd1b9efc55aad56abd50a575a130d48125da7abdda854c20224b5a196ab01a9c41720753050c27cb50d526682962d53325f2d6b9912d76959cb3420c1105fcbd370ceaf959b715000e1e33e3e3eb72e7c984244ebd3eddecc7a51748d39cba7bc24e75aaee55aaee55ace4e3e341f9a0feda7e06ed3ec81898949c8f319266cbaa638ff36115113dd9ae8d644b726ba35d1ad896e49b639f485fa926da989d27943019420796b0c9452ca51a539bf5417eefda8a44d279db2e9f419ae59831726ed584b0d43e5eca5bfc39c93668e4e1be69c94b37406fd20d749ab2b294992a424ac98ab84a49f4360d02a45e5ac1445db81ff3ef8c28b2e7a80535da46032173795cc0f32ab24563348ccd0d08000c2163142e0782063060d241a35906a602d7016180bbc037c0576c256e0c89143a47449ac42c70e494593161d6471e1e0f4c036f4e86e6c830f1f3d54417d3327ad735290b272962c7e969f73063a5aa2cc290c2ed72585124810455114118004e68a0157a9f0a62c59b2b0b420e24818b549972c59b2c829748871dae5092dbaa8800aedc2444b2965df2e573a3bb3a094fa4727d5a288ab0511dcb51dcaebb2d8982e0f26748a1c2c4911c5173850b2d6da25ecbdf676ddb54b58db6d84b5dd5aacedd672036bb3d8c0da6e2ad6765381e209279a602269097bafbd1d0ad55d3b674fa144b714492021816b808fb0b65b8bb5dd5abadbda6e2d317ebc0f5f5487ba947663f3e02cfe7b2e3fc2872944f945263a2b05048e1461b1582cd61021e2e760bb6a6fed3c8a3bf5614c5314a37eb0372e369bad2593139cbd4f5b78b1d96c3f8012e4250a19fc5e262f4958a15ea290dddd5236f5a2c30b487093d2ee4929ed9e94f6a4b49bab5ce5eae56abd94a3d34eebb35a6b91a89c05e3369392647202e57db6bb5e3016a64e4acbd159b1119806f80a2e0213e1051e02d76650028b172f7806f7dab00ca64cc131c0426018e01760176023dc829e3d6d388839a7c02cc0457805580538055ebccc39bdc478b9990b73be281ee4273a5ae2a475caa64f9fab951e35162d7bc5e0a849929280e6ab7e47e91c3a07b9234719ed9964beeecdf95f26bdaca1b9e59e4e45d05db75c6d3abd06f6724c28a68ec95aae22a14db306262626168d76777727d3610ce612a564325b3c4085e1962db52d394c4abf502626264aa7123194fb69a7924a39e5942cd61fa0fa0df17fccd78dfffbf70b189452ca51a539610f7bd897da44acb28a544a2a69d34997a60bf9e2834999982a125f1a22ca9c93668ece9c6fdcf81b63cc860db9935972c7d2d939af96aebd73c55aba71637c89376eac72fe6997e7f99a2f9ff55c68824d66423331e5d964b6cfa3d56a15ae9ac8f3fb63ab0ec26940d07a5552d8af1be2081c9c9628e21fb0155c0553c13ee0148e12ac81941a48d6d0749182c92200e129b91f13a9646a38421a6865e36d8c319cdabd404a5c50124a3190c043cf626114d0a0a15163664629c7bc4a03e11eb014cc03de019f00cfb009727c220e0e0e4e07932f6bbb7b6fce4040b312e470899e6a09eba063c752149c03c601df10fdddd82660219b4a48e2f853171c31fac71c612119ad1a76c78e6f821d1881129478b46773c395c3f69820070ec93c0c387f5c94b928f333e0cccc0401112ec8205eeb1ba7011f14654de7288daef3e9e84c900387669e06f49bb7fcb3d3ccd0acc01f2b9e85e7846e9b2da7222a53e44db7e4af1f2b30ef34ef33efef49f3a57abf488ed0cc105559793a36373ba0d08a684588bc5c60e83210e8c75faaf777990af42c9c82826642aa506a0439e6351e171c5abd5f095d05fe7892e7783b0d65ee4ed918f39a0da72cc7941ccb370536900f08eee11e083642c7209e858beec4021a5c0c2aca13e44027729a5f8140cfa3f2998b34a13bcd9614296f6c18ab378cdc7555387fdcbb70cae828c5674e2477ba1fa9d0e5a2ab0605b95015e2c01ac5c5205f10dc93fa20d8081d1d098f5e8e294f46ffd9c20c631319a44291373f9edcb9cf43ecbf34d83c593d0c78e386bf025977b652609d09f92ac80170062b00984b7ebd522e8c30e010cda79e824340220f4dd85e33418e4d99cf7c9685bf7eac74dcbb5d0da9bec1cfb3171122b8c7b3432a21df043b8be3498ee5b1fdcad8de081fe6f2a5a0e4fe1ed897c16e9c51cda85e22e558251a5513f4c0f9049c41730acd28433243aa70ca5af0713c809b910b2710527ecd843f56ec7ba63994208e27f965c04b03b26612a75f26c91aafc8388022de91cf083652d3f92ed88d39d8488d910ba951100d7556004f00553ab0ca181943d2909d6cf57b48a8d4a89c9e6e4fb0a727202d6140800081096164a44a6a526a7a3535c15abe6030d8ab0a119111520d490709e9d552e7f57ae944111252cd54453115d1a852bdaa4855a4d332474747a7429162bf97f3c98055685683640dd84f863410ca48d6f40f71f2d1c387a7b8f78109d08db115468e7608604d81cce98f01562832a79f139aad190c58a510559ac4e9a701ab110bc6fe99da82b17dc46a0d368cfd9e02a48cde00be095e90bac89af1d7459a0a3f12be0976e055859f530b76e30d365263b42135bab9b1e9b6b1a9e92ec173c0c7814334af02efcac5a60959b3951ff04db0bb37363702decd8d8dcd951eaf49c9352b1baa4681af7a35df6a65f3ad5ae82f3c33ef35efbd9a4dcb9b9b1b2ed549b13f86c7991528e7b549f224c75c63a5c99aae42805505561b5b055eb0c037c10b7c1d179ef0c580924735a47a4ff515e4c02725cb64cdb4b9e82f03ce266ff987306516cc3972e0b84400b871af082284610882b8c76bd6e6a8e1c845f72b3dee246bfc733c91635356737aff0914cb40a5c9513e030a000201688ecc18c9f9e1a3078eebc626962306c5028c3fc04855030b6a5035092225bd4042073318ac0471c3163cfd28f95a418a1f7460c30e9a9a76a8a3bb0bf9d855829e482364081a29a5f2c5420abecc60a80b172c3575e1e9ffe44b62e1e4092c2ae0220457d480870b0a9463548ea917b001acd3fc9ad2ca8554869b69399627c771a08ed4fefc3cc318ed3aaf9c7395ebea7def62d49bdf4a88bc1a6b5bee2bc8c228710f177260faea2d15d68b72c73fd7c9ae5385bb933b19b19e9457ba833a6490eb77183341e2fefa44e6b3e6ed4c18b92575a45f572ec831b9c47867f490fdfb939a20ca4b7c9c4fdfbb1e3fbafba8907e879776ffd486d4e887798585169ee0504313268078e282122ce810030e5a9cd02008275260914a51b470022b62c812d4c34c82073658019630840842688a1017ec5bcd98a334e2051eb05c5933bfceae524a2927a5140bcebaec9546e4daa1ba8bf2c467596a59e2532a44ea7dd2f5c3e5ba1fb65c779380f8fb2ffeebe0a9b5015e7fd786f2893ce93d0993c70b555881c76b9e6bb434c6e86cf2320c106240be1c6d13446103fda54341142df4179eef1b939037b0826c9c731ecdfc637dc41111b69a155d9bad2317bd97c62796908d95f25c70c8fb0f74a0896544a1679304388f17764d287b8ccada185361dc40d778e43b4f8731d6476d60c85859a8dfd557e84ba90a63add4a630d69ff5e7bd934e5be7d70ad4a4f567a573ce6f871cbba30a2e6619c403f2c6539035feff5705179d090d724c26dd9bb3bba7a35fb20bd03a618b2db9435f12b1d32fd93d46ef9b188eb13f86638c89a377a3e7d8c81cc523724c6e19dd039289dca1124abf307a3719bd679e947fe567f92cf9928827e55ff92cf9520047e6abbd837cf6551c7ef9448ab4539f714141f676a8d08bbc0503fa0d288806a199149c82c9315545557565699de98a68b4309c9e9c9e9e40f0a4cc66552435253535c5c034040529618464848424630a8d6e2910183a348aebd899dca940aa220e355fa92c7952a9aea351a3e8b3e050cc5730742ffcc74a857999ef6e0b0adc7b36aa4f19d06c4d21176794195115149a0b79e3bf84357a17b2c66946b7bfac6ca5aac087015f85e2c2bf2bcfa961f632cd15870cb1860e1d3a3e18f0ca7efc75651db4a36b6edce4e88ff50d0c158c5fc8204eeb1bcfe08b12a731979c9393f361d093966c4d4a4a4554a6582418da7c19cd5756a1b8542692355568393637afd681cdd097bce54a2f55f863a576aeb41afa0d867b6a06bde6394e5b52f29bd248f3c26fb4d06f370ac33ddcc3bcd9ce92920fe157812ec55b46de72fc30459ee39fc11faf798ecf3ca76be52cc5a17b71eb501d8ad6998b3bb887fe4efd14e84a5ee4464278cb6f34efc2b9a09cdca9a877f23c5909952be57676be0cbafff8f3d45605b154c8eb15800f06ecdb0fd0f3a43af5ce7991d3fce6b71c7e9ebd860cc13db4d6211f4cea7d287fcd9fc2e9cfa01711699e0f73198182dd28bd229f0af49b0c9c40bee5c78aa554d664d66c8d40a467048f00df116cc469b0575036c0077adf489c767aba3d01797ada69096467670788104646494d4a4d4d4d5588888c906a48484851842a91aed3f972ee3a0e95ca5d2a739608ad447ef8f8e1a9fa3f9c26930279d35f85ea8c68b668602d82590105e9cfd6023c87d61b9b06c0b066cbc6a686aba9e1c18307ee71da8e1553a7522950f2c0c0bcd3aca5f1d8d192070f1e5caa529894ddf1c1a42a6a07eeee0e097cd44ee0a321a8c455fa6f83e36ad4a04183c68c1042c03d4eabd58dbc2806ee7125bfd16e5bfca92a551a23c9093d2714891921f3c0a4f0099f873aa1bb27d813b8cacda0339ca1e748b2781ee6d2840bdddd4ef413a3cbef2652a79990a2ff94b192fa2577a41ff3dedf9c9c967491a227d1e92d2a9815cde719fcf28fc89d4f7613ff40f552b399b5b7868e04a8289e0e1f0de6532b2417fddea0231cf40d31fa7f35b9c3dda0990eec2b5dab79349d0f45345a184e4f4e4f4f2c6f8a94d9ac8aa4a6a4a6a61030110d41414a18211921612e1966e8c74affd0ac56e19ccd560ebb02499c560531fa3b5765d2e48e9d2fd56cb65445aad9e83f8346e79191b161a348c581433f56b877727964583c3221eb9db87840f81832f327eb742a30b12bf067f54e2ecf8f1317cfcc87f0decbf4d081f367054e1a70d2e634022706e7950f9c47b2269c75b6206b6ee6ac0bd2e84748d1df8639b6c8b10bf290594bc210f770332750715d0694709fe71089304198cfdf043b4e05fe5839c145b7a08c01a0c6fe98faa8040d1cf92cab110100800293160000180808060442c1501ec689e0f50114000e527c44785a3618484581240752184641108518030821c618630c324a3164560064031924adc3969ce090b2e1203210cef9bc2c5968a162f32c33a9bfc8420c62e62cb747afe605a9b258439d705ad6df22171d08ee6b691205110e972222967626d37077e75f7360c0556501c694ba4a2026a92346091fdb69ebf2ee9b86f3db6ec8f7139c2bfdc603a0ed19799c1d6740b1c81b36fd64f8f251290c4552c3f0f4c077c47749107015e006a0aa62d40f5e89785b293f78be0f386063fc4c9e407350eb4a4fdb6e05575d8835427930edd44c1bd5fbc3245906cd209399dd46b0e052e5503666460716c73a674114ea8fe7150c792678214e084a27b80d08b7df8875439886c239d3f61921f6941563bf2fd9cfe23f66d7c00913412c75e985b34e8d3fd7191511697938bd8832d40f29492512b4f55da26db82a86c12bd52c9d77a8a50e693b4b91560baa08bcc50d922cd7ddcc4a4724622d93851a90c345734e46e0bd856e1707b6586ad653450a2f45835d475ae509120ff38248a779d1a557c5fb198ae9c8121cd5fb52039807db12cb5a07853aeb14b3a7f1e82fa87472e13a9d9f9ca66313bad3a8e99a18cf6736cd0785a9c948b8720c56585580f394a5a123c9f3180def02b0fe44344b36c7078ccb701817a5004e08ad8aa8200b5a03a9f781c32a00575548a419a181697c8b8eb3e9102f49cf492982ccbb036e9578f3cfdf7a87207bb6f6d7f85d81b0ba7e666e580320ff89f4b771bfd2cf849b303387e2f8108f89b4f7d3a1f50fa6fbe780989cfc5407df88f67e5272d6db89cce84a09e9c2c3f3874bd21fef251aec1bd2cf1f7cd22d88e612432768a1bb39b7f6de9346f18d70c6847a93777f66d9082f7cfb899f2871d5d9968a64ccc49ec417bfc192b6ad61af4d90ee8492a514e445ffc67f4fff469ea5fe4d5e9144cc464dcc305278dbbef0f129dc4eab99d46ef49c61c1c2a05bc7e7df5e2ca05d1fbcd972cfccb3f6ded1a655cb016276dcbd63e8e377c643d6e7a1f66b0951734b208400340a91026dd15c6586a27d83ae83b34d9b0086a2b0fe96274600c29992c35717f575b0f5b547e697afbd69d59a872bdf71d8d959499be080a99291a7c5cc2fcd5606d988a156bf7fe41c62d418c55c48be36af5fc41c63d41ad55ca8bc36bf4fc41ce0d418d51c28989ed6a74fac16956d50db081299ead84e5cdc6b0c48ee49969b9527a69ba91441972f3243dc1c59a6d24eaac0b7f21d65a1e99df10ce1cd5452d41ecff24cd010cd58d44e1a9fa293be121f742387521a95b53da745747e4e5780302cc5eee9d1982761354436efcea999936ba523d2392d292eed2431769a9303ee8a6ea7da385e2d4ec4c13fefcf237471ea116a5d225456dd621d8336a8f3595de76caaed78fa9699bae3095ed9ab2a7da80e1144ee86f79891c31ebcd4712bac89c3f113574898108bd8f416b6b98604cf6b358883c63788cda89058b31e113e40a468197127f528dad815a70a5ab3d8adc88a39765443355a8df77d7496733ef5edf459765ad7b973c434b5ac25260b44f7b701a23c19164d9a0fb5ab741ce226e3726fcb5a7eb4e4644c6ba74e2e33928d8af897a3acca5b60d337ac422f7f55392217612119046a864c4835b82c758591bfa72ec9e001196ecad4127c19919c90fd04a0e54d4c0ffcaa4890e2870cc6cbb6c9fb760e11ae11f7bd39fe6ce47393deb1cb8b45cf9180825d86ca1992131f3707f0da6b9ff82b91d62445428936fb2dc4ad841d61fdf1373a6065e2ffeb013d3ab463042f6f638992cb690d35bdb2e934d4d6c5bfbafada9ee9e435c0922bf28731aa323a1445d8dc9970bbf8302c1bbf636380983256d1fe4e555485abf675f2ddd8a7a202e0ff373fa8f1d2952c196067d4a0163e85d50990d1cff7de838c0d65540b95a16b1c74d0e36ef8654ac67eb4f5037932b6d6ccafc736d6959f160366b499a3ce02e21d7a2b3dc342a1b1179ac28670b2f811350ab0c75ed7beb6d663075067c9a4f339e6ff48b50abbed48d6ff2ee844324bb74c2e4b9b6193d45214e706f68c73ec9c7a55e7412b43a22f92b1678a50dbf4cac8bb884e9ea39f0150481289f5ecb6922f6252efbdc68e44a26fba6af5f4b94e86f6d223b4bf440aa06b123382079eac0b8f5677e09e0c8cef1e452fabaed603279af6e31e4e16e13b99cfa1819e71c746f25e5066445a47302c0b99d95b4d54f4544c4c8c030cff8a4290d4ccd98f356f0f92e33b3bd716e8b89c0b2fea295409d3f31b8d5a02b1acf2f9cc996a4f9b6fe1745d6a23c4a655976a3aedf34248fdbb7264f34424e1a1652d1b69e553644df03bd19ec3923e249bf49769836f96f635d98e359dca99d13f22f8c9882c38d6c7e231f292fe0b44a6bb41f5940dde7a015fce1d47d686230b4372b38ad988b5c2c85e62e6cd296fa891bb5cf4bb9eb20b2931538199d07cadfd004044da9d09aa130d1f003ba8d02432b020f1a1ad05984304428c419d506899aff0b61e3584cbb387f0736c83e617ac63558b58a84e6fc620fdc62e14e3ddd87f1d1a119f0de2e8fdf3b9db9e720f5cb1d60ec17d8b9818268e3e13c0ee807d2aca7f690c294b6804090b04a53f48972bdce97649cdca6fa1602161f19a8da4ff72417bbb27d5302df93eed3219ae8aac6c5405809cc3e037e01019095357c1ebbf82ced944dd8e66d5e626095c0c3f2458e54aff97b4cd0bf4aec91a1b2c323cd4500dd0ed8da2934174d66d76b423b999367c8588a5506894062650af5d6c2ed44b4aad7866fb1ea3b53a2e39b200a77cb815c14bd0aab43a9162e1c8a5413ea9e1f29a513a248d2be11262fa3eaac10b44df9cf90a64afda466ff970051f58ac3627e27a2c08a16f6fdc307af9b488dc2551e2e9599050ee307a22ab4db7719d85068cbacde4566e6da683b67980a4ab6ce1fa1b02fb44edfe8d8ce1f65c21bad4e2a0672b71c12d396234d40bcef288d1c57b41de73cc3e4296af37482cd88bda739d6acb15451c6cd2cee7893b86b979248304264062c013efb97cd07d74b2f167721bd13d179ee74cec16bf0ec9744ccca0fb5459e303bdb4a8a6cb0caa587efb92cc6a8d8b2eada8a28f2b69335e2f25448ce50f5f47a1d206bb15d8288b3b7e490b4b06806a396fb1bac33b17d27ed8bd3552e1a8f2704a55b4849191cea37b36873950271e6e617e340cc28dc689d20cd23ce0da55dc1acf11daed36630782da73ce498548d8646d725f5b880d672ba1b53b99811ce3811cd96fa3403e04abfe490df22ee10f32a8b74860d248d38934d154bb897ed758368d3af613c13173344919610685a000491c6d0e598140cf27245906e1b26e23eaa4959c277761ea7bdc10344b031d343287508ca3c5698d9051ab20c7e89dc7985a8e372fa5085b20fe8f49dc179191cd3b6e25c72acb1a94c1b8c8551c5a2cea0d7bce2e628e183546650e2c3f4f21e2d86b2ad9b16a31aa1cb83c7e05c0f56fd4b2cd544b5490b27ec501a493bcd684a1dcc504b637a165afa2e11e258f40cc50128f07f28f38d2190790cbd75d64bd70dc95cd57f5033e19b25cbf43ad3159af510a3df45bc8eadbebb9bbe7686685ec3cc84f4302bb52bead9fef17f1773b3aca13dfa6b761c6f2a7ee9300fb9e5e58a6801976d1f471adf8f7c7e6a383b90f6150411c591433fd7c927ea20caff447a0d5852fef53585b68af71a184755ce683ca4e0c03118d802dab9bc9f251ead4dcd2a26c88ef1d9e3f53e3420c9502693448ce4d215e3456f8c9dbdd443a2cd762b32969a19ee0c59e93202ca8c90f23acca838c9701901ac552cd00c400071fa4ef5e47a120280396363ebb28a4d286ba0dee580fb7d57138581588a7d1046c42da35adf26ca726049a82a13c8e14239cf1172018d47c1ce1a39de93f7cbb1fd5c889b0ef7f57289f7bcf93d7df362e206c96f6a65c442872449a566641206c2ff51e5e1d7b3cd42ee95a846bf632bbe388fc62da45f38cbf58a8faa97e97955fc59bfcc875ff5982cfdd1366f20191976b365ca4e88f5be1e9d5d76b5fb90fe095173700e42a023366af53ff5905eccd972b5cb72da5af359db720c0f1db32cebb53b449345583d8cab12612da5732ed9b50fec1476d843872d09c438cbe45605d551a76ba21b358b87bc5174e48950e524b9090c64683b9c720559e3d49fe34845f92e77e2bd6c9471c5f201ecbc06b34dbc671403acf0cc82c5cc0a354605940b3b2234bbb82172a7e8e8a376f9929380a812ad63a856dcf06e01d25d0cc28dcc5129f5ef95621989f397f785ee7e2ebf6fd2d5a61a31a7207c04baa1cc9d5ccab0468da242f1aed2e1c31f9237ca5ce309c9e0b3fbe3b88efdc89325c79afefd7b21c452c0ceed8b0e3bd30c0006a66ae06cb49c62bc98224af5812ec6522c207e5ac869046481dbcc6dda092a1be516d2af0df6f8dad549830bdc5967d5643117352ace0197e8c962000e861aaaec3c68bd2547b650a54387b9d0db520007f5948134b07a75427e01cf9255d5ecf087d5336b9c18bdab3bbde81feadea2e10ce48eca379910104f9befaa6a7c1357541d63062767ff067289c61a415f6a120812a7b1c13869ed470c443580a105264be62f3b403c9ac338aaac1e3d6822ff8e5be07d25c83f2c3849f40839e8ab272d157043be5ca86288309723679cd415ea47c5d6605ff24713a153e326014d73174b72fa1ac6e85c46da5cc4173bb327a55eb90039d66cddfac72e7d04d41e172bba2000dc2253240ac898ad04d87c104d8b2ba94977cff1a23824f539999b1e01219091ceb9ea0f4d23b892ee2038e553f400bbb072ef142f313c7b5a741401efccb415edb878e937d01a4c79c0c1ad50e7fdc777047e31cfa47494c1b0d6cf062b26c93892bcad147c75bfa1f7d9f6fbd0fbd2f3e038a7636aff215722ef13912e801a7d0814222ddccd22c26c406388f7b1b461c4a161837fd366b69bfd54b2fa2a0194f3723be1cabac0f6eb5efb76620e9855a8c8a06db042c7e288d82216e7e10e3851cfa0a430e03b54fd2c0b8319cde1fbadbb8facc98b5a0fe23dd59bbb6176c480cf27dffba4968a2cd01b4e5fa08d2dcd24fcb4478c39c2e0253b0e97b28164c19b8188b0d38bf87bd1f670a8863e289fd2a7d447b9e84b04207271de99b4a8fa35df57a3f7ee1b4a5abf63a6892b267b81abf65beaec6b925c9ec937150f0d8c30f83aefc81e2df617cd262dfa833ca0dc4e9c37769c99994e398e7a9ca18a2123851945e9d36dd4f99418de40eaff1ad06e7b47e3e47c64150c77dc1ae47f24251dba708bc2819c98f8849f33722387514dab61f93f2e8518fd9fbe44b8c24e05b705364471f60ed99d17fe9aaad803c9b30a271149985c89f0985df406a05fce387198d23ca1202c5d302b46e6505e8eb8e591a43c83e427cb06f73452c88a40411a854dc9bf050fc66e9f8344f10afb4a95c3c8bede6de4e26785b6fabb53f49c89fc1ff35267ac95921c9c578e66d5e1e3728290c6f851da0e3f1871f1fea8a09adb529ea69076c6aee6fc37d15e60f97524b1a4f4149931ee592236613b6776e25ce6c7d57b8933285d06005aae71f68e3f8c00a561af4d1d526fb12bed377a8896f5371f4d5da013dba4b9e1ecc8f5002ac91ee7dfc784ed32a10bbbc4e927a0cf39363e194417044729cfd1ccb58fc28c23dca36d58ffb8a6f54be94c12ffd3f817d84cfa933f5d4f0cffa7b04f86bcf132c796278bca2334d2d10fb516a39e03c5b4eca019602aa093e13491aa9a002882d0f8e63013ebadf539102e8b0fdf655b6121f046790ccd08b174af7ebf8ff781f347c50c180105f29ed26176a71d1d0a6d2deb809ff0baf9291a19da530c2b4ebac56511edd201871f41365a5acde9a0a7ab2af3cd23779bfdfae8a48ef184406f92816ad423ab13f7e911a2c6eb223156589f34bb4652d10a7d3ebd5c4e8b2e7cf4a230d8925d42e533c08f959bc73ededba86f5ca6c5edfe85a0f14cca337d5dea1a7f877d9b99447356ae6c410fcf634deb7fe8c5e27493d863f5b89294e91132bb00ee074e513ad35e8232506184898b0325b0a03e1a8cd6a2cae876e0445b3cb3a883a3d9e985d829464a2ce5e898e2a70b6d07d7ba19e18c5852c4e94ce6ced62e9f45782c275efebccf6a928e08543e8f12e8c15ae869bf20238a5547966da47707eac61132547de96ad95080dcc27c5a4954619de8a445e0a84b99c5642e7f64d4030913c8bbf8b1fd043885e1b0b1f9866b12c88cdf2aa38f464cd4af03ad411773f5f5a421387f2972d06094c477f977014c0eb7e245e6fefe875200228e6b3f4b75efd5d491dfd0ce02563f059c6bc6d73016bab53e220212219789154ba9ff19600de0ab34c599a4f6213e1812b15683772bd1340cc5cc49963000a8de56e8725304a2dd297820762d1788f4e1d43cae4d6e57239a4c1e225b6a68e192db7607d371412076f03ef044b138403486a6e2175ad972c2cb75885e644d3a881e8108847c557a048739b73824bdc7a25d9f694e38c939636b0c50f95857cfc194a21ad1a3f61476a0aeca6fce7e3300c4981d5a4ef9e9d141e3044a78af0b53fb70c1ecf8aef56d4b8b1cb776098a4dddbae3c8aae5b64c49a6e94c50895798686695a988a5f8dca641d8caae1f4987545f273917a2e2b81a2934255d9a9e10aff4b21bca36638b032d7535646b0e3f0ad5c0a82f84e55589acda80308ef70e1e0390cd99a8db4dd90ba1056c1e24627cf6282835397af9ce290bc64a8a459e690367660f43b3874745277fd105ac098800f83c74697e02709aa38626ec118de47a05335b563473cadf2a4b17527624ccf51646f131ac448691fd019f9564ef53f13919b3a12e0a2a4616db2d75c4096e9eb24d57ea8f86e6eb62d3b1a4a5b826b82dc4ea6d8dddc77c44d7a5c9f78d29a0a80fb38d1e49303f317f67d9ea36ec926cef8fe33ba903d51aa3c16371d4e399200fc6e6224e1821193e5a1af7724c27f4522ca4e9974c0134d44b62f5104ee9a42c56e4cce164a3495e0b17d79f5865503ceec2d091c752570c24be022fd72406b033cfa01ba4eb6220fca7b00c1c694b23a24537553d7df562269fe70f143c94749d0d16355d75828fe06b4c1107db151200677529063aa3237bd8ca0770c4e6bd3dda37c0d65a86083465dd9c70deeadd22b88ddb4dcf752f656fa8578f5472372f19c89e85dcff0f7a01e1f44c9945746c57ab1281029b10b9b2c0e47226eb405841a2ccc778a6fc474786a13c0594595dc6e012b1691cd339f29798a0c6e84b789836c724f2b58dbd15deda17ce143553c14e7fa18c0309e9ada1328b8e7a516ad5e543a5cdc5061cf46969063049aa36ea3cdd60e7919a473852a74d67ac6965544f920b8707acec2301a813c270cf61cdd51c42af2f3506ee8edcc3f3223b91eaec08455643f23c79c36c760154b2887e1d4e6b20848b0dcf96208a2cc38a67bc33849961af856be0c8566c631a308846f2f1b629ee69080d214aeb7e79c29af069e2dafe8c0dd15c19cc3a5353a960faf40ceb825602010e19c71d72832e3ae72289cad5b0de67a4da351647ccb59faa8126d35ad47bbb2bbb6eee30967fc41a5ba15990baa82f0468fc52839b1a552133276298cb19d58f771653078cb23b9e2ade1538c84448c0355b90b6f296cd049868b8dfc1709ad794000364e2b2c45e4e1b511b8d4af0e22a3e7be5aab74ec01d7aafc09d1214056259fa2a800b7019a7b5d4290f32e94904894dc8faaa706db974147eda33365a0138c5227b0ac85bb588b15cb3ae57515988cba5e5fc58ae9ee6c737fa973127ee271b33b53275114f3a3ab31ed12a4d5b7ed5b8362c3066f41de6c8bab37cbe63a1c2b7c4b1fd522703c356df061dd489037b19cbb7e150a1c70aa8172c9ee4828e02130a86c71e185e3b6d167954f52057c0b91fe6b1f61feb15c47e7280bf5df2710d72ff5f5df261b7ac653d9d5661e5d7d89f8680975c19a550a7e550b76fb2f03418f4cf20cb3c2942ac1598ba33686185ff265e78dafea22830b6b51bea6088b76aeea2f2982ace2cb56a60502d905c5e6d67ed1fecec1e49dd7b228e85a495e82a78ef909682862fea017c0342d86e2489c11b2febe65973333386867e727211352228c283444a420678a9d0b2078b24199e88002e6c1b1cf6ed7c6ab8fece50cd95df2a4417dec0350b4cacfb07b816e588862e8cbee9db1d3ee1d56b77bc7292385dcf001bfafd4a17e77dcbd48407e64c5d0f48ee1bfd3e82a4f3403e5ddef895fd4b01b51a9feff79e134e88e38b4aa88aed05ef0e896cad4bd0290fead2d7916ea54325d468ab7aa0a5cd42162c5d3998739b6f7cef68c4e6370b32346a9003a7b19722a5ccc225a6174c5dc92f14c558369502c3b2711c2b08a5fe0364924a1547cdc5bacd2ba84149356ce4608f50c56534e4bfee99b74ea07e882b03620147a45b7f66a3f21d03272ad132fa42c99ed2f21a453b39bc01c516d5e4dddef95c8dd75d9d4faf8e99acedfa764a05723461ba6d708dcb0b5f7434c739556a408acd7836b442bbcb50e6c65ab2d1f39a2274320542e049a5cfca4d4553834f70ad1dc2b46738f4b6b8e3c99edd0f5422a48095a87e80892d2253b97d70a6ca1a19b5b6dd35830462182ffc25fae19c09772f1e1907d6c06bf18e76af978735524b3e88a8829eff89f9c4d6f40e59c0401a9cc37b7ccd8d6d7f6a063d371ae8d93273f98dfdc357739e05c635506e7d62d7d470405c82cdd49f1728b7b174ba8162ae1ffe096f6594b0ec2130239dc75d9b8c5f24c6232dbc09ed20dc296e09f3ff4eff864114327dc37c14e1a410cfc7ad3f3ff805906edff82154accc65d8b3610dc770110825cf52da76afec69dfc0270796e4108149922e77d55773ab159867b93c26b17e1b9720955440915c838150d026a11dd4bac9152d1e5126867f813c41f0eb02caa33d6adb23e06c8d0574f5fd0fd0c101bffcbe226d2dece95fb6a153241e84057cdfc2704d1c88a170f2854c94607a89c834e549fd691b4d5f0c33c070e85c48d81caa011d7058a8e5ff9e9e6ee26960dd6a562f565af7441b24c2c382c1481c6803553df5bc39bcb25a0a259272c3dad89605dca9c68f5cdeac5607145cba94ad83151c1e95193a0892a7baa9999c6c0f8942be5d6ba6b3a00ef970bbf250ae6a0de3402e688b511b06b6c5546715eb2077db8e67e342b18055b747904923044984e162d184696175bb66bb792c1d4a22420075bc7265618bf935eda36758df43ae8db6d6c3206bdf279a645d4c4b881fb1663d8a805330a845acba533d537fba28fa367bcb68888ee9b76dbdddda4226eed18339911ebd98b004286ac86bc0854052c694b748184139e5cd461cef10ed33f19a1b89ef8005cf47e2b7de931f1bfc0709133fa4f5a105b2828f10ba8c545b3f81d34ee76b9f1ed485f3c8ac64278c3d10d68c58811e16ed2f520c532275b73777cf86c918b024dd64cc0261fae9f5e3bda5451e315ea070c345217bd2bd08bed8c9e49dd11bbde51c8f4a44122796710bdb08d027c67461a0fccecffc6be044e9c4b64674955c90cca2e16accacb00b361a584310d7b99e1d61ed3aa26dc45a69ccc1845dcd843aa8a80572d222c6926c08eaee68b64c153a685940f451859a49b5348e0d65cecb3cfb4d488ec202ce25a4676bca81b82f90c62aabc9a2d21f7760ab0a1927475049cd0efebc90746644b73aa1647a11598703cea5010303ff47f48a05f0033617fe1fc9910446e0c910633a723d1efe17a3f36e16f7eb78448fff84fa48959a1ee8d8f24f449c3fe0aa599bd9b9deb583f646db511c34971a486e998f54177a274f4b38499c2551eedfd6bb5cc1d73977ab3505a29634650b2fe5048f0d34f7e76c9b16f30e9a42faf5eee4b562cb0c61673c73f362f6d6cdefbe4023f888c926586b07212d6f45bcfcad5eabd1775b102acc8721645fe081ac4d502643ce4c9b2b1bf2d5cf33c59f63e5d96c72fac272b7f98326a088a269e0292ad30c34cbb0b2b60ccfe618d7c595332248c4b90098c864f02483c16c351e88befa53e9ea4ffbab29b603b1c439948fbef7124137c1e0de6fb87a6a93a270815a9a99a8d91db4006dc7182945ac4f989cdc6669694942d874dc5b96e73f3ad49e0a33d1ded4e98a8b85199c41832c8d3d7b8f97f01a13b46c1696e20a9c065621946f7cb5668edeb0a8c03b8cdc9680d0f0020a67ffba5f7c0de7b6392972d093ad306e87a78a37b313199740e2a7cbc995620ae44e684a3a2620cb7e1e20e07bd572e5865b9605c576cf340a0d03ba17d00a43547d9950e7fe50943265bdf394bc903dfb4d51e7b07eeea69085b85bdfa73db58f30bdc828ae4e2818d69a51eb5ae4465b4e5dca5fa4be2d2b6966722d8283ead7aa08fbabb10f7dcc49566547aa99c46fc5fad10527e7f0367c0eadbacc5c43fea56bce4082fe6d44c6d2788cf97209512c6b60caab7925b8e1126feb7d72a6e4c9d8781668c39b49c08a1655836d1f645054593c6ef9e74b9ce79a395ca2bfb2c489539fa31f7b55f7b5b8a8b46a34a1f6110f45a48fa785e94946dd6db81b82260ed200344e6a820c09d56107db69314f2ccd476cbf45115e31aa54ed9b88a3a111abd2a90b3c70509a28b325cf0aa7a336888b993448458a28f906f60070c1c90e48da340858fd21332ebc418921218f8ece1398d9a2236acfdbe4fd838f3ef7dc2ae73968ee91117e3b8aecde041a8aabeb6ad3a92710790a76374110ba2c0dcc9b1a98f2d141418ecd8b0363e045888b2a97249b52231eecdc0cd0234c970867f8917e37826b0611147599528f8e40da67195096ec933e702a18b25c758873a1d75e7b79b210d21050944183cd15554ccd31f6b5005f3c266d07c08ea2a1e09def847d154b70e8e9ecbc1259929f43a9363c26fbd5d2f82c9222cb0d36071aa016b9f3051fe417d9fdd7a9e3788d6e556a7553879c4dc02450e77bc5b5bbb7c597744b0afef2663f35fba103aabaebbaed9bed56d2d2b53e0ebe5438888ffdf44ccd56fb76da41639f4198c6c8cfe22c1e430c1ee4105ff3a1180cc8758325857b6c9aa1048e061ee937eb499caa04ab67c55c14e341578d00406d102fedcc7c968f062ac423d04e49c0a6ecf0dfee81aadf5cb65b303f8aa8cf1c052a0c5b6d0f7fb41e3caceeca5e3b779e9b9aaaafb7df5b69abbf0fd00f7b56b1de83aad0a2dd6e3ce402dc33f5257d3fdcf1f8fcd1b090458bfaaaca76b59ab1180e4332aa5aaaa1b51e296f08bb7e8c454b8a29ed960c0194055080860588d6acbd957e52825ca2dd06984d0ba84a17e6faa48ecc422d9ced7dd63a2439d37d994dc7a3b166630125ba53398764b956bbf69556a1db3e18653d7687d868f7b8c54fdc5f33909b53fed74aea4f4988f84080ff4fb4905e79f83a74696dd516c16cae897a2301691291586c10f74608122fc27bf638d2b406a3b3630c625729592a678a01d55e865d3664175bf059bb356679e81271e90542d65b0a7c3dd6847ce9a7d04962be0cdecbd224612b9ba0263e9435160fcb597d755af53a6e57ee46aed325a3712b233aec681cfe34d9da7706f8696cb0a3dd348872b57a69550c28c8ede746e8c8a184e583109d9f397399372e69d87ab45d21a2b726d298e5501e60a910dae4ccfe6bd9ea69e833f6101fa3367d0c035d7672e04d9b3f5fa01306a8ac974d6d47182dfa7c51fc8372eb67c4ff9fbce0a359225e5ae1b964f6813997cc0870e379dbc601e58ba201ee74ae54a72154be8720532d9f153e2abda1bea84850c28e08e8c3703eeeff514f981725cf7e1cca03db0b0b42e7247b318c1503285ed086edd4f12f24ee5da657c747e79cdb13fe82d1d2a775a4d12046354def644bf8655f011c3028571797c741d230af83c88c78bf3d1e918ff0deb58c4e1e6aac221e6961727041672ed23942ee6a3ca9d6aa8ae3edd6d7aba3d85e7cfc7be8dfb23adb5d6b66dfcf844acb33cef27d3306954430fabe058abcc4ee66533c19abbca8e1336cacdcc4c562ec144b55aea3a3bf721c942cb8200bfeaeca6a9e32542df887d8fe5026c640f57131463df29931c2a43605e3f8cd1ac60168b4dd4084673157840ff9207aa3ddeb21ce85e2580739f0a9a74e6cee604325000f32924e44456148f35a2bb7f4e8c906b60b1de5670d43984ba43f5edeac1f74233233eecb6de9c8c8b739fd98ce6b818ead9b94772912a3c40d64577426f0a68ced06d05e560dea67d9105a183b49567cbf33fa9178a773f376459f49ace2ab385b32cc6875ec31e0f0fe687904211e98c7bb6854e2f8f7487f611cdec53b8e0105b0c71b9924f4f3b2eb03ab2b59cf6c80a00e30e547f85607d0bc99e77760e2055c5cbb401b942efd67226e619076f4707c65e9eff1adc75c92dcf4f98f28d0ded7455590da93984a42ee19c843cbbedad76227e0ca60e10309e0a0cd3cd3f9a950048eda731082dfda105103105f4c33644a6a03bcefc914d08bd59fad796615b4efb118e34ef38415056cff0bbea9c36c6ea5da8cefc9221447dac1e29f16ce8b03d5d51623cc8595a661c3747a4acf897135fe6bf70a1e3ed60e4b4024ad55b44773d52f7ad35d50c7209ba8746f6f29dc1a590f8f22b32676f4061b2b674c37939f8665b7e1f87258b85bf9e9f40cdcf088149ee5f6280abe71f79a83a8b11a97ce01b300209c9f3ebfd843f71bed31ece980f636d6acb3dfc88fc57e601eb864664921cdb83f79577ba504f30e2904f12a2852b9f456838310d6fbc99769e44bab5427a031011f961824291327f2f08576f5519faca9e206968471d5cb978a3f361827a9b50b6fcbe3a2d4a59580cd7d80254319b1c886e781ee8b82a00ece27d1d630e044da861f1a73d56d359013217e7c88a359afea66baeda586c209431cd76e4b82346c5ebe14983f81f092fcc7b405c16141db1b7044cf21f92bbda590c49991f1ec4cc786c235f5ae90f839adc98b6e9fe7549132dff422d5ebe474b3189837ae8e3f55a706d0c1ca608a00e636b91e1eeca6e18cf682c546fbb8e159a52187bf088ca02e844c6b8fc872416c36c41984a41ffe4565b24f1836c71ea42b00bd51eec46f142458ff6483128f91d14a8032148b811a6d5a7e662a51734e5d49c2f5e6f4417f19c37d55ef14a758b100a0432267591e58ae3d980fa3a3f982f7df69292d9f375465c391c975f8e170020ad2f95c7a5d0fee359241ea4e1122dffba351c8852b48d5d73e25a15a8d25e1472d538f8e3a5f25a4a562ee097726dfb6c4941d7301822ff2c21391660ecbfe9540cc678bcf4cf0befd279fb87d219c333374541d7f67859de75ce02643b847ac39f72757e407f4eae8fcf2f9d687ed5d389923ba25ca631e9a857504705dfdb3e4e12a636ea0c85dd8074f889b05221ed96d77618c91a495f9e2b8ffb6a74ef2e276c74bff478ff81b98f1517df3c5132fbcd1e54d1b1a991baf5662feba113723e8f570cd0eac01e7f9768a2111d3c858c719eadc15b6dfb6fc693305130567370915b552304a403d08024ed335ca0a8837a95c9027996a1bafcaa69602ca74410a187bd600a481786b41011fa473f381019234a8b2da849e6b289ff4a5954665c19165767328bed473c098ee4f3f2225689024892be0261ae83770c2206475f58d330649f25f8e205c2fb3eabd469d0d278dfda319e457ff915682a0adf01555529d39b7dec151db03c722798e10c98cdcaf0a585f83f17304388b5132c6362a442eacfb62367e1b78c641b11c36d8a6b00b9f3b191529b08892f2ccc7db0b7ab0a94007b8b1c1ea85cb775bbf6a5e3fe64b07499858ff893e1f95b3d2f76229eca8ebdd419bb8ffe21586465304f46c53fa10a590f4d0cc008adb0496ca6015e379c4c517e3883796f81b2325892623ee09a72e14dfe1a3d8937dac10c0cbe7aab8b78868a620fce48186538514860ea49c41b501733545302048c61dacc376e66b902e059d72f33749360c9cc5c44c466f7076b9fedf5d2a6c965d5c9e0397f630aa4e336149329ab22d4a673cc44001078ce7733bb5cd8eeb90dc95cf3dc7c41f0a5ef6be9308b374d71df403f5c6543ef039b4d3b72ed5b6d96a13496a93fa8da93178194feb0731ec71f942e234a965795ffa07219e93d5daba3714f62118f929994f6486da69ec21c475039c0c902f9d8d9e351a256fee6b081b98cae254ea72e4d4d6e4b6e7a9b0d9abaa332a1734df15bbee49b3876d9e480db4da87987a0bf7491543b95bf308a4257d7dd56f18883e13161ceb107fc6ff1f9b9cc70c82872629fda4555c2ff9a1b83c7a84a4a89b69c57fcf91a9b888ce774fc2f8b05b8033a803e95975e1546255b3beab031fe82f94010cdf471d0320043ef91b8b226646049437403edd73c02e36a0b3e7a3500d1655e31dd5a95853e9467ac77b3cdc4287b77bd53808399f524b3d95688ce371a4c4ca40fc62a3d71ba0fcd8bb1b1ece429fcf5ed859bb1f6d56a8f62508361212a044dbfe2f183b572618f555945799ca80d2c1844368f2c3c417945a0c4b506bf4e19bfd6625ad2ea934d94d855daba2192878e3ac2074fea71c6d358427d495ba9a3b58d762b9c658e4828782c5305ee0698db06c5ae73f1ba6395b136ca7556409c46cc35750188507ca5413bc3f181ec7e12f9de0e62d3d852ad97534c7809a36c0fa00427b6336edcb4c7737a0900797b86b576c73749af2ff0b658fda9a211fa6d08a0cc2f7d2731bda7e146e60db7359bdfb70cdfd83ae0c3d1a5470d7a5c7cdb14134defba1144703ca8ae43eb131cb44f87cbb135f5bbe5b306586df1c01c541fc3b5c18614af351667d8a69342dc1cd0031dda3c40604bf11141232ed9cc07538238cba8707215b9e17bf7ad1a62e39225043fabe742c9f6781ecede931d29d9022c6a0a1e11afbd86ae64936bee0fe2c0e10968e8a147bc97e216d2d5c070bb4db17326c23736f1c9dcf559031a0cc77a4b78e2250a14d7bc4ccf03fad329de3776fa2d4e4721721d5198637033769ad3a2a59bc3727fdcb5e31cf4d257a24e202c7083945a9f64438887bfd86b2cb11cce670e1f5af12adb26add60f5b956b453cb70193ef64da3bd0060c8aade1d70be75ee2df8200081556893bfa13d468fb3a23068d0d2c245da12011c42706d00845cc0b99e8b4146b3a96f49a82fae243f82b289ca245a360ed27ce4c1751bff6cd3b57ce9d07fa409a822ff4826a7b8a588f72b7ca253ee0a2da35a3cde61665198f73e0263a490e1ce00cc837eb4b52a365192bb0cdf2b8707a48d94f5195e6a86a01cbcd41ec37a1f8e284e20d39a4eae8ee01adc01738179977cdced8efc801220417c08fb50d5e7d8c6c190d558704ba0a40cdf30d2cf899d0bba02b6cd9234a73ca7de4610e36a12c029a575de7390a43080f184aa5755cac52b3d6eabf7b633664848cb401e3dca414ee7f69389a39dd1c58b691706fc9eeac990fdcf18a340878db2815fa06573d6aa4b2d87e6e0e60c11a07069f3affb71904168a6a2778c6694838b902fab08ed466b805a3ee4950891c8630c22a03fa278c91ddd32c859b0dd65cf88623f09ae30e552601a9cc1e2f5e07dcc212859c1b19e273dbb1de786fc80c1c8aa71885e7ef0b74f3e183968b77c2b935d05a647ec07669d9d5c53e03b4f0ac6c37e20d7ab7f3041ff4a0ab5300d01c4f8f08ee44bf1f3647e82a64f2f3868f5d1a4c2f0e5706c75bc93da7bdcde14a31f98aabec9f0e5778d14fc53da031c2620fd0a10b9f4503a284ec59dee18ac8007e63dce7e107a64e1774c98cada2087623cdc57e7595c50f17b5ac21e18a7d86b640a81dad4d81406e25d704af3b187eeb457975209df41b4f7ec050be866c6ef3a845db4e474031033c8e945bcd05a59238365e531b358ef055539037179c8e2d501e46bfff25564936be89afea752767c268320a81371265366d3818c6a086b5f136bca15038e814fedea0f8aa1a750ff2126bd501df4c0b30eb7ab36479acc73e52a85cab76142664c717284e7e4210561140acba566c501a8621420c79b36b15042b21da0fdb505df6d0a3b46ff9f2d612333d112e07572816d05d2bf303540e188d7464c89d145d7f1f24e1a57de19b05395c9831ed90fc791a6b3a29cace8a2da8cdfc7e0be3d42b98e7b83c285fe2faf6a0e063225110e9cad081eeecb110a20765d01bb98869fdbb43abceef88ee0f6fe1bfc2aad2bae3af2513c9643d3954e686f2b8198cf6d0277c6d409865f46eb3297731ed979d68ce932aca9fb53138891463dee2190692fc6fcd1b878018a2e1379fc4f5070445ae455333899681785e21c53a13d179768c74f7060a9ad726cc9fc27a70f3af4fff021a78701bc1a666d5cf8442f055c791f1800e40932e82f2a20acd301e3f63e1a7696be80370085a814854259c7c5282c7187188b4f304b6e94ce26ebed0c596cdbe4cec895ae16eb937ff802a82582bd13d7aebaae20bb5d26fe2ec3103a4a207a86b89e98fba5f05929331f075b1d0c489f197215130ab9c45645e23896d048a93db46cca606a5c21736ac4e24e5f7391015db847182099834a9b5ebc72a348ceba8e340cd2931157c5a9880544f064242927418c1c040bd888298a04eea30dbe2e5918a8f91199d3dd585d4b62a229e2f1a8233a9433e4504169016342a2042934e95d55c475d31055f835cc3d43776c51b07746b2ceedfffd522baf215cba43aaabbecb1228f5091328f28c70079a37cdd72e50bafe05052c378a936b8f635cfb38fc421ad39452aef3e66e68f3c7aa0897410f1de666764c2545f94a6eaa4aae18541bd2c162c389aef50659f480e344da90c01e10b61d03553a32d29cc2f6ee173e40224c3c8100f42b4fb190d54e9782354230b6b06d56ae9319903e730652e2a4b2a920d06c7d5795dc11a53c8a722251c94e537a5a81ea0574e35ca1e66684ecefe4f0ea799964b4b2795abd8ed4e7384dc39597f069016a0486159b32b83cc32d12b64cfa168ec4b042efea6a1ffa0858a1802496a9c11729d39ebeae9645ade322bc5193433cda8711fece1163069f07c48f2936c632cb4e48de1a5508be59981ae052883f89f105a39997b423fee09b6ed05b83e8909559487e96a8fb3263a85083894d5404c0fa49cee0f2fd8183847c0382f7f110410917ab2a1f49dbbff4588566effb4b84577ec1f29abdf9592261f702e27512996416d2af521e1e67744722c7527e4680b89f9f3482346ebe191e29ac75f2ec5a579b79e3b8e99791be0a4036f5f3f597143484dc7c81c9926b20c5ea9a1a5e03fb84aa40f1a945ecbe6ef41c85cce1858980189058ce4d85bb47a270c78ff34a6ea6ef9a8ad03b8391ad2f2f2971ae31b0eee3546b4bb96eae750f72c6bfe4eb0e8a12c403e62e3af6cfaa739e1086700adf8b58709ed6986d6804de997814520c800ee982e89d1599908ea078c04cca1563a0fa62289f833758d4ff33e0817fa3f068e3d27f516eb1e6920493aaa358fe469ce1844243297062c823cd316bad9a7704a991898d38a0ae05662b38622f08c00c2803408348997d5d57f84d5624e8d1d50348ff8803b10249d076a9ed470e6ab1687450a87b29789f65fd4d2e9ff825601a3911ebd449ac9688e9f4919d1d5eb6f238ae4cd398c32d0c0a689250de4fb15beb3635e20af704231f59584300afa6c94fcdba479ced39541f9c5250329a777a1d0cca195ec339f15b57a59ac6a1f2eeeffcad73f153db0cf2aca6197b8c14b62032582c0570e8d9ff78ec26fe25683d544210d0c87ce591c8613bf0520fc0999f767e2de8b97e2f20eb8782b7dfd8b9f16827e04a04b35b374b44f9c4d66fbd0868610689ae50aa38fa7a466fff0acee5dac3e666462b2d0f71ce2d1cad1a589f6d8c0164370f32623c5af3c6364e3e1440d4943a5fd4b33e939683bce71a62ec89d377acaf83e005898c6e1b337c61c80e3fb282e5c881365830493af10e29018a38c62f21c8a545adfb51d2ffca0405a7ba0233dce9544ca62230891bd915550c8450816416f4367188aa4ae173820855d33d64b310911bace2885168af02273c5a701f390e408cebdb6e9c5de3388c0b163614bdbb9cd0aa8347b05e5bdd161d3e8326478997fe539c144b06222eedb993d5cc89ee1e44448bdc256801928fdad988d24c7217a44e4f494a727eef75462060a69dd7b5ad4f4c9750d6d77cf49c020183461c33a483529103fc29412e72834e9943d38cc9b09fdd664ccb53d243c22259c1159d2b185b5f0e30105c6579176b8370eb2c9f4fe2802f97c2fa3eccd88407a904acd08b0fa1ea63dfa2cea9aabfcb27b7cef08b914ed81b5485a6876221a56bae2af965cc20b9907472c87ab8ba2a9355011084876d847856f6fdf1f5261f31d8913c91deb503307c230dc37230e2a0ec0fc9ffc898e677dd5f15edf8468254cb32c434ae3055408137c77b8e1a252a053e0f24502714dd36c1c02dd82e22e4aa111055c302080f0df1fe650d485e9d290b3f41708461f300f1b1745e77df5a1aadcb169fee1210fa7a9f7e5d3ef0270b4b15767a2245996c89f01a01841bbf2a3778ec8c782ac39bca3389f1e67c37f5a6b6e34cf373ac4ed49317f8434dabbc6d50cd847fabf2272f7d6526c03e4bd377a724bc4767664ac65d7d9d75e2d99a0ea77a248e484935bd383f579584a74629f3995db46dae68f73644ecf743c30f8d11c392bf8192b0a8783cae7745b5ded81e43d26624e10471386b14be5d55980eef4c7dba58320d9c0a2a6d221dcac02bbe7154c5cad33d0a2135a82f5db4757a1e7f1270d530a3f6187725e792faed02d53eff2658b2b08aa9f050b4315e5994fa83a828eff0a8f655c1bb3134a43e6cb9119f5929b50368ef97b860cbd9c99506af139641fd91565a4d73720c6a16db9189765379f4b2c3a91b98cbfb16bad50cb86a341623b045204c89d4a3b1e604571418c992c7ed77a965d1866907a6ab42e1894081c62815737904612806c591648f5765b401e112e3c47e7a205489caf43473791ffd8030de4e666a772833522b9a83a55bcdb5781e16f50480e63a3a8db86809fd9be0e350ef0f59acf469b433ae332d81510c29d94bcca623789521d2e5aa836b652ee2c2e7b29e2f42b73f766792dcea477202078c84299dd5b683d2813bf3d8461b1cb3c922c0db0ab75c49371407e1d0c433ea46465da20c74c6046b1649ff96b32b633e3a70651e6b5b614811084282d667bf099754271e43a6c4b49c85d8063ef8d21f072cd74cb84484597e8ee6aca73e60d68fa88c8d4b249a1582fa6825439a2faea8d88180c6d4e5182cc8af2484e1d9f42f2a9833d78bdc6a69e89811b00d8d3db08c951fc1d0163cead7288cb4b8c47a5f217a6eabfeadeb2593820be160fb849968c2d7c8f417f78d611e4e7b78f8b40a2e23e5a77e39ba378e2fa11a66f671286ba5e0ad096d25d9259f143c126185da761be05b0e2ff49d46176e330ea939caa5f5c80d8e6ba947edf94461c958a77952e93dffd0aab13465ba62e28cbe88b64df04115b2dbb83c02bb95f5c485618906eb4630ba5c8c965a8cc01e6a8aad8a01e26a21d53c7ddf2fd51e6f9aba3fcc115e3b740b79831cf6f797c1ac3a6283544de0ea8d3dbd474f3c7141605873201910ca6aa497458d3ae48828f9bb71196355135bdcb3bb3cc5a11a4b68b07b13002c7caf275d1329b889780f12a7ee4283521672c10b2732e086774db643ca1d0c111feaa991a60aa30cd4d08549c1371aaa48c26e13391429ee6b9946fda9908656af22a94a61977d7e3983c2e6aade340d2125af08a78e54eeef4679fb1a62aeb2e2b4ad1c55b309fc6a405691d1f09651af0e6a84f797a5f155c6426393250825d9cb59204704305a914a6a23dca8e21b1704b2ca9c32a9cbb43557e96913b8e86b74a6b6a433db23819fc161accd935ce8bdc6c859a6efc2341e841462284c28e1523c28b30e5d8095c12895143a25f84afec806c439413b62e4a1382e3504b7611d7095775415ece737bc27bfae0d984c2d5d54d01ef952d865a2e38fd37f720467132692a5fcd3eced94c30d20955a537f50a75c2b687363f56a12e5b425d73aca08c0060c2d5f7abbb1f8f807662e825c4fd0de89b763637c69819362cefc2c58c48f54426e560412632440b9f8f0c34339ba187229b356084086463f4d6d5242c668676c17dcb2b6f11571659a9d6b2ee6ff585f0fe9ff68f67db9bad7f7943bffb4bd4e18621e65f9e6db748927684a169784538180f048563d1f47785132a0e57587318460225180a6c9713645d03ea6a2a7f5a208eca4a07f70cf54e1d9ee90bf0f9da339bff3f4a1b8478391693e1655f4459bbe4fa61a99a5c9dfec6d8a310c419030a3a8e38c1c93c5c8988b704a5f0b76367c4556dcee36ecb3e51059ccf1c7de893bdc9967b4b99524a019807e0073808e2c640dbafbf889a73d6443bfb526b221afd8ca69fd3c4a9fd6cd543346a3fb3e9a1a53d25a95ce7c61cb5d5a6ea569bdaa8b66796c2b196dab38e6da2902fe0c84f1fb09671fcb8070bf92af734fc00570965abd564e52727da99941edaad9602f85bad9fa56a4bf401275698291367c99a2896c80147c02071820b104b2d90bdff8aecd4959bf4d026c27c489eedc4009cdfe4443c6b9ffede640c43da931259cbac63cd33a91e9af590166e3e0ab34e5188147dabea3ac695c7b3850c53959f5597268354fd8055f987bce518c0a5c920a5f673ff16e58141e633bbf41fad1f22983ee5fe4871641fedc2f6600fc17ccca4faf1f80b8e32472947996351cafd48b9a760c73d9d950bc2897468736cf55f707dc07e6c514a554a99b457b67172e3b8cd7f0703fc41f007e26fe44f827f06e4f67aff9d3b5dec9f75a4fde308a4cf2aa59cd5bbf627d52b299ef7ea21988f1eb76f11e6c3df95545d4d93c1698241b66f1184fde89f3fe5ee50ff104195cc2c8e72834811e6a35f7c35975f7fe153fa9c5e80190b2e1a0c52fc4093618986823653d2190d05561288dc3699032ae6a6c264db85062db5bf880795dacf802e2d5cd57ea36d4adac50451fd60171aaef416a554b9e5638b2252fdf95b2288bb31de1f980ff9fe3cafaa2b3c99034eba3ef497eaf4d3215e2be4da3527e48d793fb9dd735ad463b1deebc2ae5f98b331f93b1ba8cdeac4d6fec89c2a51d8d63be7db1fe9755b1da8fd1bfb8e6a6f6c486d6e5156e16ddb76529e973759ba3ab63db76d2f1d8acf15a7c559b0902b682da5d27704157d2e1e565d5a92235ea892c69b919c719ed78b4752122ed04289274b3c39b38400b88001e2cc0f36bc7141992a4fc02a408464668702efaa58752fa87e90b374518ee0c1812c8ab861218c962e8290b50d2816b0c06429891db09031220902740ca4a494883753f2785569c1934454912329cd52854897858836922661229338797d8c968485242ad43188c5c40aa268a202171a90e8e10563b20c41850782238aac79e28296850c03a84b430205923045f8482f13b3c98290239a945439d9826506afa509624343c493275ca032a5c314ba4d176010c5a5e985186867aa687a4a224396294cc8c4d08eb420809a53977682334c24b0aadb33bb3e19a45fbe729880fc3cbc5bba461094f413628467c55198d545212b77a5747717cbcd0757595b28d4b16db228f3d3a94117063190b8a17cf9940a5121e66eb2dce436e54f1e7a3f0abaab08c84dbf2abf879cfb51722c6e3f723fe5c2e9f95e8eb01f5c29ec0757f936aa58a508802af52c593ebb7c4e9e39e7fb377ffcb6d7f4cde5e2e85eb92ebae8024edd64e53e10041fa314695dc2de04d82e660147504a6e35603d1800119051700c5429caade2f99c9bc55c2ce4f45de36cfcc90d1b55a660ca393d0bcf5d4a20240f64a494ee2c7852ba5f711daa6c1b69849b551bf793e3388e931c0cdc74b93f1c47822537508991db945f65e86a500adc1bd57777f66ecbda9f2d677d366f81361b6f6c5419756bd5edc709c3f62b7d32e9fdd9b6dfdd7626f1e6941cdfa8337435a8052cc166a3ce2ae77cfa9c46b82672729b646d31e79c37589d8d8eba9cdb3669f0e4cbf5e601709b96fbd4f747e99bc3cbe62d57fa0b553b13db868a7bd0f6cd03e01a83dd64faebd6e76feb87ab4ed1dd8388f8b6511d56d354a2c1f2d9abfcddea9852865f3d7435380598d3ea1bd5775bd923a9b7b75137dc8dedefcffaec770bb4aeba5e5d71a4cf81ad1f3f2e50e97b28e987a5c96c9a49a6d3fe2c592026b57f76b3a6da3fe9733fe47b9d3f5b3de84bee69cb07ed1edb833db422abe9c967833f14530ff1efc6e692cde13fad68ac0e961106eaef0983e7e18dcab59c53722e27962550c669c56aea581296d3fef4b38cb00ed692056a1613396335d59e53a7498db73d635755fd3d5add6a024b6482fb789be044bd647dfcfd49f50fd00f8a2b6dab8b028edd14062a8a75c81af7fe52cae6d06189249524b33fde50d28aa4c2b2615147cffb4716ebd8c4a9c4406436078b3385fdf125eae8d52daa3f174e2bf7f19f294cab3aaf466e8bed0a95137160d6c7df4cf5a782a2fa4f518db3915c54e796aaffb85d99296c0eaec8feb00e6ee945eecbfef88fd36aa995ecd0912d53992c5105d6a709c90ecdf71baa4baa44c2fe7c50c0bacbee395b9b30d57f0b81844624748c5bc6519561462ee37ebbaa91c520dcd35984eda28bcaeffcb9da4764a10fea90e735baaa24617f76893adfebcf5f1de3699e8e357f77c57744c0d125c30a1c5db577c6803c4b753660ab8b301ffeb32be79c73b1771f961d3479e4de3fa97a93d57d085894fed6ee67d815084bdc6ab4335927bedcc70747a98f9d55ca8d301f5d8ff6877f95a88ba56e22cc3fa9ba38ba57a063ec40f6279422ccc865dd738d525b903f7e0ca4423bb450a2a2a2fd49034e2a7afe797890148b7894402aae268bd5bd91cb3a11a904b2ee3d2860c7b1e695eb04dbc37ec8e767710c42c520b58a6b28d0ef70a841b9268b45a75351415141353143edca655451731f2a2a2762346cacd254d57ab09294716194ee59613bd54e50fbb74ee4da0cfd5b6b39ae0b5bb69cfb156764fc0ec7cfe65896ccccdb6f217dd128d03d27960036c28d2e64ddb38ce99375cf24654bdc17a132de03c8b86524659d5844051d19f72d0b9582a99476a53534edb369281bb04dfb6c0d4efb74c7d6a382247f699f9d5a401934cb5a0abe3825bc29577eefecdddd95e2b3764b84ae3b941df5a6fd5ccbeeb0abb7cde6d607491b3b0f62409dbab42174e87951b94d94524ec9261b4c156d08230008e3142707cc2c0b104c1395aa3056edb3b40795a7147c8575692a68a92c1cdcebf6204365189599e55ca241695472d81a4f71aecaf1d40cead853b95f1a2ae7838c0bc24be584b8a17242bca9dc6f2d4e2a2979b07d759ea1c251e7976942361f4ca1ce7ff970a6ceff1f7ca8f3635045ea8482a24e2080a8b3071dea64a5336386c8c89c87a8ea5362a83ea56a872a6a8cb3aadd575c1aa6b209a903d4feae378c0bb8c86122946b3870a9fe5ccb030789c6ab19d50f505b38c93a022a68b346053ef430ab42d6fe446d375f811232d0fd42ba3f4ac14d494949e949269fee10adf25ba2ba0baa5327707812abfdb20947102d872540b5df79db80ac5c918498365fae5e20732354e0706406831337c8dc89ea48aa7b13ae5954e47a31cba93a7e3c238e275a6a33a9366871c31250984089a2769009426875275a2e31c04069e9062a4ffc20f326aa372172c9dc5b48c14d992a6aa0a0c12273f1db6102737c004717cfa271a2a70aed888244ed7e9088142874a85297a6039cda5fb48319b005c5dbd5b1f5d6bc31908efe71cef90c34df93deedcfb67948f76793d265d2e496c2ebffc06bbf74e9923d6e5eef9622eb98cf5fa3f24e6529a2d02c141888eb6743b393fab444439c9d6c0effa62826de9573caddfe0d8f1eb03ffc439c0802ecc35fe3b5c35b7bd32290fde11bc0e5a7e0120971b2a5ea6a0b245b5c21e37f2d9101ce58b91287b6c6c9ca2cce992baa251c413d7f86b24681414e44838a845efb00581d1fa02f9ecd315f75fe7863ce0af690fc42b70bee3d4e92c0eb99794f5050110f1024961d26ff0d122adac1a3aaf603e9588ee844aaf687f07d3b01bf1fc1dd704cf2fafb28a56147bb053bc659d5279a6ab062e6adfbb558de6ab913b5af81891726ffeb7c1b423811e8355ee4008edf0b6fb1bcd56ae20aa4e14aed1fd243de62bd8b394b805c977b57c758a8ebba1ff45bff01fdd6275556384aa48eb1bcdf967b78d4d5133560205937977c5afda3e43ce84439a210057b686d48f221852c36306dc102c9110a28820989993149285981509091fe12fabfaf3fa69e97e66aba9bd043b01fdcd3f92e181ca5f24581c81a05b6d50884f9cf95e5fc5854e7f3bc4e087bafd33cbd789d10f6dbf27242af6b5731c83cc5a1f114c5325e7edc0aa163f361a18ec77d03e0bdefd68fd6c3e827d51a2f80f4bb0f42bf7b07d0efbe09eb9e9bd010d6893d641de2545b218c7e50edbe7b01244134ea98e7041cd7ca04215d48427746c0aff35969e24023441148cad89085982502e10a9c2a9e4841458911998e88043b4206cb1191f80915cc90012564b0dfd608258022839a24bc300a00fea887b827a16373db8c3a3637201d9b517458217d00843a556050a330bd4c47446a9f69a5fbf93ca7767efe64751485593b5148053a36a3e88843da67bef1fa11018e6b658210188f8814a3882ee312432421cb81853aeff1d76aae66fcfcae7af24e0cc186182a687ca813227d57643aefd16ffd05ace39ea7d0ae738aa6a5f9ccd4443464f3b9693e3799cf4f0c65b66c7ec7d5f8f92438d1ebe7d32f1c7d6efde04ac539827c1e11e9bbe20f139138c0a26526cbf96d8dd0812c329ddfd60832248c48429623c2789e87f3843a0f0b3be9390b42277e1d0b47596bd0485373fed5a449cdf919546aceb6a933c652cd79d70c5a6acec308e799baf3e44acdf9174c966ace834dbed41cfe7e0613c363230a1c570ab6402627396e00332e60548050c3c91954e44c2cb932b45b6c5c01b27e8ab01f624fc7c420dc5329e4a872cf9bacfc4b83f0d3a71e65061a4950f99781c611a0b0fb337695f59ee7e2178e60fddef340e0a6c84996df83e0de095e38525943d71260f7b2fb0eeae998f79c387e95db9d61a97addad9cdf1e621d3acffd463747ebb9ef36c7f200ceff01be7c9707600d3020ffd401c30f70e57fa1f36151c75650fb38349650a9fd439a68672ec15650d5096155077ca103823a5735f085ce8cefe9211d1d9dff7ae8c5ebbca703061c79787a72441172fec5f7f0103ab6f32ca794583c9d7024e7ebfd7742d8ce833bfffdc2f871a562b81e09468c1830d8003122a1840c08d9a934256f5829860b4628c29aaa1a4fe3fb8508dbe1d13b45a1d0cececf9607dfbff82f84f97fbf752784f9ef3c570ffc150a61fe753080a01842c744982d0e70173aa15058d4a3fb9e8e75084b40fede190431848ef14f07b87cf6f59ca09d38e01c83e8fe70ff85ddfe70cfa164208ef532c21a5355c6d350725565fc2baacaf819e1520f4b73baaa32442fe4f9adac10c63db73c983f3ae8a28b2e8e64ed1ee6839f3e3f15619c5824ab0c53900201080925b8fa05e555cd2a4f55168fba7ae0558fbeb63ffdfb26066d5832b0196266dbb68d84f105cfde6c8e0e48581f71a9664a1d6b21903ad648433ad6b19e0226c93c188b49b28722e75bdf8391f7396fe47dceba8f68e47d4b445af1dda7ff1379b632ef3d9132153f5c91c30c2a9ca0a4241bbf870fb02a3b05556efda03f722f849bfce0a4a859731804aa639df303c852226be9588d30a0fcd9416d0e66fdc842d47e066275b0c4284c3199b73d7befcd8fc3e6a278e256a3e29ebf37aa6d8a7b66274e52eba1a98d2a86fde9671d23f316e4b9d008480d33e098d4fdcd4d6a4b06da5e06ef3d5577f7f93c5a7253951f14a68ef952c7dc95f6c71bca7ddc01b24ab1a1744c4a3d60caa628a5dcc795800d65ab72d3728187668a6e6396c62461a2c6c44cd6dd9fda18a831b35d29a52b868c1832b47c39785064cc9031637b6a2999f19af19a4c5e345e343614d0a841a3067da1a8883ecf27adbc6efc8d8fc18001e3c3363468d008400f0c06eb110078e3c60d02c4a6be000420000530800004200003e03053cbe02783570c280828e8bfff34d80368e0d260030d36f80d843610fa8080808410e0b1212a222adaac90ec38519480a204d02d9b150d1f72e8c8616606ead8a163c7ebfb8a62af43870e0574c001071c74c0a3830e3ae0511b00b5f362e70b8206c397284983a10a0c3fc88821c34c0e1e14193364ccd89e5a4a66bc66bc2693178d178d0d05609c1a346a9879c10cd30b6e6ac06ac0aca6ded0a6ac682fd082b861e3c6d4996fea0be599545ec8a1ba6efc8dbf52c7ef05267066cc982106200dcdc8150d1a3402d0a38666a40b6d2a079a91226edcb8418058cc27e633f534c5880d30f8c9e02783a9a5a924b3ab2925aa450640190035694df19a022808280824137480a0033c51464b99a932335946830d34d840f6802e30d9406803a1579929b43256920b5a0b6c90d05a708256a6051a9938343259cc6864aca66864d238d1c81841e6094ace1672d0b143c78e3546785a38b283831d1c449121421b0303d9c20a0ae04001696863dac8d7982f63d0806f6af09897695f54e0e67ec182f6762f588cb2ae972bbc4c515dfe49975fcbcb13a3f4c20410559f4b97525661749053ce3046c2e0504709064e1d415a982634305734306c6860bed0c054410383464ac9d1d9b66ddb04e6893b56c76a539b06666977a4b42f69769f685fbcd0becc3ccffb5ab42f280071083393b5a88eedd3ea55d88a4b0b339581c7dcdc639ec2a0690fe3673c6606a5e07cecccbe2bdd97fdbd7ac7a4131e83ccbbcc454a165e8c08c8ff7131524749ab149c3ac6b834612edad0a496a8685bac685b6a4fb42d504cb42d4eb42d4b344880db676b57ce35cc3cb7292e8dbbfbc6d5ae7c99734e8e7269531bc2c1b8509b2283b7bf0cc2a0bd2bf7d4c829e7546d28d6c570599a1825aed0c45059ef5b313bd4a589a9619726c689d5f280c562b538c05dacfb17289a981842ae3578ee1ae872559766e6aa8e5cf7ab4beb92a6f24b66e676ff9c695b665d9ca82ee9cefb63a6aa36971bea2869b53675a495cb939e66685dc85457c758ac79fbdb338241407e29703bb64cc48900cd8b93972f75695e98685eb4d4a5757953c7575d2f56ea8bba342f522aff47068c144dea089a94159a14159ad499af8e124a1d5f34a924b42d33da09b248974959657825e5834014cae41bc92932f925f4d067a087c22adf845a957f24b7e9620a641334322965de14e0b68c6413344dd0c8b665d46686904149d6f21b0a2fc87d967b97a00eb749ff1b569800dc9f2d0077ead2965822d6a52dad802e41a4f2ab32f3bf9a480c593f38c5411d7b7614d2a90efa0d1ea38e9ba68ecbe600be70aabb11662848b850fda53f95a2fabf906053fd3f891854ffa059adfaf378aafe20fc50fd812451fd4b98a23a0d0b17d5ff0423aaff10364c4a5c3565d1e2c4a4ba962daa3f652955d66eb994dabfcef3056f35d8a9fb463c9c69c24d1114d4804436b252120d32d094d4ac7044098bcca138537364235fe1a9ea3f325575463384ca1cfcf9bf9c4b1c2cd5ddbf0499bf1017a904bdcb36bbe77a8bbae7d1dbe4421810ead1aea34bbced19b7a6d6d7af0b299d55555172c47935ab3a04c17d7a56295b55ea9ed30a5fdc44ff0b61db27c95c2a846d9e245bf7899253f44082fbd07fa41e8c7a30eaef1ea90723effbe9b3c496880482fb882c11a9c87de88b484831f7a1ef8951b1b1a87f652cdafdca58226c134de818fd13dc87528fb2a2ef52f4bd46e977c8b3040833ea7ef42897ea21180456b644190603c21a210a4be427f7a1ef68d624f9c23ab8d2974eef513ecb40ca882294642b6320c6fcb0021b292387b3e63ef45d5680a34755ba3571531580ec0f7b4ef4a1d0836b14ee476819f785b0ee6ed1e87b2a22f1701fffef91fa599f24f3beb00b83dce7e927c9901cf680eeb937ea9efb6d138db8a722d2bb8f3f972473a32ee70970fe075b57b632d67b22eb59dd30238fbed771df7d92ec0b57b6ad115ac6fa2e04c398cffef87c9e52268737329e024607293220dd6f992db2a22332af633dd7282c11a904b26e064a71e4a8ba25c9966a8a98bbefbaaeebbaaef33ccf7be266d6795bb440c28585721942cb4b094eaa052ad02c9b941b17d47e49c1fe57ffc7fa83e615e012d26d2802d236a8fdee361cd9d2ed9ee77918ba3f51037e4c832296e7799e90a2ca123f1d2c20572a72e226c531e6a4ba482342f58af2b6679c4f554bc7e47fe928716ebb3bbf777bd6573a2675f86f3bb1395a9457ec8f641d2e4a2b06924ee028a3e44c4ac99aa492554ee5545ee5567ee56fa4924b71290ee5513e7329aff59b96e23ef22905c1d7eb3b56fda51371a2b47237d244124efbc897944a1c3dd26aec3755ca372f29d0adde58b59437add44cddd44efd24c5642555a3aab2ba6227764a828a9aad9509270c612566e2a6a2221e200021c1a97cc9d4249de4939422a128e8444932f9af97cd278f052f25cb9150f294ac280ee0579776441bd71153edb368d21cc1e6f52b4c2a29a5fc6d5eae79fcc3276fdb9597d580dd4d29e5669322a28a90d2a308a8069d80303e9d6e2964c58c0e770c28a51f5bf9d22df8e4b27285eb61450bd8b3091187ea8b93e605dde6129322b68c791d9992e319e1454a931153a814b158dec0d046054068a1c595a351d30539af408a3aba7888c052bb4a1044e0203f3056254bc62102863043b88053025f75694348416b94ceb2b644ac52ba241493aafd38760603227f7feb527842c79a29f6c1576e9242299b24b5dfe5eba24ce008d667e7da23860cead8d795c20a0d539a581402038e41afa02e72f0e5a15050c76065168ba0a00ed6a0a93d95eb576666d115be567c815f7bb8c7bddf5d0b0fb6a80055a4f497cbc131a8f672b377909094b34d8dc7b21a59ac799267ba564769653563e81a9bdadf53dc6f0f0e771762779ebfdc33d531aa4640ff0b0a70046b5c81a3b46a1dd6a71f882b2bb5b12855b0d652f667c2a6c09156b06e5fa03cf51552260f8e1df27b623be4e2f720018e3cfcbce2b8d5f1c8ab7c9a0438f6d3130f149e34a092ebe5215411def6f4ebab52483b559fab03c8cff5893aae1b2993553e476d0e4f27053c3cb4ee4c81b51c3cd0ddddb73772d9e61f240404c8e24b048f00471c754708b18e05f5d0b2d8c33f03280416e180536368118711e0f7d0ef042ad052fbe5cbb69773ce79140e6904f47770c01a6c40569a22aa9d3631f24a133195ad46a57dfa5f54e0d84e3554004a282792cfa663fd56fbd3f28d5c2645a412c8e4cb76729f869250eea3e47babb1b18252fbc1222feb0101fdcc5fe51f5e0dc25f4f0f5899f9ea0b1a6274772d9686581155fb911e0557aa317e15e92505d8ff856b40ff71df387f148ff2923f138b0f037064255733931b3142b342a976b7b8c8e6c2982e58d47e5791da1fb2014756ea9000bdc97d48c78e3ad6efbebbefebdbf2806b588147437e04db809ca68a26b597794e1a0be6744e53a53804e9280669febb32d231f0529adacf4c2cdb9e1a568074c94505d8df4a75a4b0cfc1933efd3a52c0a5a56952d540312159a8fd21f098024ded6700c81bc21e207fbe8ffed1158611d0918030c480e3ce281d695d9fd8570e0ca9ec525f1d71bcbe29a4d47e70be14208c9f4a916be7047c185f982645541d254d0aa5da4bacd4833f5106573267a61efa5e8890051bafd1f4d07aa162a96e15614d550fad9729d8d4adee610fae2270b1d4433011b858aa6dbce9ecd3d999a6b34f67679ace3e9d9d694ef72b399d36b9c9229bdc64914d6eb2c8b6a5e1b66a6ae10de26e20b5f6c992c24ce0528aaef6699db953faee7697b313090c1b29c0b091821b376edc2421a50c195bc890b1c50d390cd981db7b860fec8f4e01ee8fd4a7c0e1ea86eeddc6026e527ab38880614309860d25293edf7fa4535c296efb80dbb18f08efd9150c1b4460d82002a6dfebf00973f3076a90cc95a7ff14c3b07656f8ab8bdd370397c5ea49de7699f3757a20a5d84638f160dd8d1b196e96af9c9fb626a30db8fbfce369066f8bc0c95475f8b79503653255e60fa2281385d4762f096463916de412252eb4a9626d154b7d7ee44a50c7f27a492e50486049022a0b337159a45801330989492e5454592612f327d3246bb6ad14595860cdaccc26de62c5d26c6a279f82614c3b6d96cd07b831cd8fa4ecc24d471ce5b0b8bfa15a6c38b26fb2f451bb5c3b5e3069b961475010777b2864a9bcc4622f212f2c41317630c38390202cf248ce97acdf2a51b403850b9222f188eb0749b579dbc06db278e4ae0d967cb1c2e572b2901ec965d6e9e39594948e26d38efaa85f0530b303132f219f991dccf020e48bd1f852059598e4c2a5f65521c40cb5a5f2c614e1b454b30ae1a62aa5571f1c0c4dac08e1262f9b1fc93adfe5e423d9a59bc2acf43501c7f7f595fe744addef76601d50e85377532a871f78d4435b802aa090addafafe276c0e20fbc321107153fbf85321708716a82f5153b3ea2f0532018eac04fb21ba96abd47892a3c9808eb96c025190635191534fd343db557e8475b027a91cca8ef9cb895646fde53dbfff83c9e18854f77b373cea4a453fc24032cd584295cfc3fddde7915d9dfdaf633bf6703580fd5cc5f1ab2cd74bb000ebe0da21e521d04cd1d0689129782f9a80e357298f5fed6ee6313c385e31d0f53d5f3ad63dbfbb876f3f9d03db532f7c00c75eaafb3b4bad0447f29b8eedef98c05d86fce44d263b794c85a9d4fd2a0f8e1ef2292c05747d4f10931723d2c5062a51508942cb1a2fb6022fd3aa94aa9aa26013455514549d85a7e342c7dadbd6b9e257bcaddb3306492a0691cfaa411685a024b92b6e80c5914a2aaadc1f4067b08c4895694ec091e206364cd38854a738f28669aaacca4146bc2f0bc8dfddddcd5e42936fe714da9fa08ef146c1ad39395fbafc6dc311ebaf633ddb7ceec76dfbb6717b6e7bfa23c76dcf05e1bee6dedf73cacdaf72f4e926d24fe28890e2d7b19ee28db4cef0937586b47ae8753f706873b1693a51d64351f6cbed1857213d6ef9c178c9d1eefbfd9ddfefe9a11c9ddf17399a5f74b70f6fbdb72d0824318122b2850cb41ecc4421dbf5f1bec396387ea31c8fea0b1f912a8cf79df045c8d53f581620c75aff8d473aa1ecd6e73d7f4eeb6b7d9e277694d5d1954a404efcd48041781fad482c886f8b4f1802c089a9d029415dbb7f448c0e2be3d37eb0ffd54eecb6000227b52ea262b0b2010659111a02a526b22891d2e5055460d20e57c8bc59c1042b5489234b52e2848a1d8850522b4346b62bec8c075d3642c4770ca7ed19c287272e224e534fc7be99146ea06458c8f641f9821949a41742b06192490f0bd9fc24d9f8aa1cc6f6a77f064e8762c666006b9e68529a540cc0cd0da545db6b466a4da0bcc6cb92853a52314a6fdcbd0badcb9521685d7aa075b16109ad4b13c771ecbebbbb4bebb2c4e58a55868b14afe1e537525234120f9ea8a864cd56953d0a11cd0c000000006315000018100a888362a15024cb12519e3b14000a7294446a482c978903498cc2208861180682300622c600820c32522954450747b5ac8456dfd38d0c38a2dd2a253c191c77f9232d1d20a39f59bd1bf026f4223aad6f4d0c8fbac4d0ce4f5183c08158d5afee1c55a50e9f3f4deadbbd102f797e39d1f3d3aee8e05fc19f7761055f8551b55229d9d393547b7bfae35c40dd7d6347edd8364ca17e6425db54666f6fd5439bebe974effe09dd3d895f27715f46dc07bb8ddf9e01e3ce5e6ff3dc8d4a55bb2cdfe766eb45c1a1e54de35cc324c98cf15f2a32ce0b0c8e4a8fad36d55c860db56172fcc8d38e13b3043afbb32ae007a1db494fab32fa89d331d90f006c1d87460d70079dc53e22aab8d054f7f1f34e81b58d91fdf6c1914904759cf224a95e7312ca3d9069b5df79c39e4a829657f85cee1884b64d2174d00c5bbe0556426d98f570b7c371a0017462857d90b903458439959c3578af295337a9f1e120bb0bd166dc09219251f262ed894ce8903b0ee8ae4e395fd589fb7357e742aa6e797da352f93622be275791c62bc43ab8b7887a42f09037f813c5ecd2504b7e24b7c63e1ed1207e2af4a75d83e0cff641566e20110710dde09145c0745038c3c21ef274804b595a6b9a34ae6f087d7b92a45c761d158a1f4a8550e9fe74b0c847d8b030769e1ff84deecdba212dbc1923923a012977a97f8fd539b8c20b4afb01371808c4ec50cd1f95b28d23fa8ace42141044b5c8f6cd12358bbf72d0f3b944efdb88e322a572e847d1176a29740324ec252a109841d0520dedae312e12e6b922d393cf9568e2a1464769a88b1524aaa333039e42e754ccf4fcb0696aac68b29a8a47cc66564000e132c262ad7dc2f1ec3f52ce7f298c2ed61c491c78c355b415f8d489633c33586c692a38502471e09105c5c13e84b3831b90a587fa35528e8eb3b568f902eff43612333868babf0cd583847858c18d5bf61f6ed9a00c2a509f2a65eb938260de00979013d58fafe8f547e58b103438b9e1aa72e71f2d34fddfc4365f6853a7cb7e4d9755e6089adfe2614e6035587f2ebb869ce7e3ce40e81cfe281f69f448a8bf9134cc9c0889cc5c40fbbab887387e7b154a9e7e02c4b171daf614461a9f4cce381d03b8d9c1e019478cd7b99ffc331c737be8d9ddb3b9712ac07880ed11e0b7887e5ffe8246651456882740d15f028d848919ee5b5d8797006a733210e8e554b361ecd93fc92373441924d70ab602d1d93f4154b168fbe5a12e9d4c4b5efd375adda6218c8c99e099c603bd85346822b2c043f4b61c6c60d9d96b420fc810c7b3efa1c2f436fdf149feb7c194de55d550720071d69c34b593a09124d4f8410c16fd44475a302f06c196cce2ce30075a640dce2dbd19a1293e09946bf8dca8daee5c51e971acaac9f991994da606c58880f4afed4a28e333a0122f8838dda24a8845250862454d085759ed6cab664713a60e8b6c0c4fe76795516363197a37bdfef2ed7e45bb21a3dc139d4352046034e565ad43955b2a45a878c1971813b332c03ddc4392d1293a1acba6f51ce84476ffe030c376d368c6e52cd9753060b7b1764e54d583fe4caa0ef970cf5137fbd94a08ce85b681fba9f4dcfc6411a223fca2e9efd9cde5672cab4247e9a11c9983b7a45ca1fa24bc5254ad1cd596832e0c6a71e19c04b7e5b74cfdc38a85f205a8731b6cdec33387e64fa82f1da7c9ed1ce787a824739fcd4acd01b18facab865e31adcfa8ab514445cc20070bac19156f279cd3218f72801df636aece5f44737b6c99f48d286267727f67b816282e09041a7178c47b01818667020c6dcf1801b253a63e0b88369b70cc1d12e844b8f4654953bb4dc3d416878b78a7189d105c950d6fdc6843834eaeef31018cb8a6210ccadd656c6000897b0e29d9526a28fbebec0045d613f48d13d99e4f3b986c96ea451b0532b0a3d86d763ecec211dfbc8cfe0e3c1826645a3a9236b0bbfe8a3de768f9a7f39b384946907f49e351b53520a20c3f7279c2603c04c72bb9d544c41af4f6f48063dc3ab54877971e662c619e6f3861db6f12c14acc3203637d718984dbe3d4798f3033b46505c196036f9cd187cb4954fdec4ea9f1be0f28120236b17e656bab08a02743a0167c0e9a296b2e5e0f5c8a1d667934729c4cab39fbdad928c3378a20a2b4bf5484a6c46d81f584ae1fb46ca6e58332e641d62e2add8e3daea9e4832a8b1710e8626a6ed7ac8e19a4530bca48d5c45d74e2aaa08e739a8dd4ac75e3c4f24d93f6ed5a00dd23e14c36edacfad3e2d5584e1599e33920aba08646464670b8854db84712c0f2012a1849b9aaf9f27d3a1cae9e27613fbb333bcd5d7b1759e70605ef2ef7784f7bba6a95fabab7b3dc45f0245891b095bd8b07d9a6755542d3b51043770c245b1a7b1d80b0b6aa3357d5c8da4534dfd07e5381a0b7ea4303d93b2f47ac28c52a7ed90e025542fa11b32029d3df58dc8391430e915f65455a83ca3da30ed90839d0251a6923755bb0dfb1d9cb5f7982fdc604d0e661a86b6d73056ab4d36cc494ab03210fb4650c72d000bcf0b27108582a3dbdac9bf6100e300b5efa780c658151e0477f8005603abe29ec0ffc8630419b3d2147dabd29b470979749dbc52ab7eebc005dd51c279bfcee3dae1dc98389f55f912c9a3ed1167c4b163ffbba725e39496e5d4213188e7c7561ed77c4b19f07995e58fff5c9b6b8d80c703705437d49586fb3ce021cf0748460f0c55e1bed5d32bc1dd0df402d7478adc14698d900ca0af29e57c90b9116140706c0ac6932e1293d660fecee78bc568d6169e8d9862ce236ab5c4b529bbd76b77d4bbafaa064c6b48d5fb40952d707e7411d45d21ac4817a18dea034f1feff2efa2e38ffb40be9745cef0eaa5c6b86d46422ca6af877572bee232d9004a9de33ea0fdd2cf77d36b486bde227b47f7bfed3b673e2732e5f7770e0246d4182c8910f63ffbc8dd6129e8d7d2389f7fbd5e9aa68d2ca50dda185e7d6a1df1fc6b7c4cfc67d24491ab84b55f006b95c2df4b3723a7d0ce64a9b267a6431e792d9ef3c4d76e8b3937f1f38605ef3af64eba97c49987927d8af65eb1ef0dfe4efba66f569b0cc0c1bdf42c33ea293293f1d0299a8b4495ef5673da7e822532f2b03c52b5a1db31d5078b1d79c035592958ab4a7226ab2d49e1ee3ad17481a109c5b024a9ac736a391cf29b5a60596f65943143e9249c232a291ad6d24dfb67f9296eb2e009e6c98baeca1cd2e1103b8c1fb273cfe2ad9585081d0408243ee1332ad4e982c2fafdf80143bac1eb977faa6e6c6ba7bb1c50ed2d683285e81de902999d6537a852026ad78b52f553a1314e929e84596e6a08d4c1a9829290269f8155d995425eea586fafbf9ea5607d23718b6ceecfc19fbeae37dea6adf7878a398b3ad27eb0940d0d03ba068e0e04b4bc90192cd386c239302662a8a602df0a7d4c926bbfcfe4ccd0f67ca1f40d9d393cc9be31302f8514064045c87da8d91dad35e0afb6f5e0a846b8d7d55eaacbca4d974de79c515e267905fe29e01479926a8f69508f32bae44c4a8fc16fc914a49e90659975292330f37a6539f735fb5c27b8107d36693160b2dd6e672842425be29b4d604cb3bb98c653f31483a74a8209f39723b3f4d21ed83280ee3139015bae4a24d023ca943b12f66c25d4d31311416f4707287474e3186a9e38036326960a6aa08e62def57d08958c2809155692f6fa95153f70a2a2338f35bb59da1b529579f45602395299f163c9bc838bd32902b34e39ff00d4f29eb63ce120c88b632d53ec968227b2afbe173e7fd998620c14852826b86842f8bcc1498dd9384304f743c758a2224ffafe589c587c2ded98bf64852863cfac436abc50b37eeb95256428a153a573edae05abc167581ed88f8ecc74a12b1a26c4bf2658b9372e7a8f246d22dc71ff9c14af385fc5d84c57e7ab5f40cacc014cd19af68cb2032aaf47853a24870e3083e915262b978d27bd33973c8155b96cf2c8aee53955cb53be70a08bd1241062daf842e655c2fc24220039c89cd5252c47a3a7d0248f919b03cd341fd02b2e809d19fe2009891ab3a7d603f42c189597a47768c0c1e3a127e69e90db0594bf463bd50833a8bf18793aced85d246b059e7890964798e3c13db1f3bfc81c6d10d5dcefeffd8832d8005e5a458de41cda2b0f98680e8e12c13f00e6703928cd064f5a13c5ad7eb1ec485caad7eff28012ef044fd4f820e021bfe67cf2718d11a3874cdf53b8ce2d4f4e59461ce3032c336459cafdb5b3d3398d2ad644ae5300650da42d725c04c17e4d4217545f75e0f2d94e33d91a446480b02c99d4acc4ad8e6afc0d25f06d226a7a11f171825532c10176343df4852c109c3103a67cab0d500d10616a582e5f4c59034b0808fadd6beccd716226346803b478c611f4014f650ae0426d7898b96e600f8606640e8bbfd827d9537bfa1e3008ee3d080605d6cccb8b1644dd32d0426d76e7e98b0b968f8eb5ee4ac3fd0f7f7d97009ca2a92c5d07471a95d33808688ab8989fd59305f2ecacf97e26b09166fd679ef7a5a107472b98a1b3897683aeae95e72c7a3d8cf11354b955257508ee7a04251f2ba5aca9e8abd359217b7a3a4504785de05f911ce6856db454be3bcc0732a42630b02bf36ca262f1c314a8d1d3f2dea943c42c39ac52711fa4f4ebb04b8f5e8d67ffd095bc2de468d3b08f21a78249b592f80b2685c4404a4db4358b651eaee51b4cb3c20e13493bc9803474e3b23b7f613f45ef6f210197b97286b27d6bcc734a9155f6ce3c0ea9702f9238226c6d5f01519456295df3e53cdfc68708e686230f9c1509acddef6258663e372d1bd8899f3f1d8a4b57118a358bc75b2373a91d1a108f4c89bd1f82ed748e48b1095a0aa87336d18d5fbc36bfebfa57a1799b9a36b9e09e0b8b7498db13c56fe17da0246b54d76fa2ea7ba9858568f7ba43b0f6bc13f604c5be6ac29955ca78c5b5cfa2d8255e98ed168a63eb7390ff890cb7c637ab6321e67a3cabef18b3e24f95e14f57d10ed2e21b96d9a1a8d70c95434193336dae69df2850060b559b80ad544b40c47402d151d71b335333f1f414c56436bf731075b27e2a681dc22cf6d68ef879de0894c05189325f3d70ba0844611b5956308c8e344a56e6f60ded946f8215a752c139140793851ede4b8612a3846a9b57084a006edfd5ae44de7f020e8ebeb354bc28f03b0d2964e1ef18653c6b0b00949930c6f861c8f2d0c8b3a4751ef7349cb7686e7105435d8194364b225e49c71633cf647f60a2c7e52e8ec97f722473be79f1af547202c708a08706758664ddf70ab9b7edb7bf39ab3dcfa91b915c1be8aad553b8ab1ee14670853c2fbc1412d2337cc09e34b87dd0edc2aaffb07c7ab4b9841ea0731bdfdec046d4394517e844484e5d3c6a9efde4d982a1c3627bedd13c0aa11632981a918ce54eda685f9b2d41f57743b293b310c187b1d5de71eab7db41531d7b9508e7c0a2cab0be9fc1e83d0cfba948d3f97bbc33a27884e5f4926c2726ef36826f1fcf98362ec2433a54f7664dc7fbf4b3cb4f91dcdec65e0aaaae51e439befdfb66da437c2b3d5dab6ce808a2e0a31b82435ce6b5b05b05d8acdcf22d74221eedd2ce74a1f912440ce0781d67838fc35c0278c2ac4a9026cc80e94cf189d1aa29ca9ffd6aa986c9c7e78bd2150e6fc72c61aa04e80d3c45a8ad27ac57dd7f9c63493f3dd7fd69a5445d84480ea28f613359bf6158e44b57edfab0c2a4a6cdd82ea5004505c9e8bd248c5e01738a766123bb9e824a533cc920371ed8d5b834ceade242762001d79f466632b9a3bb2d3dd45559c4199de2ee913fc26017f338f790868f6fda278c249fcf2fc270c163d4f56d19f3934f99aa8780fe600c6e4cdb3add03f5da4fcafb9524ee20e8edd6e1a0ed1ed7228f2a41905bd44d743c320291d241da993e75dd682a99596246b176c0168f43fa8a53a9d9d533ed1e754639eef5b07c0257a95b6ad4de5c9e0974059f0a23bc0ebdd6164ee867500295b711a59052f0cd976df6339a6653cd402ebe7e77bbfb79b25e93a70b4404b1c946fad152ed8233dbd9b111f0a2a5d0754f7729ef34bed5a1299425d5c17c819a29eb6387b6d41d3565043c4972d6c992a884e825922f1062bc23c0ad13f661295357d38bcca6d32d3253e6d700f0a5c44eca46afd3f99448a2f58df9f6400edbdc392772ceffda00dcf2b8fff45e833970bb6ea2e547ff44b12fa87c377b05e806691937347f60c8c8bb5c804da128a7fed86fbbf67c6a0ed882b19e35d6761407e72fb6400389292c312b44d60e84051ed07b42034205420e6e5c4f8bfdcb2140d37aaf59c6317ee5abb454d80fd858b1d7c52086c100ec00ba8dcb24eeca770b7341d0f82dadb22f9fea35294a34f9765470019dd04f63a00087510ba218953c96e79be13b0a6d3cff7014b6defa625f14c17297824bc47898c43048d31518c6f9a39a9ea8d2c383f960e29d4dbd1e404384fd909153ac9889e5accc0b1e844427a0e41def24eb78a696772190104c035633c7229894eb81c75236db0a4e9b09589c13356f2c85bcf683b2820346ad10698eca4755e56f7b69494b0607b186f943e6cc9650234b542a1dde4e3874c4bfa81339e1575c46533ab83ce202cf706686a5647d97ddaa72fcd3c5b714bb4ff301bce99900e8ab187c44e0f9d2719970952d27a07bd2ee0ee7ef06c34f0f611e60760f421804fb0a3d8beeaf58ce433b2ec96e2cb1251f3cb55572aef514aee14b7ab4d858fcb03ea637a9857af05f4102544dad86e6ad8610eefc91c8100cb8d99907ed365c8eb9eb94c51986cc8a255f985754a22ef548df1f58c8d6f86e9cb2e3827eddd39a9d2d317a5c325ffbd4e3df975914b325aa5048e8e1d1b28829a4a7fca5c71edd489de04e44010e4a30df30ff4a7900e6a7ad2e5f09f44cdd167926e5ccea99d718efabdb5d79462a44d0cf18f26fcf172e0687bb88415644ba1611716663cc9ab8d53707af29e11e4075a3e290167b72f490533fb475d17d0618bb933e73b7f81184f3e80be551f0e4e273fa91b168ace74f1d4fbd6cd1f71f1e6b3b5717815d988c88a90563efe0285bacf006d5e62bfab13460edcd65bad597282a220e6e86e5f550485afdaeba3c4624cb0788d208dbfb07ab4b31d31d738e86bb04bc71f641580a2d9ff455516e170e88196a454b13d12bf5f8fcc99c1c2359e9320c2d0ae517bedf70cae78a1bc1d48f5dab1a56c1caa7a9dd10ea13021ea2d4455c80ac792f1f747e108c5acab25c9db665b650315a7d6c3852c701d06e4f528771a9690221d839b28ab6e56c7d15bfe033095a7ebe31988e77dd27e76c5e070083443658ea1a47cd9ea586ff68803c91b8dc5cc2a2161a1b79f82adcb113139d371b7e2a869b3db9c7864d75aa4c70715db8af1116f3675bc9060f27227a5f5e136af3f9d454c8c8ca19421aeac1398d92c0dcc41d1171588ed3a3b74bc13ff22c7aba43f64ce72c424d6974231ef60fa23df60741b423e269f1e3ac796a9ac7ad101f4767c3326af0563b30746339e47d48ec30321ac1bbce4904602cdb2c98ad0f42387df7256133104cfbc933b38dd5ff6f9946a7534f7003ea9a5cb0bdde91558a84179183f0f1b4250d15265a2c38847a6fc9d7aadc075a42fa6207f6dc5093126589c9855998805a80dac4f2ea013484a4648c6dc1b902516771295dc34a19e8d32615a136019987705abfb9a40695150912b13a0ee587c8bbfa9778a541f88635d485cce55bb3f39d32cd4cd2dd482505b37817122a0d53c0c5949624336541442e5be9ef18ba153b77f372a0afd17dc53d1cab8047abf53b07b24b24397920011c7d55151729822a8680376d8be4352d1c0d66067013ef6c20d2fdf6dd3544cd40bdbb7f11c165349078828ebf2de2e4a24fe9baddb0389645e8b6652bc09d108d033059d31261702a7a612b518d95cb66dec820d3f477a95f72df01671acddf2160d887b299858a99e9ec99705d3e565ee50ff765894d599c669a95563cb7ec6ce62c44251ee04bd73f0eec244517a9070cbc77a698bd8cdf10d9059a98fa469c672452a0ffaa94ccfeaaf003452008ba6b5b4227d2c1cb6aaf672ca2bd8124a3f2649e2df73a8d90a8342752a13d2d50a42e01e0ed1b9f80698136f8893f4559e281fc370a30f70ab18bde61bab882214f3bd94949628b3097e02d9721d78697e4965d56d6abc038814fb3633252a005a6aa30aeccb37fd97a14901e4cd56f0aae335ec61b5cfce76924e6705580a809a1cc265e03b7d9b8602a026fd7f85fb100765e871f0f1e4576d75871bd5a3f502a28a8205189c731ba51d8993d40d622077935588c48b142da898bed0eff468d78378f8d8c32341690b38538c9c15c3afd71eaeeb98f4c666afa8002e736e4a2a8e53f5d8c09d996a963c89c7b1146660f85ce24677582a61c9d7d25a0dfc74f3501a0ad41fcaf83933f09faa32411ceb23c9668354d2d62e0e74f9ce6df33b4e51099b4471a1bfea5d76b56df44767c89b4ec97b6ec4f7ec019168bc3e5986b730d7ff41a620140ea69e25215519de02ffa3b0fabd7fa6646c297e361d52177bc25f65a2c12912f5f463555b5d8038efb1ed875b04e6fee027cf2dd1ecbfb67e1a9900fb926c4c6c61a364a0c1ad6fe7125f082dde82db5ee90ed51c185917cbd4c9ec0a0ae770232d4dc79d63b08f387dcd1a3789f720e7e608e2a01e54f441027c4e1c9251c75b16a887731b970621f411013d3f6a4369210a7c02784f47d8a2881d1d408a27a0fbd61ed962ac0b299ddb148df8ce55229604756656f5426c4d4780207b3ba58a3ad965c73e92eafc034e5640376941b55727e2d91926b39ed53a20294e82be6f8fa1fb450e2cef64d709984c9c8aba61bd1c7c9620712cb0730be9586347bff76c7606e25ebb571043f7c0cbb4c30b4e9cb9b8adef10e8706dd56aae275a653b8205d9db09be9d705d28f6900dc761de85a4dc4794933bd385b3d513ab3b84f289335947ec8b937ede33ace21d517fe2983c720108060da8d077c181bdf3574f02770fa8de83236845ab7d3eb6bf53ce091e400a27a1fd86b947477d049ff66db7c22390c0aca612f1e40135359a3044bdf73fc51d2a84971a5e99f22853e1400512209e726af180a43c01da37793f8a8fd2a2e3194469823579bb9bdb6fa98d3c1b3bc5f1d7c0023884a251d86d7487a174589407f6cf51dacba36b541c791d2fa28ce1d65c225096acfcc388f0d510e3261c06dc245796e613e1c65e8bd57932d90e827181598877048d301a11d7027dc9fea2c879e09335f1f37cf7f8d9037b649d053eb18a1ed87a4b31f88379df157812aca76ea5871b72359fd81b4ed4234090ad9db97a02e28a53712fbca7937cded3acba9ea777c5996c741d185ed1dd4b75634f3b5f71642eb204025534425117326e40da93dd4f31523be43540a9dee305bbe87732b57deb64d04aaa6980b6691148245c14354bd7a2f13e0f5d75b16249c384207df161879bbe6ab00ba12552a896f26e44479993b5690e2cff08809e1b69bbb523853c02b0e7466c7a65578bc0dc1e00d073116c8bd94363a9861ba4cb5b0c74535c5f6cb061860367f8adb5e00979780c0f0bad688774258cb816aece31b2293316a625c8e0b2c1d3a8341af6de1f6b51d833e7050bf19f5e2a4de2157562260b1f970642916f3a810abb2822839d3523e901c852cd60244f6352f612b4b9467150dd83275d0363e8f98b1ce3edcdccfe1b0f0d80740f0ce8b9076d8fca29a9ea63bc08099cf2c2774f2da897caef968490542c44b672505c8525f6dd54d825f177aa9da56172147d4e7a141b1576f99917fbc9493e0002c271001743667668b1caf0513c3313d257bc1df4d779ff241d7cb6c22ea05bc55e1cfdf4751530fa46487f25fe070d0525b2b60f2ac1717fe49b3d61743e04bf541d9c89acaddda3dabf952acadb1a35f509e81261609fa813cb0d752d083544782282c22ed7bd35e06f61a25b06d3591922de4166e113e52fa8aeec4c8a2dc5a17cd4c4e88fb25c27c8a433c9e24a31de1fef6174783cb55cca011f421bb1d16cc12828e14b0cbf35ec2273f3c41e0104c8a35825e07c551dd407e1ac161d1917018dd8c82bf14b8921afb9732008959b85601e2142e85236c65b6e0ab40f9c66a9b42689f1552f59cf23123ae4442b9f7403f8867546edb1d6cc0efa504c3d0da62bfa8593552e7611b68c99fa34220a75dd7c4b1272832a39ee64a10094c50e34c11c15f2ab55a876e4fdb08bddf8afad73b3d987969e442554442c403a2485513eddaa320deb751c9ebfeb001db792eed96668dbfcbc98eb812518fd39632419ae560303064a3901e91e1cd74965460c4a1b4917c263708f19daa224a0e084405060f002db43bedd6398a4541edee04777a6f508a1d429ac384a35cb052862119c8228845d2ca0c5059902d139d9c5d5aba684ff0f9130b56c927bec670f0129657048b9dfcc1821fe4ceecc78aff0840f067a25d3626d3f097e5091bc9a809b9e84841b7c7c8d1950396dd10797722f2a6dc7d5dd0da042eff35469af96da1813cc62d4778ae1081c4cb91e1d55e85bcc6f3d3900fafb47bdb78ede7b5b7659d3a30bb1791955b2c1ca0cfda0c7a09523085a253270bf507126f6642032d01e1088f5b83ad2d4c851157b0451fe40af89c0712ec960b97d718744871b81224fab279add928ce55cd6964d43778f16a6a56d08787abdb3a8113f9a84c7f923be3041d3f571df03a0298d966446169363505e685e6cf93b4cee3769d0464b3297d171c02b3f4605b573824adc18708c9664b064bdde23d061aea6a07f57b22473bcfe5696fde5e51e00cd1801303e11c357a9480b722dabd8a03130d102330c4504827117b150d641cf77d8de7ad7c8bc8080031f653ae47fe3e77d78e0763f8aa6b57589cd5664577be85ba2c642b7a0e2fb782a9febe94029a48330a2334acd0249ea228677186a560bfd635927fd998ec04631a766d4c1470fa05fed6090fd67d69f8190f7300abc8a61fc260b0a690f5088ceafa612db6cf68aaca9c05eb749365b29eef8e6f5acc2d7dcaa8a1c60003a9a80090e5e01fef4a1824ebf08d2fbe6df02d55dc7e0464c7904f5e17013c6e6141fe75e0309301a452d355001a671e4a52d27c61bfbcfdd5e024e63574a8ba815bcab50c0832901cef3a068954b9b80c6f9e17a354d50bf09a801d752bb2c59762b7ffe111ab4a0c60a002dc9aa1904245ec55eb3758c91aa97356bcd2c2ca53b254917a486013b2b284ebd58ee2124852418306580c9b52ec3c004d68031abf1370f4f4f389d8342186afb24cd12a12cd0470a5c567c4e5c39c7943f09fc7fffe340b6f591f85f4b4675550cddee91e16a5e4772e8e48f50d0cd7a7121cf0e6f4e4eb4f0e0ec2b345ec9886e67ae287946541b7e2903f30637b09e6b33838b7d3d7ddc501e8560837342a4c2fe17e18a7b800d838d2f969180821902579dba2d544739648a5907837954410bdc3a4a0e1251f07361dcfc353968a4d485c0539c2ff646e841849aac8d3ff0a4ebb0d394bb8ae6e5c5e82705ee26de61d2ca7e1023b7db16add608943bbcdd243988e098c7980a67d5f43e61ecafa770c0392eab78636e90578e324dfc2798231f40c5548057395f6b8418f08433c82431f76f7d0310bec01375e7a56542837eda9982c3f3922b94a9aab37e6bf595a8f58293302790d6efdb97e545464e140348b6054ad617f3065cd615c52822e6b53262402f8fa52067246fdcb472acd8d808f3b4f14012344fcffca5b960675b4ddb1465638eb9e1a0f9214d6e1e9c84f87f404456a0f20d4e8a0c1305e86cc8c0645345980e6bcc5ab2167451cb63521a121117723e48e1f3e77e934c55432214728d1ec04633b9f9a6ef6ced941fb45822711c1a4bb39e746464019fe4a25f112e53adaa1818194c7b90109364d0d07e84d5ca4d895532edde23307de1046637acef259454b96448588c6ca3c9352c3a6eb4a4570df2d7f4afaa3cf5d38d6c54638533660a2035811ec96382f590c991e7a42acd362b15e150aa024103c4d5af22485032ec7f5a414f8903300b18052ef943910af4743ae193a5b816234f67b033342199e5c5c52904ed0d69c201269b7021c99e0b35ca8b21b0d8696d2893ad84f7a777c3e9a6939deaf8136c0fb3eb84ddb98a618ca374056014b8bfff223cb5617db5cc15421ccdd282450f5fec732a8976ccc78bd2cc4e977ae6210645a6805871439a4afd41931c164491e38ee673f5282e8e5ba4b53bd6ff3e71af61d7cdaa0de4a0b024f6bce109183284ca8ae81c024c6b7e22422c1f5527d00a345a1f05098783ec62a7c1ed57fad2964e9d1c3326a29d4142102be564cb094615dc0cad78702e9156661e9c509a26316b9a8ac26498ee32a2a2754677988cfefc6381fed421f7cd8ae936999f94efe920c867c2c1f666bf46f42449df51870526b08ef1f5631a3a34161ff48403314ff21cefd3a1f4de3bf97fedf0d076a57f34bdf593a4e9e04fe7e41867a3c93a06bcdfd0c4e0e1ca4b17196ac23f8f66c23c80629918d641c0aacbe43c788c502dd2822944d96483245dd381a5a754c43b646e2ccdc4939b5fd7184eae1ea46b455b2a8e7b33ca9ffcbc066195700b0bef66e096cb7fbba5763d3d12a9f987b929949cf5425daa3cb58e753df36a904883548b7bac7955c4028adcf936f4fd80aa70d0648d2c2cbcce019987b5464f72eade470ccbef9b4262e999e6574aff09daf58d86581ca012d7ecd25c7d1781047940b81b67dcfde3b7c86cd7ae454c293dc74aee805a0b1ba756c0dca620ac79373aeee9c0ee34a75da640759999d0e37c0d3471d0a876ccecdd11518d2649b3ffd44c3336b81fb4c8e248e18911d3ed83e75d1aed128ad80b341f3e9a5bfc785a3d45a86ba140250953a25c2151b96e386e3843775f8cfa50dfc47d535279a5ee391dc00f2ae39285e641ad4877503fe2a04ad15d76e58de0d4d974ee71e56bd3759a4e71847fa12475b228dca88cf69174889e128a5917c001c8badff3123ecd391ba733f3aa7f2bc418a802b1343ba4a604641a5ac4b5f3ca5b8df2bf0ceedd791ce23eba6e516a7edfd7730167bb2479dcc2e8686160cf8622c5abb028cefd51fdea2529778f1ffa33094c02e407df189c05962b67357ea53dc72b1e1f4c5608234393eb7be3fcbf1e46711a738ebf993825100714e0019b60c0cad9b7cc4081f688242dbc7365ae20a1825995e2c2fd84cb978a2a9c465ccee1bed5138738ce59e2607994fa579169506d20c93d5b7638d31c68fd3adeace8f73fbf9f6eae08ebff246b6c93de2b84835d8b7f3bf92a7028408ff0da6589540a3891afb90c4018f1232ce458084b69aa677462dbf2720ad4852c227de4a8912309825c61f1ed1ec084c9283444dd8f14d8dcde8606b60951108e522e14d404d606756031221936115afbdab30bd957f07954958f0c4918f6810bf6ac4fe9728a36527986aaf6915fdeab07dfdd13ebd31be33b459d952cfb642297e537fd886e45ef6b2c7a8c47d583e0633554740b91c688ca3a6ba7d0ce10fdb9c6ceef5d4b3b692a367be31cb964c09dce2771987fc05b914f7954b84bb55d8aa6a2a3420151259e6098bfe3c39a3c6981c7f2158f16a875451c9eb4708aabb80ab9f4c421b4f042de92882a75bb3bbb030a32967e27af91360a9fbece613209bdaa655c1d8793472de478f5e0c04dfdf1118b1e760f7dc7618fc53790438a7f82c5609105e44a7e0c68bb263222ac3133335f4ee7e2192be33b66fb0d22cbe89500e4ec1dad674d530ee7314f8d726c3c1e5f171d87c1e305c51137bce3a958e3c90249f5807e58acba16135f2ab88376594ce00c343599d0f9183f482d8cfe08857491570cc09362e5a69fe6998b3332fc470d69332bd7b60803470664de3f6225a9984cda105237d4dece5c3f41465fc4623692d650f0e81617d721cedd397361e624b0410efd5f047e1fe4a35176ebf45e1edddb66627b9cc6f585784efa08b39b1665edb02cdb67db6adb68c4bb9cf438f317f588e957c8fe677af6524293fd90e01886c2fefdf7b0278f26375e80dc41c775e958a679627719ab59b5b5e4276562ba02e512fd87d5790fbfb6d62b7824e4b7d780863789084c3e848aa127001ab18421038868ca9d146605f49914e9876c5973c94d4c304e3b32537d6854576ae550e24ac66826015f19283c1263dcc2dc0d70dbfe0c72bbe4ae293f74d2b4609cd9beb3a6533396908c82d200916c3958573f15aa89d03e182bc5fe93596b10381d330bb513383c1c775ee2cb199c62a96ca4c212992dd77291607f2aac35bd5f522e2463e5b42c543af47cbacb9819d1e96a408b97eb542284d29465c728818f8055be1699693e4c6039aa14a971a541a8fe5e5338e200902d0f573324d29c855572d84d061ad1002f2509b40e4276f69d09401e0f21620285037ac9159c99d835477b802db212d606940d30b628119e94573ec1f2262cb6063a3cfa1682e7465dba8025f4973e524f6c25b904680154423e79f52ed70849f0b9d237af08471af397b1f58a8d22c15325b8980ac8c30b0f135c57c232f177ae1da58773364b84b3716746cab1427b14757e8ecdb4620a7c48cc3ee1b38d5a36a100fc40994ce67c306e57768feb8f4e37b1f563e72b7b0dcde5f82880eddb237b13d05c4ac144452633932fe202eb6feded80c20a747b420faecd8fec81a3dc664b5f4901b6ba5d167d0e083b5b831fe85d9b16567dd7428d95034edeaf293a8edf223360c069126d05fc4fc0b83a5a83827c924568518376b01b3bb3e6c16cc71bbf63acecb4e9d923e46be6f93eff0c075cc93d656b65fbe8ece4fc16cb3a84fee889cd6530e953b14c223448d0caf12184c5332203ec27d762ea547961133f8a0f4af42ab368806e014712ab6e501bdfe33f3ca6cf465d8dff07ce43f5b93e06a3d81992725a3c6374495d98fa451cf6338047f0de1957b2b2fcf351d485e9e3c626be8848b5dff09b88fb9738478796891384189267f7c4fa3e86734a8d649e98cedb5a86ef2a37b611f5f3c4124f136be7110c466c2870951ee252e657d85f19bb82866196d619a7b5639c6158077b5bf8487a40ff909b46c34713f496ea163d8fd249415192bc28b3ae5ddbc9f31ad355eba55a29c23a1970deff22d113c903fce7eeefb6440e0c0ebbb4c1c866f4a73f35af681df5fb1947701133eee2db4a3522d948ce5049f9b1963e7da11f22062be981d0730960741060ca9ef728249ee04853c73e3a93857405a707b8df6649d4145953f3e1f152a4b0e81b69ff4ba15518364cc01404e1fc7da2cc193ebfa8442e107ce87cb2ed084d1f48ac48332ad15c4717bf72fbf9d52c5f97c94a6eb863bf49cc64e2ea55e031da68b4ee670a07fb9376269d1808fb7c1a53486086b8bccf85935e70243dfb1e994ebafb2bccd980b8ecc77603fea51b57f4ada2ed7a07fc4e93e6dcb95123465013db3f790746fcd203f3b97213ecd9f6ed0b13855fa1f7137305580919108f7d43f853d42fc43088b7e214664e25848d4e93bd9538db0d42a0e7568e521360355893c697188da9c27d8f11953a8a3edb123e93724ea7e9dd2443b4f617848f3cf7dc717b784975ba9ccc35835c4af900739f28bf8545453764dea50afd16f863ae6fbc7a3996690e140a159be6e07d5dac3d86b5dbd4c865e2391e5cad1f5c65555bbfdae227b10a4883a9bf4b0847d2d4aa9146db4ac64b0620fceccb8c3e1bc6100be12ed17ac2847e2c07172298823c8ebed4f69a916c4266d0269c952391243d4da2eed9bbcb5f29aa55a498e5c3b4bad6fa86adf58546692c09ffe96b212e555bfe4b0b9d522f05049787340963483e9db59fa9a789502e0b8d9aa34eeb1ced3b456fa2eb039685a0d20a5d57f1f5d21fb498e6041b4beb3549b603ccf70c5896c6835aba72de2bcb542aa59e8320b3ef65a54fb1c6dcae1c8ca33e56cbfd9cdd8a79549d74b8e007480d72e82ae0ab1fe642b6a568241e07c4eada761196e76a444f51de88f4b1aa844901be826efdf89e0ef8f4795b24a7fe4f57760718ca7fdbedbd76bb36cb9761d89980b3ca96e44238c101970bfbdba07d290ebbd156b5be5a830e87dd5b888d5505bd3c7527a87bd3676a7489ac5df9eb00b8764586f90c25af73b6d505c00ad1d95d042184c25f773fa5f608ddad475f438f6711af4e28954fd642ec39fa0d716529b90fc0f37cca1a62150ed22a1dca5737eeb61f33876d579d6c28fa2bfbabbacaaae1abf11fb981c78e71988b024e3fa151b5157510b82d75ae5aadf98679743780fd661cd8ccf7a391068726f8f6838c5f176092c1d43f24b1503857b94f89024e14fc88a5b75f8a0e1b360ab1a5d4f55fe0d615bf0ba611290b8bb1570de9fdb403c34b6742ec04d934bdcabca661f2e8f3317b3ffc9bc8e1899c285e705bb24197a79e61b8e02126c076c40d78c63ce740f8f5e30856115f4239b73c8aca74a51166fdaf7bd702d2612abba555934c05cd859510aba1282a0208f9ce889f52ae765f5cd5220615539244a6376c9d3fb6cd89798018f7f3b6f0f83c297920b48718fa3e2766ad27d4968c79ae7378be8c28da6cdf6f566af982f309901450f64c8e2d4a93626a6f92aec310ac6f73a2904dba1318e2fb5ea0495d41328aea72aa3159a284bdf5ee7e92a817dc72d6b3f5b212ac76f317ccbead8f8af4ccdc3f1e5f39923fe928ed426e6b37dbb86e7b0d2a94367efc84c4901412d5017572e5d922e13a0d77a227f42c9838d6781776662d803a9a4d8036cabf879b41377427e8d849614c8fd5df89224536ef8a64510914efce0d17a0c3587807d50219ee324e9f39c66542f0b50cd6ea3a7778e2ac0c9024823309a381ec800624e6c6a73d88537caf0ab5e872bb873356bc17af416fbd6915e4c290aa86ca200a01f92912b76dd03c45e6ce21a395af3b7aa11be5f70ce88d450bcb6aafdd17d6f880b47a68e2cc299737a7e47aeb0dd3aa4dff5980c583195bedc475fba3da03a17970d9349bb52cadc9f4f586eb121e5b3a8898ad0c3ae4e0ac64c058f0f2b9acc9662633ed1221d3aace1e3c49e7fe4b1944c564bd32a71e44c4c72d44ba57905517aa553b851e8faf389e782c97662f279ff5ae74ec4fa573607675e905f9cd185aa9a1b1eb3e6f0bfa9c13aa230266e2f2ef0de49e814ca5dd2bb96505a3af8e860671b87420473cdbafad020cb55268049b48878688fbb70e8496c7a30011841466a5628fc147aaf84ac6e6e9e47da18705105a5fdd11316cd8940d05710546869b3a5fc0a69b505cabe9f5acce613b2e1cbdf8124daa453c77c8972d6eaec5c9feaf89ea67908c542c83fc89c90a6602d31ef6dd2f9a08bd991fea1bb5a9232bfcd79081a58f0ed1979365b721e2d5e6499ddea47310b90a840c894e70531db9a141fb41798e02f3d0d8372bb3e038861f19e8d856e5a1b739e18e8289a432fa6fcf9cf925665cebb539eb63b4f0530b83457b63f6287cd5d81cdce9813630c682b9a9145be93fff280161fad2d8464433e36fe2de40ba16b3af515cfdef90005f48c55e3953451bd7486de010f60d8fa65a59389dd9969209082050841060c5e994eb4ab32723aec293abb065a48c805b9f64199a6f65a946b34ec641290037bdfcec3f9ea8e83a59f5da31ae6f72ac1f741fcd9c950dad812445d428d84d1b37ea8c31fbfbc966d3027ffbe05f551f88af96a2c23811bd81d0ab6199e514078b805118ff61ed7034abca2083a9246b21a560a145126dacec26c0ec7ff5d3e932373cdc3f982a0e7453c6c98c0d6f2aca6b735a657e2b46aae76b981a54550e33b98959d9747c01ccf03199ec39b5300f30269f2fd929d2f1bde784b470389102d527b020889be76ecb5bd8e03b08ee0b11294b7e284f6369828f744ae3d174b00e34b907e46c33986d32476df7c980447b7075fa7067530ae141f7d609118abd90fdfcbc878add9bf98f29b05aafd2fd8e80696a28b3ed4175ca3704d3bcc914d83ef7540580fb838276e31d60a8e8bbb1556cc7d79a0082817f02f5cc634d2040c6680a0e4ea33b922cf3b9f2e7e5d14032b8efffee58931214120ca761fa4dcc4cab77218e996fee5edc24856a2290f57c2b8ee3851d5d91a5bcfb8a4753b04e7f52f37df57ff0252d6087cab293e8d70ffb8f75f0894d938d0d4814c03e364772d97091e5a98f60cc965ceb0752a28ec26b811feae5af6f3a4fc7b627cc98eb1c3495aeabbecfee0ce24445248a533ad40534284baab0c79a4ab89e29913ce790b947d4abf98baf89afcb76af9ec8e242e41a3bc5c6433426a09b8bf5429823d77eeac9bc82cfa0abf5c1aa3c5a60f39200339b5415548b644c12549a932391846df4e7b214a1c69a525a16b2947d4640cce0fe0cb540207ec45d7d4c30269e614a074b877d26ae0d35800a7cd56bd113c7207ef79102a0f37a2d142ea2ac3d4de3ba6867ebfce1cc85720ce5380b919e787c4462b404badb908ea090f283a22c6b1735825830108484dd761dcf36bc7dab6c19562af2c8726174f9bc934206c306bb1dbbeb73a2d21f85495851c1e7b9e98e9dfacd4514f16c2c5a1e91df141bbd4c4deb5a494a6830139a674c92b859d5d4e3e8694f758f34bf333482b4e55835b09be71b6829a2682d1476d31b7d67fa07d55bc0cdca982457b2b284ab5f7506faf6bab73409a77d0c1781e18b5f83a329cd0d2d9356ed47ad40b5bb0b4d85a7b770f6a39a128050fabb2c3e707773455408978eaee21c3e67c29e338d145d641945f890e67ab9473499ebf4a1aab0ef3ac08e6e60514307fb1abc03490dd0c32a9f679cb98a2417ec1739c2ac79d8d6f382571c9718aef9755e04423c76951e47cd099bdeb499aa4acb54cc6beb5ac50460dc25850700d7b707b9107044c2beff8932be89c8e0f06da1793786bc62df36596e0717e583c501f8ae9e57fcf66ac3d713599ec7083390024a0f773e6aed936500c41ede1a0e2e1607ca7541271fc474bd24349ea876c2de3941790b570dbe58f7c993a0826afad26e6dfa22a312c2e70c5aa6c445a430f20be7a78e87b18e4eacdb66d575c2ea5a85d57475fa04c84e09b07d918db351a2e17ace728b0d59c36fb744554a66c464e03a9964281ba9e05ad82bc1ae8a8452f5f8c2e2d4191c37939aac8418c4ea46dff0c1ca677890958c98fb11dc0115adacb4ccb291779719feaf760cae930c3469ae1e06083c497407b1b87314273422caca0b3efdc1d027452c0f5c328e4b3a1cf501238da34a1998120b75d9c34f0db764419b74b5a8ed86188495abc05fa9cf3ffbaadbbd0e86a65804a88d8ca67e60eabaff70fd3af48c5ba756bb0770e81d2f28ac74e321632fa2777d3fc8935959d24b79658c1a1bba64739c698c53469230c1bf024f50efc9d4bd2ecc55fe80acd837a7d209314f6df4a23cb71abf221d7191e473ffb7a02ef8e12e2922c548023170f7bb302c892363ac86e78425aeddce62c37930aba297f81cecd65f8a2f5c7a90b41b9a2cf4021c7b918e7c19735f98dc04223ac2986174fd0a3183ce569e2de6d1757ed3dd7e2929ac97ba050206fa3bb614f054160b34729e4092bd89bc43b5451612bf521d542dbc5022921f11349254529b7ec1fbd0c291699fcf4bea2d13a6be035a7e91d931e1a0ab608c0e53f1fa8440ff41049db85f2f70086d2223232ce7f0333f244dd8764310f55f14f046b954daa1e1b13fe5471072fae097e7692dc50ffb00902b77df83f40a8871ff47c315452d53fb0b227704d2726d8314a2887b02ba667319db3d5838cb4a38f769f5052f44a2cbcb9fafba282ac797c03bd582890541a3db695ad917d76254cf00e9166b21576a557a8208c3dfb905ce2b16a510747168db1a21d82a10a65152c648f34aab23a642abe64af4c3f76bd0804cb3fa7ff2394cb6d6da99a6122830d9b5771e05161ec9632665f27e55e7c80911918c4280f6fd43677a0bfc2884b55ed4c541b04246f3eaa1d43f8c1887628bc998c2e8d7e38cbd0ad482f6c69067e1eade60401957db0abee95031ef47daf06846633b8786cb2bb68807be5019fb3b6857c86f99ae7f28f04b2c2aa7455744411887f87ccc99875c462c54153d8f58611725f328053d23d37c66a1119b8d4219ca449c83b7dfc17d2d97ccad8297b491dda9f90e6fdc04565ecb721ff240e138ae9420ee9fc03d9891e5c984217af121f43c61bf2a6a40ab2362e240bd05735c9f9593ad9c426ecc35c46ca82de0e8f27b9c7ce423323545f2adcc27cf4d7f4f66faab3eea14d98fdca4cd0d1f6e75f4a655b70a1c21c370f31033a20278747d386fec1e3f6c9faf81464e813f9b61907e592c11a1212d364692d7a781139cdf200c659de4b86ecd1ec07f1a57de320e1ee015e294f203853694d2115f753b1773a31bb2808012bc8d583c50d1a8f982f4f96801153ebc90c8038bce51768f52d1b0423dd2370ce742fea90e0867c6cca7a5153d5629c49252f8881ffcaf292cfe43bec7338afb43da4325dd336dff3240c9a3c98a8ae405ec747a61a3941952b2ea12e2b90c94884abbd6a6d51928be30d7c49829bd877c77f85220cf4808d1650b12fa22e8f316ab5afe0a6a5786d836f41120b15855158b73a95117ede7b950898202636aed469d818d95a7f3b730f5c52d53ef1fb1b2140a1544e8b9a98094219166dbd332cc84310959ac282d1349889ba6334dcde6cda1cc6a603c21db8471bc4e747c1f5eb27dc3d021a2287b9cfb44d3f03a34f1d18305a781ccd2ccda9cf52a9fd9d610f6c1f951dd5e22c09e6cf591a5ea2b10ddd6d75eef831c9689e75d293a49349850e14c449edaa5479ee9e92a649531dda1da1e7502fd588f5aa1f060be25a291ab20bd29261ac02d5c2adb536ef2122f42b2e592c5fe267e18fccf6c3a7e420800a81bf5148891d1da461c8acd1ef542c23674a501b20ba50a371dbb51af75495b2f1c17c598b255045c330c2c31a9d0e8389ce2258d294c489558944f6e09ebae8c08e9b185c23b4cb1bf80ecd623bc30e63cc60896efb61b45b9ec13e14eb21aba7fb8d0140581b52b2463351e2f29726ea17d965de642ad9500e5fbbb7d7d6cdbc1f593e081e48ea9670516a66039864956f952a52af4cf2fcc7115112351ca6b939417643642c73c43dccf751539fb49a8fb05f55f67246ec6987a72623b00a86b55e38b606f6e2192790fc1ad56f00fa02aaf1a3a36403bcd01b9fd28d685a5f516b2ea063c7c6013fc2567287538d5f1bd000d8c66c9d384fe7ae1b0d9a6cd1dc54e8436156db077e868b85f41305737034368da91f135945ac82faf5dd0340ec3137f5cd38bec9e2d5924411a49de2b16ab9777f38a3f0fbda4727fd9e67a0e4d31d2c75141ea413422f88afaa303038610d2b88ac7457d8347e0b8b54c96b5999721040ebba64323e1e68622e95e999cddf08822b253d3e615509e833644972ee8e7b635186d1a5bc610cc1f1148e393615df28380d012db8b6f82261e93400731386b7defc4567fdc5575e0651ef31b414483e507b196456dc7a277cf400cc48fb12428aec0e087081b4177552d04faf0ae8a1533c44ae496bc27abaab0fac9090ab1831c31e9002456d99f312e079a55d90c23e4accccae714375edf02e1768e7ecd1f99ed145c8863cefecaa492edf4fb3f4417844248ff7a971684da388400f432c4b8098fd5fc5514304f5ad3d6b2063e41170a57323ed148cacd9dd37bb63e1205a980c78bca1c8b0b324e097986b24b525028078b40e646dbe431bd93b00e230b019075d2e992ef4db4ffa2034a26f9418a7709e536b6cbd0d11b6d25f7b36549b2952f318b68d263482b6139b29f7573e5ee2f503bbe270abd898752c1511a4b9add380518a395d30c6bd0688f56433e0de6e992459db85e16d1f23cedfead6d4de709db37e9e4b286f892b494d60a32f76058e7e9fbbabacc1bd44df54df481c29f7129e02713c8e46167e2a04b00562b1e7dbd9c1e0a3716295262644873333f6b4c35ea8b05c3a87df7730c124109b4ab3eed7b193c4e793c8a2e4814422e8d82949f6744b137ee61b77f338a224eba7a474c894b249cac178771b9bdc6034ab6b9d8bf4655eeb3fb1bc687b067ff355ff86cc3f86aeccb07b07ea0322f8c7533382f38299673cbdf424cd3f06de96f776bbd71d84385247f8da755a8b2f4dfee953f5c78cbd143755b1d7b58100bb48f50ac928ef27da7711e6a9909b7c5d931ca3171e54ca4a6e4720f69589522fd9814901efe248c6423b938070249a44fe88020a46e6a63d0727e5d9ca0a638d7435dde23a1fa74953f0408c8b044b08b2b79a49f84cd3755c6d812ecfced2f22d6e380b316d50621e1d06eab8b95ef0d0f2da2bcece2e2a3ee84e6fa47eacc4f3bbdbba4941be41c8c75f20db796502d5c1e3d05b159041f092f7e82d58da9887dc3ebfc0b179ce1b3b350ab476ee438c3c796d47947ca229f472addc1e37cca199e0e0c4eeeb06c0c3b6c58e1b8f782761a2d00abe71d14581dd85a63fe91254139ff15edc658ed0eeed159b5f0c969ccea0cffdd87780e7bc0ca5405d4860090c9e87250ab6bc7884ea408ad46e3f91cea979525d926ba24248ac24d137fd537a7452c5cf20d15a2826a1535bbc2027345e7ef95b731eaa2febbb9411725b3d1b212b877b17b50036204ae3719276b0c9c97c5c5e8fb694a7657e51ca6291f8f2ea1b34b1a87b8eb51c1090d74d1fbd64606b39c0f6a52117262a9c7a1423f0ac7a51ef7d5705cb2b007b22ae13e9f4fd06377454520a2e06ac1d3b28ede0859fda997bd30ce515ffef6c7a25659235c18877dbcec500bd92c4fa11f19ba02315f2759ad824e027fdde8d16d1e271b0c3a21f8eb8a801bb76bacd71942ae5bd0e4205d6341b355f6fcdda75aceb6e7576abe1be724637dbcadeb77bc9b8dda612aa1dc5b4b622a1e606b0f6d393172400899d255e1231579143df0689a75209169c34c892cc372b87b918bc974580a986ede33cdbab3097fd7b772824c1d04d5d23096e9bb0d5a6045a72ec4878f0efde6e022190f72940ff4beb0335dcdfa3f01ffae945906d5b286052735bebddc29495a80629e97469d771a3470a2e06d609a2004f0c23e1ab2519c503c8a423f773deeec154da29359d18338454cf1f513fd607c66fee3a83acfc606bc39d8f85c60c6a6c44ed07c3654216495021828bc9990792d8742120aa49808c7c0016f1d0c3946d34c8094beeae3e44dc44060603f9be0fd646bb8bbe60f2b52aaea668d4e58c3db615a29b38994aa374f75fbd4f6b4d46b4f8bfe997dcb46acf041c1210c4a906f9f00f501f8931711a01b004874b9a8f3a302e03376a392ecd98f7c8964e98f97b501b645f8a10ab7d311bc3f18460f01a8b3209ea195af4099260efe5d34fa85e64c43744418ecb0f297f3f76808401df2ee0f8670644bf26a0de70448dabb01f7fe461d880c68b9a12ca0a050852e5a8efcf5ae1fe406057af306677aae02a5312210e46ee1a13f71c14f250efbbdf4045c25cb17f8497713aed88f744f2d1edd6215940d35a2ad6c801482a15416d8f80042386e85e714d28f0519e390a1d2b16ce10df74889d30786b7e3f6cec1371b44a85c197aa2c9b39ccec92d1f78b2f7938af4569d184d85b21c2491c017b01a5cd813b03e1356be551d426313b27cb42557369c697688cc8802c8312c026f291c5f0d49e51c2166eaa3f0745c033cc1b5cc7ef0501a9041c08dea8531043c91dfed994d4ab0af94963a8d31631e25b56908e7ff9dce8963e2154d11d95148dc45106d0519b0da656066535a9a25d852c67d890e2da29c8e3ead5f15ed5550241cd158a64acafe33846f606ee27b8c85a5966ea33c85c2c3763c8d1e5ed7190dc8688d15f92bf51d5a4d3af5a348e7eec7951964e040255d2c70bbfa85c72d42c1dddc88ed2e396ca58685975e44204f3f6bfc5a4e6a2968bbb88e71e507a0f0224050120b5105ac2588b2de09f0cd4a134f38708c71eddc1dde88c476f9cfa95db75992efff17f21ed1063d1dd7708bb97a99e91a3b49aac3d09dda0715487102f441818811b64addc895e10c55350f8e76488f8579f5125e92938ae2b36ef63d5336cf8cd34b0faf3f6dd92056066ea23b06163dd2e410ea0caf6ee0fb74ac2d417839914e4b32509eaa44fecf71c8dbad9ac55e261e16656cf848784288a30d493319d6666e3ee7b01279dc00565142a3d165b1ed2f08353bd29d0dd0c8f01dccc5947630e3a11a34e8825585d69da2bbaaaaf6a6107d8e92858eaa36070bddbf78f77075adc9dc0adde4bb3c21655819f9854aec7ee590e0992170ab1e3891c8ec66f71bf4ad6c5bc9df4adc4a605ec172bd412d0179623f5d21c353e1fb6f0ac1980602bf5ba83ae4db3090031f2927628120c733d97680e0e7b891e3b9c4c11557e9676ba4e5571df9414b98acf195d20e492a87cb1d5f800269887ac5ba34cd28c79996d83c789bf50af8205bf722c01fee91dc3123e9fd81b0b9415f6b2cc4e2b8569aa88d6207a400e6ffa88c714cf5d1c0cba87f7062bfe76ec3da4138be35200f52e0d4fc12bc9dc1a083c8f895e04c472077fe433eed3199cf75f408a61deb0d551115b15ff73418272e3ddf22536e31d066afa0a5219b410761d2e2d72d1d837bbc1e59a500f0e5048b0803a052dcf6b9f4600b4ed34dae9f9b9841b054102d48c32c276e59e364e9e2a4bf00548b9c6ceb4f0fcfffa61659aa0d5920220de00003cd30fc2be9c2b554de15707609643b34fab4c5c055983a49e14cd245a7c4f1d35a0b06022732230af72eb4c1dcc0b2b090063c7a901f784679efa62eb14f76c05d6648e8de9cc45c0de4a1f0bfc4f2e9289a98517a8b2732362372a273a3bd44e665be28e780fb06805ab1263274732d87c1140276ca4772b2a29c26973acf3443cc335675f3f78e56ea078ffe6455a64ff96e50a45c8cb535d36f412b12c181a9a83b71f209ae4169384b63dea2a4fb620f0c327525705bdfe46ddefe3e449979a94ece178b2cb74904e930c4e2d144dd8083b1392fcd7039c545a2a9bd494a51e2a7ed3e208b020f680d0f230c75f2fa0cf397d6245d549b3b70d0bb58da58c1f5626ee17e26d14885e3821ff705330e9ab2dd384736a4b4408c4091276224f5e6ca7260e13c05c1c0df54408cffd1f77e8aa0d37f96643737e08aa3aa8c9b14e00eddbd9c4e797502903e15e6b6fc00f7c7fa4ff3c3eb8908a1e34715027df991ccf80e1f1888953c775faeef301b2d7d88ad4a22aa44d937823a38ab7485c85342d030f250dc0f1aff1e7679b88f2b65107aa1d18f62c9d31b4c7bb58a972e04e6ae5b1403a72654f1fbac80c15b6c4537259339cb2f113629a966db7ff7a160e12ce23ae2f82f876d6befc30def4857facc0677a0ec983ae478c0f14e9fbc30776665236b9bad1bb03a56f92181f312ed87efed875d593c9dbc334972a64d1f12566e984f4305365ef9c3d2bc12ee45a4a2a179011f8511ea4c4b5fe778e55632cd63309336006100fc1fed98dc83d8ead95ee8bddba2b7be25dce060faac3bf82e25ddee1cbcf33899313089c2b91938224e4d64458434d205993a43741f1758cc5a9b9389a2d6eed866c5d47946c3e442072cf772ed19bc3251a1ce5013703e782cad97333f955aa0824fc3a20becbad8e07639d313e85ef0ecaa6c5eb294357469729a997aa62d9322a3e22973b6170ee29893a443f35d6483f91058ffd6d86c90c99691f972e5fb743925713d78ba8ac197bb5cd3385b027865688452b80817ec1e811c989cd830dacad5c799bc62b2651b22fba3e25670ed7c1b73b10973e0d94027bdef878c201df8fd722de1d84a081cfe47da2391bedfa5ffb04ba4143611b2a5bebf9944ca219a9d8ffba2ded44aee0ba054b1550c499802ca5d1188739a305de0a21e65df558550fbd6a2650432593e77391da176a639b902fe89a4aaf604409a1f663e2e84ea90e2015386864ab0bda1991ac41c681b8f3bc6dd0d90868ddd357e71e7d85bce9074c0babf7c31bd4932667594101add1299181d0f1e0af91e9f5f30e7878718811f273b037d1322b36752364a93b005a2ba9db9c538632d27d2c81bbf4a912a2e88b6c84ad58e8f2e25d942586a0872a5bebeca64b8f4d2b8e2104f7b2404b18cd35e41424de3068cc2fad5c4c484197c34e820f3ae3a915cd52f8beb26a12601c32b1b731fd6d0173212babbe50917c841a318bd4a74af23534d18261ecdc793663e30e1e1a1545dd31ed380630536b5f471627302e118e49a8913dc509339f2e99b19aec79d468280d5bc3688ea2347e1f421d0a50798494e82a05f3702425028b25ed0f18528bdf6db24c6b721f9861cfb06dc9a75e9404fb0928eda2a14e4353d7e49420c5291764885b84cc6cc77ac5be142f42b474bed5b642c888aad6e7af3f26783b32b5cbb66a7dbce00523fc85c64f4ba62acb51e3dc32791c726b37d1f3b5dc691b9a7374d33dd32d8a227d66e2fe952762e5177400244240d091f0249ee9e4cf831aae60a7b445289b4aa11144dc3d92e0bbb544be68f0dba1e850c7f50085f4c0a19028e18012895072bae281f08bfc1c4a46f1bc43ec4cc0eeec2dd432e6237ae557bac06ec31a7413db35c04d817bfc2fb7aeece8b6b786a4464734cc48b89404c91d233e177cc82ee97cdbbd0f5032682137fda440bf02c349f49d0ccd84180b4e55eed91c21d422d300c2da276a8abfd83e67a83bee8c0dc71acd0dc873f758ce0dbad17d037b780a4a221fb8e7caeeab1190e55d642a8d8a96c0dd366c90bd60028fb108bdd10bc9d90785185e9a2223f9950e317ca743ec040cfb3e1972cec3008504cdde54dce3781bd17723621857cc1c42a1aed4f3ceb61df176d5afd0f67d026e3bad36a529bdece1856581add07ad66575cda6e26f62d61deb064363dcc3d09874d867065e0c3311c38aa89b4e43e0dab04a886ea39b25d67823787dd77a84fa4ecc1e546356305c4a92505779caa4cafa047ba1bb57177f206d667120ffe27d6d2aa9781be2ead063f71927d020e320c8c2c218cfedb65211aebf508929413825b3586786c72140c1cd0eb96af3fcb2488a4c1c1cb06d3700733a874916dd9cb3708fb98bb407ed93bcdfa936b0120d25ad99b196c1876e93a24da93ed6c0326c06689eb9b18bb4ddf10a5910f26b4e203aa12fb9c59305a954d07460ab51c74a257ec61d1da6adafd1524a5e2ee9154fba0ce73236d5e971d7924821cb75af33fb4b53ac956000220e10004f0cc198e75dd8ffbab6951b2c6e1d5b5712fde8fa7c150bfc96e7b77db724b29659232fb09b609900937959f48a9d76dd52a0ef03a577134a781299a78c3eb354573f1eb6f608a16ee10ff8633d88af314f2389f87ea41be0263903799c21ed35bfc0b8a6f6b0d28c5d45b54bc579ac4a1bb4d9c6ce734bfe148f3d7f1edee0ec7eb1a58dbc58437133876a9215ac7f2c53ef31dbb2b7dfcbd9a635945155550a1f253eb4013366862e7ab8fae0ae9483979a840e308f2ed1dca22aa366fa69df65a7bc169236710f34608eee8b9d965213e367ce138ff1b4f8edaaad5fc8e34fd9a778ea57b93e2f67260e79cbbd7f1c52a2e5bac9eefbd36fa6e27308f759cde81b5a3565a6b2d6735897d731c6adc6f21c65bd71bc4a411c494f1164813287f7643ef2f89b87a9eebee70675df3adab9ab743734d232204d24b2242f43c005e121102e871af3c1d5a15555431061b3d7d74d8aba7e3fef46e5890065c18828e061cb45737815daddbaf9e949dc8a3dfc8455973aa99300e41ceb1f4148cabc5e10efb5b58a3c68865702a4dc475f3a8974484507242457a3764573d1cb9289b7c736a02532d6e3360afe13ec69714ead76a32432107e9a7ce4305c620f2556c285b1c6976d8a76e9fe61cc405f78699ae952037018adc04a1242421379104f1e2e94d9f7d92027a6de3ed456cbb57bb4ae89e542585c3fb6e09533661ed13d7675beb84aef6c5111b042e37a16b429d5e5b9f7dd283d9d5be2862690f275df5bb1b74489dac83a23bf2c15262bc6ddb86b76d5bb153ddaee8c12b847c977aa57856eca46c90ea49f5c8a06d542e9c13f44ddbf4095cae76b5a022bdbc88a23b4c3d52932a529dd5191432654958100c9603aecadaac6d551b941d5a59036ebe64771d8e3455fa7cc3fa67be1a08400d7479c03140c6f49df9e8eed29149a3ef2c0c93463be757695ea5da4d315cad567a553728d2c411225e10451475064792bea085226c688044162a558de2f7cb1782be7084ff02d08f2d31f864d038586bbbab3a3f64aff4e5471fdd0f50123e32899f116c75ab8e350c8e34f408090a190e501cd91de070748404850c0728e6d1d136ef0a7490c2018aa3a314975f455d7cbcc9a4f2b32565b16dfca38fb7ee23954a4205491002900f7ae6b4d3ceacca1f10ec144146802da3dda43834e7471ae95e41ea92d121280ec53992d9a02b8f1b5a5a4687a038361ab7b4a86c26e75aba70cce13be73a193f3dd768e9dca9dd543813a8fad15b5a5240a042d7e11c0327c20749c81fda536afda8f166aa5bfd315ff30d771448a3e038af83d367d2b05e6712f3c7b525e4c0445365123f3f264dc3aa2d9bc2718737f9a6ca2a8b370c6622e4b1eb747ceabda91655f9cb3dc8a34afe54bce5832521eb523db946aacb38c8ae6399ba2d1c697e73bc6d5e353781b56d902d22914795aa576302bebd4a4c446e696a6407f945fd871bfde27206f5176a3d1cfdd665d8820acad5314a89dcde45e0720ef20c2d4e3f3901e4844f0d03511886547948148c7ec6cb2151e879276a7ed45a5ad1ebdbb51acde58413461f051d959743a2d09a1ec8724817662f873831f4a3a98a6f7ff1724817749efb54dc870e34d553d9c70e5d2fad4e253001203437b4d82d827228a574eae8d0212a44837024508bed37362822b2a44ee58d7511c5846c50abc5a523d50899440244ee9cc4953d4ea9c53e42f2431e6b0be81593fda4380b4771959fb889c3662fa42223f2e5cb2ff194af70124779f6cf4f6ef21f9b9c988be7251f3be79b6bfe33457cba74f94c6c33d311d3ecc896d4371f12b90489bcc416747e53ead5b81d2df5ca74636a6d47bd9243a290f4ed2929c8230dd25a374c1969e3cad9e179693f1a0ce7f44af66a6b6d41bd129231431b51918cd9a13d4764cc167e7a555ff0f253c0992dfc6c0188c59bd902504fafaa0b5e3e0b7066d6f30abd99d96be6d3ab6ac4cb470167663eb31fd09b99fd0cf5aaeabc7c1538636428e6de8c919851af6a0b5efe093863c448d6c29b3122f3e95565c1cb3701678cf8fcc8e0cd18f901ea555dc1cb2f01678c00c156bc1923b0a45ed59c979f02678c24cd54bc1923b39e5ed522de488f91578a3763e465e4a85755052f9f049c317264048985376304c90851af2a112f1f05ce1821325284e2cd18293212d4ab8af3f233386324c88890ca9b3122a4d3ab9a8297ff81334574764ebc99223b41bdaa43bcfc1338532448c8c49b292264d4ab8a82976f02678a18c94abc9922329f5e55d717f1f9497933457e6a7a558578f91c3853a4c666853753c406a757f5042f7f03678ae0e49078334572867a554df0f23570a6c8500ce5cd1489ddf4aadebc7c0bce14b971656fa6884b48af6a095efe05678a08e1f9bc99223c3dbdaa41bcfc0ace14e9799dbc99222fa25e5512bcfc09ce14212af2bc9922457286fc992240bdaa365f04a845f9334560df793345603d55a959415ab3a4ea03f45d572059d4260ac9a29386664929391963f22e82c7e608150a3209e44eede17b8813383fd6214d15aa78ab80b25d2e0dea95d0d09c2ab97937aaa441aab459a7e615fc1182b7be511b4a593cbeaf77765c784bb66841c1205ddac7f035208d11c919ed3c288b3621b514482ea8ac40a7a0b52006da0d88487ec85a4bce50a22cad07e9d2be826f1490e0db4d52906f384e1f9de6b166e5a2a647bd92a36a4691fd3a75ad4559d36602b528e78f9cd3b18a6315efc67409d6318890af216dd580e34be7c85811256a719c39ac3bf64ddff846c5a6346d5a1437a5af69b1a52db5d8da4c69f695c68880c8238dd1ae63697753a22e2a210da69b61f7cdd8392e9c1667ce8e4e8bb76553736f7e53a22c1ac37453a29bd2e7b4eed4699776ada5b5a4bdf4e885c49cfaf5764cb76ea7b74eafaabb74549f3d64d83a8dd362ebb65aa1d6d25a08218f7dd3ba699c9c0a6a60be74aec5a20fb0051a3a1e23682f8890d394314d1ad465ed3627e451c6d0979f9c1c106901124d3842030ecdca504c99f9475e85b082274f1f83f004f9fa361c7908b2f33d8200a97a83f78634405a6c5ac4e5da059c6fc7b9c35d17a05d25969b7a8d5121d2457ef51a4817f9a385f17efef4aaa7e3c80ea48b5ce938f2237f724f670d0e688bd398f5bc0a12f28871d7a2632aabb7d4aa8397169591a56f57a9d989ac3284a395e2843cd6c754703244981aae42533d959d4ba9807ca00bd0be620b79cc2b82c8a33c626488a861d7cea316a7ecda89346553369126929db27924ad8d3e4921cf9d23e48c1c9be9a22ead71ca64b8c59a3418d161ca708b66ca5a53268f0091b561d7049c6c864e481ebbd578ee30fa0525904291ae5bbfe1fa077040c31bac6fe107788436a84b8730272928317f7834cdb5f003f345e70e9d54b03177140043f4dd833cc23c8c8c9932d4c0d4c0fc11e65b18d790d1be2f6f5115d9f976155cab107010c28704b880f4ed13e86abda6a85a4c49e171fd0192861b4aea0281eb34b8ed34530626011da81be5f9a3ea95b050830d094c80c70db5673e175e637e5a5d8623f519b64f8b3e6dfcbc92f2d8affe99fdd3ab1a2af1a80a622323789aa72e01e8557b8516f288ff1485dc3b2de2af06079e5b7ce7e2c9770e03ec7c82dd0bd865e77296deddcc47aa4285a3e83877a1ec951537f9ca11d951e0887ae19e9d7317ee3dc44c9c0b172e5cd8172fdc05c7c2e26127595872e66cc8ce853ccc6709c7e99df7e8b2c91e4d2bd384f4aac5ab6b3ccd82e1d553dcce3a99c96180fd6a979f1760c35aace0046d80f3898c80e83bcf3f9cea08a4585d870a447d412de2ab10725645505b93466d512f87bf0b17ce794d4b8bdbe8daaaad5a536dea96bbce7f7cdf6587b1b3ce73c8c30cbb708616abdba5dbba35fd6ab1ba0bb07d5ab4d1ab91731b3fe0e8215b6c719af9b526861179acadda62f196b065e5aa73812176a0fd82145d422b06e952bd86427c15432060d82abc2067541fab8c9d4d1a76567de8bc2686117984e961a3c5ea34f367f0a103952d7227d94ebd72de02760fcfabf8d8425df55c57569c259c3d17c658419edef9585bb2d5abec55e6b24b4e406f018ef35be4d0ce5aac3295d7301cf1ab568ec8d341cef3cdedacba55ea15e8d5ed52af6656bd0c2e5b0610ac352dda98ba6c6a31a32e37669485b187b5d562f5937fced92866bdea69c3cc1eb14a32463ef58942b9fc16635e01c75a6bbe7a0a38762cc05185026a40fb5182633db1b3af6e028ea992dafaea30942543460d9485f25438e2ec2bc231cb7054792697f15203535498c3daf90c1d6c91538bb17402966851a1a717d632aa749275793065491aaad08dca435913a95dda79469cbba5b2b93cb21fb1500f3f626cc3be69d710b254820737bf921f5473a84a956928d2185734a78fd36702cddac3335d1f9840dfc003a6c529bf20325e4a97cfcd96b2c55c67d3d9347ff56ecc19da23b44dd6dc46fb50b5cd0e304a0759d6295faa90a716a29845218ba209a1288230a3e8b9de8d7baf86a3a81912054ecf6d8aa9e405396948a747b839e55422524a523afa949212638855e983055c0db9099224e496b37b7acf6e393186410c1f64eaa38f976faf01c87288527b96d2fa14f4f1f2d5698f6e1ed0d8538cbba3595b74ea80c5d841af863550fbf4cb278f530739530753c8cca1ac7983b3336d90c8ed953a8d51d80c5c0fbe0c9241d214929efba6d716650b39a3b1d6833cb688914f67ad735ed7264d532986cebb10e3e4118f2db60bc7aeebbaaef31c3a97bdeabace71f05a842933bfeb6ad0b58eed8aeb4cd5730e63ec857214a5df1ac4f9754dd328ed1c83a30c206ff213285becbaeb34f4b7b01661d2b8dabcd7ce5e72f2f852545443354f30a9953e35a72eb25d2e283b1c9298a927cc9b5035441e553644158c83e8150d4f20d7fd74de506bbd0baddf6ec48f31688382b5765d8fc04226d3b9d7371b60215bcf66032ca406dc0458904779f4435938b44b7b177518a6ccfce9b35ef54442cae049a365ccf4c1d8e19043b3a4f78cb5e8833c633f1d4f991949637a2fe17b7a114eb68c9cbc210d3e97f886aa1ff2a8b2416b755186a3cadf09f4f7be80aae7667dacec753a974105b44beddc0675b1be7905b2be6d9ba669d883fcbda606c2a4317d2c58db25669bc055af95eb2eac41b4e41af7de1dd337cf313dc86b38c8579aa6b286ad51bbb9808435d742954dc6b806ec33a854396bbd6df337efcda97763430235705461999c55a5cbf44e8ad34d61962ed3e4353813f883d38003ec946edbe639e46fe186c1718e3e6a4eadd39a8546eaa42bf4515fa1911a09c441caae36a8cb3a722f7dd808e48043047cf8d8e1ea9dd3045912f9c106081bae7aaa7b49e5d05e592f361ba86ad14bb5986fc0a1c51cd6476994830426305f28ebc49d42295db04b17ec38efd0620e2db65442661963013943073007094cc0478b2d8d6838438b537ba20077a6d57067578905d36d340638e943423c426a5a54468d807e7270fa48cf62433d3c3635f368ca60403a3940686ee0f1121beae1a936b5e6480603a23a3447e90851ecd5d3376d837414049b3b53e70340ba5145e40e6bb4d8df216a08d41042a8225045d820c8a31665e800aeab4129ad35c46fc371b329051a072767bfc8d0e1f237e0213215ebec53450d915beeec1375e939b428fd94a2431261c8177a00f012f643ece7e34d85e24fab6d9c65839aebdb35d44d36eaac7dd99785d91765cd24fb72dcbe7939ec4fa41637d77ca4412cbb4383ec2ba95976c7cb611f87f6b54359d6c7be2ccf16d2a1900ad957c5db4ca10ea4d56ad5bc7dbdeccbbe6c45752dda20fb831ffbb23ef64756cda57581439d206f46b800841b5410d3064164e9dc9c94d6538a0e8b2a22cb69f3c9e4901c9a8101edd232484545454585c7109156eca3424e4e4e4e0e4ce8f57abd5e2f9fa120209f9f54ce26219356a48806484fecc8ccf96cc4cfc63f1b9b6a732344470867e8c63534858a72502ac85f518b292de4f12ba24217d100f94051d1d24e10d2cd8b48490776b4d9f4c48e14dd169022595151d16d013972e408073eb0011a54108e14f14092157d545091112981d2cb22bc2452029fe75041e4e978569f04799c48f86738916cf2a8d57c17fdf41a1ac65841aeaed958ad46e89146ad66ccf6b18cbaf4db7022b5d8b2c648dfbe7244ae610de53891f0ec0bddadb98d9e95cee9c38a1172bd511da7549fde55bc42843caa6e5457e91c77b59f520a4e5cfbdbb33949bfa2441e55634501e5b1fe0d2dae20218f35a5fa0873a37ad7b0468bd3fd615a9cdeff633e0d5f5c65ce4a6d9d49a1c8c889ce67e7b3f36994bbc579bdce645dde94934fdf26e74ebd70c4f8861cf6e9f7f6ccf4ae3b45c7f42e4ce1c21de273ce85e2733ccce742192d762879d48e21832c5b6e129c9c22c872892bb466cf395dc20a32cd39294cd9f988f2316090af4f4715a5784e1d7b8a17ee109fb67b178e1873e198b30c29284e8c526911bbc9b518ce35175398b243946ebd19ea1c6712a773618a29dc218a26e761be2994c1e3069772067519e4ebb2454b01911e6073070cb01ae3693102941d6d080a0ca6386a2182a6b2e9c78239e7a49588103794d26a899ce00811216a6c12d39bd52ec99666734e7b9784968a28a5575b7a2dc1acc54b394b3c73ce39e7926b0908144c39a5784901e342cf9e22698aa52988a6386aa2c3f9b16251482805d7c287251ca174f2d44a516230a79c520ce9198772c4143b2f874c51f3637e2987485133054e532990a450922226c50da4a891a20652c8a458c2098ed2222991e05ad49028610851ad5286d54a80093834f6a9b59516816bd183943f8415232156f35dad4570284232899cadd4a30444a9528e128f7c42d7219d5576778ac652aaea728aa685026de0356d17a43818947f376bab95d94a71ecb6d94a43ec160ad24a119b209bbe8f4ca0c6b906529cb6417f6826ab49df4c16949386751368addba0a06fbe49e976b3db264da0a5600ef956fa68f1c21444b1564a5d7cd6f65bb5ee82b3b14faf01f2f105af4bca75f16db8437ceb36142909284e4951bca3e6d5b92828b2620078396474333c1c0198b24f244e9febb9221ce79384309b4b3903fb9882499c857783244cc1d8f10a1f491c6312f1534e82532b409afa9b73d3fb315fc694f89802ad93a4345f41e2e29b782a8524dc21be4909f863ce3e0543e2d353294fc1aef90a97bd4a8563904ff99882bdc6e79fbe0294de8d542877882f2d4c8bdfe623f6cfdbf1fde61c065b9ea3d1cdf06ce8fffab90074343c1ddfd3f0769c7e3af732e55639f93280d932d7df402c80ee034f3ecaf8daf25ee7db9448dfe726de8daf1b421e3be867733a2463b2d35e827a8937f3f9b6852939a73c1d9fe730650b7788bff9b63550af680ffcc391783be613469f67b26f284f07f6edf3eff3a44bf59ae3822b4057a30005e856ac0e06c600312854f60d7fdfe7a82f7b366cfe6dfe7df9cb1beae4c17836d01f436000cf86ef3fe70cd0c5783b4eff3927d3cde0e9e8ffc07e6e869e7dcafe81f5000890b3d6cf206eb17227ef46cae73028fffc0351608c14640d4cc9ae6517ff73542873b843fccfafdb6de65096a40177dede8ff93184648dc457780f12c729a7eec998cda9731bf8e37b5fe12427cf06ec9b2f51059bc7bec927d9817d934fc2c37c9230b5021cb1a3bcf37664df9cf374e0b06bf1fb6c642faff09208104acfd9e864af5274d0a73fbf767286f7d5bbce9e1a6f7d220947106050f51b58afadf43486e0718b9f7334f794d49e26528b9f677026b5f885f8048efeded8a91e1c7dbcfca681178ca5a60b97c4386795ca47f13f26b2f469ab6c178a71ceb56f685bf66cb162941132751fb184fd70b2659c33b458bd5a1bcaaec5dc8d2f0f531d05cb5dae9eb34ae5a2f882f1078d300a68b1861de4d9c2e12ce5b2a4d42bee28378d8e7aa5218d9308cf2ecc2828ac87973eb3208988ac53a35ec98edafaebe7c8db912ea9cce9392fbd1ba969478a24feb57ee5b5d12f6ab1bd29d8dba9f54b85281752225ad4a2752a4459d4a85dac5b7af476e588ac39bd2c62f880d62e49e5881abdadf282d42fc84dad8258cea87408a4b1162d259a2ed655aa9c31cf5bebd4ad8f94e8a9d0db16279a32d4ad5328c8d5e6d6a91424ebe4b6bf931f29518c4689defa09d4a48be61b48a5a83905bd0b561c7ee9a5f4baf4b0765b6cd8c3c63a1db460cfe225919690e7668763fcdae03f55281f72ad28b3796c5e4ea2cf59b5167e1a4e598b4737b9e2f4add4ce19ce341cfdc729ab61a55e7dec70729ae5cd3a9c0e4736763857cac9f266d4a5abcc9b51d63c9a324f89865836c1894f2d4d0843a810faebdd3faf0a511e5d868fd31bcc5dff9cdf63aabc99f937f4599ede2155a972be6177ab843d0ffd3fe6d294311aa0b9df7f2baafab4e10f5905123819537bcaf4df192e1c4d1a92a3272ba3c9e9a5dfa9b9f43a656c087e0c42030c40b181a5bf3c04a17bce397b00a73108cf16e4e190434bf8ebd3c32187ccfe4a21222d9cc7de8f437a210b38e41580fe3a155ef0c41457088234ea914e48267e2097588274620a4f48311304084f18800a0608423b8d3a8e70845c82091a4c885ef8eb21142d66207681850f4ca08a6821831b3fa412ad1e9ea67dbef576a4bec3310344dad5ae76b597d3b80311502a583d91834bfafb846bafe9346e20a65501c725e26f0ecb82bf3f7f5d7a0bb8387f17809bc84185fca58fb18631d6b0a661ac691ac6589b4c9cc65f00f5c1023ed85fc79e8e98bf5e4529675049c3aa50ef67386a5e7b843266e6e3f0c56779861af0d73c00ff43564185f6d2cb3186af698087fbd869f0ff48c0638e7673014174b48c1629a52e835a23f81a2d52246439848a1966a0fea2e243a8287acf75e3674b4b8ba23847fc52d213cd217b4e2488884046a48723447a90a186d09ce35cb670618fd9ba49535654ee1d2a63e06412e77057eba4dd9482ed1a98837a33c006a573f60ffeaa2eed56b75a275cdbb7cd627cf2e99cc9a4c95447d9e8eff514c8033c9d3a8ee34ca6eb178ff9044ea016afbf5cbf451d5e1ade9828242e2bc571dcf5eee9deb9de427a35ff3ad7d13087162f490e447c38a23206dcea55c90b97cebd004bfc55e2d3738967e7722e29711b9d4b40f9b9c427580296dcaeba8ca1827c83d33a537fed8a729ee0d4c9b9eb664eafba1d48562b5197ea2c3ed61fc8aac4d7ea73a75daa6796faa32a01a70e0a89ecf9966a91ab2f3560648cfc295d585c0205358b8f75a8f3e955f72ac14399e8eb588dbe9678cbe6252c289f604665cf5da762e95858bc64eba657ab8f4fe6dc54595a6cf47c20bd6aa7431b51af3c96fc604a54d848252040a60ccaab77c00372c5f202060b0ac68b1d144755a277a1c9745da8bbc38123e72ccee29c863716182d2dbeb5b4b04c96961d7c68a88b33af4b23d28aee8e101e17ca59c04682017652bb54e7e60e2c53e7f5f2f9c99939d52bd05717ce7a516135a80ad5a11aab44b5c8c5ebe5f3a3438b3bf860913b9f91c57768b16e2e7c585e2f17a14f8bd5abcbfa42b5b20c7ef2eff67cf5dcf97cf9c818f903d42bd80dba42bd92c151126987166bd803e534f3a74e0e2adcb94258dc05d846b276517acd9ec953774c266fa93b2ce1cc79cd9cd775a1eece75b9eeceddb93b97e7f6c81597920564c5e7077577b8bb435940507747e9ee28bdee8ed2ebd680a72f0fc77152964d33c7e7eedc1da5bb73775e63ad0e84b2a492d20460be938f2f2f2da20c895b61bbc0a4c64249072a63266a883cd69d20b5d52ed5b389af788bc9c78cd3ab12ef36183eaa58dc148eb9572aefc22a84a77613e513ec1cd575dee59cdd06d5a157d9a4063975a828f365757157f113137701a77499a89f0e0364716ecead3301573c83f26545e516b9daea41ae56bc7af5415612c86a02595120572c5ebd122157255e61843538c8127739a552f11693e9c35d4ea954bce5cb9eaddc58bc64c559421ee66fe1d8e1c8618fce51b526d199575ff1ecdcbdb57639a552f116f1745ad1e5944ac55bc415b86a3e5aba394b5670748ef271d6ab91cebc3a8f5e75e198c3eea27c4085302dc2a83696920cf2903748d410d9f372cc20249343d5b450afb24908537d059c418ad35b401b529cae0215206b90e274169001529c5e02f290e27418e00dd265768eea483c0604c91a111f8e9e880f3cf5a6599306223e187d750e77b39bdd0c61e4b49576f6fa391c43f02de038bf25b3786dd539e21395ea559e59c21cc2b458e5c4a8d025111f8ed456f516ea950c221da84b75155799c84230d522e72a70c43480e1289066fe4ac88019285f078a55c211e79370cc26483042190dd3ab69f2b69cabd377e70ae995573f3f79bdaeaf453f640c48cf13c44f15849efad434ef863b275b2cfadac91aa8d7ec5d07faf46eb8d730654c5e7d0132667af51d72c579c58f921fc547d5a3748ee21c48a5d8d1e80250f8a18e068c6e0600e48e70b3162d176b17eb2ea52ae4858fb547f4b1fac4f800e09f8bef33cd66b3d907fac7dd7c1f8b51b34cb1d8e7b2635fe89f2929e9fb4cafd7ebf5adf8671a1afa5af867727ddf27837fa69e9eefdb66b3d9ec43f1cf7463baf952fcdb62b158ec63e1df9694f47ddbebf57a7d25fe6d4343df897f9bebfbbecfc4bfad67ebf93e3c9bcd669f4fa3ede6fb56f88763b1a0d850ecf3f9933469d8a4cfe41f7ebd5eafcfe787873e3c44d32cec727daeefc33d9f5f40cae09ee992319e7fbe43afb27fee43c6e0d96cf669b30fdf7cf82616fb3e2df671fe51ff34ffb4a4eff5bdbed7a70d7dde5ad2f769afeff32965b4a149c3fae7f2f40120d45c2e6208e345a8b908c3960f9c7137e06c36e36ede3ac8720a6349602c164b7aebeede8ae935b4627a995ea6d7d05b6fd1a293c1e432f5ac985c2697c965ea79eb32a8702833d30dca6c36b3be926262114b4289c562496f5d45b595bc865eafd70936d95c5b4fc9e63a6dae6f73596791d25078b6dda0f0ec8467b3ede6ada3acb838869350612c866338e9adab4c42d97ae0e7f335f47abd9878ebaaeffbc2d193c93cb09fc320f68c5db807bbb00bbb700edefa09c6980883549be11b6da6cdb4193ec15b37a1d4352da62569312da6c5b4226fbd04c607724adc91228e888bc9199c1197c41da15c79ccbd32f90cb715f6f53997ae41ed4d5e85344b739d5cd5e714fac9ab115246ebe9a92ff893571d19d3e227afad665d998dacba643567a9572c514819ada5b5a6f813e82718ec0896043b52d42b199a9032d7c8080a4432c67f9a757776603b423b319f5eb5d88194b940403df8d38a9f9ce234eb629d3fb5a810daf327a7ae5eb1f09353134899ab4385f893d31b19a3326b9695c99464b425a33649bd4ac98294b14b4b5bf8138a9f2c0c4604333a991c41ca58232322413246c5d32cbbb3f3daf9399df8c9fb082963818062f0a7123f79d734cbe29b3fb51ae7e4ddea15c918a48cd5b13a552cc99894ac5955264392cd4e2b9c90327569490a4532a6036a5685c18260433fbdfafce4130929538d4e3e7d640cca4f756767eeecf09cb203a5e04f3e5120652a104db36a8bba58c74b55c7c89ffc0252a6ea4cd70e3e640c453a9d9c47b3a88cbad82525fcc9754819ba3469589741b0196afcc965348bc218d02bec27af41c6687e72187ff23f9d5cf68902fdc93d90be0b29438d5a644cf593e3b67e3af994321468d2b07ef213ec6db48d0e3597d6136a2ecda5b9b49eb79e6af958645a2b94c964d657802719604632c06030a3b74ee25e8bbb738164b8406f1db5d2b1b8adabc3e2b66eebeabcf5acc2a5c89658c864b2a5b7fea1984c60463018cce8ad9f54db89ddb1402676c7eeec00bd75af0493d896d5b1ad936d59ef52da0ad91289cc939d42d9d25be7bafbc18c603098d15b37a1ec8f5a4daddc4c9577dafa46243b575b55a7b66aabbae0ad6f937a446f89a80652c7b225994c46c55bc7d4136ad182d53798110c067be2ad6bb5571e500c4f87cb73eef9c4e8dafbe96438e7a2acec01b58bf576a01aca412faf077286f542ef851a923b8a13c338e722efe5f9b4687fdac5ba177beb35ec90861487e6509d1cfdda4b1bd25eda4b7b694cbc75147c3a3a21356b8607e4f3034e1ae03c6a175bc1760c7a402d5abf2047d4a2e58a9a024d198e099286753b53a9b275829c617da44378eb262e266758470dc9d5c7cf95a7cd9feb73bdade9d5e8bdb49b5ebd6643c199f46acfa75932dcba07d42becd63d981704ceef402a35296acebd288b932e42921382e623f7f396137aeb9b97c3ff4eaf2fc3ebe68cae9bd706ad8a2aaa00c35f0f00e811b58b750ef46231caf2625e0c14e49106327cf3cdbda25ecde849e50c7ba26eb23e039c45ed625d46920c50d53e3d18b5eebd4457b547c3dce2c7580147c4c57aa571b1b729cd75b3167a2ecaf27c72bcd75befbe1d185d0123aed3ebf43a75ea957a3206fbdc5cdeaafdd86186fa4db1ae8376adad76e9dab9fc4b8b9d63a9c5e9373184e4b1717a58af3246f3f62642c650fff1bdd56ca84eabcf60a30606b44bbb0d15d062f7aa36adb39ec6200fd36227b5487d22b5487dc423cccbf06c94c714ebd5c31de2d75043c23a37b18cb22e0c93643fdd909b322d63349752fe89ab4abc5399f8498a08847eaa5fecdfe7dde79d9b785752e226d8e7741409845e1279498404ade7c24e03e7d75e7121089a780b196448693939297116de8d12141f555e52a22a01534a4a5c7c16ae2a618172a272149597b04071150b1b4a4abc73ae553b4a5c8552a2aabd3a39393951b1f06c38711507806eca9839a3abbde23c056299c282c50dac3a3991154f7993f270f4ab4afc3a4c49e75de7d94d7c8577c3244ce9c21de277de491963e25c079adc043c31651330c5c4733671f151fc24c524dc213e4a68a2a95f025312a6f050dfc47f7c6fe25de72627de352d6abec2b3c1c44f421ee697f80b0f47496872612857a97246f988b5b1f6ce10f258895ed3bceb14fc3a8aa857b4a81ac918f99aa352cec3fc5438a6bcc4eb50afc6ce39d77a74feb9e65588fbbc1d13fbf79d3c1d9d7ff7039d87e2bcc2b917c9a3f1bce69fc71092f378e2aa1e276ee29ab78e8c4171cddb0819d3b9e65cd7b9ca4f3a4701e77f9e0d26de9978e75c8d6e82387ac430825b7de705f070f44de7e528719af9275a49389a984c5690dcaeebbc7a3b4cbc73941ff34f5c05b6ab45cd4dc0c6e161fe49d88d25de37ae79397a744e8b9ad3ccef9276d5b87a359af430f1921c921247793a4c4ac2acd5ef9c7a396615318ec863d7bca679ebf44aeb81f2e95aca51de0eac0315ce3077c75dc737c4dd75f177e56fc95ff7a48cf9421f2fcf71328cb177bde21cbb0d9a02e5af00471509e82870ac3ebc8657d4b55ec9d7fc036b8d9ca1551fa48be64610f19a9faa095ed342af75c2d669d1f64e8bf6c26441bd12fd5a7feb55465992069c65a61a2dce51d3348c438cb10d62325986e8dbb7fb36be2eb596f2caab007c53a690513c790cf22f6fb9718e34f37bf448395769e31629c63730d668acd75bc5df90336e4bce7672ceeadda09e6b0df353b0f7a031869053b6500775eb5e6b68510172c675941132d6fc5ecf59a57217c57b15d0e2f56b02e2fb38fa997206fbbf664d1aa463e9fddf47d27d1f48bf7fb399bd5ce2251120728c601fbe5cc20a3a40207dfecd37ffb27f4164df5c49bf3936dafc5bc20a371ff37289a3d8e73cf62dfbb6c4134b1c0156563a1964e85a780782218ba9a3b5d65a9d8587c3fe17ca1671af6a7461078661c7d2d2b970f102068f1f7dbc7c8d01833ccecf357faef9f6b9f8d8730d533e8cca3eebe7d373f694ea9a6f39fb985237f046ae5f08130306b907cdfc1e9a7362f70246278a2e31441700d0906145458564058ba283a8e42cf586412e15a2190c0000002315000028140c084442a1602c8e1351113f14000c879a4c6848994863518ce428088210320821030002034440646848a4004c4641dd5c54186e42cbae10028eb0265a5a71e09a442c3bf645554b50cd2b36f1c02325e6ea087f8d1ad2ac8f616544d803bd5f2cd74d5ed5982de3d950c790c120bf50fcfe2709fc0fe9febce951fd30989031c068fb979190d95bb5b77751fc432911422650ffcd64f8840c29fde91ef1a7bafe8311530a1955ea9fcd650c9996b5ebb5f276922191092054770714fc3a854c53fdcf2531432628fd1511f18f64e58fb5e418329d1d7af9dd9021da7fb483fe57fdff6c74f221a3daff33697e42a6e1fc3301f9bffaffb38049858c6aefcfe252977d8f6394795f6122d0bf4bc38ef659c854e250bafa43a66defd96fdb1b35d67f9c2173766dbc621d8835824ba6a990992df9ac47f577b6b04d6da571a687af33e0ac72841732648dddee417bab749b060f19520d6846221f1732c4a38d4d98876d9e41c8d03e9b7fa9d85b29f581cb103238994f6df02da0ab6db807b0a75cf9f2e21ec1a436ad88f8bc4ec8ec2f81badf8d9e3bf81461305d4366a768c83164827e810906de678cdb670ba4c2443b4e51a91e324d46f39d775c133f7774654c183249c6796bdec5986fc24a2f8919658dd4ac25b03a58eacd6ce6673862cbc7bdccdf9aab6d391ed1c4a58ae2bfa8745005710773254948fb3a190bd3340f621d94d9a95041cbb774c504dd9ca94ce7b8438bb2a77cbc3f3c7ac29cea6e84748a10ed3e9b8d34c7b29b5283024786f64fc5d98ba50a5222e0f5209e544743043d9dd2f85e05c73679f01407559fc1660234594c29edb49373838345a97a06c05006413ec2d277078f2243782590e53616d8083139ad8ccf014dd1c25290dcc1d93662bd02b97e6149ebd29ce40f8c6c8e981e765223cbc65135d414f848e8d730fe922709deda660576943230e2b13629c4ad0ca3b1f8911f5daeb68a5de7e410af723995e0c917814701d54fee5218ae29adc5abb26cd9dd2cd2bbf4e45b75b01906835599044a5123f1dfb846819e976a80df2aeee5927b5c8a8d3f5f4f77e4abc76ee62f4391f861d691a701736a87a9b8e973a174d3dc5b88ebd86118da46265262d66b60cc9ea45f0c8e4d6e4deaf0bcb23903f85707ab7525234498afd07f68695bfee8d28e0136c925a788c02f6189637717ec6308296dbd18c97b7058414a2ba24366762af7f27bf73bc482a92ccba8a8675281d958598b81baf2deff0ee560b2068685c1f961d6efc8d8bf192e2ba85bbc3fb4f42be6e82affb8a4f04afacc076a172f92593ad7beb5d1ff855f002ab3a6b0bd35d27a0e3d16e36066579d67ff053c306668f85c4ad5cf43325d6410dcd42ad1eade3b5f0dc6839ac95ef313208c1b7616bf61cdee6a17e114dba6547a47e06b71ea94cb67871a2cf21d8a53cb06430b877e535b5122babbf5020387b50d7b402e154679faef4a94f88518eece6e670d402febf8507a4d5842fa3e9b1da2da1c4153da6133e5034426cf3d204a575176613726a2ad7185497ed00a2ecd0415130a15492e0bd3ff072f44e0b8eb0e518862c54e8f586cc406eec0ccfb064050f4765f66dadb8645f678ba8ea12607a8fd8f84fa76149e6fb374c5eebb631984b9163029ab5e4de6508deb983f3534b92e1c12d8838d6150ae83a9dc4ff4f11f60351ed1c4a58ae2bfa8dcb2864eeca0950e2efc43349100f66d2292de8288ce892f87f07d513cb98c0628a964f8f880d1cd662f650badd74d9833eda31f64aa704ff6a010ffd1bf7c081e2d82279f61e8578901a102a3e26fc5fe451ac6470fa00d008841fa6d416baf503ec09421c2d70d1ad6bcf0b2a1c5eb0ccaba71847dc8d020dc30bb74157fc08735e0044416957877c92d7cf5bb86b43c1e69372c5f5340b98ec2fdd07b0ef48a143841262734b687e006f99c6e18b4210c5b1c29763a3254cd74812a2c12d64dd080ab24204c10ba4c6bdf887cd6f19d5ff2e7fde8b7585bc83cbaa97a70b4fbc988b4d225a2b3924957eef231c66d8bf95e7a0a5f8efb54e55fcf649df89b527b5981530068f59e1146452e1bde63119e75129b97f954ae708b1530cf113212ea1826439a926eee6e43869e1826b20776402ded9b9519b4dee8038522e27db180e4604a4f063b34ca110f2e4e2f277258e75928321e057da2a1472e8a8a406a007e954be41d9d9e67ca59d006b5dd266230ee8ac089174efba76941432eb69e43cd2af4322833a1dec8b540ddeb809b30fb7700f2b49cb270ea8dc3ffb2ae86756ce2ff1192a13a9d9f3c3c578962c8018d2a5b053063d16cdbc521edb840c13bd755b40f07c41c8640a50497b5cfbff08fcb840fd8b3f86edbf6bb695938dabd0880cebe15b119a454c2d682a89f282e6df3b5dfaa4f03e75fc8df59350e5a1a472138b9452b493e582b805cd189e4965e589d59f44a058e011ee6f17a38844e7e284bcfc5a2bd190f450ee1cf6261fee047ff1992eab2a2a6122960b505d4f285cccb6d36455f8e6576bb63bd919d3261d96cd082004b4e34065c7e3b108ecefae514e1b606e6e6ff5921ffcc840d1b5d2daef30954ec6a0f8619c64433110a01fa99474b6f155875df6aba01a3f35237d2406a7ab035e494d01e87ec0a518d7295427c9c53a7c56e9ec19f967b69b232f274caa6c73cbc0d730b151956095d443efd2f4d5eb160ca11f360084e8feac027980ed990120b8ffe5e6a77e2b480134893632a8b9cd5375e76d5b59998e51947659853fd2fde131d99959777dea717b1bd953dc2200cf4a4aaaa8684d6b361a244aa52ecf3d66efd4419b9ed758b909f9ae24513c514dba5f1eddde7bb2433b6c7d31319a7590841f4beef0788fab1fe378b05b74c3eb23253cb8561a19cea44a99ec79b25e0d812b44ea948188669b2faa80d0213a2eb092ba89019d8fbf92f0122577bdb0860148e0afc9f13db96f62d4a2087d1285d44c38506fdd5b57ed38aafa34507a3ff7dea6636af15e2a8773fa2e76d69b7b417f4bcf661f776a53e6c0d58175e440d6c0baa27a3a0d7746da40fa316d5f00bb39fac33878f550a11c1bd0460d0a362ce23f04108b0799e3e7d5243cbcde8464ffe0d47b832d788d0ab8794d831f011790981667bf03563c63f1fcf97869032b7abe6e56a40458a34fdedeb6ef499dd0982f9567970481a27e52c7c5066507a321dbdc69cf93a118d91ec384a94dbb3b6a96d3bed19b0b3c37028d8cf5b622b19998950cfe50b0cc1ffdd795b70e7580996bc8849f49b1841accda7dc70f7ccb546699b824be2d75d4d29add58ae53cb6561754913ba0c21cfd19c216e3a2b53d2d63e55a192547c72a10f2a589ff27af532781afe04a318b1699575cceba8146391304e70f902bbf287eb4bb6ce6083eb0ac026a3d42004b7b1fd81bf0825256eb50fc319944e384deca9947c763c3f12811b739effcfcc5db0c8e70240688f63655359e1a74670fd779ffd1daa0314ee18af0c47a69ea2d10b79c71a1a051887629c5a18a08c21a68703e81062a34fb9716b302f14000b3bafb1f85c2a20dc74e4bbcffd2788660e779cce3274cdfd4705fc254518b50024fc55d3c41d67d6cab4e4f0da7b3ff1a7d5a8049c162c751822134f9f9bfe4720a04156cb9095979eac7f1f51ea44288104c64b5f30ca2c9e1a7724c10117a5a6f1ce403d4b987b740dd92d7527f286de0d29d613ec7e38f0f21f6563f613100f31787700148b0b12ce673a804406f7fb107ef03da87a02342ac1ae42add17a6767d2d50c2f6f9afe16595a49f60d93f531000643638ecf1da6c2a800234e6ff5e5605800775a0ffbf4542279f5682060c05e2f57f12c4bc6e8f2ee2739cdd49db965080cb9698f3371532a2754af722d18399e5ac82d0638f4d1ec927565374d4a8f82a2018c2b8e6d19da0423a721c9091991f6dd0172722fdc4c7483d7741138dcf5ee148357d74ac87384c9d362ba2fde8e1fc935bc45511daa83ae64db3dd15f0b0cb8c82624b5072a746e1e9508213bdf870ac0de5b6e4f5b5ac61eda6458af7b2b35911a0206825b1d6ac15b87a7872ae8bec6f0a755169d114595c2af58d118eefd7da2fed2615d365b13d6dfe4551132d1abe57e5165786d2f65e6cdac8d31e0a1d463325e0fa9a418da8bf4e3911f4d38207f6054543867a78230e219e3b7f64a02c34f57720df49ed5d1c24957b45659faed623c1c2e6b88e56cf96b093cb917dad927879ac236fc300ac8f33a00a8c366dbe215f37df47380808f7fd3dd8a3066a3b6dd9c060d1f2832b933c6e5abca1126257050623859df8547f949c4179c529c1aab11bab9c1d42706954fbc047a6bd10827f085f118e189800697439d011736266941b20cdf018644bcd10321c0d9ccea5da1b255248a4237610dd924d6d4dcca170ce2aab87c358a2065c89dda14d5019443037eca3bb58382d0ac8a3141610c25841565a671216c325f31ccb808e6489a050e6cd74cc421df4803a630bc9fb25ca1f501929d71b2358631cf2b469f61b3be932c685464165f4237baf815413ec3bbb6fa5a9bfd8237fe88227c6f8dcf1883db55eb62d0b95dd1006b8cd6436a3c4f76e6df537b0b886dcee368e6f1cb3304333b18642fb7dfce339dcff39912c707f3f772c170dfc3113bf0e84a6ff6111f6bfc70ba6cb7f4f91b61b33d589fc6dfb50bf042fc02e41e375dceaade90a2cb7d6a2577a3816b01ef590f89af0965beca11c420af2edcf34e92b0a016db77e871247a41ab84f8c405862d9ed07f387a319d584a12f14f7e7f93e2420dc1f5f188ea950eddccb19291a100821839983adaeb67b1e42a1e5c5b1e4f1d245d9014d91f995a3c02c3321371298ca8550aab7654f421deba75d7dc256731c15188362b11b353628fede4a31f4fdf9fa76b3a0f77d1d65290c1e2ecb2ac28d60751c9e6cd0b937466768e0d887bd3830982f11ab9005b35ccf3de56e9f545b8fd5bf7d45527b903edc27a9ab37b7d1004955f5a65f3027d4d4fb32be773c02ca9dd36512c363cc69d73bd996c8841e4e505a3b704b6fa6b0121fbd1dcebf0a0d9719d77325ae9bdaf8f55858d9fd68c6e28532959283b44a405828b27d7aae2c29a3dbe816bfba6e0ff3d9c382760c1692b0d0aab7a3eebbe59f0331c326bb57282be7e15df14de0f42135c3377d30e95ea291cb87c1107d77a927606a88de4fff3c5d71a398441858d4131d3064cbf1637deeec46ae8d1a3daa71a8067c4e91400eca6a2d2ce63eff275d092fd6ae16fad527394be3fbe24dd9b3dbfc80eb98cb26b9d6361130e44da18a7b81fb8cb9d44e15586000e54a324b2591c966e882ae4eb57757cdebe1542ca8cf0f0c6e1a599f6c40bb157be50ff1d148d96a370ca050a5b10174721044de97cb3efd04c653b68482b70fcdb3a951073efaa7532be33782f2cb5845b52cb53ecd794afad5c58b29348e6623e444adb4c3ec4f00080780200c1e643221f8725a27176cdee10cfafb0960d3c3e099950598d9e882076a7902a317dd0856260eb4d282aaf40e83a937f11645b4c2db070729f59a0f5627f78f580ea4971b45578c86b5f640acc12841af468d1225b90bde8c498c91df77a948603359ad8c78309eed35abdb17cfa6c59a11ec2e2f030b6664d09e434de998414ade2dc3f05d849e61abeba8b2ba83c72f0c4bc50ab03c5a277b809a77d6e15e122f8ad78b413b1495aa8509e90fc606b228a1043cae7f4913a65788caf325143ab69578350f81da2716ea0f9b05848285b9a2ecbc40b278c5f2800b31a9cb9eced3c81e7bcf94812c2fb5e834b8ba3ab8d7546f059803d18c09f195659d20ca0c0e2ccaada9eedb0844a4d9d11293bd6e774aef26baf1ec4464b223ddd9f2d92f6e961b580ba376cbad478016647eb8ca496050e03835ae6ee2d5a22ecfb57c794d05b29b7a8c4b539c802e7de46a0bd140917333253fc65d16288b626fb261ca16abfcf702004500f738adbe1ed549dc3f42de0cb348a3eb12a52d785c510c01b173d06a02569d9559a93f5cad4defe8f69dc4bbb1dd1f3676d8884ebaf1d2845fdd16f4b5c023c3008c2bc9d502dbd66d09b6dd430dc64d3eb03844696e90091d81f448075ce6a11bbbad31279120af1f2e7ad2b979cfcc02ba273c61636f9be600615645eabda113cfbf84e3cc7ca26ae34bcc25865b509859721993b9116476ea5efac50691dd7ef45d154a8c56a26e3c64b6f730fc5786993dd64cff63c78bb96a5a942291442c5c4e0d4e64542d7e18846e3fa23edb2503bf4df17e8291bb8c7121cbe3d0c8e84bd4bba30f3a5ccd76e5c719557ac8c1a28db5708cee7e68d76f38485709f0e620db068b4b12b2fb025ee2dfa59b1ec182af9ea663160c84299d1e07a7bdefac916e38743b20f143c0371be974ea6eb622b74dd3fe071e840092abbfb9c8a990a1659bd8d8bcfc65cbe7a68128072d2c2ecdfe7b89645439950683a7e13810343916c8382278e30d78afc4513b679878813a2f19491420958b944d4d3a5433e2c423984744272a678767e211d75d6a43bd46e36288e5735e8134cc5293f5a274b71f96e92f43eee19f18fd0403b1bfe4ff54c56d01c5f23cdd6f09e24a40459f062d33a7f95e03accfb30fdb60550f0f193b753f279c264a98eacc992f8ed1571bd1b3b3ec79c637dcc01c9e2f0cfa17c1581ff68364365d97e251caa4c3e4a4970b1ca1c58991ccfb62b2d8c1dadcf4fbab077a6f4b9199785c248f7ed08a0c38a79cc5f51d0e9145ab9879cd4b06251f9c5a595c2a5bcbc862591028329a229a71619f97eab0d5e13a24be7819edca3f10fe1b5a20349276a87ce80d4dde21d9850b65da81028f7c12231b836b13fd4a70d8b8e5707bd5b50a64dd48cd422e0185a57aac80c73f2f4b876a1c3860753b447ef13bc49fc1e165f937e40cc46064d943352f9a5759d85328e5e13d35c30ba4f66689b625fcc825f10919b29647df3395a2d92326b7293498c05a050d51125410301863924e28f32e52a49c7da6aa4e2757f38425cfacd4950fd2e4952e74808a9b99eef08ca1d600f618c71ecaf0568b958613374321bde7c2937726e0f748a25ecef71a9af29dc6c678ed1f4794a2003ebe8b05016ada5475fa6211d1fe0d0f81ceb7e7f08a9ffde03c44a16389d3973b62148f9de3860622d199c79e1749e0da77f751a3bfad96935649831a6d89d7e02c6f69d4c84b0e258c4b89c3e068e9e3c43c1ac3ac0b720d4fc8b94de6b66cd1cdea14d80f94728379b62bc8ab0888600f1e8a69c67e374058431a3814582c4e398e6ff1c631219521ce157f709272d95a44701841ec67da98b6b9c5d300a2cbea8c7ee37f6380fecc56a220635ba5e4ba216be39fd048248d147a2d15da53d1ebed47ad664573f2fca32bfb86f6713a39eee5ea1051cf362b31e91a22b1574bc2d5c89461ce88115ba68bfad24b7ca4d81d12c42040c64e9297e209f6c2289e25e97bc10601cd2cd7930221678e9c3389043695a7cd8cbf07b44a7b59b5974ee89d5d18eda8f93980ba65b312489629b922c8be013d175f813a254f16cc571786ab4956d20353931784e7ad5331383d1f6a560b0b9845ac72517b8cc9392da760083b9c116db5822b8052f1b321d4b7a04e52852ca7594653ef0f90c7aa46fccc5492c8bbe192dd179f3c533941a22f20c85855319e7fa8a2039f8adb74faacbae28307bf9a766da667149212d2ab852fe7ccd449ca5bd577512cf4641de058beaafb4baaaee1a5a41fc9d8d90a46af605d057d70108ed955db2f37f0ff29c01fa1d572b014687473f3cf9b190411f418681d1d80be46dcf11f423936b6590371f5a972cefa7e5867a7d9279c5e1cbe534d83716a5378f46386c9e6f280a8ac1c0e0c7c2e7ba1403e09e9db1b589c0ea898588be0fa0540216305db58f04bbe67695597b2fe3498d5f4728d893ad98e7cbaf86ccbd44a9fce7de5944d8d77027acdde7fe27ae26ab7d42ef24c83c0b6cdded7252065aa2a02ecf9e8d7f81881ef7ca2a2a4143bf6646cfacd9911208d8067b90df2703672a7deac2674e5cf4f35565a6cf0eb06fb5b2f7bd5c005b7ff49363cbbd3b2ceda3e972d3ba0040496a264ba5c7094e3b1eb6301b1d70887c8ae07029e199f1ca369a7b07c981cffc7f368c3007545cc8970fee47e9c2596b4eedb894fe721b11aedeff71a1f25a39601dd1f887ecc0f24cf276e6952823648639ee6a4b2b0d98b11ff727d8c4939e7a9e34e26febbf3cdde56d09b1fae0b3b9290f5fd2a1c0f012ee43afd2a8f4b206c3a6aa895d43b8270930321c67a6c51dd52b4906109b8c336f0406e6de6667cebb781b5e3ab58619ac71718bcf15ce9aaf949f2da9c40edd6e68db8fe42466b17f34ddfa9ec944a7263fdc606d4e68a7fcd93b463b5f3d2eb5ce3ab9f6baf7b5e8fb7340728df1953660f08dc3b1669ddeafb22a8ca24422ef327ebc87962704f248d5a4d603f92811f2f02ce808794ff633243f15bcf1bea1e4373ee78c01782bab45dc37f6ab8f0ffb27072ebc5c56251afbb30dcf091cb31c2fd4ea6797e51f6ee68a511b5e548992dbe08793b570971d8955cd32f98fb843756a1949986b614ca8d919688748b4b3b9e33d3f2509c80371c0c01de81a55703e4f54e03d24d5b56131c04258d73f93c016254e296c3df5518d598b347e30092be3ce34d86bb4566fcd0a79aeba4fc509ce942af72f3f6d74e5f8dcdb0d3ce2950ed39b84bc0a8fef578d93bc5105eb44ca6e409f89090e129fcc70d60dc5e77ef8e956d0d415690a0422af614eda6cd321eeaf8a1db7f14f82c0cf0a413bca2c38a5b3398397dc6a661094413f7c5e57734ed1babafc0cc8afed2912a3f940dc6e02239748297b38ac773bb72fc6b46e03ec9aba7842919d3ea91b7700b8866172e33dd463ebcedda356ec14b7d550c016e58cf866cebe24d326d570469f97d8cb9d0125e8e4255143b8d29e6fad8a5a820129348d12bb057d62704880f9a6064ae4f62137e09afd3f6aea9c785bceef9ae22bbf084012a4bef7ab8b28cb1ed57d9567c36a180415a3ef8e006232fb7121a9642135e96625b86973df29e2487b0a70f21f88d7b2274c31247e7ed1ea6997067c2cb8509b49d920941e0514e5359e57c3226fd9ef2768bc015865e0286c5b610613f73b32b2e578e5e2d1e0d8f54d0e41ebcaea088d2b7bef0c508bb62c54cd3fb4fd6054fe88397332ce74b9cf63f7e6f37dd0365fa77976e86ec757c6962f76d9e3e78d176201745cddcdc2123179fe7a3cdae73101150055c729173cadd71a56f4c2475aafd55da42d07eebe820220f5e1adda87bb708239e390b10771a5a3e78693d1a7b188b496728c44c116a8400284b471dd08ffb35a803954e080d8187dd03864b00ebbd7b7f8bf895ca123d2910716e0b956b764c45ec9fd0d9d3bd851433eae9a347e0ec42a9424a23101db38ec93c882da97c6f10ac0814e7b8e79c49feb19adf8a8a2ac23dedd27d24646fdd57f521390f178c1fd4604fa47ea1feba07c0072832f2b87c5e2da2f2522cefda9bca240c5b317d946b4a8a46b0cca540bf4e3c154d73c6ca9026d32487c6a41c34ccc220ef472e977768715f1d1923cac0b4f9b5d9b787f5cb1e7ad1c100f22b4038db61b1213f8c54510b5d50c744273dbe6f07aa421b1cc860e47417859e8803518154976498f13987309c346632377ed4fb19eb56336cc7ab71585320950b2e660264ec3cc3bf75ba9abd8976682fb4f6a7c4aa3b7e4bfe5ec6c2419c08e0dfea3d34bc22d41174f517216e25fd750af1c50099be49f5d8ac7276b81f62b9fbd22bb867d15ec44a29feb7ca1098792179d68d2b4ae4dcf8f42df748e049fd592e34222fce735570ab583302bef10d27e949c39f3963a62fcc483e7b970b5ddb006340f24cbbbee07dd11341fb466684b109c9b69aa4b17733073a213ed85b1c88a7cd4a3792f11ce277fa3e0879643fb2775839bf511fa23ef37bd2f377d9cce96f09da5c76bf8a09b5ec4ae24494015effa6c79b1824cdb1871af6a1d8000d5aaf22a8fa47e740c671c8d70776e775df0e2407f0c107c62ca1fdbb00e4736e380f4ae45a7a7df60cd13b35b1ece8504a962010450855251eea5f011c0ee48b79f16ec7e6dda4ab61266253c1a4b7805db636fcbfa16c3d08ff385a735f9720fc3a2daf2286a39c7cd9435bbe646dc1dc7467e059900453d6a93927e437d10bb91e9f9cbe3e83bd479c0480a1b32b43fb7c1127ee7c7019c26158e72dfee91dd5bae7dabf2f8846f6f3b2ef0a2e8cd7ff3353c19ce5b1fb59d01e6cadd6e88678574ec98be6e8c44181c07c879ffc030a98c0333b71973f5b6e53e0231ac537a82c97e6f50057645592b1022d76465068b83254b51a95396072fa1475c84b35861bcd0113c301383de87e2fff06400d5860b14aa74298e8738c0ca23dd5842011b0691b064185b384fbb601930a156370650df6b1ffae2bd2cc827f62fad560cd37665a3e9696e0d5a3b5cf49a67ddbd6708580459f3e9701d36a806306b9bb05fdce087ddda880d239672a0bbc98de8a2a4ac06676b6bda7eaba3109e27ae6e534c536ea096ea03aa10acef623e5b89e377e89d1c70164386a431242d92807b1c39e33a8adde022244f1e39cc84f64971f56a49c7fe9d27f039293817c660369bf5aaa713113a568a8f56abeeaf102519b7d4d94109de6186aac8ffdcaf2ed42097eaf195b3782b12b4b313211cd60de889b57f2513c7dc419326a38aac29bc5c9e6337066c4944c447a126c9598926372a76b63f9d877853a409f243cbb482956f8e5ebae0939e6c51835373f548dbb56ad7615b126813c0a150001b86430f30493aacd18d3aae54ec347addaa008efc3f6343e23fbd12213a05b9ff5b6465d625067671a756d0e4e8a993d11c475f5067b4ade4efca29e2cab6560ed5ec3d2f6e3dc18ee1c4696da3b5de53f6b8eb1c85e19d96d5d8d25321c8c00b556a7d7a2cdcf33b176ae82faf8ad4ab7ce5269abfe0375740bb7b2ac6c3d6613d3a3dfc461a2905a8db2a1aabd376cfb2399dc04178962b380fa10dfbe1b211055d4a8397ed00fe2a3e8aff2c4471924e189520f9b880913e99cad0ca21b16e32495265e2debaadb249c5243b3a0661996796d09e5ed1114649990f54afee5b56e2c1ac1e474c4422dce43c03d10bbaeab6e1181e39a967a291fdcad11a937673c3d68c8b9ad2a4f445d6bbd08d063b42221f274b682f6bb2eea41c939bf14447814e1ba1b6ec7db9f1060112338d652e21cc26bbbde725cf9ad6ae3eb884e29c0c73d4e49a4d1447872632aa9561b067290df6de2eac0ae86ff981314e27249a90b7b1d2fd7e8ca7cc8d4a4b386a23f445ec71feb32a9e063c2771b8ebde9ce4fd1cacd8a241a2286a9ad2ab665f856004a9367749cc1d7d1f9e3df3018075e8653ed4bc6c18a6825b11196f35c85dda3a338de1f3b090b3a588f03a1d2c1561ebe1d12f69eb0488f16e63f1c0c05a2da82d49e6bf264e6e5891a5801570553363ff7d00a1edd74a75cc31c9c799c6f804364737651296181283a6ea081f591445b10a80710a6b50f20f3c32e8857e2eb6687ef1967cf5822e6bb55b94ae6e2f1845dbe2b9c904c6ee8a12c157657c65f788ff37a96dd464ff0f44f6de90aaa7fbb4d6b05558de940fdcf4722400e0e07a372a59c301a5afe94b25c416a68f519260e64bb35484c6ed524c0142557654911545103a2321d392be068043bb4e678c8705f6c9d50cd3d4e51af4cc685813ed9e05d3bed34f3fc22b00f2c9398dd7b8284eab00cb0585fa0744c4769288f4bb44616dd5188bbef6a518545ea99851a00d8cdab2fe1e1b26a126088788354a76c34eaee0941393c25886a75eeecec65e4f9bff32cbe7c84344e68ac4614caaae35d2b35d4c10e2169d657be50ec9e2cb46b71c0d42a013eb09840b81f9b92185845cffa998aaad88dae6b30c37273f3711eba9a7fddaf747d5caeef0ea8cdeb2d257e1dbf9153ac87200fd591979ae5d60940219413dc2d90663402508eb85b6015a46f07277d662ad98a82812135591f4566921b7f796b89f2cc1f374916afa5d38d509335fa4f1189ba996665cf9c9ca20f1ff10bab3c346cd2f45139c19d5ed996ca42fe959cf2c87af3926d5d9e8a3398172f38380cfdcbc3dfb4f52d480d05e13a2858b8b9c2d6cc0a72e91e163020e83a606b9f54b908880066bf67626cfef4a463d8eef197c8cb6a21d9d6e10a7f62b7e1db5a7ccd5ab0cfa2c67810c722e736ea67282616c3c766d1ab6667247840c2fee81fce95737f5976874b5e25f772c9a7777f953472108eb5ad25dfa92fe34192005a24411b151c05c44fb23fca93b0c11a97cd9af99f75f58bd655a2adf7786f4248a3fba294cc30e1d68458fedfdb354c942451a9e4f53a876de4278e41453007839847dd0d2c1009d6994a75bb5c1ba241e1170b833e21a8940c80a9e218dae97d7be2d3161883f53ac819c979270a13803d70a289d200c10de1d4279f038a1a4994d428d68ef9dc2cdf47c77bac88697287cc11ebb68eec23d9275c6786e03a4ae6c99328466f83e5d6e1aec90e1630906f57789ca69f775a268a5a4838912cd9bdd8c266f5f5d548b5ce06abed7ffc885b6f1c28d6a8693fdadf81cf63e2d245a654c00e4eba27d36b03993e61accd70e801dbf0ab690a6223becded9e15cdbd91427e84a34932735ccb60708731f98d64260f45df47803bdc0d8818fb192efaeea2aecf7022e7a7ac90ea67a0aaf847507de340f10bd75d5f310d3c65199e6751021b859574a09af9222505b0ad7235b6add269523b42355961203d04c732269ab03a82570b3c0584f2c9277d6e7cbe84cb201e37b0569c52d890b4b990f661ef250a5db1bd28da4736898841ce3a81346f471c10998e4669057eb2a120a7ec68bc6288a1495b8a3363894b89e951b68c47e411ea042abdd77ce6ac1a52d5599cf144d7233bcf60d5751a2f131e178e839afc538c5f08601e297b05c25c3b21583f23aa670925c52ada5e5101efaab0db283619c0d4472a543ebd80b5a7d090dd43f477f099886a528b38c18d912620c4f9948f7741038f1c2b412bb33d8ef582ed01c342f9538b85ba7fddbdcb25587f830b72706e0ae5ccbea04936858ae9e1127e84faa8e75e092b1350042fb19e9a9a8817ab7050ee99d1f06d7f75ccbc5982942c0e7277a12e5dc94f24a5ba13ad2cc63c7e7ef2dfc47cf98879d71b00dc309f31338027a00dcc1795c3466381efc0aa3954c522e6ec1821441a5825720419493703a154f4d9385f4ea4ff1a5f5fda37e7a86148c07c1fb2f874c7eb3a80c6e69589dea2cf3ec3f49d01c6ccd6cb1bb2bc8071dc687c136280c4cc861c2a0cc97795c271aa8a48d4ddbabba0ba39364f7b9249e60f5dafd1b510a05c66f218532885c4b3233059d5709f9196debebdf8679f7aea9cf23e703b57a193728dc9f1c645fa8435d7bd4e72b7b10253d5bc72373e18dbb111cfba9e7b4608737ae7e17675ed46aeea50a035b59f1d760f597b8b96415d420c00eaf512609845d701d4a3b2916cef5b9682adb547481781d71b411502474e3880b8cc411b78743870fc5277b14d82034c12f8bd2578e5beea8e6443f4a4ac0a062026cabd2cd3aed068925ab47b42fda83c063d01bcbf438572b0f72f945c20b86829849f01c543425f4261956bfda871a30845eeb1ba1524e3047778620a0d635abe92a47a9b8ddc57b3e01a2747e60f361ea4cb2bdcdf68005ea77c0c2026a7b896ae597f6a7e2bf93073762a0afe71dfb8abe5d638315c3ebd69c38ce736925bf91c802fd10e385408ee306b76754e14dd65be3906a61629af540574c50c2baaf65520f60d600e1a8f55bfbb2db1eecd7bbe97b76a834dc238852bc43a28a18c53783f6072b027fb5b2272812558031bdbc6e837f0a60ea975763cc1736338ed705e35791db1b442f6444d9fc84e0978bf62c2d7757010d951407ff52af5d3a85404bbae23f334c5bcdf20dc524d18fe6eaf0854a8a1433c277c927055aa7530510acd5eedb4007b7699d4772748deb36df014d2bca48e1cdfa4ef90f729f86ddce9d69cab30ba9e34e8514b11a50fb8d121ce202259f580e4c116813f3732897341e15fca19ee6fd12bb5d3fbd02349a80cb36d6180a7f07c841a41370edefc79989bd6c97f4bef8f2bbff9a7d24ef344a91b1a6c1052ecbfd1aafb2c6248ae92ed9e872fb8db3ea94979e90591444d5ec25645f0da2410f3c32412b96e964a61b91c5b25ce2aa5bd9d2413701d143210cc4895f697adf06d142bd4239a976b52324d40d978f1eeb61eb346ee24b6a13f6da85bd59cdf3c09fcccd68973e51231cd125f0b43f2a3b8eabbd36d5561ef45545c646887fbbe0b3f2d7c58955dba84826dfa963fe27a5b70eaad42a7a450cb2f3be11bc6277765258c2c30b7526f2573b6c95ba6a7dfbc01cf304d77397506a0369a1bb808224eed122137381d82a9c16d39b24340b45ef47232a05a2ed068f04758cb27e152de63fa79816f808b6c792c30818b84d58fa617ac7acbe1f0b7f51eb0573eaa660da8dc799603b2ef25d0530f900d684d0371af98a6043d4dc7caa2581c40911a80b5229f2d0a0b60be3672e7d0623050974bbad8ce922ee708c5324d103f7842b4df9b910a3a217226c39899bdbf3910c87926d679d4ed1f657cc24f4fadbdd25b02b38fe37fdf2d116e8361e2aae4a8101209036012580a6215d0afc78f759fdd2e4a5c515901526dfdf497a0a1454e712c2fdec2d8540483f22e46f84dbd02ab02099d167a739f66f2602f3c7fba18339e74ad51b0d6f273a0902b8b813df6f73b4d8decb4890a0a9f7d9a63f9118e7d9ad54c386bf5ed3dcd0a8ee5b491fe9280c0fce31dc83889e77187efad3522ff2f92ad41e0d40fe3432466a3cb6b0b09c95e50c506fb66e8d4921e6940438d4b17f436a9095c99dbc25a6375b539a97b1e55d7f9008bb351a9fbcbf7df56035657eec14ddacbfbaa40adcee6ee5cfe086142ea7daa5807408c55d0192dc0335343f9351914c808ebf060561d90e6f0fbd39fd81d6862be1f465d93c19acc6b302b2ca3d78252e2485b00f638e4ed4f6faa6c451d833b0976922bc1102815ed7480da916fe0b29568ea1788e2322ba36a4f5afa8251a6b838e73df9bdff1eedb8b6ee6ce7c3103ffb9a94759e99c91f775015a7aa963d6629965058f2d8de5d3767503e62e326e2f2759aa38f4599a3999e7b95e000e5d9d98c18c54f46c9b690f872e21322728688ce789633b0928c0723c0abfd1e0d7a3614c608e59b60b2e08c83f9e11a97a227cef3600e9703b42c9a6b09bc657e1ecc013a3faf14596e803c9891f3e709e8376e0770f48a3e8db389a0a1921f19d6e15418f06f6820ea73357b4237c0ec2cd8a8341da92e3589900cc8483cad195495bfad2d148d9cca41209d7876288027af66059d909ae88cf4c7c4dc754296ba103d557e48357f4c3c11fa675ecb444b921ca8b00f34dfc1e4c75e6cfbf95c549b483662d2f4f421d075a8ee2a2396be7838d42f7c281b04d3f0ac0ad9d8205848c76b64fdc6abb902c2aae26581ad7b10b3328ad0521d4ebdeaddecd0b3a840dbeb2acee70177f50ed217e40d1f7b6ef5ee6fbc29d4d8ee908339e84ae10ed72b19ce26796afa779e986e0187fa9e8d61f32c47dc57a6c562c7d2fb67635c8c57d131ad09c1c0ac501c97c3d3710a921f6d0b431ae524c77f78ae06c8d1ad2b24c1ef4ebebb8ceaa1c633d9427be38901785c7d52f7bfac8e7e3f4089a67dc8bfb65bba3e9815b2991c1de3dabf363d69405682b4fcb74c894493e0344e16ffc36c277aeee2052c1081465326e3fdbaaedd7015fa7e4603ba61518d49b371539b9c153eb5508e8a14c4d300099b4992820c92108ba7f330325485489c0bdeb6d5bcbe719e45a3c86fbd08c88194e5330c69ad9cbcbc902cb76ff484a987e4570ee237244c6dad4be9a64b91edecb25371711b8d90d19f125fd85cfa7474331219edb122eba1639ecafd1192fba7d040b85cea2bde5ba9bb4f3955c61c0004eac1019ffe6cbf695dd7d5b6f53abfa016c1aaa266646f7558c292b1ccab3253c8b0224bf830feccf85655670ed2e1f13465270d2d13025e2141bc894196e5bcea012e76d95ec52986b71c3ee68fced34ba9e993873e355d038f6f1a737a5eae47b589aa10a5b9dd754ee177b2ac86ac564bb198fe663fe1b8b40284037514be481065c63c7647617c731f5b0c6159c2beaeab638e78b10c8b1404a605a65e61d60786b9069883aca7a81cfea0bcb72c5b1e9942442f630c5f4b1695cfa5b727073389e0eb2fe4a0d5acfdd30e96feafcc9b0ce29715d4c9715679bc6f25731a85b72ef23b31898e0097461de95de909e6e79644f4f3411fd048dcb94012df8b4c3bff47ef249429cdf612abcb7a2c38510069ec0441612c7184a75d064555f157c91a5342791e2cc630253ef5a8435eef6b5ceebb183a0645aefc6bf94bdbf53f953f164235b4e5c1f6cdaf5ca4b05b9cccde5d9c68064ce4a2de918966fcf369f0aec6ab892bb2d13afd9aca897309ed3862ac2ecccef1e5771b7423b85a91f66de7c1b9cb38875c60c94ebcbb14357a34287f80637ea99b66c48c55d546a76a87a2096058d4b942b7b9c1016a7b00bacc3e216de1b9ab5cc5bd7e33c5222130eaae2d43dfc12f7177a5d26fde95d49d55cefaeb6f5dbcb68689f13ade6761733384f80459c919f1df955bcd47f20bc2547a1d2712edc6e340c902be9bbcfe18e2bbc6eaf5794f0de58cebeed292ea0a0599237b66dd527fa06210e4492562632f7ef58f20fe35d643799f5c4af3d2a4a20764175c58063513372c565ae709102d5c336bbea6e289090990c3ec31119e954f7f450994d70d58a26b018d75193b01bd0602e5e00a329b690efab64d08b76ab667813d8fc57759e44148f27bfed95388bb9c3966cd692020e31cb95111fdc1a1a49e0178f462f17e8483187339f9d35ba8b4ec2811f6eb441fb47d6bd4c8467db5913a6ddb4f189d95cd8ca2dfba1d1c391d34284340b9cfc535a5418e08d591a22124a9d95b98f27da3f004ca5acbdd456b04a1b4336554409919554702163c8d2f8fce34a78b2687af2796c69f6daa76b632bad8df888e30243eb7918f44ac8e2f572211626d33e0a00396698325deae6574e6cfa161974c8b9d8f53f43ea1d8f55af57b4b05c4b3266fa8953aa2e12ea908bb7c605a7957068054f8c8e85a0c29a08180c3790a6bb23a31ca0a7e43eb08a3faa3b8b42604883252c3caec4999449726cda6e85552ad8bf89678993ae50e2957135051a18d3f8829ec4c8f397459c533c06503901debae45c4290a93a8022544baee84eea2308d2a9040a66b9de8aa51e166daf2f19be5f67193ce4f4f557dc756606233d757aa805760f269414a24bf5027ff8597838d8e292e199fa898a304b422e25ae73a898a096a402512aeebac1b8b7e02530ed22281f82f79f1ede3f4ee199ba8feaf33415165b3dc85bd1e6a7997c87e74fe2031a5fe0326d0da42732d16580b016929909604d09a005b12686b81684da0d609b0954052eb4b8096056ec85c013ed99640c4fb9ab1c9936d547cd0bb75e55ebb6e8bf4833c5d8e644df05a37c07a751665d127cb20071d07f1341b3b5d4cdad56cca60c70f00fc9ec74243fdc053844779e7bc342bcb23417d36c4a66ca915e1d77c3626014e892717f4981c2cddde35ecd31482a652f0a34fbfb4b4c4a179f2f1e8c9d895bc8421069e6fe0e222ffd3b68789ac468e472059683a06944f30d02c938bfd6283987ecca609f504242697b6fa57acd8cdd370841e8e7c43409f49c45cdc7e09f38b5fd769920b19816fdc8153b785f7ebeb7b8356e449dc3c93e6faf0695e9f4e18a88a51ecee2c967a1692bec9909e9697f45491a4356578ca3f72eccec9d5892df12c86f1b82cf62a57f124004aee153fc46768096029119529a65ecc2926f2045d602756db3eeada988e3f6a81c8aaceb48b71155dfd388d6daf32bc4f96dae80eeb5b8659d31c993283010d65509d67afdf592b5cb87a021e6b167710912222846a4f95e339e100d9876fac6d5e0bbbe24a7fc701968302e002307c3068051d3f9d61fac3107cfd013b283a6e37d1d95b72eadf9bce26cad711daa3943e874892cc547764b7041579e7efff9e58d8f5b4db0e52b4ae13ec9e54ee60d9be528e2fea963413c8d5b890b09a79ddcbb91b4f543200b0a187a47d0c60955044c7262a0c7997dc48630d3fce9fb2d81f6fd3fe6772b80ee45911aa0e7305631158b5193a212d858d3f1699195d66a4bdd42d0ba209b8339607da309408c56135d8c6e1ca639ea4e596857053e1701509d0b06474ab0b477d8aa2cd290c58a2e9bf08fdf8e9c563ede5e26e7a09ebafe5a3d2f9706a35bf1455f1ab4afb07ec48f0402bbe19bd9b5c64fbf483dd35b9ac449ad4111b5d2ec09297130e6131866b38f06b5cf06c593299d5a0d924b32f6c75939dad6e78c756775bc147424b864e9c0954e80069fe46c43bebb633eb34411e7b586287b5028f51669360d0c94476259d9e8e4c95c63cd606dedba85744bd46625f79fc53ab394d4cc47856a917d502932031ddf3f73301dbcc813daf1356c24b929655d2b91cb32793ddc15fa0b0892d3c6d7bf553a254de6b6d44789f7ffd4a82207b3e0651bb66a1fca89a11e37e2c277254bafc0e493b3f153efaedbcabd7bd61f4c37254ea6d0e51912d0e5f3364ab22f68a91b4e727663ece89cf42410860a418bf9f43b04ccb3d99306e9a8cf2c2d539659f9632f185cfa19ca9d3d5f7081bf49e5496c2586ab37e5be31ec596492f242b57945b147f656ec0fbc62af944eec53f165ebb3494a986d91da0872ce13a1338160d8444ff668092d21755c033d1759b2df3260adab1b6d96870a623a4540ba37243d89c8c12e539a23397e5d34e68ba0f315ef291709943332cd64f74aac076bcc8e5c3c8fcc1acfbf946f679438beccbee71f063438b59107dbb30cdf6fc2439d9811ccc056eb2f9c67311abf22c1d8b6e712d3f37ac1f9cdfaca7a17b9215f99026f230ce919b7f91785a352ffb13d4a2453fe7299447b02b36059a6e3d205e36d1cf36963ae5db04e4e1553fa6495d37876796fbc20bbe779f32f96384a6978a0e6bff21e4fd81ecac4f6ceb65f31c6c9f65fe7e41a55f5da6285e9330182599692d93971619fed8b604032d808ef3cae83155aca43e03f4d47e4dc4c9ff4989646575cfda58d23de83967fe84fab7052d04a98392c4c027a37968a30bd9a916f53486f55a9ad82d3990f45a4e471edd4ef3735f02f251f6846092f1f4e10699fc75704e209a677e91e297c39aea4b0b6e9056b959a28fbd2777b108a1b8adb99ee11dcd988411cce35c623b4243a7fb6e14c8fc64d87067978e813991c3f897bc55bf2fb10f737d34a9ffc822279f8cdd01e286332269d67af8f527ccdd582d626d47c3680b3efc5305ecba461503e3ae468c0bcbf775bd3f701536e7fd27741c5778a8c92839de17d166e04005147064c83968fae3a4970cfef5723a8cbf83e1cfd90f9f410f45254825c8f4145cdf6923d5d93910760c8c767a2f94ded9a152cb9cd5971ff25cc3825df074c41f115fe9e6ab28f3933f01a2719a3dbd6f49d6295e1d671a36a17c0ee2dece1ec5ff85c6f9b489b63ec17bfbf8a6fa1b8bc20ca0891b89466246e24e484afc5552d63a8eb7d7d7173eda68430206a70aa992047b50087f73dc3d7d1758cf58b036d3a7a25758b902244453307e1a7a0289e2d853171158c4d48983a52de4685fc2dec166f1b259506a7586e133f670b7ab35b4a2ff394cf4f977c521348d141c385dc5ed315a65bbe18ab79e6a62a8ca22e74639cea7d29633536a3b526e38be5d3f312d5356edb35e5980fba17d23dcab10d69b28c1fdfe265128b3244a0150f05d45bd77b0bad28daa9794b1e31bef58072906c1088888a583d023bb3bc5428c9cdd1daabcf6bb48e4fd7449e3246394ab988743e29f173e046b68dfe62f1839b0539fa781e349d672ce270de71c7ab3df3069a0d41df321f7c01c65439043681be4194c92c8490add7447f5a77b1187ac2e6207d6667d331a71aa775d4fa2b4f1deb10661a0921873bbfc178e4474d96295c0061973aff3178c4c3497ff84ab82379a319fb323a0ae1d62fb0a8f69035a694b5e639afd361df6153f8908d4480599037b9c2ca0104b63eebccb91cf290e1a590c54630e9b2c0a0744173d71fdcf30f9e3c437999958eda286ef2a5743667a0a8909d5c6245823c257307a6ea9ddef2ec7ebbc9cb8a5af79ffac082b83c23d9de55c010a572208b79c30343f322b3a8819a9c9b880016390aeb6504b2c07b1bbfa1351c49a52fa5350ab59030bc49001536881fb1ef0f2555c1e32bee0aa118e5c78796dc4d3a3ae8c4698472286177c8a0059a9183033778a6652f950a90cbe519aa849a23b7613830f8599fdcb82c5cd659144c758b77e96c61a8b96242330280df1a17a41e8a60ebcfbd89544cf4825f7c517f0e4c20aea35e4fea853353feba89780a68495bc369fb001d63aba8496bde37f4280bb06782a9811f689dec586caa91481c694709ed1c5e3dfb0331e0a25668090ff7391205c1abea186f895aa144efb59383f87ebe3af3677c33875b70e4ffbf9b4b7ef87e376629cd6c580a6596bcf425e9a3567529b823e6aae2e945f2d869814b33a5c6e8b9e4707fe00a2c6ab785589dbd21a4c33bf526c01d33d65bbcb5872b5016734d8b55ce507fa687fd0f1d5a66a648e1f7e5fb94545c74b94d5c945010e3412aff69d4357b06e41db5cdfad56778b1ee0431dacbe42fe890b688c861f86828faa591cfd80c00b68ef0774a84a1729851abb6661149da3dfe3113a24700ee4136464ff7cb300129b9c1bed0aa10bb284c2dcda8937e866b8bf9f56f4c03199f9720821007668eaf708818602cec95401b020241b9be8de4931364ab70d84944edbbbf6c118446123d4f7e02a0321631ea87b0ea22e10b275afab0d141ab418739240e8ad7bc7742f4d2bd26672fac1d50084826d6d0e246e4f1f05ff83ac4bba3f8e36a3d5428abf079b8f146e54cc6bc977ad4f2310b03c052d0e9c06c14b564dd8fd91810757d53274fb0183ae194ca402901cac952bb9bd66be7b9d87a7c6699ccb131b6d3d03b2b33817e9a5537b341d489512a83ead49502b00661698fdf1bdf5724ae93a10f8d59cd589e3d4d8388b6ea2bfba18f84cd8746a42eab8b22339f9eee208074e0fa701c6d124f2d08cc9ab6894a6505f709740a98a0f98f5e32d5292c3e4f43a619ee846c960f64707b47b3a5eade275122d6bb8eb7a269368f9f93a99303f81bcfc0c33dede9a2cc956b1becbd293636495aa97472876b72e170557753b48909ab86c75b3e1ae4d3aa2ab96fe26620d79593f164aeee85cad3a2a0d53f9adafa92e3077bf6b9c1343794df96980a15cf8396b0a686cbb0d8c70f30400690aff0d41712c7a2743555edf6ceaeae02585163cde52251482b0ac9d1a40e8f9e5f399a52dc9eff4615a2b23c56771a843458cb19f1bc98ee755e3aa9ccec1318ff7a91f8f636e49373378bd44589374fe4a2562ff5eb80b06be4488487d053c8d67e2d35e7d5b1f3573383423149fbc8c0faa318219ddb45d0834861b5814d23c01618fc2c45f4789af8c42a4e3104820191edf7ff93e0094f0d49b1ba8d89282e35a181c0a96160ff26e44b40338ded3eba00316006a567463807f34beea77580f237b11fef6ad588bad6933e6dc810748339f9084df569d6310ff25f102f9f922b0346460c7c0ebdb2e206985440c9cde801ed399fdfe8b814b16e79901f5e63b12876d570263914c626c64c079870c1046aae9a27f95fa5544a3dfbedd51f2b70b946f0938261accc12598e07692cbc708b7195c5132822a4de33817c513c861e7b1741f4e04bff2cb4f3f8222bc23c3dae3d5311f0afcdf9e8c9c92c7da172b72f602be9353935b30c223a8d2898dcf8cac161093ce2ff7e9b44dbb82c743f3e903c0a8fe2c704b03bff98804dd716352feb628936a308fc45c9ef1abdc53ead4b737b4933c291e460fc1d64b9ae4befdd0362fd98db430c6325741cc4471db8bf13f97f5aea21d720578ffc776251525c24bc5a97b01c1ea747dd5873243a65c33dba25ef9e90ce69ad8392619941b3f6eae6c6c11fb6f7fd8c7ff806304dbe51d7a24fd284b5ad5639782d0085752449a89806ccc1d2c937bfa6a7aa7273bd2e17dd72c4a9a211b38ae43ca44d7cad8b90ccb535c7fe5824a820e0617937721205273526fc0ef6502a7c5bfabf6aa67d4d83b94a8116122c820d56b0168df8b1927eb3504a5028c8bf7ef456453258d300029d1051c5a0434de4618ca2f137612119e901932a7c90d519a741f0725dce3bd903a2dbea519295c6d398d05d412ac2fdf4efcd4e97d7563ae6824bd9b5adc3aff7cd2bf3f761fa7bfd091be100eb6038fa52964c09654a26f068264615262727f8e1aea9e3426e09df7b6a9d1bd8d615b5813bff6b3fddd35f1d33edd22be79a92abe7f3742364974a05e2534040c6dc2257cc012069e44eda63c470d2b7a3d9064d5c9fb877d7c4cd5017b7ed452356adc892e90a033d85e3d4b797d8aa20d33be0570d07cde8874897d6eb359ac04f03a0cc0057f3bb492971b50ba560add9be01363e13812dfba48046939c0443b86731ad931dc311a93eb0e6274557072e66c1554c124c7713198ee81e7bf07dd117939e60a30ea1a6212b098b3851dfc1197347ceea58d76e1742dd0d56ef33f301928bbbe47f2e9bc2ee98660a983ac1680ee20d090a128106d0c2c9aa81e1df73936d06f15fc4a295a9740b36da7093f99f27db01335579f96cf6c28359b67c5c3bd606f49eb29420006a782dbd90602c80c3e396f7d5addeadf41fd7451a321b8336863196e140a5ab1bbb9f2755bdd47fe25612b3b63172e01464b974712a35f2ca502b55da82a4d7fcaec60337509fd4444525edd0558bda7681fe60a060c811da9e215183f5816ad3ea893152bf645d08b8c098324ddfd87586b2f607cee6a0f278e808d69a3da9c56ab8aa2c6f9a01916a1bbe6ede2440929b87d30ae87054db58259a36edcbbe0c34d3a470943d29a76c9d47f899ebdf41c4913324925ff621912b8806435dbca18c91d33e96200ed17fda88ae89a5872483b9d636db630d4d76df8951866bb5535e08a0316e35e461383ae2c414749028209c0ae8a82a5978607136f7becc206d5db5255bd077687875a908b7a46958f508b8c0976f30d983c82fc7e71eae94487d906317c320c353ee353d1569850d5828c29a60cce9112f0889d42460f1e2a405fa9ae196702e6c394228d01ba08e573c822fe7eedd74d7f3c753ea29569482a8ed8bc7103b32f1abd956ba7834ea5f3588156cded07a68a10e2d96ff03e7b96c85c8fcab64744c406511ca5886fa8e76b99ba20bb66925cf5208a95a5644c308eae5588bda1d407fc608e9b4d5c95edb9d3ff4afd54d6f18710f435d4ab20eba347771b22bd0e286a6067eef5ed034eb53e0cb1fc44ad6c6902c561f8229007a0678bd91de5df55f4bc2e5014c4d62e4fa40f62df01b87340b14f08394f906c63c3afbdc15ca078f10b209c921baab56187a39a122378a45ce091b0800f0805c83691875e1b6d61bab5118e51404e6a1ea169663121cdd63ec554a393c02c8f473124c7f3ac74bfc7782e3a10a9295d76200773dbfe9d0cc054103236af42a169c0aed88207c93eb5d594341977055e6d26263c56525774efda233770624dacadf236aea2546d4642d0b8338318769d93a132da3ccb08bd711ba9c827fde2c338961de7e30ca0a56534705e07b3220636a0916a8b38c9b8b22ca379831b63f0f977675fd6904a73163f3a5a465ccc64d43cbfc214b4be8c525b780f85d10afefca354d2ca8e784fa4bc47d0d83d0c4730792544ae2c05780997427501ac235c51325e803dc1419e9827580d25b9658fa220e10339447fffeea4550922d1f438a1673a8db84b58c93d7afe5745ab7ba925f6b7a94e543ed8f121d0991fceb93fffefba71cbeafe6302423095fa124cd55bce32318b09c5e6fe5b28a5c46ed59cb46cb6ee2988108bfad76904a42acd014e3535626fcebfbf6e7816dcb5224fe4a809473638ceb3e3848858623260fd7f4e3100d8295d62dfa095ad649d7c456ecde3a58b2527f1ca1d8f9f97ddcdff975d8595c01d571ae4c278cd988bfa1bb54dbe0feef3da425e6ee8aab2f8a1e4ddaa39c93261955a000214fa66464c17d8c8ccf6d2b7b1952e6bd4bc5671e3dc5572ebb3c85d17334d055ec44e14f33fb079f9948551cbae5ea66bb9e8b09176b3c4fea0292b52a7871c62fe67dd5d8bdf96b1a629c4a4dbe58ce56581fd92884075c9a22159ebc8a7d3eb5ce01e44d28204a5e4f18b65b5dd5882114322be0737c191d569fc4a67d30ff3116731be91e41742f23e2c2731cecd09b69b484c2cba616d162ae3223a29a7af631e24d69b79443aaee89cbbac19184f41b3242aa31199f35dbe06ebf5a587e15225ed50cdc9960d2b900211accecf69052b452ef1919b276f9d9bfebfae8b0ffcecca30ca45bb8b7289ec923aa6d17c4ba014ab2a1786a6b11ea6af1d53ac5e87c612f815ec490ef244af835bc7a24b3f8bf66606c1822403ddfa7733b66c1c80bde29534a7103c57d3a4ca79fb2cad356ee0dfcf44976e97ebd96a5c53defab98a4597cdab1b419e271719fc2a7d83e04ba76d29f61f7c02c418e493e58b77d183dd457a3e2c18043d89a1f2834dabf06aa19261b370c35cdba6111e5f2b8c85b6284a6b122000f8a3997921ca377d16256eb90ffe313e4d42d476e48f36d6e7f6182bd37ea6d639f8a385cb16e9c601b359d057edea6e4fc29182d5102f7d47f1a0bfbc2f798ea549a02b89793096465ac9399ccdec9dd3b3a3c59923ae23ba03648785b077bcd6bb3604862d7eb9d0fdfda8739a09883ff476086c15dc84274d2e56968db5f1124df91f3459086eac4f2c46642137142d73bc0c060585a06b3ebae5ab54d9ef8d519cd1bb9f330fbbd47eee87461f741724b9501c4c6a326c4a4e44b0e071805b4d8c6019c534c656d38baf2eef68e201a0dd47b0f20615707411ff33bc654997825b63be0518b1b121357f30fad01b16fe6c1dc6449d0ca6e5440c00627cb898298332ded417dccfa62322ee4a806b99b06da456d22b96e5db394a87de48ed7423f1071a6ea22666606a86aac59213f8b434c10bbabceb25894573df14c4b3261da994de42ca66ff1be6d28e6adf398b22366a303e7fc795c22d1e1c606467123ba3c2af5cf86ab82e3512864a2ae50dcacecfc74deafde231f575851eb9f376a3435dc0c434b5405e6932700558af1341e606347abad7407a808bfd4f08d632d2ecc84f52776860170ea33f9cc1f9e33340e976d936364b5d50fb0fe535d3da81bbb1c11fb0b4a1d34ed5166c603d418a9d420e4b25fa7482700cac7525dcb32b93f2bcd6b3ad1b0e244bfa0c09894583e113bb9068a311d5054af4a19d7fa313b6703d3543c912266593042070e03d0ea6e38261f1df55dbfe95801155d2df9fc6ae9becbaebbf518d567687087b39dabd2df230576c578a652b2b040cfa6dbced15e0e355eb951de4146a5dc603789faefb9a671439647470055de2842b3b8a27da3cd988bef3d65737394896ec5ee6a4e4438014e4ab396e433ee1456ed6baaf499686f943a2cc9a787a910b69fe96f7013371d0e9fa92f7475d1c2e6f086d5b71193f26c5bd3a8c6201051b977e6316361f9cba5427ac69b48df992999b75d0012b0c55d8e879889551ef0c1772bf782d44acb458c1fde22479adb65c3d179d9a71f6c07d6c803312386c2a01a00979c77d72b8e33b7f75cad3bcb0bf01d8ac5934ca261aa13bba67f7f98ebf8f0f29f31d5bd999a7052b31dc9c22b7c9ce252de7214c6c2171dce72a6dc249ca3356510fcbb583cc72f19be8fca0ef86d407cd7e431fb068c628522a691a3f5735125cb2b7486d0b56a2d75ff307d05e081b3ea8c1322a5b7a012dd75d620db9052bbb5d37a41f0834aadcd69bcd114d5eb467928fbab08f1cbfe812b467a8a0a7ededfcd8d5bc329c48e0532cdd287213c6574b6e6512b8f211203a2294c4a58aee1a2608553f54aa68a8d639fe3646b36372a5038240b10a95e5433bc534a494d489b91118da704ebadc1b0783a576944c66b5e60d1f57cdba697be7c6b431e3891128da5575a096d34ed6e2a1cc149e5c74c3911dc128aef20f41d90815f83ce6457582b9f5b8e497d51aa38f91682ad3cb495599fa53329c99384c17cecec52d529ee7793dcdd7de6e67cf9e3313a9a6c65e57296a2f508fb6a69839b2dacdbc1bf69e1709d0d454aaeae1ea8a815765d647738dc3ccdeea5c33556fda15201d96a864bdb87418002494de8c8cb0041dc6e8410119a7768fd05724d9b28b22073a9c1588c6067b9ae6e15c0b5e9b19d4f7f3a83ff80b42780b10c45da1e597a1f16bc40b28399c48a6ce234a315db8988b225894b24f0936570152565d72d6bcf661ed8414cdb35d324cb35462e0475a74ded25b2c8ed4e4ce9fc535c5f55cdfa49a662dc218fec8c39a5a502a5b2b0b684bf6e106c1fdd536df27fa1721865624cb9d7dc47951f547ca479db64b5e2b7854e46d1f3ae1238c20016e2535fb957115cacbb2b861053f6db3c50cdce3d556cd8fd7a322dd6bb0204150082488b9742e5678ceeca297c2f83da694f9322e99609fab60a05104c0529c7601c32cf5d77e824fe00b683077aa0a016542a1b6c547289332118ce0719f5883cca5428d8cd124b09bab77a3d8849af14aaec684b5d6c3319bf226b0280fcb9dcf6cb5d4a654c669aaf09b45acd7153ff770014fed7e37a8bfd8bb31358aafb377fda5fdbd42639da62da087a9aeac4220570a66184a3f9d83ebea6724b5330b48830d008d28045ffd83e80f01b41828c856b9a0f509fb5e8f0cb1b1350fe414c147315c0d53f5e571a999f618d1bc0a35051860b3860e1630359cd060d4f786cc9b9ecfd2006bf9801f76705957fc698eda0e78614f8a1dacb9560ae96645b260e2c6874569c11576385a925fe34a7c292314e6ac2c10a7f1c8c33bb775248083e9732de0b5b85a3a9c5290bb87f4917a8ace4aaa5e90cb32634a2343b4adc555c9e1c09527806cc55662b8a8f2e2e1789212be2a8f106b5321f61852d8db1c3d94d07fd24fe57ab4bf491243b846a667ae1e6c33a07ba590c8adfca712796d611541a369938df9ea629543efc6bbd69e62e6821f685889442ab312fb93a666e75798273c8fa06f182f608479be63c633621b46f2bc3897cfe6593a94996ad593601fb44299b5c647c3b809ad50aa039de358b68652299b94bcb048d9f31694b2b7d829245ac4cb96ccbbb8b7e671e5d239ce9db41f5fcc564aafaca3d4bc9d512b7fa361d5a9c4f82ad65272628f1256ad2953ce02a5759652f6641ba5cdb88508e35ff7982c85a17a84e1837dc2ce96b98ec2ea5ce9cf313262283f2fc2bc7971d50192e715f00243e00462e2b257370c0d0efea5d09bc5501dcb63ca2c47fe7be00fede0b2c292c39d843aef7efb1a2706e095200132967768140f93f8461c5605291557a36e4d4c16c4b833eeb6deb5a213a123277983ab445a1539c0c7e367e56359fd5e1d619dc3763d190af62f993ffeba9d05eecbc8c2924fc784c2a22db8c6316e9ec067d5545e01ef26c2bb930754e38194457740d196a181c4d33ed36c92fc752e12a5ed5c92c50af0159c7f24a5999035dcb2528143a44469c6c471e35a2dcd54db403eb5846c3a9cc1f34dde5a2ff6b084e23369190fee0a6bdd35bd93fb768548e0ec0b0632a1e81f6e76b9f268ad3fdc40370bb133af9a7614cafdf7364e837441e9e8cc85df6ad5c2e09d8ca92bab9b5224fefee4342d2f43151739299a14a57eb1bba1933bd04af980a3fdf24bc8c80010be85c9f84659aaf19d59101a10d4f6b516232f52d9c211d8873783199897fe2ba6db022b0e2eb5388e6c13a30c1f327968a23a1be1dc1c682bbd41242b73bd544d021beb7966365adbd3c8451256cb901e57902ae87dde742104180872123ee06f443d946c20018314fff3f409142790287cba93aa5647dabfcc44d71a34347472fbd34ee82725ddd4ca767e887b239d83ad48dbfd980a8a8b782720d2cda273950f6640788d3392bed3a136fc922f33ee6bd6fdb0385dbe5a231207f246e54f1618ab975184704d4922c603a0aae44ac93f3df1c68d8408012b2caccd3a4c213838842a42d3d54aeb497644dd0753f2dce8483d9de024b77ab2f814338a8a7941ecffd9a5ca4402361312863345c5f7023c9b5ee1f4bd3d1bb1756d652cf633cc20a4b9f6a0ee6e51d685169220f12a1fd317fd4f4d3394adb6788a0909ed18e24217db67da9ffc31946fe348aed018a56eea212759e25314d3c20e33364a72e324983c34b5273fc4411c61d823d97bd333449445bce17a4948b5504e876431f1647f0248ab041ae9f571e5d42efa3b311ab00056a2747e52c13cf0b77d17ed383f9e02dcfc592f3d94447f3c18d3c9a70b788119e662b3c0678900c1a4d7bf5c52040e0a46b94c537b916a4fbea1be12164dfec130e2ad141834130a808dce745e6fa72cf802fa2dc9f30f2ae8a08374e4703e334d8c9183cb3d7af68916ac7aa56aa18e7fa25f6d3ae7cc92fad37f930a64ff3845ab1147b02e908efd190bab8b2879830d911f67b34ad75ab3d8cae733e15a2c421f856795347f6d8e1510d91ca79750d14d4e3971ed0254da686f7317ff51191e50a8c260791f059e8a02ad7a7045d6294e511129bbd106335513220b6df5c695f95bc7794c2357c6230d258b001c302182c36e784bb87203bd20783935b0229b8f553800455d3cda982498a890ec703706a340460408cba352c0e801c9f0ed0644831bcdcfa0765e470b56d6705eddb26e2a07b6be3faa9970d6b3238b4da350f4b934c07743e8af930362d7e7e3cb99a9667ba0adb8d323fe47e65a8110dcba9163320224d1b2208f65dd0008325ead1be8dc6c88e30f02c5f12e258ef13117512a2622defb4fb4d0e388ae3b69b91df295130572eb78ff519e65fa1d68dc7f223cb50ec0ae40b575f3432a86db6f8ee9eb6ece6c15aefffd3209a25486b48438e4694b402ed0e96c619626383346912399e6981ecf45be99d9728f198971d79f062fead81d53d5009811ae8d18bb82a3e4002f859be405c56c201a05bbe408b7a300521d07d49f0c09d3cfcbff76bb22d9f7deb58e822a1644acc0cfa1bef5d11309c5047120e5328ba0b5a61321178f6588f8ff49d8491861063717a2d8e8d52264c4a857690fbeacdcc31c939ed7beb538b3b4abb0ce59733a854a6eb6f7586db1af041f304b990f948685555291aacafa26ea395e62b8d5cbe4b556442263b274b1b5f0b9cd68830dfc04e84a510f9a98d9886631ef6eeb4e038606e8a3bdaf6a31ab502c3615057f66c31f23bfcaefc41cf184558605a95373754f61853fd7d018c18df3816093cafe6ba471fe0061134906adf15e9c70a29938bf7afe8fe3a945ecbd648262a2566e3691a22999e4f38074cd3a65104c4a7ad642ca84bed300854246b24918b3dcd147e9f84b606c92ced4849600a204a2a9fefd8230f4a67daeff0416ff381d02f5c7b063e068e6a3f37dc034243da54d7df049df311ec04e0918a15271322beb58fd5d9317663fca95d71f81c68b158487a666828d8ba7170318ce7df469b0ea8865bd721ff64745bb0960d7adea896ec4bbd28d8910fea2aee574b8f8dc82c650c73132bdad9ca4311b5259097f74613a81285ffbd4e121da245ad610c0e44cf9744e70b8958a96ce8f0916889895815ac639ca468a6339b44acd1cc38e5f51b506419414869278cc787bc0828044a75a8c6797965a6204afda7fffa781b517f43371f1d50cc494ac634cf823a38d0bbb366e017aa3dc85526b8102cb358a6ff94c23b411648026ce26951fb49c53b4d11f4e7917c7cb48c0949e49cc6c737dc5311c606284f2eaba1396fe867c16247ea62b2ffbb2ca32985da0c648889fecbbc6471cea85e3b5d9fb8c74de0db6d290e6d7ff313363af7f78741d6e9f315543ed2c6d0f989d310ef2e49fe59af14c185ea9abe889f21dd861ad69f810a17cb21a878d8f94810ca5b4f20bd4d7971bc72b5c54b0861016cd7a409b1a51ffe2485a9b479bb012d4ef138aa229e61011e93261f1f0af64f9b8089ce2aad98d5b0198a55df850699bdbd95f421cb36305e29a0bd440f269c0521e0056225d95227eaac68b7244b0849a51c7fcd79c9f131be3d57df91e996fa3a7aebe8f5495585aaa8836e0fd0f7ab3d7a22038c0619fd2fcb13efe1e050de1f988ed95e43d9ca328924c8baced9f291c5368ec28bdee8c17cea43c0e0c04542c1f2485cde812ea1d1758575a6826266b721043740659ae34f9ecdba898bb4793cb3fed1e883c21e05a2eb4bd024a875e7d5a405c51493a486f916b0178fbad9198de20b2558d160c0b4e630132be37f96c85d5295a8d4881a249bbbfde5de9869d2549e3843c8e42fb7413f7ae60abd537defe1cd8eb986564338d5a1d9d70fd1b69841042f6de64ef2df70efe078b0764073388648c68c3f91c5b24472c3e4698922a0a51e8b71f12383441d0d14f86bc4aaf44940c0504015730530853f13286878787878787878787878787878787878787878787878787878787878787878707ebc9f224232051e4a98becc178b8c41499c11144a5802e9165ef85f82f1a91e533686d494a150d89f28098224729202ac20744960653e2e1115ce4c85541a3760439f90ef5ca775feecb086ee42f91fc1592bf427a25de6d3e860bba6f0ab1d6daebde17d65a8bad3db1d6da5bebc25a6b4dacf56aadf564ada9d65a4b6a6d516bada55a57d45a2b49ad2caeebba466a55715dd745ba2e91ebbaaeeeba525cd7756dd74529a5d7c56919a594524a29a5776429a594c2802c5a29a52fae172faeebc5afebc575c1f88bc378715dd7ce85e78e9054cd9e468991359120745b808a7e86805c2360c0f0660ba6aeebbaaeebbaae9a939a96544d1f4191356930758026cecffcd1336b00c57579b3074cc5e717b873e6e23afee5e2c2c5d648d57cb13a91353d4023636f78581b1058265a3c09e44920fb225532c8f6882c490539c4d248222b2385581ed20a560919c4c39d5de0ce9909c7845385a44ae65897c8923f903fb6a5a716b580608db881a928d342a6b6a44aced4a0c8922af851817cd49f1eb58705158a18988a9d4d4db4a2896a8d547551d589ac0e038f9af3526f5aaa4d475199e821988a3d2c7aea8b54b54fed11599d841652693aa8ca7490ca430955891d988a9d57f8f071094955db5497c8ea255ab525e72abab9866e508da87979b95a52d53057506475087a5c403331578f082e285c4462500cd289ac7885969ca29ba1cb263e7131118172722e9d2b0841729432590ee1520207a63aa7c09db36c269ba1425215692e97c88a2db8b95a6c68510d1d7ac165848c76efcdc028032da9da40506461208602c1d01f17da03cb40a120220ba2065123550dd089ac05c8ee265026148051ab82a9bc48d58c1e91958229f13b3459ea90a34ca14ac880a9ce15779e8d8a534868fa109a3f64e3377571c9b2bdf76c75f7df33680665d93d6bba659bbca74eb778cf97eef992659fdeb3c7ec9165b714ea965df29e2ed325cb66f196adee2ebd6550b7ace996355936c95beac856f1962f6d7323fbfd4e02b2640fd923cbee16ea6ed25bba48972c5be4ddad6e65d9ddbb833a28cbeeaee92ec9dfb44eeb64d9dcbb5fbafb25cbd6de23cbee1e3145a8bb5db27c0520ab5d46efbe107085097540ad1f556cf57140560c8a29f2359dacb90157d9fbb321f7df955efa8a3a277075bd638f9822dff7b0e761ec1d630fe3937b3ff130c63d1e8e19e3d9d3a8387da84b64cd244c217406cd19644e25d093136feebc38c63ffe31c6c7c7ff8be3bfc0ff7dbcc0311f4f1f2da99a364bb480727e6e7a6e0005c6deac79813b7bb873d6c2e43731f94dfeff263731c1fff14d4c4ce64b0b1cb3099e2f8d82d189ac19029812df236726473945c0c4ef4d17988a271393d3c9e4271313939b9c4c4ea7dfe437399d4e412e70cc272c831a15a5508fc89257982d344553e609254c4c3c09e402776e813b672c4a4ea79292d34b4ea7d34f25a79212939f6e722a2929c989b904cb1c21a9923ad325b2641082cc169fa29da1214c234e274fe2c054a9a4a4542a79a9a4a4e425a59252e9f4929f4a4aa5929c31e1984b58ce342ad20445966cc10d90cd4f4d8f7c011425259e9481a9d899850a92528984a4749252a9f412498984a4e4a597944848488a54e09849620fd189ac1e838cc99130f246ba489b3230512a794d04532324242323241f21212139c908c9c848e9242f918c8c8cf8acc0318fe0f6912f52d53f3d22ab970053e287240d90e4d104a9040989d73d2b70671522a491111269e4a49191918f90464824928f9c64844422b58d088e99147f4897c86a1cecc8169d229ca11c48234646bcf60153b12391ba8ef48e44229dd491ba6ee4a48f90baae6b981438e60e374c4baa9a475064f51135404df3d3323d4d02284824af5f600a7716c19d336eebba6debbe755df76eebb68df4eea46edb36a198371c856aa42a0ad189ac988576c919724364a30526bace8b413015b56dd3b4eddab66ddfb44dd3ba6fef364dd3744651a7d523b2221180729432465022cbc79c51e70cbb9a76aff6ab6957bb77bbf64dbbf7469a8b238d904b644518c094789c6ef951e423c7b7119ae6c599ccde6bedbdbdf7feda6badf67bed5a6b37906da05131b604459606a20c50e4f1135f7ace00c5bd5e06602a3e63b873764d6be7b49fd64e3ba79d7336e06a408d543d4027b218409423e426c886892060aad239299da773ce4f3a299db793523aa3ce78e9115937c094f82805a00454d134ad555748a2c80b8d9c321452b136e7be34a5b1e91331771351519eed79fad9f17621cb9392d0f5ed7d69db4252a5f57d892c0a53e24f5ae45096750459a24aa8fc92e57d9128f23077b534ba9c12bc4257284b7b851a45b26001202a1ec046160d98027b54902510b9caf11f96258ded069c11c305dd77853880bd881105257002093b3b3b3b3b3b3b3b3b3b3b4e38e1841340800001020408102040800001e284134e38e184134247fce46ec25f8329091b252fa194b6bb08dddd9f7d7d7e25d8224f7bea98ad143a69029f2cb54ffb6a8e24243a9b214c115173dbae513a4c383c2db73752ea9593464d0c5fc0d48c41f2a4de61ea089d3c6d892d73c63925445df014e775fd6ada3b744fa0278c4a3b59a60a4037e26e536a65b893d948149ab5620addb21d9ab9edb3999cf4aaf66299b671a38e2492628444c58a52090bd3c9a4858b634fbb60d8beead6e1ac28c88f0f4cd1379013604ab693e564a14cb33b7627bbaeebda9569d9657772bd25baae2cbbf4befb4d9eabb7ad5e8139bbc45a056f5b25c60bba7ba38f88031f2f29b9f771a38f7bb5958cf3b422eebb46d5df0fb46eb2dbeb367b6fef24604126a7d0ec7c86e93b4c7bbb2daab7b6eb848a8ab0793e4b21228261a3ebf1da6baf1d6d2cf2369b107ff71490aaebd328f5d6765da91e4a1f0d0a65e7fed973ffae8fe6f964dba115d97cd0a1896c3e689e8ecd8b3f38f6dc21a23933c232865468f4e0404241246a549d31b9be614ca32a849e1c9a386816d71cb11c9244b268f2983153a651554611069822874c1868ad9c018af981c9f52e924855891aa55eca7c192a3d435e5a69a5955266d7a5072b51aeb733bda26d2cd7359215ba4fca5c8f97dcb7673289be4cafc89873646f9a6d63c9bcd91ed5014cd901a9079d1c82a97a6a135930d7d31f37d487fe642a6d069491e15703468635600d5823d71a50870c51d9560babad975b9c12539b2a8c90405584237095fd25252452c30c4398eaec3b4639fbbdcdb08d5b8eacbd6e6868b6cc97c9a3fe6ad846dbfcb879b645f4f27a1ffd91d48b3e33fe789ac5578ed4e607bd6954c5a141647c78ecbce8b4e49a65574c9649d44c97fd8b3d364172ffbb62723d8cd993eba74f51afec88993b5149481794ebad9f435a456dac4b07538ac995ce7cb447dffed36ebf791ffd893d1b4b5ff38a8899f3beecf6d90bed621ad4a87a8a2910a6411a55cf61fa436f70680e0542aea73f8029f58d8bb0d97ec34574990a519d46a9af3c685e7eb5948a6cd150ae45275186eee4fa1ab61e3a5b4c7f50410ec155a59144702573b555934331a58899b1ef1865ecf659a957be2805ed59d7a19f2ad4536a751f8d56a3d4b7143a29997424b90f9190eb2954492440947a3fe1ea1a69547ba5fe1bc55d3be79db2d6811d3bd647c2fa7cff4921b96eeffef5bb7fb5952bdc3429876aa9f3ee908eeca3931b42f0f8f11387d0e8b4108c8d8f2ceadcd55f5b8eedd683b1f59079f1c72322e6cd8b3e8db2387b7bf4074c519b2abcdedab8f9a0c37af1c78b3e3d3dd1a706a4d7c79f268088c513a63c54a334c953a9a93d3668356c3db4a783f3da25f3bae51a8e431a557f710c18745f1cf2cddc26fbcd1b62ca19a9d328fb7a99d82335dbb57695912a9546e9d3f86c4ed72af53c9ba169f318b5b2d65f9ac8223dcdeb1a556f2bd6f2a1a0094d3081091f6002c42128cb777737125d3674bb24430a34ca7cb497929cd0c91c88329f93a37ddacf58b197a48b9cf1ed358f467d9d13443fb700573be2507f33518d4a3ab36b308d82aa06822dd475a506d211cfe22247af6320fde48e1462a2d33537ba8af44919faddf922cf03aebaed17e790f2caeece165924876049d3a3518a6884d07d924645c2b225771aa581fab273cbcba25ed93cee4a172f95c02162fee2cbbdcf71f341922e5d5e485e03013590494ea763bbcbfc664c9e3162d07d33e67dba51a116b27d42ef93304388b40eda70a0640943424137c48c3bdf8914d2a8295b30356fd2eabee992e775f4898879fb271b26cf4b1a1d3ee08ac301e5a4a492eb9846c9cc999eee933231df7431c57f73f34c317a4264bf00a2cccfc0de20cff70e7203a18380244c9e73be85344ccc68b3116396c75fc4c9f35246cef4cafc7ccf68835b46aae2112d135fbef8d255bee006ea2021a96aa15209a6877c4fafe890b20557f7f312097015778c68cc76cbd18ffd18a5b45846c4a89c756c189bbec0d454c912a657ac2743de6341e5f666a3e69c39460f856a1d261c92fe24a318e5a206dd77caa746c5fedcba451319cc462d3de83282275dbee0ec125c75beae43afc05fdfe1877bea03fd8494ee206ff40a2c8118a2dc90afc339e79cf3f1e2f959e8d989bf1936a037a346d915d6fb99efafab099897fcc156be5ebb1f4af2ba8468d47d295ff633f776add4ae9db0e712c30b4d1635924ff74120ed11902a2b32a2a274284b9ed91416ff6098fe9964a832230559108029524563c5e42e6e72782d76e01c9e40a3247788e939ec033c85dfb6cbed870e64f8d8a8ed3bc808f4ca76ee06ccd0f3a00b1cc3079dc9b7b7780671c85a6093732d4c4cbe43732d4c3c13f458361a2793f7c16b67e16999e2afcb14cbcd86d474c8147f33b2f6a93da38738c60cba8f5e1392513993324ad8127a1f07f2e6c12c21aa513d6306a4a7747b6f36b67f9b8d9182eeb3599ae04df0d82fdc3ce8b1a0507972988899e18bb079f4de6c504f46a32280c24adef715de7715de5722f1bed28897c213f148de67b7d373e73cd3e6a180e466038eb0f6cc447fbdfb089ba8073f4467e8ede0404b1ea2f30e0eb4e411fd080f11b767dc21e6ba256fd7b5ef2037fc71a02513613f7a7b883fea097192b18925c3df0372cd5365768473461e265103a29f285986562fbd8f4696d0bbb71845dfa80c789fb55d572afd2854760c778d12e2bb98029f1094520534caa7ab41f4e565344aa21a4696265992647995199bfc148246eeff0bdd773a792752526b3b03bd12ef4dfd0f257fe3bdedb1a014d0286a1b40bdaf5fe10fee728d8d75c8f574b331e392b12f785df2334b152d434903e69c5020505946a36aadb5d67aaa59d248132480c9f61bc9548846413929d028d897a7b7b15133c1073c4029a594d29839e79c734a29a59c734e39e79c27b97473ce39e79c281892033273b85578245805cfc0341ed0b2e503b4f3a3932223057530cbd39f30544588527f7bf0b033d4430807e847a6f751ffa1645affdd9069fd5723d30ca6eaf52c7fb1a6075c5d31d6441b920a6dcbf0d2ecf0590f5dbffaec71eb6106e5e9651577f9c2a3390353d947f9c25c11972fbcf96cf9c29a8d962f9c49982c5f1813c2f285afce856dd3d87ce13aa4e60b5f3ff407cd179e91074c65320b7103a6b2d77c0253d9b5b3e140993353a75153a651f45db2d9b058c342b7423a7df4116de0aa3b7dd4015c6da79f4a802a592481c0955664cd9898424f796821cf172b647a08a190694ca66f656ada58fab57ebe7ece5a27099b668747d8629f9f3358a751df9ca13c745e7068cb8fa24ce750a635b1e7f6f6df14624165d206c43cd66d38e631af086a324d4fb61a157da2cf679aa6e9f968d467330cf39c69d5024ea78e02fe4d9c1cffe68f04fc9b3e5440d00578b106a214f0e7289512d0754078f45986634d471f55e8be5863cab2dbcfeca8cc65dc10d877582067f3daa797552d236266ec5326d66093fd87ba8ed9d8936b76c41c337b163168f05d3999d25f39974eafb4a001e833ed235b8e144064d3db11b3b5194bf65adf1b10d92b86778c869817e84096ad13116d7420d7f9e94d688b62228b964e9d5d41a62a2d4a694febea21f35426ce6c85d133e1c8f5ba3d373a82200a9e23cc18a540696ca13402750b0d4da10cf39333a9919270c597e8d22bb4bb4b5eb71e80297015410053f3b1e5eb22207bca39634be4d1f3b1a557248d34d605c74ce9a4f49a238d0aa25171a31ea4a452a384e8161aed1580809e88187747d9a6dd48297d8fe2e8629a63b669948e6e0efa9ef4c3b63efdb7712779c3a8dcb8b30c035c6137bb40d9bdf7de7b712cdb3bcbae7681306efbcd46a3edde7befbddbb6dd7befbdf7de7befbddbddee766dbbf772f6de7befe5fe69e746dc27dd726c1f6512e3f0cc1bb6b99fe96874bb8ec330dfd1b66ddb369a9804ed2fdc8db24d236131f6d0b0d0e8c59f468d348dbb58660ca374e3b8519659b882abd1a8d3b891bce67ddab9d32d87fcc82b22621827efbda37bbbcd66dd66b32dbb34ec17c34e3a0d6b0cc32446b4b15cd876e5eb8663bb1c65d2c33a6ea3925e01c3b0edde77f75eec1ebb86611877b7f7f6a6dc68f41b333cbf617a52878d44acf6cc6a1c877dc3360cdb308cc3b27fdcb3eddce862d7d331dab02e85482adbb4c865a57b2fc65dfec2d58df7e47ebbdbe5582e6f7a77748db4e5d8de2cdbb66923ce04ae324f0770b582dd7befbd54d22b6cdb866ddc766ebb431887cd48b348a78976f195336cbbdbb6dd6ddb7e81b06cc3300c1bdd7fdcefe8725e0b0d8bd9a6dd8f2ebe4097cb360ee3380ee338ee198771188661d868c46d18b6ddde7befbdf7de7befbdf75eed8e7ebff5b079f1878898475ef4b978dbae806d77dbb02b92e2f55c964224dbb073d81dddcbcd6b983ee60c131133cb33fdc53019af0e23c418e36c494aa29b3f44394bdd30f6e4a82794c1934fd2e4faec7479bab4ef793a7c569234524a29a594524a29a594524a29a59452ce39012135dae41adbc4a2e4a5158f3ff58a0a928fa41079bc4aaf901e3fa357bac7d3d0a157b895edf13cf48af6f81b3fc407d12bf4f10d68153e36e594f326534a390486ef2936b9b5cff0750d67dfb0760e6f6fdca2c3a3937077114c7a0a2cf2119ce22478e42a30c95760152fe1152f3161163fede040cb8e9886dcf14646b6f8c905072637e206c02725111f7feae9f59d045cb9a8b5de44a0c6311d76e3982eb1cdb01bc7d438a6c36e1cd3ffa1b2c9fb2291e95fe3dcfb5019c3cf4e386617a6d32dc7e9f2f3f413cbe9f23b3486bd85099631328d523fe38d544d00a2345c592195a8c22162369d309b4fd886bc09b70e0bdcad9212ee15b87d7e8200050d8d701375116ecda5e58b849118cb182c671aa773a8e0d338b9be7fdce890ebe1dbdadf6b4fd6daf7a78685ee8b3e9fa5828d30877d479f1804ae208bf56c491ea592254d8a22b4e4fa8c46ab6c9144a92f6af1c8f5cae47a69e0c9772aa2e32e23512abd226b83ab28638c9d65ddfdccc6ecfa6054a8334231872975b5ebfb46a2fe52c9f6cd4daef6ab33b9beeaf4332c9bf55a56c4ccdb778cf2f6ceb24cc65e43f61ab2345a750d354abd6d1bdb47d83ec2f611b68fb0a750d54740947afb1936b399c59e1dcbacb55ac3d88ee91eb57f68ffb06baf329b966d39b26fdddddddddddddddd9da38f6916d3ac519b1f323c3a9a46d5c7ed72e647aea7365bf6c69f466d7e1073ccb7fc0d5c4196f9a64d33197ba02c8219c7c1b4728c0c9b8099c9d9489bd970a0e411ad660dc3ac9d9b52721aa63b80a98ac5cb6be2ee145fe7f07c06658ed16619777bfab6fae6f4bde95bd3b7a6bb27dd2ea5bc2677c43cc2def5b727dbde9e6591c7bc22621e791ff6ec75cb21bf7945c41fd44686c70b4e8c99ee5aefbbcf3c694fb65f9a94346d69689aa6691aa59e4201854b4811aaa84d7f86fcc857ca529ec374075005a11128905c4f9b90eb14723d3cb921048f1f3f71088d4e0bc1d8f8ccc054e64273b1e140c9738624d3f567de70c7947ec4136358ab29fddb753c7decb9369628e77d56caaeeb22121214c4064f40f1c428c6592a8d74256dca208327dfedc948745308a6285c411bc41a98420f55e83f1a39a856777dd79c1c21b4fe27d37a094056058a404fe4fa08c0d57c3d0468fd03e08ac6eb1d0059550b5a9842ae6f000320ab72810b50c8f541c0957dfd0220ab7ac10b4dc8f50a80abfb3a34a4845cff035c61afc70159358a2880e4fa1b70c5bdde0664553290a1895ccf035ca9787d86ac6a0691d70f00b2ea902105c8f53ac0558ad7d780ac9a869a0656aea7015723afcf01b2ec073e55ae9f01572b5e7f0364d9107c02c8f52a70c5e22525d7a720cbbe900000b91e05b22c093e1b723d0cc8b226802914897a0c5916091970657a3dea04ae4c5e7fb86a610259368605f525906559f0ad605917c0144a822c0b83135cb9c0a5d7777045b24196a59941fd852c3b03ab44552242965d02a6d0d76b7075bdd6783d84ab1f5eeb6bd7ae5d6b8d35f223c97f9d498770db0ee1687408bbee10fe8770b53a842cd621d44187433880011c420210e01016a0008770871d0e61ce87d0000638c0213c000f8790071b87d0c621eca1071f6ee04000cb21f4e110de3884380e21020e21cb331ab758fbc5f618bee7b88b60152bf0c859e015a69be0d35b6093bbc02d8eb18b1f708db3e0cf2e91e91180e3860f3dd8e0e10006c83b14800003d081b57a37da485d732a504e4e0e50077531cbc71a5a24517a766ca6514a297d0c1f578e89a3d4c49df32851a32876e55c39d7855d17765dd7458b68cb2b2ab5684b8281b628050255d7108d82762153fa049d026d42bc2ddaa2ad1513467b7ae512d22814952a2965449192e87cb4aad248941bc0157c5c02b22a4e4ca1446380abed25a751e707e99546aa6e4bc589ac4bd342d342692ace3524adf75d7ad7501d7ace9573e5781f95d17ec036b3e0cf2a91e911803f3b834c8f037f9626d3dfc09f8541a6f7017fd60599be07fc5916647a1bf8b331999e07fc592432fd01b001f0674990e933feec4ba6df017f360499be00f8b31fc8f404c05f4d43a61f00feea904caf03feaa19323d0b7f950c997e85bf1a45a63ffeea50a6eff057bdf0552e64fa0d7f550b5f0dca15c8079bfb1e1e22e66f0a7dd30b99fe047f330c991e06fee618327d0df89b66c8f436e06fa221d3a3f0473d90e951f0474390e963e08f1e91e901803f5a824c2f037f14894c9fc21f5541a60f00fe680b327d0afe280c32bd0afe681299fe06fc511a647a1cf04797c8f402c01fc541a69f813faa834c9f03fe280f32fd0afee80f32bd0a7f34081f2542a6aff8a347c854f5995535fe4d7a04c8b23e7c34a10357930890656d6c8c90035734820059f6c78f21e0c095f603c8b2373740c8d4b22c0e8e0ff2d50164d99c9c1d648ae100b2ac8e4e0e7cc015b70464d956eb063570a58206906577766ad003ae4492802cdbd333031ab84a0103c8b23e3e32c8f47406ae464e4f5b0059f627a6d0d35319b85aa102c8b24190802c0b5402c8b241474096158a812b16a7a73ce0ca747a4a4f5fe0cae441580094821c74022112647a1a02c8b242a80720cb0ea101b22c514c310364d9a298e202572d4e5d942e44044314044469c8456780ab1aa79f5d180364d921318512c115098bb6620af502645d3931857e6e01b2624d4ca19f65c8b415450603fe4b189f04977e61921a38fbc425a781a7e7bd7871720203460d35d860030a858212230600002043462a158000a4a4a8a8dc80830066e490c3df02bbb889c94d2b308b8fe0154f81472e82535c05163987551cc3dc2fc66ef1ad81fb34b0f6db225513a8b725533a84fed278dea5f1f1e2850f9b93139b1f3060fcb8a9a1861b1c1b6cc0c941a172745050745a3162b4760000809d1e19327a7c52299f9f0004e027484a0a90ca558032bd0dba4108072102189a717a4b94c36f08c24148009f3194c38932bd2d5a519d9eb668d4387dac5929529dc65b359ed335f047733d0dacc22b3807fc91e4fa195800f833c9f538e00fe7fa1bf00723d7abe00f25d7a7e02f95eb0380bf1b727d0a7f39e47a191800f81b40ae8f813f48845c8f823f78845c8fc21f4442aeb7017f3009b9be06fcc125e47a18f8834ec8f527f88351c8f52ff007a990eb3dfcc12be45a2709d660aabe843b98aac77804a6ea8f4b3055ef029f60aabe053e4cd59be01398aa3f61144cd59bb00c98aa67815560aa7e059e0153f52398064cd5a7c03ac054bd08de01a6ea55601e6ec0543d867f80a9201a0053f5133f00a6ea4b700460aa4212125dbf371c327320db67508b5106a8e3524947ea22a9d3283a04d471a9a4234d1988420fe70ba0aacf0051e85fbc309952a4f8ba287f534635851a859e4697536afdca6914bda97e8879d557ef1212c347f7d9a25c0457dca4899944ad9224236f51986e51976b9e5475f53edb75a5d27fe5984027a3326cc2043339830da1f62924e90fca83fb9746a1e791e957789fa557e1f92c0151e851a8bf54fad181fd4147841212327d910f98a23406c181cbd0901395847441f43248c28f500a42c7834ca9e6c51a5216ba915f1b0e942c72ba4389686be29129d435937e0a994370859d7e86a173d037805d135976483751d443681f646ae96f4a3024a764d844095ef2bc766302981208c9271936518216f6897db62a4c5186197b36d269734e20b44f4dca39e568db11b38a576cfac5a6f97bda33277d0d80d8691510de0628d18c9098a04c616810fa15ffae6f20b6dc6fe01ebb47e5d2af5bf2620f3864051e82bb181c22e9efc59db66a3db7b1cc395f7598585059e4224ff122a9b26d874456ac32d70fc9f41a9e2abc4f8813119843b9110da24a253432c4542e0e8f72e430c7e18dc31a87330e631cbe1cb61cae1cbe7e288727a7c2bb119966a8844ada2aab9a640acd800000000800f316000030100884c321a12c8aa3604df50114800e86a24a52421bcb83799043210501318800000000001100001080992a0028721f0ddad340dd8b034da1b44fc7a2f025ead0514fd0367790f870e29fa4ccc25a844ffea03653af16381adea9dbf2e2fc5d15111db1f1d58bcd7acb75f5c209f14f8aba590c726a7176265cf5ce8d2d3be26f8f93eb38dcb33b68ec6c9bdf634f06b2a3ba7ee10f80c6e94d59977438afb8a7084c477effa080cb68ba65212ea843bd822e839b6fa51cce71e27cea030fe701c5413386f364725c1227bf0a47d3e328218ebe114edfe2901d4ef51c9c03180fc7f588c32ac57b6300a0d52e5ab98116a568b8062d1cd1f2135ac8436b37f02529ffc19fccb0605faa3029edb64c2f85d5c311fe43d4b3c4e869c32a2d4125d5e6dd26b3c92ad3b406e9c1a27c913c3ae4f0186f9084bd71d44d0a952c74e3086f20017b43712520f1c2926c485f1d0e0decc3d43383353ec749e25b18e3b0094eb0a0ee2254ffc3c56d6491a726cfe5ecffa4b46fdad84497761e005f70fc6028f305b2cec4bf25f7e69bf8e0e0ce17d919907a0e350be7be06566487889a7c41209c0100c1e3aa300a0bc8da8a0fafc29b1197f317ad033b4ae6899cf65c0a2064771fe2f1c4cc1674538bbea595a18e42361b963f2296131c51b029589b044f9a6fe6ced4b2e1c1b4dca086dd40bd3779155227450c7719b50f471dc77c8e92a4c935a1be98bb7935bb41866b91c0b9db411b8599b6f669b9071a997139d64db8b30b1436ce702c2def75819029c74fc1b68089425f8d294273b07604ca70d484ad18fa2c125152f340211bfc44baa015141a108170d76770bccf07e8c3037805fc9f39f6702bf82c6d65e9a8da9767c61779ec855ee7251af1b27e97cdf9aca7ef94a0898520006b25860000e4df699f3b214ecd075610cc0a0f366289072b66653439847ca2b5c2f999acc0f0d3bae5245b15715570328f31c87a87365b4fb9825d98e1d674a432310c500b5ad58dc65ac0b7ea26fa9e852d513dfd1f9e1963881c105070226254ef22c6a3011e74c4f2921cecbe8a4d5019045d35b0a0a1561dd6e329b0bccf13100e9b078c01c79905b054884c28de359bbf0e0f386462f301f66d1e2604f9a1077a1089496b40b0fff244e7d302179c10e0bbca02cafe4e5900af5dc00bf0a0907015298d33250d31b27868a981d280466e7ac1f78dba108c56b8157435b8d6e3a94c7e3c55992ad7508471c141e3296a49265511862a9fca8c218dd5a6ee7ccadf9efb657b081cd4a1079c87930275923134d09c8090e3ff195405781fc467bf5be883ebde4a0e6dc5505124b23c0439ba18d171f43a4345b439f0b9bcc1641aea2e49d1268696a6e4b62edbe00ec4728f86cf515f5681cb2e368973c04ed7025d29ed072cbd0b1bd302e5422a34844ff509f7db4ef6b370d835814c28e5b24f8582c119a52ee126d7e6577a73c9ade53538d361a76913fa5fa3e4daf9e2423dfe60ec7d6f14f67d3520c0343989fb12bb48eceb154a6fed0f1850ada3fea15378f469122ad399d72c970e81ccb8484e19fc042aa0b9aca72ea1c9c7d1efb260fbef5c4e493483c3094bab1203fab645811b0192c42f9c93e8cdca289f0276f98c7516d5b63bdcd88e66f8977e15facdd023bc3f8db6cd37fa44a05b90949bd66b349c3ecfbf881187a62cc35f5326b1d69704ef07938c08369c3cb0ff0159c0f5ef2400f6cfe62c637722d0d411f5c6586e350e601cc1a14e9d43d14af8dff92421bfb05a9a3dc28564f78b4f128922b1fcd1df52724082d3a58404732065d2fb25adc1e6038363024cb8b731580d8ed0931e25e2fd589d0582fd97634f1992eabfc9ec6dda4a71373cc4581c5f91294b4708d8c43c0dd1b7e091117b89ec6c251a0a49bf8b5fc59a48fc246aa1d0b733145360873c84104a140ab4802de7ce9fdddf62d644c0e183c207e502e501e706e183c007e780e701e707e382e106e7800337037ed4deab0d02681041f473ffdd63d3b1ac41f26043e1816dbcb06c339b6cee22211bc3e5d6ac33dfd6f6316bf14add8673abea65d3fa3c870fb7200d58f336f414a68a0299cced20abfe208b7226b676f1b67600eb76224bfd205b75268b7661bb7631fb72215b52457e574bbcc46018edb0adf4726b8ff4ec2f64ed18b3f6502bf38fcc267068229294e254a989fd08096b8f4b81e1d3400233e44152057df0f3e343dcdca3e7b583de3cb34302f28e7415e4b3665b8b3eb40bd4573367456103c5dccc0351f59dc657603684af60d0b5a689629fcfe74e2af5b1c2a327b780726035fd9f6da45a2997657e7f87da263c6cfece0d33e050e57683ad45edb0b18b59a00cd68eafe6088a1288ef45567e8f9aa561cefa2af5735a154f076ae98392b0978b8f98b98dc0d84509871b361a9c033d75be5244b480b0f9a0fa41d106698bc628faa1d3e1ea028e77822b59b1209f13bdbb7ebca25f0719249a863c93efd8377bba1782689d3102eb489820f0a2d702251d2a5019b044065dfdbd0aa217997dc0ed26c51c36bdf1b9d67932974ca4d19d0d2173e7b6de8541b106c62e71c0f60f830913e22509f5e9e73ae236d009ca2dee47fe64bcad3868b915a375bdd895b4038355aa9616218cd25807f3ed137a510367abebf8ae3d37e17f869ed90c8e501c9bd7e14a32e4d740f7a1693946e71a453dba239d213f75e2cd1245f33631e1528d6c662d1c20e312564c8e2b62e79791b6d7611b7419f339b89a6f1451160028e07b3a08c0e5562035947d0e6e9091830ae844083f65c65ff4abc6db30a1aa7efe037d278916b36af9052c98763b1d9316cc954a97225999d7853646dc217fe96102055a85281b08fcbf06b14d409132e42bf51ac5632df4b0dab9892bc24b2b862baae164977212f20fc96dee00558aafc40a6100439838cf0a6430edd0e2a061bb68f93c52c42e360477b45989bc91bc5bc220b904786ac90bcf105478de4616497a00fd46c220fbe46be868cecea38a3476eb34078f840217869a9dfe69b8b4de59f503d097f7d5397a8276f0a9a3fc02bf1b727679c271656e9c919d35f7f92420ba49f3ed154f3c37f35a90881d8e44646e00eb95d516d805335fcdfaa78ce3da65acea96e4f5e9c009dd10c641e8f8474bd69c2baf37f4cd8012b990d9f6fc27bc9e2e6d63b250a8ee8575f86d652a50c9d80e5c4d1b3869bdcd01552cab282d908a165e933f61acbc7aa4cf264ec245636e675d2b480a789d8226722474cb03169764c2555673ad1a134878be3d61de0b32c985c94595b1ced6653af2014f078b4dc40874e3ca42fed4e31f71c7d2d445dd8d496dd7868fd952ac7a7d50644b0ee5045a9f12399f7c92020dcef4cc3754d7bebd437ac5a51455a8694fef411fcb47c68af048015f37e2a0f11af702d5c0c3d16aba10b1abc87d63c4c2b4031063b7e09cf33ba382a3049420c64f2e03a22874cd98bd67d06bcdcc05d0327078cf0efeb729c4aaa2a6982f724a02c2891853389bf81d2d6fbba60eccea32c7cf98348ec8a4fd0d326bbe8d7bb17e0d9ca03e0ddd4a7fc6fed8be8cff3d3287b4508f30bcb74d2bee178774f19a12b69b2c0bef4641ce1af0ce28b865e194046823efb75c1d92d39ad2cc7ea74144bce6a75863e06f2cb088b61699bdf42b7e07b0f91a988ced0438ea70632cac468314b7f9f39d0e04ef7dd6b904e0db22d5cfed7832bfab527c4569af36877280110594a9d8a5b310847fe98a053bfa45ba4e73e3374739f497dd20d3cbaf67ad615ee64adec0e0cf9eefdbcb5c3bb015e99e3ccce638d13eb0b8dba61ca2782935465ec8800a2e113ce55b511e0127c5296dfd29b1420fae002af96c1d048c4ab815078b2ed1293a693898f40895e3c74b47b7a29b3aefec739972536e75622ab924a2b50a5b5345b85adcd3c400cbb9ca8505c47e5f4efc0ce3079fb713ff85d9ddaa7f9fc361255f732f55a2797e0880dbcb94573e08bdea5c45dd435ae1ad053e285e90ef85224945b89c592488a329f09b2e1d969565b8de218cea410c16e67c396eeabb74e48557183cbeb48df3550d8e2130b152f3f8e1dcc28c17ad74811cc59e0a16dc1f811754c72636b03c1b18fcc7cfd73fd90ca346fbcda7e630890b2959a650467c2cabd1949229e9b205a8773c1f2b88bacddca7630dc264c1ed842a68f175c3aba7c172495ca2c1ab021fc5c7c023b071f036b9f446a855b3add73c4d8830f72998ad49030a46b7efe2f1d67b38bc6e13c8fda0b9a36e2f93ff24b985e69878dcc8a9999b8a41cbc0953c6875754deb196810731b973e033a5ac86c8183ac4e52036e0591bac4cc5376823e294a58db71fc6c1f5ad3e2e29ababb61bd6094f73f7237ec7bf9cb883495e89185a253419c5a309955fdabb2b8f3d568c252916ce0a545e0bf0d8ed6f62ef46ad6add96b73e17ac9517d7e25fa7d3a4da15d48e79d5bc53614ecb03bd8ca71980cb84ddda42a6c875ecbf9248f0a1c40c790d2b7918af45ffe31e3a8c9aefa38358bdf83452c536416b782ad0d37667f6b406b58c9d20c695f446a25b7a8d33d614c69f732fde4c040c42730ae31e542732c1073445e8f9727ee7fe67c5e29a86746a056740d7b090c19755abfdb3869842ccf44bc1205a427b37bcb24a64ec16da6d6c454c0e2b5538a62bb4eb3cc2d7e17ed3022ef8fd88c02e3cc33928c5a074611cf5943bfeb1a2644d86821f829ce3b4f36e99c3ea1657c00a84208fb346d781f16fcd8baf5c80cc3d1fd3cff34551e9875b33ef7bef18dc73621361cce757cd13904325d6e35dfc92d61a82d45f8bf969c11d0293d3a5d73d2f01007e5e387e2d61d4078c1d3d1f25f03cd27372baa554afea878ab69d61e539ffba609caa5b63d0e34ea99ea4309a790865d1368162e055206e65f0575a376f80e095bebe41783526897664094fdb82fd796e34ae7705644853e4aece53623b475c7b7d097df034fc483714fe8ac6d8896a98169b766fb0b4ab2be59fea40afee9c5928e3e7f9a79c55050a0e4eadfd1542a3b31e9b8466f6f492a2b6191aa929cd8264d91fe9fab7aee041f1ae24b16f6e38b9bc42110b8fa8a84720fe14ee7ca4a81dc75bd5f9d8bddbb6f0e5b617c64b1829682785fa14e351a09c77e99a42de24feeb494672be26c60afe174f7e8583897926db95aef23512b6d43dd6f941942ab078dfde89fd004d4c6c804371a9cfcd5f59423c71934eeb73fda2abb8b6654709f9cece3e00613775b9053247407045cff96516511e35f9892a1c1c059901ec2be900d352101c8b9740de76e321262b15237c4c1584f5e66c70031272e1b1508c77bc411a083a5b216224cede88c36e30c1488379d2bba9ed211dad06be0eb29aea2c2a945b64dfecb1c9cb21a382ffa5454fb3dd40c85d73d5c3d2ce873f7ef4235a1375c58f3f680adfe17397b6ee2203925cb69fbb79b75111c22eb789503623b2780f35ee5843d703844e446e3680bd833604b7da6cec13f3ecbcebfe3de9d44ac5a2c39e56189c2c90f22846042a2c7e29da409435869db8f21bdfcfe8c43f4b711ef170286b0f33d4de17a654e5e48c01e88a041810ca29f819ef85385da8701b7c0e24a6d3b068364f18c8e65d447e9e3501314c9e87b6e4c563ace26bc5f7d36d1a21cadf5cbab0ddecdd7b2ee288921d755f75dd6a97d30219a41c9da23dcbaa699700d941d35f233ac29c00bcd7478da551c3604e78990daffdb66e5097bb2fc3deb91eae6dd6e8c36342c15b54f42eb6be82f27ae09c766c9c251f957ee052009506bbc3dab880d845b358e5fce4a7b5c97a336c6350403e963c20eae0ba2c6e6d85d2d1098d78e2e98b02227e1b8ac4a7800382aa86118640425004445f7587f84614973aca6e9ffb63112909157b917ff3d9349660b73caddc85bf1a598da1654fa053a76d6f8fff0d7bd5b2cac0f622a1971ffa55a27d52a61a5633f75bb7d0e761b3cf0bf5ec8b57750406d0d6260c1b2a81050faff19b952309e38f1bfa20cbc7972c85c2d05b820cdc3c641c571a00f5cbe640f17f7f7fcefabc6cee014f0cd83b74d8aa069c6a7e2c09f017229dad348f91e7d60e18b017a2a123950d6b2e517ef633b5033f67ffc71bf7bb0742846c4f84fc009f44d5d1697595e46afa124a02ebed17042217e0e21cb3c92003d172d5154f23eaf4e99c212894aae70693bfded17a641e37b6f6010602f4712107dee533349e084c6242edeed19d46111045e2107c85a105011b93e6a04879ed2d8f2bdb3dca5f1ba72ae4110b8fc384fe406caafc17bb129a3e4f2b225a632360854a712bde9e4f879c7d72fd2100d01a2d0ed48e56f43cf2b194e02e542c6aabd803104ea33a016ef2443201e9a791f9d02536646a3b44cb0d60ec05969aad608632bb1b42028e02d741a21d561e9757f8352fb039a181a826491505750e0bff8141a5aca37006d01bc0d9768a2019aaea423953665ad0ce40627d7ad54c314ca287a3546577c37c06afc1420275a1dde4d345b36230e010d4c8af44e4590016d1b6c9716fa4af5a0c3220c24443220f77123820d78ac96309b52f24f3e54abc22e396cd6131075b4329eecf22a82c898153f8ca98026cc403801085e577f9f27ae1ca400b34d9b9aa82f884f4038610b5ddf87246e0f6462f81ca9334c65450ac90f1fc43e0bd60d3169482dd8b2d03c57f912502686f211c0e3ae0a44c196ff0526c5f395692b46097409c08f92cbb08a54932660efcae33b9069921bb3434dafb1a940a5b1413c5bc843763609905beba3823ffff06e7fc60e062b638e83356106b062b2254009b96c328d9e4eb5b3b75b812a9dad8c8884f7ff64c8ece3d183ac1406ea7da4521758da40812e6fcf683ade864a23120b2e0143799861c4c83a64abbaf35b58d4df6f489207d52d580ff0f50e4500c1e3ee20991cc772d60cdc05b39547ca05aa85b1398dc65783fda6ea3571883ab11860f86535d26bfd548f5e1b7965cfb2b84bef31b33b7d27c1baf315a499f0bb141827102fd3af03853f69158024a173976bfe972a6e32c0ee3dd68a0b3c6c5762ac8aea1cdd295cb65825343b30dd5554caeac19d1b828da4062dc0b9db588049c78085a3ec72f111d953d8012a9e49eadd5e26c7a264f538acefc092d1eac22c32c29b3accb94670504aec8d746033a70c58585a365d899804257c5b4c73b38c3644e09b3540b3bddd599fef678a42e451314bae4f9c15bb22cfd35f7292c320efc9ebc1df34e18361203090fe8392e5839bc960ced9cdf6f9089c90cd338862e51e2caaa1612fcfb57e7ddcc24e4562f48355cecd3fbef81d454b4b9932853cf45947427fd968e4b38547bd6497c73fa3518c7c90c9d72427ca97ae7549cb9c48e4c1901f4ca21a278558abc1d21392e57dfd091311c6be345cf4dd3012b58cbdd9060188f515275e2ed5a279dd5ece12c987d9f7f3376ae2e685e100da49852761ee7b8297513df11b0f8dbc7bf210f3ff4b9f54b9658e652400dfbcd852554106de520aa64628d913d6a401f52d8828f60db8982bc7926f74449f77fa2fae6fa30322d710704ec92f16b21718ca17b6f4647eaca38683ab4c17d4aec18c4cd43b1b6f93938e055bdcd59ee74d259fb0c87d83c913c9ad35f195ab396a4f48e1b2a1a46a15e727ac3f4490bd9747449ee10be6aa9bca5bb0fa96791347bbc82c4cc3c59e008cf47ad4f133aec197dcea48e13a413804cf5d63908d5d1e4b6e93470e049a64e007f9cf53bcc9484afe1f9ce53cacfba88ae41413bb3201944ac2ba5684531996a9c68f284faa8a702a7386c60272a00b5b2894137723e79046023e3b63df299ec6de655a309ccb0966d7f59c2841e6d635959f2b299c3b6f102e14837c58fe1870baa01c353b9b087b6ee7f61c37d2257096c0ca327d8e0a8b0a629763c5cba9424789e3b12cbb65d2a6bac2e4f48067163b052a3296d54ed17c76a7797382df5d3e7854b1058c537e51b159a9cd7198f31ff036ae872d77f60749d70bb9ce2f5d2af818a4b7805c0d2a165bab9c500ee61201796daf84a504e97c00a57e43d290a3b3331548fb833eab6d44f5c8176e84d57fcb5a55740ad18590759386ab0e043194e515e479fb2bdfe60cf3984e5d337776c049d513e52f7399d745663c140a1e2053631d92500e329ebe3bfa1d0fbde5a16a3c40661b444310beb2636d444c59416852319bd22dfd1c3d8ee7f844f5815421a3ab0f5af0023abab31d0478520f60595f82312292286e3c9ccab8c20d37e859eabd08f484fc29d08537ada22828967d89f158b84848963dab0e7e9d1b2bcbdea58521b4cde55bf12a41c8a69ef5f735573a4119ef140e612a6145a92e5fa50b6cc64772955ad91b1123c1e5bbc45dfc0552bd493a8b1097b45b1a1863342114ca0660031c2b77b538428823af04d2cd6e648699b739dcc03b9544921bae6bd443cc9f2c1b1211c5284ef82f30905ed5fb0b66d9cb15b6e207f499712319f4f9dae896b3ba28dee605b02e9997837601b951e2eec6d2e9df430d9efe17c6e6a141a784a9fe000a813622113019a69764f750a201ffcc65decd4fbbcba30de2cf1adf975de59edd3efb808da61645a541928d2f8e8a40e91eaaf8da748e0cfed1b7586b366316734e045e828caf415cc75926654406549e3154723b9c82f9b866671294d8dc010d1369a90a0c8c8d692ef2278ccb0ff184ca9f938ff4e1887325399c6b3bf476f8768af79ac8214967052fb80a4918fcf5b3d1acddb480716932a02085175244ae44be9a9e7f05a064f5c0aa47925b88448dba745dae6145529acc3184ee718919fe87f15ae9c306f5b411484d08e212dd83511fbf82de3b6c435545a12c0a2e8cc1d64962f1b5340ccc428f7ef55027285f19ddf4607c1e611f7049d68168ac2b3e97360127d1f9cefe46a600760a620dc29a187ed0c60d5ec7d70425fac80cad693387174ac90e98429ea23f9a4aebfb22d6a2908f460543cd2b9a5a01ddf615c217d575dade221c3d2b8caf44fdf5257ba45d2f966936ad09b2905f0122a4d423dfd2f8f7ef6c2ef3dc59c7fe066cb168af032a68fe15d244a2bc2d90107fe1e4f5d3eb9ca4f1a54c6494dabd175dc01ee02379aff0433d77342066524557c1f96db264098d854f6889f87746bf24ea089ae5233a0cc5701965d6c05fb4668adab7d2649bcf848a4fe6e4b823ead6fee901fa636fea609ccd7c69b4c849ba16496744e375c6dfd4da90430dbaed08585eca71f35adda03908537616022cae08e08cb4e971f262ef45e6779f1d00d06d468f5725092ba91961a72a7164df79b0e83c46e699f50b10b17c191e8ada62b57d51720001bcecfaad5cad32138f39bd5d467d6673238e49d125df772cdc5f53a8e43c4be876870343f439bdf88e818bfb2269b1f2cf9e699285d7121bc83eddd7bfb2f55d1a256b8b5be22ccb4125c511cc9107a754ecb44068622329c81f339f522d87fe59dcdc1cc3c08c329c1105a369672793c76af856d4f93baaedd8abc7778adfa757590c8d2a5baf977a4a598e7f14b2d12bd1a959210092230d3a6d6191d9de0575ed0718475a3cf2c0c4c2b14f7daa9e07363a2f3357058c647a0fad6a3be7e4c204423c01132b6c021d7999b0c4f838ba8f547c74a81393a7136e3a813b034677a53bd480a0ae2b31381540eb9032a27b2b4b36429752dd492bdeb0aed01ea06d7b4e4c7c0afc352d46f7d6cfe87dd774cdc16ea53d1cbce00a5f38fc732864f7cf3408a87e1ab344213640ad855274680c50f6b627265bcb0ade2537996bcd764a402c330a2d60c3d5ace510930e7c0fc9f304ce23cd30f836de0b6d22bd18c43ce258598ec3fc3909501da700bf928e17aee59e13bd071e5151c8a887a03fd653b9985cbf24362cb03809ebf44fe79bedc948c9fc3b806550bc2ea405553cdee00b9e52fbd1a42b06a80bc66dd70642ab921e1466a28d649d156e7588c9b9fa8d80f13f778d2350ec08ff4db1784189709152956215d33e3101bfd44600244c1918a190d1d722f580729a0b8330075e18349b3d89ff62dca9aba9b3b54a376480c7aa8f7eb1d8aaea0d2ea6b522582bef87e842dcf72800de8a4a64677fa04ecdccef1ba08a1b220f60fa78a2395eb308559892f89ee07b3eba5a74662b369c50485247a6dce1515c9da9e4eda16ee9ccfa7ddd6347d5a6e7c992bcfb459953281ff5910283508305db385937e01d08cae6f77c04743bf929f1add2a8eda955d59bb6ac448456ae6585480a3595a0b34b232f4892dd911aafa485e775562b68e26c45a03bf2c7c9d4c61ee41afe5cd0759df569e36079f6850ad9defa6eb7290af3e43546acc1ee8a0c0de8c996563d94a098b20467cf4cda036d47989dd33a5af480c9b740fbbf7d4863c2739af526c5b7708886e3d9cc84dc27c3a976bc41ac0a685a92c8533075e512985ac6a38a06f1fde936cf9a0e470e251bfba3ce24d75435f24bb0a9947412d9b81366ae7832e4fa0080b6f480b3b30fb90a2d65e900800250331c7e731f1585811d3e2b5e212e67a643be361d33c2d630e03113b1dbd67b49aae36ea7a5233463eec7deec49be13f32b415c3005deb689492b37f8deb7df7ca4376a685499db1ff4b10795f99038de9d159b906a70bb8c9d6f23b438916efcb409e3538c14848df8429476c63c6953cb42b03b54b1ef2083c1cb1bf1c4a3bb33805519bf66e2ee3e5d76148b856232d375f67ba1fa84622c0627914ce629aa7cd6dd08320d9f77d87d4691e37d2b6f4b8b393dce36592b8f5f5d2b763c7828f337608d0b20802ce956686aa1d10464026d5f334762c1ddd1408713bbdbf4d4d44cd06696fc1802be4343fa7b9a9d4952f90ab639f9b9668064d4a75e9a7dde536b24e9977e30d1f4bad4bbe6861a7950f51a14b3fb004277ecd7c819382a7bf6fc4443107012124da6004eac69165ba1573a563e6eb73443d8a421b06d76464a7e37a06befa8d9a238c2b36449324ed83eacd47dd1d0e90b8ea3d8efdcae903001737f256f12819d5a3d9893fb48f9fc3f8905cd6c49b81ab102edc5b86ddf063312e3643e5d48f2d763015060381b88038dc6e8256ae4a2971dc2c8f9c61c44bffed08b79c2b83a032623fa695ba7e53858b9ea970f90183c24063cd4a332b76de957d457b1c73511f0e19ec86d2e90d14d3c06ce70b94d8c2377f6aa7f67d1db54a933a17a339207d4fb42f8826db9cfc703443a086fb9896856faab35397908a6a27bbccc93ecc9b896c2967134f1c6c8324ac807b28cd7d6c0f1dfe81ec3cd85944633bac8a6e50bc6d1368f2bb696de416d3b28f80a1438e0f70dec91d68e4e5d78007981e64485f7357395044536da443378484fdc68b39541d889ea4ac1ca10f6c41383eb513ece7c41a9fce2fb4562286e10904eba5a221cf30caaf75499eb624cdc9872bd7339432a25005a9f2c5ec6ecb1b0017ba605f27e113f3b7099cd7b3e515f3373f3d9f8bc889079ad8aa9108572703c33e68ca64281031e19c355d9fda15f36608f2e808467f4c949cbaa94f649e3bd924b44075335553fa4bab5234eb9483f123d6612e8f54b755680eb108f4a117a94fa62aadd25f5eea9ad65be36ab9a0e382b908d3a128e2b8ac66b1628e9ea0fc37c1ed5f20d05077877ca805f596b4281eb93f8ff5cbbbd6ca42ed4a181005dd4137e467c8fdace0d05372176dda2456a38909c5d668cd34e3a1d3cecb48ba257761e0196b4e0f7fe68d936b5bfb3c2da1ec6db7a5b078d5247a6d9ae5cdba2425666d824a8a6207f54e2eec6404e82a35025631babe60b611a49a2b480f74db76309bf18765c7ca2b195ca848f0b695005a84baf7b333ab93ede4f5ca1b54273075a34b21eb03052410867b7f9e5c0dc58f137097a7f78eef4feb3deb30ed3d33da038896ecc256d9db98bf9e9bd92e6e42d2fffa22541afdc2912b3d37a44869da23039a1ca34c4a776e0f287a040ac047f2f8e0da635a58cde9f6452f159f9712f9ed3f072fd05fe5842c041521eb9d0bedd93b5f2b0a614d0d37d31c64dbb8c324578b479e1dc2d980d2573275264ba53529fccc4982cd2843d18669e0af4fe840937b07d631ae9e12910c15ad07a6809041461ac3b8eec6aea6a97c2b8240b33c3c260885bae5c23799ac40f4c218b3de6e2f391c5cdb28d2670596433db4cd7fb77e4c4aba3fd921ed7b7892d719006b509be15a0941d35832c1863deb341168b202bbe3cdf2fd2fbf6211ff8fc5bcd57e03465d9090ce150fcb7bdd681aa58f701626376f3f953dd96099727dea0c1645e5b769a3621a76c25608913a8d4377916472764a5734bb974ec8fe9f427832f3c22ede077736ef7cea432ae555db1470c0c63ea99fa61609f9ee6dd6df598841e7b7ee6759ada37ebcd3db06c61976b3ad33ce7992c756ea537bd88a7061b96400c1df8c78a077356793128675bfb4da91ade2cb165088ae5dd8cb0b583940705e82f3b814117fde2f9d6bd8bca540c8d19c103c320be47b61cb2e7f469069429a7868f0170088cb02ba5a705baa48da41c59ea365ae553c953a78ac3ef90711803d3c07b86e3a2ad1c3e5d5e5ff804f5c309b006053c3cde1aa0109506da8ad89fece8bbb6ac7541c527be3c4063b93b02cc28f5cb5011c310d593aed630aee8945fec76a5355b54eb17a30787d7593e3c5708257d5c9a28ae208cbc798679fa3b898a706599a9272539537d4ec2956d07f14e2875c89e148deb76aae7bf996c4a7ca36fc2a15f75db7b5eb824a4852474e82800fe718a603e589d52ecbd26a4de2786413ce9ba5a736f7d98930cab5c070fe8c0dd1a768735c676e5d722bd6dd8e0eca5e1ca34c757a0139b500cbbb6bede7eefbf9521c52409659ada751bf9b45cb951323d82dbbf7022cb84a5f285769120cf93dc02463891ca08b950781d9b8a5c3a9064f3fd16b2f89abc119a830b410fe5c0511196fad1ad9ba58b794a098e2201ac5ac722580beea4d11136d61a20bde97af520547e1c1bc5ed0fdfa28f55f08615ebe3853275d0f2fca884aecb4c9b7429b4135ef7fefdb4f7a4d8cd53e408f7fffc0bc7c5d7c5f0a023b26eff781c6cc9e74228e68a51f31834135c735a2cd8486719130b52e4e01427d581aaad5c9802501b3af208d489f2d8f17d09d3e1ca7c2e82f89fc685c20f030cce422c24fc125caee52a049c8d8b0e1d32a41eaa578a9e308da544bc0e357ac6d3e445c835c50c20ee82dd22188df7eacd38c2afe4ee96c54a132a6a2a9b9421837f4514c33694f1c71292fcf5545e2aa32ebe852d86b72f0fe9026683143240332a48cb647177def00d975848db7513192a7ae4a171fffc4ed83661e2590ab075b80775ecb82d04c891257f08743daf975f9b65572ee6a152c870730c8b4d37d79f1575adeabf43da52c233f33a3295189ce419d6bc014c379865e9394948e398a49f68b72e8140c61ceee3199d9325429c6a95bc61c5ee062dc57c77a58e1079c4ee984eb1079cc70386ad76f3462e503e3c24409896dd6c4e3edb0b3e74cf7261543aee5df39b23ec79f828417ef9f9982a40c954bfd4b1c94ed5968801f10671635c02f6f2a031f1d1cf42deb716ca2a986303dae9e637dae8a00d28a657f97cfcc6fd50902d1fb95f830e9674910034220458805c28b56b9d99724872709468ad2843fcb9b780d07510d581d16fcb544002c0f3a819b514a269ff1fd4bbe7ef18ee89ae6bb2002bd7b49885aa9d879631fd2670dbde8fae5abe9aa4fc40b359a4b0e6f7939adcd2179a917bdbe38fe5826426eb447544f8679a23a26831de3e908db35b02270392400bd63d4a1fa7f9ad8e7be6eb6389877a57bb2dedde6df43d1c533768dd22940b3bb2873d0b742167a7e4fcb2759e9ccf96571f9faf89e1da2802137b637d881d2c9ee6fdcff08ad3200054e9c3d7339dfae1026f6ee5be02bcc16bc19dd61132e12032d7222de9859b0a30a977d24fe692e37f57592f33959c96ded10a4c606f4049725f0f5a15c08f5a9280088f193726fa63f16cc329c639d460da3653e6f961829cb383afc1f12c3d9d4a3913a02388a9ad13bbd0235ad32980a1b89e35a6df9eb83efc0c88e1b2a259267687553ea34d5ccf9b586ec6d961ce370565c1a712801c08c80e902ecdca21204641a4d00a76823a798d385d1c37f1f0d46080d99f3b873c569364005ef45e5611ec8bda38b083e9c026879564e374b76549b8937ab6c9737169895aa91f44d66f763e9260222b17001581c1dd08fbbb026624411d8a87899d5f3fce715e90054b6416e09f884ad8702d3a7f2f26c57daa1aa7e193c91922f215362d2e917f087b15d8a99c14228248a954019f855ffb3028e127721fd22fc53cf1a1401dcc31a8e0fc884643160dc2951f1ac16a8db7060f22a3b969c4a636459852afc3ea56b96af0bcd07220ad4b8b94ef48044b2396e22a4fa2720030b1371ebd563aae6bdbd9f5b6d3e8629b147bc02e749c6b12329b75638df59f41a2bb546ccc4436f9724001009131b3845658692577e5d18ea9cb4d0e962719fc9ff28f01bd228f0477557a5772d4c1eca848953eebb49fe0cda8f125e38c47da718a39315263fb6f542df46f8d2acce14866da4c28859396181b55ff9f42699a11b16d876d6963c5284d10bd42ed36412a3781ca2718a2b9d12111038730153a4f5c6afde536b47685fdbf89b14519ea0d94ae6626bde30004b11f251d6fcac38c56cab894c3777969d699102ca1c0861dad48ad46852015b898b80a36c568a9f7982c552ad69d117ab31c84b375380b9ca430984086639e5f281e5d35c8a0f135600082c66cc6a6185305529d9ab9a615e9d1a25126d20a448e850d269f1e414f1b319d2a820e13750f4ecaf80b5861dd54b0729a22901051ed2321ab44d92d6a1c02f058e2b0643df9445e69f88b7a5575a0ad1b44cd908ffd25be53c719cd248b4faa81110f601474f759886bb32635362defef5d104f6287e40e05fd234935d95c7e3d7592285afa7bef5effe58151578797cb55fbd61bef7edd2f8ce309af49ed7e77bf72393a165c21cd957bc2d9f8c99e6b809e8ffe8311240e0f32263f5a43bcf36a277f759ac9a326462348ed0c723badb9bc6788863e50a5b286acdadfc9ca634dd236fbadf2b05f9ca62e48385563c22a7d4978f3d4d9c8063e06da57deea44077db15a5c7372a68ed5c931eb111f6c37f4fda3b1eb324c33bcd5d7462cb3d4661c6161c9d4370db7082cc293e2d057ac2513e68e574eed30cefbfbbe9aae905f6c1f1a67382c959bcbaa85612efdc7aaff4276c6dcc177a15db69ef30c70abf0cd121c7c8e680c8c628499268532ce9918826d052e85bc388cc207340ef089ea6f1b8ead1032e6e38d126b167d3425d825032c7a20a5bea563f4229f781714fae39d57d3f42474ece809f3e3c09adcb0725e0a389346324585369c16b9ca17284e638ef4e51341c97e961ec3991497ab418fb9c59a43ae29cc408dbf1a470d079934994b8342862e6841bb267a39e2ed2456d5c1579f1d832c0f8be43b17a64c7beb906bda06a0a50b63f8d3905e0e47313df1c7844413332218a852595e5ea93688c94e741c5670ae7d20441908bf67743d35dd2f9e8f59a4fe9d2d94b142c0a5ecd7ffea0c2fa16057ee0fb00c912de8f4113dc4a2511473d999cd4ea93f0b3e2117b930ba1f535530df64236df73292318c9eed8af424d32bea29a957518f64bd8a7b48ee51d423b99722bd927b15f736ea5db857aed0ae588f643a453d927b15e929b957718fa49e8a7b24e955dc2bb9a7a21ec7bd85f4a2cdbe6f6622f14603b9848b05c143f305328126bccd8fc676eaa70b230ca81e318f1c308c942acabfc569cd917077298189733482aeaa697849143413fbc11c57c3a3c6b6cfb747cdb27e4c910696b38e8efc01fd6e3218f1f0522241d60004cdfd2b3cd618b8d66f8c8a1fa1b500f3efedc0013ca11f0c485c46ebd61ea6e8a1300ba32438ba062e4e928fbee822c475e641687991bfe03103d34ee5203afbb0269b2af03051e252c111150661d90fe4b9e70cad331dbd26e3c2f85818887b8df0e265fa856103d87cc5efe35b053d7e300102d097b043d432d6e8958b33d49b4f5f4c955a8b2904aaf288638a349e9b36cc22b045d99487dde609c71b4b1464b1ff4371239245d9b62fe1fc486201d6dd2f94272e5dc072e70fe78aa51467ddfc61f04309856cf6ef107e406a31cbf6279c1f902cc076ff84f389250bb0effca179b13485acbb6f186e50a298cdf6e74c2562593615be869f91435c982167cdf3a4d5ec7583dd1c14e8200cf41ba4d5f54b941a1894f546dbc5219381a83b0ede3af05ba7abfe522c1cebc509cf71f2f11f840e47f6cb2c41103c737c249051bff083fe51cd57d48b3c7dfb9b848fe6c443543d6a8e7865fbb866082eb23b7a32882d785835d7955158cea627d5f7adfa6d1b67355e44460e63d93384a6a7fe1686ca43ed04bf01113c058ecf868a8aac6960d52f486482a3bf8247ad1adba962054b687ac2c9152271834a9bfeea739d7ce9adb111e860f72f88411ac1ba7b9050f5ae59d5c264cb5560c943d5c4a5fb8260b404cef27ede004934bd4991376fea3bfd8fe7219dbf30585b5051004826567ddf9d5e37fd951bda56ab384064b96d13524c18cd484d6f8dc23f57d5e3feee05c180fe6d8be9301ebc36cc2b87aa47d27efbb3f71df3a60f7e02d1640f913d963f5860ec4d0595f3077f07260a5cd35751f9c95df599f57655e171e0452bb0690d1d8ae8643668e832ff47940d41a4ea358fe95b66473f6691566a9bb4fca423be5362db6efac87e9b9a99553d4c5ae6c9571f4d1f587de137eee86d32e2fd2ca2193b09c8fad6c47d993472785eec5dea2914346cc66e619878229f12c5e23cc54575ee2f4c64b690d8679d50c4d3a91f09254c7ff731a106ff9091db7a3df0340e430ede77d33145368be091df5fe6ef583da00b665431332e9e4623ea4e3a76c2dff59712c1b5c06e5f9a98fbd037085da7c17e0d29db4488fabc9eff11ed5281888c0e793df353eb76c5c1afc3f471d89e9c9bfbecee6406475edbfbe3f7687544f4853e9ca26a4ce4f5c41f1f9dc22be87641e415a51f758b1b175e0bb0b87ae415eb40df1cc8eb89bf975f66345c0e9a1ca5fe9386bc82393c3313e4f5ccaf1dba56c28b458756c2c4a25d4fe10607027925f077ed1a8bbbb081405e5bec4d22fc784114619966445ecffdf169d1350c3086bc4aa6b3dca21ce1bfebfd3a842d51c08eb8d3327ea65715f2aa4a73ffe42fea373b02870fc7bbcd55073ff2da624dab43c86b017eca5576fb48ae62f37b9b4a735f25d3ffb090d73c7ef153bf9568425e232ead1a70b6b43ba8fb3293fee63ee4feb9f508ecc8eb2f9b9d1e253a7e0e484ee455491917bb445ecbc3774fbed0a5fd174e246aa8845b23cdfd636dfb218b359b05560f82415e79129cfc5a70ab5cb6082d2877f978d5ee8ebe56c66e6ff23dd8dc87204d03a0448b1e725a722d3824bb86469f43fa3af31193a79d88dd51ab28127b18931c7383df25269bd930c82907fca17182d9e5ed1fed34277044fccefbc4be59739f937a92d44dc0ee2f459ec6ab30a42153bb728edddb61ccfdb7e981e0ac4d9b55971915ebf3ec32b761cac237c402b67327eb5f7bb38eab1c21dfe3e4ae485f3e96db422c292d6e167f453bc16bcd340b6df9cb588435fe6ffc5776c70bed1bd0f4dcf0a17a66e98d4be6d79508f4be1d51c12be63f94b242d2e411392cb4e68b034f32383f3e5e01f6a106576b3738913ab195c560fac937c3599a4e848d6f8f5de55db75a621a5f517e845838762eb37f17d2da11bbe4e13540f636b11e0d82f49cccd715c0e3ddebebbc03be16a6b950cbbf8805cc55eb454aab45438dd674610745be3a8c1c4449a75f62f270b184ebbcb7562b6c332aca8f108bcef49fd9e9cd33b18089c6456b36aebccc9c9bb7b1753bc6c0f1edb7abbd2b5fd1b6f9a885df8c056c8d93755598e0c489f734523822296f7f10d4a5cd27197f05fc4b547dae2ce58af44f17ca661a75ed97b080057d9fd1432600bb12354197c4452f2f1df542c641dbd6c24e387efe2a6878efe315dd092d0bd3c4e8e55fc46276da4e0aa20d441279cd36429aed865a4bae9e9d22bb051b7a79d094b97a390a3de9e254f0bcc11b21ac90e98fb2400620ebcba96494a9ba4c45c9a9086123f13858fd2bd203290b963934e55fc22246e22c4d5252663f8ae6e0055a03566a23879a23d162b51d66da23a047269c042f36bdd104ae20f3b24df21eb9efcfbb8d6efcd1297390c51939bad5fbe010382837fd4242edb563000ea41e5dd7db4a3c9ef3d67fada4a51c302911f0dbeca99861247e30e38ba26b05768c4d90a3a42d8b57a56d91293c6a7521889bb14a5b8d16824fe33f7e42633b47b410e38e006e0c5d0514ff7dbc26fbe345659b07bdfc8b58b0d34ac57df2843aa9d7fac1aff0dc2b9f1e339e1298017d9086099cc7fe5fe1fb93121edfaaafe664c03e69b69758919deccfb820aee8f2a015d80b23664530f03a17b46e151725daab3510c389c15e3e1c9c766e7d44436ffc118b2e04a6872eb51d0c941d74f01eef1d6ca36f2007249cafea36022ef3a2958775387de3ca15aba175168a17bd828438c6978b58e0a1c116d42b22f11a185eac775d5e113a11316f3dc7d0b99af6a2f05a874a3989ee31185fed3d417cdc13613ef7b0a3491d7d62d727a5d7a6328891c5e308177590ca1f49fd988ad717c875aed358934659e685005b1f3460d98e50fc08af18d604f7f8a1d23581383354770521b05820aca0fb61536538e3ca6ef569b66b6b2fa903436db1a04cb1d97cd7196055e6e08d2c48827a56fba1f9a7d2d7d69e97fffebe3755f4d40da7e509af3d6cc8f6ff2a82ee85fb23cdd5699fc4c8b94dd0b26375a979bd5c4b67c729e434d30a42a0144f252ac081c0c5bc60abd8c05ac5be72a37844e3796dd3a4f08e2e45758a1a2eefb8a70a7945422ece8b91eddeeb5141ef63001c1e07089be79125236c122db3876755c06257bb545c561b15860661fc8ead7679d2d2f789fb91a065f761218a13c7dd050cb717c8a57fbdacc16d65e919eaea456c736212c43e6c4016b3785c0438657d29d05e48fd7ef86df7895fc4fa895fc4fa8b5fc4f2895dc4fac45d8e9d40cfa09f813e875ed572fb053a9fd845ac4ffc72ac9ff845ac4fec45acbff845ec04da0c7a13e8d3ee11a95712f62a8680ee46735e8e2ec1ff93d28396cfc867a5eda8308e38e4d2e4fe90f0896c101252ca6b5d4cc9eb0e3d2827f044d44b4f9b862a0198cc097737bab9a418275a96e914fdab6ccae51deb839865402259a56756521bdc2047268f719eecb5fe0ccc2169aad75a3a2d83258ae330a6ac0ab00329c634ab08746c26d901a899f524ca87e13e128f55baa58a28e97868688fb8b2597ca8e8af981e77534bdd71a9dbf1d6a722ac9c1671262dee6fd39879158b47c3385fe6f9af62149638f162d1814cf05cc7c92e953508c208b8f46d57b5a25b2a86255a4c1a304b61219b8bb8768bc5e605159f2d52171b5e3c1bc14c116228bfdbb94426f015492d9250ee558f205ef4889e925ca939d388636e01c589be38b4b9153e946975f28d1f5f9f69576de6d158f6d32cd82df619bc6ee074417dcf81524deec95adc579139c5099f20ce862b53d5a6611fa28661d6f12ec9cff3c0dd8e29926f51942a1043c791bae41c86729580628e1c187663c0b275c1d4089b98f528c90c071de03a41728ffa51af59e96fd3be5d971790db0780d59ee212afbb4832ed018a5069d5808fe49505b0b03a92ac45aefd175702add6eba27f753600c4da2cc2f2c1cfbffe6926176c2bd172780aea74b04923dc5a8db95105fc1f298423a3bb6fc278213ec17551756138c4eb01f0e9164d46c9d85e4b1f8203eba708b7ab16db649c5a6fe1365ef29461bb18ae5c67870b101a0bb4d59d34c06123ab09bb835f29b758af9ea1be139dc890b602019dcd945c30ed8e1582e522aaaa2f953f38751c7fdca0a1601d29e6165455a29b3c2c7d31bdeb9f5ee2f006943cd058490e3754d2c146943eb881d20768acf481c69572b871a50e36a2e4c30db852b01e5e4d60a421309104004e97dd0880a7b09f88fbe428a8ed3ab7b04314557464e647e13d396276f7e294196b18d3298de56badd9369d56abc7d95a43736f8c5c035ab2fe8858305a554d1b2991df178f490e223f2e36912c4d058eb835e2b2a422bb6611461505b893f7ed4069e1018426fca72269bc9f6107c6069767a94bf304a2350aab5d7b640d581e888c30bb8661cf055a6f33da924864776f6e1940087a08700874c3e1ffc33d1bdb36e87760e48c16e8b2e1bb9ae68c1b8eba6b17b3ed8473ce396bad73661f2b7fe69fa94d71c4a8a4d2d3bd7803cf18b5995368fdd6bec93bf7b94ca019146b68da8b463468d0a041a334495a47e7a603fff67802396a8a58a312e19e04e29e0c22efbdb7449a7af4d9d87289bd74c6a8cddeb89bcdd2113acfd8c4730affafe54f264eb3d8187f0653139ee4638cf114738a28833e84a6fbdaabf5bbdb6fc819df721f00b6fcca7d78cb39c5049a53c43c408a790b10bbbb3cb6efce761382d4d5fa2776e640995a411f5ff8527aadd359f7454ab1e8b1bbe9e48473b18253c14149f3a8664dda604cba1207258983fb6afc56e773d7c66fb3fc1b71fc07f8dd85b1c7b6282ba9f4e62eec3fc0f869ceb9ab5fdc18e38cf186b108e311c61a6312c6258c4df0cd449abfc7af9cfb0f620d1cf44bae418151e0c3ed0f59e4938aa06d609812250e44f437a300fa832e18477d10d6a3c54892554b44e9e8f9fb435126128de04944cad1303db8e78f4313740dca500695490eaa23c8002584124a931c3e004d723802c2204f14c3dd36d866cfa963c5121b233162c48811c323cc42402356891a9a7d13a26ea79bdf8ce2c3a44febf687b333923dccba0e641e6e20b71cf229852852fe40611c4314d8c565ba00716fdad8f3dd867b13092d7dc0195d14df5d20cbfc09c4bd19213482779f3640f7dd8fc68ecf025341e0697e8c4800c30836119065be17f4102116f02a006b7851c6fcf941e04bfd39e34172cf3bdf9b73874d670dc115aad70c593c93671fa42ce82fdf8f3126cac41909c09e9d720102015883479451bffe0ff862bf7eadd872d145a666f0a8b525c6853ed0c75e7b2da519bec85d3faeca30456b095e69e9683ae139e101149e004113400002aadaf1718debb8177d0743036c4863c38fc1b265dc831405581fca1eb6fb4c26ddc9163cc587dee33cee398f4c6d1dcc6c1df018c23d2895c8211c8ea0e5d53ee2477db2e5e3a84fdaecf8b3e5a8cf3f2bc29fe21cd941f62e04a54e6dcc0de0e3c79c089e07e9e334228bf501d6e481c9c8f0c46de30c17a78c4cadd05629d2a60a266178f2cc1e3fc5273941b62c279ffa76cf2e5d394ac217afffe24d2c483299a46f04bfdc998fbb226acb8b3bb840c30ec51fc57df24c9e9f47eaefe88b45f9a6c7bd1887dc8b30c812ede42962be43f86205f802e153a84108e19ead7933711c55ede37839b8a512f7a2cd64d554ee37e48c702ca9105393479ea0802f11fe27c7f0851da5163614908547a6660de0297e133bf2f860faec4884bc918ed0ac1de712477d93b57962fc0973949084752850b67086e6c7973099f22ac053fc2d5c30683298d4b0236c47f9b2c7533c4a2478947dc7797a228c5e5324e2c0e26797a4250bd38bf8ae038477a0818ee11efc3827e76d9c730cf802f77c4a29a51042487fc00b04ffc2699ab03efddab9a029ee7961c730ecd8861d27d0f5700fbaa42294c981962d371c44b837238610b6617fd1e543085b624016d84d121198e25efc8942df48fef86ee32823f8f38c28234e6e08f7643773e7d1a37fefb564fc1b63fcf83cf413fa8b403bcaa0188b4b3a1d700fa96420f7c33d888240dc83efc3391f54253da0f90a123f51fcfcfc44f1f313c517bea0050983367d18c43d1a67e28c130159688c44b8473de7903bfb74f1ebd2547beffaec4edd10fd88d5ba4fb88b504ae9ab6ec8729428479bde9b429cb6df609bef0c1ce3c3696352125ad65ae563d2892695a648f4692fbfd6fab3fec428714bf781156d8e53b7ecbe5cf2b884b9f4719d479b8e23eee5b022bee26be5ae79dc04f3f7710f4866d928136d3f586d6a7f025936be214f0e8776af497223ce9b90454ecee431248ed22f5d32e552aeee4bf39860ef834db845b6f2299421ff869452aa206491ff23d68850861648248d55b24f0e0c87a7e534b2e4862bcf361d72e7c7349b5a7cc52212e20bbed84f7422d262f1155ff115df6a60eefa1718eddae1d7efdd577d107df6d86e39449f654548967b946645ea8c9cfc0154bcab44a217894422ec0262971ed34e7495842f72d35f4922d67e95abaad5155b0eb989802c746ae0eeedc74ff483ee92a198233eaec4613932e5487ec41ade0494e1388ee30464a1ff412dd4cebba0a1a4812f76d3972e4cffc405fe2fe5934876e5ec9e1c8ef8a6fada24b134fec969f606e94924e88ad2455f0776f4828e3fdf5f7e044c9d3c7d57c1172d9a74f70559eccf55f53fd2fe5c55ffcb7e3408596a7d0dcc2dff02a32dbbac8b343046012883fedd84a823ce553132750b714d4d2e6ede35dd7214217f4894464e9ea2483762520a4019d6553334d7ab1cdd18d34824ee511216e83b09a210f5a7a3b29f3275fba1fe94f2dd09f8a201bd9b8035ecd3f71cf71e478db4c879b40967d3ca4dbfa1a5496b5a69a59556cdc995f63bb440d3ffe24bfb22b2b8e328dca1901ebf91132386e8f872140aa993488c98fb93abac33a2820250c66a2608119085bee4c0b0e9cb954ca9e8228d9fe8d7dc222bba8a48365d553a61a4a2dba1cc7d3b209085be9193d7b822eadf8f3af025eb72dc6e48dc96d22e367204a40053f950de97d6727263e726e742daa965f5babb6b533bb5ac5e77f9384823ece3f0186e9ca7add54ecebf72397cc655cc69937b71810ea8eed5a69951b81739b8dfd0d6122f037c791d8d84ea686d32451de9bd5cfc177570e83e6f5f142eee77f1791362fbe15e948b030e8fa17c9103e735367ea22fa535226e1717074e6e2ea5c83e0e71cbd8828b3c2cb8d863e222ec062e2a59c1459f1ff7e89fd037e162508c717149148a43aef2189ff1953b8d739cd7bc80b49c3246eeef64ff863bf7c33d20f4dd6ddc76406fd1c7de846d39ca669b10f6dd5b378ef2a7ef38f005fe60ad8d71b56964b188f56dadd656b972e12b84bab0034f449758da29ce5913d172d391edbbf9c4c1f909719ba20d07cc2b5e5a2b15d1d927ff64a023b19d1fa0a0181ee10e050789361c1868fbc93bf80836c9a734e8cd47a63860b3240a693632456a6e6c727274781cf55919258eaa7f046707c9cf0ba827685bfbb665df55312ee3a8fb479210da9f75b5f6677772f66761db26a179edcffe08415fd4a1887e6eba23d0cb5a6b270e51f7c3fc89f5f0c0946cbbf38324c7a563ff6e3bfc08ac884dcbc61c7153b8b367569126b2b21b39cf3dedeb86c364eb50281c22820e70f60e3028499df809773b827ab67d9333d0401648c3dadd86499210c18e1b0deaa194ee3803cb3db87704f5c023345c4250cfc670479012215cc810f8e2319f0188f21040cfbeb59dab5672b4b50142810a04812ff1ad2a0c60d8f689803b2e4c29a6b8b57f2e86f4bc1e0f8035347021006bb82acab06fb50ec2b635d1b72b5e5adbf06764b6c51671cf6d0da00cfb96896dfff30f6cab23dbd45a9b6c0359ec639a29d7c30df7ecaf6841db2236eed91664b194da772086f0942d6261a06d8ec13082211ec5cfdb73c54b97461b6632f085c696a618e3b427cfc5d1794b79025fe84b6badb5d65a6badb5d67e9dd9a4a91ff1ad193aca6e983f72a36b1285be1d0ff76afd164f4383e6d1c387a3b6afff43a6485f9f88208ec25f5fc80076fd21556bfd43fc54df45a67cd41f751f74d517d5ad13e25e1d82e381c31a74fdace5be3fb549a6e4df7f999a7fdf6b7114cbad0f3798ebf7ca7be7bdf4de7a2f842cf6f270af421eea83fd52e1145bf4e8f8747f93078e0fe31372c691f5c9ad3d7cfc40edf9401cba8668f56c3798bbc16c30346b1b4ccd7883b1396f301f5db8475dbc7bb18b3f5d78f034ff5ead713c21f6706fbab7e8810ff77ef0706fbe13340c1043c0171d96d0f4756069fad1bb6b2bece0756fbe17a3af7757b42f64891d448169dfa6807189887687908866692873fcf62ec6cc0559227c3c2af54047019f68cfa527740c29a54fc8d2b008baf444e989921134ce252368084440babb77a527748a7b50965ab0c20cb84444c3c7235c62699862c0be6c38a92f1fcb0cff72d069c4bfee3e37292b9522da654c031085c8b22f0a4ce77c1db5c386efd4f7ec3e06c82978e21a5a56fb926276193dcbf94e5f90e5c6a8f14096fb5fb5992ed0d9638865f529a728ae26173709becccc72448b29e8fc9f8a8d630d3f6247d17cb1efe3b9c1d4ec59a615672b8adf0615a2bfaf220a1540b2fb340f36962b4ac01afa489471efafd0c097a979b0c4be36475a374dd350d9f6469a54323951b1023fdeb6d28b3e7652c401e1de85e1e1de7d16e84fe79488b070a9051a924a4f5732556d60a0b2efe393124b7ff0a746b75841965b62e90fc6108712bff4274fe2506267a43ee9e9e313ce64a5c4022d75d8d7e42b477fe508f88253f6fd134e05b629c079a81de5545e269c0a4f10f4a7c2daf75dd98efe54845829ad22b4423706d42f3ddd18409fd4a908c9d3fd12b792b9f4f7228603edeff322862325c70405fda5c8e4e8141999f2fcf75352005ff0df4f61a5c8e4a4b45270f6a581f65711521972549429e7e1fe8a8c4ce91c79baefda5745e8c5cab1c941057a8525530e63df2fad407f3a471b791bc690a7c4acd8606ec8710bc2c4a24546a9d9c50683c30613b978daf7f18b0de673c45c768c25c630f2c43825538c315ec9166392268d4cb6d2127a7bf9a32e484a4c4a08600c394aaa1cb58d60c718d053511172e19e2945fe677a15a1df4108b25c95a11595ec5484568e5801018b5c22d2229758fad3309f7287164ca5c4d2d8250eae4226658726b600f69dd9df8b58918d35568da7487f7f65c6532a2aaf7dff5ba1d9f731491320c61931d2881115632a4697180b10e34b8c58935b69093dfaed2f10db70218bd45af67d3c6383a191232a7b2a4b973c0b90e94bae35a404926d1207795071c9548a8c9feedf14997d555cfbde9f5c49051a887b578b9bfc9414c01a2f6231e57eca8ca3467f3f65358624f67ddc62c3bdfbdb4797162f8020cb6d11e49eb7007a118b355a88a10519f6fdefc518f6ed416edd8d16412d805c2d622f5890e53ed0be3fbb16b153e1b3ef8b321791e06212d506b6efeb9c7bb5d639fb3ed62a4232e57f7f45e528f9f757eeafc8c097f8f777c0812fddbe8f4d3698931cb7205464df82b821cf2d0853a65b102c72dd82d882d0361dd98edd10ee5ddcda3cba0559eee39c451cbe716fd347620ddc049471ff6227f6fd4ff3608b381feedd176d39b27d7f450b1adfb4dcc33890e58e6090e57e4c0164b9dfe2fe875b42dcbb8f337bb34c243b205c10c8727f7244b877b5ce081d71c72eae20cb7d1d2cdb08e94da6988a907bf76f789602bda272ef7eec54863a1668ff4f4568df0f729f0847657f65f620b4ac45061b302c9520c6e8de695a3ac1103f80701445550d0d4338aa87107c01c09e7e9aaefda7653e96710b42e67a339bb50d06fae5b27b93f250257bbe1247c5fdc53d8574ec28ccbdf9ded19e1a99fa87d2f5a61efd41d797b55e31a23fcab34f3598f36ff4000384a3eccf4ec7f3e8e1e3071ce2a8ca0147a178972fa7ddc36144ed4e7c2e125636c37982baaa3f18326f783ce2488220d3450e76df10e92b9f91f1951393b484a635366a28a5366c28a53658365660a3c646cd1cd24188382e9d1fe4449c1f5057cc094c6c4838fa0b127da6c949cbc4669edc4c2313483f417f415c964ca039c58ca94a258010c6183ca59c3f33843cdcd34edccbb47b19c43367bf219d4ebf9dbb5b293729b729adb45606d687fef5bdc6990bd85dab73f2dff363eed3dad6baec5ef6a5199a65f416d95f907cc3a78c724ee92f219deef05b4a2d987306847486cb1c408bbffb8c22cc09a16f948bcff5a8f5de0cd6dac580927ae002b60377bee0df27ac9dae399ff83b75eed481f75a8a23eb2e37dd839dffecc9c9a05a6bac7e81d60ae43e5fa832c8bde81e9041f529e7736a50f84fd6642181a28352084a21d05aa0ade5ae0d76c98d50aae44aaa642af2a0c4512e644854f96c60fb8c638caba25096ad1c4593b11c055da4d80b83b9ce5de228b7f48568f1f3199996cba6d3255534fb6a3c8eb236dadca1d8ab44899e20211c24242485ea876975b47d6cc2915e73268f0100803d04b6a42f95b81ef2446db847d1a0a5ca51a7d3963232a56d0e7c79981bf244efc77dbbaf01db762eeed17781d66ff2fa4d54aa7df106e301c852469e5400b90e005f6b8a82be1e7d13fa9abe54c9137d93276869f3290dfabaecbd3af6de2b1385642af240efd3cf64b2996c2555324616992f6532ae9246dac89b4dbfc232ae2e377a80a1a64ff1bcabd1cf5eaa3c457a7a5dfb0895d133a5d726a91893a7b113cec569dc37e166388dfb25aec569dcd75c0ca7715fda784ab76ee84b1c9943e2b893ec27fad7e5a82fea386ad621477db766d3477d2e9bbaf67cd791696dfa17b6e90b19b2010ec824666884f6e7499490d99fdf968ea35cf11577228febd870b9d1030cad7161fa14efda8c6275be9b0f735f1a201b1464efb53b82828296e060f7b1060c533e91cfbeaf29c12c4c8369f00c96c133788571f08f8f7bf7e46fb816734f56bb714c0aa9509cd0ae73156d0c5d108e449b4dccb01e80c0433d7c00c121020693a8a066dfffa1b3ef0711ca328efa70d08e214cc35ab190d8a834d70f90eb67e572b95a6425400fd60c5339479e2e2abf9e05c6af8a87fca4041c7b390a97017af76fcebecf020a9d8f38aa06c92af7728cf653fb22669763868240d8d63edf38aa661c474d2dc8bdfb95d3625add26a9cc8299758d98ac0df42596b49aa6695a74c2d69040845406923ed8da1773b2fe2f066ded230e7c31d95af704fde1a11b3099da50eb3e1804db771b01149b6adffbb4831dcebe1b9bb61f918e48c7a4f36ae9984c3f3f1bd016a44722150b1d1e1e99da7eb61f91ce6b67f37154cdd6da8e6cb0cd6653b2ddf8e46c45369c0dc9be2fda9438aa66586e30518c6846b4a27154dc5c3c1beeba096d432adc62f103d4fa61c1228956ab55a568551c2c3b2266de68e686635be5982ce26cedbf201096713926ab86a0f1d0b672946f21477dbef15f9b5dff0b12571bbf7c8c392cb4c4bdfb232168dff83f1cdb17bf50cc51f947afb3972b32d0dac79c65328fa7ee491774104fe1237ebaa5bfdf43a6b08dbcd1c37d201c357afdd90c17aee1300bd370788567382c2323539a92a1ac8ad14e5ed3b4ef015fb4ade1ecd81247b96ec4954ce5c043f5f1d3adb5da946e30dca6a4f55533904c6d2eb9b94a4ce8fbf53f4d892b28880bfec12f9a823c28f9d99706ed4bab941a8610426c371c19c509ed516230ddabd76e3a349e1ccb419af670cb40eedd37629658baa6450fb4f637a4c67ddede6cdcbb735bc91476f9e9aeb08c3cddbf576b93e9dff3b69aadc8bebfbde08bc4d5de9ab32ccbb0cc99478b02aca181ab41016b6cab2803065ff0864415f6d5fed3a6b0ef6b3d38fbfed460967389a5e9e32d47b62111fd4117749fb1095f566cf8306b1cdd714fa3af6d156b50274001897d1f638dbeb41764d1724677e80b739407b25c4deb3221e81a69adb526695dd2da44eb13ad5568bd42eb7bb53699fe3dca8d34a96472a262c548934a26272a566c2bf7ee8c7bb73edcb08c7b77ee0fcbc07a9cd01fedd9f7a2c480ee501e1acb368d01e1de10eedde881beaeec3eb8b1e08bfe4c3718528ed6ad95d64e6ba9b5d55aaa2925515aa2d484d2134a5550ba82d21b2895d8b5efe35aebdf90557276e32d56452c283b947ab599b1764a83d62f62c9d41673d69e17ba78603bab58ca0d4423535fa4e3a89a61e93b7cd74f84857d6b86e5ed01c65393874b44b56fcdb0bc58da0d47acb943a9db12ddc5c8eaa9521c91e2887bb076521cd9ce62cb91bdec3e17978d1f4359df6ba12f79badce7b273d4ee83d9dad70a815c76c54f5f526edd67eae80bae14d11fcc962f3107b7e43e97bd8284fe44585061da7cf02945b5db210512c832f70e33c4361cde8dfeb0a664df77a9dc07b3eb4739ff84a32feeab1de5b98f02d32b46b426244f178695dca9997039b25dd25748334197388cb3e319852ff8af867be4bd5a9b4cff9ed7a209ddfb1a90d47ee08bfefb9a12cdc784d2f2e526698331c97e9225bd21575386b5975beb7eecb9e5531a34fe1b32258ad132910c16c56c9b0f73d3f0108efab618b7e30c2cc9c3517913caacdc6a8df2a8fbb61fbd0634c2b18d3a94118fb6e1188974ee855956c6a5709e48b4fd0d4fadf8fb5bcc519f286688a3bedc825101c40dafe2eff330ddefe1a9fbdb92dcbacf8273711af94ddc0ca791ff06aec569e457c1c5701af97f38114be891d9f7856cc0539187fb2b388d53c1cdf0d37d518ce8be6866f4f713b1be2cd3e3a84f84e3e3a80f0739eadb5c5f065aad1cf561997dffbec68265d8cff613d3a143221e997db54ab3ef531e1947e99915a6c12caa84e72ee1b33fba64df2562fbabaa7d59aafdd5997d2b6bdfb79b10da93baaff4a417f138aad2ac5835368e227da9f3417b52874212e9388afefd1c9481722c2fc9428e9aa4ee87b9b5df7e361d4769db6bdbd9781c25f7fdf919e6281decda71148fa37c87b31ce547707290b83ccb88ee8b582fb2111511b5f468f498f3dc1b8dba1d413e225187a2cd40d15c66dc1789967069b9db11e493e1d7348cb58cdb11e4e30324f2d97c0349827a827a726cdb87d11fe29e10f72ca543dcb3175662e92fea9111bde2a5e5862fada244030d7b641e998a53c848800720bdfdace3a8f8ba3cdb42017af67536a9b3268027fbd62ab16d4dc4c2b63fad8c7b766606bafee72a1b19996d734b9e5a7e735b1e03517908f00888ca48dcb33255c23cf0e5b71eb55240fc6475b6fd28e3c33dfb3ddcb33f204beaca535c72854879082d9932c913922fe3ac78e9ba61b4f64b2ad02d90858e9e7630eec17c2e3a7edfaf38f0e56e8f73b0459f5fe3ac0d6481b1eff5d11f848d5ec41911b7ecaa6f3691714286b807c4ce8edd06dcb3012fc8421a4cd082266ad0051fa0bcc00b69c879014e0d4a500424a441474bf9a33fade3bf20043a6cff2c6594b0a7074984400a50b4c10a311e261b7a6e7610e312010c673ea15221a5869301ddecf8da8da33eada5b5f486634e25fb3b29a282a20814db5fcf6007db5f33a120e1ae027475f6fc4ca320498bc89e3fe75f9414c4242fb8b177b2210a11bc73e6da04ff8b8218f4c00afa9b1a8ea3e68eaf1dc971d49c279b0dac3d93c02036b596f3b3e3101f9ed270fc143f090f1859a2085b6882064c70020916b09ac14feb88170cb96012010a9687d97d41ae690afad3496ca25091631249f8c008f29599dd9df1cae00adbb7e6a2b14519e6148386b161921b7ca046e70d93dc98a1b2e0c60b28d07ac324375388ddb86817f48b0d93dcc04007fa374c727304b581ce61c3244c6c01079ac586499848c2113469c3244cf8200cda6e9884091d5432e81b364cc2040be6147ab461122666d6a0e78649989069d1c1842a890d724a1b2641620b7b6e98c40647f667a2310c533e1627ad78d2f21861acd965c49fe10e663ed890ae4840579005c696272d130cbec49f4d6c23e2feee49eb4216fa23181061d3cf4642262860b0e94b6ee28c84367d4fa5bfdbea36b7b1f40865fc2d070c183b07b2b88c31465c22b2da54c507f379604b1c1de2a9b158bb623c717ecc895e90857ee6bc0fe81dbef3c76d7bb8e940d93afcf9e5cef9e46e7a37c6b89b3954a32b183b538d257a656fb9931664a14fb71c3036ee4c30c842fffae8f943c4446944cc4e5a4644abb136852ef8026353182c70b0095eafe89ec3a34b0844816890672044b8672385a5e88f9f646a86d3ced220ad94036d5536c6cad0233b3f43341bba3f766746adb5564c87accaaa648ac662aa6d9f0e5595a3e8b65f63ea90a3be961e323e1cf5d55a73b67d5b79b63d02b4b36dfdd9d6d6a16dedffa8408eca821c45634b6010148a3175db517f1cf5d19f5a6badb4eeecd45a6b9df588a33ee82a62d3ba89358efa66d45a6bf517a631bae4456318139179bd5e3cf4a706d12820aa4af1b3edc30ae49efd1e4024a002f1a8405398e18230bb17ef3d915adfc0518160ea175c2e4c2926eac3bd1eee5908c02dd42f3c843d85c598de0f2a33c3c5533e5aecca46164a886d9753fe2002080b34f4092244a6e69b66f8c97d86cef68f30315abcb863f4c1a3879cb4da9b692ee4c9b690c2ddb1154829638c524694098148a9e04991524a29794c18a59452be154edb0cfa73b1557c21867bde92e2026eec2447a11f2fa52e3c65e24af164f6fc189e52e161fe0c247bbe4b0f78983f20c39e2af604c2cf9ecfc369f4d9daf3f5dcd357d42748856afb2562dafea2a2bd6dec5966cae5b0f18433fa94d38ae473a33d69a0e3ddd446a6ec68c9484843831e0d69958c9ed1ab93167c21008da7e88d9ffc7d14a3476ab623d9fe9f2eb2fd9dba8ad0967b3e8af98f62a32523a197163bca1e1e47d5b8057137986c83c11b4cde60b60de673d0d170a33f8a8d84464b9aa03fd3378aed177c79b1dde5a4d5bbab42958121c2a8849a21c90d66103dba92245930c3feeede49928520e932491670f6379364e19505d73e69006541e6f521116d23a535e7067e0f99519fec6b8e4c8d6a13f000a5cfbe66f5b3ec214461e8657f53eaea12e029fb2cab4af60d3b7baf2faeee4096ec4b33d0f73ff8da19dd027cd180a6598035641c853ffb3ae3a9981b689f3d1592a9ca92272fc0171b6c2ec01ab515a90f7cd19f3dfd812fa3cf9e02c9d449660293b9cc88d1124924279124893449244a225512c99248974412650fbee4cffe53262555128944da3691ad55a354d34a44b4b5b5d60eb368988406b67189a5f38f6ea6e1bc89ec2813893a98b90b59461b67c4101da18cfa95d5aa9f65bbfe576db0b337e174073169db4adb66b26d27dba662db566cdb0ddb66da60e0deb611c92497b22fb174ae3dee65db5661ee6555897bd9d7d7cebef6c0aa1247c59d7d7502ac1103cac83efbfaaa3b5fb300ececbf0a859d653f402fd378f43542148c7bd9d7faf7ca941018473900fe00513ea0ccfea3f3b90e468aca8a0ecf75305254567478ae8391a2b2a2c3945c0723456545078feb60a4a8ace8e0711d8c1495151dbc1207e5e983403aea26753d204bd6419d7df6988b31ee55ea234fd9dfabb5c9f4efdd707154feec6d38aa7e76aab8761b0c8c1c53b2eb90a997eb29db2d884d74b7527db92b76d91f063cf0b9ecedb7a73eb2a565fbdc7ddebfc924b7ecb56d477ddc5d9d57bcf48a4d88c7d9d57026c25d896b8102a8af7195b4eda87fdd9bde0734dd81e283dcdbcbbd75ddf7636b1d0aeeec675d11d5a72508e3897380b7f65162a0eddf9072ab2fca38509cd0f345f63169ce5904be4c6e53983cd1cff8fe902793cffb8f3b8c7ad0a6f7d2973380a9f8443cd14fc20d368d4fd8c1a6118a4d890084d6d4dcd69631c966cb586f198f6e5a3847728ad4f8a97557355b1e79b6a944dbbdd775d3c23992936b6a5470535353b371cd3276c3b1839be311ed5b8cb18fa1063d4e51c45b8e88736fcc718fde15aea98116c3bbba3497156bb4cd96f128abd93216f16c194791d6aa812fb17559eead6a8a60584c561393d5645b964ae8384b14e3a86ccbdb0d6a655bce51e7155d4a20e1c9b69c6d99c3b8db21c56a7569562b2558abd58aa5a528a2b38323ba5cf76ad3ed3ed37b9e546d3a46db75200bfd520c340a4ce390c38b0de637186e83e93618181b8ccd291b4c89555a42cb981579c54b4f51de361c5206be4021f0e5c6a018e3a9e89236666c3016737101988a6880276a0368c6fea250f4f9a2d017613c51280ac5a2106459f1d228f6e346c951b7edee6a976e8deb400b6fcdadb936b78868cbf75ed16b9fdd9a5bd3f297efc899cc266b451cab829f2454b0245bfaddd5a659a56d39e2b69307fa427236ddf6fadc1f47e1146fc321d26cb26e871431354bc4481123458c1431337725535148686e4b85b49018f7a8135a2555140b19b201f8227a7a2fdefe932afb39cbf83920eed1c74ef8c1123bcb5986636c302c3906207b4b9e02c87400b96e4110205b3b12fd90dcdd2b5edaf4f7a54aa66ecc4f14069e9da8c44c1696452d95aa91000020009315402028140c86c3c2c150928589a8b90f14000d86aa6250421749b3288651140442ce30820c2140c0000080ccc06814007072eea6d659eb5e04a5188ca7aea23d4754656e696e086b650503685f6f4f0ea5897fc1646e8ab14500f3f2ecfb7ada35d251260f19d80715069e8187b99b177d8c70d85e7fa338939c24eaea2da06cc90939a2dbee53556ca8b2d4f7698e47aad63ee18e48d2da413f725ec42f8d2b143822c76210a86e61117651cb04a03922367adfb731f1d928105ad35c70638469127d668a425282d94b2b3658ec666640b6c49b34aa5136ed06fbf7daf0d5de9fa89ea2b70f592d5e949dfc5b1987cd8ee8630ac48807010b91f0508ba57a3944760726cacfaf976d7d99d83c50aebda8db05647fa3975b508234ff0dfab2159290327bbaf0ac98062050d508ddd92fe9542d347c08aaabd8b25e79141121cfb1d3ec12398ce890f2223ddae6571ca1cd7b330db9fa4d5a63a28b37172ddef3563e141d534c5fd2bc809a6fc20b20173f4bfb730843131da0193a03a57dba8fe0ecde276a62f12ca80bfb58f928688ca94fa10976e6233acd040826c3f0da8eea8afe8586aa27a8d2d9a05713f50505378d81d19ece9bc8a91e0e597e4d87e0109b3601012b75cb1c11be325a9f3d58dd23725d4fce3c06627c319923768a59ad857091125d558aa91ab119c95f87d8ba71e0a42143f137998ea094c81c4a7b497823f958319c3989abb81c47ccdbc2558b8088973cf3c1b9fbdd72e62c2a6e535752b169937a823d3cc5afc53fd16dabd2d30960b7f94d2560572ca00e695035013dd93d9f02c85f7a6ec85d06eab56607940d30b5f8dbe63ac586258c33145168635998e71da7f75dc8ef670ad17a10c88c0fc2305a4e1b471305111b2c1da82e31ac8258802c7dffe288c8d108453ee93b3a4bf9ed62d7d6fec2518ff865a8ba7d6e9be28153292f744ca8980d702b92d60ff412030b44d9bc780816a3027e223489b0bc779cade43106c27b08cd1a960bae76cdb917146fb2edb4b221336d96ea163b3eafea335bec5759072b4cc83df44b3ce0085526ec427c696c81acb03968ef30dd40cbd09dd0b867f9a7518b1043eb3231ec24b8e64fdcf6dc67e26e9d8fea33e2fccf09911de784866821682752860dbb288e40a0afb4d274bdf8f45f6721d3af028d6ce5e6820385d0efe6457f8f565a381f1ed9c4aeaa76efb1119f104206bd2df51ec6c2737f1b7e5ed272379284e5e25bca2bc187762446889655ec0bc53282ee5ed9652520955ec5b9b0138bab39e6d13b9f85149b103bf200156a8eeb4e1903cd214d050692fd2fd0e6c10f8167a637e0f99fffdd701da4d70bce7098c4a7e6d9e19b521a014480e07f676a5bb3c7197077d9f2945aad441a13bf558e9b3afd0a69360df1a4ac425d776a1e76104b26d0466a340401213c6125261fc68d894198b173d481b9992d4bac975278941fc25c88afef94b69772b4814c3c3a3415d1224bc462c34d5c32e3997b7874e7466baf4c3e415d0ecfc4a0511c3a0cd82e4d1dd952de8bf38477c59f2081954b82c677fb62b7e73bd06ef8c8ab0aa92876a291d2e81fb0cc69f888d72a4c5d361b646d045109e010be5888a1d285b0e88b48ee43117f842442a1280a3a616767e1a936bff79f73dfff2aa821e50351e90e49c6d090b879053901c35625ee5be6500321d9d9a832a4b98d217db06c691804840989ce67a80c521167b8c96f8c2d97bcc05962a05647700d126c5b776f0cacc45ca6a8c5226824244b69b398a0a5b14de5f329c40f5b2b5d1a3e69a11512879865a9d8fb86ccd6fa44283fff6fa2023c0a0cf57e5405b45625156cec7721adffaac822200a83214d2684973c2bc527d9efdd2e4372d2ab4c225e1e2d38568b56f1d360435ac2adbbe11ea3df90b2602c2d1b18096689c24187255dd5acf14ccdbcd42d5f5652a9999b381d46b861891c8c800472a0c5c4ff728665821357cb10e1763275a7c03c02725fbb1e3af43734ef61badb2852436c105a83e4fa06075e01d7f88af10b363575c9bd71fd8fa2dd8788116d0571375f3a189e3872672dd2b67232ceb49ea6db73e21d12221403b6fcf56534de7c7d4194e68b190f22b727cda5d4186531b738bca7b9f45834b5b4597a4c724b223944ba17dee94b8ad5b02f011fb18c851985d898477f0bd57c33aa2029bdb35e7d9b3dbf46ad505e7adbe61119f9a54d0a94d1d85e008fa69ebc3b4e98525cb5cc95909b8b1d5d8ac5321a14346ebeb853b255599d82932d257074f72db838eaaffa8b947b908e3dea5b9ca6d8233bcc0f3f373f0ee30dbd883245755528296d3c42510d4e80b374139b803c3d4daf760bbcf83d2d029175f7130c9db89a7613d6684a6f220008c81eef0b8c412afb0f7083f43c82169b65241c059e9bf6e9acbd796a5b58e297136878543fb185d8d1df1cbebe3ae5a504996254c022c62059cdf308f46cf66b51ad452f063fe3b1fb38b3ffd1914efe66e81b666cccdbd0732c4b8e95d56bfb19dcbaa342263002cd80d044049a5d0c12631d358793dc1e4d45b15c5dbe03a27c236edc43fa2d001a7e944a826c6b27c9055e8179d0c1911843446b70423776eb233723659c533cc935ab6d132fd924ac703f5c8fab55f9ac870cbf2953cb132b3b0c3285c9f0eaaa7abd4fd905d850601c46482c687f17f1945bd81cf788d799f76e9ca258d3439365c7a3d11327234c3ed65a24b5438264fae55ab61fa9d7454ee07299e69bda91f23cc38fb2750557474350232ea0732bb2c326c32c09503908b2daaad084f092a87af023ed7061add092e5151a14c86599017df015c0ecc0b6e92ad404a4d19661218d10670b69bc8803d2c893c990c65688dd572967977cd5f8a565e657d238f778a00b16b265767b3228735e06a78b56ba047ef20ff28810bfcca0cc51db9f48a358d83eb4fdb22c6a9343da0a83332424d223162c5fe45c1f0e3cad655871a994ef2d8ab479055e2902f836442a415353f631ac5f7edcf5dd6281ddd35054d9a20d921fa1103a16d138953c13bddfeaa7cb214ff054d1084025f60be8cb2cc6b088ef49941b9771539584527f8019ef171b36a7c2c75c3fb9855766b433608d47347f21872274d68121c80ea611f974b1bb3cab14f73cd86265641fb31817f32d53ccfc969c768880fac0c089456e3174d231566b0eafb05b3330d7b421150c334a1a971def00c23e29b661be582e42989608ec4d1ced2032720f36ec9640c1e94dacfdd5013e49509388fba55f35da841d0d0f77a02f6d2036821a3d6e148af022f1208157d8573db867b23da822e62d017e53da005c022e2150fd3cf3aba97198e647729e26f1919887a92ebf06a4a5f65925dfc316e717545f56fbe556945b6e14bd6ab9c9cb69a2b6dcd8f13fae5914f8a862584535b17b5f74524a5f15a289c910adbff52ee3ba84087727764b4ea0b113117aab296b356a389fbc74b80118e6dac0226013b167fe677ed188a1a67051e1133ba8cb42abc1520ea101babe1e73df165212186a440f52a28974ee9dbccbf681c05fd00bc28a832a8eb4ed7922721db0203ff8823657b17f04a31bc90b5811ba7ff988092888be33f14b6abd6dde8ca3c6b7a9ca568c78f2337ac5d15f388842bd1285458a2a4fe38f138b1226253d5b2823c3f9d4e8cce648043dfc7a6e1c7bf1b48d088ab52162f24d83e2736fddff390a2f79e342d3ef3e7de3822fd409546a8fa2650bde384028660539ca7e9f7bc8c48489397442f8336d1ad1a813e8a1e9858369586b96465626bcb04f942c984d8f45536953743abe25230b66d3609596758c16256b66a663a5495a2eec892a2b33d3b0d4248d145d876f64cbc2dc34665db27bb529be513f32a2085c6fc1fa715ac7c937d22dae7cbc18a74caf6613501b5cb86a514706c569c253829c83c10993f0d12afd77c19e0bac3683cbcb3b3960b8ea9aed48fcb5bd637d57c5fd460fe8b342e284ceb5218cc10e98519faaf02dadc3e41ff9bcdcd00f477c9a5b452ce1e34ea293d2b5a56079a1594ce7ff04fd2c698e605d08349ccf71e42d9e687a5804e6c876ada277a26b9d066edec8e955a5fd3c4acf07cead276f8718fcdc02fe8d36e3f95cea519e49e79b51f482a90711efc395329dbe732cc5a928e41afa4078f06da2805eb3c332f60996a73a072a0b310470697bc1574a825aeaf61e9808eddd2e75e0aac2aa134ff8d0d2c3afbae31ae84961cc428b01761ee1ebb816eec18858745abe5e5b4948c8f74b5c9bb89f332d3cad4c18d3aa52910a950f12984ff6cb62cf59bc770c9bcba08298c5df24da41dbdc4458a7fa8dea4ce8681d5cd2acc71f4c49970d4f5c273c04cf1a036a556892bd0114a7f77f83cf65216be1f73d143899b1dc9fd5e045675a333523e0da0d3de100720daaf6feaae0f1222cda5c14093b1b75432a12d96e90688b6e695d0d574c57be63cb2a758fb542464b74c5e0f4faf606ea0c0d61faed17f5b83f99b45b19bb286f78e65df98ddd8f78dc72fab78b548200dce2be3816af327119666d7b22a7c94f30124bb9060ad65b778f70f8317dcdad0566edd528a2656ecdd3158a34c35839f3dbe89420ab7c1c3dc053e2bd0798b871dc9f136edfe7147972c830260c1130bc68d4fe7364154a6e4b2baa1c20be54a7c42d77e400a1868e52f560a8fc5a3bf4d1f3e108c5c0ca533e83ddf2ae752acbf7a14baa1c58f8fe7970ffd30732c6ba0994b835a23279dfebdc841f221891c84586530a1dc8b23e91a775f2a72a4f0cad1906c2894e9afb2408474a3912d8407c84e787390dee27e0170cd004cfe1e49e49f49f6fa665f7f25b5767a13234e2ab57326b80ddf9062398da624fc2cbcebf3a42e2fa6eadb2f475f659eb0e8e606c96b65951b44cbe4f2ec4acaed717857b54f8f0fc95bb0b9ac5277f5c5b1aa1aa53b0f5b818e6cb3d3c97e95bd2ad849fa7e71dca5c5e131fa305b74c751a75f20562bac478988586780029b5993dfdd8acd06b99467c6e09ceba1d892c420d42fc566af950f762b9679c2a4623f24ef9021b9494b72237868bfa5a2c1bd6c186826ad9fff3ebe6f9da60f9355c04ff150201d58e4bef2e76b2737ab9ac2d0ed15199f28defff61c3a96abef88facc9dde54ac057bf3b223102120081500246efb83ddd7e7a3d784f94801c470c342da7f2fa8436f69a5431b31c3fbe44a7b2e1c08231e2104fcb02840540f36c92c629bd7629460513196a2de54d13344c8192713fe0e919b326ea8b60a592b108bfc859a9d3bd9a844add5e05e56141abf1e8b2c0911608957a475a6851c514b983bc21971e665b869be4ae96529ec456892d73b135a4eb0850861ac79010419d6d47ae68f35383f7e052bc7b85604f6e285ef035caa41b8bba221017f2d167097d158fd96b899515bf7ae8139f347e8b1d59412d026e5da8af945c73e02a3bf59f254bf50783ede751239d279ef1b1d706b50154e653b5adb192317eae76f498123594ccb21893195f8e076c6342752e019c604adaaad73181324e635a680fe6459845c5e0ff9a935cf23132d503f08a3b09de6ee02e8d7e6b9ecf360829031c3c62c3e8a2e8aeb065030c58a02e09d9bef039e6b90bb3fe666aec37f7e3df0f658042290a2a109e33ddba696e11497305563a56a02a8bd97ef711fac8525197bbf2a6b8e300bb753eec6a5945f8e1de27538472b1037ebc67038b0a6d1dc8ae2ae91ee67367ded2ad484536a7ab9d08afc546566958dcd91bf1a57fe434bfe3a48fb6c7300d02b4c7838aa161cb21139327f3a8e1ed484d9aa0ae7649593c4cd9c3955027a6e97af46394026233b8bdb3a006dc013ef287349876e4b0c92537a62a3e437b8e367058d700c7e235d418d57ec7b4ef2db70c8a43da25c5cb7f7d485f3a7fdff61ff69cafcbfee4212c2e9f9fc4d9eb576977b463c9c670b2a474a2edaa5145299e9779a73ab28822e3310145fd170cc9768aa5a4f61f3c8e2d650ff408aa6da1f6d0b6f5bf45b922ca4d25360a1c349996ee4bbd15e2f1e81915c107c6373277046b22e78b4578344486ca6d7d899b6e4bc67ab3934b2ec4ce25b78c5be4bc956ad5517e39ad8efe956c923cd96e3961e144a544756e6c1e40b020948245fb09372e81bd75d49fe10f63a5aef01546a655887185010f4256d00cf43a8a522ab860d669df0f41ec08e4827cc4b8ce766dcf815c04ea825a3601c9720d70f478746e753f3f31c81bc3a179563bddb30e5e31f77a48a283f4090e877c0d5ce72c35ec27ec852450d5badf0b22f899ead4b59f1438a98da203721ffa08de3d148340d19e95b0ccefb1c189ebad66d0e1d69aa5e5991eb0940e191740b585dde12b6ce3633355ec970deee6146743aacc0a5da65a60a8123c5ceb01224919bf15f41619a8bc458c0b87e4269fcebc9483a3f6498d545b21606389c525b88cef581aad2d4baf296902acf1c10becee87b587674e98f98835a68d8cda0a213198d78764f795fe2ff815cf9848720e3e88e2e6a5df8486a37e730b9f86466b0f5f31a2a363d46563e4dbbacb6cd39761265cac04ea57b84b9a8735d5bbccd360bd4182aaf98af67405ff3e7caf3df7dad1ae332ce664a33a9d3fae27ea04b310054cd34cdbf2331b8bf53409c94da863c9c7121fdb28fe510b57ea9dcfa972f09a5b71b7292cc04c6a48c7a54ec46df0fad70027b2f62028017ea013e6bde3a7e4f7e7207f9f133b25134250dadaae6cc5e301c58a307c388cfab2a5d08a74dd48e64c0bd60fc5b24f46c7145575c9921b72e35562e018289a0299102cc1328de26f27c2521a2aa7c709b487898ff3748a8661d769b3896024a29f8c828f5cbc07a819d418252867fd616ff125cdb7fbb6a0ee653058c0fb7c919c4790aae342bd1a91b6ecec2f13911f2469c961d1c6f57489f004fdb070506b46d07105d8468b195dc51e3a1004db5b8af0b167ed94f399d17ee14728a23ba27364851f774b275eed77b4c2d0f81cfa9844f3530c059b86f58ef0405c18b46e91b0db405c3b67346e6418f3b979105195c6701bb99f841755f843b00d0f46e5cc7bf876f0fa589a13adac7658d03bc2bcf6cedbb30c601b18aec3a638b5bd8b0491d0cb5eb412b58fe7b2eb0b1bd9d55d85683553dc3ef770a550c6d6b5e5c235195bb2a5512ab111b772fa8ac8c79ae4822508d6eef9ad19b81bc5aafb95ec944a5a8b9e2492d1af8f338a3f0960ec9be41de1e7dcb943cffaea3fac4157bbacf6c9bb23cec5c3706e5385a62946ebb8345a515a1f6b4841bcbde37744c419a3cfa5e3271372a07a33f14ce042a0e482a193ed08e065c47321e11a6303154d08fa05e2c9970fd4a702158e23db4491c4de5741180db140bccf433996d583aafef8f8b98b190262a132bb1623d7208d6f1772869cbf8ce8c5499ebb5612dc5a7941b0b097bcccd0fa64f6f7f63846111318a400c5a1559049006cdaa67fcb4e780ee3aed9e1f0dcd86cbf702507e20341c419c649514f1a05dced98bb1055fe18d359179c8704db1c0afff50020138c4e64a839dded7fbc139fb817d42705d498803a60e06db13eac8ecb4105313edbb4e4310c1641054ca11ed07f10a5cdd05fadf0215a3af4cfc7e71fbcfffd372a2585a18631a10928f223f522a7486fac5a868674485ac1dc95a2bc0693d8423676725d452f04032b545226367f339a7c1d9166c20c5e5c50e60108d6246e4afc16393136ca2c30b89aff28a460ddbfdbfecda14f96733966c6bc50d6c8317c52111fafaa1ffd97d06ec04711dc245db5d558baaf06987a9a1ab70e69cf385cb4d9d2f4c908b73c93a3e62ba715185e8f4097018589a2d35060d08063f3cd00f0b3263b046e713d5994648d48a978fbb81b2642b8938e424e48c0515d29810e2bf5089d8d3b672777273d4b38c7248480e6a56734b7b05dd178bd7c482f22ca7dd407d34b063d6c19c527eb1212fdfa19a0dcbb88a1ccb1e8ae6d5f683e24e95c354d8c05984074429d4265ca447be33cf8a43dabce0b4933cfea6692da4a4aed9d3689adac19753c9c91acdb1518ec8edd1f2336880cb2763fa16b45287e4038fb55415510a7a00036cf093cc227793f4dd877b2a4b91f14e94c2cd7b7a8b4ec778e5cfdd465df5a8c14f7bae44373f172a9f71c53e77b81709499aee5f7d6b0989f491718324b2b4f032ce8e5d501d189c0dada6ff65dcd5069a1fb01fba6fff1eb033458c5cbe09698a092119136f877287ddc24f3260641a5837bc15e91791130461597a060933ca1d121c9e12ba5473e7103c2495c02bf4f2f9b9c0a2a32782b14dad202707bb9e8b9f6fd11d435ec0c5c6c56a4ab11352ca8a53c50a11955d957067e670c227745b4133b279a02a8e8305308be93ae7ba33720a0296da40736c39cd89bfe9d7da9eb4be2a7b88184cad21d178a454dd03d976606a63ec86dfb1562524e42997ce7bee483050a9be4a41390a028e2419f995cadb1ba614aad13e91f703cda637f8648dd50e241f2fe2d43465cffc1a0c3f2d8ae2176368799d31e6023ff4a6a972773bcd95619b64f3e3b01d419e4c2a7d2cf3ad4e40d4c73559de65753c430d0f79115c08937b7cc008b402c1e5fca2d46e14c69bd2e701184a1400abdd9fba970d09533e2441e09dd2db520b674bf73c34a186ad894615b9f7c2e7b319d9e902ed09a242b90d08face02af9140a2a400ba18454f16037e2b219da9ebda8a082ab5f868b5e68a0fb61e03eefba500589c25df02579130b00977c6265d6f34f6866970a2ba0d77017557462d3b52af211382cfc99e7c3f00e635bfcdda31899d3da77558f8a9b678876e730033d2f28b4e159bb6d43197e9e3591f2db8a01de6a1bbf2a4b10e15cf8dfc89670eed31f4e058353d42391f6021c8f0734e07b96680488a22034a4b13f785a49ba1a79ec870099f299bd2804a6ada219d610db82c0c4b2b01350e1a5da9088007b12fbff26c682081edee105f433311267c17299dca9d91b8d0ef43aa15113a03c29aa7dface35d5265563091ec0509066ea66eb6790af310b8fcdb0eba93c096611e27b7cfb1cfa58fe64f89900b0eaf5e1f7febabf8eb7c125a85a009ae2feb306827250940d41a40445fca893fc834c20105be572f3f05be522a2eeb7cae5622fb92186fd41166b07d1cad01c39780f183362e5d070faa4a468116b6c90de591a9c9fa339a2a7c4ec9177f7c837787bd6d29a11d9bbce476c38d4f13b88d02a54d614412e861e144fc4d9f6cb9180c2809ff478da07977bbcda46432f968fbf6e2a04b7530b5120eb797aa3abe6bff478e655937b1f8a4fa242bb6dd582a3475079075192bfac4dba4d201631978ade0588236a41cc2b426abba90b8aee2eeef36a3ed6fbb9dcafe57c2ef6a30ac034024e3bbc7d66c0c27f636d97eec19549017fbaee884422d6be51e37680534def0a0fc0d833e61fb8b39b37326161463ad692b228b032230d0b4c960669207197b34c612ac2dfbd7662613113a68771350b3bd5261cf8bb1d962a7fbf26133f4512d3c9c5aecb06664371a94a2b41105d0822425b855e7e281b4c21c0a566bce137d5936c45a36ed8f1bb010bdfa773ae558711e9d5009bd61f36922ddeb2f559b055c0946895614bc3a8656dba921979a38c1f74f5a61127aac67b4328d76ccbd93b8766ee2af19a3e0aa9e341ce6e0716904a8ed29978503c118908c345975acb09e837267e267b4231f1dfd2ce84715429722085410a1c688f6f9d01c1682252f66c5bfcbd7b12bb923529139122bd10f03986a551361b9bc986a392abe3c10bd8cf3b4ff8780347001db8b27b3ef66d4c0b1ca5eaf657c28f2bdb387c02143814d0ef9149b5c1a6ef7c38fb5027d8f19272190bb547038ee3fab085a49e3b100b8b4c192bed5689477b023244e803302d53d1db013236adf49a700cb0a7d5c40cb4fcfd38d9db43397d90367dff7c0f8b883750ede38a638ff0563be984de9b173fc509dab60d8b183f918883bdec830e640a3ae2db1d2f9255d5bd807c0617aab76871a668fe128c4d3b666a8a06ff75b080dc352d10fd7dd8423febf36b67609f0317e7a76ca22ff5ad0545fb591257230f38879352ece6ceeb6ede3d8513f8b857e0cb9d9f2ec13d915033cc2c1bbd65b8a1d2e358926ed10596aad2e8a5a705b1ba411fb9583b3873b4bb01caaa4c640b811f8468dc163b8ff97c2be92e0013f71d4b3b59b41129676344375b84ef60009ee2e68e1e39ed4e7c59cfc3ca70499bb84fed04a7816894b117446580b71bc9b215c0161b8df824b2f4dd6c5ca2ae43df3e23dd23bde3a662884f5b9e921e0e50c117b84371c97b31892c8ef7db4dc30af9e616baed9ca50564150e7e76aa377422fa0ecdd73a0aed21a95efa5e9d787ddd84aecad52d98fdba4dbf15062b1af1d26d9a2ab47cab0ec39c9acfd8512f2156f385895baef4303c8f257307f73fd984c6d1da39e2b612f03bc70ab793bfda2d9905121931ab8c37492ca920e03873105fcd20c978352f9c968e2ebc1b080b0a94d0df7306b9a9ec420a4978f1aa8049eb9de22a76d10dde0c63057690ff0498797417077bbfa447ff75ff2dd9af2c98b298e823bce671ac1e6904e20be58b812d351f14d6e903e10f9fe1f9fba3938e1e968555b43a946cca3a47cc5d62a5bc2b765dc787cfd3c911f3e0577365f8c359ab0c5d65d44eb9e3501f6d182540e8df4ec03748501b3dc8cfd403d91b6fd8a8a0967372fe389ee1714c8791f363cf355ef12e97e3e489fb878538ea0fefbd0210bb1bde42e9c474ff948b500159c49425150f06480149c912d0d2d3c4e93f7b0279cd528013446a88ef67ee713e963a88abcef22ab2c44e2c9e01b0366f032e98e689f633ab626bd3c856ceba5f4b1e17b1f78ac0da646842da396d032b71ee4e7b21cdc3bc57b8b947767c3df662c64e883a0c9e34d7d49bfa930d5ae2c3116f5e405d927516b368476b708750561fffec0653cecb17a9a03989c115d0e67487807117a0bc4844072fa707b04a4c031f30b72f99fdc294d389a909d719186a7b2e3ba5d2f359a1c5520a84b084a040dcd081cef57ac0726026a1360a3828eece8416bb7cfab3f5030dd8a3caa19bc44fc7fc8a58d9e890920790ef681dac46c989155bc7b5740359e0a1129a18b6c00042cb5fcc483d9dfc3077566d7d829dd70a1bf2b717cf02eb4efb7e308da2a86fff33e81dce59c15402e4dad44d76b26a4b84b74b00f708b4fd6d0059055880f7fbe585a65963bb082cc49f58909de2724fe63fa4251814fffbe8f7eb09b60216fab7dfaefd0e2e5716ff663545c06515aedd60e8c98110835e4c87525c27b8f4c5203c1cb13c55fb50e3aac8c8d2ea052166347fcf147fd65eb0f9708af73158ff873a25585960d9b873034db1511b2b62b53800978b074bc8d03086b62ce0b467e5150c382e992b948615c5eaa36b4b6c5a932c1c49d1d579e2eb13ed76c29ff6d561b1a7db011fc48f7adfdbc0b4a1b89267462cd296bb98c7c907987236e66c1fc91652def72cd7606bd1e613a37414d4d87562846dfe792d4ef3302baa54176461d09ca053fb07a4e8f796d1e9a90fca8e71b2ee83c0608ec220325148ba0c2fbcba09ba5d760d112c35f99aa7b0b7b01765a31a85693b15e1436461aa4b13fbfc3336c9fd16aa802c60229368dc4aee3fd9ec1c5c698cc6c0185e43c4304177c2b97148065d82ffb5f1e002581e063ac5743e8860ff0b1d1e70a1e675031c05878ee5809441a96bfed41bdac670fe8218134440b75e7e0ae56ef9b040e01617d779fd5fda713022c705ca3c4609c9cfb48833840e16e472ee6254366d7e4aacc3a4b97aeb67a2fcf4cc9e9ab9bb1d6a9010c3c9572be5cc10f5e44b85703c8dae00bb8a01480f53d16717bea107207d40b9d2103f80e847c3f81399029e8f25e7c13291136361d479cda486214c74b784f2b5894e5366f9393f3dbe72b3238b54355be618684214be869700bcda26115a498589f7b480574d31e6066d1c2572665e37ace61a029da071c890643ba1ea6dc63cbaf4813f0c9ea8b28a99523f32755d887e99de9bdbb254efcd6d2ba061dc48f0a3303d1cd48056998e2428244cc4d799410112f7293da1fb210b8d7be3b55cc2bb442ac92c687ce17bcd2c14e57c35d471aae6d097dc5db7b9af0c76c9cd597f07776459f2900a583e3a0a16400aced53f274eb9284c56df05bce380027ca123e7767cafcce691d7acffede20a3ff03fa34daa543ffbc1e86b4e59e02f5e4fd7bd26ed17c6ff7009b9e4213210e5bf130cf7c50097996e7bea9588ca95ff6346af8da7344007c13fa5574afcfc355d4f88ae5a264b6d10ffc13e8573e8ce9be529bced5c88fb7fbc812a3c31284055b208f234333b17c3a8c5371c75be8ef4687ea72f5942f42268bcafbc62e0ce4b3f4fe48886a28dab685e83ea043b4ebcbdc46cf522f0be6ae70ce94c582540b53bd7152befb20ac5b102f77f6f7b334b5b85e909c6a2fffd49a540b5259d2ae18ee6060c90f8ba6db815dadeb3d20bfae7ed9c94afb478285252ab9b780e42b4d8bdf98b6a38d924d74ead58540c9b0361beabe3a6ecb7b9ac22da5e66505031d56263c75ad80e5963969d8e564cea81f28f0bb70ad127cff10114e667d2ff8487dbff805d7000c222dbdc7eddbd5dde4dd7aa49b105359ef52a3783550330972670ca8486cd3b53d7bc67a6a45c6bba66c52171013947054678f04e67c73ac11cb6c9a64354021b4e6ea494171c59f624f547c0cc822eb61cf8d67b4a1350b86111309f4194774571ac84d78576491b9d574c348d263acdf5469809b60c08e5e53ddfa802512c5de25511274bdf219944bfc99217f46750d65fa5d3c3cee0cd4f6d92d3c010aa339cad7c993afdbfcb24c2e2206c799f9e59c54b1079def1c341afd3168a93f02166f7477973864b5cc3d43dcf1782ad04ad85ad05ac84edba05cce8809e4100fdb401ea95443f875e93354b91bff59aebd515e9b2a3f15a4b01c407fa8bae58b05a8a40fef39eef889aceddf2cd36938fab26b775c2e2d172e2f61f1851d4505a43df1bf984fb2809f1fe829f50b41c02eddb96589346a0d5ce7a1762665b7fee4bf8c9e29d2f01f9215a80091b6f8c075764103b6f7b9a79ee8e2638196edc376e9da11d8dc96b6b4f6a38e564779124a72a75749b5d4904a792d7c180a1078924eb827c3e48b779f3183e5113da21a3d60253b6e098f42d49e52e4d3ef42e006ee2abee2c99b02c6865ee250cdfcbcdccb53b580ab155af0400716930044a05c20774a0190b8605941b60da0002e92df9d7eeaad3af6a03bf46d5434565879ceee8a2fec21c270e439dc09488ba9b41163ab262e23142d8ced8a146400938cff442b2176533aa325a2ae016a0396f7e1d24771ab89abf046d655e11ca8fcc9a70b9afdbc6d14209f05fb8f0d5c63e57a3fe2b6a4759b10df87aa4a1b7111bfae314fb3ca18f5dfe32e8b99626adacf8fcc3e732bd1fdd328dee658b52dc234cc041c87d1252c2db1cb7f47a0a162bebc7c53cc5afe67d58ab5fcc2da1dccca7686aac90094f47d02a41b988ff40d1a6157f28a03d5688dd8b21963201e6c21a5043affb315382dbda8ba951abad4175112cfa42c26a28f7ad258fde7d1f112013aa41d45bfc63225c2460dd52fb9e7af718090565e873167e76b182275e2514e461d24fd3326464d0e12e1f632b961cac7a9f8d1b5872b6bfdfe4052c1ec25968bcb2df9fa9052f228cc7eacd629962bb7e1a202cebca74eb558ac3cabab7208d30d895d58c8c2b4574316e4efe23ce8bb98c6b5f7dc7089af014b82690386a7c17bd08e822fb2e7ef0ba93752c70f919b070f2a33d7def346b1a00285c830640e5294eea0a2d0596abbc7bcb9f5d09fd749c775f061221c6f253c5d308f5cbe780a7d16136c1bd98b88eafad8c1e16de60f3bbaaf1c13d65c9d65287a5dc3871115a1f7c94491eacf69faf5f5efbef57595adf255b8f88da53510f7d7135d7c13fc35050f53f2823edf5fad792afb009394c26a4fb11933e814135c0c6f02106c50f01090711bc0ecb9018b2ed5f1ee3ce6163bccda6fe86b04031d6da442c2c9b25d870ff97248c2e8810c73d5899185731ff0239ee197f1c084d1a61c81a2ac5b1118db3275338d6d3a29f90022fde06c16c1831f557c1dd329c9989f4b92f72f26b2d04af1c0fa0ae53132876400f1bff9c600414b8b40dd52195b17c891d5713bc1fdc70fcad7c17c857d9dab3710a539f0817b6f0c90eb27231f207861a7d168af6f40f21748036d3143abdf523f40a4cdeeb62fce38e3364053a6bdb1633c00d80bfb1741f883598d0230fe32d0f48b5a1b651db42d2889f5e80b9777be3a88d8065db456f493d174677c0bf4639495ed6192189ea40035fee7d27dd0b831c07a95a6547db741aedfc783684f8ff9cdd566a4d195291c097538e119d4b187e0a5d287348230b437b10b7d3ff6de5ac8a1f8e936a1774374ca3348e0704bf93d3f4a148f64a104abbf472cd88ec520c401185f61e8b0ac3a23e68e761b0b2064ca9413e738b33a902ffb749c7ee8b1df8218226852e144ca4304cd6d44a500a3b52b799aa079566f8328f7a22ab96fde563eb55168d7a16a92224aa8846f4edc503059b6ecdd0f3d3da46e4cb3b5e9eb0db9724e7159c69a64654b4e451a586965458eccee162ba9e7afac2d78d77a001e7d8b5c3701dd61e2c06a98612724fdc663f471bd456087282f3d2167c4c2a41f6e277d1c2733acf571b8e771b78836e6e28cee1fc530bad03173f6d35a2b271a4d9dbdf648e0d97f4d8ba36c0a41406e8314c688f2f8bb89a9fcd05f943a4d25cfb46361fb7e7fc66e9adae07dc6ea58fe4fb06b1e394e4ee37c960a9593acf8a1bee5c06ae0b851c8f500c541bb80417fcd90b8ec8e769b7872e9c80e1d12e2125823d186b483605f33d0879386e2dae39b4afc6c4bea7fe0b165f3cecb86cf65b3fc3dc4195de9457707942ec6c9999cf1272451080d57294943560b2bc29aec1dd223a5a93b30fcb7f6660df4d42828579c207a0e1aea2101824a30d9385836c9582836cd39d71c7e16ddf3dd945551ac1bf896332b28d93b1fb581a21f9dcc07c9c84acc063016404783ae68933f2921483a8211c3843b2788184e5f490b7244cee9da271a95cf4c84a58a35afca6642c0bfcae9cad71cf6cff0e02799fc6bdb4bcd32c0ce822071744902e8ec1cda291b078e58afed7481ac3728c293de23aa97d781dd383b382f672bc5c6ffcc3324226a1b9dba5cb86e610b66653f5efce46f6fd5aec45e871e762253b3299dbe6a942d827f9217dbcf4760ac8c5baace493fcb326b55f43f04cc2d044892530ea8b5bffeab3beecbb610000ccb190974c9644026872826ac0227a14549ec3484231310b6cb38a06f410b826c10bead5c4b8d0f7deeb04d6dd8027296813bd61740c6ad8cebf66087e84aae08903e24b45f56cd02eae0b90dcebd979cb6d4cbd70f907fa25eb664e08d6d8bb72d79564b96e92d3d782472785adcdc400b615bf0c5f996508513fa138b8c0e17fe38f093c7850a912f3c7b133b70e9e0c8d19992fd2f4b53853232a4a8c0ed3d32bd4cdd6f40b3a20b30decf2d92c52b21769bb4a6e84efe86813781bf69664d2ca6296bec113f10c21778849e08fe7ef23ede7491cdd8ec13d1fd673748d475870dfbed73cfb6d7e999e0ce1ca533801dea80ce8feacb1a390639c842aa3c2313e9569f255a4f8bca1955971e825e06eabc62d0464ebbcd0e18f4eb95619c53a47032d9b4352c282fe250199bbffc4a27c4017329b6da0276460c946654890362c0cdd9dfc72af3acc7958ab5478ed3c6a7c3abe5d9a9a38341d9c92d706aab2511ab10b632ef74aaaf4c0b51795aa9fac34c1d49636693cb6b45c468150bd89f886de9766f39de464261c7d0c7427ce9410ff8d758e4f9b114e185ce70aa33b399d6822b6ea80f97b8ba2535e0934fc6fbda14780ed9ba7cde22a4ce4fefb5e17fda53bd1023ac32890d2e614268d81950db97a6b623b768871f654b2f0091ae616aff4363c4c478bfea654c336f410b143c6fdbc547b753dde07bef8e229e395195a5e1a2765cbdfd470b0856cc1f31892d9e252e7be6c70be83939384cdb2f15c4386b83dbee4fbe54b55122e0d0f4033feb3b5e1dd4850307f48af37f8a16e60414367ef80028af42ed840c477f2f9f571002f637ed673fbcac4323f338c40249d2ece3ebeb4de395171211cf2f7c84c226af6b98030dde3c7ce9b4f2ca1a5dab094b053f56cf39292867ceaff7412027b63ad0a565dcdf107191538331abc0eb2bf4a1988a6cc59dda24cc22ad14abdfea00ff5fe4a700442ec0e7f9aa4caf0c1db81cd7c579cae45d2b66970e2d35b4fd14d0eeed53550e476cc369c8dcac89b76a7497e81b950a2708d4147dac0112a84794957fbe2cba700c6eefe7d1ca24306ee67e5773a11e14867c20dd5c378906ec9bea5f359da27307b46598acd01af2a0cb152e97dbb7042694a1ea2de6096f41e2d670ec0236fadc142d15227ea0052e241e9f1339808a52ae8e055a1e407fd2bd4dbc29a78c3dbafa916b5094070b237d270923f6ac4c9a491244eb3933d60448e047e7d90f596ffbbf1b13e23081c05763c72a81eee1fa85daca139fb4133ca184ebcd676c432265f741543081a1f7896fc9f75e1fb7a0f6d617e413b14a0b4ae7d7524d94a69aab8b8e508e8608510c5008dde4aa041a5ab1f98bed698b180ee0b717b336238dea1aebffa21b111880f3e1c42b99b7de44a23f1f8258143566f5e046f6d8eb98075241fe82b29b68be44290d8768f7d7e7d0a1046958a0f621eb1410597268e6fbf6710136624af403bae5cdbf2cf8703108323f0683b6f6def6fa0af9c41ff2fadc68d8b59dbb6adf7ce5e5b0f94f1b813207072a5b277d3c0829b5c826303ab4cf9f8ae028d8a945e645f9645ca6fd7cd26bf49e6d806d5d5f7d855f89ba561e45c532bf7ad813c55e0785c65ed5c71df42ba9c8cfecb0ecbfb30329c15e0e7c7c426758d87bc53e53140c01568e8c9afd34a8abe718a270e4c89edfef6345d19dc5624a815a05a25a18e034eb06f02906854601ba21afaf6665a8b2d8367251308dd1360297615fab28de63a29915953ed1f5a4e8aed90ec790eb77cdd3c07a24f6bd96b0b9cc5390cbdfbd0cf570e8d13d78218a3a0d8a2c6f82135c5bd0751a07fc53c6a847a63a6f99862a009c3fd554543741674874d29b745eb4332766c40dc35f105c65e48e3f406ca99711538106a038adf926bff1c9bdfb422448c6a0c4721a3c6bdf435aa0523368922d0228ffaba5c1145a7d2207b0d48d879546af257fb7c5527c81fc6e362571d729ff342958654242164f1e134ce84521d70dcc4409eb930903a7b7d3c6d41e991b304b94e3279166329489007a875f2307c32f0c5737aa8a7bf8debf70230ffc6f6bb55c4f0f36f24de3eeadf6ad28e9338c5a44ffed0280e663f3ed6d9934556a2758a377f7521b6d5aa3769c82d25a6ad3df3d3c315abc8fce282a3388457a5ed4273259f6d01f9382e2bac9be8734825265108838c4a74bda1cf776a752031782aa9447d22d89594e78723a51485e58895eb20657fd2dd75be43072cf7c2aec70660df0df6f1d9a3ac1ad915a166e48c0b9306228bfac3e2c09b5c7fabd099c04621bbe959d916bbf520bcbfbde60ce1237d64105ea2ebaec6b9213b5b5f2b54911a5b38720ab38ddb87bb9fc7c7a0ff1dd98a5d3ca196c068349c1cc3c748885f1b52f845627deb50d3ee534717e8047f3cc915960c857c163bb1e56c2b57f2f19bc8d4f2e247439074dd05eeba0ed1a3c079c4a0f8e8d3d924a861652889b236676378ce41a49b43b7a7a2aa47533cef13b287282d99471eb7fc1e31535ef604d7e08d714664743fb468c994a45f4d493a45b2db8bfe186d4ab26d740662440a2781fda295f3a82ede41aa0d8af0da4525a7926860434190648f6f6a8f543b903761a8789bda9a296a7ed62b5df250eacebecec852295e035a0e18b92a1e2f99c4033720dba92fda2c41cc5c50767b76068424fe79de4385c407adbd1c9dce9f9ef1e6749ff7c8b09c0855a3c8050007f38807debf2b2aa4f0aeef7404d37cf729fb246bd68f90a199b56ee8130d2700c3a6a644dd5a8de57e8744185db3ae6d82711a437fdbc804609ebca3c4d692872d0067805630ec9404cda9c132e3e6f8e2ccceea0c1be256578938e24d222e3987c000f868551b4c1e2c2177e7d6cfe0e1b5cb2a838717f6851deef5f56e6a1de6a3f92d0a79e19dde583d9e40c31351626a3ea3632fb2100ee22056f878a549158e98a14f7b23c4c28e59a8c2174041e80fe5089347b33a327eca418f3e4945c540b0dac1bc891b281fd711840b23a02223fe340d32a4c34397685788888ee5b2652d20329285fdec538e2f81630dee3aec359d4af884ae13d5d55be53fc6e10180f73ef2ff7b3760fa50bce8f43006d7876ca9ce552f55d006b77739a9c8a1a68be04bf721abcad3d4486d6489c0def7d210b56d56125b47f8c633a628c2ce122f58732d18aa104273f7145d32ae1e903eb7383c435125bbec90b7053a11c2b2e883b20a6d094a815b0a05fd4b85a3388eee079d395650dbc0392b4faec1c01986ce01576c9ef8bb06302a0d8acc1f5023fa348bf9513a8c497523cdd8f942cc62625639ed4138623a51ada4363914b866091d74a95440809ae32f8f2b05a1082dbd50638e2e7b9e31591e1efd64da8ec35674d8e26d0fcceff909a90069143aa943535f174cd86b4b4184a208e56c0513a2ac7fb33154066333267373b5a0ed92dd727a89df5ea3e967b0c40d827b2db622637271120c08fee7a5096a56473bffba1051fc8297782a67527ca9fa9447de1a05d09cfb4cdd708e94203d928d3be5e7ee0449aa51f66c7ba99c8905d5e8d1bd39bb46c50a024766c0608b05aea36c1b4026b19992606e1d3e281e25378648f529d118e2694a453a9c27bd65506356fb49806cec5eec1838e8eec9d5b27432d14eaf80fc50adc884f911988abc82212c20da8ad604de5d26f552a3763544709cc93129b94c9a697ce1e4819f596bad402146c505c1fe30de1405ab05a65132af332d7a84417078229144a80f3ce670268e736e17c3da6c2d37e6ee85846b274df45ab52d93084d22418e0fcccfd1286a3a1b56187fccf1521b171847bcd52588fec3f968eb56474f78557509be5fe2e946dad75df740295b5cf681b4ba1aa9049ee861f338c5a401aa434903f025369aba2134320de2f923af02f2060dc0930f14a21addbd6cb02d442830287ea345a7600bb519abc54da3358676aa1d9b25b7a1605f95b74d2e6ff403faa43f7fb1054e40aeb9165a938046e15674a938105e7af92e0f7a475fd7c805fdcbd6f49bebc65a45258e879b9d4e706fb368a0e6b76c7ce9cc1c5270c725447eac052bbf457322a0c417eae00022714ff69dcad6d40d2b04e1ae414d56b3b2151e559a000c8abefb7222c4796c108a6087116094c4c066ae945608abe82bfd9869b903c3c47452a8d1becee7b637fc4f40ff24ea64bcbd11dc32600df4a3d4e027c816a743535ea2cc9b5c6a2902bc7d0ef1f6bbd61442485502227b5843eb4873dd5df0ed5ce8e119cd4c308792460754344980132d8c5226b5d8c300bdef308a51678151d1d90c8afb1cb7a10342859d038797ff8901915d8400febe41f7e5a548630495c9243ecab5876b626250f1105a50adc6f22086ad2c6385fa8904e5ac555131673feeeac1be4772df8bb937807b34d53cf0d007a44ab9456004d2b023a5cd326b4259debc86b67aec37abe5078838e5f04b5b2947e0ebb707d27e6d043ab84808ec3b3d5409c463705f87c50fd6e8b8fe56d80429b6d54e0ef7cbf899f625395c83e70d205508fa8a9f85eed2635d6deae5e396887e353aacc52fa024024a9e1db740a70483db7f392e6f941d2a01c70cfc8a0b5e9cd72789de56205cfece5172066a039029ed1ca8a5be3894598f8bbb588a28d3a31ed41152f30faaecab71171b7eeb3158f8db8e7d5b12030de60384d1a5a7a29ef765f270c5378bae1df28e5f7db987280ad724e789723656ed8eae5515c88f99fdc79b1a7ce3260aa7d410bf971c9c2084f872da3d6367f2db6acc1bf9db8319df186cbf3d8aad8d6a04dc9386ee8671a5d8ab8e4bbbbbc7dcca3d41a50c49b48e3f7b759dd57a5ffd24f764241c816958de04928dfd9b25390e8c85aff878be73372019cc128b2af690b42e25c9b9819eb61d9dfb077e1842fa5ff16d06d0769501a4b28a873f43277155442525f6da738f0f06d2580d000af1c0d178264c26d29920f15f10bac76de295345d654747b37f0cf40d3e063ef3a8e96587b285d9d6ec465c553c108c4b1ad27e9339f7885bd84b0313a7f7e99f5bf2f385c42e4f80d203b84ed562791d6a61281c081a48cbe9ef058cc2d222999b9bd70c7d3e0ec6a3fd0334c1283740e7a3f40968c95cbb9f7ccd349e28320341c786544bf8a7aeceeae5047603055d684d959050e84cc9b066086238de8d111b16ad37a97136330025cc4462ed90dfc1e5459932c63ea5279d2d77b9e52d90025f2528a3334a2865795617d4d8cd92768a44648cdd24afbc3feb02c3eb351841113620cc7c319f22027d19396a0d527ef823293b032653ae5481b8e1b9ba2739cc4ab857fae146c1f6b182a7d861e8a033a8e2a98d32ec417a35a2a5a86a32a9878d2a9794daef284d5bec869592ee2f4a734b41a9a18fc0193ca85ec5f56df3038e891aca6b156a534b3aaca16aa839dba424215fdd72d957af640d4d45372cba404311dd6508e34b56b21f9c9acd5167f301229bfc633c9174fa0536167075396316a1a884a21fd1c9c1b2eeeca6288142752239bf20bdc498933d50381c2db1bfe25b6d9963a354ec892dd7039ba830f7e77ca70312ab91946c706c3fcb01b37e385814e9d5f13e1988922d28a893c145e2ee2a7a580a7edaf42e52c52fddf0460f7d7c66f48494d60111f97a6c0b5a8710c66b8c81f3fcd9a756f14a190a5da57bf75731e79e3b44e9bc7b5fb1ea41da4609bfd02bf6bdee85f94bf21c7759ecee00132db148f4a13ad04bd9c743dba91d8841638504fc2f6ac0840812533a20ee0da649719886d4c494e5a4ac1e90f60503dfb83f4723f7af471599e489a2e771af2a750ff746f3f7aab8eee3ffc520d319230facce486362c7dcf0ddf801c7c022ef24587f52160302f8418f8f8dd0f357067b911cd8b2857704ba1c2ef2df7102f1ef27cae5a4bb6469793714bc45c00bb27bdaa5b7af63b3883a8915d35545f36788a30b77d2438098e25dec7ce09a719295d91f185f62cd666746c6a9b17f885a9d1d692b1abc1401f16a023857d13ed2b3015c36540d26b723195fee2899ee0765fd57c100e7c559b59f349d4aa30428968c3dd8a44ef34c85533e419d599beaa97831a954cdcd2bb158ff8380aad5d96ef83d52fb04b1a5dac4ff397b05a062b707ee9a42cab4692c21270a7fb42cac3130706bd18965afdb08a318317162354a6cc5d90321a21f78bfe3a58b8894ed003b521c558da5a0f5e30da0af1f22a72d70519c804c02ec9f663bc9949a78ddaa98d624802f7414ce746fc0e86c7c09e20213e8f34cb7ffed9a474a87971caf09b1698d2a96594186485c73ad342da1cf56de10302e7326f59866be51ddd81afc568af73e5fab5819486acb1d427bf9c34d1a375a46b0e3abc3e8f8eb60ed2662bd106781d2422ef317f3225dfbca0d1e7e15a8f82f4f6205168cfdb1980cd6eeda63bd303efc891559ce5fd0eb22032e17420579c7adb6ed9bec23362fcdbd575451a388896379611ecec598959a4fe47f234a8c1cc36a22f201b5391639a1b861ef3063d6544e5f42bdd3121d601436031c650ad175cb244d0a51c42766b9a496d631330600bac190d9045259f8ca5a27d5e585ce71cf173e5beed56213a3119c8f77f148d17afd80937782351648d28738a15787f79a61e3dc1a4a14d1c078877cb14389d9c2182a9f7d9f663d0a5fbdf9e5be76bd374052134d012ee161bfd60fd41c0fc4a7d3e7b1a754668a3ea2388b68dfb3b2d7c35c48755592f2cee220a626fe8f0f61c956150046aba26b395db4ba119edcbdcbe7e2ef6988452a1c03901b7cdb226073e22438ca4d2eac4a024ef128c44278cd75a4994644e122902733f4b45f91d4af9f9209677a8b3901a03ec78870d0e6809e32eb274a3bbf7dc7682659217d7b8a44fee93940045cce872280ce563db97a313dc21b8648631f09fb981572ef56fd4f59fa7afc804f6739c66af7d20e76a1b752209b8bb3c3ff3d1e96d3ae4ca5506ba2521d02d59c7b251e4bf1be3052834742973263c3b308cf3e4e1246ed4950625eb4de120896fd7066b000861ce720a089fe923018910c2c8df0f3526c54440b109809949315f42e2c1f4c53928f557cdaaf573f67600b3dfe59170197ede831bec55b2d7473dcedaf8aaf67705c1615740a6f80df8655d1e5dacad3799941c1a556f70375ec58072cceb7e824483e9c4d3e6554547f8584952a9dd3208244da0d4a5188621819654f53d581528e3c1fa37ac3a3e5fe3cd24d362d0c2d05c7a19582b471472812ca5c11cc2f34a0b2e254f342834f92e897ab144663e47d44c93ff9520d2743da3dc651b8da26e3902fa9c9332c15b2a165be0658306e7226e6d664dfd539eaa1130e34fa9321a0f8e0bb4384a264f386763ec2c6325358568823993acd6ae24e05acabf6e08fc8a6c3b2097ed3f4968f8637b3d689ab9c4e7c736283d980e52df2e0e47ab99332df0cf2eaa90d8336e9f64364a35e5f33ce7667c69b9d3b95e0340b95516fc248200083b0df00af5e95713a0b0772e7619af0c2748b9d1555284e10e4c8a506ff17f15435f5ffabe61d14166624a4364a69c85bec77a95fc76b1a4595991e846007fdbab471bfc37d6ca8351d1e790d478d9636cb0fd910f7c46b3d020e8380c9d1a7d4f33a189c289bc37ee2e74816c0c3996d3f0a9aa6485c4379eb60986b8db0563048f1b8ced2b9e73e1143ebf8bfafaf839e2d8ac0c451dc2d8bc10b3d2276229eadbfff96efd3dc4b50ddbfd8fd2a7b5f4af773e1fd8a7aa568eb6fd10b1adfeb20c4aa43cf0b0e52546088b21b9b34f26d8dc8389128795024db4d1898cd6ac7bb504ff504350b371eb1f1ee882363b0597efb8eb5e84c41e4b672192362a9721d2e330d7bef767a3bbc58ab3bd00f486cbdbd59802078b00a1a47e045b37fe1f790b6b2d51c57b61712afc11be425821f5b419bc42304b834f69c49d38c5e3ef0289670a5156c3e666fc9c13b27710c1b16b1ee8abdaf490478faa94f4744d23eaac09a3d03baf3990db4046d33af8a1570444868ccee44ffd47a20d4785c57979d86865313c5bbe17aa318f1e88635db850ae46d6ecd4ebe6d8b7d2f36b1c72a629ad17674c62d4920c1115515dc5507788b3d474efdde2f7ce741345550ab989d4d4329986cb92a819164c6257c8ef5ef68ddd43fbe7766e7471e9c25027f932ea33fdacce7b9a879d3713e54bd4659ca0e2e891bbb0e1466d5dce9d15c91e1067f0a3c743ea674dab651601cfb4a90db32523cdaf279e4e4ec025353a23cf84255de4a132a773e4c82b6960f61f3fa13ded8e5f30beae4e76c9c5a726a553b148938b42840e8f607c983f8563be9a229538406e540d4e393857b08dd5107bf0aaca404964e84de7aa5b033d652d16b7629e93e98f2c2be9aae7ec6dcf59187cf3da992b97068cc02402f7309326f51c001b41e805cc95ab6ad2ae7e62a683a5acbc8ecda8fb2c9aada6b470e013122ace30a2c8b4b6f3d506de17bcfeea0e07e587ae9fb7eba07f927c85c903c8c9529b4a99c8e842ccb52908f26730c5730d904569d4b91db28d43b8140ac420dbe4c49fc0722acb11842bcc1f3188fdc1420cd9c17cefddfb5d94ed039d6f351cd32c2cb2d29c06026d84272f03876c65dc97239e33e327069673a09909d205be59eb74f140272e0ef6b609149f0de21b01141206fa08a0fbcffd4b92c05230210ff67b618eefa0c52c125bb6cc765ba3fc81961a0bdb98670741c802c334f4bfadc265c436167c1d364364d2a38e7349d4078b203c200d3c9c7f88fe957e968f0ebcab6f36caf9435a72147bc7c74e76a37949ce8f4b47a9ccda1590f02ac2d2aeeba14d37182746890c805dc69426811482f5c4b776fc6185d6b038c644b5555783bbff620b020d93e539b8e5dec5fec7807ca97d97c79f0de0856a1bf2f32eed7d674d9b63abb4cb339f5474afa2083d27d9f3a2dc3bfcc97b30154d0c5133206bb1d5caf0fdb0c036ca4bd5e747a5715ca8b52e03121ac337e959348fa5670ba9b2f41169033ee0da905eae7be9ee20280457cad3e844f1e45bd4e21303e21f9e16e245ad209898d2a0afebc9f5c8c14955688fbf9121162860026e90a72835eec1a2be627a66927a73c44e1316ee493489673cc375a59eae3695c830171cadb6e903723e780dca42b37b8f13aec1377d225aec748bbf5394c891921df1c2306feacdc3de693092216a157043a325db9c0f3523d5fe43c54292158961393127c72feafb000410e0e0cf5a57b3f43dcdb70bc837c173c4c225c0e9ba2b41ce15d1bd89da032831a85581af71bbd30d3e3c7890b363697c6d7b3538338a6d3863c59cfa54c997139a15067aa787aef79c037f64b74540a7f484335479143ef4fb8b2de0ea4aaa185f9e6fb968a19b5241b318860e88a7e2f1c493578e768f2613212739fb4fb8c6b50d871000bc2891e0b8fa83a2aded132a4f5ad410c1c7d69cc3fcd7c1230ab1a8cbd08af151fa211d45717150a6f4ceacbb3082c9c469fc782670e28f72c3bb4cc20e458b756fcf565156be260c8da5b5ef285dfc1e7142d74cff604c3c13cdcb8570133c7454f95eb9b54be887e9b2ade0ce0aa953cefc0fdbdf567f8485184f6a7b9ac4f8c31e5fd95426a966118471c4a2cca20f7fe7a0c3238862452291e6f4036b9241ec13bb09b9e22ac3a7b92976e763559bf1ba98f52965e3edc4c5bacabb5e4116b77dbdcf6011a7a88e64188f697dccaef8fb9f4f18d60b5ea980d7dd1c9614c7a514915faa243ae7a030da0b076826c8b77a7a72d6161b8d2bb1ba78d218fa72d94b3a521d2d342e8bdb668386eea7a76162ae1d6a81b425670279f502d44fb9926f500ad2a5d01f712d73a8729431189bb2babd96828c3de57e2de2a97815d27f87f38550b2aafbec5f3dd76b282e1f3d30a8f7b1c05de19925d2cbd7645e22086e57cc45afb5c05a5162eaa5d00c53f6300b1277ed751a142d506bcc26c32914c28172b812159013977042cc967de1478e3993885ea56ab8f5a10df9a92d1ea16b87f1fc55a46779b6315c4ecc766526d7166f68897d58a31e7ed41bde59bccf46f282bbe8a935828605e0829eb58f302721739da2d94940e42e5442e98bdb4672f1e48ae4cb3d03edb3eb4c5b37878370d6877459d36a83067dd194a3827dc156da96da2583634aaa53e10294e4bf0fad62f59d85aec166403506208963fc5edf0d236c6a3bcc0037304ed70c3754a4d46e0b79a737e3de789cc91c100a7df2080abe474765df7a3e15bfec7e3daa22633f9c11aebf666760e7b06b15684c676a6418273a198257bc21d1032cb5354e831341e5548ec8f1ee1af39ea92b8d28fcf20142c32a07b0b6f058de97229a74d31167ec91df3c0f20d3151f2b64e114c631d1ea74e1e47509cfe0b71a9664e6f138159e9cc88b2c1963bf715c1e23e4128a7a90ee799059d6459265ecba10bb2e076b1b3b7b6394efaa41659871d3ed5410359d4fe7024ba61202bea787fde473229e650c664cada90405eba0160519f33684f8d2f0a24a8732f9a10bc4cb03fa12b7917cc612b35c10aec7d86a5dc1b11e643a005cb964f0644b8b29f69d42dc3162eab72fdd6b2a65b3592459b3e86cdc17ddda6a6ea5040b90a59240af3bf547c5342f1972d93e5c07f6fea41671f8a6bc540bc35f538dc9a420c48f64213be03c0279b05fb5913526be1815d137a2433125f897bf3fc71bd1bf1cec04e5e1a658c1bccb6322abc41229218acd953121e1936c34aded5e98dbdac1b5e8d94d7cc83b6f74869c14fd4a3018666c0ac7f486b5daf8d60331b37e8e65773e945aa1a65ab720a5da7f3b0edcbd04e7ace105b7c4336d9d8fa9fdb2e4216e19706011ffeb92b496d60ec8da8fb12a022a1c6a6eaeb02b2cd7f525f79fe4fa38ad8d74f4392ce33ac65d2f26aef233f3ed1b85b1dea7565505a8f84c6d580451095a95f2ad2df20119cde030ae6ae985c304cca9a0e7d89ec14f1fe1da13fc47faabb768ea9efaeac7f7f7ffdfcf76b37bd7f065f46d2aeacde3d640980383e87d905b2a502b3d3d5d14d5345eddd559121c6b5d0b6833eae20b54f6aad5ad10ed292ecbddb9adc5bca94a40c5c093009cc0951264fd2ece9b782244f1ef2ccd190247f302270d9c3c74360da6c2b409939810f3f4011c50da9bcb96f91cfa377b4a3c71aefcf1c0d4832f5f74623998e443c772415433598527928d6ae21cc1c9959a25f548b14c6c052387a924be1875c9534b9a5262a0f511f7dd0eacb188ed954eaf36db5d65a3be79c63254b7b6bf7e4b2c090079052962b100b822d1618de4b2999908a5fcbe6cf0576b6c51263ebbbed3979a6cdfbf6309dd376d6498beecc140f4b467848b0e7995bea628d580aa23bca2dfd806bb97d969858252ce980673fcb33b76494c13c734b4a7239cb258e22820ee93aa4ed90067359feb56009b7bed5cd0f28c7ba6c24d3f17108d2f1e64f2c5bb99cca44ba2cbbd699f338abfad8d836b56f2382969c5ffe5e25cdac6c410b783a0d2c6d9ed56afe2a1804a81329e9e405e938a4956de6d3f94166548e8174b4a04acc06f6c2325995b54a983fb305751a37d9dd9dbe539f9402fd9479acdad8196c3625279627beaf39e79cf8deeb815a5c2fae7077f759edc5d69ce19793b7f8aca09d75d6b3d7da2b23cff766e4f95d4ef8b5e69c73b2dc6753307577f78beff45e7776b3db3cb5c99a3db7c6c0991477777fe7711bebefa4b3677edf80b3d924bf56af954595f3b1d933bfb6005fa87177f71637b2d9cc959d2c3dc855004494b5bb7b7432bada6a2b59eb9afb38c487b8c3c81ba05990b9d6eae477ad7366633748e4aef3f3e958f6bbd7b23edfdddd759ebc360fe93ccff3e87b9ee751259948932b206af726c192669bcb5c135d32912641f06b851d09823662db859aaf32e5f977be37bfeba73cdfd6f90e4384eeee9e93e58db982e9cbc0c1f504d3ff9c59086ab101eccf5bf5592d27dcdd5d46ed6fe775384464166c8b7b256f05ad60fa33ff3a06da80e9630a5ec1b4ba8b3ec25bf7622e6976d2d51693be7e7f56762ad54dbfbfc9971a7777d71a77f72c8a709ec515cd80bb9d12cb93a18e34af1d0a798c2ab87eae64bd3844b9bbbbbbbbbb5777f7eaee5edddd6badd51d167b1cda0b0b481d0b640880002e0002d8806a24e026001bb69ef1b942c8cb000a4cbf87044c5f462842c120003ca190db7021cb138a502e00ba115e60c10e0074c81b39fa00393e0090638c879175d87aa683f12f72ac8e05b016b0638642be8383dab366bb6c3d60415039e0616db52ec21bac5c49d8930b1c0628e1ee60955928e40be8a0ce5ad89b507c527bd81a1301d3b709c5273814b24fdcbd01edf6856c283ec1fede00316a44a1a9d65aadb5b6d65aadb5b6d65aadb5d66badd55a6bbdd65aadb5d66badd5babb77b7850b0c8b3ded458c9a8e05b50cbab7ffcd18c00c20036c001e204868068e6a9d00a1d025f110a63f8050c8134028d4912e2a307d1f2c307d0d42f1859e50c866202314aa38c01ca20d808c507c01830d748418f0d42cb86047c78ed2ceda05413b3e1d00903fc6b0c141b58ea606e385cde180078b0703c0c900b003109cdfc3d70158846b0f10ec9fdbfcdbd5065018e18e0f94f608a63d6a036a12498846b8561e0ea82e705acc6e7a3c00acd66b5deb185870cdb51b29307d592832c1f6690cf6805a6bf5d1d5be42211b8a4c707deaa3eb5eb8294611fef9c8ca1fbd02d36791af2b4c9f953bd3abead4ea0a87d04fb6e6af9b327dcad355052ec27916571403cea97cea763ee5544995a92ab1bcca44a52a0de59bbf16e823ad9f956ffedc0e35518c29d89f0af9508f93b657db7933878f1ed126b86b94c6047b3bf15aa0bb7badb57e212c0bae5fd77516e89e52dab59b892296b5c88171018be7f2644000b0595fde16f903ee80b01df07d2070d63f6e3beffed509852ee9b202d3074028e491ae29307d1209a63f4e8101190a55a0119605fb7f8ccf002988218346f8224783173472a6dbc09b006a7c34173368355c14e000e0745beb36ff75faa7da59ab6ad6bc59a8a366a0196d71801b992c0141e0749b77b374d6ea57aff3c8201056843d8360ed2aad3a196c9853bd09855c0bf61b88ed6d0ae39b939b50c811a0801a0b922920964158166cdf061873b714160a59d275851550ebd3b70985aa0d980df015e2f01c4403a64fc526d8855c089c6e73dde67fa2055d4ec0b309a6bf6682b30756b750a6aac1a194cd826f548502cff2cc51615202dfe499a3c2040d95115427b098678eca10941548c1385cae702ccfdc141c3a1fa63871c2b23c735352383265842f38bcaa61700b253a228c85e84c80613944619b3c735292ce48592205be79e6a428d982c33c73524ac0826179e694a450527aa232815979e694a87c70284dcd6e9d39a51a58b8f85e8cafeb7e94dadc1878b18b0bc62e66329b7592118e52095c2d64609a4901e326ca500b9f2f2e64e0bd3886ab9b198ab03650a464336c01614db1199a5a2f2957363e516c700c4ab014802e19ac5e2ab4c428523837c3919b10e9c965f50295a24431e2e7c42afc36e26b5551526509887f68b1a4187d4de19d42d5a2127a4f88583e2a103ba5a16b642d159c97938168569d307553c07c06248be6f6c555a29a67de31dfd292a5a35c14a9ef89d5932b1c252acf8b67e5eb88f0d174b83b6d779235e76c11da62570632835c68b690dec7d651d7e6bbb770ada23c5b8625351c71020a14153fdc968012e10967c30a4e70e5d67587352c1dc09030838aeee551591d5e9d0e5fa2b877d699ea9440504e70f1ec919a665f7ba2e6427d349a3df7d36f486d5abf91dc17acc146e69cb77abba56f2453d7f2814eb8bbbb9c5fbbd6a0164cbbeb4f7fe30881e97fdddd4d4126500ba6ddddf36beefba63b976e77eab19c59cc358be1ae4d170c05d875ca18ed6ac10e8459c15eedbe3a6e76b77fda22b7b58c66952c9fb43823ec334ba21d98a565e43617d29d79e10d0db24b090ca64f678f6d19b58c5862d4ec691f412e2d6efe0972fb7bb591f25c482e2456d30deebb9072df71f6d4bf9eabfdd943bd2372bf8dd4a4e13fa79074689ced266db1dada32ca9f594db3a71fcc01d32f2d0e9c2ec694c018be3969388e114d671651d47c66c87496b3e9053abb4ca93cb1c8eedfe1c541f292ef540eea3e220a5720b0cd33f7848a6cc413aa5cf54409afb3d5499c7b52c48767ffe5997be2432e6b5149f800dd19a6f881682b2a7fb41f37bdb2e58812df543f1119b8e556c672746470c9f5ef1228b93e5e9243aeff3514c47cb250e2faa06dd3cacbdd36a1905301c472d5729331a3081864c83024e1e4490b26cc5c0b49a8402ad7878542b55620ac704186297dfdf438a9cafdd89630a348e2c8a889124b4dd4308ba0228514554c95e0852427185cd62ee172810440b95f0ce3420fe1a57476ef0da9941e7537f140af4e1add96d2a731c1f4839c8edd0da94420b88de29cc784f99367ad064d12347b6c8d3335bc1ea991153c8f28f528ce7e498deec5f8b3eb781487f36e259eb5eeb5a7d3d4c8976a58a70b958c6679b7f3e1b16e97f355236aa463916a92f84b4e995c359d6c81ba00fd3ecef0e90214a06dd593ea9ae5b14f95605b6302511f74f1e02a835feb06a2dc3d826ab486aae743f366d88a85ca6b9247d550d7ed795d3554355445d4fed5eac565569cdc01f10075cd044006a021c4962c5e77818280808258fb03a86d2dac826ce4e8e163021e7c80cbf58578f966fd70f2bc2006bb00f56954ad45951e017905e64f0ecf7efd5a6bd1a54bcd64a951dbe6ad3e4dba3bea03d5cf519ba6c5de65b5beb06bdc4349be3f9b341a80336d0ea8254fae5e7acc9efaac1b9b02dffeb995d25a9f66bb5a6b73818bc0043cf8118408084598727d4ad474861fc7a8520bca37bf99db281138d221109cc73e2c0473b27e765e37821611108a502444165fcb7aea758eb3326bec74528087d4cc8465a44dce53026c23f04348bf05adfd2f7b4e337da6260a7581d099ffa03839674394fc39f8350986a28be257980366e391b54e869014706720200394404040b1d9a4b181c799b62193c7bec5c9e5b4c2c93484d7b422db9990d9639f09c65d03bfdab7d55d61fb4e04044ad442a8c0f66353e0725ad9ae034148f7f44848dbbca122b61d222cd93e4d629f2ab14f8bfa4796edd32547d922b5cd6ff6afd701d920b7218912b3dd481fc4b1fef18f7221f9a6f350df51dfd2ee4f1da62875f7da6856b1598e4ecda74cee9f01d45fc3c6f5ba09c47e255fa7d6586ce79e5fb7d6da3bdfeb70a5d7b9b4bc5ab69e55fbe9dd0e1dadefc8d97afb1e597acffaaf6b3d0d3b6879dfeabebdb78ffbc77b1a76d0bdd775f5464581c82f01aaa0c129812ab171426717a48fcaf5e1852d47586062c60a11e80ce0ca14940057a84822b7c83377e569892f366974fecb6a7dd9d3b2f97d7f05d80192b1d963bf622177bc0e12f1c6da16043afd890f870ba618bcb6d67e62b2a870f7df5a59647bed26dc68be397f6a9d351dfb5d9bd6da9c6a6b9d916dbbced6dc55ef8ebc1d0af529f81d59d6c8ddc39cc031cff3bc32488d6cbfeb0e8cf54f07822008fe478260ac6b20e8fdbd247dd0f33c32488d6c83d4c8958c7df7d4d5cf7adec531029735976d85acad23e8d9ce5cbdfaf6bb2edf1b56845cdfeaf07a64567fbea51df568cfa89d5febbdd7c9d95d85238569cd07e86fd7f65eb2c96a23a8a3d5876f5605f7126e1a15dcf44b9213b314839a9c98a39c9825772ad7af415dab4d9492dd94613879c2f2a443e1cf4b34698c524c1b1daa4972ad23e4fa73fa9a4923fc4ac972f6b9a4463c928cdcf6ce53ddc6005a8d72c55101269250dfe74ddef4516f72a7efabaa2dd615c3bf5d55d5e4d4c5c13815529a99660aaa2a57307e257a3d556bad7f9f4255fa5e47a47f28adb29ab92b4999fa902371221b0ab1429246adaa925481e095216a491e5549aa925425a95252bbc96d3d2a07b3a7fe8b29261459e2d9d76a3e3ed9890a9794eaa7cbf5bfa6aaaa493b2b5b55bbc9e949f43ad1eb685593f75d53f3d42ffbaaabbc74a4dc5248566989894caed956ab727d2fea42212b45c56ea251a78ee47146aa09cb19a9335267a4ce4c356ea4d9531fc6045328a8dc51ce53bf8a6437d6a28a6b9efa544ddb9c4cf3d4cfc9d1d1912163c68c1ad9a33c9242e5fa1c4449e5fa947447ea5aadb552814b4ae5ee52e606548a6a50bde44aa4a8da2a58c68c253c85e8dfd921b59abeea2aeab8491da7a406350f1698c719a89fd29bbca9ad3a0d525fb9cd91ae5a0929c98ad6eb728a70c575adf258814b6fb2148cf8de8454ba54ff941d959dfc49a9f6adbe4725e58eeaab2db97e4bb90d49941e7275a4fa342fb8be0f4d1adfd7efab56d33f9e285e516489624b143f51044531144551145ffcfad48aa6b9628557a4344aa7ce80a9ef792b14594af2380345ada567a0ce40d1cc630aab22fd53adb523082e2eb89c69887864914e04bd900130153a924ad10d363035e842a1a2e613d5c90b735016d82dd03fb082a00dc14e64b158e2fda0be9748fd2294bef85df874ecbc4e1c6d74ad766117de1c1da51ea5975216a52d4a3f4a414a434a6bcc9efaad11c8a76b35c8c6a441ab84e60f3d336bf52bbd4113556718606ec0813fc0dc8052d11a93862d1390eb06b9ca00e7fd5c59b9ac1ee5364ad53c75d2f02e5d738fa21bf8115383fad5bf34919dc44970e951376af6e4ccc460ef123573a9d953efd0eca9b48a5275adbe0552f55dcaa33ecf033d2ff43cd1f35c9e873defe57930cfeb4827ae7fb7b9488fea19751310022ee79c73d23a67bbbb7bcf39a7e7d017d0c0650a939a12ece0728289aa48aa367575525a1ab960770fe2ee1e7773aabbf7c052a5aba1ec4f952ad88db5873aaafba1d13bcaa8a33af0c5825b75ef462398b16da9c9aadf1d59ed38676dc24bc4901283861851622c99704215363042b899e2668a9b29a898c215851a1c1a709670a4e05099bab991624d1a5c028e0d385370ace05ca1b55237e29f08fac8ca9fcb150a552cd89f3aae01fb53217f75532155956f0a86c384dd66845825c25456c99715b4ea602854bdde8442de82253ae9210e965115ac3cfbc77d522ac56b9d010598fa90110b03463f8c7c9760fa38dd90bfbab72cf2860e53a82c53db68086c53ff383dd928dbc582b9c9fd964cffcc1cd352eeb756acdbb5becf3139f5cffb9a790fa4ebc81b0beab6feb49fe67596c9f34c68ba37b7fed953eecfd1a922df5869faa15ee74c6e6b7d3ff53cef193ae9047ca6e6614a22fa1bf84ec02f2d937fb4368ff79d0e8fbc0d228952da2c9e726b4412a5923547bff39ec04b3e7a580e66747bedbebb6dd75da36bf308fc853940181a1a0a2137b9a6fe4d03957b1882295b0e7af44ff8f39f66c8d0a9cd82be1f976d365fe4ed1f9b87fd476dbe24f225915abb061b4b9ba7b0af31191d63b2fb35c3deab42f2ad3546d249c93208586bd7b1eeb39e8ef759235da21ad04bcedb01a5815eca743b9a59acb10b720b7e3786ffc23133f833142a89d4fc9645af6b047275dcb7ffddd17bb2a4fffddcc02c8d10a9b47b853f9794224ddb778210550471250d991d6efd5469da6c84c8f2030d5688a6aedcfa29d3b4cda862c18a2b23905005935b3f6d9a3623374c81e40a698d089680ba35ae79ba6849d79250a2aef9681f37df48da875c86b9a44837230d47569069f2848911dcbc02917dcbd05576f7fa1ec49f88b514056064cc7239238c0f61723f86aa92fb3f6726f7d79eb2e47e9f1e5af06148a881baad1801a722f19f4ce66efe1888a4ec3f7f2893fd59b12b8a8861108c01e57838e1d61faa10bf844230c1a585ec8fb95065ff2e14f299b5dd4aaf23a80bb79c2f973a4e52b27ff5bf44d97f0643f61db2c85ec3ed3485ec5754c92ebb7935e13683005384116272ffcc881548d570c4098c4822d43e256d770e841163c2131713c824716b1c9ed00ca9194325835285c2c204b9df4605ebcc0c2eff7dc83a3905d9bd646fba22bb0e3c64f7c071db8cc80085104c9058811065a0aa0f5988c9fe36daf6ba4d314f6bb0e060421216656efe9f67af23d525e80ed3aeeebf4454c9fd58044518e52e624dee2e63725b596a4ba2d572981378fe785fb6debbad56cbdd672ba8ebfcdbbae39df792b7b3d567ee50baf23b6a64a5260d03e47eca84dae8508f30abc1a7e4fe494928936e508fb45ad76bfdfd3c185b70498dc46f5a47f64062e43c4d955e8f3f8a0fd78b2f8aff7a1fe2bfc822513ad187f858741989a27fe9c3237c0ff4fe8380190cc3efc6d2a72c4b6f02b9fbee73f7e1b88122f96bd5fc6dc0f3f7ddd839dde89ad660245d9bd96d5d53c9943ba76eec9ecaaea923bba7a6ae29777fa704d53575fddd539775c83041c28a841ba44e30a20406290a27122cf9b2c4ad9de6aeab3f7604e95f62448d7cbc5160bb56984070d7c8b9a3a43c289ee57c55a243513e2210e3d6ff41ff8fb67dbf901839874355ee2fd2b6d984a42b7590031f13f8800ad8fe4e825f47204edb41ffbef7b52cfd3940737dfbde8de054833f28478ffe08f41498222088e26f9da479ffacbf8e3a5d5b823f0305f5e7e83f725a420a0206207a30e2d6519c4c88923bc1ea2804374d9c5c795265c4101063c210a1e15245d465cc0f238e9a2cc1c2e5a84b03aea84880aae289243810d65869c221891f982882023ec070c3d59222a69cb8f5f7c3e0017f76bc4f60d00cddfa8d5839018b2ba4584db972c2140d3810b1821174b872c32d0bfcb957182169e8362fee9f755cbef9013c73382cc9337703159967ee86abecc1073f82dc209567539eb91b8ab014dc7a1831e0921ae1f079b3ff5a8d32c148c2e59cba1f595e8fb49ae52efbb3debb52e87723110f3ef8419f3e81cb0945abebf8de23699aae51abae519a8ece499af7337f37be4b93688204cb9416355f6cf0a065861dc48c708212dce853a849e3cca4d1d49462597acad4c91b9d3b496e9a699249a3337d7a353471e02f98f5ae2f6952a61ca81988eb594f9f52d6909aab3456555464a6c04875899a4d358066f8dc7ffdf79f8cecff1a67f3cc9cecff8dde7f9fc71a4bd6bb3e30fbe87acff578f4bca72ef283d1832c98a6c39ff6fdcc1f8963388a284f0c2165a5829598215c50a086490a31b480c28d7e38ce40183de01952b03f0c1f28656a1e67b272266ac564459948c0d389872b2bb460041063442872840a1aa613887095c48db23868dbebe9b7155c06a99189748d0ae95af37c80c3af1a0c0c51c50f67741801949a0c5744408108b488c0cc8d921449d7a88d12f50774fa644aebcfd08325e00e4456477448b2c30e5a44a0227a62aa4252c48d3e65ea9e3e08413ef0a06bf455c09f4bca94e933d1a769fb004d0e659c388164872e373a9740f383922b1c9c54f0e446491612dcd92a59b1722204294841654405252c61d1021aa32c8e0460ca92a1a32a67a880a289267468a1890a5a947cb9d1f7e0879069bb400c493cdc3064c50b4737fa204cdbdc925425a60c0b564426681cee1b1f3050ca9df33090300b6465ea6369712eb8b8b0128624f835d431cb6427bf9c49050b65863ac4c193777050fea319c526c63743b3a78ffa8786346984b5ebae2eae664f3b79f1826c9a51a794fd4bf0bf16c9d2e2b2f8de585a1c914d427ae90f86ef63e901abec202e771d59fa833e26e99a2876207e481265f17bf0c5d81396e54f47e76f07d73fa58c28f78e929c7336f8614ad30c71ece052c051fa01861a8e24396da940519330446c913202196efd1f0c1bb0955dc9ae5c284d1ab6dcff53fd4e450656b4420d6f745d28018715ed14ed2879241f37b1fe6175a0cec7fac03183fcdd8c30c09b31848d3b453b4a32c837a37833ba72a763e5b67f6a9eeefe66fca787fa281024ea82cb7f9215b9ed0511ce6d34a316fe7e1d2b590bcb74ac76947670df658a8af585b198188bb962311c8bbd6231582c66138bc562b17fea5aff473d5497977a30b41b363bc9f97f828a9a3440efe7dce0a7a806fdde7fac714749d7c21bb5bef8f03b7207277ed3aefb3b709451037b76dd23b9cd05f90263dabd8f94efc5780a7f6f897229bb928de58e92dc8f77703b4b3e973b38562e59b9fca74ca3c5889a3dddf58fcbc8c864525da35456d5351955d75a02ad2a7670b4efdf29da41721b12115c5964914516b7174a3b4c9386cdf7f33c4d1ab1f0cb2fefac303708bff1ef5c991b74df3b228c063d1465e3f737a3f73623f8dd3c6d74ddba11f63623f8df288b8d19641fc3910337cbfe9b757a4f3a3dd13fa83b0c923040ea9a4c06fb6f043f36cac82f467bf34627447fa37d370ee93ec4e0a86b4d04e7ee6d98c018246180c494fb5d0881cb1d5c89c151eea719e16426981af4e78cb224a34cc9ce50f7cb98644db228d993db5e0ce59621754d461453b3d3bf83db51d23fe1f7c3c62144f00786a20bbf60fe3135b2211992eeba17dfb9f82ee7bbaeeb9eccf853e353cd1e26c0ff6eaa7f6ccca01d255dabed14cd9e27d33fe0a4117e3f38cef011836bf80e1006b8d9e3a313daff94db68e40ece799a664f6d08bf18692e72461a91f3943215723f1ecb1d1b727f6b2cbf5cfed45f1951b9b3a4dcc1022b029732a95c43f0825f7ce9b27259b5a85c45b421e7695155245a54ffb518e78d367e2e93c23229999493374437485c4c619b915490fab718e7eda3218ecedd47431fb36ff133f4d1bae17c50b718693e5afc8b7f4116d1c0882c439f8b2330f8a5cd762af7bb40ca02dbee69435deb2bbb025b474634a470072782bfe3d43fe54f89e24ed30e6ea76867c98ed10ed24ed2ced20ed30e969d2711ec7eca6d39dfff546e73f135c4f1b9235d20d198c207d275240d09cbec69d9551896b2ab9ba4fe2977706128bb4242d4b69b9b25374637473748374ae10ece6d396118862f646aaedcb6837b41cee6d9c13979f1e50e2ef7d394dcf6e2fb69491f4da96bb4a417a34c4df3843afc6f8646999aae350d8976d4b5965d758db6a4d4b57e9a5b4f33729becea622c9bdd28710106b7be945db5a474ce58d28c723f8d742123eafc71c65991f3f4cfc6a1d66d86935df5b8e124072dc679bb57e0cfa5ec4a76d57f33d47f43346d3c529876481a13c50c50b7fe1bdcb4091992018a0a9ca678a1e2d68f8192ac8572dfd479b7b92dfc39631a189165e8363f1ce95217c03ecf51f3d85dbdb7bf9b5e67efddab7fc91f5dab6f0303cd1ed9911f04c97182cb5909eacce0c0079e3ad5a9f8a60e134ef97e338181f4df2ffbe75ffa9d63224d76c7ff7db3fa6cca2cb2acfda009b89c513ddc46a479bca7b9964162f9fe7c226976501d364236240b070825112e6753ec5db9269064fa2d25197f9361a24b7e3decbd1e3e22d03fe1f8198f9f615f5e1bd6bd174696176771da7a4c1ecf7bdce01545ef43fbfafea0c6f9b6c6b2c57a1bb20c926d9e359634c770c0fe861d60b27e797fbecd08e4924162b9f5dfb73ea8c31df7c6ebfb3dcff33cef09483fec5db08f81a009d81fbbad870d0c13f6d48667d48ed7c36686c1469a0ed8df87b170d8bc6b669b7176cde675b0fef52c1bfcad7f7d90c3c61739bb86c570c41a88e53248fec8d658c66afffcfdecbd77bdeb5defeff5bcbf200d7150107fe0ed9ffb9f0e1576f276cddf136928f5d17c749126aff3d037729befea0641d3ce84cf7d4c14263e46856aab6d14c69430e64815eece7212e3442e6b9e3931632e4b4c187c291d71cc7052314e62bacc2bcece06258894802197374f5de5e98212455d37e7b4b1eeddbbb6ebba8e65aff5acb59dbbfbec3a1bebac25693aca6cc9fb4f7cf373f5a4d1794ddeb1d6d14883a0bbb57c93e2ea9dfe7529d34c734d3865cbc3925787377194679e39d4f3bcbfdfdffdde7baeffe978e9b0b53b68aee4f5114446ee7e0a91d77728c2ff73cd9f3adfb47980814c3a94e7cf9e29802eb1fee9ce7fcb3f346f8902a5539b59a80b6690d821334ae81599283258c82091514266684cd518a9314e506bd218e59944802454b8f2dce2240acd99a932b3448e75b7573361cc7031e34367264bb7846ae9cbfc10087727fa392a273447e5029592ea95ca8425aa9c2a8425d4e4d8a78415a2cb2da1438ed1dc1250686e09159610a1cb3367664d8e758d7e224dc797e77b9507ef7699a2945e961394bb3babb5c4d402adb5b6f51d1d799ee77da0123238168bc502c33248d47cdff785621aaa300c43d145864c6e4c97dc981b4811a90c993269725796a47253544938e5d925cf1c1256f97bbd5e2ed707b57539765775d763f276edf57a1a0abdc80b052e2992952cf6f3b18d65d7cb45969d5d2f7a11776d0004654e7e91df359bd3b5a744fd53df3eb682711245d420319579f2cc21b194bfaf411ae2089f92658d4c91dc863379e88f993ea58f646fb3873ad17422d25503a6140441b0eb404ac37e2a1b6323d68269feb4286a68e84673b207c5f1c0650b6ee806fbf059285ca1a1cb910c6b80b0c28894125448e3240818c28881bd93652c833fa78d124d1efb7e3ffc22a2c2a2dffb224b8c84c9aec260d7c0bf5d03dfe6c511770d7c1f5d3560f0bdefbef5ae91f51fd3eb5d6f5fafafb5e2ff3049d3815f7c1c989c65933152d6d2ff75ff7bd768247faf3ad2a1492df8228b74cdd276745fbfd30192fef53dd2f53407b98fe1bb28146ce64f88dba8fd26f6a9929d448c09e104228022044a6eb8591923dc40c512282741b870b3b1fcfa6f2c63f9fbeffdebf7cd307b1924bbc29fa1eb83da759c2c5d1fbac6ef29d2ebedebbba7ddf0175fc7ec9a8e1924bf1e93b30c127b8d652c86032e2951e85127c231e759c0a58e4e4d86cf0ca01a4136724ca07bdfe12f3e1e813849dbf17ad7fb8b244dc7eb5d2f3e7e1ade7861fcf45d1fe42e9ab4f4bdbaf6bdbb9720c8a2ebc1b1a4a2ebf1fbb84435f017472117e98f47d01f9c99222529b56ddeaccbf54e9646b2f83ebac6d208f81f055d22b9012f83df8be3170a85a18fde7fdd3d2bc95b2271cc8c85a42ea4eefccefbde5a6bedd8fd57e91d67be351f7f20248e72796ffff8d77cdc86801f6e404c3098609b27ee7754f8dd66953c68ba77159e5d6bb5c8d2b2c6723a79e453f7af0e8e45a8132f6091ba8100245a21c92d4afdcf2ab96d9c3cfe3cd9dddd6df3c6d4949fdedf2a5926db346df3cb0c3be0d0020b69aa90e0e61d8881892928275015adb9f9571b758f34968281f83743a0f99e97324b82d50478f6f46bf4bb09124a9048e21dd076d0dc4fa90838229a6e309e708ea79c96264323104af67818597039a97edc5bab8571ca9e0b83257bcd6d399cc7df6b3ed70acf1c18353907264d76c70087279cdb323079fc9fc8ee55647f7a24fbcfdbc5e17eb8d7dc366f3e40d967d38fd9e37fb1c04f71b4881af953249f3d3ec4fe10223753124c0dfc89cc322dc1ec71929260d6dc568a6bbd78a0c1627349713ffc857818a910604833258c0a476efe7468da2cb0816a0616a4a07242959b0b691e7f3b0a09d235fff0072d83a9d277edb3bee669c96f8116775f330dfc993fdacf9fe9f44f28fefc6fe6369d991a4c75808272d45828000aca018e17040acad11a65744d072828878caee99c001494a3ba0f50508e4e04768606fbf74f1a4c7e4fc09f9b52dc45013fcee2fa4789ef00bbd6d4e22cce1659238b34c30eb4f0408482965c13dc00608001062835a03005092c13f0e7ee9f65abe405470a5bc7787e8c7777f77677f739e79cb66bf2b9bb539c03a624f8d700498061696607e17d2dd835259cb668eb5437f97efe9c550acc0e5080497a018c922f55d0ba4e160a46dec397a95cfa7c791a12c1bc5f53819b6eeb0b16d0eb22d697185eddd4fdf202686d8480c9fb12c28b6aa304abce0b950bb7618292f522a66b22291b445315fb5c2f40c428cbc60957cd332ff562830db69184a879e66d91c996c1b33c735e9a8cf182bb20cf1c5397a6a39c17125ab5b6d15bd33e422a83fb43ab6f83dab10d10b8976615cfeaecbf5667b3daacfa58ede0ea26ddbde4ee2362399420fb577f9c4398ec0f80d90a385aa2144186428ebbe71ca3e4625626835c823d54711221fb8fb886f1fbe76b8d5c8247fcf0042a2afbc70885bcdb498005971618476160030c3195fd5f50ba068a6c4b921cf75a5114237240a28814acd0326ad088b42c0e041031d0665d9c7e035145bf8b990a210ed29650c8675748e13f2dea06b98cf9e33459b2cf6af784420d165698400a28b00cdd3a888613ca4049e128055b6ecd25771637e46e6c05e32977d7a189ec5c6eeefe43e012f4e0831f1dcb6192fb865b67c1a2c504b93f160af58b2c4a60d142982a5d62b8cd15a4b0454c530aa098a212c20a2a2f9b2d5d56c8fd30cf9b8da68419d60cb14488352ac4503aaaa2ca85114849ba7954f610ac64bfb9b997146eb11c1d1933b848310410eff30222b530b1057785042c4c31bf6071f913c15f86b8050047e172ff2c6a05b9bf26040e412491fb5d3e51986822810a0a5640418559a6851c9916aca810a54c0d228aeea8163d27bb8e0d63ff99ffe766c8fe6128e4b52c4194d0821568aa9849e15542a353a8742cd86541eb10d58800000050009315000020100c088442a14012a7999c2a1f14800b6fa048645830964863390ec3280c8218638c310620408031863043434402c33265a1026506f4513121c7a4b673f8b98409390a73d8e814505117f4ffe9525ba2bb3a1b8a9fba21dbbc8c8ba3f675af286effc83c202b53932d838608ab452718eea39472dc0e6c84cbeb6de9ac22293bfd4976df66d57deb20d653ef227906692ebece0f394db01255f936e942f48a1259e5d3cb589836ce63d5e3aba085e759857f6d2b9107de50a4c1432ad0ba21c9cbb8cf9d26bec823ad3bab16abe382e9107ff39f42149106ced47ed2fd796b283526f93c0b7cb73bcd142298c72408d04ef2c5e47b1c05ded066983532f6ab14177a75d22ae04412fe56012fb3c534ac9587aa68d44f56638cf8c4581037a6be182d9dc56fae068eeb2c5f067afa0710c73e369a81de332a53e31a592f482b74cd87114ec47666cb5281272771c7182a48b9b8149cb7a6c49dd4fef640c9b77bcda36d18205ce527648f9085c3bde17c5dfdcf5f41c950273d2b41da66573631e21b503841c8b21d6e2d53388d5322b223069c83dfe33db156342717927192d4443f09a4ffd62cf9c49e62817472f37516dd4dd8ebf8ea01f021a8c14349e29199200d3aeeedea34624a50b8b0fe65b541cca8d5540697aadcf893c6017e17a0000bc453f00c03db00330d27a62cb06be78528a1c5a6bbc7cf360809e355c22542096e6e17e4fcd51b9cdcb5201bd158a0ff48c093b5c80d977b3c17e57378ca4d55e8a58d1dc0f1945c887c5d01e6de47e6108dcccf25c36fb1fb8b3e3bdcbbe3aa7ca8006c1523f93a5c1e2ef9d6962a569fd74b277fe783d1e9b2908e7b162f1f3cc1e9a8fda7fb1307c2df2a207d628e8cc4989e028e30c7e46b0204415d06816f5de33188e3f5b10390dd11e390015fb00e3aaeffb2ad56eb12329db9f4ff28c32d9da2666775a8e623e6795fa9f1cfa894f459547ca53748432fe36d823ee572a5c7996f7692e9d5d88f514d440d9dacf3fd6f0746d0a6d5e5af2c39effb1d0a4d9222773f5b8637bee4ab65e5e4c3909ac4f52697986dbd33c9a7262944da2e20c0e6cfe9cb595ab1e3794b3091e6a8d0b2efede981272fe325833b622df6a95fb9fad4ae90761417156a3eb2203fce571494d938789eae152b2bec5bb0cc16f6db16d880dd20e3d78120f174b0a1aa22607695b27d2bcfa0719865e1750026b8e08fcf1bd33f81bf81f130a33f77ecd8edaa44b30e281456b66d4fcbc61c01c0ed52b126ed6b88e31b509f2cdf3bd2abd8c45c1ac85bc716e51bc3e50dc7a816dd24e7cbba8fb7bd3bea9894338f65a3a5997ace1389898c3a35a60d54d52349ee96e6a734b5bdbc64636a3bc8c6e0310a1a744252fb1004a1cbae963e8e0c0e93ff430d11225da4bce2278343e1050037d5945d9d77677886703ced7560997df90629a285deba303bf7cb8195cdb55c095d060ef3ede2908cd6621202514842a493275ed281f15a9da3190f7c541768ad704e05c141dc2db29fce574ef56f69f058eb1da6c4b2fbd7522350de452c9720917d195b5978778af83ad09b64285c39b11646420c59c40678d7a854f527b135342529acaa9e2e9969d244d4c9af2438d7d9d06343549a66aabe34565d61d76d3a09551d6204774868c76d6a829fa22c149dc7750662fd6a91b4a86774c55bcaf94e90bddf53b9627ac4ec89980e57b96245782e04a83c612ce6a58cc2268332c38143286648626045cb15b50e024613abba246f6b5aaea409269cf9548ff00266a01926a912796eb07d34c261e482d90b455a90e3a4b5c395a6241a8b1b1069648eaaec39382dca9c4da704fb812eebb8a0e9e65553b5ceb348a468eb96f951e84379805ad915c1c5a5d85ae3b9e9047a9659ebbfd74221f56b2d8806a5b8926c2f455b1fb1f87055eeb5d57436c733ba13698dc3584df51dfc73f7c2bd69b8970d07e65cb86ceea8eced47659f0aa456c61764a9efbd549c3fa345dc7d1d00f5a668765a68b642b3c017a379391fc7746e5bf3ffed4db77b76abb7db37bfede9b6dedfea769466fbdcce1041fa0dbf7a0513d26dd74f520be670563af5a95c13d0deb881124a0ae306b353058cce81da04c9d5532275128d593000bb47238007fa03f3c35c346fc00dbe3fe3c2528a09ebf25aedc535652a5a9550be85f5715eb9c42aff4baf515314d0185a31174a8c88ee3499c043a70d5305e45f7b547aa49c064c68c50c89a30e2c99d4c0437376a9175a02140121ac2a14d11fee713e8ae2095bd8edec1b290e334be514a09cc9f902e5d8a8148e54d7d651ff19d139bfd32eaadf6d24d6c1ea8adc63a4d051191c77b48ecb7a8a13987f6c908ba0500a5b1549a93890e57eaf7a6df6a15ca0cbba09cc38adf746e021be79cdf26ae31dde90b6142bed4395d5686b286f1858cf2337f04593239da1b930c83a07e460548c23c576a611a5a4c56f529e293e17ed76a4019531914763a258335c3eac6ea287cb4f904d09eb86e587352b95e30f6b50a94d14f60fe7855526faffd8a63066cec0ba090e5d5a3dc9f78bfd3a1d2542132aa1fde31b41ff25f83162200e2363bd0396451d28e5ef8063971604080829467d03b16173d71b03612b2a5760a83be8675551f702cf0e905fab84f158022567865bd796ff45bbe3064a19ef7ae4ffa7c439e23aa1e3e02bdf5a1a5863e143edc961f24623ed31907ae911b0464aca2987deac33615407d7b80a3b17652a8b82988c9debb5172355dd404909a267e7b83eeb18e2ee012dc4cd741571a67c0873f6945f3c8703dd2e084115d260f890709e8728f2e0270e4c604041b3237e6112e2627fa51a9d6d4459e4113942064bda6d7f463673bce21a15014c9be3ea4bbd11b0651bc5287d9819197e67fe0f31317c95e28311859d5472b83be7d5a58c623994752cdb729b430d787c16bbe1b0ac2d5005c525565879760c02eba0945c6215722f82bac68a782a000cee1004e64c9a2ece2df0c6f099544fffe0b1019d0fcfc89d3956e3e02d18e8df68497823cb278a587fd843b9b15e7cd3f82661ba6531645d0caa20cb5e9b5d2b056fc28839f36e502375ba5deb954642df9501837524768e7719d436184389224c3f3dc24f07d082bcdb8376030e6323b9be8fcaf28720b2dcce0cd85fa0a0e20d44346bf51a662e0431bcd5e357499d486021dae0b814111aa81543017e2c2aefabe1d9608f579c4b20dc72b0f006f6163ae6813124354ed3db3fda0e9ec0a497adeb86914ef2a1e41cc5614a821463b1a0d4761441fa5b52d279ab126b472c53d8f08af0f5a50e380677b330352aaf6c849f0b140cc4cce6e959e5f96cf70626898daa603392e071cc5e430a619b21804896f9d9fff2eefb442ac9ec881d20652c466506986ed803e74b06aae10cc6f70d4afc63e78d3c7903a7a4f839768b5fbcf773c130a7a91596562363bd4eb609d9033792d839857378a48b8840f021b50cc23bc2d5a356e751f2ebb6ba2bd11e6116b0f804eee401aa5c2da7079dad966f8de9139bd130ba03d17d20878330fef7425867eda554d04e1f1d11d71f5511278bace513102aff660a7d4a314f952e593cddddd9af3ec88e4ea09c1953c943f2cd1498ae9e4a72d712569e0a56e64d8ee21d40153ec01cec9835035dc68c7a0a77cf63d9803b9a704a282013a6c1da38c18ccb70174ef9c1bf3d610e4a589a9c8e1738c538c6090e25ff0e4706f287b905867ed7bc876f8264703dc099b6b108af080f4bf67a934343b32d18720fa00ee176a9b7a46c64ff56d9848a3feed52779fa45cb152da0e0a3f007a8e43db8ab4673ff81aacc9f6598d8ca407fa9bd4a013188e11ff5e0faa1ad7c6c45685ff2379117999ca57343329a21a23321da8740d67d92feba2000c310e8f96aaed6f9b85ec581b5b2cdd1108468e16640154fda597fc92fc5b545eb6f53382822240dd5cd9d35e498e1fac9220574322670bd47bcfbf508e75db63b6332c15ff9765fa4c57b25a8ec3291eb07268ead32dcb2eb6696575972d1e049b422f65894b05d446589a4c24cc7703f5eaa17d746dcca77bb97ad48cfc27d21d8dcb35ab55967351154331a505a034c4efc6fb039a66bd6dbe34eb4757afbeee17c8f5a49b17cdf17ddf7a2bfb89c9d70b294de0d1e647008b4bdaf5ac9b8e1544876d26295a2ab0dda5cf96fadf79f29270042469aea52692383f94ff062ad9578f38f04820d9fb1c7bf1be958c55240c6114ca1f32f39318f8a7e76cacb86a372fac664a2ea4de4f2c7873029c57e8b7a26ab2d8a5a3384f43736f5db6d1d528b5a87fc67a66e1c8621c17117c2a39a22f4578bce443b483260522ef3d3f0091f400e7cf2ffa348dab6c22b2f17f1d9dce5025a42a19419b53f8d2c53f454061cc5f4d8501f0021951db94271f60fca45e2895263ee662b4690cb709b32e6aef1cbea5d226c681636c9166a203df36d3c12686c61e615286e95dd533baa14a9e41022969b38c8215a2e1243d681953c84daccb02fc39d018c531acfd49f6686e49e52f554368da6e850089389ecd16c3d579ce562cd6493428a4e36d3d967f81005be0b71ee1afb11d699c8a893654bf286fe6814402a3450f818c1d60a7fbf01cac2d28e684dd3542f1f951a3065248eed1ab4253d49c27a0fa99da312342c6242225d3de961c3a5643b2c498d0fdd682a4bf6504a8ae700a8c3cdca838e5a9375a6e54e77f79d37687507048f8ef875153b8e63ef7f49aa7c8623cc2c7dc8b532b87886071ed20abab520b2ad9d36d4dd567b2ae99548914635ec03eaec9e34df62aa1acbb7140d16aaa519d6611ab6bec925b051857a7f828142e6c8824b60ed4bff01eaa65794fc6a147e3befdd0017c4942b414e6c3ba8bd5ca2ecea52202f7303722ed082f1b2ba437f2074396cc1540c921b78c927d83d8cbf9bc017c96235c8acedecc8352788d535eeb38aaefc8642221e2aaf64ee7e99bb786036cd91372f98c88a774434514a181e8a380691625a106c5d5700e9b065aa53167b5f0edb2fb33b1804467ef0b8aa106d274330acdb999b598216045cfee3add4d066d6c14ccc5af0fe8526d2e119ec054214550afcde25c30284a25d91cf636ebc99b2810e09fbfbd1545b9d60672101a494ae3ac29f67cb31788edf904b446bd4ea109b2e6672e65d080c320439ecd9f85aaa0e470325e60dad834fdd13fbe22463c33bcc667239ddbf43bb6efe41c2f6aa54e61d7242f64bb48d9d914607bbe14b4016e8548b33e37a21c955369f18b4e2cd92ac5ea7f7a84328782b40a2f79fc7494876992680448e25085b128eb6109fa2cc17a1e8b6dd0bc4c9683b8d5e3938a19f85eba73a831d1067532d079d20829566bba4dc5f82fbcbd4292792726415a02053a6fa14d2e20179b8d6a5eb4b791ae1b7a24383eb01c64a232285765ebdab5d366b5102f432985dbf7a6d2186433b9c1887fb0c28bb0a416cc6cbd1e9e46b2b9e0ecc5f262270a3654937408b4710c9231e806c231afb70f9066dd78fc827fa1204027a797b7dda753f1099226a482165f0af0dbe831ee612fde006f66d124ba5f9878e16105df681e82e8fe170c03242211c46e240c188a804f7636b6bf799313c322708c34dc7f4b0f5cc11ab2cbdc91f84484fa896090ea150a6a89ee10f3e626f1c0e210b452b59e33b48e58ff4504b4f68f4a1cb21fe4d8e89f4333a8258527f5e62e5894de21df97bd68a718e3ccdde5d2846105c6bc1bb3ead06e6b60367f3b8a59711cfd1a4812a24897827e5c70776597b01e73f1f563c8327534e0d78026ad9ef06397a805b8c9b737aa20406a0eecba0fa9e53919bc33fe14d70057d69a063287fb9af50c4944133f28237124332c7894e831fb1d5c9574c690fa214e2774200419cc1d0d5a62cad9b59436755ea459e7078453954aac12b7887d8c7595debbe2be0851ef0eed7ca60762e4541a03fbcf34e256383da7b02b6c0695e7b60010a47a407d29d1eaf6e80205adb9a715da68b3e6b3aca67e0a5c3c67c4660c5995fc97a055e89c6924f917bf79f47374bf1a9040f8113183e8d34a0d7aa923625f10099dad82147a7b1f550efcdf170cea6df6b86bdc9f86ee9eca291773c98dcdb9077df83abffac533ecdbe4a5234e1ac729a259857503b0104967ae9fc84c69bc14e8e2f8f15dce23bd705cf64fef33eb0e0054a1fc952f08d20d2e830850c36b86047decf9dfca9734f6e3aa6b05c5fd4c87715a65655b6344e782373676ef18a1b7e4ca53b7c6718709a9ecb223bbd344ca5fe23f565f0ceab0e9d72714a5f743468aa5997409c0b423b459d023869990b66bb5ffd2dfc860330d50eefe965fb1788360de6a7474b5688ca7c5ef3ad55aa0caa795b27b02abe889b449610f7fff36ffeaa77964ee814ae2c19de26697272b0a4e3907c182c02b9c8ccbbbe7463e9d9777bf719522bdad2e1c48b963900b545f69081ab0f1803644c2c0da6ced0d24d6c342a37e1cb8a8ef66b99fdd21e0ea8d61fb82e309ffa96d1ea72d8619d54dceaf9fc3cd6ddac10bd96d5f300fb80841b746f0882edc8c2e4e45895642cc0d519b8f3802ecfcbd533b1c43cf253fdd97a8cf22f1da3b4e3c87db7aaa01ee796b7b68b3b5866e753ead501e77e7fae8113c97939967716ca6b8965c5939d5be42f5e7144f57f1d8738b22e66026daba8f1d2d7c23aba4ef71b7c69da894850c23a07ab718b71226c853114f27013a7c3ac38398029b2cd39ecb90877305da5e0a1152846d142d19f0825b64cf2c96b8e76291b1558f4b9d24a736e60bdc10c7de37f5a67c605ae4d83ab5b76b1145a94fb5ea56f37952499f103d0aac9a63b1956a85904ce55017637fe0c54ca29eff5e10b594055e94228512c62dc1e17ede3404ea62ac02d018b0000ba6f30a835431b2f0624f5508c33052800e18efa3a28bed1ef9f395dfd41acf460f436fdc59714613954dc2817c18045be1233ead8f8c2307effb163b71138918a3d9425528a209d1824323be9a95e8eb934654833113c53865f31abdd2d9f39cbe6199d30a2e01d118ed35d116c47da9318fab1bec80249e8c79cde7e79b9372aa800d53a3af77db2f41b8ebb197858579ee09011dff265a51ce09ba05ec0bafb293a3efe3721076f7c42e69148731c091500d4e6e2b8be41eed6b090fea795c39908b69be7be6e107a5fe1979f21290fa1dd5c849166b03be5215d3cabd72c6c5b317eb8c41760a21914ed3744140a9767bdc39f41901d5b6575e08404d2e90e9c89b10c6b28349466ec1c607e836c87aebf7501f2169ccaa065501ee4fe0bd771e5940ce29fff4626f69f84fa314fa1207516eece1f410eabbc44fc52f5d0524002b3ff0e53884ed5a32701bc7f5ae239d20205b3cf4f337b85f72c16647fd1aab3f0282a43f44c9209328049ec37ac6f79288828ad264b20bbad66dbfbfa515f3478304da589b2bc87f3e7a5171e8ecd6292fb5e3f5ca235d800a6bcd8e84129b072cbb614926bd77e37921222ca7bbc303a551908134f8be731517e6689036bc30b15e2527ae092fe99b1f7eb7549600bf42ea3093211bfe94dc101945d0a716f86eaaf0f433b5231e2c05ee251d3db21be694b3cc2bdd18ab9b0c4d9c07d0e219b04be7915cb226ac6eab88e03b058fb763a5b5b095b318d00e1cf399185489f21f1b97ef1f0c7a39aedb247fe57b6a42caadc988e07c96d89933e3afae496f89a62f2f34bc8a341491aec5fc1214ba6c5f413119669e1200ea87492c4f64a73f0a3ebfea912704d9755f19205b9efde0eacfea8c06abf2030818edde8cf040322f7b9f13afd37acbaf11d4b57ba7872063cc37a568219e5190b1cce9a2b8315a09365dbb025fd16607ab35b5a2d6255020b83452664561f6aa78fedce47208f8d2c390bfa1200e5dc3d98d3631a706274c1653193f118c612e2d9aba166ab990e3461eaf89671c8318df0b2973256fa42bb27793e0d175472f2de6251eb87adb04e359fb8d28be2d2223ab1296f081e19d8077cd4a8c103de70b2d23457be85a6946f5175dc580dbea7d4211ac3cf2d1a85350a34424eab79d1805e43eb297dd311ceef878a798dbc9431a0426b59dd5801208c9983294af7a3de9ca38b3ddf05379c94270310a1a4e8f5684afb36c1f62b80173d8fea6b21b3a35a61fc619a11a5ad8286a690dd45be8f55f41e863229e6b9998f70baccd21f2c4c0985c9d00ba7743a2d6e0414de8fc71072d4da6e01dadeea0c2ebaa215223985be2441e445505a7445138ba589f3de0fb2ad17a7f455086b4e31f365600574e40723ed45468a7a4f7e63b07f234707a3aa558726a990ce12bdc8a548351929ef08c22a4df8f5b6671cb39e49ccd6fd79a4f99e9d3de38a16bbe002e8a48c16ef57657544ac294d59721f748c43207a38e9a6ce89ad3134a9e34e329cf7aca082199e2f3642dadfa6c8b02dcb78b53bbc67ff22f69a5a9c148e728185aff253fe66b9bd2063978eb4f7cebb9d1021321f54992128ba02be065f59ae1bcffd0313dd9984a82748c2f349df868913827e81d7d695214f8cebeb9b990cdb2f35462d521796e9935dd176336b930aef4b0da18d3405a42473c27c424f74d1ba8f96d1298109e089ae5268e91dea199070011d721fb00350318448df466097ea9cc18464380587644d9b2b06e48c72287e638c87c585e9587703c462d916af58199c96d311cb344d0658b0a63e4ea59d41dacfc3b22004692eb1ab9cc3be6758913c07bf19e6c065072bd89e97ffcb82729385f496e029a1ba3ad317f81060c4263f39d83c1ea1c51561068f57f0231bbc52943309c5d3044332d564c97b7e9615b5184c87f9ea35a6103c4e094adac2f40a3c5e07960372e8099155b39f14508f30b9d7bcd8a324a52f39a4362eb43ed0962de84de6922b43d89c21186c460812e56a8a7e88a1f0629d1351d06e89dc08bd4a70b3a8c58230f6de87126529cb92930e99a48ca5875c69abfea4ef29470464feedcc910db800313cc052994685083e25716b426c77fa492319a8d18a5c8c988b0892a05cbbc581f235507142c45590b060b4aec1aef8e6e10342f6e491f63675f99e41bd19ce0b736c71553461efe88d8689a8de56996058bb6eb4579612b3daa0d985d1b212a5d3b52b21079df42cfb027063695d9e1010816a3cf04168042c8446260b402daf350ff4d85d7334fbb588f6ab5940b2e734ce370341906b5a8ee8c1209dc645eff9124886d7d5f99277b3549ddb53d263d3a6450e9075500f1d69e350f4e925a7ef1539e4fc416a10abdc42afcb0fafccd3d7b4aaa3d81cfd345d7f8028272d42f6452ac5a38aa74521075def9b7bd1d1df2bec134ff2acf286e2612ec5e52e01175beb8c2d262298b80a8464ebedabcf2d258e2cfce71dfe1706a56ac92c912ecfa23c813439e18f03174f1841df017ef3cf78fd93b1a3a909cdc738ce5288a35286ee4a33482e6affbb6b0acf6690efdeec2de0eb2d30a74cccae98e821a1588e66cac2ce44001a99001ef8d822b5b2944c40a6d7e910773f1c85a62cf92592d2e2f9bd0a1962bf6c04d280904d8357cd58cbee03ae67cd054b33e6a5c5bc6d96491598d07bfd8771e9bf3c9aec0813804f596df27fe45981b1980527f4e52d36d09c7acf8213909424bde1aa9b13a880f3b997e3ba3ef4d89c8909269ff1ed3f67d80d73a511ea51e9e103412867f48e7671d0efb9911cb431bb49ef530a307c9a290dc1523040aeb1459f2ad1871d5f73674100f2ca61c5299f466bc843208de21eac1f86367abf1c3d26f8450402bcfd1d36b16f0d82b1c77cc2810e857fde0bbe7ba449f02eb935351c5de40319ba129ba092156ffc6c8971ca6c3e3c010e3f58477e80fa7c9375a3339e91ccde3a6e5e18d5b8376fbc7ddef0ed933447c914d109e7b0b73ebf7aa7f93f0feae89be6568e09073c20ba0f85246253482a283519aa9f57fdbc4450ca96e40c73e3bb01ac3e9e8f05c9e654a487ed2ac8d6fb159599897b3fd939018bdefa4fdfcbb8b4584845e21ac8d6162af82b5513b054488941fe4495fb62fde5879decbe4f046e37397335ec9350248d63704ec0fde710522813a4fa7b4f420f6ba4f1ebd493149a9ad4c061859dbe0a564abda69d3f8090812975481dff1db1fd5799040f82c963631726f07466700b740aec80e3b963239e34444316d70ecbd73635a0c6acfa903c5b25b623a7d910e23f573c523548d594c5723ad939f19a8ec18db7497bc38bf1ef0585710353f9fedf5d2e8c645d6e240de115c31b215f60679b93a54fa98029accf0bcc38db5fdab1548e7866075f609458f8ac2df95de90a9a4f9f80a98baef87e8175fcc76cfd9b853da4cd5e7afac5aad7b9089015b9f4b86a5a26c94cbddcb7a4fb54afff1966b1c084ca0294390b21469e6d7b033c3a6b5fba15b01cbb5897643b90554f58a1552de81b1cebfd438e4cef143ef67a820afe07625ac52408738a4d89744bb9483105b30bc2fdee6c76ba4f30fce3011d58a34a10802db74d5c1b3efcb1c539bf417da4b8011b43ba5d2cb043c7c0a6036aee6e5fa8d0a85ada94337e37949d34593788ab27ae9b5f67264c01deec1edc7f0aab8ae616e48d3b0d567c8093c182e1d858a0605f6d409facc6e9556342812242641bf9d2292f364f3e1fb89bc29eb6a63b1cf116b84222632b0d717f3e20d5117eaae57151ed7a15ae710bcd6b9529b13dd2ec7a891d481d77082e02a3b75fda31ec0be93a63d7c907331ef102f98a7e391e08dc0512fb32471be2c4933e68770ddf603e3f7e07f658d2d9353545ab76aea76d10fe624b23259cb07e0c4969ebb30cb87f07ab0257ddc40474daad91c8e4ae0d76451f18db4e88a76733801722721f4032864a0ff814095e0e606c41eb26693ec442318de4e9cb76bb90a4b65697b2983924bc2db7ceab0ca5fa83cdf0723358de0a971bc3c24db0b41d2ccad75caaeeffc5879ebc73b3100fdc6d0b502ae32d7645b6f7d9445702df64a2c6781c04b82eb63494c73e5f55b4c5245c4c9d64683e4c32340bd5f32d97e89c8d20fd9c75ad4e85cf3ce958f613e1bb63d39539af74f95145ed991d35de9e0a6c679f80d9014cda23a3c691680f04a12b89cd11cac78c6ac8f34046aaf5bd1eeaed87b8ff47bd0ee479da3557449e2b890757f7fe5cad2bc5e5c252912ca1c6db1ccf0947849123b8976ebe385797705176697bcdf452a3f2695ae2279a314a23fb709dc0d1103c7609f01c429a66205368596c613d47aeaae69f5c12141d74448cae0b42ac9009801d30543c602eb5646f5608aa9f42fc5da7f65bced00eab281da6cd0be0e20d781ddfac008241c98e99f1f6eac7d8ff5bf8713769a127fe6d9a142d839be142b6fc9356c43f1ef1f4a250316beca5857e76c9941e58df9ab8ebfc4885d4babcb42a27318258422dd566415c6988654299ed402477877d63894f4eccac949cf1be492fe225f9217d1b36a201dd1ae1b72351ffda61656226e103913c6ae670362182b199e9bca00fa153b963dfb8cb603c4824954e1c7894de6bdb66d99821aa5ea635f68aa6c6304de964da1f8ee394c34ecc88a929eae09337747af1ad9b08d9c3490102a1a4574c25d08db9e288c9b1a923e44593e782bf9c5327651c26eaa287ff16e8dcbbd3dbcf5ef6b5c75eb15742f9c4d13659a6dfc6564ce39cb82e58b4b41cfe7bdec3c9106992c238d7c79d0522b8ad04bfe8fe3509a117c575fc4bedd1be2209286ee7eb9223480712033f60f57b4a693ccf59bb0d3a31b1dc0c0833067507b749050eb5b1dfe670e38e9443e9a536342376dd53b11c2afd2a9318562e831bc33f01901800d091a7442b7c5584d28c749204d63179fb2d73dd1e2df3c80c254693eecc29b73452507f40e05e47a21b133c0732b2e4f72574f8f6e548507c212c601f4d0dfe27de047e18a0a0c7744f6b26021663f61f6ee2cd79d0c938d241f2f04e6a0015708d19accf29bb1d8304a8d14455b6eb85e942f4eaa014abaf0e702ee59d6fc720de963bbae7d6b7fba56920c146126fc9a2ee4c1a6d5e5821a39c0a36d575b7d0ee82bb0ebbfed5e5342cc67db200ae2be4c2f708fb498ab6b9786293b36a0c177d9765dcf1d660e6c11313b5967a83bd8c99d47b88477d107ab715d975770c9276383c694003d48ebb3949b8ade38b9d9f6bcba22b8f79dbb5fbe2ec992e003c9dd4e811f5a5ab7d7fcf3b01b8896719f3099dcb9fce3714c050430cd04413298843c07770a7006105d7b8cf4ea4d46898545765173d89138a4f8056e9c85761819209903de9d600f212c077433ec97f04c85c836104986711e030e4e675790b698b62321c5ac01d3f78d3fed0ef01c08a766553c1afc52f01191b7eb5ef005aa07d8b33b20e239b03d89debd825e3fc1d0178003a42372143da68431d80d863e7c21f5f5efb769cd67d272d36df36fc0870d3922acb89277454c309d8487534ea67368395bf810bf653d6173774bea7cc70317ea17e0a5e2d223a0aa9260b08ca1904029b3d21f6d47ef68614186054aa738abb9c7087fd93f79fa6b7f6d76908e3db3f9927d39fa5df81522a856b78dfa74be5db0e3feb8dbcc15c62445ac4118687a825c91cf9b58661b84929bad8da814217324696212e73ca6c5b65c113d124a9458762cf53b3ca5842f43a142d969dd3b0d7296ceea6d6924843b1588b0fb4053ea23af4d591a5f00a3259ca4f1bb66461dbae2e1de5b7108b7e40a38cb6d9517553f4cbe732574cc55f0b9aa88c43f5710b466851d0ea29f0cac2bd7f8f94e4cbf599569c8aef66fd8e6b4abdc33b1032310cf705629c1b72c7d816fec4c2dc5a791422bab91ffef88af64a10ffb636c36d88f7632031299547f7eb1432ceb60714fd0f5ec42e73e1e2a065a133b40e3d5feab4e7d3b8504125e6004e0eeba09a36cad5c5db9cbb37f577eb5cdf7efec8214b9461c25b2f1c4b27b5bac4798ad1bc595700257f1837a4a70c8c69233db6c0ad7cd4839625e57a75aaf8ec70d3317efb3444871291df28a26c981c046af9ea66b61cb850174d6a55bed6ebdfdac701f5a8ec2d32c97992eb9424b30c748c2f40e76c690eead13c6355c9b64376d5e2344e28a82b27a69925c7724ef6b59be7285c44191146b9b4317561577afe35c076cdd5d6e7d7a8b01701e0534c584de24a50cd7e01c3baf59fde666223c1a1e6d9d355eb58b364e1d57b45c82429831b1c1f597f2daea336bfc33dca2b4985c1665c450d7c344f5da506006a875235af3891ca92be7d2a7918782da8ae1a9db5e6d4dc61f846582b50ee2a3476713906b5065a9d31baa052170697c46b0a6fb2b8d6357831536043e9f6033b8c2f7e2acce88fc8debecc8196823957a14be522d3e27c7c9126fdf27bf85092ee68aed6775159ec6f43be1b56e16263a44cb31a03ac55f45771195714a54f3d702da2555dff72646c1d5c1d508beec230406137fd3fae1647f6b488c2165424b24bdef99d0528ff76ceeca8a922d19c98af4854d5adf2362e5d42a3036241820d5f0c4a177171edd711355a16d254c35d321adea9bc188b970e5bbc7a342267d89cfce9ec8c5e9a97b6586cfbe0304d731b66ad684e350262568dc5c14c4760a54524fec844968d680048001a9edbe06fa3340697c4a404202bc1ecce722c96a268490436d89d75d560573a0121489ec3a59bc45f824ab26cc08dd94cdcf2e2325ea22fe75608cc3086741173b85359b464076808d149222cba2a89be9b867801dbcea6a30b93df456911b52049068befcca0de60de16c76e156541645fb4537fbce5f632be2d495818751d49a864753bf93bce680e870026d619e0794756625bc72c86c3361ad7a166053f020e263aada5395a891e8e12e2845839fc266443c7a666b5b5d1a7ab302cef3e272be867d44b78629f1bf44323f8be0fca42ef67e85a2faf59ce67f8da79279d61f820ec17c559c65f3ace321edb1d94c1f12e67191dd0be2ba3626f36abb7144d43dd92935ee6f3478b60a4d7a1e4782e6aaa23ba6430f2f18feceb245568e90b81c755cd0822b2348853887a9ad4051a57dd77180c5fc5c6286d63f894491ae9e7fe4bb29632fcd512fbee9016f9537f62d0c5761438ff6ba984fd9691408c3de0535fc7779ded33687cbb8ef3083a343788550ac95285f8b05a2568dcf6077fb14a87ef410956ddd1a17258e98e31496ba24497ff8275692861fc3a7d2a768629e9e18bac35afe16583185d9d2d34e912086d777972e83d252833952988484424edc2e7d6cf4c58cee4f96016824d520be51f66a44a12b5442f54d4e423a2a6d3f30c7c80b36ffa946440a9f18f4903cfc661ae026ed80c38fb1df40ef2034ff6ea5d2f861d1d010bf3a45b959d14b142302a8180fd955af8e789e586ed0114e313e8d7435b9a414955eebc062d872d76bf8e64810c929fc1820a91c614e4a209b5eb98ec6248c052f4179e0627527e8021ffddad748f6c3d15e7835e38ec5eb1b9f8e255cb71b3d1c7def62d0e256f0ff618e895acd8d4f5278aa3f5372acf0376c3207dfea11403380cba599ba8726cbcc1ed071fa98f9ccb9786e206a2ef8a53931f96d2c6068ef379ec0a55da3a2fabe5b655052c663d4938fa5d7d990a78d639a3ae0f8040f744e7a2cdea5d0a79b633a9a7f137587e77805c84c197cb32adf2e5d20a6eee6af5aab94b27f1e32f25b99df760d177cf170b31f617f3a16eb50ff9090f7f68eba79503318a29b63d3cf62953a3c6ad70a6002fdadf41bf3b802072c141642556bd21007a1b34f1eb704eb790bf8adbbf2f3795dfd582df2aa6e0d5e19d7ddd055d8f310e1213a037fbb69a2bb352ef5386012389cdba8295a0ca3101beab3cc8016140ddda061a7530b9afe1cf2392483f7697cee7d5df9dd8298f249731a01f457c9f81ce3a6e5a5f56ff4b10f99c490b2d0ac6fe68012699316524fb31f0349adb61a75920850cf490f529a9a61cc4bf4eb95b3a8dc1bda92e9a93258bbae3e549e151a0899d680d365c19622571df8c25068dd840c9409aa8a14e26629b5e13bfd2d74b741da7e5a56fc57423bf338ba1e8ab4e8a0c3256750250b0c91403eecf4843328517fb0923c810808a6e8df410c2a3b9279ede94f510cd2d30ef31bedfe116a8da088aa477e0fac922498cb652cd2d2ed58f2d198c11d830bcb63ede9909ed7ce507bdb8b85ce686e509eca5ed163b184ad41a61ac1253ecfc37a203f10e2cf9eeb85f671465ca6734d7a5a0eb45fc4d23bae5625130e69807d2c686d118b8b39246ce3bb3b935bd5bccb4b72ed484c2a8c66403727c15382bef1e4b40162028179881c0c8a50b8a83ea6a90c46b2257431093a004e2ea2318254cfb69d523d3ca39c1bfefa679ab4c895e08ad4fb7e72f869eff2a24065d38d48b05d06f7330595a9930641d4984f9c5a48d4218ac5265ba35b7de23604c27c1d042c18e34ca04bf6f24ce5e85a9364e88d0be101221356d4d4c45eac030ee12111429d35382349c56c871ea2df74756122f0a18dff8cb53d1ec46fa0d5400132337f1aaa87e636b0edc099de90ffe01aa42f988880dfe01340f3a5f136d1dba9fab9038b2588af3006e92295132f48e8342eeb0f300ac289e9d7577213f235c533d5d5fe28648a73e0fba36d7a45eafcb05132d7c1e10dd440f0ce8fc4cea79f561288332b798a180b585a67de138653ed5101b699323cbdfc13229a5e36f861593af8b5a785aeeff5f9b2f4428492074b691bf76f2c720e3222443b7567071c5d848b10c295bd5a86a310fe7bc5250ea562c0e3188d9457f8d2e88b20e9aa30a58c85508979c891517d0d48c3c6193dbf68d0564ce022b6c99373bbe39ba3f995bf489cd0258258bb9b4e170d0f4c2396f46c09f946beb053a03d199d1b936d65f6d4109c3c3744dcb24fc6438b4c443d2027157362e222b28fa823188ce0a7f8afa113d2a29ecccc458de3b03499f27d1148f5515470ae6569582ef2a790372fab4478242981605a353037ac4d31962b59d8e9b9b8779fced4eb6045165668e3320d202cf7ae554015fe61fbb2d9b06a72805c6019a94c31c905da6a6770fe62f4445b58046846bc9ed9e2531e3e31f4d4430b03e1d49054658e72925fb63a093bc20dc4dd3a1c98b00e4e5d6ac92e576ed73174f1a77a26102abaab7286b9f6c982bfe1679b49f43e00516af6be5f89d543ed8e93fcf289067c6214564bb6c0935af0c9d6d8e8b585c662ad8e1fe268f3cd8728accfff05efe44496d363aeb248df6af1b1022d8b91cddd2c8ee6277fa42f8e5757e157a4fa3e8962e759a1f4ed53154afbe1cfb013d514dba9de718e6c443f48cd215678ef823314872294b16a3a4af587484aa68d345bf6c9186ab1c415d0969421cdfbb3c9b0390202d32690ca3a80f0d4bbec7915fb761c98c7acb9133a0bd731f4fd38a8308e4af3066b88052c33e1d04369103a60d1f00d80a292ad6ce3cd29d63de5420bad40d4ca1a8c6bce50bc604157da5538518ab3b6ab800795327b8ae884364cbe22c6cacafa2d3b7f9d6e90cb3bc82a14d829d1d89193228daf4d4475cbe1a794c59059172e88463203413ca537e47396623300a0f57e45472c518af90f9d894410c063957cd5aa3d22d3629014b5e39d675e6e414dba8dc7bec925717b340c240f83313e42f38f748248012a12a5fe54111186dc42db60ac30061553232726c8e4faf6099a0df5af77d19968bdb6acafbb889c8b2db37f5f1e865566cda57506130fa0f7959fcd07903618868bacb624a3693077642468b1c271c0556e7b94bb3c529f4b482666b3a6c66116039fad2e4be78268aae013bc7159ec38c3852b58209790c390e0b709d8c726940832b2da52133581fa04217a89016a7519b118c11045f1c559056aec361614803e5af39b930ce528eb49a01c1854e53c0a72b38b62a0ba19f2c37eaefc21a3394dfc91bc4c208422fec24fedbb69e7d19ee10ccc02b2f73e62853a03c3a3db0fcd9f13c9bf58c2935654eaf0e5b66df095647cb74094c4e1f1e5e350d2e0d39091cc0225b88a7da3d1fa71d4009da4a5f4aac27866c33b8940bca355a8b8ff0c352b5eb2cd0c7dd9f439c06d82ffd9ebc696388b9f2126368e0b23563aa5b407d9fb724ca06f5e38b728a69fcb11be5625a215b20cb8e0d5d8f20ec29e29682b2fd104f3ba3df26fefcb0e0579c9bbdeb2111c2bf483221f07ff796936745052f990b6bf157a12dde3c586efa8d6ab04f8aea14ae217b0700f740aab02480f2536663f2ffe394bbf0187008259de133590033f7b90a15a7d7e828e999ce3a7968c27f513bdbc28782c40e88eb69ff7512e56ae29d5c4ddab16ac1f129bc9797af3b02253ff24b973a63274493283d7d43851802e1c0616d14716572b4468d45be4bdd20928cdd0c5d1ae47637b00c154291f9c3578e9fad1dee89fd55b394b76dcfa0bc4e817c7d309530b46dbb5617a154d9fb3891fd6d11578d0ed7f3e6c9b2f69a6420da5cfe506d4d4573b755e705e8263dbbcd03723fff9e5a4c645e5f339d8c2604773118b86d7a46745cdbf1d9aa40ee8f662cbda681191a2ef8426eaf9304830f8426d359059eb05e7267f3fd6891ea0bda34008047557b8e0ec5a41d2031c0aa500d53744c8919b2ca5b8049fcc430cf71bd2d7b6b12a015173dfd04b8fe1a0cb2ff969c17d3ddb1405a026aa7a55e5bee444e534a5028a5d89d05524a1928adcfe6884e24ca43affc9535729aa41404a492e6060771f4f9cf772d935087b5a002440b46f890a7f24eb35d99cbdd4ad9422f56c6e871738a09a98c4dc5ca09a7084eb7c14653b96593eeeda00d15c60eda9d1441cdc7c3bbe1ff2d7b8b7b4278b78da6a8524e1fa8b66e4472a8d1194516d9f03c83ee49e9b1bd295ae44dcd55b294d235437c815fd75c245efec96424a26cad8594cbc582164504d082224885523247dd1feef2d54b345555107a363f03392f63ed63646d2bf7b99d6bc19c9d96169d615df0ab99f9a2e7e3d212929b077b59b8f9120efe46e78de48b89bf21351f8396b7d6d9243474deb25993935d60a21065a706cd157466f123da61aeba1aa3ce6bab1e4fd36cf5b11f10804fa9f6d6efe5292487ba021e1e88b4a19555f0f0b20f5fd003030c8677a107fa21a17e34cd462ec2eebee30fde2b9475ace3f0ab3ddec2a1090f5fc12838302a663bc6bfabfd6afa51a48c554be02037e6787b17726ccad0a9550bb2aeda117a3b8469e2b9c5b914f0f657cb00cfded0d97ebae8c6fcdf3e25d58022e8f237f0a2a5b8d0b23dd8819e646109f880e4c57b281442e41318f6dcdc1fb4e9467fbcb319deb10adc6c9047088c98c7ffb33e98c311f3079fbd6084407802fab277751e4d500438f7ad51890ea14489fbcd5ae67acb3648c69c7dca588e5f133952a2d484fff5265d8429f244a7386eb89ea67577a646d4936359d5350aebc84e6dc2ea76fc1171886cedcf1401bfa6bc441fc971c7ecb0e7543846a2a86bd5630554a4619cb53d4e88feeea190eeaf9821b52967c7e9630ae672b20e9d6dbc9ace9af222a644c75d6057e6139f2b2ae5618773d03d44d84d79965bf78b890350825151409b6c069863f46f9a7b4a4c11ea40b312b83b660458d8e4175d59d9b2215c09418f4f2a8112041efefb4044587b87c637d4242cb2db0d2d1acca2432b5ad1e62b9927020dcb82286164a217bff97f92e4bb1b7f9b0cafa337b074876c59876cf6de82e2fc3a52b14fb638a091e7e21e7e5c1527d2dfcd2d3d9ae2b76e45641b9ada50e7aca4969ada0e64968880a422012ac8f82c1ac963d2cc5e0b4ad110038a2903fa2c515afd28ceb28cd27ddd8bc205a5c6c238ddde85ebde5f8fee3daff3999cde635dddd69d6e4d8f6d0528735791a59d02ae8486a03ac791b09c0248774715d23ae645c0952c217ae1b5393e23e5a8b15ee788cbbaebc0c37665dc53ba60fb8c8159c6a9635491d4a957dd635b27da3ecbefdebdae6263cee35c11a2e5b7d3f5d7a7ab8774cc3d2bd64ebdbf1234c8dccd7c0b085a74b1b507207f68af4ef94503a51100300377325e5dabff9b8543bbc714d81ed7b0cb4eea357239d3b688b4f61bd78b051db6f1b5e02078bf89add250e069c45afcc7f7902851994014dbb7b16f492c6871110e92355fbcdecd5435f67492cd6a599c6627c710ce532f691c179aa17a6cea647cfb73f181c8641fd0f4b857208f02c49042c58c217ece512f49a1fa9936999d0482dbd03f470d85c1235d053fef52a87a5220032189d520398fad1aedd8b09e8bfe9ef97f571bebccceedf50c95e536fe8b42cf4bc90dd8ae4894f37b4922ee1426bae64b439ff08677231bcc9c765c034712542671375575d4728c04ccfe797413ce6abfcb49723e744bd029c115940c046509707e3bdada3e7469cc2be53853703231b05ce03c62a03f4c3c076d69024dfdec12d15816eabbbb31ab09c386104ac664cf0d17f08f25ce93ff454926bb886b0adf32872096442119ddb83c157f84eb2023380c51e6c78d12835f3fea35dee9334f8e6a6c4b961fe04094c0d582e7de663cc6743df45af3410ebbbdf1630033fde9ae06897f872f48ae7d9e92425a43b4cb6b3cc83c8c16f7de804dd0b8874f37ff35add50c9cc4aa8d0051b5a2af04e4802c66fb70a91c988dab89ed9e1cd119c3f8139a1ced442bafd599c4467a3748b07f228daf70945a5d9bdecf1ad87a24078753129dfdc39dcb70d497f9b0d6da74fed924b88e4cbbffd59ee7ef6bedb39b8c64688919df655350a5376e1474431bbe0138785fcb0d4a19ad538138ab8a0975e39ae683f7ad68fc7a00c95fa25be6b74e20f691ebc3d85f123196dddf4749e71edc233ba8cdc651354b6270d3c8e01013a0b30a633c550d98c4a84d9f3a8a921a102dcf301e869a07612586517e201ab5fd7d67c0ad848c7c4e963c78aee3919b3b80fe26e2114ff114d38b4dbb304c3b8c1f7770ca7ace04c50de1469fe04877a4ab0f625dcd288155878c4db02752a0624823073b1608c224844a0453118ad576d55e3e361781ec280ca02d3450065ccaad61da1c0f0235a0e071b407a1077ade82d01e3f397098bdb4f4049b42f997ffc3a268bf80e004d9feee4aa2b55be9f8502d4e9fbab528261e19a725ad52125d580d8e28989a7d4e768f8fdf63daa415a0201c92c6ed485ed16051ea146860b7709e301107e992f728f77deddbdd27241114416385f990593257a008b007c6406360772e3bd158d2d6c984ef876968d45b26e9377b3adca75b01255965e459bb75b43f4b3c519338468585ec94dcf35cc95adc4329d372190f88d31bd4fd305df05875777e25c73cdd0a384b3dec1e865c65cd0fe3cd36e9459a20946cbef517e7dac71c57d7d8756cd2ecb2eef1a1d9ffb26eccc4cdace72a016ee81d55ce48bae7d8d06e98dddbacdecfee46037ca857882513913cddde256608eac0007d85328814b133b8cfa0cf80de1bdc64c09e80f32e697f2c40fe39ba99535a3674c453870fd3bd57d6e944a9f6229340835937e924830f5a7e12815500ee9db7d28dd962424aed65cf277be0478ed2d894a339853a502d2472163fc62dee9b44a00d381b8d6a17ee1b75110e86769f56b86f9d27947385bb9660e0ad441de693bb1617d296ad0efffad05de33ac51b68a08975e2b7d2f14177ccd4a77a1642929be3e819e48e5194777e944e2a33fb6dd378c84d3feaf8de73a76668204373489deab9d74f839d4022d9adc63b08b2b7db8d07285a37c13214ac4a0311ca708bf70010e3afd5c18a7cf4f0b476f91ef9802f3d74016ce87d0d180ef6ab48ecee1b1a72a6a13628db53ce6f56332bfc9665543956962145013fea9912f5b287200845b3c2620be4e07c136ed18b28e63e80961470fb56795d584d7c2507dad310a282d77f31ae18a143d0bc65e45a2c3d82e67522087977248477bb417538161638685cf8e082a235ce49f2d4d2198df0565d48095ba949bf3e6548b1f47bffa336c38f071eb89818412b1a352512bccc31f4533c5c2ab38a2eb5e0a26c5034f3f48fd5d97b0333b083f4059de47045c1ce706c109d9e5ff00dbb3a9339cdbfaa1ef51e6848f316461dbcafb8c5584e25c7850a1c94926e3a95e3405cfe40a2f7ae612db11a26473e808e75b4f104502edf7e19e464e4864abc33c5cb96f956c51717695e420ae9d58fd662286cf567566adbd0d7b8b4e3c73418de38c84fdbb668d56ea047d2ee6ff61aae474a1d847cd5e813a1d463b01ede02813b5e4c7531d85f9f89defa74d3ffdf0c2942e5c8015d124211c61dbb4c213072d2f26ab035d47681dbcf6257f820d0b63c4a3dcded92abec68cb11851552f70b4e72cc208bb8207a078a5ea42d4d8b8907342f3188e604864989e1da24c03485c8d015ef10d3763fbc29a0924e66478bce8c1656b6f99e9567e42a161ad5d93890a74d974a0c5d193bebaeb3831c1a041639ae6972d9b1c914b421fac7db4217262d300f72098617350bb0ee73dd252561f724dcfe3c6def0cf5574c6c66cd6feddc608682e7de1bd76cd4e7d398893ff66ba5e7c59087db4214d418693218d8b639002c18846c8ed20cb1c632e857a2a0eed467fffddab8d95b6cdd2844edf7a7a293221b3dc98ffc18519f27a8ba2736e7d6cacd4e573a577369a906746612d16f00f1d999cdb8cbac7da4a2a074eb7dbe7f96f11d2e0f1092b1b6679f8b150c0e60e64bfd6f20f25f9f3a49b605c31a037c906f528ae3b4e84644b442f6ad318ed17ba4e2e51426736cb4c33d31e1b6778bb9b3f708f5a560709c17a170b83b8466b2ca38da13a8f372f34cfd09019db5d15df0d570539379a78db0ee61fa4de0d1ccb894add1a8e9b9d507e7041df26f4106b1249e0201804561847900e481c1bf34c484cd0c830656ba4ef0b4462978a78b103d2674c3160e775c2862277511a2bbf0b07d3043f99a48ee21a46b473297db317ed870f8b475cf25366e0dcb403e220239edb151c59d7e3816076dc7a5b211440c5d6a1d28a6705061ec6c4c0520f77fe1a13d34cb8749e0b9b4dee2d994310aa8f2f97ae2141bf06ffedb516e4502987550b078b76abdae1e5c2326e84d6483f12347ef8589bc41abd4ffa498b5c180a7052d656e67e56b033de7a9a3f948f72a9b4403f3a0b869b94cbb4620e1e81eb6790f7c689a19b0df955e87be5bf7b9e026b53f6d20f6ef69504955eae21fcae90accab8adf99ecef2a033fe82338ca1b538e2606c7cfe35b0950a279e1b3c419cd87402b7a4306a67f492a543db981e29224540dd69975826cb5c6b75df2d46a0e4dce2a7f24c5e9d5245672a061068d9e3006ecf7bedd96aafc964393d612230f1ff4f9aa22e0489ba9fd8f327f2b46e33b110484825f81c7fd5b544becf2b3bc1e9d4de6b651ae62613afd27cbe614ac5d208e1986849f4c318e656c712680ccd45763587dae28e724ec75f21e14e3aa489f4630ced5e341d3c1a4a8c53a9d737ad63077491e0cad403c34cb995516775a423a3782df1ba69f8b20a69f8ef1dc48cde4004f8972c206bbf607b27ecf256bec69e3a1d702403237549e8947745675eac9ecfd5b85372322ba42eeee6f2e50c60dc9dc3fdaa09f11ea8f293d937069957141932a81d424b52ad5641c93f6881e0168e42452be648d2385f82b23085c93088f5c3cd68109c2324bd7e6aa3539ca94e130229581141a3d84fc276fb21675ced2a40c99dfd826a86be694792b9041b68e3401425d739f2b76fd51fdc07cfbfe994b0d0dd7190659685036fbb1d7914d73bc8be72557fce2bcfc38f96a5972a711fdf08d7bfcf95670e04dcce0b69e009401229135e341547a05524133b22a7c8b0472ee29665c93b5af71c65586b20a710c8f6098ad9960c592dfc3af7e1c8b3aa49c1482541e84895791f1c258b89c81c40d9c1920a1185a9e3640a8119bb0db3aac245e22937719265dfe05e1dbd829aa664e12ce0b42525264bc80eb96c125bf46b07b6bdfac2011d8fe519d6785df5e95f1365e5d092c749df97e4ce7377bec11834c25c07331276d34a9f39ba0d073fb01ecc9928bdeab1996c7bc2501162fd913cc9528d6f212fe0eb1f25273f478d946e5a523c34b43aa3b2da98f957de2c80cbbe208f951040adbad8b90b4dfd7bad51f3e5639564960018ba2a48b1d30d816047431bb761e645d1b82356e01f73fc2adc870ab7c2bf9472684ed0d506ea0b795af5a1130c70711aae59c4a1e2ffec6a936e559d64316aad56f139102ea7cbf6fb2b47889a1d1c1d7000227d5cb26fbc85856caba5284684230de53c9945556b3c2803b9ee208714ed6a2edf0f2b8fe2f749cf992077ab8a5ee21ec5fe86c6488ae1292d1cb3249a20a628bf369e1da2a22f280024b32b777f8994cb1ad60c5aa4dc2e270d3a3a032352ec59a29233e8443a9e0997db439f691fe781060028baa95cd81be8f2df975073bb8ba5a2cd30575e40a85e4bab1c24a857f97e869e6fec53f2b56f339fb7be166869b203301f5013237ee087f4991c29943c8fd272c9a161f23808c4feb9d659a2dc01e9ab1cff44e9cd95e3429f9b7b528c8e670876e018c203d34c3cc846860c69507f0a17225711788fc1d54f7564aa637604404dfcdb624fddbbfcfdf4de0f8dc5109db89e5027e986cefc41e189c56b27c08cbbf6b5f864afcc512f19598ceb259d40bbdf34157272ec82f0ad134095bc467ecc83975bcc57ae22564bc4e5e2dc3f957df51ed4a5d855182a4206f7cb51287d56192ad2125213cd93d4d1193c7979dc636e1a9b56c784d0a72cdc02028e49fb0009dbc3b481f9bf83ba0e3ade3b8895f85abc31ec3e37d8518a829b6eed09a3840a54a8bb0daafc4101435328c6e133a154c76f144f1cae9da0e027714486e730bd7a85af8681a05eb2f12113ed594530b40915b3eadccdd24e4d5bf43506105a15df4091a3a7c7b53bc5737be71049aa94a6115315c6077e57651ac5d7d690333779bb8d5d7857f0481686335ac48ac6b49d6415ccd6f2c59212b0cc47ccbc304213365dec61bd14730d1fe258be093b91737fe9c686987e0268142caf2db009534d37c423c5f6a05f5e5b8419b369fc2bb7df187b5e2366eaf0cfa3e0096022650d79675c5217820c6778aa10a0f5af8f638a7bb76c052dcff810361ddbccc5899b6db0530aa8f694442642dca336ace3180173b821c5d056b31425ea43cb5fd8ea5800985714e23b58e10c6757e17c7c0460f088a69178020ea165ddcdc97804e50846343fadc90b26022a3bfe4f19a21ec8e2d7bdfccf1633a7fc88c0d5ad231db2f7821906fd7c4ee6f36c6e183115237481dab083d427fa63543b74e26a459bfa9535437cc535407d457a374049908c969625564982df66326031654a4153899796a2d55439680a3ff703b9614babb32ff88b7afa6e6136f8d56e9243fff1b016d8ebd53d5dc489e67b35618a2be084c4cfb00d8196790aa00bda9ecfbf74050f8f15bffb8376e91b18fa89390a90a80e93b7501b16b0c95869f82cec9ee3b79d9b74c32ade3df5b153a92bf071ac15d92d9817228000d0e063ea78580d49325a564906f798a2f77328825fcf31d47551ce8342543ef2de5000dbe6305dbef04c4d02858ec52d6bfdb784529a0431069d784befb750125d52cb9cd425ca65bae83bbf739dd0329cf30543358da7f925817be41e30276cf69e57f855396801ebc58ffd5390aaac24533c2194ebbbcb7496d6aa799e4f0f97ba290b84db7f2a27d0fc7353424162424269f4a3bdebafd5dacadcddd309516f5985bc1ea11fe8419b61b1ccef757cb0b341ecea54f8074355e1c18b923fa86300374089f4c367c51d70edffd985a237525d2ad22cb471d4a8e6661fad9b6792c6229351445581916949c269a8e952762ef316ec2e31ed71f47ec99b4efd0a0fea9d94297c68369e438c85a1303c28e05f29d341f8ae48066195a4466c2691c7754479222fbdeffa88b13632f349403338bc0ca168d0dba1d4e24055427a678dc53bce105d976974d9ec12855edb1f71b700c066b73892544ffd44dee41f94d4d0e94cd70af78a78b95b77037111517e2aa460382eb163df00c648bbb9160829d4a8acc55ddcfb30cd69553cc12bf2a44a05c3cf6b8dbac4d88ed86456e7f4d4392d490cd454bf6d9aafcb45351a4e227f7cc9bab4389c9c2aa9098be1c8b0fbac7f48808ebf4292d69acc14c24dae2582dcdacaf711715ec5461ac0ceeaad4fe69b38aa5aad9478f5d09bb5d2108aec6876864a53201a08083919d640a118615a2f555e066dc8d20942b81ea8ab4cc8a3389282b45d00faf98880fc4dba278bb844688a7b109dcbc73e5815bde1b3f580e6731a1c5c1ed06b4aca287c0520aaa2e708d46ecb84313c8416d5802ec8296ba5278e14fb7488a52a938bfeb3ce143f10d5b1fd979ef470f4612a5ad0c0f8bb5c1061f9ba22093d1492d4a86e7d6b96add5843ae8f40f5bd9d12113a3329ed8f47ba42c287973f09687130a8c68f5e7e329f11516ebffd51f7ab07b151e55beb8b92a06b4e7b3db7514940bc36224a4046ab6022181885ff5e586182285b9a6782081dcb413e777228e09d582538774572d1dee2bc6beae219391a3f41d115f15f63578fda281fba32c209000092ddbf3ff0a7a9e6d3d15e6be545aeb788eee2598bd2251e6351dd530e31c8f4daf4ebbc8d744b39d420b3d799b8095ab7ab57c94a09d7bba643b87ed5de851099b7b6bfabb03cb4dfcc722e0f95f167c406f3496e90fbc7c1362f7d457f133ad285d646c185ba58034ef7a31f32fde8081a28a485a58317770ef2e2786d20e71c1e2f28eb986f7d60584e45bb21fe58f182fa041c017661c856c5aaba28bb83735b2e6933e4e7ddb37f4fa543a02060dd9459662125b1f081632ce3f416e48234678bbfbf62fef8542e7750246b64f40f23523b2e2489450574190c5ad5b2ab1c7e5f726628f54c4f45e42a59927e0c3176f2bd330dffff0923685cc86fcd9c7445a6f21ca12055e27e1f74e87e0ab5167bd554e1f47ed2f9f3ec2f816f7fffe97542b25283f97e4062967d4e715c911255351b284974417cd1e10e5ce0b784f66b86a43609b4423bfc9ab7b781776c4add33d5cb3c5fc6426a6434b590bd604ef8af35547b22e172b28d92fc142edf45ab41f9d849967d5a8b559f1496f30149fd542043b96c86b052fc41e552156139c375d4d6a5d35f91acb6549fd0a90cb621c30ac5a588436d8ba56fab2490c0285e9900be0991d4a86f5fbe62304a68622ed7ae20366fae481887747905d63a3002773a93c8e7305d06c59dde04f070a813a7e1ae3105bc1cd09b211dd6b2bad07dd97babf08edf3ce88bb0a74be1530794291c640af0369e341e9b9363a5ceed264adada025294ddb898955aef6e53f2568a4c36f16445f2ccb437b7004164a354103e66a323e81ff2d3f5fd74893aa7ba32049d61f538c52ee44acdccd476c5fc9f93dea8dabf6b3328e6c72c656dd32efb0e7401b9dffe68637e746a1779d0c5a678212bcd7fb073b7784a6728c3cd433923c53af410a03720643069d352fce68a6383a74c143e0c33ddd5569067b17ca39e2fa308ec028bc4069b9d32ca60ae5b5632177e1609c450c9ec0ca22cc984e9a713f996097922ec916e457d343b92a0df57a26ae8ad6a4dd4116d0f06e91c2d0eb1546165d889b5c202dadd405c7595429ffd3e638e1ee0abda76beff0d9a29c7655915f1babda37ca431c5d31d23b264d74e1d0b4a5b91d911456ebb4838c70f7e83f19520acc99d8f693d309a66eedb4882efe7b9f37593289ff27e9222260a3982beea753dd1042cdeb38a74956ab813cf693151d769c15b6c623de37eb9c45b76394bf59694c5025f47900c118d39b2e2addf5d251390800227e10cd139ddab1b8767e65749587dff9ee09ec63ea0fe172997ba84b50c1e3d8cbeabae3d1a84a355f58d7ae224ed6180b549736fed956af2d8d5b9fc2c3f0d71d7b2567f0225e9cd60b0522b45b3920d1c647a90e270feb332cf67a249f3d9f23c0a1d8efc84dbcf59c1fae4eeb4d923725997720aba30fd99c345c65d05e128a1540387c70b36f8886a2e271b95d29f93bd460a4533e0ece8f8da77ae302d72d55c050e87d26250a78d24f09d163f34328405a04777467924de6f4adf01a09b143de4f920fbfc3635ee234c2fa8bb4154646000aa5bae2dee48d6e6d5e1ea8570441e5f15a1c38c5f8d3dd7cf63c9f8665002e5f76dfd802ec99e16be66460442c5cba3eebb53dec09b95ef2101804364c69b0a4d396432783d36343aa1c3845408e38cad79eb0c08c1e52cc7b61f55a82bddb58f8ae03462d445a029360c4f20e03e0796a68abc57cb0f125ca46d39df351fd50f54a9a80112aa8796827f9ec86039863e052804b6b9097b3f2be1f5da42af7c04539c2b29913581c5f0c2e6f8a24b1e20817efae155d3163884befced024230955dda376ff59513fcf0123165f0fd811c60124add5068a417df811abd14947a4a3ccd6a26486b2ea3a0fa540c322d1145912c71eff6e179077eae99741f8a741cdf233145ee8aa41373eae61b91ed02c1f2e7fa957f680071b5a8c7efa6dc8d1cec2496a830c7e71602b8ed0237dfd52fb1a40394e7bc88a5b4e679b0930b5d89e5a57abe011799cd35529b2b0612f3d095329bab1e3bdf7d00ec47477370e9002eac6b625834771d64ee78077eb46fe0e1a79726fd2b57653cc92eefef80e8639b83c9179ff18017e6fef05e588ceb0d6b26629e25e499f5e5cf155ba5e6eb65f06c598db7813a77a0c3761addb9855c19300976407b1171de8af72870224ea1ba3f7ae8c01061b7e4d41645a3310d992745438b318067bce84609c841696174199d5ff07d34d8f96e219fd970ff64ab12b1ef3ecedcd050c66a58d65f29bdf32a95a38d896d0c1ce7a5c5858b8c3264951c398306d00fd7903c9ec542895cf8548d4222d641be4d5d6d357f787aadbea0a6309ef2fe117c965c30ce73ca9b0a19244e8bd48448c4f21a07cbd60f87f2e977e05d3ea9cd14e54d4c3ccc983d29aee44b3b5187e025946bbddeafec6be9091110cddf1d823a9e0811c31fafe17c57e33bc37bef8a34bd7a8d2bb0967375f755a7b3cd31fd5f559af5f5518d2a866c4306c35ee976c6ade79566a3ab24926ee16196b06a667eb3c026849a879fa8a96a28aeb0a5fa82b956ebce6e651af437a7885e4387813e392300f670fa242308805df488a11374e671a5a14f60c4eb3f9a9b3e9a9e253d3a747169f1b438709fb32cba81e2aefbc511325f336df51803dd242577dcecf3801cc20da88c825dcaa68716927f1593e0b0a4e3876614a9d087d262becd02e50fb9d4627513e6d53bf4da5c2c8ff187c6277ee46416d3ea541daad2b003ee4d530439adaa8e073293346e108ad2b7c49714cbba1081f23437a10bf0357080926c52501f14c01d9a58cdaa30e0f9a66539c8bce30375deaef7de838ef27608e7f743808460342c0261842854407a96f6a4c1784af052820acb4458142e5d463bc16c0005a1ae11f0ca93e442cab5b6e51e4d223955055d20a84403e5d4167f9864d454557881b84502513a8a2a1a4aeab4532fe560f60462cbf51338287bd5b69c46dd7a51744f07ed2d6db4e8ee7aca1cde7d08dbd1fdf33cb7e53574c58a3d4c1a73550658e0482183cdd206ec38a9dcac1ce4c2e08d0467eba41bd8568458c2ffea285ac70999ab506016888498aaef0a0b8dc162bead91257eca2e7510cb2a8dc1b6230ba3e30d720d0b549aa0cf9ee9fb4c2cf5d1f36063fb38718aca7d239940f533e0ea0971996a5ef490d435d72b060b30184d5dc6904978156f419e97528f1a9f887c6c04511ab5366feb8d4600a5c6395a254ec279982a0f01207d319a289ab0fe93e6289b377719779409bea17b230002d23163ffead3f4a3933c9249ba3035ccb1a6b4ef0e5a757c3715656dc8d9527e3b7d1958da0826f75038f4ca100e6dc1883959b8116c784eb58d2d6972e89d9aa7dfe55e69a1d594e5bb065f4698f1b3ec7dc2242d470e3d6792ec34c9b401f95a81f5a327683f557f23af8be6fa87e2d6bd1968dd0a38f9a67a00979db90757daf505fc4beb36cc2edaf56505a4d5e6203c96af0d83289e8b6f3cdcba71e0f19d5583d585cc72c4f1f18dad7df33b1bc1f9ef4799bf75f4c93ba22368a0a66ed99fcddc474335bae7d702b448cb7da3894b0162bce7de008d11cd46511163c0211f44d0fb0b206de4a9f9b8660ee512e29d4e75708770f364acf3c11e6bf4ddc1e315230e4e6d1dfe8f9abd6f8c6c7572a1eb53cdd0ef74107bb0a540077d2a7464b215d88c8276c4297dcd1e6f65932c1fef6dee0b0b20e075794e72824ce291a602234f02c241cb261fdcfa30550c4e529ede6fa2c902e09c7dec0be5c62a2e9a0e0653afa58f2138a955c98733f43d3916fdbc8281146eda323cef47a6bd11fda80adc54b626e889e1c242080518566b3b6201d58e5c998431064fdf816b705665c17369a8489d4aeb155c2668245ce5cfedce44b705255dcfab3347e6e43971d8c41a43b591aa32c4c7454b4bc629e8af1b5d630b60a45bb8a6c90191cbd162b5a2182d377274df362df695d919f5f9f62a9e3be4520689dbc4ce23e0a5f686b053884006cea6e718402d4d314edbaf481b6633be8fe1bef7a3e1940d132820b9f3505ef2461c32fa1a58ce5c8e066b4c04c3542352bcce517e287bf80099fecf8bc8c8d0b09222d1c60cba29e7fce53b7174cf903d8b495249afa2f3e9d023f4c890c7e4acd6e478e4b233f70bd561717844a1c51389cbec85aa121df7d166976df233a25dda7712d54e3a03f28925be28b2d50349daeb0da133bc671a0b3e4652f10db068572b076b1b538d6ac1fbf5c1d6fee9b4a97c14931354aa463660e2630dece9b92584faa7438cdc1d583d2a81cdb506b377b49ceccf9b4fdd96eac6e96f7cc8689803a1ac5c87082675bd96638d5c5802d329a89297a8e55e8c03b66c086f442110b968ef596feaaf0610085f8ebb5cdbee21c5d94889c388d6df38ab022c9964e4741f60c91e094900065812814ad2ae868c1d43e88cf4759d406ee77ccf0e5e8794c3701af003528fb35d87429c054c5ab2fdff51abb5533e6cd0ed5a9b07912353946b4563ed24161078fd3ff16c6c5ca2efc3428428181be9ee50accb5b735988038a2a0a32253e0df60a4da619a2f83597fa4ab3a5e70ab67d34892900cdcf536d8614d2d19fef8e23d82bcc9cd483510e23a19816a59c6c9224f9e698d1f3a06e4421a02a69215d45773376b4de2e24c59fd2f9ce728220f4729f7e9b5311adeb07699c31db4225ab3299e61f22d0deca72989fa11f00c4d9bc5960d7ea0547ffa9508fb7a0eb4f3bee023fc6d95a168c47a9aec931364b050d1e5f36506a35c8ace94e037963a3ef2f81f9396657a1a162ba2951b0a709b098a75544dcee06718fbc8df5d9ecb0914ba119dde25eb1961d16ddbb762e03a4b7ca6bb72423eddcd7cf60993bf1d4dc5cf2b6988f9ad7b3c50a1ad316916187144fb1f4c2033b81d8ea4de016db249889a51a7869768bc407fe8b4f7c9b4c6b735dbcac5fb5938871ae57d086d2e70fe99f9db6b76bee6164c76ff7f83b34fc8d44d23f3a9499bd09e6ea7000b864e5f464b7e994e6b76baafa5d5e9b301efad7798f7036d1738b976c6d3bf58aa86b58990fd91210fdc783f425bdae3430b9da05aa28afdb46c04c940d91d67a66d74272d8bc7758c53864148dcb8ea7b67978542671ab353cc50c748e037ad792955a300a434e077fb61f3238440c524d5f32e4b40348d74da028d23f7410903340395811d0841949ccdfe923b66e35eb4edef0b99fd7f9f666e177a87594f7d2caa8292f3abfce19a8ec3580a28a4fa54f92ecbdf7de724b296592329d09e9085409d67ed441e0409c60040a319ca0e4c3a8240a50f693e2096ff6fc7cd8aed3c91715f345933dc57cf185dc48e4866d4b2de0f6fc7a852e53dbe1ec3ad62ff46a4f2f519cf608a97ac6b764b2765ea2e7987fa0df2a09df81b707344edd404f8eabcb6fd78c78d865de54347dbb67cd480a6be94c0d1c094e48b80423a314c0a2a6353b4546505fd194a75a1221e653a6b4d2a5af9644a9a3d38838b5323aef594b32854792249d6df7ac9900c5ed614e6badb5f3d6fa37ca6f16dd4db6fdacb3ce3a65b1ba8d41583cdb7bf6180f32c7e0d8b53823e9f1c49b4250da842d8344e794499dad9a8d0c094cbbbeadef7965ea083aec2a805dbf0331a8f7fa9c312a4aea8106374d6e450a391c8841b53d5b6caf9590c584a2249c74b780123e2c45c1a921116736917002872f6ad082051eb0d831ce4892c2da9f5d7e486c6862fb6724606c7f5c6b65e20c2dc0f72f674d615e23a186119ab6eb5a2b985d6badba7e396b6ba3285f4a2b6de2268cce0412b42063040c26a490e5e787e562d58a4eb8917568c50e9a3e100f33cfc1effde294cd26cc5f8a9f27fc4bd876bb67059a38dcd6b103cf89f6e9338d377b4abab46b9660caf218349606c8e12569fa175811a1cb59e3513f97187df7d413f46007d1c61f810964eb8062e19c68201a38f99ccb55e65db94df352cde2cd9f3d656ce2a04fe97b4ef4fcb22702b9cab65ab388bdea5150026485f9d03ba1e72c07d1233a67fa3e609a7169d94dea6992bd6848dbbf822a337f1d48ec5da31d6d9751fd8db0fd9b15096ff6947569d7f77e96ab6c68c8d268b34dc807262864e8062668e2ccf0ccf3af14210a3f276e41b8a70aab41f2e371c89d2aac4660cebcc909ed4f7fc605d123f729f277b47813b9252d017dd7cc619f9612d80d9cb0eb5b91464d1cf587fa16c4c09b268efa5c0edeb42bad5b5c466914ca65430ea3ae898396ae99376975b7119645f61d5f412a98e5c75b4ace75c932e1cdf9b9033ee470a549d20d370ca185125294952a8a7093647e9e6b803073821246482c4881a5b064450b4a740803677e9eaf812a60a60b1a5cc022cd0e2660e0892f6688d1010e1267e6e7f97abe6bfe6480abd87c51221858e244b151d9a841ebf041221d4680010acb0c5d7489e21ad18688a82dce28a1c28c7ef8314ddf45ffe993719ee238f588a3644f26385f647b32c119b3b5fbb8e0607131c1a1b22977bd7bf1dbb12442ee7bc7d2beaedbc83df740ee6289e99e5aec6ed43f5abbdecb59cecefb7e7f7ba05a2db677820fb891822e85ee74d9fdf9f6b499c147627471438ca51dcc6c30376ca64062cb1a2cb8d8286aa105353f5c31a208b4328415d04a0b427c443aac70c5511032948166cb3c23831632512881c2c60a127826b0e4b4df94c1043dcf584399ced0b229fd5ce9eb4a8dec2c3731136386d311bb7e7e43c39b2ef27fc61243637d0a2fbc6c5467d68c1fb6ff34c385edfe51aaa7143b148142102a18c146cdacda35bbd2aeec5aeb0c4959c0f002c90c354d6695479324cf53eba68d5f19aa087a32b1b15246a8421fdb30d25f6badb0cf49aa77d039270ecada3bfce9c790b8d06ce1c1052edcdcd410e00b2a74bef081cd9731c06c359b0d2d4c6ea06476c6e4e6e9dd50d993a98ca79d803d99ca88da9aa502944d29f839e9dadfe5fffea4c760438c2e4972e6a8b22111681120988136ab725f04fa848c0f50b32a2f1f40aabb0ef00220bb521a3e15baa15802d9e0573128b6e97b58bcc04b25f1a58e17fdd88b8e3b26d89f0ea00bb476b94afadf7b207e1057afd25a45a1bb71ecd37101c995c02dd1e54c1465e0100608ba30a4bcd0a54c0a4a70319bd6490ed49cc1c2cc0c5a9461f2c08b1dc278e2cc98229acc586774396b41b1fdc25f3f6f1e5eea006237cf51a649c809a3e3062a6c52e02fe4da039838e85bd824bdbe931387ff2427986abaa8c10421b0b8a2e405a2a215b2ac59a2cb98362c4c364b9ad8b283922c57aacc29b45491059a22aae80266e6b692d45a4ac691ff107f225898d88c116b466fc407a079f279b97aeed621c6761e408610f1f0893d05b0eb97344a3145799ad03f8e6dce58ace0c7c9fa2e31ba52221c7460479f38ea7b142452650598b0fab556221dcc505350a28b2b76fd211bd4ffa920b5eb7b4e349814a5619ca46ff2cf6944194290d90286236878e26e48b2258c1b1fa8786146dff3961bfdefefb4299b4a9ce1820a0dea490a4ccce8bbd19411256509c30a9c12c630136746df8fa64c005e64d041891acc08c26846df91a6ac626923841a2ea086f862468bd06e347a11143d6b5a9af62684b7dfb629d22627297d265d5233db3bb90f9de2f753fcde9b06d0f33bd18bdcc8ab44396b3ddfbf823be67bdf7b9df87defd96ffb7ed545d34e9c7302d5cd7e10f74edbdb3bb5698aaa41f9d2d3d312548df646e4516cfbf2676f53f4e746a1bbef6f356fbcd893690d993561f6645aa3843e057b32ad69b22954957991c32893365e2462309f3aad8b9efb13e714bd77f17baf46278686a2cb09e5b48983fe8a085dd2294694526a5433cd3f3ba09f29a5fa29f8a52bfc52a7becc5bf5ddcf39e794014dd8fc099bafc594188aa0389de634afeffe1efe592d9773399736be63b6ac51fb538a0c2fbb0f087817994e4e9fdf4f3b4baf88a64fe9a4352d48352d53bae0a2a2bcc0c20406161b460b68bca3dd1198a2683927c00a2b9e9eaaaeb26a67d439c85934e9c6e410ba9931471806ecee6ac0d8feee6e460d194c6aa250f304ebc3dbf59a09ac3d99d474f18debd3dcf87958b1f16be032d80c7f10251b0f31c34f8469bb36ae4529daf883f00b6dd56d20a175e0525fe03e65e6e13ee503711f3cc12bc87c5a7f3ed10cff04735bfc3af05f805f03fc4182d0e73e04aeca66787c090961863f575908dc732297c757907128775c165d2004886673943229cd885ae74f599273c9556b35c4dc15754416456c34891469ea98245cec4b4d49ccf62526366336b8674d49cb2e7b9cb0210688912af3222576a8cde81fa9d3080d2374a4062347288d1ad10792b3d6467486bfc80406ccf01bd1d9fdd89e1a7c01c1e36b3677109fddf135c797904a068d0b7166f885dcdfc610aa04c17f5fc8d00d4cd2d02608647b200762e4724ef490204e96b1589519a9792cb58e3232e4932aa1451b67c142192ff67c3b65538b2c4e3d20418313659a6673d226eb988a47016da710ef3befbb9fbfc68ba2cce55c75f05e428668174ab3eeb917a27b08f7ded3cd31d1ef60a08121883734c49024ea82139aac24b139a3059a99ff4fd98f2d9cca7899c2a48827337f72ca888680134392182d4c5e8ae6844d276f1a2668d2ec3466f6644a1365e3d893290d96ad9a3e19a0314377a605f3296afb6b0b324003452998bb6cf9d872ce9fc19c3333a13133c7ec2a36ab0940958adc7ba2e574883fee939f32993836197d61c2b6df785aad9f5d030abbb7b96d9fb3d62ed77f49e66d23b28193db7b4f34f56cb04fc55961af3b02212bcc7e4b08fb530c1a33bb24c9f87bb5a9ca863e509b92a469c6bd1df2001c2266dddbf77245694f2f4a4c7679e36cfbf3694f2f4a4dec1b67e6a84e4d4e5b7537b84e9188d9ffb10f64ff02fb1a04b14f64c3b73655a717985807855c2446ccfc3bfa9306b9488e98754fc120564b146d9466dc4f104811f77950e3501521f48b2c6086b8f7ba51049a43185268b310441869dc73e38daa305b9b2accd2744902f1a8b2014041461925dacc3e902aa3514839660033658936b3be349b68b4999fc1e588e64e9e3425a7290633b4997ddfe24755e651a5236dfb6e546934aa1e1dd11a6daaf6ad912ab37f84d2885459551a52657466974877725a759fd29db23b6d23ae4a3bdb8c695d92e782d2b456f0a3c37decd75aeb035de06de2cb47f7e2c07deaeb1b77f88eb9ec15f5a30388870641866c509fc8e7754bb508f7dbffd8138ada456dfea960fbb2090143a8b23d38ea9790f0c107c710c00f47f2e5c31679f5e076febc33c0ed3cbe7c70df7d3716b1cf8d3d4e8ea40e204740f57ba1ecf877a9ca62b15a63fac91a73b2dedafd12a3394eb671a5c2dc87ba685fdbf1ebe654d01b38e99ec7507fd23fe6ff8385b8c6a19710333c0e4d31599620cdee4f50080748ccb00f60a2b8b8c1eb7ef694776973ef7562f5cd71dc6f40c47de890fce07742b60f1fc8f1ee44ba63413e5565e1736fa4cac09980e9652ab1b939270ea5a41ec8696c739cb544dcc737f73197d12e34ee75700fe432d68c7b1edc6bc07d10ee87b84fcee67e03ee3be0de5eeec917ccc4363764fb7e2352652f213904eff3cf19c77d01660e0fe896123ef7dc736fa1660e0350d99c309a631ab1b99f7482d9dce7ed3707013c4b8db10ae37e8af9e2cb0ec59f0ae31e14812a8c7b57139a7bae13ed8eb90fd74dee670f974ff0f07b75bbfe8b6efdac32fbd88992f1e1bff33b63919df7ecd271b95eaf2b2e9d5c646767fcd171c518155b22cbc9308e3d8af442530bb5254849ac8d6b2e1c0d21e282d1a6b5273a88f7d6c99d9d71d21a7eea84dfaba277be9c53f4cee87d1020cfe330ece23b89318841dd15ec41e2eda3cb49fc2206dc7b9e0e27f1f800ca89af1e5cce734fb4bdf74620fc4e9bc274a2739023beba27dadd58d29df3e1109aee580855bce719636495bd84f010cd78c65938849e38cc0e6e426bdbbd2115861fd3da0531e0c0410fe86e795fd25a6be7e91b2f0aadb5e3dd78fcd42db5d56f588ab05bfffac3ff86388977ececfcb88fcee3d7e13eae1aff7aec3a5261f8e9d3e0a2e3fa1a228d12e91a27f11fc979b6996dc4b95de2dc3b62dd3ae2dcbe337ea131d2252e2b31f4f5bef541cffa956be7278881cb35d230bfe3a4e389ad313b89432174399d5c15567dc872c4d18ce7bb21228801c3454d8e38374b6439897f256a2731074ee21f52f1ab468df574a235af7bfac6f35c4665886a1a839f3ee1a766f0d334f869147eba06bfd35ce633fc5ee7ff361e774d4ee2ef461ac6c91a5068ff1a8de02903a070a9cd789e82423e6026090a0a012a7384960d58ea7af18648db3c6f4374da3c23f8001f758021932e591b6b775519ad5518fe9cb576b9fe6bae8dc91fa09a9318bfe3f79e689e08de5561154cd32c829fa0105d61b4cd126796663c3f41214459accc2218b31011926081a18819cf08e4f848c76f3ce15c375f3e6691fab348fd597fba735d9d5eea5594aef32bb883baf2bd2ea32a28d56e14f9f0050a20be3cb166db0772bddaef81b5bcd9e591dd2971625195f9e509a829967a4f1c83577f7e3ad36d836d9b64b6d7f6447b0bb96832de38bbd15ed1f33d4aeb2463136c3fe736efe7b5beb23e53eeebfb87548c39f933244257cf3604491b86d68ca510b4646c8abfbcbad7594652ad2d98b02babeacac29a5d737665c1ccae4f9b7c9b3c6a2c4c01526342c6924ed6e7a0689a54030a0d8e25514991be22da9093f595783c808c1adc23264e2ad9b568f53a9f14050275b6d2114930a2f32b91049d718a0eeb2ccf8068408b5263155d52242b6a00c449ba8572a15555656485e57c8e687d72be3e6d92d22a52a9ee54dda1d5e0f8d19e8644912ad112322338e142062d388031ab44559c9240410a2f90d8c18759fdedc55710d657f1673dd1ac25be80ac7e350ed1d9ea8966a493f53d9126d1284ed65f89748ad29625917e71b2e24d919cacb1099cacde133dbf249fb4b3c0f8a22ff9f2518bf8570d5c2e7b457d8f7dd27ecc7e05f675d807b27f817d1eee6373119f56242bacfee80072d2d639e3c4e972d26aef89b6aba85aafa81d4df1f6ac1d35f17cbcd933b246292354b17fdfe5647d1b34687ba78d4579d97de67cba3989f15b5dfdef68f51d5d24b222b83b0cde3480deb6edbf0a9cdc46df7c73a74f038aee7309bafcb6dff0586e5f6bddbe7e91ba891bf73497774fc03d97f118f46da22f53769f8eae2bcc33f889833af5af355aab9cca82beb6da5c67adb5d6e7cee8fa6faffdcb5dd1e60a9b167f4be3b762b82718642e81facc758b84e793b93d410c8014e5f707a2d83ec598de383aa22125614b97f6ad3f67f5857021e4e7ac90fadc736308fe759c85d0fd7c05e1fe73757156d8e5414277e32b487e2f27d491db7d04e6ac1befbd3f442da0b50e8cdc5bc55885d9bb64ed77e0321b4a42f78fdca7b4fb54c97d7a346b4ba8ecbb610b749fc7bd0fa4ca8aaad0f7f1fe10bd699b7dc92a8bcd9ebb415953d3a2a40684999a96a2da951af0e74d45ba7352a0143fddb8cd5224f7b94a306d92148552e11edb2f72120f39892f3572124f7fa2d9f61a02f57d1caa727fc8bf8eb3c2b6318857df1bf5246710efbfffc6a118c5773ce224c64fc6d1f69d4795d57102cd1c78e3efc0c8cc41371ef1f320a11d6b2731ae2fa58eae0ac3bff1532ad402ba31dd18eb8ddf7391ac30fcb3aa6e9b7d23a0d1241a05bf66f8a9127eca053f5da25fdc67d656f8b2f1d39afbd48deb0c7fdeee11726332367e8a9f1ae9d898cdc63adce77ecc6999856d56ceef155df7acad50c2ccdccc5a0d0e2a84019ff02c3b144f38cda35ddfd6d729fcb0ebbb4cd85589d3ae35159462b149867ed61c1824474496acb5dbda1cf3b3b712731b8e5661fe148480cff0134d9331ff9f2aa35134f7258a5493b6f814373a72247f4ff2bbc5badf1a75ba4e4af442410de1516594e22d3569cbadcd996fa95d324e9fea3d1b44e812537cc5b481854eaa30b7914597574ace7b76c92ef54de33ee05d73db349551ebe114ecfefa5faca42fd84d4ebca7d168391c814c65d06ca55017ca7db00a49db1f033c057f3eb00a49d38aa588a938e98f955ef555dffe679fb89e041fc82775bd00aa7a206fbd513dfec692a8552bb9ebaa35b2e88ae6504ad2b7dd03d54f5475f89271d2bfa6e2c489b3550fe4aa578df7498b176ae2f07fd5dc2fd476bcc57d2817f7a1f7c94c06dfcf4dc5d7b5e2eb7e5029dc30b5d23775fce43ea9f16e71721ae0f1d311085d42a0040f623c81658ab7428309544a401a218519387a4037fe961b7f52086fefb576b9ea2d85f0be9fbfa734a7364d5135a8a527da9251adc969e96b649313a50f54ebf7406e450f400cb851e86eef5ff5bb1143bdea889fcc6c0ee9c99fcc71f4ef5213497932451433e0700614261390c1ca9a273550b070c3cc2d9e4d2e1e26dbff491f665305457abfd9fe403caeadf8e96e11e36e69a3fd5d218499628c12922bd28461012d31403d51f252c61b233c479baeb8a31b51a6f630ae5d89b2af8cd9e5db5998898175d2861119fbb1d183ce3f4121f96d38d13db23746d4527d95d179e3719b61d1abe00efc3703fce09c38b6b1c465d00e626d5b590ef3b736037fd23fe6ff7351989ccb8be73ed12009739138016661c4143ae0285c92595ca961b567ed4a0a5ab8787ab2ebddc7c593c32a8f413fdbdd9f6c5b10799b4b176800c4c3f5e4b7674d29ce2e634979b241d2a49a81b48de53606fd38316355f6593861805f68712288c0172a4c8146892ec08cb1021733556ce84109174a62bca65466777bd694c6a040763689313d4cb4b72753184a6011061071b4de93298c1c66d839db9dedcc633019a3c91851c6a032061632e64c6194d0aa15c4623f5e44f1828a1758bce0927f0930c4a88151851460848975572e577feecd73ce49b78aedb5d6baeeb5b86e9eefbdd7eb3a7d86975df4de4c75dd1cd77befbdd6de3a752a7bc78118ccff662e89b63785524aef57fbf55e7bed86effd6abf5e7bed575b6bad750cee8b19237fcf02fcd8153661bff1ab34d04365f376f7ddacb51a98bbdbf44b0c8f3ba3a72a97147c49c628d06941d32f2998139652e5e410dcc06f85f7e7b1ecce37abaee007fad0804aef5c6f7e74fb5060801c23eb73d273feb8cfe3be4c252a45531c2812af4e20675243c48641abf6acdd90a48e118ca0dd1ce99255b3c18b6d430e4a4e7619eb71a2a44489d66f54d02c285c458753e02d60ee19cddab366058817f46acf9a951bae1aaddab366a50510581961894ace79d543185a3b15463958e8d41434550f4cb0422994740f5d3448c553aa071c687c5138853d20815e9929a01911745c154f5f95a71757c6c9ab0246279359ea763632515c6c8814fe72d63487cd5c65cacac9a90a131699a32a469f15342a6db4d3142a57ec543185526952e92994720d1dbcd9e369bdab76d594cd599b3914b0ebeb9963db15b6ab4823b5b58d1774994acd1cfe49148ce7eeee1ba8eed5bdd69ce7c7e67b94521c93524fc0a460be906b5b30714c4ac7044c72be53f7f167e29873feecf9bf4bd7d4b1e7bbe693f363f367cfb466b8321b55ae6ce09001eaa32f679d35ab8799bf9e02c69423b6ffe68aa28a55fbd5c083183c80c065850e5e76ad6fefad204a18275fbc2cb10298998e2fae8082840d1327679ca1045626b32a48ffbc03971d9a78a8a20316a4075196449591c58c2c63dea401e712466666c8e18a246738b152e26cffe211418320b3857c6fdb38270e4a69399b763522746488d278389116398c8aa13f4a2b43b3404d951419511a1c4da3ec92e793ce39e79c93054a9ad9064045943670b080e10b992f717e3821051a4220258c3503890fbbe659add9da8f0660972c2952d4a4dc70af93713fc27198f96b1caa6c7f570e276ccfe1090723720b4caf7f9d4b186f22e93ddfb76decc924860d5328d1f3ef19badbb3664316d7061b9cb0c2861a14adf7acd9b0c506251b8c92707df6f237c62a67fd654b9763b50b6fe38e8872049523b0386c6ece3597ec132e9cb517265e9a7889e2858ac3e6cebf94731bb0b4c584c0d608a41d74114a50382f4f34e0239e6a508eba3c35a00b1414234d0533e99c3d6b494fa4d1dc9eb5242578f6648a5a52decdc1368eedf997feaf1ebeebe3eddba394c62acc7377b2969403a2dda562fd89409dc5bc67df8ae4cbb5564bd65a634ee99ce0b7cdaee93f71e8f8e9726a78ad395ad46aab53dae364ce3953aba9f078f0bde3b5f8e25aad4f8cc77bafcf9cf65efc40ee138f94526be78643b771dd15dc1517e35addad7d6a2fbe4f6d7d6aede6d688237bef864225da5a95bb0e5b4fa5730febad50007fdcafafbace5d8e0bc1cf7de6ed38fbf73197411f1fd8db5bb1c4db7e91edebbddbf6f5ef960375ebfdf4ba7a64e7aff9ebddf2f5cb59eecebbed2cfa24f1630fd46da41307f66a999236fdbde7c6b3928e894a6ac2da255dd3c69b247994ed3fbdd6c4761a657b49a76cff5952a5ed4bbba45ba6acb60162c37677f7d966bb17c9a0b4bdd6a6360ef359b276ce3d3bff24b3f3db5cc30f3bbf7e42c2ce4f9ed839cf1c96f62449fe0edb3627d8dbcf1d320d6ef6d624cade9a64b1b79a1321b69816baccfaa8c2aa2c7cf0fb101c47da3762a4361546dfd2e8ff00d1e771849434852ad1a75b6a5baacc9b4a5f4a9a42df951ccabd0d8572aab26a4497664542ea80be91237589ce68d33741791ba5a50ad5d4510e6ad3369496b569d38f01bb646d2cc3968ddfe2cf4b369e218b8d5d795f6b4b1e6b6328625ba417b6454ab3ed476dd2076fa1ed5a1d2779aebfe4bcf4e54b29fdac62f3c5cb153560d982862849120f4f5520098193286e884860c2a8c99320a85421614654458c302e90b0830f48bcccbc8ced6294b6df99dfeb2aa3cb9f167425380c34ec9add8f3043c97677d79452b0eb0b6936159ae5e2818188036c4f678e4fc78f7fbe8fc7214152a99148856d4f34eb72c6d0b9d65c73cd474753b451f4d6c48d13b598fbbc7cd0d8debe0615bafe86454b4310b31a4ebaa4479b1e6d9fb3d6f582c7e3e321a2191e5d15b6fd9c6d1f76d1dd973f7bf36d83d5fd862b3134d18c68f6e5c5bb832abbbfbd912ac373f60482ff8976bf1cc460033e6ee0071027b7ef3e83fafee0fb48961fb0e29c935aa0916cb188bf7a74ff8de525da15ece1e0587e60fb577087dd01f4281441b1ccfb1bcbf9861a35d525bcd9537a493b3ff7f37eeefe73f7408ec19ea08f19c5b6dd18947f7b1a4df4b63d7e20efc40c744bbbc8f6d8c3ef7563c971df734fc11db1bd8da10bba9c4fddd2eebe9c4f597f374e3c7e760cea96761d3d8f5624cffe68df82a2ce11fae29b81e9b48b6423b4b53cf4cb9e041316286b5894f6c4b2b4ddddabad615192b3670d4b13ba97205385138e45858a7b2a72a91faaef5be0f7ad56ab95eabd1c4e7fa91dba887efa33350addad41229bd30e6ab15cbdd61c07aa84ae2627f8e19e7a44dd6ab59a4b40db73fde818a93f60409c3870f6ea535f7ece5a7b3bbf0ab915c709ddcda99eebbcceebbc518f2591ad85aebedcf720dcef73d69aebde1b8fb6272a8838f9be98e20a1ca4285bafa30a89ba9c994a3d1886dff7a5be158661ce8339aa9faa542a87e33ccefbf07f783f522930a7e5e22139a22e7cd7ce974a7dc913c462bd2bff07ba4050a7d56a813ee1ab47c8f548a5c0564bccf994c81acbd56b87f7ddfff0ba713aa9593b67cc004bbf767c9f7a0d66f0daf17df83dbeb187ea7b3dbe76a43efc1e29afc709504138e4d0cf8993165198c92104a9562bd5b2a9562bd5b24752e317fe83cf7d38822b60910308150504abef0b7f157e2b1586af411004c1f057ad4e7fe1f7f5ec2fb5c1f63e4f81ff7dff02c71fde586eb0bd1eafe78b9113fc74441cf83b3ae0583a276e29e717970e5dc107226cf073beecd99ccbf5bfd3852b70b56a05f5f46c6e15f6749df72f70ecf18de506fb0bc732a8e7ebd15a7fa9b7efd9a931033dcf2da147f44d171b44994b298c4173031434be068d30f4bc55aa86e7adbcd4ef84291a5f230c9f3e8da06f8739622896748720e81c08fece98f7d771a158821fd2f81a200986618d7ffd8bc6e800a21d864f23e401c3f0f5e1bf6af0f0b0c0f05fafd101449b06c871abb1548521d1b7f3ae9db1f4bdf33aff89a15876c0dadf0722ec9d577df9edcff53be2dca1ce4e49e36b7caa862881164d630cfa36f8edef0bc3158dffc6f2db5fa8b3b313ee7cf8ad9d0f7f27d4194ba19df315d1f99cffc652672c635be753df124b20bb15ee7c4f231ccb1a345ce0bbc00f272081b86a8ce5ab060d1a0fd278f06980cfb3c1f7e06b7cd5185f3c3cacef60872e1a6319f4bdc6d7eb8bd4781affaaf134bec648639c4ebac632ef9df11b4b9d4f7d29b4c3b16c7d8ecedca9b10cfabeff520fe42b91fb9ca06fd3f9e0675b482374830ecd59f2c4164e64d1644513659a3001821cd74a7d9f9035f2a562af852c1772a190cd11a9116d44c8cea558c76285407697c1b104dffb176bec4164774684ec67f3173ef76068029e514f469a844c2489d1f8555fb2bec571dff7ad5eb5fa16b85a7df87d393939ab577d5fd7fa55a97f727a0c626d0e0cda9f83abe73acd855335962c8e23eabedfd1c921876c6ef565cfee7876f78108256b775cc775a9cf9fa3f254aa56106b7b2ab10b52b1bc526f8ef3543fbf5389e007b1f67c2ffcfb5b389641ac0d8e1960bd78ccfdc1f7de7bdffd071274d1ff8d1274d1797b29cc26aa448aa024cfca138d9f1526ebfc4e059f8ae08faefb5e7f9fb3f37d5df7bdeeba9cffca14fd9d2396add7f91dd103ba43f5544794408b6e8daf124bba5520e85cf6582cb0f33a4e2596e0ab5ee0e800a20d92afd58eea55aad6abfec5dad96165ef57a0d7716508538f654a25ce3ddfc543824fdffb88bc1cd5c87a4f2c9fc8fe4ad7ea83a9fad497daf3f4abc2cfcf6a81a5ceef7c8ee801ddadd68ece28643d954aab54e38ba57a96ea572a6b647ff3bd9cf7bc07e1c6067f56aab164adc0f1a5f345540fbe8eeac1578115b4c66f2c8148d658b658abd583ab077f05f63c1017a8f3606b2cc107e1c66eb1c6d7ce0e4b67f45fad5e3ce66e8dafd61761fdea5bac5f3d6b9c4eaac695571ad95fce580a59d5ab3c9d1acb7014b29ee7fdd77579bcc182c6678c3c5161f1664f3997b0ad2f59bf02c1544a2c733ed50ac3d56a05545bde043fe5817a48a76cf8a0178e3fbcd677634bb71ef45aa92cf46a8d3f8e80ff4a8d3fc2d1fb7268779ea8cb10b6feb9734621b73a7f0fa0df89a52d8736d7b1beecbe48370af9ee5ef539b49c088aa5910dbef9ced8dde708f9fea0c6891307ceee3ee74bbb39eec194e8c1d06e3d27962c1f43bb08edea6eb556a2045a346b14f2edd9cd756339b4bb55cba6deebc6d6eae773acd67b465aa92c3496a9f7fed52a8feceec37fa5c61ee087e310ebe56315c25efddcfa3b21bfe04f2741f05d64fef7a59efb30057e38f6006fa020647978b828876cf83d014094a21f1937a0b0c0dc484246084a2c04b3bd77a3068dbfbe7fc75242875fae9ed53d15bbda0f6f36fde1ad58a9f041f0bf6f058edf83ace7581268d139a32796747b9ef2c4f27bcffbd737fee8ba9fcffd6a2cb5e7a3f6439a5d5ea4bdfd4dba48ee336b3f34b9531cfc4114a403fe27965d12079eb6f781089bfb9c1f25d0a275de134bd5e707450f4428e9ee74c472f5ac6f3ded9e3e4b94408b5e8dcf71e07363798fbeef9efb32e87e630f0f4401d509fe003d31672c5563d03ddaf4bffba1ff1e6d3d9641f768a7c60cdca3dcfdf7e0d8e3f3fe860a9afbeebd9f4b6219c45ada5c190480a8cd8d20d44d9f670a5b7a20ea460d57896b81670abb296897309124e81eed3bbe7cc476fdd8abc7b4e2864d8fcc982143060d9aa7272db4b8e28a3367d2a4e152451550505cacb0020b2cca94e96282d1c50f185930f5cc30c0da0a9b3e585b61082a2815940197a6a01c6dfa2928473f785c642eec9ee09d429e914c3727d036fde9e60420bc69009de2d962528bc48611499bbe0d23928290428ab9c50f3288615131ae2cb44ddf9585c693054f1641dfb62178f5209d760081eee7e26d7fbfe4f909b3579c1366eb156fee17671e8f9783caf2d7041400b667c785631bb07a4bdc57587d3a65799c75db766ce374b23e77c732abf408024d1cf5e9934ed6fae5e7bcd15c54e5eb1847e6b3303b30d9fe1f75214f55e3500ae801aa08e3bdb808f1ea2245003c0a68dfb3e6c398d922430b534d0b10940afacd286601a8f05e43bc88a81d19d125262441b3a1a47df8f213f381cbf6af200804ec5f578589f5e7cc3ee7823f0f7ffd03e4cfc3df8bc5fc7ffc8178d42a3682d0a5ae32fb84524a9594b66fe28a8da63b6fcfe56dfb6dc448d80ab6327358a389c323e0334bc3ee741dca7a9b6a64546bb53af9923b3954ad5527f725ac84ad704bb6734b660e0c75b43d9f2ca8540c57e5492c152a1a09002008023315002028100e0744229140309eca82d87d14800d7fa648664e960ac44112c3280819648c0184100200308610a0a1a91901a0768a7d818b7eac3fd089e1e02090b2aa5f6c0f40436d794201583f976c2b742ae7b143737d2f0f73528b88e1dc0ecaf800590a2eea4c0c5b3a3e1ba4262520a01f590725f6d17ab392f6e5dbc5e76e9d9082e16118440b27069720a28951b40515d0e9919a69f439df911f56fc5392e53dc71b9354fc2c3c673ad730c4ac1d183c48851c0a3cfb22190626a60000987cff88102662c39aa737c7873b49f9323af0ef2b5b136ba4706ba240fc257f959908ce4ddf04d85687ae11071bdd97598caaef3c6dc34745e02c6983dae976251c64ae8a4c5d827c0983ee644e37d8edadeba92d22802514122174eeed4ff82440713b14ac3a32e6495b1c4e338182079e28ec71271d86a08c0636f1482b6f923b95fc10a19992acc674b4267bd2923f61b80b29a08fed79328fd039bda46bee4f05fad0898f62000b5eccd559dc8c27fbbbd1b3791ea979a8651432a08e2b0ac2666396d8ab6e989a8f79f2562424b82047eb1b8fece82eea58232c3d4b6a968c362e878a3507b2b0dbae3da9d3f1c3a616403f0ac53c244164402dc31e13ceeb421c0b41f08c81f512f827d2e30f913493893e508bc5dd97a6b711b9220103bb94ed390ab4bdb154f2f59a723943a035ffaa958bb6804106ca3cef6ec0ccb91045bec640b5c17163e46079569257c4ae9b911a3928c592502e480561af0e86dafb9aedda79bd34c5d24c1b2ee75fae2507038c287f6a07c8b52b112040f427eeaaa99cb5be0985081eb8ebeabeb48a229c1ef1cd20d6c8303a4686982549e57c9ade882602e6539c8571e47749131f67d6b01e382aa869bc6279b9be04c0f20bae36043b2eeee31609e65eb00d61039069458b33bdfef974057419f31635bc88cc82a935c43da2b6528f28e13b60ac2cb14be07a052463eea48860b16a948da52ac3fa74e44d5518facd210bb139899d382c1db1eba15e2a7bf3122a90644bd74c08c94b8a207385c7e0ca4c2c1921d55317e68ae247d4b474a5df07efd2d79b30269fd0b1276010f192c38fb7b71a517238a81b15544ba7e1727099fb810487ffd8f964b9a5d320de0360076b6d43ff358a158be0b95b5c1fbdbab9abb56993d908ad57d4193a7368ae3ff7c45ab6fbeea343e4a4c4746d54699a7b86cf30f86b56b8ff45dcf013fa526e18f8c7f5eed97f1c57bc18684b2170f82ad682de855404bd487da78f8cd0dc7b5f73334cf757dc9110f765cc8ffeb821e17dc0ca45e9881ad4d25f94a213d4ec4b63e6fe18281e18b734166b01a56ab553d26628b5ff64389e49e8effdb446a68c75139d9947c6c2db276fd10903c249fec1a764028a9c5d8faca710799d2ec611f3634c9b1678e9b44052a3253ca2523cad4b622801f49d6e579f4894977d4a22d1d4cc6a3c30851d6b78823b47b1e03736329b3ae9974a9e0702f9917d9832527f2ebe86c77e2c663c6eeb99d4c8a8e69f89e3e9c79b44c14d4f94c882a77c2185f265fe28bed124841d1fc085014cd8991af2b9ed8569aa6b77c59700b117656c0acd99c7bd3cf7b0d875db068ec1cbb4b87ca3ae896fb3c49d69270809bef33bdbeb14d68f3d03f0ed057a38f61a08c335b67e684b805483ebb6f608def6ee44817f92345c54f67b3b0c4563aabec76ce79d3fd2873208b0931beeeff683ca2494015b1a7767805400eeef2d743560c8eef239571cc0980b39356cd346149a5f95ecac26fc30c7b7f36b4fb6b25730b1a167fca5d7ec7ffad310885d9e35a741a6b8a0a7fd061b25cb566d985db4ceadbcf9de6f626e281fe461a9aefde9b975a5e191e3f429a5d0a0936133efadfa75886bf00a19806a70108c903aec9fa7651afc2f5648402da2ff77b2236ff1369c0c888a0c8115c545871e7543acc07ce137d47feb440f2c7cf03fa720209300424726a17eaf8900311baf9de2a43d98b08b0405703e5280931b2c474740fc1ef0f9004de005710c80409b5d16f8870448b8a1e117e7b0790b347894d841ca818d340340aa86361607e2bf827815108d41ac02c898f328120e88b7ca63ed1214a4215f6799fbb107517f8828e51745a3889ea295cf7744c86c1359408aaa12be781a3b80d22a0b255533c4ededab00af6d407bdfbefef542f1046d9d2488ad7c48e34807c24507cab2af6e0023ea722bbfd7fb1510ee6d1cdd7ea71546c38952a7ba765257a4d78dea1ca278ddfad8a9f55cb36cee74245bbc3affc7ebf933c5eb701b5e0c908836c16824eabe33f0b58bcfd30bfd1df4443b7ad1e3551118806e2d2efd6205fb15c0d064f01330aebe43e8eff00587d536b6904304158ca1559c38efe7185aa75dbd894078258cfbcae13a4168cb19cc4cfba1840850777855ce2b3811b83b9f039f571057e092df56e858645dc3144d094ddf3fdc891bf1640140efb87bdb78649d8e6eb845c2be975c4077dc21c1dc896e81d2f0028519ae90e45f7eefea6a820edd90c00017c9c42179d75da1dabb22f2d520b05eb81c2f001b41d8353c53042c46d6d5d30fdf3afab8376d05ed954c0eb7b8d45f2e5c69539eea191c6c8320ec9d6a5496dbf0011e2178067006c2ee8b3f8d188120ad5c40b87d820b5e4164167e3cbb3010760891fcfe3e2754d77840be481ad30261bb4ee15ed31684fe942509d876b8e115351e80a382b2e7e365fc55d0e83a967e91c47e283f4539368755d1d7b9d030b860b4b040d817d79e64370b14b52b12fbdaf3a40843b6900df6d6eede028178b840911e1471128e624bcf0c391eb4a1a69b935587ee30341925df93159c2a1bb7690b6d31667060e1b2a9d03a0b9592b6ba713a4e2a4b4238e225591aac90c7c5b22283f88480ccaef264ec83bd4166fa978d5b89447a4c9ac51ce15cbc8a051ce26493f81887afa0fc8f08cd1408baefc952a83c30d150ee0cd338f10e7dd40d024a95e250ecaff53253c8e34e9e395b4e8637a658d309f799a854771c584412b0ce8b90608ac4961c5ea9bf8e0546591a5b64b5c5061dc26169f01a46a6e1d64faad96f8b248c6daab15b8fbf2181a9522a25a58f8a01b0a5afdac3070875153e8d8a664092a2687b6124cb8fb45547464a54b62442eaf1b67c4280052188d0d00922a4b48013b5a439cd463b19330e52d8b01c84ec1e5602c489e73696d5a282aff880269f1d3a5003622b60cc3b85b9b9a60a84462e807c3207fc387d10f92aea1e29b786b2730fee3969896e5ef0ef62a0c5cf25ae5cd17ab43ba7474e2b48d7da4b13d35d8a905faaeead05243c5d308b2fa26971ecacad981858e07527b4ce6eeff3e57c888000ea9f3f297379e83a3875286f4acc3025f2bc524d747aab05db8b1801f6ba7240bb227b45d2a03bccc813a1c5aba876741fdf4c140defce1e85cd232e53e753a94305a8396dc06be5faa571f40a8ddd8f3f4c882c95e52c125e1ae36d7ec236217b2310ff197fb52bf58ec4863ad34d649a9f7f7904f62aab4b9817c02ada30098147d33b43447daaf88bbf23abaff8cf365a3fb570309a27c30f43feff585138ca2fe7fccacbea3350810317a10e744e82761da1cf142bdb17e8d36b319a48523f92304dd65d83597a150cbc233d50b11323159aee13b1cf92ad37bbcf01523059f44a19ac8cd1020c0f83c76217c744a62cd2ba183d38a3931a6ff81a201d80f553055c8baf8646c610b441686507979e9b5e7bcf167bc459a45a72f4219768644b2e53a2110b1109e253721958cfcd44b06bbdfafd0d412c4f306cfbfdea235a8e67c76de70c1cdfe0ed9ed1a098bb10711a4253af1a9b82bf11911fdc813585a9d045541089cbe81049d442fd2ec8c1c023576b9696e3fcd29491230d4de4ce80f2b6d4248339be4daf30e64f850c103f54a52bb6c797e244296bb5e1b8e36fdd362e08af4362ab62755d6aaaa3b181df0225415e49c060fd061a80dd983c7d17225ea70fd4e3df007044d108be8c173dbb5e2158742f7271d526e2083642c37e0ac9badb95e945fec5af9b0ea29a6341ac0ee18e5837b9d215242bfa2d93551d2924e5c37ae5ca3281e66b2175b68a0b360f52ccb652b91f3acdb8c9b243259416359b78a09783f87222e3902c3cdbf6212940c11fb42a66d98ca3ed194e3be815194b036e0f247407ad10810042c2d3b493fa91b9964ada724e21c835736794f9fbb36027705437178fd0b624a2f83d0d020d81655c144723e2b3b6de7cf4c4dc9f5222730363687f64830f59a80fc651dcc450e3eb44bd10f29394651d97e201cb419557b673a41532a9c115295c24d8be6553e5d08dd9e9411ab840030ff2760cd920061df018b63c5ea9bf0bd2e90670bab1a87cc0510a3842c4cceac8aa865af5ca25dea4e36bb90716f41a89f9d3cfbff91a6567a81b519bc8b1d7f5e47b172db83d7fe5677d177dadc423196e496cdb4e691032797b3ecc425781a2aa66577b80731ee02ba5bb51610e87a9053eefd2e6e66322f55917c26fd364d36503d40eb883811fb2b3387787feeb7b0f32464ed72c06eb4ed56a9b3474a5e1e9ccaa980ce6fcf64a24b4f450955b4618186aed1e455767f9d270258b13463d78c0c416f7e5970bf0c433b86948f18d599c10715b885d06468c6a413576d53af52f5f04910d09cf20bf016275031af61bb4568186a54be73557699bda500ed040da95126807db1023d17814833f10689a6b4499be0655fe97aaa5fc2f0da14c20487ea91917641706102b515f56a9aa86ac2ac5630efdefdeee1b760bdcf258205621554a803668fb4413fcfb9f945e0264e725d3ffa25f39d9b4ad8e18f878515aca4066015a43fe25365bab96bb1a657624551915840d300dc88f97d8adbf52636d42cc374e9ef0396e271aa65e08988a238464600c396532575ef7f32f4d52ab9b25fa7e41d9a116e3a50a52ae410c39e63de70ae29afffacec29b4826061addcae1f794372493f20d6452ba41ca04f8bc9d437c07a8e791917dff2dc0d8363107ef2edcb0321c75c5e6b4f98032005b7c67fc7b1a6e9469e153b61a458b8e679e06abd0265093ae3b35c6a8e3dc6cc4cae51207dc6da7fa1e3588e64859f93ae1821dcbc1db10c4831e069e9308cd86bef0491e8dc0ca279da16c5f2d295357f1dcfc6e688f1fe475b54012fe96a94ebb38e6fe4c079e6b2baad1958847dc7f1ae983aa2cbb1509af9a8181ed1aeb8d0c32be37153f05ed448d967956318a080c4ee32749be5544d48e2caf8129c96afbe636166daccbfb06d69dafc2bbfe68063c4f33ea0f235ef6b0637aaa1e6cd4751180469d731168128024f3e11b5f5565b47d0e179a7862461216fae9b2779e5d9bfce636f41578613828446514663c0968bd1803c9c92681edf76cc2424e8b7d5868b4a044aa1b4af2e23d09840d4b1620ce0862490fc0b73cf7ca1dd3bdbd8e919b0cba513931c30ff79a6312e9bfba5805ff5ce7604c8d9a9b32494f2aa97aa6fd7ee39939d672c7818a8e69f940a68edd87521d5867b4f12aaf6bea9ca58c889a7468400141be10567701fbe783e1275ba16dcf1e30ab95e609819f50b8a47005b7f86681cd91ba6de3205978618760e6789a69ea89fca24bcd927c296aca645c67b7bfbd058dfe53d76c1a5da8d06832ccc9b9c42d80cec29d54245e26f182df44d0bd8725a181be60359522b44a578b1e1fe0b702b1ad9a22ce46028a814d28d1a95fe49769479a08fbda550007d7d46419e49958c0a744d977b109412ba1e523626ade01f1b96388f5e65798da568a40ba23292c6aa06dd732fdac0811833de917836041aa9e0a367c205311b318e9149a47f0cc4d7f021feb4f67a7bf39d4e06293d04f942e2c26ab212a0998513c05d0fa9ee622cfc7d9d27e96b7cef147e3df32657dce4cf17e4b349cff5878a71b36ae25c65cace40377ae2976e8658ae1a1b94e47b585f4cf335263c5bc785a6ff216777d782cd2f7b764cf24e2c818a141f3daaaa3c64bc91679cb7ba3d2f89070da124f03d7e47a405b68885676f8c8ef9bc4c3ac7949d276d5d056e537a881574107295b1701a143428f4923b7b596fc6dc091aacc5aeb139033c7e6b6183829866e84c08c4d79e1c83c847c636c5102488b0f0d11678ac4b5d0b020a837fa417b22bc1900e0fd5c2c1a90fa2d080b809d862d2e9173c41420533ca6644e6c217db181564b5a745e7f369b108ae68120fabc42e3bee7bb277ae819bcb4a7bfe823b8b915c1064e227eaf9e23dd3c9df98b2617466e335bd60be5c35dcc2eb6b177950bcd14d21a83341a56b9d91fab80970f874d432f76bd19644cd864f21e62b3872f9ed5931764c32202978ba6152c1a070705cc1a461801e18011c699745b241f86674d5c34845342bb2e494d13bad7271924cc95013a49a8c87a201529105ff28579d00c916c14c3f6c442780e2d4b6f0cc984cadfa4ed6036d1072cd9ee05140341b2c78cf205472c2b9516c1bc4c815ba9fc11c66c91db56f38b7b18ce035f7c39a2930fe899159cc664920f1e4ec347f660e9b7d6c508a6b0b6a06296c21bacb8272d5a52c68b29551699ac4c46004f9c85caf60c3e5b0838fb36ccdb52f5b99022ef060ca8505ced54f06a6697e749fd26958f337652d7640b0cfd3aab6bd8e55dda9dcfb89494c905d45769e281295e1bfc3c6c1c39ec00098e555ca50afd14683ee2ed02c71a4b06ca3113efda2c7047c2cdf9d334124960df56bc4be18beb3f9905b3b226d52663e559794bc91751d27860b9b4fd00ef8316e11036dc39ae3853753060d5280f7f46a29e540f4d494b6bbc098d11b862c7b5466d7be9c0ca13a0f5984ada99f28f203c10333a09337fbc8c8ed65061a8940a5cf874406286283d2083b0ded2e0e00d7a3aed639042294347341472e77973beeecefeab5a266be2a40702c0021c57c1e993428d21321bc40b3c19707010369c2127e426c880a8f07d2b458b56f4c02c338e91cdab1e6af8229abb27dfdfacb3123080be5c21eaeadd937937019571bcae4090ea0003af322daaa510070257f5086694393478a0d9c116ec83fcd6b75efcd41047147e01a71307535644acd3534b13edd0e38ac710d90bc18de2a5cedfee95cfd7efc0b0a3a55284f24e80c39b9a138a038a7035a6105098719d4a97c95a98d06af0326ec5d4e84f60338dea20a15d40c0933a9d47d25f8915c0b845f4df930fe11a7730d1ce505046cc83814b6f796c428c430374a0e683e70ae15a4bff9df844c9f3c6a8c15166fe5f08622a5e76e4b32f55c395f4f8c39a763e3d804a484ba0daecc2c4f57d57447eae156e619083c1fb91effee082a55bdc1cfc5cca3b0fa22a058fe8accddba9fe7a5b2ea43facbb10a96f51887332f061ecbaf509840998a553958ee2b2b72b9e49acedf848cbb87d9d811d52ac35f4aa7aa415d8add9582d1d59c44614b3e8596843c20f243c8281f9dc8f20f89c973e7ad93b5f65ff651eb5db890b3ff41dc1a267dc1162e620b611726b988478e75c762925651e93b27c1183f94267abc087e4196189633c5c71320a4d2c962cb5618a73025b596c0361a9802a30cd8e88abf1ac0757686fed7ee88701876edec9b506cd11438a75432a6d2d5ab906f99e49ccaa715567826000a2429d653e10c4bc5721bbe10934d85ef8780366843210c1032fbb62f19b03cdf43702495353db950dd5524321c7e5b5c18c18473d627af884113181403e5498a8a861de63a0ceebc3530c164f613163c525d428f591e7d6b38dfee4deb5d202f42418b6a112d44abbdc0a9955eac615bda6a6c080503d6591efa35f7d3d01ea10223efad5b4d888d49d197ad598e5114d118b02ae04022f684a7dc11445d85a1a8ce3e8cddb2ac35288820b263e886ba932cca5a1a40040acdfc2dac0389fde32c5807d5d3a2bac83e187b7bbb3eba34c0ad15fa4ff4a59a84900d3e0f7a94b2ba4077934d4299708889a09bf6941897ef4d54ef425afe9521aeea7b5da47b685eb4586b3766888329cd28b028be7ae8343f314c2864a46f2fd8a7a68019b52746282329c0613b0d810048010cfdb02fe0489b29eb5bb518ce9f5d44ceae9e2297dc03609e667f2f5bc872d2e0174f19bee0fb4d65afb124a2a661d1b00409f872317e08ebe00a75350239f495433e8e1c439d62e8b41959103400d1137b657822a277e6c9f4af126dc582c26c0b80bfb4c5e796b6f53b4538837df3789e7126d836c4c9ebb76fcdafe1cc858b0746840dee93f074d678498712c3009db34800e76f5581a8a02cc196691200645d057233a5e2fe6fb4e93fc414dcc8ea5cf3291b09b903e4747ea3d7fce2c7c27add38cf82de84d581cd55381a51f633e39b8c93a596fcd83f6161f190e8e445b4081639e0c3a2bc16a0ddc1f724dd9873fccdfc08fd8c992bd3d848c25f38b459d008898a07fca0221488b735adbe527f14b932d4bde7446456a801a447d5c9187540d3d44970cbec3ce406dd6a3767c6f2bf499b8ac044b7949087382c09fa6133bb325908ed0a7ef1fd750670108aac62d1999084d211d40e9ba04822e006d215c3338e35ce8c3ee342f0eebbd14d64d00b7f87e821cb600ce452713f0dba55cfc3f514f13320efb7e82608548b4f80cce21ac7b45e0a4c642ea2fc99f94c09162c097441f0b4c2079bfc963f17a04c5abc8244ffd3f6842d7ad118b6eb98d2221f2d3d7c80858101d4b7582c8bb93da6aa051298527ace49a05a56d52ac3fd37825548980414049b2ae2cfad02d19624c025a18c3c4ac804af6dcc22c6070276946f326b87bbc34799e3d60714b1279c2a00cb10ef4f4728161c368e3cbef67040ef05955506f6d2d729b9538f210abe21f0a246d4d70fb81a8409684b4d9c7d1bacd8ac0259dafc16c6077e1cc363f2e7dd53ab80f3a62d3e311ff11d1715458bd139cddd8b8d839892c78012c5755c4bc73e18d1130b0d70600cd99f44eaf3c5ffbc89b0f11ce348b5210bd67dfbf958f8a0018cf27bc60e3aa436d74e93413e506a5710185417b4301701ff40de9d2f9ac8a24e4b3d774ce9ae0b3606492590e01e8d5b9c22eaa1ab58379c602cac366edb79620a3d0416e211b0c1c4d85f1eefa78e7daef1060b88bfc7dd10710dac09d60ce3d7560659babf11be018ee4a84c05433d65d8609387cbd10c35860e1894a0477ae62832f1c3571e9192686d638dd43a5d56d3ed0a60c5cf25ef25d66120707da4ce8c7f3746f2a02d04d3cd760bf9fd30f68acd18103578e029fde18a0ff6e8a173770c54907d5bc6e3733af948d08b5f19126a12d75825d711c963c5a361b2204a264d7c4ef8be29e55d388da1a22aa0c0bfeb867a2b39d43d6d04b4fb33838da1892cbc93060420b04213bb97dcb16b8630bd13fc0c40d23777503e60e385744dbfb8968ab6945ab0d52cea56cbf1ff482bf3ea2422406abc282162e930f2992bdae36e0398bbeb005fdcb168315f54a245e9bdacf8531ee7ebe6aa3cad5453c449785977a5a1436229ac5c1dea661ddea5b522e3a0d374069602829be13e161eb7293caff4bdc64e90425ae3689d81242a6227776148acbcda47c24d7576d9b3c55db62330b63dc8028817e2c7a4f4c6559ac92ddc05133e70701f415febcf2d1b064b7178148f60a32bf10b27b328ff8dabb0d688ab45480f822b21c855ccf27a6d7f16888c44cea2b614eb5beafa4ababbe1d3f66baa80369da1e0d2b7964b4d5d6f33b8aea95d4719141300861d452e318fc63397d3effbd8cefea207c82844570672a582ae64f300b6ec358355bf4efe7c2f659ca3ba42a801e5d476294e62ed53396de74679314eee626dcd1bd936e54c5abfbed10725d2b950cfd5b7b8b1e303e27cf19a119b811b250b19267a13e9f63b1614ed94819a7f588e20d42890c0bd3755dd09a12caee7f25a6ea127b8171a312bd1c35d0424018d7238e4aa55d820c377321b29baa6f5d9a206ee19f877aa5d198e33d6167262b06d9b0526fd4c88cec127275bc022e55c56c8e889ba98cd8dc01773209b2d501ae3297b2a7035c652a645f075c3449960ef15ab1abb84e54c8c666b8904b881d49c3b5c01b74c0960e0617dad654cba790bef0866c8a64f296695344d02ac04c79686240d47b0ccd40c82b807660ee5d80de12c8410668c8f1dfba42ca65a1d518905715f96a0259aaa79fd5afad03c9bd6d56217e01c32a0a0778f7461f8e953954dfbf239c4e18b2c41084d29402399a4262d078da644b091b5a2a70b7f149ff529a34bd60311c64623d6d79af9441394d471c6b4353219f7966529736509446aa31d11452b38c65568a9460a5fccb0d4457367baf58bb5e2de64d4715cc6a4c8f6fe79d682525c3a199485b9bec25f6df0e2fc385549ae6739b644811b803a0c7be56260708635c294e094bd9bf84e60864fe90834577aa565d495f7146e79078c953487327d1c264be9d9c6277801395d68c768c7cccb6a872464898772f54d851ba12dc3d2abd6901b6280b2131865f1e0a123b07a746945c409ce39b97fe716c2eb52e5828e91019724479f07ef9273dfb34a8323f95d5646fa9b272c2a801a5594bc3ff86ee37b6c381ace488a9f9d8849be8b31a89dde669e65e35a4eca44d416dca80ac4df94d976d77ba6c5b3c32eff095ddc6eabb8defbc73a6639491ed93caa1ecd8fe8c383af34ec6144eedbfc9b077f47ba7b7fc2489c2b12427e0824bf8ca18b2152260b0ba1650d8175aa1307961902d993b843301b3b00a20b2e9817c96e3220c7c3078943f7a877f8cb56e5d0770c821479848536b55347d36d8216519ef8740c78c73ddab2dc5dc0325d234900588e902f95294c6b5b63c424715ed3490c071afc65c5bf7210003f3182c0626fa2413d6c147b79a0de0c927f47bcb224e908cc80dafa42304ae484a76f1e22b24d66c81ed279ea7c2426bc582700c13576d7dfe7481a41a87d21a89eb9c708a900ba93d8cfa902bdfda9f61404d77400dbec33bebfa314abc464a96f90400f5263988f1b42f45f0f9cd6dfec0ee17d7578c80121b2531337901a7785a0bf7e19de7cdf8ed6d772e24fa0897a3e76fc373b262702e9e562fe4036299370648b3ef8cb17000f523e408e1d325a529257f92b2af7ac73da2821a1e88fcd3775cb8ce4ef38643dd19fa2dfb78ae78f9724256b1644fb85a5dcfe5fc9d70902c15daf1f7d2ed055337d21ab37efbac62d987fb5e0dbb1e2954eb97810171658a13ab440071454ff8ad7e9ed25ea6484e074d579d88ebe4e7fd9b811bc6806970257668cc9ed83240f9fa270b31c37ad59067bdcdd0a76ff89c868ad132ef883d764ffb204ad2b37f7590f76c2ab38c6c240f0b0a1e44c5fed391ef4666553998237e05080a9d77f49d6e59ccd7d147faa6ac28edc3465c8c7e02dec216810e6c835e492563695a8b5144d969ca5aa749eb49d2a8431327ad2749a21a4d40b914ba10461339e0363afcc010240fcc5041836ff7be0abc8bd00fa2edc82a7df2c429dad2ed78b60278ff5bd258665c68d8aaf508c859421a131667ac98502276a1dc14d6973e088be092ae982ac4d6750a9f8a1c7aa58dd041127b4ab1210226bf43103e218bd35fac069470a73660233f209d9d37c905623145e24292b50beb628bef11e93616481506e18cede242332b5032e2139ae97b2643fb994de9a255d2f3253d086effebc79d101b97a7ce2403fe8f345fa2c19c1b44dd1d940fc0fb418cd260ee0e2654cdd913dec700dfcad8452c31aca7a1346599a30d221ebf789c8cf42028a9ebc182824fc446c1767b832080794c063c32b255a3c493e49e2a3b133cf7e898123ebde9d8c2c223746962874c99d5bcf1a1a72a6624e842ea9886405f4be3a30d525196aede763a669ef9d616a615df7f1eb43d2b263ce190640aafc2587b2c35aa9ff52cd9980b6611194ef713f11f14e2c8c07080ea6a1a525274ba1296a22e478de94c71ef87cfd6b15050d75738d3e840b537e288153a3fed4c9cab60cbaea47a061f931b3bb459b154b05229fd52d306f40da0ead1898d96ec5a8db0c4f856b4cbb2384789400841fbc7802f927d9539133d18fba86e509eea45d3bdb97214ee11280f620c9191bbb5a0a7e7aa4aae179396021b251e48837fed3a8579b9a3b54666f1cfcf47f40c2cb965e90293854d6584d9f8aede2b442ff35fc10b3771ad7c38feb9d18499340217f491feba5b5fea1337e5064ef5d0259f32be418c1ba05bbf674a58356e13ce5c9c1480a30a218e07e645c8fdbd5891a4741803ef05cbf8a2e36231482956f00d8d07129413c04c62770b59ac752fcfdefbd1229ab272ca08e30607bd5a016986f63dc4997305e811cad06b1ca14228a7cd7b85e0155e919faad1d6cb57419b5d3debe6bea92bca680cf3a2fc7b314ea7d660fd7703a81e94c63b7010cad907cf7f21bd651947d86932059be5e8da4fd312fb389164033994440233206ec709261bcbe2c92383480de5d6e39abac593f7cf7b76276981b4eeb098e59e202d539d2d306a7436035b68a8f096da9f1916325d30dc662c2b4ef8b38d5a3a9c5f9691c217cf805605342d0cdec944889085feb7a7fc317682c6e953f4e9db0ad5810a06c96a935f248b893d637a688cd642119b6cb2af81c9f46610b5eee6a42e9d5adeb4c764221321da08852568d94f50a977a59dc5e6843ba047acc8fd219282af624db2743d2fe588a333a195e4b121326d97641ab86e7f27a72a88c159111ede1a3c351b7418e1d30caa4219f37f25ea371d6355cae23271fbf86c2ca1953fe3931c628075ff7529465fa5714a2f4e083f2e8111890bda3f595291163433eaddfd2102a5458767b7d326285b42b4104cf5a97658b6c61148c54ba1a01b4457f6a0a1776f3afd7efd21b62bf1415e678985bae5d31b159d255d712f19c30f6ed98cc5352c27d9dbfea31c705c90de80ab94890130eeed689d9350113e694bcf2d27b4f05860edae620c9d7daa7ddd333d0a93137096027b3c40af378d2ef5b83184cb132c17107a0204ea5b05082f589c56045a02ac451b16f10273a15a576cfa4b20ca91722e24bc14ef2ba007fa6b939cf3e1447bd09043b5f46db44156d7fd6c05b7035ac8c665d7fcbb69924f65965d450ccdab5598aa460073c6452955adbe1514c1ab44630aec7b91c988e0d80a4c2425550a987b373bf3562ebc39fb424ac73f5120e18078bd867260ff65007593c8f713cb162b097b75b5502a6e9fc27e179e9f72ff1bffacbff956fb8d66e641c2e0e1d1d6e4117199d25c2beba34619f88682bbd505360f58b0a3c9a1fdc727dee54b5b0d2d9585868851c7ce7596a38dcc78ea6bde82a730a34ec79ace947668c0878195a0f8daa6b334b8adf341af3e7e27032f295b697c24fca32234121e13558db7551d22f62fa49324413ac1c1ca65538ddbbe33b866ff050b3bd667185398ec352099229501f2d6e3d837add8aae26a176f451848a53086ad7a895fd40d841e2af22b7ac842c0dc2e88dee1194ed97f623b26e830562de6bb6be673ad9deaf256dc168e9cc3cafae86ed96dee3d3d25c7183dcb80c8b016decd1372abd7a8cb7d0f78dd23261492aa65749b65a766e16dc33fe63c2ddb6bad4167bca5646bbd792a538f4826e51af0d0f59b244b619559a0a2be45fc19cda4628250c1ad5528e16cade085c631b3bf57860110bcc1e4c8a960b8ab888e9af04b45a0f85bc3d474395643d82da8e9d1bf63ec016324ca590000e563afa5e3f5f05253bb40b0308c7a4bd3daf38b2b0199eb22702983e00a25cfbe528cad65c2a74107638cd6f4963f5c1d7c75b39c331b860953b23a51e9f3529c68e715537bfea15d5745e00f65c33bb0f295089030c7e922c156d157211c2cb306b1aa835cb7623ba0abbd92738e120b542f814e699a5f9bb842dddf979f43e23497ecb747d2d48496c0cc2112d188d80c5ecc861922e1aae9e434f9f598fc5cfe5dd4c70627ef91617e9530996ee3b303506fa0b874c484c32e9ef15795e4fba7fadc42560a9615e01685c6ca58e6e0fb2cc8333a6b1b5ad50cfcd4aaed85567597003d04fa590881dc83c15ab134323bdc9b62d3dad01e5d42c66bf443743791edac5029672c004adb0d4bc17ed484d3c81273491ff51a0078b215201d8f12541242ea1fd7a524333788a5ec2dc0ada2ec1f72015f5f211b13c0475d8e7235d7e72b082921b0617c8839e09da057dbaab5bb56e5030b1550e032ef0a64930c3a817dcf64a8d61585e90317dbea6e1fa41e7815251a29bce51578746b6df4e65ab4d302edf6cc3e0457de4d7b094679068b252287e20e9219f338fc4a4732bf609a6c21b1c5346987aa7a10b9b62b38aab5721f9e7e8e8623c5e91353baa389aa06147552cd9cbc94f56732f52ac02f91c49822e3c1707f63aaf06d5c139a398af0f47de22474dc2898032a4f62c719e7cdd22b65ac5dd631614e20685e3203337c72a4d39a02af6de28e39d4d12fb2b050f30b5ea60fdce81646ffbe5a84436edd3520398e77bf95bdfbe94365d3b1abed54b4bf3b9b79dc6c14d69c650f3b9f345d27b7e918ab3f4be81bf36c282144b35f3aed5eab18a0e38ffecfb365df799323bbc0f9704579c62ec67e15beb23df27a1a6c79068545d04e560dcecd01fc8697eaeb71476c31ee9baf40d147d512852c80205a881e52568f42c56b79d72e3d7e1d805dcf11701a10ed3afbedf62ce6112007218a42ac3464f75649070da00c1d3007c1ec6d07fba580a9fefead0e5db5f6227723a4804d745b042a361e11e5339247ea67f35771ac440b56a5a7106914333cf15dac7a86d54975dfa4fb634edf4e49615c8c3ecbddcc9cf426b1ffb0582b8619d2b1aa5c398c37c3497ef639671448b1d5a9f7b1ad097d31baca5db3d905ca63843543c43d0f3ed61ab040ab8c253ad173eca9bbde2d1bdadfec43f1ead4474978b49ea9ac4c1ef2ef3b77b7baf8ab16e595d67cc9be108dba59f3118d5283acc9a9a183fd341931c7004e723012c5bd92015c45bf97740098986f64877de730e0944f1885f4b62d2438141bb4d408a3decfc9e0efe3ede7d6580c8f537c460d13f88887b56c12e59b37b1e2a90b8724683a51064fc7cfcb6c496d26764063d3c47791a6d9735411db8759275058657806307f55c8d8ed06c5b96b4aee462e6ebd1c92a2de295147fae3d90fbf876bbffbb36ef396f4d75d8af4f777a725f10732937eb6bf0e9c0bf18377b51dc20470477bc33380fc343ab83d609caf3c7c8a7c06e4c905c64616ae04e77fac9862d937a1e7a147a5a8532b0d557845ea846057cf6e12887ad6d300a99933dac8c46a4fc5af141e685f6b17a2c802ca167282d9ec0c04174fb0d00f8556da4b87f3f3463013f8f10e0426dc0f7d2b5b36a08f34c41b6c1280283a430e189d3ab18c28dea10550ea4044f1edfef3fc75a1cea711bf98f16f59e439898ef6c13f70eb386ce6419b2ae608ca53d39d3705df74dc87f3d6a234b3e41ea66dae2e167ea7145c113fef82a13c2b1373826d72a74453cdabc9d712b046963f329ce7b2ed211747bd52656b3eca823d536f5196eb7f4062fba59611550878fb24305027e37bdd7fd424da3893cea2f5b47313238d7363072bc897becc0beca77120b875eb6c1cde7c782aebd0ae17d073c2b1b80124b1881aa68beddb6abc3b2bab1e9a84df5fc533c13c63d21e3c4405765c4c6844df93b47f57c3379f8c22abfd2bcb553d071a63122077d646a4f39201371f2ac5c744258d8383891c4cffc4c193b409038c38d9ddef1021d9eba9433d086d1352a4ab3ce1b829524c33d2b4a120654dd96f9f8015cf5dbe92a9cae8a01a184d42a24da370e58a74f090550094e40a261686e45377a4555b9e6724090c59add67ee9c7c9103bc2802eb38faef8b39d5ec4117760febff4d45f3b204d54005173e513fc5f34e3b56647bd9b9a9f0ddc29d3fb56e636a965868ed5e648e03e582edaea842f632fb0824852d03bba4fb1051a63eefe8798576e674b54658a83159cf34f0e1d291b759e612ffa5bf40c1ba0ce700dd6600f783085f7fb5cfd59705236ff64f5cd1cabdb2d434abbaba3ac7a6320d24d53782d01087437c01af90d41da9c69533790cc15ae3509a086af53533084299165dda351cc3a5735910b8c50921bba1602d989e4a41c5cc4c8f56e9b22365b03756b9e10cbf4cb425b1f7b51f97c405591d781486cd6d920158be98c53663b4b39f5af111134d7fbf57f888f01364407da061f0e6841b3ff9f25c73dddfa395806b4ffd151dee699ff13af6be73ad1a1d488af7006563f85a17d572481aa9dfdd39fd6b039a98b58d63c15ae720739415f3294c96d2915c07a56c682a50b6ace52bd050b43b0a997c8c50d2044162446affdffb86f6207b04d302d5b5a01836618cf80cd09bee836f2cc6d0f0141f6249775add42d2986a05c5c19439aa98dca78f4ae634f8eb6105b8202f761c5e431fe186c3402e705580902d800bf5de6d596ace11487229e753ce472b99484f4e5150317426e98dfbb2e57555d5aec38908c0a9c18a417a0bdb7d8c94dd205c43393f0186465eb0bc60ffc818ac24b5159a977615597395357e638dd0a4217844626397eeb9d651d11d774dc64dac0bf7085ff6ad832e9794f20595cdcff3b353d3767ba08d022c0b6624e3e21279f305675909759bacbcd209ab58398cf481366a645eacbc1c322228a8d1b53c6480629c0ab339275f41ddd3c38484a0529768689533bdbe6dd5fb5be778b866ea9422b6adaf0400c8b9735a8cb06065cb3c580b2feb31d65241fa5b4063af19e886881d357e5546775e2e26575ed10939cd93ad6b11c2d78701fd1d2396ed9a980e272d7458bf1d57daf599b78d65e64a555687559632ea7c06b679b6b2fbc98d4fa131d33f8e4e7513c25cef3cbf56cc2999d23fbd2b89b8607e3c50a88a04e32c5bf48e95991268c62bb0c2866a40947ba6f231fe196059bbefdb49717898787c9ca49c9e80fb689a9ca0ae446728b3699039570de805d97ced7beb79de918cc786a6dcf663857c7dbecefd2920603504c2265571b868e611d9a058d799786139a7a141c34c5bb8e88aaa656667fc202c78ca33151f2d95fad839d8a7ad1e3ba45912c9142e81ab93c29c7b3bcb60abeb940278cf3a03e5fcd1c1760129eebfdf96194d048bc66eaad50af62ce5cb9406fdabc9c1cd478f1755c5e904132691a46b393bab5e2eaeca0b8128ab394e85febd8dcddd7618c5b44697e51b81e5adf7394e3ec45275cdaa604a2ca15b6ce89e2e3dab7620efa048990cd1b02c6bad87605479904a5185f7b3e581abe9056d616a2f66c7d71680c6ccdeebf40525277bb742f59c0a478eb9e71f8c71eff25a05619f18fc0de0677f06d4cc712d3d3a31d1e112fec199c7386bd2082a14ecb25ff9268f64e181fb7c7f9487662be72ecc9e04456c29f2672157de3d1af9752061f10713e064fcab4481c8781a9ad25e313b2445b9ddd57235fd2197117d8e9393ef04a05dafcad76235750a4dee2df80942114dfb060223db2765db56a45172c328a6fb914d1f558fd0433f6380f4e109ed13da1040d4c30afd766c4186669b561fb1fdb6b3768bfd212579675c9ac78b42497cc8d3b2210bed29c0c6d6924ce6c23912cb8c7a2e211250169a842388e6b06daa3509f7ad125f4b5ab1765adc4995f1220b5dfbe6c1604a8412989d55a1a312eb0cc2d7b038f10d5d1ccb6d6358b375053a8738aad0be563698fc5cc18ce9149daa70b47815f9186af088101b4d2108791348d564fdba9ed0c3c17c66eb764191f8ef54861d11c84936f24052ec90bc829b45afc8cea619e7159c6ea0ad714fc2e8003fef8b0cd0e135b092d0650c43164ca59b0a5e5de3cb13dfa41b097522d059aa16733e6c08155f4e5834f7426a84f976022608eff5541d24868340a8edac619c33d1bdea2c23a2b7f6d31234a127881df04ea714256f420f0303d8d055be09c8c5ece741393f5d7e2d5a0b2a05737edc43f80a39baf8d15f6c7126ea9cebae94841cf0cbc417dbfb7979a39d4b2143771e2ba15beb7fb04e9e894a2d45197190fa5618779dd004544d9920203b4ff303f5194daddd80a1ca30a2d2b23e4c826e2213f38529a9c9c3a0b050e01887222a71115c584a0073b247ffae0b9ceabdc1bde80655195b71a56018b862b3f712f454f170de292b7016f2ad8d5664161fddf2b134b6524536b4105c2a14c042908791947c733cccb09406bb4afd7fa9bf37772ea7ad4b2b81ce3b5422a913954b2230c739867236843f1c9ee9c16a9408ed36cfb4c4c7da4c2e7a18f0daee9d7d1e5c58c84daa599c360d0d457543c2b5b2d083c4779e91a685a50b06edfe83d8e9396c71572f02756b35a25ecc4d530a39b6ca05b70f646705fcd3e740a0485a763c7b52951e586c688f264f2f75d50207ef73e072bb7e7f65ef170e3424715f4543acb4c27ae6c237b6b79e75048c810eb6a57e64271d97166110c6c49bbbe3dc416a1111deadcfe83a20806362995c8c18c99beb246baed5599a68f194c53998e861779d3bb8bec6ea22c2a07c80c5d54238e7a050f686e08f045a6234252345dbcd15980147d3aa856a5538541e55fa60bc0cb671dc28fa3effb90995a679e30bcf3035da5cd971a9e9ce3453730b715f263373c50b1cffd0fcbd6c566c5b07f9407ede037210452bab539d54c8c17ed1d888cb342f560179d4fc0c6a98f23c5b08264474cd88457e8746ab100f2a16638abdb04356db1341dce4a985d7495a60e931c76a2e0f16d1e09b51747ce8de3ebfe34bbd6cc39624232ec38f754158ea924ca145e3733dea39768162b105a80c2efe204af541c38ac74ca49f7fc1d64e0de2582ba93591b85d0147af3c658756b698c26222d6abc4a4cd520c10caa069cb11fd5ac85d9b8a5239f19a46ac22f9215a2ae399cfc1f590a51b657e7789bc3903982b510349750bddafff77a4eddb2ec55ec650eca87af36d1e305814596ac3f839e5f1b12351e4f448520d56931319804ae63637ac32153446419d35804c899e0bc5c3467f50a8d0de54949af96fa9118b3a68da6f1637774c85d2f1cd239ef316d26f310b54c8391d949785e956bfa0048ad8cc010e1e819433521f872d38b6bc9a62cdae01e65ca6f8ccceee0dc51b3d7b5c9efb75660162cd080f8018d30dd83f74e816acf21c2d244749be50a38d0b4a7ceb34b69e724fd809516a682a42a03653b2a20d5ff985e660d69add2807a4f8ce455b8bee94ee82d9227d680ed595769af033cb66cf60122b35a22e37dde2f0a40ce7ff9321f96c7a0421a9ed901af7019c5b018b79ecd178d6ce6eb459eb7ac30264c6c7922bb2b60db8319111be4ae633661a3353e7ef4c0dc901e2661432f50cc6b0119fba62d9bf71145bdcec7aa8bac1d635f5e17b4ad71458af0b372a27a48a0b4686c4b1ecf8a5e98fc3c0a2380eee6ff69700c2646f60c583a409c697e264cd81e8ee36041d318a58fbb818f85d3d382b480e74a311cd9fd387d2c56b0029c49f8d89ffa5fb9f4eb3686765b2fa240cd04889ed100381262fa2f09a1d12e4063001b61e928ec02a668fc82cda89e7e3a04f04211ec2364f9fca3398688690580bf923ac83f849b515d90749cdff083eae87057f3f356b448708fa5ca66a1e9b3c8ea8def080e6ae48421e8adb0e103f8a891b9796cf1544d58cf80a0a0015c98f2bc518df8364d81681409d6b904b62c5996441b1ed07923b06a3a4259377a925fb42a8a7b99fe05b6871299b1861c75f63cc805906526d370440694cc8c5a0d81eba993929b1150976431b78f92da850c3752c8ccf5b20323bd3e364a6246359d87cf240acbc99c14291f0f25a5ea2507f9b4e04bedb53e44c724ede8a369f72a03f8b56654505631e175afb1154d3349f206173d6a2d103b1332c463849a8a4d28a0c483755055e911a221041a4c6df83131fa76b9260fbff75231e15370bff45368acb8c948a0369d4d0836359d6234b66b56284263e840d602cce94d8d4c10a499e274102f6b0eb44eb7b77754acad07d07158e73dab2b4b90cf696df273703342d0f71aed6ac8d199fe374de928cce9e66d9610f7dfd272d5f6916ed039c533ee38a1a1487ebd9bbcf3256a1781074a3439dfe84462bb91727c0c16bc7af83d08e3b07f22f86dd94d441c812f4ed74e65218cfd31e6a5a1bfec5776dc544fdb0124735994445184bc2f5a0a22f3a32dc9c531d91eb1b65f5a097a17f1dd2e08c4b4c9cbf2830057ac5c707095a4cc70cb4b3257681c7fd8804a4ae6a1c57da0c0f2c386893b08765ded76316e44d915fe818f870d9e6ddcdbdd47442b124397e85e75bebb2878818210bd1e82636558e9a2c0c32f11bea25814b649b2a028dd64a2488a3864f7e3e194911f93448c3f145ab73394b3a19f295f055fa64e88c1d15ca1a2b9a6ad484e2df68daa6c78028fedb4b08613e6678e994ce86d48526ec8655b92d849a31c995894f22414d7bf0a2022f87a29818610314314361d2c92eb6f805eed25ff61d31e66edd50bb2d666a975ccf2c4484302980826e3153386d568d4d890c9f56167efad615b437beac69f42887e68e7ab97ee3f7d6c993e6e759ce4655929c00a0da4209912ce6a082d50ff468bf6d596ef125fd24ef544c5054f7090b635c2b25cead012718b3dce01ae38e75a5b2e358fe71399c00b38d6c91c589de026c1696eb29dd12998b2abe8ee08dbc35c1fbb01980f732dde60908e0e7223657d9c6483e66e054af81e69420207d88fd18c1570609d36efea726fc0ac32b93113b7fe22c94a4ac342f519ac36a946dc6fb6f4bcf08043b879e988789de6c2e7ce4a1ad0a4cf30372f4b0779457b9400951a279b763adc287b85b80b72d21555160194c4a3ee1fd1b45caafa40683be2e022928532c674e0e56149b68e53c21d270afc64c4d5e2f0e4d4072de696fe0b83f18d851b467770e8876315dd29cc82c5396852813725eb71b646360a68d8c7794a64fe8f71d2b3df49934fe9119e23de020bc7752a3f4e44f0f7df89168a9560da68661e4d91633affa2443b2e4c6648a05a7b88f0ed8d7c7d34f1464aace74c38e91c41b09a350192bc1146561c3e549c8304ad3ab2a0ba541ea573dd052aa75877e983e7e7acb0130684c5314aff95513a21f21dc15879efd7e03ebcca99b1b2fb41c4619a2f8f36b6163261a2d18e94746e242c2cf1c3f9b1e2e7a841b5060896834f63e8269d853d4c5baaf6c3c1e13875a36467a77e938659dcd4858e8a3f65c045a80b8adfb3ebb574337a73c6e39b3523f6eae5e0201a2a185864783641808650dbf180219dcc055b25285d485913e52a28b5a142d337b156eb64ff8916a8c058c6059fd649c51e171c40e4f8a9d2a8ee4625fc279d99227f1d529088018cbba92bd3aa4027f866208cba4104099856ef378e7859f4b60625a39b7fffc1cbb954e7f4cfa4591ff2159159e14cd93e3d0e0ffd3c6df7561cd317f88b958b526542fd9807a7c9065a1819b318baabb4c168c7e8da67cc4164992c5c6a14d4f0b408efb93a3886fcc5905fb61de9d43741b074944de61b4ccea752db37412b22e71e6976f8362327e95dd937810d7d4373c6eadaf2a889528ee67e4ec93191339a7587548333f93fb998dc433703b7f58f1f5d3b35dc457a222a389bd808fdc2b8d3080b89214ac9f29435e83bd57546a2ff6b06ecd95e729ed8ced1d5e4c7c72b5080a74269780905cd38dda60af1a4d3d337f5767640ff52c2090ac5b557aa859dea683ed4f392976fb205ea301b4d9b94093bc33f18447886f5e5f56dfe54b6041fd82744e63f4bf79bec1436e6c1ab800f4c0f1b1f2f432f83f687b482abc8339ee41188317fe81432d12cd7924f8d1d0e6e944bb1faae9ca9c79a9ef4eb029a9864872368247cdfc01cae5b4921377122641503477f1794dbdd4d13c38b636713405274f5ec65e00813e6fc26dd24cc5bde880abeb89ad01ebcb98f6a8f6e9b152a209005df2e082e6717c68879e37f5736a764c0f4e7185f7b623125c821fb6878c8ec309845284eaf1d61b02d335e538404b9728ab0bd76c503c2246db3e330e340d3791311558c8ace01ee43f60a0f95785649bffa78bcbbad4e26041e5eeb702d022c8fece65a9b753accb69afe7a6bf2642eb4ca8ab3b457aa4464a961b87563b877cc7c388096410bb7bc2367aacda9912d2270745334baa80add244cd3ae87b5360f2ab610f25efb08fe1cc722ce8b94a00cd1b469648de27a04296a032064cad9064999ad06c6643ba5f7659f408235b43881af62750aa7c827fb244d0699daad4deb7b04b87d653b6905cb3e9da762844458bfd9439b08d6ff4591643f9cd64b118c317649e03bd624ff6db375acb9bfdc76f908b4be4a68982fce2588ab5c86c2e9301c3c0a6751b984c3e110ea55df4e4a9614e84dc017440228764a291fb77e31def9a242ca1e460c1faaeaa27394b25b57ee7fcc9104df8dd149b72c69ee94912abbc452605f6b89b106bca1a2bd9646e0b705e4e2e5359f053dd95052f23648bc97ef3d06bd4d847c4d3e50f4862c63796433c0964994d4453256093911f37d8f0f1528c443b0596974d2c8215d9c3521cf4f5f06615be2f2f1a46a1dc8576a6ba69ebaa7915b0e32fab3ed2c73cd58bb5de9ac443818d91c9f4bf5aad341cafd071d3ef2a3203dad797e4146bb73974c4c3a42704dd64a904c9dc700c5ab22dc7cc806dd68a59ae3e05d93e81d03b981cbfd45c0e081b239a133653ae5b3631574037c070f87eb11a5bfe2778c5483fa8ad9b3056b9e4ff4614a5b58b0218c6ad29fae3864ad8947ff4176c8e001cbdb1985284237123103b4e68507890ef0ff7153f114173c257cb5a765e6da0c450fff1cd60cff452a60f199964f124f73590371710b2c062f613de27de394a0da3718c2e106da14438a3fdd04eb2097a0a6d0bba32311be22d080f90ce5a273754d0672178ab6f754d8f5c4621c58aab4a1a7d6f06e7aea89ca26cea91ae113853aaf87e3656a6ee2abc7b0dad093a88c9d5a75ded43d85d2a4491dfe178403ad60b4d0b1ed6ac78ed21998f59a40578655fcdace46ab73fd962938686aec329fccbb0661c77ce54fb35df9b23e9bbd0381e64591f5ac2a374ca843350298cb47c60fc58dfc48ffe7f967c2c44fcfaac445eed2f1827d5d27265491de4a9a9c14f16412a6cdf9bcaab98cc24d3af383b5041874a6cc06cd347d9afece40f030ad8d9f501e7d98127c76bbf20089bce1167a56058d450ea7c96b2ffeb15a5a938f583c2c81ebf4ca69566f803ba00859f7847301513514fa2628868c3730416b4f23c365425761d171cf99ae4cffdefa37005e177941bec89ba8fa0b2269721438ca15d89dc559b43282d84e8b80ef1cf2e8c070f84cbc01d8678858e5b67388ee47841e02b7f7c5d6d677003f40df9ea1cc7b43633fa074d9a08ca518df0b7b3b75987d9fcfc014001e3df9c4de810e93db39ba767386fd0db643629d668e61e70909df28a5ba3bfca05fc207e8b48336385e857299c15b5ba455d33a94c98ef30896eb49cb33cc27652a2e2bd1a8f630ccf23f2155e1fc17cbff5307ca2f4e6924e2ba04c96d3ceac601f008fa30902ae60ecb732129171d6f9366b02b0d8a3ed26726673e16c2bf3e0aa99c69ba14447fe859709385ec3fc3f1bb0b7cdb61a182e7c526a002a863ab01dfe60e9f1d842d2fb85b8f9ef15d19cc3d0e838fa2a06b6e26df75ffe7548eb231c46f008a56a702672e35f8b89a2be11fbf87e861e30967ba08c564c0d7051bf98db7b50247458e681e8145cdd963820b4aee9a8fe668b5cb526023b00a09cc354d9cd7706c952e1cf8191b9324b037782da670681d83e47ebfe1af5d2afba2bb1c82554e79acbf3d06dad590ca9e70b44c4d01a59b113c9d1b20e95807b5004509fc31e8d407c52de21c6da0bc6f4ec758535be47528f2ab2775c28b4e09849ac8561ef2465a24cc151273ee331db4a50b01965f7cc39061e05a7c838a002e464668f0a44a36f5e3b66d35015b9bfed4d0bb1191834f0aa3930a89b922d595f0b1ce04ced9ba9b21eacac0c5741b38605222551f75d26d9c1f055e172e06933740270a40e70484d2a7b4e12ceb97422e2e18fd57a3e7c51a129fd80fc7369de98d6975b68843c32bc8406aa1698a060c3cbf0687c699d1ca3f2c91c61c085dfd19bca5390c9a1243a778484d6272c32b115b909e1f845aa7ae7f38180aad3f0cb01a9bd62718a36cde5f01bd4f676fc2744b4623c442bdd1a2938c17cd6b3fdb877d342fd4f3991673de6ad2f28eca81d542f80b11ee8b604c4d3821132690e3609d1af69a370f572acbf4f50555d2ebc15c42763c8642173f76d0ea5b457115950d94d776e58ae19b6523c1504f8c56a5791fb40db1ca06d50c1a414d18557d76fd790d89e7df59bc6f2f0d4bb12c94a0b009089eb85d45742bb236c7f506afcb74449f9e1857d042c01c4bb54991591eb58f1e8db6a02d2bfcfedb1b2b78f56e3fc942ccc928582c39c560635eda284cb48bbe858173f5ba19eec2fdafbf4a4195ea6669a0000a67bb8cce488ca56944738434763828e60938699815880104895427eae9bfe668105affb26a29fff4b1e1a65b76ca3ed12d086dc214c9dc649b2d0b0afc4e5ee5363c156ab0da78362dc91327c800eb9ca3547ada86065d6ed944e9d54902acb8fe6ce32cde75f70ad70dec68a84ee4864889f24b3c525481064743b2814006fb89e7dffd8c18a38fa9fa446e72886d25b0cebcf9e5be31de49ba66e3a1783e75f14c1e109fabb60a92c3203287c7e142befc3958982f281c2fdbfe2e1874bdb090fe65dbd53b0ad17851cb11f435bd5a1d5954dbc90e9b197dd9f5cfdbd64b95e9e8b8aa9ba1ee191d56ccb77bf6892b587671286355005e4b3dab3f614c3e5983c70d3b7b84b6350a09cb7fdae66e3d53b25640fc27e4393e60d00f865af5c52264e52672cf532c62ad326b8392c01016cbaf8f50516325a253d8a44bff17215bd2c1ff913e8f568c5080936da9900cf0dc09ec666cbc41d967b5c41d9080923e3b82c2fd1ce2d491eeb32724c8f73feb725ccf97b196cc4980c1b73264ad83cf67f081704e90c567db09af89da29a6c298c5a0b97b05038e8c78ef8d0d236c5e440884e1e20354d10bd4091f1900a51104a21c7addcd49792da052cc35574e139108758f288ce91824668837ebdde0a00805fa594966a8338594ab478b35a166d02bd60bbc5c86943aebbd2a91c88228dc769680db2fdbd1dfbdc76d4a66044474b767e6248175a3880926d1e68bf758d8de11570d8af8404f834577f368a229cbf7bb4738be557635a2ad1071c663b8e830c162272b206df234c1ec939d3e416f77e272587c0d2a13209d0bbfc12c7c288e9d4cf7829b8109fe7e8019ac01635fd793e1f6d0750a66617c49fe008c1030e675ca4bfbc434a80b9f68bf5be3eb93283e29bf984acb17e61a523dffb904bc90338b6acffa4bd8610130423306a067247e9b4081ab95741a22f87025445920d609a06bbf57865d2bf550105a91318e9fac318348422aaa248ec365340c5951a4f4bdc24221a342822a904249d89d1d5e7634b2dc4110d9ceb31b2ce45bd01aaf6cee3803aefbce2fc4953d51c4694987affb1c5e1ea814a836b994083df6af2dca1cd2010f6720022471a32d88d432cb62ae65e7b5175dc1f61029b33d6025eeaf1442ad56bdbdb2243989feac5eeda771a3b35c4088c65c7298e7c068c8095a15baddf3a45f68b3dda379ed9b3ca51a61a3453dcd094c9ea76f5f417130100978a98da94cc607746f57df7608139cdac54fa0a3a3c3c572b361fc2d0e5c7182eb5dd527406def60132a4a80b7c6823168cc3b880dc22c23d77368ae2cab284d2e21f2a7a198f51e8a9f2954b74e808ed2294077b036dff75924498c0651faedcdaf5ba5c0186c168484a809a46de4a28519a9685d4b85f07cc854c25ca4b1492ca18b3e07bae40361ef8eecea7f6a642fd412c2773bb2839c29e3a9fb4b94a976b2a5a220f2d56dce0dad42559254e6f5b811db4df907a8356c01afd621da69ffc33428f982221ad7e9442b549684e4272097d84c5ed12bbf5fec1da002a397cfc2bc70d41a55ffef464d1536d78c7c4d5b0225e22c206f3168098ce7b58c9b72d4c60a1261a4de42a040930b2f20983a5bbc79f51bedcf445ffc87d218f8592cda824e247b0b108b92b2be795f39500fdf0ddc57b347463cd754d79e3ea553b8dbda733821db73385595d6a7134217f133b65feccdfcb0f5478022345b9c03bde4ad4b2bb7ec5dba80b6d59f7717b097634d20789ab0013ea98e3d241c2b24db6333b2a10bcf66dc24784864003f91058416b67d1951106961c21d83b27d3e461213c9b4b1e22763ba7d568d665f9588676c943acf1f7c762190eb4d7f70ba8002f3c45ed34ba6d7894f93c900a549d89028a40a3e0c36a29251fc4c95cdb0a227e821f2b7e965c253a858f378da7eeda545e37537306bb8004b661c7dead719148601bf83a36eeacbf3d41973a68d69fe98eff4b57cc2fd1ccd584cc850d401aff543bd31fab887ac8a19f67d12eb368bede7bad265cd81ed944c0cdb3b85259933a2892f8b4280d388a6f646b96309cc19408acf6b0ff72e141fbb55ffbd2cfd4e155485d2e9adbe088f54ac025d2520d3826a78872d227554569dc0667cc649b4b5041b06350d203ebe4c6ad6a28733ab5f3734108c8eab045cb61a6fa41a83a3f63df0edebc3570e7173215e2861411c6d05275cda07b81b4e147b58049df02f1df6c66a7a6fa8a20de2f957d6e0fc33287b42a8182751f64c45f9e34340812b2cc6348aa29eda974d4cd3d848072d6c39f4c9344354f9dc95a334f68a1737bf211f1708560bc9f96b63c799c2c337f319e41c837191102d79b24c592bf1f485d7476cd77358190ece9e00d66dc73aebd3417fe6c669c6ea129487c698ba76412516bf9c5eef104da870dfb6321dfaab9066e20e78894dacc4b8fff5d2b00cb0d376416e2f0299cbce2a9fddf989157a1872a400d48df6fc9b4d5ffa36be359e2c8b74cc181386272f641c92a8bb79834eacffd5eb5edd617a994a6f8391c4cc5b2c1426c8d7927287b6d830956626b2790e338c76c1b66a6daa006b862b1f8a4927234e376ec4042b69c239c5eb3d289281726b2b30ff0070dce77ebf1485b1748fb4d5186bed296c56408704838ca57b48626e1ee98952a14c2e4be947aef83b26b0f9a2a0436e69e21aa81873b6fa78dba3f939332d00505731eeb2ae0dfa62f4ff9f73f86007721781d0464cb4048130c43add5e4b8bc5ee83c01f313ea08948d0d58801881df2b2224ff2df101477af37c7aa9d36e97de6dbc1e8b29a215a6e6ad77aec6162eb1b199730c10707a3116038834ee420e9f6e481918cba27c225f2052b22baba3db28055c7b00d526f01b4a6038aa012885720942a52071a2ecb7250757d5604a6b2821ba62bc78f58f9e3d389f53f01aaf328e91c17001797c3c8eb3d9f8037d9e0c16f9579896fe2ef94cec369aa9f2b08266586b7ac867d1812b4601dfa976caddd726f29534a327e085d085408a89c46c97a94f588cae9b1b740c9504e4ca3973840912b061e2528ba64d322c01eedea9fdc58832cc0e5275dd4a84057a9775df7d58c8c5cd0808369ba2bcd629733e60cfd534a870eed524314b0691df0c00762ba003b0acd06a631dd1016f0eb21e5e6ccaa696e39f50390733d36cd5b20289284f44a5ca510886034bdd560cbe7d0b8338bfee36e632d40df99ee01f7f11ee7f199cb5e608ddc5f704eb839d3a7038c5eb08064ea96aa920242f5ecf848b1fdd47a0c80aa15e0c7059b8c941f3e2dec707516aa27754bddbc85aa350ae5d32cdb2325f5834205a1703658155c1658a8894d4e6ac747caedd04d8cab7b526e5f50149f4b7eb9da254336b74b8670562421de65f6dc076986eef28b13ae5e85d4cb9ff29971f5019066a0973fb5d32cf245c6d555a99c1cae1e1b5336385770b92c2f01a0826ca85b151450d86324056189423c1f8b1545274a94283ca81d1a173b3cdec87972c5ac8a2c6cc13228445227b2f8d2a447da2409a62f3836301720171f171f58e92dd2b35c8aad5995d5fd0e97171db016180833c14ab01de28b0e203453e987882e59dc25e7e2e2b200942d254bc91600c34e640b203980de05a06c289bb33f6986f615503d5eb92625bbfddea768de42edc86ef4e6f6a36c2927a9997f9466718da76c56e72ef949f102765738e1fa9bbbb749dd66dcd5147aa8400ac22ece10e4a486513b308ca27935355dd8829c38612757c4a320a3142ce809ed77f1e95c7c3ccfc5c725e72d5229017f75aeafb4ad7d204064c4215f088c5e7220ca86fa41dd4eb89f9cd4ed845b21758bc9518124c45320cdd0303fa2088c547280e40977fb45289bb752b75675ca76fb4fb866915fedca0995d32cd26135188f9494ccd62c3245bb4dbaf8d87c523f3f364038a89cd2ab007910ae59554a174097fc6eaf4b7e413897fc725164970cd908a9d71f85d32c97d26337cb5d6c2e3f2eb766d119e8f547d9f49c7e4eb6d3ed54e504d42cffa945a1d578ca66f50c6ddb71229b3dd169160be1eb9f92d22c12354bdda460cdf2be3f85936a928a791de5410ae2a9db6d4f558940044a2f706517455c2679cc7f86a0588f3376453c2a42c38844e15e9860e7e725cec519826473356f478a40df19e2c0f534cb7dcef01bcd7d7aac358bf2e0a0c6a290efd29e1d146dce7815141793e54570f186dab9fda12afa0d35433de99657e9ff6a2efb6a5fedabd1523eb9a635ad694d6b9f6651ce718e739ce31ce73857810a8c44229f2b7e9105e88c3aa1329a435f9e33524a51cb619a1ea3bb20c389db2f6a5a7371d3e3388eddb4fb7604fdf684cabc459abd3c684f4f8e3a48202f2e3a5a4053a9fa18c3bf400816b209bdb2dc6c18689811dcaf6bc334cd448eee17718eb134adb6be1a37cd0b4fb61e5b9eb424489e7efcfc06c4f34237cd224fb610cce5415b12c9073f2492e3f78924904b7e2e3a7c80b49432a0389ac4e932ae621f3d8e3d9e031c185df1b2811dbc63870dee4ea98b1466c3884709c208f12eff77494d9b33a6db2c7e351d55802e52e68cdfb8f860b2bc8839b68bc37640f2ab7d1f200da994993277056015ff8c6596095d08271016e99129bde106bf399539e38ec5ad781bde554a29fd6ef0d5beda57fb6a7315c6b706ffa8d223508b74bb3d021ec188e4f3d5be30e6579b33f465536267dcc6f9c608526bedbcaf5e57eb4f25f7855f5e262d550f392c75497798d3d0a9d01f6c7aa422128cc070ba4ce174b113b0be88a70cc03add9f5e2a52588ffdd4c681fc560230fc6caeeaf7f0bb7d54e6ea5b8327e394b8fdb98123817699c4afe65f197e3eaeead78105ec9aab54ede2f3d5fce6b9f8742e3ed5c587baf87cb5af466f48dfa5efa730bf7562154f2029b426382204861a05cc200746c3d9ba7fb81e3b488716b835b97d892158e52cdc7e92c39c890c40d2e5eb3becf6bbf8786b36ede6adafe6aa7ee62bcdeaeec385c4138c3cfcae889999f90beab15ff4c2049099c50fc8dda9d3af4a8ffd9597ef579bab1d464e0524534998e10d1ea6691affe0c12d3762e45265aefa51401262ba2eba4871f18111ecf2d334a6712982c3bce59206abfadbc508b7fb453c8c26c9c5870dd0c5e7f6bbb5207862919469bfd5af461f88ad81e5a9764724520a276a260215223a9fbec05f4b16fba1bff40bb5463f85845e746a1515cd17f5cb17e0a8c717fb03d6ef7b6b434a92c17ee8bbef43df47bd86b58af67b8b2e6e59bce882f6f85ce5568be2391ecb5d91e2a3f3a46f1a065485a7c632ce4982c44891972090c517264ff6d24b1038e76a4e270de1dbcf2d488cda783982fdd943f4a2a7648f6bffd4c3697af00f7f52f41204926c49e5e682d7b3dec8f2ed972380d65a6bfd8aac3847b607d3c4c03ffcbd447ab247e203e6ca89f8c7e09f4924c152e85bbc00e9877a70e871bbf72a1aa27efb2151d43cc3ecb1fa835fd31cbe30013d9c3d763437671230e35dfa813573453f2609907ce18539335b48fea41dea85e187a147ca0fa485c579c11046061e018ce9e7bd41807fe9c7409f087d2394da110611488fb4366ba05b0ace93cc0dcd5a0964ee08e6f37bdea3cfda4511711134bbf389cb4fcc19cf39eef29f7e3c82e405c389cd7464af926f6d63db6d20a09a6cc635c440c469b3c62e2ecbb6d1742ed3f2b2b7c8ef044a29b52390ffc4fffca34ccd75aebf685a1b805deb9b88a714bddbfa879999994924e6effbbe5cf374d93d939999994462febeef0bd2352f7e9aa74baec118a17bf8a779baec9acfebaf63b318d1eeee6e3be79c334b95eeeedac11821284b951efd3964a0c9b7893bb243fbd03844fedc4e4973d60e4a09050a185076a05401058a5a6b0d89a0d894509a789ee7894640a516b9effbbe11490b1f2d6ea4528b2f4aa516a6916b110370542a954a608b16375ae4b48c3c2cb558a263e4e5ce1274e8d0a1c3e5e556eee0a6694405a45fa5d57eb5b486a82718e7b3220ef64e1e3a00473db61c3e0c631630cc09865dcfdbc184bb9fce3589487040927106104cd33f65e080c0cdcb820046f356ea346f311cacea9783f6c31042aec8940490ec26b75fc4398fd94ad7d9efb39dfb652b4baccc16124d7073c6fb239a492fc142e5e01f4d6d592aacdad4d75cd51fa6a9b61a496d150821e018579d348b5fd466fafe1abc55fad7a9db540a673ab4f2fb70e41ca269df00a70e72b58b53438e76fb6dc0a1af04899e786202f6bf4f84b12e6580567cb58073f19c619271ec1701724b9e27b3e47172fb6dc0c047e413614eae77550d371c41526bb8e1483f92a66f73b3df81d9b23f304db59933f44f5826d0810ecc36a695253232457aec5b0dcc466446d9880646b8ddca71a7d48d787f13dc64daaec84da480249026979db052e066709dc6399c44d771134e228a24a0502dae88829b10678f914582198cdb0f8070fef0174e20bfc21c78890e2d1ef10188d38155fd538edb3fcc1b0f31816ae52633573987b9f2aef32373c6ef12ce8191609a2647e64cfd890502fc837dac2ee38ebccc0e1680e4c4c59a85c3f3f217acb5fdc2e4e12b7c2dc94f86debe28148e7af4a721b5a127e9750f891e0a7de28879446f4dfb1cb50a06a5f54867ab2ac1aa7e273e29ea0c3a67e64aab3924213589b9ea5addf3bcd61a4512150ad3f0b36ad5a25e51a3b85d9b98ee356fca0298d2aea33e4a0d600bbe93e4dfe30c6f34a9ee5ed7d9efb35d170279f142bf63b6422ca6e1efbc03ac02a0c20eafeb3cafeb76a81080d5017af8787941824f9feeee63ecbcef72f05b3b2fccc16be542347bfcb83d9a3c67b5a4f912c733cdc73ec61187d30deb53af220d736052c925e5c04f18e2b77b917b6e3da7ddf775b63e9105a6e19f33dd9f54e03a575dab779dd7fab549b755b801e8c4e9945aaf36afdc23e174f11b6cccded1f9020ad3704fd923a731a7cb0cf6ec9ca18635ec9b8aae366780dcfe1d5eece6c9bcd96dce5099add27f2da89248c455fd24db37125b5a2211a8fb46a390cea8ac61a2b05d479ac5b7b982d9833b47925e926ffd36b8fd5ed3b973b6bc98ab4a40b71bdff8c6b79b0502ec9f93b5823992fc91f43859279863ab26eb488f5fd32b982c0acc9148775dd775a1afebbac943c32946efd99061273842aa74baaa7b4cccb1bd588f4cdc78b166319339f6934424ae6a57680e1fb78bc4dd72ce0b49c87a5dad952b0cf807d7ba0412f6514d15bf0bb67d7434a0c6a6c685d6b11b08926f32322f20bf9a99fad330071f2ecfcb3ffc8b768c5ed35b485cd53a22b16148bcd538b3132ce72770e43b9680e4ccc9ac05c15ccec70b2058da46d57a9ee7799ec71e7b9e573b6bbbeab98ccd372f231193b9ea1e02065c438226c48509d83dab721ca0f72421ce5fe839f431733f6926b32512ab609a27f44f5034136b9b491fd14b34144dc5ed6f58c32e7f1c048dfdee2adbcc191a05aa094e828b7841a6b32192a3f0a47138eceb81d15e5773a8e853305b22714e18304d453267e89f8ea8827f740aae3b337f0aa65d0232b9cd47fc125407f69943c2acd32cd28bdd6e2fe62d1957b1a8442ab95eed6b0760c183081e44f0208207113e5fe2e011040f227810f1c2e38607112f4f68d115c689788c4ab6df7ddce6b7765501a66cd9ea19ab9abb69a8dbfd31300049d5edef1accdc8a4983c9aa5dbb2db617abcb33674ac3a6c263df6d2ae6aaabe8220b21a328c0ae52f7ba4a7d8edb4fcad03858f504d03ff434e4fb5d17d948a3268ba797aed30346a973fb4952a9e3ad1e63aaba6d60dc3ea367ae6ad3bdcd19f69bc90a7dbfe34c9668b69ac9a469270d8302d652900e7352e86958c3fa3595b9ea351a8e7eb51bb9ea95bdf455b16f377b0a856c13f20bd6fd40dc7e27f4eb79408ffcf674b23517c9f1321bfc330aa7abf39e85d085fc5d2f9c97995f49a611fa5d68ad177e73d52156f5a7809a361d79c98a36f459ef8a6eba05a4554b26cd6a1a81e53403c1966d77ae5e90e948351e94351e945d4d7d7902c83e7a1c59b643f0d2bee60c0cb7dfcb98a5f7cc19bac5cfed9fd30748e6aa5330555bdcee37e2aa6ed76706d8b7becd99792b87984c771405487ae9a55776f95e7a69ad95b19d684f975be40ff508c5bce930114795256f783107f9977f92e3e5232e396b6c8488cbf09f98b9bc8204efc0ca4f1072b212f786ab07d81663d0b058c30d2c150707a2090494b9f68a4ccf3f2dbf0cf38882ec704386ed483b6626a2446177efae3d292ef11881cd420b3d46578010186a4a0f3eab68fc867ff4932cb8112c4e56f5be348a18e140280d0e1431c2812246d228e1708407d37c83071c8ef080c391f9c6ed676702e8dfb4afca5713b90861840decc5092972643b46a6248024e529485fd37ef3164974f169553f8bdd37283bb42f4e50bed8a1a5be40ed304d0a958282425999abf661ca4fbd875fb453bb9e3f89eac8af86c25d87d91bd8d159abedeccc66a4df5e75083a3000ee27926a3a56ca7722caa747544f8f9cda021c51544449e9b1dd8c1e9f344233d2e8f161a641d9a070ae4a835ba838b827159b2b7e940dd3a496983fe2609cdbde4a0d916222053b7998bae9b1df73fb017ae3d383f29142afc8a66e27dc0967999eaeb0e5d4adbb4abbfad1f6f9dc7a7cbb64dfdc7eefb668e99ff5da7a5c45180ed2f948413e2f623a3a3a347afae953d0ed779210d5659c1eafa84eae4e80a47f544fabfa51b3d9c9076089448444f534f76579a827e6d84fb22ce562938b9f73e68c7d7e76d2c464a1727a44d55ad54fbff3b106a8da2dddfeaff4d615a45f574799ab7ed4136d8339366cd6d12e05fbae37eed09b5ac8028dc20070e7873a7c8cd8e016bfc1aa7e17260d57e11ffdf365b8c54033f3867306f57ae394abc234a82527385040b071fbf9843b59992b6677d94b0e136acc5f623da241434a4941ba9726d4e6a270eed8a89d1efbc9138ec31727b29c988e194872ee927e83e1ae8b75d2a3acc7cab785dad9a9e22d144ea760de42d9d8842f7280246a860390eb5f6d1cda6b0ad9550aa7cb1562c181fc08de86dbda54cab977ce566d221d50e589f4d5639344727c512402892239de26d78aa48c8f4b36aec7226a4eb3483a93358b745b055f3e971e80e59ca1b3b91a63b6281553d57f3ab5b4d0924b97e459a454246609903cbd7ae4eed28ef2d3eadfb92ec45de9a7b32c7468cdc4cca6d29f4e35ade22f5b8a4f7795ebf2320a7dac593eeb9189c9722be6d88e65aefae9ccad7039ac4ca09b89c27a8e1f1cc89ff4f8aa54cc1ffd56cc553fe94db8fda11b90a4a5ad14bacd55b8dc5c9dae6d2401d3979e0693385d45c30daeaa0108f0d6cf369b8d9dd25a29756666e63a46e5a2627103fed11d364f8fcd5c368ba4252da98fea35575672bea4e100428815ae71382afa32730c1220378e52aa9aa5c6358bce1a0893c55eccb199d623c728947ed257e80c58d574569f2cf533adbd982c2623f4236264dc3812ac7226739ce5bce4b2d225690903d2be665d673a1ea3c984986e9d1901ba48d2d7e5d071e6eabba4e3ccdcc77bea8ca4406dcc168dd5d711b7997859122950abfa9902d519587ade2c2fc9b696d96cfe7201861343a1dc6f3eb7816c2f4380ecb6187aec8f81816e4b8d3c9ca720b0ef0b13b0a6d56055ff1a37371c8e2b4a8d38fc3af3d6f42cd7a7b8cda7b4e1535c0ecfd2acea58dc8ab7e1547a942a75f7e6e959cb624843f8b23865fe6820ccd5931c402f3b804e99634892c11d502fbf0800b6154841586c40480a52636b9cb76a15aceacfa2735ddcae37709be36a4fd5a1958ab9eaae9b512ab0e0228c31a8d8410f1d230c2e2e16d4bd6532994ce60418b98800a3019086ac461ef2409ac175a03e442d8c56a419dcc5b632c7fe154906bff4572407549fdc4ec51e0029888b3e82342ec60c9002350e276a5ce34a1fadd4dddd5d342a85ed1227ab6573ec9685c0a8b3d99cf1db9c49c0edb7d559d5a9b4665b9dd97e6eb11bde7e0682b56889c69366d126059aa2f435dd385bab1a272474d9a604e8b63aab33fa6a16e8b60712fdb0a421a6eb3b9000c93abb5d677556674872211e80e84ad496a8ed5848c19f056e4d4e81693808f3871943b8fd648cfd9a6e1b10b491b58d8ca58017807866d0f0804a848d12d8506ff016827610a94bb68d8e4bb64d5f6e9b11a9e4d912d870031b385f8886071099661c21aa5d57997934a2410d199682b9ea5700bf5e601a66327fa88088db4fb248424c31448024dfa8e7d199cd14f63b88968269ba6f9f33f54f4ddca083a8b5eb6a105d44124d44c1329eb6699bee6edaa3c9d4c454c359bbaed690cbf8e999abfe18178024f79086f86d1b8662b6f8e6aa1c1738c60288a9e828ed9847a3494b1dab989f7f3557fc3414f947ebb4f0432587ca13374239f01b19a9d206382518b18a440e4205f891228a243481078b8b3f345b4a8888a3644308287660f344f95384943078c4c8b9a2c94580b314e3e68a261b01cef20659aec88e46942ad931037b443b5b373bf0a54725f43bd269e420f5bce93d9a2d36021ef5aac61cc2ed17f174d7f51712e76988e9ce1310f8f53d4901c9e973ba3d796efd49860c73c6736686e13dd449859a8101012ea8421fa7937b0335ffb0c062197cb2530cfe85edb2354b06930533c7fe538fed25fed171b3cc558d53ba3bd0217d8aafc180992c26628efd0cebd15b8cd32ce662321151eef72debca3a35b058b0ac5dfced306d44e6aa4f97c379020297103bb6a308b0636d73fbbb614134ea646f71b79833cc7f39eccb321aa57ceba6781edb784a1b56b04c918369bc0e8b95366e5bb6dd1f3dcb1d7a94c99891246916f9c2ed2ff4fd313ce0851ffbd64029782c9babfe1d3720394baeffa58cedde26d0c8f628d2aeb544c1b5ebaa7b28835fe182339e30491800adf8bac2fb2bf8274906ef61445880fe4a6e277ab6c7ce9979c473d574b6dcbd2288660ff2636bbd765f88e4d63ab7531aabfaddd21c8d5698df6c72f4d0f225f04daf420f5714313df83d94bea507d3830fc35fa4e54b5f04846151022d5f7a1a5a447a7355bf69075be1d1b1a85f6c85734b180e20447d3203fc66e9605f075740e9491f3dba657a8f4d2964015aea39da240815ba910de7f521a6906136af26e0bcf3eec0d76faec2798b7f6055ff10384602065d174e76025b3257edd6fa6b896572c4128e01d3f88ba1f0d7eb556da8cdfb92abf80b6101496a9bd971fbbf7e7ac3790b7c0a4473b73f260bb0e5c1f748301268791de274150de08f2eba312f0045e02c3df834985e87d843cb9b1e147bb8a248cb9bbe07f04b3f5d221e81210c3ff84a5c2d210c8bcfdf2226b982c522a607c5242d6f7a25ae4972534b486931319004c2e1e84e5210162dc99286785f4ba9eddc3b779edd40503557fc32402cebf0139eb11386f1cb441a426f1488fef4486d5a80349308c36f7a6f954418166188944423b4d218dde11ffd340a9fdb3132e88ec9440a42df441a0256ead65a1bfaac7d25340bb579740a95836669966dce787fb212033a65ae3c6b3d3a8552a16d502b9762e157f7a4c7d95cf10edd611aa6825f346605146aa336eec1f10b87c3e1702deb59eb34ecf52abda8adbe4a22b5bdbc456ff401310c01daee530c1fb5ccd4866a0d7db5d65a6bf5a8f51c6d4269b4768249f197dee4eeee3f36958219d94a5dd402300577779f335eabe6aafb15da651ec5af5861850f5de6515ce632ffe2471ee6e0430e495e2f21a62f0900783273e21ceab083a5b43fd12dcbedc6b82c54f4592f2618548cd9f297ab684ce339fca34f2f979c260de9392444bde479231269e479a5672bb35512e911540eb6e231d32c7516d6273d76d56915d0ed76ab4ee6aa3f54b3304d8d62fe78794e9d22cbf5d7ab7a0e738943d22d223dc3057195b649b93d46cea10ea6bb83bd2c8051576b7d799dbfe79de779340bbd51db5c75938498ae29f4d27be114b0d15512713cd86b45f71b12e0bc2a1a2b6132613661b602f4073b8ed871440d2f73bccc41248c2b5734ab9886904afce7557247a399e4923bf425511f754f9ffd32e79c73c6e6dfb42401f2e4263ddf26a5a7b4798db8ab5063470d1e92f5bc9fb9e90313b6cbcbb97b78c311076fd8ae10c67f8557e24a8530fc31afc425823d0192f496fa159ec322301f03b62459e1531f1326897998d28429b2429215c4242a7cea95b862420b13c21449e1611e464c02f329bc12978b11206dd2a38e77f97e98f004160192b456c6c8bcd505b5aadf5a103c9d3ce83b4806a6e761759af5b2d3ac1d3c4e2898de43f004a2c00305144c38c0704d35ad0385efb7240c97c23ce942ea4955cc93e10a3fbf2747979f7c99193d0a2b885ecc63c4e92a71ba8a0423c160b87cbd98bb7b31d493a77b9aab492dfd938ef7d0051592a8ebf2289c4e3f4932b4c078627075e000c3d5412f8f905e8bd32daf147dcc149f4b0c768476c7ea785942fbb236a185f578537acbe2f4ec7665b6f2daaa1ce0ced2f6a4b3e514e0cef20740d78b95c42a20d905756f70cb8b75417386f4fd1d12e68c28f4a4a78467047b4270374717c7ed17855e96d073c25c79735829bc9a131ecd2ae1e918e1cdae10b73fc6dd0424493012ac59235107faa49b6679599e709dd906314e40fe182ac098786769bb628d0092f4262a7f602b7fe033f2f822e07ab991ce9c01127a4a839e2bf242d1d3d0cbf1643df614bdd8fc9800753c01241d7663059c651a57ba23dcee9f97b67b89a7945366d7cb79cb8bd129367a9382bbf4e62dcfdadc1e293d3d65fc5cd28be1faed8b04bbfd306212b7dfc2bca026dd7257bf5706cfedf7724a359c946ae45cb9612c91d2a2ae19c6929ef9dd2c3d76ce9dd64addadbd21d1e0f67b21d17ba290e779d5e646619e52992d18f107a6f17cced03f61a142dda9e751774aa50d2b58ea127aa3b7d09c7239648398ad0b48c134deccfbb04461a5893692b84da5883bb234890af8078cbd3121bb527d29659ac5ff05e4f15a8ffdee30e00e08739b1e3bcbcba65937cd9a4d9ae5a277965a947f01bb26360f3805bce91116e453de7084693e116908fd1c58c811e60c7dfa201620e9cdee8e90be5e4c88fece8d39c3a2b7c45cf593ae1763038cb101b8c4645524ca90a1a1e30d90f47236d65ececb79b9772102744b928125907a98afe98e44431276a51ee649306fa54498094cd7ab059cab7a21c4172d98b2061aaefe01d0009cae1670aee94abd87f3168ce8c95ce5cd7aecd28c9c3bcb328256b8b32c23cbf5b0f4dcdcd59d251954dc260521e2c55ce0c5bc5438a7ea5906efda578fad839765ae3c27b415e0df590b0ac225a917d4ac865dd2cb592f47041046447a391bf8bdbfacdebc4563377aa3b79a50e8e564e8be6ce2d31ea555fdd486a7960bfd4a8f306fb9b52048a908bb5eee65611667085f2be620a485f8754a29cde9d1be442f3757a1e7e53c2c4cb04c0b03a6a9df6f73bcdc9ca16f97b8277b8465b2c40d2f7d0c2fe7e54886590b82a7d3ff68a49964d8cd6d3ecb0774946ebd50d34c328ca473ea05dcf89bab9ea2ebde69add469e88349655060ae66d84840a0d5bd1aa0fcceb8fe3dbcba088ec61e7374fad39d652d766bf546445be66a52fa0e454911519488ed3099c3a60366fce08b2fe2c0820b2454173ce9b2e8dcfdad771a37a85574c00e44159a0d80d494e8073b8d17d488f080e84eede0e4fed61f6c96c398d02a4588dca110795773e22a31d3195d75ff67c2f5efe15dd7f99f9e70ddabd85caf2ef7ceba98c249e5de32b78180d1845ad65e178c245cd19d2518552e297391a8e176935a6a45d2dfbdcef9355e653dcecfe971fea706c8b66bad959e219a3d4890eb0bc04e1c4d4add99ddbdab7bd78973aeead7747b13ead3878e2f35c5db4fa29af93f166bd755facd16df1e3df2f7b5bd53786fbde589f6072c906151d62545cc091f9001b6864b17d80c982c02cc5126e6655ee16109d05ee9aa36e6aa7faed80d5631154f244bce7070c9977c8e4d02d48075e63855e08d592bda6cb73f36c77e970bc64aa0d79c9962dc7e1a127ec9945c2dad9536c91d99ab7e2e9100f62519688892cbe2c82a01d6f4c7a05303348220638a1846308212236661ac2146134b9cb4e1ea1775a5b7a60d65e55b73c6f500d3309da993ca9c32cf9863545a964770381c8e6d286bba21b853ea4eddbd56776666e6eaf408264bb078807f744dcd86b2bca1a656fbee86aa751d18cc33f3d6cbcbece5e525889127f63bf422afebba8e5a6652102bf28d6901d889e4dfcef945cc355cb72310a63ceb4fecbaef6ce8e97721f99786bea669a8811e5b01a3efd7c08401f6ae11ff06260d8fc4d1557e0107ecb82151a6c750d7b9bf780d2219072d12d187690df0133dd1adc8b339f3ddfe5aabe51c76c2329eb69c0b8e4ce2ec442077aa1c3c0581f5922fb7abe81b55d210b63cf405321092e10273ec1789d81af9e11a03197d4d03f1d6483462bb464a4a80248f3d36777337cf98ddbb50b7d444d335b9e4f4065e512a460720d9b81c86e09ae56da59251c66849469906eda166f0d42e67fa58e73377cf299d0c324a9fb9bb0d08e80a92b1b821c2e7e2301a02773bae8d33e1709e63029254c7c62c019232d7d936675a6d65aa1ac7b6997be3a84e0ec85598d954ba153e2b4b1e0a541a63aecab9da01abaaada2417b925dd87b8e8e8355ed44e694472d6b16c76aa04373158d739ce0a9b99299d6305b0ad9e62ac6715fa93a0e8452e3d8dd46ebcc4b61bb683845e9bda0e5d48ec25c68812b890f077099de8700b84a3ce50b275eb892f8e005e1b84ce21530447c6033a0b84ae2acab3e4634306d3c679dcfb874498e35cb6b5d83c97231e688450c662bccaed2773e73b16db8682273f9301a02e725dbfaa590be6ab7f2b35506209902ce8d51c21367221a00792269455980b565cd6a1c13313aeb2bfcc3c96c96c50a12a5efd8e6aa2e6db852589ff401fa45a690159345ad60570d638e9527090791c6152f970f1f1336d71530f4e78591c149251ca227a8242d7637390b158d000000500033150000280c0c0643a2f1903c8ee3546d0f14000d788850644e998bc4b12c096214648c22c818620800000c019899295105b844998f63b1ebe1e6dab77b2ddfc762d3e2c6daf9a9ba0529ad06903a2571adfb3fd0f0b736fdf90bf1df0c7e94ef461f689e5d2469bb02223b8acf82ce6375f3f7edf48a738e03144c2378cea167d2ac6e80d6648503d60575a8d3bff6681ac870e305c339ff35a5e54c16d4972d5f731c05a30f7b28566479ea33fbfdba31dd72a43d83104d3f0a284d009374266aaf50c40a2f7b00039a820bae86ec0046d4831ec0a63cf516bbde77fc106768a950f72ff6ba12c0890c87b4e79c1cb10084783b6143060ed4952e57813d1b0c54211a5429776a385cda1c31bbfece6d78f7df4b4dfbea5c677dce0602df47068f968adf3d0da9b4a1c189c097acc75a2f107a3455ec92c8cb63e5e7a5b316feaeb4874339d2a7a8065e85deee3c91dcdbc1be7b28ebfc65ca03a9bf5d5034d055d8e1dd9e07083c2e531425e3ad9c19019483f00a4482c483ee3bf931bb4ee3fdd2723c782a88d4ee065bdcfc13483a09c123fcdd24a2799779c462377cf65e9a27bab7685d55cf60793f291cb8c6bd934567d502d8ef2f149aa5f366d4e874701c345560f652867b1e3f70c0436c8afd4b60e05a29f3d02cf4fa08bd2a5374418872ad10fab2f52fc80df4f6a2c28b25a260445865472cce691ef6c53e7ca93b0f48beb3cbb3a5556829ed3e19e5075046722ef0eee605c99ee1397a54cbb05b3a5a74600880fe93fa48ffd339c763c033a3f5fc0dc0aadf730a9b0305a386d3810a38def6968b5278ba7a52b03f5938436820b91706130654633722951d09afdd1672524f862e0d82993a155d903612928a9020f031a743b71da2a529630a8bade40492ea9b07eea20520956cf049f2b16b9cf9c96aaf590760bd672db10e1511b0c4b5fff1dc88ac28c241d92113e00744b48366bf0e9ed2b67afe97414368f711de14451cf30bc3343cae19d0382b1f94c5c3370d0483184c01e06cc08c365a95720eaa17052644b90a35bf527a977b817c3aad06f4839e0de7ed8afe9df53011b06d47ebc77229f589399e3e921885470a2bc68521ff3598d717c335498af10808a7eea2d11be114bd59e1615477810f3d32bafd939bfced067cff1d1a1db232c56abe80be265b47d83e7f08ab58ef408886659c9af707ae03e805b7e20e313e7591f28efc20badba82b820ec8220227920fd280e3321a839c700e7fb024bd123d1ea1b61476a9758f13ff636ac339eec20f5dd9a8b5bab8acf465defa03c6b4f88f5928ee2681b63afe5897ddccc9b76c0bb6543b6fab85e38998f43b1e272ce50e803f45127b057c59a7cc1c550d0d043059828a1aa6b354499704748703ec7d8817940cc869207290d831afd627e58966ac9ffa1a4c800a7fcaad029f078166ba4707308ed60e77eb183bce4c60e67abcf0133adfc5b561dd7f4163dadb3d9e1f20379274a5ccbf295c74df3d7a4669a801fc4797afce54989b10af438077aea1e4a14020d180416c97aefbe7db57a05d8ca264e33d0572643c3471547f09927902dbe1104be72e4960d2ca45c2d61a06195196eaffbfe4966934678a0551251bd3f4716305f0cdef6938a7a64dbcaea73e0fe6af444f2227d51129c9b4c422a196a7164372d40f4b8d7cf03ff1c01ec21bbb5f420d3c738559b419630c9771c84937eb213b09e36696b8135b777db4ff430a496cecf710d9334b990d6be68f7a025ab8a2779168842c897955f88c8b315ba0918e1c8f537081d3d01f5614932e6e49bc45b62191f60ef032f668ad71d0fa2aacf99af39d382ff16436dd512bde3aa60b04e197652c51db8ad29fdfd446cc59f76ce8a0045b5e1c29a495c6dae5ca2717aec88218b458f917b1edb94a6f3c684aab98ce76204aa96dc3fe1adb74cc6bc5adfc89d5f08dcb706d71e6e16d82d6a7e13e5131945b30df2c37efcffc1e4310ed3b7f625e1ec39c03e05b45c404f6266f769d278b61a0fc4fdf92f111603024e4f6686082613d7e8f16988f9812c9ef70beaa8c911ff7056d343c7f404261923dbaf7309085b3f737c5c591ab4090ec26b03048b4c592f64fe4db88404a3b0f536ccdd25e3b71484c6e226e2019a1429c44b73aca48d47af665d02c4f3ebb42cf2124ab43c88885fd1ba1c2258b906520c8b0b1263504bdc0673f1b39333d04f70218c90db26e810c4f3b1d0a9bff0c66a72390bfe823df8c3d3a8d5f9dfe9c2d61c07674f069f77e4278f18cd4b019a667c1eb8226082070102b96dd1b33c2facc484e964f837eecd38a4d2ca55314bc9d81faba84f41f76ff0d907a58e73f38b02c4d2f28cd9cfde2940888be6309e1d3de79695794801b4aeac186d217f06fa34d9a7a67f6d92437da3b070c14b33f21531c4a2cb0729488f3b91ae57ad9b9780db87010ab40021c8c4abfc5875032c00cfda1dced37a13e43e88df27e55375c75c8b6ff5f6509e509c931ef8f5e84b2d075497c9ed5edd27396c2ac1d15d5cd1504188cd21500dd144225f5dbe04da92f4284551e38106afddd05654db478e296151fa3f3240f557ddbad7c20bea216308ece622fffce98d20999432e577a40e6d8b15ed2e89cec991e97ca72747158f8c590d65b6352ac1f6d553c246806e466ac7b72ef3251ff182d9df3b0b8d5b9e0e0f2bd96fb5165d3b0dab044373773e93f0aadfec49de012eab67c7dffe76a33784df77fc6b11c6f02e1963a23ec8a4e606d0f89a5a85ae6826da34e852482ce53ad6b5fea5086c7f810511656286330780837f9f2210e8203bfc1c4809438c4f3cd3904b6ae6fdd70459d04917baf71568e180e3bb960a2d07201b1f260478db015f306197ff30858a83eedd3806b38499c1c0beadf88168e2710f1933b0618119b748372f5afba50b15ab9d741af1f07a09dfdaccf6891aab4497b268b81c152e9246dcfd82fcdec16e62c44aec445d11a02016bb43a289e26c6a16941aa30b6622c0618b686d43eb69588d54a9889086c83f6de164283366eb3cfb065bb284de87060a2a16e6657aae42911dd261f6c2463d364619d11c5401f0f1c6c4428711a43f27fcd4ed2467cbc430be964d4c711e32b877bd0e1cfb4e8389ed82af45789e840a4a3f8433b8dbed99951a4c181517ca8435bf5511476a4a5b84c9cf71a5493594717dc604dc0963c0687b6140cd26e74dab49346b2f02e19401d5e3af6cb3d178986b0357d6165066c7f0b211cee500d7add1ce107ee9e23acab028f6e8ab0b677f181c343090cde6f66d11b5ea331c96760d8ed03280e06e3ca030614a9e5ef04fd1364dbabdcbd6ba8cd40593d148624b7f87437e4d65069c3fb013f8a5bd2e559e0301dccb798744150ff6d6db07ec039399b51dd1ae953b87442b77a2fd54925a7a79b3ae8a756cc8e5882fd62e8c92d75e7c42bc922bd813a0aad4d725202143401c3fba1d480a5d1ab6326524a471b2417183a83d32a6ea9f29a496eba7b26befd426996b1928dd0d4d8ace8c424a9d2416e5f32f9f572c401bf8e027f099bbcf52a77d2b73cd993dc41b22e71aac1cb215f8c6f541ef03fd6cc06e4cf90cda807ce2ddb5cfe3f50df6162390ee632ecc2636e738008ca49d8bbf60cc0757015ff7cc3e3adbbad062efcc4ee1304758c73e11a89bdb09b87d23ae79b4f992e620d9a198a3071230cdf8c3a9093c544235a1db0c84426e4156b69d7091408414f823443727052c81a38d46669fee20007ccd80943271f87e7025004710ed6758634ceb6103ebbce5cdeae338a9c902de7a0f65b9acef6b49f57726dc92ae6aa393c0dd75726e814aed7961b035f11559602814b3f0c0cee46c72c77c2e62dfab9af60d58f38563e185f6f9111cd1a9a9a467045dccd5b895f1d85b5082e635b5cfad6c152cc26c49bb347ebb47f6b8ae33521ab93a889dcc15ab8586458d384eced28193d76f48fb1c6515e6f9ca82028dfd44a96aa5b38b6c2a446d30ef8acb195d4be75535b9685d46b3c2adbfb4af96292daae028b004f82444cc3fe4fb0775c53f71c8c3f9f74102e893db59373b40841e29cd0c503e45cc22453cce51c51e8a5c6117dd432fb2ad186544dc17386873abc8c69896973c715ac32dc17910273a6dd9ed4eefa8edeea83cd4f2ddbdd8eb05f636abeaf58963d7b7e305adee1bdb855fcffed5eba4c2597f937d6385d2cd89a5bd073a94a0a9d2f16e48d0cf1c2889c98b5952f94b881763483d7d6893131020b17499069e54e800774d77f69fb678606836dfd718a2430365dc718de91576d5d89dd37302d8b7dc23c12dd1b16336ba411e63969cca246d6ff8990817247073f85e5e69184563a7a68210a8cd1758456e7b4b0728a5483d66b960c50f4ed1ac04f77f4fac81bec844412c6efda9b3b75e736e6406be79aabfa96f042cec3e0ad6a50f5d897afc75be8ebfd0847bca21c90030322c3e2bf0fa1f6268f52860c6c75487de4a12f26af5d13acc5f21670bdbd588318de5b337e59f1b35942868985024a3437acea63aee6e37b1e9f24e929c9ab50ace196f80437b235202f5e339a1f8b8f9d9c4897247421b77779b82ab259c89f2d6eb5275f342275c02cde4c57b9a9590ac0b8abd85d2993b4cbfb95292ce7fb6b7a80f63146b28cca0d3f14a2217ccd4d57de28dc1cab67c5fe6510eae1cef03012e1ff42de84ba432642df905081ac2c4f3662510356496bd00b2afa65d0e8b240dca2859c87179e77d384c4731d4cc5a380092322d2ce35c0aabffb4246e3d72831bb10582c421ac75b831b003984945065e58b497b085af6229e3b9cfd861b3496b826e27907a5f4ae71474458130c9e7703c8eda6a86fba82a886fec7da58dfccef77ddd7ed1e9053eeeaa0b1dd998e03390392527839d0753f0ec72c8c1bee91c7f65bd423a3b4004b51dd5889a3e4d4fafb026cdabd80ed6b2a2e703a8d7a1f2f6cb5b542f5878d3117e2ec582a9b32226038b393350ad6b537eba70afd92c2243274a7b5b5088b2da220525c1bd906ab44fd15f064591710323f0773869d67ee0b6915b4d94fdd8468c5c341ba043b4fbf7dd52dd22cde0f8435e7fef798986222d56cd56d352e76e76b0d70ad45819f2d342ede0c706d79664fb47087d7ffaf2d01d634d708b405a1263ce3154f4f080b2fb920bd3a9cefd08bb8358aeddb2221ca76278391e5fde5a07126b771d3199b2969ee8f22f2d62bc385d89a080d6a121efa39a1e116bb1e2c1d23cb2ecaa4330fb51902e20d20525ae8c8514faa0cff839367a447d7163f9f4ef487936e15de4da09d990a04e6806e53027b69f5d4d11e24288635d9d9480c460b595cc663af807ff51138ba0f8838532dbf26aef0d83f6db22a5b6a52002238799139ba566489e4168ea0f02a2850ce64e2044d7cbeb7f876427a1b5bfdd2309ff53b512d6b6dc818640e59dcabc37e6c02d8e801cfcb115dfa3ad16c445a0d1b17eb2f589790275886cd09ef0e6985ca264febeead9e462710ba1179c0bd2ce2884e64c19cba017678a4168f94c7bbd964a742d636d16c62584da18afa2dbb0f6abaed94cd77131aa48946e8d9d009e773083b8881b9c9b7b184dca074fce45bf36749c0a854b524cb11a305e113ceb2161a61701693c8071b7048309c7a375ba0277384c59a3322253a46f185aacdb55f4d578978c28e2c471566b80d7390c64aa4b28d92640b8c569771efb85d3b386d5d8357f6516a3d0b095ec52291e16c584879422b7ea7b284d18e3657413b548db7e9ec6ebcd876fa0a4069e3e59f27584e0e3232cb33fda74c8ad3d3a0013b406c35a6bf30f49a8b76ef74a08b9313139ba68a915ab9df1d9df722fc681b6537ea686560488bae1b33677b115c29fbada46528660ef8f97c4b84bfa898c4e651622c12593764880d2b5f41b0c2765dc2d233c7150e13b644db70ff80fb9310269b867ac773430e721d65f9b9ce215178738013b1df246c101e485c89a641181a300e35f161ad1ed3b8195fcb9db7845439c662cf7d8ffcf469dd479fc3105a9992dbe43e0d045e84124730524d97bfeb0640206f88cd7a60d55b8c722f2222d5e7916fb60cac2a2d8ab0411cff41eb4f1d1113ad48ff99f864db553cbb8ae8736c697281d4e3ca4e87d591e0cc658197eb57806cdf79d269e875b5c6442267f7fa200ed30ebc3647b0814c187f690948bd7661ea1de7ae701acebb5bef1fcce73eed3819b29da1b8a3def8775eccacb91286528d054748fc8379369eb448580915ace93f3e895105859b57c23396928e3895fc83956e1a6080c7128936dbab6a60c4acaf321a49d7c4b93f869d05847d7a67254ef7b9b691aea505e1c66221541c94564a2fa1df45d75359d0bb206510c223020c96fb982a6fe64be71c326b344cc5ce79d48d63ec17457870988f48e2740dc32b828198bbbab5f319c05f608bf631ee949cd8118121bb74f260868898da71060691f9213c80eb8d64403daf0f54a0e790682782bdbdf8687d82465eed1bb76d444d6be461ba7a3e36e28bffb1232a29bff99319e7bf7048e8402d5c4e587ea4ab060818f7284cfe129517a81c143d9deae8533df398362b36e98831664e6957f291113f19298c3cc7b0767347144b69908c10149b68edc8bfb5f461fc8de43c6be87bb0f29f1eecd371fd91d28f9248a56934b0aec95ac9c8326147ec14b6a62660efbed4c0621d5628c883128642f9414ae7f9039c72060ece837ac9738706a3fa45464c868c39ed28f7ead30dc927d98e4a22b85e8b912bd407b0cdceabf56e44a6b3fd620349e233f9b7d826a25f72378e69e3ef222cc29d1832198094b21ba2dc6a290ad58b2431a2226fd040c6d7944502d78775115a91ac21944cad36f63545f607441f2ed781ba8c5283cac2ce16fb51685301814e903cedac0c0905b5927b3f29e98c0e076e50da628f20a344948da7a0796f5540f3c1494bb26c4b5a3427bde90bca48214b0992eb7f2bc5bc2705b2b382db6096d17476255d76af595778e466668494641485fd3310ef8e8445c1c924de230ffb4f7a5904a2c8f66140b9b81b157005283f0902b3c33e33096593c4a54cf3aadd21c6c239c8f2a384e5d7f309decad9053f5d9e428228e32dbc49f66950895ca903097185b8a8ddb4f34bb0b0742fe5420684c5b89ece309ac9688b8e58c518ba41d301e33d7e2112a240c71c13b59a6304d75050cad61987cb25fa82c21b596bb289f527167f7f643d8836b6bae475bb4c89a5c44ea75fa7dcf1f1f515efb5446b57bc8735a94471d80a9ed9f6019755eccbb8f073c7592f2bb743441fed98d66749c44d692c081205d6e276ec729aeb17c7a3e9f29f270ed89710a0ed7801a914c0778234fee256935ebe250ba056b6874edd6739919ba91abd5642b314f546b9b227e6d5093d0cd67a24aea50cea022a282d9a3c70b3accca3fc31b450e5da3c01e8d645d310c2465c63b2d4844b1ec9ef5c4214ba18fc359c2d29d919929f577ab9a7429bd251ec3ee4e5999235832af94f41c86eaee92823ae6cb440a169c4dcdbee895f6965dd8e303bfa0df22a5a41972f0e563a1471974479e3547206430e6409ec4f780a5beeff716401878ce6fba7d126fec41feaf5a02a65751813b8d5ebc6d163b794c71a297bfaba25ce65781d184b09f320066aa9e00cd25120ba7783d17f09d6930038c06db871f4ef655262e3511f48bb8f4bb07ff38ccee3355d41329a52ffe72aa2b9215376b728ff104208ad953ed1245348626a2a363498b229192494cd1c512f6c4111b36610642a8e7b434be946a6421c199769738f09f160704a3f224f0273299e4e92ebadc4d4206e9e78f6fcee892d979b89727419c3fd112a4c2cd600ffca85ffea14e9019c2b9de11ad0f63c39952ceaa8c1fae04c7122146fd7ad64f3bbbe08a6654ba825d357a12fadb4f69aab674764d29a569bd35617fa28e8a1f204b63d411f496ffb0bff57a24c3062416b20d2a3ef3287177898e39b465faa394321a36f0fd8d9d7ff940e1d4faf303016d91aaec674fb01b0ecb0ff040b183a233eca2e1b8764b838064d32fc1ce0ef669729c79816971add2dccdf2dc8f77218bb93cb8b874a4e5da7cd20cde469e583d8085b2e4e7849000a0be05b59985ab775579bb7a74b33a9a88ec4c3d250cc5ba2f2c0b894fd18a084a4199a3c3218cda82d18f963adbc4229679419ab233003290200afbbf2c4e22a7fa84866e9a377fd1415bce14d9b640814d822f9320ab2e4cb774426b2705554188085357c98435a45682c5c9dfd7205a0efc9bf1703c090d9f64d265aff7fe9093279fb17aff484a32c6aaed8d3900e552d4c45433adb96b147bdb4db64847cf675943ad4e8536643c810d52e613bfa03a3824bd8773a4d16e37fac03aa7734d0d0ed29c641a461eba0347c360e4255f505155185c2dc5c8e39ab0a1c8d67149b070ec69eb11e633429cdfad523cdc902fa4287cbf00f84382a7ef56e583ea1cc781f3f91babeba298f291355937a9659d195615212b802da2dff64304c9116ab1dd3dc85931e281cbe970a61de02abf9ce1b6825cfe09239b2dd30fe40cf926f24983a1ab986a43a1894b7b00405fcc79907c08eda4fdb8c076f032446b2afc908b3cd02b1a119bae2dd3b5eb972ff0e7c43b5e62101e74be6cd77cc8113559954c17c51e078f9da083e41de80b67a53127f8261e1a1c8e577e44729218d089e6d824bf3b490279b4463178912855e1c9e91182fc591660a8cc0e71b96c29cc59638f1ddcb2cbaafbe5f5a5e888713cc3e94f4c8abbc5da2cb765dfe3d05274c088eec2f519c463fc22215fe3d08b0293c123566c04174a25faebf8517695df921f15fee92ec824523e140ab4f1efab8139b2c96e1082d849fff997cb193bf69e8951856e355412f16db1567b5c0e2d84e81923b1c518802a052efb6fc4201c6d857666be063f1eff167cdd783bb77460516cd05d170e43731c58d19d4fea5ef24306f90169346a2fabfa43062ce1a1195800c64c7511477f05094d30eba93f82816050f0a72a46ea927380c51257833506f680306350cd289bb3099b9742aa298733a89d83f150a814ae054b0cffa1ea2dd049dbc46b032900ca4a53ba0df8b99f5602664bf5648ebc93e814ff76d11d83d353719c3486b4eeeb87e8b7439acc741581156dab35191d900dccfef9630e33ab065a9102dde1b9642a7e5b48e6cc25e5bf616fe2648c554703f2276feff9fb94185eb680e44805a9db98442e510581dbc3c9250b16b35a449462910b6822015e81a255e8683369fff189477c994145edfdcfe7435cfbb0655b5a0da5ab8edb310e5ec71e63eec9180190051e9f0054714d22f66d7aa0a1059a6dcb3af7ff50faad286d09e99453502c60e7f0ef8a4320ce04e9f3c407e6b8934026df54c858548838305b10d7c1a2aa62952033cd746a885d4b9c8ea15272db7ac645abf6d1556ec06f4da23b0c4f451fb7886221b22e740b2c2eede025ad92ebacb7340423232ee55733afcfb34650af2a350e5c4e568c38e9b75ad44a5ca0c268f5219aa93bb76b16862b1f3e837d9227c2f06c90ae02a1932ea026a97a10e4831b25b5670f221065dff56fac3a0b43a17790967b977e6c54a654a0929ba1de201667a5b43d18c3c20cfc8bacde588cf6e088c207a6f3367888ffafdda1a40412e6fb8f7a93ce7a129604515862c0ef90e1f843146429604802e9dface569b00d13f167131ef234aa946fb1835dbcb20a5676f7a33754ba119035386c64a7830300550dc33c365507384313aac269196635ee2cf76e7485d1d1cb0b9e220c13b383328a0e68024578d380bd55bb6e661f42b9f42e8ec9f3a3ce66f79a4cc528f3525a72a8b3605a6d299205da7e5a5c3b60aee1bd34012b2e4705e5da05009f32f9ffaa1112228d1c1e3fc9d2229c7c3d910860a4b363a11259cd7020824702cc9819aa2e84f9936d027eaec51be202ecf6df48b435759902a15de8f72c300d5537284cd87d15fb23c553892d0207a561260362404e5f58d0d3bffa55f228a871c0896b8e5ba7637dc05940e0098ca8ea4ba5396050080f031e0f9275d4e1da99db1aa083fee01c4a8508bb38ec7902315b1866397946481ef08ec44ac40d255920d0acbb71616a78e20c3bd1997274d30b3c3caedaddc0cac11442834aeb5d96d98968779ef67130ad1ec6d40473b88a851bd697e8787092d8b03892718887eced2e41f9990b3a8332ebecc4a80b9c2841f1654c2a9b825e1c14edf07b42ce274fdd75fb87e4eb001a7c891dea29ac3e5234bb018e861abe85ae799c0261fa658e03a473188bf7006852dbc66338aac0dc8300b1f7bcf5fccf635bdc393d601286f5dbdf30f4865be8e4a96c658c8892ab187b31ae69d4989ddd9ad5da2cc8737442301ebf894e6064368a0a77b913728d6e17ef36306f6111384d2e493cc81acea699a89bf3afba81e9d6dcc482a5478dfffbf0550a7b2700cba8ef6e30b6d2006b799018b5167b1be5d4110145999534ef789df071c5d8f2832ca57baa4771f492add1656ab1b41ea00b66dc23bf1dbcd3dc65bc9eaa80b8e8ed7cbe77fb981ee8a0f1d814b48e681c1dab46fb422e01150f2d88996590fcf6bc65a1bd43420accbe1978f01c5684989a0ac0a10fc14a64df171c71bc7c665acc2601c1f3a47d564ef93739d349f61de2ec2f961d23e9da632ddf3bfcb0ab7aca4031032e16fbfbf81aba9191d9b060c40cbba6a8fb1059161763f9dcf04280201560fb19de955e0039e986786df1ac5591c462202c8c4ea2f2932962795ced045bcb893d16a760855e25f9b8c61c8ec7915988e7dafba5fff087c39f624426cb1835d817cf8aa88f576907f64e08f55977cd37ed1661b0d7646fc0bbdb3e08f5daa06cc24351528937e2014a43b0dcd01589402464c8c606c7fe889cd12a705f210796663d82f8eea10e6a8a26c424c688f35358afceea2ec10ee9577bc56a7bf100d0222cd5f15cccca070f2abeb0055654ef4e9269aca3aaa5bc7792fe1a084860e4e6ecbc3c189538454d21cba7a87675b71b463e45e9bd2dc98a331c6965bf63efb10250481b42d101515cdf673ab0297248450f307d124309a2b5352abf65b10cd01ac1ad159993e27bc7dc861c8ec3425474439516661600ab360a8aab2a52582b9d0e32bbc0f55d84ede2dfd2cc876a5951d195500f9a7555235b5478fb4d184b9410ac5adb30ba3b0b56d3c486ba3092d142f8685625199cc301415871b419316011c52aee304d6568766654d506a6b70dd4eb7f9f6de8d968a2ca6b07e9ec460c512a39bbd08c77e74c225f5940ed05bbaac21275201dd74663fa2720ed5a289b9ec5f4ea118b243c13afa4f22ccdb647c67afb95072430b53925244319015b79f1a2cc6a477bd8da7dc12d04789f3aebd5ba8dc41a034541b7bf0d977a5881b95131ce9d55c78c73991113c3a91225888152d7d5383492b372ba8a223a40ad5c57971f2224d938d2d926dca2c7a438a035a40ef65009e28521b433c9bc8adca826e978ed7887d1279016596ff2344a681ad344e5622c5b47e1f9af7be91c49bd9d14cf65c057ba309dc2156d2f8130acb128f482273648ce0b805a7721c8e21d3f83d88a4e51cd85145959c837017b1a0d78c03c84631d810742002e54e465496c7717a822ca7433009d2936002ea7be09ff851c47ac1a79192a1809c5108e1714e944821799555f81a403802c88fc2ff19665d2bde382e1b9ec16f4b1b250ec4a426a8039401873a8380709e44bf2c7d0d270698a806dab9802ca860a147c5c594a9a4ebd9870fc094836860e9580cc88e7757c419bac349d060eaa30c127077348f9493d3e501a75f70a0951c89724bcfade5738f166711da6e94d18107b7907e140f9bbcacb9455995a532041d520675f92e8786a72e304f086b083cae51a76f03032710107c0df990f6759c80651d1a45eb0040aaa01bfb74b272557ba9b2097b06c8077bff816a771400d769186e07566016fb1a06231e4f13d4da01b17c6e0428453860ed1b614fd59e8ac1e1f7d34878245d9e32fd432bea3dfcac03634ee1748991b122e1135daf2a08fea8d7c7eae09796b0fd559e69ed0aabf45ae2ead4f10861ceaa03c257d3d6e08a5d961004a6c71aebdf4a6e1826119721b318956f2433400c6d4380db4c3089be07d94475624f7ca7167d0dd66783a95c34e3319e5ce074f6cea9c5c429dfb7aeafe0bd1e872e74167143a1f4ecdefb869893d1e35144c027e2e3de8f610c7a2089420f4fbba8ac4487474721017b62830c76463b213740d535b6ea40dfabafab7906972dbafc9e7260f702fcf121c3944da45d07342c14f8628785302dba87a26151a8c3728e48f822ceaf85b789c597afd1da2c982ba7ef7f43ec8db9084ada7607befd3791287b79fce9777fda65045242a23e5ab13eb7484a692d2212a5bc3b1d03133f89517edd1e7f44ed4463c37d40a0154512d18793b1d806ed8219a632701bdb99fa1451fc94f233530c1f38201ef60cd240ef03c7907cd78015cbbd5644eb40f87485f6909526e211414f7c7ae96fb3c60fac04fe205b0ea2172b622c5ab0af10241ef3bfc929b3dd87f6da2a7c6e2486a98512e9a40fabbb582cd2bb6f34ee6191fe250ac8776945d7707090e535f0351716a38c4a1ce1d942bdc31c715d1a0debfede2b0562fbb815b66434b0a764b042c577e41c61b16ead5bddb162b192c09ba6aeeaa1a30ceb530e46f60570781ea51b8f4a91bd20eb28ee71c4d385d017d2409d128b463db8ce1441de64a15108d453b055614ad492fe73d6149015c8759bed0861f27a092c450f9f41af7c4561d66848b90305581bbe770921ab90841662ab5e4c5728f5d03453390110aed4037c1a2c2fc708df5252280a0d2d720de67461931a08df71c4694449c4be29ac088c0c70f8366295247e64ae284a0afb8c84b443360192847b832f9237e7dcd6a58341e75c511a3c5843a761c4d45a13665f953b80645f80bbd55c627e80ee72c1a47a50500cf67fd9cc94ab1b0752124f1d1c867ddd6e58c88bf9ef667609fac12932e16b1f83bc381000350df632786aea32682e64df6612cb6c9d1f5d261cf585ccee9cbd3d9a6ee906718b12ec0f731b010b88f298cbf476f8d457921b347f07262d5907346e74e25c0a82bbba51ed31ee5352bfec11d550fa099c713b1c91b8e93b1b652e845b62b37dd62891ab360a310cdd055b241ebe1299e570eb775ab98f65a5b950947a3bacfdcd171e2c8f3444620ee42ded47f409a376dbab92261b759b79a3f6c0cb7fa6080ca69dee32afa7a2ec1dc789d6d8cb0ecaf5fbbfc80ab6458f93287b7e98fa239a32ad8183163f9657ac7f0ad1bb2fac108deb90ff23ed7e4f1ebe11e535ffcce5427d58102861133fe40d0774e0d56ab6210effa90bfa1c6c3f4e99bfda741c9bb86bfbf5741f957b36db8fa6be071b1e79423530f6fd65e59a9e667637824d3dc844ed5f8aeb78a148b691aff2358150367c327320a97a184cfc508af8134fc8b5138a30907a49dd46999d43cb5c3dcba0aa7bd204f3f6dba53513f2418c3e88500d197795a3302e6089999a2be91e62a8fac348c8ffdc855ca57b66e7666140a7cd01f87fe43432e74484359a302f798d4d207ce1112e903e87372a1b17e5c5857ff94d46c4bc9a15314ad434e1a4462037ecef1f59e7da08cd31341938e53e0b23810c2e045d67d7917423748bd6428460a95d52282db4559047d3f181d42adc9ca9a1e92a5e2991be35caeab220718f568302045e94b67c50ec71cc82077314a2bc129393c29d816ad7a008b9ac9a8911dfa4f098229f1f50a59abbfdb69abc327950039f409d710b3efe086cc1f93747d2b5e0a90ec8e534beab8e6db6564d3a56e2609b2b8594fa37986dcbd4abb9192186b9701f887c350169c613577d8a652b9b5c7b9552c81b09279af91d5d2a134aaa78762d632db17ab658ce00447d99cc96ef8abfbf11b94252637ee543e6ec388d7fd5d0e19d3532e25a7520dd66193a12974201bd3ba5b224c670667b0d82f8f0d7c7b4720caf1b27009671e34481b109bf6be59635d48ceba070acf9feb51fc5b3cb9af4cd590450036a63647a627e98e305ad5cf0aa26b693e292be34afbe51f5c359dbb3c34bac6b669e9769e7b343d860ce461795a912929d2326f1c4c2e6c4d43b6bd348a05dc32deb7811fba9e5844ea88c13d5d73f13ac99e42fb99c237505942ee464c9a726312a0f32df6cb5449cb0fb1c8e10e76441ef20783001e43b726d265f56a1dc7f27d481389897cd5be1447c860e6440672ac64dc2ea24c4b2d294ed88a308b32e851d286a2109b558ff85dc7fa485666c25653730556bbdb3ae65418087c0203d94ac692f19e82026f0b874ac5e015188366a6bb52e72bbd0e94c53acbd5086bd80c520b70c9993a1eae89f25e514f6594092d1cffe6c21021ada9e34eda62ba8a4d6eb817b2ece1417e4ba35eff0fb70bd639e6242045761863e03df8d96ac0536eb8631e3ada6a8aa704059754f785c5c1fd5ead157076131cffaf2694d88e2387d99e565568a921d83bbd3a667a2e002c6601361b541a2669a88a873915af823d9ffea05b3d5c69629fa0ebb20a02e198faa6318937b8d23bb8e5ab95f0edfeedd44233163cb656f76f8ae2ac0cd06e1fad7c90ca6f4cba0d6c6db19533b37c6c8d52103fa04d0210a192cf659b1b0c58e52872df0de75adbce2edd8174f99046c36224323ab24766e4105ecf702b40591c2cb1d2ceca754450278143d191fbe4150ebdc7b63c6ddfdeb252b9232780f1d646206af529b2e9fc0541cc0222f3890486f2fcfc82710025667772a50585c292ec766ee73fd4a27f88c3766177d540072f87619e002044efefe34e38264836705de23088918403f84d02e685451f8eee7d8389242847766364ce1852d44c2a96ea51f698cdc8d3d195b316f97d7f91c3a47c45dd79dc25f41e25730b8a23c9766aa931f31a7f1bdc1eea9321e477a181b305092d7d5629944a2c26b1c7d04b749aed333e7502dd87ce5712531882fdd6a8c07610602b462dbb06d15fa8888b7f3ca01d135bca1f477408195f21f66eb12d5b89c6a104e45cf09083c5f59e521757f43875c4428e90283ec9517585b8fab29cf462907a570cf43e3adb849e4b809e8188a84328306fa8af2b45a363415ae59e8876a5638e4f8c57617369e5ff046c54664ace63cd96c81105bfe559a4d170440c768d27421aa02d29c5f018f194bef2780f81f3dd375fa58cc0067687d1a96663c33ff6a641441269e2c836b606fcde79c49c1068b4ecec1d8c189ac568d393a9714220ea620db7e60a884d74c98f0169a87b23459fb6776a7040053fe43104837d6b06ae933a9854e4d83cc380138f98cca0aa7b2b927c0a474008b23e8f605234607e1b3b394caec5a0150998dd84f3b30bbdf236071d5cb0f4efca4fa782aa022cd9d69c13e6fb280b7d99df7f631170eb4f97da4590dd0a7f3b3b9207136da1a900e4440c49fdec5e607e0b1271cf09cba41f51a077028e438796a17fe921ac465bb098fef1879948c3aba899969075b66bdda82e76c5cccbbe79e94574461b1a9dc4156c2e4a67a16374668cc104b8892123607386883c5e12298e27ea580b86e2b87fa684869c5d2b77319a4dd512e35cb51c85a78b90543a2082720956b8277b00102cd8756424106e94d74ff1ddb6c552f4df081acd11b4ac4a4f9e9c69557b28e62d241af337e9d5497066a7585c05d98b6c53602ec02d25fde450a389a7884b7cd3cbb9376768c20d5cd6e856d2206b56de9f33283d8a241e8b969c0a812434bbac15570f9f3d92a5180fe96ed57df3ef480cb612065af956635dff9b4598ba6778f6f8aa6ef573c33adcb03ab0729b8b82daeb8c3c60c183e1d58fb23ec8e0aff8a7a55e4e3a7d323243794f2bd5181878f4ee90a4339d41eb0f9a8d3d6307c2f8ada522fbdb9bc96b990cf30dc99f5603762f4e1237f3936329528621aa10150997fe1f15c2e8ab1b449a3c62f7578b12020d6d0aca7cf6683addd79a2f5680ac1703cf7e25c9d851ab6db5439af25dd7cf0cece3c86f8fe0d105ef87741514e39e229ccdf78298a8eab5f907ad7b62aed55ea8a8883e0821cd6d18e4a212b2f34a6dd56983ba871679179ee0f6d684867efddb0bf6e6be1dbcae716d0e295e19983ec7625f01c2b05c844bd881b1ae93847f4c580f71ef838e53a51f44b6863934e871b961636003f568d3c7cb799e909e8406bdf4f84c5c4f3018601c2e76419d876fd3d705076e4ae21cb0b0b8ae6f6ea279d357dea472fe19b75b3102fe70484407c6dc2bbb115801ff46f36ea80ac31a0666040ff1946d556df1dedcc24c80d987db71c5568004cd70c270f522b444e3fa9a06b8f3487379cd5b6064ed1802bbe0ca2b1b3673006bb2ccf48182be13a5f110269ef7c33704e5adf5caac55b665fcdf2e585d4755b8a67bd65672b6a083a7450c7bfe79c5e75f5b0b7a227632d243010a3111dedf316a1b678cab95b73c88ec0543703252b7e161085db42983cb01aaa2de043be102e458018f266013e949fd5b018db39a3c70093f080a1fbc5ab38c7a8b67f277c605894518e0e5b3b7d7fafee05f80290f839e79a9eb776ef17e80d307f35c71dcb4655c0738e72558f6a48bd1a0b30bab8981b488247dc8621a1e4b7aaccb3f96f004259fe1fbc23a2925e0a6144ff030598fcc83ce50e0378c1c0e476f536592c0377e654475a6003a1994596308d307c9d8bb9499be4ca47538d7cc0dd4869ae88d210dd14d5ad803350667c07f1ba90c0b5bd856dc789f49ef7a6b270edc8eb74bd465436118a09d7c10265213c66d100d3098e29bfdb1a371d75f6ce20a42c58d3176a98dc4a908ccade5dcf48a94273da3283f8384aaa91e120dc65e35d08ab788de36e5da2d91f1cd3f6ace1be8f25ac79cd5f67574ebb4f9c1a95f6db8cb1592164694af31f7638491d3737d953b3a4804ecc048ed42c87e4c6735ff1746fe0da06f936e98b060f1496a15847ddc2f1c4dd5a4b665fbb3221c59709bd18d34fbc3176f6feba85afd571ab542e7d06be56c2b11490c779c82672cff60059456201fedd71c0bd1360101a89eb915bdad8462c13d73270f972837190666ae6b5ae31c2503da9448646ec54fab74480901db945214b2a5aba085e31950f1c6fcc82b4415b05a5fd2561edb88ddd5e82942d20f53d4b9148f063d8c2a97ddc9146c3c7f5a581765643e078d5a9cbc064cc9ad1271dd62634ec37a4be02fbba9d8d84d39092b890406f1e803924d79347b7afcb8ef0273ad9d640a871f358e2b6d07f46e170fcda2e693c63203c1df1268ce9aea944f99535a2185ed886f4f843f99f4c32ec23958ffa932a9decb02cb1db2b59b8497a47d7b25f314fb6de5b429d8b6ff925e3f28d3ea9450d1b9047765a0f689d765fe488428bdb2be7f971440937cb94484a255457e9ba637061cebea3912abe91536dd28d74d591629b4d23da56f9751c71491bea9b7d54f20fdcf2ca42244004f39c24704ae26550404a23d1853b58cebaee17cf333f3ec6918dde2c0cce6704aba031e47a8316ec668c98d249c250a4b763c5812227847304d10d3f37b5d41eb27991ad7ba9ee9cb50c259b94c136453ab475d39636a624530d75afef90ca6c231e78d6286974027f575ecddff0cb67b104e9b87b23cebed782624f2b7f747b9491b7c340c7c9ef3aa761565843d7d1cafb82200fef8fe41b10ab282f72dcfe52e6b45f9ba835bc618b31538b335745b3821ccdd592353ab80a754c2be84ef9511076a09910a2d3febf7401c1836de8dc502a3d27653951b6e339fd20bdb1b44da637e72059374865fb98d764e50501bf9869ccb5c1661df6fc9d0b6aaf81b3f448196e895983254209b7716abfe1415dea2461231fbc94c198a5dc01b4228c6deaaa210aefec54429105b1724dc20d136b93a44116cdc28d1d3534f40f0b7a39ee22e266fd06c135fad307305fe41ef8aa8ef4f1dddb839c1178ec211421f1dc6d338486b335c4eac4db6672b7e199690479714b2e9a57e6cdda065175d00370f2f15a88c71b06fb9772b688bb2ce20a86adfae5fbb78536d2b4a9fcb626f9fa9607b8ca0d223856938192a2f6f838c9a4468034ec7a92b8ea7061089cd1c1b4484c3a904c8b2c60129c9bda651154890e3e04de896b15c7d445f00c5608e60f4042eed96f40b04ce0b5801d506d07a087aa43394d6c045255568fdc8402dfa5b9ee0967db27b0c4e87c35ca94a1d8de0c1675f50d2b0940b1559bb9fb277e3347536969ea3542774e017c21b355d26d02a6556c9072ecfb12737ddaac0ee1a2b2b901b5e861e7f2e78583b63cd4b4466763f6d45ce1a2de9e0a42632608b9c20bd10af6684015f1e8d350b05c694cc519f0cdf5f068b501d55964adbe36c3aa219b29feb4670ed125c1ebf59494d201454b5a114e0f569f68f1e8b8da6b465e48c0b8bfe5980ecb50ea6c33d28db402d0f1b01b230a1ea0bba4a410da55dc816ff8424c934deb1e7c0ffbca69189b1df50167d8cf34d9be800b2d2788ee4d6e0438ffd1f82a4adf37207d2c24c63e181fae12c4adc54e670bea1aeb0e1391235bb2e06dcf2ca213aad1bbb05fc58d461bdccdcc9ab80a1517b3e34c1892e4b35c765b4dd6aabf7ac35a2f30690569ae17105a9f249431558b1fe02e83bbf10a463ccc3a05865995668c85cd348253397b18dbda72464b919a3ffd11966cc2af44b5717d08d0d5d77a6b70700515a2441598a2f63ff0c675b67ead7a8664bbcf2f65bc11674a20db88a9c9b062badd8e2add28080d47a211b54d5113e795012d3f0b30e0135bceca4948f91d0d88d613472daa68278b9255d383be50780fa659eff8850718cf49a3733262049ee72a4fe071405f10dce3cd03f704ec33721a6022c7e61066189e7c8b287d1425211b182f720d7881249f0f28ee526f7da125ae7b17627ca7efc309d1f28c5c35a17070c8fa039d49cdcf0b68fcf54faae4f8fd95d1b9c7af65f343705a4060d6b866bc1d163cb12e90d78bb23dd8dcf2038521ef30fb4e447931501a52ec49d3eb1ba22233e68c101a7e81e5d86702310032307452075eb9309bad7a4953f39b152336579606325c2a7d9b4d8f1af8da5a1a2d0d726aae1c205d1ec1d6429a0b89bfa1aa178bb95a30163bc11f2c995fa722c8471aaf945a7e9147bb89d4863b3c96e5a988875b99ad12691ff9397b6db454051c1649135c25ec90d333991bc9e13af164f93a520a09f17ac00922542427525416747063945a508a22f5ee2bd28eff645c10cb96884c2edf73a411117b8088abaa304c98c4b7a43e20c684ced4d5fae1c581b241e84a325d1cd59789027a39e3043b284e9765c5b25a9b9fbd775fde5de9cbb07bbe23608c89b500a3eca28854b785aafb362c66c4d2bd1de9800a300f26641142cfd5669f3c4bd8fb33e8858c475995410f0d2431bee8bc80e053a37d9765e71904a15681a383b064c2f30e744818832baa11c83da251634eec6749f8f6b3537fac795954fb63e1291e485a008aa64e02d37c0bb1fc76475519dd93d4f3dddb56bfaa79d7473a6ac1dcc3ca96bb30171801fe8d88564471f92718b586a4bb70f9d14ec31e559c344442d9a68acce4696573416f4f6e17ecb3f168f0587138dbe661bf40b3d936b91959e65e6a35478feeea8603f5942a82ed2018ea0329a4fffe77fd5b04efba736e152ead9eb51b1659d4912a642bafab505ea57459d8c0c5198a60ded0ae22f0367b94ebced332126d8aca35330ac9948454f1795edd94d5419f9a54f9ecdc422d970e8c135326c37c8a1d5e4eb4ecde0501ebd5ec23947cde4afcc1c95d004648dc59437cfb707eeb348f8fed928cd92de2e587ab3f52e35f6a611443fe5a5a29fe563c245604326ae863488008a7cc871142327e7fb7663759fa3e1b7d98c2ca637d3b7e6d7847aec5ef1162364f8126b5c6f0ad2efd7bf67cfeec77862f4e7671cee7b216741fe37c440f6477814c71488d9bbe63c064a0eb2c3146df8474efd4d8350a9f740391cf8a639514cb102de8076854029408ccc43ff94e149dc709d6f01784b6cf5da63e8b9852d56fac1a84a05cae1fea1eaae421728914496611a4214c25f2162aa1b4a66d3ec45b37306e91f8fe49d621a33988864525f6a57f2f5dba6b3b08d902295e39ab4df5e7947fd7279378fd6a366ff1a9e7cc0a323075f309a45a50f915f791b213653c0a09fc44bc44477090db485df5c4fb6bca3bba3e8132ab9c0401c563b0849636f53eb8fe86a71d1dd96eeb2d71377604e3a5cda1a3bf7519a34c6be9fa088a0a352d66f909fca531f3925f720ea3bf442c9a2f0c8873db2fb4d43501a56e18b6011234ab7ceb7b8f1840824a935fbb79ca07a6ac3ed9ac8d09b716da34c7fd8fb4d3efc0f300e683a0c837cdf08432430b08ebc7aa1e23366b064e8e43e6f5831431ba057202ba8ab612414df94504d656c868ea1d243eb4a1356f0f85986b2a03c4298526e7b9e6caa0ec77ac170e84558718c7ee58ba2e3bcb71fe81878c5ada24a6a6b1f9273b45e86196121e8dda2914cdfda109340a474dbbd26df94a6b70f5955c2105e832b2eb372e0e17a3b6754de7bca7ddc94e31a8adb83d01e5526c42f7bc3c286ac0aff64c9ec6bea41ffa9341d3d8b71e3731878ee5c2cd50771b934dd8f0c551ae93ad7a1a94c1b74b3228c40a73b6014c127294a70479bbdd8d01a0165c86fd9deae22efbcbf47924fe38c00947138c3dd482d45dd487c04cca14fa5e93b2971f4e1eb0031b5afd4b3b785e3d52000fe651c4bceb8912364bd368473cb8ec4a155b3aa2afa960e99d36af5ff2ae49821f8b49fd2c261fa35e0df07e015f62fe9ec0254ab1d584e8705a5e8bf72e0586462f41f52b9b7cad936b9c98e65a3a2f68cb7e5173b14f4b4db9d0dad394bc62b8455a2cdfdb253e11051555f1dc7223fca83f56b84753a9feb88e85d9fd1abe3c1264197a592afa685bf6c2fca9273b4da84f3e1c80ada4d13d951ce98a0fdd93c181854a184163d4de4546e1be304251d43098670862204dc93a364ce5cc2653bf5eafa788663a41dec3e351807fbdc0b57b7fc5e355ed8565e9a6d1e9ccea63639b11dd5947226151e061cd9ebcc3b99783a4db88352852ae8cbd468b0a0d6a9785ea8242ec793cc717b2cc0ce1c3259f559229c3a26728636f0c017baf26a4e984ff044ae17c77ac59df2b07e50e30fabfd95d67141031d65662ab062d4b105cf1600f085f168cf25f2c96ec7cf9546326a032192e1004524290de45104d0f2c46109587f0d90d94c7b9b23a8d763269dbc3641617a6bef06c548fb9d4f26a24844c342720659fa1a90fe259ec09454c0278c2e00ed68267b7981aa8efd60d37e2524d1e4e5cffafe60ef934d6d2124aaa95839f2912efd5717b164de3481d0f4f036079f2c67ee5ddc24fc21b44b0b138cbd1e78096b090fb6e207c08f09b1dd64a0a673175408a9b03ab96edb81303bf273dc9496d19ff2a8b48afa5b577a4bf495aa6d4c602c8bbcbe6604fef0ec8eba6be0350f9047d8613e4f9876a63f9b01b6fcc23f466212c419856799676d0015f3d3b380c49cdc6bc654700356a1884031c287022bcb1eb020bd50e241aa901fe8053d263eb7438c1e8cef8cd1e1b098417b21a49dfb30e285eb28db58f56111f14b22249e2024d9a7b440d7ff69a2e5e9ac378134f4809a69674bd2ab39217dc6a846eebe1ba70e0fce672476d5ad5e5077db876500cc445a8dda55b4c60fea5274f6bde126540b0043d2f68a29ad2e1b9ae894b2ec77a88f25bd99f2f10ed7bef1ff203af3f7f515529ad2325cb03e3a7e9eb27d1cffcff8d84fb6b8519f3f03d7a6325c23b82a376c2268771144867223c4ecfe5b4401ba0c7003f1006812e0cb99a5e55b142b73a19990f2b24845125559d4e054070a647bd7c4045af58d5b0d93272e515fd503af65b59f539856b039b4736bf726c1977e16ef208dd2e2f9aead3d0bf86cb08a96843d37c1ba943014b84aecb1cf578261c77c65126d1ad8b9c50f2543e7624cd25d69004b01129f78e51de4b76f1712346783dc49d4f0ea139bb863b963d083afc129ae99c9b0468e6156591a95434c3adadf31b02a93856c33844fb7229101b21b30580c148075dc890c1e0478f4ec461d9d7138570420cc61997621c1022a1b3c3c9eb1f15bbb00ebb5e03139b7e450ea3aabbc8f1bd0ffb938c0bdfbb937458390a8281c308601643468d3c970b7685ac33c57f88831b9edbba635996f7cd8beb2a1d6c788b51a444c425788aa948e7885169f5e3f43bbf0090affbcd8b8334217410d6f1f22748d83107e338aa93012679a8b2eb46694213dabe216d22f59239a129da4fafdccde5d7fa58fcc178b9de758656465c406a2728f706b4546bda873a70f478e7a04a2b878834542356ddca2dc1ea3614122ef1a8f8a9b07d0d9a05a4770174c798a89223b08a620430d15498a93f3984fd62b0c42e3038d5653908feec8803ec0185c4b026b6b892ff8c1d4dc66f7a426816434611f085cb5d6578db782f7c3057dda91a78e45033e7f48838b8c04177050a37d987c3e4140685cfdd480e80e5a712dea17872e11fc8031dff7c6bd912e2abff9c3145ecc4846ba174ca6afb8045dd2c0b7e570efd354b9c25a585925d99c1baeff8288380ba8b45c4fc4878ca5c89277ff3b3bd1ff8826be10d0660031672ac71641d981999c9c9c281cbc54e2297fe9e0671577f7ae06a0370145ff5334785326febe2097ee14a11425179b721a74de3939f0317775e9210d734c8aa7f606e2ffa596e329a5153e4c7f9044d98d6affa8f512c4daa336100c7da5dd01b352ed3c360a80392eaa658ce4f433c277647471e906dab78343626b71da7ea9006c1cc1dca454d71836fba4dcf63733c70913053acb475b14b9da5516c98ca78fce0ca0bd10a12a5c5864e4cb07a4dc1e4cc4cbd613f5c1b8397b2fd54e9d5b04917526da38d6cbf8635232af012bc8ba496aac6958e64e24f65098a0a5b30254d7c9ea2655e720fa06ce8670616ca17efe809ccad425a4490cb7959e300d06e5af1f2226c4c351f58c874817d6563c94d92ed94d853be302b88bde9a7eca21c7b2171218417eb44bf332755bf9d3e820781b4c252e34a93278b646366f6cebe23bb67828491bdffe7d4a565bf763c9a6826088291b103e04402901f5f555fda8f3e08ee1bae151c4d995abe2b517cd5385aa74f10afc230785365caea8e7bea281edb3823fc65a24c12fe5ebd1b790d66b391652342ad304af4f1b6cdda3143f584e2a6d9382018f7c9f9895aaddba32ac695d2281a16c107f9d58482c2690b0491c0d900108206ba45bfdd5497948b1ec3252724f079a484e59990fffb4a141a713265ff01979061062382ed022a43ebb7ae06860e5b401e336babdadbc15282b103f3628d3a202fe043db62a5ff21ca04cd583382a3f00a9a216f676369e94239d25966e47e8dd385e789e00a4ed3f658fdf9f8cc947512dcfa6307da5c9d7ea600acabfc39eaea593680aed9beedb143ada29ed27136a2297278621d63b1dea1066ea4efd85b0982e3c16c1064f0eb78f89b554cbe2696bf455e17cbbf224f91b1301c4e226888a141869399a8d458883cbbce9f72b6956f147d5122165fb598ccb32b1262331d73aa82dcb6b2e37170ed3b7caad9bbd95afa038a56610e2eb54d28e536013bba76b54dea44143a5a3d4f8dad4cffd151a14be67f586bbd6c1751718c106b1ec34eb3eca01be0185b1ae4de612dae6e9614499f5192c4ee0f02d04aa8c35a2889c4f88cf3efec5e0feb1bc24a1a079298c0d17698974c5390aa408279c23a20304ef938e69c3a5147c8e921228a9ee53e0907c7539fd82af97600b38c93856d314b407af5e04b66658ea7bc11753567486291e7dc8767015e67c88932643efcafe4e676cf094f6dc9589d1633d1f231429f31962af194948a3cac90b0ad8b1cb643e6be9e961e2bc2e77d949a56058209424674df7f195403dce1860cfded4602ead2378930249db8d20e53f0a278ea902b6c0b452ac05dda5ebc696fe2a94c8692f8080af3315376f7e5b2096905e8b186f2b20f2c7d6e7819753fe7f7b0bee7f0067c9329ec63e63997f5e209190e2223afe704a7ba0b44f33ae1e214290a6308a1c3a9497a833a41c403f1d0897ffe4ea702553131fa3efe1ac1822f88cc2848a63e309072903b87f9cc1c415a8b2235c076ad8709edc1d1efa563af71f2eb0108b9b8a6349fae8a220b8f83fe5684fd5d36aca69ad753d7e2ca153d5d2591941cb7fbc2260b38985e068925b80884a1f1719a92997b00b823c21c6aa2143b92646a9341c92f4c1316420d064dc48b99b853a891d8e7fb677a66e01035f878fe1a75866acb789c2e6ccc118d15016c9ed5fc3b1fe2803aa74c971f01a173e538e6169f1292c4e2df8128e733bf6cbadee437eb3804c23f26234df4b52fd9d83199684aedf500bc44149ef128ab2a0db98c28a9808ee55fd9ef17af40cc47102ed63f9eb4e8db682ecca01673c33291b419316ba556bd59c3c2aad4552f19472de589b440713b7818285b4a109071a6ba2e5670b702fc54a20ce7e6ae603879623f567c411e980e44ba5e4a709249eed539547fddc79311bd39c9f947885a9070993cceeb9a8a788852e5b14c6eb6974d504e937dfb48b32f0afeadca0bc7a032dbafa18a201a3fc6adc33f3d49a7e82d88da07b19ab042238aa7fc3c974c149fa4df0f3337d1c23b2141e84b304b267fa158c4e35d55663f55e63e8da9c2b98a8ee7ee3ca02b6e87acd824c371bb9c6d5a907fb1674f8db17d5fbea700fb524322c57f59c445be42b215e10fb83c5e9382a0e2dd138f68a825c9c629a01c437bcbadc8fb174cac4c7757a6fba885514e059207312a857d309a17f9dc15bc603446beaf18d9a9d0e15556d86a1d47730fe82d664001096dc4a3f59310d2543786ca068113368a0d9c059d5ee2c0816e2335667b4d28be3a780f3bc535755592084d8d297d16a3bd14a532dabe0640fa2971cdd968acd8bce0b2ebeca0f8f39334e8e246b4b8f1ec0fea0ee7a69954b534d1a19da414c6c16ba57184157c758b02928fc3bf01a84b8c72734e02349251642a6e63dd04de672a138ffa2f3ad2b5d190fb0efec95394a92ce3bf6f6be38418e000d0612b809eae7873d2c57ca5e2a20100506455946e1851e5a0f1c8fbee723e9b20e40ffbf4c70d744ae57aaecb986718f357b291b57acbf256fe694431d859fd991a1c002ee0ddad200b5a7e93f3476524ab515294ebea90bc2717ad087a73a92c0f5540e527d17b0f5150b642eaa4b76e376095ebb765244830219d4816725ccf07bda4016dbfaf35449a57eb89dd645c4d6c32422035ba79ff6de9922097a97ef757ce2955f69f195273dd9462836e1aa919bf6a0d0f8e0e37de6afefcdca6fa30f86cdbd87cbb7ee984836df49f7303a627f318aaaa565723bcb609a5a033fe64b867d317d90c065b547dbde547256dae1466f9a4412e6219b46b257b65ca5eb0aa0e085955ea685afa7f76ad647b54c0d7f7aa39a74bdd6fabe6e1a5058f8f1fbe248fc0a03c498bb0f84bcaa28ba8c8bcbcc7a7a47d9195cdd403358e0ee1b7f21bab32a33833e7c63526035f3c4242219ff424808a8c3d51648598d06783a6d5df55b7dda3f875d3d3095f92180454d49878cbb88639e16cdd6853f4304e5add0a5e29cf74982d5ac9d99334bee3a9498e135554e8d3cd0629238360cbe6d3654ce72d97e21abf197bcec9929487cfb0f1ce82de62f560eb1911ff98c5d68b9ec535cdea5fdd06f7edb81530fcf490888b1e2214e4033f7f306f8919073dca1e440ccf46162a2cb3f45fac501a2e05fb9c1c9e43c2cb2463541f93186fbcdc0dd291b047f284eae9c44fcdc88bfaef0b5113964b13720ba64937a99a6f944a2e7cf9a60e5990ad9369db94c2c887a3fbd2fe0c8e10826d49991018bcc5d6e5f7046e48245e47d7bbecf41c8fdce4e3f998b47a2fc3f01a7a4b848a827b6038dd235ecd7bfdff97d6462d0a4277fcd7a07626c9c5e6979b74bfae5e87c65e9deb6f2653ca2d473a64909d876d1dde7a30895b89c87d4c2bc176ef901ed2f947e08eae28ca20a3bdb77acf928794f475532af39b5db192dde39b9382af9687c81b7650a26fb991c54bee94d69efee02cbe032d7ff74b596b5d39ec957daf2261090683509e57acefd788de72e6887330721a9a1694b5e1deba56b1311c3553ec1eef9354c883f72f01a24cf2dea65140005138f362f5bf63a49f18178a0e11f745b88eaae65713f084c2f012d78febf9641fc5ef0625910fbc0366d95b539b84226414a2090b7618fef088ab1e2765bb7413c8c1907bf954d2e82c10d4b019398180d177b20137aa5e2ec47c798a181599448ae402a7665980ec12ba61be5154f78cc621dba434ca0c0bd88793e22a1abfbe5dc42e8119f7fc8b1c1792d4893a169d0fe2fe1386df0bc36fc308d4dd6729091b0544a1ca9061d732bf0cca9f78867da0da15104953fca607ed2bf838a00af4f85b9c7b4eda1d40dd684fe1cb574c83bffddea77abde65a4a139675d7914bc7aaa74dbc5b5a08526d1af222b9a07e61d8abc05db4d07c2f698bd05483784b14802f484a2fc4c2122fd02cc388e753564af31c8dcd1888370456ca226ea7ae6b218ea6e42e95103bc9c86d2133feab6c05634ebb5ec37209b2431aa7c55e72ef98abe14cee91163d63bd418b44da10752aaaff8b7896c7e1e014a724cf57de0d80be94e185c552e7b82f7356397be9d8bb7580fb5dc96d504b2c00408a6ad04b92ec5935525ed68c325dbed81ac5d38b2d86eb74a21fbaa783fca20379cb5ba68c5f24196c0a8bb5e85efdf350bb22ce7558957e8cce616e159f76ce2859fd73e890971a735858659514d0b9dd0760c5ab9455aa89878a869aa1915bb87725e80d59e352fa1976654852948e1c19a39d924c5236a247dc100e2e773822a7c894bf17ca9c4cd9c4d95a078978b1db4b1fde90227d33730a3d4a51344dc4b5decd111dbe3d62793b6c9d729f7fed770e7d34343d78b456a8fe5a967b6d7ce37725ee9b65edd3b7b5c26fdf6a5b538a60ecdadcd97afbebaf1c205de4f237aed5812d40eae9c3ffdb24273a8581746b1c19a6c472716ec3fe5e7cf55aa066ad005e8c4bbb1935f3729d11b09fb441b42c6cc9e2390070af666c8a0cf96ff9b826d658c7e067734f3153c8dc5900141ca7a7f8ed08fe42c4f52956203e90db1050a6169bdc08d065eb7016c8a5cbb2f167ff63e492e60bd6a73c70102f849b606ed89cf723f120dcbf207acfd37444683255a000e6bc88b0c708fbfd0d0363f5d7516b046217a87df3ab06c6924a13fb0e21d652df8d0c075f1370934c236b53320c74ba8f5fbe5c1e7b463f7f73405b0cf33af8628b1def14c02ce1697e6aa1a62674a6a4c6d9fc54fe423096a6487a006fb4f2910bb7020d59f68b1560856c5463da09107316c6977ecca4c292d20d03613aea0a15c00397242da1894ce44712bd1694bd816be961d27905ee292bcfa57477bb40a0f1f2d722a6916b76aea7310a9dd6688ae2aeb569a0b0f37a9a50e8766d2d1475c9d6589a28d4cb6aa9ba9e22fdc14308cec13ee7998324a364e75aa57d9b6e509e6a33099b14e8ed8c4b64480283127f400c9924b69a4c90884e8dca8d71688c836a28914de42e36710fbdc24a875d35b399cefe526b032b09c77667a0ffe642cb0fc9932259e1042826bc0088ad95aa656b9bbd4df7117c4691ef128c647c9d01be0e157b2ab1b1131ef6a323572f4bebfab0cb683d26f3c8de307f928cfdb531665852cda0b1aa288c0421faea7d94995153409f7194bc02c759750dbfcc8f484b1774c9efa8bd34c795ab4d37cd976a1e20d7ae2babb72577e3aca79cfb41522c2136af19f9aa9d8450a443ce1c5b89fbe74889ac4b60091f40c69fb8b63c884a4ed7dfa2323d3a207cc54b76abd89f19155555f8f62cd27f30ad0cf170a56e6badc0a352700b0de08fb545c225616b09cde414d81d9c236964540b3103ef365705d1d5cf76cb16b864210c48beb909364093abf5a4e50dd8cc50a7cc2b1b1c0f0d88c6dcc2797d9d89aafe992809a9be754628158b113436adf6dd1838ad0e6df9616858769a4a23d4e630a5d5f199022e9a6f16139dbff97ebc69c64405fa541347d43785588bf649b04aaa6b9de046e03b93c8a6b677f81d6f7ecd24ab1d63077a569da1ba3dcb6ebb3d1cc0fe4852c1f693cc4198a0a8a363fdc7b55c35d3c4592a5b4af439c3e9d5baa579f4834e32f849319098225f27f92feae819b24da45f5dca4d24c9c2929218f6d60207f9f0e6616f60219b3122ef6dacdbeac535b38ee1442cabc711ebafb97018b7e30439943906b8375960a0a5e8b91bb2762588c73866d9f3da74ddc2ba248203af64550bbb77306cac19f7077c865589dedcd081bba5869e8eb9b17629ca1ab028f28f877fa090aa488f4c13fa3f7545a639891df584263b336aaa46deaa548a34bd9252ff04be55754ae2b78e0446784d98c252a5ec5ebaead0f3733062bc904aaf4f7da9cb6a2e5f7b7dfe5058bd01b10873130af9aa73286464a6d70c95f6adfaedab8985556d9d9ff9d473bd29f85424c9110dd2ac9b996a9a41fcdb36b32a3c7cca2b550c67a6ea83665e955b813a9f772ea8b1892a7a419d261d879b2b35f5122445a8344316adc238d0a1192ab942397beafa69907deb005c3a938c3e4b824f58a15e2b0b2e237b722d91eb79ae86711428f386579ada0e6326c8013a926b5ad797e8493b1d4d6113a0caf3834ee2fa08eebc0244783e6a2ac85a10907026d63f75d52b75ab6d04325f8dc1bdac191c83d712e15c603324fcd852697758b7a090b33c443c78173990ac4416c7dd068407787040d54f1f63d0fa97dec810b4bde8db0224a06c7c1e4ba2ea645d1d3c48e6ab163f81cacd525d87babc6ae072614ac6c41abeaa06d22d83d8aaee4e1a71424c58ac4fd40f424f8d52e8626061cc74900f3a0021587bb4c473dbca3cb995d11a1b63d6200ee70c7e9e1dd35e00541810b71f0ccb987966a45ba4b382b2a7d6d449512546dac330b5da59416014ff8b4bb365b0c84ed385b9af67aa65d6075f262eba6085d036a9b7bef6ddcd9c8d16aa36c0feec7ff7b446439ed9f1451f1e2be9b90be9760841a4a3525101084926953fc251b9461c6528bd794c8c347940e6c3354911abf8752c0c1415009dbd8eb377619ca9c94e753d810454485c78deb60fa86997764ceb0d341aa380a5aa45ed9a3c8cc90fe7f18ce08c70b73e1f37d0ab53a69d19c093c4d6a7146853b8e10f6f61de58b9542fd6f1431619d4cbe3661f4082f065119b8c42fd4aa045de1bbff109fba908e233bd6693e16f0294b8a9fa042212e29e0551aa3ea1086ff69120827b13affa69708b734efa3e25a080c2e27daa9f921b82d47785cd5502f121e6d43fa116e4eccff0250dc23cc2408708069646c747f958e184bd8cb700c04d5704a5bbf3e60ba7eefb776f84246f0eefe30f1cd2fde4c980e2e089cbeb2afd783df47c4422b9a189bc1edf2fe4ee839401ac0346a0b30f9923cd76bbe6acd432cc4e51e6c4a946f262cf4ee4f28d023d42f46509e95bfdfe6386f7d956fd4fd0311ba29da29dcaecff0c3a4b1d63d60fb6278dedc2d7f72154e8993b426f152f7c94e48f4bf88ac8d1c44e868014c7038acd328d5b309c55a503c9156f8d689b5ce0c5d56ce4656cdfa2d35c730d32bfc23b39e966cf5b6cd42415eb6a90cbc62a40339a90700818028040886c9da828a398f5885939f9d982c7c6c2ade08119163f37224412b2a5dc7bef2da54c490626097d0872085809ebb0b832a6e8e05a99b3e670b164b5472efbcb4a8993d37fb1e4c930cc9281b040e72b33c20de5ea49b973f080e02045bffcf1d107c71297fefcf9da77e42919d2a6c61a390820557a2a5fdec81739a42357476ee8e5fe9104fd10bc4ce9fb9dd55c03aecb6e75be1707209f624d7e385c39c35aaed25a8abd1af0a05fed7b35b81a05bccc556f49161af5e94b4c77cc891521a95c35b328787878789ce5434e14cad5bd34fcc66fe68d36b727610e873b426f9b5f8d97e5b77d200e0743959766b97c7c69b7919fc5b0460e1f0e1bf9325bbe5bb359ad9edf17fa70cc462e9b21cf793fe043a948e967a51c8d2457a55aa9301e1e1e9eccd188a26ffaa6716eb2b6699be666b064ec932b19e4607fa971707087fbe02f12e7e293dc6171a5bc912cfd5976afe7fdcb1b50def438d839b2f7a8b82b73d2161c57be44191b525aaee160471f6e460e35fa71b4a4744e1807bf2984731a72a8c6d9f61c5c99df75d0a53d09d13059fbb6f6392fdbab22bfcbe14be5332d681f661bae81c9e8c42c99ed39032143c8d832dad12c79877cc988321f1cecf123bb102f5762fc9eca1d147b0e4a0c72e1a587b3788db3f81fad765471af8789443b6cb2b89ad7af2721e6912c9b94ba8eb59d7dc7332cb3b63a8e141dd84816db2d9c97d8d2402d409eca1ebb9e0a35927ea969bf03907c1c5a4afb56dac753efa0cb1c403876d0b4fa75abc95116c50d6f6e8940d2b270e10ef990fc52e45096441aa541a5a6c52499460b373ad291380f3a64d5b6d6b4ba11a95b6fef3afe624a798bc708929e1b5c4a29df55fe72dfdf579e0af27613d6704d0fcee2a14b0f4f85ddea23459c5df5e4b07fcc687f9932ab234ee2541cd49ec32b18d43e9a2b78a6168752b41d57f4e0731544bb82474a4d9357f05cc173c5911d3c15730e4d035dc775e44bb75aaed3729d96ebb45ca7e53a2dd7c16e6b5ae7a06771d8da8134313ab271f08703697dcd2ef2a55b2d97968bdfd8b4acb3f8672a590682aee33ad28673175808f121b27f9c12b3e494192c597ef5af2721e6ac5f9af32b527aff0bc77c32c10d7b55b1afb87307792a66edb32faa9846ec1b23dcb0571de3bfd59ad5aad5bad55a6bb5b572b5926aa5f425ae20176e189bf4084203a57718a4b8a10e1fe847a052ac63091e7cf054d89f8aff8e94fc3a1d353149961f81521278f874b4fdca83fdba89e464486e78f33b083ab84ad1cfdeb2262651c9722d408e405eb0c9bdcab27b3def1f04b1998e7e6dd999b5597e3192e09e4a70b7a6a4a9e3e00e8f64e91b296ed8c9ef95a7b8cc8a3182b84d9639e885fb15217d8cec55af5c88a9094e90428a8bce39e98c020b51445153e845e994524a29a594f273d06b4a29af7972c5f5a7f3bae835e7c428bdae39a79454ca29a76481cce69c534a3aa59453b6b8a00425104144a665512f7ad17b746464d4e42807ab55102b4795128c9690a32a0848c6c95e2a8128475512903891a32a09407952f93176e73ae3c7393f82dbc7acf9df38289d88c794eccb71f008f92eaf94cd3edfc1aee3a0bc3412c8dfb4ac723ab44d0732a693234723c70b39f2b523eee7b08910e99cde91df46e4c7f0e3864d443b927ecdeb9a73ce49678635e0a0cfb6b1f3babe99d3447c64e73971e16648a8074242434345ada1212121a1a22156115151ab6888c512124ac21272c289295c8c527a746464d4e4c808c3b09b15d144134080b450a15285e0fac710dc1de894524ed984ee8aee0a1e8810b10152f5fca0a747a55a05a9543d3d3d2b95cf0a6815b452f9f8f4f420f1e919c210ba1a7180f170e022830a17a9e5b719bdb0cc2566e3663a07db5a178e3443cb59e95e7b4d2aa7d349466987c5ad288b62734397dc8f82d2b568e1bdc7cda8fc8a0d0c1674816273532c2a8b5b6358e166bec3a2f33a2cee7333b41cf3851b66f8fb669881d2154aad8d2b337cf861c08021430c2f545260c0700183d702060aaa830103c6098c9309c6850103060cecaf5c047381c2b9d10926383903c26d079bbbb1651afa98f3c22c79e2b06998ec4c303747b365fac5186356263b4c763626466898ec48263b4c7224931c26394c8c30e1616284c911264632263bfe1299e830c96192c30487894e6422a443021357dedcec663897076e86848e848486868a5a43434242424543ac22a2a256d1108b2524948425b4c41294523ae745e79c744601a4eae9424f8f4ab50a52a97a7a7a562a9f15d02a68a5f2f1e9e941e2d323032000815ef4bae89c73ce0ba3734ea2a19959eb99fb0c5bee639cfbda19de9e62ed338cfd85e973f8c29ef6a93838ff84c97dec3b69827133a10f0cc3fec2287d4a2737d34a8c308c523ae735278d2c4d881f5c96f94d07333aa594535e3f8410a20612238e1c1122a4650942ee952f4e24a713c20f8f14770e7e1c17fac0525a540b6e78fde6c66faadfa4c88f4339bc83580d07b18ff1ccc86f9eb8344b9a78aac9156c32cd92a352921b9d5862942347836689916a093859761947b3a4c992a26c49d192a225474b8c961c5dc166c951137ff19b252d9a25459ef241b3a4680991df2cc1967892c648a3a45d5b07e517e49cd7e537cee212fbc813875302e8d27723232b6cacb0b10288a4b1c2c60a1b2323a3232b8058f1c30a205604b102881536474746464d8e8c8c30620448d5b3a4a747a55a05a9543d3d3d2b95cf0a6815b452f9f8f4f420f1e9c9410e6c6c728c6004974e713324c412121a1a2a6a0d0d090909150db18a888a5a45432c9690501296900c6c60032a94e462342d21a1eba25f8a8dce22d32fa494d2eba2736218e6829d9d9b21a12021a1a1a1a2d6d090909050d110ab88a8a85534c4620909256109d18006179d73d21945caa473c2c07177773fe5983252c1849892ef0215832b4fb47033c922bfeb6067334a889410519a2969296961dc0ca5344a5a9446494b0991122225444a8c9434516244a3e44889d12b69f98ba7a4480991122225439e9222254443981ea7b8f4e775cd2fc54618378d9b9933e366ae8965f1bae6bcb278514ae9753319142972d139279d51509772ce96724e3ae99c524aff39e9754de9eed29950e34a47012827204715147c90695a59b317f3c29ddf9f853ebe141b59e4eb0b6d7671342d154d4bc5cd5c3395af8fa6a5ead181a6d593334d0bc8c198e575d1b4560ec64cd30aa269ad5499a6e543d3eac9ced1b47aa289a685e4b67c26b634ad9e2b083387524a29a594e66014c3e8534a29a5744e6c66d945e79cb40399fef5fde0855b84eecd51050514f962c23de5a882020839947199e07239aaa0e0c9e1ec1e28745e050a9b1caa3ce1b2325bd2ea4275fbb3f8849164a158c559668eaa277672e8b95051639c53fa74776feaedd4779842adbcbd0d960cb3d5faee99cd81638b7169f21409e3a94d7e3398c8c1cd4b0cc2d9de76fe8292d95e05286f4664b6075b5c64e020abb7a6c6f9a7812664733d46909508b7b506d23e3d41d77ef6bdcd21eef631db08c9db04320495375513363d3d8e85bca920bfb4ccf6ddf2c9db7713897d7334b0a7ced5cfb22cf30762268818637b1a1988377806e20d8de48883db6f41400e6e1f3db5f57412dc42983414f423049f5ca9ad9c8d1c8d24a527bdf6b5108dd237cbc1ed6727e1665c1b6cdbfd6d33fdf67dd4cadbbb8dfff04d758420797b17b2fdf63ec45329da47d58f50debe723885eba11133f7853667aad36fa16d9f06f2d4c96f1bf793cb1ce4beee0bb3cc7de4932fccf2e9c4f564f86b22269c7dcde3e0f62eacb8adfb611665d8dd3c1747c351f5a3cadbb7cef61b0e07bb6e5b09d7c8408cb17d0edb5b1ce3e0b6fddc9ae4309c79db5c00617b6f9bfee12fa8df62dee66f1b0aff48a206256e7dfb5cadb556b97d3cbc759253e7c7959f23b99790a3ca082cd30d6e68779052eee8c14155905c4d9e1d2443726c3410c43fb690833e632a12ff9b73a64cf949198b8e84a4e5610707b9eb6df6d95c79e2663f75c89ec7fcc21df26bbcdc3d3c6734081d3372f8355eae33f54be94fc7fcacc6cbdaa7f4cbdfbe987d31a5bf1b3473349d536ed0dc2f8370d09473bfc6d698fd75fb6ed0dcc3e474ece0d6d03e55f6b9e87016777196d661847b5d2d9983998c39fb52e4639f7d9c9f4d1a1c376846a208403ebb5bca96dd1d63cbee8eb16577c7d8b2bb636c29bf20a49d9e398bfcc29acf698afc6ed01c84519eb99f3e51e4595d81e2c6ebc31e9e352dec91b52f4c29b5e4ed9b9bd94a2f2d533a9d5a5c8605bb5c7aeb2fa09fee8d7fe3e95bfcc5863232f65d772f56c24aa57aba5fa555f1547d17f0547a6ec67ee9abc536ffcdd584dd3b5b6b6358bfab1f3f34e113beb5543f3a58aa2753e9df5f62cff5d2977e7aea725f6ac9a72f7d35e1fa257cc2a62beea6fd74702b9dbe305a5b8aa5b71f9afe74c2b3647aec4bb8f4a7d3c974fa8e51fa221c49012847551484b24b8eaa2814e5cbcecbde6a2d2e7dc41cee5ce3fdd387a6af26bce2c4bd6f7aec9ae22ddd8fa5c7ee177a1177d64e7ff105bc54faed96bed6199bced2574f4e386677cfaa54af149ebe9ef08a13d7f4a78f9c0ea68fc7e9f31c2cfdbda6139bebd70f887a42b1a87b7a94fa28f58b31c62972263a656d972d4929363c9bdeb3e9bb414f5fd3924d5fb999fb91337dd843e6fbdb76391a9d4d5f4ff8029e4fdf71341288d3bddf713666367d45d98ebb214f5f47f03e8abb21f3e9519c0d9a4dd24f5f8c31ee57e4e44d5f11202f73624fb0eb380bf6f5c4dace4b9fe7b810c20dad942f27783acbfcc8a5dcef06cda52f7dd1c1ed635cab263063da5f9b4b0fca170cc4a658ba41dd6f1cc43e2f0537f472b0f79c1d20d8d7d670089ee0782ae744c753f5b73fd9f194f6db77346804f4d6ed80b63e8661f631ac5e1cdfe2faadd99d6c9fc3a18fcc99be9abe941a319b9ec649fb93f6a6afef236f38b4251cf6c8a53f611c6679bb98f4912b5dec2397482739d9bead4189ab6d9af3a82051e7afacaedb9f5fabac8f7de7c5975cfc4aaadf0dcac5afcfbdb5918b1c17bfa89363b496528efb52ac7d4b69fd48e96f1bb76d4fa9a556c3316fd1c6aff4ada5947e7d4b29ee766cdd1e6b1976d12971eaf571e5575677be7cd287d98543eeafaf246c81ce2618dcb0470e5348af7d8dcdf73bcf53a5a7afe229ec9b9bc19e746fd05c831217e3300d99b93742901c8f725411e1472e7d2d95627311c8610a57d3924929dc778366eeb92fd6342b733504e1fae78f7d610c2263b664ed63588914c326d51a9f7bacd3691bb0131c0731ecb927d56ec741ec63c4d53ea97eccd7c417bd96a84ddcb003b3f953523987d4dac425c20d3b391fbb4fef21d4194b9508d7bf94815e40448e9c8d52ce1cb480cbf51e94388bf7827fb9d488e121477b4fa11b065a2751e5755db20b47b2ecc24e4b976c45ca570d0149be39aa86b073eda07d64cb6f6e62960c94b30789628be5c8cda2900fd572a5ecba324d23d1af071d36f95ecb914ad774c2b1c30ea7fed2a7779cc52ffab5cd0f0733256ef68511a88d248b4f4cf1884cc4d40db3e8b7bf3eec491653e411539d86fd66da562d472ac5e0f0f223096d7e0db776d28b6b00c635a0b5aee1a096fbde5bc4c626cbeef5bc7f1054d9c4959ec1126526448b6cd96d23bf7fc8283f958c095af6c145c55d5c6ca4c43e1067e66d4f42c4f8f153d134dcc969b517b95bc3f45b4a292f6a821b7a309eda1ed3b298e58b8ab3f8fbfdbc1edccc417b71e7a978d88f98857ff82b1e63fc18196f85a1a9f02985f1171a7a6272a891638708e8e881880f74f6833705d9df6595e5f7423784b93ebbd7f31e068656b1611b56ebb6598ec3308cb3d956710d4cc67ec31ace6e96d8f5cd3a2d9df8ca91a371848ad3829226b339bbe888f4fe94d27a15c54c6775d343767323c3267b21d5686047cd500f2e0e6adad7e2a0dc31d4438da6693b7a38a2b3a30dc97010a646d35c34168ba5a3d32c9d66e934cbc26c487a76788c1ce9d101ad8e0e8c0e4caf563add4ac662e15c2156a94dc3aab49e07f375dd027658a8d41c9d1d1e230eeea8f1a147101ac070d0116378f4d9b21417c7a4b286e7b05b70b4a06f621f111b06d979b2f6f30b61aa946096f20b6bdcf4cbbe91a4f9d91782f3270d28b86164f5b6cddc6171431cb23f0e30608d2ce5570393b76fe51d4ce98f4399c61b70a892bd6b64ec4f42c8232f35ea4d3fe6a65fe33491de7190a76fb0b8e1cd321cfcd9f4ca33b42ce3ca977befbdb9e1fa1251bd5d4629679c92946223fb17622047174adc108c13fbf0963fe61c2277fde18831e6bb38327104b59a64d2b3771ccacf8bd9c70cd5e462797e686b11aed7b6b294e5bf0b2fdce9600a15adbc92a38a0aa08c24c71cf37cea819b26c22abe0842265fda07e845cfe5e1339ce5600fd5c08217aaebfef263da2431e39ff5d0d72c1ed97bd74f8d52accda7f329d5326cd28c07fd1e998aa77864ef2f1f46bc616e73fa4f8d9bf1ce7e511929697e6d895dde943465a683ebe5b4f167a478464a27a5b3869cdbf3298ff9d7fbfb531dae6b5ef3ba40e79696780310f2e3055e2f6b2002a7c807337d30d30fcc33f76889319aa8a33b464957142c9b1cf39c73da29e75ffff9f16b16d190837dbd83f7ff617223204a2c23387fc35804e7535ce79c2ad929957446d2ef66504a2059ca482119fb69e7cb2fbaf464961fa5b4d23fec2c5fbe733a603f9b45fa8e1a016dd4367c47326211dc5ec335825b29b26cefdf469eb7cf5ae42df44c39e9b3b36d9667aced43b7b9f1b9bedb6c33939ba98f71333ed72f6e4335609edfac1a7ba394e7d7440ef6f7e4f0a524851ce0725699a23e765027696e06e57fc977cec60e126309220b9d21fdc5de576a69d6e5fb1a37e3c2df5433eb1895bd93b71fe328b473907843e7dc258829efef0a8c0fb3daabdaabd68cf675dab4d8dcbd85a9059e3f8068409aa6a934dcd9c31a6b45bd9b579e6b2c96c6d280b495168419614d4a1f6eef7dd5b0ca7b5b8aa7e114efbf57f11eccda6fded79a473ce57da18fec3d7d2d45fb161f3df52df00cc2cd94be14cdc39a6046de7bda872d3cacd2c2fb5ea585a7799ef6295e0b2dc5c38ae68f096402718d65b1224da87234261beeae01b9b8335664faf9c36602c18ab022146e91f2e1f53ccff34af68783f44bdcccecaebbc6f3c704825994e7799ee7b5782d498b168fc22d3e72dec7b81624c36345584b53790aa5ad3677f7ed7ddbb4fff0ba71f15af87e5eca474ff914cddf845d2c867f21316671f7f38f87f7f74d5fe8bdc9f3a683a8477d0b1cda8c42a1fee219de24a70d908ac2a4af6d4933e0cc416b32f1745fbfee51beee4f5fe8e518d96118fec32f035694e95f1aab73d841c2e6627881a9e03005671886afd25a190ed21607e9c3805d82c41b626600ea346fba3f3d0a0acafbc816a370872f0e75dcb73ef2c53d68aedf4c41db707a140e5d289f3e46a2586badb5566693f5ee4da6bf5f63ad76c8bd9d65d3cd2653f4175386fac2eb5fe8755f1210e4dac44111f4f681f8525b71132253538ccb3865f10546b2f4a0f9f42e52b8614b6ef964c03ccd3eefa3a60fe688c667fb83b3360ed62307e9db16d97ee46cb4c8d546ae9d147647dcfbf5c87ea62c6e8b1c554fe0c9f771f0d4ed3e84417de872fa1044f9f0f3fde8d7fbcc5f361bc942ffdebf5faff50e700710b2d00c677fa38d7ca9de176e40bc47f9420f3c7de1a3be1084e9bed0c5f4d5566d82831b4e2419121f4f01cd2096a7a2ea094432fd163afd858647fd872b2b1f7699d2f0692cc9421fc617a5c65af9e8171dd4581c37d3f52a6ca17c851cbc9ffd1351be970341f2e500907cff070e6453503e3d0a3e513a8d14e148e4205551b193ef9b70678d65a4b532d58850b836a935723329eac1704d41ee77bcf70b6bc06c7a59a4c3fd64eb934d5e7029fd1a5e7079dcefd1d9f4282728187b94978242c1292894f7502827281f51f451f0a7a0fce9a3a74ebed0473e79ef4f7806e1664e5250b00fb97190a2bea230f617cf20273c855c144eb97f3dea4fa80ff5cecdcc2328944f417d3883780ae50b7d64943fbd09cf20332829a8ef0698a711e766ae4b4af2200e529a3bc9cc0b1fdce9f3cd9ecda66e1ccd7dd357ce86e9ef7d93e94da66b4249c1bd1f7a90da8a3798b08df165b3892cf4b3ecbef0c195471f4e2495e3684cdf3d89b3d19c0ea6efbaefbe69c441fa538883d4348f3848af34aa5d931c7a4d37c9f73b68095d7d9dc4c1226ebc17572b620cfa91abad1863c8757c82427d468119d5922cf451ef58aa6414ea6d467d44c9a050d2e8e4e42377fa18974d3cbc6e3c88a7505a3c7d49e4a9224f794f5fb62a0ac7ece1797fe6eab65edc99fec6dce11a30771fb999ee6be44c5f184436fdc5a1f780ab6884a3baf7efc7c88bbdeff00f58be6fc23f0491ef173993f7919bf1be1a30a34c386614beb97a8ced4e49288fc29d4db87350267d78dd64fa2df0ec417538e6daa26f6966fa42d0b6ee177a34a37c138983f46b58c166837a9a75d9f65996515f6dc928bbcff4f138bde9ef179efefe090e6d9ed1271cb3709230922059c36197c3cd66e670b30912c4c88a5bc9e03646e01e73d6e1cfafb30fd99d44c724a04b8d2123b2bf8c298cb27f4b0f126804dca0d1ece35ffd62bfa2f4872fec07cafa85608b1c663368960cb50d316fa0bbbbbb5bf5842c7332eda7fd57f75ffd587773343e7cf084035071001274dbf004c824f77f51904106a0152e40f08449050c902106333f44266c3a907b60dff9e26e7419fbc20a0465598665dd7d59da7f5d5863f6eac7fab36ed9dffddefdf537cd06ed332ca30dda835aa880ec1fa5a67da1f64e3ffbed8b8077dee807c467edb1c772e46874f869b873bdb6debe8e31b0d7b0b78dd883e5ecd3e1a06338023378b8bc879f1b46554d0ef9028a1609062aa11e95d06a8a22d5143f504d5103d51438aa2988504df14348d5842224a89aa0aab1deb59de38ee35104f724e4865906575c3c0535aed4ea5e299547a85dea865aa3a77c7417808922423be2fb7b7405d9ff5b6eb2bfed19c28f9cbfe82008ea5b61a3b24290da6141c382a0d62e8b1b63cb6cce2ad139eeae85c017260de5cfd0bbfd11dd1bafdb98cdeb861990ebc23e7e60e602e3fdd77b65d8665f78ad4c394504df1dfb30c3300cccb2ac9fd2ebba92454a994996d6a6363b4b11fb9b9e47b1eff6ec7ad08b3b07e79cf4a240ccdc836656c526f6dcb94e8be119197c40b3fc89a3cb28e7a431b6245d34c715e92bc0e8c7c7be1b638c5f17a5c4e667947ed641af138b2f323019a5b06625d5068bd2e3625198b840cc264024c000ec0c1de2a04c0225e2a0a4370e4a2ac4c197924c09d31d3722351a68209de7681ab580bdd33a6023119281320dd662920c868b743fb176f28e57fea4625fc5189493b9f005b0ca003c0258027033da0b29c0a6b96f5586456142fe9183d2286507b7220093715ca4fb5a040828d37d74c759e497f08f24b677aad3834b59439e22b2f9e6d1341ae2d8cbaceb3b8566923b593186fc0dd321a269245f5cb532caf26792027033da9c06b0165f32190d51b87427cc284f8c3144895a3447a2a323c3a2f4dc901ac0a22c712b0e76c35a46a1524524c05a4c65344c8b1ca42d6a448f8ad0af48e9af22f6a3acd23b252a82eef80bea43bd7caa236988c2f5fe561c802472b024592c9e063880b558ca4c5ca4f418fee890b3c8bf52dc0c4a830c6ed83ab5c506e07355a7e59a6d90a932560032750116c790a92f6880b132c3875f0696185e5452f6163b15225f9ca875e3524403c847c1de836800f919f62331867c14f60e7b923800f927783b3921cb375191e55f1c63014207428c03087d892cbfced020536758c70ee42b37807c8b5764ea0cfbc9d41756c35185a30166ecf323365ec85406d812768b5564ea0d18642a0e4b4291a937acc555a636c0927094b9b0f36c3295c65aacc95407d852cb5cf2887c9112c6c50504ff3def4af9f22b4a102e2bc6c891972acb0fe74e4bcacfb0e48931a47c12f62392457e55c0454089c4dd70009817cb52c10d005ab632c3875d3198be0cb305ca03a4775400f657c63e0b648bad8c03c91388ce0c7b591f9aefd0aa932708cf8de7e49f1b24c9eefd3eba81a8914616eb63b00ff3aacd322dcba47b1a98650893fd630fd81f4e75498518638c31c618638c31d2eba7889d9f2290fc1471c44f11427e8a08c1cf102f504ab7cafa19d2c230ac5a9f21419aa669b67256f6cf909d9f21487e8618f13364881f22be10636ca21f228c7e8840c2e974ea13d93f440051c1a3c943efba5a64c0e090438d1c3bf0e0df83ff45db7312646f67798c3f2c3882123f2ce0915262d9cf8dea87052cf8618110aa2ba4c00b3f4864810a3f483c41850518fc2091e4078923fc20b1c40f1235f841e2e60789205fb842d7752dbc9f15186d610ad5b3518021c8c50f119fdc39d8f887080f542c0d5ec460555454546290e1f1a9095f13dff77d33acf0e0e78819fc1c61c40f114e00c18f115bf83182f56384eac7881ffc18a1838b829f132421fee0d8e007a7053f4714fd10e940ee7e88e0e4cec1194ae985fd1081e487880a7e7050405242f5810bcbb4ad6e5555052d4c405505a15c735455a128bbbbabaae033e79c1ca94512d7755da4d20c2428d92ccbb2d2ad82cdb5dbb66dd77471c164adb5d6743a11914824d2e984a80927f6de7b4fbace9e4ea7538732fda06cd7751d0a858887a80785282777d1d64de3482757544abdfb8986488aafb34c1742b8210e36154628724c0edef4bc063f40417229c79f267e10fde821a8d0a487f09b5501947b88dc0f932311a4a0cacdcda0b270637671b0ad837de4621f663252db71506ab86eb5760e72332e9e9255dbea4723662d666dfbfa0667250937e6ebbbdc392da45661840a4339aaaaf07113e4a8aa222823e1569193a36a082887910839aa8684b2e466aaeafad7130a6eb7a722572af7f3deaf73f07e8ccf4b9adcc7788dd164bad3dafa1a86bb95701b742c04d58324953d279d73ce1cd48a04282ad08d5bf520fabe8ae2860e94239007b50d6a4ce9408acd9d55a844ffa5836c0b8cdc5a5c482492ddaa4b8c0c981ce6b4a46dceee397b725b871ca9fbb96d4eaee79c39782aca4f7ead2a272db82f333ce9e9cf30b326a5f511237fc06c4babc927e1b0b17c0ee366645b292fa55476df758f7af9d5ad6b3528215f6b6d4786af1eb9ff18ef1e41f8e0207dedeffb5fafbd0c19c6d190b9543269a62e35d65d2a61d8d7c6bef4df0cecbf4fa5b1c6ba4b25ecbb41730dec79687f5f868f1cfe1877f115c3f01bf6fe845b7c0c18655a128ed96ad65e6bafe5384c93335254aef6fd734edced68f5fb01cb5ae823e6d915db7e9b5fe59c13e36654beb917d18755185b8d4fef1c8d187e7be7689c7e9eb8197bf21aeefe62d4478c67601f230970bbae8bddcf902b4db84d39db5ccb0f673ef9e9e0c9d7b4b43cf993d76165c09da7b282c116170ecbc0303806d7c091030547c0419a83835487a71ad59d3cb5d15b3e508db6b8c8f054e99aacb5265b2a59d2b5d6254606857190d25ef9b0fddf2b7370907646e1e86087a383b7fbd237c7bd9427299fe34e9c34ddfe12ae957b89fb39d9df098ee4be7390ae780e5295eee8434d85fd2cae5495e9eb88ad235cf05a174c8436b362f015276a4dc26988c89c41b8f2a7fc00a661188665fff91aaeabeb9f611cd05ac541cd7350d3b4ecb1ed717de2cedfde034341a0c873258cab16e9d9f5b96458d62d2df1fa5a5af274e2ca9615a552fa9c734a4a29a54e293da294524a29a594d2a794524a29a594be8b20dcf938a2518d2ebe70b93a93b8bedfbd363703c348a40bd31cc3a6ccb24c4e4cdab06f92ffb1bbbb48480179d676778184d6c8716f4f258da32133e935ae723364267d25691ae935d25fdf596e06e9af4b23691a8944b999ab39ed79f863af39f6d9898345b976bb7f735f1df3087d34e6f10396af8fd971cc58e63e1edac71ce391a391917c64cdbf131cd2c7481beedccc667abfa7effaabadc56cdbc8d19059fbaa69da93fefa95a93d492369f7a783f76b5ab276351cb3897b1cda7712e9bb1b773b31f2072c939e03597bed2407d7342bcbc7feb2bd1d77bffcfafdb85b8250eefeace7e0fc15266ee7f9604bebf417c196eb5b71703e922b576637e4c77ffebc9e3e865d1876750e90d89065293ea6418a31e4bb381292b2c434fc7b45faa8d25da494f22f39a57744d197abbf7ba7942947a70b1fdcf9b41ae1cebf7d8554655777166640997f3922375fee2a490ec9225fc65554b510cf215f562e616c815225b355fcb2b8b2a5e5e3689ccc7bd053292d0326879b1a9ec2e4d7399f4cc51cf22588c832df264964c5388e10f50587e7707046a0ecb607360e22110b7982f33defde39bfbfbf1c0e82f2457beda3892cf367e86d1f5eb02e21db9c90e7cf2a72a2d9a8842c18c541eb181a11000000003315002020100a074422a158301a4792b47714800b889a4a724e18cbb328865114a610320619038680008c88880846e300a2a960797975c8ac9a228adc49e1c3d1aac3951473d08c6d44874edd386e79b51752912c3b061b5687dc8d5ab2bda64cb8e401c7d6256b934d176384b5c4802ae4add49c0e9021797005466da61fce2e0e4000f8e169dd4b6ab6dfe6cd52e77b4ee176a444c9a5333730d88e73af59a3dbdee9d5712ae714c4b5c48b4209e2f196901c15dad4f23f90eeb72f0bf05f7617b9115a036825386820abb882aae5d10684296e2d053b55ecc80603565d53c5ae03d6ac5c38fdb554bdb7c8428d3044bf9b62526e1ca9ff42211899e52d537c5ac68791b02be95281c87253fc05e216a20e7799bb32bb0c07ff0dcee684dff9e91b7fc58aaca69a97790c568f0a50a07e962ec1c40a10501093f41114112f798f564ae8aee295b879e1d9ae516fcf9b579e93b9e6c70442314d14e6a00152a323ee5fe6c7407b6ba1012e3bdb9b3e2ac45b3c948454e14c0694faec309c030e18bb96686ff44a5608dfd2b5710350b451507c661dae05cb165cc4b4a50c59e8670cbde51e4651d69adde0ce2ab81346fcef87cd57d61cbb73498e6d516a97684dd6e246d9c1e3e2e0697f9ae69f0e697d93290eb595be971e03ce167138d8c3c3164254c158c6dca8b2f2ca0a8de9e4d43b6580d9a781a0fe990e7cefac16549e51b22de2bd8a66345a8c1554a84f06af85e79996e044dadac09a039d130c1f56b6cef47492a9ee96b8a79f3bd7021982dd38bc8a831e32f5fa595b31757d709b9f41aec5a90a3bb3eb24cf7fa3f3bcdb03ec62ebb108607828c41ef49a68b39ff867b1db92c11031435c0abe00c457b9962e62f9b09711356760be25c8b29d52b33cffa26cef79d0491ff18798f9103a9b89ef7d96380224c18c1045b0b2fd7011125a929cc0772e69e07f27ff8a40c5ca062be8a8e07e6811c7f665649ca5cb46beef796995213de82f402474b428a005d2353762f4ab682168fa53afb5d5e1f6afaa3125dc127fbc52330e6a1e01d9a3c84275677ec62296980ede5189542295c60614322146f689f7436f080c5b6f807b0082344fb03f5fb6618e1581601810ae86e914213994c6178c91ff8244df6f4aba04d3a0b3ae78e0337d951e004aa82938110decbb778bfdcc26651c97922fc53ed3ec6d847b6484c5ef9538b10b83cbe0a5de246aafcf159d61e122607725a0d789cadb2718eea2f1342af21caf0ffc948a4458b651738bc5b12b318672319e8af3cffd4d090452f510e6d53d0f9b286874b42bc584590e46c92c6a011ba080bf81edf38b442768cecbbce897650e1bb813e79fc032e270a1a97f6dc30548aa64caa3c4ae2033928e7567868499782f6177faa5000a26b1fb0dba6b844474c20c9e3dd80e10d1f1366091889e51e5d0e336cee12de77c557db19dbbcc84754dcb59d76f1c7a6eede7223260d440a1b39f190e3d320fd68a31e08140a1778e05ba77f4034f3e7fe45e1ec18da378265a90625372bdcc08eb9146b28e169d6095c3fde97c37f487dec839dce53773a3e015e4d0b40d3d83e260d18c0c1434022e50fc963b0699c09599b3a12768f79ede4d745bff18fb095c7ca25ecc8d4c30f6b9960df7883a708b5751f264e1287f5db4b215454fc5bd4d5fc26a9fd69ea951d74866798ed2e91b2daf654803e8f959155a3cb5828dcdbfe446ecc8244471420a5cd70bcaa057e1fe0fa725453f4b5302035681f4d829defc4bbb1bd47eeff8ac218778c25221c3ad76f73397d01c0dc52b67c63bfc63e12c4b7b472cef3bcf1aef18eb78169f229666145bde761589d1cd803475a9a0f65db74bfe4b5cc9519b10dd06ffef35483bf03018da80f341bcaffa63966e633363fbfed979882699ea1a42a0d1ae62fc71cafbad5a0407f28fccf8e03e9834b2ea73209cf1545934f174fc9fb0002d58e01bf9b1c371d6f19db2b6f71c59ddd955bcf8a07e664af06c833c098ff004d5393aa16951d0b4e275acd9b8887031fa405c37e7ed9c6d647bb8cd8603f04ee134a18532bb9fc7b5a1ac2db055e41a6078498ac8fca3a25e81c7ef51cd4fdcf6e9e2867bfd9ae47707b3a7649d6560bb488805958bedf91fa82f1d1657f9116865dcf56cad2ef7b1df3eee8b7d00c8fa55f7057c1f3a9f0a58cfb90ee4678b9271343c16ad99fee703a5720daaba124aac6046707320075d5c23f487f89ad9864af8eba679542b687ac0afc2226529a8f67e06c265b45f10ce411bae136e3c982815df74077157f839a4d6286e3dfc8052542bd878c0abc26c1896b5da583248c7511674432190dc7957cc2c42f3a81b7a9bc4e36ebaeae523da4671c50af75f5008d4c5590fd746e650021aee723f675000945ebfaeb55f0f65268fba436efd3aa03b53eecc5a4301978c7ae0d520a8556e82a2acc16fd8fd4275e3ceb008ecbf7ee7a7cf043147e86a9264c040f39fae06ebe9782b3709c32ad9d1e7ee15caadb612d572892e69f4e0716dec764e1ad2ca74ec9ca10993d2401c060b5cda2e712d51359161345b415aa8485b2fdd549cd0c6b30ea55225c5e7a6e48766b567cc1af1f14396ef6b7e76361cdd8e6a00252bb262bfa5a708d8591acaa5fb63e1756e4cc6c4aafeadc2d82d5a3844b140c7da63224c54d357d6216cfdfeb5ef5c5e883058ea3e8201d2c80a941e43cc1ccaf22b8e11354de252ee7c34656d697fa686501da7b65a05e683f4587c37ec53f300dd897f2bc32ebfeccd59e8042613ebd0da105b92ccf0f66f98afab79bd3f00a9f4a5e183eafdd4409190d065bd7277b139ca0494de61ca8e4477fb993052e30e6f42649a8d0a0f692409ef25e256de72d4180c345ebcc438d2904f4f4499261958ee0228533ad7609a3ebc148269617c1a742588af13ae4f1b2b7a812a4217c305fe211320e0583bb3b00d5380b3c20d1cfdb93798bb869d55b5fd01a36fd4ae2f88cc827368f5a57bd9d1959fbf43cd7959c07d962c29781033eaf141b76e07341f202f32c389107e25ad4403aa44f79132f4ac24c2458133597b541698cc809f4459360a92a53890c660731c98ed17ff3f7af20aa90365bf3479c57a6e458f3c54d667ca1ad46a1ca1d28fa5aad4c33b56a9e633ea54cd2af652697b136c35cd467932bf8537a0439af279b56849b290d7e8b69da68f29bb24a76f01916f821480b5ba5e64baa238dc4709f37169561fc8132d04e9ba13774d71b83e9f28463fa5b00a4fd30453c12d0ec9c898480b54a6d6855e47165f112c0a8a64652bae151f02dbd5822df43d55e0ddae6e7596dd43eb50a7da0271e2a858e7d85a8fdaa3f3f35b0a476ca340ecadeeccb2f18b64ec6e4a2e1fe0cf689cedbafc2c65afa778bee266a29094894678fcb6cf2957040ec58ffbd07ff31c2591d12758cfc8c00bfa1346ef103308de09cf3c52dcca6f76e0debbef1cbac031d03a25e2993fce5c53a7eb8b93d72bf2040171e577e9539ab3ae33907f84777ce96ba461b5485b189ebe2978152fcd31b8bee0b91054c610d11c355196042deb5017c7f5d2e94bbad1abae3de3dc7a851a4d5eb22488fa78ef155a1b0cd1457d9b3e725010d407bbc12d383b0696e782a71b60844530af1fa91f48483ca9465cc4da6d419b5db675206c68cb97140afbe9e96369a1471e23273282f44a2d389a3a26677c70536da2d2555b23b2c99bf6ff7acf8bf29f79c06059519618906831b032fd8da0d1044758f994357d969877bdffebc8fe140ed64c20ec9688f7bf4062f90c564b5713b10cad419f96fe5edc123c9485104fce1f0d69f35bcd738f154affbcc8b89bc8ecaa0235659f55425cacd30e986bb0f5601b889a32a22e8092686744d7513eb1396b2181d172b8a2b6427232fde1d120012a2d3adbb9be3966429417717374a7cc4965aae58143e379fc76cb6ce09265c2ca4de59bc2a83b31b9e4fb4ec35b56f47b626f373caf9f21a63d85c9e10b02f313134c28814924e2b5c804af2dcd8b2ba8bfd05f865c870f0f2022e546378203e02e96e19d823cb5a039663f297552fa8b499b88da9ea054c1dc260102a48b00a9c2f129faddb8f5d3db62deab1eced214e5dc70b9e5105bfc144aa0eacc6057a86a570c51c1bcb8669ea1ca77605a40c180e146435876545c697751468e120b90f6793024f76898d8f137c67c46b21ef5ceada0ee3015065379f4dc913ae66ad99ad8ce46e9f0a3790b47c3848f1d63a588c10a3af6e4a8185dc51f5065fc1c2f6a43907249f1cb9bf684fd84df36fc860c12457c687589f58e0acf8c73533bbb0b93d6e2f04787b27f6a0347017d489d7c341f4922213c979d429ff739779bafd13bbaf60e20b5cc5d4188972babe404848b349e7de4992ae2810fb3b4195c95fb1fe5d2e74d1730cb3d211953bde6e047dd19b41b1f7980e3c5974e3056919ddbb1d7ed326cdcbbba34d27aaa5e37035fc8469cd39a48c792a8d5bef851efe4814a2d547fbf34184467d7a4cedeeb13c40d4593cabd20ac0c16bc2c5b47ec7afafcfdc7b2996ffef9c5180a0fc4ab94a45b2255fe340c8345eac07b1413ac6bf6410014199226810f1203a7c824900ae4c11846965e307f1c1760642df6a3b7d75d23015905cceb8f794670fb68084c6e3116b30bfe06658eb49fcf9d297581858d3db049752c68c3089f4b95b231e17a5a9dea8377102e42eb2fa042be570ea72221e1da58b12f3e2bfe24a85cb9ca3242be6031d166cc412348f6f837722e53c65a5a6df3287ad999e70c837e7a944c9be1f62db62624e743034dff6ecdb28da938cf43697f1d1bf6a06699b1db4d1ebc3d0cec163296e51db08b38ddce5c0bbaa9cfbb46738a694b923045a2080aba6123b37f2da1d3e962152bb4323798b0418e8641518e0d41c806f3284242380f15e00a53f32ff15d697090372166685588f47c11494cf53087f29dd6978c5fcb76475ef047525f0a7e161293fb6945edd054bf923913ce97bac0a9fd2ba9a9df5cad4d553e827021e7ca633a2f15575561f1b82384fc60c3738166e89048c28ca2bfe57856e1ba690dc1aacd9c0a709cab5be56b6ed00282499da824c0ebab67b2110b5614b4a135c9558b7582baabfa43f6c237adc8e23fa6e0b23ba4d2ceff8d4434fba7c2ba90a2eef0145b3270e7732794c94eff3ac9d23ad42d053153efbbb5605e9dcd5d47ac8cbf3168f48833c7652068191054174552f44e7b823c012ee626bf6850ebf78ef50feec9b6e9af64622e4bf2fd2050487c5877c8852528a5a0c96f7e006053a065fdcb9cd9e7c402a7dec0d6f1465736153648e86f636d7636e6cb1ca3359a5de3c01e0bb3d374521fd50683002c7a36d0118119ff888402e6fa7abd80b251e7b333ce8e4bb9406da97ddec6a867465eff1ffadc83d5428362820a2dbabd41e287dac4c74badbc4f249950a6eed94f9f173bd133a340ac4512af46aeba64766ecd405d83fefa8c12041042519bf4de218189e7b3f1c8e2c4ce47a0f41d4ba4c04e34ec5518b8292c3e8d0249e68c6e7b45a316ee621780686825f13338846956afa80bce6e3500a6241e4fddcfe6e36704991631a24900f574437b60a591b5c3881171d1aba4322a4488f08e04ef2f172d6629a84c7cd0c12041fa1c30c003c5a383633a2d049529a2750c5d8f9ae6367c20d5a9fd684e9b8da8516a9af166e386998b527eeb47dfca3f3c0bc1de62c5b67c4fc163d19db46cedeab42c0aab5de3e9ad91680e7a889ab65f36594723ba462eb2700f906f9206dd03ffc8c269c5858124653f5e4d96fbe89f5192c8561e3864c3604e98f0d2f7966f7ee3693c4b0ca06f47e07b1358f73a766762485408485d067c2d8b2e668e804977fe762d38dfac7dec8e96330f0e017f6139b248172636c0d6cf1569e6bd46b4f991e5192a5c0fb34036b07cf5f3b0604d04b5b02d8d09aed77e1920415d741b7abd011df7fb0a2328e5247238aa0f4c54720bc143dc8680fc966dad3dfdefd9e654106075a2365b592fee42559f472d4a6d5266cf61a1078b1d3c9ebd5970dda13d20b4b8dae5a94fba88584616e305fc4deb15b12681947112b34e1f48aed00c7aa709f91b30a6193b67056d8f822dee4d92c353370e4e487635bb36433762717ce20e635a5482fac12b53484b90938235f09e6b2ce7d27ebcc8e75e5039e5e3343837ac6ebb23528cd7b5ca351c02478373570f9f37fa13d40eb82aeacef75718edb1a0a50f42ba9f9a1772a2df11bd2b21d84eb342bb68f8504d47dc79cc44998241b8c5f4d3bcc8aacd79a3fd52c12ed0e0e50c838298210619159bc85cf7ce70f5bfbf0123cf055f7da60c2e40c8f54c050945c51cf1886a88be6e22a1ee25bc3c1a99b6b0dda53c509f98a221df183c2bd9ac041052a7c9b3b64e88ac6c99b07e2b69286776192329a7564b450e2fc8aa5fdf86d30db4376bf3c7e2d50824cb03bdca59001a04ab4b8f3163e48033ed24e83ab4eb9999ceb7c2554fc45396ba2ca00022af996377bf7bd8e9309c3e018e9a17e9a45f0453a3e18dc7d83a42080d1ac4e485241dea3f84e6e093b5bcb3997147e27c51c28b5416da2688464e0977bad6d42204465d7db4182c231d242cbaed7d5984b06d5b111acbf24dde17e7c3f349071b04672714bd4319049ccd7cff2908a5dbb9dcf92ac591a49eb8745fe2c58aded7a0e5afec10484db4b91fd6804552efa1feb0138e1e858d0ec2d7d8f2a286ba09e7ee51cdb6f5d51f3b828acbef44c8c41e27c664dda7276004b437c109ce64faad801848736f26a263dd140f397622730a966bb57164fdefcccf467cfc6e3c98c683b0a16e42cbf46344d0986c5e4cd79dc5d2f68773472fde9170946b318cd50c0c3e7c1ac5879184576682f2443968e7f2798f04fc02a49e926b9024a1f04f943038204e5ed4eb6a13fc6166ca53f1a8cc8af830c124e9ee0122497701411495569eed89280eac6c465688e14510430bc416c63ac9cfbb26dd6d09826d8063deea9a08437310c49ee10c5aec58863002f322b46dd14a7dfdf0973e897c62be20051dd673eab5b9a92e04693af41567b83d8a8f3abd2264292bfb75175c5f14cbd211909220f4628d928ccdb5615032bc44fd5748864ff477d88756884a9bfe5454b27ce60a810148672ba015a090e433acd68aafa92e15bcdaeb8b647a43642fe2bb9621b0228d32b07a52f3bdc8e82c5495fdaf01ec1c3d88800128d00b8e06822570f998e5963425aa43970eebbfe90cd6d85b39962ad12812d48e023a936280c3c77169670fb4d7bf15e04fee65c4e6641257e32b560993251bb644da5e7b6c0d708f96869664494fa9c541cb84dff0cde911fbbaee28a801ab2ed008b55f5da9aa004e55bfd5420134437b859c5e3a1d74e838d88a1663410ce121a3dfd108a8a91d9d995293c21901e9bb10531632092c34fb5f5c035f46937a60050dae2e8c04b74e91aaf4743017300c3b3c5c6c93d7329b9a17492263d0d68f607d6ea5f8e3b7cab4c4806a0f6ac7f21771d36cf2ed6ebc09b2d8591be5834a1b9b5f69355475057eb95f3136e2102c0b926b5ccd93d8aa8a73b5e37ee5bf982e09c562a5facb4b494106360be19f4052769b28f3e0778ee4ae75580fc4d99e2a5ff3388def9ccefd5ce6a4eaf6d581dea480e8f625b0a66d9c5a7abc8fa1c1c0363e21d65f65795654d74d465dd56351aa6a9716e5219ecd4715738b4f183af92873db7f85ef0c7415697ca9d588bc1815f92e69111f248e4d6f6db65f5b0499e7c5f9212665bd5b5f8c5d9119f6491f75428a09d795743c5eca0abe603c4d99cf6a84b19d07d9c3adc1f4c95d9c531cfd02c23deb78a6ac1e6091077ca879e26a5b3032d0f2ed5375d08456903bbb43aa603fbd366204591ffa90aebc019466ddd91a676f5c36541a336746c532919c6b9142e6f68aca3e720eb5110ac4f671b0b260878d0ae708dff05767a3e2d852334e5fc47c578aa5a584d2df17dd96665bafcf5a2f53ecf9af431ec8e0a105d5eef888954c117193130661515ba798014809bbe9259c8c717e3892bc5b4b6f5644f66da0197e67a6bd01ff37d84bc375d7120a501c47cd34e02ed4f9d4c892db43a469c8e52c8fde10d34b130d24196d67c11546f81e7b15823c9df17eecad9cfca138f7294e16ada8f5a641b40d5761124846a4bcd3a4fcf9262bdb3014ae900ec2c2dd91946908aacd8cb86027bc17a139fc1d572664779b49a172cb937dc8c32f03dda358576b33085503a629109101f073315502672a94ffa0b6be7443c23be6509a0ddeb5029eb8f03668c23976394b53d5927c42a095fd662a12ad88d349f67a5380f2f595b9b845905069615e6bfb34fb22e9f4442faa6e63db9694c0269958594f1a9ae1efd2ba1ebe44f42854ee791d8573efcc75572e3e5ded12a4bbc0e1dda0edc195151d1483650f0f3e67678ec3797d12251b7662c3a2307596cc93f3bd4d225345a4419b1d5cd79d5245bbc358b2978bc5824fbb55440ef25a76f6ffa4c2ae384c0cf64520b26c4e265b540c26bfc451fa8ac522929ea5b2b6476648b8ede906a94e247e3fd1076184953bbaa740c7ce48a985735e8f68873c67ce36f25e2098d148ea863599b11362da6805f6957b498d1c1d41ec98d4832da69c4e74916775785f31e0b598c0163eb784aa2d4129073bed6633c7c3672e16e60e7bf6ffb953950b9fc7a9c978e139fa6eed728679c2ed3c8f8661e6d89b74a71dfffb201f07b0a03a95a2d77bd84f03b97ae9b1e56dfe6b134a12b9f5e1607df86d90f528c01142c30b96504250130a10129b29fadf949402ad7bb1602ef429afdda8075e4b6a5e99d3b38a348f74f69cb4f7d9a2077cc902fbfb52099353e1015ab01a0b8a0425047b66045b4c34aa626aee8b1f5a971756595b204c770e7713bf145dcadf1a56b106e33943da100856a786b359b0a117885c761309fdda4a791f8e4152001e1d99cbc72a068a30e68f185c035b080a744569177e9974b8928f9826656253e768de2e93d9370e3ab0d8b7b2e031a8c9f4ab2c0b9d64f318c68e7c4753483e55b87e2aa9913b4419625388a48cd8c1315a10d5588894615032051a792baf39072d62721c5b35c373ef780e2a90b6d0ab2901ce9d51d565e60a0083f0896f6e438962f79639aa75160acbbb412c854d72a24e956c8950e38dfd53d9643e50862132bc1228d63a03152b0b71a54ed54c6f2aa867ceb1841854939f3b743b55fbed1f66ffe1773a28f0ff95552de25487098d6bab8eb08ca58981b855f1bec1b57940ad423e7d00c779f998fe76a0d2ef8934e305252a6c07052016640c5e26a8d5a1cab456ce34e612c3a352ec94dd5787d1d7301ae6cf36228c304edbc21c33f28f6787509f859f7f388f01b77f256092ec750c68be1561989f04ef3167e715903fb9cb24f8673fb80db0d91f361c2d7dd55e5f850efece97353e400e09f17d5d47c4cf77d82b5957ec44eb130013549716567bd8bb04e5f88b71e9b75fa0a780f9e35f5028f96bf3d720af826c3d5b9602c8026610534ac001843612735184e9aebde1fab3ef537bb677db0a0d2fc65896ec16b186cfb808115235eeeeb53d1c22ba2786baf6f47937b033e2a908525670e05f3ab903b1733fd475176fea3e6018fddde083d252845a0149de48fca3ff4e059342f9fd9da6d30961b488f585c1b6b84a1d3e14c75c0d8e218367b56322cd3b1e2ab241ef2018ea4a18b4f7a502e267268d2c46cdc7a5b30d439f0eb61d83a5d4358d9700bd68f875704122dd7f084cbbf96b01f7cd547ccee408650df19f908e3ccdc232cc00fed5ce49e7c48e68ac61ee61ed9672e9b1824f631142e453ab70889ef40580a567402c801160dc8a9025d8b3fb8b74052c1ce5c686604b3e99b34eaee4a08d3f0f2839c42dca956824cf4a61a40cab0b1044f23db5e8945802e15468a73ac9a477b0b45d8705be450adfa057d4c915396786bd16411f3aaa8d79d0c577f24bcb101f0d15c79cba226b055bf6c0bb5f7326cfdc6d00379ed5bc8da90e9ebc598bb4edeffb5afe339852ced7b502912d51698368edad99d9663c4b81ce2b362b454668706af8ed6c0352e8b3d3d4bcf42c98a224c0ba6b37b4e1b1146c0dafc40b8496895d7442d804a426e36b541be7b89fc217da78d739eeaafc7964e9b03184dd961e406f435e47fd2a964edafda413d7ea3307e1e5b041f31f63a7afcae1d746225b72a1c5ea16a39898fe5f0120bac910d4d5f6feb340e01aa31f7f0b220d1bc6e81acfa8cee563d6fa9ce5b6a9b4d8e8a1622079bf879c61a61193bc756f0866b92638c576d638918bb96a50ee0ffdc319b0819204c989b35765a772f4f1bfd2096cd804daea1257827b47a9d5fe27131742f7a27e860e7d8a2511d8a75169f0f49f75b1024f0da4086113281354b23cd526c45b385d50fb4505390de7b9480f442f428f15ef31c50ede6570d278b4066573db15a4d1e93982901a5c13527ed4a8f30b0f188e9456ebd477280042a2e7d325a5ac634d4dc4010bb65a09c65cd66f8635db15b39171889b715cdc487502d2bddf25c10e84aee27258a42a2ea25b1ffad8f3c2613acb6aff30eeb15e4363442c60dd407af55b3a862d854cc72fdffb5be0e0e26225225e08ecb61caf2c7c11483b5daac6691ce837bc2d9aff6a8673794978a5e54fbdee6262767644de1dbbeefa8eeb46961bca29d802145e316e72d6f11847a28573f2a38141393b85080940c88c4208f8706c99399f174f9d00d2fe65b6c0e1bee99c38c462070368244ec0e6ce7a2a88eb8649861cd813214439b70f67cb76e291352b9d3de11871e7cf88d7baf84d2a909f57795f8246e8015aeb2c3de7de3904500d0ac8bba1251747d2340ee8aeed93fce752f036960ac823f040e94a86388d4e04380843cfc25e822ba1a10daed22ad21951cee160e48f03b15145775765f30ec9707d96cd5b58b22b315877dd019dec1bf84141d021838405bff27b3567be4389e591f44e08a2cf999b49456fab85f0bf223e1fb9613f0d9c8d29048f9ed0649937fe129e5d8a16a4d573c1354a81e3f98ffd343e54814604c82c74bbccaa2c86086069e0ee0ce1270e50d8d8f0f707bb117e19692f056c796686920015c662e974f72b1d1d9a2c4019764fbdf17a738e9513699573446feaa18efee9fd01f5a667d443992e3671c37d0a40b2c0921db61a1f522ba953b0f351d72343980405b4518442ab19da137abbd0f4c177f63f8178f6cb57320fb23f2cf50381d549f8f3b87b6901b6a859ec4ff94c9305385044f93b879cb53634e8095f1fe95ffc3690da3049228cea82a84e92aab303bf5b23489d0961b71782392fa148f9ee5a826277a9a78a818f2719dbf04337209d88dfb0252b0a0fb632e029f777fe2bfa250b191154882ac4adb10e55d8934864233b74101ba3a100c90ee45d0406bc1882ec1ddc52de8db0637543bde987a4dd5ccdc98c73e3dccbf2f7da5acc3aae697dce92b798617309d92d9f42363ca219cab1f4478b64c3cbc7e6edbd3ee0f1a1715c7c3f39c56e89070e5f8500a1d005ec5328d31974fb026eae27a38af15962c352f8e2219a68d98f3e0cb20379d4e82decfdc5166b9ef2c688ee17d120614483f3d1ca5aacf6d7640f3829da4842f94a50e2ae8678103f36cc8b43156c31863ef79cbcb8aa6b44cebe331593c6a5bc3d8861b327201b4f6bec330edf32fdc78ffeda7f724a45887362fbae273f159a5e973565a078bba1ea499b3859fb5d74cbcb57cf6f0e646244586c0ad4351a4eb8126ad308919716a3c85bb3efa1f5cb5f8fb7c2a32f783047c4fad35d3103ba2bc4bd825bd129c44e60aba8f68021c7058027ed4a21d59f65911d45b0e4292a396a34d664aaee2de6d4030ffadedb7a58831d7d4f6882c036cd9acb48a122c7ed8a62b8a450679093cd25f168260f7620cb1fec3df8123cae2515354367561c7fa249f297591bae1b524473ef4190284fe05fcb50c6829b25d7ec8cd4e12bddc925d8283fa61482f8fe1ae415f26def411902f46c39334f25aaba57f7012235bf2bdbde6307066ed46a8025292064fa339bb3922c9474bd22ec9b68c00cbf46eca4d1bf17f147a9e756b7bd67a75f73ba106c9915d51920c937323476db7b10b526a52ad5b87d076e7dbeb5355a3abc69eebdfc795ec62eb33a87d9c2e7234b1f91a70ec2518f9a90b8d6abc373722f77b504793227bbe96cd44158a96183b9f7669ecd8f9c843b8f2e8f526661a27b6f511f09089b015570efa968276dd83c511dbe9d3a6d8885011487fe9d20a2bd37eb2dde5ab4f75a8135302c1a8828e602434455e2495dbc9cb5268073bcc7de9dd80349a52506d7de13915ccf833b8e78c90ed813e7a8a43a83a880eb34a7607509031ec5e0567baffa0de63930e99052600303a532795aac7cb727ef69b049328f6487573b22470958aa1e90f5a69736b06fc98c36a78f9686b084631e6808ebfc6ecd2a46db4ce6331ebf3f67322bb17521fb43b8db37613db34fa06fd821c69547eb8af78630665a992b06d490fd735241dd67acac9bd21e2114d485274aa9f4741e4ea7a6da0b879dfba2d27a783756b368d128cfcea3ba8856382adf4b430b1e9450f073cec4c1e1378e2d2909c62e462a54eb67e2f990944725d6e0b42aea390702ba327967f7896befbe7314042861769a938670546e99cd4e97bb0998fe909266abb49dff41b599d8230e7a9020556b2e531a3d0964f5d565e8a2e9f54f821ba79bcc8fea782a6f514987ea3f046dee34061d88ac2b2d7957e0893a03ac69b53a9c32af5fc2f507498f87836a13134b96f71ac4496b4e9a15c3242d9de8e1d4a285d10dc6e5abc6ae78b356d3f70caa514d0bb94ccfd4eb3774835a1c4093767ba990f6c32fa807f86aac3449571eb04609e12def1e5bfe2b2783f1e8a3e53c64ea5e7e8494e023fd141b552332a859439d8c58035e1c6a9754d7a9fee142839fdee0618262f5a86ff0281d8fbeb971475a37fa582f97a77cf8b8a75f89feb7447f1e97b30b3523d5b4de1266881c083584b52e0e801042534f04caba41761bf0d8253640fff2260a86bb0e54f06105c35441185d176023340e0d3a96201efc8ab92472075975806a8563207fcc72eb48f6faf58a332c41f5a5ee3efcd26e09981f9a163fc6e1276467301fd7fcdf0f1c327d5bea4e575c0ca7cddeeaa10513288348d3c306f060f6385d357c9707b5fa28615953b52908c2e3c801e6117654eb25d52683a45d416f441cb4dadf4551f0040c6ac6be1322f6df4880b4b63649c48d67009be8f49a4ca72f3371b088e90de3e04e324729b8f3d39b7eb6e0f3c5da94c2e298dd748cb6b5d905bd127e5bc426ef48759f469976521dcb583a3cf66d5c83352dd432e1a7809663e0385a54d38a2509690ff2781d9ec4b050095419e1364255650dc011064ae9ce05fecafcd0c62cbb2dc20a95bfa244de16d25d63e2eb42d08c642c152ef23b991a4e9ff50370770091bbdd43281a233b4da1ae6a9b30c48d44fda2c8c8e77e7f9fcab2550b5a03f3c7c3ab8a81dc22919e8045e67cce9046b0b5ca2e410bfbdd3d6b7527e4bc2c64cb0fc8749dbb95b320b6153040110aab813501350a1a77ac824bc967dc0b5f38858e5fb41b868451b243d17762bb215111a919a1a2a0922c5adbef91ace12a9c8b1baf586b0a05479522df4927cd3555d4649300ab1fd4b1ae0548137a8cbc6407fc09f8d6b13266bb64ce735ecf171e24fcaaf96862766863541bd7ac898dc8929884cdb100da1daee5a98d6fdb1b3d3e24eaf48c5aa872ab80506384e9e2f4bb578bdc4c2bae56b6dc02a07402c83e0e46e8b70322f0ea466c5cf152cb0b13b7a1f4cc53bb40d67f62c9d00da840b1068cbe8ebad386b64e43ab4d74195ca1da80d818356ac6a52fa5838958a2b0170931ca68e0385cb44223cffb2d7d20f4d93cccc8a6472b1cbd451a103b0fabaa6110d1a1f1d75f704847e1c9c74e21e3f3120ee90259b2c193c1e6206905c3cf91cba1c9477bdaedfceecdb4fc8ca66eac824bbfaa1170e8c1f0675df623d17485a3bc843d10a9ab07e892c351ea15c3415b3b0cb15203fc9e13fe43fb3ecc9c478cf0db7ea9f31191e2adc2c75f1ad42c17ce41478203a5260ae0e10db6874322ea99d175e4d01d8173e682c6e85c05401a041bcfda8f16a42c16ad908861b274983beb5fc5fb47c577318817a05a946c332c2a554ccea9a80cd75d6933d7d68a0207a8306feef141cdf05963a98256dc778f56b4a31011e934364650023f658cda8d82b90ab3837d22b4b22cb06a21b1b913246fddaa63d9ff375374c5fcdb5340902c8f995c0cddb70a803500a6f432b511998ac089ad1b76b77ed0878e1746a7981649e91e8ff11b47f300c1f223505037c162d54e1b29dc2ac9fede693b39e1a341c5132b7c298a5e83c24e2bf82ef55ccf9f82d36a9891b561fd0727a46d79a0cb626eca76944c762a6e9abf846bef3862644c33967e7673bf6522b415fc0519f3b3e2feb31a130718020ed0056df9be6827161a9c4babdba49db661d5b345c38f26cc5d19a1a597851c03200a70188bdc3e52693f55c224fb853f6033afcee2a48575b6003f7f06710c98a22899c30b8fc803fcfc559833f857ee8acdcfaccb3bfbabab2930971dbbceb511d4cc97a2fdcd1aa47af4b3c83623d62c2346f0af6bafcd907a6036973922484829e5a93ef30c3996c17de88a29dcce141470fbbbd7baf898e2c3df74a2c75ad763dba68683fe12cff74d6b04305cc2a1e17348ffad1900ff304f80b4230164129ab43435b836b3e3ef5cd886b346ac8c13edbdd7573e3a5ac75cdeb1baf6af1a660b88da98e9d764b5bc449285845e7faf4b3b4b25b7bca3601c7f8e7ec503524cf72bd683854d73111851665b003fb8d4063d2f9c084fa00c6538eb6d3cc03abcc2d452099d4a71e12f3169aa2a0c5e2441669719045fb0a3240ae46201053dd5ce4e99d3de448c44eb340e2b2954d9fce5fe03d9835f17a78c925ef6f512af4b0cdaa6d151dd2ba96a85e9c42c41a23a0952106f9c62b245a6e607688130456b31ab0ae4791982132f4e4cbc1af87a7a2420e70aa35817a2ba6e548c229001be9325bef037e981dc099f5c158bdf71e8b005de48ff9063c0bff6762a1836055a8bf45bcfcb3ad2d6ecbdd9cd34fd842ca501c83e3c951d867b2d0586aac152ef5ffd681effa4dbc1bf0fa5c103cc40d49eb62a31aab1fa6050252964ad0f590b5b5346c96e097a624d81c8d49b777291f15d83007a60341e56e94d589fbe350f0fb510f28766a5e08aed73ef21e95a0a1bd789e726d28bb17d36f1d88a14f05eccbdc6bf59150997d8182a0f7fd417e1f332ce506a5eafffefa17c737ccb4899b090e95082460da5c27ff51ce377816f5adbe99978e25e78375cf9a9f0c63577b0ad2379ee540c45142f759596ca70a29eaa502eb2e43fee3850c16e6dbc6c8e88a4cf9f25fedbd1bb1f8feb9027f09187a5e4e18570bec06aa9e6ecb764c0e59801782aa013b493c971ef9b367323838bd89da05363d4e88b74b43ffe86ed4481aa69d0f6251d80e72a87b72b6c24cc935c95b1f7c656dfcefd04f9a0565586f2ed120e054a6d292515415b598c0ec3597aad3d2acf25fa87de4fea26e766098079bf855c71e700f046e08052d76d49cc066fc159402a7cf1da8adc92d66c1f5bd9ecb10baa5f61288f03bfc616c8bf2c5b931c5b55e01b6d88c1e0b43f432c8548194f7c6d85c6d21f454867e08e0cc1245d525aff4a1e54b52238c6c18321bae4ae044102fe606d442ba9b4f6a0b0e08e8682faf0a0b9b046cda5afc6c23068925f6b8da388bab32833a44a3a6bbc8f5e0e089ed646af11e5e26fde2b18f0f11b6ab9a71a9eab4f8a711900e65f4d0715891eb2b4aad87e67a937107e49d2311da90828916da551ee7b75a794b22395c7bfd6ad0a7bceb96148ad60fecbb66d317955fc29fa667b26cde8c24a873ab7d6e4dd3dce52410be8d77e90af0dd60b6ef97b8b00120fe02e951e3a466660b8026cb01915ca52f642201a82b824a46692945aa76e565a3347eac1369b7b22045fb703b933fe674adc52f081a1387cbad348b476deb8f5f1659fc441e80cfe1a51f82e6ae075d4fe2a648f6cf1e8c5782aca19252d1bce91cc132eba297c57dba6085cde91baaaa274e7e7f424a28851ace47fc4280c9798a1450e5bcb8b4719db3bfb085d2cbb4740b24f41e1f013353f938ed5a1929fa9a78c880b7c631702237e1315b7569b97d7c8e4bfe01f31056f5ea58c2e8f046e43f4d7fdcdd373ec6440da264bd2e6e4c0b6861c987a655688743012953108e2df20ba266504f704fea3a397ccd0ad1975f69d682427383f64b20ea3df5cf20f30d50efbd30b255321ddb61486f3c4752849352f5ea94eb8d39c640ac2a0f0c8bb23e14aaa71cd03823d9b0490fe2ac4f7239de2c67b3fd772886b5cec584f73b61d7e4df1e1d4edb0dc0c71c2a227feb8fe1d6505d6042ac0dbeb37e86a031e02e362996634aaf16fcf1257a9c990a10b33f9af1e9d3c5dc1b4698b9a77cf0238a37129379e19e0dd5f707c5acbfd7bfc5b22e6fee6855f4cd11220e6f44074f0297c2ab53f423db3fb2b20f1a14d8a88864c67dc4f7a9c1c5b67dde6ae0c77cb8f5ab1b0d975aded94f8499ba97fd6ae7810d41be9a0509b3fba5fa8c9162a3cf0bb02ffc900868e19906b7a4387b80faa8b755beff8bbdd59ccfdc0c586e006eb5130075b0c9f02df4c9b9fafb6e0ed6ac8d896311183c2e626ed5c7a4c3970382011cc6e025e406cab13980f2ba86956019c81a85f028f2b46d2af6b6faa7c89ed4de83e1c9a13420a72cdc870c42f08cf9b86a79c340fda2571ef776476cda4fe9780b3d4833df0161ac426424591b32241abc55389bf4faa22199e56b74193e07ce29ecd32d699f9338404d984fa0f33c56562576c748efdb08c562ba582f62d6861d556f70bfd28b5df3d6d6b931f52d05a0ac747620cb33f8879c2d1a3b5beca363523ef74382bc185ca17c8ea26ebeb9b7a929ef78373b7269b029ea7792d6a542953f4a517e024d3f2fb29c47fca3b881f254e2bf7afd117e6e5e71cfbe1097e6cee979cca5f25ae5d91b5535424946f9077216d1b97c30b7f1751f25338bebc339eaf4f4e3fd187198c49d0f52a4ba4677776a99cde9812825d72c54e4ad3815f8514bb3a5d68714f7e329f69c601c8dbbf16a642c503c4a652a54900fab3761a952b47b91afa7980ea1699944844aeac210ec73bba7a6998fecdf9d9c8ee45e798ba39968d1181fc229a4dbfacdd47e46314b635fb4e4772e0e5d8c1c8c9aeb79f235490ca0f6e8441132aeae5598950fabab8a7b3f126013c1e9319d68de70b1880e49d0f5f842f13d0b7e6b41880d0f0025af664cb205192659ab25659658cdadab911795c1fba95149000284d48fde6c975731378095b9f81ccd5903862cd2dc65d8a5f826deee5b3a3205ae45c34cb7dbdc570cc1612a58be3c2fb90e2be1ae4108508a21ad79b3b2b3481bbf0a9fdcf96f6cfdf74757c61c6aa13365d935f268301f4c6a0500464c80885ec1e50bcc58d0b43a4e68622c77c1fec260cd9e5f4249bf1c4f164c8a7aabaad157c84ce3c09d902c82db3921e3cb5ad33a4c38aed05e6215981c7f7cf187f1145b4dbca5aa1d06c0f6d6d81521fa01a8283cd83f11b2294c2c46b01d88b2e3c93066f0f38957d50963fdd190846841a6708510f2c6700a057078f443de6c79cc4517405cb5f0e92fe1d049eb2b3c277301edbaf934bbb36a6237a03f6f54fd38deb14319ff7727dbefbd8636d746fd860d023997f9d0966c093b5ca09ad467e30fdf16ab65a716bbd2e5c9c04f0873ee5b14618f37586664e4ae5531e13d4fdfc6f923d12547fcfd4b5ca1fc6a80efe555955befc9b829b0f099b19d2761145118d581bf8a36311fb4325088c69a52b0407cdec6ca3a8c69fbd1995ca925b5c55ee65a6258a2beb2e245aeb8fa78ae146df2b9aef31b787d95f31054d59942714905c03d91158ef742a65890d8513397c02cdb94a150fe8dc227dd98a56903225f0ca650d14bfdd8a791bf3eeca3d6ffbbf7f41ff954e192cc0c9d46ff74a389dd9f64b1f412b0aa238461bb2062525f1af4cfdd499417875a474a6086f1bd4c59304d2bfecf660e45f3f70a330b12ea1e982ac5eee914ae11314e2f9177829d9a0ec70c4e9f19fa2276fabca5c6b53a9cfd0370161dd4a669711903ad6cad66c00489e29eecd9a0a0db085121a2f49f7ac58dda65a4eff7ba35cd63250ea39aa1671ab22e689aeaeb51753768e0065765bb44e68bfd27f4bd49d8a6f890c2ffe1e5ab540f44afe2272a5074b0606753489c31bd61d804fc9a548005acf08d504e7f5769e4845f1a076b19c27c69741ba00ba3a2389c4d0b890586db2fff4a3d8153196a00aa7b18b966887732a40c2875d968ab1c44da5f843179c6dd3aae84c15f703289bb09fc3422e0edd6b717a98191a92c9f8c4af01bb768a696a4a292d32a9b069303f3fddfd8613758a887417fde45ad30dc702bf426727aee81e5e7bb26c342a53c2c2e82fc5bc1e7df49746a00eb4b85a09ec4d0e0f65c185482e1920c8dfbce80614388e3bb85648870586c3214fc558cc5e92b32709b17955bc7a4c2f52947d521f08ee1cfe2e5d472cec807a241af2fcdd782c071f091cf3b614f74939d917c05c1cbd29a2dd55742bf02022531cd034cf8ad6bbaeaaefdea6eaccc90ae1d80d5fffabda039c94b24d29d84db99bd8a2ebd4f980d0fe340784b650f980d0aa68c16646f95e0bd00306ce37ed0de122139c2da58902a210da0057197f64d3410021cdf3b8ce15ddd6cabe7e2570210975a95702eae9dc67203f066ad18f5fdfaf90fcd59667d48eb966faeea0323c30b2edfb35c6dd041332c984263834335129e6f7a2215945a3cda50a9ba44a247a02d0019f351a5709a1e1fd6d80a0a5727947f4a19ddae786b8a560304f67efd49f66c28b21bf1cb86ebc8d67bff671bc4f1f53766772f70c564425d43794445a24983b1f4acbd2dde616ae70529b4d51821c681540f90e307b07a2bf8cc951c4b2cc24b0445e4622075f9b2aec58faa486b479345dca099b3958221dc5557013ff1dce809d2aba8dc1633329c336a204134c68e2269814aedb33cd14a4827be4e479114751f6259ca66e6d75ae1e32f3b0af81424d36020e812b4d5d861a7f48b3408b3929f11a70518764868af03092cce5464c8c5cdcb75047e7624a284c2d453d4af524c758f49c3ff4534ba4a1d9b37c944d56cc5495dfa9969f8397f045e02870133c0d2d6bc57a38021e0aff68102a062679d6924ac40ca42170a00ea7df71ddbe3342c668d7cc4a4e29550e56fed0ee6b29169dec8b2158fbf28da5a995c1bbe0927370d969486e5688c66fb99ea1c4dfdf1aa4fa253c9aaefe5a5ed8a347a49598bec28f340c64b6f6d0f48d54c2160f153d5be0f3aa3d1626656a8460cef952c0fff05213a2f65b52f65df827184143cda51e5e166b4455576ed28621d7f48d2dffaa0859f36dea1c075392c55f1d512ab86679ee4e8738aaf0512c9d7b338049fb3df8981e7725bb36e9027e0450421c67ece6ca42fb67618af6cab1f4b1b8e6ce43764b533033a1d41157ddf201befaa0aa9582bb0a0a655c73c9fa8994070bd81de90f42fc49b230102a4d41127ac959854ab96eedb4c1f9f5bd14aeba0cc2866da8acb571b8cb8947b32afd2208d8a392542541cf830e6944cd5ffc13d1eb3dee447674376f911cf649f4e700930410b6a8f558948cce341f0f46e6d944e6de310ef9fe70e5933b2bcaa6eacb763d39ce6c762de7e304e20474ac4f259a72f60642814b494069eaa0815812a944e061dd4b97d62d8789347d76fa92a11f09e75023a86a37e434a5a2999b1d23ae3950c6b9fb0af1ccefca87a67022da9b3f3934297109fbd8a562924798d98ea481af1abb566a1e30ac9cb35be5d506ec4fc7e35f1d54e978c96bc050557305f8da5c9d623253f87ad9b6db8f8ea8f8f1eea36ec9802ede09695f2be237a4b92ec3a15997a13db35dc8b2b45c472d2cea7349e85a75195e27c11cd5cf7e48cf1cfa6556372bd52725a4f7bf1caee880850e98f5d50d010be1efde3e044e5c81ded68d4d10e08a097fb02a98287e767a2b1664bfb4e41ca17e306a8865a1402029654052c21e60477a89a1591bf515e333f57c329414bcdd957e632273ff038ad7710accfca934a8d1fac6494f4e956b0fc0c8cd2f1bae8d2344e93da5c49fddf450b608bf815ee0b1fcfedbbbb2160a27e4bea5d0de3bd8c89277b4fadc55398cba0cae1873f7833b7404fce1b6697d5e5673b7a2cb65a82d08f3abf0a43c6de1d966491d7095a4d2a973b7524d8859010a569872aaa69329b6636d3e74aaa59e8ce6c80f7217e2a8cc5c8e4880b8170e0bbda0ebc49e5cc5486192091cb5eb3060edb692a36ab40e7a893abe7b1e7298610e148aebbb11039626ba70b8fc07d0945e7ed539e070570c94e0324afea86961afc2e88b2cfbdcd9c052dd1b4b4e22c7031c7779b2487dd42a5d706501bae549a73be071e74049565dbe675070138ca3aebce1d2c642e07f09f90a11f595a201ad616ecc068e325b781640786f65001fda9a7e86d5c4377d77fa5e8600d82971af6a6c77697d78895bf63d3b816736cc857155200ff4ca5a7db7ef3130fcfda721a3cf1a027f1dc86786978c742f0c11586440b74bef8d6bf55b67038bcf64a5f1680242a37910202611080714d8d6bede9952d8269275df0e1601fbb6f308df70df5d68aed622ff8b7013dd9f73623ab39cf308f54eddc4bad899ced6c6295d7959135355e71de8e297e85c1a88514cdf86217142f901e42820efdb6f0ae91d76c55dfe40b30e55c7649044130a851de34d49639d7a746ee8fd1e951d3e1e4d0d25ad4b21636498856c8b570c824358d369e8705aa4f552339c76ef2f71f6557a2f20beca7d3cc79436648496b86b7153cb907bf52c1cd61380f384855e2d8543bdd19194dea4747b33869d53ede4dd634b8c07232d71096980dde51c636b02425db104efff02408109167fd130ea47b50e2f569f842e02fd6bccbdb9fd700677d6c698e61f515c73ef8316b430394bd121a582892ed62eb6fab7df88c40bd323e7383298899011f9875a52fd631f4993488f6bf000eec1bd085ede133f6c362fb2bbd3a8cba0d57bc8a4d53b828dbc25a95e4b644f50964e192382c9f4f4c9720df5175fcacb7045ceecc7c1464c00fe4df84da0a570203f06a37f13823ba0368ce1e78109c029f4ef32129840553c722da4d81cba8aa3188301611a06db42cb476772313ee75ba30f21e543632b8b3d0d60b68ba197fc1b0509d013187614eb875197ff1aeb877a662185b3ee72888e35559d60c3b509c806a1104ed9b5aff35d012faf41b3557e87879b5b07f1b22b14abcb2dd7c84cf71f82d115a203a6662ee43339e8f5bda26ea94dd88b1af76e01c3dbc661e2b75ce37237d8136e7c85354c13c8e67350b9f816e3db1bc5c1a25fe893022ab1a2eaac28530abb597a5a51044b0163c820498a34fc64057d79f26bcdecef04fd65923288ff680f86f9884c72851cdd58ef05c76a1f59d86d66b0ae9fbca985dba8db8c5dd50548aadb8ac01f69491096f082ec8027b46a9a34278bdc6dadbfb0fab66f1c8ada6ad42e07df5f65cb90eb9491c55699102ddb5e13a8067f12ed7f3038e3070e77bea5aedd67f3ae1a6d8b591a503a6cbf99f69c01b7efcc55ffc6b8035a30667aafeb5bc5374f4a1b84cfafd5486df7a32d949ed9c95b299df15ff781595e058c4a43b831aaf9e0f44d0fdbef68423380aaf4cae95a414325a4bfb8d5bb35e15bb0d9eb4c3d717407c0c6038a354f5beb1f223b3928e31c84fcba839af2073424270b4989710c704a39b0d976083ee98d4fc5547d980974f5ed8af655b25135c54d18487900ff00559e51c3d5191035a4c3adbc1cfb0c9c9d1157f5fb2ef59dd2f3ffe9aba55cce7e6a876a6db3ca436632dcb3fb5fdef2fc542a17d5142b4b59903d3a676881f2f5c671c3c4fb56e19ec75960a4312fb69cdb45aba20c4a012eb8dcd0dd8be4e6c80ce8deb89aea4d27cda6a42e09e2e963c247e8b49f09392a16fb8b1c1476f1312dcdc323289c513a9c0cc73c9fbd2c550111de1ba2ba4d283770c5ba51e3c9a5fed004d6858f75534b6677a728408e19aef2f89f30f87d5681c3c6908cfd7730ab4ae7473e420ff3de001751d80adc3845ab9e964f4db35aad494f69085e8adb90649660264bb9548b06ce77813333d97640d8a3192ac41ef5d74353b607a0811c61cd73df8d33c688b6c56165362d5f734cbbd3ffbc0203c982c82aa517fc39b12abe0b3042796bd4440fae3de1cc4bca04c96831de1796023f2a2db4e9eae14a5e748a4c98f14ef4ca3d9956cac06ceb6dd095e9645a05a52293ca80cf45f7e539d72262b311c55fa9ca0b351c4486730453459ff31c9136fdd5c024d7bb2ce67ea4cbf60c8b55ee8b55e48986c2f8c29c5ce32fba92574abc140742ca5be49a8745ed50ab6185a76728741423327ac2401b3bab3a76748df2e03384dff882670df3740d9e8b13f0b5fd02e30cda120be123538a8660759147a162bb76ccb490159a35fa0cc577ab779bfdd1fced975e707eb277fe969bf5b13d521d09778d3b940ba0c8218b95f969813aa7db204d623072e95d68109636c5dfe84712a0d2b110f46d149d38a92cbac5454359eacc35330397ca00446053f0d283f579c072b755fefecad0e28ab34ec13450066a60c63ca7685dc1ef53b6d870d4b18462b6469afc2b07dea7e79948ef69cd58f3de24b9971396f7fe01e73c52aed60a5b4cec86c44056b484b420c12387e346e105842f34f45bc19e5590143017ddfc4d1af2701cff79247be5e83c5688e730652163ca318e4bf4dd494e9dc376687185ff45d9dd2f069421575d854720d5056abe3473602db414527db35e7a99825a6ca4a9b517540d58493c3642385851e47265e175b596e81c8245f278e02eb4195e45520c0cc7f21278c30cb763bc38e69102876c87b930c904cf9b19d42690bbf01dbfc4bde6d5c654513a69c1c21f6ba6de8507bb2c3b773c479d033aac19046322fcf52019c3825f645e76ef805dd06f0987b2a042ba84c88d283220a30dc54e9b73f24544e875cef5b5258afc5418b43bb83dbb74b39a17db02c78d86bc4dc35e0aa1c305475cabe0f9426265a5d1d589c8661ea99192a313feb447073e22bde3d9117a13575e1939e11f21fbfe52b6c9c77dd8cfcc6d02deb61567f6a8088ef20c5e2d5ce02740abf2ea32a9995c06ad07fc6d60f6a3228ea5047e80149c6651360e748cb1172491fb4ecc72ebba648b4e3b4155eb9878efe6a6020e636ca3bdd58882815c57a0c99cf543799600da28d78d26c131df83faba08d2ae881512dd7860d57237eefea8f38e29f2254af133f8e159b46d58b88fe21a1ddef8883c45ad2d01251e68771abb96e507f9ac60592a2a9baf8ee8ec76a084a0874207e4dbfdbfdb9f68d7b2e4d49178142945b20131a3131000e55448a21b26f6bbe5d7d0fff825fe6186a1728eab14d21eb7b4b9a1d59452d85e6beb6c01521a6a5a599a68e093f6838a56a3ae11a04add920dbe24dbd7eb3479e8147ded82e44cb1797677857538bcaabf7149d4f4b2cbe91fc93a8e612ed1d56a16bc905bfb516248f0c3c3a1d3e99017b18742b9d267c77d40a0e8bdf0c8fbbdd2b22b1970cd275bc34b8fb02a1e27cc689ba29017933638529430254406c157854bcd3345aea55db8d49134c611ce0e5339e87ae628b346dfc067411f18804e7e168c2afdc949d1357bd4a2da2897261293fe5d4216913c9052ed3194cdc0aea8fabf5f5d2991c0fc4a94559cd2f1e0bc1aca5ba453919d714e5e69681fd107b13325aff48ac5d4c050b0c40b1fe6d5500dc2598f9aec3aede4d56f1683396f73fcfbac4b3c315262d619896d1179ed080f60f2ed13ebe8725345a92b04067d9ccfff8ff213df224e9d1af509d9bea09d8ef17ec24fda7280b6e7edc55cba710fc3c9fc09ae189abe8521f85c2bdea4b5f1f5a7fabb0fd3054e98ef79bd0fe38ccf75dc903dca06bf973170c70b27e4463a3592032a63d3ce6e681b53ad3ca7c65622f9fa030a7d6d2bdcdebb4786cbda1124e08dc7266d88cd94a6dc66daf839f3bc98fd13b798ea78d6cbf874894d038705174d995d706902ab0717909ee49f9aaac8c604fdc449826f500d13d0ac73c072c11a170e4d70abc8651ae7adc65134f988cbbe4a8b3fb974b076131a3a24b7af07f24f9b78fdcdd74ca135ac7d331d649139a1501cbdb5b86d9d46a36d325e7b6b403871abeaa1962f7385c20525464c9379c89c55cedea6973471dc2b711882dc2dfbdb83b55e842166052b6cd251b827bdd27eccfffb08cd3c93eff792e35a45bc544a28ad1fd2a271c84c53a1af266e58fab72c31ca9c337445ebac3bf4940b1c1d43088380e1203a45468d18afd39cd8eccefa146fdf75d10e75c8960014e1983e7bbc491ac8b84e92b133fd28aefe813e10e2531074a000b028200f200328eb0538cc58e18159f22818049dcfa8097b54bee522af273473682ab0a9ad62535db26981c26e8e3887682c25c964d3604951864084f20ba1eb5f14b99344d1735bb668cb81a6de581eb21b67cd1e2711238708b6778adafd68b39def6b457f90b154180eb82da301a9734e17279669d2ec367467ec514dec71699035833c83038f081ea018534426db152027f81c90f01bec081e2de521f6ac701209e4b1ecac0aed8032ff60029e7050a51034f1d4fc25cfacb8bf191cc9a18ee8f5dcdb0a8bc50c6a9edbf039d5317b2a8f3b774b622f44d43aa3541deec436711a8d87ed472db66db8ccba4b4508b5b378402c945cfc78658f271925f8ea25f08ee55d58dd9f6422e5ec2efc1ca342634f01721dceb7e336539fdf43830c21aa1bed732532f00a6e4640f21248cfad8c2f700e5e13218c11741225430fb51ea86271e24a6ac3dc2bb2d29931d040ba9630300cf12fb70cef7a25fe4dae497a49e05a0793aebb6c8fabbab847cc490729579cb133721caf13016ef5b710054b3d751be1f605e9ff15166f09abda0aabe8a6a52e1b0617e31b3dfbf07a01b86f035c69f3ce4e94c14a87c554fe53151cd840f128a6f3ceb0fd305a8cb38f4cd21c2b9858df834ca831273a89935d99259b95b9c6444df0389676c0261f90b243ece414927b8ca471c6a01bd239be72eb75f108d833fa5cb809951a5e88940721fdcbe86f51b8ebd8162c4c67393c474d9853a18f673bba1117a3dfb2878cb90c2f5ca4e53ab635870a6e138d0b6ed8bfa3de74ae12b4316b46a6c02f3cb1c6e93d41781ed3a1bc949f8b90f342a1a12fb4c92f0888433026e39a62b54df1b3b013d2818016a6ac15b4235e8505f4ac292119963ed2e857dbb67616493d401d133ee81381044637c18b9a239469ff35805170dc4c066909ba22758943751dfbd2b57ebda97a2759a6ea16a318594ca5435e8cafd0d7271cfed31b784aa635c96eb8d7f5a93adb7a237e9c5a1b804a1e5ac86682ca43911da8f0aa1e58cfd85c87afb3b3c3f8e52ee86a44a9e46da1c09881a6e935432cf5823a8471bd6a1a62311e73ffe415bda6bd82a8e7bdae31154389d584f093554f2a976734889ab1bc94ae81dc6f8c83a9c87c8180b7456b3d3fcbfbb41f5dfb3988ac8a6dcb1f0877d375830e79e6bfc2d6cf240f5a899caa326e510c7a62abbe1f6b4fd609efed959f51ce7682ba0b22f69c6a46120cf12288b05ae3a49c2e1a8afb40c724631f8c3a084f63139bb59433c135ff43f0c3a004c63c70b54f04963d808da00e6cdb58499d729277c863adc565d20703e9529d2fb076202ebc344076aed3a7b6850d34ed835b4118c265d17ca72aa8b11b118260ea476964ba2995fde9fc5629d911abf62d8b2189f46e72b3faa934126fece715b7171697e68a6dbfad848100bf82391a970a57841a05361b3572294dfb8122841b09e98f6dd4854a4a78d40a26b8b164003198410a97f2a585e886b4f1a45eee83a26533676a0fe886e6125f56383c07ed1c409324c2c244ec761087f49d34276a3cd44f5c9a9c7a4c340647fe9be1967acfcc41e893242213c0f0df6bf59a638f1cc8b0086f078131c3ead56b91e16b33015e4865e46f962e2a4904bf59b06c5f792c47f4965e1fa41b13a41f2937a5c77ded75099d17856fdd2ce4d482e50c8b0a29f463a6752530a850c9a1fb2c8873601e3fb25c3e3470822f924bc52cf5ac88c2901b62100738a1164f59426e6ed4808cd8cd31dfadd720a657a70a4a5798d5dec8f3f861483365112db6ed502c2fea5e3851704857dfa7c97a9eef67e6a43fecca9c3e4e391872a024fb101d086d4b994c9c014cf7e43c33cd9ab0f3ee324b72aeadb1f80d2b7ae7b4a89c4a52ae9d8774e8018ed95c8bfbf7e17c59ce03d4ef88248a362683859729d2681e2f257d3f997e1faace58a39f06380e3abfbc6fac9792819b0f3a79c51e425c90bbb9f7d478203712da4b1daf12b45640f6130c6570a50c3ee2dc071d4a878aa46700e3537194b4818606e8686e9dc7f480be778af5f96e4cd1b42a8afc7c202e1a80adc4f75441185a25e12201a4161d3fe72e3a76eeb1c10c3f6fe31ce4c082f3367f875d005442b2eea5149cb397f88d9d44bed08afcd798298fa8805f54ca5e11b89a760e6d0f24c96e6488c9bc36b18abc36e56462a51ca62e73ccf1401e7cd7ce8a54df69ff51a0273956b6d5f49d8a21377b038856a30eb323dfe3e610903c21e7371e10d194299978d415f2ff56d4b3d3144d861c676fcbe34c5958371f10f0adcb6480458f63d3b335841fdc8e64a19f8da4df9815d4068dda5b517d1e1dbeb1b554113d26f0bdfd21600095a21e37a629a044e8471f99cdecb12b68425f51c1e62baa43000523f42e100153c5ba7e3bfa3e824b7365d1077a9e973c6a5fc2bc3152214be03f4f834e6525e7cc3e6f12342ef8c87fa9051865b635f78342fc2dc464a38e4fc265973a581ae9031b912956f3bc574f1cfec6ab5e4493f0957f02c6a033117cb06edc9518571e4274515111907374dd49137b30b5a4fbb558a84ce855f9e20843f862bc1a91634ba0fec8377475730d1ff6d68afd14fd917e4324000e04fd27e667376677bb7ebe0873fe2372c8ca7ee5631c7e39765373eb684fe4b1b6adb4660fad0f09208410bbb6946f1f3a8d0abe925fea0106b96d0dfda050ff0bf1d989a3832814fb97d4831c37ccad887a6db18fea02ae7b1a818d2e7a4c0716b683d355a923aab5fc580bcccd0d9436286c3850622dc68fb5b937e4e91544ad9de1c19142a1d50dca085d844504960a3ea47cde9fd1151ed6338174efcaa5a101db13f6f5dccb931465063778d88f5000c3740638287a771e27700a4a0821dc254dcbbd09a6e40fb2d19163815ddbb4327b8ec712053b3e49574b07649981ed0a11b082f78453a2079b7cf6187fc2f4fd58259d3dc125b5b7a1732a526e44644401761a99b40c3b8ba8d5fd20e1e50e4b975de6154128389e5005b52cf231741806632ca3c7d13114590cef3831f298f3a5f86ae39847d0e969dce0cb393366b7355429031849acc39c2263ea416c74cf9dd4d3cbb20fc32b430e243de4439345c1bec58b92a933a4ae559a93e0de61a98c4020ccd79a0c2266f6dd1046f243526d3dc47aed136990af09d3c79ecc47c57cb1768e4a6278224af4cbd96fe28729600f3e61726768b5e1d552a35a355c829b20bd2c7847e07896c50f8900d6bf6bc9c5dbf7983e12ec74a749588e87176edbe46a46b69a53b97ab430f82493f4ffc626fd577a8c24181597584159e8c34d4923debe9a4ca8442919eb69979f97370c318124144f60e218a77042dcc66520fe5149240a5944c878179391eba72fdb8db4420165895de638f9672aa02588093f33e8b5b0cd7206f3dafbd6e8e9295bfbc1d0352265791ebe81bf2e2a91539bf67bcabbf98b1663d22ea47812452ba26dd449e8effaf29a345baf14051246885dbf346f079d4666be512f74e14658b6067fd4d1be0438ed0b43492c7ad88fe2feeab152a4dd347077d011502b03d8f254197fa3b750e4b158ccc2aa3da5625e547820dcb8afc773914ca3351fdc0b5a7803962dd31fe8883f615cfbd550228836ec159cd67a79629420e10de6a35c9b503aa42d031a451b5fe9540998c609b6eae207592028aef0a0d0c6dd3a654f611ccd1eec3757b3e4a18775e2a7fc0a19035c95a50332e861c302eee7cb41d66b040abb88a5252d2ceec3a56f8e020eb799fc8a1b968971e1120ed30f9cda9fdce5105e958bbdb742f629a665d8616bd94db598d2ef07bc3e5684b98a1aaa2762d0cc0e71bf77602fa7aea53673d9a78bb249a7782e3dd4390ac9ba1e1a7abd9ac2c08490a78c60e5eb21e82492259ac966733e6e01f4f04b5baeb38c88bfb00adb22cf2da02cf1537970dfe2b431d1b87c11ff63730675c585f5fc421ab68255978186d6ccf9835b3e50b6ced2803021e4873cc63ee6489de55ce4bb7af88ce1e82893955772fe4716e1656d586f0fd1add9881cabae2bd6a8a3de8e7443d946a0fffda204c90d12a81fe986de6eac259e74b8f62eb336a42ca1b25cd9e1b6d804814730610c0821f117d7e3eb8c3b1080b63f43ee8e23791333a5ec6a777d1abd53a8bc2be1078e5bafc0976dced19a04196700fcc13d05c6838638d9621915e5466cd200a06a01b2efe295a9b680d03c99e00ae1259d7f3c4f288a9b5e204b218a2752d4328b200c30e372ad9d94947bd0a4ab1c4be41011f42a2d0198c8efaed734fa74d11aacb469bb44d0dcbb116be1f98391a161511278ab34ebc068359df7c66f93811a31b97dea05ce47d2d93374094d4edb82a83ce1f0456ac38d2893ebd7ab1f62617b2f0e782926e92cc12e9abc689145f350bbfa540a0239774e29e836bc9c1ab47315cef1cd83a51968f32350d73c1e12e234809b925be974705d07246972f01d4dbfe8192c728ca9d48d22352319bb1538ec9e11f0dcee46bd55034390d22994e85d9f4ca62b9714912dffb8f8136c657e71973f031ea7eac9472e353528edbd0ea89f44b18f331b4bd7fd7e918d230fb6d2a03c876da4429617b330d92f0411b91069100bd19ff57a0acb0d2c285b296164381ee1bf1f13063f4bc2fbcc53ae6af10192d66bcba5609209c793393b4e54d741020123c9c43976ffb11ecaa5a46f38f503d8acdeb1d8f81a0e66990a84b7abcf27149e5e59264595dfbaa297d9097aec5edf5f2590dcd0d75cb740a05704c0bb0f8d86155fd92f9520a3dc36a67e54a89f0598ecc4d141144aeca3e44e7a5909d20d12ac1fc90d1d6eaec5377bd118c96387eaea95626d469d1a32b04f8c5157d1679f486083906e1840ff069d1ab47c262fd7200d73369a7eac227f84e26c94473911bab55f608ce9c5c45282443742f06fe5fb082e9c75ea83818a03287776dada718d0ea10a83d2dfc9e33c9bc3f0b8f112fc977152b0811a64ce0debbe23aa305e752c91f837f786d66cbc8f263698f96e34fcea6fe7284fa0d24873c39ce0bd1684ec11d38b02e06da171a38e0ff2a51b9001d7f6d38f35d4a7d08c5d626c2c40cd1e143d90f6a654b26c3d6a29ef28ebf692630c93c77dc5b8d3678d88c60420db4e400ce9f17f71be601110e31e7d091ddf9f3ddcb12cfd7ddc709882049de235edcfa2f1ebb7a3831450d9aeefecd3892f5516db2e9c872315e44d51bbd03f090518b225710a1863b6d27afa4924cd974e8ea8ca0cfa4e3722f1b26009399541823360cf90078b6fd2968361188909c5525d68207bc92642fa0df3d3b92002e56d5c8e134d7eed14f991b253690ea98fd1bbf289226430f3823b591384b0823ff825ae1392c4f5abcde74560ba6867e02d0a7965a4b4cb5a19185a103c96f9f34dc682a8c06787b4dbccffb97f4ba72bbeb2ba3362ffae08fcb6209e61259c43c348d881a8c81c1cfca7850b10ae6045176af8df091823278e65609b65e8da32663d304ff72bb1e0392ef37298bf5875eb8a0753d2421ca4cf7f91bc7de6164e98dedd0069b1a4e8908a5030c9c558b5af5c0cfca95952b178e8aa0276f8f8f38e4f575be0398dabde031305807cf964c13eebe24179ef75b2006501c8536ec3a6206190e3260ea6331a82914622733ef32c5d495cc4be86fef43fe6ecd1d25efd0bc88e39325dd05e257481b2bb667d36f5495641b3db9e177446f2b8b106fb58272350c282da058ad28e7c9b1fcf0f704e716b4f417c63217a6a07ef2694f89b79584d64300c2e7346816e5c82608e293e85c2a6d8251805a31b1ebaf0e296f5b9055a5f71c0dac4ec7395e5de6717b6076a13bebd370566c57a676f8170e7ce93e970f9d0a304b3166c8dcdf74a65020975166d7aa5272a0c1f85b369724d178805c0ab60b9c9a4fc661ee691ac204c05dcfa461b2bf031b3b9b05583f621fcca3a0a241ff91e785b420447f00f689153cd07f46aaf141583a67411da7ff99e197de264c820dbe9ff48a11e67cba41707a8bc08b72434f5431d43615a37ee847585b3e5eb7d96c48ea42a8a9f204fbb3ee0a5eb52b4f802ff6163f6f00fb64396117cb61a6c0dc0e6cab04610d26df403babaeb73d620b78bf2196c12c60a46fe243b20072c5aabc564082c7a029a02266c5e308eba9a0e0a7600b06f8d12c969bb364e36b1c28e661eb0c2ad70cb74987214198f5b70696c6337e4622f1e2a0028c080068470050927647ec47669ba4edd9e2ac70c793f887cfbb7ffff6fa5514959371189a0b04d64db7b6f29a594329b066d061a0751978a0b75b344621136a2326217d87167a5666aa140f92f54cdbb9af0d8eb58370157181446b7e12553f62f8c90899f8897364fc74897c4562c9973c04b97ccb2d8dbb839a4e7f363a452c8492764e2259908fb9868e300d83dfd929fffee2c51ec4765233463e268c76cadd8a7d17cb15fa86ac83e169bc5740010458c64c752f634acb5228ae2e37c17711e47a78b62b7b99e70f1695c4df361c77cf4383a2ffecdebe03cbe116f701ee74289d90a0ece8be2e37cfe6c85caa0381dfbc04776cc3de24d49ccf9fc628fd908cd1c1166c77c2b8219d1a1fcb38e9decf874848b3686f1305435641f8b7ddd369ab3a7612fd863bfb8264bbb29f5e7f1274fef2c3aeb493d3d3c1de3e850fe9e0e5453837562c7e696f5d00c6ae973931d475d321b5d321f651f76cca2988b5ee0b68b1465d26e4ab3d8cb3eff4c2682b8b7812605ba4820d8ba486696899629265aa6d0d419e4742b821a949f0ee5d734b4cf0f50a53f4040403f4d385c8fcf4fa53e3f3f3f3e498e1c217b7c2aedf1f1f1e9198284346eacbb19345b3d3d8f91b446eaa994ece9e9c1e956083af63c919ba75b5dcf53aef7ea83568fa880b263a640f9f192b6e189d9e898fe63b7e750a767d6c13ec3b2ce0a4163ac8006e5ef4f62b75b28507e9c6e9db0a30d7231a5053d366c8cfc7a833d76f26ca1da603465a2cdd9313f35350700e840c600ac8e003b0001c0600fb1637e0c543530520d7c4481f2eba8a2d88b220623ca441b0e53f6b16e733de13122f1f37fc679f1312ef6388fa3f2c73a066369b508a7e79b132ebef8f9b1ce12d9b2cee9c91281367c646651ec1f13632f7e7e7c44655053ecb10d94ffa7dfec8e0ee5df9181a2f387a7efa8614e9ec7ba8c6795ca66b39917139375db848ef9479e4e6f8e8916879381ca46688ae20d3b669ffcbddba8ef16cc07b82610b6ac5b2604ec9b6e7fa040f97bf293ddcabad559a7fc3cbd9e63a7a746800012a0c1ac27f17c8c47c44e96cc5f43654326e29d16b80debcc6c23a166daa6cfb1cf31d1e22c931df3ebbce1e23e377a64c81815cfd63752e1ce0df0bbe1f5bf99c96236356e649c6f8b1a988d68972850fe166f8f5822fbb9daea538b6eb34b476c966967e667755b5ea2e936d84c7e6e820eabb0ce669d2c11a60dc2849c3f150ed19270854e67840835406a80d400a90172a90b5703a40608ab46861a20ae262e9c0b886a04556f5bc0eca8787242a785e2cceb81a5dd2599a742351a1a585aebe96beddd94bef055fda664c209a0e78120e7b0ff51c8de70ef4e74d24ba39df475b517e7235713ee6ac26d4e0dee946a83bcfbd0695faffa4da90e597dad482b3d2374e25e67d855ab587bf71f1f474731adbdbb0f0b464632aebd7b4f2daa45fcb57727972e79714dbaa77b53422177c99458011f4776fc4187ee675d76b2342fef4a10dce5dddf00ce6c1a1ff9a8663c5b5f4fbaed57a95a1f53533fa45216ca3a61c74b844f28017feab5d6bb7fa7aedb6acceaf46c757ada53a6e380c10813941b66707224e4ac9de26448c106d3f60b58d1d1c493d9ea1e4e882666b0b2e59603c70c5988e08205540db9a2d3005b9818c265490a9ed872d6eee1aa080202d37614b8dc9ec8c16c75eaa44bd34e0812125e8c9cac8ec14975558a9a68a1470b4e8a4efde9d4debd9eac7e633dcdf3103a67fec59f87d019f3aed7397b771edcb39628309e9177f4c39bc19262de9253808506794597bc438ccc5436562628e611071d1d563d72c94bc20d04ec78ff5fa6d78c2323d6232919b116b9a4034678b2a3153bda1835c7b371af3d89f556a6df564f4ffdf591c7d4767f3a787f3dc7d45ecb5ce92fc6d4e096e9adff7a68ea30b4610dc330a4e118f33d3dc36e996c8e9e7a67ffee7f7a372506d8f1ca3c985365ba5d3a6247cdb377bfc99969d1017776c6bd7b8b13f71040f8dd613cbbbf4670a91ad92375c98ebbb7ceaf27edaf3defecc8ca515e51d8ea37efc6fa98bce5dea84bf358b03eccfba4677403b7d9dceec93e38eae747550383b915e9177b77d6f9f5a4d6af7afe6149243bded67e56bf29651f36865784c57c54a4c8bda2cd3e81b1b49b12eb5797067569327f3f73a0a343f6c8161cd5f201a2403f3f3f3f40e56acb162734a0e182bac84730ce47ec78711490aa86fd9a8d2c4972c445ceca13cd6944c44b97bc7889c9c7d1510aeff0931d2fa849c9d57414a85631efaceee929e7c2719c8d04d120301b5992e408f834ac76e14255231563e6ac3d69f5d988987d58f21ec94b36c63d3a4a59f0b396accaa109bb10a24241414fb81ea4d8f1e6f8f975bcd3467fe2271c75c922bb4bcb474732623682b12e67657785aad5dd0fb3fdbd65524a1bc192de6b2cdc46a15038325d5fc209b602f6667f67991a67b7facceb74f17b6fbd4fd7795687fd6af6b24ef3df57662c67a5d377bea6b33ed657ff9dc66ce9e8fc0c4ac3a1f1d0c05eaed6cc779eb3bfcb98332806467feabcf7357ac10e116bd5927d8f71fd8c1806382dd60af65d6526400459aff9efdb6cf524f2f9ce674089602f574b87b51af7774deeef61cffe0efaecef5fd09675d77ff74cbe0195414def6b54c637bef7222c6035c225bdd7af5d40784f5520eca8614e9da7614758795fc2ad8045c1d6a24bdb790fa7feb04434ef7d9d81ca9879ef2bd2a5e5bc27be378e0fa62a0da6f735089dc153ff7daf81f6f76c6e408958abf7fe830488b00017fb3b6ed174defb0d08f0414d777dacb75ed661fffd9abc4f00345f3f8312d1c89899bdf71b50a29af79e009428f6de7f406938efbd0894c6f31ef79e469dc185f6773bb4bfd717fb3b35675022d87b9f01257abdf702a044ae1744658c4d08e5d9f39e99d423a2908173fc9e326b7ad2cedff4249e9ff5a49c97f5249d8ff5245234fa73e76d7a12ef269cc0c14bbb71bff79edeef63bedd42b4d5a3b1d31a34e87b9e5ebbe4f73b7dbce4f73a3da77370c9ef611ddcae16abbf1dbf5ff51c3b7e8f43d369740d28d0f76207e192df730ac5fc62baed479b90e9bdee265cd35633bd23a6f7a18d42ed62765683a6d80bfffe7defa90a01f4cb0b509c5011810b3c5c39c3a72a04284007a5a51d4b92440173a6fef24b7a9f97818bc3da5a44534b643fd1568b19cdabeffd5ea67fdfc32e4d258297fcde0b61f4921f28464008dbca829096f23afe54bf297961bfa5b40cc517df0e864cefbfef60c8c43d4baa573d4f55c3fbfac19087ef77ceaa41f753dd4587ee87fd458760de8b1e68c76c472bee9b85d632d8d5a466d2cfaa1a313b8629bd798ccc8aae64685c4d38cd8a05b65435700e8bc358b018875523740dac85aa0657c2633117132e9bdd98f47befbd77578bd57b5f759a99de7bef5fd00c136dfff6fc1c50da3dba64b6f9fb95cab8f9fb96cec879d9cffefea644b1bfcf296dfcfb304ae3e9a1917f5f8812897fbfa444387fbf46693e7f9f0625ea7f5f034af4f766f4274fce5f274be3f92cfe104bce7a0e56fad7e0d0d0d86fe30fbd0ef7d871d4d18f19908a181175b517e7a50d425326febf26fb4da93a91b6ea9a68a4dc9c65cab970dca562a24b4b32466c91223333e01f3f567bd62b0b1e05cae20f294265c878be27eb689068907a3a3d5faa0d5c2e558dacbb50b3d9cd4ce766a693a333cbc999fd0c17cd6637b39b3ffa5e6f35f6b23c8ee338d429d9e9d9c3d3d3718c0067e44b114e86146c38499e9e5e801d11262082ab2208084e52ec1898e3d8eb297613c4ff9fbdd67becf41cea41cf43e874f142af7f76ac6330b9e892d9281fe51f438a1c4962420c77a141f94f8099f80b05ca2c560b958d951913711405ca3fe2c05f6fc351648721de481816a784824d05707054356462df114d105f02192832776e4afd84ae6567c73cf61b8f3f95c4279fa7df461e4df6f1d475939de7d476937de7d458076fb2e79c3a6fb28ba7f636d9fba9bf4df63f35b8c97e73ea70937d76ead426bbecd47a933d76eabdc96e736ad5267b8b53c76cb2d79c5a66931d766abec9fe3af5cc26bbebd4abd639fabc5e6db2b34ecdda645f9dbab5c94e736ad726fbcca95f9becfcd4b04d769953b78839758b4d76d5a96d36d9f7a9639becfad4b24df6d4a96f364ff779b287a7bed964074ffd9becdfa9fb26bb776a71933d9f1a67931d9f3a6793fd9e5a6793dd9e7a6793bd9e7a9489518d638cecc94e4f9e4e4fb15b2940af79fa4d89043b6659b7b19132631ffbd0bc8140ac890eedec8c1d6fc14ee02d0e1861b714f442432c99bfab6cc87cfa0bf1e6f3e27b3ad093fde7efd1eaf32a2a8acae84289649f7533b0ae06e50f5ff3747a8ea30bc7697e90808618298244db3e3dff60c2352de996749786917438cfe3cff1798a2eade7d2c8cf620820402121010109b970c1873ce020c739d076e1e2e84550908d1299efc98eb1d8125d2cf6e242dd8a8a669fbfce969698703752bc29c58e625d8317695c2a2e14c5dd283a14939189c16033332e9caaeb82825435b0d3122b4ffd69d8a0d807a96ae0c86431f27b3aded91897d45d52c44c756909e7655dc4354cb1a0213a34eb493e4f767cb3e49325f3fbfcd86f4a31271b237f4fbfe19d8d918b6c8c1c3bf261693e6276b2e4d050d5b828ebec98a1b253d665dd94accb4ef9a90892952956f2943ce506dff7d516702387abd50a12602862261ee37b2f8b0416c60245c69ed7f1bdf75edb08df3114d7f35eab6270be35dfaff765541fe08ab158717e9a2eaefa170326c65997c32da076432869acddda026ee470deb779fd8069eb11ad1ea0b4d45bfa2354197638dc13d71f18862918e73ca5f5ce9f13227b4dc82281b555740b148d2a52210caf36ad2ae3845418bfd7d09911ae0b94b41fc3961aa0704052549754c8623d8a82d7b7d5a3dcb589555bab623c4fd54136f16b991da3fa005b3a7a163071079e895fa656954c6ab542d5c9703a8542d920a839878242b2a15d507c8666f34b4301af255cd23e57d948992554e12b96f8b15a6018f6d155b98cebb5299521332304d67ac8c4c0769199234b9dac85301ea3fa2aa52121d96aa098f66b660da9e22a141714afdac42e53d76c4a65b85ad038d5d4df6bbb296d2d08c3aed65af35ffc378b17ffc57ad3ad8d3809b48c9419823a7fde87c130a5c7e7f52345f8420a847a6b9d0a35dff183922574ad39b58a38db2c715bec3ec6db0ee1366ecaec3678b7689ad3646a2b6345925aefb5b7622cda6ae5d6d618be6c6b0e0b2576dc296607b8d070434e059c51bd3931b2f886951708d5744c9ac362a98a2ea62da704e3ea4898a2f1af9837468080fd12ec53fa36b7b270c3d268744545453988680e0b25cc9dd37e0948a6fd13984efb285891317359fc30cd4a9530edebd13e1782f187d1276b348e16f7ba1b97b44ff4490f252590b244952a494efb34280d070c456071018a0e400124eab405b8c268d684d303d0f3f6c12ecd133180dffb4fc440feef730fe1b309e1fbc0ee8936217818ac14bc24fddcf925e9e30ebb2492b51f161aa44d681393529c19014a31c698624a29a520f823a539033302985e1cb8e623d30910e6fdeac4ac36e79c6b662c1113b303da043faed7896e62daaa497100e1a403dac48c40f67298da160026b50d9974a4389898f931fe94aa038abb268e5c71171da3a294524d29a5a1cd5a6badb5d65a6b3d4a29c5a025b211b06fadb5d6da14100f298c500f1d52c8b1800b151ca08529a2052f48183d5501c4ad09159ee02b2a1378c0a90827b2f8f002140d61cef803416f0bbf26cd41291902aa8790db092c990288103e4820c4adf63445142d18c1238996d32614c6b4d54c7b94c4045312187cc8e249093c90c2171c9400c1b2443501140f4678f898401655785981d31212c43014839120415200264a0e4c3c156e0f1d270812458fa3db8f304c4870dae76076e02d2e1f86d01205123b54a104c408fb7c851c5f0ab5c86372cb81490cba0d40b550c3699fdbb0e3cb9143772b82698a114441a253aae4e0e18a0d46f080d3b123fb80b3440bb81f2b20193102470c61a276535031b28220446425b1dc0a37a31da870e205c9134cae1ca123488fa72a45c71539b2100e7424e1a4c310503d78a138243e52a0f2f444881f4178555059b2c509214c3c2431c4aab0830355470e01cf0a4b3224a12485a627272c2fd4b0811b951f5143e41b1a90d345c101872b27c0828386215a5082071f4aa0208b12782ee4c0413c20870d505c78628515b72c966e4144d1414718221a28c2068890203a7cf0a1ca8f28ae2479e206255cc23039d2e15df131c10dba2635a880034ea77d4e258737021c4c60761499220b2f392a57c4f0e9c050f207f88ad268d00a728cf1f5be4f06303d642af88c4973606cc85380e991a5e09f497360368097e032603ca728725f9ac87d59c2094b814224f7e587ac42eecb0e1e0a725f6ec84c725f7ee429b92f1ac879b12273c979c952c50b220a8eb396f030928517a4137879418b1716bcb0404bae8b15238a725da0e45caecb52ae8b0e59bad090822e3dbc259c9b34d745c753540bb670d5956c14156507be4d9a8b62b2836b93e6a2927854a29278d40cba0a52d40af8095f5d8181e035573c1570b04a6e016739c93f28c122774511cf88dc153d320c392bb0f07ae4ac80ca5981256785115630c1bb7c607efe71968d841cb3b463ed022f1c9b340755c4b4519c7dd8100c5a51a60ab7f1f127630e25040a911e9a50f284104240418fc89d6007051057ac3072d626f87f0bdc33690e2a88691b757907de01670aa1b433af7882a5326e4cfad5099d49734eeccc2826cd390165b260988ed7c47406bde2d6815fb39aa6104a51aa13ed7d8bff7a8bf1de78fcb9a38639955acf633c95f8df9a8bc4eb6a72f743f37eedd2749eb572fd7d0f2c51f7a1d52ce9fddfd7adee7a597fbdae5ba7e33cabdffcaadbb4faea3b8cef2f4cd9b75aad3c549b41896238341e9a1a9dce2384f7f7a09aef2ebcfd1dc8d4dffefe63d6ec0596e845e4e2c0d27458abef3e668dca80a5f6f71ebdbf937b7f1f55fb3b0fdf3a9d6767a6d379ded567efd3c567759aafe94938afd3717ed54d98f9134093cbecef39a65ee17c17cd199488b5aa69ed565fbdacb3fe7b5f11dd244004bddadfdf9c9181003660edef37669fb9f677d96b7f8f7db73175cdfedec2cc80d274bed79836fb3b2cb6bfbf4c2ddbdf5d01523c8842001a176880f9a58d231ff92865c441404a2fbd3227c5311a8b813130c6823ee9839b87277d70f30f0c51b0d588caf090323e1fe393b3be1cc8c5094c89df507b1c5019df531c0e64a2c690220a9c5f7cedd2fe7200c5abd049ff2f2de87c988bf3d2804efa20145c9e70a205323c277dfa97b673d2d733343a2c58014b3cdfd53987c17e74bdfe5fb01b58cdecd264e79613c67913975673aa80894b739d5a529005cb6e892b31e7c3eeff7db0b64ffae0064125c013532b49a0e0044f2f60601897f6119df61cffd2884e28ce825a2df63c22d8e630d320b08789e02f9e85d9ec11816fafe655c206edc0b7f8c3f64a530f39963c4de047a4e9fb3e70f77ddff77de0f7e1af5313ccd9f372cef8aba22ccb32cc501f78a170c5187f80cdef03f2d126558d5d6a17eec0dc3d1029d40af4158ca1a46ad6ed763b98ab45a4e44e2527a2a92ccbb22c893035e97bb14cc599c3b8f53c8f0a229610c14d445459826f1327a229b4d65acf5a6b3d6b3d0f3ff1eebdb5da7b6bb5f7567b6fadb9e26cf3b51ffeacb5d6b3d65acf5acf3ec1370c2f543dc1ddb7e3377bf962115bbb44a452916ba2024ab5f77af90343afa9e28b23dc81bb1dbe5db6570a10bcf74a15135c10bc614a6f956aeb54083a3981e0bd477cdff7e1bff8de6c0415dd9422728061088629ad5321080411524010043d00862118a65221180504ef1d0204efe50282f7721102047739282508a2e98993263826601882614aeb54080251eb6d22c1bd57984620821f9640f05e2e20782f977b2f08decba5f4ac0d945aade7210131a6b8dd86ddfbdd0fe7ef8e30d8de1e7850c28001821d6da8c0f7639656bba4d55eb7b97ab676691e50292ad30329bc52208cf335ebe59b45f14e0436e85e9c6f7d2cf4bc4251428c7194b9f243c244bf9832a889ef679adae2ed756c7e3dab92406fa45fc65f296076107e5f7efb26c6a00ebd8c6f188616ac54c77822545996e5be39e70cb6a0bd9c133113568b5ec23ad1394a59c25c2d2425a7527229b9b22ccbb244c2945b52964bca1d2e52eebda52db145c28471ed6b5fab4151b1fb5e8befbd578aae291796dff5521f983a436baf98ad258231b62c7c318b8584dbb88c6959b18eeb27525bb9b09eb0bccfedb06cc8584e74b5d703bf30a5bddc97b3a55e875d124b0a8b915068a7ebf7c99878f9c0ebbf94e0fc7d1f18a6f4de3a15821f9524def77daf1e72a85f3cf0fa2f244742b0830c2fe23334382f255eff6584d5fabeef9bd2727d20f88161087e3ab85edff7cae10593d5c46c5e38d894b19a87159125bd88f0fa37bc86f0fa426ca801898691fc40f003c3542a04bf1708c89ecbe239def30af20394c46b86970c2f20af185e3f5ec0f83e286094df0745f97d50c428b3b571f2b11625edbf30e9c328ed7bfbb54bfbc49ffff9a1527ecc7c6dd55929583af0af04cb94aa860d12ae1a12b2ff96b42c7a833a85704402bc1b39b82a87a055d9c05f7b5bbb340658d2e26e14c175ced7fc328c15bb3763a8226d83bed07e19e3d0f4f8bd3cc5e5be70efb5b522e92147cf2b71be04c0efe58c7909b70548a510a62ca02c8bb22ccb32cb67aba62772b0e9059082b39e1e2fd7292c510ad7cf200e5329937a99833936a92ccb2565961d9dab6c0961c2213952f2235cb8924f295db82c65599665298429cb922565b9a39b92f3335229b8c4a5878530c9a06608f9d66a5ffb5aad8e66a6f9c78e96526a2d78ed0596b4175c605e2b5c380c131a39d0c0039be55edb24fc3648adc0605585ea959f8cef410efb314783bd9ea90e26d14b7a2abe04ffae55d9b83970e0c7712f06cb6909e136568e6db58ac13e0b2ca62b9794597674a1950b532a73fe3c304cd1b8a4a5c9c26b4881400d80a189557789700910e4fc5a08d8d13e2c04bcbe8de2144a18267d990aca170cbcfeeb055e3f211b39447a1edd7d6098d23a0583fd8fa390103fa2e8e583d7d0c5cb032150ea676886ac3e26fdaa523d05d2c0ab07afff722166bf8e64f6ab055effd581191d9322a15b99b58802cd74e4b0705e1ce0f55f2c946fd21a2f2d79533bbc60374fe26a20c839acc7ac46af0dc0c0d46b859a16b21636319b4da90c9b970664b30d5e9a5763d2d97e19ddbc7ebf54e8fbc583d7a738fb9542ce7edd787dede0f55f3a78f62bc7b85f45bcbe2b4c8fcf68233b5943ce90e1f789b588eac2b7d5cc229d578b5c59709ff207639a2c3f2e2cca2030bc3efdc2eb532fbcbeab0baf4fa37819c3c62022dab4247f28cd7b4fac456551d1cc135cd336a5324a93be2e63c028efa511616b93844b0a97143b8ce7612c5f96d3a8044c1b29729619aa110100000001d3160000180c0a864462d1304bb334ade50314000f5892465e5a349649238120465114045108c5300cc4000000648c41c829c7181a303e192271c090e55f11beb93fc06e3390f66630548fb1ec771c87ea49cd7d452ffe5f15e9ef62c8df75ae3ae62ab55b73092754caa225f9f45d6297e9be2f46c95b168bdf53ac02c06031ada3bccec8eb88a9d7128ebd4f66c05ff1000df1a4684c0e2a9ac07f23de61ef3006a058aa9ed83183c43be4be857ea3ea3d0eba7446f31151d6833481f61104895c7e877505bf4fdf73d5038f69556d8cafbd163cef751e6a7a6d08203e37d35a9e5942a758c2eff03e1280a170f02bb6f07f789b7ef10bc4fd2270cd8a6eac85de7e16b02c2d849387a24dbf6bcc4e0b59a71423cc3e3e44ffc1443bc505bf05cacd28188dba2ab864a6e46119f9c102643bfd03f7e39e14860ce2d4c00fe736ef6ba2bc7de30d0e0c477907a0c9ea98d76c712e81d9c398c4ff070793407ba01763606f20bbe8aa98bec5b4b7e912984d10419a8b9030cae8ba88fd109fe0c822566f943d9ce3911ee9119533a06bf821547e6c26c2190e9079c36fb0c610b6a1a95122d4503dc20c5ff61f8faaea682632556b46529abf91000e95ae85af1c0bde8580cd99844c278e8032950308df67bfb9c4c6644d0d8f200a807c7de87c5fc700c19fb1464658a816be349d56c1638e11f145267745c5ef0a1c366ab1edf6f318cea2968db1179794e204bfb18ee0de5b660ca0484728e68e335bd4030065d8d09b1f495a5e4152d8d5befb9fe7c509afcf6265e34c3908267e8fe2f37e43224c560f26042dede891ea3ed4634bcdd5fb1b72c1dba25b84a83a189b13d147411878e9025e349367c137fd82b600fe34bb3af20d3c8a14d8ce2ddac7ae939bceb602c8dc52885f13ecbfdcf4bd50a0121c3483d5c96c1c724633309f0b3c4736d7efe22d3d32425be4d8f2b65158631b1d3bdbc04c786798f146831ae85685ebd4d5540ea0e60aef68527c7b295db736ce6d32b010a69b8a3afc04a6d0ca3658118ee1bd01002157b7e41ad91008b8712bda018d0b411a6b0de0b4488f75a071e501e9a0ecf5de66bf5bdc0deaa57933313af27136beb645f8f647a936b665a816bf86b61a3a3f442fb4ade31a71c56fa7d5c3312e06b309b9dcaca1d3303f708239839c39af24adca86b20421bb3f8a1d7b73d1527ad4f0e61be9eb53b3f942212950e30f18658c01260b73c87d6bfbd6f7c5bd80c5dd985b91c38c0bd08f541cf3ff8ddfa826d79a4fa57d5d19e84107e116da9ed779616679f499f67b05f36578b74dbe7f9194b2dc11bd6bfedcd0a0e71dacbebe812078f632a052b62009207ba08100187d472c7fbe03b8fff3365a341322e9c0fa9e1178b6f142c0eb85321da195d34f3ae7387e5947b9887d706298802186180fa71f65cb0b01152a24bc17b272fc008a54da3e5013a883a1afa8256745be6b671476b671b6bbd71a590dd065071932680ec43129db02e486612eebd34819ad7fa12f0a87c5f5d6de4e1054a417938d8a100c92388367282d5bbbc40c297a71aa9ea21f7a40d3412ccbbb2135b66eb966bf0726409513229b4e84c7209ba17591235d47cecdd4a19a06ce4b0032ac7f9dc69f195c38119b27fa0a1b58b1411f38f0465e0b13c4e6bba4694cec474097812f1cc0fb0339cc4bdbcd6538275b7c994ddb71ab859731cd6d0240401596fd0d0f8d99a5cff8481a8fa2cebada7ea9a126bb46f61dafcadd21d6d344aa78cf160b5a4e444b99c1dec5dc0ec4289d5a5b4f5fc0c73bb821a62c6076066b1cc8f04b5791ee320e67532226d95186be64a7e10d24198d8b3c4f32e1cb577b7656890c5c48993ab53353d3275d7dca83340c174d3c950e9da0634fa6ecb965bfd4944f094f20b55c3a249ee38601ee3a9481596705e28d424ae40f24ef0d33c61015e3e277c3ef94d2a9b78d21f2c136326573de43947d8ce87ee192ca680afbcd524677b3a8c713de22e2e39ed0e7d7c97c59f380ff4d96ca57d2c0f42859c8e65f00a608f19c18bdb88407e35fa78c9a19f6b7680290459e29861928359ea9d3529ae9c6610c50b1b4789bc26980079ef94d16637f27a61273958f70ca281566a5451975dbdae4174773d0322f401719e6af240d83d752c832d7a98ad10ac4995f3e558f4c851117e4426ab2a4085b23006138f1f66c9dbd2685d6570e99b6ff594febe24f56c1d12bf762c2cb030c74d269831a1e023558c0230f866a0722882c982379a2aa3ef077b8a04df84fbb948c675b6092378377b6ef8daf832eddcf0036503d8f4d16793c05521711e112511a971fd9bf70de19e1423b5c47ae1d67d794ff577c9ecbfcc4b937816bd0a943e733318a6160930893c13ff3c2b3850bc9ddf84ebe35d6843bfd9d5813e6f98d6be798e14071183415ea080637d29667ff7e6094778006d497de8fa7bbd3974524cd5ff775cef0fb9b99b8a5c52530a2bccc3e523f8f0d6786b7e1cfd54dfb9cfa175cebcba8b7f4b607dce70fa9e34358ca37cf2f388444e9e6cf1646350c19890b43fcf009afbcd0be86d1a76e32ff8757c13a50297ed4abac18d4934294e0d11396dd00d712c7b4c53498655f7ffa1c7588d0e10341ba67e8cfb8c7e831abdb6bdc05f78c3b000d31f46669b3219cc6d2f74e28fc70acd13d076b1e0de8e675eb667893a8a64997f1688f62b9f5048cb2c1b96a3d3c3e3faecef2934a37a0339b7646a0cfc035121eb6b87610e5caf1057541760d5334dfc6e5900151f304b500048f69417cc179017d48bc2a8881345b3887f0188ec4c512b08177de6ba21462d82a073998d0463802f7f0da47f6983326e12bf09a3535abf1c597a4e532c23b24799e16cf8175de14cfa7b1a976874a68f219a4633e12a8da994c73420d7f7de5489ae4324bf951130fc34c79a1c925f83743793d9214f6461d603f13e64a5ad1807d92877d18993d606efe36df507e955233b8decbb5bdbb1c81cf8a5d23804ddd4cbc03a7e1cba75b29a8cb1423beab6b09712f3e9e8ab78eba6ff71d3279fac235e3f320c56950f3425bc5557b4e09f5ade6147e4bef0a641b06c6b228204297034ffd3dea293fd639a43f1d19bf0d4fab6838de9ee9c1cfbcd4a768079b89db751eaa12bab066824d2ad32b489fa6388b7c17ad596110910c595ff96acd48cdb47d07f8a4599190c4e0131021653ae2ae8d84d6fd169e2122c0068aba5279acdfc3f820523ed68cac9b41a9eec523b471fa21bc1f1933e9e0a533f3cd6c754ca639d94e81e9d20ec57ddc41bf36e2896e8b4bb6554af550eca70c23966aa383016922b408a19d83c14c9f2d36d595d3db23e6ea0bb1f4756b9d66b70b09589be524bf4dcaa49567c07030fbf00c2a36dc0e066e09f5a0c27275b734ef88fd68f421e88c03787b20147469bb690cf7c48a2fb1b4396e89c832965bb73a06bbe3fe070ada827cfb5d35904d4c73f4d83c9541d11029bed83973565d42e375e11d0502784c96af59330f1df744a7af2331488f82ba056438f87f5ab4f4bbf24c1fed468ac3ae934a767dce52b3ac40dc345ed546919b7c87038fc9daa5a888b2dd4961a472588f0e5f51547430094fa97ddca10db58195cbf42906c2d2a92a6edaace2d6768a0e2dfa4e03644f2c6523cd6af6d432a8ad80d169f9f62577c2e374589b574b37f27f73f50c14e092af005139701eda91378628b2e4df179afd8448a816eae470f2de220dcd8b97c0c8661e8bbad403e26d0ed806acb13ad25e03e58f184dd0adc5042cf4d3241993bafeaa875c4885cdd7bdc5efac87ab08b031ef62b5aa5e8f9dd098b3ca18dd1c65a4dee935a62b874d41225485bff73918482d69c5bd5c4f78120922b59e54e4654275a89122edcaa6b1cb466b710ef21d1df0878e805d0658eb1c2a07eb4b320fdfa2918685717bde4d12de43df3c47dbabd798a1b04b45e73818da29e1efffe040ba131e819e10570fb3ff6990bd9962c869c59eb38180fd9079673651fd7b68fce3f44458e4c65af1f3c8f0f96cca494c0d09fc0d2b29c34eaaec81642f5769d7004c06fd3f2d3d041d0a129357a6a6136266249f135644889a1ee0145a91d8d2429326dab48d4690ffb25bb02d0c641ee75040b9ba0032cb070f7033f4c7e20dd16b21901020aeafe7cad79dd7040e284578d4fbd18aaf2a6ef1fdcf2828644a6e744750e29c136f1d133800967721524ad735ff2359b9f4fde86fc40d3f9e4a80e1c4c41b8fce071732d5fc181340b8ae13c5c018c47a01c836776a70b2d47905e89b87d1aba3e6cd908c34ef858907aac6fef5f80ad783fea5413b02f38f3ab25bb7fc35d43968d24283216f362ca59ec580c888f1f0fe093d544cc021adf398480028c4034b8f3cb0032d324f3402f8176e4f90ad7c0174826c613282c9136f70093060c1f4433362549ecd14390ca3d2a9ea0db0c03e41b1d4fe71fd47798c08fa52f43d8a90a60697a40edf7d75e0fc301c8b8db60adf45fd9cd052ba46d654a0ec03717ace7ce88be16a2890949c25e48c4f504884d761d9484992d0dc0f1b08a83ce9186f761d89bdec10e04ee143cb9493af1bb349c3aa6e08c36514bf194573a1aa00e6a828656d4e4d64fa68a878db7c79e129758ef25d3ac671c78397d570e7aface4ec2c8da9c4247589242f184227262623969284f9fce56829e4153cc85123fb1abdaae345f8c0667a205d82afd8298d0c99fe75077c24e3a57ffe7107b5cc3a72dc75f1b2c3f8c668944b3ae911fff780a9288977da3664289b95dbaaf0c8548e9660d1f4706d681ff1cbf91db00ab3e8e270db618bf869380ed1bab78cbd5fb576811381d19ec880e2faff2ef235d36e79dfe2085f3e47532c4c661c18aebfed047708ce0508449ff91842ad2a3b28b33dfb007971ec2fd35d7ecd32104832ce997886178b05424d13dff2626bf7848e07cdc96b04c0d91f31a2b32a624b3519343c01909ee381dcc6d0ad87da186ab299d083cbe9c2d0fe23360eccc803a9934c4a697aceaa31e42b357fa0691105229501dfe61bafd39f591af41fb0c28da5bb4b1ffaa7f0a0a788538038bba99e38939d8bea274173d31f72d95a46e4fc6c4a2b10bfedf3a7343a62474bfc23dc0b5445d0c3b8139a3d5a306e654f070a76e3b42c75b13382af11c5d3172b342b758b2248c15bbe107a4c85eecc3233ed3e10c74d6f37b23143e3e7242506380419815f4942a36931a76aaa3f6e30228bda1f13efaeee6038e71ebdbb308fb5bb573e730dc7e15f43ec7c2935fe52e49d4895e9cc1b8cf4451069bfe4e0388351bcdb51460012baec8585d918b35c63baf999f36414a648c292ef4dcf127528301d2ff0965d569b7f6e2f12cb6c6ec349ed6c766873a4ef7c600ac65bcc4d9add8c9fba40c8c40d912fbeeab561d354ab4360359b477aa01e7ce79edf706d80bee0b90a5268c0022a3668bc1c6053d12228d86d9d9168d48c0005f4c691f6c1176544c6d183a60500bc51434f0baa0ec2c4d0c507e929ae372a49f4c6d04e04ea6c90debd75cf74d381193f59fd4b1f6360f9defeafafd9e31e7e69f87990d8f5ee28242f96b8ca5555462670d309e65c493364d0aa3c223b0c445cd5cc733187db19dbd4a0bcca544a4c89d1a06ea1c84f8f851e6b3df45669c7e40d999bd05097e28d2e29e715c4b3b612a0aeb650d83f0d4fdafc236bbd83f7ff426cd386fa1d14e483b92b566dbddaef2cea74ee494306c7078e1703e2039e220b45fa71c6e51e1a5a7d99cecdc914e8376763edacfad1c307b7f6e3c0dcb26341de852350e2395188817468db441c4a44a2e610fae7f0ed23ce2bf2101d8c43afa78792bf0858b5a1da843482a6433f5abeedd5904eb5dcd4c3f3e7ee348303e3c2d4130df901a1cf06860ad44a40c472b850030c1e097b500616a57a0d8e1f30f92ded7fc17048424edbec4959965ee2faa50a9442ce9540cbac0bec88511a43a5cb1bc6a0bba3308c570ed722ce1b7615d7f760494a9cbb1be76bf4178122470a5e911dfdaccbc530caf3680a180eba03e6771749acc64dadfef0228bb4955b10256ada99a016420d44e9e9b0475eb8e4255429c34baf36d1cebcbc245121d1a8715478c8f34e948b7ae8c0817064c27ea8bbe6f3e5737272359f3883a4f8a10217163183a9a8628c76ea12a60cf7adce4bca5b22d9ac154b3621a57ded402fbdc3b6d34ed056f07040b2c006f0278b5fa898edf97c5991ee4c9f5835b039187b1c264087cfee623ec5032e5eed801374914d18f49576f7cf5edb5c9f14e8c6b71b03d4f5d16934795008403120e46945222e76870a67b2f005d706e877be8a8e42945bd0afee4d53dce2c708dba65629c4d550c19b7f50602ed8d9534268072b44483fcd8a23833ac3a19a2118d82ce5750463f5e31211a1c7e62c788f18f68381848fd79821aa385e76262c194a46d13920fe904ddd593e183d68c4a75612eb9e2ed810363893b06aa52b61f05109a36cd30d9626401475dd8cdbc4d36e41458443a1dc47bf1d89cb030361888932bbc2e690bb917248484b8e29680025f991867e5c1b137be45497921721cad512004d8f343d2cd37fd12274c45534e2e1f28116625c6228cfef05aa1b77421fd6a57a2e885e9207f9718309ff6f6196d98a4845bd5d4d842aea22104ce078455ad4ca99a16814f19ad5328a4b79676a36f60ad731f0282afd8ccb8b36d3ace6d54f2bb2d5701438b2b7126c55ed51b8f14681bb3e713534a907db1112adf73b8a05365a8e472a60d415990b2fcde89ac705b4aaedc820a1f286690e46ba43a42d04c80f08cb62094be1ee2a8cc749db590f1cc4eb81d7fe990f7fad1918b087b044ca38f056b6c111ba076735055992a9f23bd8700b1046d07c8aa08be5e0b3c63a60a1d8eea736623b04e21faada1390260c2c63abaa86b8a527bf3c3e48ef9a0ec351e312ff96b35ac7a4c678e46731a8ba0613c7206d7262e691298d53cd639a5940b263bc5e9c775518dcfd6e02a9fa6823b86d0352f47709ad1968b79b8ad9fe9372b7781795d0545665d29ca83924cac2924b949537fa28f1a4e6a19e0a74daf59807e9310f55608420d29e6c62f9789186ccbebe036b4d80d04d4c9df32a13a521d0012e370a0e10d1feea63e3a90421c7f0118f52f1a27645004c72145853dd80e2114fbba8cce1975ff8e2e7a9acd13a6c0fd08622e1afc3d7f4f7c1063415b213639bc208c3ec271bdc8218aad247704452fc8de1c2a933ad8c863f70aab38957c718885041d48d3ca1952b5a6b682c092a721e1fd61ad5b546e0d707aa575728c6e66622f46d8c9341db3f40809879eff7ab13ea1c8baeee46c48a9a8b42fef6923830549fb833577a9f8891ee8ad982ff61123ba3e27d139ab87faded9792560f6f908bb5d301317143544d3a8ba04b725a0fcf10b5f9d5ccc87bc92f91c41a271062169ec6a04a82ee6d8a3ac068bc6ae3388bc7d170bc06861830bc1f0e626e45e3782371dbd6eb40da26cd9f0ac485bb4093602ccccf99dbcdf1b57a8647852ff9317f9d7f735aa269da50f203d9382f1573f4e710971832f79890daef8edab45cc8e7ad5bc588ab8acf13151e484b92bd1410c4babeeca8fd5587629689b65d21971eb95d8f71913448b9ecdc9b17623899629028a4d1417efd484dd1867be648f5a94568c7d40e748fb122e6548a775c5303f2681db6500638eb979aafc1753744394c9a312906e8071d4e8d7611b51e1ab419fdc25b1143795e720b16a93c56acbbc5c5c21aed4bea82b14d568a16acd3cfb9bd0cd040ed06ba2b226358c410fdfb3e828201c8b2f5719537c126bfcfeda7b00444bc05c93ca51e1421bf11d39bd83929acc339e59a4089de5a34734e6f20f6cda9dc64af397171f63d159fd9e3d18324aec9233db2ac31c4686da8f8b626345538123240e3d42332836aefe67fc15b537bb9f38cc6c88e21edacf1340ef4e435a7d5e1dd41fde2a31dfe94f29a4b336778aebbd14cecb2f73ac5af4547262e1ad8a94126e6e990cd89dbde5073d94fec089f4ac7531b2cedc0ef46bed1c92f873a47ab88f80c2dd9e3e50f3602f454bf10c777734f373e39573cf9498433958bd3a8b1043989b982950aff050cf8b006b871abfc661ca9d040230c12c94505f0991fa7a2127b5d60a7bf48f88bb50b4a1f8f6eb830a89796d4c2f9f1c1ecfc41485662ea73bb81ff09e5a5ac79d7538effd074dd92a2d4ae3e7d61cb8b5e13cb803b4919c8c5be63cbe62648c2b528630cd335af7ae3b9a0733509e5b3907ba9089dc9fcb0a38238892812bbd072fdac2a7d1dbee5054daf67cebec9e7675a9d2169df5728b254025d437cc7332f7c4236dc2e8318f0e602258e5932ff607caae15513c56daecef09eb9f03ddc1ba9bd4309e5a3ffe4a9f41b86dd16312ea451e33ca0af0e320f7e191bd0ca0708c6bbf70b835d6d607bf49ebbf08a5675b525beeabff62ee9dd2fe998f82018c4b5b6c6d16305d52b4c2414d1c420ff6da45de2b8176dcc4d571dccf231a876b67315857049e063b763c558a0ad877a97ff6ac438c023e8056b51406f5eb2ad90baf74b1aa4fbac9de21e25dbe0df0140a59b80d036f32eb1dbb79fa0df18a666328a5d2e0cc168170adf8ddb58ef63469ebfb0edf127cad9d32e6220b2f07c166ead518f177a5a033edecbb9c884a290a4b125aeb597c897f5a0103063a8dd3d416a6d7240abe93586ae5c46f81678ef757aeac296c979e96be301908def7b80eeff9c971ec8bab191b917acfb0355640890c2f743b552aca932d8967347785e04b59018bfa17d3f548771099620da765703a0000d5b2e14fba89d05050162cf6866264489f78606b5a0bd53fe9dc24c6b0869c4344408042385f103aa2b78c47533778b1330ed0b8ef1453093153271cf2d41e6560d5469dba830317cd04c13db717e418747ddc9c600cc5f466441de1e1d3707ad20a3675b9e74af113d7ba4f8c154bafdd4cee3a9437494ba6178a32b8d066c97abeaddbc36fbbd1618eed71d2b0cedf05c15bb63ac1fb8f671c7302e3f331fc7bbfa2305d379f3ffb9832fcd2e982a5ffcb1b62f5aa909391e582cb70c590f6c74ba8619334f2f72c683b7c7bc06d2040458766212c9a0e35099e360c605522189616924b561a2419f6084b3832c00d525d1b3d52296ddbce54dec0b2256ad92e202b646a955f05612a28a1ec56ebf6ce82308016a4d05b9d32f5fe9d37d1cf8d9a47e81d9217c0674119a7314dc8c7615590a090828c41324f69be6dbeb7a99a33df266b6059268c7538db8deb6907aeaf1334d9f0f3a24d64485084e5b9586a3bbcccee3a013564eca8520f5ec3f8f7bc0dba79e8211ce05b48f646a490e4cdcb3f79a9fd75699bbcf04719533e2ec5bbee4c4dc0345b150e1ca436c88ab964cd1731eac03bdb41e88e367886e407755119fb6dc8b8671143cea3d8ab15531f601f3f557ac0810886bffb2d9fd3cb74051e5950b12c968ab2e440294be32e8ece144d11b813d14ef9d3b6a3a03df8584fa50e5ace800044dd742a344892318e542ac4351e88ffa9d02a908d26a173d6be032104a89f856f0c716a447dfa40aa0117a5c6ba0ee4dbbdc3b6b53ac1cfce49d3cd49ab98d10c84387529381ab032deff886f679b2d3e7762fb439f0264cbe86e91a2a8391a2a0dc1b8427cad43bbac90a27ec1f74b421451abfbb7fae8d0f6b0a1e5d3a576eeffd5513116a1946545ac4175a454d8f2a7fa63513f2681a1b2e0d9afedd8ad3f564baa12ef18f695c7db1809ed51e661ce4664ad0d47f765b882270b40dfef7296e6ba8f2cad35921acd425db94fa3a5c3ba06a9cf6d875622c0ac7cee44a49303dc3f6a75399cfb1dd52016c39bad163aef26953c512cec83dd504737a7a70c6f332de90ce23bf42f5f594edebc862faced4b149a8e4cd49679aa2063e27c23995a4393ba4b32a70e3b70b6476ad615049b14640f15bb831a4912426a6e3a7818cec49e4a5988c32595ee3fdaa0ac62f3e875345fcede47bc1e7f84eddc021273f3011865497c609a022df0db9444b262df417a5ca814f5b7fdf8419e8a8e592802fe0b90d32d6c76af91b391a70ae4cdccdd554cd57006cbefab7bfe4b6fc0877e8f878ba29e788e566bd3cf3b554f2cca2e84a5da68b63c18320f5ab2b5b929acd323f68a2c3d4321162c5ab7495930907bbe2671912e411a3388bb456085419ec885bae733ba6d2017d5752bc584ea22610f64c71a1955175d15846ef5628b0bedd070c1bd2d461b85ed9163400c77d093afbe7d7b9f2cadd0762ca0765676638501bf5d317a359eb73fbcc6179264150c711365948db1100601b989b0f351d2dc168db6c3a08f20f19758d819ba8fc408999cb4bc48bc4907477f1992712b3a7d198a7c93f7c9a121c8a43f4d2b2fc664e01b3ea2ff8eb1c5c7013790132d8cf93c92b0143554dd4e3dd9cf880cab2cc86079818324132a91d9ae4ba5ade7db9385a4f73d77f504e7486be8dcd6428939f8fab71a81aa35cb336c2ed0d1c24b52148e25c1dd8fb1a902cc2eb7980d40f50eb116bda55944ca6fcfe9061e4859b6418f0c98b4592e91ea0e577440af1a65a88e44bc8517d78c9158ac10af04faa237dd5884000af73c8770e298c078f95c86efab5c9025e532279b19b9b357f3d7bb347a315bebb85d473191114679ba3b876cbb8ebfbaef76056321da39a1e01137acfe986cffced81a0d0c6258cf2bed02b1022f4e9c283de11b501d78fef8243498fb51fb239e3f798551b5b60c9a800c752cf47292ca1fabed25fb23a81bee7ad6e0fd13f571702a2112f2f75a5bd3d5c8fdd23822d98589416eac0f8d3e7d60e067dea20aad17b1d98da8b09ff6c887b58e91fb7629b2a996703aef79b1e945b3ebc8e2187e89dfb42e83c6217aedeb8cd58ebb3c76efde3ea7c01567fb3e6787d777a40b83145a5d2131df84a071bce7109d7ff4acfd85b8cfe7fe73581d0ff0807a97be150257ad58343e9620b096ed90c2142ef56830e02ccf469a01c84651bb084ebad8e0e36521643749f609ddf0c00895eedb6df7ec24b4f453ffc9b790791203b303d80bb6ec2d99abd27ca56732cac5015659335b614c36ea0ca732110378cd0333a68d48815e229eb25c4b13ec5bcda08aeeb09fe75c3d5b3600f993baa60602ff71fd7f40877dfab9bdf4e1ba99091ba352dc04394391dca21d89dd55a1a431c1ab5cea277401543f49840c8fc1081905ab3cd1b4b22026bc4161e270cf055a6f30a6bd68b6516c15380939181a846786f0750f0594b58101008ab9ad79429295413a9adb92d12c2e95118e70462ed82eb43686c06d202af350a4b4982862616e97483924df1b86a2e2a2d20d28565fe50ed8758edc7161e6a777a752ea28a3aaf3c75b0ca2c4fac7abefd2f556fdb1db8ac2006c0bf636d855b752584b88083c4121724d4a3364e8fa925e51f52b348b79142b40c14a85527170c73ab1aaccddbc24fe0eab2c0a5c10d7e3319ec9fa2116542fa04989295d01587aae5ba083284bf13182e96ad0361c05679e0ffc7b79c049a1ee82d2059a83acfa625816289cb12398dc2caba7620efad96603c9057d3abce3bb8bc86139cb0255979606653f01305f9e68c779e634ae3703bdb826197f811169877af24eaf6f40b0f4944745fcf9a84fce9cf6be17955e17742fd033e329456ef22014db3f5c2a89ebc6cac48f6cadd3be8132913d5af2364399b78c62a314c8d17f70d226acbda1fc9ddef3eaa364993098729310a09c2a3939eb1ac6fc913ba860c02b87bfef6d1b4b2e4f8c9a739f2795517024a3ebf5c832bc30415372c85d7ac88f0b1de497ae02b3d9d18f18f7802ba854d840e2683ad0547d4ca638e5a1623d9c71962bf7dffeb08ee66c753e98d533ecde3cba0ee8ca1e1e3543ba5db9638fd99bc4487a045efd5a044148bf8b05b6c2801fe26e3c073833c261e55effa5a55c31133fbf46b6eba41d95b4faa563f54d4bf597181f9edbf29e5ffe6e9d68b1560e9393320cf48ace3c1bf5eb7f989fc22bb0022b6d32ff02699358246d9f2b7674c6044467e822a97d0e48bce5c1a7039a5c663cde8613a7ab595c3a3c9cf190d6dc7a7f32f6f3325e3cf4370d1b80f865719ecde5ceb20038bf4989b02af9482a25075502769d1698b4670b53c8648e846197a98ad9c0b2ca440edf59afd52e26516f3104b98b5151d437f57efc216f9f8a67673fb5ff7a32013c957426b4f5b90f4f04a484cc4fee13352fc9259f1e5216e85e5096fb0c8170259226a64ced71b2fc8244c520958c07f4ac708c579c53560ce9095f0f7ccbe829b92c89009d7560ed44ff821f308bd1cccee91e29e6473a97141b967273a54f726e2a3cb9bde1c9019bab8a87b0b88da3b04aec3108c1b412c5c6d903b80fe86de3a2c35e11d67ddfaa452826b59c85e9803fc28d3512cc849cd6d27694c93aa7716e95c2fbd6967e3b430735622c7c66bde38b0f3831fb896f431121569ab7be2bd5d72e91ff3b34254d8a0862cd9024eae67d04f718921d8c7b49647a00e32ac948a882e41fefb75ed832793342b3913fbae4de415e3d0e295ed3cf433290e2ba4f69b517745fc053c398ad996e2b38c20f9e326bed15da2fb7d5c9f38412b9be6ab653ea9cc8769130b54ea8ac08d4e5fa93746a31fbb0027f59095e96e04646786adb8f7b99065fb3c7c2c9cab6fe4b187f8c56546cca892a68fe9c2767dbfb438bbc74d26a1e1e50c63e269f44ab06b63c1658567f5a599e0df472aca05356f69e9c682ad9f4a03ec1efa63e4c5de28f16b84ed284a1078712bb01efacb8dfd4ffd9869abd87c394bcb47f6c3add8c4775a9dab189a7a41cf979fe664e160a48b1ea64c3be33897cd2436a712dc3826eed534839e4c25108db7e0028d49bf29bfc26920965a941845b7670f3d05cbb1c74f282375d804b1b38618958d4634bb07c589966d0d457f9510cf02b11006994eefd9b367a3a088fcf14b6bfe4d11499301ce6a6a98f8ff4b43e65114c4d6c3e0864ac6623cf6449e3c03ae59794043c8f948c7a79715590041e5524663870c97434c03cdb4004dcbe9c03454bf32466640e69a843cdc309379fc00bce0413bbc03478e47cb221a25517e77462e0048a5b0cd9d9fed190a480ed60f06fbc552f096a308e7461de4359b43441de194b59fdd383fa774aaea410f4b7e2e221f0ec2fa4c5ec95b83d47d68031471280b14c1ca1206b0ea25963b5308b910285881bbcfa0b3389b9393aa880970b25e7bae93f3e0057ca6747dc1793ce58cbba0c80c35f7698d7a78794e05875c52181139da7737d2f5c3481efcc27d5764dec36f15633b39f30e9a324bfb4818ad0753a52d706da5eb02cdbf420f3a9039839600e567b97c66154745a81a5dc89f3dbce7524b4cd73ef2a958812209290c4039a2fb3dae8e88faa2628eb6812db6c135df41bc9aed9049968c92e0ac46943c8b0fe07dba9f585922257da3d74f31b5079c5be278a5b8a38735207344c96e177893dd9d5956d3f04e481d710ce5df35e190ef77d374519b3436fd8e3e576a82695707507bfb1ab65462b35a4aca808fcd4d4d652117cc89c1b4b1de9e6c1f38400a7566538f5d4a1b1101ef0f5a2d93eb5395d3e771a5a4ccb0967a037afc06283c5f7f703bd35c643b2bb831b00d866f80e4600d773ae2511abc49697eb21b6093a900ead0573c4e572159630af123f8cf4633298a4c29a360a353bcae2d66b763cc52a4dd8014946f7e1aa82b05b2b73820fa9c7e0026a96460f8ac4461b13458da97733e7eac1718b2569b1b4532d6508d311399a21b1512e9375694cf3e7559efb8fe165cfcd80fe44354579734da275b153c0eb8f6df2f64f49ba9fcc2b14c318041a7c34b24f861f5b9a8948424dd42c68830d657c747347c529881f60f4bc270b33e71657d4d4ea258e32605923a1e764b2fd67c586528681e9e01ec5de9e3fb2e760478cb521ab9fdc36510790012cc0586bd13d462fab0e8589bf611ee7137524998ccde166bc2447618c53f57529bac1617896b327436f8c26b4eb04cce39610341e2a21052f8f3943b1d8c5f4f182143a101abfb9c908494fa3721915734244313d28d5e1a0ee97f3948090fedaaa63a62844162e6d97743ac3ef7a921e553757e0bde78fc3ad2545ff0d8d4047402b158fc4797d3fcba6025b400c6dcb46be0131213149cb41968fcc747a0ae14b11da06ff03e046ed14dcec0d1c764ad0b6c8a374b9719acb27aec2a1571c0d205af13ae8c3bf474807bf71ab1b4faa36a4c0188ba2e72ebefc72d961bd81fba3e172b91b835f1ab6232d330875bcfaa6f584ee3513388b3bbb3e88c7ee05dce1a71d418bb03271541a25fae14731912e271d381f79639ae00def0cfd0418fa8612c3c967e4fdab613959a003ee505d027cb107abe70777eeaec46852ade759a02e107526902777eee713a9ed8484331146600c23f2482150ec5506477431420ebfbc28f9ad12aac95e3049cd633ae1e61101b08155583b066e7fb35e4ffa5325fef028fc02e450cbab597f86f8591a677893ca8c30dd68062d5228ec5aa91d233f368fabadaea53a718e88bee780533479a4f6d30c74da9068796697ddd6103de915c8140ef075e53ee8f40546d5e5194e3669ca4b254caedb1caf04b5906b2f8e5cda7f4ace5ea154727d0d2d7226ba851845849d82c8e96f5c3cb551f7801729598cd675c5ffde45fda7fbeb329fdc8c722fbf437cd5c2f909b7db3b1740f51f45eb99c5360e9118412ecb6e92eedede9da371151f29a6d3d5ab2ebf7cdc1f4f072936bc4ef41e61ff3f709b1ae778800abc7250d7011ff947507f342b7c2d18559ab0853427406c4a18662425a33b6b372d211e1abac1660ca21cc3ebecd612f43c9d8015345ddc53b9bcb4fbc1b4eeb6bf0bfbe0646ff58dd42f0ce36a69bb141dfe8102ad31dfa1012a34eb955398c101d147ed6bdace556c73fb28d191f88e58b60980461fb2f014f870d23879bc1b7af8fb9fdd88da93a36f5b07c7a6078b5565f9476bde9a977cd294b9478c9d1b3496d7db24f6d0edf97f4add3c12dc7625254d1783dd999fec6b2f34c9c5cc4c33bd32728360dbc37f0a904ee90653d30e8e80f125b4763316e0822bc289a5a79c85d6c0d5426f876b72636f5dfa9c23574cceea6c217265b5c3b9d429655177694bf4dcafcaa870eae20426b7cf7bf0e5ec55e3c5d9f5336b66824c826ab504f589a9bd2fd90c1db26505aade6660ea6219bc3784c424c68401f26e19989e0d1264d8cfc0ddba5e431b0bd76ac5465956516ae2a3fb584b7fa90100b2265cb51a6bb9a11caa563e57c3a9f67dacd685b394a02bc1a17b4dad91d04dbe19c2609e75ecdb6d0f28ca667e75e04a0bffc9993055316439f605f922c1795b98b64372a597337db314ebff307ec4a1bfb29ccc89d71d2072f04b0eff14b35ea8d21d44d093362efeb9e13e8f32512d8b3b8d1f24a9ff9b4bae5c6d3ff31d749db8747155c3c7f5840247d4d0d53793cd2edc65ae5f58a93d88b31592fb1a0c4b456693e537045e7a9cac1a22d00b1e944b1f2c15f8b1b9c0ccb72a4da5cda6dfd64cf2d5118b0b699632b5bb97565b2f0e10fd911d0f7b9795325ca7013175869d7106e3fadc7b84793a57c3a61ddd7a3bd16835b31b83049419f6d005e4d9c1773d07f26da6efc2a5f5f666a5e4d47ea2c1fc491917a0f3faa886f26583e693ab32cab599e697b2c867f4d818d7a8f2eb01474109c46ab7efc69012ceae32cd0aedff4295e5299ca83409d7219d2d30708eaa32b9fd759d40c10c5485d2a1448692be5c91484452b14903238a0466d5d8ee21d184f9c5bad884bd80a342ba8110d23bb2a5baa53d07ef277f7040adc3565a53d602f64ee2b3f4dc19246aa7d91b9a9ec3408569a97f642e69ef23e6d79007ad503dc00e2d48804dd7e898f37c5b1f4fe952d0ce58ca78d73dba54e2941d4063cfd97af530ae0398507e4fd878a3b67376547f353c6066437a7ca009a273523866d0eae26feced20efbd05abdb575662e3565f9f6ec2fc8c98d07be17027fc1ea8d4a0c1a0005c036be17646a8e0de6ee1457f58ac6d109899b853b4844c213b6560c0e92288e83299b96d4c1482c097ec21dbaa64b65a72dfd08af8d71831e518611bb0211a281b2d1121803eed5eba4048735353cba01df2c80f3a0a9aede96b575b9661938083fe0913785fc68c82e514eaea6e03f503a31b9379a050913342c1bc534bcd8c0296659a23c3aaf663e7287ff3a2bc33b14a8de0f9fece2e39eedc3c43d3449d2077fe7a4bc70a7306c958499b2eda643ac554680091cd74d00b36f94a63c2a961cb2b31db11d83155c933e77ac07f360280c39191e7116850a5c15c730c39bf82907596b2326998202d83827ba9eb197857840fa5de7286c90e3d75ee846f1b53e19e0acc04ed6c089cb58b93790032c1fc73e0482b73936a5f7612b03b5da86cf4e7d9e4b5f6b32196105a6f265cf8c35315605c8943f39d8f5a183e35d48b055f6e45d50e5b15b67f50c7e2dee638bfa2a654acab0a5edacda2fcb4cd205faaa1b4b8ba12d1d66aa7da9f7de6734d767ee5bf67990ed4e32222ca6d0f534fd4bdf958568cd4c2dc45cb5cc5e772ae018fedfbb46fdff6b266b6a57638acb9789c76b752e66dab4cfec944cb703a4432b37991f755ef8f92bdd636620a8723fb047d0de907c7bf88def325795d6917f029857c2f54ecce8dc91e537f94fd6e8a1fc20afed094c5150bd3444ac9cb220c52fbb704948f122b3aaa933cea344acdd084c50cc3a66805c3b8128abecfe4c134e551a7cac9069648edb43bc9655baf02c1ed77d96f3600ec085286b38c76962b49a052599a0d599f722db29f3962a41ec0816f1b2da654b43c62471fab16d38f831fe33437e3fe05060ab16c3f97e47765c0e7d75bc84c2df8c1cea19f96d9ba2f0c9a2246d521b1be753451d76dffda13ad1cdccd6d4142b821749879c1fb287e2667dbb26b48112a7758000275606f8dea0a6be2f9cc16eb5671ac85cd14526699103e33831cc6ba4434dd64f46239b5cd1f98593a8fbe9bd98d167cd5593275819c815d4cd8e66cf9dfe472c25dfebd3d1bb0ec22668131e94b0abfbd45299b677cfb1cd99164b3503926426e7acdfd5037bfc2b9fe466f0b460200e413b43c60df4d88217325a323738d6fdcabbcc1066efd729f3fe4cb122323a879a825b2989f61350ed0a8912e0e37deca03a1cf1a94040709f67c759640948aab2fb3bee09b8c2b11b0d19f697c7c231fa7459b55a8e7126f6d43d334366e508a85c209f39ab2673369b443980e5fbcbdf1d88e16ecfd43c8e1b094225b923a044091746b9c3009fbc4cec0225a88d5ea6e853f3877b646c7a250609aba782a75de516144f26f7d3de21af5e15a3e4c06e62cda7dde7bb0a8955caae396b0ad17c35f1ca53956f0cb144b2e554e01aa6ed1d044ff5093671232a2b4017a2195da0f08bbd940ddaf2a7f589acdeb11bb5fed6fd0fd1817a4daaa1f8158ddffa2b69824b2fd9a9be580cdbd26617cc7e09aaef7a85085dcb357f558bfc63393532c50f060abc4ec7b1b2fa3f3c60664a88daa46789e0c94a5176f93f57e0e5eb2f31d067100ca8a61e62269f41639c6bd81aa0391e4b196c2ca30bb191904b5857c06201e71563ac657909396e96b22b902f46586b99bd5a359afae81b57f9ba8e98b4cc7c9db10e4c2ee5f7f54e8d1db458246158f304d107d1de324b43bc9c3571fc8c5f1732370a0d89e748403a8ee30171db409806208512762d135c5c99105b56c8c6257d311815d90e5c0d64723c21447eff942920192898a8461551046192fd4651e874c0b5a84885083a0cc7848587a137fb9340c5753489f55b75d5354393f93233b2c47597338225c7c4c7e5ede4a7104f185871600f9d6d5555372ee470e4dacbe42daec201d7c994f40d0a61d5b9ad1541a416042a80df1c46f8c62e642057b48e5c6eccce93e155f0dd874edee9a5fc1e57ea0adcdb9a86767265a7047d77f11b3519d9cb0418917377e2cd212d05bbdfe76a14c57c2344f5d03682bcd988fd6b2c64829d3d5075d81e8d74ed65cf8b9111727d00a5d72b63c71c57f94d9d6864142ec6468abb0f0b4ec2ced12f115fb3a73ff83cbde174729d1bec15fcbe05a758995a4b2883049f7953149fda3fa8e1cdc4f159c7aa29d705e4a751cbb1053786e70109ad0cdb360a7264a2cac020fec720b78fd807e0576e982b772e06e2a5292901f3a4879b7c32f46f1ba53a265c22d79ec07ffdd99be88c1fab04dee98bb199dde3d57e78915fccb40856f5f9bed41e8e3f4168bf6e391c4ae8e47fc4065448d9ec39e59543d09e09a40ae1d06594910980a304b23fa6a97c716dcc8c6f61c787fcf59816b294d5e7585f08a2d6d50851a0af9c93ee470831f4c67bb272acd5062c42026f2e6c9564503fcff6c461164a259d10f06a1d9ba61020da55691710a028ae845e18dc3e42ca8840069203bdc233c3000f522896b500348bd5a8733fc81d0fd069a4e6993e32cbaee671b1ca8c9aba794fc7d664c1a41611ca1b51c09c76f2d5b16d20bd00a401a08ea2e30ee8a7aa1e33e2596680296f1eac08822b09f9c4423c01882070f76cfcaabcb849b0b77a041ec531ea9f9c67106d48f708fc9e5c98b9238bd24668850da56a5705c72ad285efad44ba172e2372fce1818c466648b3963d1ed6e2889d87f7693446434a21ae702a9e1ccb93340274069c51022a6cdd4b5f400cca9a0ef53ef6bce4c1429833e2fdec33487d8c2b8024a6cbbcc15382fc21e9dc842d967a911d63ed5f908309b427cf71db6c3d3077e36b3589bda9d18e39a728907f4270e0b6de7da335df4935438b9be76f996d72ef2df796494a2903ad047204d104b4274a0011030f295c945010a3c44a1494db858b0daba916b88cc172c48f1a6c9f4d4a0c71c28dcaa850d90e71900c1f7206e61c63cc83ab0bafc5f7023189491b3346176bebcc31cc07639df1c6ac9d9934e53a634e62920787240f0e4717c7240f0e718875c658674c8776148d2eccda98b5493ceaacb3ce9673bd89d534494ae8cf67edfe528ee39df820378532086aadf507f8e2d79166ce2a6201dfd62b3ee642029b20feff9f089e73dad7d47df29da42b56009d3d1f80f3d63a7b6815e7a45abe9ba69bf6ffffffffff28c84cdce75f7eefcd53652dffff7f21d89cffffffff5f04dc5334e85469870c3a55aab1c05e1a59187492fc2f09189d4679b10344f760224d39ef25e5c12ebc16df0b1c3b8da2611774aa84a209162954b060078876e9153558c6651ed48b9de6a065adefc63bd541054e8a150ad60e10bdd36fa4687ca27b24d0d961e51d203a07c5aa53259c4e39a822ef00d161ff92d07447216fba76da396c1bb16685ee9483aeac22e71983ce9ec92baf7142d3a9924bcf741a453ba93acd018fd92688073be5e02bef00d15f0baf530ebac83c39d7ef3a95edcf049deb94832ff25662dd4ea37ca71c246fa468baa1d06a31a81968906b9c68ca339e342fd84818e40b3024d94362e1296ce4ab3fe89355e4d442817cc8275f75c3c674bb713789ef3a2edcb556ca061d72501a642b0a1458f2454751027b6fcf73fbe2a9e298a20ba7af4e5df419a407413c2888e3382620b8a259fbf78a8b707395561dfeb9575dcb3de73c0c9f885c2336e271d1420cf7578c1d2d1f499812264dd3344dd3344dd31c185ddfdfcec33c080edb9833c0d819ba74e972e2ca959fde9cb00bb3afca3d11b6396cec15468a70abb721be4b972e6f615f606f874e706363e7b4d34e164e4330f1ffcfbd12fb4364c173fe7cfaf6adb5b6b2aa803a77d8fb28d855325c77f1b780b155d1786bed73dd5bfb177bdfed381f1646922320ad059c701d8c2449922443d800d9e58d843d18490621bd4f939b9c4024f91ac7dd6433d94bf6d1565263439229903637a1053b89a67c2359e5c048f20264ce86912409fb416e1d1849fa20754e9cd849b1677cef7d64d7b069d846760576913dc336da44f690162ee648922e785e7fd13c5b861730b6901d64c7b061d8329f0d60242986dce007469262c81f8ee360a405faa971e1995c84fb5e1fcfbd17d788f5e671d1a207dc15a1001b33f6de7b772063e4555c23ce3adcac22dd4d8ccd663b2175858989d21bd3f2258bcd66fbe1c6264b12a92c5398bc952c4926b77d4e4a299d340b0ca00f59fbc075d87a8f83d4950e7b96bb4b70d92e6c0a68d94036c0f7dd3fb68fdd6317ed1650a8b259d82b041122082490d84418dbb60a3b858dc21e6a62052a284386892a945a2b29b096cb98259498924202098c31124950ee1363f1b87011de4baea0416cf2193c24df3b57c1203639778d586f1e172d865e316a9f576d478de9ffd3266b1bb70c2ea80805d898b1f7debb031923dfc1159b9cbb469c75b85926d43ec3c1f75e5811fb40a0cc27277d604674b5425156cc8b172f27f64f2c2716669c1ea8047062a4138d94d96a433014c626c7b41a06460312aaaa2e043442984ec38b172f230895007b4bbbb1a98941357761294e5a87acadab96a15de33806ffffa250ee3b77d7fc17e99c02b710ba723a3392d5a2be82a6656ca430357233b473c38a3b37caefa2908c1eea806bfdb93accff611d27d0554cd1348761d6170527a018a1cba73435747fbe53a3eb76e7c6ece9f63b68b3b54f6badb5ce5a6bcdcc8bd502a0cd52b5f9edb5d92185ca3d77ef870fa0df2a71ff7b33cb9661e778fd174b8e77619fbdccaa0f6e3aa1f964a76c0ded09b74d4597425466adbd5f7fba2cd4534a69aee34f30de91a6996e28fafffff9ffd3926be59f3de3d943bf4fd52dc1fe2c68d036b3ea48c791a6839f1fd65db6d29f1cc76be7c43247bfe20ba10f2408f0699ecf9f9642577c4da75ecc546c82d0e5f7faffbd1492ea25d49e6b2862bac024a47b2fed7a285d3a73e63c627786831ce43c62974f1cdba951633706164115d9ba960d1c74888816c98a92669308b56856755820ba04d5711b4e389eeccb825f8b0ed122fbeaf8bfa03b3e7b39a174088df033f68e47e0a9779cf4a9b5e0d43bcef38994068ab489f62a9609e874a98edd113d6333c6f172428da405027950c7411d4298eaf0b807b63677390eca7362c148afe3f7899afcb0b8edebeab04044377cbce3235820a21bbe10f6056186d3883b403e2362150899abc41d1fcfe2b4afdb759cb674ae4d3e1e547792e8ca33c7611f4433d671ee823cb78a78ecc394d6e196ae1bd025fbc2d8022159a04f1cbb95185d52a3ae9bf52e66645f1ddfb96180b8a48e89de71da548146a727bde354cb9458c7e996fa336b2c8ca07761ef66ad07148d75fceae8bef46eac6397a5e8920ed18e53a2fa5379774303141914da47c4045566a50251d22cc9caaca160c4d61c6073ce39c9916f6badb54e24781fbc1f4adda793297dd69c08e9d38952d77dd69c1c75ca0070ba4a4af6d7bd72c039753228270b4474437dc2964b07b9e5158a66819af027e2a04ab9475be111badc94833c54abadfed76f36929346d304d2d36aeb9657285ab75ebae595567f6aadf3b6b86a39d72aeb53afe20ee6d1edde7c24f9eca1f5d62d019290874adbad963a5acba7689fea68390e195d569b162ddd726e79b559208cb5de9b735c6d3a49467ecebe63451542907d7d0e987d7d08f589e47aaa728cf665778e3107b7afe7984f150eb085edcbca2a508e9351fd316259203da9409366b955b2dc2ed51f9a6eb965aa3fb33614a65b9a6dba3fdfe3df27eef8b82762fb9ae0dc3ca8e220edeb39286e8e97b01b289ef6555e8e398f188f18c8b5e8987e5b7508aadb59bf2d8ad4bf5552196feab7359dfab7bcd60e08de0eed8913b77640c03b361679c4c22334882d90deda5a6badb533cb2d926d66ca84cadd7b6fdc095c7a29672b5501529f4d65c0740cfa6c2a23d54b9af49c2b31071da7220925e8de71cc2bf7260cda53c5127c1e3da973fc551edc741166cf731b11e8520fd14bdab95be3a65f91aa0c4002eda5ee3c967ac971a1e9c4a35bcb678cfb01ee9dc8c376ca6f151a96b9bfd07c2a01afb67bfc5d2f179173ce39678fb5c3cd0a511c11721d5cfc769efd401e6a5c3b970b45085c5bc88fd624fe2dd7fd7d8e2b854c325fa268000328b080c4cd862289270146ba0c3b4d67882ef78ca271dfac156ddea05be28a5ae7ac75d649e909b4ce6460527bb94e8912cffbb26a66a68aa69c464563ea9a1bd555ba4bf5260c717028ce4d4883b3c259ad56e18dcd6a55a357ab15cd6a46b55aad3c164210a4b5daff7b39aeeb30f66e128dfbe443fa40306755a867cfa42da5999169347b66326d5d231a578807dfaf6cf64c968ac6aed13657a98e935b7a99e8bc4b57e9322d5da5d58aaee64d485bab1c9c9c9c9cf0c62627a746e7e4e4d0e4cca87272722ee89e56347045035755be8e93874648e3bfd6346446d6ebc75d321014d4e27134d595ef5299d901e4a46855c62cfdfaff35d6ff50cf5536e0dfefd2efd2f4f5bbf4fecff83fe325449af5faf7a8ff9f470b13b6279a44570e226141da42a5341212d21214921118121032569ad8c01bc7df84088e39f004dd7704ee823310a0168fa3a9ae7c97cacc0e20270537b9a10992cad28840e9b08991be5122fcd2a485112ce30ab14d56c510a697990c431d70b63c59e56c1d1b2698746097a0873b637e043b9973c2e147440a154852dc06ce44898915263326499cb381c4a2eab3c62406550ce233a5b0e2876b891413a22537d687e162c1c862c996705c8245e9d5628c9116de14cec8f321aca337e566099e9c2fa097d490cb2c31d2e1d29391c02c11b28f6c47b7232b5a8ac23a9ea048ca7c44eb95e7670a7b80914128473684611d8f841c1d357d503525656a4740faf837030dbcc7616dd123adc473ae377971c7093c47583ba9cdbdab1599f5592bf2e445054ed0257ad69cacf432db7b83c83a297dd6820cf51227f5dc27d8672dc84c3643939111941170354567a3314e8866507dce6c7dd6664abddc7dce38d06bfaaccdace05a0d37258628aa25a9ddbf499efcd0cbef498c30c96897e99c407af9ad3e93b0d0b1f795a1ffff1d4662cb378ee3b8f93404f53eef43d2670d09961a12da9c25ae2199d590d4509b95a921215243020404c1acaacd50a0972c3d2559a297b8c73093dc7a896d12a81ece257a10d5863421551b72b381880bd9d6cb5d1be2645390541b42ab0d11521b7254a63604c8969a0c61acd464d042cb31839191d10c4a523529466a306881e1060318a6a22a039131569ba082360b72e0efdf2c3f6060879a121c9ed4941cd59414a9cd005553e2424d49d1a4d668294913b6ab413527a57566a5506a6b47865e5aec0c4f48b72323224b44bef4592382d49ffaac1131224244fffaac1169e2514a5272a2b2a79a4c863e6b32207dc78a9ca67b3d8317ca8441c2a1005194a1284252fcb87dfc6b915082eedfecb11c5318765fd197ebb8e7b8ca7b0caaf8f77ddfc7552d089cd03f3e836a0b02b47f7c5a1e96ab35eedd346962309fb57ae2ac63adae3acebf3306c82777750dd54151ad28c6ea119b7c246141362cd0c7f2748d244c4610e5d3a6799969e64c084f81267d7d7c72d0f3389f3f204803a4e11d40d5bdd6f7e9ea94f3f9a3e2f89b113d1a9e4b458366d50ddb5fe561af45838405d411734c8316127548e98fce9ecabd89710c382ce1b0a5e583c9729026fba043ca095da34eb90d437d8dbab562259302891ab5b3acb8f39cf6dbadb52fce1fecacfffcbef7dfaadfb52dcb591c57ac424859a1ebb6aea0735819f11887a15e42edb407b5f7d61c7221d7d2974381e32c0a4df4392a560e773a3d1177d4de21549834e5f39bd5b1fe9859da2792f88b3b3702768a5cc5b559404c83a6fcd63bd481395598735a5b6b9ddcd65ae7ac5bc06a6d9d3db7fe5faed65a6b0fd0c6cb6bfcc08cafd76bfc4630be5eaff133627cbdb8cba3baffffcecbffff7f8b1cddf2af5f13be0e63cf23628b117c6f87efa519e8bada755baead7626f4e7373e30e7231b17afeb5cbffd3bb6dddba27def8dbb6bc2873707ddc8f1c59ee701f056a0e31dd7f1a0da4d5acbbbfe7030c78d7f6014775c8739d8306ee422cdf9c81dfad61dea9ff4502a4f7adba5e860c59caae8b7a37509d8008d63b9fbaccdf1cc7ba38b27e4faf57ba38b270c63bc37ba78429bcb3dbee963900d1c160887b72f185f1e6f4183660c6b671c7ffe5adbe39e176a990c1977678caf3f563f1d47ce72795ef63cacedcbe3e158436a19b0208f87630da965c0823c1e8e35a496010bf27838d6905a062cc8f33c0fbfe771ec43c2fe7d4898d65a6badf5ff10d96d1e3d0fe7f736d7ff38db9e198345f7b8372388a57fe4217946f25fe7c90f8afe7fa2cf18e1fe1ceafd015881400038fd31fed800fe2bd057b086b6e08ff65565acead21e67b95e87fb73a8f7eca1372a01fe1f865969382bbd99e6dc671dadedc1d27021d7f22177d530d88c31b25899b309a2ed39dab02ffd242b65f43026032f47fae4ea83cb460170c07e2a8d1bc999975353114f408a8a748b0a65ab51f566c4c8c8c6c5501df3a3e8091252488dac9219d36863f650eead72129539923eb019c32e71f4e6a3a1c2d4b1deab73e3a940f5a8ce58556667cc181de71e56842e793aadbc6bcd202a83015d62dbb916b5d5b1566ba95824bb549fb63ced701775a25a5cd469e42dbdc14e8995cd6856689ffd31c616d924a5245594ed363b9ae95d446eb240dca3c1544685f88cb0d9e31ad35bb1c9c3ba010d2d6d591d427942e9cfeb204efe5ad587ca6b526b8e9457ae73226f85b08002d491e2983d94cc216f0b0708addfa946e18930058a16e6d8b5e282fc379905a252b5a4379d0bd4c8b983af9c41ecb58872dde0e33a3ce819362606146cc1bcaec38472b0c5061e7041522c756caa230cf045c63306ee8161a71dbf492331609181053b2230c655815cf5455d21f659419630c2bc911be83cf9510194020680cc8a017270c20122c8d0d902c8382736f94e0774893b5805d191672cc7b167571d2baf238f0d000469640cd6c89347c0567b030266ccfbc09d0fe8728e69b5b07d157990a76c0704bae41bb4c1d1db98a73a3c681ade7802d80f32ae40f4566f3b4fbac45d830c3020b98a142860bce8e171d1e2355ec0c202d70a50fc38d79b7ce28e13629ea565004454545454545454d4d4d4d4d454545454545454d4140f51515151514753535353535353525252475151515151513629292929292929cbc3d4d4d4d4d454f5003879b75959a5dde84c4a4f5a6bf591e4e9fb3a046813b367064e9026d79f7cefbdf7de7befbdf7decbedd79fd733869e3df47babb2f199daeb65ee13670fe53495f698f9a81d2933553b22a676e4cb076a479c6a478450aa1db1523b9274a326f822438ab89055458a7a581404ea91e6ec09f2439d5130297cd21616c4b75959a5dde8ac0a08d2cc596b0527396b257d709f78461098a78a66864667cdb2b9a935ebd9a463a44bb2e4ab306772978e912ef1d409986da8b42d1be990d968000200e316000020100a068442499424499cabf10114001055883c62583095c843a14818c6611045311003310c035088310819c59872aab200b3d97cc8c5e0dbacb43dc9636562aa728f32c3c4a12ea22b2b4432591a1b6c80a4f08221f6f28c3b202c57a40b93656cce92dd15bc7e1fc1a04424ff57573e030483e87be771f5b5f8022eb3826d9b627d4d9dc04a01388351365d50932abca6215a250a9c48cd3e8482faea22a54419bf3f689a4f4f2795c0d54936cf719346a7a42b55851e8753a73d0efa791fccd97a92a4d36a024ed08ac4c62890077160a0e14a2cc94ab67f5ec15bf96114a4c910a4fab4ddbc2272cf9bdb4a429b3830a48807f08b4f69faee0592773a2b780c367402b0b6d83f222c99445e87dfaa680c771e4ebc67e324b8d091e4c40d602696ec9ece919a944c793bc40cbe4d19fcbf9f1cc72158c4876d1a5b2323dd10e72618b3f1c013268d2a976fc8e3230d394c825218054c6d164b2ea1e54c5659a03dc87136326436c21a405b41732558cb120bf7b01a78107928d0451e6b4296410a41d8758eb93199bcb222ba916cd5360031091cac6341b87950cfda51d4c6551d6e210e61d0aecec6be4111b458ccb9a3ff17ac06390f97b8cf616f0ba88fb3827cbbf603ae216f8571b2aadc826d100151642e13310a23a4cfe760489442ba2c6f0a7f0176f247762b50d94262b9c0ae0600d474fb8eff69ce0aa34fce8173ba455e3087220412e61cacd2a143175fc05761e2a04fa8d179b53212de3f9a7f0664ebe1b692ddf00b98a9bd67d0066d2509a9e50c8ef244e2bf7592dfd7d1e534cb8c9f8cf2bc48bf3f9f984502d5c14a32d53817039d32fdc93079a7754f7974844ba15aa0c627ee4063a5c8ea2c4a1cf7f78eafc29174ee26f7db5812ce024d02ccc138b417fcf98a9d34eabfa42654a9b1f7d98a400409d29d531c3c3bc5c18495afceb2e329ee33ebb98c6c613977fa5c13c429bdf049154a23e6bbc6e0de285f3d57d95386b24d792243bae674309e7030c1c8a0a19535dc1b1d2460e184a2a521c5c9c7e0c262cfa1f62cd9307f5030ed81b47e5cff3cd9cef8445f0c4cb57924dc0185f1a37d2c6bdda1cfdef382fe48cb56a8a17cfbd5885a7a5bbdea970cc8bcf448902996612dacd3c49a3c27d6fa3928d6c294dcd7c18545e94bd27a0850f02f1f9e0b69c83caa22f2bfe518d1f4cf9bf0fd7a6aa0b5fa854221c9ca805c88b226a6331971699b07e568633cadafca9cf9c255a9999f9e486556782b95f82465b6b2bbf785468a0a52f8e9d15f2729bb43e0f18c77a0acab42bb0bb6beaa8cc01658334dbbed204553e6bb0ddefb773462f986fde80dcdd12c41866e716e4f5b4d0df3db13ff2d992145a8d6020f9b06f7018027180d8fd96f21c71669727421b34480aa4d009094b6c226abc852554022ce207804bdd4dc24beed326c3bd27c3402105d8dd3c11307ab6babee7120fbe9fa18d26ab773822e5e4b84d60c10fef9fa8b97d25d2fc0da6ae8c4a3b625d7229eb5adccad4be905c405b128441f498e1f0af4ef3ce7bac7c23406cfaa509c4588f32ff4189862f15a158869c4f2ce95a711431c9ead2a6002b9b6958bc218d4e18eb3e7e0c8236164b8b375ca6d7d3927ca084c946bf2d211f6d503f0a971b8a897b04118dffa9d57ded6c758e728e41ece63de1c2426a0bdbf07b9c6e098db0a7ba4108c72bdfea89e9b5092c8dc3a1431df4a3ecb56733555cdc696bbc02b54322db390b8a3cd8a6f36d9fa2dded79d18a75b13cb26b70256cc070462a86d7e2ad2db0932d6e4156e7d9e41c38b063959dad183ee5a7fef5b58008fbb6a80921d62b8c9e93f27c32657744e134a82ade2a26d6ac3108aae320357bc686201b772c7e21a5745e2317aea8286028e1cb995b79d25985b7b93c49fe770e180c591d18cd755fbc219ccd8f94db00428b974bbf59fe951e259ba4aa7a752af07d727d17622c171236469fc35bd93344bb3fae1d39fe13655a3c9d12107cd0b0dcd93e8d2b7cad9ca0e5d91557c74e5c7132b0a2763dead50c254dc3cb5253dbaae3d9a51a726acdbcd04c4efe18e226f780fd042bcc121448a89a62a5847987565657653ebecaa61857d68225e49d5c60d43608108ec05eb72e9bce9cdddaea93b10b5cf86af12788fd5940650097b7fa676bd01fd3c1d7c07f97918d95779ac3e6a63308eb3a9f302b52b65551258e9b5405f71f179d5699bb4114501894387ad03e3444a9b5f9b244c592163af4605eee3a682c56f30586daf1a986455c57ceb21b861a2030baa0ca70a8b246b325b7dcc4af284bcf9b55cdd1ec7c29fd5b803a835f8f5b1c8e1fad0e72318fc9acc0a4840e6b0b11c0e3e0eeefa0e6392cf23a3b1222874072a3a4c5d7b85d8636e74ceaf2c37000c2be61fd7ab46f6ca30bff895e94a22298e8866eaa2c72ea118d73db289886d4b854a1edc1247192c7c31767b5d1126c34d75e1dd0be5ed64791bbfb2fec9fa45a34aa1e782d46afe35c93e2d699d7ad8e7d0da17b4023fa6067c7ede387f493a6f3032088859c22981914eb2de95648556c9820fecd89993c8427eb365d3d825fc2c1752a36691b28c78cc22e62ab5e5c68c029ee115939176d25be7a93895d65f3254212b32d0f62d2e60ff074fbaabd6cf3c3eb25e1c67c13607616a166d535f61fb68123952bc653260fdcb605384e885c480da12a186db3f6bf2c06cba15dbe60da3f3e571e4e8efe4bfb2227dcd38835bdac8b3a21381951c16fa665e999e46323b03949e05d45892c6b5e23b61a3584fa11f15faf623e4817a2cbac73af8b7ed45b01d2e236da58b3d9b4ceb60ca7a6bae5b743c53f844d9b0c0f1bd884bab2e6e373fa62351628ea7491fe307256375281c02e22299108ff5a347cf997d0ad73f4aeda61b235a768edee0ac0290deb4dbe7b0688d9e320a706d5c1bda17d6fe21ab3cd80c2d472f11063c44c7129ba1b5f9421a53153e1d398bb400dda16fe474690bcbb3d24079aca0a68b013b5e84a096532deec3b97eeba31debe72f700c795028415c35c3b54e1bd66dfcf2432f85d6c3a4345f786015d493645181671bdf92bcfe697f1f9570c8d29ee34426885f2eba20b1e8d6b08b68751817ece583673a91862154669af97e7c2b8ed25206def7e0b57096ccc07dc8df8aac2309948d489e7a2853590ad9ef82e54edea642b0fedea86f668e4b12def7ae94c2165712636896b6a1d2ed39a5184a590c942af1ab25c9daa503a6fca32add4fcdfa21a4c9720ec8310f1ddadb422a1ff0677c69c0ce907b3a8e192bd5de2b21a1e8587d7669bf00e20bee2d61c6ab028e76a9584571a3dc4ab6e34b74e0163e51bcd53306a3d1c601959252c13a953f04a493722d770e61f48b85d3e2444153afa2f308fa4a0c7876c840c34ea963c1554210c0aad3ea8fbdae220301fa8bd4455dbd732e6368e5b80ce975eb5d9dcd351b636f54d34b50b9ce586eacead30e3bda4428afdb5d70e61d6731d9b501839ba352553bf511f4d322c62a71c121c22940389a93ccaf869db855878d06456e385442f8d15f6eee539c9044fb508b499ecae102d45f7a74c718b3b4306a8664f637e4d792ddf8f77e5e8dc6d9748d49d35d9a79d70f0c8d16131a773f831394332b8febf033c28c51d2257f3c24ea5cdeb554ec0282f966e6407704e3939a8cd8c08c86c4d1380653f3c562016442029fd84adebf86a9346531b81378b72e9b4f33c59d112d77295dee814a3ae2ba456b4debf57b44768a8f9d909c4c090ac58edc4f1bde79644cf235aaf0e4e83e50bcff5038ff4862784cdb2961cecafc628bb9c85db05a3d4268ec8ab7575842147a47a0d14257eb69f30167bab4e00102494ff337aff08885bfb081b25ea414670fc59dd584dc2d64b62e4bca6e696cae76b79cfb4931d26d96f7e00ed65fdf8fc65bf72ee4d3a4be1d3815b4a6595892077a31cfa4f0a4a69c33417153fa170c957ff341b3776134745550cdd25d918aeeb7b7ba50a2bb58f6a3e55846f3a0461195009203405fa8bc1d06063e63b57f8d5edb9c7226729b5d6e69012a6d887c76afb7d148265a1934d0606193fb9b9207e74d94c1ffa0fac2bcc83e9e4aa5539ea6cc3d0abf5e4dd8498e725ba20c1189200b2114f8946a042327b6faf236e29054d33953fe782ec85b5dce76d297f84b64dc0d0246e8d0334679104c4a57155471b59ae94e3a54230245ee0433ed6bfa14c9ebc4ee8c6976c924b115779237eac8530443c7ce9538c4ed8f720059d1c0980edbf73f1aea512a425bf941cd57991975a535bb2e08a2b05979c03b5864a8a7adb36ffa0432f26d4abf1d3d5243e5cc5b74be69157f07b6a7ea3ebe584abc0607ba359eeb0ac2bc7dc20f7c0e4937be066c7421b4cae53e32f228fbf84d561c45fdc8133302a7cc9320386c96f80a8de158e8aecd4f7fad991454f976c9b2d01cea9778cb22f8f445c24ff17d71fadee83ae690526f0f11d093ffa3a2b05ea25022792fc1783a8bde69067b0e23d4115bc11f77040c368d05f5c99c6ef760eb06f8e87f2487ae883b1aa2f8b095442cfa2dea94e35c9453cc019aa5f228cfb928ec2e8f163d8a391e752981263a4e2d4c60d6371517f46bb1e4df5dbae351d8e532f42f7a14e67cd4520099fcc16b04d930bff1769038ca3b40b3514834c7a28df20e589a5c53805ce9f41ba5bcf07915bb84834af6d0e7ef94fc6ce98e8b1e05340b3973b1d0af2dbfe3d1a3846d8e8fc663583a98e952b6019cc2763e6a8694eddce458a008b99669d8ba9d565063a8c24d51b28dbc6e6a3a34936ea1785267ad0a3022cff30cebcedbb97d950dacefee44864b023d5697fcff3dfcebfcbc7dd97711509e91d81247d8ec51e1d908a1ebb18018665b1d313ecb86df9266ff0631ad5155ccd039c0fed2661d2049d0157b26352db4c782feff84e2e478256250f9a469ef3d7aa3f0ade0f7f33b3fd6e76e81a75f3c268bb0aa606919b9ea979755f9712a03617302dc28f70fc6e1b41a60b629b56c2611fd7eb3163c4db5f9381e84747221c559d8e40c4d0565b220e38df75e91c8726cbbb109888cca83bfd9bc68e1dc40509c6b458d583ff1a7f9453896df0132e2d8e8f689b6daac582dd472d41cae320a979a390f7cb27a00c771a99919476ef9b7f4c1ac6ea7d7a37c67ed35dc5afbf957ccecb19a8de8af84f2ec4fc400a9ccc8b39559b49ab2e145ea3d631947983b0a79c9eabe3abecf070708d4de2d7419b822dea1fd268d4d57637b3ecad780562e014bb94837e225a19fb6dcab887f00c5e9570e430903ee8d6c85a1b9fe1f23f853cda11d738fb3c4ea97f7e138982eadcbe0275a983d38c9c5ff1876839b2c50e468d292dc2f86585891cc2900fa00a7321fa5d227a2c3c2185428bb684b8ea258cfcd4f44c3efc79155d40c9b9b18f264cecc6ae6504a15d70f6a3f40817325a57ecc68b95a5e0dc0101411f10ee76ca7bb2afd36633ec8b283e0c954143e76d308ba6afb0c8d5812a06053b70dbf1f6fdcfbdd3a50d21e21a8f30421d8ce630ef82cd00ab727da1a93e9bf0a9e86796421e7c563851218bb291264b0a1d93809177008fed9995eb4f7fff4cc64a2d214cac73ea99822ac440b2a8c55446a64ef84143390a720af22da4e99a5fffa7e9de4fc4d95524d9e47817c8049b2ce452c426c4554bb7ad6a849fd142959e2700b854e64df0a051e43dd1511e9341b2f33b076f2f49e0e7c91088da992e4ac118e22ad4ad167f133e81687453478912c4a37ed59d299b818cb37dbc19286394038a37c9a58bb6c601fb1e31d061dd8137651804847bbced7d170b09045250378619c9432321861613bc484c5b51c9115a4ccfc2f9f7507f0e600678282204f1688892778d7326c3b3503d353c0d065e316b47c6dd555570d1e9016cac57be6880353de4fd3e9a1d8e08456aa8c0e4855c156d8308a3ec5b0812a5d720ec778f590b174c049ab8d71452e7f32f5d174d34181c6faf850b7fc172c29c1de3b8eddf86c86003f4f71196fa7f9d586c2b676d2c4cb094cd55373c665709bdf14348084bd56fd5ab52d1d9a3d577a11ba51ca68f403a92fa7b148055a0427f480eb598cf11b0a5d43de0890f8ffc0d4c638cad0c5275cc8dd7ecdf6449dbe22a0a99790ff229d268e1346efa6a1feaeac96db5e56fdf037ec4fca6f1c9d05e17dd082b44fbafb960b311ec1bb2a8e48c873cde8cb0c31125ae0e8c9ad438362387855f94b89f8f42cd985cfc99252d2dc3b5baaf8065eaa4b933bc94d6b0b7b5cf5402f26ef39483933f4796c40db4a5116da6f46a61568c3f44640ee06ef87f602702b60b751a0019180cd0a386756e78611f61b2e66186c86006357f2da2565034838023447879283c9f0db3796a02ca3343b4edc11242655d0ee2b171175b2345c6e0fc3004beb5022bb58b9aaffde1190c4d5f3ad03bb11315aaaaa7a5663797796dad8f7fd63d1f20f6299b48a458cde37735a6ae8a5d81bebbc53ba4c56aa58e18961ac3da588788d77edf618c44841c595d210ae526269ef3be1a55e8d96ab3e041cc9b6604b0e81988b0f4250ad3ac4187e4f662a8e3833554724934a478c69d52586d9516658294d86db3b5c8f160d19af2c40ceb2d79e209c2c31fd3e4caa3dd1665c59f6759fd9fbd1a30fabceef1de0c82cfbb8856dc808359fc1a5a705763283f33109ce7b6363bb2dc8b2ecfbf0020b451c3d1515cb83e264ac905c897ff999869313328788f9c190899474ca991c66e13d9fee74f863a7f65abde0bca0295c1badf759a0ee092ad0f2d8ba740da0dd5010c507caba2dac9cde510f3db55795437139d64a1e6e9634b0e4823cded8f04dfbe8d3106ea00a1d79eb9acc7c4fef7bb35788b48c92c40461ea859bbc529454aa10e212355a74c518eeb95838cf25c2f5c079dc4095c0aaba95c100559eefabf70e926b624f2c6ae32cd1cec19f2aa9ea8bdbaf3517de849985bd615c126392dbebc59e88a029402fd72d7e75e3635945e07f63b8ba3ee9ba206601e70c874335cb3d6c0d4801d9cfd17b5065815bcac43c930e29c21fbb5ded477a0b6476b72c071ecd34f0ddab872b98e212c9cd7270fa4f092a02b642b451bb55eb0d31431a53836761ece48476529b2899b5752504b067f345c5868e8cca1cc7fde4140dcd7bc748540010566ee191e16049d3ba9e43d50d8385165945997fa0e7cad15c1c0989011e844e670481d98f2bbea38ee92e77177be691322087ae44814a4d3c27b8b8973c40724d08039bd21afb55ad4e6acb83aba044f005566e11a2560281d16acacaf5ea22c3d2e0b86e1660553a48f1c29f266201bbe62d566526b2153dd0986354804e1dd4ef620be84bc9889f91c09790ca75043d444913b4bf7fbb2c0527bf2fd2da8c08596afd1d8d1ea5fa82c645f63904d6b106ecd7d197b7efc2d9bf179651b7b5fe745402d05e13772cbcf321910fe56417a98bd2ab61bccae789cad3f7f3e0c8e6aa92461a28261d05ec1d186f5e4b3a40521fcee845f40580284e2ef23a3fec44587e0ca7e98fde0de7e153d99cabb62ef8502be5be64cd95eee8cda25eefde07fd0a85b3ae74d762db7c50717665b0c6f31b5121865e5ae015d83ff483abe97938ab1f540d27704a0ead46d63549e889bac50e71763a8aa20525000c7f8accc74110bd77d4f7a1cd0bca74263d0e225421549204d13d81dc563dd1d564a323f51e7bae57d85571b51539a709799244fc3597e92abd6288d0c8268a4e2edd794f78a0e88dacaafcfde208920c03657bd6588ef7477fe0fe24552b49fb7c4c41a0e19a4d406f8a86f75ada654d17e08532941f24385f3547c534047122e1706b6636b84bbb1a5c1ba072da04fd51e72c9bcbb9e25e11c3c6399e48a388016a7244e71e6816491e805a04ca78894c25bc19800aef05664e2094f8202af1bfb64c6306fe7aba784d64c74438f38ca51a5e9b6468a4a9855b2656e3caf8b229fdda28d5eaaac66a40232610af8ed1b1f0653b26cc888f3a02690c5417276d432a749bf38e02934a82d90feeb5122b104a428db464daac41a281375e5fcf21cda51f04c80accf4a79359629c9881ffc855ac9db1c5af45d11a897dda2c5e05b5c13ef275eed4b5fe79a73afa3e59b54f11fe9409e169b42062078fb640ff6ae42ad8dad8f86f8976290b2af641c9dc5db32519800719eecf5a34dec3f779d29dba01ccc2ca8018758a1605a27db613c2668b2dc8a71fdeb2b74a7afbf1312d5d251ea28ae2375a9528033afceff5862932a235d2f8b5fd277dd6d584ffbac85d55f0ab65365e1097deade81a512c5a23b10bf358720b42e6703486b8f8c7eb86c765bff0ef6f93b1ca4f3e1c2c008bcb2dce25edaaf01ff7cbe310f7ad098261280c52657a853a3b5a5fb8ab116ed808d3ffafc33e60b01decd349656e5450d59a18f24ce895769efff43edb04137e8fc85b7f49e8362d4502d03ecb7f7f1fa43957fd4034c5c0de17d3e586a5230b17fa668c7dec40ad3085362c5d1714fb84bd2df0452f1eee55e70b7032c918b3a5fe871ac9629e77e91ce7880831d1d2c1839c45b90b4d4c74cbedfa37b312cf2e7432f54e286351e455f85df944ad5795853ad5d9545836a43f29704c30ccd37435cacce0a751dd7e962a04450a8073c1f48f3e29ad818507174eccf3227e9c6889734929ee2611322e444a51547fb4333518bfec71194bb8b3b7f365eb2c0b641e872494e51eb5f020e3f1a9ce46c4141c89480a80b2ecb080e8f3849e113ed9fe31666e31a45ca19228591d01b9dd442f5dcdb41eac65cfeac797a23919dd2095cd368bb642499ebd0daa17aeceb40c064b2a69e32f9bb8d977ce7f2168427d1d95c4bd16fafd944064bbb789967ea85856d375d4717ec8456347241f79d2e8f3cafde6121417257c6b9ff1627cc7f8f0b9a6312b7b7af0b214be36ac1654e1b6a67b57f67d2342640e638c7d9eefce514d8ac41df41f3041cf452d69fce426a249e4a50c50fff42b46600519b4440fb3ffbd6a6145e4bb3b4a4028318eb5cdcbebb3b502a6dbe8fdf822845e24f6221186e60d754e6137a66e532bf7cf98ed2c094f8a8b8540d0b6e7c7c0bf5751c89aa1eb94e1e611b257176414b250147c66ec4ef95307030e5f94ea76ab21103594e4b3bb85f5563530c5c327ddd2d54527cb501d33a6105d8e12e8b23efa03dcbd2223d7280f934466c7857577a8124e6ba2ff6cd224bcdbcf76d16163ab866e30b03132a645d63030032317b261f2eb9298b6a6322013745293008d8a2c369846ee5827d1f110145ce47c88876162bea21d3e6bc40c36f1411976cbb38590ed1460661eed17fbcdc0f878da91558d5644b5b27c0ac6ed997753d948e7e742d6161581e67ec28c570414c91cccdccb8de0f4430872405ea045d03c1b55da55c0b76d21451f71881a6a55ba086ca1081b50be8bdb9808e21c5150e4ff334e60f11e8f685f924b035554ced26690f5e379c6e17a171152fc0721e7ad14ed49b623f5678dbb23b4f4bff2eeb6d6772036a13596f5004c20db0761b05e31ef5f1a33a2fd59df16562541cf061ce1fe26b42081fb3179e167d405687834c0dda1efea5b062510e39b6c145e65be3dff27a83521353fe107f5fde06ccc1b5ed383742fc4532815c6c1ccf918632de661544adb00dce0610bbb2033656bde34c9da0c559d9816e903f0ba60afc65477612b52aabaa4eab115315f2f7a542b22570e31339a00e42d582308f573c5b0056eb7d6240619b9dd8edd84fdc254a92b2b0c476be18a0a25c9a30bc5483a3df8e42935e8ffd0c9ccee754ce4445c9f48135b7d47b5772cac95c2422675573ab77dec2e26a7b677b20b6b5e4e428472971e550dec71b45b9604ab3094e70df3e61a23828d27f48a881789319607ba9657afe9ff468eb40ce55b8734f5191c6b9b6df136055acdf48dae9058ccda9266dc7a386b2e2742f1edba268e5c3bfa530713fbf70891d610110068a7c48c8e6725a37074e644d43178943a7ca0e019f1a122a0e7ae01cd5896681b0fb89d2c484be4ef8018a62ebe72a69717679a4d0b341ae8d5488d2069d7c1a828d527422f3032e259a67eefe808048bf0f9e3d3cdf117743076bd5b1be5391477056b6c7ca32f12e9776a108cf17b5e1368d28946b51ca74b009c81fe853a84e7b6bacffc5b90f5659d4d5e93bddcd3e52ad52ed6d7d6043260d5dd61e41bf7e498bf8c391b5d446ba7aed5969302e7264c5214fbf2e79ff4d59301ed27c0b08432cbd34266c4ec99c70e693dd3968084312633f8d469da5e084c8a92ece38367c852556f74362fd53713da3e38609c00f10286fadb316075d2eccacc9869fad2bb9fb473cd7c801ece74b58c0813eede7d91110bce57fb9e5859e62e993cc53e992fd5b322a2a3ebdafa7b508a3b7aabcb3dfd6ee4e58c4aa9e912933c895988413e7ba4e3e7efe794650c34319cad9c1598bf3a1fb49a6422f880bad365ebef313b8c5d2fbe3d1dbf834a25fa8045015811f6685d81b786d196a3188d681498e5f23396e84112cb7fb59daf1752b9cc40bcd4578256d93e0b30b93a290bb99f974e852735f5e6fd74700fd048fbfe891d43a4bfe2e4410bea55273330666e1cc563949787e5bb2b16a3d395ee4a154679dd73c2204cf76e16ab5241b0f3e62222e93bcc1dd6b9a1ef503418a9aa33ce337867ece1ad1409510f4285be9310b0baae3276408163c31d3bafc83d13e97483548a3a528e4dd8183338f83918043b439a33319e801452c923b7950b361bb3fc4c8ca6a96a6abf9b130e7f49a3d0055ca613d9d94f5154fa6103197e63bae44c8cb09a662f0c0bc2446fa285e5183dcec1adda8113a9b4dd627dfa64b38b2d3213f8f50fae73665a1bd61f4f274824a8ac939efdebaaa3c3ef3898148b1767fcd5c8ad7264fd5f701564e158c8d2e8f79a6ca0686146c87f3ba3e9d38bb3a59a72dc5165650d062098481d79ba67cfafe5dade24fed42bea4fcab445c8566aaa1344e6ba84c75be6178f9997735522c60d7cc9c799aca8eace14bf5b39d8c437888c4b4a1046177c213b70b661567c752fafde338020cbe530d9060209bb2db8de5cfb01a1085e1eb3bf2f4b71927e89588974c309a1bc1deb9adce541b96c432d874ba15699c12a81c2f56c8c99f494f1b2f00a6ff130a3d99a43abae87ed87b8aeb08fc73726233e66b97f1fa56c43eaa23d26ff11364155f2b0079c0cd6ba9d6101d20481f5be2fa92615f991ac7f21221a81d2f42579af20c07b1fcf2a1bedf857e12279e5008a5d0b441bda65457ed3c54eaf480bb01f88f26795737028df5452807eb28a5eb7977dca8632cc9887a0392062210af155d7e27a54a50c3b1e5e511ca4e48c4aec41c071e3b68aacd998fc0a67de1527c4189a3135234288bb0853ff8e5b805ce301395411e6a39e6f1d7d7e9fceffe3841aa9184baf23393f780a10afb2e130aa5ba0fa8dfa62320bd6634672fde982b3e630bf8739d6a4ed0ce6c5ce747e266652809d06fa4e33f60f82414e459affb2e0e3a86753e55d7a054e9a5b06b4543861f5fad2ecf3a93043cecdeda8f137f9ab56dce963c2f1e843b4468dd0915bacb530400509a033fc344439c73f1af05cb4a6b6de8b5595e90569d441af09d7c113c316e1886110917500fc121a34a253c7a9e77d2c70bf9737d3590be86f0ab6cc0076b279c7eedcc08170b30d3cd25cd24f25df449ca581fb0cd6c08c31e34fc344dd6ec281abb3a7a367b78464eebc969eb4cc6267b129bd72dd2af879ea1e8111ce8738e57cbafc0b506ad188f4133f4e2ca09165bade63a5045ce11b06738ee6c6c6fc6abd31010e788a56c87509cacce57bd75a34610813f9c8fc23886b13500033418e03b261f370bb26c6f26a95595b0df6abb9669a100aa376d267bbfb6724ae89b7efad19020022cc7583498998de3b3586b5e9a1a86b438c4175fb668fb3c3880d406c02aab81c8a71235dd025ec15b22ba8c1af97e9a69adc9128699c531b316b904a6d1d46514ceff77ef87a5bc0c3bd2b267868bd3ff3500912ffb911efa8f9b7b8f9ca0b2eb370b31546f433e7262e9e307dfc42a73287d9ae3aa463aae40cfc88a85890c95ac57b0f35bc0d89b6a72027841fa4e1fd56e11ef4d5fb2bd4f376f0ff3caeb94c32e933f0a4edfeaa5feae8d53cf6bb82cf360f9738aae4554bc9f1044ed2c70111564b351f03fff6cbbee73fa416a97eef5a9b4cfe0778281a28fabdecf18aef13b82524bcc9d97c5c9b7db9bd8f9b5657f3f81daba60dd69a63d7316def1c14044ea1c22f9b2952c81b8da8bd1a0a012104053c636441081991cfa6d53f23184027d674abaec0b08ce2f64cc519952f18a9aeee541fa0e3ae8e84fa077b83ed7560f3891fb16c5b1af785764f29f7882bef74dde0c5e7e221c941a8982d4e271525a07ff9c96d81227c8c58f5711b1f43cab1c74c6d98546787efc740d8eea375fa670e2595044b1d2400b7c0c23a0994640519238ca128427b936289459efd6f5c0e3a37968354f7789743c3f5fd76db59b8b4ceda16acf274b46ef2e6608d511c8e3492123cbb7a9d18cefd77b8c16529714a22f8c77ad6bb4911e0be4397d3a79706f745c2035871f263e52a86ff3df5eae2e397955bdd767615db4bbc4faca8b8362b04b519f46626ceebf6a814ef3f8baf229fae880c3c70738ecd1fbc4f4acc7b7603675573cc81e4312b913978a3bcc49d3de4deb1d32e396dde80e0cb28b37093163011a856c5a24becc8bd952539b6566e7767c9081ec3b0a536a5a94b546074873904fb85f6de3b302253445abf7ff1c35320f104a81c3c68c96ec1e038a96d469b339fcafed506f9e2f80cf95db418b3640ac7919f62243b59c31682a41275590eb9effa4391279e68381b3ae49110c89a0eaf57a29d920bc36b71fa95b3335f2c31b7553182965ba427abf03c0aeb8c40b81366989d4b5852b61988ad3421f04d29cc443a6fef6acbc11629f9fed653df113db73d462bb4efb5b6860b577c9d3be318f0d17fb855859eb9ce88068e8422ad1aa00999d104e2e9f8ccb0101f04065de73adb6786e5a7675d93255541d8db92117036c88878366628761dbdd0dfaf9ee8cbd16e022144b2b094d4e075e2f38f07261042d4a83835e79cecacd60e47d1037ed33703a8e79fccb804028f733c31698435e8c1274e58c788ebe87995440f634a8c879df207cd51644a9a5154ddba29e9a4764597cdefba8d1d74b3c417eb7428e73aab9c734fe97715937ceedd13afca2c10a23934d1f4df09a2c28485cb794f07b876ba8b67da92b1896889c2d338636528a23016d3c8c29327bd8dcb6637ae3917e5460bd1c3774f0ac8e2d22755af2772fd9f25c7bf2ae148b1b3c3492ebe4366366dee346ac36c3a3f1ea3d285377e7c2784eee7e4585003a57941cf91b7dabe517ce56890b491e08f3e78a667751e1317dfb6181430fe27905c414a08bd1979a838389583b555c9d2dd81de7f4392b197647d348dbc5760326dfc3aa3ec9f3daca6fdea390108b33a54ebbfcc45838d57ea159d035ab9c1768bf4c8bc019355f28e42efe0384e8d92fbf05092672d617e7cdb6b95f70e85a05d9f6c8beee4955edcc9fbbf5226ef4c201f92433534f87cd73900ca992b26de5ceea6a4152eb0d1bc673b65cc8246a31c6cc06fdee7a6a4e6a409b01a2a3a286e086bed612c4a7aac43cd229d0ea8544dc860b46e812bcca45b1807d721104eeb63258987d7f665bd75815caf7158350c8b2f56542996281dafebb0c5b0429acf5ad200a1c84e468d7c82167d87188e06d8b67dccc7d790809b0d4ef6ee89c5ea0c05f249a8011b91d91ee2680a25b732a0a6fcc08b8a113d85d8a337c8a2737ade05f6384281789db727db05419c6055d0d876b5b343c6d4ebc7338462bbb78a80eab4b88d010cd82aa289396cd7a7613b9899b098a4044be80f505cc4b90a9c822c4ca2bd2db3285f21847fa6d57db4e6db281e15b08f758c46be10e2ec85cb3527d569a6c805bda7f90ea576e43a0783127329f73192e76349f1121b6f5a31d2135ea6b9f42a0b0367612981bfd366a48defe8dd68f4101959485bb17a81c8b458ba813cfcdb88d753c368b90fc0bbbcd5f471d81e1829a39e30515f3c233d15e46ec3f561d551b3299e3e85c0d302468c4ee2db46ca946abdacbdf3638b48ba52b2110e8f565e938d577203c2e4aa062b847204d9a9130c24252c78f86e354d97147b3ddd46243e4e6dae1a19013de36aabcb67db8a1d26609c03ec87c18f3d4fb4c5742a80e2fe6983ec8e472c942ca6761f0990bb4e56f97ef6de60ff00376361edcb65eacbfdd23dbe282308149ed93580a36495e495be1a22e39b71de9195c9317c58cf91a7052a35cded103532fa4e0aeb418cac10c8fae1c25ceda925626a285f2db98755b934499a5b771ab9b284c2282f1488f5228caeebd2caf31d61851e21442ae1b0571c2b6b831b415e6356e704a231808cc8940adce425d9cfb6e96569d242d7583fa79ab6908e6b6c0bb10e642dc74cf118d2e368822b24eb9d3d1edd86c7d68b9fcd749a156a019cef26a770a6a629ff6c02effd4f9c86936c687e25224800c9189f8646556bdc7a8ba433d02329fa3538213978260978ebe52ddee9c212d62de157abe1dd8fa3a65a8dd391697b15d07ec5d304bf1a97ba5a2d048f16e30e7c1daffa4812cb5f02daf728e46104918839592d67c20b674894c0a42b8e1c6a3c04823018fac2314c80dc8515b3263b5ea2e32db3a4c15c1d9f24c8fcd13376cc7cfd1a8b18f587eed146069c58f8fbe50913d78506ff55addca8b21e4a60d2ce96793e70a0a3d05cb00803071a6f25c41eedc03b09049031016e9828abbd44b73271c73d1c0740aa58af31c1a0ab3b8f30e16c0050e113c00913d5ae073f9c091a752629b5170f1c418f8f8689aac66098e319c42322235cac8a433e4d737fb7bc2e12a1313e272612f06243ddd00b46c43d9bf3ad0156a4593796cd5635c60125d0614afb9879e95b9f3f46701fbbfd049d805dd717ab9b11e09bbc3961a785510406cb0c7e5123a4b9ecf277acb0c12cc5a8b455f115074480502d726860f51acb67e957a27c32debb4665af101d72c9fc8589cc8e206a2e50c3d4be9011bb03d70579ad70d9d0d18c21fb4c78b861e377e21f80949d2f81d23cd2c59918546cb57f80667355ac9561ae1882f32434d4646e7bb7ff2680da667c150bddfc84b1f047f0ce8fb30d035fb1a61beb9647f363c8e88e3b4b14540b94b320595e295bcb9d34b5b3f799cbfa84b69eb5388ac27dbece9e6f532ae1be3b9685545519257a2033c1ac90e8c6bcc8a6ef73f6e28083934c7e363a2e0b3e78ebfe56da94f54b1967d6f46eb3ff340bad41ab7dab7887a092117eff2b54fb5997fd974fb11aace270d211a0f966c64593f130995273c94b789062aa696e8c40ce72ccb9187cbf585a353e002f08a19c1c4292a47de92021f3fea447ee85cf455d3b720dc1cc399549b8e3c8494de5754c6aee5ebdcff097aeacc7930d77adfb66955184af5baa5d4034029969d8fa138a64bc5f8171046fb051b0a648364c8993ec0cd416b67f487f3e0440f27045720082588020720d66c28239112d61159d8fe2e59254f36d4d8b881b0f7e02e4107ebb6e550bead8051203fb70afbdd560ba98f0579604b35da241fffb803778afdd29fa80effcbd00532e22c470d1b32d7b9e65ff1be02d7728d491fd8d4498c1801d64eb4c2d943f00f2514642bb90247382009b38481b30a8ce865404614d3f299adf211ba1ae9af11a574dc94325d75c8e61815e307356efc0040bf00e49d073854383e461b57c1bb69d764695b82118735ffdd5a4e0ad7222596d5f13384b895137b3ddd2d85f56371c7124f039d0a06528dba83ee72fbe6b9272ea3f7cb6365fc79d99c75fdab9cbe891b561108f4baa536ee698035f1080fa29fc220e19c8ae703a1dc88f2a17ead9c314632037328cea2d8bb280a8a52702e2d3ade1a6c8cc12a9f3e70387792ae10b830c8f387dd7bbc1134cc7d779405b065d51dde8431b550216069a98e1eb94a9251a36d721f0ce33d670021c2c69d4d510c402130b925d8634829a0c8a05fc71a2a5544549e2f4bcd30ab3d22ac1a331976ae9325e657828592e2c1c5ebb05fc92719c767c8608c77f06e9ba1ed948e72073933062ade8d2fd1867451eacce4666fb23af2f9279f43e6e7f2d03fbaa862d7d8dd89b895c6f0d95b31af434206f351213c18f8ca18ac8c276eb07f0ddd462b6e6842ba952a505aef56322cb9d0b8a84115e5f5f25b3a5e5164c48218cbed77e03304c98d904a79fac52d04ef8fd3d1858f6a50fc3dc237501957922ac69713012cb6c60cd576a0f0cd01b9e1978f1cc94fa5b2e5c410dd1aed8647cab1259b9a1be132cf69c3e2ec0e2dcc60b17cda2fbc9c7d57ac45d7f0e159a3b79d80c5c044797a61976951fe328a0fc3f90fb530024c0e1f9e9c2fb3e9f323aa31e3d28b3cd120ab6f9078a586fb286802645030f27a23286ff2b9f51940889bfd35421b208daa6238a0c32820c7aa82948eab5484d902921dd6e4a0a76256e6949c5ba8b3f85382f2262a88d9788cd2a310fd4268c7718c6c40397d6dcfb6cf537f7ae1b8a4780103b411072208b13cdcf2bec48789efd2fa831b25b2c685549094876c692ca4afd246614567f84552f89e5d0d5adbc5b71e191955b78411252664521db8c6f69a03aaaaad6baf1ef3cb4d343ad6893230058581445d4f3a88f8d298e6c871a11d4e775c081520a8bed81f5c7552cabf8122f0f938a634b8bbf6c030e80a438d48ba04d8e556a200e1cc36aab704a71987cc2da1084b93739def0d7e6238a8b81fa51eb79004471f47ed97574eba3579067150451f41aad7fcac18a8a63753fdc8b42444adc419a72742b7aeac49d5af18b2d0115ddb7b48877705e0ef9061e646fd53d24c647413a960a7e2dfb0adfe45246851de248eba7f88610fe70e3afd14898b3cf5f60c719365fb22422be7f834b2c038b5e303d3f152998ec0a3bb42950e6caf4a9bbfa1156e4acf41c6b585e6198328d37abbaa40287b14f300772232faa7e7b945781ae58836389a9885c19e059d8713f282dfd127065ec63cf98552cf8975bd3e636a77d69cc8816066af11ae5b50d7eb08b3e761945cf820fc30a70e52f0e72822defc01a3abd768810c1000da16bf63609257dea1361ff0b3622721297fd96ab2341f137e8077448c4b31c02240124d1a8e010383a225a9a209b59be8196f953c551792dc08c1776d06a3bb7d0f9a791d08d1d2cdc6dfa97c1e9691e0faf47743b995498d1d086dfbe4516510392b565b5a57252f4ad6d45ce99f41b24fee39e6fcc739ddb58db4e1868a0280c377ca5de99c132dfeec9b3d7e49612abefbe4b05b1318fbb94ac283a45bcb5c72355f4165c98f1069632c46edd0bec59869b1fab38835577dde092bb00e745bea6af2673be0ced4898c9d3d576bb8316eac4a8edf9ae883f4514bf664f25ec0356f183b94791344c4abb6fea7eb830a4c2088a232b2e67147ba3992a07c116ea8b0710234a2cc120206fcecc08dbd46768049485f29eb331264be4e587b8bc9d1d7615c63d5687d55df39322f553f6135df3f36c7e2ac51713962f5765fff272f25ac8f01022d5fc80781e06f2aa8c04476ba0772278dac3373bf85054526c1ccbbcd994cdd1229637249c7b97f51bfd1c237d07b27f8959f1c1493e7299c2421b58e6d36ed6a4a438c1fc541a7c2635e519339fd1a610789f5b050de7028a2b42e312ce232ee02c8e0d860a08cff86480e8ccbaf46c6ef70f3377e3ee4d1005d1ff39304733c2148e24d8247eb0fcb06ec710aa310007ec246e0c63d7299064fcb4085068e1d01648e105c28671b4b6695725f5d32faac072d493655d1524c57c6026232c0200010feda604524664a2a8c922eba554c7886233404e3542474f19e329e660dd52ee9f8588a60a610c5d83da43db1e18c55e6a74f9236fc1fe839bf430ffdc9f7f385a6378e7a10f8f301a1b84a32745db35ccae6b057ae04387763b9bf21ca062a0c1ed28a123e6a1e78b7af4db461010c4604b067e9fef1b45c811dad0c28962333ea23a6a769a7d44d2eb72477fd5132d89c03ae4710429004299fa33d6953977ef8695652520ba7b791d63b5a1d591ad862579fbd8ecdb25d09dbbee7cb525609a6fe73882ee56c2e2e71fa3b7558fd1a1f215ab36ba9e02d7032ce9358f418f85b5f76a44ca8d76a0d8404142ccd486a2b2947a07e57216f16249133e0c9389e14bd08fd3aece79207ad2a426441fda6fa2e37669d7b530b0db0e464e156038f2cf7f0b8113e6cbd0c6da19d75dac872436011d2568faec1cec0e176cb21658c3953857642873f6f9f123109d136c7149362621024b733c53f7fc4dc3cb24682835bea932aa9d554289e544239fa0d99ee4ff8cefcbbea673256327f67566cd1f430cafbf085c4ab6bc0bb49ef86c041b2bdcec5e806c92dcb0a00392b84900f341acb1602f6afc8cbd29e461cf55c2eca9b6b1b501b9abd4707c0869a3d2724789620e6cb1db251a2991e679e6877d8187ede243decf6cc83f7e6e10b63c69b5f8b9e2d86b3199eb0a79127b0a302dde3a17af6bd4a35c77a8c5230ab9f6875932e5201eb1eb239355c5a71fb8321b98232ad996098a5c19e86c3edcea7569ebad16ddf3bc75cc956b33d47ca006343220a4b6417d3732419dec282eb91be6a9fce23af223432771ec18b1b9c9100c05eb16ec10c23c0ba185acc7618dec1cec4ad6a9ad832a5a038248da9485861c53d96a8d67b0b5732ba99a0a799b6f4e08e2f002d286a8bc61f8cb552645b9aa5d415cae1f42c921ef920316f17f51ae00fe9731d641562fd16d631b7660a8ed7b86c8ca42d8e82736928a3abd7db861cced573d7348bc47470fa0edf4af8ffae3e9cf165d32796f68237d45c4187856d2bfea9ebbd31facea18faa5993712a8ea82a9c20e0a77d5e83528a14a1b546a4032d9a42aa4722b3205c803e93bdda56777d0550ad39e724c771c920b8027aae26f9ff2ba55c5c743f5ef1b12c84148478d748c71ba2df15a91aa46d23c979cad1874b1d7bc52dcbba2c77fb2f7fecb2958cdac2d704fcd765d7150f2d1f03b3dffd0dce7100c8d7e032c72143cae2159e01aa6a1388ebcaf862736927915a181f807713935f22289897e0d119ac46dc84d0ace618b560753ab520e5458923c447168aa0ff371b34c940e209e4787a06128ece05a76613ccfedf219b28a8def2b72514e0fc3f13c7d204b418a5cdd468ffeba5c8f023e06e4107eb2bb7e9013f5824fed95660216eab3c4d84a770a86939d1abce273cddb6563db088fe515b568f0c3a07dba6af24ad46f07f1fd72e6f967b4432c5eeea894261f27a00848dbd709b05caabe61b14b3ccf73974dd3d720e03a0e6856f0151c2eb851841eafd16208c196d3b017b0671cce4d5d363ec459af33b0dbcefa7f9c477f6c115d7ae7e910c3e0574d35da3e686697a4d2fc2ee22b1db0491698613b698a41afe666bf949d72edfc4cd655c609857011a34f0226b1e8643c8ec08743ba82cac19427c40bc90faa2f3210975b984130f60b0268f2808be438fbf95e34b03e959a7ba96f5eb044c40ed760cff1558907d6806309ff440586ffd8d21edd7f7a89e8ae7337af2cec2d12fbf17dfd499194067bea62078a6a430e0f31fe9fdb5a24774f33c5d50e7f9ba24ada6d09e417366aaa2bc52767ef728c090c3a472865063209157e153ae3e4272616689f714cfe88cc996073d230e34dce6ad2ab441aa8eeb6399419041a19a79f5b20165a4d07d592912bc458ffda21cd373fd57b9035fe0f4bbfae6eaf84d8d5c7c51e8496307f1068231b57e437be339c0b080407689d7f41c61627251b401155d50c369c0f40638b532e7320dc8ec9c4a4b5bddb9adc524a99920cac0a93098f0958481d62bfebbc2fd4e13dd73bd9fef6851d7efdee53aaba93ad2ad5bdb47cb0580ede384d8cb4560ac4177d4aff8443ab45a6bfe18a7d4419fa2e485cff4e1d81e3e211373ee71ee7f497d9239eb1a5941ff861294fc9939b11e625469683d3e5889b4aa55229ef65f662664c409eaff89ae00a11573e4e8e0fa7d97efe0fa43c1f88d3f08839f5f383082152cbf38b18ed2028cf3fe234de77533fffc5694e3fffbd9f0fe3d1ee7068732a75c2d2f35a89833dbd4ffdec9d9048171d9c18e7e78279beb52118cea687c34825cfe94bd9e3c9f3439f923dcf6bbd348fbfe67b2fb7e81b9dbd98bd979e0e6fca4412c4612bab9fffc3612c7f59bed587235ff3af84ad6020f2e222a43c64fb939c9243238efa3c1da8573da7c2ae9b9855cf2366150ee3d1f68546f2967ad517a6fef4dc478f47eabbd44edebe188072f719d9a269bce058eb2ca7d961c518a5f40fe7042623a41a8cac4832b2020a465634d159de9ee3b8248e43e9e0700bb375bb2e272275ff0020daba5d87e423a7eb929292945c5c965c5c66b464c46861b9840061b1ac569e0cd1888a2e12958c27b5e48a4a4180b85c5c95312e2ec52825e575dc120acaa95d89279941529a404942b7a50a85369972a9f324797b94cf9227e95e5a5a9a629d7bbd925e2f294992acb0b42425698525a6a4a51556484a4a5272692b40bf75e5abba87639bc66cf158c1b3c6b8fa4d99830290e960096cd2d4500b3d09a25be079cf14f2a6d06ce2793d3d487c46ab5c900bf354c52e6d329e5487eaa12199b49e4eea237f6d3f85669339244542914bb24a929a6b508d269155e49284e22eeb17503a14923d4aa5e4ac6dbf1ee8bb8cd5af87f93d9ddcb09178d0e742db73b15770a31194a42cbf7e8c1c8a89f178d0183b835c983be436832a27d3bdc2b82f8724ac896cd84f8e8286888ea448119ab4273d45485494644f9549685eda41b7da73d27ea494294c352a4ddeb3488a527b3a6450db9f8ab8269291f982562b8e61033dfd9c8965fbc4bc1d33b9bef476b0e4ea3dc01f020788cd8fde0b0788cd270011139acd4149eef789d3536108b2c573cede467ab60e86a7afcfca5c8db5bb6750ead845ad9c4da57e5d73b0bb6d8d099f967db59170226b91b76f253f447a96251e525f0ef5b4ecc857c4c1ad7db01107b71a9bd87fe46b7b077ac10ddb276c9f59e434451c84e234b56223ed5f67ddddddddddddddddf565fd883818430665284e89dd43a4881f493d98a2afed6b8fa0bc3d8ed3c81cf2f65c2b36b51aa8835aad233a4eb34d21a2a3254e86f780d373ef4962783a4e9805ef69f174704bac2316a2958c67c6119d29149b6e6c1a68cbdbb7902709fa191a223a92c284a489fa4869ca90921cfa522d6fbff21ee07df7ded3bd8759e8a5ab6aa2274334a2a21492253f404142de5012af22e3e9a0cca0e58062336386931a142850320780ee15c60d9dca0f00e48fab2e913f000080acb2be8dcf24a771010c9fcdab584ec5611777640a79493aefe15caa308b6691c352bfbd53d99ee66822cddf7e4a711a170efdceeeb0a606722a79dbfe52d59c4c0e9b352a54f6b25f3f1c357e96384dcddb4b20eeb73fe23461cccfd0929ea023c944243b92b2bd50cf134f5284b4bd03cd9a4a377595ee7f72e84d8472e8345a0e1dcad1f6512987ee3da07e875dddcf4cf314729a1054e2f3b304a8c32dcc5cc3d6e499b2d93391ccd9e94e6192b2a97612e928498a1292593472d2239fc82149934445edf20b9a0af5845d356faf82c783aa6064c4948c7334322205b9bdfa05ad4a595df882f6052d7374ab56a65cf80229ca7c516b7219473f2a532e7c51146562a69e0b5fd462989f8a81be2a876239c3d1cb999fc2d3e7933d72a56eef722477ff7118fd71200f5a024adede851c96426c7b1f5a6296b7f72742c4a2d1124be4cdcbdb3b14a789464ad4f2f67ee434336fefdbbbacb368ca26d051d77290247b8ec8dbcfdae33d72367d628a9042236fbfd16f9774b67933618273908b1d8cd3c8975c37b99f1d1ec2fde43038035363738323078e59332f5184711a1d73fb6abd489e44f2a49f076214a2100528c0e4497108ea569fbedc70cf4818072d0df436fd21e4c4949a0d341ac0884d1f3c91e7c338ac8bcd8f71d5a2c8e1062f7c000587d8a4731a4d1104a329888c8a20ca8183e1a9eb058ec3f69e4e31d781837a1bb7317e31466f64fa0fe39e3e8e849ddeda7b29c50038ba9efdd3ebbc0aebf8902ffab9e2ab6e5cf155bf138300f7dde770dfe9308938a77b15d6a9912ffa1dd689f2b513e3be18eb3ef583cb2366185a51c6fffbd373dfbdfcf0ad7c755fc77dae1ceebbef38578ef7dd7bd895e37dea535e87ad74e5b872baf7deeb387ce3d38fd2ca48db30c543a8907b6dc249efe9deb2730e9539dacd93cd3b36f971d6cc9d3a4b3387ea4e1cd7a1ba8ef3bacef376c88fd669a43b3501d760948976932f98e7e6dcb6ed7b73ffaa93a7edb294524ac9038e690654ec0084207678c10e0f29e0e18b13e400840c281812c11032d4c0880639d860054082a0c312663002102c40a23af9312306b42582849041900e2938e1054260924204146060040f92508207b52396008594263e5c313342861b7028e2022ad430851435142108209af821031b82f0841f24c182a4dae487112404a108342b96aa6c61c408163481841b90989ac0c10f0124c186d9124d48680045d06c07274344361421850813d490e4430d5e10460d8a2800d393a5199a70040d5d0cc193c40bacc84188a2a3091c01085134610a1334512274819d243c609183240c41862c9498c1081a74116507363012e20231c68881284508ac5832c20f4044f1c4870284f18416842b72309b22881f51898b264f04b5200c1818b1830e5c2c61a208551819c1040343746183309660840b7868d2400f4be001054c59d48014740125848a963002073df4a0450dbea8e28626474f6a4b000203af28c8c0851454f86881d4012b8060c10966484c14fd102515182080155f0cc107a41ee8e0478721285441810fa898a1073ee8400a9f261c66b3167c11812c92380210bc98d2c50c40313640053f4fe041184b7861829d190c6c3862c2cf0e7a10831ee20eb2a03d51e3228830a45001085c4c595a810b8e60ba81bca1881a8830c410582c99c00d31f02207198090850ed5063f768e380248054eece0045e4411a3f4d0e304309ad09082ba04024cc021071560c1f3449500c060882625f400a8890695871fd1ab8811043526c26062871e2f9e70a41f16a0b3f838f1028d43bfec7775acd60b0c3853a306470e87756cc618bfab0831f95942c464d6e4c3c44416549bc12c0720a5a320da0c7e8096847e664132206a3390291dd1847e6640c87ea8b98048e9e8079a0baa08fdf0c302a4d90fb21bfcf850cb21c9a7c80725202a37689243cf922471ae80463e1cf9c716ff62d0ba9c184cb89c200eeae9a8a9a9dd276ec6e975a919931b9c8999a901ae56accb6a752c2caccb6a75ac16cbd3c16a4d3b6d175b605a3c1dff9989dbb16e1456ab8b118389dbb16e1456ab8bc1c48da1c4fd9756113198b8fddc65ddee32e15ae2f6732f2f29a430a34ccb444b85ffff9fd19211e35b582cffbffa95fb2afcffbb5898c9f56baef803451cec9eee59b5808b968bb75bb9be6b883969dd62d06a4e3168ffc2baa09d9d9dd3767665b5626191116574b6bb333164c86875ad197686a763c2b06e8bd57ab92eaddcf439daf565ae957637ed8a5d5d0c5a0aacfba2c2cfdcd3c1063f8c0310801acac1c0783a28ed2c0e89c4a41dfe3bb559503a36386afa6b70d8b89cb3c6e671e4f01a35dc04ee8295b0cdc9ed2891d2a61f6d6e361c638c31c618638c9dc4c94f055dff7e1d2a55aab78bb30544888332561c444847fc31c6d8dddddd1dbb3b76bc486a0de45b09ebc1621b977f4a1265e8d8c891599b78a6883c72c448ed8fd4a851a3c61123456a458c1c297264062607482d484d08911f3e6a2da4224e2323930e524e8daf4f4cafd5d92589384d08ba376193c3073623361f674a9e9fe341b1f93f7ec8f381183d4125cf2f22db41a3277e668ca4e83e25c4addf837d1bc72dd9faf9d26a39386514a2d05e0bd1c1d07fe3213d378e51c6bd66b6c0f937dc8ae91077bb562b01f10fe2a01025202f2fa4850469a5f62b04060606464810204a2ec4e9074488cb3fc2f11e37404825204742822411c9010353a34eb89db7ba7e0f07716ae3af7ff33873c6f9f54e9809336126cc74af38d56bbdc1a9cf6addd4fe5a69eba82d74d31c8da410ca618d2c6b13eee4ba534a29a51bfd6dab5bddb66dc361ad954aa1248731411011e560c9d1688a219428108eecbc349b1c37b4960debe6e62607cddebcbcbcbcdce4b0a1d9e4b8b1b969392881b8fe58cc7c6f72f408712cc181e332bd7047397c7982ace1a07f74f0c65f617f8ffe705afe90fe9bf9e1f8e3b8d00f6bf5af0fc0d2da1b5ff4c37150e5b5a042eebfe98123e7ec1b9cc681c50d63d18dd344e9398c4f64f93bfa061637ac51a3288c49354e4313224992d1084a53962c25da03ae9b1886fa30d0906fa4955d51467ec433620b4edf2986a1be119b674419e93d8066daad28239fca6fbd64295da6b81ec56809160ed410a96182033521a046a8910d01edc901983c64be3b4d480426dbef2c1477fbf87266fb8010746ebc610ec0e48a87d05f0117bfe5208f9729222b4be944065c0a47cc1490b8fe292e85a1fb2b16eb658a99829369bf97cc3993ccdcddddbba779dc7bfb618d4d0e1eb1074e0e17144629999bd6c1b067ed63bb9fdfb3eb388ba537eb1c8c99a9313b47c2e3e0aca9097bd6d4b3e8d18f743fbbd99467d7755dd7755dd7751647366283384d0753fdfc1ad8fd95d48711c9e6ab91af896f521be876e7e479def388d9dbc9f50b8de41afa5b14b7932b87b26f234f2d07bb4682d54a58bbfb9cd3bf3d147011c6a98e112242866af24a92eb3f64b76ddbb6cd613c4cbac91140372a0393ebbf12854beb272a1e5c1e3de4206d9ad354da4653017177728475ac3e89dde7d64feea90f3f7bcbb7267fd147e1b069ad0e87f753386c81ef60ee5a77cbc258c403c5d383a42a59f2092942dfc8119dd4539ac27e92d47dfc0012c4ab177190be105b1b926fedbd3e6812af41d5abd75a6b50adb5a9049d9ebefbb812a7a9b05ba3e2b015d84ad5245f2d932a99bb9f4edd473d1d9dffc9f34fa5bc6e5a4a6bd6c77683d3838220e8ed4537c1ba0cd65160bb86f296a0f70fd770307631d2d48f2f5b91b682a3e31865c200dcf78fd865f2356f386ea4a6313fd6ef4604e7bb4cc218105f487da3a669cc9f3df0a122cf09458f0d1b51667ed857e4f92e4de0b826d494d0cd048fee2be65670b8f439eee9a77acedbd1a94775137d89146dd09fb8e97acf13bdb8ddbb471adb8e992976599499bfb284d0651cb60ed6e9200cad6a24a43cdf655fc7795f1771777ebd998bde4dcc2e4a8dd4b048ad6ffd43fdfdacbf4e1fbd06ec64973f96b8483efd669ff3ce9bcf6ae2766fbbee654e13ba10f66b280eceefbe3e0ae3d220313599069e68a36944b422475ac603a63c559067bb20cf8fb177c873da95af899961c113af60149ead2813e360ffc703c641179a01644347bf12524c823243d4c2916b843179c63bd2a4a40f090a118d4acecee32095f5ec912ffad6725d8547ba105126eca64cbf9ba6cbe28b7e92925276a64cdf85766316e20b9b96e9378dfa9cd362a7b51255a6df3d94ce39a750a642b81039a211124e90c3560e67329d2c3ddc4f156d4caf712750ea7193c3268883f471286d1464ba81f8a242a20c9d8f7a5b9700051d21e1f8f80124484ea5f5f4f3846393e3a607955bbda1428e8e888826cddd842ea1ab0e6a9f15dc3f54f6aca34f396ae334bffd53faf537dc9e95bc24c8e7dc48b4e17d7c10441bd1c6966d4db94b3756beeec7c3043551cfa1509ced50df4b94714a29a53ddaabef7d29dcf84bbebd1387b7d5fa0e870f360ec19919f99447d6551c944d54a6c4b8e4a05472502e715774705b2fdb4571777d286ae6345b1224253f4eb304a8df75924a94694a8d4a95f5b75aa975a54abba98acc79e4acd4a538084aec3d3f5752195dc98ba4ac38f4a0fa644aab756fadfea47a3a2a922744414ed3bd6ce24a1cb6f272f5f23d89f42533f992bffaba49c256b0fbc83964ab3a3c6cbf86b4d29dbc759d3b0e5bb249a29ac9694e5f7fd7a4ece9897d236131668bac9492e3528ef171dc18e91b2ef3280bf22d0e2d1659caaf3751467e51bc54724b99e73190a0cef2525e6ee56dfc6dbc8178565f07d188be9ed2dfc99ca781c2ed59efe21faefeae66c7729ad8ddcdadbe90aedee588ebeeeef23f57238932f355106d789e2a88363a2fcd975292962889712699d14f367db20a8a76b249c2260f0f65798a65151ef5a74ce2eaedb841ac70d8eaa1fe711a1e31af3a492f69a1fe5016e5f973e5a3d10e6879fe6c4e25f3fe94f2ae569bfb9cad429994ad94b2faf97269cad544e2e0fcc9b3fa15eed99459538c33ce996c9adda46925e51429e51429a5dc3a499e549a38cdd01287b5fcfc9ec162fcfc56e23019ac97f1b54f0bee1ff99a3f599d4ccab343fd10fadbb6f188d9f10a4a843da35f68a4fd0b57bf3db7fabbdac9f46e6f23edd633e9ab99c4619c92e434b1885bf9fa251479e4b0204080248c88bfe64f9ed5f6b50aa2cc7cd497e3e00f07e7af9e7be51eabd58ad25568f38b83f38300717082337ab129cff92e49dc70cee66c06f58dce73c94f23a2cc79c752dbd3c1f28d24da5041dfd0372aebe70759f99bf2683aee51abd55c3dc7f2e95881078bf3e47e1847aa2165f652ee5ff95ae8eb260ef693eebe9ef275ada97cdde42e0b8a324c3e0fa2693492bee1b22c65decd67bdf4c8711a20b30a9538a567e6237f78c0766432998c7efd53e8650ba31d28c9f35fdef094a5c43dd497b362d05f5207378c53843059be03a28d98651759e6709a1a07258cd2f50f676a52c6e538487b3848899e3848177017d0f3b1fbf7e36162df0445eec9f670dffe2643adcf75fbf6326f1557cf5220c6aedd2e6e207f512007697864c90a0eee4c3ae223433db54f3f087d2144a8d115331f25f4b9ed0b5ba80e9d9ed068689b6c9baad6ee746aa1adde384d687b50d68c3377908f9f4b85248d423edb534aeb86644aab75efb66ddbb655da65ba3d7d22f47dd0ff413f08a502e4bb91b095d3e36a3769d6de4bcbf45d9c7063de7e0275b9c95090d3b4c89fb205cbadb2bcb300b5929944860e46a64f2515d9c4c2d3e334489c86852569dc68dc523e5186be0b1297859b2a913f3248c5fafaf188597ecbe7921f7365d17730a20d225149aeacb4bc8dafacb05abeb00499f5eee9d8565fcc2b2721966ee5ebccf22796df3ef92cdf09a8696cd2fe38b86ddbb37c316fdb8a85b7cd035b5eddaf33cbcab3beed5974acdcaf9d9d5ff4f972e83e6d824c9feb221d28fc1b8e69bbd6cc3b323a00444889024a5ccd300312557450451513d35fc37fceccdfc93326cbcebd1d313584c8d0032747dee0e0f4c811e234e14d51ccd7f8ab85e9d5c850533d1df6ff3b1ba6ace4f01253bf20736220ffe35f5670b0165b5c1d94b191dc4a09cef6947d69310c8f8ab948db5316d71ab94ac93546998ac3fab362239e77d81c8d6c99bec42b48b8f1abc0e0709a1a5512d7c54287f1f4421cf4f7203c6a486ce4880ead5123c80e21416c9cc6272ef2396e7a9c4ed4e6e626470f0702242e596badb5f6bb7b66dbbf73fa66c781561e227fc6c1e9e990dfdff4e4dff47090d5c5b472378e71d0bd896d66b8211196252f9be56dbbd239299db3a507b63cbb079da607d4a3b0ea0a597777776fdbb675f7d15127dda08f967cdc09d5477dc30e40854e42dde0d4849a12ba93b0dfdd75de7b9e57b4c4c38d04e61352ecefbe72f5b93ef29e7e08f651d75e9f9e5bb264090c5d94e75bfba88f47c7c53e75f7d7eeee93c735964532f79cc77ded33dffec9da8e42fe01e3d688e141324b52637383335f0211c6f23366c145de6a548a63666432a2e0a2bbfcbc29c913c80a4adcedf3afdf4e8e18870e4d238622e9ad0427a7c52ed40fc1f151ffc92f8cc9f28f441bbff97526cf18b16d2d6276b26350e2b962831bceb8bbb5f7ce1c729d25b863c773ac82430937cb94eb3e0ec94572c34ba5841c4fa66fd3a19a688da64cb9af3fab921c5229be94b90f5bdc47e7b8c7a143dfe8cc3d87438914a7e19e3e55a25d670edb38f8e5902bf7e9b31c76638d1a1c3726d3d4100e87928efbc2112c65aebb7f3ba1ba6f27b70f65d409e3d0819b55598c28220eca0fe2a07c7150be64594446e9e3843a4d61b57af86b8bd92367fe09e6bdfc1758eae5830e537df7618d8df6c697bf8c43eccf79da594189fba89fa7afa27e3b6d9f119a2b965b348dd6bc15072ec284b5bdbe9028339ff31925cf587de46b7e94b2bfd62a831c9ca19cc99e28e3a3f4c3045413f2a6a13c2b2dcf9f45ddcf8db97ffe9ca2b83f3686309f498b3c6713fa46fd9863c5add434748836246e224499f913c6e7c6b8149b3b52e63cde236792d294e2b0a61213938b94c25914569f3cbb964abaf2a977eddab595e67bb411abb75294997f02e3761207275765edc2c5a51ffbc64a96e3df1e9a24ac95fe95ec74c738a759f1a4386c59ab6f541f1d5021378be46b7ed56116559fbe318f2a15797e2a06d77f49c9c14969adb5ce6f2614136ed84a791631b96c0e3b4fa53c5b697e33cdefdafc6e9a5f8fa4b3f8c0c568c3696c9e5f97e57733d7d166f975d9650527b4bbb3ee73568fd2dad1db51f64b2f28a5d4dae0862cdbb6ca8d1fd6083b4ba3b32ccb62972e2e6b079df3a68503e400c5084458d2b17582c965dd7657107726069c2e4e5c34b8e1bb82b82d97d0655d075d42b7c68c1037e6e782ae202e0c93fb0ec677e182564a699df467a5274fc7bc0e3a9e4981c14557cce06d3d18858b30a103408aebd57feec48f9e0ffa8d5d39ddfe235fd1bafc7e7ee04c8d7cc518fd626cc698c758f421a7c5f7a2f8b6e9c6fff8f13fbb908d7fe3b7e27ffc0813db65165cdcb931372ae2d9d1fd5064a7c921393455b97158ab501dc51b57466b51c65519e5844b8fe4cbdfda7b5b2d7ab47d90d3d4a52d8708ab55c4974fb421713dfa6a8208ab48c497cfccbcf22b87b4a9cec0c2528568938c6db8f270639eb5cf1ef9f29f79317f6408eb50f677e1e2f6d7a168e38878837e17430b098b5548be92b8b58bda45b4119bb6b6b89f3fd32a310c9d3b1eaa50f607caa7286e936823e64d691665dc3d49bd668a32fe2730ee7c32e4200f2ef340e50914652650ec1a8f72d8e2866cb892269f38e8a8a62bbddb949a867fd4218724cde991ffbf536ffbb91ae3442ba088d5e010e3fadbdebba7f57d6353da881065bce5440e390e0234b66122a036a5bed1b594921b5a210c31c1b0c3b3dc5231b04a2b3b7031cb70d4c83971b434c1050746e88084121b906841acdf6b0090a2762361ae9caa5371fd8afa9d583f8e8479123e92af7ed611eedde24607258a0977fb5c39dbe9b7df89b5347915af35e5fed3d7b2df7b71c3d6377304fa3c625e4189ebe2644a8e1e0f97ec4d0e7615a7b2c36bd265b87f83e303080a8c5b23615e6b229c374ddab49f5281a1e1865ecb356f72e9e2ca97eeb0486b8269ba437d83025112d1916adb7ceb1f298db058125e22254449681754b64fe518c511b8c8d1d33193371cce64dbc58d464d48099ba7092058a42571277c72d83db31e2766487cb62f644d1eeff1bb1cbf9bb9e9f1d8e1f18811ccf6b94827eaeb727b3c6294a98fc231cad88709e2561c6ebf3d95cf72b05d0e01b51c8cc2e5b4b8fe2e46b88e713ce1722e48b85b53eab9a374d013982ad6464d3c0122d6df44bdcafd0d05e6b1debca7bf675366774b20d8fc1172d9e246231e59cb184f2e0c466eee144499fe4641a4d13130dddc3fbf7d44dc27c6c094e9ce8d465050e98e753482624aeeef25bdbd8defe86c2df6f2cc168721c83d14659cfbe8bde031ce37f9ee73c4c1d83d12d63488af7e1f5a43e423bafbc7712371b051b21bee64ff181717a61ba5ea074e8a6bc3739fbe3f662b5f1d532ba7477d8cb95ee8befb188b1fdaa54c3f3ad185d210cac28a85151fb2bd7f7d23ee8f43e079c87cd78ee8446d4974a20ba52cbf155b64c5f542aafb94eb85184ed1f3e1fd09bb724edc9f2cf6211f5df716a352d88550dd67f3ddb4be18739ad3bca8df8ffa3dc99536a7377cf474c4886794f15b1ec17577e7d953c0e45925f7fb0f9d1e86c4c1ad79bc650e6e45f4fb091fb13e378c33799ba95183c388d36cad972ffae16b7ef8e5ba1261a8af7f51f3b34f3ffb1088a16fe33e0e10b3ed1bd75bf71187a530eb592bf192f236db67ba9a6bd3ba89a144709d83f18d684a397a2c4cdacc8c92a53e12b6e1997790521807e9d7ba0ed27ffd110ce3201d9a6102632a1da211151d2149515a62aad596f39d46a268eef388241c462661a7f8b2128827e1bd99f6388d7f04e5a9a8a35cdb4adf65f10ecf16766b3f740990c358846690e9d3263fc4e8d3274d4ec392e95322a7894cf850cbf469117d7a64b7aac305307a503fd2072472bfd4af76e51c47e8b5ceb771f78ac33264c41969b4d3b26e8bd57ab93366e00086755bacd6cb756965277676d64977670aacfbe2c4764ee28c2e4e6e38e7f7e100d4c41963e724ce18593dcccf45c40d675ed1d0ddddc234d2d4c401e7723a920cdc70513aeb31891873709c8b1307387921ca04a28b933b5b701925c6e81225c618a34b9418638c918b33c638a34b941b639c31c61863748912239d31ced400572b3a636c459638238db1f532edb41dcc7f9c91469728f465ee32eb468a3b193146972837c618af4b941aa01b9f7b7949218549297d1b49e38c2e5162a45f339d7125091cc761eefd5f26ad75dbac3d9d501cd7759de7a554aa7b2f1737fe8aa7a395e38a8535e7a495b6742c1e6d3294395617637ef4498e115d868c96415b6274325a2dad568bc5b26ab5566eabd552b5525eabd5c22111146d5a074f524817272e232ee71ac2c5899db1524ae9e2c4157463c8780183826ec87a172eae7f0aee02c2c589cb07205c36b827a2d8eeb1dd63bb6a70633b8e1dc4c5166098c9127b40957b98f2e5ee571c25916204475408890a1dddd450a9916249154851921c818086a255b184a27594263421b9975a52d46644b3a69e263df1880a2aa8206a2531c262ccc15fd9e18613365fb195109fe80112663cc8c28818448941f061811450ec2007a3a658a529612d19400a2443a0f47f382c4a21de99524a3b09bba1450ea00009914405508c8251f4451853809102246831ba84c42153cacab45699239674a266701d78c75c638d9de7f7d7b775083800c143922c8bda13314a118e8070810b9a10031baa12b93e587f46d6afa96fc3946faea0c7396f4c1ab2cc36be33db8bc0452ec670a311154d64d93e175d38ab18325a1fbf35b333b7e6887cb166bcea67601d262a9ce352fdcb97d5617dcb4720c65a3ad7eec47ea6e55d3e0231e682755ac2dbfa7086c6628ce5ed8ccfa57a1cf9b22a0c430462ec46beeccfc03a367827f6338eb265fdea59980907ba57b9388cc3a3c3046706d6e9588c97f13bb1f6747439ab9ff1ab9ff1dc6af5ac5fb57cc75a7ef59cec8e78ec198e0f20428a441b53e5aa9f9eea837172439be38dce2bdcbbae23719ab07db87bb977ab8252dbef25fbcde43057cc7ed7ec3715fb5d258b6c57d9be4f4992edfbccfe45713ef6fb47c27898d89fabaf63465424c9f69bc86954c8f6db89494f87cc9533e3593fe359d8f542c758df31d6cfc059fd8c6f273603cb5ae76241f5f455d8271bf1d5279bb60ca40597e55befe1f0b06057d77a9f6c7ff533f9b2dfea7399abfbd6efc4745c1d764160f52cdf3d0bd661d2e11c969a7cd967c1ad64638c65e28f32f3bf28335f068ef2855b5a96855d39ab677916acb3c23b31fa4724acaded59fb24b956f5f65b8b086bdae9ed3716d106eaedb70ffac67d9b7afbadc4bdf5de36151fcec0aa576198eef3aecb3573b4ebbecdd224ac93ecdfcf9b86ff5df9ac7deed4a5bed951afdb4edced3c95e78098559f4a7d9d57abd529d57d573bcf762954a7eaba9bea566eb7ba8959a5ea584ea9eebb8fdef4befbd96e47fb69ca50de7284a35a24bf42a9493f594eeebb7f61e5f3cfe91eeb309938c7dfc33a3a3b457080d82829c5a31cd57b5887c97c9dfbaa9f3847a7258cb5ec9adde79fc231a4de53d83f06d70cab5296df7d8cdd9f912fa94a799f7aef2fd6b9cfc22aacf3ac191d0feb44ef632cfcee632cf5e1f59fd80501d57bff82ea3d1c03939c96677d0cf755ff02ebbdf7700c2dcffa0aa4de6bf95cf37eae19595ef5ad3ec6efc4ba8564c85a58a77f66423fb29ec56059b58c9fd6bbfc4e6cc6c7d8eac3166ad65fd5b35e3206d669a5bf588749cefd16acd317b3b08e0aeb34ede8d2ee514babfaeb5ff9ce9ffb17fa57eca37e2766e32ea51f0451f9e247268b75bf7a26392b4cdc7fb85f611de7b0ffcc429771b39f182f652b2b2c4aab185827c677392bdf619d18d86bb8868ead6a2e335a5fbc1f95eefdd06b2d1f632d2bcff23bb1d58752d67a7f1d2639dcb7bc0e931c19cfbd4e0f03623172ba6f619dd633c969f90eebb4700bd671a11a3a1603d3581f63ac0f5d88855df577624e537d8ca9b0ab621c9e5a5493fc48f52dcf2487f59d0b02ddab9e497d9d594ef72d5847ce3a2c59587e588b6ad1ca17e56be2d0e68bbb0fe5acc33130c9517df73116ca9f1854d89593f2bec3ae177498b870ba8f408c75f885daf40224ecb043914c1693b41e8fa9be042be0e0c6d8d1114d3e494111534cb1a4c23194c0ab31b9220748cc641c764dcc82cb8523955c38b29cabfa1e5478cae44b7ef7dc87f248013cb4d88cdd2d8680f41345a81671df31ee6b5229855d39dca79efb1476cdaf492b1c7163ccf542c7baef58f7733665597add7bd835314e2d922ff9f5289cb32cffd423cbf237d9d6b3cd369f4efe4f942fd794f2e74ffe915348bee4dbef47fe03e1c6182a1663e1fcd97e720c4009583bd35b3a70a17ffca880a6135b2597da2fe79c53d22b542e85c04dbbd56dabd37d4e77993a02d5b474cbf3ed117ab2d6e28d04e0943e5970fa6ddbeedcb62fdc6c3c9dbe7a2cc8dfe4c76cff64a53c5929f1dcbc163c16e66f2e2b1071e953ba516c044abfad3d173c16e6669fb35b7b2c4cdc51075b20411e52e322a7ba0ee3396d3ea5a512cf1588a873ca3ae7fc9e3e27a55387ce59bf526aeb0e7fe93fc49f5259657d99f6a993dda9fb2604673955e7309ed37ffc3ac49f7e3391df1e0b4ec4f58f799e9c059741a9accbed1469cc37d2ca9d912c4f119c747ad3e7866d94991ee36bbe9c12f76401176b0f19711a4ff5de49d5aaaffe4e75d088745b3d79aa9e67ad17bd3f7d2edbc340d3f0de469e3c0fcb97aa232e27c4693afa9c8ed3a4defd8f380df7aab74fc469ecfb7736f5f6ebe729d5a350a8e711330abbee5fd4d3b73b797e2a55bfea8b42c8db8781a661a783dc737d025a0d47997a5dc7a150a8df50b1a863c557ffcb114cd2696e2028676a72487f16e9bc42c35d91c19df684aa5c279f10c2921c9f004295fc4410906c3fb3e7d9cd6e744a561317a30c17cab9f393b76fbb8212d7eb9f6c4b21e4fa199199620c348da9722955a6c08414c70ecbd3b773823e5bc987a2bdad5f519ac9351f9230e9d57ce211b882c3589443598b341f5903331214229a94425fce297de48bbeb5359667c11249264929d28785ab9047124926d524cc8f882ffa4c18c9aae8f9a265345a11eb6845ca956d63b1b0cc6d93356f92fdb75ad465d3e491d29ba20dfa281cee64548ece699e78114b6e6fa362ba210e1e38700edf5c7e6361f9186d6661c1a10b8e4a667c0c1cda960ce93efed34ffc459f3eb1295c7161942eebc37e92e9dfb83e9c71f9108cf1e1b77cd862a13509731f1cce802e3804bf0587adce4a1f7fd1ef66e016963519b8b682124ddc93193481dc47c2a665c13e533c1d73d6eaa4b14c9a8de3b934991c9c326b15d758704de29afb4499702a65fa4b93e903a4f3a8b4ac9775410b19aa1100002000f314000030100c874422a160308cf334dc7d14000d8da25466489649c324c6511452c818020c2180000022002044341b0535010668d81b439326a8519ab68420f9c4c3fe84e219fe45ecdb3f03b4dbbe1883467966feb2def20f0884b4b8b73e3b473f2c838fbcb77ac380db2db3b98bc827b3f7b4c8dd8d8fe7a82cdb991d3136ae340a1b91ee97a4c28b216e871996c3b7836ca9e6caff5a3fa8c6e124385dc06fef1311c0d519fef1782d07e9583055c2adc81bd2e236ca2ba32b348cc9e37da6a959cf29407579cef0a93f64e7da8ff15648ae36c4a4179f8e69ce36041ece8aab37c836a42da0789d5df852101bfbd6b2085ba9f58e119953359517d14f213d3165fcc7b1546c34011ed9c4ad7dc7b3b690b712b0c9da8560a5c0fae7ac7e21d2586938f87e6252c212f6a48057727d4a7b5da4113fae715af4eb4aea02d99f6c09868f5260effe502cc1c9cb930f47035e819856c56c073c50999b21e1cc4af0f479b16709d6c846af52e476a164ee02e6f2411fe45fbdae8a9aa67ecedaba1829b1b155ba310a35cc4e4a89a4b0fb15056886af0898a8841168b3fe45cac483c5b34f8327f33c45055c0332ec6ed960020507403448d2b38b4bead4e13d7a0edec73a58550142a93d99f4562276605766fa754bfbb0f2f5163129c5fa45104d52e6c694e8b0d563c05c823c56d12e67aa778c5033443f8fdd692610fc2daf2dee5896611bfc7cfbcbbf33231b2fd058d98ba70493db4ab79b036e2ae5a2a650a980b1c70ffccb0baf91757a2931c2415ee5bba62e92ab3640ae3f81545f43e225365c72b1dd2a3ee01ee4ae096a349465f0def07ee30b2ec204bea2dc07db052aeddad369f01c2aa080db2e271ae73ce801b76878077749be29f9d515fa98b605593cf6586824e16f2559abff887b8f1f96a696e05aa225dfcb7c81406665177aa933fd0785bf54e101b718938f1ff2d20ebe65a2e2d4717c6002e308fbdd954cce736b81efeec9753080cdb762bbd9122d49ec3f40f4c80c1111acd07e0977c00dbf43808d5e0ce6eb3730ead42c7a02d3fcc69bcc82897eee74c09d57bce1814a2ab82fe5f9041efd0874ea09593ab15480417345fcc227bd6003a8890e4878042fa816b295b27721d1562deb7cc01d298e48a67e4593662691a32cc1158f76d2c69e05ab50471d92a6302d2725c3d7e46255b9ed59d818e7073a71a43033a71da3f87c48382c4644a8c6845188815f14765a8e5d48e1829d7f8d0e2e95f221e1ae79a132627bb350145916382363e640d8d005ada811b327e16405e3b35ea38a70462e3a32c0793345210696000e3cb65f62e6db51fa629c6fb885f1c751afc9f29b96638adb63e03c84ab18fba6d4afea1f89747a0d400dd988f1377595bd6bf21defec1bef371240a80f9fa3d40d06fe86716a11fb31c43e3e351abf412f06b2d6d73ffc588486d3e05d272aba1d9f71fdb7d59648b0fd8569dc48453ae1a90309658099779234894c02886d4c5678a6043fa180281ceb301d2ceb4c6b0698072064606358af79e69ce80d20fc2524417dd7e9d29f35181838843b31960f07685c33aa140c20c510894842d92e8a30f74486b016daa57e7e2b608a30664c303223d7e18cb163773052bf4c3098ff8565112e0c10dcf55e1cc02074a33a0b532606e95b31f9cf5a6d8177e58037adde24c2491a78b0940dfb58ba7d945a9ee08e1a44e50b77e6ce08c837f6bab38b0c441d047406aad9188821ded0c30174b29f69ae62e64037d58133274ad685c8c69277ac6fca64ed140f627e8b450aab7785fc601930daa47d5a69a5646e7894ee192a4d35f95792d0d3e63e6ca58baead883584499ad55bb57f3f3afad2de86d4b48099b1409fc21f7134203a2f463b004e03c7961ae08e610e68e9c57f46e9ebb6d4697f74ee7d09a8c31a54b9390c5e558cf54033610cf6d743945dfb42ed86c377a2ed28c82882f42dbe99856c18d06252128c97d4eab668542ee8a1330028f6fdaf13e837518285e6860ccac9bbe7fe0d7ffb3ec5de587b445e6355c5c549780fac49ad0854df80043fa41b7b1e006566c38d4f253461870207a8b89208c99f2c9b1fb30fc42dc54815b32590483224df81ef3f6df33cdcec21f17ce0cf8a07dc0a7f7caf646fa8e9a1971d46f3dd55389f99a3189e7f655fc9c825e764d75e417d9985aa5df90a49bbefaf20c0947806d33b71e7f9c4f9ed9694fc4d145a107af9815847ccf2bf38759e8711a6198323bdb329cea9c9b9c7731b8afbf4082658bf9992d6a03019baf75d86d9fa2ad8ec1cf2f53e649c8c7cc1c91cb8f103ceb4dd786ebae254e14f2d17b049a5abe0033482b580596177caeae32dd663dadadd967768b38d45d37acc540ef627110e6cb8b894af3770934d0d6fc26751dccb450093c3484e7a4c4564febb9c341a500f8a84c5223dbdfc6278d44072f12f37b0c06f5e1a14cbc2e06240e703fd4167442843e5f657e0b4744cc67257055d115c255ec0a1d5823961870c0f870cc43b6e1b44f5539726262b0b302685b91f189c44c2cdccd3ab14dc9be9a3a6df7a2b7db2eff6bdfbc39ab63e0add7cdc397520a7c4d120e978ddd28311425f4d09ab0948527f86e4dbc191cb823eac092e472f6f971252c8060ab2ba737dd42a5c34a6ee90e25e7548287ecec2b3e7706f75935e324bfd87b9b05d35fce630809d3e5b2f7739e1e44ac7decfda3c1fb07c4ac5dc64a6f79990df4ab20c575f89acff0143181c008b47750e819c44fe98d99f56909db172a9d17ed590627afdc108d62d6eeee20fb1faf343fc77fb10dbc2fe7507881afdf8b51ce3877409cca597b0da25a5cca59ddc5c894ccb253267c13bf4bb24bb7e00e1f1b55f15af3c9dc4ecd560304d2c14f8a11b91f9316fef938e35a841fb5dcb0fc7a156431d0284c5ef47f9d50080674fb05c5273b66ade25d52e7fb0614e81a2df69ed8c69ac36d22b9588be15eefff149073603696e91bd07de22b7e182180d8fc873b2250ec1947f7bf5cade71d8795c59d1ba829fd8b2aba88552b6912e6f0d33d0cd9f48992a8c6c59817b4fe2f46be23ebaff4c83664be57015d7009e4561551dff2e65a1fad0ea03c6f509943c3fc1b8dd39b8b8b629819aba10a1a7ea604710e81beac52c9232950b7fc761ffba2742f3346a8fb0cd9b4e6ee2a3243e797285fe2f836d4e78cc3aaf7aa33540a9e22c678e2e9b6c418e40426045b94e6959640a853b573387eb5382b37f7671ec7f7cb9f257c9f2aa8c0e4faa5b9ec92c588d49f9ecc1f352225ae609f03828c291a227d261a912bf4412476a21c19d8745822f208379a7dfaff7edfd8eef83cc359104ebe3942277c46f891d3d683e74393f5c9fb961b398d4cd5c4cd51b80176c35d6036889124a9bf0fb866da2bf99032e5479a52c39e756172530000ffed6fae656035e86a80ba28e477530d150834cb707f1bcb70935e2f3807fb72f789cf6e58364839aa44ecefeeaab0dbcbf693336a5536d6206d27ead004c845605bfe73ca99fa110daaed3cd18a1bb5e2b869111a0840d69cbc1861b36e0269d095375dc8526225e52293caea77645c60c794020e924a73a7716701c79d49114835a0185fb37a18d3ba38909b99d751c18aba8f1f3e9d7a91ae2d1aa4f2df29f78262d804446517af857659cda729ccd1c7e55c095797b76d93bd1f99c5e89ba2f0d71709d1f6ed8033a054cc90ce2137efb0caf15ed228ae7075082d5e7b498717b523d45bbdc93ac2980bd4ac2b7d6f015391d41f20a41056586e2f8181689cdc15ea3940a285a20968a0713174fefebc880e93dc363b831c875c0fed938dd7d28da5da6d2e699ed08044efbd549a83001f9b3b75efa8a8e01de46cb381864bddeb8273159a62247aa0b77a2ce59cf8ca26a2081cd03ae05215b20806b1ef05e91e59ebafdaae80c935ddf0366c7e729922a7b593835698345a084fd3ed02c443b3fce90b44d814d26dc445e4771530b2c466cdeb49e4cbd529c4c6c1799850961b3be78bfbd3753ea81567df85e48e7bf2903913d9c671604a0cd7ca7c095fd67d263c446454b6b6b3de74930e88f67aedbf3cd02cda910b0a6b39391d79ce43fa9122b327742a3531551aa54d11a35377100428a410a58c185b58206518ef469e2caedf6808ef4ce164ac790d094a5c2c422fe2b823be53eaef0c58979116b83984aa51b02da7c72bc0dcd2cf527b1ea022beeef3e9e60422abfd3103b5da460c852c6fa5875fd0183c9b85aeb64cdab1a6c360fb8d5b2af6fc919210351dbb4160968a2ee4e0ecffb0947b8e07070aa90143800205c66cf704839bd8182b62f93d01fbe0b9478e8b37b5b9b3acd441ff1db2763585b9c9e61ec0e70a7b302abd6782838263a27c6aed9cdff54f9b53df82323bb6e7475b681b732281760b38fe45e9b3f7bdc754d8d6ac3aaa355a3d03c3a2cd6397272af7ba49db1d8ac9237cf08a1c453ae816c0ad9d25a8275fbfe72a0c39011009535234528f7b0184db02534bc3b7f43469091d8284e5ecc2d6fb88b9015b58b5481e1a86789a545bc0c9d5767dae9df29515b2d36f5de592bed9cb597384c4dd2e3d2743c4cf50301c6f1176a2d30059ba626d5a4a117c6bc50650c3d3b8c4b45fc3ecbb5aa09723bc49ed8d7f530eac2bde14cc424e41d0257604e7b221bcf80128a8397a9d0746e515ce61d0f5a1fb657e949b5ccd7c1816910863f5f372fe78fa423102e4aa3ed5511923d9e195514f643c123356c86153d90ace0a51c069ea2cb2f6ee7e9e861ed21e9a8de6be355b9edfa0d4475a9a2eafd09c972e8335c339ac8ac32afe00e3efd5e040c4995c30fc2f2918d0d38dc5fae39c55b7fd926d42ed681676d0919c02d0000cb7b653ee3be832f3138d027851f91a7d73ef30b206147de369aa11154495d623a880b869969c8d4a544744d8bd6bb8ed42009e08692f699e29ce3792c9d40b603327742d0e2226952b061bae44bd17774c3dcf0746f5b7dc299143bfd5a8dbb4b8f0268ef1e798c05805d885893af2adf2d99adee694b4856473a622d5f09a0fc62c16f611eee00123e40d027c32f9dfa0b6ad9345a570b300c441689824fcc1396f990d111cd545a9d344bded3302c3aa109a7b7dfb492c291c6a360cf95453dc4f5b8e17b9a245638056320df65fc19077099eaa615ae8e83bf955c69103e2125a5c0dc353808056669483d708e03c9807ea87fa95b06fc322db320abb381b50c07ad96ac539730f1483252d3d6bf231361c4162925687cd251e832f0587ac76ea2bf8b9f90bf0f4dff92a4a86a178f1446c88603d1acaad34802cb226366daae30c2c4433e640a2453b09228bcc49c9c6ba8129b2ce093cbc62600cead1c50e5334fc24a67f1ffa82c57d5e2538a5d7e0a8fcac65cdb0d94852db5cad064de9c1b6e71c58fc3f362ae3d2b3cd787af5e94113df65a6714704d678b2364ca47ec1945c939b7f973c7820f3eaa96395d7581858d147c2f6b223bb8006cd4e32c1f0e0d4decdee1d5972858597880c137c8cf379ced020df3683e74cfff4a2d0bac3369587570656d2eeb5b05542409a0ea071bff86cd059df999f8c234bb134e42c0bd1fc27eaf47f5a08992b79147e68757acf22f25c0fe61074903707d9768f77e8550a78a0a7a6af0d1e28a3a0274d686d8f9efe4b543c96d9f2368f9d6ba508e940d5e820484e6ea0b8a46410150ca97008a7f4dce89f095909e2fbbe54d4fe688dc615667873780f1d8188641c14fcda992e4614cd61ddba53dd156c7d7ce702c8d54ffe124afcd4d7e1a6ac586538e4c008703179752a157b9cd96490ef486b17d6fa9c5e9ae320d69057b8a877387fd19ea287e5d893f02138da0b0040d3aacea66ef4adb19ad2527dede889ee94427e8e43b58792e0a7c725993e9a4e502a6df75d820c9f40112dd67aba027412651210ecbf9750a77a0e1dcfc3b12784bb27f0560546d76f654c07d631a2343452b58c395abe0bb7cc7232845b6c022695c5a7d2559291c5b92c4f3cee50ebd45b8065ad0982e5ad4ca1d7caec78216cf89b8b332490aa6e04288061c17572bbfbb2e331cdc76c4d3729cb2224eb62719280b773725aef0aaf15711d19671fdf1791915ef7566eac28d98eb222eaa4f4aea5bb8713627bf26109ab807e37ea006940c039b1b6d3d3a8b044ab7f9a5333b78c4872c880da4d855c96beec66b243e23a6b2bbf76c787c84c15f7321955f2da37b68ec28b5f89af96fd260f6501200fde2ef0ce63d6d74cc1a81a4f50109dafb24d55c9a544dace0d516394004f0f64400b958e1ef5a8cffbac647b2ce7a1634c4be4eeb030383934aab0287f526ad52cd9a98c85fb76c28348c18828e7bfd6e6ea0fd85a317e0008312c735a3c19264ecc12d129ff759016ed303431e7ce59cfe5b3cb369032298844ef0ed53127844b97dd2a9e46a8cd410ae1c8250e0da83655828ac11bc6d678f926f8afa12bd632d300cd744866b8a07db4f50f0ef8b8e9a033eda8cfd8b062f9f0867242085c951d12f44424f6ebb1ac13a565fd12e9322fa31c33e73ca91d7a8ca0ac58e872c2c3a585d005c0e87791cb26b163647145d02c253106993a6aac222849e64e67b311fb659f0a219b3124b1dc77b0ae2401af054c58915586d36f1538d39f38b7e887fa81502878a8c0f9a419ba9d9c729dffb2c42d1ca539c49e78464819452ff0371f41078aa5127f58fb4119b71b47624f153d6db187240774f2155eeffb04b32e8305c1f5173480e712e22c4eb97707e09681b8e71e8ec04c945ddb7e274929bca80dccdbf438e3de85cbe76df96ab9ff96b8b2a2524c64a5f39bb4d0908f0b6d041d9983434b7917930eaa3d87c8ea7eafdef3f38ae595c9135d0f36d1ebbc0beea4afe2d0fb6626d5d1ba45cb8861e5a033e914cce2830d229ac26562d9963b7e8d62e10a5dded8fd0223e4623e41c020729cdf993f63c881cba6f59ca05ec8e59854a8139482b5d713cd8f317e82d1219902302c6b8bf609fb3cfbd5255258652a170f06554b8f08642b3ad780afd100055fbb74f3467cbf88361ae16ebc333ab1d46fb5a6c0ae932989062a5deb99e2676e751b52bfb2e55742b440acb1085932ce5cc9df6a8859c743686643043df302f61a8f9f1d80f06b4e54da97075b454b1c079623c9c73a26958acbd7239a5f3c394230e574f408353bf892392d00d2bc32f52fe88e5f95c3d6defcb6545541d7b3e5fc882751e49e07a88049f5f394d53318991e554fdffa7e46eda3760e7fa67bd440b9d82d4220386fa43625dd823e5fbb0ff8dba1c57214246170253b7cb526fbec65a516e766a4c12020a9fb7c5e34498770dbdbb451293f961d67844f8b3292e20062d13bbd9da2b908e1cf5f90fdcb95f0aadb000e5440b89f19a3fb200024f30c0e7fd2cf2a95964c2e151c38dfbffe04cffddc0ced24e2f83f2e8f4da7145f41c201dcc3e3a958b13c6f3f9b8e3a142096d52554671f24e096b5da495631d89e2faa73130fc176aa10c01a7306f2256dd0db6bf6375d938c61921793466d89529c8209f3ed666b34cb1bc95e91013497269b78e0ab0916ba570a5ef17e9f412af0b1b0848ca337d587db277909cec18034e1625a9c2c1b1b799f197a9fc903150d64ac8e1bcc5dca50f79be4514255515f28dd9b07f663f2d5697f221d90a264c110359e8f396a2bde46e882b72515c4603c32ec20a68bd2d8fd79aaaec60872d2148c5d363471a552cbf689c5a50dbc79fbeb22115fc3494dda28ccff5438377edff23d333ae3301fe039cbfa45044ca962a26214986980f71ea999e1a9bb46bdd49c18f3fe4f0e3bbc1cbc7d3662d96b6e2d47132ff7dba64d39dd77122ba32dc12a83491c173d0004a0aa2fee212b69c75d9de3288ab2d41900cbc871a3f06938422ac21602392ea0867e3029a618011d9504d6f39cdc0929dd247b31eae2937a560e0b5d7f561d3f0f71e25a99b6f854dc93bad295308cdc182f3b984299f52201c722141716154d532e05572e946f88dbbe9eab832a7abe45bac5d5971f3a5eafc3dd2bf457a291705ea819be3fdad7eff4855bbcebba9975c6b374e8562eebd65461d88b809e7bf9116c6a933bbeb2e87f08d22099fcdae81c12c1d19c9bf443c75369df50d4c17ae1b56c1b01206e092e942326d0e06560d6d637b37e3d0e5f92e9ca7193bc0a6bfe8855983b1bf81c50fc63d15d316861010ff2df71ce2f15f7d3cb7791d588a359aa6013eb457ad58d37c4c25f678d080b95f8bd8a3d42c99470e2563115623c77a46cbe6cf63de55fc50685035c86f1fc5b6e298dec7b5fbafe48afb18a7fdf14152d80bfb3077ece59b105f30276bb6b85bd90a707377d2d65bf3fc6bc4273d3dc363e7b8e8e97a95c659afa7216cada7d15429ae28d829a4491d3190cf9c6fb0df37a611a3b1cce1f5ebff071cc808c50c68cea8374d0446d700ed3b5b460a557c30f57236b286990fc5bbe468e7dabdc25542ee151c3eec521795453b8abd2875937b4c50c3eda3016bc64ff51b676d3f59b61cc785653a80cd326bdaecef3611d6bd41364c1888e0f679d8d8525cd9ba14204903063dd2b55ded99dfb1f0805b5245846c62b97097b0bc34d70ff23df50ca1ea0611483a7627205c949a79c24258f75ccc71d33b599ecd52f56199952b7933641fc07ff48c1491686cf4649f65b4baa17a28f778af650ede6418638323698c2029e7d746ad981620f9cae7f490576d3345cd678fc5fcad4a052f583abea161dacd4d5da28e923aad006e30eb04d108a0a3619fc43f3f3362c80da342113eadeb6733ba474f9339dd94c97a4ba537c9f0badaf954e0612d42146e0231a54a0f0bc5bd6e925a6ed18ef5358359de35641589f6f2cc042320cdf37d3472077e553bdbd54c7bd19cb07bef422939b0a0236c876301e55e746712d3a5c1c72c907f0c3da7967be801db1ebcc6701df9811e097c0cf8dea2d37918a23831b801c4af372b0e04450af4b9354f6af827d4f22c3a80818e62f3ec8a765ae2913f6d2ee810777af0fb85f97f318c47709ca13e2485d5327c1fce1e08f78b683a0393171728d583cfe71572fec66abbc487850f07fa697f8886ff64b2feed35ef8f0dbba5b5fcb5f9d7e0e9bd2676d96dee7d500215ba45fd2545806f6ff4781233eece26cc2f670a5198a3bc6d210cd3059228c25ae00ef430c9078a1a65783a429ea706d12d0bc400a6de83f2c612749b5295170c46929b8ccb4dcd727e80cf9d448d6e84a665d54de1ef747885371981523b244ece97361b8aed550d7f51bb2eb228d721c8cd11303bfae4064c1760ad0a131f519351fcc73bb39b96a27103839cc05a1e3c271edfeabc71b0262118e9e182e3d2c6410ba09feb5fd66a4dd11e237e6c667270dbd1a354811fa5678e6bef60ff71057c8e4137da77e959ed2064969fa6a90307c2f14f065b1d5c1d9ed48bdda854ab25b6c83307709c3da72636fbe309183f55849649b490f5efb8ab293c5b607dc82ec7f579fe1da5afaa9b99abd0cca3d9c5381abf79ee3f0b2c238a367a91bc08eaea59b493cfe619f3a8243951b6383238f267f4ce86c592ff7f242a52b663ed73381044701e9b5eb8053eade6fa25881bb2bc1dcadb04b82fad6f7acc28eeb80c1927f9b5072c7b49ad149d09568723d525799a0163dcb8267b973b4245f9fa2d4a29067b6251de48486ff5b1e855b85e1599fec940e4dec6c785097bb2103db47071075c52183d9a7bc75fece935593a112224d63b7a78a60472f87adb81edcb488fff6123bbe52557f53525d664d263db388e2276302ec87e83b7eb69c7532828e981ad159b570d78b393cdd1e2efe44d886c79ca6aed0158eda461747e7d09675570576cec230d46d2e3b231ea5354c92d83b4f86dfd336125418b171423948bc7d347c900578e6ff8c9743ca668633be9a1523a89ab9473ee23772784f7821e094f7a9cb8e78c9f42a1ce4a4ceff2971bbaf462d2dad208af271811bacd820c885a94f5585a90ac75473c0d2f6f83b0188ae3c8c601980487fd0e013f2ff83aa08f0b9f18f3a28f7fd029df257d246435251c7d83f588af5627de3c4906112f2880fbd02d1f04dbc777943a9851e722abf81644a42f581ba9f46162e7515015217a68ceef6c90da205a3313ad288c83c9700db30d78594aea8468257e765ddd7a0e615bd101e7eeab3e75307d59eabc4374c11341ad28ddc81db0946c938b1004e1ec5f32fcebac62ecdd3efd59dced0a5cb8405cc9fd5aed4642cc4dea78a15d5fe76853f1469ff6fe11bb11e673be1371665ed172e316502965e6d3c87acb027fc76ace695fc88849e7d240fc30fc0a5289e1499ee2577e77144654baefcd5978828a52b228b0e3d429884c2b17de2a28dcdc1423bd44131e8762ce7ebb7e60005b88e29192e4e8b0ab51a51f49c965eff5ea4aa486d40d7c6df222aa7954a1fcde8ce4f2282c94439adfa95144fa8f106100e4511533aa7575f94bf2e1759a70f750215a370926c6677805e2206cb97aa706fe6df8917889f80670cfff3d3a457e3ebbd2be6e552d5d06e6bd0b8b4c38892b0de87b25b22fe6bb1dbb414d446bc5e20a4148989da9314a603c1f2f3f578a2fcf37e73fab91a3b10d1db0fc413e66064f392ac7ddd4f608cf2e976a02035a14bce18f1e4684ce78d2347fe804e9e28f445bc3a1090cec839f5dbba9c9f1411c718d677bf7f7702b4556c870a3f21cf4ae761c55546c1549e2a05523f30cb1fa9a008950d6645d1afba9faada079271d137b00aca0bd4f5b9766627f864f5eea4cec022920ebab4154c53f0b24b50003c210c6789069a3381de56931f2a1aa11f18dd112269bdbb2a152eece56edb76234b7028e406d92db2161e695b37f3ce565e1213bd51067427175bed7648aa87168ca2a3540775a5e42c69703f21971fe725ae14184a08d3a17d7357e6d12764ea33452938a01320b292b1323aec90fb0e18cc54a7d3a8d268838c8b11f69106930619072ef86092b12920ce9c256bb8d08c9582b38048c68d0501518f73c4d776801e57fa278c5c93da278613ea4719a92bd72bfb23887bf697600d99e376ddaa12d4d117eefb4fab880e597750ef8d86d5298dd56673b5d8823e99929861041e5a98748a55796ad086b9564e997dc06c58aa6334222d56ec7b53e5ad9ca8a70cedb545bd95634a9f458123e76e9540bedc4bfb123e009286df280ef29abfcc511f2c405b9dc1efd957102fbbf384ba38e9830be6e40705c871280100963a32da8c38cd8e8044a685ab5933561f82448f0b258d0015c08ebe2c0fdf7cf4c3da8036a6db78a1cf6f2a7293e95c12c381a466208304f86ceee2257b39942f48998c17c05010b1e387ab0476a3802cb542b632aa7b880052fab8081b751715f0d74a65fffeca0c1c72b83edeeae81a57048ec9d6cc8f92973a1a9d87668067da3dae5691e1d935645fd07fecd1941a7f3225db766d123f20d50f56dc83d92428382e14ae9c2fb6c8f05171c45d87cb88cc0900440a0685024936d1c673552fc594c9aeeaed4aabb4265fff7385c1f7aeac831122fabd45cf199678e36a5c9884c7a0c7b60e43d48837fb6a475f8502f32d3a5e4905aa70e2724c33a352d4fd6cce19c3965acefec326b12ae52598b5c89225b76e255030f28153dddbad691972c2086592b1a338c2478c597ad2be4a04e08c58223e896009d9a75637a84236d06c377e201961bb4cd8693d414c7c97eda7aba093e7c3b0c0090dd6f4aa200751b27de53f59276ee69db5f6c492f7ab4e2d1a36c28d083e91580866aaf24448b1ea19cf22545a4db3ce30089f4334423d959498f6c627c4f3b5b1ce9c5c122d4c3b3674ad9768e3ea8b7f1a41a8509f55e65ca5ffa612417c093f44cdceba778d17d0920ffc0fd4faeb137ca6a5adff62bed81086d2e22b61de1294f386447c6f607ef9f745e19dfe20a083123c492b6b7a9741823b86142b0d72b775708ee6a7627ba72747330f8200c1b25dd890be2fb52438988dd919c67bc2203092d88b1b27aebd4c8199bdc239602e73aa2d15babccf1cdd4a1fe490c52ff4aa43130d442bd5d0a6d69f124ad22a0571563cb9548e34b184f165afa8cfb38b893a4e5fe281f55511c3768bf240cc591283365fc8756d336509c3f904aaf0681e204c3e16709eb5352909ce467b2e9b2122c8c4911df3054b86be401f5671e1fcc5590572468fbe1acaedc08417fb5e59fb15261e1e502da5cead1f25b80112b06d0c00ec562e340a85b87c261134942d098ff5f0fd3d9c40d1cd94e01764d86403425313b6fe00b839135e61523312ea03d62622db04f52bf77d5ff62cde62f546e64fb6ecaac4bc495177c1e2a39a216f66662966516d7a85c36cb72185f6d9bc517d6006174efe0d944a254f57ea9a8f54bd2c2b9c249af89cfebf5fa4b4b09dc24888af817083738f3cf9275311505668ed323166f89f8f4b59e500b7b31dd5aad2c8ecbb059301a546b4217c7458f4161dc9b1b0b4ebf9b0e476d7921227b48c358ac858ea4ab47b0f65e64dc971a08a628c9ec4086fe4b2f3b496f394c2525d0e1f805c1b6206965906f8d74765d3a6630a14116114775d5f50a860f15f042f43035443bfb324d51afbe8628478555227e82a83d06adb2c252c3c8c59ebe9a5b99c14fc05ecc8367956ea8b885142a068a9441bd53b79f96d4e40c559861ad8c045377a8389fd7d5c1c520d794542f4cc2953c8af67959791e26b6493b22f110b475bf8e1eed44826a723ec0c97913ca19bf53ce3ceedc6a97cf72a398628c32be937b2ac8eb8e9aaeabc3b51166c8c2d22c4419190fed8af644c483fb7bb3f840562f2ca0552800c49e683ffbba197ec178019a71770207e1fb1205bbe845618a249c3f01009e7ef8960b53292c2ddca63b3a77a150fdc227d1ed29697e58bb5d3560c7d4fbc34fc171e0b6f6e15c1ea644a46130de91d604d3b44bf24de5559a9cee9b52b4b14b9a14e386fc2898aa7f3be40895881287394459da885e8461cbe2df17445625ab337aa1de10a8b0b82aa1c1d8da277db6c2c7911506553d8443df8263ac7f0cb72dbed2905bf075d58b57447bf1f92d681eb985c7d92d55c7377cdbdc06c519d88bbb303dd0cbd7df8793cc76cd5cba698114258cbe44c7c50fadddcedae219b6760dd95a1aa5a27817be4cc8eef83f36367ab9550a59a377ed7f0a36d0ccce6bec8cd01285f73b718ae3740277469847dba85693618019784946ccfc6b0b8a118e6c0b57bfc00e80958f483417be1122e6f16fecf372c5de6e8c01b4aff244f178e44f90ec77d4189484021a08b0f940f8c124eb7a8ceb426578f638a759ed456c08720206f29f8305adddaabea8153bc5122c847a63a5e7ad17dc92428fee0c33f59b38e0c69032b6e842e1554a43b6392eccda4bd70530d025a490b27ee3c9f3556e41ab67283ca32072a5ee58dcc4e23799021f9b900539a4a747b0b79ad3e9305aa7766799c7f5725804d70eb1d3b61c6469536a77e8a04d8e46702ff15edb6379910b314935fc16c1473e81efa1b2d03c43daa510059114a3f6c0c62f92cb264b651423b49caacc2d83240d152f2364f1e4bdb08580eea280a87a5b997af67764aa813dd68fb4c848f06c4cb0d11419b599fdce171e066e4b6537291ae0a125d64d1c423b5219549939c951207862fad5aef6831615d4aae791053ed5e4bd1a3f6e05ad75bfb221fbb543b981719a4cfa2500a86639d14a03c4d962bb4df5c290af99861f97f94dad2dddbd6a6ae32c50f47c1bfac139024156aeae853a82faa60c5ac14d7cf78fedd61826bbc4937a5a681762f98029192118eccd9d7b0a1200819b55c9b2eae1fd4c75f26b08f2198644070716806a20f13b96c822ef305152e109a16969c3de8bf756fd261a8e76560a47d9813209ea39d33928150eb457feadcce891121d50177cd482fa980bbccc8f49e935fa9c5f1fb69909002f838996089fd5c3903021183b2781a480b959423fb60e611ae76a585f0bcae601b10fc575803e3d4a298c771d17878117fe424d3cd67ee82b455389b0f9f4e037e8e95593bc3cd13fda1fda82db708914462d8d1e82a76f3c1c8e136541b072f621278846ca5faaffc82095813570cd91c6c95c93d079f52340fb6e1c112df0105440faca129dc1e8d248c69056ce4fe97c49795ed487bc76304480bf5f235d4278a307a37bad9af4c4b20d8a3772d6848bb59cbcbde6bcebe724ccbf0fdb6f9d23324c19d4d97cb088ca884b2d122d3083cd382380aeab2a86a2b24cd51c276cc6d20f032c476e02b767deea34ff746d9714ae833d064fd1b5a7e1a54b60a9f56a985ba011110d223f64f0d298a5a4098467c46fa93dbf6cd28ef2630a22d2fc5dbb110a0a6ee301b4c4912455de11dfbceb526feb72ca15c5dbd54f6df4c89cee60beac23636a9094f3d192e92f6fd263b116200b154212dbdda8f66f6441c6c2938f58e7c9ec938b8cce940e42156c9fe12561023ad0fcc1299b253184c5f68cb4e22c58c06723b0b41b1936270fcf80a517b6222de82709290f2a716893b59d6c04b55ed889bbb107cb6dcbdca84a84b7d56459814185fa20fc16b54972b45e870deac4594117f823092d96b03f49d48424a1080495ad7b6efc99102e79fe8b36e35afc7436739b904cb5bc80cc95faef447dd5f3b9897074fb98b029c00cabd2598aca62705fa42fe1cc4c913e9add801121cce42edcc8857d54a596d793378a694b0f5700af40774221808cba8014a366603089986364e42443cdf94324eeaaa3de1de206114820923f8518146eb40790e6d1536488b80b47da5c083a82e439b93911371901158fad2d2a67c34021ff7cd517ab93934cb6ed9e4c9b9d5c32ee5324c67cf4df1a363701a57f57928d9b0055d955408770e313245a893ebe4cb739036ba0c09db422e74312d90d40d32f8c1cf4246da3080334ebeb3bdb0dd09384995727797cbea467c68980a01393dd8b48c24fae311d78dcc621a697e33064b79aef3868c527e0ca2aab62e8e8f52910e16e5e80a637316a2e247003d80134683f393cdfd2fea2e69ae7e0f0ea0e5929a08ad847a453c47abc6df3f89cd23c09a865b6e10d0af289372345faeda58450962a1d6b79d60ed5194911afaf0912fc3c15030cfde948dd77c7957be824940903c265021c806c4de4cd4546d94cc65313c2c0a238d81b246fca1f1e9cfea481ab258d7b457bfb37b446086db2791f1c798b7cc5c4fad92bb44932921da09ebb104625b1f22d3eb020350d5f0f9a89acc1c158a0da1945f61b8650e33e74e906924d08cfd309533ddf452eb53bc5c1d541c4c3606f324b11e9d13d85198e32e43fc2e223dee04e1bf7957f533d97bc27ff2b7285c7e695c53ffd3c41df494ff61ed8e2d4684401b10b36e3add938e728ed98f28175ca6ee2b0d7a5974062a2810cd2f0259bd2a9596fbdc6628d00f02517a8b7a1191c454fff844a1fdc63de49c55a3e78cc0319154a67ab811abbd467db22424640ffd8daf44051b1414becae8f098194a7db9ad608b86a249c7444b4513f90e39bcd95b174a4e75325b993d644593edcd7486ee52b0ec979d7a46a61990f6a6787197012381017a54ee7765410b9e68c7e6912c40ded26df028cb18700c3d1b46858fa129020aeea110e34d77ba475e4c94688c442cdeb034e1027b7692d88f1da941ac4d80039b73e88490cbb399af7b27b5f29716ba4665aafecc0d7a38385077eb324a03f35222c39d73d05490afb8161138c0d8aec187c92c183710e5062a454f2b0e457a188143a832c2414582d804d677d6073648022ffba03594c302969ed9e36b9906187efb06184e73c603ceb3bac853174831cb47438dc853387d3e56439f49d6141943b27e299feb944ce39fc758381054946c7192f208389ba15b2626d82bcc0e530fc6a1a815fef38f05aeb09ff6da9286121066ff803d9040ae2b1960ef43ec6f20aaed42025a13de1532406b9b3b538f50c39dfef5b46afc0b74013898b0f1deed068f72535dd89eb2073785e9979177ebac35dab0685ef87296a8ee2f30019656d6a41fdb0b9e4fa56c444c86f81b403fe622962e68d60785026c60d488ea16729ae4bf080860eee2ab422ad5af8af7f344a011db6a9dfacda3b09eb64a01a080dfc7518e8ef7a6d0a84bd6410d811471a08de4ae6f2a0e795f89a631a08a24103abe28832a9cf19208f4dc6734b0c3982103b1dc7890e88b3665260f45dfd402626f4742d5af8f4659305c0e8636c9dd16533849b273bf6832c723123172876196d1f9b059f23d8670a02bd0bfa8193188fdac01b09b82fab04976298911d46475efbc3dd4bb16eda5a985fa14978b4f4d732d1fbf186518fe81a3a86c41af397671cc381bd6d6bd20e5bf5ef68d8f8970a810c5666032f185df69fcd704133a823d8392970d02dc9c545a07b76f7a60138beecf0b62ef017e0ecb07fdbdabd82cc0e0052e260276b7f3156ea062284e63e155cd47df08ce2bb459fa1f5cee0d6abf54e647fe78b5b5b691f53b30b15a0c093bcac1348804c6da10de839abf49c5a4ef56ff2116d51619b73cf913f52b9f1f7bf1a82f07771adf7c340061d70d2242819470255120f9400bef1e9aa4e941682382aeb3878568de19599d77cb0fb51e66bb10fde40f3e9b1d108d2314f25d6b654f2e7419e9494201bc2882339f692cd26ba39f75333dd642c2c696bd6436a354579a4585b2b2eab1dac488a2c20902c72438a9bfaffa371a0ff069fbb9bca6c84940541ad86d78dbd9010c8f8e03fe92458594d394b8f3d3bac1b2ed6f73cff36f2e3f9481a9c214f980e557885e1d6b69585a26829aaff60a69d68121d910041245e0226ed0dfdc5f831e91c7cfe874bfe744abb74d29980d85b7895dfd930281782d9227500b8a5e44b386c35cc3117517d57548bec35cfbc77aa40deb20e2d63109394225cba6eb5abd72b2e799bad26c9803db1de582f13a0b6c0dd86cc66564c4bf51487cc24c8173750c78e1a21930e43f266218f88edafe401d4c4d16e2fe4973af81a98b2fd592919bd4dfeeedb46548d5643e19b96aa86318ca4fb6712f0826bbbd934e068cd03f008a4bcec4caab8c4fe91cc0b599da42bfa432a21d44799e0dbb668302a329585cfdb9e148bd39b1864a912c20532c5c6860ebf94cfcdc161e53b58bc8b9a7d308945a1871a64b4548097c704f59c66fa9bcf9ed3539be241b68f2c13e6f218d1cebfe056cc4c48fb88ff2a0e5d1fcf6055acbde9cf2db18799b2039eb37f15cbd04a42b48610a0be609a3a23bcc41fcc998eb88f3fdb2b60c002603a9ba5504ac952ae37321c49552e1deedf7d918a94d2ddaf9e0f6cc35337b2c6257339a908db02ff1d452e5f00d492af1b016b54848217804421ad28959e8e53cbfcbf2cf619eba9e9d60256630e738758fad0bd96f27cae445dfa057ca2d03c1360e9b55a919d26ca35781a9100c7cd6b149463f2db3434507aa1cf3a07a64ef191a8502a5fa01f194302dcdd5bcb2e45a2f6378b0807732dc17d9ce3568215d98e96d0b0837bab032cc72007b8f87f8e0bd4806eb24a3505eb66ff56e96f0809d5aa51b9bfb181ff15a7d1d55ac28c002a35289b422754aee9daf009673eba5bccd8d5107ba768d0d8baa56a6bccc747baf6a5ea4f82ed40172a952fb3d4bfa1829d997bb2f19f3b2e38a728190d1ec59323a55113919c829870475b46c64cb2dcdc911e8652a955b8e3c703202e862e8bb82598cbdf81336d1a52ea9d0754057ab4f533212b4db09d7f7a042e5e27ff66e34e4b7e684392240203bd06936fe23e31f127e99db0f77ba8e1aa0b74755af55a6c78a81c7cea2f514bc673e5383c76f0b209a7d40cb03717c0408830aa13cbffa072eec7eacd24e3f02bedd13fe39162ff75c85f76e2d501dd728d0dafacdbdf20077558413661f0f52079f165fd26785879492122bde297d9dfe6e8ba15782a7da25bfbc6d214b29f2b2a57ad18a6c0720e766dbdf2b75fbb59d653d4aaf632576f2528bb3580d6607a9c6ea705860ddabd7b491184400fdcd12fec66145db8b4a98523d5eb3dde23a407e076e622399a1b01c1ad0ad60b0907da38a68c785a8b37723406ac5ad92bd8c01eb48132c9b155a807ca594e2381425ad5e9177343bd00f6b26a2abaffde34ebad011c37a85fb2f5dd015dc040b2b9496a08354e5cecb6c5cde05de6eea69cc667b79c45b8068184a8495d5dea96621acf8e731551f93887cf508b9c92ed6266cb27da4816fbc39177883f06c8be5116032a6a42d18fbbbf13f38bcc2a3e0531781e5a764658e816a0a06f6d7ff62cb8084cb50f45cea544ceb1f85a3963f771072a212c7f0018508d39bd6631ac837770114f3c1ccc73a4c6ec437a03ab7e0324e14985a39ae1a4183352916ff1bd6ba39c8bbc595a3e2133f423155fa2533cadb313a6e00e20f3428c48a2b354e751c9c90800a27351e768aaa0e368c6617e8df263fa46ff28048011ab6df9913a42c377377470dfc6176125f020d468031f743e9435fda67d088824c9cbd2844bf3f804c06ea035d2a70e5fc6bf43d6ebd9e36be80c78b378c47d92e74a424f1143d782fb906ed2a5dd350a69f33a0f4e761f29cc62dbd9c7ddd68374d5fb7a569586aa41bc66c7b410b5666573e6e1fb2b876cb03f4bd754b891c4ee0213963ca77dbbee883657c1cb81ad90c6b016cde63f50d2a140779ecc54f4d2e39da4870ed2294c354858690ce9dfb3fb477c252b872e484a9fc7583e081c41d9331a2b78de0a44e4ec3e259a34f1597428aac69a096055b50f56bcd5b424c7f637d980c664e2c6eb3eba0d760d993e7c56bf9443d6bc91014229a0ba52865b5e2e06ce94256a63dcc4f72ebc3ff9bd2550cce9deb4a4888482f7d5d7c1fab2748f24a0d23d7c906859b35d4272368af1672848073513859f4d4f210c65cf8606c9c409756b2e3377bedcdadb6c3b8226d339c9539254caae4547cd5447ae85e4aec96efee158eb4675d9ee6c6177764abf8e7bd8a23a4944f107d18fb4439e020fdcc1ec0f65301808f07888872fc972c315b6b5691b58930e16a2f056310571a756a9da8851101b47f02aa6a3c41ba3a33c60bc7f22753c946b649b883c633e293e47cf8bb522b2065e0bb5ad9856094721948d00c21af46db75ec6dd298b30866d016a2813639dcc9915a199f200aee04b187473a4168f466e7d32afc34c6d4bd037d9b019b0519ca2e40bc1cb5be0458739bdc5e0accdaab87283709253b58c71e95a69ccd9870a63235ad16d29d8eec923de68c9edad9302abcb99466a37ec1c7606ca0643ebe043c5b494e732de2a6d36ebd1644ce43fb503656302a56af86e960052380ad40d411911c465416228223b3362067ac435bd055fd16a37b507138b961a7c810c105bbdbed071442b98f14a5f0de0372f2cbc02e19c8f047d4b30ee99bbc389a0f59a97e83f76ebff1607ead2c14cd90da399dc7f2b3a29cdf3d6ba1f8de0eae7e2e62d0c6700b7b830a708b53a17668db4dd0ad360dfaabdfb4f1a27b6af5b94b172228677b8225ca54845f871580d54d461cb05b3e5c8cd51aac5e172bd5e870ebfddf411b7e601d8b7cccfbfdae483f064d9c323f03383fe71bb05aab199f5cacced4c60c45a911ebabd18dcec7c328bda05c6bc75eea1b6cdec16e35089ee64b45d534233647d25a22748e9d02634f50011429b6268115a4176eda3acd441dc4b805851814f8cd357931e62ee03a5cd17f9a859d5ab5f6a5ccd6ebd0838f5f2ec0b75ef291ff4d9ecf9872dfa7793d4727aacc1276920f040e6d635c6bbabe2c3b99306ada18f56bb4903a2c51e29ba136f47ef79d9f618ca32b6f58780af4aa94f1678c66c0b97a5ca3d4f91af2490ea0509d53874daa8fa5670cc908f2d190ba11636802261bca5339a69b788ed76e34b9403570b11449de967019cee40cf3c6fb60d6d958781e61bb713b2341d68e25b998bab44f6bbceec2d96f93173295f3a19f3977eaa37cd2c17008ab32c4967e39632940d96e9d8b49aa3d173a9f7dd14dcd2083a94cf780a0eb615bd4c0d9fe264b7056a80ad7f8a2f3b9555e6fe83a0029e2956fcde6766872b2ae596f34ae0957ee17b1988371631f8fe7b411cae22dd0f8deb822e2df86468c25afa787d387837b7642b0b5c3b6ab6499e0aa116fcb5f4597eb183a0ec74cade79e392856f6ea29883ca6148d740db9fbfc8a0591aff1d439399cb57cd13863eaaa344bcd2fc4044ccbd4dd8bc52ab9ba33a7bd6a5e6a16b027b4fc5c20a9060ef0fb6f4bd481cf1eb6b4629b419801c91f35ea83343a8f5357018a6b7739085aef9b23fca4d693e83b6e2b781d7ef2b67f3008eb9e4bd9862fe453dcab6625a5d6f46e4a132104dd178bd9007182c1d3405c1cfb83c32ef6e4edc161a52b4f78f8bdbdfdd8569ba1b9290221b47d28ead94dc513a6ad0ed23258983f7f9ca45b864d40e3f3ef2cafe705b50d47bc5439d5dd8af9fadd47b5d9dba0bf436cdd6582cee3237844e05233bd1537723b6f4f703801d562b6dccb41037936f01d04b46f876dc90f2e19978f7f44c44fc800199a65b15fb95dad7bd51a5b903d851d0615e4bc33fb2753c025859c10cf4893695bf85b81721a7c30953ddd6b4a26051a430215a749aaae8639d587578ee95e914ea9dfc167a2a56afea20fffbd07945ce8c1a7b5bf80f185e1de403a772d9288354a616c7d85cbe7a5212fbf8133bbfdf21b94aab24555bb4ac2cbb5473eb120bb52ea4f581f8b70c4308e162a4cb9f3581a2d51b563d6f39e8fa79a58f856c5e3465c9e5488bed4e0f066078ba62a5311c41f68edb9b2adfdca8dde2f6648c80a3507bc3d512ba0c537df055fbd0c8d6ed1f6a516c8c007be894f76c1f7ecd6ab8ca0cdebf47e505145eb3a374eb89b8f5dcce1f2b038f73b96854aa03ff3049d27388b31d6f215add777b657816675368fb9153728a32752e353aed5f4aa1f6d170784d2bd5188c8ebedf866901da046bc1399e970bcaf48d6f8ffc862be238016d122e15820b5c43dded333cc4423e58ca8f3c53786e1095623ec410d1eb8f695ba1f08a59eafe6974885120c6faf2ce3e21d5657f641fb79dedc80f9887e017e8209cd995732bc618bfc0fc6806155e0d3be11da60b55d9b5d12956f27bd3a34ef1a6bf9f16722815f7ad3aa97a5a372095554976f876aadb1317911cc4aff1bc5e0eb6006e554d16d58aa565636d21fdb4dfaa37b1c25abd93ba91e8d1c2945839689b50158029d333b9eafc630fd9659cb475fff01c526e95dac3cb857a8c0d3eaa417a509e2a94804b3bd0a7a49951648e8c3643a4c6a87bcd4d3768a5707695aecc6b93f418bbb20afab56dbacc91d22c334bf2a8c691db0071206dfbaea9b9b013d1a49226b20c5857274074b0bbe91c13f28ae39b4948a7e5a4812622774975c61014b875581c1cc7d7ca94df266c3522afd4105338ed7ed1ad5d20b260d005b2c4a50f1411de3f25e9d8e758ec64f49db6a8a325eb20fe8206a8e0638adc523d393ac1a245b0a0424ae7a45e3a475178b7a2b90f5198f8eb249dae02b773169ae3c39d6fe506dfa06d7ca5c48dfa9375d0d5ec90b8bd7879772474b5f1f93b406fc432d351defb29a6f1900e2149f7a5b3008c7ffac4eea55980979a70954a80e9fbe8b016c5b4a8a9e073619113cd7c527bee237b40538d5e7ff1be3cd7d8931104b66b4ca49e81af77bc467f14eb99e8eb76eed033a2a40d45ad615f8445484c3186407686ba7c67ca3db7d8cc9a961d8a447f1e4e8d11424f8f5e8408196418315127cc282a0999cde82f93b35fe5959ff93c8b815ae5cf90a2b5f7570a24428f4f743703b9d81699f7e6659a3b5e4a4b30ba2cbcae4584c816328798516d850163a7f6ece18b774d6869e8422fa29ec96017122846390869125b834e4cddd65c9a1af561feb30acac833bbc0ee2ead158859536bd6c68c0a2f453141291cf02f3b246e80710c91c009a25238c4479c4bf32c289aed5529d06ea49d7072e346d5647b9f50a7d7897ea2c19f104dc38f40d19da0d097f20d8a39fb854149949f3d0938b67009c58069d6c7901445f40d8ca198ed622b6eafc5290664dce97aef5abee1a3c89c691bfca2a48d78a7243ea10144b03cfdbfd059d00cc5876cc961c12eef86c5493e813e55b6d6abc021a1b834ca56b8fcc36d834031021476b29557694552e92b9ab2a45b89ff7d8a7203378cbf91a23f60937594c4e7a5d4551b852becc5ac418fc4cfe1063503ce1a75b6106c1644e9e1e8b2ad510e91d52ad9f48f4ef96a74e3a46f4114f6f78be3044591b8f0dd4aedad3f6485a4c5a09274217314e2fba82d414c45aa73a376c65140ba91f80317b5bf4e388a006d104f9c8ba0a3f0f77ba5a22e451912bb13ca751d9f968a33795f45a06bf52bbaf20f889311e9444d12049c777d62453856ce83f05427a21f754c9583fc00f6f01b564ae93677990aba6a5c53d5a86a2bc758296ed1b58a71c2e43e84da0a6e22aaed5dfd5a43811ba8c3a3287dd57927dfe04c2c66549b25a2631d4ac40347caf49a5485b529df335f09b8e411d16aade47d359a21d693bb669857695cd7192396436efd8c55b3de0694cbb4e64fcd624e1db595aa2e7bb3f8cbb623e301b2579d8b3d3fa62632d91e8c88e623532891a98f7bfc6a1e87d293ba6361451e1b5d93374f16909de931dc3d9d3331e81eac5a0832cf06393f6521a99ebf29de0dad79c37aac9ba9edd3394cfc907600a549688bddbcdc4fd2b81a38f571f2323271ebed6eb48d2c1ecc971a3dae22c3c6f16b8e03cf34ce45f4af5a5b1c7695663d546d9f8b47b15de2800775dec5d90b320b41874a28d98cbb602f1bec8209b2d49a8992c6e5db1dea35038ea80883df142a9c1070ff91094b4ed3e33b9bc645ddf6dd42e0a503baa4d000a9dff4e6329f9620c2ec4fc6751753da02227e697d2e38da8d060366a9c42e6ed74dd9c2c7661a1be5e610f1e86c680690717a1bc731a18ba14aed6a26de4e547dbc06b8c49ac29d0b381b5c2ebda39cfdd6869e7ce9d416efb1f327cd80184821f758b0c745cb968c71efbc14421c49204e9a9d3c9550585d95e076959c11349c6ba620bfa511c8695fbef4c5774024722416b296168deeaa22eabd977452090c82fc7835e99af39931a2f013c024b78b1c5f7cd80991b7c489f1f848bf589d769cb14a15691724bc61bbef2178578b00240767649ba520b57856ca17227615ea5ead07b44a112a51343229d86b7d36448cb873bd25cf3a20a1f14a7058a805efa1b47dabda7cdfe1f11a5ae0701f10651b8cf94fd8a736df4cefdbcae48b2e3b7961f102d5f28b537529415c03301b09437f5ef6380bcf73465e4d719517b2a4e7b6b70a9e418276ea54c2a5bcc6a5c209ef36e955a2c946e0dc4ae0c39cdfbd020794b2173bc18076c38b70e7ba5754d3558cdb532d3aeab594c06e722ab9102edcd5ba37310fac05c9e977c2e1b06c9f80ad4fea3816fba8f9d19d52c0463acd43268fe6d0fb86f4bd4818005054422ec9d748aeda31e47c2c8dec8c1d31c8d7b3ddebf9eba6707dfc30db16661a9a9ff6d990f1a2fc7edbe97da35c6b6a315b81388b0a22fecc27f94a82daa44ef50a949de17b0bbee8c3ab7c94d2800a5fcd2f2c48d466fd592f99440eb85cf34c1695153630a818da109bf1c435e2513efc816317da6c40835097d6351e70500481e36b1d5aadee95d027be139b67bb7188034ea179066270a0ea2de7ff5a9e9767618f51507e5e96d746a56af0b914d66dfccca0e969aa201de29089f2f834c1c59273efdec7596bf14da42fa72f6771047f92fe3e4ace53202b9fb254d46dc62b50ac9b08018041939f6317ba25d6aa4fc3e8e350ae52982210ae88c4cf64a073fb1a631c9f7fcdcb3924dad8412d25b596c3828946f4dcf531f4007dd8d2af7ea0a02305071f96d1ba2532b6e8dbce4f47cc943d89d81add1e3ceeee91448a4c42545b5b9522b94bf42ee5fd1d56b2b6d104dfb4b67f002f445ca9ce7729e0f7ac21384af0a66eeb02512609e7667fc633086e6ab4dee71bf0d615b1dbe2e2dd4c8cb75ca8531936038b2eaa90cfbcd774c9b6cb94cc4adfabb94061e211894124690e399a18752a2631d5eea3a3af757a53846d1adb803fc0c474970007e651faedfeac616732d4df492f37025acbb329f2cce668819928477a50504b840581ade4debb9577f4e277de899201e4c522cd9988716260f12e8ebcf3c0129ecfa1e35ce7ce246d55acf497261ef0239585cd3021d84c7a2cb8fb6bd7ce62230b26a6556e676b6efe2e5935216a316b82f3cf2c5da0ef35cae703e6bcbf6884bd4197fedc043a34211363e44123ef4fd01412f0cd2c295425d5a37cad461fd29e9a1f8423b45d1338c310a6739d3421a0ab68290303c179409de2bb57c9899d54594e536aa290c3ffab74d9c88b81a249ee68fafa6a41d7ce7d444650adb3f71b8c8ea9279c88b3c8870052164c49b1a1a65470906e484a18c46b3f6f60ea14b25eabaee8f8bc868ae4878c3506d66559c5d1f0e5e27c51c5735349b6a31cfdb54495ce1f54d80fe2496eb39eaecae33be40f299064575079d2b7b2ac1e7078f8f89e60a4b76fb2bf32a7a004d60df6bd10c852ff7a3c41c97ee5fd094f1382619e3a41d8aa954bb58afbce67c7e06731752d195b8fb741655ec07da192390860494ba6e7131755a4fc9fe9d55dde690c44f569f976dc7dce3182f9d03e3a4e8ea73c48a4d244d4dcfbbb487c833e14ff4957df379e429ed445c22df5c474f445ed87ce996aca563d9b68839c4e7dcf2c3c6f1b624412fa26075efe746052d6b56e956d4714aa00341566f5cfe5b81f1b1b33c73120f9a2e8a071c130bba04ddf4ddb40ed02ad34a5e6babafab91edb8b37057120b2718c79cd7089ed39d171800e82c5edd640230ff4ac04be78ac3443b654e953dcaa76aa461f07984eb65f90666c7c9eaa0e7599139d27419d98336d5be73c707e181700e59c7488448483463ef42313b91838e358ddc9adc5eadbaab8b9f8ac746a4391d69fc2c48ae33cde06ab48950853e02098b8049555ccc7408db4c37036abad0d2e8d0cf652c3fbc08e7157898f4c060e97948f35c85de064bad0a3372c8a68639cda8a85b7d52520e4e07078c6b8b91f2338f4ba626a95001cc3d49979875343991bf81facb91ecf2f660bd1768a8366525f3fbb20e491fe4eba5ef79fc4e3cdb795a49b052c7668e934ff4640f676709383aea8e12e6940b7f3f475919c0e793cd7758b7e698ec260220c0f7e9ebbadf31b50e5d5290788bd0e2ea26a8d4040c3dd2514ce7cefccc70248fa314ddd809c57d247a8cd480cc251dbee37470bfeb66db011adcdc0fe4ca74958dd7e29adb7d0ab50e8638154c5c279505d6b85a2eb8e918756bd7fd3f2c7050b06601c17a81e3c5c3f87f085a19530d0d92fa018685f98c41a4c9747ba3711507edb9f56623224039103f062c16505f2b1764316b20c56a01d87a9113aeea3829a3353548282f6417ea41ae568d192f81a5fce89a03c5946fcb7e80ecbbc2a29be1472ffbb742e04bbd9461c6476a157a8a83a5bc107b20c861a2e481d0a6e987fbf29425bad4bb97bb819c1886694d8c65bf6652b4eb049ccd676fb37f69d03d1f805e3e0aef9519abdb6be59be95ab1244e3cb77c9dd7208d1ff3f5394c77347706c96f62f396f0fba819a0f84e95e503722947fb90ceb7220372b61fd2e646c11d599b43f1b1ef2b63e4c3b875d49132e225373b674c9eec889dc2a65cfcb6cb5825c1c602e755262d3951301e61d0145eaabbdc87f46694d070ebd3314caed545cfc120bfe4922f5c2c4ea6ff68b3094b4fd8d895632892fa2a2c55843c22d288dedaad865dec243f65db9c996784322bc6b2b4b880a887d198a56fdc1ab4e6433972caa6df547a31cfbcb86a9568c45bdd4616e68c202cdfa1d978774190a1b10982a0be7910439ba8e22cbd221d81e4e0b3423e254cd9bf21b408c4d2de2c0abe926c4c43d37534c553c40345843a27e43286158ae8c68661395da2867dfc990aa29396dcf06c5aa25dc4491748330d7c0bb3a3325c06d2d434a212e62e0a1fc9e8511c86d09e4858d7d1298a90bb605eb6a5ea557bd7fa83cd9b501f3c6e0c6ab5429d3f310ac340673d0fa4c6dbb2211c5c9520a5afd6fc339f7f59545ef0dd0daf5cb624ca47c6b2ae36046aba60271357544cb88c91359636942d0671531525718bb4b8b01657af63263bfb9103043171d2762fd2f5e110328c8042934316c7530343ab7938e2feb124d3746dea02f84209049e831a3585bf23ce01c3229e2daecd5970ecf07516e66803f915b1b220f007d7fa3561b96891957f3d5ddfdab70e380044158fda571d5dd85c24620b742bd212c9ab9720d8de3c7fe8eab346e8d7cbbd0577f63547e54d0b9aba88456d4c8efa8ed182b5090b585d2e3aa1832b6c6fda93cd5596edc8cb9e951abc13fa7dacbadd40e212a89d677b6487e25b5b18c1cb3b1b210fdcc60063a2fadd8de6576cdf4afc27147ec5190514964d02ef986b128e01d8ceecfd2479d3bb4094931ea7dc5c31db6454a96282caf800c5d104dfb528e91dfe0ba8af2dfc48a8b8bc113111097b358c9685da1018ed771a908a976433cdcd2468bc0780ffbfa0ddca06b10ff67e062b5c3130518f4c2de6f2188b85db3c412a3233244a4fbf214176660124817bd0b5d8a002f1889fcb3985ca8b3d120ac4846ccec2956324a4a2e63a6f9904599182796f36b139b0e3d24bf1ebd8284e83d676f8c0c19c72531b2ddeefec4b59711ba8b6ae903918bf90c230d79d4067cf04182b17b47af727509c69d4a88e22c65bc5c1d281b521d46f518aa75ea6c77c939e76c30e59c1ac48cb1ee1e1d9d2d947efb6d4ef969bafccdd35550d9734faa559f2179451e74511ce4c03bac58e9dd505b7bb90cd193648662a54f3f815921b9456784a5552c456259ce87ece5cc6131694715a7543082220652fe691107989a4993106a0301b3ad1b995863efe8d2e33218c0bad4abc14078b90d721cd4f89c128c55516eb4b00a9d30d2778f34bafd139d1c16e39f92e46a25572962eca77f078fa1488eae4e27b82c0d968184cfb0de28d085e6039abc60acd42c023f253d7f60f803f31d1ceb40937732b07f87567c700427b31c91f7a821200c04449315c88c4d1b74f3c062bc43eab489cd71e4d07bac92e5460bd7c40d5b698cef4d4c432478a0bb70c33ab5e8c4e21cfa6ddb69229e22af6a5187eb778a32c94d8629b4c90a3063a30f3a2fa953d49b73f5e4beed31cc28645116d15e498d50cf3543ed79e4334a296358aa2a70d1a2f6b2c74012a6ae894b82ec66b3a41db2a419b1c0ba4e0edbe4a8a91bc649a3ddfb7492b25313f021a0951e1d4f9c5141b270c2642d43d5926672a56102e8a1ae482d439347f9cc48b6780571a8ea9913ba6a60920520e74f3ceadfb3ed642901e79bc7b0156e1c1de2584aed95aeb2f0af0717253fae9e84593978e812c5f8fd1564ce4c5dbcc33035fe304c4d336a2ecf01c3bf707ce6d28940a625644988137646193fbef3de980a33117d8f2a51c6dfe2644c089ed5e9ceea58ce4157b910726b70a0a59a6838206aec203ed3bf6715bed4ef6009722080b012a7584cb032df1287f62d0647c30cdcf41c10a7481335312534d6f6421ef5084f8c8fccb409ebe650f365a8034adbd76088d63409d6f58785d84e8528c4fb839c66fc4cad0c3370733733f4de51fbafb9653cdcbe4e88bd8f4dd383d0f2b2e6152fa205d7e50032e1dcfea1cf5c62f0317fffbb2e1c5fa35260df918f617422e62fcbcad4b55c721a04fde32d893994f2cb6563fbaaaf468cac75a3201203aa9124a66520421154830cd3b5212e7a7d89b3c66254db2a6052b4d403648efa2037e71581d3cc90bf685e7aa8b85ea070087328c5e8bba710d0a1a8842611fc1be1eb2895acd6c0ece2a1a2422e021e3401ac78f6becf00dbfc369da25c3004302256b18cb4b811c7810981e83ee3b950ddcc2af0ba3250b5fe8ddb5df8969c67e06f0bd44ee27d387f30e9586d784fc11d7a10a739bcb68928c96f89a1d644524499a167d427e8d43b6691012f88aad2b49aaba9290433714dc21d0a76174617c45afa503a66661a9e4ad953cd1c7aa586afaa6bd4e89d4baa44aec27e3bdf443176c72db417f88b29c5f46a31ecce6080e9b6abc14fa3a166950d7e6991f4a7fdc479bb74677174737855a2a12c557f0acb108a52b10064096a6a287f6f3668d30ad2b9b335b2d5463ae6184d318dcc7a3f314f0a0069ae80a38cccb4fa3cf308cd6a479f4d02cf18e3133c9b12f5a4a78d3729a0c03283af75a380806d2c9ee0f2fd71f5462b529c43cef2478242c31adda857e0b420b7d1528884475287d75c258af00a58b52084823a9b57f4b15c966bda0c284df2400e66b03214094352c4536c03ab59d78c329150919ef924b150ec551949368ae40296508b074dbce196a6fb2a0411f32a63b3d1064e3379aa7ba9983450a09985b60d1aa4d36883b87876659846462094f6eefb093e83d81192127c109791ebd38ef1264bff5b98ce85643455fab78de584bfc0256dc1c4ddc9b45b71e123a128ae2ba63fa02f582f4a774c5df79788a7bffba69af8258af93e4303680888e4ecde0f8f19f818bcf2dabae012939de727e115f9fc88295e636eadf260bdcaa3583a46cdaca032e399b25a716e75776a06736d00949190064b14b34346e71046ed7e70466fbf8811eb90353922b3d7c8649cd2b3c1d938a0c59403d46d2493bbd74ca26f5f654493562d71de8748b1e965ee03ae2aad9cd8e724b552fdadd883f6f8465ec808a2c2f58749170c7f605a0e827d4cf22f80fb31ee33eb3f1515311232ccd8774ab758dc32dbe34add647433f123e8c86870fca57fe10a5885c5249aa8d658788483bc2027c940056cade89327f7b324feda8634402b3c06b4f189ee7b6f19def02526e9c6c1469e9e8733c3378df33eff9106f2b6031a06e74e928afda5e033540fdbd81f4f382ef4cfcca2983c88ac2bfaaf99c1e6c2fdddbd95b42c4d770a569914686a2cf72daccec0073f2d96fb055e11245d8000b977f63e3a9e974da650e3d67f047eb94b740153946bf1867119cf72ffc9d5c520a9306390abd4620c62284f726364f43dff39cf9bbb2483a2fa8d3913a9825b22bc1b17d3801f0a2a70bbfc969171ac886a4f77f95859fc8290053a432408c007009152f7446bad3e987ca0c970f003d3f12106b45629402032cfaa9d6badea17af98cb7a258ef0dd5dd1ff22f1851884142cef6dcd3e5525bcafb79712a8b54a2fcda53166ffd5ee262c5d9f1247a2b2c026712a290952ffe17b17a1e22a72e96e7b22215a1c1ff18232b3286923ba3bc6b5afb3b120d074e3119fe721d051b83eeeaa252273faabb556e42861500ac1e78ac77293249604cbaf14cfabd7c7293603bce5b0cb2a056db58168ccb6ced708a85d02d42994f65c95a6e7ba1157166eafb486325740518aecc22e4f90cfee7f3d529341dab758da13b7b25973a64240bc1255db97f654c71842e64a8ab9ec1a480fe068503b1eeaeb9bfc7a12d9e2143654aa5ecd09511ec944860c10f04a95cf51521928354d69ce42021e0ad3934ac7ab00e00e143ebc4f97670dbf5faf8b8fc082ba758efcaaac0e921b85f2f738dafe384a8a85f7f00389b7739f8385e4c39cdf7cce8c8e8410075fdf8bcb6e6357140875103b2e1cde85fb6ca138b7cd3e2d0a87e6b27df013629a23c2d6691f14f017d4ea8a205b31e52503a907413cf1e99c4985e5624a697d50d0d5786f6b0615e9dd88127aa9e37652670732ec15be7cb09cbd018a8fa8679cded5544765dab3e373383ce77a8493950c850686abc5f6ee234eb1152070be2f0b25a2d65c5f56883f1a548163b185b9ba21157f6963621812fbeaf0465d2c8934692a086f88f4867cd9ad5c5549a2b137f01101f9de331526297a7a044b0e8d5cc92f16cba1916408c301e28b8325a024b307467ad3eebf419aa3384d20a769baa2b9f5666008f7ba1aed41990622e3d4efc9e94b88912ec6d8b03fcd02caf8195afc138f9cc0fbda31a547a01db78aeb3e45201acce882b4672176093e417544841b811b9c53914165060506c278352d96cc1e2cfeecdc133abc9819a899047864fbb6cdf6a0401867a65aab5a6c89df6582c0fd80612404729c18cf1090144f0f02752ba081603336e2b4e1b8f4598f810bd8b5e504a2dd03c958af28e7788fe0714f17fe417cd6a5a1e332a5a5fa099a0d5ebda0757d38c3ee0f07155b6b298bf4e5f4d966b68c0914493ae0902c0e71b64b22966adc6ff8b391f583b3c5765bcdf20253b21de589940756a62e04c3b94584bbeac67fa84bbb242bf9c3eb43c4fb4a807970379cf11a99d420a11aa8a4480331cad004f64fb532e764c67e63648cfcc539b2f77cdf741682ed6a4bbbfc3dc294ec86b883edc4e619697e5cbfdf9434d9e63bac10fb5818b1a96ac8bc716ff38b146620c2770de92cf52d4ccc31a8138899a906daa186fdf6d7c0cec33bedc866e37e3982a0f0283301ca6401a137854492f5fe17ce8bc00213371549fedc65a663dfde5f143df476ad42efe2767084e3f156e28da034464d6480ce4625b250a124dab8ff2ad17b2105eb6c5f00216ec10703f7c8d0cc0e061867f4c16a2d8f68b0ae4a15b4d41d6c8bc52cc46ed651f53671e8c4704ba9a1d0aaa225ba4a429fbbe2700a6a94783ad67da6693e40cdaee2bf2f52d7fa440ccde1aee56fb0c2af2f720038d3472cb37cca7d4832e0306b0c48ad80015dd92729a203038b1a4d744bde4b8d1f94e45f470ecf535018f9880d486565ec0387e58451a3cce00fa9fc7b9e27c12880291671a7d3b364cae051a4844a0c87bd33ff87c3fe85d3ab1a4e38c128c82294d66fcc449cc8d6d861bf6eaeb945ad47fbf6124e084d0015f091b0f3c222f1060cdbe042435b1d94cd1b5ce7fa0e55991370129b36ea0acb7f4d669eb72a0d50e1801bfdd12856dce4ad9d7c57a115409f28425b679c066a4e0ca5a1ef22256f9e9dc523273981f26463a5c69627f914f717c34e909c995f72066e07425d6723589306eed720c152f1c8c0578d0d3c885557689316107065450ea528b364c28ff2f13db4f83dad382918f546a1e5642a727b02c83306881dfbf36aecded00ed2d47bb849b8449a02fc250bfc2c61b5234107958ab27bafd24fc2c1a3d4766111dd43a350b0e2de691ed2c2b904f9c76e77f50dfb34a9d5f45c4a9c114e43b6149ac42893a10766257ddc17df7e7b4c8827a0c27d56dcbd74898fc7ed1b709a844ee385daddc5c0d57851a1b96fa030d0c65b375292186a503e297b3e65634983ac182b0803eb1842f775e6b1d3711da0f152da169efe973d32aa58e86b2f4f7276fbd88014f50637b4a216d5631c3880baaa840c0c50f0ed173cb4a62f7903022bc342f8d8a42e012c0a7a7bded774827c24ac9bd4d68108daf5e97ab700efe78aeb168f7713bf872265ff279608a1a974331b2ce5b5836cd6eb361e7f5f775cbe18a5125c55c52e876ad91d3990613bb2a2e00a69831453939738ac27f1029cc841139e016ebc958523ba6b6cb713be656a7d2ca378e06dc023a9adc211c5285b060c5d28473204f64fe5d8913b98839abb7c294b692992d2eb7659473a484735d8a5265d2a85a5f0da5d6a64778dec42877748476adc7d1dd251cd5cb694af67342e2705a5488aa5488a4b490ab9947316e2581cf5baa4725be45a5bc451038969bb8bdd645359d964d97eb00fde32f02a4628d8b473f243fc31fb9c9b29395d5f0d75c5064e18c5b282a80d6a27210caecea3ce1932fc3dac31000e3b63d41fe20561cba168b79cec1d791724c6aa6e6ef2f5594d446f42858ac47a3cc42a0143a36d0088fa083a7123768a5eb7b8658620378c1a6f3215b6af957e0a65e3e771385e4eef8ac165bf486cd45d08e125283be0bde0c8a6656538dda2ce2068c17963c03e7f85c11d22305a710a48720c6edee521843ee345fff92df00e0894559e80d1ecc9616e1f2fcf4fb6f784d84b7d97589a998caea7994d0f2d71a7a1d5e8ce95294318bbdbcd905210ffad638c1ef829689338d2dced7daf334a5d452f149fa06d3565bfd318522222e162dbbee40bf2b0f2affb5b76aea4973862342240cf359890a513770533489d2da194af5880bc5b963aa364c365f4a286da50dc4393430ce40282eea3f52af3e674751fb4891df3480f489c958cd262839772431d1c045164901131c4c1d077a4c0c3eaa629fb45a459580b79bc095c47a31713f1c080479151b35a70a5fa3a9896945d24b1469d59e6525cfa2440f03b3c6b2322be61da7398d4e010fa354593114f99bd01b9bdbe103ccbf6d776692696197666e5a3e7f58f413041eabcd261138ef9c6ceecca1631f207612a5671d5c266c4d48b5f20d82650456322fc6172b03f948af6de2921bc96eabd10a09e2b74538da9011aecddd0a4bd81dafdbd625e08738e1243adf359ebe36135398efc2a7c53bde50e1260e9d11c6bd4e09315657ae7b6d70e1501ae29a5cb797416eecdc92b6f72d9d05b6fd789511e37c89c2e2efb0a2264bcbaa007062af642b780a17923fc88af82bb694faea5beb5ebbe051dc665d6244f2755b209576a0d334cf214ab024390e2acc2517235e2ffca3d0f1ae2155aa408142a12e51991dbba05f9593d592907578caf2f452806b6b5c3b6ff9dea98207e6646b9030f60506f2ee59535c410044a098b965520dad9a60d0d1567878fb1295b17570d1fe1b55585c5f3fdd18fcfcbf71a89ffb71b20ea16c8ed1de16b39424ca23404c040ba68774cd8b705d349756378fa8cd5fe67af245b3f5adb264d2dcd68a5d1165ab9b96529095b0984093daea7f4aa7d9b99f2ed33af5fbf37279c30e6a03e66efd56bc17d8e5221b91b7cf20432f976278da4a114a1f45e14ca1d3e2bdfbeadbeb0297856a46001af808638d56e146badf521a8476d549712c7b44cfb10d40c9482a8fad5457c14f27462fa7074ab67450b462b5a10bec738d1e1fba61e2975ab4bc75dd1ad2eaf0fdb019fbabb8a0061e75617bde89c2fdef8c4fb38677777b7b5e9586f79adb4bbbbbbbb7b3cca8aca4cb7f563c6b6f3f107fc9883bc871dc47a98719eb7ef582f3231f3f1ef7b160a94767b8ed48ee8b46b4429a504e13a5db3dc947408d5c57484f11df1901ea155e08f18326f0e4a91ad8715df923962745028bd1785825087b6019da3f2ef75e09f370f96037e7cff620fab8bad7acafacedb5cdd4d51c843f889aab4229cba11b9759bad4bb7145baf22bc7d2a87c361b91588bc086f3adce3e4b4d30fb44b491d66c1176127311c1604e1fe7a590c3fc5d61f96c3656a31750b570761f81806f21c0dcb75e27721f251a4418c77ef2ec82dcf38a09cbae9e82ac4406e41fea0e9f52b8c165fb81fc3afcbc7ae61f366c4099e3ac541311cdde2eaee86ce7b1cd59fd7cbd1b898a3810a3deb0b453e7a95de5876c794f58395698e37a91dab55af6074e2a333f9e8db8e942a1cf1eedde7701203d199ee1c8d20963b47e3876d27487ce768a4e82a45b34ffc633efd31dff248ed587df49b2324439d52ca398b7033c5d2a17c3f442fddba1b98a3f11c8d171d9def8da177785efa8e13607420352f7de7716474067cf498610d5ec3b7d7b0430c7d3b901bfaf6d00ce99b73ceb936e2048f532edfecfcb6f3edf5b9e760c845e80ed7c088e3523b18f0ed405ccfbb9cba61c0b733e03b77f91a6d7777bbf9449592ca0ba6ba5c0a41331ad3316306e8c7fb79fae35d79fa9e02fa04bd514b8664bc21dffd20dd4138827848a7788777d80eb8c3d3fc63070b1fa8610c11d11fc47aea3eb3cb3b9e7585ee55ab7a1563ef45e975512f6428e142e7daf57d5d9001697c5c00f955653c891ea9f4b75d5ef3f4ea20afe90aaa2050ad547ea182500d636262dc43357137abd94be317a84f776f04af8b3aa517a5d7452ddd224249a7591e7d9b4ea77777378d5bb89c7aa4eea253fb1400025dee1e745d20d065e9cb322508473c747a414dd336fa51cbcea95fd9baa17e5d6fa399e6d8ab18131b4b95210a88beb9ee77ef7180a4dc3b17bdcbdbce234182769df848f040bf3d606e6744f7f3a9dda2d91810d3ad6c446a0233bad84c7f3644accd7422d166887306d8a24b9b99579002918de57dbb06ec1c18cb119d959d33c006db636e1f4c5152e42da77e79ad6d1fdc5e97a75dfeca180fcb9f532cfa99a9e863fe21fa9df9de12acb0c28a2c64210b5ef08217280f14a000852aaaa8a2a58cef302bfef0ac9b9875fa65df44b792300959b264e9a28b2e9e78e2092db4d0820a2aa888228a28aa50852a381b43a68432090b5a5336f4020c30c080020a28c21086304c1eb66cd9c2850b172738c109511a469131c68ea219e506e928632371c51557b411b13dbe9e506e908ecda476472773f68cd0896451e5a75bbd5912aaaeb8d1712c9f72667a4da7f3f19879eb188906d411487af51abed70d618c526239ae5f5d6b5a189f6b592b10287aad387c1ab3bd7231ca88592c35841bbd8aaebb16456fc96ba3ede1658e3d8e86732ddc7bed2074103ae71e92ea5c77bbeb0d54d70f02da5b796b28d3f65355ee750f07154da96a9e4ff5edf1862c889005ea618433f10cf539edb9db7357e551511bf6fb51dd4ca578a379bafa87c3bf477a37307feea81a0370abf6ae8e7a4efdb1ec1c0dcbe655af2c9f7134ac9545afe9345322650634d20ad555d1f42dde909e2f47c37acf6541842c8860af07a1505d0c8489fed284bbc13871943038e2c943495f940a31abc27c8bb97c438136bac5878ee11855cb3c0751970ec230f547a9766f68e256a71657af99be07a12cebbd97295e2cb182545417b35d4d1e7a05a207f59a46d2387036baa77df0b087f0b09578d857a81b798a254f4f1ce2a547305c08251547c3818036340b31c246f24019e81fb7402437b85bdcf210beb8a56b741ce36a02459512bae52d5d4a08a584b07bca08e5cb86134eaf828a122b9cf8f8b854b6e8c420563e4a72564f60c2111d3b24c742790216603c7528297479834e75d005dce838d0a96f10bb814ee946f3d6303720e176de1ee4fd9e639ab776e83053173b085534ad77475910a017044d36ba395bd9725544b4936059e6a35e8d62afb09aee247d739290cb24489ac764994a57b42ba42b21240f097520102882240882502050d3f6d95048f97669c5b7c3a729713757250848a94b32ea7a84a7af2caf74096574acbfbc7aadf84a71df5fbe598bba79f3467d08d53c34bfa1505366eace55e9291de555697f4f667ed9ca4f902041726db4793ec25ed55ba5d69920781eda6890ab72a15a06338d832adfeeca76eb494fafba795025d77ca3242753a59fd7c0a6528d54ead01d0e6c401390f4e8a16b38294f60f9761f82c091733e9a7f33ff1e8d348a4298ada078c59c9c97dbfb3967c4db4b00e69b01eccb5ba3b72e1bd49d73341c0d46572e25cd3087ebd1e1dceaa784590ae8e6acce72ce9f4fe7dc67388748e80e7c74d45ddeae9381d43997d9e5a0def50974b4279f4b0973ec3f9751caf8605be190c739155ce750ca487d5ae767de9c5b6ef9ed15746ef966b3ed157439f69d93d68b9f63dfc1e642ca465714c26c055d745af2c922cfca8c1a64a852302fa893a9a491b2d11585305b41d79b543a8a1385305b41179d968cb01f8e49524bd8aa5de15694bbe182d0f3f4e1a4dfd88b16f607ef4dbc8ad1e24660ce9e5b113ff3261d36e1be7f3a627a65bd97f821790bc79f27f1276e69316e43873ddf556ad8d02a366c688657a0cc133776116dfe244f0caa8ba1d1c7ea2f1e30547d44a86f42fee5ed72297ac27d0f5f1350de2ec843d9fd35890d7a387ca463fc34cf1844b4ab0354bdb4d1836c9257bdead73ffe0f0b13948bc5b7bf225d636bfa8e6c9e83cbb4918a82481369228d8f944cf8b8abb4bbf201d57a434c58d14c96f9f86419133e3e3e2412133e3e3e9ae6e3e3532a31e1e3e3e33e30304cf8f8a82c8c8f6a890c1935d4e0e3e353800c062affc6a87d29adcb4e7a6192499307558b85307904c95f94eaaff623cd92b0958c2493d54eb68445fa7309ee3e255c7429259caa218cd29a4dd05caea7569826b050e8de8c4432994c2617d38d00d954bae3fa99b71ecea63e8cc7e8adb97393bd15311d03a816b5a8e52e221164e9bbd2ef4bb7d28a96cb0cf27ae5437f43845c2c47bf8ef426aef39113b7fd39ac45601d92d39e029aa7344df5a1d7939558e584ebdc95e0713740284c16a1d32b58f1b3b0c7e368407f48b8a017a272726bbc1b345eba4ed7b80fbd0859d335fdd2830f2eca16a6f439b580e6098c53164e30343ad06ce185cb4b9617e93684e190abcb575aa439b5c3f20972101817c82da7d8e2122f4bf194d0561bda2a60618d3d0c621287b69d20fcccd12df98c8c94e071b76bbc081f9469b8d2b5d8f0457fd80f0dfbf50e5ed4a4ca896bb966b9c4d3c2b05f5a96d5af6bdc71d5c0c67844acead57bd80f965769311ed167deb22e6550554c50af0ed3a189544ddce8969338965b59db91ef3981a06aa96087e7892580bc6b3601c89d4d4d835650063a850958c101405fc205b905c242a21b01b201b9f664041c0604c8651316c82dfbae287210e0af1e7f65bcf578eaf0aa5753f81c449ba8d699b8ee9d4d7b7c4f5ad1254eb5ef402b8a1c0478cbe3b4667c5a79e331435150554d885449b8d11fb51ecd5a7c2468ab7ae25ade035a5512eeeca14accc539a7db49a5bc264e11d143f4e90e70448460c6cf8c43adea39a0a75e27b6637ef589e9705e2c8183293e382f96c0c114354f1ea0a7d93eea6db97e16e371cdacbd7731323251294c40aa3d0713ba88819d1f1cccfc03ea33c668493b3110a803763e0e6073533059f23af02d1e796b2740d8c9c2142d2af84294243c4cef895dc0f1309b89b7328ee544583904333e7af4254021bdd30d8fc05cc4bcd6ea8de1a8d5a9db5e3dea53f5e4e2548faa245c1ceab0ade6ed7d159282d7c52609cc8d00d940d7b6ead80e19011cd8bc4cf162091c4cf16209243650d5842a09ed2f87c5a91e6b75d08772de7abcad7ee150b7dcde99b51879c43906af26af73516eb43598e10ecae3a78bd0235629711f76039af939e10583946e9de5d269bd72337abff820c663e6fba4ab9070a594deedf5613a5cff8b0e863609bc95edcb578584fbe0950733c5dd834107edcfff981f24c344f0b12096bdf2416cc8dd00f278fec70e3c220fcfe5f9e7f6f1fcab42b5c47d3edd161e88632e56e8d0217c98a66e9c4b7779936e490823c6437a741512eef41dd7f7a06e797c530701a2f8b9859fb90b35deaef19e74d18231f67327d3391a97839e06d62cb738b5235f4d4985f1db61f7b743242caf7189eba46861e5bd2b715d143f507e9b011320bd1f0ee009793b10f613aa3bc0bd3746cb618451dcf731c6b7e8fde94f5a98823ae46eae47a3f28b0e3c290cd1812fbea0314ff117d3554fc2e82266c87d8e04c851458a94b2dc0d3a413939f2db2b52448afbda639314c4f8de8414c2bcc5f7e81756f20bf2e5c82552d798578cc2dea5830bca570689716b204efbf369fdf0523bdcf7145bd802162cae237bba3fec9f3d517a68661c952acead666868f56bb3b965cd347c6cd93b2411b22042cfc39c192ae30d2aa5c1dd56770a9e076d949cbab13c5a1988dc430e1d7b445b3ef8d08ec529afdc038859447b8b73bc6db55aad56bdea6d45e3b5b50a618ef6513a7c9c9871c82b983da3c17d4e4a7275a899567b4ccc4afe40f30a875ebd292ee542a5ff2ecaffe30813c11e91bcf9f0f1da382495a33fba067a059b10e92550c65a17dc2a418f086dda94c8ecd9f38b3ea265aabf2a354daacc8d64733d4820185ff1e6b0ac943c30e6051dbc5638b40dcc2fbfdc416ae5547c2bdfc48d879d778e79c8d1eead2928243da04c7bf486413b6999185413d7a3facdf5f010851b73217137ac6f18dc976ba2b82e74a4cee4cc2c4982c488bbe2be703f4e8c19a7e989b2435b5d822e5dba507918fd033352786e7b8ed1ec367a7dbb226773bcdffab7e7986fef5337d4a7ff7337ba22b865cb962d4f8b96d6a2458b16a8458b162d518b162d5a64962c59b25859b264c932b364c99285da0b0b185bc6004199828595aa84891327acad20d1b6f3db73e9a1dfde6fcf435e035fc51bdd4438a16c1fc3387833e2615790b539153d3eb43cba9571a8871e82ae1928b365c896225b8e6c39e2610163cb180d650a1656a012264e9c885a866829a2e5889623241630b68c614199828595a984891327689621598a643992e5880b0b185bc6004199828595aa848913276c1843c22812c691308ec0ecc3dbbdf9c1e2641c186e8b9371373f6338197743ffbc3e5108b31d0ac9d0d3bccd9ce97d998f112ad641788b7950bcf2067a0fe18c3513672c5cdbcacb5d7ceb9a13aede52d553eda9b5718dbcbda018217eba194f058a8c31c65825c64813a765599635a36559966559303715d9552ccb82588ee735d02d48218d3fadbce5bcf439e79c73ee3479e973eab026c572c089659edd63777eb364f7f40ae6867285ea0e70af0a21467d435551146e8aface5378e5ed4a09eff4bfa04b2c490f1d04ba26963af2291c59d986102d036de4d032d0b73cf40d07e8b0780eb061df51cc39bc7d330635a4fb86305ad1b22ccbb2ac68f56837a10e6d438796796e394d8d11568f6e0220ac31aa8bb1eaf3cbabab0eb3f62a41697e52b7bed91c735c4ea37f80e6b71e1e0773e90ffa86e548e3adbb906f39dcb025926f32defa1680b7bedd922d612d00ff9c24e39f6725510eff3c74b37334308f5e034578f34d00df3e65752f67972e5d7e2caf383e08bbb73a3e40f332e3c4ec621cc34f152a55aa54a1824547e99eeee99e86f263ed8c56c426e4a7f5de09b61d5096ba9418871c5afbb93b3c0edf308a1e4a410e631115da27eea3a8967993a21e74b58dea172acb9594529aa773d8e53d794f5e93e76449922449940cd141f8d0ab966fba9c1e3dc7cc3df4e8556feebd6a7a7b4b05d1ab1682875e750f116befa357ed2ed2b636c61c0d7ef42d15b34ad17c90f8383a4c5ca3030f99a657d092d2a7f369651c275d8c93db17c6c97dce538fb057c6563f676c34913969ac43cfb084b4e7beb7bec5efc73386dbf288603976a004b95b99beaf9848d30c2ddfdc144b07cb2dcbb2acfe9132e6a5dc72a4ffb8c4dbd3117fbab3f2269dcac7f7539b7aa1ba982d26296225c62277bbd6c73f9d9f2f6f169d99d8bdbcbd677fc6f77c3a069afeb03763744a13e37b8fe6d1e42d0787eab0199f5c7dc554b67b5d1edb46c82fbfae2b4639736ac7cb6c89708cbeb9d8cf9238468bb9b92b4888bca623cee9558b7a401b226f0f4288ee76896ff498b71c1cdef254f42dfeecd8d3abcd5901a265ba47941b93d8ae2e8033a8efb9506e4afacef7d8acadb9e772f8a4e11390251ff5ed59899bbd027fe3abd7586bde362276befac322cd966f4418f1d59f5f1e63b6346cdeb0fa633ec7705e0ef2aab4db7b50f5c8e3c6d7ec1c0d48a568e1ca4707c106597e65295af8c11c84ab63f8e6c683bce60fdcd8680eea1107a831c75c877edda8573cddb91b3839371e943f70e3410eca4040aee3d0a9c37644a7ee96ec42c5b9cf53632032861e1703a17428fb75ab5010c2ee28a01b11df9b0f78313a5075564acb771e341fde86f8e715832e4ca55f78e75fb4c12b80f459032d4ca2e88344b0bcb6ca09f7b9f5fa50ce451a3178e7ef054e01345c012c774e84dbdc19dec1978277fe5cf0cee386c3cbe4d0354a20932808176288aea172e71ae840d7a8c19de7b81b2377ee01a700cd9d83c0d520790f28739d52302fa8cc9dd7401b2b77f4bd1e82c75d7157789e90f9c94f58e52715a64e6bf00bbbdaad97b5b36bbad68ad5f485e1a88ef90cb4f18cf4b0dc88112a15aba0b88f7237b61772980dfd208751af6901b8aadac0256431dc6f6d3f66b32c7ebd2a8655c744490871e16243c6c5861236c345ef6bc99ab0196e037e2f00ecbec62e942e7715abc2a832ac1846a0e5625431c285cbbb301c3623eb10400e63868e97bd93c2c5e767207143185ca2781b44efa47049c2bb8a55615ceb9d142e4dde556c5d0098623afa316b0652a98e8155ac4a4300f0fbd28ff701f00ab22a5ce3548d11eb698b18f374597bc2f5aa3ca8f94e8a18a2505125cc66b88ae16eb9e58ea78af1327a27850b96920db58aec08f7101c8d5765066ac600f20c4c061598f83660f7a31bba09948132d82fd64bb278b9b01cef91b8c16bb536d810aea2aab2382a5671b9f46da81e83dfab30944a856139dec778bd560000bf9721c27ebc0780d791ad38e3f7356035ab7a2e751bb07b15f6e37df66e81a361b90d45885c556e37d99a487ff5262ed57a459dd2ec443300d8c560f700c0ee33768ff176dfd93806127f065ef217c618f72f9171c4dd1e152a586018ae6e31f51ab025021f0f0a752f912b3c436e9059f90adce8766833e22be86919aead015f4f97a8d020037793ccaae8d747721dda7a890a379333a460703bc1fd0485db09923dcdcfaedc4b4eb8997431e186d23dd6bb523321957267583506dc447805b09c849b49af32f9f5613afa47b949cb586e751414cab23ccaa07203ee268d00cb65b06b046cedc45bbec2eda4652c6fac9764f1fae26ed22bf82117e55ed22bcb43b8996061b1ed95e532ae881a66b30c2b24546db94eca1598bc93c265c9bbc7707f35d987e1b05986cfa54ebd579663b887e06858582f71342c5791e16eafca927fda09c34131cadda879db7994a301632d5635e13a296160f25b57f196bf33fc160ff24b1dc7f4ca9a17ad356f2278d0b472cb8891a457ed1efb8b5e35e4690bcbb793c24593ef2fba8674afd654430d98f64ae52a97e1ab98c630ee301c29afe9cba753deae81ae01e32f28ef2847f43c49f2868c61e3dd918df785ad585c1d92401b9d04ca34952fda4dbead4af748d75079abbc6bf0d3d7903711bc0caf813270176923b98f604805c3178cc2d04aafda4f58da4d795bc521df5eca9b8ff1ed5ade505bbe9d94241b61f8a4574e300c6158045a0c8f40245460c329df0ea202e5dbafbe4abebff886503585dbee90a789341277a38bb8ef28e3592deecadde253b5a5b7dd69f9c1c2867752c40086efa16bbc2d90d2bcb99452d2f7e3ddacd9e821cb75517ec4b8bd7a3fd2faf7b3440984101ed12b4885bbb9288f0907c6132f8a378587fe943834dc780f5142686006ca401bae0344a00d216d2306fe0c79969436487ccb71a4ebd0ab9a2ec42ec42b4801bec7857d5c58314ce1be1785f9a2301f161313eef67eb65c0fcbfb4252501644c822bebf8214d1565b9d644e3221bc1a1d74e0e1c805e54dc7fa78f985851e60524acba5e538d20a59d421929792fab5540cf6da74e28b9eb852d6e810df87906b833d2c2d03fdbe944ebda61370371705cb43eff1036d841c08288a50e5a1fbf0ac40fa704812dbb3f2b08bc7f09024a816dec23af1a50ebd823cf4aae6aa96dc4d473ee5c14a2c1479f932d758b93af0d0355cf3641d7051648b13efa45831f2057827c54a927f3fa22184406996d2e400440fd514f745f8e4fb5141b9eff17c8d5625c5dd5e93a744a5e46eefa7899225fdefc745f9f7de7b4d7eb29fd1cffd096388e8278c22a19f308e603f369bbcbe26a0bccd9832d5b2959bc07c43136e7d27658b120fb17752b62879e835baf860680bc1d32746afda413f5b7af57ef25585fb9a748dedbd2d3cefa84094103436ed39ee867bdb036dbc6e729b5856b4228c524a19a584393e79ef3df982445803a7b4a8e594526a5974b3160e9dc7a4ba187f503b9099effc43f4a99b97e45b3a9099973948fcae81efb90a09778b3fdc0f0fcfeb643f1d421dfbd3c737901f3b7c3b1acf3110cb75ac1d16f89ef163877f8e46b70f3b383c6fb7d2c6b38149a8f0e19075513681c8ad09407e416e1de426b05eddd98070ea39f6b23f1789dce6d413827928a75ec89f87406eb39090835c88f5eab092a321240920461b12219fc00b03135284421eca4698a066468453cf1dca40dfb1c170ea652040847c4451117036221f6521425c68c786da7c1b01d4432ef26de608a1dc03ca2801c33b3616efd8447f38c80ef88f47880f28037d8822a00c74201170361d8032d07f44c0d9843c02ce067b3c3fd88ffef7923c23499024b9e9905b2cf25a237e39c8fbe817fed1af07030284c8435988909087f26b9223e06c86bc27bebd9e507bc4a2907d597334a83553d19fcb9c8a79471ce2d19dd97408b599bc142f9c78097f73ce3907a382c2f5ebbaaeeb7af29d9d04793bd12b59037fcca74ea13bf788fbc92b403bc4b20974479d366e171d84b8e5457538875141e1361a7e836d9de1b7d85c747737173e1eceb8f9789df8d0237e9d5550ee73b945c7ee86b1716211b2c0b2e563843756781c8d9b2dc6705246ac86d585125e0843f7aa7384d21ac1ca6f3bbf0518bf7a27c50a4ebebaa782eba46c310573da359cf74b8f2a78ebe109a0f08215ebd97ae9f16d9068e47f885ef015bae06b6cc157c982af963dc183301d59c60a6bad5debabb55a1326b4ed28f9ea4e8a95143c08ef7cc8b7cb3b14daa663ceb7cb2d1d973fcc013b7eb9298890a4bef428a5acbe498f32e2c8ed721f769cf0cee1b05d3f5c0eaf1c57755f03dbaf0d12b1f337728c31d76771f1aec6c55bfe23f2f0d0e075a4bf87d88ed3cbbc89203a949609bac805ed482d95464a2855344517d110a1bcda85564c6813c1cbfcacb0dac5d7167ad46074313a7780d026827fdd8577a14cbb83827c1ee33d93839411d7f00035d0012987f0c097f8f1bc212d032960a7db7bc3112c8adf0b492220bcaad7b431d7948b728c39ea2d94cad63014f9ad0033202e1e7563c81897a82ea646f77a3ea1af76a7755dd78583e2cb2986e3721514395487e170ecf2cd5fd5e4c2775292e8f9cb6b18de7ab52c4d5587dd8dd1aceab917971b7a27858b9fafb82d6cba4acb7defa4e0a4c8dbd03b292c81869fa1b799a657d64caf72b81c94b799cbaffe47afa0e4dd4f292dc8f6ea7289f198d7f52e2bbfed405f3542b5d40c17c58143396a9b01d9e82279f5ea6a3fa1e2ab5a28555fefeb4462ce39e7f48751076138e69471448561d468251694f0d7299e215f81db2421f207c370181223af69581d44e783bc525bf1b3b9188991679888db644ec29988db8c320d94e1019d5684697a5571c87390e25287e5a858a50508482aac5e2d172c307938f28799e1ab77941f80e1ab6f443c96b11cfdab5e819c0d69733dce260767330373e815c5d32ba63ec24392a8be02b719597e618d0a48314daf2c27611d5ac6f2cc41791e18009b19619a4680e575da8a859f774fb18e991c64487157bd32c0ccea2d5fe100ba6c05c394df9c8cd951a92d82a224a1f51ea5f35d4b4a08b18b96a53d1d8e0b2bbb9d62cb2ba5791301ec2d34a712e33eed391add64b8312dc5173ced2ea6510f6766ac170f3d0665e5fba1db683e6ee1a3d374932c171563e4c2918ff159519d37b9913a881d8c09b8afbd8a2acb2a06b76b38295e58f976cd3b03d53ded1aae1b4775af42c2ad211c6ce1c815572b95f0c954ca25534e994ca44c4ba5cc0699f1242f994c265ad2d129954e39b6e4242f69575c512a5952e9de177b4529cb4a25189852a99479c96b56c29bfd5286393c79096a8e5372936b2537697eba579a4a6e2a95ae759d944d011658608145162792674e2261181f6592c55bccdb69a79d76da6961606084781ea6ace4d968341a99f215e23327e54d881ff944c5c49cb2c88979521624d2e944d2b417544e4c16593652a93e4399306a74da6ee962534a88df72626262626262627a15e3a20861a74c9932c50a995b9c69d64b6e2dbe33cf925bd2c9ced1f5eb0eb35ed337270711ef493ebd844b77a3a79c22e5548e9393fce6946f4a79cbf9d2cf936f25bfa59ca3bdf554c992fce4243f6956389d4656d0ac96b793cd3213eae545b342e9742a95eecdb2d28b974a3ef29797cc5fb2d2e964f2a169a91c25d7fca6e4da8de6d78729a77290bce437a452cc8ffc46a3c979792175cdc214f75ecd2dd6fc4ed2f5e9a3895fdce4d65e9f25cf7cde649747d7f2b03ebde4f7c6faf4bbc3fa749b85fcf50c756f4e5d1fb9bda429a620d9526635cd39aab529ee75ffebd875eab76a9ae6a60c3b6979ebd71ce5176719a9e4a452c86f86b986dd672193c96b5ac3ee4f26af2113361a91aed7ca6364c2ef43d86894b797e152bcc9bca4790dd446b83f622312e6598ebdc246f8deabd354080c480c3e3e3e3e55aa54a952852a54a10a565861851554b8a7172dfe487b29b9c94d5ef2919b7076329d847cf6e2a5f84164bce6a697698508f909533af59bda54f2cc4b970aa51215b20ce76ea5cc9248303aad528d5ca3c268e4fe23bf23a73e22ed8c9c342af90d95f2d65f72948f30096f453ce9e525bb8675fa372260c94f8e39c94dd8f58a74bd24e431479d4e5ed325ec3e842b867174fa4f39c8fbcc379dfecc6f966923aff5b4c5bfeefab3bc3d123679096ff14ba65c034b1834f24ccba41c7b7547a351532175075a68a18516545041051554a850a152c5c92d3e99dcbac984359ff99a486eca46594e91aec949261f6926acb9c332cdad8f1cd98ffb41524f726b4a65d9013b4fca9c9439c9b36cddf5ca9a26de7cfc44999ca4e9c4cf3293db39aba8c264b224933d9d5e5026d7aa309df2b6f3271f6113de7cbc092633997ce430a6d3093b95b0b6dd3f39c44a8e63f2eb25935f37f9c94bae9d4e3db84f9d4c7e32b9495b8dbc945dafb4bcdd8fa95cb0727d987cf8d0895674a2159da813ad5c1fa6542a855dcbf8f84d27e2e8c4d7e2bdf7ea44f73104295ea68f304cc9e768e4166ff6ad2955ca3737c3786d3bc230eeb091d7348cebfcc81bcb61f2ac94671e6930f647338f34bfaebd482185a6bd8c5ef276b59155a9a448b9c95dc754fc5136d2d1b61cfb385a0e42f2518519deec8f321b6daf2c4a8727952a3588c916d8bc75867c9b13631836f9ac58cd4e356b5e319f23f7cfb22c1b79f5e9d9e974726c08cdab6378fbe11f04e6478e9148a411e6d5b16d88c7bce26d88af2fa30df353bea1898b651dcfbc662cdf907ce4241fcd5ab718e9c887695b643ad2569fa33a43d874e72f789b58a8bab59ec4663d88688b2912653366d450830c192a552a0503f3f282429d4e2653a9a4692452968d46f78a44a11086595b2b08745da5d2689437aaf9366fa05ce49bfc929f4479b33ef350286fd64fd7f0cf53d6b4bcc5cfdcb91a23cf86f84d47e2e8c8df74e4937c8e460e31d368fa4febcf475947be75977758ff1076ef438cb89857a490dc6252e6b68ef0f46ce6693dcb32d7b26b738c7c661ed7359f6e4d6e3dc32953beb9d3b56cdd5a9f7923c1cf4fcfbc0666996b5ed3d65ff20d4d5c9b7352d6473ef3cd299b6f32b75234cdb71c2999e7482356cb2c89248dc0b89094942df311ce7cb435954cf3ccab968d308e268dc0868df1b826bf789346fe3ac46e343765d7ab7be5cdc83510126944b484043f30f38a105ec57634aa236c77bc37b9c5261f9570a6673e473ef18ba37c94351f653eaf4f6bedd5a68fec8bbfbc78e6136f39aff9c8b32033fe7a4622dd9cf9c8339f23bce5fcc86be0e441f2bb65fe92b59f9eca722acb3777a4f3ec75df72b427fd208201aff39e0340de93486e6dc92b90e9a34cca41de8f4c7e83f9d63040e16de4ce4f781b5d5e318cc40854e049e004793fbdba112917029393a89ec4563dc8fb90d7ccb529643975e39fb97fe6239df73aef49a6bcedbcc947382be5eb1a76f7b36c3472d736605cd3aec360d7a31c936b5e351c530ef29ea4bd2795bcb11bea24af249c79ad8d3960a74df83d09f793f2f69e741d6226bf79d3dee4359084fb4d78bb0efacc49a5dc03fd916b79d37ee4996759c6a9fef076738ef6d55dde51fd3df753db723e75e37a7ebaebf99971827890f75b90e9cf75660ef21e870340de3fbff113fce38171213b74e9d2a50b4c4ee23515ad415f54550e15b8cf9d942b43de8b587b85cf7528da57832e9c7827a50b9edf7690f3fd173fefa47c71e5b795942e8cfcaa6bc8c900ca00226e43330e2dd338de0deecc31beea2eaef420efb7b7a9543131c7bec27ba804a52ba8444d9774490b15a21900000000b314000020100e878442916034a2ca82de3b14800a959244724e18cbb228c9519442c618431421860000088888ccd036001128334a3e0159c946b8fc5544204b63232aa5c7706a6c379e132704f1f9234e6e74ecf27aac9a0dfbeea5fec77626da7568ce0f5f2ff99102706072f7ea9327bb1a83e928a55c3dc10d0ec3b3c5caf47012eecca2b26d725192523d977cc2dbc0f0d390f9f1c6891fe12d18893068ebf652bb867889c04f8e3ab265b4de84e104e4b29aca6cb8ea6531088d27694e44d53287d5aa5d2e23c8383db52fd3707d1ea9312097b38252c67a9598b75f621b50f62e8e235a1f08b2676f23f5b1f27fecb28c02f2620bb6bf9ce864013b08841f88c402875cc753f1e29a1899124470fba52f9408071eab6c9ae42c47dce112142be6ea4f316de2cac852a4a7a25e73f8c6eaeeab840cbe99f25ea037a188ca492e11ffee72cc43c160c85b23cfb71ee0c1b778f4503a4a437546118f43eafb4c6df3f1af7189c1bee91b21693c0db140b7ce02a5740bc4ff0db41d607de176b8f3460f7fc2a2115075e391311a768832d927dbbf719268c3712b863aef2c920ceaf1e5b66725d3b5236e1af7d30d05d2a3cb91e83734b3dcba0e565c424b5341f857c2fbdf3db7412eac530c8119f28382668a68942d234c51c5d7e51bef3272d9f16429f36a9e659a62700f1f3c083f729faf346c4911b450591a3ea157605346790998f65a922fb44649142dc222f3a40350eb5b06db098f5d15c0f3ce4fdaed21636fd3ee7b71034a6e4ccfb08a37373e899924468ff8615decbee35a0f69c5b96bc8c0d5f60ac6c63df2519ce96ca2a8ee365fa27834d94540039b84e1a5857c4b1dc84701b82470e03e1116e16cb4f6ab842c35f5e7d156e3f5c8e0033bd25c75860053937c1ed65f3704ffd16ef6772668cb04603301d3af111c663221b5e4ca9005a65ac030bb0cc958950bcd9f8c0d173aad7edd22236327b2f3c10f57e0ca13be48c44fd6b5de9437fdc551f82bb691bc4a0d8af21d774210208af240cc60675164134fe3b386332c71060a12d855af8fcb5d38236b34163390024029b26f09814f1d97813cfcf75380624b98bac6b078c1ee247058c6d208cecc772e7f9e62f5716c1ea9b26a812b91ab7b48533d35d85e0e6661a1b8868e330454a6af5d70e1c62e961fba6492425217d09560c65405b35b131e3898bfeda014cc8366cc740daee602f3093834a9261b084c43607cec7a6ab141171070680486f991027321e69c51e8aea20ea6c81b46910f07b36420bc2d7d4691ef96dd82da9f1aba354adf9854dd243190570b693a80eaa3654e43c42dc9c16663ab4646e325424c6a916d5200942720b582890813e3203340aa5cf15d8eca3551263789889bdad879fcf72fb2b1075bb99de1fd690f0ee9587136968da8564b4775d502e25f7c72d5373145a611330cedbd292676a43412dcc348d6c2a70d4070173487b0ff19a0990ace1fe71d1ef1ecec6848dcc28fca9d0a7603ecbe7fd704c13e80ef93b3f524d4c182d4990111eadc4b0f5cfcd547366be7de288f1efe15391415b025a7d483965e8281c316873750a784405afaf38aae318f264296fd5572bcdcbb04c237237f881fcd01d83ee1dba97e6827f1f8dfd5c72c3d49eddac0f9807e14c9cb4cc384752b5fcd2a97a99eab65c294c35c1623ecc736ef2de24a18a1a7c462c12944d543e61c89e9cebc38504c346d50ad6f2edd4771769cb5b2d7ad65b45d8a7f78ca12c1437347da179018a283105a2a15c4347fad1b2b34061a44cd3622e953665814936601d6ea2d90adf060c8b3fa609aee6094fbe1793ce9044bc8b4f36352426fa371db842d0373374fa727c5994b270b1364f7e263e9f984116413cabba701761b65382acf6beaa53f06d38fe120b386fc9f173dfba6abe2cb72bc1a4d91a02bb3f645e8ff47b2548dedc21557a1bf98168a2e22a032b620fb44124319db1d884384d8e331d4f99be6fc0ef70c107b762795cbfee32b5bcd3241951b8da0484b8862619f4762528444e584dae17a8cd40272d79967db2c8385ebc9224007daa8634a8fb65d6d532c33e842d62841b222aa153603c9eaf13763fcb3d57f5718134b766349243af8d632e86f0edb8593332600e1f1606106ee69c32186e6a5941f84cd3e9031d0289249a0cb383a471254f1ae19398d9afba9f0d04c2fa773348a2006693f0bf9a2c0260f99095249e9fb4f7a02e80b0ed13381ffeed4c21a2b0ceb2296d298d2b21afdad71d37a4962c9e5fbd7ecae1ff1470bcfec89d4ae929a7cee7e22452891998b4d820191a42f4cb62574505fc454795f4bff4749690e5ae2c372f85e2de79e28a50c351577482c482d61a4e390c58e8bc8b2823cb3b4cae85150219e635ccebda188e6ce160a48e56cc71e91719c900e431fccc9e11d5d2a3b12f808359eaa80a5b89b7c08b610b16669ce4164289142cc68507a901a8c2833ca63f22ceadeafc3f08904f2b328a066c9b5f685be9ab1e2aa844c22653aa1f520517d1e0c87f81c2be6512056d97f25c79b8873a003ce8b955c8eb5bc358da1b756e21307eee2497fc713f397464d7a098c7c40a12535917b60b2d03cd59c8b812883037d27718522be155bcd018f76662de116e65465b1bfd6b05edad742382e10fc549ab4907994078a3a92db4536c23d8e71e32d4d0729ff3975f0986f3825db9bf0415f1050bb48431e82170d075d4f855802b91792141c4950b0803d878e1785f03583408ad4f17e823d49d034918adb94aa119404b2d6d5a174ce284fc43c37b8301254c1518ef038144403f72b2079bfdab10c27a5e8a2b96355e9ccfa2f670ea7f4f7d8a8de5d6a11309b16a849b5428d4950faf320e0a37b811fd9585be0a18406563433b4f7e44f1a75424e604bb740682b1e44d7a28bf223ad72ca8b64ca39210427afad00d597ae9b6d9f406052d0027bd0afe0c6a49e6edacf1d34811f65c7fa734cf019e669438592038ffbc0bac695005b245d81a6ee48c6fbea5bc2deaa440ce2eef4384ed4266fa1f6ade09525ba9010b1b7e646e12e680da79e1c27218ac53c7e86506594926ff4badf6f09526679099b48eb6307ee4a8ab7f5a01e5f150377c5fb06ac7843e339ca56eb39eea6bb53d433b5f328ca92a88abb60b59cd76c0a7bee22a0ce21d99fbd1524189930806a443cb82bfadee70d9a04f293c45d7c4eee5e4b9efbe0b7963315ec64a2e3601ef7bb7c765126b4ce7d24b5b69a6db14effdae028f43f7453e97b50e4f23300ad8376114a656668599dfedab889bd24b10de6fde784fca65133f7aa167b64ff3d51fed2a8297a55ab7965febb5afddf58337b65126e547e9ab36097def6801f6c06687cdf8c331312c404f45cddbe3f1b5aeafd5dd63002a4cdbf4fead9ac5f1467c3cbdd171aac663b76519ee0c10da2cae0a66c6e3302e287d15db5034ea31568fc14c35002167086ff97f83274b0bac6f2024e3b302fff787e3568eecb109e2f89164ff4a78b4665519ba6929d07646a06837f9e443c6192184c08d169fd0a8d1ea70186bc971284aaf2f5cb5182c7c22ac10ba19c024e22e25f434ade9380562ac5d91b42e8be6f44571f6e79c1434d29607f175043d358831dfb9822f3b4d5e814340467c2dfcd2da303410a986a232bb981aa2354308b233e51587662814dbb3e659a6d77a4a2b9dbd137969249ea43a805e83ab0d477b167b574e2fc6eb4792fc0c8bf5afdd7a8897b950420ccfffb44f9a791a6e8b5167b302ed62d282a7a99b492a4522da631656e44b2cb2d6ec8c2030cbda621691cc125de5004159031c551f562111f5e6c4dd916681fb51ac84749c17096e007b4ff5cc878309b7c737ed2001d297eb9243c4bc24ad382e5ebbf10c4bd8c51eb180d5aa6c6901194367528705c0362c00b2ab50924593f6c3272e0292787f363534807f18c6956e6afd76d1ac7e19a7be3c628f77e19700035f55e5481160e7795da8d66673e6ea46b66349f82ac8016a483d50876b600bbe2a45679afe2f4b9262a26445900e689cd638bee9bb1e2f44a0658f40537073c90c58bab1b6abf0f0155c5e92d7ecdedb0cdc7d100ff5f628228b9d49a4f5bcf11979e1f2e41e505f10082d501693eb601e5fe7ce70e0ccb07fb1ea00fa04baa864b276be17f9129cf423470c6d347975ac2b005a00fb7bf1b036e43dc16615718d3cc8aa00fc25c2d1216a21e1638d3090e3876a6db4bb2c141a001678cce5e987bad70ff1c0d968effc3c14c2db093ae1e4682693c09c10d82b129931848566303e71ac370d6b51e7af67ca9f5a3d3e1008ed31aadd60d05af344016f7d3178bf0bfd6e89d020943950eab05c13e689c28c2d725184d338e61ad55612c2fa402759e728c0b43c012a9aaa3e7c78e19dfd4679fd689842501030f925078a80e4a9a214c6fc264a6da11e02d798268c666f410fe6aa2eadb5f781ade6245ba41521f2bfe8fb359c4ac387ad86a24052349e29f3071a7cf36e94b102bf1b684d0cc43c18843c4792bbb318a4390a8ee66248e58a4e27586c35828a531eb5720000a0269270755bfc29d80860f44479ae2561fea3d2d061e76da43904172ea75949a49bff4b5867058b0e0a817f00b50b684780243c4fefe2b7a6079ec533087abde388c17b1a5468554c796c8f2e2b744bd2e5fdd56ece34765a22266971b8d114ff218f5a29aa44617e0de512d674a96f81744a1699458f02f3801a62c63e197e0849c56c4921f82099a4aa1657e8222689a258bfc0882d054252cfd259830d3150d5bd43bbc638bb934cce7e5445a42bbfeb87b1fc6cb42d0bda475ff14482acd376ceba953d21432807026e2e71abc5a51778256744ae839e0f6925a754c6afb514ae37cb441bf51a52e1b1e1550906b5e2ceb3c67f4b49d5329295c4c2c507816031f8e3a27ac90cfa7bf29f1a5922dc797b25229828a09954adca4956043e16db3920287408c279df415efc3f070f6b3b4e78cda7c7a63ada13250083e231d14f82248bcbb41f237ddf637e488f53e0f7e56ed07904938def6107ad93dae1280d17a40c6ab302f0cd8ec28cdb5caa221d3af43085e5933451281c17be4f0c6672037a0b7aaf611115a76b1b036aaf8d7a27fc1549c1e392403a88aecb1fad6b96e00e1630d25c5fdf771c138f6151e10622b2e7e452499c1b5a21f0bc4e1f6d6b1de0501918c42319b6fc3ae7b548de0ee614cbf2fc31804975ae96f719c5b762196d2e3da526b9d5778ff2c7ad5fbc4026d8dc3f5e346e692e7ce5feb14fc49fed9f66dbc38857fb691ad16b8f72ba744613ad2ab79e2d9236123a209dcb64680f5eb43ce5791ddf3efa08c13ca9690b03c09c296254950680a750c337781682dc4e107c23f9242062554dd22cacf2bfdd873a63f15d3827e1fc2d65d9b3fea98a0b5c859b433e1fad2609dde455db56838268b7fd1c82f27de05da8db5fdbd54d889eec80e6ab71973aece83add816356abbe8de03b4a6292deb8cc2251b8f0a0b14c42b4597762957a16d2283c316c6220e545c00d4b106fb648f26592ba82da7b4b2412cf1b2038bd41802c656446f6c6e559d5f0b0c1b32c1667a0c2d7074ef992cd206b52c7f5d86e48373f533fcf59b461f24d7e99734d549d04189e917d34223166743076819accbb19215c907f5d41df1fb9227b3e648e888ce384bf790a4060e0b74a9d82eb17e797f873a473c1f00adabbaa84e26eb0e4dace1c141529855a4fc29309738b052f402136bb65c44f985b4d12621184ae8f5433cc30adcdc3aee2a902fe69900cef7652f2d9c186085c13ece118908c66f182d99cb9a8ab5caed7789ad793f957e50243720c2c6c1495dcd0d3017ffa1c21f8ab2a3481fae296a8820e83a2bd5cc9e4aa2794d3baa1668718ca1a015541b9dc1cdc1f340bfdbf9b94b7149dcdf93a429a380120966410f907385555c9a1749c80cf4ac421dd416308a309f33833c704358ea71ebb376f14e708827780a5169c01477cb41149259d58a31a4105620a85470a03a5c61588371d0f7fd200443d101228d3ea974b7e58d53a7af10389c49b8a1b53fb5bc848d2d440d382531f68ac10327efb9edb170ee76e38c5a7568e7d2d1c33b7eabf8c8b761760075e17dbeb8e4dbd5569343d1e82cbb7984dc2204cca782452e096ba87b6e7225c26e97dadca335e192c29d4e1530c2d8c0100871efca01f80c7549c0157685e4da7cb8fab845e6030d2bfff7b946f78e2596574f35a8851dcae576cd3072fcc136afd4a90f5dc966d94c4364f91d3212d2a18ca15471435693ef4a4c346e7cea3e2e86f6559af45bdb46a3f720fc9234c1b4f28efc87c2f48d1a3e6da91c2c56a753fb881fb771eb227cb29fd32452637435b1bb328174303d314dd2c37748bf1286e3bbe6f6093135ab95fd156dbfcddf1fcf231477ca13298d1f84b84ed1547c8e80b150d45339801da745a7a4a9faf4b0a2b9b914a37d502122853a15c601232aef463ac29e16459a21ea6312234ebe9383a0f46718752099715af4b2275c9a80a4da3e32450c245c4d0640c9c25b15618e5f3809d655275939d69a66226b4010776f08c5c4b9ed9029703864111396ce8ace923ca15e854b8bd79263cb62074712bc89c26d2616a9515209a4be8650affce41411a1e2bccb0931cc5525ec63c21cc283434a8d1ea61c3b4dcb440ee1250fa2f4a0ef51f09543e0306580cb70bdbef1bd980199061fdf8fc51ff70c0f3295594a57e97664f55991adf47e649c93ac168c29d4ea68f044e7853c04c710ea958b8f30474768425aadb7632544c3fd75fbd355f1a36f34cede928354c5b2d73db5ec104adb765d96b886ad636d40951ff9f6e5c9cf58dfa176b8ea53bdcddb2ad5a7f3fd290a87d51062b12e034a7ba74e61b28f4a222965780144648c4d3678051bea0da782e1a6bd211d18c5d9a646f1564e3520ba18073c3ca2347a3d59b2c6dcce49d7ff7a8d8ffbebbc61eb08827190cc930c91735ce82c278d2c21226c82b1cd4f7b2d1da2d8684595a9d7bedf7ad7f90293df5e58e998df42d35c95f4215b32bbe5f1270533380d5d900a77fdbfe0b83795515c773912534d3260f309cc35d5633ea1f3145594cf2245bc482183ee3c78993c4055c5ce0b75465367de66f64d867bfa4e796b3ca0b905a8c120ce681d3495daefa680ee2ba829d02655ffc412072a72e5fb8f6e1995aa1bfb0f2a35677bd60c75a91ea5bac680d1a1c0464b0dfd530c4aa3800a6784c2a54f3c818c3cb22be1359204c245c2c30609787ec02c2d9acd91d87afb61478ef1b990542d72d226c15ce5ad3e614f1b6855cc1598df813ff2b5aa1df58c4767116ed51512e88412bdaaa0d44b5c1b169050d0fc6fdf2af8e865ee1a6c7e8ea921eb46105d256a8fcc7419f92cf65db8665f8296de24f2b23307fdcde8fb4c4319f441d0519624208e1ff18a1e89d992807759602f41d3bdceb6c6a1bfb6c85c58f85a9735e6190745dd3f781186366070e9aee603cc9c9163b548e41c1132291a6e542105b669ac802781a509d694bddb95d5e06863a4a056c74db7857a2a418abe105417c72031eb07db88c58bf4fc0007de062ab82f5d848dec145fa368d126c1ebade7a304858ce6191e56ea147041d84e3565d4199bd3b8f0f07a7b1817d869bc7ae3e6dd79790db29707b79756d9c8647640f402d6c87856a776703c1281b5d250a4ca160188384813a4d218dfe6a242bbe972b2a8dc8df6d8c44d89f68bbb4028dbfd6b153815b9913e4ba92056e4e68c4fffc9725aa2a1889583e5f351508262cb00253d47e8b2bbd665b00639628d6cf336062b6d6bce60e6191454e93eafe61070dffd593790216c00b68e778d59ca7e9e601ab96c519b207ec1eca309877eef7e448be982048571bff4f09b568515ef16fa12dea0ef15136564bd342af3c458fcbf8a0cc5a3ed0f024556597f292923d3152a028d22a0a74ad36647d61c38cd868eb76702ab521dca92ed0d95641b4a7533117dd8718710131521d4c008cba48cef0941d3b73fbcdbce3a4e2e8181627751972e1e44ed86add72c33dad1c12570e3313e9ef09e5c687c200b528da78d18003e2049cc4b238ff3394ef061d3f02110f19cc9d7a137e697cd44f5a92d3be95a86b4cfac5c7bb5bc4b9e596ee090123e3a108f8854979903e18ec417e35e125eb93c593cb66fd93034c052cd5d1529fe28ea685df9670a92064f53577ef759c67bd5349b4f4395398230a50b2deefc75d49243f31e55184d3313c380c8ca9b6a6ce9b0a4fd3a09228704a9819bb10ad28aa02ec50783b5e18c520413b4bc6e3f9932398575d421c87b22d092b9bc652ae98c71d068e0bcd9591b4c12f68ae903abee4ad98a0b26af1f59a9a0e1717beb9f57b6efb077371ba86c2e6a7181c1ef7031a715452539a46b8ee08ebdda453a1bd1f07798cc4e365377d95576936990a760d34817a9f8bd7860f939f1481c247f3f618b9d0cd51b9d641ef477d634b2365dacbcc9ae6617e7027e5ba0717cca0aae1b93bfaf66d8f9c3c7606f20af8649fcce7906aa670575bd978b9b023bffd6374860832011a2c25dac2db9be17d16354732d85df2811fb7e6c5daed85f21f2b68a070be6e4748df377875173b690a4657c394417f99574a4a34970008122d8ccc955229e5e78bbe7d6221d52f686f9180aeb829f864704c26323f57565d9d41472cd36930b4dacaeb83d82d3da2369999ad0b20a48725f138205cce7b5d968dc2e2f5fe8d4b2fc8128a289ca66431b92439cb4d44dfbe72543f46e84a46831c2ec51ba831251a5f108b9d0aa48e531841700511126db13602ac9e1af64c0947d043bf91d89aad631e8f2e4ec578ce520aef2c2e2896495ceb02aa78cc5f9fbba30c7ade38bbdc5b6eded20954b1dbdba635149699f97e1092a0bd7128760524304124a516377d5352d279734d162201e440200d240687f8dc4df446cd0df1cd83669ef2c737158533af6726abd2260243d801b8c201bea819617716b96b1ea5d2d86eaf4e850d186256247e48b35805203ed03f1af4017827908234934eca205e27f43240153af6dcd2f246f8e836482e0f770e065596b0333f9148d2c14ea2653ec0365c919f1b29627910f9aeaff1ca44338130c2d6780269ecb322154db27925a235a0c1ff785213da1e31de955323b97f33fb113c714eda8312df6f5c75a41ea987f2602e4c34f7e2f74e304a0025aa695b0e97fada5d9afa3d6cf2b5732cf7d7450c50aeb04162364be13027245039dcf1b055071039c466d8b5f8564e66670386580c8bb13457bfed8101e622834bf3041bfb0711278d86bfb2c8e2844ed02dc1ddd8b103df32f32bf40321a98e5f52acedf908733c66bab21e5b1eae04d801b3ad5f88fee1be9de36ca51b5044d8dfcf21440206aa13fe34a4ff0a79791c707129df683c740801ecf0566d49cb960e5ed82dfb33de6588a602a2e03b5cd84d3648bc270367ad700773f242902efd8f8d3c05f6f4631832739e16b6f48b14752eaff42c9ca33e11bd005800c7c0f6ccecb8a98c52d52d01171b6c6c938c40b67f38faaaf09677f62b0beac8540d0838de315684ad560a7fb533fd87f4c20538e99dbf231eb38d5669631eef3d8539161c8e5eda107a833353b4cc7183e123bbd1bc2ec175bcb9b44e57c3affaad9ea694c90e3f25afc78a3aad5929f6a26244032e45c0f62e858908973552329f59559ebcdbd85ad2c2c88ea3eab025fd899ca332a28da96c1b518c5282a2bcc49907e694058f5526f74552dab60a1eebbcfd53dbca75f6390492ded46a74ab18adee8ce593996103768f1de114fc673cb929db144ef1ba7dd721aaf73bad656aaefd41a4a9c3b517025c80753f1b4da71eff7214622ae33fcf371dcfdd7cfdc7beea5fec01523902b1605d0a89f2fd799933a9f1df435d937592ef9f5ea60e4b504932e982a9e6d90041e8945b63b4d1c3add668843bf00224c35074d8cd181f360c408ae8ba971b72ba66dd95974fb23c5362a4e40d267cd4edb5e0471d004553e595b9c40757cedb8878115aa587ef04e5d013adb2c1bfa157fa16b0d4dda4158510318cade3b5c9a48942e8331bcd23b84e2f39163a7150390b2b3ae9f05264e5fefa2d6181f67063e732e6353042949dd69743a2840eef657dd2a625fd52e08dc86b448aea058eb531df857142d996576c12177ef2f6efab0326b403e829ae8f347b56cbad407fd044744c399dbc8ef0ea0b44515e282db2a9d503385be62d35fbe84e3c28dabf3df04fea8bf6e21fc5537c601aad100d8110905b61255a561c4999c129b1cf1d61bd616c77a782fd16b146e8e4e1104b41017b0023b52db304301a8bd5d04ef92774732cc11ccfcf01b304a7409a6a4d078f6ea1f77ca738e861ee874c2d20ca36163fba0e5c20d413cb99d0cf805952955f2d16c91cbc9f9daab70ace3d3d0e7877cc82c95242a0e3dd067e9b24865c281420e459a0eb6597011a96e9b9f841028f841c9a98d4134b62a085497f6d888df262c89ac2288f88768fd33c451a723114718437c7599e5a3a54fc062d873c5701381dcad18e872d385f5f290d44e502188bdaf30ed81206f0f80829ffe6bd3a35a4628c418b29a873d555f3a1856ae40e2c8009f5af5a9d8d1eda6c122a75edabad799c62097638c32986c8a1321af53bbf8b23847f1cc1768659390bd7f49e5fba5edfcfb62d5fa88c9b669a0c53095b889cfc9ff056068e3720171757bbc701d46fe6b332e9c50860640c880c2160c6be642e910593a06087e4c0cf85442b5764a7d43fe736d468b70946f29921803458fb67a791e801a27f80ea2d2da42c4bc3fa9008db1035c3a4836b7c615b858809c0ce086fcdc2754563e6cd164dff74720916a25b8b4150ab4e0ddf7dd38ace8064b2222409493391b17c2a04237539a47103967b0647383a5e0ef2867d068561239b604b938073b0d79172a52009c05d73ee54d95c418b13972ae45c175fd7767b183b735ce9bf952a4370fcd67bd29f077ecef82422d8644d6dd81ce49a993f6cd9b08ef08c23a353d39b40c01a7ee2a0601665953bbc60c905b167a0f4387cfb0cad4109416a3e92873118688153b0c49fcf9b142793da0431f6742486987ad66248e0ef0b3ecae76ab11644a39dbaa29dc4df4ce9acb878110dbd08611bd1c8e76178d4376e57833a169455c78c8c27154f68952c64addb1601834b04e4db1bcd3c2f0a449bab1a863874f220fff2fe4407fa1ed7dd160dc89a9900bd999e800650a35f5a490d75fbf6d04b4be065c6ebbf3e281b4a348836e72b058b0d347f7fc5620d477d26fc55cf943a24fd7e5f62e17e8b171302468dd91a2ef335b3acaa6dbfa782403b0050a501ed1cb7d8c80406ee1905263fd9ec7b90497c5d7da755ce2a61976aac01544411747ff0e3dce9884c1b93c2bd00c2b3d84eb00fe687f550dbb09d3f46c196f567e91a4bed1fc5df6a3f587ce2a62e382bb8e0440740187c81db2bd85716034b8d31937b2886f4ea07fb3dbe1231d9b409fd706768ebcda9efe44dffa5c860b89405e73bcf23ec6dc8e5636766728dfd66c36c9ff050a370fee1ead12b2c576b151e3985311fa7579e36ed754a6b7b05de60d8b818e12241882edf2700efd0778a800cd6f7edc89926c97c249ec2190c9f2c179e0ee0d875f1b17069f27c0943a849188cb90e2a83fc76b3fcb1eddea22699c5551c90423847837624a5327fbcff00e4e951f2223d7c51750115ec7ca314b5086361548151e0978e26d37fcfa1d74545ffdea9b29d742817f4356e2cf679f6ee9f823398e7a5e8e3126c94696aa8cc5f640d10a727fb54610ad33387f037d3ec43919816789e619de65d02d78065494834c78197fa80d3944e9eff6f1b338c8467dfa408681f2f80678fe32ab12d299001248e43ef195a8baa14e0c4465521b58d5413043ca69bf056fc359099935df5b5cb6cbc71895398e4e3817f7174c48b12a250097a8fefbbed73981cef808a142984a469c71b1cc3e7ef2be6b33dade28b92b788a9264c1aed1b3cf0f286cc09f21126b0b92264edad2f573c1dcee562d6edb98113c801328d3d77090294fecf7c7cfc6d87d5470423b2dec3e719c9449c29298fdb40d7f95eba349b11e3cc2a637f28640f217ba0a0a13bb382981616a9b0cbbc81a2ef66bd8363c7026a34f6722176ab9c4f03caaf920c0fc7f9d5ce0f166b1a7770a0c971ec643fe5e8b07a7e1c596d14c11df2baad805515865b86da31844380419611693770d23cbb2df11391a3a182a82f31c5c42a3ec90de2c3ee14de6c7e22099f7194926439858c74ac922f023d1a6dad4f658c93120f4cf9e197915edf7e266dc0d1476a4607b14e61da73b12f5a499bb28649559218703fc62d83245ef84b805e8b6b4ddf98e5862d4b92f29d7e5c8d1ea331829782c708af70abd8a8f5bb2dbc8f79f135018ba51d9c67b577ff9f0c01f0fbe747dbeec6059d8fd31ff3dccf34bc381b46c804e7feb03f416b2f5dd7e29a5ced4a6839eea5127c84ea14b5e698a13a546375c6634765bd89aeaa0b4870031ad52e31d03d4eadf28e2c9279e74e0df55be26e5f6ac0e0b5561735b153990d842703ccef43e21f568552e04bd7e3d448b2fc9dd8b2ec4d37ca3187a520854d4b5fa8b3b6d973f13055d29d8d130b70a7dd13ef84c5bf0e986fb22d79765a38474c874df2b7936f40985e195243b3799db5174c207ca4dfe52c6589106d15887fc7295aebd6c59e0c1e3b4f370719ec5a8732f5340cc19b403db4ef20e2dfb2bb6179f2dec8b519e0b93668fc0b5a0d2c4dde9656d3e03d0499e6f675d8eecd697bdb6d8b8f6bb8dcc0431841ba4cb96cd65c2e3bc94499cad64cb140c27a78bde9606cfac8f89c694adfb1255a20d34d6c258774117ecae920ed2b0fcb4ca589fdd964518784e92bc39efe1fa6cc532df2be4919fee2e2c3bc7eb2fb5594345b3aee26f827477de6d4b533a499fbae1be6da30b5dd0e15d6a58b9f5df24a1b6367a46f6c3e0df1053cb64eab462046b3120076130276a5da93626988f31a1ad20577f2f8763ab82aa985e01a1c8c3a802985a5d8dc3827850842609eec196c478a7de469a718370f8e1b513da9f0e144c738aecb6c58dafebaa2d0ec6ad71862bea47c393524d1020750e8aed461354be2bd919afd146059fcf67cdaa4365384ac42af7f4cc12788567f2cfaa709f812311a52d413b20250a03936226a13d26649ccf449a53d45fa95435189359304da6151da00b80d1b48af4421d927eaa9280604cb6bd939c0da27516986594c036a8332bcd29064870bb7a3a9be026fe6c3044dda42b7fd069b536b696998f07009d0474a5d2d6b48eb8d682a5c51ed66dbc450064c003aa6cc5d1a4b37ecf538f2018dd2c2423d763083f4447d73867c71e411f7bd91fdf5ede561c5e0265fdd2bbaef9dce40914591036430749d8dc321f8bd8a5870a43a41f6aa875dfae08eafa07a873198a3e5cc48f18c4733b0d0f1e1e2dcb22eb6370e1e0276088189210ca9f1f691a3a96b2839cbcae115355424bac8d801fc79f51ebc7b0fc22e0f549575b4176733c0aa684c1b72ee0c14f852790ba98aab04efeb921d091d22865127057b257376febe645330f165c7f9d58fb3a221a26b00fa0ae1212f73af61b477436411cb3d1d7daf406348ad7d651c80903fbf6e5888588065d50d908902846c56ad155def12536e6de336b55bbd142260a4cc25553a2e27b2bf82bf419a55d140568a158d6204a225d20bc099345922df1e1c6fcd8f983bb6ac29c9a0f22a2956f5f118c6d60064a80bd2da6377701938a1db3bf10a221fa56552a272e6b5214d65c24eed2de69022ce2542738914bff63b18e7120aa4048617f9b710dc6ee10d842ccfb1b0f19a4fe265575b31e8f4ed1cc83a18188f5778b08f18134f5bd50faa0da12f4fb7e3c125a02d0115dd227709d0e93a2f8b0fd82c3f0f0e0e48acd700c6493a347c53d60aca77ba47de78643a5ef13f5e2808e0d7d32f8f085c17676d7e34c480f862779b9965b51fa3030015ca647fc89e1c040eac68e95e24434a541b446c412cb55ec87ea0b3370f7b9bf433b46200531e16b5f99fdb8771861ee21a5bbd12cec3031200e794653bb86ce1e98463869c9012604dc3d253575ce40319038bf783de46dc19c74307eb50ed41c7946e5cfa9002c8dbdd4b3148957c41f0064a03d4515dd8a44be604e7236df60f574105a99b97229e32d19758825c24251ec31e845f9be96ff644b7411d020ee452f897be7ac72716bdfce08bd2849a7f5c342f85d2eb8ad19101937bb8cf46300e065cca19a3383e7bb54557875f1f116dc50268a27b9e1917dc2bb2881304675ee590765fd374e552e0b766f67940343e88cdc9cd5dcf76bf080854c7a8e161723fea8866d380dd059bb28d36d86856017e300fd90b80640782058049057ff3b04b41fc14dd1ea5c6e0007e8df9e18538de44f0df21bebb71149090a495c04ae0939f92e60d499fdda416032556973d58eddd31bdbd3a9093284f4d343bd69fee1306772c10bd5bcf768ad1059f63bd9dc77ff107c8434bc3ee8e60b0c3ac1dba3e2b4d895b4ce7bde3374fc3090f02ab53b1f9245d9f7419ea5c694a712ec3d13e0e329b69d2f52f8d2c6097d922385abb04909a34ac8d5360eb04a03599de0e9f157800079e213000d1eaba1ce24f788c74ef8147de9084304a097e1865177f27df75c44fd873dde5b5ddc981d90bac7d023f67bde23f0cd650310a7b1e2b7a2a4dc20def50ae1b58e3931befec6768c7f1b09757f6ac2dd68fcf43bd741d0235358538e7f4e764cc72ef47b3b2d95f578c06ffc1fdc0bdf471b8522c59769005eba64d2da29ef4b904921fed32a293c1511d76ea95b037f61e852346483e726f7374477e1d16fb05a6e5fb82218fa88da989f5a6e65a631567caa0e1debfb39617441724091a83ccc92ea0297ebfa9100c574fb6d44a493bb9123079f3f266a49a5abcca9f62618f876ba2b555770afecb31e1ca924273b73c26f88919e03207c0783cb106206e158438c79c4075eb9659f6a22eecab0c49dcfed2e3f4cd51f9fd0b46025360bffc3b6159be490b52df73dad360616715f18c9974448742a7e1aecfc0e97fd9b8da81cd57bcda2f31e8168a0dff0a43de78a1e0783797985f5999c9c553cb90e34cdcfe119848b36a24296f9972e18d1a042ec9c90ad1da7dee787f7d861f41f499b148ab257522399d86286e87d04b7aaaf190a1aa3834a145cc3ee6e03ff79868c39fd48cdb83713e4a5ec814964ac1dde2e732950043c55fec3ddffc4129b0f9382dca5fec5c5ede1b7933763bf1153ad15f28789747a8482c6f11ace574402d0d75a569f11298a13f630f155c0a0ce52267da16025f452383b738397c3dd4b2024c5d1297aa835ed1199753f8c7b55d16aa02117e4ba01600e38766d7ddd23ee7105d9c3626db889eb59ff40998e5a3560880928f033568d64e99231953e3cba2b1d8310a462238330f0e936d995a7693efb9815d809ac49a3e88f018b7a0a0faec27e150c0918a2cd6021d2d3eb450cde4bf8282fcaab82031a364ed70478c0687779318bb67871b596791fe92bb9e3afb0259235baaf7c9e2698ea7c0d34e92d15a5716ee1b4d7f44886c02ec548e03779ada65d5ea9353e16124b8c2736401ded200e1572802787c683152f492b1899438e806286da4821f48e454a3495e26bc476c51b60a0503fdc6495f1eb6a0e911b977d08eecf0b408dcfae3fc38a25acad5b8b27b81d35ffe46b63399f739245e4cba7cc3fee44781d0980f75cf74725d94a4034838d2c2a817c2f556d7a1b8550805d0e11159316343c1d33d0157198471104e01dea34c5025d7e0e105b2a9366da5c890bfa58339327d442e7b628e74102a6e22b242c8defb51c0a79f359a4ddb03ea72b70f8707f2d0d1199f1fe10e371ed057c93ca4e40ac8a4dccb85bdbf2d47403618338863552eba67cdef99f56962bf07c8b80df3f39a1269c24455302f30e28fb5e562bd5286052f1343fe5900fc78014984771c0af01337c32bc8f49b70ccda80c2151b4fca30df96e24f916ceb33d06be9e3844c893ff02fb116640ada69e19ec3743d45901d63d0c9dfbececfc57ce1f7c6d26a6b1f8724dd7def33ef452cced87668f6f19fa3349b1439c244ddaa00edeef5f723b6d4b77bc7f2c2508ac7ffe22b0d23bff0b3f147a2d43f6d5cf1dd085b07e524b24723572f1e5dbdaa22da2bee175312b16706d7203fe47f1224df3f6d6335f77eae0d515fa906222f424efc35287e5f16e35cb6dd63ee913089c606dd62d7d13ff6fbfe1657df8f720789bbd9bd46602b5e7313279873c101f28ca696cd6491e069f0385d8c4bbfd5825796f03c1dcb14f3effded85f897d21285df6c7e2cf31ffe52cf19cd4b4f304711faf97b10dddc649420847991c93b20326b3e1f06235e83298b6196bb21bcc0e64980e85b108d48f80809d41a84cbd172ae6b22a18ff3964d915954973cc50681358c545bb1875954d510d0229fe56fcb72d466c98af950752646600b5bf9ac79817ff5de480e010bfc1a23e3b17912c182ca6e3edba247edeb011acc6ad16b80e63ccb943a9051d88e8cb2495322ce25e97c489d27643c21f72dc2ed3072652d335958db51170b039a75a4a9eff50b017346054b590b7b459302570d98adbcc5f5ed75646a4cbfad7db16d103a2956102509b96ab05d9184f69ac53e33a9f3a59cb207b938ec913fb0f073ed0d54965f06c40a445d2bdb5f45e29a6706145ff02b83586d37e9d04fce2b53d1b0ce8f25a75e95e52b7702ffaf0ad0010d39f09b2ce653703aa0572b19d3a2819aecfd3f1829aa2b884e94e11af6446028b2576d17060dea940f5fd16366f513348bbec4ca9f22093948834c16ed54c8647d7d243100f82061355bd0785e896b9a925d77af528ba126e01fe826e56007ff1ea7a0f945e2cd04bb754b141f562b59e629830f699a77db343a19badc1e074f364dfbc1954b4ad3ce38be149c0c13064494e5d058cc3a2f38754a03014bdd78f771a6cad41e1397628c65f31a32fdbad6cea5ad463e3c274083ea8cc93a5aac9d45141fc500f25139483b7b02c66986c37e8ed35a8d5fc34a15dae1b7e4122903af2203892abc8cf4d5b749b57395d53a13483cd2d41949e3418fbad98fb520d29980277318e7ac8be2ad1e4fb4f570dbb84cc69353bb1ca2e2530737f68f5ae964dcf0efab39a1c44a759dc16b429e812459e4141f54bb555aa7d8ea5c0f64b3769825a507d1385d3b9c4a6be6a560b1c1806a8fbb5d0a15590e2399860257734ff5390fc1c7de2dd8de0a1e7afa3f6ab62e22200816887df1a3dcc29d59c9a34ba96ea6f9a7d831f6aeba22179c905fcd8f4fc25c4956cf5817451f369f09769d08b559a3b6abaa78338c28f791df4384494bca16affafbcf78da0cd68b5d2b0a05c5f323296ba748b2383d96bf80c6a88ee434dd8eab81608876ab692f1135ecdf68df0dcd2830293da01ebc688a85ff09440d1969236a672510d95584ee91a1250d7c5a38ee1fd0be7d549adaae5524e1ed110cdc43ac3666b67803f578bc45771808ee340836e7f669a71774205c18c8c149b6b919649ca923e24d57b5b7fa2b2ca6503e33acd643610a130eca4b08005b0aee50c6206e7a89ad250c30c3cb521289fedeb853633fd63312cef12a063ebcddb23666bf7a9d0735302c8e3ce091cb2e4c00385fab9f8699e404a05426ed849450a05d240863b5cba6480cd5c2a97290349e7c64cdb74367e81e59a1c7b725173bd5718eb0691fbdfedf49cc315f8c0096b3cfb530176ed3e4615e3894c269bc4e61ba80d857d371a56a160c5db9ac108c9e2e9e584bb8466be28428d1ed2edee9b0d5851dcad9d9699745bfb092ea22f2a5e15e7f4802d465ee018ef07327f5dbc95896f7eb93058a05d97bf1f10145833a3f1956d06fa157f715a6a5676e589ed1bebadb426a45d71bdcfd684c73ed98ee031a1470f6c8dca9f8744162b58952f86532aa0034ed777259fe5d0203877c3ecec72893030d9fcea6ae5482964f5f7e787b8863f058c2faf72d604e07f4af452de1b67feadb406a55b2b5507f1b2819894a5869094947d50379f2e70be562ab3e9ec35927f08fe16885f2c5b9c66d5754e98f2917e2aa8430a898a2644e497277e5be9e1f345bcac8ccae8af13b822fbc02681410e6bf100897760debe170a24e9ef4be0a6467f3fc73957cea0085fb22446db048ec5a8d8a089eb09e6e992c46ce86d669ee13686eb785f81c250fa1b152079a7484a8baf4fb1f6ea019fb8ddd9f55baf90e1dcd99e40ad42e77128174a619f72fa2319993f5e20ebc625c487005a7a3207724beeb03ef5af62d2641cfb529b26026a52ce2db378fc50ac0e74330899cbafffe43cafd4bb89578e441123f463aa6701d90cb066b2baa40551ed889ee0d49342869052492e67a0a69cd9817e159b02bbf50cb0f21c1de9ba85e1b660ba3a194f29c97ac72fc0cf7cc060624b1b002b97be690ee9f6562a9291544daf6281dea9f0ad7a87e3b21192436c554701185bc6e8860091fc201bd150a9d0cd8f0e771f8d48c41956746a2dffbcc462106c5638c31cbcfd9bad700d3b5d57d0241bcf9ac1128a8a537644284ea8acd1106610b6ab5acf2c416939e84e82e7214f449284c6b392136b262660d49f40c521517541269e0e68ae6515496d7edb0d6ce16438728d987eafb1f875f62ae8043fc0db5741f129a6b0427d3ae86719dcbb108ebed116cbe3f3ad227df34b5ae1f5efec8f30e7ad757917e00db932235964bede144977e847bd3a1ba45e50866c6869c92025bb5d332840b848101e55ef9815d7632369e3710ea0c8e0e9b86d09622ffb10a79dca0a2272180f5ca28a98d18120b200a1891e6e010125e54c9eb1d88941a624af20b1193730c8aec69151701269d50cd50ca512e64e759747a686a6458dca8d201114d76295a93989646d8a0a1e4d8d75e2e308470181f683f8dc8b625d77fce616f5c3a8c0d2f46d089089f78dedc64cbbddd59176adf6ec8fab70a687cb0402dbaf84add99f77e6042927fa9e8c6c7d23ada447a08e737cc857b493dbf26da1729e103eac71da7e221a45a0e6b618c3b2acffffab1aa7ba22e6f167a59c474cb5ce5d8f61f08a2c4d88f90f66dcc99daa828f65b7c5b97f7af07dee8fa7dee82142375379c5782fdd8cfb16200782df597caf1d841de71f6e72f16a9ce6eb1e083a6af2de106422a7a524241e42699aff512e9dad939518c0d7a1fbafb9bfa11a19bfa3ea7c075087b9ebbeeeccdd30f3881877a3bd86da126891e859f5e2a0fcb38324ac199945a9e803142e408d03128f53f8d73e19829be7036c905166a7acb3615960331359f0cf86d565213e0892055aa50bdd01addccde84146178b068c59a0e55af6b06a2307fcb564c67b914072fa10ca5b12009452086ac94d84db059cf1d8de5520d1b01a066e7416157479311417446b4b7e7b000100042001808005b6024f3a929b587b700c3ef3d70fd3cfb912f090a610d8f5533792c774f00b4324e0a421c1ccb3bc71444a4263461843cd561e40323d7c06e56fb51e0fd8d401a025918530dab848c432b320ac5daddfa288603fb5e064cdbe8bad8ac18287d997ce28d1d5350b7a399f858ccd266ad4901592317d25b603266039cb0a3121458df3ab23b158f847158e0624ab18487c327d660a47825e5c9639a623c93c530d0249dc2507a083a1b02da32fa5005de1ddbbee510bb5d5aecc925a722bcc3d6d70b9cdb6f579a5b44ef374f616951c48d5cd0961502fed3365d099b0ef5df20e37812877825ff1f0da1b7581cde9bda32b2cc70afe6681085a0658364598c650614df1bfd8060144d299eb40783a19d11f4eabda0729116878660261e8f23fa64013100b87a14922420571b7817cf16cba70afb58830cf23bdcc2828855f382dc6c715a2b3fc24a22baf120e75dc19ac504493b9f88d9393f364be37338bf71a65f664035090292fcbe6f02c4cf97a1cace81ccc19bd88a45ea449294878b7e04211bcc1d10ae4c57ef6853eb6fed921a2be80767e6fb913c3708a3cb738b54f9773c900debb19e5dd1cbe970055549250aad9805d4d4cc4e393de17f963f9873d908cdb3577f36ea9ad7b73bc8017df3f5b0c0fa2702bb7a495202fd0389c29616901702f8f42c6814f73e5dbb12c5358df546d5e942f16269d40b373b8536e75b84d4a91e63bebe88a5b71b52be8cc4302a2d0fbab70d9ec1506e72a1e899a853e7903dd099936f334d2685f8a1f0f00324573830c3062e62f9705f667028771c3de3cda4e41ab347e17d767858f5ed807c1976b421105ffc559c1313a3886e6acbfa9bebac02920b26c3e41ff044e91504a17bd6d9ea94acf8e7bcd5ad5afd8e4c710ad1f33b173d1e5fbda08a0de75137e0021c15c217121316a1cac70862834df4c398678c5f1219c6eb5d30468a25010bfe89110712422ec1982a5ed3aaf9ee4c92d998078dd34ad07e89e4269923346aa03726c06c71bd333ab76d5dffa2da7210266e9949505f3d4e57c37a882744dc2c0244f0d989ecbcc80cb54c1c39a4470380dfb2ece80160edf2fbe12868a9529eb6672a85727ea03148246aaa50eaeb0a2a704f444c1c94a5890a454b64d0b729b625024a328bda2a4694d7a4c7bb829407875fd08c082847de0fc9b11e8ed0018b2c6a9f0067e6f1186ac89f609492e423708490e71db5348f85a341ab41e01f113a9a69d07aa5beadfbd48ba9388bc66ebb6056637613e6c53442f528916848582f3c8443cc48d117aa0065c77c359be82449c7e7462626299e7889d4885e822abb926a14010ed3397d013aabba1b81929b1f50205fc49c7800d593809b40b2384d83620e2396345d3f2c13bac395c2b5aeef7bcd67a5eb281114a94b15e6ab3ed48635b74f6aef22939a79fbaea90716c51438f303c54218dd18e4094ffaac5bfe2fe157ce9f9f74248e17e2a771d17a5b8ea802305c9ea2f80162d4737222f7fa3cc6fe0b1ec0deb7524ac98ca866bfcca994365c0391820e249d257f42aa0774141d25b7f1bb33648c8b76801c4cca9ee69fcdd9efb925d753d96bae39f254d4f318b70d28a9b6a0831c1a0ceea90d2d28f97abf2fae65392175c8dad14319e38cfbc212c276fa14eefbe5503448b9f3c6f40f63cc6bde07c64d369bb15549c183b7192f862d1f594a2086275644c7fc6de7a8471a01b2f6e1cdade332df446cd770b21c87039b43adbd3ee958f441bcbe47db8b8a5245a80ae87b08c77b00248adf57e9a6f78f1a175fbd6f0315dc90a6d23193ed1f5ca16c4c603552f0fd591a8c43c7c2239a73b0bd7b672dbf35d558a00658001fa7cf8708235784c0d14bfc8a5fe41b47b5033815a29a5a8245b07998df9c091c643051e3e417aebdf222d121de64b01255cde4e340e2f30a75e370bb67c02ed7ce2421debd497d56242efadb14d0d1e0121946c9a36906b9356d72a64ae203201ac1848be3d70bb9d4cdc8ef10623726720aef422f5abd3bbe577d0603dbd77d0e7fb43be8396b9fa5a9cd00ea91af79b50c98d751c4d53bc9c28ca572948f5adaf0bf655c2ce8e072cfa8fcb2b99392b15795843fde39d4ef77e62bcefcc05d54581dba4f041e16aa83c3147585790b1e465373377ab1a9c11be2843216787d017ff9b29e30389dad6360ee47de51b57c6ec4df7e489f78637bfe9d4c4817827db8830f4b98e19e92d96b9332eb4cd937e709044f8ab7b94f2649d3c42453511c4657dfa67ab6e0d38303026828903e0e80ad55d164ef9173605a4b40e4b5c703019cd53af204b9d8dde23b08d5610444555cd348fbe7ab63f716148be3422aea9bd48154070463680c5606a0fbef81d0e09150c5fc1c62e1440cad6367abdab40f0a4fef1d4d45bb4e779b31aef1045138380846394e5d739f840de93487bc531753810370c8fc7a63ad418abddbcd48071287388136fd4d7c4ba54dd2433d592bc3766b0e48dfea0ed31cab42c8dcc494b057f39432cb627462835017d2000e06977098c98d79055da00fe0ba57089b75a70cfda62025a1332b280548efb224e91d9e904ebbdaabc3baa3861a759ee7956306acf4dc3256a5243ba66266e60cfe275c94f82c6f6775f7a86561b9eb52185d9aaaa6750b5009b2d1cbb82198c973c3c71d780571e99887b87c52414ccb13b642ff3fa6eeede53b1e64edd40d1c7dbd984f1c099d63394f9f021289d519279014b64f485f09f29ba38ed8c0654f4572bac94a6f4a448107880d4c84ac309e74a99cd3410c13ea42ed3ac1faac98425affa8dc9a4cb4d86ea8db77af6271830ab8c67c956f6d5772fc4d175197678c9593ecf885a561349c41d05e9b5e922964109210b34b02b39207a7ce59e3ac046312ea939b1775314d6dd23541fb6e3f03fc241e44a492d60294290539670c7c2e5be8f36bb42f503c67fbde56851e2c98dc06b0241509a4d091dd96149c1bffb9124e95697f35d01f5497dfd2edc6451f6fe89f2a5218c84eafe76a6e6293c36f4f2b1cc5e1a67af46f0f31f39812515eaeafb095447902e7a05be0e0a80bf6294e8def3f3490142435cc9f6d8edf71434c89163bd6aa14b2728a515b6e96de9c09df3dafd1c8c5b08ec24396fcd0212a225e5ad74cd05530b3079c94005a6af309c62013787ac21bcff0a97d2387f935f9effc1dd0e3a1608157806f2880075c0f277c2bb80b97d0469857e06dba8bc7e4bb0b9554cb3a53763cdcffa17c9440750166829660efed15cf119b76a659021011d7d8bebaa78428096c9e339c6ae726f97864419266a5c429a3a00d82a20f04b5f07416ef7c1282ddd7d07add39b14a095d76e47cc152e445c53337ae9e795c549247bc2339cf6ee2e3a837f9f2619cf819532fddcda46e445e32fb451c3a38583efa692217011a2bc59674cdc5096ad1950a302c320086bc0d2298a7ae0b8c45e653665058edcb56420f402663d141fd956375a9086c371b582e447c4ee1d15891c3a428e29e97ab6054850f5331ec081d93530157aefc767e6d4d4163ef9ba9881121968650bbb44c3cb0a472466c5a66e7c0692a7784cdb0ee7421b6c1ab1e959891f661faf6ecb27b98d6c597bc8735807a816b808d5b5a389da88549a4300f305af6db5b24ba6e779a633aec60eb6d1f1f17ca85add8b0f0439910d2c471440cfd59863621e29ae06ee1d0325cf9e481d372637b855c31af5cedda36384e46fa143ac5d68eea55703af363d351058a397ac0d643d32b383935d74ba6c45124df4cf3f8502fb344d811ea0d64a2758ee412549ee1ef7241a9902fc64f818d1aaa930443b00af9adfabe1a94d184207930edaa0a5970ac990d457ee3ebcd0d889fbb4d28a162d6ba195b474d0f67692248184e4b73efa1aa676ba4b463509c16cd1c6cdc18ad5cfc70e4eae5b871b1144b0b133d95fa7bbad99c44d206ccfbbd338d5aa7c30471b190b2952cd82a267136d6e28436b9e39930e9456ff6c2cafac277faecd4bec121d7f14fa69cf9103e373043e86331e6737bfc05f2869b7671519c5cb9eb703eeb1755a9c06cd210e953a8c4c38ff59f926ca58e183e1111f9398f39267a685a6be16dbb1b94bf83f8605b3b6ea6f377016c5df8e65c7d43f2e3f802716981726d7fddca75f653dfe9de5d0cbfaf31d01e0dae1e64f0d34302d4020a1dd0a3b5b88599e49e4cb8923f39668d1146d60c733cd7f13341a092e1f642598ca1b5b3759da86d6d3cefd73e518b90725c7ac67a87b3253653a086cbfbc673136bdd34c9346e8e224811932618f9774f5f7ac5934d358ed9a145b283c2943709b6ab451aa7193c9cb9c20459abab72636949c2d6633726993f1e1e41e3fd8c40fc8938113bc339bf2eef7ceeba0c397c604d78af2f57a7cd890935f4c662cfee8e7ea660ff872a396dcbc20e6680c93286115956805326ce8b8d743d4a9c14c5471ca4cab47a208e8f57f60b4b989b1539e75989650bddd9400ce0c15724d3146aa54ffea5787be751f049a59225b1410c0982dd7ada92cb9e2cc9aa0bfc3f3e4cc1e45bb71d794d6868462f8be5b8f5e0defce9a0c9f2ae99130a5aa407ee350027a27e74542d92c7621f53f6d2adf6161228e41d288d524bb768e402567653be080802898ee18bb018fa88a9d06e42023a8d56f1b7932a618f26bfec8333824ba2a77c97ea10460d397cc3476726f392430b5139a7add8135d2b91aa998639f3055b1a4cc091902e437124dcad0e19ab0af4b55f0a05cc9ab25808d8f245616dedc6d194361ad1d2f3e77a6dcf53f3994f0cea4a0c56781dd91da134749976ee580aab2f7a0df02bd5f18911bdaadd0bd73b2f674debbcbf185903c0bec9cec85c384248fb20e184a8ffd59a0f7ae610af0edcbaa4fc800f95063fc1e0ea9057ca297fa12fd7c507d60b01e5ef9c4d82a3002633d761b5403d5faffe8948aff72a937e7199b3e8f63010816a609c27d9faa9d9a191a03dbd8082c0658e1ab7637e153663020c005a0bc01a86b5a91366b1034873601c568da30ddbea679b4a950c9f09e277262debe410473953e9807ad58e526cc163e9a34974e8770da7e5fbb5307694b97055fccc5f839166cf5448b118c5ebebcb8c7a7bb5e83f4ada04921aab59835685478c9d0ee84f90d65fb07c7b0afa41531aedaf23a9972df3c67b3a76002773bc1f30b0e74f73a5f5655898836dbc5d390d1e93b7d173a7a9dce9ab7d067695efd9201c5ea2a12c718787a2f8fdbac33bc83850a2aac83f982540c9169b99a651dfaf0d2b7f76a6f391b92bb33b06bd76c056025d837b16379b20ae28d348402860850a430619f6288a2087e1640a7d78db388f5420230bd6b5912d1665b0ccff5225110f02158b65ae2d92ceb5083d6dd36927b6cb34a499d797159aa1868dd3e245c268817b82e17b84389ff65310bcd0be9da3efe55b5016f2fc96f077223acd9f1bf2eb87a817d9fb69af4d878590401687ac36ca96405e5b832d5d6620e2a505b4ec5f0cd956ea758d0f1c66afa4a47db90dedc99b5cb0988bdb7267ba54f9c23c03e414ed27f76effe7c542840687794ee0cf88e92b2ac9b212da9b1ff90ea8a8a6b98425e7a565a7bcb493d6ae48485182c0456576bae506c2df6ddbdee2be11fe56cf8774daa85d1893fcd1406157ac0b011ef0128b6832d68a383e632a42b123014f6631f71dbca04477e2b3f1eb4a9522a0e7a66f36dae73a559fc693c0b83c529bcd23394fc676bfeaf85efe7683918c8a3f1dd906a9973dec2d763786071c724662f7492037dc896d149671949a5ae16781783bd75f4446a3d28ecde7c91a648b0ac0ce945bc78bb06432fe5e08e1a4ba62b20a31b6b48f9739d1ba63075fa4caf73dc4ead53a9b68c2eff6a2840b303ace4735fa15c784131bedcc61126cb14c2974a833950c4089726b0f05354997274e9677352c82248e4da813153736c56c6708f9bd93cdb8c1bc849536cfe2c83c90663561a58278f45d8cbcd2d6aac467b68fb39f3641fc21614e21b6d2f27a95ecac55edbce5bd6f427b3cad0abfa388c1f878c759c5819b367decbc6eab3e382a464525325f23832ed7201411b6ace78f33cb0bf831f246c18f121f112b2d3cea1d423087ae1ada66291a78f6cf0909c4e4003c89f896772d4601cd33c03571b31e96573b1415b15f571a1863714580dbd207f4a45c38434b24a9fd02f0546b25e06693f533bc552e26a0e4866dc107eee521052f89edd7968446b82bd6eb656f5a7a012fa789e8cce185d45e8c99cd38500fc9be7a16d73184399e1039ae097a2674263e440dc76c55670876fe82a155cddaff30c0c57b5f4e90cd4d77821730dc7deaca9f563b5e7c62353a75dec2a8b2018df3d2c1e7e2283cd5f13f7e4256f1d3c9177ff2b3eed8c5ca8ccce6afa29994d950d76b33e19e6d62d976cb67481b92a590d15ae2e3c6f583c29520c4c86ff982ea65cebf77763618c908542a97ddf8769b349c662829e652416b95894fa04f11cf4d3309e3b7113d3cb1c8b30b44817118656fa555902466dff0a25047685904d31fa69818ee6a306e5be922d01bfed88effd1502e1223404d5811998207d34dd42c0beb7e55a9c69878453a3dcfa9b2343ce4dfa3430613215f514dd098905ce36a71e8856ed2680f4c091ab4654985e3e5a859936db0328dbf747c7b43087d903fd0e898bd0f655124b2fdce4fcced54cced000e2b16e4cab170d8cb3d089e61e4bef77eecd393718c81100042fd00c68dd10f591db782af95bc87d446f37f3e7f4f80bb58ef4e532239cfc132900ba2ff4de16d0190f94fefa26c5747f1042ef1b7a048cdcc683913c32986ede9bc31b60e5c7745e62a136062cfdf1c66434bc34aa75f9f2a8ead002d9d1e00d1c011ca0ad1bee61e4fbd0ff321b575decd92dd069db38a0561a4dcdb221ce4e3ca57a1d925b81fca2fa476fda07950fd5136daaa40074bff41d7760e5871ecf1d5cf1233d1aacdbc120ba0fbdce1d58e947df750b567e4c8f4c99db0320ba1fe9dfac895af74ef80effde65fb0ac326f1056f3f24751fb7f0fb1f9100900ac6b630865edd406fe2e389e128bd1418dd4fdf770facfcd2e3dc81953ea407c3bddb1bc18e42c8bfe5baf476259cbb867795240158ea7793eb0e56fcd077dc032bbfa6fb2c51df584bb4570378903a8d11e9baf63c3957725a20d4c25cf62be33aced02297b85b9dd6de7303e1e59ccc1383d3e5c97cc7332be3249e02292705a0ff357d8b89fbbec93daefca02917299ce5d42e7bffb14a3fc797c17014b1bf4ebb9d7bcd419be204afec6d26adddbc26e3b9fce6e7102c0a3836be66c25b011c72a9201d3b410d7b313b080ee6e27233475daf9bb9dcf1dd5de210ea724db20ec71a91b4a7c372aa71e2325f19ce2a638c852f5c3743ebe4f66eb43b2f5098371392868738bd34f760d3812d16a5b30493461a7eb05e25c788fd422d63ad95e270308dd30ebb494df8b618c940fc27ef1e4797e207eec221c2c52029e5e5a56fe0e67878124320ee995b1c34e78d12bc3e5de16544232d2da04291755bd49889a9317fdadf6fd7410fea9c757d1fe610165f4eb6888dacf4199b17414660ac5d64bf09ceddc5a998247e59c20866ddad6b34f065dd3fecec2f79aae44d25404db626d7647310dbb86bd068c21ba49a84d2de7f2d6d875a0963eea1ff6c099529297bf77c9f481a58d820d1209bca966e2bb9e48c4426118e00b910b3df707cd38c499a3f7038bb72e0fab735b6a9fce9bbe56482192d136fddb48a478f33ccbf500ab0f2c7b5a0c1ec42736738ac0b2af60b382e26ce77d1d823523fd3eb809a2c43619693d48e2b9ebe30d8be3eb7da90cb895737b72e4717190b7cc10bc6400081b5f20fb6202bf4be24a374837ee2355ad45a6dd874105b4e3cb647e01ad1b997dab10d5f417aeac52fa5dc1c240d4f3b49ee59a21695f1926c167395112c50db3c067feae67e8ef6da843cc1904bfc17489da2b39c5e5d4aef15bcacd4ac1055d991c066a81ebae3787346729f504cc76af47c2612f6457e736f656cf817a71e82d3b12f542806495072c0841a8ea8f3613070599446b4195652db11378a645ce26677cc2908f4ba9cd91d28046090e369c23caf1ea801b6e00b35d0ac8fb6193eecabef671db00be3ad269f37b48abe4259798d01aeffa82d0d4008c2100d6cbda395b75804ce2ba02b67d40df26e0514a3dd31c38991900ca8767db003034404cb535b823bab4d75a8235e46498df43b43caf35fc4c103a7d5b5a9ffeeb43e67a183a1ed5ecb4c269238a18c53995e0bb4b58c2257754f471e2e94a708b8b1d3832bc0eeb79cf132cd524c13a66f68145f713dd3a1e80d006cad8c1cefafe97b75382bff08ef4d62a5006a7e8161d4cbc48eba529f0110a727696ca1a7080d388694a5f5e303777694d047e5b225dc86902e2291f3df1115ca69cba79f793bd138edfe861848d0026759f852f958e872472f191b5706ad6011582c5d04eca2eccd3ad5c76c49085f22b4365126fa97a25339f72d2649dbaeff29ff6c4af86ba5b82754d83f33b220ff48462d476680e1e2e5f36ae879742c1808dcb50ec2db4a5ebb815b431b598416491629783193a5b07981e007d1d100cc65a353677f30308f0af70fea1013c5809c6d65cae73c1b20ebaf4e926d18753169f6590655ec1d6539dc9c3eddd1daa405fccae987aba148b0e10c2e4a8dac2716a31ba02e786036079d20c9fbcef14657624140f86d417c06714d70c474764a4a714c7f3b58d7eb527fc68240bdc5562eb48b90d825d6886a01ad392f7b51b23176014c982daab948ce2c0d001b3d86f3a2f08eb76b46f214b28b2a19185e2c6592aa6a49b29210265baa0a415c793269094cc001d94848a20f636f856a9537412c6db85e08dae1d4948c70b7c97a9b7203242e181f5858b154af9f8d3e661f4d17ece2eb2cea3d7bd3802782e6305182686aae5e844b1283ea9317a8d61883091d78f94860ea402ecdf5a8dfb8f5bb1569ef12c17e1d60e140f7d2b1149e09595ca98cad1a848ccd886c6c0482d30c9040a588fba26fe02a4a8037aff096ba81ce6c1b57247f3fba99c265d9e24406be8fc06d75043300b654496c3e4691de9f07c9e801c826ff41b943a0ff38df665c156ec4b1362613892ca4cd58428b73b0687b0249519cb955ac398c630c24873b41c4451541e39294e431d711ab6faf0091f291a316e109d66b4553041d31b5b31ca5a0975fcee686fbd782539ab95a485dbd64f1dd648fe606e3f344fce0b8698c32abd64c20d284da39002855d57a4594819ea7c64da160ebac1bd063b7d011713a6ba6184086a2e7b9e0ba80fc0bb80c079e0e680b53d0184fad3c9a3627d5fbc4b427a317e8b1b2d8cf1ac0bbb250660838dc350c595d708a862abdc02dc86e179b64b9caad0ad6a08ec58618dbdbdfc47e703e1fdd48fd33abc6dfcb855cae8e72b7fe9be09343dda34d5f3d771a9b5704f44ba0cc6a9c15205fed3e4625aad22db201b77aabb070fce34e1323d374cd962ccc30ec95ce8f89a906a2b374106e84372fadda59786e17cdd42b48470581b4a4ff8e76152acd1c8b8a5a49ac341af12bed809f1462991d5b0b4344899deece518ed82fc550b7db64600315532f7c974fb16315574a1cba9a292b55d0fb0bcaf9cfbe1d7cee3c123b43eedf09e0eb7378c3ff68eddca732a03bb6a4c3682967c735c8ee477e7367090c65c7e5da5095aeffe019f3e59fd6c9fbf89ca296bdfac82ddd1aff2d74efb1e0345fb5a90d47edcc1947ea2f754ea05190b292820738d0574a8ff3364da2474b99a7af182389c6ac5caf469a1518eb196520df8a224a3f7e607b4a256b69e39f89d629b70d2280f84452f466afb642683788ad60e57cc956110e6a96103444862ec5f450feea4ff850e1414d97d71413688c30cec02c639034029a4526052ddd318a64d0343c121ec67d3638c705a54b2d03b96dd0599a99b7b72f4e00f0833a6fb6d80651e394c50768a007b2f5fc5dc67f95c16b93219bce32bdceeb1828ca796e7807c015ea020ec8802bbaa4c63085d85081b1489d644124c0ce4f406276054cc352bcd392aff281f302efb7b7d5d3b465215a997b64601a6c8de5c74fd5ab7e3fad28374da09e68ab81734f142d1416a222b29a556efdcf579b5d8e635649279256a05eda9c63993fc6933bd4515c8e7204e6ddf95c8b7670b3d1b41b0aeda2a3883d909d7ddada0fcff865a85167e4a12981e474c6eeb9862c6c09b8b2469216602bfd724b993b30add347f08ab2067a32fb8460378f05a12164d32e9ab7fec12b6283433d8f2dc25275cc13bed8050c232e28ecbc6af539a3b929de255aa0fc2e480a5653786860d0e3c68125332fa883b307d45a520c8d0cbad3018bbb42b03b8d133831cc87c44e7d0a70a050c5b8e7d20b6a4cc7b4a1ab404666b79d8beb4def1c00d106e124082a91d4a0b568daa3e4bd98378fb05008f61a934d8c03b9e07339c15162d2b929c27e16c4d1b8c1825e490a63a8e8b4630dc9df25d3c01bb3898a74b07d3da4a84477ad05028a5e4c1186faa7d6863d4ab4332c9823b672e92030582b95a9950e626ecee4d72d19d9aac09b59114521f5146e8f648b35668f79f631dca8e47f695cd0b677e4a40720a949dced483ad7a2009c17c30fc96a21df628ba1b9e110a210964a2531c42f493d2e6a1919a1d5139525709e97b57a338d5cba35114cdc3b5521a24ea620ce4bece638659726c916e37532c45b1ebd1d5d04f3ab739399954199406e7aaf853c2a1e6e2fde368dc7d74aad5e71dc37acb74789c52c2755738a677b29f7a1d364b68c015c8bdb3bb0e2ff2266eb329979bab22034eba0718feca4afe4ffd7a76d7fed9e16e87d77dc89dda848743508debc903efd898818046c652e8191b6dd9758099378751d2b43090973d3d9b111b206f742c37a8f7d943d50e050487723524391eaaf680a0e16eda81b581130f9326adffe102a28b80db3dda9b097d826a2230b60226bc5ac460bcda24c91800671044878f9d996922e26ed45b6d22e18664ca8cf5f2641375c07f6c2e054f9583630b2d2070922e4211a5868703512b4052af38ec32ea4eda6005ef40185e89ef041fb44597a45400f2250888829116de6f12b2cb336ded0ddf4f683b7344de268d2f9202e4c8727d8d19e9a631d70715ac909baa0d3683295e36c65e66ffa22747e5ebe845d824fc0651702c76d4083f742a10cccc0877ad3bc369ba6aa33828b6050e5960754bcf033554d4baa2b7c53d54d7c65f40e5c8dc477fc3e1d12569ecebf31fc7f46b3d41ce67605042ec8c2f458d2e046484703df42dc82407d1cb06aa9e185163e53dd94573879e1a455b051f420d00f1d2a527c99179fab5baa7abcd5840ab02db31d91539cf5812312de6badc3097bab7ae9ba74f1d5cead43165b0fc073fabf8042af74b1cae245ab68b0f260dfacb9837e5b90e1a94db5afa9dd98ca45303c03bc3052ae7379f8a3d44da1b9c0c0669d4fa5091893a8dd060ed7d9d236911e958793327c28abbf221ab512b9d46600badddb5cfcdcc172de097d2cd8ab0352b6915bac6c8b1c4869c23ae7d25e1133822cee900882684d197807e99eeea43e5d85b20991c3d0c24c3d90e5aa494777cc45d678eeb16c6e31d174ad84a070e7d8f7772e6db3210022c56db471bffb8df7be7bcf1b6f79f7a677de7d07df1da1e0f8220d61b2f4206c2698542faa9b59c7d6515f1d75abb79e7a5c98a37459597078c33befbcf59e37df79ef9d37f93d0b704961c63949507dd00432bf4489fa31f77d70ccd84996e0f03aeaadaf8e7ad4514f7df5a9b71e7542dd89b48a0df9118b475c1308f01d2ba9146dfb13d9cbd273172859f65828b7abdd1a69480a327f66ad1cb0289a13633a084f1f6c8eb44f4476ce73973e9c8134f2a52e1b40be4cf34fc0cc9be2d08b9260c29da0cd7879901c5c7f5c8f2c7502556b83da3640ee1672974336b9b6a15425d805da121bacf16fb2acf481c12221d5bad5708557b610af35d51395da03283c653236eff6ab62328091e58e9d83d91db6e99835923c4dbc421f2ed612dd041a100d7837b04577ba7ac111186ed317cf29e08c11202cca13803344659bfe8d32c9d17c4812132356cd0dfe1628768bf2139151cd689aa96a8772aacaceda6674d640e180cc078436864dc44d963b59d836076ecded00540e16d1fc028268cf15c87bb32f2b2d25b02b78b598472de960695c209d8ef7d0c8e0d7f3dc2aab939e1ebb24d902206697d6d2c27eace661fadf663848b276584b9cebaa1c8b54f04c45273ab75155a8529e916174fb19f34a66ab7ffd5b7a094a04b507f16e682f25a1bfaa9e554c83fd685d73a8652348e95dba5e88eeeb63ec36df0de3762be96c04c4486193fc043256d37876603ddf922bdbac2164bdbd16da88d9454340127ba4497b0c02bed82520c367b88275a18dfa05607e7d278122579cd618611d79e9778247a235278129dfc93fc9003be07a58c8bcf844982f950704ee2e619309f383e36128108aa2dce22beed5a41b8ba9864e32653349b89b243264e120190cb6f88a09a3033a1898a9be7b6a1ed3e8b410e01542fc8d0528e90f5cc9b410bd0cd17b1d48666fc7688bfc3e3f6b5e2eeaf660af9a448d6f009f2fe925fd449b1eb1d116200e767191050521742cfef6959cc2b9ab08659af9b3284ec9f99b08d092393d53acaf84e65acd0be500a2dc2a185a278806753d2ea017a551d5722b807b68f720af7de538f64c0f187801ea707c08a6134efbfc90c505ad04ad787ee5095de8585e76561285c728e0936a9629e02012cd984e116ff9c7ef9b9b003e23def5853fddc14db494e79fd4c278723aadb0e2b6d65d3ee7c561020a2328d96b9bd39629d56621428cf0525cef22e9571c2762a0fb23474da56d6c9ca86104fdf33a77346d1de326b079dccff12d69004e72c75860a90362a3ebbd00263d4fd49c79b591892a61c8023f01f2515cbc9bb009c838a3252ee9d8234766704d0c005fb32221e9aeea2e0bc469de0cea9f86ec6075a6eebf24593688186df8ff3721360080be12e6f5fd01f5ca6d040a8dd865a28e7753c9b83126ab605dc40d37c504956b424a3862a51d57d2bf8a5ccb0d0136e7bf8bbf5180b1c15d4427fe70c01410601ecc2cd288ad76cfc1a8879564d68461eb1419957114190d2c39f4fb226619ff1e321a7930042fa7bfc62ed4858144ff7118ff6dfcc092bd8800f2fab2c23201500ea87d19a6738a12b3eef71ff2004bfc36bd46b80acaba7b9ae02ce96d01cf24b6d93390c7076d3407fc82cb2e22351c6012bc083246bd9e5c82d9cf8e6e7c14648bc4a12720232a64a16c830e0bdb30b8e166b8c1dcfe2bb76954e3f2a82921e1f780bcdcb92cdd9cd08f12001d9062e6844e073949150b374f022d574243f03da711b340c5c9dc50ed0754eed2f15023443397436aa2aa92d88be638cd67000f47fb0c7fcf82f0b75733d32548a2442f84e325b7986b515b4ce5aa1d7c7e62b19b18c62affce79441b92c9118bbb5ffdb18ae11d4fa766fe1b14e5422ca3c54b88f0033a41607003c849d53c6233d9d663077b567c0e2e083e199daaa1ff58623df26086b9493725b6afd72b2822b254ed9aafecd8875c5232d091d960ffc89b8b9b1f1b86a73c0a0ba4bc031a8abe805930d2324a07aa637065117c93fc93208c8b96e133161b0f3417504e8b8668f50b005a0db6d747f1545a6179f8c664452d42442087a61e4be03671a93fa8bfe52b17daf47c90c6f78168e8020d5e3749c8de7b6f29a59432a514cc07bd0722083428b01984705392315dfdea19a78a854f3b6666e6232d32937bbc71c8a52f392dde4f86a123253cf7d800fc2ce205a1c31af7a2d61d89d7abbbbb7b0ed1bdf8975d01f04f3f64ab4c85c513fbfd8dff6997bfc67316e53cdac4ff216c3ca1f8e337cff7cd8739e7d83d16fbb0ea3216349a5c0815005f4c8642cde91e94243c8c175e8ce38c531fbe08938b910257d2592b77e10e7d83b791652c609c5c8c56edb7a39b6537ae237ffcbd6210efa6c6f002cc563e7e828608c9a0c4c84911548eb47cb105b9debbafbbf3b513d6dd5cb8d9e8d77b7cf383cc7093b5f7868207b93a4f731ae716a4ffea26e71f8de08682e79e6a76117503fbd5f816ffc6bb2f86be485c0886894432a34cc15fbaa448e606c4de17face9b4b63d8d05996e8bfd0571a214182a48616375c310ecc3900e3c0578dc0d67837aec31de61a7374e7dbd3c537fbfef257df78349f36a2a2a7b0cef553524a29a57f5d9f71f2c701483ec4852ed0c891281b897bd8d3bfd80873f966320ec6417254fa897a0fa163f7bae3fdc9913f7972342ba6f126c3f54629f7355faf09ae727a9543e6ca38ef46c7dcf1b1037a8f0994f2518d9021ac39b61f3c780bc2833e185abce3e86530ace4d02cfdeb9bd2998fb7c0dbd90c37f327aeaf0eee6572e49871e3858ee3eaf86c6af3f499d46638fa5ac46e00afee9a129cc13e075765664c47d8ebeb679f619b4fed2ad5304ec769bb3a150c6ca603a3425a20a27ff50604c31ec8d515a95d8d9bcfd5bd13d6fec97550b38be0adef50870f7480d181031d3c64184aa2db09ac83107e10420f42b0d79c5756efcce7c8d17dcd633b01eb5241e8637586878661226895a339fee43ad9677452489d463aaf3a6bd7aeb51b9be9befad7739f17a1cff7cabe7617a7b9f7d87675aa239ba97ee09bcf0bfdc367072bd77525d8184792bded3eed6675be61aa379b55aa58712417d753d89b5ffb0dee31b5aee3606ffe4cc238aec49da0503270d8eee25ebfd16318677e9731777e77692fa5cbcafd5c8ada9cc6f5f5231907c00a32aef95c4a775dc2d5ea12b2e73781f24079641ef9fc92755a862ef60b5ecd244efa61d60e1442699f8075ba4fc09e7f935a91ae7f86c90059cc8342599b7477b4ee4bf49b499a27f6d9afea3a38942e40f870fddb5d04dc6304ec3a308e1b15e145b88a0d67e29e8f17f513f79e1441d9e57eaec4b427eb4caa26022fda60e0996f3e5fd3651c967516da78f6d8a3d861e98340e4ae6f9f140bdb631fa47da67ded82b6c7b66e078620ec35ac7b00b72ae63d77a1f0b723571f6e40badb44b7afcf98625eea4ec83d27125bb584ad7faa1adc7e3822423cab4ba40beb026725212d106931020549f9d9f711451880d2c7815b4170e9e745640a3256c776a834cba0d625c4e7c7fa6601d77f398ddad1235cfe0470f94bcdb2d0c6fb29d75458d8970dbe43956b24ae00bbb30377687ee800e348ee03ecf9ef401611dbb40f5087eb70c70e76e55e07562bff934a0a0bff065beed7405c8a7218c61d72cf9f65cb9731d2d9b60332300e273db961a503ce967196815fbfbbf4abbb8c5acd7ef0e8f16b0bc2e30de8dd78d48a2a5fc0f852c4e52f5d7c41ea01333f33b38ecb58be18dd4f7599999999bd5b890cc20823e41622eb9762fd19ba75de7c2ca05ab98ef3f5dfb9719dbffe3e2c12df9c73ce9f73c2676e3e84134e9ea8c529498c6ec4ddb7bafc704e082184fc73329c13ce09e75c419a2496ff82eeeede92b651eaf21cdfc9d586f2c398cc92bb7c42cedd5585950fe55f9773ce392b84107acbab8c37b8c6e84fe4383219a11d418bc8c720f749fe8f9965f78100927133e7203dbc98228e3b74e5f7fb77a701f6524488b530bab0d300ff377467f745ecb2164617bb19a49452082184d0590e81032e2e63e9e2e4b22e63e982c465aeb709d8cfdef8c569c6d18d7f8a31b691353163c59021f788bcce324e4087f1e30e23fbf5852c6736259d199d744e29a397130ed5bd48fc01dc22920814ec57f7e3339e080443f52bd65d9d922538adeb8b665a7575bfd38ddc69e6522597fc8f02c1ae941f93716507859fddb8f057e8e1c51471b4fa23f70ddd584488bd7c321f632143cb3dd297761a602ff70508650610b016462dee907cff673a5dde58602d8c5add0df1a3a0cde9fec04913403421a54b6d550e493c220716d01004142d254b705afd45332d56b2c45bf58b16d0eabfb8a205b4ea5094a0fe2121d0067eff50941f381425a87e77435eed9090f80b6ec3062c50d200c3081cd0c023328116287ef8618a2f96f0418b5bfcdf4ed02341d232aa5cf8d224b1f42b841042ca416f32d02832cddcc04629b92a99a3ff37f3f8cdfda694b2631c2c19d522735f1836258661980f867d6fda57ac628d693e28b272b5cffa6650bb369fd257150fb4a99532ad62effcdae5615916f6321631b4b8bc9d60e232efb2708c4b3f7e8aa0b0c797d9cb3ebbb4b697918a8eb8d9671863bd95bea15bb91a55ad71af72d86f9ca7755f9385453d47b531945cbe75c36a166ecad200539af21a0d922be8734da6f185287be2fa1897a25f3bd514969f524ee503cb375e4ffe3761e97f9aac1dca3df935ee718c239fb7671cf92815aba8b0fc2714f35ccfef58f711ddab43a961e57e3dae7cbeb0a7943287d1afff15c3ba8f03f7ea98712ad5e4348116bd53c1e157ad4832aec1b2f7ed47d6bef9d45aa35655536450f3cd07d65cdb7f8d96dfe6258695dbdb0971cb3ecbb82b8303c9d5fe7a838d7b7d965dadf3fee26afe55307e753d5f73c105972b2db0c151085cc06f4051058e2a7290e48d1f5a1e4712115c49e2c3f5abe54e299d705c7f941af70a0d6336da51118610715881c51137d44024450f494924f124044c18d1f2b659a1bae6a2b2e01007eabd045cce8283965b2f67c1e1cafd56f449900b4772f0507396d8eb3fbee2f9cbe0314613a267fa1e37e1c701405ee97776bc8ccccb7c2639dfb0971f5fbba69937bde5669e377bfaecd4a57ef03d75a787a7877f825f7aa21bb919c97d47a224ba3287e9a3c9f43dd09a4cd63d94ca3dedb2568506bf05d78912763ff8c6f728a18eae3a6459741d95145ba9d65a2b154aa264a6df4cbfe45bdb45adf58ba369620b70198b2a906ecd32d3973e687bfb57176432752b68333718824abf75430f6de697ba0770cb8336f3bb64a1dd8152fa8d2bfad6f6264ec9926f6d5ff4add20f4509daaedfba5ab72f5ddd07d403378e31ee437134eb3101f3d9ea53ae06f120c32e631923e9fa46bf2405fd510108cb308ea6890d726f0a8136f337eeea7e8b15522bed5447f4c2eed509716f02056df9518171269003322ec8bd8b03d2ddec2ba55927a481b82019d81ff75400629cf9e3802c5383cbfd788d30252cb0b0620c2e54f019230bdbf065b6a958804155adb66fd7679b0f1554abb5469feb5553541e64dc1825b8336ad8fafe156ef52b573dfb4ab31b584f3e16c3d4d0723f06709c573dbab28c716202da2598300cc3b08c8a2bbbe84356cb806de64f3376c6682feee4cca752ec2987bd6a0a2d4528a4be26aeb1708ff925aeb1606f7e17c18942cd46dac15eb456ae56db48946a9453fdc0c2cb58c48841cde815367c71b5cf320d494b23f5f8ddb8af65709118673e1ff598402384b0238285716623f1172242958eb95fafd161c68852fae99bb07dab4c0389fb9db613789c20960c6e3e35de77c2b60606971b554aa89cb07cfbedc9e1164eaea4c1848aa6636ea4d047ca875b9451c69b185757beb785bdd1eaf3a642b76ab782362ab0abe8d34616c180b95f8feb3af4294755eea9a8a8c2f6ed0ca27c62c748638c2d3d3e3c452f4646f2dd218431fe778a5deddcfd2b94da9de2e94629a57c08070d218003c3fe62797dc42e293918e4fb57ff50ed955dc753e0663b094fd4aef2434865adb162cf58e5cf5aecab53da5d73765f945e286ca2ec09a5ba28bd6a95745e3c50b246bfa67d5621f364af6d41fab5ec33e85f03831fe94bdf92706508579aaee93fd629bd8fee315f3e6516eaef7c1f6fea4a5c640fee7409cd3d200a62dd49f9cd61ba48cb3ffdbc6abf8f2e61877bcc9f9f5f14649dba1e685d6019b1b3d4fdd611714f0ab9375b57f755d495a9c146f7e094d2275bfaf3fdb5b9a540bf88f695d3e817f1ce7e31e23a3ab4fe5ba7d6756837b3d7dc9bf5cecebad77d21cb88db4c9591ed5731b13df3e71b39e23ce945e955aba4465ae0675a6ef6181729eb4318040699e1629fcdeeab81a1fef5cd1de9dbd73308e9c397395782b992a6c05d6aadb51f0917daff42b8f02d641dfa5046e6e153d87d97bcba87165ed8698c235f2689f5effbe55cf9d0851ee6c900f3a466def4a9992e8877677ea6bb71aff4395ebe8fd2cbd2976a8d245cfa1f0937426d7231d4fb028403c9ad737e0fac30f495e160e86bb9784b9db6695dac2e3d7b2e0332bf88fff5c5ca9fd167c5b08f32eb86b747a583ed5731b11f1f695d8287e0cab73d26cc3a298d3786be0e07182b31c688c61888061b17bee96a02eb9e2f0386450deb2b56f844f7e226f70d21401496c6afb48b3d4cee732e324691358069a5b4e7ec620fddc3b958f7b4bebc7161422db1deb77321d04e43870dbbbbdb5b39cb88a3dcebec9aec5936af2bc3fce288aed7f8cdf540aab9c73441c81764bc636313ae30872208219cb3e80b84f3355b147b8846b1092cc2e2f7354dafb1563f9065ea76a08d3499223d027ffac2ce3d0b212c11ddedba5aabb5c37cab1bfcf810abd7457b0b36022b10b5c47e2820c8d2e24e74b71eb479a24ba0e22c8dddad78b0af3de950fee9db9d96bdbb64d79ccedf3a024461e7d727ba5c73df9188115dacf327ba077de7bea652234d10734e31321939d23861bf9afe9a1bd799edad506cd3f33f8e0051d8f895004dcc18a31422ebae5b03f8b6ec6e772b18841012619c6b98135de784b47b75cd7d4de5ca9755739a202c7c8f51ca0c17252c7cecf2153d2079bdb106b593d39d9c0e5dfe4ece8df674bd651ee7b47a719edbd8c49acbc325d4708f186b6afd5aeb8d7bd1abb9f1ed75ffc93114cad9711d7777773f924ebdc821457c0b00126e7c8e4ba0e11e115ed86d4f8c033f93f18b66c412163ebc7c850d5b4cc8ccae0077284417148aaf7826300f04ea47712aded9e9ebdd7c3c06a7c55afdbb53a1f88a03804c60e62a42201939e9286ffc9a9beeeeee22cf59ad6e4c75b996b09de336f1b79fdc4e7f32c852ce0aeb719bf826d47825409e1800c093f9e87c70fd313ee462e8963f254e88dbc4cf38797deb8ee608bb75f1b2153b58b93fae133473fb4dd0e967cb3cf0f664613f1f37fa90f2b3979fc10ff63797f0c33de2771709d04406b32c0372c0ed1ec72eeef3f82b86527a2abb23f1f6cb554e97baeecebab09671e2af3e013d6efc7800e681529edc8bf16be2bbbbaffe8addf12e47c68931324e987a4b9db9c27e3b3762613fed84e9a90ac03c31581b2ec13fbec73c92553f3e8c36717e3beec5f761c28fbc4658f8dfce8daa7756edeeeede02e6940311763e851006dffc187e4296fd51c36ab5d3f3b3454cc63796b16c8174b5df3e631ccb596e38f223dc608d8c9a3c29c27e8fdbaea272bcaf2e10dcd2b53763647bed51d85efb1eefed35d31956fbad4b61af7d2a48e9fb9baf96854998cb599850b95f1b656172e5ce7cad6de40cbfc78d40168799d31985fa50190cd1beccaf605f66851c3f43647a2e07906f115a7da80d0593e9c21037fa196ff135eeebd4f6da9b9e72a9ed7b95c2ba940a326f9f05998e857f0ddb38140c717e6b59a7d55be3eac246e2462894ff7baba3926b2ef03cef47b8c10e21624406893af241b9071f428f7198c774df75a27c58c49faa5028ef0e81ac1e9801ddd95b8a85fa59c7c2f53d507a9035a48bd7c51b22c5ce102508fbeb67c8befe0cd85f4bd819a2f8cf503ffb1964eee7dd4f01995fca9e9ee8da17d230a2073438830b299cb45060acbd9f9792bfc2f5584a762ad4cf3ea87ef6de055d8f5129acffb7cab01e58ab8c3f80ae771fd0f50fe85e55a220ab07c543881891417a1645c608b05aff5f661d285fb356631c7f141ab6efa75d0f125f720a7ebda8d4ea5e4cde5bed70acb962ff064218e2155a5defaa76ea57b51a998f1fe106fbedf8431bf850aece6e3f4c374381b1d8fd9e794c173e0a85bde4322e5bddf867c414d8cb1d939aaf32ba9fcae8d2e75cf8c17e3b95b9b4e08210fc23f021e487ce5c988be9b6c03852ecf518970bc15878bfa7efef2cb4ba27c8da81f55ac86a24281becdf16e0bb005f08fe917a94c118e37bec609057d6f192deddc120afcea4ecadbb18e5ad5a4b59627f8062eceeda1fbb945f49acff0f909021232cb15f1ba5e0f373cb53b04ba9d02f7f85f8dccdc0334409e28f5d0ace1051dc6a23ff6ec2ac1e492091c4153450a28b262dff7ea224760c6d36a0950f1f8731025940d8c6df9d71bab01f23f5f3bb471cb267c1f7fff7563bed7d83acbfafc8cd383c204d1c9edc2c3f1c65a12107f7e45ebb018426ac080423bf9204e65a719d0f4ab9ed46b00a642989424370db5b8c2589cbedf72aaee3174bd293cb5dc622668c1830170097b188f9e19a6185e08f932e1914d6f88ec856e29e9760cbed22f7a854f112b891ebf80c15f6133a5d947bddc9bd7e13dcee2e435cee7ecd240c1350b415b79b09e3a4acb09f5361c23c8c04b29a4923814cfa9d4abf5bf12bfdae8559344b7260031578d9c1181a5a1d061ebd208e4a1347a589a372a00ba53333a71c95068ed38c8e991d32a71d368419940935c3a3e4fd3f8f191136116652dae9e452a41c216df18252199b2a72a326fec4a1648ec42289553543d3e54b1831d72fed76e0d8a99b3633c235c24c971931944a292537651779d5f859bd200ce38beeee29a59729219cf2247492335a11a7ec2267b4a2bb3b4ed92d67b4e2a443c78e6dc796427777da3193e9c0c18d66ae2827cd916577c615dda078dececece0e462f4c46e81886d16b723337dbac19f502ccc2de9fbfbb486134a4b41429522e29524ad634634d7646a6243393e339140f2b532f661827e5354e42a74a6794333b9075bd67f31c8a07d6cc1ac21860c81c078bb9e88c127e1b0d81aceed2455c77ef51b6bb54fbefbf49d162fb4f3aa48e1d3b769c76cc64a731d855a1b364c952ced9344a49c213164e19638cb43481406fa052c36626b5ad64aad654da345b65b44ca65e954e795d57869d4036d3ae76ec463f8b56da75fdd873a56f04b2b04eb20dfc6bab90e642c6814fd386fdda28a349b2402e0c39011a8136d004f720ca0864451f393937465175bf368ad0c5d88fbb0ce9026b60f4d9048d1b16420869dcb0fd0c218410420891bc1e800b2000f7831042186b24006647b9fa1709d808198da6da5225114c3c2c4a26841c3b58f1590784a7938e991d39429041591e26114aa94da5d1642360245c5f39da4d00c89a180028001f407b9a37000104c0a6a4e307d37c22f9412fe5cf6e6888a8dd75d09ff58a79ec6d9edb0f57ac143f7dc84a71975221fefc15e277334409f297f267801fbb1473cbae563b3d3fcc224242c410672c41050a2bbcb494c0ce3b8636f2c8cb605d4f9364dff318b2b04a1b0559481637f9f036c7455d24af6b41d8af8b4a53581f0dc9fabd59d742c3ca38de41b9de7dfbd1d7bfc8759848226e03e1c5aeef995d1ab6c54fb298111367c2c2ad99b807bbe8b281dd544a523424db4c2efc2e62d282d32bc475843cc02621218c50cbcbae730941f9d70aa5fb75ddbfe17ba441dc81f02bedb80855832ed2908eb2b8104688c9a0a086959fb98c74f4c4852d6851f7e61215713f28d70bf117d7a1147deb625d175fdf6cc8b826326b3eff009835bb9fae87d632cff03366a29b8a3d49138eecc749321445ff711dc89a4aa2c4a79fe97029d7615a1373c3a51c6f612c62427033284bbd039636922ee5da0805f661563781acd9f513bec1ed8276451c5da31c71d88fbf10812c28f359b363b7e9ba4596d159308c9fc920282692402921cc018d1c50281a8951361bd2ccd4ed90ae01c7172a6ac02e83f0e892e4fac7287d427718603932c38b16278f5284862f6b8df1ba7c46e8cedcc6a97bef4e414c82d0bf74f1c4910d28a5d0045cbc6c3e10cee81bfccc7bf39f1b100f725d1b90a96112c33cc6408c13162d6194904af84564b7d281c6c0c20fc247f38fd4db1b94d2a8b5e8348c8da72825e4346a8038a24b155ce830c549cb8baa8d83082c4c801193e48c2c5aee55505715b3031eca18030b09c478c08c19803185863290804091d42005bd82560c9b4d68ad754aa95e5ba608238d098da8700308afa2862c64b80c996b0412403001060f9a1c89e1ff58b4936ac7652c4070c1cd2e63014218976b7a6e51a9ca3da228671c2d71410a4ea0830f3a50410cbca8a0091fa2e820cae009aa0a00a05c1f3ed99565d8dcb239e7755d99c3e81eb3ebb8779f77dfe996de3ffb8b2be25d2a88f6750a88f6d8672768dd27e4e35edde71f43dfebeb63ef1bf758d488b48f886f6ca38bfdbcb0cfb82eea1e4e371fac1382cd1bba87fffc89759f0cce35136ccaf817e71f371f694f3ff884fe9e1f202152b62519d7441bbb543f518bbe77a9ee8094c49f43b8eb7f284a50fcf9435186a204d5973f142588fed50d7941f369378482430c6537543b8636dce5abcf2d8a149fc86597825d8a05f9f483e2d76e284a907cfab41b226a3510b4f121448cf8cbe02f39768f5dd48cd2f9dce3dd4cdae8842e9aa291e822a01f88d4514b01953ef5006e953eb582ccc3d2cb70428094fc401b58c2d2439437a608e1d04dd0dfbb2b8900b4e0410c236858630a164528b8c245cc194c84d0a11504fb7ee75e48bb74c6d057fe3bf7adbeefeb16aebfbf3fbf30f4dd5cad5118ba45e8577823f6ecce58ed17fa568d13d26e7dcc0096b3f08003954bbfc60bb776e7e6e275ae484d4931a416f57ce7b2de80645d0a88b37bddd5efc815d24e54fb1490ef0a83842069257caf87f0c7bd54ff7cbe1897eaaf636c91513781df4fe03b856fd81f2021ccaaf1128418619640f1c50d2df843984524854b113eb8b2839330cc68c197526c988c2b0f2c67c961cb8542841097a7b89c25872e37334db1dfcef5dc2b42cae715f1058ae94d5d75589de3ef817c02bf7ce734b0738b7067a7d8f9d9dc7e60ffc27682dfec6b1a0af360dfdf44304fd6d5605dab80717a603ffe32c43ca5078236fd910033ebdd5277621c1d0c74f1d7ade3eeab5847c2fdbc8fdf80bfc412312243ac25a76eccf8f6c8afcfe667d90f7609ab7de9bfa776dcbdc647329dadf535eebe7ed85827b42a127fce940ada979e05ed4b5d7d16b667d87d2cb4ba13e5de8fa39e1f20211a87f9cc62b4170706e0a339759fd0cead96997b40202143f889b010bffc3d2ef7e067f13380ff473c3abadd55bad6e186fd011232840864b5b5a82f560a0721e4617464031809af6f17d252202beb3a6eeda4b0feab43419bce828688761cd925471cfbfaababcd50863f5529d26deb1e11678520c5c8112c5836adfe469274fb5b89b360d0ea2c35b8e036ea366751f245f757ca525ce7a7a5005121d7f70f997ff5d7246b2f674972eb0a99edb728fb28d816d53b6d51ef7192fba87a6987cc77202b05041f7643ddf22ed5442dce960085e27fe631e5072b30838b9515d0d0e221c2d802c6961baa788388567bf91901073bb38576621919b7092c67d121cce5b99c45072a3733392a526aa7d45b47a8c252151036be17a10fe9737c77eedc7c74f8dcda7df3e34337c1cabf27c949d74d74b8618f0cb5926652ad45a13e3e7cd8d515e40cf165c72df7fe1e777777777777777f00578b7a48d42e5390713b8c7252ae259a2437da4913ee76a749f2543449231c599a249a2499d140dbec89268926892609751a4333f10eb37519999691f0fdc2ee5be15a8d268926e9c27ae1a7a307db7d55c2289fb8c77fd9c0c6aecb02840e5c3045103a08e3460b9044c00b172a8a8e5822022b9a16f839eedc4e7ceebef62e67c73d1f5fceed1cf75ad2c0e6dc7ed8f9f0f7d1eee3734e97530ab8bd59d4472538cb0f61d45052736b90b02bd791df8ccec11bb9ea3eb5130e1131a0c187241f82c0c61a3d4cb7bf07f3f0f79b42208159ef86982d6c60c1184658d020881c60e1c1065594d8f042fff0eb0f24e640c3fe457dbfd7cf7d736f9c280a2a3854411aa2ca156498b003134fe080c416227068f9cb804e284f8d14738e9490207777777b181e638ea350dededede39eedeedee417033733db2c1194cdcc0410d602c118131b860d145156318c1822d4e49f8b7b70868acc1c30e923829428a562ba992e58ba2295c84c160c020690435d0818826525640051b5f5049030a0f6838418e225b4aa25feb57f5c0d5a262c822f477f7da6fcf80f05fa71f3ed4a0562d0a082874ba9e79a893472fec5ffb3118e38b244f80510497567b092caaff8db48c2fbbd8ddfc2eec17ed91fda206a1bc54a75615bbd7644cb12344e957ad7cf89385cfa923dbdd4f207cfebe0e258514761f6397e35e1755f1324aa3eb5dd54e3ca46441c4132140e24a960a3461c10dbac832258724dcb036b00c1dbab60384d02b91122e80e861082d9e7041124e630a2d57f460a485105ebc39e7a684d43e3c27c23fa96a9895020f5990000c2c5394b481ba8253458a2446c004076a24b5ba0da4354ed003143280b08206e9d06f330fff4cc5ee04e76a51ed39c058fe7ed56a8ae7ca61286145142a40011b4cec8085ff757777800cdac022a9871f14c120cb8db3738e346eeecdedf1d8aff59ffa552b2db1e565e6619dfec85d2a485ffebef2568bea31e4f49b1cf7d9addccbb98174427952d56469357795c0feb5a8f78ed4e03a355a1e41771391714d85c3fead965916891e2079028a304eb668f5a324d02da0a1872f5450b08328bc9054244111a68c33b0e0f0efad768c32ae49f9730b7eaa65a10d1109c63c19a2488a14a0c00904960c1107196b080183205aad4396eaafe1346bf951fccfefc5908d70838df2a7ab542a4781b1515341082195c27e40b788bfd0eafaebf2ee5b5d97422b661cef6abec66eb5dae9f9b1c2b0069501b79f01ccd3b73f6bc6e947a161fd7ef536f3989c8401f46031ab005e3861058e2bd068e2847f91590b4faa2b9fe75fcfcdf47a9022b44bd1af5daa013f3ffa31f49d1fe55329230b2121ec6ce49889d5de0a09610a7fa1cc97d66dac6452bb1972a3a3f865b7722fc7ffbbb937ee75ceea86bb59dd78446a27557f0d54421b4107fbfdea7ae6315da747e42d323f3ea53107967dfea7a130732333f3166416995d6a3eed520d60f9f3f2563b02446103a24da8746e766439ea10d14800000a4314000020100a870422d160281ed4b5bd7c14000d879a44704817caa36114a328ca1863882180104200000600cc18cd8c036a965f38f2483ceeb1128169714c65777c0921396260e020b2277d374bd366f10605354220b106aef91c940ad7717a9bc2035dc3c4d2e85b27a292e6da03a60770d4e6b67c08bff9a3658a7ede8f591035942667ad790bb2e088686f1e1c965942ffb91bc9b7abb51233d3f90869a9b9a58fcccf11d2b42f551d55933d32964f22a4790ca2244ea0cd6f5ebd296eb5fd534ae2fdca7dacb53246489b206241abed0334a2903c4a4823a61bbfe302de975501e9891eef81b3595b83fff33c95681ff07f1c62ffe3393c890ddbcf519b6241a043b3f5db0f8e929016b69b5ffa29035a754dd6703f8c50a2f7962867b4a340c4ed6cf44614f13b663e75bd481f423784585470dd5b288e8ff56833bec9be96cc0f05a139c0b1b40f1fe3d97273c4262a8f0df772a68bd4192b1d42f9df355ac193cf1fb71ad2f0a21ee3f31fee478c458e907adb31e8b4f6e7ee9fbc05b070c8fe9d70dfc2c8dfc3de9952be06bc38b2c1bff67f0ca0b51b5d185069c9b915c7f96d8c238d16898a285ebc35957cf687e5c49482e57ffbc8db4edfaaef6a35285cdfbdcad9d0a6742ad261eb78acb8072edc3d4d99811d07a34f5901886eb1d8093246ffbdbe0c2e894d4967efd06fae14deb6a784df29b163bfddfa468ef6d281a5636971823ca400140b58cd50a7393e859fe763d2f496225d657b8c6945af46d1b3eae7068d1e2aa34037f408baa18aedb80915cc4f8df51fe6379015741bc4260e9e782da116cc50a8fb43e0fd3e57830721ab65c20a17efc9fdc580dcc9c58d65b8847036b3ef812c385243c7f20dcab5fffb68661bd4d21e7cd11a4b98ac22a24c59d05fad620a0a7b90bce363deb15f38231cbe04ec26029a91ba25d4b8b9031a88559f5ca94234bad722adb280e02c57a1a05ccb2d10f38eaba1c991f17ec1decc15de3567483ea9ab6879e83335c83b3477ebf2f799f36bcedadd1a4fe5568742193f98ad2a1b6c168ae4e129e91e2f5afd9632d9c9830ceba2855fe32a32e74932d833b652fc98bf2bdd6a200a4030bc53421b9e619eac0d5ad9992a5ce7662b7923ba645beba40ae7f9cf621a6a699e2b9fea030a94a081208a52bd1571d73d0cb7ab2b2f7cba45d93840497b41868d3f7f14cfcac71884391d63d0575ef6fd929c96dc2768f7e96ac2415d2d9b64953b07c327ac40e3e1511f74eca1e545eb9402a58ba97ce6c6816c8aa4e34399bc125bd8bc7c215ae898f50984745cdeca8d90d26db73ef0f3a062563d4e3c6fa0e4d5cb9a10372bc2b534c615ade8e9dc2efd2e338ea1efc7b4e43e2ee511956e1cbd013a6a25c630587115657f9402eff37fea9308524e50f62b8be08f57c82cbabb9021c53aa6a65c84fdb439ff56d1b830bc6af3fd29591656dfd2ae3a2b9668ef6980c18fb36770ec9dc9235b2368cd871a0c9cd43c5caeb87922e3848af7aa1f9ae0426e49a23bd3b3964b90686191ac66cac8f3d672af501560c523c9c9ed4d984b18b23d04d44a828977b66a2ded0a201691397869a2e980075a096f912efee55ef82141fd8cedd457e0e35e4ab320ed8abc906ce0db95b871c898d4ee43164bfc15bdc4b5bcd634e0043d2e9fc5cc0ad15076fd34db25698dff0355e3986daf7f064cb5d7f7d322e8c32216fa4104b28809feec059511ae97fc318aacf6228f4a054b30eed20b4a0b526c3d93869fce8e0d3a57e0789fcc9aaed7963f833afeccd4686f34b1ac171923353784d2409b4a4cc9e6880d149846ec0c43acbb495167ce1893a67f805230d0686373ae62a70e89ae61c15d6ceb8446b65429d14805b7a0698e3119a3e53fd2cb822fbdd4928807307f2b0e194014b82a4cedbda51fdbe6f766b31284441c3be30e4120148d8763e421f00832fc1e2f2ba060c023f3a1783b74260d5963136404b96d166243b67d50c56b5a556c5bc242f64baa74d180b7b7cf99303775e7eac4d416ac80c080eac93a7fc35b1c356fa053670031da4894f3aa50aea8c8de6c2c0cdaccee791d80730eac11d1c0fc9924ce060f8bc8b161ebd72d2905040020cc8146bbe15e8c2be5a8da8be89b189b67f1f37581adf2f5e478be93f4d02d3a5abf838b006db2e3714138b7ec13cba4759eea8a2de1386e3d13b62f1ea8dee4d9c215f22ae98e177d5d7da3426a27c52ed7141cc6cf5eb8058a23ac3a9bd094c7750c480f516ed018de2031229056ae945a1520e220ae9a1ae6f84704bff7496dc7cf5b46ed4aaa98aaa81c6ee239823f74269682b821502dcbf44bf1f50009b5649904f58faa3c3780508f1f5fb2b23341374d117568b498d07913e21fcc0e5a9f7fae99bb63187adb05c88ca37c6486cae6a2c61a49281983c36c45707d9f05c7183afdb0d963da340cdd374d77f944b655f67ca6b77bd16cf003d5b990b6b0bf83fed8170a60eedaff64854b74e86b49a29caf769481b6e635d32ce3f7cef627db18d6a3a00a53af05ce757b5f820e14603adb62be0d06775eb628e01952460b752d54da3ade659681cad682d8127f7268593167416c8a51a02431440f36fe4321674c3999bbbb5b33040cdd9189914095b0b7cd42a24b212465fd738717bb4a301946c4c042429018edce439b199b2690e60192dc407aeb2a97d4284c98a6df580e6520a8a26cf0c21bba78b9227e9762fb9b63c27e214020a8bc8ac3a2494595b0c45852f012f74109edd656de8d7d9a1f58f131befd46d060a91cc09929580c4b28fce6d7e6a0362a582fb11643a2a527217eab76c3801e2c2c894cf90cad6f3b6f0ae13b45aa9adbc41471e74781a909e6c55abdc66b97345c87a315952af957ea27444256ab162883ec0bf608d1581ccf5b168ca6d62090352b972b2ec146a5bfa0d8d81bfe7a252c1963354777afb5aa274e61a08bf19f02e4b3da787e6b2e6e65dd67b02688ce70bac998052c69eac571f487ad99c51fa10550ed88286a0a53789afb3854b54a48b9547a1326b6fbd10f59412af8d1b84ebdfa2b191014a594ec1343f7fcc4cc485b4f0f6d25b1c91694e15d43fe5edc8ee1bac07725bd20ba7b96512f8704066411beee8a7db380d37ab338d70c4271b82bdb950a14a39687b091d523a1759a496ee7006c832a7c14e78a3810046b3f8a31304071f1da4d80348bba80e6c69483c9e63128e12e215f77212db509c80748978bc40be2ede1e903cbbdecd28dd6607556a17336d759c06361c4c86e1270bceeee8ea629e172ca2d547d242ce2de7407e816d2521e3bf84e89540c4bc7400408de4637204d1f2584ac6def9cf67a567050a0dbd6e95d19b8c33c95116301163c560e7941067d904a6e01e5e76558738e671226040c6e6505d776aedce2472dbdb793bfbbc66fb0e7cb1fa02be43d98aea0bde68c9f948b415b4e1e7a1edb01a68affbfa9b3e90babb57f882621263f3ef4a324bae80918c8525df048170ff05f38a1a4323882acba0003985351f3dfe1c9aa884f8b6191b909225c5a925abc7435d311661398dd216f9bcbe5412b4df9474455f835d70c4c496bdac3eb59c7448c24bdb58ae63f3e82f008e65240b930976c54fae54a39c775d12ca60c62c82d685ec18a231f0bcc21210b6d5544594a19a3aa6920b8f637eb7cb0a1bb06db1a1cd7e23eaec71ddc1b6e41ab591e53a5e55aeb1c0553048f4c8b1f36efd23b72ba8743a903a39703ce1aedb38f37e3841880948944b7461a45230f124afdb444c8edfd1df0ad5cc9e58591db6b5eb3104a4ac1ec696dd985e4dc1bcca4bd4d605263117de866c3726f0d18dfde66061ff22a4171ef8052a41166dcfbb9f952dbd53ccbc53fc74f23a83e809ca5bc124f27898d17842ef2dc93d0dc22aab905be84536e9f945363bcb972dff8962f20f227744bb07e85266992dbfecb3099ebedde0b632f93b15f32175d39059db1a68b05083d4bce038390b0560451d88f36059801afaab0132d9aa4bad077a79d5996d541a9c61fd0ca6ce312166a078dff4f372b34c0e1ac939e91530b20e2c8e7e3529778e61888b128bd81754503b27c372832b33e24507954cf0028597e1b96c7060ac8fc39333aac2b09399e070ba0cf333f26bf041f4b28a76d13474104e103c0c11f411b3e34d2ef1cc3c2c82d6f11d77e203894aa820c16f262b4bc762c06f770d37dd586695b181e428755d9305323e8900601d17c4c54ff7dfeed648601fab036d18282a391223c309e14c323dbf655b69eaf17f943e48bc3e83b5db74a80a32f18004fbe60f6df78b2ee7c62e8ca2bccfe0dbbb58da22dd54528a4ece6454e053755207033760447622df437a755e62405938bcbeced7cf1287d25fea0c0b1ac071bcbb44c71aa00cebe1aecf5bbabf9c65839ef7f5bce470cc26f9af07301e80b5c266921e77478c2d2d4c77b93ae3d728fec7e7e49cd27bdfba4c7814c8268cb7295bcd740880963b412a6f8b6abca83ee90039f469eb0526338e27a39d49acaae444b5af1071279284e44a8087aea4245f81018f84a70fdbbac509690f8fa9d97af8295e197d87676729eed46e7c6a97c938a299f9a2e0c0989754bb3f6e82bc01b7c256864b9dc40c72097bb86d965f5b0e65f923e19b15d9e53ba4ca7a41a17c1936f1785c11495d817e5591b0b70cdd7433e4253d2934b195b1612010f709334109e7ad49285a054c340f75072c46c062801720c3d918f86a525e98accc67addb41921bd6d8abdba2a9f1166d4ec884501c425794edc050c53241719f00bf0a505c3a7fe662a3b246088639446ede8d4ef808df226d5bde617670ac82654ed3878c6fea05418702102a2e8d7ae1cfae0c2119c9dc4bd4048994d1eb66b39b8b1291cad6804332f4371eee6755e6942eb9ce8cd595f06b7f0f37624b9cff2f2bf13ed76c2134502cc305980005c032eae7d0c2863c4c321145c3ff57acff49cd2fc76b3bf489e86026d2ebe321a5c6af5d7668e877150def3b3787948bd195cffdabf094fcf04edafe2dd0346ebf27c286f73e9b8fe1623fc8d0be558644eab93b89999181bdde9520b0ed56c3b88f8e79a9e60dce725be211ca5e698df78c3e5804e93425e1ce2a650d14d3a678b21913a3a0a5c84ca150997180e23f2cc2a02ba2b17ce2e14c6f6957fe2bacf15ef92c89382bdd3086d2ae390e5cfe9dc60304307365950462017b2340419314d975bf906c695d7ae734ecfa4b61d9b2ffb322dd85ca49f574bf6584516aaab6c79144d08e6275e2dfa768475eaa129ab5e24b5ef87fd350df0f468ae0a466a84b85338b9cdd194c3894b1d624eec14cc9a4c0fcd64ed1dbbea90e3301bd088b46666d6d55d820dbea6c1f52f2c4307812f110b9541818d8390ade70dc97da524a54c60fc7a4df685222f19bece8e293ac50758d3115463baa194a0598b0935bf3a299e29d2e0102f7d52c8a660625915a71acd64611acbc09342d3d0d97c8552f63d8458aa7dd4aa63092a3588cd2021163d8aa043f547d2784ed5f01ac4e35e1f904ad85ea6dddf63e2fdbd8be64a71e27d32321142ffffc5362be84c68eff58e9a3159810734212dc830cfdda1ace217a9aeb40e313e092af763717aa8d4faca299e44f8789efe96c413deab6e9c754cda144593c06e4c83ab4f153c4392ffe5d3f1d8ad60dcb4c2c830c7c30ab05d8f4adfc07ac761aae0ca9628789db0eac5904f556c32e82aa0cfeae16689ddbb422c325760b55022fe83b4233a4fa0ffcce99974512b8411b2bf15ac4c7ba31975eb73c09ca043dbc0bdb61164f7544d261d0159b8db6d3bd50f8510b4511ea16df60dba7394bb4f397b3006dbab31ae390dda9dde23b2b3ff2dc93569a3bb59d68a92197c7efa395b21db3f1e3bfd33ee018895c8f8eb9d859ba9ed2414fe5cbc724d69e9976a5329201e7e216c9d02d7f8ba460e20a3bed728d425a8e7d56fa854c71ba9541f60335f99bcff6eccfb30f95c02455b4854845ca748ee0805be5615e02b2140d3abfa6e5cfec2b124eace52cd140663a57f17c6a717f89c6349abac08c56d2aa80bb74e538d2ee84b8e9b9c038b1153cec11de02eb7de5ba9b7a4a4b7eaca1adb3080830a8810cf71dd3bbb09bc965eaa24ed7132f0b079766094a2fe928eda90230256500c2614876bb2361c8b3f62b227e8e60b9966ed4ef7ca9baf4ce2e34b3683fdc181d3e82c7cf30fbe0f08b8123ffbd296f310cdf26f48e807123328e940778d1c3856a77b399b7b21caeabf619eaa3e089b36b053ee785e3727f12e8a4890f7c29f6bf6c4a07a372a8f62eb40ffc4b7cf5e44d570990cab1490bf9ecf17fe36066dc918d3d686869860fc0203dd82bca320492a458daf23901fe8f815152f4fff549c5f3365b2082e32996e282a06d1ca628e4240f5438d4d4dc60423ebbad1e685ab4b13ba9f81276395fbf7eee53b2ac3ba412cdcbbca63b3cba8e226a0ad91a0f7947a3cc0f1870524bc1af4f732a47d9159f36912e52a14ae561631d5c94c02c8039a58933ba88f9afa4b63132680ed51ff831725096d128559ad0857b7663f73042519a02212f281eb17b6b1ae31641fe753b2a5a75fefe2bbd93c177c4982f659ba0f3c9638b6e855f4e21d53f5565408a195a52f094c3eb64ac6025dcba0299099700a096c66579e02c8325867f4c1b80f3e398decd7f15ad09e750b09673eca183d4974fe9b2e10e1bba2d55236112b02dc26c5b254780cd30cb918d09ec2d442f923729de4482dee90b2818f59ea55eddcf517bef49d67d0ab180534a9197db671d7e49a0a672a0b172568856100db1dbecd55e8ac10c350b4cfb9f1c1041e5a42a331a285de674861e0f209fc87b3625aa209ed390135019016b9e4f208a15248a332569a8d1a05a43c96ec00ad8dbee07c25310d61aa6dc2bc0b631f4ca8b544735230f10fbd56899e0e33cd55404a7dfd469ef02a03f29db119d39c936e223f53842f47b37176442058894ff6301fa2a8b55042ec9aafa86d403491c832535c867ee3831704488a34667acba3d0cf85301513e0d46c94f8e31d2fcf1cb2a492f11978b7ded11bd1be295feed6aef046ee9379ca9bb32da4417421ba80066b4dc0db90197cff4246ee8d7c5490df46cf39a663148806aacc910c2570f43942f375199fcc47afb9770b41c974394f2535e7cf704841c4b40a58a15d432a13c3b991eebf46437f6e2bac2d46bbe2ccb3447098c74e1ba718061f046d3317e726fa0037f5c815b419314f12b58cee0b3fc98aff0269dcd1e47da7134e223e3776af514f2a06af5374d484d39bc848f2b2d501f8221139ad56d25a8e2ec09940f1f94bf52f8310a7c6328812ddc63f5a4a33f4b8099a567974b577ac07a53ce47ce5ec2a9a1437ecd127d3adb086331e9be50bb9fa1d31a7671b2698b481210b3b09a15302a44f1db895b9d82f7fa557878924471ca5a682e826407f7cdebb5b4dbb5bb0c64e06a87101fe8ec8a2255e44afcd25b4c0a5896e2211c2b960948ec10371b9a5d5400febe19628d7476d8f2707f0b345305538a826a300162ed14b0266862d09703f6f4b062314ac426517ec0249a3937703df43399771166038c51ab9672092a81c08d117654acd533df1213f3bb8765268ef2018b51e2c41af7593b5307aa7731d318204058d5c6560bbb633da6b3f4dca363c852118ac1a3b230da93328e2494305e39e3a20901fb139a97eca2762e9936b0ead253a04268293c23abb0c96e9eb8589f08c3f9c5d0f2ddb98796bb7f2559c13616c0c637b04e7e128b7d4e897b74958cd2db86881344fe18ae2356adc8b1b35864a2240bfb2970513825a24d1dd4d242c9facbbb875f8222517af1292be41b822729e59a3e4da75c2b27f1cda036ff92e50cf733becbfd7d4852de2a024f3b06f7a94da6d9bd40bc0589a21217cbc89b5600a0645b46e7f4dc10376a0b858245287e8d2dcf97f962f31d63874b67948456cbf6332ab26f1f45f81527b856cb3f5e43f5110302481bd8a24b044b69da51f4fb684debb7640d4d949c3042ba78273e432926fb7c19318fd3c2ad5ae8ef576bef6b18b8b3b49ae655e4df7bee8b11133c5a4ce2535ba52a8f28c07d945b1d7da35877cec8173ac6a33cde39ba8b6c3cf526e3dd7ef32353fbf7bc1820e116183ec1e21ad7cc5abfa7c03c2435733e699bc8082d6beb487f1256c516847ab7ba327dc0e30616b16a50f92b0be18d7694bab111ea49bcef502a0a1b8e3e97e9df19149aedd1d8b647256a13fed0d21e72e997949631bed2a0138f2d63a8af9134fab88f99fa3ec867f83ac674bc1d6943dbbdcf73ed5c3fa7ea2b6355cd8f434a0a55351eff13f40827c29dcc3485c7c3813b19bf13459eef83dd49e61009cf3549a65988dc46a78c1a1071bd41902adf5d9579053c6ded7ce171bbb1eaeb0722e51f553f259a6cabdf2e79c4963ba1fbd0d2c229ed2a4ebb8eb6071dac008face329e935670c6f198f78c31915a0391471826efb8240e72a90d935855b4ac6ccd55c324b5939da804faa7002eac82a0fb6c0d860a3f5166ad151fdaddd5da549d0fa9aac1557015694f98ae293961bb87e052e824c5bba401509e62bdfb990bf7264417b46ca5cab9a1afa9e60a660459d4a9722bf95943130e4c9778062c44094396af48591ace5ba2ce32d540dd63c11eb67f495494f2965ba127de009008ce8029f68a0f84a6be0a7076d4fb5745465fb6dcddfd6a964aac9106fb19154e67d94591d43651accb5fc5de44aa96e9f4d6efe80cf86381b42d3bee22333818c3141499371b6db8180ea0830bea5e21971e23a65295f136b0ba3fa851626d49b46715d33dd0c5c3b17ef4b1c8fcb34ba998a4276dae4cc5913d783dc9283f5ada8b1abd75a9cb8ee9fd05f4c78b4b8e96d9b12e521620feef9b7257b58c60e4aa4305bfaad032497290db6249dc0e8ec4334420a325e007f9a9242b6c92efa4ffd555b7fbfd44aded8a9752051db1ece24d1dcc4e9f5d6fc7e187f3086db1dd274a47945d5f8fc6c1892131b2808856b2c19ebb29b562a0fcf474a0042f391ea6eb66b1c265e55d53e9ba255fd52cad95babca92cd6260b55d3de45463ce3ff783be9e826bbbcb73a5268519a79370fb1103c726fd521c36225ffadbd3590430c09aafe7757420dbd1cfd622f2583bcdbdbee86098a3e99081432adc690bb9f532b66031f3a4349084bec5853f7a2fc154a6ee30226ebe108544a2481ac53aa7095535c1cc497a07eb4700b5e65cda1cbe28e5a6fdf972a50e71f73fb3498022b6b1536b0a98ccf63b7fb7e22c9e4d73f854ec66ed6457b218d319bd92db7d094e171124e39ea4f2af035073a722800fc1a468bae0133b86073b3261254d976cb7769095f725f4f9a20dc2516da3ad2290021492f999920404e0a18b33dcb3e9f570d927309d699f6caaea96b325c410cb7b5274aa0b4f4ed969a029cfc3e43df00b4f7e0bcc36e35c62235a1c8ef412f3becb38fc0ae19dfcf606b533934fc40ba5d35554cdda518fdad2bea4013993433825322b25b29cb7c91cad6b578adb23ee7484c2c8d2fc4bd06ce857974194564644abde0a3ff45e91574e03bbc67ac49c089760b0fc2766390720c8fe7a042cfc90b4961d1906c8eb4e313d3088933d884ca1c8b26f8dcb16241fc2486f2d3379b9fee37b3ffc60ac0e67fc8d1f5eac7945d520963b45d7501e2474d59286334790521240acfeed53f73c90f2e9328b69235be292a02477ab31e12019bbd373889dd4f6f9d9e8cbb449dac8d97cedc61abf6c7915bcc1779ab37f7429d7201e070af46ee1a0a645dd2977629acb55bba2466df35d2d3be2a8c575d40fa051eb529762093787b28d44802ad8e1d414a41bccc62d304cd988b9c72fc838beaf1179ab9d262fcfe64ccca6cd411c0507d7f12eb2f06ce4533e40b3304b03a844a281eeb4823c2f7e4e27b265e138f3d6bda26f8a367c9196fbff4928ea157f25cdf9f20c27e6409496d74301dfdd91b4a46a2636cf4bc54d8f98f322db16d45f7b918b968fcb4cd4db6b609703bebb846e79491e2799a681aa8f215073f75e0e80d258a38261f79956035e3d5e30681608e3db7fcc1da4f5a734155ba1d5eb53e49065b42215bd60572bd695ede14421d88b925956bc1f49dc3f75aecfe22b49502cac05f9342ac7981e2af31799890703f815286bb4c031752d432e84047582978e823d8e71678cae38df0ce30212342047ac70ea35c7170f0b61a657dc70a2e8186e0b78bf200dbdb50879824f89d6c62e23044a36816d7630a4d75d79e4d52bb79fd9b83914f27dc1a89c142e8b87286a574bff71ac306d588175def378376186c5d4b97d09cea4bf319824f0c591b034570b4d22e9fb397ec85fe1124209a5eb21fe24f5ccc312324bc982121b32aab6445ee0e3c222ae02d01c62dfb830ff7b49b8ece0cea4c38d412fc00bddc3efad72cf2b1bc28348ed1e4a65e57c258859ed8ecd21c00abc9609766887bce27c16d31b87d89b8790aee98402b0097ee06fb10e1d3d678a46ba65b5ef15115202a916333cf6bf3ba5d9594179cd95a9cad8213e9861a7f0b67bb71a5649113e12c48bedc014ec74468d70da70e95c0b288cee8cdc843d803762a863fe0163e12ec888b698f5b88d84ff62b1cc79b33169570b022ea10b11f1dc5ce103abb34a2e078d373c84de8e00f1477ebadaffd6ce721e8267e448ae5dcfe287573cb51fa65379ca2573e1914118b041ade13a6be34deb8b428a1eb6d6e9aefe2421a42b2ba784861f23183d80fb444be03898e3bd01bc0ca0910fca82defc6883c2f47881de27aa80ee77f9016c02d606da3ff80d8c21c8429c82117b97461e7e27e3184852db28b3d3df5b71b08193e2a5098d4babc83495a50bcb4e21f0d275b6ab3e7726b459eed3c9a766aff87447b5f6df596ec0ed0f14a009e279f981386dd21ff7b464e322724c4e033d77157226db41dce7172eb52b6b9a75130832a7ea9bf94ddc18809da8bcdfa34021e9fcfefba837354d7f7c83037225eec0337aa26295f12dc4f37dc93334c62cefe1e4d9b8109b1a486a52d2a714f269272484386c51ad305ec8aa729ff60b025a96781762214d215072c75c84a73de9aecb2a4b20790c18603582f5260950a94a68700a4ca46e61262e5057e1daa754bb1a1510490f21ba31dc3725f143698b5432a1f8eb8a297d4234b3daee3a7a03b86c497acff4f0014413790dca7a637af116ce563bd28ea7131d37c96a698f1c64032ace18aa538fe38d21ec36780f6869a6c1b8ec1de99541fa25eebd8b3fa1c8e3c48633775a03d08f13089555e85c5698a0673bcff2c6cd172c4d6cb4aec3b048a18e164d3491c149053a375bddd4fa4d40a88b76167c1107f0ed02629f1d3e360f8f09e7d787d0564e568c181d4b680f19107bd92c0a33faf76ddaa5a70b855c398e5e628892070b8f556642eaa48d18f337cdf080cb850152864583cd02bcb5ee186a973a2b6dc2ebe0eec35dc120f0213bb5ae1aa6fcda870046b9b171b4d078c1028fe044f13dd8f84ab84535f67702c378b575ba8293fb8e1c8e41407ae86c3e92c4a4a81821df2ad7f349f97e43c5d5eb488e19ca4edb6746c5a53869411cb6e56a2b060c145615d24647b1fbca5ff3ff704174f5e8f6f36e2c2a737d7c29c789adf182d26a89be42bc1e9a67283ff3fa8821694411f51ba14a45dedf750c1ec89318ec1bbabdbc840f64f789081848ebbe7061e27201ed6da08ecf634034218ae6de38c7f390b8d61508c81d6cf80aefdddb046fc17a84013d4071da203127203d75d002d54179f95f92df9e483eea13971b272983a590e0ebbdaa343beb8795b2555d31781fbf9a58089046327568ea93715546804dd9e50c457cd2774166430b02b80382baf2b43e69d9ac54df84900499d6da60792fe1f1f2893478a8b5a7432ff0f6a20efb030025097c3c7f784d196812cf098efc950e1a21e8a107d24c002d0d290ac00f99e60645824f5fbf7718c8d647fbd16ae2221532c6b39052194e3abe9c8859eec270145770d12e728f0dc651567a0c76e6087aaaf44bd630dbe7c2a481b2ba791aa87d380403d3fd1a813250e5f89bebf2b9c15b1ef08db309b6cb4eb54ac9e09b67f9f1056bed1118cc30603fb3c9ef636fca270360271a86a82bc2890ca69fedeb6b614bf37439813be8b61f216978bcd831bbee188ba8a5cc9f6713d52ce919c4b3d75a31ce9f775b8aab7df6f8c35a799199a8383ffb3ff31a792ca9f05570b8332155e1f4221172f81feef3b78d130d1f80f1ffcf7a1ef5b6a5bd002ae82292043d89e03bf456d6c987bd5afb1c1589c7b567e4519eca84a9d6b3339f1bdf8a63c9aa3ce748364cc67ac7f64986b091eccc7d8f4cfaf0a2abfc5ccc05ee32d45e686794a69dd952220c005ce4d82a155928f2a831d59351e7823061c4fac7b8ae5619a2eece0c3e8dfc8446cb10b6297afb78e868ae861881fc8e017718fb680f23a1d19bd6c01b7c1eddfe0ae92009fe87e151905b6910e26f25923d09a5028d2b064623ec3b47436aedcfb0782f49edbcc2d9423f165c5d78a96b3ded1829f535708608e0fc7d7373face13d5ccb2cc700769ed0c26102695337ae177229b4db0a8be2f50fe1ef38f35c14239f6f09111ecbcdb28c8764d89c975270d71da09c6bd5b41f9a8a6c3cb49a3145bccb35fd972a215ba72fb377b29bef609821c7efc4b9f907e26b7eef298b546367fbb7aaa3c89b1f5da51a4ecee1164b1796d81c0f0cb9064b6f883af622bad23f11456909f0756cb08f33e4545a8738c978334e5f02f9a0ea19327ae37ec46cf598a110bb1563864aecaac44ee5d8ad043b1563b702cc54c7988a185b197ba8626ce56060ad2750c42d0e3bd2454a5bf17edd9381eb30d6344fb309c31fafd6881be674c2cf27e4c9863e1bb0271bfa6cc03d2fc4f3847dda30ce13f67943394df8d3847bda90cf03e679433e0f98a785a9cfdd2fa41dfc79e45ad88ac4ac6fe9ea997c8690ea904cd0876467fc26cdc42f4c8692bdc88e544cea7e336f8c4ad9574b95fde4d2e12c6aacf69f846b1e9572a8f913541247992fbd6fdfb416ec12d4e97d7966efbe9ea9cff79ba975e91429fb2b3a857465bf954b6c96b8e7a7b6a3c27211f7feb8d1af156fd66c02c6f72f3128a1bd63f52cb48e4481065e98c91ff61284ef3c7a09a9244fd82154b44e28230f4cdc27dd26a47b1e3805721a91b6547983d61b68b125dbb67ba56048f5781858939fe310b83d130e851175d8a068e7df8d37350d545a390d9181ce4dbc7770de49654a0656de4bca8c1798c9b64b8e98a43eb19dc851523273c43f90f870cbc35916d3f5cb8af07b58b0fcba34a031880ad86474b15c9728159fc1e5b5a50ce4bbe6904c6301fe3093c1513ecdb5256ab7e22018bd8398b4e8932d361d4bbbbdd66c3534eb42fb59187d959133c5727340bcaa63158436197173b35326d3330a73d563599b98251b3532d4038fa09792737650d9d2e552839698611cf6fdaade11913cc5798bdeda2b29585093fc2b7e281bfc0f82cda112f2284f02f002ad46c4132892c207d4ddd4b82b22a5faf364693e9d2d9cd1cf07b0d5d4bc1011974802226ceaee8001e4ffaa7978601a93d47c783180e8790cc4270b5c9e93c2351d6c2c6321cf60ac77ab147695eef062fc45b078de22e46fdccb895c061680eee260b194ef1e129a4c39163b6361f029ea13c05cd506cf0ac5bb6a8302c516f51be6c92f408e94dc0b0c2fe63c51f13fe85bc6cce0808ffc14941e8ecd25b77c201563336345b17808a76d9e40c7293fc2235084f4fbd379c93fa2dfc820a861509195b1e368deb4aaba2ce26563085d2c26c023287022310fad433fb0ca23e35fa26f4aad4e7638a734b95f02e61183474e939bcfde84620b270ca36e2e4700d941c7101c769434bbd0d148a863ef8b22b1da22f39bc1cec846a0d05f1e4eceb15efaeed63e5fb58a7eede181245071138fb79e51ad56c7d7d39b9acb65754e19ba0be861a2938d4037d62acd0e106f14c87abf2e6a97773712b524903781fdeab90e69c2c7fa33c59df2af9884f811bd3bae89e9cc6bb0c00882a14cfc0893815af44d0defeb779ccbf23dd0d27990b122dc6e740e309fdf4ffd5069c3d6f2bc4cb575fa1ca60203bcf7a8dcd17b6c259c08a86052f218cd30649307493808ea766360d46bde9881d9bc3722eec531b0b5a49b32104cb2a3a5223de52484e1bbd79c44ddcd2502cd4906971f623b7debcd3e8ed66b325a49d2d771de00bfa970ef8bc037dfb1fed649185c3790c379012ca55276f3725533a875269fb2a6a4595eb77c34f61a0abb7e50c37bb45b113d1e05281e0837a292b0fb95f016b98607885ab7027dad507f3e378125f2fbe5736ea4b7e9ca41d7d68db26ed93fdd40ff009a8a2f04832b15f2053dd8debef40c6f8b1793b3e490d38e886b8a19c1c789c205a9c42c53fc7036061e764bf2fcb936c52ec45ff17d7551c2670bfa47e6393b9e624999a7e03e6c834007a42ef5f6ae309b7aca6ddee48bbbd94996829e985568d65f501384e7ced5b100b0e0c5d83ee9547b035e9e6320969aebb603c4148a01f3497f5ec036740bb18fd96fce3807d0852aba16458a7ceb354f815221191b95ea05d1ab323668a05ebe1522b886266d6bd4e6cc8371744590b5bc51e2ad627ca471f0fd50549707a145562886e6af7eb1da760685e6b14c1d87bc541f9d83a6b6fdaa773a1009f6f881b0f9dcc3e3fcaab29affd901b3cd450230259b495487ccfba70c82a6c6f4473596d2245362008d0020e44c468ae55de3ee0a9c575abb340ba805122e4b39a46ab00f9c36f7020ba8b6b3f7d5cd1258a503a54dd6ab95b9c1723d21f4cb5a412674a72447c65da797006232d28583b6a9732829bbca40411890b63a5d6ae596d30295c2cae468dae8711b12dfe01f9c54a5b453c10e6cb3d01590a8eac74a7cc83508a6930c52f20157c25165dd33b7adb0701df00ed484d849bfbda901259d0b90d6116609575eaac04f7a94ab5395557e37bfe4cab9c1e7cc17b8156cd714105812285cda444d9ee58894756aa93f202d9a6f79639968757329f7c0ced04367234abe86324bd72b7568b36e10c3a05841f2297087be6e16463ac3f79dc1a83b3cb33126927f0ebad7f5551da90a2e04a9dc4a9e3434dc2f3f81607f821f6ba92f79102a63b7413ce8d98c736d7aeba259ae76884a357399b442807d2519362591a5753033f9b275734d820ddee99c6b7c2664c3487ed96616a8d35dd33b35c4821c263acc2b1678b8cdbb9bbb0db6c600af9bc5dbfbf0617d7f6df22890e00f8d16271d54c34870af5e8ce389d51cd335cd1f7d5d991d67ee0c19242b6c72a6419809c3241b108c0346847dcebb635386832dd70645025f27842a837f24e93515d62e880521927ffd9b173f132cba887157d30a1ce3e4758a2ca86a30bb27452030c190be787ed3ba40bc3ff2711537eee516ada9f2251af50b1e786494ec49f733efb1e5cb472d6c01e0e2d16b86d688050e6bac1f1d30324c92103a17a9fffd164516003187f77d74ab85f9fda574ac371e10bcabc1bf075f7308e5d8cf1f78b91e9d2a63a406420f897d4f8ff43080d4a024006fcf65d6babd59e6e31515746fb78f8cbab77503cee1754c7ff93a327e138b6e6ce9ea67490c447f6e6d0bcb03f3158b5713b30babec826069f776760cdaf6c1924b4977918ac933a7e014e74af50b67d988acfdb65585ff52006165b1414e195922c04db1a6ef5048f26c959935db3e1149bf4a302240a2a40178fe3db871a0b4c1bf880dd63cfc9167503043c48ad98f6ff7a3b9bb223fa0ab9f2470e3b8071e3e4f6656f0dacf9227b4f5bbb7ee838b0cd41e3ad416ba6c89b2dec4083d75088f39c8b1257c58790567c90bebed3ce6cd106d247964108c2d551fc9d396338118f29e98b076e4f997d0436408829d09620c4b3771d243278beb901f8da5a9ebadc7e19d93186a432c7ff5b81ab742813c05d87e09cf854aa39e81cc2f8c5d0664ea9fcb562e53b274194f4729cb42a815b8b03708269d59ad56e0f53ff771f537cc78ab90aeb252a4c9c648db0f4a5283185c883b703925133db8dcedda71936f55f0bf83d9fee5b8f48e162b415871808a8d78413913467499830ccc32ad7168ab3a26af7531e312cf51cbd0e3f05928c58569f9e34f6c526e566cd987e7301d040e0c1139e9e12db70a6b309bd1e06ac897ffd249de8185078a2122d7ddee082be5b68478d7e2583ed068f44f9fe17a4bab2eaeff17d729512ca68e3a66fe2e358a8b051c2d115e0b1919bc2133706115533d50fccc394cd911f7ccc5e4b9aa7800bb7b57615bb1d13596da1d7c0df053a8fda706a4009d45ce1e0bf9d89c018f151c4f21a72f6df6d4d40000d93f2d258776c7a36705808b3d1f98fe23f831acbc72c0c273731130e9178a3e45bfd3c19f4b4942b0df3e2b0975dd6069f6b27d12753857ad5c825267a16ecd4d5d8fece3d5ea7fb4834725865fb91d28e516dec7bb0df231b969a4214263eeb191a6b26949c9985957967c900cbb288d0cce330414c01a3ef772ba9d23a8301ba99725043d4757e01640dd43d9a864731cd86f2e5a41c61dce5f91407f8713d160e7bbb5adbccd4c643c2e26602d360a53fe1fc3761b37ae5418ce02b92bc8549ef11464dcc502f3bf3b7c27e93c16b22a64ac4029590ecd41956166286da5cba3e3e0120152d88eff65c18222f8c8990db906b8ef8991fbc6a715f9ed439dae7bef3e38a50f8ec6060645c0cebbc6d195c31ede92f3ee7209da9f74d18bcea45441664e4b0180a3e1b048b538404c85b649068ad672efbe74ab2ef137ff70eff9dca605dedb3faf6d910eff204eb5e7caae9f2b04ddb151104b4d20f0435811af64f9e8eb1dc1324457860c12633e2eb2b130d2f7cfa270e19bbb35950e63a499bcb2c242df440bc2817a3b72fbd04407fb6cc5dd4828591da1a2160373340b6ee13b6ffc9a1457175f5d25ea1d5cebdbf7b98d249929bdb151a178e2c6ca9971fcc57ba7c9f1a54faeea9674cbcb6f4a4d688c53fd88b430967eda0fac228d2dce92285ef7cdc8c1a05258d99540864b29bdaf722e2e59641ef141787aff182d53b79fba4ebf4ef4c7588b46135b8f3a379a76353482b7b8cd012cef75d288b2fe672deb488f61d7347681d78f7d6f45aa5cba71aa82d4a9dd24cd32dec0eb7bf0aee3dbf507689dcf1f724c7d5908bc9a2ccbeb9362e14260ca915d3f08f42acbd0844e971d49c86fa05a4f007cd31abff144e1f7ca8df7c6fbb3490981b6b8d9b2c309cdbf701826e6f59ee7e9174decb9c50648ebddba660501d7cbe32991bd347f063bfcbf77a2fa2dc46f157faf3e8c01bf316698d93e067e0e58f898afcac3a111c21248495b5a32317fe5bdc076b7b269d740cd0da85df39aaec364883abc742de7b00a73b6ed53242e094098baf02ca3fdbd2e4b3bb0dfd4d8a5c414ee6c3e0875645ef58571a5cc565740babbe1a57f292b649573e6e7ab092bffcac0e7a670c7ed4434ebbd6dc728c5af8cbe87a5d5ef15901b7421511ada0f71c333a3849ce5ca85f9341448f107004701a635bab26b2a741ada79b7609b3e1ea1a0439c86defea6a519b8155aee7a48380ba9e0e1bf51b83f8446ccd0ac4e19ba6bcc230637bf2b1a4a898eff30f5150de620afdcca9dd9c91eaed072c7f0dac0960891d3145a532a375788adc46e653d683c6ca763b8dcefda3ca1694edc5b4fbcfcadf804830c763caf034f9470eeb436a071c4ce2040caafe7849f192f215416acb7c8818317ca4de8cec210ac4623c57080e80532e8e82e88e3ae8e400ebb6ca2587cdd1f1bd3efb4979998fb4fece70bae4e43df302a78d103a3d1070f02b33914d1f6a2329b64f9ae5e9060c186fdbfe183808660ca4262affbd58040204c79a13fdfe3638578954ee6d98ee4fc824ecc170b97b73158795e99a171f998bfd9a1fd82ceaa4fd2c1a6ab9881b6ce5e34cdbe484d36f8c384075d00c9346c90c805048222cf3e1cd5a98e5043c8fbb5acf85c07cf7aac6449aadaee70239af5e68aa6f69d4e278b31a5f7e9dc7d637b263ca712860746090b8548a0dc6e68d2b105d3ac3fdd681ab104c38b5e8d849c397aec82d741f72736b1716cfa3ba675a313342e038cc52047667e8554e388c1a7cba29075c3b45c04694111deab6e40a38eccd7f8ef301f4f07ba37612f5cc0d4663c08540b24109a3d17d802bf772ca0fa4c57772b3de191c3c8816691f92efb2c908a5d03229b52437bf6329943f1cdc8165bc03436ac00f19f8b01109c0dc93ebb0a7070bcfe70e6ec62e7623cb569c28041daa5d79070d49308eeb62e5efdce3d978432bb0a2cc13917d1e182686c2e4677e2d3d0130c847b859ffc82dac9ec39f97143e66abb75d0b770c6fe8e0448c6ba4b32660115888d2dcf37da5ba5084d2eb784d52bfbc531febde2c99437cf001cff24c9d1f0720be606f08cae6049793706cab7565a6ba8b90776c714b6ebed6048b72a10d6bdd9ef1b06c036331629c8a7c2274e0bdd9359a534a5d340a50f6b131b9130e677352e44e7a39b45e84f40df7335635054b97287a3fb4190f271a541f7c8c6a60cc00d6b0fa0aaed6847ee9e0f8bc494d426ffb95f66021257b8033c401c57fdeecba4807ac80c150c4e7b6fb4cc8767e2cf1dc2b8c259740813956ac1a52d50c59818d6f00d2c8da59e9def663ecd2d4e96d3e31b82f4436e41bfae23940155de252de44494e163fe91991bd49f6bb070aee14a0c27a36a11d8c7ecaad459f25d87800eac7177c161f093e13cbc0f0ec65f2d9e3abf884d8880ddcf669623d508c284d4201069170aec14208b755e74b268898b13113078d93662de4647546cac6c65aeb07177c09b0ca60d582a2677d11e65b3acc51b0aa3a9714c0f7ad419eb09d40a0c0d0d385f74aae504b6ad19a3c09c3c104864aa9e7db69ff838f68ab375621685dda07b1108d5306262b772999b6abd8b8f457bcf2853cb23a2417b2c0cabda6648abf48e6bceb55b864faf758cba843b5a1886fa2d0da409690d283c681cfd4c4a6a3d0844fb6cb8c2759c7ae5887cc97b93dc113bf263bea388be1c1138c394b1d135696ba305177c404631a3db7c697e270275fd2df1fd9dfe9ee64e6101d27cdf269f9f264dca52401589ffe71d2efdce85277245e99f0d0fc7c5176aea516dd85381174154bf9cac8df6858373946d1c27ea5f67197956d9029fed74e5b87a8ce14ce4f160eb0268f7dab74bdafd28e24c625abad4afb33e8057e653d4c0d05cf1f8336decdd10bddecaaabdc3d51b3507023428a9de07c9f39d6f72679082fef0e31a201850b2476e931fa57d652632ae8919e6f208b9688d3618b54e6cf1de4858b18722e411ec2b71069b32a3c525377736c62cf0fd3ebfdab64c8cf09f630311bff215fc1d03706f447394958afb075742f7b2ccfd09c4077a243255a4c0fb13537a903830db14e55ac0137e92321b0ef4a723875ad9c781a4d278bc017cdcb6462834ca658598b3ffacad44fa5864795f77f039bddf19ad8d6785e797ef77de2e3213130d45f40dc20abf2685488f4e8699490383fde068b5455c97e8942f9671125be29fdba8b0da3c9bec43d30c822bfd072fdefc8eb11edd029eae864ca5b426ed4efdc6fe2f96b74ba3c7bbe706d4d98f6c87541b2e0813def0a98c69c5e58cdd83d8fbef7fdb189093dd7797053f200f8c6845e1c52bd4480ab1011b087f8288d05886a090f2465b46521af4586d5483a3949289e067cc6cc63820234f91a5d5cc63c37729b2a1dc6ffb068a7cfcf18b614679d753fa670d75a71fb6ea1d4c55a8918b9b5b75a147564a7783b29cc48719a31293f0770ab4c57f76305168bd6f0f2403dd570a069152e197bf502f459262c412a9fc4df88166d31e7c91dc0083d4a6b8e92f4d3417ee3d3576dc837029d5de3915b4d4a05f3db94b262fa5f713f12bda1073ebe8f11678ef1ba80743ce3e00cdf9c56d0ef8f503df0d29feeb829d302d6f29c2aa55fee58c5d87e4cab9dc1b57c9f589c564298a7a682f1af090bee8c97002b021c4b29ed42490e3667b796fadbfa448c75d73bec57feb86c7bd71efff980540539ece1ed2f4b633ee5cbb14b277e125f0b8e071203d09d2d1d099c874fad4fc63d4ebe64866d3f06fed4a2a136ad7a53ed964e10549d695e6da8747f879fcef0346f6550b43ab1c68831ca29b653159fc572e91152aab2735b64079f0133f8e3b884fe6b173dc2a8101b4d9428d93e8ba85a4c7445ba9ced5bb7086e445e379e721f27e515553111634c2a42d80f0ca4db402c2d9c06f895e91b6a91de682c61139eade61f54d56cdb6c1fbef3eec5b154ff53c9df8e0b408f4a1e0743f27e8beb04eea67ba9abc30badc30ba4f2aeb0b1a09c198f0bc48c9ee2115d9a566e91aeb9a638f06fa677f435ca6dca07cf2cf7ff0352d3c68b29cbe2f36bada31b490de3226028a0b6095084e10bf18f3252f637aac411bdcfb3c0c98f27173b862c61a962e4e2084cb63c15c4d2b3d402c6b5ed47e9f9611b9f5d6022068e50ed70fdb2a69a8e85444403b1bd7a1f7d65bd08c1eeef18afd8ddb15977a51a049731c966e37dac585e34454d7fb670cbc197c34ed9c3762b03505cea68111cd3ee640108772c440ead6cfb0f55827f7c19685601b777f284d86f4433258e0ec69aadfc72c39b7e622473983f1406b87a6f779987cdb1bbdbd3de1e692582ade1e3f19ae2efb165aaa9f8e64e3d79ff63f7247bdb584b4167812b8223b77d4855664ae3ada51bdf35d236bac04b100e81ea706b8360ce7af989e32dc227a21cfb9c3d245d58b1eea047144f42b6b23d0f3873b6b7040a2049c0e6caa140800d6c4166850f6ad6d180c76ed2102800eb70fe844d3fda55004a090be5a7cd654a154aa2a3ff1d51a60e99aaaabf4889993221ce666690cee6b73ff3c61693ec4d3d09f7d3eaa103c30b1598c357613eec36dbb2ad4f5f36fab4c844a5fad8c3a0ca530d29a04205182cff03a089938356407a668917b0db670663585e3789eeb286659ae91bc286b43376dd90ae77e104932f0455c720d32c0e1a0286d4b0812035c92193e0b48e8b78e00a5e465c3eae13ffa19b36ab48e5c1698d841d1916fd480f40413d9f9fbafcfbc7017011ce1744e1c687c6e761480c60449dd1ccf418698bbe5ccfd1946e0d9f0d58e4e4da41c371348cc2c022a5087e57920e9e822b1dce11c25e46f421140e2b88b0063c86090661dd031be894ab419da5777b10a877d980bf0c6ef9a8dc2303252c759b605167da21a9d3fabf06a9281cea501c9dcf44e37281bc9fcaf1010329821b037d375f0a02ec9e0b39df9c0e407cc81292d2a4309a0038ce050fd26894c4692224d1602afbba86ddc45b97ba508404b4f9c46b0774dbe55b7788eb225831463af6e1c97be40681fa37aca466d7f850d082c845427d1fbcfd0fb4f25f3bc18ae373e734846fc5b158691108d172c92e449dc2b0a3ccc48c718bf357b033678e15c7575ac780bd4f54e863fb75678b8f5f51141c2064cc0e6cfc04587a037f6556b4a898f5d1f0eba57fa258cdb1d3c236843b9be43e158d98d207f9e4c1b9ba9b64e867a5c64cafe16f4b6729850653f9a0c2ccda618330f80742bd3cec790df31d499a39a138c74a05b638f434e5f4f02bb8627e69b0d6b8093e2dd226ba79526e4d6572aa8d4dad71c794cf87c0466cc12b7eb08c4027f1201d482a6bd6bbfb648f9382c042941ea64a3c1006c0d3d5d538327c54dd9a02b9b33fc81c7060bfb417bb67cb80d33acf9c0328717e0a06581a6d1b74f32cebc5448c4f6ea83096e2ab208921ebb98e27f6bbfe0c443a551f40890893e7b7ac10ff568479a10779afe6f547175b5f744d3e6e4eb59ab60deb6d3bd4120484fb4fcade3ed8f94acad94780cc7531e114444bdbd001bee2e41b6615294304d0c9dd05b6397be951ca9d7c88d6672afc9a224a2c48c19e9ef1fec22dd95724f295a00ad1101936ffdd3209f4437533bb66d0c0bd621b38cd59e4c781cd0128c20bc898a161f67f6d57ab3160130a7901d9cc6f0c8ef7174ff522db95a5352ec47fab17a6b5a063e962a4c6dd3702927901cd4165cf4f823791759ca74f4a1005ce430663648dcb464da47fc0cc1b03e80535e6cff07477fed062c47d8fc152fef5879dbc2fa9a198cf425da43a54f7989580d31d8e5f449b425cf9499db2a0bb06eff953a0e2afce238d893c638fcfb11b3a663bc31a553934f29d0db01a477a4017bdafc88254628ae57190e39a292bb04f0476838942c3ae79a2fb37036a31f52992de53c55b9c77e172adf5134f885dc3d64bb95fb1292f38c5b5462c045f14da38a9ae7f94988197de10c4f5ee316249299910412c018461982b3246e55c4ffc7771ec22390c4a37f347551731a5b40946dfb9124b651fdf24b4b07d86c8c37c653526bc7c8c8b578b8b66a8b9e82f17a4fbfaaf5583ca956a814d758163271c1d26cb57a8ba292d2499b1f2a34a5898b2dba78faedfa727593505ca3eba883cd1e8b3cc506965e440242819f345426b89de36c2a22606f1c59f536ae625482ee4b40456e958c734a7d86c88fe3be16249ccfaf242c6fe05c52194978722590a5b4e831b05a9c7d8509469016eded593026e83c89b91f30079647902946162894c0b689a137043b1341052cd60a0af1ae20762a512cb1a85f6f23f78e25ef92a1a1897c34cf928143f3750fedd64c85cbfa0526595ed8f75598a4880c8598392cc5fb000c220735e971b40510514271c21ac57fa9eff151edeac414d02e4245ab7a1e3e1711da555c2bc5bf76cde06c0b29054b4954fbea97e0a4ac29d944ef87a378964de6f7a6f7a4f06ce0518ac29740f9d3429cc0ad0812a38c0793dc8d6c28ac90c96bc2e6482b798f7876cd20da5d58340c4701889b351f13e3e2969dd3f60914a30c9dcf905b47abfca9c79d31b0473f92fa44edb654da36da93e8ca2fd7f81f21c89f58e8652be8dea65733056adb09eb5df417a97001b658014ba0e3d0d3f94ed32eb54c2f7743983e23227f624032fdff5a520449943ea960ae38ac64f52207480205048063db37883d1395526a786af7749ebf766eb9a34c6073c1e583360ec4d2c2b0a002f0a1736d103e1b55e92f6216a35c18d057fc14ecc2195e96be5f8bfda9067361b70b6870edb4cb2b023398ba1c43fe430c26fc9db3fd4ad4a5bdd57ffe0c7efedd1b552929cf3eb41caf3a082b12c391bf3dff2b0ad5aa4346d7fb52ebf29c7af7f59b72b036a7aa5c5170d72d39cad688c283c113b3f5b5c1f9f3a5a71721c234cfcabdc292bd28e8f9422a683f5f105f193637dc65fbbd1fb9144348b438a5276c12222edfce9c923bd8c42fff1335643ca50ff433fd327d0078eb3baf4238357ff6035224713e3cf2125f990c07ee839412ad2842a9abce3251b234ccb38325158db3e17abd4eac50bbc35ede8da121d383ab8f2d480e3cce079c33dc87b86f7c29cd499546dfcf1f2cc0494d2279476cb58b3e4a053e04f65ba196944f0ec79762a91aa7a923e0d18e44a475eb10403617d8aae1c8dc329d970d2659a7626800339bb48212fd2235028f0915a74e1e8582a6a49d5f0780b3d25a2dbc9924db6e41112680c1734fd06aea73ceb4c0b5bc7a15677fdc88024a904cf937f99894f5a7c859800ee2ab67721a4922244a43893cc747f4797c58827a452c23cad9d9ba649e89fe4a74a9ae1cb7a03754a817eda9a2a7e00d6e996a566b8bbf98adee89c443f5ca845f7e60b76f88414bacda75aed21d8d518c5218c121ae802a1de6e0340843e4ed59f79a08298148d0c827ba413e753a13b6bb3a98a88b2148e658eae871fb8bef8188ae0e37a3f1746bb780f18265cc58e49b363b72a60d3501da76c14a2696926d8d5891ad00bb719455f7b294e6a5689f9816a9591efba38d3029d5d4701595d5da1f60ff0e8c635f9686c018b2f8f07f1d4bbaa241400fac2d55465d104059a41b3d142c630cd103ddd0e02729750b1ae6dc46940c175224f5e28a13aee2e1792b33806e0750bc78810ced65f522f549b50fbf56852bacf129831d4fb11c19e1f02a910c76439b74e79549d68a04ab022b50cd9b98a7c6e4bd535d0b42c771672e25da2193e8d179dc2d9b25d1a4f51d57be05e65e7c1096c3d9ffa5e77a0bb4351dfd78c965f3c334a2728883997b5a805f292a0e054327c4352af5304dd760f33d025e15fe7e356e756373b4b4729ee7469305e921f69b39bce7a5d029ee83420496843bcbee875bcd86597b58cadd39df05f78fdf5a47e5efe0c501e342204779eac7b50ebb82b5aa10ef6acf2b1d84090a7863662d1ec3a98eb8113bfaa08f43ac5b24f35fe90bb3d944321891302cc585df3b1ad44075b7e46ba2ceea602bd24614f740946840790e78a87e3ae88e77ee57bb9af13230053dd035b5fddbb5cba26d82ef1f715e2d63bea2156498ef5cec853f631d96a5da198e80f95bf51f6153f6c2504422a6ff647949a075349f25f64ec618ae847290e738c9ddafdd631e38e6a0235043bebe93b9448a6f1e4f5e9844422bc849ddc3fa015849dcd23434c39ae250764dc5e3e1db3ae4b8b2e17cc4198daf768c6314f526c7953e1603d1a01fa3054d8400c8c1a0235a2c0cd8906465d82ae1cc23ff8d416c21e6ad73f8963a47ee17d3632c0eff7bdcecf4819da058125df14cd2e86db8f022e1e1a2a60af5f9f32ea3892e8eb2946b0e008d5158668a44f8ae5bd4dc7defd8bdaf37c103baa14da67c8d21c7671e9961ad8f32784992f9968ac6784cadc11882dc5d81cd4569686898f30b26f501fe1d661cf15f3c21e40679b50a40d11bb7505d19c73c5af4ebc72aadc52e3d5630f124e010cafb69bed3c8e361de836104e0b02d84dd949cdbe82575c5e4f40bfe0730983f24a7005748389f9261b61b22b65e6d98a5881a36733a12ab76883eaf38b8ea2881aab3d95907f6e2eac45493de49eb0af796b61b6c7e5eaea541570c0756e78e1916f0e1e09c2d47dbbd9281ae2b0b8e779e577ddb17f71efd6f8ce2aeb76b0728efea6d24bfe5147ed359db6f6d1e01b142f71a4f0a319fb16cc236d51ece6eabf0ec388ee1bd4fce16fe21613d9127b564d4438a143aaaac304ddb4d3974e93b0df4aaf63270aac042aac3093cada7b96919251bf81a03407f2ef5ddf7b182fa493090390c86f81ed2ca7aca97067f8a32b45647ae8922e0800f6b7b2989d264039a3c9c21f170468c2fcb76d0868972de75550917d58ca124a6e197eb79e1dd5ce04459216d82327baab4e649156109551ca116624a46250fadce6eab4ae10032ebd0a22e68c51815d100ee2015629a69e210888cac318258e75329995b8fbb3740c01862a86cddc03641c33c32450ad81d22a2bcb90967b43054016c77a65bc02362104c374c3280dc232f3f38967573618f6023e67bd0ddc568238a0036696fe184297ab73bbe39d4ae887b389496e1e04883e494633540bfbb599247efbea54032a905e6a0ebdf2e55a57d72ff3ae7e6978a83f7b101e1dc2bc08051af9127f9f08f054e7d2da8762268bc2098fc2d097a9297ce52d81ebdfd3577d6a0fcbfbee9d91caa837076116358abffed7c747e7e2c1827e1a9d4b34f77c1f19c2fb6bec086fa5b1790a51e0328bb6af49612a95074f6420dd3792f8388e552dbe12c72d2e71d222799ef92965f54f912242a105d508404cdbc67aad91dd1facbb49bc4c67b2d00310739980cdb3bdfe3c92b8e9b0ec322534b36899701c0edd06de271d7b68ed3cef50e90142187857404bec294cc080b28a406b4c1f2de9ffbbcc5f5057e6b40051f63a36f1faf70e257d5c540f862b492e708738163f62732311a8d2137b1bbfb3fcf2a8bbcfbddabc707fc3133ba98ca074faa6ae350a5de1988baa5840b1eee8694284c5bf4e33b2fba197ccf4a4062f2e9259938e276e295f0b0a4d37af0823538353b22c446f4d7f6315239d123c4b5617852a9b5b59d02662030d0780ce829f47b925958eaea99c6366888487acf26fe683d9e6e14f45edfe4a380410e463bc6cc87cb6e3505c7f8cd9f60000026ee9070886d48e2d40559678a34bda20b3d53e6adae044dd4473e0929601183e001900162d2cd65ed5c95698652a3bf5d3e23d8c3ceba945d044c19b1dcf71e3b5f06acef9e7bcd9bf6c5dfada3c130a2111b91dff89ed353738b36e61031118f70fc9df89e96241ed979d598cd45553627804c0dc470868beefa54eedf5c227d2c43e080ecc71c61940fc69e706f6af28cbd2cf30dc7e57522b35052635da8d78984ec70e45a453a85161525af3b5fb904b8ce59884856b8123e6ad5dac23b5e10322934ef8b1db740e26a50d351feb5f3b7d35d146a469f91e3c34ed82b1e62e305031d21b148a9de631d776e3a5df1d4da673b68ef2e887264316ee5a9ae539d934661df329a22b8f34f92707830825cff95d8f10493198004a0309eea8d27d59a5033aa21cf33f030552371b8dff30fda0d228abe2e89c94518791f9463c8e5fb2e01ce4c2ba5e55563c6c4596d6f8ecdbc2aa5793fc114a4f002cac0aa94b48cd3e3ed211344567b80b8acd29c059bc72e9188e0b6471d2fdb37818350a77393d6e97eace38c312339725f09da5ddf93743a9b75fa248a3415e1910decd6ec500a6c3753323d32889596476b257476a03f866b91128f45a09a0c2fc8dd8c0f091754e438794b1041510bd426cc42d9addfd46253a551dff99ee9f2e17b698a40e79a4b7d11b3b590cf43bf2c438a35f7b21d966127387b3038dc1199a0a34f38df382156cb609670103926ccaa66b31ada3f8dced58a63ec7579db3c98d896ef460ffbdf54c5b12dfc7f4345535c51a9b0d20fe524db3fc807fbb895182f2db4cba24e31963830b505bbc8633e81b910ab5d4bafd2e6be9a17a578596323534fd51e05ba834dd1450a359d6373addb1cd5db370cfc6785689a9ba0137ad8babdef8809b2ed23c5ce651bc0f203075331312403f261bf9e0af4ed5b6381b18a1e965457234888580b206ed18cf44b72066b66f28d8bc3cfcf02e352bb0502d9e074295cd180e77caa0ff87dbf741f26bd3298b430950ea80e74766bdadf77a20d0e0a7f6714a4f22eeb5dce21d2a9cf7b4d72560f639ee521438118442cca9380bb6672844d12c00041053e478a23014824e46239dd4dcad28387779b4e42ad7c977b4fa3ddf18284f1a747f3c438d796182476cad712f84c881aef55e4d1be0bb447100e7b47bf7d8d01395ab373ec668b8e137a89ffaa95191efe0b4aa9a10f516bb5d3f1fd2a59051f37c0c70fffb5470f528b1e4dbf030adc83cfc72c85a454f11b4d427c77931b27d91385500dbb522a9e234a771709284b59f0cf2a8d9a69d3f83fdfa90fc9ce56d604078290b8ed1a1761ca649a3269004adf8b7410b35e5118850f97173750bf805d6a764398db2553a33b5890791b93308886c0bf374052d123ee8b2a18bbe3b2ec9952c4bd5d8ad99f38c726eb0d67d3a4c2ac70356fe1511585183bb4d03437ad310abaa3c69acde6773fb327607a0b3ec0bebc736486457290ab8ae82b45bf3a0df8d5ab082fd37c41e06967f0e4e3e402291536ec7aba6841e61bfae049ed394a1533159507929edfb86e2b1ed6e8302cb5d9e87462ee060eea9588c8774d914532d391ff451661bf0f24639ed4593b9ebc6d38a2920a7d944ad798d0e144532ada718e294b737c85083cf591f8608bf33413aa9d63e204dee9cbdb3a0a844a80f0b77a391fb861c57cbe76974a19378666b2ac8808ba75b4a65abacfe00133e149587510c74de553a4e71aa4de7385c96771da6e56da7bb1be06151d40dc19259eaa29f7e708a089d5a44c24853b0dae7cc46b2274f76952b9e6b315f7470caa64fc31cae44b21cc402b0704b7ce85c62621ae08427987083b3f376b433d203bb3b2943e1ba3e00c7bf0f0c300d4c3924b6858dd8827c1d0aa95e317231f49d22dd24dfb8b4d50586bfdd0a984431d1fb17f50fbfa17353abce79a7e81dbab6288e08820e22eb482689f945dc5ac2a85c3758a526615013b4108aa944a575e62ab39b7dcdc6c55f71c17f03776a50c89647a612387852169bc040f887106f398495df1a51853b91f62a581f3367001d0b94ce46b1045f2a4f25a8e8255e5fb637ad7d2d2313bb5c66283ea43f7dc8476315c8a6f32089e01a3a190e6d835cb86122744be24ebe9c52db211796d9c8116533744567bd93fff80e8637e79574e56f759f132ccd0a28c4d8946842efd0821332e6ff42161e881d45b5158567a2fcc2e436205f90ed8683d8881b09a62b62c108db03a3e1a06d3253c8a053036155b633b852006d3025a30855fc5799889f417cd555697201828edc098e2aaab8b63dcd54ddf7c270e919a8bbbda65a44f5701dbae4868168c8918f2d07300cd27921ff7d0156bf8b4b6a3c51ac57de36899fea4f3f82ba80b413e18d79948807a10bd1b5c46cdb88b0527330b1110070c0fac7ca2081a041a9fa7078992c109789c8f8ef693df26b7f84f9d10b52fa30940d2ee0b86fe2a4ba0859612082c81933c7a821e6aae48684f1ca67d98b2263231fc6361afa0ce2a5af268aa9cb6f3b8bb5c3cac1cb6761fa810174151c515af527f7fcad69af81bb22a5f0cf6ac8378d65f283884421855ab53554be4e9db4bd7b04a5dd7aabc637e3b3bcf1bb23e43fc3721a60e56e6e494ebf55431dd1a7f5f47cba3b531ba1a1a71debd542527cace2c981fb7fc7ae6458012b29db1eb81aa82c8a8f45f508d10b406c675fbe2332cb58baea57a2bab90d45e24e7b5ed71291f62c036c9f1c4d0e7179a941b9e8b8fed11bd7ebb3a1b30008b86d05a004d800f002240e5bde3e47f40a8430eed963167012159ef99f677710b1f51ee62da1941cf4697c2ecd25e39819a13abf3e285c46399ef5089ffab144ceee9688e1df52c38b78ec9798dfb04ba6ab57f29c3e779cfc678680deba3a7b4d733df2f849c6d5da35a9d0d2ca04aa6b3159c6123ce85617e58bcd25d09d75b236ffe6dc174cfb5c7bc6a39bba7e33c4185418c9b5da0007bf56cfb5a0dadff693ad357f3b0c91878c28f77c69493fa01473aef5698faec50367907a796f21e41a948ea54c5d30fe6dabad7ef40b69e255d270133a7b7d8fc33dcd2a109a4e1081e6bb4a63b3a44d865478ffb043e94ab8f312c072d0dd605d9573c76616f24219c01cb77a5b31b6d9a10f871c9ef0103dfca6d54044e7e0c3bf74e9f7a1ee3d29662f35ea618b38c41d37c9be0c4a3d368f3c8d819c0f818867b9f9b9f1b15ddff5cdc867fb7308cf82bc51cc84ba6dde98e75b3cba71c8b94cf03bff56b1159dd5ee723c5b88c04262d95af31a28945df5ff91e366a48218a79e1a2516bb1cacb55abe7409c10732f35ad1a03d8c938a9204ff5148fa3bc3e00f289f2b8dd9f2d00697058938f22416ed6ce988f02aa6e33a0c42567f19d39a5cb09341ba4de2845dbfdd67e1a3982e95395288e0d3a67d1269051127f2f3387a09e107c35f9666467c58381ad087e354b494be333e5723f6badb3822571a214fb06b6adcdfad804b3cf286ed018cfbcd232557b46c196348b3f5958629ec070e5a6009cedf574bb21ad294dac82c521d992f3bd35fcc5cc703379fe3dcdbb9d75395f5033734476e93b63fe96fbc8e9fb80682b78007022d32edff25cb04fb0e6ca87fd14766a9af7f1e23988ee3d0c860f7b5c6158f0af927fa345681783ef5d921a836262e01c0205ec75773a7e0b840dc2da655759abcdbea4d9ac55b2fb00ad28ce6f89f4e9d06947a9030e7e48c48bee0fe504d7b5f78a417f055a8389ccfb8a5c70982b6a5208811fd15b5f6d81667a7b6cf36b3df56ac57c6e68208090762dc8297a0b703553786c3211b14f3a9623a81f803281917848885026aaac0e34e5232eea0e2d37ae068fd0114b6dbb44120197cba9bbb6f819ba5240270cd6c8196b3bb6a801d141b4bc09f2362ee2d6b57cb2af1372e0ffabc0f7d1ce3fe0bff3af8863870b1bcf639f484141ecea620a7d64c4b6a7d00db817d68055948ce86241335616fb2e2aca0f0b9c06f129bcf087cdae918ebd03a13c6659d1133e0cb093b57b0736dcd46f7fded62d7294866a825a4a1b52b11b72905e1032aee0b6dbcdebfc7c94f81e2ce6a12d0d19b306fe1bd6caa8f2d980b90fe5bd9dc9f96dc6d61ebd924a3b715fbe9a0a4f1d2f416f766108b6aa0ee2ffab1a5d2fe71b2255e40e555f12726d4efd5b447dda40bdfa9107b979784286a238bfde9777b7bf6e577c4689b236941903c4c30e45e461289346f626a21e65ca81408b73d03759743105addd5709cbb3055133400d7026283da2f3d970bbd038e4d9f577e06cecc022902d68dae55c33568a5d7935599a638b9959c92bcd3d69b4b23413813d56ac93155f9e30a57379832ac176cdf2f5e0dff7885756e21c6bdca0a7c45e28b9cf943ffb656ffffad002aa4bd68f732df7ae2eee2391da2b2cd6aae26c2eb01e7f1ee5941e7fd2bdeec461f59d8461cb3a46658fb30962140914bfc6e9a591c22eeaa1332c0dd0618372c8289ff8de44e8bb6e0ae32c7f7b97009b7868c711e9fe3731a246c4587d471e31673a9d83b1b235befe08c6ff3fa796e4f08eedc7bd47156cc067871354e33d84e99ff87df6a080ae8d0f46d496edcd77bf01fc3fccf1d4399cb5ec6c436ffdcbb24c35c3ea0d55d606838f72eedcf9de0ecc7d1a297b591b819c635d48e070ef31bffabb9e766c989eb904407482707cef7fb3c31198b3f6d820eaf9403885141b77350455988292e7b4563099d85f7781df46e2de52ce6d257c9cf2e64a85efa8167d38975c36bfe6fbbe658c855a8f4be158d9eb9ae6c25028a91a40110be966a08d8a379d9422349005ee150040b7d5b3a427c14208ab5fceb7b8c35ab26f0e673203d394d4273e8fccf65a2a7217ced334e6f057f848c830eb400be86ceebac862f177edc4b5435b7accd0bcf3e997965cf0337925185983dc33a5e5aa68411b0889b1b4d347e20ff9e61befb210796c38c9be5a565d584b6f654803f0c4710a53618df1b020a97c3b75c01a7c0bdaeb02bc2ec15753540b54644db159682e96ca34a3b92451412e1665d181caf41bef621e969a46ce80df81f4f9ea3ad594bb6699c7da710f36f056b169bb5940f8d8268c0115d3ede5b506fed6c97927734aba15b6f571f98f2fd5a7a341252c5835862f390595c6567103e22fcadf19c1a01f8a9f371bf2ea967746acadfe7afb9c8bb03c91132f0cffe342ba90b6c914ff4db4269df798bfba02498c9d4a41e0a0eedd62833c314973191693a609f0655c36618e0c266c75045a63fba8593054324a3a0969399e57a4f5d759c4ab3878bed11e963ed9fc90ea33a6b47c9eb1289aab19274601c1f7598cc717357000e2e703c4dfedbadd3e9d1ceee2cb82c7d0527d78ab36b34ef2d588e27fd208219b90bdf7de7bef1d140b140a540a79bbb2cfd3ee6c38ba01f6b588a58cde8ffd7b7cb79c56e4be0336d2bafce3aaedba6ebef7be829797b92bd2414254b762ee804d1754e7a595b44837c67af9528c4884fae50ed8f47d5753e79fa4972f08f5adf9b4314293b9d36c8cd0e2a7125a91a0a915099a3b458a1091550d458ab47dd02c22a6e88b172e6fcbebe28d13347788d4104def45a2e9c9dc3d2d387d53cf47d33b4e34054dad085ebe82afd699e79cb57aef514a6ff5483f9f3b60d3f69256f905a1be79bb975a91529be7cc1db0e9ea175ba77f8ff551be20d4d53475d7d6fc8250d3bc511ae36f13457bd9268a162b68cbf8b9acd9f2534ab959d37aad24f91ee57bacb3defadc866c3edb4f27e96b9bd9c7bedeb29fa36d887ccd3ef6133eebb4ab79fdf5aeb455d9077bf66b9bc7dfac6fa73857d9ca3e5b35ff7eacd36eabb24fa9c69a574f9a7df0b15bbf1ebfd11fbba855e57cf50d8a417162b03a81f14ba26188f5fc58afe4bbcc3e56ae36eb510ac4ca3ed5bda725ebeff02e54d9e7e6975fb7fd54bf14ff7550db43d266410b3e062b6f38fb54306f2fcfee8181f34673de3a1df926957f37e300a1d9c785687a8f219a9eb4b9bbe9b8d5cf923dfe65b38ff5ebd571dce256bfdd5b8bbbf45b953ff3b9db6af629519f99df3d035ff1bfaf6ee183b7dc164f3fabbcc5f772cd48842eb96e93bffaf626f3ed4be84248047c53aaf276efaddfacb5f2375dfa8e9a52df90c2609dedbefe5d422d6e0fff342f63deba5b6df6db7ddbbd77b3363f7924422df3367f928ff595ebb67a9cfe56b30bafeb693d7d7f734216946370e0baf709187874c3ecd3dbbed57a79eb1709cc5bf593dcecad1637bffc6c9c68f230a87607f48a20ac2024e3874343435eec9321861802b201879ad0cf298510428824e0651041042180229296a17bed93274fc25c990f041040440883f8e1871f6a82e612245b0ffdbccf7d15b2f9683e37dfe6ad675ede676659f3f3c1071fe093b705c6006fdfde4f59052d66f51a89ccdbfd6993af59052db42a3f15b4909f363f274e9cc0337ae8a10730807036243e7543e2537d22bc0d0f9a7c4733d0bb99836acb7c83ec2a4bb4f0d2654b13d39d4c864e7a3f1e7da8a2434a044f5222d02145650910a231042f3545858bbdf6a6a87c91a222458a0a0c5254b0b0d6de4a56a9c2c196f505292952a4a44c494929228544514a0a2a85449714123778c1152924a24821b1440a092929248a4821d1247544135247082175440e66903a224bea0828524734913ac289135246182165449794115b5246cc206544162923aca48c282265044a8d54142fbaa4a26c918a52452a8a95549422525178484139420aca0e209782d2054c41d1a221e14587a7658b9452caf79e7c571528881a8a3aa4a04891a242841261a80461468a4a9394125c524a809152024b4a892f524a7031e7b437a5841425a0b8b5d67aab24c474bcde0f259c340956566585b9be58d8c24744e9789a83eea692e0a24a2a0918a492184a25e182541255a48650a92478f080693289539ef4e330a0cd8e9027a6a480d1b12282182a292844a4a038691297541148e8eef64c158146aa889ac7e3c16bd72523f6abd16aa587163ed39c21984d2bddf14b3230e9fd8828d0480a783f240f1f03b8f17c6c3db01f0f0a63bfbff78e8399cdefd57952f4d142cd0e15da7ca68bc39c40b8c70010a3ddaadfbdd577a2167558c8737fdd4f2b18cf2bf0e131d6035fd56b339fdec4f8327740d795e3adcc5147953ba0ebe62771eef02bcc75f1f6e71d4b0be334e9e37540d78ec45c503f20797a56ee2e4e61be5b5c8733d0f5fe3eb1203fef319fdeebeb2b4aaf1d8d38a0a07e87a7d79433221be76d080492fd5e3ec5bffb8bb989e7ac1dce4127eb49daea34ec2fe65c7d5d9cfd5dc738fc8b7bd184e12dee75df0c14d4f164a4f336a4690692b3b7997de25feaee3d48c4a2e5458e16a0940084a21d88a032040dba48030740fc400729d822aaa0071f2a7a50028b1ea2e88012822789212bb0c0f2c48a2a220a78108a420c0f28d8a10a154704114400be1042aa8d309aa4cc885138a961844045137ec08316a4c03e3142133628fa8187293a7012b1848089225420032f5570c0840542880923c408821726c24803156b158f0861208107580841041dacf84133e205291ae8a0c3154fe2143cc0229e19430c910512b014a521458c944429d5e275087b82e8a3fd6ea28c82efab8fd29915a069d160cefc9d320a41d26a6f35b158f8c2b29126334343ba81234709856b83857653d8681ad7cc3406804ce300d88c1a3f6bcc618df3d55800b8f1cd00aac604b88d0b60d2a1820176f0d071001dd9cc3d424d7b6895d142b9559d5d11a007d0d6ad5230b91b7d2b4a0fe5a13dc651718f674e7a43d0f23dd38495054daa33b1b4adebc61c1da6c6b2008d27011ad38b720368cc6913a0b1e2a6b11540e39b1b575c63eb8db14de32b008d310034ce6a1a8fb6c65a0a8d6550683c536a4c93a3310947e31b17e56e74c53129c26365fa74604870901ae7a0695c9a698c824ce314b4c6dba8710d00b0c601b0c18dcf558df36d2c809b01ccc604908d0b101b9b2e8b7b3c2a9864844f878e8ce68a7b3c37d7572dac8a336557269e3811a30d94184d9ae0117aa28a941194400735aea480a7fad018942b3b18821a44c842258c28986cb9411a6128c1c53ae99a5a6283ae37ba525a1a43eb34ab8af9f87e4469850922076052002589252690627b5001119ac0658934c6482d11eb0a7a805420ecc1090d5c66f005132f50a004a2c80585116c318112614e00c616314e1cfa8a9440a50563a8c0071d3cf10422b8f0e1055466c084053c88a06e41d03aad6b644d4b93a9e63546cc144a074b478d3174a003164d8620c50e37d070410dc6c0c10f40582366a9ae0062c4014213d2f801cb183f10e10327d808a38c30b8b021064c821859115e65fd5242a04620448c294df8e045125220036b460a2a0eda00230618420801a6054f3a7ec67a82149320de2fb5044c531e26ad4fa804650421860e6c80c1833378ca9802073f108114561c01856732e9c9061b3d27a5af674f3acff883e69cd3c7bc0bd1c7f3f101540f62800414292770e1991e20c196315986f8b2841093073678d0f32d581689e69b4b3677b7e64ea39967929ec2104767ce22fc20066d9c41c50dbc5c414295c11056fca00661d43063c91035c2da2991a05e27aebdf75e99168aee707acb85021f89a8a6456ddd0bbdea31bc52724d5287f524d89554cf5ebe75a16358dd6397e1da639afdcd1bb6ae65f386f37d0cd5ef7d95955c89a1babd929b37fcfa1c83922ad7cfc7402da704fbfde4945c8901fbbd926c83a17af60bdc633b54cfbec33d663541c3415aafe1fdb06d9d4a793f7e34f68bb2fea4a535c6d9207510a1c1de7085fd5ec93d762c2b91bf7f5e883469a27df426339779bc02c0835683f4b1e15f1ba48f0de70d066babdface4bebaaff286ff7876a40eeb4aaadbbce1ac134dd61fcf93d61f8fd5e9b4f51aacdb60bd88751cac472c6244ec1f171f0fc48210c98e718f07d72075589fe16c903aaccb7038481dd635ee49ab128146433459c7798668b2ac5b61c0d86098af2e83fc7dc63d4b8b6ed113da0bea13b57085aeb6fd832b74b3e6128939317e51e5c6b4a387ce4e0b2e4020e6c85ba3175a95bbbb592231c7da77032d37a61d3d74765a7021061bdffd6128febdcfd710ca963535373b7a5422e00d867819833c8c314a0c4984624822f460009ed06e3441b803bf59e7a4c106a8a2599f9896a9ef7650adfa785e68d199010a3c30e5244ac3bb004f04cb4cc3d40f541a9e061e783aab96f2214ba949dbc39702424bdb1c44e70e68e485f6c67492e6baeb5d2d8f679417eed8d103480b115a2c9a48110121c4baa7a58926f0098c02a9f818e57b35ad54fa4d261c1c20179021f174a3c9b22acbc202b01465aca2adac94336a53d66e1ebf938d27b5f3f7246fc1da440a33e265f3031d48af73af8e0d0eb4fa77c8c5f84e2796e2a591a2f0d29d8e8fc9f3522700a363bc0d0f641b3660dcd8e4c04607365d5ed2e63581c49b4f3c94dc4f7cfc9187194b7989c9476ef3a11d949fa7792d9aa0e44ef2379ae0fcc34ad1042f39b8e506d67ad394c6f6a9c9a2861933881e8542055109b74c392fe784403a4df3366436fcacc11648a58cb476f6b5e24453ad5044e95aeb4dc3ee81134d9004c5105d5ff3f7a3fedec808850e130dace582d7460ceda6ede1a539a1ed6bfe7ebcb67fd666074f4b25695729661d9c997e292b5fe4112c1356965c2b3f5cd63e21c67aa27a620ced091c644fc8e0a2d9410506b6296df75101946e6a4af14da09840a31f26c4c607d65abba3085cec00049735c2e082c70e01053df84207483883893278ec1224203d6145db191e6badb54ff450c241d89c60036f919d50e2c68926394a69dc68e169d929f50047a5c18cbd3661dc6b5f6a415bdb04136dadb5b73661545aba81b7e981d63d177672f44b5939a251e897b28205c6c6a5a7aff9fb019be669b303ed4693a4f4a59b68b60943bb9963de9b4c002163bf14133e60c14b60c9d6902dbc43807257342dd144cbf44b2d01a56d099608d3f337d3e605b2a696205a2206dddd745c81f5a8801e52235d30d0eee72f29e6cc2a49f5f329ccfa799af041f8a276d9ea1d3d3cecae8a1439d8b33b9a371ffa1bda4057cb4c12ea9d18a1a502bfcf09659eefe5cc296307c57804f61b33a65f7c6c07b4e401d6481fa94d0c628c57c2221b2d3a334dd4008a4e90797858022886465276933e1eef92fe747f3c2109a39354952a5da5e563e489291260e91863ef6887ddfc69d29bf390097d269d3187562c02704ccf6cc26cb4d0627726c8c283f651cb16951e6bf375fac49f3a6cd38207bc2b9a6c58509a35f437d83412a1dbf0b761de86c4e720d8343ff803b1a8b75f90f9ab7085564fa50b4d61e30c3561c992254b962c598242a15028140ab564c992254b9af8218b25320b140a8542a15028140a1500d492254b962c5912794013001d6802a0c3dcd22a686161785247246179557bcd09549666f65e7a7b2e0b13726f846f5e9a5598100bb11efbf74c9d4cd3ac13ba9890a69890f778588c48931d76c0d7c57adee341edcd2a5855d7652f0cbb6e57353dceae0a8361a45dec1ecbac6b7138bb2cd81606edbab0201a36812775400c7b9166151362bd3ec37a6e5b191372bbfec27a26c5ee85c02e439a3b2944ceaa054f484d292a424f0942cb2961d8404eb1d64229616aca16555555a929504cc922a7346165540758a4a03811143b0515b6d00e9f4f3fc108c87ca2a778781a71d1de212c425eb38af7172733137185b99c6d344373fc779a286f3593f4485ef8344966df8b657eb9d1754f4393edec8cbbf6decada24ee4dc2da18a175f2b1f6809faca4655955d5dd57a74b72b7aebcf560bfee83e50bcb7869e5ccb3dcc1ce48403d22e163d886195a1eeb2b63ddab6940d78874f17d80ace0f84ebc38328d4cbdc6c9909a068834532beb0677b240a47befbda38bb4f5c85cbb0fe934c77e658f277134d868247da4ace42fce5b0fc69aacf2d653fddee7fa98da3493bdbab4ac7b31c9ca5b8ff6d17d662ef3eb38e60debb41735eeca2e475cdeb20dcb5bcf757c9f2be3ecb3dd7769e5adc7babc8f95656f3df7f23e57e7dde73d993822033f9c7baf86dd9bc66e9ab39e7dfb195de63ac6759bf6d7343476f3b4ec33ca32dd9fd6d1d017d00de63a1a1a1fd7907064d8af509696cf7eb98e86bec73a1ab08bc3386aace4f84ebc57f39123cf581fc970a3dcc91ba09bae685eabdfeae2806ebada974a37a5d20f14860dd8247341e21d3d80dc689f39be4cdeba8fb2df63d72c9f4c62bf97999412cb2fdf008561e3d993eaeed07ab8806f64deb06fd6759c7dc35efdfee05b7fcf1e988ec732d04dfbe0fcf24d34bd6ebb405df403d3ef71c84c9411e2ba1edff578697f55d765bc716334aaf2ad5e45539da6fa8d6a5ef818cbc87c3422e5d0fcba617fc35e669a4babbe136517745dd7edf1956932edeaccdecb5ad5bd52a46857ceb689a2c9dbdb44b1366b5704da755df37ae4b05bd51fd7cd247dffb2ec83dd3a96af241d64d90ae72b494b39afea613f183fbbaf46b7b8882f998ff02f1c5b9e6a50ecd188bb6d8384d6e1c723a18d3efbe2edcaaa5f7ff2a3cb2c1bddae7ebdb2599e52ca1c87e4ed3a9657f6b92ebfdd57d9a73af6ca67b3bfe4af4adeebea1b8576b5b52cebd7a3f60cb330d70535d672965d5d505b5c655c17d4d9f541b236580e0bd26e71d949d20ae93b9194ed652e73673336738dc37277b50cd7d9bc55cb5e78e608ad7b5eaccd40d71140d70c07dbce70b36f64757c5bb2baf775c97c74edd9a57cbc0cb7dd6fd5ed88dbaaed5e57b759f6ca172e4593c432d0d5d67a7589adbce12a6fd7e24bfbaa9b2f1be9285a4524201210e95e1209088804448a176b1768446a8c6686945515cdccd5b28cd4da0888448a77868ad495059461b9036aa96940a451a701a9a4c4124b292fb57b319036991ac8250191803c2ebc30451f2fbea8dd7b81b4e9991ac825656b609ff171a32291f2bddc008d5544fbc38458a422d5e81527f31bdc8d3a64459369df91335a865557252f6ca38476db3aac6e0345b37207514a68f1b7e5a16836506ca068f2f73650b4f8fbab511a608809a91e7f6ef6a9e8107647df7eba0241515b5b967daecc651f6a55b99339088a1a880e8d4e33861d636be692f7567589dd72f833d6953b79ed9dcc59963b193ad495363ac5cfb28e297d1273b03b692c1f41f0a5ed2b3ad4324afa249a240e385f8b03a2434d0f4738854ebec7d9d756bf8ed92afb8cae1dfb65337ef6eada9537fa0ddf8eb07565ee362c7fa594b768bef240d4be1b556155e96a93d9e77ad6d1a1a6438dd1cb8bc379a395bcb9dbac8faa68ab4abf7b6be51114fd9ea5dfad2a3daaa21f1d6a9f9b3b4b4abf579a412b55c9c7573ff7de4a5adcc5b9033655e4a294f205a1c6b2c4f88af1e64ea67a95a1f69a31ec068906dfb2b8ae86b67e47afaaaac2b78efd72d70d124d764b33a351ee64de335c27f31a44f19555dfa9b8ae86aea29435b4c47ff2cab92b95ee0da23ff6640f8f65f517bb864a56d7ad5f8925965856b292d50eced9e5efc26b4d4a99b75a8ad7b8ecfa7ddeaead625cfd5a5cb759d29252052d463988d670bf14d5ee8929e9e0ec5cc71eff2c76a5bce16ff5d6e556377c6f2111aa5e71dd5669e9573d2dfdbcdc0061cb40991ae09744bb42f0e3718c0f3f3ec91ddb7e643ebac75cb765f88fbbb8ae485f59f691c9a3eb2f8e30cc5d8f52d8d688eb8af48824bb0baba1e58bf4cd6457a4afdfcbc91c73a36bf25ab9f19d782377f73397b9bb3968b66565b9935acf70f747cbdbc534c75c506c9969a4e14ef5f0171734fbb46da6681d44d94cd1e4ed722773d0ec8a13f3661d8f2e233189cd5cbbcc4718bfbb8f9fd9aa6fd6eff1b151deacadc2aa631baef2cbda95bb0dcb2c074d9cead8afc4b72eaf4be6edfa56fd5e6ed5ddaecbdfed1ea7312c30883684f75d59bbe00759983bf8c04682c85723193002c7fcbd4f8ee60ec24ea8df1629744224e8f9fac620428bfd887ea8a2e11fa62487391ffdccdd5f97a47bdee74fef331b49d29ddc891133824393253aa0fa3dd3bbb402c2aa8b1042086105ab9b0a461860ac5d3477f616f54be242bf16fa71f702dddcf429fe3d7233462eac5b8ed4955aca7a799bbbb74d91d6ede8ba03a747ccc15eaf53ebad3dc6bd3f1ef6a38c2bc579b323ab7c4511e0ec23fb6d002633eec2b97b67c2a11147c9e45e58e24b2b4593fc84a29b5d3f45eb6e4729bf23f3bc2c84416042d0ec19ff60c0049d2927138264904c31a1dedcdcbb439a6ebaaa6635e79c735292d6e1dc9876d48a43234ecca11827866882943e225a247c57a24331ca28a79c0d650c8274afa6954af289914f0c0e5147d4e9cb09da752986c0a27bae53ac07870ea73ba01e6260ad45b562d5b2261a505a979c4589c41c0821b43e8b663662db9a61068e86687a613ac6184bf75ba8b5160727eafc086d763787a2c10b5db0c232aaa94143bb8fbfa9b22bcea6095a4dc7c328300a8c1225e6c02611a2a2a966c775b3e94bc39ac3aaaa68bdec6b8aa68eb629e6546c8a39a69823e4350d55c86b1ae80cb34853f9a9a1a175b772316ba96b4dbd5a26451df123523d9661d985bdc62e8d54aae9b1a73da994524ed1cd49ead2cb5d55e10a6df4f96c948d72906c2c3bf69dd8197669a468aa9934cac905d1b8067e82d6ddb423bed90fa34ac68c19d345d320da75e68b0731758793bfde0f24b0710a3e35b4a53df23344d38b219a28918e9ee823cd3b2dec4493cffbe1f388698fdc6b1f8ada7a1c6af96d48ac9f39d10fa82efe94bdf280f7348fd88f7c175f83688f1cf69aebf510fb918fa7d88ff5983ba1eb6606eb11ead16b628ebccd6eb94dab664ec37543accfdcfa4cdeb46f43649e5de6d9b39853ad38790f019d0c37bac6f508f528df70a72b977e63ee7006529349a21faab4fcf57e20818df54818b9cd7eb6ec87f968a8d8704c7559fdd623f741f5a786b636461cb063a7dfdbd1c5cf0bcb1e0f78e91367e2e581f5184d3be24ddc69e1deca9dcf21f6736fdd5ab756e5c2488b763a7fb497f75dad396242ee4f3a321ec764faada8635f65349008b5cd97eec4fc842c58a9d02a3d695ec35a6b7d4563e2588f97afefdf2c8a39f7b54ae7190e5e47271b591cec124df4914a34d15bfacac12dd1447f9f75e3f25af9547f2f1a900599cb1cef70dcdeb8ddd1f6c65a6b2de9d6e6c841b149f9543b1c246badb5d65abfdd0dd2b4419a4c7fa914afacf25588f52aeb254509b1918398967f134ccddd3d7c5d8dd8316cf495778689393387f7dd0c23a6e1a1bc3928b67c5d22185e6010be074ecd90cb11b4ee36951aedcbd1969c4edfda927b5aba72dd07bafeca98a48261a33bf920cc47769c60200bf036cf30dd0545d9a75a5987ef66517741f172f49b15b5fc8e9b1a12d5b9b9c2fa51f6d2850b18f4fedd7bdfad2c63d59a60e00a758683d738cd7a3a526746a7a4d3e95c348736481df4f76a5ac9ca5de937354d8c31c688e58ee6f9541dbb55cd589886abbfb85b5938265355559555c16befae9577e228c366b85186cd70b4b5518659b4bbf8a584a5ca12bd442e714b171608ae89065ce1035d5d1b65d84c95545ef3054cd4512fa3989e74fd4836e9fa8c8daec7c274fdcc076ca01f619f5d9f820a57d439db46d4cfd5d6a1145a2f933b53135dafe5ee54ba46e9fa2c77da93aec7b295bb10f4af7b4db707eed156f5fb0ea847e3dfdce15cebfe1ee70e5a55f537d1a0b3a8ba130db842a5813a9e0c15b5a91f110f5db47d046015f53383a1f9547f2c7d3cb5f7e2aed44185d89f6eee60d79fabed73be908c9d18f38b4255942fe0214be3f423e2c14994d2b7899a8869a21eb8b402fa11f550a56b06edaf8458209898037f7f7dbeaeac3dfc22a5f3c317075f4fe268381ca9a31e976a7e270eb3c34c58d939adb5765afb79ce72257879d9396d90ead8729d129cbb29e4baf6578c50981c8a94b43dce60fac2d91767c4561cb487300783c1ef2098a2092184d9e60cadbb3ed0f79776afcb98048730ddcd07451fd917f4025980bf108c11994ff7f607e72e5eddc43e8fdfc9bc9a7995b55ce56e2607c596c941b1610a2a6ca1559fb8c2e29d5dd979e805ae602f678655e0d1087b17146fc6f2c93af6abe50ec26b88752c5fdf8941135717510fa8c65f7ac4d967b1b2cf27ebf7d5abec75f0de432cd63b98a5efecebdeefc4ab7bd89ecb75e50e0416471b920159a06d7f71b4bdf0d205112e201871d83348065c01bbc5751f68eb750b968bbca841eab0fff20132200bb3edbbc0d22a5ca171c1ed481df617379b86bbfadedc85a00fa58ff8ead52716817b5ce598752168eb10920159780d6fb120f04bdf68ba19920157b0590b8e2cb03c8c2fbc326084ddfdbc791e897d834dd7cf77b06fb079a16af902be9ce44ef4f7b85ef59dd5e5d0db0bb12f3dd1c77e8f85d7a58aa657610b8dbeca7b99a23b598fd3dd7c272540ff45ebe4bb7a998fc41f9d10ba9b4260d3ee666b5d7327e4824cd3d76f32543cd661c75786ec87c96089c0635dcaf0be1882c7ba0c350812f058973230a0c9163cd6a50c930c29ac87c920bb9c01c5ea87c930802a54d0008789811837b8fa613200200827a290008b4c3449426a8bac1f26030e20a080c997513f4c06580615467862ca98315a3f4c06992000e1892730497285de7a57ae93dc912173eba16d048ed98830813cd669cf360203bef52313783cd771357a36c21c96599915ec3b323b59c118b63ee230e5b057ce48905768a4e76465f4d74037b4159cb5dfef48ecd8e219ddca280b1ea18741bb754cb921f386b6621d3f4c869ca12c1e2ccf906594c5a31d6794c583b9d8941b8280eeac600712a2d2562c0c3be6b26bdce852066d749c51cf0c9e0cb6ec645b71e2001eebeffd18c204098fc7eb2e0e6152f456780d2d2e89f51d8478324ec9950c634ce4fab197276a6514e5b1ac5842fd3021272bd65f0385a0ada0288ff583a3ed71b263cfb2f5b668511bc9e42bf46fa3bb1b681ae8a59eb841db272992ff78b0a2c6a929be743d0d912d7fc34c03cd2f2f686984c8a5e47a764caf890521c26407211e1eaed40bf1589fd88f033a6643508bd491653909dc82bd68b1b6788155305825c90e423c57e8afc20a3499913c2d6de48d39dd40d340362ae8010a46bff1581cfae3c9b6741751586e1279ac3f1e9c9fd471b22721422d7f03a98128b0c40e2dff523de8a2bb38f482bc07bce8ce03442d99401e7c8be321f2e0bcc9e337b92814ea943be2135b7e028f07e31b194d1e16783cd6ad38e9c1631d734cac95b378bf91c405c37739bc1f47843a363c84ddec38e14f4091cedcc10309a1a0a84b02b3159b0308cc3801bd2f863a4943a225507410ec9a39f0cc28f2be18ea99396025c97b0ea32d506881e2b119b5020f6080e279877fd805b42df81dd5dde341cf1fc1917009bc01219821049b32b4ee9ac28b2b5cdefc9dd6899e333545979e938cd4952c7af64b5d01a30b0cff6ee0f4f1dbf41d6c72a589c61093575cd0f330c8189ae7039ddbc4135d6bee6c7d8fdac450d75a6bad6140948ca92b465c6152021b316c8852528c59430a27a4a408137326a52f526a2fbeb86feee6ac660492d453bcd1142cb4ecf17c3c1f9a7c0e7813e326b4eed742eb7ab4ec010402ef470ebc0b336e89501e012f28f37c3c20f4713d52c3d7c5bdf7be78e33ca571524aefbc97c28c4188e1cd1a8c30c61861d085b1efe3b547846a090e83c34083bf177e275ed855b7a715187d0f8bf5dd9df34a48df5f70edbbcbc291a0dbd708d402b768bb832e81df0da9a6dffb052a7ccd4fea90124e7a2994e40a7c0e160f85340af9cc74a492c61ab3986ba2a9144d33e64c68923ade3d5f4b8d56f3846cba51d287987b16f3f31a2665be466a78a35d557bcc913de2634edc793c229098337f2d1a90f91e3a5a4f2a658c718b1e1e34db1de007e30e58124951a5658c47812617303928e10ef0fb23e5102d816af9097fc178988f3c7800f978c01f79f000f1f1800fbadd01ad83b43ef2c45007c147b4c45007d53c35086f9433df49c484f78a5701b7239a4c7005f8b7407bd72cd55433c893d15a6bed8d1aa4452d4f93c0eff4c97742ddd1aedc919dbea17657ee480d7dc3d5f2b09bf38b97af9539aa6987c9ca514b35b86a24edc29e499c4b8b3990d40f0bf2e28da6c70191997f37cc34fdcbb0a59c2854d77c643ec8da826a79aa05d57356693121af8bcdf587e6e923c5a0bad705aa476641b5112906d532ffc074e00af12dd0de6fa210f8ddd018e435e59e96a6502345cdbed33c8d5b884ae0e5bae6a806d38114b358902adfcac2b0755dd8cd56b8afcb8d3933ce6041687e162d5ebfa187d1460b5610c20b2d84b0c209299431641d0662d357bfb7bb0db4f9eaf164e56e9eaa7c334ddecb567f55a6b793fe55d1f5f434cfab4083ade57201f6998733420a604c71ec8110c29e286d2c41951edd98d37da0bbf81b645b014145a4ca4c84505aaa0c3969b24407140f9326afcb144800210332d4c8d285a77a9324194883096bb4f184315b78e1a99e2487315b8c910613d668630ccf0e9908275c2c4184132e96e0a9be83104f955fc7af407bbff554b3111cfa34b3cf9b46e0464c1e666cc8cd5d926eb69124940c35b27469f8fbc84d6e12e1848b251afe662442a669af2ecfd3cc1302f4c21e8af5e4a633d224f08c39a7396709a68c5dcf8fec4ea829ede84f3ec1680f5fef9108f5cd38dcdcd5db35dbefc8aa4f466ee46d8122ca0f6d8fe43d1e5804e0eb10d9120e9959b07c045f9a144dbfa2e3af37022e1d3f7f332f7b93dc363391337a93b902423cf58f480b11fa116931a36d8aa075261898bc31626ace82e62b0df03e02ed99de8334eb94340df0fba9d07eb9829637a9208e20ca2b428a174dffdace6fd5d6b3460ef696969f24286af90a9b4ac1428a0ffdaaf59b3bebbed67b6fadb7de57aeaacf82d650cffc7a8457cc6a3e5d9f899fb920705dbf3ee373b3951cb713b3e2e43c39720e16cf15bb236d04b226f7535955506dd95b5c65b505afea6131b3f209dfb2c7af988f0ce6235f3fc3d53a536b7dbcb2854de05728314518656431832a3cd6e32dcce7f6c9bac4845898655d7e1e468909b92e88bd301f25cec78e49dc44020c99d35b2e3afb654667af488469fb8971a71bc7eacc2bfea4bf5e613ddb1099cfcb7ce60dc749cf8e2366311c1877aa3fc9dcb2b85bb90efebcae5ef38623f708b5f6cd87741919a1d68e71f493cb70eee847cf21737037b89289eb847a26eb648dbbe170a2299fb20c24879e42f6148380d016b47a9256924325d411b3f0836a1f31e280be1e54db88edf7788c2e2546700e7dad5cd1dc035e2c7b3ce009b4aa1f9108865ae69b218e2823e5082dfd888c70d23efa111921a5f1e8e174c4c2050748471c29341a624e07c4052244518a3a7e0333d800a48598f3de0a30a0954a5674949ae489442268a263c75f0a0d5e5370494c4d179a1a642eb8e9474405891d67f4b8824809261a2084148868b29c20a3720d25b1240b13049a2f2e18a0608214fa114d1962eb4724250c91942e299c4883ef260a19c5e236f81738b893484a93d9d577ef4be5e8b427fa0274019a16a000b40ad50a6bb602bf1393c448218c303e422ec69c04c608638c31c60861aca64c4ae93ca927fda4f613ce4aa77d3751568152be9b3da496dc099ed4f8bd1f3cba831510e2b987305bb1ef64ee726839b3157b4525f53bd4dc830378ee2f845ccda121111427e8b4f5f55d92eeeebcec05de17430d2bac3645d0ba6b00f89d688b801f95d1deeb3940c3bfbf1d875352992912a1a687f1f492d23967adb9a39872af65a55346ac0793191b125f29769a27f819e516f815e14507a1712c5111c3f49b87d2f4241c3a48f6c480d69568cc4dae74a42978888ae8221f94b1f98aa69a89a03c6999cd2c7152b8064b8c084a95eb5d8122a5310d6cc1e48d198303c7401d446c067274c58c8288082b444434414404954b44841044442c19124334c42545118c3e66c4ce25bb0b30d928b0b8d200ef78644fa08da16282864443a899245008461b053ee98890d7f3f178b407e623d40812a19eef3cb0461b631a4954234b97297ae211d1105ef47bcf01ba564ae7943246f878c0997b0ed0436aee70c80768d8f4140f61c4a4c4f4ba28944fd08e84d633efd5b452e939f6e808632cd5f8b8a28e7808ab80d13a58e511421175c4c32eaa585bc9bc0d91af17225fb35043aeebd1d35a41912bf58358e926fa90124d4db48725ea88d6a60dad83559e162e624e1741b4b34488a563cd41f3096c127de0808aa627a345132c42db12067e37afdc3b571f668f84d6e1f4abd62c9a76da7af95e7d726fce579c9c97b3e83d5aab90b67202a6f1bdef3dab72cfbedbd76a6f6de5e097673dce562d87d184bc1d3187e647139cb954134d38960e307ed8deea7e72af8dc0792dd572f3680476c4d9114d73c247b39bc7e3fdbd0a4e20e09700adfaf567e1f85e8cb38aaf6cacefc7001e0f895d94526e36c599f3b85e53da2041eb4c6a689de9e6f190bff457eee2ad8ea069ab47ed72009e8d4955ac627c7cdcd46a14973ea444a1b81b381ff9e4635440ec014e7734da3aa58c8ff1f297d2c759246f345d9f56cc3f9b35b4be2eebca3746d0ba9b6bcadafc457f1d9201abb8620362039c9ca701b2e626e64cfaa52b0665901f7d02f2a32cd459de64881f7d02f1a30b35ec8af4edebc2f5386876557feb77a4ad752e4108296d2fe98bf7614690c03602c7649c104eda7e1b326f9fe3564cdb20beb475492c080e1af9f77854da25f673717c72372eb96bf4792d0b13624ffae5489fe14ed667ae719d75ed19b7e1f8ebac9279c56d389efd7529c74b396e3d47de70c8e0f8e448f2f528fc1e07d723d4d96f701dfe3d89ebacdfd370a7995b14b87bba97c95a34e944137dc6dd44133de15c02124d5c4402bb3a4ec1077a991d287550a927f71e8f3bf37b3bee2bf6636f04768fbd1e0f9ae723963d1e94d2c313d0bf1b4a8a750c0c21eca785066c68a1411b443f60e96e123d01d3ddfd237a22469320d1132f5e443dc1424afb444b49f4448ac637f0b10b9237d964a304adbb6f461dd8b8a28f1a6b4b6da50e5b636fbea20e7b9bb568b2a468b249d0ba52d37afb1aec066c28f2201c2c2a0d2220c0f423faa18b4e403fa21fb8f48c3e48efb7494234e9a3e61975d0d3fb255ad012b44e2312c30c30e7045cf47c04027e374fcb8e4d1ada7deffdf0c92eebd60759a039973e4a8903d928614168ccf994eba0754f4b0eca668c060ff389441f31c079211073e22950cce9807464df07a828a8d4adb4d44d2d15229901000020005314002028140c084522b170389c68c2ae1e14800a9e9e4a6c4a95889228c8519442c6108208218400009801181a0d2b00ce90f8ad7e1a2bd18457d3e19fd795fd876847beb63c20025d7ddbd75ad6d60475349f0c0eff4226f05155aeb54dfd9b977a60b133cd684c30aec87f0b9905a5066ad6045120a08cd7ca71c297ad2bcfb83076b276913808ff0b0f5e379dcf52cce49a8e98a7a428e8fd508df4c54ba219d8692e8848642f05506cbfad1a7734059543dcf6262a30ac8d9b34ee97934722abb5945d6a7f3d47984082a48ea219b289f9d4a5784f3d1b7091ff594c65eb598ad6da1f75d4c52a34b92ea27db81ff4f1950db6cac3b0edd3354a679f3f7df570cb82d86716fb740dd0ff256751352a4d261f4dcf3f9030072160276fa2969324fa04cd9461d591791d7a85d234867c32f4ea833bbd858523af14ec97459cbf52b42e10c9643ab789519fa75759a191fadc29d7e650b2f202ca9b82ece4ff02abcb5bc8a5bfc8072faa9b87c6e23589cb1157252e3a74767eb176105347271e345afa45c23eeda82815c57e3e24673476129120567eb7c6b40f4db4b3c6d5aad6b4fb00515828fba455c9294785c05caf14031c5f5b11cae412c13e6134e1ef010cccaf14360809ec33a9cd27adf385c5ad6ad342eb1bbca650da3abd266ab4148745013da92ac8f9633208a00bb6ab9bd4fbf59e421d9306e75c80d529131a497a526723eb13efb8528fd26df43c320529e5402a1cacc6a43ab974d19d4858df218f9e4795a9989e504095d1e2f2a33e3c397a60eeafb89852a601ef9ba8d4d01176c21f66d6ebe01b051efc80e661a22160f785f4b81260e33fefef1bdc35ef6c0023195078cc14e587bf1928b3f3abc86ccbd45e43d89b738ec3ec0d329fc2f338b1f2e2de3f7983c810867d4db0f5dfab37660d3a4cca441a258772511e607561eb67f3b54d1bbd12002cd9a6de1939bd1bf8920910cf29deb3309fc544106ab144e94e59300205aa35f1bb8162ee053750334e950a00dc80a8f16574c429dc809fca6e54aaf72541e8527fe70223b2b2ed0d6e4b2c7f37fc4e810f6ef118da5863ff9d744111a75290d99c7f1efb5565e9028c7e9cd25606a28582650002a657931ca33cfffffd85eb2654093de072ed6431d50af2e5c4460b8f64bc4a88cc2836d2ddebd2f78bc8d2d1e84dc07fd49b5b9cbef257409a02303bfd0120e541f5c6c3c3dda6f0a7b20eb95578bcd6f7e644d89cc0bb29e6fa4fa5b5e728b64326c9f1824066676a99d0d0ea60889471e1fd55d6efd23b50c7aaa3fb66b9c514715d3793b5493956f54ee62ea80f90cd1db389dbd601684d26f3d185a89838aaf610a43e64263459bfed2b435e45bc10b71754c2d1fc3f94a498968de9680fd45348e16f9353f81cf375d93201e570b89fe8643304cee498887a2e7012bdd6ca4d0b1f05f1dc66b851cbc1ab9dcb53969fbad7263759da71445dab6a3cda415e229c993249529a907b05bc07f42996bfba7c10b8e1673bcc85291998f09f0947f804344c11e7e777f561d451e459a42958b41097962dbae66c345ecbbc66e4667c6f129e2a9cf1a9d72e4bd0dccdbfdc506d28ee0a96b00d4f8aeca650e3667ff5182f8068635a795f1d9a9280247512cf8afae56d9061812467139b3ae2045c880b187636ace0bfb0d203c7608cdb4de09999318fe286fa733e9bea9e0338705fd0ec4a97d8134e6c8caa203c67f27e645bff2e8f0dc473d7e8d08d0227c0a5f7f5735088da2ee48ce4c8ec7d6155dabc3bbf61c60b591dd9f1554b5442f892f22e62f2a3a65e9162e81c967c08d967d899665053cb93974c36956408793cf21b588b468122afd36e2cc9bdc10e13454449a7b11d2051fbd925c74a61b2208c950de28a8e8067fe5838de6a86a8c7ce23097b5993b006a7d5b224962bf0a09dd1631cd96db2a44173c08290ee634f4ac8172479c719acee06b338b3e5a66a5a22538653f590a8e14b76d0bcb97a4ab0d3aa385191ea95b904a41b33600de1c20706b861eaaa67cb02ffd495526879b7772e8009a10c73db4ab8e2a7d32d07dadf4a40d051d01a43d23d3e2e154247fc5e23abef044db2b09311f6bf9f805e8fba3cf700a73b765967af99f819a300c2491a88426c673de44dcdbb6b07fa6220c612853b7e2ac17d2a05707c091d57ba68e08687f342b6714517819d94aef22d9affb0fdff363897f54e88cab31b65114bcb8ef07f4c57814366c6b1841303ccf386d8567bd4cf3777a9b8d7e43af2d3c839f0ea27b212e7801fd8313f5ed59ca611db64bfcda11bc8e954e4f3dd04f5d2673937e24ffe200fad42bbc748b840f2f555b08cfd601b56a1032d2a87743fa31f6a30b512e309de520ebc8592d470b1a3ccfdd72b956377c067093f7aae2c237d1a46e3c41f9539b5007c0209ea06fccb8385310ec41c81c4d1d23a26611f2520ebb4078eb36f56960bd3d1f0d237ea9645fea895268891a0b0f261efc6a494a9f878afbe54d0a109833f67a5b8806e8cf8c860f93e0a0e13903f0d5d46d06682c802fa53e81283217959b0b52ad20900e2c2de93aba2c7d5318942f3e3b272ecde430d3a262a1de80a4fc6f16ffcfc8c93af710ccf238d83c4abf74c9a9e1d5cf31e59640eb6778c87e6cb775f28312b5fa9c1de5ffa429d7ef7d5ac07e3167403921969feb8529708cc245009d089162d7afb816123dee6cbcc029d25f798dead1072cd5580bcaa71445ab6c0254ad3c19983ce902aeda71e317c1b7a119692b60726aed78158ce616c3ce205c5535b69bb31c4248369d21d35ae73f93db62903d05c01b7f075d01eef1094583735e61c0fdb5d00ef521d6fd538bc70c4c3d39b9c36f37973b8db00ab9ed681d112969dfc48adc15ca32b17d1fe087eb2a6328d4108c40bf6142ff41c5ed99723174cd09c72cfb84a0e6d3082f608405dc2f222b4134e358b86cda9140bb586b00fe8eca6ef20d14beccf1277442c288ccac25a267efa54ac59c9faed551e3f495c1e07e636f93cce3ff44d84f14f65690719161bb0f07c12e275deb78d969e754b5005c96a5c8cc5f0ea2c31bbf5b2a1b58485ae076e90c629888a9a792d3ecf0060cab9a13f4745bb73f34ffce5eae59e74dd1ed1beafa2151d5e421b74617269296a6efa487b8b56753e2981bf291d6ee7d1672d0a7a69413fc7ea07712cf878e75a13a4a4591db662f9fc8bdf11edb0b390d152a7a27d7a8113655d8d691ae1ba065fcf03b7f5605710471379c737c373274b6dfe9f325c48b34568167b9ccb24d1ed46161abd1709042dcc9d23aa27ff9964fcab5f9af34827afb436a13703a1db2d4be15a96ee9435d3889019877db1bc1828303cde09f4970d4f75d3985792c9ecdb29112ee289975c9cec2c4ae157bf301644dfd914e3b9c58d895f2281740363df9c305af1356af499813210248023489f8ae5fd67c4b4705aca15b1d3a5edeaefb4f1a147c2592e55f5b6ce2fe081c27f7f6c2855f0f7613aac6d5dedece4f77c15b0bda6ee13e254a1c4479053c1b5330439004edffc891aa35057f5b8263abf0593a9b7fb47c809a75a43ba3dc0b0f8e4261cd808e5e0c3f00be95565cdea46e1cb6887ea0a45fcdd85f6866cfa73bc4739ce33f4ece68af4de4f93501d5c2a0b889f4b8d688fca4ce1db5ccb17677944f1fa934b5afbd7560b1882df4855c20f1bfa6e3d858f42397e7cd09a0be6bc423fcf148154d09a0036ecc0101c978e0b123b6f9134be27c3969e14b57e4d494b798dd57ba8e7b808f46dc18863e31bab8bf76629c355ef0fc81fca265b94634ae92cd6cab7337d8753793fc505cef9e5804d231a43bac6386ccecf8c91786987a8f6972bb2666b554dcaf9d3559af64cd954df61e0073698038ca8025a997741f46652779334e066bf39a25da9a484d42132556c6ade14f2180e64fd8a80d48261bf9e9e018dd4cd8579e33ed15df531f843d940528a208b8912391f417e745b3bcaf4b35474e4793216e0c0dd4299fd33baf3815faebdc1e45c6e55eb742b18c7fc6ac2c905ac251187e35853bebc1c97ada0130b3f375af5148ebdf0be983756141c35969695e5169e92696bb57425d6b9b620ac7efcf73ee9e1a788f061dc1759ae185b38838b60b340e7e0ba6e5968ccc2b9166d67a871701dd30871e41b4de6607e59a3a8f65380bb214ded3d5c03dd11c8ed7dad6be69c127f9d2b688ad7a2625ed628be536200f6798944b907b8d226b38063b45f02397255c73232f363ba372312ee37904006554401aa07e7a89218c339d7e86b753932f4356112fe1c4a96dc9fc201c40a8125448dca11c1160b804891ce60a63b848db043f1dbbdd482084df5375121c4ee0b551b2740818ba8373e21e0b8c6a870827454903f5ca7ae62a4669f35d0b78f991ec31b022f76cac70ecea558683288be2398f36cbd54abedac6871b5e547d8a909b8a955e5d4c778715f566660f0b752dccf69d5bf1ccdf5196c57f18df557c910ebd44a3a5df2d0fe2b7881a3d7a29a08d54abddb0f99f58781c3e16152478154f51678fef77f1eb1b4a17a8cdfc38081ef3d564f82122c8731da5c5fcacfde09bb89d58643beed51a48285de9bef5370c70a82e82fa06f2d3fbb356eaa1ed3d7dcaa3518961d03b75b32a6950059c20f649f3ec9cb31a69ee522d4e21c24ed4ae1f125305938fd54b0bb07680763107858772773ed13619fa452d6290b95ccd8dce9900be3406c19f3be3054dc6447d222656f23196f6eee3d5d0cab330f221eed652d82c9970c1ba4aeee88e9f17258fd792aeb2b6072f394d214e56959b90e0b0fcfd13c73334cbeb60ff24160a002b3c31a379303f61e462ccc960abf6526709084458362cd6f3a4df5898734dfb535298d4a20fcfca989479a7f268d8cdcd2303727981f75d1f28e5711e17669b0a6e2a7c41119204772af7ff69512b8bc6b0b9e38a963dab5a45d98fc357cd7bc7b33801d00b3ed3def08471c6c453a7cb8df31eaa26c3858fc7f44bda73a09b57002e69ae26365e4638db85a945d3c3bf1300a0d0ab7cc1663e87ce0be7851f68fd1e9531baeac6d78a8238ca86b9e0a4fbf932d8b3b86e96af3a72e23e757a713a3fb3c0f7e6a111d48a1b7ae329b05510dca46d74bbbaeebac3b6edb7db59c9f980d9c3125aaa8dbd76262da7a6564322081fac9e6e6dec84d1d20c3930e33046763a41a0c6d5f371c8660928fdbd7731c183ca4cb33aab52f4453168bbb60550ca4f976ff5b83106df31708e02c00c5ed09e6de2cf69940eb1e043c8ac38bb5b7f8e10d13f859ab226e722201cb49acefe6abb2411fba17014dfb057501f3cc1392a90376c207f6dbcfb3896378669eb5431b7f747398e98d3ee2b805c6b3458cc4e2c00f7ba05ef1f7718174873b8cf53e94228fed12407106263ca77dc37a67de3ff09aaf71720c6bd17cb7303721a9a469c143a6ef4d9a17683746923203b0728521ec4636939b22b6298d8867ddb232213d54e48c3ddf822d0d4b3e07183d142230715fb6673360708c4f53f3882af8c1238c75ceba8e898290855f223f4d6e0eccbad1aca1cebb54aae80e80e7f40cc70bd8784d22b3bf4ed9085d49614fc6c936af0b0d546afa955e1ef968e316e220195d93e90bf251986d4e376898b4209f0bac52bf1dabec8e6c4ad3fd5a4eca7afe40881c8ea9bf4899373e90a54f4602176d7a88b895994826cf853872c3546723dc1034e58565635711f5312b848240386d77e2ba29e297d74caa9caa152c8d8f815951cfaf223f22a8b5cfa5f3c050ee99a73a16ac6b1edd8a2595184d05eec3200c8ff28a0710ea37e497215b0a4ae4d0561f32b45c97012228256e2420c23c163db269df2682ae10eecf808221205c9caec9f71b7f2f077bfbdae81eec72174c5f5fb303034b3e03872e96c2c839fda479003e8eff96685622c8abfc42cd954e571f610fd84fbc1f9947b4f87b575bba7cb40b21680e2284715952ebbf201149abe52a4c6a8d607d09581412ea4bd3939f580abf085b11153449f39d3ccdc78901bb637bd6dd411171a1e6a8baa29f1d8332ac5e23ddbdb2a4501a767a6bac85caa8e20d139bf4cbd41b8bee62cb8e6ccb3d5cefff81a1bccbe4584a21c11dd2aad0e8ece61797f190b68db7cf01442077ccdf41fe60b26dcbc0d6d9709a563c6491a4dd7ad53a37f605bfd0ac5f5d2c43486d38467a853a9e0b186ff8b6e3060c2af78c1f798c77daea7f08ea26963080531e1e80d36d1ae50071bc67fbc3b32da2787abfcbe9de3da5eaf781f51879ab082c2a0823a81286755eeb052ac804ace6522591b4a7283ed2a0ba5f6ba046aacba2727465f4c1af2641f796223236c7c5d1ee9a27a2b6ae22fb1cb25df64843827229eaa441a795cf30b44933afd91442c1ccb7692ac0f0f7df8a3f9fb4d729c8aa80bef28898fe09d40ec187711eacf6d4046dc2a84882b08866cf767270da60de2406d4cf15c13788602348f59e961a49e6f9cf4bcc31184167178cc6463719c0b2962a9c9f4475d73be3e2010d8744b130d13b359d9a6884621d3fbf8c06782192a43569a1682d4281f10ccda8d40acc943ca376a2b40cd75264b774465626ea91a986eb39d044df9f7d2c8536a32c4b1b57dd5dfbcc8dfb6625857918dd60aa07a4ae15fd6cfee50dfa16eac51f83c7dc35bd9637f353c05766ead975976430c5f4435b0cd7ce8ff33ff7dfd58a8d0e1a98bf01a075dde91d8f84d0951fb2b2b422659fda8d6f886b0b1b6b61301d125697fa8910265d84ed4eba0c851f6f36946192aad4c92ed85e3bf25ee315bb6b6b8f835738956a005cdae96bbe85ec3af5d435cfc3e4baa589f44445c8aa6378f01d365a63ac645c1d46a54eebf7d6cf67808b822b02d68fc5f18606245c04e1a60571f234e898825a1c1103cabef634d5b616e42236a91ea8b509f020ebe2c63e818b08748b2568dec25f3ca47401c98d0b357150469d5361ef96828bc0a09c0d6d41ee3759c340c7eb5a77730490ae4edc8cac9cc28f5571d42392ee496b359cd84040d99906383c0933fda0f6b1f6bd1e9788a5479d6de4456f6cfdf2041041a51ea11d557d0752ef351f1372bdf00cea35dd3cfd55e5c9848ec2dec58dbc420ed0fd5db2b353bb726f963817f41f0e63e22e748ed17c486d33c967ea9ed646d8f478c886539bd8d7f998265afa49227da3ffd77a3ba1164c0c4cd79e5137abb7bff2f6d62f442ef917c678eaa61239ce8bd7ab85bc7375ee825e0f13b08bad90d3c19e053771065ec7fe35110eafa4242aec559058d664a3e2fa318892bb698e73e0aca1dbc3bc7dffed8fd07466064f9ae293a3e02c5b586b8942df3cc8a453347a1a3345753b1dbfac958e1e347d356c2ee04712e3cab98be3b6a2181d52aa726fb98f6c949ae990428e53438efffd37a2b3fd6d490b5b6d9ac54c0179882befe533637646c44278a5fb15e9d4a7fac162ee0dc8220097886804c5491a5a2c560988655d2ed9b0053438e34cb509707cd2a273994baa93c2ed21c36b5c4c63a4eaeda03399b3ea6b553a72af44c487fed6a0df65ef02a1cbd43707dd6abc963a2c9bf817b480495d9bbc831446db82c8a0fec28b5038f90f2a62365493992c27fd070c7e3fb10984c9b15b587b12c2c8a6cd77a46de25448a26f4f22a0cf6d1f75226d4212c5286650c440a2cf0275111cc5721aa7cfc38620242f732bed15182d5e64e89e5ed656a26322d350d6f85c5afe6a53d49edee1cdd0854710f61da48ce068aefbf264db81e9061dddadf0e034388da993e1940d7cfc26f89053dd9a8738ae33aa810cda138e40f90406a170d1f130bf137e41405687d4ed9133e981549d562d7498573a76894ccb30b1395ca5145e33730ad1094c782a07e2c1801319d501a9ad82cf626d814010dcc368d7f7cb297ee5951d7c6811bad13dbd84c9f5bdf867df6b1a559fc8fb5e8011bf9c9384aa7b757955050b1c8ab302659bfd10e0e931a7ab784d4e8639d69682c6293ebd66cacf2696a7be50b019de47afbddd452e678e333705a4165ea7032393816755a5298ac3e08ce8ed83477a78373805a5e293a6e82b71d6c5ea80e2570f19049b29bc7c6ff5e5538168bc2c4c8af33fc02dcb2783e73764e7ad0c2f336d17c326d78d56eeab5909eb69c4e66b5c05c1c8017f07a1874bc3d79e1871466f70a90ec857b346b1139aec549b79ac75f84c3e5aa44f491406b0ec6508c27b2f42a11b1935f870462d203d16bce04be7e8651684fe6e0846bcdbc1be87faf95f1cca28e975ba40ea8b0386e3650e4c2304436a0ba94af2384e4af7a0aac769038b55093f178ed31c57484944da66d002c036e877a05c154716e7593c603ae3cb34d4c691e13118bdcd8d8ff2e78497b1217aa4be94e58f3805400a5a84a3f005701b47d42db799343eb0de2697269456181a6b0d6a772501f0bc883d6d586e0d3084b893e8e4f1b66e8745ff5bd247f7ce40b8461e15663f28449587e09e3373ab8d9d753eb0e25107042b8e78b65541c2cd5dd6b9fedc85acc73b11e420e7fc0558d9f6f3ccc4646a9deaa293824da9a302de9cff9111c00b54f6e2ef6ce040cb8a09b15884a67dc96dc46ffc4a8653872e48ded63b78ca134bb72997212d98b98f856ea75f60bb1b7f4c4c623fb83c150cdc17333ee9f63f309ef366802e9d834ce5dd0f8a4c46508204a9399e0d299484604c6ce9e7df584a27ea515802d17fa76f717fcab74fe22b7419897622c646cd042b8fc55b33a83d64b0359d4a7717f74ec08d66717521759377502ca835f6bf0b6751340cbebc933f69ee65458f23c002b6c7554fcf9061d7961d9a227bcc19852d420e18c1370e120dde31395ba7e866719e0a74bbdb42595f64989794c576cb28eb3944b562894bca9d2e1c6f549cd89c88767f040fefa4455fa2033f016d5919073cccd651415124f408900a6d81669810c80f1e9f77f5fcfcd679f2d87e268b4065355c6200bcc6f0f04d04a5a00babe34214e2e57c122a282a0718accb6f30315d60b3f30aa9335434f7ae8555424d7b8a18b90e4915867519319f7c0a60d5a86b14b6576f64111e26d44b9b9b523d170ab4dc842cbdcc6e450af8dfa45fb9a906df159dc1c624212c62fd6f1b09060278ecdc467d549bb63208c9a41fdf1268c4a7c060a79a2de9380f36eb28481dd16da8c67f0edf82c09ed3604ca0c97d089d0abb7b7e93b4bfe6566fe349cfb7e6397853ae91a6e9d8810056bcc8acc067a4074d2e3853cff801c9dea341477640fb8150ac65f3e90dabd3a9570cff95ea6d7d8bece20caf652e0034107d3dada9ffbd4e569d7cebd7a62742c8b5d6f454e546b58ae0623a4d795a695a4abd1ef0856e3fffc81c250030d7c83631e9c86ffd35a5a3832f84657144d401cde78bee8f173942c6cd6c5c8f3173a80792dd9105aa8692686735225aaa66b739d7cac8fe24dcd845b3fa23d0fd9664b88f9032a70e503d0ae119587827801c81ce48057ca87a1de040e5287350203fbd64aa97b5978f8bd0f843b18dd23d2a67b72911e0320c947b2ea392fc13978c388ce6adeeba46cbc1f8a714b23fd9c0e912b1157fb481da3cf59930615e3d016e81c485e5d533971b07132091b6a09dbfb2eca5dacd8024e4b023499c952490f4d6f844ad48ff3344d2d7d4d71c39a43bfa014012e9a83a277120cc8a2c6b907c75bc14a6a5b02f3f46408042628440c725cd8c431a3e9025a27c3949d001f65ee6f5783e7c05e737ffded33adbf327bd15fcee4cf63dab8badc613f561c15ca3498396e09f73e0a011acc958a89a8cd970b10444f4de915fabda9e2206e3e6428d621404dd5f74b00e334d1b2489d0c6d0582d2c078ceb0000aeba41828789e73640ac90ed09bb7682e9f613684fbe86dff3bb2925fd6d135a908d3d47c02bff182f55fea9f5058158fe0b2704c754e95923f612349d79265f02fff1b0edd9b5d7e4091fe7bb2eaa22134b3fee6059a823f9bb9ae731642a1ba151cda79c3206794ef3c41cb5e109dd175216a84344b2427687e964bf308b292208c14e86fc3ce4ab8a2feeacc6ac9c204b3378bc00a970e9390da72f2161a5a0888d4e3398273d9d8582d7c7131586f7ab068a3a8c748ac22cdfa8625ad04a75150d24826890459db4269ed5d1a37561ba184e65e20232a3b9c7f566908bfc85d7d0e7a5b94618cf8b8153f89f075e9088926cf22e8ea71a24de8870dbfbe6fb70e7d83d058b660f44d328c751e94d3308b2fb2734b58fab9f742c8ae5bb809337fbc2dc274a0b515d18dd2356abc53e89b42babaada628bd9f63ea639de52c29bc2076c837e85e03f0a375ee5daec15f9f2273db3619d3390f0560238155f1687aa89882fd33e7c8a1ef6a6cb8ea68dda8822be3c7f35f97fd44b5de09f262da6bb2e36af7803f753d93dc27306704081653d942596c2e5849b86c39c50a0fd39e3d82fe812251e4f28a25c4d5ad09da0f1c692a15de2cbc33deacf4c04cee9c0d2d6394c6356f25b0197545103a4a10b4fccbaa39da66c6b01cf4261d3e88b6101c632ca257bfee72b8ddf748953b3c96c4cb43c5752bb13a1402c0e991546b610c8cd49062a60f75e1d82326bcc3fd3af50bff57145195f9731be9672d2092d0d5b4dff91c5a4e74e74b31bab792f5054f21971d99d86b431c71e9b945362bfcb0226c946211ca355415589e4ad9ff2573239bba338323871f9e2d9d18c46e1aeec9d35c330e7a1e1f798b4659572e9358db23925bb84377193036df051d328f801511c1f8692f8e26722ee9095bf2f75c92c38230c1bb220ab26ca84ca879553f93073eca577efe08589a312c30368c2b628e7f4bd499e9eee2c4578987bb374fb0bf95f46cf9199ba0e1a60089136f981e1b1c0278f599388ae78569a255755472953b3c15e5edd2a8ec44d29b09b4a8d99ac0d2f271ef308142bdd3df0e6a4d9af11cec930efd815885bfdf251220ad51e304b35fde3c8b0443c345fd0b3782f61805cd8726250949eff1a1d4e43e055f25f6eef4b233909d033463c3cce1deba76adf4325b5861c8eb920ee7500e7c7e619ee85a959c97b219d87d1e38d3befe4c5df3ce912485751dc28456b9c17f6849a0f35324444c398d6b62b58645ba33e602710a38155614ae813c437192b5d23ad7216944a6f892a0f1b9ff561015a421064b397c2eb20086a7e3ac8db7f88d7af3aee4ca12a4bf8f6374da1b8455edff1595c2c9f349658802b2c8dfa22727c8ee0393cfdf25940b464c4e684973b4bf57df01e4bee73e9475293f0e7f0e1fe19557548a08460c85cea1c6d756b82b56bf2bb9a373a38600a9b3047be949905a24054f8e2ef52d0058d78c4426039742d2042e5828a6ae108af051c558b215381e14c312574de3c4182c48a72cafa20c409a2e10cdda7a8781b7b632e4e5d87b5b7300bf3f753f19a4fa2c9b035ff2f797676ef82947c540b1cc539f2575d7cf29f272c449ac3eab74ee4e0144d907da1b7935290ce763713700337929545d9601bafc2eb9b8f423549efaf03ac904efce8333f368766f0cb8a01d3fc36bba712e363ca362019f7a66680765f9f21a165553ac589bd4c61e5cebf7e675bcc5ea6c70f3139cfa474b8cccd6f0d1893f2d9483498ced95c89c68badee96b730562f5d1720f8242e1995e588c324ef6ce878d13df70bb804e758f55553622912dc904bd8a7603512e9c0e024a4c6bb5a6af105c20f700a373abdfca53c806ee6bf716143884421e168251add1d3fd3f8f2f11052f9e1f57a1943c745848fc6514f79582a779beb9f62887eb38f6b3a7799d7f10b3628ebba01f858e31c9e14d953b675a830cb79c953b75d9044866c809a661cb06ce4103acbe14f729153cb51677b5e410d118204271a364e7e841c35bbf17da904110d114e7e449d76918e3641aa589143be4d73ce174849d7c32f4ca415e2bad7ff206ac73984cb3a5edc9e8068f1549f885c8adcca79ac3bb621838138fecfa8df5138123d28281c81c99ec6edd3bebfd022093279fd7b30db4b13f336a2f5a402f4b16c73da0e5ee78c1b41867cbc894d69ba5740ab64e0bbc771c3e7170a6c2608889a2d6c8c74b5bc0038dccedc9282a1c2e793263d19acc11465ad92593eb6e471e0f8ca08d587e8d92e49c4da36503ec38fa777edd128ee3e927e1aa4d474ecaf3481196685722ae20c33e796d32852ea721c4023c496d4c92365241922b027bd0867e5d9fcd702361f1c95e864d3d9a5694637a7720f9aeb582b66751275a0a9eae965f6aabc6066fdc19b78ea388400801ea25cfc63cd90fa77f438fa279614db927fa925b0131748a97ef095549c93d49e9aa35ef3fcc55b729a060450e05c945b1a9cfd48416e07b1319c05459d1cc821a70b9dbf331b707bd349a0885dcf2059934ce6292c91ac5db09a34077b2737859f2c250c18badbb368f087ccb985a764b88d09a002aaf587430c54d2f4f42199ed858707b5e04da59afd48a35a635e5fa2188f2efa3c3e69be29f8d8f6bf532b8e38b1241565ec84481513da227b5c70f4fa22bdc820dd7cd533ac960de92065cb35ca7eb9b51dbbb8813e12c2ec1de10af29dca8bc8ace92009a3bd654076569cb2ea6bf52f7f58ec89e24a943d4c098d235a05a3b847c016ef1ebdab0a2dc3dded8ed6fa140abc8d1a4d70585d9d43c1592ac5d3bd6d4086208061cb1b6fdbb2e59a5e9c383b8beae6ca0bdad93ac62617d503ec67845a7f1c94804e59a22318020e16dbd7e7928d94d6dd697b68aad6b8628b79853451bf376bf60e5dda106296afb760c0f2573792d24d2d0b17a46008b06b83b7375bb54ff98a90a9c566543c9bfe83bdfd3d00163dd63cfc41fdf6b96bbd969819d72435e2bcde0f45988d77265cfc84e518c19b377ae3d100d3f871ff6331c8d63caa003f61b1ad04ae8cef8838022086a9089a5cc288ec8077d9634c642456220ec3d4978068386472f8c45a027c89838e7c663d177b236fa2577f2f85d867558208962096fa0d37959b4d678a2c06482e21b1554847fc7479e08ecee96c791c89b508e6415bd7c1930cbce0c530a012f2313e1b9eb570efbb7bb72fd21f777cc8b96c45487d22731a9b7fc7407fb646fc3aa63de28d8fc3bdfdd1c012013eeb9e97b4feba7f7da58d47fff75cab4043785faf0740ded84e82baf4eccc7e6dede286600a30b7de329ce372cb38f5654299ee90d269b279096005dfe5822117b94751d2e92f27e722da7a0ebf40b74da0c12edd753886b24b952b82511ab57c83114c87058eea14a30b7acc46b88efcae9fb5b59c44a3259fcaabde6173de3e3dd4fc93243fe6da56a5f03e72edae64eb7ac2602bf46bd90bc27453f7e11fd44013c9e088c2519e699cc605e627abd6a434d82b5e551818ace1c1c92925758c298c3272c8cbf620f36627f1370aba8701060b5ba80609d52a772eebb370347924b451597eec6a2b0c2138e1e2b2729aacad6dab28ac199feda94a8d3b0054a6ef97d57e8a0867f5ec770b220819909e7024e4670afc5e10fcae227066526f4aa5ff5dc877d7d2c1466dd75eb493ab701ee38962486d5130895fc9ee63f16bf584b01309a588820e35911f34858986e5374cc20bbf0c9b84a5f1ec40d84d961f7152df56f826f82075694defe53e1e806394f29425aefb52fb7272434d28f8144c38f22efc8a1192f053ca0eb549794368ec2c85ff9d3534427619086051a9eb9a1ad8c0c4440c7e5a6430faf33b837e14e4a83b6a2a1ad8d5981be1ac566877af970c271c1042137b1e2c43bb3b6d38e20a5ec0fee5fac078ad2559d93e11fe34784acf431eeac9365aa9851a1acea25aa5e19b0dae77d7f0b599867d631803dbdbddf04ec2fdb2dd6f83cf45191253dc1312d154c3a18f60fcea3d5a355c3424d88aa4ecc31e4a51820d387a19274b1ba8e9fc1fc594e349880df0487a5ebab1236c8c209efa51b1ea51ef098764c43d81f98d11b1845ca19ca993669948a7b872424f35b63582aac49cf0448c74b7496d53e0c41ca02001df0fd5c23d6918ec1442522533d01474c8aac16416cecd89957880148f3ce7c5457748c2ff767e487e1974672d373c5f65a446dec4c140898441fca83d0ee2a456d61b73ac455abd13f2a8f2afdae514005efb6805e22efcd75c32ed5066b0a1571cc3be60a6d90d43227ebc2696ce607b33987a0505ebf83ca2f12d873a70e5fd095c4d1f2fa1f95cfe33dba4c621c6a920366c9e15097452a6d963424686bccc3e39ed47e45fdc4c3144233389c88f9f28a69868f43547ffa018a18ad962a21477849fd1b50b3f63ccb1f04cbf108d84592d6eb2ab4f9a38e91afb8f5313eec537ac53fcceb3d03cb3da9b64dc8e329e55305465e62f5ab574e4dac841596ccec986a467493a19def6d4268e0a9e26d7fa2cbb5ce84d488e0cdd47dd053708e58a083e1948d735a0fb5654b2f2dfd74aa938c9be36fc7f9a5d65039083d5ba57622b74f006b711cb55b25b2830857093a89fb4655009becef2cd22a461c6e8879719959eed24b1bf321eae685b4c6e2393cf4548b8e4d385a76427b91294c5b824eda30ad8e622a913725a7d46278821f4ad549ab7be7e0d48d3f13a2f2a321b5516100fc2057943a67421dafa97cca446a82dfdaaaae875ce2f835dd484c8432c9da440880d1e341f111c6111138969a70a7b6a0a5cb5b343b1386a437c7bd3cae381483031e611b570fde1b415d1b0efba697eb9ccf51182cf399934851444aaf9a5f689007b7985ddd0617c26888986b496b4dbd1a1bb6257b250b7269523de42b71c6ff91ef982ff74d244874c3e47d4b5bac8d47a9c5f08bfb297d439a91beda7372784f67d9884c0693ff92168537df8441aef74bd2540f2ffd161532bea078a143c1724a80af2f073d0b83c61b843120f86e3bcc046c263097f971c3524e256dd5c7979082a7ef1f998b51fc8a5850cf6f53ab63acccff87b69d23b9c7c3c099765649a9d9fb27fcb51e19af7300b5aa0be7d24ebe6c1a20c460b72569657c87fd55a976bd53b92004e3d1777478a0d94d6e79618aeab5a6aae8de95a8f4c72acbef4030021a0b215b0462fed7b638df50a87030bdcdf0932529c2d31b680f931933ad403117e1dab55d7aa32560f4b2256e8fb38ebb97651b4cfa032aa3a295cd33fe16810e4d0d7005a44a707e89a8d4d3cb201e5eebd1f479aebf91e75f48b00a206a912b2f89ebab75f59cbd4cf63b2866fe48b310cc779865da9e5cd715cff0b28a2c2a4c31d77e01d90a7aeaa877cbbee8a880f89c6ec4328c2d2f8bbcb5e6904ac00af54e8b7424d1262499284e819fc7d13b5639b7beb4621f48b4a619ac88ee1d84a13554bee17d0e58d1710e661a8cc2c79a1c5858ce1cb3629217410b66576cd3e5b66a2b9091ab12384c1408cc94bb18a1c4e017ae87fdefdd9d0d99b33db917ac8cefc59ec8f1c4492a7867f8b8a5f16758b891b93d7ad43113ad213563679c3f6be604f54b768729e3897364597c8ee7806098e51e8b401fae82baad3c34cde9121a221c6d94cfaf5985a8ed8d2ac61abf864e51701ab13b71c8224c1c06b933a28f9640a1bfe1658f327d5b83e5daaf28a2c262dd324f020eb5f3e4fef0c2ca882375786cc1021a82a86f1f3f3a1e04a620da0ccfc9bfd4fa41c17644566af28c0f3f6ba6a25d7f7811c2f1ce325a221bc2f75c7ca67f2c893bb0d05e1466daaba23970dea782a9e6f1abab89d57a4ecdcf738120f38e597c650f79517f4a5ca5ca49dead28011973866227f4f69ff185d9e766725bc6cda403b533df2ec08e0b023cbbeb0db6f720052c9c7d78a9ae42bc95356e19646301ed7af525bb4fe1934366e0e21aee02770050213187ae64f53422b79003cd2de1210e0513b5cfbffa2fe9f35eabc748c5d90846bd245fc6e30d21b506c2c195eae37b785b0f2d6739bbb146c36b06fbef5f102c25be0ad4126de9337fa4f571bf74a61706f1b3161f318d005410a5c73fab27f5e9430dce0915670876779030ece9c366d9df0ac0dd240f53d2b3ef9ef49298fc3aa93b56cc1ee3f25c3be05e2a8a656b2cbcba9f1b26ca939e1bf0f5e0532325177ba9199d5b4f07cc471cb590f873ec34aaa3f1da9af14739ccab0fea7a661a4474de067ba93eae9f0afb1feebab4839b1bccab5d6729f53776037f8b62a96c1faf8c7c5cf3a5c9aa8c10bc87212205a5d6702bd0b0833d82d6f03b27c5ccbe97bbede27569d32de528fa7ac8b75794e11170544c60cb124f95a46b1d8054cbc97ede4eaeb94c9165198049a15842d8d543a619866ef5e12c5826ef1396eb1dfaccb70ad5301c06b8bf0aa9ad7ee825e8f838a32075df119f8738b8a6a23d8904cb6058b1f3303ffa8f5a1064e995f5066aa3be73e8d04e2c4667e4946eff38ddf44f6e5887fe3f20d285ee2802c87e69f72ec89419e5395180c9d08d10e751ed26bbc0128a1ecf51818aeda890d7c1f5b07a46d2088654e0d4e1008045cdf7be6ca1a823538ac289c36b5ad5489e1edfa91a41332156bef160646382b787220914e92b1b7f7e1fb39bcef74d72aeb3a696a2c11648989a323a55af9a723392e76d5b695dcf08bc6b7089ac3ae70c483aea879ae8948118c1fe00b964ebfb600dbff6e97fda0acd737f52d8ebf27ffc8abcc35fb98140141ccb9a98611bcbaf3b874be0b479b44aca43862cef1be8eb0c8683b1598eb0712cd6c681b7ef2c1f7443a4bd878839b8900696912d21da651061fe5b6a11ec94503953889e934cdefdd57ee9a0669c7fefa6c1d22cd1e31454390d6e016e75ddaed5d904c4b7e036de1350a5cc24157f6dd4ca4c13bfb9ed3d87171f9de45b08769fb16045e60e4fced7e54dd2a421957a2574dc416bc578f948d1f6678e6f5c1579ce584100363788e4a44a5bb488cde8b5dcf7f33191de99c7c97f6769af304e40973cb9ad91dcbee171d2b5b1e29eb6640f2cac27407a904dcba349b9a945ba10a6a2b3652414b7191b88f117ff7b69ad782d31f504ef6fce54f74d5d030896b3c68f7ca24aadfa4ec5b594806f590a314c537d19af31f6ae688bc21f12647d7f8f54a44c1bf41809f873d859ceba52542b3d4f840ee2a31df48bfd8dc85aa13fcdde54d7aa66c23042780600172d40ebc7dcf71c73c6f22deb58e26d12bc2a66e1352924c71840ec8e00a2a49b7c2733f9562a1a8e1df69d19e14b8be143061d84c2418e4ad5656503167aef29092893e4142b265ef53a7e6bfef4b726f7a6becebd6634e90adf2d7a5792846b96bebf61a3f6d30090ec1fc381d344bc57b0fbb927394d7fbb741226042da6958411601f13313ac731a3722c3e373040a9b023c6d759f55d6c050ebb8493d0d44fb9bd5b57964e3c7da2d8b031a06ac11365e0baa75bccd5b632f01aa577b967d7d821a7b10d910043047644580cceab9430fce760742a512db9094aba085a6cda197a5ba492b7d52e5d7d7261ec9fb1a4b8b94e034388c8c3e322245147b6a5f6c8b5e975fe58079060bc4272c4b81522c9a032e800171e83bb5305f4fab2fcb8cd9934b2e4162aafb11c8e15a78c1e54798c930c8d51b89791adaedb23049232b2a204833e68084d43d8f836d6a5ed43ea476db6223901393ea92368f7d6b533fad8d374e92c009248a37d3fdeb18052816c82b0f0ac7671f67aa0e4d8cf7a30ee4541b1db9c017db8d801a54d4676fad64ed01593989bdf851266016294821712e5127d3ae07e156c407b9fecc4cbc06aa82f64b51c52d8c12e0dea1a3d3b2421ca5fa65ae69e62f83c5e6d53e87ef7c810833580baa480d30120a333fe1fcfb382357dd18651edb27372d4a33af19b77278bbc1502fbc03896e552ef698d8c89fe80dbdc115de15fc75dba2140b29b6404752e1cd9b7af2086d98011b532aca752943ca688acb59d9517c8e86784dcef3e836e4623a0250f56b0c97794506e7a7d0204a842da1f25390f89ca409cd7515f59c079396e9b667f356650658312c92b19f9a2382ec779b32cf483afa79407036e010299cd3b2566961ee24cb38dda96ad0f0e12905a6e79a7850f779c35c567f997f64242896410fc895db6dc522214801c3ff892d6a04f046430af9a0ae589041de48d1c97cab50139af459b0bdb10add184b6f60cbf5d5c7d94d6217d87e55ac8299eb30ead684d26ae54a05432349869c651c379308a4b1f2268c3531fb9570f144ae41171e3a5953a9c9bdfab031cf70b6e3488f01f97b8465bc2a0fa45deb9a2abed3487599027149a3183dd281ad62448338803178212425ad31eb5205b6f9b8c59b4f00034872077e6c768483622952ed51771f19251620a1ea7eac88e03b4d838be66ef6f68933bea6ef8e0dcd1ece5c700cbeb69565863f46f1e76703c0da615731e33d891f283d0c7860233bf576f57ad5f728665132f6fd22cc0eeffcdfae81c7d54e67de09cce171f5a6b30ca7853960e68cd38c49c71ab179de295a2e27727c992d0f10d34473f4366558facefecec21601703be221bf064e7e698d282266c93c5f7211ee1e0985ef297337ba32d59e091f319c79ca074208ec3e219cbfeeca882e174a29c9c46a45d5faca2f2d3ccbec789f02a2192c3a02b42d69c5af393156e14a1ceb7556c6c45e6f6dbf9d94678403d9da538e5c72286ef635d7b2d1da6760f065266b71f3e487061bc4b2a749e6c4a5bdd21f990ff2b7c57cf106331cbf0c2612d14dba48121ab480a287f80cbd07dcc681387139a190539a0eae39acb72eb7b8b94a6ed7ca8e4fe8ec051c42250312efd8f243e852e1313189d2d846b123a535367eea818798b262f113d1bf2ad092f7c2d4fd14a986b6388f828f33f53ea6dd9b79425d804d994d45d9b8f7f94c3e11018cdb7de42614897f38b945e9049c68c2e9f733e0616afaa5026c90b029a6600a9f3bc65b55cf1416dd7048153575c28c27c60255021105505c9ee89e136b14348f8d29f886e37bbce8996f3a3d9e17f3cc4a233501bdd3b76df968d0292333818a307b6d1d08c6091a3243c73b6008ae44929077aee257a514e96bdff73f7f7e9dc70539efcff0e3284c2deaeaa2edff004a6a84761e2f884cbad75482630d8d35a5ee1d9f9eac4a425bf0f0e0f152c2c0683de3a596379cb32e04449f5ec2feacc989c1cb2bf9008d068e0de9db7cf31b19cfaaf89279e526a96158347b8c5339ad903dc1e7c6ff28627fee02ffd2107ddb05bdce56d585ace340af48cef1daa81541095e846aef2c7d4febfcf0def68fa0c2b7cfc12387710ff981dd9a8a30b3b568aa619f518fa811ede2284074515b33db121861b96089eb93539105792e98fa0b4fc3d7df4599165a226e0657078b169a835200bb579d42ed1e4eaaeafc632447b2fa9df356baa55a30b10e1d8702eb259a39d030043efd5aaf815daf28ad124dddefd9d0ab336a0b4361c1f75a276173ce8c856fae59c94f932020dd3562d9344f677cf2dd77ee020644355bed1ea5188014fd66870ce3f9f165c224878818226d8051c6b74aa56fe0a275fa166115f96f2aedc28ec3e77c9209d89b322b299e1a5f41e3b8ceb805befab3160a94d35591091c5097bc36b611bb69b14662a1d434f2336902e8170819ab9f0e80c1038a19761388dcf798fe77791c788bfca9ddf2c4de1caa00923f23575d7cc34104ef3f794c45cc36ef13e07a942a06c21d2ea83546a0995ef1c9bf3526698cc1d9a12548a6e7ee313fb889a344c21640eb884756dd6b633be767bf3aaad79fd4471249ad6fc3ba4755d4c54edfff33f4a33a9581faf6ac0ab8b24db7a23e0f398a41793b525237a637fb97d6884515db1eba5767df919068d1ccbe6b611e649cc91fc732e8ef464202f53d2156896d4504402d5231d6644ac2ef2b6a0c26512b0eb0f7cf6da00447d6bae2a182a29737cf2c5e975fd1699736ea2fed12b75311f75f64ac982500e912b4c628f2d74fca9168e46f9583b27e542b4d0999010e48dad98435cb31d4e16f96402da2fed0daac46a318575440683b2575e18dc2044e9f26ce372ec9ffec27774e4132176ea3426b5c3645622256b973267a494d6ee1360ba80487a3cd0136ebbcf81baa119c5f3b39c2652dfc8a29d5e8910cc817a042bca5855cdc1ced46a35b4e396abdcb7c74ac3eea8717d86074f75fc67b1c2b9df9463eec18c0c221134e236c0fa563dd0b7fd4d1ef185485a600ee57aac1a7db2a6e55d1ab82b43aee59b6e80c454e07d0f4ed679c5ccac44a3cc9713137b7cede51c9d22665f9e6528324bd1ff671f48ed80951201e6b144e8dd12257bf324d4bd4f38109ce51719c17fe7671410a9d2904150d193d2003b33b5dbe74a5aaa826c7d632afcbe03376789f6174510cc5f034d3ada72dcf956c56af0d0a60a88a4a96f329a20b4dab95fab497938b702d382963ae86ec810f566440a9e1c9370e8054a29babccda89e96973bbbc0fb3611c0e2c944b14b07ae45c9a39f1c89bc2fc12b0055958ceb78be505da66ec4d6aa35a33b124c16b96494b3357d43a9eef67b0ebf021a40270299a4ecab97c8684fa9c1a0e284121ba2da07d59b3a3f031b38873829404bcd2596551eb15b0b7f8db4738d1854ede96200cb0f18a78894fde1fbb8ce76ca5beb255a009b9db3ff2340567c12e28d3eefd6a88bced3103af007bce223757690f1d5e92fb929bcb9b1680e8dbe39e3ab19c6de96fc36ba14d8e99f13aa36893a5b0fe3cc00725b72bf5cabb35df2f66abf60b810b5ddc663236333f1c2c44e8e7c23224b25b88fab76059c40dcbed30b31b2791befa77d8481cc7a56535fedb2a3ab125f6b4ac3cb22d85cbe7740c5b8d0906e9ca05679a30000edb38a556a35da707399ceec397c8815a6cbfcaa6ffda00e7a6b5af935b20839a97b834d530a71c2c46af3e2bc0fe3b6397f6a2e4461ce4a137f4feaf2a2dd09e952554dee6028871c6f4b1665c02e15765c490101d7a641971fca5ae7f493153e274511b7344a42eec2afbf0a2352b0ec84329df1559c3c649df2ca97988bdc0681c9b78fe3fa6947d9e87d8038c4f6482409087c9a78180ab6835efd8e66869d3dbf1804807a714e45e225850b415ca3ed0c5c0171793c403b94126473876bc59948afc1c82d28b51362b63378564f1c1662d8c1ee3c5424f2adbb90920deedd3dac5b23991000d1c0d68812a3ffdbb7f36923289bf0a8fa223b1f7c07b0cc20b373ba2b6d737629bff931781ec419663c6064b4ae5f3831f3f85e7e0978e80715dcf4def7fa81a044e0d2bfddaef3e890b0b54e331031888101a65f436a2f1187c70af62068835714e8a1bfd82f882ecae252a98f5cd1643978751cb0e0ef48218042b22561be8b25dc942a861b63150e96f0a4b24cee000bd3c6ba1eabd35d42f3c6a57c338110505577e153a3266571acd28ea27f3e1e0c7d30b84d373fcae14d5e4afb0b04edb0148b028f241e03b751ebaf648b0cbc3edc8cd736fa6ff79b89739167b95ef3e1cd766b69f9e6b3287104b87529a629a3ee17d2ed7cd148abda8cd2ced71843f616d1f9b72d2822c8210b1f207f031623a8f26f6b128259a03fce91ef18b56eed373b94cf7216ae9595f87d2e21fbf1134f4ac4ca45b1ff7cbfc3e979b6eb98de9a0a13472bfdc9bd97e7e6ea0342c6f485ca311e353ea257c7abf13a34bc4ae2e6dc48c069fb5c3368103ede0c6e8567c29beba7e36e58e89d4f41812d9af98f7c824dc7fe13430697c3ea827fbdfc037a11cc04029ee25a332db4bd1c3e7b71552f042c5b00ccb87b6db126abadbfc7814fcb6bde4857a0c1979955ce0980c1f3dd4e91178b3c7fe8d7a7c3acd50093bc616824967d3d6e32eb640f26d80fbbca4369e87190e0c2ea8f606ebf060abb4fccd26556394e48dfdaa0c423f5b5586fd5d9e882507f1a18794cf9179da7be310a5336cb14cebc6014942eb8221ab25454717b183cff907b810fcb1bd1c491911083a78971f2a62724994c8c548e5cd2d4826d643d1ed16ca32458ecd25276d12ca95cf22b1e365a388e0ac61090d76eddaebe3cdcd247627f7e7c078d6f04ab9ff11c11032021389e96a66bffade32dd4cd69e7bf2a06684893208279910d7024b92f9eb8f167aba2f0280c7b350104d92fd87496319f64e942dd42262cff0dd53b417882d7573d45b8eee0770219f9f4d72efd709ef1f1b317505ff4331b235dcafd774ffe2972fc0e20d60212df70e3da42378618a041c64f57dbac06ff72dd6c06134466124a2c54544e8bbdf59665da148af25ab9b2ed61cf159ce8b078a082d7a5cdecc14bb38df7eae1f141927deb0d5bb9b49b501caf4e503f55924a94811c629b824224c52355605d4a47cecef8f1b930b3979efcf5f8c0e37656bc2d9e9c25998557129b61c75914bc9683c9fb2ea05476fe093f1fbf330d1a9a8276389aa911c21ea7b8a40fe436425791b1a1cf3badd9aca6c2a3973810498d6d859c5c06b1e5bfbb1e2d321f515822d18912e227f9a10d07c053373720e4f62fc3333a532fa66bfb1075af5fb92d5f3eeea862541848044489683186c2c2e086bcd3e75762266f4b8b5e9dc220912cc188fd0215df177ac428de051a6b707af79c723b208e565277bc10b5249e8b02880e3f614c8168cb6cd09d41bb4bfad46e7afe746a0e4f44600675caee37f36581a987658c746d87756f24b46bb72292e26fa71efd7fa7cf695ee4892eb14ee9967d191da75cbc26118fe8c8da6adee1ac0a14cf900b1cd33bb8c34936a9626436ef497f8fd6280f0779b4d006edac9848c394a86439dff6076a791f4c82246c5c662d19c2c5984765af1480316c8e9d394d5c97eaf0cab9a0c58cdfb4f745490393731832665078c290f318304a452b5689cedff46d390e4e625366ce336ccc453e3a5cc37598637d796caa0aefee195a1c7e3b564047c57febc06a844ae1afe39ce411ccdff6e8f5e744b3bbad4b34477ad49456ef08187843af6fb5ee894c53155841b31018945b63765cbd03d332265307d01633c4e43d20961a3a254e81dec4ea4616105d65e41c1df3acceb0a6a74c94142c2ec4bb49ac9857263fb85663533c6a785153a8492bb3387fe89802baf2a33a70d9473a34b08300f58de362400decec0ea125f86b1fb7e52c3b9ae4e71d5259a762da3d79b6d37b810320ea1708474c382b7ecc9dd4b9656c925334e13aaec50bf00afec2fdda01368e72eb274e69a74c1c157a735a89a69730e3af22ff625c6e2d3cb92e4b08e7a1a208056289c0a7f82f8e27912a91f906854709cc9b361398c88b7e3c2a6626d208c84fdf2123821393dc28f306a344b741944c9dedb3d1a926157ba83fc71071ef4d73d1f15d5b342b943fe4de6ed8dd75f603513ffdd6d6754d87eb2d589cec2d538ef07fb04427dce11a22b8383d3bb6b8e42f9a0f3a984204922b85ad45967711374260e47a169ecf3f84354a3e880412cbff765d24c01d7a9798555b01aff9e9591a5500e735c071f8ac4f4438a94b423eb6c045e744b2e37852addd60f88cb3e56f7bb48350c6dbfcc3c4abca65da88841d786b34ee271ccd613caacc12d37aad650d5c06fef17deb96bde6c47be7250b0cef6e55d2badb7efd79366c5c6c8610f94889b6cca49251bf2031575b3057fe436d95031d71b03bc8acd8a8036c5f9e651d203af1c6c86e0d5c1696450b1ac10ebaa3e894f2783b19117b5ca128960290a860b4d2a3122860c6103c56f172a599f15a200c9b004643df6b9c94cc12dfb0320d11fc92bf9dc3188139d0f6e43f95d585f3676894eefb64247b344842dc9385746866f400cb9bd0dd8778e19a20015f7dc12b1d31a9109a619696cb6e44899308911224be39cf8217c13a1db9b87644adf7e5c3c49d52fd7071ffee7c511f3d60e5897ef9f5a8941e5ee0ab0e01fbb59d0c3ad98bd1ce511f863d0e5248ab16574735113e36486b8cba4d39f7b7c04a0db72fc29e8bc6bfdbc32690361f13012da293c9224c08dd5968d8d608cf5912fb60f4fb06ddae21c0d136fe6f39e405709418405a22dcb9edad080e23a94688e54045f4c248a7c951815ef6d47ec571a207ceadc235ca2b95dcca5a2a656952f9ec41cecd7f949e7d4bd492d30350163cf2cd52f01cb27b91cee88cb560999fd8a405a07de518b84833b6a31269042a968e7f22b879faa9ad672b266927e568477d4dc5af67239cc3fee80f00100a919b9d280f65f121e143902d4a7a68e86086257e8692247d7c71d755e36b2d12cdb1d3d491ebc2cb5f271f440b2e28e920b1a963043691f808e0e23204772bec358510f4e479f027dcb7a6ad1a942b361fd88d064c8e7b979d9a880012e8cff1fcea74c1ce2174ddea1d97772f054c0815d8e6699dc7c6c7a689441b2924111c91662aee4117d3472809cdacc2f919d1117853e010ee42d61c787920d3bad4fa535ae74811bb1d3e23640d7b8aab1ef26ca34b21698df4e60da3b18e58838a0809b87d7527587b9c12ff7ea9514282a662603cdbf5048511b42051f3aba6fb4334c380da598fdf6c29b054143e9e032ec7f21cda356a11ec39a6ec95956e0c5b0866319c6b2de10081c30e2a058a20d8479cb219983aedccf89f2b0aac1161de737077fa6cfde1051b48429a99609b867142056c388a1e05c716fa13193e18df80d599008f89074358a12103eb6b2450d2a8385f51eafa1acf1cd4f24f657acacf1b4c90edf8a7b8c25feda766a4e8f0a1db946e2782f681f14dac403a1add8046bf0d66da2c6857f7ac50f797e514e805c6cc0cb89857fbab3ed990bca521b308d9ee1494e8d136768f893e228af1193434fd0eaa65816608e97a51bb58659bbff81fa9a04ea4529a7b29f240ca4e20b0779dcd4a97701271cc9e72346d3873c7f7b2d14dd88a70fce82390a20dde72b7c3de946326b96d1c872e8a6234af73089856a061c212e3825eabae087c226e609c51b4e30e3535849f829d3dc37d8f6893f250ec1618a3b0cdb929daadcfe73fca9c07b28468ac73849e8b0f9f20b571e5566d8e8249b8f6be67c8bbae733735e2a613a4acf50b0245a3e451bfd4ee70ba32b9d9ea71ea75c21d6efee5890a8f49b5f758b9b5ebb5dfa96c339cbffbedc28d1f1581598b1f4fd0ae0c8e26d5189aa329cfdbf300abb784f0e5505694f8a2a9a417d73d2db67905b4f2a8b631697b53eb2cd33cc74e13f3f8e8ab2f68dec0f6a0c2a717c1c47af717c1b2594e8b510cf71b433e1496ed3b8f1d6e338efeec1b76f4222bf07ed713b02770ee444775968431f16907a9a097be4fc6915a1e6cb4db25f2f9ba4fd25540ad573ba7d3326ddc89c467cbe2581f26373af9b7830f061e57b0bcb92746426da73f7bca87f35502cfdb683178f8103083e7f529d1bd43ea850e4dddfe85a198ce9214e351834fe6d342bbeecc53b53d0210d0223c4146dcf29fe7cfb8a612d7ce4fbe14c80ae7709fb94bfae38c565fbffa5b758ff11529deb62ebbafed11d261ea72a550b61ef1d008607703b10e8648cc7d717e61fa63dbc9a008a52924c41159a4b3dfd2800002aef48abeb61dea5d473675ec437a29fc8cdf94903e62be53bd586dce9bcb05d6ccafe3f5e8037b76c32994827d8233f10950ce55436a7af0d3c4ed87dcd4f70b9a3e58965c120a4bdd6bbe838e0a523a8acf6977641837037d0ac87328aa0a30fe54774513f9e0400039a4e0e1b53dee249c05b09f54f46660fc9732fb35dbb3d1368d541c273eab8385215c67d78352b7baa78cc31c9c917ada031e2293e352706e3d85d82665066a7a3fb54f166d0b7baf481e4f745dd303000eef484a9f653febd1bb0f50d059d0f073de1fdd64135e37007f6ac4ab97806464c87835b41d730682484df2004bb058df60b01ce03b986c1f04e26ad1fbb345eb6f12d09bc87292f7e7b1362582e78e107a093fb1670b431b39b15192350b10e41794259d4f0783602cd5dd7b0b4927b446827f63f7e4e4fedb4ed31cbe355ec0f96965d79a4f63dbc957863b73db6217a0eb354630033496861763e1b4cae0d19d88697d783b16b1087a434def33613a5c6535f18933ac393ecd0d6bc0adac1d07503889521d42e8bc592bb1555287e019c7cf1c4efda09795931e39c124a16ce942802c8f92c662c8a8149b9c00b61f12e5a8fb1f45cfcc644f2091c7fbdb674743e66d80cb823e684c8765e7742798d711c25e08fa756624e226741e8bd3aae25c7e75b80f6cc925b679ec3769f1df97ce9a6a3e268935d7884ff8f15b3d0e3b32c4993c7800acfee32bd7c97f6aa39ccfc38924bdeba66ebf0ac45f44abac53d1da7a92f3006cf166728cbd02acab83bc54526a8b63224e44449cdb57a50a68e68ad2d95de8858c702a870efee4ca04c9660e8a1395d31bd61b7807b3b8e9b6680f02ae08f49610f0b45c9db1fb55070eeca72061d40be1f5c28380c675f5ef7c7735ecf45da035da713dc4dc07866e96907d71d59ce01ac542e7237959a1c5d57e0f0285ac8afa8269f4dfb87060cf5fb60aa58608586f4eb689b65d382db989dd815bd482ed32b4b51e57fca74c97e4609b6c4f193a2b889dc4de6d52c8da5b94f83d42986055dd605faf5c80f6d9c71462c5b8a19fc8a44f3fee4f96dd38760b47eb678b5b4af71c58650577754c5e5d632c2b79529bba35fcbddc704134d7092db5765ac28aba610240f28afa988f1635c0c525dabfe584cdaf05232baad7d382d6b6414c9f522c0a0249238a0cc09a312a78e8a7e70c1344bb114ef5b7a73d92c001b73ed5ec9f96da31c75f651deef64f951541c804093c51af4967f7883211440fabb856d027b6bc7ebff674f12b096b4f7c3f6c964f8e1ba473ed1f56060c7c2b7f8256689dd39c63cca4d65ef83fb1675e875923932e0743e3f05691b62f6ddd28711dddec647467f0aeeb914cc2892aafa493b8c433c06b54675fb3fb4acb306677bff782c6d5b431f91059f078e5e065e92663fefbe84ccd372787011e73b986c246d88759ebbee2e32f3a73944ef413c411b61b6bb8cabbe90c07ecd62ae3a3057d282b9489bc2b1066476ff4bf4e75d1ddeb3b3295442e2d54151bbe8764cc9faacfb81835078c8f9bfdb1a63b6479ce722a965deecef651ed6a430e6f1a540ffb7b2c491fb575347b3719742773779931c3a5d16bc561340e102f63161939ac915f4e08cd5836e37a179579da0ff7a15575b7464dd53f3e9cd9132ae9478d108866522e3e9da4fc39b02ea297b439055745e8e0be4e979b7fa50a8034260152bd04a4e29db5b67724ceb5ced48a6be427dbe3281552ffe7689b3eee55140e18c09fe5e82d9b07560b41b24b21ce1d54a13304321025f9f177852baa5f7b5a91a12415ae1772c63c2bfd825e5df4f51913fabff7818b07179c86af43c314b3af6a4e72cf3626647363bc4b153eb97856356964c9ef6ff2a8b3536c2ce94c16b6a04a3e1ac58f6155006f0525ae63268263dafd55bdb325fcf9f666819a883e07a46cec51e3642c285caf7a1b0e92250c7db0332dde366f2108ff8b24ae09cf81a8b7d1f99bc24c16a2f999e4cef406f788d996d4914c5821bd15478d997ddb10dd34085e3370579f4bd9c0503e8ac6126f74ede289dc82adfe116a35a71c91d990f039562c1bf0de644989e369f91b1897e2ace6c272258a29a075b64f0da25763286c0c830da57f8f672eac7ed5d089c46d5931b982b5660d6c6bb8857c850e43cb2d08b5bac37dd00132e5104d2108814358d5545e2c5247b07895cdc91bb260beae525fc9c076cbb93a5f6b547b5ae530ed23e0a0f78ef2a0eb182bc29754cedfce298bf3a2d590818004b8bae5533d73bda7d1d3445a46f7aca3cfe71d522ac15c5d09c285ac85cff3538f892ef6167b36c9ae6b7a3d7a26b0f5a29965a586fc66f545bd0152c2b15eb8da0fa550bf5e93e2adb0554d3c6dcea9d9d30f8da42963dc8a131aaabe8c74014d8f63344715223251213edcfab31cd832ecdfeadd05b6227d62e45feddebfc0be30204016c3921c1ead77a54ee61179f228e69c621c8c788e56e8615b3a4e50aafc06d461fc8bf3cb31c0b1828ded931f2b19441efecdc7fc3b6fd190a87e8be7ca2a442e48dc48107852730622016a129ead50c225bda268e0cb49a4cf6e9f0bd31066614066bfc21b097599732a01d6015b9414bbb0b6742d83990d400d43752a3f45fe84cfd3f21564d9665773a25b2127be3ae397d832175c039b5303b615eaea0f32d8cae7bb31c518b7d119d429dc6ba5329accd5cfe2503126aa7fadc17101a99f8fcf52279caff58f7f4e46e0a9a5c600017bef2d3a95055feb082070b2f28dd261f868d855f8506d156ef8b6a506d2081e943b396d3e8f8e5f0b5f7259f07c0176499eaf802c45c2e4a1763bd442c90980b06b88c621d14e819cc1a5c54374a5a06bae1237417d60fe9e51a269eb5a83dd0524881b3a140c903c5eee7fc066524c08d29431d4d33cdb68bb8e7e6b11311e00ca7968347a355fdba051d0747fc4e961471e5b2b4ac18705ae1227291b0c6477b41b90d0584f08ed93b9b9dcb069be5ed1870a22587790a388bb8d35fc000b7ecdf6c3802ad5029bf598ebb9ade1ce4169516f4d6371b7a1512f30d25420be513b81c5a07f38d2b3347ba998261b608227b227cab74b70288a7aeee61f0041e79d41bdf44fee1405fb1c7369c0ee7a0b5fc124c56492e9d5cb4c396fdf69fe0b7477f0669fdd6a2fd5de59491ab4086406db65ae6e1780b096f09272d71237179629ee69d9b5bb67cffc056950305ef9f7cf4d3fa85b74f450cee6e8282938fb1d8fb438c1c99303948f846bbccced8b0a01657252c1456d1dbda2286fe1891db139f1fe7f2ea92c38a5b18f67f57ab828fe58188af50d9981aac09b4144c33d60b142ba4217f03a32f1e634075a883b076a186c5ec4354e34aa171b0061b52d5ca8b3c7edaa8111b90e2cbab2983182288441181802ec2cd3557a991ecc2ebce944e2576888d0b80c41915538ac040a4dbf0c822828210174c29f280f3de00b68dc97bc1379bc3f849dd1ff116e6f7375063dde4fab887312ebf52b4bc4051a010fce27f8777945eaef5edbbb8d84247602a64f8243ad3d83dea55e9be92d322d0d9f3e86686175f785c916860d43612a9fc9e9f3eafca5c0fa11af0c70d44921d725c37aa1908000f970fa09d90ea9dda8934f98f7872931dc7c9d307081acaa4ecf6b77b7e868a72bffd07a446dff5a9bcbb2b75087015d56e17916f1ad3326ce1082b1dc7ed7a50962d8ca928506be6d96a9e444e7e054e16c42d3808dfdf53384485c5ca1f5ffe14b45b4eabf0ceeb53f9210e4aa8c145a150360256c64663d9ea5f9c695e6699cb393eafabfa2c3a52237479c11b4b1055079f19d386d0e0b222fec1d0870827424c2a88831c48f5d1b619eb118c22353f9b355b453993c073e68cbaabdf4859a7c5529237b45ce27dab0e794053b1ab60db7a5198f13006a87807233406dabbb455d8e00436d9752ca48d72f6c73ff12d680a27d06c085988ffda3c3dee9087884cb70674c7c87c25ebdb279e0d8d588bdde39fc9cd1b5bd92c13e5f84e24a739797a348f5113e2d96e537be05957b061674f191222a8447a97286400551e3d2a0a88100038b20f5d4540baf70fdede1014267a0f79bfac9f20b3519e23530a28c3b6cf1a1c7cf03ddc569dec93b225a03ec07d01c249de07a6bdca1a1b2ef233043c52ac15229b427ce25125dd71d98071c2f0b71f86b24772b94f30f611dd526518a291272cf79767445cdfece785c0b8237388fa8337746a560985d72e213b1a47037cf3e56d7db7420495962d31bcfeedab2ffa821c28c955df4acc9cf2a7e05e10c62d0d72c8f3d37ca3bdcc90712c04f115a3f9bd1308e1537a285a6f3e68b18207fbf037b6fab5d8d7aacfa1381f045cc6a66c0af330c5fea95df3e54d5b343019d6de79ad49a98f6d5c4a73b66dcbe6721b2da4a89535822eb41451cab80cce226a285e20d9b9f39c0f6123b282232c99dc7544b9399547f036ca610f77e11f53091a29e55c06f0f9d9808d051eefb549dcc06bd44a9915742a90d4b14b00ade6198031977674de7afefff062cb158146d35509bc94ca6d9eb63e1d521ba02413472e15275dc2eec9aab8654c1e827c4270ee980780a3fbab14014b7a66b0cfe4696fea18803b84cdf5ec53aae493487b05b5217e237936e837ef0381b2109a707409d3580a292cba24e63e259cf86b024767b6ba91e09fe75eabfd47dc18912f304316910060d66167dae3cdb673c22f0f60b789ca5a0a8a0df493cf77503e0c450ffc74266c5e6f1a9bca2f4b5c872b90e721d13649926778ba915ed041e3e4b8ef9a615fd743a68df9ee0751bbed757ac0a69b8db26601118ef57173b63475e74bbeb4091de898164b159137f08d8ee796b98f08292bcd27af91cdfff57374ec1e13df9c10861ac74704b3cf3b1a137d19db707166222c2b9f57ec8b7aba28caaa09f5d4892604de2cb7838e9201976df82af375c4bb1703eaa49dd1125f7b2d67734df7a4604541745dacfcb60bfca2baa8a97180ccc8b71d6c62fcf786af96469495ebb76ec1326be3afc54c6ac8c659a6dff7cf316cc3d3c14a84cccc2acab1deeb1302aac6b1401855233f6f1b6887d40207876f0fb1115345ca6fcd6eedeb53d22455a6cc211a6a35fbc0bb0e09b5396c09aba5d6ce618815948eec10d866b452cc2562e0a621959cac1149d98f8ae83fe8c36949203656a849bc6c7a31164dc50d385e68e547b3f8d3a2559bbad42378f13d7d73ed6dd2f716f7dd74a96c2a3bc75e2b1128a1a344fb34631f7271fb16d0935d49a449536d6fd7f69261b31d1943c12520d323e441a7f4fec0ccce7dfb10e58e61951039372a70be2f58e82bd94778fa2bef9ba30abee53c488b6c9567b7bcb6cb317bb0fcd88ff1f8957f722c17aa071ff80af808c4e9bd1c00313caf9c367e9812a55bb328e4fc662742313914e94a79052fb0a8b4cf899f551408737f11971ad666edc2a1eae31b8ffbf34d95e5d16a2a997a5885e6ad5b4b68a68329317ab991ab3d8dfb39429fdc670af012f9aeaf936bb326adda3628ea18532fb95794bf36ebd3615c71411e6fc97dd922c16f9000a69c7cf0ad038b0e85a7870ab7bd1e7d94a0b5d5139683359b5f9d9f80022d9cc28bfcd01a3fa80fa1d2c75f1f092210dd1419a8eeb4f465add88de50b6fab7df4bb746c32523d9579eb6619a2ae64f24a7c2958e2ef679dd830cbd56eac044552dc47e1f553c65d676436c90f8cfbf81356d0657d1513051fe26862837e7fc88e8de41daabb64d8d7f7ed5085a41cddf7a5f89a537dc293897ae4cac6754f167198575b698aafed1e894da816b1e4f535169e7c9d1d745a188580dee3b96e374ff7716a44d0ed0d8fe6bb784eddd8d65d6315843299a50926ed74cdb4fb246f7c977a1dd227a02bdf12da9f81aee4694eecc5464c27d6cabed7757ab2138db17b3efcb7ff83bd63ebc10a704025f94790ce282508e5b015bbcc6a6c70d3eb680ee67b69bd4af2e18727b5db0b5b842d1cd8e81a4a0d5ea25a01540029ddd88179662dddf595de28eee4fe007e3fb26804ee766ce7081c5a28062e1a89e06e3fb49480378749fc7eccddddd1aabfa87e0b625ef8942680e0cdad5578a9b2cb8c0b10ed4c69e3994ac06344ab201b79e768f69803196d0df6b48ec72d951ce0c2f4acd922289ff2a844acf843a2748100c68d2cc25f5a86cfd65315bb5f10258cf9afa8cf3748523270d5454e29ff4321fb6457f79885ddd91f3b82a0280420c828bfb4788f1a5b92270282d1d89db32b9e04c91531052bce7562129941ff9bd53b7f0c355b9d0874990cbe7bed5255f0471faf59a6b1248c4451e58e672883acedd083c9713a217893d2f1aea172fbe949b064c14a080de85e66cf84da96da86b13eb52680161c99e300a96404637bd483d779d0cf6218435a31a60ff738207577218e071711be99e93e3e880dee8e71190f59355125832e0ea52c20a6326e72383b344ca6ddeeb8ee2567da4175af6ae1b699d02587c8dd5e6fd13aa3760f16ed741600f8ad49fce5382c6cde8f662758281f880432ab43ca280e9b44b307d7b6067a1cf3dbbce243491957a61749288081c69bd8fc8cb70db1b99564fc48b0482d04e8f027a1428976f18ed5f8246275455ed3f2335764b8be5e2771f510c6b191eac66caf73a2b284d410f06df60033f988d72166f90bfc16d7bd2f4c220aeb1e47d187134fc1806933814c55972faf8900440b025e2eaa099181bd81565828b6d44c8eafc1360faf81cb408d2b2c303716f9f6516c6b69fb90be99c6349df26555cc931169776c336364daef7240a3b95ffc01c480006fadf3186aad907832777986ff51c50dd3c3eb4af89e6268a2365e35bc75b620664e60b33fbc9bdbbb1281c37f3f6a635eba09c9b94f10df3edbcaac4231a3f40044b1333c3f5fa347ba3904b2989c015a14c0f46e06948fa3dd28744a9237792c9b03cc457fdbdfd25bf2b6b4719f00a53f75e035f6b8ba1cb2ca6bf8d08b733c371eb4077c422b0305f38662a06a4c155e73170c8fca1d0adecdc3aec8b69bad2de514477ee78ba3e59bef0963470de202d9443fc87c7828b235e7bcd8e81024e729a555d76999f5bf698a4ad542a754d3789c3074c4e87359b7cce63ecdf249781304a0a6485559b06debf856b0d1c98600482c95aa71f8cc511e7ff885bafdd90ebb140cf59749ebdf9e2fa14a8586d922f57331907f147d89a6d7cb3af494ddedd7fd9a1abb563a54bf41c78ccc3f8e8b334948d779d393cc2822e714080217b5703ee88cde0824b424560d3f26248c6a08acb3c0a47eeb9e7dc7294027d0b5acf749ced6bd72e740d2224257362fd101b0b9a7b5f975c01eb4f710f3d41ed19cc9c9a372c470cf15b4daa2d735b698e8392206b107795772dc7c1823a0d5bc2de646898d0c31e907f3692da2cb730f4dad9137308297386b2e3b769ef7fd9bc89c034dcd2b86f01e563d6b92365c9f1186aec64d4cef0e9b07b146d1b1eaf33c30efba9ca1a97dd970a5436b67b214fb8e39ecaacdac4fbe732c743588f8623ccd2032d2daf6101df9d3bcff27ebec2bed6c28858f1285b59d6d5112c139271c12b4ff4961fd3be7cc07168edb3f039cb15f878eab12f0184b8c20d239f1675b212096ce8248d1cf103437b108f7c5690e344ef3dcba6a5a4f59322be1c6480359c01480552dd666b8b4f3d639861c1fc81534486f52e329665bc6fa401a59bb3a223405b3e8b4dc8d5b1bfc8b90e16dcedc6fd7478614c38e8850ea5991d1259614a63056947167d6770bd29c219471ce12209900288fa05545d862629a7146aeb1e84c8b30f081b13ba21896a37352fb67e7626acc9c6d8127ddf64a8af06b7c8f57be9776c799f6ac49a8bad915bef5ee0e264b2dc25e02a86339fcc1cfbbdf6365293c76fdbb278d412354da3b3da8201d66908e58501f324f67be0ec3a78601ef11a9d8b9f3523a9e8f83c73b3034c21182fceb5daa0b0d58c3bd5b1aa1cbb74540d965d6c82a16e4a91a4534528e03fb91843adf60d5e9a78bf39961b15219667a63225b457ca643ed0b962ec6f66173ca1d9928f549afa6f790da2a8d8eba991da960880650c367122c037fe7732fa59e9948ceac8cbf3283c63f92bd1a06cd0e00bcd0eace5cc44a3d43f526bd953b7cbea046686d581d982ae4616f46078e0e127a4f7438b4eec9f17b15d5b1975b468bb27acf886fbfc9b46cfadf356d60a5e58e3764003a238ba5c4303f24b345fbeebcd39e96e33a108e4060f9571754264b66da7c97c6bf36ff92e1d2fcebf227392e9b9b6bb12fff0c5d360e1377a7c149e4d9f3b9108efba754ae92d4cabf84b8e68c2e4cc81840da812e54fb3a22df300ba33bcccf1af32b80d8d9f4da74d649f461a2dbe6fa8d234997bea06f14dc905952c9171dd845d1e2c1f6c8483d3215ae6c31030227ac0cac9e470b58a68e1f757e50bf5ea69b240851abc51f60c7cc4f30461389514384f34cec6a59627c579c639783593e9681efa6d2d6285f784fbeee6eeec258315ff81a5f10a5422365325ce25a1618f7193131825345ed606c82960237ead9df1c35d06b8cbec7f058e0fd5b602c36be51bdc5b4ff3ddf47ddd27e7182f5e96337c7eb0b2ede47e6e9b1803c239b7e8056e39ab18b9aa05fa3b9776c47680d0f3671d5446aa3b98d5562829faf9483af093231817d5f6677b1532e0c9b8f54c70969fc52133332d4e78a02ad04505c2d05d02cb2db17eeff21cc8a86c9718ea98c6af296c63d1cb879516a18c458eff4d3c0bef8ee790e980f5b9140abb1c8b58cd8f97d7a446d45b126b12e884dbb33cad9d759c20934fdb1ac5d6eb007034155861c68663595ea74e119b7bf738985f0befa3bc39bc9047409d25662c6cd09aa9977b75edcc43b595971ac02f7ae106609fa672738bfde69115249473dbf5537a73af3b51306df17a291da0941dbc0c7fa4dc4c6c129e8ade4e8fb0f0ca88b85e38eee12cb8dea3ed66c1fa0d2294ae55fb56d6fd72524576fef528d555d32498512a951b96b2f69c1a68312e20fe1eae5662111798a120bfb241c590c9b7f947edac2b2492c96446a6f90d5bf74fdee655aa457818913f1c366d4682d5e19bfdaf9411a34fe6561151e5d29c900ba470849476ddfa1d06f49f133e1b4f0b784342f02f3098488d3016591f33fb4d18a2f56cf35988097c31a696f980d1c03ea13699af5b29fa4fda95610559e63af52f15d41ea702dcfbb1bd7f86496177a0e24238ea583f4a355721a827e88bb0d712074cde425baed6c63898a141b7603816a2f06e30c2c2fc1ff029107eb16c3c021dce802f17f73ade457656994f5f8a243860bd540dc03e68092c99107f7bda57357e0cf217990c899b992cbfa31dc8e91219e1e97f6a8b354d5770fce923af334036735c09ee8aca57b946d92a239ee1d65faf2c0444e4eb488bc67bc5d5c7040cfe948d6cacf9020db9a4cad44091a45fb965804859247c225e37c5c4de2d8c3e29213705a819d8c9efddfba8fb3ce2531a1f0dc5c17691682f99c0b8f9803c837da8034b4982f158047df542d59005533dc9c7bdd670ec7cd44dd8e83a856c186c43623f6dc56a1177af7e62e083714ec41f8e594d04e80ee72da3d733fc25e9e401d4a3e8bba64d3dad4ebed4fa9f9baa5bbc065030cb15801a5560b41e6505a4128bcb7f53b1f717e85c24922c97bd243be085a9a34950c2fe62756e1f905cf4ae0b71e7173430bcc6a152a8f7aa2eea7a175e33ef65b56066c5826baabba4fca0e50d23fe3841ffe0ddeb0380587113fd86239a8cbc3ce55b0993828cb112c32b850ab0d64702cb2deff1751a75af3d87b806d2553ce0c6ad0f5442e9f465a76eecd40b91f23c313ffd6269ff2aaf5ee71f12951471396015faf78553f8f2b3978f0de2b2d224532fce2fb54c7aa524fc95e52f0d3cb4d5eeffe4e8a1b8262fc93a6fe15f36f01ef40a6976c677313c0e4819a72872630bd37d4566405d6714330f079186c92b5124d624df9889aee1663c6587c7458ae571c2dfe1b0da63d056ba2b1871eb8f17d5032aedd440a3e2fd330aefeff0301d0d13817ec6d24f85119036e7c3003687ac5772ee2c66f1ee45a2208d7252b1aadc02bc9e87211b2fddb1c7c3410a7b0fe0ce89462f8b5ae410b9ab1a170e64cd90e1c99cfac72bc0df77692a428f524ce4b5cab98013734632dfd244b73fb0852b96113fe3e080fe11eb9c2b25910067f6d29932bcbf083aaeabf476c89534d89fa346886fc59150d19edfc4798fbfd99e1ca4181a610f48addfb2c70b997e5db018eb4be975b2400b44988775e9480124627999a8c46024fdb9eaaad13b8e8c34e544f66eb46d948bd8b6cf8684f94431429b80d3dd686996c0efbdf04ceed69bc7b830c632f4f02054c4b169b62c27337b37b2898e372954a51a56a4ea00705d3804c77cab6cb94ce6b0897ba36e7af0fb8508dec910eb958fe484f29530e559c437dd368c2d1a4c66c0c8001be1afdec39bf6b8a00d5633a4e3dbb663b132743f51a54b8023b4df6fe4b4ec6f1d78cd1b9d5c1206eed0f290e0b932be297cec4f15ff832a6a82a925c2a2f6f6cb268dc53e260993ddc1e49652ebbd1e9ef9b2435023a99e1535d404ec24a07bb1ef12840c9cbe94ec0fec770f73e862fc40978bd8525d2f315e9a8ec27d136489932ec808f1bc459d73fb2e1cd099c0b232ce0bfe85278278072095e212c4586a6386d3648d514af699c1f1111bbf7de5bcabda59429054905cd04e804da0a9e26a456094597fda5a1463fb1a225cfcf99d9c4383249951b460589ac896eca76b14086245060058b217c6613793e88c3a4b283005a0b2f6082c0620556b28cc067d2406585232a6c8a134c4a1839230970d88187226010025812a562812f498aac50240b93213ef2d2301d7497472aaf1c3d606b6ed894a71ce65c7167f0ac65793afc3c2587ab083a9592c406af2a5894c00727478ce021030c5b884811c30b2b3441a3d4b630295dbe8bff52059896f6590fb9b2bc617bca8bab2cca98ecae1c61525cb45faa4022a4d07b92fbbb5159c813cf255dbf77bb67008bd2e5294a981b7e5e17a2fc905264045b1a025b4222c415589420b0284cc0a22c592205cc891030275c604ea83c21ca0d73f225873839733226e37cf639d9afb297f15127cbe2877383a559768ba2953cf18894dddd07d032c4912c4f9000510311174ca01c511e4266683ac2b285891a7c7042a44c6bb5cba1b7048a51b5e2ba00739243ee72c39cf870c19872cdb02844b9614b4a396c25d8d295ac752b51245a392a89b0a71ceb1742ad2d43fbfe1c7f957ddaad626cc396d02187f2031166eb46ed33dc8aabec79893c314fcc6a2f3f5ce5d8dd5de3bfd8a711e74b29a5d642d739e164723b79ba5d1e499664e2d170b87a1c824c92d56af1ac561e933a12046d6879f8c40fdda83d7057ee1cfab386901c4ba3de911f7d2ccdcfc0e5f22177f9d4903af2a509346bf1dd85834088784797c9f14fa039e290c70b994686dde523c564ffde22471bb10707ac4aaf3acb1e1c302cbdea1c368c8314d816933b138971520688163628f9482282a862e98a0f449e10e143e348122b4e10664aec071fd983902e84f4f014060c163e32ca0b3fa06c71050e2d44f191bf922c1e39ac90a5062558c45c19810f008e88810a8cc8e2c21655f8482db12078a821872725627ce4d790ac9e4cd4892ea9c39bec302845b0273f6e0baa963d02064588ca0a6bc1950c9a1bd6c44a9326395c65d9838a8c0a2a925021a557c49431fb89ace9d34d7e28f1905b7e956f614d86c8524a5ca7b061d481351992676e589320399c7906293cfb27fa092522257c404193947ea0a2788200b96149b8206bb9614944d105538c52fe942f33a667923a5a42ada61697f8ddb9e46e0a3705a41f0e378a3af7f37fb89ff1c77b89877cc65aa943ff51ed4314530fe3ed8b8ffeddfff877ddfb6ff8077c141e423d8873481dea788847ead04f3d0c3c84f453c3978628fd1798febcdcccfde673ff339fc34348db0fd99fbfe11ff91ec7453ce42eece3fb888766bea54e7d9a6f1faff9f6b151d9dc21a41c2a3ce43ef6535f73534801e9a7c3ce7d0add737002ab7d66b11b913a943e072cb05a0a434813689fecb56f1f0ea7907dfb846e24d3f9a82b9f234a62499e11729343fb09b48ff614709fec398ae2212305a2ab434839340d0fb9cffced224d80870f0bf2e7c7d730056e24a0e1ec8bdc47fbf83f191e62c17db2e7b0fb74d17bc639e3336f8e4b82d4a92f6f4b9d58fdcb4154c309fb3c52a7be7ce77e7efd8935e2a1779ea15a9fc3317e68fddb477ec8a37df69d537150c7a4979ff254be89f99df32567b02531108284fcebd70cd6ea9e4be072c53afce56f37381d5ceeed12218532dc73c39680c9599090a418b0210f98b5624f58eb4603b6a44b9658c76c4272e5c61dc5b8f65272fab1b4282c09a249df0a0c892cf2961bb6e4884c475f291720f76faffd4fc53fdb6b1bfe0dfc685f35ec1abd43483f5287c6e93e71dec0d90b905bbaf41967e2cb1bbc823938e0c398848131617a2d547b4aa9867168d69ad4af015392b47d27d5cff2e586290922a372c39404a131639af33319b339bf9a90e49d2451f2e388a2dc34b0a4984acbf3c339e4c9605c71146754c78c1c943d9df78995445c28a09c2d5bf33577285a83eca75e3e88e369d3d6bc7d8eabf19ebb35f7878395bd1a2cd4cadf7bcf5d6bbfe370d84bad9cfa70464efd2af6d47cea5fb26c3e05e6d457c98af1a96fc97af1a997c17dea63dc963ae06da903e3b6545dee5fd8dc973adcd7dc555c71f653d73ecd0d857aa93f10388bc1b8e21e75655c9eaba395bd0eabe28afb2a591f75b8df7ecec81ca6efa83859e83cd6146b8a35c59ab25853966559ac3a0ebd299627fdee9bdc999a629e35c532a6a6d8515396d13c0d2661a32f25873d239a99e2e89263e213c51e26ae26c51e8babf94f334ca7d3f4541ca2bebd9f79fa95d6a751714ff3dfa76ef7ce17724fbfcfe2504895eb7f8f3fec6d9d2a149a59973c7f0665effd9679db36370cfa77ddbd7f64d17ca54f73eb9d1bd849ef0b812341b2686ecfb8aa1a04b8d7fcbbbb3dbdda677772381d63ac29d6146b8a35c59a50119545d7f497e4cf0dbb12a453a9542af56d5f9b344a467966258ac3f9f4e70782c5daa545747e20d0f9816071c823a7344d33ea66ecd18a9aba12ed2ea76eea469729d630fd2b63ae50c12204961e577ee418b96155b8c89df4b22273880c128f3c4fea45ce80ce937e06339cd98438832755fd0c69defbd0fef78dba5ecdeba0f094a8afbd13c3eda74e447db4a98fde7f978323157f1fbf1082b44fe1d033eaadf7a1874323acf6df87333d3c0667000a07cdbcfd191ca47d96adcf6459c31e8377e013f7676305eada534a29ed3265ca90396166c741d96fd9b45eb65748d1fbd47c77f5ebf4ed1552636bb69fb936844dcb66be46acb944489f3773e7ad43d82806ef606a7887cc19b631a50301ba566b1d0fc0122165b8c69517157085d8f84a6ab596e3b2e45a47469633e24a3ecf0562b92c5f06cf8c160d01d8f8fe407410f09c63e5db04005f212d03bbe767599681e0db78a3e19a6d0eebfd809d4d1d006ccf6e83e9598847ac81759d0ec7f6d4a6d4665c696fc196775e229aa6691aa5f8c666af391929a594524a695520cecd6ef42c032daa94b3f35b30477e11d3938e77788ebf0197120bd15ec6b0523a087806531d0cdb535e7f616389901ccf1a15107baac420f7ec3af6c48e5d06aa84a9c9a995660608a67eba582bb69608c967c4ec8b807c4ab50f877c3abdbf10220e5d774d45c08a2e71d5d9a3f2943d5f13d8d066dac5c6b2bfea51affbc3be321cc1f802ac7fbb0a3a0781f5a8576f46bf1bf3c674b94e885a77071eb3e36d73ce338e21eebb212340e3e7d51362d6426f8945e7dfaedcb0265c3944e56ec247eeef868651b646ec9c7e4037b3c3cbe4bedde7d9fed6ba8eb33333931249939a4b2d6e9608a93ef79bf695ab9493bfddc8f79c79e9aa25429ad9e36ec69e9bf89e85f4c4d4ca27427215dc13d0b36af55571555fcbee256243cfb3b5665ab661cdd22742cabe166559d69dc595eafe60432f672ad0ef063ccf89854cfbaf0e029ee50dadd22537ac4a906c80dcb02a42641b5721d8f522594adfaa409c2259de0d78ce3ec342ba4e9221298355f191fb9b1d8e960889f3b8cfbb477bee3ddb2f7befc6ef2e97511cf4318bcead7cff0667e49133f7ec548b3102f1e56b1f0e0ce08bfa6e7c204c8b6bb83d588f455de7cc88af0fd7072965aceb9c560e5bd9ab8cb493bd8406189238112186892584f8810a268eb4e0062658885ca8082ee357a76e5e4d8bf2032152ea5a36a78c3d9ea726a5c4937a7645d7d76bc839b36c3a0ad93bfbaeeb761054c8334c0e4d70f718bd3bc6efd9d46528d5e4bc0ce6c40f5816250fe34cde41fc383de631ed8806dfef57246b83d8b32476bf23893d31be93628f0e9e1cd4bdeafbbd4b7b90c1f70fc5ee4721f6bcf87e1224ab3a81dfa86f1dbd9483505f438547b27c0927b26a8e0c9aefe7ae336d705d83eb4b52c7dfe6fbdd87ff30721dfc28f6c0f87e0fa2c279d50d6fbee36ec8d3bdccb67b9cbbe3e60af1648b3d29aebcc39ee47a97def18f711dc98b96ea6dab354482f720ca7981c376c26163145e5c2d83d78bc495d7a0be06858a4396befe8be08fc396b5115736b148cea5c2722f59383932380c5e2dd35c2d7730ae8e7eca41f5370e48b22e103914f726b407307a6c9efb0f62cf8be7de866469f0dce790ac0d9efb0ac41e1aee5baa648d04650cc9490e87e6ba91abc1f523a943f32d1c1cb6785619e070f531f0922fd5e0d056554d4d0d0e6b3e8c3edc49efc47f715d87eb46d7fb9abbca34d773fc115917883ac41e0d58dbb9a7ba335f736ba6a9405cc54fd54f5d1bf60dbb4b68419e1c461fd58f86b891fbf01f3731d574abdc2ad78a13b9567e00fd07551120d95f5504c8e572b95c2ebd73992ed3651ad24c72e6ad6e1bae97832376fbba6d31e6d83493737e907fe4513d0ad1dd29acff9da205e62cbc514077103f8c91939d6addd26cd5f8c0c61521aebc1bb2720c2d38bbaeebba6e66ff3a7bc18bf3546bd5f5d24369491224dd59c69443842c495ccda43128cc227135bbbbdb8810edc4465c4d296c8893271857731a7d7162caf3c88ddc29889be56eb1aab7a119c1f3bcef6b6c50dff7d6f3666666beafb1f9cf7236a8bbc3bbf7a0ae0b81b217515dc7715cf7379f053db0e3de3bef61bc98f9ed7637cc91bb6de3800ad97b9adfb6df60bc37c3792a8e5361306f4033df73df75dc0365efe30c0e8540efd260ae7b475214d6016e9789c9875801cad2f170b15c2db02daeded9be7b07a62506d3e2d45d2eb7cac5f279df77432130de38c2cca7baf780b2070265f95d67700a7b287c83db70fc0f87df8743a02c04aa2c07026594eac5871b58ffcb91e56fffdd10287fa0ad5f238640f5bf0ff5a064796fd9ab806c3430db0881f90b2bfd9ad7be4f754302e4ecebadaaeffbb01068e3935fc71359adfc7d0fcaeffb7ab36d55ca1b15ec1dd046e56949f91de87df51ba12a659edc422068a377fa5b014503a3772c8cf7d6912374ef8425a8ac55d1dc2bf74aad5fc395ada84ff5ce0de7fdcd76b38df07df77293f246efc8afdfbdd3bdd33b40badf34faddc67567b9f91a7dd7f08d89bbb9be92e3f438a5ede7f777bb77384dd3fe0609bdd35f42eff4d4708ddee9cf70ef4c9edee9ffcc337bc7f64ee73576b750307b9e6729a54060ea6d9ea6a6a60628d37c0a87402008e36e0173cddd52530303468d4a150304ef169a1b0201e550080473900a044110ec9dd4833c4038315c2da9abe56ab95a46b0f6c5ab70f78efd17b7777a07053da3936ac5e18c5c371a1b8d8dc64663a3b15d17d8ac769665af853504311dedd4755d4fd161a1767a6a27a71c7ad1b5c276b43ec7b5d35396b8c678c49d72d0fc2cbbee9401f7b78bdd38ec94c3ecb5e950b4a755d3bed63abbae622abb8f3222cb3e7dc9423d7d7b65449d8a6b45e15f75b56ad8c3737a512a05cd3f21cb3ee7f4a71c87779090fdfd7635e7394addbd843d2e4f12c5eb734da9c2c5b88d2a8808d2848b939718ea0c5e68bed80c494d5650c2045d7817615e5c025c4e1c487321e2e5398cc00616bc5c3d851233b80e557415f7800f658870e1f2241c9737692596f00f2cc0e522e8245a78f26ae27582282f5a009723d14c4cd16338701d79bde095840745cbe54f806926545eee8286628baba05de0247841e30222069699262e394051c30c30323d9a943625b46012822846b6ecd0a3cb2b621153f4812ab8c0e28a18199a58b0405c6078f8a1072bc2fcd0e525c58ecb8557149a1399d7133cc8702d7981794549b9cabc3a70657102c10d8e14c409ecaa32948092830f263ea6a82086252830517e446105882546ca7c91f242f2da781c19e14296a7228074e101189a5640e38029882841c68b101717ae1faf23925e59903982e1d5e5bc0b8bbea20a0baf03482f4ebc2811f112868b22b5e8041d4f529628483e20439025a521af4d7371b1f26a80070610806b4844d570c58b01ae1f1c05312c7c8766d2f2721fadc45f0070b916ae76e20d832da0f80b57665c7d840c38b8104df4861988d8904390275ac8bc3004b70193252d428240310190255e345c50689922e425e6d50413232aaf07b810d0457475931514597a85e0a581ce062ea97231f1b2e2e50e7011c170c5e0e53d9e5e0170b1944c2a6004e89807400b323dbc64d182284e2e70c18207d2141f6c680190277420410ffd820cbc085f725dbd1483178faba904435c712c2ea59ff4160fc305122fa6573f39316385577b5182f2025d317c966db8d6eb655fb3169860a038146a7971453522289e5238cb30e89a2af9022b0a70791059fa8b111314bd52f0b27169f16a588d112ba76100d1e5567420892a4b98f00084952b7c321698a6e4ec73228bc6277b191e6802db610544f860e464a4e82d725ec8aae4b8105fdd90f654c2089ee8f2440f297db0a13c42d23a92436c489d25c902bf79a3d819441a8eae311bb76972883c7215893dd9101924f66cdfb3b5722b7b20c98a2ea7560c1d49d8ca492b89c3d623f970c8237f2f7f7e3635efe7bdc14125ea359c610ddfdca0afdde0a0a9140e3dca399433efe77777f0f0b06fb9e25de2cac9bf30f909f4049ae77c2a7f070f57d65e137227158cc28c8323d65df96fcc868ea48524c7a7da938791463818ba18635be84f6401451b917581a8e3af7d9c1ec87095b3952c2b67acf1810917065fe25b1752ba519a446a20b26ac4123ea8b19241a555fdc41cde03c51eef49d080f7e0c1613b95e06114e2caaf09526c68b355c59e0dbb91223c22689c8cff356c7c20856d5a37555ce588ab9fa813a9586a43041a24eaf86bfe5edc7670624c1e406e981341ead72c977c8cdcb66dabb5d65a37bacdd722515c7974ad9248236f64658fa8133fdaedab558a3d9296493233838ad265a5118eeb9e93478dc3da43ca9427511c34f35e5c2199690f2e1396fe16ae66cca066fc48b2a277a45dace11d5e264c3dbd3738b659f31553dcbda3fa8a6f70d87cc53bbc8c8daa068732d71d4399aa90eba75ede90470afb962b5d9cbe38938e7ee2b4a8587b158ce80a8cad54c89db23f79988985dc294b2c55de7d9136d87076313a0abd2e5f624f2719994652fe5276a5d6aa358db2944f734813369c4563b20c8566e4386616cd1e4f31cc75b2a15fc9f2ef141b4a1f597e2889624cfa903f624fc4f18b34f217ae30c1852cff7eb1ee8aab3171e51aa535628c31c61869744522a72e5bae3861438f652fcadd3235137a8ca62026714095131be26429278e0a74f709766dbd59dfbd7fe73804caf33bed3bad831cbfc986aaacaa19b64fd8f0a57ffb8c2e7167577438f123eba30a94df4b9967185469ee4672d0cf381b223853d636daa22fb3a4bfc9ec52ec45714514aebeda903a8e42bd39130766c31c19d17f76d03af1bd59552b81be86b3cb7d07ab8ae94f7aa3b0210e759cd81336941c3bba5d151433d43e54e54cfb9eaf952953e629cb97339371a8544db68541e998aa19010000b314000028100c86444281502c1e1827513d1480098f9e3e603a1608439120063110843000c2300082000002200c020006c20ce246170be3d2362a2f9341970dc22e25725701bac466056c32f833cd9a10ca55a3fd7864a66b70b7dbf20a87ccd266cad6205a950a2c51fd05aa12b3f5cbefa31fbabfc144a95471f977fea6f9479494589a51575c694be5fa2ffe47f9f9ff78fd90fb897f913ff9a604382cf9afca14a177a3e9d79c7f816689049a81148d82eee6f6a4488ae0f88534291ba5965f68e26431fd86f52f5c97e05595fc547f31fe06054ae73487f225382be8ff025fa5ec9265453a6be2169411b3d7c2d512418a6abf6a6a627cf9c5e75fa82b1d82198894e2d920c24bf04dd4eff9c7861f41567a7af5fd7df563f74f902c2d57ddfd5e044a634d483fafce95e1f4c7f167c564108eba9a940b07fe02a1125783f87599263ecd747e611179406f0e10c6763e7d853d507776e174e9e03285986f9027f36a4b69e72085bb95130aead9425b69a6e1e047105afab70134556a33a06fe05e135d76fb76c23fa221a56e43fef9f62f242d55b4b5404d6933b6705c1a6ab9f7239097ced428fc95d8bf0a6225f926e2cff933ff70fd7998121868973f16a605e65405fc95fc4b1e3cfc18fcb4c27e9f8f9001306cfb7f849925c218c2616968d5214d1fc6088b90b5d525b452f00129872fc3acf41d56615942d78afb6f04a31463c47fd03f1dff8aeba5a815c03f9ebf805b82b490fac53125d0b2b3fd17284bc45795df5280606381608f51e1c6deed68ff0e1e943e17e6fe027c09a515f77f724c297611fc1a6e3af0ae4ff90fb24b35cdfe1c1dca2660ee62bddf11c47a3085ffcfe6bf3057fa5315fc2bf9997c7eec8ffe07dfcfb35f7bff044969c9d59ddf3b3f76ff84d3255394f0ff2bd694625b057fa87fcebff62fcd2f9b4037d8f811c44a4ea9ffffefbfc38f89c920999ddb64e04be1f48f17c37f0733029fee0e4498fb53678e31826114f22560ad025709a971ff1dffd4ff1245966aca36a253cab2f67ff1e7913f06f8a5e8ac03092575b5fb0fd68f205b7aaafafffd7fecff0d9012931a845da52e15f18fdd7f602e8d5eb9fb1b882d918925f895fed736005e825b19f117384bd85616bf03cb528c35834b69e1e2f1cf2020a578d781e8927b0dfcff40c3ee25f1aad8dbd014d4e8fd72fccc7e81e6089d2fbb2272d837dfda3abf21bf931f2b3fe4fc0b1b2503ae57ff1aa02ac9a90afe947e06f78ffdd5fe30fc7cfb71f921f213f417fdc9f34f802b55b239105a72aa3a332d84b390fe7afc1b8429c9bc843f87fe15c229c56615a04b06d8ab260751d613abfa654c0130498957169dd1fb9605e2a74cda57401ba13f6ad161e1f545a2451de05b3f488c5f18d3e767c756d0d735b9348c1f7ea2bec54d172b6131b46856afc2ed73d1cfcf966ba0943c1c8e1b10a744f8644d625bcf8dbb9f809b3a6aa4d4855a11a814c53b94925b6595b7d47940120fe29bd9fd088ad27155e56f99dffc13f8c3fb37c02e79d62a1e96184435502b55a619144b435a9a1ff6cff4b3c084e013a5dcb4c0e4144289a832d0d8085b55ff3780add91d804a0bda1a9a0b02128efa0be62df388b8b0878d0d653240b01a24b7a8a526485e12eeb391c36ca409658cc9011c9740721cacaa12b6dd4a5a1e584d74bf43dbefe638e78c431b0ebe828e30676e6fa671b01805904591d1d99e9ee88ff4853fab9ce78cebb1c695e3f2a2cbd3aae36707fa7018a167de0e653396ea410ed040790913be2de086d501639f0de3fd1291066522f88ca82e96ae17d775e26b49f81275e1be5b21e4e8b062a0d2fc007ced2f8ec0ed52871a0faa790f5b62051a85da6c5aeda2c8737b2faa80b695a74065d812c7124fb486a71744ff6c43ea5c5ea79157c1ab66ed3e05c75612bc64be1d30de30fd39c4df3e4068d7384b6ed4ea5836201add0cfdd5d71da5d56d16dbbe37133e94caa5189c7d26688ecd28120ac9c45383a0131ac08fbd3e56025d4048e422b5e603933f61695c7685fa025a19359cc836b4358feb71b78ef466390e7119b26f87ef84c537a2f6fae80661b02e2259f204e86ce98f49617859b259ed3c1948d6ae54e7d30160924a0d32e9d1048ca19bd28721ea5721b6039572cc37b932744096888cb33b9a36e06338e7e56dfaacd2f074679457ed6bba57180c30f9420342ee4b24749b3169a3348627cec372e0e427e260de13beb3e549919b8f44ef6ed77edaa841d484c93a4ce5219645b804c24536f9e990d5d622f5d4d09a3bc3a77834b7442443f0adcc0a634f482f44d67287e057b890d2d2781391a6382856496f0abf23176d028cc69b3c5344e32b9d81458421e30e4d16a853d9d36467887691a257b1f907cc437bd1cf5d79e32c7b8aec2ca809542bdf1f4f4082917f60efbafb3622e3810e7e2b6322c20862c401fd664eb9b6a563ad3ee37375e404342bca084c9ea4ce6eefe21224d2e36e6f6504931ff581c2c871124e89261710d648e104e416ee59eb0fa3ab48c076bc8cc2d033821caf09258e85ab338849beda1e4a50aa5d480dd052525d542333e48955c61b9e94002a614d9e423c17610c38d8af66238dc8814a5ddfef993fd27172b601e06aae10048a993d1d5d17955b3e40cec70351658e2205d8b80684619affdc0a70c789ff086fcb7fe4dec1cbcdb122ff3afd4f09fd6e5a2c595a0a1d3b00fd6fb77ce222e9fcb9b614096bf76cfc9a97a1707b177ab871af43203afde4c3c8e3680d00dd76bb6f6efa6e696f0bdcf153d9dc41e2b7087e78f638796201b225055a8801672b295840f101c5f0c1ac0da72947746dc158412f9324056131b439bf735f6275aa312faf23023eb7602e00896c2ce408c9fa87e54ae0f9fe5ff6c6c32a5f58748e407e05552c8d41559d4a0d579fba9cd601d53f87ef3d507d4642733f237374ec5a1b28c0ab1332b6cce679793fc2d8c1608b2f8501df910c72d018df8ee60fbc26ff60f42d646619c58cb932fcc892164a481705a20fe7e0153b79e02cf08493a414013c01d6ddeb6accc4c02feadcc8de020dc214f629eab4327518c9ab43e1e8c9b019802cc4c0ae992c4668015b822e3dd45beb9c352d2322bc2667d03b2c0ba0f4a383ef9f5fec691bf970c8674033ad0f0d36bfe51bbb701e23e62ff2c26a69eb169f295197fea29876bee5c68a6331a013a4a75931209368dfcb94ccb9820acbd33a4060c27129ce95338da1c76fca122edf49449851c027c8c3c8196b8e9eda5cf7409281e7f378b663321217d85959a6664ad496ec664a9c9545d38ab3c301063bde98b09a2a59386bff8eca12b541006b031da65c43cfea120c266b0a051a2b407206fbd3236b2bffed3894055c2a806591aa8d8153b061336235661999f5b71b4e5690496c61141541f738437472d3c6a48fc129ff461d75897aafb92d7cb086e195c5f4a8a4afe09bbe47a9b7c30ba1e7c8c10c4950dbb2ddb02e16df2ebcd745b44373c085798620b8da1d6e665cea3c1d7faeae3ba654b54cd8796099fc871064489bce28bbb2742a26eaf861719e83e2c8b62773af386a0b9c635682b4a64cbdc80bdc838d0745f18ee48fa9c98f96da23c4c88c2e659c1170a6c400c927655294434234192a34d5998357f9c158d4d3d04dfd5074d7babac811999843f3a38d365de01dd394847d3bfe96e8829ece9300bf0f2354bc50c5557d338223aed176c57b2d0f94b0970b8bef37aba4654ad587ccbb415e1af6162ff17b39208a95951ffd9e8b5c2206654d9488b51144311b21d222bcc17e5432911f45dcc03947da1207a57e7b8df9d436ba92afeaa1464ccf3e3f583dda79c03d19afaf939779c17761bfba1d3e87d7638128522b6ee1252cbb6fafae7097a01977dd8c986bc540741c14842265846a5b29d0e922a02673f9325169ca8764aba87861e70f994f015d423cd73694feb4c69b8532b40411088b00991f2d5d252f40e6d51eb78191cb34ebfc22103814f48a8c7ad645a936d524ba8471c2c9a8b2168b01d88ddd973c48ab093fe3715c0f9a5b2bf817c70deba2baf3ab0d35013687f83b9e3e080354ddaf1f77bca877557b6c8706a9584e53a48ad409592d529c7762078549dc9446786c9ff5cca4d250988695437952eb50c2499e7f6b1aa11f2556d7a50cf201afd7c78c82401fa224dc32031bed2692f8247723b05a61ddba1aba835c411c7427c247f53878ab8acd719018d3f47837de167026ed1d143121e771b60676901e91b55240785f5ac730eb61b483952d3b0e0869528bb57f33e1e3b1f32dda1346983b1baa105284754780b825db3c758d792aacf85987aa704f232186e8e4bda47ce47635ff2382ae1aa370f72b00cf3fcf49747cba857649c2b02a84a6488a08cdeac6b4b2fb602a4639f604dc2e1feb29f33e8fe131df37ea4d695802ec31f18ec438c9624672b1c309a6b3801f938670156e7a749025a34761a0cc5142b1ead8ae7532a393a8beb2c92b9ba71ddc18c0b6a9b1faabe1f7b6143666c6c7d844299a63efacde2d5fa244f0c0755197efa1c0b21dfb19a2bb1755a69e646fb208636dd16efea42a084cfec313e19fba21d0734d17e503daac97566d2cc4d59b552085d6e934d24eb9b1e3b37dc5b0731b6a7686f1987d443db2bcc03bf06c441ebb73b4c65ad48daccceb60dda6a37ce0599fcfa6335e7e617640ee749415c7842d530b7fc046608f796f7516a80dbdcd44fbea938ae98efbb1cad1d617ee43243cf08899f292ee0133ccd9bda43fb21b6baeb71fbec2a28132a6d40c506a4790d333e038bc8b2826ad1e0b5b75042d5437bcaa772eb7e4670bce9e4fd4d6394caafc4447722f0e2b28cafcb29360695db89d6d5b2c25c79ea58d6989c0684633e16c7915edcf7c0af04916e41c031848e7004ef70ea44c7984094d0acae114d273e1eac62e8495e3caacd43276901ad30722294a5dc656f84732fe1a7424de72f380b54f38ed68419ca3cbb1ab5a303de3df3f2c5a82387170ac2d5a83b49cbf1b03af1c5eacd34bcee70d47a1b866d668f2548fd0b1374240274b1b7651a8776a58d6b9218e48cbcbaaaa8151b78d64b8329c4cb3f3dad30f368f72db2982c2daba0bcc8a1ef56d8cb842bacfb88a56ab33fb4eb9022035383b2f0e0ad65179eb8973e200d76111189ed79b8b4897dfe9000ec06ab5b9c4f4d4a97d291292b9b97c19a0a24e2f8497495d15f919c4593825337e30979ff915560900db4602a2c3f994b106c317316fcb63ee6b5e3478fd971bc180d6a39db6c59d83dd5929a8ebf4b8a97b8e90d1f8c3cc33e6ee63d80a302c9120b3bf1fc1a3786b0dade79e21cf953305e362221aa2216f01111471a45611196e78b313390036e48510316b270ff5678753f32c5df2ff864e7ea2c5fe7d018404afc75bcf77b1443303482910124f97768386b361f3a1ef0e2a00d28678a9f8be73739110126d56cfca9cd7e7704316638f714040d5e2b8810ccc2e74305f43a7f3438790787c54b2f73b9a82710cb5889380d8a1a69e22327ff4acdb7db91bc55963cf628a493f303696e6fd06874a41099dc6f16d351d70709c03348b69dc4f49527aa14fbf765cdb3bc783a4dd6999c8fe1492bce710e0b196e9ed98cd050dd16ba2cd76c6b122930c096f97479e7de41634567e684ee8542b7edc4d96ae9e09fc1da691b59669416a3612c96205948d98a8d271310aaf6916c7a77267aad4b511fe5e9b0a10d9d5bf0136d7358592c9c2f68c5e877f4300519bfaf2f0e702d0429b814467e89821a9adc79c9a8ff192a6bb6a310d01f3dc23700ea711aebfdf31d0f18b39519311c433ec8f8d5a88936889db94b371303e7e1407d78d3fc5a4d4f191016f6ea7ab927205a3bd3ce5bdb4abaa85b2dcf4a8a926d5d080bd16efa09ffd24837b47a075c041e60a247454df8bd87ce808397ec20a38db1e7bead7c12a558b8f42d021e669ec3c24e8ecd34c9c8a0378473a80ae46fe29d005116c004eb3a0de3692652a126ab9e51b2105e1276a23a0a019576659cf29bc752b4062adecc7daef1b29695fc073151a80cb0081e0868162f41b77dfb05fe290451182f63c25498e32cf476f24c271aeb711b239f10bf98229af4505790f33346f585e13a37b45cfebe6a8b816a3d64400bbe0a5f6280e7d83b011e6678d4153977a7d0fabb58bacd25fe7c62ea51ff3a1e121dc051a9b7766ab68ca10843b5e72320b57288a6824521578f98c76f1e0c00cdb7cee4c70e7b1b659dcafded535ea79f7ba795ae1d5925baf42621e747d22b780a04282394a9b0d54eefc06cf5e60e9c40cdbfec006aeb05c123759b00240c2a81ed956b327f4547395b80e54d7429b2d51aebed2639e35b8960714e7d84271f1af28ca84d5bc81e1a91bfc1ac5b28a58ec9bdfb58e5dfa29476c68767875561bf910f02383de53e29aa7940acc8cc6df502c98e0b85986e226aaa9837ab093bf025d80af79ad287cca60ecf73844c1cb38532e4d047de6791760600fe486f1b542fc42be94e609d8c457a940e2717a9670bfd5e8d9aeded8fff1a0ae56031f18acb7d5a9ace147ed23400ca400093c4d90897f4ab2fafce4bbb709eb358e81972d83c1296d2e2882347ed1d085c8ee54e0d311111d952db364c63aa850057b5b11e90a11d351094265b0660f6dfee1c31813319bda38116b150ccdc1c3556ac556700c5ea6c536ca965a0b8bb44095f9f06db5e49124b53880c6c317252f859cf1707920c4d2860ae59bebbf24e33259248d72534f826a6b6308d86338b5d375bc871c9f0f3156c06d9dc30fd65cc23b927de4e8f1ef137e941c6b5d5fe82aef7233e908cc97c3bb07beb49859eb896c581f8c8f46cf766b39663fdcf3fadb6bdc2817c6a06b63cdf6c2b61a76e8d77693e7b563af95c4980c44bde420a2746ce388b54762f7c4a9dcc4b67960cde3cbd59131f5277449e5582d1c781f9f0987aea216f8dace17c4d0c91c1dbac624c149a13686e69e5ccac166b268414a46980a3be73ebf9722ea00501da076b620c1d27e81718d72d7c620320758419d0e5c1e8d9695abc09368bb3579a2b7e7a3a3fb4668be657911314a8cbd22274eb4b0dbfcd9f70b193d2ac056e460e0a52944088d280f001522f2262a293320d0034fc1a5554ec294f8c37614b7806efa720463e184387a21124af97639dac412ce879ca30b103ea50846f00ca786af1d2c8b3e969f4bf3949b6c995c5b24f26d8e582902442e01d2dd99be666b712d46e0e701856fd614b143f8b5c47b10ae6abbc59b7dd87daab913ccd6853722411cfa66b28db714ea4b5ca0cf0337e56f59768d4de98b4dad08efecbfa69259c6dc7fa252213ff3a6289c141285a5f87b1cb793c7825f6d5d819b9ad6833aee81140c26ab293482bbf059a5614eb99a22e48aab41342514f7069cc834d68b5e28e64c551f0aec3cf6637b65f8d013304136e66063969828789247eb21e282c503f10e12d3cd0f2a69cb7d65caf2505cc02c1fa6b7e25ae16f773a8720f09cb4806024a3a3c6cf5311c36050e9298579a87f0f4c54310c50838691d7568c5f8f974945a8dc31f324a29fe2749c2055184ed7610ef01d5ee3fe0af1299216d25be2ea68c615cf19944feae231c5735cec1a3168a44ad030de791a15e2274a875a47c16196a16e9c0745e78b187a72d33f228a0bbd61eff1752e3715c10870ef4b1c466641b6cb2f6f59f312d160424c41ddbbedab3a7b5a38055ef901c18f4de19d01279d92dbd50cecf6b2cab64e2e227ea186ee0e9054439a9a6464084e4c4885d348c2f8ac4deda4e4360aafba52bbb7224b28a9e026569a95a71428d2d1ce4dc0f94f276dcdfe42c09ed8a3c3b83202591e81f04a6bbfd7bd46dbcd9b65cbd62a0bcbae97a0cd3d387fdb448c5feb5bc0d89e8abf67a75aa49561ff82ee25e1557f548539fdbaf32027884ae1b4b3320e672a0e17eba9864c448e81f299123b3e4065747753ec74ab1a5fda394f9bafbb21e28cdeab4e6635465f3af536d03e1f4bbf9741361645d32a336b1629df6d108eabeb371708043a049bd50c765532bc773f3ff9386d86523d51f31b6625a4414832411a4cad266be6c39f3ca923749d0a73679ff22757e7e56eeb0ae7b37518d173bc9adc60c7c2ac5ca0293dc05a0aaa1fbb7fd561fa84615cd2a3c9530ff42537800d08ddeef0af96dbfbea94b5c519c1eeab97f4006525a06dd5bbb0c673d3aec29c30dbf69726125b43add05a547e8e52ad8eda8f329efc0240ea19a9595bd90f3333fcbfa4e9ff8b1cbd26467346d1c9b2893642d77a56b555910bec2cb9b469ef794b6c424a8d3d185e67e91b584b1e0b6d8ad815b22e734eefaadfe3a08a390f15481e7bad38acf962983ad3ecaaa2245db3f529661f5f3275abb289b07a3cc5795bfd9ab5845991266734155cc5160ca0aa8660bfe9b6e43a8a85fd79b2c6151b25db2dd9a1d5c825759e38933dba4e9f8af5bfa45479f8c4fa0f88853e732aa759c5fa710f0460edaf4efe3d972aa67659395e58fcc5fac1acf37729a796092487592e76895741d0b37e045eee496fa2541d2940883f96310297f5d3f4a7cdfdfc6ec01221eaf7e29b6db4789447c8d572df0271a11738543f3f718a54d68ff725b92e13241c46c8035ed7974e5b69153bff8d46cc7dd4c7dbd34b004965ca195ff822c7946b172451cf4bf9bba615f82589bdc856ce3750286990cbfa6d5e890010e8f561c1e72711c06a1cfc4838a1894400baa0768440e5eb29c014ba398b7468869b448025df21f49181fa65dd2100e54e095198db62afdb564fd9301201f4f791818fefc40f2262de00d8ef969fd1bd6cc5fa28226abfd92a8ded0351903ef177ef7992e5049a747126644acd070a2402684c93f612360403b0c50136944a7beb98e584b66030c2ea0398216b9f432c01ffde03166c6fa71d8668da090ea4f9855a02c8a4e9d7f268a0d62b1681143f22a1e63f2ac56a614b176cd568ffed547c2483ff5d1201bc8a5ebfdbf664d2cd1e110f042b33c5bfa10a121edd32be98f2946b05a9f902a918347b4df3a40a2ec717e4b7d0d85b28295fac9fed050c1f410c5a729b9c2f982ab967d3999a796571d572befc3f7ecf05ce123726b825da1c918173f4870b94ff6ac1aa8670542a40cc62fb42f6711fc43cc7eca3216d73cc0294bc9947a1f1019b00cef666a182d33e0467137b9d25405d4baef55fb7af409673fa417049326500935de926104d26fa94af405a96fb9093651bd997de0a6d2ef1ad10f6ebcb168d7e1024dd146396de2acda5fe0c795e447dac6a2b10070b9ee6c60a63d6714c2236dc341a040af5eeffc21062dbc245f99ea0639e5b253eb4af68c0f4baea0685b7f17f0a403bb988ee68f3b899e35bcb01812c4c90dba0c8b122efb556f87acd752c310cc458774d1a8090832380def981558a479cfcb4b63a63990cde8117bc1715dcc53053441036e90614791d6209dd4fe620511761973db5d8df92d46d24ad4aa0d3e6e07482b766eb98c8c81b7b4086c54d93d1a0a2e03a14e256fea54d20e3ae11291fb171017e5d8613020e1e8968862cea686460ccabb1219d1001628f889bcc72b051ca9400acf1e2bd32d1534571299700d97aab4cbf217f250fe35923f457af40315f716ff525932734ee2fc879625456a8bdd4ac49910662e5ad952896b99b2ecf60945ee85e4862b1d949ceea5e2be160b03dfe4391c9223bc244824b4b29263529d0cb4dc06fc322cda439efd422cdf028589ef701f77a1d3976f8b5ee016761ebe7e9ebe81620dc6f44485e38bfcb6ec0f536bf0758ed61b2a833f1509fb35257e66b67ad7202112708a1f39f4f9aaa9b8bd02bd3430262cb8dc83ca16e67cdbbf40c1d51f02da3779de9c33d80d8684979a66af9b3b732f8684a12c0d44ec8e4d858b33408841abb9712b87c3f3ee86c631a7cc06842f2eb3e254c084820c245d0340b406c04f7a10560a6163f1df4ecea860cccc718f34a18050e6e68823dbdf2ef47832c7954e93de65ef21f5ee5232d011010407a4edd68dc55bdb14f8298b5df2f17cb32222becd657df2bfe10eedf8a9731dfee0c86aa5885e9aaa3a451f775c3b25045a41df315cee5be35b5a2e3b2e93d420dacabf1e3f5f7be4219dffddf58752d9152ba92f31a9d915e4413acc6b3e4dbc46100d10299310b8d3a96f717267c904defb3903d484480e1636f69ad16e995f6aa60d1c3053efac889a302412a8dda60048363121a0ff105812d80d4a8a9ef7cf7e9874209ca5d1f754fdcd6d9fca08ca0bbc7481469cb00c709ee8f0603867d477930263539a9f09dcbf18058a801888bce09fdc1a403e4a1aaf25d8670a2401c2b4a4e6df979367a78db3ec3600e01a81bb0350a8ba54c856619fa07f3905f9c64417e2f7c83592be3df2454070612e2895a63a145aa68698ecf66cd57b7daca199edea72a561999cb9a818526e4753559685cf3d89a66a12b6e8efb602eb7a2e911ef212a39b0d423ecd9fb50b21e1cda0957a1092829fd994715f9d517da1c51eac3b1f3877923e7906e22384d038aab7f600a6f91681025044251dccc2aac337294a01c28523a68649ae004f008291406a2a9429abeaee6de1345f8e0df7622ec785d123593ee0a70ee75ec3cd0de3e9876decafe7a5171662f98437358fe7522a03488047fc4a07d3d9613d67c367dce170602cad39ad36842f97aa8ca68f60161301c124d5ea64d3306914e168ffe258f42d26d4dc1282f5ecb7c06067c3ab852058a33c606ae6c837d14fb764ee684d0cd694aef416779f3842f377b331bc5d54a78de366ae6c718082569e02fedf8b3a55971df296f8aa71a08dbd974d70bd00b6c142e6dd77a62c3fa859aa1a5f41e09bf21c87eb63aa6f1c4f4cbc7483e3f78551088a83137f65914e2f830ad9976ef57be384fcf76bf5a5ccad01f15e474a9abd1190bb808378d7dbe514816b07976a26890cb13957f582ddbd3129f1d5d9c24ebafd81376f62a97ea172960ef971e81d073473166becfbe4db6177819ad8368fee3a6f7d650a7ffe054a52d68cf656c41c32129eee835abff0e583c6b8500d70d33d22b3b3e2686dc8c3200b1123cf9a524aacbb2267ceb528f582bb767a22b8c20b61619c0c23b367d41dbc360b5e18c7a94dc4fa3442b880a6a0316a566f954166c3f20cf482ed5c50743f91845128e228ab550762ffe8f02b15f10d3e02e6b4444bdec7cd2beadb6c80155c661bfdcbb6c54f881e446938d58f23032fda0d9cc492ed0be8ba377c0107fb42b820f9dd80f9209bb320f86c5f17461f3b7488446ff244b915819c1cf118e0befb700d7e263ce1a4b77eb9dd9345fc5dd4597243c9091cb457dce5ee2800689e1aaadd15671fea80181fa750658f1698a0a8cd052bc04f87bf9939ab2bc110bb7ca6edc533b724fcbb5e62022e734f55e79fa513b314100c31a32744a31e0af142efdd62a072640a02c03a4177b4430eb916117725a923816e79501838f0ed44f1a3264f08ce5e218f923719b1e8ec016686059e1b45d154f2d8c779bca763b11ca96a565b1ae1c165f5a1c4fac7a5d1c8e616b5db3b44b5b0890c137602f72e24f36bcb28f09887ebdc73b98246cfc1886dd1d3dbb6a508f3fd6547dafac87dca0feb42827ff64eef7eafb3ee392489630fb61bbbe321bb81aa00d85e4a72d4d6964dcec8edd5b88edd5b046c8a400bd6221ea5c026fe1eb46cfff23afeb4582dca65791e386b46190fdbfd2faee1fade71b0635e463bfd1ede16fd352d272138cd648261faec072c53a8739d38f83d3abd054335cb3175db27d72a28e6cd94a1e647182b9df0b52513294e8498ba403d8a0c4c9463c6bd76d28a36d7a292dc8743a4dfa0e9e4d2a2dfb92f5c68f438c3bc141a4f5be67e6937614ef03a36183aebab1c528d983e7a60df76b3e68ec989889f5921549bbad368e5439e0cb2cd540fe4e7d2cab38070aad26a841b98d8e2b8110b375f832d5cc6a3c13a159f559d44a20642ecece6e4cad809be51bb00487a3e6feb0b77ca72c107bbb9f4685d2537cf34fc47d656b8615680850788f4ead88b1ced7006753143b36719aedfdd682a045a4c86edac6d8a6ad675f97f989d7af373d04f07179af82cbd627567d0e08180c420c1a95d3fe2f84d9899d0d4b1dcbcf0c845c83fe4f1b9fa4751549c5fab5c31185d8d43bdfa3a2281008f0a4f2e5bfd4155ffb51ea59cb7018f00335dc1562bce9fd13411ae63522dd346485b64a1dad9e2de663feae5cd04d1d6f28367cf6bd54656e2da62aa2b6d146778be77bdeadc0fff602aef6cbee06e5c561926fa7f70522a35b68b88442447f62193675aaa8158f38abef35a3f1548e384aaf253331a6a6e848876d633964948e55ed903bbb2bd76a36703b9c32cec38ec941175a449758b82e23ab97c06b0a861123aa4c1506ae86fb26a3a6852e78846ebdc5a8ab2ab1e1fbd1b4636429a0e026d2663e833503131829882c86109d7613dff7d3b47a16af8a32ad84963fcf4174bb7c1f6f514f10c5263b7beaa44213df652d357361c648bb1aecc0a603dacafc15b324a28a12e20ee3196378757b8b51459a13a5bd6b3124c9636238f70e2eee1f593e894517a3b84de6772e8a5f6ef81867361fbdb4f6da0efe9d3bf8c70926c2b91f84ba09718c02ae78384b91ec46f625359e155799ab4c882f2488cdf85ae0b94ecd186702e0d70c4ba84b40091b585305c4bfbac976d21a9121ceb411f6cad665308205e6d02e30479356b4a9a4c13cc9e28bf9a32c1ca4f05e8ee58c83ceb62c22b84514a160bbdffed3c1576e6e0ec89ca2737ecd5f4f7357b2ba1ed8d8993b65d4c2bac752652f3498c64a61e4c2c37d2af922b1c9b6d0eec0d72dbc1785688689ec0cccae7831183892d571b4f2106a6b202d21395ceaa062920db6297865c5bf751a53195579a67c40990acb9f830344ad1561b0398b1b99309866639c8d4725c2d4283e572cfb5e41c515497f829df28d74527194adf9d609db2d837df0ea543c4bd4981328070c4b1cf383312e494d25699d9a1f4c991b1a4fa07cda9d56702c253102827f2d779a006767fa3e9f14a5067bcd8255c8e34f209fff88ab986c72f28844d186360ecef9761e25ac2e090f91623ec5da98677551215705242a8e5bfaf2b853d1d40abe9e583bc2e3fb7dbcbc14876b4dfc9d0c242ff7e977cc935808ff0e4164b0e3057735afe36bdba0160b647b57eb0b38af35969f6dadcbc6ece70bad3b494a0295a96df5be61f11def94ff8c901dc271e2aab32eeed3e72cce5e858f4a5b7612f801e6c213ceb8d2e2c0ac9d19d8f7be512f5b9f11c89a2da4bf1c203bdf066ff10e05671581630042e43ca4a8b740559b46ad2ad227eb6e0c74c3806f25ef1f88bdd11a348cef7b7f4b3f15e1d35e0d434638243fe510b65349a7b4f6874c5394cf46903ecd460aed50cd4aa22e2ae201bfd8e31e635937ac012b4702ed8212a5bfcb0e801db77a88a2e2d76586ecb144f7a6ade84d101ac34285ef8316addafb398c67e46eb03cff19e55b85e95fa1f7dc7d36d6b9a0f995c6ee2629981b2e00d739a784821a1549436f23b72479c18450f437bd5057460b0839b44cb6299b7c1f5473cc298294fd2702092f27b8240da1683028d996ee05af7fc62754be90d91836758e5ad61709f656ec72eba53aa8af0556246297262d7aad465df865c0eaad94dd5857e5b6376a48427d1519c1afaad4fa7f333e3652e8966d8139ec4ceeb8ca8832bbe36810ed33c4e339311cd3b5ede839b80f28077858140df183dd7894181b7a1fffdb720f0850b2d58a3f69cad387e9365631ebe0c79e8cd5b86473ba97dce4514d788e94ef78f436197fde8424d6744a2bb66aad15e62faa3bcf1c4ad1a70592e17e670e19d05ab08bff0be95240258814badf5ca933a7d53e8b0a192c5af23fe79eb23b6c18a38e3f0ca77986225f39cb5d8d2ff48e402db6e120f36ccdd297d492e0d07b0b776e522c77ee7acc770840a797223086dc903620740014dc1e052d0b4b1810635b0b310231fe56d425a78733e21ec2c9381384e2e2afc425c356d68a6e6f662ca71fa582d350a34c0303f605402e400911fb93b1a9bf20dde80918bf4a8d8a378ddadd51437880e865ff44a6b9b7b631237c0f8a3c197e3f13e3b34c038fe80bc03c1c99c6059f1ee8ef4566c5504f1e47843c7f2521ac4424e11543fc2a7908b4700f9f8cbe8e8a05281e2566d22346333312d08d71c0d0e8b059bc50f9e4456fe599e05da874906cc9811453d94cbb21667582bb6b7414f41eee03b0dec2bcbb7b0d305492ce84eaaa3507a20f8341068e929d99d0618edaf87447912956b79763e3334ddcd10315f7c61e68d1f7ca98b8a60fc84f852a49b38a3975b2cb4a3729040a76a73efc7143c82243dc1324dde5ac0bce41dcacb606f208dc13d050f408181830147fa5b10d39da99e9295ffad3c586738ae320d19e2dac42b98833fe096e30a32e4e36d91e8527263e1cb7bafb0c4c658aacf46dba9430214132037c5a49e09a144a9ba911cc84852a9ac6008bae7ed4f02f473605f19086167ee1ca101a4280abe8eb54544f0d1869708d650774a89e028f7dd1c0740e715a70c0b820e9ea84df063ac02ac86739494e1d6150e0eef611fa46b2f64f5e7e8123d75451f63b1097dc172079d51a29a3e903e5d2417bbc0070e44c5c786010067a4ace1684a0b52254e775a2a018e9ea4edaf5ef53b98b201672a4fcd500acb8c24622a357a711374ff722a5ccf685c378a10721491e3b46e2911566e83da14c946070c65e9836ba7d43dc730107228fd187b3710b5969f4c695e95cb7dafd110e09795c5c80d64e9dabdd73f2c524710549faa44d6dccbc86db612372a929c92380c89b979bffaa9941576ecfb4ae175b5b87b3cef76d03c04e322fb71ec0a69e8a0285dc7c131b7910bbaadf2e3bb419a278d496be592b18ad43ca67cb157389599395c92cd8b5c8878102679c2450abd0a37dd4958ee302501728b155c877df565c6044f95590a49cc2d9f391be9ed28c1a26cf7e8dc2480a0500f5703341fd76613490144f1d36c2810a02e29462dd4517f0d5bca5e1d4a155817dd656a0169c92e85aa25fc809577e77b8523026c68f5368afbca44aebe1770623d41c81c0e22dc93b405afaf90a0cdb2aee338cfd0911b265e3d808f29660cfd5540b3c2634411faca9bd1815c01514994f17aa252fb6c85ea51966a5b0f185aa434cb4c7b6de7ab3dc4e705d00d0b91c83222e06c50e899efe60f52df4fa67c9496c9d21d1d7cf8620fab5a0ef36926d841e0b5e18abee77c6c1b1da5b18747ce34cbc7fd0763e4fbc9239bd46cd5948852108d9879428b58c463e1b3911f75439b9d90b323ada91497b72408a2f50a664d543b13384a22c47a952ba81795e98b351b82530fe827044de070fb4caf53f987a28c6858d8bd96b334629dc1237a478432f8adbdb25e5097a1d1399bd9c2a161e1794ea6f2e79a48bc5630e604d17fb5da5e4a839193edc0a7d14ce2918efdf63c4b991278cca2d40b5812ac3a074fb338966944d9a69a857bd01602df573c686b234929690b8a2d7a4d84d5d87bdc7eacbbd347e6e116a17828b8e3eecca36a59c404cfae86a328d4e4a931be0ab640f12b2ccb4edbb73753b4fb19f00aef1865a9d753344ce3950980e24b1b258749c405fb3bdd6e2618e38b90878bd0f80f990d5b0fc9ef05ecee038771d6187c0d0fae2be2d43d128ef74a4cfbf5ca830a668cc21bc881c86c65305feb109a21c070c80fcd34790ec32dab4b7d86f02d444e1434641d60f4c2f55ae0d67263b76a6519805444df6ea2e4619e3e651416b4b0e31029c037a18f247ef412adc08f67b376c4a13e31f50905ee59626df0f520109f86f9fb8fe9cc340e3b9c67ac44b19d04d813520318e02c306dbcd7a00d093b5de34578050d38e25a7e1598d02d1d6608d61a109e710ab6d3b524d408524f448f78faac1708f23f460fdd1745bcc2de40210e821673028392e0bdc58f5b699681c8059ebc8b826be551f60924d3ac18cbac69a371910858103ccd253705f6f8ec42d8741c0d42a377bc23853a376e3a3f9c12e2b1a3314e7845b860e3c2ce78bfff7f28b7fc3daf114866947e1c39d6cd03e56c9683c252f407674216d2a61267f50788c786b479c71602a96543277d7afd3b8f6fe65b1a2e70d8daa526ab80ddba9989f0ed196c5984a93421bf458a46d68669346a311108dd621348b2583e4ec68944753485825f848c3671ce483ca20be8328e9e1de48ee69f1d3cd0ccc4f8df9207b2fdc95fa069ec744139a8f222e5488b459f81577199c9c1e793f6d6c869f750a946c2600cd1ec6aac75805302d6cac660abe7b906985b10f989a43b69cf21974d01d68532359833658b2680ea0047cc0d446558242fdfd82e2401f3e7a0930ac393b1eb96c07dc254c041a70f8bec63b8d857798580db07ec12f9f0a28b759901e95ead19df8d8f3a9d9e6447959e7bf2abc5588c11cd0bea52d19bb99f9bcd33f90abb23551168329094e4a898cddbdf2db566df990840213e957081c0f7bdc729752dd343072c88740af9b8edaf9c763cc53a27fb52136596736be6811e040e3003094e7515323dfda52f97c86052177e621a423cb51a4629261983f7f938ddfa5934face44408be8240cea5b3290baeda019ae99a7892124f9610c47437fd713f86d17a11162ddd3a2f1c30554eb0aca5ae31a9a738c40334bfa7c99366dfc518e53f48d07af5e54394d0f030db72c8e9dc9b04fc53bf3d368c1ba628cdb9e914cc69350d22a48d153538404e063f6e3ad807935d0bf4d2d732c14c74b5944253bcae22dfb932401cb51801d4baa0e65bc38e1ed381b7165a55dab9bf17b06301d30501b129ce54181ef6ec4ceddfb6062e92dfeb9b7b2d140112138ee61b4fa1d24667438c431eeb2418417838baf70edcbad8016aa3697b149ebee403efb2b6cca34b7de421cb6d33315b86f331f4fb21ea0d0c7a59d3169ba240fd0c84c4c522b035b532bd51336da06be2b6ef2d2a726c13c61b4f7e4feb4fa0a539919eb9280269f7bfa9adbbd6e98c10c0157c4ecf713ab2eaef2edab836b74125d4d68bb3e453e1dad8d63f2f62b48ea00f485d37250b80b46946e749c5694f31c3d2045280b61272710a8170f3905401ced2e8d62250078ed1a888ce05fb96a99dc811bc44fad7ea025c9dcfe03c8603b338d03906c7af20a926e0a7fb1d931f1b7de242a72898ecdfa4dbc729dd1f539d7bf1e86fbe9188c64fc64dc0d5ddeadb7d2429b6dd917769a220646f110aa386d95ba767334b944b184fd7cec80cf99d4696d47ee032e3a2685d68ef72f0a2134c56909e24138d676c22723e2a1c2c2a49a9b7ceaac886e00c907cce797d694767663dfc83ced5f4cc25a77e7ae5183e37b04f9c906cee4b7b9c567d9ac88b8dde619e9df73e6a53aad436e4855a7ebe49935db4ebb666ada7abd213ef04ea265c26fa6166e9d8bed9e6cb0b7edfc4804841915237e124b6c3e8a575a72b9a35b9c304bc9a477a0f6dee42dad45f06959dce632eb146adf014b0ddc2daffd0a9633225a29e69a81fda243b2021627e30b672f510b34b0e53372bda3211f4bbf22910733fdccc26ef2fac69a0f9bac7be51c7b1a878abad60ceffcd3e65451d0c9a107b6d2f0da918b36a8c578382f83863aaabdfd4c8d2852cb15807409726585407900830a9f331f48fbefd572c76b52537d63b9940b1195716072c8cb0494733f339120b08578710e6d0528d40443ee95da628b32c92282145620c76de9509fc1603ea45027d5a300eb1930e444b6eceaee363e0535f20b37b23f6acc994a532f1e2e1e161f1c3e0e7d7e7e0c72da7f3125e7668a5ccf7ebfc7463a40a16a31cceb82c0ee00fe190931938e42b84ec650109a11b58bd09d97b6f22a594524a2903140800075007b63e6f812dcdbdef7e5bc2ed3b776579a8ccdc9556df65b5f573a272a62c87d1da5a6e5e0e3799bb26eb59d75d9335ad5bd25441b78fbf6b4d4881cd7575735f109caa1bd4b71bfcb1af79aeeacb55f5fde08365b5b7076b98dffe1e5fbfad765bb06bfdfcebdb706bb392895fc16b04149f00b7b75f8a3fb6bffd7e33dcfb567e2f3e289e47691dec3b5725923647553728f7deb630e7aaef076b682b3399588e2ae74e80cfeb8c8b9189ad4024f78739aa9ac1d6e193dae7857696a3ca51467ac6b85731b16ee688755d1f9b3b282ac78acda01f2473a450d50d768e8a4aacdf2ad63f134415ebfcf557fe98200f73a2729e7254b9aa1cd57e22c00c1e8aa2aa25144bbcf068eae1b399fa164bea5b4c09475700bb61038d55d8d699cfd69d9e2754380162335f9277cc438b6bc54fd34a736b32ec60d6395f9a6ff7f97efd753fbda0aee2076bce133cba153ff05069af7ea53dfa20bd654e450f6362148c040a500e1d68e8165316c0304002073d14ac021b200103962787616783aed3f3848c7e3b3d4fc0e828acc8cd38716297e644d0ae4d9a72c5982b5d76686139a26851e18a4819d655b2d0b11bb2b11b12634da9342342519e118d20d3d434a1219565c9a2660a8fd34591223ea476b099da4d39eda25cc03071549e31215426bdb00352c6c7172431ad2a6793ddd29125204c5965092a3633762a301d194a517e0d0db960b38391560609b3c2d8d70a3cc0c22429a784ec9e6c45a92ad540edb6bcd80929a1a24c5282e869ed089a1287981d111915164835286b4bca195ebb335c66394a146a9035ed8c587809150d1112b1cb522ba19021e1856483a5fc22a50cfa5a92c22e478f1d8bab5288540946c66e8516e41a09a3528d0409155c60a18532ca6cb7428b1395f205931d4676423fbc60955e6e3479da6959da958d408a32a24409644d89c48c1298165bc05839e164078664d725482946abe482238c068906859d9888241d224c9452da9426ceec489c916db89456aeec6a3cd99d4ab086ace1511a59955ba2aac0d85d5951e24499c21914b44b21cdceca18b2852ee592b01451c817542863102959e8da8dd1b15b63032a4699928d3514659bb5115b6a7aa089286b214b79630ad6b9a3516437e6832cb3a34431daa67654a3cd296de9499b14bb2f6d4c945dd64a2669764f64762aca94b49464554638d9552529ab0c49e3c2a2a35422c5e554666977c426c54e091b1332d6ca13695a90d91929c3a28574a18a1327a55792b26808142e263a765d52bb184e2a96cab13529a6ac31b133b166244d161933654db49461aa94659cec9c24f931a48a6b874647292475e4b43b5b2af2428af285174c34ad31a5295b2063a34c86961d922a3b224e7c244933448a8b3ca3034d4a8493d552920b299c5c30b10b5a134b53f62053fe28db4dd1e242151a4e7656494aa221e50eaedd958e3552259713a96649879a14bb176a4cec5e582bc7a429a99031531644cb6ec4c8ab8819b993344a765c8894597c9468b27645553bb51a9d8275c9e84496baf9a5965a5237f97fa911f15af2be621055a9069cd770acbc8e9c6b9e7b2d6dbe987763d420c527a8bd3867afb54840f0708c26885ccfad701f638c794fa319eab4d59fbb196e7a9e007b9c71356dfd5abbc661a7019403af34c36e316f839d9e9d0c96c7ebd9f095e6c600f8ddb0b363cb1e29638e2a8746fc54a659ea266f072acd51efb13cf48f469d4ebfd0944163d64d5d993322c4b43431009d7ea565c16174955785d7f7de2a88dcc7b875805ad741977643675d5a8c1063d0e974692ac2abbb6925420cba8fabd9fbcbde5ba7fb9e966eee806230e46ede7b6fbec331e24ebfccb0c0e426de14c0d04cd7079ad161fb762846b1d32f3334528c5fa75f66984a30332ccc9018f538eebdcba8f5bd37ef30dab21b3c8ebcd4952e94d9328e65acd87223ff7899b3efb14c92be1fc3debb77ada825fc72e4f555462b0ccbd0d09a6a8cc54e4375fd18ace63b610e439925a851db34e79cefdd8d3177fa556645590345df6d9511ea1a74b911038f67349ad40b840e23f6f93afd6ac18516cebad95303aaa1b91ac52c75f08a21d1411ca2c939e3a2733f83ced32da973cbfa15a64da75f67561ddc626737e9eb4c57bf6508fba0cd48d88563a3836178c9dc8a0e8af63323eae02db417477137b15b927e95bdd0816cbd4114450f6d028505dcd3f110c65bb6b608e938e7837b86ac30e82e66b2aa32240b95155c3c69cb0abee5450e6fa8b2104bc3c5d672f0500d1a3bfd2ac3b257c8b76c0ab7573fd6b80c2c972dd9b9acab9b3b180c582e2700caa1039ba13ebfba001763a81cecc8ece8a4997177fa5546d5cd5c86fafc96adc89d01ab5667ec1b7a4a321dbcace58c5ecc84b99e97972d37acd661f022450c751ddecb1000805e78d83e2f5039b66cbd2c855c57fe2ec9a83bfdf2a242c7a1f41274d385cc4d9732de652c7709d35da6ec2e4ff057971d4edddc09b7805e78e4a1d08db7cb519711ddecb500abd55ab448e11de1fc64694a10bc8eb19230611886039d889f0f00d8a004c0c994709b6ce8a65dc864408c41b7b984b703eac561973a27399b1fc3febcf3c5187309d2314ec738c45cac3ac618639e095d759d7e7169c1c5a89bba6eeab2bd6581949739ef510d4af040bb07da7b1483fa94c741fa3c9eed793cdedb1e865e75ba11e37efdb8fcd1a52e47d24bc9f13e81d48e2101ccb05b6b6ae895a7e02234f2006ba79808f5fac09814da298f1736d62ae2c4da43aa01ae210e9003a32e07f348f078423c11373d4cac294b922676e682338cd10a632c9c431c89d31f79899aabadb556dc6b035038cb40e428115e0822e4055d2122d688e86246c418a40326a8b2c110268fd7d36079f8e3ef704510aa12907eb2b8eabdf2172340bbcefd5b2240c888abbdb7fd20ce15dfdba6f7f6957ca43cac6d80f602799073cef977b6619e7f72a9ebd502620e0fe22ce68f697d0fb4d752c7f63f6ffb6d86b69a81fa262f99c42691f55b43f0ed83b78f242f7923c12b018cfbedf1d7cfb752c703edb75207bcbdfe5bc96def817a075bc50f3ef043a0fe81d0dd4c8e4060b551c033cb83968671d67a631d5a8db1ed26616c7169565b49b25612e3d20a95d6c8f62c91ed5d9bee49a7bbcffb315e134c30f43e39aada806a42996b0937ebfc63b47fb90ed244676923dc6ea5797b7d7dbbe97d95b3fda6389bed76bbfdfea93feaf0cd9b879f9b1f5f8b20ffdcdbf4df6eb79b8de3dc6e361b7f9cedf61f9cdce7be83c5e1bcadc4d13cbcf98dd36268d56e86fb36bd71395bce5bb51c9cd2e4233992a6f8b534793dc2785de38ff8b99bd6abdfaf37e5edbd94589e1b0826c5f28496a78a24ee6f48f1c3f0ab38019c16dfa2591ecdc32c0f07ad99e539b33cb5da74999baa1984dfc1d6b5b0e41d2ea873aa7a45eaf454d114f20d21685e8f0dba96b92bdbab1d3a10c1f27cff9198e4f9184d5dde9f9fd735d66eea7ad8cddacd5a9670b7e5a90f84e1ded348ad9b56addfbf4196c7c4615dc872a03ef7345a63799e8abccc4ded9c10b0c65a972605b39526c766ce1509f76a5c8a15661d019d9e2b808839db6c2d9877ce172c73540f86e3388e206e1c6f37f043d87e1b7f6e1fdad20c51aff822596dc589b83a96e60980cc9c2b7a39e470b1e47f3f044b3a9626de552a8cb07e6bd724494932d219d0e9999464cbb1039950e63cd9a71daea54ffbd58f6d09b7d32c7e00d42b2ecd2eab75554b93feadd83e367357b8848b7bfa549b389ccef6868c1af7b82700eecdd0da30188260f8e0beddbcf839240e79539a6389bb8921589626502ee44d494129acec07b199ab6a41afafa41fa0d333c5534f91d41bd0e98914d64d73043281df9f124ce8fcbb3edc9e3fe7aaa859cbfb7a875af4803f1547dba33adba3743f0fdf767bb3a2f58d73338ee38324589a39557d2c6f25dd2502b846b1dc1ac49b403fdfbd5fff7e8eaafeaebf430ca675e427a5ae542d5198f7831b0ccd6cf9c1cc6daf65f9672b11c0751fdc5ae15838168e0d11956e9628140bcd725d6b5dbbd696281c0bc944a5bbb15993ea5f2bb6f7dd93bc60b6f7e9102d140bcdee1dab1bf0d77bebbdf7de7b6fadf70d3fdc7f0f92b6cbf6be502c24ab6968e159b76f8942b1d02cac4f4333cb238666bdbe199ef55ad67a536fc22bc5f6bee7718da67deadfdbefedd4a7d4edd3d7649f3e3044e3df178a85661f0743b28bf3856438ff955ffd2f09878ffba97f6fe6be37c35e936c86efb90942178e8562f56f48731c4933d73ffddd6ad24db4b6bc7dfb6f0fdebe906c6fdb47b475babd77387613bf5d7edf771345db4e4bd26a2958814d0cc76a06e2a79fa389659dda69354905fabf9ad4737edb7539d8a79b217f7e73b45d3d7f38b6dfd46a5d6f33afe980ede9b74f637583706cd4e198698dba0982aea16b3a0741d774d0a86bfe811f6889c2b1902c1cbbd54ff1715e3ffdd5a46ad4f3c773d137de10d7a25af4582a10fcd59c6dae45d5084858410d654e67333caf67d39573ce39d7daf270acd588bfcd16d24ad404c4748292ad05612daa1be4e780fbfa35c9f28415d7a2ded668721e8e61e7396ebbd6045b99425a896a92cd48bc699c78ab45958867300cc3b0dfca500cc1b01ad5b083ad4567071d6a1148d6ff6a51cda06a1244282aaa457503dcc3c7913d36437e1bb901775c5437088bf27fb58887365a8b8c72a0d8bceef32c8f8e579bb659256eb3657eedb6e96e6982d059248b6489ee0e1268bd70c82dcfa66f89ac51ce5f7bf6409370595f7739ec8000a103a4076c0f5310a4487503dcdb711443c8cf71a459ab26f10ece5adba4cc7398c9b034b107f0b325b23cd6e809a8e9044b14be35ca1f96b6c41dd45c5ba4ba016895987435d76baea508b2469c73b7a4aeeb6ec9ef993455d0f3d3f05a13cc9a026ba49bba6989ac51dda02a7533840eab4d42b2a2d689e54f7e5e3f1772d0ba7ee0f6ad51cd20bffefa22c949dab925ba8516bc7ddf77fb1e2ccddbc7c9fc3652df48fe368b64915690fbb773ee04484ac1977fafe0467e6fb348350315f4fd22a8eb544daa4a626deaf94115ece0c7f9b9ae8764076c2f3f0601c448cf39e7bafeb37579efb74416c9266d6d916a06194d870692500f3b3f22ebbd4ecf23209d7f9ae5f97ed77ad6ed199ae5a9237ca8e204f2b777eeeae3f979feac99415431b4fb39aacf9ff3744b8a51307aea3b9d9e28d07a58c7fd2c12eb39e7692a775552e544e54c61d183cd79ca4de53ce5a87255b9b0bb77dd4d9adc49b6979f935bc9f6f289747607747a2229d9bdf7a67beb0741bdb5d65a6ffdfbdbbac481648e22f394cd904170ef325705f207a9589ad8c37dfddbeab74080ba4ed0ea01995349f18b4d641b9c4dcd62b3d81b4836130818c99154228795214e3d2c87121f3e9c646132a262ce84785415c918b364ea8a0b47e7c98b8f1b50408c14d16bf01a8c46a3d9e7d8016632568031d3a6853252a70e21178c7515e9684a718328ab5ef083f39cb98d18df10cfaf43ea3957bde8395f40041fb15193d5e4f00cbef80bd371615e21cee70db273aa859d0ee5bb1b72b953832a49bf279593a7a5064420419a42254408460ab0d0d4ceb25a7cf9e173796dc21ecff600d0e9498545551504346dadf6da5b3b09903eff4b025d59a4c197db4ccdcbb4c5bc515364ba79a7ac8f79abae56bf7f8930b93b2eb7b562c59a74d39ae574d39ea1a5ade9f72d98d618a4c266f4689124caccc7b44f51ddb45345ba69af7037ad56926edaaeed635a2046fac5e0acca151f238c9c35c9d90c8dab9b556d8d0db242d6480ce37ad585c3ac46949c52fafd3a75a95abf3568a99b95e8a69bd5686aaa9b35e9fa9855c904760aa99b14ccd64d6a522bfdc6c7a461fdfa9894acdfef297b36d85e7d0e4460f13ad1a30615984fae89d1121b4352f4d0e173a1fa7d5d5abfa7149b9b9e0d1cd0d38697d38695ef05fd62e9971b526436d0aec67953fc1cff8831c63c345567a38a05c0803105a1d1c28ae263958c8c6419bb71540645e3767ada9072a53cc0003ec3089054138c175e3590a2581c518124889912289840e34fbc2ea70d2362671418cf0baf2c8618de981e19444e4f2b88ae30d5f0a1695643ca94908246c895cf76eafb75565c7def3131678daa1e767ad6486104a8ffa7daf8de79ff033a58c3ccebc2db3252cc2333661dd5207f0927f411e31fc0f340fda3dffed64fd778f8aa213ba1944ea811b55e2e8337764255d91f63f4132a6a8a4786317efd5688d3404133d06ee2449881cbccd3320ae0c3083a0e7328805e4b1e849785c70290f0cddbdfd296b7ef60b969b9e5fc71bec325cdfaf4be599f96703bff0ed604ea561c9fc764e44fbb6e9aeafbf3e5fc869af7a9153fb83da56fde4af3f6f76ff771f77170fe06e74bf05edba979ffe63bdc5b82091dc4f9b10fded04bcb1ffae0587e601f7cf203b0923fe2ffd437b3ede99292627803794c46dcefd73fe283ffb3c507ff46e6dc3f4c35c018e34b35b8b75baa81b5d67e1f90a2fede9637327fa207fcc510e8fec93f867cf352184d0cd866fca5819fc9bd497a35ce38eb9cf3d659e79cb1ad6a2622ac6d52adb55a4d9f52a36a916c12ce18e7a9aa4e767a1e11c188a2b5873457657b1604cdd092a018dc0722ebe64513ba67d70cc8685a1296445a55abffd19cf37f363224f953d166e339511b679ac59ef0b4f091fb3b58a3b9434fdbc15a6bdfac4496e7664cadb595dc40076c4e93b4f284dc3e520971a7e78cb3d34593515211519111c6f8864646b92a1ed888ff9ea0761e95d1eced5e15c56d9f2c53a9bb1a4d5dcf366a6a5f1e0c449be4513953395575e481a8623792fe47dafe411c0e87c3fdfe11f7fb71e5fe5024cd10382e8a614ed4f7edd7cf37dffbb7d53d40fce79395d329a88395b7c208525e92b1a7ca686a307b2cf660c507efad624cea7cc00ffee09e765cf983ff0b310ee31e7fb8a41c6ad1839bc6e12eeee6bf5239f775f7a62fb6d96cd8cd1efab463c3e1de0c617bbcc9ed61cc79bdbdd9734fe81db4e2070bfebdafbb57e7c3f7e387ef6dff233eed62a9f3c1f6dfffd8fec3f9f1291e49123af8beef4624839274984510db48dbf1fdbed7b9f4bb250822507bffe6de94e3a7ebf782e0bdf77b1e5a9edb7fb9f1f68cfd7ee3f73d7e101a3c18c11a928feb3ddd5b0e692b4d9dfb98732a63886eea5c10978306ab617f1e68d7a50eae3eaed607c57a887e136f2450c72488b08bf77b4e9a273cf50f9352395fafe8418ead7e0fc993329a5cebfccd79408200523c7ddff77d5c04ddd4a936db07d60fa82dcd8a958c9f0eedd1df6fa39f75e8474b1d9bed6d246f4a8f67cb53607ba0b6f16a04082f9f87b9a30448cfbc4aa9566a58bdca99287c6c792c8ff8e187e1e72ccfedc30f432e7efe9c2ee7e7f5c0fc22d973233bb0bdfce18321c9bf8a1e763670607b1d72cedcf6f287414673a7e7d0f61c74e8202411c0357ecf80f1dbdff73972dcd0f3e9e71c7cf1840329ae94625acd10bd96a64ed525d5f9a0ffc6ffc7ea7ce0afff13bf3ea845f175958176cab8ea37bca207a1cee70f3528c8f62b7ea05e1e84506b075b4fe83ab50c21f7fa75775e9a2774dec19b2529e630869aeb104068e8f6eff738080ddd6623396fc9485f873fffdcf5db38d5b16508996f126f4a6f07faeafeaad6d65a2baef55a1cc26215ce103684e59de78b24de93713fed268f3eafa7c192566b6dfbbd5f87e139c7dc66b3d93ca83b2968d0a04183069d7b73b76fcbd8cb14d5e9b964d613d0e9b9a4168613d8572a0596c7fe8902d1c96466e5e67c32293999b476befce613743e565afc40073f0a42801fa8d372c4d26b3e72762e0951cad79c2e5818d93059af224225809c2ea89c2eaa4e17504e17a7e5a938631e7c6d0f6143b839ac9d761387907388acf3b9e3fb817a755cf64bd582a845d0adb47731d4d15ec330fcfe86b6a7b7d7787fbf8dcc8d43c6bc71d43deff4e4e1047f01cddaeb83f55ab37e08d6a4fc8e60f1cdb7c4956edb372d31cfb77a7580307c6f2c374e65ef265c6f1501b96ecb878deb3a6cedb07103c4180d00646c832ff8a16d5b663eac6ac39c5b58a89c5b4dcead223ecead1db7565dd1766915523ab5da3c3971a09d38b21ce028c7f8edad0505dab498406e80122319adbfe43bb6b14debb00eb4add4d90cf5bdce2c295c5ff0e3d746036c8da68e1f00a983ee7bb5d353ebec03e8f4d4ead26d9d9e5a61dddcf9b48014c4d14b955c5033d8377b9d8e3b3d6525a9067bbc978b7f03c0de7be3ba48721ff1435288a8c747fc21ee73a33511f7095f24857c6e3fb4e383fbf1877a7cc2070f407932d05e4540d7bbdae36f5f6bfcf49617540eb496b5df1b686ff7b7380a090dbdcfd08e0fa50b0047f066d45df2a6df722128da7200f0d9c2db7d4d9a65af0fde92530c46d2461d501f4752ba0013471b608abd3eb86d01e03bbce96cdd3ca1520cea73d286e448520cea83e40e18004de2ad059031bfb6d2eff6b30392a9b4661161a5f5154f6ba5b5d25a69ad75848b3968daa0fbde4e4fad15e70d2f3668d07d6fa747ecde3bc20541772002c540030f438e7bad10ea26ee60041044e8c1c4ed87390cbc51f73d82bace9246405d582f1128a15132042b7a7d4c82e0ec11d4698fa44ec16c365bad5d67dda446ec9ab7663d67bbe970b3e32b8a012dede804567b29bdf8c4523bbb9e74693139bba28c9c5d4c679752dad985747609618c37b7727299517a727529e9b96afb6c1f975638f5811f985798fce4123ab94674138b75cae3857b79a0f59ced812008de50843a7954199b40039839b572409d5a4c38e3acd66dd2a94544e6cb993516766649b16c28353ab35438b3a6ee992584d233abebd2ef03c333cbe8cc6a8a01d64a2b4e906aabbda9b2d75ea96ef2938751373b3d7944ddcba3e986f8e2ad261e25cead36f6dc52d373b647f78ecc85562a4e3dbaa8097a526307151104486678a1904a31c4a4cde61c06d75c8bcd39e70c83e73179cc11962462844829aa5d584da19a2758c6a458894dc1fa516d64b143dc46163b6473a581d523b4974a2cbfb853f214abacb5d6573f29a295c03af004c585a0b116616a8c70a5c889d550141f64a6589d5b9ff0c4440d14d05c89bd0046d75e282a0a42d2444b4cc53c196666d66898107125c58a8a3751418465ac091b3067da64d9d8d02f2e3c013384450b5b65a25c3062c6110c35395cf858e2e9fa802a2259b2b2942115a9590c688a32634ad49ab020ab21a26e9389ec5003a680cb2910d9b98ec09abac10e4031e64989255f90d0b6b092c29bae235c61482bae88d9dd28eaf1c5e4c4102b2836a01851608c1c41e165c68cfa24ca45c3a16de5c8c27236e5c90c9f1ca5040a2e4868425e54f0c94f7ace39e7bc556828303acea65c60c1340466e4cb89284e3b46e0f071a1c8281206830418b3179ce0989d55110273468668c51a26c282658a96276d582c35b9e00421271c54b4702525dd11b8b08031e40893530583251492e1b8c102e9852e2caa581f2e35ae8a96ce9a5c61b2e4031a263e902ca12172044845d3971a34aeb0d2289d3868c0aca8126222e5426aade6c89668c7578eace90e16ae5cf03a82302654462dccd808f9e2ca204a97142c5e441449635373cef95a225af51a0b4a356ec4e061e44a988a0296d8176546884c0c102c1d57d67265958dad383e608cc1bab2ea1246851c4537335d50c0c0889a827cb5b19ba8098a2f276276342dc9bd60b3a482d6131a4aa0ea556205b5732ed2f154c40c0fb4182692e0acf062cd0a423fb474ac5d0bd475c60bedc68e23426a48ae96e430aa7206478bb01c3ed64a80e4480c12132f86a41c1143aa295c2fc26a14a9a0d5b25821858cd3c58f3142467434e5785c464092a0a1a234a58da512cb2f2e95587e716be606b80568022663ed2c8bec4b911d5c40115b86a825c97202479b8c05e6bb5a8f5aeb1e5deb9b1be904c2181183034c9429133efac88e2a55575d52a78f4ea16bab285da3e18874adcf5d96adc042cb21428b1713208ca15a7302244d05222b760e6acbc84c8fb0314e5e38db8a10a5302f156a0cc9495847c2cb001d57b735788b48e671b788641e57c8e57153582e44c75ed3bd203bbe80c4384f8009a2428d568a3224c43c29996983cfa8896d11723271e40588181c1f5a9a6411459110b265cd866a6a438aac6c392df11f6a7619d8b10585020a2e1969a8d59a25c14a0c5692202a5288526d8081221c0da13aa2a2b176860db75dc168f66275b966585dae998f1ba0115c4c46a0a892d6c54995965a98d7909123a124ba2389e53c4d8d2b3184b07052048a4f0623e5460e17a2a098b2d4c36a085b15f1ae2cd62a48a7716a4185132c535f6a3e5985d6181b3454a8a0b2a272ae3da66cc914264cb0b81cf1804a23c3c55409245554ccb03100cb4c918c33c91524f587d50a174ec07ca1806542ad05cd7cab671ef95e60ca1985448d96134beb8a051c1e28506cda4cd1f2a3057e25460a499ee090716496b5630b27298bcb894a90205666d6f0182502c54684931a4723b0aad4d5942e410c8f34337e08d510d2e66275b9664055652c924cac21e39a9a408f1f28700d59096303abcb35d34971b65013c5a64c0abee32a87ad8c16186d720c21695329971c57fa62294d6ca0a521d1d25491b09ee07c9ebab0b6d6848152ab21541d19cbb2e2884c912b2b2c910542655f628caebe5ab05462f9c5cd5462f9c5d5f105cd101c2c60b15106cc272739e38a5a634afb418444798eaa3218536d890d3951d3f44214ab1d63400cd2b8343d19e3a44a098db5d65aeb4c6d58ea1b29a663c739bce9cd90f57e7c01911197e398ae4f2b181cd32a2298bc8298f922b201346190a03491aa42e3a3cbdc387e169c002269949272d8903ae35557627030c0e24a0f20609cc88098a00b8c9a116a5d5836ea900eb29b027e6c413ba2844cc70d051c09615242c50a5f6038aed670706c9238ed186bf2c40900f2dd019db9a8a4ac9846ad64687000000283160000200c0c880342b1240c6238e4e4031480096ec4405044938885a224875110043108c33010630030c620628081d010920d20ca510a7d0b9529160b3696980b65c71b089ae00c5a07e59316469478d8e0c21a0b8641927b33a281f4dcffeb16e2d5b0d8ab100e50d15a61492f362fd4bb8aaec867e0be9e78da1b92dadb71a4677114ce773b536636abc0ee6e1f1b6f48025ffcbf93be40b5626c5abdc9f5831f36e676206043f5a16ef9f9a0dc8aa480500a51d49912dd83a0f691f7a010c8550da7480e3c792b7237738c22466758f61f362eb06d68061e57599af88c7047a405acc410a4398b81d82e82edb71f4ff05540e7dc117322dff238fd2ab256369a8fcf6107c75c7a8a80c6b5c5bb00adbf7e4bdcda55103640ce7c75193768fa7d29b0cf5bdba97f460e4818a25f7c518957dfb656482093cef68b824c847c6e548b5fc59a4450690a7b7b2fec9e76b6162e58dafbc8313689e4f572748826626d1f2302ff02f42b65f1b21aa565d7ede559200fd6ee40f069d4af6d120de4440972f603fb01fc47a59d5b0259b43d7e5d5b5683dda4ddf2a7a291b4d2df140ece544eb5ee7b1723b2975111774815129e517922d05308d20c1281b5736a9829e3e06197659365edba74d67f7d969e76c40ed56cdfdaf0f27278aeee12190e7f53f686b6bc5c886bafc528561393138d63640ff0cd3172ad91efba6ade0df3b31a8547d8e93b63b2754441fd07e73113587d5f607f8c2d7812c941e0eb771513130ea481d95647fb0775ab1f141481742b0d09cbd24186c6310f7659d91e465a3e81b6fe94bd7c7241f7cf3bbbaac52706ee294517473390b50f84ff578ab77941835a0280a0366ed1bf4a6e1ad8bfa8aeb04f41498a4ad04f606f538569864589f73e9ec163e82039cddfd50f000472bb62fb9c4c1d7cbd75f8bf95faa9c3fd64427f5c995bbc87437a551b74e9004418e3db58b67c028234dc986f6f1a3b8790ea5b4c2068270183d61adc9ec9e7e119f4847770bbcfef2d46e082d3b9649516f76b541cbfd0989353ec670ff961103f03cae93463b1ca4934ab9312ebc44f69b5bc84436c01fac8e1f9593a53043a9f5e7252f567e680bef730156c65ec5cb7dc66466745c504f677a55db72f707676b6eb6556d5f087a0598c95438f522a16e8bf275d07fd7f6f2a33b176744a56a9b54e7953fa7b9aab8e5d74dc0a0aa3367478c8fe0bfcec8d387d0c0511e83f1c5fa91de6aa120671af510c19bd57d5ae1da99672af56edeea1604de292ef23226ac9f7a4a184f908e692fa5f94004bdb45eae32547960918001d0301533156e16553ddb4441bb7b45677b8db2f0891ca4a489b48e19dbc796ca8b8518001c017c612f9770990dd27e8c0bddfd3f1456b4ad87804c372f824a5dc42d365ecd38c0a57596c9c1f41c62ced9373ce456ffba5e2ec6a4eda0cae6f2ad40614d2dbc1f1d36f93a7c497e94bf611a38ffdc64a64daaa06130e182a7a2f6032be6ffffc6d4c8d00032643c6055ebe8e4f5a1abf01e617ce432b7fb02b14bcfa106c1d71a2fa6acd75ce412fb8fb171e0d5855f5e98575ac082cab15ff2ce379fef6f3d1244909677fba148dcce5c1f72aa519b473037a268a6404418f9f3e69da4b97d07d968a34a826680ae6e6c9c3a22b2fab007bbb8fc7d8f33ed005edfe570b4baf693e2e1555e61fc32876b850085602084d3c6f5c32e0542fccf14531976a24fb123ec5ed0c80e0250d267f747d7c80ac38ce40640bf25454ff77bd1659fa9e6d43fd16e9cd8100e0b520186b207f5366daef8dd8da52d5c0c2887a7877e1b2ab78cb0725a5100b8c1ef63a97a65dd2efdcb8f01ea26a56eeddf603f03ab67d7d58727c5909166b2f2350776252a6d49fc4e6f6e8b8fd5caea9bbf18178f269f5bb43899009bd6ab209ab4954ebaa5c0e8ed36ad97fe1c5be21011785e11bc790a9b7fbcdf1d0a584fc8206267715e1b625595414715263f40ae263fb333b47cc9791f8531a333991bcaf51832243381dff493bfee819fd6947efdc316d14fe565c0129a9fc5cf674055e058b7fd643ccdd5fc8df26b8ace0e5d2d67354a8216437eea3397f46c5872ee9265fff00efa7006a5b7c2e634697064ec9a7cc43b315a35fd2537ad28c8a74aadc104fd03ed72a650f99bcbd06f841ff5c4632dfbd779aa5c7718f183dfc8735b90dc8977dad4e80a7db7e6be7c5a3d5ef0844760b90059e6fe119a4641bcc006f0fcdde10f3e93b972f0b91e8d56711ba0863362450081a850cbc4827787d00d8adadcf9b5695bb14a0d10d8a4eb8bbf2d2408a94df27d5190434c1e5c1c84f1e2f2077e646a7c5454da63188873a852570338373291d78037041309a25349542b9adac25d591221efbe33d1ea90380d74297e7c01b3518f0abdd87a2a36150fad5180c8834f2e7dd47c36afaae3552404b701b1b34fe54bb1f69e1ae498dd1e1e48b2df30ca7ba9a76a0124b4132d00ab2ec9061f3a0564500306a0ba8d774336b97c731eef4206b5dee5985b4bd96a898b998a3b860123d1193e22f5b031ecaa4974b6016704160d35f279423641c5a922813a695b377c7410472270438bb18120e6b3225165a21d37b3db0c8c092a04e9d9ea42897e82394950348a09730af52ef8733ade890e14b46cefc5a55d4baca16b047ff5f421eef1148db82c6cbe3ce0a53acce2c78dd5677a457be8f6277d6c803d13df160b545eca95308143b1f66d8978f84010fa289be79156ba959246678573601dabeb89b2b6f4c4d59a1c0bc1739a536559f59ca26184fcc088675f9a4bcd080fed294ce8cece5abd649096141d894ab4981dd8a0d3b0a57a0064311efa514d6b63326fbc69a28d0817b55625c08f2347dac9be33af9828770dfe69afaddbf854a893c7a34caeb5024f13774a57d022ff2dc73656ad1f1bb5ccaeba2612466bb5ef1a36b7697eab5fc128164b7e07aa1abea232e0f45bc2e6f37c7411723113f1529a5d6f85c719797b59a12e351b42155ff08e87f48ff055ccd72d74549840a903e527b19fb120b1489e139322f14c9f3d949a962508b05ac31c36e6e048f609c01dd58ea3c4fd08207ccce700061292b723c299aae4682a6aefdb5a8199c25a04b7d6f73497e48b92d640517b2c3fa10b8e8dcf8a4ddd7695e0c3772112ab6d755017042f813187a84a524d145af7bca69aca4cd580e9d5ad136b48c6f055b4f15859e55b63bc07689c8e751681b0db7664412a5d9a9036edb4f29d540910c5cabfc2ca201e1b33c568ded477486d7c9c048f10919e2a2dc985c527c54802af9acc89df6abe46fcc64f1dcdc4c057594c6be9bd2c4607ea85308048eae7ce387c48ccfbec5f25d18a798c9c47583a2c82417967cfd2be79cbdf3e03599b67c4babcf2dab05f431254b6c530bd017d07bcb0ccd009c62e694eb929f38027217ba62e04ffac55828c550aa73a3c4b9d025c054ac07c509a7523ab59ae200a7086957c82ca1f997acc891f213ce8a91e0d63adcda3ea1d5ce30255d0cd3d8a12b50f9a9660d29da57e5523365dca8ef2b259abe3f15a829cc4c8e3250f6a14765f2fedd9cc095fd73b7f95bb986288792115d91fc82600735a6185a4ef686ad62e9efb6d2b22520f5d772666d370b17a8d4b3035681a34213d3643a6585ce8ccb5a465b6584ad67b21ba8b205768cb9b64adbba6216ee2bef36dab72d3a95dd7c2cc5c92e32bc5890b0b49968e23ceb19185f7e85d86890f75c427778f3d8fe67b8aba7ee8d13cbe3df426cd0fe7107d209887c647a3d500b7865a64090b1f27f41202f26d39bc08a32431276c2243985522ae21147ecd8a99bc7793985d8a1854f732de232bf195876a1da318b08d91e3502905f4c6d6eee4447c5279108701f8822213facab5a17213b5b2351ce2143cadb430cdda9ef2dea7e033d09fb75b2fa63d5afc2472e61b1b1035c09bcaec4e08b55705c6d4498506a84db5b29f22223b29ac299e30450c90abf1e84a0a75bb2e9f1a148ad5f1636dad132f1137ef7dd0183011daa443b1a17687753c075f5efbe86e60c72db548485672cc168670959f4ef7f27c2bd2bc0a6278171bc5003baa7feab575aca5c9b88a0bef6ef53a3f94214ed95e98a0764df94c815e07bc066b1656761d4a5a107dc7c308abd06ccf5b36720ac176f4c1752ef27a50ebd06410b01426d95cce7864a2bf11e7f0aac54e5312f6112aa170eb6ff93ae7dae3d12f6cfd3a7fab4eac12cff66c7547507136e95af013982e2b4962ebf9018c880ba66e994fcbb281340b1dc84caea4ea421748028852b4d3e90da5b1c83ba6faf1daca66cfd817e471727ce5ca0b9290ab7adacf1e5fbe70c96c6f21c79132a7d97fd2ed39500b1b3c0cb6da1ffbf5de86f9a016e49501dad1231d5b0224adcc393b89551573781628190bc4ff1d680798482e8fd5c7921da1317edbdb415015a3742884532189543bd1e20dd4d0162a5b336760f3b2e30508c9fc37e85833aa9029a16cbd9e974887dbfba11ec96e65b8285cd740eaba0474f83aac8da86b0e8b50303158ac821875cb2775f25e5c093fe8bddadabef7ad5f471b1e026292fb5628bd4709b6b4b6ebe9f945d4d9dacfaf6b418c13f2fc5bb6c247f80c7a1c322f5d5bbe4e915c7626a5760382b3b890207a2f86ccf4d526db418b6d5926cc8bb4de70c5a2c539b189a1873097083ab1e8502ebec6adca8ca1f6f1dff4a195302537bb0ebbc93fc50f755a0577927b9639a004aa0fd1cd819fd20286f271155d271c0d73959ff2c36641c14e7d5232cda45d2b1b8e2ea1ae2ac91aa01b3fc4968bee1553e061624cd4df00275bd104eb3cf317c1b2f5acf3c1616c2858eebf944a936c3d1d5f2b54158f608c15ed677602b603c303ac414d569312e345ea7076b664f085337e42e38459405a200b53ae70c549375e89cfb9584cc14fd5e26a9706e7e4c506671f1d22dbc5a7b85da6f54611f887cb47f1720148defb718481b42b07aed38c608b4768cca72831331f10881e1fcd0b78b3d5e89f2b47bf485284ef31f89737418ea8f4ada756a1731e64ab7e6122afb1ac001e0b2b0ac5a586237b061864c42bf464a845b2fba244a3da853d7b8caa20491209df8554b04eab2c71bc5c07728614e350acd6d0b4d62709337f7ef56776802e1b0614671b7990a4407aa58deb6dfe9386114872cad3d545bc3281c2b1f06eb563dc2868e1defead13d1461b7d784bff120a2ba120b894fc48b211313e9779c0620f289d1dc49586c379123d2597447019e3b6d27c418518f9270c23ef78ffe715af5aa2b685b34288467a31a8caf456e6a27a3d1ca1bb45475b3736731c9cfe7a4b01f0823e72d045c3eb0809f59463ac02d6b98e28c7b1a589a2515e0d168fd30606b08efca8ba1771528972bedcf93e314e9230f81ffc0e8519b0f1b0277e8db11f01e8b373ce77705e953c9c25f6fff8e919234f09d9284fac6e4d6791ceb677cfaafc9e15c00230330fc2d7cf0ca1a1e8d93b394ab516d5dd8570bf628eb08b68cf0ac051cba19deb1deffd3cf0f07325648f45369115aa5cc8ffb1b9ccdf4b745db38027150f769d6ae004139e4e122ab25bcf111cef1185bcd875cfee64261a184ef55075108cfff54597e1dae36e5e1a506ac07b718330712294833f38f0a6afd0cf1c53b5e785ff71e2218bff61d139a325833f0feb38cd4aa86973479f0c836eaab8a7f920b71821375551fc96d79872087488da9372385b41a35df92abcc8d568f22488a104069f6c48b8778cdd1724fe5ba9541d6e894f842d6b892a9142e2abf3f3852d64d4fcc0c4124cbf4248d5ee40a99ee919c08cdae53c983eb656153fae31bfba80a70320d5b8c06c049c31b74869f3b8d551206808a63bc648b9326bf6d797bd40178be0de9fd894809579de50e59bf350568ed3634c9ff7aa82ece5ab12f2c5e3884bb10470930247e79f3d95e80b6ef96a747488fbce228c037abaa13feee0542813667bc2ef5f00f3e8b69930d3824b2f83b58636adcbcf09748ae34bc154a3d5a1463d0e09a3980cd7a8263d464d964d4847889a02f88a265d4c8b4a8772acdedf8b5ab92ed276a4a3da2eb8e0b31c3d71d8bd110f5f0f2860cee4572f8e9b37d836631e825e8fb1c09a9b3c55933f9c5d347675ffe0fd1ba760b35dcf3d11f5f45f106849a15bd96fdbefd3d46264c755ef35cff97b501e1e13ad7a76dcd097c2583d0f2955f25aa50008ee244d572195fface99e8894ec06ad4cc6dc3e8ffb7e17033572a3f8db75c75ee4284f5195db7e539ac2490b06517ea960a681e9fb12248788a495cc7aa6d492379509d440c85c2ec9cce6b5577989181a5e342f6d4d8d22ca380ed807d77489fd0e66e0cb8df8d36db531572742843a05cb997b7f54647992a802f23a6b5aa62851bc7d226482881c89ddfe78dec2bbd40cd23a7d523fae9ba9fb1568bc66298048ded03120dceef07d69a2a0ae1f675760736d1d6fa81810bd97949e11ac71b93df76136c2a28ff2f6edd682adcf3176c02ec0051d5a0a9e2a160c421a44526ac076e1035b6df9f4541d4c0d625194cd4c626b5cedaec20faadc62dac16f761527e4b3d81f0a8c763859f385a2032b8191b62af72c34c684143708341896b3c26655547cb4fe2068b0a697fc6c6915534c1100beab8d12fb097da1fa13fcc9621f35baf0b820b2256e09b43fe6266ee81753711cfbb688573790bddd973c5f5268b7c1cc6f3e7971f8f2419cc1fda7eb19a40ac271990738b485a42513d130a894747bfd1ffb88fa6c3555b83790ecb8dc7774706e618465ba00b2ca8da64f9bea8e36e91e2ea45d94133217b8877763ece6314323a25ca94235e4f7026c6a650e40d84a62c1505161e88d4feade21bb98efa77228f3116b892a3bae8c33268a511024d78f14ad727c65e75f061f0734980d2f2c1ddea564991c7d3315d1d35b27da3c2c55ee6c525092007ec8da08d8c32045512d87de276aa3444f2e670941564869b90944c0f2c1c6d7f4db4c3bc7174ff2a9c07410a26bf31f7d021f671c0261db46c0e02a36ec67e1eb20669b0fb8d7b36827b6a7e776707a1a4aaac32c6c1e9dea23c1101602e2eb3e4345d491c5bef352b7c1cbd8e83e0b0334d6b458437b72004fc020a9c0f28016628a78bad5b0715ea366a0d4a767c0b6a1b0d38138b1b257fcc5a76e0cf263a3234cd68bcea0f5f8a178e32fa494388679bf496e29219fbd31938600ea5f514349970ddbff1d1ce19c2e9210715116dc18efc7d6d6349ce96e474e35907ccb9121f00469b5d5ca925570903bc43a2f837cc222674c2f8afb2d4c100b7a4af3d404022f9669420269c891fd77f00b275019a040686db65722e74490434705e934ccc2f3cf6670423a296c4ce995f504ac5c13ce9df419a48423fa1fb33c14fbf3d783f57a2661cb413151fc0939f0732d34d1f7d4184c304697b9f155637fa22e0d7f36cadac98c1888e5f123f70fe2fef58a32fd8eb41b927987de6e2b74ec0e404caddc24a455fe07f65e2559e17f445af2cba2b825ab061f6a2b96eefb6dee3bd122afb72a29505620c5d0df6b32571e3195fdf6a0b2d275ca65616ca81f31b8f37f807f66be0b125a2fe0f7d499fb724a72ffb5f6f1a800106a17ad78b95b97cf127ab6f8a1adaf8dcf5825e3e654111e8fa0794aa6b210d6406e8062e7a8f1ead1c739ba5a6d74b58872dd682a149ff2a0ba63aa4a3c565f575e676320111d272ab7a9f1e0405e5ca49ff159d61fae82c92ffc2f344e842151a5ab4864e9ffc9fa5f9757901c2390a1dffb208e8a2c51d9107ef94ae5b8ac0a4f61dce208efcdbd139713612907fe1c3829b445744c92e956e2c6b236f21cae927e67b94b9ed11a6a04bc24cc7565047f6f77f513a783a1e8e7205251d2973dba8538d090c3277f355118e156cfa6f18b86b78deb22462d0503f60d51e51ba5987e0a3a5f79da5a5b07eec0cae04488908e014eeb9a48592406371f5d9def953427d3c545e141c6a0aee840a0c46c421b172e83c95a079376c7f20ec69d3c560d9341a6aa81109c2b544f393f273245f2986d93cbedb7e4a8228b49beab4a0e57bc578829fc4017d7385c3fb2f6b64f2dd251f4f0a3bdb8b4c074d27172e902ab1e21e7dc7d0d3057144c20d7cc07b463c34be100e703072842d6aa69ee1b56443c739183649510a8d2c4b098d7357974e210b988ef3b80558f54e1e0cb43c267108c90083a232ff78a56525d9579bf3d2cc41d2920d46e3c4f4190a3fe5821d5cf0a98ea364b48b5f67d4507de8474880a322acfdbb3a2c451d2837fd28513857b31aa3b58e19d8d5a96d4c64b4568cf782c4f73b9dbaf18992a8977a4baf477d67b6c76355911245b7510b25d4d13dc899217f21b7cea8702ec6424b579b8196341809fc55656320e84599b969d9d2be042ec4d38bbcc4035889282c59009054c239e3687a68620505c8186bf2415a27f5111d9d250b5766034ca4c88f9c3b9b5d9b235f98c41fa60be5e0b68b27d247c5231e96d92750517d8460c5f0b5d8595eee19198caee9da39c84795d778e2129fc520735b6faa516eb57bb937353f6ad74a9b05eeadc1aeaf080fbe9f6ab82ae7a0564f8136c686af052903f195a58dd200255ab34fe793f530c29f27de156da9b865244b5a6ccdc07fb7ec92847d6cec0d0fdd9534930070e33d0b25660bea0a570564beb9b97a832359a0ed2ded038e48e2e523979a049c0c4e7d644364288195a14b987778bad48e4725adffddb89e73be11ee9a5fe20833612b8e3364784cc88014dc2f304ebf2ed83b0f96e40ddbb68abe3617bc1e921605a3feb29d96ee4de4a8d00f72f538073548eee6bd80ee05041d03a8a282f22051791f101130a4e8d0da4f49692b5cc78a0726485a1e8586b47f6e558343109950fd701978f53378b619caf64d8bd5c83a02a355b7c6bdb1b13616822cc6555608b319f1b04df8996d02577f806c247e21c5e13742914cc59a95355601abc1346d4602fbec9a2ced1aa30b6a08d4f2ec45d5f3cd8bd0331413ff455521e03ced9d2fa409bdf2dff54605a77c048d24e5bf51ca143cae166a185c54ae521c980fa854d53f3ac0358024232471ee2f67d9eb97b868ae28e9e2365eb5dba45c7b3ba880541f1b60e4dc02d0eac483b4576bd3ac86e70c2d73f079795f60ba98227990c75c255feb00b118324bd051b6e621a6f853c5d84bbd13be2d5dd3026b7010bfd1dba2b3117e4074270502a0f7437af9d393579a92605d6e274d32694520c571bd48707518bd6e5b8c724d905c01ca461389504205402a2ec78345134cd9ed9e821229676bd20a64e90bb201e6b53873313d4fd4b9e177bd14d81b425105ba0d308f8de89d1c2a6218404ca4a9dcdecae5f66450b14dc995a43529d7a8f206d511bc98085dc9ae00a0abde14828d6244abbcf04f5afbe2bac8de825ceae7d950866e0c5d1f982f6e3be8b2c28cab04b44429b31f3b586c7d06e8d72dfab7fd96da453f25e11f01091c6a175343c6a5c0ff7daadbbe27334c6aaa0e6e770e835ca5a123ac9739ac149883e3d4f518459d6962fbc0d72247b16a133c7d5c76400332c83fcd5ba02502916d3aafccb085efbc2af60ed025315fda4c97f126ca656f1723132513c747e1e2f7557817a3310c564f5f0b6de8d7c01cd035fbbab8fbb5759ee048f40a4c5c64ad2baf1f17525b47f2a9d263ed681443d11c599f0fdfe2c57f3c4d23b07a905583ed847ae9b3661615ef9aa7f1626269c8ed3eb921a11d30c4bb49440a913e067573018b0376169bddfa35ada430191ede6764a549ef460e902de468186bdddbec0dae1267a8d07ca7d993469fdaed562ad4973f93e4994843fefb4053050e773fe4d4a8ce2223394577f2faed9485582819aaf69d44e609fd1f9de88c843201fcba7332c5a758f529da524755291c2f77b198c18f7cea7c8addf6be802dcd056f4d5d0b30d7f20a5fb0350d8c1c4b478896efea3e22eceef4d1212271d99e55591af624c6739baa11bfb9d0ff243ec1a3edcd91890638bda8f542fc0a56ba0847eb28f6c1f94ed6d82f4f2edc1a562609eb1c536e93d301b4d257d6d30b10e8888e64f18ac4ac6c89705ea477080ba90957dcce6cc8edefbccdceb0f7cf9db41650492819cf796d1f9d31c9b9af26340a383c77b09fc2c9499bd7be1b0c018f29b1f86a680245eaf8c3082854edd88c9f8aa3617a34e51fd0f6552d0aa941c128e11369e40dcd4d5af920508227dc19cdb2ea0ad03f9272dfa600a51b4f888af26b9d970bba02e3be061b841ac241b2613799c2c7bd774edafba545e3f5706577cec7ebae2e36a0c8078f0110db9c92494f11a8d5f93c8a07c555a64dd16c1e84b874623c0f4dfe4b9936ffeba4f11e6829c1155a54a1ca525cd9415ede62d5939ae756fd06ddfd71c0444afba7ba3140f4550f14574881b88f8e6aba6d1393185a8dc42b53d6b6ec6c342f89c95c496aaebd543b437f42bed929043ee83bb2dd5cd38caa7f135124fe220cfd5ae0b454edea42ddc636f02e82052b3e050a5be7202dd98d45296c943118876291f0e3f12635fc61a904a04e8a118c37cb5673ebe0a51a142153fae11110a8be8c6e5a7622a61776da691d55541760287e9c5692a667d31680fa4ace20b8bf37b6fa1f088adb9e7b675bec0dd5355e1b65399040d36391c530122ca7ed268f7519675babb42e393753588302c11f037c84bcad6b29698a351a06b2cb0a370abb72af970f947dd7676365ccdcb703b356c5f5481c69cc5283a2b4d6e21431931934462f2632db8f2656d3270d740446c1aafa4f91e62f8865783156ba5adc91f89519143db0b72f0826309b220aaafcb042fd2350da406280bbf544ac02805dbf1047fcc3780d40613a4311b3a3bef106c325cfef2f0a11b80c79947a80d6260488a95e3f23fd00c7e9a0469e338037dce08de9e473a3c55757dca81895b3b4a3480004cd27560eccae2fa59195e161353d0eef4e3064c98e1551d6325020684241942b443c543865c1fb087dc3270a62cfb1d8ca7b07bd88a6a073c17e3ce118d03a6d4392a06e044b56e09f862861cb18420ebede939e701185bc3f6f8848c08cd5fea4dc1aa690bcce40e8504863773e85c59245cb81f1e134136feed56f360972e9402acdd29c08c86984405877238f4e6f4c1319d685b6e1d1d83d88940ba87a51e90a93104451b2cb4954e7e4d3806733e1748c9d113e9a50c6ecf19f6e9459fad492547a3ce548efede6226b8ae563e3b90a396afbd8b8fa1a1989a8c67815d423f878b6b57de154e773ccd7e7beb19232898ca77bc386bbedc03e9c2f3da617312427198d608f467a670fc248074e643b6a49b5b9536288df9bf0aaeeb43b5e88c3654493eaa6d91b5c85bbce148c4d0dd0e1f3e47c64a68f8047c61748f65fc843afb9519dee3a23c100b458239be883f58ca79721df972cce332ee8c6b6aa42b05ee7072d6b7bdf29dde97fb359ae69f755ab42f0aa5ce928a3e7006dd95a05a11fb223c6d020f3aeda38ea520139552a35a550236b70b34dcabfa729503076f2dcdf06c295feea6050ed631db191502553eea587906b6f507500c5627884354bb46f8cf47fc1d9efbd1474381f20d17396b5d4886bcf5a94c8a47f01cf1b564581323a7a640777a6012f74092fe0eacde0b7b53180593af5cea4a5ca8727a079b8917bc9e13f07f738e4c607d2b187eab110739489e3113c02714d6643b7ff155c7090a553a25f9b05e23dd45a195c0b11894fca70441bf207a05427414252a78218096feaa00906d3f702907b413588a403989d19a796dfcb4c260bacb7969b09910a8f2d301eae55b5107f2f18714d87ba1f7e5a0bd567d3776c0dd46817fc4f88833e1d9cc1950462771e49cd09b4091bfad0abd29ccbfdb34f8f0b7221d58e4a27ff80326db7ce1fac611fe1d424d6dd6c95fb532b7604bbe20b7e46f6856500bbff22f5f44e38b2adccc71d7d6e7303b0bb64cece03f19aaf5221c0e962cbcd2216b7cdd537383fdf5617a3f9fff4225dfeecc4121a65b8741997cb7f000b4f71355b21190c9f842c5ab703b4fd7329b73473595646c153ef42e0f9a2b5aa124f6757554ba716544e0e666808c149ffef38c484c5c607a7dca4eb984b93e1a214eb4a0517cdb86ce18175612193d2a6b3f720ec661dbd10682bd943f7f033438b01ef5b766787bf82cb96fb4617be4cc2ffeacbb026494bd9d34596a474e0b377b512fa15539bb3750f9a926769d0a7b306f670316fc95fcc113da5f346f28a5d1641046758108b16804767ba23c0a8735d95aa7b9ab157982e3679d75c644405543e9c7fa26e8915a1a4dcb26d21d39f7422b0647ade0109b7524069599330fb217679e6fdb200b6c5b04e753c135cc2952f771f0430abacb30e0039c1d60188e22da728e044ccae1aaeffe2c1c5c301698ed160600e2ed51500e11b0e411d29666cc12ad22d6e9a932386bf9b05af153701389c5e07054669ec0c1ad977136c7c6af6dbd1f39a7db70457032ca8b2aa6095a02c35c6ee8162bb7e708e410c848c59996f6ac23aacd38d40af7782cab6174eb3326f25241fa3058c009a0628c65ea301aaac57db66f3c7ccdbf0611058641be61fcf53a8b51f0b2214bc63b73b635eb47a850a900745387cb8217ba3f1245254dee01b40b51506ff2ca03604903e2f6a4ef9e27c86b07341bcd6315ac269a503ed516013acb7b65a966a3dcd6885b969371eda1f3db2fcd3835282e39b8364a55b3eb86438768620ec434f53954c3a2db169a0f77baf381ea4bbd76da7d666094f7af95429cf9df6944ca019c66b0f1d79459c931932a614be944f8a58b6a196921f60150d4fe40d0a7da44c15862f1875e42d9df0eda1cb28e2ca09e2b8cf3efaba211d4a0fa2ad598fd826316fc2fd79858a345dca05c91e4189013f0ed8341dfed259a41b4eca67f197ac50ef222489e576ff3200e07b9d0ee7ff97b3b44494fa9d38eeb32e1bad6d5998e731e76b86004203e742f41015861adc6034c4be5328c237adc45ba9ba33fbd999367fee357885ecfd11b855271235cb2b59101068978047f1f1b81fb0c9f6d6c536a6d6e3200e2b002f7586f7acc85504020b3e2e9db3b9b8e38645bb6c75cabb65c903c2101fe6ec2d8f0373dad094003a20219f7762a18d75cb435101062f0c8aab32dea1f0e186a0c1210f2ff30418fd537910d993a83fdbe27f737d1482d83b21ea7f9dda251a87e85855834d3a002d602007d8f6b1171b0ef33ad2a51f7bf58d70925ed8b14fed20d83515076b219537a725a612ec017e30afdedadddb8555f1da29b567f012583c892fdf4a1b0b5866d21049ac4623b390c6867a670d921890bcd938165c21ac18b1e5d356c3206e77047efc3c7856402115583beca34f98f2f69348a13e78da6f84d24080878588dfb6bc94278efc26be60347d5554db618163cab57c31eda00a4f105ae4a64924c06a5175102141f06198fb7484858fc7cb3aca2ded809835ef0aae9610405d369c26a8ca89a281506a4a4446db47958e3b66cc40f406758a2e653c0f056ff71f789439b5312f2d420b176c30849abd12d2ec5d56462141715e7636e7bf4b51dbb162283d5a583306737e9754fc1b50ffc125f393eb113e5a61682fb2886cc203e02ff97109b82c36104762b6311b9478f02571a5763195c7916056aa69ae60cf8ef2f17d08fbe833bb07f7084dd971daa11ab358485028405d3d0558af8dd6e05e4c9d2bfbf6dffea6aa42e39767fe391bb595c838ad96998c0d4eb6a10fb058f4380025599d9f9fdfd87b7f7fc08a2dbeb0614a1d30db42cd8c7d923c40e1beafccc49402829d0133acb4d5705d0c4ff990d99e82fcb44d61a218e6d9a9d9e642fda6859edb2b8aa855782d7d16b96076cc520c4401492fcb221fd62e83c3c9bf0a1a76ff5bbef7b70134b608e4f68e8edd9f492923ede2ce64b0c5d1b383aaa55aac5e6ea4f43e7e19284bd08b4ca17c972327358f4e823701ec77bad8cb928d9c8c21e27b8e2453984fcaf8b533ada8ef16f5d27fc3d71635e26c6b90de877e7038c802f0eb47ddd615d4c4a71e82ae175233f1a64542cbde2fbe5c0ce5fccd8e5eed4a09e651987938f1ae66468a71ff2402901bf4bb343ff64be3b402f0c1abc6007941c8e10815a47e42866ecd2e86f58803268b3a020a8e9cf4642a75d0dcf20d2eeb93a7847d3becfc64f50191b4f6feba52b31a0b2265e93e793662086f8226df52da075efebf4c56c7b225e278e73c411d33a085f092d069307a7e102837531c26243ad2e621e726e6edfaaaf54f7e21ecf95d7342f1ec10f7b66b0396bc02c8cfa2706b1eff62cea4a45f680ae657e82043082d3aef272f9a59bee468ec3b50d70898a664ca425422c49a69b83e8650129640ee8c8f3184cda886177ed4cd201d96ed713bd40c83d4da34486ea03f9e15c73dd1338bbab187f087a3aef1204a487cef19fdec1838ab93712211ce34c65de0f0a31c8d48f53ddabaf58d19bf48ba997b469c8f9c260c2719f8de4892659c0a18c21bdd64b2367d406b78b306a3472e54b53b208b696c666a446ab192a6f7df665d24f3518172f9dfe96211b9ff990653fc232d96a2a77f90f09e80e8a2323c8d7312f50251374d33d9d73103644a485826c357f10b9fb57f951f4306217ca2c1422e85e0b42bbcdddeb43b840f6e54d78d109a1367ef91e5085fd691d597529f01ab92f4f9cd7bdd10759dbb0baca107b9f7a5d959bf4a7f5a9c0937336f70d84333c789944f6808a9f78834591259d704e5484ec5e219e5c61e7ce55f5d868d5b8864b7a7f8dec8e0117aafced08b5d7ca11b943fda8865bf3c8221a16c01d0b5a5b816e790450bdb7fa30eb86001b518e18b402d8c3ce0f61acaec018dcff3b0d89b3ebb3d65eda1e6330299cb10aa20ff9bdf37e055b34869f1f2beaeb87b3e0ccf4fb5da320fba3a1d2d201e80ac74ca12485d437cdb1aefe59c02f9f9237f8e558645ed04abf9c03b7e2b6a0be12bd8727bc0b1cfb5ec027229215f856e47e062c818bd30cfdb8480bd7cbf8297aa4078db87c802b6c2c48a8341e1031387f4c7d6c9eaf8dc3a38cdbd41370802fcd3aea702a882a6f4f01bdd9ebbf150a52eef5e12640be3cdeb2b207aea68fa46eef97091aecf4f34251680de4e805597a9b60f011afe2d2004f83427af8308d2eee0a5a8ed627623a2688ac557f4b98ce12111bb7d05c7b49a8f74fb4b06ebb9ea02b56573956a1087f0411d4b3453adedc89c8d34733cebb2aa663f7a258a10a25c8573aed45a42bd6d83ebd90a9f93b1dc2513264a8369a4cffd1fc1d398dd1debb87760bbd57c423344188b19e4ed54cbd83914afc5d5c4ac73ae7f1e42a410231c5653783dc7ef2c56ad252e2cf188cb42833a73b85d4c82f40fba2ce2cf968ccc621432fc735484914afb72186b98fb81c1484570af2410f93ce2d1ccd336770a62bfb53e0a65a96da531ae36174fef919027f2f3b1da15d2fc3d114dba01b27a4cd51dd2253f5ee8f80784314877f10771339f4a84a5790fe6142b8836ecc878c3bd12e10cc333c484122dd89336fd6f8061a8b0ede2c60ce2042327682fbb06007f67b6127ba9e1b7a5d8d0885be897ac81afb3898f80d5595ed24f61167ce2ce897efb164b0382c74e61abd3bb85c57330620844bd12a0c85c3807e37476ee2a87125d2b65e89e3b73bd965e8c24688face71b7f8602463d610e8544f3da0a4b7058233d0625d3d6f5686e617420c12c495f08ef9770a210e75da709934d150ff1c1ad459e383225183e843dd9a0c933d674512a90fc46cda96f9cbec43ff811193e4c017702d780d0de8ebd907c24858b8c317ce32d193321e1ae4ae2ce5b3b1de1d5b8367e691688184ba66c301908cae01221ae2b4770a02d73984b765fa959b1d1b14e63403616d2ce268280454cfb75f483ad1cde7fcceaa6bf73f0b8853966b12279a1964d5e52494c8114f7233de02128862db081e691efc041b652274d5fd5d8c27b7f3b6077f966701e056200bd8fb049318403518668a0c64123503f7cbb52ac5e8915178afdad3c4d1baa7bc69ce643e471d5f9ac9c60c67aa4fd9212e01b9925b3d8c130bbceea447ed34ebe043b54354439abb17dc5e414162109a76f9f1da1a7f55720588030271d53b748a27ad11b7336c0ab33ae599fdc47bc07239fd2d15a12afe6c0c186abe656c4656505dc5282a88c68ac3685fec34ae7fe212e82e2502245bcf73036dc28f428be44ed065cd4c8e3030150353c53b0a9f6f325981d79f9e211fe3755c97961f3274fd80e5a152e76b53bb7756339c2ef490f252e633cee786b6fe35567d40e0530ea6bd990f30f3c6461f2d70decf61a29d8ba770dd0c225ab580411905d3676a644c84eea2aba4532c5857fe6acc113ec7d4de6a34464a31b7038293400966d392562f758b3f8ec8686ab35c4d2fdaade368843e71890ad7eecfeca946023ea8b30a91d4846445886e65766cda52e281b1e61419b50ec5ec20d06aa610953770c0b6a2f72a2c412260041d9cebc8ef864555d30c35c3b2ecef2696bae57df77d9822a8a2b5b55b3141329f8d8af216b8545b4be6e361ea0ab8980e17c13a16b33a505fbaa7c814293265328c6f554eeb20b1ce3910b8b08221b56ca14d27317dd8950356b8b3e6be4c9cb896c9fb5d1a5ac12757a3f552cfd2952803a20a9a3d33b4963846528bc8b2b79262cdd0155b0311c717cd4677b52f1d530d24a8fdd2e5277c092ae9216eca932cfb1286ad1c6e1fceba9a9cb49ac77f907eb7d872acf7862c14ae03daf51fdacfe1554657daf423de0396a7820a9165a48d93a2c1f829de5567333d2e9a2c8f268b3762bad58ba9d261f2224ec9885b8cea072cbda5c68e7485b656feed1de7b4989886d74ab7a51528ea6968400ae716cd0ec001792635d0b1093d640db67a69765c8f8635346d758880cbe6c52c885cc51d40f35ea0035189ff57566a25a406dddd35346f6bc76b6eacb834b790fd7772097e1942f361a690d7e4b047ce825d1d3b38147a86a78affe595cec1eb428b6422ffb43bf59cb274e826a6f803ddfb3b10d5765dae10509fcaf0f0202da2c767b4434c0b75c152061bd2ae1f321f71c11ea91ec5ab8fb0a1beb5e7060df9b3e60f5d43baa8adced7baa6c291ee3964626564ed8a4fa173ca79d9ed4a5a4f8d34e0a6ef30dd98d03d91ee09d7dd2cdbee57792d98b97f353e394883f6b5b4b45d5d8fca18c7d369dfdeaf5a059b7fc142e04ee023bfeba983f5e355407c8941e364fdd54519e3064bf75c4f2405c77cd4cc9aef6b8ac7cf0c7a30d6b20632b69bf9840587b918cb61013e8b174f4302661414c131fddfacdb23e3b89a28c7d741bca9bb25a205388680f2ce7b00ba18e0d5170510b5da549735da2e7864d7380fd4076556b159800687b543a9b1480e0cfc2c4794c58ec949ec1667c542472a2261bcf0230d41e98f15970c5c09781dbd95a6fec3013e50a32be51814dbbf1e7a046f712918ad4a7fed164ef821752edaebaa31102fad4f21e797093fa446052491bd1e41135ef674b8a51878f20f4f7ea0e1ee474206efd17f6930dac66336d682910b55f876053b5b410a9ed2282a5cd089efd405b4e928952f9e02861e544200fa13748aa759be68d828621bc94cffc150e64de7cb87ce518818a254394d6d5348b1a693f70fcbda49a696c07ad8bf37de29514ca5d6caeb30e6698ddd4758405cd03a890d886064ea7ae74c9ebc5555f97a32f94c60e070a4a3053878f277d134e0f9dbcec49191c5ef60e9b7c268a0175957d0b965b6f0c5735ef130b17df5a5ed6652ce0eeb467cdd7d4860e35ed3fd6451ebe0265323983400c883c6ec7f13058f1e6b767dca09519a9b1861ce1fbcb313ca9cb54d993fae538227f70caba4e7494c2b7824dd793f53ec0dfed59cf49c0b0d1f18d3b5283b973e39ca7f1884dcfd2f5c06f34be536b20160523706aad6a934f4d15280e87f76e00aae87f11869d6ab857f0b162df560fb2aea56cf9659ef649ae54264b8b9d0e8c67f48bf896a1225105cc046bd1fd38abc0911b11cf3ea1abd45eb016a64899f608672e7f0df432a3049360080751832edb624da25722a693df9cd326a044aa11c733915c4f5b7e03ac9f0694aceae5aec2f2a91386c24acec6fd3dca56b6bbf40fba55be7c92ee21aa53458057aef66bcb2355d629b4b959a256dcd14f0735a2b26720118004610c00986a3660490919cd7a601db378c49cc9effe3ce4621612f2eebb457807335dec3c650aa08ab94d0950af54af34f1ab56cf01ef876112adfbb8d24c29646602e0280ff63ee2f8a84ca06d18ed316dd40b6d22b8c4e97579e77163864aa46111f8e875fa138f0ba5c7748b825040c3cb81429ddcbb6da60ebd76e8cd47b88f70cf984b6b44cfbf124de5d32fbf318f2d4d7bc55c69b9b1b3db8340f43b9e5d6b28b823a4409fe7e4b7856797ea81d2206b2fca9b02425f2c485b5646902bc9cd6a3ed59c7f97db1f283558a2920d681be1d542ec9f121aa759fd97da78cad5d2e4c7773885cc161a8ab3d0273fcf5c458ba6675efa4466b471379bc87a5e7934723b55cebe31aaef7d2864c862b1b4d845084e9d1410270d1f08b19c85882602c4b0931914eeb321393363391792d17996faa79d69950c88412bab23b24f5fd8245a480022e89d94b130882a1ec6a67543f2b4d09afe8208e2c9067a41f44e83a5679aaa41a836cc25646c741f838627de47a469065c01f2d3457fb24f6ea7fa4d985a61a593a15b0c905598100a4775512102f37c77394d68b302913f06249cce96eda6fb5e210ac95bc69b48bd5d21c25157cbb8e0f926cb5a7709c36b65e8e674d3873542f60d015c1c600d1ea0c652c37c8ee8953c53f01f3d4c3db9c0b610794f2128d20652a675a876e90e35331a5c8e7107ef1805689f18ee9f942a703f2502540cf19f236ca1da24079cd76fa8535fbc4c9d07ed3e66209af7bb810303e88d400faae8ded5050e58a27a10bb9e464f397cf02a4f8ad9404e96f39ee0aa40102545e32291d61abc1cc7b79cca33d1d7c4cbfa42cdd2b7fec7ecbe4469b1d1d27d2bb21dee5424ca1d3fadf041ef6274855bb7628846d2f52a12ed9fedb6cd053d92626e2e5a3abdf102d69e1903ec68401d213481a205e0a4ad489cb0642592ba55f8708684abbda1bbf2bc3f59122a2b312c599b654b8fbb032e6f86509b16a26032409d1d655aae6a4e3d7264f658f531a7f202067ea9648ec45689a37f945e555508da2191cff7303df1447ca26c2d0091958bc86395a8ef40f48d07378b9a4322fe10c9fa1acd9dfeb16cf82410ba6c765193c10ffd6833289973d345c77301caf9dc61cf237c610f46f79f403188a3628c73ebe80e2596dfe226be6228e044b87727089c2a383162455e4d14b833223f9587c24f88c40376f6c6a9a63b19888a3273d184b5ae24d922a969643a504fcd8ae39ffaf008c878627c02e7417643a6753bb6042c0a8092df5eca0bc55dd42a69dd14c4dde5decf5f61b7926ef680d859fc28a28f6004f7379c4f4a9799844972ad712f9d7b5aa18f224b4b16107ac5405bbb4ecc7f1bc06c2c330e91221218579206398d2a4871761a76881b9c941d83244e196ef15a6a0416d7f42d2324112956312c0efce31cb577d2243c4b216e94bc302fa486c59c2fc1c05eebd4ea76f85cf0e3e01764b4458984bad2221c3e10ccd484af5d5eb33f16465b175e077cdf107346a3e092914ca22231b3bbb5de158065c3c48ba00f405663d7704ac4d35c01127fb176f4867ac3c928d2a29da790750163bac766514ba548c7d6e3c1cae3514238ddf1bcf6b57761db0d86e68b1a23f34548f9703dbce58b43d8f456e50942654283419b03eb687f6974a600d4238aea7463f3efad6305baca0cdbf64574c38108b53530cad10a02526042bd6be7c0da326fb43cd716966ede2db2e731d91f59639fadfc5c89e5558b27716269a3251068193d1eb4c9b4c2adc6b91974e93daf04afd372dae9c42d8ce7bdaff893353f8031ce7152ed2f1f099c54eece12b06b9d0c7844c957c32ca73bbb981fa9f10799f108d66b7531bc1835ccc8562ddb36f54ece1c6daf05a7f5401e14aabe8097b87c7be2808f78569f020a5acd9e0f6fe3c03ae52a5922fe9ce6eb7e021d8bd236d761939d99ddcb0db61d1b6f8750cc84f84e19f5b90460a5b6fcf9749c3f6ffb09e9dae35c40f32cc8392ade4888b8b53e5ecfa4e17f4b603a514c31a96378669835cd002dcf136b386f19e3081d40f4225ecff15d02ad712941493baaefe504d1ab2acdcbafdb9e3280d412e322d700eb1e5687abb25bde646f32168d17ce9eb84963a828b8c643db9ccd468b2658b13fb1e258dba7f31501583a25e7c1d0a8bdb4d640ec882eae498d016147ee2eb9106356e144776212db2ea04089b1f5b2ce93a355bbac0e56634087bd0a9432c84332d34161b4a266cf1ca3cf6bfa740d4ee55f7a49d1006d44b9b12c241beae2de8665d078b91b6d400cbd2a6810235aa8c48f315c2ad124b56040d1e15a7daa0016b6bdbebc051eda83e9532330a6c7fbc608e18a9b888cce2437e79d7d2603cc2659e11a0751ba2aacf983b7ea7bca2bed6430d257e97b6672f14c7d16cb83e58d2a57ecc85b6fd962f491eb3814310644aaafd5663ac2eaca95e381b018b042fa53cc2467f29d3f588ccecd5bc1350f516edb154820f005ff57a0983492b21f2c46e7e6ad205264411de2a3cfdcea3ea64f0c0283574d2517b29a8f5ec1ca19885be485587afacae8a37cc45096fd99f6b8e4c209c57498b4084fae11c7cc6a2f351c628f502f69406bea24113b59acca47e14579d1cb3e80d870b3cf89637220735dbdce338d03719d79538cc986759328a949184aac5d5816a41de50e705ab86181864babb3c192997c26e63756144c9de82efcb42f1c1f081fc2190fe069d0a5a5721d7a8079e19179c750175a4836e4d8489469cbc0e1830045dd2166eab81b0078ca6a9c9802834dfdeedafa5d213b9a2e345e3e223921a220f43d6195ebfb7211d5be762b54468075b53e894112e1ad8a8898ef3e7c68f3d2d9e4921481d896d7db38f8ce79a0c9087ff7eef94441f9b967e33190fabcb98c3bfe373c8ffc85cc39260cf5667330396833338c7f8d4fdfac98c4d8a668f06296504c05ecfbe03699b6060355772c829a3b59b0f8533b436500af59e30fc6cae3bd05d32ba9ba74d786402ad69b57625be9d126e846c9c7b61685e31032cb28ad41ba4573a1d0524b0ec1177802445019d5b14308cee299913920ef371fc5a17b363d682f6d9c59a30ba562a155e8e0bfe245f2e12fd72d9c0e3e90971911a9fc78c05d67838b6841e881607367037688718c5289f0d69f56677d580a6fb2b098b2bc25e31d89e24a29a74f9054e5d4ab916f011a6c513388fae51f36bf36ac618576ae71903da9e9003f8ace60d53b271d264552674e8e4626f3628e5e9939ec83b72759f2f3ca3eacb54800cc52b7a19165e023c8c67b9fb595871a8179c6936c015f3729dbdd56acd865cb322b68295272503f3214a3a77e569a582a40f5f988235702517f40790eed45bd4e72a15d44bb8926a589eb95ccf1f3c4bd109e0696339ff0d106540c7516c9e1a32830051ba3be9b73be0f2e67c26ac0ff1d602e2c4860010973746344b36bbe4d6e887172f18b069e91f25f66161711a041aed4ff826d1b5332b8180a10c2dc357721889c0acb93dba7551cc32fe5ab484381075cc805842b8ab1e16b43c2b251ee0ca231a489c0333a6b9c4a1fccb347f4b11880acca838dc8728a487c72a1e1516ea28e17b38266574792516fe140d24d326984ddcc70f6e2963fb0c3ffc0e0f2f43e955a832248418f599d7845104e3e8327107d700a52398c933c8a14a2e1336eda9bd669f604c92bd38015f53598c5d5914a0b95b679e270328c5d79583c569f985e5a44ad82aa7aac9c84370554c6b0912a5cf987631e79abe62ab03188c227bf4a80fc7e43c5cb0b2029d84c8ca295efb0a8b05a6dcae5c68c77cd625d2f19c634439b0f9de1784c50530c236e5d5d26981ba776386e2daa8c5f14f1e310c196e1d402c6d556a011bf05dc3d4d4c813856b0dc0e41388a617c674890d7f45849a5a2a0adb1fe4a602847034bd0a04ad7327689b6c64028adce337ad44a3de5c45edac0e9367bba0c83e432952e94a629fe94a7aed9c02be8021a23d78487a9397581405818b66c20309aa0a1505ca85b6a93663bb281114503e472f28030b164175eb2241aff3e5d63b3f7476fcd26ef6ce0b9e369cc619856f734c9acd732267c918dd0669b263a6434719557b44034fbd801e7c503558a63952f24653f3eb553a9eab8fdc20fecb15271f82ba93a1e16dc045f5a747e41326e563e078f0ae0ad65c84e0119ddc04437be226711902949cb61ab001954618274d19c3b53e5467925ff8e6b79b7f786fc29ec823550f7f288da01cf02ca1b09147891ef2fb8d0b0a4421e221fcf0474c6b3fa11c2defe71237cce03a44f31c7d62fd47d8b81fbd30a1350017fa941f66a34d75136169e9fa65e05fffadeb2e77680e4282c5ba9672911d021d0ec497f0ebc7d1ba5dfbe4846588aa1df2de75305fbc65e130f6a3f2e7a4a6c990bf801d443a02126e670b9136ceea50544f4cf348867929ec0e4e87f841dfa0af2ee6c5cab6516cab80baff3fbe2f7043ad31c232ea5d1315d6215885d84e06447d84304c67de1a416fd30122534d43b3fdb185bb738dfa174599547ef1802cefec0748643e7f4bb15a3b3a9dbdbe7e85d54e03157c9600a84a8746992baaed54388fcb45c5d1d534c7893356e814bc5ca182b68443a5aa22a9ebccfd6b03600e28a83378bce2285e10449a435206382858c43898258437f372cbb2bce9ef94402d61bf38f5a7213fa07d14e9c556b30ff02790973ea1a2eb91eec79f97773b32cf1967f38d7c0c7f9245c9a4b750df490f03fff1d191b28d445bf395ca5162559adea0a8bc701e64d65bd6eb42b1ee47e6628aa13c071c51ae1849964f64dc42b6ad5a427a11308d6ad65943d6d1f70fa659bf300e7c035172364690858cb288a40b5a90587261fea8d425393ceafa864442355c6c9c8151966f17087b00f2ba9854024aa309a541717dfc994ef62f03531793204214246fe3001cafd3ae0c94dc4617cc23c9d423cfb4a9e505d7ff4711da2405f49d70f36204e69c87569b8ef21d0f7be8dbfe5aadbf5dc496781d6d5fccaffe08bf65d4cb8b39fe6cb84d2e18e61f3963e0d085b38375b89a3b2f11e6d56cf0017330c17f32eb85be17ff985550a54bb9c96870009c5556d70ae3662a61838178ddd228c6b4435b276ccb3f04092047d46ac5eb910b714325400a402be2f55cd191265ed390d029ef1636f7f0a7b0667e7f4ae2f25c4c217c10432cb9c3a9d864e7b85c267e5632acf27c6b99b99348dadfb7fc1c7e5a459a14040d5b97a3c861c3f547d4c540a25d7ab18ea711ad28b7719183631dac56ba74b74164b832036de3c095bc9a2629282eb9b9ff2592680a4baacecaa23c1266222bfeb4407109d2c82c617f9e1c382e8ce8a83a73508fab81093978600430b8cc9376a1223a5ea241ba2b4e112858a720586eda375eaaf60252b6c04a20d68a656aa3f1cd10900c25c1ed92b2f3d59c811d9e7592cc0f619dfdcf9a2e2bb92bcc69bbb94b77c4b688a6211a3db94f509a5ff7ad1914b0bfc41f6e9ecc3aea95248c4c5510c16a6158b4219f6192d95623358dae7125bc240ea46b8fcdfc32e364d7820f884d7f6e31ec0fbe94d5a445b74ad7c9ec27ff7d8c5853a8455a8fa9b89fa03155bdf6d027116419b8271585715ffe62e21e75c8aad20e042096a01e1b34b71d77221425d721d936b938ab5a62fdbb02d380db1bc3a576ba8cc5540f85b81952967689924b73911ed6aa4a48bd44f2b05269b79e461dd2f8d45f91cde83fd4aace122b60c4c2f559323f0329157c85daa35fe383dde25ed61d779a8dd942fb16e4b01da0782f1c038dfb02e45a4b3bf95301d6f3322460aaa40001175cb71da5054228bdff4028802537478cc521d81e8c541db8448ef4f06d84c372e6703eb1b4e306d2281089a09363d7f5c1418c4e9770e19fbc8b47bb3fdb97c89fcd771e8775adad1f5ef9e1273fd2cfeeea18d17713356d2d5dde00c7d8c86856fd74b69b6f870bd9a3936f0de99819612c558c57e95ac0d64fe2f906e69dceb00cbd2b1e5f3ffab4c0d6c3799805be28de8f225f4ea49ba9b97e801cc83026ad2ed615e408e654d1be9547cd4437d2ed3720de7d566f27d6dbdf6f7adfc6affda69addf46b8b3d917ff8cd03e9170ddbfced0b2d1017230983be1ee0405eab5056a06c049ae08290fa55931b5ea9b66434c1d607781d6d6db2c114924b2bbbb7b07fd051c06ba05b60e66308319cc600633985faf0cfb3c1b79fa6da6e9fc5f0f8b3dcc955dd9955dd9955d3feab8c49b181761b391a7c3fe351b7946bc60b311f62260220c1b0cf6a29db1f517edcceb657b81278cb091f8143ceb378b76c6c562bd76f504fed7cd089988d8cb9a814bf7d70380f7d7dfc8faeb47c86afdf5225e66208e3afdf5b1a73b008838e2a8450cc4918b2c7194c9c4f1bb4b1c6b634da7ebf491ee3ae6e9230c9bcd76b3d93c17475bb7b1b1d96c3c24663e2448d4a01134d481964332d172e8124c3882d864939eaad1e7eba91a451ed09d99a68f60a6e934207c3eed06ed06ed4605a242c1841a39427cfade393a6a24f019025b07c1f79a6a077cf97de268ebdf63157dd6f45cdaf17a288a2008bf3fcaa55bef05c12baf46d147b9c49345a92d921c1b65ea181c998c85acc80e95cb7e1b525d9264043347432fc0b2d422c7d002083048e9310408635ae4a22245b60893a721e59cb3ce9986c442f6355d518ea21a53bbfecceb956a1422d850d55a6badb5d65a6b78e4479b2ed42271e9eab57c605961cd088fd89a4b84f0bdd22e481e8803efa4afcc17c4bb6273a356815d3c1007de495f992f8877e55a8c53a32647036b243983c929e02835485548f468608d2483c929e028f8de1810b7922b281a1ff912b1768b2d2146862e5d34f912b15b6c0919cf2c4883f7c8594a86ba10f341ab58739692a12ee85c2d6d06aff46b955e6bfde9b5ce8741d13934abdcf9e3754dfb0e71b45629027a9d945604cc39435caffa85cbb1e2f55f216e6f8f355f044e942102d7e95b1522f0f185b3a1922be60dcd9bb47cc02d202c2bac195f178bfb5c373a7de178934e5fb67ce8f4650bb358333a7d191ee9f465ee768622043ebe70364172c5acf24014f06eface7c441e0e36373afd49457fbe70200a9dfedc4d9dfefcce74faf323eaf4a78743a73f71b7b312356a722460cd2467abec43d72075fab406698f844e9fbe709a49a74fb355a74fb38f4e9f622a9d3eb576860324b30221df1a2c0bc874fa95e67ec42074fa358b2f1cfd6a59d0e9d76abb9dc568f0f1858bf58052e9158ef9e8f42db696bed0e95b5bc1197c66f9a0d387f9c815f74561509dfecdd2e9df5be95f5b67748ac4d2ed5f40ae986f39f0e6d08f2eaa6945124c0e766c7a155fb869ed7b2103acdcb95ee18e4d7751c914f473c6f0f9a38bca63555104ce268cbe3e17e802658bb3489060aaaaaa5b83aa5015b2344797e892950ce955044ee0293c09314ac283f01dfc8898183c0992fd948c51d28223515e724f444642434a7e7458e994f84c89d12559dae992208990327469e554172ab962c7506f74f93548aef8a61014a9cba739b9c2933fade40a5d857c74f9b3875c915fd62f502bf04b7b4397dfa506472c09560616e475e545c35a24355d6a70c492606560417afdf9bad2ebcf178d5e7fd60a00970cbdf21c788e9612eb07961039b56b954b069e03cfd15262fdc012622d8e54080426d93b6c15660c5f150aa5422030c9de61ab70efcda7ebd51bc22ba2b94c213cbaf4e9bc21bc229a0bc6330c745046c23d50263daf745046c23de48c413b1b817ff41a558532fe11a5f54c365e195fb009337c94bb295e1938b922832e45f49db3e3e38b04b38f36b81b6cfa156d7026ccd8e07afd97cfdb74113e236af0c15584492104211e4e30410712df81cbc09154590cbad42519928485ab645bdca4a235a8d29cb5ba1b9b48c3a7d863220d97bfb5c79ae28d8bbf41c8ab6ba517e3293eca1c0f8e9108658c3fe04aaa7af091b7c0bdf091e7c6f06d25c447ce659522a1ea63ed89876d62088efbd5c003129d62d0291561a380be5b5e97d2564fc817fbee94d25d05d0441c400f693d2cd1c330006253df1c74492ee709ec7ffd15defe0a6b7f85bc9e9827faaea9b5d239e79cb266d780b1b001324d9ada6b6edf22a6145159f34317b98f2ae853e432859cf5dd1a67bfe940b6b89f02996b7102fb27b8efb23e3ef665b735e18d142570657f21732daca8eb891a9b582c067bbd5eaf97ab0664e9594ddfdcd56ab5582007595aa6905f41d9defbf33ccff33408b2e8cfd9acef11744a4d45da536906a238c7f34c0c711348b8ab4bdd0d442698f0913b7901e73698b95a8cf16f16382cc2a033c658bbac8250c5288ec55e16c3917d7692a58ff2c9e6e4644a1f634f4e96ba89253c493791e4848658ced989919e73c6d7d6a71790400ca784932a325e8c6a8075ae3301f46f26673456c54d65180189d471077a8b20b4680166e518637905e39a44d13d6f26417ae25e8cb1c51863f0048da26d36897471f30889c9c54529616ba594be20871846a831c48c8c1d2d7a703809891a72da39d5f07c08c06307912838002195b0a3090a3cf420c499343d3308c131e5053654a1d182078e748901c22eca51102f904d5baa6ef09121cc8fa49e1d416889c104881860a8a187f8802a49861f212c01c28a182be4c786231d6278d2021a152821ad7043939d0c272d3210c4c20d1e3b7e5868419fb1696ed61101ca3818b34129d54274221fac7362824ee9871ec647057de487aaa23258260d3e70a1cfe713cd09d58d04e889f93a7b24629f33021f634f29072c6250fae200aa83c893bd05d00d64ce01f4a7520e4c6238254a509aea230a2a5b465e7dc371625c161be9532f83f153a13c5391c55b3506632a3b4e9c0d3be08b5f628c7f0ed57ac5056f8c5f043d25f4302385226388acf0a3851ce553c7f8317e5bdda016e3c6111e7eaab628b5c0a20d636c53c13997bd931e4e4ee863edc97b9a73da79b37dda6a9f7eab9a90137571d7896ae04d5a71645582538c79341b3c835e6b1587b4807326252a9c05a5268f870597bd52129f759710828fda7b99ddd6029d562afeddaa3d39b5ce2e3091a18f7c89c90fee02262ac0bad4310102820a939f1c47688ec8e4ec113962c10a475aac1c5d39aa528f9aa6202571a18810ba291e9e68322c7c1c880b983139b2cc589263437f2097581df9ec30efd2a615618cc1255be40ddc06f7aab0e087abc0816731457c08af811bd5f0c22e754b4440f3bdfc21927f7abf41af5b51cf76b7f45a5a9b5ca6b872d26b43067cadb5040999823e0a0ea80adb93007bed62934f1bc247db69d59a7399ecbf565be7bb45698e0e2e6dbd863bb986b72c8eb6adb7d67be65cc35ba618dbf086ebbd37bc5d6b2d0e6ff6d69ac35bc594eaf0467bae09e1b37f794b8fa5459ca9c49b9b16a54c31ea8c3c7dc7d2e94d3bc63ed5a9c92e0fcfb51afc72dbc42d176b34e80fce5a732e93fdd76abce32cab96d5ae4f3d71c591a7f3c73ac797644fbfab51f55b55af38f2e733efa74f4b0cc551068ae3d774469e9e3f6b8cafa4e1b5b9d6f480586b6b4ba6a03956f888d3290a5b9b646a5403af67d065bd3201381ae897def73dcea4b456ccc2b3f1f6a96758a492960347a12353ecd8f677ebc6feecf36778bb9c8637ab6b78ab37d9ad2880236ebb0d6ff486b78959f56f56acdfd377ebe66602fbbf7fe1bd96b1c54f8627ea7a6276f1867bddba8fa3bee999330d6f99e2de478debbdd6da3eea9bbdb5f651df2aa6bd8ffa463baeadc0671fb5be7fc3624decb9b95d5147d4e93d4d97e10d631af6ced9e5ec683fb87c99c3696f99253f73ce79f15416536bad382b45dd7b6bd6464446465ae629e734026254037b37f69c74d22548962cc93927ad463a9614a95684fa688f8cf4511f011d110942ca50cea31a8e945cb0a7ac9e6079b9cb8afb6e857d53494fb0ee920c27e98cab9673152c872675c9345cd2942423588924236100927cb0c4a41ddf2c4904f0936278249a5c7d1934e90103a08917164e132cfba6899318064d66808d6822049435116289903501e236b2373183db2e754d7e64b22b30b6032c0b7fe958ae1cc86ff85a443c16d20e9b1029460d68f5da5d5a9faec6d39a09ae35c4c351a3cbcd70c1082bb2a550c724a7957874115dea94888c7e4c0cc921a16643da5c204385168c6ae832e648ccfde0f0a149095c8cb4c0561dd72b1b29eb2542fd76a93b1aea63899c0b5a672845571ee7b2b75dea8acae8a46743e78cb38173ce30181942e36464233de8fc5a26fb5a910e9806fc69a022297d94399fa2262f1415d1c089a48a8414b96039977d91963498cb703625c0f739286cf03d0d877ce2d713f6f36cc87eef27fc7e7fd65a13b3127de39a1efeceac0ebeb51f4eaa814e9703e8dfac043df1b412445e80ae67a0ccd239734d8a8f5a8f306a7d66b942c6529ce836bcd5ba7d3c931f66ddb8c016e856005d0787d91b8c5d4a6b6d59e9c20074f6403b039ef09a8970c1fe05cec673a8cb076734205cfe68d32ffd2efdfa6967b88b19d7e96ae8d3c5fa9a90c582c16030180cf62ceab7bd7fe91df027f8938b737690862008debe67bdd69f4b1c6355a703d2f36b1d8f9edf4640ff7cb97b3a8c7e417d83df077ee07f1eaca0c39eef6f7ffbdbdfa8d37a4f6b0dc3d6f5c7f35a6bce65b2ff5a6def1b98b8c363b3753959af35e799f5e3b39e158aa3ce37bbd1fb65e8c17ef9a0b8a35ffe3793b41bfcfbfd7a36c2a840fdbb202d079dc927d71c000a3e623c0182c94c1298192db2550a32cce0a487ae052c645fd34de9c4e89470a0058174da189f2758392b4b9e398a22c28f2820782cb8ec7997ba261f7268fad2840354d3aec94a65c165df0485ca96b80906213d68b021ca88932702912e2d1e2152689ec458a10540e880319c9f893d1d5c8bd979c965b418262fd589898849870e18c3616a42460cf7988e544161f23e165cf64b626e6ef4259db4522adeb8a895ce39fb0e96954e7c5992ded856daaab5152db57fa3a2adf459504a02a5a2a57405b25acbf90bfcf73ba827acfdad59f465bfd526fb36abd5cd325915399f9d7a1efd7de9bdf7de2935eb3e0b1ad6fb372a6a99c2057d2ac64a40e226fb7575f974875a392faf73da7b717efc2ff2e38c7178bb32d739b5bedebdbae6c7df41c69e77bf7bbd3ae7f7853bbbdfafd27ee16d7ef3e5dfa6d4e18e37a50eaf68a93e61efd6e3afb48a6771bc3817372e74dfaffbfe9ca6efef47bb9372cf3c241668ce39e7aeaa48aaa8d0d55ca25b0b55d55a6d5eb2d6deab4eefc5231eba12f7d3c470fdde6e40ee3bbfee79ff0d14779d8e77a8d37ac70fa8df5da7e238bb3b99c28e2878ea302eaed39e3d3da62d4b8c5f4eca15f9c349f91d7d46b3003bb9a25a8ba536f419dd0fd7730b3d0361a669a1ff33bae36764b1ce57672b4a0fe90665d0abe8e57278a0f43fa1bac1a4ffe5aad5aebf5bb4313cf05abf8ab3994ca13fff8bef3d9dbf3bc52953588c737da13f7f075ab489b67a2267abc3dbee33cdc6e18db66e6eb257b1a76f1d8a3417f070c7dbd4ead0b37faba27845b902db73e9058343707fe183ff227c3004f7876f73ee765dfe9c73ce3969f8000b648128a5b45624f26badb5daaa3ee55b6bed9d7fefc573a94ff918d3c0f0515e651a18aedf7b7d13bdfd7d1eebfe8d0b2d8e63cfd60bed0d8b77e80ec914f8f52cd47f83bfa76bd1032bee64d0b36877e24e06fd8a762753c8d77407b154c392d2128f25a2a59f251d50c4404103c50b1426261e4c4450a298743411351935d9681a6242d32484274b083318f93153820e83245cbe80a1225594c60b2149078ce12c4df59b2e754b669a7800e9423515f5b850bc85661ca89da2b0da0b93c26a2fd4264ec49fd0d0a8aa11358547e14ff8930fa01881226402bc03ade7a4b54e0fe4dcd1b7fa3d74ce14b6dcad160d8a4f1df24000b438675ba9eea9931659cd89a9aaaaaad095b5d5eaea6a0ec95dd6f4f78e8e4e9d4fb1c54f6fad09d86cb6de9001b42c9cee84376d5b3b53023f32b49ec914342c33f6853797accd77c95a4fa75ce4ad0974c0190082535e960d6f5acbf9e9ef764513ccdeaa34285e7f6fd66c944f9df59bcf5abae3e73312c0fa0856e0d339de3428fbf205add9d7d6a8e3b748c45451edc79547c75f85aa2e7eba64d15c59d549443568f5b8a272aadb79012aac81a91bc1ce5b8f3f0c1980bd1717a01ad0b70fb83bf7823d9c495aab547b36d6a09c959e8db3471f25d5a54171fb74d6010e5491a29629ea4bdb5a625f3f79200d8af7d85cae5698aaf6b841d46756adae66b4ef9c5da5da52edba41e00e5a662bdd634e4c55959f28bdd9a7567b551fe3acad565797ea5e3a7be02aa6d7c7554cb6b95e3f4b296d4ecb15b02ee76b2b168d2d77abfebe509c06c547de6bed3c8a8fb2ea7e20f3ae290d8a8f7a26e9f5b74c519f568583b456bd3e522012cc7ab54c41e70c216033a8431ed9afa557f66e653067f70445071420507ca0043d11f304cd93dd93aa274a4fa29ee09ee49ef07842f404e889d0930fe690943ad8f802735a6bf395c9acf5bcfd93628c31ae3dd7e29a411053aded5b1bcedf96d6a6f7a3ab5ab107df708a37b7590e1f7c19ad17ad7cbb37fba29429ac17eb9e062b286db6cfe37ccb649c6fcfdbf6eddee196328517dee8f6be83f045a6de146f3a08c59e4e776ba6e99efd6da9b5d6da29354bffd47fb3af5bd38ae16d7fdbebfb3798c31dddf7ef6fca306396fd29deb8d86fffb63dd18ab3ebdcd3a7fd7a63c59b6b8716202d447dd4d7474b109d16a12b1c4098ba9a72c1140ba67098929a829a6a9a529a329a9a612a684ac8548fab2919525559a6b2fc904527264b2e8b51161e596eb0d666adcbe2833196734a89856a7a9ff7e53afd3a053e7dfbdb587874fa57c9a877575852d01285250718ea76423e34b71f43218b992c62b2e4b2106509c262a5c3f2340216a22f588076632a65c165af458c162f7dac3d85d913013a4250bbf7de10d43a08ee5b99bb2328ea123de3b87e04377869c07a892e91b64449e705a27014b446771abcf7febdb11d1814522b0409248c3005eb6bebbd35ffcd19c69522bc605fb43ac458cf4260ab8540a7f0a8010992d3821d78f0e02189ea0e22a2aa1f2ed47ce480e1c123e70b7f11f51104ed4c4695303876396c988c238cc681c35c1c739773ec688e301847188a637773ec72f4a8f0248a9221b62c1dcd1073688188921b391e4c612a8e29a3aaaa0c898189cbce872a5762a4f911534369ee724c5b254b5be386149592d44e4a0a951492d4111da4669032222584e20a4481618521456092c4e099c0c21784c4c4e443250812950b31aee8ae4c5d017225844a75c018ce15253c90c0af1889fa62e50b48abd698671b1728836e91588d73b6d6846a2b0a1d15361292d6692fcbeb5a4cc80992132627cc8e09d6f65037c8350e6601e8ae71f05ab6e080034f7bfa0498930297fdc8c9e18a8e2b40ae8470a58c940857bc48d5b0f26485ca0a122b4b567a58c15901b222146565c527ea8b5490548f1a94a8165c3951366034198b2819d9040b9e2c557ce8a3ecab50c1aa50a9b254c5a80a0d555ea8f2a38a095542a00204151da8485161a292848a0d545ea0a2021511a086802203f5056a0bd412140d503ea0703c0df164f5d4e569f794f474e48985a71953cc4c2113958b7a9a22a684910e741c2911f9e164cb5510289ca09c9c219a62c18612f3080682f012e6c7ca071ab81e1ba0a6d4e0040c0a379a3c1d1b672b65a47e5ca03a437001c185304d5640ca51404793992070f860cb6e1a1569411719e2595b8b08f55aeb97a8b6beaed8c987de65cb1c1dfcde3a02af7f35ae974ea9c912f5e23008591fb8312b04f7a8c1ac77f0dffa7a961feb3c8f908e44edd1fa74005e93b3f136bd4b9e27829cc60482b79bc653f3213ec487f8101fe2437c880f7510d4df98a7dbda47e4cf35b9f133336b24f0fa2caa9e85c026a97abdba7b46ccece4ee899cb6031f2f95d69ccb64f52e8d77a9de254ed52908aa4d50e6c18694299b14143c5d9c6e8ad1c5c9e8b1d97cd285e9a3763272f2e2344583425ba23d394006e4730984e432c38a8c9394500f695ad4283a13240a4d2e860d39c40604b0039703102c637c8c702aa50f9c944081d9314bd080d4697c94c1fd908402931154505165c30727b460c30730cc1d52ca337384124e496a8922279ce7d4833ba5809d829e829c6aa870f2f93897fd0f45b2d6317a20126699ec6b52bc1c91e2d4479953a1454a521f794e8a11974224c7f110293fac94a9184f1c08a7b1454a0cdd992cc50426e0cbf06603a59452ca4d61b3f106f62c9ece7a70e6b2f1842cd6de7b8b3b334d7789200b36f27418b6ae5dcf67a3adf39fb391a7cf980d45d860d683b0d968ebb0fa2cd683fc61b3c9df06d7c46c086bb9f8ac8aa367ebac190c9bf7d2f52ca837836163cd465b67d99adc1334f274d0f6149cb15e96b938caa6387e8d38ea7cdfe6e2144171d4e9d4f5ac190c5b77fd0c23f07ad7df19085eaf7f8f7b6bcdb94c46abacf26ddfdffef6b7bffdedefe71028027d357d7e064d21d6f30fbfbdf7dedb76c38a7df8fc3ff1c63586e28deb6fe6c37e3febb97833610ffe8d8bf5fb612c18381b6fc01bf0fbbe87cd6840f8278e56943abbda936ce626cef5fbc1a72f027dc5a7cf04fa193485603f9fbfac925472279f6ce20d4c0c9fff7c4a3ffad18f7ef4a39f78c37ad87fe18c068473d783ff3deca778e302613b334d877d0fc2c097bff7cfa12eadbadca3b4ea01987da804adb6f83484684802000400e316000020140c89c4811c86a24050b37b14800b63943a584436944644a138208cc3180ea218866220046000848120e41c430a3a1e79e30cbb677d710151ac376e50fa3de87a5215227c7ece40cf3078ca2dba562b5dd9002999506c8d4deccfdcb5be7847fd4559f4f4cc6ff43d35139837a005ed00e53ac7fe1d35218d782bee5442349a72455d921d27296e198c9e47b223e1b03c961aecaa6620fba7a1621381bd421b079a5f4632713dac180e84c70cc8e36d959fae16d14c94b860fcb43016e85e7b8f9e088230738f6140fd7c26fa7e4bf2ef2d7158d012c018ac04f0730fffc6b411452c714d0bc6bf38cfe8f507291abce7babc6575a6d1a3e52cbceaaa88ab6d501243dcda2cf170052fa3da848ebd97a3229675790e9d7b0e3244c2880b8a72d1b9e5af527e5634d05395d7ac4ac6870018ab49e0b120697879189ceab41bca8c9cb044e93e098f696a9c4bde174e4c327ef3932e956d787c04ee158f44f3b0949aa4b89e38f459a9ae1790a5af46c0aea1d25aba7d822ccfcfef6d33244ce3d026f4c62ed8b9166244b0b4ed5132bc0817365117a122cf12a19482761080fe39a10bcd1bc938b330a3696d4d03f3217e3698990b9649ee8f6340f009228bd6874680b43d38255cfafb72057511b45a33d9e3756a45781435184427797784853b3d75f3201ea1e8c71d2c0fb95ec241999f6139ebd1a59aacb91dd972e476db8d495151dea51a02503d34f216d61566a6072575033d8fd037e6db2eae5dda6fcae1582e54a785e859445b3226e1588ee0080eb4a607f4281c927ec25364a140bc9825fd81b077ed46d9f5c20c11217350bf9d136231695b7ed471e003f23d18e7ba8fbbf1a15a0a47d08c7db2c21406fc5d956e2cb279b28cdb359d71eee873dcabeedf0d86dd08e3de606f2aa0959e9872448403829019e22c92a496016f292b55e1be57733b7bad6f3b1a444db56927f20d19ad6a297c988e4d9301881c0efe7585e4ede279884bb96618fafc2170b2049642f84e81f487584192fb2eeb6c8990d87a9c7718ea0488573c1229f335039134d1105a1a692fd10fbd41435d7ecf24eaa46110dc2ea751a1e35c1954b13e29e644dbd6f38862ce6153a728a1b6f9876a8fa42e5bd03dbd02feb0db6488329724651e99f06f533167f48588c17926ab1735bd4d71afa211d5dc6c5c84339d5373131603985b05b630956c3d6bdaffb8dd047601d210df6e6f9a4cee40b11a0aa1f1e752883f95719f19a77cf557fb6ad5b8bae85bbe33fc21a55a4b896eac9f98f48f370c3fea23885016177e58e3043d13881e880bb02c04dfaa6919381a0bc91c6df82096da0154bf94d664e5de0f1e9ebd132a552be371945e02d30651400102a2414d45d6311a92a3ccd1bde97d7b838a58afa59692fc1f6796d57924eca782c464ad01c79571424505bc4edd76aeeed8e254434cdd2d31fce6537458a8cc36ab3d32fd392adf82974ac80ac72e63d68288c27872a055e4dba68807a9f5c3a22ca8e438c4c3aeaa0bbfe818878b1bcf759ba099aacfc811b5229c10c89967282ad9823350d3b65c2afbfdfa7c69c658a50032deba4a8249a5e360169a1f6e3b4fe2e270cfd93114d22a61a1b02fa040534de3a8b8628853b2a213815360c7bb3ac8938cee52c123288f8363f7ee55034f49754861f30e9a5ce75151a3ba6cc4b26a7236d67abfd0f1bcdbd27946c408145eada9068339c09eb85ef4b2a70cfec29ec167827f0ecb9303341962e23b9b36ecfc4c55ef766e81e8d6be732d88254197a640c4d677f5db20ad2a62078462e1a000efc075af25817c433fd8450b2e145ec7380196b6f98c3d1c4508ac5c195d4becb41f60468ec8260f011f5cb775222e2ce299a523828d56b71d6298b671deea211603ce1e7028af774159d2318c631d01c93fde854b92c7782b7aa902562529359a4e4d532d3c69fb91714488d273f85599340210fa87db108f1accabeb535423cadfc4d8044c2cfdf66fd621f05bf61bba1ec4aa5faff1232191274c6ba34d9fe2c7f55b62179a923740654afb056a422e2b6d402605da03e478e59902e28e682d9f81e154078ddfbef1887662897520b07d0dc581f7eddc20f0ab674afa5fdc805e2dfa83f35fc3a7cb7927433c386c46bf2f229d29659ff8a79a9250c1be3d6eee57bab99bda31148d7a0f9af2e957ff4fd35e05c858c040160a042a9d8420253d903ab7196e9c101286b3d2c5d471cab112e7e4e4c454e9dceb4cd991045d32e729f8db1726d79b4ca952ef8e809da2f2cd29624110de7adcc1c9a351d5857dbec16541884cb1ef825242e9029f654a21edae118573c578bd80516f354a90852558b2ed2cfd46e13c6560323c830a7c5ac0076d4adb922afab1d3183b6a3b2a8b28b68e01da7b7fa01744a921f6ea95eefce9fe5896baaea7101a7016c206c50105305a0da7e2ee2c5e4acef16a8f857340291d7915cad1874ebf19daab78796f911836c58be7129113a1d820a5338a50e3ec85ecd86048b3970c262061303843dc78b9887503dded8fc6340e79370e513b0d0f907697c582991e7a30da1b543bdd54ef2e7e037218c266e75144c861630605b74a30165ad60568003b57e142f24307dabea96f204a36763e3cb6a39afc367ca3203a6814c203ad810b6a742fe145b96d4270fbce7ee7f2ad10afa6076eb54b1fd9493a938866ca1838b97f063d605f2235e4c0ff06b8e0bf2117fc52e086bf8c150b623936f306be5445bcbcb930255e7829064d770d116150b1cfcdafb039cc9cd95a9374daf1f37c52a7ef7ed41a2ff25230de1705100c6b8db6995c9099249baf99248dd6cc124d964cd2864b8649c36586a4219149315db24f66bc344832591aa5992d9b24992c0d930cd786c90c4bc324c3a5799af9b27192c1d264c8e89f415d14b2945441f1c567bbb161710e26849b1326443984631910b11eeee30e778f6e30be5419c9c597b2ea4f1f2ecce0d22db8ab1ae1b473d3f54538f3f8583078cc57c58ceaf7b4ae9083dc3d6ba7128609c1fef4e4a5a582ce471ffc95e7ee50f92c1e94969989ed019f7e4274c53066a05a0af323c99ed591bbf18c8dc79d0be1d24e02b48234b59e82351e2a8f7e2e867a8661b285d0471b65df2c44478966a512d9b868d41649e4e627dae1df07c3001aaf3f1a1eaba9404f0e6a97576fc7d4d5cc3885f7943ae5ab12dea8852ae703e15b43bd1387328c6e6a9495b55ff50e677d05240aac185deccb36606875f06ebf0714c0b500b0ade0ed5250aaac2f15b6d66304f3e8d7119c358bb39592d1b3700a0ae78719a467fefd34a1289b61182025810ad1aba9fface65392fadf60ba9a5fcf9d78c7330a074e5590e912c9f0a82c766f3674e38b4266fa147a4508a0f6449763731c1e35d511d306b4b36cbe21031e71432bee1a565ba4489462a6e7d1d2eba769294004d482a480043b1a69bb036d518f491eb7af58a0e4bba2ac4e61c2144dad40e29dfb5b53939473d54dcd9bc043a538709d84e2cdf247d04e377fd88949f1e4af8200af82e7978f6c2dc62318a0fbb56a3fa3a39bf75f02c4f0e3f231db32741b34f4e80b33657b6e753a845c08806821847aa7f3ba1a7e379646a212284f2a962968f40cf652b5abc21f0e4c8fad218c5c57d0f44270d082629cb30aa663675ba4094585f5b9319317e98f05ab3bd3091f757955934ea4fa002a3ce3b325fecaff67dd8b977b06ee4d1eb5bff0e57bb2a934c35ce5c7b8253431e25189be944bfd3a6ed344b1a711ad749328cd7777dc364530c74b8ddf1136a55acc6bd22003239e78485a4f134180e89932d4a40b1e57bad9b30383261d5e05a552a2afe28b0da454b713c420720e30bd3f4c210a095cac0955de0e1762821bdcbba35e5619c3b00392d507334f237b61aa4161290a8d724327fd6242862f818e4861828ca6cb74e7cbd9239b778b4068ffd84cbeccfe159e8d65d6fd5805ee7c5db0f3b6c834f9aa2eb9b554e0d68ae63c9b1d5133394b2dd083a20defd1a287f6ecdfebfc55bebedb2f52ab25129770c2365a8f40f4b1a856e040c4211a2ef73138370faa2411098b87341d914f4f96c3c58e331b02775b005a2e20a32054d0daec964f6be496513451fb37e40d17a35b73b1625acd61ab3521edf363d9fbc4e4408bd562de3f16717172abe931f324d0c60c1323f3eb0b16519f0408e5feda13ea81540df699ca3f84eeb0a53e8a5cbe36151afc2a6a2c4b625a5963fe8a88143f50a9ccf3c2e7ee87945cdcee7045fc74b1dbe45cbf2d28585cee98cd219cdd313796ae830acb1009f85822be596374c419125e6fcc72e58e41c98395f1b21326310dfc688589cb51fb6d0c46167e8c070c0224ed15c786ed8a51c787c142f0d294ba0639b17888ffd5ac3b6000001e80662ee85a26b65ce6b459f5cf6141ba7c47f800f9265930d3180a9596fb72d043cd007ca930c1d16a750b44604aafbb620b7e15a3b82bb00a28f4ba435919611b06b6552d7fcff3be09ac5dae05e647948cfdbfd1185582d79504d284e722ebed58287e8e7264d94f713c0618d166b5ef28616b29e6782e9b617d27220a6ab5b3315bd7a06068c58695ef0fed89d88c70f54d9b903a6d3d878a0443a3c9fead8a93534529ebc8e5e6d0a3d69cb16d963dbdd9f9fc1278184643368fa3975a272f0be8d4f877251a45b51c9d51aa8a6966c0568c876c47b4566ba3a083826b1ae36bcb8204898c71133ee6c2b1a733d6783d74d4ae57172427abac0e4af751602ee696cd0d1e753c48046329f797131d9bf293d202f06144042c1223d2d4c98039435b01dbae833d82e58063473ecc82cc3979ccb84dc693cd205bb8b18bb811f5d86561831ddba4311f51a220c5a17287b988a2dc7d00298c4bba044a5c30c7568d61ad81e603e49a8b127601fc69fba36e5a9fbf8765c02d2e96a1e168575fef951889cc5af9e2e523ef487916aebb315f85bdf68a2ea3c5b0c9be4c341060dd468740620f62fea3c9327655a53151c545bc240117588fb40fe0618b9fab58a163d57690d24b8c103992de0d11c16f6c3a641431fb7753a0f03aade1aca9a388701d7d40b6e0ec5e99164b6442c610f401494dd306ae111ade5a1ba08c4769ff399fee483ee01325ff759bf0ecd79c3d86af17a2959ebbc37101ee28a0c94ea3c04906f563715b31c036de0b9d40b67f420d6180a94acd98adbcfae163c55d4d9639a437365d176155acbf527e990c48ffadc210b0130b668803b02032b0546f1ea7cb8964282327eee1cdd8f4505921c141acc8657288d299d0eb6368bafa7d826caf0c623903f1292ad434355d5d167e4aeea565cdd9d5509e2461572d2e785cdea731305d60323b784ca2b5ff5d19786c50d69503641cb81cd9cf8665c844686c9e29bd1b2e220154c637211430bcf329d0dd613731d4597eb06a271f78282f190e3372f838738495be94144b8887f16c2fea36a1ffcf0436459313642f908018aafb82aab2a42155bfa576b2674072dc9d0485242613c3bb9fe419e694a91d23360b1870f0bb4cec27e7c36c2384c008158fe012b4a1777af42226e9a8e699de3b37e07ce1e2c09194e8f95ea96d687376f6ad7985e25824c762ab9488a4c1fff773c614f5819515e98883336e98aaeb540f5dfa450bedaf8f3900433532c6a89103c8d6dcdea0673442b57072b10c162828cec3dd5ae8eafb2c288d86b61a5b272a0d25fc357aa856443c4333463ef93b0ca879325df495c81082a27aea57baca9007585622e2bd595880281948f7e3b1ad6e080ddcb61b29946d05d7daa6e02c16220ed4586ca5285a6add8c27976c1872c2b056119c6a4955295b2f4d1e412bc2b23247ac3186f008ea66e0de631253edd2a34276c81f003011b4a005a900eb35f5bc0138910abaff192b38e1e9a39ab2a3c246e443a15d16fd166f45a7dee18b7c03c48a816809cc98b35662a08fa71a39f99f32904405702fb415f4677e43c0f39896e98b1760a64d65e830fd7091329f946c14e5e721ff3fd19600a5421a7c215a7e6a97f9affa31dd47b34333795c239b0ebb23275cf8395a0c01d18e45c7b1a19e2bbfa48a965eac2e0eb6a05f258d2a71b71320c9bf84f2a2f63777b2047e9a45ac494515d416ede06a0606b0ebe37bb9fb10fa2a8ae6ba41aac8ea3e1847f1e194cfb7bfab90a6c447a09ad6ce009f1328eaacf804803a409c72b721f9006588857f00611c3cb50b04104f86d480398e11fc87718cd42b80f71a12719f5e532cf67c4764b9a9e872a1a1fe51f0ce686f43dcafcb36f5d610c0cb0d006002b62960a4c056515ec4174d2024e75b92d7413bcb6a13a8d735a0ecae96cfdb80d9d4d57a7c2befd218320669739d890ae623730b4f43a4039bca446ec3718508f0e2cdcd507776397b7d3e60969f7622895f004f9602cb50e6a03493b108aaeccd83501723a444480d53aeec726cac2cf9a105520736a0c33ab3f24897f31df278fdd1e7e11b7521efad34bde19782c4ba524d5c5c28e0bed9686b62370c08214a8a647467e2947ab59b10229c962fbb98c215837b04e135681ebde7a5cd1d588cd558ee3260082e455ba2a64b36002916f192ec72c366dcfa81a2369d4094cbc2432dd124b9f29af62c9166c66bb7b16809f1eb15a08273892621d5cdc8176cb781ab8bdc8409c0efe18abdd04904bdcb478e60ebf2ec6331bab58e54f4adb37c3de938d4c6b2c289e97e21e143ccf1a1b2d10c0dca57c1b01d84981b552826d00c6391a167810dcf7c2932fc2c1acb9557349c003321ec961cda84871a325d69f8215541942ec39339fcbf6cbb77d6febd36944e15b49e0a6aab84bdc71ac1b9c7e8f082a1fbb6cb11590fdaefc0fd6372755fdec689c200e80489e206934c4361d09d9aa4065621ddacf12aae01c938ce56288b1b04ec4c69bef3312531ae7c2fe8a0663df1bae1753abc202ea5f3fa578c041b8f759ee98b9823b29e66a586f4888bf664e9df2470fc499e918ae0b19bd850f404b813ec27d197623f54ee6472985b5286ed30e76888d99f63914d96b33c7cac46f8cd34fef5812208f08341f5789cf072ffd18151d458ee92137ce4b1c36240f615dd252eac59aa570e7ec9ecba5bc46503efa6749e01e9cbe8a9a84941832c5871f1cda0fd08537d26b54dbefef25ec5ca0d97e376eeb9e668a39261b1f764e5262702135cbfec2772f2a16a9605d06df352db1d4240a2c25251bba42ae294875c8215b0255ad13f6df60b217495a03eade339d9e07230b3ae1466935da623a32a5280eb5bec531a3b6e3c66c3932b778f64bf64c6f8d4d65cd972e40dd16b139af7c6c4c93050249288a278af928061cf0610d47b09ecf9c68d9f0735f6a80e9f40d58507459932e393b833a8cb34b61c11ec2bc1487a48104352ff7845336cf32eccd7476c328898e80650e82d2d4bd0626d44c6191213da7612fea79c91132ae08fdfc0da05e5eb3f719ec3d8b80b019a2e0ae044ccaf76781b2d5202fc5903ee3e74b2b9aef32c97fd5708969357a97a2ec2ea945d0ff89bc910ce7a45d9c939175406b01ac9ec8a0b4bf261cbc4597ee3ba27e04df40afa3c01b49c0b4e0bfa30be65fca873a4579f2d874678d2e950fbbdea35da8aa9f1f47fd5fa85f27aabca968c63b06ee78fc7aa90e66214aaff97bcd40659cf44cecfae690cfa3163361a70cb2cc8aec9d7ef87859f14144ff7da24eb8a41c6f9519a9c6297555100228f2d4c1b40faa2331f103578a9e982a027c3cba022e2a7164b435a6c3db9514ebacd8afa8df1f0221bdc19489e5428cc9ad39dc882a94fab81b5ba495d61492b00a8680c8eca7345bb4978d824f055cc6ef2dc6e920f63192a88e9a920ed3a3fa92debab5df81247822d7992a1dcbb8e46d469f30478b3ed1168bf7bcc450688265c2f92dc66a6b7c1fe0dbf3b307255cca424b06b4892dc26da9c1ed626135061d4673f919993cb6503077fe3973406b2400546a58d447e56338427c86519d9f60b8a31d16a8a0ef2c5d0bb343c2c48f645e523b068b3aefcb8334d0660f4fc965e94eb25316616701b8e982525bf16510a1d2185158a546e24c5cb5d1e20b66a2c1f50f6659308a5f519e30c89f3551e8efb2a956a3d3aeb1f3c8aaf3f6488a471a05e7032bc129c0c57d1cdc08a70f66fb2a02bfe4d55f12bc1696025cca9b89111c5f20a1cda9023562331009010bd8bf595934568054560d4ee1e388cd5e770b5e7b517664f431f38b36611193f4e288d132484523e9a4c55d901dd45fa5241ca603822ad0b84a69ce6c8cb75ffb1f327d1d9cfce0147f2e92f903add2d42280a935b4e5ef989bd3f55dd9d740f39a7af7f8a14a5f32fc2cb2334e537474ed41521c087f7a4d8cbc04cd339a55cfd87bd7f7a5df15d079c18f4a84248dbf659da0b8b6b10d61c88d5979c81d03e2f9522d43fa69c14c522779886b666142f9b1a5ad581d61cc2c54a3a510f1d6874ae4c7036efbc9ae3b92f180da0779a40040877178b73b3a21db61f83a4356c73212d29931fb77a5fb5eb393e6fc9a2a66aed51e20ee1e5dc7f1570ef4acc72abd90cac2c0d12f649b2639d546730590f151fd32a3d7acb0183ef70876c1148b8d2e34b55d9c2a058cd4720feb9a3d6d7d15b9c4e8cbff4bcaeb18c64da6f9acccbbadce2c64c1841025ac3db6380a467ac19b3bf3b9c9a794d99ef6764a6cc5740e62f648fb97d541ba12d5e1896b124302e2e025bdedf51299d5e0cd0e58e85f463ee5114f3d504de37136a53ad325e4f7a0c7ef2990f80cfb370d8280ec8bdb86a412aa4b90dffcc3483f968dd1c9169abb389aba30c907bdd87ed58dd2440bee10ef32117cb6208d084a0ce1f59d89a32fbcd7c8047208cfd05e41e18b82e5ad920a784c5bd3a7073ff4355a1f1b238b9873f233a02dc068b92ca7773d18246034eeed1d292957953bc56418a2526c5c1800ea902be89741bb1985e83bdb12a6c1663204e82ca412c04666800cec49d42a0a59b3bdc8da669c267a53322fa0113217b74c301190a639e87bc0d3db2e9fc3375f9410827b8e886df8f2d997d416111a6d99ed4eeeda07121224de70987d6f7cee8692a9cdc744e443ebec1c92620eff643b022206a20df919e1adc8b2a1cc32bc047f6aa73f89679ceb655a39eab5ffd8abd5dcb5ccf015766e6e4cc2ad2af56bbb77489b91a26dc4b9e4dc8d061a9071b44ebf71329e06a235fe662e8dbc8bb1120f3e7ba45ddf9a2135091fe43eeeacefa60e8eb83c26f6f12966bda576b55bbcca9e2bf1d1a9ace22f38f44849d54b8efd256e09b023b4084753bf0e5a034fcea22d6c268873d812404cb4565d95c0f6b9fb9efa56100446daca7f510e668511a53cf06e40b079fe44c7ac28d0882850f760d60018bb91b182e163889f4014d23dd48d04a124273784137d468ae875e1769d17d6fe6328746093c55e0d5fc8351c50ebb601669cdbf59784b7eddca0d033e9fda9bd5627dd4db098ff519f935c2b7677cbd279e140345b2f61ccf67f87d7610d7e6e17b86b6b7c5fd261a6c47a049eee9d43b7bf8583325eab20df55c182053492d373b02bd9484b8bc1eced812364c16071cb65e2c2a55eadb567a7a064395683c6692d11e270c86e562462c37b10311378f13e87ec46207dd1d4d6a3045fc4179d5795db40c6c12a9c9772192ddcf6ea01edc13608fd19bd3fbc6712eb5ab651f52a64444a90243da94c0910b31201b1ea8b0a0c47dfb3bafb3b42d2756a51e9fb9058600834c133e0b528ee7c7205d1097e3eb6979d79f44e5c5ecfd9ee660813eb56dea455ab59553fdda0e077625644f988b63608ec3642f5347867e72f47ff1fa022889a87d6c62e71f59e2c11b03a4d8ddd03494cf9d397ca03bbe3da6c904bfdace0400dfd1fb59e5b9f8a2df525f135fb0e8a01be3a60a803a6947b645ec1c880531323384bfa81d69213388cc3da1dbaccccfad4e7dec77bd3425bb10f2bb8c0725a1c8b7f86962cd118dc3e3cceefaf1bc9e74485676922c155886ada32e8c1d337c8e37bcc84d2676352096697b890afa3a1f9a40d97653091f49a8fb114c42d73e327c36b14bafe7539c3f1ed5f6527548bdee31f0ab5fc2696852d16d812e6ba42d04b61a06220eeb63200279e58755b7dfcf506b6ed88652935cdf01bcab902140d4cd1a4cb12e40bd2274e82a1b54b04f0e4550469c42a6b14556513adf6345ccb29c40bd104273dd2beb5d87bfe30b5ddb1bedb0dbacc8566171721a4da329aae88c200f6d203a4a151e2b65756f26eb895d482d66d1dfc0844427f232bd06945d25f2e018e3156103c197ee55fe2c3c687475332217b06fe000de27c83af9ac0441a5ff6dfe5710f24b08b46cb4d96552153de0bd48717f9e95d8806e3549975e547dfcb48e96ad8db492ba24636148b4987893a113e823680ed130165220868dda2c0dcd14ad2a856ac54fafba9ffb7d3cbccb4f97a1b94bbe75a569f7f06a531c448e075084210cfde2ccc243cb6bc018dac5e625d6a1391cfd2f7698ed9c02776e0302bf5dfbfcea57a83189e7307a5ae4513aaef3894fea66033865035bc421a2b80941d4120021cdf586e60396ffeb083285aabade4eddd40f3ee126500d413bccf0e2ea9fc3f347f882cdef27cd437f72187c5d5bf710a12147fc37e08ebf0d38e08f0127fe33c8057f0c38e2a72147fc2556502c1ef6d5f7668d6114c30411f748a61aebe79834e70c5577077e8a6acd0b88d674d20850995f95a987ab3b7272385262580d5fae19ed019f337ca179fa6be8d977abb10b1f7c2a113f3a643bd9320ee6bc10ee0e2c288a2da4653771a2b684b43878ac7b47a54aed6db61ec5655447a4b3fb86bb08438e516aa7d28a71b8a5f31a57bd310990e04d85a84c53237e1d03c7dc0ad63e0c521887dd87cc3fa7c880500f46fb7cbd0c95c1a4f7c5d64bd48cc803e468241be2cae4ed5fb0c5011c220f6147a39b3699c687893e0294e47b0cff1556303b24056fa98dd5bdacd5e801d1547632017e01817455d326066ff1c8ae8dfcfb5c781955f3b69c068957e1339e1b590e1090932d95e22367b86ff3636dfbc8741e8dddc5babc65bedcdb366e480ec34386049434d30c103e64c5679d4532963d6314505f6140f0e8fde3c2d6085bf477899347aaf8f403c7637e94198c9be2e12ac398f2d9d7bef8aca0885adcd7f19498e362a83f8eeb85cad00133ba0ad78b3e97f5820cb9a0b33307b8afdcbf9508515a3c78a776f38d2db28ac02d99a7da89bf5bb8afccf808c4ba6b61b6b934e8c22ab8977d097bd7ca6f6ed76af5d637c278276ca1dfc801688223479898ead1c52f126bc87a5dac50165d4b9d0bbd54cd864f09774d2988f4d2bdefafc4a00ed4ec223f7393046525c6084a3770bec1c6570a646a271508ac1b7edf7ada910950a114a1014580d414bd98e5ae64ee664d93eab0f8b22b3777d842e59332b40b252d838f863f2ba08297bac71b3778525b575304281fb889cec681f9822e3e01b9a75c76fb94c6bfaf2162f72c669fc0db07cfb3ffbabef970548f64c91e20b07a703cf76f37100a431ea6522127309b6e890bd0de9968786f0a17021d60d1a380b0118ee800649a91e451a57b17d54b80ac8474b44c96c5c2863c217f6411412b56a7eaf3ae58678d96e893e6b73323e29d4aaee271aee5b59da60656e00f7b4aca80802334924ad009d710855c92b095d42ff8dfa1b7b88666001955c1500f1dee1728cc33429e92a46cf65c8c6d79219529d35076db0c8572b72764494b616a5faa510dc7a94e6d1caec8c09dbfc690c623d97e0f098e8942aff821b98dd1cb0a24139f0c8859a5891e55ebc8df2da9f0414800bc8a5dbc5258f7a9825458a04ac4dd2860d68103763a1d5ef8b045396afc7e1af769a829a40bed803c53743bc096784ed4044fa39adb1a44cf47da020f938532eca91dbb2ac07987b04b32a98011c6b5f6335aeb7687d779285324c6ae16d2d4a77aeba3cd50c908248fee70e76a0428627487af0da1a2a4813be193817c8f7fb93d5b1b1277b80741e84e83b84345b69002e744bbd4b35b5831d1904085d2e1840a4940c792c12c03dca122fd15700077a89607e0017796a1ae55d5a5c526ee324fb5e84a906939734b8341ebd0a498338e0d8eae3f86f34f54b1ddd3d0432f72b40ab8da4bbbc88b15022d7e05be28809b17c5e9e291d22707fb0d3c397c0011138e8db64e7ab4609aab3dd2752e119231301ffd41f070029b3ca132429e89f523191aef3569646481b61444d4750959e1c0b3d39806e78b53a49cedeb60e25044927f1f5deaa7c4a8d502059364e3d1a96d4955626d55fd9cc47ee91997e27ca81aa84de6dd9135539ad69ffec4fd1d6d0b4831543ec3a89e6f051a92026d8ef6e49b927bf0c4c7c005cdb983f1853abf3177c41c8e4040c5aa7421e0744680f77d8401237d0af83c0ebb0f226afe2382f7a48010ff79f5ad44848e7adc01d5c960ed101af1a221058a106769608283974a83ea2129911d8949ab56724272bec2dc61279b860cc12dde586c8e7d7d5caa9be72ac4922e19d5ce8f7c7770361807cb71563f8967cb3bb78ea1152d2d5f91b321a023c83ce521307828da1526a747ed2581974e4ee9f234ceaf8ac1ea091d54084b90530dc2dcc3423b80f142b0a5229ac00a925f738e02c7a0cff95670af3924309b6a037f3afa1a7d7016f5b8b93765ac2ea823692cacab39f022ee703c54cb34578370e25e0b4d2b22960152f0e2d5159140f0d3bd8cad4e1176eb25231485d431d4955d67218b5e2148b7ce12adce0ca67f0ee1e40a692ce2beff7494fff5ec8403b0896243e28c8b7252cea8ee236244165f317aabf0a1bae0cb5d31a823777388682cced999380e5323d985d46d612418086fdc96080962296025aea759a79b9c67f615b7a510af53093b195ec8f8f8b1fec36ccee842d029bebba96d158dc5be54fb8ac6a248d357761004daa4c7ab95a65290612f4869249a421430fe98e39660406e69d9cce9fa2960191a8b422b60d338f6a010be90af2843583514b05d8958d01f2678c7330e1c19c9df0b8951a8c2b1e3a04c1ea132d52b9c7fbbc0ef0f853145000b2437ccbe4338a5598327f82310d443f2e767da716bb1ac9778c827f2b1b1dcc1539f4033484d874a39d629c3fadeae5156678b4421c5dfe8ab3f966647656bb45062d7fc9e729c42ceccc129b6c94bbe9b4294b723f6017b38a7ec8ebb827a0469874b3cd6d195eb89dcc493f4b175e063f9b347e9e3512eb23a88ae6c1a01bf4a3d92dee280d04bd346474e5bda16073f155c2120621ca5370bad8e3fb26bff872a2e9ef4e603ca9bac4d7d9e0c9bdcd8ee3fe146e1f6ed79919d610b76bbb9920c5b810d6b4bd852e7e7beb0c65a08d208dd2e5aa0f01238d0be2736b0790249926f274bdf790b80d0d7e73088d869d9c0b05e25abf8701f761e0919a38123934994b4591fca6ba6aaa1d54008fbdd4575af5cb4fbca45f9575e948baf000cd54dbdf2564324a450dcef894d8c561b55dae3c8aaf9c0489f7b75a4caab6e64b962efa4c69280ebcd5bcfadcbca8f983ecddb417c5fab57afb8a1903462be63dbc9600797ccd399e704f1ee848d386df22eff51b6b1161a75eada6244d6319c9e20b9f58b5e14a607d665b6174dfa3552d0c22f5c0e39cda0ea3905b26a06986117d42926812c4904dba692d34a635d334015ec8f7ebeaeb4d6d09ac564a6c06b17700aeaef60c4e1c4c11b6ff3489f352cb7d0837d7bd9105caf0a5f97460c2260c03140a16975509e2934ce49bb9ca611b81c5b152368682160a7c69a927d49e1ad836d3122b1565609ba157bba4a9feae501b2d5204c241227a330fd447a9dbcfb479fc0dde8049f00f5749fe268862dfe26cd6c090d2ad6e2318ce093800d3997c6a8831fa06686f815450f8d1e01d12a44aa9cd693d2c4c954c8634ad11a6378f35c157f82ece733b4ed698daa70a0e20bb833f7ee7e7a2a4beee99379e400c0ba10931953cb3a4dcae74d82d45b687f9ac903c4f89f22a9982521b8f16b888c4ed704a7c7fe425d9deb18a4c08fb4ec0e446c0b7d5e2f6e5ef2865978ae56775e88a88d4beed66c021c27b0ae1191a05236d6ea683bfa5d6f1ed893dbe40e4c1c1a6279360e1b651b41dd820d81ef727650786e2c09be893bd04dc60f697aabf335bc617c3e11cbf6fcb565a3ec621047d974e895e9cfc47c47e537c5778a392b9856a4cebd7aad749e930f0e673dce275eca2644ecf7758fec9e410c5a119dce19f715aaa84e4c680d9cd4da5221ca52acef1032853ba78609c5b63fd16f4fa46684cf03c9b5d19e9b074a667e78f2ed68415ea7a9a8164c75746d8cbc547a3e2c3b4dcec03e60a047b5778c34fe80675f629543a362bb5d24f646c159b969430cbdd95f9ccae7111ae9280328a09ea7adf9b2dbe4866d9dbe6551c92ce2a88c9d0f660bc483ed0cdb43129d6cec186c20ae04315929942bb7528046201fad15c3fbeac5dca70c2c03744ff4e31523a1b787e9b5ba1fe41143a7f717ca81a1911e1ec74c1594232e41784dc0efe2b32d7758c354b3aa090668f4e7909c0496d9dee129f2a3fc85f4b5ae923df0ff8d5afe779930191bb89ae239b07bd24709a0acc53f12abc6fa8441c6a3da4e9035ee882ab28d21c84769964f3853a30c3a76dd33b6f4a8362bd17fe7ca522e36813b514dd5ef3f4314cb0359236763ecd650ddf3fe1ef034cce84dbfa490824e4b29b2c49d951cc2f7e70f261854db518e3718c2b6b4e168be0db30dcf363c3bb8d981b301a9d910a0b81ec81b141fdfb9d294647ab74f9f3594c3e9c7aae85e0fdc2af6f650df80259c853101dfd78c7f5293a862925f0aaeb38f2432681813aca4839f2e1e3b551f7d150a2dc8738c34b8f721b53f16c8981362c1c80186f57b1d0da58ca7596a2416f7c9ead70bc430076724406a14ad2e445e9d01278810fec8539a3d5175aaf52548bbd682c1b37c41f14dd5ed4d5a267298b82030dee7dcd6f39a57b4e6e31710eed64c6d488340835590208ed55f081bdd451de7b78fefc12f1e6555e1f43d8d1fc4096c674bf46f9bc0b857749609db8fb262acf200abe588cb4e7a69030facc5932cc5bc5c2835c2a7a48b37b8cb05237fa560ea00d771cc120ff23c9aed6eff82654b8c4618fe904af2b662ff275ee9143398e9470d801abec1a62a209e7cb7c1e7f09dfbb72b995fcb6a4c465445ca19396581c7b120f5060d019825ddab7492f8f974543a65cf7c410dafb7a516c89dba30b1c1fcb0daf54c3b71cc9a283e8d535f2e73643a32b15f1bafda76f7ecfae2d81631fe9b48e86d1bd8fef3d5d93447407f22b672fb48cb2f4432e98ad921c01543c6c090c50da2c28bf3c48d38624d049fc6892fe68cbc073316dba856871f5b234a8a621e30377c9ec9b719629d68f859c61eb13b2fca302afcace69811f19c97dcba846b07b7304718216c8dc52c6265815d409f82551b9eb056bf8ca9a2792ef815d3ebb41e43d086b480c9c2e0809ef7f02482e490621064362f9daa95ce9ca4fd53aab928bc9b874ae0bf0c2a453c628ef106358874972a611e63ef1d4e55e8a91de2ad51c998b8f9145abcdde80b36d9844b0abd61c4041bd3c59284bf8bc606ec3aa9d3ddff93794c0a41a12dafa02cc231fbabb7c5b652700dd673bbeca6f02cb78c73834c19581c8a769664c3c8023d128b7cbc3e7aeef919ae2bd7d9910ea5a2dda4b3dfaeba6d1bb02b7f104b2b0b238dcd969da9e25c881473e2ced3b641f9b744c4a0b50ffe72b51029b5af6250e649e1536e844896a37b157971b23334ab29b256659afa97837ec8b4c5d1d04ff3d2760eb807e51b946c466ace2d3b23d749bdf6e93aaa991ea40d1033ae92ac4fc54c6062be5acc68a7db40611d003ecd6898415527371ba3738b4bd0b9eeb8b71c329be65631bb46c8f7a0391a4dd1a9094eeb0215a1d60a40ebe290d22432ed39088715dcb34aebdd865d1a30e462bd00851801edc9de25ac1321f7b834ca6b8e805c4b60dd928b19fec956e58409e8ccecc506861253d41c01dd4f9f578048bd5b8f02a245261f984485443509b7ef04825d35451a748418218130ef160be64cbaf34d2bbed58a417d4af3359338a2e35daf763b828a4606bd2c6b3b5cdc9b403db60a4ba568bd7c515b57e214ca04b2ec6450b4c58ef55f271b817656d999d48fdeaf34794014de2c6f25f3797f3c599d9dd81d9d40dc2b1a310559e5cca159c7cbf265b4680e32a39ea6f76e2148dc8d54352c5311d0a61aabc7d48a43f61490c9605e66d64b5a1909289596923118a4b574256a0b199b5188fb7c5ae859c92cff701ec23555723ead3dafe5ad54db236fc7feb5f70c71e567825e7f888e542593342853a60d1166cd391babc5c7ba862ff68d036c20a2aa316ae0006c7580bc072f86e09c598d94110dc086d5b33508fd50ddeed384f03f0d21a95b23d3e2eea40239cb38b2a7fb72e07d764675c6f1a7ee3fff7335cac62258e8d043c43e6e6bdd6dccb68def2d0a53a61ea225df8a85236733ca467b222e3c0c6805bf96c14439b90329e31746d022588cdda2613c4e6f9e47d4d1aedd65cbf6adc64ee82d78c8a902a21f8948be24a64d041764704a2745949ae6a5f610b245bc624ee36855232ece008ae48ecd59592d96ffe86d5b1c579969476eb50735fe578bcdb041f4ab7db2c499fe56e07034b826c29f0b1b2e56309cb32c6cb8cd30c34b0e513bac74d99086dd139653ca2d9cc24634ffc248b31a15412685c6c7f1333fc61953f9ccb2fd310e978e1c8b59d2b42ecc6add4a5abad8e8902edf104ec1c26ecef6a2b8361249dcccb777a9fac5cf3d0251d83d7ed847cad460190f7ee7a4c551894a17eedad497bb96b1baa987f89db4ebb8eb6b84056a25469e41d4e088cd92f732acae0857f5989b5109b9a7ee0ee5edc3d7dbf5a907e177bd3b2b8aa8f1ed09fff4ca0fdb232c6a19aeda46a319b01ba2c4b474907e8a27da158f7119c58635e25aabd7637159addd437971364e1b67f9c0bfc457ad1db536ca442ed9e4364ad355b0d4fa42fbd5faeed919859caf9138141a3be1b03b7339b75cf6a72514d02e126ff2669d823710db23a6e703d2538b9db77b2588d3d4d4d67c01719fb7c3137024e89dd2c83ffd5e428e40f57a980d645b4380ac85363f8da667e2fc0c0309086041402cf09f3a935c7515251bf408b7cb445526a89087f18fc85ec9a6b939eb2ec910dabb416362a351019e6e7dd46f911e7d3ec8ffc42457ac2171ff3cbecf895318ecdb9440b0a152966ff62eefc61fe0c682222940553a918ed0ecb30441512a4c720077b0b0329b7a9048383e06d58c42ca09e41487cc23dc308ecc42782b780b1e9465ffaa880f9869f80115a149610d549eef7748251250a14f10f5f21ac44db24810c577c22cde7eeafe74e873f3b28ff8be14fdac0d7342995edc405c28b64c4bb19c95e1312c293855ef4ebb11e2510391966892600d105ceb4115510fff4c4a8f7b456ebef6524b3f6c1d0a2448a6841458a6e58e8928272bc1142e4d13857d1d462dd21c268b2c02844b808d388a9f8e904098216fad46afaef389235a2bce462c411bfdde2268137461a2c83a40b892231b150b31c5cd8b1ad95d45fe3bd18b8ad80dd191a09745e23a22d7766299f5f5b03eb0670afb5510a5489366ab846665ab81002c86d8215fe279a4ec7353640c12c9b7bf49b40cbdd42c254450423534a1d9a88d8c892ea30c20b52cfbc103992a1b697d68c4b30da0102321ad820e8613f823ee409cb068ee70343481e1bf980802910ad10df4aaf489287bdda41d2f5013758230c4f891410b1a1c628cb9e10ab223fb106358e4b0cd55645842bcdf5c34572a140efffd2c33fd51d13615f94e13e36b4399356a8ccb6482e28309779b20e0176fc357d2f57d250f584aa40c3f7a76f191f10b83836415678b29bd83c3b0e0138c881a6afeca2ecf4acfbedeaa74f5f625a57048f4a0ebb909ae83e6b9b3737358882e0821e05def6aee0a6666b7a2e6de5558fc57737d2fc2970aba59fe6e5c0df7ae5e154661e489b7d649fda48fbf337b2e5b9ee5b7855afe29ee81d58be1404518ef76aab67fc38e9f8ccdbcf52159b7cf1917d2e28f6e0e9cae3c30fdbc9fc7842a91d703c6c59e754b45a030b7152776cc74c25353a79f9a3a2e623882269197c92ab39ca515045f0898f8f80559e89d00a51b16cd985b9e252e26190d641d8e597c55d460cd5551996664e3d83e570f85955d9d7860148af31af5c9bd662a1a94f01fe06b91fc31e32ab0f582067bff256cb7d9c9878c55028f3649e64144356fafc50930b72021cd4508bc1459ded63fe9d2a7c8a4156dcf1c35b41528fa7ac2cca125bdf5f32d5ab04a5c998ebec1089d0d3d36e5caf98240dcb9a273ec83b41ac6ce3c0dcd5b9badbcf080df0e4412cf74149ddc857ab55455a4a75944cd121bfd9b1bcbd14215fdc252adf87b3a43e3636920ccadfe7d1d99f24cebb902833f8c252c2d3dbdfcb951d6525d4e1c0403649974a49ca054c9dc4525cdb37f6c518595581f1f5e4317bf44c03a5b7268692141fdec605808f92f72efb0f34585964055db843a7ac1c474926851f86768d82d20f2a9682886e7684d72bc51111b5a193aa7ddee7423a7edf3ccd20f34bb89fa648c4dd77a5f5f9b4fd2081961e5ac2ff8552a951ea627320527790ed2cb5cb2138398ff1132f769fdbaa9d7aaec672180ae57f7bb10a121aafd2cc4d011957e174468158ffd14c457cade6acf4ccc5caaf4bbbc053748e0aa3d3fc9a4947ac794f342868a4438fb8a23f6f7dde2281614ea79dcbb35437685409453f1f5668e6c49e9d36262bb36ce5e58814bb22c598c50432ca974226b5be0d89ad1034699f2d2f8a24122f5e1b7cfb312423b4b7769a83bd1a38742be04dcbe427358cf8e15e81ab070e102a2f70a0afd1eb25087c28842b0bc865b504921997093f9acdac481ffc89e5fe31b1832fed8d24f5083e7d28983045eb4de8b5e4ac6ed31f8dbdc0e265f3156f5df100732e5a636e21ad3243b800a017821a3b55355783a1af2d174c946a2febf87fc042235403fbb1730006c1a00ed8204443b9dc22e6b801a996f3867794e7f513590e559fda0406285f33f29cc43568169e2501543c84ee9e9571c157201806291a6b31625792807aa82d34db1b50b091926d7f90b5b4357b590a301551318d12e7df04f050267368f10001d2cae302ed4a4664e8d08507366929c1d0d6a492d734596d3edaa8de5ab761ac08bc5a644c804b3cda5aca232943924c30955a42d8281d6b63d52f1fe7fdd380a812e98c2355be6637ff7d7530061e249713bd3e059f6e68c6be8449792ae5d0f1c1c254ac75588cde68463d00ec2517143c9c1dfd1ae6bfae9426e229afbda1574f84480f8379c3a2d8a3a69c5157004d0147aedb32bb6ff63fb9421d74ba5309d0850d2013da2e6fb96e50c8d3f998b64fc5dcb92be062073ca5fafd3993b1ad4fb0d27aa51aa8d81ce32423a30b14c323db51e2c95f7f95a1c776fbed2db0a4e23437133ff8e33354136a745435ed1e0bf479c73e2f859e628e75c0466ba5709b7623c006b2cb57d2cb6c67fbf90b69c8bf258844cf06d4e143759b24b27479774b699af7a42559d3d0b467ec166c317a2207e79a8ae2e48e61c75645f5b87c34f599b0be7c3b65cebba0c5fa98ee198c284720032601097b1a146324a54664c284c360de512cf344da81cb87849a7c17219bca215a098b966974d56885a99e99a484be0370efa2705772fa455f194279e206e4018d1d67e866e5445d05a4b78d3168bae613df75039074b53c274549c21e568951ad5d22535bddc4c5aba72cef68ec00c0508761160c549eb5ec53771de3385c25eafee19f485ff9935b027cf5235ad91f6db928aa53268457d133321d6e9c2babdd0c2f7c3fb63acf7ad1d68f4925b296dfec6b9e95f67fba4333671fd33111a4c7115411b956b3931a32238c3f9104ae5d352e123922dfee1a2287ee19ab7faec1364d7a454b61f4f03fb963cea3e387db499174557403e3397dbffdcce9f5663ea904c76542e03ba36e66f5d405a19a50d4d8293028cbee0b89d9d9dd976765b22a4bc22ae68ee12b259e82e2f91c0ad2c1b3b2f2751a430142081ebe81fc9777067f40eb83bc3d5d0e9e89d31fc2943ba254a2d0259bc68459fb4d648eb261b2122894476f7e696011f070e07b10639f2f5ed07de8d77e3dd78345862c8123fb893624befbd98b5ac652d6bd17b6f66adccb296b5ac652d7aefeda18712d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d06c808686868686868686868686a66752ebd27befa4f7de49efbd3df4f001253efb9b4fecef2a095a3d610921497824190289900f242b24499008412205243648668eac90f0c0862e7c617ae4bb35478270a4c7111e18b9e9c143410b2a52aca4545238d4c9b45d1f25d228531c919661f8e2b178a6102541700d2476c1a4930ad94e84097894f4e05132c37384274916789210e149c2044f921f6bedb5788e2451828ead744ae791ceb332928405497c20592119820407c98f111c19b2c191213f1cb9391244129c24373df7baf7e2cfdb978b7d99aed3855d31b2bf1ec216ed5fba9cba17a98a32481d0d306cfaf845bae4178bc7e2a121ca58b178b287f19177178b232ef6f5d70bedca74a6bf723d9671775d73ba015aa38b720cede5732fed964fa18ceda5fc085b487f7ad3531de9c275d4870e8932b68efea028d529753f98fa508bfa6c139c713a3dfdb99efe6c3a1a418fbe3669983315b11022638a035fd08f2bf882fec5b0eff1a66fe9eff777af0d7a82a943f736dc197417d37f2e36f63d4a06e9218c911f7b085b44ffc5ed3de982ea6e788932481d8e0883fe0bcc0dd2856affdd5249c319dbf638e85ff0c76a79d4c5e01e853bf4b11ad7efbe5943f802f3360d25477fc54737f658a5617ffdf496acfb5e36d67d9f68d3bfa8ddd456cd6990796fce1cf7ef79de4518f3ada7b80f614b0bd09bbff210b6acb86b9527e9d3bba059fc0adde23b9d7a0068ef6794b122c298a5c94d19d043bd7cf8c1b04dbf4dffcd6d7a019ade932e0078d3b3c018f84dcf4997873006e94d1f4d6f42b13002caeb40bed0bb8938ff65198b77e18168f12b5ef4a36f8005a06afe86aa49c302d09bbf61d55eff3f84baabdbeb405ea153a6f75e66860b3a65ea5236b8f02bde860bbfe24dbfa2d37184a9b3b1e2bd4e478b5ff13a58bc0b2f33e34a17efe7671803ff54f19f6f4cc5633fa7379351dfc29b3ee5a3ca9f9ea45fe07d8a7e01001ee5bd0900af427f2f6c538b9f5f43baacf8f9507bf89fdca2875094ad1f3d5ce93eb959b8f05f4e6918dc33fd8a28e30508c3f4b300dbf4a59fde62faf89ef6a4caf42a9a05be30bd677ad3735d0bd3b33041cff4f365a0baf9274d1f466efa8b33a41f691e62d61f37a1d8f3b54c7fd6c6f467014063cd025ff8afd02e68ee7b61fb633a2d3816c20c821082256e96a8914bf4904bec9040e48d0e23b4c195be41414b90c544f830124ed76413315032e4470a449468c1c6a2165780566801458e4f77ec802fe04e2520ca6e87086cdf32025b66c6f8834968b9e4a98931ca196493d292935e1e2918b1a1356f10306c217a0d157b01325bc002b2052ce04ee83ea160b8e213bdb645601b5222284d84211f7e9a8646c5b68f5ba04374a84a97d7af9d216a852d69583b7679657247f2c5b3e35e74186354b1294ec4a13451ea8eaa81fa031d724f3eada1351227a78342e4efdaa15ab4035fb8ef582610b1e5571c1ce972edb8eac2d9f2ad4c066bb74841fe2e237060c40d904b07578362cb6bc896aeaf9d8be7ea812f2e254018f277b6d486e00bf9353b308057cd55c46573d5ec59831d8bd87468cbbf68a20c0861cc97178db5577fd66e21945b340163061fee4f021b76fea21eac84264c40c2e8078b21b22aba9c943a20c6d4e61efc592bd66205d98b1fddee4139350f73c5564d76297f3984136a3ea00411345082c6526248899514a27836f9fbb85a4d9c287facdc777c47fe181a1a1af29d196da2cd10853ed1a6669e4d863edbca20ef861f33196075016e0125df0bad8db53aa572ce292595a2165072dcdfb5f0e3438fa2164392869c92ce4929a5516d8f1eb5d65a6d46c3b6189a37312d86640fd9c3755c272828d6c49a9816430ab096fc6f86ed75052d9cc018b7700263dcc2098c718b27b09b23c70ff25640891f880c1181422409224a20620422428804e1b8f7942001111f6e20859c1a2456d044076654244094004814238cfccca825d8b556abd65a2b0b4f14806c1f263e4ff059828f117c7ee093039f237c6c7c76f8f44822ca25c2aec5126329a594d6de2badbd97c71115ff859f0377c6f5d0efbdf75ecba2d4b228b57f891e63cf019619d96bcf819719a2c7d65a6bad8ebbd6aa8249b71abe7891aa6b5bfb6eb9e3aed6ba90d2df828a14dd4aea6ad702005f2caec5570bacef49c54a4a25854b813a59daadd4b25a26da22ef94459666455a8689b46b2d6dbed4787f1c16808b1eae67338058d442498e2af6d4f366e388f943163f78adb5d65a6badb5d65a2ba53b68f427d86b5d8c31be18e3ec628c73e4204212434924899ac60c3bc42862145bd3883fa54f199fda196b941e29d5e60e71da6bed8dd069d4156861d21bab6543a4d12b4e8cad66653424662dec5acb5ad6b296c8ba321a9e5999b5ac652d6b5dd7d3987fcd9befd42c964dedb2582021943d5ca7a6c76b72a8ff6650ad9cc6f172fafa263b6d8ac7507f7d0ad4afa03e857a158d423da74f1d8e976dba3673db726f59f6869ddc2ac150106b5dc75a3b64c4108f27849ed0b1d65aeb38d63a8fb5717691c77784b88e8d372e0463d7b118e38bed67b6f473cba0ed3eeded679b371d81641aeb7060db2a69d87d579763e66cd8f580eb48e7719d5b12ddab7d26d231c81695487aa4a1ecaaedea5b578e193dc7733cc773b2c8eb104fdc3c31e3d27e0edf12035bbe13434ef438a1e38410f5a1fef296d1315772d7f87043fdcd28b3b3524a4e24afb7f24f2a4ffafcda43ec57744adf57d117a5aba92b75a22ebb12771fcbbe3a1c2f2ff24ea0096472fb821be498e7025f2ec475e8957372cc2dab7d293dc773e2cd64810755f8f7c65b70ec4317e22d39f6238ecc68f87bcb85dbbe074df76ebc71cf3ed6f5bdcbeed5dff4e62cf7fae5c18e23e4bab5d65e7bedd5b3a45f43a91a4d1c1d6b596beddf1bed7b0f0f8683b990eddd772fbe7e3d0a811148c47197cd76de915c6f7dacbad5bfadf6ee5f98bb3bcffeae85effb3b8ebb906b61d1df6ce9ecfb39def0b738fa636fcd60df7367df1a69f70b1f6b9ae8bf1ca3d76e5c9fffdeebb3487b6cddcc73f08d1777340a6619e073814bd3ed395106a55ba54814a12006784ee63911c6ad3ee33ba2e0b0caf11cbbe9b872957d4cc724250d633d27cab899e7bc7718ff97635afc5801f17a0e759dcafda009204df810fad997457af22527b3bcd7ba9665d12bdf7a6161f1bcff155d4d124f157478aad08387899e7de10fa594529d29f4328172fd91ff5cc109644776aee2bf709926fa3aa34d2ce25e12f76613519a10b271c4a2e3cbda6c4bef1ba59452ba7594524a692e7513284e1da4bb7badd1cea09a33bd9efed01d325132b0ca3f7f7267ed65ab9d40f1a790b75c2f585c3181228cd8c5955724dad04df5cb8675d3999fda9f3f764619593b57d5ed88667af9e81ddb1ab4fd717703c7cb0a86222ee67f2bdbda6fbef51fe92f5aea34be487bfb7b791e2684d8c4f3bf178b6ad2c7f9178fbaeff70cfabc3d6110a9d65a6bad24d2d711c93651c4c6bbde8d6dadb5d6fa13a8fef71233ea603cc3f0756986e1eb16116d26d0d5746b1986af4b69cf0f1ae711120408fd7b0192ae232dc3f0359233dee34334fa99d0a68ff9cfa68fe95fa309a4837489362cde070541a0d18f4d6311faf445ddc7e2398f4db9382bb5acb5d65adbe11bf7c576f77e9cab1974ed040af27080b10a091512f2ef85d1dae8b5baa454ce3927959256f7ea2111617c38df05f458e00ba8a17b8c2eb2a7099700ccf087c188edff1264863fcc6afb2380670a34dbdf068c96f583ed4d78c2e3890fec11c123c2127e7efcd8fcf4f811f283647ff707c80f919f219e10a0088132048a11508240f101a5889f285084f8d9f123f4f304e807280a900fd00a4808d010a01b201d201a20238066807e401102e201a50894202851f08438593989c2898f132c38197a02e5099127493c09f264c8139e27354f74f0e4e6890abc200cc10302131e303982890b9810c184044c3cc044034ba22c19b224c9929e253c4b702e15c2c6c6c686088c3f8a883fb167499026486872d3048926354d749af868d26315d4c4034d76ac86ac922cb15909615584ce0ac88a88efae7a30c902131e264f30f959f2c344083dcc38d971d203273658ed701203272d7042e3c487130f345935c942932126449a1099382bf9c3778674628c3c4b7ee40864c6083d4a8003f8c218704307d4bf4a60c1b6152551ec6f850e0baa9220f9aa55490db055af1217e05b616a28e9715d4a662cceda9047094f078c4461840a469c18396224092349303204233a467e18b131e263624a5d724964f99ec55009e30c3a6eaf2673d28b419e1fa79cd3dfa1c0f41dbeeb152e090e7a36f9c6f4ba6b98ddc5dc8b41960ffdbb1c8b25e4f84158e461ae5bd95830c1217c21e30663cc8f35ab616f56c3ca6ad4c0590d6b4a2718aef06af2c76d6984c7863c45745ed890a748cfe621ee442f6dfb97f51f8462cbbf74e461a1841c23cfede2ce841e8c61bfb49d5a1b677ffe38565ea666d82146b16d07b753d731eef4c017924a2c8afd65f842bedddf4d2580be7ddbdda870e30ebacaa7d7502264f65a5866a44c4a89adf62d31932948cbaa15d76a690f334bd75a6badb5d67a2fa595d21426b5f5daf5387b0ebcccb81e636c695b37dd785a1a6e17600bdc97cd344c420d5cf0840f867a2429e2a388088208816575c4c2be32edc960c70d8a8044104666b8005b685842087a70d3b00415a007b73f9329c0171666d57a6f56ebbdaa0ce3a96da533aa86fbce8030860c32b3af864976d51658e203e8898daf22212872828b595a2611c2be3a1ad95553206e3880b3f195a50b0d4b4c01aae0a6618927e00bb83fce5ed67a84e182499ded1b800210e00516180378e1a2450001c0151b00937a84af1898d4a26be14b0676fd0b950bfd98562d2ab718deb4619a7555bb659156ba16bb07c0a4b6600201b001865894278bbc30e62021ecf23571dcf65ff2bcaefb5eb2940da527bd8d524712bd0d1cb11cdc933117c984dfb49534a98a3c16fa9a666516b383fe308f0991fef216e58f597bd7dac56a1673b13b35ede6f5010cb148de1d0343857c39fb17cbb5dd751ff0802b5576ea4c8f897c9f5e10c2abc38c97d321b0bde202cb2d5fd7b55528c81fb631d611e36ebaa71241c6ba0f53f5b84290286cb02cd337b291beb183dda387fb663bd84deaecce6ac0accff4d5348bb1d68ff47d1879e3fe22d6a5ac873b7f8ce831cc1280b597d918f66560633662f6a5e3545c83c575c28dd54b665bef09dbd1c03adb5df8025bcc76291b4a25fb3a6c67a3f4db6f9d8e522733437322e3ec2f7c81656e862deb86fdfbd1b27ed4ed60f7cd1a6e0bcbd86aec62d867577f3106ebe2cdc5b0b7d6b2321bb332cb7a2bea52f625d65f04b23f1c9886bd7c7a658f33edaf2bcbb04b63d96bfc98cc9e6631b88b3719dc3e6c2ca5761df764ad95c2918f7680e18a7bf355e1c8f0d30435d812a1173fc3e66fb34bd970faed6d9c7eeb741c315fc7f6a7d9d938bde94d9d0ed39fb00a04f97bcf48be7e7b530e1f74dc6039b6894633e68771172da3f7f7b20bea3df75c8f44dd8f344a6712892412794ce4ef270d993dd238b05dca987b2f38fd3c3dccb2b788fef4fef064dabc4eeeacc3f40775e0ce876deb319a57487e858050458267f9fa3233a2ece4eba85787fddbc9cc88dd9723ce18c45b688c3a54b3b8173d1715aef026c0a7e1af5a626dad94ce29a56a0910aa26aa269e50febcad4242a5c4b5b6564ae99c52aa9498ae73c50b636c69ec1f65f679f1533cf367357b1bb983f2526b5e12fbc61e63188d1b7b9c558d3e7d1a18f598c8d6a5eba5adfbb2aba45ad39c54c9a9bfd1969f0558445e116e7fcc755c07bf9c7fe9f958bd10cb711c981a0860912ac9800be4394017cbd5e6de9c39eedf7b89e1a5eae5c55e9b1d29cfd7ae1c669c40f3a7bb010708b8273f07e9f237efff3c968d9b409b07255f2cc79a8b037cc2283b7a91a8b342fec27c5d2291e86fee6e44d28fde6a98f5189ba4eb108944d7c3c8ab24125d97f5395f22fba687f192e84da6d25fa5aefb6276738ae6b4ecfa92e84db1c75692a3bf8f2f3dd29ff691a424dfd0a050927c43833e24ed27e02dd7b244dd2703811c70e8a0699f655ae7423e870f3a44ba258b3d7e1841d3c3c7653bc78930b2acb33e6aec6124665913c6b0e52bc9717f31780e035c480c3568b081794cf2f7f7c62439eeefa3e7b8f096d8bd27b30286176fc15f3422eec8e13817c71bc796a24ed473aec44a2ef21d5b4e78a1052f085960603cfdf6a552b66d5ba9542a6ddbf659c9d4ddb07f1afdb775397cdb0cf5275216a5f8524954129544db567a18591271db367749f4265377237e8a87716efb140f3305c44fa1c2f425d387e305aec454548929918b3153d1a528459d54f76595eee352baefb9eef35e74faec71499ff447423dae98b7a4882d1f5bcc43926f909eee88ca9ef4d187b7643342d0d4981ea5493fd2f1333d3a759fa98b38a297f2a34d3462d3404af327c2183d8ceb51277aabf3c3c81c452228880cf16cec98a6b1fb63bd253fb68b3bbc257653c83d1c3ae4f04187b7f0d8bc55fe60cf9725c07ef590e4b8a1ed62ec4499a609d9c4289127ea08d56cf951c804127599a6d92feb2820b3eb7f2f9cfdbf9dec2e945989190a3146183c17bf825bc95b9c815d8cb1ca9a4d29a591524a2975d58ffcc1249bbeea47be3a16cffbe7385f4915fdccb1fccd637e833b29bb8fdb9fdf48bf91d56feccd1e5ad1bf37678efbf7bc8fe55538c8f48b5136a57146d6886210a3940fe35285936b052e1c29a594c69861588b31c370d56a8cd46950bc99010657c9cf348c772cee49d50cf2c7a23a22cbff58e843cbb26f3dd5d062b6935b5a1679f3be58c315178d325c9bca87919287b9e55fcdc3dcd0f2ac888824c618638c71027d3d6cf80223a3c2c953063b5fca3983eecd99e3fe3d6f0249977b5996cd8279f82f4ded6a02cd201939e10cf2091461aca4ca881c7726d00eea18063d4c148931c65c570d31b65699c5d01a6f1cc773de75bcc5c64c68ff7656f65fc4c15e922ca98013b4828d1dc3aeec3c29649f01e32a1464150af2e76d5903de11884e55e1e4e8c3553552157bbc00e34e0645f92a20597ed08eef39f03202a614538cb1a6691aa68fb519348326d0b66d3f7fb61965dbe66afe4ca16d9b4233a8d2973192e228e6288a59fc4aa42c7a994013687b295a28acf0630760431e287e34aa514dd334fa9aeb788ee76839e70c2335fcdfdc1afe4c732139cbe8cbf84f1a451c81442051270289413cc773446e6f5075aee60faad6283e24649aabf953a750ad0133c0d05bac95c328708c9873feac92b8d8caaef670e3b0afb73029a5967517c618eb64f0b7ae6bda4af7debfaeeb667f69ccd46d25edba2e29a5df9bf395f2de9cb9ebba7fe17b73e6b8990b5f3bc8bf7ff597ba573593ef755dfe7fdd9b33777133d7a5d79dc9aa991c05431d995eb8060821bceee31bd8ebfa5d2f35b6fcbf2f1f420821f4bfeee86567a2ac71a853e96b88f2a34e2451d6aea954eabeedfd31fdb2b136e96f1b6d1ac70bce52ca7ba982c89f4c8f97ff4b639abb52f779a4ee6389510591bf1a7e73f76569bdbcec91bec1c2e279ff1c672d69494b5ad29296bc74b84371c7ccc71ef14797ca9f3dcbca2bf23e5c8f25bb9464b97f7daa62551052aa82c852769fc84a29a594524a2925f69994524a99d23e7bec5dba74e9d2a5b3a882c8aa20f2f5d6a770a70a22dfeec3f1a20a421504dd620a0a54436c26d4c96216b398c52c66316b32992810f7e974ea54447027d3e9497f520d713aa986d84e39a338d510da675aa6659c86ca543fbca87e50fd908320950a4ca8df188661f6476fad25e1402346b1b727712516e4d1931e873d42e5a039a84a0539e82ea954502ae5a0a59983aa5440d21f0e9ba4e2495b469948bf71de235d5474294cdd77ef4a97e9bf0a88cf411f8794dea1a2e2419be32378062cf2cff3ca0ab4b05dd4551b9c544519e8de214601ab96e57b145bd2f097934eea7a52d5924927d593c279eff429852cf0c80216843c62aa11e49457b11ee5a4d5628c893c9bc16432994c288bb52c136559ceb2519691b2ac94655b9699acd72ecbe466371a30e6d46dd88af046fdc9c4446bdbb29853072d24ecf0d9a52d8bd9366871db5ad129ed8faaa5acc6864513e6274ca2b0c9613405ab2aa687de523fcef433c8145131a250287f3be72a7e8db546548cf164b57327523e691a598c7759cc56c3fd0fc63da91a4156f994ffe24ddd4c271497a2928a417655d1291a4a55949e2845b60fdb9fd330d2390e95818de2385ddf42fdbbae6f634cadd65b1a6eb7ac5ba8c83d4a73a6e734dc9649d38027d50fd7a99a06f736dc6e5bbcab81801e950972c6b06dc3aa13e4183f25e0fe77fa0d4638b5fa56f7bd6c4b07c6f097aff272347a51bc711eabc8c3675c88d58aaa78f98e55dcc81eef7050d1394895ac9fa2f22929af523b1ba95779954e87caa7503188918c3fd8c3c9971864051271b88a7a188f41bc45728f7a97142f1f7328dbc59b9708c43df9292a0af531466b2de68ffa13c79d5031b887d270733ae2a0ba9754fdd4af7c8a4ed52e6543caabbc8dd4afd8880363e82b5516b61c7739cee2b88be330c7611c97719cc67118e609e52b8b89370fe30cb071038c515ffedeb6bcb7efee6dfb150da52aa555748ac639ab11bd844913464f5845a11e76d0b2acafd6bb75d2d0bd4f669b5e94c598388efbaa39ed3191ebc747696ba79da7afa747c1c893cedea4b18f3acb62b24d9746fb833d9fa7bfd187e178b92f2fae9213db58c2da55ec85861e2f7c00434f287f9cca47e6e4cd3f1f3f10d88b1e88ecb5c7c011fe18c85efbcd3b4a7dbabb534a319f4cbc4829a594527a58c80f84e18a4feed8f3f184ee0f239d44fafc94f4393fc971620af7296f83fb9414cf712bcfc914bff25fec91d127ee9829e8d3165a701cc761008f14bff25855829ca27f03f7a9bf21e5553070848d9557790ca4e852f253cf7dea31b0d2a56497a29d0da9e7de46ea6d7ccaab7436cb7ea43d8b19ddeb3191e9cb6ccf9e6a3bddc309e22e2a3fdd27c5ff931bf527d43ff73233fcbb940d29cfbd8d94e73a1d47d0d7c17d4ad7d950f9944fe974a4bc4a273303ab40407d88916c7ac7893b70b61c70703108a53fb318ea3ec45ba674eba2e2e75fedfed0773eb8721e153acb3229e5c865f6d538b03d92d98eec6291141d5752359fd32a1f70bef6b1c75b86b88b4acacf23404fb6c41dd1c72378dc919d9d50e59fa2391dbdef657b47c22fd2df8f50fa1b9df4e76d93feb0ad843f6bd50f77aac3a54bef7cc8e19eef48ad837bd3da9983d4ee0961d50872910857d166fa8e4d08f20ef463ae17ca3e936997d403638828a5a399d51079903efff53b9fa4dd71469fe364511767e08bf9ee3bd67734ed3baa0fe41917c27bb80fffe1345ee345b88d8d3be4dc543f7898990c9ea948902b8fbba4f8f9cf7139c71ee9b2f2f3bffbe58ffbef5368e771d54a07afe7a5fe55382e25e70efa90cc76bef381fcc51d3b96cf9ef26768a449f9c38db9a74241c60f5523c8dca77c6a86eb3f1ddc9b1fff832b9f912eb1677e8a8e45a46aba4a883ca90f4fcf10b8e58d5ccac3bf299c86ae8adef6797763be18a5aa176418f76080e1de9c3917f21da9f2a028793ece6af43835f918db32ee2b03b66df761f373d041c7943b360f9fdca8c4ad56563564686600000082026316000028100a8603229124c8b2340fb70f14000e81984e6042188803d220885114c32863882108190008308480514868c8061132cfbaea44f25b48624d686caf1caad041f80eb379382481a9948a31300348947126c7eb4752b00b9744e9f4f81404f2c3c70c139b756e085d604c722be4f2cfe88d1f153d264f1950d5105e09591a84fcbd1432792941abbc38242914ba631c30c551bdad980bb518a453cb043c3ba5fdbe3fc91954ba7820c02f85f37d029c02ef5d5d4aa1e2a05ad7a8487d4b302e651aab2a9d5399920e96a8f01569bcefd167bd945b29ac6ddd9a4d94c5e8ce9f9570ec244aaf3066eea6794d2f05251a7fe206e65a11229b7d63a0ec2b6b0c60166be19eca742a09bf07bd94ce782db76f9640f219ac101bc0a0d21aae1fe3ddf571941f13db78a06c5e4a9963989ad8a41312682d29515e8a0d164fea17cfbc12aa837c39e0211c4df9337100e0e6d1f570a799322877097e35d4858e98483846cf0985608063118b57e1dd949762dd6b1817463ff8112a19123f0e8b8921081be033de459a2a340003a7f6bada9bab38a5fef22e45bcc8767bee52acdab33b777729446e2d6df7497966ab94359e600f8afa4f01c01f43331e7b6877772973ea49175ed377875e92af16243b5d7aecb71a22376fd86d3c1eca80a9deb9235caa8972ac0ecbf197a5aab2129262e2c83204ccb42e2552294fd2663f08a82e255a33b02e25cebb35d2618af5fbfb8e629d4a0ff7aaef21add73598221569d3f443afabd2abde68333603e48ebd752d893745ca75875fe7b0f391a325533405f27e9279dfdd8d2eba27e55b0bb72e1ecd3f8ec3da3de17bc32a953c58395e30748bb84fb72c15dc14baa462cfe10b39fb2c8396c928f5222c28c94b2814558c9413ed6687f8f7fedd0825472643996e080b032a0a42fec072dd453fe72de72208a5a35f460d731e1f311b4afd0cb28c5cb63010d233803ed0c161946584c5adb8ffb4be9cdae137ed32282ca7547803a6b2d51fe16b187a3941895b82edbe6554755bb8a0d8d533748636db4384878bc9c99e87e3f6bf5d2005ea0aa94a9810ff4fb2841a20bee3f5cf3ed9f486f046e04875001313045aed0bc74852a16504e30a9386771f080babdcfb9a91bcc86211933fe0b31e37adf2a93ff4c1f82cea713e6a7b0727df89f8d82e660640b0f064df468cf15652982ccebb25161ff9a882c5f48b9b37a6d6981328376633643021ffef965f745eb9e33579e809c5fbfa2c1eb46da260c59caf682490370dc6ddfe561acdf235bc302395d446b87de3a995bfa902b9ed1b51a3d8113aba766c9c0f9ed6042d38478ddd059ac67a0997a94d4fcf8689c73643e17673915c6b4688a6a7a3fec42849aa3fc9dc3166f8939704a6a1f212ab2eca42798986f064701116b6220c0e14d7d6c4fbc224fe643475ab75713ecaf1839f9a982fcaac8cf5fb605495afd3776a7a35fefb80070f6619033cce2bfb29df27827d01c46f81f67d82928b4b4e3e278f8688d1372efca4f45c61c3e4991805e0123f29214547928fe29f197efca74b41ef2e41d0d6a6f13e62ec4f1ec2fab7adde719dea20db3b9efc2cd23f7da95f91e6e9091ef73c0dd2a7f8013c46965749018f8b5113618ae47a22b74521832ee0c7455c7fbd08499b1088ea8972e1f12e6ddfd13bcf1fd07a3af85db97f5efc9a0b8f63a5024ca3dca28be340f6ac5c6ecb980fa4c6637dd315798ce9ffac5d1b8b783e63305aea4f995606fd01f4c72ad05a0c4602a058201c129892f255bdbf72b8d621610b407fce8f4526c871f2cf2e168c43e700ea467aebb04a7535ae157c1346c8ba0a47b6ea939ed7ff6ccee1cffe21067b752a58a278ca466c99f9c58ffc855b87d5e215adb2f76a917037f416c4b3181e9670a30a73c1e89b686da96a412cac2140d25ed172dd3b9ef1d65ce030536e65a611b8b90837c7c6107df2281eaee2689ee8c106071541918dceec4421859a468c30b24d587c16ffe13deccdece62752f563d8b05279babdf11b64d4a2a306b1e7c47e2a3e09fe34b2229a7e4aaabd465e29a7c71834a7c24a3ae9459d4b83793c040a0ea9e495b343d3bc1efb605541e130e6c092ec487a70d8d04039d958584d8651a5695e494d8b3a119e4dd314f28e4ecc1796ab749d935d03d38dd418358d185a74c3265ca7ff74da571339595553372cf91427f613a6a2cca78413dbee4c58ed1fac4e3a7d1e6ca9e9291ec04795679394c443f88042f7a518e1a64b2c8210fe3e5232a9e9f386bff28b6563848cd4f9fadb72cd646bc5a22454bba5fb4dbef1cd677cf2ae5267a84d006593286e52f71224cc97dd820e1af7a0d902dc0238e569e2c8a1665a87fb390e74b0a53ec46fac98c71456917d3ab28f4e8e1ffb60dbfe54005f2cbd8166121937de5ac6493a59ed361fcfa847030e8e0d9ab57970573b279015f28383eb62ca935a10106d523040c95b3761e84bd6b3ad20408e7e27341b70abbd239f5c14d654c4b9d15ffb1a4eaf87059b2de09b33ab2f81d961218aa64372e87d3805051539ea1e9e3139bc7a154c551342d5275d1d1148a087ef387874a764f4ff0e5d13cc5870561a541ade174d8df8ccc9cd8f04623055066723c920c4289a2d13a637a4abf9cc37d3660ccee4b7fda3f30dd9a321bb0999fdfdf658f7f38f7718b994b0f023009f6e2448f727b05641b4c501e2fd4e2d0b5dafa6244cc775ed255b1c480f56d300f9e45b6b1f027c8b03c4cfb480c5b2b235148c4669c7ef0a446ca0c0f584c51a438dc09e8e7382c2505523835a004e90f65d24ed14d8aba5e74e5b31fac097d30a36cc557426314794001dde7d8ddeeec151c856ac353b6d785aefb213a8797cdd50a2cd2fa78b7436a2a11d45033710f56a29df7ce71bdff96cbeaba6f0ab6a674d13601f75312f8e192349639955fc1aa8ddc0f0adfa011e6cd351bace2c337ab9f4ffdecb4876b09aa3c75f2e31889e35c9c3da0bbc278771be608c3f2ba2cac84a33caa4c3157cd2343348d9dbef6afe6e34f0c7d289d19a5cd043dc67e9fd639140a98578b641a774e8bcfe585efa58b7ecf89c7020c285ca663890291c59818a2b291d45cd43e1dd7caae51b90f498fef6a4d1937d1c8ccf051aab9395a8fa0676e77c5e29efb7b60d769903ed45ed64f5580ae0856f0be45bb0fda4c7f33f03a9ecee1307704fdd3b2775f2ef2b727831e3e08ee369eb9529aa6ee5728cdd62e9c30197a4cf6eab70a2a1cde8808285db8eae478cd61ae06de5dc80f7bdb56ac2b435a3293aec2b72580ba181483c0b98acd20f5f6fc45b776ae1529e0695b9442db60dd2ee6fe0cef66980b3e53b700227348248b611f8e585ca2535d90a5c69bd36d069288c203add1268f844f157c6fff705f0e5fa6fd52ed743d9db72be795d208013372660844ef23c22ae3b387005768b6099a9f7cb01c00599d01b69b96c96fafa0ce183fd301f88b0376142791033013641d4b4c25eac5a1d501bd843a1e8c43990249c050d9b269b2d4e66c8cf4120a5b0e43f55170d7b05b671d7c980c988ce2675cb050ba0deab514e152cd5e829af5c5e9b2332aee8b06e82c494077d9d5df0398321a74075dc8eaeb6b6fa8ea388d3ba99738dc1f8fee884c91e2ece7ba3e2cbb98806c601368e71a9903806be5420183f3c5d4e08cd82120131629d1484ee2d930265e7b740773772d7e4a19cd435f94827754d3e121cad545f93e386083e84b0f3711d79051f0f2890a65dbc9d1dcc6492019817b7a5999b2857b7fc1f87c05dab0fb6eecd79479339a94c5a7e0d938d5a8fa66f4a7cd047cdb3b26445f1cead9efb2cd7d15fcf93fd2b921469e18558d0c15b3f678e828e2ee1444141a5406b7af1de0b1423e39fa3a5f8ec5805db7fd3d37b74894c2bd4f31ca5de68e4fe079ae5d0b4082de1dc17b20c6b878ea39609faf0836e17a40ad2c65e084f1f92b6e24ff4cc14f0ecc52c92346471387142d830b5a46685730d3fc27e5fc060056ae44c7481c71b4ca4af600701e609ee4bb4176719ab4490910407a5060c02e792f6f6c8e061efcb194d39a2803c6595b066b70b86b8aec21c26a2750f7920687a904e713de67a1d5d822bf7affe00dc30c09d09069245e813b9519ce51af3825a8edfd1f236e866b0f81f45d4e6f493ff6fa503fd43cb45b7e3097f8d7b66929df66a00586ca4828c22d55389d8f4939f058353de4413097defbf058a80225a3979e18295fcb51dbe5d38defe202a8bfba9e253f010c7eae6fc2f0a94b771c5aed091e2e3f59eefe12bf81030790bc30ebecd9732d9eebf2bb398de91dcec1540902110196b750e65715417f044ebd9127999b44411e051d5bbb629ecd10c614b66648816348ec304f94f46820a132f38cdcb7dc9e99719090bb3cf3126eaec9cfd7c0fb4fc944e21fcd00c21d2c14b6d728d095c986fc3219c7ba70f379161a3bd131916f40a2daeb9e42f4636eb40315b9628e226617f57a148280716ac717e01931db0935b2f03e9bc47f354eeba07d4c287838a2783598f9f5664b900ae3788c56383d37f65d131bc6af92afc171d91ce4ff053c4c277d90ec57ffd444df5b6888450dfaa7df3a8491d95de426f8cfa9dc758220aeabcf3a2c944214e71516ed16810b444d25605383008bc200dc19860aefad61a0fea63a1225425286c0bde556da16811f6d01d9b11ca19755440eaff5e831a81ce1843d992818da148585e1fa6615d556913b4413b1b56b1ef873ee74feae8a197271beeb37d76e77526a668300468d460202936a73c577a6c84156ab20dd8b671b3cb530939e368d6a937e0692b320dfa9514735c95beec7951ad9ce02cb58877d8dc8dfa0c6f7a9854907fca8d40484a00dc5500c1222d204e098f7e90daa6c955ce1ea39862fab4f148c3cffe9d6f2639180791eb160fd76d7094e2047de780fffd07b5bc19e6105997be5dcbec3d0b03f1b49ec3ff684d1e1ad6bee467cf28e3799b174b72a970581c20637d8ac9cd7ffe684d852de625497fb3a1281cc8b8cc59e18806cbc25cc76decb3fa27f46d5bfd3bef956eb3fcf33ac76fdb8ac35848561c5e5cb7af2c74108b81ccd23c9108502a3c4b08c24e2abc32e5608229110d531866779c592332e6bf8390e14b4ff314a2da20e4f1950b138b16d20d715c655800aab863cca80f8314a301d6df7a2fd400afcc5feb06c468ffcaa552f1d44f0f68fecb00d2cbd6c35d6c5a6969d535dd852a6afccd80d629d4cdfee210c8ecd0bc0d1592f342226493da0c2ed92ecdadb6e675162abe87e1c8f10fc28d63320709cb3982c27e1f35e04eca5bad57a5939b78dabb5083d8e46c113ac6a4f998f7336c9d867413b9bb4ad60ae075e25220e1960a394fcd1d58952f72e11a4cbfbd6479627e1c5248ac019a4e3cfdb41dddf450ff48df0b12a90b2b18a83ab9055d51df698083675b60adc75c1b359fccced38a900247d1ecc28e185183e376f792f50296dcecc154b5826d37a4b84784396348e0141166bc42fa794944c725482c46acd30215b9f923a0d6f7daf45d4948eead03a8b9c0762bad277574e641329658cbb359e1a5955bfbfa0494f1608520c0ad7480b103f851b394c709293c63d98e201639361b969a1c35f9f7d188e51a6caa5a768909749b1771b58b7738a6050e07a3045a11f1937229e421affc98ce90795e50241f0df1df86e7b7bb14d79661ed589d2d3156cafb2779f31a34c2f956f44e7871102bcf333c2401d32d96c59544108c5543bc92360548a5afca51f90f8dd23a2a6802e73f8580dbe5fb370b7accb8b0599060955eadaa20b14bec00374ab5656bea580d52ff7d5ace21de76e13c96b1f2f1dfb0c239a442654bc10b13bac9538d4856549c432600a2aa3104e0ef3263a9ca4d59d4d8ecc9a6ca5c2008a131451287dbc5fc20ae7306709f13b3a267527c81e80764daef476b212d22128f33fd98593a10f550afaa9a601e2c0f8eaf419fd62c2751da582cada0f55c8e1e55101ac2dfe1c69f07d077db85f20fc8489d379db843d936b242d83372d8554733c88ea4d9bfee559bf7e8e9e8705bd5f49bc1635732d59bf1f6652e3a88e6ba20a87e75f09de968e9eeddf0f46959093e73e0be3f2ca590c3045ad23adbcf47b486925136a6e936a74aabde40d6e3f54531d9327b5707dc412e38d9910da1a6e5dd0e4db23397235851158ffde104031f0808130f8230a3d92eee9373d05080ae964a9d6fb65f829e1d4cbb1183df25eace3d0bb04a007873958b7bdc2afee9394945a1cd481c2d0e5a5903f3b446544e678c8d7ef3c429a8d6e2ded9ae7a0638666a3a2a5b9f8312ebc77cf220bcd84f0a8c61a61e51c7c799610fc3869959db58a353adb61318115491be78ac331d6111477b868db482ed665b24e85bd4aa7acafef847939528930479af24d1845dd27250e09044a8839102bd9669767f4768d2b394116034e039b91d9cb8d7481ef9ed7590b302a30a2be9b05831fecd9c3ee06842d55f6e15ade772962ac6fcfa3aa8200b03daa21c283c0f89cd00b8d52468c9eaf458165acbfa3f6bfc2067b78862aeae2af1f74d5eb961bc807afbb5d63927ce34e268f144670bdbb654c8dc3590deb7f7575da7618e837aca37c85b8dc023b53a54044ef07b4aa4244b095548740c7faaa7e80d6c597ac149cfbf7070282200b5b114477e113df3cd1f4b1599fd680deaf6ec366384be4a89d77274aaee1a41fcf4c06cdb784774bac3c6172aa0bbab3c3cd084f9ceb99603ee64fd8df8cafe5e8c1b0c7d93db5d1e2f7e253a8a2372657c3d15f6a82996ce5d3ea1f2a18ed5e2abcec04734fae9a9fd621554e0e91259fc76d6f6f84463c7b046222595a8a98ece45b2ee290432c0e8020f5430e94558f9d096a85abfc21c3a76b6fc95c52921002f339e1d267b04fae819c9126163bdb7965927de3a2ecf9a69bc282d59c57fca24dcd72023240b51f76ba06b512b269d3ad995362119fb428d337a5b146e63780c5760bd138835301b12aa9a3290ea4e68c142430b1c37d6dd383dd118c96a17898b03933dcd93075b2bf46fd35d1c2f50721be84f350cf365d138517676645e5dee0d4737bd8bbe9443f225aae74b9ec9c4fef0ff5a95ee88d4741667a358e4fb10a20d5baf1b0e637e41382a3fb812995f953598d86af476c2f14a736188390b542c2956ff4d79040b9ae3fc8c9b3f42e12d2194355ef97068f2e7bac555d4f8fa8bc3af2cbf90d4fb21c0f1bc467c493f1287b5612483c010a251faf13b7b80a17f8d21db279a4b113dad20a631bcf95d5527c05313312314e602a6d05c8ec91126aa818dc86e5af6a3f3425ac3bcef2e37f4adcf07ed8310ff80050a38e2a8797d33fbe41af2665c17da85a7952c0bf397c1b85242f404af070c1e94436ccfdb72701176027630213a097a8c5460e50ca61aad6a6e079f6f1988eded45a3e502e1a56f03453edef5500f4268f3924302633b39a8776229ca06b8bb23669450b73038411cb68755e7a18da07cc06b0110cb6cf710fe12a50f804d7a36ca77209b5624f56d9241303b64d2749639f327622eaa1f8583fdac0567471257b1832860a0787af9311dd92802ba8853f2a7bb7d6c71030854f6a39c0c384ef99382828738a6abdf4ec5908c3786321d5b305849c686ec28281fbcd6ac8506435f0c1502bce8e22e360993cf75e54e65a85391d89dd2716ce0a3bdaeac00f75358f005bac27b62eeb31b44533c8bf8f90199c6606edb52d5d688d9b88c3bf613385f1d3092e60c308b074ee953dc07c8cd97d94807230583356fd03e39c2e5f5442c05a0fadcf274762d49059e39786361971a36e561df57d51000be08476e1761428d72969d8bd80d858fe7d48ce52b7f8314cf814c270f5c727f299b3c4bfd962e5f403df7edb3da7b99118a38648ffb0619c4934358187d838572425eed5104612caf8ef3e818ec2066f844b028e3dc17f968fc7f225d77d1ff119c2b7f8b922dafa888a948973c7561063c91806ea977357fdcd7654622aeaf10840c21a134211116595de232a451d238a6dbc33a4fd711abf22328c30e93c62cded4da22db5a280dc8977a5fdb5aabef546616afbb3be20a6bf2858a94c22746d670cdd2437f27dd28d1accc4006878afaedbe4f9b372cd89625831a72eca441055dfb570a64fe7670835f82aec7a22747d87ba1befbefc3e59b1b078b2a1ee42bb2f8b321795f65abfe7bee4e5117b92a915a59e560f5fae629b36fd7eac76290747ac7188aa00d00c741acd1f807190208e086aabf53fd44203ad414ccfc0156d35f92a59e06ba2ae75c66a4f12869bb77d017d8ff349ceed5d1baa30ef2806057be7551369c02c8ac01bf9767447c6ce58ce22b48f389ff7763906fdbdcc8314f27bb276881d77b6526fa0c3ae974c97a9eeebf8f37279f39e352337ce94e0d42a871b3e2c85dea937815aa4b43df258fbc9689b1124a6a2221e0ea7180ab7077692cdaf53d4fd3f9ea410ea781945323ad276e9e78b836ba9f4b4ca3bb0d57daf584bb3fd0b473b47b62fd880c03770668e2be310f9da5c3de193a9a5ec67996ff0c3b0d928d970ec9ad44aaa6f4f6513f8c92e5c38fa413a33fcf3408f6da66029068740fb19f814456389126381f5ff01cb26b403d790a70e86a63c262027f00c958173602d21d96fc31360f1c387c6f8dc880d55dfdc25b5ee7f2b4065e3b5804e660fb810c7e4802739f5e77545e799b97f4f32f5f247a2097b94aa8d95c4409e2fc10a7ae0125a0141403ce043523f15c4e7f2ff403973840318cb518dc084f4135de7d19bcf7938ca7a06a86a758158782a3d34f5064e63a97ffadcf138ad54397244268cefd080d4e3661435430e3d10e84c03fe6d3d3357be9cec6ca063d0c7a0ef530eb3d7a7aa193e889976dccc4f02413ffa2238c6c8a7d1d3b2ac7125c0b7f5d94b869fb9d13c618a2c06458554b3e4cc9f104b8595eeee79a1e7fa1b7263deb4b0bc1d8e351e04d912146c6de4848b20690e720ab6a80ccc101a66c2047d04d32c41d4f3c376101728938ea8f7ce1ae0b37d314801872474650ccfce3b98fadd2a8cffe6086c0533097239bed9921cab26131b05bf985de7a1f6459ac0f3b58e7c5e17db2a4a8d9bc1e22012bf638b171bee77009131f84df8ff3697ae0508ae72724ae81a2541917eb5c14bfb4bb82533e5d1a8a1d2049132c79d5961d58fa7b034a2b3ae61c04e8554f2c8baebd4c7bb5177f2a5de6d68e1ca19233e1bdc775b74ce240fb4255cad2945d476a14f9a304613d1d4ffdbcf7ee2f3d208c3dfc390f7d001238994488083575cbf20ef0762dc39ffae91261c1237823e70d6103bb5a2421364919d897de2daf54215471642d97f8083b152436fb8e1fe4c79dad9b8b2bb4ff8fb2ea2ce98875c4b05792d84ec4e1488d740c3492751dd4a67db620187054391e41307e89d63ab461dca7f04f68dfe1fc4344245e849fcdd74c5676be2f99cf1686afa52483045bd6f0faedceaec6ec89b0d7845993c069ffa7d8632f90f42852a68418ffa95ce3d66146126ab6dc807d77debd15122cb959a76c90e827d6048f541244b79bb8afbc0e2d4eb5cc0d020e680a504d785b979191dc85935ecc76af1db5a4b132bb89d7239ad0d3cb92a2017c5308619f5eec877e7a592e4fb4f5591d8289a02881dc5efb79162c92604c6890161f094c088aa440027b1a23701e2158cdf34edee4bf8dd7b34910d505e4c5d680d3a63431fac34151619a141dbba5a617d9119f7c13b866938c8c02a0e09003afa6606d2ed7983ea27971585e7c440a87cebc58eb64d3be723dc318c28b7368be3014c053bd92ac27c2f322e9d6004a1ca00295634be5d9b41602da020f9818176c967b6e0b0590a5f8522e9e8378dc7fc53b618ed2540fc9f6817101451979faa0c0b4212ac119c198765e741857a9706576cdf7798501a523387be25c195fc070ee9047cacf4234b833cdb77f3b556c568fb00a19d5f289d7d560a7fc2523556fdfbe28cadd59df1d6572bece8752c9a2ebc0fc3335d7e05765b9e525db610f2e683ebb234b753d4dd89b4aa54f6ba665e04c26fa24f14e9a02be270540c085fc17a56aed5d98c34119a73340a818433bcedc8ea9b3773e1b4198e020373f9561463188d987063aa0814f61fd48f72dbc3e5a398556ecaea40b3d2b99289fe59513c46c057adbadc73e507a59bb54c7a7e68ba80647f57a92a2dcac0fe6e5e511786d4e1a3b06f332ac1c03012e1187e2c71eaf23d9cf8a5bb346b673bc40d607f471a17052b7a8b5798d7ac8224ae03fa006750ece829c2e84b5134a699995c041c04ce4b6269c0add205b36a05a949434de002f1422bcd372901d6b04c4cd9140814d9994975bebf1c7ad124cb1fce29925172279ed3369c95a9b0780137c6f9c8281dc510027a9b8f3cd38cc0dd721b9f4f9a398b8749150bc58a70f0adc27988909b2d512645e34a482b9179df97a920d065e6febd25e84d70abc3627765ea6a56d7a090d63ee9ea636d2383a19758367adfea8a2acd62cdba103c7add802802e952c5bd16e937b22045c8748325cc3d0850c488c267a60da1c7151d2d0293a8b4d319f3db02b8d5363d30a636c56bbb3e61aec0e25027dbabd59c2d3f2be42e621588303e766f298a29f27df98a9f27a6a3a624a6369c225ca4049584f025cc99f6f3334cbda9373c7c3964f785a9e58ec2538189c373c2dd123cf86a4df66312d6663edce96108b25c638a4d743c0f2e8c9934277d4e7a304d052467dc47316f14f4dacce72e2849e3bc46ec58b9448614c191b421ffdd43670bff983bf78d6713be30bfa39ef55b11984505050771b7e004f273e29ad70098e19a4905870c8a51fdfda214b40328c4bc1c1156195e098c435a504aac376a3044f23302a9f1ae04e08eada26bc0acf0f5057fe5284facd2e2408bb04f4a115035695d04770377d4de3bf546edf900811be10e630dc9c6e1cc1a7e236ea2a8c7e1521e7a73d310fb04953e871ed006dc8cdc1f242099914e3347550d4a379f8b30e2b007e0795964152376a0b3bb160011e1ca1c5ad8386053f81e10ad35068d453c0b415719704856c49cbe7a382b3535aaab206bfd3dc40ae29cf08d53401bd95b2a720d957d8a0718c3fcbc2808920453029e81db465fba14549e12bbb1b2a05d6ec9def9f36d9ea68942c8e0bad0d2ceece7a2e6ba6441274dce4daf3beda3e1df51394687bcaa88ded1317a5059da98881df561db2643fdaf14569b10d6b4ace3ea5da3c14a565e09c16de6025874045042dd02ca25685dd342ec446a098c48602e63f7771b409f22eee2d3e01ac3c34a92c45655604f8e096ed6c568e1f2b3977e3e9b22ff1c532ec2a106638dd5f21df907d26327c1381d1b0fd2617ac64f29ad7ef846176b5f8f1102c965f71524e26a344eba7e184011480b91c1ae31aa173cc3ac08c880fed64d94a3863c707e23f1efb4b21517e91d6949528352e8c303f6671dfe7fda544509a3a36ed6be0b2043575c2e7cf21d198003d7085cf91a1e0b9fb24764de0d246d6b50f6ed08b85c3f4e7b222e70ee20fd00a047697e0e26196b1435a0c0af82697090810a63db6a60e6481964d96fabc0408081655170a156160cb860ffd591179fe877dc974236c26449371abb6660aaddab0f805e40311ab764c865f83abda8b982aeed0d0bde3fbc9a6175a404d8a6b3bead71df7b258aa4114bd27b5b11b9aff0ce0a271581a39ee80e2b7e949716e55453f1e4c22fca0976eb7dd078bf954a57a43ccdc7cdc41e9f0a228073c2f81a18e61c5e0519c40c6034419000cdeac98acc506ed5df9bfbf933e85ab01ed89a6f88f17af74724c60abc37cba6d4602c2f846379ee4fc2ed0adc0f8aec53c21ac3339dd6e89d32d7a52e0c56f48a4fee08d74b03a222966f7883ed305be0ca40e5781aa15b1b55b092d330cb8e1136fe4ccd002c5a76900a6db02f2906a3844a513de892ef777b459cd4ab2bf743ea49276a7231a4dae9d8c1c8fbc680d54e4a57f0f3f44c7c075b47d2871b1ecc3a213022b7279625ce2ef2f8be51906e1108e3829211165941c05d8a963a888585a3814d6206c4640cf026feb1f454f77050b8da09f018ffe0e02c26f3b1f22e2b296eeb2e8bce0ee78790cfd7a5bd9aac4b37fc9a8ceb4d489c8fa6a8c4ca2d486a20be5d0ef449f3a77dfbf9110f0075783cec1df50856662d7ed2fbabdb94900cc1dced0c25d6c66824231fe41add6c7275c64a627319b874eac7247bb3c4c3f56f5c653eeaa5807ee72f75c400ba044136576a6e9c8ea87492d51312b0589f1e51520bbb8150901a0a0c0963d5ad5e3b35d33f106f9126f971f853d6fda2bc246a5fc6036ff600a2fdbcb9db7a8393e5157197aecb158af8064656969787ad7c0fd01f1fdee7275ff43bc6acb2adb0fe5a68394a5dd9eabf01a6d55f83fea7e8d7295a00fd663bbed89e3cda2fb5d95fb3761bb35a6cc10152c0d4730627869af346fa0606bbf3b106407d76e8705c7255c26221e748278d6da2ddf4e5b4bed05cd36c20a87af87eede237ce565576fc5639863c86c2722355320af3e65f04dc657a89b238e4c1ad7c674b7aa07517a01736799e9e27f690a72ef5e67b8ac364a8191c6471b135e5410663c7e9878c547d2e9af4dafc200dc45d996e2303a2fdce53892cfaafd4ea6f09375322b797b09d2e640ef6fa71e1e208fb605dc5643b2fdddb04f9848e08eb656ca586fe2ae0227b98eb605422ba536596e8b3311738cb630f262933f6e0787aba69221a0ae916d835ef95851b6c702073c163399b432c9a25735619f646233f554b4d6f9959123a77ca9cde399a55f021e56b75e3bc776ab791f9bd4ada8bc666ca7d2c8833d9a0475e0176e83a77d32f61ca8e22c5508d2cc3a479b2d1f0978ed89643f6a6d659df5a2e40ef5b818869e2ce3673cfce901b9a02b5987bc3b68b14651da4763fabd379e3648f4590a11f4e96c1597fa676f0f5a61d86d5fd92eb9f2d463fc8125412387a4f96173a0371b5148b7b9867ca1e127a91b5e2c803868b365d8ff0c178903f9c1841b707006433e3473a1b69a1fee3b43031e8ed76a0b335146bc3f1782c6a072e424c90ea38c86052781d101a8b9d32ded239da5aa0b95420f97623effc0844dba239b18587860da703e61bda493f381451811aa927ba24ef533288de9823a587007ad7758e7c502e74e21230c5775c0e315aefb9d78546fef7910b9bd58ccbf59f6bc112544ad78217f402ba7e08ef3fe3a06c443b74fd3aeeeec4f7f930112100becd9c89b9a5af43d6f0107bdb122fd6b1d00be040d3cb664eb24e0a95cc0ad2d3bf5fdf2975b521eefd790b718cbd34b26e0c2b3bbbb4db6268fc184a73825b6b5265e6480d050ad9f3f25bf6de1f2332125e01ddc8aba944bc03faace27663a8344644456e070664525ab94549ef381c04aa4d5396e1dc779ff944b1c10e5b238aa88274c6f70d676fdc2400404e61073ad4959a78eb8170f987426ad52ec4a8752e2646bb706cab4720a41ca256bd7cf58e5031929971cee1c99d346cd29280f71aa839cf04b845b93197a74f93ffc4d9a2eb727f73c70f754f979ceef0f70f7fd4d2597ac710265587ef46a6f4c786bf2a8c5c3b2231a70012c1e390bff09e6516ffbd41f3299881200f4607c6856f12ed4f7df37100e0a01641279dde265234a908971592ec2d0565c4ed6b7cc44c1a46b5f110de0041434dcc1b1372e956492e3e5bd56d33669f83e2ac91d471cc8e4819278f1b411202f86926aa4cf8c5c4f88daf00c71b83f441063e50d89e85634174f51cd54a89a26e64cc31c631eaa26be5c55707fdd4d137b07a5ac4672dc47021c3ab06a091d357fc278d2fd32a478811d8f5567b75e0a3445c889e8e87ae1226859f7c22b903b04306d110e814965bdc21ee0da6b790ccf072c0996a70cfcb2107394848aed5f86ee2276d0d1acb76209fb156bbb53f6ff08926486391d39a0a3244a85782f7cecd8f65098dc843dab67734cbc1e275095ee5242c61319eb984f359022ee3dcbe162368e9258584b0284a296bb1c25b18331a090924c2a12e545d88692f728d76a9a197d1854d8ded72d5212516458fcfc143258ce20bbb66e801eb0d7ddf5c2c77c44d9f40d1bff9b58468443c80d8662d86b6a0c08efd765e7486e83b2690f590cf1ee2a01229e52ad5c00ecafa18fbc5e101f4e499cf59484de8a9077d952a8306e0e267216bfec38aa7de002a25c328cdcd82cb01399d44b3e5712f0095e6635668ee03bcd4a5049f057571b919e488b71d297c1bc173737956468f2344573d8bc707afea06cf0ee6bec2a6046f1afcc79008a716f6e4a92278dff897f115a3d4f9b16ff9c44491a504331dc5a3a1c4b1f4f5649de9fd267105749324491b8c4c11705201e14ac248b82f89b5dd04ec838ae56aa0e1b630865590b2455be78cc9de27e5a211cb009f8487d58809479da02f2cf846ac051d3d5001fc92db6081293c50dd311715e96ba0bcc05f6dac135e0234b14ed2e283e7852a4bbaadd065004f808e78fae04a2539dda0161e475182e464e7f09366fddaacbc5f33042c2f82b31d39ec6eb0afc2f2846066edf9db3410c8b6c20fc7ad9f9cfcc812d0a4de79746cf621767fa8dea27e1b091171b850fddeb31388090fe44ce9fe6bae4e801a0d89f009fa86f0f931b291215a4e92056ca483073218d93d48a81d8e09cf1054a88a19440bb9f0e3aa991f8e5fa0bc5d1eabc0a4ded30e7d0b18475a56029061c0cda421160b01375c2d8c5d00f7dd90377adc9efdd3de43b3b92aa578f7ad8813d1860569f3ba2b07c01bfdd91872058e9f3dbb2b69f5a3bfd93d342481f4578a46925de7544d72fc7376f1ac4358b521c5a5c39f331b2d321d21021ed2ed748a6d31f389a10fe0f572bb56b71bedf7d895046c14feaf11ba2b2e45c4b7969b903211e29949b090db7e81fad30f05c5eb21a97f65d5e7e77e31bc172cde16fcc852f4830a079746884727ccae5659e177ddd2d5cec48407d0d16e9e4915117cd3eb121af9c95be6c7c3862a7a6bbfa7d790fa49594403701c4425688ea505974af1ac148f510a122784d651374881847c05d8d7f14d8612657ec7c104bfcccd36dfc8e2f69c9c03c9ea68a1c71cc991b31fae4d12de07bee27799de2fe240864bfa256a1b780a369e252625b254f2c1223f1fb17b012f9ad3ae400467b32b10d5b8d0d498e637ebae0f1b4438528f260a9f1dde1c4392435beca23af402f3fb2b6116e5e2bb61d3c958027114a62537f3a9e7ed9fd9ddebc4cc0005baea42c3106300db9400ec8336eb4c60bdf91d574096664e1271f7247f1eff0de93c21021395b75a9afde72b301fcbcc1ee662bce54008312c08ac32bbee13c423cb9b106cda78d53132ee1d638197f2208ddf3ef2b2ce4831f797f8c4f171bb1c77fce8bb79f17b1b9ffb96ce3492e637da0b410c42cbd14c81b3ed4a2a28a4de5e881cc3d8e952af8fda8c7971906d6bfd89080f679fbb98db4cf978340b2666da9c88c3d8b7095d0addb7b5cf42eb34043af8f87e401640887f1273cc3746ea9242dc18b6de591c3f846b0f3cc039d9856f078e393da22338b464cdb56da53e10fb2b2fec2856e78f1a3b32655d903fb43b7a0b031fe84788bde59708eaa1b7b3f0e92f95d2af20fe3e36d04ea1e0ae43053fe022cf412efc1f860d07a6f4f7c826001f397dd8b56dbca1520227f687ef1edf7a5abd8e05aaba5892810fd98bd172c56e71b05389d8cd03a3a0070a552173f4be0ab0c4b73cbabde22e53cc2d4ae4d9afb41308a96ee56236b031b25445ef48fd514a5027f2b1b8a3b87a92b0b23e2df5a4b2b0ef1af040da520352004e7f32c40a5a3955448dea1b5f2e56fbe69f8ad92a47748af76cc4c9fd36fcd5b1101ff41b34e87ecb90be5159067b4cd0b9d3b53e33a63ce0d9b6e4bf1674f5f8781b329fe00c90a50b7d77b0d8cff45898b26f9d1627c93353b86cc28a9c59e2157c4b9d2690cf1c9f2c547e0c2f8986d0dcbd8078da3aec458a57be83dfdde953ee47992a23c19152c9994bdcb75868efca6f4c0efcdb098b9e26a3056a75114fc80ac235f77f0a91191587f5dc62e7660dec0ff47b70fba3fad4b6a693658af26014df5c2613d1e84ae9734d95db5e17e13d5ab4b7cb8e27dbecbcd2333a8ec963026a67ad2e211ba5c987bbf598c2262a8fe3bf2af32fddf93de86b5bdcfd7fce2c07fe60d70052e5ff5734404f0025ca9cf827645b85b5a344fb238eff31836b8ddde5f206b9a4dd703479cf7b63e92044ceb30371ea5e200c1d9be48340da1e0e135278d8f70a33a664698dd58e7803851ef5d92fd3fb0527a501cdea14eef9d658f041db91c34ade457d5f7bd4d0827a0dcbb15bf41fd041596507eb6d995c72f6e281679cc49c300965214aefe0a2e2dea49e662b0205827b19392b09903b6ba57c887a89172ef2083cabdbf25a5c8c44eb4b7dd07bb8b9aa8d88f7c60bc7b8b02c990a967a04978a389e6d174cd20cd62c6ce555f3e40ef7491b57d16bb9e2a685969c49dbce847956f8a2c4ef38724e7ba8ad42009bc33530b64c40dd751b4f742062b882ec4122d03630c8442ca1686e2cbd073e8cb7cedfacdc8e80814033b28dd28cf92aa997cf5ab0284637ba7c4e62b6cf7003f2cfbc28ddb572e2b22f8217b1cc2d1766b43c5b5dac08e0f4e9526dadc5e9374943d646d52b464d4ad869268cc81f53502a2056ba93875432392cd823617da0b21337e2fb999cea0551606018715c094336ec3498dff32647ed876bec5f4f8233d581b5e0be302a3fcdf5d795c0512e791591ee93a8b041ffc9c16ce23c3ee249ce63f8f10aaf69e499139355765029c0c4ec7885268f516946831ee43d78da6d24f76293026ba2bbe9274a5b0f965514c9a0363438d1988b0a6107404ec78b6a579130c7be8c8fb6ba18600baf7848cc9a34456219ddb737b5ec0fb20fce3601e09962eb6c022a06185e3e748dbe3ed145f5eafc0125823f2260e6bb9773d23dd465bbc78fc40c5c917979a7c01d3e567dd7a8d805981be1a876d6fcb24480a5008b2162814a717cb6cdc8a41dca3fad4087c183d77ee9dc554247b932100a28b35e2a837f5b361d0c7d34c7ce20fd13f065aa42a5a9b7c25a6db58efbd3aaa7f4fa10d91a777aebc834321032391e2d029f4fad0de9297021f376ec98449b2737dbdb2f668229bd9bf29ef881d5c2f7803de7b25036338789b70fbda6030c9446d8840de4312130990fb1501729066038fbc001306968d937088424a4bc812db9128de0d63dabd5f3ce063047d2fd77dec016bef835c013b3ea809ae6601932943c562ae49943241489c43cbc78982890c4dbdc91a920791c8bd214da63107a4839702c1b6ae8d3526758fda5381212cccf1d3cb8e4ee6c5a4c3659e02133825efdb67f31cc800a9cbd11647c1f59aaf7a74400bad708b935600527529a6de832009cbc46fc0e5d7c6fb5ede17526ab56964ff6633638650c63a2ae025af37f65ae9a8ab83ef167891ff105079abf17d717d847a610a0e24cbaf3de17bb4c1cd04fd7ee47b348f217e13719a9f7ee80fc0ea3844c901fb9a8c88a49aba31a9cc36a53d47f7e4c269ecf26c445cb565c73e97a2259f2042322c605f826fe5c6d2d79760e4bdc74c92c7d425b413ac18880044a3a90c66bd54f213fb571324eca2b353bef7a70842c95b11e7c6de47ced7c5f916326e5ca60584ca768077be4b601934200f3ebdefbdfaf32107129d549e8d909a38da62cd3515ca74811371be61ac892e817e87ae94e79239c21a195c57329ce56f571d6f423e76612772a2370a0fb13d9d6559432fc7f413268567d67b5e8f514f233d264f2f3a3e88b5439be4e9e18ee962da3e5d5f33e39a8d4d0cc5289e74264b421164b0dbc647fa45813381ec1ded3973aafd143694e49f28d0a104e462b4691afdc16037ace1f991524d72246c9fd9dee2a2536f64c87621ec7fd3be102f8348918d23022a51498b46b994a5b8bf800e94ca3883ca594f89418efab5eb9f5af3e10a862f89f8126022ec3ecc2f3dbc36812dcfcef68adc3ef018162cd32d19dd247d221e9e835e463dcff598f536d16bf2f4a0e383b09825e9996928d6def28d02f3a992059621c1a626a01ecedc083e9788e77487ac8fb0da691c80d711a0584f7c2ef600a27a9292510e67ac9111dbcd5ba2fdd87f8972c53e06d21ff734bc89be1b5a8dfb18de9c7d29acc27dbcbf37082cd0dba26fdacbe086f4676825ecdfe036f063b07aec69e016f86db012ecd1e036f8507eaf9b21b6156d6103784b06d900690f8a88f7167cd2e3a6a7e28f733d9908dc64e9cd8cc24d70a962184ca3b2fb53cf2086c34f1d032b4927a99220039817c6d8702e950740bf911b8ae8a2298245ac488a3a2026583b921fc115639f5efefd5a04dd54a5ce13df44e091b0da392cf975e4050fb1c175cb679d8ba573ce8524190705928c830299dca10859e6a08024e3502021732a22c939299064381448640e0ac872074564198702129943010996b9634621f0ed1f90c91a68980e12d9630169e651912ce3a4482273504092392820c93915c8f8dc3b0202e16f3e2b995961c1ea90c8381549e49c0a4858c61d3102816fdf5e4c56a0c07491654f8b48b3870a2419870212b9530199dc418144c6a140e233de11503e7f97f532c2d28475093c20057fd9f8b4085c3e4d09568be283f462719cc02fc65ef669e98ca882e986de55f3f636b695b1fe80bd14065a4d4a8694d520867c5fe4ae5308e6a141dbb99dfccdbf02935397d6df9efe1036d59709db996cad6359abfcf881dcc73c11e0ad45381604d8902e221ec2e17806be109db835299b60efd4670bf785203705079776c51a390447e220c531215d441ca844e2667588274c6318c57cce9b2a40d94a04e22002576448a9f3584771c6cae22f4943a1965a00b187075e8dac5a23ccc69bf8e04e1045e276fbbf51746458193963a4c0bf9429798a1105e280bf237bc973abd57c7ce6465db82f2b515c2b89d6fb1e53c53c537b192598d5450fbe21d4e2b70f586db7386ad2bb0e083255c8050303215f93a496fc32499dd9bcb8a781086391decdc1a6ec8d1103d0f779812d10b50ec78721d05453e66e13b64c6c6503297cf79718354776555438aecb73386abcb32eb2b1582af39c1ddd45ccdff249d3a88f5c75e1217ce86d7a0e48310fc58b335d59ad5d071f34a29d6d3a427d3ddd435c49e55394da355c4df7102e8093b8e412b48b32cfd8f129319e64a3ec7ac67927f1ff44968fa764390d31752af7a6f10a8213f77b69696697f186c30f2e92a2a6688aa0882876583ccdef1afe04af827d7ff377eccb41c0176fcdda483225fe75e1f70fb165e3e74f3044c5e56888320c3a33b27edec62ed0b07eac59d023dfefeee8895b8063beae290314882ed8fd402440f2137a3d44976de92c8d541ca97f73d2edbae98d9d1f12c9d0aa75547dd911f4e6b83e56817d2bee2bfe636d5f52b40154bad43c6b67b1c5a2389726b3de8052ae0b3614a2bf06ae93fb55c2a492e4a9a764f66ebd2540c5232e83b9cfb020e5305b16fbf30dc3bfcd1cdef83e5d593e6db3994d69a2a9fe95bad8a9e0663f65b88bbb5d9e0c16cf000b74d8a0d06c05279eeeca4934bccf08221f652399f68e071997fdc318d0727a566e58cfa57c7916c09e42269ef2ba75e835329f8f810eb3bbc19426cf8f65b51514044632696d6d5342c4391f611dda8ff538ceb5be120905b52f2c873a891abce3af0c50830af7d17b8385b964b1c39a18b52882e355e3c44fb521814a44f2c567a61ffabfe61b79387d0aa29980dee6569a6ee5c83b0612efae4f968fddb692249926459997934398c0bf8319ef414e49773c16e860538ae82da22885189d3fd8a7ef469be1360975fd4b1940f6dc3cde60cc4ddc3fb95170121a55109cd4d43561c823c0f7f3165887a00f1df5afa636df6f4981f1146a665f3415c05a40d28c5c49ee26ff5621025a686f47053494e11800330849f10f673cbfd0db7f19aa1ec065f96e2f248948917de4d64712e7909adafaf652a527ef2550d60f2522ae66fc5edd5c4ff6d32a8f856e3f12240cdb189901549dbba812d875711e52e33f71d101757a81b5e3171aa4fe8dbf187598a50f3d9831dc168b56200671470094fed626644c6efcb8bc73a41bca2d70f628818e0d17041062d4a7941c31d65038709b6fb9f5a6cc683acd1a0f588e8742cabeeef8af30d1af3976427b30ecf207a2b6557dd8e0c90842d62846ce5517078aca79e0abc4821b082e011f10d261010c56029062446f015a813f7000bb571f233b400bbfbb120d2e47c6b948da45d9b3f346db159815815391d08738001004624f1588f425b88231629a3c49e636e931eb5c6c87417082abbc8ec91a87a3f4591fe04a2f2dbd51670b462a6e8ce935eacff3e88f52f1a7995085218185f91b82f162ce8a5f8fe11be3a33985a04d3bf85b92e5aedc0c8a43ca1d95e0b9789351d3d518ad1b810fd78bf86663e3314dbdca38ec041bd3ce1d0e1b6981d80f260356a4ac5ec7499a4640c413ada19ed7127224f85e85c2fb1a5be85e83b060c35ea1d512d117f2109c66e889cb127e3f9688e223530aad5a057b8652b289b9035da0f04679ae9392d39bbe457e92c271f19b65280f624b04cc89b7d1a0a7ea4f31f212cf40294f46be36199ccd838128d6003bba89f6969f660705f67003710b0828b06af2e72ca9e42528b09f314b408da693e12c13a78b53d54551c1629826e7628226cee247d8316b46fedf5b661ab832f2bc49328d54b73941c215e4dec44119bb673ef4b37fef32b0cdd59202ef17200ba6b6821db2a014cded45c8c31358a0872933b23c54052178efcf7b4e581aa9427831609ae19b2dc9424a00aab77c26891a9aaabd97671d2726813449bc3130bdc1d920f437a04c96b340e9086876788dbad770571a77218aa51b8d74a311747bcacc99d8762d81bfae6b0d777513fd501a75031a9e88d2ba6b03802455a17d14d5815be3de74a1f1aea1c96439ae36820c452bf0dc97099673e93d560809f7134a16a82cceb1c9f27a47ee2e421e9ec2029d6978623a94a52a09d23398fd0d747dcab24c02690d66cba18ffdac954559e3b6875fbcc69c7e8d43d46d7a828036944a4dd090f7b5fc71a1045df5267a34176807d0ae9460cfb5955424d35b4770c724f1dd8e95a5b59caeb9b49ab97f00fff08f06d868955c13a11be42ac4971aace0a3f7d132c43a792e32f89008ee6e19ea786035be4d172fbe397167e6542753eb1d5cc3ba7ef8a95814eb5a12dc805439b9841a3388fbaaa7740603c35e07288882030a44c1e4d6504e3ba42c9b7250bc7faf81b4a76314a063dea67890a4ef8dae81b0df0194a2061dc0d4b76dd65a9e4a6ad124e30980ef4d8becc7de53f742a5fab2cf15dd21b3fdeab60c35d0a3f5f31cb1932d294850d2c202db94351420fa2e58873c0043d2bd03e7fefb39d47f9fc44da70e6eaa9607c04d1e765f39744cb9a81c8134d8e8abbb43f47ca876c5479d1d0cacfe4c7092b44f39921c0021e9074d2f57b5c94c1136e480d3623171c9bac7dd1780c23e5209997fa08b4d39f027e803f886cd934c60b3f9707236b186f3df15e03cac7bc6965e835e554c2f8f51261876ef8b6da03c07bad75bcc94d5c3989c2367ef65ca8cf18204379d372e12e5329839fc7c96e07f02c68ca683ea53311c7b7096326f74ca5874359a8c8e245a6e03e5d7ce548319b733dbb18bd83e50c66d51c41c17e44c056252f1b402b73b4fe2706c0bd18298a0a731b3154e36b8b0e091713d62e19f50546fecc65e942937b7453c3947055dcad5335680852da2aea188c9d8f1e876ae3d00bee0723955db47bfda627068d019c1e199d6acd626f185c32324de6be9922c20270cf092044a255c6b28c04ba3a7621e5cca9c84efc0068c74cea21ba205743743f15808701fcfecc889b4a763f92a54f200fb9ed35fc727cc1518d002a081b2391871bf7dfd1451fe8c36cbc8930f2ddde3d91b03824bdaa4ec3a4d24e2883bda99a5faff61c08faaca83a32492d92a0d6710f23c80468f8c2a41ae9c34652c84fc642e4560231d3ff3ad92b5ddc00950bc330e42cb6da470e9af02240d1664a3379995f2391e1c6c0fb0baf3c8832fa829c9d710ed765a819867173c180389707b1911388a551b9447af69c4806089dbdda7c9383504ccf86d718debabce53e45ca7e372bd889caa88d5442a8dfd96f22471a3737bb88eb6759bc0c9b99074d458e89a9bde90a1a94bb192cdaa8436376d49f5e03241b85953fedc5de48417c35198aa328d1f05b6036f57104205a1ad40f83ba0277cad280bbed6a02984f196e3309cc9cd3b75fc6edbe14a0b6bff77536d977f38b8816ffe63a0225c1d61b914d6966100788f1bcc3e439db0d60d43e61cb70c9052a1d47227b5e023a281a8eb38bb149f3572f1447207e0314bf34fdce57e4d00dfebb524cae84d37ba3fa502b0dbfbcaa8b6fcbb4a8d6fee9bc62729f16f1e446587041b98b8a08860810e27901da219b95bd9b7292e6a7b9f210cb55ebb9ea24b9d9ddbd8426ff7ca857545e1dbe14c4f5fa02bf1daf36110433dec31098e1733825d1653fe8da7f5e061690078eb0ad351b64ff9f0e7bb45d2094461875d1d0001ec6df0fd31ecc673d6a1ad8249ed2e0608506f2681c224afce55e6a91b06e1a5e4680ba4c8cda82e19db0fb885c81ba3e15713499be021fb2192c98cd60528c664063dae26158ecd96e58a158b3d0f618593fa3be3a399ae8a188f0a754012f885e8acf7079bb3378f38998bd43f5bf7bb24e0994edb2b2af2727dd921eab48e887c2752c20ed1e76f790c90b8e9e709132590f12e7063a7f22ddb7988d6b1251856d8f7960312ba78535ff0d8c78c855949b72f5a04d8bc57cbae9f031cbd03cbdd54af9bd7adb9faa232c75f7a83a5fe6171b789fd4bd697e6f531c5c9f3519a9637fc2a1e33c8b121eacce0f0d88b914d88ce95191c4d783d1aa25d0f93450cd2c809867e8b74c016aed87379e8a08f17e1f18e62fb97e7db90fe1b91d0f135283be5537e3160c58bec013f5acd73d55c41396f4f71813e25cec5c5f9e3b839647d1c6b5f1e0f078e6fa8fe9ab954c3aceb4e7082f1479d83f393ab4a70f5c321de5ff6da8ff980c0de5cdd35f20a8cb0da4294a9c220942040a85a509eceaafe944b1883a744a58371f53950444bf6e8157937695cf95780bc1888ce4bd2b4706ff65a78070c92c4c05956393acf4b0423311b2c7ac3a3ceb12d2a90d0ce70982c71ab07d100eeb0c2436f20bccfc5e3b1f17c28e2372656bc7097befa9ed1527bcee1018cab2273a0924cc33886a6ed7267fa0f3e0dca2fe33a8cbfcadac2681f13c8ab306804744ea33c981e9cfe140becb86ad12d8db117e8421aa97a49e7fc42582dbbda3f4f305cd724ba2c86cb7b58e9d02ac490aa8975585739930884f9f77cdfef34ad205b7700319be7dfeec1c8df8963d9f223e638d4203a43e384da2823a373c19957af0b81818982810916d883afaa0a9267b069bb5270ba7569a82a8106572170f761d58f94e6d55b1fbfd031f5f594d8a1fecdd238307e5aa20eb600cff117ba3b738a4d22f93774208da9b01889ba232c3c6c81150961f93604e7ddb861a83147363eb140c284d3b4222d10e2e1fcea1f409e09c1c616bd83ea45f0d2127200a51c224a66e7682ededb8a3061a25a26b43fe1aca731c2c800791e4f06356b2c36bf5d306ff688e3d822ed68d7d9c232b2d987b36558b78b11b3110463da8e94346b21ee537cfa0dc14415ac3cd0e50f3c9bf18e0debee9e8cd842feef54d49c48d562c4e71a79725fce048f7dfbb94d0ba02523b93f81007a2a39c015c146a5ced0554f7b8a1b416211d2cd6f3866611ca1416ffc76c6332afc59c791dd353cd18ace42e1de24ddb4954075d4b8db805ea3b388cd46330577cde0e09d83c0a2fd1a88d87d546b7a1006827218c248296685edb9f8de42f4a0f74dd165300450dbc121595d2c1a2444a57dd670804431b5a788cdfa10a2d9f4774f2fe38158702424929ab1cb7c75d3d5b49a45530763304509273edfd477de4e06be22ab5982802a5f90620e2c5a17455510455763cdfa9c78300637ada203b870884d07587ee962e91422d181d205325e3e53ac5e7856693cc1f9f684bc3ac443417441a48bc5524fdcee732570310e6497a7b63c2d78e8dc7d6a4af5170fda447904eb3485a177ff42e6c7dd0a43cffab87ad0e353f036b99edf50db584fa8cbd146cc138c167fc67ab13cd9d41779c7302c820271c495a261f0dd422fd8b9413820e35ade60b3c41466944f914dec8b18ba990a6fee27e78e6f6ec7a53e675f9a770fe2c102a122a0b1c2a8dfed2069856cf7abdf04224d92f0d3cf0b74679430cfef6775133a66c7330cf40fd4661e1aa26a5ecf851a1d1d70730064ff5cace10cd9041f209b091fd20cf8945ef856fdf3cb0aca59923576e29f6c34a2aa95ef1273e122d65e1439d3187017d7726d86ae4b7c66aed7cf62023a7077063481688bb4f50d88a042b0e3cfcbc5ea899518a22a808201c72cb02148f5d4f26b441a3abc9149f4b44979908adb2d436fb4899a4b15734d43287e128a8e2c53026c2be01c8bad76d2c3973c11b7f0eb33815c20e25da8bc0d1c8324d1790bf7a02034623135cebb510b9c3b97eddda22eb9042ae069d4aa7daa9dff7b94d039a12186214883007f0800dfb291f0286cd7f20f00da43505f2ce26074ff8e8fa7a3bef04fb43eeafb179d71059985fcc94a13a55c93c84235e65d16fd814acab5c5adc02857f1932eb4fe38a23372fb1fb8b83e870ed962319e9b942f1dd280d951279da8200354e43d5c01ec60b41dd44c5e7b25daf41938b82281c8a781c84ed088b1682e47e00cd47ce956dc513da6b66fdbf4beb0a82a864f11348a4ba738bbfebeb27d0658fe6091b232cd7468382aba0ef0aed8bc0583426bb61f71ab085fb0436c4d2f8b4c2fbce40420bea2caa35bb567b25d9df27c686628a6bbe10ccca155030343bfff54b57a9e2a5beb0da3de2fa52dd0198c2f97d361655a2c4ba95374ad4bec6528de70289441a7563ad2ffb57b1d9ff252f387c386ef2e66e9d05980fb0126fc4fec6cda2e7823d2e8671f179cb80185af5c15d234f607983bcd0d69fc355817b987be2e74340665647f8099d390bb164dd15144455384143b2cbee17787ef8257c2be5cfe3cf045d0e2e0a0c8dac3fee534aae83c0162ea51089a86c4763502cf0fb8309d0762a4fa39677344841cc6d70177a5d27f19b17343994a23ac4f786863ed044730d8c234e62459c0494185c2034c8df998c1d5d5ab45f67d338c68b00186fc5ce68c4624d076a700140803d1d0725e735adee5075c9ca1236a44ac42c9e48311f6971f7d2eb3710e25ba4508cabf24d5985e4db0aa5b3e3c86737c9c6983b7a670034f969114d9007e060ba93e86681ee696469b3e8a7dc6b3ff0cef3311bb8b1ae8ff5748574887d12216e1674469d4303542c985eb7bb179115ca302b1d1509087028bb07ce2ac4abb9e748bc6910659b4aa9f91d6aa81d0d03d69d3cc6b270b68e2905126f2501fa331b8fc251ac6de559f69f7657e84463e9344c0e5fa7b322e501177fff15ac498fdbb8be258974e091d887183ad355027c5c16be56883670b2681a14010841a821a420d42359081bd11352ddd98e285dd8071e8c57287ab2f6fb1bccf932478991816cb0a00ad328125ab62599514a9f08cf40fdd0f2996059eab94e8e0f7227038ed0714b53b096eb868326d5249f96a0b6f73058edf94459a958855f167a005b10b91263019b221a6d3d1eabaee8d14e3cb01ce91a8516e45bafad5bd25ac5d9f1d4663d52cecb888401563e5db8bff62bb92a548648b058d87dfdffb63a8f6d3143ca54feab415d8503d3cd81a4b458c0116380be96d415a116d0bff12be2e70c953f09b92c67e0e05d9c7873aec2e2d2b577ed1b648512bc2c044fbf29188b6983f6c02c3d7a1a2b09858c21229ad9ac75e38acf94272e13601d5dd24887bc9e74f6bff620907356cb93a8a807d77f523449f45c9bb25c9228410ae161b9aa6a40874f99b858883180acfbb3593b1f07797fc9024bf9d2b32deb2a19eb022e7dca3a263df122e1281b6f1153b0127f8f9a4e60409920956809812b18ed020cbcc2a0cfdcd0220af7334c3aefcf3f9dd091725c35833259b6408804022045604b764e954cfbae854f58d340d83ed3a3c71d4d596f8d6844e5a7ff2fdb8df59a35c1448f24c8a6c862b22117a018b6be75a4828825c3ae92063f44028eb87f90157d191888f0a8464f570de872b5ec602962f9df590305a209cd5c3bc8053e14044aa40974e3a48182d10ce4a71395f6419fef9f14db7bf0271ecbb70b053e180840cf2551a7734b77420ad2bae53544293a5f4bdaae430e8ed3edc650a6ca1046e96c4d50f5710300a8b73f599dc873ff045c017af2e7b6efcaaa57f1c76308d646543c6f446832ddccfd0b4f653f5bd185bdceca51247b32ddc26a5b040228d3e890e8b718a1b08c2301cdb5e39a891866d12da9374b56fff776f9902151091105b0f2fc654e0b358b129a5e8be04cc43a7d68725eb6130f72591dd50609050ec89b2533eaddecb572cca94d2f5604f949d7215db61e96a85a52b168b32a5045f755f8c4509bd044126c8e0f4b8120cd60303c310060361a0c356319e115cff7af2e115f381ef832be60b3f84d20a7895a6c067f55c117f2afcfb20d87ab1e7ca0aafbf2d91ca94d2ab074b084ae1bb7aae64b94ae163094149fc106625fcfb383d57703ecb2bbd9ed573652afc1bbb62257cd76379a5576c2a7c57ec0a965782bde790d0336525fc2626a0ab27cb55727dab27cb556afdabe70a9610945c9fe5f6c05e84c1622bbc9ec5e44ab0d8fd155c0f8ee1eaaf520f22c628fea65cc59e7cf081ef834f7c9109f837876704f1c26e0e13f11f04572dc07a1ce180566834650a0be087cfc265fdeba75cc9f24a2a165cdffad8142cafa48a4d61c115cbf24af755bf8ab1b0fa562ccb2b85cf8ab1b0baaf8ab5a013be3f0babab8a20d823cc589b408f3123f84ab7270b9845bc92458c61698546e02b813d58b08857b208112584f2297c2fef631163424409218a9329c3bf25c0544520a9ee4826cb9ad2c96021533ae994619a30a5ff305046e9a423be5882392a31c451894b8c51869f868ad25ff48096d2df4de9ffeb40e8b5f089f084b11e98e7f8df9ed57bcf915bde9de4fe2aa60a552baf6d919593447c7f5ab85ae524b91ffe2b278913159e55ec8813cd491479ee8bcf7357d7bd75e43ef82a1d604e60cfeac39e158b1582319e7025f638d15a4fe0871c88a2045fc50131b70465177c308788180b9dccaca90098df9a277e6bbafcd630b1668c0b301dbfa00b06fb0b82e1b760e0165d2ef15d31a85b8a25785945ae4c29c4c07d1a5814b29ec5e261c54090c502572dd63fa9be070fa39358b69eaee835d78baff25a149d968bf52bd6af7a56aa700a33e5ea55644a2f556abe941a94fe5393a514434b592bfd1786997dcbf0572ed6af58626b85f34eaa558f4bb57abd6b7557eee33e2d189458e62451fd4ab522725fe514feea73f45862e451f1a87a582b70b57a281d95f2fe2ae614fe9595abbf319e56ec884cf65abdc7ee5f15cfeaef4ab5daae53b6120083d16861cad5afde4be9a5971ad812aa60ae0f5d398ff3610e91d7fd2d966218860f0b4331e6741fe7c51c272ae5eab229719cc4e739222b5baf9e28b77402dff52ea8cbd38ac94297eaaf8be7be3fcffdd5abeecbcad6bb6237749e307644260b63342a38577506e65e8b72d994ee3e220f8cc623bec823c68ec84a19287331cc9439e2873d3c377644e6a02c7c1a58d4fa1b63b1407f72523df82d4da504bfc5c3ba31a7552cca2d552118a3852e807ffdef8be0b1552c642d512d01ffb65cdf7ad68b3cadd812ebc51781157b5a3d85545af796017b176bb573bef5ac9cd811992b941569b9e33c4f8eeb5f39ae18d487394bc4307664f51e73bacee37af5f0b8624764652b26a38d2016619029758af4c081e7c110e7614578705ccf032596fe51744a8ff9b7c49c24b0d77b0ceabe2a8c15b997c79f153b222b59b127550ca400185c4af1554ff7c59c283ae5abc0c8528a1f8620d80325defb03cc6f0c6b6268f3059a327cf18bd5b332d0a554fd13eb97c652fbbd600f8c052a4eaadfb71445d887b127f1c387c59e6060e93d4eb0f7255195b31445a7d4ee3e4eb0f725e1835017eab6c01c1160aee789724b7f28b1bc1f45a7bc394b56cff2d0613a3c4bac27f1f72d5731f1092c12e5beae0884ea59624ad6af6e09f33b00d5b7bef802bacc419e23b29256b6ca5088318aaccc21d25abffaf0c656394b54df7a5599233ca27a3006756960ec882acc89724bd6b3563d50b7bc4f1748c5e68b28b70cff1eb913b06143a4b1de490cc5d62b7c31dce2759a9525149995ad6fa9de29ec8180b18787fbc0fee62ccdcad6d3c02259093ecc092c55e5fd30e69453a4f5b23264a3e8945062c9bab077ba2a1cd16b5062098bf1a844914775693b76e8b0d1c300a3130febffc67858a20c9485516e49739f150cee72fd75fd6d022c6a45e17a98cb9564e9beeb5d2cd65fd6dfd60be79ac1b964c47b7fb57461d7d562b17ec5fad5185d8c916600e1b7ba94ad77b5de15feead7859b72f5ac1a39452aa851e377e8b864a84013c66573dd6491c10b18312098065c039661014e05392280e52a26822599b21573c2295d7f4508a7d5834fab673d6ccc3972af6b69f5acd5b3ca1558060971767600211969baa55257b674f9b28303eb220dacc5c200c706f84fac5fe51cb93f2fb4943949c26ffd92eb5b3847c25f7d98b3c48a89b01c21783d872a4708581a10fcd298f9a559f34ba3e697868c1961c09c6025f82e8a45304045e9fedbe24c89e5958478d950fae3fc3090a6dce148607eab84f19b00323ad7fd822018e33922935d1a40d68f0b2da5ffd2b8293f28fd97e64ad962537ee93f2ecc94b010070995b2d52302a3f8ab9067497c188b85b31257adbf394abc6cbd6aa5c4cb554fced24c15bbe38f8b305c7401dd6bae57f540c0a8eae1e9e151bdf83caa97f1a862476425cbdde7160159acd5833945c0bf3cab5fbdac144b7fb18747ec7180d1a9a5523d948e4ae9e5cd59ca59d27a5ac578623cabd81159a97a994b01731084854908a1e4ab282350f9c14c10400401c69b25c228853f82055068604d1048e8b0a50825f0bd07c882ddd245d80ad6f21acfbdfeae03e6333346ff6991a5746a39892db0c87f68b854f11d4e5fdef0bd84dd1c11c03284853de027318196226112136879fff0869ec4045a4059c8530454d5183f7cb084852128f6bcd778c2bf651873edb82fae564958acd58ac5628560112b96abbfd78be7b44a9558aa74882780b9584306cab8947e41588306ca543564a02c4909caf0aba4c3ef95f1974596d27f67daa854ef3b543f6067d694ad3625ebfe44f05de28835704059eb77664bd8ea71f073ca2140a3d5abde066ba7f5ae1daf77bdea83307902c56bf708f4e13efc5b3d40a00f7f560f0df4e1bfeaa11da9546a6e17aa1a2008de0bfabd514ef0f8b77e852942347125168fc7985ce95760ade9b9378cf79d5051127f05d5bba08abd108252389b02356003f6348e1cac11d194e2df078aa274d2d1f19a982f46e1cf025a545ebb594af16dc8ee0dc91c853dbe64e7f6e8f89d41538aa1cc858252144f508a7f59a5ffce9829433026bee72c017d872876997960f41f165ccadf11d25af855410210fcaad0c0af0a0c5cf1ab428a5f154d74f95501868b0cbf2aa67258ae1c17cb25b25c2c17cba56ab55a51ad0484b96a2070d9d0009261e3854d0aaefcace08015548831264d69c38a30ac15cb0a30a593f8b3a2879f155d7e66d8fcac98fafdbc48f333738453eb6706cccf8c0d6b7e6610f03393e557c60b1fda944e220d3f2bb4fcaed8920501b30b94f96191056646081381a8125566ba946007be48f3bbe24c9bdf1553fcaef8f2bb4289df15627e65dafcaec8410d18bf32bf5f993111f895e9f22b43c4af0c985f1544d0f02bb3e557060633a3aa7543b115fea0d852d628fdf7849bd2c167fd3d23270c12ea58dd30628a3042708121482084926843f98b02861ae28c016354e072a9c199f5f083024d6bd6438b56fa0f0a2f254ee93f28a8289d80aab0665fccc4003386cb172c576460f3c33295668c28d010d1c4cf052f3f17c2fc5ce832036396815918bf1f1b0f8d826810011a0320c1002336a0642403101073458929dcb85172b125c2c215e6e54ccce83f13e3376612988d998d998da902ce8e981d313b820bceec875116f2883111a0a8c298ab62a048f3528b343f2b6300ad91a9393a020292c9fe8f1e303a1d1dc110c6d1bf4c0604e46440100441d0c9b0594d01f31d3b47239093acd51ac79d9d1d9cd91063f8b0ab72105412df754590060446a7ff55cc557f9d8a1226f680b252ec01ff89387d09cacacb726a95f73eab870c19f06160cfab46b1b57a15cb85556c27b613ce44d58a46f8e1e83ef7c1d80c02e30591b0e2e5cd9a5f0b6dcca811f36b218a5f0b617e2df840e667e5023f2b617e56b2f85931f36b41cbaf852e3f2b65fc5ab8a22f07ae3bc8c46c88116cb5c67167e75f2603022ae22e0bbf86cb5ce8a1c46a04f759f8357c063e6c055e17c3d58ac6f8228cb1d17dc07be3450b4459298e7fa3470467e35fd033be8d9ef12dd0e1e0ac46cff8aad8e8d201698039200ec852fd910e8d1c180cc4390a5534549f83a37ab990c0a231e674745ff78f604358b14609becaa946a903b66260634347075ea5783b75d8d8a1d1d33a3acac181bd2e8d98939192c63b3d8d0f025e4753de231a347a58ff383baa07d3a3590ea3939381d31a81189dfe5757a829a77070b88c4e3b3a6c9461cc293d32121a498533b01573c5687814592e180538031f04775a6deebdf786611882e0952b6286044102af805bc02b6016700bb8e57e01ef97fb450cc19d12247a1a6874d1b0582c353a3d48b88cd7882846c1fa62049c816d4250e755aed66ab55aad563aa0d3455316b91f4ee580e300660b987199f930ab6156c3cc87190cb31a84c63b55b64a9088c8c938122b8c1df130de72f54e44a50bfcbb65cb2533e66e09535006ee4019f84e77cb96d0a8e7e5cbdd72a7c220e12a0819326440a22324d58fe19d72f5dc26c0590de3fd9996f18697f553c022a71ba6171a5d32341998914c6814444c68c422c3240c678e66a665bc66581a80c174583866a498ed30bb32bbd282c00ccb0c8b1901e030aedef56ac16030182c47dfd282c7c1713d8d071f961ee104394232027bb95eae97ebe57ab95eae97cbe5fad7cbe572b95c2ed7cbf572bd5c2fd7cb05f46ac1603098cbe57ab95eae97ebe57a01c18efe65322020d7cc8691c6ab1c08f692c0f89a496084bdcfc6c0b01811c0b881d90060b31b603a3f2b5bcafbe37bce124dc515bb200707c7020b2cb000e76bbc60393d51eef8f769549523ac67cc19c75104580eaca746cce9fa8f3d5efacfca5409f4abd2a6643ded48e5ea71723241ca97cb821a300b6ac06af43cc1aeeb2d78d8bf5eaf578cc7825811d78f638dcff9b14727e64423b6555dc6560ba78707a65f488cb323ae800318576472a640315ac0c4352108c66605189dd2a372b562ad422716caf07e18de17c57bc196fb07093f8b190d173ce286018fa8007c69018ba2c4999acd002b552af071c080f90e1cd8559a1560cce9b985665a69c6f0305a0f7bc58a2e4e18eefc0085464d60ccc7343f3a7e74fc0ed94a549a5239e0aa71ba5dc0dbc41d7389b860c0cb43d96a8de3cecebf1a914d48e4f6a0ed023551cac0a871ba311919a3937b1185cd6b46a2381ac1f8be82e1f6bc189d9c0d97a9566b1c7776fe65b2cb2506042493fdefec1c1df9fdd068251ac93c4d0982b2db73bb805ec019e8b3025c2eb211807e5f7cdabd66583fa8725ec943a31c1f72c015d892656074da510509552a194b47a962b14496ea592cd6ceca88c6abee1070a662f5b86ab5fa56ab858383f3b784b17e45e5b512737a4470269a4036c6d832828449d31328b77397f6b6d4943b75af64b95ac435220850c081510988c82f932628aadf8145abe7517deb86018fb8aafb3e0b1f1c411969ca10e38296b2d51a5d2dd7bbfc37709ff05f31572b2663333a6de022f37ab970564f05a75c856cc4d800651e185fe210b0a8c8736e1010fc5205bbe3dd81c59c6e475784f5b07e67276cc554aff2323ae18838add997f182231537cce8b453444489038ab96102bddb93a4fa208ba9d56a0326572ed051722114a1ca0b5318b84ac0b18163434a910b7a2dfd2ac114d27689703bc0c8728539aa2751005aaab4441aab0caece50950dece0ea61812bad3634d00061aa2035583fc423f98430562c50c95e455e1eaca0883d583fac48400711ac0011c38d11e84a8e8964004d4cc1dadc0d2de408e95c12748420b03028aaf262fd58bd5a3442a32511c20c5e454a30f5622de530b58cf01029f27219c1613a9124308543a415bd56350b72562f1891580a3d4cbd5a4634c0c130a50a47d55d8db07aa94058f5485282b52e1e2d23ab170b839c21ae1da212170e1b3a3cc871a347eb83234d3ca8f26a91aca19c21a325262c1c919543550b8b5c38228f0fc272b15ee28fea887814e4aa78c21ed50d2057cb0c7f100161a7f5bae132aa2244098ca8251377883b56404cf5a0210e517da0d2c0b543d4a113146660658afcb9612ef41143950ab11410a0c4aa3660083364c23400081e0640650529fd94cbe62d052801784282132626480060a4763424f3a0030e5e7cc28937648cb18110923a88e5c080e5e547850f3d8c4024845a0617e0c0564d8c3942882f3ef0b0001dbce0e044c6e387bcc18626a6376ecac005800c102c615b6b1c2006349704c28bf5c5005b8c5c5aa0f9e14bed47110e1bb0578ba58327024640c9e7122500582a42a4461434aba0068d1c51010510c00b15a8f19144038600824a09241815edd810ad30330212928014b9582b076039e2013bcc80001854c841010a88618992241078e0dc14842800db886c1e04e01a71036006546aee16e005400bb8b2c039035a019601c7800e081b003e894e77c7dda095814a470e06200ef0826ba38605aa0ac01a7714698030d50b74b55a208ba502c51bdebbf297e7183b98f21487b78ab23d2548c61d1cc06b9ab3f9d0f387d7aa400f24e9f14ef399b75e1191b9aa89bb6f1f77b05cb3a4cae6bd71ef87dd1a85135e33da4b265be15778d15e2afcd79ef62afcffbfbbd3dc7b380747d4803ee9d9bcdb2b10d123bddea80e5dd5ab7d8c7de1ee3a14e76208fa7dd57ee33ff39389aab897d555c9edd515ddbd2524d9d3c2d83365d693428f113fd5bcf1eb9e53cde397208cdcfa995d732cdefab34ec5ecf8643cc5e1ddf29e01feb533e3de77e2cca63809bbb22c91a8956557252a00e00e92a1c41d04034cc12270a88bcbc54d0346882037e7be1a300b2a7a24a40f923dd2ff07430401c40f409ff417b8b7e0630640ae59b2d528ab4322ea78608625d423bc88d2624848020560808103100106cf0f48e116678670e0d0458b9892282453005d979f1486bc8981163260899e30828460b8f0d911c23035e4480a527c28e3828eb00746f65ae0402863662374b281f18614e8d00507079c10841b461ec4e080018266570cdfcc06648c3d947085e6265cc1104401c8302248971c65801e40020a09121f7818328e019a49c10d53b4e0c18721486180467cd1c20a26286040608634e00d5f7829220d0d1b00d5ce8055b8dc08c00854e861f2820a3047c55771031650a418d00488248b9b11e01041acfc620c181481344b30357140892a88006f94e0091bd80570c9010a98eb860137304184811f5a3ae0e166a08b2239245183171b185d2e1409e841041414d0e00502325c3051f86072228a195000a2e51e00cb09580e70c20d4e3071e54611d114860e3e2e76a2a65c28227049a940c40a4f2eecabb4036f0135d840461400d2cb638b0b8233478f001d386fce0192a0b41e7836f0c45d044da090c10994a264604393a369018a560172049131e09e44982ef291962884f832c477f880b943d0992652c003c9b3e4b0848707386c881121725e16571cf1100407a6764c31b02054850a1d80d2733f224343152a48c1ba0002ee1a5409e187541b195c57dc4321a43ce004181cce9b356bce30607c534597206ede40c08a08e078f205069956c69b1ab48ca1c2b532c942c5186f58182346eb3c408d1c1e08e38d69c60b4588a0059f82469a374b40ecd85103aa0f80c59937641850c6267a7c00c28a8a37366a08e18b27cca840054c13679ce017a603273928c8883167783180169b364be85cf044c4194d4860480f01317cb9a10c98337e68f383ad4c96bd820c3c9c11430f5e2491831831c4c4d0e58cedc509a276f4662c2421c319019841890b5a0c7084074eb49c11821463dedc882600c474e58c0e5ec0d954e91400c030e58c57114508f10588375362b619648ce08b3608e8820221f02035238b0504917b9263dba204a7194760004cba63051c03ae4031c30b029e72a802890ea498a1c90c179ce062088f1c58fc8e4062c6d39528ce7871c513218030c40c260f38d2a5a0878f071400c90c1f6410bdb8e0e801460c223378bc6029f3a361091acaa099e117d82e54a0032b352876b8d1c096193ab01e19e1b244879b0a3031ca509424052418d970e3002b4815f85030a28a1270dccc3004154b060822c5c47b23ba51a1035d62511c8000860ab871837188022606217a90a28832dc14c1620428b2202736b430861b1a01cc6c20c4162a6e90c270534309120c4046f964019c342928c19b2807b851624c9a256752902688de1938a0b0e18929dcddcfa0220513d0408e3221901c44bc70a7b95f158e2eecd638528dfeca471c40461c3e461c3a230ed16f9f5b7ff6b2ff7ca25a455fa3bfd107c1c76216efccc7e86f1fee061858dc2833de98c0edf63a1535d154cf1e08d2cbfe54f303417a15d5651c0a24499eb065cafd6be38d146e8830de30f2afbdbeb67339fd7fddf64e87b2ff3b97d35b06d2030488bbf37077d12f68335e80c6fd35ba5bdcbf467fe7ee3f7cbc4086dcfaecaaf1ef361af56c3e5514f3329b374e5b33c5e67841cfdd57364b791feef38942a1e7affb778f97f7c6610a36d26896dcecaad314f3f4cf9959bcd33f9b5df50f121f6d6461f3c9ae9fe2bdeec7ef4bb7995976354db5cdbdba6a94c53dde6afe994f146a657b3e6cd6ad8dfd35908a9a5329cc034f0022020494818c080282f440a4cdcc67114400e554540956f2818f13ea43c42713c9452410a98413e9cde25e12ce038807ee931e05a013722d6ecc58230533996ff778d9a65132d7e27ad84cdd9cc563b9e82290eb2c2ff754d5b82cd7b9aa1b97c66d0989461eca206ac0025b2d9518deeb3840d241bf2388215c6781a0729dd5f57baf4a4504c240bff786a0917b5917bc375c49012f7859e08d26f7ae2ecbc19b4386e06a257bfd154150dc72c57bc1d72501785daeba2218e67b9dc07bc196139007bce04a04af07c07b57e01de1e2b837045b1450852c1af7c6bd17b4c28bc305ef0abc170c6d17145f2e2210e726b5ae6a4708e4b240500c0a5f2078673a455705310856246402bae82003ae18ca441578435725e07e711df4711cfdfa152f7841705b608f7b41150886f70edd9fdbba382008822b219707088ae0050a4918062d0f6e4e0539401aae0ac4e08a37bc0e3201ff754507d195ffed1220f41e0a5c0ff20418ded5155937e7865c40b0550bef517841507c10c725bbaa7b594062d00640177cad5ae0bdf7b2eead5d10c440b8ba2e500ca16e0c6cef0d7dae0b06de16eb8241400d400dc00c420ffdeadc1508de1c710c4510031004efea8ab9ebda71612af0aa2e0da37befbd3ce0120be6022fae85c302ef08b7756f085ef11ab919803f401d200c6481aa1b5ef082309025de5015de6b74c32bbbae8b73ef05e16a00be42d66d8917bcb776efea0668e38ae05589f70708bb21a8c10d43101441f1fe00c19ba32582f7dedacd007cb1c22bb6c47bafd1df9d1e2da4f003d50dafeb8a3bd7e5aac14ee0d1e072205e105c81ac7cc70b806046ee052f78eff57196f73c00542e1b1340e06f820e6bcc98d013c61825e02f8670920135ded4b38533e9604566c265bc890e637068d2b32e19410727364c305110243a683002235807d00f1d767e7e601a41186520678751cfeb1665e09a51747ba380a23fc3c95b0a34f0b97d01696a7a58690fd4e3032f343236b83dce068fa302f762010a7e5319c05f6cc13d7428327767e2ee2bf848036f7cdaf2b99aa8dae6d0b8476e6ea104f70bb5c3fd46c181dd9794ec3cb16f26cbbc55db344ace64b856dbcefc64e69d496e15dfeb28988014a6a625119686d8445862b23d29933c90a49fc9d49c92b9161795f6a0dc1de8935eb72aeaadbb5ff111d604f449bf55cc234fcd92eaaac3ecf9e3d3e58dd9ad7fcc7c0aed34f7c36b551593096c69c8a8a20cf50369a6d240610227636422c827200307154e3419f34d130d24252c0290c105453b84782217c683074451b348001a9d3200507b4f5d3079a2820b7e5e40a3450eac1258bc70021772ca55c2810d3c087b18a282c1985f982d9a10d3b4b02843823138d861fce85c706446e8450d2e320149776749c881104e50c6163ed0f001f5e803e7124e1b5c1741e0c32b4f70010c195538c022827502ee513e6470980798f01be4e3aec195290f7382b82b79779619778f0204c186295c45820cb8739961ca759482f8c5e2c59dc7e81eae71f733890b4c0f9b3ae7f269e2f08f23a3221f4443424140ae2977ffc047971357107757d1adf3f93b7363deef557d9dd7f367b2df6d9d9ad3cf269aeacf26dbaeda358307ee2e02b46470f7dcb23beb14e7dc3d76ae3c2070fc11f449cfb2add9e36b1afff871f4c3e847d10f1f3f887e0cfd10fa11f403e8c78fa3a323a3a3a2231f47444743474247414740473f8c8e8c8c8c8a8c7c1811190d19091905190119fd283a2a322a2a2af25144543454245414540454f4c3c7910f231f453e7cf820f231e443c847900f201f3f888e888c888a887c1011110d11091105110111fd183a1a321a2a1af23144343434243414340434f443e848c848a848c8871091d090909050901090d08fa0a320a3a0a2201f41444143414241414140413f808e808c808a807c0011010d01090105010101a1eedef37195c65d0a48b324af557f362f97d9d5bce36a0a292840505649b843d95058282a94580ce7ee2ef7273eae961c87a6ec8ac3eebe81bb6be0ee19b8bb0e77e771f725775106114b0e0c70b85fe06e81bb57e0ee7a33aa4c70f71d1f55fb6bfa54db15f7b9f577bbfeba6dee3ebabb8ee73c10a4d72c29441a91413e3a6f9fbbbffb05e330f74bc6dd03e0a328e5ae59329fab99cf20d20709e473e443e37e724140403e82300e2724741a05e58a7c5694d47963d6247decd6a82eb33fab8ec5a690fb5ddd5df5316ce3aa4fffb8df0fdc3df5315481cd6d8aa1fec6b2446eafe257d7bfcd643dcc46a96dadb6d356e726b73f739a62d3dd5f34dc412aeecef31164c2354b6e5e6eafea9fe8f93c9cd9ad5fa7a2be94cda7da7edeed57f32eead99565f3abf8cfacb2edceec56332fa73d1efa1bc5ec7e9d8aee8d79ec9a7becee2e77bf2d7767ad76bbfa301276655922eeae72770cdc3d8a8f378c7b86b24561b6868f5706778f5ad31c9ba1d6f76dd4c43d6ceee09707775f7dbca26b96fcf1b198c5a1e98fc9ea1f1c9ae2d0f4a755753e52ad8aa63e6ad4aa3ea9ad0f5191ade848ca07880c3a22837eb48ae29d99aeebfee9b55bb7ec0f9bcf146d713f99b7ee7edc3dc74717c38cfb995314e75ee7edc3fde7566cae698adfc7aebcccbbc047f7e21b4d7bfa6f1bdf6ef8e83bee5e818fbedd530cb5f232ebeed74727dd1d878f2ec4dd6df8e83fee40a4f2a9a2afaee866b356d1d96b54f66a4e1fe933eecfb45d77fa67a1bbe8ee38ee7e733cd005e664a6a64a0f734c2006b63322bd9847a247124faa9b23828754c27b04e65142150bfe1e4b30c682e083e2bd4f60ece9f55eaea284a54b1445f1f5ac96c87a89a18bc57ab5624fa1288a62188aa0d3d265a1c2576dd64f29bdc7694aa98a45991265a7bc31f0f5147b623da80aec9abbb1394db190753fbbfe671ed6e4a9e2dfa889a63da9fcdf14e5ba5054b83b797b5a6ed97dfbda4ddfde8764d7bcf1d69f62dc9aeab256db67d7df99096a098a83b1a5a2d5a14582eab597cfcfb8337fdeeda21effa82bba9b7c517817f5be75e7dd26339ba34c2c95b199d5d5a796eacab6279ba1d69dd32d3633eecfdceb61766b1e8a5321e38e611b8d9a0d7e353fe19da634b56d75ce39b2470ff2c4796b15fddb996f9fb593af65b697cfd7688f1eee3e828f5a47b7dc9dc947b608f6069608bb81bb3bf131ca14518a8882c218c5c6d89ba2c784bb93e0632fedd918573253c8acf1ce64db5e12214f683e157f0e7ddafbf8903ae7dcf3f2463ffbe49c7bdf9975fb1add353da966dcaffb7d48a1a173c92c6faf6236ab59f7b9f5bffcdaedf63ee4b3ebd34e35df509891a4ec6927dc3ee37abefd698a96f0e490afddb6cea74e45ff54711466cfdcdac3e69346d9df38e3de87795b4d5334eaf7d3ceac5176e57d6ee9c9dcfab79b0ff77edd6afe757faf7ddaedf6dbcc507fd3b77c62de6bf4f389eaf53fe722e4ba792b9b7bfd99b7ea6f1ad7d6a85e31aa37b8aa45ad91fab7ce1ba73c53eaf63ee49971286bea7cbe4e4533ee355abbddde875451de59a239d4cc3dfdafe2245fbbddde874c22e4c9e7d6dfe9da95dded8d4d546d79bcd56471da9ae86671ef719aae5b8a489296c2499e363b120a6a9fa6e67349c8994fb55d95349b79ad565793cd4fe69a1e0905b55b6d571e66b31ab5b2f94cd71597a1d06d629b46936647edd38e5a1992badb35f3d0dde4cc5a5d7b3ab34948cecc6694e5d932ce87791b27a149d1a463d22cae24dcfdfe2012a2e2d4911ea5a24b02a79af5111199271e9e76ad1f9eba18050911f1e44e54b46870f7cbc3ac6e657bb61f3f8284be38a54e439c46960fa7ec746ea9bcd5d5cc3c53cab6dace13fb20ead2250377bf41425b5024d46db8fb4589b289b23da1a02b3b2fbb54c708e8486aa7430d4b35ee6c18fd487553eede818f3a233a1bab32dcfdeaa0743c9e29057474cb751973507232247234e40690bbe23ae75ceef936cf941a12326aca62c69c2537ad9a72109b5d7b39cddbb69e4245e78edcf2f114dd022823a79ab3ca99c483d3cc39c5b9c489c409c67deb7cdad6d3c84790e9c6dda7a4dc9d85154c13ce74c9a9967a671d8b9bb69a59b615e2d36c4673e85e9564a69c757833e9d0242420e1766e70142a2b3814704770437040ce33a56c79afbccc9ea84de79ccb3ddfb6a979b76da78a6db9e7db361b501011d01adca4fae1eed76868c7cd8dbb67e0e3cdccad8b2ae6ee77bc7dc0c643716a12050dedd06c68461b0fa219ee7ec75b06e34de7e6b29d616be3ee365edea82de79c8fc5a889e6b29a71abde596b6c6373ce4828086788ed8314dab8bb786d41b6cee7a9666d5434a6a0c6ddddfd8e2940a570fa980212b700ca534072f71d1f53b0c0dd877c4441cd91bb4b911a53104ae1039b14e6e5dd9a592adb78a69450d0508d674af9fcf04ca99fdf6a86ca0348c1dd9d7c847202144cbdb2594a4e4f9cd35ff7eb8de21ecde5da1cbad39a25b719441691433e3ebe8db2b82744c256336aaa6510095bcd3c1feed9b2c639cd4da02c2d9dd0663c010827b7ce3a5dab7fef7428ef73cfb771de6d5ade6d35f35a1d8b3fef763ef3ee35defd4765254c5159c97882ceadcf6bd5fcb7dbe75d93d6599f39120a6a0380c4f39025161db2d5ccdbba753a4badc36911cc6e2922695a9a1a4d9273119d7bbe5dab699c56578d73137777611f4db882f6b02d6f0c85b23d9b4d882422817c746b6a025066db8d73ee6f504fb78cb23ebefcb4dd6fad86e412f16487ebff1c9a7b52f935fa79d7f420dc6cec6abb9d79a75115cdbd926649cd5e76eba799e8c65bf3408a5a85641e56b7897bbc5548ada6aea8663394d050ab7d9849ce399bf6e15e129b9968ad716adad8dc93c23c560af39290a4302fdbb2c6b824a1a116490af356b58947429535a72ee513b3ba9e93265b6324e846d52529688a55b488896a1565b714115fdaeb9559e31d9335a72e6d354311613393a6a85588130b90678662b3ce4d72668b981ae5d56a5acd4feb6e37de4cd954718a95320fdd6acbee34dea9ede8a4c749ced76eb7dff8ffcc52403b8d774d52f866d79dfe75ddb5acd90c95d95797344bee34de35d9280f45f264e2cd74a61935218acd504bceac3ad9bd96655721454eac54ab69766559224c7cecbaf1a9e2242966736f498a79ecaa446b9c0af161264c5290ab129d4d60024a71ad56b369964cf1cf8fc9f67aadfe69f2e42449f2d839a56d9c59ddef55c993fa53ac53d1a835cdd5d20c95535b0dc7688433ee0e05c20841f2d659b5b179a33633f7f48f99a146f0a004265d98b490b041f205c98e4ae4cc290e6b7488baa25acd5b2adb761aef6c78e313efccb3651e6673f95c931c39dd1d0a0084596a0380294ba71b1922e1c84e6323494948662621b7ae2c13a65a2da3ecb6f534def8d469de908d6d5bcd5a5d3a71de19b129b1090db5506bcf268579452e707715c53bd486371eb2539794183982aad5f0c643cebc35aad9cce45cdaeb899fd2b5b7e6d4a5345d85e85434c5a14c99cd4632bb9a3615d5a1ec92bcb12eb33a5d99792b535657254d27ce464e9c774a9ecc35557a62318fcd1b47090db56ad47adaceac753657b69794cf55080948ea8adaa2b2dae3b14652bcd5550acaaebba4ec24f36c278ba37a6dd2518b6466126a353343d56a78e3f35c12d2739c1605b5b6ad593497ae2b6e6b542adb748a551daab79ad11e6a6636f35a9dd9deca9e4243ed99ae3b9dd9ddb669a528ee3a45f1ce437a3c536ab74bce7caab9a75ba6a72578e3213a34a7ae4272d2ecccba136571a50cc9cc24689bd699d5a9a82db7b98d623b771a1ba9d54e9c7736330acf1685776629a866939e762e61163341d2a9e8aadbed6aebf1d89dadc7b3f578ad2dc57bdd4243ad4da77960821871f7263e0691d2a399bc9a73d4ceb8cf1a67d654d1f7a529365175ddabbaf2fe012164e14564501029e4b3e654d427a7d8d4675675a8d678b73193bb010a4288b99370a48793e4cc36296de16b92ba1c7177004c71f72511dcfd3a11bf0f30a2002c5819800d4f00b941b3e4a9e62713e36c196537cf67555f1881bc66fdd61891bce0ce8cb2bb0c1242d20f11842f4694d4d880b03fe94f94f764a26d941f14c833ef8df278ab799f904f26c6adec67dcc6bbfd58ab98ddafe6defaab12bf21648dbe4983878f359cbdaeec671d66d7ad5fe30f336cc6f15ab5f7ae1f5ddc9d877ae28c99b8bb8fbb0bfd08a259f2e7c95c539c2d6f7ca229862277d6e846a5f213e9c1a9b8438d4762ced5b4e5735575864237ca43d9de0faed5bb7da251a78a95ceac9de48dcf9e13213d1e6b24e9ccbd1eca6e5de3691c85e46c6ea79a7b3d7c435316e73ff3ab39e35ef3b0c1dd4b1f8d9a7057334f97b5edcc679b9a2aaab63e3ffae767ab5967b6f719976655c53bf37b3c76f76766716ae673a33c34f51d401c451e70a6bcf166d2a9a80e6b9d93ec75a77b58d5b6bcf1d6361f4e335babf1f2d63e6dc32ceed9764e53dcb3e9366d79ad2944a7a22a8a79b9496e733bc2cedcb8a7d1213e9ce6261f60800569b20861070e8821c5172d904eb86045a555c4ab4611650cf0484801d51118a1c30530c474a0e302921a7ab6e4082500072288f011c1ce0c3b5564f1c31016ae681a00902e560e9a197db471ff1a057cf441739f8a5fc5bbbdd5cce65c2e43e1ddaffbf599b3fa3b13dd453dedd7ada2275ed3336fb365b75657f37da9babe4649fcb975a7dbf7e1fecc9f71bf57f5556cba7b908b3a1ce723110f39c5e150d577e6280fc2def8f1c6e76b1f8e4af19f59a3bbfda78a7fa7f1eef12db72926a1563bb31ab5249f3a15dd99d8fccce2cf2538a981a7e0e3100fbdf6695dd31f4224900feef4c928bb651cf4b87bcec7219cfc644bb1b9cedc3d881edcfde6a390162232880cf249d59545a1d40ce40e9a10d446b364de588742615585a3b2aa1a6285178d062409c8cfa44e54b6731949ad09bb64e73292ffff970a2f75a2b6ac3bb3edc9c4ec7e243a563c2b1c2e8024e572aa4a0304e9b7daaee67ff06a4e6db6335d433622191cb88b0d60fd6c7d3e102026ab71aa0f80f8488671f77caeea7f2d9fabf939c5e19d8933abb67f66366f357f2dc52a9bb78a42a00141ef934bb37e20407eef7468de915907d26042d39290924929e4915eea445df0d1878b8fdd6afe29e2cecac1dda9f8e8d372cd923dccbbd9dda77ca4b5aefee4cc6edc8fce69ba9a49afe674057e9086d7aae30734f89f6abbe6dee76f28140d3d4de8841bfa194d7feb96f7b5ada23629f4c4ac8ddddac65bd95c8e4ec8132a312512604a44b2eaf19745822a630f532a150ecb832daa883a048938707808623580291189ebc64bc74e0b532e13c400841ac0f60aa755a475b47a8520ecbc5415986a8999b201a58acb28548008605e4ca9c62a392c3085b3634a4cb97668802c10543f2a19b25c00c380062ba296299d1e9d9ed0a7c64f6ba7c61b2baa0f5a472ca45027446af130152609fa59bd5660a642d82a874883058557c41169af22aa0f5826ac72ac5e2b271c1b38365c3bae1b2c134233acac728461aca8a298eac049e4bdad830cc41a8eceaa8678439f5088c80407c70a6495c0e26129515d40e3ae60a14ac46185add0f5d221862c27ac0a7498bc6ab056ac1bb24422f147bc2d0c429e2aae0d54414cd10053a5c64ae4690db1227a6125667b7f73c606b44063851929a268754c06d8424429042046ab4a06087e788c3a2dd6090158a2e44d1567aed82006202e3404711bbcb8401928968840037cf8213d98a1dd31689604216093268b334e1441858514a004e0c912252214d940031c37c62041a50143b0b0822d031dabba010d2fba88c29b7a7386094a00e5041c376a5c008a2726b044048c70400c0510802d05134a58a264a94c154ffc92e06204035b9481c2892594e88107185c6821046c2e90c5124a8c79c095a97c2e11a991c3468c30bc90c209248e20e2070310209fb614a09c50c21211106ab31a3965aa9002871b0c70259f290060a90811105c8c30ca5431c513481c4184037e384001aea00080252020d48c88826615d410fd8d1861743185144f3881c4110ef8e10006280001ae7c6143014a083fa49b142039a243830b1d5bb4c0f0a404a01d0800834f4a08d79bf00cd00c550a4014b4821081007f007db8426090087449984ff841f8373600755c1e568e1083bb23e2b8375c16545041388a3a3834c69c1c3818c074bc6ab858ad150b5cad54aaf0faeacaab830a4cb15c2c9748a355c4728519e0345551126bc956afd69188a373459c954d8c0a73c21a229a166c985ad100592e919693c1d110532f12704e9146a30a48c4a37222de1512135340900ca9d10228be442433134c69c04a0f3355aeaa5663832055403147e8132bbbd555c8edb5c6bddfedfaa3a24c74e669bcdb408fd44c923431f26ab981d77aa32b9306d6f8fab949035138a94371bfae5b035c5c034feee4910676c458801830b8fb995514b33a9faca2b9cfec9ae4c9dc46c1bdf57d7b6773cdbb263188b89359877b6178200c2e611c01e304605cc0dd49bd33aefc93c5e608497af8d29caeba872f2d018c30a40e8c2977276f453e846c42f909a738309e80c1d3f23e6a357266dddad8b83bf90519ffc28b7f1144f63776bd9df964b1a9d1f333b046c5662ee35ee757cb0c8cc9c00919789f8d37baaa7faab849cd1ab2cd6994083559d408a969b93ba953511fbb33ab33549a352f5d69e75ed59dd932a9ad1762dc492836ebcfad8aaedb97a2db8b1ea9a258e745095ea8dcc9335dd1cc32c93adccb5250a65aad29eb70af8bed9ee2add77f1d173f2ec67091e362479a2cd26449a3d37c1ad09d9cfd07b2255fd3abb933d5ac9471eded7d4814fdacc33d1dee41a12c8bbf093dd5d57c8dcfcce69d0ef756b6c7ae2cfb3e16b36b8f979bc8754b65152ab3bd2c05d54949484ad876c5ed7468566dbe136736eb309353cd1aa7590aca94a5a03a294b41999e96a5a04c3d9c8d20090d3959b7baf4b4a3b687f34e495213d2ce4c5b48608b2cee284079daba851127db2d80b6185180f227fa274021930879b226a9d59aa256558b222d3c4063051a17d09c68c60b8cb9000c178872811e0b8c61011a2c60c5c9dbcedac6e6f454f1eeb34d5d6fff4f78d76a273ae4f6b955d6f282de679671b79d999ad42537d9eb34c5a64fc599d52dde49e537752ade9999c551f8b3d2937a67dd6fd4667b8dfedbb2ba2ab1a1703b5794cd4c782d0f55d124b5daed041fee33aec7c35b678c77b4dc2f3dc9e69e2f3df3d30aa5a29987ee9cd91c6f6573ebd6ad0576c8d9998519775ff7cbfe6cc72cc2b8bb67f1c471f9cc023d336b3eed767b1feea39ace98d92aca3b33e5e44c5dd13346cec8b040b37546652f863c7314de6d2cb260b1c3c2e88a35ee4e6abda6bfdbf5559d8ad288ec1617a5d75dd4156472f8780597752daf6042ced81585c2bb8cd328ef8a1d77128d3b39d3567871bf2da578af4d372ba658316486545733ebdf9a45358e623223c695cc48798a35caee8d9a015acbac71276f4967ba1a6942ca2d39d3755786ccca662965b6b8bb47fd94b9b94f667b4a63999d7c9e2baf8a354ea2bb5fd74d421561ceccc36a1553eee4ad88144e72fb33eb2c256736a7a48a5207f5e589779acf5aad296736678bcaea6fca4fe9d7838a33e4b9ee76e67190f2f68e24bfb6551cf5edeafbcc43f78f1f4f18851330131542692a20800a0c5500360506a6a8c01452a6e0408a0a48d14a41945b76cbbe97f3d3e7ad6216b7ba990c9b4fd378f7698a33cee9bafa902469eefc344dd36cf6a9d887c3bdccf6b2daae3e1afaf4786d7a92cd38da4cf6facc1befb6b6e51487ffb71a05191ff81845134edef6bab2b7d7686ed565360a252840000518284c2876b893b63367b33d33fb37956d71375a364daca24f98977936956d71b6c76a9b539d642834c5b65ece4fb73f55dc6b55b6557a82cd134d38d9b46449936ce2b09327bc3c61c4ddc92d959ff61a6329224e88c0891d9c48e2c4cc099c26d89033956d77e6e752346a4d5315ddab9a7e2e41f66b074041bf5bdcd638a7bf73ce65281447adbb333b69228926586882893b91e780bce19d42a18f7d389c0f47bd4eddadd6e848013314e08102370a58c04417ebed966274ab794d79b0b79cd922b7ddc6f9b4d96e9f731b054d9bb04fe68a246736a766292b9bd33a9ce69878a28409a0264c8ce4f7d09d9e809a214b7f2a99c01677526f8c9aabd69ac7467fd54fcb50689af5fbf291dcbab2bd0908914943e60b194c46e6a4e6ad66c66d5c9237edcb4f4c4be8b0449325882cf14a50a144abc427c12609259c3cb3149a7b8dde66327da2365f7eaad5b42f1fa9d59a6effb9559fe5933af77cb915fddb2c48b6e4f63ebe8d4a09790e3820b796cd58ace2cc93e9cc9e277ecd63c8d2a3e86a665cde52594dd1adb6bfcd9ccb652df3e5279f8a9ba4f85c3a517665d92480dcbd4c6274afddc6b0e9e1a8724c14eeeeea92315fdcc73152eeeeaf96639898af71d498775cab91a0828791f882c4938cc4d1aae408364ef256d3475dd5d778974fd4445ba623b664565d5fa33ee84e5bde97471ce9d8b6b7b2598a8fda2481366a56f251025d2480dd49126e9fd955489a621325426e13b35baac8139be4cb477cf9299fabe9cb47c474408c19d2c766a8fc3e9c10520c978df2f2138b79997d1f55cc7677520c002250823b46208c7b8f997b3ed8545bf6f7aa6af2a6aee7b99a5139024b220033a2082354306256c41927cf1cb5a6294ed392cced9977a6fe75b36b54f6a5bd1e7e5f7abe0ff35af36ff95c85eccca42eb9bde6d1cb43c853e39ddaaa99f664e64c9e331f8b551427fbacc33c34b33d2645e08ab0418419772733665bd38c7aedcb4fb856ebf6d7fdece712be478f1a91a6254a7af420a5d05c667b384d3111b97567a691082879f79ac7fb521c2f6b5de272093aeb70ef732b1304b880c00b1000b2ddb26e9fc75bcdccf2d437734fa73d1e96cabe9d7358ef763571dac33b8d6b22285952d2a42ce1012078c09a076c71f7bdae4d5eb379e3f4cc687abbb5bb2d1f907979a3698a1e69cae76a12c91aef7a494dbd756bcde28d87e08dcf6caaab9acb4d304b249beaaaa4566bb23d991897a638a773cee9ad6294c524f05a255b458d3c6daba891ada2bba4ade253cda6ce678fa8a8cd50a8ce268579b68d3a01204b41b58aa3767e32d19da1d8aca23615e5e5731582f3e1246b4e456d6b4e5d4a9a3d4d6868eb9567dbd876aae89153459f6c289bb7ba9479584d310f2592535cbb0ed13c536aeb245b4edb7395c2bc25254fec1ab5b35a24a33b6d93b0ab13e6d9908688a09078e6935acd07d4998718d314b5b3ceeafa395476a66badd674a6eb10337752a7ad668558530a41c69d3c5b21a69ed675ffd6add3cf6a3526321434b6ad67ab4f10e20979f65ee328217e9cfcbcdb99496a554a536c466535a90929b704c9ba8b028304989d93d8ddc18810041b277df9292aabb35b54566fb23f97ca1d44124174e9d183ccea7afaa82d09b5f2b69a55cc633393208204610308298098e2a48dc996e2acdb5fcf96292a4db179dbebdae4967df9e9d7b3d55bcdbbdd9643b6ce3af4dc6d6fa7b1919dc64946d87ce2bc3b51101a420148e841386a4ff8e1e6698b7b453e848cec34de2da9d5326f3577bb6abd9a3cf309bbb26c4eda696c84673e39515b8adb6d627d2e9da8eda84d536c7e1971940f66a2b2faac0f41dc7df401d683113ddc9cdcedfa67e6a17bb3ebd3d25e7b3edee8d3d80c95559d8a62dcc333600a06443180070f6778987212b7b2bdd7e86395cd50ab2ec99379b775bbb259a7e273b6ee675794ed9d38b3f855f4656a4bfa52ddae2854667b3c2459409bdd46f10278e5026e58c0102757d5969ff0be3de17de2231f7708c10e5f665965579cec37baaae50e4e3bd8d0818c93b7d9d3cea5a7c96e996d3714deb52708b734c5426e8f4fb51d75b8e26ee2b4a7c30d32beb1979c97d1dd49bd9aa78a51754555b42437fa67665775dd45b51aef7208410e5f9cd4bab7ee1c98cc6e3cf3c9a966a65bdeb955a6ea9c8be4404301e85633149b77ab62de9be8c6bc75b758c5388dee9a145000051cfdbab2e76b1c85ffc4bb1287368e9be0e0c549ddb651329b4bc0e109071c5dc8b893eada6bf1ba694f637979b7755e4f225dbab8abb9ec82eb8243fad2b4cd69adf1ee6f107303ee86316da3ecac3b5bdcb359a36786d268943343a1d9cc2c9bdb116c40a362261b78d8299a4b519cdbf1d1862936d0c819db93ca28a923b3ac863535707167732ed7e24cdcd39f5bffc9c4e9e3d73dbc63331352457f466612957d6eb5465fe3a8a61a7a64666b30e24eb26bae067767d9b5e4f2c53d671ebb6e8c6ef444718fe5f2843cf3ce6c2fe36e5c5aedee37fad954514d439875ef4c15fb649606297727358fa6918620a4c6bb19d4cc908554d79d46a3de374b532c7b356afdbc6b9a41a6cf19dcc90488d9282d013776cda94d097832d7f4d314ddfa338bcf9c4db3d5ff64ae69930c1b90618bbbeb5474b72bfbb9f57d38ea35ba691a479d413ecac0c49df4a998f7ebda848036eeeeab8f901101649ccc2602a4b0194a45556cde6e4949698a85d46a49aff1372121f1cc274949e71292d4aab4753e9372cfd78424b52a952034d426e9a4a4990c89673e41925a9578e6932d63b640d98213430c41c4f024861d4ee613f356f509efe7b53c540a6b9dd3d7a8ba66f455f4765b9590be34c5e866718f5ddf97e69dd31e6f356f3acdbbe55dd4ebd453455f2d49dad352fc990024c0810044b4b0d182831625a316a0ffdba96218b280010b0c5030ec7032b3a8cea73e7d6912214fdea76236ebd435fd55cd2cda46c94d04f0460040b8fb111f056013c050162fdcdd878f5980c8c2f4829b17d6bc7080176a2fcc2c80a50577a7f988a50417deb810850b52a30b1f5c31c1152fee4eca64414f6632a1279a5d73ffff6bee9fd46b8f87be5ed5a7e51487d1a8599b9a5259f63fbb69fcebceec6753cd3bd9939a5d69298b5ee99186d4b856e7a8756f34eb54bc334f15b3288b75997dddbe6fddf89f74d982d942911535ee4eaea69ad316e7f2797b1f72d6cba689f156b399d9df7ae5a1e76a6ebc93e5b07efc3b3355694d3d60ea36b52305151aa804c0dd97f84805c6c2122c3cb180646421675c218915f20a49533e30250153427077233eaa60021576a8f9a8c24e051c52442005072944c8f05521e58b8d3e96141152b751aa83a832514544dddc49adb38af2d8bcf3f9ab4ef1d67ffadc74ce3975c9d77cc8db2d899027363df3a53854ebb5f7a78a65e46ae675ffe3d77a654dfcb319493e6d469232dd6a356b75152282ec69425fd33292cdbbcdb6b95733543e9fd0f44d74af527967deaa4dda6d86c20ab4d94c76e6adb6ab90342d65678a96f03515869cd063662819cf7c72fbaf69fcfa356e75fb642f6ffd2956d1dce3d7585d515445f1565793cd3bf3308b7b3675e5ad5bb76656d7f335ea33d5955df7e75d1379bbfdd699cdba7d1fe691b9756b347d3543a1ba33b3adfefc849390b713bda17f66d6d7e2d0bf11f1b54ab7a7e9deba7b1f8bd133f334fe34cd51aff1d396fc3cae35f23b6b5dbe46f35ebfe9555ac9543241adbc5ff78d369335e1e5ddfeff53c59a8746ffd79daa6c8b23ffcfac31ce67ab28efd7fd680edd278ba3d8f573ebab3a34b319aa764bb1b922a9d572cfd7747b9fa131a8e46ecde6991c059d52d00c88080000000033130020301c108d864462b15092a350ea011400067dc258804a97c8b23486611432c4186308010000004004806684a4360028038649595e28086f9636d6408d0352a1225fb74c7a9b42d8a31aa5bc93ed6fc9f0de087c0e1f479919e674c414917ad02b49a247e2b3652b40049029ec0ca8fd78a7b979ec66094b769c310b98e650d1b2e0bdf6787a2c75796f4778d0367b29059a0486bd62a14fd6bf514aad28293adeb4e390c68580df6b0482145a1df929d8ef5ce13bf5668cffb14472db870bc6d1605c913e3c5d2bddf881f386bb83f86e830f6b11adf58c3fd7202c9805c02b7e1ae844097e1ea708badb07c30391bf3274a6e93f19f6e9142ba52f67fbe1d69e6b61b7df3b22c4fbba97a5d4cbeecb21cae70c3f61394dbcb649d8a0be479f9f4c390eba5c0748248cf620fcb0c6c0d87588cf8e504a440f4b957fac4f7e4f15e7a51073e04d8cb1bcb018fc1433e3a1258849bc3c1b30085228876448540fa3de111d888bbfa27436f02c566175acef699b5fe31a4120b2bc39868167d7e198eff165b483926649a591a3a670cfc29826308fbb7a3d5327561419f21ce219f5d830659af8ac8feceb9fed3050fb1223c816f3e2549b625e617464aaea9203bc71b091eb9163350d09af73f137652e1047a852ae109a04e6554543d9d2ad4cb2e94d54de2487e00643ddf6f4faca43973c17e8ec12257e964e80731924af0be99dd6737c884d01e29b9e1f17efbb3f0c072a1c85cef3231c4d60ec63115f99a79508a8b1cb24f45684315e2f2d8fbe740fe2ee59f79efa328cf6720171de9bf2c9263d1cc25674cc7df39ed179bc229972bee361748583070c74aeb0393f7716b10581c879c6d00e6abe1457739d9da5473788c49e6a7ab7e5c9e674c56dc926558b24fb85c0c844f4f7a9536823a4438345721e29d02de74d211d260a68d724fd8001a7b4f78f364f6423af9f6b3bc51406d389862d95dd7bf16722415828ffc6dd5f34484a5015482a0a9eb5cb1f25ce06efb095aece87460296d0749b17457eac0052f0b51d3a7b6337106ef77f2f73c1bd97a2b7bad8b89f70b05aeabf6be1db01ffc020d8e5fc596e28f719b889b6849e189d7028c235d68a5bc9500ab5ed606e23560d86646a1bab86ecfe21f8ff5bd375b5d46e92f37188e85dac5969ea7500b447aa292c6e01935f3b329355c99055d66713db2c7a87b24e8da309743b161f330e149de0430d0223823b15bfdd796bf8ea664eea7fb389753b963cb7ccdeed93efb69dde962d23e0c01f2f6795b1b23c0838e14c18295eb54a5119de1f967398c12aabe5c3889ec3a1a29b36ee5e83e1085d343360d329ef17d1e8e03c6d8e6614d8eea16db96039ed344841bf8711a5c05ab29f1cb36d90403b827ec930807946969d1643abb0bb816b8b46c8f9cfb38315095f50cf8df660327d8c418febccfd02ffc1ac29ea12864b4a14fc1f89bbc35a3131673dd24de7f0faf74dd998e185583668e6a4f8974980fe3539d25bd5791aefeb32f65b762422a38bab4253713706febe698fea2c4fac625170e91404fa1f8daf09a7761fdd34310693f958e382c2b7c9cbc2ea7526a6f07e635119a024e403d49cc01d580f6cabfd4e465abf99d75f81794497f6a09ea1966dde81fc62e5277723af87636f0d08355b971b0717efc0a3439b4ccf3feaec407e0280efa4c68b32755a2efdc40fd53c4b4d9720d00683fd73e79591ad7a3c6aa35b50854b832c6d3482a44e4f6ae381d35a9de30149613bf296d059125067e5c0ce7c2be28d5f431f08443655dae0c44be29e601d06c9eb19dcd4068f6fca0ce029ee5875a1cd5f4cad144e2d02574dd24fa9ef42ec102ce03de6fc65352fd8f1749067e21550bf01fdfe7ce69e7c6cc4790fcde39225ad817a0ee05bda756aa0f315e2920640a539501924d351465cf3627cd29b4b1698134a6302fee15462ceffae80610d963b102cb612761cc75eca7c706d22764079151e1cabd7218d4ab73b54f7edd7c7136302c3b6d370c3cb7ca995c122f0bedc948c9dcf8ee3b031b3122bb6648c8b778aade79e2a255f14c1a141dbd79012f396cef988bb94210f8572cf19a2b12c00b484d964d67cfb35bc4fd66ccea6ec8d67f847240d67d306d0f7ab32826c363b635748206c862ef2ce82794fc95acf4cfa9036a39376e4fafc95da0e9f3f3c09f9b07f492f9c4341b79bfbc841dd0f2ab74877ac7832aa742a215a044a092d5287fcb7aeb626de62f741b620329da56db77138519dad7fa98b2c2471bb0ef854ab4bcb752a94d70b2534db507c995923d0d8377129bb2251add0b5f705b994b134881a6e8375d028218b42e278bc68e73ef4bbbac29435cd244f1ad24a39d929d67082bf3d8a0f121741826075d383c5a55aabce1a951721dcffcfd11e19eb6b549aecd6b7ea2baa82ae3a528be037d3af2301a49482b3d89fd7974f3ca66f51f92749411fa56b28500b91c2b464b891847c61cdb9eb9d927be37e2680f16483a2af08525cfe472011e6861b70d97bef197c14821b52da4ed0d4f8d41b8a9d4c4eb45ab4a463651fb59d0fddfd320068bff3414bd716a58b1594e2d8af9a3123029db67e4e43bedfea2be87b9990152113ebe15a1e3eb3aaa70cd199efcd7573607f58d1508036d1fcad217166c9e1dff98c043e097aac805dae6fcb1a6a246f4516a045882f05214a372f7011440a37e19b5a05391649f3e89240ed05d671593ca636ccbbefd027b6e591918743725fb5bb25baa0ec1cb7338db4b2851fe8589dc9beb482068c1c21d2107da558a4a3396c179f4c528f0dc2044a88993b51051b0ce1b22caa20876316fc568e6f0bf42f6b2dc98a88ec7f867dee44e3c222d7322ae45b4d01df193d9aca96d0d4d720adcb4b757e95376c44afb615d1bd4a9e67e0be4ca995f52aa7504d9d2273a25016044b8b04595f4286c51c96dede5184e0c3f59f7fcd5c21308ea320337dc9668c9ce6effa13937c52b36b829a5f9d7cf065db631075afb8a04ed0e4d358436c58d856a84db55e8911b86596bfe95ec18500321e1cee75fef0fedf046739698470159641cc1209f3ef00d0312b01a90935ba29e0f403c2f9c5d121966e2d7eaedce3de06e65995e14c73ac5c63a033a1a327d55474a31628adba5d3f46213042078715561682852450e321f96c42001b85aef8dfcbb9d8df0c5c60e59f0203b8a48c9e78ae4530e528c7a55b4e4ceeb03db604d96ed6a07b0d80be0e6a6f08625e46032bdea56ab82421b7210e9bd309c69690f53f1906e1aa9e3202c9d39b71a0d77dd488c7808c3c1417cbd89d9df0fe93b94bb6b38c07037e72c86724cf912427235a8b0cba95197239ec10b1831a344b210f0c63661dc2d190f5f17f089d5b1d59c65558b346c2895cf121c8455356a7007c0adc4c96959cd36b04b343aa9af24e6749e9f648edc4169a19b8317fabab6d8856036b66c08911017782ebbd819731849b63530964850824b253c0589921c003f62eb79fac8131286e714ea97cfdcd0e2aaace8e1776778bf98c20fd68ea1b50fe2d335d3e2b99f36c7059f2ee49c4db2c63fd2c11f53636514c18b9734d8ca5a9b64cc57072cb01fc059f341b525de88f787a9d8eac3346a05ec369319b6f9ee6d4c5f84b5ed3f074b5fc384c65658dc3afea51245c8e3b2060f444b5e2ea5a22644919659f3da9aa9cb352a6792895e14c75feebdddb4b42d249788b30c0cab1381ce3e619605224ad37d9a2705099a9f947abddf5657ce79cee12c6154eeca92cf0a27e98eee0915646dbb497b60f5df243418ec90f623cc01309751efaaf691887f67827f223a0e84ef95da575d84fbbc28dc1effe4570368080bd65fbaa13287df977510238f69e9c56fdae71c5a9e6bc21890805d99e25bb9f14e2ed64b4456a77e8976fff2ea3817e9d2293606ebd6b6142b82b194a10bceae8cf4132d935231cee46a16c28f7290153e6da6f4c26cd46b74294bea166008929fa5941b261508e40b8dfff26b3958c7e1281b59aa1abee67a21c308e7dd1cd278204edc21a80e2de3d52a0333c87c81133d897e4a315fb69be0a876cfe5f9b0c6a1c4884685fe6ace80f8c0baf8e38a00aa7a83a4311d405bb1aaee7bc36fae658cd7a7827435c704ea272632845cd6b01e8ed843027d64bc271587374e69a5ec66684ceab1e3f26822b51d8564f30ace9922e9b433702dced6e9dcf6739b3c678c063dcf749c2ca96850b58dd0db78573879b43383f017ec81918f11ab23d9b1f722d33039b28c152d557f3b3faba443565df4f0532f59abf079c5f2716ed289e7294086dfe73a0eefe447bcf44bd7f55822350071628d2d7c36af06edd731c2c1da80f1820ce682a85155441d9640ece33f62ca4451dbeefc871ec4310c801afcdd87989829e778a022a4d7b83877885fd1661c85ef82a769b2fe73d90c226c226b93836f45097ed136ca89c667079bdff08ca668471a858c51288f2a2f9367005f6366dd7476567195a4115465429b2aa927c5343644daa31a0eed10263181659910138635a1a6d2cf84fa670a942f91dca4689427a7b46436855b82f072d134f82b662f48be79101a217f0d341e163e077b40a25f1b4e02dfd680d4eaff8a4830cf363761d152c6a9f3438712c09528ca54f14484ab270fcbd908b9fe97249e38fc2ce89d67f84eb0aebb92171c554d6e49adc2686ed9acf28fbb45fceba9f7240163d00750645a9e11816aa3336428aa11b58e06e8c525ebeda578bfed0ec714ee26a6f1dad7e0e17e8e41918fa7af3158ac2854cc0ed04998d34669bd9a53c15ab38fefc9895cddc4f557a61a14a981fcb2fbddbb761ce1d30e7befd16a33ea46b47b7a30b016695dd73d3ee363c5f5a148d6708540766fffd9593a2ccce8e5ff1d398b071044553924e0f770b8e142236859a386227dbe57c1f470eed005f09fbee5614961585c298b47a49ee3769d5d9032900c7694e3ffeee62d2462d7bd560c30bb461c202169685a6cf3cbf004c277bb309145f349a53076c088c1b87f3649a5e90cf0e3117397b074765d7c521196f40e4cedfa8cfda14bbef0bd51b069e1cf48b42b43ecefb90fbc428d20d812189013ca6389762e2b66de3e24d4803652606ccc0d678115ab9e79b9ef7a1cb2ad2be7b41917c35852dcb7e82fdead69142d00f19967517d7249330f62d8d68ee1cf957d271f22b7f01238f706916eb20038c82b1944149eb5e5757e3fa86aaaa781f052c4bf8b2560d4e85d3c78d1db2dccac722e2d35ca776533d134ccb18dee4d0662169c320ad32e3f14c200b7089fb800a9f4b91c23e2fbcd1fd08abf7b339fb448d40a974a2f32e7b518c88f8b88b634ea6a740d4e9924f1b46af9e8c3c7a159072b9c5431008f3ec2db178a79f893296f86861fe0eeed63d2419871548c97f597718667b581eab928c6075710db7d536eb0522609ebdfb185bc11fefb5f206dc680c1f855a8771c34d104edd2a45bfd3a5d46661fdbb80554335198a9daa404e44f53060be0c35a15e54e7964102531a2d152ee9221b55daa60b396c62e9c0961353aab2555e4a18f1e8e7cc23bbe82b0568dc61d83a5993f7f7552033b8fc101a2e89d1530b160e9f26537946aef17000b63903a970b1ff9832dc4e50cefccdbdbe19fdcb05b7b36af1cbca0aac82fc154d1b095443268fdb23266735e67eb16b2aece5bcef451346e4453554931cee42a8721bb6d7728dd9070c0f4ef013598ca3eb5c052f7a21187c7b36f10be4b5ca7499e2b0cfc0972f8986eb90e798109f09641cdfa91a21fddc45f6de7eefbe09fcb249349a47154a4b37c2af550808f23fff542032ef78bf6960b525797df265211482b518a74c3bf9657d9d39ff65707b1508fbddb0fd5c11a276be008c43f90b4faead6fa314a4326366b5e17462ab0a321b95110225f30e7e3d11c6ce930f9f7043e582d30ebad3e9f6b307353fb557b03caa31d31827637808cd8fbdc667a5b1f69af5666de968584f8e57821f07c7b8cc2672a3d7f2a99727f6eba25631b188cd52c48cc4bc900f527b75190b463141058364acd13f8be7f991733ab9ab4ac9c6a2bbed6b108f642ec31cd92f385019042eea328272b4568d10980267fcf80424037944336281154cc1536619848810569c1de011c52398b63a8047a22833711083417c5dcd4559180a17a5c73b575e11c3983106cca6e277b87f9cb7c089b878d2fc5cc5f97f68732bbe4218c2f58c920f54b6227b7b5893b405e73cc1294c251ad983c477e4075cfbed1231d89336052d2a2fdb63c5d5a1bbbe7322e5f4da553a2db88ea8323843907cad79ff99807bf719de82d7611dece9b44aba19ef8bc23829f9647d865418ca59e96537cd619f7a3b965b394bc4052262af30fbd46657331267c022b1f3ea3b68da3a0137cda96b9498ae27bae047587dcde81e65a480a2ad83277abf0e4f900553013e116a5f359b2688630e2a8649c70f87b03380f89f01df9e9c04d4106e5943560b3f54532cf01d22438239cb2c805ccebb03a76122ef4659722300ca5cb91b0e02735a76fae8991ec2142b2082aa66a631781bd4c3a3b321f065d5f8d395550067b406cb7875913e96b520ff49a5a9ce66e5b34bf136b53dfad94bd320c83aef182852eee9499fec8468ebc9fd07ef8befc1252e5a953b0221b81fb8712ae95f3a33bda653c7040833b08c7799308bdb56f71f6e32a8805507408ef6f8aacc79dc854149bf82860610b105d8c64bde134b7f0007bdaf260cf80e5948a2bc104d3a8ac37062c3d3eb9c3ff08cc85a086f4bfcd389593f91cb52887709e0f99d38d5858de631b6685a2607856830e032b440a0f9047a7508fcd111988d52cda24c5c5eb1ed044493a295232133c4dadb6af85d6ce37a44b7d558459d4559091db70b78655193b2c803f02b8e4d744063e163135b9c88371c0b1ce517eab724bfb946b647f448008651b18134e40f82d37e49556403b20f2cf44a9ffb03efe0776847dfa72dea8eca6d90f34bf4b5e4e45f3158fa75b5ca4c14f6d02fd084dd1ee0870ea8c63651be27b64fa2e004e54513de0965563936da15c8a771797e2dc7fa9a490997cd77c2e1bd8328316e24a0e5dba63770ecbcfed1c17f880ab4649852dc3d341ab9b67d03ad6ff2357bde85a189d0912913ca6fff5fef8de1ca5cd8fccba640995cbbf81f1f0dd8f54a96f405686d4d68428f806fe76871ce8c3c52c3c09595dd2c6559c51e7628eaf8ac2259b4b0e517fbc6d40f4c0829f55a12ac2a5e96d88404b64c367f284a382fc7b504f00873eb3d92b1124611214d76ae95bb5cc9314f8ef6ee1d767f91469c66bdc9fad64b74d562e807f5f79b6d020a10b77c8d97d9d2331187c0dea3f2d7f50259bcc8d012ebcc9df9d6e5b675e671ddff0ecf2314b6c19b9f2aa0d1cbe1e0e859d5448d1e49e420ff0a0fc5d3ae7be80195fd5fc5121525405da664b462ce685cbc50611c026780a45f509e654540994744edc2ba8976d6d49f263e305d597ad2d2111a7bbd1633f3c57f091cc42015ec3b46f3573610feb125c2f68db65ba39ecaeb353ef2b3da67bbd3f3b3a0cb96d8007adc77c5b717adf1d8514ca6ec80aa6874d97b3763bd4f4fbb94d1eeca66b859d3f2be9349137e8875342de0c1a3e204e860f0a6d8c749c568f5390b6c76b05c8e3d69a92fba76f8e329b61ea3c4ae6d8af3b9582e3e0d754f5142ab234c05d7c16a05619925780568fc1e54c8c35030f91bc827491267c76fcd0594d7bb22a6e1b3915dec897af73251517b63d22e3f9a654515f4664be5011c96c6b56ba31562466932bd643efcf9caad321fd3337be446f5ad6c1655ef88faa8420732d924b62f73bd876874e85e92b5d3b5f24ff45b1843fd587dbf7c59f54fbd2914c2f3b2e45e9dfe04db9b9212e88c9b02900d4552481c62c8a7b7d6c4f72ff473c1b4cc2deaa5e1ad4338d398d31312fe2ec761edfff90620b30f543ea2a574bac5a8aff77cfc0e4d145c1f7de17ca1468bb064bc45f98f1062b94e82ab809ec98f22326a2f2cb43159da0ac7ef49d3f1f2f3bbb01cb35aaed7fa555113cc5bf713739793e83cbcbd3d04e22fde28e85b91639a9d3e63a3a9f382fe698a0c4df0f0dcbf0b09aeaa1631f0167c24429228fe1df7773dcbf8ebf4868e3493cbca365ff386ff9a4dc08aa969cf5ce8cba4ac493020fb0467e8ef43f58c2ba42d818b701aea51a66a7eacf89a4903122f4408e01da30a3795eb5c04189d879163636e3a38462075b74be9e9587473f5c99d52d64d194f51c1898f43e11622af766da08aa1f29ad723929af9356a86331a0ca02b0a56ef7b5ac7b6ee494d6709af65cca2f9e603d0fe75b352e6c096ab7bf8666500cb8f1fbcadcf54b8fedb171b9ffe1bd31b600fabe03d2aca1e37148f9b46d2c4186a59053fd884f47030a16272cba7e1c4d96792864dbe1c127e4614feb92e063c8f29487a3fcf6f1e58cf4ea6bc38d5278fa6d5ec1e4a35411d811ea68701c867d7ccecde41d42cbcd0965f2b5ab6200cc1e475e1ee0682b87ddd59feb92f18bc9b93f32f6cff0eab2b2498b564539203f875d60ecabd40c0dca7f00af3285b413334bfa1f5038d57b8df9ef97f141f80dfad623a4def5930ccda5414bfb8ae45ac55a5c91013971d1014fe3876c2cd04dc82a540e30bf0e55b5f8cebd9b3d173a11c7693a734c33e09df16f831342a6956bf66dab07cfc32fb3a0da8d7c25fcf072c1a605c519c5e74d42d3a274058f1d487c3b02f166d51490965f253fe4022341ecfcca959a6b8aab861cf5ea965861b661bcede109db39c57439e08539bb519a00c988104e603bc051ab3d84e5d98b391c9724d908260e1d8e0d4adbb52e014e1efa68c57135d0b4eb1bf009790eb2ea67be88c10404017f5595da50f792044d76cc5f9aab0fc5a4976e9a4386f91f42ceff8be61f35e35a8ae07fd3c09f6ed553b88c743bc42879530b0ad024e783453218180e46c14b9329998d97e8cb747f58f3973da0d7fa68d5e6c8c9b5e1e26fd583cd869298a0ba01f32fa5bb9145c11b0545ce685de86db2f53549430fa2b5dd1256d8609ecd3c87964e6c1fb7937e8b388b57037781d366bdc771d706c3b5d6cade5fe9ad126e41d01b35d3d6c3c3a15603b9e0378df37174c68279fdf49e3c6c6942ef276cff65d4cc987bebc36dc300c0db3bf87c49d65ac7dbeed69bbade29551edbeefb3f55d91269b2df758997046f8a27b6aef96a2a6434ba35c16b3fe5aea5a38400864ae4aa0b598932c6481c68267485165cf9f797bba4f5930d258b12355b71e803d0152d1a1f95b64f82488b8f766e103d78fb301b938d96d31535fc97c478201640041a75258bbe49f7b2d5714a623bae61477899dbc7559e555bc769f851c53052a64f9d596d5d4f0df9de0b18f9e7548aa60a780eeb20ee3a16641d243b2808074e202dd60f054b6ae06b6b46110a70587dd7dca814e3aa3f3884b226b1221b8d253484ba05a48d20a1402e362970dc03936491c726c62daa62ce590c911c83a286fad5bdb12837361d05e1413b67adadcaba9b8dd8e2f142d2fa2630a1196cc7a61ef1cefb2a3b3845c8b8eb47d38e93ca11b41806fa8f00251a1ca2cbe94b70c43dbe995df5c8322e0616c483eb7d14d82577378d8c6185121cd6796fb59f2963b093779e73b7a91d2997bd9dbe5b634957a8dc2c6b86f852e1b4e1d04bf3f42d1910f3bc5ee5ec96e8f353f9570a5a92bfcce783208970956b73a22197c28e16f667357565e141b943f2582181f0c356f551ff5d574db25add3fbb250ba11d0ec829a22faf080b21a493c43fd688d6ae4a5182a8fa7fbd44c2135376014337ba9e90c88d02216cc462307452c0895e9a4d45968fb7b43fd9192b2e1a4c7a388e367a939d73173470a49365d2b325601f9031ec2c88e2a188658546035146edc4319e30b01e72cb3a0a76e0a0d4529d3aba694211c0add387d68afa90fdcea35436de4c824ca1296cf418af2ce21e2fb32e6b006c30e623818ddfb57b81d4df408e5769c6fb8b218baf4b335dfe3b40bb6d4bbc003b0759cf96968dfe021bfa1fa17ff431ae03012cc5a752e55fd6e717b05fff7dfac4fbc3b84432ac78a4290f523eaad9cc5225627a29a6cdf07661d17e36ac35334cecbefd085897d140ff57d39a8cdff2646ac399c360e94d894217f6cf5466ed4fce1c98d887dba50d2a2cf281a0406f6e6e56d82231053566d7c19c9c4f96804eb40bfbbe4b6176c7b1c6e69006acc3f82e4ea86393c2c599d93555a98f0200f7abaf383a63d6b99f5f9438870b23041f712f92c167adae7ee90a9135d1fb5490b15edc2cdc7d0f49a85bb078b7a205ff13e8239790b2c1e5d1c409d925dc2a67d4a2653d1fa72cb9f1da7e3c02e05a5b4d221bc92ed8d0e2a009f344cd3b05daf1fe7eab8136b78f667303208c8b70a18e9a64d5421995ddc7d8a57a12b05f76280eaf04302f27d58f262d0761ea9884e84be0952c5d84e120ee1c1e81fb250e17a2ebe3b220273459f42942f58aa5a15720fc9b5ead6d590b5c970d65a3ed6814d6f3dd5746e10464cbb422892b5326ac30589266dd6f18f32001cccf3b9b7a4acd7caa913bedfee89bee20b3e57ef54625bb18de015f00f506c21633f693b86442502c71d80e2e90172ba418c1670e13c72786b4af7d01879180c3e2b725a2a7ed678ec9d88141e05e6f992f7ba00fa339031e88e0c06b6d350a35bb4d990bb1025730c6714ffc03299cd929ebce9d59a904a9773bd930c553d8c66d2577b37d243630be38e9ed026c193c26a4f62d705eefad51d28ab420d43e58fecfa1ae23a03eca2972f19f48fe9a1dceb4d6b19c35ea59438d00ed2cb89920cc03c42db2389ee74c9c37abf84f8681eaaf07038dfe863521d1f5310c1c5b0eca48cf6fb6ffc9ee95bfe0d8f841df593c90f1fb4bda7b487ccae09da99634d63f9b6de2cfff171851f33f9df05c6cbaa8233f2fd739f115c7f2e20b2b23270d4b0789f8d180103f00b7fa37f133b03a8dccda15022c6b1cfd6eb78701646acbf238956bd48d803011b972b8f543e78a63a8416f9287b4c47f628f30a5118f09207d87e932cb53eefcb9e82470fa2bdc59509e6efb386e2c8aa404d358e8213b9f739ebdbffb5c4af84c9774cbce13b765e2a62dceffab8f16e0fc23e4a9b765b0a36cf85efbc2afa70ee4ea6e1c290a106e8ede3232be808a8142ed2e36bba1382ad426e48217bb4f0988363dcdcfff9a9b9de22cb20d8e71163b5e7cb7d62920b8fdd2dd369842ba52164b46f795841a362d3df9704279671e6df21a26eca8f473dc34de14c7cc6082c0cfdc71499fd3b818ea282a91bad1fa28d1cb0343016cc671b786113dc4b59734bad72ee0f5f7c82ec15628e83fe12fbabc6ef2cff88016f938e391464196d71abe2f83283e413d9007bf1c946e17f9d41bbd5dfcef701e8ee353c2054d5ec3a4c030c4bbddc207b80514d7c2c9439e57e9febf833658eabb3699b5c7d5f03f607c56a53ef23ffe38f9b46c67e2ee7eab620287d65686d9547ac44845d118eab11c7f77d4bdf57117c8c7f5fab7d395d43a4ef7e9d1e72ba91f04a298cd70d05ddacd3cbc35f7864a21f2512b72e83b5634d3cde2dd01ef3c369f27bdaffaf8135cb7335348e781d35bcd9e66ab6992cda8873c1e00ea81681028f1871a3733f1bfddf7d99d92adfda1ff01da0350ebe081d78c62ba08d91de3fdc222bb89f915180fe19e5e3695f6c32c93bafa5dc88bd5387468c85b6ef435efd122cb56dacba4583aa7ac4a57c7d0b277aa0403824ac111c62d3c6c884c20b20ea0f51b1d5909c881f518717e11c529843a6741a36c4c56378be6bd0f9389d594b65fa64c96ababcd12ab54663d902386736b3d82155f163b0267caf127da720c6a04a4c0d9a79422bcffc123567c66ac961f299e6bce57a82eec9a1cbcd71c668d184a744f2ab5e8d6f041df17ace850f62f3c97302fce1aa4665673da7f48efac257ff26f96cfb3ca89cc87b1ea4eb990b50b340b66d9ef6eadf05d515529025a37e207cd3654649164f78043983381a04c284363d06a729df1606a5a4511d66c235db1bafc66a28ceb3f7ee6c65c1257fcb8d9bce8f259000596391aa5c4470a846dda48f0d2b5f5e5abef6404227b5a13ed13ef0cc0f7810eefa9040d4185cb21b935d2ee7400545828b0baab638f30ac384335295cd658a82ee41b75058b12b179f76f8ca450e177c4cdceb24425ab0682d61fa490f7b776d4cc37dc4c41ba8644787ef38e2fb995449beba6976fe61d3f40438f95cf56e5b5b80fcccd5b7310858963b21f37dcd72807c99bba996142286dd4858d2810fe5bab45ff19d314ee9c1f070dd210c57f746b3cb8989134ee00fa7d7a2736060d23cf353418c9cb8dc67da3d127c02755ffdcd9d9ca3fd5f825f8b7b3e32b806878198d5e42351e52348fc6fa7c8bdb8dec9af5786fadb7e48b4f7ff91e123bdac8037ddb1fda6ed3eaa340443876107bb001cc56ec295071edcbfdc861bd8db731eae90dddfb4db6d3877de6fb37a8a11e789afcd274b9f03fdc723fabfc17b6c32978fe52ec3598e97dc7e03d29af97aa89daa36ea26bc13c74d9f27301e0bdf097cd10f7cbb7e7c02eca848802af5e621dfda51b590d0f5c10fc5b23de26e025ae048174ebf5c2aafb7befc77396121784ff7a4d306953631c5f4988090e0bd1adfc8ad511800d2fa34b9be2a82e19e2c892cb8dd547747ac8f28fd6e94bad59939c6182ee4d8cf71d3ba0c392a259ed10566bd1c264a89e3f297326b11da05674a985e8a781b1594da269078605c4961fc09ee0d4955c5fe2d302057c6297fd1fbab81652356150ce75b486fe9a9e8d495a631cf47d710f40049cca7831a756276139d45ca29561b5d7a429d03e208a98cd354a4c216c4628e2a066680dd4ac74aba237eac5d319929478faaadcc79fd0360a9b1c30e2c1d50e02f42be699c7043a02f895fbce62739874da90e52a62514fd4a1910e7b7bb3f71ed4772badfc93e34a72b362d11410e41b993de26794edd907d1dd916a45c94d5e1b2da03b05b3ab84218b646acfc6215590d23bfb300ec2aeb77348a830732a617cdf112174eb3eb00a127f4bab5fd8ad076c28300c5d694acda140bd03168b51959f16a61d5ff8afe53b3d34d93dfb83a35a0092e9c6e3685516a6c747be6839b35f810f60e8115a40370d602037a19926760261424ff55db8ee7796e888733f1a386c469dff43e7a1b6f957ee5b16f13eec97e0f8b07d76d7ba7bfc848e65573cd91aeddf229b68ea74b9839e2f2ad9fa0bfe8b797ce8a559f0e366d0e76d1ff581472197f87fd9bf6ca21b66362ec922ed1c8ca2f703760147fdd693b81c4a6ef551db8c0c9cfc4dc3fce79b930a2b962fac2a05ed0244a11e0da5f6e3e07cff81b561513ac5ae847dcab8a21835ad5033f51c2236f2a78c05b0e1641daf3a1a7e08a2a46b3f9e755db9ffe1d63d5edef2c79f1f7427a6f0ac2082889d6b9a6ed92e2067cb91a4c0077fd31154662a61a8e1fa5cb06c1eb77f9657e0d7052fee897325b31f04e88276dff0ca2785f241614a86ebb283106fe3c9586d13a3c708262c3e65f0b03f1c2ab54ad4c95827ef8567f017076d9f3b56d043b6914813abfe9cffff3cfc7ad257ecf7ff6841056e17f9610fa2b61f51593e59f5983bdba83babf076ab2295b4b52228dd1145e740370bdfd0a264e00ec8fd37c5636486192d8bbb01c2ad29f57501aca25c20eba716e27f6e967eac51670dcb9f3890a18f82a035c5723af49bb857089b638c37797ab56a4549596c6dff813e5cf4093190283dc93f13287990756b2c15399836eb7bf5467d1ef7ca527cc7f600551fa0eb01ae9e18ba09c58769f2e36a582767c8cd859f19e7df3a3d54f3ecfbb9308d276a6e7946c3c3f18d5b9a90e97b6a3c15baa1d1a7f609aa4482f79150e5883f221c9707dc7fd53e19cdd6f468778d57f0361231acf4a3e2ba70fb4d555647badbdd8941cacdc1802cfd7a5fb1886269c5e6b6e408dde7b6d8667f7ccf5dfc41a9bb5f76122909db4731fb7d12a204b1a2b86d5f123bc30c89f731a3773d448ca63746a7500c8277e1c28eef7228c46d1893bff61d48ed23897c12f8063ab2a1f0b9832099279f4c5b593e30f47ff56292dff2fc1e205ab09221c20b4a65b62b0f1e3558af788b8fe1051e6f85203130dc02c059470a8483feb1bb04c4543fdbbf2f0bc116a1eff49baada5d3c557cde0f1ced0681b3a4987b1cb4e629d338254d6513e7498d0cd407d59d7c60639fc17dfe692ff03a5dc57faff645096599b31ba3a6f7d4a804bc53df4ca6c701a54ba731b33b01e9dda76c290612aa4fef7468616e7d17d9541855d8a51fe61403a0efdc343b32a05bd0754184ffa81aed286d7a6e9d904c23cd8d3ea064550ac515e9aef1f9d747cf3c488b85f1533382181af4e0f9163b38077eba9b0348483ccbfffd80ac6eff9258699854c363eb6fe26deb7ebc8877385776d930be33b8212f5ae851c55da96fabec8863aba2bf79741f08b3c7058410a8f83fc90f4921f8c95ffb897bdb610782b3c6964804ef5f7f55d0e9abff3c67b2c41c4917a5aa6ffa8b37bf843dbb93ef41def3e2ea3805b50166ec441af8fac2d7772833be5e2487cf4f8c0ba7272283e7a7c645d393916073d3eb03e4aa7d16b89e9332cbd4ae7a335520e87ab16c4a0a6e84ce25f1980dc89e8608007e2512f02ba7c634db9381217bd3fb0ae5c1c89879e1f5a5f8e1c8b875e1f5a5f0e8ec445ef0fac2907472b36ebcad0b7d62e31dc9a6c0f60f2dd9de38ce6c767029ff4ffed138389d0c857f291562da73ceac406754d8a0fd2c3f99da108ddae6381d80ff062a2df0a64fb75a4b1c121133eea2e8d374b7157d85f22be673a761e4cf0fec194b29f0910cf8998e8cf34b3bde8c46301fb9160cddcc2cbe394482703de0a0538f9305062d4fa83918dc3ef3dfcbe2f9a071f4c2f85afc5ace4e41900f7828489d54a9a9647bfb71de2b46edfeec23ef2f1b82d36d1eab7b5c3b50120e7482acf9db203e97398405efbe883c368b331fd15093e9063959d7175085bfa6756e79080f0facb7996b12c4e33743eea5bc65061ec95542e2e2019aaeaec1556e0fbb55af613985e371cfd755d14fb0ec669f6a55dbd47d6dd7ffa1565e56f9023366f8eaf4ba933c2718577a3342036fc7ba6bc9ef0183a5fb66346c4541b18a76804dbef02d414fbebc93c8b7a85da6e164b4cb69e8ffb95f3297b19ecc3e43fd5a65010d9fc9acaaadf0af24cddcbcd68e04748b0c9bfb34fe0b50794442cdf5ae18621ba910697ecaf082ee4170684fed718fd7944e8b97bf727b489d03385cfd3c646a07fca338e7ab7f7e0b9a9fc1a1c9aaf3a5fefb7ea3723ebcfae53ec920f47e4c9bb9792167552f2bf6bff66d9e9939eb7733cf8bb0a38f1d8789c2b95fe26114b4f6bd1848f0514cb70045acdc06dc00f978e5b29f0bdef74d44b0fb455040650c5529de361e65e9a9f2facec8d8fa6002f2811299e75e829f775ffbee697bf27e518a2889830fe1fc8bfe0d391b364836433243ee12977768b40de847f22c111f8f9ea54fbc4f08f847033dbedb13244edbc640e9f0cd200feb5a002a3f11effd65d96bc72ffbff7b26a06fd31c797d76ff496bb1163b89edb75cb908c05a36f1f01c94fea21264012c8b84e00c0eff5ce3078ddb83128c6c91fbe89c29b50336571115ba0f6f4d16a6aa262109772233ede74d2ae8bdb71adc575be15e9c1b662b332b939f4ceeff98b3184cc43ffe5ec0709194f2b04a49b8203832b2327b6228e71578e3c24ac4b5d6a455149b35a84147d609fca8e3c118eac6acdce4d2393bc07ccca17f8105e058d706e4faf6280128f492cb3e24a88c7ac64f4f1f2493070a00313d6e95578aa52e7bfdce12d732d0f86d8e0c2cb2c9b3da7c7c292a2457ba29159f26b44add24e2436d1fb4aa0edcde829cddd7a4b18550eeb49e9b7447f398ddf787dee85f18ed5538698a4439910b6e08069325ad821ed26c58312c01cbfbfe06e1ef3879c13027d78005bf63b01ac75e9b1a0379525eebdb0b7efff54c324ee659099be36834de81f6544cf0a54f1481ee9ff31c4fb32712ae1de288f8eb72e2394ca863ec192b1fb4c25f2a0a1abe769cbbaa38086b27b38e0669294a4edfb99a8a4ade0e5b3d36968c1037b8438a224fba7cf678239400b2bf61158d6ebb3d1b3f1170c9f6cdacbe74b81d4f6e7738483e62417c42c62c78db4bb98d6c2dc215feebdb5ef8638679e50f9cb2d499dc2a58898f7e76b735c88e7b8fbc8305451ef8107c2a456f4fbf4362b11d3c8ba6d40f8dc3f8bc1620293f64325a1138e5ff00d7179e313f970b7862155132bc1c9003a73092bea3933516d315e2079ddb10baf4db83cda8f2034655f573a5c0d8cb7fb907f484d709b3e6550ca53b782a1f80d71c382dbe1899cfe01ea756b05a7c107ac92fd8097ad3ab7046b85e954ad0afe198a71f4ca3812080f97b1f662c481cc67ab75bab126461f371eff141d73ea8b312e745b44d5ba741a6ee0e673fce4564e5c3aa8192920ae08c67f5b24773892196ffc245ddcd270cbc359469abf15073a8e1a45e9d8a167a8bb9badb9dccabb3250248f8f6d070edf9acebd07fd92e5970bdbda681f7d38d1ef0b529df8edfa4789692c69a5918b3732c5ec278014741012b4680ee65efbeef972407393bc7de35c94a8091532150faea6dc89ca12cf51e5ee14a2659ed30b54b4d3f1f9af015130cace19122be06713e12a4512c6c097b5d5aa8359d425be2395caa4d8fb8e638d87ccd8bc878cca65cac208e7bc723aeedf26d8b70ae37d57954cab891a99d8b73deecc7350d05e0f2802e37797cb64bc8e0c79c7e5e14f029b24c1e452356014d8cbdc6f591b3d0f84c9ea689bdc14b21abb3391229f24e0d9c85776b403ea22de61ab2f96921c1b46f6a07752c6b136d51ebde4353beb83a7bc1c33695530857dde6f15c99cca082ebb2a4139333f005ab9adf2d1d3de3af3ab1fad166652affcff514f538f22ff366e0d0a6be2ee51dc7e8b8fdc9f8f9af15de4c9d49ec55a1a99d4803598b7d241a9169d765d1cf1ce3049a1e8e5d3ec10f2570f56880b079e0e2321e68c82ba92e8bfc902b29619305d7330415f3b75d904770d5c50e6fb1c3d65ba611a4013586fcb64d84a325fc0049a989b731365328b9610d9b26a56c26737b090acc4aef542b28267a6ec7a567b76cb8916e7b2f0458914efc5a48c1293480fae3a83a5bd752432ac952bc732b83f99e85777fbc47b8cfbc6c15624b8d64cbd01388d076ee465cc71588abf70311be2618f39cafd99354171cf0a6aa2103af5adb9796e548ae7a3512c83a1c57cc91970d04cb1a56015f10440ab9d78f3d40859fd2291bad3cffae6e3073d68bb4711de30258514c59067bf97d8a542e183972b2ef94f39ae614f2fd90ec59afd3add877b1c5f42558157602f6c9851673c4e1c950e86c988c9f73755c6f0aca24568d705efff09840d7c0a77c01a2b11ee7f321672513c6166891073a2d34a3e943c4e8bc98b13d0d18763bfcb075ca240e264239260f292f23a7d2a807252ecd45b9e8a03503a83e8e49803147338e2adc8fbe54824066e3b6e0995b0dd5a7a6d927a1e333978e0a6059cde947cbddb1633bf2cfdf1a74c4b1b8eb1054d1fc159334b235230710df0aa90aa024e6158c9029c69e4387c20517e1470cab7aa3a54fc79d9b52da0c24904aeaa5820b36d1bc438d8f4e0216ded131f9a14c8ff596af8609bfb4d17219cd154295c030d9a6c96848482cd868e54a32ea18f459e63660ce1b5c0c2662b4167243db2ae6a32c183a4a20f91f3fec5d2940648532683ee9aadc6a573ad42a48cd8488c1e515c11e294c182feceed766c6c521d141024e99bac2ca29c40a27124f625bbd703825d6ba6865bf484d8a6759e8a379cdad8f4d11506d649888f97d9c5935f836244b60dd5932f0112c435361cf80028beb8179728ec36b1c51684bf885d01d1eefcb944f5fe783cd31af472945cff1b8f955289c7f9c3bbf40299cdc8b82c1009ae4f58a18170d064d870e8e713c2aed79f303590c6c7962b1aa4f280efcb3d1c90c8d2c9aaca8acd6b4473ad338bc87140b3a279da721809ba3ab19b7bce28130e7e0d6fc49a2315e8bc2cc4c04d38e7c1c46e19e0a2f3fa4c074b26da0f1e7fdab6d2b170f9f978fbc573660bb82bf3a189caf899644e66831fc0089cc1a720c74fe41e14a4e6be165f3d0b119c323774f5c471d592f5670e9b4303208c25bcdda94a4702dc5ff6f0099e0a165cfcf853d7f80885b68bf9fd69cb4d0fdfd91a0437ce3807c9c6d5af003c3458e91c7b29e03d00d0e9acc6f506393e19dd5e9dabd7a4158127adbddbfbebe41c7582cb34da91e3935607a7ff0f0cdc6802043304ea7b2a2fd7f1b06e22961107719376ca106b6162925df5c401185978b52cc6d606a2fb061f820f7665a138c72e6a300d018d6ff61f6e8ea35313d849120ef0e26930a8015f9bfeb2131d1bbe63ab44746e51aa34b02e844c303b66c4db2f70ec0768a9478070805cd03132eacee36cf9d1aed35357caa118c28d8e69ed484678963deddcafb8bcb1e6db2ef07a1a10b1ad300994703892cbf6f52e0f2c9ead1a5129399a568f16ba5b160436b87a48ecc3e05b6d7d033249dd9d8e731c014512b941543f6a0c9a7f1a5ab9f10bf1a7374d54a8837b9de1c897760d57cc7594e7e71f78608198db6cae7ea1b4aea7ca246bb32328f980ef310af55809f6117847cc4dfcbed1dadcc61ac339e986288ccf2551985d54972c909456593db028672f8d913d640ef3fd0cc861565a7a3e338b6d6d6bdfef5e1ab045d19eb5b8451e78c57d800e4a06aa9c0da802dac932397f3d090fd73ef0852730dc751c0b3cc52a698c03057a9ec4968396edad6e5f06f99a62210b0131547545581c4429d1c6410a7a3c0f37a28ae63f57183c7b746cd0e62fd75ad2755c468e150bbc7a6602fd4d9ae3236f19d9bbc4acb46214c2f27fc03c01768f78d6891e05d1e77d887b11181c982441e556ce508e91ab7fa57bb02ebb77209c7eba473680b7587b9aafc318a48d7aeb96c0ee8ad82bb573b3691cef88aeb90dcc47bf2e1055d8f066bd6b5a0ea16625bc6564045460d67167368ba07b3d9a529837a18d2b3cee50f1e34151d18f0d0651560672d10da88530a0e7c171b92d9b54294aae3a44dc17fdb52eb375b9bd22774486ac6607ecfda71fada41b2c40ac4ad3ff5bdd21e2657231e0d4e7727474238ecb6ebd09e10461c2717fbcb354b13c308aa5139fe76d189a82c03560aa6601b796af77f060e405f8ed5f1cccf3a62eac6b9941feb9c9f4833f6e678a8ad49bcc9140157d9f8c4f9ca9463f095334a831a7cd378fbf686ca6fef96c68669cb9313818ef2f93c100e55566bb82ffa9824c78f8c8264d750f56602c84031ce9daa21aeceeffc59c184094cf138eddad49cc3482c209c23d12c0007f0028cdf78327c5f903275d645bdf8f39569e929b8c56d7340ad01ecd275ac91c5b5758a10c0f27b7c40d91b875d998666d4b264042afb4f2f0a03b9f0f75473217dd92728eb01345af4295546b6a3dd7d6aecf083121e634e3f41f11dcc56384eb7670b5a2d90c0676a2db19f61e320e0a067f25b9aae63e2303db6411e0481dab6a8b138d5be32d55e8c632dbc66685c716101c23c845555507f4d6f3e50fe102bdfd83c87e6025bfa55c688f22fa4e22420fee5e2f3e0722cb56d05c530a2b3ab0abd82173092005d5fbb076f23b3aa74055194a9cb03d0dbd8eee1dce31243d6ee219f4da1f9b4e968dbbcb68ceab51fa1aa3457b23505cc08aefde0ccc00d2ca36c8c00f6594e58ba93a8287d8de362284e56f3a4c5a0913b7e4cae8d2f9aae839c299dda09d7a378d60f970b98bd6979be15661c2d13293d9200184b116bff9efa15e1e8252d50ac18111b9ed7a2410d5bbd942d99b4457d44bffee9d7287044c37b58f0f5aba1c447b5aea04c17e8c03a3074a9cbfd61f970031a4163703ec333d10608aaddaef9cc3e07fc5b5a289fa800e183bb1546d880d0966c4a69d885643765a2cf79cb522cc366af2d4de2b463c1aefce58ca4da9cb4f6f9001682c1f2c1acef84a2451bb92f56ad655964dd9bd1eb2e6fb1a2ef7d3e50a70dca9237031ad77b4c20f2251c3a37142bd62a2c18af988dfdeb63586c660323dd60883ff6ae341e1d0b7218dc82afe96e1a61ce9fd348922f5299f0b511f42df793fcba79da8fe6c0fe697191d53c75f8d02671c921445d90f49da8bc7970a0ab8bc9b4407d722509ea5730fe51a20ff7a0fa4702815c05f8f473fd379a4b4ff4ea26f0e61fe4ec69bcc6a48f31ad17de409284c0cbe5069b20f16f0714a9c7f5198684b13a89e4a1d6a992387abcf710f10917d603f1c1c85d845211ab6ced90a68f862c30482d8f6ce5bed0731f1080639029df59865986bc67e8c62ef718c94ab87ff298ccf16c0e6dadb4bf7bdce4e5893a8fb61637af20b51e86babfe308112c9df0ca47785aa9552d5c34c445f2b33df8c3b324dbecd953511e5aeca5df3cb3096a2de39347554c168c85d018b75e78286b03ec3bfbfa18440da87e270f145d64a6783ae1280b31b3f228703edefe7299c9033a507ad168dd34a73a9bfa4b254fe3af7de218011b4b6a155147599ff985c4d48bb020d1fd58c2ce30ded06433621a2148f72014a01d6eb6c5c8a9a5376bb6618ec8904377c1ebe095c8c74893284f1a3963851c52eac5d51ef6e526eb06f4723c261e403831b1017e87183977509214f357d57e93bc9aad9e820412c84ea6cfca523fa9c913a5190102cb3e8dec39d310429d2b1bf15079bdd019ae2258846d1cb258659dddf34e0952e61d599f67699cbdd6527c04d45c77b8891d3e880aec59effe27df888143457d571c5f8989cb35ff591b0f2b910c86876b8d5c18acf8c9ea219f6e75b9ac33776ecd7419891e7fec12e533aa12b2c2aec95c75165bf411c4be6e8aa4acd016336a7090ae1fe90797b9db8b89735661081fe6a4aba8855e9822e494173c308a6d3868d66cbce61c6ccfccc23211844fd4a5812eb65d89d6f023afd2fddb2e8a449f1e421cb7b9abf50ed6be0a3f8ae20d9f012344482e03b18c5de4082c18abdf2975d8f7cc03f48ac58eb21ce075b5345240ebdbbc22802e9408b20b3e31102530ee72c12574e91326274f8f964887f6f14f885371aa8c7be4ecf74a0bfae7808f2722d5aec180b263aacad01e9c0d96f00dd591a152fa5de6e60a61d9accd1737a2b19f04e310248a563cfcec1430f430b3a39b131558a5e9ad944419f11eb3792acf62d4d3f0fc053d9a221e24ffe8791f0df8bd2d9e891cb048f426e6aecd4f4bfc31c90918eafed0b09a119fd70cd4568188b5cc5d9ac5b604cd4072218f935b5fb9904738338544adf9c5f33173393d5405b57c1f98f33cf39312690142b08a7069c13b1a2db6ababd9dfd20ceb6f3cd492c2cf94ffd9cff787d473e6f6abe4e902ffc42d3fe70499100cb899c11168112f3ca28f115ff5d3631c92d17bb22613304dc66cecbce3e96181614c83eec884f95421c3a2eeee1203ef115833d0a5dda199928d88c212bd1b91a8db325ae487f28654eec7729653db72b7bc68e29b19576d9a073e9fc68db96da7d702a740c902136350d79d285236b25fc04b72575201e5198c753f1b9f5b10b0143992a2705699c980cd9cdec9708e56acd658dc17d3f0419200d98e228ea06b854a5cdf2f7d17a0737f65f4403b38c66e84fed9b8279dcf7e8772ac9b9190320e2b1d7e8e795c08ca9501f8ec8f4191b8bead6a4c85791f657bccd30b69550b82ad5f1fa73735a440bc04ce776cb3c3ba13557380baac219cac0bac74c9ddb428bc8376bd1c10cca230a281e060ea25ca8b6755f8f091b1e77733f60dc2ff46236149452568ee7b3abc579c3adcd78319000415b3ccb6542c8d805050db6287bc0d0c257810094a465f904a919d39a4fd136b74607907169bba27e22c815ac587c7f4803437c9a32c8575cb331be2597a3cafafe21ab121ae9ad70aa77a9496e1c7401bd314a6e78522f848828e9c651346262f16f130b9d94f3068cd1033da7940aa6ae44b13c138ee8a7bbd1e390657356b1d8a8fb71b6a95214e608a505fcc3f433d8084e2e243e0c8e571c4bf4105b568c56701725af51aa5799981b44b661bfe8628f5d0123357051c34637a9cf1b699a0a5101935c2d5df92006ddca153e7cb92fa2bc49d2ee35f8485253c44883450f36c4cdc1f19171e55b8bcd4aa904e7e6ba6f7b11edf2beac682939ce79ec0fd5b874212f2f60520f48d55c1527092c8d64bfd03079076f36a9e2d5267198c5d72a27fea2a6c287f1eef5ac21c718b3e886c8f8d3156731f63feb14dede6105faedba2c2f9162982b3a7eb81e313f444bb20bdb2393ebdc06f57b28957ac74e261188d7819d1352abfce6c0db1fb15ab0c53632a6d0ebc007aeee8f56b7233f4f933abc09dae66306f36606c6933bdd78f2344704f7f7dfc51c9a27ec6378db08f15a72168b487f0bcba05a7c2e46f3f544ace348ea41626650bdb0b58320ffe79da1fe97d97f164dc6491bc374f88a3b3b1e90b732ba374858fc011c9dfd06e2b650e276131c6e855bffa16a20ec01709545c60e791522603d45c361825f1f5b35febfbed1cf64cfde9fbb0edd87a835d3f56395f875ef15e567eaf4021eba0ed13f3abd706aa0bd0353378c7c2f746535e1593bf3da60b8aa3f52b4d9e8a61fae06b6d76ecc34a2510358b00f944d911fc840a202ce98574f01786003dcea10b3e08d505f2fea86950c83e1baa40e7f7770b37b2a6d8c4d472118aec91eec5fc7dcdd4495f462f1a111f443edb8c63b00f1401d2647fe9e88098c062bb11434410f4afa04cdc846af156c224bb1fbfd6bfb9dde2e5690de84fc9bfa0b36c8d9fdfa3e60fa09fedc4f2f4a10c2a4787f40bbff0af60d441e4241617585320339e8e1d58147270f3d7875e0d0cb4307af0e3c7af2d0c1a9078f4e1e3a78f5c0a393831e5e1d7874f2d08357070ebd3c74f0eac0a3270f1de03cddca7523ab802ef656dd46b0fd257490601f527bd928e001201e6d51c08e237916a2e4986b2967dda09134b74974c0777282b1a68f0b0fdec394a022ecef3c9ad4b8bf6099448f2b90c8a01c517d362f1d40f9e1e05f771b12effe7014c3854720e9d1a2b8003d60de4ce4f1413c1a2fb2e1dec4ad4e775b87cfbaf22f822939c5083824b89fdc03ca0462410cd452658cf8600b1bc6ab8581038efb0098259c870c6050a4584a9b29e6f1c9f9ecb972e3a4315c5ac03b832e8c245e897e81360db7613fd2664bd4cc25174788a643394f7cd70c70d4df40cf4d7c4c833421b646d420fa73793c5ab81ea191f559df5a075f23e6c875791dad689208b6e60771242cce546cc8717fc26970f9c3faa71f3353735308a212feb27be89d2786a8d3f3ed585c45d6d7956fde223f7e9103ea4555957a66a1381f2c2a23816e9d36b1aa2a01da9637f4da08ac9f5002f4c703ef27b400fd731ff6b351c0cacd3b50e7a1d04d2c10fd022542bf0fac1fa0c4e8ef02ef07e82948bf516a6a606d04e13f00c7b31eca4068c8f611743f298c266729360d396e4754521282eb0c01b20d49787d49ec940e908ae4bbb46f42095a0a31679ba827999a7741b9530251450072241d2517e8794648610f6ae9c20b67d7722168f87d993fe684e80c6334b48f059f4232ae0c9ba018ae2871ae7026437c6ed47e2c54a62eff39ed1b3c3e8e9e04c63e5d1e482182f9870ae552b9f1cab25ac416ea7da67e5cc8bbdcc766088a441184d8172557948aa1bab9fa4490d73da68ec02d848e79c9ef25a1548101d9a1a32c9d1b941ee73420830a42eabb86b97c78c15ab92dfaa6be3d6ae90a68e4621658d27160c198f90301cbb8fdfc55ea5c1fac15a5a9ba998850c3fb9c748674410501933364ee2bf56b483983557c6ec8876ff319bab301f4e08202c0bfcebbc5c71acec5f89f008bbcb262f204a9f74201657f243546512d76f18bef626d6e7e1b4991e9c44811d12feb1721c31dc0c5c90caa64f24dc69c6c3b79b7e7db991b534423b51eb6bc3e9b1c2239c8a8ba0bf618968ede62ca327b7b523fcec195d7cacd6a7cccfdb3dace15db0033aeb70e58b8d12934ebdeb622fc99470971c881e88230f83278666a5460c91fd4787d7bcbe27609aa2f245324812e803a10744552ec4b16c2f05ecaea5eb8b0865958e37251b14e7e46be1851ad4021e0318e23e5ea53a064bb51d98ed75ebc32899f965a778de9052262767d5a0b4a099c2ec128bb85a99ca8c28e59398b9f73261e52d77ca61eb3ee96ff06ba06a04fa5fe67975b8fb5fc897909be16c77c643729a6f10541c09686fb90b3faa505a2a2463744e275a78c415cbede78f359bf34885cb93d02e71c88dc0403df0f505ad017861277e8d92558a09f038c097cd76667e2076f4868c7dfd6def009f117b3cc0c62c3e5fd96498f5742c1e8302a9159569f10278127eb7d1fbac7c0d830f0f003c3c68661800c035dbb21dc76f074973f7974cb15467aedbe6eb4bb2f2524cfb6d0780a62308678710bab20a5a21c35c8ad404a85725831820008990509c85a00efe962cab0452e7591f52c1fa09b566cd3f00aa508e7f335cc510b6cebdaee0f1dcb46fa13a0f5b2c101e5dbe838fc4f0dbf18ef0820256cb20516cb7d581af0dea69137ee3cf3eaac8d942f59a95d30d13db48a74992de742c95f87a9d5815eca146091f9752e7d030d09f89291e31a4eb6835e9cbae0f2c9b99c3019a880d8eb4bd240c1fb675b99bdd8c30003b08c77906b05d73fb30b585db18da294f6b8cdc2a936f374c3b8d453c1dfc09c2c0afc36fc004f098b71afae8495414732e311862c4ca995f41509ef556b2aba8662bf1dd9d82957d698a1d4190ae59a37ce8790e6d792b134a12370d4a701e80a13754dca8dff9fc44bfefab6aee82cc633369b870e4287312ed2a19ffe3fe806d32592253960824b78a0a0f2b391c21e764683e9f4044a2723396298d571f0723d5580e08a37df9512d18397505bd75bc9261260d63e606777541a4828f467018456afa087def0eb3e5eae990d7758920adb36a0c9eb4d7a33dd24ad1065f452f4ab4dde3304e232b51bbf26702d8fe0d285869517041297c1146036eab13fd1de0e4fc81b788b777f5dd3b6d48a1d5cd8aa734f104b5ea5135af030b512174f45300ec5c8213d47ef1c1616433a8fdf0b0f4b628aea82efdddcd4e6aeb5a8f5f0e0a0cf74865ab2816605c0196dc56af13c8827e71262a58b6e0194da0580a45a3a6cdbada426bc15e24e4dc949ec4d1b055b71bee448265fef07c512735ddc9834268a3f54b43aacb1a389fc9e9195caeafbb23b238c968475ed05d99f3602ddc1b904a9a357042750dffe6f4740220d07a490849edd4619ede6fc40d4ffaa8557f32491b6dd0089fe38a288874312b9d9ba5408b43b3ef855e76287e01deb3f8ceec02fc89099264da1bd7a11c6a7d804056d4cb8889fd149c2ab84fd788d459e11b925ee84a25acdc58b3dedf0642fe3050cd3ee7d5f41fdacf0e42a362aacd96790745b4c1e1df7b8050cf11efa7066acf6bc1b39cefbc0549d604baed94aed1550aec0d80d8bc9ebed200c916dfae771c86fc15cb71eda74c9cf913589dc299d560324cb09b0d4cb334c7d08f18f1d444a2faf793adb353688dfd146297fb1772480c73708f7d5866a5e0e76d4572e7b863bb3ead47b46e8cb163c62eb6f74b8b77ac0bc4c8e6bd47c4727da468c6015f64dd191cede9cedf57fdb60c07d42117c519c454fcf247f19ae40875418964aee44f34049999a832b4df8b61fb8f5f55c9cd6186fc92d8cd6972fe51f26281eecd58b439ec35321d2cbd35c098766e8a67ccaa66a2be4385fcf6be30c33b224275b3224665277d2ef46a32018a22733c0a8e0318d572400c88f78e29d713e27d69e3416774f1d820045bba7aa2f855e86686b86965586b444c4e399bc540983e3f0cce3f96f5a7f094440d516e093d51fbd939a61c190c4c64c34541ed7f20553e30760caf992bb64f56380f8d03d294838083add8170feb6f0386da677829d9568c0f47d6c7b5109e427b1f1b123051fe9f0b128d6fcf85680de2a1f6de63b923a355a33dfb30fb1a3a4f8fb5dda02983d12ff96a02cbad44bea83f51cea25d573941623f6e120de233076dff8a5da1c84813f47e006dfb863efb0954972876bc4839d04732902922dde2d01982aa813d44954bd0d8a09699a8d076ad6f2d00bc941f8a902911e784ef19b30ccf49ebe2c3022f90156537d01f51005abc48e6f1eb0e9dd95e5d39f271d3257da57f2e732d83ccb012e8fcc44f3e67a8d22033322eaa5601bf5782d5a9759341d6e59f2c0fb533dde7172cdb4a853a7aaf083adb584d8cd3d2d9f7be60383c7301953c4c2e6f4df40aeac5631dde66a086055af9fc9a1adb06b893b6d102cb4dbc6ec69a798e65f2d1474083dc6f641b4381dc2737a5393a1d59c032ed759e5fc7155e0c1303270b3246b92ceef389660aef393662e2fbfe7af5e153986fffdbe79bdb1941da7b57b615c85fc9d6bccf4a84e6d9c1078d0df3f06dc76fd31e096a83b892fa6d6a8b8d6995a11bbc048c8272aef267ae8dcaac67333d092573e50bc3f52c3383d71433625921fc0bef5f9501f485ab72047fde140ff3917ae7f711effe9ed022999766d68d7a08ebaf7b1a9e25d21bec15d7f39d38dadd0e80a691b88eedf6808b82102249c129fe7801edcaa9cdc0b6de14607b8ebefff7bf20bc63e74092f32ad78ba150a3a0a62849aea742f7a61342eecccd7953fabe673f51e2a7fac9ab3952982db35114299642750e796854872e049c02d1936f2719383bb58196240334061c332410400a02a44320f423000ad3730bcc5c203500e0688e1b7701e38d7ecbfc71bc4e101460f206079dbf8ceaf698a8ee02516d6abdc85631757cbf1e2e1b02af5c22590be7058050cf7180f86ab080cc331f589e19e8fa32fe0508ce8cd45a998d9c3d7cd198edf8f65dd83a63255b9db679549c3f5d1b5c6b0fd7885d0d0709ce3079d494bc8da3d2ac218a90fea9a14e2557bf9bdc6e366e034522fa1fcba04615390c7a118f29e2b8789b28f4491d0265d58c8bae2c3a817521f54b8ae14c50c18e416b757b6f606b2922d60becc14b69272fd61a6fd201676747e23f5c164a69838395c16f6c99208cc0a801a111951e6581f5498cd62c35cd910218923f3aeaf06cc5b75c12f3b0a4459606f3f85fa60cf8aff4b2c706cf8b0a837ea1e27c4b0cf015e0ef5415d05c55186991ac77c9b24d4778f4a4c79cff1aea65a4557966a2cec46aa501898f20eb640173c4ab59ced5f973c6c5474245dabd2654be52b572881e00dff2e5eaab9ff1a1389fd8bbe70c8d4b53c330e1ed41dbf21f562e10426454d48b3bd52520bdad307bb59f93139289930d1249f14c77ad0ba948130bf1dfece96202d0fd386c5562e1a7d5579c5cac3272033cb247db00340de139d7d6999bafba33978b60a1ffab2a7107ad91d659eeb6c4fa13b5bc7ea8940ba9819b48f908034c87464a0ff5b8e8008de067586b8427da4fe3360f651707a41f5764c7b2f8d7622c72bfa4b3036b03b48c45f12ddafb67334f12c16114820dff55a936a757a08e7bae5debad9a9891b549a3c41ba0cc2eaac44712d601281a410c9ae53ab1a91a50eb0da6ff486d3b0b74a6d4067b1f5c28972b73541bb9f11db74918f5763b5c1cb6edd80016f9770776b9682ec4b004bc226815e88ab04cb90dd124281b9ebadaf87ccdded215614858134a979c8ccb29ad0cf2d00d544abbe49c1e8740a67e913bf1c9a8cb90967748165080d6407729d42d677313a72639071ca25be77587e2f1a6641e15462dcda5f031319c5909a7af5945a37eac217553abcece1502d1d8870bac222d7d5d0a34f6a930ea610b48ddef7437ca5c3767b30efcf45f6de3c00ebeefd9c81dc410e209dd5646f7af7cf447ee3f6e445d2fc7c7e6eeb9e49f35f567149a004f3f921bb823ef59a0614da4ecc8b21ef7f0ceb543fc72cc5eb9c194d3fa2a94feb5de388aab5afde30ca9f64e758fdaf28c46bd0a17180c055dc87818294880f1d72e0e400cd51ff3e451185526febb3cba6a81ffcc1276ee9fdfb68cb60eee38857ea166b5167dac55fd799f220c11489575c2a150704015998a6b46afe987d42c4902e0e6cb27d97f40e0f3cabdbfe824cb85af706d2b79c47302820bb19016cfdcbe05a21384a995f5f663551ce6759eb916b85d808169f9c3de70240c3482647a7cb46c261095c2647d03b9f781aeb44bde59cbe6ca08e27c8373884f0a5d063728eab4bad4c53dafe710b791e3358ab56460c2d73e970c1b4b0d1049ee0a1e9808c1f1031cb2c2103a773d4e03180055de40772625aa2926c50351888bba5adf4a033562f74cc620f52238528ae42f585ad109e6c6567c1b48e18feefc906fe3681c565bfca6692432aa5ccfb19d8e2b1b137f4c90e4f45bcf1c82332048530b0d9f207628ea79a42e2361638712dfe76597efee2a1ee6ad95133534aa500337ba9eba9ca1ff183d7707692be94bf584a6efacc80d1d514d44d15fe18b712f70fa613bc40a66011665494caa3efdbaf486ac247df37951aa98ed7681974fbcfbbfffcf51bc8ab43fc1ff93d5e698db21557b82db26a55d4a5a6de840071721ce3a60816a2a4e037a991488d88de82c8e6de13bbe270ecc975c28a5b8d00b05afb5363fd898a0b0de489f95ea8a4ee194d46a7fd56b142325cb5732f5756d10abb7bfe69379eb10b085919c69ad0b8b6a88aded99acf43e7ea18d943c361fd97140814f415a285db2e29f167862d410f105016d46772c4105128a38e180a7afb0551ed9b0c2db771eb04debdc460f09efed7cedeb989f82102d7c9888e1c68f5935158407990f7182acd6aeecea7b108592ced538be226f529075ba307f7ec79cf0483daddbb39f5a766efb82f1e1f78f2409ee6d4e2aafa0d7c40eba12d1ca3ca3792878d02f16a733cd0efe0c68fcc6064dec587b6a1a43ec22031668a7fd264971309ca9d6ca51d224a5a2c025db9e9e5e774cd89c3e28ad002d51948678256b4aff6c32d5d971039cdf78ff876b16665a62b8f5bc1457dcd17e8c5f9bebddc883bbbd45b8f875d837a93fc208f0cf386b9483d75050d1d23d21d8a2b70f94bc0b59ca44c79c6d7bc3fc5b64e8b261b1371c7460183809e8b6aa7dcec7fa4342c4d79f3b64b1fd37a6fe2d6e03d6dd6bba88b0956fdc98a3a73b95711be37525c09d1aa7ba6f858c3b65bbc67d4e27000d86eb171d2207ff0cb76e52e9f5cf332deeca91dc93d1454d0bec26936ffdf5fa4614f94e49a5e377486734ffa83f29ede77d898ef47eb336237a071c04f3bfdf486b8084d3ba6a4669a264d8591f0ec909b9ad5d57d95bd1be7b7017f350fbe17835f579077c9278e96c58926c800ebec4561449bcd2c5177564182eb42bcb180fd4b7028f5defb077ab41f6296983977118a59ad1bc231cf187e5993a14325fb67b08c74331f364d50311d085123e0e59f58f17879319f8b09ca68f7c5fd408b097995d7a2d2633034d06d6930ea138042744365888dc551592972cf654ce3891a8a81cd80972f3d599bbcd9d744124075b2e86f1bb2b93da23f6481937f09ae6ccc44ad0eef24c9971f86e697d49d7e4b0005049a0a92204a931587f5842e76f34ae30c7add7ad0226a5169f3bb395d042d860273adcd394bf9c8c1eac0b0c1963ccb6d501522aeac11d7346fcc7024a9e33e0c85680eaa7a3ddcece4a7678006dbec64ddbfc87e1f970079c34ccce87efb4201515ad6789f595bf6e337b415f4a04210623bd96eadc5f2145569cceef9ba1190234bc4de87784dee2ac7d6c0cb8eab11b0a0a3122c7c0e55a1a84ba8a58266ea12c1732e22c262c88160a007051a5d0bbe7c6b53ad4fbaf9d17ea6dde68331766a8812b9f9b789ed01a4c8cbfc99325a9c65c7075902892e8fc7bde9dfd9c72cc95aab6b4036162aa2e382dedc8e402ea2c9b4ff1fabc5e4469191a42c0846c942d6387f6c3f60a0f6eaef01415fcbb25e36e5e8516102a28e886fcadae6275d320f4df9a0c998ec1f4db9d8681d28f77ef176d7e547c53bc9642f2de5ba453467b88d574bf9800b16ab93af5103e64b868c8a2a19e9334011bedb51aa3ccd0730d924885db3120cb9e90487cd67115b5ec6a6106fa1f004f40cf91463ea95ac23d389f026589e80b869ca25e1154d12a70f7e0e61a3a0558e466e9b69802569567b42f7f6c5c10119d6c7d6ecea5e25e8c332e7cf86dc944427a827dc04aded01bbddc28af1b9bacfe6edb549884d8e7b5d3f30b2b70c5d2ff416bf9dc56d69d40d28b8a27f821d4cf705be33c65f173710983a1dc0c0c04083430703a30e1d343898328e25fbceb6bb6cb61b916dbd4276a6232222e224b12119257458918888883c634fbdb514b7146d15df19ffcf9cc66f74a23d2f04fd3f136236d0889ba94b8bfebf69480f2280525528d9fe7f29c14585bf481339acfcff12c5202fb11a809b91ff67f2d549e88382018a47ffcf44c5228da11934acacf97f271b31202ddb3c2dc5fd3f934fa3168f9466ec61fb7f0037a4016a5cc960b8f3ffcb6e604cdd03ce9537ffbf6c3345fb624145863a35d912b3e7cfa612ca3a2525ec38fb369570fdbfd37ec2f02bf7cf5470d3b2e0ffb2ed29aa99848d99e0ff97cf5c43022f9f4c044c30e6d725e97bbff0dd6d69f5bd5ff83f86df7e65f2ff5f3e4ec670fedc9c6c9bdc7a1b29be6963c3376dccdfb421e58a3a7c0071ff0004a070f24d1449be8982c83751487e13c50ca6a6a7ff676a346beac1c4644e612e2f35595aaa446992a42a91203db21d3562b4082dedc199a2b6ff6522b5ed5bd7d099f0a642fbdd6bea4ccffd2f95ccf1dd65592bd76b7a6e8e69ea1dcbc17114abad33a4a934950e84e77edbddf0c5beb676afee53c59ab6db6ee510cdb49f1c1bfe2f0fddf11c573b9da1426c76756d84e6f73ad86bba8fced235e06dbf9def8662e9b9c3587aaeedecba4fb1bdaf5363ba306e90a0ff877bf54f96a1b20cfd5f060204e7fdf1336bab70dc87cf9e8ea5dff4fc2ff3e0f9e5f34e0f3c3dc6f430f2ffdc8d736797e1385a85e317b077bb998ee93b1bc77216fb7646a9147e6941eea46df96a6b238839e011eabedeed7803ee80f75e8d86f1379e8defe8dde50862d9f80ea3ca2f35fac6f9dd653b5e56c6c76e5b5b4ba552eeccc6f7948f8965e33b8c936de5fa95636cacadc9ee99aeab52f9c5d6f64cc77da6b3f1cddfbadfef7b4fa5b12d95da340d968def3a5e04e3fdd666b15229944769b8a923b1a903408e24ff8ff8cd1c850f67f397bf4e9bf7fab5c1b8fdf2fbc5def2f41584bb200e3ef53bd37e38a038a4fe1f8e0c9e5f32a775a66b467ecf9dd18693b1f7d559dff75b868dc22190b1f7351aadb5f4b6ccee70acb596d9541a8e2ac276b4eeb7fd62cb14619b8c8e9d6edc693388f6b90b520f61f895b371f6ff6f7d011bdf3ee7bc48dbb1cdedb8db35cd4806d7ef5696dfbbdb50eeace9aafc421bdf3e677cbf7cfb66efb8932ea401b94c53e99bb6bb75a6c7debb8e13a57475ce7b6998aee1c6b7cf9928cd62a987d20a7c8e79b54a937538c2f655a4cdcccccec2387d9de3ce4e8d6f7abebbd5decab696eef37bcb5a6c6c73ac3dd6c9eabe484f076929a7e19796b76ff6658c6d6a8c7de72d4cd7f73ce555dbee9b3b8ced3b7e196f9a9e6eefb8a6e33a4b76b5c657fe46e833cdef9777adbd96b6b6f6809e39235d5bb98dd51a72a754caa58a7fd2b7d7f34ba519ef2a4d36d373648aff97e597f7089178ff6fb5ff22b75820e2f45d87c50f16d97f67043802dc595cfd7f84158356b4715ae738822c6b57e85831f81f813f57245041c84f859597df99b0a92285764acb724d157c1f41c5d43f7f1272ed33bdf040e39d7357a4eb297746f8668ac6ff92f02e76b907727a96deddda4c11f74fc61de9668aa8ffafa2a0f32fa3e8f25fb63596a3e941da4f96b576282c3c0abcffbac72496217e7981c4fb7f02d109caff998e9f27eebe3bc682d617f0ee76f4f944739a9191354d949920f0ff4ebbd7bc6769aacde75890da17a38a6f677c3eed998ec25df1ed868250c2999eb1245c0050b3ce3196daf87c62a98d652102ff7fc03743e035433cff8b3fb1d4c6b542b5c2b4c26897a75a1a7e3e410c028107049b3ed374369486b582f0b6efc173dbbe07c7b0561044897bee8b7534ac15045148c3da6d57bcbb3d6b69586b7f7023cbef9c2cbf5432ee24b33d407a78ea00e9ffaee9be411aee206d069a7db311cf371bd37cb3710cf37fb9bcf2bfb4f27ff77ff7cb2a74759f19e9ffe51b1f95ffe594a594ff6594ff25747e19fb626cfd5f4279e264d9e47fc964c96d77f87fa9e4adff9749fe97487eb1f9c8e7eddaf9388257a91da6a976ef76d0be2e4f5f6a2c67b3d09bf6a3ab73defff2c8ff3f2d2799258c1481b5f5e0ff92c8b26d08dcff977133215dedd8775e0a5906f95f3e2e81d0d539cffe31d3b10f7bd94396b57eff4b1e923b742c73303ab13296f827dcb78e2f1fa321e3f35f0c4b3d9ce3373d87f1eb1dd3311658a4f3b4cecf450b9516c5fedf1a4f9f644d4deda3b6f2f973421fa76fb8a72d3e9fcfa75d3bc9b2586a63536ba875f0dcb6da583e16f7915dedf74a766339dc1b367e3e1bcde05ed32f9f3dc1def6bdbbb1fbd9b3d60eee74759f19ffb67dafa6ed14695ace16ace99bfadaf56e4767f3b467fe5da92f351a957611533967b904f4bd86e5f84a5d16aa3f0af7ed2bd77d91cef6bbdbf0e0fff246a7ce5d9ba1dbfc45cb6a6ffec29d5ad376837377a46b3bb8c35d119e63aad6b6f3e040b93e5910bedf6c0f846f9be6bb6d2d1ddc196978ae6b66993c5d770aef2d773a3afbed7482d6397ee1392f90cf5a48d7f97f671e3de69f2e8ff7eba0b37eebbe483bf91adf3e677cdbfda6d67df599567c7955ee364dc79f7d8e658ea5ef9b96e371cc6bedddaddb176b57692dbd15a4e9784d63c86af9dcb3f47366dcf2fbe5755fec8c341e77c23dda6b2db5db732ce5f34b1da3ebee18f3065b597e7bdda09ac7bffe02eb28703f28de200fac1b84d605def13a393e2848d8c79d73cfd2695ffcf191c39dbfc1f1c7472e1646d7dd316ac7cedeeb6a1394e28aff77be583aeea4f1147b3d3615dcced0f7b66f2d7dc7b3762bcfb494bf52fabe679a6a9d69592ae576b7ab55bac36f1dcb39a552aea511b9542ae5909cbff74bc7c76a3ee6868daf82d9b7af5bdf37ceb906ce393d4ba552ce9d55ba4a4bcbced620a09d95c9b2362be5bd0b72a7965e7cab7c8e7995a6fdaa2dfe7fef9b55bb773260c09f701c1db3abbde67950380fee817b8cdd0fee303d27d85bfcbf732684ffecbef896b57d70a7eb4e61a6fde6e8662a3d68c3bdfb75eceb47ed5941f84a1396df69bfd745fecebdf74b27469de939acbff74bc7a935dd63a9d4e9149313c35eefb8a6adf27b955ae7f86abf78523fe76bdb4ec0b635414167cc48cf8a2f776eefba83d9b5add93618efd7bb6d4dd01a173aa7744efbbd5f98beed3ad67658a6c3b0bc8f3b6d787ebbfd4ef97301e7ce6b56daebeb4d57672ecbaf95bef9adcdcaafd81cd3f2f5e6f43c53e3acf46eecf09c370637e856d37661d2abddab74afce31d68ee7f74a8debbed8e558a692dd576b2c739b1adf762ccb50ee94c2c10dbab9f577308cf3b78f4b6bfa16acc65c2c8c73a7142d4d2b95f2fb85b5f520afdfec181e97762c2d28e54efbede34ea954908bd90cb061fc9eb9932e6f100bbfbd9e69fba55669326b232dc79d6473cc5fc7d8e6853420afcef1b47327ff00d5669f95eb5b6bfdfe9736fe7f09771876a8758875e86418c95051d33090611bc335866ab234175310c69f655c4c01e7630af8334bf331053c8cf3e798cd132b184231c4f1fffc2916f61a5a783b6ca8f5ff855b9eaa85f9936f9ba5eb7beed1fa9ee367e33d187d3e6dc215fffc97059a8409fedfd9a7597f060609fd3be798c3f19b9d09a97196a6c6597af0ee827717ecf2f4ed37ed27cf848a549aafa6e5d7aefb623c13d65ab9995030db9885de5d10af0b0a0636d6f540bab7cb73f21c3470eed9bf39c6ee97c5d67879b2fccea52d3e9f58ea61a05660d43f96dad89cf0a839e1c384da6933763f0e247207a2e7d57ef19e4f6a9cb51b0715a9fd7ab55fbc426acf0aceb5761ab5a3b2fc766a4d579b7e2ffecfc593fde1fd3faf691138f4cc3dfb47fbe541e1a82cc78ad15b4bdfdd8e66df7e8ddefdebec2b70c219807bee8b7c2f52e05969f289e0bb9b7b527c533e0a4dbe3e3eb8265fdb3f967af8eee1f95b9b9536f7447c3d66b727d7dcc3fab7c6dc8eefb7ca677a1ee3d4ea1cd3b0f6adb56f638fbeb5f6c5521be9ea9c076b05a16fad85de8d756fad85daf6ffb25ebeb5f62d73f6d95229bfdfe8fd72cee139ae72fae689525b505a81c16cc78b79ce5ed3734e4169afe9b9d8d9611acb7be74e679c85caf23be7bcb5d93996f66e470f4ab9367b6b7bafd25567af8e41017baf46e93b1aedbd1aa5ab7397ef466d9c853afb75d029add2bcdfcdd174758ea351e74cc773dc59d3552e8db35041c2b7d642ab747cb5d2b222d51e7b3b150c8cd7f15b6ba17980d0398fe70142673aab8dce2ff5b6692c560b5bbbccefd87edf31e9fdf2b7372ee563dc903bed0ec7f4e26b9de76e6ba9b195966f5ab6c7a664f6ebacfb3d776dd74e98e918ee56eb3be8e4cebe8de139eed89aaed63dfb4aa5f6bcadc6755fe4f48dbdb5f4f4e5309febb7cb349de5331d37de357d3b6b3bae5f7ae6d5bc44a99cb4c21dbdbb954ba5d72ef6a02d8d8d5aadc338dcad75c7be579b3bc6771dbf592ec6b7329d0db3e33a8ba5a96261559a4b39dcaf54adcdabb1148e6f3826bbe5583badf37a0dc752389672388ec272ac85e7773bbf373dbf362cf77806e44e397e393cdbb7d6ef4f50ef4e961d63f98deb6e77b8ae0f1a68c7df3cee9c691b439c85beb516ea7c6bedcbaf77cc980708e54ebbc777f4a665dbf9d65a28af692dcc6f5ab6e16e6b63fb7ebb567ec7e6388fb32f972a5269594e6a2b52fb953a673bb7f5bbe8b4e34eeebcb9fd36d134fcffd437d1acff71166af8e78cb32fafbb624d3346ab55dac915edf7ae560579dde1b75fed6ddf73c6d957903bb9d301677c6b7bdb15a3fdb63ae1de6d9a57f9cbfbcd9dd93a3ee89ce0dcef973ad3541ec89dd639ae6f9b4bdbaab756cafbf48d171b3bdf7645b8771bf29b0eb4bee9c0fda603e6371b5012e0e4e5f4ff5e6d876c870f0ed70ed50ed30ed10eff977dffff7476f8bfe4eb75c7c3521b6b7a59e06a8f8eedffa5de1dcb6fad9501ff9779784f727c9ddbcaf3df7bbff0b6effd2feff2ee3a757e6f7bcefb5fda6dbba219dab6efd5a93d68ecf01ca3a5c1794bbab96dadb5df5e0fce741c773af98da931acd593edd8daf9aed43ed817e3ffe5b3ee8af14cf7fd2fe5ee7826ec30ddb78466b1f4dc4dfb2de3e099d0dece6f1f4d0f7679fad27e709f09636cdd01693f452acd77f7be9bf6b37bb793df4e47fb05d674b6b1eecd2bbc52ef176bc77d1dfbfad9f26b977debc16c631d4d0fdabddb59e7d8cfb0f1edeb304ded7433233d2bce845aba9e09ef9aced275c758b0f1edb3eb6099c6ebf2d4f0a6bef4e052023c6b69bb9bf67be5f26eda4f96b576f53d13c27d26fc5fc22ddffa4cfb61a98775b7b575cf6a0501ffff9a0df0966e119e10d89a6259b1b6b7ce71368275e6cd3029f695da6f2c4d754eb5d73bb696953dedec7d5ff9fdd63ccb23c0bdee566b5cdf73af7698bf6129c2e8f8b3a6abd1e8b3a6abdbfab5ed4eed33873bb67738fb320aea35c3feff01032fb04c5f99630fc0986e4cac3506808302188c3d7f06fe0506d6f1afaf2f26333526036968cd05946fb5b15c50c8d98212cf847cb4206dc1d4f3a73d6648019dff99b67b35963b8ca7a9a98086624d336aeda9d6b6e7ba34654d0505b0734c67393d73fb95c7aed4178b33419aff77fa423696e0fe5e6bb1d4465ef779bedeb1616f026b26c0fba7fd982468aa6c2268f3cf64c54113418abf01f0688080eedaab00d29a6080de2bd5ec01d089f58501186300967d460b9a606a2f6bab4c83147c31fa3c09beacbc2cbf54262f089a5f8fff4c765f78ff36060ecfdde986a5afa667a4e5aaaca50df1f87379483bad398dffb7b572b6d6dadaeab20ed8575393c7f59a99887b79414ad1c585e3ad733cc73c9b7deb41c62d2fc0ff67ae687a15e882f35d3ffe9b5d7fcd2eb5e74f788e15f9187fdab573a915f54dae365259584dae0eff2fb7fce5955513975a1da1e616a46f6ec181b4ed17104b3d84fb6c7d3efb627cbf7e36f4b3be99f5182fab9a596aff9fedb75dc38d893d9f512cb5f1f9b4ddec6ebfb10ce5636e6f5c68f33138b434face6e6beaf399b5ddee6ef71ad0ce0b737be342c1e733fabfa49a5a4a5d35ade8fcbf759efbc4a6d5fcff6256ed14ee7756dbedec321af0b66ba75df7b8aedf69b32acdff733ac27f20559175bf52ef175b46ad6d322cb5b1e9e9ac4945a7e9c268361f9b5183eeb92fde735fbca3769486b536dcf3d9ef57ce62fbe01c967a98c5520fbb20ac15b4e9ea9c07c7c704dd6ada4e96df39f99d7b3e0744a37687a37387e1f81a8db36f346b3f9f55b9dbcf271c3596a75ac06c164a0f3e9f09b8fdf2b7d642ddde5afbc25ded172f03169ee3f73ad70c90e56483cd006dffcea613a2973b45bebce9042867b34da72867138096ac758ea1003afc3be1ad6cc7b45f1300fff84ad7d96613a0669314daafd9746d36e535992039676dcde93b66b4630e68ad5f994f6979cae5381b37997a4c6f9bbebef74bd764b26a32d13499379acc6d9329d6640ae0f67badb3749dcdbeb00d954a6f4627878383df7e85d27751f8edd7374a5ba3301cde5bbfd96914eeb6b6c3745f87696c73a9d67c3befdc4ec77aaff26c7c4bf9951a5fc7a4bd57a5d0deabd1deabbd57a36f97b3f10d07e7d6bb5d5eaf46673ab6a7cd37fb77f29733d235f612994bda7f277f9df4dd65eb1cdbfcbde106dd6adaee5298d3b6e7de6d2def869620fdff627c67e39a6a5b4282d5526998cbda6a56eed7999e36be584b5095aa54b2563a40690a252ca5aa495a26a5981427ddcef1f68ef9dca5b2fc46a550de17a330fc4a79959ef297df771e772ec636975fedadc5f2f8c5da559a47ef6e47f94b67e93a6bbf3c3ac7516e4bef6ca752df08d4184bdf2fd94ccf6383dc79777bd196c76898db76170b9b3425c90a6df33ebd6d2d5692a324b5488922e198ebf234b6c672dd1cf749b772bfb1f14cf3be4817890b5218482c20497804486acf49c9b2f46dc7af33cad8676995267c94e11196232fffceedcb48cf63dd7ea9bc5be1ceef17ca9dbdd65aafd6f86a9de379d6d6764c36d3b3fc763876a4e82841a336ff72fc2e4ae199969b93a762d330b137ccd928462339468618a5f9b7ebd8cae1f95564ecf37bcb72ccc8cac85d6465518845658ae8fc3b7b193cbf6363b1758ea9db299772452414c5253ab475da52692c4be92ba3148646e1999e46ed188ea3897aa2a8ed8bfde34e7bdb151121a284675a0e9124a2ebbf8d280e865f4457d04369667a3b1d72a8c43be13ec7348643d2fffb9099212c2363f733a4e40d3532f6ea1c33763e4378ef347475f76d61214076a1c87f9b015da1b69ba6bed92ecff4946fa98cb43c57e8e9bb2c471192f2f2abcd00132244e8e99def203cef9c65ede29cc7e7414ae0f9a50ee21be41444e75f1af7ebcd6199be05310a620bc4471a53f9f6c5daf27bc7811ac7024d017a04c80da0102f754a3999b36fe79767e91aaefb5d7de31ad0d91f3d7f9cfc3ba57597e56ca8dea4642aeb38de78fbd3284e0b0c1d7ddc564cd6030065c9f1a5c89397431cfa0ac8398465115ca01d49e7ada691e211956b19cb8a0151b73607080d0df67690080f912ec0175b0b800c634b9434c262e37a5b9eb1f125080e4309f3890f97f8a4501ea78a22015087b45991aa01c482a4010c4f38afa80d100f226d69e3f5e6e563a846d20b056350d9a928b3117de8a061e2c01b7559a4b3d3e7edefd20a386264ca14b869c1e0e1085d50a64e209311e715330219ccdc353d8542db16e1aef66b03ca51e7ad0db5b1d639a6ce5bfbddbe7287196958a6696a349ac5d6d728563b37bed80ed37397a3db3a8a96a6bdded90eb758f15a38afc3f49c1bd964ee48a459210b2fff2c8eb048c19fd2db7eb38bf0fbff4ba61d3a9872d0f867c271e39fc9468d7fe61d0daa1dafc061f7cf34e3ff9f986430c5f8ff258c141fdc14307ff67b8c3fc3ba600ac1146f6261b0dcf3699d63199e5f3cbb77bb3666a1b9cb51fa8e66b5d17fa617d6587ebb636c33b96851c28b5b82332f340138f0d66ff60168f1b1ed9dc576c69b041459dd15a96f3d8817859218d1e199a046fcdf73c7469289a08c20228553f1afc3bf223dbfeb61c58bb80ad1e61f1a42cbffdb21e2b22e883120b2807802020808a9938f71fe9cbb1c7dce641da6fd3e3022232bfbb0f721ee03da87023ff7ac3d6b397fc2fc0977feb46ba7e020b52fd6fdbd5f3abbee80505a16a49b69b434423be69b3b9f4d8ded39be1bbb1fbcadfb224db5633ac62e67e37a901edcf63db8cf79596c8d67dff6cbb7ed7b7f592cf5f09eb5f45c5e87e9b9bb0bc2315d87e939c32cb6c6b3ed392cf550b06bdffb55a4d27c8f8fff2fbfdd1ee23eea3d9af1278fff2f1c1ab67cbdabd57b570bf7aeb6ec5d2dd8bb5a03efde8edebdffbc7bc779f736f3eecde5dd1bcabb778f77efc877ef0eff4f808ac05494e3a3edc807df73c76669b92f76f8eef36df73b2abf545a6672847a52acddc8d878d4325040c70667872b3832fe96389eb9034180c3f2d1a03b00cc405f04acdb81489a106a819202dc6f72aed42cf21140d31a347f9dd82162328b466114ca58d71c000cc45461b0864a074e12cf9056a1eee3c2485986d5a2194596448118943b492a5413bcc8b386500282ada6c6c4be2969ee16899e329a3abe9c905d3a33950886ce66648000e7529f87bb00902824122c93823d72000bb3249114908211587f20f1b91ae4e32e2612006bd256e3d863d815c84126f6ca580a00832d4017d4083783400ecb0aa36c520ef991bb0168a7190288a4d411d59bbba6239019baa5e89a2b4f9ada0c206069d0150f0a80e492b8e99036eae0f21718c219654b352711cb2322208aeca430ee99617c9b541d122400e90aee2c49893414b2740304e7c8ae6cf9dd19c443a44d64a0649c19780a447740f598613c3f9aac587a5127c5948710443fbc0c18981b073666caac6ca93e4a9162236bc08326e68a2139ae340d727d7d59cdc02cf51af014023482e04218fa1882834e9add88127eb442081273c716b8912791781814c1b82bb280098e4857cc8b072f89404a0a36f0b0246601d5265145d5946405640f2c903f210a6dec31c327e250c4d887ca1d449aae644dbd5b6c2a518579b1d60681db28f5761db5c6d5d8a636467bf5b5a3747586e328a486eda3e356c3d671dbc8b8b8343065e77fb18f9650ebe4ff9db41f7f3e9fcf670226242cc7781da6fdb67def6abf787d0f4a77bff70b266d5bf526c4d2b5dd3f937df76d686736a1ff97ba361a2dffdcfe616b90ffdfaeb576059b4f2ab5ce6e2de4e5172b48d383726f4cf52fa79459aa4993e533c0c9cd52dfaeb7f22bff3365c8fe33d1ff4c3313f67f4c5a1e7de1df46db7645a6d9d2ff9ff17f2645260ccf68d4ef2f4f30b0ae305a77070dfc1b148c469f338ee7cf4f73e7bfacdcaf51b867e04ec59a66b431fc33c54cd7af5a985ab06001cc42b7e5b7ea64e009c193998b95eda6c8261bae4ce71f2dedcc69e63c239b691b43b4c375b701dd002cfa7ffb5d79ea63ac6299ba975163084d0355ae6202bb7652fb22d3fbcf24658afe335df8674afc67b2f0cf54e19f89c23f1322f451d1c510c574e814f654c38de9ec0c0b975496940899065108064e600264fa5b46f1eb8b5a66c8d24c5fd6285d77c7c1ea939f63f895b3bbf505b46dfbdb4423ab5b2fa46275d9e8f617f4ccc4c7b4c7a4b7ece1c26d9e3a77dbeed165201ce2ff1fc3f4ffddfe3feac205ecdab9a54af9ff37eb524338ed971a46ddfbc76a67fef686d5cefce94e8730bd4daf4f57371d73cebd84d65e25effdef04f1c183149254ca3f9c6743ad732cc76f3d28cdfe09e91453de3f13de3fd31d53dd3f13dd3fd35c343a210a75a361fff1187ff231feb4c75e5c207461cbffdb7087836b73434b23bced999e1bb3ebbfc6bada0d4bd77680725fec7c597ab05fefd830cbb5f07dcb2a2639e8568547a85b81ac6c4ef11d7c3ebb9b577d370ff1ddbcbc77f3e0decd6b7b376fecdd3cfe6eded4bb794fefe241faff00514e059cf0c0bc8b47e45d3c16781adec5bbbe7b07e5ddbb20efdeb978f7eec3bb77f1bb7716debd93f0ee5d8277efa0ec2ebd6b47e85d3b38efda9579d7cecabb7640deb583f1ff5153585c5507a6a29aae5ddebb7663efda75bd6b07f5aedd1370a3ea96bc5b17e4ddba15efd63dbe5bb7f86e5df5ddbac477eb0cdfade3130075c0caa572e510bd2b57e75d3934efca759193f2ae1c8f77e560bc2bf7e15d39fa5d390aefcaf1bd93950b1df32ed4cbbbd028ef4225df85c2681690facf72a17cef4223bc0be5ef42a7de850278374ed1bb716ede8dabf26edc24d47dca79f759e3dde7e1ff3f78ab080dbc18fc3f8427d8bb101abd0bc1cebb1068bc0b21f15d0870ef42987ab7cdd0bb6d68de6d93f26e5b8b77db0edf6d6b7bb70dc0bb6c8ede6583f32e1b9677d9a6bccb76e45d361fefb245becb16e25d36c777d9e077d904df65ab7b97edf92e9bd9bb6c08de65ab7a97ede95d36a6771f0c7af7419c771fa879f70198771f54f9ffab2e0052039a040c405d3d11b8b2aaa292729bb2acfd6702633abfb85717d7965616d61593d55203531573916a4aca6d461d80325080c000014f01989c98cc7b8eafff4c00989a9898984ccb2826dc25a62566252625e6a4aec894f4cc4890fe99171e396ac4a8eedb978ff65bc454c44cf44474086e691747c734a4c452a1ff6f9ac3520fff991908f1418298769881988098677ffcfcffb28f5c97693f58a6f1e61869b9cb6de5f9cf8667ad5d1d261626072f817b0746cceeefc912d47931ac2bf07480fee9acd035fe7f49b1d4c3e7b3c35ab94ba558eae1952e4e4a67e5ce011a8b1fd19d1b9beb4136e72417e9b1d4c3117251505c289bff2ee8e1ff33dd71771c9f138665685c8277c25d02a4ff27abbb042eae0429ffcf1b5fac2b018884121c8ef3a7e27f59992b81e9bf5337c04510f4fbcb1b9cf323d48b1e42fba2ff4c3e4efb9e63bcbb0bdabddb6d5fc0b5a59d1b01aa53ed59cbe77710eed6d876c27d6a4770da6f9fbe32b75f46e8f5aee797cf53b139afaeeb14ffea1c639b79b78759e04182ffa7da31d68eb165ee1a8db52b97cccebb6495ef92997982df8ef7d62fe0ff52465d165be33576382f8badf1e0b7e35133f465c0d2c16f0fdcd6f75c07bf746e9a0efc47f2569981ce6ed3329537de599a361430c069733b01039c7395cb2f734e21852dcd94b30cc49065f59f68d900ba8cb46c9a4349df71fe7f397816f6ff6b8b65d4daa6e09a3d525b944af93ce79a2dfb2158afb35dffcb1e4ca8ff9fbe5b76f7cfe7f39f09e79ff9e6ff9b2a5d671495ae3b85addc4cc776bc18d33531e3d2de1d03f474d2454e5b6d48327f88f8bd771d84f8988fd17e1b5c07714c37ffcc36ff4c36ffcc35ff4c354c34ffcc33ff4c334c32ffcc31ff4c31ff4cca7f66987f26987fe6977fa6977f66977f26977fe6967fa6967f66967f26967fe6957fa6957f66957f26957fe6947fa6947f66947f26947fe6937fa61326937fe6122b957cd041bf3f0a79817f785c4992356235fe5fda47e817adbb830a0ad6f545e902a1868084d143689f9489e4c8ff47975abc94fc3fdcbb615afeed595bf308361c6f7cb1bcbfd2deab11a45d2afd5f72856dfddf593e067718886060c606bfbdfe674ede2d90f2ff5aef2ec8e12e809f3fb7774ca5e5bec83bccc7f8f359bf554ed3f70477c1d6ffdfb46cff5341e5db7574a67b349ab57b5cdf485c057c76edbc1bbbab803f671af967166112f9670ef9670af96706f9670261fef867faf867f688e2f1cf941cf2cfdc4123434d57c70408102060c0fcca6def3a865f4e73a81d021c4ebba67b34606a7980928be0edda2960c04c6ff90c7572c1dc9ebafab85f6cfedf8673aba973ec7e4dbdf683b858c1dfffcf10cfdaecdbafcfc13ad72b86ebc5bda2dcaeec124d0733c707074d95969c6bc843f867e2b801008cab6cb9f5e46a1162b2f867aef867aaf867a640d1e313374d07ce8470af93df7fa6096a7c77e83fb34457a407ff99246cdaafcb5a98eefb678ef85f4a1061ff2f13b958852e961cb1b0bf485ab0c2fcb8577c5e847be5e45f50055d9df37ab7abce79d96cac2808cfb41c63f7a363ec7e30dc61186a33763faa9d37d3728c341f4d0f62e9daeeb6633e7a30efa6fd18bb1d4d0f3a13a570fc6ea7bceef05676da63db3b8cf7ebe0d67ef9b85391fa4eb7526ed73495db701f9b69ce9d761dbfd9421a90bfdd86659a91dfdaa91d33dabc33f6fb0dd442f91cf3be5877411e8d466b32b2c0779b4afe9d73cf6619e9998f91f53a5bd354b749c2d16d626178a6e502bbf505acf37ab769e0df8ee90201e5e94bb8956398f6ebd61790f60bbc69bf28b8ee8eb1a033fbf66b59b7be807cdbafb7acad769b903ba55d86e3c6b7afcb70dedd6da9fd7cda5c96dfad1c5bbb6cc361bccbef2dd8f8f639e1be1da3c6357ddbf1b5d77f5c4b15abd2ce4469f56fee7c556ec7f21bd3b49543b9131b77ab58187d63ef97bb0370fcbb03e07f27b5c1bb03ccfe9d73ee80a75931d01520b918dbb5f68ebbf5cdf63d62720500fe3fa3147e3b5ea7c68b751794ba02b2fee5abdd6b3bf7098ffba4049eb57415769fcc9eb61ece730394f9d75667b801403078378085e7cf313637c0981be0e99f3fc7f8734c3b7d3e6d9bf0eeb673903bed991eab69bb9a66b4e92b95d75a1a913b6d5b7bbff59bcdbe5c0b376840dbb19d55a4e92bef731ee7f4ed3669ff5fecdd26be7f04fc7e39b6dbf6eb3639709b18bcbded7bdb3b9eed6d57f4b3b77d8ff6dbf63d184afbb94c595ca6c3dff63d679e533ad352ce7867bb22b52ff22a9dc79d4ebbea804be1bc9edd0e6efb9e54b12bdaf1b553632c3dd3d4fe6ebbcda0a6e1ae755ff1a6eb4b8a6cb837c676cf645ec2f1ffcb10201e65b8f46489d1db3d86f3b6ceedfd3aef4429cd3828adc0bbad85eb975191fa4e9d732cc75746be18c7b21c63c7662d9d4a6e5fb01d68e3dbf77c0ef26e7f780a436988939ead796fad7d29d59400ffad737cf3b1bbb1cf91cd4925fe5f4a77d3b2a094db0ce2addc15695950caafd4fbc5521d399154e6c7a475b7bef3db27e5dba95f994768ff4c0f776327abce312ddbd17beef7dbe57edb6f149ee969f4ee36242590224072f0ff3d1092ecadb5ef5b6b5f4730163070fbffadfccfec20e0ad9114b3ec7bdbd8f86e8ae9f04d318edf14a3758afcff126dcf54c204f1cf4c5bbe58f4ff3efe9914f0781e53e9721be197714bb126a3fe6fe66c3b739a91dd345dc7d11e03fe3333d8bddbf57f66d60961143d6365450cf1b5cdca567dd0d760356adae13f738a76f8cf7cffbf8929fd5fd2e945fb85c4ffb7b5711d1bdfd16aafdb2cfca13662618a347f148bf2313b6cc1231f8b6ea7d12bb57ebb1d75c2797be3b596da040960ac69a8cdffc700988652dcf4cc9b18192abce72e0868d3d9c137ed3777db341ef4a6fddefba52bdccac11dd0fe8bc273b7ab7217745eef98912e8fa6c28c7d96e538b18eee6f4e311a95e5ce5821b1822cc763765f8cad334ddbdc86264a15691acf8ea9f30d776ebf807d84ef2df71a4bf31ed7f7dded44a9b402b7e3f7765a9d9d8f8dd5837567744a63fba5ce5a7a9e395d6719bb9fb40287e7d78e6db8a619e578863aefbee5f77dc7772cbf75c7f66bb62f5ae738c6f29793d156f9e54eeec7257443ee8cceb494cf6f97a9bc4fc9ee99a6da371cd7755fa4e9e82a635dbf54b8fac22f2d3be1296ddf73fcd6375de5807d817876b67745e7f3297da1fcced8bf9b7fb8ff332b4441f8dad631ff5f5e5158b6d9c1b3c3ffdfce13a3e74ffaca08a6ce19bbc3637370ddeb7e675ff88d56eb74d5a96ad281c49ff4cdc7f8738c3fe944fe926e329c4349c78cce817f2ed6b5dbae183607c6fff3fbe58abcb1cf4c42e604629863b6b4b4b3d8deda721ee5c8bf7c3334b434c63730afb1c3f3fb50df8c53a9575f1bee331dcfd92fa0ddbb5d3d1306d672fcda6dbb757e07edaef80e763b705b8de32dfcde7317843b1c09dfef2e779b70cebb1969be5bfb47fbbdf02bb7bdb3d8ba99d00e9ebb20616591ffdf46e1b9db6e20fcffa02cbf735bf8ed81808a549acf694b6519cab9b353e7b7c376b75fea365e845f380f10fa762c9f69b8d7f0f5a6a76174dc49df9dd169c37d7387dc2926cb6fccab313f0babe3ae1593da52bef8600d8d6ffb1e77da7695e6d8cec8ed5a4bdb5a3aaedf45be6e7deb973b6b7afee3dbe96be701429dbdee570ecb32dcdfbeb9c3aeadef9e28c5520fa51578358f4b6b7a4cfa7c4acb94b1d6ef754ec0953243f89880290b9a65e09ff1a00c8fce2ff58908e2a1936958850c8c03688564dcfe9f6bc9c7f0f9e7cf0000089b6386f45ee58401a606ad1839a5600a7a39701afc5f827d899162f75e0db02626c13d777b0a15d3d4a484cbfe9b6ac0f1ec4b8d32f6b9e90cf403a5bc847f26dc33ed67569d5c44119dcee26acce5d949cc9e6aed2b757e6b39a721524c5e8dbe986fff4cb7a70f6008d3b7add5fb6746f8673e21fc33db7e7c019c63b9308b1748ceae7d9776ff41ea9be653acb6a6dada2c5fa6b863731b5bbfbc2fd2d3bc0ed37e72743dae07393cbfcfe714abade7c4c8f15b95de5aacb4f745069d449730bb76d6f4fdd6fd8ed70862b2fd331f34b928696ee1d3dc9265aa2d7d34b548fd9a542cfc77199efb1ccb73bc4c73dc36322e6a6fbbce90a567ac2286f86aa580786858483828183801d0af8f6f4f0fefceae8e6e4e0e7ac94103b130060b14244000f675897b755da29a92729b972efdd24296abbff496b0307e1a9d9adc3f33ed7fc9c644fb679e99c173cb67f5af0c0ae7d94e26d998837fb8a6196dfb0584fb4dfb5def1710ab9db36b5bb305990de057ae6daddbaf1cfccac9755d47a56519facf14fb6732553bdf3f334cc08166151819a6445599faffafc1b433e71919b52f229902e65f9e92776bb1b5136bbf8cd4b74af694c2e5a5d16f4a5193d2f4fc19a50d7fd278fe8c62e1f9334a187f22e24f2852f8134a877ffe64f04d2880cf9f50b6fef9f3091ffe7c32e49f3f6ffe6c3e697bd2c49f4ecaf0e7fb4d27d2ff7732c69f4db4d8cd2625f8b3c2379b2460428889947ffe6c32195cd26109dca1f2d0dd9b298c5f260528e2944172aa190ae63028e38026a6ed90c6a3684c87665d143d22b2d06884057364411f8ba7aa568f2c69dc5caa0096f796a752cf06fb4d95561921cd1a544f02c04e8c00ea62db66924a425053cdc41d20ba58992288048c2d3e145279539e4c69ac591386451d2054aa7cf4d0b681d142eb88562bdb565128dd2d0f8feacc09c9336cd1e04ad81d3010b624c52962402030248516630c28b44a2e564e2a9575c2b88da87c8c16c9c0a5eeeaa2a034c267077547b31216218333390a714dde764f1a3be040301a11168014a3c1ea8a981a4a472c2e124512ebf898d166ea56aa020561d29dde1a434b366c6d179f744dbdec6101a96e4e800424264cd8954352754ca2fa462221b43d81e54522aeeec5a50592c591b5c4d142d38c90934995577c2e702d52424581324f0885c2476576a402f1e1c365111d9ee493105e9d8b144cbce8f0989ba460a17fc14ece9320472858907d8972480dad691b764cb13144e9620a86a3c0a7cf95d2c8529f22730ab0d028c0333d806f435e3cbad4f585664202b52cac4018b028c511ca6249068e0d8020149aca43a2c494183327d2d0dc2a32842625887222cb7566b8a7304673ea68449482b2a8dc4dc58034330035e8e2a949d69c28aa77cd629d4a5a0664a8039564819724aea905f565035c8307a325cb3e0958c4d7bab0e169cc93454cc2c30d2464f4843d559f33491e9435953567aa14d080f837433068e244b202e5901f7642208ee8f05b4e40080da68131208707ecd4e8c77f9562144473167464d0e00410400bc0ffe62130886820f754508a8c6bbae3feb798543abaa26cdc43d6a4862495ffaf6ad4e8058fa4f40916b1bb26948992d8b2b0fc3c223308c182e4ff7351c51275b1f0238b91298d5eab0f2418e1d0e3aa824d6ae0c43dc3a641158f255d1fa62522ada4cc19c65a30531a391ddc4868b65c0f94d8d623f7d661fa3e3820c10250579a1b82e03c3235c2f1137715640f989d181cd22322a4a43c790260ae6a82a5c94bc5a1124f4a12048a91844384209922d09c361dcef048f2f3e6ebb108833f655eae07b312587132f0b11462f9276eea08a328684c6a535034180b6346c2c86a9cdbc6c9e1849331c3e4d173d4210a111aa73e372eb2f854ea3904489584abf1498ab8433ed2166f5a5bb0563f4a35431170249b5db66c783a6c29365aa05665c54b57a4198e3e7159307ae02d027225a39ccb4cb77a13c24d83a18457dc63fdd4ffffffffffffbf6ba42a451779328b80c3487b6a2168f0b022a453c6e8ee8d213249d8742c816019c3e54030422244338236dc0020e2c301e7a31b610ac0221cce8405df6367c88384aa1213c7a9cd8ab03b6881b600fb6ed099b0f5f88767624993aa4b3ea8f0cb64d39b6696a8283c70c050144c5797151e5ebcaaa05c888cb86013e85114204114c7ac31d052d3e54ac6127cb44058443959e192a139bd033b0d78b2420c892d86a6b54824a8aacc780ba062aa484320432f22e618016935802c9097ad92246bcf9127416010ea8992b482c1113fdabeec19be1625b69e20a18270b6a486d49534b3bcf34410823d58592436705d926295f772863ee0c93977a260205993850d940a49b145305d8404284e4d9143820eb3e6ec29ad0d63102104c060019a20b63705cc15bd3831312201cf48893a439b826670eb81df15b292239a87b4a9370ed7393104737a98391ee164f9ea80c577474e70913c2507041d1d284381ee78495ab98010dd6535135449b9457e184a7981525423632cab0745059941458244b97ae23a7d99142545227c06e7270620dca5a7b004a32310fabef2b642f2f90352aa6f914a6f52481fe2c86e9b20419033579c8d8d1c866a2448a5c1b1c292a5f9d025f6e481eec8971570fe0c17de96ae871b0708e0184237b677df614ddfde183f5691503f2a09e16248430b0f273c3aaeece0ba5dbfb72693421b1089c1a30106ebe45e5164fd3838d0862e8434137d6c4a60c544d2e7f5848d1814635c71512a0020dbea8288c7ea03af4aa1470922fb9c81696c4aba1a8b0363407b535994c342c21681aa461d6c808f22d7a80934d5220d6a0fd456d5972f0ad6b4014650119f705873434d7345841b6cd03841134b574d7a0f0f2a55a2311f0034bb60f154d24145868e092b5af31bd88842b53cc4370037e0d84c3a6826f0b3f6a2119e236fc020f010806eef06d5ecc505d2a2c3387d5126df3caa0b1eb2c4086912c5acec000f0533b6372840018bc2f29426700357ec25ca86c9e5d512375385503459b97e7859eb73c17079c18e5e5658a12f1dfa18683477e109a12d5062d48942847123cb226c8a9ed65993482e5791293a12173e268afee0e061893f2a044285850212da9d3b497880180349646dcc8f829721471266bcb96182cf152ea90f8c2f22a946887fb0f8d9f2c0e9086decec448e07578a94bc48243380a0d2044b94b7427420bcca2c3866e1a9e043448aa41bb8c6f8210559a30c20f61049203af2a5b03791c455e24b804b2091f343b0049ca2ad13405d77dcdf5b11392dfaae18fc925c8181493d0d391b7182d2469a572e03194d55683566cd1b2032c72d21b62f0c348e916384ebca0833d3b63819facc7894a6b2528b4257c61d974825f1a76c83243d232e38565ab935016f2b319e4917a6dc60e0080b051701aa1f2d0685407c3eb82e132dda61c85d5068c49c34b4285ea6cb255bdb5ca32a254300b5b874a93f71f40015e134a868cb021b30281c2e49008bf3d5c2cf9d2067930a532326072e91d0867132a98c90a2c4aab0108bba5e5c8a0521f4803009909391169320657eda2ecd0d791a40e9a8e7b9a4f927a4cea13d3b4ee40170bac267064e42e5a3851b453a24e4397678b2e45f91a35560510f0c1b2278ae951c12945f6842d41f3186acd0e59921e00708469d872148f565058322a185383fb01e54a6e2a658b3303810e1d4a009e06b64ef5a03f0cf96a80f15d210c851e2be1138f0155d131967aa47a24b84d458a82ad3b7265094306b07b0c820e1000840932d0eefdd0f05539881515e7d071231b1488406471930335ef0b8a71870c14274b6a139109720bd359a50836506e80a284664a036f025477c9593755e906020c0a8344a90b741c2223c050c0ba590acb568e424ce832386fe40920340917155838b231bec4008440c3c1c9110f02342979634270704bde8f417bd3bac8c013da9360e6842110eaf3ec7af440d0e25fcae9cc2e0497a8e818440b00659ee609876056002f9068554a2030b8a3ca1dfd04972bda05911040aa145228d0d96743f28b6ac16996a4058080cc1cab5c42c52b23d5c8221fcf840aad95ec9707952d606c1823b88b6c4ef4c8f417938202e790381404e552009893d3eec058973c78c2bc51e4269b67cb0582300662732b2905202445b809a9a7e207858d0884c1685078933aadc265355344c9e6c93373c88ba30a23447468c39ad4753e294c16300718c912237f7f109a3e9d8268c177832b48265d551a8f891832a8e09b4b00303b046bc12e10c6530e045eb6fd00626ae1901547fd45614313041add95c5456b6a745c111251de234c00bdc4255484b9ad4a5a40c6b8bc7815b8e3543d624814853fc5878dea18a110c6c2efba28c90789c2a8bf4838c8ec436527a9269a62ae30a58112019b024a251de15575e535601a625774e386a6e6050dfb890e0465dd4dd873f0750e28c310447088facca3c05a610a77a1cfd887c2bb2428d8937450d3f2cbaca0d277d4efaa8d0440c9a72284b0f2f32453ef078bfa4143830a80e514c7242ea8e5e8b1f749cd06963d1282936d707e5ea0dd70328293d52764d0a29d3607c25f02ac007440b1a72685c49c71ce3b66258a92bd49ae7a5123b0eb86995b9ee4de17325e78e10930503183604c0b27cb83024b12b7046d863595d23093383e8a9a3c0db5da463db970385e2209e260a72c45964c24ac9082b1e4d86ec1a5cdd39040030abc88543480e1376626f9cecb01204e6478a11ae07405cb464e42c48b367566465c19b365660d3455270699488d8d24ba268a682724bd0a42470a042d009424dac504769c9d375ebf4fd112025e08fa52c4402570c7da99a018387948c6199b792e421bf231816b7deb662a45822cbe20829c001d8e117a96610b94b4a079d19932b145069c4a861ac2a2a80021a235e5c99b12489181337888fb4603c19b234a46db2928e2c465e2794689b6e226244c510c85d1fd2c0790105491e158a2092a02c92ef1620100125e507e29c2c439153602577878726ab815533d9c0283759b61d4363336290c9b2a8435c358d6ac01b554924ca150d70491049013968c9e28b44586050c1a41ca80a841ae11d4aa2b25a6e18389991e2092f924d066ecf99a914606332f0b8420fa8c096060b140e2e58491c40420d181b534611002f855ccc298a32a0c11906a682252d567337a63460609c8a8a1389c9234596b25c531ee2d8a8a3c2a28a6af47a72084b8104883d00bf9282178541190c7c281e6141961dc067492091148dbb0e1c3008f8b68f55a7331d84305e6d7041e5f768d26bc61c0e253e7035835d3a1ed8e8f1640603c835510c3d8800202205a6870359b2a111890f0d3d8010f171c26a908d2168b8ec54c9e1a380e26faa80cce5c78320b30f218ec83b199c60710e90709544b1c98386d0831c1441acb80943e4899505aa2739730df44c7046ca0420c000e34acbda8b62d3c8461f473512500122357025209a0585d87640234bdb5cbd4ca2528586d009200a66e461badb0af8e0e849640bc49f237e73a5387458867b7b9a88116caec9969900e08290312c2a59cc80896daaee0a5cea7650265c492b9396c7479ba705278d04381a543f631db2c46d25bab8d1b47e6040c20b8e9e9386b144938a061e545ba64bae7fa008acf112034395589609adb32a895db89a1561b454bcdc68f23a88ba44955583d187486e446050c903b855850a4ece1b97c14714146c2205aaf10381a0ac1591912257140032534282dfc0189fa81a5937241d86f9d3df6ec07578723b3095e595836428521e38154c283e604f0aeb108417511a48415c6126d76248813a6fa2d640500a746cc206c98ed80d014f03844cc9f19964c592027844e5644946cf002096a08e67dc1d2901ee395f4cb8adc81f5e434450aab10a2220b01948ce8c0f2766a4c408e6f88024cc17367b6762b48c98f057558684d843611b058fcb929b99903e715d825c04d93abb5290a500928c9b1a3b9152813784a8d191c4cacc70dee1c4e420eb8a815c0b304e14e850c2ead98501d1284fd799d4033612896044288103ce165e84e8da995cea71e2881290b56980bd3b575756320c9915a66a55ce7ca062a2abd20e179706425c143db708e2822076504d1103888bb523bd81a7a04d29fcaac2adea90b07a4a03a9efed0f07ab91226280b8da04da2203e08154c51b85ca80e164aa400f41d3d21e28a5430b734a58da4274c5d0afb06a030e57c37cbb8a6f3fc6081df91ab4d5356795a5d7b4723260a08d011c0cd5a68b4086b2b31e122a65d8dcd51fb90e548f7e602860a20e22252a688277bea4b8965645081693834a3400c202a8329021a0a658631d878c13437d1918278c98509988caae641109d7430c8f19370afef8e13b9295a64cdaeda7e70471a947bf77eec089e2c241324c8921255124cf88c8e160d09fa9455e25d0ea34b0d0c7049341d3a29f3c59657f3048807133ea1a24a7e40b0cc91a5189887a58ad88b97a2128008986473ed229ad4c666964d205290cca646559e06ac47211a612972a490faa6759b8e4a204955e8fb695d07048e972098b139098324304a4244dc249e0b1c89143659d0990571885b98a64d887e64416d1ab0c0c2075c83244f2623bf235872d405bb34c4133d8788651ea4319b53109187c917cd3e60b06190f03aea81ca0948e9c451160408c58244b6f5fd763d4d500674302321322dc8d909124a50c54515461f5abbce47e0923e367a5c94a4d03bbec16b01458de2cd696c01401707db9e11448a4586648c5e0812b460080716fc4019eaaf4546a743568a2f2c41e1c5672737863b66e5ca92fc4937c64384070b96db228f9e03279c65193551402930fd78197c83a7e102797885c56dc00d1c3ae0aca9327545144564b25696872f009385b30158c4214425b5cb624585a53e9c88a26962b8d1a992c6a116982904b7376526cab9cd8baf28e9245c620cd4b53956533b4a83ca1493103070c2a163236c82150e5b13958e7218a9d2d74546cb11cc0bb2374521129d4a3a0b216540287ec45166283ad96d4a48d247c50807240b53dc273486b1545680fcf111626264cc3f6d8201483481915830b70e02c520442a5c996072155a41cb3323c6071c0cc8e8558744f8f261138885123b860c51bc6b0b6730cd5ed0d60d03635a4ded1a54e142f3881812a739459e284b586c59d133709fe9cb1fee0ab9350e72e0a418d146a00b045e57930498cdc09e29b1ae38008805d08375871902ac8c0e273078b6d0721271a0b06445b3ca835485eb0f21241e2cf135c598a47da1007b24a68734835b911f50575a46411a94d6463e47db8920c1cdb908db1e04b108d8332546eece9a2ac46d151800a4d640817475902c9cd917a8b43e3128691883102b672d81289117355205282bd4d24c40d051cc02f86dc22741d70f207783837092039033f8ac9e8b962d22c71c1463746485fdca5ac1e5e50568f74751c7da1aa3481b780cba317640d578542aa7bb1c22390eb93153572008e7011a9cc5cc54422fbf366d87266e060d5a10a715c28316e5f0866de5c997e026d19418b5a542586e19412d03e3926491f4d4197f46810be15b2691496264f8eae5ad5b370c1614831594291dd54c1859b0e5a979c7c5600287b63a4ecc0979af1c49d2607483f385ddefc8ca914cfb19e805286ea8e851e2b181990425af11208c5c21cb40cd3c8a02d899292c341a3aa1aca2b3a9a551d2e5c4252c49e46d84ae7b816415059e08b596c10463dc7184a11e4d326415693301874074183798f85825ced08188b4a12c9c217252eee2386a64a2c4f7cb13235e75193475b519a71e21a459685181d64b98d2419ede153f9e7e5a73e5cdb348ada8b13a206140d20919c0e05d47810913575014d9f2c8103c2cc137814803c7ea4588b20ee942d50b1cdeb739d0a40210ac9ab4d824a4988ccdee0b8c0054b80d92347527c663859a8ec1ab1a50a843f20586c48baaa63250c230241c2ea06017cb07042a57851398894088a0593154e4ca4a823ad5920858df0f88008169a1a666e1e85a183e0f00aef44053a41e91c074802da296cc69c5d142615515ddb06a316fc04c3ee1e4170214d5af5bd08d4aad450b228d298a4105650de70f129f25250072eb3478d101470fd48c1e7fa270cb3f304906b8577b33b020ed496483a2f07b5aa3350424240acc256b8191a496def2cb73ca831623db3a568c90ca8faf0b0700bb5875d15748b6b85c4521c8091ed419b9d3a453381b0aa72d8c0c294470880290c5525982dd4e856dd121b42ab881d9d94397c8d19acb0358d703e750b3d45418b4c1c207347d5a38a2a10178bb51c4e7d94280a62b99a4340eb06155ec4d484b7636b44d8911d4d334d689797551e3d47a819d8c491412872aa163d481d39150eadf8e1a109f3a30e5056a32c513100153907bd7398a6a8309c46c5998121317046a22057981e4148853aa80e54a26324e44d4d081f3b0b9023b92e4b90928c3335053ca8a9c3228f0fb500855137649d3b6042d42068b02de4f421644cb5c841583868c217c54657d7bb0e40aa6041692a321964c103308a2e7c4094a26910d5e422c8c89f377adc0c353419e978d030559506a4308f193964b0886edcb0e111074b4bcb8a491bd0091bf094ecede19df8f39a2a6162a8450d903a72e85ce8ca9adc23950309bd080744802243c45059936612ea4a441681c1153ec4c408a4244622c49b665710e90a31267c552409a99216a85aa0528a01a5433907e8468f43dc7c863b944e568926d1512ff614cd6133206e852195005c861cf5da48e93ae4224c99344b0a0d31e13e29307b59244558c50a1e26ad66a2d06afa7762b10c8e830693574c0496468c703df760a5aa9c596b64d8c564864b550c97ee4c823142699e29853517690dbfaeaacae511360c53b2184c1ea82165e101640bc5872b117556e99219527f500e9d806abe39fb13dc93e2982dc9cd358450f2aaf82af908eb6aeb72024b802abb4108e668e17923a048011846586c89397080105ad39417306034c471e805d69ab05a9d305185295b424806626e04e962d016288f26adb649398a34f2d16342e75190252e04d4359972c89d40b1024992315994ce497564a8e842bc2073d6142a58e1e1f8a462059715034814488e50881419110347cc358781a81c245c4ce2cb2b9c61e40203c83cd3b0292f8118a97855987c93b1b62411910a132c4708dc0c118236836f52172e274ac4a251102abb892b071d915c888a220d61cd99c2dbb33488cb8e116a0994d06b62928158142e580ad2e4ceba858c0e0ae6fd64f552e2080ba0140bba642b78a44b734007a933361e28723b20066a501bbe1a8282445e7189f382848c5495c02062a25a3aa05a97a2348de440c229c200504314251b7e40a50cb9d418b242088643177d8167fe1610b9226b12e732994067080a1e211ef42012ec82c0a3ac2ef491d100a6a1665a61f2eaee713200eac61fa49c0d773ed4d9ee6db1c2c1a3800b39048913ba665061bcd1b1840b84983f286b26d58054dfdc1c0d8c4874eb44356be68c48fe89faa92b83674bb2826a6467d74686892fb63b6f2450888cb4b586c90c3826ac388845bc3173e06c8dcd654aa2e34f21d58daf4541f0886dd8325715e2c6081e0e6e2c05652369a4d9c9f0624b01976bdea2072ebd80132c719506203567d4f04026a889c58101224780445056a1aac421cf168e11b593a8905a0f214f415a8f77e2bcb942254a2592ab07321304fc683a09005a500983891fb2e5042f374800aaed0a3d662920dcd921050aa4fae6af44d61b4536ac24c133241124104a8b0a0418a2c03c927c5248c503276116e5b51d3e009b82f4a4c49e2ba2106f24445a23e63923590ff40cda9a1b03e2d828835d05b4911135ed5b9388c8a43f328ab9181e9c12d89972cc7387840aab460d1b5c46a0ff7fb08686176d347b3edd1888dc1816dc187318dc3409424334a6820338cbb4aba241517f9024aafd51f0ff7e84fc18319461c07c1f457d1089399dce30feff84ffccffaf0b046c5b1314e463a7d77f6a0fb4f64dcbf64dcb827cd4f6f08866b3eff309e16c6d0dadb6a3d9bf3d4f76edbce3691bda59d4d6eae9f0ff01a47ac4fe9d0aac737cbda375cfb6a19d113e51c913fdffe793d774350f8d8f61bb7646cbec379ab55d178f92b76b271d67f967c272e5dfeff5abce3159b5abbf7b27cdff770e79ca2c1231885292c3a4062fb7c93b8dc322659f310822c538c83d27cc1fe250e160a081169f2616911bde0ceed94a53cbd9d0a3c89f5e5c053d1bb075f68afc0ba60d6c7cdc4abc16c706a6ad4a106a3c5202790606a505b1147c9034189d947cbd0d80a4ba50d4923523026419e064353211a4c748060aa5d88cc23c7a1960044c97066d8f914f2614d10c4a7a441422677745c2d16302116774383a4535205224b064521b9f062a747740385061a74104931fa689b7351a00d10c5a7bce98a321cb4fddd723c40b8e7d4434698ddd71705cc2f6e01181dc1d35a038216d3b423bda0988d081cc308563539252f5c464764b472dcc0046c7be6d90c81d115c50be6d6b037c654324cfb66e7aa438dc691fb601b4e4ca5870b46c5cf3b896d5bb606dccf1a244932266401aad449e62838ba434a215a2fc8ae21289d1525e9cda1f1d1618299f35ba10daa084f1c5740ac715b4058c51a641b24920b28c6168165194e1a0b0b8288e831e2920a916b36cb983c1f1adb268c2dac710a55fd9e2a060d7a1245c8a452eb7e1089fa54aad20a64a7aa6c624d23a4163c98ff1498bb643904ec005d6aad5f3d1e60e6012025aaabf3a1cec2c6e779e024bd0b85ca2de46ee3c08524343c7ad5d7386f076bcbaa1da0c751c514edd040e6dc9300c32c2ae3cdc63641bd864f3c003052ccc9dc6966d5c69aa073dd80ff498a5a9b068c666faa9b01fa3046a337a1a06493a3352cf98c59880c1c8443dcd1280c79ca3363599a5fac68e4482191e59e25285a5824e862e686a761cdfecc9b0020a954a2d1015331cba283b834e1566403044c196afd3cc1eb2e91b6a300265a7882da0ea5264301b820fa2c351622e0b98c5ea62a503a5050e341f1c32cc60abcad04a20eb322470df68c7e9d313271030e83aad6703451c8b74941e1886ac408ecd739abe2025bd77662038fecc98e481c39c412ff00e256b8d394291ae34b29ac5ecf4b584cb092e05b06ab2581c8355555cecf183ad12312246ee6f1016edabf079c34620576222888424854d0e0346761149c5465b183572a293c42e2b0a7c901974d9f080c0db9ae22226990b34dc40118b6209420e67688a5514051969300a2b3453c40068d0b24d848a01189a1c522363fbf18481431f8e87e86463c8c330152391a32c2c10e319447195a4ab8c8bf03b06cc3c13c5f79fab376fa614e23829782627fd92c403b6581d8fe82db9bab18a9874c50d852b0f2f1d534b747857c5ebdaa0a7b13130de5c9725d8183e314a59cb407525129260905546062614087199b0be8c562441a0d7c63a4605b20f00ba115547936608c290a3adca657ac750152591aa2a305ce213f5c0a26a58a20c1426670a54c1d854426241e0ad1bf2c2426143e3dba56075aab348803a88bb10518cc210ea7e70d8f80dda53fdea8816ce1c8b3978cf9547781e14780027918158b582a710ded64d059a356984e9e0a8b1b54762d71dd438780a0fe5e22c517f3469b4bb6145a36659bc504c22620bf5c921b0ae4f0f3454006843d8040102324d33bd01d675a9cfb79976277d6a847822d3b37712259b474dd40abc609e73632c2c54be2d28d7ae48806c7c2d307a177069a380a6d445aa686d3104e20ca6f48a9484dfa493048325d2402085287604329272b931ac2313a550f44f9d3b8a02a289a24dacfa1c7a366110dd90c2c755e2a03a8a97fdb0e563a542f4c21d3cc2aa3f861c2e10712ad238c52d73e1064048269d537921ee820049ac939ed74d6cd3c1b0ed49963651b23a499688c44b2286373c7831f86013d50c139f4c76cd9ebdb530e64e962037b08285181cf0aa40e73c5a78d124639cc933b3e0a0af8223260b80a0f0b70a5b70e8097af133a3420ab2b9291106a4a85058018c022139530015c874508842a5c5f6986003850f6814fa2858f520e4654a5202a21e4424888d88735b10a1d60ee11cfa38a40f21281e361d324c93c2b0061e43b883a159a1a4c22f7a66776060582a6c5158c12d5450c6d2db1a1c28c2445932b003290984550847041ea223bdea413861cea2aa2676800807fdd0b99a2331b230c824ef8802185d20072107efae289e40e05330e85211901826db2558c791760aa7095550c89fa05d40100e490a248ade23824f0902d97005ee088c03d5027f8218c70930282748b1131a5458420140a801b011a016c013514042c0af1a7f83fea054fd79f8e3fbf3d3e33759c08ff1cd4f0ef633d0f7a6af47dfdcd7f6c7144842a4467d4c754ca2fab6e043c3a50592c5a1c547838fca57b61769cf8bd45e24913d0ad0bd0467f410e951d113a137410beacb06d8865e5e1f3b6f8985bcc73cbb3c3e3c3a5bbc202f5efd07ef0d085e80bb35773aeeae7beed2ec2cd951dabdb0b28bda8909aa6354a7a5ce04b36e70ad8ea3683016c66c412788ee095d07ba3dbab93c7346e616e7a07305e42ae57ac861b360f4f87f3936bc4540ff2fc7948cf2ff879a31d3a133de848052dd345067880e719030c4752911c7222e0e312e8104446fe1b575553bdf53ed237a3c9fe6b5bc92978cfc0e9f7de9037edc9b3dffa8ff3ff0506fe00b3c811ff002fee903bcd303f8a6677ae62fff9957662153c033992e2d252d8f00f9a5641a6ed74ef8ed35f300012673d223178dfcda19cd201abaff97a249f0689cce40faffecf63ed3e6cc182e67acfcbf187f8ef1e758987ba6c699c819ab95692ae7f7cb67bab65f2c966eec576ea5eb9996c5aa734c56e680d734f77bfdf84d5bc338b7fb4d6b33f06d57dc764531bb333333ad053233333333ad8530ebfcd65230c8c431e9534aed18a6349fdffacddafdd5ddf8179bc3b6f55bbf576ad48e9d55b943396591011c83c64a957f262aff4c53a4fc3f5a9ad6ccf9ff3ff64eef8a91ff69904b7948ed0cadc375bfe737aeb1dab9675f2c639fef78d6fe7f807729afcf29f99efad6ef954a3926a58caabbb5df731ce6921bc69154ba7debfae52fd3927f2625ffdfe486098bf23f05c6fe483005defacb09bb765eef375bb6bded407ebddf2c671af27cfe33094150df45f902d0c5f5f2d80590cba504ed6e59e434bb7232c08fd0dc6360639eb359fa03449454970875210eaa96ac03a3c33ec0ab1c2f1d5f405c0903a004de19412d1a9ac87184b5a7e6c199dc840d4060314d86980648536e422ea840c4744216e0c042e35799a42833117de01071cc56da80299166a5a0c4fae3c6a39205324849859afaaa9179e2a6a454f283f3ae589a1a3ef81168c0c347bda80407f069a96b35420271bfb151cd1a315dd4a4c58c27078cb4ac194cd1e08daaed4fccc2ddc8a630d10a55444015382a129c427b22a5449a7c02eb2f1e53285a6215aadafe2ca1403809025c43d364484525099da4402b1c9ab62af605090f269b53056928a184d024486334e273e90222ad346e1ee2e26da04b7fe8501b3264b2aae2ccac552a610c751b88298ab8f5d0d02cb348c420ca1d8904e5c9f33658c94fcc96062fb7c93b6dcb08d3725215279fbb8ab2cf180491617bac30ad946264e16ae03e27cc1fe250f913736304b5c825bd125c0db4f834b188e43004d21b00763a9d01abc13d5b69ea3897a848279b9608c2f1c62df2a71757214f8dd70f22a62d2520b75c75f68afc0b260b2602583b701888b1c28a8f5b89d7de0082e3290fdfe39b2270a4b62a41a8f12849142501b27ccc700f4975c1a0b42096820f11ebc72339c1a02baf2b4627255f6f02f0e401fc8288ab041e2c29178a5ab26644c8ea93c3b6488f8d1f472fc0c96a6422480daac41675d9f17ad300cb40a1149b119812cd2a617d3670b8333835c008982e0dda64399478141a55beb9a827138a680623214a629635b61d947d802544ceee8a84a3be3e585d1c2d18f173654ac4191d8e4e177e44b94313a3ead0a031518a04964c6adb8312c9494291c62c87dc64d1dd01e140455da215d95e47c5253f60544c7e9826ded4ac20a0a24befcda5417e516806ad3d67c88123e06d899b6d0d1304b2fcd47d3d42bcb0405bf6e15058a3212e958826adb1bb0d221aa4b866117c34654893edc12302b9b311bef11a0722bd825a284e48db8ecc580c51aa8561b1607a550988d081cc1005194510ba08de01b9c1d794a4543d3149575678e3037288c7b72db53003181df94e384003c5612a0099b32a72470417946f110a25c095890270058b6b80af6c88a41925ba4b3d5b400d4c634b8f14873bcdc30b1e347af0e5f90b40c14a4bae8c05474bb93f47fc7cfca9653cf7712dab77c17a9d62586f4d092b94e47a51a24911332077852c5b92b6c849906645e42936b8284a645e574279632a5017b710e55714974871cf285a241fa3aa30bbf2e2d4fee8a850e3510aab75ec4f8b1c57cf1a5d086d488224ea4304210484ee0d52d3291c57d01628c0fad35108024124ecc53448360944364205263210a959d381c7159a4514653810c55485356a1132c74696541c073d52404e49da1911e647e4e151796db983c1f1a562a3d28f0c36b2da38302c6b1f43947e4e3d308815540bbca4f94d09761d4ac2a1000418c331063e6091214a6ec3113e4b958236893e06fd2261451653253d536310891001643ef2e0a92b994263c98ff1490b06351314185ec0762d880ad209b8c056abc81132c15d8288cfa02c1f6dee00261900a60110bf107f7fda4c52fdd5e1606789a0a64a949ac9f6aa53d82cb0048dcb252a54d917466858f02d71b5ee3c085243430724961b953e4ca6fc4050ad3943783b5ec153663142ac5804f2a0a9cd50c711a51488390acd07702e086aa175684b866110113ad6883d49a0022ef2a1ea708f916d60130728125eb8de0481319b78a08085b9d374201062259606012db76c5c69aa073bf4e0d148fba7d26090b84a8f599a0a8b66fcfe3c6a31029e9332e8faa9b01fa303808c880ad10105c78b452e3d0d83249d15e991ad51e02c0199942b56b31813301895f0b0e8880f0a200cc15872b304e031e788cd0d31c3277a98933bceb82cd53776240a6c31278b8938071e4b50ac2c71a9c2523147889e4b3cbef6b2fa052934353b8e6ff4107a52a3cf891b15579f2e28542ab540549c22e4425568cc908c08572ecacea0538444eca8d93134544047496a88822d5fa739b8a44b1f77922b8d182f9bbea1060310890f6c9eb8f0943959c4125b40d5a588e08e1b749c36f02972e4658a0fa2c35142ce4f559e0e32592e51a6a82cc2349010d074353efbd02aae7c4c585dac74a0b4b061c6022f3df8a8ec493553386498c1569199f4627a22a008174442a220eb322470dd4c30891379105eb58cc89a3e3d710201e300883962986050b501545acf068a38166b347b02a8015213e311a50786212b900e581478b2237144e4979f56d317a4a4f7cede5d094b94067458cba3c2f167c6240f1c68194b434c8f6fc0a3a917788792b5860244864766ad34c23ead225d6964f50a1f590af4e47de932b4a7ea6b0997135c083c4112e24980347d50006a3a59b116e10ced05458ec41d7226b851d91aacaae2628f9f00759431ca447a90d4a58a881131727f832034493256ca59c688ddafc2e70d1b0c065393172ede926e908b89201292143641085e13a0c4b92139c24a2d501c98485fe8903111f2c5518f9f356e24151b6d61d43c26aa300503e513115a2e27895d5614f8f07e18ab60c7c0a10886acba6c7840e06d2deb8c893d570414884121c5247381861b688033ffd9d11366015d18ec54e2e05424aa45c98637571ff2865c1172384353acc2c10ce225d771d9668414230d4661856535324c9581a25c2eaef152b0654410447c614e0c49e9c5b99108f06ad0b24d840a01541a1c1ea0a71eb718903aa446c6f62300e5179e028775cc6c64bdfa703c44a73a0e9a327856389d34f0c30a81c5c65191c55490b70e19b0382a0137152391a32c2c1015c2d253b8e440122d6a10c55592aed2ce41e3f1ca80f95991c5ef1830f3442485d282aa1c3c6c6ff4a6fe5cbd7933a540264384175587b1c9085152f04c4efa213961274d9f226510991093b5c5ea78444f899d333f373a0d598b5054561193aeb8a1b047e14725a61848bcc0697ae9985aa2c373c3035109076316263c5a7d0c5ba5a701b2383665d22c349cd2a8063d8d8d81f1c6d07a00310b0d4f93a3660936864f8cee64607d04090bac1c2db6a0ba12094920e808609447bd0d2ca61faa3230a14088abc4892091cc22d0a89fac3519ad4882408f4d0f2f322d6547c2221f6d5181ec03804e449a333d66b0a80c38c952356986200c39d8e26cf9f076a44886c4ee4eef18aaa2249286a59aa50425282ce9e2c270894fd4030b076b5f3224e5347052c9aa4419284cce14008bfc63a4499e2a5d2ab96c2a21b120e006c82664c28213190fe02e2f2c143634be36a12343600c2238f1bcc2ea546791feb04074a2d0dce2873693a4ee4244310a43c85ee0e6048e0b68a2c474c161e337684b0dca4058e4c5183e7f865c1dd1c299633107b6a54938f714c58b8ad49547781e14587b62853699d8281f0c2f1988552b78080f11b8e187018f862309acbaa940b3268c8ae824190c496e27ed98629bbb1771015321458a0a0afa9c533f51636b8fc4ae3a92f8f0f1c8f5e6f0af0b0b4fe1a15c5ca53e6b768c5142a165c38ca9268d7637ac672186703034a3cda0b9512c5e282611b1e54121b4c90559de5490cbe21058d7a7c7192c7813a432a0297290830a6d089b204040ba9310392d8d5698d08b6a24183dc44300aef1badab84080c7931658d7a53ecfc609887e3730736fc45139e95323c413495c13d46570911c9599666208c7344f02d24b15348c624b2303b428d93c6aa246801236d25ea2396b7618ab9e73632c2c545251905556a0f2b224529372ed8a04c8c6bf1e289a7a18d3ac906ed9269c64262d6292c3c85f9a2c4eac96555cda28a0297169a477c83650159b5cba6b8b2110673013dda4b03040ca57a1950bccf0199628c7f4905da4225ad60c0958f94d3a493058a2a56887132c2df18e9b0ba410c58e4046551598a0f063b181cf8ccb8d611d99288410b98852a7c7c16cfbc7aace1d450151445ab4510c3c2e214ef292b2ea73e8d984c127f631f4e7928632beae143eae120765b05156b957800ca4bd2357d90f5b3e5628e4c962a747d60799004329156b4e401949b22ef5f1d3f2e104f68e171e61d51fc30d2d6acc7cac6c737a2e613915699ce2921172441160a00b1c4f684c01202493cea9f4c252c1d6ca553d3a12d54497a0efd1081f8d3cb10637ea5c24c292c43ae979dda90573b45190e8091ea63a18b63dc9bae90cd2e3c5c08b1450b5a8ea245922922e7b0e01c2f186c8db3fca7ac38317838fbcc34f9c244543de1b09a62cf539d9012cf433c14c8c1585b2f4808822935db367374a8166a0ab0df19e3cc8ba9325c80da4a0e5fae40988424889422a0e7855a0733498e19193ec1e84b06e134d32c699bcb2377d7162c4fe109cd06a855003db33840f059233504336ec983354c564011014fee686020e812996faa049d4c4a127e8c5cf0caa0f293718640615d8b245363725c200146b748820a372c78e239aaa1338609445f001d587cd90b2c9d55482fb04397748e9430d1849caa9d111adbe25670aa002590e104eb648210086c4859b159516db63820dcc474be913201443cc5c46bc48665c67372099f1831d51c91770a110d2088bf22fcae8d1879e0c8535de58f520e4654a5202074763b69e3068d420e502ba1217c12a538d3e553d0a044349a70b5a766091c5912c8d02895044f971bfb852530004529d68019e01142ac740446aa52c081a4416a665909f3949f3ca03030eba26351bf1a06a933ef8ecfdc5408047774529078ca45a03045000dd2414f920c926c5464f5b07b12a7f5f0a50ba54e54693a8d79498d1cb7de19b35383ba4485d96b0deafc85fc3e003450c2c512449fbd098ea7362a7251a0ae4c64806ccfba74b2a0a4eac119725280a898944abf943e8ea0056a503877200baa3870e173e10052617f14d8db1175815e20048c98decb51211171f513c1a49914a5015d4f28858a851cb0d285938ac204a79be059a79d1856ac5474240a56776070646a5396f885ced104893f4a1f9b460c79a283255765f7c490f16936e68c5bd9501e1d7bcf2b63413a809cb850baa3296ded6e0409955ba4180a799d2ca35b364600752702a662471cb8a8b4a3ca3f5c7089bc6ae318f0afa8c392ae387126455e0213ad22b1e9406715d71f12642c698e69c45554decfe542ac167c6903336236eb65e749d9092c12e8362b3d81e144d96a8a2733547626441819944ddd1720522ac9b7744018c2e8f83083008e1e50c814527b8e490f420b1c3496707088fe71ebb4046ab2b8a2710f81430f599d047f944cd1c172e15018961b25c38a2dc704f075079725c8eb453384da6300ea75412b30329c764052911d0110b7e75a4c254aa7205aba19122b32a64084980cf013667d36da02e71ba90a440a2e89d94c49a0028070b3a328b2b143eb284f92168727753b10a9287a896ea11a84f81b3c5424e7c27450458b4c24bf1800031c4307b7862c4534f05a4f0c102abb154904010d08191c41c041f61b8a6b4566071260af3343326af8581d2c627aa8a0099c32d72d90013881c38399042c1b272c7c896b52d9f3a443ad0551a3183686a0656a15a833082f40c3d9b8b800083a8c63ccd2e8472153a41194873fedcbcb22a2ca1002074fcb281c71d23175752342a2a1b909ae8a66c343c3825428f606d89b243f5e2410eade811350056d62b48d4b2281d9a8068d2854b1a46cc01a51da854d228d6a11c740ccd8800000000004001f31000303824188c05c3e1a0844a6a26001480015fa866a45a9d4bd4308e510c214390013200000000002018491410c6abd1df9060d92e8f9f02433616a4ebff406be17bf6d8d1d32ee546ce2d906a5c7fe6abe095fb9d0691e7e941fe770a0bcbe3a64164eb24abec7cf5b002efc952b0085ab5bdbab0deeb11b8f86cbba36d8224c0760baee1f21e7bd285e27f184f30832084cad528d27196919edfcd60bc54dc658c2a0357906dacaed7bb677bb7ead3d20cccc52eada6a59b8d2d4674abcb06193ada62c334556bc0a73c911d0e47d7189111d15cbf9b73f7c6589f7621de2b75a8de6ea348ad065810c75dcc9e08bd4ec5c79e7cdc8c91856d3117bd42b7fa80d9b357aebf9833535a1f904e5ba59503ba29b59bb9136484d6c9b51723c1b67a8f89d94d6df80dd9d8b1bf56b98c458701f3fb37550a72adcbacbb526a55bd2513a18521bb4dfe0142ceada8c566ffc4d72484a03f3931de9fa618f70b8b5d7fb5c5ca2fe88b9dbe4668acf3457cccfa34c8c45f19b2f3972233ff5423637d55c99a5fca64df9f3c99efaf5156ea94e513a132b8565977c4ca6ab9325eaf2c132ceb15cb8a92659b6619205aa6572d7b64cb1addb2847019af5c66235d16b5cb722e5e46cb97f9b27e19f897307b7f2a66e7af909959cc8c1d35b39d9ed92a68d6289a4d24cd80a6d95ad4cc5435a3c89a9da06b762a9b21d266426db3206e16a66ee6e4cde2fa669fc019a27056489c111a673191b392ca59237346e99cb585ce84d2995e2a7546ca9de17a676bc1b3a27826933cf334cf5a44cf5ad5334bf68cd23d8b50f8ac2b7dc6d13e63c4cf1ef533247fa6a47f0609a06914d05409b48306da2e8236a9a0e13268101d344f08cd5742d3a4d0782d34a3185aa386e6c9a1e97a680841341415d17c4d345e142d52456364d17e5db49e30da5246d3a5d1c8da68451c8da58ef608e4d162e21bca3eb46ba867230de863fadda4b2bd0ed8a68ff59a6b4dd492c6d1cdff76b7ec10df7f2f1d6c65997903647aa723195b52fe0eb992f3d7548f5ec740c2f7f38d1b31f98725cb03bbc2674e92d0edd3cce783cff4e6bffd8399513004f4e0a44ec3f06d2d073739c862b7136f08e6908b107ac5b84cf0b6423cc4dcb1227dd8402cc551e303625224130634a522732127e61fff77e3860399c3ed00eedea397a4e745754b293e2a511974ae046cbce5e6abdf5c059ca2bbb40c7d9826cdb3f3906d690ee128c7d3b94380610f51c0e224bb44f0c812cc5804bb094e1d13e43905170fa2a5b0b5537b875b189bccaf682c12ce060b0dfd004f4362ab907f23be186f5926e28b3e6dc1c005d64307481a17aab12050f1735721e04e5d03a2e9f310f872865dae39e0a81856ff2beb2fea393202ef83bfd1b8865b937b273d36618adfbacd8009ae9a435cebda7193b31b2caee7e4efcb6a57f1c135aafee1ca4c93f6cb3254d688c1dc5a4cc543c9d0519567c1387f97f926201fe0f80dbfff516fda5df6814a2e53fb5a8a45ee2bdd7a4f9324de5f9836e4d67d96d6fc1f35cf405ea4a20d30b06d5604d74d420bd0ca220df92a0145a99991a10190607c54c5b04486fa6cc16237f056c22687787052422f22e481071e605b20fc17ae88bacd3fc6ff744bc4bc5cb408d4061db6a86bfd77ef836a7fdb4bdabe5344cbffd3cdccfe7195b0d1d197049a6f5ee7eaaa4ee144fc531cc53ea2cb83760c66bed4f53218fae8620f70b0c24b469006c0ea0fb9936e9f17450febcba27ceddd6a51fafb1132df49e220d79439f2bf92e0fea70e26b9a68252acaed144ea7fab65341e70b887ef77de230c9a1f5681ffbcfbf059a9a9cb3fa253e254c597cf6b2e030f548b35ac93f0bddbcdf948375e963731ba6377900bd2d299740596fff025edb5ed2b3d77183e8f61dbf0e44b37ca4dd265de4d7a6f734a8bae6896d674f4a22b86dcc1f6939cc0b1752e9b8c3406b50d9daed8c8a0a0b89cabebc1ef6231a50ef24f00f903402fa2f7d29bdd5cb7d2cde426e30674e3ac3ecb20d3512afb66a2fc0f24b0423fbe31f19996bcf39de40d0a41641f9e05723755fdd424a72635debfcca2ae6756635798ce8ad4a3bcaa8c25e1e8876963f1f0297947b48443e51640fd9bcd0dd69ea1fad4e9da5e59cc22b75b96000cf41356711ec9564ea06e9caaa1e5add960a91ebbf18348d7ffe97592ce4e351e015031bbbf1188feb0f6657dc9e98fb18e7a3fe02de5a2cdd0438c20f66b8f8ee64643ef81c8a04169c98aa2d016f8bc41645070e2da47d2ae438a2ecbbea584608994bb1b2e70c31182ee3b3eb52a96a37271fb31eea61be71683a41ab2576b8c08e7e2b463cb769e4ea17930146a6587106d149bc2a557a90690c13471b4f5eb03a2424acc2aa04956543384113798bd533b430920be98a18402d2f5d5bb783dbf1f1ad3f742c04361fd42843753dfef9451675ca6714fca59ce6cba00d27d6b00d1690c87b2d62e049a1e29bca14dc56fef13d1fee8e77f8252ec15650134a8f4ffed6e8fb571da2b0b9015bdb1c67d9781f50e2e4c392adf7da5486715595ee4929cabee22f09d5e339c395fcad4bf2c92c6f08659c0c9404033f3c91e3e855b72d389cc4ce001fada7e4c90e42b76490496001b31c2d613e75cca786a0635e40c48f93ff812dbbaf959f9d347d7e12e47c462a07b76bda6de7237977cf3c78dc0df9e2387c736a1432e11d05260504b3c4787b48c127285edddc50368cdba4242b1e8b5eacf4323cb4b35b7e745ddd5a932795dea9b6e02c941ce8a1d95ed6a517dc54abe7e0a8e7f0b4bcf3b429d30f7c1cd34cf4af4e05b1a398df78ae340699fdb5dd073dd72d3e2fe67c0a758fc1c97b6b63100d8e8668fa8a01d2cc47982cd9b12c4d910802719e6cffc6df3e71aec15f504f36e2d5012a0e80f3694e2ace25c1985fa0c12e8725242fe79e442d393e6ea35b3c146601144443e27fcd3841728ed1b7dc9c4ad119c58e3115057e1f93c20c49b8995bbe0e933ab18fec2f7b0986be1e2f7975d0488c22db9dbada021dcb88f998dc83bc1e3184468a39efc0f8dee9169792969469097f7f3bd1c9ef2c11ec21dc463d49e07bf716bfc09e270c4f8ff7e6a84f45c83a4071f39bf00df1071f55703f03cbaacd3fa07d39b7189a78a5380d6e3b3076a5d7974db2cdf7d58fee7b9cba6b74f141e577edb32a69d205d22d004c55f6346a9149961b43c82c352af38bfc7ec2c66ba2d08c83a0ee37df514b9f16818823596ad8cf1113c42fa5f2009bf87c74012bfad4b4d314695d205ec1bbccdf88874f5c2b7baea1e81e9adebe045813b87d6181796cf0eaad662afe36b2b2248d99dca89c71ce16aa80e62718814916a270c5e6117f4e0a7bf01ad6097bc7759714adbc960fe33c254acedfba4238e5e15a711eae1a14eab06b4a55a2fb019432381e4b78c5e8f53226a500a66da5f225888dd6e6136a1518f45155de99ab76ca6014fa33e3a5b130c413381d145bad48afb4e2af18d34ac2056ec9d884d26aeec59e25374b8c5e4742d52d216b71eb415403ec7b9b8d1e8df1c1688c845cbf2956f9c9176786ed5dfe15076e6c69c70adc459ebf9fcad742ec9855ed2ede1e59edd9251cad578661ee8d0c5e9d133247d2519ba2de920b60dfe3b4f3203a2d6c095c100f1c12bed17381066a3302f80fe02626481188c6f00f14e04bd6dd0d6e6204ece6e8657a6413d0099eb6ea9599e6e3646c5521296edd8bd968af937e328ed4d6cbecb350ad3eea5c9da8da3dc87eeb67c981c1b15564dc29d367ea969a35be74fc98be047f168fafa46ca7414dc4fca0c8482607b41b7d3c46f5f9bd7171476e25a49d58054ed8f9c36913764a83859e5363cde71f5c93e93f0b7f907317e98e22be0ad8bc4a2f102fca187de1efe755022e0c5d2f3db46d4c921322b94ee35a71a9b693f6c7ae871ce1221c8c4fbcdbd50267fdb5722c94f908e8558ed88c49720fd3b8a349bcbeaceb9c01800e3f49e4b91bb9449e2faf1c31db618fb8d1968b2def5b4487f8c19bb3314d761af535a0b72728dbf9d8ea7bffc37a84a2210a1e030e418c11b07053a3d64b3385b696bf7873e2d9bd8302a16c77c9df70f1f7925fb3f11bfd604de778a7f84e43f2043d1ff6f042eaebf3773de98ce77fab56b8cdcb7fc4872b091406233d2a3d5d025443d8cdb6bcf5794f96722cbe4dd97ecce3c0e24ca1e6205d11b6ed10f65bb7046cfe707079447438c59219b0be27e81dc11104df1e384291014a04e100b64b81a124b7daab12db19006d171c57c773cd8a82986c558edc419fa8ddec7882b44d132503a1d8480ca88d753daf386ed81d9b7d8f13b250c0c3e76dc677f2f6bb14be509a86f55d6f3ae7bca9e2d51fd20ecde392d898b6c6e511555c2a6856650e6c3896bf5d3ea3bad5dbebdba6237009c8e296eecba3852f03b91c1c696761e3e3a1f89f4a4ace5678d4348a0f7e19e11b27806fe2c4e11b1aaf5d94e3d14466fa9955d3815fe7f391bc66bccfaccd66d0efeed3599398690a3048bc67921ff07500148a43b91b80c2c45bbd387875a8032cc8148422c0aba6d17df96428c47e7e6fcfc2d1fed63ceae6d205df422f31acfbce0fb9eb01d446b83f2a686acfad1cc3559a1e736c848b2e9dd817979faf6e6354c5bfd117b22f24defc89e229b1f2b8451aa80483aba6f3ee92b9cffac081b974fc2a1231d12c9767b32c10dcd7f315f206b17210a0add440e82b63435e058ef630df8aace283d09aad99cb987869b095ecabbc0f35653824d888d7a277ab6822730109ebadd0b5e1dcc9f70848af7e4bb5a790c9cba979f9f735cef7b38bdbf7830c1d20bc933130ff16137ccdd38bf409a1e6297aa6e593d68e40d1e7dc15726be9a4cadda9dc6a83943d3c83142ea1db471466f1dec160776a92f012a071aeac74289bc56d71ab660b95171f1bbf5669966fef93c2bb7789e833de59bc68107ad089bd916e53ee33a7d8b19ccf7990cb3375780f3b4e1e4cb1bf33a2219d03eb74b0bf63c6af1741572ea8a487a430b3cf6529ccc5e70c8dcdd6e78cc338abb319b8d9ff836c0b28814c18b92d8839a8f8f6b52a6fb9f7f7dbad318c93d45435d5b1ca050884cac844034aed8edab10e6b712eb91ab483ee589da41acd320b912476b246a2fa96e4dd9600438eabf7d5ad03e42b2bc08e2ed5735a39750a73296a249990b6cfeb97b6dd6d2ae210a29c1cece68487f96e5193504815e65d633806d5b4b748c763d64a3477e0ab444436e2babc36ee2684609dbf9d177edd70cc2823413ebcf71e295c2e68d70c3ee4f3bb918e58c7e107bdf7cd05e4833b8656c46bc09f259f0d9ffee44e0f6672790dbce5280054c5c817f3dc595c35863cacaf8a439d7d920ed52e4d731525b2f7af096cdf613bbf220fcbe53dc077e6d4af1bf95874dc8434760b265fc80b2696138e87e32ac3206c86da68935dde33ab5f97e95833f119df2f1a729b2e1490bf4a0fbd2466fc24e05d0f8d3ecd9b4cd1e03ccc6eaa78c9db5ba8fa31fdc6376b8aa2f3cb641ac3d4ef502543dc0603a2b1f7bc70ab368acfa60dd9a0427e98234e914eaa8496320f89e0d9394c8150aa4831b3be953ec076164845eff20122fb6afcfd851b002c69e3adf3b34fe057133421939d9b2fb64405f8ceb6f0f66059060c3109c374f7cfee6e9e93499919dfd3addcd7b5ef68cd6ee5c3e524bf4e917ae97b169ececdf90957b0526cd3761aaa58575cd35b4524928f2ec89551baad670c116d04b91af0395a8df7343a8bc43512a4d0a180b0e048f3834dd5dc14920caaa2c15939832b7cd6d64aa39db7d3dd8313fb358d1b51f62b50ee888eb72f6543ce380486b6f455cffd08f43e06ae64027f87cd28670389231dc81f9b069027327e7d0acf477d3cb782e8e4f20b57dc43e1530464d505a8047ce9a140c8b712987eef9a849801351f2a194b5ed3762fed9deac98f0e730242d30f505dda35474723e28c7d6ca67dfcf25622ddda6b47770797322ddf903f59ad30bf6d19fb62c799959db259a2d8ffd5be3498bb268f39015ecf045bd8de83d22fd7b7dbfe353a33d31ee6de1df0b2ce7d2c12f527bb9765ca275a584f7f1e68de2bdbb079abf3c799d26f293b1c03f6678f9fd64f9575c9f0e715076b1ac9db92f8bda0485957c9b1bf355af58a60b62e437b6380a076d857676b344b45d0f3f17cce8a25d8c36226e021a4d38f9d9e6c139253ebf241988e1dfd765465caaaacfbe0c026fc8f7a2f6df094a3c7f241908e2bf473a56fc80fd1f2ac6a64e6aff6befcbc3f2a8dee98ff7bb3e4ded3de8e5068cf13ff676304ac0ee6374a92c73ca7f01cf38265b544a7c6d7dfea6d403662435eed2a7b7b7e546994eaa99d4754fa1a08252dd2dbebd454f52fa0c9673de4c0a5b7356aea91f38f329c5820b18f47c02f70646fca3f6fc0d88406c46f57db28832aa6fa17c697e54a09c18d68548581754479ae4ce0835b9e8815a267e3238af53eb3b0cabf0ba26c7bc49af7bc403816f9b5a6c3f4ed50d8f43466c8b0ef10dafea906d34d4c3f210facfee211814ec0d29fd30be73dcf33de47d923fddf6286621ccb275ce79cfad7727a48b5895df9f18de19aaf2ec3cd6980c375e939e6e0ec04b6430bc5a2551e412c66845e576aab68b49bdfe0e1e281ffc57eae73456c0f9abc737a9a2aa7f3df62f0cd26311ed6a0f17346c17d58327e87539b344de4e06b6d15fa665adc9920983f4bdf61ae18f2ae3d7bed8d59575e4d44bcb751973ab5da73e0e2ff07401e7fb73b3a86ca61bb6b2aa2b69c16ca8fa91996777ced10ffb8d8756dffe22d4faff331879b4150ee3045dc8cdc711a488ba217bcd7e39eba01c6ba10eaed4fc984351c7066fa32be6dc66794504e7be4f7edaf0437d63fda5a17df70a3f1686c1b9897f997de683a19434bd5066398fd91f9000ffb2747fb0de74f48ccb83d62f714584b59f77206b13ec32edf21ce33736018688def01d29785cc04d5c5cf4e5c2e875dd640ca5331c32ec979b6c877d06699b6d92db2cc56cf32f176e3281c032552b72d1533d9f5061e1c5c8666010d8ce97fe60e10547e2fa93ecfe323b02e824e7e06f8b305728499bbf2c29d02ca92ff748aabb855f19bf62fe0e4c3a4600803f1ade1e3de9664dc03d51e3a371af1dcd4dc8803d6e469f14855d3b3351ea79dbdd5ab08e6fa1a5d8bedae769d13c7f502cc3ed375b9e20b4c8ff447184428d0dd0a0e47ef88e034e7bc3b0ab8c1919d04feb9052cc96bdea499f2ac4bd821a6bf449da3039e2e3ff4a09eecbfb97a65712e13c82caad13f39cd055e05abc2a30d7f80608615cbfacdc37210f608471695d52d47e9ff5d1cd79146394d8095d9f9d4d41379fea91d7bc0b98e780582eb3ae193cf43e73f9271d145b0e67993b6d516d4435009cd80c5d612245cb704a244dfcabcdf7f3cd817314f432da489d97d715be4dd4b9aa243aa37fd2cbe4b717a7b8f95cff11522e073cdf580fbd04764208d6da132915d6ea2cf2ec74b9d220d87cf94b43ed4c3d6501413175735a837cd63b2b51a275e445af68c232c044b3a23d5f11b0ea1f813f7135cbba390d33843b07fdb56a70acbfa08c84b9ef15ad42954472bcff8833d54822d8bdb0e099d3343d62dac7710a8df0c8330fe3d2d9c2c5c11eb93efb25b4dd9b5c2cb715853bce6e5eaa6f05e5b472330f69ad10bd50d093ab9420ce1d90999fd3acd062f9679e5955c1a3623478ead4c5111948d6c9c3c97d608a43453869303b53285d59b37c997f24ec6006ae79b0eec29be429019e64a1d4faf7f8e261e1d758fc9e82c45ddc24f85881fe81feee889ed3231f4a4ecf5014c2ba1dce16bc676f7afabeeb23ff55d538dff6d87536e7f9977612de4f200ee8cf94b19fbf76ce65650a2961a25c5eeecb0b53c45203de48993c54d84021f0190242970b22ed2084cf6b9c5fbbecc03ba97c6b1c99f7910ca5c6a240b9c3b36b036a5312c3e6cad7802e3598aa0d605bf13ed3cbff740a7d9d87f253af187e030e0ca8bebc3af787d0a6810fb26c958fbdf5d8ed0f3e3689d4ae374744652cb98acd3b3cab6a4f63e3e5c6c7c31757b181b2eb5f1736c7132bddb36dffc8bd9e53c80d6a53e86d0fd9dc4a76edf33336c95f58e0bcb7dec41cda051ec5b1053fabcd08233a865c4a70cfae523a965767d4fd78ee0fae8ccbd50c531aaeba76ec9e7c03c475ae17af8f0d822b82a592c014dfaf4079b7752252dd8d892c42a146bff865d347e87ec39262cd324d9ae0861f839d1c149a43c180e774188ffe78609ed6285c0a1fc8697672edcbab2fb770e6879a96081ae7829033af58ef7f82e230d6718566206c7fa4695842c76dcc2a978190d9424acd0bf54b84fbe78015c6f7be6bc247b29863dceeef130dff5c9ec2893514bbb75c1e32e65eeb6cd419b6fddc86d15a999c0db3c15e6442baa4016968ff3c4ad14a3f0395c7dee2cafbcc7435c531bc97e02981fa08f42bc3b692abff4b6b5deedf2adc7eaeb85d6a7a25fe1f848bbbcdcaf96871214ab14f24f8e071e29a3dca6fb9833907f7afdb976de50fd3e129675719cacf42dbc87aa6ee1fbfdc3cdfabc9ad9543548968966d05ee4bf97f500db1bd4b05177d056391db05377da6737f545803e8614f2ae59f6fc4ce72cd31a03ce4d56b3f32b0cbf9b1ba7f4406a06ddcf04e90f54b1c17b62d623c6deee54fe855511da53d4456ee53abe1ef27da950c606f45bb176aef3d74f8e24f356a3f373d08b0615e684c30fba8041b2e764c3f9c10aa447158f4a9e132367aeca070e4e3fea08a089c073191c7a1accd4c29baeb9fad1487fb83f2800845d2f468aee3d0489b4dc616e29d7410db352b2dd76c5e7d32f386e5efe3705d45d0dab867aa6f0fce2211901769fe66a43a71c15201da8fc01f5798591776b765619878f5ee66c8128ab306b8409f7a76d549a09bc639ddafeb138d6b1e0cf06df949019cfd54cc1251cdc5d018281023621cc2c982b4157f4845f9c80d1987ec490ab05a3f6a0693af294e5383711792aee5e35d10daa283ba483c266465d521e439f09378c880461d2d4ce113d0afa6bfccfede7121f43ed950d036e9f6506103fb792afe4bcd837b94ccdd44e2ce33ff23064be216bd6a4c9c14395a23ed57b7feea82d8d090491a81be39e4d3bc2a724637a5da5cfa92c18b51da40900b39a2a3b1374778c82ab9854c90a90ab0e3a97614ed2df2c587bdc736a87df549a7bc8ffc2b6acba8487ee059c7013f458c6290f9f87ec7d3ebfd6ab598a61c88f59e4f079f58aad8a34fee6181b1bdf6d643c76912fd6906ad98d98a54b0959986b21e54e39c5bdd46667eaa6bce964a4f2b43694ac6d125bc6f052316b72701e4234a3a1e46e0c2d8877fe233ce6c9815f2abe6c4396f987d68561eebfb67312ee0944359762203789e5c650beee63ada20f90c87a363bf1d3fe35dcbc2d73f2d68425ed3e83b368e81ddcefa6043a110801cb44fe7f07201c3cac72404e70d831af67ae82811e938030b61703da23b68aa27063819a723745d0805474daef3c5cbe8469bc9bada57594037d1cc5caa802fb23dc0b911d04bcdbfd194b5ca298e1dba68c6d384fe90f62009f0fb5c86254e77dd46586d7b5a7f36cb660ddb01dd9fd6be4911c792e4784cb8d6d11cd7b3dd10f4839593b9094bcf7bbadf2842987e2750a3e383e2143e72939d36fc8eac064014b8ab7d122c2f602bd3da575df93aba5b54d59f50cbcba6f63c3e77efd0003bcf90aefac190f2aef63f485f10b276f22c36f20dee8723ba853ac10fbf233fff1b703bcde3f3114764c6b81f07014e19d5837c787d2c76583c61b53b6bd886de0bb52176f01611b94c236f33d7bd9dd949b5aa80b62c8bdceb96ccaa5923788a4f255a2cdc2e135533e77d3d33f1d9f1679ab7aaa0a66ed6c214a2b375cdc2cc60703bd6d314d65f7d2fb1e654f0f9c418118883a02af4b597a759a1c9d6e49a32be938d09f0ab27402ee9c136e5077f368deaa7e31f9d0663cb7f81afcd3701c0cd4d0b5fbd44c824734293b95b0421948b9c734b328059bf2f975388470e7947b670ca1d41146a3cdf32b734410040cd1acc99820a7e67ba942415446e3470ffb33506dad8256c770a4f387cfa028a67f210db9407a84e3d498ed9a819c9b95962c022c7ea3fc2415d8f63839cc58746f046327d740559d93722e5089f8a7897057f4f97d25e8aa0910fb37a5d7dac0e72beaab1df528433d052763d897eaa7854d8c3558270f31fcc7e96fc13adca35627c92264540f11d8e28038d97435b0574ec3bd521a6df829780ed2cf113bdd886415cc340fcec57ef60a4e7e50f5b6010f057d7428c66866cbfe9aaff24e6d29a0ffeb020543aa6ec693d77847ea76159d94888dc5a248e4c966827ebfccd7c385b695c4560e089c1fe110e86ab2882922195983be643f43e7e9bc8077c6f166a3ae77eedcdf7eed3d4e9ab2d1570c5fd6fdfc8dc2f8337d675e94af98554b0583514dc90cc483333c6bdd36e0f5ff6350b6b4aa8f7b65d926a569973fece6229b97b73cbbb7d3bc0d4d6e82129611e0ddf0e88bd0b34a0e6bd4bb006b2631917766ef81c5cdedba90287db3bb58107231ee636d1c5678c550e8261d06d4a0c45aa346f66531b7bad4be26118e5c354f34fb7032eb7316276fde1fc97e1ae90daf68baef6fd5bb2ad8bb3f14ca9630442e4608742f21dda24b6ac7db2584b6315ce62d7bfb7a68cc6636381012a43287e99c940a6f1aa5f4b6351420698a0200a124f18f664a3aa28dcbf3086833197511b9bef624b11ec8ecc4b785890cf4e141c729071507fca8efbb9a5388abb281fd945384704587489e51bca402e3362dcef1648bb3fc2e5df6dfa0acdcd2e873599858b50115c68299a9aaccd83c9734799bd8e1b7a9aa6f6a3d713669d2764426b210a82d676441de36d4d2baa8252b48dc5ee37bceb0ddf8b311dff8181ceee173d2679a18320121e695bf06773b9ceff7181ed07b01189a3cd23eb5079d5fe3ce1f2034ab74666229463b53596dfca99c4a33656a86932eb442150d3283f6c990b6be11c6f4d2bd00808df3bb5b75b183ad6dd5f4b74ac4851cfb54d718fa6d45516a1ce2f26c199c51461e78c0a95899539a3dba7415a81ef6878ce1d4229f0008283cca6791e9165bab9d8fc29af78403138ba8168763981bdd9ac84831f5de314a4c6a3647d4e7075db5276343b1f101346d2bc6a06b415c594934b453a0c69fbda992dd9382f813662f6c10bbed29697b4a78d518e0d964d07048fae84768dddac5139bf222a028cb4518f5356c250aae349a90fe97949c31068fab241081a90248d54edc1ff87985805fd9040b65062526e3fed9576a24c7685747cd9dfd2451ddc1eff452140ec729000c55ffdc6cd1bbc86d0b2d27952fec57f633f70c6c3c1ec8d6271260e23a858fcf2f589eccbb7d0332a39a351a1b80307153ce2becef373f4ddfe807f4a78a5e580b436b6df06e3bffa1c7ee3fd816ee908f9e4771a83917d1b2f3b5342ae0f673909e7e04c638ed79189316b105fa79dd33776bd9f4f65ef1be397b8ce267ab5a2b89400a6a64b9373b4641b73753f61af8932e0885016147cf379d6dacb9d89d78cf7175b4c4a5f036b3f9e9a242c19b45da2daf3b1180b5140672b8383bfe0b3ec549ffeccc53fb695cd562f37324f3b18157028db3a235d30e3bf1c0541df42c0fe5237c5b93cfe381948f45edacec81026f3087bad16574c2682dac07d50f292da448d22c7ea7afba060bdd19de305a37049b52169cb304b3c7b1611ee1831c32494f36b0be2b2734899e0e1925c65dc81ef7957561998cd5c47b15f0686c599ced1a01a5242fb4d9b4cf7b8df03284663313899c27c5a71ad6ed8f23883934622050ae2d488f4098a1810a40d08ce4c46d1eb3d02599122a7a908bc8dbda0d6ed19d92d13aa6dca47a0a129a4fb565fbd373cdd33f95755c7b2d25c6da1e3fdc9dddb33dfa77dee78488c3164a245b4c0c306505d5d5093a49303cc98f78824ab88b37f5f7a5f494527161732411cf1eab10100275d8ab7544430a0a9db162ed02dd1075e965640ce5da5c8168fd70c9f35130bc3d8cf5ef8922ea2e3dee9c85c23acb23486980d4dfe503ba8fd1dc82b7d834096df17abdfce10e2465a9b57e2519d26de9e6c6f34db42d81144e74592d96f95e2b532120964b84e81881bdaec9693ca036b905e5f11ca595407a860ea257cfebc603cba9859a9dffabb3069e33c046771838528faaa3e2237589932aab6a694dd6fa0ce53c049b88ff15efc29741adef7d6aa504d1d3bf6417b3716623c41c34feedf1ba1f4c97bd7bf13aaa722a4ab633d1977b59830cc90fa5bfcef6c9ea70af579bdd0d65b57034ff7028a6b9843428bb3c08a432cc0d89fc15a1d8aff6d6fc200d518d3e0d23e898dd48cd44e5e8604fca1fcd4b777d1dbb79544d50678c0da87eadce32c708e4985fd629d0f47b959d68b60abd92409b0c2df1e3c32e1283fe03831f6004233e2b4d4e838d3561077bb0da1834215fb30e4a0534de8b2973b1475046702cd214068d05698f22c63c41c43f8789c610cdfeb023291e8884884e33c850d9e42289370ba9f57b6976d84d3746dac94f9b06f6bbd6c6cf77e7bf052d596823225ac21cada6af6dfb318d3c1a1ad82521fa810492c52f1809eda7ff76f2746dd2f5554bcad92747f997746f1262463c98d838f03ababec418f800f37783aea7162199e9af103c391c16439e703ddb1122b2a0ed1713b6e023de97c58eaf4c2abdacf382bb04eec9f2b111cde89cccc60a6eb6212847cc321bc04add1cd32cd2af7b1c254d6dbd77c69d77e638175980f5300f2a48ab13d33b826610d5c4aea04ac63a6ac1c3a477e126d83168918563f1c3be2867171da3a7cce2cd5e77bc2a2d00870e240ad2e15fca015d0b47be20aa613fbfa92a7b81909a087a43b7757ce28e6a6d29e76631d5ca97f1c6ac7062b1c9c5657fe4d93a95157f9245b487c4b6bcffbe3ef39a7de634d53c74034d0fb570bb938890362b26a8c847b8054dfdc0cf4a5a07e5e93d470ea9e88b19d3129d3ad3b851b292efe70053fb8b2217865dfeba84daf8244694d37ebf6ef5f246805f5af52c1aee1f70ec177a6298d927e7c281ce459da6800a738d63b560dd612dbd66e15cb539f05fd454d26906dfad51fbc478b5b8a9a6ceea50df879b5aea60d49e178b55379ccc569adc0d19f23be4b4cad3ae6faf5ec537f53c8c819960748b8861ca178f93f653f4d3a6822a970306fe83de88370320095eae1a879fd8d99b744264ab6a28956dcc241952f4d933c1c3f951b19659dbca15c13f5f66ed6f7dcdde5d8cce2eb6a0e26696f3e7bdf910fd5019849f24a5c9b2114fcd8f1e6075e3a93e784a29da6c4e158ffdc4ce07ea46e9b180732518581241e30c67190839e4f6809734f578e8fb331d4a01d7eb8526c39102982a66f39dcb3e44928fe21d98b9affc309d656d89952609340390c068be09f800df7415530df12dc69d3990274204c30d90884cdbcda84a511c8890baf8acc17389c26e77f6687bd7684bafc45dd09021d78c59ede2b4d737fe8ae9e22c42e4bf0a7a233e4af1a19bee18090f7332a0e9887744e5722acfe68ee48f7cab4bd2396a316993d71e5a12116b8bda024cde98e6e2b2c7c4217a7a327b97008ed5d60fab4178ed901e7f147313cd17485e8e8aebe583249eb7d3db1498253192cc52e5f945e5f2d39f303a81bcb8400bfb7b254c39ee1701b7b9053c1446f8511e017288c78414556eb61a041e846bd40c5f5b8e79a48dc786121e0c018e3060b7342081116408fa2e107b1c6a55e7e50e808a747d71cd3cc31cb1b7a5baca8f440d3e2f7f181dd07b1bfcb14a98779ba1d7b8ec8ef51e7d975f7a5566bca88eb94fa9f32c60419dfac488cd0f9738cd452842d0668658ff4826243d1b9f8ae4bfb7a6a3c5f96176547ff379966dea4e4bfbc94793ef67cb3a82640d1b2428762b6b14551e56860d5820cc05565474d2fb1ca14c6e2e964f2074252a774382ce517838457f9bbbbe03136207dcdb1dec2a63a3fd112324dcec951c39477d1e10ba31211e4bc72a0e14af52d1a4c14c981b311fea42e03efc626d36eb41a695faaa1e5a88d8b6a2dc776bc3f40e3db07b3cc9ec18f0965927210c1222514c35699dcb0d8ba885615b1b6d69166a6c7d7bc404c6070a3dcb43af83c02a861f4aa481ad93d720d1afba3dc6ec8105e95261a5536af65135676363a62776ea1c20a40da988b5c8c12cb67c8a8b42b3ac6130d8c21b2323c23391b86afd0c570b04ade29a445f5df290def6b6edc8214228f90b9b6503eb67b29adefb12a0a8d1ea47c741c6cc56e04606e22d226589cff88c26439266f72e892e57d289d63ce4a11c8302b2103b5598f63ddf719c89e2659d31063c7096960ba44e368f460200225a0a9d35a457266f942c85b0e159aee368f776a29d13e15068498332d1a5a882e1126db20022a28e68007e91dd23c61528da1daad80bd642483b7907b6a8173c562392b0cc8b0d773ef7e603224233478167cc1256dbf15a72df800bd44b7d24e7757111dfe0e3b32f29ae2575682a1650a5729c9642ff627155eb113369188a42870d625ba91d1341fa4de5a3ac3655e36b67d2c49e44a1e20f8c77399cc3fa136ae0c8a4c9061bb5d34eb0bc89485f22684a342b15dad460943920d57496cbc321cb17ac87bc1fc46c358384f6ac9fb644ff83cf0986678b99bbc391183f98987561cd50c026a28437d6eb0fb863898717739f21650337ab71f16330f3a350496a56e888edde77bab01fa8259d480b9580065343b54de29f48781f3a1ed4900e70ad6a079c4f36dc405a41a4a9c2ce31242d3d55199ed0c9a9ff3a80387e5a77ec1b20b0d8e12e8f440f763bbacb2e8e26462b9d221ce15d514f9229993a715c9e7d71f995b193321d18a30d708f5012c4009aa9a299c003c22305ca33c49fd64ad952565da0ac4daa0ec63e2dc4f3521a8c2dbf6d55ffa617f71c44e306a8c3a726a53c8a469bb6825a7835eb237f2306c1126f0343c79cb0831fea6e016bed398de604e26e1337543c644a9c107e68cb2935751b2985d55d81ad8e6d87fc8964d7ec4d2d1153619507953c59a056d3420e9a68465bdeadc46ccb7a8bd51eb1946ace0c3580152257c705a6e0eafe5589839b2c8a6f05ca9c6fddc235181610c8dbf3dcede7028c08c11ac6dd68b2a8294ec096ec5af15088f76a0afae31729b9f0f08922bfd6eee0f702b59f8ab4138cc3a1e285885a57276387f2a97fdb0655efd1b8e74a67b1ac970aad51717bf1dd47f45945b14783d15765e43ea06f859a31c1d8864ae30d70b43802b6bcda2dd022bf6c2aafbee13121e76bd32fe47217eaa06b23bfff8dcbe14fbbeed84231dd289e097add00ed28df1c983cde0877e7d979066c6b91570c6d03351b0f42ddc48276d46ef7761429777b95d0a6e2724699557a69f46bd104a873d48f90f64707c18762ddf96d62647513614ecea1dd936bebd78a84618cb94ae56c5dcb4e8d1fcfa8c140004aa0c4f6f13a5b78f6896b6c7ec8acad3c1cf3b2a39b5a9e69a9fde3a2d5f98a18d5dd5abc53207de8fc851da55a9b9642d729969b2892f7a0117dd02b49a79c453bd7ceec08c3cec64e3098c0ddf650f221b4a201321e68e620679a561c45ee2989d91415f87a8374dd84f596f6833496edbc1cd8d79d4f279773f9dec56e6143cce93377a1a37404c5c21cbba8a94af68780fd0c573693562d4d7b9448260850013bb25747c4e10d51100a51705a7dd0c1c90b9f2aba1f34dfa65675b54d6ac184c677aed9c758510c06c6566a7bad65ee3024ab3a8060cab52efdd4d5c105d4a2eb6328e065a11b2997aa21b08ec8f37f66c1914b55ac8aac178421b2bb659156dae38587232f08e026210989a2e3840fc5a89bfbcb9290a3ada476ce01c68739eb6ad6dc0cd09543e8e5104b82ae76454ffc058e9d2ad147f1cebd537911a5a5663541a3768e28ae0042a7a7bbc31439b826a93047874ce735b926ce82a2add623a098cce94fdc1144e2f32418885cca366b630cadb42422c2bcf16d98e9a5324f6aca44ab7d3d234d5995f1b990b63f616a6191dd0701d7f332103057e9cfe06ab2ec4031d2732bf04596f84e3f6be197296980441495746b38ea284637447d20774e851fbc6100be5980845de3f3244bdca9809aef73581ac90363035fcb4259ac789ae3839253dc62cd92492724f74737f1e8f47972a24fa1b4943dd4328ed717b413b6481be7455b33b62b830471a2a0fd6345cfee6ac3d0b1f07d241e47b25e4c8aea5a915461da88c4a14dc114c8fea7db02aa7b31cf6e99dff982580e81b9f3a6d8a2fb20969f4e0a4917e2af2483933cd2784dd295ad1913c32645c4e642bc10a14456691aceafbc9011e52183f7c9d537e411dad5232074be80121a0debd40a22ca0f0b8348c061c863c4bafc99ce40e6f31abfa29f94a9e5c3929e8284046895380a0d23606898d2d459576137384edaa92730976adaa9f52a8500f77b5f56870760b347050f15c1d5c61309010cd61307f89ab942e9c88330dcfd830928b13216d5aa40d59257724b2e6f0951eb3c224651218574871023597161c36a32cd615c789683a258e4ab5db51adee22e03f6ee1616e1647c3c71457e1776c8025b355961d5ab7b332cf255899c2398d2b76a3a575595aff990cc2314dda3b9276f4b9b3ee0e81e3830e8799a5ba946631c1ab0a06356203bb021782169f3af23b0fc114f509a0b3df0147b7ebae976c75dbc4f7c161ea228a11eb6520b1239216d5b3b73b6c0c7640b84e7872d483e5a4151e5cd098db6d883e7c7664a3d6607fba142179eb074a2cc50e1ab439d9563de31bcc3131391139e8cd5bbced2935311fb0a324594815b8c370f92d22890c3ee9a317e97f23bde585116bea0e08492ac9079648ef15f2bc9e73055e5374cf0eaba9cb28fea59621e12b90f9c8626adf8b81fa50f173141f13eee9f0dd7a5b25083f5f7714d5ea9ee1bad4df227ca0322704d26544ba60ea85b64af63a49ba10b3a09956fb2aab1b5dac02900dcd9eb7bfbf08fb9aa032897c9a013919bbbfc89c7d89a6aa8cb78d942775a328c80c9fccd6a0abdf64326f12adf305a641fe8c856f7dfa877d3dbd6894f369f33170f3e6786cfcdc62ff7192bba622f99dc9b0237fb512f19870a8307b074aa47cf15f153162f26b35684de1dc7c4aae4e119e9dc90f258648f6c44715dc4fa47bb9d8a6b9312e27d2d987a99eb2a37f4ce1272816aa259794f74c5e3c38d576f10c179cb37c8eab4f573d8b503cad78d6ce6af42ee601d1eb0559bfe46677925b30269bc8ef72c376cfce54a4407094f690e7fb83cdf6b446cc52382ad81cd7b3abd8edf3a33d43cd53b0b694319c13788dc3ffb314d7267b22682dd862287c684527353b2d7a96942802aefd5de9831ee76fa4847e210b2db7937ce44910729fad84f9e820a882cee6509af2447f443cbb5f9e58d9fcc233dc5a07d23833e4323d703e20bb088804be557abc9db1e04b5d1294fb8387f1db0c39f433cd9c5fcc46f2e0f85eccf132fc1ed4bb5595f549e483223d4c5bc0c40e9921340e120260ec3f4df498dac53bb9cdae85ec2e6e090383865c2604bbda7c5ab0cdb1a051ac5eeec9ba078cf7ffa672bbf16584ed5e4445813122e773211860e2c3a0750f3849c99af0eeef650123e8f5813776613cf2b2ccb1914eda0cf4ce91411d60deef465fd387052049f4f3077719b177b29c62938c9b9e19fdfe63b54b315efb9ca806befffa65846dd181ee3cd35f0b463ca05f65505f0a5ca189179b939970db3989e973e28f956d4c1f5bc28183b18dfe128e0afe2dd6873cace07f135076d46b122df5a2d2e3ab6906229bd0f84c9734198ad375c398216420703f3020dd7620d7e1374758e9b325d60e74ea1e509d436058d9779cb1c67de5fd452ac795ecdd3dd31978174c1ab590a18e68aa7a52808a068d635bd633a8e0959e3fb34cca4e369f2e4afaa7bbe5cc5889f70d721631330cbbc9373f747c315fe48f0a040d16052e508dee84bb4e42c9eb79150578d1cf80732e8bafde256ee37a6d74575de84ab847590b7f6fab38525defb36c706df980309c465fed1633f6759d40df601532349cd9b3f21b8d7efcc86945ed11e9d64bbf9733a38a009f901d9c6d3b1a87338d74c5cc674952fedb2eb551410bcacc4214faefa414dab6354fb7a31e70aad7e67076db7532760f282b9ff0a216f2bd06db7630c0dc06ba5b823be8175e2dd9d80eaff8bbf974b3d228d7b5c906bef790865eab2afc234491aad86e07d1547389869fc37faeae428eafa9f66d793bbf92bc59f681e374bdd362bff871d1ea60ac85051c7c4a863ecaef949e50da926bb7bc513f54be3aeffa7c4159d4b9eee13e891b26478de787713bf22561ce269f248f35b5417e2302a933e377f203c7202103df6c6436180a0f56c0ebf9699c92735d0ecb4304685be55101c1e78e9e8703829a552b893e38fe8282d2065069579b3d32f60cf0deafe45b477d51512b610716f8c0f966835d57c467ae1353cbcf54219b8f1136c831912f0c415447543fc6bcbeb1720c5fbe9ec3d9ef456b61131a5814e3539ee89b95c8d34aec6683d6059875b48a303ebf21229369cf128c464c957970c3497acd64852e629fa542b51f2fd7e961e8b3a6fb99d386ff7cefb9af67ebbf870e009a45f9aebe77b13315560716d02c7960e9d65d9750fc2c204a24a155fd4c1236a2f448b85b0a785051dc8d148ea30c4f32bd5615d4dc8a55be9840f8fdf2bb4ad7fd5a3660c76e9d33b3e3c3913e7b385c45387e98e6fefd570e581e8185c0d900aed186e8b213b4a7fa4e55cae720855fb99d581e923673788b8b23e672ebff83d3be71713ab9cf719ec9fa402b5dbe87baad55a938dd7c5dbcd7be1c0afb747047c75200b675540d303c35538929c79807f567eaa4f54b00adbdf1b9de1cd4dde089ddef9589ed4d11b3ab423641522b00fec61b5ff9b92cf820c782a1dfd1b68b62d56f0fe64f3899a4a53e3031eb720bd8863d77ccb66a6cf169387f8bb9e5b14df70d83abbb4060086e32b9a46836f1d822e5d2b75fe2743194b3fa107123e679084815bea38d0f765cfb624ee426286971b749c791290fb7d9492149dd345e02ee9d569407595b7f71317c59f9f522dd336f2998ea55b67f7cdf58fdf5044e32d35c6da63c5b99abd0c1bbbe16cf67fc36d6775e8d60f9fdd2f15ddc3f4ddf10df25dddfaf6f5c09b4e1dbb77a078823150ebb7ce31451539c2f6fb956c468b9df7c9a9d772ed1acddf953fc272fff25c4724a94401563b9b28974c9e0be1bbbb8d1d7a27617ce33f4305717831bd64b63c693dab28d9e53b299b0e4abcccad2fa50c4c42e2d09cd1159d781ce9b98d299358f60293749f877133f129d45ae83a2fa0e3c1ed6ca27774c68585398be40ce296257dca0feb1b3ff00c86e4c8fc2753d91237be5c3605a69312e2987e1ea30a3d648cff29d071ba4471bc81d680f29aa0df645b44c1c1b2255ed82b6baef3846702633be89cc627fc4a4a21f48ac2ef2d7325311fe1a8a2b7ee8ab4507fb03ee5ad6ccaa2c932c9569b5208bcf7a6df8046951551aa344ab9da09a0da8ca92f8a71caacc23934bc264702f593ade3a99f5b37162067b6d75c8aa6012d8d1d4340610c5353b42dd6e2ce7c6744d5f274d8d909ca35e7bd77d7a7eedabcc14fcd79c4191c084a85409764ab7b172540fd00878db48e6879c61a98b3d1acd830fa1d51c26a5dbfad2ce2a4e07f5b720b4f10161b448bfe6e643af2c2acf74e255753b51fb0be3d4f745e7104dc50393292334b8454b316309d417cdefdf1ab7f52d05adeb89a8720b89462c122880c74ff24487a08966b98ca1cfcaa303988b3849ef24e10b8b711a584e56fcc0f2be9cd0875e19226abf134b9e9394e5f8f36590ffcb843bf26b23f2157afe0b340f44baecfd54eb30c4ee5c252c02a8611d24c561f531fcea81c58b31d7644c8f981ccaf679c9b9c65a41b3631ca24eb89c399ee9f42737fd6326bcf97a927b7364346028abe09151c2dfa0f861810275b0ce31603856db06adafa2cfbd1695975e720031568739db151bd2fc60fc64a5ecedf10c525b53cb181c9bd825ddb2a16c62c6bea562a32b952f2b0091be5694582d5a7cb91d5d3703ab44ad475502ee95afae0f571135afab839ca3701b10001129ef84917032d3a4571d51e0f5c3d8fa8bbffb04a0610ac9213570cf4c8e3a07c4572db79149708b87cddff9d08dadab50bee9b46625ed2bc3d2fee08914f0d8aa789d94a7d824ddd0532015a72c27729376a05166793094fcea5719afdb9be6d1620b6f8c932a227eaa56f379051a45bfd42fc6128570d7b9a39ded86f3db9b516f78d51925449527a1d3a0d27785dd62552c9f27b0a0151032d0d0b954ef7cacaa68894c6665dd112f807d0bd177c28623461ab0edccfddfab95d47b7c5e2198966ea8450625be0f8b0dc8f113723b1cecd48aa912c7d800e66ba6eb6f8557d190a3cf1b0c5176fc5300d98cabb3c82e7c996afdd02375a28f6bad907fbb4af95597766ad64958f03b1ece9234e6c63c2d6159e5c8da2266dc9c4dc79b04c33b00cef62941e0e9c510a8ebac1b2f8d25566dda4bd3d2eceaf43439f2ae4230a8a20e3fa96ae9af92a00d29e1303c6d809a58c714371c08ba953bdc7131154c8d505a2adb2fd6d7a2c76f12bf28e4b861b35a77cb99220bb210d84dd0fd34cbfc48909c891e4abdbe2a59c45535ba0ff81ff73b3187e4f2eb41fce8ffec001405e854362aab065baef321e50315fea1ceac195e2cabe9971ddd8ada69d1d4bb2d24d7672061b180cb16aabcf1e01d6daae3dd13cb176e76029296f749f5a4859680b1b0e3e67b2cdbd36bea77866f464280c2da56b1b4c6fba573a55d8eab3fb693774c1a3e90624017050d1bec4e9cb1cf0b11155a8312b36fb97e6cb0f7feacfd218696d51dccca403f12be687fa7f3cff5949bcbf2c5b1b5894753d10df36cc8de45b00a1b032aa600de887c20bb18169851bc2ea2aedbd25338af4dcceb39eb92115c7c673aab1caf6e91b3e364f200827b876139f17b24d0ccae6c4c2885b5cc366398a87cf5f579eaa32fc8e7662f279ed3488e402d5dd03c50678518b32a9112044de3737b62a7f997d4799d7ef5f690d62e8e75091c0d355e6f0a201c176069f21ddfcc6784e1b93334114661760ba7d0479fd46560b32fd0b7a10710e1c121430973959a41083585708a77a2549ca91e8704a4361527f17cdd67602e69eaa11724b28b87eed54661183e4788370fc37f16c3d1c4222ee6391c82322501e0a4fe23cd7e9de959616b44023058e7d86a4a299767ea6626b5a4d4c6f2525ded3ca2427b2ee324c601856f26833bb09b6513889f4390f7e4d20ddc2310c3a4421197f1bf4694d2346e0033aa15e67ab482353b7e468a37813baf6cb32843f1b2c5ec3cc3c3268300955cc4574553800d1fa8a058efd06e324d864021c86999942a017844ecd134e733e3b21c3dc7bf87453e8439e03b684292075f195ae9315e1e5db5f898352a65ba8925be58ab148f2d4d7db97b3361943870144b2ca1b4d38b89ac841e92ec97233ac91006aeb2e1f7cfbdfb9a0e1875013a4fc9d319b33388ac52522f2f0be545f32b0dc68c97b5693853e57f63f1ae41d5bdf157cc3b3cf61bd8eeb5bcacb1efd27fe7ee88de5eb98278287707cb9f0dc5fb3d9e4d773ed75e543df6a7afc8eaa3a92d7c250dd57d40105d72402b601b3b11b38b3efe02385674cd0de7aaa4c2161202623f02a3a1ad9eed9911f220ad4590a221d6885e8337d5fc0a6fcf477d0a5406a7c570d72be5534687c91af0ef02ba7ec2f3d90aba888a61c74097b95514188141d4d42583b1fb018fce714a3553905a2eb961a4ed34ff10ea6c8907d2f8a3c6283e90f273a4af074666da21b9a9bca6b991929389673b99e772197f691a97e7672219bfb7a2e61db94d069bc2efb5bb5ea612686196e03a93ffb0ee0675dc78e0e9a801360eb860bb50a3323421f190c503b02b5c22a36cb9bec203b6d10b21343e604ebfaf15f86b9524f6887028604c7a9617d553d73cec9ecde5f77ffe6f18a1f15f682aa84ea54c02782e035ebd049ae3b52cc6cdf53e4b325916d9e2fba1f27f007badde11e1f2f104e8e7093966e5f6e446387b0677043638ffa036215b7c13ee2d021e243e5a2c4cd8c571a7834fd7d08de9f7d203ee65a6d87c4131cacd3df56c0bff7d7e6abe6ec4590d4d4c3a0b50401c83fac2f8ff2171ce1cdb90916fd1ea20807d00dc08462468f5773e8927497d0d7d28f63920a7706d754cbff9e4f609e77260d01d2e9a9d54027bf6ad58d8cbe960420b0bf2dfb04cced5f3d9aa0ec305b4be3c2f66df94fcab9e07040e37465c4f960b870e4c73a26be1de4d2279c38fa5cf20473f013c807b44ad9b4b72595d83a6151acd144269fafcddfe6da6ae59b3ab884dfd33bcfecff65e60bffac107ea2fcfb73dc2b297f4e132b1fe6175f2c3e8614e1ffa09f242447d2ee29d42fe933b6a51120680e412b76a0889406ffbcce78eec26819030df442b2dc17aad1795e6fd2906d097c6f3b2a9c98726c70b23fbd3e3378343631109be60c502db2589088374753858e9e25ca767c749b5398dcb1c478c7a7126ff71865b4f4b20e780eca582cda1c2d49f6b23628a0ff52520d2e24e22df5e72bd9b3108c6ac06ffcdd290a29bb23e94450bd2db9f9d4e3a3b42a729878c23d2e017e54a1221bcfe1b90b63bf4e0383a43fc86b529f02ffed244167dcbecbe9f33f7a77cadc27d9f671f088b1784f8ded3031fb27aa7132bd1609a99424e008f7b37c158033e38b466093f1ba932e2e73a39e61317371f37a8c656c67adae8bb57b336d1e82a1bd27a00fe5e7f2c4cd41ad8024d21b847cf94de789adfe876146f8eafecdaaed23aae395f75210e765fdefd0a5d51ec2fd6db6064a342dce3763d397b35e206d51fb19d3dc2e6060409ece1d5f4e89cbe586c7d14dc7365588c8d80309ea533819893f7c3c0becdd8373fb3df1bede17f9527f7d82909e3d88cde80eaad4a6cceac5fbb805c0bc2eca621db970b53d6ab435c11a9ca2c5c91031336f4fff5d20a0dc046a6dc431c36af1a01cdbdfc8d35877300e751fe03fdf6e9fee56d98ea8469284c3c4ed9a6205bb6b5d8de6af1b4006cc5600e78e2dcaf36bc9b112dd6c32b9d097b4d262757a7d02c6dbb43db3c569d709d65ef2176e8504fc34a3450341a3eeb98b33a62dc3f3acade9b256ff4bceca0f69001fa33f9b3b41f219e48314222c9117c4e0703489d7f68496f0747c91c2cff80e0b4c4b4d6dca8b3e3f102c8da09f5e28cbfde5853f7db5ca2e3dfc6600b4e482a06539f826d8cbbb93cf7d7409fe410aa906d55e8faeed1c086f9dd34652950b45ecab0f6ee802356b56287f1cbf22815e13a5cad313ef83c80f961b4346aa3ca700fdb7e49627b1b451365de300fa41f23ad86e03beb088a2153a0855bd798e5d6c8f25c3fb4ededabc6ce97332c52c9d676d891c2ce69098b11c4f8daa0cddc7913d07db5c30322caf135a77ce972f9cffe0b87346868bb3f3ba71fec0f3a0366cd9e857eb00b373836e81e879c0307bb3c6679e405661bbba8615bdc18b85e381dbacd35e291dbcb5e89ca892c1263bc596921020ba6dd1d720b6dd9a94839e276d8c42cf33059db88f45b50b3e3ed2e11a0b222594c5a85acdad9eb220c2d401b0167c9a1afab6211e3bc680f247bb1c771e97c04ec93212fbace26356116923eaf09474e921697c1fc5d9e95057d205bde923b33cd9b1e984193f55f8f29e646b3e61dfeecc7540d14d19cd3e7cd7817e29e8e493c84500d592e3f0e4f99d4091c64327c6b3fd95f0b525e0acd8f97e83361f47e7a656cd39e44012c44ce0944203f2772012ab4b8a7f96483411857528349b558194c82de0a5ca12d45ab3ae925162e259976ec3ea39d561ce331138041f1dc88496644a06ff1d6108698dc07361dda12799a9fda9d4ad955584af9a9fd7e9cd2da40d7038dedb1274cccc073a41b84f55dc42a1ea75e1b7522ce34c24ae38c4892c6be6af39c1d0caee38b25f5ce133ec48265c311c6c9c1bee954191d1e6d7a26855926d0a57ad8a9ec50679cfb51798729070490924f03efe0ae295ea553def32b1e9db7396c445d9265cfbd787168874e28e30ad4bbae558210681ebb35f2d8cbe5425618f1323143f606f24070449d1635627829ef24001cdb77c8aa9f196e0d571b1f0d8581d1bff0e8c6850c72106a79392df2b00b397f3b3fc992296267059cfd5f687563fae90f365eb8c3bfe190dd3021b83328685de650c15a435c6d3af8a34284bc1d0471131040dbdbc92a5be390c018b85d40116e8631d276ec5bafa56f133513a88e19281ae8dce69a1fbaaca64a826ba6cb3261821bb08a5b09fbd6f718d3ca4ce6a81e6401132a7c499ace814041f9edfe44282a5651c80a5d2802ada161f7dd21de5838c47b4f8c56630615086ef6d6c3538780aab59b4109affe49b801abbd4ccc9fc5cf082cc0eaaed10211777fb78c9d784a2d1313b1398886aed2634af5f375246d0bbc316dd03b563ed5598815b5cd5daa828a8e72f17dda5fe1a66d08f8a35386b301f1ef6eacec546d31959903e704a20392cecac66cc85ea6c079f597d49d53fe633447d60db2227ab87b00d285e48d9aa4a09e6052d1c0752c6a5d3e6296878a64ca86b6e16171f825089f6f26b6e3c6f4a9fe4b0345802a7c8915e9f87e918eae94a2e39af20948661907ab3c64833b65a704c7d78f0542927e9d84083ea124406078600514dd50302675b08a9346f9c92600c95108dd1a99d25cf7f34c821b13cb8c70f02e37b5e62cc3d514a00f0cd1ccf106c6adfe27330f5014f54ba2b93d064f6c383b5014422e213b1d3c3bc7efedc79d4723d99e085c47d1b0eb3d069afc8a0db04273551e0c4a83ca83b5b1880ccd7b05d9c302d1f9d3a99531699982685f6e196fda1d1caa0b8e2f80a715463c0853507e77774e59d3556c68427268bbcd8caf6e88713877171b9247a4b4f54c117906fb16d2d7c483c0db7a2d3089bbff5c7061abaa9b34c52fcd47a8fe361e793efbfdf7375c9b322d7560ae21ac1349e75d3eaa740d740399ae99b141263b661dc64016a20af3b6cda57b87057a2708d204901d1b33c052a2a05fe818838cc52ee6017d6f54c9ef34c483a1b7ba45c45cfe6c994df8f0e1173646a47dc5c572b8a93f5f599f91a63be45970a07943aba97560a78c8426fa1e1a57e270214d4b8e600dd67d02148e839685b5b0aa0af5228ecffadf0a420d60b66be30625b481faf2d61a08743d952c7d03847b5ebc7e00f6144185e53cf7d3311f920c281901c8e29ba3c1dd4d8959e58d18df2068cef7f83dc973a8248a37a37db416e748e8ce387259e16fca5c880666e555163e96d04f2746bb081f208e2d7acd93b2d1108608bfe551a545d8b42b1c6a01ce944b24a9963faa41b4127ca7fd6088cfeac93ec251ed63ff2552e94e01ba135e8c957dd3d2d99f0c2c1bd492858964f1ccc2a1f47ffc3ba39b407a3f282c8602b964ecd2695bd6a5a0622696600e8ab8d4546dc3171c1f15c5447b4a26aa04452370a32d2aed72681c3e6d20ae24f1152009745771451182a9c5074af912c62a1890997a3ef106a2fe3eb4e5c38e269bd850c613793775d83591b1e412d092d076e24d807ebefa6092265c2a0c3c275f1cdc1e2f9c51fc851d0651fb4bf95af9ead31c76ab857906d0c8b97dbfbed6a9ccc715f3e3727c56bb62ddda1dca16e7f40585378fe9cfee213b9e9aff5144019e00f5b53bca0f215dc6b93470361aef31619c427c4d5f8dc13ebb3d5b56525c12e160605dcbfea7e863f403cd653d247b7c8c010506020cbe9fa69484b953f105163717c65034da06b9499bc6c8a546f08ee305ff9691dc7b6899f28bff42c0f747d6affcb6d042ba7fc43aad1c274982e6290af63aed739c056582994c2362c20d110a06c5918b6231904c33c60341bae7859566bfbb173ec0f452035d75b27323da8ee06287337f3ff27cb5d9bb20c2b2952b8668fd74986cb4eb8c13271eeb5525559d4abfe5bedc29f429d5d0d61e74c3f3a60002526df72c30bfdb39b201d28a69dfe1dadfa99d6e7d1ae6b70a1359b937a57f7b88da5f72287deedbcdb2acc3b705d5a8884a9043fa5a3e81249f1c61a78ca5fe72e0936df8c8f4cfa866705e0eda96fba4d7eaa7f90ef3748b15bec1029535895497a5c552767571ca8b60a5da7062f24917e83a44f25975b2cc0ad51f42d8acc5bc0ef1b35b596069a784be6f8210a883b074fe8186e875a062fe0dc68fc6c086fb74281e96f1960a4353cb5039faf565260c95b3aebf4c6ce34d9c8f00f582ecdbc876b1d3f65283cac3e1cfe5e234dd23c5f92ae4a9e34bb23c2ce38e6f39d0a88d81f4125a1a0c6b7d66470067b5497fae40abbd2b20ffec81dc77c3e95272e8dcaabef4b4c39ac90d1b298519c5ca150ffc04cc3a178db159bab261b827792af9f3a0e3dfd9a785bb2425bd46d63d56fbc18bb6672578579e3ed120fbcf688c09dcecc3aac68c1cbae97fbeec2423d7cacfeef4303980f63682de27bd772b8cb77276e18ac50de6a29f9bcdd4e2ce77805ca3dce5c4ce78f5225117ea44acb817a5dd81b5d4a905747dd2bc41894434475e976242a232f6fd755fcfbaf04133af944ec4d726624287cf2a1f93728b210936eb92ea97d57fdc7c7c7f848fa9f3c0e623fc7d93327f1873c68a3ffbb518cff055a5fe30377d78e06c47147770b85b74622cd8c5074df7de4f12c322eb0c0535d8929c94442b2a47ff638c71ae58099eadf758418dc0cbd02630acbb922b0ff5cace08258ece0583ef247b2b89b9057cf3ed11bc12c0ba89e7e6811da46e0f0c4e0f0b152a2fd7b8f708025d576b28cccfad70772693303c7bf9131e1de5c1a18505e4d4245bb281d5116d34c5411de983f312cc55d05d3d5e7fbc04b01057a4bd7fbde791b06f18d9cb332397b9767c9f1cd0b645826e97ec350891aa671af5c511a904ef0ecf776393c9a25d207bd46936bd177cf0b8bc896d8c26ab3fa56960796edd0ae01f5b68d8178ed138b1363cd4933a2d3f445abdbaba2f39bf57e31fdff79b70fb05892e425017a0e31a439f35d117b8f8b99dadba23dc2c9913d0c2aa45a0f81e19a2fa65060e9f54174b463e0a8e9c05f76fc0ce81d175a865f0e4019eee1509262b8d931dbd978ee5e88260017b1eb54438e3ff1f4b37cf1079fb6c2f263d9563c42adc4ce90c735de3282648ac0dbb99828f6f3e4dead06140d9615a9ce3d2237358e45103cc258f8126bfc81cb961af37a707203e51330ecb27314a761806e8a2275ce500219107e13416a208fce318e28bb7100f71075562ed101dd64537e1158ee9b717d19becad445f978e2a820a85a9889cc8786a9b15095929dd84184b1810a6d19cd98f4b6a18d88c743024d1d117dd7e1cf3b87c450366b0511ec0fd17ba8481fa21bc244b5839cf0618b1a8f97050007f47f1c73a3800e7d9b788efc4a782361e11d17fd334983725be267546578f414fb24afc4635d9240e27795f586fd4de642412c0df4881ab211db9a04b06c40d5f268933ae10c38918da11909778673bf7c852989f3530ef831779fe6dcfe1dbf8167762d4640dacb0edb328be0e6ccb022da481bacf0f58bfbef72f68838b1aa0392a913f071a1608b15faa6b0bada2714f951508c9b2826c32d0ad037474ab4bb376014ee9fa8ba4190d3102e013e4d1a60898a660aa5e71e32706295b9b22f34902138c9bc7283caa40d2035afddc71cf40d1dee9322557d8875e6758095375148155c91c4ec837e689b3ce059c6f74df5705128a96b7b86a2b8d0969e6d6863848836cdbc1f2a3cd45b11c1ad9d49bd23b1bf74136d030c17a4fff27effee5ef036a59fb35a687b0aa105c5439a5518cb1010302676e1810daa844fa17dd0d144188a4f323f96a9197b3d49b435bdb1ab83f741dc126a141288de228468868121c7e668a2160bdaf1952830e1bc50cfcd13663e4a5d2c1993dde18d8c0f1db6d2206eb12032900f52744d972436772c7269923c904b82161b95b7a116fb0d801c52f2ba6f210105c272c785e7c369218f67ad21e25da184166a84b4e85c2478f19a199e50f60e57ae1424c630bc8da04be847608ab8b1e6a90a4f74dad9dd727027b10c8c0051de4cf9ecb1f4925863587f494508d0e90c1c517911f08647788ed91a03747fca5f283faffde86c0d8aa7074f311891ba265e615f62a9c50c40703f5423e9e0f2171dbc5a9edd6e750f49cb8bd3e2c7867fb4114d9611f48f93bd31a484ea42538d6b456dfcf61aaefc6d4a1ad4c0033090099a7470304a6f026f716ef5570316372c46848b2010dfd6a3e9f0d189ef1a99460dc618c8c96575a1d7da459f81881c102094883e8a8dc57c44aa320d82d422921468c439e4a85a0a19fa250c1a6e8f73d2114cfd2a8e9bbe03f94bcd9bc62a043592a8d39772096bf647fbafc9d51ecb17e7b1cf228eea1007310c3a7e91ed212ac809bdf216f0da0d71c90563a68ceeb83e55797b32ba28d0f55c582ff0152a671da01b863eb0205eafda95dac0920b93a4e1e7bedce463e51948c8c00a05342c40108314e08d19dca68ff1682f7cd2bb47da815f800e2337ceb757fffebf69c21eb9842d60133bb3023bc6bb2c35f0996de5f02972f59a64813dfe3b0af5bb5993bcc8bf1f78135ae35e93b5e2b5f463264469bf747166438daa127afa85f6080b84b2147fd3674054f07f4a554cbc3770c98da94a85d8db2b4013cbc6c55c3d9f069fa9c54a125d4ffdefdad7b5ea58695dd6afa445467a8c8028d95eae41ede5d5c7d8d970bda083faa35cc397e3359cfa2dddc890b38dbb6090f5613f8ca4493bb4d52f32ce1911e237765248fd99389e832a920771e526b15d79bbeb5c8116084e3d8d965702a050a0b5cd5dde78a14a1610adcc692bd1643709cf119d8fc362f94795089e085cfbbdb01b18a1abda2055d2d3abb3a6295937addc05f5429e5bfd20769c5e335f2afb46c1cad3d5da45235c7387a6ce276887980cb3c9046b8de5483ec846343987ab0cf4489dc76ca1c36b8afa78d5dd886b340c3caa776c1b9a3626e44533212b7005a7ac474d395e99fe40396d6f351c7c7c9f1ae0aeca640f01c4005b8ca12a07041b3a8d593c93c7a167cfda8bc5f5a0f10f2afb87b5a9b20a46a3c5ff58157c3c500d0f8ff48985d8774edc9fd0eee091b089a5049db42a01eee1abc991f12c3c2c288f2469f033d0ea35c4ef1197d757da3bbbae230fe4c94e0fb96e35335e60ba3949bea24ebbb343c9954c89aec151ff4ff9aed61111ef10ba09b2abc388532ee89c282e25c228422fb7e88c7c2f6c4aa8af1c858ce47759c43b3022c64c3f2b8557c9cc2ff477a29658cdcf88f333234209b18a9465ab16e3ddd1182fc385d6047877b03f68e3a211d1a6ab1a6dd63f48cdf39a83e172c1a1630052ec92a7efaad6ca8934eec04c6ad5f159c80996ebcd10dd0e6f2f1c842d960e40de0b6d4ba46d660a3f65e56fa1db6cfc76391ef97a02f5172776cb13d9319300fb2f3c3848e474a933b0dd2b8d74909e2b6354d1d77982a0ac41020cd8d729679d35657c26e4581bfe287e3c627a32948df873910aa91ae9208587fb4fb71ed7b516b8540dc5ebb903397be49904e8797e98302a1e773badc97df769664a38d5f97a795cc6d00dff3d8435bdce68e30eda1a92983121328c60fcb80b7ce55fef46b22fe461df90a9798d85551d9ea090373a3802d28b332a9bded1cec6e8a0ebcc70a5c75dcd395451bbd2cbd431b84d0e23689e48f82b377be4e9ee545074df9aac50eb4ed7bc140836df50ca1fd58578c4fe0eb43b1cd12055753461e77455a59d9e0d533f50cb060a32e80c3e41c89a37795315e49c56814d1cb438f083aabbd0927dcb35694fa6eeada1ef5e77be61f5cfc0ea674e39aa4869ff1af7cbee6c7f913f9b1a50a3f7c827f83657524d85e0b791082d458c02d29d81b8eabb8de7f0a7f9b22cdc93b8e89a531b36cbcefb00fc3f69c1421c0c76002d71ad6dd4b608abca187888f479dbd05b363cd9fbfc1041193a0ffb2d829e744dc9b0246a3f193c63f715c28871f236c5f80a6ca7916fdd780a6cc9dabdf1ca0f08ee5d3b1c62852602c3f8b90e38b8345d8e0ec82c995b8fab7e0d5c26dbab669535cfe40959ebca40d0b5f1002c3ba07c7e7f18da53af259fbeb997daa785330e4bfe33b493be3e1c9879ccdc09d33fb76ea3db41b00a38770b7ae2702ce3bf5cfd9c1b062bfd24d3e47568ed8b066fd119b6385d2252f1cfed6bcf71778a3bff167371acd28f0ecba8747ea258fa6602fee293c998c102e2360203ac665f0387ee4a18db2d695cb875d13817cc3bedb00c80da3c0ac8069345f61c3de0a784ae14a17fddf7d1ca05fffd4d2f2cf872ba22cdaf0b0d7d4abb6873aaf6eb621e23468aeb368ff01e330cfba4969000c013b46cb5d3992eb434ae24b300c46e9ae3bade60d10b269f4e10c1fd11018b8afb1c9fa1f357de9e702f90a7e13373751f4f0b07ff0233c467d01e0aa52ba4e8ba475f0fd702fe4cbcbf997e6464fd142b725c481843afa321346eb9888990a253420ed2b40a1192d06ca5ddfd81b4198c70da29e56f62e737c801d642ee5c211787f23ec243224b70ba29b9f84cde748ae304edc5ddfbdf5f1613a37968c307a8d16dfc843b8917ce1ad03eee3b3fac554cff28dfca65d6bbe2ea039b98fa05f77b8ac1350a695bff65e76df8aefdd8de65c54bdc269e1f35f722c9f6e3c1a0f80ef6f6376d6d72c3b356a74f6f6dee47bee8abdb3ff9c6c7ef82382737ebb2c06e505cf7ac25497e5facd60430870597082c9a74a2732511e59801f20ab3da34e0949502da3687b8d7bd341f62b8114a38722f972cf43bdfc01e7a576937b369ff8bf8677a970b8f55b77716d6f7ec2de3ed46b2ba8f8bf39b11dca6399fff353bf9bcda003eea8b7c26a1ea4de7e453df8d9b688955c3dd73bdee455213ff36ca96e4ea84b4d1bfad264db939fb30fb4d5339f387e652bee4539921816e53e1916288c950b2ed0d859f731e802c8fc8f4f9c928d14b34f175c516ebba27b980892dd2e885604022f3d259af7226aca228e85d642e6c055ae2f14561f4c01ec63922810878124cc3dcfa71912a3653847a91bb47de225d7dc92c4c0d0b4319fe3036d0bcc76bc881624d17c5dd0692659024f764b2d84da4b14d08752ad56244431ca41aa85f5c6512bd5fa82eea686cfc42cb7936cc9c20048a953af01be494d520588e14651c51093b4a6705198c320d209c16a92290b5ca245c1f4566ec0933486f585e630972d353b3a7b272ea65af660dde9d1eeb6a1b022057f6c72e1676e320ce1e1158f622378f06fe2b130b99c30431126e23fc8daea13933b30eb3158e40d88818adf65edf962001f7404d09d80b08d40017029250584bca4b1d582790dbcdefa51e1e6912eb442732b4ed19bafd9f0cb035fc427077ebc09d38f2be33d6a165442cf5f719faa8d3d04a3dcbccfeaeec3a8b18760cccd52fb282ce67dec8676d99e9c4b3106b2a1349595e4813f192a55a19669819d703edecc3a2beafe4013e0d050d845a6275d080cc36cdd97b0b5684414dc86f1989df1af81d9e780ebfb6486fb3d91734cdc34a6c22c7b96d789b4387fa464d1a048316899847fa1900ea2380a2fda201d46d3878e53336646326652ce36173c2bb364674e8fb4012fdc041c2a150598d0c6454edaadaecebd672f786f43bd1dd904d902cc4362fced751ca3228317b21251c5fc2c75a61671a8b1450c8897d994966888e735794b9ce0d76a64490f01e3e566189ba853ef2afbbdb0b34077fe5a9ce5d09de08f18a5607b09201b59b2731a3fdf65188ba53aa781fc613f1b7a808fa619ea71dad5304b8f69da8ce6cae67eeecfc6176064808e7ffa463e9218d3674ec5a5ef27c007739a6866cbaa71236fdedc687eea843e7c8ed182726ffb72d11994fc067f29a830b0e02e593fdd36a47c52661b7f5094915bc0252de28b070d3b4fe89e1143c3ec2d468e0fb1d0e508c8b0a37c4585ecb7643f8c3fcd584a590a3d9697018236790658bcdf51cb513602457506d1b1b59f34ab4ee81ca6a2740ee05474c92ed20401fa52ca929f304fb550348cb14dd340a5088f3ae49472e829e8d726f3257ef189bb975f568eb2127bd7c76d45b3ebc0a081eb4a2ded0fc11ad2eb72401e41432239cf1faed2c83d9a16b70edfb02a26213a1b65a0eb4bc6a94a53dac7987e4f2c0abd81f691e5de23a6a5a1a068c2b274ba450397f1c2ed08b0a0eecc4f66c9d8902a86881be635b6f7291b35dfa3fa597998ccb41768674e32aa4bce815302dcc271b832af201a76c3bab982ca4a37862c3a6e237fa3515d71307f41a04d3caf4ccb416fc932b148f52c06c445a56e947be00d72ddf5f771722824eb94a63183593571aa0bb47d15e9adbef94f807a80c7c26b379d74f33e12f334aa58b7488d6c541e23dd74a76e8bc972f87cf5f4ccc3c48a2c49ded4f7e702fd938cebb9250eeb8fbeca823ba053aee452912a70463c44514af781f9004d0c1045d9a00ddeb3c101cf7010598f2d7713a9ffc870fbf036c4531fec4f89e4ad2d70b5cdce872c150cc0b0bcd2e6cd329acc25d3911c00cc0d01126ce6026703aefd204b2ae482bb8f5c1730c2848fa15f8406b450f990a40111f6de0ce9544e1266c40ed3cb737dbb1b36961161b99234c45d8188b8f4126d7b0bc22d3b7b17fc1f22ca0721c28689ef194caa11e380172aceaa24ffae78eb4112de345cbf8adb4158f73bcbe681ea32c731ce15fadf04bc37fb63cef3b1b4fe9c78a8e4e3e055d538be5248b79a6fd38805330b1a195f8f1d8536fdf8e40286442b438e23f5720b15072e01bba6f56d62607fcb55bf5d805102008888bef3a8f56a4132e224a15233f9717119d53d4da062bb85716bdbfa611fb5152687c860d86834b11a885a7f673a547afcfa1a6247d153ee73b36f21e0553df868f4231447a3579ec86081214bc5a075db10aaef6a480d37352683a82ee6e058ae0a36dd947af9c5acc54403e3556029ce343140331017bf4a3367b321badcc658892c83ee05e730ef49e71f4140ba5495bc95de863bcf6d87e52c0db19be55a8762f6122ef1db171bb30dc10e573f5b9a6bfe63b5ecf25aa45ba3692472ebca31533ff5ef2b701770b45d3ec81fd21aba8dad410330ea0759edb2db8b03493529aaee9e1055277f9f648cfa76bc4c5e83956b4c360d70a9eb3a43cbd0a8b5f9869c15e486e0f42ef39416952f70b72f55779e6006e8527088475fac26f50defe533c7ce1a1e68e2cc3c68ca96a1ddd49e07d0624ca925ecc72611099e948e37b68c46984d6059e4f8cc69b44ba204e7248694171a2be53c2b40574b0c06be09c52b2d103346e8a955750ec633df1c445f1a65b2810701498cfcbe1d6fc8465edc3add3a63cf9775d071a2b944836f6ad7747fd1168b6293130026261f9266fbfbe0d6a1c3be3ec5179dfd3a9eb0a305c662411cc290c5e261393b2819c083cdc311b0093a3fa7cf8bd9ce286a18a26eefb0b030ef80a73347fbb26c36ecf0b1a4f5d51e1f64345f1786d422a904f72aa93a927439754f4998798e65b33507801d08c30ebe5bb2d45aad4ba524ca5ab9caf7cba79f1d1719eb049cee2505517e674ab130a27c41f9f778b8e5ea170accf78152f31207376bca53b86df4c38ab27de3a928ab0e789ee628e005cb06547c299652b503b90f947ff49131a70a71a4b032f089066463afa8262721645d46493d68ca548cf789c2838f10232cb557c5deb245cb90c0570cdaa742bf2632ef01485bab76d7b5c8f3f71bfa53e5e2a2a188772bb85e6dc92926f07e1f92b75c93a955338363ba73418703f7a28151da4299a2a84ac00328bb2c1e4c267aea960d2dd1abf2c11e674693970d3622c4f1ac255687694d69c9f19f0a6230fb2282966844e0e28f8d34dc466cb2f340ba0337f8d208d55a6a9010c5539572c33f570ca73bff9ccf3042388c3e0b9e58108d67c77dff7162e3ba2a321070cd1a477878bd447fcedc7a3f048b000b5fde2e19887a9a6ffb2bc421683e298bc398f1343ddd5dee68cda8151a559c4e2276105c6ba9986c11e63fa6ed79a1ae6d1159fa2fae385a418d767f6d19c6a0496a6786748604ccdeb2271b0c7443cc356507f2265387b7558311576ca63703b54b206ccbc3109e65b930e70bc583bef841caafc0122e567731b103f9992cbd3d225bb9138fa324c3538569612f05b15e6bfbd51a9a95860c30c138f45eb2a31020c56a61f2a7a67c5342c195d481f559a7864ed0cc053d18857ad7b1d03cfb6694b3c4339980c2c8470adb8559828b633a0c4109dc1ed1ac3a8ee1390d91aa46e895ce0c2204e1db231ade0992a118d0f8c26ec5e7a88b4ee5c16c1eb4047a4a5b2c524e2c5ccfb54eb2e0010caf2e3076f14a13ce41e754ea39eeb44d955a898ef18fc9a15d2e7b7dabe8d251f992dc0db5e4d0161dafb7d9440913f7f9d6b600230cceb10a7ac9ef9265ef9e50d73140856eb37c5f49e1b00ee9b7ee457e885a5ac25e647aa4cd2e684b42ba7f2f7ad10ea57de05b045d6394b3b8f9420fabfa6342439e6e54999564af3d290a10280b7656a17414f1c55012a5bcd20b11c0c86b0f0e3a6d8ec90d281c633da6d5e340606bb6329e591bb866fc674bdb32e4ead3a9d1f33075b36333c5569e39753ba632639cc2c748fad0285c32027826747eaef2fe494ddd4764ce2ba06d2d9a34a94eed5f9c2b1af3cc8bc0a03f85e8b922247308de6ca0a8fbf00acd5babd1e540e40bae02bb292c62031e5d451e0f3e10a0b64907db742b084c98a2283a2aba1810ba4eeca0e6dba682d43da50992524a49701f3e5ce9930923d13fb85fa694524ab27464613a036b4da0b56d3b5f12940eea0d0d3dd6d98179d1121e2ca70946c6fe116fb66c55b283b50f9b2f3b051f3e4be5121d2cd4a8bdcae6d25319d36f3c5f72b098d24c2c3e0713e45eb6f1e40f94e080ddbdb3c749d3735951c32586e3f23f4ad8584937c245ae5843cd62a31bacf6bcb539d750467f50b5c08e0d56baa49841a49c68a1db8d6792a641b2f2349a60328db8aca4a16ab0d239d62c5bbe969eb5dd78b2705690685e87a371e1685252301dcecbd260b5e4970b1d73e5fab56b769ac1822c79e7377396fbf16938303f02192ca60f3a33e60f657bd029122459681d6aeabda6dcb1f78803c36a8df5a2ee366f75f6cc6d3ab2065463a5e58528a64ff4ed9b1d7c1a0e8cc65304481674c83554cd51a1fe572c2f9accc73a463853ba1831eb2584faa1e8cab965e81b4f18c7d680302b79b2468de9b277f576e309f302359c415e56f367e454be6d90a57b1b4f946a708e6858625a54bfb7c4b86cd1598a58c1440d326f88bd6e3c352e90c533c9118e6395c7312a44541ac7a8a8c4b0c37130068bb9d4a78d3336f4dab60d726431335af61cdb86aa614b5d70962785783d3adb5fc59688ca09c650495db82ce6cb8a999b822fc27eea45cb7275dcb6237299af3d962cab514cc7af34bef758531d9817b4d258eacef1ee3ec718ba63e1709a173058aabb5d27bb76442d35afac6d903d8491d5539ccaed6639e610abb4ab1f13a6a746d6438faf355290b1b3d79266c5d59fca5cda868f98776e3c35aae913c0f5f839a6292147fb992a93166fb1e83493e2d79a36a6dec6f305a6c38e681e845125700bc59834c5c65cefe7634734d356b264d4526aed92cb7402b6507cae3d06152b82d175e3c9c23a46208ccb8ae6b123293b1046c1b4447d341682ef5f4bae6374ec2af290221a33342b23538dd4fa43e6baf1c69333f0828554256ea815dc085b522fce438a68ca38637d8bee69c3d860d3976fe309b306d468bac336e9c53b6ce3960c17ac7fcdc4106b116e6b4fad01bde840353842d20b5a9056db7b359faaa38de7a78cd1c0b4c4efa2c84230c157bbe82947e7dea74cc2588ebcac6858a0acebcaf759bd112794fa9c8d206ce4b83df7e0768c06be74d891cd21662c84b8bd8b4c69d2d631453886b7919806df91152cc79e13db169d2a065bda78c274388d5da08c17a152ef39532932d632880ad2a36df89aaf83b07de710222d3d2a726e1b3eb54a464c2936fc7d0aa327e5be18c345e768193d7c771453b0fe716c4a3d17d9c5c49e188b531fbde7a3674ee6eac35809b2d58e9f9b9e507efe18b23ed55bfec4bc1342cd1ee70718cbb7557a98f6a53ab3bec711b234f553ee9c61aaf62c51525fac8eabdee7762b6eed70e3c972a465450313d3a2e1c068de51b032b17a08b26cbaefaf8920c8ea86dba276422935729c9208bc58de29ad37a778db6e2a35389eddc552e91a723755f74d31e60291400da785d33992e9c709b63bf725f8ba657271e37984d3791964317cca8fa2431b1db5db786a5e567a0817ab57bdc3c8ac39e288d8c61326a605c87a861a8b8a53cbc7a97cc381d1dc22d1bbc4a242703d7d3616a74989022d56ba5ed462aa9456b58e1bcfdf828714d12413b41268beaaabd27795995a71ba5d87f4df735dc61494c1ca4b4c54412986e548cb4ae29618178569896a64b19862df4d99fb9836d5369e1c1897154d663fbf34122ca8ac2a75c7c4a9a9bcf17cd8e100e9b0325adaec2145343e82354480e4c77addaa1d22ed7e866a379e9a940dd312b5d54a317aaba75be82e3e7ffaaf26e8163dee8c9ffc6532998cea21453433f0f4d17937e650d45d663512d320179a467742cb2ab9734acc09652fe4a5aa9c6eef7b0f5d39b6f1842e1c8d1a9c8e9107b28d5e8c9f984666a5f0379e6a703a4634f9852ed758e7b7179b6e3c93a6f5f43da8b1354bfdd2ddc6f3b9b04cae061b43b120b795aafc4f8492d5ab9452a605090b4b662d73ceb16b8409bfa5dd786a3c3d05a885c3d1b0bc8e46038010ac66e560667e5450b17631729490cce298d8796f844f75741004cb7da2b8debedeadfe379e1acd5621d2858a162a405488a81011a2e260199d0e22d1201f580c2aa7bac5a71445876ce3506a3cb0d49d55356bc858fb531bcf0ecc8b0a912e543219989698c9c00ec7bd3015946234cf438a68cee8c06a57de32d3a7858d62db78aa10e942858c4c26935121224445e3b292b60373440807966355ae1a4c8fb554d9369e302d51a320cc4a67050cd3121576386e8714d1a00da4ce0534a0c1400692a6039168328de674c4c117c7406a341c9823493aee022a16b8408489b1406ae046a3712f2b2d11a8c0839a0e60d161301aa0800326f00009703a1081074060020f70800322d080959616ce06186081054040011148c0b60452d6e0a80133011fcd09019f5283c501d6602d1430406a382c46382c0e2840cb0408909a0e07864560005808c00201500480170d5c9160381c2b1e839d0d54f1811ea981463480921d38494790496a3a0cb2380dcc1a900325a9d1ac01f5e9c01c34c088384a5a5e981a190a60f1c016e682a28146ec40008f1ff060438be7496171830a68442ce023e86862888135000d3632a111e3056fe471068b1c2e785ae08234145151020b0660068c1d2b383247191f07155c4184081970d831069136a4800b39c4c0228c3686f4618810300a01c623047d410314d081820304d981177358c20e38365d6cc00e383627d8801d706c9ee74945cadac01170c0312949052a4e618a5290621479e0218a50dc018a4f78021397b0841d95a0c424241109483cc2118d60041d8b50c449224ee64044209ee7e9801d2c5ed4218d0dc0a08d40acb8200e9b0f18a1422c841d30010d8e1fb004a0e102cd7b9e27152fa0c20158d080e7794a5e7b01d42180c70f9878430b1f7461831bfc00668e035c81821498327a6041812bae7881c23381e7794aa4f03c6ff8c00b1e7801d35901630b11242c3080850b64415890a00258b840164e89c11ca2b8e30b61482718e1294219629420e5481c2947be0a4ec4f13c6bc0e752b289124d28c20d4f491b382c753c4f0f093ccf83d22302cff370e079ac5843c923801e54b183941617b498c075b470c0f3f468c0f3f460c0f3f458c0f3f45040029ea747caf3f4f0f13c3d10f03c3d0ed0a300cfd38300cfd36300f04961c1e229c5939246049e0c1a3f7ebc54c0484cc3824198950abc40989517161606040d2c1816282930802393190402d8e1381018715971ac03d323000d10408f003480043018c1f390008be7e90180e7e971c5f3f4b0e2797a54f13c3d7a3c4f0f94e7e971f23c3d7a94a0a0e214a6789ee70a94523ccf73451e78882214772802149f3084273ac1093a36a1894c3c33b41002e18067402c52de1d4fca5379526660823abc1ca4bc12780567c318de161e14de125e049ee7a57fa060e279502ef13ccf1af04812222e02cfb3ad10cd0878b43b3c8ffe065a063a51c526d63d671001460466c623601a0ca60fc0ae88c353851dd010c58bea0c50d03882c607de804663799d3c0f0d1d9a3b684a349c97aa33a1798d8f17d05185dd47bca08d177c0f305e408317b8bc40189d350b714624ce88a30717cff394fc6ae6e10c22296774e08e1eaa9a6d70411c485c60821e1f70c1113449629ac66545f3e34787b349530c2de8440b06d182375084b4e0122f0865ca000b2d18411e28166841e6d90d388abc3b8a3ce1b9b0946d1449000b0a81892201e04497154528d960c10e58200412aa148c19779821c721ccb08426fdc68c67c6069ee7794acc3863db052b10431b588991b2023056d00032f42a2ecac8434a195f5843f3fa32f83250fe0754400515d440055ea800081d4d87d3e9e46341c40e227a205242e4101a65974102888c20858804c8b083089c65195c41861b649c41c6066a40860e605c981a1ad736c518738ca184e779504ac6d083a6bf13630831c5f33c4f49ca1899bc8e14eca0bb798e147c408c3b4c3e90020ec0e63ec421061a1658c2f3f4c6418c06f4c1012962943c4f127644c381d128cb11c60b438b300660028030cc08634818286889b0a38130be401833c131a40e6d94a0e4378624a08413ac8c00861c60c480610246807105309600c60f1e7c71df1d08b1434816841c799ee729119288e77916f0a408a143cb8a0b64d1c017e634302f1d8e1a2f2d312ecc697ad71072802f3cf1451bb620e48a537c410a232f2dcf05be2ccb172ef822026d7c918295568a060ada80021ea0a02405055f4081169ee739c093820226b030a7d1c0bc705c931041c153879220a360f7c205415282ac200818ce295ec491e2851b23f0628d142fce48f16203cf93e2c54a6bfa91002ed6e01a24b9a28e8e29d858e9441d9d3ad85899a38e4e1fd85889431d1d39d85881a38ece1b295c1422850b369e15f5a40031450a90423c2940daf0a4ac6104205800c2460a902c9ee7d9e4820ecff330e29428763c0f4a259ee7592305081552803c210548129ecb4a1a3f2831893a241109483cc2115b54d10846d0b1084524620e441ce279500c61828d0932f13c4f4a09f29052020b3c4f218c909245191e232e2b6930180d0beba010e2799e2b500641823ea490600329ea119f8727e53fa6f81152dc060f163fb0f821884000e20f7ee8031ff680063de4410e3cdce1799e12143ba0aca10e6680031de6208737b8a10d6c789ea704650d6a48031ace600639ca408631b018c2c060f8421c29a801285e10238b2a70a001c587279894b0818d32cc71451573b00213ccf8c40f328cf0830bbc610757a31157bc2cb600458ac92ae0141ff0831cba0b1b090c91c21f04b1840c50220f51d821461c27ff092090e8020a17e0d8c21529993d3c0f4acc1b4308920145c60e2decf3a064010b57b0421133aa40852948210a6f40615fbed302d601117400a13c010d27b8d104262c4a1290d0e2cc782e2b5f90a1487efcc8000b3230a4c5ad91828122a46420c5082845208209ca10da50953254be7818083ff0410f78b0031de460c960243f7e5880011618c01849d65003094a0c0acc8f1f2866a0bca074505ab0e245903258586286f0ba784bb3701a0b035d491b232f2f281105a23c14d750180a1a281a94173c0fca19cff3a0b800a5053f7e28921f3f521e1024e50159b4b8359e07a5080a0b50cc4059c1cbcb0b2371e1acc02c921f3f9417c98f1fca8be405f9f143913cf81293c519efcdab298b33120b11b46004aea345162f48169c38821f3f3af048cc8b0b1492850b14025bd810e6f292c5081c2c2326be97172dcee87c980e1323c9e36c01d361475e90248fb3c5c79c81521298c65bc9e205491a73c66ec764a1001b3c063b6d3b2621d1bc1c794951404a8a027aa424e00e0e60f1630309c8c1c73858c6cbca112d5e568e30232e2b1dce062686b11c69594949c1c19117226a7080a4f8f8c3f308c0034f8a8f23293e22f07851821404e4e1190191140458000f07d838a206b085b90fa0c101cd0690c0ce919402c8914200395208108107076cd0600603700386c1bca40820082811408100ca03501c90120037d6480980908793028034a400608de759e3b9a45481527a1c494149c4f35cf154d1837520eb2c666666dedddddddd55555555554d29a594524a29333333331325945042092594504209259494524a29a594faffffffff7777777777efeeeeeeee6666666666dedddddddd55555555554d29a594524a29333333132184104208a1544a29a59452eaffffffffdfddddddddbdbbbbbbbb9b999999997977777777575555555535a594524a29a5cccccccc44a9de9b575382d112e4614129e6c02019b023c6e3238f323c3ef260e3f191c7e6f1910790c7471e0a787ce05189c7071e73787ce02185c7071e34787ce09182c7071e20787c88028fc78728fcf0f810c5169ee7f94003328fcae381f6118a4d3c3e428188c74728e0d080c74728a4f0f808c5cbe3231424787c8462018f8f3b4cf1f8b8838ec7c71d74787cdcd1c6e3e38e341e1f7794e1010d3ccff380c7011548c2e212067c1c30e04a87087b796e91b15ee0e3b8645ab0706170035da016990741006192342d321b981608b3a2450686c319036af1a08b1aecc84b0726612c58b030172d3a31ce715cb4786981812f2f2e50069c4e8489698dad3c18b681897919c2b6381293060786e312848c24302b2ae00c69090261984b90ce635b3cb6c5182f1b98ceca166c0bc8c260c8809d18989813b0b030204960569a13470d222b1d188d1197e7791e14063c0fca029e07450109781e9494e741f1818200b8d22172c62a12840c0d178b8b30800c599a31c218638ca5594254c045186068ccd0020cf6f21c7c4290646184111123c9c764d102618218e1b0bc20d065c588cbe2048c114ea4974e86d9099bd8c34b27b389a4b30282cd32b0f0b18903f8d084193e3441878f4cd401134ae0c0064a9ee7819d4e70e2794e3671a209382e5187974e2649d3bcbcb023cc65a565a5e35e56de4bcb0b53030b8e17cf739289174d1a9c172c3e300206c3de911706c3b8b0040f2c71c5f36432b08543063c01841ffac08727aa5b57bbdfa9d65c3f537aedaa63c288952e7ef367e3d7af19a2636547e70c2e640bfbb52f62716ac6dedeb929d5da2a6225ffe49cb596f6bd739988952fbe778db5e73c3d772673ac6e94606bd59139f7974c10b15844c990a65d983ebed4e4a62e981c6245c40da36a47a6afd51b62a1cee7d247f5943ff55b88b5eaf9998941f62851b2614288a59a7d849a1e22a6c89ffa26980c6235165dd26c0aa9fab7ca6494263551c144106b5d46d7bef1dbbe4c4e1388952fa64bcb228c8ca163cf0313402cb66f1721c75864f42ffe613da4ce1f7a5335f1437eb2086143a9ccb5aa2f4cfab0d82aed97b6dba17f827cf8a8eed4e1724645bd87d5b61b47151974f7b4dbc6d3558874a17202152242543cb930d1c3aa6dff550aa1fc54bfefb135b6a4108c4c1f261798e46139d6922b98148aadb96606c3b8505130c1c38a287173e2cdf69f62bcc3f2d4d82a21f43a9baba2b4c3f2cce464fd54d9362ba5494e438a684aa0605287051bdcd6f8dd26f274c8840e2b46d41cf4572c327f0362328747dcaa3e91fefb37f3736ccace0d6e6ed4fd672287e5efe2839fae39841a4c4484a86432308e1d69411987b59129141dfb2695fc221c9632e862f34b2a9531d5deb05a23bfe71663430e2adb78aa10e942e508a7c345267384d3794ab9211162e4ed4a254ffe57f98f11da7d5598761ff9c633e645e334d985c302249351438a684ab660d286c560f77373d67cf48d3688091bd66388f5aaa34f4c2d536b58acb9a89c3c3dfc7c946a58317e529bcd2a59438ad2b0206aef1dbff89fe8211443888a0a1121ad7198a061296cd56f55ec6650b173d913a8008941609c1b2667580af18b90955b05a3e3cc648eb01694523031c342a8e522e690c187dc6bd5d1a2d3b92479593962e27cead2b9047f73e3407c5192605286c5528ac96ce32a777ecf840ca9b93904dddd2ae5de6d3c9b0e9331a87d49bbf142159b956b3cc1c48491c9c04e767ec2440cdb2d726aca7e0893307853ea32a5f386da458692244cc000f31fea3f047f618369a848987c215fe2e674d54bdb106a3a9c842b1d2270e318904c06122671ace81263943c35d52de56e3c3d6305132facb4de4a6982ee96337a17b607265c50ed6fb58b904ac99177a3a79a52cea942293dfe8d274cd781091c2be9228d2dfdb2f6de511097980d196e259379693404932dace8a87f1ff1fabf87d0c6133e8e8b267c9c21fb265a5870993d14d5fb448f35516a6192859558736dfdedb54f0d061a7915225dc0950e11215858719955fc8ccfd4356f57589bbcc194dfcf76dd2a2b2c8812b7b45075156abd26555829b9e7b0d375b28deb2c2654583155136c47e9354bec6dbcb3698c4a73f22293512122a43d152242543219a4d98139a225593099025a5e4ea4ac08adcae586ea627a4c514caaed4522984861217570d52bd544dd36893186ca162a4054b4501143c8c6984461254b1b9d43eaf963769afa8dd5abda124aa5103e866de3b97130ace35e341b981535342c2cace525d5040a6b1b82df31b9e8de9a71e39a87e9705e529a3c8115fafd57d76575a5144ccf86966174dfd1a84b260c07c9ca116e81891396b6d5f675efaf5d32f418f4a27f48110d121337d6a646e9c5d6cb28e993c1ca4b36617d6c885969bfe6103fb6f16413262ca6203fbb73d95057ddc6d36585b568603a9c1798b8d2799b59982c6121d54ec9f37566328f2493c9d4e42349ca4409eb17327e97df9c76b2b7f15421d285179b17e6452693c9b4703a27c864f6f345d38714d1946cc224094b7761ba161944ec923241c28adb5c7ba6e2c64f53f2828c137cb105176024e9705a608bda32981c61a14edf5c6aad254d4dd342b4d8220c215d743431c25208417eda9aa7d6c0a408eb918a889939e927979e086bd17ee354a6a993ea21ac7f7d8f944b9bee16bd3616a7a78e9013aba60d79e3c9c2b29286521c9808617db3850c3616bdb18b9e64601284a5aa58f35309c1f7ddf7028495cd9beb92f3f48f15fe60a14e65af8cb89f4a30fa604184208c89db2bd3846ce399494c7ab05063513d3af7d930f66e3c7506263c589b2966beb7d1db41861b4fa46b98ec602942afdd3eba47c931d1c15297583b7a9558229876639283f59e6d438e54274dcbce2f2638584e3db6da88bcad267338266c2cc838b53b748f1aed6b1b4f4d312849c5621a3f253bb81bcf672941c5828a195a951c7ae614d28da73ac572cef94ea1b4cc696b6fc414ebe16bbc11328acbf0f3a56150528a959ad3b49bade90ea9073b2f9c8d27291654082ad4506ad890a9da783218f6d2a6663b25a3584a35c614afc3e7dfe78dc3c719a250e6b19e2933474929bbd2751bcf7f43091e2ba5e4a674d923f51a34a25e6cc145a6bd3180a878a10244850c15224254d61670a5432493c970aaa144146b3b1fe64309a14daa5aa6261025a158c8c54dc7fe215e6dd04118f63299d414a2e48ed5d84bae2fa653ff924a156432990cca364a40b1124b5d7ee794b17cfb3a302de6e53fb112aad68b3cbedf687e7a62a957ce8f696b57ea88851335493a30af42a40b954c26a32808259d584e9982aedd93693f569c589ccfeff5c25584a2f3c6938b4c2693f12145342e259b58bae935c8df94fa44cd4b34b11cb6c6523e6fdc870c6662a5eab6fc185bebc78f03e3128403e3b2d28130415a389d133878848c8c6b7ea10413cb99e39411f14b986ea30a912e32190791b0bfb01c695961946c945c62a92bc82e3e841ee4f425e313234a2cb1f4c157cca2c3b788d9379e9ce8b292c96432f04812229b9abc44891dcbb1ab6ad517f9e56bbef1d4a4f2a0a4128bd5abb6fe8fe93725b4f18cf91c94506229c396c97b5153c84d6d3cb5062593588e3eb1e2a6d63342a5369e1a954a745052c74a94d8a1d7d87b286d4a24b1583786943db727d6e922b1ba31a7b8abd63dc6e728514a85c44a550d31f8d21942947ae3f988d5142ebfe634fdb7943862b582cf9363dfd814849b464923d6f7bf4e2f26a4d225cd832e6a70348f71583ac12861c47afd9499a6b34caf10a36341859e3e6bc5f231e7849d98170e8bd37cd21c89d1a4c1e1b0785221d2854a0a5480a808512122444565b7e7438a6890942c62a97a6ac536a542fe386d3c559ec7a8b4ac6c5e3248549ac7a840e7c2d1b0b03087163a97305c4ed0454a14b17ed5a6ee8c9abb9974e3e97282f42f473859928895b9e853442dc6e7e7e21c2b39c5d1132a76a83943c452cbcb58ea8856b9d8f0108b3dab62cc15c5cff70cb15842e6d879c7d66dbb108b93a14c49b1f4983e2116238eacde693b4a8cde20d6b7dbe7dd9bab9862278805d34197ddef0df9432d102bb79dda85bfaa1b430a10cb9f72cd4a3d7db2f887e5bc15c3866f599932f7c35af8da7d73cc1e52d6ba0f4b97795d520c616b30311f965bd6f8bc3d2f7bebba87059d45e54c31a8ddfe921e96538c15c35df80a75ccc3f24fa42c46f6203f7a3c2c56eb94bf478ad2b9efb0d89ddfb5efcf478fedb010da54e990a974ce451bcf0dcc8a1a9b047ee19904a6a1519a75580a66740655b6f69a33db78ba40168d834854b22609cc4a871df91c52447382123a2c86efaeac8e3bc27d3787950a3a45d1e343abdc580e4bfb5b54711f35b3a610909238ace7947b301d6bfaff0787a552b5a7ba1ea1e6e72b7983ae624fab52352d2b4feaa9527ce9b8791bb1732c71c37a895083f9382a7beab4f1d42049e37b11256d5809d1a2e731be965a616e3c91a4a1890cb26896931f4ad8b01a832c727cec5817ae6e3c372f3147940a912e3a61bc679788a2640dab41e831a598dc796b0a6e3c359cf76794a86175c76e1fd593cb085b6d3c3b0d4ba5e7d654f9dfbe6bdd78c244c8a2616110e64593064b4ce35421d20511212a0a96a061b973c7e62eb243cfac67582fc14f19a16a6861f237c34a6cdb3b73985a7faeda78b20a912e54caf88285b9242494c8b19083af9da27e745d232e9a8446de6250081095325480a87ca102440526068c4ca603d3e8b09608c7808f332493c964321065828fe3b29c69f2464919563b524cbda7cf35d658da786ea69618971232ac670a36def4387ed4a4369e28b5c4b88c617dd4d7f7d9f94de33e4d2d312e6258cead5b88dcf2a7a6f1379e9a97959461f057eaef492176f6e73c3e7ecadbd5c36635a4040c0be56beaafa74711bd68386f933b302f5df285055582cc106abccb14cc8446491c2b31851a1d36e651d36b2cf1c252ca25b3676d4f3516d1c6530de8a21d484607868c4cefc0bc74213dadb3ef3fa355efbaafd1b9a87a37dbb63b1756442caa36b4ed254af756d680ad70ac6fc510a5065b29a766522f4ab6b014821a2172b0d13f04d985d3d2c2711a16d6b2a212c3b4c455a285a5983b968e3eb9429ea403e30219e6654588cac2f28858bfbaf7cc3156455858e9b33543d61a82be0c598ec410415758aaa9354a8eda9453a84bacb058bdb7ecf9262b971ada78328c8be9c09091f9302db10a6b63b2e728b2e75c555f29a1c262f58e35629bca2e656bf1981899cc4b077e0f29a231523285c5d2b243d7586d7afe8d91c2dac7de6d3ac75637916faf4461bdc36ce756933bbfd76456f2467a77eb77a657aad2d5fb6a62ff6d53bbd8db0e85e5e0b38aa9756e8b0df548cb8ab692272c944d299752df37828f369e1a96981694232871c282bf99acb97fedb81ddc78a647bb4652232b5b7cb7c4b8a81b2b2964ea796465077fdbc6b385d389eae53b302faa254d582d597e6c855836f5cc71a24b10b7e2c2c4c86438d16545134b98b09aa5d65874f950a918d5129663ff9cf63d234d09612b61e9a6e432254e51d13a5de30531e2c2c6c864329997239c7c254978b6ed5941c4ad2dab10e92293e174a00c5c382e994c4b8c8b0f29a2b941091238a5e257f66467949e99db2a73cc65722f2ac43c298a2939c26a7ee81b54f789bda760324189111664acdf7d53fe55eeb5e6a2a408eb39ab8fbab0571fd4202d21c28a8c45c79fa2b2f717dfc653c3c202dd960c61357b4cf46e1d730df5f790221a1794b4b122e73f538e35f8ef5866322c2cd0a116c24a8d6272946c3f93c1e40259346bc0b7894b82b0fe6143e87f1f44fc126f3c9f4b0ca7a3591694006179ae4b0599b1b6ba0bbbe4074b63bfe7fadcfd0fa5231c8dca9897958e112ef1c142ce65832e6584bb8c358deac162ae5f6bfb14130aef5876630945458995ed11eabba8ebb8d52456b687ebbcb5f832dfd7b1de27a4ebd053fb4ebd24d627776d993db6c4f223b19ef2e61f335db9650e89d55c1fa7728cc9bffd118ba96dca5c3e7f3021a723d63be8da333ff50b9fb311aba5e689607bfdb6cbc988d5db7c35755bc7759b742cd731355b6ba6d6747311ab17a51761bb72313e4d452c4d503976db7c18396522963be81e5ac57c13749973ace79165db6edfc45c262296c7f4afbf99a223957988951a64dec9ad2ad80ca52156bf16d33dc26ed135ca422cd5af3bdf2995dc104a42acf7ac3554f9b123733888c554dae65eb394ccd913c44af9327b61c2ecc45e2096b716196491a1e75a7a8058cb0f9bfa774aa9ccf787f5da2284e2c7d8dc3ee787d51a4af650a276edb0f5613de449a3a2f8da7f647c58c9fc2c72dcc372ab301d63afc5b44f0f0b29bf0793b5bfc7647958ecdd3e57ca5b7fbbc2c37af416c1ce676c63ff1dd643d44d5b8409a3363b2ce79a7d7adf55d5eb75580a396d28a26d86b6311d565410f9a77cb60ab19dc3da8f2cee3a6b4ea52787c5a06aec9de3d656598ac34ab0755325736f47382c871e622f21872939956f5828adf337c4603b941072c3520d9b2b979e41d80c219436acd487cc7d44ffbc29251ea0b0616944cecfd1dd3108dbaf61a177f6aeb913ca6c91d5b09e420b1d52eea23be62c0d0b6a63c9a5f422b7e4ca685890456e4ea6c8e9abcb33f8aa6ee4c81d75aaf6e517f9a5e68cd273ec12344366e4580a3e7aa7d2215bd568cbb060b24feabdd654217a08850c994101ca18164ba95a74cf4d8e5b3bb318d62777f89c4a3dc73c325f01250c2b324b2e328ab153c27560584e958ae8bd4c0c914245f9c28a8c3ae9536a99c24e4ab5b83550e258c9956b758e694b915f5421d205cc0b8461421cc50b4b6373cfc1a7dc1bcf5721d2854a260361565ed490221a1ba07461bdd4b2254f9dd8b7c7c785f5526ad784fe1e058eb5abe0734839a6a25ac7285b5809bd3776427f511f5c142d2ccff4c91c7e36f58ddac6f371fe6b124a1656ee7ba2640e3ef37799c96432fc8c850539b6cb75d8ae5d7206d3616228942b2c8df95ca2949ca5d79c6732998c22c96494265f91a49c028a1516fbd7fcc1d8cfa16b321b2855582ce37b6df51d73b4887908285458fd34f62a3b5ae718fc292cedd65ac25c96b11b631429acc6aa9552cf35732829eb841285e55a548af52a4a6df7a137166bff770da6566f5b2914d6b74dd6fa1d73a921744f589999b4356a94deee1ac5094b25d68ff13a8b2f7954286e2cc7b0718baf1e3dc4ad32994c6f1b284d584d19729dcf5282dd4ead0161625a349f280d29a209038509ebb555579aba28dfc75ef5049425acb8c91cebd614fa2b273e028a1296ea16b793ab4ca63e5d2693c930276139b6dbfe62720d666b8884e5b6b16e752d55c76ee9088c907a27a490429eaf5257ad6647c5d05f7a8a6284f538ba37df149bf9b135c261791d5244a328454031c6ef16954585e2b344762844e0854a1d82aff56b46382cca5086905619bd6af26cd4fe29397a0832e816a9c61ec28da773841831c211728433249331c26181286dacc7ff2cf553cf92316c4258aa1c52d6e0d36610a94409c27a9808c5071f620cb6732840589c1a52f7cda973dc18ca0f16b142aadaebcea9906a33530e376ab666cc74141fac4e09e53b5509df636f5c04941eac57f59934556caedd48bd81c283e5d425c4ed1e6a0d9dcb4702ca0e9652ea8c62648f0a283a58b99e92738e4c2d720f37283958dbcc2a93a156159fc64c464304141cac64aa8f31528c5b19ab8dea02858d15d3eabbe45cfd7aff4bca1798959615964ec5820a26571d5b4af075f7092a96c607fb3b71f49692f5146b7b7d3d2957698ae5ca1362fb3ad345155129166aa59145a5aaa55f760fc2092996c3cc452822c81a838f3cc9c92896b7cb6fce3eaa7b5bf89a4c792ce88b944598b639f6e8e1b1d03e6529d16bc99eb26732573811c552999a833129a56c33b9d5124e42b15e836d1f67a7a4dd9b2777acfcc61efb1b3291d7c10928d6ab658f206bcf6f86e0c6f30506763869fac462dcd653a9e71c59636dbbd18927567288791ddce84a3dd9c6730db849ea05c224699acc21453499934e2c8eaccccdfd37d3c7cc64dccb4a4b26a349f7b2d2b2ad9c58ae238a2a32e71131deb7f1ec703430d0c8e66fe215b6b70e714a88a14783134db022f58f52f2ebabf4ff845e4e32b152a27d8838c5c61cb6c6c47a956e916bf6dca9f796c95c62bd6acd89deb766efbf62c28925d643cecd2d6af01929732f499818994c26a3c9b7a39167e6665a95ee093d628a32d539ff642a5662a1f58fe9f9197bc9f4d5e000c9647a48118de68412cbbdcca692677b6fcde2c92416dac65293136deafa491d0b6d64ec4566ecc68924564b10fe32151725cee54ecd482ce58960e417198caddc7b1d16d34507266142d83881c44af75ca9aa6ee96092a68901e4ffee4098953d79c462c4bebdce770e717b4d44482603841347ac5551d14bdeb44e9bb5112bbef4ab2da6849e9c2623563e975aebeba65c4a08d1b156ad2b56a53ae1e6b69345b4b77e424a515aa4fc9f3b39b69d3091b6e4a67fa2888538a955511374e69f582256cbecf4c98e69dbe6453e07ab44c8fdb33bedbb982dddd5356c2de1c63393c964be3b3047f406278858bcec9b63acd3a3476d4f0eb15264d19dd3b81b5f6b9e186245e888a17af03147adf5492196eb7faf8c7f9dab8e7a4288d5baa9f8eaef3de528d1c920164bd95af5d5c5f55e3f11c4e2965e53bdcfab92620a4626f39a544f3809c46af1b165293dd6e96d6c2780580ca5d4cc29a8ceeab5a7933f2c87305ba63f95d9b69d1f56734ad74c8e587583ad0fcb9335ba1457f35d9cf061a1c60f935bc794117abd0786fc5eb3fe86901e56eacc97e2364beb129de461a1a82e9ffa63ab6e639fe061a54efe58524ec41a25c5c1c91dd6a798ca7ea173b0f5be1d96a796524704618229a3d661f58b6959326a84de3f744287a5f0c5c89e7f82fd89e21c56d3a69852cc951f67d34cc2891cd683af908ac34a6ab53976cc207af11f0eebb90493a745ef4879f30dabe3324fe8598bc8f521548413372cef961e7bb91b19c26ec3caa71f91c74c4ead438d0dcb29a4cc137754e41aa3352c15d94b9d8add635dc8d4b0d6753b656a6d0cd1f2491a96fe67b2976f9bf2646c4f708286a55ca5e79ad2661edbb9332c854ebdd564895c72a9ccb014fbdcdc8e2ffa42cf1339168c49775f6c08194b88294d2ae1a40c0bed4be5f6f72f956bb4f1ec129c9061f17bbbce1c9172f5eab57132860561839f6e2163aa08356631ac4791a9962931526e671c8695b6df2b84186bfe670a86a5cc9c58367c9099bfb0d42aa6e97144cab9d588e0388963ad8a9c927ad95e7aa58ef70b275e581cfbb56f4bdbca3eee6e17167388f1a66baf1173ec77970babd53bd5a07aaa916bcc70acd5c4d4f63bcea73297b7b058448f92b704f7575f99b5b0584caeed23b5d0698c69b3b09439f8bceea5d72d3777b1b05053285739f7bcca14325f613573ac90e9b2ab75f55d2bac7f2cae7af119a9c4a056613517156adb9882cfa9a28d6be00b87b371698b132a385b6d2db5ea14563ba9948e34a5fdd4baea58d4ef989452befb792285852073aacde9a386fd3a0aeb9ddd8b889f73834ebd234988fc1b276f2ca5a2b276fafeb1942a663228355058a935c7f8f7c5e69bfa329997b6c2c913d6632aa5a70a41d4225428461298845293496012144e9cb0dc7b72aa8f14674cedc2891b2bed832f796e63e610b9d7c249135673af7b8cd97384923b13167b62cc2157cf1d42cd6565a38132587941553859c2428a5b772f2756274a58ea9ddab56356df56937486709284e50b5b6caeffbaf143ee2ce3819c20613d143945e4b4f79b83ed8714d1203939c272ded6dbaba831a67cb08714d1bc13232ca56fb9518c1f9973cb8ab0126bac227bafeda918b7130c274458f03d15235a841023f40d1e4942448e9321ac574fa57b28e1ebb7abc7d9c04030568e68f162386963d3116af694ebaf922bda64d4da366f0d6d63b52f319c152e32994e24994c2712a5c389109642ee5f6383f9a8d543680c15202a28052a6208517196232d2b7d1284f51a73aee07be512f2084f80b092337a91b99ffa7fd44e7eb09aa587529fd5a56a499df860edb657fb281544c87f1f1423939b5c38e9c17290d55b558fa9f7ae310f1663d852d9dfd91bd2ddc1eae575fb5c62e7ec902a1e9ce860b92f4b3121a5ba399883e5f4fdf75f2553978983e5ded426f82819840ae9091b0bf5534819c34e41506a52b11cb142ee865699da4d1bcf8e18990c6469998ce2334c50b1d85be68eb5b90833613cc5ea44ccdb631c9f42dccf144b1352ce2e328329b22fc572fa9ded5f4be6a79c142b6a44cde835c79e2af6512c7dc9e12a7ef7d8a9751e2b3a7ecefa92c1542f311ecbffa17599606cc9185251acf646cca0effba454e56a289663662a3f39f60cf7e11dab45e522ea559eec1f9fe362028af54fa94fce5f5372f0d1273435a776b5a839d3556b3afff8be5f73dbcdef7962adea4bcd18badae7669d588ad43183ad133fd4a84c3801f312d3d1681e60b289c55a6cd5eb4cd13674b9f17cd1c04e9f302f9c4d5a1f986862bd4c1d5f29067f237c2e130bb95297ee941b26d6b7562e355b95da58e24b2ca60c664746cc41c8495962359498f27431e13a74bdf1d46c3b628429a58b3129a7d803c2a412f9c9dfd5a7da7c6e4d55169ba36dfcdce94f858810153e98506271f68b9bda3dd2c4f91ae5b2da3099c4823126945472ff7dca8f6d1ca5c1a48ec52a3947ad9255848b3293c9d4a46b1b4c24a16054b95093a97fd51e89c5503388d22d522dc6f620b1d6c1d4defcfbb39dc121090c268f60f50a26b6eb2e97933a62318fda4d95319752296c44a3479988953956c0b1587372f50ef23aa79edac272aefd919d422869627e86155a582cbedc4d57cfd49a601696e7c2dee6e6d89a3eee680516d68bea2594988b2d1b8b7f85c53439f40e2ab7565861a9b742b055a7829a623b7dc7f05dd2468f0a8b5faf538d2d6e33e5535828be7668d1b737c69c93c27ae7e6450abf5323635158f98e1835f4d2bdb11e6be988bd366caf49a1b09c8aa8bd7b6e5f838ddb1396336329935a6516ff9f13d6f3ecf47ea850634f9b1beb1ba184efe911bb6e13d66643deefcb5046041513d6c3969a23f7aae4ccdd12967fc7d716bd65dc8a951296e2f6b8ac11276141d7f85e72ea0926871e09ab3fe933a62023d85ac123aca80dad1b61b1a6f853838b10e7272bc27a551615b523e8b9de4458df187a19379fa694dc0d61b1d65eb91653a96cfedcc66a8e586a2d1f53689b2a21ac4ee4de92832ab6fcb44158d9ccea5f47c61e3365560061fdaa04fd91b727659dacf8c172f04514d3216e8eec212b7cb0a2434eea9c436dec1759d183f51ab79f39e5fb8dc568050f96c27eaea0d3d808fa8b56ec60ed3b4ae9b98beab3b968850e564a668db7116a19918a56e460ed53c40ab1a792e2c8d10a1caccf65f1f3bd9552bdd10a36164c84b4f5bb26624c632a9627f4a49a4a0ae37b29a262b1b67b09e9b732e8289e6221c611e53e7ee8c18ed0148bbd36fe759ba9ae232cc56ae81e2b0613ae4be42029963fc698e1c654efb0c151ac44eabb454ec41c740ef358ee35b6a9ff3c9b2b87782c986cddd3a24eae614351aca7a0fe3e530d43b17edd3bfce8caf572c33b16234dacfdb8d9353504c56a0e32851f51cb85bef0134b2976d490795288e5424facc7eddf217f85546b09766235a55a7aa44abd5711e4c47a065b44d0b1975e8af16d62c575cb9035e69bd4c7a789b548a18690c174ac237c9958a9fc9f3ddfe82d397c9858d1d323f8d45f4ce7f72eb11e7a31ba72faea59dfb3c482ea29b5ff927b04157b762c7dea48f927e589157b9558d9f2e1b277e7a9117b9458fc8e315fc7ee0e22f526b1f43153895c268454fdab634117196c8c3d0699a67f92584f917a0db7e1eeafbf48ac77b125bb647ecd913f48acc8daedebb4d91852fd1eb196d79143f8edb1d33e47ac4faf997b632942c7f43562a9479f96956a099ba7c788c5fb5a6ae75cf22b941e1d4b41cf7daaadf3639b6f116b5d37c5be5f71728f4e118bbde6ebd45b5acf145d22966e737a286e73d61c7473acd6ed0da67faf45eee710b17c953e43ac295e97cf1d6231e71eb3db4cdce99c33c47a3139b68dfd9ac16dae108b39758b90a9a4092a7384580995dbd5444f93a3778358eadd99d2d40b9d753b41acd4365f2d6ceaae905d2096d34ea89d0fb5e4640788c5124ab82c766cfdbaee0f2b9fd9631ed92bf4509d1f566aeb9954a2e4fab06284dafee36b494517f9b05a738eb465f36467710f0b66dc46dea81b5410ea617d4acedbf3d490c107f3b03655a7971e52dd941e1e1663fd069382cc9472ee0e6bd9b9767a8289127376580ac15608e3b7f5d8aed561b9c75e7a88f96b17916b7458e8bb7d83db3641065b9bc372287d917bec9463b03539ac66ebabce34a6fdc65a1c164a1c3b358eabf121d5e0b01242f99cfbf8d4bb7b7bc34a486d2b53691d42ae9b1b96f3541035c59eb394bab561a5460af1db98acb5e6c686e5ce617ca4ed1541b86d0debbdd7ef97fd2997bb4d0d8ba37beab4b96a6c685b1a166b10b54685a9a161b1734bdb2d7136bbd4ceb09adbb6c89d428c9fa56686f5e8dd37c8de1b6c949a1c4b356e28b982fd383a2cc3624e19656ba64b55488605937b9ed1593f2704c7b06282cd557afc7f951e13c3624919e4975ec185ceb1302cf8ec2d17b66773c6181856eb2fa78ad0b1f6c4d817964a0e137ae698f79d62712c065d4ccc113e7b9129e685a5d23be5d49cdc5815ebc26a99de208b8e1f4aea1917165398aeb2f935b7ff0c8ee558b775937bad5e2ddbc252f60eba6ede981616e2e7c91e6a844f9db22cacd7dc21ca169173cc956161255ff55473e5dcf213bbc24aafd33164af30c14dcc0a8bdf5f43a8eb5e1944ac0a0ba95b55eacfb96d8454580e1d4b989c455de7e014d6c77d5fd697babc4f0acbe1d37dea1bd1db7c515830a33a5e94ae9bdbbdb1a23b84fa9f2ea3670c0a4b5d3f665ea7dcb7fb272c85f45fbb9f6a913577c2fa97cc296dfa10b37537d67e8bc9a93e1b45d44d585129f736bd67c2829950dc14fb39b625ac883cbd6782f17172252cc74a41c6b645169372121642edeb5c3ea51a43c24a6508ba0799192bb647580c3751eae5e8a23735c28a8821d6e60cd17b5684e5a0be7c2fbbf9ba4a44582efd1f8aec598c0a0e61418509bbc1d6767053b5b110f4075326c3af42088b354d10424f17e127fc2a82b032363bab86d1753af7550061b56b0e2946ecff796c5fc50f567a4a5f7b52a939c4d457e183a5ab9ebe8eeedffdaaafa207ab73b3fb39e5df0ca9af8207cbc56c664e6e5779ff57b183d5204b998af93f98dcbf0a1dac5fd67693bee247afbf8a1cac77e7e8b14f5e97de5f050e50e474a8fc257f156c2c5f951cc14cca0eb1fd542c7fa9a053d99fcae93e2a167bbe574e59428494fe295662c9922bc8b98953df144bf5554b885b334796be144bd73bb172b5eff4d3936269667c992eb6bec7e947b158e947f5e9af5b4ae9f358acfd6bae5395531af3f158bd0e2ab4082d6a07f345b1604acea1f4dbd843951f8aa5122eabed08df9ba3bf63317dd1b5f61f41b13e2364ed3c2d4399f013eb9383c8bdc5d876e1f3c4da868923ff7372efd689f58d657bca934af0c1e7c47a4eea1abbf46f66bf89e5b1b56657af923f7f4d2ce71874b4cb1ea3659e89f5b8695b756dd91b31b158b965c38fc839d5fa122b3b76530dbe1495536d89e5fbd827761437c66e3b566a0499ba863431f65c89f5106b7acd21764a2c6e67fd9efb8eb55a9fc442303e37729d9d1253af6335777764e69224567cd0a9f40a799b7924167a30592945f0536a3a2496738e8fbd7bd54f2f3e62b562d1a1b48ef7df738e58edc5d4547bbcaa62b646ace4bc717b49638acc88c5f8698490ff65a4636543a6f225237d7db68815932d533145d8dc3d45ace5e720f2ccdfc85a2562bd3e7b330797f9b939962bb42cc2c71ac5c71e110b6a8befd13f9522758885de6b94ce3d63b46e88b5afd8639932baa7d80bb11a6adf9d9417adb326c47a4e89993a94cf41e5412cc7705f55f4f7d4a2201647a736726ffc4d4f03b1d0b65e47b12d4ce901b158b277881b846e95ff61317cefea3aa66bb1fdb05482d06337d7b6a5d48785bea11715b27a8fc987155f75fac434d932947b5889e527f5ba9fa2f8f4b0f495a9dad6542f2dea3c2c849ab3a1c69a830bf1b0544bc99c8b2af21d96ab5fdd7f8e7658c9689fe2e6cded35588715a33b54ce13218690a3c3529e10a24e49ddf5b539ac754f9b5b6a88b966268785d2378f51633bf85ac561e5bb8faabd63c42ac26175b2f7732f7e72546f58c9bd42bcf1f719a5bb612954ed97f81f4c11761b964acfd2e16e8b482166c3d2e48c10437e319f5bc3820a42b7b0297dc46ad5b0382e23e89ca2b6eda5342cb4dcdcd545cf862fd1b01ad4c632d3535f27e233acc7767563b3c7de6bd10c4b5d7b97367f9f5a87722ce5705fb18bfc9cae0ccb5122965e8bbc09191916724895b1a6b3fe3486a58825e42c5b95337a62584e5b5c4f10aa8ccd6158ed57b1a7e9a1e45d0786f5b931b5d20661e2fdc2d2f851b962eea5f8108b63354b2cffbd775dc2f7c24ab69adad4bdd4e9bb0b4b5d7b4b94227acb2217d6f3049f3277231893e15828b296345dfc6fde760bebdd328c4cbdd5774d0bcb3d6aea16316408a92caceda83c5da1dd67d5b78ad5be65f5d25e61b5a428fe4bcd07a1b3c272da60d4843032b82d566129a69b5ab38fea34526169549c5454ada1278453588e1dd38eacd9f8dd4961c14eed9eaa9748914761414fa40fb3356eddea8d05a37e422db2c7c4984361b967fc1ca2a652217cc2420ca56a486db783ce098ba5bf5cc5981321e6c67a9e1a212f4b8e3dea262c98fab5cbd4941c7bcc84156346b6ad1fbaecd525ac067bf3d3538b4d59094b5bec4c758d63fc4c4958cca27622e6de43ad2161f5aaf7fc3db6ae341d61ad3f67cab9ba47976984b530555b64dd54e68ab01a630f394dd8323d23c2fa54aa219592f7b587b0a07b3fd46a7f1b25d236167b666ff74eb18e0b85b09a17b9baee87a85f1016728d50364e1fd3cb0784f5cb72f9f12a6776f78385b613a5c7da5daae883a5d15183fb9e72aa600f56db46dcf0f13695140fd6bac81e549e29d1f70ed633f4efbf9b4f79361daca79b481d7c8c5d7c9683c58b1e649a8ef92dc4c1dae6cce81d7f0f3616f287564507638bf03d15aba58fb129d3c88fba51b11c620cdbb9a79acef8144bfd5bd6dd8f55215f53ac6f2eb7156c9650db2dc552fe0c1373cbfdd525c57a1175bba89aadb075140bb6be5bb5eadb2fa694c76a6d4e69a64e098fe57063534f2547ed30932896ea87a08331956a7d9942b176d9eb67e2a8485da63b5627c48dd83db3a63213281654b76f113f7e1c3be627967a9ad4b2e4ca138be363a5b9317721c74eace430d56eb77a8a19e4c4529434c5f4e263ccd4dbc472e8d37ba9d7f5bb7f9a584c3f7a63cc1bb2aecbc47aee75c294183ee4b1616229d4fc18c2b40826d82eb1228c2f3f7b9f2f73b3c48aea8ab5dfc24dcd66c7422fa27ecd9fa92d5925164cf5029344a8b4ececa6c2501c05310c83000003a6ba2a00f3140030281c1c8e45e3e1380f1451071400053d62609c2e30288f05a38140200e0542a1301005310802310cc3400c4351b4a495d9015734e071b6e32bd76f7c1bfda622362a78f35830cc6fca9cd5ccf474f0af12aaca3ac0cde8f563fb5f404f09bcd982eb6101476d9dadc2eeb605371140cf16d643016e7ba15e13586f2db85140e9c142c42c170ea0779380b75af85e0ed806ab4eebc57267f960819b05e8a185f456c0375bd01301418f167278f3007a5261bd06d01b5690e4509dbba3fee04edab93982a995c62424654e4b489752a4a9a45a12db76b21640987cd22c6d48a57497d2a75c524b366930b992989424cd4909ed928a24956ac93e194b57129324532ba9249b549da4905a5a2443c995e2935cd24a956433a9a5624ed6a916af2829caf90bf7f29c36a7c0ed39154c125593153248e7240ac95243524a76a93ec925d79674b94673b852e42f65694b95f6f63975268fea902d194fb724966452635225ef9c9ae354bf8e288447e2902bc527b9a5b52b39db9e3a97f7d43d7b6ecc39700ea7069139d87f70645e556022c7cadd0b0fb0b5ccbd182906b714cc312d61d7d09b28233b22cee9881df9d37654c7ce94101473aa41a5b0e49ee2922c6949c56497d4a424a94eae92678ea68872d941bdcc1de9d1f7d4bd3ca78e599063ce8113e78a40c3ba6925a7261249b34c0e3064aca7c630b0e352e83a9d0b318dafc5b754335dae9a70d3adf5836a520777e7343065328199831660187400f5c902a60fdd436551690d6860825fc5250bf36928f73c9fcbd3ca553de94c55dc9bc69e3b17c724486daec0d933f554eaa9d7ece238e826456039a90153931b9c3a2781d5490bce183b80e2a4646510d48dfb3434adb35133ef8d6bed649c93c0b4c102684faae0724e0566270730372882cc310da01adc802a95f45637ed3963ec008a8312609b54e0ecb152804a1370762f4d42ee951bf5d2dda697eeed9953a35a8f5437e911154fb614966612192b518b94a57fe3253875ee821b730f5c3897e0b439056ecfa9e0e45c0567e70c7037278249a266524236a93a4921b5b44886922bc527b9a475553c9b4e8dcb76aa9cb537e6dceef3bcb8ba29d220e064797b3952cb5dbe7a7c2f599b27e55805b9305368478eb96b1e0770cd8201204e9046edb925db1e2d02b13f8724ce95ee6ad4214bf751c0cf62688930d65cbf40f22d11a1659c4336d4711fbefbe50b6504bd52f1c631a04bc36c9649ad6998dcbb48a7d89b926aed251d208de584938f61869f57d8a53ad38dbc483176764d6b11e09e3604f07d8f459c7e74dec5846ee742e7fa89728cc649f7172d46874473191d00f10849ae84c6f47979706f0603afe23f4b67cc4f826e4588534f36f87d426708e1c724ef0714bd8dabf33dda72bd96e34dc6bc3733272ec68ce339ce7aecd3dbd3a5390ca8df7a97e9de515aeae8cf813bb1653661505af4362ae2e813982c8b8d05c44ba2655a12b4e220a759d0c343478ca64788a2d20eea65e465cdbbb97353f1b6b1475170e95c88a5d3fad450d908e513a8a496855cb41d5b494ce7c1e6e6413910645297583ddb973246693e8508a924d4874b6feec0e3f20490e7b8f09966910b3a7596627863bc8ef59ab2a07fe7c4a2bb36975fe1ea00a1ed2db47d3918b786a60e8349c4ebb502b95c918e58a642969f377ceede664ca45ceee1b5c10cc2d40382db4e42b3a024dd25006a145b110b953fbd5a7b5faa12787fcd0ba43ea98b2b4cc65da0932c1a4ecfefda5e5bfc54bd773eb5ca2c3f59de3a11927ba171ebd99748f1d5b588e0b54b928be023ed65997b47d3b68bfee1d166b92cf944a0320ae660f0b117c1b3d2bfa4737cf3332df326345041fa5da4df4368a5d07c7f7e2fbeab5cc3359a87c3894afaed18bf254e46c214cc10daa780ba73679c1a3dc19706532c55ae163e733d8f9e75566905ddd6a1a85c210ee7a21470d888b63f1db1562a62cab24afe1b26d96215d4269a4273b51fac840f1b6f96fef522c27526e2314596f33cd8cea0dc5595719fc273143c1e43a3b4fe5756438ae9880fc6db0083ae2d467a36674d0af482b0c0df4dc3593b3e0b4bb47681bd786abe809e3a0b72cd2c471c1a53cd9506febd6e45533a289b39eb4be2ee55064bfc7ee95971da141924b452854740f14598082e3c3725f07ef131e1d38661899af554e533e96bada5e1a1308c9dd4d041742483aac65f1b5af74c0ec38306b87eb45e789bd6f222732cb61adde705e28cd4f6635a3b18c98d0b936fe9453a0e34e6c409af65074b11db5df6e051103991252af4944536d928bfe673f761b8fd5f2cbd031eee3213dea0f9e84fa610e78b417cb315dcb71752002e56c74f586cee733fbedd05558670f14fc02c9d35d4c5289582a0aeb2c7cea29698bf92f735ceac97cb3c4ca23b1a68c5f4bd3abbbf0d108cc8a9ea58883274a2deddaf5578e15813406930207ebb438067b529137a3e9120bd2fd2284955570940db23c6abc847edb6d1be3820d6937a42cb2550632f03843533114b701c956773eb29342d5bc47c982c4dec2c10d67b85a6c2791158700018186ac94d9315d2e4a6e87206e06f05b0008b084a57e6a26592bb5109e2895c953169cf3cfb6d5335599d66ee65b6c4388c34c961795024a18e9930bf1ad1795cb3b8b7ff737665f7e659c9cd92d49c1f68bbd2e7045820bb7486247915964b5ced672146d821f19323a192ecc4402ef99c74fdc30434b61c434845f6067f122365b194d1efc9cc8d6b202c4b36f5749612616cd1a49e4078760f0d73d2ac93442fef082e5eaeed7ba491858f01cdbb7d1ba277c13901322100063bbbee9506d183b867cd86971a35563fc43d2215100d22a15b85a736732a032771739ddc91cbb069d31344d0d20411910e9178ed2e8f678a708b9860581c5f94aad9839f21499b5a89cdcce9ae4141ce4502cde2c863af3084d4c2d8daddeb0d29a499b764e910491b40429827b37c71cb5bfc8b27e94d04c0cca87a05b6f87c2403c4aad913e812b3241590eccf08cb8290943771df8ced609d6b9ef43c01129d151031eb6b52a28014e52f8508549fb1f5d8c11b6130cc436fe06178843437d49f8ae68a60355e7823751fef3d1b9da84a16135046520ca73ae1055faf4fa08dc0d325dd3e24a7b08c0844f14e1a3015ca085ec1a422caa6e4b9b5fbf62db9bc5d7e4013332a48413b2e0e866e5329ad4027eb8479ebd9ddab66848bb51aece4ec20879f981f22240a9013e89e54f57440eb4b513a4f2211ab520682974a67d0ffd4f1bac21f2b3b1b344331a28e6d5035646a305325d6401dbb52e1b45e81db75ee2aef7626ba2965a900d0d6047e180224ec320e6ac1b7cce5615e5bcf039484a5ac694bbc5a632a4fa91ca727517495fdba395b6237e287b3ec58be166749fcb1bd08ed8b33c94abb150008abc989a5d2daf91cf711c2286eac1d2a4539958613506d5a67c3ce8f02d7c201f0d223d1a584b7640190e741a2710bc30dc3849325a5d9367b65b80caf0542159c3cc5315d4c4baee1aa574121e0c54d5c5eb592128742394a37294275433a9282a243843de71d4aa82371426745c1a25edd52e76deae686d32f012f1ca5fe11e5339840e50c75012a90430cf33d342c4195b7ad1c31ea98b2cb76b25135efb4db260d859af071a080c7c3f92e6eed5f546d834f9c4240aaabba6c251afb9e3d006bb6f824df42301b2f5c2953d1bab1326448360399225f0358453273e697eb6efe09ac204226965282ea03265ec912dcad4520a0b123857dc19fbdafba505fed8f31b8b3cfda911ee9d3a0006447fb2387bb96520c467e716c4941f92b01ba6f24e7cd7c72c72969dce09e6e7d927b3314ed12a18cfd003bfbed601bfe833aec69f21355dfe1064e40b76616e3ba9d2e9f30768fcd7858c8d0c0b83b8a977c0af329b1520c032fce1dc8008ec5db33966600b972d9fc609e31baa02ad794e3338b7971551eb7c6e61857f512ad3edf221e6bc9d4dfaa8aa97a09d4c67e2e628b138c90141b2e52348dc5f6cc6afd91615c4dc5f59e25e0349dd39a0aaeea16e8371c3e7ea1a951ee4fd7640d4c403b467e67a8008aea2cedb41bde9064c5c833d113a25d62cde3ac9a4b3de58a2bb43ae30633b3dd219334db30e47b2140b9386d4ce964b280fc235077e37ef6969fdaa2afeafed5e7a17b6d4576926167fb343aa91af4367deacd0abd2cc2d17e26ef1a224c5baadf0a3db93053578381097ef5238a3a5638fa8552334a22bdf806ac8d9d5aee2ec895b19356cd43202c70d4c0c1548066828ed3694196bc63cfe5eaec2dabb0b39889d2dbe04d1bb83058dd866275548ef71aacd26bb25c10ad6ff2ae2990e1f40e662707e79735700b4ef914276533411d72b072433c0207c8c4d65b805b4fbff43626e61ba0f786d80a97fa05c521ba9f39cf97b76fddb2e889e1ad27a1cbd987ba8f899edcb9ec7d2083ef2a4378ffad7738f173a83d7165265b303d9308468bbaca34cc0922f0cdd3a0cd06a64085774acdf538927aa99dc8f350c9a69d35d91f747fa716e2d5d39bc8fadc63e213ac1fb72bea7c3052a361e4613481c906d616dfc17d09b97a99cffe03c0ee73011600028b465575e8cb76a5b0a5652ba5a40a25b0f45c3b701730b70d36741b15ce8a46d41de99830210cae36eeab85c881e8bbda72678cf69582d1d401a91e76aa8853c7e1bec64a82026c6177a43161d3bf92463c2f15ef463431cae97c6f9d30951c37ad9a02f3fcf86566f62b36bfcc456b9b8a20388130bf6ff6973965bd9e47ac2a74e6f9210564979bd1dbe110869d2dc9d11ce7691cd081571a21221fd8bc13f91c8f77cd330f2d019049718102383084e87a4179c29d124214549839e0fa953bb851cc383a16b3f4546d74a74ed2aa14f47501c87239be06d4124c4515d683bfb5b4eed70753f006f021f282de12d6ceae91035e53dcbb210a05a944f2ecf64fcaf22d5225fa6a41ec6c3936a4042f2a11bebf25e2987542ec0f28e513443c2c604cdbd07bd181005dfb71bef208ad3708aa4656e4d557dce0295b0a058dadf55c815c1e9e5ac4e212e8d4151afb2d2e8604d051767a6fb17b0819aedea1ec1500e4855e1713274b6f0660fc7ee2892ecebe4d96bc4d8e50a0fc84b900c8e8c2f016de1193aa6e134a8409a061717c096f90e10337ea5e95cb2db952605940fa8f5ba4327c5075c32120859353b43cca0eeeb3bf6aa84212d767d2affd193ee6af4dee128ee42f40216a4387df08245a3568560d8654b2d86120cfd3fce80366e26028bfcb9ac978f67cf39a74dc0132c9e7fe8bb47c566b03f89c590b8e95fd4d8c57eaccab2503bb3a91dc2a6e49e6260b7d81f215e8f9860fb85d1688998557dfed2153539d97d2cf06e28bc4166b16e865e8d856d86c3aa80ec971b45703c16e6add2a59c7267c5443f9a184e903cfba964b917f1983eb3f24694e0071522a83490dfedd2004f400631c47fe812d7da1b83d6b3c68d1c642f27757c382f6a9751d69e9eee1a9c2eb1388cb919fb59419607f9c667ad401179c8e5c1c517a846890afbb5eba6ec1b47b838707ac69f87715f102ab10a86e002fc76daa387ae21ae99da072d4c4b64e2f8b1dab698db846f076ddbda35c4f55745e6c507bb63da743dbe89c7bc2fc50924a4bf8aed119676c4f953c5356e8c09622c791cc3174a2082087a873500be75113c3055df359c4d58b520bea2585409cda60a9ab1b379823506dc738238f285938868041b49a1f02263580eecd46c492b540fd17b788802ea0ee8816698deaa745e4bbd00c8ae89cfe571fb91c46840008e5bb7dcdfa2eb5ffd3a5d094851acb7fc7c190b4a2803254917c6a8a42247bfaa7b9d1c05fff51d4a4406855a04f2def77dd5cc4c0edf6c6f815b7dd374ad0ec6afcf28a56114efe8a67161b20ea599658a43a01ee3e3605ce451907723f438b89aad80a67dfdbdb21df5bc84e5a7cc459376ea92d1f62ca2466e6314dd20b47529bbbd647caa16e85bace1915b005f7d123e24f56e79c57b55e3bbc7c24bc0d7b7e36fcd352748aa58281948987dbc132e3342ef7f79588474ef192874ecfd3b90c6d023f27fc1039a0772926ace6673ad5a7c090b25e5826dd8a17726ad293cd7e2500b89ba2757a2014ff8fdf20303029517f3755959c9090697c49975003c2f5a163fb8aee8eca541dac94e38abc97da134f66754a20e55a0c39e6c532dc4878c00b1629b3331707274c0748e617df0811182c15fe0593246e963ad38b0ecc68871f80f5e584d5b518dea3384e4d1ac5a7b9d58bb536fe27b25f0550dada19c2d2b38e192c4a26d66b1ab353443d4d48de3e406025b06cfd97b183b33cc3dc7e710801cde572860dc9af1e7427fd95f6cf07453762cc183e19c790ca9a968a34189abe2b95bc92d027959ead4fd54653d834c2c246761dfba8a7a9a7fab8ccc65fd4a1e8ee6012ac0c57c06bb95084b0bfdf3d25f398cf39293e52f0bafc44f4cb35811eb9168e3f51e4b497deaf4a16eb041b8038a3563078d0e8c6734382b514c026210394a3c2912aad6997f0bc1b8c981e484e31c031a5647a782f40c20ab8d32e0bc7140b2d83ad827cf1eee7a7c7e328cca14dff37c58b73043637a3fe25576a53b8c12220ec7705524cdf02c0e6845f6f75b5c3c4969281b8eb156818c7a01c7d43d620ff29549185a2395681e4c0538194958ddec6cf2cbfb83a04d8460011bc13d8780a7e833a394beea63881984e80f832b999129c4546300050c5a5b4db25544f804fc32f3dc62c239f9d5fa138adbeeaf2c9b17e1cf7842ad41127026f274df5e7f25ec9df339117e9d2e1f70d9aee335a7650d8f49e00c1188178cc487fa43f1e56ba8eddccb0d4020c00a50f70102843d75e152dccd9e1d2659cc6b9d3c381a7e45a521c54e97764652bdb4fc786597d4fbc0f0a28d26b7c57be5a1e5b58e4bc503ad23e8ad0dff6323750470df37f867a8fbd386861cca5f1639f898514f5d7ce76737a14ae77432f1b64091c07bb2638c159568fadf0e876e0c5f904ef6cea9dd3eb2b22ba59e41a1865d114291906699b4739cdc480964531fc400555a87c81c311612150497a3078ac6ba80bbf51716ce555843b26a7fb2f0f3905f15d5cab8a51d0bc440b2db0e24e4b8f9a9e6f54527d71b7180d9ed60e8ecbd68afe75eace39214dc7fc4e06e13273df8eb42d6a95a87b77c5b443c9500de2fff51dc2c36e42325daa3a1ef72c6b2bc14dbd1b593575d6b4873fef74a1ec6473708b97d1323405de2f46c6303718a580efc90b398cc47b2276ae93e24efa9a76322b73c471ccf08323ee0cd8e3799ce16e5dbcec5c1acb4c771b0b15ae8f8705adfe128ecb26de5e22b919ed800a10aee34e074c7b46c61a9c400571aa1d55b8c7558685f87f188472126aea007eeffa46fb90b754991d28c5d4536e01365fbf91f59114ad8eaf3ac249c1574521c625c6a1332c9cd755874699c630d1d421b9fd2459b25d8e5310ffd12018977099290c07ef7f7408625a305d9536d9c12ab67ca8b41bdaf0478297e666ab9f3d94b0a05b691aa9e2106ed3a24f7608442c680ef32261a9a2d25615011c5e48e67fffff5e38214050e05f54180841037e371aa841e45ccb2fdca260646e8768b1d10093fad726da52b9377d3a7749583b70bb4330e6cbdda135eec955027ec9de5a8c4aa4ef082a61998560882a4dfa8b69add9f7a4dc30abf303557a265340339cff4ae30e8c8e498879b5792a367b6cfa636e720cf2231709eb896a8c15f37bb67d3775f0823e52cbd56721dd698b126d540bafb6d95e00586199910d142998571a0da3df1b0f9066da968175a9cb71cbbb42d2578d71a13048596137cffd509b31d787f7c0b95ff2150de954bb7b2ffa36b3c9104602d94591fe4ba8abb2a638b070d7d2d1757eeee8205a6475e2ed4fa36f5feeabfe572a80db40af6ffc7b19b220618793a921e280a6bee5ff68a2d840ac53f4841b7ebe4bd88aa040421077144915447a2b63f1c13f9d4b041b338ad571827dff64f3e7b349824243a999486c40b43eb6fb89d8164c210463e894ca0bf1a37001182456a482018788fd3b107429d0dadf8dc937ecb273bf42088081b70ccf464d718d78be4606488c0033940f50c901855aa4214211dc338628c5b22c1cecfb28a9348dd0a58379a5da679b5633465d03d03f111020ac62682aa008eb0851739c4f4edb5dc7ca7c35b02ca0b287d7f81d099a5b4bd83725a3d3778e978367eca3add3bd1991e53de0da915d8429a57b5f07002bf654336704ed7a547f6965ac9c35a2bac484b12546bd6d41350c04a06467962efac355e97d17b2a7138fc25dfd4b5a2c7e52ac89e4694bf03cd0a7c02452c361cc31236a95ab63a9cb3404e7db434f728cdd618ba4ca0b51473670f36d90cef3c59f10d19538817fa18e5f47c35c98482830f65c4279677b80dbd937b91479d9120d578b7a7d94c09f957c1b424406c7698e4682140a8775c62a313409638c5657345c4c58da1e4f6530770d4a028e3ebf25fdcab887f5295d1c54ff2579b0eae98c9089927d530fb2dba016f66fd7f4a6f3d7a5b49b578f2e05312332db1cbd77543db4ffe57603a827091046fc3f2ce5aaae72a57ee0b7d4c10c75f1f6415af4fbead9a86a010d813c9e2e4234f5dace546bc1c36ea9ae5476ce93ef881858daf4aa342fa3f5bf5692697df54db99b0c4fc71e8ce91cc42d1de020c7dc1924aef3166d08093f289e6a1dc9341899173f4559dc81381e1724215c1fee3a5d8c8a3b9f60c22f86b6b32ec3a0000fdd93c5d2cd2ff4c367f30112cb1d25a0ddad499891620307f8fce095720be42ba3787a978c13c59dfab9765b22d238090049ad86baff6ba3bf7ac3f877de673eed492d45caea8bb5008b7b4cc8a0a5d359e798513962cce5d8b878da07379699e97e92f29dc56702a2636ad3a482e1d2d35a3aa71df506c8fa9917a96bae27c00d196c4a79d0d8eab9b10274350e37fb3c4fa0d242588fb1a7ec3c371d5d7c5191dc5084ef9ef2720151a9731f12108d9261ea3380651ee319ec00b9b90f3a3b27931efda9bc49c10c61b82a943171addf9d2c6d5339cef0ea6694c2d5591756979a17b0cf390dd0f017fed6497dc9ecd1c42505bce001af6e77fee58f6a64b3cdd5661216d32db77554dd749a0353b02da7f51e816bf17bb116f556de0b349c4e0212f10d0b433badeaa2b3a78a2391d4142266bf28b25d98ba072b97979a6e01f882bbd1cc9d01ddcecb4f983bed10d6cd412059d17a456caa0631c8aee35764436b79107ebc87b958793681f7231aed2013231c48b40c091dfe4bde66d3b7405ac2988305091208058d628d78dc95571f906fef6e42a9f05abb5acf4343afe21503b2179f93de8dfb489b010462c589794098d54b86e23d8712de43aa38c5d9953d1d1f986ccfc0dac05cc3e065cc8172b597e82fd9bcc180b9decfe4fcb69db8efd0b59406929f67b98e42bf134c7ff05e1a23b6223220d38d26af4b8a27a2dd8275a02cc75d9317e48631a4ec67d6f895288e0eb53554991c82c44e700e17dcfa30799c27f55a78ead671cbd93176d79fa964c6c565cd529903990e0abcdbba17625679876d5ca0c61426b7446dc3ce4d8d0615ba6aa2b0b910c9270ddb5f2d4f8cc0bfad4bfa6870a8b56301e8f3d4e69968e8984c6da34f556396c34cd5769a5a6f985b6cb8dc8edd331bb8d0d45aaa7e59303779a666d7a9adf804fd3e6aadda815c8a506bb342b99e94596940faabcaa48f39a57d928ca7aab5746d499c939acd4bc90d7302f1518f4450fc5491116cc4af723742519be455e20d87c24bfd3e6671d1e37988df50feaa550999ba92d9298f1e0e774ed8da28bae02a9e7f73b73eacb27769feb99e744be77ec9ebcf087e65ef4bf0a001dca76b1f002e2082d38e17526b0348eb556805d53b42c978a4d66105d7e9faa817301a336d94abdf5036b0fa8748b21fa8d54cd90d0ab053d95e34a59ef4a34dbe7d0e554639a61f4aa65f38deb40bf3186b7efae5b64eca6d885daea935a47cdb065e779c4713731a6ecff50527b1efaaf2851c2879866c7eab41f4e38e078eafdb16d7023548db8e0a7e828f57dddbcb19d04e82821c795eeff47df9d582cd9f21fe224e0f30eceaadf0fb12a1620bbe9cf0d59bb80099e9052b5ad5a39ce47de7d6a067dc2e4dac6e803f187d6a6e1adadbf7f6de2670be652aead7f921fc3b413663ed69a91f17edb10fa8ab8bb0b513c29f92b1eeee2b7f2cdd8019e0f65325154c973bbbfaced2ae05f0983e4110f55bf558b5da6dd99a3cc66e1d7d15c43b0bb5cabf1f46c523724bc69e6694bb77989ad0aaa85b6be8ce4b19fd99e43e61329947e42cd744625e5392a6fa02e17371f3ad741a4117e11e02ea412ce6cafc06afe9358dc1d2339e3e4beb6296b5ce72cbe83e0f0ebae01ae39a10fb22abec587e301cfd116297cce50523679e9a40eb3bc1fd90e086ff1efa1c040bcb9a488e54f41055120653cdaf84f45b95fd85e8320980cc93368043008edbf5ba1b5b64852087c911603e662422ca32b28a3dc1b3c40271bed1c0bdf0329a2bd098a7c901d610958ddf3d41274f5e8e8b46f1c039824acfda8b5bc98d6612083d35e8c23428fa702768e83ea91e8ff383a16318af347324a6c5857cfd97be9117d67abc5f5232e6bbb9737a826003f7609da9c07533cbe9e9e7a802cfe5ec76a84e933c1fee76673be936445de361637fb711f54ab7323b5488b58c21280f4de554902cfda0997d6ea3db78bcfc276ca49269e86009f380bd3c63ed15da75b8402b5e49260c8a90c4e0c9492f0ce293e6d16f9f29ee17eee45c057066510fe88bfb8682c7a484f7f88bef7d7994357cbe49b25f0c04c91f0893f0ee185e0ee8c5d55a5699af216bb5b5016c7258093f3649a180ddb3edbc276395a8007d5dcb3109a507d05a0a2381c34b9e6cd37bd148358d2551caca8b0a888c90a3155399015ab80422360911828d0d21b9a0e0f909e3c2061abc3a620ba18d6667845be6b8738347d5b9ffbd36662bcf239f687f46fe216b1a5f433b3cbae83979c57284b79e41b5bb1347d5013b6f124a6cffb73359c8706f94f223357f13cc6d3e6534cde9c2817e157fc1962769338e2fa69dff6d471dabf844da0568b7274db1f3775fea80009ef54173228891c262e602259ff735e77cbf3af02dce9f37b5eaf9c5a5ac9447207cbe7aadcbdbaa3aaf3f292d65f8e797fe8e098f4bc2b1e4fa14acacc62f5aee8f68fc23c55059389afe0156f186b072790cfe3f3231dde57e1edf2fc94622377d24d44d17ab56011d1dc16e474571943afdec6e979f1b13b868590c108379dc54d7038404313e1c44aa7c445b299b2c2558ee1b819b15351cf3105a9c44e993140df350089ef6b6705b07c19da7695e997899aa37080a7ab10b269d5697131626bd33e911d5c478401148fcab023ed580702bd1a866c8c9b61c3c55bda591ff052db5f4c01580b97fa557c6ecc2fb04046dcf6f3cd553f5975459b151fa74bff7507f6ca3c52699f157e14c1b525af5ed3ca3a3cdcaaebe76362949100e26b6e944f258001c484650e3093cae6aa3cd64c3f396896b14f04b837c9b5267fb2ddbdcacd2c4f4c9f9fe617f4b9b8f15aa3f7a5179ed5cbe574ddb56160166fdfc963ef77ebdf7d1ad0c305db06beb18530cb1efcdfddd20746c3b8c11ec9610deeb47f2fa30267cdde41112a688220dc72db697b4fc4459d9d48b007118bc00cd447527d7996920ecac1050984afb30b93bc3ac2f57a577467fe850ad578168d5bdeac49d350014b072fc6c3aed711b80e7972d39ab4238efe3faeadfbd6ab91cbc43b9cbd8f87141ba117ac4f8a483702ba9298ab37dad6964c3ea5796fdbde777d3fe4953abeb8851c6b77d8f7482c4396a5d7eb2353a3997a98b1fe70005a06d89eb968cbba485af7604cacf9df0058ddc52e823c6fae12c100e505f717c27ab0356ea1fc7ec17772091e0565efc64d63b8d4cadb2b5f07740f8af40c27f2079fc6ccefcb93bcc26c58cb96957ff4e45f3060cb58cdfa2d4754cfcb8dbe9cf402212a44413840063133a81e4f33d1046609e9aceabdd83fbef36667b62d3dfad7ccd1a1132c75130ab0fed98a81d125bfc6c47b74c27b79fa0a3d6a5b8baedd3c6a98d3fe2b97eda5d5b763b7361223eca818687590e0e1d9a37ad2a23cff3029c84832343f469fca562e740febf6b675b91263668fa6985179f3e2323fdd84176f607eeed7fe58cdaca7342ad0d1c4c9e081cdd6928ee2a1a7a34f10919ae3d5d7b293169bbe4fbc2f4708a17a9ff0c9569409798e990d4b3fe3505af26fd9ec688f9ae3dffa76e377aa4e6d60de5a167ea8eaf9f47eb58d6de7d20a08ecd5272c74cd860f772065ee399d34a7950156005a9230fae342d4747d1609c71d956240f6cf1a22d517f8974d4df2219f5d74845fd3d52517f8b44545f201995572445fd95481f9c7600983e30ff3ac81957e7e5dbf9a9d9f9bc1208a3edfe331305cc2dc2bc7b217c0ef8f00bb1ef8907ed063baeb856e3eaa7c91dc6604a9fd815c10da9530a0523c6b0d681ac49123b76892c66f45be966f758489d817936bd5bf15751cdb1b54c92bdc26f28191f7a6f9ad8805ea9512b15caa5140d6f4d3c34619d865d82b221ebf1e0eec12e56bbd8b0ab56abfad9487cfadd569a9344aafd58fcb7d7e1e07e990cb707ddb75e686e6db0b375741d0d4f4ffeb33b850487258a249bd27e76bf8c89f757613105b2ca7177c49afebb0e838dd1cd58ab0ce4f53c6058488e4c8ae4c43127024f3705e22fd3014f6a3874100fa272f0e58c486aeb48a7a031f52d98e2fdac69bcf6206d7910d34c896a3e9a889137951886bf7d076efe907d308d41bfb12ff2e87b6297d27b938ea79d7dabdcf86b76d3dbde6f4a93a1af863b7d3a216ea9d59f26bd89b895f3334e2e3f2d5879c626fd8370ca7c8cff6264ea7a1caa016c2cd8c6353c2a8dcd32d4076bf103ab806bc01812dac56f6aebc953775311aa55d4810be7fd9d81c61a7e59ddd757f80c2b6c28dc49f91dea13924a2031489c420807dc5b6f7b3b9ed4df31f10ef6fb82a792950cd8acba1c656eadc6f38f6dc2890d0057aad5fef984f89491b8474c86db165d422ea2edd115334bfce123d8db7de858464eefc65302beae06c8be59beff2224b1ae49ba9af2ffe2913f3a723ee3f91fe7db56e9260f82a0e65ae7131843cba0656815b8f21cf9b393f74d963bff7dc74d1e1dde05f0f88d89034461f264939e64a5939d599dd271cac85a5e4fb6b777fa7cb3b385cf84d6873a7995a78c5e6ac56df9aae77c1a8eabbe9196d42e3908a8d9ad9ea7c65c8778aca96ea8437287458fc2ae91ebd4234d36fa8008a4d8aa8180fb77726b92b33e097888b4ef0b7dbe159d106110b9129f78c4fff49d1f276291ffad74e3b0b7cbb28d3823f98bb2cd02237c00732200dd3f8f77de60ce0784fc8b549f63f31c8d5476c771cd04d91ce0e6a85d3f74945d0e95f60404dcc3f329d0cb2f306e1b5b3b32b1d360ff67818ba257aab5c76ea7e6ae91b2060f0a5d61d5725f1df590eca5f1bd023453aee3bcca3cecc6cdf6ac9d2909f40902eab4ee28aa5a5a7e211d88cd422b878444243c7d0013c5b6183d06ea0e43414e0b3a4054dd7a5b88c7b7c452501420848236ad479466301b00f60a645660084d82c8df100b4b9c70df909f09b67586dec75997f94ee079a45f1f4b7a42c394e80479716474084621bc8810057f38cb9a48a825a25a82a84b88eaa5433e01083a88d29321f45d6c6014417c58af9ed7174299b4121b63b8ab1dd922a76f91236a2aa6efbc6f0e7d37b5a345df02753f3f99aab522c72becae654a192d3160e1a58115270b16d9b228d603d2bc2a923bd41572b2ee8ec31e456a1542a0f7d08f1bf226fca857fd2ac8b94e73db32dcd183f67dfa8abf78006022cf60fd9e6ea626b53f8f2e80b0dadc6794519284245e80553a2b260e4dfa815c01d84ab4022eaa18fe4e251a5d9bfccad83a3eb206863879357e95129ccd48388268f7095678fb198ed88fba3464f0cd98cec73cafc1b66638972007eee16d8ec877de2f85dc4159042990c0cbf2959d647de4aaf2ea09d3b455dec09023937f6e5ff845f1ae9d9d3814f80a7add9466e13ebb199c68deb17a5a502a81caa1031949033d75e4e3c63c98750c4deafad8819c19eb4fd9d57b88ee6e683920f9ec50dc9bd7b1e091d35a9d0475ba059f8d5c275d44a49c4146a324fc2daa6c0905d85eb98b3f3899f4a3a96948d4c6d781ec238e77b233a8bc114ec9aade95750248c913889b0e3a757f7e6af299538f14697487bbefd86dd7b2cf35571851dd964e567c39d4cd8cdf9f6ebfbcb875032e3beb664fec2341e3d519ffce67472ad0eb37caf665c98f6a401c0c3ab318d0549dfbd5eb50dfc9bfc95346dccd649254698999175d63480afe57e4abe161a666971b96c38db7270c608dbebd055fcd140c7a938e3616db6d781a97d54679919e8667855f775cc6c10406ce9df70041544b92634420f3bb30f35387ec91888c2bb879aaf7cd1f45acc90d8088670b3ce736489cab4b2bf3296ff80266cd9a0efde647be4bcd3770af24d378d4e4a6d08eb385c08d13a7e3f1b735f2601493868c847d6a6da55e7e738ab2f80c70ef90e0cf98fa6b7c8c238b5372178ae20e13b1295e55ba5d69bc602fae7f979e9cb797eb4ce37fb299d7c3611d72ed56fe9af8d16be21b8dc3956b9b7baf7bca2c6b1650b9f81f571382563b0d19de894f57f6855eba78798699dd961c16fe4ea559e23a6575b96f66ff1b78c83785c9d97174fb4c52229e850d623c9ee9cfda4b1f76d55f0302131bce6833d9f5b69cc2f573986b5376bdf6741dc4735209b905f4288cbfde9dd5d1b513e70fa929f6d76bdc307b27cdfeedb3a519f44949e170bcd9f6e187b18beeeddacea41cae7d30d2e2ed3af81ca9a37bcc13e8493620be5d2fae6c8dc0fe4ad94fbe1b59906618f2fc93141866c899e83f8de47c38f5079b69c9301b368a39471998cdd059b9ff21928e3c3aa8396a0c793514d00ae159a823fc1b4f856f032aea7802a0e701bd0c665190668d47569f1b2e981d5ff31e2d3d28a0b5746f43e94a145399f2abb969d7ef9ea3dee0b4b49a1a30b7e27c61b7b65dfc26a17ab4bc0c693f974e9b20472fd1e9ba3f97c9d1fd0ab5867fbc89eaf8d13ea30923b87bd4e6f1776a2da6f79616cf6bdc5e39f67bb6b8379c6ecbe0b5f470b588b7305cddba50e87461b8a4ed2866c67c32462b695ef889334f1a886a540092f592509bb68af9179b7db1c10b197f66d137217be5d3773ebc25e3c72c7e2fe5af4cfac0065652fc882279afb1c46bd9cc13d5c4874aff02e504182c59f5511edfa3d1bdbb9ac3a335b7320aa21b18b577eb7338bae4ab27397e8763376e610a844b7c752ec777617cdbabe7a17895a90ec4c8168a6ffdf51c1aaed9a5bb042e056c691c37ed6d78d7c62e9b606ca65e53610ddf6a0c5033c1045e2595d37cc93d31bfc06b9f554a37a49545567ad75e7e97cd9eab93f6f4bcb76ce594517a2166adb9ede77ad9adbc7a894fdfb3cb5ea419a74ae2169dfb2eaf9f65cbaf49f9755ed95366252f11a590349e4b9d2e8d1619b016d386fb677fe3bf5adea0516a7c3de769f41cd4afaf48df3f88e43f286776406326b935790ccda5307c415740ee00d8169d498a32df1934fd780e24c91eae0d3c358a2e195affd687faf27a3a33fde6426a7c73baee6174e2656e42d7bd51ade0941a7b2e1fea766790ff464c000275ab799dca7ab8f453bc6490f4307a819acd7fa417d4e160b98a23ae10d5c85e85a8ed5515172ccd8501f66632eb60ad644d82024f98ac5afd816d0fa655f09ab942933d05992c830676c098229e665d08f38c7029d4a0215516f5a19e7584764098f678ce6b0fd076d065895b61f8ff4a00f7cb90b145f382a43ecd4c42b6b28f6ecfce20c6b04e37539414beb38a3e73d62c684a963f3c3b1d453cabc90b90fcf3deee6700dc94e1c8d01787f0c4e83c283642fe55a3711d5b5b68b36d213329bf44c8e5a596816d20162c67162dc3489aa68a26a55f8ba01b44da46e60f30255e2c1e8def369414dbe785f6e031ba36bd9840291e8ee135c5a5de0e235858237e4266ed27ddf18c76165b8e80b39fd51b0c0f9cf604f2c6344551afe60c7da4ec9e6612d045080b9d8a95f6b33f232989200a9c814b53c825c3d50dffcc7b51c77b7dd3bb523f747787fa3a04f2168f298529b5e23303553629d47c233dcf5c301ed194f2b6e593451682d747aeae26af4f1b2948694098dc2cb4032283fce48dd98ef86cad26157402fc9275580e6c0a5bb52e66fbec9a2ce9e4468a062a241f66afdb8d43087b00e3cb04a7d024a4b101bca2d62b0ec404e9946e70819a85b11b7b4a832f78ed684aceeb7efa9fe92f88e7cda5948d3b24207beffc1b0f3d8fc85bd25212b15d67270e26357bda1058b123266d48aa5b8fa456032bf1abeee494df08588af2f2777bfcf76f3ad61240448ce830ef93ca6a47218c3df0db239b2f617af0af3ac3f40e69705ef93fe824df4f81428ba2889e5d8853441f2a376f2df65c1d5e7619a6980c2fa8c1d9061bb4d2cedc40477d1e49e14a6760a11559199423e085d375c5f2a30847678f5d153a51d7ca9115dbc10b8eb59dc0b86d367276697f57354d18292dab380f77e887180dbf2f311ff58433d171c324d8bf6ffd9e9ef5370bf62552522eab2b7f252d60510f21e0a2af323f2efcf01e4e99be75b1d14ee74c6050c9ae5ee206c78bfbb3083b04fb4302cee1e81ba2baa63b21d2a78a9bc7c598fc8f8d42ea1e00f5e3d79a7fd06e08acda1983ba0ffb31683d0d875a9cb170ec81b55b4f675a590c414e0aa24ff08dd972d29bccd904fc5ea7db98772bc468e17d80334ed6c0ac33b21e547b67a776cc6d233c4bf95f849013e4106f6e1f55ad61969e3842132a50647c266f432f821ffa900a3d68809063aadc161b0012d9b33f307838ad718cecd1ff59c78debbad04e2b9302de5751a2410a5ef1f379dd1ce69d1b7ff8d78dd8c7dc5c7f4a67070440021e0d44530a3960b46439330f0f0f0f0f0f0f8f6c1cda08a9ad9143d855a624252771f6d29367b94c29c9945212bb83776177f00edec13bf86d8d904689e8063a0a440aa90a76f2391daac2f0e6e541f72499f5a9305cc587adce522aad870a830a5572e92c3fc5cb4e410218a6a8333592e44a2b5300a314e6f78aefc1fd46054b11000629cc914de708692ae57b6423b0011a35608cc2a0d65446493a71ef5014e62cd162cf77cc59964361ae92203e3afda03009cfa2c7cdefed6cfd84412991ae536668650b767123d1c851c3468db20e2d1490fa1f808214f4030ada80e109a3788f9e0ea2f4b2993a610e3a73cf4d7cbeec30270c4a5eae8eb54f0fdb6ec2a4724a1be716dcab239a309a0aeebe97265d821046264c9f3b4fa574bbf3f96060c21cce4d26c42c1751c704965100e312263dd2c459dc5858bd3e16c709ba38808787ff09ba385bc0b084694e826ccf73b22f7659094a98477ec374277e593c258c4998adc5cc6399c7e9f725612e0f4997bbf47c981146244c597212f2bdd3cdba83010983a78d98a1dfa56cfb4718a47a9223457b1c61ce63b9e5c4544cfe2b4f0e301a6108733a8f8713af3cefc71a1a8e1b9f70d8401a58cb048311a68c3bcddefc8e79ca4518ed5454493e6e3a9c48173014618a64d52374de23b0011a20809108d37bc9c9d3f191b67d620ac0408461eb82a5186b99e4e3c71ae2210c1e74740821a789df591886308439ddf7ad092264e463ad840046214cf9b453d87afd630d871e21cc417b8a5e1d96523708c3e47c26ad3e959ab57cacd1c091e371e06841983e5c96efbe9cebbb05c2945428f590b27ef8540184696b54640ba254ab5f3c3ccc7a00e30fa6f89621352f992931f9c19072161dcf716a74a537727801a30f0613215c23d8872bf7f0c17869a263efa8f6603c93a17b359b1e8ca54214e53f661364f201230f26153d95905971e27ec18341fbec4c7950615424ddc1fc41881c5562a2164c3b986b5ed582c987d25a571d0c9facf427a55289dc14061d4c9f4e8f185539e2a46d3c8e84008c3918ebea3ca54fa76efde2e1810e430ea66ff1d421ff85f03fb18d83d953d453410795a384110ec614b35a11bb7fac87478ec781a3750330de60ac5e33e5173bfc8f36baf002c7df708349a730fac32ce4fcdf6983214ed0118fa92eb15e36982a447b3db5eb89b2ab00c61accb676f541a84892b57dec0860a8c1d8f9e29a89303d93920693107dfd3977c8d62e7dacb1a1c1b07121a5511d2939991f6bcb011867307ca59b54edfcaf265402186630cd4537e9f13f1f6be4108c3298a28c8a6f3ed992b9850cc60fa72cee7f4e3242f2638d060a6ee4380cc01883418490938731adb4edf9581383f9357bd64270b1a47f6901230c2669bd3e6a2ae4dbcadb086c80c6c300835952503b1b6f393d8768043640c30330be604e67a3f426e7a057cf8fb5340dc0f08241044ff1d74b3cfb6b1f6b4b6e0230ba608a23a62ab7d6240b326b040c2e184f746eb19c9cecfab4058352fac2f525651a9a6bc11c31b57ecb5a2e99593088b11261e2c9d90c190b467753e1d5d2a6de55ae60d0529e2aaeac3b7e680573c897fc616e61af2e877e09185530da84bf8dd79a0a8613f39fb19cfb4272533004dfd2e3a93c548ea00e2d1ed051e709185230463cad5951439e129f0e2d1ed0518c1230a260d2fefe3a6b0f1e49efc8a2e3c67768f1800e0f8f13050c2898939fea04256ff3974c097468f1804483f104739d7f3a55bb6542e47caca9a12fc159161d653a26f0808e64030c27982ea4907a7dddb9bdfad82ed0ce2c00a309e612da45b52ea70e95b480175ff823c0df8b2ffc9d0a3098609c4b2b1dffbb3ed6f7f04021a8916c74d18587078c259863cfe2fc435f0aea625c200d7c991209c0508261bbc45c4e7ac437bf28c04882f923a5204ca41061ef22c194ea47335c3b680feb11f60a23b4957fb038018611502bef9870c254123c09cc165d2387175ca085510473e9a8a392af2a6e46221442c85d92a2a505118d1c22d7259d523e8e873f16070e722387173086609012f2c8479bf7201a3970d8a861d7a185023ab8401ad8628bce02021e1e1e308460fa54499bc54afd5c75100c3fe1f4ef2ec8109d0204d30521ab1a7136d6cd1f98645784f07152981a9d0f0c42f20813d7b1b28f1501dd086c80c60d183d30f648bb4815229ec4b6033078609021398558a6e5365d0d10183b304e0c213a57b985dca102c0d081c9b4c87512e321c9fd3930df8f1e71133d4def8c0343100f9727e818f58edec09063a43c61ddcd4b451836306d6b57c81fbeb23dff58fb1c283839bef0c20603880160d4c0acaa7fc13def45ee3430e4d4eefaa6fdd5253e03935bc457bb9c27847c32307abefc8adbb12ae11818b49b49d918216ea6c1808131fd4a644dfa7ce4ea05c60e65dabb4abaf99f5c60ac143f717a95e2492d306ac70ddd925e54dc61b0c02ce69df596fe55182c84e01527f8a79f54617a19a52aa439f7244f8531af6db4bde5a4f3a830dd8f05c9f9cbd2854e61eca02dcb2df69252318541997ef67bc81143f552184f4ac8c154d44eaa440a938ef61e4582dac8a1519846b353626c7c9e4461ae8b3b0be2633cbca13077302f7929cfe8374161dad5b80e12b2ee5fe813e6f6a0bce2b25b4f9e307ae9603729b885d83b611859c9157b7438618e34b1f4457d3661defda8dd595edfdea309e37bbccef57663227b3261d271b77a1d1bd9e1c184692e240936e73e97ef4b184cdf7dfc8cf4b49fb68451b5934ee965251fee4a18ffe3e28238a574244d09836fde88df24dd23542661ee4c89fb69ff73c74512a6d8fb31d6db1dff964818265a5986be0512a6cf12e172d0213eb1f20883e9c9fea5f427aec911065d3995d52eea74851b61fcfdc897f258107a654618c5bbb7fc525e84d16d82f94f0e42e59115618a113a49ef79539171228c3ef7a29238a57ff4883087949de6a34f3bea7b0883ca093713d71026379562f13e4effbd85308799a4fa176467939710a612a735b725bab5b98330dcbf891197c3a7cbad20cc7372d79427df4ed706c2f871bf2e9deedef51510c6d4165517b3fec17c177984cde574daaa1fcc1e72984790e5124fed83297c9b9e209267a750f9608a5b4192aa7cdce7640fe6b57c721372125516a2078385dcc86361d5828ce4c1e45ef2da6dee9386120fa6cb197341750a923c79075330e5352b2aed659d763085f0d892c3fee79c9275309f50d765baa6645c920ee6cb17c485d5679b36e7608e4f79a28738299648cac19cfe7bef831cf17d310ee62822be75f77aec43389884d5c7dedff00de61035d4cbc8e7dddd60921b3a67670bb55f1b4c2ed6262aca68cb0e1bccfa26557e26e8f7780da6acba90b198b70b4f0de690929bcea12cc5bd6930c57c9890bcd52c28d160d25f97dcc1342e353bc3b9e17f2ae39bc128fe1d9447f9506ad700ca60d0db8a2297f4db29918f351a5b34068e0300880190c174656974f55a4f7c3e06f3aa094bea5a7ba29d6230a6fd487defa4f597a261d6a18502d202318030182c92852ca13f3098d3d489fea5147c2dc67a88017cc134ca3e78b0ea9920362f98e4459fff09d9179235802e18e46dd7a48a3dd92d890ba60b4a477abb1925f45b3085edd4414da5a924bf160c3a990a2fdeed6f5acf82298ad0bb146b373b0461c1a4cacd64eca5af603a895942ee56309c50d1ff21bea56e1e40150cc1d466c80f395effe6118930797808fe676957218a08a3dfa93b551ebe811587309cc89e252899dded88218cdac12e7789b3904585306e7fdc54cf6526ff26842944ef58a6a1469e18076118f911dc7c2c42482a419843a96cbe252e95a43310c691b5adadb4fdca0f200cd7dac1ac2defa98efdc1acf1216e42b4943b323f98b72fff855121a2c3d7079378fdd46bd739051de48321898e8df1d13162d23d18d287d93db73c2195ac07a3962a37a517a48e8ae4c1e035bb5ad123e7dc140fe6eff4e54964cc427bee60b4ac7ef24c5aec60b6b10b16627292666a1d8ce7793a949c1c54d0111d4c2674a4cb5ce7d8f6cdc154e15425a55b7ed9a31cccafa16d4a5e8514411807c325bda152aac8b29be160789157d5c144e510ee371844ffc745aa4a5adc7783a9c7c6532797a8164f6d30ff87856d48900d2651efa52778ca49e8790de61a392affb11a4c4a6d858811761accb6277794f04ec9a6a1c128d9f2670bbd1fb3973398549023ca2cbedbc86630056916948e37e9da7304897f71c2a229c960b02ce329ac4c3eebca184c1192867f855be8e7c4604aa359ba544a294764c36090ef1c3a64490183b14705914d4c104ac5f20be6584aa8ef104eccaaf282218eb69db2f7f38abb0b465d2d593a45fe5fd2b860b8e04984eecd9aa8f0168c96e27122520ca1c2d48231526effb34bb3600e2286169d5772ec9163c1e471575cd74a8c47d1158c6527a311436205a3454e97b4891252fbae8229991c153e675438f72f85f26ce1148c2be2ff6de7b92e45a460b8385a546ca4c85e320ac62a13592ce47d92775030e4246af6cd54ec2da52718949fd08f12c4f5e8a413cc6e27ecb388fcd14bd504e3bfa90f232e8509a6d117db6bbb6f0fcf120c4a081d6b399ef00eaa128c914e975e9057120c32b3cb3d2e998e104182d12a2bd48e107bba224730b5a7bcf179e5d456d0088653215b4b4e17845545304cb635ada915f6c485088668fb6f39a46c08c68f1eb327dd650b5108261f17cbd143f038a20a8241484b115fb234b427816030a5595717e28bfa4e0facf881397374921a995761621f1892956984931c94a8600fcc29c7474b42454fff393c30c71a11c24b5cdfcada0e0cde11ffb2a4322122aa0373881c7e3f5bd45c9f3930752953a622769e970c0746cbe5e96adc93ea0b6f609a91a2ce43cac9d2496c605029a712bf97e5edda1a983fa49c9f46a740b082068693a0f15ef319d9a3594ea692aadecb3230699b3bf17891cd4c1603d387d524115729672e3030c4daf051f9cf0b4c9e638d4a4aeb424659062b5c60ca2b77ed114fc29579042b5ad084b31219ba52eca87bd15cd879d1655e341778a38b1c5d5060050b8ca545d5bebfaa821cadc25495b38fde96bcee715518f46baf7acdf99afed4a5c21447a70ac9dbb72ac562a0c2f879f32faddc5afbae7488710a8350dabc4c45ed369b98d13064430c53183ccfb3bafc3ceda8742146290c395cace8eaef1549cd56767a88418a4d64e96e3725b2651f6b34fec8284ce92b7fa65858f61f89c268f6f12a2288699f4b284c79e47bfb29afa44a068539cd6c7f0ee12badbaf20943eabb38a2a727be496378229116b53fed9d3085cfa1948cb5f8133b9ce04210614deb85a56cc2dc39e97d7a9dfcf7f977648186189af0259ba770939fd20f87189930894fd4db89b76d31f281a317d09145470474cbb0100313e609b2c473d62f4996121c5b78c1450d2e625cc26865b327c74e928318963087d81b7d49ca477757095396b9c8bedf23c4a77010831226bb735f77dd132ae42010624c42f3d8b95f4f430749c2207256df6dbc87283912664f891e613f45ca128384f9c693ea968ba27fa9188fb022859c154533b3625accea711d9713a3d7730f0f8f24c47084c1bf82ce27425dea1b61502d73c9fc52cef3154618849cdde4bc327511c612aa84259d5662b8ae0853fecb2491f248d3179408f3aaf69e9227228ca75359f270fae67d8b71085377877a1f09328421e5dcd96d4de37babb4428c42182b4c9f55fa8813e3ae81a346094a87160ac85103470d0cf8165f2301346878787878b887078d128310265377dee9233e0873aeb6d23b2652be1395208caa216d2dee8460d53f214620cce613d4b7881a953d579a100310e61c5e2c87ad6ae8be3f96060d3c1c7a02e3e1a184187f30e85abdc88c91e62ee9d042011e1e3846d045171fe6079388ad974665bca95a8710a30fe6b82ee31e36e783594492a74bc9ad3042bf07d38585b42bafa57f923ed668e0c0d15ea0e0d0b8f3b7718128c4d083e9b3c3536a55b3d9109e1162e4c1742552dc438d16cb311fc4c0837947e7cfaba264b5857631eef04cca298d7659ca6611c30ee60e6e661df1468e7d4cd015b88bc0066804800531ea60f8f49f6ffc2c7af2f8c71a0ebd410c3a9847a93862412811529659831873304512f36515721c39a11ccca6bcf2d3f907b9997dacddf82db640c18d1cc46af40d2d8210230ee6c8413b878f32594b040e065db9e28a5bb8f8f83ed60c0931de6056f78827bfef7284186e309f1c712162a7dd2aa58f35c5b185175c3080861738f4c410a30d468c6fd3181369e4f810ace57805c460832987e8b1eba13a5a823ed61e3db2d1850d8f1c5978787878e0c8228b0e14a41a371e0310f0f0b880878787878707aa71e36d7c08140337ba4834b640ef5ee040351c35bce8bc220b31d6f0f89d10f2548386d98220861a0c3a26a88d9efecf1c3f768bd331d260d2ad3ecf9eba123d090de620ed44d3aabdb73b67306ab9a72f3565d283da0c0695d304d3fa6e19cc21a3f6deee53be4632986abbfd83776e0c86e4eea37116924932d0a1c5033adcc61731c4601e2a881106f3b011030c2588f185e3bc8de58df950211f6b0d011ce4460e2f70c4f082a19212163eab48d14917cca30331b880d9a5ae4e10211fbb11630bec65fd28eed997a297e30b08d8480099400c2d9847c84b9ea47f09cf97185930659d59b3b46a6afa63c114acfe737a9f9d8e9d87c7bb87871631ae40caa226857e5793350331ac60d0f14b6fd5e468a3a12a18de652d678fad0a31a8602ab1974232b5ee95728c2918ce7e2589a9d1665a9682219a88296ad2e5e37ca260cebe9994225f6be494420c281845a599d129b4e69bd213cc2e9652457011bd697282294bc821bb1eaaf4969a603ab1ba51394ee2f8cf0483107e3b39854ffafc976052a6e447751e9544e89560bcac0e9f8f93f1f027c11cd4ebaf5bc59160ac1c492c587a2e71fa11cc69bbee929b86a52c32823154afbcfe8553f15011ccfa715384558808a633d5dfee1fd65a3d4330967bf2115f2957bf2304c365950c6ddf39d4768260ac2024e4524a0d04d3f94b0e2e52437fa57f60d07f39eea7524a04bdf8c0a47492f7db22c7744ee981b16c54a94eda2a558907263db1542a9b841879de8129490fd1334cd9ee87756052a7272ee32408dd73600842cc6797bb383046d2340b96d444277b03e38c1651a9c2b581c9be3d5645c598ec6d0d4c41e77c96795093946860c8a1839d69664593e80c0ca6772552d292741c5506e61c4f581a1997e229c5c0f83d7a17f46dbff93030a567c50f93931718c592ce49ca25ed124262b8c09477232f9e7a69e532460bcc1eb28a25e1272f9b30060b0c33618216319672897115e6cc02db874bb12e187f63dead2d74ce29e58259c66757d6b6ba2f4798b1057347eedc9c9b34b3bf16cc55c12cad9536bd22cd82f953fe609795632c984bdd68affde8dc21c28c2bec0c2b18e7b4756a8449150c17dec23c584a22fddac10c2a98b763acd867e7f80e01a083195330755a5159d2c498f0310233a46098b4dba172853ed6baf02f7018036644c10433a0602c51a5ab62f43ead93c28c27e80c279873d4acd8113a4ea51e002acc6882f947992a3139763e533fd650b0858d2e68a079d098c104937cbb54f12b5c16110537b410c28c25cc508231e52598e985cf8e77e15f586146120ca94424695e614ca6450e339060d24a9575c5f39ef6682788194730489d20622afd067464d1c100057464613ab2e84040106618c11c75aec3b4998a60128b1e5410e1e9c8a2238b2c3abed81cc30c22146edfde4fc55a52f92118948fd87309a743d25f08265379478eca1ae71d9a1184b392685cc85e9553433e4d76f854229955980104839604754b93cdbbee1f78512c25bf6879516a860f8c9643dbb9c4539154b607e68f11c9dbb43c30472faffcb9a456077d07c6f3d021e8567460fa1447488ff5663905093372603e53672b491accc0813946a9b7679e3898710393259553239b4990f5906d606a7d537f2a8f5c86d0c71a8ef3857e31a306a6b82ff93d92aacf87c65987160aa871230717fd377258e06bdcd0c2a3c68d1cb911d8000d12cca0c1993183e72455f4aba469e2c71ae962860c4430230686a8f5fb417a369564f8b19645077b98ddc8e1850f66c0c094edb9d572d671e27e8131d3cc64723009fb161798439c5a5b49428cfcf5632d015f70d191800a7478787878e0b8c18c16983b758c8b92e3887479060bccc303325661909b62c1eb3e774d491506996493adc6aeb64da930bf9b52f3e3a93ea509152619ad19c923b3e3884e6152592ee9eb74540829a630c5c457ad1f9394ca520a83906149f5cc74fe8a92c22484b6f7a95daa557414e6b8f14e974db46fa9a230085915a174291426fd69b636d7638a0a0185d14698c627d9965bffc4a6f26df484c9457d10e1b7c34350c9e88421957a8dea94e6844909d10bab244d4748d98471c73de890be2449963561b6ea72bd90947e39c984b9a36ad6a43055414d199830988af59fe3989a9c2f19973005f19fde418c99d50532408720c31286ab3a79ab6e22d6fc1f6b663624903f90510993d6fed412f4d2af429223b0011a2490410983f8d61029baa6ec34b6d8a2699c7206f81b5da477828c49982744bdbbbac996a52e928d1a04c72364008cc0066808e0053224612e7db3b795654dcffab136ba283533332bbc4a3231021ba0110055c88884f1b3e775b68a9cadcb196440824f2b5256cdaec2feb1b60cb021813d198f307bee2c9b4f9d4ec8cac75a0d1a4f030529e82df43820c311887759a5150b49a66a234c21924ae7d15ff972d440c68386286430c23012a4eadca97d10fa451852faba77f9965ebd1561fc786fe2222811a698edec1724049521228ca391cf2478ae92593984c1e63ae4d2b9c35b8e210c29e44937a52adfa2c617270dc7091a021d5974a02087175c181b12e8c8a2238b8e2c3a50f00e301985309967c83c3cce681896e04a1e6410c218624ae25ec81e2f3c0a903108c3b5ec7dde5a10227b2b08638fa94e2cb73fd654bb901108b37a3c15d39df3b106088308f2c945db1f821b6e3664fcc1944ea4939f73d7bc3b8fc30fe612d942b44b37ff21d67121a30fe6be1821ab9a5021a65bb690c107c307492376726c0fc61dd99e95f318d5491a37fab7d083317444c65daabcefce2390910793fc20af434e677d6ee1c11cf7839c339d83952b02197730eaa8b33633f59145e7634d6bc8b08321071531ef43d86dbc175e748640461d0cb96372b6099ee479fdb176e373d8783a1843af6f8549fa9d82f6630de7608a615697fffa6f6dddcb9083f94d6d5b8f349ddebb8f353309905b7319713077127db223a27030e4a44d49992dd951256f309ae4f47071548559851e0326c30de612793227a26d83b9da430525421061e21f41061b4c75c1f7f6bf84e86b45c61a8c6a7f1b1f49a4d4136d1564a8c1a8ef2945d193d47212a7c190dadd5487caa820030d66918b21399d56b3199dc118da3d5c10e2f2741ad50c06f167ab66a3ae3ee7b40c26a52f955f5e85b8ce2183d14bcd66895114648cc160c14389d2b1d4e91e930c31184490c97121d682fae0174e2304366af0911106b366c44b67615a1dc2603057ec2a9d42e48e55e80b866829e3b7744e7d1df4b1568c20c30b46bfd393b6972571b7d20563857b10392cf985b2a840096e3c8e4301420120820c2e98d26993b9ee24c63db60573ec5b0af72194e95a309da4178d4f6aa53fca82c9fd4e7ff8249763592c18ee2ad7a57bfe9c73bc82392c9ba59027eb87cbac60eaf418c1d73bf2cc5530fd8d8a753ae7b40d8b0a66684b946ff59c7da0ca9882b94b8eeeb49072b458a4607c51d5d222259edc370ae6b29cffc5d4e710c38482a92d8d486b4b173310643cc110c5239fad4e50a1d5324186130ca6fe6b54ca8ab3336a82f9d7e563a88f6a7521345070234759116430e1205e44e819139e5f42e12c74589edcd5957045509616a3245ca14a9e8d6957598d0457a297584aba2cfc233897ed27f9ac6284947ccff7d9f4f28f5784ffd427779cd8aa25934104733ccb612bd9c8f98b7daced104c71cf8367a58830a62f70e0a8419cb5902104933091ce83b2ac3c2faac145108caef1693fd543d84fc3c68d2f4ec1800c2018e25e3aafcaad223efab1563c90f103b3c5aafd094ab2b87c3ed618027a01193ea853d7c9f99ad7174f0f8ca691b3467d4298b4e6a871c304884090c103937c56fbc95912d5d3c7da16a706fac2ff860d0678787878d0d8c20b2e6aa00520083276804db26c79b93ba866e30265cb2b90a10383921b79a268c9554f49828c1c98d2f23bae44d0e7938403a3a5cec9f44d2dc5fcdcc06caaa7335ffa84d8ad1423c8b0815125486f75fdbca293353075fc885d675aa2ae847b208306468ddd9ef36cf722321ec89881a9ca4ceb9966fb5f2d8353f69ce05dbede1264c420870c1898c4497c0b216c077904379a8bbfa10890f102a3766a496fd92cd1fe210d192e30e9996ef517bf107cc5814e03325a608a72e14ea9ab202eaf4624208305a6f819c1245e27f529fdb1c621b0621586b56c49989814269645a30bd71458a10a73a7e7a774f1fb58c39127b02215e6f1012b50619a70a3adc2d684741736ee4a60c5290c41566444515afd4bf6b1a63772786185298c599ece3bdd4878d172d8f812bcdd175694a2ea36b79446fb3c444861b01e6d1db4baf4ca95e371fc8d51183d5ccad09b868d46b78515a2308bcffe78887b65118115a1483bbc7729b36a4b21284cfa4ded5c6f6276ea393ac7175c28e013860db1f1cf126ff6ad159e306c6b79291da18f351b5db07568a1800e1b12382958d109e34648224adc96cbab114e184485ec749dd56dc2a0848827cf52af0953d4cf71d2f6ec8514ca84b172526be32bca6a2b4c982da9f9f7d39ecb7e711b351c7009531895ee21faa5b2ab58c2a463357cf48c8caf9530a4904b95bad3734f650eaca084215a9feab4a8a5ad4d1e58310993ab4bd4cb927941642561d2fe381f43098d0f9e48182cfb66b66ca81c2ec290304cc821b623c79520921e61ecb5d01d3b08d3db0f696085234c6317bb313ac88ad88d304ff02472b2929d4eac5630c27c92aec53ebea814293ab06211c613d275c288eb10395682158a3076a9eee810614530975ccb62635f3b29cea2a326d091454749a0238b8e8a4047161d05818e2c3aea011d597494033ab2e8a8067464d1510ce898c0033acce08005110c5ff15e544a659e45d7062c8660dc4bd2c63f6749385f08a69dcfa5cfc6b287fe836050a9424bce1813baf3c79a9905081640309e989cc376ef64d7f807e613d1a74ea784fb6c25163e30ae089bf0d5a394a51eb1e881e1849e4febd0a7656488050f9c49b2d427a798cb80c50e8c132b5db2ae78fb21494307a64bea6f92d5fa5c8a0c58e4c09c53974a166533de3c16a081050e8c12d4549468f90d8cad23ceca74795249b18149a438119404c921018b1a9844dc4babb3ffa1ee9489050d0ce1440ead8b65a33e9d89c50ccc17ac7254f2505713cec44206e6b42421968c18c27be701163130588d978c5e280df3c0c07467c1c672dc4c1a60f10243fcb42939e3528e185102162e305fec74fd69c2cad9d80253f8ccd2e1912e49ceb901162c3028917f44503677e6adc2a4f6218c8a53722ee95561eeace599efd65aada5c2a04fc952b1720c3dffa2c21c2f0725dfe659468ee514063badcdcff3164ebd798529ca0e7911a4fb7a28af2885f9adf745540eaa3c7e0cc215a4306a565d7aa9bc25ba1b85495e8bc52ea5623f9b44619cef109534a48e7c34140695f49c77e45b33d182c224adb3d47877aa929c4f983c08a553c84999b24ef284e9b54dc607ddeb172f9d307d8647fa94d725f8cc09d3e513257d239f25153761ce9252a8e09126795c13e63d4fb94ee798f6d6326130a9a363db3b4c98f47778fa7a08e6e9bc84b9433f947d7fd061439630e814aff48b4ef9c7649530d85e86a898bb091da284792fcfbac7235dc57412060ba63a4d96b108b74bc21494c98cbfbbb624cc48983a58dcf848272f270c09938a9de283691b99117d8429bd52d6f4e839c2341f29a84f4a9784e88d308f8b14156cb7a3e6cf0873a9b869d92f8d6af98b304e5231e5516c2e59a7088398a03c29eb8ce0964418f3c295faec26a77e22c29ca2b227cd49b7d4e221ccf7f1c98457ea8f690d6112f391f27ed89c11ab1086b1ddeaf4edd3b71621cc1f9aa5229cded6910dc25829e9d6ae5c3f2a6682308a5093eb66f24efc2d1046cfa56d2be4b1f11610e689356ab93ded837f7f309a08b669ddc9b476d40f660926e24649af2ea5d20743f814274dc8079d73f1c1a4437c4e3e619aa2f27b308b87ef91ff922f4febc194e592eb281dca8339e8a07aa4c6fa8930e1c1a4cdcf534a95b4b57dee60f26c2e5faa61da4d6407c35f880c0f3975b3ad3a98e3669b8b7ed272713a182dbdc4ecbc4b2bc99e83c92f8db8e88b13676a39984c4456244f6ae9378983c9f3072da23ed256b8e160ccb0ec3e93e659ee7d83c9bfe368e4ea9c52fc6e307edb7e58b3ce224ca70d265571cc4696bcde2eb1c1207c4b27f9dca8166d0d668be69fdd752599eed46052a762dc5ef2d360b0b06f932fe5b0ff27349822897ab6249b30217606a3da07ddf3d8da90b2198c172a9dea2b2527a45806c3588f99057921b1f26430cbe8121754d24adef7188cfb7a3ad6a53cfff71183f1c42acd8ab084c19494e4d1f92cc160caa9adcdde43be8ee40ba6359542cc49ea828ced0583ce0ab91d8447d0e9ef82a9d4c8daf028272388b860ee9c09a2b3cfea6edd822948fd60713ad382d9c25aca9bfb22d2e22c98ea2b2cd7838d05c37e8ab7eb5eaf60fc891f4d847aac60b80aa954879c2a18fd2f2b7a2a513da3a282e9fb53aed21aeb5fa12998a49aca75fe9d93ed2c0583122a8ba83c71d5dca360ea9e3d21cad7e3ae0205e39f0abf53dfca11ff2718e3daadd66ac4f7ec4e306609b12e5dd397736f82f13a42c8a61f37d2e64c30feeaf62811d43e9a7409e68fd439670f9920725a0966f3943d6f5d9d0493ac5239fc3d46024aee64f50886a82ababd713552762318476df88648b9ddebb9a208e6cff9da59abadedf315443068cf50e7a722bc78ea8a211843051121be2b243ff5c7da11ae10824984948408a13a657cf28a2018c373271dd127dceeba0208660b292be95caa1f18f54eefcce909a92ec5072679fbd9e47d96a7d63d30a439b1593a85f93b9d07c6f64eae55a5e3a992ecc05867e355df639fb4a70373f014ea7396e031a49b03b3099d82e58e0bd5a3c281e14314d15af9da69fd0d8c3fe993cefdca8cb0d9c034ea2f3ada5b0dcc974c88ced49ddf100d0c2a68b6a890fa2b66608a1cc1a36bd5e9f4ed15323089c9233e21a96cfa4c57c4c068b9ad2553c3e5620c842b60606c91907d6d7f4e68ff0586efef5c734a7dc427b9c05c6adb32758457b4c06059944e1d13f2efc457b0c0d8f9793fe76a9953e12a4c377af45bdbf6580a5561ea98dd59b9c488de532a0cf259ca57f28608924685c1639cdccfd9908b9ec2143efa7c4827dead7c539853a99ec94998be387a29ccf5dbd1f7ef23c81191c2ff52b2452585a3308574d123a2f4a23087c578b1c87f4ae40c85b972f43b071d26f959406110fb886da5a58256ff09e36887a44a23b8574c3c617a3799774ae28da8b713e6922d572a2997138694f42531a9952d7bb4099392bad725d6e2e43c6ac27429d9df489026adbb4c98c44434391174aeae7a06264c6b319e236f45f1d832675cc2dcf184123d92bb25451208332c618aeac14d8bca71375209d3aa76ee534a52d2a7a5845152862c9590cc199330dd8851497687049df399332461d061aad3f4847b38397346248c27725f92ad1891d25b8619903008e9f31af28312defa234c5fe7ad3a229ff2ec1c613e1d59951f3a8d30ec84fc9f446626674b136630c21443dba7d68a29e2468b306751223f5ca5acac8b22ccf92384f7da9272ab25c2d84967a78aecb56d0811a620eb39cb9d7d08f3e40b7dbac3e8b4962c86309e59aace67a91006f915f1c2ea76bcef0961d021ec9759d5cce5f4204c27667ff59d546a7f8230a451cf594f4d8a1b25e28c40187457ac4b8d09330061b03d5356912d5b98f107d3bd24a1d25b502295ea07830739612ba5583aec3e1866245b0771295898c10793aa7afe7b1c69f76beec1244975c8d14f19adf56098dd91232ebee890b23c98637ecc8ab815e2c17c7144a4e5990e1ec27507f3f01976309e864c4af2de4277ca8c3a98fc5225156542d0d9dfc75a656147811974309c483c254272e6600afe4174f68dabe823e9c10c39642b1fad2d8fbb650a33e2601ea54b58ea86b81b351ccc1641d6c2e88e92764a1ecc78c33d27ad4d92d2f9cb124061861b8c57396e67a737b405b5c1a47b294d0a292207d960f26cfdb80db575a5cac18c3518644cbcb6af570a29aa2125d773627c4a7549926930798868fea684e98cf73870e4781c38cab0048706a38854d2c48de68be79cc17c6abc4f8ccaf69cd40c4651ba434b4d5606a387cfbba4091354ef99253825cc2083513f57ee093a73f1a931784a5be3cc4e7677c230430ca6ff3ef5ef3bfa946c6130c84e936c46f9c8fcec6e0333c06034651d474b508fa2a3695f30e9d39343a6ec5e307d559b68fe018ae0a844afe5025120108682c1502010060308f89e0423140000000c18918662c170344d96ea031400034930264a2c2c20221e181616120886627028100804c44030180c0c84036150801c38864344fe58907986847b0f7b8be69b5731a8833822d4105328783c03a63b55ab0576492905a62438aae8e4bca0432725564786f29a7c4279ea207bd8a12cf432496af696248b54e93a633b7ab7ec901fcfc53b303bb25f9de33eacaa24581d59f3a727662b6d4c0f62072902c14ad97aa10a004ec4fb5765d741e8551606e0a7758100f2228d107f545e5c51ecacc56132e6609b4ccea8083fa61330a8f6b95db6fe18ba398ecd1cb52afea4c72d923590eec0309f5b95ee875ec5ea2eda6263b3187dbc75b40ae050790d5535389f963af6c549b7107a7c3dad8adf04b5f757390d2918b5b70d569bd1f71c597558a0efa3e742063aac6bf6377084d3b10603e9cc22c0a090d286969c27e68438735b6421c8a7d8d9358201db30ce6fce243fb3d70286f52a3c2b8a94ba7e514ef8b1d7a4aeb1d33f7be3efe78dd95f084221bc8f6603310a748347a30dd50205860a80f2417542dd40e1a11e8a5a948a13a1fa82768bb8a0ac00b51e12ea3f94178a1a6a03050f0503551b280240b02ee50bf582fa09456714a0c08bb293da808242cd433da1a20c5576028a1faacb51d45219282c5414ea190a0d6aab82aa9d9ebd5e48a108a008a13aa1f44435d7a2332650f0504d2842a8722821543fd4150a1b6a9350eb854708dd19540d0a1aea098a08450c2504552da080f4e789faf28dd2017bec3985b242b501057c0eeaab0905ab9cfd517d58972f0d82fa8151c005b6caf3200a38db7cb3223e0af8f44cbc5964a8bb9c08f59651aa166a0a0a01b502c587e201d5835242e540bda1805006ac42c3e7a38d50408bf647ae1e940ec7326e70dc4583a1eaa1684f934f676702ab94a8203c99fee9a25b2c2a503da0ba50d45019283c540cd41b8a006a4743f5210a733e7d51f42405c4351bd2ca13d7d3ff137d8b55e2b21794ac19052cd8cbf0792a7e22831a83e29f40960a56b9486d423d40b58a02fe009eac993fa8384ad8899c07140314124a1daa0b25ce8fa20d1d6bd411a13e50f55018a87882d55a64dc4c9d07794ed6657fb956aa8c514030c1e0187644828cd25bc8785c8d0412c9718ad5509715b1ccb85c663f1d071a4c776a54e2d0eba96f0b355d0d9ca2b62d0a5a84e8994b3952129d308d4545c8d6c311059654081c01da4539818ebdcf096f3918b05b69d869c0ddbff9c8c0a5914609eb11bd21a451820451c76d010336e6c910e066769359649cfe4407cf9bbbcb6c7cece6d982098bd1d2660ddc7ad34cec0390b54ae5a1a15e1d2ecb19a99f6fb2558b2a11b88a178b418dc3265b86e761ffdf96b500c92761fac9a0ff99142c5d6d9a37d52a67062f24d84da60a8c99dd2ce9af6ab724b2b10721a6db8d21594b01d1ce76af4f59eee44aab3d61d8422e6715c41200928543e821b6a06c85efc36c660bdc55e8f0e05a7b80e7399b04762cb0d69c867688deabe34000d8ad5a050be06f5a110880f9bd5ba7eea4eabbe58f6b0e6e88c559d62f4be705d78a9c8d918310685ac039f950107c988bd53debe905d74fb310249e8f6b78307c6995f318c1afcce58218c1b98a5cc393e33fcc5396406624ea61e15c214fba16f704286a560539ec284f09c1ae8685ef9c9a4151d523715eee90183a69fd25a56a68365fe618c940d497f2684151593bed3e13a689e8a4f0d1c270c61f872847c52512ec63feac0a0dee3409265e7c94be0d7b0aa34680644be956032f780329081186402d07d2a736e5332b2f25ad23be8d0eb2a6d11b2ce615ecc47245b54396a3ef563c32e48a785b8f15222df8878a0ab7b7f97ddc3fab72bb08b3f7199eb4b2130ba005091314f8616907ccf2a5da4b6c1a9f3370040a0034b1202989c35368461baabe42728897c0d9cc088b1d40af3704487f648210034526c65b2d536d83d8e90ffa29046fb3163e61ee141e955e8462540077bc4d90ca03e17400380e1f01bf3a1194032ab76dca73853c44cb2f10c0e1fab87cb4fbfc59eb41e12cfabc38e02df466b9a3fae6a8b2bd37d4f7d9e517da82de5803da6e8053d01aef06414a042d44a4b2d131bfb711b094b8f342fc6fedc39a1012b09776de9ba108028a332483d6b2874f16567f176375fd4a81590ecf5bdef327e9774838f05f1b3bd14f930f4f064c19a7739282683d406c390705786824ea5e094c44a6c1b78d1b3e7d8e34b5b44a154f2d294a591c5031d8c9005633a02bed785e7510cad0c77bd70690e7fbb2d1ed054c940bb861114ef0dc44fc1e3c8cce461b72536955bf9f0c75f2088594eced2a790de33327e09842baba5eb5595de22b4d21506928fc2b6bae65e5c00e7e45565635cd0dc09275df44c6bf2021d800e36ac4439a08d712c5b21309e6c5abd670d159374c0b4f7e75ab5b1aed55142ac0d0568a300e92dbfa51b759014d779573f942e897afb4ec2002e6650b7dc5e4b1fe1370a6a77b35095905684f51c92f9f387284f163f81010390dc77f29e6738de3030e3a9d29f32e0eec18ff9449e620220c9e536976384916f2040ca9359c79cd3763c1d0fa84b5d81d3052b56a91c8b7dce93b8c0310cd80ed675eda07ad1f63f76fc5ddac0e7f93e34e2e687df4e6b4119683850e72a486a15c0718e7b714cd84668b7ea6ab7d112b87484c5e86fbdbb7bda12a8989070e47e197bebc530aa15388a62d08193d64815851a46634a56ca0df3743f4a60103eea80a9f3bd3c1861e63b470172f708e2ed725c02f79ff2278cfdf777e2db42232246e9d819ac05456e0e1cef04ea8efdc92ae482b8a90f4728d414aefde8217b2c2d422b2c827504576229b6bcf5771448158c9c4b6828f865adb5b16903682f48dba37b92215de74fbfccc6933624c8423c34ecad7090f8ea4711e72615e8c2c1f7d2fb6ef6622730d7a10e2848b0431991a6201976869727ec4a7ccbbc3c8f50bf27bfd349f29afa08745e7bd06fbf412b3646e46c4cb04764a48b237bafaabfa18c1fbacec1232762588b1446907f163b576466e883f8eeb3cc2145f1d36544262eaec8c16da23d4124853cc818e7a520112b44932d0132ac9f38d18d57c2bab023ba1251e08ef80583e8d882f72459542ec6ec919da2758476dad7e952c2661e3e75ed5960254e2daa79cda8e2e2b00ddd4a37ec3123c4eac291763ebc7cdf99f20ebd00cb5106dfe2a41a560283e1b2e4245045800325baf341c5f34a5ea0a86c9eab6029c302882e0a494b0939a72115b7ea0904d048348c8b832f91c7b8a24fae9008b5ddf23ec600daef4460d0446cacf53990b3c20a8445cef70b286ce1e669066359ade2e326bcd97634c7178a0d3dab1bca25290c2ff7e48264182ab2b934454daccc2df7e9b7cf6fbf281687cb70ee475ff0a56c6deb3dadd80218baf32009b29ad0c256c070329996964f2803c421ef0674932788f00c4e843376302462c9eba4249634d8ec394103c94790735ce9a1550b5a5ac8161b8265107b5768f3e3b563824c7aaada19bb727de4bd72fd3b3368646ef918f9e6af827eb0ac977b380c66fe377a17a0e487cab67d21ebc11cc5bb60d8e1cf6e142adb99eaf21cf426b856d13ba88eb129218d139c7f7425b4fd0855fab2790d51ec03c3a8219d279ffe5041b029919ee7f8b6931c9d003d1d42d8d36f112f5863c1ecaa67466e71b5a009851046db095753fe642f55aba6e5d777925f17efadddbe44d705754f5d6eb678b0b846614c9a877d18d1b51f3f30db5af1f65c2f011ab44464f0ca77b278617749fb1f699b767494ddccb04b05ab47da1919f63bfebd562eb4f8cd13cd63178627a04a2db1c4b57af0823dd55dd43c00d45c0725440be3581af9691289b469f564ea86e2282cd50032c0f28d1adc7583d39ca57c0b7f7175576a61721a5f884f060eab45a51f19bfe89044892299f43c0074818f297705ef9eb39dcbea12ebe5e928f61e694809d32480e6cd67c028674a2ec3e8af3c7693d8812f64773f98117108f0bc849745d6cafefbcfa886d6e1c31d6811f36af62f1d981a82e6f37a762591a1dea77a1a3bc808f9ff571fa5130c9b6930decfb55c2f390ac8f9715c73c9175de4ec34fcf4bb49f8f99697e70fa753c58dcb725ecd0121b247b2249aa52d5cb5a2a10f8863374a8a98a78beb487d0a898178c928d15af595b64419003ecf72640bc35773afce9a48cd35cc33b502ce3f1acd010d33f9f949f4019eb9ddf4348b7ba0fa120ef11ac1967e95d6bfab46d03cb925c770c193c426050ad221501414e113494d7b7ffd7153378b823f99310fd6a3007dc9195856554105775b86a70f57ca3818a0853086ce80a146ef132214aec8ee55668e5b6b6777268284d0ed6f1707255dd45ed81ce8f50d3eaa0b06dacb420d04d750485051171b485f56bac7859bef7a291265ee331f92ee40351f238a30a27db46994b4940ffaa91e2d58f816c2cb738bef02cc9261baad4388b7311e83c91a1cea47979550978c9929522a7bfc381070767dffebaacb46d7f06a6fdec8d872d961470c9320207ed59b6870e2fb03131dc713dbd23a00a7b30bd05d2d007cf81e5420ff9d3b4b770679dd94c07e9c9373e2756803109891e19041a8031e8e3001ac2068d2e2e9e4ee2ddbfc8a03e5ad3f5e773740916ce9784ce021604d021713ef0061b0ccad5600376e3b476a1b049e9c724c759d7154c4544d022c4305108d0554472a1a2fb71ba3752fc5e4d6995785a4a4194f17e73fc6aa0e6609ba62345e5e30e7208a7df85f177f05591cc2137ee6a0238e89fc061aa8461459968f3a1feb4af103330b046b34a556dda991ccbb03d507fde133c38de337881de72df46ded352ed7f6d60a8173570ef79591c8201c5a0c099c3817ae6e304b055745b4c2b4cb451bbff911fb477d73bb739345ce55844199de487f0c43782411e0e5299df7943808a4cd171ea74fdbba25c7691e4652d10df6fd9b5e795a2e88d5cf3799bc9301717c184537c4c3a7cb1c4bae17e000177959b8c8e5b0d9b33a8e8166da5dbfe582cd24b172fe86cbd0d1c9d232a6211aeab197a9f7227e819364109c525420c0ac53138a350877d9eb77980b254ec92d995c8320c54ba039072c9e5cc61342288742f9d0e5d6d389889d66d478b085c9b142d3548fb8e5a609a545d62431b995803163cc25ba71558dc75ffab361a8692e3012c85958b789cf41976c2316289ca91abc364297e1a943db93f37650128d48b98e4323eaa8b28d62a2747fab19956f575853632d35fc2f9edff15f9a7840eb01a2646e1d6a6e48c13faba949011c9cb9742c56690d4b00b3cd940adcaa0f3687b73a0e30e60f6916d2c46108b9d7cc8d8ec4ddd2ce407a0ab9251165eb5642b8910509e6cbad42b305a452b177525adfbaa0a74ffcaa463cf9132d6d0463647d223107e9b1292369a01e34ac38105ade78be7c48f0026cd0025c73423031c8973aa03328bb87c4d9b8f7bd14d3a6bdaff42fe50dd133e275b4509d7dc748fa3157bbb0bf77c27afc7acd50e0e0342f76d32d24a29810a0b1295e96f0d2b981d5b8c3fa32efe0e71836d303d01b026a63472653acd7199f874062797c8dd7429e13d1f8021a2cc6b5d63eb32a45750e02d983b346b872bc9948d4bdd4820f580280525fc54934594a87f1eba3a05353324e49f164fcd8530368746d04c4c4563d40ceb8cc0c7084d0b409d631363ab8e72102980a710091d2a8007abe4b352f87b3d10888033487167cf89ed3e2870a94c24d6cd398712599c36965787bc30b1728c46f6858c94de582a314691d41ea4c4874a466fe0927af070edc10e059ec8846cb702dea0838f5a72c6243a152903d4a873e097cd5b030d361e5153d49c141bc50a0a1d8a07d51d4a81ca859a818242ed0520f75a42e94080e240a1ab514027e9f97432541f8a0a6a0f050d0585d2c120f7cea0085028fea83e8c51a128a0c8c128208699c022238517b71880578a7c5054450dd78283843216e41080416945713874284701d573447e6e1d403d4315a10450c1a02e50c07fc0e190e613232314050a7c507d364267d881a2c22860ee66f2b738951071e84d0110ea02450b350305d3e0704ca054044a4751020954417541d5e7a0a6d0ef2d1e1214b91487c303555b544d0a8950f7e6460a96c028881422d40b503df34c217c25f3d40619097e9175e36301501ba07050f35034287aa8081415aa152820bc4361fda16bc2f49f76596469a7f97c889ba940ea16a2ea23fb320e0a1054758102d2e3009f850296a96ac00244bc24729bf6059c465268d0c7ea0025e9bee1c9cdb54b4d857cbc8baac682c4d36c4085baa86dcbeebe0bd1eb20f654bb59f66075a73a339ccb28721acddecd53d1f8691b7bf483147480522f8650b0ee6566868e5b29477f1fa7071119ca2073c98c09c4b4ddb1d237f9a4e2c9e453b8102b69ce3c7b56931084a6809fa35823439d2c6441398c21b41fefba294f407e3a41b1370eced3b7c338e8ca54484b8212da1a088b517b0e09e8e6b4abe23f518922f70d15e7a4b7ed71917b82161b2f0b8c312062e9a6f043c6096875e8df071e9f529b96db2a0a4f54de1a21ef0551a7b08c6e8d853a5c19ac3df51f2eb2cd3ee5bc44f7a6198a0b211a0107ce390fb9e51ed0cd25b8affc8ba20794db247d97c95f5ab8fe8186a0ca17d2f247b28a33dc18ca17f3672a51bafceefcdf66204b0bc18f27070bd16bfb10b64e31b1aa6df16c6aa96362243250d441dabcb6a7f6d0ec9c53708f066936e3104982842885f1aa941fba5141fccf1ac97db81a87a00a3a88e79f03440d5dc439c4571f208ba2c031ec805997eae756c3bec8ae630546cb40a23c7d0059104d1135344d0aae4f3ccb66be20ef2dd820de3202a61b3b590e5ccd9cdbcfc85a54c1693a37d343ba5a8286988ce03493bb562f1991af282c9a57a4468430272d0b3cace3f87e8adc646a20081eeca1c2c8c567ada75d08c65987894dd10a84515fcba6a653da1635f1152ba2c59a376713a8f04172040bdcfe76e3b3cad01acf220cc46486475412a1dab8b286c92e541b2c863620aac61b3f5dd7bbe41a31f14188d2ee1bbb6de40f362c5744f237013965bee5ee670f533600005f8533e5e205889b75570779cbeb46820fc9f5c76ec76dc2204c0662d3264ca53535ad9c1c93be2644ca770837e32da5a91616af2a8b00144ed8e28e80fcd0cb376156f59669c0f154df46b1cf702c37d9b8ab994d7bf4266820bfb5df32ef94ba958e44595ece3b7c83055649c211c471d88608bac949c61460f520aad177b8398851901afacf48ebaca66125ff0502bbdb20218800142c83b4b97183c3918e0a0011b62b908b1d4d1c5d20647b5e04c0911aac2030a4deec1e04b7619ae8f2ea0df4bff25f6618e67fe385eb425e531482da14584ba7f7e219a1b3122c7154dc05b898a02dfe5aaa3278d3bd7e22ef60e16f38a72fc8b885bcd44fd1d4108d3b901fe5c94b8142c3a9fed6fa71f8837eab2a74db6a350f1c32b75d95c40d158b63176188d6db10fca619005d830118f9db36a5a9064c8843e38de9e48e47692e7a673fc86b3cc933803f029db5259712ff9c721fb9351f1138706fd6fac0bb41ba9cffe3a25d9a188d4ca52e01861fef119a4e196fe4b50c428a8eb84e8811a2aac455db5364433331d2848baad4e91da2926bccc5456a63ff31697e854967b43c0a43b0f5ac8063208387ef25634dbe7d8a32ce4b488829764721bcdfe633da32e9372509cf677bf0337815a464569855d8052f02946684b3dfe09d4dbc7aae9978f1dbff59856bd3a63031345a771e5c64ccf43d6d968920b0f02dcd50c0fcb02afa0d3f94d0367883c85f7ba40a157605c960aee9923248261cd789ece9f280abc95c4488548cfed61530c5569490407080558f51893f274aed5efb8e6bbe4ca623a0f99e97f3bbbe442c023ab4abda0d37efbe1482f4df3a7e65da022868fa8a66db2b2881470cbec3386393ec0baa9409731dd8990e6e660c4bf61036accd9b1175dd5684786ae24d37855f22d8ce36733fd6385265efcd2c88a2f9e844dccc6d9346b4be494e1c4335562df81d2cc3bbc818ae85a5b1f332e4d1d5141bcd2ff499a61df6e852767995b54b2ffca6bd440bc7d00b590891467683899dd150b2259e91f4a32a44bbe8982ef4c61ea144bcc764d5be56a931a467fa654355e93ed8a17882dc7fa5239deb9bb1825c6f9022c04d0acfbb5953d11041dfd1ad0a9352b1dd9ffaa6de065e95d95bb00a4366c92337ef10a782496f5bbfc37e0bdb0c6fc7418718c7875f990f6c8f3a9fe86a3d99bf0095de12e87a178c477edd00f9157bbf766f9ec239ff176c2cda36d7546f2e7fe3d387dbf9e526a554c2eff7515b79b3bbf17bcafaca33a443d835e3d3c0c864973d3e6f5e4567f1fa84d0359379cd9f662a4eea34398b0ffe266756023f3acde43851b9d4932ceea8ebc4646f560833ca2ceb1d3213835ddf9fadbcf2945d4856f43f629460190e334b2dd4df6301113eec3851b657451c18b0c6f091aba855a98159b9ec1a6343eb295785571529545ae81388a7b837c2f0eadd8312192e400af1bc1e60eb5934ac42919fbad116817bb457278c316f6129dca5cd6ccf3ba68c1371916ddd826a1e64177abfd10daa24fc2e5a3e13cb6d8bba7f538741b9ad15976adb5e4a714f8b6c6648a4866823dff8abfcfde33702165fe9126db26eaad72a803adf5b36226705d5960c2bdf754f871108a63f600f8f3c770ace1f20369f827b7ecf3ba005905cd1f67f98a1826fe2a9f770284d93e665852c84b87d60a9823159278160254e43d6afba8266c887fc47aa94cb7b45be69f0206a233746ebece0de7a28f02176d0884e52b2873165a654a35a33d904774775dccbb6e142075c558e333d46dccaa735a49c22e94b5e608d72f715a3f8bc9322af5cc50765bb30d9c11d688f631eb8e19a77c8b051f436d14f643e697ae74971804c7a0e1ab64361512fa204a5366b056db0d2f4d9d725f1b5e65a36f89da4d6344e6db2f6e971fc938a725e602317df68db6b7e0ed3aae399dce37814f0aca294b012cb698c7a7715d1962a3b141ad7085d5fb65a01f1a59b9e7971fb175337ee9a18651d96ec8342055b2b79466b3f78b6dc26a6b412d15b52825f1a453780f5a8a16635ba769c22776c1952aa48acb991d228570ae165fb59590401330b40373a13bf36b1e9d4f3a4148811b5d69162f661282fe7470a5852b260a84fa14c420f13f59d3346dbaa38538e6b4c8bfc295f927535a62b9042128bb8b2b9bdb1d3cdc225acbbcefa6b0e6dfbf0a85a3f4aef53e07980d5e61153e0b26f0a73947c2883e7d9a57b9b45acf94b725452485a3a832cf0065f1d9f4da83aa9b7ed10f0c712d8d849c5da8a69a6cd6fb11d3d71ca30750f4f17d70110246680a111d8c5a2bf6ec7c9eb581a77fb858ae474d26b4d03c3dc8b859f5a3f828f66e9da153793932d955441d86b77e990a540036210eec0a27e750952470454307a8fcb6500fd1bbbbeeb622dcabac74fccaf9139642d285959974a8f9ca92cf13284c21161a7674b082e115a5bf689e0d99184247178472d8f5d1a4bfdeb70aa1a4cc8fa0eebb7f645c924ffae936151ead2b291784db1a4ea66f8e7dc20f8004c144b4c071fcf0b6dd39dc63c96b315a68765787f8e652e0bbb05e4a517f1d328975927e819749db785b7330cc1d6f2f032f1431851e1bc4285d1e100c070d7cbfbf662d71a3b804261bedad1e1f9bd0851f90255957417ceeb683d065677db37a6863c27ffe9f15e9c04f0f8294160fe378a1804b2d963e7004414bf6c1af1078a258b18fa87ba05898b81d5f8c0aff0de04b16d08a04a50e635f70c920b38f16246ed95f9dfea2bee30e1cdb6e22a010a3e19c276bbff35266a1d9f0ddcfd86eb597f7c4a8767138053dd54de41c208b6a64711918a749274adb30c8abfa62b2c8c7d373c2c76ad71676080b07e4d8b895a0188ac9dbdd52a96c7c20223a9a2bd67f25392f6fcb32f85f2f9e55235fef88e222e11ed72dc3a366ca1d9add2c275b55256cdb4ea8a6f24805ab7e8742279f5d6aae35320b1bcd81254b5274bba383668edacf81d07118e43933e33ec2a1a63f6ecae5ffaf79604ddb54e759fd6a5ac2e1181fb62690612052419d03b04afe4472f552b9742fe7e0b86fa22acaa4d5ce02bc47ca30c65b808e22229458714753e9109b2d1332ee26f49b6e3a0581344693717e8b002213b126ee845e6837b46e45bb1bb29cbea16ac17c154f32e5e90f9250c0e1e682ef4a64d5680cb6bf42d2fc874f4ce88ebd1696ab7efcfaa821824d6fc7e24a44b9e0066456f5b69931d6018894cc6d4bf816b534e3f5e5ecff7854d44aa024603a4010305e0058307153ee5d43536c9aa160cac32817035c27135e2071bc9fba8231b495916b4b77d044d5d6001a30dd2ee794b1d66d44710a6c97ca33c85a343af4d638d0c61d0ed0da29ac14c8a1e139e175006f399aa9780f2883b91b0051832b340cab864527d9f4cd1ad47b978ca36990c0ba466a9a76091eb50714b1d29a08c05524dcd383439e335a3ad900c72e22dc88a04b00779b11d5c440009c2c196617089d5cd66150d155d8413ff4859da482bb2300f2bff8e02a64b7cd104f9103fbe41a40804cb1c9cc431a1748bd01d0ba00f73e23b888c037013a34604148e59503a2e400a1a29c945904505900afcc48dbe1dad384b36d8e40a503233e0d0ec9595b9aa9ac85c72a36f28640f97eb31d254e8f9e1b4d3ef3202529e424a44f50d62c9e83a9cee88be237211f885f00a7344978e4fda2f89fe982954b1a8d419b5d37a20c489ecdd5df763e022cdff5ddea42025b17ab6a079f61c0b797e38a9b27e4334141295e043c347943e85233c54d2ab773869562e433323945795f99572ff53f02cd52bf7c7600e72d0cefde3644e1aa660458cdd022ea684100c3540b0a6d0013c0c3c0c3c0c3c0cdc2e187ae36f03438e4c3215230c8d326ac494924c29a5e45e580fe6cf82776b44dd7d817d87245c100cf60b190ce8ea0e06f1279d9f25cf3feadbc19473cd92cabb0e06d9ef2f79ce83901fd3c1b42fa74cb68fe6609291bb27b7252f7f260753ab76b838954d5c5a6bc4c1a499abe695560d3898de94e0a54a8eca718b0935de60cacd54bfac3016fae51c9c1294e31a6ea842c655c4d2d65d8b96665f87f14f97f11ef8428d36cca8c106f3af28fb91b71825ff22d45883016aa8810035d2c0a30035d0c0030c07d43843d530c38f248618078c3290a10135c6c0801a62e0a1801a61e8318605de7d20a006181250e30b6494f13458400d2f2ca046171c50830b5b3066be7c6bf5f9e86b47d6d60835b44086026a64c10035b0c0e30035ae4046016a58e10035aa60a306157ae0a831051af80f3192901a5218408d28f060400d2888d478028f1e6358e000b9810570d470824928b573794ebe09e64a7156b289ba52b533c170d2c99f20ddcf82da76ece810b080470f6a2cc1205a62b797a0425e2929c1fc7dc1df83ba2a3f3f0906f1e1b93eade5b82f428249906d25c653c9abd1110c5e597749b679afb811cc9564ff1cf3f12218c4df8dd89d9d08c6132dad29152f7a949c2198b3d9d6cd684708e6ae122c09d262bd0f824934b145b97df854e91a403025a59359f76b955ef81786af92dac3058b76f2be3079d015dee6e49c44512f0c339f8478372fe9c479612ae93f8973aa94fdb5bb305896a4c4341d7d495474614e7176afdfa3b9309524c9559d312e4c49ceb2def5791469dec260228490cf36a6458fb630076d5a3ab67f79deab85392761c407fbea0a971626159e4ff84f3da1549b8579aec41f1d538ffb18631c2029e810b080070088404316b1a0010b5329d163e96e6f42ee25d8b1c3819457982f3b5ff42c7aac4faee8636d9c1cf5f3ad30059d59b28e12b2a3faac30afef7d291d42ab309a9c9434ad3cc553912a0c5eeaa352a94fd9829e0a93601e4fc899643d9e0415a634e3b992ca62d7a6730a839f24e5ff4d10f1f0318539c925d7a858e9837e4f294cfa3cc5740813c4b6438ac364c57333d91d85f944a5a4a275236eae284ce9fc72c92a96c2741b0a93fa0fb527c9899bdc82c29c435fbe0a8b8ba5da4f18f3d2ecf65bda13e650516abb449b31499d305b9dd81a4a4e96ca9c3099aded56dbc596353761cabd8b2f0be7494b34613691cd8f2f4aec5233613a499ad5123ac4844996be943a09ba84c19227a524d5cf12c6959113c4c35709c385b66c3537cf331f254c62d6e2ba095182d06d12e673d7dd71d1b66b3149985a4e687f55226192bb9fa33f5f6643c2bcdba73f3ee8504ab34798d297d292fd4246fe8e3096202b8e127ae249428d309fa0b3e69daa08edd06084d1e46ad172d1ee640683c6224c41c682525259567daf08f3ce8b9afe786294c68930cbc7aea709efd7af22c2a03d4fe5575d1c75ca4398b36c4ba6ad58c5958630fad6c8b533955298570893d4101e21cc974649394c5ba6091a84b904b55ebefbf93a1784e13eaed66428af52204c9229ef92613140184cbae98f87fe60b0d492632949e5dad20f86151f29eff667a2587db06bbfa357e911a90768f0c1a46c3c8425b9e3b8d67b30e94b8f6a2abb3baed3834189bc1ca773decb93e5c174494688922ceea8933430eeca0012c4c7eb18a70698d0c0834988b6bdc77fb7d15b183ac0d071020d010b788880c61d4c9e3ebf6773926c635286183e44c079071a7630e512e4732e1d472ad0a8834957c7adcade72279ce8600cd91bda7487e6601051957b3aa4fe071d3998743ef587281fddf1389844ff790b268809f32c1c8c2722fb4a7c0ed12fdf60caff26fad647286d1a0d379894a462b3e26fc2a8248d3698ca3abb7dac144e9ea883473e7ad880061b4cfe725246b5b40955a2b10653898ccaef39f6499e527671a0a106d3953241972cba654c12c8a7a2910693f44168e594f30ad0408341f7374dd0911fd7c33cfa0234ce60ee5efd203ad77e1e44c30ca6127e466b5c69c627bd40a30ce6f61d3f4957b4d9cfe7a5818006190cdebd5a2b6e4a74101a8351ef3c981c3e480c66dd36b182769153fa0983392d4d49da713ede3618cc6741c43c8de7fefd05a395a7ff957c6ee2a65e30652b6552fe757ddabb601c19565eb9f7e4bb4b0834b860f8926284906a73cae52d1847efa47c502abd85570ba692a3c3669ad866c1249768d2c4891fc753090b46cb0f42eb89f90aa6e46a17ccc6e63f9b56306528b7d2a1fb3f88bd0a26ff0eb7d81aa282b9f6c3868a65224d7a8e0c1a5328c4c29ca872b2537734a4607e8bf7a0540c0b1e74ca0d6844417b37f9701b15b515b39c95dd7672d5e6cddd020d2818d333c4e7196df3ef368d2798e5e414b5f2a94bbab01312954ebe36c62698f24dc5e5ece92e5d260d2618e4a82a49d0f79fe653bc04a3aec994ed204e92945c19417a88d1a3070d25182d2c8912eedfc4b2388d2498c4c9ca23d4cbe394276920c176d0388249c8522ee71f463e286123d88e43a308a66ed336e94e4936228facf11809d223071ba4470d7a88c14307348870348670348460d0bef433b76bc25270644d5140230826bf4f51de92963480603b7e71337c71337a610a4fe285f7cd5de7bc30985ecc7f8feeb57817c692bae275a7f59ce9ebc2d86a55a272f548cfe5c254d209ef26888b140be1c29c929d6b27b94c7fe816c62bd9ff4a6249253db630a9205757bf7a2d92a5843a3565c27cea3198410b73a84959f456888a53320bd3a5b89c794a6eb4c6b230791ef59cb2e57ca5ef8c589884ff3c4a09f9d1a3b2336061be3d258f56b3939fdc19af3099145b7274b194f7ed0c5798467c7b9056f1a2dd65462bcc5dffb763d2df87b9cc6085f972f6d295e52075cb19ab309e58dae964d4ee62394315661d9dcfff73acd41fce48852949c29612bc2c6858380315a63195acfb6fb4b289cd388549d23905ef7cd254f4d80c5398f479ac98a3aef2519b510a73ff29e153929438f7ce208549963b69ad94fc9ccbcd1885493494acb6df1213a600086286280ccaa4ac5c75b2c9298a720f334261f254fa2edb2bbae6748719a030c9a923fa4c1a8f1edc4f989229d1ead12a5bce559ae109c3a987329ffdbc6046270cebc946ed7dd6935c4ea052e54e2686bf657b642d070788964d98be5ccc427afc9f5819592b23ccd08449dc4992f8717b634f34ccc88449e4828eb893ada30be424686e98810953a9581eed703179f54b1884cc99f4adaa549d829630780ab94fa759f2255a09f3ee7e6fd8e8d6133f258c71d5fa964bf211de24ccfeae272cda9b14941c49184b8923945ae9cc8884b954875f0f2a6e9e1e244ce263c74e9a96f48eaa86198f30f7b5a89a32418a85e808a32949c94159d0657add3acc6884c1b7e43cc85f2b3735238cbef339f3f204f95ad930631126a9272531ada4fbce7f8622cca7d4955c302311065b0db51ffa9268262a87198830bfc9a6a475eab9c6e041468f2e03c8df80046cb698710873b8ad0eda497857b9cc3084493e5162c227253c98d04a116614c224dbb93d9a925732f2f1a31807d0cbc0870f11c4620621cc292e3c2c84522a55c91835701d14901d3b3231631086ff4faf0d2933db1d226608e253d9ecf2fee5647278654620cced7136aaa454f6271f595b1d616840dd300310c62e15bd3e56e938d2eac58c3f98cc5c65bde54e8e3e068f2087077abb33c30fc68e16ccabe3babbc73098d107b3fa65d1fd5932b2767c30db95f05822775a6c1ca3070dc8d883a982c55e515ac4e4e746d668f03f46d91e0a66e8c16cbb67ca2c76b6ca56c983c143b5c9154e4eb2fab761061e0cb2bde272f47f927e251966dcc19ca1b75de9aace04b18e303ea003c730c30ea6f571f524e8e589274807183a7e073d4cf0323823d8b163461d4c49ce51dfe309e2615b8819743048cb968388dcbbb4a43998fe925ebc0adfc1aedbb163c78e622d881972308eb7758d85d1d8cec5c1149428ba72f2e8683a38182e98a7db33fd1bcc39ce6bed3a65bf6a6e306875d7c89ffc249fce6843a11f4b4bdc8e550533d8601cd3e559cf625c10320dcc5883b9be4c849237a9c1f859336fbe7ff489a63498bc6269a57ad060b44f71ed627730d19d3318ab84103326540a25da663078f87ab332614c54bb0c6611de97fa6a6c94ec92c1a0c2579c7da9cba2eb18cce9529fe72427f2171583f14ad03e67a25b2c3fc360dabf7d39bd120ca612a497d44f39c9a17ec16c26a79dc395dea91003c5ccf08239fe96b09aaf2447785d309d942fd933c44b30f1e4ec236670c1d4baa5cbe2fae7687246d6ca160ceef19792c7549117d38249296126cda501897e30ac9f38ba53f78af49c9b10d207a39b5a2ae194ebba89f860d0f94b29ed7ce7700b2442f66070ef9133f932bb63c43888103d18949cd893f63f7cffce83c1dc3e2529897d0d2178308cb2fc78a621364cf60ec6baec7f927b497292cb0e26d9abc3777712bcdce6516208a983b1e343ae76bcb33b7941081dcc314d79366579557ff442c81ccc9d2548cb2537ddc70441881cccdf492739252c3dc5923898dc2f89d70f26d2c403f9383cfe53a20f42e06052827bb96df75e3a930a216f309f342f4a99248683103718bb7d74e5933ec54e9f1b6490903698bd3eae85f5f8a95ceb5b84b0c1e8a592cefcf89c1db41f014307183ac0d011c607745860072f42d6605633d95450ff512620774042d460dccfe92acdc92208498329bf6d759f9ca757151a4c52878b27699646d67a6d107206931cf6e42c7592dedc6f64adc6083183c9c2454813ef7622e391353c3242ca60d056e2da6782dd9b8909216430a55dc9e1399ed649dc47c8184c92b4942bc64d924f8acb20440ce6ddca9cf5cd47d6d40d21613058befca5e73d5b186f646d8c1f41c8486c4308188cf1694dea0b1dc4e8d810f285102f584817f404215cb01d5982902d98539013c2937072f0a4a43c42b4703b4a1642b03083902bd88e1a8458c16023f6c4a9999ea00f11ecd8e1e34715cc39f7eb2529d8f8497b64ad06ae03534308154c42ee7ffc9d1eb55b9982c9d4bce2840b256dda523029b9766449a33e3fe95130cc988538cb43c120b34e5225d79960e2279892b03af6d92c4e305f253f394b6b79254f138ca21fbf724ed761ac6382614ede9fe8f3124caea562eb452d955e9560dc50d2ea5fb5bc949c2418eb63cac5f9d441ba88048377bd6c974e2bff3982d953acfed6fbb8f799110e32cda42b9f2db7428a600e96929c8bf5b94b46214430a91e695ab54a4330c8d6cdcd4f31aea264647f1022844dd261a5725d10a120b82775ca56a57ebd630810187b93f4c979df03481853b0f10b639cf614d1296d29ad8dacedd8b10308367c61da4feb37a3266f4e522ff6916d92ceb0245e182eebbf8390a5d3c8d02e4c9f265709da35d22aac0b83ea9ca34962ee9bea9a0b6325afafcea5feeb212ecc2687bf78c2a9f9a7b730fa5be59496ebcdaddbb1c37d944146dbc27069dac446fa8bacb916a676936fe76dc654286dd0c21cbf64869ca0c4dafb6761f8b72e93ea7dc33c2e0b936c297946c58f6fa6b1309f1ea15e4b437b7c7164bf07903060618a15f3b13bff0a53fdaf9fecc5ae308926f8ba7b3cf934de0c62a315862b59fe94543affe42456983cdda913bd74779b9c10b080870f1bab309fc58f193d2255184674ca853f15464b7bca04a1438b7f8f0a5338758bad97526b074f61f23219a5f374d62439a6309ad89ff2b957fb4436b256c60f2041c878b41f78f4230475a530ac998e975e27cd934a073d5293c2248f27a5b4e6cc97ce8fc29483257d1384ccb8de47d6140c1ba2b0438f0927cc55caa1306b581274e89077712f284cf224512f675ceed2d7c8da0f1eef478c311ec88ec0c62730c9e773da13c64effb9aab5ad66eac14304363ae18cd727294d9cb09285d47513863ff149caf1526f6623363491785b7dddfef7b57b26bcb4279824ae39b266983075ca494992b09b7d5dba843925ebce393e9735554b9883b24e5216719ff6fe4a984ed691a9172e84920c7e810d4a98a43cff93a3741825f3c8964dc22477ecf730b9731ca54b4213eff7717fc9489852f2f192d7726a318484e9b774d076ffcd4e623e7a9ca5066c3cc278eaf4bbe4da1d614e162c79e93c7b26a939f8a18d309c94263b999c6684a9ef9438fef941e7828b30c8f1a03644798a30854f7297501b27893f1361ca395ece3916fa4fc4883049c92b8758d57fe67f08c308912b6a02e4078fdc800d43982de8a0ec93b0d713bd33cc808d427c7266abcfd97c48b38ca7018f20ae57860d4218f4b968070b35f75a26076c0cc2f81eef3956be2759b991351afc93c1c3c78f04e1712eb02188e45d5369965aac1a7c901e3c727080a005229faf0aa72b4927fd051006954ca45c89962533cdc1fb38fb8349969d32398b3e7bf7479607901f3c727080a875c0861f0c965ff5b633afaae436fa6052c9d3d78a2a61dbee91351e641cce2b810d3e184ef98bffbf75f0770fa63241cf873a41c4c564011b7af0e2560adf718bfe08a2f736f29058f2ebfb09a2f691352583950c1374593c184c9075724f656ae30ee62407934fc9a5a4e5ebc8c0861d0c26f44b6c8fecd26983c0461d4cd97e97449810dffbd0c19c9469b3f441c9cff93307d347ffec3cbb6cc82133e5f17cefa29583f781898d38183b94bc99b814d4e30a07c379a951a293f68f2dbec15c73c972ca4f82ca22731b6e308dee8b9e6bd6a673ab36dae05f923ecf456b2fd11d3b76ec6043a93b895d3183a1031b6b30bf25f1b28cd6bfadaf06a378be78de6d0bf1491b6930c8d8ccfb60e5c84ec0061adcd13958f99ae90ca6cbb99126eb6a063d8b7bf23ce15406537e936dc40893b64e440653cf6577137a4628d5c7608e159fbcb4e5314b25623089f755e1bbbc84d84f188c1a3a2c0535f9d0d182c1dcd97f6371bb19a62f984d3ca9a3a7a87bfc4c0f0536bc601239e9e956a6ee44a50b26adca3d51511b5ce092d2e1e53aa82c7d600b36b6608e77a54e8a258f507f69e1249f4b0a7e626a0216ecd821c62901df000c1d957c0330748ce0033ab20d36b2606aad0fbf5541a8d2a1b5c10616be24a52c49e7ade751dbb8822925bbf2d22176c38258061b56308997a474d5189db349abb0abfb2949eef6cbb512071b5428c6679eb0b09c8d2918edeff5930e72b584df86144ce29dea58a91cc45328e8322041c680828d2818dbac2a5fec0a964f86800d2898245d4968bff109a6bc1f4dbe68160e6c38c1e8694295497a6cf3434d308d9fecb5fcd9444b15261867cd2a5d6fddc8899760569332d342960e56660f1b4a305b50170fcfb1f29f02c60e200724c8180148828d2498eed287bc7bf2ced1b30d2418d544aaeb8c360ff6114c5b4976a6b7c58611ccc9474fb2f44790627bf4c34762c04611d617f55f8244482e7966f48886fb890ee13bdd4943cc4e34ddd9106c0881bbabbfbe1191265d82dc00046c416e5083e6818d2098e63aa95cbd612a5d361b403005659db2d7c9fb74f20b9358ae759572bcb57e5fa8b226c88f9da817a6203a25699742bc30acfc6d08b16d2a42be0b938f9f9e5d708b16725d9864d6cc77f08bb1b4e7c22422c4a5f0a9e3c2ec965ffd991fd7aae416c638b1b7dfea43547b6c61d6f4ac26499f61599d5a98c2a81276fad3b4309ce8342a74ba3cbaed59984e8689a2f2949ca6c3b230a5e8fb29e89855ca6d2c8c214e3651465f60619e3349da0a2d9ee396571867cea289598b2bcc75722c1fa1ed8349698549fe938452baa2e5569515e64b133a49e3b6b1ff2accb1d2cb9325b7d0a9548561f6d229edd2ca2aa5c2a4c41c153d49d6d9535498c4ccdec9bd1c7362780ab3ee5eb81c744ed2e9670a63679cd2f3d495734e29cc23f4c9bfe7868ed24861cc52e2495e4f9e521a85d1842e5d4b7d92f0c9aa210a93787777cadd4c12be0e852945277d424f0c8d9747d64a056a80c21cd6254977afdbb95d8d4f9853c9af8713cb3f9de809b379f0ff9c64fb09c294408d4e98b26ddaa7f814f428d1c637d4e084c9cdfd64df0e65e9c43661d0cffc74eee7fbf861d7811a9a30c8ac526ae74453b27e9940f5cf25ad327bbbd05003137d4eea6de71e1e5be310b080070a6a5cc22ca7ddae73475dd34a0d0e102016b000f247c18e1d58c312067962f69892ac4a98e47bface5fabfeda2961bccdd5d06946dea427f1999decb8a2a263d49084494decce0b42ac428d487c234c86faa8d6808449dac96b52b6f023ccf62697eca354c850c311e6583f32e2c467dbaa34c2a4a7fc7f3fa4f78958750b3518515ba5ef2cc2e4a77fad3e478a309be852829824889b134f84614709b26cf4eedb3d883056ee903be2d94af810a61925af78a924f89b0c61926d323f497225a98d8530b9a56eaba03c761e11c2d4234b9bf0fb31c4bd419884b06ad1f434498a550d41986d474bddb3ab04b9d20b350261b62c313d492232b70784e1e442bd4929ffc19cffbb6fe477bfc5b5861fcc276fed537eeb8339a8fb3541c7b95739f960909df3954a6ce6e4a43d187fcc3e94b8fc5142d68349101d4cdfa48925a83f0f86b32459e53c4a5d7b840793349d941059cd4bbe9dbc7b30ea5ac92927aa07c3989810f914d4e74fe6c1585ed6ffd92fddcd87876295beac244677309fc9b2646dd2365f3b9842c926c9558a263f5575309fbefc94a5ae7ef4f68114b8410793146579921eb5a054b06c0e959c63a911ab758235b20b24c81844b82107b3eedfdf5bda13f154126ec4016fc0c1bc2676784982cba84adf7883d194c5519743663e99c9811b6e30a57095ef142c3d87db6052da39ada767b354618331a4c7f1144e2f9ed4afc1142e5b9fa4d28ef523351894d03095237a1a4cd9392234c4070de6f592f475a8f80c2649f48dbd12c3ddc26b0663e97cd51e43a90ce692e4fcb1b28f50d793c1249d3cb9c7c3979ea563309ed0a7befcb2cf87530ca614bfd869d9cc4abe30182d8855d2e57b3a540383b1f46e7e56e7cffbd48d2f182d694fddf082490e1bad25965f5f236e74c194b2fa655d365584aa1371830ba69bcd924fb0591137b660102769ad144b78d58984b8a105e3873b5d4f712ba994d8871b5930ca7c5acb9fa4381037b060524aa55dcef2f1491ac148c48d2b98d49859b6cbf69c6d84c50aa6157516f794dca882417f8d786851e94b09550d37a860124275fad50915d72f9a871b5330a5eab0726725465c89871b523029edf92a4f9c521b7e148c21ceb4890f3a7e250e058392c4b4d3f9a44b92de27985abfd7a49649cd927682498915ad4e9f54d7e146134c2add4992c73fa19354c204a38ea5774a9de296b4d87063090695c2d7280ff1c8dac5e086120c57b1e5e40921dbf51e623cc3e046124c1ef483aed692bfdd6f20c1e4d7f757e3b98487d008e070e308a61629ea574b5092db78c30846d121b2be23457e7e2cb8510493f69255673a44ae5b64b841049350929825175b7bacbf37dc188259d7735b8e3d5a41772198b5e2d228d3104f49340c378260dc132babf9997c4a7c1b0c3780602cb1f54de9d13dfa2fcce1ec93d817a61c3abe9e921dbca2a717e68e7bfac48e3a499f102f8c6dbda25c554fb08b7761fea43bdb76324157d4d620a20ba3895689572a69fb996220920bb386ce21c455eec7897061b63541e673a97a18931f68ef16858cd041df4b467741c4164693939a4bf1acaf9208088f1f888520520ba3c9dd5aca927f57bc8c8c51831b246b4110a18549a825c94b84d2552b8dace11d41641606ef8f0fa124a5c4833d109185c15da4c8bf3b3d13cb26446281271defd48f546f0b8f1f880b2c8c3257e972feaf68496af802a009915718ee427b2e1d952c87d6550b1157182c8ac5a524e55171433e7cd4e007900c342b445a6130ad1643873139643b9f81c20a83fed48b63c2f357cdab30483529b77ec4ec8950441586b3f8f25fe22e920ac3ece7edf89f4fe72e2a4c59727e27ad2405d5a791352485c8296c8788298c2589ba5b89df55f847d6ca7484a1012061f81001183ac0d0c1a110298541d7c9ce784fbda1c5420aa3adc8aace7fe7685e7181c828cc496c4beac42c97d2771105ee0885e974dcc2042505853955f7e5ded176f5f013e62a4189792373fce3f535384032b063c71e443c6110522ed44eb6aaefa04e984a36f573a3d29c30e66f8930d32d6a95ee4d984d4d7ec731bf5822c45410d184f192a8ec1a1fe4fb148348260c57a3224f720beaf6cc2f8860c26cdd6667f6c1d3bc7e442e610c71f224ad654990964202114b98f32d6445e8ce39443ab2c6e307421b8854c2582aaba449d1fe81ec8708254c729222a36b2f483b2d193dd07484a18163bf10998441754922f2761c594b388848023dbfd89e932abd236b08021d60e80023b1b445c29c61c25c1e1d2577744898938c594ba3f24798faf458d493b3a7188f88234cc27d496255aefe34a748238c6b618492262813db9608234c56b36e5254ea3f576411c6d0169ee4fdbaaaddc50b228a3067dddcbbcb89979d144407cfc30e2289306e7f2ec964c78aacf0d541041106b792774a0efec94cf46e0e228758114398348434355963e393285208833ab1de345e2fc705a5408410a614bd3f592559d2bde700b9c11119842927e1a24678877cf4c8c08e1d381084d15a3db4e3839ae56b6f1009c4773f27e8d8631b440061ee5425e4d35638f1c191b53b1d6168400f901b9c20227f3057de77fa5c6975e7e07d9815113f14731a1ee48752a236b226d207737c29378b1e4e41840f46dbfd3db172f27c627f05913d984bda8b63a72f1ce001e48388d123a999881e0c9f76f6a49b3d0fc7c97c7b878d99227830e9569d93e2c911b983494cf0fa18276c68554e10b1834934552a5cf70421428805913a184e5a9bdad8131d4cf245c62a295d5e3a670ea691fbb14a36572d8e5d05113998d2a510fd9cc44db6afeb06913898cb942a39e9d00af755183c8c11227030c69aaa92c4adceee0da63a2594982785ca577260e800a30c11371846e834c12d495592b6226d30c7ad0edfd36c3933f9917c70000c1e86106183395ac7084f59d5728d0c0c1d3ac2f8c01344d660ce259ef6b45bbde551113518f7e3c72c89a2d396f84541240d668f279e6c4b3933261444d06030f14e1cdde555a2967a226730e948cbfd93bcd22feb10b080c70344cc6050fa59ffa4541fe47a2d5206833449d0ad2ce522d6152183694d3865da23ac2fb88783320673a812cb4d87c7bf908b88c1a4c3c7bc518f399f2d913018cb949d569cfa94672d41040c06378fcd4bc2e9d25519590b72831ab0059220f285db0103112fe00b44ba605031af2429573429e713174c72977041e99e1cc6f25b30ea8554936d4b6cf7245a30eb993cc253d0ad66255930ef5ee838a3f2fbfd8305539ef4775a21c42edf2b98bc74734d999aa5e45ac1ac6e920c613196539655306d9f3aaf10158ca77b7d72d219da9f82b9fbc354490f4bc128de4125a5568b8241767aac9cba824cb144a060ac90234e923c6d1910790257dd25c6673c14d320e204c3e7f927dd1543e787c7239e41a40966f1935dc177e42441c58309c63a59d49258a2a7a5c720b204e38d1eb197efba3ab8236bc705112598624952d23b91a1fb3f3cecb6209204a309e6275d6aa5243aee428820c1f8e9269af925a1de1521448e608a5f5372fe1caf4f220b848811cc775d52782fe9445a5a04c388bf973427c7d99d050f2244304952f01cfd3f9558d27f07912198e48962d2872ad5eb3922423067fd4ff1e3cad6e99c8804c1f4af2f7ab1f2dd994704080661da74ceb623f3cc1a596bd3118606f617a628aa57f572b72f6e44882f4c622f9f3fd3a43415d31186062ea417464ba25cd98e344994b82a11c20b73d63a1d3b8ab22311b20be349d317a2a61594705a4084e8c2ec6ea125f555275174c07893019842482e0c163f26c9214c8e26687161eed5d02994fa236b69bd08b985395ada4e1f74c86ed3d0426c618a36632acaac1e40c608f2e34706541221b530f75cbe37adb3d3cdf610420b83a7fe5ffed1499a4e1d1e426661d225574a8b77ba2cb621b2305f5a0e76c227b99773426261f42e398db813d6e2f46708818541beac8e5c93d773f22b8c1dbbf24f327d395a5281105718c46b6bf45d89fb17c221a415490b9f93e43217fa0a4258611e9de91d3d59566114ffafdc9ea458eaca1055984b647e89f6d4364a7c88905498840aa253575edfacd4c8da211823f88062210415b643f714463b3909425976ce2974c20003f910631d11620ac3aefb950ea6d385f9b81426f115fc738dcc0f21a43007ad4e49ac6ef7399511326e30461919ad81905198d3eee2532825264b92aa284c496b975229091923a3cc50183f53c4548bca497f050aa3da760e953ba87426e913a6309e3aff8b92d28cd813a6b9b0ffd1fe72facd9d30275592486b4f6267be4c4e1884ce21d772d5849b9819b20993645af45de5b0d4b035618ecf92534e213545949930accf273927f15cc9337c4308262ee412076b6f7515a91f1f22d001868e30aa10620953ee8f7982d9083d1b932188904a18f76e4f0a2ae78a4b77ec00430825b291bbb3380db3d8124226610e93cc8437255cb660626908918429595af5a06d374448244cb96327c6cb56a54a8284295cd6763f97bbf49ec4471847a80aef9de504394b078f7c9ce521c411468d33512eeba411060f9d5a424b2ff9ab5a08618429dec922e345b784911761b8e49b7e2abb8b97bc9621441126ddecbfd70e3a9f5425c2f81b6e4accc4e64b141126e9234eef7a8878091dc260e2b7eda5c8d710b883430a61ca26a99c925b3ea8feaa21843025f7bb1cb75416932ccb42c820304410069ddd792efc68287503610c51a9dbd6f1a1040b200c4a98ad74efba1542fe605c4b297fedb64615392661aeac5f57324e49e2491226dbf368a7a4124df991309a78c9b553699d542724ccb13c4bbd9be8d9f9230c7367929ceae8bc26e80893e5ddbf60fa2db6748d30452bbf70d729c7df8d8b9083117927b192243f8b4849f2d38afbfa8616a91a7228c2144727b123e6e184957324c2a47e96945e1263f6d376861c88308a30eff6245752973e8461438956fac6a396a78630b8eadbc54b21234e0a61b2647ad4db2eee091e21dcf9bff44a6258f2b5f420c7200c26872b495f334170207200c276648e3f600e3f98c43b954b9c5f7d287ddf85881b37e9c9400974d45922071f0ce23f7a21a4ef8e05f770831c7a30995bb97c8bc2c8c30e72e001b9a173e7d3dfe5e6650858c003e5b8c365e1dd4354d36abc5fcc538ea7ab3701dac1a4ffe27dd14e9807cb0739ea60924d2595a4fc1fd3ce1f591ee4a083e9450921af34078338b9ad839aa42a85c6e891832c23871ccc1e439560a2ddd489760edec71b5621471c0cb2254cce1fe5467acb52c8010753bcb151163fe7917d1728e47883614ecdc28a14cbfda41b8ca133f4eaeb32b2d60683f873774ffa20417a00c9013aaff7841c6c306d776ca8bbe4c94b6e0da61153aaf6e28c29f5dc3121871accc1b2083359c42b28531acc9e4478f8d221dceaae53420e341843c8f0d610695250692721c7198c9d930efafd4add2f480c317ad0c00ca6562d295f6e5668d3cb601ea52795944b2783c17f7b4fcfe5acdd298c841c633089a6275dac4f8eacf140e78188c124eaef2af6e3a552c6d3a0182147188c6352944fe1dd313a82c124f849a52b855dbf184a22e4f88251d53b5e27211ebb3b32bc60fc13f34b8ce54f79a45d3099b8b9cb62652af319590b42460f313e488f112e98629fb2acb81a59fd8690630b264b0da56546c9298b164c677127a9353d2242250be62d49343d7f6641ca2e841c58308f6eaee838b677520000428e2b1894c5f2b06157e7256761e4b08279b3e4a072a27e3b997e04c914e4a882792b463b4dac13d20603c8e5a0824958b94f2dbe7767d9f70012240239a660da4b9d66fee42ba5444dc82105538ac8eff5fca9418e2860dafd82ebbc4923cf1c50309ecc2fa1829fb228678e27c02087138c7aad765259dd9724d84506399a90399890399660926eb1e37a560190218712ccd982c9bd9f29c14a7c8b227224c160fa154d0abb648279236b3990605062265af796fa397764adde90e308e674e2f6965d6531e194c308b683b790a308e69455ca4e0a7ac24b1011cc61bdb2945cf221984a9ffc54417542307849eadaeaa431d12b47106c470e20d88e29e0f8c5e1f0855964fb241d67acb5ef5e98a2887cce9fefa57b7961d290f5b9d4ebaf78128e5d9893b62ab14d8aeac294636f62a8905fa1bd5c9852ca5a1fa2449564172e0cf7c992ae9cb52ee0b885b12b7aecdfdb5cc0610bb3bd7c345da5fb6d7961c3510bc35d67f79ffad8417a46d6f48a05d1c1232d4c72c5c56b2b497ff764fc10c3cbf500c72c4c3b962b277ffb47103c1ae09085416d9658b19292d764a810386261fc2873c2f8beb03099f87c492cd9f990f1f8c1658de315e6b4a154ce90d5d3370870b8e2979326df2549c503385a611aeb58e24faee5144e64854989ecf47abb796a269ac1b10a22e05085c15ec468a9b74be96338526110fdf9cad27470a0c2e051c7835a10f23aa7531854501f53df6f8289de1426e1f49bcbe550a733540ae3a54bc1a3c50a553a278569472fd6ac75cf29e151984bc7acabcab24e4aa230b627957b95dd5018445f921767495018d4f77eb2aa93f498d0c85a0112640c1c9f305c095de9cfb2ed69e90953ee14b4977a92be63d809e385975bf56052d6ce09c3989d0595de3fc7689b30a955e8b9f0ccac94d284a9a3a9fa244b4e260cb729e2eb2aad5592c7844989a1da4da4680b42c44b18342df65f9b8af8fb96308b90352d3e5a472869254cba4f08f1414ee8543e254c6b7a5775b64cd879276192f3d37c7a8f1da27b49186e3d96a4761d6b8a84d1434eaadf8f9030d8ff7b9fce3fc2eca992f0ba1139c2202b8e65b3e065234c525412e476329537438c30892f7582879d0f9f4e5c8449d2fcca0ff61a1fbb22cc29599dcef8927f5e4f84f947cdc51abdb1110d110653f1d4c9b9d45cb0f8214ce1c496e0a32efee40c6150a293aca1534c185952089352279a3067a2ca4b843089fe97b482e83a0853c89a6abdc91184f1b404bbabd41b4a92f281231066b5249b3e21b445e91c401854d21546bc6cf907a345d910954b51166a3f183fa558ba9e5216b9a6f6c154a28af6fb68f3c154b9f288302baf94927b3088f20a4afc9ef56092ca4c8efd3cfb4bc27930a8ab4bdb62423e4cc454d68791bf23dfc194d3845affc9b5a7640773496adc44888d9ab60ea692ada427cf15d2c1a0e40f3aefc436cb973207934e53fd3c7aab4437a91c4ce623abf297bc74d21287e44925e7d45f0207931c5ae2e9d115a652de60be1833eff78b4f517583f93b9c741525850db5c124edad859256549815b1c1249bb05cbe65b2baca3598e41c9b65bda1ee564d35984d3c1de73ddb96cac13498cd47858a361e66d6f1c08106939295ebdaa5b746ed67f82495733398b34fca6f9d351ded32984e8b32492e25a8b718a980830c6613da54ca717a2f2d63309a25e9313ddd4927280673c91dc2554ec7385d18cca26455bcc90506931464253d4aa56cdd292d041c5f3009b72449c2eeb8a98eff10c3043b7690e12160010f16e0f042e2e882f162c96d278afcac5ec1c105a37b49c279523d901f3c4e0238b6606ad1a5e71fb267c1d282491e255df77c099e3a94028e2c1864fbe5284bde6a2333137060c1fc9e5b3e5d8e9b7716b780e30ae63d4f6b2655924b3099d1020e2b98c6249346e6474f4e1d1c5530fc9ba43b4b4a97ec526af041c828e31207154cba4b8cf8ba6869f69f82d14c9f9c2e48932e9fb014cc313fd5a518c292785a144cf573a2675bea5338c101051c4f30fa8997a4594f8a030e2798437fb9878f62e25788a30946936b629e94332698b24a94a815765a4bb80f816309c6ed94520952fece724a09a6f82d7df32786c0910453c5cfdd924d5dae510f0207120c7349a71cc1242b6e683b612d67ff7118c1589265f597604a3544388a60cadae9e9b21d2298e405a5d3567ccaa1f9104cca7475d99fd6f1fc8460fca04f9c12fd71c2c5c43828021c4130cc97b0e2a5b7e265fb0f388070bf30e999885151a265377c61b21c2642e9f0cffef11bbd3068c89e244d09fef1a46ef002cf2c418589a193cde50937766150e29e4a9d436cc6a5236bec3dc420c36ee8c2dcc1537cacb8173772e1462e4c6af66424439170031778e3167ac316c6b7cbdda7aaee3edca88541c5f91116a49df0b86961aeb212a653342f490791e1186eccc228aac7d25e5b4aebcfc81aeac1a38c84c701f911c4043b76ec10e3f018838c0b04d181c1c20d59c4c220da749a3ed3a5f2f4b030dfa5ecb78e36e9e5e41526ed64e6f2265adde4b8c2eca27e2c547dcbc5d28a1bac60ecbe2e5acb792553b76eacc230aa2df25e7d3c4caa0a73c5fd5b7e79cb8ab60837525143b8810a83cc59ec60ca74f44f374e61b2f1134b3095c4e335b06307196e77c314d8e84629ccf972cc2b9dfc0faa2685394539134fab83acbcdd1885490e3142279547fdf2130216f068c10d5198b43fbb4dfc9c651385c2302aa58c5f5d6e80c2246d56eb93c93655ca1b9f3086acb01a8b7a56faf4c45fa2c627615f42d310b080c78f1b9d30a91497deb99f4fba3c270c2e27769f1cf7ed1f6dc228e29e3f4c652dd9234d18648927eea5d0cf4fa14c98d48716934e2c0f6a1e4c18fc53aa1359aadd83fa120661f7e12cc592f29ab0250cfa7323bf4a5e4d732b618acb1b5da64b1653522861d895137af95326615013d6bc4e525d76154998d3ee99f84165155566244c594aa824a73036964b4818d746e5b56fcdfb183ec2245ac9d0d175471884f6e79eec27af74230cca83499f849af7b418230c5a4a5e33a556bf841661aaf130b735a7797214613439c9733947936172811b8930556c9d9d0b2f9d5f42842998f8f026f6b5e41c1dc2181f3f6408739d285ec2e82ab3de0b615a3d7dba3ea26d4408831063c2aaf2e2a8f4204c695e94896c3fe9d74410a6b0d749c7e45c92e407c2f07392feeaf7b0e702c2d4f969ab15e492d6ce1fcc295ae3639efaca6b3f98937579679fb459a53e9892124c4e352df3c1943e258f0ea82431461259240c8702815010c4303cbc4d00431308001834248d85a2f1803c12d67d1400044d34224232361c2a2016161a8b84236128180c0643a2302014060602a1501048424a9c651d2b37f54dd34deaa450b5a00c0bf03052fb1efb559596adf6285c3d511d63581afbf38214e5ea8fcd695c0c8c1919957f55b1ba31e0c130acda6159615167cc6d6808925c991760852dde1ef9159ca604e7b08c9be38ba3e189315b6dfcf826f419cdc34a47d4798ef8fd229ffb903abf2b775a37f855291f073e26ba0f4b780ef18345027926c8c3997fb412882c6c9d8848af29e8038c549d984e742e364366afb037aaa2c8019c33bc0d0e49e9904d774ffc151a176c3cf9c97e0e30f14be4458fea5e4427a7e3933b0b8664e3cdb3db4acca4d7a75b2992565a8d0abb11acb8ff6ef5fbd939dc2dd237de8ef029e9c2acb947200d876b66ee2cfe9f284121c4ea4fb6c7f10426039e442239b320804e3c156cb80e8c340503a7b95b5dafce54eb6ce9ea948a5e139732dd999a37664f3419af105aab0f170ee1075587fc8d5796311ff33ea6fd092aed6dba0604f3b641da9fd640e4c37591b344c7786c2914a803dbc7d69c2761d4e497abe3d679acf6d691d3d99785a13709fdbbd898a221b74b3c6387253152068fbf6cf99bd8dd50cb5b1e544450a64b5986017a54e9b736dcd1ec3dd5848405d4adbc8efb11a88c8ae76a91808178f171768ea7829f9ed879ea434c8eb087281d161d1e8dcf597d3d02cc3942ac88ca8a9c45e75c4066ba2ce89e7f50b380c184473c26a7c2345f786c723cae5bf6c0dc567d167c0863c444b04276080a8e16970e8063368f1496e0763ed17840ad4d50212a53c7e8877ef18bad1f68b39b83580c84311123af76847c7689286b42d1b40f61a928910d91503804d881ef84427fa897413bb5e6825f439b4a8869bc92658d7046227145062fe6f38783b46cbe3a0dc6e4a9fd74169fc4168bef411c596b0ca65f224496b56884770e43f8c07540ab44881c416a6349d55c5e148ec16dfa1411df02681433efedea3611832519aed26be51f73630f850d0065bab4085c4c5698c0ec49232de3a375e8f007896f80fae5bdb3e57acae88e153b32a6cb8c12d0629600cc6b20ca9311ae18af04dbe28ca5a5d441f017b869e008518b4bc45c27bb10e3881448db81646e820c9927014a8f6fd396cdd2579fa64009a02db7e874e71174ef9fb2ddb084a3ddd46fe658d761431dc7c6c7fd9aa3949a23561d65dc15b0a3707d4d519ed7c4071b9971c3301335a8434662a2a1aff3732915c0c5609c01c62d83311ed7ca0365e0b08f734f6792f9b70199ec1be6f9973058cec5d0d2b102c5f21dd56dcc64c6dc125c089f57bcd6904c5e5f95f8c7343b9c4dad3403649c1cfec6c76b3f45faa2e30b0a320b012bd32696b2734cb1daf8e3672d05d346d5a6c195e9bc6236349f6cef8f6c00eec30b13ac0d965b531a15ffc878435a10cd4a8ac2e0cf9c2939a79a0d6dbea3e59678a618f4808da754229e6834325ac72ebc94f83394c2c67c27cd170510694a9ef23b3ce49eaca4ad32203386c821a857a9d59dfe650dd633632950c04215c2176354a3b15cc0643c39a34b612dd1a697b32818da143c09752be7d7c8265fac0918caa59eaa0bf79d73ff93b564fa0fe9917125e9079283562abfb234dcf6fe54f97750326698113968144b7a6db7cf4fd857376dc9e57d97f744b83c2f882baa041df49501f66ea89466c6a3164ee44b02fda89283c3f5145fddf794de34409e1fd584e6884dc0d147193c8f1497dadc1534412967560a46fbe510b22063fc7b99ca5ed7ad69f05b05e4aae222a000eb2d2f514c9b67af6409a13a7e6594d8fe1165105e7df0a4e365d0798c68343cff44774746c8d09d8a91c24467ae63c05566e14a51cafbc247abdabf14804e5ed86713a0ea3670996e5ce1f3eb06ae2a9a5142eb85aca84ae892802f91562ec1edf091d96f4237f8c4139eed44914f531b89308e570631b4003b95a9303f13fb2d093ce4eb620c535ad7d67787ae76136e3cb871b41f5e9c38a98c391be6e1b87562f46688ec8406c792d9e0dd21b603925ed40a01b638e898892d7a26f0ea40f093ef67fae5e095ac32766e2d5e47da6d3b8e78cf3f3ac8ca7c296376ae89f00ecb9a0291ec39dc79df0eb97a8b329f683565c88fbfa40f1859827cebae7794da2787bd0d852d4ffe199a251ebfcb97acb323d54ad235aaa710e52d8501ebe19218984799ed2077001fa44878cd26eb5edf43f52abfe148063139631322d6d2aed57ee29def5caa2b2ec79d5d57a6b89f2b066fb9e915d099ce37ef4799dd1123276f7e759e58e9c792db5873e490dd0ab98cc069351af260805ba1e976dbedebc7fc159935a3ee5cf18ad88eb8e4775b8ce230d32955c46f4e0bd09e47765a5871cddb5704f75b6c1018032542429ff4a6c4bcaedc5863cb72aaffc798ef7d669e7f3a40b6f39388bba77b2fc84fed636d7ae2dec71d1e99343c30e2132863858060458b2f220a4cb8eab549d8476c7a62ad9938ef4dc63b39a6910730b5c0bf6999904d6246c356d57543f671c982d203f420ef0a61c3da70f001932a0008b93931544d1036f2d913768361a90707396222b98b9ce6cae36c24a50c0d0d532125b98a126e43a9bbbdb585a638a9b4bafe8cbcbfaa2cf788db1a19b64ef9862e1495bbe5ecc44b30a74b1a1987d71a4e14a560c81c314ba1d79ca9e962dd3b5d80664128e8c59214e459057483d82e3e2f022749f6cd329bc6661f5423bafeab7bb1978b3757a9fe7c2a8e1d9c51cc6f2801191a49897360e612c8b6deeea4616f3bd1473b64a652e55a78823b4f0e581ff56dd1ee448bff0b50b8bd5c6d0bcb62950a0a2ba85583ccc6efcec75b70c73bb653be66fe0e85cc1a4fe1b4fa4a890ccab3ceeec000124cf7b7724b3629f76845e1180acdd608cecce0e1f2435e1d9492e9d25620d1c801619ed63d54a2279fa31f1b55c38da68145e0475a42591d95c1436f419d5022cc769b8cca27958760b6709817b629c410852c65f9a5e4acad0367166ce15315cc7a917f781b6dc637bcb2b566e08c93cac26a838d1d735f882c6405491ddf2f8e272399690548fc9c7c90a0adb29d14969cc7930eb8e631a94473739e773c8693ac873a20e594294d8aa14ebf0ecaa5a8581d292f51fd83c355c92021d3820de11b40c83ec41ff5f225ff3c2c77dfa3711fe417357f2caec1a34f6d7e1df9eae7825deef2a1a93dc961314a049619d88ebfc955ceeeeec0d3b53063f0da604890c2a166357c2ae70d62b50810187a8d7ea698b43f91c53bce3882201adf6ac817d486c7547d916f5f438760d2c9f39a9e2644e161731f3b382452ea2c22a43cf8c9b7f0416eb8991abdbb697bc01269c2650263a14f7ddb8caf9769ac6c831d57e40533071d1b31346d6338329d84938772edcfbacb33900e763e8c57cbc9ac04c9ef0b78f7b3d89e8de4185f2ccb0e961e92064b46a151c42beb86bf855aae6f43bb19b5774dab3303a23a00b9b831046eb590ce1d0b713310cababdd8e6f0913a85bcec469ebe247beb0360000954023378045b403eb881824b5e13d01048078c800b00a3808b2c9b2f1ccd2a79543c60e69c20441d7c55db4ff795e82668085370344a63f35230c7c9e245dac0345734d25acd343db5486db826129a19211e60c429b90a711a73816c218311436f83b9069d009ca7b0e6c53bc1a17610892852c12388141a913788fe708ac2cc64546696c039b461e72b9325d830ac2bc0138f3d10d44b1a52e6779cd69ea2d46a134a09dd3fa811fba514aea11498fe93ac7eaddb96e410649e009d711fe6e955a7b5d1a1ab94b6486d7a403e3702b4974aaca176410debfa7b03e98303bb8e3754a6faa493a30d194edfa0ee4c66000e4a4a74f3534ed0454f05d73036355f52651c5b5528f851a3a8117a06f68da475d4d6f88677a6e7fae74dd346a3ec024612c5b98e067989908f4b50730c72dba4b5cd101c93121ffe38a2c0e38f2188b812cd7a5877a29e8109c366be41427a1e9a7a5b4c8e7a8dea653a71c3cc9203147c5208c85e4c8abbfa16d91e03ae84d4b4047227447d1146ce4b34f035e2e1573d505f97ead1e94329f84cc2337520cbc67fdb40d58f820d5c1f98ab19e4c623c775c8e38fa65b48566f1b9bfa067c9ff8f4bbd55f520c242a68e539b8e0f4409c154fa34ab10bcbd4018a2245524a9e9a59dd2a03cc85ba08f74ddc04d9606a1ef8cf241e199ea66b85aac105875c3e8db721a5d7b143da25dc676d4266ad82966f88779a180ceac9476090e9ad8166e809262d9970c89ea24a2df8a548fe52c9e490f3e828da2709778a11fd54f218b61116283091d204f34c34b674309e5adeb5f24c12fdb4c106cee08032a415deb8459b4c37cc46d00ab6c6ca4512d5587aa12f43e861805558faaa1bfdab6ad20015ccd86a1bb6cb071dbf42f31c1d6ca1aacfc4b8740229ce2ad27adac2dec8cf8b94289128ec6b81fcee04b721a40e5f078c02f6000dba2d013e2960f3304a70292a5a6092d85a0a88e9fb3ca765fa43f4a546cd0e3e08b9982677b2ed417b8463f3ac5d1a434723d31dea8ffac439f5a8ea4f80ba60cd5890ec184f11164bd905723d903cb74db8888fd87dff0c29ec689c5468e9714724460a2a3a5e39d86daeba570edf177a633f7e837049e0d2c2800eb1c16fa9c16cbd93bc440c091a1cbdee1ffa78abd131a887abe572268ce42220a1202c894666cc9cd1750fa0190eb398fb6af539892680fd2a064b3c9fc91d4964c0201503113b0cd4ac0e660a69d501d7df9647671ce58a153805dee69585ade70b0e6866bc415417046a1c71e9a44ce29ff5666a607852a608a3b5f273f77d627193cc52f249586283f37b0955dbd8f890245732a789947f26ab61557d8726ae894261a5006a1cc1ae578c428f1de934aa2b0f0f41f51a17405162e3a97ba5b2c80d7946679289ecb6737a39e2f8d6877c9405297a298ffec0f7e48983328671623af28bde49cc1a33ff34cb641139ca8bb7a89485a3e196070a298f5052452d14200e1fe45e263f419041d3aa92e44d554d4041985452711963fc0399ecb228e3a567c6f569a882e3526f8fd1cd6b5bf4e98c67da4ef6665683df12fcbd7245925639552376cb5e42a66f79cf5595d2ed86964f187bc48a2914b7de83f18e545acf6176438ce5101ad28df5dae01b83c2586784bbc1b5b89a609a31f1da089f10a68294a607233cd508416a6d27974d6a7a28a8f2964a587d53e1fad661f5d37341c40793bfe0c06af12a12a945f97cecb5e9d96ae008f6dafa30dccdb254a8a15848017aa31436f28f5385a50893c6da1acc931863128a9c2b2691969350638b349c19ffa87336e4a6cb7042bd1ef3b1ee12f75c9a59bea5a5bea8a6802eeb7e4ec2adf7311ba68c93f0a35473e054ed048a2f4aecabe158e2ea8f73d87828050b3b5ce0cf50e848ff9441df283a038ba41e5c3b03bcbb0ad54abf8e4b29b6159c3727d81d1c72ec5462e70d4471632a8b5a128f75752b8b446e8082282270f44090599de8c283b28ad6b3541f50685998f97980687640ec2c0c2c4d71e316475dce212f9e14433dbaea30036bc95c8ca542ba4be471321802c4480842fcf404ee9058a80b99332d98cfa8e7ae549cc57a3607d84217e68ef57581c54bc98d7b0f5360d6eb6dc7743838f245519551d1e366a352307f4b0a6e00a27c347838ec4e7bad2f3663ad6c01bd7925bfbb74290bf1770b1326fb276a9fb0c3656e5237cd50d341214089de0a44d8efb177e0cd4c2413acff8e0449d4b791b31987c0d1a8665f478c305ddb46dbd5c19fd8703e83f7aaef57de25e119d0e4a5d8aea2780388476a2fe4a0794b1b5f5efcd2e47fb9275d1131c39057647bf19623ce4e1317a5cfb3eb65e2f0459e6dde0a7dbfde40a8a3dcd2358398692208824f511bed6c0dd154e71f885debdd6fab57380b4ea94d625706cef6b8d6a224d5046a9b2b1aa4b166e0dfb6c4c20bdc5642a29f5866c1708322b2b920ba5861701e1b7d98c022db391d1929651ae2bd2a3d0f07913389c96c37e6b709cb20ea932ae4e91b97ee170cce85d7abf96908a730eb0b9a60f29af0ff9086db5440fd325ae4662c68fc03bf1ed68da8574cfa019d731219824c8d122c048491563840224a05e40c0d35d46b0c936d97bc9cff35df1347ddc6e9e307adce5f09dcf713b44f4e90cde2f08378308c5200ac582d3dafd8c2528df71446864390d8081ca558eaf908ef7339fd1c8d1b0b05bf8682eb6e5b7c6550a4a2e26211f455deacc6049cf6453ff08cbbb2543a12f8b4bff0518d0b28b7813fa1df88560f282a2271f8053a5175b9569a02417b81ef4ff6d1dead167fe53542414ada959f0fd7535aeac7d7198ef905b6a77fd1af9668b0639ea80c501b2b075b49084185eef17bf68c3c50bc41a45aa52895e3bb874acb32e63efd0f19e6a233a797939a33c10a234ec4ed1607133ab2cb60faf09b8cc09d917d405e3f28e87e4f97195d37ae4ef17eec46801fdfe39ed0388501fbc6d8de25a9faf079f28432f9ba6ca231c57df7e98ec5157c42ad417af1a35ef898812202e65056b5ccc1e11d34102fa0f7eceed824b6c94b9f6f66f215f2264a06fa6b29442fd08d8fc6da3c00a86ad4e9b5578f5202440bf0ffd56f535ae4a5b6d9434ae77fc95cd7abfdf87fdd385409877cf3ce262bb28878378ffe39a8a1ecf46c1810aed970a6228f257d21febd1b8f33a0257810cc2bc91f8f6f8b180acb2b273562091af4080141b6aa9ce8aa78bc20154275eabf1a4ccfe9229d4475d15e4c6f7843a4b34de71b3ffb3650b4cdba8d70766b73baa66343e761afdd5e6f6b987546ca98e6ddb5503c28e38eafe062a7ffe2f5005c8caa9d05f608e8f56aa84ba2a628ef7ab3afdfc5e354f14afa4383315dd20b514792aaa28a387ecc40bc3d5d898c0f0d465a0f7ba9f08d68d2ad98a954c45a0cda917440e2995fc878f1d0ec5477d5988a893cc929de22de5a2658630ec0317bd2ca8e40aa3c408419e7fe17fd102d236ee209c9f2060bce07905170a0212af1e31b26e8976a47be73c38c7fd71a44448d9136eea834abcc5d9eee962f2f4cf1524f447665851282e1d670c9088891faa3fa33e16dd2adc264203672dcd93c1880f3b98ab0ded9f464c33208e71160d52034dcba15aa7bc96797e4b26944cda8a70f6342cd9e96306244532c054947c718d8b9bdcb9e4ee56536f575b326441c92c6dc51d6374401fd585a749b0c685ba161f0cd30d0abfad8e4b186a80878a18ff9957d43493080f33fe2c80795ca3e84460bf52ed8b03a1916bb59d3823ec39264b0b57383a3a762d6d56ee237a4d16c01d7d4b8cf241b0cedf449061b011710decb5c0e854323c299e1160e7629819852e69d694ca8bdde1b270f32466fbec1809ca7b402c14b1ee915420ef0086a70d86d79e153ed641561645674ed0a523ddd7597d1b4605ce85db198c5c5d7a919b7b44929630619bb2a948946adcc0aa2ed48fef18ec82e070a507302301ce23b2ee42ec6ac22424761d80ca3803c7c2ce37a8a6379f94938e48920edb5fb26d393b7066e0f9a45bc89ed0057e6ecd7af3acb99aa53956949cc693fe8164f000b022d811d7929e66750230de804a1f6ccd3dd49c40ca185d560959b9ee1498c9be089f785b9fac106bf3fcab3115b7ba8e5b4018334dd7da83ec44056d88c47165c5542d7dcb6017051b47879cccac1d9a335a720e66d341a54f41d7866860703c2941f3c4c9ae54952f5341ff1b348be2b99628cd304a5d4e9c478c79f3bf680885cb97a43eb351f89ca94e7751b841e21e2cf83228847b13f5686b783fe3e91f117584b1bd18864daadd45a2222081b631cc0445f3715f6d0ac0b9e5c7367b5ac1c7b651f0857276659493915ed1a40a48037c604fa3009010155856f70e26e1e23cde6becfae972e174ab2280e893ad1ed138b14d55133178286aa3c135ac83e813d90897f3323d5626931c74fa4f134a025622e1a4b43c7a6da80c6a76ed7bff42c088501306ff430285d6aa397e751f1d86e6d8face7b3c1c5ef693d1188384b60f0d11ec1a79182edaa89b0c3b021a5807665ef9a538a2ce6f5814e85054dbb7a8cce08c281bf3d045d7d5c0e7b4fd001ecc6cb09c87861371412637e5f5048a0fae388ecc28d7ef8489ce9ab01289eff668dd2c761bc8adb1043a96ed34a616ab0bc2e027266198ea426d4335222c8e517cf722c362568010c5e4a40bd9d80fd72ccd81728ef24692501e30939f0978c4309aac894ed6551a53693e47ad088ef3a0f866000b6a4bf8452053307697bb25a12338c133ae9b58540b18f7603acc635d1c4d5c0a6c0f30712ce24a544ce18aef353437aa8c7659c5e65ebd41bf607e51410af6da2b8e2101d9ec413eb9b66228b7a5ec662b5d2c275b55657bb6fd4b28ab7db58902d2c40cfcaf1fcd844b64427173ad04481e922fa4ce016b0d20eb1e4d2b084a73bf82f6a276264489451ba0bfe0d4eaa01dc52609b42338352e648699568b05109b82c11520f1496c583d74b23bfd418a16ff61bc6b36479d6c73890bfb19d528661007aa2ea5671143a9ba8a18e02e6383dcaf69dedc969fbe0f2e26384eb62fbe194a6e93a8ae2eb1dc5d1a8f2720e3592d3a6ccaea36c02525d57df247f18fa32f2782f036290a376997fbe3231b8987fd37fc9a05d3fd83548750c58ca20d94c18abe5d3f634329fc9f6ea9643c602a0931c0b25815e43ad71c3e4608e58d4e983808ef68a1c58b8e8148cabfd42424eac6a0d6c0a3fc0b9abd1f76dca5ddb5ba3c1a82d63eca12dbd03bc16a6e126f238409e7ed48c5575fe40b39126b742512ed52be5eaddad635d1b0b683b18f3324ba73f890f62b5361c5473dcac322cc20c52f215ad68f423f7b312e7d0301d57e4c885d0ee01d18919b40e13bc8b44df136294b4ad3e9998dc124a530dca78cb49485296910e48759adaa27cbc2c9a930c7e08fe0ddca1911ebe1a812219707c10f6ef1c693d0eb52b1aba71a3363b066e784ada141ac099c64a9fc2908bd0d51a56fbc36ca5cd9f568e40adc400f3372697c250a2dc993d4d24dec292d50cf22da2732e6b83e913f5f3fde07b0c98b9d5f5884ef619d2217f08660d1fdc920cd1e41fe4189fca4c1376f4a5f61a19d8e3aeb26152cf97485175d0f8d56898c501899d897b0f1d9a9c9c30e524000afb2cf0630f8c9190bd7563b72eeef77d07f2bc3f98f634d48202c4699c5cd4c663432dd9b48261e3329a9b2d150590d0a2442f3d10c995a3d79bd8546ae942c8256737a3f7d6bc51fff626ee1180fd7501d40e4e9ea06915d6d58d7f3d11202f18e5a9960b421a2605104a3c6d5003762431966da0bc46721df2dfb358b182283d62a3b76f5e08311c94b4e2b7b622889f4b8cacfcb33985a3430e51c583816f153bc94dde87a73ed57fc0489c13a179a2a0915853738aaabc708a063b1c84d2271c1df4b3c44687a46cfc865a58e583b877eed865487407ced6ea9290080b12380c0ed9b5aa559e147a20434098c87367db1b182942d3814c4d6cccf1da29793e000ce23308df5cd0af0c19c006cab661eba74ea553a0061358952463ae77e434681fb138a61148e00fa249960d83e72fcb38a761f2443850321e13a339ec4f21984584729f37282d459e649e0b04b362a20411f740587ab5a8422a28ff3187cc46e7556a85522769517b9f794fc8fdfe9abd313ff83ef0877a71dc9357c3b6ee8a58c453dffc61efdf13ddcd26fc7e0558875a9a394830810e2d45c378baf4c3a931660fd837a46a448191d9efdb6f3f2399c9315016fb649ed319b967e3654b84331e2b38a4cd6492e446f230cb6137157a380f05a1bbe0df9253c3b112870e53688738b8e018a0fa7ff74b408f8732be68866f54ca2d4217be3b85523dc4d44c65ff4a86f23a91f725082749a12b12492b2a08bdd6b02c3053a41e832c20084c57145dba53b984bae583133e41b566acef2dc66acedba98170a80ebb131e00d726a0bff1b8425c431bc2bb94da2aa85daf376c1836978144506016afc9769b5148f47040e3bdf58410855cd126619f84aa3e89043e8f5ae43000aec4e532b87ab7210f0e67b9c6f435d05e4e2cf2915dc90fb5782f396b0093bf071aa710a630e6dc10156e27a796fc4bfe664cb855c07637eb4ab0e90f0f621a2ad2bc9c1fd7db7f5cccc74c3375dd91db07d561c2efadb2ea6ac5112e5e07647ffca89622b8a3dbc33358d9f057dee321a006e40a28c203d81c39f66c1d3e9c60ef7db45b401cb833402f6eadab135d7d696d4ad83dae50bfe186e64f3f1745e2f3e7c798d468488caff898b6f4d38df831403296021055ae21fe96c37af565dc6259d111f522e433dd2ef0f8f348d6cc21eda1c87728e28622b43bf5ce941114dec11548f1025bb0b80c988c6a7c312a50805488b087d6b4d328c59fa53a9f278c24e9d40d83fa6625de706466304296b55dffc13087401a59fbd12c1bc1d288c123358e9cc53305ab17dee9614983a2fc8a74459f612778664220f42d51a0d25091c977072c36a7a1ba0199ba3369892826db406e6b36aee06040e4658884a3f8607433f6a2692ed3e71849e3dababf6bff382b73794d03fa3ffa58ebd9a09ea53cc707e334ef230ad53ca86f2fa01b1042080a920c9e206c6360df25b723459444768be75d1fc082dc544218a9b553e9c8659a5d5974b60766738f4e62734733a0063569ef4f2c323b65b7ab2b236a6a54246bfff1fe7018fb57284a2828dc080f182a2e38a1bd7f17883af86e982ec387e77464c9e6d406d1dca684f26de51b9461a031e82b3c72eb604f0af6dd0dcf48411d1940f2718509229793d0fbe8863db431dc0f510f1aa3ef6d1fc09626df74c0bf310f1c62dcd0052029c624ce4b9f3957e565f2f8989c13a493de10282e6408ba7d14073a94e9ec29ce59db25f651a4deb5a325c8b55bbb10e117077ba1150899d75cf4ccd2b8795ba2c27d8653732006be59e1033ea202242b2ce564d3eefe46788bf0a5436565351daa8961a0934cbc56e7d76844b0fbea480e68dee6a1e69efe5c93564b254b1260f73229e5fd1f73bdf85572042cb6f51184580387224fe803c92c35dbe862b70e7bbecb4a6b5c76a76c9352e152578f8eac0be47141cc38e2cd06fc64a1ca5958223a3afd0039c1edbea70635820ee61b9dc0a51a9475d7e55e856da2cf3f11d09fd0313e0b0bf079d4d330d9c97d14807ed62ad83fad235717f9470962f294aed5715838e902a1dcd7190f29b30ea08b571f9472ac2dc5e1e6ad02679082fc338ff4d27ae0c146d1ee39f06100e9bf831e3d6b30c95145541b9e9d9508a458fa171d0654d3389561869220cbeb6498e698ada6c13d476cfea39d1358540512e612dcb83aaf90aa15938954b7fb5f0fe7fec5785ed2e66d554a963aec289f254d5b816a39f934c1709a006705b55f20e16bdc2b2be8b0e1ba60420a1a08af6836885212cf7733fdb1b0295c5c08fd4d124b9902a36f61e52b3317eb2d13ee89030e4951738fb1b9daf8811fb855c1ba521912d73e5578dda1f7cd5d09a085dfbb7a4fa1aa71d795f939ce68839e54853ab2ec45cf9359eff42ddbbd1a15ec279e45c0ac5ed28c9ffbaf1e884da912f6726ee10e991b74a64801be5ab123ac2333676bf2c140de493d534966fd7105b5583a1ba54c6cf7a169962408d06d45df04523100481df62c7b949f4b4762bc46cab3795efd0313be62c9726a5a98a1179a1a8d559e15ed9c951ba0b188e2c0bea8dfd853ee73607649a034b1d2a8b0ad5d51abf1a81f94280cd9f00f622cb18200ac963cd5a2df4d80800fe3c5ad2c3df04307defc4f7b996638b68bb7f428a4d3abe00cb0d073719c423c81a356151ebeb45cf37b740d4824fc30e5ceaecb99000134aac20439a532a76a5dca79c88e10e1b20a813157de8f021850f2eae95628495197f298b75dfcedfd312824807decc462530017f675440652884c1045855c3bf092a15511b55a34ddd7d02988fa4635bc5727ce825dae13a2876322c08d607410bc82849536f03e05ea33b89e041add0e08cc741ddd7fcefc70ac6a4419fb2ac0b784a4f9c90de17f4b6174b554f29619b047c7fc30b6a3c3827d696e5335ec219bb2c1471b0f28b5c1a10fac67db34aea02dd3d9a58dfcd45c64a4b6147939c00f37aecd0bdbc8ecebe331699073bc5cab86c7e42a39adfeca7c4ae1c6251cf9339dc8a3b26c99a86e3d90143cd3dfa205e41d00d38edd56de7a72f305c619eaee8c654639b337d9ba2fc650da8bd79a6f7fc5e076f5045eb325f1ec6a8e72cee6bd9dc6911d8ae2ee9dd884a63b3748f2a4d088a9f3e93fea41a9aa6d8552f1c162911909eeba94fdf312a3839567b198343d8e1914a26ff23233a090bbb28d034ca5073786e3a74bdfbb09df7fe3c53116bdfa025d7500a900d6ed0b5d735fa26e0e732e55d85dacb78063f7e3a74aec156fd6681925b6142e184dcad81c5b328cc34134f1d2b60c79398f88012b85fcea2e592ad33bb5eb73fd78d7936ccc9c3d3171e5dc002e2067d61c5221c397727ab5455aca1f1a5589b52712756a9455b005130dfd6fe78d06c82d8c80f6e7baf1968b9b21e898ad285d8df812fe9bea2b4ea4e94099256ce148362ea9d73bc2ffa14ab3ef706a61db1a3c4bdafd652a32af3af75cc789a1277c50c3c2066fdb9b58da73c2da5bda9b7a9b4c76ee311d05a60b154d1c62a237276e6e57d4a9599e696efe123c9242b31cd7319c9c3edd26ddd874722846f3027f2ec6a75eda57dc9c7929ae8767a83ea6f3a60e9e574353d45c3c26bf8eddc05854d724c9458dd07320611f15915c1418370f7ef30f00e0256dad2b10f7b3ee37fcf25b5bc059c6bfb771fb6d594181dcf5a02e5b07350ead35ef8e32d33818d8b31e290e4495335269ca0cd8635dcb28ce4862595e5eca58d127fe4b98208b28a383b89251045ece41731aff8261c5b1c44240ae9e696a3953b2a70fc5b4c249287b47761d4f496900a1f562cc062047da891405b22d01120923df8a0cf6e9842d7a165373d94ae0040ea252d3d12bbadbdc4a6e3f9c04ce43b939e1e4bd99bf7dd969ef73c589861722f939ef84f554f47610f9bf5a65b148e0e7c2cedab6798a57e4b254eecef1fb9d44c0ba01807640b1b9fedac059d42eac30b80d64258bfb354ebe2e1ca7d08359744ada6a4eec4e06ecca2bf655f5c99acaec46e2a96b1a9f3e4a455fa849a8a32a40541b8c2ac5ba02e1589d37ef06d96c80387808604ad39905747ee6d40c17b7c722976ce9ef2501e588ad13d87381188ad465c5a4143cdae023dfcee13a575ade9a9c31611d1279ae1c4f78f8fe0c8bb6a22f6b4d350e630503fe16c4937bde0c556095d9446352f66ce53dff0962b6972e5418f1b25cf849860d0b77948075149e205bda9257807ab9ba0cde1320984c6a880dfc96c8071b1c7f2da8ec8ca047b147504a80a99b61c91ea7f4d06914d78d99ce373c4c2186424dd6cd6df079d2c208719d896ee787b807a5ab9d56737bc4d81c03cbe4aa35c5b32ae12f9e2c5614b15c2166120d713c33d93de3ec91093772bf8d08e4188a15c9328236b87199253b289d3c41d8cffa7c00641c92ed10f3101abb2f1e77469d91a66c06325388dbfd2f2c360673ebfd8c193596492ef43a1ba6170b071d388dd27335734b9d9902303021aa9bfa130cd418a5997c9cfdd6937aab1b1082303f459afef59f6451e0f25e87c34286819927d84c7cae19cb08f5e80ea20d8cb0de5314825323989daf1b6eca3b05acb07d3738bcd3fa144cfbd22941478ee78df607a3ae9f4ad9887e573dd0f53a606c4568bda02aa2c72a14e79ba775ed7b5118569c9897b30a9b764c5b90f4a0f57518d230f53b70d5c403c0a602df63c657b4fdd4f57e9dbc2a014eceba0959f50bc31d397f3c6c6786c2f412c1e7fa43cb96029c2ad2f040320bc955843500da0f3bbb5afec172677da5a33bff7f84e52998227b559158a08a67b506d263cfec956c253b7dd35169063b829d3543e7cef19cae2fdb21e50612eae0ceb97aa3850d31dd8a56026a6332274b5629913b72df09384fe6a4a928dde489cc83bff7e04f1add99b36d6e2e433978b9f4c812cf334d620d7b7dc0151c8fdd3eb2a05c205dfda81855432895012d30409216736b07d2b65023921d686accdf1a56f8ca8d26d5eb62a716cb8a9e2179dcfb31cd4eb75040ac4f115cd6b61cc94eb584606a5f28426ee44d086c3606aea10fab2c50534858b573dad364501065f4b620afaa2e29a3c0f36a9c7746511efc373f24db2037861e4f8ea1d8e7b4c016ad3b06a38c4c09d3520ae8e0f4cc865394153585a12927f5daa1b1679f65694ab847f237b7d6a7cad5b93c014bcc52b86a784f4eea03dc9aabfb4f44cc0b728f07a9533b77cbe9420793ecb299dcced0ff848af5596cc971cf117f2f2f50464a517a2f180c8cac0bccf1ccde67c92afcfad11177e96d2335be9e6a861990221b38ee469e8d264cab5097348184095253cbe6b5fe582eaa09c685929db3bf1366262832a68f9f1531bce3383000d9bd5f4a01406a3bce021ca61a628a0d39d83452d36cdef0843e3b2eaf28dd10a8d9f5f2475e932cbde780f581eeb74227022cfe8823ef4b2b8864a308b19d07e16e75b1c6025970a52d34ab02ca04b5efb575a277f6b83311aa0460d8b6631e64e1f02e790aefe6993ad3e68ac03ff9b7ed4b6bbf0c722a16e0ede6596874c793f58f10a96564d2f0d90a49aa7110d1195dd5d437b21a1d3331aed4621e25f418123a4718c11a59693e4eb5b550c90d71e72a67cf9607cba1a7c4cc0df13d9b012c860f9cfd072dae0f4a7933cfd6b5c021ade72c496dada4819a5051e8143fafc360876de800a65260393e3d398df9102280c5dbb2310b01ccf5801407d0822cd3e72991092450367546d3adc405c4ac1c3fde4c85229ae8eb41bd76b4d4b19f4b8c2172b03ba8f8825084ef642e280f703784b1c90054d32e6ec18832b6792cca8557c3c06d28e1405813874c765297c5de4c2489bd5b71b50157b4a05702127a927283a5b7b6f09a1662c092dde0730f44aead54811e30edc89c30ad48831060254e16aab08ad008d7adecb8010904f07e28c1da4eab1ed57436d97015df59add78ab654190f121b204d79817ecfe1007b5fd4ee94eee188c9016f930fd3c73984f1492e0da4b3dbb4e10ade98a0d420caca0419ee259fba55f6ef1071249f101ae2de60ecb8337c4afac1924e41ad62c9a15b5ae6f488c3922d5a4b2f911c399b336c8adb59b6e218b658901aee1512393e3adc4a954365c97dab20e5e08d15853bab36ad316d0f244cd9a1cb56435b118d990d5abe5a7e53203624c93738e2d744dbe08cac2db32d4dd2dc979bf043fcd11a27e5b8508e446337f67024fa71d5cf5423b7c246dd2796a99e876b8658027ffc79546489500040ba7ebf931cced46fb2c6ccd738e7b755ce0b4fa1fdf1512e552b9ad7d71d20417821638fa257061e4ec5f80c3268e9d719d87e6200dd1dec2a02be1484470a3560b25472ff000000000000000000c098452916d54e26b2b759d13f148a7f65666666caa6a733e3a75cb5d6dafa1769c35fa4f1bd01dd09270a790af582187be8347892320b8f62751c7888e1685884cb3c394cb40c889187567e42ce9b5df77d3731f0d04587985390eacd15d53bb431335e624fcee1f56587fe226808628c3af412ab934ae68896928b18830ecda6f0f04835e200dc418c39b42bf3de51c7c670f1e5d057f42c99722cdf15def220461cda141de56539d3089e85435f312162e76056c9d537f4a2213d4a1d3c6e6863e6d02df7a67468d486f64344ddd3b91cdd356ce87adac4628e1fe4e9b9862ee6c8760e52f21cd72c21861afa3895fc52b6381acf4f43bf495edf64e2565a1b030d4df9484e2db2c53843e3c187a7b21e1d87b8c43083a51ac4e2845a42885186def462bc8abe31537c18830cadf764700c5d2789cfe183cd71d44b0c76036384a12bbf0a4f0b9f27d4fc410c301c0c627ca16da91439cc6ad9ab295ee835275e4bfc11f5adbad0655c5f4971630a53792eb49d2b06d5bcdb99d9db4293ca528e27648e2ffcaf85bec36288f9cb3a96cc91857eae674456562b468a852eca46f2ef8bafd0c5530f737a07b22945add0871834b664c689d75415ba88a233f171c50ecd42853e78cd51740e3af630595368a3e7ddce1239e5698814ba68fdc8d55a8e429f3dfed8b272ecf14e1c0a8de7103cf8209e43b92ec6139adc9224a26a8e83099b184e6873182a39c73ead19d7184de873a0195eae24648a520c26f4a1bd271ae3c64c118cb184c673183e7dfd31675e6328a14d1159561153232b3192d0b7eba4ec78258e862606123aef8dcf61b13fca1dc638422b32792f07db1d3ff218466827c40a172b349fe64a8c2234137fd1d91e7f6ad8c42042ffb21a9dd38368b1e3889187730d13456384d0ba8494529e6409426f792b46b8f0bc794320102d9a5cd4ac961fb47e1a3e728f731821511fb41772e841780c1fc4553d68337686f89f7c91ddf3a07df5384c1527c5f1ca0e0e4a426f4a7d49076d940ef3a875071de6600e9aa462217cf8c1aac9386834b7ac4cc5ed066d7e309123634f63de6dd0b65bfb58d0a9064df250c643f6284a4a2e060d9ad0c9f3694771164d7bc871f276a7585a9145df1d1f6ee70f2a4e43b138751c89a44e4987453f9d359707a6497399afe83477bce0715e47a8c515ed598e8348953dc36ada8a5e4bd63a435886b7242bba54d5211dbfa748f756d1871a27c57cb9aaa2e928f4a7e6a04945afa61e2636c22793202a9ae8fde8329339452316e43745e72978788cdc6174ce518afee31039887e51a2722829faa4a15fe6294e3ebc51b43a29a554077b953d13459b2124f9384ffead9443d15765b2b2cc713c971d281a4f32497b53fa4473299e9d52bf9e6833a37e0ef1f289c7519de873d6cde123894d7393134d8af6299fc8a964cd4d343e55214584aa89762c858796cc29dc249968c2b463928d39fc520b137db4babceb76f47fca25da5c6a121a1fc44fe55aa2f51c476948a898a2e554a2ff89ea711cbcc31c6390127d5ccf1b637238717712bd5ed02f8d1da6c7a424da31d5cd5615e24ac623d158f858513c3d8ed45748b4a3eb715275a99c1df6883e12b51cf8e7ef887e634c21937fe5602635a255550b9ddd714d731023fa68993cf9aac30a162fa2492d0bbde82146c88822da9c2a6c5ef85422fa8ca3390cee23a2ddb0f815f5cf9d1d73882e7bca68216bae8eec19a2bddc30193965c7bc64213a7d8f723f2773ea0e21baec417a7c8e98c373ec20da8e73427994ca0e761344a722a22169c8c91e6502d1c7713ccdd7f1b25fe600449382c470cdd3d281997f68c5773a08ebea79c4f343ef41f7f27c103b5ec43ef499e4c3c398738710163e3479b2248de8a83df451d4b70e37f52be9470f5da5eaa4da41c6d5f8f2d0ef5fbc50d19156d2091efa6bf3c9891e6edcefeed0e57ffd1ca56eecd0e957f7f7abe428f7ac43734125e7cc1aab2a69e8d04647528f55f739b4efe17f8e28fd98c392439b3cce76c896c4a1f7d32d4f3117d9a30b873e48c13a53f2541a5dbea1efcc7111ad2fac45d90d9d0715cf3d48ce5c296943ab53295b773eb35ecb865efc4d43ca316c6bbe86f673b66fbae4c8e174a8a11f4d8fc27bfb8459240ded49d0ecd0ed912d5b3434159ac163f57586bed33ca77c42a4ead00c7dc81d1b53b8c8d99fcad0a788885f4c1573243b19fa0eeab19769ae1c31c7d0a687ea19d6346282470cada8f459c79b1663e5c2d0a49e5a94b5f2cc9a0543ef1343fe38517207f5bfd084c868963d6e88a5215e6835ddf737050f34e7b00b5d4cce93fb530ed58b71a1bd6c297f11a6b2256ea14f7539970b9131e74c0b5d0e9b4d4a43b2d00637b1489da11dfc040b4decc8ad1bfa7d7a2357e8b2e7f8272f1e5ade6385264f6b747fe4abd084cb3953787ba8971a2a341d87726dff720a5d880d39c5f786040148a1efd049349b66d1108a4217191e6139a484df120a8dee6aebcac4774bf8098d987af2174b9e52e79cd07c588eeb8af138ceb14dc0a3e3287648d930a1d7cf1f4e66b31c834f10c0123a6b5d0922d9732cd72aa1718d714304950f5de249e843d8eff81f2b68850a129ae89ca4fa41895210c011fae99823cb1e5b94548d11dad1ca56913a62bbe68bd0c5158d5711c9a0312142eb9323f9c15778ec39844e4366fcf48adcecb910da37110fb7dc0b427fd1b15298af88d213207479bdd79ba32c1347fa41d739c75e321d64f2c010800ffaea6bb5585afac1a23d6876df63b63d8a7fa83c68f73788a876a958e876d0b4a51c56b2ec74d0e6f77964eb901a729f83ce733c39c79b7134070f07ed45b076c9ac33fd3937e8e37873304d217c55678356fb3afc8d7f914d1e35e83fc47fc9a730a20068d049595c0d1ffc46882d620066d1c804e9b8adb37a283b222100b2e82d5c949f4c215b7a3400b158faa30811274a58f41b4dff5774c92ab4a78eabcc88b8a28fdfd01b9287ddaf592bda30a91c5a5f94e698033100ac684fa6438d7269b14a425c451fa9d32ce726ab10005534c95f2259a6e8a0234b2a9acb71bc8826e1525e0a156dd4b0cae3339b16255208c0299a14170f736c66d195b942004cd17c987c9c98c13b46be147dde9c2c2bc796144dea578db3e09d0a8a0a0118455fe91d86ce711cb91c288a3e49c86134ed485b4332430042d17b841432a9a48e9e5e1702008abe83144288e6191f661f87007ca2ed28256b8fc7e30ed6253704c0134a7e4d3147d18ef368e1c59144aa088c1a04a0139d7ffae8e9e6a674f80701e04413743ddced142a46c74df41b4136e5dd4b134d94efd48fc833d1c7e3d152f0f2c044179d6b1e26a656c7c197e8af3359afc865574f59a2dd10ca2d69254db293ab44e7951242b6750a7a3e257a1fcb29c7d0f0ddc19f44933985789ab2e6abe992e863b0a47b1e138976373b35f5e4df9921d1856c642e776dc9b9e311cdeee6c9ea85a4f670442f9d1da237bf46b834a2d708b96fa631673c19d1079eb23785f894d245b4f279213e33f24e7714d17ee8c12fc4fb3d4b4e449f25bf5e6ee850a45244f4f11657d11121c6257310e78a78d08a1c3ba30f4df68b67390c1b4298541866f0a1cb710cd251f3b7a4552fccd8c3f98360293cca04ae7a6835e7bc153b0ad1a9c43cf4e781071f07ff1cc7bfe1a1370fd73efda1cc435c045c942ffac60db42cccb8c341f0921cfb5fb8551366d8a1f7387b0ed13ca943939e93c3901dc72f2f86e185175f3861061dda18e3e68e33723b8c7368736cfd4f321e973117c30c39fc3146ddb2ace5db831971687ff344a586fc1b7905c30c3834217ec5ec751039f88c101ba937f431afa71472e0296e5d98e106ba5182196d6866f562a8a491cf4d2fe2c20c369c12ca2365b6d2b30a63c61a6c861adabdfe164f39ea1b37740a33d2d07b5c48ceef289377680361142e1870e346171e4618858b2bc20c34741ee718f1fb31840e5d33cc38439363f42b9f37cdf185cc3043671166743cc771bbc9769851865eb3cf7cd20889cd7f17ed861964683f85789824fa038d8fe1d0f1ec8ba199e86c5131f230b4a197d7623e2a09ba83a19998f7e7838b7fa16bf554a13ab0144b752f7459693d9f61ffa33dbad0c71e48f63eefa0437870a1e9f1a883567e8e727fb6d06676f4c6a4e296b5a385467390254569b2d04f4e492672ca0ec90c16fa2086ccfddd71852ebb721093fdc3d1b758a1d5e421e7aaa72c725c853ee8ffec38b03896f952a1bd6ccde695e7149af071d0f1799c526825dc924cfca814938c423f953aca68a60ef540a18d9952dea8b177c2e5094dcc1f4be2e778ecd509856c29e48fc2894d685ac6235135154b1164422b5da1fff4634b6824b968c81c87d5d79712fa9ca76139e860127ab7483196fb3a0c3b123ab7ece781788ed0cf67d5cb39c63c9f2523f4d9639ed79d4911fac914516a1e4984c6e3f01e5f760809d3cd184297df375f5b3c43089da6106bed993a0e6330b8702dbef0428b2b24cc0842e3df413b3bc975e8905b3003085dc66487e4b164a9c7ffa08fe829870c7125bac67dd05c4e996c58756f8bf7c04b99672cb2ea3c68fa677c76bc7352fed8419b2a5b8e2715bc25e5d041fb13e384aee8a22b1d39e8347f90373fce259f1d1c34fd96b3e6a07383b6e37c9ec34eb0281b1bf4a1a79fe5f03bf2b052832e325c84947366060dfa941c5e314eba575366d178ca61f8e4a1c9442bb2682e23468a3ba93f771c8b7e22a4bf07fbc1a2f3a83ae84a8486f17845177b73caedcaae5571459f1dc353868cc43db515be9c2695157dd8be2d12e2660e5256d155fa07a93f2455d174d8d1bcc38f730e26998a5e2c3579240d2a3a8f3226edf0e651ce53b43eaff147834c080d4dd1447a473e394ad1e5c927b12c86144d7f703a55194563f9323648454a7745d1c649ff6ff9ff48723bb6e89a62a0e83d6874dc9a235f64fb44b33e1b62b65f9e68a347e21f47a953481ed5894ef4a3e03a271e592a4ef412b2c3d0f165136df2f88fabd039a4a8263acbb048e1713f8e9399e8354f3ff4f74eae1861a2891591354e8304f52fd1e8e5588b5e2cd16f0e212aae5c89c6257948cbb12b7328d17a9cc38edd642c773b892ebce55cd52995e294447b326b7db95dceaf48b43d3972c9f7458d89882005c675053220d1758b65f7ebe441c4ff117d903a72579fd58ef1e188ab2453998c4f320c198de83a4ae1715a8ef453d419d1ab6e881ab543e7969845742985f8618254f294174574a5a982c410d97ff644f4c187f2316e8ff45816115dc7703d3391d1237888be3ac7430e9e27bf86e83cafc37e7f6aefea0ad1773c1b22e6d46d29436410a2f528796ca229b197fd20da890e564a551744e3bd593a632726ff23105da7a81e425cc8d01f8068ad3dca8c2972690e2b7f683397e51ccb23f4ef7e68d47526aabfd887b6529fe51023cb8746ccd2a3b4aa48bac93df431535a94570a29f5d34353513eb94f18d7955c1edacab0e1a1cdf241b3af62429a74875e73f35a92910cf34164d8a1ed8def8e95490d7021a30e8d8f6f0471bd8a093974e8ad245af258cfb821890219736834e37c8c8e869eb8caa1e2d0679c643987511ec57ec0a14b8df6c9c9315fdfe30d6d8ad529bfb347c9c38b1b5a9d9c9afe314aa78ed2864e6336569e9197f6980ded45e96b7994aea15379d7ebb0c24346530d6d9c5cea4ec94c43a31da468be4142e80a0dfd07ab1b3de4e8cce4ced04791cc6216b3199a9016e121765ab092c828439121f3fee41d5a121d8f19c818431f625e7a9fc50b1eae32c45032c24037880c303421e65863487a4af13832be5032bcd087eef3179bf13c74bf0b4de7a66cf940e63b7f5c6835c61c27f550775d43646c816e14195ae8cc23e6d46186b24ead8c2cb4eb17c42da7182cf4f1fe57b58e445ba45ca14da1fb91422fc673ac153a2d0d0def5fd9a7c42af41b1231739531b74428d082f2c517054d20830a66971c446aeb778e549cc21eadad99db42c9a020430a4dbce61817e3c88a7e2b230ab98407a571e115833190010594f10494e104194da81b2983094fc6b0fa71b6503a12c858421d4186124e46120e043290d087a88cd8eff9fdbbb907c838423bc934e2e48b23167f23b49763988ad06eeabc0c39ee307bfa44683d332ac4cac1e4caf121b41616979f12cf73182134f11fa45267b02c6d10da32ed2855360b96ca03084dd8e83d8fafd1d53a7ed07faecc922bf5efc7fba013cfc1f8c6281db8e7f4a0ad9c53f848b3234bd2e1411fc7d8d8f8e0f7ad6307adafaff687954921a7e8a0dcdcc932725772d0a86c5f7433fff0641cf493e3b9abaa7b83fe32668d19bbb5413319c2c37cb25973af41fb7bbd92643c6b4655060dfa903b05d5f8c859f42987d68ecd4dafe394459f62a5a8933bca39f2188bcefdddb3ce0716fd680793a963f53daf683bf0cffec19275ee5cd1677f13f3381b3db0b0154de8143175fc1ab276b0a2398bddcc27dd2a3a778dc1f35b4c15fd5aa890b9d69959a954b4eb9bbf637c9fc9a1a8e82aae7a2e45bbbd3c45bf2946758f3f65f19c29fad04f26a77a90c524548a76d5547fe3a4e8a7430c495f243146d17b5a9209ba9d2d440c51b431c7a3a3c396e0211a8a46c3fefa6b8e234ecc83a24dd143756c8ce521c6275afdf8c3dfbcfe20fac23a00fc208627fadef2c02cf632ff97dbb58a189de84f64437ca0af99e3a07a430c4ef459f73c761c8452cff19be8e3e082bf54b8943a734d74391253cb71898efec844df163b1ea19bd5c483893e8ef2f8e87c984bb41a737c8b19df6f4bb144936463428ece980b964af4be29c307997ce3c62c25f6d8f3c952cee3245a0f524988a79fcc7494449b34a6725c11cff21e89364a7e04f3d84c360e89aef72fcc8b6849c4f78846526aa61cbc8ee8256b79107b82c64c5e23daf6edd8584ded396c8c6872ca43abfc210688b1883e85b214223624e6a42ba20b395fd45a3c89e8f24516f9b024c4f82a221aff98af3fb6fcc8591ea2d5bd3896f17286e8f384c758fac196c75b212e06213a998fc1ab34c53b44901440000598e0788033c620baac9732c5d2cf88e1a62186201af50f2696048bce71e8186204a20f8ff8f863ef934a2e7f2106209ad6d8159e939e77c85221c61f30861f9aac32d9620555f5d843810f6811468c3e9c4f4ee6f53db650f2e2203e589173b92fca09fc0b12a5977f13a8a1865efbd7caf243979e8b6282111c3fd2d078ca139a3d99c71d15811a68683b30cb9c60153e6e8f2ebc38010bdc2450e30c35ccd068785895dce1f7a60b2dba28078c32b4a12b690e5312df13030ba082400d32e819f36e669da5d19dd515af9e2fceabe6503046a00204e38b6281223f8ad4184397fb03abfc963bfcf3c5d007267fb9a9ba628e1f863e4baf426c6684101f60687308e2f2513faaf2385f6825859f30fbda21c5c60b2797b2484ba6e942132e1b7285c7f6ee0b17da9c32840b6a162165d9421f478e5224e9cf49bda28556c3e73bf3255c42ce4293a7473aac3c55be2d16fad04218ff7c5eb2da5ea10be51a46e5d3275b6b85fe744e3abec3d0b4b60acd584c31c518374ad228159ad2d099197a9ac333a7d0658fa42947ec1c3b4429f45f39c68a5f6114da8931e49437cf8f162874792924cfcbb2d943ed096dc8f6788edd5298c6099d473119f32dd5843ef43d779789daf9312634be95a274d4a14a924b68bb653cd43c309f6029c1103989a65c3d09fdb507e12a2712da0f36e53c7d92c2647484b6c5b25d3b9c84fc92cd410d23744922e47ce27b70e914a17789c9c3e3738aad0c11fa9441d22207d1f11364e0c60d0deca0c6109a0929c594952457831a42683a3b48de112faf466829f0012d625023089da7f00f5df56b410d20f46122ef8194eebc783f685f5b333eb4466cf6f041ff192fe7948e10535c418d1ef4f1f479c7137e1ef49fdedab19122849e6aeca0f3340fdaf5e34e67a483aeba3ca8a049d5e4c41c34d149f24bd80c393cc7821a386852e7a5206ef23d95bdc111e3ee88c754010448c0020e3fe50419688147a0860dfa20cc780e9723a8f6cc356ad0ba498e1934436adccb177de346f9c20d8c1a34e8c2e310decb2db3039d98c62c9af2ca19fb1f6356e6b268f6c53d6477c71c6e8e58b4213ea37bfc0e58b4b9f9f3a1fa85d51caf68b5a31cc5aace714523b2d15b3f9690e3e069459316e3e6a798561e3cac6826757e157d9e305d31e5475976a38ac6aa453c5a880e2e2da968aa47c2e6cbf1c3ed0a2adaedd824cce78f4c854fd1860f63724a12b3076f4dd18985ee680f9b430c6d29ba1cc71f2a29c324742829883a164e3eccea28fa7f519dcf4f09f993a26843a7c68e8309a13a9ca168368711a2cca7e30a2128da303ec963c8dd279aade8ff9ddb424be3894e2cd7e72706ad90ad136deef8f2ae7ac6893625749cea5f923f8e36d1e4204c87f84f13ad9fe60bee67a2954acfb9a838ad6c31710595c8f2b3b1145ca2b7a4621dfa4e08d161892e071e4e14cdbc9aaca312bd86e9f30fb246893647cdd424fa38ee08dd894a12ed44eab7f66a7ff39048d08d11d080443b91a3c85ee99242e71fd19407a6a596f52d65de117d2aebf6b22893253b1ad1c648d8e9d8b7ba4396117d8a37bda0315c44af2139f977877f9d15d177ee7f4b114274b8881a1a89e8ff4c2ec4dc715ce7454413cce398a6a6102ea964018d43f4a1986cab8caa6bf486e8e4dafb236699df8f42f421fce414fb37669f0f219a1cb9557bc97754298368f2e6c97d7219ce1444ffaf1b52c86169cc9803d158a7541ee976e50f5a40742121cc46ec77540dfea1b5dc0f3dc2bca7e7201a7e68dacae3f5cb281d5a376e2c82610c8d3ef429e553b7b0ed81ba071fda091f07412a73769196c61e1a9978952d0e65000d3d741d8f050f1515d4b52264001a79e8b522caeba7e0b1b58787e67c46f63b26fc011a7768a7bae3fff88d39ced10ed0b04393d719e2293a75e8a3b2e88fff91275591061d3af52899c3c5565ade68cca1f3d8189770fed5e9cba16bf97e0f836ae59039e2d04408173e6facf8970e1cda6c41b6629759ae4ebe219dcc95c3298bdcd074cac1ff840f3c1e0f6e43a72166f6b5eed839291b0841cbd4a2055f8441b44a630d5d868788ee2137e5300d3534c9652a5f87930e714f43ef137b2292e4d05c42435f117b23ffe6a4e61d6738dcd01e242bc7710e2d19d030c3b98046199206199acd29562d3f8a31d21843de383174c94fce530a1fda058d30180d30349122593d44ad60256da144e30b74430b34bc70d8853a1a5ca01b84c616e806b12dd0d0421f1df1510afbaf1d9b2d948ab040230b4d7a68493b7a626877b1d0c667ce39f57bbe620838685c019d40c30a75a30a4dfc38ae55b1df42ef0734a88021a03185be3ac6ec08b99431e66948a14d615c35557cf120f734a2d07b10d92c159d59244f030a6dd4cb61f478e387ec3b8d27b49d51c67bf3625c8fd37042dbc96349686fa6ac864613ec98ff9782e798d0c4f9e7f8348f9a6b4b68a6673d8c090d215baa8426667f97ac978b9999843e458e34de527e8f1e4403099d4faad8d2f107f1d1cc6840e3086d05fd8ec377cb8145d7084d08dd9e6b957b524c119a0d9f3b8e31a5888a4884fef2438e39325856cbaaa805be25041a43e873102d4e5f66757fec0a1a426842d2b77896b5bda573e3c68d1b1e20800208300102282003ee0c389cd00842fb41ca43f01cbe3bf26c01460a4ec061a4e00461a4a00209a00184b635e438495c6e658a4881c60f9a8f29c44cd1e17c9c7f1f34a2571e49cf9224cff6a0ff8b11247a4a79d067c9af4a21ffad8370075d0acbe1798e2fc2c3940e7acd518b478b912d74ca41d731b5f55fe45a2a84060eda14352cb956f4c8f0d2b841e351ffa34792df21ee0e58fa040d1b3459da71302d611ea2a9011a356892fe5e794ecbf68f5e80060d9a941a3ffa879ff3c5970b2e52f03a8b2e7e3e6363ae198119b2e883c891293e3d0950ed19b1682f564795f348d61d19167dd28e63557f6fd88d5fd185a9eb55e5e50ac95dd17a6acb9edc3d948c885ad17a60aa122561062b3a7de98b983f5632935b281d9db18a76cc72c2ef4ce854615574b2d1fdf5ca772d663ecc48459bb434eebe25f190e344cc4045937b77fa2f3444cc3845dff162f99363c71d047d045f84818798618ac6524469cd9b139255cca10518250531a3147d301dc7612137637ffc164aa430d25c34427ec628da68221ffb0731572c8a9c118a3e0ab1ea71f810a6bbc20c50a479a26e5f5552553fd176d6949234745b28b5a0707170b1b488199ee8cabfd3bdc30cf903b130a313bd690e9d67a3da422939d19eaae5382f2762f8bb8592a115666cc20f33a3a37794acfe164a25f0c28b2db4702e8e1168d18517a5802dc2184107c0e0820b041040010420800208a000022880004748139dbae70c7ba1abc4832887199968afb5a5dc727a7f4cb5610626fa7ecf1c071ff9047ce15d7471032fd1c6d43c91fa934fe9821164e0bdf0e20b0b5c1d6658a269990d31e61cb3c39057a2b538af493593b9871e4af421a4320b112939263c89d642c5f194301d2e2b92e8b237a3454bffec919a1189d643e327362cef56740b250f6304c4856135cc80449b9dc2c6e6eb7844ef51a8ec6fee1dd17f18e6d1882668888799b3c71021c688a6a373f0eb6023954716d1c68c59a2e440bdc3941294304e0b3314d16589a91d1ad3434f2a020450000128115bd6301d6bb01833f888609276f8d055e63c590e718620739cb73f539e767e0bf149ce7c9023df0b1282a491da7272fa073d0d828eff726e4cc870ff4f504ca085736152e0c54108083304d187d152fe7cdd577176422728134809cc08442bc1e37f352b4290f89d6146dd1e3d5acaf2650b257368014621040602c630e30f6d08cb3deb32c99e3b7e68a2b26f44b99c1a169a1c9b396f44ec0abd98c7f7c83744685b5668826bbcea38b4aad0a9c5f1c8e3826e7aa8d0bfe4694b88ebcb0b4da1b78ec2791cbc95ec8748a18ddd54394f3b6accf92874dae26e312dd53be750683b65b694e3e811553b4f687573b062b2c15dc3754297e38f0a8bb16d42d36dd9b147e9eea833a1b98ed1c1a866d518ea12fa909a4a93c42befa8534293338fc714e28894e749e834723c31b6e4a47d6124f41d1d2fe768479afe1ea1f1c93e11275323b425171d97e61c1fc414a193b8e6679eba23b349845ed322e64e1e1dbee68a308021f4e6e1a679584ade5008fd6c0ad13765e8cfa90c42571acef3e57720133b20b4296b72f99ec254693f3069d0f0953b27b02f0cc007ede489e163d1b37c234d18400f9a1052ecf81dc60e5f291e000fda18346a65fef5e8d0d9428b52c02418c00eba18434acf588c3d213c001db4e741bb468b99170c20077d4c595ce27320f182555b18000e7ad79ca0d67963dca8aa30801bf491a2576ceff44d56b141af1172e7be0e461c400dfa09ff1e93cf6e3c1311074083264a7c0921675609db816863164de544b05061e23fcc21da90457f12ddf3e57958b06a23167dec481d2489b12a2f9a50b0018b3e94cba9c3a4d812a3e7041baf68e723f7dc73b2055cd148b47f472c897ee17c80ab600461141138054c30821b378e60a3156883158ca485efccc06063154dce4bfcff38ee8ce0045e68c05380821174b1812dc21841076edc104117230823055e7c51041baae8e3383ae738683f155d96599094b763d2142afa38187d9f10bd9c277e8a2e65667f10f130459f5525c699eb94a2e9dce2e3612115a2428a3649e78bc1ddf2b7c6a3683cdc4a51b34374922b8aa6b4827688e7610a910d45bb9de5fb4242dd520a8a26fe856ac9cb5b74f313cd74f841cca41a235e7aa2d98f1e5a19c74e74ad39c849638c73cd2227dadf2451524ae126da78dfef12cb983b779a68f522767ae8b31db59689d63bf2b81e251eff334c74d6293acad10309d3d4259a4b499625fa09d1de91e53c39eaa884b9325928d174f0dc1d89e47e1cd3245a8f1d5e8a312b8926e71cc7f1b198c60e4291e8334543ab88878521d15cb4081ae3c589b12244b0f188b6e369276be9482f446d38a2abf89573c578f7198f58c14623d4cb39a7fdf14c7fe92af8c2c120840d46ec19565a6591e53a44d898c30601376e88c2c622fa8eb859529ccb1d87dd0b30baf0e2106531053ea005046c28a2f51c4398ebd84944efa1738e83558688262fa2e3dd9c3944f3fd1f22e78b1ba20ba5f17298e8d739240bd1872192a7a82621cb619c1410a2b9f6301e1ee7e830a70ca28dfc23a125b958c5d4164a652910447f7933254ecc03d14fc741638907207acd393fbe183b3b24c71fba30f3d54f96236b0f7760c30f7d30b1279faa9f730af6a18f289b7bc9fae25c700c6cf0a18faeebc1a6a6ac413b7b68e3c646ca9763f4d0c5b874cfdcc179e82f229787294a3c749a211ecb897a20f91d9a6dbdfeaca8c1c3425c1833822eb4601ed8b043f39f324fc9a514255587b6cfe42273d82f1142ee0636e8d049acf89243c62b8f337330e89669ec0e3639b4195112524812b61187b632cf2be2c74a193c36e0d087bcd27192b13c7ebda10f643f47ae3bdcd06b6c86cf19391b6d6872e821d61125bbc462830d8d86caec17279860d858439b1f444df9e38f39c7b91afa24398a791cadb93a471afa8cccb9da63d0d0c60e33758aa1738656dcc7dcad7398a1d1581d5899c694a19fce143e272f67a60c199a9e68a1a7b2bd742e6368a2cb3fbac5560cfd5828d112ff304454c3d07e64477a06eb783a0986d662d04c957c3335c62f3431c152afa744476ebdd09fba68ab791c644a6217faef4034efc4ff6f0717fa702d5a12d7b8d0d66da1cd1ed772e071a8122dd342b31255e2b92c98c3a66acca68585434831a5282dcac0c6157e4d8bfe1f6e04d0400318d0028fc05aa18d0d99fdcdf94b82858b13d8a84293d69a51e6f1c24308151ac9bbe1e1ae467b944ea1b5a0dda1c712417f379e010d98c08b30ba285b5860a5d0be68a42025b9cf636e0bd515d88842f367e95a216a1e89996a8197c04e502650031b50382af1cb71c6369eb049969764b142947eb0c08613fe1c448961739837a10fbb3da77a0a086c30a1c9d11d699e9cf494b38436c6d520216b6bbbfb4ae83cb80ce971ec93d0c9e76c312ff32986843e54797eebbc1fa18fbb313742df1e934955c97af48f2234e24157bafabcc7e42042bb6761a3277786d045b6f81a3c2e478f1d21b4ed2164c77f9ee151138426e6c3a5ce540142f361c620c992c87910fe41fb16933569b73ee8a3ece99e35986e47b207cd6453ef30bd189591075d4e68478fa53b1e863b685c453f5f3e1db4d171993ff6dc2e650e7a318faa7f44cb72200e08213f9ed376c9d6170e86d9b84163fa192cc420fe9a221b5c513a2348272813c8401dc0460d1ab1d6e8707a27935f3668d0cefc84c99e554fec67d1ac96a454fe57167d5289ee5839fe0aab89459f6b39086e9abf298eb0e827456387392d7a0edd5734b12affe3f638ba7ab8a2fd6b734bfd817c98a956f41f779a4fc867f008b1a2889a1e54f64ff85b28690146d922e97007438b1b37fca8b18a3eae0a29898520b93aa1c033c05b43155d2c379dd98e825a23157d90835859244c9ca7a408ba702e2e6ba0a2cb98b24f2486ba5881175d94a08b149c82a82954580a39f64ce0451860a4e0a429da4e96a59d329c248b6fa1a485f95158a314a418c51729c3f31aa2e0a246285a0b721e4f6e07148d74567598e8f844134392fdf8c3d00dfda859a3af54eeece944db1dc79e5f633cf8a8e14497ba3ac4c5cf6ca2eb945253b7fa59a88a26dab8333daaa69199e34c7421eea943f7707e3d8b893ec6b918ba83c79683f512fd8e7a5bf6605aa23fe928e4b2c71f5c2e2bd1e885dda8912b627b90125d7657d38ee1d232c726d14ee50fcdf42aaf7aacd839e58b45a2b9a81267e27adc6c8144bbbb9bcc27bc5787ea116dca4fe1710e436a38a28db0392523af9976846b3462f18fdc999ae4e71a8c68bc93896b47994afcb1023516614e51ae438fb34731a2085e525c4959fdcc5212d15f0ecd53dc337cb2ab1c5003116df8d8714ac97e785dd538c4416846cdb11aaf61887674bef285a5e02907d72844d3419e09ff94f1313406a841883e658c2e61fac72ac80da831884ee6db82c458312b530d4134d621fdb387986522dd42498ba51a81683afe797a9c0c9e2140e0d11f54ce4dcd164a57e30f4d8804358fe394e3a50ea41a7e682c8a6cff7c27f12054d5877e420a12c9543fc2f67c68b68276a4de9857c5db43a773fedea8a1873ecc7153f0285b2af01b8109bce8a2055c78610177066c81c1801a79e8f354458e1219fdc2c130c4431ba2756c5239ce13173f4139602c7de16058016adca18d9fd5729869fffd981d9a203193877029f88e640b3dba70ed1a75e8c3fda81c8f449c1cd485175c7871b408e338098c177438de8e526e929ca2050b46f045881da831873f8aec33d7261d995b288d404950430eedff4f6a4895b32976e3408d38b421646cf049feea1e8c14a801873ea64c16436f042df70d4dcae21fe78e391174e105177b811a6e384a3991de31ff3dbe461bda70396c7f962fef1c52b5811a6ce822c6c821c49c3574c93399e96b844889caa8c4eca54222c2501c1288c32151180c2e672e00b31408001830220b0442a14098e9d2fc1480035a3016322626221e1a0e16101812128802814028140a84c16030180c068601c2b0781418139d1f008016cd28b9ba03e8f700e1f6e235787b404fa0070c2b697693455044df06d0011000f01700e167d1dba91c5029b00300c6aff7ef49b4115d100d88c481d076e636c3d28a30915837c41bcb31497b1b25d020d48500258578aa9924b192ba8acb04035b2020128755edcc9989162165216c6842938552a532ae868b3a32df6b42eb028e7bcd535c7479aa446413c435989cd4750e00559df0d189a48f8373fa054997c1c5e1ac986b73a5ae466ccdb1f91c60f57b360cd122124458b6615c6207c601925f6aeaab24205c41ebe845df81ce9239797f725c5f52d7ebea242df6447f75a389bcc077548716191497a08798a577d64e463599c3e51aa58d7b73a94c96a37f1ef125887f89249e8ad4a80c5b258fb5f53a72c3cde74a4f7a53728641206a2b1caec3e3c131cbd98890d11fb84ecba63c8def426aeb11c6376b314e5422815506710b0640162ba8cbae79a8642c7fe58b1c882262b3d6798e4e06fe0b68eec530cec6ff56d0969ee678f03a59971d51e6200ce05708c86d46c0fb3e9b9ad6120809fd4f3531c0f95766eb4f8cdca0568610a3e940164e27b3f6c1e00ae20dc9c16a198ce2009e43df3c87876e2281d3ef07b0abe4fa6301b6edbf188c729a62f41f85da4411de97f763fdca27f6bbbed7477f7fa537cb31ecf897c33f2755a9fd237fe87bfbfb7eeeff2b0dda4a18e9fb0f7f84005600d6614eb691612280d4f6ffa2f825660030f903042efd8b81ee7eab78c054adcb3db0030c051a79c573c192d591e1eb7a69f00fd25ddfeee85b57947d1ba5356cee833ee5cf29758a39d5f0147fddbd6aff6c4cfc37d62c317ed6cb45e443cbe53908272e90b8089a4addd2fe8a945542f2fdf70bfdda84a79c372973f4b0013a93e65db59369ebfb47256b42c991501913b6391238b6091a41f287a4656d5dcacd2b47e203aba1d68cdc74beb0d011e60646fed0d14666209bb619ea1c716beee650eb70e377fa218a0a64455bad01d4b9333add4be13863a513741f821b57e319b80fb786deddffbb413d74df22f186801f38e00c4867cd20a8bb6e2ade70908a2620dab8a0ef40c56efcc77f94ed24db833610f0acc9c893f761486994fa8ab51d11e66f6d25c0e8ed9e8128b57c9f35644688d8792b78cb9d4ce733c087826ec4e2229e93de0a1d5c87e44767108b1b683b56c695ea22a6c55d2c1d4cf2744fab5a981c9c11ea63e154fb46f8778692256504763330443530c346f8cb01603aecb5cc18710c8c378c61b07d0c8e21638833a996c5c8138cb00b4f168b1b219f23ab91658430ed1685c5781415808a3ecc4aa5c4b38807bc882966274c3ca26a57d8bbfdc5dc35bb7baaedbf82749fba60ab50cc28b51b5b59ab684c82bdf757352b4a08533987232ae084c07639f8a7343d751605c81a10e1ebd6095c2ed420e58e87f4745f378ee899ef8cd23736fb8369f37da593a60aa911f86de16aeaed5ddcfa55c98a3b0cd6113dc2a534dfe16cb0f3e6729f2c86342e808c685ce4dc452322f0114ac88c919e5eb9a8744320f401b418999571551091630e7286fa325b1bbe058c2222fce5891de97c8883c6af07c4c8f86413ce8c36b19133dc12c62fdbca8a9b32ddad6d9b84d9f87d7a67c9bfccd7b9d81947c798e66d22a884e1fcc47ed9878e043d241abbb02c62c726f4dab5f640d836cd8d01f949350dec0d6016a9a51ac843242d414508915983279a7864f7975bc54ee3faac58e5a042e4845e2dc1841108fd47d3ca9e1171894dd27931d9f4fb196533f094b7bcf2d0cc1cc05c25334913063228a761688e3cea7939517d4863e696b8b11aca908b2c89bbf4c1eeb1ad3de75e7a35d755e4f35a49561bcf0eec913a9bc9d628440d7e39cba7b853e608cd7706eab1dacd286fd68c7564df35ff74a636bbe3bf2ea8c0caf0812d555dde3e429d9fe935213b01800d74718d7c74ccf1c202d69a6a58836d73fe5935a02f58da5977151ec01add148bc6c2971d316cab0551846d44b15a00464597af0ecacfc0f7455a1c2c5783770ea0c98377b74ce6a016e8ffb89c84d202e90f7dd9bc2ef902fd65f65a24ccf8cbea7fda345979d8ac05cb3f6f4c0c606d74297f283f1f0810f36fdff5ae0b84dfa49cea23f541a1cdcab314a0e31cb8ee803be5329e4ca06c654306198416db404e05ab2f1836bb89a023d29e7b355938fda75e2bac048f9a186eb8714c451eed3c9324dfd3287724c158f788ba6998d0ee4d5dd4d6f26479b579defc598e566c966a0328b1fe62944bd29d30bf4af81908f6db86759618a6e2a62e04c496688e95e758ec637a81854c5a2d55bc8a662117994183414366af7bcd872c9812b1c5861eae0d0d35ee5c9632a92f870238d3effb9ff278b781db6b8f3ea22d6cb06ca9e69cacce15183eddefc6b0615c48cb84802e1d1205f4d681a2f646bcbc350366da7a0e95517e2a484ea088a66fcc98a5ba83724e419b81478d701a61289c57e4b4de08e5790ac446b2b67cf327c2ad7515c01db49c0788b7e06074ddfa3a6940ce1d24a449b3e76b625245786f09cc6dee8dd512b9303d9c73d7a62dba394f1667d23a9439b57cdd3d6e44dbc763040c9fabfbc499c351be57ff569472b678128c137c7c2b67e4d966024062258ddb3a3f4c38553f5d8830c7ad2972194adfda361d3c956e74ab229a32ede763de78e4994c8437e7278ac21b5ca6b64a15066cff66e32b48b905221b4c22bc6957780322241a50dea96e56bac25968043c4a2fdaa9d4e9103f87a0b317ec4eec66078c23b72e9b2b9d0f7966d59d2e1b466d7199849f892d28d233f1207489fd8f54ad5f86595da72f7cb31cbe0ee634c62ddb34b437208f3349dc4e14254d7795d2f8a2140135505514888a3f7df30c931c3ae83feaa0b868332a430528080540b150144dd5d01312961977cd34aad64a3149f584e44ca04ee89a5455dd8c326c1baf7520ad155e029274a2f24419214fd25323176b6a4753f7d1b8c71d5032d0138d3b62890d31eb0078fff5bab8b3e6a92d2a175756b9c9de84ecefc55a52c396f301f529a98867396bd2806037ae3f25bf348221e12d98d74b1c8dab7c6427d2b2df6c839c58fd0b3121c8aeacfabb29b6e73ce8699251e2ce85bc123765b37af3e2389892141f4918e4816403e6f8601670b23c91979807558e70e959150d9de51026c98b09dd64d9e34ccfdebc63fc533e3a5b878808794e192d1e1bcc697485c58518a64c9bc807f4a6c5d58ea25bbe27e04d4ab2baf2a2f89ea5c32b4df3ac5da1c56b23f3645fd85d1ca3c1c68761ee4e7ac2a2c92165873a3036444f98e84acd80ff252e972566b3b6ceae7e6d85b7e719cb317b0c5be1c5371a3a2c34ae3d40f8010b8fc963d5980dd684b9271fc3df79ee114f8aad60f81f5b6d949665f02ca32eab71796f68a842c39633d36d9b65a23da8690b1452a8faecf41f3d5abec7a776e26aac1a2c09c65920fa04d33f9660b53d8c4dc662928a6b14ffcfa6de5ea3a6197296df32997b8caba60c3691a8e9052f439da879c97c61a550b2a88c44999f2d023e72c396214025f538ddcb1c37a1800ae017107ca1b99a0534e7016d059ae7f511a9b818e8ab827818ff91eb8d993f2542542c1cf8afc2e01940dd40599769714e0f4c02331d4c0608535e1275426498733d0be99fb0907bbdce831f0dec71792b10c64a0df3963c055ac866f66d0dcf8d92da2386d522761c2d48d61c1c365c14d7c296da73cd04cbb1e7df2ca7bf22a96a9f070d87a07162cc4c35a9b1c57448e94cb82603459872149ee2e958971ae3254d7a7ba02c2b2393f56cc5d81c60e78bc408ca3e8a28fe9d1359eaa615bb7b68e8933113adf03e5707f9d45c16854614eaafb2ec2e6a9224472000aaf3a359766aa4882f3a316a6625cb781b61581a6d8802eafafd4c100b20b133c8f4d1419d28b23817ea4af44af892af1c8db2b6d74875f4a865ba1bd235b177f1a111131bf9d371787c9f9f2d2df95002bde7285104d2c967eee0d35768a922ea562069205da0b1f7b3234f764a4e2709871c01afc98536bfb48ae8d7cb7fe26efb9970b05ea6bbde244f3ccdae1d49bd173f8022e6e7daf29812b257f191de43f9b9a30ecb46c61403f00a7db461d0eb8698aebcce5b4532b3fdeb86f78407f6216f13b5821a3ae39ef0a7dea62ee0e8ade915d578885e94d7b5a85db0453f2447453758a54cbc0123fef2c2988b5005d453564298173157787a61b765b69f2b68862f438b56a8771c751312805ffe78c9231144f3550d76ea05b403ff444529330cf803a01c9340a54362c95aa1f1361ba9a0f7e5ea2ac8da7c2ab999d0d8585c071973ebebfb581ca97f2699f7d29887dc040efbcefc79e8577489a0cdc0021af988b219902d0f91433338c5f78e22c98cb9fee4f5a2ea88551aa5926542e4d9b7eb54dc63eed03d3e8ab952896faa260656ad2057da27f258bb03450f9677df78ce6db07441bd589ad2f5d27905acd92a52cc285455ec0a43f9f1fb25eb1b8427ff47f13c299daef058052ae3286d5f0b20b010f8bcc7c0445b8e37429e4c7f729419a05ae4a528f2520a53287a65c59f993175f48d02a110a1489742e4756290a7f337389233f9eb7a0d68b8fdf8bef9001646314b1c0a5964e847dea8828255ae90772ea42478e0900c3fe8695f14d2aa179ce446bd12223d8c8bac52eaaf309401daf36f1808dfaa63b8776a11957feba2eabd77b97472ab33676f1a25db138377922960a68b4458f9fea037ec21cde05cc7a4c3a52f1ee77f69428a9347d37687b2003ec2e5de5e6f361bf17c498eeb81c854af2fb7b4815d011507435cddf381be611427aa7eadab8e082bc565195e1d0da8a96d3cdd13dea2ace375a8b4c101b68e6c17ae84f32490ce62be8a9f3a8d92e044b463933a265f7645ab9acf81f71935ea6979039dc9a4c9947e3dafc0cdcfcfed5778aa49a3926951b075e0714be9b6520ae9e53d0b087b2e1292718838ef38beb88ca44d1349b7ffa8c1ca26172ba4899e0e803c364d120058379aac256901aac1520ebdae5dde612846c8d03b80f6d71f49505f9ce9805bdda9bd70f7e21184809f6c69ebcfa3c022a2922828b9c30ef14961b2be1c1076c3ceca9962c55ea0978a3864f9d8cd6b7f5e41d64f926ec0ace18a694d45d8ded4740093c146d0b3d2ee56101500ff06a19dffcfa992910ab7ee64607f251024b9cfa86ac672ea93386cfa00d007a2f83a1188fc89a4a4636fbabf586008dd9931029fc0dde683ecfc0dc5e40812f392200055252bec5d8d7a08d6ca436cf8c024b9d5dd8440faa7179beb29f5964d45903f42dd74b293e6363b5c648004c2a10389403ae8d8083f3940b91f074ef5ec4345a59f44e0fb8b6f9413413f586628b4b7af858c7a009f0adadaa85f921676a128a62b7100db29e96d12302291590786f42617467b4ac23baa33fa2415e5b549fb57b7e4848c1b6e9fa0e3b59034062cf2e5408e41ae8ae8ab372e6d8fd795efd30e426df536355ddd11ac1861bcb274d79cd21e73118493fa6705a2b35d16304b93122a00806296207b7db01e9ebc86af25521b521d455e960e15995d72a58aded4fb9005586af03161655d8e28281f1363dafdd3787d85478e4a41b5c35c19a41014c283dc94001354855f8ebf1ccaee0f9c51dda07842ea5d910be32b9eeaade9f3eaa30b6cabffa78ee7ae4b6b0a35e2c809744516e5c2cb30046281b4bd80063834eee6574e37db3d45664198404dabcfff937b0e4e4b6fbcbf052c28fcdc5a8459578012f3a2612bc0d13d8bcf9473385eac05c8a166602530f2ad0a81bb193e5494616aeda09414119d94e15d1903556fb6c332cbcfe46722ce1bb1c5016b5cf48cfad30efd8ed0574e10cb68c013dde5b3998e473886960f93ac91a19e378cb0bc06082d5a49781630b8f5cf353b76faf5ce03e2b2024b6d0ee8b7ac2df02bbf69f83f11961d46f873de0b72f90aae6192e8965229086b038af862ec39fcfa5b2997fa4f7161b4f7c85ae52c71c3b74836147b1424514f92ebec9edcb0850e2d4a4b479fc44485e517db374f0240bb2d24dbf246f3b030ed1dd0e2a4c570e043e14649ef7fac44b8ffe0b7f266b2ddbfe6039aa8a7deb5b17ca282b74c714a0af1798e7f4c957e09d94d153ce8b3619b073c80271016024faff5a6eedcef40f2b57629d4946e1bdccdd04c8924dc3752166adb3ee444b9a084b9d784a4a3c05dda1ab0a58b0c8a85d4ea5444dc9fe3e86053d772ff0bf61860175918b5f57783663b835b9264a750108030ea4ee6cab87ce00108832bf9713a26a37c0b1030e9fbde19f7360e5fcbc9cc53f32c5ed373f650f95be8e3657d665e917e131225ed8d3750e9951ee67d07cd09a15650bd4b9f749c60e4b5fe66090f8609860b0db0ed90f04aff5b286000395d3c2bf25b78467a98e96b8be102cec89f9420537a87d60a8c969fef9f9e646d938c7f35ba1cc2e21ddb13d93801db2623d48a010ff2b50e1fbcb3f0811611f92c22a197dcb3efabbc5e6edecfbc4de9f7e82b4605c24b9903b9a1b1aca82b07a5a760a71277fb7c9a9c5b386dde61a6ff1f80539bf0940785af52d6017a3699162a9a5b06d672ff476e429df6e772b80cbeb91f082c34a550f11277003010ec9a45c6452bfdafc7e84a9237fdef3880e5dddcede1f91aca362137d3dfed7a8b2501681e3151c2c21297dd234b6a65ae54b14b99d7f96b8735768d14436011d0a63f6cd95c2568cab0ced0662a64d23c2b1b541b0b4c9fcea7f14af02f46afbd9ef28aacd318e97b78b9093c10fa2c80f5d8f68b0f20febf6d1fd1362bfffa8811012e8115ee24ed14290603c24ff0d0f41fea1045dfa4acdb13d00e6f82db21cf20f2130fbcd01ab651d430cb48100851014d0c6fc53178416f6b40853008f94502c582824151d3191c8c3129a822836187b0f0f28a088d61e12fad1827347c315a960cfc6aa4d5cffe3e335982935a2abe2068fc2831989a9d761dc39d6a9653000156bf4f91fa53103a1d9f24186802f60107f002a45ad00e988f5901907202620a6426b38367c1c601788a157f8d005cf5fb15512ca966dce40552f3aaaaa4fac4557f5f3a9b330162fdf59531f48def6be138d54076bcd0da17714d50c7e96479288396c01474e5b17ec51d2da82ecd8b14086d870ad254ba84f9c27ec402ea36c37368d47ccd76fcfbbcacb3a75febbda1d9559713334965364a32d4488821f6d4fbf018fc2ba19f988523097714b3c99fdf72839f1316a1d08f1d6f0fb5abda39b7e7f2a04b42a27b098965a72ca3d5ceec71684575b51a053709dd2288804adc68c972a0512ba38b1e7d5585bb4fcf1072d0a980449e9e0da37cc036a3a65eb9699aa7203d5655356da0ae5f3291d3eb0ed4082fbf5f2bc04995534c9ccc4a82dc7d654a18bcb12b2f7d87ab1c3f40c725f8585b0f425ff8cf9e9a7d200382cb66c6a1374cd9f40c48d312d2ebebba8195833a60d091a96c3943df9b6da8deb7d540262742d20c51c9aaf2f2b0ba3733deecb66a0c41af04a8b816e8db89b1d75aaddc6c420960c13143ecc54f7b27c9c58660dc1c4dfdaf35daf0cceca183a06dcbbb8d6c4c1ac52825d5ab5951eb9a3ba922b9d9237e2bdfe62677ddcc783986b9727f9b49f44ca960b9a134c73736e676b5498a0bf5a1c29b74bb034199258dd8554382b6b8d89e343a58ee4bf96219c99fd6dc3eb4587bcc4b06600ce1bb5863b6c37009c5bbf43e804a987692266b2e2fc02de14b66498c1b0cca6b2f16d2545b1888e8b9b38babf8bdad3e8699029ce59131e4612d8ca45b04402d1896a2897cfba9fd32b39fd0ad80a29dfa4de946455f02b636127de78f3f62553144fa238152c9615bd7cff23011289082901a595ec80c6cc070f507ac55c5db97829e93529499020b6baa85575ce86b9c1ad5a64af035b5f01797686b70b606bd1bf3aa92d1dcd8cda80c22a54905eba93948834a28c154c35cc388458eefa2e65135096d9867b3e07eb469ad1a33d2c353478cb2a95e954255af53b9bd71efb9017d3eff9e651b732b02b7c30c4f1f10f6542e6657a50d4f7f25fdd46c7f563a420327df3275341d5abb44f61303c9674e1f6b7bb323a4c71a9983004b975de5a5b7574b73690a1176cc9b4d706a004ed474df126642e633576ec8157a12e9da5832b812d831919428b8ecc14ecfc6a90958d0d3ec5ec33f536cfe825a8262f0916ab813f089fc47a0b9dd0dd75fd2b9166a287347a1c9ac406fd43357a69ef42167dce0cb01d961c5a4b2abcfcd77277b2d5384289c8639536ed15196662f00c4656a515fb45eb24289baa5135d06039a740a0a3505dabdd05209e6681f33e656ba676bceeeeef72f210bd388497ac3a42a4ab1280887494ea9d2d247aa7c14629bf3ef43bd23e92ceab3f9566910a91210c18166a890d8c37e4374af03deb88f96025f641215c13077d8e06809337dc74ab9468a306cd81ba22dc786bc4e22e9cc886784872872e1317e65b0d1fffc86bb2b87991241bc7704547c4eec2fd0805a2130e490b1714e035f8d5db48a3722049bfd0a8e7b00b1a030e514d472312f8b1567bad57230d07e9b41a75a45bad01210d567a81f4ddc8724bda808882e8722bfdee7894e7769e825bc0da6401234981f7f05a64e518c8a8b30f08c6131fb41647fd6a06c50a1505b2818547728438d8a03c37f1813a128285f285a282328d88bd2f125e554b440d9a0863eb54fcc4ffc4ff84f16a76c14f493da13c693f5c4e329bb8948da42d33459f4a83ee48239364151158ab680748404a116a0fca0264565b4948ea8958bd341e5895ad3a7fa746478c007d50aa2fada0436063ed45d75026a4831288692415509541c79fad2bb07b5878a84aa41e140e50414d0dfc8a7d01f507d901b94670d75f5443577a69ebb64374524f547e49303d51205c5863200a540c141714031350ae87c1839ace0a87b23e167204099a100a160559408b2b87a8f1d6a0e15020a03650785818a1aa5ea29568a0d0a1e282035086599e7ee5784a1cc50e250b9a7b8b50d4201df53624022103591f3647c21a9ba69dcda04ead4282f29050a23a1fa6e5091814af34f21bf4508b229d4560445bbe64c9db750de501b281650c675940e43bdab1a543cd4cb14c5895ccf6602a0d8f6e0d69c7b1d1d51506da837282c2829145551d859dc1a03d40fd4b38ee2f6d3bd490268dc9a1b2a3c142b50f70ca1aca15e500100353e9b900b41083212d427d42c54c888ba97518f928da4344365505824aaefa7a69b7541b54314b0b052992295504c8c8b0fb3a5756b0856487585924291879213054496d0285b0854360342bb4414b2502fa0964d282b003231d6ce96281f85df08407da0a8a184a0d032aaaf379b3241a1ac05655262855c89bb288b48a495892588a703e507f56aa3e27011f97340ad417941518d0295620f1490272ea2454734c227ffa21c9e710d1183733b994472fddabfdc74f0e455e58327957844e928994211879e9da440d5a09e4201bb5a76c18819414554412b9ebeae523e08f317dcc0164476b47e607e64544e4be3618d12e0c3816bb9fba856e2a8d65c39c380ca482fb2941dd5de985435eba88056aa350021e5e3341ce29b36e971c243c60fe1fb58d54ea65a3692506e33223fc96798cee51d6a4922b4ddd154024a7de4ef0b1c63768b7c5271d6ec52911c29c6210c91c3843a9c4496c65d42d42b731f458a95213d5b931a03142fce1eb45b18940754e035f2cdf02e3a6e7f33424be28169e46d9be0423b7787b4437c4dc963890eb7f89ce0da17a2c051e506f2d85213e1b3a976efe3145064b9be04186ae5a471c738d262905a552dedb7a41a588236f731950634e0240fe9ddcdc8e6b559f7248c62711d9588744c3b917622f31f40cdc12cc9377fd3dc75cec9e3bb46bc095ef8efa67065e0d3927846b008fe1b86742fa2c9fd5bccd061a385ce2b38c2ea5075d449a31c498c1d188eadadea93b4eaca61e586ca66d168110545c5e469189f0104d7026e5085580d3260b25472ff000000000000000000c098456138aceeb4a92a02305175a8cccccccc746bf76906d156fe226d9e226df80bef40bb0d350df60c6c67a7c69ba4e6a7091d416a3d19cfb93455051a8d20cb5978ca78e162c91d6881186340a00c32184192bd1ccf738c93f1e30cf8e28b13d42d82bcb9153e3aba692882e071b6cb5b9dff30f33412b1988d99b4b74564a8b6789966ca0ff37e4504c92b85cc63517a410bc2f8e20b31c24813d0388419c20a515f1082f49f73cc586d1d43b78320e5690f53ba4d7515d382209e86696b8d658e2a0582209ed42d6764f6954e186584d10282010604316eccde9a2d17fcc180c61fc81b25bdd27fe05fbed3f003b9d34287be19a6008d3e10cbc30f6b62d2d1fd4c830fad4c994ab767d7a498a8c751e5aaa367eec01ab8c0b5f6408e52d0209e762a88b527a8810b9c861ec89d6eb56637940ea09107f25b5d7969ba7acfe281f0723a5177512ffb5f1a77b83d1ef5a03475448307e3044bc30ec4a0fea9a3ec71496a474575205aee281f5dffa4860d990268d0819031e5c187a9296ef470e08b2fbe0883c61c88f5b17cba83ef0fde6a39d85e57b32b8b5106c6418be832cbf46caed71ab1a139629c80030d074245f54af789ee0d24d7bdcecfeeac39185d20061914a0e106d2a70cf95eb9da898f551b0819bf3b0e513954d9bf026f01150d361877a9f256916d6a2f6739aa0ada1d2a86c61a08aff6661ef5a1c1e234d4408c26ae9923d559f788461ac82935ff626fa68186a27186721a6620f9a5e74cb5d4393d47086894810334c8403a9b1c8f472d795a4a630ca4986bf19307e3ea1d6220ed6618d7deeab4a08581d8a2398805f1928cddd00003e1e6f2a5ad9c3ff6188981c617c8c1c4e6858fe3687a3f6388018115882186056878811c3a77b26d3533f02ec88007687481249ed241d2634f9d9c2c62600427b0000d2e90bd2b7a98dd2317842186061e004619618480c61648921bbe7e527d177c400434b44050fdb191fe68b35807d1c802b134ef479a73f2de206381a81ea7cda76439cef1a5710592e6ecb765df5a8110193a98cafefd99f715338d2a90925e9e4b6ec79be1830615b81069b76bfbb6e9f8fa0f2f5c3386181068c118621c23d09802795cc57ed366968f3aa62105525cccc17bdc41bfe7880229b5e7c830bd6840811c05c9f00d1ec6501d78041a4f20f85d5c3d4bd9569b3286182770c1183f81ad319e3140c309e4ec38b4b8921d4b3f2d6841186394c13546196454804613b491dbeabab46f6f158ffb831e4f62290c4619ae812fbe00a30c7fc1175fd4a08c1ad06002213bc51c87fbe99b18b0e0c778c1328d25102ee79f17f5043378176835808612c8f371d4a041ba3f8a396094e11a4030caf017600dca30a49104a28d79545a99031a4820f9e66c664d66c79ce917d03802b93c76e5944246646c842e7e24e6fb97a308845a4dbd740b8f331b229073cc8f6aeeeaede06bc56008c4d83e1eb63ddebb384f05348440b610ef913a2fde7bb2400c314ab280461088f159c4bf3bcc1ee7edfc0364400308a4f038dc8fa596294a2f830c5c018d1f10ac9255ce1f1b242b19c609838c30b8c860411865c42001347c401a9f3d6d37f56dd5cba0d10392e6759856663a7a7c80b175821394f12918438c6357d6c7cbe00c0d1e105eeb83c630a71166ec8260b12a2f43cc83a7ac0b7287f16fb283f092517341781dfb0b2b99e6649a810be27c676ab4cfeae1036f41fe9c234ce7634af71e5b105cbfa3b5f871ec71f55a903a4f643e990dd53968b1ac5767b9da4d9add78f4f9e6539e858b66410c8b994e467c5990e3fc602c3545c9af1d0b72feb0d457deb1d6535890b63f7a50e1b25790c3c739dfe9d015248dd229e656b6335a41d0cb9dfeca5159df398315a4ec21791ad36515444d77bfd071f4610c5515c42873b721b7ec2e7f5241ce1bd37cb6b7cc400541722d97b447e3a9d219a72057a7a5a8512dc7ea22166698825c69de3d59eaf428d403cc2805f982bfb467a6c4e60cc6563919648451860bbc055c8332caf8315ea031a8c1188781119c600133484178cdcb51ea6b14e4ec91a51fee473fd619a2f863678482f878eb5e73f4575f18cc000531f97dde3c327ea296ef92b12a7939bb56fbeab0734f102e784756f8741d21ed098a8cac4eac51d21593e9c3f49339d2b00c0438c0093a769c16dd9b7ff1c5d961c62648ed159d1f59471fe93541ee9ca973e4dee8d54598910992da9ea4dd8baf5d3a031344dfd45559132a1da72f414a7b1fb35e35e5bca725889e1d3d734ca6614625489e2dcb05750f534c770625086ab9fec1cd63cc2408b96b95ca83e8565e24410aefdb890429ece35cb6d289fd784810d7c7ec2c5656d4d38f204ddfd6a67d98d6b6750449b47e2e7dcce139e78c4634e9211675d335b532d9a13f91e9e3bcca19419e8f2f96e65fa7f5db1f0c068811819ac28c45103778c8afdd4df1605e618622c8d551b9b7e6767fe9b130231124b3609939b68e3b9bbf3003118490d5141fe7d8198720a68e3398e6cca179cef1608621c8915ea956cc91fb49f7168c210604661482e8792fbd44479fa247d9308310043ddb91fc92670c82b895b4d2cd7e5c5b7907f00c4110e2f2eb497f1c73981108a2b668c6b017a73a1e5661bcc04fd002327c066c802055188dd9816f8ac9d2197f20ade5703d5d54c50d921fc81d1ea8c71b3c5ffc75c08c3ee8563775dd9676e3b21ee5b86392accdddf08130fe31764cc333f68079cacda94e3d0ac20c3d1037da772244fd6b32be8c1a94b526ccc843a76f79bb5c2a639031460c1c30030fa4b49fe33bcd8e4e39e30eeb5a4eaf0eae62969131c30e64cddb58bba7767b65b9e78e4cb162b2b35233ea505f180a66d081e8276ae1e34f9e3a8e9e3107826af4cb4eaa39cccac88198263ea778513cce678903593ce907716d9ffe0287dd03cdf18dcfdf40b8eef9bc0e6337902c5e3ebec34aa1f5b681983fccb94c1bb281bcba5b55e3d9c3c8dc8c3510fc834b29e66a5ef6a8196a2086d3ce8faf39a58fd78c3490c74dfe430bfa1d35cd4003696e4377d3976571c7987106d29ebff9df651b4bb918c0e0f38f3058d0812fbe78410b0e061c1833cc7033ca505fcc208339e623b7510fdfcf408c3152801f0318fc038e0741d9c700066f4e70bc0c5a0533c640c8aca67953ec8881145dbc73b87d3e1d7b184862a135715157b739184822bf17bf6dddeff22f103405cf8ed13f8e7d5d2f10733d8e2183e6d04c6e17885a731e6627910be4101fe7c7a934d3e8b605e2aaeb5ccef832ef282d10462ba6b1fed7789b2c90ad2cf2d2c7225e2589600616081a677a234ec118df8213b0c0c3287d8119572045f77f57a60c7295b102a9fd72bc731e7bee385a05927ffe0bea62e2e32a1548963eb0adb6e858bee4291033e578e521439602f9eab3ae7f45b7ba8f02b9ed52f420938102b1346e4ac7ff387b684160c6134829e4693eb91496e9760231785f6c0e463e58b401339af0cd6f9e5ff2e03b4c40c47265ed6c1ee6fa2570211ed96c5a094e87498f2478f361fd4b4ba33c1c09774571cf51d6472878a97758a91b36421f87861f8fee2a5959047c3be8345babd1d522425b2d9f2cb5c710d2f8d216bb16559a210462c6b49e532f130462e7f78dafcb0281d0ee192bdb4ce5b3f507848ef151ad2ef3672f7de057ec98e34b1f6bc18c1e1c85ffd833af378307c418dceffe3d788b8f4300bb10802e2c17fa850904808b2c81006e41b0fa4ba9d451c4aac716a4cb389a3f4aae96bd530b62d0d473f1336ec6ead082d421fde3868dd3b7ff2c48579b2dba6e644148fbb7ec605e7dbf231664f10e4f4aa3e5c8a50316e4f9ece15993af20447ec717dd781e86ec0a7256aff7b8a29a7f465b41d432c9545db2827411b167a3a1005641484b1d7f530aafa13f544156d970677bf9efe145080a01a4823c9b37ee71aa33fad684005041caf13b6364953186182fa0c1ab0504700ac2c654bf11e9cc97c380b12718a38c17901863a8004c4188cbaf397de95210c57caba7669347b7a420e658cd36d1397f2d3a0a72f8601a9e655110632faf7b47eb62658682b45933cb73cef972212848c95734bfc97010c02748fb399e003c41fc8e5dbfee5f2704d009825aa799850f4e1073e8fa9b20e63887ea7661bb82003441caf96521a6fb995d0cb8601402c8c49a3e4cd075531fe5d8b9735e9730df7f3c8e49754b10a2e296f6a78e4a9063e5f19ad4fe0f3e0a250a2d2bff121db34924dae6d95272936c61210049dc15a683a97b9ca4e365508a104024c81f634e1d2e53ceed40829cdbb1ae7ed9d691ba9310c02308f2d15d343b868dff6b108023c8e9aa41ebf3a6984b05d00842bb7f647a58ea5ed1b920031fc8800d07d8c8120023cce93b8fed759c45907e534ce1163e7de15104396ef4e497459cf49f084279d45857d10ed52382dc9bae9fd33dca2d9b4390c3f5b2ac1fcfa9d31882b879b5d28547dd61fc280431b6edc5b5cc9552cf842056fc9473983cc5acd80e82dc49f3e3db5e1084d19baab48e2c7ac64090223d73732f13c0ab97742e498502f8035153b725b7189a322f01f8811c6d83f56fbfdfc6d50701f081f06176f8e1e6a5213ff6408a4fb2e992f16e1eeb8124df29878eba238f03310f44b5d5fe6449c603a132bf83d7f40eb7870a7677323e197620e768e6a27986eea0af03d9cc9357aee859d3830e04f1bdcbedc0a277650ee40f476aeb633a47f7c8816ceef9c78110629b3d880e1c881bb335e5cbaf29e70da4594b1e85dbfcdeadb881bc57e7e12d19d367a50d44390f3eb6a9760a196703c174aeec7bf33510f6c2858d4cc9fd6a56c3fb759aa3f4eb700a200d24a9ca1ac34746032942f5c26d46e58eaf211ebbed071fa46620870aea66177d9b3465206eaa6efa8e73f0e14e3290c5732096e38397d46320071e69c667c95c1915033146c744a6d457336118c85a7924811873ea50afb2ccb2030904cd9e661d3b2a68e50824f1f39cdebf10d98d4096983513af9cb8bc08e48f563689ba945b4e0492664be6f02190ca529a660ff7576d211035f665d36946e60802d13ead5a9aba880e0472f775fabc5eceabfe80d83296c93c8e6d791f90ee37f4ea3a04e801315af4d698c3541fe508c003427b740d336f17e4b03f0f64a4d50539e64fa17fd397e614e7823459a56a1f769a458c0b72989e23cba1e71604d38f74b2c27bacdf16840e2e5ede768be7a616c4f5ee6cfb118fb7a10529f9c60fee17a7736616e49cc2d2d3666441c8f1f0d43a866d78c482147f3c6ac66f1eb5c082d8299597ac82665a5e41ecc02f4a8c69a58f760541cf3aacb9689aef6e05f1b22b8cbbcd0a62fa38ca9fb3dcebb4ab2076ceb12a589c78abaa20cea5df6c692e6a9fa920e7384faf73c731e640541065e3c64acb3c05413c8ecdf3e6d4486f4d418e345553eeae14848a3b0d5339b62e460a72cc9d0c19b51d05b12dc45cc7942808a5966b25d3868220529b42490b0a624cd797aab72fa3ff098207cf1b3fcc17fd3e3c410aa19ea325bb1304f18ef113d6b15ce60429c5f71c4ffa9f2abb09b25fe68e3be7e649ab09d289eaef5db4dd8d66827ca122cde348c5876282bc71392f53b9638ebc04c95ebe523497cd0fb4044953c7fd725825c8f14ddb7a2ca9ec965282983175d8183c93209464677c4612e4cc619ebf95a5892341ea682b8db9e54ad9438250973ec6bdbe15dd3c82a4e2fae1bbc8edef08a29fa6f839763482507996a37e1823c8abefe1fda6b3987d1184ea28bf94ba46ed8a20dee5dae8719fde9708f27bdaaa5cf23c891141c8144436c8ec7ea5872094b57d954b678fa521889b73f0c9ef14e4a310c47a1fb796bfec21128294d3a4837a5c0e82a8f14f54df1304b9fca6d6e338c4931608e267f258d9b3f3930182ec9ea4a3cfa92b452bff40fe301d68ee78c5a8d20fe44cadd511ff9199e90369a7925c4e153e103c63de94ce3a67660f64ad170f1fe87a20c7b31d62e3cd03c1bec3b23ecc588fc303b9ef3a0c9ebdb2c7d41d4829a7438de6f93215da810e75c23a9075c377bc1b257b3d7420f6c570fb58e7404e8d59552fcc4c460e04cbb92b156369943f0e844e1fce579f458f337020594efd96f1c312ff7803c1afd4f2b279f4386e2047cdd1df35875dbcb4816016fc7e565d2d763610f5c64cd7dbab52d640feb8de2acf564332f599e2d99c06a29b86ddac7a0f2f1f1ac81f722e1d77dc1988dfb7c1e2e7a84ab3670662c59c9f7ae3398eed9581d4fbef81bd87d0308f0c24bb4fb116637be8f1c640789d30d720e229079e1888b795322b5fde7d776120f795c887561d3263070662fa245529d7e68deabe40da8bab1b3ed7c27a2f90e3283be5e5d105828876aa646def9dc205a2f5a5f9e83b4fabbc057225fbcfb19767fd5a208cffa71c8ac7548d6681dc510e37a91f768e8a05524a5f99e226ffe0d22b9065438686b4a8b75b819861773bf3a45f38ab40700fd1ba8ee897930ac4ebb037f476943994532076e8d32bbf162a074a811493ec9f481705d27b4d077f9977320705a27754f2bd7fe3d1b327a0215a9f7c730279cbea2cbc751c3ecc9a40a8fcd61c6daa54bf8c09a458cffb1a83d7c6644b2094768cef41c73186c894408cb1e7a1a54e13bd4902f9633ff1769140bc1c6a788d995953e508043ff538ff9ba3c618819c35778ee7cc3fb4bc08a4f0adaea989408e57a1773e0f81d01a3df77c98a5f61502c9d2d66719ffb0b00681dce5e189aeedb8402077944751f43c8be607a4d7e4e639f0f7f5481f903f4407aa1afba330f680acd12646d24307cf0078404cf3dda476da9fdf0529c7b9c4bf35f3dcd30571ff6a3fd59d6eba2a17e4f5787f3f47df6d1e5c10d5df34665ba33a6e41b2b4f05bfdd6957e5b9053266f09cba9b673d482a89e642b3b3787c9a60539233547e529f9f5b320e598e2be5d69265b16a4928b69dfc5e3d734168493f89c65ee5b42840529bba3a0a7c9bd3faf20a5f8e71bdb0349d315c4c84a155f4ad3824420a8d10a721cefc85eba9ac90a52c741cff2782c972d263357413aadb9a8f1febb5ff10f355441caa12d15911f47b7169b438d54904f3d3f3ce9e8e6390a0b0418871aa820bddb9be6bf6f95354e410a15fd1f73766b0f2f621c174ca08629c85f9537e64c390051a314e4dcc15b660e37793691fd508314e4b0cef22965d0de9b6b8c82f8e97168969673a65c3544419a0e63c7c93b478d50103764cc9abb3c5a32f8e20b0e4cc00531602c3cd4008522b5b1f1d5758d4f10aedc54dbf2062fbf1450c313b487bdcc27216f9f4ea812df615aea6ed336d51d7df09fb26b708218f769e33907cb1ff49b20ff6fe4e9c781ddc657431364e9ac7bd12b8fb2dd353241d6b9e8ba5369c53fa70626889e5394c7f16da8fab3c625c86187adeef33f579ab104d142746e4f1f9cc6116b54823c192ed8c7e1a3b5cd518312c4be1ff329e994c3ca93204a48ed26bdec0ebdab21097247c918d472caaa174782f0137f52c1e3ec870b09a255eade0f32f908a2c764d2b2f779b73882bc6e2a253edd919d55a311c49bd713ff3085cf69acc1083be6be5ec3dcbce4a4ec36cc858fcd22634e8d459033e65f8db5acf617540459234375cea9aa9108b265cdb6b757410479a3773acd4187dac56b1c8258dba931dae60c410c5ad925d3779ad1e2408d42103c4aa355ec06630b8f10c4dcd1058b513b4cb51930b6b034506310e46033d999c771f43e05412e97ab50a933ab747c192e0804e9bd3d88d7d3e85f0f185b0d08726c49b54e2f05c6d6591863948178f8f01d7bb4b12d3f90e39dd4128f7e7f6292d1076286060badd3f181d839c8b8fa25bdf1da3d903e5d7ab4dcc8e820d50339a5cfb1a3c52632ab7920795c9a73e4a16ec4777820c827ff900d899efbdc819c520aed7c39da0ea4bcef49dbaa37ee6aea40cee12ee94fa7e039ce4107b2a5741473cacc010735e4a071a80187372c6b1fe56253df2117590d3790e3b27ee862b9528aba13a8d10673ddc7e079391e8c2d66033174103b7f611a2b6f30b6346d0da428a35ef79e06638b6ad540caabd816377c98197a0758c082302af0c5176938810bc248c197139441460cc608400d34381eb356b2fffd0ce4384cf46914cf0c870aad9f2e18db801a6550d4a443d2ea2bd6b756edbf642045f338c60e3558630ce40ea3bde7a293740c21126a8881545b1b7356ccb5f3698d30907c24d2c3f128bca72b50030c248f99c53e34058c4d810bc248410ac460c1921a5f20b4a61c37462d500ad4f002a1e3b39c3be77f8c8363885186b7046a7481e829e477488f9e273c2e50830b640f45ea62adc729a61c1abc7141185819a8b105a2e5b30e3ded23e99bb5402e8feffd53792d97919105c29e7bae0ed3a7f41e6181ecc12d6435fe98a407185b1fa8710552554af9d8e976c7510435ac40cc71fe918e39da64a67ba0461548b25e9a52b414f362a521a8410562f889edb474595edd2990f247d61f7c9c43fff8679918619ce00520a82105e2079533fa9a7a16b36a44815063975c654a3fa7540d28903ecaba981d5cddbb19c63958138000045a4086cfe08b2ffef033064da1c61388b9d5b3e585cff34924420d2790db652ba7ec2d1d5df9c517d604a2b5c658b51f3e6db0984056f5cc1e7f0e5c34a53596400e1be62635690d2550696ed7ada1a651e62d919fb9d7bcb26f359240c8b2186cfbe242548b048247b9715943e589696a1c81942dd47eac38d92c3519358c40f6de289ac6c3cdff0bc438c8b87a08d428026973873eaf39c72c27d6200239f47c8fcc2a5e6308044d3dedf4a1b6836a844092d094c2b22debf858230804adab9c6393a9ddacd40002e9a35ce9b5ee996d651abc04bef8c2d0e0c1a8f10362e5ac4af7632e1aea1a3e204e67ec309a47d5f57a0f88e3d39d3cf278257ad7e001616ed1a1f162d6153589416317464317e46ce1961fa249ea6268e482fc1f7705130b17e4706c673a588d176b1ab7b005f1efb285b30f291b99462dea96b5b02cab880f13cfb466ee394c3963b78516c47835dec1050fc3444f6316c458f1bde23fdcec0e1ab220af87fbd1eae76341bed68c9534ceb0208b0772f572b9ed018d579043e8b5c796c398c2423fa0e10aa2af975f8ee38c45cfb782b0d71bdf038dcbf839271964f0a108a0c10a62140debd0166b8bf9096a1504d5980e9995d2ca215590f34b8731211ea9209cdd7e8ede435141fed8b6b3f71ef754770a4b4b4b54ba5d3a3bf96a87214653fcf7655ea1592e05a173cce71cd5e70445461962b0800c2e1aa420a99bcc5db07c14c40b19638e2ae698ddc1464314e40f2c9ce9054f60c30316b0e1802fbeb8018d50905a3a6365f774f431ebcab431c8d85d030d50902b274b37cd3f41d4cfd172465a3c2cef09f298bff7b5071924fa4ed0b1225f562df15eb11e9557129f374b1ec509c27c3cd7298e4af5dc9b20c5a6a8e1e28f6df24813c4b60a1630b6641086b907345b7526c8d175a77f6d1c8cad1888c1023f410dca096ac08217e02126c8c1e628b3e865587fe612c414dda666630a5717b70431fbb7e58f2e45479fd2a80459b33d684db3c9a35018658cd7c01734284168c9cb1e37a79836ef4990365d77caf13c49103d8c9239dc1c271224efb91c75fa24f59783042988d5885dabc6ded023a8aa8ece682f7b02410ef154de7b5ff97300826815b35e5877d369fb0fa45909f9d0b5f6037155e7a3e3555c8aba0f244ff59a830c8d480ff381581f74943def7b20b7fc9886859ba85d0fe48f3bf46dd6fac0a3390f44af4fba271d64f038c603e9af925be7ae1cdce70e84bef439b7746b076267f12fd33ab5d8b60ea4bcec39e5f0573a1063c63c7ffd49573e7320c58f376e597aebf022079245f31c47e3389083b1f85799e1d2bf70d823f1b052b9ea1b0c33bfe9327e6e2067b10f99fbcec252da0692740afefb9f3fb3a46c2086b9fedf909a7d316b20854b8674fe5003e1c627375af7264d4943bdc177eaba4503c1a793f8d7760672607fe255261f6ecc40cc174fe252fcd64e590652bcf58fd43d642055694a152fea63207794fd76ab34fc52500ce43899fb1f55c2403afb78ce32f84b46c140bcd87953dc302762e117c89e4365dbec5bb6df0bc4d70be191c5dcd2778124b622d7fe29bac205f279b054c9d26954cb1648f351b070f34a315a2067ddfd78d9f3e5ea592099789ce9634ed5ed628198638ba6df4bc68dee0a24db1c4794f9c7493f5620be5e775c31e6733355200792a5693b43ab6e2a90c2f67728aaf6faf114c82a1d79e5c83229904c3a6a31cf8902313beebab09d37ee870249e4d433d77f02395bc34fc71115f19d4076d90d993585d1dc3481e431548c7d6ee8cb308154b93c52dac385eda825904e6335d5863789b89440dace1d788c798fe25a492075c88c1ff7ca61cc151288df5e95639c4dbeb13a02397cb51c897434ab5b1981f01655cbf3c5d471a88a40b4fc9e39fb44205667d4cfd1474966aa2190f63c8675141e597884404e31f7dbbe3ee3fa4120b7aac6b38f3c7ded4020c6985e31eba334cf0f88f51bfebf635f2deb03e28bfd78865ed78f1e103d4d778b7ece546a0178407e6d0fa7c330bf70918d5d90be3d2a4d641ceb38c6862e08d75e15fc4e3788546ce48238219d739c335345cd94079ca0704152d1f46f9791d470d9c62dc89537a545faa94e69366c41b85e893cf198dba80539f029b3eb94539f655adceba9f3c325f730b3e0bea24e25aaa4bacbccc2b2fb76b77dd89997053907a55ec9e5473d6c6241ccf091079583ca1e5b0a0b928a068fe34be99992c43fb0f10ab24946e5eb2cde774957103e52f6c3e7681df5c7462b881d373f8cd132efa0831504cd0ec35b35b5365641ba79a914a443ecaaa30a52bda74d316d6ce7aca682ec92ad731b5ed6e30e54903f774dc71535364e413c39fbce0ee2c6e36c0af258e638c2e2f75a2730b68e3f0bea05364a418e66c7f36d4383e78d14c496ed313d4bfe6139a320a96cf6e9e4ddf1e74614e4a83a47fdcbd15038e131a693d79e111485dcc8a7967e06cd1b6c7ca214bb6f9578d578bbb80b1d3b73c7e758391b9e20aca614b3a5e754393a3a41e8a0562af3348de84f26c671c1040c0bc200c301cd093569f0143cb44375dd04216af12e5e2e36b6e9964cb4a3c9d3e4519835418a71efa64a3d8a282f1387e87a342a9f46e3620313549c47d8a475879be7ea606937fc87cf25086f6a611d3af29cfe470674e0aa0c1afcd9b004a952def0405364ea792a414ecdc82059b14109f2dc8d477dca3944692641f8bc8f1f73757cb13d24410a3b9fa328abd6b2151b91207b48d914331f26d98b0d489062a88aa9784b1d6c3c42d9b82899f1343bab7b97cf412f6eeb6fd4606c6d276c388274a22f9faa5f30b6acd2c4b0d10862a8b464ea24f371c65ab0c1089259caf414c6e36e778b20e6f78bb6f4e9b72e5404392ae675bab496d64289b8c34c3ddccbb24ddbcad35eda8e68eb750f450441e3ff484efac6ca151b872077698735a2fed1f78fc686207666c9d792f398520882660e6e9922a6800d4210fcd2a5b0221f45f86b6310c78688ba5675e80019d810c4a1c0462048398e62faa67efa381601418ed963b5e439364f5d36fe40c8b3ace2e1928fa7e507b2668fccea52ec021fe304297041182e7081bf808c1390f129307f82e34f670a1b7d20be477733abf6cb22bfe00531e00351f644d3ab79a7d2dccf400c16980d07a0c1c61ec85fe271e79395a5cff540cef89b263b1e437a65230f64f968493f16b54f36eb256ce0e1bdea785b3d750090081b77204be5c8e850626c2a67c30ea4de6471b5f257cad1d8a80331b87ca49a7ec92b54f26dd081f4daf9956473ae6d3a07e29f8f6f1655ddad0f39902cd755ec736473d9230ec48df92b7e982d1c48be9f3367a8d2d4d4bc813c95c3a5eeacf4e29d1b8899b3c2c9c766a30d24effaffbbce6cb081582b6179a36afa47a2ae146cac819cdbfb3f6df2f018c080d850035163e7774baff1728ed948032975dbc36ea6cd5deb73a0d140ae1ddd98ca73d8380371ce5f2ca6653310c36cba937e67a30ce4e4ae396d4e36c8400eb43d7476bcb6413f520836c640fceec0753dfee869c4b3810d31902a7fa6e44cae74eae4c0461808ed1e7b837f5e69b558e03eb00106e2744c72f7de5bd9f105c29dd4a7f09e3275eef002d12fc5d50f3a050836ba40dccbfc723699714fb3c105f26bb8fc21eb525b20e597ca9b22b1a9c1861688759acc62ea3cd071b09105f2947a1c6aa50e3d858f0542a698e3e3d5b2061b5720c4c5ddeed4cbf7b636ac40cc09c9cc18e72fbeb0b2510572e83495bd16bb208ca25428258fa962eecfbe6cedc0c614481f3ce2b492d559dc36a4707474dad4a545c98448f568a67b947c3fc67558f0c5173db01105a2548ea1d25d658eec800251722b7fdcc1a9ae5b369e40a8b34a1eb78691afdadc1865bcc08613c81e5a292ea4a4d6836d3481d8ae192773e8a19fa6cc061308eeb1e593ccd1a3e7b4b104825ace8c6fb18b61aa0e6c28e1b054c572b44e02394cf6fb293d69988d9140ca8f17bf6b5d1a6c1c816c5f9b3f85afbef5a411c8d17a875d6b8b76952e02f953b2fef9985fb92b1b44208ccbf55b2ad1ecd11902593797c79d932e0442fe76e8876bf4de1d60230884ce71f688c614ee360710081ec97c5c9a9a9ee707e43871c937060d1ffbeb037238aee7417b702fd1460f2a29a9d3b8980e8bd2508f3f54bacd7b19d9e001f1e4c3e7ca5c5dfa7741bae8b5fbab539f7a5d103554fed49a9f3373e4821c1ea690c1e3b0c3a6e0821cabcfeeaccbc8556ef1f8a4ec7ac76d41cedf18f3e1f2079b5a90ae63d9d5e76df09a16a4507d51eb3bc65cf12c48b3d6791d2ff4942b0bf287cff18fe7c71c1a0b62dd7d764ed391950a0bd274b61211cbff5ebe82ec262b7bf33b1e7cae2047151e3dafde3fb05a418cd60f0f8f0e2b08d6d5e2b9a3b30a52fd74cacba939ff795590aa7398f5f16d8e52fe53414e6139b24b21ba33f3a8205988ce8ea318fab2e54f41cc9b4deb165f3c57de14c4fa0f339505d91c2a5f0ab2c5b51c7ee4a420e593babd1b75e9791444ab0fdab7c12ab82c0ad2a6d431c723ab8a361484afedb4b8396c1c1314a4dace0c0d1a3a637e82d456169ae25567143d41ee8e2d6decfa381fd609f27959eb6b324f757182e8b93b55faf2fba6df04396dd48d5d371fb3f49a20476d76eda82f7c2713c41ce6a837e7d50b9a830962d8e660534c5706cf2508a329f8a59cbab97b4b10a7ee5365e770336e2a4138d19c038fef4cc18312240f3b5c77f5ce793b26413abb8ef27afef42309f26be5ae4e9648105ea38746bbcae7154890e3f02ba68ec31f410aab9bc3623addb83b8214e37de86c7923c8d2713eba53d6543623c8a1391115d63b98bb08c25a467b8a924cd92a825011ab1de764e93e3411e4c0bbedd35c5acb8908c27f4ed3de81872047b1381d366c5e360479f3454eac5708729ca3ae98b42304e93bab698fbbbac706418c312c7d6477f3a909821c79742c559a0904395fac798e3d26d3328020ced7a7b8bd4a5796f90329ebbd2519fd40ca1422c38de7db8b4c1fc839d40afe96a309cfe103c12537c71656ad42bf0752c8d7471b3adc4cae1e486963b8fd343a996b1e081adfaa952f96c2c40339a8646339d2c247df81d0396e5a904fc94cb4035935e53d9deea803294ae74d1e3a909267ffe44ec67a6d0ee4b0e81f3a9664bcd0210772ce213e5d5e37574e1c48591ebece0f07729ceba15954de40deaca19dbb811cab3ece21d3a50d444ba1734a7341731c2e6c2054efb9c61483e6ce650de4b06334b4e46d2b59d440cc7f51daaeb6ecce9206f28c7bb25819ad3956d040b8d0f2c158aa9c8164a531eb48c50c84f30f3f1fd8efc7be0c04ad78c9b2a9476b4f06f25e6a4c66dd2d8b190329e64b1eb685f4ca8d1848e7b13a63aacab1728481509ac2c79836b5ca010682a7ce87587d12dbbf400edb2f2ec60d973dd60ba4f8712ccb97e285bd5d20c7e51d35f9c90552f6d5a6b9bee67a0bc414b24d2ff3d7c4a805d2974717a3f4270b84d5ef3889a68ea3cc3916c87158b66fb6f915089dbaf76d3bea4c1e2b103367bc28bde729fc552087b3943359a70ebea702d1b3f5645c9dc8c73105629aaacb51c7aa581e2d05a24779d234f23af7de51207810ba25d95b7e7a4381386f9672743bdf61ee2790b38c6a7c8f8aaf6b3b811c6dbfb86e560f6eed2690bfbca30bfbe8ee239b09648fc2746c99ea25102e8885e6bc8e3bf695408c631e47ed9dab0306100229ef65528fa691f79e3481018240f0c062647554cecbf95e60002090da2e67acc757998e0df00342ea78d0e01f733b6b06f00131c6efe78a3719a007a48a0b4bb1e653d09a0dc003f2268dee5836ea9f6378b10bd226d5eaecf7571d7a744170add9941d7feeb88e17b920c5d0f217cc2f3ef00217790bded3238f8f677fe7425b58f5a17a7bd94c7f5ed48294fcec2dc2f38216e6c52cc8591ee6ddcaa13bd47b5ec82263d17d744d4998d974ab5ae8052cc81acc3b76625f727d7e05f16b37e5bf156b0b3318bc70457ad10a52beab8a79d89185b4c90aa255b0fcad82e8c9d2445590a6f7e38f175ecbe3302f52410a1fb7544c9d3a8117a820a4a5ecd2c155b9253ddd8b539063cc5462ed7b610ad205b958b5f9bb45f6a214a4d41a2dcac4ec7fe40529481253d1e163a75497f36214845b2f7debcd9979698117a2e84626cd2edb2abc6cfb552d655ec250f01726f00214fc09e267dbc7b14bb4079917313082136cc00b4f1036ed46ab8c9f4e103ef0f6983ec65cb1524e10366bfe48fdf3eee56d1344f3d8d2d24159f8ee35916eda6ff0582e9920e8d646b18f3986ca3526489b3d9ccf94f325c86b19e93392294e65bdb0044936e8a6c6eef0e2642a418eb6838628ad3c5d9102bca004b9dd73ee3a73c8a9ce24889239c73ae868491063a8e56d683512a4e8521ec743d10b4890bf6bc7cf5bbe82588f20a47472c9efd90b1fef08f29aa7b57a4a234813319b6275f869d48311442ff5edb1bc396d7a2c821029119fdb2c8a2078fe18a3578efb3b8c27829c396c6c17951141f03ce9483443e5bdf810244fe71a93e17332cf1a82bc295b5b08728af0147f344dc5781282f466b55a1d6610a49cefb38ab5e7f07308822c356a39f4f08247848120db7b9816f62c7fcc1c204821d38256f6f70fa4d2fc31fba6f5f6f0f10341ee4ce4e7a36ffcbe0f848ff7632e77ef7f6af8408e8ccbb0ab8c9d31650f64a94a9e991dd703e965efd264f799c49d0792b8879bc6b28d07427b58eb6b1fd5a798ef4098f9b61cfbbc98e3b50339ca67c87410eb404a0f6e7933aaf473d08194331d74eef03278c8e640cef5919cf94f4978e440ccea1e365327710f2b0e44ad57ebdc3e9a7a8103e1eef3486498b6075ebc81f0d15bbe16ad54dbb163f0c20da49391dfaa1443c71ed406a267548fff7083e8051b489983ece5b4a96b5247f4620d9ac56cd6c19ea586fa02bd4803413b7da0f13c48bee4885ea081f4d991f8df873c0d1f885e9c819c7647ecc31c63d4f2885e9881d431867fd2f4d1c64d8ac18ba3ef9ce9dbaa357a4106623697575f8fda63d731903cc5c83817c2a2552cd10b31907f343ffa141ec6f00d7a1106e2478f95cc3f59660c045e8081dc1a22439947b575942f90e39c63b00eaf7b81986d42647c3ce638d75d20478f769b37485addc8059287565d4fee7bb105e26e8cd3f78b95d2f49d175a20a608f1ec0f9683c59c17592064fc56cab697e99c82e80516483515db2e9d17572084ea5887d9a161c10b2b107ccafd6cdff3a20a841af97075f3a3ce1f4785e38eddd11db7e6c514c86f39f8f5d0a3bd9042de9d7673ec38fc38f4220aaa48856b989788666c45861750d81278f104c276a8ad91d69b17760231a759d08bb3ced1789a40d896b1f5bc14cb15670229f7db99c7a4174b207578d0bd3974e871e62881542dadef35d9152a2609c4cfecdafdf0f6ca72788104a2590cefc9827f04f25e8eaaecb91b8194a603ab0ee171935314bc2802e9473d1ccbd1debe4f04a2674f1d3fd45dfdda8740d62c1f53ceea6e6da11088a96396908ef2799a2c08c4b618b397e886f9090452b278e5b1475944b71f903d4fdf475ac1be2bf2c207c4ad0be9aa31f8032f7a40b295378fccd290a93510bce00129978a76a06a961e7bee7641caf04bdb7b41d64dbdd30551edb347131f7d43b8910b52864c0f2e32afe5eb385c10d43a28cfd71cf33a7be316e44c17563b8a2d1bb6dfd982ac61fbe38ff39f4866566db8510b62a6c58c1fc4466fd082745e2f9a692d6ee1c62c88a779be63a8e6afb4c882e43b6a313ea54dc71f2adc88457d710316e48f2ca579e315844be17a3da86cb9821c83e7cdd261bfc46477a315c48f1f3eb01cd3af7d9415248f2753da4b278d6eae82b81df7c2cd73cf6879431584f249ab9434dcdd89375241f268e77dcacf2a549035775ced51e7745c5d26b8710ad268c59d87ae212c365390ed3ab450398696f3dc2805b9c7df3ccb2a07972f29c861e6dc471275ee611905f92dcf988f47b1f3ad28082299ce3ec7a0ebee85821873186374bcd01cc5102808d1b166c9d45116ebf30a373e41fe28c7f238fc28e6e0367a8220193fd2357239909b3b41fa74393405ad89c898e5043908958c8f3a872cf3d04d90f77292d11c7f3253130451d7988fbf64668298d9af348ff707d79c4c4c10d3aa3a4adba1c6747ee625ea8b1b96a82f5470a312449791ce2bdb51dacb71460992ca965d5e0d97c5e3c708e394046e4c4212e4ff17cd1c684fb4a78a182c48c1095c10061865843183489cbf6eee512e7830c888c10b36053720419eedd84ad93c4e59078cad32c87016dc780459bd56f673760ef67f30b6ca38411847811b8e20a6ab2ae2bc5bac5c343b4c29adc2489e588c6f348218a3778ee3a67fa83532829cd472cc296e18adfe405c042987e52f4bd1deb01ca222c817ada265ab3011e4d862d80b1a9ba3aafc1011c41427a52e9f8d87207649c741e5f01b8274b21fc6cbab1f2f640a414c352e752395774d2604d1baad323cf640933e0862078f3c47ebb3395412c10d412093f1321f2d95f2956951fb1fc4ef7823104497148d51c70308425f0e3cebbaacda5bfe40ce1d4fea873f6b55e534df7d2068c7130b1b3d87b30a0e37f8403c8dd1118be73bdab1873f790e4f13eb81dc335b973f6ad563c90339bc7768b6a1e2811c5b901f8f1de664f9bc710752bead0f9f4eadaaab1b7620580cf5e9e13e10eb40d08fd1c737473662261963b84107f2e5053971b78d299565b83107721cf5c8b2484757e8e44012cf11933978230ea46d8bc9fea472a562851b70207f7822939e1a4a2a8d55b8f1069276d81abeca434cbe6e2057acdc15e63b0ee39f3690536f10d599ef60f32b24dc6043c1926b6b6ba82f8e1b6a302de355f47a3cdd480331dd3e63e59819c7676dc20d3490c3696edaf8b796e2a60c325c30017ec28d3390a6d2c72e66db0d3390ae5a64dc326cc67e4bb85106f27fec894e6c7ba550197e84c1822f630c318c0ce5c00d32903acafd81d69c7aa063176e8c81349af692771627dc1003e1363ac77130e9d2ff32e146180a6a1db6dee3606cbda0058719230337c080dca58bb85898a4b594a6ca77f66056eb23e3c61788a9ee27ec33ca1b5e2045e854aaa6982e9083d1ce5a2f1b2e102d7b79c7ddb060ad6f811c071f7665f868faaed602319c586b98bab09e3b6e6481701a3a5ceb9ceb0616882ebeb162fe4c3ba6ddb802692aab65f3308f70c30a24b7909ab1d283a7bd39b8510572d46ff3c93fbe8c871be106150832b71af3e33a7b70630a44cf39e6e7387f79430ac4fe20634e1a255aa627c28d28103ad094b45352aa1b5020996dfc3c1f9d1b4f205806150bfdf10d27e4b211751ab2a17515e2c13f6ecc5e92e046138829c5fab0f1ae39c48709c4e95adf4e7b698f72dc5802294dad53ee9cb5353f7f704309041fcded7f39f2e8f76612081e761ac9d873512d3c2490e3a718fdd1b0d62cf21188f22af61d3eaa4fcf6b0452908d1d73f7f5cdd32210e5530e99a82f11f748221032e7b86853529ee3c221103c2dfaee5b847bda8725638088861b4220974f6f90be0ae35a2206819c7a2a4dbc7298ebdd0d2090e3b8fe209b5f3edbb41b3f2025bff30f33264b1ad5146ef880e0d67eaaea5f87e1460fc8e91b57da63ea060f4849fa6374568ffe51b40b72983adcc35ca150005d10e3b94d671453f1b8d28502e48270ae97a39472435b0aa2130a800bfc02dc22f3e0a5593f76178502d8822817e3c51c497f0ebac442016a410e8fc45226d1f70b6b0168b1b3d002c8420b100b72984a4ae3af25732ab0a82f0af00a826af2d64fb96123635790c2e6a34dda6f015a414cd196199952ce8f530158414a51cf23e6631584bdcfb983cd1be26c55102f467bb9aaed5ef6a482a81e94de06c94fd13aa82084eb744e4178d7a9d08bcc315ade14a48f76ddf3430f2cf22f05c93d74b57ffa4941eef70fe7a3bfcca1f8a320daabaa590ecb22c41705b943fb4aff966ec1624241eefde878f36eb5a50d284862c93b6a2bdf05db7c8294a23f0a657b31796c3c41b45ce6c1b5359d20eb5f0ebd32678b96c30952c58b5dab1caea3bf0992e664bd2d2597624613e4358fb7671bdb75c9042966d94c2922f355c104e93a8a89d0f4188f1f97206ea7c81cd53e4ddfc312a44f3962b52bc7bfe95109729ceb3897a7f82defa004295c5a86f6d03109c2a49d5f364deb1d3a2441ecfcaaa11d47a7dd448210d9b5f2631e49cf9020cfbaf999fcc7b2f911a4eab093ef56b93a76c4d165e8f8a9e2d20852ebc7165efbb1c7b1851164b54a5b39b28de9916511a4725b0f7a3ca8b3b32882f8ff53213c5412416ecd61959e7a4907154410d39ce5a09be38afc8720bbd7c5982f3efd5a0c41f2e0d153f4e0e121f54290e37b54a32947b172ce094176abd3ccf05974733e08c28737d3b1362708928cc753fb4105298d08a8140d4622a2c160241209840151300c50e63d00431408001834200b456291702c9374f1031480024c241a2e2c201e28180e101c1c1412100e8602a1303814080302615038100a84c3029c3c08f601a364b1a81127bac4b6b71f7470da432d9c14ec4ade132815845da329fd36d3b017a072cc461fba9b631a61b4b7a5c011d174e25702dfcd6adb671c7d94a90b976a26341cad59526a280063d240483adc99d2bcd2756c69360cd5961c8f14c89879a870271db9f04183560599577277e31c5af52ccbd3348fa404a175df4ec8a90a577d4ec245d8e3d41901856ed9cf93ce08d80ae6d5bc48d36fded3db7c518e48e0b0cc5c83a7f446795f0014d9de6ff552c2c23a927e3814e560120a3ef352c3f38de1728913f00aa4870acce0fecc08b7e09028f156df3631c4e45994971cfde87928324d837e5277774ab9c86e1a41f8ac8c18520cd6c9b4d516234352bfcd3851e56512187a8cba1654c6b6d4fba6b7f71c2a6b6273e3948758430cf4b29e627e8ebc1b1a5bc3aa11aab4afd5e876fbaef3d36438a15d71d01b978e2cd224a3aa3b9729cedbf0d1eadb5842efae442827e3b750b12eba6aa51f91a35db83b8f11cf88266765e50a823da0320a86095569e230d96491be9874aba9ca5f31ead4a7ace2fa15963a89695593f9b9d18bf680058a4d2eb4a34dc20a58271287c1c6d88ce01c0ba8a19331429e5eb34a6ee45af10f2ab62729d89c9edc5dabba2f9fa283b2febe0d42d6c39e2f6dc42a60a3dbbb8d449d13b84801495e200097865d529ed098f3f5d77a618cb7e30c15260d7f43899f5089b2662ff9877eb05e9f7a4ab37457d0036f1c4adf30a3da779b9d0b4d114551414fd0611019b0a5053600c550a7ecc1aa3e2809291f9137a80c24a984210d58d094ec842b890486628abf1ade891151a2eef08cf9a3a7c12be20de463360587581daa64c82e08fc2447068ce0474ea578aceeead5ce90375e86ccd1a0a3f7a221b2f42a2ffac72a4624cf3d273ddaa1a4f7357375073d2063c64c4d1c0cb200f64add12a547a3c4d1875d9ca027961424a361bf2dc0455624bd47a180617a2fc0f7e9aeee6d8e47e2f276b1273058b4af3c2344fb7e33bb75824630cb3d9952cff51df2132d8a6d560c80c2ef07bcaf396b6f9eebbc144254f6d84a2e9e6b9818667bac285b8743055f453912895cc69f24aeb47fb242df5c4e1b9e1c01f6c03999ca04ea40039be147d06ca974e63842b50cf4683803b4675568041870ad7f30a16749225762714d95dec7c442bf16d07b991bb84e3c92d97bb117bdfb43c8149ac1150781f7f7b59f48801f38faed204b62c6dfdfe76c3ab4f0961fcbab601cc5c836c135bb887028dd699d38fcc5a1d97537b2f499f3dd8f9c1238f32bb46aab10380715c0bdf0841447f5048eae0cc4ccc3e7e5f1d35bf81ee081fdee324be5a55162fa2088c0a5c8b20b8eb16e69c8f78acfc272f381597cf9aabbadfce3847c36d3f3f43da3b5af1798bd4faf1bc3b9dbcc1eb019d1d624933da18fe672e636913fd9f71351482c41fda0a6edc5ae81e2e5c84e118b26a298ab7d999948318f5e2364174c54b3e04893719cbc69498bfb20f4b6879e135215b01b6f112af9522f2f2ab1c016c358c4842139693509a86942476feb0e8494f1b44b06f0ce40f2f94171749a7d2481ee2e4d2387edc0d9e009dba44ea5d28daa0e7c6e1ff2ca9802e7e66d73333b9ef53c22a4cf05aa9de21f8a0d1449a5f06e32d53c321c4df56aa82fe97c9b1cee4cdc5c300f91d2ae885abf4882947567a0a3d06f304ed3091a6d42c80e0dc891b6891736ee1a2418b96e7a5a2e0300c182a6308c22cac7ae1a7f817151758d06ebf434face6d99ffa185c4479399b811ed97e65b2090d597bbedd366072d22568762a54d28eaa56c715ff6b5de495cec5615ac5132f92255dddb99fdd2e18f82b469059af1a9500c75a685997b2e744e4b615d2a419da594a883afda93b814d273549217674be0088d37a209f3b0509b23e245310fad9d6c63f08d29e00af57ba733e6a6170e22fbb8df518e27622d7b33c3ca6fa53cf2848eca186ec0299e52fbd42bde082f51515225e618315c6bbc6ac0a612703f017c681e40fb912ed90a35932db5469afa198504d0ed66515d4591059762f8a31c094cb8d3c89bb609059a864e8a5320ddf84c5e6a1b403d36a54a396bf78d9669f13a77492154cb93ec72eb6536a6d7e1eb5625365888af8a7b24e4ad2520ac663075f76e17624f2794ada579e01acf089bb8b5145f37228bc0f0c8f9c79b2df02b3efb2364341c98927e99c5bed0d3d1c404a0b0dc447518bd3847bb0c41c75a22e78d218d0286eff9319552d2010edfa1a9d62bad882a7993f8512d35941118c19e1a6b545a6a8c14c61a37a65085ec85a55b3c409fcb084060e34be27cb2f21e23d5c91e80095e64dd22b9112e6496fc2eeb09211304c0f8f5b87d651e99f3d95e70341d2bfc3d999e800ac8706ef8a5d9de982629d1ac6650036d0455631a3c80e10ec4399250deba967a608bad02e4c89a59e90d3576e17a1c45d243fd9010f182fa14e93a54c90cf02b3d7ae48418c552836cb9f434a8d3b5a07e449bdbe07208e20e2455b07e8aba51febf47c05dc2a6cfe1aea6a75571616df91021f7b893930692defc19ba65e08eedffc449f0f546e29cec2e56c2201c6a0caa6bb29024120b12e7f35f263dda43b0255a94cd0a62f063f6cbd13c7dfd4c74f9a002dfe507c558640f8857ac125699e3d3d8b4e81e478b3c26e2f1a351c39b36482e4b801344d3627fbf329e44735380409fa7a3c7eeb7b7565b669c09edddfb58b69802b6b69244f0fd4c652c9ad2728322d9d80e3863f8e3ce2c8b96e71dc0fae2b22f74560e30c3ac6f1ebc639d47004e166cd2cd2bea9608ef5d51ff142afbd17fcdf657adaec8c86443c963bc28ab2a01a007049a2075492e4c78ecf67f666b42b62b7bcb4be0d47e2f58c2687e3535f5e2529ec136ea8d36786a3c5309e3750f1875524339f6ba47c8b28b5778c50fb1d2eb61d7b289dfce595998ea7ab62d06051e98c2e9d1992aa88c1f6670100c3fa420a3464461dc52b1267738b66b106dfb607294ca7b099fe3c3703444d6a02908c7aaa6d0e1a7d7268e9aa819421bf2c8518df4d3d2567a496e2cf7129c52780c1ff4eb7420607d6433079cfd4d62b9786b4625358df5cbe3507aad9655401fbaae25e144f71ac7f2c98d5888d705d129ab878a8a55c26fa9ac9dad660da3347bb3fa67ba2408ac060b966783b2b1ccc91a39c9c2e5f4da7687359a99a7d024e45a688d4e14fcb6b8c274d84e6146e8961cb33c74cf99c649e7713f4ee67911b0cc25386b18e7fac09302391ce1e281b166291b05dd5b4cac732aabdf22ef77f20ccf525410c85b34f50d9ca181239d0d7bdef46b24983592c455d4d27216d39c49ceed408614b38caa63b9a41a37fee41b0217e71997bb097baa8a4601a5850bb44c56079835a0ccdf0dd8d9c1d00f981acc2fb629e9746c248e69fc6002567ad25a081827af521d8bd57046e08c9d2a1cb0604a3e0c3d05c8e942d00cf2604d03dd9d14cae04584c9f837b6c4f311ae8f3c02c5508f3b2d54ba57cc8afbf964eaf1771c3223972b040c790203575312a5fb470fbdb44795532905bd3d7cf3f1344179c58e44810aa1a92b0edea659565a3f931f8d339a56c6cc83864c3314360a9b483daef5080551d37044cf16411fc38838e423411ad29058ae92dd73b4c0e7c2bae3eb9dcde9ec82e644459641ad209de05704aab01b7586b7cf3e3e9300751d512847ad5cf4a8010c40a17a35388bede94b0c667d20513e5014df754cc5088bb7917082a8e896c90c66501ea7afd50e74b56eb148cf37c367276e4558824674c23a1d7a2fd21942f0321ca3a67c299f8be3eb03af1be861eda2d812b6450271eea0b3fbda14617228bd920d7b01ca25a07d31894b29accf5096c72234ace71504a6c44205e5eb7e5cde6816c66aa3786ed8046b7ab376533ce773b7ef80a784388779c42b42a82b25af433a83385b6bbb134fee968e69163491262969a75762991274abae9d52c82ced5b7e13e393dab6906a95d2647804850000698a6af093c47561fdd8775861e0810721a0a521256e1f31b9553aca2eaf494aa04105469938c714f909e5d92b6fefd3a1a104f221dd5782aa76001970867ad6940b2dd5506038567062954ff9e1fc1c60ce50948794e91e6be038fe417206d3ab5f74bc1172a54bc6554f3e7c88b3c0c6b0314f05ce000a33501014245f87127ee13647d1f0934c18a978603cea5fcfbc8af1a159f2bab48fb12a7c1d7a911c48811ad4f9a9865ad4d65303ba4c9df22f4e1c889dcdb22fd59b20b1781ca4c059a33a85075a4fdb7b3839078b2dda1d982ff45d0cdefe48d7fd61abfdc49443135d0cba960c8613222287ff9b614d7f206239191c4e7ee0be4fd502ace91e4cddb6681ea6c8651946b735ba3dd8c36fb8fae5014d7bfec697288509f9c70b42eda0148263ab77160957d13b043dfe662675f7a7362a6389da906213ba0c571eb68b89649f48dcdd04c079dedfcb47b686cdc0d49b824185a17861fdc0c26b0e8f166d5cc3dcedd9730d4246e13160937101f01eb252551270648875556e2a1293539efe00919116d8ef12c1df2a7949fa4f5a40b6cee1c9b6d8888b8ffe9036d72628957ee03880ec8d9af3bbe018c1073d42e02e8c06705e3b9b0b5fa15c64f44b3200140d9013102efb6bdd35907cbce020464efa90fa0c23f5af9783b984f71f8cf12af30d96b914c00453577b0e2f0b17c48a1f1223644aa17509e6894dfae3b1f1c08273141a2f8eece2acde6a4265c4d28656108f8143201a06f204043155ed035d027d0bd61e882d5d4558b44492e2a3c1e05c615201b755694672d12271937c075a45416b48a0f11ca88d571e82f25a8a9a6ac655c8488dd84086ce5c9e637166fea0e42a74e4f9fe1d66b502f47ecbb4b842ec5b0044ea264d409dcfa1cec66dcf1e3fd0a9086ec5fb86c661d42072577258ec7273d03ae6b51d7f25a0da9e554019c8f133077afc9ff5a207d273e8c0fbc7173d07ed1ab4911105dc5b0bbab70d786315c1516a16ec9580d9db2ee4b0955f424b9d008e4db4fcb25ca5ae40fcb4d756be434bc2a53c40cf880326afa45222cec4e18e6dadfc480f58fe761d0eccaaa5516799e58c40c372afb25aa10c2f7a0e6d316cb8a3982b94ba6657686861fe6fc915ee1534861d02e145b197924627b1051672a9b0ce51fdbf090e367eafadb35ebd0413768dae2bb0453accd59195be29583d3f494e7df7b2a47882eb43d989363514500e7663e38dcded2da4f6f63e7a4c3084257a346427d28dfc1aaaa3fa87f5dcb0d51225a1cc9c20eee7c22a16a8486ffe3e8216e4b06b34f8117a2461102297139966a8aec388187994cd2448bf59b4ab1270323b860d74cf95fcec5169d3e2bc002a82afa326242214a1035c7eaa9481e861b10ec9bd0abb1c451fce991546b82a422dc8f8996ede3f3c6fae1a97732caa102fa59e5ad6b7ac046d51fb6f33ea8c0dc2d1df345eebfab473c13e72eeb06d162993b5c3fa9b708e2376c769b027a134d32fc8920f37edca5f41bc3abd49a04f6b91d8242388f3fcca3ce8e102fc0c3ec58d0fc1e2038ec2371c9b4c1a1351043b83c7a9fbca050da78bf9e01bb97268ea632f7c7c3397350f85c12d8ca8a44c0c7ad7c7606450a3c8b885b7eb5b4439ad76320fb60b29471b6e874b48200f2054d22cd54eb2d698479eda8bdaf8d3c1cc3cf842e221502940b5d1f98219dbe0592b16b9244ec767c3b6df5a2e32b453dd4f1e77e4c3e5dcd33903b80b507a2b5b713c2d31c0371319911a549c5c824333b1980ee3c5798b7fb5ea4187395830cacb19f6dbe2b6a0475ede41f876e1fa904b721f35e30899246e61cee1d1881f9010ed7c5f28ef696a9b5f9e88cf409adaca0938d4459fee4b365fbafe29db60225df3d9b34bee39ec53b8999842493d820cbce8c719d352c996e894451adad2592ba43297590bcfd7e15de4dcdcfb349ce93e6d4120e4205fb7fbe8cfe73a2767b2889178f5a367f19ebfc8a539ceae7588b4d63fffaa3f9cd601decdcddc742d2234963b302c065279ada49ee967d812a9a62e3de867ddc3ee4ede5a03b150699120b783230385ded29c9a9bbb1f8732cbf99f53d0d4534136d7c7963a960b947e432d9598b220d3ee41f629fb20cbcc51539e8225126c67a35f5691941d5a5240eef4a61d0a9a1b45d994acfdcc8243298eb7b99df33b8d5bbae1e3776618764d3cd47032a62c798b080e6f43a66c5df5b30045831ab8a6f3bebe19c9437b1a130bf9e89bd78ee82ba0b2ea70356773bb6fe15067c7d78a741908cbc8fcb68973f9650042450503b29d064b917efea9af38c372fd9c6be986e2456652e2e324db99ddc3319dc6af759a4b8a115dd56ddffd9b1c743833810596edeef9c05de42ce6fb40877ba850b1ba5ff9227d5266918eb01ba744cb6831d9d0142a70163274616d8ba1ab36caeb4e91ed080e9759d56bb8a11bbe614770ae9bb517f914de596ba1258534c7e8ad64662bce222a091a26cdf9d8fc5261217b3ca2d404b90d4b8bba2fa38796dc73db87604e39f153c81c0fae61abd6d9115ee1e6bcb335015dd6305e44829a58959ae450a1eae7cbfbed438a7c88f6e40ecb5a2a427972aeafe71e28b9aad50982cc4dc269ac747c6777c158e9fbf6a64e543af2be46e91741b78ec8a85617950c608b839722c8a24f52d29dbe842695f501bb1a07f886642f7335e63b1feeea80cd2c27dce9789ffe0c6b8bb3cd0edbe8502b81a05b6dbf5d44200a0a294bb00fa06dcfe78964fab9cca774f3036ae9693b34007f5c9cc33f9bb440ada6f41dd8d411a7b73dd78d41a7f965cec67ce3435e1db099e113a8602c3c3ce92931c1051921ce2f37a52c52eb7eafe558e7958ecc79ec63b96f7b2bb1baf93856d94ff8ac9a582d24d93037a29e5dd1e8589fa4a00a3ed7b00db09bac56bc6358034f30a56b0b5d8d4bd616e37384ea6292562350cca89790bae2d251fdf931f5c9bc3450474a03a382e8926219ad59749e9fa338e758be252de9642f8fe9583d7188f40ab9b4317606a6accbc0a49df3928a63655446a4a21ae2902765673c66b5cc9c883419fcc1f4a66a67a15d8b899cffb92701f9cb587c2b73114789a8b1812d5db22497371abac1f734481d001c6d09787539f2ab90f50bc84ec70aee65f555f77d244fe057202cac806a097408c201b78ae402637e6f0994f543c86534f0955700161d3a322711cda501fd547c7494f147679a96264703169d4f3411df06711f848c739f911a3320c3d1126671f83357857928754cf6d2fcc47929bb869b28a94e13e16b370786fef9496e54a50f981c89e54fbf072bf81e328e50b5d0a2dce76a92cd6b358d3d073e7718d64312f634c59b33bb11e8c030d5b782d0a3f3228da007b3f02f69fb2ffec6a8318210fc8e761ae00495a4c34a0b5e9b1892b4216e10064a81e27681001cb15550dd69dd05145a2145409e3a107dd663319005a457be24903111f4cba43af8454ba49714c3e7b0d9e7f1551d35cd6e22fcd1995ef9b7b0f3d2142008900d6b26ec1c35a9c437933b4bfd133c0f8c5f7c5af0be20ca0bc07c63ea0fba98f5819d5c1232a18167e2207b0c45ac6a5e61e2baf1aed5e69efde3faeae97347d5bb7ba1aa0ac1c332524a14f5af0fc9c494b646e4970750ca6a91fef694b36bae0a3cf859d44fbbc29e1b50c09752e322f35d46646b360a44871f41c02190947c6302b79da240dfd84010300fc35170f2afadc58967af8ca2d9e53eb9d144853121d84bc54dcbadf0c0e8c5ab4844ebbf13719ad25cfc753ba0d94870db7676e0826068c19042b82c042f650f597a02af74b93ef61a922d2d8a7df53f7509fe2a00ae8f7427157ec8d1bef2114ffde3c88bd311d1e61d2d5e7f92433d209989465e36c3f4a105e0c4695c1348b33d1d7354954581b207ecb7e72fed4c13cf2bff290e607ad5b0f096f62f631ecc9202c7de6384e6100879d8b1cfe4b0d96f6791615f21b116dfcbe974c85b46ba7d011ae86d75415d3fd06b5b405b22cd589390868a3ac65344e6732d373a4754f05bcb059e1238eadc653fb8bdeaeb30cc00f6aae4e7da262b8134bb3d3042465849781b9ae302b0e2f9a3c5d1ccd28ab883e96e5812549d5f3e71171d8ca4a2b5472aede4934e9b929d995f85acd7bfb26273cd75738c1163c46d21e300d95cbdf306c3fc3106637416915300a6e25af4ee58f171776382b0cad123817d1e0f713a3b97146079bee0080b9adef4fdd4d28dc0388726e737c93e17955088f7fc970882230b86550de2db295c30eec5064485a2f3bb63340ed582a7e7f1ff43b775d62c86be139e328131a4c3bbf5c3e263de3e498cf81432ae68679517a8417c5521fad73b71d1cc936fad08433f6417a165a0c756d43cf979db29483fb0bcf72716120a42f3e1ef05ce3514350d61d720fb7bc77fd2544b579d5c191c9a8bbc0939a377c34f3cf99dce704d48d435852cd559b7c8601526f24609b33a1123fca00b0f48d9ffeef738c0e1b02bf97d5bd971f808e8b00384ee7df1cc64cb40762c3e467b3574d43be4a7ffe4b9536d69344465823c8f8a91135d77a7d90283f41b1dadf59f40e548c96370c2c10781b0ea7d8e2178d24dd16dced0a7dfe0174465289027f4d2ac15f6ac4d3c9f01202cb3ff33c65803e3c248eac57b44000427ad6b3b56afeac40108138d5fc0f307b2d844891cc202ac27931270dadf09fd9cf012368af535cac44f1d5ea0eaf4dcc0909531c33af2c26967f4edd0131e8cc23a58c49b40d67638bc8f16a3a0152d134c024d57ac94cbd7fa11c6c949bf7e1fd10b1bb36219b6a408ee333033d9ba61239bdeb4268cfad745b5362b4f6a5287560a77c9c345a490116327f9c91430fc7fe3bbf259a0ca0148a961cd7af7822733e671a97ea95ac8a107eb2666395ee674fcf52fc00cca7fe55af97ccc8b8f37b672dbc9aa07c845651dc7cadf873ffd14fcd5187bc68ec354e54c9829148fdf333942aaf663279904426073780629874b50c883faa1d146f137e427d3f863349385c2bc84130e1298d0380c871d7168d668435e55c835f93ac8c7e0cc11bc39014b1c7e91a91f53d0c22467de285150a2d41c7ca63c0e9c775f6dc24952ec0bbcbf51ee8b5f90144082e4193c98447f24d1431b2a8102f7d05a847b70c14e29999759f12fff3a6e3e4fbf2a2d2ffa1ef2206d0a51779321662cf3e6f6e6bace24a072fa28fc612b5020e2fec7f59f6533130f8fc0f61a9e63505c6d7c5b9afe7fa6fbf08ef6e14aa442b35749e00fb5be58c184f5513d23f2254292a791637e61dcc7ddb1c56bc6c0a24055d457e18fca8ef858df9f266f29eed34b8e8807eaa4a8f300d13cf0c62d3d750387a2a1db4b581e74a8db21efddcfda02674dacf7782e9bfa41b44c7a8c4fe7e01cace790920d83890ee6c69be28caa96689d7024bab845ed30a3988fbe6559cc2a8198126e9a5312805f2d3e71476fea91b720c0b489742e7c1f68a9112be6dcecbdd7757d9617c5059f1390576f4c7b2ef1a841b34d16e7a6b5bb0c3f207a07164c4c2b403677298c58215e24594ecdc809ecc45e74f4cd903bddb150897ed5b35600c2034e50121b676061d424513fc0c9712258ba28aab181a60fc8ba3dfe0fae2f89d2514fca2051e8d11a72ab234b17a08186972d7429f9578acd42c33d709a5f1ab86b920385da2ae56732d02a2d347ab9528e56b8821300741e369b9848e230d10d395b1cb8bbc52fa3a9b2dbca1f3313e4593af53f898482da8e474dd83e93b5144fd947edc70beaf39a2020f38483c4a577e54d86ae5d2c506fb596a473bd2a55e57a6d80cca751fb40473b28ad5bf97d4a69f13d59a3e23ac13a90f9d13d91f1650f7c4fa8fd7f0870f40153bed9b20c30a659f1eeec8a4056bac9c5d894a1a563ecd8526cadf46881c20ddc1339f51f9f58d7514e7ea375d9e2b382b2635ca2e946b5bdaa1e5a57a4e123e09f325ef91ec26af421c0f702d154063af5c67f44f58d46a669028720cbe06f9b4aba92643d09ee7119835bd6f3610cf5e7c3256d2423adb72ef56f3ca0ab50da68bb2821a74cc85b2e913642f888c6793b7c87a1e7759c665584fd31c30cf1828ae3ff84a66af52345b105fd45771cb84c3838bc25cbfc21a5aebd7dbdac11908ac44be0c4958e2c6e5b232d9284b2aaa5307167dd92e0444d281fb086a1c18db86810982adf435d4119c6ea14d9381f6166b7fe753e2b4f00e4da138c22a16ee0e462e16ce6dce682c07a392a223d89c95ea0a92a1987537c1a611245d6ba596f47ecbc25f840870ad05bf0056d0f8f913b17f2434c9c46471c924ce2ce17c70016bad12507fe30fdb21389b54690d3951a2fbdc954ba19914fedf5910189aaf599057cf26544a9465b83bcd0a187380fc9e96ba8f67ac81104cd1619795f0eb9bd639bbe2a0f226641b3591f8d17102a2bea4e94ed83862dab552008f41779ebcbe27cd9a1844fa240d796ad2233d6da4a9171f7e2e43d1287a40bc67f07016347cbe2e230e2cab816678d44f4e225aac992abb8b5592060521b69663d2f9049cb4fbcd8cb7915e741715ce00916ecf3ae9bbd99d6bfb1869691017ba4b71629bb533b22d467bd9ec32bafc7f3f58c23122ce652901bdb7222ef232be8178fd1d9000d5fa5c86a2fbe9839368ea0242e3761921d88e64922cb33f58cd44639b43c8b0e011ecf571dda0825dc924fcde21ed5c02efe1a2c5d53b506771082ee8f92072843d496cd3dfd74af7a99a6ed5836a075cec4c8b30d20296bb2e8e980f69ece72aac3aef92cc80d5cd542715acf2784be4b8a3a4a3246400e683ca49d7b57a0b701dcb1194047eec845d9d39ed36e2825f92f6c3cf72f65d9e31dc275bb093e2d4f87f13c5db49e1744083908b2d2fcf95aa7e98189cbcbfcf9b7521a42f085cd4fa1a0e64193124ea44ac02adc77690807a13f3c77a0e0aa43b9ea05a383214813e2b7caa98186a9975b7597561a80ec4a5913ee08bc3ad354625e7a91fbcd94e8b3f3183da12bece487b2da412b02be8184f7e6d3d84c9e050871746095ce202af7e2157aeddca6b1bd119c0f040b8a9337e80aaf6868d1796d8b4fc3f4224690d606d9892c5a6562ffa3db974d4502f3386fc8818a6644d995eb79a37c14c726c1a17da414d8f79ff980757bf97be007c921f7645d9f9547061167c366fe912b96fc8a6b6c35fd9cf50ac993ab2e9e7b4add7295f8cd887eacecbe20009d1963125924cbad07e3908080464a8e524713f731989639800e97042a040e1880ea877e038b9aedb18487a4d451fec73c2a0b728432041212ff283636ade37b82fec8e80f0548f7550a992764f59daca95315329caaca94c037098c9d5afe1e15d841e657fe70f586250a73428a7c34ee54e568858ecbc892dcd987f4fb52a638fb50787cfb3f410cb6f8b87954a8767fb4d40696714182a16a00679b9820b2192d0dca6c51fd2d29dd5c9d9cd1bee2d3032c0f438a62585b134e4216176e5e3444c1437c62e66031a44925ad043e35c84302c207850ca17c8963b477eba877072b53cd67ff84eea71d2a741d7bdb4e4b96f5936a29dae3776da2c40727d081d83f571ad905ce12c48001c5b82be984b7dbaaf7981771aaa907ffd3f0583168f52aef6c7ccb0d3724b925c6f9d5d3e26cb4ecd2ae5f8a86514684cdd022ef504787b64f77ea02d90071f587fa0068a953ffdb7869534de9e14cf1d28ca3b0278f387c571eeb952d5576d29bf9913d43ba6c126812ac9ca96c028807a80bcab2ebf5be219cd116e37552cb573ea31cb3ef775d73cf30cbe0ef4ceb533cb4ac44beb67be09d49475b9da09adc2467bea3180e2cf07b01391e70ab79d9ff81417b2fba218e0ef890015ce02e09df33809bfa7ffddfd1bf9b0775cc706ee246e59c66323113482f3eed82059cec425642d3ec260e4454a8635b55c2c7c850f0fc9ed89b2200137d766892c313885895fc11d61cee8ca8bb8983b1a266d844668190967f50971d09977818ec703e284160be3a1e73b5f38ca3c1c1e35274168e3707df5460d8b8ff3ada3fe587df545b97ced8f6dd6bbdc5dd0a6191a3e4596289a462873fc844c166f1aa9c24edbacf9bb2609c8cba0c0d1a8eccef29ebe55cacb82fa98240005309f852fb3ab6ecb93a7050f89cf6bb7f2126c88890b220821702b6b67beb04070d4573d7795e51a331820feb1c87891e771c1c2ab68e3706c979a523dc878314d87c1bf72d28453b98804148aebcdb1ba873aabe770ab36edd51d1a2d14ec49df903dd72e187ac6ce3def0ab3eccac91664162fe4fc424316d2afdd091da27a831412974c98d4971a040b4c2fa86c0db70f886f9319c9eed386444c86430a8e2509a96937b4ff65b324667fbfddb0e0e858aee8dac6bbdd74368b865a10985ca55d36c78e49a0b96278df03622d59aa3f3eea6c5703224a9240c896096d86a1007a0922cedaef58a68066f67735778fb8a6496fbc2408c6cd52cb01ff86436ba6e48a2a01b21b972573605d979654c9eaa21fab3d863000edff243db8848fbbfbff396d59b31103d4057a61a0e0933fa6394ee1cad6825fa25f322fd6584b78c76655c9e79f32eb22cf7c1650a02dd0666004c41f360b89ee442205202d5bf35a12b049a49725c3aa10dff342f962356ec42c473e0e9f796a4065d0a911d329b3d91de9fefcb28fd0b4306e0ad02080c795186a93de2fe2142905360b4ab3d2af102bace7b77d46d7b5e606e2fe8d830a4c91e35482325528746551f3b6b0f642804a345f8103a00558c5e01cda8d76f64d5633ce7316aa659283726aab644cb5017f4873f82c6c9f54ce6cbcdd261067b5ee61f27caa474fad5138970659c1da6370db8f412e30d3d7982d198e0e8dd17c39d8ef4ed958bb61e9af2c4cad75ccfa1d58891ee3a893bd72aa994cbb24f92af32971b61df932aa048ea0a9420fe3683dab8eb715850419768410ed79b441c8a0494be42f4dde5b270aa545ceffe82443202cc681e0b78f8ba8f251ae0839ba726460803afb0208e249b1eb75909daa690c037d2a813784ca9b476d9941c0168dcf843eaddc0da406107c6228ccee04f032e0db41a7ce70e6652ca018e7484b288c1661a1c9c1de5b277afdbb75ab6f9fa8a09752abded74dfb6a9fb4d22f4075be9d2e7c31799d57963ea320017838dfb538a7216fafaeb51cedb3000c05404133ace8de1d69a1df0f309dfe21d52133400c403fd0bd8a5009899191d4b4bbdd3fc02b053f46659cf022f1220005014804559400b5c2f652a003ef480c4f70976b1e8313ef03207faa2c2bb349f005a015e8abd643e02a6146866c0f5038a084874002650f5a3518d331c55cb006a561d3945606a49d2010cf00cf00cf00cf0e4ddf95c0f48433a548730b1efffb66dcab6d651d54699a49c16bcc1900c22c8979d70534a32c99492500040800c7cf12ff52bc3053d41285b035e03710348f572cc9a56b34e7d5a3ba0452d7d7fb8a0aede65473c3c0a92100f2f2119f905e4413838da0f933ae0840e885bb142beb3349a6da698cc61e5707140a7bb52d9f15aa99b05077446251ab38eec7a527a0332a3cc5bfb2637a0e5f4ab9aa7ffd9bdb401ad4bb6df689cd739173620a574518d68e8875df51a90c14df6a97bb7b839ad06b414cfd53a37e9ea16a701b54a2653a75f9c96548c0684ca9c326b2dca671963ce80546f3a2b734233204437b78ea1f93a369601a5e3cbfdc14ffbc6cf9101293fb5faace336069494fa45e1f76fe73213035acc41c53e834b1d3b596312860503326f307d4acf6a51ebfd0b68c1f566ccde3eb1df4dbc8096365c3768f85d40ba2cf8c99732b5cb555c409ef03d199feca5b7d31670cfda279569470be8985a3ead7a655b689805bb0916ecae809c55fd1b557da9b66505946ca6b8fa2cc75ca2aa024aab57263726f38de24405f4787c3a9d99672f5ea680922ecbbe52d9dec92c97024a07d5edb2cb1b33c9390a28771d36e75bbcfc4a0b05b44a2d4b231ef71f1ef304f469f962be6894a63be8047406931fa7329f225f13d02af3b4f79c7bbbac4263c204d4a6eef1ec6e2da7dbbc049496a5bee1a22901b5af336efe7051ee629204d4ad6292464e9070266820c5b7ba60de9fa7fdd1627204d406d5e2fe75cc1f8ba3607206cab5ec5cf23fcb966a6aec1928264640b867f92f5fda1cb2ae1fb181133390aa19d64b69dda48c0919e8a81e5e9bcba5dde6998c81525a10fd16cf2e99582762a065f99793d8289aeba2c2402b69a6ef4b4675652a02da65b655231bef2d4d4440bb2cb5e5e6cf9321a0c37c7d0a939af4b8ec440867d89cc7b22fb371510fdf9804a12740e8c90f50ea4b47d7729e9c81890fd0daf779ffb4cbdab7ef01f2f4ba70a633ffba977880306f71538bc15789690163f2057a3ce72cfdc39ffcac7780dc8e579f33a35ea0738db9985ef7bfa67480de64aa9bd585dcbc982e10665249555f4dd5262e1062aac53f667ad7bacc014a5398a613179fa9411ca0cd4ed8cec6a46599bec90dd02eaefef0c268709dc5263640b620af65c75c6bf6bc1e2e2635303440bee0a352e6ec73259f015a5e6d7239f732409a1899976d1944356a1203b4742536ebd31ae2199cc040514d73cf9c549cbce0262e40cb132fea6b693a78fa4c5a80f650cfb73109490267496814b60d90443902625ed024e37cdb6a8c52ce409676d2d24c871423a03fbe9c55ea2c6e067ab443d5677d9ad2329432502febd6fff01dab341432f4ec385b55cfb42ad54ebef8f974ae9431902f8b7e5fa752b9cb2a8a18c87fe165759f1b763426014a1868a54fe97c71a2dd325904a4aafdab29ad5f7d914284d4630e9bdbbbdb3e643bb896b9a4259fd33a65081421a0de5eca8e4d390868d9749e5b541a05084615fbd8eda6ae6680f203c4c97c2d6c8e2b1f1ca50748f5b2f0f55df2ff1d850747010329b5723f7d4f7e81328ff15e7f9221734a9d0565076e38a54c7e9662a5b9bc7065b4953f19141da0e52c8c6a502dd4554d5da0cc5ca72dcfb36a2e4ee102255b769d5ee6142fbd8a92036416db53c3a6dccd2f8303a4bdba36a9e3a310996f80967aa75e6e8d694e77a6a0d8002de65be5e1ea37775bf02835c8533e7b8a7ccea09d83476d5068808cb2f6d7a3fabb973b79066506a8d582dd2a4f9b366d2e03b4f2d64968d2edcf9d2931404a71b3f9a953e8bb161820b3fe152e371fca0bb0a0b8e0dc2d1fc46eee637366cf494169014a67fa12ead9620165058817a6a5b4672d45054771b99dbbfa33650bc45c0ad78c4ae9cf16142d5052b6a8f1f3b426794a942c905a83c9fca5d665e9cd1a7b211e200905250548f15a2fe84bb2beb4dc20e9502fc5e412481dc387172af746b38462dac3435e978b9540bcffc6d3abbbf351acb137a104325c76d4d7f93a67a999047ab5b0ae53d4756e3acce91d0e4c24815cd3b256e559d451fa68d2984402f9313a37a7a517172a15211eea4384022150e0041208137feaa28e9a47a023d6aae54ff2be1a7b5f856f808383a4118db6c62efb1835d3bd93ca08a41cd5d2d7bba4514d5fc4de5a6eab2977f2a269fd4c8b57f3c21345a04e29995d6b6dd72c69268940786dc86ea8cad6f905110895bff299d5c7965beecc617208b494cff2c92463cecb2e8640aff8e6bd96c5a4fad44921502a755ed7109d10c8ccd9fd7d579e350f029dd1fb63d799e8c65a10a81753dd27d7629340a0e36ff77c773897999d00022566f477e77635fa39f9034a4e5d44337cc6fda8891fd079ee638c975d706933933ea0349d0cb6af4d7c409ab7866711d9e4b233d9035a0bda7a6d74d5af6ae901adbd2d74c73379404b5a759e17e475ee9499e001ad741817a2525fb567933b04a8cf2895d457195b3eb3c66a49c85bf196407ccc1b53ed32af29299540f8c989fc46a5c3f5cac2a050e218bd357c69877ca737f3fa31d9ee38e2db4d022d2a3c271732777424042892406b3186989829b95dea20f1e8e8c01319490ed2d1a1b791388e830472f36dabd559beead31e9447a023d01dd7fdb54c8637ede2416904e25cfe99541aa7746b1881d4fbff39b5dc627ed517b1762d4faa08c4294f1d2e99cc26635312814e9bcbbb3dbb6719e31b8840b9d4f5e952feee36abb1c7781e8740895d5fa5c5551f66121159ce102873595c4b9bcc8d42a03dfe988bdaa9c273eb41089496dd83bf169e3a52a101110b70705021f2c1c131066510e896560a974988abc3aec483c4630a551fefb1f4011441a055e764ca93efb8f297ac410904530081b615ae99e3d2bc5f8cf207e4be26f5a736bc5e3d51fc806c79bcec65cf8e3997fa804e29e22e5c4a7571753ea0b416b328650fc811d7d7b899758adf50f480105bbda93999fa8e32943c1c050f7c073c1050ec80ce276ba58bdf2e3e7c287568ccb3c78927edd8704fff9dc3c6342a6715020a1dd0217abf71e3a830a5340774dc739797858b1c50526af9cd62eaee4d1e07e497d6c26dea7bfb8fc3012d6deb9371577ed36340286f40e9c9a05ad0cef1ab85dd805a99e59f12e31ae3c76d40b9e86267b89645ed6ed9804c42f36729b65d034a939ad7f64a5b098daa016d1febd3ee734aa5c63420d4b556a5c598c54d8d6840ca57dae552d2eded73674069e9e5def56ebc4921c50c5986e3a800850ce82c76bcfdd4a2f80cbf032863408a18902eb39ef6534a1850eed9f59eaef7ce280506846af95b94f5a7b64ccb17d062cd76d4a832a61a7b01255ecb72683cbb804e339de45dc6f5932d1750725e6e33dd967295ce1690415cb7dc1bba22626ac1532f5ff39e785940a7d5f9cd0e1b1690dafbe4bc95e7cc2e47ca1590f2ca3d9e8d8a0babbfc0ff043cc4413afe175a01a9c757be3d2975fc885791442c50c515942a24f39ef98b5f2d692153610aa99997d6714b55f3018a14a2d0788eade9a3c72023b3690a1490351f9a94d86bef6935b3407942529c8094176dd749cbf5a97913d031e9aca6ea51ffb687041fa2548cfc05129f8a9107b1a3306109cba973fb658ba758ffc3c68d1213509480d4b2dc7c3abb64dbda2420b3e9265732a3165d87d3400b7a267569b1a120018dd36659d9ae778d0f6f1b53d90114634fb43d61d09001e72a592fff130618c731c72d209590dc18194013582c4fa83f401324234e3c80c4c3861fa089329868e289276a20c0034c90902c1c02008097b8e21840135ee24a6484a3004c0440014e88d8e8a8227909494707029868220105406b93496787dd35f6b2808e2633cb47ed9cb5da0a0f75c39484783ca082850254ae6003c40aa8fddcbebd1d35b3caab80bc9359f6c7ce7dd9221590d25e473db32e9d963b900759900799026a4c479372855963ef803c8856a4901e255678a828a0850ff677af4975cb523596f16ee081fc4887151eaa43a5b741d297810a14b0b0919ee044c509c8f5d8518dc81f3740d2019526385161427a942811901b1d1d4e549650f224200f42808a12509b5f8bd75a8cbbd91da941f29104a4ecd62435a9ae8ea747c9470d12efe8682d09f14892108f9291bd34d2a3e4e306484787012a4840c7d69d85dfcc1534902fbe9b4ed25f060d5793d810e9b0ca11906e662a4ec45c8e4bddf5a99c6176c6cc12d5d89a2e41c508a66206ca5e169549995cb86a315054ca4087ff6ed3e4c97fe74506d2b32cb53853fd1f7a8e8170515766378b295d4691511103d92f8bb3df4a1e1f241d1f1e22e25162430433059530d0aa655ba5b5be13173d35f63acc85548af04751ad3765f02b4440e6ece1e46bd9e6497d24fe612782ca10d0a2562deb8fea53ebd0685111023a79567fea6549930eaf1120332a414087b6affcac7d539fde0a122020b3c78e2fceead8a865490d2a3c464ca4f20394bad33fa95bab0a558e547c80d462a7f8ccf625951ea06ba3b0d72e9e4809498788b9abf000f5b1dfe9b27751482181282cd080f45360808363025234e0fa3f96c523e454c040ec29295375cbf3323c3542fc238a572221262b5fb4092a3ba878712e39bdd29369291c49211e1b812afc3bb027a8e8002ddd4bbf2d8753a58b65e5517cc9ed2c93bdf2112a42ae10f9a870818e5aa836a9aabb4c87951ca4e619e5336dee64a6a9ae3cacca471d476594a9e0005ddad4366d964a477d312a37e8e4ab57aba4da89046fbca8d820bd1b95292d76fcec3b18951aa8a55726c4b470ad85e40a918f0f91be312a34c063873fd3fa2f2a3340d9bc89c6c7c78e711615191cec7e370b07475762b0979636be185342e229e40a1105833f471ddee56e122f785f7981dba667838bb870897ca40e2c464a5c70bed7e6e0d2ed9516f09a7ddeeabea3d886a50a0b34efd262f533b55c95157c5ab9f8a699a3a2a28263acb54fad4f232e655ecbce8e8e1011430dd88dd88154b640e7563aeb738ffe4127f92d2a5aa0ef7e5e7894b6fbb15e250ba4d7cb1c55ed93dd7d2a2940e999ebfc71cd97b6dc4b0483aca8426b8cc3943106114288218635690c9209120461418ca3308e03a1b5d803126030398ae4300e821886811885083184104208218410420831c492b26e30288912cac239351ad1dcf9bc97469b7fc2a209d041d71772cf4031a2bb923ce58245c05748137169d6f1ac8a45d81363687fd25308468e8703c6d2f8a926256991883a6c8f6c3c8e24da015fb16974e4c91e64789058d8778778c8a8db84f1acc4a21178afa2d4d2d3edac09aa4f9557283a8b731c740db0e4339063359c4988d34c90660e14ae1e296f146a01a0e664db62a28c830831f8edab2294dc43051e05690e1d9056ec0ce19f55d034cc0b9ece95fdfde7ec668eae1dbb6ad0acfb0652944af3e6d4a74e76cf02e305834b90e6d6092a78290135941e3d0f46b5d1470bc5ecde6bcd099ca21ea1e30a95ccc1ba58d0a43f1e22e5d4081e9c765d49b5426499e530fcb59add2a41547f56919e421c9ad2c605b89553b0cc188aa40b190e992f95f925b799f75ab8c5d6e0d5405085e141ab3347e17d40e0c25819291015b45e38c6a267b7c7b229865dc43cf127b43737b2c0e3508ac9e1a4938320315c4ec10e4ca436109c44423d0ec06e897bd2db9679c8ae6feb81b073ff4bb65a363e0338a80bf7a21b5354ebb9c355fd04e59ac8e99841c0a8123cf3b614cc8ce409533e324998fb79537fe85c03f617b66405cec53d2a629faa5ec349c5541a72c506b8e8bca52b226aec211b1cd639c5aba8cdd4d0118605d50ed9b5cb2cbbecd4a70297d7e5116e19e25c19fa7632d591caede47bc912b6047bf7bbeb68eb56961bb7ea8e74009fd5471a1306b860b769bc5e04e397627935ffa2d1aac53e5daa96f0d4e346928dd95679ad4c98735383f9c3588795db33de86164be5cd3bffa397514883b38c47117a1f63a0e795155d4d79f303e728515ca695850f2003d1e9c4d275c8e000c1d530ba71e7de8c004709d578640232f422985ec5bcd044ccf93f26c7d3d412c161169229a6581246233229d1e5449dc7cdbe0fb5e4b037dd02d53de108905379fd9b4b95befb14af67fcc643c9436fb5f376f398c6c1748a37c93febba81a4ed84279a51b24811743cc1fcac6f2ea67941adb9994ff253b703306e2a3333ed73ae07ece266536a3feb9b76e1604b4c19a704b36636bd15b15acc1071472d3138675a939e1e33f0542e76fc899a357f371ed31c93525bb076a5e27cd8347bf6700f05da5dff368e4bd5f64dfb147e1455ecf85bbd85c71178f3c7f244865ae894e3d34037ef68e0c6da17d2b2111f7698543d91501aaa790a21aedefe1b75323208d114dc40296c5b4cd000aa0d572959d0d4aee3b3d8e65ceedb5872a5872f65c8dfedd42b41b33a1adfdf592a41931b8d30ce90b0d199391aa76f2efe8fc9034451d2f9ff4b18be89f9f08bfd4c3c4b5c09c67c82a7a2fe8c1e281b2a8add1ce5a6680cceed0643935f543444e02975fa4326b2a3750901c3a90d9fd79020674b27534583bcc1aeba4d1a372fd461272317099aa063c2c839da460dc58d38ef804f48b61304f2c0a5220e423972ec76a06f996ebfb8216b56bcf53fd6f274da18c7bc5e284c712d01ac699eeb52916077eb08ab910f7c522fe05ae669d1762f217583d60c360cb9ba4f2a24890748d6181d3d9c19d10129b8a3dcf30cf54fea895678ed6c6e5520bdb981abac541f6a8bcf80c7e515ccef68a48525a08b6929ed6834313dcdb61bfe6f416c7552064d278d825274265a15a0fa47676f2f7d2d09c65795ad619d55bd1eaf79c7d7c779ab2e213a3960d4ff3e779644c8d86288f565202d7de35ead569db89710f1d8e10c37b6f3d82fa11c56eb737c6979936e363ee201af4192d78d2abfd4f3b0d488de8daee09de0afb8f9557756a9b44904466f89d5db4c826cf7fa6bae583eb436a8b14fdc055e988ee074f8effe71c46f303fd68781c14a53da73a319d1d81823791f90b85d78c492135a2ae41cd0fad2e8d5c325f5c0e4febee3dc9f446a863df256983ef3201753009e9f10666269270d8b8e50eca5a34679a67be0e3f0143706b44e323bd626ada057f0bf53a6615bc6dbccf1f185548bc1ca6eab3ab6e95341c2f30d196a549f5b05d3f63373eb2304f00663f9ae9dcc07f4459b090ff76f4b253d321d0cd7537b3dea05e600891310340357aa8cea1e2885c620ecc5182fdbc4d61b020832bb21d66631f0419591e88120da0b3970bea63f9c6c947cb4987089251903b79fbdc94a29a6a7ac526877c5176548670e955074c2e3d165cf77d6b1dcd68af8cea834a64cc1328adb6dee63a85ad287a16399772fa00bf467005274123b2cefd97e1e01913ca0c2c74cc3e4d88840f669e039622124c595680758321e7144c9a200320844c22fae45ebcd6923026b095b7b10f13b05d526a2880fe3725598ec44fa2b064bc603433bb5f9a8c3f0c9f48418c80a45cb84c932661f97417df85bdef215086ee21210e7c021fd48e8e58ec776fd88abc59d483d4776157fa1ba7f7f668d301a02a551a2056cf56fb2ce0c5c3732080141c49e1286adadcdd94ccfed2482c5635399a8ad9751777668e31547d015ac5fbb0bb10d029640b34707c6e872bcfb66150934b10d454f47fdfac51e916fa4b8111e8c66da35df72a479b49fef8a2224a7a60fc9a960081c8b89d739f8e989100b4f067d07792b6d6229816518f3c078101f874474fe69a5ccaf767ac0b3a7c5e50fabf06659213a4de037a5ef8232b372a42e12a5836062e69befee58412dcc86e6ccd8d1698c4721627e9f76304b18805f7e15a3c15e946d000370ae02c145ea8d90fbcc9979ff47ff3f829274bdd9ab441b4f1f5ff2294eaafe08726d7b5974f4f6f1f8800861f2a0c2be032c43577aefd76d65478f82effd06f935d4ebe1597ddfe6e569d19af53ba95e55fbec7f0d677257c51615fe7a65286ad9aa8614ca97770d6375f3c2528efe1206aa94fae6eb959d893d8f504efd811ca19393ca3327ffb7f22a0311cfbc4c1fca529ffe29c80996a3134a36225e411dded313281111af3d87d1a40c928123f75cc0b9a399fdabc342fa3e4049ecda6832affab1878368160f15b468a73a5ebd93ca1ba3963fc85c0f58eefbe1fc83af4d5eaf0eaa0a39c7ef6ca7605fb7be19a359935578d9e9cd0139525f498362b290c35603642b150cd5bc01b12239345534c2872ceb019f81466484a5f7a3dc54b7a37da1ff82060e7f028e9f2f4689748e9b6074dc157978d5a32136e404b1860d4f2766f768a52a4471ffac90a78685714421de4ef915990b104a5df56b551aa9fd9559f36ec3a282bc5c83eaba6aa87aa90f6c61640a91585eddba6ba7059148708347c75dff33c67fc7c374658ad5b68836c476bc7c6963a3b216f9156288b179900d90c7a3249832ecc36902f5a41e755d4bab7ff5ae7ad24b4aed5c3917bfcfd300ef31dce8dd8dae7a9971efd214d4fe80b3edfbc3748733b2ecb762a69f7cb3f3902b5c18267c794883f3ab6ba16ee98ca1caf227337c7a19ecd9d973003171704bb351955e7fe1e4efa1eb9af8b87435fbc1c80f137e037da2333bf9954c75ac6f76459d02fd25f30c6c58a6a46374001800eeff22cbf97cd41b69973161cb77ef39cade29c8ba37c243b37469cc3da6066d183c204412101160b1d034091a916a730dba528b282bb51b0d2eb1b46cffbfe82eb73180e3dd64bb50d07cca5ac6cbf8427911fd0eafcd06342833cb7ea0d8e52a82997767fdaafacf8cf5f8d703356f8e9e210b1c717f65417627aa9688a2053a74f78d686581fec24a86d02ee0a8a6d5c6e8add5ff9f93be4c297b2763d2bb3412160f39f9225ce129ea2fe8f67e430876153e96ddc2de7a6a6de2d3ff996b660131e7d67d04fdd33634455c3453e739fccc2d2d685c25cbe848054a6a9044e4485471538eaf854e96add4cd11d7683b2ca7332f3ed011bfb4718a23ddc7002b0839066f139a2e1971556a2f8e82e104c110c2d83135e50c", + "patch": { + "balances": { + "balances": [ + [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + 1152921504606846976 + ], + [ + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + 1152921504606846976 + ], + [ + "5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y", + 1152921504606846976 + ], + [ + "5DAAnrj7VHTznn2AWBemMuyBwZWs6FNFjdyVXUeYum3PTXFy", + 1152921504606846976 + ], + [ + "5HGjWAeFDfFCWPsjFQdVV2Msvz2XtMktvgocEZcCj68kUMaw", + 1152921504606846976 + ], + [ + "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL", + 1152921504606846976 + ], + [ + "5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY", + 1152921504606846976 + ], + [ + "5HpG9w8EBLe5XCrbczpwq5TSXvedjrBGCwqxK1iQ7qUsSWFc", + 1152921504606846976 + ], + [ + "5Ck5SLSHYac6WFt5UZRSsdJjwmpSZq85fd5TRNAdZQVzEAPT", + 1152921504606846976 + ], + [ + "5HKPmK9GYtE1PSLsS1qiYU9xQ9Si1NcEhdeCq9sw5bqu4ns8", + 1152921504606846976 + ], + [ + "5FCfAonRZgTFrTd9HREEyeJjDpT397KMzizE6T3DvebLFE7n", + 1152921504606846976 + ], + [ + "5CRmqmsiNFExV6VbdmPJViVxrWmkaXXvBrSX8oqBT8R9vmWk", + 1152921504606846976 + ] + ] + }, + "collatorSelection": { + "candidacyBond": 16000000000, + "invulnerables": [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" + ] + }, + "parachainInfo": { + "parachainId": 1000 + }, + "polkadotXcm": { + "safeXcmVersion": 5 + }, + "session": { + "keys": [ + [ + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", + { + "aura": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + } + ], + [ + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", + { + "aura": "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" + } + ] + ] + }, + "sudo": { + "key": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY" + } + } + } + }, + "id": "custom", + "name": "Custom", + "para_id": 1000, + "properties": { + "tokenDecimals": 12, + "tokenSymbol": "UNIT" + }, + "protocolId": null, + "relay_chain": "rococo-local", + "telemetryEndpoints": null +} \ No newline at end of file diff --git a/templates/parachain/zombienet-omni-node.toml b/templates/parachain/zombienet-omni-node.toml index 29e99cfcd493..2f263f157fcf 100644 --- a/templates/parachain/zombienet-omni-node.toml +++ b/templates/parachain/zombienet-omni-node.toml @@ -14,7 +14,7 @@ ws_port = 9955 [[parachains]] id = 1000 -chain_spec_path = "" +chain_spec_path = "./dev_chain_spec.json" [parachains.collator] name = "charlie" From c9f15ac7afef38ddf3d0aaa54acac725c4f6504a Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Mon, 27 Jan 2025 16:23:08 +0200 Subject: [PATCH 334/340] update gateway contract address and adds logs --- Cargo.lock | 1 + bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 1 + bridges/snowbridge/primitives/router/Cargo.toml | 1 + bridges/snowbridge/primitives/router/src/inbound/v2.rs | 9 ++++++++- .../bridge-hub-westend/src/tests/snowbridge_v2.rs | 1 + .../bridge-hub-westend/src/bridge_to_ethereum_config.rs | 2 +- 6 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0fdc9327e6ce..94fd25a93a65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25499,6 +25499,7 @@ dependencies = [ "alloy-core", "frame-support 28.0.0", "frame-system 28.0.0", + "hex", "hex-literal", "log", "parity-scale-codec", diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index 3a35f14b04f2..c9d6031c2355 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -236,6 +236,7 @@ pub mod pallet { // Attempt to forward XCM to AH // Set nonce flag to true + log::info!(target: "snowbridge-inbound-queue:v2","💫 setting nonce to {:?}", envelope.nonce); Nonce::::set(envelope.nonce.into()); let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index 9b55b0d76d03..92756b699cd9 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -31,6 +31,7 @@ xcm-builder = { workspace = true } snowbridge-core = { workspace = true } hex-literal = { workspace = true, default-features = true } +hex = { workspace = true, default-features = false } [dev-dependencies] diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index d49b5834cabf..b894da5f7a89 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -14,6 +14,7 @@ use xcm::{ prelude::{Asset as XcmAsset, Junction::AccountKey20, *}, MAX_XCM_DECODE_DEPTH, }; +use hex; const LOG_TARGET: &str = "snowbridge-router-primitives"; @@ -127,6 +128,8 @@ where ) -> Result<(Xcm<()>, u128), ConvertMessageError> { let mut message_xcm: Xcm<()> = Xcm::new(); if message.xcm.len() > 0 { + let xcm_string = hex::encode(message.xcm.clone()); + log::info!(target: LOG_TARGET,"found xcm payload: {:x?}", xcm_string); // Allow xcm decode failure so that assets can be trapped on AH instead of this // message failing but funds are already locked on Ethereum. if let Ok(versioned_xcm) = VersionedXcm::<()>::decode_with_depth_limit( @@ -135,11 +138,15 @@ where ) { if let Ok(decoded_xcm) = versioned_xcm.try_into() { message_xcm = decoded_xcm; + } else { + log::error!(target: LOG_TARGET,"unable to decode xcm"); } + } else { + log::error!(target: LOG_TARGET,"unable to decode versioned xcm"); } } - log::debug!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); + log::info!(target: LOG_TARGET,"xcm decoded as {:?}", message_xcm); let network = EthereumNetwork::get(); diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs index b3010445c030..faabb67aef76 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge_v2.rs @@ -126,6 +126,7 @@ fn register_token_v2() { let eth_asset_value_hex = hex::encode(eth_asset_value_encoded); println!("register token hex: {:x?}", hex_string); println!("eth value hex: {:x?}", eth_asset_value_hex); + println!("token: {:?}", token); let message = Message { origin, diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 2de53d503118..519775fe6908 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -57,7 +57,7 @@ pub type SnowbridgeExporter = EthereumBlobExporter< // Ethereum Bridge parameter_types! { - pub storage EthereumGatewayAddress: H160 = H160(hex_literal::hex!("EDa338E4dC46038493b885327842fD3E301CaB39")); + pub storage EthereumGatewayAddress: H160 = H160(hex_literal::hex!("b8ea8cb425d85536b158d661da1ef0895bb92f1d")); } parameter_types! { From db3ff60b5af2a9017cb968a4727835f3d00340f0 Mon Sep 17 00:00:00 2001 From: Ludovic_Domingues Date: Mon, 27 Jan 2025 15:37:00 +0100 Subject: [PATCH 335/340] Migrating polkadot-runtime-common slots benchmarking to v2 (#6614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #Description Migrated polkadot-runtime-parachains slots benchmarking to the new benchmarking syntax v2. This is part of #6202 --------- Co-authored-by: Giuseppe Re Co-authored-by: seemantaggarwal <32275622+seemantaggarwal@users.noreply.github.com> Co-authored-by: Bastian Köcher --- polkadot/runtime/common/src/slots/mod.rs | 121 ++++++++++++++--------- 1 file changed, 76 insertions(+), 45 deletions(-) diff --git a/polkadot/runtime/common/src/slots/mod.rs b/polkadot/runtime/common/src/slots/mod.rs index 131a75f3d743..1fbfe451dcbf 100644 --- a/polkadot/runtime/common/src/slots/mod.rs +++ b/polkadot/runtime/common/src/slots/mod.rs @@ -149,7 +149,7 @@ pub mod pallet { if let Some((lease_period, first_block)) = Self::lease_period_index(n) { // If we're beginning a new lease period then handle that. if first_block { - return Self::manage_lease_period_start(lease_period) + return Self::manage_lease_period_start(lease_period); } } @@ -237,7 +237,7 @@ impl Pallet { let mut parachains = Vec::new(); for (para, mut lease_periods) in Leases::::iter() { if lease_periods.is_empty() { - continue + continue; } // ^^ should never be empty since we would have deleted the entry otherwise. @@ -381,7 +381,7 @@ impl Leaser> for Pallet { // attempt. // // We bail, not giving any lease and leave it for governance to sort out. - return Err(LeaseError::AlreadyLeased) + return Err(LeaseError::AlreadyLeased); } } else if d.len() == i { // Doesn't exist. This is usual. @@ -488,7 +488,7 @@ impl Leaser> for Pallet { for slot in offset..=offset + period_count { if let Some(Some(_)) = leases.get(slot) { // If there exists any lease period, we exit early and return true. - return true + return true; } } @@ -962,7 +962,7 @@ mod benchmarking { use polkadot_runtime_parachains::paras; use sp_runtime::traits::{Bounded, One}; - use frame_benchmarking::{account, benchmarks, whitelisted_caller, BenchmarkError}; + use frame_benchmarking::v2::*; use crate::slots::Pallet as Slots; @@ -998,10 +998,15 @@ mod benchmarking { (para, leaser) } - benchmarks! { - where_clause { where T: paras::Config } + #[benchmarks( + where T: paras::Config, + )] - force_lease { + mod benchmarks { + use super::*; + + #[benchmark] + fn force_lease() -> Result<(), BenchmarkError> { // If there is an offset, we need to be on that block to be able to do lease things. frame_system::Pallet::::set_block_number(T::LeaseOffset::get() + One::one()); let para = ParaId::from(1337); @@ -1012,23 +1017,32 @@ mod benchmarking { let period_count = 3u32.into(); let origin = T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - }: _(origin, para, leaser.clone(), amount, period_begin, period_count) - verify { - assert_last_event::(Event::::Leased { - para_id: para, - leaser, period_begin, - period_count, - extra_reserved: amount, - total_amount: amount, - }.into()); - } - // Worst case scenario, T on-demand parachains onboard, and C lease holding parachains offboard. - manage_lease_period_start { - // Assume reasonable maximum of 100 paras at any time - let c in 0 .. 100; - let t in 0 .. 100; + #[extrinsic_call] + _(origin as T::RuntimeOrigin, para, leaser.clone(), amount, period_begin, period_count); + + assert_last_event::( + Event::::Leased { + para_id: para, + leaser, + period_begin, + period_count, + extra_reserved: amount, + total_amount: amount, + } + .into(), + ); + Ok(()) + } + + // Worst case scenario, T on-demand parachains onboard, and C lease holding parachains + // offboard. Assume reasonable maximum of 100 paras at any time + #[benchmark] + fn manage_lease_period_start( + c: Linear<0, 100>, + t: Linear<0, 100>, + ) -> Result<(), BenchmarkError> { let period_begin = 1u32.into(); let period_count = 4u32.into(); @@ -1036,9 +1050,7 @@ mod benchmarking { frame_system::Pallet::::set_block_number(T::LeaseOffset::get() + One::one()); // Make T parathreads (on-demand parachains) - let paras_info = (0..t).map(|i| { - register_a_parathread::(i) - }).collect::>(); + let paras_info = (0..t).map(|i| register_a_parathread::(i)).collect::>(); T::Registrar::execute_pending_transitions(); @@ -1053,43 +1065,48 @@ mod benchmarking { T::Registrar::execute_pending_transitions(); // C lease holding parachains are downgrading to on-demand parachains - for i in 200 .. 200 + c { - let (para, leaser) = register_a_parathread::(i); + for i in 200..200 + c { + let (para, _) = register_a_parathread::(i); T::Registrar::make_parachain(para)?; } T::Registrar::execute_pending_transitions(); - for i in 0 .. t { + for i in 0..t { assert!(T::Registrar::is_parathread(ParaId::from(i))); } - for i in 200 .. 200 + c { + for i in 200..200 + c { assert!(T::Registrar::is_parachain(ParaId::from(i))); } - }: { - Slots::::manage_lease_period_start(period_begin); - } verify { + #[block] + { + let _ = Slots::::manage_lease_period_start(period_begin); + } + // All paras should have switched. T::Registrar::execute_pending_transitions(); - for i in 0 .. t { + for i in 0..t { assert!(T::Registrar::is_parachain(ParaId::from(i))); } - for i in 200 .. 200 + c { + for i in 200..200 + c { assert!(T::Registrar::is_parathread(ParaId::from(i))); } + + Ok(()) } // Assume that at most 8 people have deposits for leases on a parachain. // This would cover at least 4 years of leases in the worst case scenario. - clear_all_leases { + #[benchmark] + fn clear_all_leases() -> Result<(), BenchmarkError> { let max_people = 8; let (para, _) = register_a_parathread::(1); // If there is an offset, we need to be on that block to be able to do lease things. frame_system::Pallet::::set_block_number(T::LeaseOffset::get() + One::one()); - for i in 0 .. max_people { + for i in 0..max_people { let leaser = account("lease_deposit", i, 0); let amount = T::Currency::minimum_balance(); T::Currency::make_free_balance_be(&leaser, BalanceOf::::max_value()); @@ -1102,31 +1119,45 @@ mod benchmarking { Slots::::force_lease(origin, para, leaser, amount, period_begin, period_count)?; } - for i in 0 .. max_people { + for i in 0..max_people { let leaser = account("lease_deposit", i, 0); assert_eq!(T::Currency::reserved_balance(&leaser), T::Currency::minimum_balance()); } let origin = T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; - }: _(origin, para) - verify { - for i in 0 .. max_people { + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, para); + + for i in 0..max_people { let leaser = account("lease_deposit", i, 0); assert_eq!(T::Currency::reserved_balance(&leaser), 0u32.into()); } + + Ok(()) } - trigger_onboard { + #[benchmark] + fn trigger_onboard() -> Result<(), BenchmarkError> { // get a parachain into a bad state where they did not onboard let (para, _) = register_a_parathread::(1); - Leases::::insert(para, vec![Some((account::("lease_insert", 0, 0), BalanceOf::::default()))]); + Leases::::insert( + para, + vec![Some(( + account::("lease_insert", 0, 0), + BalanceOf::::default(), + ))], + ); assert!(T::Registrar::is_parathread(para)); let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), para) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(caller), para); + T::Registrar::execute_pending_transitions(); assert!(T::Registrar::is_parachain(para)); + Ok(()) } impl_benchmark_test_suite!( From 4e0fb26f8e2480cb82bfb06220d842f7e6c45a9f Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 27 Jan 2025 18:01:49 +0000 Subject: [PATCH 336/340] Update from franciscoaguirre running command 'fmt' --- Cargo.toml | 4 +- .../pallets/inbound-queue-v2/Cargo.toml | 94 +++++++++---------- .../inbound-queue-v2/fixtures/Cargo.toml | 14 +-- .../fixtures/src/register_token.rs | 4 +- .../fixtures/src/send_token.rs | 4 +- .../fixtures/src/send_token_to_penpal.rs | 4 +- .../inbound-queue-v2/runtime-api/Cargo.toml | 16 ++-- .../inbound-queue-v2/runtime-api/src/lib.rs | 2 +- bridges/snowbridge/primitives/core/Cargo.toml | 2 +- .../snowbridge/primitives/router/Cargo.toml | 6 +- .../primitives/router/src/inbound/v2.rs | 6 +- .../bridge-hub-rococo/src/tests/snowbridge.rs | 6 +- .../bridges/bridge-hub-westend/Cargo.toml | 4 +- .../asset-hub-westend/src/xcm_config.rs | 2 +- .../bridge-hubs/bridge-hub-westend/Cargo.toml | 4 +- .../src/bridge_to_ethereum_config.rs | 5 +- .../bridge-hubs/bridge-hub-westend/src/lib.rs | 7 +- 17 files changed, 93 insertions(+), 91 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3ac7d96922f0..309830e81063 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,10 +49,10 @@ members = [ "bridges/snowbridge/pallets/ethereum-client", "bridges/snowbridge/pallets/ethereum-client/fixtures", "bridges/snowbridge/pallets/inbound-queue", - "bridges/snowbridge/pallets/inbound-queue/fixtures", "bridges/snowbridge/pallets/inbound-queue-v2", "bridges/snowbridge/pallets/inbound-queue-v2/fixtures", "bridges/snowbridge/pallets/inbound-queue-v2/runtime-api", + "bridges/snowbridge/pallets/inbound-queue/fixtures", "bridges/snowbridge/pallets/outbound-queue", "bridges/snowbridge/pallets/outbound-queue/merkle-tree", "bridges/snowbridge/pallets/outbound-queue/runtime-api", @@ -1228,6 +1228,7 @@ smoldot-light = { version = "0.9.0", default-features = false } snowbridge-beacon-primitives = { path = "bridges/snowbridge/primitives/beacon", default-features = false } snowbridge-core = { path = "bridges/snowbridge/primitives/core", default-features = false } snowbridge-ethereum = { path = "bridges/snowbridge/primitives/ethereum", default-features = false } +snowbridge-inbound-queue-v2-runtime-api = { path = "bridges/snowbridge/pallets/inbound-queue-v2/runtime-api", default-features = false } snowbridge-outbound-queue-merkle-tree = { path = "bridges/snowbridge/pallets/outbound-queue/merkle-tree", default-features = false } snowbridge-outbound-queue-runtime-api = { path = "bridges/snowbridge/pallets/outbound-queue/runtime-api", default-features = false } snowbridge-pallet-ethereum-client = { path = "bridges/snowbridge/pallets/ethereum-client", default-features = false } @@ -1236,7 +1237,6 @@ snowbridge-pallet-inbound-queue = { path = "bridges/snowbridge/pallets/inbound-q snowbridge-pallet-inbound-queue-fixtures = { path = "bridges/snowbridge/pallets/inbound-queue/fixtures", default-features = false } snowbridge-pallet-inbound-queue-fixtures-v2 = { path = "bridges/snowbridge/pallets/inbound-queue-v2/fixtures", default-features = false } snowbridge-pallet-inbound-queue-v2 = { path = "bridges/snowbridge/pallets/inbound-queue-v2", default-features = false } -snowbridge-inbound-queue-v2-runtime-api = { path = "bridges/snowbridge/pallets/inbound-queue-v2/runtime-api", default-features = false } snowbridge-pallet-outbound-queue = { path = "bridges/snowbridge/pallets/outbound-queue", default-features = false } snowbridge-pallet-system = { path = "bridges/snowbridge/pallets/system", default-features = false } snowbridge-router-primitives = { path = "bridges/snowbridge/primitives/router", default-features = false } diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml index be3f42d9bd75..49252cc8d195 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue-v2/Cargo.toml @@ -15,80 +15,80 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { optional = true, workspace = true, default-features = true } +alloy-core = { workspace = true, features = ["sol-types"] } codec = { features = ["derive"], workspace = true } -scale-info = { features = ["derive"], workspace = true } hex-literal = { optional = true, workspace = true, default-features = true } log = { workspace = true } -alloy-core = { workspace = true, features = ["sol-types"] } +scale-info = { features = ["derive"], workspace = true } +serde = { optional = true, workspace = true, default-features = true } frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } pallet-balances = { workspace = true } sp-core = { workspace = true } -sp-std = { workspace = true } sp-io = { workspace = true } sp-runtime = { workspace = true } +sp-std = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } -snowbridge-core = { workspace = true } -snowbridge-router-primitives = { workspace = true } snowbridge-beacon-primitives = { workspace = true } +snowbridge-core = { workspace = true } snowbridge-pallet-inbound-queue-fixtures-v2 = { optional = true, workspace = true } +snowbridge-router-primitives = { workspace = true } [dev-dependencies] frame-benchmarking = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -snowbridge-pallet-ethereum-client = { workspace = true, default-features = true } -hex-literal = { workspace = true, default-features = true } hex = { workspace = true, default-features = true } +hex-literal = { workspace = true, default-features = true } +snowbridge-pallet-ethereum-client = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } [features] default = ["std"] std = [ - "alloy-core/std", - "codec/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", - "log/std", - "pallet-balances/std", - "scale-info/std", - "serde", - "snowbridge-beacon-primitives/std", - "snowbridge-core/std", - "snowbridge-pallet-inbound-queue-fixtures-v2?/std", - "snowbridge-router-primitives/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "sp-std/std", - "xcm-executor/std", - "xcm-builder/std", - "xcm/std", + "alloy-core/std", + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", + "scale-info/std", + "serde", + "snowbridge-beacon-primitives/std", + "snowbridge-core/std", + "snowbridge-pallet-inbound-queue-fixtures-v2?/std", + "snowbridge-router-primitives/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", ] runtime-benchmarks = [ - "frame-benchmarking", - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "hex-literal", - "pallet-balances/runtime-benchmarks", - "snowbridge-core/runtime-benchmarks", - "snowbridge-pallet-ethereum-client/runtime-benchmarks", - "snowbridge-pallet-inbound-queue-fixtures-v2/runtime-benchmarks", - "snowbridge-router-primitives/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "xcm-executor/runtime-benchmarks", + "frame-benchmarking", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal", + "pallet-balances/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", + "snowbridge-pallet-ethereum-client/runtime-benchmarks", + "snowbridge-pallet-inbound-queue-fixtures-v2/runtime-benchmarks", + "snowbridge-router-primitives/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", ] try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "pallet-balances/try-runtime", - "snowbridge-pallet-ethereum-client/try-runtime", - "sp-runtime/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "snowbridge-pallet-ethereum-client/try-runtime", + "sp-runtime/try-runtime", ] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml index 05a4a473a28a..1e37589242fb 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/Cargo.toml @@ -16,19 +16,19 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] hex-literal = { workspace = true, default-features = true } +snowbridge-beacon-primitives = { workspace = true } +snowbridge-core = { workspace = true } sp-core = { workspace = true } sp-std = { workspace = true } -snowbridge-core = { workspace = true } -snowbridge-beacon-primitives = { workspace = true } [features] default = ["std"] std = [ - "snowbridge-beacon-primitives/std", - "snowbridge-core/std", - "sp-core/std", - "sp-std/std", + "snowbridge-beacon-primitives/std", + "snowbridge-core/std", + "sp-core/std", + "sp-std/std", ] runtime-benchmarks = [ - "snowbridge-core/runtime-benchmarks", + "snowbridge-core/runtime-benchmarks", ] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs index 5ab12490d040..e49e2309318a 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/register_token.rs @@ -5,14 +5,14 @@ use hex_literal::hex; use snowbridge_beacon_primitives::{ - types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, + types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, }; use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; use sp_core::U256; use sp_std::vec; pub fn make_register_token_message() -> InboundQueueFixture { - InboundQueueFixture { + InboundQueueFixture { message: Message { event_log: Log { address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs index 52da807efd31..0182b03bc2d5 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token.rs @@ -5,14 +5,14 @@ use hex_literal::hex; use snowbridge_beacon_primitives::{ - types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, + types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, }; use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; use sp_core::U256; use sp_std::vec; pub fn make_send_token_message() -> InboundQueueFixture { - InboundQueueFixture { + InboundQueueFixture { message: Message { event_log: Log { address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs index 4b4e78b63513..7989ea8e3f07 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/fixtures/src/send_token_to_penpal.rs @@ -5,14 +5,14 @@ use hex_literal::hex; use snowbridge_beacon_primitives::{ - types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, + types::deneb, AncestryProof, BeaconHeader, ExecutionProof, VersionedExecutionPayloadHeader, }; use snowbridge_core::inbound::{InboundQueueFixture, Log, Message, Proof}; use sp_core::U256; use sp_std::vec; pub fn make_send_token_to_penpal_message() -> InboundQueueFixture { - InboundQueueFixture { + InboundQueueFixture { message: Message { event_log: Log { address: hex!("eda338e4dc46038493b885327842fd3e301cab39").into(), diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml index c9c38a44dd54..d57b99f18cf8 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/Cargo.toml @@ -16,19 +16,19 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] frame-support = { workspace = true, default-features = false } -sp-api = { workspace = true, default-features = false } -sp-runtime = { workspace = true, default-features = false } snowbridge-core = { workspace = true, default-features = false } snowbridge-router-primitives = { workspace = true, default-features = false } +sp-api = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } xcm = { workspace = true, default-features = false } [features] default = ["std"] std = [ - "frame-support/std", - "snowbridge-core/std", - "snowbridge-router-primitives/std", - "sp-runtime/std", - "sp-api/std", - "xcm/std", + "frame-support/std", + "snowbridge-core/std", + "snowbridge-router-primitives/std", + "sp-api/std", + "sp-runtime/std", + "xcm/std", ] diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs index d899f7477b45..e406f1613b43 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/runtime-api/src/lib.rs @@ -4,8 +4,8 @@ use frame_support::traits::tokens::Balance as BalanceT; use snowbridge_router_primitives::inbound::v2::Message; -use xcm::latest::Xcm; use sp_runtime::DispatchError; +use xcm::latest::Xcm; sp_api::decl_runtime_apis! { pub trait InboundQueueApiV2 where Balance: BalanceT diff --git a/bridges/snowbridge/primitives/core/Cargo.toml b/bridges/snowbridge/primitives/core/Cargo.toml index 7aff12dbd2d0..42f0ac7f3c66 100644 --- a/bridges/snowbridge/primitives/core/Cargo.toml +++ b/bridges/snowbridge/primitives/core/Cargo.toml @@ -14,9 +14,9 @@ workspace = true [dependencies] codec = { workspace = true } hex-literal = { workspace = true, default-features = true } +log = { workspace = true } scale-info = { features = ["derive"], workspace = true } serde = { optional = true, features = ["alloc", "derive"], workspace = true } -log = { workspace = true } polkadot-parachain-primitives = { workspace = true } xcm = { workspace = true } diff --git a/bridges/snowbridge/primitives/router/Cargo.toml b/bridges/snowbridge/primitives/router/Cargo.toml index 92756b699cd9..bc790f420e2a 100644 --- a/bridges/snowbridge/primitives/router/Cargo.toml +++ b/bridges/snowbridge/primitives/router/Cargo.toml @@ -12,10 +12,10 @@ categories = ["cryptography::cryptocurrencies"] workspace = true [dependencies] +alloy-core = { workspace = true, features = ["sol-types"] } codec = { workspace = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } -alloy-core = { workspace = true, features = ["sol-types"] } frame-support = { workspace = true } frame-system = { workspace = true } @@ -25,13 +25,13 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } xcm = { workspace = true } -xcm-executor = { workspace = true } xcm-builder = { workspace = true } +xcm-executor = { workspace = true } snowbridge-core = { workspace = true } -hex-literal = { workspace = true, default-features = true } hex = { workspace = true, default-features = false } +hex-literal = { workspace = true, default-features = true } [dev-dependencies] diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index b894da5f7a89..1f9877841980 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -5,6 +5,7 @@ use codec::{Decode, DecodeLimit, Encode}; use core::marker::PhantomData; use frame_support::PalletError; +use hex; use scale_info::TypeInfo; use snowbridge_core::TokenId; use sp_core::{Get, RuntimeDebug, H160, H256}; @@ -14,7 +15,6 @@ use xcm::{ prelude::{Asset as XcmAsset, Junction::AccountKey20, *}, MAX_XCM_DECODE_DEPTH, }; -use hex; const LOG_TARGET: &str = "snowbridge-router-primitives"; @@ -172,7 +172,9 @@ where if let Ok(claimer) = Junction::decode(&mut claimer.as_ref()) { let claimer_location: Location = Location::new(0, [claimer.into()]); refund_surplus_to = claimer_location.clone(); - instructions.push(SetHints { hints: vec![AssetClaimer {location: claimer_location }].try_into().unwrap() }); // TODO + instructions.push(SetHints { + hints: vec![AssetClaimer { location: claimer_location }].try_into().unwrap(), + }); // TODO } } diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs index ea2eef545ea0..a59fef45a6b6 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs @@ -25,10 +25,8 @@ use snowbridge_pallet_inbound_queue_fixtures::{ }; use snowbridge_pallet_system; use snowbridge_router_primitives::inbound::{ - EthereumLocationsConverterFor -}; -use snowbridge_router_primitives::inbound::v1::{ - Command, Destination, MessageV1, VersionedMessage, + v1::{Command, Destination, MessageV1, VersionedMessage}, + EthereumLocationsConverterFor, }; use sp_core::H256; use sp_runtime::{DispatchError::Token, TokenError::FundsUnavailable}; diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml index 2010a46f1886..62922b97e35c 100644 --- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml @@ -12,8 +12,8 @@ workspace = true [dependencies] codec = { workspace = true } -hex-literal = { workspace = true, default-features = true } hex = { workspace = true, default-features = true } +hex-literal = { workspace = true, default-features = true } log = { workspace = true } scale-info = { workspace = true } @@ -42,9 +42,9 @@ bridge-hub-westend-runtime = { workspace = true } cumulus-pallet-xcmp-queue = { workspace = true } emulated-integration-tests-common = { workspace = true } parachains-common = { workspace = true, default-features = true } +penpal-emulated-chain = { workspace = true } rococo-westend-system-emulated-network = { workspace = true } testnet-parachains-constants = { features = ["rococo", "westend"], workspace = true, default-features = true } -penpal-emulated-chain = { workspace = true } # Snowbridge snowbridge-core = { workspace = true } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs index 19e6249f56bc..e4acca2e90d3 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs @@ -650,7 +650,7 @@ pub mod bridging { use assets_common::matching::FromNetwork; use sp_std::collections::btree_set::BTreeSet; use testnet_parachains_constants::westend::snowbridge::{ - EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX_V1, INBOUND_QUEUE_PALLET_INDEX_V2 + EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX_V1, INBOUND_QUEUE_PALLET_INDEX_V2, }; parameter_types! { diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml index 55e37becc763..e0f4eeb52276 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml @@ -109,11 +109,11 @@ pallet-xcm-bridge-hub = { workspace = true } # Ethereum Bridge (Snowbridge) snowbridge-beacon-primitives = { workspace = true } snowbridge-core = { workspace = true } +snowbridge-inbound-queue-v2-runtime-api = { workspace = true } snowbridge-outbound-queue-runtime-api = { workspace = true } snowbridge-pallet-ethereum-client = { workspace = true } snowbridge-pallet-inbound-queue = { workspace = true } snowbridge-pallet-inbound-queue-v2 = { workspace = true } -snowbridge-inbound-queue-v2-runtime-api = { workspace = true } snowbridge-pallet-outbound-queue = { workspace = true } snowbridge-pallet-system = { workspace = true } snowbridge-router-primitives = { workspace = true } @@ -191,8 +191,8 @@ std = [ "serde_json/std", "snowbridge-beacon-primitives/std", "snowbridge-core/std", - "snowbridge-outbound-queue-runtime-api/std", "snowbridge-inbound-queue-v2-runtime-api/std", + "snowbridge-outbound-queue-runtime-api/std", "snowbridge-pallet-ethereum-client/std", "snowbridge-pallet-inbound-queue/std", "snowbridge-pallet-outbound-queue/std", diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs index 519775fe6908..106175e385de 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs @@ -30,7 +30,10 @@ use sp_core::H160; use testnet_parachains_constants::westend::{ currency::*, fee::WeightToFee, - snowbridge::{EthereumLocation, EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX_V1, INBOUND_QUEUE_PALLET_INDEX_V2}, + snowbridge::{ + EthereumLocation, EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX_V1, + INBOUND_QUEUE_PALLET_INDEX_V2, + }, }; use crate::xcm_config::RelayNetwork; diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs index face6e3ef1a3..c62959e6ff9e 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs @@ -42,17 +42,16 @@ use bridge_runtime_common::extensions::{ }; use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; use cumulus_primitives_core::{ClaimQueueOffset, CoreSelector, ParaId}; +use frame_support::traits::Contains; +use snowbridge_router_primitives::inbound::v2::Message; use sp_api::impl_runtime_apis; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_runtime::{ generic, impl_opaque_keys, traits::Block as BlockT, transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, + ApplyExtrinsicResult, DispatchError, }; -use frame_support::traits::Contains; -use snowbridge_router_primitives::inbound::v2::Message; -use sp_runtime::DispatchError; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; From cece49a02635f40c2b08ab5e26188fd10496a45a Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Tue, 28 Jan 2025 10:54:18 +0200 Subject: [PATCH 337/340] Update bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs Co-authored-by: Francisco Aguirre --- bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs index 4089f609485c..ac4302b0111c 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/test.rs @@ -57,7 +57,7 @@ fn test_submit_happy_path() { RuntimeEvent::InboundQueue(Event::MessageReceived { nonce, ..}) if nonce == 1 )), - "no event emit." + "no event emitted." ); }); } From 5b281261f949cd0c15fd6d223b550221e5b23785 Mon Sep 17 00:00:00 2001 From: Clara van Staden Date: Tue, 28 Jan 2025 10:54:25 +0200 Subject: [PATCH 338/340] Update bridges/snowbridge/primitives/router/src/inbound/v2.rs Co-authored-by: Francisco Aguirre --- bridges/snowbridge/primitives/router/src/inbound/v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/primitives/router/src/inbound/v2.rs b/bridges/snowbridge/primitives/router/src/inbound/v2.rs index 1f9877841980..6a755c6f8115 100644 --- a/bridges/snowbridge/primitives/router/src/inbound/v2.rs +++ b/bridges/snowbridge/primitives/router/src/inbound/v2.rs @@ -225,7 +225,7 @@ where let appendix = vec![ RefundSurplus, - // Refund excess fees to the claimer, if present, otherwise the relayer + // Refund excess fees to the claimer, if present, otherwise to the relayer. DepositAsset { assets: Wild(AllOf { id: AssetId(fee_asset.into()), fun: WildFungible }), beneficiary: refund_surplus_to, From bec9f2c5214b114fbe9241fd81881285ceba3b13 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 28 Jan 2025 10:58:21 +0200 Subject: [PATCH 339/340] pr comments --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index c9d6031c2355..ee7130285d99 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -114,9 +114,6 @@ pub mod pallet { type Helper: BenchmarkHelper; } - #[pallet::hooks] - impl Hooks> for Pallet {} - #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -194,7 +191,6 @@ pub mod pallet { /// The current operating mode of the pallet. #[pallet::storage] - #[pallet::getter(fn operating_mode)] pub type OperatingMode = StorageValue<_, BasicOperatingMode, ValueQuery>; #[pallet::call] @@ -233,12 +229,11 @@ pub mod pallet { // d. The execution cost on destination chain(if any) // e. The reward - // Attempt to forward XCM to AH - // Set nonce flag to true log::info!(target: "snowbridge-inbound-queue:v2","💫 setting nonce to {:?}", envelope.nonce); Nonce::::set(envelope.nonce.into()); + // Attempt to forward XCM to AH let message_id = Self::send_xcm(xcm, T::AssetHubParaId::get())?; Self::deposit_event(Event::MessageReceived { nonce: envelope.nonce, message_id }); From 687e45e5661633bd8631084e085c48f4be9e0942 Mon Sep 17 00:00:00 2001 From: claravanstaden Date: Tue, 28 Jan 2025 14:21:07 +0200 Subject: [PATCH 340/340] replace operating mode --- bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs index ee7130285d99..d6249aff30f8 100644 --- a/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs +++ b/bridges/snowbridge/pallets/inbound-queue-v2/src/lib.rs @@ -200,7 +200,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::submit())] pub fn submit(origin: OriginFor, message: Message) -> DispatchResult { let who = ensure_signed(origin.clone())?; - ensure!(!Self::operating_mode().is_halted(), Error::::Halted); + ensure!(!OperatingMode::::get().is_halted(), Error::::Halted); // submit message to verifier for verification T::Verifier::verify(&message.event_log, &message.proof)
Release notes

Sourced from lycheeverse/lychee-action's releases.

Version 2.1.0

What's Changed

New Contributors

Full Changelog: https://github.com/lycheeverse/lychee-action/compare/v2...v2.1.0